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
use crate::engine::ecs::component::Component;
/// Controls which pointer event types a raycastable captures vs. passes through.
///
/// When the gesture system walks the depth-sorted hit list, it stops at the first hit that
/// captures the event type being resolved. Objects behind a capturer never see that event type.
///
/// ```
/// depth-sorted hits: [drag_plane (DragOnly), row (All)]
///
/// for drag → drag_plane captures, stops. row never sees DragStart/DragMove.
/// for click → drag_plane passes (DragOnly). row captures, stops.
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PointerEvents {
/// Captures drag and click. Default for all raycastable geometry.
#[default]
All,
/// Captures drag events only; click propagates to the next hit.
DragOnly,
/// Captures click events only; drag propagates to the next hit.
ClickOnly,
/// Passes all pointer events through. Geometry is hittable but invisible to the gesture
/// system (e.g. a structural collision volume that should never receive input).
PassThrough,
}
impl PointerEvents {
pub fn captures_drag(self) -> bool {
matches!(self, Self::All | Self::DragOnly)
}
pub fn captures_click(self) -> bool {
matches!(self, Self::All | Self::ClickOnly)
}
}
/// Controls whether renderables should be eligible for ray casting (BVH insertion).
///
/// This is intentionally separate from `RenderableComponent` so raycasting policy can be
/// expressed via topology/components rather than renderable data.
#[derive(Debug, Default, Clone, Copy)]
pub struct RaycastableComponent {
/// If true, ray casting is enabled.
pub enable: bool,
/// Which pointer event types this object captures vs. passes through to hits behind it.
pub pointer_events: PointerEvents,
/// Higher values win interaction ordering before distance-based tie-breaking.
pub interaction_priority: u8,
}
impl RaycastableComponent {
pub fn new(enable: bool) -> Self {
Self {
enable,
pointer_events: PointerEvents::All,
interaction_priority: 0,
}
}
pub fn enabled() -> Self {
Self::new(true)
}
pub fn disabled() -> Self {
Self::new(false)
}
/// Captures drag events only; click falls through to hits behind this object.
pub fn drag_only() -> Self {
Self {
enable: true,
pointer_events: PointerEvents::DragOnly,
interaction_priority: 0,
}
}
/// Captures click events only; drag falls through to hits behind this object.
pub fn click_only() -> Self {
Self {
enable: true,
pointer_events: PointerEvents::ClickOnly,
interaction_priority: 0,
}
}
pub fn with_interaction_priority(mut self, interaction_priority: u8) -> Self {
self.interaction_priority = interaction_priority;
self
}
}
impl Component for RaycastableComponent {
fn init(
&mut self,
emit: &mut dyn crate::engine::ecs::SignalEmitter,
component: crate::engine::ecs::ComponentId,
) {
emit.push_intent(
component,
crate::engine::ecs::IntentSignal::now(
crate::engine::ecs::IntentValue::RegisterRaycastable {
component_ids: vec![component],
},
),
);
}
fn cleanup(
&mut self,
emit: &mut dyn crate::engine::ecs::SignalEmitter,
component: crate::engine::ecs::ComponentId,
) {
emit.push_intent(
component,
crate::engine::ecs::IntentSignal::now(
crate::engine::ecs::IntentValue::RemoveRaycastable {
component_ids: vec![component],
},
),
);
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn name(&self) -> &'static str {
"raycastable"
}
fn to_mms_ast(
&self,
_world: &crate::engine::ecs::World,
) -> crate::scripting::ast::ComponentExpression {
use crate::engine::ecs::component::ce_helpers::*;
let ctor = match (self.enable, self.pointer_events) {
(false, _) => "disabled",
(true, PointerEvents::DragOnly) => "drag_only",
(true, PointerEvents::ClickOnly) => "click_only",
(true, _) => "enabled",
};
let mut ce = ce_call("Raycastable", ctor, vec![]);
if self.enable && matches!(self.pointer_events, PointerEvents::PassThrough) {
ce = ce.with_call("pointer_events", vec![s("pass_through")]);
}
if self.enable && self.interaction_priority > 0 {
ce = ce.with_call(
"interaction_priority",
vec![num(self.interaction_priority as f64)],
);
}
ce
}
}