Viewport: a soft ground shadow under the model, seen from above (ui groundShadow, on by default)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-08 14:14:29 +00:00
1 parent 54c7690271
commit 1970b8c680
2 files changed
+50 -1

No files matched your search

+4 -1
View File
@@ -88,8 +88,10 @@ pub struct UiState {
pub pick_off: Vec<String>,
/// A click on a face picks its whole body.
pub pick_bodies: bool,
/// 0: shaded with edges, 1: shaded, 2: wireframe.
/// 0: shaded with edges, 1: shaded, 2: wireframe, 3: shaded with hidden edges.
pub visual_style: u8,
/// A soft shadow on the ground under the model (seen from above).
pub ground_shadow: bool,
/// Sketches hidden one by one, and finished sketches shown although a feature uses them.
pub hidden_sketches: Vec<u64>,
pub shown_sketches: Vec<u64>,
@@ -118,6 +120,7 @@ impl Default for UiState {
pick_off: Vec::new(),
pick_bodies: false,
visual_style: 0,
ground_shadow: true,
hidden_sketches: Vec::new(),
shown_sketches: Vec::new(),
hidden_profiles: Vec::new(),
+46
View File
@@ -162,6 +162,8 @@ fn scene_key(app: &SolveApp) -> u64 {
app.session.revision.hash(&mut h);
app.ui.show_grid.hash(&mut h);
app.ui.visual_style.hash(&mut h);
app.ui.ground_shadow.hash(&mut h);
(app.cam.pitch > 0.0).hash(&mut h);
app.ui.show_sketches.hash(&mut h);
app.ui.hidden_bodies.hash(&mut h);
app.ui.hidden_sketches.hash(&mut h);
@@ -270,6 +272,9 @@ 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);
}
for sid in visible_sketches(app) {
let Some(ss) = st.sketch(sid) else { continue };
let active = s.active_sketch == Some(sid);
@@ -301,6 +306,47 @@ fn build_scene(app: &SolveApp) -> GpuScene {
sc
}
/// A soft dark ellipse on the ground under the shown bodies (darkest in the middle).
fn ground_shadow(sc: &mut GpuScene, app: &SolveApp, st: &solvecraft_engine::doc::ModelState) {
let mut b = solvecraft_engine::geom::Aabb3::EMPTY;
for x in st.bodies.iter().filter(|x| !app.ui.hidden_bodies.contains(&x.name)) {
b = b.union(&x.mesh().bounds());
}
if b.is_empty() {
return;
}
let size = b.size();
let c = b.center();
let z = b.min.z - 1e-3 * (1.0 + b.diagonal());
let (rx, ry) = (size.x * 0.62 + size.z * 0.15, size.y * 0.62 + size.z * 0.15);
if !(rx > 0.0 && ry > 0.0) {
return;
}
let dark = if crate::theme::is_dark() { 150 } else { 80 };
// Rings from the middle out, fading to nothing.
let rings = [(0.0, dark), (0.55, dark * 3 / 4), (0.85, dark / 3), (1.0, 0)];
let n = 48;
let at = |r: f64, k: usize| {
let a = k as f64 / n as f64 * std::f64::consts::TAU;
Vec3::new(c.x + rx * r * a.cos(), c.y + ry * r * a.sin(), z).to_f32()
};
for w in rings.windows(2) {
let ((r0, a0), (r1, a1)) = (w[0], w[1]);
let (c0, c1) = ([0, 0, 0, a0 as u8], [0, 0, 0, a1 as u8]);
for k in 0..n {
let (p00, p01, p10, p11) = (at(r0, k), at(r0, k + 1), at(r1, k), at(r1, k + 1));
sc.trans_tri(p00, [0.0; 3], c0);
sc.trans_tri(p10, [0.0; 3], c1);
sc.trans_tri(p11, [0.0; 3], c1);
if r0 > 0.0 {
sc.trans_tri(p00, [0.0; 3], c0);
sc.trans_tri(p11, [0.0; 3], c1);
sc.trans_tri(p01, [0.0; 3], c0);
}
}
}
}
/// 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();