402 lines
12 KiB
Text
402 lines
12 KiB
Text
// math stuff + gutamatics caching
|
|
|
|
Struct SWWMProjectionData
|
|
{
|
|
swwm_GM_Matrix wtc;
|
|
int viewx, viewy, vieww, viewh;
|
|
}
|
|
|
|
extend Class SWWMUtility
|
|
{
|
|
// gutamatics caching
|
|
static void PrepareProjData( SWWMProjectionData &d, Vector3 viewpos, double angle, double pitch, double roll, double fov )
|
|
{
|
|
double aspect = Screen.GetAspectRatio();
|
|
// vertical fov
|
|
double fovratio = (aspect>=1.3)?1.333333:aspect;
|
|
double fovy = 2.*atan(tan(clamp(fov,5,170)/2.)/fovratio);
|
|
// world→clip matrix
|
|
swwm_GM_Matrix view = swwm_GM_Matrix.view(viewpos,angle,pitch,roll);
|
|
swwm_GM_Matrix perp = swwm_GM_Matrix.perspective(fovy,aspect,5,65535);
|
|
d.wtc = perp.multiplyMatrix(view);
|
|
// screen coord data
|
|
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;
|
|
}
|
|
|
|
static Vector3 ProjectPoint( SWWMProjectionData d, Vector3 worldpos )
|
|
{
|
|
return d.wtc.multiplyVector3(worldpos).asVector3();
|
|
}
|
|
|
|
static Vector2 NDCToViewport( SWWMProjectionData d, Vector3 ndc )
|
|
{
|
|
return (d.viewx,d.viewy)+(((ndc.x+1)*d.vieww)/2,((-ndc.y+1)*d.viewh)/2);
|
|
}
|
|
|
|
// checks if a point is inside the viewport
|
|
static bool TestScreenBounds( SWWMProjectionData 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)));
|
|
}
|
|
|
|
// less code duplication
|
|
static void AdjustClean_1( double &x, double &y )
|
|
{
|
|
x = (x-160)*CleanXFac_1+(Screen.GetWidth()*.5);
|
|
y = (y-100)*CleanYFac_1+(Screen.GetHeight()*.5);
|
|
}
|
|
static void AdjustClean_1x( double &x )
|
|
{
|
|
x = (x-160)*CleanXFac_1+(Screen.GetWidth()*.5);
|
|
}
|
|
static void AdjustClean_1y( double &y )
|
|
{
|
|
y = (y-100)*CleanYFac_1+(Screen.GetHeight()*.5);
|
|
}
|
|
|
|
// Vector/Axis utility functions
|
|
static Vector3 Vec3FromAngles( double angle, double pitch )
|
|
{
|
|
return (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
|
|
}
|
|
static Vector3 CircleOffset( Vector3 y, Vector3 z, double angle, double radius )
|
|
{
|
|
return y*cos(angle)*radius+z*sin(angle)*radius;
|
|
}
|
|
static Vector3 ConeSpread( Vector3 x, Vector3 y, Vector3 z, double angle, double spread )
|
|
{
|
|
return (x+y*cos(angle)*spread+z*sin(angle)*spread).unit();
|
|
}
|
|
static Vector3 RotateVector3( Vector3 v, double angle )
|
|
{
|
|
Vector2 v2d = Actor.RotateVector(v.xy,angle);
|
|
return (v2d.x,v2d.y,v.z);
|
|
}
|
|
static Vector3 AngleToVector3( double angle, double length = 1. )
|
|
{
|
|
Vector2 v2d = Actor.AngleToVector(angle,length);
|
|
return (v2d.x,v2d.y,0);
|
|
}
|
|
|
|
// new GetAxes
|
|
static Vector3, Vector3, Vector3 GetAxes( double angle, double pitch, double roll )
|
|
{
|
|
Quat r = Quat.FromAngles(angle,pitch,roll);
|
|
return r*(1,0,0), r*(0,-1,0), r*(0,0,1);
|
|
}
|
|
|
|
// included here until Gutamatics updates to use native quaternions
|
|
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 = swwm_GM_GlobalMaths.Normalize180(angle+(2.*atan2(q.x,q.w)));
|
|
}
|
|
else if ( stest > .4999995 )
|
|
{
|
|
angle = atan2(angY,angX);
|
|
pitch = -90.;
|
|
roll = swwm_GM_GlobalMaths.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.viewangle,player.pitch+player.viewpitch,player.roll+player.viewroll);
|
|
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.viewangle,player.pitch+player.viewpitch,player.roll+player.viewroll);
|
|
else r = Quat.FromAngles(player.angle+player.viewangle,pitch,player.roll+player.viewroll);
|
|
return r*(1,0,0);
|
|
}
|
|
static Vector3, Vector3, Vector3 GetPlayerAxes( Actor player )
|
|
{
|
|
Quat r = Quat.FromAngles(player.angle+player.viewangle,player.pitch+player.viewpitch,player.roll+player.viewroll);
|
|
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.viewangle,player.pitch+player.viewpitch,player.roll+player.viewroll);
|
|
else r = Quat.FromAngles(player.angle+player.viewangle,pitch,player.roll+player.viewroll);
|
|
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); // viewangles are not used
|
|
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.viewangle,player.pitch+player.viewpitch,player.roll+player.viewroll);
|
|
return level.Vec3Offset(origin,r*(x,-y,z));
|
|
}
|
|
|
|
// thanks zscript
|
|
static double fract( double a )
|
|
{
|
|
return a-floor(a);
|
|
}
|
|
|
|
static double lerp( double a, double b, double theta )
|
|
{
|
|
return a*(1.-theta)+b*theta;
|
|
}
|
|
static Vector3 LerpVector3( Vector3 a, Vector3 b, double theta )
|
|
{
|
|
return a*(1.-theta)+b*theta;
|
|
}
|
|
static Vector2 LerpVector2( Vector2 a, Vector2 b, double theta )
|
|
{
|
|
return a*(1.-theta)+b*theta;
|
|
}
|
|
static Color LerpColor( Color a, Color b, double theta )
|
|
{
|
|
Color c = Color(
|
|
int(a.a*(1.-theta)+b.a*theta),
|
|
int(a.r*(1.-theta)+b.r*theta),
|
|
int(a.g*(1.-theta)+b.g*theta),
|
|
int(a.b*(1.-theta)+b.b*theta)
|
|
);
|
|
return c;
|
|
}
|
|
|
|
static double MapRange( double amin, double amax, double bmin, double bmax, double theta )
|
|
{
|
|
return bmin+(theta-amin)*(bmax-bmin)/(amax-amin);
|
|
}
|
|
|
|
// this can probably be simplified, but I'm lazy
|
|
static Vector3 HSVtoRGB( Vector3 hsv )
|
|
{
|
|
Vector3 p;
|
|
p.x = abs(fract(hsv.x+1.)*6.-3.);
|
|
p.y = abs(fract(hsv.x+(2./3.))*6.-3.);
|
|
p.z = abs(fract(hsv.x+(1./3.))*6.-3.);
|
|
Vector3 p2;
|
|
p2.x = (1.-hsv.y)+clamp(p.x-1.,0.,1.)*hsv.y;
|
|
p2.y = (1.-hsv.y)+clamp(p.y-1.,0.,1.)*hsv.y;
|
|
p2.z = (1.-hsv.y)+clamp(p.z-1.,0.,1.)*hsv.y;
|
|
return p2*hsv.z;
|
|
}
|
|
|
|
// "fast" exponentiation with integer exponents using loops
|
|
static double IntPowF( double base, int exp )
|
|
{
|
|
if ( exp < 0 ) return 1./IntPowF(base,-exp);
|
|
if ( exp == 0 ) return 1.;
|
|
double rslt = base;
|
|
for ( int i=0; i<exp; i++ ) rslt *= base;
|
|
return rslt;
|
|
}
|
|
static int IntPow( int base, int exp )
|
|
{
|
|
if ( exp < 0 ) return int(1./IntPow(base,-exp));
|
|
if ( exp == 0 ) return 1;
|
|
int rslt = base;
|
|
for ( int i=0; i<exp; i++ ) rslt *= base;
|
|
return rslt;
|
|
}
|
|
|
|
// pitch from one actor's eyes to another based on a specific fraction of its height
|
|
// meant for player view, Actor.PitchTo is recommended for more "general" situations
|
|
static double PitchTo( Actor a, Actor b, double hfact = 1. )
|
|
{
|
|
if ( !a || !b ) return 0;
|
|
Vector3 thispos = a.player?a.Vec2OffsetZ(0,0,a.player.viewz):a.Vec3Offset(0,0,a.GetCameraHeight()-a.floorclip);
|
|
Vector3 otherpos = b.Vec3Offset(0,0,b.height*hfact);
|
|
Vector3 diff = level.Vec3Diff(thispos,otherpos);
|
|
return -atan2(diff.z,diff.xy.length());
|
|
}
|
|
|
|
// box intersection check, for collision detection
|
|
static bool BoxIntersect( Actor a, Actor b, Vector3 ofs = (0,0,0), int pad = 0 )
|
|
{
|
|
Vector3 diff = level.Vec3Diff(level.Vec3Offset(a.pos,ofs),b.pos);
|
|
if ( (abs(diff.x) > (a.radius+b.radius+pad)) || (abs(diff.y) > (a.radius+b.radius+pad)) ) return false;
|
|
if ( (diff.z > a.height+pad) || (diff.z < -(b.height+pad)) ) return false;
|
|
return true;
|
|
}
|
|
|
|
// extruded box intersection check, useful when checking things that might be hit along a path
|
|
static bool ExtrudeIntersect( Actor a, Actor b, Vector3 range, int steps, int pad = 0 )
|
|
{
|
|
if ( steps <= 0 ) return BoxIntersect(a,b,pad:pad);
|
|
double step = 1./steps;
|
|
for ( double i=step; i<=1.; i+=step )
|
|
{
|
|
if ( BoxIntersect(a,b,range*i,pad) )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// sphere intersection check, useful for proximity detection
|
|
static bool SphereIntersect( Actor a, Vector3 p, double radius )
|
|
{
|
|
Vector3 ap = p+level.Vec3Diff(p,a.pos); // portal-relative actor position
|
|
Vector3 amin = ap+(-a.radius,-a.radius,0),
|
|
amax = ap+(a.radius,a.radius,a.height);
|
|
double distsq = 0.;
|
|
if ( p.x < amin.x ) distsq += (amin.x-p.x)*(amin.x-p.x);
|
|
if ( p.x > amax.x ) distsq += (p.x-amax.x)*(p.x-amax.x);
|
|
if ( p.y < amin.y ) distsq += (amin.y-p.y)*(amin.y-p.y);
|
|
if ( p.y > amax.y ) distsq += (p.y-amax.y)*(p.y-amax.y);
|
|
if ( p.z < amin.z ) distsq += (amin.z-p.z)*(amin.z-p.z);
|
|
if ( p.z > amax.z ) distsq += (p.z-amax.z)*(p.z-amax.z);
|
|
return (distsq <= (radius*radius));
|
|
}
|
|
|
|
// hitscan exit point calculation given actor, entry point and direction
|
|
static Vector3 TraceExit( Actor a, Vector3 p, Vector3 d )
|
|
{
|
|
Vector3 ap = p+level.Vec3Diff(p,a.pos); // portal-relative actor position
|
|
Vector3 amin = ap+(-a.radius,-a.radius,0),
|
|
amax = ap+(a.radius,a.radius,a.height);
|
|
Vector3 tmax, div = (1./d.x,1./d.y,1./d.z);
|
|
if ( div.x < 0 ) tmax.x = (amin.x-p.x)*div.x;
|
|
else tmax.x = (amax.x-p.x)*div.x;
|
|
if ( div.y < 0 ) tmax.y = (amin.y-p.y)*div.y;
|
|
else tmax.y = (amax.y-p.y)*div.y;
|
|
if ( div.z < 0 ) tmax.z = (amin.z-p.z)*div.z;
|
|
else tmax.z = (amax.z-p.z)*div.z;
|
|
return level.Vec3Offset(p,min(min(tmax.x,tmax.y),tmax.z)*d);
|
|
}
|
|
|
|
// Liang-Barsky line clipping
|
|
static bool, Vector2, Vector2 LiangBarsky( Vector2 minclip, Vector2 maxclip, Vector2 v0, Vector2 v1 )
|
|
{
|
|
double t0 = 0., t1 = 1.;
|
|
double xdelta = v1.x-v0.x;
|
|
double ydelta = v1.y-v0.y;
|
|
double p, q, r;
|
|
for ( int i=0;i<4; i++ )
|
|
{
|
|
switch ( i )
|
|
{
|
|
case 0:
|
|
p = -xdelta;
|
|
q = -(minclip.x-v0.x);
|
|
break;
|
|
case 1:
|
|
p = xdelta;
|
|
q = (maxclip.x-v0.x);
|
|
break;
|
|
case 2:
|
|
p = -ydelta;
|
|
q = -(minclip.y-v0.y);
|
|
break;
|
|
case 3:
|
|
p = ydelta;
|
|
q = (maxclip.y-v0.y);
|
|
break;
|
|
}
|
|
if ( (p == 0.) && (q<0.) ) return false, (0,0), (0,0);
|
|
if ( p < 0 )
|
|
{
|
|
r = q/p;
|
|
if ( r > t1 ) return false, (0,0), (0,0);
|
|
else if ( r > t0 ) t0 = r;
|
|
}
|
|
else if ( p > 0 )
|
|
{
|
|
r = q/p;
|
|
if ( r < t0 ) return false, (0,0), (0,0);
|
|
else if ( r < t1 ) t1 = r;
|
|
}
|
|
}
|
|
Vector2 ov0 = v0+(xdelta,ydelta)*t0;
|
|
Vector2 ov1 = v0+(xdelta,ydelta)*t1;
|
|
return true, ov0, ov1;
|
|
}
|
|
|
|
static play bool InPlayerFOV( PlayerInfo p, Actor a, double maxdist = 0. )
|
|
{
|
|
double vfov = p.fov*.5;
|
|
double hfov = atan(Screen.GetAspectRatio()*tan(vfov));
|
|
let mo = p.camera;
|
|
if ( !mo ) return false;
|
|
Vector3 pp;
|
|
if ( !a.CheckSight(mo,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) return false;
|
|
if ( mo is 'PlayerPawn' ) pp = mo.Vec2OffsetZ(0,0,PlayerPawn(mo).player.viewz);
|
|
else pp = mo.Vec3Offset(0,0,mo.GetCameraHeight());
|
|
Vector3 sc = level.SphericalCoords(pp,a.pos,(mo.angle,mo.pitch));
|
|
if ( (abs(sc.x) > hfov) || (abs(sc.y) > vfov) ) return false;
|
|
if ( (maxdist > 0.) && (sc.z > maxdist) ) return false;
|
|
return true;
|
|
}
|
|
|
|
// ui-friendly version without CheckSight call
|
|
static bool InPlayerFOVSimple( PlayerInfo p, Actor a, double maxdist = 0. )
|
|
{
|
|
double vfov = p.fov*.5;
|
|
double hfov = atan(Screen.GetAspectRatio()*tan(vfov));
|
|
let mo = p.camera;
|
|
if ( !mo ) return false;
|
|
Vector3 pp;
|
|
if ( mo is 'PlayerPawn' ) pp = mo.Vec2OffsetZ(0,0,PlayerPawn(mo).player.viewz);
|
|
else pp = mo.Vec3Offset(0,0,mo.GetCameraHeight());
|
|
Vector3 sc = level.SphericalCoords(pp,a.pos,(mo.angle,mo.pitch));
|
|
if ( (abs(sc.x) > hfov) || (abs(sc.y) > vfov) ) return false;
|
|
if ( (maxdist > 0.) && (sc.z > maxdist) ) return false;
|
|
return true;
|
|
}
|
|
|
|
// calculate angle, pitch and yscale of YBeam based on direction vector and length
|
|
static double, double, double CalcYBeam( Vector3 dir, double dist )
|
|
{
|
|
if ( level.pixelstretch == 1. )
|
|
{
|
|
// ez modo
|
|
double angle = atan2(dir.y,dir.x);
|
|
double pitch = asin(-dir.z)+90;
|
|
return angle, pitch, dist;
|
|
}
|
|
dir.z *= level.pixelstretch;
|
|
double len = dir.length();
|
|
dir /= len;
|
|
double angle = atan2(dir.y,dir.x);
|
|
double pitch = asin(-dir.z)+90;
|
|
double yscale = dist*len;
|
|
return angle, pitch, yscale;
|
|
}
|
|
}
|