enum dt_GM_VectorType { dt_GM_Vector_Position, dt_GM_Vector_Direction } class dt_GM_Matrix { private Array 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; } }