// screen projection and other functions from SWWM GZ Struct dt_ProjectionData { double tanfovx, tanfovy; Vector3 viewpos, z, x, y; int viewx, viewy, vieww, viewh; } Class dt_Utility { // cache some data that requires trig and quat math static clearscope void PrepareProjData( dt_ProjectionData &d, Vector3 viewpos, double angle, double pitch, double roll, double fov ) { // store for internal use d.viewpos = viewpos; // precalc vfov/hfov tangents // (vfov in gzdoom has a small quirk to it) double aspect = Screen.GetAspectRatio(); double fovratio = (aspect>=1.3)?1.333333:aspect; d.tanfovy = tan(clamp(fov,5,170)/2.)/fovratio; d.tanfovx = d.tanfovy*aspect; // precalc view-space axes // (don't forget pixel stretch, very important) Quat r = Quat.FromAngles(angle,pitch,roll); d.z = r*(1.,0.,0.); d.x = r*(0.,1.,0.); d.y = r*(0.,0.,level.pixelstretch); // precalc view origin and clip int sblocks = CVar.FindCVar('screenblocks').GetInt(); let [viewx, viewy, vieww, viewh] = Screen.GetViewWindow(); int sh = Screen.GetHeight(); int h = sh; if ( sblocks < 10 ) h = (sblocks*sh/10)&~7; int bottom = sh-(h+viewy-((h-viewh)/2)); d.viewx = viewx; d.viewy = sh-bottom-h; d.vieww = vieww; d.viewh = h; } // simple projection without matrices, translated from old UnrealScript work // bFast: quit early if point is behind screen, for cases where behind-view coords are not really needed static clearscope Vector3 ProjectPoint( dt_ProjectionData d, Vector3 worldpos, bool bFast = true ) { Vector3 tdir = worldpos-d.viewpos; // early bail, skip behind-view points if ( bFast && (d.z dot tdir <= 0.) ) return (0.,0.,0.); double dist = tdir.length(); // points are pretty much equal, skip projection if ( dist <= double.epsilon ) return (0.,0.,0.); tdir /= dist; Vector3 dir = d.z*(tdir dot d.z); // I don't understand this math, but it works? Vector3 xy = tdir-dir; double dx = xy dot d.x; double dy = xy dot d.y; double dlen = dir.length(); // guard against division by zero here? if ( dlen <= double.epsilon ) return (0.,0.,0.); double tanx = dx/dlen; double tany = dy/dlen; return (1.-tanx/d.tanfovx,1.-tany/d.tanfovy,dir dot d.z); } static clearscope Vector2 NDCToViewport( dt_ProjectionData d, Vector3 cpos ) { return (d.viewx+(cpos.x*.5*d.vieww),d.viewy+(cpos.y*.5*d.viewh)); } // checks if a point is inside the viewport static clearscope bool TestScreenBounds( dt_ProjectionData d, Vector2 vpos ) { return ((vpos.x == clamp(vpos.x,d.viewx,d.viewx+d.vieww)) && (vpos.y == clamp(vpos.y,d.viewy,d.viewy+d.viewh))); } static clearscope Vector3 Vec3FromAngle( double angle, double pitch ) { return (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch)); } static clearscope Vector3 CircleOffset( Vector3 y, Vector3 z, double angle, double radius ) { return (y*cos(angle)*radius+z*sin(angle)*radius); } static clearscope Vector3 ConeSpread( Vector3 x, Vector3 y, Vector3 z, double angle, double spread ) { return (x+y*cos(angle)*spread+z*sin(angle)*spread).unit(); } static clearscope Vector3, Vector3, Vector3 GetAxes( double angle, double pitch, double roll ) { Vector3 x = (1,0,0), y = (0,-1,0), z = (0,0,1); Quat r = Quat.FromAngles(angle,pitch,roll); return r*x, r*y, r*z; } static double Normalize180( double angle ) { angle = ((angle%360.)+360.)%360.; return (angle>180.)?(angle-360.):(angle); } static double, double, double ToAngles( Quat q ) { double angle = 0., pitch = 0., roll = 0.; double stest = q.z*q.x-q.w*q.y; double angY = 2.*(q.w*q.z+q.x*q.y); double angX = 1.-2.*(q.y*q.y+q.z*q.z); if ( stest < -.4999995 ) { angle = atan2(angY,angX); pitch = 90.; roll = Normalize180(angle+(2.*atan2(q.x,q.w))); } else if ( stest > .4999995 ) { angle = atan2(angY,angX); pitch = -90.; roll = Normalize180(angle+(2.*atan2(q.x,q.w))); } else { angle = atan2(angY,angX); pitch = -asin(2.*stest); roll = atan2(2.*(q.w*q.x+q.y*q.z),1.-2.*(q.x*q.x+q.y*q.y)); } return angle, pitch, roll; } // for aiming and shooting static Vector3 GetPlayerViewDir( Actor player ) { Quat r = Quat.FromAngles(player.angle,player.pitch,player.roll); return r*(1,0,0); } static play Vector3 GetPlayerAimDir( Actor player ) { FTranslatedLineTarget t; double pitch = player.BulletSlope(t); Quat r; if ( !t.linetarget ) r = Quat.FromAngles(player.angle,player.pitch,player.roll); else r = Quat.FromAngles(player.angle,pitch,player.roll); return r*(1,0,0); } static Vector3, Vector3, Vector3 GetPlayerAxes( Actor player ) { Quat r = Quat.FromAngles(player.angle,player.pitch,player.roll); return r*(1,0,0), r*(0,-1,0), r*(0,0,1); } static play Vector3, Vector3, Vector3 GetPlayerAxesAutoAimed( Actor player ) { FTranslatedLineTarget t; double pitch = player.BulletSlope(t); Quat r; if ( !t.linetarget ) r = Quat.FromAngles(player.angle,player.pitch,player.roll); else r = Quat.FromAngles(player.angle,pitch,player.roll); return r*(1,0,0), r*(0,-1,0), r*(0,0,1); } static Vector3 GetPlayerEye( Actor player ) { if ( !player.viewpos ) return player.Vec2OffsetZ(0,0,player.player.viewz); if ( player.viewpos.flags&VPSF_ABSOLUTEPOS ) return player.viewpos.offset; Vector3 origin = player.Vec2OffsetZ(0,0,player.player.viewz); if ( player.viewpos.flags&VPSF_ABSOLUTEOFFSET ) return level.Vec3Offset(origin,player.viewpos.offset); Quat r = Quat.FromAngles(player.angle,player.pitch,player.roll); return level.Vec3Offset(origin,r*player.viewpos.offset); } static Vector3 GetFireOffset( Actor player, double x, double y, double z ) { Vector3 origin = GetPlayerEye(player); Quat r = Quat.FromAngles(player.angle,player.pitch,player.roll); return level.Vec3Offset(origin,r*(x,-y,z)); } }