diff --git a/crates/ui-egui/src/gpu.rs b/crates/ui-egui/src/gpu.rs index 7300190b..e75263c3 100644 --- a/crates/ui-egui/src/gpu.rs +++ b/crates/ui-egui/src/gpu.rs @@ -22,6 +22,9 @@ pub struct GpuScene { /// Translucent triangles (alpha from the colour), drawn after the opaque ones without /// writing depth. pub trans: Vec, + /// See-through bodies and faces (appearance opacity below 1), closed and outward-wound: + /// their far sides are drawn first, then their near sides, so they read like glass. + pub glass: Vec, /// Depth-tested lines. pub lines: Vec, /// Lines drawn over everything (active sketch, highlights). @@ -49,6 +52,12 @@ impl GpuScene { } self.trans.extend_from_slice(&c); } + pub fn glass_tri(&mut self, p: [f32; 3], n: [f32; 3], c: [u8; 4]) { + for v in p.iter().chain(&n) { + self.glass.extend_from_slice(&v.to_le_bytes()); + } + self.glass.extend_from_slice(&c); + } pub fn ghost_tri(&mut self, p: [f32; 3], n: [f32; 3], c: [u8; 4]) { for v in p.iter().chain(&n) { self.ghost.extend_from_slice(&v.to_le_bytes()); @@ -151,6 +160,7 @@ struct Batches { key: Option, tris: Option, trans: Option, + glass: Option, lines: Option, overlays: Option, ghost: Option, @@ -166,6 +176,7 @@ impl Batches { if let Some(sc) = slot.lock().ok().and_then(|mut s| s.take()) { self.tris = upload(device, "sc_tris", &sc.tris, TRI_SIZE); self.trans = upload(device, "sc_trans", &sc.trans, TRI_SIZE); + self.glass = upload(device, "sc_glass", &sc.glass, TRI_SIZE); self.lines = upload(device, "sc_lines", &sc.lines, LINE_SIZE); self.overlays = upload(device, "sc_overlay", &sc.overlay, LINE_SIZE); self.ghost = upload(device, "sc_ghost", &sc.ghost, TRI_SIZE); @@ -181,6 +192,9 @@ struct Resources { /// Highlight triangles over the model (depth test ≤, no depth write). tri_hl: wgpu::RenderPipeline, trans: wgpu::RenderPipeline, + /// Glass: far sides (front faces culled), then near sides (back faces culled). + glass_far: wgpu::RenderPipeline, + glass_near: wgpu::RenderPipeline, ghost: wgpu::RenderPipeline, xray: wgpu::RenderPipeline, line: wgpu::RenderPipeline, @@ -549,6 +563,29 @@ impl Resources { }) }; let alpha = Some(wgpu::BlendState::ALPHA_BLENDING); + let glass = |label: &str, cull: wgpu::Face| { + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some(label), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &module, + entry_point: Some("vs_tri"), + buffers: &[Some(tri_layout())], + compilation_options: Default::default(), + }, + primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, cull_mode: Some(cull), ..Default::default() }, + depth_stencil: depth(wgpu::CompareFunction::LessEqual, false), + multisample: wgpu::MultisampleState { count: t.samples.max(1), mask: !0, alpha_to_coverage_enabled: false }, + fragment: Some(wgpu::FragmentState { + module: &module, + entry_point: Some("fs_tri"), + targets: &[Some(wgpu::ColorTargetState { format: t.format, blend: alpha, write_mask: wgpu::ColorWrites::ALL })], + compilation_options: Default::default(), + }), + multiview_mask: None, + cache: None, + }) + }; let image_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("sc_image"), entries: &[ @@ -659,6 +696,8 @@ impl Resources { access_version: 0, tri_hl: pipeline("sc_tris_hl", "vs_tri", "fs_tri", tri_layout(), depth(wgpu::CompareFunction::LessEqual, false), alpha), trans: pipeline("sc_trans", "vs_tri", "fs_tri", tri_layout(), depth(wgpu::CompareFunction::LessEqual, false), alpha), + glass_far: glass("sc_glass_far", wgpu::Face::Front), + glass_near: glass("sc_glass_near", wgpu::Face::Back), ghost: pipeline("sc_ghost", "vs_ghost", "fs_tri", tri_layout(), depth(wgpu::CompareFunction::LessEqual, false), alpha), xray: pipeline("sc_xray", "vs_tri", "fs_tri", tri_layout(), depth(wgpu::CompareFunction::Always, false), alpha), line: pipeline("sc_lines", "vs_line", "fs_line", line_layout(), depth(wgpu::CompareFunction::LessEqual, false), alpha), @@ -807,6 +846,8 @@ impl egui_wgpu::CallbackTrait for ViewportCallback { } } tris(pass, &res.ghost, &pv.ghost); + tris(pass, &res.glass_far, &m.glass); + tris(pass, &res.glass_near, &m.glass); tris(pass, &res.trans, &m.trans); tris(pass, &res.trans, &h.trans); tris(pass, &res.xray, &pv.xray); diff --git a/crates/ui-egui/src/viewport.rs b/crates/ui-egui/src/viewport.rs index f7f7fdd5..804c5a27 100644 --- a/crates/ui-egui/src/viewport.rs +++ b/crates/ui-egui/src/viewport.rs @@ -226,12 +226,29 @@ fn build_scene(app: &SolveApp) -> GpuScene { _ => (true, true), }; let edge_col = if shaded { c4(tk.body_edge) } else { c4(tk.text) }; - for t in m.triangles.iter().filter(|_| shaded) { + // Appearances: the body's opacity, and faces with looks of their own. + let opacity = s.doc.body_look(&b.name, b.feature).map_or(1.0, |l| l.opacity); + let face_looks: std::collections::HashMap = + if shaded { s.doc.face_colors(b).into_iter().collect() } else { Default::default() }; + for (ti, t) in m.triangles.iter().enumerate().filter(|_| shaded) { + let (mut col, a) = match m.tri_face.get(ti).and_then(|f| face_looks.get(&(*f as usize))) { + Some(l) => { + let c = Color32::from_rgb(l.color[0], l.color[1], l.color[2]); + (if faded { c4(c.gamma_multiply(0.35)) } else { c4(c) }, l.opacity) + } + None => (col, opacity), + }; + let glass = !faded && a < 0.999; + if glass { + col[3] = (a.clamp(0.05, 1.0) * 255.0).round() as u8; + } for k in t { let i = *k as usize; if let (Some(p), Some(n)) = (m.positions.get(i), m.normals.get(i)) { if faded { sc.trans_tri(p.to_f32(), n.to_f32(), col); + } else if glass { + sc.glass_tri(p.to_f32(), n.to_f32(), col); } else { sc.tri(p.to_f32(), n.to_f32(), col); }