Skip to main content

primase_core/
draw_groups.rs

1//! Clip-driven draw groups, distinct from static state conditions. Hidden defaults do not remove geometry.
2use crate::{Component, Entity, Geometry, JsonError, Mesh, World};
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6/// `core.draw_groups`, on a mesh: the group of each primitive, or none (unaffected by group channels).
7/// A clip addresses only groups of the same exact `scope`. Group numbers are 0..64, indexing the
8/// occurrence's [`DrawGroupDefaults::shown`] mask. A lowering resolves a source's group vocabulary.
9#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(deny_unknown_fields)]
11pub struct DrawGroups {
12    pub scope: String,
13    pub groups: Vec<Option<u32>>,
14}
15
16/// `core.draw_group_defaults`, on a drawn occurrence or rig: visibility with no clip, or when a
17/// channel inherits. Bit n states whether group n is shown. Defaults belong to the occurrence,
18/// so two occurrences of one mesh may have different defaults without duplicating its groups.
19#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(deny_unknown_fields)]
21pub struct DrawGroupDefaults {
22    pub scope: String,
23    pub shown: u64,
24}
25
26impl Component for DrawGroups {
27    const NAME: &'static str = "core.draw_groups";
28    const GEOMETRY: Geometry = Geometry::PerPrimitive;
29    fn to_json(&self) -> Result<Option<Value>, JsonError> {
30        crate::json::to_value(self).map(Some)
31    }
32    fn apply_linear(&self, _l: &crate::Linear) -> crate::world::Linearity<Self> {
33        crate::world::Linearity::Invariant
34    }
35    fn remap_primitives(&self, from: &[usize]) -> Option<Result<Self, String>> {
36        Some(crate::remap_list(&self.groups, from).map(|groups| Self {
37            scope: self.scope.clone(),
38            groups,
39        }))
40    }
41    fn verify(&self, e: Entity, w: &World) -> Result<(), String> {
42        if self.scope.trim().is_empty() {
43            return Err("empty draw-group scope".into());
44        }
45        let mesh = w
46            .get::<Mesh>(e)
47            .ok_or("draw groups on an entity without core.mesh")?;
48        if self.groups.len() != mesh.primitives().len() {
49            return Err(format!(
50                "{} groups for {} primitives",
51                self.groups.len(),
52                mesh.primitives().len()
53            ));
54        }
55        if let Some((primitive, group)) = self
56            .groups
57            .iter()
58            .enumerate()
59            .find_map(|(i, group)| group.filter(|group| *group >= 64).map(|group| (i, group)))
60        {
61            return Err(format!(
62                "primitive {primitive}: group {group} is outside the 64-bit default mask"
63            ));
64        }
65        Ok(())
66    }
67}
68
69impl Component for DrawGroupDefaults {
70    const NAME: &'static str = "core.draw_group_defaults";
71    const GEOMETRY: Geometry = Geometry::None;
72    fn to_json(&self) -> Result<Option<Value>, JsonError> {
73        crate::json::to_value(self).map(Some)
74    }
75    fn apply_linear(&self, _l: &crate::Linear) -> crate::world::Linearity<Self> {
76        crate::world::Linearity::Invariant
77    }
78    fn verify(&self, _e: Entity, _w: &World) -> Result<(), String> {
79        if self.scope.trim().is_empty() {
80            return Err("empty draw-group default scope".into());
81        }
82        Ok(())
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89    use crate::{Primitive, Topology};
90    fn mesh() -> (World, Entity) {
91        let mut w = World::new();
92        let e = w.spawn();
93        w.insert(
94            e,
95            Mesh::new(
96                (0..3)
97                    .map(|_| Primitive {
98                        topology: Topology::Triangles,
99                        streams: Default::default(),
100                        indices: None,
101                        material: None,
102                    })
103                    .collect(),
104            ),
105        );
106        (w, e)
107    }
108    fn groups() -> DrawGroups {
109        DrawGroups {
110            scope: "character".into(),
111            groups: vec![Some(0), None, Some(63)],
112        }
113    }
114    #[test]
115    fn groups_validate_count_scope_and_mask_width() {
116        let (w, e) = mesh();
117        let mut g = groups();
118        assert!(g.verify(e, &w).is_ok());
119        g.groups[2] = Some(64);
120        assert!(g.verify(e, &w).is_err());
121        g.groups[2] = Some(63);
122        g.groups.pop();
123        assert!(g.verify(e, &w).is_err());
124        let mut g = groups();
125        g.scope.clear();
126        assert!(g.verify(e, &w).is_err());
127        let mut empty = World::new();
128        let e = empty.spawn();
129        assert!(groups().verify(e, &empty).is_err());
130    }
131    #[test]
132    fn primitive_remap_reorders_repeats_drops_and_refuses_out_of_range() {
133        let g = groups();
134        let mapped = g.remap_primitives(&[2, 0, 2, 1]).unwrap().unwrap();
135        assert_eq!(mapped.groups, vec![Some(63), Some(0), Some(63), None]);
136        assert_eq!(mapped.scope, g.scope);
137        assert!(g.remap_primitives(&[]).unwrap().unwrap().groups.is_empty());
138        assert!(g.remap_primitives(&[3]).unwrap().is_err());
139        assert_eq!(g.groups, vec![Some(0), None, Some(63)]);
140    }
141    #[test]
142    fn defaults_preserve_every_bit_and_belong_to_a_scope() {
143        let (w, e) = mesh();
144        let mut d = DrawGroupDefaults {
145            scope: "character".into(),
146            shown: 1u64 << 63,
147        };
148        assert!(d.verify(e, &w).is_ok());
149        assert!(d.remap_primitives(&[2, 0]).is_none());
150        let json = d.to_json().unwrap().unwrap();
151        assert_eq!(serde_json::from_value::<DrawGroupDefaults>(json).unwrap(), d);
152        d.scope = " ".into();
153        assert!(d.verify(e, &w).is_err());
154    }
155    #[test]
156    fn group_json_round_trip_keeps_none_and_order() {
157        let g = groups();
158        let json = g.to_json().unwrap().unwrap();
159        assert_eq!(
160            json,
161            serde_json::json!({"scope":"character","groups":[0,null,63]})
162        );
163        assert_eq!(serde_json::from_value::<DrawGroups>(json).unwrap(), g);
164    }
165}