Skip to main content

TransformComponent

Struct TransformComponent 

Source
pub struct TransformComponent {
    pub transform: Transform,
    pub pending_look_at_target_world: Option<[f32; 3]>,
    /* private fields */
}

Fields§

§transform: Transform

Engine-wide transform type (also used by renderer/VisualWorld).

§pending_look_at_target_world: Option<[f32; 3]>

Implementations§

Source§

impl TransformComponent

Source

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
Hide additional 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}
Source

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
Hide additional 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}
Source

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
Hide additional 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    }
Source

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
Hide additional 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}
Source

pub fn with_rotation_quat(self, quat_xyzw: [f32; 4]) -> Self

Builder-style: set rotation from a quaternion (xyzw), returns Self.

Source

pub fn with_looking_at(self, target_world: [f32; 3]) -> Self

Source

pub fn look_at_world_rotation( world_position: [f32; 3], target_world: [f32; 3], ) -> Option<[f32; 4]>

Source

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.

Source

pub fn set_rotation_quat( &mut self, emit: &mut dyn SignalEmitter, quat_xyzw: [f32; 4], )

Set rotation from a quaternion (xyzw) and queue update.

Source

pub fn set_position( &mut self, emit: &mut dyn SignalEmitter, x: f32, y: f32, z: f32, )

Set translation and queue update.

Source

pub fn set_scale( &mut self, emit: &mut dyn SignalEmitter, x: f32, y: f32, z: f32, )

Set non-uniform scale and queue update.

Trait Implementations§

Source§

impl Clone for TransformComponent

Source§

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)

Performs copy-assignment from source. Read more
Source§

impl Component for TransformComponent

Source§

fn name(&self) -> &'static str

Short debug/type name for this component kind (e.g. “transform”, “camera”).
Source§

fn set_id(&mut self, component: ComponentId)

Source§

fn as_any(&self) -> &dyn Any

Source§

fn as_any_mut(&mut self) -> &mut dyn Any

Source§

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

Encode this component as an MMS Component Expression AST node. Read more
Source§

fn cleanup(&mut self, _emit: &mut dyn SignalEmitter, _component: ComponentId)

Called when component is removed from the World.
Source§

fn encode(&self) -> HashMap<String, Value>

Encode component data to a HashMap for serialization. Read more
Source§

fn decode(&mut self, _data: &HashMap<String, Value>) -> Result<(), String>

Decode component data from a HashMap after deserialization. Read more
Source§

impl Copy for TransformComponent

Source§

impl Debug for TransformComponent

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for TransformComponent

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> Downcast for T
where T: Any,

Source§

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>

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)

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)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send>

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<S, T> Duplex<S> for T
where T: FromSample<S> + ToSample<S>,

Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<S> FromSample<S> for S

Source§

fn from_sample_(s: S) -> S

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<F, T> IntoSample<T> for F
where T: FromSample<F>,

Source§

fn into_sample(self) -> T

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> ToSample<U> for T
where U: FromSample<T>,

Source§

fn to_sample_(self) -> U

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more