The MapToHUD function calculates screen coordinates based on a directional vector, the view rotation, the FOV and the target Canvas.
Note: In UT2003 you can and should use the WorldToScreen function of the Canvas or Interaction class.
Contents
Parameters
- out vector Result
- The X and Y components of this vector will contain the absolute canvas coordinates calculated by the function.
- rotator ViewRotation
- The rotation of the camera.
- float FOV
- The FOV of the camera. This value must be specified in degrees like the PlayerPawn's FOVAngle property.
- vector TargetDir
- A vector pointing from the camera's location to the location which should be converted to screen coordinates. For PlayerPawns this is
PlayerPawn.Location + PlayerPawn.EyeHeight * vect(0,0,1)
. - Canvas Canvas
- The Canvas the coordinates are calculated for.
Return Value
The function returns whether the coordinates it calculated are on the screen or not. This is only reliable for FOV values less than 180°.
Code
/******************************************************************************** * Converts a given directional vector to canvas coordinates. * * X and Y values of the returned vector contain absolute coordinates. * * The function returns, whether the target direction is visible for the player. * * Created by Wormbo * ********************************************************************************/ simulated function bool MapToHUD(out vector Result, rotator ViewRotation, float FOV, vector TargetDir, Canvas Canvas) { local float TanFOVx, TanFOVy; local float TanX, TanY; local float dx, dy; local vector X, Y, Dir, XY; TanFOVx = Tan(FOV * Pi / 360); TanFOVy = (Canvas.ClipY / Canvas.ClipX) * TanFOVx; GetAxes(ViewRotation, Dir, X, Y); Dir *= TargetDir dot Dir; XY = TargetDir - Dir; dx = XY dot X; dy = XY dot Y; TanX = dx / VSize(dir); TanY = dy / VSize(dir); Result.X = Canvas.ClipX * 0.5 * (1 + TanX / TanFOVx); Result.Y = Canvas.ClipY * 0.5 * (1 - TanY / TanFOVy); return Dir dot vector(ViewRotation) > 0 && Result.X == FClamp(Result.X, Canvas.OrgX, Canvas.ClipX) && Result.Y == FClamp(Result.Y, Canvas.OrgY, Canvas.ClipY); }
Example
In this example CurrentTarget is a targeted actor (e.g. another player) and Reticle is a Texture (UT) which will be drawn with its center at the target's location. It will only be drawn when the target is on screen (return value of MapToHUD) and there's no level geometry between the point of vision and the target (FastTrace).
Reticle.USize and .VSize are the width and height of the texture.
simulated function PostRender(canvas Canvas) { local vector dir, POV, DrawPos; local PlayerPawn P; P = PlayerPawn(Owner); if ( P == None || CurrentTarget == None ) return; POV = Owner.Location + vect(0,0,1) * P.EyeHeight; Dir = CurrentTarget.Location - POV; // direction to target if ( MapToHUD(DrawPos, P.ViewRotation, P.FOVAngle, Dir, Canvas) && FastTrace(CurrentTarget.Location, POV) ) { Canvas.SetPos(DrawPos.X - Reticle.USize * 0.5, DrawPos.Y - Reticle.VSize * 0.5); Canvas.DrawIcon(Reticle, 1.0); } }
WorldToScreen
The MapToHUD function can be used to create UT2003's WorldToScreen function for UT. (In case you prefer using that.)
/******************************************************************************** * Converts a given world location to canvas coordinates. * * X and Y values of the returned vector contain absolute coordinates. * * Created by Wormbo * ********************************************************************************/ simulated function vector WorldToScreen(Canvas C, vector WorldLoc) { local vector CamLoc, ScreenLoc; local rotator CamRot; local Actor Camera; C.ViewPort.Actor.PlayerCalcView(Camera, CamLoc, CamRot); MapToHUD(ScreenLoc, CamRot, C.ViewPort.Actor.FOVAngle, Normal(WorldLoc - CamLoc), C); return ScreenLoc; }
Alternate version
Here's an alternate version that I cooked up for Esc. I put it in the base HUD class:
// Created by M.C. Spanky, aka Martin C. Martin, for Esc (www.esconline.org) simulated static function bool WorldToScreen(Vector WorldLocation, Pawn ThePlayer, float ScreenWidth, float ScreenHeight, out float X, out float Y) { local vector EyePos, RelativeToPlayer; local float Scale; EyePos = ThePlayer.Location; EyePos.Z += ThePlayer.BaseEyeHeight; // Maybe ThePlayer.EyeHeight instead? RelativeToPlayer = (WorldLocation - EyePos) << ThePlayer.ViewRotation; if (RelativeToPlayer.X < 0.01) return false; Scale = (ScreenWidth / 2) / Tan(ThePlayer.FovAngle/2/180*Pi); X = RelativeToPlayer.Y / RelativeToPlayer.X * Scale + ScreenWidth / 2; Y = - RelativeToPlayer.Z / RelativeToPlayer.X * Scale + ScreenHeight / 2; return true; }
To call it from the HUD, you could do something like this:
local float X, Y;
WorldToScreen(<trace result>, Pawn(Owner), Canvas.ClipX, Canvas.ClipY, X, Y);
A couple notes:
-It returns "false" if the WorldLocation is behind the player (or so close that we risk a division by zero.)-
-This assumes that you're in first person view, i.e. not in a cut scene or bBehindView. It should be easy enough to find the "true" camera location & rotation & FOV in all cases, by looking at the player's ViewTarget. I'm unsure of the details, so I'll leave that to you. :)-
-Canvas.Clip[XY] don't always equal the screen resolution, although they usually do, and I can't find a better pair of vars.-
Floating Names
Here's an example of how to use it, some really simple "floating names" code. Put this in your HUD:
// Created by M.C. Spanky, aka Martin C. Martin, for Esc (www.esconline.org) simulated function PostRender( canvas Canvas ) { local Pawn thisPawn; local float X, Y; local float W, H; local bool bOnScreen; local vector worldPosition; Super.PostRender(Canvas); for (thisPawn = Level.PawnList; thisPawn != None; thisPawn = thisPawn.NextPawn) { worldPosition = thisPawn.Location; worldPosition.Z += thisPawn.CollisionHeight; bOnScreen = WorldToScreen(worldPosition, Pawn(Owner), Canvas.ClipX, Canvas.ClipY, X, Y); if (bOnScreen && X > 0 && X < Canvas.ClipX && Y > 0 && Y < Canvas.ClipY) { Canvas.SetPos(0, 0); Canvas.TextSize(thisPawn.MenuName, W, H); Canvas.SetPos(X - W/2, Y - H/2); Canvas.DrawText(thisPawn.MenuName, false); } } }
(If you already have a PostRender(), merge it with that.) Start a level and spawn some Kralls or Nalis or whatever puts a glide in your stride and a dip in your hip. The name will be in the right place no matter whether they're above you and looking up or below you and looking down. You'll even see your own name above you if you look up. :)
Xian: Thought I should mention that the above code would not work online as Level.PawnList is inaccessible client-side.
MouseTrace
Here's a function that goes the other way: given an X and Y on the screen, it figures out what direction in space this corresponds to, and does a trace to find the first actor along that direction. Esc uses this so that you can click on another player to select them. This one's written to be in PlayerPawn, but you could modify it to take the player as an arg, as above.
simulated function Actor MouseTrace(float MouseX, float MouseY, float ScreenWidth, float ScreenHeight) { local Actor Other; local vector HitLocation, HitNormal, StartTrace, EndTrace; local vector MouseDirection; // This is right, we divide by ScreenWidth/2 for both x and y. MouseDirection.X = 1 / tan(FovAngle/2/180*Pi); MouseDirection.Y = (MouseX - ScreenWidth / 2) / (ScreenWidth / 2); MouseDirection.Z = -(MouseY - ScreenHeight / 2) / (ScreenWidth / 2); MouseDirection = Normal(MouseDirection); StartTrace = Location; StartTrace.Z += BaseEyeHeight; // Spawn( class'UnrealShare.FatRing',,,StartTrace + 500 *(MouseDirection >> ViewRotation) ); EndTrace = StartTrace + (MouseDirection >> ViewRotation) * 1000.0; Other = Trace(HitLocation, HitNormal, EndTrace, StartTrace, true); return Other; }
The UT2003 class Interaction comes with the functions ScreenToWorld and WorldToScreen. These functions are meant to do the above conversions.
A word of warning, if you override the CalcPlayerView function and don't set ViewActor to something then the engine will crash when calling ScreenToWorld and WorldToScreen.
Foxpaw: I had a question but I realize now that this was a function you wrote yourself, not one that epic included.
Wormbo: To quote your original question:
Why do you suppose epic used a different computation to align the targeting reticles on the redeemer view? I'm curious if this method might have a drawback that makes it unfeasable in that application?
The method Epic used for the Redeemer was way too inaccurate for what I needed in Rockets UT, so I wrote MapToHUD. The only drawback of this function might be the difficult syntax, but I never saw any other accurate world -> screen conversion before.
Daid303: I'm wondering, there are 2 totaly diffrent methodes used above here, but they both seem to give the same result. Is there a diffrence? in accuracy maybe, or speed?
UArch its a little unrelated but ive adapted the mousetrace function for another use, is there any way to have it trace out in a fisheye-style fov instead? :0
Xian: Although this is trivial, I was wondering... Isn't it better (optimization-wise) to do
if (FastTrace(CurrentTarget.Location,POV) && MapToHUD(DrawPos,P.ViewRotation,P.FOVAngle,Dir,Canvas))
instead of
if ( MapToHUD(DrawPos, P.ViewRotation, P.FOVAngle, Dir, Canvas) && FastTrace(CurrentTarget.Location, POV) )
That way, the game will only calculate if the Target is in front of us, instead of bothering to do all the coord coversions/calculus and then saying "ah nevermind, the dude isn't in front of us".
Wormbo: (Fast)Tracing is an expensive operation. It really depends on whether FastTrace() is faster than MapToHud(), but I'm pretty sure it's not.
Xian: Ah, I see what you mean :) Thanks.
Related Classes
- Canvas (UT)
- PlayerPawn
- Canvas (UT2003)