Skip to main content

PointLightComponent

Struct PointLightComponent 

Source
pub struct PointLightComponent {
    pub intensity: f32,
    pub distance: f32,
    pub color: [f32; 3],
    /* private fields */
}
Expand description

Point light (local omnidirectional light).

This is purely an ECS representation for now; renderer integration will come later.

Fields§

§intensity: f32§distance: f32§color: [f32; 3]

Linear RGB color in 0..1.

Implementations§

Source§

impl PointLightComponent

Source

pub fn new() -> Self

Examples found in repository?
examples/folder-text.rs (line 270)
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}
More examples
Hide additional examples
examples/collision-perimeter.rs (line 104)
86    fn spawn_light(
87        universe: &mut engine::Universe,
88        x: f32,
89        y: f32,
90        z: f32,
91        r: f32,
92        g: f32,
93        b: f32,
94        intensity: f32,
95        distance: f32,
96    ) {
97        let t = universe.world.add_component(
98            engine::ecs::component::TransformComponent::new()
99                .with_position(x, y, z)
100                .with_scale(0.12, 0.12, 0.12),
101        );
102
103        let l = universe.world.add_component(
104            engine::ecs::component::PointLightComponent::new()
105                .with_color(r, g, b)
106                .with_intensity(intensity)
107                .with_distance(distance),
108        );
109        // Visual marker: a small white cube at the light position.
110        // White ensures it mostly shows the light color.
111        let marker = universe
112            .world
113            .add_component(engine::ecs::component::RenderableComponent::cube());
114        let marker_color =
115            universe
116                .world
117                .add_component(engine::ecs::component::ColorComponent::rgba(
118                    1.0, 1.0, 1.0, 1.0,
119                ));
120
121        let _ = universe.attach(t, l);
122        let _ = universe.attach(t, marker);
123        let _ = universe.attach(marker, marker_color);
124        universe.add(t);
125    }
examples/opacity-example.rs (line 389)
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/text-animation.rs (line 40)
6fn main() {
7    mittens_engine::example_support::ensure_model_assets();
8    utils::logger::init();
9
10    let world = engine::ecs::World::default();
11    let mut universe = engine::Universe::new(world);
12
13    // Input-driven camera rig.
14    // Topology: I { T { C3D } }
15    let input = universe
16        .world
17        .add_component(engine::ecs::component::InputComponent::new().with_speed(1.5));
18    let camera3d = universe
19        .world
20        .add_component(engine::ecs::component::Camera3DComponent::new());
21    let rig_transform = universe.world.add_component(
22        engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 2.5),
23    );
24    let input_mode = universe.world.add_component(
25        engine::ecs::component::InputTransformModeComponent::forward_z().with_roll_axis_y(),
26    );
27    let _ = universe.attach(input, input_mode);
28    let _ = universe.attach(input, rig_transform);
29    let _ = universe.attach(rig_transform, camera3d);
30
31    // Small on-screen help.
32    example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
33    universe.add(input);
34
35    // Light so we can see non-emissive materials.
36    let light_transform = universe.world.add_component(
37        engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 2.0),
38    );
39    let light = universe.world.add_component(
40        engine::ecs::component::PointLightComponent::new()
41            .with_distance(25.0)
42            .with_color(1.0, 1.0, 1.0),
43    );
44    let _ = universe.attach(light_transform, light);
45    universe.add(light_transform);
46
47    use engine::ecs::component::{
48        ColorComponent, TextComponent, TextShadowComponent, TextureComponent,
49        TextureFilteringComponent, TransformComponent, TransparentCutoutComponent,
50    };
51
52    // Styled text root.
53    let text_root = universe.world.add_component(
54        TransformComponent::new()
55            .with_position(0.0, 0.0, 0.0)
56            .with_scale(0.25, 0.25, 1.0),
57    );
58
59    // Color must be an ancestor of the glyph renderables.
60    let color_id = universe
61        .world
62        .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
63    let _ = universe.attach(text_root, color_id);
64
65    // This is the component we animate via SetText.
66    let text_id = universe.world.add_component(TextComponent::new(">w<"));
67    let _ = universe.attach(color_id, text_id);
68
69    // Route into cutout pass for cleaner edges.
70    let cutout = universe
71        .world
72        .add_component(TransparentCutoutComponent::new());
73    let _ = universe.attach(text_id, cutout);
74
75    // Font atlas.
76    let tex = universe.world.add_component(TextureComponent::with_uri(
77        "assets/textures/font_system.dds",
78    ));
79    let _ = universe.attach(text_id, tex);
80
81    let shadow = universe.world.add_component(
82        TextShadowComponent::new()
83            .with_scale(1.35)
84            .with_offset([0.06, -0.06, 0.0015]),
85    );
86    let filtering = universe
87        .world
88        .add_component(TextureFilteringComponent::nearest_magnification());
89
90    let _ = universe.attach(text_id, shadow);
91    let _ = universe.attach(text_id, filtering);
92
93    universe.add(text_root);
94
95    let clock_component = universe
96        .world
97        .add_component(engine::ecs::component::ClockComponent::new().with_bpm(140.0));
98    let _ = universe.add(clock_component);
99
100    // Looping animation: 4 beats long, 4 keyframes.
101    let anim = universe
102        .world
103        .add_component(engine::ecs::component::AnimationComponent::new());
104
105    let faces = [">w<", "^w^", "-_-", "o_o"];
106    for (i, &face) in faces.iter().enumerate() {
107        let kf = universe
108            .world
109            .add_component(engine::ecs::component::KeyframeComponent::new(i as f64));
110
111        // Each keyframe emits a SetText intent.
112        let action = universe
113            .world
114            .add_component(engine::ecs::component::ActionComponent::new(
115                engine::ecs::IntentValue::SetText {
116                    component_ids: vec![text_id],
117                    text: face.to_string(),
118                },
119            ));
120
121        let _ = universe.attach(anim, kf);
122        let _ = universe.attach(kf, action);
123    }
124
125    universe.add(anim);
126
127    // Process init-time registrations (Text expands into glyph subtrees here).
128    universe.systems.process_commands(
129        &mut universe.world,
130        &mut universe.visuals,
131        &mut universe.render_assets,
132        &mut universe.command_queue,
133    );
134
135    engine::Windowing::run_app(universe).expect("Windowing failed");
136}
examples/gravity-fields.rs (line 735)
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    }
examples/vtuber-example.rs (line 79)
11fn main() {
12    mittens_engine::example_support::ensure_model_assets();
13    utils::logger::init();
14
15    let world = engine::ecs::World::default();
16    let mut universe = engine::Universe::new(world);
17
18    // Light pink background.
19    let background = universe
20        .world
21        .add_component(BackgroundColorComponent::new());
22    let background_c = universe
23        .world
24        .add_component(ColorComponent::rgba(1.0, 0.82, 0.90, 1.0));
25    let _ = universe.world.add_child(background, background_c);
26    universe.add(background);
27
28    // Small ambient so shadowed areas aren't pure black.
29    let ambient = universe
30        .world
31        .add_component(AmbientLightComponent::rgb(0.10, 0.10, 0.12));
32    universe.add(ambient);
33
34    // --- Camera rig (WASD + mouse) ---
35    // InputComponent is the root, and it owns a Transform (the camera rig).
36    let input = universe
37        .world
38        .add_component(InputComponent::new().with_speed(1.5));
39    let input_mode = universe.world.add_component(
40        InputTransformModeComponent::forward_z()
41            .with_fps_rotation()
42            .with_roll_axis_y(),
43    );
44    let _ = universe.attach(input, input_mode);
45
46    // Start slightly pulled back looking towards the origin.
47    let rig_transform = universe
48        .world
49        .add_component(TransformComponent::new().with_position(0.0, 0.0, 6.0));
50    let _ = universe.attach(input, rig_transform);
51
52    let camera3d = universe.world.add_component(Camera3DComponent::new());
53    let _ = universe.attach(rig_transform, camera3d);
54
55    // Topology: I { T { C3D } } — add a small camera-attached controls hint.
56    example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
57
58    universe.add(input);
59
60    // --- lighting ---
61    // Directional key light (slightly down + forward Z).
62    let sun = universe.world.add_component(
63        DirectionalLightComponent::new()
64            .with_intensity(1.2)
65            .with_color(1.0, 0.98, 0.94),
66    );
67    let sun_dir = universe
68        .world
69        .add_component(TransformComponent::new().with_position(0.0, -0.35, 1.0));
70    let _ = universe.attach(sun_dir, sun);
71    universe.add(sun_dir);
72
73    let light_transform = universe.world.add_component(
74        TransformComponent::new()
75            .with_position(1.0, 6.0, 3.0)
76            .with_scale(0.1, 0.1, 0.1),
77    );
78    let light = universe.world.add_component(
79        engine::ecs::component::PointLightComponent::new()
80            .with_distance(120.0)
81            .with_color(1.0, 1.0, 1.0),
82    );
83    let _ = universe.attach(light_transform, light);
84    universe.add(light_transform);
85
86    // --- VTuber model ---
87    let model_root = universe.world.add_component(TransformComponent::new());
88    let model = universe
89        .world
90        .add_component(GLTFComponent::new("assets/models/pc-rei.hoodie.glb"));
91    // emissive for pc-rei
92    let emissive = universe
93        .world
94        .add_component(engine::ecs::component::EmissiveComponent::on());
95
96    let _ = universe.attach(model, emissive);
97
98    let xr_input = universe.world.add_component(InputXRComponent::on());
99    let xr_gamepad = universe
100        .world
101        .add_component(engine::ecs::component::InputXRGamepadComponent::new().speed(1.5));
102    let xr_head = universe.world.add_component(TransformComponent::new());
103    let xr_camera = universe.world.add_component(CameraXRComponent::on());
104    let _ = universe.attach(xr_input, xr_head);
105    let _ = universe.attach(xr_input, xr_gamepad);
106    let _ = universe.attach(xr_head, xr_camera);
107    let _ = universe.attach(xr_head, model_root);
108
109    let _ = universe.attach(model_root, model);
110    universe.add(xr_input);
111    universe.add(model_root);
112
113    // --- Background clouds (occluded + lit) ---
114    let bg_root = universe.world.add_component(
115        engine::ecs::component::BackgroundComponent::new().with_occlusion_and_lighting(),
116    );
117    universe.add(bg_root);
118    let mut cloud_params = example_util::CloudRingParams::default();
119    cloud_params.cloud_count = 8; // +3 clusters
120    cloud_params.angle_jitter = 0.35;
121    cloud_params.high_y_probability = 0.5;
122    cloud_params.high_y_multiplier = 1.5;
123    cloud_params.seed = 0x57_55_B0_01u32;
124    example_util::spawn_cloud_ring(&mut universe, bg_root, cloud_params);
125
126    // --- Simple environment ---
127    let spawn_cube = |universe: &mut engine::Universe,
128                      position: (f32, f32, f32),
129                      scale: (f32, f32, f32),
130                      color: (f32, f32, f32, f32)| {
131        let transform = universe.world.add_component(
132            TransformComponent::new()
133                .with_position(position.0, position.1, position.2)
134                .with_scale(scale.0, scale.1, scale.2),
135        );
136        let renderable = universe.world.add_component(RenderableComponent::cube());
137        let color = universe
138            .world
139            .add_component(ColorComponent::rgba(color.0, color.1, color.2, color.3));
140
141        let _ = universe.attach(transform, renderable);
142        let _ = universe.attach(renderable, color);
143
144        universe.add(transform);
145    };
146
147    // floor
148    spawn_cube(
149        &mut universe,
150        (0.0, -0.05, 0.0),
151        (10.0, 0.1, 10.0),
152        (0.92, 0.92, 0.92, 1.0),
153    );
154
155    // back wall
156    spawn_cube(
157        &mut universe,
158        (0.0, 1.5, -5.0),
159        (10.0, 3.0, 1.0),
160        (0.95, 0.94, 0.96, 1.0),
161    );
162
163    // desk
164    spawn_cube(
165        &mut universe,
166        (0.0, 0.35, 1.0),
167        (1.0, 0.75, 0.5),
168        (0.75, 0.70, 0.65, 1.0),
169    );
170
171    let xr_root = universe
172        .world
173        .add_component(engine::ecs::component::XrComponent::on());
174    universe.add(xr_root);
175
176    universe.systems.process_commands(
177        &mut universe.world,
178        &mut universe.visuals,
179        &mut universe.render_assets,
180        &mut universe.command_queue,
181    );
182
183    universe.enable_repl();
184    engine::Windowing::run_app(universe).expect("Windowing failed");
185}
Source

