BREP_render 0.2.1

BREP Rust rendering engine: kernel-fed scene store + wgpu renderer (headless artifact, desktop window, and wasm canvas shells).
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
//! The renderer-agnostic scene store (requirements R8–R9): display objects
//! keyed by the SAME kernel names the feature pipeline mints, with typed
//! metadata — no `userData` grab-bag, no renderer objects. Rendering, picking
//! and feature-reference display all resolve against this one map.
//!
//! Slice 1 populates solids (face mesh ranges + names, edge polylines + names,
//! vertices) from the kernel's native display payload. Datums/sketches/widgets
//! arrive in later slices as further [`DisplayObject`] kinds.

use crate::camera::Aabb;
use std::collections::HashMap;

/// The kind of a display face (surface classification rides along when known —
/// typed replacement for the previous app's untyped `faceKind` tag).
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum FaceKind {
    Unknown,
}

/// One face of a solid: a contiguous triangle range of the solid mesh plus the
/// kernel face identity.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct FaceDisplay {
    /// Kernel face name (byte-exact pipeline name); empty when unnamed.
    pub name: String,
    /// Kernel topology face id.
    pub topo_id: u64,
    /// First triangle (not index) of this face in the mesh.
    pub tri_start: u32,
    /// Triangle count.
    pub tri_count: u32,
    pub kind: FaceKind,
}

/// One display edge: a world-space polyline plus the kernel edge identity and
/// the typed flags the previous display layer kept per object.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct EdgeDisplay {
    /// Kernel edge name (`faceA|faceB[n]` convention); empty when unnamed.
    pub name: String,
    /// Kernel topology edge id.
    pub topo_id: u64,
    /// World-space polyline (chord-tolerance sampled, ≥ 2 points).
    pub polyline: Vec<[f32; 3]>,
    /// Auxiliary display edge (not a real BREP boundary).
    pub aux: bool,
    /// Centerline flag (hole/revolve axis display).
    pub centerline: bool,
}

/// One display vertex (kernel topology vertex).
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct VertexDisplay {
    pub topo_id: u64,
    pub position: [f64; 3],
}

/// The triangle mesh of one solid, ready for GPU upload (f32; the kernel's f64
/// buffers are narrowed exactly once, here).
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct DisplayMesh {
    /// Interleaved-ready parallel arrays: xyz per vertex.
    pub positions: Vec<[f32; 3]>,
    pub normals: Vec<[f32; 3]>,
    /// Triangle indices (3 per triangle).
    pub indices: Vec<u32>,
    /// Per-TRIANGLE face index into `SolidDisplay::faces`.
    pub face_ids: Vec<u32>,
}

/// A displayed solid: mesh + named faces/edges/vertices + visibility.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SolidDisplay {
    /// Kernel solid name — the scene key.
    pub name: String,
    /// The resident kernel handle this display was tessellated from (0 = none,
    /// e.g. a synthesized sheet with no kernel solid). Handles are monotonic and
    /// never recycled, so this is a stable identity for the geometry: the R10
    /// display-reuse fast path keeps an existing display across a history rerun
    /// ONLY when the name's resident handle still equals this — a name re-bound to
    /// a DIFFERENT handle (a SUBTRACT result inherits its target's name; a
    /// roll-back replays that name's ORIGINAL producer) must re-tessellate.
    pub source_handle: u32,
    pub visible: bool,
    /// Optional per-solid base color override (R14 — user-set solid color),
    /// else the name-hashed stable color is used.
    pub color_override: Option<[f32; 3]>,
    /// Monotonic content revision (R10). Every freshly built display gets a
    /// unique value, so the renderer's GPU-buffer cache re-uploads on any
    /// rebuild — correct-by-default (equivalent to teardown-rebuild). The
    /// reused-buffer fast path (keep a revision stable across a `reused`
    /// pipeline result) is a follow-up optimization.
    pub revision: u64,
    pub mesh: DisplayMesh,
    pub faces: Vec<FaceDisplay>,
    pub edges: Vec<EdgeDisplay>,
    pub vertices: Vec<VertexDisplay>,
    /// Per-entity + group hide state (individual faces/edges/vertices, or a
    /// whole group). Default = everything visible; see [`crate::visibility`].
    /// Reused solids keep it across history reruns (this whole struct is cloned
    /// forward); a re-tessellated solid resets to all-visible.
    pub visibility: crate::visibility::EntityVisibility,
    /// World bbox over mesh positions (edges lie on the mesh by construction).
    pub bbox: Aabb,
    /// This display is a SYNTHESIZED committed-sketch SHEET (planar face + named
    /// boundary edges + corner vertices), not a kernel solid — it carries no
    /// resident handle (`source_handle == 0`). The marker lets the UI treat a
    /// sketch as a sketch: it is listed under "Sketches" (not among solids) yet is
    /// pickable / selectable / measurable like any scene solid.
    pub is_sketch: bool,
    /// This body is SHEET METAL — its resident handle carried a `SheetTree` when
    /// the display was built. Stamped by the pipeline from
    /// [`brep_kernel::is_sheet_metal_handle`] on the RUNNER thread (where the
    /// tree's thread-local is warm), so the UI thread can answer "is this a
    /// sheet-metal body?" straight off the scene — the gate for the sheet-metal
    /// edit features (SM Flange / Fillet / Chamfer). Immutable-correct: a tree is
    /// attached at solid creation and dropped exactly when the handle is freed,
    /// handles never recycle, and the display-reuse fast path clones this struct
    /// forward only while the handle is unchanged.
    pub is_sheet_metal: bool,
}

