Remove Gutamatics, use bespoke projection code from UT99 modding days.

This commit is contained in:
Mari the Deer 2025-03-16 15:50:00 +01:00
commit a5e58add12
19 changed files with 103 additions and 1216 deletions

View file

@ -27,8 +27,8 @@ extend Class SWWMHandler
transient ui int numcrosshairs; // how many crosshairs the current weapon has
transient ui Vector3 tpos[MAX_CROSSHAIRS]; // current trace positions in world space
transient ui Color tcol[MAX_CROSSHAIRS]; // current crosshair colors
transient ui SWWMProjectionData tprojdata; // cached Gutamatics projection data
transient ui Vector3 lagtndc[MAX_CROSSHAIRS]; // "lagged" NDC for crosshairs
transient ui SWWMProjectionData tprojdata; // cached Fast projection data
transient ui Vector2 lagvpos[MAX_CROSSHAIRS]; // "lagged" clip pos for crosshairs
transient ui bool tactive[MAX_CROSSHAIRS]; // denotes that the crosshair is "active" for drawing
transient ui double prevframe; // previous frame timestamp
@ -128,6 +128,8 @@ extend Class SWWMHandler
if ( int(ts.x)%2 ) oddfix.x = -floor(sz/2.)+1.;
if ( int(ts.y)%2 ) oddfix.y = -floor(sz/2.)+1.;
SWWMUtility.PrepareProjData(tprojdata,e.ViewPos,e.ViewAngle,e.ViewPitch,e.ViewRoll,players[consoleplayer].fov);
int cliptop = tprojdata.viewy, clipbottom = tprojdata.viewy+tprojdata.viewh,
clipleft = tprojdata.viewx, clipright = tprojdata.viewx+tprojdata.vieww;
Vector2 actpos[MAX_CROSSHAIRS];
for ( int i=0; i<MAX_CROSSHAIRS; i++ )
{
@ -137,30 +139,28 @@ extend Class SWWMHandler
Vector3 tdir = (e.ViewPos-cpos);
// project (but account for rare corner case where distance is zero)
Vector3 ndc = (tdir.length()<=double.epsilon)?(0,0,0):SWWMUtility.ProjectPoint(tprojdata,cpos);
if ( ndc.z >= 1. ) continue;
// invalid or behind view, skip
if ( ndc.z <= 0. ) continue;
Vector2 vpos = SWWMUtility.NDCToViewport(tprojdata,ndc);
if ( !prevframe ) lagtndc[i] = ndc;
if ( lagtndc[i].z >= 1. ) continue;
Vector2 oldvpos = SWWMUtility.NDCToViewport(tprojdata,lagtndc[i]);
lagtndc[i] = SWWMUtility.LerpVector3(lagtndc[i],ndc,theta);
if ( lagtndc[i].z >= 1. ) continue;
Vector2 lagvpos = SWWMUtility.NDCToViewport(tprojdata,lagtndc[i]);
if ( !prevframe ) lagvpos[i] = vpos;
Vector2 oldvpos = lagvpos[i];
lagvpos[i] = SWWMUtility.LerpVector2(lagvpos[i],vpos,theta);
if ( !tactive[i] ) continue;
// draw
int streak = int(max(abs(oldvpos.x-lagvpos.x),abs(oldvpos.y-lagvpos.y)));
int streak = int(max(abs(oldvpos.x-lagvpos[i].x),abs(oldvpos.y-lagvpos[i].y)));
double alph = 1.;
if ( i < numcrosshairs ) actpos[i] = lagvpos;
if ( i < numcrosshairs ) actpos[i] = lagvpos[i];
else
{
// unused crosshairs must "linger" until they merge with the ones that are drawn
int j = (i-numcrosshairs)%numcrosshairs;
double dist = (lagvpos-actpos[j]).length();
double dist = (lagvpos[i]-actpos[j]).length();
if ( (streak <= 0) && (dist < 1.) )
tactive[i] = false;
alph = clamp(dist/max(2,streak+2),0.,1.); // this should make the merge less jarring
}
for ( int i=0; i<streak; i++ ) Screen.DrawTexture(ctex,false,int(SWWMUtility.lerp(oldvpos.x,lagvpos.x,i/double(streak)))+oddfix.x,int(SWWMUtility.lerp(oldvpos.y,lagvpos.y,i/double(streak)))+oddfix.y,DTA_DestWidthF,ts.x*sz,DTA_DestHeightF,ts.y*sz,DTA_AlphaChannel,true,DTA_FillColor,ccol,DTA_Alpha,((i*.5)/streak)*alph);
Screen.DrawTexture(ctex,false,int(lagvpos.x)+oddfix.x,int(lagvpos.y)+oddfix.y,DTA_DestWidthF,ts.x*sz,DTA_DestHeightF,ts.y*sz,DTA_AlphaChannel,true,DTA_FillColor,ccol,DTA_Alpha,alph);
for ( int j=0; j<streak; j++ ) Screen.DrawTexture(ctex,false,int(SWWMUtility.lerp(oldvpos.x,lagvpos[i].x,j/double(streak)))+oddfix.x,int(SWWMUtility.lerp(oldvpos.y,lagvpos[i].y,j/double(streak)))+oddfix.y,DTA_DestWidthF,ts.x*sz,DTA_DestHeightF,ts.y*sz,DTA_AlphaChannel,true,DTA_FillColor,ccol,DTA_Alpha,((j*.5)/streak)*alph,DTA_ClipTop,cliptop,DTA_ClipBottom,clipbottom,DTA_ClipLeft,clipleft,DTA_ClipRight,clipright);
Screen.DrawTexture(ctex,false,int(lagvpos[i].x)+oddfix.x,int(lagvpos[i].y)+oddfix.y,DTA_DestWidthF,ts.x*sz,DTA_DestHeightF,ts.y*sz,DTA_AlphaChannel,true,DTA_FillColor,ccol,DTA_Alpha,alph,DTA_ClipTop,cliptop,DTA_ClipBottom,clipbottom,DTA_ClipLeft,clipleft,DTA_ClipRight,clipright);
}
prevframe = curframe;
}

View file

