Implemented "six degrees of freedom" movement for guided redeemer missiles (reload/zoom for manual tilt control).

Made part of the guided redeemer reticle rotate according to the missile's roll just to show off Shape2D.
This commit is contained in:
Marisa the Magician 2018-08-14 02:57:44 +02:00
commit b24ee842ba
5 changed files with 144 additions and 21 deletions

97
zscript/mk_quaternion.zsc Normal file
View file

@ -0,0 +1,97 @@
/*
Quaternion math helper class.
(C)2018 Marisa Kirisame, UnSX Team.
Released under the GNU Lesser General Public License version 3 (or later).
See https://www.gnu.org/licenses/lgpl-3.0.txt for its terms.
*/
Class Quat
{
protected double W, X, Y, Z;
Quat init( double w, double x, double y, double z )
{
self.W = w;
self.X = x;
self.Y = y;
self.Z = z;
return self;
}
void copy( Quat q )
{
W = q.W;
X = q.X;
Y = q.Y;
Z = q.Z;
}
static Quat create( double w, double x, double y, double z )
{
return new("Quat").init(w,x,y,z);
}
static Quat create_axis( Vector3 axis, double theta )
{
double scale = axis dot axis;
if ( scale < double.epsilon ) return Quat.create(1,0,0,0);
theta *= 0.5;
double f = sin(theta)/sqrt(scale);
return Quat.create(cos(theta),axis.x*f,axis.y*f,axis.z*f);
}
static Quat create_euler( double pitch, double yaw, double roll )
{
Quat zrot = Quat.create_axis((0,0,1),yaw);
Quat yrot = Quat.create_axis((0,1,0),pitch);
Quat xrot = Quat.create_axis((1,0,0),roll);
Quat sum = zrot.qmul(yrot);
sum = sum.qmul(xrot);
return sum;
}
// copied here since Actor.Normalize180 is not (yet) clearscope
static double Normalize180( double ang )
{
ang = ang%360;
ang = (ang+360)%360;
if ( ang > 180 ) ang -= 360;
return ang;
}
double, double, double to_euler()
{
double stest = z*x-w*y;
double yawY = 2*(w*z+x*y);
double yawX = 1-2*(y*y+z*z);
double st = 0.4999995;
double pitch = 0;
double yaw = 0;
double roll = 0;
if ( stest <= -st )
{
pitch = 90;
yaw = atan2(yawY,yawX);
roll = Normalize180(yaw+(2*atan2(x,w)));
}
else if ( stest > st )
{
pitch = -90;
yaw = atan2(yawY,yawX);
roll = Normalize180(yaw+(2*atan2(x,w)));
}
else
{
pitch = -asin(2*stest);
yaw = atan2(yawY,yawX);
roll = atan2(2*(w*x+y*z),(1-2*(x*x+y*y)));
}
return pitch, yaw, roll;
}
Quat qmul( Quat q )
{
return Quat.create(w*q.w-x*q.x-y*q.y-z*q.z,w*q.x+x*q.w+y*q.z-z
*q.y,w*q.y+y*q.w+z*q.x-x*q.z,w*q.z+z*q.w+x*q.y-y*q.x);
}
}

View file