/// Source of monotonic [`SolidDisplay::revision`] values.
fn next_revision() -> u64 {
    use std::sync::atomic::{AtomicU64, Ordering};
    static COUNTER: AtomicU64 = AtomicU64::new(1);
    COUNTER.fetch_add(1, Ordering::Relaxed)
}

/// The scene: insertion-ordered solids + an exact name index (R8 — the
/// `getObjectByName` heuristic-scoring lookup is replaced by this map).
#[derive(Debug, Default)]
pub struct RenderScene {
    solids: Vec<SolidDisplay>,
    index: HashMap<String, usize>,
}

impl RenderScene {
    pub fn new() -> Self {
        Self::default()
    }

    /// Insert or replace a solid by name (replacement keeps insertion order —
    /// a boolean result reusing its target's name stays in place).
    pub fn insert_solid(&mut self, solid: SolidDisplay) {
        match self.index.get(&solid.name) {
            Some(&slot) => self.solids[slot] = solid,
            None => {
                self.index.insert(solid.name.clone(), self.solids.len());
                self.solids.push(solid);
            }
        }
    }

    /// Drop every solid (the scene rebuild path clears then repopulates).
    pub fn clear(&mut self) {
        self.solids.clear();
        self.index.clear();
    }

    /// Empty the scene, RETURNING every solid by value (the name index is cleared
    /// and the vec is `mem::take`-n out). The history-apply seam MOVES existing
    /// displays out this way to reinsert the reused ones without cloning their
    /// meshes — a scene-free [`crate::pipeline::SceneRunner`] delta is applied by
    /// draining then reinserting in snapshot order.
    pub fn drain(&mut self) -> Vec<SolidDisplay> {
        self.index.clear();
        std::mem::take(&mut self.solids)
    }

    /// Set (or clear) a solid's metadata color override, bumping its revision
    /// so the renderer re-derives the base style. Returns false if unknown.
    pub fn set_color_override(&mut self, name: &str, color: Option<[f32; 3]>) -> bool {
        let Some(slot) = self.index.get(name).copied() else {
            return false;
        };
        let solid = &mut self.solids[slot];
        if solid.color_override != color {
            solid.color_override = color;
            solid.revision = next_revision();
        }
        true
    }

    /// Set a solid's visibility (R11). Returns false if unknown.
    pub fn set_visible(&mut self, name: &str, visible: bool) -> bool {
        match self.solid_mut(name) {
            Some(solid) => {
                solid.visible = visible;
                true
            }
            None => false,
        }
    }

    /// Scene enumeration for the host scene-tree panel (R11): names, kind,
    /// visibility, child face/edge/vertex counts — as JSON.
    pub fn listing_json(&self) -> String {
        let solids: Vec<serde_json::Value> = self
            .solids
            .iter()
            .map(|solid| {
                serde_json::json!({
                    "name": solid.name,
                    "kind": "SOLID",
                    "visible": solid.visible,
                    "faces": solid.faces.len(),
                    "edges": solid.edges.len(),
                    "vertices": solid.vertices.len(),
                })
            })
            .collect();
        serde_json::Value::Array(solids).to_string()
    }

    /// Remove a solid by exact name.
    pub fn remove_solid(&mut self, name: &str) -> bool {
        let Some(slot) = self.index.remove(name) else {
            return false;
        };
        self.solids.remove(slot);
        for value in self.index.values_mut() {
            if *value > slot {
                *value -= 1;
            }
        }
        true
    }

    pub fn solid(&self, name: &str) -> Option<&SolidDisplay> {
        self.index.get(name).map(|&slot| &self.solids[slot])
    }

    pub fn solid_mut(&mut self, name: &str) -> Option<&mut SolidDisplay> {
        let slot = *self.index.get(name)?;
        Some(&mut self.solids[slot])
    }

    /// Insertion-ordered iteration (deterministic — drives draw order).
    pub fn solids(&self) -> &[SolidDisplay] {
        &self.solids
    }

