pub struct TransformComponent {
pub transform: Transform,
pub pending_look_at_target_world: Option<[f32; 3]>,
/* private fields */
}Fields§
§transform: TransformEngine-wide transform type (also used by renderer/VisualWorld).
pending_look_at_target_world: Option<[f32; 3]>Implementations§
Source§impl TransformComponent
impl TransformComponent
Sourcepub fn new() -> Self
pub fn new() -> Self
Examples found in repository?
examples/transparent-cutout-example.rs (line 20)
12fn spawn_gold_cube(
13 universe: &mut engine::Universe,
14 parent: engine::ecs::ComponentId,
15 position: (f32, f32, f32),
16 scale: f32,
17 color: (f32, f32, f32),
18) {
19 let t = universe.world.add_component(
20 TransformComponent::new()
21 .with_position(position.0, position.1, position.2)
22 .with_scale(scale, scale, scale),
23 );
24 let r = universe.world.add_component(RenderableComponent::cube());
25 let c = universe
26 .world
27 .add_component(ColorComponent::rgba(color.0, color.1, color.2, 1.0));
28
29 let _ = universe.attach(parent, t);
30 let _ = universe.attach(t, r);
31 let _ = universe.attach(r, c);
32}
33
34fn main() {
35 mittens_engine::example_support::ensure_model_assets();
36 utils::logger::init();
37
38 let world = engine::ecs::World::default();
39 let mut universe = engine::Universe::new(world);
40
41 // Orange/yellow-ish clear color so cutout edges read.
42 let clear = universe
43 .world
44 .add_component(BackgroundColorComponent::new());
45 let clear_c = universe
46 .world
47 .add_component(ColorComponent::rgba(0.98, 0.72, 0.22, 1.0));
48 let _ = universe.world.add_child(clear, clear_c);
49 universe.add(clear);
50
51 // Warm-ish ambient so the gold cubes don’t go too dark.
52 let ambient = universe
53 .world
54 .add_component(AmbientLightComponent::rgb(0.22, 0.16, 0.08));
55 universe.add(ambient);
56
57 // --- Camera rig (WASD/QE) ---
58 let input = universe
59 .world
60 .add_component(InputComponent::new().with_speed(2.0));
61 let input_mode = universe
62 .world
63 .add_component(InputTransformModeComponent::forward_z().with_roll_axis_y());
64 let _ = universe.attach(input, input_mode);
65
66 // Start a bit pulled back, looking toward the origin.
67 let rig_transform = universe
68 .world
69 .add_component(TransformComponent::new().with_position(0.0, 0.0, 5.0));
70 let _ = universe.attach(input, rig_transform);
71
72 let camera3d = universe
73 .world
74 .add_component(Camera3DComponent::new().with_far(200.0).with_fov(55.0));
75 let _ = universe.attach(rig_transform, camera3d);
76
77 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
78 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
79 universe.add(input);
80
81 // Key light for toon shading.
82 let light = universe.world.add_component(
83 PointLightComponent::new()
84 .with_distance(50.0)
85 .with_intensity(2.2)
86 .with_color(1.0, 0.98, 0.92),
87 );
88 let light_transform = universe
89 .world
90 .add_component(TransformComponent::new().with_position(2.0, 3.0, 4.0));
91 let _ = universe.attach(light_transform, light);
92 universe.add(light_transform);
93
94 // --- Transparent cutout: 100 cat faces in a 10x10 grid ---
95 // Place them *behind* the starting camera position (camera starts at z=+5.0).
96 // Not attached to the camera rig.
97 let cat_grid_root = universe
98 .world
99 .add_component(TransformComponent::new().with_position(0.0, 0.0, 9.0));
100 universe.add(cat_grid_root);
101
102 let grid_w: i32 = 10;
103 let grid_h: i32 = 10;
104 let spacing: f32 = 0.7;
105 let half_w = (grid_w as f32 - 1.0) * spacing * 0.5;
106 let half_h = (grid_h as f32 - 1.0) * spacing * 0.5;
107
108 for y in 0..grid_h {
109 for x in 0..grid_w {
110 // Topology: cat_grid_root { T_quad { R_quad { Texture + Filtering + Cutout + Color } } }
111 let px = x as f32 * spacing - half_w;
112 let py = y as f32 * spacing - half_h;
113
114 let pz: f32 = (x as f32) % half_w;
115
116 let quad_t = universe.world.add_component(
117 TransformComponent::new()
118 .with_position(px, py, pz)
119 .with_scale(0.55, 0.55, 1.0),
120 );
121 let quad_r = universe.world.add_component(RenderableComponent::square());
122
123 let quad_tex = universe.world.add_component(TextureComponent::with_uri(
124 "assets/textures/cat-face-amused.dds",
125 ));
126 let quad_filtering = universe
127 .world
128 .add_component(TextureFilteringComponent::linear());
129 let quad_cutout = universe
130 .world
131 .add_component(TransparentCutoutComponent::new());
132 let quad_color = universe
133 .world
134 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
135
136 let _ = universe.attach(cat_grid_root, quad_t);
137 let _ = universe.attach(quad_t, quad_r);
138 let _ = universe.attach(quad_r, quad_tex);
139 let _ = universe.attach(quad_r, quad_filtering);
140 let _ = universe.attach(quad_r, quad_cutout);
141 let _ = universe.attach(quad_r, quad_color);
142 }
143 }
144
145 // --- Gold/yellow cubes behind the quad ---
146 // Parent them under a transform so it’s easy to tweak their depth.
147 let cubes_root = universe
148 .world
149 .add_component(TransformComponent::new().with_position(0.0, 0.0, 4.6));
150
151 // point light for cats
152 let cat_light_tx = universe
153 .world
154 .add_component(TransformComponent::new().with_position(0.0, 2.0, 7.0));
155
156 let cat_light = universe.world.add_component(
157 PointLightComponent::new()
158 .with_distance(150.0)
159 .with_intensity(1.5)
160 .with_color(1.0, 0.98, 0.92),
161 );
162 let _ = universe.attach(cat_light_tx, cat_light);
163 let _ = universe.attach(cubes_root, cat_light_tx);
164
165 universe.add(cubes_root);
166
167 let gold_a = (1.0, 0.86, 0.22);
168 let gold_b = (1.0, 0.74, 0.10);
169 let gold_c = (0.95, 0.92, 0.32);
170
171 // A loose cluster that’s visible through the cutout (transparent) area.
172 spawn_gold_cube(&mut universe, cubes_root, (-1.1, -0.3, -0.2), 0.45, gold_a);
173 spawn_gold_cube(&mut universe, cubes_root, (1.0, -0.4, -0.4), 0.40, gold_b);
174 spawn_gold_cube(&mut universe, cubes_root, (-0.2, 0.9, -0.6), 0.38, gold_c);
175 spawn_gold_cube(&mut universe, cubes_root, (0.7, 0.6, -0.9), 0.32, gold_a);
176 spawn_gold_cube(&mut universe, cubes_root, (-0.8, 0.4, -1.1), 0.36, gold_b);
177
178 // Bigger orange/yellow cubes behind the cluster (to make the cutout depth obvious).
179 let orange_gold_a = (1.0, 0.62, 0.10);
180 let orange_gold_b = (1.0, 0.78, 0.18);
181 spawn_gold_cube(
182 &mut universe,
183 cubes_root,
184 (0.0, -0.1, -2.1),
185 1.15,
186 orange_gold_b,
187 );
188 spawn_gold_cube(
189 &mut universe,
190 cubes_root,
191 (-1.8, 0.6, -2.6),
192 0.95,
193 orange_gold_a,
194 );
195 spawn_gold_cube(
196 &mut universe,
197 cubes_root,
198 (1.9, 0.7, -2.9),
199 1.05,
200 orange_gold_b,
201 );
202 spawn_gold_cube(
203 &mut universe,
204 cubes_root,
205 (0.9, 1.8, -3.2),
206 0.90,
207 orange_gold_a,
208 );
209 spawn_gold_cube(
210 &mut universe,
211 cubes_root,
212 (-0.9, 1.7, -3.5),
213 1.10,
214 orange_gold_b,
215 );
216
217 // Process init-time registrations (loads textures, registers renderables, etc.).
218 universe.systems.process_commands(
219 &mut universe.world,
220 &mut universe.visuals,
221 &mut universe.render_assets,
222 &mut universe.command_queue,
223 );
224
225 universe.enable_repl();
226 engine::Windowing::run_app(universe).expect("Windowing failed");
227}More examples
examples/button-press.rs (line 185)
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}
200
201fn spawn_raycastable_colored_cube(
202 universe: &mut engine::Universe,
203 parent: engine::ecs::ComponentId,
204 pos: [f32; 3],
205 scale: [f32; 3],
206 rgba: [f32; 4],
207) -> engine::ecs::ComponentId {
208 use engine::ecs::component::ColorComponent;
209
210 let r = spawn_raycastable_cube(universe, parent, pos, scale);
211 let c = universe
212 .world
213 .add_component(ColorComponent::rgba(rgba[0], rgba[1], rgba[2], rgba[3]));
214 let _ = universe.attach(r, c);
215 r
216}
217
218fn wrap_at_for_button_middle_width(middle_w: f32, text_scale: f32) -> usize {
219 // TextSystem layout: each glyph advances by 1.0 in local X.
220 // With `text_root` scaled by `text_scale`, each glyph is ~`text_scale` world units wide.
221 // Reserve some horizontal padding so letters don't collide with the face border.
222 let padding_world = (middle_w * 0.10).clamp(0.02, 0.25);
223 let usable_world = (middle_w - padding_world).max(0.05);
224 let glyph_w_world = text_scale.abs().max(1e-4);
225
226 let chars_per_line = (usable_world / glyph_w_world).floor() as isize;
227 (chars_per_line.max(1) as usize).min(200)
228}
229
230fn measure_word_wrapped_block(text: &str, wrap_at: usize) -> (usize, usize) {
231 // Approximate the block size (max columns, rows) for word-wrap-at-space behavior.
232 // This is used only for centering the text root visually.
233 if text.is_empty() {
234 return (0, 0);
235 }
236
237 let wrap_at = wrap_at.max(1);
238
239 let mut rows = 1usize;
240 let mut col = 0usize;
241 let mut max_col = 0usize;
242
243 for (wi, word) in text.split_whitespace().enumerate() {
244 let wlen = word.chars().count();
245 if wlen == 0 {
246 continue;
247 }
248
249 // If this isn't the first word on the line, account for a space.
250 let needs_space = col > 0;
251 let add = wlen + if needs_space { 1 } else { 0 };
252
253 if col > 0 && col + add > wrap_at {
254 // Wrap at the previous whitespace opportunity.
255 max_col = max_col.max(col);
256 rows += 1;
257 col = 0;
258 }
259
260 // Place the word (and preceding space if any).
261 col += add;
262
263 // If a single word exceeds wrap_at, we don't break it (matching TextSystem word_wrap).
264 max_col = max_col.max(col);
265
266 // Preserve explicit newlines in the input.
267 if wi == 0 {
268 // no-op
269 }
270 }
271
272 max_col = max_col.max(col);
273 (max_col, rows)
274}
275
276fn spawn_button(
277 universe: &mut engine::Universe,
278 pos: [f32; 3],
279 middle_wh: [f32; 2],
280 border: ButtonBorder,
281 text: &str,
282 text_scale: f32,
283 color: [f32; 4],
284 background_color: [f32; 4],
285) -> engine::ecs::ComponentId {
286 use engine::ecs::component::{
287 ColorComponent, TextComponent, TextureFilteringComponent, TransformComponent,
288 TransparentCutoutComponent,
289 };
290
291 let button_root = universe
292 .world
293 .add_component(TransformComponent::new().with_position(pos[0], pos[1], pos[2]));
294
295 // Frame: static border around the button.
296 let frame_root = universe.world.add_component(TransformComponent::new());
297 let frame_color = universe
298 .world
299 .add_component(ColorComponent::rgba(color[0], color[1], color[2], color[3]));
300 let _ = universe.attach(button_root, frame_root);
301 let _ = universe.attach(frame_root, frame_color);
302
303 // Cap: pressable face + text.
304 let cap_raised_z = 0.030;
305 let cap_pressed_z = 0.000;
306 let cap_root = universe
307 .world
308 .add_component(TransformComponent::new().with_position(0.0, 0.0, cap_raised_z));
309 let _ = universe.attach(button_root, cap_root);
310
311 let face_color = universe.world.add_component(ColorComponent::rgba(
312 background_color[0],
313 background_color[1],
314 background_color[2],
315 background_color[3],
316 ));
317 let _ = universe.attach(cap_root, face_color);
318
319 // Geometry.
320 let depth = 0.025;
321 let middle_w = middle_wh[0];
322 let middle_h = middle_wh[1];
323
324 // Border pieces.
325 let full_w = middle_w + border.left + border.right;
326 let full_h = middle_h + border.top + border.bottom;
327
328 // Left / right.
329 let _left = spawn_raycastable_cube(
330 universe,
331 frame_color,
332 [-(middle_w * 0.5 + border.left * 0.5), 0.0, 0.0],
333 [border.left, full_h, depth],
334 );
335 let _right = spawn_raycastable_cube(
336 universe,
337 frame_color,
338 [(middle_w * 0.5 + border.right * 0.5), 0.0, 0.0],
339 [border.right, full_h, depth],
340 );
341
342 // Top / bottom.
343 let _top = spawn_raycastable_cube(
344 universe,
345 frame_color,
346 [0.0, (middle_h * 0.5 + border.top * 0.5), 0.0],
347 [full_w, border.top, depth],
348 );
349 let _bottom = spawn_raycastable_cube(
350 universe,
351 frame_color,
352 [0.0, -(middle_h * 0.5 + border.bottom * 0.5), 0.0],
353 [full_w, border.bottom, depth],
354 );
355
356 // Face (pressable center).
357 let _face = spawn_raycastable_cube(
358 universe,
359 face_color,
360 [0.0, 0.0, 0.0],
361 [middle_w, middle_h, depth],
362 );
363
364 // Text. (Heuristic centering; TextComponent is top-left anchored today.)
365 let text_root = universe.world.add_component(
366 TransformComponent::new()
367 .with_position(0.0, 0.0, depth * 0.5 + 0.006)
368 .with_scale(text_scale, text_scale, 1.0),
369 );
370 let _ = universe.attach(cap_root, text_root);
371
372 let wrap_at = wrap_at_for_button_middle_width(middle_w, text_scale);
373 let (cols, rows) = measure_word_wrapped_block(text, wrap_at);
374 let cols_f = cols.max(1) as f32;
375 let rows_f = rows.max(1) as f32;
376
377 // Center the text block around (0,0) in glyph-local units.
378 // X is left-to-right (0..cols-1), Y is down (0, -1, -2...).
379 let x_center = -0.5 * (cols_f - 1.0);
380 let y_center = 0.5 * (rows_f - 1.0);
381 let text_offset = universe
382 .world
383 .add_component(TransformComponent::new().with_position(x_center, y_center, 0.0));
384 let _ = universe.attach(text_root, text_offset);
385
386 let text_id = universe
387 .world
388 .add_component(TextComponent::with_word_wrap(text, wrap_at));
389 let _ = universe.attach(text_offset, text_id);
390
391 let cutout = universe
392 .world
393 .add_component(TransparentCutoutComponent::new());
394 let _ = universe.attach(text_id, cutout);
395
396 let filtering = universe
397 .world
398 .add_component(TextureFilteringComponent::nearest_magnification());
399 let _ = universe.attach(text_id, filtering);
400
401 universe.add(button_root);
402
403 // Attach text color *after* text has been built/initialized.
404 // We intentionally initialize the ColorComponent before attachment so that its `init()` will
405 // NOT re-run on attach; this exercises the TextSystem ParentChanged refresh behavior.
406 let text_color = universe
407 .world
408 .add_component(ColorComponent::rgba(0.0, 0.0, 0.0, 1.0));
409 universe.add(text_color);
410 let _ = universe.attach(text_id, text_color);
411
412 // Stash state for the signal handler.
413 let state = ButtonState {
414 pressed: false,
415 cap_root,
416 cap_raised_z,
417 cap_pressed_z,
418 frame_color,
419 face_color,
420 text_id,
421 text_color,
422 frame_color_up: color,
423 face_color_up: background_color,
424 text_color_up: [0.0, 0.0, 0.0, 1.0],
425 frame_color_down: darken(color, 0.75),
426 face_color_down: darken(background_color, 0.75),
427 text_color_down: [1.0, 1.0, 1.0, 1.0],
428 };
429
430 BUTTONS
431 .get_or_init(|| Mutex::new(HashMap::new()))
432 .lock()
433 .expect("button state lock")
434 .insert(button_root, state);
435
436 button_root
437}
438
439fn main() {
440 mittens_engine::example_support::ensure_model_assets();
441 utils::logger::init();
442
443 let world = engine::ecs::World::default();
444 let mut universe = engine::Universe::new(world);
445
446 // Minimal lit scene + pointer raycaster.
447 use engine::ecs::component::{
448 AmbientLightComponent, BackgroundColorComponent, Camera3DComponent,
449 DirectionalLightComponent, InputComponent, InputTransformModeComponent, PointerComponent,
450 TransformComponent,
451 };
452
453 let bg = universe
454 .world
455 .add_component(BackgroundColorComponent::new());
456 let bg_c = universe
457 .world
458 .add_component(engine::ecs::component::ColorComponent::rgba(
459 0.92, 0.92, 0.96, 1.0,
460 ));
461 let _ = universe.world.add_child(bg, bg_c);
462 universe.add(bg);
463
464 let ambient = universe
465 .world
466 .add_component(AmbientLightComponent::rgb(0.35, 0.35, 0.35));
467 universe.add(ambient);
468
469 let sun_t = universe
470 .world
471 .add_component(TransformComponent::new().with_position(1.0, 1.0, 1.0));
472 let sun = universe
473 .world
474 .add_component(DirectionalLightComponent::new());
475 let _ = universe.attach(sun_t, sun);
476 universe.add(sun_t);
477
478 let input = universe
479 .world
480 .add_component(InputComponent::new().with_speed(2.5));
481 let input_mode = universe.world.add_component(
482 InputTransformModeComponent::forward_z()
483 .with_fps_rotation()
484 .with_roll_axis_y(),
485 );
486 let _ = universe.attach(input, input_mode);
487
488 let rig_t = universe
489 .world
490 .add_component(TransformComponent::new().with_position(0.0, 0.0, 2.5));
491 let _ = universe.attach(input, rig_t);
492
493 let cam = universe
494 .world
495 .add_component(Camera3DComponent::new().with_far(600.0).with_fov(70.0));
496 let _ = universe.attach(rig_t, cam);
497
498 let pointer = universe.world.add_component(PointerComponent::new());
499 let _ = universe.attach(cam, pointer);
500
501 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_t);
502 universe.add(input);
503
504 // The button.
505 let button_root = spawn_button(
506 &mut universe,
507 [0.0, 0.0, 0.0],
508 [1.20, 0.45],
509 ButtonBorder::new(0.025, 0.025, 0.025, 0.025),
510 "click me",
511 0.18,
512 [0.15, 0.15, 0.20, 1.0],
513 [0.85, 0.85, 0.92, 1.0],
514 );
515
516 // A small 3-cube stack to the left of the button for gizmo testing:
517 // [cyan]
518 // [yellow][light brown]
519 //
520 // These live under an EditorComponent subtree so clicking attaches the editor gizmo.
521 {
522 use engine::ecs::component::{EditorComponent, TransformComponent};
523
524 let editor_root = universe.world.add_component(EditorComponent::new());
525
526 // Position the stack in world space (left of the button).
527 let stack_root = universe
528 .world
529 .add_component(TransformComponent::new().with_position(-1.55, 0.0, 0.0));
530 let _ = universe.attach(editor_root, stack_root);
531
532 let cube = 0.22_f32;
533 let gap = 0.04_f32;
534 let step = cube + gap;
535 let y_bottom = -0.5 * step;
536 let y_top = 0.5 * step;
537 let x_left = -0.5 * step;
538 let x_right = 0.5 * step;
539
540 let yellow = [1.0, 0.92, 0.22, 1.0];
541 let light_brown = [0.80, 0.66, 0.46, 1.0];
542 let cyan = [0.20, 0.95, 1.0, 1.0];
543
544 let _bottom_left = spawn_raycastable_colored_cube(
545 &mut universe,
546 stack_root,
547 [x_left, y_bottom, 0.0],
548 [cube, cube, 0.06],
549 yellow,
550 );
551 let _bottom_right = spawn_raycastable_colored_cube(
552 &mut universe,
553 stack_root,
554 [x_right, y_bottom, 0.0],
555 [cube, cube, 0.06],
556 light_brown,
557 );
558 let _top = spawn_raycastable_colored_cube(
559 &mut universe,
560 stack_root,
561 [0.0, y_top, 0.0],
562 [cube, cube, 0.06],
563 cyan,
564 );
565
566 universe.add(editor_root);
567 }
568
569 universe.add_signal_handler(
570 engine::ecs::SignalKind::DragStart,
571 button_root,
572 button_press_handler,
573 );
574 universe.add_signal_handler(
575 engine::ecs::SignalKind::DragEnd,
576 button_root,
577 button_press_handler,
578 );
579
580 universe.systems.process_commands(
581 &mut universe.world,
582 &mut universe.visuals,
583 &mut universe.render_assets,
584 &mut universe.command_queue,
585 );
586
587 universe.enable_repl();
588 engine::Windowing::run_app(universe).expect("Windowing failed");
589}examples/opacity-example.rs (line 21)
12fn spawn_cube(
13 universe: &mut engine::Universe,
14 parent: engine::ecs::ComponentId,
15 position: (f32, f32, f32),
16 scale: (f32, f32, f32),
17 color: Option<(f32, f32, f32, f32)>,
18 opacity: Option<OpacityComponent>,
19) {
20 let t = universe.world.add_component(
21 TransformComponent::new()
22 .with_position(position.0, position.1, position.2)
23 .with_scale(scale.0, scale.1, scale.2),
24 );
25 let r = universe.world.add_component(RenderableComponent::cube());
26
27 let _ = universe.attach(parent, t);
28 let _ = universe.attach(t, r);
29
30 if let Some((cr, cg, cb, ca)) = color {
31 let c = universe
32 .world
33 .add_component(ColorComponent::rgba(cr, cg, cb, ca));
34 let _ = universe.attach(r, c);
35 }
36
37 if let Some(o) = opacity {
38 let o = universe.world.add_component(o);
39 let _ = universe.attach(r, o);
40 }
41}
42
43fn spawn_text_label(universe: &mut engine::Universe, position: (f32, f32, f32), text: &str) {
44 // T_root { T_scale { TXT { filtering } } }
45 let text_root = universe
46 .world
47 .add_component(TransformComponent::new().with_position(position.0, position.1, position.2));
48
49 let text_scale = universe
50 .world
51 .add_component(TransformComponent::new().with_scale(0.18, 0.18, 1.0));
52 let _ = universe.attach(text_root, text_scale);
53
54 let text_c = universe.world.add_component(TextComponent::new(text));
55 let _ = universe.attach(text_scale, text_c);
56
57 // Keep it crisp.
58 let filtering = universe
59 .world
60 .add_component(TextureFilteringComponent::nearest());
61 let _ = universe.attach(text_c, filtering);
62
63 // White label.
64 let color = universe
65 .world
66 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
67 let _ = universe.attach(text_c, color);
68
69 universe.add(text_root);
70}
71
72fn text_block_dimensions(text: &str) -> (usize, usize) {
73 let mut max_cols = 0usize;
74 let mut rows = 1usize;
75 let mut cur = 0usize;
76
77 for ch in text.chars() {
78 if ch == '\n' {
79 max_cols = max_cols.max(cur);
80 cur = 0;
81 rows += 1;
82 } else {
83 cur += 1;
84 }
85 }
86 max_cols = max_cols.max(cur);
87
88 (max_cols.max(1), rows.max(1))
89}
90
91fn spawn_text_label_with_bg(
92 universe: &mut engine::Universe,
93 position: (f32, f32, f32),
94 text: &str,
95 bg_opacity: f32,
96) {
97 // T_root {
98 // T_bg { R_bg { Color black, Opacity } }
99 // T_scale { TXT { filtering } }
100 // }
101
102 let text_root = universe
103 .world
104 .add_component(TransformComponent::new().with_position(position.0, position.1, position.2));
105
106 // Background quad (slightly behind the glyph quads).
107 let (cols, rows) = text_block_dimensions(text);
108 let text_scale = 0.18_f32;
109 let pad_x = 0.55_f32;
110 let pad_y = 0.45_f32;
111 let bg_w = cols as f32 * text_scale + pad_x;
112 let bg_h = rows as f32 * text_scale + pad_y;
113
114 let bg_t = universe.world.add_component(
115 TransformComponent::new()
116 .with_position(1.5, 0.0, -0.02)
117 .with_scale(bg_w, bg_h, 1.0),
118 );
119 let bg_r = universe.world.add_component(RenderableComponent::square());
120 let _ = universe.attach(text_root, bg_t);
121 let _ = universe.attach(bg_t, bg_r);
122
123 let bg_c = universe
124 .world
125 .add_component(ColorComponent::rgba(0.0, 0.0, 0.0, 1.0));
126 let _ = universe.attach(bg_r, bg_c);
127
128 let bg_o = universe
129 .world
130 .add_component(OpacityComponent::new().with_opacity(bg_opacity));
131 let _ = universe.attach(bg_r, bg_o);
132
133 let text_scale_t = universe
134 .world
135 .add_component(TransformComponent::new().with_scale(text_scale, text_scale, 1.0));
136 let _ = universe.attach(text_root, text_scale_t);
137
138 let text_c = universe.world.add_component(TextComponent::new(text));
139 let _ = universe.attach(text_scale_t, text_c);
140
141 // Keep it crisp.
142 let filtering = universe
143 .world
144 .add_component(TextureFilteringComponent::nearest());
145 let _ = universe.attach(text_c, filtering);
146
147 // Force the label into the transparent pass so it layers correctly with the background.
148 // (Pass selection currently does not consider texture alpha.)
149 let color = universe
150 .world
151 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 0.998));
152 let _ = universe.attach(text_c, color);
153
154 universe.add(text_root);
155}
156
157fn spawn_demo_spot(
158 universe: &mut engine::Universe,
159 spot_pos: (f32, f32, f32),
160 opacity: f32,
161 multiple_layers: bool,
162 grid_n: i32,
163) {
164 let spot = universe
165 .world
166 .add_component(TransformComponent::new().with_position(spot_pos.0, spot_pos.1, spot_pos.2));
167
168 // All cubes inherit this color.
169 let white = universe
170 .world
171 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
172 let _ = universe.attach(spot, white);
173
174 // All cubes inherit this opacity (no per-cube OpacityComponent needed).
175 let mut oc = OpacityComponent::new().with_opacity(opacity);
176 if multiple_layers {
177 oc = oc.with_multiple_layers();
178 }
179 let oc = universe.world.add_component(oc);
180 let _ = universe.attach(spot, oc);
181
182 universe.add(spot);
183
184 // Cube grid.
185 let n = grid_n.max(1);
186 let spacing = if n <= 2 { 1.0 } else { 0.6 };
187 let cube_scale = if n <= 2 { 0.8 } else { 0.45 };
188 let half = (n as f32 - 1.0) * spacing * 0.5;
189
190 for z in 0..n {
191 for y in 0..n {
192 for x in 0..n {
193 let px = x as f32 * spacing - half;
194 let py = y as f32 * spacing;
195 let pz = z as f32 * spacing - half;
196
197 spawn_cube(
198 universe,
199 spot,
200 (px, py, pz),
201 (cube_scale, cube_scale, cube_scale),
202 None,
203 None,
204 );
205 }
206 }
207 }
208
209 // Label above.
210 let label = format!(
211 "opacity: {:.2}\nmulti-layer: {}",
212 opacity,
213 if multiple_layers { "true" } else { "false" }
214 );
215 // Keep labels away from the cube volume so they don't intersect.
216 let label_y = spot_pos.1 + (n as f32 * spacing) + 1.8;
217 let label_x = spot_pos.0 - (half + 0.6);
218 let label_z = spot_pos.2 - (half + 1.0);
219 spawn_text_label(universe, (label_x, label_y, label_z), &label);
220}
221
222fn spawn_demo_strip_pair(
223 universe: &mut engine::Universe,
224 spot_pos: (f32, f32, f32),
225 transparent_multiple_layers: bool,
226) {
227 let n_z: i32 = 4;
228 let spacing = 1.0;
229 let cube_scale = 0.8;
230 let half_z = (n_z as f32 - 1.0) * spacing * 0.5;
231
232 // Transparent strip (1x4 along Z).
233 let transparent_root = universe
234 .world
235 .add_component(TransformComponent::new().with_position(spot_pos.0, spot_pos.1, spot_pos.2));
236
237 let white = universe
238 .world
239 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
240 let _ = universe.attach(transparent_root, white);
241
242 let mut oc = OpacityComponent::new().with_opacity(0.50);
243 if transparent_multiple_layers {
244 oc = oc.with_multiple_layers();
245 }
246 let oc = universe.world.add_component(oc);
247 let _ = universe.attach(transparent_root, oc);
248
249 universe.add(transparent_root);
250
251 for z in 0..n_z {
252 let pz = z as f32 * spacing - half_z;
253 spawn_cube(
254 universe,
255 transparent_root,
256 (0.0, 0.0, pz),
257 (cube_scale, cube_scale, cube_scale),
258 None,
259 None,
260 );
261 }
262
263 // Opaque strip to the left, same spacing/scale.
264 let opaque_root = universe
265 .world
266 .add_component(TransformComponent::new().with_position(
267 spot_pos.0 - spacing,
268 spot_pos.1,
269 spot_pos.2,
270 ));
271 let white = universe
272 .world
273 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
274 let _ = universe.attach(opaque_root, white);
275 universe.add(opaque_root);
276
277 for z in 0..n_z {
278 let pz = z as f32 * spacing - half_z;
279 spawn_cube(
280 universe,
281 opaque_root,
282 (0.0, 0.0, pz),
283 (cube_scale, cube_scale, cube_scale),
284 None,
285 None,
286 );
287 }
288
289 let label = format!(
290 "mini: opaque + transparent strip\ntransparent opacity: 0.50\nmulti-layer: {}",
291 if transparent_multiple_layers {
292 "true"
293 } else {
294 "false"
295 }
296 );
297 let label_y = spot_pos.1 + spacing + 1.8;
298 let label_x = spot_pos.0 - (spacing * 2.6);
299 let label_z = spot_pos.2 - (half_z + 1.0);
300 spawn_text_label(universe, (label_x, label_y, label_z), &label);
301}
302
303fn spawn_demo_xy_plane(
304 universe: &mut engine::Universe,
305 spot_pos: (f32, f32, f32),
306 opacity: f32,
307 n_x: i32,
308 n_y: i32,
309 z: f32,
310) {
311 let spot = universe
312 .world
313 .add_component(TransformComponent::new().with_position(spot_pos.0, spot_pos.1, spot_pos.2));
314
315 let white = universe
316 .world
317 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
318 let _ = universe.attach(spot, white);
319
320 let oc = universe
321 .world
322 .add_component(OpacityComponent::new().with_opacity(opacity));
323 let _ = universe.attach(spot, oc);
324
325 universe.add(spot);
326
327 let nx = n_x.max(1);
328 let ny = n_y.max(1);
329 let spacing = 0.45;
330 let cube_scale = 0.35;
331 let half_x = (nx as f32 - 1.0) * spacing * 0.5;
332
333 for y in 0..ny {
334 for x in 0..nx {
335 let px = x as f32 * spacing - half_x;
336 let py = y as f32 * spacing;
337 spawn_cube(
338 universe,
339 spot,
340 (px, py, z),
341 (cube_scale, cube_scale, cube_scale),
342 None,
343 None,
344 );
345 }
346 }
347
348 // Label in front of the plane.
349 let label = format!(
350 "XY plane: {}x{}\nopacity: {:.2}\nmulti-layer: false",
351 nx, ny, opacity
352 );
353 let label_x = spot_pos.0 - (half_x + 0.6);
354 let label_y = spot_pos.1 + (ny as f32 * spacing) + 1.2;
355 let label_z = spot_pos.2 - 2.5;
356 spawn_text_label_with_bg(universe, (label_x, label_y, label_z), &label, 0.50);
357}
358
359fn main() {
360 mittens_engine::example_support::ensure_model_assets();
361 utils::logger::init();
362
363 let world = engine::ecs::World::default();
364 let mut universe = engine::Universe::new(world);
365
366 // Dark brown / pink background.
367 let bg = universe
368 .world
369 .add_component(BackgroundColorComponent::new());
370 let bg_c = universe
371 .world
372 .add_component(ColorComponent::rgba(0.22, 0.08, 0.10, 1.0));
373 let _ = universe.world.add_child(bg, bg_c);
374 universe.add(bg);
375
376 // Ambient so unlit areas aren't black.
377 let ambient = universe
378 .world
379 .add_component(AmbientLightComponent::rgb(0.35, 0.35, 0.40));
380 universe.add(ambient);
381
382 // A bright overhead light.
383 let light_t = universe.world.add_component(
384 TransformComponent::new()
385 .with_position(0.0, 18.0, 10.0)
386 .with_scale(0.2, 0.2, 0.2),
387 );
388 let light = universe.world.add_component(
389 PointLightComponent::new()
390 .with_color(1.0, 1.0, 1.0)
391 .with_intensity(1.6)
392 .with_distance(80.0),
393 );
394 let _ = universe.attach(light_t, light);
395 universe.add(light_t);
396
397 // --- Camera rig ---
398 // I { T { C3D { with_fps_rotation with_roll_axis_y } } }
399 let input = universe
400 .world
401 .add_component(InputComponent::new().with_speed(3.0));
402 let input_mode = universe.world.add_component(
403 InputTransformModeComponent::forward_z()
404 .with_roll_axis_y()
405 .with_fps_rotation(),
406 );
407 let _ = universe.attach(input, input_mode);
408
409 let rig_transform = universe
410 .world
411 .add_component(TransformComponent::new().with_position(0.0, 6.0, 20.0));
412 let _ = universe.attach(input, rig_transform);
413
414 let camera = universe.world.add_component(Camera3DComponent::new());
415 let _ = universe.attach(rig_transform, camera);
416
417 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
418 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
419
420 universe.add(input);
421
422 // --- Ground ---
423 let ground = universe.world.add_component(
424 TransformComponent::new()
425 .with_position(0.0, -0.6, 0.0)
426 .with_scale(60.0, 1.0, 60.0),
427 );
428 let ground_r = universe.world.add_component(RenderableComponent::cube());
429 let ground_c = universe
430 .world
431 .add_component(ColorComponent::rgba(0.95, 0.95, 0.95, 1.0));
432 let _ = universe.attach(ground, ground_r);
433 let _ = universe.attach(ground_r, ground_c);
434 universe.add(ground);
435
436 // --- Opaque yellow cubes behind (contrast reference) ---
437 for i in 0..6 {
438 let x = -10.0 + i as f32 * 4.0;
439 spawn_cube(
440 &mut universe,
441 ground,
442 (x, 1.2, -18.0),
443 (2.0, 2.0, 2.0),
444 Some((1.0, 0.92, 0.15, 1.0)),
445 None,
446 );
447 }
448
449 // --- Demo spots ---
450 // Left of the left big spot: a single-layer transparent XY plane (16x16).
451 spawn_demo_xy_plane(&mut universe, (-14.0, 0.0, 0.0), 0.50, 16, 16, 0.0);
452
453 // Big spots: 8x8x8
454 // Left: single-layer transparent (instanced)
455 spawn_demo_spot(&mut universe, (-4.0, 0.0, 0.0), 0.50, false, 8);
456
457 // Right: multi-layer transparent (sorted)
458 spawn_demo_spot(&mut universe, (4.0, 0.0, 0.0), 0.50, true, 8);
459
460 // Small spots: opaque 1x4 strip + transparent 1x4 strip (along Z).
461 spawn_demo_strip_pair(&mut universe, (-4.0, 0.0, 10.0), false);
462 spawn_demo_strip_pair(&mut universe, (4.0, 0.0, 10.0), true);
463
464 // Process init-time registrations.
465 universe.systems.process_commands(
466 &mut universe.world,
467 &mut universe.visuals,
468 &mut universe.render_assets,
469 &mut universe.command_queue,
470 );
471
472 universe.enable_repl();
473
474 engine::Windowing::run_app(universe).expect("Windowing failed");
475}examples/animation-for-topology.rs (line 28)
20fn spawn_emissive_marker_cube(
21 universe: &mut engine::Universe,
22 parent: engine::ecs::ComponentId,
23 local_pos: (f32, f32, f32),
24 scale: f32,
25 rgba: [f32; 4],
26) {
27 let tx = universe.world.add_component(
28 engine::ecs::component::TransformComponent::new()
29 .with_position(local_pos.0, local_pos.1, local_pos.2)
30 .with_scale(scale, scale, scale),
31 );
32 let r = universe
33 .world
34 .add_component(engine::ecs::component::RenderableComponent::cube());
35 let c = universe
36 .world
37 .add_component(engine::ecs::component::ColorComponent::rgba(
38 rgba[0], rgba[1], rgba[2], rgba[3],
39 ));
40 let e = universe
41 .world
42 .add_component(engine::ecs::component::EmissiveComponent::on());
43
44 let _ = universe.attach(parent, tx);
45 let _ = universe.attach(tx, r);
46 let _ = universe.attach(r, c);
47 let _ = universe.attach(r, e);
48}
49
50/// Create a cube subtree ahead-of-time (not attached to the scene).
51///
52/// Returns the root `TransformComponent` id.
53fn spawn_detached_cube_prefab(
54 universe: &mut engine::Universe,
55 scale: f32,
56 rgba: [f32; 4],
57) -> engine::ecs::ComponentId {
58 let tx = universe.world.add_component(
59 engine::ecs::component::TransformComponent::new().with_scale(scale, scale, scale),
60 );
61 let r = universe
62 .world
63 .add_component(engine::ecs::component::RenderableComponent::cube());
64 let c = universe
65 .world
66 .add_component(engine::ecs::component::ColorComponent::rgba(
67 rgba[0], rgba[1], rgba[2], rgba[3],
68 ));
69 let e = universe
70 .world
71 .add_component(engine::ecs::component::EmissiveComponent::on());
72
73 let _ = universe.attach(tx, r);
74 let _ = universe.attach(r, c);
75 let _ = universe.attach(r, e);
76
77 tx
78}
79
80fn main() {
81 mittens_engine::example_support::ensure_model_assets();
82 utils::logger::init();
83
84 let world = engine::ecs::World::default();
85 let mut universe = engine::Universe::new(world);
86
87 // Minimal scene with a camera so the window opens.
88 let clear = universe
89 .world
90 .add_component(engine::ecs::component::BackgroundColorComponent::new());
91 let clear_c = universe
92 .world
93 .add_component(engine::ecs::component::ColorComponent::rgba(
94 0.06, 0.06, 0.07, 1.0,
95 ));
96 let _ = universe.world.add_child(clear, clear_c);
97 universe.add(clear);
98
99 // Input-driven camera rig.
100 let input = universe
101 .world
102 .add_component(engine::ecs::component::InputComponent::new().with_speed(2.0));
103 let rig_transform = universe.world.add_component(
104 engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 9.0),
105 );
106 let input_mode = universe.world.add_component(
107 engine::ecs::component::InputTransformModeComponent::forward_z().with_roll_axis_y(),
108 );
109 let camera3d = universe.world.add_component(
110 engine::ecs::component::Camera3DComponent::new()
111 .with_far(250.0)
112 .with_fov(70.0),
113 );
114 let _ = universe.attach(input, input_mode);
115 let _ = universe.attach(input, rig_transform);
116 let _ = universe.attach(rig_transform, camera3d);
117
118 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
119 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
120 universe.add(input);
121
122 // Light so we can see non-emissive materials.
123 let light_tx = universe.world.add_component(
124 engine::ecs::component::TransformComponent::new().with_position(2.0, 3.5, 6.0),
125 );
126 let light = universe.world.add_component(
127 engine::ecs::component::PointLightComponent::new()
128 .with_distance(30.0)
129 .with_color(1.0, 1.0, 1.0),
130 );
131 let _ = universe.attach(light_tx, light);
132 universe.add(light_tx);
133
134 // ClockComponent drives the animation timeline in beats.
135 let clock = universe
136 .world
137 .add_component(engine::ecs::component::ClockComponent::new().with_bpm(140.0));
138 universe.add(clock);
139
140 // Root for all visualization objects.
141 let viz_root = universe.world.add_component(
142 engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 0.0),
143 );
144 universe.add(viz_root);
145
146 let anchor_count = 16usize;
147 let cube_pool_size = 8usize;
148
149 // Three grids side-by-side.
150 let layout_a = GridLayout {
151 origin: (-6.0, 0.0, 0.0),
152 spacing: 1.1,
153 };
154 let layout_b = GridLayout {
155 origin: (0.0, 0.0, 0.0),
156 spacing: 1.1,
157 };
158 let layout_c = GridLayout {
159 origin: (6.0, 0.0, 0.0),
160 spacing: 1.1,
161 };
162
163 // --- Grid A: detach + re-attach (reparent) ---
164 let grid_a_root = universe
165 .world
166 .add_component(engine::ecs::component::TransformComponent::new());
167 let _ = universe.attach(viz_root, grid_a_root);
168
169 let mut anchors_a: Vec<engine::ecs::ComponentId> = Vec::with_capacity(anchor_count);
170 for i in 0..anchor_count {
171 let (x, y, z) = grid_anchor_local(layout_a, i);
172 let anchor = universe.world.add_component(
173 engine::ecs::component::TransformComponent::new().with_position(x, y, z),
174 );
175 let _ = universe.attach(grid_a_root, anchor);
176 anchors_a.push(anchor);
177 spawn_emissive_marker_cube(
178 &mut universe,
179 anchor,
180 (0.0, -0.35, 0.0),
181 0.06,
182 [0.18, 0.18, 0.22, 1.0],
183 );
184 }
185
186 let mut cubes_a: Vec<engine::ecs::ComponentId> = Vec::with_capacity(cube_pool_size);
187 for i in 0..cube_pool_size {
188 let t = (i as f32) / ((cube_pool_size - 1) as f32).max(1.0);
189 let rgba = [0.10, 0.40 + 0.50 * t, 0.90 - 0.70 * t, 1.0];
190 cubes_a.push(spawn_detached_cube_prefab(&mut universe, 0.22, rgba));
191 }
192
193 let anim_a = universe
194 .world
195 .add_component(engine::ecs::component::AnimationComponent::new());
196 for i in 0..anchor_count {
197 let cube = cubes_a[i % cube_pool_size];
198 let parent = anchors_a[i];
199
200 // Explicit detach+attach to test topology changes via animations.
201
202 // Put them on separate keyframes so ordering is time-deterministic.
203 let beat_detach = i as f64;
204 let beat_attach = i as f64 + 0.05;
205
206 let kf_detach = universe
207 .world
208 .add_component(engine::ecs::component::KeyframeComponent::new(beat_detach));
209 let _ = universe.attach(anim_a, kf_detach);
210 let detach_action =
211 universe
212 .world
213 .add_component(engine::ecs::component::ActionComponent::new(
214 engine::ecs::IntentValue::Detach {
215 component_ids: vec![cube],
216 },
217 ));
218 let _ = universe.attach(kf_detach, detach_action);
219
220 let kf_attach = universe
221 .world
222 .add_component(engine::ecs::component::KeyframeComponent::new(beat_attach));
223 let _ = universe.attach(anim_a, kf_attach);
224 let attach_action =
225 universe
226 .world
227 .add_component(engine::ecs::component::ActionComponent::new(
228 engine::ecs::IntentValue::Attach {
229 parents: vec![parent],
230 child: cube,
231 },
232 ));
233 let _ = universe.attach(kf_attach, attach_action);
234 }
235 universe.add(anim_a);
236
237 // --- Grid B: continuously spawn (attach_clone) + delete-behind (remove_child) ---
238 //
239 // Uses the new actions:
240 // - `Action::attach_clone(parent, prefab_root)`
241 // - `Action::remove_child(parent, index)`
242 //
243 // This avoids needing a pre-built pool of cube ComponentIds.
244 let grid_b_root = universe
245 .world
246 .add_component(engine::ecs::component::TransformComponent::new());
247 let _ = universe.attach(viz_root, grid_b_root);
248
249 let mut anchors_b: Vec<engine::ecs::ComponentId> = Vec::with_capacity(anchor_count);
250 for i in 0..anchor_count {
251 let (x, y, z) = grid_anchor_local(layout_b, i);
252 let anchor = universe.world.add_component(
253 engine::ecs::component::TransformComponent::new().with_position(x, y, z),
254 );
255 let _ = universe.attach(grid_b_root, anchor);
256 anchors_b.push(anchor);
257
258 // Marker is attached under the grid root (not under the anchor), so anchor child index 0
259 // remains reserved for the dynamic cube subtree.
260 spawn_emissive_marker_cube(
261 &mut universe,
262 grid_b_root,
263 (x, y - 0.35, z),
264 0.06,
265 [0.22, 0.18, 0.18, 1.0],
266 );
267 }
268
269 // Detached prefab that will be cloned on demand (reddish cube).
270 let prefab_b = spawn_detached_cube_prefab(&mut universe, 0.20, [0.90, 0.25, 0.15, 1.0]);
271
272 let steps_b = 32usize;
273 let window = 8usize;
274
275 let anim_b = universe
276 .world
277 .add_component(engine::ecs::component::AnimationComponent::new());
278
279 // Phase 1: each beat, spawn a cube under an anchor; delete-behind by removing child(0).
280 for i in 0..steps_b {
281 let parent = anchors_b[i % anchor_count];
282
283 let beat_attach = i as f64;
284 let beat_remove = i as f64 + 0.05;
285
286 let kf_attach = universe
287 .world
288 .add_component(engine::ecs::component::KeyframeComponent::new(beat_attach));
289 let _ = universe.attach(anim_b, kf_attach);
290
291 let attach_action =
292 universe
293 .world
294 .add_component(engine::ecs::component::ActionComponent::new(
295 engine::ecs::IntentValue::AttachClone {
296 parents: vec![parent],
297 prefab_root: prefab_b,
298 },
299 ));
300 let _ = universe.attach(kf_attach, attach_action);
301
302 if i >= window {
303 let remove_parent = anchors_b[(i - window) % anchor_count];
304 let kf_remove = universe
305 .world
306 .add_component(engine::ecs::component::KeyframeComponent::new(beat_remove));
307 let _ = universe.attach(anim_b, kf_remove);
308
309 let remove_action =
310 universe
311 .world
312 .add_component(engine::ecs::component::ActionComponent::new(
313 engine::ecs::IntentValue::RemoveChild {
314 parents: vec![remove_parent],
315 index: 0,
316 },
317 ));
318 let _ = universe.attach(kf_remove, remove_action);
319 }
320 }
321
322 // Phase 2: cleanup tail so the loop doesn't accumulate cubes.
323 for j in 0..window {
324 let remove_parent = anchors_b[(steps_b - window + j) % anchor_count];
325 let beat_remove = steps_b as f64 + j as f64 + 0.05;
326
327 let kf_remove = universe
328 .world
329 .add_component(engine::ecs::component::KeyframeComponent::new(beat_remove));
330 let _ = universe.attach(anim_b, kf_remove);
331
332 let remove_action =
333 universe
334 .world
335 .add_component(engine::ecs::component::ActionComponent::new(
336 engine::ecs::IntentValue::RemoveChild {
337 parents: vec![remove_parent],
338 index: 0,
339 },
340 ));
341 let _ = universe.attach(kf_remove, remove_action);
342 }
343
344 universe.add(anim_b);
345
346 // --- Grid C: move cubes via set_position (no topology changes) ---
347 let grid_c_root = universe
348 .world
349 .add_component(engine::ecs::component::TransformComponent::new());
350 let _ = universe.attach(viz_root, grid_c_root);
351
352 let mut anchors_c: Vec<(f32, f32, f32)> = Vec::with_capacity(anchor_count);
353 for i in 0..anchor_count {
354 let (x, y, z) = grid_anchor_local(layout_c, i);
355 anchors_c.push((x, y, z));
356
357 let anchor = universe.world.add_component(
358 engine::ecs::component::TransformComponent::new().with_position(x, y, z),
359 );
360 let _ = universe.attach(grid_c_root, anchor);
361 spawn_emissive_marker_cube(
362 &mut universe,
363 anchor,
364 (0.0, -0.35, 0.0),
365 0.06,
366 [0.18, 0.22, 0.18, 1.0],
367 );
368 }
369
370 let mut cubes_c: Vec<engine::ecs::ComponentId> = Vec::with_capacity(cube_pool_size);
371 for i in 0..cube_pool_size {
372 let t = (i as f32) / ((cube_pool_size - 1) as f32).max(1.0);
373 let rgba = [0.25, 0.85 - 0.55 * t, 0.25 + 0.55 * t, 1.0];
374 let cube_root = spawn_detached_cube_prefab(&mut universe, 0.22, rgba);
375 let _ = universe.attach(grid_c_root, cube_root);
376 cubes_c.push(cube_root);
377 }
378
379 let anim_c = universe
380 .world
381 .add_component(engine::ecs::component::AnimationComponent::new());
382 for i in 0..anchor_count {
383 let kf = universe
384 .world
385 .add_component(engine::ecs::component::KeyframeComponent::new(i as f64));
386 let _ = universe.attach(anim_c, kf);
387
388 let cube = cubes_c[i % cube_pool_size];
389 let (x, y, z) = anchors_c[i];
390 let setpos_action =
391 universe
392 .world
393 .add_component(engine::ecs::component::ActionComponent::new(
394 engine::ecs::IntentValue::SetPosition {
395 component_ids: vec![cube],
396 position: [x, y, z],
397 },
398 ));
399 let _ = universe.attach(kf, setpos_action);
400 }
401 universe.add(anim_c);
402
403 universe.systems.process_commands(
404 &mut universe.world,
405 &mut universe.visuals,
406 &mut universe.render_assets,
407 &mut universe.command_queue,
408 );
409
410 engine::Windowing::run_app(universe).expect("Windowing failed");
411}examples/router.rs (line 15)
3fn spawn_runtime_text(
4 universe: &mut engine::Universe,
5 owner: engine::ecs::ComponentId,
6 label: &str,
7) {
8 use engine::ecs::component::{
9 ColorComponent, EdgeInsets, SizeDimension, StyleComponent, TextComponent,
10 TransformComponent,
11 };
12
13 let root = universe
14 .world
15 .add_component_boxed_named(label, Box::new(TransformComponent::new()));
16 let style = universe.world.add_component_boxed_named(
17 format!("{label}_style"),
18 Box::new({
19 let mut style = StyleComponent::new();
20 style.margin = EdgeInsets::all(0.5);
21 style.padding = EdgeInsets::axes(0.75, 0.5);
22 style.width = SizeDimension::Auto;
23 style.background_color = Some([0.93, 0.88, 0.98, 1.0]);
24 style
25 }),
26 );
27 let text = universe
28 .world
29 .add_component_boxed_named(format!("{label}_text"), Box::new(TextComponent::new(label)));
30 let color = universe
31 .world
32 .add_component(ColorComponent::rgba(0.38, 0.14, 0.62, 1.0));
33
34 let _ = universe.world.add_child(root, style);
35 let _ = universe.world.add_child(root, text);
36 let _ = universe.world.add_child(text, color);
37
38 universe.attach(owner, root).expect("attach routed child");
39}examples/example_util/mod.rs (line 45)
15pub fn spawn_mms_demo_rig(
16 universe: &mut engine::Universe,
17 cam_pos: [f32; 3],
18) -> engine::ecs::ComponentId {
19 use engine::ecs::component::{
20 BackgroundColorComponent, Camera3DComponent, InputComponent, InputTransformModeComponent,
21 TransformComponent,
22 };
23
24 // Dark blue clear colour.
25 let bg_color = universe
26 .world
27 .add_component(BackgroundColorComponent::new());
28 let bg_color_c = universe
29 .world
30 .add_component(ColorComponent::rgba(0.02, 0.03, 0.10, 1.0));
31 let _ = universe.world.add_child(bg_color, bg_color_c);
32 universe.add(bg_color);
33
34 // Camera rig: Input → Transform → Camera3D.
35 let input = universe
36 .world
37 .add_component(InputComponent::new().with_speed(3.0));
38 let input_mode = universe
39 .world
40 .add_component(InputTransformModeComponent::forward_z().with_roll_axis_y());
41 let _ = universe.attach(input, input_mode);
42
43 let cam_transform = universe
44 .world
45 .add_component(TransformComponent::new().with_position(cam_pos[0], cam_pos[1], cam_pos[2]));
46 let _ = universe.attach(input, cam_transform);
47
48 let camera = universe.world.add_component(Camera3DComponent::new());
49 let _ = universe.attach(cam_transform, camera);
50
51 universe.add(input);
52
53 spawn_desktop_camera_controls_hint(universe, cam_transform);
54
55 cam_transform
56}
57
58fn hash_u32(mut x: u32) -> u32 {
59 x ^= x >> 16;
60 x = x.wrapping_mul(0x7feb_352d);
61 x ^= x >> 15;
62 x = x.wrapping_mul(0x846c_a68b);
63 x ^= x >> 16;
64 x
65}
66
67fn rand01(seed: u32) -> f32 {
68 (hash_u32(seed) as f32) / (u32::MAX as f32)
69}
70
71#[derive(Debug, Clone, Copy)]
72pub struct CloudRingParams {
73 pub cloud_count: u32,
74 pub radius: f32,
75 pub center_y: f32,
76 pub puffs_per_cloud: u32,
77 /// 0.0 = perfectly evenly spaced, 1.0 = up to one full step of jitter.
78 pub angle_jitter: f32,
79 /// Probability a given cluster is placed higher than `center_y`.
80 pub high_y_probability: f32,
81 /// Multiplier applied to `center_y` when a cluster is chosen to be high.
82 pub high_y_multiplier: f32,
83 /// Seed used for deterministic layout variation.
84 pub seed: u32,
85}
86
87impl Default for CloudRingParams {
88 fn default() -> Self {
89 Self {
90 cloud_count: 5,
91 radius: 26.0,
92 center_y: 2.0,
93 puffs_per_cloud: 28,
94 angle_jitter: 0.0,
95 high_y_probability: 0.0,
96 high_y_multiplier: 1.0,
97 seed: 0xC10u32,
98 }
99 }
100}
101
102/// Spawns a ring of "cloud" puff clusters under an existing background root.
103///
104/// Assumes `bg_root` is a `BackgroundComponent` (usually `with_occlusion_and_lighting()`).
105pub fn spawn_cloud_ring(
106 universe: &mut engine::Universe,
107 bg_root: engine::ecs::ComponentId,
108 p: CloudRingParams,
109) {
110 if p.cloud_count == 0 || p.radius == 0.0 {
111 return;
112 }
113
114 let cube_mesh = universe
115 .render_assets
116 .get_mesh(engine::graphics::BuiltinMeshType::Cube);
117
118 let step = std::f32::consts::TAU / (p.cloud_count as f32);
119 let angle_jitter = p.angle_jitter.clamp(0.0, 1.0);
120 let high_y_probability = p.high_y_probability.clamp(0.0, 1.0);
121
122 for i in 0..p.cloud_count {
123 let seed_i = p.seed ^ i.wrapping_mul(0x9E37_79B9);
124 let jitter = (rand01(seed_i ^ 0xa53a_9d2d) - 0.5) * step * angle_jitter;
125 let a = (i as f32) * step + jitter;
126 let cx = p.radius * a.cos();
127 let cz = p.radius * a.sin();
128
129 let cy = if rand01(seed_i ^ 0x7f4a_7c15) < high_y_probability {
130 p.center_y * p.high_y_multiplier
131 } else {
132 p.center_y
133 };
134
135 let center_tx = universe.world.add_component(
136 engine::ecs::component::TransformComponent::new().with_position(cx, cy, cz),
137 );
138 let _ = universe.attach(bg_root, center_tx);
139
140 let base_seed = seed_i ^ 0x243f_6a88;
141 for puff_i in 0..p.puffs_per_cloud {
142 let seed = base_seed ^ puff_i.wrapping_mul(1_103_515_245);
143
144 let ox = (rand01(seed ^ 0x68bc_21eb) - 0.5) * 9.0;
145 let oy = (rand01(seed ^ 0x02e5_be93) - 0.5) * 4.0;
146 let oz = (rand01(seed ^ 0xa1d3_4f2b) - 0.5) * 9.0;
147
148 let base = 0.7 + rand01(seed ^ 0x9e37_79b9) * 2.8;
149 let sx = base * (0.7 + rand01(seed ^ 0x243f_6a88) * 0.9);
150 let sy = base * (0.6 + rand01(seed ^ 0x85a3_08d3) * 1.0);
151 let sz = base * (0.7 + rand01(seed ^ 0x1319_8a2e) * 0.9);
152
153 let tx = universe.world.add_component(
154 engine::ecs::component::TransformComponent::new()
155 .with_position(ox, oy, oz)
156 .with_scale(sx, sy, sz),
157 );
158 let renderable =
159 universe
160 .world
161 .add_component(engine::ecs::component::RenderableComponent::new(
162 engine::graphics::primitives::Renderable::new(
163 cube_mesh,
164 engine::graphics::primitives::MaterialHandle::TOON_MESH,
165 ),
166 ));
167
168 let t = rand01(seed ^ 0x7f4a_7c15);
169 let r = 0.70 + 0.10 * t;
170 let g = 0.72 + 0.10 * t;
171 let b = 0.80 + 0.12 * t;
172 let color = universe
173 .world
174 .add_component(engine::ecs::component::ColorComponent::rgba(r, g, b, 1.0));
175
176 let _ = universe.attach(center_tx, tx);
177 let _ = universe.attach(tx, renderable);
178 let _ = universe.attach(renderable, color);
179 }
180 }
181}
182
183/// Spawn a small camera-attached help text for desktop controls.
184///
185/// The returned component is the hint root `TransformComponent`.
186///
187/// This is intended for examples that use the common topology:
188///
189/// `I { T { C3D } }` (InputComponent → TransformComponent → Camera3DComponent)
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:
- examples/simple-demo.rs
- examples/pointer-events.rs
- examples/diy-panel.rs
- examples/util/mod.rs
- examples/vr-input.rs
- examples/triage/panel-pierce.rs
- examples/scrolling.rs
- examples/audio-clip-instance-demo.rs
- examples/audio-music-demo.rs
- examples/text-animation.rs
- examples/vtuber-example.rs
- examples/openxr.rs
- examples/text-example.rs
- examples/gestures-and-gizmos.rs
- examples/background-example.rs
- examples/mesh-factory-example.rs
- examples/bisket-vr-demo.rs
- examples/background-occlusion-example.rs
- examples/raycast-topology-animation.rs
- examples/font-example.rs
- examples/vtuber-joints-example.rs
- examples/animation-example.rs
- examples/folder-text.rs
- examples/collision-perimeter.rs
- examples/audio-graph-example.rs
- examples/gravity-fields.rs
Sourcepub fn with_position(self, x: f32, y: f32, z: f32) -> Self
pub fn with_position(self, x: f32, y: f32, z: f32) -> Self
Examples found in repository?
examples/transparent-cutout-example.rs (line 21)
12fn spawn_gold_cube(
13 universe: &mut engine::Universe,
14 parent: engine::ecs::ComponentId,
15 position: (f32, f32, f32),
16 scale: f32,
17 color: (f32, f32, f32),
18) {
19 let t = universe.world.add_component(
20 TransformComponent::new()
21 .with_position(position.0, position.1, position.2)
22 .with_scale(scale, scale, scale),
23 );
24 let r = universe.world.add_component(RenderableComponent::cube());
25 let c = universe
26 .world
27 .add_component(ColorComponent::rgba(color.0, color.1, color.2, 1.0));
28
29 let _ = universe.attach(parent, t);
30 let _ = universe.attach(t, r);
31 let _ = universe.attach(r, c);
32}
33
34fn main() {
35 mittens_engine::example_support::ensure_model_assets();
36 utils::logger::init();
37
38 let world = engine::ecs::World::default();
39 let mut universe = engine::Universe::new(world);
40
41 // Orange/yellow-ish clear color so cutout edges read.
42 let clear = universe
43 .world
44 .add_component(BackgroundColorComponent::new());
45 let clear_c = universe
46 .world
47 .add_component(ColorComponent::rgba(0.98, 0.72, 0.22, 1.0));
48 let _ = universe.world.add_child(clear, clear_c);
49 universe.add(clear);
50
51 // Warm-ish ambient so the gold cubes don’t go too dark.
52 let ambient = universe
53 .world
54 .add_component(AmbientLightComponent::rgb(0.22, 0.16, 0.08));
55 universe.add(ambient);
56
57 // --- Camera rig (WASD/QE) ---
58 let input = universe
59 .world
60 .add_component(InputComponent::new().with_speed(2.0));
61 let input_mode = universe
62 .world
63 .add_component(InputTransformModeComponent::forward_z().with_roll_axis_y());
64 let _ = universe.attach(input, input_mode);
65
66 // Start a bit pulled back, looking toward the origin.
67 let rig_transform = universe
68 .world
69 .add_component(TransformComponent::new().with_position(0.0, 0.0, 5.0));
70 let _ = universe.attach(input, rig_transform);
71
72 let camera3d = universe
73 .world
74 .add_component(Camera3DComponent::new().with_far(200.0).with_fov(55.0));
75 let _ = universe.attach(rig_transform, camera3d);
76
77 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
78 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
79 universe.add(input);
80
81 // Key light for toon shading.
82 let light = universe.world.add_component(
83 PointLightComponent::new()
84 .with_distance(50.0)
85 .with_intensity(2.2)
86 .with_color(1.0, 0.98, 0.92),
87 );
88 let light_transform = universe
89 .world
90 .add_component(TransformComponent::new().with_position(2.0, 3.0, 4.0));
91 let _ = universe.attach(light_transform, light);
92 universe.add(light_transform);
93
94 // --- Transparent cutout: 100 cat faces in a 10x10 grid ---
95 // Place them *behind* the starting camera position (camera starts at z=+5.0).
96 // Not attached to the camera rig.
97 let cat_grid_root = universe
98 .world
99 .add_component(TransformComponent::new().with_position(0.0, 0.0, 9.0));
100 universe.add(cat_grid_root);
101
102 let grid_w: i32 = 10;
103 let grid_h: i32 = 10;
104 let spacing: f32 = 0.7;
105 let half_w = (grid_w as f32 - 1.0) * spacing * 0.5;
106 let half_h = (grid_h as f32 - 1.0) * spacing * 0.5;
107
108 for y in 0..grid_h {
109 for x in 0..grid_w {
110 // Topology: cat_grid_root { T_quad { R_quad { Texture + Filtering + Cutout + Color } } }
111 let px = x as f32 * spacing - half_w;
112 let py = y as f32 * spacing - half_h;
113
114 let pz: f32 = (x as f32) % half_w;
115
116 let quad_t = universe.world.add_component(
117 TransformComponent::new()
118 .with_position(px, py, pz)
119 .with_scale(0.55, 0.55, 1.0),
120 );
121 let quad_r = universe.world.add_component(RenderableComponent::square());
122
123 let quad_tex = universe.world.add_component(TextureComponent::with_uri(
124 "assets/textures/cat-face-amused.dds",
125 ));
126 let quad_filtering = universe
127 .world
128 .add_component(TextureFilteringComponent::linear());
129 let quad_cutout = universe
130 .world
131 .add_component(TransparentCutoutComponent::new());
132 let quad_color = universe
133 .world
134 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
135
136 let _ = universe.attach(cat_grid_root, quad_t);
137 let _ = universe.attach(quad_t, quad_r);
138 let _ = universe.attach(quad_r, quad_tex);
139 let _ = universe.attach(quad_r, quad_filtering);
140 let _ = universe.attach(quad_r, quad_cutout);
141 let _ = universe.attach(quad_r, quad_color);
142 }
143 }
144
145 // --- Gold/yellow cubes behind the quad ---
146 // Parent them under a transform so it’s easy to tweak their depth.
147 let cubes_root = universe
148 .world
149 .add_component(TransformComponent::new().with_position(0.0, 0.0, 4.6));
150
151 // point light for cats
152 let cat_light_tx = universe
153 .world
154 .add_component(TransformComponent::new().with_position(0.0, 2.0, 7.0));
155
156 let cat_light = universe.world.add_component(
157 PointLightComponent::new()
158 .with_distance(150.0)
159 .with_intensity(1.5)
160 .with_color(1.0, 0.98, 0.92),
161 );
162 let _ = universe.attach(cat_light_tx, cat_light);
163 let _ = universe.attach(cubes_root, cat_light_tx);
164
165 universe.add(cubes_root);
166
167 let gold_a = (1.0, 0.86, 0.22);
168 let gold_b = (1.0, 0.74, 0.10);
169 let gold_c = (0.95, 0.92, 0.32);
170
171 // A loose cluster that’s visible through the cutout (transparent) area.
172 spawn_gold_cube(&mut universe, cubes_root, (-1.1, -0.3, -0.2), 0.45, gold_a);
173 spawn_gold_cube(&mut universe, cubes_root, (1.0, -0.4, -0.4), 0.40, gold_b);
174 spawn_gold_cube(&mut universe, cubes_root, (-0.2, 0.9, -0.6), 0.38, gold_c);
175 spawn_gold_cube(&mut universe, cubes_root, (0.7, 0.6, -0.9), 0.32, gold_a);
176 spawn_gold_cube(&mut universe, cubes_root, (-0.8, 0.4, -1.1), 0.36, gold_b);
177
178 // Bigger orange/yellow cubes behind the cluster (to make the cutout depth obvious).
179 let orange_gold_a = (1.0, 0.62, 0.10);
180 let orange_gold_b = (1.0, 0.78, 0.18);
181 spawn_gold_cube(
182 &mut universe,
183 cubes_root,
184 (0.0, -0.1, -2.1),
185 1.15,
186 orange_gold_b,
187 );
188 spawn_gold_cube(
189 &mut universe,
190 cubes_root,
191 (-1.8, 0.6, -2.6),
192 0.95,
193 orange_gold_a,
194 );
195 spawn_gold_cube(
196 &mut universe,
197 cubes_root,
198 (1.9, 0.7, -2.9),
199 1.05,
200 orange_gold_b,
201 );
202 spawn_gold_cube(
203 &mut universe,
204 cubes_root,
205 (0.9, 1.8, -3.2),
206 0.90,
207 orange_gold_a,
208 );
209 spawn_gold_cube(
210 &mut universe,
211 cubes_root,
212 (-0.9, 1.7, -3.5),
213 1.10,
214 orange_gold_b,
215 );
216
217 // Process init-time registrations (loads textures, registers renderables, etc.).
218 universe.systems.process_commands(
219 &mut universe.world,
220 &mut universe.visuals,
221 &mut universe.render_assets,
222 &mut universe.command_queue,
223 );
224
225 universe.enable_repl();
226 engine::Windowing::run_app(universe).expect("Windowing failed");
227}More examples
examples/button-press.rs (line 186)
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}
200
201fn spawn_raycastable_colored_cube(
202 universe: &mut engine::Universe,
203 parent: engine::ecs::ComponentId,
204 pos: [f32; 3],
205 scale: [f32; 3],
206 rgba: [f32; 4],
207) -> engine::ecs::ComponentId {
208 use engine::ecs::component::ColorComponent;
209
210 let r = spawn_raycastable_cube(universe, parent, pos, scale);
211 let c = universe
212 .world
213 .add_component(ColorComponent::rgba(rgba[0], rgba[1], rgba[2], rgba[3]));
214 let _ = universe.attach(r, c);
215 r
216}
217
218fn wrap_at_for_button_middle_width(middle_w: f32, text_scale: f32) -> usize {
219 // TextSystem layout: each glyph advances by 1.0 in local X.
220 // With `text_root` scaled by `text_scale`, each glyph is ~`text_scale` world units wide.
221 // Reserve some horizontal padding so letters don't collide with the face border.
222 let padding_world = (middle_w * 0.10).clamp(0.02, 0.25);
223 let usable_world = (middle_w - padding_world).max(0.05);
224 let glyph_w_world = text_scale.abs().max(1e-4);
225
226 let chars_per_line = (usable_world / glyph_w_world).floor() as isize;
227 (chars_per_line.max(1) as usize).min(200)
228}
229
230fn measure_word_wrapped_block(text: &str, wrap_at: usize) -> (usize, usize) {
231 // Approximate the block size (max columns, rows) for word-wrap-at-space behavior.
232 // This is used only for centering the text root visually.
233 if text.is_empty() {
234 return (0, 0);
235 }
236
237 let wrap_at = wrap_at.max(1);
238
239 let mut rows = 1usize;
240 let mut col = 0usize;
241 let mut max_col = 0usize;
242
243 for (wi, word) in text.split_whitespace().enumerate() {
244 let wlen = word.chars().count();
245 if wlen == 0 {
246 continue;
247 }
248
249 // If this isn't the first word on the line, account for a space.
250 let needs_space = col > 0;
251 let add = wlen + if needs_space { 1 } else { 0 };
252
253 if col > 0 && col + add > wrap_at {
254 // Wrap at the previous whitespace opportunity.
255 max_col = max_col.max(col);
256 rows += 1;
257 col = 0;
258 }
259
260 // Place the word (and preceding space if any).
261 col += add;
262
263 // If a single word exceeds wrap_at, we don't break it (matching TextSystem word_wrap).
264 max_col = max_col.max(col);
265
266 // Preserve explicit newlines in the input.
267 if wi == 0 {
268 // no-op
269 }
270 }
271
272 max_col = max_col.max(col);
273 (max_col, rows)
274}
275
276fn spawn_button(
277 universe: &mut engine::Universe,
278 pos: [f32; 3],
279 middle_wh: [f32; 2],
280 border: ButtonBorder,
281 text: &str,
282 text_scale: f32,
283 color: [f32; 4],
284 background_color: [f32; 4],
285) -> engine::ecs::ComponentId {
286 use engine::ecs::component::{
287 ColorComponent, TextComponent, TextureFilteringComponent, TransformComponent,
288 TransparentCutoutComponent,
289 };
290
291 let button_root = universe
292 .world
293 .add_component(TransformComponent::new().with_position(pos[0], pos[1], pos[2]));
294
295 // Frame: static border around the button.
296 let frame_root = universe.world.add_component(TransformComponent::new());
297 let frame_color = universe
298 .world
299 .add_component(ColorComponent::rgba(color[0], color[1], color[2], color[3]));
300 let _ = universe.attach(button_root, frame_root);
301 let _ = universe.attach(frame_root, frame_color);
302
303 // Cap: pressable face + text.
304 let cap_raised_z = 0.030;
305 let cap_pressed_z = 0.000;
306 let cap_root = universe
307 .world
308 .add_component(TransformComponent::new().with_position(0.0, 0.0, cap_raised_z));
309 let _ = universe.attach(button_root, cap_root);
310
311 let face_color = universe.world.add_component(ColorComponent::rgba(
312 background_color[0],
313 background_color[1],
314 background_color[2],
315 background_color[3],
316 ));
317 let _ = universe.attach(cap_root, face_color);
318
319 // Geometry.
320 let depth = 0.025;
321 let middle_w = middle_wh[0];
322 let middle_h = middle_wh[1];
323
324 // Border pieces.
325 let full_w = middle_w + border.left + border.right;
326 let full_h = middle_h + border.top + border.bottom;
327
328 // Left / right.
329 let _left = spawn_raycastable_cube(
330 universe,
331 frame_color,
332 [-(middle_w * 0.5 + border.left * 0.5), 0.0, 0.0],
333 [border.left, full_h, depth],
334 );
335 let _right = spawn_raycastable_cube(
336 universe,
337 frame_color,
338 [(middle_w * 0.5 + border.right * 0.5), 0.0, 0.0],
339 [border.right, full_h, depth],
340 );
341
342 // Top / bottom.
343 let _top = spawn_raycastable_cube(
344 universe,
345 frame_color,
346 [0.0, (middle_h * 0.5 + border.top * 0.5), 0.0],
347 [full_w, border.top, depth],
348 );
349 let _bottom = spawn_raycastable_cube(
350 universe,
351 frame_color,
352 [0.0, -(middle_h * 0.5 + border.bottom * 0.5), 0.0],
353 [full_w, border.bottom, depth],
354 );
355
356 // Face (pressable center).
357 let _face = spawn_raycastable_cube(
358 universe,
359 face_color,
360 [0.0, 0.0, 0.0],
361 [middle_w, middle_h, depth],
362 );
363
364 // Text. (Heuristic centering; TextComponent is top-left anchored today.)
365 let text_root = universe.world.add_component(
366 TransformComponent::new()
367 .with_position(0.0, 0.0, depth * 0.5 + 0.006)
368 .with_scale(text_scale, text_scale, 1.0),
369 );
370 let _ = universe.attach(cap_root, text_root);
371
372 let wrap_at = wrap_at_for_button_middle_width(middle_w, text_scale);
373 let (cols, rows) = measure_word_wrapped_block(text, wrap_at);
374 let cols_f = cols.max(1) as f32;
375 let rows_f = rows.max(1) as f32;
376
377 // Center the text block around (0,0) in glyph-local units.
378 // X is left-to-right (0..cols-1), Y is down (0, -1, -2...).
379 let x_center = -0.5 * (cols_f - 1.0);
380 let y_center = 0.5 * (rows_f - 1.0);
381 let text_offset = universe
382 .world
383 .add_component(TransformComponent::new().with_position(x_center, y_center, 0.0));
384 let _ = universe.attach(text_root, text_offset);
385
386 let text_id = universe
387 .world
388 .add_component(TextComponent::with_word_wrap(text, wrap_at));
389 let _ = universe.attach(text_offset, text_id);
390
391 let cutout = universe
392 .world
393 .add_component(TransparentCutoutComponent::new());
394 let _ = universe.attach(text_id, cutout);
395
396 let filtering = universe
397 .world
398 .add_component(TextureFilteringComponent::nearest_magnification());
399 let _ = universe.attach(text_id, filtering);
400
401 universe.add(button_root);
402
403 // Attach text color *after* text has been built/initialized.
404 // We intentionally initialize the ColorComponent before attachment so that its `init()` will
405 // NOT re-run on attach; this exercises the TextSystem ParentChanged refresh behavior.
406 let text_color = universe
407 .world
408 .add_component(ColorComponent::rgba(0.0, 0.0, 0.0, 1.0));
409 universe.add(text_color);
410 let _ = universe.attach(text_id, text_color);
411
412 // Stash state for the signal handler.
413 let state = ButtonState {
414 pressed: false,
415 cap_root,
416 cap_raised_z,
417 cap_pressed_z,
418 frame_color,
419 face_color,
420 text_id,
421 text_color,
422 frame_color_up: color,
423 face_color_up: background_color,
424 text_color_up: [0.0, 0.0, 0.0, 1.0],
425 frame_color_down: darken(color, 0.75),
426 face_color_down: darken(background_color, 0.75),
427 text_color_down: [1.0, 1.0, 1.0, 1.0],
428 };
429
430 BUTTONS
431 .get_or_init(|| Mutex::new(HashMap::new()))
432 .lock()
433 .expect("button state lock")
434 .insert(button_root, state);
435
436 button_root
437}
438
439fn main() {
440 mittens_engine::example_support::ensure_model_assets();
441 utils::logger::init();
442
443 let world = engine::ecs::World::default();
444 let mut universe = engine::Universe::new(world);
445
446 // Minimal lit scene + pointer raycaster.
447 use engine::ecs::component::{
448 AmbientLightComponent, BackgroundColorComponent, Camera3DComponent,
449 DirectionalLightComponent, InputComponent, InputTransformModeComponent, PointerComponent,
450 TransformComponent,
451 };
452
453 let bg = universe
454 .world
455 .add_component(BackgroundColorComponent::new());
456 let bg_c = universe
457 .world
458 .add_component(engine::ecs::component::ColorComponent::rgba(
459 0.92, 0.92, 0.96, 1.0,
460 ));
461 let _ = universe.world.add_child(bg, bg_c);
462 universe.add(bg);
463
464 let ambient = universe
465 .world
466 .add_component(AmbientLightComponent::rgb(0.35, 0.35, 0.35));
467 universe.add(ambient);
468
469 let sun_t = universe
470 .world
471 .add_component(TransformComponent::new().with_position(1.0, 1.0, 1.0));
472 let sun = universe
473 .world
474 .add_component(DirectionalLightComponent::new());
475 let _ = universe.attach(sun_t, sun);
476 universe.add(sun_t);
477
478 let input = universe
479 .world
480 .add_component(InputComponent::new().with_speed(2.5));
481 let input_mode = universe.world.add_component(
482 InputTransformModeComponent::forward_z()
483 .with_fps_rotation()
484 .with_roll_axis_y(),
485 );
486 let _ = universe.attach(input, input_mode);
487
488 let rig_t = universe
489 .world
490 .add_component(TransformComponent::new().with_position(0.0, 0.0, 2.5));
491 let _ = universe.attach(input, rig_t);
492
493 let cam = universe
494 .world
495 .add_component(Camera3DComponent::new().with_far(600.0).with_fov(70.0));
496 let _ = universe.attach(rig_t, cam);
497
498 let pointer = universe.world.add_component(PointerComponent::new());
499 let _ = universe.attach(cam, pointer);
500
501 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_t);
502 universe.add(input);
503
504 // The button.
505 let button_root = spawn_button(
506 &mut universe,
507 [0.0, 0.0, 0.0],
508 [1.20, 0.45],
509 ButtonBorder::new(0.025, 0.025, 0.025, 0.025),
510 "click me",
511 0.18,
512 [0.15, 0.15, 0.20, 1.0],
513 [0.85, 0.85, 0.92, 1.0],
514 );
515
516 // A small 3-cube stack to the left of the button for gizmo testing:
517 // [cyan]
518 // [yellow][light brown]
519 //
520 // These live under an EditorComponent subtree so clicking attaches the editor gizmo.
521 {
522 use engine::ecs::component::{EditorComponent, TransformComponent};
523
524 let editor_root = universe.world.add_component(EditorComponent::new());
525
526 // Position the stack in world space (left of the button).
527 let stack_root = universe
528 .world
529 .add_component(TransformComponent::new().with_position(-1.55, 0.0, 0.0));
530 let _ = universe.attach(editor_root, stack_root);
531
532 let cube = 0.22_f32;
533 let gap = 0.04_f32;
534 let step = cube + gap;
535 let y_bottom = -0.5 * step;
536 let y_top = 0.5 * step;
537 let x_left = -0.5 * step;
538 let x_right = 0.5 * step;
539
540 let yellow = [1.0, 0.92, 0.22, 1.0];
541 let light_brown = [0.80, 0.66, 0.46, 1.0];
542 let cyan = [0.20, 0.95, 1.0, 1.0];
543
544 let _bottom_left = spawn_raycastable_colored_cube(
545 &mut universe,
546 stack_root,
547 [x_left, y_bottom, 0.0],
548 [cube, cube, 0.06],
549 yellow,
550 );
551 let _bottom_right = spawn_raycastable_colored_cube(
552 &mut universe,
553 stack_root,
554 [x_right, y_bottom, 0.0],
555 [cube, cube, 0.06],
556 light_brown,
557 );
558 let _top = spawn_raycastable_colored_cube(
559 &mut universe,
560 stack_root,
561 [0.0, y_top, 0.0],
562 [cube, cube, 0.06],
563 cyan,
564 );
565
566 universe.add(editor_root);
567 }
568
569 universe.add_signal_handler(
570 engine::ecs::SignalKind::DragStart,
571 button_root,
572 button_press_handler,
573 );
574 universe.add_signal_handler(
575 engine::ecs::SignalKind::DragEnd,
576 button_root,
577 button_press_handler,
578 );
579
580 universe.systems.process_commands(
581 &mut universe.world,
582 &mut universe.visuals,
583 &mut universe.render_assets,
584 &mut universe.command_queue,
585 );
586
587 universe.enable_repl();
588 engine::Windowing::run_app(universe).expect("Windowing failed");
589}examples/opacity-example.rs (line 22)
12fn spawn_cube(
13 universe: &mut engine::Universe,
14 parent: engine::ecs::ComponentId,
15 position: (f32, f32, f32),
16 scale: (f32, f32, f32),
17 color: Option<(f32, f32, f32, f32)>,
18 opacity: Option<OpacityComponent>,
19) {
20 let t = universe.world.add_component(
21 TransformComponent::new()
22 .with_position(position.0, position.1, position.2)
23 .with_scale(scale.0, scale.1, scale.2),
24 );
25 let r = universe.world.add_component(RenderableComponent::cube());
26
27 let _ = universe.attach(parent, t);
28 let _ = universe.attach(t, r);
29
30 if let Some((cr, cg, cb, ca)) = color {
31 let c = universe
32 .world
33 .add_component(ColorComponent::rgba(cr, cg, cb, ca));
34 let _ = universe.attach(r, c);
35 }
36
37 if let Some(o) = opacity {
38 let o = universe.world.add_component(o);
39 let _ = universe.attach(r, o);
40 }
41}
42
43fn spawn_text_label(universe: &mut engine::Universe, position: (f32, f32, f32), text: &str) {
44 // T_root { T_scale { TXT { filtering } } }
45 let text_root = universe
46 .world
47 .add_component(TransformComponent::new().with_position(position.0, position.1, position.2));
48
49 let text_scale = universe
50 .world
51 .add_component(TransformComponent::new().with_scale(0.18, 0.18, 1.0));
52 let _ = universe.attach(text_root, text_scale);
53
54 let text_c = universe.world.add_component(TextComponent::new(text));
55 let _ = universe.attach(text_scale, text_c);
56
57 // Keep it crisp.
58 let filtering = universe
59 .world
60 .add_component(TextureFilteringComponent::nearest());
61 let _ = universe.attach(text_c, filtering);
62
63 // White label.
64 let color = universe
65 .world
66 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
67 let _ = universe.attach(text_c, color);
68
69 universe.add(text_root);
70}
71
72fn text_block_dimensions(text: &str) -> (usize, usize) {
73 let mut max_cols = 0usize;
74 let mut rows = 1usize;
75 let mut cur = 0usize;
76
77 for ch in text.chars() {
78 if ch == '\n' {
79 max_cols = max_cols.max(cur);
80 cur = 0;
81 rows += 1;
82 } else {
83 cur += 1;
84 }
85 }
86 max_cols = max_cols.max(cur);
87
88 (max_cols.max(1), rows.max(1))
89}
90
91fn spawn_text_label_with_bg(
92 universe: &mut engine::Universe,
93 position: (f32, f32, f32),
94 text: &str,
95 bg_opacity: f32,
96) {
97 // T_root {
98 // T_bg { R_bg { Color black, Opacity } }
99 // T_scale { TXT { filtering } }
100 // }
101
102 let text_root = universe
103 .world
104 .add_component(TransformComponent::new().with_position(position.0, position.1, position.2));
105
106 // Background quad (slightly behind the glyph quads).
107 let (cols, rows) = text_block_dimensions(text);
108 let text_scale = 0.18_f32;
109 let pad_x = 0.55_f32;
110 let pad_y = 0.45_f32;
111 let bg_w = cols as f32 * text_scale + pad_x;
112 let bg_h = rows as f32 * text_scale + pad_y;
113
114 let bg_t = universe.world.add_component(
115 TransformComponent::new()
116 .with_position(1.5, 0.0, -0.02)
117 .with_scale(bg_w, bg_h, 1.0),
118 );
119 let bg_r = universe.world.add_component(RenderableComponent::square());
120 let _ = universe.attach(text_root, bg_t);
121 let _ = universe.attach(bg_t, bg_r);
122
123 let bg_c = universe
124 .world
125 .add_component(ColorComponent::rgba(0.0, 0.0, 0.0, 1.0));
126 let _ = universe.attach(bg_r, bg_c);
127
128 let bg_o = universe
129 .world
130 .add_component(OpacityComponent::new().with_opacity(bg_opacity));
131 let _ = universe.attach(bg_r, bg_o);
132
133 let text_scale_t = universe
134 .world
135 .add_component(TransformComponent::new().with_scale(text_scale, text_scale, 1.0));
136 let _ = universe.attach(text_root, text_scale_t);
137
138 let text_c = universe.world.add_component(TextComponent::new(text));
139 let _ = universe.attach(text_scale_t, text_c);
140
141 // Keep it crisp.
142 let filtering = universe
143 .world
144 .add_component(TextureFilteringComponent::nearest());
145 let _ = universe.attach(text_c, filtering);
146
147 // Force the label into the transparent pass so it layers correctly with the background.
148 // (Pass selection currently does not consider texture alpha.)
149 let color = universe
150 .world
151 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 0.998));
152 let _ = universe.attach(text_c, color);
153
154 universe.add(text_root);
155}
156
157fn spawn_demo_spot(
158 universe: &mut engine::Universe,
159 spot_pos: (f32, f32, f32),
160 opacity: f32,
161 multiple_layers: bool,
162 grid_n: i32,
163) {
164 let spot = universe
165 .world
166 .add_component(TransformComponent::new().with_position(spot_pos.0, spot_pos.1, spot_pos.2));
167
168 // All cubes inherit this color.
169 let white = universe
170 .world
171 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
172 let _ = universe.attach(spot, white);
173
174 // All cubes inherit this opacity (no per-cube OpacityComponent needed).
175 let mut oc = OpacityComponent::new().with_opacity(opacity);
176 if multiple_layers {
177 oc = oc.with_multiple_layers();
178 }
179 let oc = universe.world.add_component(oc);
180 let _ = universe.attach(spot, oc);
181
182 universe.add(spot);
183
184 // Cube grid.
185 let n = grid_n.max(1);
186 let spacing = if n <= 2 { 1.0 } else { 0.6 };
187 let cube_scale = if n <= 2 { 0.8 } else { 0.45 };
188 let half = (n as f32 - 1.0) * spacing * 0.5;
189
190 for z in 0..n {
191 for y in 0..n {
192 for x in 0..n {
193 let px = x as f32 * spacing - half;
194 let py = y as f32 * spacing;
195 let pz = z as f32 * spacing - half;
196
197 spawn_cube(
198 universe,
199 spot,
200 (px, py, pz),
201 (cube_scale, cube_scale, cube_scale),
202 None,
203 None,
204 );
205 }
206 }
207 }
208
209 // Label above.
210 let label = format!(
211 "opacity: {:.2}\nmulti-layer: {}",
212 opacity,
213 if multiple_layers { "true" } else { "false" }
214 );
215 // Keep labels away from the cube volume so they don't intersect.
216 let label_y = spot_pos.1 + (n as f32 * spacing) + 1.8;
217 let label_x = spot_pos.0 - (half + 0.6);
218 let label_z = spot_pos.2 - (half + 1.0);
219 spawn_text_label(universe, (label_x, label_y, label_z), &label);
220}
221
222fn spawn_demo_strip_pair(
223 universe: &mut engine::Universe,
224 spot_pos: (f32, f32, f32),
225 transparent_multiple_layers: bool,
226) {
227 let n_z: i32 = 4;
228 let spacing = 1.0;
229 let cube_scale = 0.8;
230 let half_z = (n_z as f32 - 1.0) * spacing * 0.5;
231
232 // Transparent strip (1x4 along Z).
233 let transparent_root = universe
234 .world
235 .add_component(TransformComponent::new().with_position(spot_pos.0, spot_pos.1, spot_pos.2));
236
237 let white = universe
238 .world
239 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
240 let _ = universe.attach(transparent_root, white);
241
242 let mut oc = OpacityComponent::new().with_opacity(0.50);
243 if transparent_multiple_layers {
244 oc = oc.with_multiple_layers();
245 }
246 let oc = universe.world.add_component(oc);
247 let _ = universe.attach(transparent_root, oc);
248
249 universe.add(transparent_root);
250
251 for z in 0..n_z {
252 let pz = z as f32 * spacing - half_z;
253 spawn_cube(
254 universe,
255 transparent_root,
256 (0.0, 0.0, pz),
257 (cube_scale, cube_scale, cube_scale),
258 None,
259 None,
260 );
261 }
262
263 // Opaque strip to the left, same spacing/scale.
264 let opaque_root = universe
265 .world
266 .add_component(TransformComponent::new().with_position(
267 spot_pos.0 - spacing,
268 spot_pos.1,
269 spot_pos.2,
270 ));
271 let white = universe
272 .world
273 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
274 let _ = universe.attach(opaque_root, white);
275 universe.add(opaque_root);
276
277 for z in 0..n_z {
278 let pz = z as f32 * spacing - half_z;
279 spawn_cube(
280 universe,
281 opaque_root,
282 (0.0, 0.0, pz),
283 (cube_scale, cube_scale, cube_scale),
284 None,
285 None,
286 );
287 }
288
289 let label = format!(
290 "mini: opaque + transparent strip\ntransparent opacity: 0.50\nmulti-layer: {}",
291 if transparent_multiple_layers {
292 "true"
293 } else {
294 "false"
295 }
296 );
297 let label_y = spot_pos.1 + spacing + 1.8;
298 let label_x = spot_pos.0 - (spacing * 2.6);
299 let label_z = spot_pos.2 - (half_z + 1.0);
300 spawn_text_label(universe, (label_x, label_y, label_z), &label);
301}
302
303fn spawn_demo_xy_plane(
304 universe: &mut engine::Universe,
305 spot_pos: (f32, f32, f32),
306 opacity: f32,
307 n_x: i32,
308 n_y: i32,
309 z: f32,
310) {
311 let spot = universe
312 .world
313 .add_component(TransformComponent::new().with_position(spot_pos.0, spot_pos.1, spot_pos.2));
314
315 let white = universe
316 .world
317 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
318 let _ = universe.attach(spot, white);
319
320 let oc = universe
321 .world
322 .add_component(OpacityComponent::new().with_opacity(opacity));
323 let _ = universe.attach(spot, oc);
324
325 universe.add(spot);
326
327 let nx = n_x.max(1);
328 let ny = n_y.max(1);
329 let spacing = 0.45;
330 let cube_scale = 0.35;
331 let half_x = (nx as f32 - 1.0) * spacing * 0.5;
332
333 for y in 0..ny {
334 for x in 0..nx {
335 let px = x as f32 * spacing - half_x;
336 let py = y as f32 * spacing;
337 spawn_cube(
338 universe,
339 spot,
340 (px, py, z),
341 (cube_scale, cube_scale, cube_scale),
342 None,
343 None,
344 );
345 }
346 }
347
348 // Label in front of the plane.
349 let label = format!(
350 "XY plane: {}x{}\nopacity: {:.2}\nmulti-layer: false",
351 nx, ny, opacity
352 );
353 let label_x = spot_pos.0 - (half_x + 0.6);
354 let label_y = spot_pos.1 + (ny as f32 * spacing) + 1.2;
355 let label_z = spot_pos.2 - 2.5;
356 spawn_text_label_with_bg(universe, (label_x, label_y, label_z), &label, 0.50);
357}
358
359fn main() {
360 mittens_engine::example_support::ensure_model_assets();
361 utils::logger::init();
362
363 let world = engine::ecs::World::default();
364 let mut universe = engine::Universe::new(world);
365
366 // Dark brown / pink background.
367 let bg = universe
368 .world
369 .add_component(BackgroundColorComponent::new());
370 let bg_c = universe
371 .world
372 .add_component(ColorComponent::rgba(0.22, 0.08, 0.10, 1.0));
373 let _ = universe.world.add_child(bg, bg_c);
374 universe.add(bg);
375
376 // Ambient so unlit areas aren't black.
377 let ambient = universe
378 .world
379 .add_component(AmbientLightComponent::rgb(0.35, 0.35, 0.40));
380 universe.add(ambient);
381
382 // A bright overhead light.
383 let light_t = universe.world.add_component(
384 TransformComponent::new()
385 .with_position(0.0, 18.0, 10.0)
386 .with_scale(0.2, 0.2, 0.2),
387 );
388 let light = universe.world.add_component(
389 PointLightComponent::new()
390 .with_color(1.0, 1.0, 1.0)
391 .with_intensity(1.6)
392 .with_distance(80.0),
393 );
394 let _ = universe.attach(light_t, light);
395 universe.add(light_t);
396
397 // --- Camera rig ---
398 // I { T { C3D { with_fps_rotation with_roll_axis_y } } }
399 let input = universe
400 .world
401 .add_component(InputComponent::new().with_speed(3.0));
402 let input_mode = universe.world.add_component(
403 InputTransformModeComponent::forward_z()
404 .with_roll_axis_y()
405 .with_fps_rotation(),
406 );
407 let _ = universe.attach(input, input_mode);
408
409 let rig_transform = universe
410 .world
411 .add_component(TransformComponent::new().with_position(0.0, 6.0, 20.0));
412 let _ = universe.attach(input, rig_transform);
413
414 let camera = universe.world.add_component(Camera3DComponent::new());
415 let _ = universe.attach(rig_transform, camera);
416
417 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
418 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
419
420 universe.add(input);
421
422 // --- Ground ---
423 let ground = universe.world.add_component(
424 TransformComponent::new()
425 .with_position(0.0, -0.6, 0.0)
426 .with_scale(60.0, 1.0, 60.0),
427 );
428 let ground_r = universe.world.add_component(RenderableComponent::cube());
429 let ground_c = universe
430 .world
431 .add_component(ColorComponent::rgba(0.95, 0.95, 0.95, 1.0));
432 let _ = universe.attach(ground, ground_r);
433 let _ = universe.attach(ground_r, ground_c);
434 universe.add(ground);
435
436 // --- Opaque yellow cubes behind (contrast reference) ---
437 for i in 0..6 {
438 let x = -10.0 + i as f32 * 4.0;
439 spawn_cube(
440 &mut universe,
441 ground,
442 (x, 1.2, -18.0),
443 (2.0, 2.0, 2.0),
444 Some((1.0, 0.92, 0.15, 1.0)),
445 None,
446 );
447 }
448
449 // --- Demo spots ---
450 // Left of the left big spot: a single-layer transparent XY plane (16x16).
451 spawn_demo_xy_plane(&mut universe, (-14.0, 0.0, 0.0), 0.50, 16, 16, 0.0);
452
453 // Big spots: 8x8x8
454 // Left: single-layer transparent (instanced)
455 spawn_demo_spot(&mut universe, (-4.0, 0.0, 0.0), 0.50, false, 8);
456
457 // Right: multi-layer transparent (sorted)
458 spawn_demo_spot(&mut universe, (4.0, 0.0, 0.0), 0.50, true, 8);
459
460 // Small spots: opaque 1x4 strip + transparent 1x4 strip (along Z).
461 spawn_demo_strip_pair(&mut universe, (-4.0, 0.0, 10.0), false);
462 spawn_demo_strip_pair(&mut universe, (4.0, 0.0, 10.0), true);
463
464 // Process init-time registrations.
465 universe.systems.process_commands(
466 &mut universe.world,
467 &mut universe.visuals,
468 &mut universe.render_assets,
469 &mut universe.command_queue,
470 );
471
472 universe.enable_repl();
473
474 engine::Windowing::run_app(universe).expect("Windowing failed");
475}examples/animation-for-topology.rs (line 29)
20fn spawn_emissive_marker_cube(
21 universe: &mut engine::Universe,
22 parent: engine::ecs::ComponentId,
23 local_pos: (f32, f32, f32),
24 scale: f32,
25 rgba: [f32; 4],
26) {
27 let tx = universe.world.add_component(
28 engine::ecs::component::TransformComponent::new()
29 .with_position(local_pos.0, local_pos.1, local_pos.2)
30 .with_scale(scale, scale, scale),
31 );
32 let r = universe
33 .world
34 .add_component(engine::ecs::component::RenderableComponent::cube());
35 let c = universe
36 .world
37 .add_component(engine::ecs::component::ColorComponent::rgba(
38 rgba[0], rgba[1], rgba[2], rgba[3],
39 ));
40 let e = universe
41 .world
42 .add_component(engine::ecs::component::EmissiveComponent::on());
43
44 let _ = universe.attach(parent, tx);
45 let _ = universe.attach(tx, r);
46 let _ = universe.attach(r, c);
47 let _ = universe.attach(r, e);
48}
49
50/// Create a cube subtree ahead-of-time (not attached to the scene).
51///
52/// Returns the root `TransformComponent` id.
53fn spawn_detached_cube_prefab(
54 universe: &mut engine::Universe,
55 scale: f32,
56 rgba: [f32; 4],
57) -> engine::ecs::ComponentId {
58 let tx = universe.world.add_component(
59 engine::ecs::component::TransformComponent::new().with_scale(scale, scale, scale),
60 );
61 let r = universe
62 .world
63 .add_component(engine::ecs::component::RenderableComponent::cube());
64 let c = universe
65 .world
66 .add_component(engine::ecs::component::ColorComponent::rgba(
67 rgba[0], rgba[1], rgba[2], rgba[3],
68 ));
69 let e = universe
70 .world
71 .add_component(engine::ecs::component::EmissiveComponent::on());
72
73 let _ = universe.attach(tx, r);
74 let _ = universe.attach(r, c);
75 let _ = universe.attach(r, e);
76
77 tx
78}
79
80fn main() {
81 mittens_engine::example_support::ensure_model_assets();
82 utils::logger::init();
83
84 let world = engine::ecs::World::default();
85 let mut universe = engine::Universe::new(world);
86
87 // Minimal scene with a camera so the window opens.
88 let clear = universe
89 .world
90 .add_component(engine::ecs::component::BackgroundColorComponent::new());
91 let clear_c = universe
92 .world
93 .add_component(engine::ecs::component::ColorComponent::rgba(
94 0.06, 0.06, 0.07, 1.0,
95 ));
96 let _ = universe.world.add_child(clear, clear_c);
97 universe.add(clear);
98
99 // Input-driven camera rig.
100 let input = universe
101 .world
102 .add_component(engine::ecs::component::InputComponent::new().with_speed(2.0));
103 let rig_transform = universe.world.add_component(
104 engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 9.0),
105 );
106 let input_mode = universe.world.add_component(
107 engine::ecs::component::InputTransformModeComponent::forward_z().with_roll_axis_y(),
108 );
109 let camera3d = universe.world.add_component(
110 engine::ecs::component::Camera3DComponent::new()
111 .with_far(250.0)
112 .with_fov(70.0),
113 );
114 let _ = universe.attach(input, input_mode);
115 let _ = universe.attach(input, rig_transform);
116 let _ = universe.attach(rig_transform, camera3d);
117
118 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
119 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
120 universe.add(input);
121
122 // Light so we can see non-emissive materials.
123 let light_tx = universe.world.add_component(
124 engine::ecs::component::TransformComponent::new().with_position(2.0, 3.5, 6.0),
125 );
126 let light = universe.world.add_component(
127 engine::ecs::component::PointLightComponent::new()
128 .with_distance(30.0)
129 .with_color(1.0, 1.0, 1.0),
130 );
131 let _ = universe.attach(light_tx, light);
132 universe.add(light_tx);
133
134 // ClockComponent drives the animation timeline in beats.
135 let clock = universe
136 .world
137 .add_component(engine::ecs::component::ClockComponent::new().with_bpm(140.0));
138 universe.add(clock);
139
140 // Root for all visualization objects.
141 let viz_root = universe.world.add_component(
142 engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 0.0),
143 );
144 universe.add(viz_root);
145
146 let anchor_count = 16usize;
147 let cube_pool_size = 8usize;
148
149 // Three grids side-by-side.
150 let layout_a = GridLayout {
151 origin: (-6.0, 0.0, 0.0),
152 spacing: 1.1,
153 };
154 let layout_b = GridLayout {
155 origin: (0.0, 0.0, 0.0),
156 spacing: 1.1,
157 };
158 let layout_c = GridLayout {
159 origin: (6.0, 0.0, 0.0),
160 spacing: 1.1,
161 };
162
163 // --- Grid A: detach + re-attach (reparent) ---
164 let grid_a_root = universe
165 .world
166 .add_component(engine::ecs::component::TransformComponent::new());
167 let _ = universe.attach(viz_root, grid_a_root);
168
169 let mut anchors_a: Vec<engine::ecs::ComponentId> = Vec::with_capacity(anchor_count);
170 for i in 0..anchor_count {
171 let (x, y, z) = grid_anchor_local(layout_a, i);
172 let anchor = universe.world.add_component(
173 engine::ecs::component::TransformComponent::new().with_position(x, y, z),
174 );
175 let _ = universe.attach(grid_a_root, anchor);
176 anchors_a.push(anchor);
177 spawn_emissive_marker_cube(
178 &mut universe,
179 anchor,
180 (0.0, -0.35, 0.0),
181 0.06,
182 [0.18, 0.18, 0.22, 1.0],
183 );
184 }
185
186 let mut cubes_a: Vec<engine::ecs::ComponentId> = Vec::with_capacity(cube_pool_size);
187 for i in 0..cube_pool_size {
188 let t = (i as f32) / ((cube_pool_size - 1) as f32).max(1.0);
189 let rgba = [0.10, 0.40 + 0.50 * t, 0.90 - 0.70 * t, 1.0];
190 cubes_a.push(spawn_detached_cube_prefab(&mut universe, 0.22, rgba));
191 }
192
193 let anim_a = universe
194 .world
195 .add_component(engine::ecs::component::AnimationComponent::new());
196 for i in 0..anchor_count {
197 let cube = cubes_a[i % cube_pool_size];
198 let parent = anchors_a[i];
199
200 // Explicit detach+attach to test topology changes via animations.
201
202 // Put them on separate keyframes so ordering is time-deterministic.
203 let beat_detach = i as f64;
204 let beat_attach = i as f64 + 0.05;
205
206 let kf_detach = universe
207 .world
208 .add_component(engine::ecs::component::KeyframeComponent::new(beat_detach));
209 let _ = universe.attach(anim_a, kf_detach);
210 let detach_action =
211 universe
212 .world
213 .add_component(engine::ecs::component::ActionComponent::new(
214 engine::ecs::IntentValue::Detach {
215 component_ids: vec![cube],
216 },
217 ));
218 let _ = universe.attach(kf_detach, detach_action);
219
220 let kf_attach = universe
221 .world
222 .add_component(engine::ecs::component::KeyframeComponent::new(beat_attach));
223 let _ = universe.attach(anim_a, kf_attach);
224 let attach_action =
225 universe
226 .world
227 .add_component(engine::ecs::component::ActionComponent::new(
228 engine::ecs::IntentValue::Attach {
229 parents: vec![parent],
230 child: cube,
231 },
232 ));
233 let _ = universe.attach(kf_attach, attach_action);
234 }
235 universe.add(anim_a);
236
237 // --- Grid B: continuously spawn (attach_clone) + delete-behind (remove_child) ---
238 //
239 // Uses the new actions:
240 // - `Action::attach_clone(parent, prefab_root)`
241 // - `Action::remove_child(parent, index)`
242 //
243 // This avoids needing a pre-built pool of cube ComponentIds.
244 let grid_b_root = universe
245 .world
246 .add_component(engine::ecs::component::TransformComponent::new());
247 let _ = universe.attach(viz_root, grid_b_root);
248
249 let mut anchors_b: Vec<engine::ecs::ComponentId> = Vec::with_capacity(anchor_count);
250 for i in 0..anchor_count {
251 let (x, y, z) = grid_anchor_local(layout_b, i);
252 let anchor = universe.world.add_component(
253 engine::ecs::component::TransformComponent::new().with_position(x, y, z),
254 );
255 let _ = universe.attach(grid_b_root, anchor);
256 anchors_b.push(anchor);
257
258 // Marker is attached under the grid root (not under the anchor), so anchor child index 0
259 // remains reserved for the dynamic cube subtree.
260 spawn_emissive_marker_cube(
261 &mut universe,
262 grid_b_root,
263 (x, y - 0.35, z),
264 0.06,
265 [0.22, 0.18, 0.18, 1.0],
266 );
267 }
268
269 // Detached prefab that will be cloned on demand (reddish cube).
270 let prefab_b = spawn_detached_cube_prefab(&mut universe, 0.20, [0.90, 0.25, 0.15, 1.0]);
271
272 let steps_b = 32usize;
273 let window = 8usize;
274
275 let anim_b = universe
276 .world
277 .add_component(engine::ecs::component::AnimationComponent::new());
278
279 // Phase 1: each beat, spawn a cube under an anchor; delete-behind by removing child(0).
280 for i in 0..steps_b {
281 let parent = anchors_b[i % anchor_count];
282
283 let beat_attach = i as f64;
284 let beat_remove = i as f64 + 0.05;
285
286 let kf_attach = universe
287 .world
288 .add_component(engine::ecs::component::KeyframeComponent::new(beat_attach));
289 let _ = universe.attach(anim_b, kf_attach);
290
291 let attach_action =
292 universe
293 .world
294 .add_component(engine::ecs::component::ActionComponent::new(
295 engine::ecs::IntentValue::AttachClone {
296 parents: vec![parent],
297 prefab_root: prefab_b,
298 },
299 ));
300 let _ = universe.attach(kf_attach, attach_action);
301
302 if i >= window {
303 let remove_parent = anchors_b[(i - window) % anchor_count];
304 let kf_remove = universe
305 .world
306 .add_component(engine::ecs::component::KeyframeComponent::new(beat_remove));
307 let _ = universe.attach(anim_b, kf_remove);
308
309 let remove_action =
310 universe
311 .world
312 .add_component(engine::ecs::component::ActionComponent::new(
313 engine::ecs::IntentValue::RemoveChild {
314 parents: vec![remove_parent],
315 index: 0,
316 },
317 ));
318 let _ = universe.attach(kf_remove, remove_action);
319 }
320 }
321
322 // Phase 2: cleanup tail so the loop doesn't accumulate cubes.
323 for j in 0..window {
324 let remove_parent = anchors_b[(steps_b - window + j) % anchor_count];
325 let beat_remove = steps_b as f64 + j as f64 + 0.05;
326
327 let kf_remove = universe
328 .world
329 .add_component(engine::ecs::component::KeyframeComponent::new(beat_remove));
330 let _ = universe.attach(anim_b, kf_remove);
331
332 let remove_action =
333 universe
334 .world
335 .add_component(engine::ecs::component::ActionComponent::new(
336 engine::ecs::IntentValue::RemoveChild {
337 parents: vec![remove_parent],
338 index: 0,
339 },
340 ));
341 let _ = universe.attach(kf_remove, remove_action);
342 }
343
344 universe.add(anim_b);
345
346 // --- Grid C: move cubes via set_position (no topology changes) ---
347 let grid_c_root = universe
348 .world
349 .add_component(engine::ecs::component::TransformComponent::new());
350 let _ = universe.attach(viz_root, grid_c_root);
351
352 let mut anchors_c: Vec<(f32, f32, f32)> = Vec::with_capacity(anchor_count);
353 for i in 0..anchor_count {
354 let (x, y, z) = grid_anchor_local(layout_c, i);
355 anchors_c.push((x, y, z));
356
357 let anchor = universe.world.add_component(
358 engine::ecs::component::TransformComponent::new().with_position(x, y, z),
359 );
360 let _ = universe.attach(grid_c_root, anchor);
361 spawn_emissive_marker_cube(
362 &mut universe,
363 anchor,
364 (0.0, -0.35, 0.0),
365 0.06,
366 [0.18, 0.22, 0.18, 1.0],
367 );
368 }
369
370 let mut cubes_c: Vec<engine::ecs::ComponentId> = Vec::with_capacity(cube_pool_size);
371 for i in 0..cube_pool_size {
372 let t = (i as f32) / ((cube_pool_size - 1) as f32).max(1.0);
373 let rgba = [0.25, 0.85 - 0.55 * t, 0.25 + 0.55 * t, 1.0];
374 let cube_root = spawn_detached_cube_prefab(&mut universe, 0.22, rgba);
375 let _ = universe.attach(grid_c_root, cube_root);
376 cubes_c.push(cube_root);
377 }
378
379 let anim_c = universe
380 .world
381 .add_component(engine::ecs::component::AnimationComponent::new());
382 for i in 0..anchor_count {
383 let kf = universe
384 .world
385 .add_component(engine::ecs::component::KeyframeComponent::new(i as f64));
386 let _ = universe.attach(anim_c, kf);
387
388 let cube = cubes_c[i % cube_pool_size];
389 let (x, y, z) = anchors_c[i];
390 let setpos_action =
391 universe
392 .world
393 .add_component(engine::ecs::component::ActionComponent::new(
394 engine::ecs::IntentValue::SetPosition {
395 component_ids: vec![cube],
396 position: [x, y, z],
397 },
398 ));
399 let _ = universe.attach(kf, setpos_action);
400 }
401 universe.add(anim_c);
402
403 universe.systems.process_commands(
404 &mut universe.world,
405 &mut universe.visuals,
406 &mut universe.render_assets,
407 &mut universe.command_queue,
408 );
409
410 engine::Windowing::run_app(universe).expect("Windowing failed");
411}examples/example_util/mod.rs (line 45)
15pub fn spawn_mms_demo_rig(
16 universe: &mut engine::Universe,
17 cam_pos: [f32; 3],
18) -> engine::ecs::ComponentId {
19 use engine::ecs::component::{
20 BackgroundColorComponent, Camera3DComponent, InputComponent, InputTransformModeComponent,
21 TransformComponent,
22 };
23
24 // Dark blue clear colour.
25 let bg_color = universe
26 .world
27 .add_component(BackgroundColorComponent::new());
28 let bg_color_c = universe
29 .world
30 .add_component(ColorComponent::rgba(0.02, 0.03, 0.10, 1.0));
31 let _ = universe.world.add_child(bg_color, bg_color_c);
32 universe.add(bg_color);
33
34 // Camera rig: Input → Transform → Camera3D.
35 let input = universe
36 .world
37 .add_component(InputComponent::new().with_speed(3.0));
38 let input_mode = universe
39 .world
40 .add_component(InputTransformModeComponent::forward_z().with_roll_axis_y());
41 let _ = universe.attach(input, input_mode);
42
43 let cam_transform = universe
44 .world
45 .add_component(TransformComponent::new().with_position(cam_pos[0], cam_pos[1], cam_pos[2]));
46 let _ = universe.attach(input, cam_transform);
47
48 let camera = universe.world.add_component(Camera3DComponent::new());
49 let _ = universe.attach(cam_transform, camera);
50
51 universe.add(input);
52
53 spawn_desktop_camera_controls_hint(universe, cam_transform);
54
55 cam_transform
56}
57
58fn hash_u32(mut x: u32) -> u32 {
59 x ^= x >> 16;
60 x = x.wrapping_mul(0x7feb_352d);
61 x ^= x >> 15;
62 x = x.wrapping_mul(0x846c_a68b);
63 x ^= x >> 16;
64 x
65}
66
67fn rand01(seed: u32) -> f32 {
68 (hash_u32(seed) as f32) / (u32::MAX as f32)
69}
70
71#[derive(Debug, Clone, Copy)]
72pub struct CloudRingParams {
73 pub cloud_count: u32,
74 pub radius: f32,
75 pub center_y: f32,
76 pub puffs_per_cloud: u32,
77 /// 0.0 = perfectly evenly spaced, 1.0 = up to one full step of jitter.
78 pub angle_jitter: f32,
79 /// Probability a given cluster is placed higher than `center_y`.
80 pub high_y_probability: f32,
81 /// Multiplier applied to `center_y` when a cluster is chosen to be high.
82 pub high_y_multiplier: f32,
83 /// Seed used for deterministic layout variation.
84 pub seed: u32,
85}
86
87impl Default for CloudRingParams {
88 fn default() -> Self {
89 Self {
90 cloud_count: 5,
91 radius: 26.0,
92 center_y: 2.0,
93 puffs_per_cloud: 28,
94 angle_jitter: 0.0,
95 high_y_probability: 0.0,
96 high_y_multiplier: 1.0,
97 seed: 0xC10u32,
98 }
99 }
100}
101
102/// Spawns a ring of "cloud" puff clusters under an existing background root.
103///
104/// Assumes `bg_root` is a `BackgroundComponent` (usually `with_occlusion_and_lighting()`).
105pub fn spawn_cloud_ring(
106 universe: &mut engine::Universe,
107 bg_root: engine::ecs::ComponentId,
108 p: CloudRingParams,
109) {
110 if p.cloud_count == 0 || p.radius == 0.0 {
111 return;
112 }
113
114 let cube_mesh = universe
115 .render_assets
116 .get_mesh(engine::graphics::BuiltinMeshType::Cube);
117
118 let step = std::f32::consts::TAU / (p.cloud_count as f32);
119 let angle_jitter = p.angle_jitter.clamp(0.0, 1.0);
120 let high_y_probability = p.high_y_probability.clamp(0.0, 1.0);
121
122 for i in 0..p.cloud_count {
123 let seed_i = p.seed ^ i.wrapping_mul(0x9E37_79B9);
124 let jitter = (rand01(seed_i ^ 0xa53a_9d2d) - 0.5) * step * angle_jitter;
125 let a = (i as f32) * step + jitter;
126 let cx = p.radius * a.cos();
127 let cz = p.radius * a.sin();
128
129 let cy = if rand01(seed_i ^ 0x7f4a_7c15) < high_y_probability {
130 p.center_y * p.high_y_multiplier
131 } else {
132 p.center_y
133 };
134
135 let center_tx = universe.world.add_component(
136 engine::ecs::component::TransformComponent::new().with_position(cx, cy, cz),
137 );
138 let _ = universe.attach(bg_root, center_tx);
139
140 let base_seed = seed_i ^ 0x243f_6a88;
141 for puff_i in 0..p.puffs_per_cloud {
142 let seed = base_seed ^ puff_i.wrapping_mul(1_103_515_245);
143
144 let ox = (rand01(seed ^ 0x68bc_21eb) - 0.5) * 9.0;
145 let oy = (rand01(seed ^ 0x02e5_be93) - 0.5) * 4.0;
146 let oz = (rand01(seed ^ 0xa1d3_4f2b) - 0.5) * 9.0;
147
148 let base = 0.7 + rand01(seed ^ 0x9e37_79b9) * 2.8;
149 let sx = base * (0.7 + rand01(seed ^ 0x243f_6a88) * 0.9);
150 let sy = base * (0.6 + rand01(seed ^ 0x85a3_08d3) * 1.0);
151 let sz = base * (0.7 + rand01(seed ^ 0x1319_8a2e) * 0.9);
152
153 let tx = universe.world.add_component(
154 engine::ecs::component::TransformComponent::new()
155 .with_position(ox, oy, oz)
156 .with_scale(sx, sy, sz),
157 );
158 let renderable =
159 universe
160 .world
161 .add_component(engine::ecs::component::RenderableComponent::new(
162 engine::graphics::primitives::Renderable::new(
163 cube_mesh,
164 engine::graphics::primitives::MaterialHandle::TOON_MESH,
165 ),
166 ));
167
168 let t = rand01(seed ^ 0x7f4a_7c15);
169 let r = 0.70 + 0.10 * t;
170 let g = 0.72 + 0.10 * t;
171 let b = 0.80 + 0.12 * t;
172 let color = universe
173 .world
174 .add_component(engine::ecs::component::ColorComponent::rgba(r, g, b, 1.0));
175
176 let _ = universe.attach(center_tx, tx);
177 let _ = universe.attach(tx, renderable);
178 let _ = universe.attach(renderable, color);
179 }
180 }
181}
182
183/// Spawn a small camera-attached help text for desktop controls.
184///
185/// The returned component is the hint root `TransformComponent`.
186///
187/// This is intended for examples that use the common topology:
188///
189/// `I { T { C3D } }` (InputComponent → TransformComponent → Camera3DComponent)
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}examples/simple-demo.rs (line 35)
6fn build_demo_scene_7_shapes(universe: &mut engine::Universe) {
7 use engine::ecs::component::{
8 Camera3DComponent, ColorComponent, EmissiveComponent, GLTFComponent, InputComponent,
9 InputTransformModeComponent, PointLightComponent, RenderableComponent, TextureComponent,
10 TransformComponent,
11 };
12 use engine::graphics::BuiltinMeshType;
13 use engine::graphics::primitives::MaterialHandle;
14
15 // Built-in CPU meshes are pre-registered; just fetch stable handles.
16 let tri_mesh = universe.render_assets.get_mesh(BuiltinMeshType::Triangle2D);
17 let square_mesh = universe.render_assets.get_mesh(BuiltinMeshType::Quad2D);
18 let tetra_mesh = universe
19 .render_assets
20 .get_mesh(BuiltinMeshType::Tetrahedron);
21
22 fn spawn(
23 universe: &mut engine::Universe,
24 mesh: engine::graphics::primitives::CpuMeshHandle,
25 x: f32,
26 y: f32,
27 s: f32,
28 r: f32,
29 color: [f32; 4],
30 input_driven: bool,
31 emissive: bool,
32 ) -> engine::ecs::ComponentId {
33 let transform = universe.world.add_component(
34 TransformComponent::new()
35 .with_position(x, y, 0.0)
36 .with_scale(s, s, 1.0)
37 .with_rotation_euler(0.0, 0.0, r),
38 );
39 let renderable = universe.world.add_component(RenderableComponent::new(
40 engine::graphics::primitives::Renderable::new(mesh, MaterialHandle::TOON_MESH),
41 ));
42 let color_c = universe.world.add_component(ColorComponent { rgba: color });
43
44 if emissive {
45 let emissive_c = universe.world.add_component(EmissiveComponent::on());
46 let _ = universe.attach(renderable, emissive_c);
47 }
48
49 // Topology: (optional Input) -> Transform -> Renderable
50 let _ = universe.attach(transform, renderable);
51 let _ = universe.attach(renderable, color_c);
52
53 if input_driven {
54 let input = universe
55 .world
56 .add_component(InputComponent::new().with_speed(0.5));
57 let _ = universe.attach(input, transform);
58 universe.add(input);
59 } else {
60 universe.add(transform);
61 }
62
63 transform
64 }
65
66 fn spawn_3d(
67 universe: &mut engine::Universe,
68 mesh: engine::graphics::primitives::CpuMeshHandle,
69 x: f32,
70 y: f32,
71 z: f32,
72 s: f32,
73 rx: f32,
74 ry: f32,
75 rz: f32,
76 color: [f32; 4],
77 ) -> engine::ecs::ComponentId {
78 let transform = universe.world.add_component(
79 TransformComponent::new()
80 .with_position(x, y, z)
81 .with_scale(s, s, s)
82 .with_rotation_euler(rx, ry, rz),
83 );
84 let renderable = universe.world.add_component(RenderableComponent::new(
85 engine::graphics::primitives::Renderable::new(mesh, MaterialHandle::TOON_MESH),
86 ));
87 let color_c = universe.world.add_component(ColorComponent { rgba: color });
88
89 let _ = universe.attach(transform, renderable);
90 let _ = universe.attach(renderable, color_c);
91 universe.add(transform);
92
93 transform
94 }
95
96 // Spawn shapes.
97 // One triangle is input-driven (WASD/QE). Build a small "rig" so both the triangle
98 // and the camera can be driven by the same InputComponent.
99
100 // Topology: Input -> (InputTransformMode) -> RigTransform -> (CameraTransform -> Camera3D), (TriRootTransform -> ...)
101 let tri_input = universe
102 .world
103 .add_component(InputComponent::new().with_speed(0.5));
104 let input_mode = universe
105 .world
106 .add_component(InputTransformModeComponent::forward_z().with_roll_axis_y());
107 let _ = universe.attach(tri_input, input_mode);
108
109 // Start pulled back so the demo meshes at z=0 are in view.
110 // The camera will be attached directly under this transform, so there is no local
111 // camera offset that would cause orbiting when yawing.
112 let rig_transform = universe
113 .world
114 .add_component(TransformComponent::new().with_position(0.0, 0.0, 2.5));
115 let _ = universe.attach(tri_input, rig_transform);
116
117 // Camera: attached directly to the rig transform.
118 let camera3d = universe.world.add_component(Camera3DComponent::new());
119 let _ = universe.attach(rig_transform, camera3d);
120
121 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
122 example_util::spawn_desktop_camera_controls_hint(universe, rig_transform);
123
124 let tri_root_transform = universe
125 .world
126 .add_component(TransformComponent::new().with_position(0.5, 0.50, 0.0));
127
128 // Visual transform under the root; this is where we apply rotation/scale.
129 let tri_visual_transform = universe.world.add_component(
130 TransformComponent::new()
131 .with_scale(0.30, 0.30, 1.0)
132 .with_rotation_euler(0.0, 0.0, (2.0 * 3.14159 / 3.0) + 3.14159),
133 );
134 let tri_renderable = universe.world.add_component(RenderableComponent::new(
135 engine::graphics::primitives::Renderable::new(tri_mesh, MaterialHandle::TOON_MESH),
136 ));
137 let tri_color = universe
138 .world
139 .add_component(ColorComponent::rgba(0.2, 1.0, 0.2, 1.0));
140
141 let _ = universe.attach(rig_transform, tri_root_transform);
142 let _ = universe.attach(tri_root_transform, tri_visual_transform);
143 let _ = universe.attach(tri_visual_transform, tri_renderable);
144 let _ = universe.attach(tri_renderable, tri_color);
145
146 let tri_light = universe.world.add_component(
147 PointLightComponent::new()
148 .with_distance(10.0)
149 .with_color(1.0, 1.0, 1.0),
150 );
151
152 let light_transform = universe.world.add_component(
153 TransformComponent::new()
154 .with_position(0.5, 0.50, 1.0)
155 .with_scale(0.1, 0.1, 0.1),
156 );
157
158 let _ = universe.attach(light_transform, tri_light);
159
160 universe.add(tri_input);
161 universe.add(light_transform);
162
163 spawn(
164 universe,
165 square_mesh,
166 -0.80,
167 -0.30,
168 0.25,
169 0.0,
170 [1.0, 0.2, 0.2, 1.0],
171 false,
172 true,
173 );
174 spawn(
175 universe,
176 square_mesh,
177 -0.40,
178 -0.30,
179 0.25,
180 0.0,
181 [1.0, 0.6, 0.2, 1.0],
182 false,
183 true,
184 );
185
186 // 3D primitive: tetrahedron.
187 spawn_3d(
188 universe,
189 tetra_mesh,
190 0.55,
191 -0.15,
192 0.0,
193 0.35,
194 0.75,
195 0.55,
196 0.0,
197 [0.2, 0.7, 1.0, 1.0],
198 );
199 spawn(
200 universe,
201 square_mesh,
202 0.00,
203 -0.30,
204 0.25,
205 0.0,
206 [1.0, 1.0, 0.2, 1.0],
207 false,
208 true,
209 );
210 spawn(
211 universe,
212 square_mesh,
213 0.40,
214 -0.30,
215 0.25,
216 0.0,
217 [0.2, 0.6, 1.0, 1.0],
218 false,
219 true,
220 );
221 spawn(
222 universe,
223 square_mesh,
224 0.80,
225 -0.30,
226 0.25,
227 0.0,
228 [0.8, 0.2, 1.0, 1.0],
229 false,
230 true,
231 );
232 spawn(
233 universe,
234 tri_mesh,
235 0.30,
236 0.35,
237 0.30,
238 -3.14159,
239 [1.0, 1.0, 1.0, 1.0],
240 false,
241 false,
242 );
243
244 // Textured square.
245 let tex_transform = universe.world.add_component(
246 TransformComponent::new()
247 .with_position(0.0, 0.1, 0.0)
248 .with_scale(0.45, 0.45, 1.0),
249 );
250 let tex_renderable = universe.world.add_component(RenderableComponent::new(
251 engine::graphics::primitives::Renderable::new(square_mesh, MaterialHandle::TOON_MESH),
252 ));
253 let tex_color = universe
254 .world
255 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
256 let tex = universe.world.add_component(TextureComponent::from_dds(
257 "assets/textures/cat-face-amused.dds",
258 ));
259
260 let _ = universe.attach(tex_transform, tex_renderable);
261 let _ = universe.attach(tex_renderable, tex_color);
262 let _ = universe.attach(tex_renderable, tex);
263 universe.add(tex_transform);
264
265 // glTF: color-cat
266 // Attach GLTFComponent under a Transform so GLTFSystem can use it as an anchor.
267 let cat_anchor = universe.world.add_component(
268 TransformComponent::new()
269 .with_position(0.0, -0.10, -4.0)
270 .with_scale(0.50, 0.50, 0.50)
271 .with_rotation_euler(0.0, 0.0, 0.0),
272 );
273 let cat_gltf = universe
274 .world
275 .add_component(GLTFComponent::new("assets/models/color-cat.2.glb"));
276 let _ = universe.attach(cat_anchor, cat_gltf);
277 universe.add(cat_anchor);
278}Additional examples can be found in:
- examples/pointer-events.rs
- examples/diy-panel.rs
- examples/util/mod.rs
- examples/vr-input.rs
- examples/triage/panel-pierce.rs
- examples/scrolling.rs
- examples/audio-clip-instance-demo.rs
- examples/audio-music-demo.rs
- examples/text-animation.rs
- examples/vtuber-example.rs
- examples/openxr.rs
- examples/text-example.rs
- examples/gestures-and-gizmos.rs
- examples/background-example.rs
- examples/mesh-factory-example.rs
- examples/bisket-vr-demo.rs
- examples/background-occlusion-example.rs
- examples/raycast-topology-animation.rs
- examples/font-example.rs
- examples/vtuber-joints-example.rs
- examples/animation-example.rs
- examples/folder-text.rs
- examples/collision-perimeter.rs
- examples/audio-graph-example.rs
- examples/gravity-fields.rs
Sourcepub fn with_scale(self, x: f32, y: f32, z: f32) -> Self
pub fn with_scale(self, x: f32, y: f32, z: f32) -> Self
Examples found in repository?
examples/animation-example.rs (line 105)
101 fn spawn_text(universe: &mut engine::Universe, pos: (f32, f32, f32), scale: f32, text: &str) {
102 let tx = universe.world.add_component(
103 engine::ecs::component::TransformComponent::new()
104 .with_position(pos.0, pos.1, pos.2)
105 .with_scale(scale, scale, 1.0),
106 );
107 let t =
108 universe
109 .world
110 .add_component(engine::ecs::component::TextComponent::with_word_wrap(
111 text, 38,
112 ));
113 let _ = universe.attach(tx, t);
114 universe.add(tx);
115 }
116
117 fn spawn_emissive_cube(
118 universe: &mut engine::Universe,
119 parent: engine::ecs::ComponentId,
120 pos: (f32, f32, f32),
121 scale: f32,
122 rgba: [f32; 4],
123 ) {
124 let tx = universe.world.add_component(
125 engine::ecs::component::TransformComponent::new()
126 .with_position(pos.0, pos.1, pos.2)
127 .with_scale(scale, scale, scale),
128 );
129 let r = universe
130 .world
131 .add_component(engine::ecs::component::RenderableComponent::cube());
132 let c = universe
133 .world
134 .add_component(engine::ecs::component::ColorComponent::rgba(
135 rgba[0], rgba[1], rgba[2], rgba[3],
136 ));
137 let e = universe
138 .world
139 .add_component(engine::ecs::component::EmissiveComponent::on());
140 let _ = universe.attach(parent, tx);
141 let _ = universe.attach(tx, r);
142 let _ = universe.attach(r, c);
143 let _ = universe.attach(r, e);
144 }
145
146 fn spawn_op_cube(
147 universe: &mut engine::Universe,
148 parent: engine::ecs::ComponentId,
149 pos: (f32, f32, f32),
150 scale: f32,
151 base_rgba: [f32; 4],
152 ) -> engine::ecs::ComponentId {
153 let tx = universe.world.add_component(
154 engine::ecs::component::TransformComponent::new()
155 .with_position(pos.0, pos.1, pos.2)
156 .with_scale(scale, scale, scale),
157 );
158 let r = universe
159 .world
160 .add_component(engine::ecs::component::RenderableComponent::cube());
161 let c = universe
162 .world
163 .add_component(engine::ecs::component::ColorComponent::rgba(
164 base_rgba[0],
165 base_rgba[1],
166 base_rgba[2],
167 base_rgba[3],
168 ));
169 let e = universe
170 .world
171 .add_component(engine::ecs::component::EmissiveComponent::on());
172
173 let _ = universe.attach(parent, tx);
174 let _ = universe.attach(tx, r);
175 let _ = universe.attach(r, c);
176 let _ = universe.attach(r, e);
177
178 tx
179 }More examples
examples/transparent-cutout-example.rs (line 22)
12fn spawn_gold_cube(
13 universe: &mut engine::Universe,
14 parent: engine::ecs::ComponentId,
15 position: (f32, f32, f32),
16 scale: f32,
17 color: (f32, f32, f32),
18) {
19 let t = universe.world.add_component(
20 TransformComponent::new()
21 .with_position(position.0, position.1, position.2)
22 .with_scale(scale, scale, scale),
23 );
24 let r = universe.world.add_component(RenderableComponent::cube());
25 let c = universe
26 .world
27 .add_component(ColorComponent::rgba(color.0, color.1, color.2, 1.0));
28
29 let _ = universe.attach(parent, t);
30 let _ = universe.attach(t, r);
31 let _ = universe.attach(r, c);
32}
33
34fn main() {
35 mittens_engine::example_support::ensure_model_assets();
36 utils::logger::init();
37
38 let world = engine::ecs::World::default();
39 let mut universe = engine::Universe::new(world);
40
41 // Orange/yellow-ish clear color so cutout edges read.
42 let clear = universe
43 .world
44 .add_component(BackgroundColorComponent::new());
45 let clear_c = universe
46 .world
47 .add_component(ColorComponent::rgba(0.98, 0.72, 0.22, 1.0));
48 let _ = universe.world.add_child(clear, clear_c);
49 universe.add(clear);
50
51 // Warm-ish ambient so the gold cubes don’t go too dark.
52 let ambient = universe
53 .world
54 .add_component(AmbientLightComponent::rgb(0.22, 0.16, 0.08));
55 universe.add(ambient);
56
57 // --- Camera rig (WASD/QE) ---
58 let input = universe
59 .world
60 .add_component(InputComponent::new().with_speed(2.0));
61 let input_mode = universe
62 .world
63 .add_component(InputTransformModeComponent::forward_z().with_roll_axis_y());
64 let _ = universe.attach(input, input_mode);
65
66 // Start a bit pulled back, looking toward the origin.
67 let rig_transform = universe
68 .world
69 .add_component(TransformComponent::new().with_position(0.0, 0.0, 5.0));
70 let _ = universe.attach(input, rig_transform);
71
72 let camera3d = universe
73 .world
74 .add_component(Camera3DComponent::new().with_far(200.0).with_fov(55.0));
75 let _ = universe.attach(rig_transform, camera3d);
76
77 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
78 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
79 universe.add(input);
80
81 // Key light for toon shading.
82 let light = universe.world.add_component(
83 PointLightComponent::new()
84 .with_distance(50.0)
85 .with_intensity(2.2)
86 .with_color(1.0, 0.98, 0.92),
87 );
88 let light_transform = universe
89 .world
90 .add_component(TransformComponent::new().with_position(2.0, 3.0, 4.0));
91 let _ = universe.attach(light_transform, light);
92 universe.add(light_transform);
93
94 // --- Transparent cutout: 100 cat faces in a 10x10 grid ---
95 // Place them *behind* the starting camera position (camera starts at z=+5.0).
96 // Not attached to the camera rig.
97 let cat_grid_root = universe
98 .world
99 .add_component(TransformComponent::new().with_position(0.0, 0.0, 9.0));
100 universe.add(cat_grid_root);
101
102 let grid_w: i32 = 10;
103 let grid_h: i32 = 10;
104 let spacing: f32 = 0.7;
105 let half_w = (grid_w as f32 - 1.0) * spacing * 0.5;
106 let half_h = (grid_h as f32 - 1.0) * spacing * 0.5;
107
108 for y in 0..grid_h {
109 for x in 0..grid_w {
110 // Topology: cat_grid_root { T_quad { R_quad { Texture + Filtering + Cutout + Color } } }
111 let px = x as f32 * spacing - half_w;
112 let py = y as f32 * spacing - half_h;
113
114 let pz: f32 = (x as f32) % half_w;
115
116 let quad_t = universe.world.add_component(
117 TransformComponent::new()
118 .with_position(px, py, pz)
119 .with_scale(0.55, 0.55, 1.0),
120 );
121 let quad_r = universe.world.add_component(RenderableComponent::square());
122
123 let quad_tex = universe.world.add_component(TextureComponent::with_uri(
124 "assets/textures/cat-face-amused.dds",
125 ));
126 let quad_filtering = universe
127 .world
128 .add_component(TextureFilteringComponent::linear());
129 let quad_cutout = universe
130 .world
131 .add_component(TransparentCutoutComponent::new());
132 let quad_color = universe
133 .world
134 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
135
136 let _ = universe.attach(cat_grid_root, quad_t);
137 let _ = universe.attach(quad_t, quad_r);
138 let _ = universe.attach(quad_r, quad_tex);
139 let _ = universe.attach(quad_r, quad_filtering);
140 let _ = universe.attach(quad_r, quad_cutout);
141 let _ = universe.attach(quad_r, quad_color);
142 }
143 }
144
145 // --- Gold/yellow cubes behind the quad ---
146 // Parent them under a transform so it’s easy to tweak their depth.
147 let cubes_root = universe
148 .world
149 .add_component(TransformComponent::new().with_position(0.0, 0.0, 4.6));
150
151 // point light for cats
152 let cat_light_tx = universe
153 .world
154 .add_component(TransformComponent::new().with_position(0.0, 2.0, 7.0));
155
156 let cat_light = universe.world.add_component(
157 PointLightComponent::new()
158 .with_distance(150.0)
159 .with_intensity(1.5)
160 .with_color(1.0, 0.98, 0.92),
161 );
162 let _ = universe.attach(cat_light_tx, cat_light);
163 let _ = universe.attach(cubes_root, cat_light_tx);
164
165 universe.add(cubes_root);
166
167 let gold_a = (1.0, 0.86, 0.22);
168 let gold_b = (1.0, 0.74, 0.10);
169 let gold_c = (0.95, 0.92, 0.32);
170
171 // A loose cluster that’s visible through the cutout (transparent) area.
172 spawn_gold_cube(&mut universe, cubes_root, (-1.1, -0.3, -0.2), 0.45, gold_a);
173 spawn_gold_cube(&mut universe, cubes_root, (1.0, -0.4, -0.4), 0.40, gold_b);
174 spawn_gold_cube(&mut universe, cubes_root, (-0.2, 0.9, -0.6), 0.38, gold_c);
175 spawn_gold_cube(&mut universe, cubes_root, (0.7, 0.6, -0.9), 0.32, gold_a);
176 spawn_gold_cube(&mut universe, cubes_root, (-0.8, 0.4, -1.1), 0.36, gold_b);
177
178 // Bigger orange/yellow cubes behind the cluster (to make the cutout depth obvious).
179 let orange_gold_a = (1.0, 0.62, 0.10);
180 let orange_gold_b = (1.0, 0.78, 0.18);
181 spawn_gold_cube(
182 &mut universe,
183 cubes_root,
184 (0.0, -0.1, -2.1),
185 1.15,
186 orange_gold_b,
187 );
188 spawn_gold_cube(
189 &mut universe,
190 cubes_root,
191 (-1.8, 0.6, -2.6),
192 0.95,
193 orange_gold_a,
194 );
195 spawn_gold_cube(
196 &mut universe,
197 cubes_root,
198 (1.9, 0.7, -2.9),
199 1.05,
200 orange_gold_b,
201 );
202 spawn_gold_cube(
203 &mut universe,
204 cubes_root,
205 (0.9, 1.8, -3.2),
206 0.90,
207 orange_gold_a,
208 );
209 spawn_gold_cube(
210 &mut universe,
211 cubes_root,
212 (-0.9, 1.7, -3.5),
213 1.10,
214 orange_gold_b,
215 );
216
217 // Process init-time registrations (loads textures, registers renderables, etc.).
218 universe.systems.process_commands(
219 &mut universe.world,
220 &mut universe.visuals,
221 &mut universe.render_assets,
222 &mut universe.command_queue,
223 );
224
225 universe.enable_repl();
226 engine::Windowing::run_app(universe).expect("Windowing failed");
227}examples/button-press.rs (line 187)
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}
200
201fn spawn_raycastable_colored_cube(
202 universe: &mut engine::Universe,
203 parent: engine::ecs::ComponentId,
204 pos: [f32; 3],
205 scale: [f32; 3],
206 rgba: [f32; 4],
207) -> engine::ecs::ComponentId {
208 use engine::ecs::component::ColorComponent;
209
210 let r = spawn_raycastable_cube(universe, parent, pos, scale);
211 let c = universe
212 .world
213 .add_component(ColorComponent::rgba(rgba[0], rgba[1], rgba[2], rgba[3]));
214 let _ = universe.attach(r, c);
215 r
216}
217
218fn wrap_at_for_button_middle_width(middle_w: f32, text_scale: f32) -> usize {
219 // TextSystem layout: each glyph advances by 1.0 in local X.
220 // With `text_root` scaled by `text_scale`, each glyph is ~`text_scale` world units wide.
221 // Reserve some horizontal padding so letters don't collide with the face border.
222 let padding_world = (middle_w * 0.10).clamp(0.02, 0.25);
223 let usable_world = (middle_w - padding_world).max(0.05);
224 let glyph_w_world = text_scale.abs().max(1e-4);
225
226 let chars_per_line = (usable_world / glyph_w_world).floor() as isize;
227 (chars_per_line.max(1) as usize).min(200)
228}
229
230fn measure_word_wrapped_block(text: &str, wrap_at: usize) -> (usize, usize) {
231 // Approximate the block size (max columns, rows) for word-wrap-at-space behavior.
232 // This is used only for centering the text root visually.
233 if text.is_empty() {
234 return (0, 0);
235 }
236
237 let wrap_at = wrap_at.max(1);
238
239 let mut rows = 1usize;
240 let mut col = 0usize;
241 let mut max_col = 0usize;
242
243 for (wi, word) in text.split_whitespace().enumerate() {
244 let wlen = word.chars().count();
245 if wlen == 0 {
246 continue;
247 }
248
249 // If this isn't the first word on the line, account for a space.
250 let needs_space = col > 0;
251 let add = wlen + if needs_space { 1 } else { 0 };
252
253 if col > 0 && col + add > wrap_at {
254 // Wrap at the previous whitespace opportunity.
255 max_col = max_col.max(col);
256 rows += 1;
257 col = 0;
258 }
259
260 // Place the word (and preceding space if any).
261 col += add;
262
263 // If a single word exceeds wrap_at, we don't break it (matching TextSystem word_wrap).
264 max_col = max_col.max(col);
265
266 // Preserve explicit newlines in the input.
267 if wi == 0 {
268 // no-op
269 }
270 }
271
272 max_col = max_col.max(col);
273 (max_col, rows)
274}
275
276fn spawn_button(
277 universe: &mut engine::Universe,
278 pos: [f32; 3],
279 middle_wh: [f32; 2],
280 border: ButtonBorder,
281 text: &str,
282 text_scale: f32,
283 color: [f32; 4],
284 background_color: [f32; 4],
285) -> engine::ecs::ComponentId {
286 use engine::ecs::component::{
287 ColorComponent, TextComponent, TextureFilteringComponent, TransformComponent,
288 TransparentCutoutComponent,
289 };
290
291 let button_root = universe
292 .world
293 .add_component(TransformComponent::new().with_position(pos[0], pos[1], pos[2]));
294
295 // Frame: static border around the button.
296 let frame_root = universe.world.add_component(TransformComponent::new());
297 let frame_color = universe
298 .world
299 .add_component(ColorComponent::rgba(color[0], color[1], color[2], color[3]));
300 let _ = universe.attach(button_root, frame_root);
301 let _ = universe.attach(frame_root, frame_color);
302
303 // Cap: pressable face + text.
304 let cap_raised_z = 0.030;
305 let cap_pressed_z = 0.000;
306 let cap_root = universe
307 .world
308 .add_component(TransformComponent::new().with_position(0.0, 0.0, cap_raised_z));
309 let _ = universe.attach(button_root, cap_root);
310
311 let face_color = universe.world.add_component(ColorComponent::rgba(
312 background_color[0],
313 background_color[1],
314 background_color[2],
315 background_color[3],
316 ));
317 let _ = universe.attach(cap_root, face_color);
318
319 // Geometry.
320 let depth = 0.025;
321 let middle_w = middle_wh[0];
322 let middle_h = middle_wh[1];
323
324 // Border pieces.
325 let full_w = middle_w + border.left + border.right;
326 let full_h = middle_h + border.top + border.bottom;
327
328 // Left / right.
329 let _left = spawn_raycastable_cube(
330 universe,
331 frame_color,
332 [-(middle_w * 0.5 + border.left * 0.5), 0.0, 0.0],
333 [border.left, full_h, depth],
334 );
335 let _right = spawn_raycastable_cube(
336 universe,
337 frame_color,
338 [(middle_w * 0.5 + border.right * 0.5), 0.0, 0.0],
339 [border.right, full_h, depth],
340 );
341
342 // Top / bottom.
343 let _top = spawn_raycastable_cube(
344 universe,
345 frame_color,
346 [0.0, (middle_h * 0.5 + border.top * 0.5), 0.0],
347 [full_w, border.top, depth],
348 );
349 let _bottom = spawn_raycastable_cube(
350 universe,
351 frame_color,
352 [0.0, -(middle_h * 0.5 + border.bottom * 0.5), 0.0],
353 [full_w, border.bottom, depth],
354 );
355
356 // Face (pressable center).
357 let _face = spawn_raycastable_cube(
358 universe,
359 face_color,
360 [0.0, 0.0, 0.0],
361 [middle_w, middle_h, depth],
362 );
363
364 // Text. (Heuristic centering; TextComponent is top-left anchored today.)
365 let text_root = universe.world.add_component(
366 TransformComponent::new()
367 .with_position(0.0, 0.0, depth * 0.5 + 0.006)
368 .with_scale(text_scale, text_scale, 1.0),
369 );
370 let _ = universe.attach(cap_root, text_root);
371
372 let wrap_at = wrap_at_for_button_middle_width(middle_w, text_scale);
373 let (cols, rows) = measure_word_wrapped_block(text, wrap_at);
374 let cols_f = cols.max(1) as f32;
375 let rows_f = rows.max(1) as f32;
376
377 // Center the text block around (0,0) in glyph-local units.
378 // X is left-to-right (0..cols-1), Y is down (0, -1, -2...).
379 let x_center = -0.5 * (cols_f - 1.0);
380 let y_center = 0.5 * (rows_f - 1.0);
381 let text_offset = universe
382 .world
383 .add_component(TransformComponent::new().with_position(x_center, y_center, 0.0));
384 let _ = universe.attach(text_root, text_offset);
385
386 let text_id = universe
387 .world
388 .add_component(TextComponent::with_word_wrap(text, wrap_at));
389 let _ = universe.attach(text_offset, text_id);
390
391 let cutout = universe
392 .world
393 .add_component(TransparentCutoutComponent::new());
394 let _ = universe.attach(text_id, cutout);
395
396 let filtering = universe
397 .world
398 .add_component(TextureFilteringComponent::nearest_magnification());
399 let _ = universe.attach(text_id, filtering);
400
401 universe.add(button_root);
402
403 // Attach text color *after* text has been built/initialized.
404 // We intentionally initialize the ColorComponent before attachment so that its `init()` will
405 // NOT re-run on attach; this exercises the TextSystem ParentChanged refresh behavior.
406 let text_color = universe
407 .world
408 .add_component(ColorComponent::rgba(0.0, 0.0, 0.0, 1.0));
409 universe.add(text_color);
410 let _ = universe.attach(text_id, text_color);
411
412 // Stash state for the signal handler.
413 let state = ButtonState {
414 pressed: false,
415 cap_root,
416 cap_raised_z,
417 cap_pressed_z,
418 frame_color,
419 face_color,
420 text_id,
421 text_color,
422 frame_color_up: color,
423 face_color_up: background_color,
424 text_color_up: [0.0, 0.0, 0.0, 1.0],
425 frame_color_down: darken(color, 0.75),
426 face_color_down: darken(background_color, 0.75),
427 text_color_down: [1.0, 1.0, 1.0, 1.0],
428 };
429
430 BUTTONS
431 .get_or_init(|| Mutex::new(HashMap::new()))
432 .lock()
433 .expect("button state lock")
434 .insert(button_root, state);
435
436 button_root
437}examples/opacity-example.rs (line 23)
12fn spawn_cube(
13 universe: &mut engine::Universe,
14 parent: engine::ecs::ComponentId,
15 position: (f32, f32, f32),
16 scale: (f32, f32, f32),
17 color: Option<(f32, f32, f32, f32)>,
18 opacity: Option<OpacityComponent>,
19) {
20 let t = universe.world.add_component(
21 TransformComponent::new()
22 .with_position(position.0, position.1, position.2)
23 .with_scale(scale.0, scale.1, scale.2),
24 );
25 let r = universe.world.add_component(RenderableComponent::cube());
26
27 let _ = universe.attach(parent, t);
28 let _ = universe.attach(t, r);
29
30 if let Some((cr, cg, cb, ca)) = color {
31 let c = universe
32 .world
33 .add_component(ColorComponent::rgba(cr, cg, cb, ca));
34 let _ = universe.attach(r, c);
35 }
36
37 if let Some(o) = opacity {
38 let o = universe.world.add_component(o);
39 let _ = universe.attach(r, o);
40 }
41}
42
43fn spawn_text_label(universe: &mut engine::Universe, position: (f32, f32, f32), text: &str) {
44 // T_root { T_scale { TXT { filtering } } }
45 let text_root = universe
46 .world
47 .add_component(TransformComponent::new().with_position(position.0, position.1, position.2));
48
49 let text_scale = universe
50 .world
51 .add_component(TransformComponent::new().with_scale(0.18, 0.18, 1.0));
52 let _ = universe.attach(text_root, text_scale);
53
54 let text_c = universe.world.add_component(TextComponent::new(text));
55 let _ = universe.attach(text_scale, text_c);
56
57 // Keep it crisp.
58 let filtering = universe
59 .world
60 .add_component(TextureFilteringComponent::nearest());
61 let _ = universe.attach(text_c, filtering);
62
63 // White label.
64 let color = universe
65 .world
66 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
67 let _ = universe.attach(text_c, color);
68
69 universe.add(text_root);
70}
71
72fn text_block_dimensions(text: &str) -> (usize, usize) {
73 let mut max_cols = 0usize;
74 let mut rows = 1usize;
75 let mut cur = 0usize;
76
77 for ch in text.chars() {
78 if ch == '\n' {
79 max_cols = max_cols.max(cur);
80 cur = 0;
81 rows += 1;
82 } else {
83 cur += 1;
84 }
85 }
86 max_cols = max_cols.max(cur);
87
88 (max_cols.max(1), rows.max(1))
89}
90
91fn spawn_text_label_with_bg(
92 universe: &mut engine::Universe,
93 position: (f32, f32, f32),
94 text: &str,
95 bg_opacity: f32,
96) {
97 // T_root {
98 // T_bg { R_bg { Color black, Opacity } }
99 // T_scale { TXT { filtering } }
100 // }
101
102 let text_root = universe
103 .world
104 .add_component(TransformComponent::new().with_position(position.0, position.1, position.2));
105
106 // Background quad (slightly behind the glyph quads).
107 let (cols, rows) = text_block_dimensions(text);
108 let text_scale = 0.18_f32;
109 let pad_x = 0.55_f32;
110 let pad_y = 0.45_f32;
111 let bg_w = cols as f32 * text_scale + pad_x;
112 let bg_h = rows as f32 * text_scale + pad_y;
113
114 let bg_t = universe.world.add_component(
115 TransformComponent::new()
116 .with_position(1.5, 0.0, -0.02)
117 .with_scale(bg_w, bg_h, 1.0),
118 );
119 let bg_r = universe.world.add_component(RenderableComponent::square());
120 let _ = universe.attach(text_root, bg_t);
121 let _ = universe.attach(bg_t, bg_r);
122
123 let bg_c = universe
124 .world
125 .add_component(ColorComponent::rgba(0.0, 0.0, 0.0, 1.0));
126 let _ = universe.attach(bg_r, bg_c);
127
128 let bg_o = universe
129 .world
130 .add_component(OpacityComponent::new().with_opacity(bg_opacity));
131 let _ = universe.attach(bg_r, bg_o);
132
133 let text_scale_t = universe
134 .world
135 .add_component(TransformComponent::new().with_scale(text_scale, text_scale, 1.0));
136 let _ = universe.attach(text_root, text_scale_t);
137
138 let text_c = universe.world.add_component(TextComponent::new(text));
139 let _ = universe.attach(text_scale_t, text_c);
140
141 // Keep it crisp.
142 let filtering = universe
143 .world
144 .add_component(TextureFilteringComponent::nearest());
145 let _ = universe.attach(text_c, filtering);
146
147 // Force the label into the transparent pass so it layers correctly with the background.
148 // (Pass selection currently does not consider texture alpha.)
149 let color = universe
150 .world
151 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 0.998));
152 let _ = universe.attach(text_c, color);
153
154 universe.add(text_root);
155}
156
157fn spawn_demo_spot(
158 universe: &mut engine::Universe,
159 spot_pos: (f32, f32, f32),
160 opacity: f32,
161 multiple_layers: bool,
162 grid_n: i32,
163) {
164 let spot = universe
165 .world
166 .add_component(TransformComponent::new().with_position(spot_pos.0, spot_pos.1, spot_pos.2));
167
168 // All cubes inherit this color.
169 let white = universe
170 .world
171 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
172 let _ = universe.attach(spot, white);
173
174 // All cubes inherit this opacity (no per-cube OpacityComponent needed).
175 let mut oc = OpacityComponent::new().with_opacity(opacity);
176 if multiple_layers {
177 oc = oc.with_multiple_layers();
178 }
179 let oc = universe.world.add_component(oc);
180 let _ = universe.attach(spot, oc);
181
182 universe.add(spot);
183
184 // Cube grid.
185 let n = grid_n.max(1);
186 let spacing = if n <= 2 { 1.0 } else { 0.6 };
187 let cube_scale = if n <= 2 { 0.8 } else { 0.45 };
188 let half = (n as f32 - 1.0) * spacing * 0.5;
189
190 for z in 0..n {
191 for y in 0..n {
192 for x in 0..n {
193 let px = x as f32 * spacing - half;
194 let py = y as f32 * spacing;
195 let pz = z as f32 * spacing - half;
196
197 spawn_cube(
198 universe,
199 spot,
200 (px, py, pz),
201 (cube_scale, cube_scale, cube_scale),
202 None,
203 None,
204 );
205 }
206 }
207 }
208
209 // Label above.
210 let label = format!(
211 "opacity: {:.2}\nmulti-layer: {}",
212 opacity,
213 if multiple_layers { "true" } else { "false" }
214 );
215 // Keep labels away from the cube volume so they don't intersect.
216 let label_y = spot_pos.1 + (n as f32 * spacing) + 1.8;
217 let label_x = spot_pos.0 - (half + 0.6);
218 let label_z = spot_pos.2 - (half + 1.0);
219 spawn_text_label(universe, (label_x, label_y, label_z), &label);
220}
221
222fn spawn_demo_strip_pair(
223 universe: &mut engine::Universe,
224 spot_pos: (f32, f32, f32),
225 transparent_multiple_layers: bool,
226) {
227 let n_z: i32 = 4;
228 let spacing = 1.0;
229 let cube_scale = 0.8;
230 let half_z = (n_z as f32 - 1.0) * spacing * 0.5;
231
232 // Transparent strip (1x4 along Z).
233 let transparent_root = universe
234 .world
235 .add_component(TransformComponent::new().with_position(spot_pos.0, spot_pos.1, spot_pos.2));
236
237 let white = universe
238 .world
239 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
240 let _ = universe.attach(transparent_root, white);
241
242 let mut oc = OpacityComponent::new().with_opacity(0.50);
243 if transparent_multiple_layers {
244 oc = oc.with_multiple_layers();
245 }
246 let oc = universe.world.add_component(oc);
247 let _ = universe.attach(transparent_root, oc);
248
249 universe.add(transparent_root);
250
251 for z in 0..n_z {
252 let pz = z as f32 * spacing - half_z;
253 spawn_cube(
254 universe,
255 transparent_root,
256 (0.0, 0.0, pz),
257 (cube_scale, cube_scale, cube_scale),
258 None,
259 None,
260 );
261 }
262
263 // Opaque strip to the left, same spacing/scale.
264 let opaque_root = universe
265 .world
266 .add_component(TransformComponent::new().with_position(
267 spot_pos.0 - spacing,
268 spot_pos.1,
269 spot_pos.2,
270 ));
271 let white = universe
272 .world
273 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
274 let _ = universe.attach(opaque_root, white);
275 universe.add(opaque_root);
276
277 for z in 0..n_z {
278 let pz = z as f32 * spacing - half_z;
279 spawn_cube(
280 universe,
281 opaque_root,
282 (0.0, 0.0, pz),
283 (cube_scale, cube_scale, cube_scale),
284 None,
285 None,
286 );
287 }
288
289 let label = format!(
290 "mini: opaque + transparent strip\ntransparent opacity: 0.50\nmulti-layer: {}",
291 if transparent_multiple_layers {
292 "true"
293 } else {
294 "false"
295 }
296 );
297 let label_y = spot_pos.1 + spacing + 1.8;
298 let label_x = spot_pos.0 - (spacing * 2.6);
299 let label_z = spot_pos.2 - (half_z + 1.0);
300 spawn_text_label(universe, (label_x, label_y, label_z), &label);
301}
302
303fn spawn_demo_xy_plane(
304 universe: &mut engine::Universe,
305 spot_pos: (f32, f32, f32),
306 opacity: f32,
307 n_x: i32,
308 n_y: i32,
309 z: f32,
310) {
311 let spot = universe
312 .world
313 .add_component(TransformComponent::new().with_position(spot_pos.0, spot_pos.1, spot_pos.2));
314
315 let white = universe
316 .world
317 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
318 let _ = universe.attach(spot, white);
319
320 let oc = universe
321 .world
322 .add_component(OpacityComponent::new().with_opacity(opacity));
323 let _ = universe.attach(spot, oc);
324
325 universe.add(spot);
326
327 let nx = n_x.max(1);
328 let ny = n_y.max(1);
329 let spacing = 0.45;
330 let cube_scale = 0.35;
331 let half_x = (nx as f32 - 1.0) * spacing * 0.5;
332
333 for y in 0..ny {
334 for x in 0..nx {
335 let px = x as f32 * spacing - half_x;
336 let py = y as f32 * spacing;
337 spawn_cube(
338 universe,
339 spot,
340 (px, py, z),
341 (cube_scale, cube_scale, cube_scale),
342 None,
343 None,
344 );
345 }
346 }
347
348 // Label in front of the plane.
349 let label = format!(
350 "XY plane: {}x{}\nopacity: {:.2}\nmulti-layer: false",
351 nx, ny, opacity
352 );
353 let label_x = spot_pos.0 - (half_x + 0.6);
354 let label_y = spot_pos.1 + (ny as f32 * spacing) + 1.2;
355 let label_z = spot_pos.2 - 2.5;
356 spawn_text_label_with_bg(universe, (label_x, label_y, label_z), &label, 0.50);
357}
358
359fn main() {
360 mittens_engine::example_support::ensure_model_assets();
361 utils::logger::init();
362
363 let world = engine::ecs::World::default();
364 let mut universe = engine::Universe::new(world);
365
366 // Dark brown / pink background.
367 let bg = universe
368 .world
369 .add_component(BackgroundColorComponent::new());
370 let bg_c = universe
371 .world
372 .add_component(ColorComponent::rgba(0.22, 0.08, 0.10, 1.0));
373 let _ = universe.world.add_child(bg, bg_c);
374 universe.add(bg);
375
376 // Ambient so unlit areas aren't black.
377 let ambient = universe
378 .world
379 .add_component(AmbientLightComponent::rgb(0.35, 0.35, 0.40));
380 universe.add(ambient);
381
382 // A bright overhead light.
383 let light_t = universe.world.add_component(
384 TransformComponent::new()
385 .with_position(0.0, 18.0, 10.0)
386 .with_scale(0.2, 0.2, 0.2),
387 );
388 let light = universe.world.add_component(
389 PointLightComponent::new()
390 .with_color(1.0, 1.0, 1.0)
391 .with_intensity(1.6)
392 .with_distance(80.0),
393 );
394 let _ = universe.attach(light_t, light);
395 universe.add(light_t);
396
397 // --- Camera rig ---
398 // I { T { C3D { with_fps_rotation with_roll_axis_y } } }
399 let input = universe
400 .world
401 .add_component(InputComponent::new().with_speed(3.0));
402 let input_mode = universe.world.add_component(
403 InputTransformModeComponent::forward_z()
404 .with_roll_axis_y()
405 .with_fps_rotation(),
406 );
407 let _ = universe.attach(input, input_mode);
408
409 let rig_transform = universe
410 .world
411 .add_component(TransformComponent::new().with_position(0.0, 6.0, 20.0));
412 let _ = universe.attach(input, rig_transform);
413
414 let camera = universe.world.add_component(Camera3DComponent::new());
415 let _ = universe.attach(rig_transform, camera);
416
417 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
418 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
419
420 universe.add(input);
421
422 // --- Ground ---
423 let ground = universe.world.add_component(
424 TransformComponent::new()
425 .with_position(0.0, -0.6, 0.0)
426 .with_scale(60.0, 1.0, 60.0),
427 );
428 let ground_r = universe.world.add_component(RenderableComponent::cube());
429 let ground_c = universe
430 .world
431 .add_component(ColorComponent::rgba(0.95, 0.95, 0.95, 1.0));
432 let _ = universe.attach(ground, ground_r);
433 let _ = universe.attach(ground_r, ground_c);
434 universe.add(ground);
435
436 // --- Opaque yellow cubes behind (contrast reference) ---
437 for i in 0..6 {
438 let x = -10.0 + i as f32 * 4.0;
439 spawn_cube(
440 &mut universe,
441 ground,
442 (x, 1.2, -18.0),
443 (2.0, 2.0, 2.0),
444 Some((1.0, 0.92, 0.15, 1.0)),
445 None,
446 );
447 }
448
449 // --- Demo spots ---
450 // Left of the left big spot: a single-layer transparent XY plane (16x16).
451 spawn_demo_xy_plane(&mut universe, (-14.0, 0.0, 0.0), 0.50, 16, 16, 0.0);
452
453 // Big spots: 8x8x8
454 // Left: single-layer transparent (instanced)
455 spawn_demo_spot(&mut universe, (-4.0, 0.0, 0.0), 0.50, false, 8);
456
457 // Right: multi-layer transparent (sorted)
458 spawn_demo_spot(&mut universe, (4.0, 0.0, 0.0), 0.50, true, 8);
459
460 // Small spots: opaque 1x4 strip + transparent 1x4 strip (along Z).
461 spawn_demo_strip_pair(&mut universe, (-4.0, 0.0, 10.0), false);
462 spawn_demo_strip_pair(&mut universe, (4.0, 0.0, 10.0), true);
463
464 // Process init-time registrations.
465 universe.systems.process_commands(
466 &mut universe.world,
467 &mut universe.visuals,
468 &mut universe.render_assets,
469 &mut universe.command_queue,
470 );
471
472 universe.enable_repl();
473
474 engine::Windowing::run_app(universe).expect("Windowing failed");
475}examples/animation-for-topology.rs (line 30)
20fn spawn_emissive_marker_cube(
21 universe: &mut engine::Universe,
22 parent: engine::ecs::ComponentId,
23 local_pos: (f32, f32, f32),
24 scale: f32,
25 rgba: [f32; 4],
26) {
27 let tx = universe.world.add_component(
28 engine::ecs::component::TransformComponent::new()
29 .with_position(local_pos.0, local_pos.1, local_pos.2)
30 .with_scale(scale, scale, scale),
31 );
32 let r = universe
33 .world
34 .add_component(engine::ecs::component::RenderableComponent::cube());
35 let c = universe
36 .world
37 .add_component(engine::ecs::component::ColorComponent::rgba(
38 rgba[0], rgba[1], rgba[2], rgba[3],
39 ));
40 let e = universe
41 .world
42 .add_component(engine::ecs::component::EmissiveComponent::on());
43
44 let _ = universe.attach(parent, tx);
45 let _ = universe.attach(tx, r);
46 let _ = universe.attach(r, c);
47 let _ = universe.attach(r, e);
48}
49
50/// Create a cube subtree ahead-of-time (not attached to the scene).
51///
52/// Returns the root `TransformComponent` id.
53fn spawn_detached_cube_prefab(
54 universe: &mut engine::Universe,
55 scale: f32,
56 rgba: [f32; 4],
57) -> engine::ecs::ComponentId {
58 let tx = universe.world.add_component(
59 engine::ecs::component::TransformComponent::new().with_scale(scale, scale, scale),
60 );
61 let r = universe
62 .world
63 .add_component(engine::ecs::component::RenderableComponent::cube());
64 let c = universe
65 .world
66 .add_component(engine::ecs::component::ColorComponent::rgba(
67 rgba[0], rgba[1], rgba[2], rgba[3],
68 ));
69 let e = universe
70 .world
71 .add_component(engine::ecs::component::EmissiveComponent::on());
72
73 let _ = universe.attach(tx, r);
74 let _ = universe.attach(r, c);
75 let _ = universe.attach(r, e);
76
77 tx
78}examples/raycast-topology-animation.rs (line 185)
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 }
193
194 marker(&mut universe, anchor_a, [0.9, 0.2, 0.2, 1.0]);
195 marker(&mut universe, anchor_b, [0.2, 0.2, 0.9, 1.0]);
196
197 universe.add(anchor_a);
198 universe.add(anchor_b);
199
200 // A ring of cubes around the origin to see which one gets hit.
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 }Additional examples can be found in:
- examples/audio-graph-example.rs
- examples/collision-perimeter.rs
- examples/simple-demo.rs
- examples/pointer-events.rs
- examples/util/mod.rs
- examples/vr-input.rs
- examples/triage/panel-pierce.rs
- examples/scrolling.rs
- examples/font-example.rs
- examples/example_util/mod.rs
- examples/mesh-factory-example.rs
- examples/text-animation.rs
- examples/vtuber-example.rs
- examples/openxr.rs
- examples/text-example.rs
- examples/gestures-and-gizmos.rs
- examples/background-example.rs
- examples/bisket-vr-demo.rs
- examples/background-occlusion-example.rs
- examples/vtuber-joints-example.rs
- examples/folder-text.rs
- examples/gravity-fields.rs
Sourcepub fn with_rotation_euler(self, pitch_x: f32, yaw_y: f32, roll_z: f32) -> Self
pub fn with_rotation_euler(self, pitch_x: f32, yaw_y: f32, roll_z: f32) -> Self
Builder-style: set rotation from Euler angles (radians), returns Self.
Examples found in repository?
examples/gestures-and-gizmos.rs (line 135)
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 }More examples
examples/simple-demo.rs (line 37)
6fn build_demo_scene_7_shapes(universe: &mut engine::Universe) {
7 use engine::ecs::component::{
8 Camera3DComponent, ColorComponent, EmissiveComponent, GLTFComponent, InputComponent,
9 InputTransformModeComponent, PointLightComponent, RenderableComponent, TextureComponent,
10 TransformComponent,
11 };
12 use engine::graphics::BuiltinMeshType;
13 use engine::graphics::primitives::MaterialHandle;
14
15 // Built-in CPU meshes are pre-registered; just fetch stable handles.
16 let tri_mesh = universe.render_assets.get_mesh(BuiltinMeshType::Triangle2D);
17 let square_mesh = universe.render_assets.get_mesh(BuiltinMeshType::Quad2D);
18 let tetra_mesh = universe
19 .render_assets
20 .get_mesh(BuiltinMeshType::Tetrahedron);
21
22 fn spawn(
23 universe: &mut engine::Universe,
24 mesh: engine::graphics::primitives::CpuMeshHandle,
25 x: f32,
26 y: f32,
27 s: f32,
28 r: f32,
29 color: [f32; 4],
30 input_driven: bool,
31 emissive: bool,
32 ) -> engine::ecs::ComponentId {
33 let transform = universe.world.add_component(
34 TransformComponent::new()
35 .with_position(x, y, 0.0)
36 .with_scale(s, s, 1.0)
37 .with_rotation_euler(0.0, 0.0, r),
38 );
39 let renderable = universe.world.add_component(RenderableComponent::new(
40 engine::graphics::primitives::Renderable::new(mesh, MaterialHandle::TOON_MESH),
41 ));
42 let color_c = universe.world.add_component(ColorComponent { rgba: color });
43
44 if emissive {
45 let emissive_c = universe.world.add_component(EmissiveComponent::on());
46 let _ = universe.attach(renderable, emissive_c);
47 }
48
49 // Topology: (optional Input) -> Transform -> Renderable
50 let _ = universe.attach(transform, renderable);
51 let _ = universe.attach(renderable, color_c);
52
53 if input_driven {
54 let input = universe
55 .world
56 .add_component(InputComponent::new().with_speed(0.5));
57 let _ = universe.attach(input, transform);
58 universe.add(input);
59 } else {
60 universe.add(transform);
61 }
62
63 transform
64 }
65
66 fn spawn_3d(
67 universe: &mut engine::Universe,
68 mesh: engine::graphics::primitives::CpuMeshHandle,
69 x: f32,
70 y: f32,
71 z: f32,
72 s: f32,
73 rx: f32,
74 ry: f32,
75 rz: f32,
76 color: [f32; 4],
77 ) -> engine::ecs::ComponentId {
78 let transform = universe.world.add_component(
79 TransformComponent::new()
80 .with_position(x, y, z)
81 .with_scale(s, s, s)
82 .with_rotation_euler(rx, ry, rz),
83 );
84 let renderable = universe.world.add_component(RenderableComponent::new(
85 engine::graphics::primitives::Renderable::new(mesh, MaterialHandle::TOON_MESH),
86 ));
87 let color_c = universe.world.add_component(ColorComponent { rgba: color });
88
89 let _ = universe.attach(transform, renderable);
90 let _ = universe.attach(renderable, color_c);
91 universe.add(transform);
92
93 transform
94 }
95
96 // Spawn shapes.
97 // One triangle is input-driven (WASD/QE). Build a small "rig" so both the triangle
98 // and the camera can be driven by the same InputComponent.
99
100 // Topology: Input -> (InputTransformMode) -> RigTransform -> (CameraTransform -> Camera3D), (TriRootTransform -> ...)
101 let tri_input = universe
102 .world
103 .add_component(InputComponent::new().with_speed(0.5));
104 let input_mode = universe
105 .world
106 .add_component(InputTransformModeComponent::forward_z().with_roll_axis_y());
107 let _ = universe.attach(tri_input, input_mode);
108
109 // Start pulled back so the demo meshes at z=0 are in view.
110 // The camera will be attached directly under this transform, so there is no local
111 // camera offset that would cause orbiting when yawing.
112 let rig_transform = universe
113 .world
114 .add_component(TransformComponent::new().with_position(0.0, 0.0, 2.5));
115 let _ = universe.attach(tri_input, rig_transform);
116
117 // Camera: attached directly to the rig transform.
118 let camera3d = universe.world.add_component(Camera3DComponent::new());
119 let _ = universe.attach(rig_transform, camera3d);
120
121 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
122 example_util::spawn_desktop_camera_controls_hint(universe, rig_transform);
123
124 let tri_root_transform = universe
125 .world
126 .add_component(TransformComponent::new().with_position(0.5, 0.50, 0.0));
127
128 // Visual transform under the root; this is where we apply rotation/scale.
129 let tri_visual_transform = universe.world.add_component(
130 TransformComponent::new()
131 .with_scale(0.30, 0.30, 1.0)
132 .with_rotation_euler(0.0, 0.0, (2.0 * 3.14159 / 3.0) + 3.14159),
133 );
134 let tri_renderable = universe.world.add_component(RenderableComponent::new(
135 engine::graphics::primitives::Renderable::new(tri_mesh, MaterialHandle::TOON_MESH),
136 ));
137 let tri_color = universe
138 .world
139 .add_component(ColorComponent::rgba(0.2, 1.0, 0.2, 1.0));
140
141 let _ = universe.attach(rig_transform, tri_root_transform);
142 let _ = universe.attach(tri_root_transform, tri_visual_transform);
143 let _ = universe.attach(tri_visual_transform, tri_renderable);
144 let _ = universe.attach(tri_renderable, tri_color);
145
146 let tri_light = universe.world.add_component(
147 PointLightComponent::new()
148 .with_distance(10.0)
149 .with_color(1.0, 1.0, 1.0),
150 );
151
152 let light_transform = universe.world.add_component(
153 TransformComponent::new()
154 .with_position(0.5, 0.50, 1.0)
155 .with_scale(0.1, 0.1, 0.1),
156 );
157
158 let _ = universe.attach(light_transform, tri_light);
159
160 universe.add(tri_input);
161 universe.add(light_transform);
162
163 spawn(
164 universe,
165 square_mesh,
166 -0.80,
167 -0.30,
168 0.25,
169 0.0,
170 [1.0, 0.2, 0.2, 1.0],
171 false,
172 true,
173 );
174 spawn(
175 universe,
176 square_mesh,
177 -0.40,
178 -0.30,
179 0.25,
180 0.0,
181 [1.0, 0.6, 0.2, 1.0],
182 false,
183 true,
184 );
185
186 // 3D primitive: tetrahedron.
187 spawn_3d(
188 universe,
189 tetra_mesh,
190 0.55,
191 -0.15,
192 0.0,
193 0.35,
194 0.75,
195 0.55,
196 0.0,
197 [0.2, 0.7, 1.0, 1.0],
198 );
199 spawn(
200 universe,
201 square_mesh,
202 0.00,
203 -0.30,
204 0.25,
205 0.0,
206 [1.0, 1.0, 0.2, 1.0],
207 false,
208 true,
209 );
210 spawn(
211 universe,
212 square_mesh,
213 0.40,
214 -0.30,
215 0.25,
216 0.0,
217 [0.2, 0.6, 1.0, 1.0],
218 false,
219 true,
220 );
221 spawn(
222 universe,
223 square_mesh,
224 0.80,
225 -0.30,
226 0.25,
227 0.0,
228 [0.8, 0.2, 1.0, 1.0],
229 false,
230 true,
231 );
232 spawn(
233 universe,
234 tri_mesh,
235 0.30,
236 0.35,
237 0.30,
238 -3.14159,
239 [1.0, 1.0, 1.0, 1.0],
240 false,
241 false,
242 );
243
244 // Textured square.
245 let tex_transform = universe.world.add_component(
246 TransformComponent::new()
247 .with_position(0.0, 0.1, 0.0)
248 .with_scale(0.45, 0.45, 1.0),
249 );
250 let tex_renderable = universe.world.add_component(RenderableComponent::new(
251 engine::graphics::primitives::Renderable::new(square_mesh, MaterialHandle::TOON_MESH),
252 ));
253 let tex_color = universe
254 .world
255 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
256 let tex = universe.world.add_component(TextureComponent::from_dds(
257 "assets/textures/cat-face-amused.dds",
258 ));
259
260 let _ = universe.attach(tex_transform, tex_renderable);
261 let _ = universe.attach(tex_renderable, tex_color);
262 let _ = universe.attach(tex_renderable, tex);
263 universe.add(tex_transform);
264
265 // glTF: color-cat
266 // Attach GLTFComponent under a Transform so GLTFSystem can use it as an anchor.
267 let cat_anchor = universe.world.add_component(
268 TransformComponent::new()
269 .with_position(0.0, -0.10, -4.0)
270 .with_scale(0.50, 0.50, 0.50)
271 .with_rotation_euler(0.0, 0.0, 0.0),
272 );
273 let cat_gltf = universe
274 .world
275 .add_component(GLTFComponent::new("assets/models/color-cat.2.glb"));
276 let _ = universe.attach(cat_anchor, cat_gltf);
277 universe.add(cat_anchor);
278}examples/raycast-topology-animation.rs (line 138)
111fn main() {
112 mittens_engine::example_support::ensure_model_assets();
113 utils::logger::init();
114
115 let world = engine::ecs::World::default();
116 let mut universe = engine::Universe::new(world);
117
118 // Background.
119 let bg_color = universe
120 .world
121 .add_component(engine::ecs::component::BackgroundColorComponent::new());
122 let bg_color_c = universe
123 .world
124 .add_component(engine::ecs::component::ColorComponent::rgba(
125 0.1, 0.1, 0.1, 1.0,
126 ));
127 let _ = universe.world.add_child(bg_color, bg_color_c);
128 universe.add(bg_color);
129
130 // Camera rig so we can see the scene.
131 let input = universe
132 .world
133 .add_component(engine::ecs::component::InputComponent::new().with_speed(2.0));
134
135 let rig_transform = universe.world.add_component(
136 engine::ecs::component::TransformComponent::new()
137 .with_position(0.0, 2.0, 8.0)
138 .with_rotation_euler(-0.25, 0.0, 0.0),
139 );
140
141 let camera3d = universe
142 .world
143 .add_component(engine::ecs::component::Camera3DComponent::new());
144
145 let input_mode = universe.world.add_component(
146 engine::ecs::component::InputTransformModeComponent::forward_z()
147 .with_roll_axis_y()
148 .with_fps_rotation(),
149 );
150
151 let _ = universe.attach(input, input_mode);
152 let _ = universe.attach(input, rig_transform);
153 let _ = universe.attach(rig_transform, camera3d);
154
155 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
156 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
157 universe.add(input);
158
159 // Two rotating anchor transforms that the raycaster will be reparented under.
160 // Each ring has its own anchor at a different height.
161 let anchor_a = universe.world.add_component(
162 engine::ecs::component::TransformComponent::new().with_position(0.0, 1.0, 0.0),
163 );
164 let anchor_b = universe.world.add_component(
165 engine::ecs::component::TransformComponent::new().with_position(0.0, 2.2, 0.0),
166 );
167
168 // Visual markers for A/B.
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 }
193
194 marker(&mut universe, anchor_a, [0.9, 0.2, 0.2, 1.0]);
195 marker(&mut universe, anchor_b, [0.2, 0.2, 0.9, 1.0]);
196
197 universe.add(anchor_a);
198 universe.add(anchor_b);
199
200 // A ring of cubes around the origin to see which one gets hit.
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 }
230
231 // Two rings: one per anchor height.
232 let n = 28;
233 let (radius_a, y_a) = (4.0, 1.0);
234 let (radius_b, y_b) = (2.6, 2.2);
235
236 let ring_a_root = universe
237 .world
238 .add_component(engine::ecs::component::TransformComponent::new());
239 let ring_b_root = universe
240 .world
241 .add_component(engine::ecs::component::TransformComponent::new());
242
243 for i in 0..n {
244 let a = (i as f32) * (std::f32::consts::TAU / (n as f32));
245 let (x, z) = (radius_a * a.cos(), radius_a * a.sin());
246 let color = if i % 2 == 0 {
247 [0.55, 0.20, 0.20, 1.0]
248 } else {
249 [0.20, 0.55, 0.20, 1.0]
250 };
251 ring_cube(&mut universe, ring_a_root, x, y_a, z, color);
252 }
253
254 for i in 0..n {
255 let a = (i as f32) * (std::f32::consts::TAU / (n as f32));
256 let (x, z) = (radius_b * a.cos(), radius_b * a.sin());
257 let color = if i % 2 == 0 {
258 [0.20, 0.25, 0.60, 1.0]
259 } else {
260 [0.20, 0.55, 0.55, 1.0]
261 };
262 ring_cube(&mut universe, ring_b_root, x, y_b, z, color);
263 }
264
265 // Init rings and attach scoped interaction handlers.
266 universe.add(ring_a_root);
267 universe.add(ring_b_root);
268 universe.add_signal_handler(
269 engine::ecs::SignalKind::RayIntersected,
270 ring_a_root,
271 ring_a_handler,
272 );
273 universe.add_signal_handler(
274 engine::ecs::SignalKind::RayIntersected,
275 ring_b_root,
276 ring_b_handler,
277 );
278
279 // The raycaster component we will move between parents.
280 // Source is inferred from topology:
281 // - Under transforms A/B (no camera child) => parent-forward (-Z)
282 // - Under camera rig transform (has camera child) => cursor-through-camera
283 let raycaster = universe.world.add_component(
284 engine::ecs::component::RayCastComponent::event_driven().with_max_distance(25.0),
285 );
286
287 // Global animation: move the raycaster between anchors.
288 // Loop length is 8 beats (we include a noop keyframe at beat 7.0 to force loop_len=8).
289 let anim_global = universe
290 .world
291 .add_component(engine::ecs::component::AnimationComponent::new());
292 {
293 // beat 0: attach to A.
294 let kf0 = universe
295 .world
296 .add_component(engine::ecs::component::KeyframeComponent::new(0.0));
297 let act0 = universe
298 .world
299 .add_component(engine::ecs::component::ActionComponent::new(
300 engine::ecs::IntentValue::Attach {
301 parents: vec![anchor_a],
302 child: raycaster,
303 },
304 ));
305 let _ = universe.attach(kf0, act0);
306 let _ = universe.attach(anim_global, kf0);
307
308 // beat 4: attach to B.
309 let kf4 = universe
310 .world
311 .add_component(engine::ecs::component::KeyframeComponent::new(4.0));
312 let act4 = universe
313 .world
314 .add_component(engine::ecs::component::ActionComponent::new(
315 engine::ecs::IntentValue::Attach {
316 parents: vec![anchor_b],
317 child: raycaster,
318 },
319 ));
320 let _ = universe.attach(kf4, act4);
321 let _ = universe.attach(anim_global, kf4);
322
323 // beat 7: noop to make loop_len=8.
324 let kf7 = universe
325 .world
326 .add_component(engine::ecs::component::KeyframeComponent::new(7.0));
327 let noop = universe
328 .world
329 .add_component(engine::ecs::component::ActionComponent::new(
330 engine::ecs::IntentValue::Noop,
331 ));
332 let _ = universe.attach(kf7, noop);
333 let _ = universe.attach(anim_global, kf7);
334 }
335 universe.add(anim_global);
336
337 // Ring A animation: rotate anchor A around Y (1 rev / 8 beats).
338 // Also triggers Action::raycast(raycaster) on downbeats in the first half: beats 0,1,2,3.
339 let anim_a = universe
340 .world
341 .add_component(engine::ecs::component::AnimationComponent::new());
342
343 // Ring B animation: rotate anchor B differently (yaw + pitch).
344 // Also triggers Action::raycast(raycaster) on offbeats in the second half: beats 4.5,5.5,6.5,7.5.
345 let anim_b = universe
346 .world
347 .add_component(engine::ecs::component::AnimationComponent::new());
348
349 let loop_beats = 8.0;
350 let steps = 64;
351
352 for step in 0..=steps {
353 let t = (step as f32) / (steps as f32);
354 let beat = (t as f64) * (loop_beats as f64);
355
356 // Keyframe beats are per-animation local beats.
357 let kf_a = universe
358 .world
359 .add_component(engine::ecs::component::KeyframeComponent::new(beat));
360 let kf_b = universe
361 .world
362 .add_component(engine::ecs::component::KeyframeComponent::new(beat));
363
364 // Anchor A rotation: smooth yaw.
365 let yaw_a = t * std::f32::consts::TAU;
366 let a_set = engine::ecs::component::ActionComponent::new(
367 engine::ecs::IntentValue::UpdateTransform {
368 component_ids: vec![anchor_a],
369 translation: [0.0, 1.0, 0.0],
370 rotation_quat_xyzw: quat_from_yaw(yaw_a),
371 scale: [1.0, 1.0, 1.0],
372 },
373 );
374 let a_set_id = universe.world.add_component(a_set);
375 let _ = universe.attach(kf_a, a_set_id);
376
377 // Anchor B rotation: yaw + pitch (different pattern).
378 let yaw_b = -t * std::f32::consts::TAU * 1.5;
379 let pitch_b = (t * std::f32::consts::TAU).sin() * 0.35;
380 let rot_b = quat_mul(quat_from_yaw(yaw_b), quat_from_pitch(pitch_b));
381 let b_set = engine::ecs::component::ActionComponent::new(
382 engine::ecs::IntentValue::UpdateTransform {
383 component_ids: vec![anchor_b],
384 translation: [0.0, 2.2, 0.0],
385 rotation_quat_xyzw: rot_b,
386 scale: [1.0, 1.0, 1.0],
387 },
388 );
389 let b_set_id = universe.world.add_component(b_set);
390 let _ = universe.attach(kf_b, b_set_id);
391
392 // Per-ring raycast patterns.
393 // A: downbeats in first half (0..4): step % 8 == 0 -> beats 0,1,2,3,4.
394 if beat < 4.0 && step % 8 == 0 {
395 let act = universe
396 .world
397 .add_component(engine::ecs::component::ActionComponent::new(
398 engine::ecs::IntentValue::RequestRaycast {
399 component_ids: vec![raycaster],
400 },
401 ));
402 let _ = universe.attach(kf_a, act);
403 }
404
405 // B: offbeats in second half (4..8): step % 8 == 4 -> beats 0.5,1.5,...,7.5.
406 if beat >= 4.0 && step % 8 == 4 {
407 let act = universe
408 .world
409 .add_component(engine::ecs::component::ActionComponent::new(
410 engine::ecs::IntentValue::RequestRaycast {
411 component_ids: vec![raycaster],
412 },
413 ));
414 let _ = universe.attach(kf_b, act);
415 }
416
417 let _ = universe.attach(anim_a, kf_a);
418 let _ = universe.attach(anim_b, kf_b);
419 }
420
421 universe.add(anim_a);
422 universe.add(anim_b);
423
424 // Process init-time registrations.
425 universe.systems.process_commands(
426 &mut universe.world,
427 &mut universe.visuals,
428 &mut universe.render_assets,
429 &mut universe.command_queue,
430 );
431
432 engine::Windowing::run_app(universe).expect("Windowing failed");
433}examples/folder-text.rs (line 235)
60fn main() {
61 mittens_engine::example_support::ensure_model_assets();
62 utils::logger::init();
63
64 // Usage:
65 // cargo run --example folder-text -- [folder] [spacing]
66 // Defaults:
67 // folder=src spacing=0.3
68 let mut args = std::env::args().skip(1);
69 let folder = args.next().unwrap_or_else(|| "src".to_string());
70 let spacing: f32 = args
71 .next()
72 .and_then(|s| s.parse::<f32>().ok())
73 .unwrap_or(1.0);
74
75 // Safety caps: this demo can explode the ECS if we load huge projects.
76 const MAX_FILES: usize = 42;
77 const MAX_CHARS_PER_FILE: usize = 10_000;
78 const WRAP_AT: usize = 90;
79 const TEXT_SCALE: f32 = 0.01;
80
81 let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
82 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
83 let scan_root = {
84 let p = PathBuf::from(&folder);
85 if p.is_absolute() {
86 p
87 } else {
88 // Resolve relative paths from the crate root, not the process CWD.
89 manifest_dir.join(p)
90 }
91 };
92 eprintln!("[folder-text] cwd={:?}", cwd);
93 eprintln!("[folder-text] manifest_dir={:?}", manifest_dir);
94 eprintln!("[folder-text] scan_root={:?}", scan_root);
95
96 let mut files = Vec::new();
97 collect_rs_files(&scan_root, &mut files);
98 files.sort();
99
100 if files.is_empty() {
101 eprintln!(
102 "[folder-text] No .rs files found under folder={:?} (resolved={:?})",
103 folder, scan_root
104 );
105 }
106
107 let files: Vec<PathBuf> = files.into_iter().take(MAX_FILES).collect();
108 eprintln!(
109 "[folder-text] Loaded {} file(s) from {:?} (spacing={})",
110 files.len(),
111 folder,
112 spacing
113 );
114
115 #[derive(Debug, Clone)]
116 struct FileEntry {
117 path: PathBuf,
118 display_text: String,
119 max_cols: usize,
120 rows: usize,
121 }
122
123 // Group files by folder.
124 // Layout:
125 // - each folder becomes a column along +X
126 // - within a folder, files stack along +Y (8 high)
127 // - after 8, additional files start a new stack further along +Z
128 let mut files_by_folder: BTreeMap<PathBuf, Vec<FileEntry>> = BTreeMap::new();
129 for path in files {
130 let Ok(content) = std::fs::read_to_string(&path) else {
131 eprintln!("[folder-text] Failed to read {:?}", path);
132 continue;
133 };
134
135 let mut display_text = format!(
136 "// {}\n\n{}",
137 path.strip_prefix(&manifest_dir).unwrap_or(&path).display(),
138 content
139 );
140
141 if display_text.chars().count() > MAX_CHARS_PER_FILE {
142 display_text = display_text
143 .chars()
144 .take(MAX_CHARS_PER_FILE)
145 .collect::<String>();
146 display_text.push_str("\n\n// ... truncated ...\n");
147 }
148
149 let (max_cols, rows) = measure_text_bounds(&display_text, WRAP_AT);
150
151 let folder_key = path.parent().unwrap_or(&scan_root).to_path_buf();
152 files_by_folder
153 .entry(folder_key)
154 .or_default()
155 .push(FileEntry {
156 path,
157 display_text,
158 max_cols,
159 rows,
160 });
161 }
162
163 let column_count = files_by_folder.len().max(1);
164
165 let world = engine::ecs::World::default();
166 let mut universe = engine::Universe::new(world);
167
168 let bg_r = 0.25;
169 let bg_g = 0.25;
170 let bg_b = 0.25;
171 let background = universe
172 .world
173 .add_component(engine::ecs::component::BackgroundColorComponent::new());
174 let background_c = universe
175 .world
176 .add_component(engine::ecs::component::ColorComponent::rgba(
177 bg_r, bg_g, bg_b, 1.00,
178 ));
179 let _ = universe.world.add_child(background, background_c);
180 universe.add(background);
181
182 let ambient = universe
183 .world
184 .add_component(engine::ecs::component::AmbientLightComponent::rgb(
185 bg_r, bg_g, bg_b,
186 ));
187 universe.add(ambient);
188
189 // Input-driven camera rig.
190 // Topology: I { T { C3D } }
191 let input = universe
192 .world
193 .add_component(engine::ecs::component::InputComponent::new().with_speed(1.5));
194
195 // Center the camera horizontally over the spawned columns.
196 let center_x = if column_count <= 1 {
197 0.0
198 } else {
199 ((column_count - 1) as f32) * spacing * 0.5
200 };
201
202 // Bring camera closer: these text blocks are tiny.
203 let rig_transform = universe.world.add_component(
204 engine::ecs::component::TransformComponent::new().with_position(center_x, 0.0, 1.2),
205 );
206 let camera3d = universe
207 .world
208 .add_component(engine::ecs::component::Camera3DComponent::new());
209 let input_mode = universe.world.add_component(
210 engine::ecs::component::InputTransformModeComponent::forward_z().with_roll_axis_y(),
211 );
212 let _ = universe.attach(input, input_mode);
213 let _ = universe.attach(input, rig_transform);
214 let _ = universe.attach(rig_transform, camera3d);
215
216 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
217 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
218 universe.add(input);
219
220 // Big floor plane under everything.
221 // RenderableComponent::square() is an XY quad facing +Z; rotate it to XZ facing +Y.
222 let max_stacks = files_by_folder
223 .values()
224 .map(|v| (v.len() + 7) / 8)
225 .max()
226 .unwrap_or(1)
227 .max(1);
228 let total_width = (column_count as f32) * spacing;
229 let total_depth = (max_stacks as f32) * spacing;
230 let floor_w = total_width.max(10.0);
231 let floor_h = total_depth.max(10.0);
232 let floor_transform = universe.world.add_component(
233 engine::ecs::component::TransformComponent::new()
234 .with_position(0.0, -2.0, 0.0)
235 .with_rotation_euler(-std::f32::consts::FRAC_PI_2, 0.0, 0.0)
236 .with_scale(floor_w, floor_h, 1.0),
237 );
238 let floor_renderable = universe
239 .world
240 .add_component(engine::ecs::component::RenderableComponent::square());
241 let floor_color = universe
242 .world
243 .add_component(engine::ecs::component::ColorComponent::rgba(
244 0.88, 0.88, 0.88, 1.0,
245 ));
246 let _ = universe.attach(floor_transform, floor_renderable);
247 let _ = universe.attach(floor_renderable, floor_color);
248 universe.add(floor_transform);
249
250 // 4 red cubes around the perimeter of the world (easy visual anchors).
251 fn spawn_red_cube(universe: &mut engine::Universe, x: f32, y: f32, z: f32, s: f32) {
252 let t = universe.world.add_component(
253 engine::ecs::component::TransformComponent::new()
254 .with_position(x, y, z)
255 .with_scale(s, s, s),
256 );
257 let r = universe
258 .world
259 .add_component(engine::ecs::component::RenderableComponent::cube());
260 let c = universe
261 .world
262 .add_component(engine::ecs::component::ColorComponent::rgba(
263 1.0, 0.0, 0.0, 1.0,
264 ));
265
266 let light_transform = universe.world.add_component(
267 engine::ecs::component::TransformComponent::new().with_position(0.0, s * 0.75, 0.0),
268 );
269 let light = universe.world.add_component(
270 engine::ecs::component::PointLightComponent::new()
271 .with_intensity(1.5)
272 .with_distance(20.0)
273 .with_color(1.0, 0.0, 0.0),
274 );
275
276 let _ = universe.attach(t, r);
277 let _ = universe.attach(r, c);
278 let _ = universe.attach(t, light_transform);
279 let _ = universe.attach(light_transform, light);
280
281 universe.add(t);
282 }
283
284 let p = 1.5;
285 let s = 0.25;
286 spawn_red_cube(&mut universe, -p, -p, 0.0, s);
287 spawn_red_cube(&mut universe, p, -p, 0.0, s);
288 spawn_red_cube(&mut universe, -p, p, 0.0, s);
289 spawn_red_cube(&mut universe, p, p, 0.0, s);
290
291 let start_x = -center_x;
292 let stack_depth = spacing;
293 let row_gap_world = 0.35;
294
295 // One global point light at the top of the overall text "tower".
296 let pad_y = 4.0;
297 let global_max_panel_h_world = files_by_folder
298 .values()
299 .flat_map(|v| v.iter())
300 .map(|e| ((e.rows as f32) + pad_y) * TEXT_SCALE)
301 .fold(0.0_f32, f32::max)
302 .max(0.6);
303 let global_slot_h_world = global_max_panel_h_world + row_gap_world;
304 let tower_top_y = (7.0 * global_slot_h_world) + 4.0;
305 let tower_center_z = total_depth * 0.5;
306
307 let tower_light_transform = universe.world.add_component(
308 engine::ecs::component::TransformComponent::new().with_position(
309 0.0,
310 tower_top_y,
311 tower_center_z,
312 ),
313 );
314 let tower_light = universe.world.add_component(
315 engine::ecs::component::PointLightComponent::new()
316 .with_intensity(2.0)
317 .with_distance(120.0)
318 .with_color(1.0, 1.0, 1.0),
319 );
320 let _ = universe.attach(tower_light_transform, tower_light);
321 universe.add(tower_light_transform);
322
323 for (col_idx, (_folder, mut entries)) in files_by_folder.into_iter().enumerate() {
324 entries.sort_by(|a, b| a.path.cmp(&b.path));
325
326 // Slot height (world units) based on the tallest panel in this folder.
327 let pad_y = 4.0;
328 let max_panel_h_world = entries
329 .iter()
330 .map(|e| ((e.rows as f32) + pad_y) * TEXT_SCALE)
331 .fold(0.0_f32, f32::max)
332 .max(0.6);
333 let slot_h_world = max_panel_h_world + row_gap_world;
334
335 let col_x = start_x + (col_idx as f32) * spacing;
336
337 for (i, entry) in entries.into_iter().enumerate() {
338 let row = (i % 8) as f32;
339 let stack = (i / 8) as f32;
340 let file_y = row * slot_h_world;
341 let file_z = stack * stack_depth;
342
343 let file_group = universe.world.add_component(
344 engine::ecs::component::TransformComponent::new()
345 .with_position(col_x, file_y, file_z),
346 );
347
348 // Text subtree (tiny scale).
349 let file_root = universe.world.add_component(
350 engine::ecs::component::TransformComponent::new()
351 .with_position(0.0, 1.0, 0.0)
352 .with_scale(TEXT_SCALE, TEXT_SCALE, 1.0)
353 .with_rotation_euler(0.0, std::f32::consts::PI / 6.0, 0.0),
354 );
355 let _ = universe.attach(file_group, file_root);
356
357 // Background panel behind the text.
358 // Size is based on wrapped line count (matches TextSystem's strict wrapping behavior).
359 let (max_cols, rows) = (entry.max_cols, entry.rows);
360 let pad_x = 4.0;
361 let pad_y = 4.0;
362 let w = (max_cols as f32) + pad_x;
363 let h = (rows as f32) + pad_y;
364
365 // Text glyph quads are centered at integer (col,row) positions and are 1x1, so the
366 // text's AABB (in text-space) is roughly:
367 // x: [-0.5, max_cols-0.5]
368 // y: [-(rows-1)-0.5, 0.5]
369 // Centering the background quad at those midpoints keeps it aligned as text grows.
370 let bg_x = (max_cols as f32 - 1.0) * 0.5;
371 let bg_y = -((rows as f32 - 1.0) * 0.5);
372
373 let bg_transform = universe.world.add_component(
374 engine::ecs::component::TransformComponent::new()
375 .with_position(bg_x, bg_y, -0.05)
376 .with_scale(w, h, 1.0),
377 );
378 let bg_renderable = universe
379 .world
380 .add_component(engine::ecs::component::RenderableComponent::square());
381 let bg_quant = universe.world.add_component(
382 engine::ecs::component::LightQuantizationComponent::steps(5.0),
383 );
384 let bg_color =
385 universe
386 .world
387 .add_component(engine::ecs::component::ColorComponent::rgba(
388 0.2, 0.2, 0.2, 1.0,
389 ));
390 let _ = universe.attach(file_root, bg_transform);
391 let _ = universe.attach(bg_transform, bg_renderable);
392 let _ = universe.attach(bg_renderable, bg_quant);
393 let _ = universe.attach(bg_renderable, bg_color);
394
395 let text =
396 universe
397 .world
398 .add_component(engine::ecs::component::TextComponent::with_wrap(
399 entry.display_text,
400 WRAP_AT,
401 ));
402 let cutout = universe
403 .world
404 .add_component(engine::ecs::component::TransparentCutoutComponent::new());
405 let filtering = universe.world.add_component(
406 engine::ecs::component::TextureFilteringComponent::nearest_magnification(),
407 );
408 // let color = universe
409 // .world
410 // .add_component(engine::ecs::component::ColorComponent::rgba(0.7, 0.7, 1.0, 1.0));
411 let emissive = universe
412 .world
413 .add_component(engine::ecs::component::EmissiveComponent::on());
414 let _ = universe.attach(file_root, text);
415 let _ = universe.attach(text, cutout);
416 let _ = universe.attach(text, filtering);
417 //let _ = universe.world.add_child(text, color);
418 let _ = universe.attach(text, emissive);
419
420 universe.add(file_group);
421 }
422 }
423
424 // Process init-time registrations (Text expands into glyph subtrees here).
425 universe.systems.process_commands(
426 &mut universe.world,
427 &mut universe.visuals,
428 &mut universe.render_assets,
429 &mut universe.command_queue,
430 );
431
432 engine::Windowing::run_app(universe).expect("Windowing failed");
433}examples/gravity-fields.rs (line 761)
170fn main() {
171 mittens_engine::example_support::ensure_model_assets();
172 utils::logger::init();
173
174 let world = engine::ecs::World::default();
175 let mut universe = engine::Universe::new(world);
176
177 let bg_color = universe
178 .world
179 .add_component(engine::ecs::component::BackgroundColorComponent::new());
180 let bg_color_c = universe
181 .world
182 .add_component(engine::ecs::component::ColorComponent::rgba(
183 0.06, 0.04, 0.07, 1.0,
184 ));
185 let _ = universe.world.add_child(bg_color, bg_color_c);
186 universe.add(bg_color);
187
188 // overhead directional light
189 let directional_light = universe.world.add_component(
190 engine::ecs::component::DirectionalLightComponent::new()
191 .with_color(0.16, 0.14, 0.12)
192 .with_intensity(0.7),
193 );
194 let directional_light_t = universe.world.add_component(
195 engine::ecs::component::TransformComponent::new().with_position(0.0, 1.0, 0.0),
196 );
197 let _ = universe.attach(directional_light_t, directional_light);
198 universe.add(directional_light_t);
199
200 // Simple input-driven camera.
201 let input = universe
202 .world
203 .add_component(engine::ecs::component::InputComponent::new().with_speed(2.0));
204
205 let cam_t = universe.world.add_component(
206 engine::ecs::component::TransformComponent::new().with_position(0.0, 4.0, 10.0),
207 );
208 let cam = universe.world.add_component(
209 engine::ecs::component::Camera3DComponent::new()
210 // The default (150) clips background dressing (city + clouds).
211 .with_far(600.0)
212 .with_fov(70.0),
213 );
214
215 // Make the camera affect the collision system (same pattern as collision-perimeter).
216 let cam_collision = universe
217 .world
218 .add_component(engine::ecs::component::CollisionComponent::RIGGED());
219 let cam_response = universe
220 .world
221 .add_component(engine::ecs::component::KineticResponseComponent::slide());
222 let cam_shape =
223 universe
224 .world
225 .add_component(engine::ecs::component::CollisionShapeComponent::new(
226 engine::ecs::component::CollisionShape::sphere_radius(0.25),
227 ));
228
229 let input_mode = universe.world.add_component(
230 engine::ecs::component::InputTransformModeComponent::forward_z()
231 .with_roll_axis_y()
232 .with_fps_rotation(),
233 );
234
235 let _ = universe.attach(input, input_mode);
236 let _ = universe.attach(input, cam_t);
237 let _ = universe.attach(cam_t, cam);
238 let _ = universe.attach(cam_t, cam_collision);
239 let _ = universe.attach(cam_collision, cam_response);
240 let _ = universe.attach(cam_collision, cam_shape);
241
242 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
243 example_util::spawn_desktop_camera_controls_hint(&mut universe, cam_t);
244 universe.add(input);
245
246 let arena_half = 30.0;
247
248 // --- Background world (occluded + lit) ---
249 // Buildings are scene dressing and should not occlude foreground cubes.
250 let bg_root = universe.world.add_component(
251 engine::ecs::component::BackgroundComponent::new().with_occlusion_and_lighting(),
252 );
253 universe.add(bg_root);
254
255 // Background ground plane (rendered in background pass) with top surface at y=0.
256 // This should not occlude foreground due to depth clear between passes.
257 let bg_ground_thickness = 0.20;
258 let bg_ground_root_t = universe
259 .world
260 .add_component(engine::ecs::component::TransformComponent::new());
261 let bg_ground_geom_t = universe.world.add_component(
262 engine::ecs::component::TransformComponent::new()
263 .with_position(0.0, -bg_ground_thickness, 0.0)
264 .with_scale(
265 arena_half * 10.0,
266 bg_ground_thickness * 2.0,
267 arena_half * 10.0,
268 ),
269 );
270 let bg_ground_r = universe
271 .world
272 .add_component(engine::ecs::component::RenderableComponent::cube());
273 let bg_ground_c = universe
274 .world
275 .add_component(engine::ecs::component::ColorComponent::rgba(
276 0.03, 0.03, 0.035, 1.0,
277 ));
278 let _ = universe.attach(bg_root, bg_ground_root_t);
279 let _ = universe.attach(bg_ground_root_t, bg_ground_geom_t);
280 let _ = universe.attach(bg_ground_geom_t, bg_ground_r);
281 let _ = universe.attach(bg_ground_r, bg_ground_c);
282
283 // Background clouds (occluded + lit) using the shared example helper.
284 // NOTE: `spawn_cloud_ring` places a ring centered at the parent transform's origin.
285 // If we attach directly to `bg_root`, the ring can end up mostly out of view.
286 // So we offset a cloud root forward along -Z and keep the radius within the camera far clip.
287 let bg_cloud_root = universe.world.add_component(
288 engine::ecs::component::TransformComponent::new().with_position(
289 0.0,
290 0.0,
291 -arena_half * 2.8,
292 ),
293 );
294 let _ = universe.attach(bg_root, bg_cloud_root);
295
296 let mut cloud_params = example_util::CloudRingParams::default();
297 cloud_params.cloud_count = 9;
298 cloud_params.radius = arena_half * 1.9;
299 cloud_params.center_y = 18.0;
300 cloud_params.puffs_per_cloud = 26;
301 cloud_params.angle_jitter = 0.35;
302 cloud_params.high_y_probability = 0.35;
303 cloud_params.high_y_multiplier = 1.35;
304 cloud_params.seed = 0xC10_71A5u32;
305 example_util::spawn_cloud_ring(&mut universe, bg_cloud_root, cloud_params);
306
307 // Foreground city: NOT background-layer.
308 let fg_city_root = universe
309 .world
310 .add_component(engine::ecs::component::TransformComponent::new());
311 universe.add(fg_city_root);
312
313 // Background city: nested under the BackgroundComponent.
314 let bg_city_root = universe
315 .world
316 .add_component(engine::ecs::component::TransformComponent::new());
317 let _ = universe.attach(bg_root, bg_city_root);
318
319 fn hash_u32(mut x: u32) -> u32 {
320 // Small, deterministic integer hash (no external RNG dependency).
321 x ^= x >> 16;
322 x = x.wrapping_mul(0x7FEB_352D);
323 x ^= x >> 15;
324 x = x.wrapping_mul(0x846C_A68B);
325 x ^= x >> 16;
326 x
327 }
328
329 fn rand01(seed: u32) -> f32 {
330 let x = hash_u32(seed);
331 (x as f32) / (u32::MAX as f32)
332 }
333
334 fn spawn_floor(
335 universe: &mut engine::Universe,
336 building_root: engine::ecs::ComponentId,
337 floor_center_y: f32,
338 footprint_x: f32,
339 footprint_z: f32,
340 floor_h: f32,
341 floor_rgb: [f32; 4],
342 building_scale: f32,
343 window_mask: &[bool],
344 window_height_coeff: f32,
345 seed: u32,
346 ) {
347 // Floor geometry.
348 // IMPORTANT: don't put non-uniform scale on the node that windows attach to,
349 // otherwise child window meshes inherit the scale and look stretched.
350 let floor_root_t = universe.world.add_component(
351 engine::ecs::component::TransformComponent::new().with_position(
352 0.0,
353 floor_center_y,
354 0.0,
355 ),
356 );
357 let floor_geom_t = universe.world.add_component(
358 engine::ecs::component::TransformComponent::new().with_scale(
359 footprint_x,
360 floor_h,
361 footprint_z,
362 ),
363 );
364 let floor_r = universe
365 .world
366 .add_component(engine::ecs::component::RenderableComponent::cube());
367 let floor_c = universe
368 .world
369 .add_component(engine::ecs::component::ColorComponent::rgba(
370 floor_rgb[0],
371 floor_rgb[1],
372 floor_rgb[2],
373 floor_rgb[3],
374 ));
375
376 let _ = universe.attach(building_root, floor_root_t);
377 let _ = universe.attach(floor_root_t, floor_geom_t);
378 let _ = universe.attach(floor_geom_t, floor_r);
379 let _ = universe.attach(floor_r, floor_c);
380
381 // Windows: a single clean row per floor.
382 // Each floor receives a Vec<bool> (window_mask) that indicates which window slots are present.
383 let slots = window_mask.len().max(1);
384 let building_scale = building_scale.max(0.01);
385 let window_height_coeff = window_height_coeff.clamp(0.05, 2.0);
386
387 // Keep window size independent from floor height; floor height is already controlled by height_scale.
388 let nominal_window = 0.32 * building_scale;
389 let window_d = 0.06 * building_scale;
390
391 // Compute per-side spacing and clamp window size to fit.
392 let margin_x = (0.65 * nominal_window).min(0.18 * footprint_x);
393 let margin_z = (0.65 * nominal_window).min(0.18 * footprint_z);
394 let available_x = (footprint_x - 2.0 * margin_x).max(nominal_window);
395 let available_z = (footprint_z - 2.0 * margin_z).max(nominal_window);
396 let step_x = available_x / (slots as f32);
397 let step_z = available_z / (slots as f32);
398
399 let window_wx = nominal_window.min(step_x * 0.85);
400 let window_wz = nominal_window.min(step_z * 0.85);
401 let window_h = (nominal_window * window_height_coeff).min(floor_h * 0.80);
402
403 let warm = 0.70 + 0.25 * rand01(seed ^ 0x1F12_6A77);
404 let window_rgb = [1.0, 0.78 * warm, 0.48 * warm, 1.0];
405
406 let mut spawn_window = |local_pos: (f32, f32, f32), local_scale: (f32, f32, f32)| {
407 let window_t = universe.world.add_component(
408 engine::ecs::component::TransformComponent::new()
409 .with_position(local_pos.0, local_pos.1, local_pos.2)
410 .with_scale(local_scale.0, local_scale.1, local_scale.2),
411 );
412 let window_r = universe
413 .world
414 .add_component(engine::ecs::component::RenderableComponent::cube());
415 let window_c =
416 universe
417 .world
418 .add_component(engine::ecs::component::ColorComponent::rgba(
419 window_rgb[0],
420 window_rgb[1],
421 window_rgb[2],
422 window_rgb[3],
423 ));
424 let window_e = universe
425 .world
426 .add_component(engine::ecs::component::EmissiveComponent::on());
427
428 let _ = universe.attach(floor_root_t, window_t);
429 let _ = universe.attach(window_t, window_r);
430 let _ = universe.attach(window_r, window_c);
431 let _ = universe.attach(window_r, window_e);
432 };
433
434 // Place the row at the center of the floor segment.
435 let wy = 0.0;
436
437 for i in 0..slots {
438 if !window_mask[i] {
439 continue;
440 }
441 let tx = -0.5 * footprint_x + margin_x + (i as f32 + 0.5) * step_x;
442 let tz = -0.5 * footprint_z + margin_z + (i as f32 + 0.5) * step_z;
443
444 // +Z/-Z faces: windows laid out along X.
445 for z_sign in [1.0_f32, -1.0_f32] {
446 spawn_window(
447 (tx, wy, z_sign * (footprint_z * 0.5 + window_d * 0.6)),
448 (window_wx, window_h, window_d),
449 );
450 }
451
452 // +X/-X faces: windows laid out along Z.
453 for x_sign in [1.0_f32, -1.0_f32] {
454 spawn_window(
455 (x_sign * (footprint_x * 0.5 + window_d * 0.6), wy, tz),
456 (window_d, window_h, window_wz),
457 );
458 }
459 }
460 }
461
462 fn spawn_building(
463 universe: &mut engine::Universe,
464 parent: engine::ecs::ComponentId,
465 x: f32,
466 y_offset: f32,
467 z: f32,
468 floors: u32,
469 seed: u32,
470 building_scale: f32,
471 height_scale: f32,
472 total_windows_per_floor_per_building_side: u32,
473 window_height_coeff: f32,
474 ) {
475 let building_scale = building_scale.max(0.01);
476 let height_scale = height_scale.max(0.01);
477
478 let floors = floors.max(1);
479 let windows_per_side = total_windows_per_floor_per_building_side.max(1) as usize;
480
481 let footprint_x = 3.2 * building_scale;
482 let footprint_z = 3.2 * building_scale;
483 let floor_h = 1.15 * building_scale * height_scale;
484
485 let base_t = universe.world.add_component(
486 engine::ecs::component::TransformComponent::new().with_position(x, y_offset, z),
487 );
488 let _ = universe.attach(parent, base_t);
489
490 // Slight per-building tint.
491 let tint = 0.03 * (rand01(seed ^ 0xA511_E9B3) - 0.5);
492 let base_rgb = [0.08 + tint, 0.085 + tint, 0.095 + tint, 1.0];
493
494 // Probability that a given window slot exists.
495 // (False means no window cube at that slot.)
496 let window_fill_probability = 0.80_f32;
497
498 for floor_i in 0..floors {
499 let floor_seed = seed ^ floor_i.wrapping_mul(0x9E37_79B9) ^ 0xB17D_1E55;
500
501 let mut window_mask = Vec::with_capacity(windows_per_side);
502 for i in 0..windows_per_side {
503 let s = floor_seed ^ (i as u32).wrapping_mul(0x85EB_CA6B) ^ 0x243F_6A88;
504 window_mask.push(rand01(s) < window_fill_probability);
505 }
506
507 let floor_center_y = floor_h * (floor_i as f32) + floor_h * 0.5;
508 spawn_floor(
509 universe,
510 base_t,
511 floor_center_y,
512 footprint_x,
513 footprint_z,
514 floor_h,
515 base_rgb,
516 building_scale,
517 &window_mask,
518 window_height_coeff,
519 floor_seed,
520 );
521 }
522 }
523
524 fn spawn_city(
525 universe: &mut engine::Universe,
526 parent: engine::ecs::ComponentId,
527 columns: u32,
528 spacing: f32,
529 position_scale: f32,
530 building_scale: f32,
531 height_scale: f32,
532 seed_base: u32,
533 y_offset: f32,
534 total_windows_per_floor_per_building_side: u32,
535 window_height_coeff: f32,
536 ) {
537 let columns = columns.max(1);
538 let half = (columns as i32) / 2;
539
540 for gz in -half..=half {
541 for gx in -half..=half {
542 // Hole in the middle.
543 if gx == 0 && gz == 0 {
544 continue;
545 }
546
547 let seed = seed_base
548 ^ ((gx + half) as u32).wrapping_mul(0x9E37_79B9)
549 ^ ((gz + half) as u32).wrapping_mul(0x85EB_CA6B)
550 ^ 0xB17D_1E55;
551
552 let floors = 3 + (hash_u32(seed) % 10); // 3..=12 floors
553
554 // Centered around the origin.
555 // - position_scale controls how far out the city sits (positions only)
556 // - building_scale controls building footprint/window sizes
557 let x = (gx as f32) * spacing * position_scale;
558 let z = (gz as f32) * spacing * position_scale;
559
560 spawn_building(
561 universe,
562 parent,
563 x,
564 y_offset,
565 z,
566 floors,
567 seed,
568 building_scale,
569 height_scale,
570 total_windows_per_floor_per_building_side,
571 window_height_coeff,
572 );
573 }
574 }
575 }
576
577 // // Foreground city: 5x5 (hole in center), scaled out to sit around the floor.
578 // spawn_city(
579 // &mut universe,
580 // fg_city_root,
581 // 5,
582 // 7.0,
583 // 2.0,
584 // 1.0,
585 // 1.0,
586 // 0xC170_0001u32,
587 // 3,
588 // 2,
589 // -0.0
590 // );
591
592 // Background city: larger grid, farther out, taller buildings.
593 spawn_city(
594 &mut universe,
595 bg_city_root,
596 11,
597 3.0,
598 3.0,
599 0.45,
600 1.0,
601 0xC170_0002u32,
602 -2.0,
603 6,
604 0.75,
605 );
606
607 fn spawn_street_light(universe: &mut engine::Universe) -> engine::ecs::ComponentId {
608 // Dimensions (world units).
609 let shaft_height = 6.0;
610 let shaft_thickness = 0.22;
611 let arm_length = shaft_height / 3.0;
612 let arm_thickness = 0.18;
613
614 // Colors.
615 let pole_color = [0.35, 0.35, 0.38, 1.0];
616 let housing_color = [0.18, 0.18, 0.20, 1.0];
617 let diffuser_color = [1.0, 1.0, 1.0, 1.0];
618
619 // Model root at origin.
620 // The caller is expected to position + rotate this model using an external placement transform.
621 // Street light is authored facing +X (arm extends toward +X).
622 let root_t = universe
623 .world
624 .add_component(engine::ecs::component::TransformComponent::new());
625
626 // Vertical shaft (split root/geom so scaling doesn't affect the arm).
627 let shaft_root_t = universe
628 .world
629 .add_component(engine::ecs::component::TransformComponent::new());
630 let shaft_geom_t = universe.world.add_component(
631 engine::ecs::component::TransformComponent::new()
632 .with_position(0.0, shaft_height * 0.5, 0.0)
633 .with_scale(shaft_thickness, shaft_height, shaft_thickness),
634 );
635 let shaft_r = universe
636 .world
637 .add_component(engine::ecs::component::RenderableComponent::cube());
638 let shaft_c = universe
639 .world
640 .add_component(engine::ecs::component::ColorComponent::rgba(
641 pole_color[0],
642 pole_color[1],
643 pole_color[2],
644 pole_color[3],
645 ));
646
647 let _ = universe.attach(root_t, shaft_root_t);
648 let _ = universe.attach(shaft_root_t, shaft_geom_t);
649 let _ = universe.attach(shaft_geom_t, shaft_r);
650 let _ = universe.attach(shaft_r, shaft_c);
651
652 // Horizontal arm at the top; starts at the pole and extends toward +X.
653 let arm_root_t = universe.world.add_component(
654 engine::ecs::component::TransformComponent::new().with_position(0.0, shaft_height, 0.0),
655 );
656 let arm_geom_t = universe.world.add_component(
657 engine::ecs::component::TransformComponent::new()
658 .with_position(arm_length * 0.5, -arm_thickness * 0.5, 0.0)
659 .with_scale(arm_length, arm_thickness, arm_thickness),
660 );
661 let arm_r = universe
662 .world
663 .add_component(engine::ecs::component::RenderableComponent::cube());
664 let arm_c = universe
665 .world
666 .add_component(engine::ecs::component::ColorComponent::rgba(
667 pole_color[0],
668 pole_color[1],
669 pole_color[2],
670 pole_color[3],
671 ));
672
673 let _ = universe.attach(shaft_root_t, arm_root_t);
674 let _ = universe.attach(arm_root_t, arm_geom_t);
675 let _ = universe.attach(arm_geom_t, arm_r);
676 let _ = universe.attach(arm_r, arm_c);
677
678 // Light housing at the end of the arm (+X end).
679 let housing_root_t = universe.world.add_component(
680 engine::ecs::component::TransformComponent::new().with_position(arm_length, 0.0, 0.0),
681 );
682 let housing_geom_t = universe.world.add_component(
683 engine::ecs::component::TransformComponent::new()
684 .with_position(0.5, 0.0, 0.0)
685 // 2x1x1 box in (z, x, y) -> (x, y, z) = (1, 1, 2)
686 .with_scale(2.0, 1.0, 1.0),
687 );
688 let housing_r = universe
689 .world
690 .add_component(engine::ecs::component::RenderableComponent::cube());
691 let housing_c = universe
692 .world
693 .add_component(engine::ecs::component::ColorComponent::rgba(
694 housing_color[0],
695 housing_color[1],
696 housing_color[2],
697 housing_color[3],
698 ));
699
700 let _ = universe.attach(arm_root_t, housing_root_t);
701 let _ = universe.attach(housing_root_t, housing_geom_t);
702 let _ = universe.attach(housing_geom_t, housing_r);
703 let _ = universe.attach(housing_r, housing_c);
704
705 // White diffuser cube (slightly smaller, slightly lower).
706 let diffuser_t = universe.world.add_component(
707 engine::ecs::component::TransformComponent::new()
708 .with_position(0.5, -0.35, 0.0)
709 .with_scale(1.85, 0.55, 0.70),
710 );
711 let diffuser_r = universe
712 .world
713 .add_component(engine::ecs::component::RenderableComponent::cube());
714 let diffuser_c =
715 universe
716 .world
717 .add_component(engine::ecs::component::ColorComponent::rgba(
718 diffuser_color[0],
719 diffuser_color[1],
720 diffuser_color[2],
721 diffuser_color[3],
722 ));
723
724 let diffuser_emissive = universe
725 .world
726 .add_component(engine::ecs::component::EmissiveComponent::on());
727 let _ = universe.attach(diffuser_r, diffuser_emissive);
728
729 let _ = universe.attach(housing_root_t, diffuser_t);
730 let _ = universe.attach(diffuser_t, diffuser_r);
731 let _ = universe.attach(diffuser_r, diffuser_c);
732
733 // Yellow (slightly red) point light.
734 let light = universe.world.add_component(
735 engine::ecs::component::PointLightComponent::new()
736 .with_color(1.0, 0.82, 0.55)
737 .with_intensity(5.0)
738 .with_distance(40.0),
739 );
740 let _ = universe.attach(diffuser_t, light);
741
742 root_t
743 }
744
745 // Spawn 6 street lights around the arena.
746 // Arms point inward from each side.
747 let light_x = arena_half - 2.0;
748 for z in [-10.0, 0.0, 10.0] {
749 // Left side: street light model faces +X by default, so it points inward.
750 let left_place = universe.world.add_component(
751 engine::ecs::component::TransformComponent::new().with_position(-light_x, 0.0, z),
752 );
753 let left_model = spawn_street_light(&mut universe);
754 let _ = universe.attach(left_place, left_model);
755 universe.add(left_place);
756
757 // Right side: rotate 180° around Y so the arm points toward -X (inward).
758 let right_place = universe.world.add_component(
759 engine::ecs::component::TransformComponent::new()
760 .with_position(light_x, 0.0, z)
761 .with_rotation_euler(0.0, std::f32::consts::PI, 0.0),
762 );
763 let right_model = spawn_street_light(&mut universe);
764 let _ = universe.attach(right_place, right_model);
765 universe.add(right_place);
766 }
767
768 // Big floor.
769 {
770 let floor_half = arena_half;
771 let thickness = 0.20;
772
773 // Split transforms so scaling the floor does not scale any potential children.
774 // Top surface at y=0.
775 let floor_root_t = universe
776 .world
777 .add_component(engine::ecs::component::TransformComponent::new());
778 let floor_geom_t = universe.world.add_component(
779 engine::ecs::component::TransformComponent::new()
780 .with_position(0.0, -thickness, 0.0)
781 .with_scale(floor_half * 2.0, thickness * 2.0, floor_half * 2.0),
782 );
783 let floor_r = universe
784 .world
785 .add_component(engine::ecs::component::RenderableComponent::cube());
786 let floor_color =
787 universe
788 .world
789 .add_component(engine::ecs::component::ColorComponent::rgba(
790 0.08, 0.08, 0.09, 1.0,
791 ));
792
793 let floor_cn = universe
794 .world
795 .add_component(engine::ecs::component::CollisionComponent::STATIC());
796 let floor_shape =
797 universe
798 .world
799 .add_component(engine::ecs::component::CollisionShapeComponent::new(
800 engine::ecs::component::CollisionShape::cube_half_extents([
801 floor_half, thickness, floor_half,
802 ]),
803 ));
804
805 let _ = universe.attach(floor_root_t, floor_geom_t);
806 let _ = universe.attach(floor_geom_t, floor_r);
807 let _ = universe.attach(floor_r, floor_color);
808 let _ = universe.attach(floor_geom_t, floor_cn);
809 let _ = universe.attach(floor_cn, floor_shape);
810 universe.add(floor_root_t);
811 }
812
813 // Square boundary walls around the floor edges.
814 // Note: STATIC colliders don't need KineticResponse; they still collide with kinematic/rigged.
815 fn spawn_boundary_wall(
816 universe: &mut engine::Universe,
817 x: f32,
818 y: f32,
819 z: f32,
820 half_extents: [f32; 3],
821 color: [f32; 4],
822 ) {
823 let t = universe.world.add_component(
824 engine::ecs::component::TransformComponent::new()
825 .with_position(x, y, z)
826 .with_scale(
827 half_extents[0] * 2.0,
828 half_extents[1] * 2.0,
829 half_extents[2] * 2.0,
830 ),
831 );
832 let r = universe
833 .world
834 .add_component(engine::ecs::component::RenderableComponent::cube());
835 let c = universe
836 .world
837 .add_component(engine::ecs::component::ColorComponent::rgba(
838 color[0], color[1], color[2], color[3],
839 ));
840
841 let cn = universe
842 .world
843 .add_component(engine::ecs::component::CollisionComponent::STATIC());
844 let shape =
845 universe
846 .world
847 .add_component(engine::ecs::component::CollisionShapeComponent::new(
848 engine::ecs::component::CollisionShape::cube_half_extents(half_extents),
849 ));
850
851 let _ = universe.attach(t, r);
852 let _ = universe.attach(r, c);
853 let _ = universe.attach(t, cn);
854 let _ = universe.attach(cn, shape);
855 universe.add(t);
856 }
857
858 {
859 let wall_half_height = 2.5;
860 let wall_half_thickness = 0.30;
861 let wall_center_y = wall_half_height; // bottom at y=0
862 let wall_half_len = arena_half;
863
864 // Subtle visible walls.
865 let wall_color = [0.10, 0.10, 0.12, 0.30];
866
867 // +Z / -Z
868 spawn_boundary_wall(
869 &mut universe,
870 0.0,
871 wall_center_y,
872 arena_half,
873 [wall_half_len, wall_half_height, wall_half_thickness],
874 wall_color,
875 );
876 spawn_boundary_wall(
877 &mut universe,
878 0.0,
879 wall_center_y,
880 -arena_half,
881 [wall_half_len, wall_half_height, wall_half_thickness],
882 wall_color,
883 );
884
885 // +X / -X
886 spawn_boundary_wall(
887 &mut universe,
888 arena_half,
889 wall_center_y,
890 0.0,
891 [wall_half_thickness, wall_half_height, wall_half_len],
892 wall_color,
893 );
894 spawn_boundary_wall(
895 &mut universe,
896 -arena_half,
897 wall_center_y,
898 0.0,
899 [wall_half_thickness, wall_half_height, wall_half_len],
900 wall_color,
901 );
902 }
903
904 fn spawn_falling_cube(
905 universe: &mut engine::Universe,
906 parent: engine::ecs::ComponentId,
907 x: f32,
908 y: f32,
909 z: f32,
910 s: f32,
911 r: f32,
912 g: f32,
913 b: f32,
914 ) {
915 let t = universe.world.add_component(
916 engine::ecs::component::TransformComponent::new()
917 .with_position(x, y, z)
918 .with_scale(s, s, s),
919 );
920
921 let renderable = universe
922 .world
923 .add_component(engine::ecs::component::RenderableComponent::cube());
924 let color = universe
925 .world
926 .add_component(engine::ecs::component::ColorComponent::rgba(r, g, b, 1.0));
927
928 let cn = universe
929 .world
930 .add_component(engine::ecs::component::CollisionComponent::KINEMATIC());
931
932 let response = universe.world.add_component({
933 let mut r = engine::ecs::component::KineticResponseComponent::push()
934 .with_push_strength(3.0)
935 .with_friction_y(18.0);
936 // Allow higher fall speeds so gravity coefficients are visible.
937 r.max_speed = 80.0;
938 r
939 });
940
941 let shape =
942 universe
943 .world
944 .add_component(engine::ecs::component::CollisionShapeComponent::new(
945 engine::ecs::component::CollisionShape::cube_half_extents([
946 0.5 * s,
947 0.5 * s,
948 0.5 * s,
949 ]),
950 ));
951
952 let _ = universe.attach(t, renderable);
953 let _ = universe.attach(renderable, color);
954
955 let _ = universe.attach(t, cn);
956 let _ = universe.attach(cn, response);
957 let _ = universe.attach(cn, shape);
958
959 let _ = universe.attach(parent, t);
960 }
961
962 // Three gravity fields, each with 20 cubes.
963 let field_low = universe
964 .world
965 .add_component(engine::ecs::component::GravityComponent::new().with_coefficient(0.125));
966 let field_mid = universe
967 .world
968 .add_component(engine::ecs::component::GravityComponent::new().with_coefficient(0.5));
969 let field_high = universe
970 .world
971 .add_component(engine::ecs::component::GravityComponent::new().with_coefficient(1.0));
972
973 universe.add(field_low);
974 universe.add(field_mid);
975 universe.add(field_high);
976
977 fn spawn_field(
978 universe: &mut engine::Universe,
979 field: engine::ecs::ComponentId,
980 base_x: f32,
981 base_z: f32,
982 cube_color: [f32; 3],
983 ) {
984 let spacing = 0.7;
985 let start_x = base_x - 2.0 * spacing;
986 let start_z = base_z - 1.5 * spacing;
987
988 for i in 0..20 {
989 let ix = (i % 5) as f32;
990 let iz = (i / 5) as f32;
991
992 let x = start_x + ix * spacing;
993 let z = start_z + iz * spacing;
994 let y = 2.0 + iz * 0.6 + ix * 0.1;
995
996 spawn_falling_cube(
997 universe,
998 field,
999 x,
1000 y,
1001 z,
1002 0.5,
1003 cube_color[0],
1004 cube_color[1],
1005 cube_color[2],
1006 );
1007 }
1008
1009 // Visual marker for the field center.
1010 let marker_t = universe.world.add_component(
1011 engine::ecs::component::TransformComponent::new()
1012 .with_position(base_x, 0.1, base_z)
1013 .with_scale(0.3, 0.02, 0.3),
1014 );
1015 let marker_r = universe
1016 .world
1017 .add_component(engine::ecs::component::RenderableComponent::cube());
1018 let marker_c = universe
1019 .world
1020 .add_component(engine::ecs::component::ColorComponent::rgba(
1021 cube_color[0],
1022 cube_color[1],
1023 cube_color[2],
1024 1.0,
1025 ));
1026 let _ = universe.attach(marker_t, marker_r);
1027 let _ = universe.attach(marker_r, marker_c);
1028 universe.add(marker_t);
1029 }
1030
1031 spawn_field(&mut universe, field_low, -8.0, 0.0, [0.3, 0.6, 1.0]);
1032 spawn_field(&mut universe, field_mid, 0.0, 0.0, [0.4, 1.0, 0.4]);
1033 spawn_field(&mut universe, field_high, 8.0, 0.0, [1.0, 0.5, 0.2]);
1034
1035 // Group-scoped collision behaviors.
1036 // - Low + High fields: cubes turn white after colliding with another cube.
1037 // - Mid field: cubes lose gravity after colliding with another cube.
1038 universe.add_signal_handler(
1039 engine::ecs::SignalKind::CollisionStarted,
1040 field_low,
1041 on_collision_turn_white,
1042 );
1043 universe.add_signal_handler(
1044 engine::ecs::SignalKind::CollisionStarted,
1045 field_high,
1046 on_collision_turn_white,
1047 );
1048 universe.add_signal_handler(
1049 engine::ecs::SignalKind::CollisionStarted,
1050 field_mid,
1051 on_collision_freeze_gravity,
1052 );
1053
1054 universe.systems.process_commands(
1055 &mut universe.world,
1056 &mut universe.visuals,
1057 &mut universe.render_assets,
1058 &mut universe.command_queue,
1059 );
1060
1061 universe.enable_repl();
1062 engine::Windowing::run_app(universe).expect("Windowing failed");
1063}Sourcepub fn with_rotation_quat(self, quat_xyzw: [f32; 4]) -> Self
pub fn with_rotation_quat(self, quat_xyzw: [f32; 4]) -> Self
Builder-style: set rotation from a quaternion (xyzw), returns Self.
pub fn with_looking_at(self, target_world: [f32; 3]) -> Self
pub fn look_at_world_rotation( world_position: [f32; 3], target_world: [f32; 3], ) -> Option<[f32; 4]>
Sourcepub fn set_rotation_euler(
&mut self,
emit: &mut dyn SignalEmitter,
pitch_x: f32,
yaw_y: f32,
roll_z: f32,
)
pub fn set_rotation_euler( &mut self, emit: &mut dyn SignalEmitter, pitch_x: f32, yaw_y: f32, roll_z: f32, )
Set rotation from Euler angles (radians), XYZ order, and queue update.
Sourcepub fn set_rotation_quat(
&mut self,
emit: &mut dyn SignalEmitter,
quat_xyzw: [f32; 4],
)
pub fn set_rotation_quat( &mut self, emit: &mut dyn SignalEmitter, quat_xyzw: [f32; 4], )
Set rotation from a quaternion (xyzw) and queue update.
Sourcepub fn set_position(
&mut self,
emit: &mut dyn SignalEmitter,
x: f32,
y: f32,
z: f32,
)
pub fn set_position( &mut self, emit: &mut dyn SignalEmitter, x: f32, y: f32, z: f32, )
Set translation and queue update.
Trait Implementations§
Source§impl Clone for TransformComponent
impl Clone for TransformComponent
Source§fn clone(&self) -> TransformComponent
fn clone(&self) -> TransformComponent
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 TransformComponent
impl Component for TransformComponent
Source§fn name(&self) -> &'static str
fn name(&self) -> &'static str
Short debug/type name for this component kind (e.g. “transform”, “camera”).
fn set_id(&mut self, component: ComponentId)
fn as_any(&self) -> &dyn Any
fn as_any_mut(&mut self) -> &mut dyn Any
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 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
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.
impl Copy for TransformComponent
Source§impl Debug for TransformComponent
impl Debug for TransformComponent
Auto Trait Implementations§
impl Freeze for TransformComponent
impl RefUnwindSafe for TransformComponent
impl Send for TransformComponent
impl Sync for TransformComponent
impl Unpin for TransformComponent
impl UnsafeUnpin for TransformComponent
impl UnwindSafe for TransformComponent
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.