@ -6,20 +6,22 @@ extend Class SWWMHandler
private ui void DrawWorldLine( RenderEvent e, Vector3 apos, Vector3 bpos, Color col )
{
Vector3 a = SWWMUtility.ProjectPoint(projdata,e.viewpos+level.Vec3Diff(e.viewpos,apos)),
b = SWWMUtility.ProjectPoint(projdata,e.viewpos+level.Vec3Diff(e.viewpos,bpos));
if ( (a.z > 1.) && (b.z > 1.) ) return;
if ( (a.z == -double.infinity) || (b.z == -double.infinity) ) return; // how???
Vector3 a = SWWMUtility.ProjectPoint(projdata,e.viewpos+level.Vec3Diff(e.viewpos,apos),false),
b = SWWMUtility.ProjectPoint(projdata,e.viewpos+level.Vec3Diff(e.viewpos,bpos),false);
// both points are behind the screen
if ( (a.z <= 0.) && (b.z < 0.) ) return;
// at least one point is invalid
if ( (a.z == 0.) || (b.z == 0.) ) return;
double da, db, s;
Vector3 p;
da = a dot (0.,0.,-1.)-1.;
db = b dot (0.,0.,-1.)-1.;
da = a dot (0.,0.,1.);
db = b dot (0.,0.,1.);
if ( da-db != 0. )
{
s = da/(da-db);
p = (a.x+s*(b.x-a.x),a.y+s*(b.y-a.y),a.z+s*(b.z-a.z));
if ( a.z > 1. ) a = p;
if ( b.z > 1. ) b = p;
if ( a.z < 0. ) a = p;
if ( b.z < 0. ) b = p;
}
Vector2 va = SWWMUtility.NDCToViewport(projdata,a);
Vector2 vb = SWWMUtility.NDCToViewport(projdata,b);
@ -33,23 +35,25 @@ extend Class SWWMHandler
for ( int i=0; i<64; i++ )
{
Vector3 wpos = e.viewpos+level.Vec3Diff(e.viewpos,level.Vec3Offset(pos,SWWMUtility.CircleOffset(y,z,i*5.625,radius)));
ndc[i] = SWWMUtility.ProjectPoint(projdata,wpos);
ndc[i] = SWWMUtility.ProjectPoint(projdata,wpos,false);
}
for ( int i=0; i<64; i++ )
{
Vector3 a = ndc[i], b = ndc[(i+1)%64];
if ( (a.z > 1.) && (b.z > 1.) ) continue;
if ( (a.z == -double.infinity) || (b.z == -double.infinity) ) continue; // how???
// both points are behind the screen
if ( (a.z <= 0.) && (b.z < 0.) ) return;
// at least one point is invalid
if ( (a.z == 0.) || (b.z == 0.) ) return;
double da, db, s;
Vector3 p;
da = a dot (0.,0.,-1.)-1.;
db = b dot (0.,0.,-1.)-1.;
da = a dot (0.,0.,1.);
db = b dot (0.,0.,1.);
if ( da-db != 0. )
{
s = da/(da-db);
p = (a.x+s*(b.x-a.x),a.y+s*(b.y-a.y),a.z+s*(b.z-a.z));
if ( a.z > 1. ) a = p;
if ( b.z > 1. ) b = p;
if ( a.z < 0. ) a = p;
if ( b.z < 0. ) b = p;
}
Vector2 va = SWWMUtility.NDCToViewport(projdata,a);
Vector2 vb = SWWMUtility.NDCToViewport(projdata,b);
@ -90,7 +94,7 @@ extend Class SWWMHandler
double hdiff = a.Height/2;
if ( a.bFLOATBOB ) hdiff += a.GetBobOffset();
Vector3 ndc = SWWMUtility.ProjectPoint(projdata,e.viewpos+level.Vec3Diff(e.viewpos,pos+(0,0,hdiff)));
if ( ndc.z > 1. ) return;
if ( ndc.z <= 0. ) return;
Vector2 vpos = SWWMUtility.NDCToViewport(projdata,ndc);
String tag = a.player?a.player.GetUserName():a.GetTag();
if ( tag == a.GetClassName() ) SWWMUtility.BeautifyClassName(tag);

View file

@ -24,9 +24,8 @@ extend Class SWWMStatusBar
Screen.SetClipRect(projdata.viewx,projdata.viewy,projdata.vieww,projdata.viewh);
}
Vector3 tdir = level.Vec3Diff(ViewPos,poi.pos);
if ( viewvec dot tdir < 0 ) continue;
Vector3 ndc = SWWMUtility.ProjectPoint(projdata,ViewPos+tdir);
if ( ndc.z >= 1. ) continue;
if ( ndc.z <= 0. ) continue;
Vector2 vpos = SWWMUtility.NDCToViewport(projdata,ndc)/hs2;
if ( poi.type == INT_Key ) tag = String.Format("\cf%s\c-",poi.keytag);
else if ( poi.type == INT_Exit )
@ -161,14 +160,12 @@ extend Class SWWMStatusBar
// ignore trackers clearly outside of player view
Vector3 smpos = level.Vec3Offset(SWWMUtility.LerpVector3(ct.Owner.prev,ct.Owner.pos,fractic),(0,0,ct.lvheight));
Vector3 tdir = level.Vec3Diff(viewpos,smpos);
if ( viewvec dot tdir < 0 ) continue;
// ignore trackers that are too far away
double dist = tdir.length();
if ( (fadedist > 0) && (dist > fadedist*1.5) ) continue;
Vector3 ndc = SWWMUtility.ProjectPoint(projdata,viewpos+tdir);
if ( ndc.z >= 1. ) continue;
if ( ndc.z <= 0. ) continue;
Vector2 vpos = SWWMUtility.NDCToViewport(projdata,ndc)/hs1;
if ( !SWWMUtility.TestScreenBounds(projdata,vpos) ) continue;
double fin = clamp(ct.fadein+fractic,0.,5.)/5.;
double fout = clamp(ct.lifespan-fractic,0.,25.)/25.;
double alph = fin*fout;
@ -246,11 +243,9 @@ extend Class SWWMStatusBar
// ignore trackers clearly outside of player view
Vector3 smpos = level.Vec3Offset(SWWMUtility.LerpVector3(ct.Owner.prev,ct.Owner.pos,fractic),(0,0,ct.lvheight));
Vector3 tdir = level.Vec3Diff(viewpos,smpos);
if ( viewvec dot tdir < 0 ) continue;
Vector3 ndc = SWWMUtility.ProjectPoint(projdata,viewpos+tdir);
if ( ndc.z >= 1. ) continue;
if ( ndc.z <= 0. ) continue;
Vector2 vpos = SWWMUtility.NDCToViewport(projdata,ndc)/hs1;
if ( !SWWMUtility.TestScreenBounds(projdata,vpos) ) continue;
double fin = clamp(ct.fadein+fractic,0.,5.)/5.;
double fout = clamp(ct.lifespan-fractic,0.,25.)/25.;
double alph = fin*fout;
@ -350,9 +345,8 @@ extend Class SWWMStatusBar
Screen.SetClipRect(projdata.viewx,projdata.viewy,projdata.vieww,projdata.viewh);
}
Vector3 tdir = level.Vec3Diff(ViewPos,snum.pos);
if ( viewvec dot tdir < 0 ) continue;
Vector3 ndc = SWWMUtility.ProjectPoint(projdata,ViewPos+tdir);
if ( ndc.z >= 1. ) continue;
Vector3 ndc = SWWMUtility.ProjectPoint(projdata,ViewPos+tdir,true);
if ( ndc.z <= 0. ) continue;
Vector2 vpos = SWWMUtility.NDCToViewport(projdata,ndc)/hs0;
String tag = abs(snum.damage>=Actor.TELEFRAG_DAMAGE)?(snum.damage>0)?"-∞":"+∞":String.Format("%+d",-snum.damage);
double alph = clamp((snum.lifespan+fractic)/35.,0.,1.);
@ -380,9 +374,8 @@ extend Class SWWMStatusBar
Screen.SetClipRect(projdata.viewx,projdata.viewy,projdata.vieww,projdata.viewh);
}
Vector3 tdir = level.Vec3Diff(ViewPos,snum.pos);
if ( viewvec dot tdir < 0 ) continue;
Vector3 ndc = SWWMUtility.ProjectPoint(projdata,ViewPos+tdir);
if ( ndc.z >= 1. ) continue;
if ( ndc.z <= 0. ) continue;
Vector2 vpos = SWWMUtility.NDCToViewport(projdata,ndc)/hs1;
String tag = String.Format("%+d",snum.score);
double alph = clamp((snum.lifespan+fractic)/double(GameTicRate),0.,1.);

View file

@ -173,11 +173,11 @@ Class SWWMCreditsMenu : GenericMenu
cthanks.Push(new('SWWMCreditsEntry').Init("KynikossDragonn","$SWWM_CDRAGON2"));
cthanks.Push(new('SWWMCreditsEntry').Init("Vyolette","$SWWM_CVYOLETTE2"));
cthanks.Push(new('SWWMCreditsEntry').Init("Lucy","$SWWM_CLUCY2"));
cthanks.Push(new('SWWMCreditsEntry').Init("Gutawer","$SWWM_CGUTA2"));
cthanks.Push(new('SWWMCreditsEntry').Init("Mikolah","$SWWM_CMIKO2"));
cthanks.Push(new('SWWMCreditsEntry').Init("KeksDose","$SWWM_CKEKS2"));
cthanks.Push(new('SWWMCreditsEntry').Init("ZZYZX & Nash","$SWWM_CZN2"));
cthanks.Push(new('SWWMCreditsEntry').Init("BouncyTEM","$SWWM_CBOUNCY2"));
cthanks.Push(new('SWWMCreditsEntry').Init("a1337spy","$SWWM_CSPY2"));
cthanks.Push(new('SWWMCreditsEntry').Init("\ctCptSledge\c- & \cdBunray\c-","$SWWM_CSLEDGE2"));
cthanks.Push(new('SWWMCreditsEntry').Init("$SWWM_CINSP1","$SWWM_CINSP2"));
cthanks.Push(new('SWWMCreditsEntry').Init("$SWWM_CCOMMUNITY1","$SWWM_CCOMMUNITY2"));