pub fn with_intensity(self, intensity: f32) -> Self

Examples found in repository?
examples/folder-text.rs (line 271)
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}
More examples
Hide additional examples
examples/collision-perimeter.rs (line 106)
86    fn spawn_light(
87        universe: &mut engine::Universe,
88        x: f32,
89        y: f32,
90        z: f32,
91        r: f32,
92        g: f32,
93        b: f32,
94        intensity: f32,
95        distance: f32,
96    ) {
97        let t = universe.world.add_component(
98            engine::ecs::component::TransformComponent::new()
99                .with_position(x, y, z)
100                .with_scale(0.12, 0.12, 0.12),
101        );
102
103        let l = universe.world.add_component(
104            engine::ecs::component::PointLightComponent::new()
105                .with_color(r, g, b)
106                .with_intensity(intensity)
107                .with_distance(distance),
108        );
109        // Visual marker: a small white cube at the light position.
110        // White ensures it mostly shows the light color.
111        let marker = universe
112            .world
113            .add_component(engine::ecs::component::RenderableComponent::cube());
114        let marker_color =
115            universe
116                .world
117                .add_component(engine::ecs::component::ColorComponent::rgba(
118                    1.0, 1.0, 1.0, 1.0,
119                ));
120
121        let _ = universe.attach(t, l);
122        let _ = universe.attach(t, marker);
123        let _ = universe.attach(marker, marker_color);
124        universe.add(t);
125    }
examples/opacity-example.rs (line 391)
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/gravity-fields.rs (line 737)
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    }
examples/transparent-cutout-example.rs (line 85)
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/background-occlusion-example.rs (line 89)
19fn main() {
20    mittens_engine::example_support::ensure_model_assets();
21    utils::logger::init();
22
23    let world = engine::ecs::World::default();
24    let mut universe = engine::Universe::new(world);
25
26    // Light purple-red background so the occlusion reads.
27    let clear = universe
28        .world
29        .add_component(engine::ecs::component::BackgroundColorComponent::new());
30    let clear_c = universe
31        .world
32        .add_component(engine::ecs::component::ColorComponent::rgba(
33            0.62, 0.38, 0.56, 1.0,
34        ));
35    let _ = universe.world.add_child(clear, clear_c);
36    universe.add(clear);
37
38    // A bit of ambient so the cluster volume reads.
39    let ambient = universe
40        .world
41        .add_component(engine::ecs::component::AmbientLightComponent::rgb(
42            0.20, 0.15, 0.3,
43        ));
44    universe.add(ambient);
45
46    // --- Camera rig (WASD/QE) ---
47    let input = universe
48        .world
49        .add_component(engine::ecs::component::InputComponent::new().with_speed(2.0));
50    let input_mode = universe.world.add_component(
51        engine::ecs::component::InputTransformModeComponent::forward_z().with_roll_axis_y(),
52    );
53    let _ = universe.attach(input, input_mode);
54
55    let rig_transform = universe.world.add_component(
56        engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 6.0),
57    );
58    let _ = universe.attach(input, rig_transform);
59
60    let camera3d = universe.world.add_component(
61        engine::ecs::component::Camera3DComponent::new()
62            .with_far(200.0)
63            .with_fov(70.0),
64    );
65    let _ = universe.attach(rig_transform, camera3d);
66
67    // Topology: I { T { C3D } } — add a small camera-attached controls hint.
68    example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
69
70    // Key directional light toward the cloud volume.
71    //
72    // Directional lights encode their direction in the node's world position.
73    // The shader normalizes this vector.
74    let sun = universe.world.add_component(
75        engine::ecs::component::DirectionalLightComponent::new()
76            .with_intensity(1.6)
77            .with_color(1.0, 0.98, 0.92),
78    );
79    // Pointing from the clouds back toward the camera a bit (+Z), and slightly from above.
80    let sun_dir = universe.world.add_component(
81        engine::ecs::component::TransformComponent::new().with_position(0.15, 0.65, 0.75),
82    );
83    let _ = universe.attach(sun_dir, sun);
84
85    // A couple point lights to fill/shade the cloud volume.
86    let light_a = universe.world.add_component(
87        engine::ecs::component::PointLightComponent::new()
88            .with_distance(80.0)
89            .with_intensity(3.0)
90            .with_color(0.9, 0.95, 1.0),
91    );
92    let light_a_tx = universe.world.add_component(
93        engine::ecs::component::TransformComponent::new().with_position(8.0, 8.0, 5.0),
94    );
95    let _ = universe.attach(light_a_tx, light_a);
96
97    let light_b = universe.world.add_component(
98        engine::ecs::component::PointLightComponent::new()
99            .with_distance(80.0)
100            .with_intensity(2.2)
101            .with_color(1.0, 0.9, 0.85),
102    );
103    let light_b_tx = universe.world.add_component(
104        engine::ecs::component::TransformComponent::new().with_position(-10.0, 4.0, -6.0),
105    );
106    let _ = universe.attach(light_b_tx, light_b);
107
108    universe.add(input);
109    universe.add(sun_dir);
110    universe.add(light_a_tx);
111    universe.add(light_b_tx);
112
113    let cube_mesh = universe
114        .render_assets
115        .get_mesh(engine::graphics::BuiltinMeshType::Cube);
116
117    // --- Background occluded+lit world ---
118    // Renderables under this node participate in a background stage that depth-writes for
119    // self-occlusion + uses the normal lighting shader inputs.
120    let bg_root = universe.world.add_component(
121        engine::ecs::component::BackgroundComponent::new().with_occlusion_and_lighting(),
122    );
123    universe.add(bg_root);
124
125    // Create overlapping cube clusters like cloud puffs.
126    // Place them generally in front of the camera (negative Z).
127    // Spawn two groups on opposite X sides.
128    let cluster_count: u32 = 9;
129    for (group_i, group_x) in [-16.0_f32, 16.0_f32].into_iter().enumerate() {
130        let group_tx = universe.world.add_component(
131            engine::ecs::component::TransformComponent::new().with_position(group_x, 0.0, 0.0),
132        );
133        let _ = universe.attach(bg_root, group_tx);
134
135        for cluster_i in 0..cluster_count {
136            let seed = (0xC10u32 ^ (group_i as u32).wrapping_mul(0x9E37_79B9))
137                ^ (cluster_i.wrapping_mul(7919));
138
139            let cx = (rand01(seed ^ 0xA341_316C) - 0.5) * 18.0;
140            let cy = (rand01(seed ^ 0xC801_3EA4) - 0.5) * 10.0 + 2.0;
141            let cz = -18.0 - rand01(seed ^ 0xB529_7A4D) * 26.0;
142
143            let center_tx = universe.world.add_component(
144                engine::ecs::component::TransformComponent::new().with_position(cx, cy, cz),
145            );
146            let _ = universe.attach(group_tx, center_tx);
147
148            let puffs = 18u32;
149            for puff_i in 0..puffs {
150                let puff_seed = seed ^ puff_i.wrapping_mul(1_103_515_245);
151
152                // Slightly ellipsoidal distribution.
153                let ox = (rand01(puff_seed ^ 0x68bc_21eb) - 0.5) * 7.0;
154                let oy = (rand01(puff_seed ^ 0x02e5_be93) - 0.5) * 3.0;
155                let oz = (rand01(puff_seed ^ 0xa1d3_4f2b) - 0.5) * 7.0;
156
157                let base = 0.7 + rand01(puff_seed ^ 0x9e37_79b9) * 2.6;
158                let sx = base * (0.7 + rand01(puff_seed ^ 0x243f_6a88) * 0.8);
159                let sy = base * (0.6 + rand01(puff_seed ^ 0x85a3_08d3) * 0.9);
160                let sz = base * (0.7 + rand01(puff_seed ^ 0x1319_8a2e) * 0.8);
161
162                let tx = universe.world.add_component(
163                    engine::ecs::component::TransformComponent::new()
164                        .with_position(ox, oy, oz)
165                        .with_scale(sx, sy, sz),
166                );
167                let renderable =
168                    universe
169                        .world
170                        .add_component(engine::ecs::component::RenderableComponent::new(
171                            engine::graphics::primitives::Renderable::new(
172                                cube_mesh,
173                                engine::graphics::primitives::MaterialHandle::TOON_MESH,
174                            ),
175                        ));
176
177                // Slight blue-grey variation.
178                let t = rand01(puff_seed ^ 0x7f4a_7c15);
179                let r = 0.55 + 0.10 * t;
180                let g = 0.58 + 0.10 * t;
181                let b = 0.66 + 0.12 * t;
182                let color = universe
183                    .world
184                    .add_component(engine::ecs::component::ColorComponent::rgba(r, g, b, 1.0));
185
186                let _ = universe.attach(center_tx, tx);
187                let _ = universe.attach(tx, renderable);
188                let _ = universe.attach(renderable, color);
189            }
190        }
191    }
192
193    // Foreground reference cube (should never be occluded by background depth).
194    let fg_tx = universe.world.add_component(
195        engine::ecs::component::TransformComponent::new()
196            .with_position(0.0, -0.5, -4.0)
197            .with_scale(1.2, 0.8, 1.2),
198    );
199    let fg_renderable =
200        universe
201            .world
202            .add_component(engine::ecs::component::RenderableComponent::new(
203                engine::graphics::primitives::Renderable::new(
204                    cube_mesh,
205                    engine::graphics::primitives::MaterialHandle::TOON_MESH,
206                ),
207            ));
208    let fg_color = universe
209        .world
210        .add_component(engine::ecs::component::ColorComponent::rgba(
211            0.9, 0.2, 0.2, 1.0,
212        ));
213
214    universe.add(fg_tx);
215    let _ = universe.attach(fg_tx, fg_renderable);
216    let _ = universe.attach(fg_renderable, fg_color);
217
218    // Foreground opaque floor: 16x16 larger white cubes, 10 units below the reference cube.
219    let floor_root = universe.world.add_component(
220        engine::ecs::component::TransformComponent::new().with_position(0.0, -10.5, -10.0),
221    );
222    universe.add(floor_root);
223
224    let spacing = 10_f32;
225    let half = 5_f32;
226    for x in 0..16u32 {
227        for z in 0..64u32 {
228            let px = (x as f32 - half) * spacing;
229            let pz = (z as f32 * -1.0 + half) * spacing;
230            let tx = universe.world.add_component(
231                engine::ecs::component::TransformComponent::new()
232                    .with_position(px, -5.0, pz)
233                    .with_scale(5.0, 0.5, 5.0),
234            );
235            let renderable =
236                universe
237                    .world
238                    .add_component(engine::ecs::component::RenderableComponent::new(
239                        engine::graphics::primitives::Renderable::new(
240                            cube_mesh,
241                            engine::graphics::primitives::MaterialHandle::TOON_MESH,
242                        ),
243                    ));
244            let color = universe
245                .world
246                .add_component(engine::ecs::component::ColorComponent::rgba(
247                    1.0, 1.0, 1.0, 1.0,
248                ));
249
250            let _ = universe.attach(floor_root, tx);
251            let _ = universe.attach(tx, renderable);
252            let _ = universe.attach(renderable, color);
253        }
254    }
255
256    universe.systems.process_commands(
257        &mut universe.world,
258        &mut universe.visuals,
259        &mut universe.render_assets,
260        &mut universe.command_queue,
261    );
262
263    universe.enable_repl();
264    engine::Windowing::run_app(universe).expect("Windowing failed");
265}
Source

