Skip to main content

mittens_engine/engine/ecs/component/
raycastable.rs

1use crate::engine::ecs::component::Component;
2
3/// Controls which pointer event types a raycastable captures vs. passes through.
4///
5/// When the gesture system walks the depth-sorted hit list, it stops at the first hit that
6/// captures the event type being resolved. Objects behind a capturer never see that event type.
7///
8/// ```
9/// depth-sorted hits: [drag_plane (DragOnly), row (All)]
10///
11/// for drag  → drag_plane captures, stops. row never sees DragStart/DragMove.
12/// for click → drag_plane passes (DragOnly). row captures, stops.
13/// ```
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
15pub enum PointerEvents {
16    /// Captures drag and click. Default for all raycastable geometry.
17    #[default]
18    All,
19    /// Captures drag events only; click propagates to the next hit.
20    DragOnly,
21    /// Captures click events only; drag propagates to the next hit.
22    ClickOnly,
23    /// Passes all pointer events through. Geometry is hittable but invisible to the gesture
24    /// system (e.g. a structural collision volume that should never receive input).
25    PassThrough,
26}
27
28impl PointerEvents {
29    pub fn captures_drag(self) -> bool {
30        matches!(self, Self::All | Self::DragOnly)
31    }
32    pub fn captures_click(self) -> bool {
33        matches!(self, Self::All | Self::ClickOnly)
34    }
35}
36
37/// Controls whether renderables should be eligible for ray casting (BVH insertion).
38///
39/// This is intentionally separate from `RenderableComponent` so raycasting policy can be
40/// expressed via topology/components rather than renderable data.
41#[derive(Debug, Default, Clone, Copy)]
42pub struct RaycastableComponent {
43    /// If true, ray casting is enabled.
44    pub enable: bool,
45    /// Which pointer event types this object captures vs. passes through to hits behind it.
46    pub pointer_events: PointerEvents,
47    /// Higher values win interaction ordering before distance-based tie-breaking.
48    pub interaction_priority: u8,
49}
50
51impl RaycastableComponent {
52    pub fn new(enable: bool) -> Self {
53        Self {
54            enable,
55            pointer_events: PointerEvents::All,
56            interaction_priority: 0,
57        }
58    }
59
60    pub fn enabled() -> Self {
61        Self::new(true)
62    }
63
64    pub fn disabled() -> Self {
65        Self::new(false)
66    }
67
68    /// Captures drag events only; click falls through to hits behind this object.
69    pub fn drag_only() -> Self {
70        Self {
71            enable: true,
72            pointer_events: PointerEvents::DragOnly,
73            interaction_priority: 0,
74        }
75    }
76
77    /// Captures click events only; drag falls through to hits behind this object.
78    pub fn click_only() -> Self {
79        Self {
80            enable: true,
81            pointer_events: PointerEvents::ClickOnly,
82            interaction_priority: 0,
83        }
84    }
85
86    pub fn with_interaction_priority(mut self, interaction_priority: u8) -> Self {
87        self.interaction_priority = interaction_priority;
88        self
89    }
90}
91
92impl Component for RaycastableComponent {
93    fn init(
94        &mut self,
95        emit: &mut dyn crate::engine::ecs::SignalEmitter,
96        component: crate::engine::ecs::ComponentId,
97    ) {
98        emit.push_intent(
99            component,
100            crate::engine::ecs::IntentSignal::now(
101                crate::engine::ecs::IntentValue::RegisterRaycastable {
102                    component_ids: vec![component],
103                },
104            ),
105        );
106    }
107
108    fn cleanup(
109        &mut self,
110        emit: &mut dyn crate::engine::ecs::SignalEmitter,
111        component: crate::engine::ecs::ComponentId,
112    ) {
113        emit.push_intent(
114            component,
115            crate::engine::ecs::IntentSignal::now(
116                crate::engine::ecs::IntentValue::RemoveRaycastable {
117                    component_ids: vec![component],
118                },
119            ),
120        );
121    }
122
123    fn as_any(&self) -> &dyn std::any::Any {
124        self
125    }
126
127    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
128        self
129    }
130
131    fn name(&self) -> &'static str {
132        "raycastable"
133    }
134
135    fn to_mms_ast(
136        &self,
137        _world: &crate::engine::ecs::World,
138    ) -> crate::scripting::ast::ComponentExpression {
139        use crate::engine::ecs::component::ce_helpers::*;
140        let ctor = match (self.enable, self.pointer_events) {
141            (false, _) => "disabled",
142            (true, PointerEvents::DragOnly) => "drag_only",
143            (true, PointerEvents::ClickOnly) => "click_only",
144            (true, _) => "enabled",
145        };
146        let mut ce = ce_call("Raycastable", ctor, vec![]);
147        if self.enable && matches!(self.pointer_events, PointerEvents::PassThrough) {
148            ce = ce.with_call("pointer_events", vec![s("pass_through")]);
149        }
150        if self.enable && self.interaction_priority > 0 {
151            ce = ce.with_call(
152                "interaction_priority",
153                vec![num(self.interaction_priority as f64)],
154            );
155        }
156        ce
157    }
158}