View file

@ -1,173 +0,0 @@
class swwm_GM_GlobalMaths {
/// Returns the sign of s.
static int sign(double s) {
if (s > 0) return 1;
if (s < 0) return -1;
return 0;
}
/// Copies the sign from signSource to source.
static int copySignInt(int source, int signSource) {
return abs(source) * sign(signSource);
}
/// Copies the sign from signSource to source.
static double copySignDouble(double source, double signSource) {
return abs(source) * sign(signSource);
}
/// Remaps a value in a range to another range.
static double remapRange(double value, double range1L, double range1H, double range2L, double range2H) {
return range2L + (value - range1L) * (range2H - range1H) / (range1H - range1L);
}
/// Remaps a value in a range to another range.
static int remapRangeInt(int value, int range1L, int range1H, int range2L, int range2H) {
return int(range2L + (value - range1L) * (range2H - range1H) / double(range1H - range1L));
}
/// Returns if two values are close enough to be considered equal.
static bool closeEnough(double a, double b, double epsilon = double.epsilon) {
if (a == b) return true;
return abs(a - b) <= epsilon;
}
// Creates a smoothed transition between edge0 and edge1.
static double smoothStep(double x, double edge0 = 0, double edge1 = 1) {
x = clamp((x - edge0) / (edge1 - edge0), 0, 1);
return x * x * (3 - 2 * x);
}
// Creates a smoother transition between edge0 and edge1.
static double smootherStep(double x, double edge0 = 0, double edge1 = 1) {
x = clamp((x - edge0) / (edge1 - edge0), 0, 1);
return x * x * x * (x * (x * 6 - 15) + 10);
}
/// Converts from horizontal FOV to vertical FOV, according to how GZDoom handles it.
static double fovHToY(double fovH) {
// this is how gzdoom does it internally, so i'm using it here
double aspect = Screen.getAspectRatio();
double fovratio = (aspect >= 1.3) ? 1.333333 : aspect;
return 2 * atan(tan(clamp(fovH, 5, 170) / 2.0) / fovratio);
}
/// Linearly interpolates between two doubles, clamping the parameters.
static double lerpDouble(double from, double to, double time) {
time = clamp(time, 0, 1);
return lerpUnclampedDouble(from, to, time);
}
/// Linearly interpolates between two doubles.
static double lerpUnclampedDouble(double from, double to, double time) {
double ret;
double reverseTime = 1 - time;
ret = reverseTime * from + time * to;
return ret;
}
// Converts from Normalised Device Coordinates to Viewport coordinates.
// This is `ui` scope to safely access `screenblocks`.
static ui Vector2 ndcToViewport(Vector3 ndcCoords, bool useScreenblocks = true) {
if (useScreenblocks) {
int viewwindowx, viewwindowy, viewwidth, viewheight;
[viewwindowx, viewwindowy, viewwidth, viewheight] = Screen.getViewWindow();
int screenHeight = Screen.getHeight();
int height = screenHeight;
if (screenblocks < 10) {
height = (screenblocks * screenHeight / 10) & ~7;
}
int bottom = screenHeight - (height + viewwindowy - ((height - viewheight) / 2));
return (viewwindowx, screenHeight - bottom - height) + (((ndcCoords.x + 1) * viewwidth) / 2, ((-ndcCoords.y + 1) * height) / 2);
}
else {
return (((ndcCoords.x + 1) * Screen.getWidth()) / 2, ((-ndcCoords.y + 1) * Screen.getHeight()) / 2);
}
}
enum OutCode {
OUT_Inside = 0,
OUT_Left = 1 << 0,
OUT_Right = 1 << 1,
OUT_Bottom = 1 << 2,
OUT_Top = 1 << 3
}
/// Computes an outcode for a point in a rectangle.
static OutCode computeOutcode(Vector2 point, Vector2 min, Vector2 max) {
OutCode code = OUT_Inside;
if (point.x < min.x) {
code |= OUT_Left;
}
else if (point.x > max.x) {
code |= OUT_Right;
}
if (point.y < min.y) {
code |= OUT_Top;
}
else if (point.y > max.y) {
code |= OUT_Bottom;
}
return code;
}
/// Clips a line to a rectangle.
static bool, Vector2, Vector2 cohenSutherlandClip(Vector2 point0, Vector2 point1, Vector2 min, Vector2 max) {
OutCode outcode0 = computeOutCode(point0, min, max);
OutCode outcode1 = computeOutCode(point1, min, max);
while (true) {
// trivial accept - points are both on screen
if ((outcode0 | outcode1) == 0) {
return true, point0, point1;
}
// trivial reject - points are in the same region offscreen
else if ((outcode0 & outcode1) != 0) {
return false, point0, point1;
}
else {
Vector2 new;
OutCode outcodeOut = (outcode0 != 0) ? outcode0 : outcode1;
if ((outcodeOut & OUT_Bottom) != 0) {
new.x = point0.x + (point1.x - point0.x) * (max.y - point0.y) / (point1.y - point0.y);
new.y = max.y;
}
else if ((outcodeOut & OUT_Top) != 0) {
new.x = point0.x + (point1.x - point0.x) * (min.y - point0.y) / (point1.y - point0.y);
new.y = min.y;
}
else if ((outcodeOut & OUT_Right) != 0) {
new.y = point0.y + (point1.y - point0.y) * (max.x - point0.x) / (point1.x - point0.x);
new.x = max.x;
}
else if ((outcodeOut & OUT_Left) != 0) {
new.y = point0.y + (point1.y - point0.y) * (min.x - point0.x) / (point1.x - point0.x);
new.x = min.x;
}
if (outcodeOut == outCode0) {
point0.x = new.x;
point0.y = new.y;
outCode0 = computeOutCode(point0, min, max);
}
else {
point1.x = new.x;
point1.y = new.y;
outCode1 = computeOutCode(point1, min, max);
}
}
}
return false, (0, 0), (0, 0);
}
// Normalizes an angle to (-180, 180]. Like Actor.normalize180, but callable in data scope.
static double normalize180(double ang) {
ang = ang % 360;
ang = (ang + 360) % 360;
if (ang > 180) ang -= 360;
return ang;
}
}

View file

@ -1,5 +0,0 @@
#include "zscript/swwm_Gutamatics/GlobalMaths.zsc"
#include "zscript/swwm_Gutamatics/Matrix.zsc"
#include "zscript/swwm_Gutamatics/Matrix4.zsc"
#include "zscript/swwm_Gutamatics/Quaternion.zsc"
#include "zscript/swwm_Gutamatics/VectorUtil.zsc"

View file

