diff --git a/crates/unmapper-core/src/stage.rs b/crates/unmapper-core/src/stage.rs index 54634a3..80016a6 100644 --- a/crates/unmapper-core/src/stage.rs +++ b/crates/unmapper-core/src/stage.rs @@ -455,11 +455,25 @@ impl Default for Camera { impl Camera { pub fn view_matrix(&self) -> glam::Mat4 { - glam::Mat4::look_at_rh(self.position, self.target, self.up) + glam::camera::rh::view::look_at_mat4(self.position, self.target, self.up) } + /// # Why `directx`, on a Mac, in a Vulkan-shaped API + /// + /// glam 0.33 replaced `Mat4::perspective_rh` with one function per clip + /// space, named after the graphics API that popularised each. The one wgpu + /// wants — and therefore Metal, here — is **Z in `0..1` with Y up**, which + /// is glam's `directx` module. `vulkan` is the same depth range with Y + /// *down*, and would hand back a stage that is upside down but otherwise + /// entirely plausible. Verified equal to the old `perspective_rh` before the + /// deprecated call was removed. pub fn projection_matrix(&self, aspect: f32) -> glam::Mat4 { - glam::Mat4::perspective_rh(self.fov_y_deg.to_radians(), aspect, self.near, self.far) + glam::camera::rh::proj::directx::perspective( + self.fov_y_deg.to_radians(), + aspect, + self.near, + self.far, + ) } pub fn view_projection(&self, aspect: f32) -> glam::Mat4 { @@ -830,6 +844,37 @@ mod tests { } } + #[test] + fn the_projection_is_wgpus_clip_space_and_not_one_of_its_neighbours() { + // glam 0.33 replaced `Mat4::perspective_rh` with one function per clip + // space. All three are right-handed and none of them is obviously wrong + // at a glance: `vulkan` has the same depth range with Y *down*, so the + // stage comes out upside down; `opengl` puts Z in -1..1, so half the + // depth range is clipped. Both still render a picture. This pins the one + // wgpu actually wants. + let cam = Camera::default(); + let vp = cam.view_projection(16.0 / 9.0); + let ndc = |p: Vec3| { + let clip = vp * p.extend(1.0); + clip.truncate() / clip.w + }; + + let forward = cam.forward(); + let near = ndc(cam.position + forward * cam.near); + let far = ndc(cam.position + forward * cam.far); + assert!(near.z.abs() < 1e-3, "near plane should be depth 0, got {near:?}"); + assert!( + (far.z - 1.0).abs() < 1e-3, + "far plane should be depth 1, got {far:?}" + ); + + let above = ndc(cam.target + Vec3::Y); + assert!( + above.y > 0.0, + "up should be +Y in clip space, got {above:?}" + ); + } + #[test] fn projecting_and_unprojecting_are_the_same_map_read_both_ways() { // What makes a dragged handle stay under the pointer: pick a point, and diff --git a/crates/unmapper-gui/src/main.rs b/crates/unmapper-gui/src/main.rs index 3a6a9b0..5ea6a10 100644 --- a/crates/unmapper-gui/src/main.rs +++ b/crates/unmapper-gui/src/main.rs @@ -13,6 +13,10 @@ //! to interleave with UnMapper's render passes. mod about_data; +// Vendored from stoatworks-backend/about/rust, which is the master: the copy +// here must not be edited, so its one clippy nit is silenced at the import +// instead. Fix it there and re-run sync-about.py, and this can go. +#[allow(clippy::redundant_closure)] mod about_window; mod outputs; mod state; diff --git a/crates/unmapper-render/src/model.rs b/crates/unmapper-render/src/model.rs index 1c8b347..b32dfc6 100644 --- a/crates/unmapper-render/src/model.rs +++ b/crates/unmapper-render/src/model.rs @@ -156,7 +156,7 @@ fn append_primitive( // exported without normals still shades rather than rendering flat black. let had_normals = reader.read_normals().is_some(); if !had_normals { - for tri in indices.chunks_exact(3) { + for tri in indices.as_chunks::<3>().0 { let (a, b, c) = ( base as usize + tri[0] as usize, base as usize + tri[1] as usize, diff --git a/crates/unmapper-render/src/pattern.rs b/crates/unmapper-render/src/pattern.rs index 3d7e661..8e2abd2 100644 --- a/crates/unmapper-render/src/pattern.rs +++ b/crates/unmapper-render/src/pattern.rs @@ -91,7 +91,7 @@ mod tests { fn it_is_fully_opaque_and_the_right_size() { let d = test_pattern(Size::new(64, 32)); assert_eq!(d.len(), 64 * 32 * 4); - assert!(d.chunks_exact(4).all(|p| p[3] == 255)); + assert!(d.as_chunks::<4>().0.iter().all(|p| p[3] == 255)); } #[test] diff --git a/crates/unmapper-render/tests/render.rs b/crates/unmapper-render/tests/render.rs index 00e5895..41254de 100644 --- a/crates/unmapper-render/tests/render.rs +++ b/crates/unmapper-render/tests/render.rs @@ -241,7 +241,7 @@ fn a_bgra_source_renders_the_same_colours_as_an_rgba_one() { // The same image as quad_source(), byte-swapped into BGRA. let mut bgra = quad_source(); - for px in bgra.chunks_exact_mut(4) { + for px in bgra.as_chunks_mut::<4>().0 { px.swap(0, 2); } textures.upload( @@ -974,7 +974,7 @@ mod model { let data = target.read_rgba(&gpu); // Somewhere in the frame there must be grey set geometry... - let has_set = data.chunks_exact(4).any(|p| { + let has_set = data.as_chunks::<4>().0.iter().any(|p| { let (r, g, b) = (p[0] as i32, p[1] as i32, p[2] as i32); r > 20 && (r - g).abs() < 30 && (g - b).abs() < 30 && r < 200 }); @@ -982,7 +982,7 @@ mod model { // ...and red panel, which the model must not have painted over. let has_panel = data - .chunks_exact(4) + .as_chunks::<4>().0.iter() .any(|p| p[0] > 120 && p[1] < 60 && p[2] < 60); assert!( has_panel, @@ -1346,7 +1346,7 @@ fn a_curved_panel_bends_in_previz_and_a_flat_one_does_not() { ); let data = target.read_rgba(&gpu); let lit = data - .chunks_exact(4) + .as_chunks::<4>().0.iter() .filter(|p| p[0] > 40 || p[1] > 40 || p[2] > 40) .count(); (lit, scene.vertices.len())