1.3.2 update: Remove third party library I no longer depend on.

This commit is contained in:
Marisa the Magician 2025-11-01 13:42:16 +01:00
commit 90a07ee257
10 changed files with 58 additions and 1170 deletions

View file

@ -2,5 +2,4 @@ Models, textures and audio (C)1999 Epic Games.
Porting and adjustments by Marisa the Magician. Porting and adjustments by Marisa the Magician.
Special thanks to Gutawer for Gutamatics. Special thanks to Skerion for the video used by the anti-bd switch.
Special thanks also to Skerion for the video used by the anti-bd switch.

View file

@ -3,15 +3,13 @@ version "4.11"
/* /*
Doom Tournament main codebase Doom Tournament main codebase
Save for the third party Gutamatics library, all code here is (C)2018-2025 Marisa the Magician, UnSX Team.
(C)2018-2025 Marisa the Magician, UnSX Team, and is released under the released under the MIT license (see LICENSE.txt).
terms of the MIT license (see LICENSE.txt).
No proprietary code by Epic Games has been copied, with or without No proprietary code by Epic Games has been copied, with or without
modification, in the process of developing this project. modification, in the process of developing this project.
*/ */
#include "zscript/dt_Gutamatics/Include.zsc"
#include "zscript/dt_utility.zsc" #include "zscript/dt_utility.zsc"
#include "zscript/utcommon.zsc" #include "zscript/utcommon.zsc"
#include "zscript/utgore.zsc" #include "zscript/utgore.zsc"

View file