@ -1,7 +0,0 @@
Copyright 2020 Jessica Russell
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,335 +0,0 @@
enum swwm_GM_VectorType {
swwm_GM_Vector_Position,
swwm_GM_Vector_Direction
}
class swwm_GM_Matrix {
private Array<double> values;
private int columns;
private int rows;
/// Initialises a new Matrix.
swwm_GM_Matrix init(int columns, int rows) {
if (columns <= 0 || rows <= 0) {
throwAbortException("Error: <%p>.init(%d, %d) - Matrix needs to be at least 1 * 1", self, columns, rows);
}
self.rows = rows;
self.columns = columns;
values.resize(columns * rows);
for (int i = 0; i < values.size(); i++) {
values[i] = 0;
}
return self;
}
/// Initialises a new Matrix in a static context.
static swwm_GM_Matrix create(int columns, int rows) {
return new("swwm_GM_Matrix").init(columns, rows);
}
/// Returns an identity matrix.
static swwm_GM_Matrix identity(int dimension) {
swwm_GM_Matrix ret = swwm_GM_Matrix.create(dimension, dimension);
for (int i = 0; i < dimension; i++) {
ret.set(i, i, 1);
}
return ret;
}
/// Returns a rotation matrix from euler angles.
static swwm_GM_Matrix fromEulerAngles(double yaw, double pitch, double roll) {
swwm_GM_Matrix rYaw = swwm_GM_Matrix.identity(4);
double sYaw = sin(yaw);
double cYaw = cos(yaw);
rYaw.set(0, 0, cYaw);
rYaw.set(0, 1, -sYaw);
rYaw.set(1, 0, sYaw);
rYaw.set(1, 1, cYaw);
swwm_GM_Matrix rPitch = swwm_GM_Matrix.identity(4);
double sPitch = sin(pitch);
double cPitch = cos(pitch);
rPitch.set(0, 0, cPitch);
rPitch.set(2, 0, -sPitch);
rPitch.set(0, 2, sPitch);
rPitch.set(2, 2, cPitch);
swwm_GM_Matrix rRoll = swwm_GM_Matrix.identity(4);
double sRoll = sin(roll);
double cRoll = cos(roll);
rRoll.set(1, 1, cRoll);
rRoll.set(1, 2, -sRoll);
rRoll.set(2, 1, sRoll);
rRoll.set(2, 2, cRoll);
// concatenate ypr to get the final matrix
swwm_GM_Matrix ret = rYaw.multiplyMatrix(rPitch);
ret = ret.multiplyMatrix(rRoll);
return ret;
}
/// Returns a rotation matrix from an axis and an angle.
static swwm_GM_Matrix fromAxisAngle(Vector3 axis, double angle) {
swwm_GM_Matrix ret = swwm_GM_Matrix.identity(4);
double c = cos(angle);
double s = sin(angle);
double x = axis.x;
double y = axis.y;
double z = axis.z;
ret.set(0, 0, (x * x * (1.0 - c) + c));
ret.set(0, 1, (x * y * (1.0 - c) - z * s));
ret.set(0, 2, (x * z * (1.0 - c) + y * s));
ret.set(1, 0, (y * x * (1.0 - c) + z * s));
ret.set(1, 1, (y * y * (1.0 - c) + c));
ret.set(1, 2, (y * z * (1.0 - c) - x * s));
ret.set(2, 0, (x * z * (1.0 - c) - y * s));
ret.set(2, 1, (y * z * (1.0 - c) + x * s));
ret.set(2, 2, (z * z * (1.0 - c) + c));
return ret;
}
/// Converts back from the rotation matrix to euler angles.
double, double, double rotationToEulerAngles() {
if (swwm_GM_GlobalMaths.closeEnough(get(2, 0), -1)) {
double x = 90;
double y = 0;
double z = atan2(get(0, 1), get(0, 2));
return z, x, y;
}
else if (swwm_GM_GlobalMaths.closeEnough(get(2, 0), 1)) {
double x = -90;
double y = 0;
double z = atan2(-get(0, 1), -get(0, 2));
return z, x, y;
}
else {
float x1 = -asin(get(2, 0));
float x2 = 180 - x1;
float y1 = atan2(get(2, 1) / cos(x1), get(2, 2) / cos(x1));
float y2 = atan2(get(2, 1) / cos(x2), get(2, 2) / cos(x2));
float z1 = atan2(get(1, 0) / cos(x1), get(0, 0) / cos(x1));
float z2 = atan2(get(1, 0) / cos(x2), get(0, 0) / cos(x2));
if ((abs(x1) + abs(y1) + abs(z1)) <= (abs(x2) + abs(y2) + abs(z2))) {
return z1, x1, y1;
}
else {
return z2, x2, y2;
}
}
}
static swwm_GM_Matrix createTRSEuler(Vector3 translate, double yaw, double pitch, double roll, Vector3 scale) {
swwm_GM_Matrix translateMat = swwm_GM_Matrix.identity(4);
translateMat.set(0, 3, translate.x);
translateMat.set(1, 3, translate.y);
translateMat.set(2, 3, translate.z);
swwm_GM_Matrix rotateMat = swwm_GM_Matrix.fromEulerAngles(yaw, pitch, roll);
swwm_GM_Matrix scaleMat = swwm_GM_Matrix.identity(4);
scaleMat.set(0, 0, scale.x);
scaleMat.set(1, 1, scale.y);
scaleMat.set(2, 2, scale.z);
swwm_GM_Matrix ret = translateMat.multiplyMatrix(rotateMat);
ret = ret.multiplyMatrix(scaleMat);
return ret;
}
static swwm_GM_Matrix createTRSAxisAngle(Vector3 translate, Vector3 axis, double angle, Vector3 scale) {
swwm_GM_Matrix translateMat = swwm_GM_Matrix.identity(4);
translateMat.set(0, 3, translate.x);
translateMat.set(1, 3, translate.y);
translateMat.set(2, 3, translate.z);
swwm_GM_Matrix rotateMat = swwm_GM_Matrix.fromAxisAngle(axis, angle);
swwm_GM_Matrix scaleMat = swwm_GM_Matrix.identity(4);
scaleMat.set(0, 0, scale.x);
scaleMat.set(1, 1, scale.y);
scaleMat.set(2, 2, scale.z);
swwm_GM_Matrix ret = translateMat.multiplyMatrix(rotateMat);
ret = ret.multiplyMatrix(scaleMat);
return ret;
}
/// Returns a view matrix.
static swwm_GM_Matrix view(Vector3 camPos, double yaw, double pitch, double roll) {
// all of this is basically lifted and converted from PolyRenderer::SetupPerspectiveMatrix(),
// so credit goes to Graf Zahl/dpJudas/whoever else
// pitch needs to be adjusted by the pixel ratio
float pixelRatio = level.pixelstretch;
double angx = cos(pitch);
double angy = sin(pitch) * pixelRatio;
double alen = sqrt(angx * angx + angy * angy);
double adjustedPitch = asin(angy / alen);
double adjustedYaw = yaw - 90;
// rotations
swwm_GM_Matrix rotR = swwm_GM_Matrix.fromAxisAngle((0, 0, 1), roll);
swwm_GM_Matrix rotP = swwm_GM_Matrix.fromAxisAngle((1, 0, 0), adjustedPitch);
swwm_GM_Matrix rotY = swwm_GM_Matrix.fromAxisAngle((0, -1, 0), adjustedYaw);
// pixel ratio scaling
swwm_GM_Matrix scale = swwm_GM_Matrix.identity(4);
scale.set(1, 1, pixelRatio);
// swapping y and z
swwm_GM_Matrix swapYZ = swwm_GM_Matrix.create(4, 4);
swapYZ.set(0, 0, 1);
swapYZ.set(1, 2, 1);
swapYZ.set(2, 1, -1);
swapYZ.set(3, 3, 1);
// translation
swwm_GM_Matrix translate = swwm_GM_Matrix.identity(4);
translate.set(0, 3, -camPos.x);
translate.set(1, 3, -camPos.y);
translate.set(2, 3, -camPos.z);
// concatenate them all to get a final matrix
swwm_GM_Matrix ret = rotR.multiplyMatrix(rotP);
ret = ret.multiplyMatrix(rotY);
ret = ret.multiplyMatrix(scale);
ret = ret.multiplyMatrix(swapYZ);
ret = ret.multiplyMatrix(translate);
return ret;
}
/// Returns a perspective matrix (same format as gluPerspective).
static swwm_GM_Matrix perspective(double fovy, double aspect, double zNear, double zFar) {
swwm_GM_Matrix ret = swwm_GM_Matrix.create(4, 4);
double f = 1 / tan(fovy / 2.0);
// x coord
ret.set(0, 0, f / aspect);
// y coord
ret.set(1, 1, f);
// z buffer coord
ret.set(2, 2, (zFar + zNear) / (zNear - zFar));
ret.set(2, 3, (2 * zFar * zNear) / (zNear - zFar));
// w (homogeneous coordinates)
ret.set(3, 2, -1);
return ret;
}
/// Returns a world->clip coords matrix from the passed args.
static swwm_GM_Matrix worldToClip(Vector3 viewPos, double yaw, double pitch, double roll, double FOV) {
double aspect = Screen.getAspectRatio();
double fovy = swwm_GM_GlobalMaths.fovHToY(FOV);
swwm_GM_Matrix view = swwm_GM_Matrix.view(viewPos, yaw, pitch, roll);
// 5 & 65535 are what are used internally, so they're used here for consistency
swwm_GM_Matrix perp = swwm_GM_Matrix.perspective(fovy, aspect, 5, 65535);
swwm_GM_Matrix worldToClip = perp.multiplyMatrix(view);
return worldToClip;
}
/// Gets the value at row, col.
double get(int row, int col) const {
return values[columns * row + col];
}
/// Sets the value at row, col.
void set(int row, int col, double val) {
values[columns * row + col] = val;
}
/// Adds two matrices and returns the result.
swwm_GM_Matrix addMatrix(swwm_GM_Matrix other) const {
if (rows != other.rows || columns != other.columns) {
throwAbortException("Error: <%p>.addMatrix(<%p>) - Matrices need to be equal size", self, other);
}
swwm_GM_Matrix ret = swwm_GM_Matrix.create(columns, rows);
for (int row = 0; row < rows; row++) {
for (int col = 0; col < columns; col++) {
ret.set(row, col, get(row, col) + other.get(row, col));
}
}
return ret;
}
/// Multiplies the matrix by a scalar and returns the result.
swwm_GM_Matrix multiplyScalar(double scalar) const {
swwm_GM_Matrix ret = swwm_GM_Matrix.create(rows, columns);
for (int row = 0; row < rows; row++) {
for (int col = 0; col < columns; col++) {
ret.set(row, col, get(row, col) * scalar);
}
}
return ret;
}
/// Multiplies two matrices and returns the result.
swwm_GM_Matrix multiplyMatrix(swwm_GM_Matrix other) const {
if (columns != other.rows) {
throwAbortException("Error: <%p>.multiplyMatrix(<%p>) - Matrix A columns needs to equal Matrix B rows", self, other);
}
swwm_GM_Matrix ret = swwm_GM_Matrix.create(other.columns, rows);
for (int row = 0; row < ret.rows; row++) {
for (int col = 0; col < ret.columns; col++) {
double val = 0;
for (int i = 0; i < columns; i++) {
val += get(row, i) * other.get(i, col);
}
ret.set(row, col, val);
}
}
return ret;
}
/// Multiplies this Matrix by a 2D vector.
swwm_GM_Matrix multiplyVector2(Vector2 vec, swwm_GM_VectorType type = swwm_GM_Vector_Position) const {
swwm_GM_Matrix vec2Matrix = swwm_GM_Matrix.create(1, 3);
vec2Matrix.set(0, 0, vec.x);
vec2Matrix.set(1, 0, vec.y);
if (type == swwm_GM_Vector_Position) vec2Matrix.set(2, 0, 1);
else if (type == swwm_GM_Vector_Direction) vec2Matrix.set(2, 0, 0);
else throwAbortException("Error: Invalid vector type for multiplyVector2 (%d)", type);
return multiplyMatrix(vec2Matrix);
}
/// Multiplies this Matrix by a 3D vector.
swwm_GM_Matrix multiplyVector3(Vector3 vec, swwm_GM_VectorType type = swwm_GM_Vector_Position) const {
swwm_GM_Matrix vec3Matrix = swwm_GM_Matrix.create(1, 4);
vec3Matrix.set(0, 0, vec.x);
vec3Matrix.set(1, 0, vec.y);
vec3Matrix.set(2, 0, vec.z);
if (type == swwm_GM_Vector_Position) vec3Matrix.set(3, 0, 1);
else if (type == swwm_GM_Vector_Direction) vec3Matrix.set(3, 0, 0);
else throwAbortException("Error: Invalid vector type for multiplyVector3 (%d)", type);
return multiplyMatrix(vec3Matrix);
}
/// Returns the Matrix in Vector2 form, optionally dividing by z.
Vector2 asVector2(bool divideZ = true) const {
if (columns != 1 || rows != 3) {
throwAbortException("Error: <%p>.asVector2() - Matrix needs to be 1 * 3", self);
}
if (divideZ) return (get(0, 0), get(1, 0)) / get(2, 0);
else return (get(0, 0), get(1, 0));
}
/// Returns the Matrix in Vector3 form, optionally dividing by w.
Vector3 asVector3(bool divideW = true) const {
if (columns != 1 || rows != 4) {
throwAbortException("Error: <%p>.asVector3() - Matrix needs to be 1 * 4", self);
}
if (divideW) return (get(0, 0), get(1, 0), get(2, 0)) / get(3, 0);
else return (get(0, 0), get(1, 0), get(2, 0));
}
/// Returns the number of columns.
int getColumns() const {
return columns;
}
/// Returns the number of rows.
int getRows() const {
return rows;
}
}

