1use mittens_engine::{engine, utils};
2
3#[path = "example_util/mod.rs"]
4mod example_util;
5
6fn cube_renderable_sibling_of_collider(
7 world: &engine::ecs::World,
8 collider_cid: engine::ecs::ComponentId,
9) -> Option<engine::ecs::ComponentId> {
10 let t = world.parent_of(collider_cid)?;
11 for &sib in world.children_of(t).iter() {
12 if sib == collider_cid {
13 continue;
14 }
15 let Some(r) =
16 world.get_component_by_id_as::<engine::ecs::component::RenderableComponent>(sib)
17 else {
18 continue;
19 };
20 if r.renderable.base_mesh == engine::graphics::primitives::CpuMeshHandle::CUBE {
21 return Some(sib);
22 }
23 }
24 None
25}
26
27fn set_renderable_color_rgba(
28 world: &mut engine::ecs::World,
29 emit: &mut dyn engine::ecs::SignalEmitter,
30 renderable_cid: engine::ecs::ComponentId,
31 rgba: [f32; 4],
32) {
33 let existing = world
35 .children_of(renderable_cid)
36 .iter()
37 .copied()
38 .find(|&ch| {
39 world
40 .get_component_by_id_as::<engine::ecs::component::ColorComponent>(ch)
41 .is_some()
42 });
43
44 if let Some(color_cid) = existing {
45 if let Some(c) =
46 world.get_component_by_id_as_mut::<engine::ecs::component::ColorComponent>(color_cid)
47 {
48 c.rgba = rgba;
49 emit.push_intent_now(
50 color_cid,
51 engine::ecs::IntentValue::RegisterColor {
52 component_ids: vec![color_cid],
53 },
54 );
55 }
56 return;
57 }
58
59 let color_cid = world.register(engine::ecs::component::ColorComponent::rgba(
61 rgba[0], rgba[1], rgba[2], rgba[3],
62 ));
63 let _ = world.add_child(renderable_cid, color_cid);
64 emit.push_intent_now(
65 color_cid,
66 engine::ecs::IntentValue::RegisterColor {
67 component_ids: vec![color_cid],
68 },
69 );
70}
71
72fn kinetic_response_child_of_collider(
73 world: &engine::ecs::World,
74 collider_cid: engine::ecs::ComponentId,
75) -> Option<engine::ecs::ComponentId> {
76 for &ch in world.children_of(collider_cid).iter() {
77 if world
78 .get_component_by_id_as::<engine::ecs::component::KineticResponseComponent>(ch)
79 .is_some()
80 {
81 return Some(ch);
82 }
83 }
84 None
85}
86
87fn on_collision_turn_white(
88 world: &mut engine::ecs::World,
89 emit: &mut dyn engine::ecs::SignalEmitter,
90 signal: &engine::ecs::Signal,
91) {
92 let Some(engine::ecs::EventSignal::CollisionStarted { a, b, .. }) = signal.event.as_ref()
93 else {
94 return;
95 };
96
97 let self_collider = signal.scope;
98 let other = if self_collider == *a {
99 *b
100 } else if self_collider == *b {
101 *a
102 } else {
103 return;
104 };
105
106 let Some(other_cn) =
108 world.get_component_by_id_as::<engine::ecs::component::CollisionComponent>(other)
109 else {
110 return;
111 };
112 if other_cn.mode == engine::ecs::component::CollisionMode::Static {
113 return;
114 }
115 if cube_renderable_sibling_of_collider(world, other).is_none() {
116 return;
117 }
118
119 let Some(self_renderable) = cube_renderable_sibling_of_collider(world, self_collider) else {
120 return;
121 };
122 set_renderable_color_rgba(world, emit, self_renderable, [1.0, 1.0, 1.0, 1.0]);
123}
124
125fn on_collision_freeze_gravity(
126 world: &mut engine::ecs::World,
127 _emit: &mut dyn engine::ecs::SignalEmitter,
128 signal: &engine::ecs::Signal,
129) {
130 let Some(engine::ecs::EventSignal::CollisionStarted { a, b, .. }) = signal.event.as_ref()
131 else {
132 return;
133 };
134
135 let self_collider = signal.scope;
136 let other = if self_collider == *a {
137 *b
138 } else if self_collider == *b {
139 *a
140 } else {
141 return;
142 };
143
144 let Some(other_cn) =
146 world.get_component_by_id_as::<engine::ecs::component::CollisionComponent>(other)
147 else {
148 return;
149 };
150 if other_cn.mode == engine::ecs::component::CollisionMode::Static {
151 return;
152 }
153 if cube_renderable_sibling_of_collider(world, other).is_none() {
154 return;
155 }
156
157 let Some(response_cid) = kinetic_response_child_of_collider(world, self_collider) else {
158 return;
159 };
160 if let Some(r) = world
161 .get_component_by_id_as_mut::<engine::ecs::component::KineticResponseComponent>(
162 response_cid,
163 )
164 {
165 r.gravity_coefficient = 0.0;
167 }
168}
169
170fn main() {
171 mittens_engine::example_support::ensure_model_assets();
172 utils::logger::init();
173
174 let world = engine::ecs::World::default();
175 let mut universe = engine::Universe::new(world);
176
177 let bg_color = universe
178 .world
179 .add_component(engine::ecs::component::BackgroundColorComponent::new());
180 let bg_color_c = universe
181 .world
182 .add_component(engine::ecs::component::ColorComponent::rgba(
183 0.06, 0.04, 0.07, 1.0,
184 ));
185 let _ = universe.world.add_child(bg_color, bg_color_c);
186 universe.add(bg_color);
187
188 let directional_light = universe.world.add_component(
190 engine::ecs::component::DirectionalLightComponent::new()
191 .with_color(0.16, 0.14, 0.12)
192 .with_intensity(0.7),
193 );
194 let directional_light_t = universe.world.add_component(
195 engine::ecs::component::TransformComponent::new().with_position(0.0, 1.0, 0.0),
196 );
197 let _ = universe.attach(directional_light_t, directional_light);
198 universe.add(directional_light_t);
199
200 let input = universe
202 .world
203 .add_component(engine::ecs::component::InputComponent::new().with_speed(2.0));
204
205 let cam_t = universe.world.add_component(
206 engine::ecs::component::TransformComponent::new().with_position(0.0, 4.0, 10.0),
207 );
208 let cam = universe.world.add_component(
209 engine::ecs::component::Camera3DComponent::new()
210 .with_far(600.0)
212 .with_fov(70.0),
213 );
214
215 let cam_collision = universe
217 .world
218 .add_component(engine::ecs::component::CollisionComponent::RIGGED());
219 let cam_response = universe
220 .world
221 .add_component(engine::ecs::component::KineticResponseComponent::slide());
222 let cam_shape =
223 universe
224 .world
225 .add_component(engine::ecs::component::CollisionShapeComponent::new(
226 engine::ecs::component::CollisionShape::sphere_radius(0.25),
227 ));
228
229 let input_mode = universe.world.add_component(
230 engine::ecs::component::InputTransformModeComponent::forward_z()
231 .with_roll_axis_y()
232 .with_fps_rotation(),
233 );
234
235 let _ = universe.attach(input, input_mode);
236 let _ = universe.attach(input, cam_t);
237 let _ = universe.attach(cam_t, cam);
238 let _ = universe.attach(cam_t, cam_collision);
239 let _ = universe.attach(cam_collision, cam_response);
240 let _ = universe.attach(cam_collision, cam_shape);
241
242 example_util::spawn_desktop_camera_controls_hint(&mut universe, cam_t);
244 universe.add(input);
245
246 let arena_half = 30.0;
247
248 let bg_root = universe.world.add_component(
251 engine::ecs::component::BackgroundComponent::new().with_occlusion_and_lighting(),
252 );
253 universe.add(bg_root);
254
255 let bg_ground_thickness = 0.20;
258 let bg_ground_root_t = universe
259 .world
260 .add_component(engine::ecs::component::TransformComponent::new());
261 let bg_ground_geom_t = universe.world.add_component(
262 engine::ecs::component::TransformComponent::new()
263 .with_position(0.0, -bg_ground_thickness, 0.0)
264 .with_scale(
265 arena_half * 10.0,
266 bg_ground_thickness * 2.0,
267 arena_half * 10.0,
268 ),
269 );
270 let bg_ground_r = universe
271 .world
272 .add_component(engine::ecs::component::RenderableComponent::cube());
273 let bg_ground_c = universe
274 .world
275 .add_component(engine::ecs::component::ColorComponent::rgba(
276 0.03, 0.03, 0.035, 1.0,
277 ));
278 let _ = universe.attach(bg_root, bg_ground_root_t);
279 let _ = universe.attach(bg_ground_root_t, bg_ground_geom_t);
280 let _ = universe.attach(bg_ground_geom_t, bg_ground_r);
281 let _ = universe.attach(bg_ground_r, bg_ground_c);
282
283 let bg_cloud_root = universe.world.add_component(
288 engine::ecs::component::TransformComponent::new().with_position(
289 0.0,
290 0.0,
291 -arena_half * 2.8,
292 ),
293 );
294 let _ = universe.attach(bg_root, bg_cloud_root);
295
296 let mut cloud_params = example_util::CloudRingParams::default();
297 cloud_params.cloud_count = 9;
298 cloud_params.radius = arena_half * 1.9;
299 cloud_params.center_y = 18.0;
300 cloud_params.puffs_per_cloud = 26;
301 cloud_params.angle_jitter = 0.35;
302 cloud_params.high_y_probability = 0.35;
303 cloud_params.high_y_multiplier = 1.35;
304 cloud_params.seed = 0xC10_71A5u32;
305 example_util::spawn_cloud_ring(&mut universe, bg_cloud_root, cloud_params);
306
307 let fg_city_root = universe
309 .world
310 .add_component(engine::ecs::component::TransformComponent::new());
311 universe.add(fg_city_root);
312
313 let bg_city_root = universe
315 .world
316 .add_component(engine::ecs::component::TransformComponent::new());
317 let _ = universe.attach(bg_root, bg_city_root);
318
319 fn hash_u32(mut x: u32) -> u32 {
320 x ^= x >> 16;
322 x = x.wrapping_mul(0x7FEB_352D);
323 x ^= x >> 15;
324 x = x.wrapping_mul(0x846C_A68B);
325 x ^= x >> 16;
326 x
327 }
328
329 fn rand01(seed: u32) -> f32 {
330 let x = hash_u32(seed);
331 (x as f32) / (u32::MAX as f32)
332 }
333
334 fn spawn_floor(
335 universe: &mut engine::Universe,
336 building_root: engine::ecs::ComponentId,
337 floor_center_y: f32,
338 footprint_x: f32,
339 footprint_z: f32,
340 floor_h: f32,
341 floor_rgb: [f32; 4],
342 building_scale: f32,
343 window_mask: &[bool],
344 window_height_coeff: f32,
345 seed: u32,
346 ) {
347 let floor_root_t = universe.world.add_component(
351 engine::ecs::component::TransformComponent::new().with_position(
352 0.0,
353 floor_center_y,
354 0.0,
355 ),
356 );
357 let floor_geom_t = universe.world.add_component(
358 engine::ecs::component::TransformComponent::new().with_scale(
359 footprint_x,
360 floor_h,
361 footprint_z,
362 ),
363 );
364 let floor_r = universe
365 .world
366 .add_component(engine::ecs::component::RenderableComponent::cube());
367 let floor_c = universe
368 .world
369 .add_component(engine::ecs::component::ColorComponent::rgba(
370 floor_rgb[0],
371 floor_rgb[1],
372 floor_rgb[2],
373 floor_rgb[3],
374 ));
375
376 let _ = universe.attach(building_root, floor_root_t);
377 let _ = universe.attach(floor_root_t, floor_geom_t);
378 let _ = universe.attach(floor_geom_t, floor_r);
379 let _ = universe.attach(floor_r, floor_c);
380
381 let slots = window_mask.len().max(1);
384 let building_scale = building_scale.max(0.01);
385 let window_height_coeff = window_height_coeff.clamp(0.05, 2.0);
386
387 let nominal_window = 0.32 * building_scale;
389 let window_d = 0.06 * building_scale;
390
391 let margin_x = (0.65 * nominal_window).min(0.18 * footprint_x);
393 let margin_z = (0.65 * nominal_window).min(0.18 * footprint_z);
394 let available_x = (footprint_x - 2.0 * margin_x).max(nominal_window);
395 let available_z = (footprint_z - 2.0 * margin_z).max(nominal_window);
396 let step_x = available_x / (slots as f32);
397 let step_z = available_z / (slots as f32);
398
399 let window_wx = nominal_window.min(step_x * 0.85);
400 let window_wz = nominal_window.min(step_z * 0.85);
401 let window_h = (nominal_window * window_height_coeff).min(floor_h * 0.80);
402
403 let warm = 0.70 + 0.25 * rand01(seed ^ 0x1F12_6A77);
404 let window_rgb = [1.0, 0.78 * warm, 0.48 * warm, 1.0];
405
406 let mut spawn_window = |local_pos: (f32, f32, f32), local_scale: (f32, f32, f32)| {
407 let window_t = universe.world.add_component(
408 engine::ecs::component::TransformComponent::new()
409 .with_position(local_pos.0, local_pos.1, local_pos.2)
410 .with_scale(local_scale.0, local_scale.1, local_scale.2),
411 );
412 let window_r = universe
413 .world
414 .add_component(engine::ecs::component::RenderableComponent::cube());
415 let window_c =
416 universe
417 .world
418 .add_component(engine::ecs::component::ColorComponent::rgba(
419 window_rgb[0],
420 window_rgb[1],
421 window_rgb[2],
422 window_rgb[3],
423 ));
424 let window_e = universe
425 .world
426 .add_component(engine::ecs::component::EmissiveComponent::on());
427
428 let _ = universe.attach(floor_root_t, window_t);
429 let _ = universe.attach(window_t, window_r);
430 let _ = universe.attach(window_r, window_c);
431 let _ = universe.attach(window_r, window_e);
432 };
433
434 let wy = 0.0;
436
437 for i in 0..slots {
438 if !window_mask[i] {
439 continue;
440 }
441 let tx = -0.5 * footprint_x + margin_x + (i as f32 + 0.5) * step_x;
442 let tz = -0.5 * footprint_z + margin_z + (i as f32 + 0.5) * step_z;
443
444 for z_sign in [1.0_f32, -1.0_f32] {
446 spawn_window(
447 (tx, wy, z_sign * (footprint_z * 0.5 + window_d * 0.6)),
448 (window_wx, window_h, window_d),
449 );
450 }
451
452 for x_sign in [1.0_f32, -1.0_f32] {
454 spawn_window(
455 (x_sign * (footprint_x * 0.5 + window_d * 0.6), wy, tz),
456 (window_d, window_h, window_wz),
457 );
458 }
459 }
460 }
461
462 fn spawn_building(
463 universe: &mut engine::Universe,
464 parent: engine::ecs::ComponentId,
465 x: f32,
466 y_offset: f32,
467 z: f32,
468 floors: u32,
469 seed: u32,
470 building_scale: f32,
471 height_scale: f32,
472 total_windows_per_floor_per_building_side: u32,
473 window_height_coeff: f32,
474 ) {
475 let building_scale = building_scale.max(0.01);
476 let height_scale = height_scale.max(0.01);
477
478 let floors = floors.max(1);
479 let windows_per_side = total_windows_per_floor_per_building_side.max(1) as usize;
480
481 let footprint_x = 3.2 * building_scale;
482 let footprint_z = 3.2 * building_scale;
483 let floor_h = 1.15 * building_scale * height_scale;
484
485 let base_t = universe.world.add_component(
486 engine::ecs::component::TransformComponent::new().with_position(x, y_offset, z),
487 );
488 let _ = universe.attach(parent, base_t);
489
490 let tint = 0.03 * (rand01(seed ^ 0xA511_E9B3) - 0.5);
492 let base_rgb = [0.08 + tint, 0.085 + tint, 0.095 + tint, 1.0];
493
494 let window_fill_probability = 0.80_f32;
497
498 for floor_i in 0..floors {
499 let floor_seed = seed ^ floor_i.wrapping_mul(0x9E37_79B9) ^ 0xB17D_1E55;
500
501 let mut window_mask = Vec::with_capacity(windows_per_side);
502 for i in 0..windows_per_side {
503 let s = floor_seed ^ (i as u32).wrapping_mul(0x85EB_CA6B) ^ 0x243F_6A88;
504 window_mask.push(rand01(s) < window_fill_probability);
505 }
506
507 let floor_center_y = floor_h * (floor_i as f32) + floor_h * 0.5;
508 spawn_floor(
509 universe,
510 base_t,
511 floor_center_y,
512 footprint_x,
513 footprint_z,
514 floor_h,
515 base_rgb,
516 building_scale,
517 &window_mask,
518 window_height_coeff,
519 floor_seed,
520 );
521 }
522 }
523
524 fn spawn_city(
525 universe: &mut engine::Universe,
526 parent: engine::ecs::ComponentId,
527 columns: u32,
528 spacing: f32,
529 position_scale: f32,
530 building_scale: f32,
531 height_scale: f32,
532 seed_base: u32,
533 y_offset: f32,
534 total_windows_per_floor_per_building_side: u32,
535 window_height_coeff: f32,
536 ) {
537 let columns = columns.max(1);
538 let half = (columns as i32) / 2;
539
540 for gz in -half..=half {
541 for gx in -half..=half {
542 if gx == 0 && gz == 0 {
544 continue;
545 }
546
547 let seed = seed_base
548 ^ ((gx + half) as u32).wrapping_mul(0x9E37_79B9)
549 ^ ((gz + half) as u32).wrapping_mul(0x85EB_CA6B)
550 ^ 0xB17D_1E55;
551
552 let floors = 3 + (hash_u32(seed) % 10); let x = (gx as f32) * spacing * position_scale;
558 let z = (gz as f32) * spacing * position_scale;
559
560 spawn_building(
561 universe,
562 parent,
563 x,
564 y_offset,
565 z,
566 floors,
567 seed,
568 building_scale,
569 height_scale,
570 total_windows_per_floor_per_building_side,
571 window_height_coeff,
572 );
573 }
574 }
575 }
576
577 spawn_city(
594 &mut universe,
595 bg_city_root,
596 11,
597 3.0,
598 3.0,
599 0.45,
600 1.0,
601 0xC170_0002u32,
602 -2.0,
603 6,
604 0.75,
605 );
606
607 fn spawn_street_light(universe: &mut engine::Universe) -> engine::ecs::ComponentId {
608 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 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 let root_t = universe
623 .world
624 .add_component(engine::ecs::component::TransformComponent::new());
625
626 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 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 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 .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 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 let light = universe.world.add_component(
735 engine::ecs::component::PointLightComponent::new()
736 .with_color(1.0, 0.82, 0.55)
737 .with_intensity(5.0)
738 .with_distance(40.0),
739 );
740 let _ = universe.attach(diffuser_t, light);
741
742 root_t
743 }
744
745 let light_x = arena_half - 2.0;
748 for z in [-10.0, 0.0, 10.0] {
749 let left_place = universe.world.add_component(
751 engine::ecs::component::TransformComponent::new().with_position(-light_x, 0.0, z),
752 );
753 let left_model = spawn_street_light(&mut universe);
754 let _ = universe.attach(left_place, left_model);
755 universe.add(left_place);
756
757 let right_place = universe.world.add_component(
759 engine::ecs::component::TransformComponent::new()
760 .with_position(light_x, 0.0, z)
761 .with_rotation_euler(0.0, std::f32::consts::PI, 0.0),
762 );
763 let right_model = spawn_street_light(&mut universe);
764 let _ = universe.attach(right_place, right_model);
765 universe.add(right_place);
766 }
767
768 {
770 let floor_half = arena_half;
771 let thickness = 0.20;
772
773 let floor_root_t = universe
776 .world
777 .add_component(engine::ecs::component::TransformComponent::new());
778 let floor_geom_t = universe.world.add_component(
779 engine::ecs::component::TransformComponent::new()
780 .with_position(0.0, -thickness, 0.0)
781 .with_scale(floor_half * 2.0, thickness * 2.0, floor_half * 2.0),
782 );
783 let floor_r = universe
784 .world
785 .add_component(engine::ecs::component::RenderableComponent::cube());
786 let floor_color =
787 universe
788 .world
789 .add_component(engine::ecs::component::ColorComponent::rgba(
790 0.08, 0.08, 0.09, 1.0,
791 ));
792
793 let floor_cn = universe
794 .world
795 .add_component(engine::ecs::component::CollisionComponent::STATIC());
796 let floor_shape =
797 universe
798 .world
799 .add_component(engine::ecs::component::CollisionShapeComponent::new(
800 engine::ecs::component::CollisionShape::cube_half_extents([
801 floor_half, thickness, floor_half,
802 ]),
803 ));
804
805 let _ = universe.attach(floor_root_t, floor_geom_t);
806 let _ = universe.attach(floor_geom_t, floor_r);
807 let _ = universe.attach(floor_r, floor_color);
808 let _ = universe.attach(floor_geom_t, floor_cn);
809 let _ = universe.attach(floor_cn, floor_shape);
810 universe.add(floor_root_t);
811 }
812
813 fn spawn_boundary_wall(
816 universe: &mut engine::Universe,
817 x: f32,
818 y: f32,
819 z: f32,
820 half_extents: [f32; 3],
821 color: [f32; 4],
822 ) {
823 let t = universe.world.add_component(
824 engine::ecs::component::TransformComponent::new()
825 .with_position(x, y, z)
826 .with_scale(
827 half_extents[0] * 2.0,
828 half_extents[1] * 2.0,
829 half_extents[2] * 2.0,
830 ),
831 );
832 let r = universe
833 .world
834 .add_component(engine::ecs::component::RenderableComponent::cube());
835 let c = universe
836 .world
837 .add_component(engine::ecs::component::ColorComponent::rgba(
838 color[0], color[1], color[2], color[3],
839 ));
840
841 let cn = universe
842 .world
843 .add_component(engine::ecs::component::CollisionComponent::STATIC());
844 let shape =
845 universe
846 .world
847 .add_component(engine::ecs::component::CollisionShapeComponent::new(
848 engine::ecs::component::CollisionShape::cube_half_extents(half_extents),
849 ));
850
851 let _ = universe.attach(t, r);
852 let _ = universe.attach(r, c);
853 let _ = universe.attach(t, cn);
854 let _ = universe.attach(cn, shape);
855 universe.add(t);
856 }
857
858 {
859 let wall_half_height = 2.5;
860 let wall_half_thickness = 0.30;
861 let wall_center_y = wall_half_height; let wall_half_len = arena_half;
863
864 let wall_color = [0.10, 0.10, 0.12, 0.30];
866
867 spawn_boundary_wall(
869 &mut universe,
870 0.0,
871 wall_center_y,
872 arena_half,
873 [wall_half_len, wall_half_height, wall_half_thickness],
874 wall_color,
875 );
876 spawn_boundary_wall(
877 &mut universe,
878 0.0,
879 wall_center_y,
880 -arena_half,
881 [wall_half_len, wall_half_height, wall_half_thickness],
882 wall_color,
883 );
884
885 spawn_boundary_wall(
887 &mut universe,
888 arena_half,
889 wall_center_y,
890 0.0,
891 [wall_half_thickness, wall_half_height, wall_half_len],
892 wall_color,
893 );
894 spawn_boundary_wall(
895 &mut universe,
896 -arena_half,
897 wall_center_y,
898 0.0,
899 [wall_half_thickness, wall_half_height, wall_half_len],
900 wall_color,
901 );
902 }
903
904 fn spawn_falling_cube(
905 universe: &mut engine::Universe,
906 parent: engine::ecs::ComponentId,
907 x: f32,
908 y: f32,
909 z: f32,
910 s: f32,
911 r: f32,
912 g: f32,
913 b: f32,
914 ) {
915 let t = universe.world.add_component(
916 engine::ecs::component::TransformComponent::new()
917 .with_position(x, y, z)
918 .with_scale(s, s, s),
919 );
920
921 let renderable = universe
922 .world
923 .add_component(engine::ecs::component::RenderableComponent::cube());
924 let color = universe
925 .world
926 .add_component(engine::ecs::component::ColorComponent::rgba(r, g, b, 1.0));
927
928 let cn = universe
929 .world
930 .add_component(engine::ecs::component::CollisionComponent::KINEMATIC());
931
932 let response = universe.world.add_component({
933 let mut r = engine::ecs::component::KineticResponseComponent::push()
934 .with_push_strength(3.0)
935 .with_friction_y(18.0);
936 r.max_speed = 80.0;
938 r
939 });
940
941 let shape =
942 universe
943 .world
944 .add_component(engine::ecs::component::CollisionShapeComponent::new(
945 engine::ecs::component::CollisionShape::cube_half_extents([
946 0.5 * s,
947 0.5 * s,
948 0.5 * s,
949 ]),
950 ));
951
952 let _ = universe.attach(t, renderable);
953 let _ = universe.attach(renderable, color);
954
955 let _ = universe.attach(t, cn);
956 let _ = universe.attach(cn, response);
957 let _ = universe.attach(cn, shape);
958
959 let _ = universe.attach(parent, t);
960 }
961
962 let field_low = universe
964 .world
965 .add_component(engine::ecs::component::GravityComponent::new().with_coefficient(0.125));
966 let field_mid = universe
967 .world
968 .add_component(engine::ecs::component::GravityComponent::new().with_coefficient(0.5));
969 let field_high = universe
970 .world
971 .add_component(engine::ecs::component::GravityComponent::new().with_coefficient(1.0));
972
973 universe.add(field_low);
974 universe.add(field_mid);
975 universe.add(field_high);
976
977 fn spawn_field(
978 universe: &mut engine::Universe,
979 field: engine::ecs::ComponentId,
980 base_x: f32,
981 base_z: f32,
982 cube_color: [f32; 3],
983 ) {
984 let spacing = 0.7;
985 let start_x = base_x - 2.0 * spacing;
986 let start_z = base_z - 1.5 * spacing;
987
988 for i in 0..20 {
989 let ix = (i % 5) as f32;
990 let iz = (i / 5) as f32;
991
992 let x = start_x + ix * spacing;
993 let z = start_z + iz * spacing;
994 let y = 2.0 + iz * 0.6 + ix * 0.1;
995
996 spawn_falling_cube(
997 universe,
998 field,
999 x,
1000 y,
1001 z,
1002 0.5,
1003 cube_color[0],
1004 cube_color[1],
1005 cube_color[2],
1006 );
1007 }
1008
1009 let marker_t = universe.world.add_component(
1011 engine::ecs::component::TransformComponent::new()
1012 .with_position(base_x, 0.1, base_z)
1013 .with_scale(0.3, 0.02, 0.3),
1014 );
1015 let marker_r = universe
1016 .world
1017 .add_component(engine::ecs::component::RenderableComponent::cube());
1018 let marker_c = universe
1019 .world
1020 .add_component(engine::ecs::component::ColorComponent::rgba(
1021 cube_color[0],
1022 cube_color[1],
1023 cube_color[2],
1024 1.0,
1025 ));
1026 let _ = universe.attach(marker_t, marker_r);
1027 let _ = universe.attach(marker_r, marker_c);
1028 universe.add(marker_t);
1029 }
1030
1031 spawn_field(&mut universe, field_low, -8.0, 0.0, [0.3, 0.6, 1.0]);
1032 spawn_field(&mut universe, field_mid, 0.0, 0.0, [0.4, 1.0, 0.4]);
1033 spawn_field(&mut universe, field_high, 8.0, 0.0, [1.0, 0.5, 0.2]);
1034
1035 universe.add_signal_handler(
1039 engine::ecs::SignalKind::CollisionStarted,
1040 field_low,
1041 on_collision_turn_white,
1042 );
1043 universe.add_signal_handler(
1044 engine::ecs::SignalKind::CollisionStarted,
1045 field_high,
1046 on_collision_turn_white,
1047 );
1048 universe.add_signal_handler(
1049 engine::ecs::SignalKind::CollisionStarted,
1050 field_mid,
1051 on_collision_freeze_gravity,
1052 );
1053
1054 universe.systems.process_commands(
1055 &mut universe.world,
1056 &mut universe.visuals,
1057 &mut universe.render_assets,
1058 &mut universe.command_queue,
1059 );
1060
1061 universe.enable_repl();
1062 engine::Windowing::run_app(universe).expect("Windowing failed");
1063}