@ -355,15 +355,13 @@ Class WarShell : Actor
Class GuidedWarShell : WarShell
{
double lagangle, lagpitch, lagangle2, lagpitch2;
double guideangle, guidepitch, lastguideroll;
double lagangle, lagpitch, lagroll, lagangle2, lagpitch2, lagroll2,
guideangle, guidepitch, guideroll;
override void PostBeginPlay()
{
Super.PostBeginPlay();
if ( target && target.player ) target.player.camera = self;
guideangle = angle;
guidepitch = pitch;
}
override void Tick()
{
@ -385,22 +383,25 @@ Class GuidedWarShell : WarShell
{
lagangle = target.player.cmd.yaw/128.;
lagpitch = -target.player.cmd.pitch/128.;
guideangle += lagangle2*0.95+lagangle*0.05;
guidepitch += lagpitch2*0.95+lagpitch*0.05;
guidepitch = Clamp(guidepitch,-89,89);
double guideroll = -lagangle2*15;
Vector3 dir = (cos(guideangle)*cos(guidepitch),sin(guideangle)*cos(guidepitch),-sin(guidepitch));
destangle = atan2(dir.y,dir.x);
destpitch = asin(-dir.z);
double destroll = lastguideroll*0.9+guideroll*0.1;
A_SetAngle(destangle,SPF_INTERPOLATE);
A_SetPitch(destpitch,SPF_INTERPOLATE);
A_SetRoll(destroll,SPF_INTERPOLATE);
if ( target.player.cmd.buttons&BT_RELOAD ) lagroll = 5;
else if ( target.player.cmd.buttons&BT_ZOOM ) lagroll = -5;
else lagroll = 0;
guideangle = lagangle2*0.95+lagangle*0.05;
guidepitch = lagpitch2*0.95+lagpitch*0.05;
guideroll = lagroll2*0.95+lagroll*0.05;
Quat orient = Quat.create_euler(pitch,angle,roll);
Quat angles = Quat.create_euler(guidepitch,guideangle,guideroll);
orient = orient.qmul(angles);
double npitch, nangle, nroll;
[npitch, nangle, nroll] = orient.to_euler();
A_SetAngle(nangle,SPF_INTERPOLATE);
A_SetPitch(npitch,SPF_INTERPOLATE);
A_SetRoll(nroll,SPF_INTERPOLATE);
vel = (vel+(cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch))*0.8).unit()*11;
}
lagangle2 = lagangle2*0.95+lagangle*0.05;
lagpitch2 = lagpitch2*0.95+lagpitch*0.05;
lastguideroll = roll*0.98;
lagroll2 = lagroll2*0.95+lagroll*0.05;
}
States
{
@ -439,25 +440,36 @@ Class RedeemerHUD : HUDMessageBase
{
Actor Camera;
Vector3 ViewPos;
double ViewAngle, ViewPitch, ViewRoll;
TextureID reticle, mark, readout;
double ViewAngle, ViewPitch, ViewRoll, LagRoll, LagRoll2;
TextureID reticle1, reticle2, mark, readout;
Font whfont;
ThinkerIterator t;
MidTracer tr;
Array<TargetActor> ta;
Shape2D rreticle;
RedeemerHUD Init()
{
reticle = TexMan.CheckForTexture("GuidedX",TexMan.Type_Any);
reticle1 = TexMan.CheckForTexture("GuidedX1",TexMan.Type_Any);
reticle2 = TexMan.CheckForTexture("GuidedX2",TexMan.Type_Any);
mark = TexMan.CheckForTexture("Crosshr6",TexMan.Type_Any);
readout = TexMan.CheckForTexture("Readout",TexMan.Type_Any);
whfont = Font.GetFont('WHFONT');
t = ThinkerIterator.Create("Actor");
tr = new("MidTracer");
rreticle = new("Shape2D");
rreticle.PushCoord((0,0));
rreticle.PushCoord((1,0));
rreticle.PushCoord((0,1));
rreticle.PushCoord((1,1));
rreticle.PushTriangle(0,3,1);
rreticle.PushTriangle(0,2,3);
return self;
}
override bool Tick()
{
LagRoll = Quat.Normalize180(ViewRoll-LagRoll2);
LagRoll2 += Quat.Normalize180(LagRoll-LagRoll2)*0.1;
// shootable targetting
if ( CVar.GetCVar('flak_redeemerreadout',players[consoleplayer]).GetBool() )
{
@ -494,8 +506,21 @@ Class RedeemerHUD : HUDMessageBase
Screen.DrawText(whfont,Font.CR_UNTRANSLATED,(ta[i].vpos.x-whfont.StringWidth(ta[i].diststr)/2)-12,ta[i].vpos.y+8,ta[i].diststr,DTA_RenderStyle,(1|2<<8|1<<16));
}
}
// other stuff
Screen.DrawTexture(reticle,false,320,240,DTA_VirtualWidth,640,DTA_VirtualHeight,480,DTA_RenderStyle,(1|2<<8|1<<16));
// reticle
Vector2 vs = (640,640/Screen.GetAspectRatio());
Vector2 mid, siz;
[mid, siz] = Screen.VirtualToRealCoords(vs*0.5,(128,128),vs,false,false);
Vector2 verts[4];
verts[0] = (-siz.x,-siz.y);
verts[1] = (siz.x,-siz.y);
verts[2] = (-siz.x,siz.y);
verts[3] = (siz.x,siz.y);
rreticle.Clear(Shape2D.C_Verts);
for ( int i=0; i<4; i++ )
rreticle.PushVertex(mid+(verts[i].x*cos(LagRoll2)-verts[i].y*sin(LagRoll2),verts[i].x*sin(LagRoll2)+verts[i].y*cos(LagRoll2)));
Screen.DrawShape(reticle1,false,rreticle,DTA_RenderStyle,(1|2<<8|1<<16));
Screen.DrawTexture(reticle2,false,vs.x*0.5,vs.y*0.5,DTA_VirtualWidthF,vs.x,DTA_VirtualHeightF,vs.y,DTA_KeepRatio,true,DTA_RenderStyle,(1|2<<8|1<<16));
// faux assembly readout
int numreadouts = Screen.GetHeight()/128+2;
for ( int i=0; i<numreadouts; i++ )
{