View file

@ -1,319 +0,0 @@
class swwm_GM_Matrix4 {
double values[4][4];
/// Initialises a new Matrix4 in a static context.
static swwm_GM_Matrix4 create() {
return new("swwm_GM_Matrix4");
}
/// Returns an identity matrix.
static swwm_GM_Matrix4 identity() {
let ret = swwm_GM_Matrix4.create();
ret.values[0][0] = 1;
ret.values[1][1] = 1;
ret.values[2][2] = 1;
ret.values[3][3] = 1;
return ret;
}
/// Returns a rotation matrix from euler angles.
static swwm_GM_Matrix4 fromEulerAngles(double yaw, double pitch, double roll) {
swwm_GM_Matrix4 rYaw = swwm_GM_Matrix4.identity();
double sYaw = sin(yaw);
double cYaw = cos(yaw);
rYaw.values[0][0] = cYaw;
rYaw.values[0][1] = -sYaw;
rYaw.values[1][0] = sYaw;
rYaw.values[1][1] = cYaw;
swwm_GM_Matrix4 rPitch = swwm_GM_Matrix4.identity();
double sPitch = sin(pitch);
double cPitch = cos(pitch);
rPitch.values[0][0] = cPitch;
rPitch.values[2][0] = -sPitch;
rPitch.values[0][2] = sPitch;
rPitch.values[2][2] = cPitch;
swwm_GM_Matrix4 rRoll = swwm_GM_Matrix4.identity();
double sRoll = sin(roll);
double cRoll = cos(roll);
rRoll.values[1][1] = cRoll;
rRoll.values[1][2] = -sRoll;
rRoll.values[2][1] = sRoll;
rRoll.values[2][2] = cRoll;
// concatenate ypr to get the final matrix
swwm_GM_Matrix4 ret = rYaw.multiplyMatrix(rPitch);
ret = ret.multiplyMatrix(rRoll);
return ret;
}
/// Returns a rotation matrix from an axis and an angle.
static swwm_GM_Matrix4 fromAxisAngle(Vector3 axis, double angle) {
swwm_GM_Matrix4 ret = swwm_GM_Matrix4.identity();
double c = cos(angle);
double s = sin(angle);
double x = axis.x;
double y = axis.y;
double z = axis.z;
ret.values[0][0] = (x * x * (1.0 - c) + c);
ret.values[0][1] = (x * y * (1.0 - c) - z * s);
ret.values[0][2] = (x * z * (1.0 - c) + y * s);
ret.values[1][0] = (y * x * (1.0 - c) + z * s);
ret.values[1][1] = (y * y * (1.0 - c) + c);
ret.values[1][2] = (y * z * (1.0 - c) - x * s);
ret.values[2][0] = (x * z * (1.0 - c) - y * s);
ret.values[2][1] = (y * z * (1.0 - c) + x * s);
ret.values[2][2] = (z * z * (1.0 - c) + c);
return ret;
}
/// Converts back from the rotation matrix to euler angles.
double, double, double rotationToEulerAngles() {
if (swwm_GM_GlobalMaths.closeEnough(values[2][0], -1)) {
double x = 90;
double y = 0;
double z = atan2(values[0][1], values[0][2]);
return z, x, y;
}
else if (swwm_GM_GlobalMaths.closeEnough(values[2][0], 1)) {
double x = -90;
double y = 0;
double z = atan2(-values[0][1], -values[0][2]);
return z, x, y;
}
else {
float x1 = -asin(values[2][0]);
float x2 = 180 - x1;
float y1 = atan2(values[2][1] / cos(x1), values[2][2] / cos(x1));
float y2 = atan2(values[2][1] / cos(x2), values[2][2] / cos(x2));
float z1 = atan2(values[1][0] / cos(x1), values[0][0] / cos(x1));
float z2 = atan2(values[1][0] / cos(x2), values[0][0] / cos(x2));
if ((abs(x1) + abs(y1) + abs(z1)) <= (abs(x2) + abs(y2) + abs(z2))) {
return z1, x1, y1;
}
else {
return z2, x2, y2;
}
}
}
static swwm_GM_Matrix4 createTRSEuler(Vector3 translate, double yaw, double pitch, double roll, Vector3 scale) {
swwm_GM_Matrix4 translateMat = swwm_GM_Matrix4.identity();
translateMat.values[0][3] = translate.x;
translateMat.values[1][3] = translate.y;
translateMat.values[2][3] = translate.z;
swwm_GM_Matrix4 rotateMat = swwm_GM_Matrix4.fromEulerAngles(yaw, pitch, roll);
swwm_GM_Matrix4 scaleMat = swwm_GM_Matrix4.identity();
scaleMat.values[0][0] = scale.x;
scaleMat.values[1][1] = scale.y;
scaleMat.values[2][2] = scale.z;
swwm_GM_Matrix4 ret = translateMat.multiplyMatrix(rotateMat);
ret = ret.multiplyMatrix(scaleMat);
return ret;
}
static swwm_GM_Matrix4 createTRSAxisAngle(Vector3 translate, Vector3 axis, double angle, Vector3 scale) {
swwm_GM_Matrix4 translateMat = swwm_GM_Matrix4.identity();
translateMat.values[0][3] = translate.x;
translateMat.values[1][3] = translate.y;
translateMat.values[2][3] = translate.z;
swwm_GM_Matrix4 rotateMat = swwm_GM_Matrix4.fromAxisAngle(axis, angle);
swwm_GM_Matrix4 scaleMat = swwm_GM_Matrix4.identity();
scaleMat.values[0][0] = scale.x;
scaleMat.values[1][1] = scale.y;
scaleMat.values[2][2] = scale.z;
swwm_GM_Matrix4 ret = translateMat.multiplyMatrix(rotateMat);
ret = ret.multiplyMatrix(scaleMat);
return ret;
}
/// Returns a view matrix.
static swwm_GM_Matrix4 view(Vector3 camPos, double yaw, double pitch, double roll) {
// all of this is basically lifted and converted from PolyRenderer::SetupPerspectiveMatrix(),
// so credit goes to Graf Zahl/dpJudas/whoever else
// pitch needs to be adjusted by the pixel ratio
float pixelRatio = level.pixelstretch;
double angx = cos(pitch);
double angy = sin(pitch) * pixelRatio;
double alen = sqrt(angx * angx + angy * angy);
double adjustedPitch = asin(angy / alen);
double adjustedYaw = 90 - yaw;
// rotations
let cz = cos(roll);
let sz = sin(roll);
let cx = cos(adjustedPitch);
let sx = sin(adjustedPitch);
let cy = cos(adjustedYaw);
let sy = sin(adjustedYaw);
let rot = swwm_GM_Matrix4.create();
rot.values[0][0] = cz * cy - sz * sx * sy;
rot.values[0][1] = -sz * cx;
rot.values[0][2] = cz * sy + sz * sx * cy;
rot.values[1][0] = sz * cy + cz * sx * sy;
rot.values[1][1] = cz * cx;
rot.values[1][2] = sz * sy - cz * sx * cy;
rot.values[2][0] = -cx * sy;
rot.values[2][1] = sx;
rot.values[2][2] = cx * cy;
rot.values[3][3] = 1.0;
// pixel ratio scaling
swwm_GM_Matrix4 scale = swwm_GM_Matrix4.identity();
scale.values[1][1] = pixelRatio;
// swapping y and z
swwm_GM_Matrix4 swapYZ = swwm_GM_Matrix4.create();
swapYZ.values[0][0] = 1;
swapYZ.values[1][2] = 1;
swapYZ.values[2][1] = -1;
swapYZ.values[3][3] = 1;
// translation
swwm_GM_Matrix4 translate = swwm_GM_Matrix4.identity();
translate.values[0][3] = -camPos.x;
translate.values[1][3] = -camPos.y;
translate.values[2][3] = -camPos.z;
// concatenate them all to get a final matrix
swwm_GM_Matrix4 ret = rot.multiplyMatrix(scale);
ret = ret.multiplyMatrix(swapYZ);
ret = ret.multiplyMatrix(translate);
return ret;
}
/// Returns a perspective matrix (same format as gluPerspective).
static swwm_GM_Matrix4 perspective(double fovy, double aspect, double zNear, double zFar) {
swwm_GM_Matrix4 ret = swwm_GM_Matrix4.create();
double f = 1 / tan(fovy / 2.0);
// x coord
ret.values[0][0] = f / aspect;
// y coord
ret.values[1][1] = f;
// z buffer coord
ret.values[2][2] = (zFar + zNear) / (zNear - zFar);
ret.values[2][3] = (2 * zFar * zNear) / (zNear - zFar);
// w (homogeneous coordinates)
ret.values[3][2] = -1;
return ret;
}
/// Returns a world->clip coords matrix from the passed args.
static swwm_GM_Matrix4 worldToClip(Vector3 viewPos, double yaw, double pitch, double roll, double FOV) {
double aspect = Screen.getAspectRatio();
double fovy = swwm_GM_GlobalMaths.fovHToY(FOV);
swwm_GM_Matrix4 view = swwm_GM_Matrix4.view(viewPos, yaw, pitch, roll);
// 5 & 65535 are what are used internally, so they're used here for consistency
swwm_GM_Matrix4 perp = swwm_GM_Matrix4.perspective(fovy, aspect, 5, 65535);
swwm_GM_Matrix4 worldToClip = perp.multiplyMatrix(view);
return worldToClip;
}
/// Adds two matrices and returns the result.
swwm_GM_Matrix4 addMatrix(swwm_GM_Matrix4 other) const {
swwm_GM_Matrix4 ret = swwm_GM_Matrix4.create();
ret.values[0][0] = values[0][0] + other.values[0][0];
ret.values[0][1] = values[0][1] + other.values[0][1];
ret.values[0][2] = values[0][2] + other.values[0][2];
ret.values[0][3] = values[0][3] + other.values[0][3];
ret.values[1][0] = values[1][0] + other.values[1][0];
ret.values[1][1] = values[1][1] + other.values[1][1];
ret.values[1][2] = values[1][2] + other.values[1][2];
ret.values[1][3] = values[1][3] + other.values[1][3];
ret.values[2][0] = values[2][0] + other.values[2][0];
ret.values[2][1] = values[2][1] + other.values[2][1];
ret.values[2][2] = values[2][2] + other.values[2][2];
ret.values[2][3] = values[2][3] + other.values[2][3];
ret.values[3][0] = values[3][0] + other.values[3][0];
ret.values[3][1] = values[3][1] + other.values[3][1];
ret.values[3][2] = values[3][2] + other.values[3][2];
ret.values[3][3] = values[3][3] + other.values[3][3];
return ret;
}
/// Multiplies the matrix by a scalar and returns the result.
swwm_GM_Matrix4 multiplyScalar(double scalar) const {
swwm_GM_Matrix4 ret = swwm_GM_Matrix4.create();
ret.values[0][0] = values[0][0] * scalar;
ret.values[0][1] = values[0][1] * scalar;
ret.values[0][2] = values[0][2] * scalar;
ret.values[0][3] = values[0][3] * scalar;
ret.values[1][0] = values[1][0] * scalar;
ret.values[1][1] = values[1][1] * scalar;
ret.values[1][2] = values[1][2] * scalar;
ret.values[1][3] = values[1][3] * scalar;
ret.values[2][0] = values[2][0] * scalar;
ret.values[2][1] = values[2][1] * scalar;
ret.values[2][2] = values[2][2] * scalar;
ret.values[2][3] = values[2][3] * scalar;
ret.values[3][0] = values[3][0] * scalar;
ret.values[3][1] = values[3][1] * scalar;
ret.values[3][2] = values[3][2] * scalar;
ret.values[3][3] = values[3][3] * scalar;
return ret;
}
/// Multiplies two matrices and returns the result.
swwm_GM_Matrix4 multiplyMatrix(swwm_GM_Matrix4 other) const {
swwm_GM_Matrix4 ret = swwm_GM_Matrix4.create();
for (int row = 0; row < 4; row++) {
ret.values[row][0] =
values[row][0] * other.values[0][0] +
values[row][1] * other.values[1][0] +
values[row][2] * other.values[2][0] +
values[row][3] * other.values[3][0];
ret.values[row][1] =
values[row][0] * other.values[0][1] +
values[row][1] * other.values[1][1] +
values[row][2] * other.values[2][1] +
values[row][3] * other.values[3][1];
ret.values[row][2] =
values[row][0] * other.values[0][2] +
values[row][1] * other.values[1][2] +
values[row][2] * other.values[2][2] +
values[row][3] * other.values[3][2];
ret.values[row][3] =
values[row][0] * other.values[0][3] +
values[row][1] * other.values[1][3] +
values[row][2] * other.values[2][3] +
values[row][3] * other.values[3][3];
}
return ret;
}
/// Multiplies this Matrix by a 3D vector.
Vector3 multiplyVector3(Vector3 vec, swwm_GM_VectorType type = swwm_GM_Vector_Position, bool divideW = true) const {
let vecW = (type == swwm_GM_Vector_Position) ? 1.0 : 0.0;
let ret = (
values[0][0] * vec.x + values[0][1] * vec.y + values[0][2] * vec.z + values[0][3] * vecW,
values[1][0] * vec.x + values[1][1] * vec.y + values[1][2] * vec.z + values[1][3] * vecW,
values[2][0] * vec.x + values[2][1] * vec.y + values[2][2] * vec.z + values[2][3] * vecW
);
if (divideW) {
let retW = values[3][0] * vec.x + values[3][1] * vec.y + values[3][2] * vec.z + values[3][3] * vecW;
ret /= retW;
}
return ret;
}
}

