feat(render): recover Iron3D Euler transform
This commit is contained in:
@@ -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.
|
/// Raw camera state observed through the original Terrain camera interface.
|
||||||
///
|
///
|
||||||
/// Selector 0 supplies the currently active transform and selector 2 supplies
|
/// 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(
|
fn snapshot_draw(
|
||||||
id: u64,
|
id: u64,
|
||||||
phase: RenderPhase,
|
phase: RenderPhase,
|
||||||
|
|||||||
@@ -1437,6 +1437,17 @@ on its XY diagnostic projection until a runtime supplies a real legacy camera;
|
|||||||
selecting source-world coordinates with identity camera would be a misleading
|
selecting source-world coordinates with identity camera would be a misleading
|
||||||
empty/off-screen viewer rather than a compatibility result.
|
empty/off-screen viewer rather than a compatibility result.
|
||||||
|
|
||||||
|
Static analysis of GOG `iron3d.dll` now recovers an exact adjacent placement
|
||||||
|
primitive: RVA `0x36610` evaluates a row-major affine
|
||||||
|
`Rz(z) * Ry(y) * Rx(x)` and writes translation in the final column. The
|
||||||
|
backend-neutral `LegacyIron3dEulerTransform` preserves that finite portable
|
||||||
|
formula with a golden yaw-vector test. This is deliberately not wired to TMA:
|
||||||
|
AutoDemo records strongly suggest the candidate `(0, 0, z)` radians (all eight
|
||||||
|
objects have zero first two values and `z` in approximately `[-pi, pi]`), but
|
||||||
|
the current static trace has not proved that those raw record fields reach this
|
||||||
|
builder unchanged for every mission object category. The remaining link needs
|
||||||
|
a direct loader-to-builder dataflow or a dynamic placement capture.
|
||||||
|
|
||||||
`VulkanSmokeRenderer::set_camera` now accepts a new finite camera between frame
|
`VulkanSmokeRenderer::set_camera` now accepts a new finite camera between frame
|
||||||
submissions and uses it for the next command buffer without rebuilding the
|
submissions and uses it for the next command buffer without rebuilding the
|
||||||
swapchain, buffers, descriptors or pipelines. `VulkanStaticCamera` also accepts
|
swapchain, buffers, descriptors or pipelines. `VulkanStaticCamera` also accepts
|
||||||
|
|||||||
Reference in New Issue
Block a user