Skip to main content

mms_loops/example_util/
mod.rs

1#![allow(dead_code)]
2
3use mittens_engine::engine::{
4    self,
5    ecs::component::{ColorComponent, TransparentCutoutComponent},
6};
7
8/// Standard MMS demo scene rig: dark-blue background and navigable camera.
9///
10/// Lights should be declared in MMS, not spawned here.
11///
12/// Returns the camera `TransformComponent` id so callers can attach things to it.
13///
14/// `cam_pos` — world-space starting position of the camera.
15pub fn spawn_mms_demo_rig(
16    universe: &mut engine::Universe,
17    cam_pos: [f32; 3],
18) -> engine::ecs::ComponentId {
19    use engine::ecs::component::{
20        BackgroundColorComponent, Camera3DComponent, InputComponent, InputTransformModeComponent,
21        TransformComponent,
22    };
23
24    // Dark blue clear colour.
25    let bg_color = universe
26        .world
27        .add_component(BackgroundColorComponent::new());
28    let bg_color_c = universe
29        .world
30        .add_component(ColorComponent::rgba(0.02, 0.03, 0.10, 1.0));
31    let _ = universe.world.add_child(bg_color, bg_color_c);
32    universe.add(bg_color);
33
34    // Camera rig: Input → Transform → Camera3D.
35    let input = universe
36        .world
37        .add_component(InputComponent::new().with_speed(3.0));
38    let input_mode = universe
39        .world
40        .add_component(InputTransformModeComponent::forward_z().with_roll_axis_y());
41    let _ = universe.attach(input, input_mode);
42
43    let cam_transform = universe
44        .world
45        .add_component(TransformComponent::new().with_position(cam_pos[0], cam_pos[1], cam_pos[2]));
46    let _ = universe.attach(input, cam_transform);
47
48    let camera = universe.world.add_component(Camera3DComponent::new());
49    let _ = universe.attach(cam_transform, camera);
50
51    universe.add(input);
52
53    spawn_desktop_camera_controls_hint(universe, cam_transform);
54
55    cam_transform
56}
57
58fn hash_u32(mut x: u32) -> u32 {
59    x ^= x >> 16;
60    x = x.wrapping_mul(0x7feb_352d);
61    x ^= x >> 15;
62    x = x.wrapping_mul(0x846c_a68b);
63    x ^= x >> 16;
64    x
65}
66
67fn rand01(seed: u32) -> f32 {
68    (hash_u32(seed) as f32) / (u32::MAX as f32)
69}
70
71#[derive(Debug, Clone, Copy)]
72pub struct CloudRingParams {
73    pub cloud_count: u32,
74    pub radius: f32,
75    pub center_y: f32,
76    pub puffs_per_cloud: u32,
77    /// 0.0 = perfectly evenly spaced, 1.0 = up to one full step of jitter.
78    pub angle_jitter: f32,
79    /// Probability a given cluster is placed higher than `center_y`.
80    pub high_y_probability: f32,
81    /// Multiplier applied to `center_y` when a cluster is chosen to be high.
82    pub high_y_multiplier: f32,
83    /// Seed used for deterministic layout variation.
84    pub seed: u32,
85}
86
87impl Default for CloudRingParams {
88    fn default() -> Self {
89        Self {
90            cloud_count: 5,
91            radius: 26.0,
92            center_y: 2.0,
93            puffs_per_cloud: 28,
94            angle_jitter: 0.0,
95            high_y_probability: 0.0,
96            high_y_multiplier: 1.0,
97            seed: 0xC10u32,
98        }
99    }
100}
101
102/// Spawns a ring of "cloud" puff clusters under an existing background root.
103///
104/// Assumes `bg_root` is a `BackgroundComponent` (usually `with_occlusion_and_lighting()`).
105pub fn spawn_cloud_ring(
106    universe: &mut engine::Universe,
107    bg_root: engine::ecs::ComponentId,
108    p: CloudRingParams,
109) {
110    if p.cloud_count == 0 || p.radius == 0.0 {
111        return;
112    }
113
114    let cube_mesh = universe
115        .render_assets
116        .get_mesh(engine::graphics::BuiltinMeshType::Cube);
117
118    let step = std::f32::consts::TAU / (p.cloud_count as f32);
119    let angle_jitter = p.angle_jitter.clamp(0.0, 1.0);
120    let high_y_probability = p.high_y_probability.clamp(0.0, 1.0);
121
122    for i in 0..p.cloud_count {
123        let seed_i = p.seed ^ i.wrapping_mul(0x9E37_79B9);
124        let jitter = (rand01(seed_i ^ 0xa53a_9d2d) - 0.5) * step * angle_jitter;
125        let a = (i as f32) * step + jitter;
126        let cx = p.radius * a.cos();
127        let cz = p.radius * a.sin();
128
129        let cy = if rand01(seed_i ^ 0x7f4a_7c15) < high_y_probability {
130            p.center_y * p.high_y_multiplier
131        } else {
132            p.center_y
133        };
134
135        let center_tx = universe.world.add_component(
136            engine::ecs::component::TransformComponent::new().with_position(cx, cy, cz),
137        );
138        let _ = universe.attach(bg_root, center_tx);
139
140        let base_seed = seed_i ^ 0x243f_6a88;
141        for puff_i in 0..p.puffs_per_cloud {
142            let seed = base_seed ^ puff_i.wrapping_mul(1_103_515_245);
143
144            let ox = (rand01(seed ^ 0x68bc_21eb) - 0.5) * 9.0;
145            let oy = (rand01(seed ^ 0x02e5_be93) - 0.5) * 4.0;
146            let oz = (rand01(seed ^ 0xa1d3_4f2b) - 0.5) * 9.0;
147
148            let base = 0.7 + rand01(seed ^ 0x9e37_79b9) * 2.8;
149            let sx = base * (0.7 + rand01(seed ^ 0x243f_6a88) * 0.9);
150            let sy = base * (0.6 + rand01(seed ^ 0x85a3_08d3) * 1.0);
151            let sz = base * (0.7 + rand01(seed ^ 0x1319_8a2e) * 0.9);
152
153            let tx = universe.world.add_component(
154                engine::ecs::component::TransformComponent::new()
155                    .with_position(ox, oy, oz)
156                    .with_scale(sx, sy, sz),
157            );
158            let renderable =
159                universe
160                    .world
161                    .add_component(engine::ecs::component::RenderableComponent::new(
162                        engine::graphics::primitives::Renderable::new(
163                            cube_mesh,
164                            engine::graphics::primitives::MaterialHandle::TOON_MESH,
165                        ),
166                    ));
167
168            let t = rand01(seed ^ 0x7f4a_7c15);
169            let r = 0.70 + 0.10 * t;
170            let g = 0.72 + 0.10 * t;
171            let b = 0.80 + 0.12 * t;
172            let color = universe
173                .world
174                .add_component(engine::ecs::component::ColorComponent::rgba(r, g, b, 1.0));
175
176            let _ = universe.attach(center_tx, tx);
177            let _ = universe.attach(tx, renderable);
178            let _ = universe.attach(renderable, color);
179        }
180    }
181}
182
183/// Spawn a small camera-attached help text for desktop controls.
184///
185/// The returned component is the hint root `TransformComponent`.
186///
187/// This is intended for examples that use the common topology:
188///
189/// `I { T { C3D } }` (InputComponent → TransformComponent → Camera3DComponent)
190pub fn spawn_desktop_camera_controls_hint(
191    universe: &mut engine::Universe,
192    camera_transform: engine::ecs::ComponentId,
193) -> engine::ecs::ComponentId {
194    use engine::ecs::component::{
195        ColorComponent, EditorComponent, EmissiveComponent, RaycastableComponent, TextComponent,
196        TextureFilteringComponent, TransformComponent,
197    };
198
199    // We intentionally do NOT parent the hint under the camera rig.
200    // This keeps it out of the camera subtree (and avoids inheriting camera motion/rotation).
201    //
202    // Instead, we place it in world space based on the camera rig's current pose.
203    // That makes it start out “in front-right of the camera” without being attached.
204    let (cam_pos, cam_rot) = universe
205        .world
206        .get_component_by_id_as::<TransformComponent>(camera_transform)
207        .map(|t| (t.transform.translation, t.transform.rotation))
208        .unwrap_or(([0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0]));
209
210    // Camera convention in examples: forward is -Z.
211    // Local-space offset from the camera rig at spawn time.
212    let local_offset = [0.65, 0.25, -1.7];
213
214    // Rotate the offset by the camera rig's rotation so it appears in front-right of the view.
215    let world_offset = mittens_engine::utils::math::quat_rotate_vec3(cam_rot, local_offset);
216    let world_pos = [
217        cam_pos[0] + world_offset[0],
218        cam_pos[1] + world_offset[1],
219        cam_pos[2] + world_offset[2],
220    ];
221
222    let hint_root = universe.world.add_component(
223        TransformComponent::new()
224            .with_position(world_pos[0], world_pos[1], world_pos[2])
225            .with_scale(0.055, 0.055, 1.0),
226    );
227
228    // Put the hint into an editor subtree so clicking it attaches gizmos.
229    let editor_root = universe.world.add_component(EditorComponent::new());
230    let _ = universe.attach(hint_root, editor_root);
231
232    let text = universe.world.add_component(TextComponent::new(
233        "use wasd/rf/qe\nand right-mouse\nclick and drag\nto move/look",
234    ));
235
236    // Make glyph renderables raycastable so the hint can be clicked without adding a
237    // large invisible pick plane (clicking is per-glyph / per-text-quad).
238    let raycastable = universe
239        .world
240        .add_component(RaycastableComponent::enabled());
241    let _ = universe.attach(text, raycastable);
242
243    // Text color should be an immediate child of the TextComponent root.
244    let color = universe
245        .world
246        .add_component(ColorComponent::rgba(0.0, 0.0, 0.0, 1.0));
247    let _ = universe.attach(text, color);
248
249    // TextSystem looks for these as immediate children of the TextComponent root.
250    let emissive = universe.world.add_component(EmissiveComponent::on());
251    let filtering = universe
252        .world
253        .add_component(TextureFilteringComponent::nearest_magnification());
254
255    let cutout = universe
256        .world
257        .add_component(TransparentCutoutComponent::new());
258    let _ = universe.attach(text, cutout);
259
260    let _ = universe.attach(editor_root, text);
261    let _ = universe.attach(text, emissive);
262    let _ = universe.attach(text, filtering);
263
264    // Ensure the text is initialized even if the caller only adds the camera rig.
265    universe.add(hint_root);
266
267    hint_root
268}