View file

@ -1,275 +0,0 @@
class swwm_GM_Quaternion {
double w, x, y, z;
/// Initialises the Quaternion.
swwm_GM_Quaternion init(double w, double x, double y, double z) {
self.w = w;
self.x = x;
self.y = y;
self.z = z;
return self;
}
/// Initialises the Quaternion in a static context.
static swwm_GM_Quaternion create(double w, double x, double y, double z) {
return new("swwm_GM_Quaternion").init(w, x, y, z);
}
/// Sets up the quaternion using axis and angle.
void setAxisAngle(Vector3 axis, double angle) {
double lengthSquared = axis dot axis;
// avoid a division by 0 and just return the identity
if (swwm_GM_GlobalMaths.closeEnough(lengthSquared, 0)) {
init(1, 0, 0, 0);
return;
}
angle *= 0.5;
double sinTheta = sin(angle);
double cosTheta = cos(angle);
double factor = sinTheta / sqrt(lengthSquared);
w = cosTheta;
x = factor * axis.x;
y = factor * axis.y;
z = factor * axis.z;
}
/// Initialises the Quaternion using axis and angle.
swwm_GM_Quaternion initFromAxisAngle(Vector3 axis, double angle) {
setAxisAngle(axis, angle);
return self;
}
/// Initialises the Quaternion using axis and angle in a static context.
static swwm_GM_Quaternion createFromAxisAngle(Vector3 axis, double angle) {
return new("swwm_GM_Quaternion").initFromAxisAngle(axis, angle);
}
/// Sets up the quaternion using euler angles.
void setAngles(double yaw, double pitch, double roll) {
swwm_GM_Quaternion zRotation = new("swwm_GM_Quaternion").initFromAxisAngle((0, 0, 1), yaw);
swwm_GM_Quaternion yRotation = new("swwm_GM_Quaternion").initFromAxisAngle((0, 1, 0), pitch);
swwm_GM_Quaternion xRotation = new("swwm_GM_Quaternion").initFromAxisAngle((1, 0, 0), roll);
swwm_GM_Quaternion finalRotation = zRotation.multiplyQuat(yRotation);
finalRotation = finalRotation.multiplyQuat(xRotation);
copy(finalRotation);
}
/// Initialises the quaternion using euler angles.
swwm_GM_Quaternion initFromAngles(double yaw, double pitch, double roll) {
setAngles(yaw, pitch, roll);
return self;
}
/// Initialises the quaternion using euler angles in a static context.
static swwm_GM_Quaternion createFromAngles(double yaw, double pitch, double roll) {
return new("swwm_GM_Quaternion").initFromAngles(yaw, pitch, roll);
}
/// Returns the euler angles from the Quaternion.
double, double, double toAngles() {
double singularityTest = z * x - w * y;
double yawY = 2 * (w * z + x * y);
double yawX = (1 - 2 * (y * y + z * z));
double singularityThreshold = 0.4999995;
double yaw = 0;
double pitch = 0;
double roll = 0;
if (singularityTest < -singularityThreshold) {
pitch = 90;
yaw = atan2(yawY, yawX);
roll = swwm_GM_GlobalMaths.normalize180(yaw + (2 * atan2(x, w)));
}
else if (singularityTest > singularityThreshold) {
pitch = -90;
yaw = atan2(yawY, yawX);
roll = swwm_GM_GlobalMaths.normalize180(yaw + (2 * atan2(x, w)));
}
else {
pitch = -asin(2 * singularityTest);
yaw = atan2(yawY, yawX);
roll = atan2(2 * (w * x + y * z), (1 - 2 * (x * x + y * y)));
}
return yaw, pitch, roll;
}
/// Returns the conjugate of the Quaternion.
swwm_GM_Quaternion conjugate() const {
return new("swwm_GM_Quaternion").init(w, -x, -y, -z);
}
/// Returns the normalised form of the Quaternion.
swwm_GM_Quaternion unit() const {
double lengthSquared = w * w + x * x + y * y + z * z;
if (swwm_GM_GlobalMaths.closeEnough(lengthSquared, 0)) {
return zero();
}
double factor = 1 / sqrt(lengthSquared);
return new("swwm_GM_Quaternion").init(w * factor, x * factor, y * factor, z * factor);
}
/// Returns the inverse of the Quaternion (equal to conjugate if normalised).
swwm_GM_Quaternion inverse() {
double norm = w * w + x * x + y * y + z * z;
// if this is a zero quaternion, just return self
if (swwm_GM_GlobalMaths.closeEnough(norm, 0)) {
return self;
}
double inverseNorm = 1/norm;
return new("swwm_GM_Quaternion").init(w * inverseNorm, x * -inverseNorm, y * -inverseNorm, z * -inverseNorm);
}
/// Adds two Quaternions, returning the result.
swwm_GM_Quaternion add(swwm_GM_Quaternion other) const {
return new("swwm_GM_Quaternion").init(w + other.w, x + other.x, y + other.y, z + other.z);
}
/// Subtracts two Quaternions, returning the result.
swwm_GM_Quaternion subtract(swwm_GM_Quaternion other) const {
return new("swwm_GM_Quaternion").init(w - other.w, x - other.x, y - other.y, z - other.z);
}
/// Multiplies the Quaternion by a scalar, returning the result.
swwm_GM_Quaternion multiplyScalar(double scalar) const {
return new("swwm_GM_Quaternion").init(w * scalar, x * scalar, y * scalar, z * scalar);
}
/// Multiplies two Quaternions, returning the result.
swwm_GM_Quaternion multiplyQuat(swwm_GM_Quaternion other) const {
return new("swwm_GM_Quaternion").init(w * other.w - x * other.x - y * other.y - z * other.z,
w * other.x + x * other.w + y * other.z - z * other.y,
w * other.y + y * other.w + z * other.x - x * other.z,
w * other.z + z * other.w + x * other.y - y * other.x );
}
/// Negates the Quaternion.
swwm_GM_Quaternion negate() const {
return new("swwm_GM_Quaternion").init(-w, -x, -y, -z);
}
/// Sets the values to 0 if they're close enough to 0.
void clean() {
if (swwm_GM_GlobalMaths.closeEnough(w, 0)) w = 0;
if (swwm_GM_GlobalMaths.closeEnough(x, 0)) x = 0;
if (swwm_GM_GlobalMaths.closeEnough(y, 0)) y = 0;
if (swwm_GM_GlobalMaths.closeEnough(z, 0)) z = 0;
}
/// Returns the length of the Quaternion squared.
double lengthSquared() const {
return (w * w + x * x + y * y + z * z);
}
/// Returns the length of the Quaternion.
double length() const {
return sqrt(w * w + x * x + y * y + z * z);
}
/// Returns whether the two Quaternions are equal.
bool equals(swwm_GM_Quaternion other) const {
return swwm_GM_GlobalMaths.closeEnough(w, other.w) && swwm_GM_GlobalMaths.closeEnough(x, other.x) &&
swwm_GM_GlobalMaths.closeEnough(y, other.y) && swwm_GM_GlobalMaths.closeEnough(z, other.z) ;
}
/// Returns if the Quaternion is a 0 Quaternion.
bool isZero() const {
return swwm_GM_GlobalMaths.closeEnough(w * w + x * x + y * y + z * z, 0);
}
/// Returns if the Quaternion is a unit Quaternion.
bool isUnit() const {
return swwm_GM_GlobalMaths.closeEnough(w * w + x * x + y * y + z * z, 1);
}
/// Returns if the Quaternion is an identity Quaternion.
bool isIdentity() const {
return swwm_GM_GlobalMaths.closeEnough(w, 1) && swwm_GM_GlobalMaths.closeEnough(x, 0) &&
swwm_GM_GlobalMaths.closeEnough(y, 0) && swwm_GM_GlobalMaths.closeEnough(z, 0) ;
}
/// Returns the dot product of two Quaternions.
double dotProduct(swwm_GM_Quaternion other) const {
return (w * other.w + x * other.x + y * other.y + z * other.z);
}
/// Copies another Quaternion into this one.
void copy(swwm_GM_Quaternion other) {
w = other.w;
x = other.x;
y = other.y;
z = other.z;
}
/// Rotates a Vector3 using this Quaternion.
Vector3 rotateVector3(Vector3 vec) const {
swwm_GM_Quaternion q = unit();
Vector3 u = (q.x, q.y, q.z);
double s = q.w;
return 2 * (u dot vec) * u + (s * s - (u dot u)) * vec + 2 * s * u cross vec;
}
/// Linearly interpolates between two Quaternions, clamping the parameters.
static swwm_GM_Quaternion lerp(swwm_GM_Quaternion from, swwm_GM_Quaternion to, double time) {
time = clamp(time, 0, 1);
return lerpUnclamped(from, to, time);
}
/// Linearly interpolates between two Quaternions.
static swwm_GM_Quaternion lerpUnclamped(swwm_GM_Quaternion from, swwm_GM_Quaternion to, double time) {
swwm_GM_Quaternion ret = new("swwm_GM_Quaternion");
double reverseTime = 1 - time;
ret.x = reverseTime * from.x + time * to.x;
ret.y = reverseTime * from.y + time * to.y;
ret.z = reverseTime * from.z + time * to.z;
ret.w = reverseTime * from.w + time * to.w;
ret = ret.unit();
return ret;
}
/// Spherically interpolates between two Quaternions, clamping the parameters.
static swwm_GM_Quaternion slerp(swwm_GM_Quaternion from, swwm_GM_Quaternion to, double time) {
time = clamp(time, 0, 1);
return slerpUnclamped(from, to, time);
}
/// Spherically interpolates between two Quaternions.
static swwm_GM_Quaternion slerpUnclamped(swwm_GM_Quaternion from, swwm_GM_Quaternion to, double time) {
swwm_GM_Quaternion q3;
double fromToDot = from.dotProduct(to);
if (fromToDot < 0) {
fromToDot = -fromToDot;
q3 = to.negate();
}
else {
q3 = to;
}
if (fromToDot < 0.95) {
double angle = acos(fromToDot);
return ((from.multiplyScalar(sin(angle * (1 - time)))).add(q3.multiplyScalar(sin(angle * time)))).multiplyScalar(1 / sin(angle));
}
else {
return lerp(from, q3, time);
}
}
/// Returns the 0 Quaternion.
static swwm_GM_Quaternion zero() {
return new("swwm_GM_Quaternion").init(0, 0, 0, 0);
}
/// Returns the identity Quaternion.
static swwm_GM_Quaternion identity() {
return new("swwm_GM_Quaternion").init(1, 0, 0, 0);
}
}

