diff --git a/crates/engine/src/cmd/surface_analysis.rs b/crates/engine/src/cmd/surface_analysis.rs index 61450d9f..67c81c65 100644 --- a/crates/engine/src/cmd/surface_analysis.rs +++ b/crates/engine/src/cmd/surface_analysis.rs @@ -19,6 +19,11 @@ pub static COMMANDS: &[CommandSpec] = &[ .icon("draft_analysis") .noundo() .params("pull?: [x,y,z] direction (default Z); angle?: degrees (default 1); clear?: true turns the analysis off"), + CommandSpec::new("FusionEnvironmentMapAnalysisCommand", "Environment Map Analysis", |s, p| set(s, p, SurfaceAnalysis::Environment)) + .at("SKETCH", "INSPECT") + .icon("environment_map") + .noundo() + .params("clear?: true turns the analysis off"), CommandSpec::new("FusionCurvatureMapAnalysisCommand", "Curvature Map Analysis", curvature_map) .at("SKETCH", "INSPECT") .icon("curvature_map") @@ -88,6 +93,8 @@ mod tests { assert!(matches!(s.analysis, Some(SurfaceAnalysis::Draft { angle, .. }) if angle == 3.0)); s.execute("FusionCurvatureMapAnalysisCommand", &json!({})).unwrap(); assert!(matches!(s.analysis, Some(SurfaceAnalysis::Curvature { radius }) if (radius - 300f64.sqrt() / 10.0).abs() < 1e-6)); + s.execute("FusionEnvironmentMapAnalysisCommand", &json!({})).unwrap(); + assert_eq!(s.analysis, Some(SurfaceAnalysis::Environment)); s.execute("FusionCurvatureMapAnalysisCommand", &json!({"clear": true})).unwrap(); assert_eq!(s.analysis, None); assert_eq!(s.undo.len(), undo, "no undo steps"); diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 5e314838..a1d08fe1 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -176,6 +176,8 @@ pub enum SurfaceAnalysis { Draft { pull: Vec3, angle: f64 }, /// Colour by curvature: blue flat, through green, to red at 1 / `radius`. Curvature { radius: f64 }, + /// Mirror finish reflecting a studio (sky, horizon, floor, light panels). + Environment, } const MAX_UNDO: usize = 200; diff --git a/crates/ui-egui/src/gpu.rs b/crates/ui-egui/src/gpu.rs index 679d517b..960c6e8b 100644 --- a/crates/ui-egui/src/gpu.rs +++ b/crates/ui-egui/src/gpu.rs @@ -257,6 +257,21 @@ fn analysis(i: TOut) -> vec4 { let c = select(0.08, 0.95, s > 0.0); return out_color(vec4(vec3(c), 1.0)); } + if (u.ana.x > 3.5) { + // A studio around the model: sky above, a dark floor below, a bright horizon band and + // two soft light panels, seen in a mirror. + let r = reflect(-v, n); + var c = mix(vec3(0.32, 0.34, 0.38), vec3(0.80, 0.86, 0.95), smoothstep(-0.05, 0.6, r.z)); + c = c + vec3(0.6) * exp(-abs(r.z) * 40.0); + let side = atan2(r.y, r.x); + c = c + vec3(0.9) * smoothstep(0.92, 0.98, cos(side * 2.0)) * smoothstep(0.1, 0.3, r.z) * smoothstep(0.8, 0.6, r.z); + if (r.z < -0.02) { + // The floor: lighter toward the horizon, with light-panel streaks along it. + c = mix(vec3(0.55, 0.56, 0.60), vec3(0.20, 0.20, 0.22), clamp(-r.z * 1.6, 0.0, 1.0)); + c = c + vec3(0.35) * smoothstep(0.95, 0.99, cos(side * 3.0)); + } + return out_color(vec4(min(c, vec3(1.0)), 1.0)); + } if (u.ana.x < 2.5) { let a = degrees(asin(clamp(dot(normalize(i.n), normalize(u.pull.xyz)), -1.0, 1.0))); var c = vec3(0.95, 0.80, 0.15); diff --git a/crates/ui-egui/src/icons.rs b/crates/ui-egui/src/icons.rs index bdd932db..0be3c519 100644 --- a/crates/ui-egui/src/icons.rs +++ b/crates/ui-egui/src/icons.rs @@ -652,6 +652,15 @@ pub fn paint(p: &Painter, r: Rect, name: &str, ink: Color32, fill: Color32, acce pen.line(&[(3.0 + 12.0 * t, 17.0 + 2.0 * t), (9.0 + 12.0 * t, 5.0 + 2.0 * t)], a); } } + "environment_map" => { + let pts: Vec = + (0..32).map(|i| i as f32 / 32.0 * std::f32::consts::TAU).map(|t| pen.at(12.0 + 9.0 * t.cos(), 12.0 + 9.0 * t.sin())).collect(); + p.add(Shape::convex_polygon(pts, Color32::from_rgb(190, 205, 230), Stroke::new(pen.w, ink))); + pen.poly(&[(3.6, 13.0), (20.4, 13.0), (18.5, 17.5), (5.5, 17.5)], Color32::from_rgb(60, 62, 70), Color32::TRANSPARENT); + pen.line(&[(3.2, 12.6), (20.8, 12.6)], Color32::WHITE); + p.circle_filled(pen.at(9.0, 8.0), 2.2 * pen.s(), Color32::WHITE); + pen.circle(12.0, 12.0, 9.0, Color32::TRANSPARENT, ink); + } "zebra" | "draft_analysis" | "curvature_map" => { let pts: Vec = (0..32).map(|i| i as f32 / 32.0 * std::f32::consts::TAU).map(|t| pen.at(12.0 + 9.0 * t.cos(), 12.0 + 9.0 * t.sin())).collect(); diff --git a/crates/ui-egui/src/sketch_tools.rs b/crates/ui-egui/src/sketch_tools.rs index 321ba666..6e5e6567 100644 --- a/crates/ui-egui/src/sketch_tools.rs +++ b/crates/ui-egui/src/sketch_tools.rs @@ -101,7 +101,11 @@ pub fn start_hook(app: &mut SolveApp, id: &str) -> bool { ("FusionZebraAnalysisCommand", Some(A::Zebra { .. })) => Some(true), ("FusionDraftAnalysisCommand", Some(A::Draft { .. })) => Some(true), ("FusionCurvatureMapAnalysisCommand", Some(A::Curvature { .. })) => Some(true), - ("FusionZebraAnalysisCommand" | "FusionDraftAnalysisCommand" | "FusionCurvatureMapAnalysisCommand", _) => Some(false), + ("FusionEnvironmentMapAnalysisCommand", Some(A::Environment)) => Some(true), + ( + "FusionZebraAnalysisCommand" | "FusionDraftAnalysisCommand" | "FusionCurvatureMapAnalysisCommand" | "FusionEnvironmentMapAnalysisCommand", + _, + ) => Some(false), _ => None, }; if let Some(on) = same { diff --git a/crates/ui-egui/src/viewport.rs b/crates/ui-egui/src/viewport.rs index c1acc2f3..58755767 100644 --- a/crates/ui-egui/src/viewport.rs +++ b/crates/ui-egui/src/viewport.rs @@ -653,6 +653,7 @@ fn analysis_uniform(app: &SolveApp) -> [f32; 8] { Some(A::Zebra { stripes }) => [1.0, stripes as f32, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], Some(A::Draft { pull, angle }) => [2.0, angle as f32, 0.0, 0.0, pull.x as f32, pull.y as f32, pull.z as f32, 0.0], Some(A::Curvature { radius }) => [3.0, radius as f32, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + Some(A::Environment) => [4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], None => [0.0; 8], } }