Skip to main content

mittens_engine/engine/ecs/system/
clipping_system.rs

1use crate::engine::ecs::ComponentId;
2use crate::engine::ecs::World;
3use crate::engine::ecs::component::RenderableComponent;
4use crate::engine::graphics::VisualWorld;
5use std::collections::HashSet;
6
7pub(crate) const OWNED_LAYOUT_STENCIL_CLIP_LABEL: &str = "__layout_stencil_clip";
8
9#[derive(Debug, Default)]
10pub struct ClippingSystem {
11    active_stencil_clips: HashSet<ComponentId>,
12}
13
14impl ClippingSystem {
15    pub fn register_renderable(
16        &self,
17        world: &World,
18        visuals: &mut VisualWorld,
19        renderable_component: ComponentId,
20    ) {
21        self.sync_renderable_stencil_ref(world, visuals, renderable_component);
22    }
23
24    pub fn register_stencil_clip(
25        &mut self,
26        world: &World,
27        visuals: &mut VisualWorld,
28        component: ComponentId,
29    ) {
30        self.active_stencil_clips.insert(component);
31
32        let stencil_ref = Self::stencil_ref_for_clip(world, component);
33        if let Some(handle) = Self::find_stencil_clip_renderable_handle(world, component) {
34            visuals.register_stencil_clip(handle, stencil_ref);
35        }
36
37        if let Some(scope_root) = Self::stencil_clip_scope_root(world, component) {
38            self.sync_stencil_refs_in_subtree(world, visuals, scope_root);
39        }
40    }
41
42    pub fn unregister_stencil_clip(
43        &mut self,
44        world: &World,
45        visuals: &mut VisualWorld,
46        component: ComponentId,
47    ) {
48        self.active_stencil_clips.remove(&component);
49
50        if let Some(handle) = Self::find_stencil_clip_renderable_handle(world, component) {
51            visuals.unregister_stencil_clip(handle);
52        }
53
54        if let Some(scope_root) = Self::stencil_clip_scope_root(world, component) {
55            self.sync_stencil_refs_in_subtree(world, visuals, scope_root);
56        }
57    }
58
59    pub fn unregister_stencil_clip_for_subtree_node(
60        &mut self,
61        world: &World,
62        visuals: &mut VisualWorld,
63        component: ComponentId,
64    ) {
65        self.active_stencil_clips.remove(&component);
66        if let Some(handle) = Self::find_stencil_clip_renderable_handle(world, component) {
67            visuals.unregister_stencil_clip(handle);
68        }
69    }
70
71    pub fn resync_after_renderable_flush(&mut self, world: &World, visuals: &mut VisualWorld) {
72        let stencil_clips: Vec<ComponentId> = self.active_stencil_clips(world).collect();
73
74        for stencil_clip in stencil_clips {
75            let stencil_ref = Self::stencil_ref_for_clip(world, stencil_clip);
76            if let Some(handle) = Self::find_stencil_clip_renderable_handle(world, stencil_clip) {
77                let _ = visuals.register_stencil_clip(handle, stencil_ref);
78            }
79            if let Some(scope_root) = Self::stencil_clip_scope_root(world, stencil_clip) {
80                self.sync_stencil_refs_in_subtree(world, visuals, scope_root);
81            }
82        }
83    }
84
85    fn sync_renderable_stencil_ref(
86        &self,
87        world: &World,
88        visuals: &mut VisualWorld,
89        renderable_component: ComponentId,
90    ) {
91        let Some(renderable) =
92            world.get_component_by_id_as::<RenderableComponent>(renderable_component)
93        else {
94            return;
95        };
96        let Some(handle) = renderable.get_handle() else {
97            return;
98        };
99
100        let stencil_ref = self
101            .stencil_clip_for_renderable_component(world, renderable_component)
102            .map(|clip_component| Self::stencil_ref_for_clip(world, clip_component))
103            .unwrap_or_else(|| Self::stencil_ref_for_renderable(world, renderable_component));
104        let _ = visuals.update_stencil_ref(handle, stencil_ref);
105    }
106
107    fn stencil_clip_for_renderable_component(
108        &self,
109        world: &World,
110        renderable_component: ComponentId,
111    ) -> Option<ComponentId> {
112        self.active_stencil_clips
113            .iter()
114            .copied()
115            .find(|&clip_component| {
116                world.get_component_record(clip_component).is_some()
117                    && Self::find_stencil_clip_renderable_component(world, clip_component)
118                        == Some(renderable_component)
119            })
120    }
121
122    fn sync_stencil_refs_in_subtree(
123        &self,
124        world: &World,
125        visuals: &mut VisualWorld,
126        root: ComponentId,
127    ) {
128        let mut stack = vec![root];
129        while let Some(node) = stack.pop() {
130            if world
131                .get_component_by_id_as::<RenderableComponent>(node)
132                .is_some()
133            {
134                self.sync_renderable_stencil_ref(world, visuals, node);
135            }
136            for &child in world.children_of(node) {
137                stack.push(child);
138            }
139        }
140    }
141
142    fn active_stencil_clips<'a>(
143        &'a mut self,
144        world: &World,
145    ) -> impl Iterator<Item = ComponentId> + 'a {
146        self.active_stencil_clips
147            .retain(|&component| world.get_component_record(component).is_some());
148        self.active_stencil_clips.iter().copied()
149    }
150
151    fn stencil_ref_for_renderable(world: &World, renderable_component: ComponentId) -> u8 {
152        use crate::engine::ecs::component::StencilClipComponent;
153
154        let mut depth: u8 = 0;
155        let mut cursor = Some(renderable_component);
156        while let Some(node) = cursor {
157            if world
158                .get_component_by_id_as::<StencilClipComponent>(node)
159                .is_some()
160                || Self::is_layout_clip_scope_root(world, node)
161            {
162                depth = depth.saturating_add(1);
163            }
164            cursor = world.parent_of(node);
165        }
166        depth
167    }
168
169    fn stencil_ref_for_clip(world: &World, component: ComponentId) -> u8 {
170        use crate::engine::ecs::component::StencilClipComponent;
171
172        let mut depth: u8 = 0;
173        let mut cursor = if Self::is_layout_owned_stencil_clip(world, component) {
174            world
175                .parent_of(component)
176                .and_then(|scope_root| world.parent_of(scope_root))
177        } else {
178            world.parent_of(component)
179        };
180
181        while let Some(node) = cursor {
182            if world
183                .get_component_by_id_as::<StencilClipComponent>(node)
184                .is_some()
185                || Self::is_layout_clip_scope_root(world, node)
186            {
187                depth = depth.saturating_add(1);
188            }
189            cursor = world.parent_of(node);
190        }
191
192        depth
193    }
194
195    fn is_layout_owned_stencil_clip(world: &World, component: ComponentId) -> bool {
196        world.component_label(component) == Some(OWNED_LAYOUT_STENCIL_CLIP_LABEL)
197            && world
198                .get_component_by_id_as::<crate::engine::ecs::component::StencilClipComponent>(
199                    component,
200                )
201                .is_some()
202    }
203
204    fn immediate_owned_layout_stencil_clip(
205        world: &World,
206        scope_root: ComponentId,
207    ) -> Option<ComponentId> {
208        world
209            .children_of(scope_root)
210            .iter()
211            .copied()
212            .find(|&child| Self::is_layout_owned_stencil_clip(world, child))
213    }
214
215    fn layout_bg_node(world: &World, scope_root: ComponentId) -> Option<ComponentId> {
216        world
217            .children_of(scope_root)
218            .iter()
219            .copied()
220            .find(|&child| world.component_label(child) == Some("__bg"))
221    }
222
223    fn subtree_first_renderable(world: &World, root: ComponentId) -> Option<ComponentId> {
224        let mut stack = vec![root];
225        while let Some(node) = stack.pop() {
226            if world
227                .get_component_by_id_as::<RenderableComponent>(node)
228                .is_some()
229            {
230                return Some(node);
231            }
232            for &child in world.children_of(node).iter().rev() {
233                stack.push(child);
234            }
235        }
236        None
237    }
238
239    fn is_layout_clip_scope_root(world: &World, node: ComponentId) -> bool {
240        Self::immediate_owned_layout_stencil_clip(world, node).is_some()
241    }
242
243    fn stencil_clip_scope_root(world: &World, component: ComponentId) -> Option<ComponentId> {
244        if Self::is_layout_owned_stencil_clip(world, component) {
245            return world.parent_of(component);
246        }
247        let parent = world.parent_of(component)?;
248        if world.component_label(parent) == Some("__bg") {
249            return world.parent_of(parent);
250        }
251        Some(parent)
252    }
253
254    fn find_stencil_clip_renderable_component(
255        world: &World,
256        component: ComponentId,
257    ) -> Option<ComponentId> {
258        let mut cursor = world.parent_of(component);
259        while let Some(cid) = cursor {
260            if world
261                .get_component_by_id_as::<RenderableComponent>(cid)
262                .is_some()
263            {
264                return Some(cid);
265            }
266            cursor = world.parent_of(cid);
267        }
268
269        if Self::is_layout_owned_stencil_clip(world, component) {
270            let scope_root = world.parent_of(component)?;
271            let bg_id = Self::layout_bg_node(world, scope_root)?;
272            return Self::subtree_first_renderable(world, bg_id);
273        }
274
275        None
276    }
277
278    fn find_stencil_clip_renderable_handle(
279        world: &World,
280        component: ComponentId,
281    ) -> Option<crate::engine::graphics::primitives::InstanceHandle> {
282        let renderable_component = Self::find_stencil_clip_renderable_component(world, component)?;
283        world
284            .get_component_by_id_as::<RenderableComponent>(renderable_component)
285            .and_then(|r| r.get_handle())
286    }
287}