    /// The world-space polyline of the first display edge named `name` across all
    /// solids (widened to `f64`), or `None` when no edge carries that exact name.
    /// The engine-native sketch pickEdges tool (S6b-2) uses this to fetch a picked
    /// scene edge's geometry for projection into the sketch plane. There is no name
    /// index for edges (only solids), so this is a linear scan — fine for the
    /// interactive per-click use.
    pub fn edge_polyline_world(&self, name: &str) -> Option<Vec<[f64; 3]>> {
        for solid in &self.solids {
            for edge in &solid.edges {
                if edge.name == name {
                    return Some(
                        edge.polyline
                            .iter()
                            .map(|p| [p[0] as f64, p[1] as f64, p[2] as f64])
                            .collect(),
                    );
                }
            }
        }
        None
    }

    /// The name of the solid owning the first display edge named `name`, or `None`
    /// (the companion of [`edge_polyline_world`](Self::edge_polyline_world) — the
    /// pickEdges tool stores it as external-ref metadata).
    pub fn edge_solid_name(&self, name: &str) -> Option<&str> {
        for solid in &self.solids {
            if solid.edges.iter().any(|edge| edge.name == name) {
                return Some(&solid.name);
            }
        }
        None
    }

    pub fn is_empty(&self) -> bool {
        self.solids.is_empty()
    }

    /// World bbox over every VISIBLE solid.
    pub fn bbox(&self) -> Aabb {
        let mut bbox = Aabb::empty();
        for solid in &self.solids {
            if solid.visible {
                bbox.union(&solid.bbox);
            }
        }
        bbox
    }
}

/// Build a [`SolidDisplay`] from the kernel's native display payload.
pub fn solid_display_from_payload(
    name: &str,
    payload: brep_kernel::DisplaySolidPayload,
) -> SolidDisplay {
    let mesh_in = payload.mesh;
    let vertex_count = mesh_in.positions.len() / 3;
    let mut positions = Vec::with_capacity(vertex_count);
    let mut normals = Vec::with_capacity(vertex_count);
    let mut bbox = Aabb::empty();
    for i in 0..vertex_count {
        let p = [
            mesh_in.positions[i * 3],
            mesh_in.positions[i * 3 + 1],
            mesh_in.positions[i * 3 + 2],
        ];
        bbox.expand(p);
        positions.push([p[0] as f32, p[1] as f32, p[2] as f32]);
        normals.push([
            mesh_in.normals[i * 3] as f32,
            mesh_in.normals[i * 3 + 1] as f32,
            mesh_in.normals[i * 3 + 2] as f32,
        ]);
    }

    // Face ranges: the watertight mesh emits each face's triangles as one
    // contiguous run of `face_ids`. Group runs; a face with no triangles gets
    // an empty range.
    let mut faces: Vec<FaceDisplay> = payload
        .faces
        .iter()
        .map(|(topo_id, name)| FaceDisplay {
            name: name.clone().unwrap_or_default(),
            topo_id: *topo_id,
            tri_start: 0,
            tri_count: 0,
            kind: FaceKind::Unknown,
        })
        .collect();
    let mut run_start = 0u32;
    let mut run_face: Option<u32> = None;
    for (tri, &face_id) in mesh_in.face_ids.iter().enumerate() {
        if run_face != Some(face_id) {
            run_face = Some(face_id);
            run_start = tri as u32;
        }
        if let Some(face) = faces.get_mut(face_id as usize) {
            if face.tri_count == 0 {
                face.tri_start = run_start;
            }
            face.tri_count += 1;
        }
    }

    // Edges and vertices expand the bbox too. For a real solid they lie ON the
    // meshed boundary, so this changes nothing; for a synthesized OPEN-sketch
    // display (edges + endpoints, no face) they are the ONLY extent there is, and
    // an empty bbox would leave the sketch out of zoom-to-fit and out of every
    // bbox-gated traversal.
    for (_, _, points) in &payload.edges {
        for point in points {
            bbox.expand([point.x, point.y, point.z]);
        }
    }
    for (_, point) in &payload.vertices {
        bbox.expand([point.x, point.y, point.z]);
    }

    let edges = payload
        .edges
        .into_iter()
        .map(|(topo_id, name, points)| EdgeDisplay {
            name: name.unwrap_or_default(),
            topo_id,
            polyline: points
                .iter()
                .map(|p| [p.x as f32, p.y as f32, p.z as f32])
                .collect(),
            aux: false,
            centerline: false,
        })
        .collect();

    let vertices = payload
        .vertices
        .into_iter()
        .map(|(topo_id, p)| VertexDisplay {
            topo_id,
            position: [p.x, p.y, p.z],
        })
        .collect();

    SolidDisplay {
        name: name.to_string(),
        source_handle: 0, // set by the caller that knows the resident handle
        visible: true,
        color_override: None,
        revision: next_revision(),
        mesh: DisplayMesh {
            positions,
            normals,
            indices: mesh_in.indices,
            face_ids: mesh_in.face_ids,
        },
        faces,
        edges,
        vertices,
        visibility: crate::visibility::EntityVisibility::default(),
        bbox,
        is_sketch: false, // set by the sketch-sheet synthesizer for a committed sketch
        is_sheet_metal: false, // stamped by the pipeline from the resident handle's SheetTree
    }
}