@ -1,173 +0,0 @@
class dt_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/dt_Gutamatics/GlobalMaths.zsc"
#include "zscript/dt_Gutamatics/Matrix.zsc"
#include "zscript/dt_Gutamatics/Matrix4.zsc"
#include "zscript/dt_Gutamatics/Quaternion.zsc"
#include "zscript/dt_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 dt_GM_VectorType {
dt_GM_Vector_Position,
dt_GM_Vector_Direction
}
class dt_GM_Matrix {
private Array<double> values;
private int columns;
private int rows;
/// Initialises a new Matrix.
dt_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 dt_GM_Matrix create(int columns, int rows) {
return new("dt_GM_Matrix").init(columns, rows);
}
/// Returns an identity matrix.
static dt_GM_Matrix identity(int dimension) {
dt_GM_Matrix ret = dt_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 dt_GM_Matrix fromEulerAngles(double yaw, double pitch, double roll) {
dt_GM_Matrix rYaw = dt_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);
dt_GM_Matrix rPitch = dt_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);
dt_GM_Matrix rRoll = dt_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
dt_GM_Matrix ret = rYaw.multiplyMatrix(rPitch);
ret = ret.multiplyMatrix(rRoll);
return ret;
}
/// Returns a rotation matrix from an axis and an angle.
static dt_GM_Matrix fromAxisAngle(Vector3 axis, double angle) {
dt_GM_Matrix ret = dt_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 (dt_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 (dt_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 dt_GM_Matrix createTRSEuler(Vector3 translate, double yaw, double pitch, double roll, Vector3 scale) {
dt_GM_Matrix translateMat = dt_GM_Matrix.identity(4);
translateMat.set(0, 3, translate.x);
translateMat.set(1, 3, translate.y);
translateMat.set(2, 3, translate.z);
dt_GM_Matrix rotateMat = dt_GM_Matrix.fromEulerAngles(yaw, pitch, roll);
dt_GM_Matrix scaleMat = dt_GM_Matrix.identity(4);
scaleMat.set(0, 0, scale.x);
scaleMat.set(1, 1, scale.y);
scaleMat.set(2, 2, scale.z);
dt_GM_Matrix ret = translateMat.multiplyMatrix(rotateMat);
ret = ret.multiplyMatrix(scaleMat);
return ret;
}
static dt_GM_Matrix createTRSAxisAngle(Vector3 translate, Vector3 axis, double angle, Vector3 scale) {
dt_GM_Matrix translateMat = dt_GM_Matrix.identity(4);
translateMat.set(0, 3, translate.x);
translateMat.set(1, 3, translate.y);
translateMat.set(2, 3, translate.z);
dt_GM_Matrix rotateMat = dt_GM_Matrix.fromAxisAngle(axis, angle);
dt_GM_Matrix scaleMat = dt_GM_Matrix.identity(4);
scaleMat.set(0, 0, scale.x);
scaleMat.set(1, 1, scale.y);
scaleMat.set(2, 2, scale.z);
dt_GM_Matrix ret = translateMat.multiplyMatrix(rotateMat);
ret = ret.multiplyMatrix(scaleMat);
return ret;
}
/// Returns a view matrix.
static dt_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
dt_GM_Matrix rotR = dt_GM_Matrix.fromAxisAngle((0, 0, 1), roll);
dt_GM_Matrix rotP = dt_GM_Matrix.fromAxisAngle((1, 0, 0), adjustedPitch);
dt_GM_Matrix rotY = dt_GM_Matrix.fromAxisAngle((0, -1, 0), adjustedYaw);
// pixel ratio scaling
dt_GM_Matrix scale = dt_GM_Matrix.identity(4);
scale.set(1, 1, pixelRatio);
// swapping y and z
dt_GM_Matrix swapYZ = dt_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
dt_GM_Matrix translate = dt_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
dt_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 dt_GM_Matrix perspective(double fovy, double aspect, double zNear, double zFar) {
dt_GM_Matrix ret = dt_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 dt_GM_Matrix worldToClip(Vector3 viewPos, double yaw, double pitch, double roll, double FOV) {
double aspect = Screen.getAspectRatio();
double fovy = dt_GM_GlobalMaths.fovHToY(FOV);
dt_GM_Matrix view = dt_GM_Matrix.view(viewPos, yaw, pitch, roll);
// 5 & 65535 are what are used internally, so they're used here for consistency
dt_GM_Matrix perp = dt_GM_Matrix.perspective(fovy, aspect, 5, 65535);
dt_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.
dt_GM_Matrix addMatrix(dt_GM_Matrix other) const {
if (rows != other.rows || columns != other.columns) {
throwAbortException("Error: <%p>.addMatrix(<%p>) - Matrices need to be equal size", self, other);
}
dt_GM_Matrix ret = dt_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.
dt_GM_Matrix multiplyScalar(double scalar) const {
dt_GM_Matrix ret = dt_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.
dt_GM_Matrix multiplyMatrix(dt_GM_Matrix other) const {
if (columns != other.rows) {
throwAbortException("Error: <%p>.multiplyMatrix(<%p>) - Matrix A columns needs to equal Matrix B rows", self, other);
}
dt_GM_Matrix ret = dt_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.
dt_GM_Matrix multiplyVector2(Vector2 vec, dt_GM_VectorType type = dt_GM_Vector_Position) const {
dt_GM_Matrix vec2Matrix = dt_GM_Matrix.create(1, 3);
vec2Matrix.set(0, 0, vec.x);
vec2Matrix.set(1, 0, vec.y);
if (type == dt_GM_Vector_Position) vec2Matrix.set(2, 0, 1);
else if (type == dt_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.
dt_GM_Matrix multiplyVector3(Vector3 vec, dt_GM_VectorType type = dt_GM_Vector_Position) const {
dt_GM_Matrix vec3Matrix = dt_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 == dt_GM_Vector_Position) vec3Matrix.set(3, 0, 1);
else if (type == dt_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 dt_GM_Matrix4 {
double values[4][4];
/// Initialises a new Matrix4 in a static context.
static dt_GM_Matrix4 create() {
return new("dt_GM_Matrix4");
}
/// Returns an identity matrix.
static dt_GM_Matrix4 identity() {
let ret = dt_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 dt_GM_Matrix4 fromEulerAngles(double yaw, double pitch, double roll) {
dt_GM_Matrix4 rYaw = dt_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;
dt_GM_Matrix4 rPitch = dt_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;
dt_GM_Matrix4 rRoll = dt_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
dt_GM_Matrix4 ret = rYaw.multiplyMatrix(rPitch);
ret = ret.multiplyMatrix(rRoll);
return ret;
}
/// Returns a rotation matrix from an axis and an angle.
static dt_GM_Matrix4 fromAxisAngle(Vector3 axis, double angle) {
dt_GM_Matrix4 ret = dt_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 (dt_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 (dt_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 dt_GM_Matrix4 createTRSEuler(Vector3 translate, double yaw, double pitch, double roll, Vector3 scale) {
dt_GM_Matrix4 translateMat = dt_GM_Matrix4.identity();
translateMat.values[0][3] = translate.x;
translateMat.values[1][3] = translate.y;
translateMat.values[2][3] = translate.z;
dt_GM_Matrix4 rotateMat = dt_GM_Matrix4.fromEulerAngles(yaw, pitch, roll);
dt_GM_Matrix4 scaleMat = dt_GM_Matrix4.identity();
scaleMat.values[0][0] = scale.x;
scaleMat.values[1][1] = scale.y;
scaleMat.values[2][2] = scale.z;
dt_GM_Matrix4 ret = translateMat.multiplyMatrix(rotateMat);
ret = ret.multiplyMatrix(scaleMat);
return ret;
}
static dt_GM_Matrix4 createTRSAxisAngle(Vector3 translate, Vector3 axis, double angle, Vector3 scale) {
dt_GM_Matrix4 translateMat = dt_GM_Matrix4.identity();
translateMat.values[0][3] = translate.x;
translateMat.values[1][3] = translate.y;
translateMat.values[2][3] = translate.z;
dt_GM_Matrix4 rotateMat = dt_GM_Matrix4.fromAxisAngle(axis, angle);
dt_GM_Matrix4 scaleMat = dt_GM_Matrix4.identity();
scaleMat.values[0][0] = scale.x;
scaleMat.values[1][1] = scale.y;
scaleMat.values[2][2] = scale.z;
dt_GM_Matrix4 ret = translateMat.multiplyMatrix(rotateMat);
ret = ret.multiplyMatrix(scaleMat);
return ret;
}
/// Returns a view matrix.
static dt_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 = dt_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
dt_GM_Matrix4 scale = dt_GM_Matrix4.identity();
scale.values[1][1] = pixelRatio;
// swapping y and z
dt_GM_Matrix4 swapYZ = dt_GM_Matrix4.create();
swapYZ.values[0][0] = 1;
swapYZ.values[1][2] = 1;
swapYZ.values[2][1] = -1;
swapYZ.values[3][3] = 1;
// translation
dt_GM_Matrix4 translate = dt_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
dt_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 dt_GM_Matrix4 perspective(double fovy, double aspect, double zNear, double zFar) {
dt_GM_Matrix4 ret = dt_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 dt_GM_Matrix4 worldToClip(Vector3 viewPos, double yaw, double pitch, double roll, double FOV) {
double aspect = Screen.getAspectRatio();
double fovy = dt_GM_GlobalMaths.fovHToY(FOV);
dt_GM_Matrix4 view = dt_GM_Matrix4.view(viewPos, yaw, pitch, roll);
// 5 & 65535 are what are used internally, so they're used here for consistency
dt_GM_Matrix4 perp = dt_GM_Matrix4.perspective(fovy, aspect, 5, 65535);
dt_GM_Matrix4 worldToClip = perp.multiplyMatrix(view);
return worldToClip;
}
/// Adds two matrices and returns the result.
dt_GM_Matrix4 addMatrix(dt_GM_Matrix4 other) const {
dt_GM_Matrix4 ret = dt_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.
dt_GM_Matrix4 multiplyScalar(double scalar) const {
dt_GM_Matrix4 ret = dt_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.
dt_GM_Matrix4 multiplyMatrix(dt_GM_Matrix4 other) const {
dt_GM_Matrix4 ret = dt_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, dt_GM_VectorType type = dt_GM_Vector_Position, bool divideW = true) const {
let vecW = (type == dt_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 dt_GM_Quaternion {
double w, x, y, z;
/// Initialises the Quaternion.
dt_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 dt_GM_Quaternion create(double w, double x, double y, double z) {
return new("dt_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 (dt_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.
dt_GM_Quaternion initFromAxisAngle(Vector3 axis, double angle) {
setAxisAngle(axis, angle);
return self;
}
/// Initialises the Quaternion using axis and angle in a static context.
static dt_GM_Quaternion createFromAxisAngle(Vector3 axis, double angle) {
return new("dt_GM_Quaternion").initFromAxisAngle(axis, angle);
}
/// Sets up the quaternion using euler angles.
void setAngles(double yaw, double pitch, double roll) {
dt_GM_Quaternion zRotation = new("dt_GM_Quaternion").initFromAxisAngle((0, 0, 1), yaw);
dt_GM_Quaternion yRotation = new("dt_GM_Quaternion").initFromAxisAngle((0, 1, 0), pitch);
dt_GM_Quaternion xRotation = new("dt_GM_Quaternion").initFromAxisAngle((1, 0, 0), roll);
dt_GM_Quaternion finalRotation = zRotation.multiplyQuat(yRotation);
finalRotation = finalRotation.multiplyQuat(xRotation);
copy(finalRotation);
}
/// Initialises the quaternion using euler angles.
dt_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 dt_GM_Quaternion createFromAngles(double yaw, double pitch, double roll) {
return new("dt_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 = dt_GM_GlobalMaths.normalize180(yaw + (2 * atan2(x, w)));
}
else if (singularityTest > singularityThreshold) {
pitch = -90;
yaw = atan2(yawY, yawX);
roll = dt_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.
dt_GM_Quaternion conjugate() const {
return new("dt_GM_Quaternion").init(w, -x, -y, -z);
}
/// Returns the normalised form of the Quaternion.
dt_GM_Quaternion unit() const {
double lengthSquared = w * w + x * x + y * y + z * z;
if (dt_GM_GlobalMaths.closeEnough(lengthSquared, 0)) {
return zero();
}
double factor = 1 / sqrt(lengthSquared);
return new("dt_GM_Quaternion").init(w * factor, x * factor, y * factor, z * factor);
}
/// Returns the inverse of the Quaternion (equal to conjugate if normalised).
dt_GM_Quaternion inverse() {
double norm = w * w + x * x + y * y + z * z;
// if this is a zero quaternion, just return self
if (dt_GM_GlobalMaths.closeEnough(norm, 0)) {
return self;
}
double inverseNorm = 1/norm;
return new("dt_GM_Quaternion").init(w * inverseNorm, x * -inverseNorm, y * -inverseNorm, z * -inverseNorm);
}
/// Adds two Quaternions, returning the result.
dt_GM_Quaternion add(dt_GM_Quaternion other) const {
return new("dt_GM_Quaternion").init(w + other.w, x + other.x, y + other.y, z + other.z);
}
/// Subtracts two Quaternions, returning the result.
dt_GM_Quaternion subtract(dt_GM_Quaternion other) const {
return new("dt_GM_Quaternion").init(w - other.w, x - other.x, y - other.y, z - other.z);
}
/// Multiplies the Quaternion by a scalar, returning the result.
dt_GM_Quaternion multiplyScalar(double scalar) const {
return new("dt_GM_Quaternion").init(w * scalar, x * scalar, y * scalar, z * scalar);
}
/// Multiplies two Quaternions, returning the result.
dt_GM_Quaternion multiplyQuat(dt_GM_Quaternion other) const {
return new("dt_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.
dt_GM_Quaternion negate() const {
return new("dt_GM_Quaternion").init(-w, -x, -y, -z);
}
/// Sets the values to 0 if they're close enough to 0.
void clean() {
if (dt_GM_GlobalMaths.closeEnough(w, 0)) w = 0;
if (dt_GM_GlobalMaths.closeEnough(x, 0)) x = 0;
if (dt_GM_GlobalMaths.closeEnough(y, 0)) y = 0;
if (dt_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(dt_GM_Quaternion other) const {
return dt_GM_GlobalMaths.closeEnough(w, other.w) && dt_GM_GlobalMaths.closeEnough(x, other.x) &&
dt_GM_GlobalMaths.closeEnough(y, other.y) && dt_GM_GlobalMaths.closeEnough(z, other.z) ;
}
/// Returns if the Quaternion is a 0 Quaternion.
bool isZero() const {
return dt_GM_GlobalMaths.closeEnough(w * w + x * x + y * y + z * z, 0);
}
/// Returns if the Quaternion is a unit Quaternion.
bool isUnit() const {
return dt_GM_GlobalMaths.closeEnough(w * w + x * x + y * y + z * z, 1);
}
/// Returns if the Quaternion is an identity Quaternion.
bool isIdentity() const {
return dt_GM_GlobalMaths.closeEnough(w, 1) && dt_GM_GlobalMaths.closeEnough(x, 0) &&
dt_GM_GlobalMaths.closeEnough(y, 0) && dt_GM_GlobalMaths.closeEnough(z, 0) ;
}
/// Returns the dot product of two Quaternions.
double dotProduct(dt_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(dt_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 {
dt_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 dt_GM_Quaternion lerp(dt_GM_Quaternion from, dt_GM_Quaternion to, double time) {
time = clamp(time, 0, 1);
return lerpUnclamped(from, to, time);
}
/// Linearly interpolates between two Quaternions.
static dt_GM_Quaternion lerpUnclamped(dt_GM_Quaternion from, dt_GM_Quaternion to, double time) {
dt_GM_Quaternion ret = new("dt_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 dt_GM_Quaternion slerp(dt_GM_Quaternion from, dt_GM_Quaternion to, double time) {
time = clamp(time, 0, 1);
return slerpUnclamped(from, to, time);
}
/// Spherically interpolates between two Quaternions.
static dt_GM_Quaternion slerpUnclamped(dt_GM_Quaternion from, dt_GM_Quaternion to, double time) {
dt_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 dt_GM_Quaternion zero() {
return new("dt_GM_Quaternion").init(0, 0, 0, 0);
}
/// Returns the identity Quaternion.
static dt_GM_Quaternion identity() {
return new("dt_GM_Quaternion").init(1, 0, 0, 0);
}
}

View file

@ -1,29 +0,0 @@
class dt_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,28 +1,35 @@
// gutamatics projection data caching and other functions from SWWM GZ // screen projection and other functions from SWWM GZ
Struct dt_ProjectionData Struct dt_ProjectionData
{ {
dt_GM_Matrix wtc; double tanfovx, tanfovy;
Vector3 viewpos, z, x, y;
int viewx, viewy, vieww, viewh; int viewx, viewy, vieww, viewh;
} }
Class dt_Utility Class dt_Utility
{ {
// gutamatics caching // cache some data that requires trig and quat math
static clearscope void PrepareProjData( out dt_ProjectionData d, Vector3 viewpos, double angle, double pitch, double roll, double fov ) 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 aspect = Screen.GetAspectRatio();
// vertical fov
double fovratio = (aspect>=1.3)?1.333333:aspect; double fovratio = (aspect>=1.3)?1.333333:aspect;
double fovy = 2.*atan(tan(clamp(fov,5,170)/2.)/fovratio); d.tanfovy = tan(clamp(fov,5,170)/2.)/fovratio;
// world→clip matrix d.tanfovx = d.tanfovy*aspect;
dt_GM_Matrix view = dt_GM_Matrix.view(viewpos,angle,pitch,roll); // precalc view-space axes
dt_GM_Matrix perp = dt_GM_Matrix.perspective(fovy,aspect,5,65535); // (don't forget pixel stretch, very important)
d.wtc = perp.multiplyMatrix(view); Quat r = Quat.FromAngles(angle,pitch,roll);
// screen coord data 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(); int sblocks = CVar.FindCVar('screenblocks').GetInt();
int viewx, viewy, vieww, viewh; let [viewx, viewy, vieww, viewh] = Screen.GetViewWindow();
[viewx, viewy, vieww, viewh] = Screen.GetViewWindow();
int sh = Screen.GetHeight(); int sh = Screen.GetHeight();
int h = sh; int h = sh;
if ( sblocks < 10 ) h = (sblocks*sh/10)&~7; if ( sblocks < 10 ) h = (sblocks*sh/10)&~7;
@ -33,21 +40,43 @@ Class dt_Utility
d.viewh = h; d.viewh = h;
} }
static clearscope Vector3 ProjectPoint( dt_ProjectionData 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 clearscope Vector3 ProjectPoint( dt_ProjectionData 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 clearscope Vector2 NDCToViewport( dt_ProjectionData d, Vector3 ndc ) static clearscope Vector2 NDCToViewport( dt_ProjectionData 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 // checks if a point is inside the viewport
static clearscope bool TestScreenBounds( dt_ProjectionData d, Vector2 vpos ) static clearscope bool TestScreenBounds( dt_ProjectionData d, Vector2 vpos )
{ {
return ((vpos.x == clamp(vpos.x,d.viewx,d.viewx+d.vieww)) return ((vpos.x == clamp(vpos.x,d.viewx,d.viewx+d.vieww))
&& (vpos.y == clamp(vpos.y,d.viewy,d.viewy+d.viewh))); && (vpos.y == clamp(vpos.y,d.viewy,d.viewy+d.viewh)));
} }
static clearscope Vector3 Vec3FromAngle( double angle, double pitch ) static clearscope Vector3 Vec3FromAngle( double angle, double pitch )
@ -72,7 +101,12 @@ Class dt_Utility
return r*x, r*y, r*z; return r*x, r*y, r*z;
} }
// 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 ) static double, double, double ToAngles( Quat q )
{ {
double angle = 0., pitch = 0., roll = 0.; double angle = 0., pitch = 0., roll = 0.;
@ -83,13 +117,13 @@ Class dt_Utility
{ {
angle = atan2(angY,angX); angle = atan2(angY,angX);
pitch = 90.; pitch = 90.;
roll = dt_GM_GlobalMaths.Normalize180(angle+(2.*atan2(q.x,q.w))); roll = Normalize180(angle+(2.*atan2(q.x,q.w)));
} }
else if ( stest > .4999995 ) else if ( stest > .4999995 )
{ {
angle = atan2(angY,angX); angle = atan2(angY,angX);
pitch = -90.; pitch = -90.;
roll = dt_GM_GlobalMaths.Normalize180(angle+(2.*atan2(q.x,q.w))); roll = Normalize180(angle+(2.*atan2(q.x,q.w)));
} }
else else
{ {