mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-24 00:02:45 +02:00
- Move the two N64 reference screenshots out of the repo root into docs/reference/ (n64-footage-2026-08-24.jpg, n64-footage-gun-barrel.png); HANDOFF.md now points there. - Gitignore screenshots/ (session captures) and tools_pc/mupen64/ (local Mupen64 Plus checkout used for N64 reference footage; 8 MB of binaries). - Commit tools_pc/mtxtest.c, the standalone fast3d matrix-convention harness that HANDOFF/D72.3 docs reference as kept-for-reference. - Refresh PORTING.md: status was still 'scaffolding / building once implementation lands'; now reflects Phase 2 in progress and points at docs/HANDOFF.md for current session state.
213 lines
8.1 KiB
C
213 lines
8.1 KiB
C
/* Standalone numerical test of the fast3d matrix pipeline conventions.
|
|
* Replicates: GE guPerspectiveF (swapped), guLookAtF, guRotateF, guMtxF2L
|
|
* packing, gfx_sp_matrix unpacking + MUL order, and the row-vector vertex
|
|
* transform in gfx_pc.cpp. Compares against the standard column-vector
|
|
* pipeline P_std * MV * v for two scenes: gun barrel (known-good render)
|
|
* and rareware logo (D72.3 off-screen bug).
|
|
*/
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
|
|
typedef float f32;
|
|
|
|
/* ---- GE libultra functions (verbatim math) ---- */
|
|
|
|
static void guMtxIdentF(float mf[4][4]) {
|
|
for (int i = 0; i < 4; i++)
|
|
for (int j = 0; j < 4; j++) mf[i][j] = (i == j);
|
|
}
|
|
|
|
/* GE's SWAPPED perspective: mf[2][3]=-1, mf[3][2]=2nf/(n-f) */
|
|
static void guPerspectiveF_GE(float mf[4][4], float fovy, float aspect, float near_, float far_) {
|
|
float cot;
|
|
guMtxIdentF(mf);
|
|
fovy *= 3.1415926f / 180.0f;
|
|
cot = cosf(fovy / 2) / sinf(fovy / 2);
|
|
mf[0][0] = cot / aspect;
|
|
mf[1][1] = cot;
|
|
mf[2][2] = (near_ + far_) / (near_ - far_);
|
|
mf[2][3] = -1;
|
|
mf[3][2] = (2 * near_ * far_) / (near_ - far_);
|
|
mf[3][3] = 0;
|
|
}
|
|
|
|
/* standard libultra perspective (for the "true" pipeline) */
|
|
static void guPerspectiveF_STD(float mf[4][4], float fovy, float aspect, float near_, float far_) {
|
|
float cot;
|
|
guMtxIdentF(mf);
|
|
fovy *= 3.1415926f / 180.0f;
|
|
cot = cosf(fovy / 2) / sinf(fovy / 2);
|
|
mf[0][0] = cot / aspect;
|
|
mf[1][1] = cot;
|
|
mf[2][2] = (near_ + far_) / (near_ - far_);
|
|
mf[2][3] = (2 * near_ * far_) / (near_ - far_);
|
|
mf[3][2] = -1;
|
|
mf[3][3] = 0;
|
|
}
|
|
|
|
static void guLookAtF(float mf[4][4], float xEye, float yEye, float zEye,
|
|
float xAt, float yAt, float zAt,
|
|
float xUp, float yUp, float zUp) {
|
|
float len, xLook, yLook, zLook, xRight, yRight, zRight;
|
|
guMtxIdentF(mf);
|
|
xLook = xAt - xEye; yLook = yAt - yEye; zLook = zAt - zEye;
|
|
len = -1.0f / sqrtf(xLook*xLook + yLook*yLook + zLook*zLook);
|
|
xLook *= len; yLook *= len; zLook *= len;
|
|
xRight = yUp * zLook - zUp * yLook;
|
|
yRight = zUp * xLook - xUp * zLook;
|
|
zRight = xUp * yLook - yUp * xLook;
|
|
len = 1.0f / sqrtf(xRight*xRight + yRight*yRight + zRight*zRight);
|
|
xRight *= len; yRight *= len; zRight *= len;
|
|
xUp = yLook * zRight - zLook * yRight;
|
|
yUp = zLook * xRight - xLook * zRight;
|
|
zUp = xLook * yRight - yLook * xRight;
|
|
len = 1.0f / sqrtf(xUp*xUp + yUp*yUp + zUp*zUp);
|
|
xUp *= len; yUp *= len; zUp *= len;
|
|
mf[0][0] = xRight; mf[1][0] = yRight; mf[2][0] = zRight;
|
|
mf[3][0] = -(xEye * xRight + yEye * yRight + zEye * zRight);
|
|
mf[0][1] = xUp; mf[1][1] = yUp; mf[2][1] = zUp;
|
|
mf[3][1] = -(xEye * xUp + yEye * yUp + zEye * zUp);
|
|
mf[0][2] = xLook; mf[1][2] = yLook; mf[2][2] = zLook;
|
|
mf[3][2] = -(xEye * xLook + yEye * yLook + zEye * zLook);
|
|
mf[0][3] = 0; mf[1][3] = 0; mf[2][3] = 0; mf[3][3] = 1;
|
|
}
|
|
|
|
static void guRotateF(float mf[4][4], float a, float x, float y, float z) {
|
|
static float dtor = 3.1415926f / 180.0f;
|
|
float sine, cosine, ab, bc, ca, t;
|
|
float l = sqrtf(x*x + y*y + z*z);
|
|
if (l != 0) { x /= l; y /= l; z /= l; }
|
|
a *= dtor;
|
|
sine = sinf(a); cosine = cosf(a);
|
|
t = (1 - cosine);
|
|
ab = x * y * t; bc = y * z * t; ca = z * x * t;
|
|
guMtxIdentF(mf);
|
|
t = x * x;
|
|
mf[0][0] = t + cosine * (1 - t);
|
|
mf[2][1] = bc - x * sine;
|
|
mf[1][2] = bc + x * sine;
|
|
t = y * y;
|
|
mf[1][1] = t + cosine * (1 - t);
|
|
mf[2][0] = ca + y * sine;
|
|
mf[0][2] = ca - y * sine;
|
|
t = z * z;
|
|
mf[2][2] = t + cosine * (1 - t);
|
|
mf[1][0] = ab - z * sine;
|
|
mf[0][1] = ab + z * sine;
|
|
}
|
|
|
|
/* ---- guMtxF2L pack / gfx_sp_matrix unpack (bit-exact path) ---- */
|
|
|
|
static void mtxf2l(const float mf[4][4], int32_t m[16]) {
|
|
for (int i = 0; i < 4; i++)
|
|
for (int j = 0; j < 2; j++) {
|
|
int e1 = (int)(mf[i][j*2] * 65536.0f);
|
|
int e2 = (int)(mf[i][j*2+1] * 65536.0f);
|
|
/* real guMtxF2L uses *(ai++)/*(af++): slot = i*2+j */
|
|
m[i*2 + j] = (e1 & 0xffff0000) | ((e2 >> 16) & 0xffff);
|
|
m[8 + i*2 + j] = ((e1 << 16) & 0xffff0000) | (e2 & 0xffff);
|
|
}
|
|
}
|
|
|
|
static void sp_matrix_unpack(const int32_t addr[16], float matrix[4][4]) {
|
|
for (int i = 0; i < 4; i++)
|
|
for (int j = 0; j < 4; j += 2) {
|
|
int32_t int_part = addr[i * 2 + j / 2];
|
|
uint32_t frac_part = addr[8 + i * 2 + j / 2];
|
|
matrix[i][j] = (int32_t)((int_part & 0xffff0000) | (frac_part >> 16)) / 65536.0f;
|
|
matrix[i][j + 1] = (int32_t)((int_part << 16) | (frac_part & 0xffff)) / 65536.0f;
|
|
}
|
|
}
|
|
|
|
/* ---- gfx_pc.cpp conventions ---- */
|
|
|
|
static void mmul(float res[4][4], const float a[4][4], const float b[4][4]) {
|
|
float tmp[4][4];
|
|
for (int i = 0; i < 4; i++)
|
|
for (int j = 0; j < 4; j++)
|
|
tmp[i][j] = a[i][0]*b[0][j] + a[i][1]*b[1][j] + a[i][2]*b[2][j] + a[i][3]*b[3][j];
|
|
for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) res[i][j] = tmp[i][j];
|
|
}
|
|
|
|
/* row-vector vertex transform exactly as in gfx_sp_vertex */
|
|
static void xform_row(const float MP[4][4], const float v[3], float c[4]) {
|
|
c[0] = v[0]*MP[0][0] + v[1]*MP[1][0] + v[2]*MP[2][0] + MP[3][0];
|
|
c[1] = v[0]*MP[0][1] + v[1]*MP[1][1] + v[2]*MP[2][1] + MP[3][1];
|
|
c[2] = v[0]*MP[0][2] + v[1]*MP[1][2] + v[2]*MP[2][2] + MP[3][2];
|
|
c[3] = v[0]*MP[0][3] + v[1]*MP[1][3] + v[2]*MP[2][3] + MP[3][3];
|
|
}
|
|
|
|
/* standard column-vector transform c = A*B*v */
|
|
static void xform_col(const float A[4][4], const float B[4][4], const float v[3], float c[4]) {
|
|
float M[4][4];
|
|
mmul(M, A, B);
|
|
for (int i = 0; i < 4; i++)
|
|
c[i] = M[i][0]*v[0] + M[i][1]*v[1] + M[i][2]*v[2] + M[i][3];
|
|
}
|
|
|
|
static void report(const char *tag, const float c[4]) {
|
|
float xw = c[3] != 0 ? c[0]/c[3] : 1e9f;
|
|
float yw = c[3] != 0 ? c[1]/c[3] : 1e9f;
|
|
printf(" %-34s clip=(%12.1f,%12.1f,%12.1f,%12.1f) x/w=%8.4f y/w=%8.4f %s\n",
|
|
tag, c[0], c[1], c[2], c[3], xw, yw,
|
|
(fabsf(xw) <= 1 && fabsf(yw) <= 1) ? "ON-SCREEN" : "off-screen");
|
|
}
|
|
|
|
/* pack+unpack round-trip so we exercise the exact bit path */
|
|
static void rt(const float mf[4][4], float out[4][4]) {
|
|
int32_t m[16];
|
|
mtxf2l(mf, m);
|
|
sp_matrix_unpack(m, out);
|
|
}
|
|
|
|
int main(void) {
|
|
float Pge[4][4], Pstd[4][4], MVla[4][4], Rot[4][4];
|
|
float MPours[4][4], MPalt[4][4], Mtot[4][4];
|
|
float c[4];
|
|
|
|
printf("== SCENE 1: gun barrel (eye 1758.3,220,684.3 dir -0.97,0,0.24 up 0,1,0; fov46 n10 f10000) ==\n");
|
|
guPerspectiveF_GE(Pge, 46.0f, 320.0f/240.0f, 10.0f, 10000.0f);
|
|
guPerspectiveF_STD(Pstd, 46.0f, 320.0f/240.0f, 10.0f, 10000.0f);
|
|
guLookAtF(MVla, 1758.2957f, 220.0f, 684.28143f,
|
|
1758.2957f - 0.97f, 220.0f, 684.28143f + 0.24f, 0, 1, 0);
|
|
rt(Pge, Pge); rt(MVla, MVla);
|
|
|
|
float v[3] = { -57.3f, -20.0f, 16.3f }; /* sample point offset from target */
|
|
mmul(MPours, MVla, Pge); /* our MP for LOAD-only */
|
|
xform_row(MPours, v, c);
|
|
report("OURS row: v.MV.P_ge", c);
|
|
xform_col(Pstd, MVla, v, c);
|
|
report("TRUE col: P_std*MV*v", c);
|
|
|
|
printf("\n== SCENE 2: rareware logo (eye 0,0,880 at 0,0,879 up 0,1,0; fov60 n100 f5000; rotY -40deg) ==\n");
|
|
guPerspectiveF_GE(Pge, 60.0f, 320.0f/240.0f, 100.0f, 5000.0f);
|
|
guPerspectiveF_STD(Pstd, 60.0f, 320.0f/240.0f, 100.0f, 5000.0f);
|
|
guLookAtF(MVla, 0, 0, 880.0f, 0, 0, 879.0f, 0, 1, 0);
|
|
guRotateF(Rot, -40.0f, 0, 1, 0);
|
|
rt(Pge, Pge); rt(MVla, MVla); rt(Rot, Rot);
|
|
|
|
float lv[3] = { 55.0f, 61.0f, 15.0f }; /* verts4258[0].ob */
|
|
mmul(MPours, Rot, MVla); /* our code: new = matrix*old */
|
|
mmul(MPours, MPours, Pge);
|
|
xform_row(MPours, lv, c);
|
|
report("OURS MUL (Rot*MV) then P", c);
|
|
|
|
mmul(MPalt, MVla, Rot); /* alt: new = old*matrix */
|
|
mmul(MPalt, MPalt, Pge);
|
|
xform_row(MPalt, lv, c);
|
|
report("ALT MUL (MV*Rot) then P", c);
|
|
|
|
/* true standard pipeline: P_std * (MVla*Rot) * v [Rot applied first] */
|
|
mmul(Mtot, MVla, Rot);
|
|
xform_col(Pstd, Mtot, lv, c);
|
|
report("TRUE col: P_std*MV*Rot*v", c);
|
|
|
|
/* and the other hardware-MUL interpretation: P_std * (Rot*MV) * v */
|
|
mmul(Mtot, Rot, MVla);
|
|
xform_col(Pstd, Mtot, lv, c);
|
|
report("TRUE col: P_std*Rot*MV*v", c);
|
|
|
|
return 0;
|
|
}
|