pub fn with_distance(self, distance: f32) -> Self

Examples found in repository?
examples/folder-text.rs (line 272)
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}
More examples
Hide additional examples
examples/collision-perimeter.rs (line 107)
86    fn spawn_light(
87        universe: &mut engine::Universe,
88        x: f32,
89        y: f32,
90        z: f32,
91        r: f32,
92        g: f32,
93        b: f32,
94        intensity: f32,
95        distance: f32,
96    ) {
97        let t = universe.world.add_component(
98            engine::ecs::component::TransformComponent::new()
99                .with_position(x, y, z)
100                .with_scale(0.12, 0.12, 0.12),
101        );
102
103        let l = universe.world.add_component(
104            engine::ecs::component::PointLightComponent::new()
105                .with_color(r, g, b)
106                .with_intensity(intensity)
107                .with_distance(distance),
108        );
109        // Visual marker: a small white cube at the light position.
110        // White ensures it mostly shows the light color.
111        let marker = universe
112            .world
113            .add_component(engine::ecs::component::RenderableComponent::cube());
114        let marker_color =
115            universe
116                .world
117                .add_component(engine::ecs::component::ColorComponent::rgba(
118                    1.0, 1.0, 1.0, 1.0,
119                ));
120
121        let _ = universe.attach(t, l);
122        let _ = universe.attach(t, marker);
123        let _ = universe.attach(marker, marker_color);
124        universe.add(t);
125    }
examples/opacity-example.rs (line 392)
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/text-animation.rs (line 41)
6fn main() {
7    mittens_engine::example_support::ensure_model_assets();
8    utils::logger::init();
9
10    let world = engine::ecs::World::default();
11    let mut universe = engine::Universe::new(world);
12
13    // Input-driven camera rig.
14    // Topology: I { T { C3D } }
15    let input = universe
16        .world
17        .add_component(engine::ecs::component::InputComponent::new().with_speed(1.5));
18    let camera3d = universe
19        .world
20        .add_component(engine::ecs::component::Camera3DComponent::new());
21    let rig_transform = universe.world.add_component(
22        engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 2.5),
23    );
24    let input_mode = universe.world.add_component(
25        engine::ecs::component::InputTransformModeComponent::forward_z().with_roll_axis_y(),
26    );
27    let _ = universe.attach(input, input_mode);
28    let _ = universe.attach(input, rig_transform);
29    let _ = universe.attach(rig_transform, camera3d);
30
31    // Small on-screen help.
32    example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
33    universe.add(input);
34
35    // Light so we can see non-emissive materials.
36    let light_transform = universe.world.add_component(
37        engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 2.0),
38    );
39    let light = universe.world.add_component(
40        engine::ecs::component::PointLightComponent::new()
41            .with_distance(25.0)
42            .with_color(1.0, 1.0, 1.0),
43    );
44    let _ = universe.attach(light_transform, light);
45    universe.add(light_transform);
46
47    use engine::ecs::component::{
48        ColorComponent, TextComponent, TextShadowComponent, TextureComponent,
49        TextureFilteringComponent, TransformComponent, TransparentCutoutComponent,
50    };
51
52    // Styled text root.
53    let text_root = universe.world.add_component(
54        TransformComponent::new()
55            .with_position(0.0, 0.0, 0.0)
56            .with_scale(0.25, 0.25, 1.0),
57    );
58
59    // Color must be an ancestor of the glyph renderables.
60    let color_id = universe
61        .world
62        .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
63    let _ = universe.attach(text_root, color_id);
64
65    // This is the component we animate via SetText.
66    let text_id = universe.world.add_component(TextComponent::new(">w<"));
67    let _ = universe.attach(color_id, text_id);
68
69    // Route into cutout pass for cleaner edges.
70    let cutout = universe
71        .world
72        .add_component(TransparentCutoutComponent::new());
73    let _ = universe.attach(text_id, cutout);
74
75    // Font atlas.
76    let tex = universe.world.add_component(TextureComponent::with_uri(
77        "assets/textures/font_system.dds",
78    ));
79    let _ = universe.attach(text_id, tex);
80
81    let shadow = universe.world.add_component(
82        TextShadowComponent::new()
83            .with_scale(1.35)
84            .with_offset([0.06, -0.06, 0.0015]),
85    );
86    let filtering = universe
87        .world
88        .add_component(TextureFilteringComponent::nearest_magnification());
89
90    let _ = universe.attach(text_id, shadow);
91    let _ = universe.attach(text_id, filtering);
92
93    universe.add(text_root);
94
95    let clock_component = universe
96        .world
97        .add_component(engine::ecs::component::ClockComponent::new().with_bpm(140.0));
98    let _ = universe.add(clock_component);
99
100    // Looping animation: 4 beats long, 4 keyframes.
101    let anim = universe
102        .world
103        .add_component(engine::ecs::component::AnimationComponent::new());
104
105    let faces = [">w<", "^w^", "-_-", "o_o"];
106    for (i, &face) in faces.iter().enumerate() {
107        let kf = universe
108            .world
109            .add_component(engine::ecs::component::KeyframeComponent::new(i as f64));
110
111        // Each keyframe emits a SetText intent.
112        let action = universe
113            .world
114            .add_component(engine::ecs::component::ActionComponent::new(
115                engine::ecs::IntentValue::SetText {
116                    component_ids: vec![text_id],
117                    text: face.to_string(),
118                },
119            ));
120
121        let _ = universe.attach(anim, kf);
122        let _ = universe.attach(kf, action);
123    }
124
125    universe.add(anim);
126
127    // Process init-time registrations (Text expands into glyph subtrees here).
128    universe.systems.process_commands(
129        &mut universe.world,
130        &mut universe.visuals,
131        &mut universe.render_assets,
132        &mut universe.command_queue,
133    );
134
135    engine::Windowing::run_app(universe).expect("Windowing failed");
136}
examples/gravity-fields.rs (line 738)
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    }
examples/vtuber-example.rs (line 80)
11fn main() {
12    mittens_engine::example_support::ensure_model_assets();
13    utils::logger::init();
14
15    let world = engine::ecs::World::default();
16    let mut universe = engine::Universe::new(world);
17
18    // Light pink background.
19    let background = universe
20        .world
21        .add_component(BackgroundColorComponent::new());
22    let background_c = universe
23        .world
24        .add_component(ColorComponent::rgba(1.0, 0.82, 0.90, 1.0));
25    let _ = universe.world.add_child(background, background_c);
26    universe.add(background);
27
28    // Small ambient so shadowed areas aren't pure black.
29    let ambient = universe
30        .world
31        .add_component(AmbientLightComponent::rgb(0.10, 0.10, 0.12));
32    universe.add(ambient);
33
34    // --- Camera rig (WASD + mouse) ---
35    // InputComponent is the root, and it owns a Transform (the camera rig).
36    let input = universe
37        .world
38        .add_component(InputComponent::new().with_speed(1.5));
39    let input_mode = universe.world.add_component(
40        InputTransformModeComponent::forward_z()
41            .with_fps_rotation()
42            .with_roll_axis_y(),
43    );
44    let _ = universe.attach(input, input_mode);
45
46    // Start slightly pulled back looking towards the origin.
47    let rig_transform = universe
48        .world
49        .add_component(TransformComponent::new().with_position(0.0, 0.0, 6.0));
50    let _ = universe.attach(input, rig_transform);
51
52    let camera3d = universe.world.add_component(Camera3DComponent::new());
53    let _ = universe.attach(rig_transform, camera3d);
54
55    // Topology: I { T { C3D } } — add a small camera-attached controls hint.
56    example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
57
58    universe.add(input);
59
60    // --- lighting ---
61    // Directional key light (slightly down + forward Z).
62    let sun = universe.world.add_component(
63        DirectionalLightComponent::new()
64            .with_intensity(1.2)
65            .with_color(1.0, 0.98, 0.94),
66    );
67    let sun_dir = universe
68        .world
69        .add_component(TransformComponent::new().with_position(0.0, -0.35, 1.0));
70    let _ = universe.attach(sun_dir, sun);
71    universe.add(sun_dir);
72
73    let light_transform = universe.world.add_component(
74        TransformComponent::new()
75            .with_position(1.0, 6.0, 3.0)
76            .with_scale(0.1, 0.1, 0.1),
77    );
78    let light = universe.world.add_component(
79        engine::ecs::component::PointLightComponent::new()
80            .with_distance(120.0)
81            .with_color(1.0, 1.0, 1.0),
82    );
83    let _ = universe.attach(light_transform, light);
84    universe.add(light_transform);
85
86    // --- VTuber model ---
87    let model_root = universe.world.add_component(TransformComponent::new());
88    let model = universe
89        .world
90        .add_component(GLTFComponent::new("assets/models/pc-rei.hoodie.glb"));
91    // emissive for pc-rei
92    let emissive = universe
93        .world
94        .add_component(engine::ecs::component::EmissiveComponent::on());
95
96    let _ = universe.attach(model, emissive);
97
98    let xr_input = universe.world.add_component(InputXRComponent::on());
99    let xr_gamepad = universe
100        .world
101        .add_component(engine::ecs::component::InputXRGamepadComponent::new().speed(1.5));
102    let xr_head = universe.world.add_component(TransformComponent::new());
103    let xr_camera = universe.world.add_component(CameraXRComponent::on());
104    let _ = universe.attach(xr_input, xr_head);
105    let _ = universe.attach(xr_input, xr_gamepad);
106    let _ = universe.attach(xr_head, xr_camera);
107    let _ = universe.attach(xr_head, model_root);
108
109    let _ = universe.attach(model_root, model);
110    universe.add(xr_input);
111    universe.add(model_root);
112
113    // --- Background clouds (occluded + lit) ---
114    let bg_root = universe.world.add_component(
115        engine::ecs::component::BackgroundComponent::new().with_occlusion_and_lighting(),
116    );
117    universe.add(bg_root);
118    let mut cloud_params = example_util::CloudRingParams::default();
119    cloud_params.cloud_count = 8; // +3 clusters
120    cloud_params.angle_jitter = 0.35;
121    cloud_params.high_y_probability = 0.5;
122    cloud_params.high_y_multiplier = 1.5;
123    cloud_params.seed = 0x57_55_B0_01u32;
124    example_util::spawn_cloud_ring(&mut universe, bg_root, cloud_params);
125
126    // --- Simple environment ---
127    let spawn_cube = |universe: &mut engine::Universe,
128                      position: (f32, f32, f32),
129                      scale: (f32, f32, f32),
130                      color: (f32, f32, f32, f32)| {
131        let transform = universe.world.add_component(
132            TransformComponent::new()
133                .with_position(position.0, position.1, position.2)
134                .with_scale(scale.0, scale.1, scale.2),
135        );
136        let renderable = universe.world.add_component(RenderableComponent::cube());
137        let color = universe
138            .world
139            .add_component(ColorComponent::rgba(color.0, color.1, color.2, color.3));
140
141        let _ = universe.attach(transform, renderable);
142        let _ = universe.attach(renderable, color);
143
144        universe.add(transform);
145    };
146
147    // floor
148    spawn_cube(
149        &mut universe,
150        (0.0, -0.05, 0.0),
151        (10.0, 0.1, 10.0),
152        (0.92, 0.92, 0.92, 1.0),
153    );
154
155    // back wall
156    spawn_cube(
157        &mut universe,
158        (0.0, 1.5, -5.0),
159        (10.0, 3.0, 1.0),
160        (0.95, 0.94, 0.96, 1.0),
161    );
162
163    // desk
164    spawn_cube(
165        &mut universe,
166        (0.0, 0.35, 1.0),
167        (1.0, 0.75, 0.5),
168        (0.75, 0.70, 0.65, 1.0),
169    );
170
171    let xr_root = universe
172        .world
173        .add_component(engine::ecs::component::XrComponent::on());
174    universe.add(xr_root);
175
176    universe.systems.process_commands(
177        &mut universe.world,
178        &mut universe.visuals,
179        &mut universe.render_assets,
180        &mut universe.command_queue,
181    );
182
183    universe.enable_repl();
184    engine::Windowing::run_app(universe).expect("Windowing failed");
185}
Source

