Section Analysis visuals: the cut faces are hatched at 45 degrees in the cap colour, and each body's outline along the cut is drawn
Part of #51 (per-body cap colours and the cut in snapshots to follow) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
This commit is contained in:
1 parent
794a394b28
commit
535bd5dfff
2 files changed
+56
-1
No files matched your search
@@ -328,7 +328,9 @@ fn fs_solid(i: TOut, @builtin(front_facing) front: bool) -> @location(0) vec4<f3
|
||||
let fw = fwidth(ndv);
|
||||
if (clipped(i.wp)) { discard; }
|
||||
if (!front && length(u.clip.xyz) > 0.5) {
|
||||
return out_color(u.cap);
|
||||
// The cut face: the cap colour, hatched at 45 degrees on screen.
|
||||
let h = fract((i.pos.x + i.pos.y) / 9.0);
|
||||
return out_color(vec4<f32>(select(u.cap.rgb, u.cap.rgb * 0.62, h < 0.22), u.cap.a));
|
||||
}
|
||||
if (u.ana.x > 0.5 && length(i.n) > 0.5) {
|
||||
return analysis(i, k);
|
||||
|
||||
@@ -165,6 +165,8 @@ fn scene_key(app: &SolveApp) -> u64 {
|
||||
app.ui.show_grid.hash(&mut h);
|
||||
app.ui.visual_style.hash(&mut h);
|
||||
app.ui.ground_shadow.hash(&mut h);
|
||||
// The section plane (its cut outlines are part of the model scene).
|
||||
section_plane(app).map(|(o, n)| [o.x, o.y, o.z, n.x, n.y, n.z].map(f64::to_bits)).hash(&mut h);
|
||||
app.cam.up.hash(&mut h);
|
||||
(app.cam.pitch > 0.0).hash(&mut h);
|
||||
app.ui.show_sketches.hash(&mut h);
|
||||
@@ -281,6 +283,15 @@ fn build_scene(app: &SolveApp) -> GpuScene {
|
||||
if app.ui.ground_shadow && app.ui.visual_style != 2 && app.cam.pitch > 0.0 {
|
||||
ground_shadow(&mut sc, app, &st);
|
||||
}
|
||||
// Section: the outline of each body where the plane cuts it.
|
||||
if let Some((o, n)) = section_plane(app) {
|
||||
let col = c4(tk.text.gamma_multiply(0.9));
|
||||
for b in st.bodies.iter().filter(|b| !app.ui.hidden_bodies.contains(&b.name)) {
|
||||
for (a, c) in section_segments(&b.mesh(), o, n) {
|
||||
sc.line(a.to_f32(), c.to_f32(), col, 1.8, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
for sid in visible_sketches(app) {
|
||||
let Some(ss) = st.sketch(sid) else { continue };
|
||||
let active = s.active_sketch == Some(sid);
|
||||
@@ -365,6 +376,28 @@ fn ground_shadow(sc: &mut GpuScene, app: &SolveApp, st: &solvecraft_engine::doc:
|
||||
}
|
||||
}
|
||||
|
||||
/// Where the plane through `o` with normal `n` cuts a mesh: one segment per crossed triangle,
|
||||
/// nudged a hair to the kept side (so the clip leaves them drawn).
|
||||
pub(crate) fn section_segments(m: &solvecraft_engine::geom::Mesh, o: Vec3, n: Vec3) -> Vec<(Vec3, Vec3)> {
|
||||
let eps = 1e-4 * (1.0 + m.bounds().diagonal());
|
||||
let mut out = Vec::new();
|
||||
for t in &m.triangles {
|
||||
let Some(p) = m.tri(t) else { continue };
|
||||
let d = p.map(|q| n.dot(q - o));
|
||||
let mut pts = Vec::with_capacity(2);
|
||||
for (i, j) in [(0, 1), (1, 2), (2, 0)] {
|
||||
if (d[i] < 0.0) != (d[j] < 0.0) {
|
||||
let s = d[i] / (d[i] - d[j]);
|
||||
pts.push(p[i] + (p[j] - p[i]) * s - n * eps);
|
||||
}
|
||||
}
|
||||
if let [a, b] = pts[..] {
|
||||
out.push((a, b));
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// The drawn pieces of a polyline in a dash pattern (on, off, on, off… lengths).
|
||||
pub(crate) fn dashes(pts: &[Vec3], pattern: &[f64]) -> Vec<(Vec3, Vec3)> {
|
||||
let mut out = Vec::new();
|
||||
@@ -2191,6 +2224,26 @@ fn view_menu(app: &mut SolveApp, ui: &mut egui::Ui, button: Rect) -> bool {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn section_outlines_follow_the_cut() {
|
||||
let mut s = solvecraft_engine::Session::default();
|
||||
s.execute("solid.box", &json!({"length": 40, "width": 30, "height": 20})).unwrap();
|
||||
let st = s.model.state();
|
||||
let m = st.bodies[0].mesh();
|
||||
let (o, n) = (Vec3::new(0.0, 15.0, 0.0), Vec3::new(0.0, -1.0, 0.0));
|
||||
let segs = section_segments(&m, o, n);
|
||||
assert!(segs.len() >= 4, "{segs:?}");
|
||||
// On the plane (a hair to the kept side) and around the 40 x 20 cut.
|
||||
let mut len = 0.0;
|
||||
for (a, b) in &segs {
|
||||
assert!((a.y - 15.0).abs() < 0.01 && (b.y - 15.0).abs() < 0.01);
|
||||
len += a.dist(*b);
|
||||
}
|
||||
assert!((len - 120.0).abs() < 0.5, "outline length {len}");
|
||||
// A plane that misses the body cuts nothing.
|
||||
assert!(section_segments(&m, Vec3::new(0.0, 99.0, 0.0), n).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selection_shows_where_the_model_hides_it() {
|
||||
let mut s = solvecraft_engine::Session::default();
|
||||
|
||||
Reference in new issue
Block a user