Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions crates/unmapper-core/src/stage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions crates/unmapper-gui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion crates/unmapper-render/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion crates/unmapper-render/src/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
8 changes: 4 additions & 4 deletions crates/unmapper-render/tests/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -974,15 +974,15 @@ 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
});
assert!(has_set, "the set model should be visible");

// ...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,
Expand Down Expand Up @@ -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())
Expand Down