fix(render): use native shifted water reflection camera
Docs Deploy / Build and Deploy MkDocs (push) Successful in 35s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 35s
This commit is contained in:
7 files changed
+168
-25
No files matched your search
@@ -51,10 +51,118 @@ pub fn legacy_embm_uv(world_xy: [f32; 2], bounds: [f32; 4]) -> Option<[f32; 2]>
|
||||
])
|
||||
}
|
||||
|
||||
/// The EMBM camera looks up from the mirrored eye, with its window on the water rectangle.
|
||||
/// Terrain.dll+4EFF0 supplies `[min_y-eye_y, min_x-eye_x, span_y, span_x, eye_z-height]`.
|
||||
/// The ordinary perspective's depth mapping is retained; orthographic inputs use the caller's fallback.
|
||||
#[must_use]
|
||||
pub fn legacy_embm_reflection_clip(
|
||||
main_clip: [f32; 16],
|
||||
eye: [f32; 3],
|
||||
height: f32,
|
||||
bounds: [f32; 4],
|
||||
) -> Option<[f32; 16]> {
|
||||
let span = [bounds[2] - bounds[0], bounds[3] - bounds[1]];
|
||||
let distance = eye[2] - height;
|
||||
if !main_clip
|
||||
.into_iter()
|
||||
.chain(eye)
|
||||
.chain([height])
|
||||
.chain(bounds)
|
||||
.chain(span)
|
||||
.all(f32::is_finite)
|
||||
|| distance <= 0.0
|
||||
|| span.into_iter().any(|v| v <= 0.0)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let w = [main_clip[3], main_clip[7], main_clip[11]];
|
||||
let squared = w.into_iter().map(|v| v * v).sum::<f32>();
|
||||
if !squared.is_finite() || squared <= f32::EPSILON {
|
||||
return None;
|
||||
}
|
||||
let depth_a = (main_clip[2] * w[0] + main_clip[6] * w[1] + main_clip[10] * w[2]) / squared;
|
||||
// shortcut: retain the main depth range until the native shifted depth matrix is verified.
|
||||
let depth_b = (main_clip[14] - depth_a * main_clip[15]) / squared.sqrt();
|
||||
if !depth_a.is_finite() || !depth_b.is_finite() || depth_a < 1.0 || depth_b >= 0.0 {
|
||||
return None;
|
||||
}
|
||||
let z = 2.0 * height - eye[2];
|
||||
let x_scale = -2.0 * distance / span[1];
|
||||
let y_scale = -2.0 * distance / span[0];
|
||||
let x_shift = 1.0 + 2.0 * (bounds[1] - eye[1]) / span[1];
|
||||
let y_shift = 1.0 + 2.0 * (bounds[0] - eye[0]) / span[0];
|
||||
let matrix = [
|
||||
0.0,
|
||||
y_scale,
|
||||
0.0,
|
||||
0.0,
|
||||
x_scale,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
x_shift,
|
||||
y_shift,
|
||||
depth_a,
|
||||
1.0,
|
||||
-x_scale * eye[1] - x_shift * z,
|
||||
-y_scale * eye[0] - y_shift * z,
|
||||
depth_b - depth_a * z,
|
||||
-z,
|
||||
];
|
||||
matrix.into_iter().all(f32::is_finite).then_some(matrix)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn shifted_camera_maps_native_water_corners_and_retains_perspective_depth() {
|
||||
let projection = crate::LegacyD3d7Projection {
|
||||
viewport: [0, 0, 1280, 1024],
|
||||
near_plane: 0.5,
|
||||
far_plane: 700.0,
|
||||
field_of_view_radians: 1.3,
|
||||
}
|
||||
.try_direct3d7_projection_row_major()
|
||||
.unwrap();
|
||||
let eye = [1200.0, 800.0, 185.0];
|
||||
let mut main = [0.0; 16];
|
||||
main[2] = projection[10];
|
||||
main[3] = projection[11];
|
||||
main[14] = -eye[0] * projection[10] + projection[14];
|
||||
main[15] = -eye[0] * projection[11];
|
||||
let bounds = [1000.0, 500.0, 1500.0, 1200.0];
|
||||
let matrix = legacy_embm_reflection_clip(main, eye, 150.0, bounds).unwrap();
|
||||
let project = |point: [f32; 3]| -> [f32; 4] {
|
||||
std::array::from_fn(|column| {
|
||||
point[0] * matrix[column]
|
||||
+ point[1] * matrix[4 + column]
|
||||
+ point[2] * matrix[8 + column]
|
||||
+ matrix[12 + column]
|
||||
})
|
||||
};
|
||||
for point in [
|
||||
[1000.0, 500.0, 150.0],
|
||||
[1500.0, 1200.0, 150.0],
|
||||
[1125.0, 1025.0, 150.0],
|
||||
] {
|
||||
let clip = project(point);
|
||||
let expected = legacy_embm_uv([point[0], point[1]], bounds).unwrap();
|
||||
for axis in 0..2 {
|
||||
assert!((clip[axis] / clip[3] * 0.5 + 0.5 - expected[axis]).abs() < 0.00001);
|
||||
}
|
||||
assert_eq!(clip[3], 35.0);
|
||||
}
|
||||
let near = project([1200.0, 800.0, 115.5]);
|
||||
assert!((near[2] / near[3]).abs() < 0.001);
|
||||
let far = project([1200.0, 800.0, 815.0]);
|
||||
assert!((far[2] / far[3] - 1.0).abs() < 0.00001);
|
||||
assert!(legacy_embm_reflection_clip([0.0; 16], eye, 150.0, bounds).is_none());
|
||||
assert!(legacy_embm_reflection_clip(main, eye, 185.0, bounds).is_none());
|
||||
assert!(legacy_embm_reflection_clip(main, eye, 150.0, [1.0; 4]).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wave_bytes_match_native_generation_and_clock_wraps_without_float_time_loss() {
|
||||
let texels = legacy_embm_bump_map();
|
||||
|
||||
Reference in new issue
Block a user