View file

@ -1,29 +0,0 @@
class swwm_GM_VectorUtil {
/// Linearly interpolates between two Vector3s, clamping the parameters.
static Vector3 lerpVec3(Vector3 from, Vector3 to, double time) {
time = clamp(time, 0, 1);
return lerpUnclampedVec3(from, to, time);
}
/// Linearly interpolates between two Vector3s.
static Vector3 lerpUnclampedVec3(Vector3 from, Vector3 to, double time) {
Vector3 ret;
double reverseTime = 1 - time;
ret = reverseTime * from + time * to;
return ret;
}
/// Linearly interpolates between two Vector2s, clamping the parameters.
static Vector2 lerpVec2(Vector2 from, Vector2 to, double time) {
time = clamp(time, 0, 1);
return lerpUnclampedVec2(from, to, time);
}
/// Linearly interpolates between two Vector2s.
static Vector2 lerpUnclampedVec2(Vector2 from, Vector2 to, double time) {
Vector2 ret;
double reverseTime = 1 - time;
ret = reverseTime * from + time * to;
return ret;
}
}

View file

@ -1,25 +1,32 @@
// math stuff + gutamatics caching
// math stuff
Struct SWWMProjectionData
{
swwm_GM_Matrix wtc;
double tanfovx, tanfovy;
Vector3 viewpos, z, x, y;
int viewx, viewy, vieww, viewh;
}
extend Class SWWMUtility
{
// gutamatics caching
// cache some data that requires trig and quat math
static void PrepareProjData( SWWMProjectionData &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();
// 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
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();
@ -32,14 +39,36 @@ extend Class SWWMUtility
d.viewh = h;
}
static Vector3 ProjectPoint( SWWMProjectionData d, Vector3 worldpos )
// 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 Vector3 ProjectPoint( SWWMProjectionData d, Vector3 worldpos, bool bFast = true )
{
return d.wtc.multiplyVector3(worldpos).asVector3();
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 Vector2 NDCToViewport( SWWMProjectionData d, Vector3 ndc )
static Vector2 NDCToViewport( SWWMProjectionData d, Vector3 cpos )
{
return (d.viewx,d.viewy)+(((ndc.x+1)*d.vieww)/2,((-ndc.y+1)*d.viewh)/2);
return (d.viewx+(cpos.x*.5*d.vieww),d.viewy+(cpos.y*.5*d.viewh));
}
// checks if a point is inside the viewport
@ -95,7 +124,12 @@ extend Class SWWMUtility
return r*(1,0,0), r*(0,-1,0), r*(0,0,1);
}
// included here until Gutamatics updates to use native quaternions
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.;
@ -106,13 +140,13 @@ extend Class SWWMUtility
{
angle = atan2(angY,angX);
pitch = 90.;
roll = swwm_GM_GlobalMaths.Normalize180(angle+(2.*atan2(q.x,q.w)));
roll = 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)));
roll = Normalize180(angle+(2.*atan2(q.x,q.w)));
}
else
{

View file

@ -45,7 +45,7 @@ Class Hellblazer : SWWMWeapon
if ( !seektarget[i] ) continue;
Vector3 tpos = SWWMUtility.LerpVector3(seektarget[i].prev,seektarget[i].pos,e.FracTic);
Vector3 ndc = SWWMUtility.ProjectPoint(projdata,e.viewpos+level.Vec3Diff(e.viewpos,tpos+(0,0,seektarget[i].Height/2)));
if ( ndc.z > 1. ) continue;
if ( ndc.z <= 0. ) continue;
Vector2 vpos = SWWMUtility.NDCToViewport(projdata,ndc);
Screen.DrawTexture(LockIcon,false,vpos.x/hs,vpos.y/hs,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_CenterOffset,true,DTA_ClipTop,cliptop,DTA_ClipBottom,clipbottom,DTA_ClipLeft,clipleft,DTA_ClipRight,clipright);
}