Skip to main content

brep_render/
visibility.rs

1//! Per-entity + group visibility for a solid's faces / edges / vertices.
2//!
3//! The whole-solid + whole-scene toggles already live on
4//! [`SolidDisplay::visible`](crate::scene::SolidDisplay) /
5//! [`RenderScene::set_visible`]. THIS module adds the finer grain the Scene tree
6//! needs: an INDIVIDUAL face, edge or vertex can be hidden, and a WHOLE GROUP
7//! (all faces / all edges / all vertices of a solid) toggled — with a tristate
8//! ([`GroupState`]) group readout for the UI checkbox.
9//!
10//! # Where the state lives
11//!
12//! Each [`SolidDisplay`] carries an [`EntityVisibility`] — a set of HIDDEN
13//! indices per kind (default-visible: an empty set = everything shown). The key
14//! is the entity's INDEX in the solid's `faces` / `edges` / `vertices` list, the
15//! exact index the render pass and the Scene tree already enumerate by (so a
16//! hidden face maps straight onto a mesh triangle-range skip — see
17//! [`crate::render`]). Reused solids keep their sets across history reruns (the
18//! pipeline clones the `SolidDisplay`); a freshly re-tessellated solid resets to
19//! all-visible, mirroring how the per-solid `visible` flag behaves.
20//!
21//! # How faces actually hide (the render mechanism)
22//!
23//! A solid's mesh emits each face's triangles as one contiguous run (the kernel
24//! groups by `face_ids`), and the renderer already keeps a per-face
25//! `first_index/index_count` range. The face pass, instead of one whole-mesh
26//! draw, walks the faces and coalesces CONTIGUOUS VISIBLE ranges into draw
27//! calls, breaking (flushing) the run at every hidden face — so a hidden face's
28//! triangles are simply never submitted. Edges/vertices skip their line
29//! segments / point sprites the same way. No mesh is rebuilt; visibility is
30//! purely a draw-time mask, so toggling is free of any re-tessellation.
31
32use crate::engine_state::EngineState;
33use crate::scene::{RenderScene, SolidDisplay};
34use std::collections::HashSet;
35
36/// Which sub-entity list of a solid a visibility toggle addresses.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum EntityKind {
39    Face,
40    Edge,
41    Vertex,
42}
43
44impl EntityKind {
45    /// Parse the Scene-tree / verifier kind token (`"face"` / `"edge"` /
46    /// `"vertex"`, case-insensitive). `None` for anything else.
47    pub fn parse(kind: &str) -> Option<Self> {
48        match kind.to_ascii_lowercase().as_str() {
49            "face" => Some(Self::Face),
50            "edge" => Some(Self::Edge),
51            "vertex" => Some(Self::Vertex),
52            _ => None,
53        }
54    }
55
56    /// The lowercase token (round-trips with [`parse`](Self::parse)).
57    pub fn as_str(self) -> &'static str {
58        match self {
59            Self::Face => "face",
60            Self::Edge => "edge",
61            Self::Vertex => "vertex",
62        }
63    }
64}
65
66/// A group checkbox's tristate: every entity of the kind shown, none shown, or a
67/// mix. An empty group reads [`All`](GroupState::All) — nothing to hide.
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum GroupState {
70    All,
71    Partial,
72    None,
73}
74
75impl GroupState {
76    /// The lowercase token the verifier asserts against (`"all"` / `"partial"` /
77    /// `"none"`).
78    pub fn as_str(self) -> &'static str {
79        match self {
80            Self::All => "all",
81            Self::Partial => "partial",
82            Self::None => "none",
83        }
84    }
85}
86
87/// The hidden-entity sets of ONE solid (empty = all visible). Indices are into
88/// the owning [`SolidDisplay`]'s `faces` / `edges` / `vertices`.
89#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
90pub struct EntityVisibility {
91    hidden_faces: HashSet<usize>,
92    hidden_edges: HashSet<usize>,
93    hidden_vertices: HashSet<usize>,
94}
95
96impl EntityVisibility {
97    fn set_of(&self, kind: EntityKind) -> &HashSet<usize> {
98        match kind {
99            EntityKind::Face => &self.hidden_faces,
100            EntityKind::Edge => &self.hidden_edges,
101            EntityKind::Vertex => &self.hidden_vertices,
102        }
103    }
104
105    fn set_mut(&mut self, kind: EntityKind) -> &mut HashSet<usize> {
106        match kind {
107            EntityKind::Face => &mut self.hidden_faces,
108            EntityKind::Edge => &mut self.hidden_edges,
109            EntityKind::Vertex => &mut self.hidden_vertices,
110        }
111    }
112
113    /// Whether entity `index` of `kind` is currently shown.
114    pub fn is_visible(&self, kind: EntityKind, index: usize) -> bool {
115        !self.set_of(kind).contains(&index)
116    }
117
118    /// Whether ANY entity of `kind` is hidden — the render pass's fast-path
119    /// guard (nothing hidden ⇒ keep the single whole-buffer draw).
120    pub fn any_hidden(&self, kind: EntityKind) -> bool {
121        !self.set_of(kind).is_empty()
122    }
123
124    /// Show/hide a single entity of `kind`.
125    pub fn set_visible(&mut self, kind: EntityKind, index: usize, visible: bool) {
126        if visible {
127            self.set_mut(kind).remove(&index);
128        } else {
129            self.set_mut(kind).insert(index);
130        }
131    }
132
133    /// Show/hide EVERY entity of `kind`. `count` is the number of entities of
134    /// that kind, so "hide all" enumerates them (and "show all" clears).
135    pub fn set_group_visible(&mut self, kind: EntityKind, count: usize, visible: bool) {
136        let set = self.set_mut(kind);
137        set.clear();
138        if !visible {
139            set.extend(0..count);
140        }
141    }
142
143    /// The group tristate for `count` entities of `kind`. O(1): the hidden set
144    /// satisfies `hidden ⊆ 0..count` (see [`all_hidden`](Self::all_hidden)), so its
145    /// `len()` IS the hidden count — no per-entity scan. Called per solid per egui
146    /// frame by the Scene panel, so the O(1) matters on point/edge-heavy models.
147    pub fn group_state(&self, kind: EntityKind, count: usize) -> GroupState {
148        if count == 0 {
149            return GroupState::All;
150        }
151        let hidden = self.set_of(kind).len();
152        if hidden == 0 {
153            GroupState::All
154        } else if hidden >= count {
155            GroupState::None
156        } else {
157            GroupState::Partial
158        }
159    }
160
161    // Render-pass conveniences (kept terse — called per solid per frame).
162    pub fn is_face_visible(&self, index: usize) -> bool {
163        self.is_visible(EntityKind::Face, index)
164    }
165    pub fn is_edge_visible(&self, index: usize) -> bool {
166        self.is_visible(EntityKind::Edge, index)
167    }
168    pub fn is_vertex_visible(&self, index: usize) -> bool {
169        self.is_visible(EntityKind::Vertex, index)
170    }
171    pub fn any_face_hidden(&self) -> bool {
172        self.any_hidden(EntityKind::Face)
173    }
174    pub fn any_edge_hidden(&self) -> bool {
175        self.any_hidden(EntityKind::Edge)
176    }
177    pub fn any_vertex_hidden(&self) -> bool {
178        self.any_hidden(EntityKind::Vertex)
179    }
180
181    /// Whether EVERY entity of `kind` is hidden — O(1). A solid's hidden set is
182    /// always paired with the entity counts it was built against: geometry changes
183    /// replace the whole `SolidDisplay` (fresh, empty visibility), and a reused
184    /// display keeps its counts AND its hidden set together — so `hidden ⊆ 0..count`
185    /// holds universally and `len() == count` ⇔ the whole group is off (individual
186    /// hides leave `len() < count`). `>=` rather than `==` is defensive only. The
187    /// render pass uses this to skip a fully-hidden group's per-vertex draw loop.
188    pub fn all_hidden(&self, kind: EntityKind, count: usize) -> bool {
189        count > 0 && self.set_of(kind).len() >= count
190    }
191    pub fn all_vertices_hidden(&self, count: usize) -> bool {
192        self.all_hidden(EntityKind::Vertex, count)
193    }
194}
195
196/// The count of entities of `kind` on a solid (group toggles / tristate).
197fn entity_count(solid: &SolidDisplay, kind: EntityKind) -> usize {
198    match kind {
199        EntityKind::Face => solid.faces.len(),
200        EntityKind::Edge => solid.edges.len(),
201        EntityKind::Vertex => solid.vertices.len(),
202    }
203}
204
205// --- RenderScene: per-entity / group visibility (kept here, not in scene.rs, so
206//     the visibility surface stays in one module) ------------------------------
207impl RenderScene {
208    /// Show/hide ONE face/edge/vertex of a solid. False if the solid is unknown.
209    pub fn set_entity_visible(
210        &mut self,
211        solid: &str,
212        kind: EntityKind,
213        index: usize,
214        visible: bool,
215    ) -> bool {
216        match self.solid_mut(solid) {
217            Some(s) => {
218                s.visibility.set_visible(kind, index, visible);
219                true
220            }
221            None => false,
222        }
223    }
224
225    /// Show/hide a whole group (all faces / all edges / all vertices) of a solid.
226    /// False if the solid is unknown.
227    pub fn set_group_visible(&mut self, solid: &str, kind: EntityKind, visible: bool) -> bool {
228        let Some(count) = self.solid(solid).map(|s| entity_count(s, kind)) else {
229            return false;
230        };
231        // The solid exists (count resolved above), so `solid_mut` is Some.
232        if let Some(s) = self.solid_mut(solid) {
233            s.visibility.set_group_visible(kind, count, visible);
234        }
235        true
236    }
237
238    /// Whether entity `index` of `kind` on `solid` is shown (`None` if unknown).
239    pub fn entity_visible(&self, solid: &str, kind: EntityKind, index: usize) -> Option<bool> {
240        self.solid(solid).map(|s| s.visibility.is_visible(kind, index))
241    }
242
243    /// The group tristate for `solid`'s `kind` (`None` if the solid is unknown).
244    pub fn group_visibility(&self, solid: &str, kind: EntityKind) -> Option<GroupState> {
245        self.solid(solid)
246            .map(|s| s.visibility.group_state(kind, entity_count(s, kind)))
247    }
248}
249
250// --- EngineState: the UI-facing visibility API (marks dirty; the Scene panel
251//     drives these and reads the queries back) ---------------------------------
252impl EngineState {
253    /// Show/hide ONE face/edge/vertex of a solid, by its index in the solid's
254    /// face/edge/vertex list — the same index the Scene tree enumerates. Marks
255    /// dirty. Returns false if the solid is unknown.
256    pub fn set_entity_visible(
257        &mut self,
258        solid: &str,
259        kind: EntityKind,
260        index: usize,
261        visible: bool,
262    ) -> bool {
263        let ok = self.scene.set_entity_visible(solid, kind, index, visible);
264        if ok {
265            self.dirty = true;
266        }
267        ok
268    }
269
270    /// Show/hide a WHOLE group (all faces / all edges / all vertices of a solid).
271    /// Marks dirty. Returns false if the solid is unknown.
272    pub fn set_group_visible(&mut self, solid: &str, kind: EntityKind, visible: bool) -> bool {
273        let ok = self.scene.set_group_visible(solid, kind, visible);
274        if ok {
275            self.dirty = true;
276        }
277        ok
278    }
279
280    /// Whether entity `index` of `kind` on `solid` is shown (`None` if unknown).
281    pub fn entity_visible(&self, solid: &str, kind: EntityKind, index: usize) -> Option<bool> {
282        self.scene.entity_visible(solid, kind, index)
283    }
284
285    /// The group tristate for `solid`'s `kind` (`None` if the solid is unknown).
286    pub fn group_visibility(&self, solid: &str, kind: EntityKind) -> Option<GroupState> {
287        self.scene.group_visibility(solid, kind)
288    }
289
290    /// Per-entity + group visibility as JSON — the readout the headed verifier
291    /// asserts against (a companion to
292    /// [`scene_entities_json`](EngineState::scene_entities_json), which lists the
293    /// entities but not their per-entity visibility):
294    /// `[{name, visible, faces:{group, states:[bool;n]}, edges:{…}, vertices:{…}}]`
295    /// where `states[i]` is entity `i`'s visibility and `group` is
296    /// `"all"|"partial"|"none"`.
297    pub fn scene_visibility_json(&self) -> String {
298        let kinds = [
299            ("faces", EntityKind::Face),
300            ("edges", EntityKind::Edge),
301            ("vertices", EntityKind::Vertex),
302        ];
303        let solids: Vec<serde_json::Value> = self
304            .scene
305            .solids()
306            .iter()
307            .map(|solid| {
308                let mut obj = serde_json::Map::new();
309                obj.insert("name".into(), serde_json::json!(solid.name));
310                obj.insert("visible".into(), serde_json::json!(solid.visible));
311                for (key, kind) in kinds {
312                    let count = entity_count(solid, kind);
313                    let states: Vec<bool> =
314                        (0..count).map(|i| solid.visibility.is_visible(kind, i)).collect();
315                    obj.insert(
316                        key.into(),
317                        serde_json::json!({
318                            "group": solid.visibility.group_state(kind, count).as_str(),
319                            "states": states,
320                        }),
321                    );
322                }
323                serde_json::Value::Object(obj)
324            })
325            .collect();
326        serde_json::Value::Array(solids).to_string()
327    }
328}
329
330#[cfg(test)]
331mod tests {
332    use super::*;
333
334    fn cube_history(name: &str) -> String {
335        serde_json::json!({
336            "expressions": "",
337            "configurator": {},
338            "features": [{
339                "type": "P.CU",
340                "inputParams": {
341                    "id": name,
342                    "sizeX": 10.0, "sizeY": 10.0, "sizeZ": 10.0,
343                    "transform": {
344                        "position": [0.0, 0.0, 0.0],
345                        "rotationEuler": [0.0, 0.0, 0.0],
346                        "scale": [1.0, 1.0, 1.0]
347                    },
348                    "boolean": { "targets": [], "operation": "NONE" }
349                },
350                "persistentData": {}
351            }]
352        })
353        .to_string()
354    }
355
356    #[test]
357    fn entity_visibility_hide_show_and_group_tristate() {
358        let mut vis = EntityVisibility::default();
359        // Default: all visible, group reads All.
360        assert!(vis.is_visible(EntityKind::Face, 0));
361        assert!(!vis.any_hidden(EntityKind::Face));
362        assert_eq!(vis.group_state(EntityKind::Face, 6), GroupState::All);
363
364        // Hide one → Partial, only that index hidden.
365        vis.set_visible(EntityKind::Face, 2, false);
366        assert!(!vis.is_visible(EntityKind::Face, 2));
367        assert!(vis.is_visible(EntityKind::Face, 3));
368        assert!(vis.any_hidden(EntityKind::Face));
369        assert_eq!(vis.group_state(EntityKind::Face, 6), GroupState::Partial);
370
371        // Re-show it → back to All.
372        vis.set_visible(EntityKind::Face, 2, true);
373        assert_eq!(vis.group_state(EntityKind::Face, 6), GroupState::All);
374
375        // Hide the whole group → None, every index hidden.
376        vis.set_group_visible(EntityKind::Face, 6, false);
377        assert_eq!(vis.group_state(EntityKind::Face, 6), GroupState::None);
378        assert!(!vis.is_visible(EntityKind::Face, 5));
379        // Show one back → Partial.
380        vis.set_visible(EntityKind::Face, 5, true);
381        assert_eq!(vis.group_state(EntityKind::Face, 6), GroupState::Partial);
382        // Kinds are independent: edges untouched.
383        assert_eq!(vis.group_state(EntityKind::Edge, 12), GroupState::All);
384    }
385
386    #[test]
387    fn engine_per_entity_and_group_visibility_through_scene() {
388        let mut engine = EngineState::new();
389        engine.set_history_json(&cube_history("Box")).unwrap();
390
391        // Individual: hide face 0.
392        assert!(engine.set_entity_visible("Box", EntityKind::Face, 0, false));
393        assert_eq!(engine.entity_visible("Box", EntityKind::Face, 0), Some(false));
394        assert_eq!(engine.entity_visible("Box", EntityKind::Face, 1), Some(true));
395        assert_eq!(
396            engine.group_visibility("Box", EntityKind::Face),
397            Some(GroupState::Partial)
398        );
399
400        // Group: hide all faces → None; edges/vertices unaffected.
401        assert!(engine.set_group_visible("Box", EntityKind::Face, false));
402        assert_eq!(
403            engine.group_visibility("Box", EntityKind::Face),
404            Some(GroupState::None)
405        );
406        assert_eq!(
407            engine.group_visibility("Box", EntityKind::Edge),
408            Some(GroupState::All)
409        );
410
411        // Group: show all faces back → All.
412        assert!(engine.set_group_visible("Box", EntityKind::Face, true));
413        assert_eq!(
414            engine.group_visibility("Box", EntityKind::Face),
415            Some(GroupState::All)
416        );
417
418        // Unknown solid → false / None.
419        assert!(!engine.set_entity_visible("Nope", EntityKind::Edge, 0, false));
420        assert_eq!(engine.entity_visible("Nope", EntityKind::Edge, 0), None);
421        assert_eq!(engine.group_visibility("Nope", EntityKind::Edge), None);
422    }
423
424    #[test]
425    fn scene_visibility_json_reports_states_and_group() {
426        let mut engine = EngineState::new();
427        engine.set_history_json(&cube_history("Box")).unwrap();
428        engine.set_entity_visible("Box", EntityKind::Edge, 3, false);
429
430        let json: serde_json::Value =
431            serde_json::from_str(&engine.scene_visibility_json()).unwrap();
432        let solid = &json.as_array().unwrap()[0];
433        assert_eq!(solid["name"], "Box");
434        assert_eq!(solid["visible"], true);
435        // A cube: 6 faces / 12 edges / 8 vertices, all faces still shown.
436        assert_eq!(solid["faces"]["group"], "all");
437        assert_eq!(solid["faces"]["states"].as_array().unwrap().len(), 6);
438        // Edge 3 is hidden → group partial, states[3] == false.
439        assert_eq!(solid["edges"]["group"], "partial");
440        assert_eq!(solid["edges"]["states"][3], false);
441        assert_eq!(solid["edges"]["states"][2], true);
442    }
443
444    #[test]
445    fn entity_kind_token_roundtrips() {
446        for k in [EntityKind::Face, EntityKind::Edge, EntityKind::Vertex] {
447            assert_eq!(EntityKind::parse(k.as_str()), Some(k));
448        }
449        assert_eq!(EntityKind::parse("FACE"), Some(EntityKind::Face));
450        assert_eq!(EntityKind::parse("solid"), None);
451    }
452}