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:
parent
1aa44ac987
commit
5ad2f4a8cb
2 changed files with 57 additions and 1 deletions
|
|
@ -168,6 +168,23 @@ fn properties(vp: &Viewport) -> PropSection {
|
|||
},
|
||||
edit("Target X", "vtgt_x", vp.view_target.x),
|
||||
edit("Target Z", "vtgt_z", vp.view_target.z),
|
||||
// Perspective lens length.
|
||||
edit("Lens Length (mm)", "vp_lens", vp.lens_length),
|
||||
// Per-Viewport UCS.
|
||||
Property {
|
||||
label: "UCS Per Viewport".into(),
|
||||
field: "vp_ucs_per_vp",
|
||||
value: PropValue::BoolToggle { field: "vp_ucs_per_vp", value: vp.ucs_per_viewport },
|
||||
},
|
||||
edit("UCS Origin X", "vp_ucs_ox", vp.ucs_origin.x),
|
||||
edit("UCS Origin Y", "vp_ucs_oy", vp.ucs_origin.y),
|
||||
edit("UCS Origin Z", "vp_ucs_oz", vp.ucs_origin.z),
|
||||
edit("UCS X-Axis X", "vp_ucs_xx", vp.ucs_x_axis.x),
|
||||
edit("UCS X-Axis Y", "vp_ucs_xy", vp.ucs_x_axis.y),
|
||||
edit("UCS X-Axis Z", "vp_ucs_xz", vp.ucs_x_axis.z),
|
||||
edit("UCS Y-Axis X", "vp_ucs_yx", vp.ucs_y_axis.x),
|
||||
edit("UCS Y-Axis Y", "vp_ucs_yy", vp.ucs_y_axis.y),
|
||||
edit("UCS Y-Axis Z", "vp_ucs_yz", vp.ucs_y_axis.z),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
|
@ -218,6 +235,10 @@ fn apply_geom_prop(vp: &mut Viewport, field: &str, value: &str) {
|
|||
vp.ucs_icon_visible = if value == "toggle" { !vp.ucs_icon_visible } else { value == "true" };
|
||||
return;
|
||||
}
|
||||
"vp_ucs_per_vp" => {
|
||||
vp.ucs_per_viewport = if value == "toggle" { !vp.ucs_per_viewport } else { value == "true" };
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
|
@ -284,6 +305,16 @@ fn apply_geom_prop(vp: &mut Viewport, field: &str, value: &str) {
|
|||
}
|
||||
"vtgt_x" => vp.view_target.x = v,
|
||||
"vtgt_z" => vp.view_target.z = v,
|
||||
"vp_lens" if v > 0.0 => vp.lens_length = v,
|
||||
"vp_ucs_ox" => vp.ucs_origin.x = v,
|
||||
"vp_ucs_oy" => vp.ucs_origin.y = v,
|
||||
"vp_ucs_oz" => vp.ucs_origin.z = v,
|
||||
"vp_ucs_xx" => vp.ucs_x_axis.x = v,
|
||||
"vp_ucs_xy" => vp.ucs_x_axis.y = v,
|
||||
"vp_ucs_xz" => vp.ucs_x_axis.z = v,
|
||||
"vp_ucs_yx" => vp.ucs_y_axis.x = v,
|
||||
"vp_ucs_yy" => vp.ucs_y_axis.y = v,
|
||||
"vp_ucs_yz" => vp.ucs_y_axis.z = v,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue