Skip to main content

brep_render/render/
scene_sync.rs

1use super::*;
2
3impl RenderCore {
4    /// The base face color of one solid under the active settings, in LINEAR
5    /// space (converted with f64 precision so artifact bytes stay stable).
6    fn face_base_color(solid: &SolidDisplay, settings: &RenderSettings) -> Rgba {
7        if let Some(rgb) = solid.color_override {
8            return [
9                srgb_to_linear(rgb[0] as f64) as f32,
10                srgb_to_linear(rgb[1] as f64) as f32,
11                srgb_to_linear(rgb[2] as f64) as f32,
12                1.0,
13            ];
14        }
15        match settings.face_color_mode {
16            FaceColorMode::HashedBySolid => {
17                let srgb = solid_color_srgb(&solid.name);
18                [
19                    srgb_to_linear(srgb[0]) as f32,
20                    srgb_to_linear(srgb[1]) as f32,
21                    srgb_to_linear(srgb[2]) as f32,
22                    1.0,
23                ]
24            }
25            FaceColorMode::Uniform => {
26                let c = settings.face_color;
27                [
28                    srgb_to_linear(c[0] as f64) as f32,
29                    srgb_to_linear(c[1] as f64) as f32,
30                    srgb_to_linear(c[2] as f64) as f32,
31                    c[3],
32                ]
33            }
34        }
35    }
36
37    fn upload_solid(&self, solid: &SolidDisplay, settings: &RenderSettings) -> GpuSolid {
38        let vertices: Vec<MeshVertex> = solid
39            .mesh
40            .positions
41            .iter()
42            .zip(&solid.mesh.normals)
43            .map(|(&position, &normal)| MeshVertex { position, normal })
44            .collect();
45        let vertex_buf = self
46            .device
47            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
48                label: Some(solid.name.as_str()),
49                contents: bytemuck::cast_slice(&vertices),
50                usage: wgpu::BufferUsages::VERTEX,
51            });
52        let index_buf = self
53            .device
54            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
55                label: Some(solid.name.as_str()),
56                contents: bytemuck::cast_slice(&solid.mesh.indices),
57                usage: wgpu::BufferUsages::INDEX,
58            });
59        // Wireframe line-list: every triangle (a,b,c) → edges (a,b),(b,c),(c,a).
60        let mut wire_indices: Vec<u32> = Vec::with_capacity(solid.mesh.indices.len() * 2);
61        for tri in solid.mesh.indices.chunks_exact(3) {
62            wire_indices.extend_from_slice(&[
63                tri[0], tri[1], tri[1], tri[2], tri[2], tri[0],
64            ]);
65        }
66        let wire_index_count = wire_indices.len() as u32;
67        let wire_index_buf = self
68            .device
69            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
70                label: Some(solid.name.as_str()),
71                contents: bytemuck::cast_slice(&wire_indices),
72                usage: wgpu::BufferUsages::INDEX,
73            });
74        let faces = solid
75            .faces
76            .iter()
77            .map(|face| FaceRange {
78                first_index: face.tri_start * 3,
79                index_count: face.tri_count * 3,
80            })
81            .collect();
82
83        let mut segments: Vec<EdgeInstance> = Vec::new();
84        let mut edges = Vec::with_capacity(solid.edges.len());
85        for edge in &solid.edges {
86            let first_instance = segments.len() as u32;
87            for pair in edge.polyline.windows(2) {
88                segments.push(EdgeInstance {
89                    p0: pair[0],
90                    p1: pair[1],
91                });
92            }
93            edges.push(EdgeRange {
94                first_instance,
95                instance_count: segments.len() as u32 - first_instance,
96            });
97        }
98        let edge_buf = (!segments.is_empty()).then(|| {
99            self.device
100                .create_buffer_init(&wgpu::util::BufferInitDescriptor {
101                    label: Some(solid.name.as_str()),
102                    contents: bytemuck::cast_slice(&segments),
103                    usage: wgpu::BufferUsages::VERTEX,
104                })
105        });
106
107        let points: Vec<PointInstance> = solid
108            .vertices
109            .iter()
110            .map(|v| PointInstance {
111                center: [
112                    v.position[0] as f32,
113                    v.position[1] as f32,
114                    v.position[2] as f32,
115                ],
116            })
117            .collect();
118        let point_buf = (!points.is_empty()).then(|| {
119            self.device
120                .create_buffer_init(&wgpu::util::BufferInitDescriptor {
121                    label: Some(solid.name.as_str()),
122                    contents: bytemuck::cast_slice(&points),
123                    usage: wgpu::BufferUsages::VERTEX,
124                })
125        });
126
127        let base_style = StyleBuf::new(&self.device, &self.style_layout, solid.name.as_str());
128        base_style.write(
129            &self.queue,
130            Self::face_base_color(solid, settings),
131            [if settings.flat_shading { 1.0 } else { 0.0 }, 0.0, 0.0, 0.0],
132        );
133
134        GpuSolid {
135            revision: solid.revision,
136            vertex_buf,
137            index_buf,
138            wire_index_buf,
139            wire_index_count,
140            faces,
141            edge_buf,
142            edges,
143            edge_instances: segments.len() as u32,
144            point_buf,
145            point_count: points.len() as u32,
146            base_style,
147            boundary: None,
148        }
149    }
150
151    /// Bring the GPU scene in line with the display scene: new/changed solids
152    /// upload, unchanged solids keep their buffers (R10), removed solids drop
153    /// theirs. `settings_generation` invalidates per-solid style buffers only.
154    pub fn sync_scene(
155        &self,
156        gpu: &mut GpuScene,
157        scene: &RenderScene,
158        settings: &RenderSettings,
159        settings_generation: u64,
160    ) {
161        gpu.order.clear();
162        let mut seen: Vec<&str> = Vec::with_capacity(scene.solids().len());
163        for solid in scene.solids() {
164            gpu.order.push(solid.name.clone());
165            seen.push(solid.name.as_str());
166            let refresh_style = gpu.settings_generation != settings_generation;
167            match gpu.solids.get_mut(&solid.name) {
168                Some(existing) if existing.revision == solid.revision => {
169                    if refresh_style {
170                        existing.base_style.write(
171                            &self.queue,
172                            Self::face_base_color(solid, settings),
173                            [if settings.flat_shading { 1.0 } else { 0.0 }, 0.0, 0.0, 0.0],
174                        );
175                    }
176                }
177                _ => {
178                    let uploaded = self.upload_solid(solid, settings);
179                    gpu.solids.insert(solid.name.clone(), uploaded);
180                    gpu.uploads += 1;
181                }
182            }
183        }
184        gpu.solids.retain(|name, _| seen.contains(&name.as_str()));
185        gpu.settings_generation = settings_generation;
186    }
187
188    /// Upload a scene from scratch (artifact/one-shot path).
189    pub fn upload_scene(&self, scene: &RenderScene) -> GpuScene {
190        self.upload_scene_with(scene, &RenderSettings::artifact())
191    }
192
193    pub fn upload_scene_with(&self, scene: &RenderScene, settings: &RenderSettings) -> GpuScene {
194        let mut gpu = GpuScene::default();
195        self.sync_scene(&mut gpu, scene, settings, 0);
196        gpu
197    }
198
199    pub(super) fn ensure_targets(&mut self, width: u32, height: u32) {
200        if let Some(targets) = &self.targets {
201            if targets.width == width && targets.height == height {
202                return;
203            }
204        }
205        let size = wgpu::Extent3d {
206            width,
207            height,
208            depth_or_array_layers: 1,
209        };
210        let msaa = self.device.create_texture(&wgpu::TextureDescriptor {
211            label: Some("msaa color"),
212            size,
213            mip_level_count: 1,
214            sample_count: SAMPLES,
215            dimension: wgpu::TextureDimension::D2,
216            format: self.format,
217            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
218            view_formats: &[],
219        });
220        let depth = self.device.create_texture(&wgpu::TextureDescriptor {
221            label: Some("depth"),
222            size,
223            mip_level_count: 1,
224            sample_count: SAMPLES,
225            dimension: wgpu::TextureDimension::D2,
226            format: DEPTH_FORMAT,
227            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
228            view_formats: &[],
229        });
230        self.targets = Some(CachedTargets {
231            width,
232            height,
233            msaa_view: msaa.create_view(&Default::default()),
234            depth_view: depth.create_view(&Default::default()),
235        });
236    }
237
238    pub(super) fn write_global_styles(&self, settings: &RenderSettings) {
239        let flat = if settings.flat_shading { 1.0 } else { 0.0 };
240        let face = |c: Rgba| -> Rgba {
241            [
242                srgb_to_linear(c[0] as f64) as f32,
243                srgb_to_linear(c[1] as f64) as f32,
244                srgb_to_linear(c[2] as f64) as f32,
245                c[3],
246            ]
247        };
248        let s = &self.styles;
249        let q = &self.queue;
250        s.face_selected
251            .write(q, face(settings.face_selected_color), [flat, 0.0, 0.0, 0.0]);
252        s.face_hovered
253            .write(q, face(settings.hover_color), [flat, 0.0, 0.0, 0.0]);
254        let edge_params = [settings.edge_width_px, EDGE_NUDGE, 0.0, 0.0];
255        s.edge_base.write(q, settings.edge_color, edge_params);
256        s.edge_selected.write(q, settings.edge_selected_color, edge_params);
257        s.edge_hovered.write(q, settings.hover_color, edge_params);
258        let mut hidden = settings.edge_color;
259        hidden[3] = settings.hidden_edge_alpha;
260        s.edge_hidden.write(q, hidden, edge_params);
261        // Selected-face boundary: selected-edge color, slightly wider, extra
262        // nudge so it reads as an outline on top of the fill.
263        s.boundary.write(
264            q,
265            settings.edge_selected_color,
266            [settings.edge_width_px + 1.0, EDGE_NUDGE * 1.5, 0.0, 0.0],
267        );
268        let point_params = [settings.vertex_size_px, 0.0, 0.0, 0.0];
269        s.point_base.write(q, settings.vertex_color, point_params);
270        s.point_selected
271            .write(q, settings.vertex_selected_color, [settings.vertex_size_px + 1.0, 0.0, 0.0, 0.0]);
272        s.point_hovered
273            .write(q, settings.hover_color, [settings.vertex_size_px + 1.0, 0.0, 0.0, 0.0]);
274        let axis_params = [2.0, EDGE_NUDGE, 0.0, 0.0];
275        s.axis_x.write(q, [0.91, 0.30, 0.32, 1.0], axis_params);
276        s.axis_y.write(q, [0.27, 0.80, 0.42, 1.0], axis_params);
277        s.axis_z.write(q, [0.23, 0.51, 0.96, 1.0], axis_params);
278        // Overlay-widget line width (color is per-instance from the gizmo).
279        s.overlay_line.write(q, [1.0, 1.0, 1.0, 1.0], [2.5, 0.0, 0.0, 0.0]);
280    }
281
282    /// Rebuild the selected/hovered-face boundary outline buffer for one solid
283    /// when the emphasis or the solid changed. The boundary of a face's
284    /// triangle range = mesh edges used an odd number of times inside it.
285    pub(super) fn sync_boundary(&self, gpu_solid: &mut GpuSolid, solid: &SolidDisplay, emphasis: &Emphasis) {
286        if let Some(boundary) = &gpu_solid.boundary {
287            if boundary.emphasis_generation == emphasis.generation
288                && boundary.revision == solid.revision
289            {
290                return;
291            }
292        }
293        let mut segments: Vec<EdgeInstance> = Vec::new();
294        for face in &solid.faces {
295            if face.tri_count == 0 {
296                continue;
297            }
298            let state = emphasis.face_state(&solid.name, &face.name);
299            if state == EmphasisState::Base {
300                continue;
301            }
302            let mut edge_use: HashMap<(u32, u32), u32> = HashMap::new();
303            let start = face.tri_start as usize;
304            let end = (start + face.tri_count as usize).min(solid.mesh.indices.len() / 3);
305            for tri in start..end {
306                let idx = [
307                    solid.mesh.indices[tri * 3],
308                    solid.mesh.indices[tri * 3 + 1],
309                    solid.mesh.indices[tri * 3 + 2],
310                ];
311                for k in 0..3 {
312                    let a = idx[k];
313                    let b = idx[(k + 1) % 3];
314                    let key = if a < b { (a, b) } else { (b, a) };
315                    *edge_use.entry(key).or_insert(0) += 1;
316                }
317            }
318            let mut boundary: Vec<(u32, u32)> = edge_use
319                .into_iter()
320                .filter_map(|(key, count)| (count == 1).then_some(key))
321                .collect();
322            boundary.sort_unstable();
323            for (a, b) in boundary {
324                segments.push(EdgeInstance {
325                    p0: solid.mesh.positions[a as usize],
326                    p1: solid.mesh.positions[b as usize],
327                });
328            }
329        }
330        let buf = (!segments.is_empty()).then(|| {
331            self.device
332                .create_buffer_init(&wgpu::util::BufferInitDescriptor {
333                    label: Some("emphasis boundary"),
334                    contents: bytemuck::cast_slice(&segments),
335                    usage: wgpu::BufferUsages::VERTEX,
336                })
337        });
338        gpu_solid.boundary = Some(BoundaryBuf {
339            emphasis_generation: emphasis.generation,
340            revision: solid.revision,
341            buf,
342            count: segments.len() as u32,
343        });
344    }
345}