Files
2026-08-08 19:30:03 -07:00

372 lines
15 KiB
C++

#pragma once
#include "../bv/bounds.h"
#include "../math/matrix.h"
#include "../math/plane.h"
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstring>
// Row-major 4x4 matrix recovered from Tungsten. The methods below follow the
// operand order and projection conventions visible in the Hex-Rays bodies.
class idRenderMatrix {
public:
float m[16];
float* operator[](const int row) { return m + row * 4; }
const float* operator[](const int row) const { return m + row * 4; }
void Identity() {
std::memset(m, 0, sizeof(m));
m[0] = m[5] = m[10] = m[15] = 1.0f;
}
void TransformPoint(const idVec3& input, idVec4& output) const {
output.Set(
input.x * m[0] + input.y * m[1] + input.z * m[2] + m[3],
input.x * m[4] + input.y * m[5] + input.z * m[6] + m[7],
input.x * m[8] + input.y * m[9] + input.z * m[10] + m[11],
input.x * m[12] + input.y * m[13] + input.z * m[14] + m[15]);
}
void TransformPoint(const idVec4& input, idVec4& output) const {
idVec4 result;
result.Set(
input.x * m[0] + input.y * m[1] + input.z * m[2] + input.w * m[3],
input.x * m[4] + input.y * m[5] + input.z * m[6] + input.w * m[7],
input.x * m[8] + input.y * m[9] + input.z * m[10] + input.w * m[11],
input.x * m[12] + input.y * m[13] + input.z * m[14] + input.w * m[15]);
output = result;
}
void TransformDir(const idVec3& input, idVec3& output,
const bool normalize) const {
idVec3 result(
input.x * m[0] + input.y * m[1] + input.z * m[2],
input.x * m[4] + input.y * m[5] + input.z * m[6],
input.x * m[8] + input.y * m[9] + input.z * m[10]);
if (normalize) Normalize(result);
output = result;
}
void InverseTransformPlane(const idPlane& input, idPlane& output,
const bool normalize) const {
idPlane result(
input.a * m[0] + input.b * m[4] + input.c * m[8] + input.d * m[12],
input.a * m[1] + input.b * m[5] + input.c * m[9] + input.d * m[13],
input.a * m[2] + input.b * m[6] + input.c * m[10] + input.d * m[14],
input.a * m[3] + input.b * m[7] + input.c * m[11] + input.d * m[15]);
if (normalize) {
const float lengthSqr = result.a * result.a + result.b * result.b
+ result.c * result.c;
if (lengthSqr > 1.0e-30f) {
const float inverseLength = 1.0f / std::sqrt(lengthSqr);
result.a *= inverseLength;
result.b *= inverseLength;
result.c *= inverseLength;
result.d *= inverseLength;
}
}
output = result;
}
static void FromOriginAxisScale(const idVec3& origin, const idMat3& axis,
const idVec3& scale, idRenderMatrix& output) {
output.m[0] = axis[0].x * scale.x;
output.m[1] = axis[1].x * scale.y;
output.m[2] = axis[2].x * scale.z;
output.m[3] = origin.x;
output.m[4] = axis[0].y * scale.x;
output.m[5] = axis[1].y * scale.y;
output.m[6] = axis[2].y * scale.z;
output.m[7] = origin.y;
output.m[8] = axis[0].z * scale.x;
output.m[9] = axis[1].z * scale.y;
output.m[10] = axis[2].z * scale.z;
output.m[11] = origin.z;
output.m[12] = output.m[13] = output.m[14] = 0.0f;
output.m[15] = 1.0f;
}
static void ModelViewFromOriginAxis(const idVec3& origin,
const idMat3& axis, idRenderMatrix& output) {
output.m[0] = -axis[1].x;
output.m[1] = -axis[1].y;
output.m[2] = -axis[1].z;
output.m[3] = -(origin.x * output.m[0] + origin.y * output.m[1]
+ origin.z * output.m[2]);
output.m[4] = axis[2].x;
output.m[5] = axis[2].y;
output.m[6] = axis[2].z;
output.m[7] = -(origin.x * output.m[4] + origin.y * output.m[5]
+ origin.z * output.m[6]);
output.m[8] = -axis[0].x;
output.m[9] = -axis[0].y;
output.m[10] = -axis[0].z;
output.m[11] = -(origin.x * output.m[8] + origin.y * output.m[9]
+ origin.z * output.m[10]);
output.m[12] = output.m[13] = output.m[14] = 0.0f;
output.m[15] = 1.0f;
}
static void Multiply(const idRenderMatrix& a, const idRenderMatrix& b,
idRenderMatrix& output) {
idRenderMatrix result;
for (int row = 0; row < 4; ++row) {
for (int column = 0; column < 4; ++column) {
result[row][column] = a[row][0] * b[0][column]
+ a[row][1] * b[1][column]
+ a[row][2] * b[2][column]
+ a[row][3] * b[3][column];
}
}
output = result;
}
static bool InverseByDoubles(const idRenderMatrix& source,
idRenderMatrix& output) {
double augmented[4][8] = {};
for (int row = 0; row < 4; ++row) {
for (int column = 0; column < 4; ++column)
augmented[row][column] = source[row][column];
augmented[row][row + 4] = 1.0;
}
for (int column = 0; column < 4; ++column) {
int pivotRow = column;
for (int row = column + 1; row < 4; ++row) {
if (std::fabs(augmented[row][column])
> std::fabs(augmented[pivotRow][column])) {
pivotRow = row;
}
}
if (std::fabs(augmented[pivotRow][column]) < 1.0e-20) return false;
if (pivotRow != column) {
for (int entry = 0; entry < 8; ++entry)
std::swap(augmented[pivotRow][entry], augmented[column][entry]);
}
const double inversePivot = 1.0 / augmented[column][column];
for (int entry = 0; entry < 8; ++entry)
augmented[column][entry] *= inversePivot;
for (int row = 0; row < 4; ++row) {
if (row == column) continue;
const double scale = augmented[row][column];
for (int entry = 0; entry < 8; ++entry)
augmented[row][entry] -= scale * augmented[column][entry];
}
}
for (int row = 0; row < 4; ++row)
for (int column = 0; column < 4; ++column)
output[row][column] = static_cast<float>(augmented[row][column + 4]);
return true;
}
static bool Inverse(const idRenderMatrix& source, idRenderMatrix& output) {
return InverseByDoubles(source, output);
}
static void InverseByTranspose(const idRenderMatrix& source,
idRenderMatrix& output) {
output.m[0] = source.m[0]; output.m[1] = source.m[4];
output.m[2] = source.m[8]; output.m[12] = 0.0f;
output.m[4] = source.m[1]; output.m[5] = source.m[5];
output.m[6] = source.m[9]; output.m[13] = 0.0f;
output.m[8] = source.m[2]; output.m[9] = source.m[6];
output.m[10] = source.m[10]; output.m[14] = 0.0f;
output.m[3] = -(source.m[3] * source.m[0]
+ source.m[7] * source.m[4] + source.m[11] * source.m[8]);
output.m[7] = -(source.m[3] * source.m[1]
+ source.m[7] * source.m[5] + source.m[11] * source.m[9]);
output.m[11] = -(source.m[3] * source.m[2]
+ source.m[7] * source.m[6] + source.m[11] * source.m[10]);
output.m[15] = 1.0f;
}
static void BuildProjection(const float xMin, const float xMax,
const float yMin, const float yMax, const float zNear,
const float zFar, idRenderMatrix& output) {
std::memset(output.m, 0, sizeof(output.m));
output.m[0] = 2.0f * zNear / (xMax - xMin);
output.m[2] = (xMin + xMax) / (xMax - xMin);
output.m[5] = 2.0f * zNear / (yMax - yMin);
output.m[6] = (yMin + yMax) / (yMax - yMin);
output.m[14] = -1.0f;
if (zFar > zNear) {
output.m[10] = -zFar / (zFar - zNear);
output.m[11] = -(zNear * zFar) / (zFar - zNear);
} else {
output.m[10] = -1.0f;
output.m[11] = -zNear;
}
}
static void BuildProjectionFov(const float xFovDegrees,
const float yFovDegrees, const float zNear, const float zFar,
const float xOffset, const float yOffset, idRenderMatrix& output) {
const float degreeToRadian = 0.01745329251994329577f;
const float xSize = std::tan(0.5f * xFovDegrees * degreeToRadian) * zNear;
const float ySize = std::tan(0.5f * yFovDegrees * degreeToRadian) * zNear;
BuildProjection(-xSize + xOffset, xSize + xOffset,
-ySize + yOffset, ySize + yOffset, zNear, zFar, output);
}
static void OffsetScaleForBounds(const idRenderMatrix& source,
const idBounds& bounds, idRenderMatrix& output) {
const idVec3 center = (bounds[0] + bounds[1]) * 0.5f;
const idVec3 extent = (bounds[1] - bounds[0]) * 0.5f;
for (int row = 0; row < 4; ++row) {
output[row][0] = source[row][0] * extent.x;
output[row][1] = source[row][1] * extent.y;
output[row][2] = source[row][2] * extent.z;
output[row][3] = source[row][3] + source[row][0] * center.x
+ source[row][1] * center.y + source[row][2] * center.z;
}
}
static void InverseOffsetScaleForBounds(const idRenderMatrix& source,
const idBounds& bounds, idRenderMatrix& output) {
const idVec3 center = (bounds[0] + bounds[1]) * 0.5f;
const idVec3 extent = (bounds[1] - bounds[0]) * 0.5f;
for (int row = 0; row < 4; ++row) {
const float inverseExtent = row < 3 && extent[row] != 0.0f
? 1.0f / extent[row]
: 1.0f;
for (int column = 0; column < 4; ++column)
output[row][column] = source[row][column] * inverseExtent;
}
for (int column = 0; column < 4; ++column) {
output[0][column] -= center.x * output[3][column];
output[1][column] -= center.y * output[3][column];
output[2][column] -= center.z * output[3][column];
}
}
static void CopyMatrix(const idRenderMatrix& matrix, idVec4& row0,
idVec4& row1, idVec4& row2, idVec4& row3) {
row0.Set(matrix.m[0], matrix.m[1], matrix.m[2], matrix.m[3]);
row1.Set(matrix.m[4], matrix.m[5], matrix.m[6], matrix.m[7]);
row2.Set(matrix.m[8], matrix.m[9], matrix.m[10], matrix.m[11]);
row3.Set(matrix.m[12], matrix.m[13], matrix.m[14], matrix.m[15]);
}
static void SetMVP(const idRenderMatrix& matrix, idVec4& row0,
idVec4& row1, idVec4& row2, idVec4& row3,
bool& negativeDeterminant) {
CopyMatrix(matrix, row0, row1, row2, row3);
negativeDeterminant = Determinant3x3(matrix) < 0.0f;
}
static void SetMVPForBounds(const idRenderMatrix& matrix,
const idBounds& bounds, idVec4& row0, idVec4& row1,
idVec4& row2, idVec4& row3, bool& negativeDeterminant) {
idRenderMatrix adjusted;
OffsetScaleForBounds(matrix, bounds, adjusted);
SetMVP(adjusted, row0, row1, row2, row3, negativeDeterminant);
}
static void SetMVPForInverseProject(const idRenderMatrix& matrix,
const idRenderMatrix& inverseProject, idVec4& row0,
idVec4& row1, idVec4& row2, idVec4& row3,
bool& negativeDeterminant) {
idRenderMatrix adjusted;
Multiply(matrix, inverseProject, adjusted);
SetMVP(adjusted, row0, row1, row2, row3, negativeDeterminant);
}
static bool CullPointToMVPbits(const idRenderMatrix& matrix,
const idVec3& point, std::uint8_t* outBits, const bool zeroToOne) {
idVec4 clip;
matrix.TransformPoint(point, clip);
std::uint8_t bits = 0;
if (clip.x < -clip.w) bits |= 1;
if (clip.x > clip.w) bits |= 2;
if (clip.y < -clip.w) bits |= 4;
if (clip.y > clip.w) bits |= 8;
if (clip.z < (zeroToOne ? 0.0f : -clip.w)) bits |= 16;
if (clip.z > clip.w) bits |= 32;
if (outBits != nullptr) *outBits = bits;
return bits != 0;
}
static bool CullBoundsToMVPbits(const idRenderMatrix& matrix,
const idBounds& bounds, std::uint8_t* outBits,
const bool zeroToOne) {
std::uint8_t allBits = 0x3F;
std::uint8_t anyBits = 0;
for (int corner = 0; corner < 8; ++corner) {
const idVec3 point(
bounds[(corner & 1) != 0 ? 1 : 0].x,
bounds[(corner & 2) != 0 ? 1 : 0].y,
bounds[(corner & 4) != 0 ? 1 : 0].z);
std::uint8_t bits;
CullPointToMVPbits(matrix, point, &bits, zeroToOne);
anyBits |= bits;
allBits &= bits;
}
if (outBits != nullptr) *outBits = anyBits;
return allBits != 0;
}
static void ProjectedBounds(idBounds& projected,
const idRenderMatrix& matrix, const idBounds& bounds,
const bool windowSpace) {
projected[0].Set(1.0e30f, 1.0e30f, 1.0e30f);
projected[1].Set(-1.0e30f, -1.0e30f, -1.0e30f);
for (int corner = 0; corner < 8; ++corner) {
idVec4 clip;
matrix.TransformPoint(idVec3(
bounds[(corner & 1) != 0 ? 1 : 0].x,
bounds[(corner & 2) != 0 ? 1 : 0].y,
bounds[(corner & 4) != 0 ? 1 : 0].z), clip);
if (std::fabs(clip.w) < 1.0e-20f) continue;
const float inverseW = 1.0f / clip.w;
idVec3 point(clip.x * inverseW, clip.y * inverseW, clip.z * inverseW);
if (windowSpace) {
point.x = point.x * 0.5f + 0.5f;
point.y = point.y * 0.5f + 0.5f;
}
for (int axis = 0; axis < 3; ++axis) {
projected[0][axis] = (std::min)(projected[0][axis], point[axis]);
projected[1][axis] = (std::max)(projected[1][axis], point[axis]);
}
}
}
static void DepthBoundsForBounds(float& minDepth, float& maxDepth,
const idRenderMatrix& matrix, const idBounds& bounds,
const bool windowSpace) {
idBounds projected;
ProjectedBounds(projected, matrix, bounds, windowSpace);
minDepth = projected[0].z;
maxDepth = projected[1].z;
}
static void TransformModelToClip(const idVec3& source,
const idRenderMatrix& modelView, const idRenderMatrix& projection,
idVec4& eye, idVec4& clip) {
modelView.TransformPoint(source, eye);
projection.TransformPoint(eye, clip);
}
static void TransformClipToDevice(const idVec4& clip, idVec3& device) {
const float inverseW = std::fabs(clip.w) > 1.0e-20f ? 1.0f / clip.w : 0.0f;
device.Set(clip.x * inverseW, clip.y * inverseW, clip.z * inverseW);
}
private:
static void Normalize(idVec3& value) {
const float lengthSqr = value.LengthSqr();
if (lengthSqr > 1.0e-30f) value = value * (1.0f / std::sqrt(lengthSqr));
}
static float Determinant3x3(const idRenderMatrix& matrix) {
return matrix.m[0] * (matrix.m[5] * matrix.m[10] - matrix.m[6] * matrix.m[9])
- matrix.m[1] * (matrix.m[4] * matrix.m[10] - matrix.m[6] * matrix.m[8])
+ matrix.m[2] * (matrix.m[4] * matrix.m[9] - matrix.m[5] * matrix.m[8]);
}
};
static_assert(sizeof(idRenderMatrix) == 64,
"Recovered idRenderMatrix layout changed");