pub fn with_color(self, r: f32, g: f32, b: f32) -> Self

Examples found in repository?
examples/folder-text.rs (line 273)
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}
More examples
Hide additional examples
examples/collision-perimeter.rs (line 105)
86    fn spawn_light(
87        universe: &mut engine::Universe,
88        x: f32,
89        y: f32,
90        z: f32,
91        r: f32,
92        g: f32,
93        b: f32,
94        intensity: f32,
95        distance: f32,
96    ) {
97        let t = universe.world.add_component(
98            engine::ecs::component::TransformComponent::new()
99                .with_position(x, y, z)
100                .with_scale(0.12, 0.12, 0.12),
101        );
102
103        let l = universe.world.add_component(
104            engine::ecs::component::PointLightComponent::new()
105                .with_color(r, g, b)
106                .with_intensity(intensity)
107                .with_distance(distance),
108        );
109        // Visual marker: a small white cube at the light position.
110        // White ensures it mostly shows the light color.
111        let marker = universe
112            .world
113            .add_component(engine::ecs::component::RenderableComponent::cube());
114        let marker_color =
115            universe
116                .world
117                .add_component(engine::ecs::component::ColorComponent::rgba(
118                    1.0, 1.0, 1.0, 1.0,
119                ));
120
121        let _ = universe.attach(t, l);
122        let _ = universe.attach(t, marker);
123        let _ = universe.attach(marker, marker_color);
124        universe.add(t);
125    }
examples/opacity-example.rs (line 390)
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/text-animation.rs (line 42)
6fn main() {
7    mittens_engine::example_support::ensure_model_assets();
8    utils::logger::init();
9
10    let world = engine::ecs::World::default();
11    let mut universe = engine::Universe::new(world);
12
13    // Input-driven camera rig.
14    // Topology: I { T { C3D } }
15    let input = universe
16        .world
17        .add_component(engine::ecs::component::InputComponent::new().with_speed(1.5));
18    let camera3d = universe
19        .world
20        .add_component(engine::ecs::component::Camera3DComponent::new());
21    let rig_transform = universe.world.add_component(
22        engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 2.5),
23    );
24    let input_mode = universe.world.add_component(
25        engine::ecs::component::InputTransformModeComponent::forward_z().with_roll_axis_y(),
26    );
27    let _ = universe.attach(input, input_mode);
28    let _ = universe.attach(input, rig_transform);
29    let _ = universe.attach(rig_transform, camera3d);
30
31    // Small on-screen help.
32    example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
33    universe.add(input);
34
35    // Light so we can see non-emissive materials.
36    let light_transform = universe.world.add_component(
37        engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 2.0),
38    );
39    let light = universe.world.add_component(
40        engine::ecs::component::PointLightComponent::new()
41            .with_distance(25.0)
42            .with_color(1.0, 1.0, 1.0),
43    );
44    let _ = universe.attach(light_transform, light);
45    universe.add(light_transform);
46
47    use engine::ecs::component::{
48        ColorComponent, TextComponent, TextShadowComponent, TextureComponent,
49        TextureFilteringComponent, TransformComponent, TransparentCutoutComponent,
50    };
51
52    // Styled text root.
53    let text_root = universe.world.add_component(
54        TransformComponent::new()
55            .with_position(0.0, 0.0, 0.0)
56            .with_scale(0.25, 0.25, 1.0),
57    );
58
59    // Color must be an ancestor of the glyph renderables.
60    let color_id = universe
61        .world
62        .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
63    let _ = universe.attach(text_root, color_id);
64
65    // This is the component we animate via SetText.
66    let text_id = universe.world.add_component(TextComponent::new(">w<"));
67    let _ = universe.attach(color_id, text_id);
68
69    // Route into cutout pass for cleaner edges.
70    let cutout = universe
71        .world
72        .add_component(TransparentCutoutComponent::new());
73    let _ = universe.attach(text_id, cutout);
74
75    // Font atlas.
76    let tex = universe.world.add_component(TextureComponent::with_uri(
77        "assets/textures/font_system.dds",
78    ));
79    let _ = universe.attach(text_id, tex);
80
81    let shadow = universe.world.add_component(
82        TextShadowComponent::new()
83            .with_scale(1.35)
84            .with_offset([0.06, -0.06, 0.0015]),
85    );
86    let filtering = universe
87        .world
88        .add_component(TextureFilteringComponent::nearest_magnification());
89
90    let _ = universe.attach(text_id, shadow);
91    let _ = universe.attach(text_id, filtering);
92
93    universe.add(text_root);
94
95    let clock_component = universe
96        .world
97        .add_component(engine::ecs::component::ClockComponent::new().with_bpm(140.0));
98    let _ = universe.add(clock_component);
99
100    // Looping animation: 4 beats long, 4 keyframes.
101    let anim = universe
102        .world
103        .add_component(engine::ecs::component::AnimationComponent::new());
104
105    let faces = [">w<", "^w^", "-_-", "o_o"];
106    for (i, &face) in faces.iter().enumerate() {
107        let kf = universe
108            .world
109            .add_component(engine::ecs::component::KeyframeComponent::new(i as f64));
110
111        // Each keyframe emits a SetText intent.
112        let action = universe
113            .world
114            .add_component(engine::ecs::component::ActionComponent::new(
115                engine::ecs::IntentValue::SetText {
116                    component_ids: vec![text_id],
117                    text: face.to_string(),
118                },
119            ));
120
121        let _ = universe.attach(anim, kf);
122        let _ = universe.attach(kf, action);
123    }
124
125    universe.add(anim);
126
127    // Process init-time registrations (Text expands into glyph subtrees here).
128    universe.systems.process_commands(
129        &mut universe.world,
130        &mut universe.visuals,
131        &mut universe.render_assets,
132        &mut universe.command_queue,
133    );
134
135    engine::Windowing::run_app(universe).expect("Windowing failed");
136}
examples/gravity-fields.rs (line 736)
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    }
examples/vtuber-example.rs (line 81)
11fn main() {
12    mittens_engine::example_support::ensure_model_assets();
13    utils::logger::init();
14
15    let world = engine::ecs::World::default();
16    let mut universe = engine::Universe::new(world);
17
18    // Light pink background.
19    let background = universe
20        .world
21        .add_component(BackgroundColorComponent::new());
22    let background_c = universe
23        .world
24        .add_component(ColorComponent::rgba(1.0, 0.82, 0.90, 1.0));
25    let _ = universe.world.add_child(background, background_c);
26    universe.add(background);
27
28    // Small ambient so shadowed areas aren't pure black.
29    let ambient = universe
30        .world
31        .add_component(AmbientLightComponent::rgb(0.10, 0.10, 0.12));
32    universe.add(ambient);
33
34    // --- Camera rig (WASD + mouse) ---
35    // InputComponent is the root, and it owns a Transform (the camera rig).
36    let input = universe
37        .world
38        .add_component(InputComponent::new().with_speed(1.5));
39    let input_mode = universe.world.add_component(
40        InputTransformModeComponent::forward_z()
41            .with_fps_rotation()
42            .with_roll_axis_y(),
43    );
44    let _ = universe.attach(input, input_mode);
45
46    // Start slightly pulled back looking towards the origin.
47    let rig_transform = universe
48        .world
49        .add_component(TransformComponent::new().with_position(0.0, 0.0, 6.0));
50    let _ = universe.attach(input, rig_transform);
51
52    let camera3d = universe.world.add_component(Camera3DComponent::new());
53    let _ = universe.attach(rig_transform, camera3d);
54
55    // Topology: I { T { C3D } } — add a small camera-attached controls hint.
56    example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
57
58    universe.add(input);
59
60    // --- lighting ---
61    // Directional key light (slightly down + forward Z).
62    let sun = universe.world.add_component(
63        DirectionalLightComponent::new()
64            .with_intensity(1.2)
65            .with_color(1.0, 0.98, 0.94),
66    );
67    let sun_dir = universe
68        .world
69        .add_component(TransformComponent::new().with_position(0.0, -0.35, 1.0));
70    let _ = universe.attach(sun_dir, sun);
71    universe.add(sun_dir);
72
73    let light_transform = universe.world.add_component(
74        TransformComponent::new()
75            .with_position(1.0, 6.0, 3.0)
76            .with_scale(0.1, 0.1, 0.1),
77    );
78    let light = universe.world.add_component(
79        engine::ecs::component::PointLightComponent::new()
80            .with_distance(120.0)
81            .with_color(1.0, 1.0, 1.0),
82    );
83    let _ = universe.attach(light_transform, light);
84    universe.add(light_transform);
85
86    // --- VTuber model ---
87    let model_root = universe.world.add_component(TransformComponent::new());
88    let model = universe
89        .world
90        .add_component(GLTFComponent::new("assets/models/pc-rei.hoodie.glb"));
91    // emissive for pc-rei
92    let emissive = universe
93        .world
94        .add_component(engine::ecs::component::EmissiveComponent::on());
95
96    let _ = universe.attach(model, emissive);
97
98    let xr_input = universe.world.add_component(InputXRComponent::on());
99    let xr_gamepad = universe
100        .world
101        .add_component(engine::ecs::component::InputXRGamepadComponent::new().speed(1.5));
102    let xr_head = universe.world.add_component(TransformComponent::new());
103    let xr_camera = universe.world.add_component(CameraXRComponent::on());
104    let _ = universe.attach(xr_input, xr_head);
105    let _ = universe.attach(xr_input, xr_gamepad);
106    let _ = universe.attach(xr_head, xr_camera);
107    let _ = universe.attach(xr_head, model_root);
108
109    let _ = universe.attach(model_root, model);
110    universe.add(xr_input);
111    universe.add(model_root);
112
113    // --- Background clouds (occluded + lit) ---
114    let bg_root = universe.world.add_component(
115        engine::ecs::component::BackgroundComponent::new().with_occlusion_and_lighting(),
116    );
117    universe.add(bg_root);
118    let mut cloud_params = example_util::CloudRingParams::default();
119    cloud_params.cloud_count = 8; // +3 clusters
120    cloud_params.angle_jitter = 0.35;
121    cloud_params.high_y_probability = 0.5;
122    cloud_params.high_y_multiplier = 1.5;
123    cloud_params.seed = 0x57_55_B0_01u32;
124    example_util::spawn_cloud_ring(&mut universe, bg_root, cloud_params);
125
126    // --- Simple environment ---
127    let spawn_cube = |universe: &mut engine::Universe,
128                      position: (f32, f32, f32),
129                      scale: (f32, f32, f32),
130                      color: (f32, f32, f32, f32)| {
131        let transform = universe.world.add_component(
132            TransformComponent::new()
133                .with_position(position.0, position.1, position.2)
134                .with_scale(scale.0, scale.1, scale.2),
135        );
136        let renderable = universe.world.add_component(RenderableComponent::cube());
137        let color = universe
138            .world
139            .add_component(ColorComponent::rgba(color.0, color.1, color.2, color.3));
140
141        let _ = universe.attach(transform, renderable);
142        let _ = universe.attach(renderable, color);
143
144        universe.add(transform);
145    };
146
147    // floor
148    spawn_cube(
149        &mut universe,
150        (0.0, -0.05, 0.0),
151        (10.0, 0.1, 10.0),
152        (0.92, 0.92, 0.92, 1.0),
153    );
154
155    // back wall
156    spawn_cube(
157        &mut universe,
158        (0.0, 1.5, -5.0),
159        (10.0, 3.0, 1.0),
160        (0.95, 0.94, 0.96, 1.0),
161    );
162
163    // desk
164    spawn_cube(
165        &mut universe,
166        (0.0, 0.35, 1.0),
167        (1.0, 0.75, 0.5),
168        (0.75, 0.70, 0.65, 1.0),
169    );
170
171    let xr_root = universe
172        .world
173        .add_component(engine::ecs::component::XrComponent::on());
174    universe.add(xr_root);
175
176    universe.systems.process_commands(
177        &mut universe.world,
178        &mut universe.visuals,
179        &mut universe.render_assets,
180        &mut universe.command_queue,
181    );
182
183    universe.enable_repl();
184    engine::Windowing::run_app(universe).expect("Windowing failed");
185}
Source

pub fn id(&self) -> Option<ComponentId>

Trait Implementations§

Source§

impl Clone for PointLightComponent

Source§

fn clone(&self) -> PointLightComponent

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 PointLightComponent

Source§

fn set_id(&mut self, component: ComponentId)

Source§

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

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

fn init(&mut self, emit: &mut dyn SignalEmitter, component: ComponentId)

Called when component is added to the World
Source§

fn as_any(&self) -> &dyn Any

Source§

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

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 PointLightComponent

Source§

impl Debug for PointLightComponent

Source§

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

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

impl Default for PointLightComponent

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