pub struct RaycastableComponent {
pub enable: bool,
pub pointer_events: PointerEvents,
pub interaction_priority: u8,
}Expand description
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.
Fields§
§enable: boolIf true, ray casting is enabled.
pointer_events: PointerEventsWhich pointer event types this object captures vs. passes through to hits behind it.
interaction_priority: u8Higher values win interaction ordering before distance-based tie-breaking.
Implementations§
Source§impl RaycastableComponent
impl RaycastableComponent
pub fn new(enable: bool) -> Self
Sourcepub fn enabled() -> Self
pub fn enabled() -> Self
Examples found in repository?
examples/button-press.rs (line 192)
176fn spawn_raycastable_cube(
177 universe: &mut engine::Universe,
178 parent: engine::ecs::ComponentId,
179 pos: [f32; 3],
180 scale: [f32; 3],
181) -> engine::ecs::ComponentId {
182 use engine::ecs::component::{RaycastableComponent, RenderableComponent, TransformComponent};
183
184 let t = universe.world.add_component(
185 TransformComponent::new()
186 .with_position(pos[0], pos[1], pos[2])
187 .with_scale(scale[0], scale[1], scale[2]),
188 );
189 let r = universe.world.add_component(RenderableComponent::cube());
190 let rc = universe
191 .world
192 .add_component(RaycastableComponent::enabled());
193
194 let _ = universe.attach(parent, t);
195 let _ = universe.attach(t, r);
196 let _ = universe.attach(r, rc);
197
198 r
199}More examples
examples/raycast-topology-animation.rs (line 219)
201 fn ring_cube(
202 universe: &mut engine::Universe,
203 ring_root: engine::ecs::ComponentId,
204 x: f32,
205 y: f32,
206 z: f32,
207 rgba: [f32; 4],
208 ) {
209 let t = universe.world.add_component(
210 engine::ecs::component::TransformComponent::new()
211 .with_position(x, y, z)
212 .with_scale(0.35, 0.35, 0.35),
213 );
214 let r = universe
215 .world
216 .add_component(engine::ecs::component::RenderableComponent::cube());
217 let rc = universe
218 .world
219 .add_component(engine::ecs::component::RaycastableComponent::enabled());
220 let c = universe
221 .world
222 .add_component(engine::ecs::component::ColorComponent::rgba(
223 rgba[0], rgba[1], rgba[2], rgba[3],
224 ));
225 let _ = universe.attach(ring_root, t);
226 let _ = universe.attach(t, r);
227 let _ = universe.attach(r, rc);
228 let _ = universe.attach(r, c);
229 }examples/gestures-and-gizmos.rs (line 148)
123 fn spawn_shape_with_gizmo(
124 universe: &mut engine::Universe,
125 mesh: engine::graphics::primitives::CpuMeshHandle,
126 pos: [f32; 3],
127 scale: [f32; 3],
128 rot_euler: [f32; 3],
129 color: [f32; 4],
130 ) {
131 let t = universe.world.add_component(
132 TransformComponent::new()
133 .with_position(pos[0], pos[1], pos[2])
134 .with_scale(scale[0], scale[1], scale[2])
135 .with_rotation_euler(rot_euler[0], rot_euler[1], rot_euler[2]),
136 );
137 let r = universe
138 .world
139 .add_component(RenderableComponent::new(Renderable::new(
140 mesh,
141 MaterialHandle::TOON_MESH,
142 )));
143 let c = universe
144 .world
145 .add_component(ColorComponent::rgba(color[0], color[1], color[2], color[3]));
146 let rc = universe
147 .world
148 .add_component(RaycastableComponent::enabled());
149 let g = universe.world.add_component(TransformGizmoComponent::new());
150
151 let _ = universe.attach(t, r);
152 let _ = universe.attach(r, c);
153 let _ = universe.attach(r, rc);
154 let _ = universe.attach(t, g);
155
156 universe.add(t);
157 }
158
159 fn spawn_shape_raycastable_no_gizmo(
160 universe: &mut engine::Universe,
161 mesh: engine::graphics::primitives::CpuMeshHandle,
162 pos: [f32; 3],
163 scale: [f32; 3],
164 rot_euler: [f32; 3],
165 color: [f32; 4],
166 ) {
167 let t = universe.world.add_component(
168 TransformComponent::new()
169 .with_position(pos[0], pos[1], pos[2])
170 .with_scale(scale[0], scale[1], scale[2])
171 .with_rotation_euler(rot_euler[0], rot_euler[1], rot_euler[2]),
172 );
173 let r = universe
174 .world
175 .add_component(RenderableComponent::new(Renderable::new(
176 mesh,
177 MaterialHandle::TOON_MESH,
178 )));
179 let c = universe
180 .world
181 .add_component(ColorComponent::rgba(color[0], color[1], color[2], color[3]));
182 let rc = universe
183 .world
184 .add_component(RaycastableComponent::enabled());
185
186 let _ = universe.attach(t, r);
187 let _ = universe.attach(r, c);
188 let _ = universe.attach(r, rc);
189
190 universe.add(t);
191 }examples/pointer-events.rs (line 104)
84fn spawn_click_cube(
85 universe: &mut engine::Universe,
86 parent: engine::ecs::ComponentId,
87 pos: [f32; 3],
88) -> engine::ecs::ComponentId {
89 use engine::ecs::component::{
90 ColorComponent, RaycastableComponent, RenderableComponent, TransformComponent,
91 };
92
93 let root = universe.world.add_component(
94 TransformComponent::new()
95 .with_position(pos[0], pos[1], pos[2])
96 .with_scale(0.40, 0.40, 0.40),
97 );
98 let color_node = universe
99 .world
100 .add_component(ColorComponent::rgba(0.25, 0.55, 1.0, 1.0));
101 let r = universe.world.add_component(RenderableComponent::cube());
102 let rc = universe
103 .world
104 .add_component(RaycastableComponent::enabled());
105
106 // Build the subtree before connecting to the live parent so that
107 // RaycastableComponent is already a child of RenderableComponent when
108 // RegisterRenderable is processed (renderable_is_raycastable checks children).
109 let _ = universe.attach(root, color_node);
110 let _ = universe.attach(color_node, r);
111 let _ = universe.attach(r, rc);
112 let _ = universe.attach(parent, root);
113
114 CLICK_CUBES
115 .get_or_init(|| Mutex::new(Default::default()))
116 .lock()
117 .unwrap()
118 .insert(
119 root,
120 ClickCubeState {
121 color_node,
122 click_count: 0,
123 },
124 );
125
126 universe.add_signal_handler(engine::ecs::SignalKind::Click, root, on_click_cube);
127
128 root
129}
130
131// ---------------------------------------------------------------------------
132// Drag cube — follows pointer while held
133// ---------------------------------------------------------------------------
134
135fn on_drag_move(
136 world: &mut engine::ecs::World,
137 emit: &mut dyn engine::ecs::SignalEmitter,
138 env: &engine::ecs::Signal,
139) {
140 let Some(engine::ecs::EventSignal::DragMove { delta_world, .. }) = env.event.as_ref() else {
141 return;
142 };
143 let delta = *delta_world;
144
145 // Walk up from the hit renderable to find the TransformComponent to move.
146 let mut cur = Some(env.scope);
147 while let Some(id) = cur {
148 if world
149 .get_component_by_id_as::<engine::ecs::component::TransformComponent>(id)
150 .is_some()
151 {
152 let [x, y, z] = world
153 .get_component_by_id_as::<engine::ecs::component::TransformComponent>(id)
154 .map(|t| t.transform.translation)
155 .unwrap_or([0.0; 3]);
156
157 emit.push_intent_now(
158 id,
159 engine::ecs::IntentValue::SetPosition {
160 component_ids: vec![id],
161 position: [x + delta[0], y + delta[1], z + delta[2]],
162 },
163 );
164 return;
165 }
166 cur = world.parent_of(id);
167 }
168}
169
170fn spawn_drag_cube(
171 universe: &mut engine::Universe,
172 parent: engine::ecs::ComponentId,
173 pos: [f32; 3],
174 color: [f32; 4],
175) -> engine::ecs::ComponentId {
176 use engine::ecs::component::{
177 ColorComponent, RaycastableComponent, RenderableComponent, TransformComponent,
178 };
179
180 let root = universe.world.add_component(
181 TransformComponent::new()
182 .with_position(pos[0], pos[1], pos[2])
183 .with_scale(0.45, 0.45, 0.45),
184 );
185 let color_node = universe
186 .world
187 .add_component(ColorComponent::rgba(color[0], color[1], color[2], color[3]));
188 let r = universe.world.add_component(RenderableComponent::cube());
189 let rc = universe
190 .world
191 .add_component(RaycastableComponent::enabled());
192
193 let _ = universe.attach(root, color_node);
194 let _ = universe.attach(color_node, r);
195 let _ = universe.attach(r, rc);
196 let _ = universe.attach(parent, root);
197
198 universe.add_signal_handler(engine::ecs::SignalKind::DragMove, r, on_drag_move);
199
200 root
201}
202
203// ---------------------------------------------------------------------------
204// "Both" cube — drag to move, click to cycle color
205// ---------------------------------------------------------------------------
206
207#[derive(Debug)]
208struct BothCubeState {
209 color_node: engine::ecs::ComponentId,
210 click_count: u32,
211}
212
213static BOTH_CUBES: OnceLock<
214 Mutex<std::collections::HashMap<engine::ecs::ComponentId, BothCubeState>>,
215> = OnceLock::new();
216
217fn on_both_cube(
218 world: &mut engine::ecs::World,
219 emit: &mut dyn engine::ecs::SignalEmitter,
220 env: &engine::ecs::Signal,
221) {
222 match env.event.as_ref() {
223 Some(engine::ecs::EventSignal::DragMove { delta_world, .. }) => {
224 on_drag_move(world, emit, env);
225 let _ = delta_world;
226 }
227 Some(engine::ecs::EventSignal::Click { .. }) => {
228 let map = BOTH_CUBES.get_or_init(|| Mutex::new(Default::default()));
229 let mut guard = map.lock().unwrap();
230 // scope is the renderable; walk up to find the cube root registered in the map
231 let mut cur = Some(env.scope);
232 let mut found_root = None;
233 while let Some(id) = cur {
234 if guard.contains_key(&id) {
235 found_root = Some(id);
236 break;
237 }
238 cur = world.parent_of(id);
239 }
240 let Some(root) = found_root else { return };
241 let Some(state) = guard.get_mut(&root) else {
242 return;
243 };
244
245 state.click_count += 1;
246 let colors = click_cube_colors();
247 let rgba = colors[state.click_count as usize % colors.len()];
248 emit.push_intent_now(
249 root,
250 engine::ecs::IntentValue::SetColor {
251 component_ids: vec![state.color_node],
252 rgba,
253 },
254 );
255 }
256 _ => {}
257 }
258}
259
260fn spawn_both_cube(
261 universe: &mut engine::Universe,
262 parent: engine::ecs::ComponentId,
263 pos: [f32; 3],
264) -> engine::ecs::ComponentId {
265 use engine::ecs::component::{
266 ColorComponent, RaycastableComponent, RenderableComponent, TransformComponent,
267 };
268
269 let root = universe.world.add_component(
270 TransformComponent::new()
271 .with_position(pos[0], pos[1], pos[2])
272 .with_scale(0.45, 0.45, 0.45),
273 );
274 let color_node = universe
275 .world
276 .add_component(ColorComponent::rgba(0.95, 0.60, 0.20, 1.0));
277 let r = universe.world.add_component(RenderableComponent::cube());
278 let rc = universe
279 .world
280 .add_component(RaycastableComponent::enabled());
281
282 let _ = universe.attach(root, color_node);
283 let _ = universe.attach(color_node, r);
284 let _ = universe.attach(r, rc);
285 let _ = universe.attach(parent, root);
286
287 BOTH_CUBES
288 .get_or_init(|| Mutex::new(Default::default()))
289 .lock()
290 .unwrap()
291 .insert(
292 root,
293 BothCubeState {
294 color_node,
295 click_count: 0,
296 },
297 );
298
299 universe.add_signal_handler(engine::ecs::SignalKind::DragMove, r, on_both_cube);
300 universe.add_signal_handler(engine::ecs::SignalKind::Click, r, on_both_cube);
301
302 root
303}examples/font-example.rs (line 162)
125 fn spawn_text_block(
126 universe: &mut engine::Universe,
127 position: (f32, f32, f32),
128 scale: f32,
129 text: &str,
130 font_texture_uri: Option<&str>,
131 text_color_rgba: Option<[f32; 4]>,
132 shadow: Option<TextShadowComponent>,
133 filtering: TextureFilteringComponent,
134 ) -> f32 {
135 // T_root { T_scale { [Color] { TXT { shadow, filtering } } } }
136 let text_root = universe.world.add_component(
137 TransformComponent::new().with_position(position.0, position.1, position.2),
138 );
139
140 let text_scale = universe
141 .world
142 .add_component(TransformComponent::new().with_scale(scale, scale, 1.0));
143 let _ = universe.attach(text_root, text_scale);
144
145 let text_parent = if let Some([r, g, b, a]) = text_color_rgba {
146 let color = universe
147 .world
148 .add_component(ColorComponent::rgba(r, g, b, a));
149 let _ = universe.attach(text_scale, color);
150 color
151 } else {
152 text_scale
153 };
154
155 let text_c = universe.world.add_component(TextComponent::new(text));
156 let _ = universe.attach(text_parent, text_c);
157
158 // Explicit opt-in: make the glyph renderables pickable.
159 // TextSystem will propagate this to all spawned glyph quads.
160 let raycastable = universe
161 .world
162 .add_component(RaycastableComponent::enabled());
163 let _ = universe.attach(text_c, raycastable);
164
165 // Route glyph quads into the alpha-to-coverage cutout pass.
166 let cutout = universe
167 .world
168 .add_component(TransparentCutoutComponent::new());
169 let _ = universe.attach(text_c, cutout);
170
171 if let Some(shadow) = shadow {
172 let shadow_id = universe.world.add_component(shadow);
173 let _ = universe.attach(text_c, shadow_id);
174 }
175
176 // Optional: override the font atlas for this text block.
177 // TextSystem will propagate this to all glyph renderables.
178 if let Some(uri) = font_texture_uri {
179 let tex = universe
180 .world
181 .add_component(TextureComponent::with_uri(uri));
182 let _ = universe.attach(text_c, tex);
183 }
184
185 let filtering_id = universe.world.add_component(filtering);
186 let _ = universe.attach(text_c, filtering_id);
187
188 universe.add(text_root);
189
190 estimate_text_height_world(text, scale)
191 }examples/example_util/mod.rs (line 240)
190pub fn spawn_desktop_camera_controls_hint(
191 universe: &mut engine::Universe,
192 camera_transform: engine::ecs::ComponentId,
193) -> engine::ecs::ComponentId {
194 use engine::ecs::component::{
195 ColorComponent, EditorComponent, EmissiveComponent, RaycastableComponent, TextComponent,
196 TextureFilteringComponent, TransformComponent,
197 };
198
199 // We intentionally do NOT parent the hint under the camera rig.
200 // This keeps it out of the camera subtree (and avoids inheriting camera motion/rotation).
201 //
202 // Instead, we place it in world space based on the camera rig's current pose.
203 // That makes it start out “in front-right of the camera” without being attached.
204 let (cam_pos, cam_rot) = universe
205 .world
206 .get_component_by_id_as::<TransformComponent>(camera_transform)
207 .map(|t| (t.transform.translation, t.transform.rotation))
208 .unwrap_or(([0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0]));
209
210 // Camera convention in examples: forward is -Z.
211 // Local-space offset from the camera rig at spawn time.
212 let local_offset = [0.65, 0.25, -1.7];
213
214 // Rotate the offset by the camera rig's rotation so it appears in front-right of the view.
215 let world_offset = mittens_engine::utils::math::quat_rotate_vec3(cam_rot, local_offset);
216 let world_pos = [
217 cam_pos[0] + world_offset[0],
218 cam_pos[1] + world_offset[1],
219 cam_pos[2] + world_offset[2],
220 ];
221
222 let hint_root = universe.world.add_component(
223 TransformComponent::new()
224 .with_position(world_pos[0], world_pos[1], world_pos[2])
225 .with_scale(0.055, 0.055, 1.0),
226 );
227
228 // Put the hint into an editor subtree so clicking it attaches gizmos.
229 let editor_root = universe.world.add_component(EditorComponent::new());
230 let _ = universe.attach(hint_root, editor_root);
231
232 let text = universe.world.add_component(TextComponent::new(
233 "use wasd/rf/qe\nand right-mouse\nclick and drag\nto move/look",
234 ));
235
236 // Make glyph renderables raycastable so the hint can be clicked without adding a
237 // large invisible pick plane (clicking is per-glyph / per-text-quad).
238 let raycastable = universe
239 .world
240 .add_component(RaycastableComponent::enabled());
241 let _ = universe.attach(text, raycastable);
242
243 // Text color should be an immediate child of the TextComponent root.
244 let color = universe
245 .world
246 .add_component(ColorComponent::rgba(0.0, 0.0, 0.0, 1.0));
247 let _ = universe.attach(text, color);
248
249 // TextSystem looks for these as immediate children of the TextComponent root.
250 let emissive = universe.world.add_component(EmissiveComponent::on());
251 let filtering = universe
252 .world
253 .add_component(TextureFilteringComponent::nearest_magnification());
254
255 let cutout = universe
256 .world
257 .add_component(TransparentCutoutComponent::new());
258 let _ = universe.attach(text, cutout);
259
260 let _ = universe.attach(editor_root, text);
261 let _ = universe.attach(text, emissive);
262 let _ = universe.attach(text, filtering);
263
264 // Ensure the text is initialized even if the caller only adds the camera rig.
265 universe.add(hint_root);
266
267 hint_root
268}Additional examples can be found in:
Sourcepub fn disabled() -> Self
pub fn disabled() -> Self
Examples found in repository?
examples/raycast-topology-animation.rs (line 175)
169 fn marker(universe: &mut engine::Universe, parent: engine::ecs::ComponentId, rgba: [f32; 4]) {
170 let r = universe
171 .world
172 .add_component(engine::ecs::component::RenderableComponent::cube());
173 let rc = universe
174 .world
175 .add_component(engine::ecs::component::RaycastableComponent::disabled());
176 let c = universe
177 .world
178 .add_component(engine::ecs::component::ColorComponent::rgba(
179 rgba[0], rgba[1], rgba[2], rgba[3],
180 ));
181 let e = universe
182 .world
183 .add_component(engine::ecs::component::EmissiveComponent::on());
184 let t = universe.world.add_component(
185 engine::ecs::component::TransformComponent::new().with_scale(0.15, 0.15, 0.15),
186 );
187 let _ = universe.attach(parent, t);
188 let _ = universe.attach(t, r);
189 let _ = universe.attach(r, rc);
190 let _ = universe.attach(r, c);
191 let _ = universe.attach(r, e);
192 }Sourcepub fn drag_only() -> Self
pub fn drag_only() -> Self
Captures drag events only; click falls through to hits behind this object.
Sourcepub fn click_only() -> Self
pub fn click_only() -> Self
Captures click events only; drag falls through to hits behind this object.
pub fn with_interaction_priority(self, interaction_priority: u8) -> Self
Trait Implementations§
Source§impl Clone for RaycastableComponent
impl Clone for RaycastableComponent
Source§fn clone(&self) -> RaycastableComponent
fn clone(&self) -> RaycastableComponent
Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
Performs copy-assignment from
source. Read moreSource§impl Component for RaycastableComponent
impl Component for RaycastableComponent
Source§fn init(&mut self, emit: &mut dyn SignalEmitter, component: ComponentId)
fn init(&mut self, emit: &mut dyn SignalEmitter, component: ComponentId)
Called when component is added to the World
Source§fn cleanup(&mut self, emit: &mut dyn SignalEmitter, component: ComponentId)
fn cleanup(&mut self, emit: &mut dyn SignalEmitter, component: ComponentId)
Called when component is removed from the World.
fn as_any(&self) -> &dyn Any
fn as_any_mut(&mut self) -> &mut dyn Any
Source§fn name(&self) -> &'static str
fn name(&self) -> &'static str
Short debug/type name for this component kind (e.g. “transform”, “camera”).
Source§fn to_mms_ast(&self, _world: &World) -> ComponentExpression
fn to_mms_ast(&self, _world: &World) -> ComponentExpression
Encode this component as an MMS Component Expression AST node. Read more
fn set_id(&mut self, _component: ComponentId)
impl Copy for RaycastableComponent
Source§impl Debug for RaycastableComponent
impl Debug for RaycastableComponent
Source§impl Default for RaycastableComponent
impl Default for RaycastableComponent
Source§fn default() -> RaycastableComponent
fn default() -> RaycastableComponent
Returns the “default value” for a type. Read more
Auto Trait Implementations§
impl Freeze for RaycastableComponent
impl RefUnwindSafe for RaycastableComponent
impl Send for RaycastableComponent
impl Sync for RaycastableComponent
impl Unpin for RaycastableComponent
impl UnsafeUnpin for RaycastableComponent
impl UnwindSafe for RaycastableComponent
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Convert
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Convert
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
Convert
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
Convert
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.