aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-07-18 14:28:20 +0300
committerValentin Popov <valentin@popov.link>2026-07-18 14:28:20 +0300
commit468ecc4e325178c116f0de25f47736eaec2a1e15 (patch)
tree451d8be56ea939ae3e9e6d70f16e1c610cc8fb21 /crates
parent438aaebcbc5c963d378d297bfb13e2af24b12084 (diff)
downloadfparkan-468ecc4e325178c116f0de25f47736eaec2a1e15.tar.xz
fparkan-468ecc4e325178c116f0de25f47736eaec2a1e15.zip
feat(render): recover Iron3D Euler transform
Diffstat (limited to 'crates')
-rw-r--r--crates/fparkan-render/src/lib.rs87
1 files changed, 87 insertions, 0 deletions
diff --git a/crates/fparkan-render/src/lib.rs b/crates/fparkan-render/src/lib.rs
index a481732..c181cf9 100644
--- a/crates/fparkan-render/src/lib.rs
+++ b/crates/fparkan-render/src/lib.rs
@@ -246,6 +246,66 @@ impl LegacyD3d7Projection {
}
}
+/// Affine placement inputs consumed by `Iron3D`'s recovered Euler-matrix builder.
+///
+/// This records the exact matrix construction at `iron3d.dll` RVA `0x36610`.
+/// It is intentionally distinct from a mission placement contract: the static
+/// trace has not yet proved that TMA's three raw orientation floats reach this
+/// builder unchanged for every object category.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct LegacyIron3dEulerTransform {
+ /// World-space translation passed to the builder.
+ pub translation: [f32; 3],
+ /// Three angles in the builder's `(x, y, z)` input order, in radians.
+ pub orientation_radians: [f32; 3],
+}
+
+impl LegacyIron3dEulerTransform {
+ /// Reconstructs the builder's finite row-major affine matrix.
+ ///
+ /// The recovered x87 code evaluates `Rz(z) * Ry(y) * Rx(x)` and writes
+ /// translation into the last column. This uses portable `f32` trigonometry;
+ /// any future x87-compatibility path must be separately capture-validated.
+ #[must_use]
+ pub fn try_row_major(self) -> Option<[f32; 16]> {
+ if !self
+ .translation
+ .iter()
+ .chain(self.orientation_radians.iter())
+ .all(|value| value.is_finite())
+ {
+ return None;
+ }
+ let [x, y, z] = self.orientation_radians;
+ let (sin_x, cos_x) = x.sin_cos();
+ let (sin_y, cos_y) = y.sin_cos();
+ let (sin_z, cos_z) = z.sin_cos();
+ let [translation_x, translation_y, translation_z] = self.translation;
+ let matrix = [
+ cos_z * cos_y,
+ cos_z * sin_y * sin_x - sin_z * cos_x,
+ cos_z * sin_y * cos_x + sin_z * sin_x,
+ translation_x,
+ sin_z * cos_y,
+ sin_z * sin_y * sin_x + cos_z * cos_x,
+ sin_z * sin_y * cos_x - cos_z * sin_x,
+ translation_y,
+ -sin_y,
+ cos_y * sin_x,
+ cos_y * cos_x,
+ translation_z,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0,
+ ];
+ matrix
+ .iter()
+ .all(|value| value.is_finite())
+ .then_some(matrix)
+ }
+}
+
/// Raw camera state observed through the original Terrain camera interface.
///
/// Selector 0 supplies the currently active transform and selector 2 supplies
@@ -1133,6 +1193,33 @@ mod tests {
);
}
+ #[test]
+ fn iron3d_euler_builder_uses_rz_ry_rx_and_last_column_translation() {
+ let matrix = LegacyIron3dEulerTransform {
+ translation: [418.103_18, 717.433, 3.040_938_9],
+ orientation_radians: [0.0, 0.0, std::f32::consts::FRAC_PI_2],
+ }
+ .try_row_major()
+ .expect("finite recovered builder inputs");
+
+ assert!((matrix[0]).abs() < 0.000_001);
+ assert!((matrix[1] + 1.0).abs() < 0.000_001);
+ assert!((matrix[4] - 1.0).abs() < 0.000_001);
+ assert!((matrix[5]).abs() < 0.000_001);
+ assert_eq!(matrix[3], 418.103_18);
+ assert_eq!(matrix[7], 717.433);
+ assert_eq!(matrix[11], 3.040_938_9);
+ assert_eq!(matrix[15], 1.0);
+ assert_eq!(
+ LegacyIron3dEulerTransform {
+ translation: [0.0, 0.0, 0.0],
+ orientation_radians: [f32::NAN, 0.0, 0.0],
+ }
+ .try_row_major(),
+ None
+ );
+ }
+
fn snapshot_draw(
id: u64,
phase: RenderPhase,