flak_m/zscript/dt_Gutamatics/Matrix.zsc
Marisa the Magician 602a89cc68 1.2 update w/ GZDoom 4.9 features:
- Changeable player skins.
 - Ammo LEDs are now 1:1 with UT by using canvas textures.
 - Integrate some add-ons, including reskins.
 - Various fixes (some backported from Demolitionist).
 - Migrated from libeye to Gutamatics.
2022-11-05 23:59:16 +01:00

335 lines
11 KiB
Text

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;
}
}