fix: correct CPU projection axes to match GPU look_at_rh convention
viewport_content_wires() computed view_right via world_z × view_direction (cross-product). This gives the OPPOSITE sign to the right vector that look_at_rh produces for views with a Y component (FRONT/BACK views): GPU look_at_rh: screen_right = f × up = (−vd) × (rot*Y) = rot*X Old CPU formula: screen_right = Z × vd ← opposite sign for FRONT/BACK The mismatch caused the paper-space projection to appear mirror-flipped after panning/rotating in MSPACE. Fix: use camera_for_viewport() to obtain the exact same rotation quaternion the GPU uses, then derive both axes from it: view_right = rotation * X view_up = rotation * Y Also replaces the perspective depth dot-product (mp · vd) with mp · (rotation*Z) so the depth direction is consistent too. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c2df63c4cc
commit
1c0520e4e4
1 changed files with 14 additions and 18 deletions
|
|
@ -702,22 +702,19 @@ impl Scene {
|
||||||
// ── Per-viewport frozen layer set ─────────────────────────────
|
// ── Per-viewport frozen layer set ─────────────────────────────
|
||||||
let frozen: HSet<Handle> = vp.frozen_layers.iter().cloned().collect();
|
let frozen: HSet<Handle> = vp.frozen_layers.iter().cloned().collect();
|
||||||
|
|
||||||
// ── View direction coordinate frame ───────────────────────────
|
// ── View coordinate frame ─────────────────────────────────────
|
||||||
let vd = glam::Vec3::new(
|
// Use camera_for_viewport so the axes match the GPU renderer exactly.
|
||||||
vp.view_direction.x as f32,
|
// GPU uses look_at_rh(eye, target, rotation*Y), which gives:
|
||||||
vp.view_direction.y as f32,
|
// screen_right = normalize((target-eye) × up) = rotation * X
|
||||||
vp.view_direction.z as f32,
|
// screen_up = rotation * Y
|
||||||
).normalize_or(glam::Vec3::Z);
|
// The old Z×vd cross-product gives the opposite sign for views
|
||||||
|
// that have a Y component in view_direction (front/back), causing mirroring.
|
||||||
// Compute view_right and view_up from the direction vector.
|
let cam_frame = match self.camera_for_viewport(vp.common.handle) {
|
||||||
let world_z = glam::Vec3::Z;
|
Some(c) => c,
|
||||||
let view_right = if (vd.dot(world_z)).abs() > 0.99 {
|
None => continue,
|
||||||
// Looking straight up/down: use X as right.
|
|
||||||
glam::Vec3::X
|
|
||||||
} else {
|
|
||||||
world_z.cross(vd).normalize()
|
|
||||||
};
|
};
|
||||||
let view_up = vd.cross(view_right).normalize();
|
let view_right = cam_frame.rotation * glam::Vec3::X;
|
||||||
|
let view_up = cam_frame.rotation * glam::Vec3::Y;
|
||||||
|
|
||||||
// ── Scale & viewport parameters ───────────────────────────────
|
// ── Scale & viewport parameters ───────────────────────────────
|
||||||
let scale = if vp.custom_scale.abs() > 1e-9 {
|
let scale = if vp.custom_scale.abs() > 1e-9 {
|
||||||
|
|
@ -795,9 +792,8 @@ impl Scene {
|
||||||
let u = mp.dot(view_right);
|
let u = mp.dot(view_right);
|
||||||
let v = mp.dot(view_up);
|
let v = mp.dot(view_up);
|
||||||
if use_perspective {
|
if use_perspective {
|
||||||
// vd points from target toward camera, so depth along vd is the
|
// Eye direction (target → camera) = rotation * Z.
|
||||||
// distance from the target plane toward the camera.
|
let d_vd = mp.dot(cam_frame.rotation * glam::Vec3::Z);
|
||||||
let d_vd = mp.dot(vd);
|
|
||||||
// Forward distance from camera (positive = in front of camera).
|
// Forward distance from camera (positive = in front of camera).
|
||||||
let fwd = camera_dist - d_vd;
|
let fwd = camera_dist - d_vd;
|
||||||
if fwd <= 0.001 {
|
if fwd <= 0.001 {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue