Appearances in the viewport: per-face looks colour their faces, and see-through bodies and faces (opacity below 1) draw as glass: far sides first, then near sides, blended over the model

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 2b9ed16cc8
commit a3d187ccd1
2 files changed
+59 -1

No files matched your search

+41
View File
@@ -22,6 +22,9 @@ pub struct GpuScene {
/// Translucent triangles (alpha from the colour), drawn after the opaque ones without
/// writing depth.
pub trans: Vec<u8>,
/// 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<u8>,
/// Depth-tested lines.
pub lines: Vec<u8>,
/// 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<u64>,
tris: Option<Batch>,
trans: Option<Batch>,
glass: Option<Batch>,
lines: Option<Batch>,
overlays: Option<Batch>,
ghost: Option<Batch>,
@@ -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);
+18 -1
View File
@@ -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<usize, solvecraft_engine::doc::appearance::Look> =
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);
}