Feat: Phase 3 — perspective projection and per-viewport UCS properties

- viewport_content_wires(): conditional perspective divide using
  camera_dist = view_height * lens_length / 24.0 (35mm-film equiv.);
  points behind the camera are mapped to NaN for safe clipping
- viewport.rs: expose lens_length, ucs_per_viewport, ucs_origin,
  ucs_x_axis, ucs_y_axis as editable properties; handle all new
  fields in apply_geom_prop()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Sonnet 4.6 committed 2026-04-01 00:22:02 +03:00
1 parent 1aa44ac987
commit 5ad2f4a8cb
2 files changed
+57 -1

No files matched your search

+26 -1
View File
@@ -661,6 +661,17 @@ impl Scene {
let vp_y0 = pcy - hh;
let vp_y1 = pcy + hh;
// ── Perspective setup ─────────────────────────────────────────
// camera_dist: how far the camera is from the target plane.
// Derived from lens_length (mm, 35mm-film equiv.) and view_height:
// tan(fov_v/2) = 12 / lens_length → camera_dist = view_height * lens_length / 24
let use_perspective = vp.status.perspective && vp.lens_length > 1.0;
let camera_dist = if use_perspective {
(vp.view_height as f32 * vp.lens_length as f32 / 24.0).max(0.001)
} else {
0.0
};
for wire in &model_wires {
// Project 3-D model points onto view plane → paper space.
let projected_pts: Vec<[f32; 3]> = wire.points.iter().map(|&[mx, my, mz]| {
@@ -670,7 +681,21 @@ impl Scene {
let mp = glam::Vec3::new(mx, my, mz) - target;
let u = mp.dot(view_right);
let v = mp.dot(view_up);
[pcx + u * scale, pcy + v * scale, pcz]
if use_perspective {
// vd points from target toward camera, so depth along vd is the
// distance from the target plane toward the camera.
let d_vd = mp.dot(vd);
// Forward distance from camera (positive = in front of camera).
let fwd = camera_dist - d_vd;
if fwd <= 0.001 {
// Point is behind or at the camera — discard.
return [f32::NAN; 3];
}
let factor = camera_dist / fwd;
[pcx + u * factor * scale, pcy + v * factor * scale, pcz]
} else {
[pcx + u * scale, pcy + v * scale, pcz]
}
}).collect();
// Fast AABB pre-reject: skip entirely if no finite point is