1use mittens_engine::{engine, utils};
2
3#[path = "example_util/mod.rs"]
4mod example_util;
5
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 let clear = universe
15 .world
16 .add_component(engine::ecs::component::BackgroundColorComponent::new());
17 let clear_c = universe
18 .world
19 .add_component(engine::ecs::component::ColorComponent::rgba(
20 0.08, 0.08, 0.08, 1.0,
21 ));
22 let _ = universe.world.add_child(clear, clear_c);
23 universe.add(clear);
24
25 let input = universe
27 .world
28 .add_component(engine::ecs::component::InputComponent::new().with_speed(2.0));
29 let rig_transform = universe.world.add_component(
30 engine::ecs::component::TransformComponent::new().with_position(2.0, 0.0, 7.0),
31 );
32 let input_mode = universe.world.add_component(
33 engine::ecs::component::InputTransformModeComponent::forward_z().with_roll_axis_y(),
34 );
35 let camera3d = universe.world.add_component(
36 engine::ecs::component::Camera3DComponent::new()
37 .with_far(250.0)
38 .with_fov(70.0),
39 );
40 let _ = universe.attach(input, input_mode);
41 let _ = universe.attach(input, rig_transform);
42 let _ = universe.attach(rig_transform, camera3d);
43
44 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
46 universe.add(input);
47
48 let light_tx = universe.world.add_component(
50 engine::ecs::component::TransformComponent::new().with_position(0.0, 2.5, 4.0),
51 );
52 let light = universe.world.add_component(
53 engine::ecs::component::PointLightComponent::new()
54 .with_distance(25.0)
55 .with_color(1.0, 1.0, 1.0),
56 );
57 let _ = universe.attach(light_tx, light);
58 universe.add(light_tx);
59
60 let clock = universe
62 .world
63 .add_component(engine::ecs::component::ClockComponent::new().with_bpm(128.0));
64 universe.add(clock);
65
66 let audio_out = universe
68 .world
69 .add_component(engine::ecs::component::AudioOutputComponent::new());
70 universe.add(audio_out);
71
72 let osc_noise = engine::ecs::component::AudioOscillator::noise()
74 .with_frequency(0.0)
75 .with_amplitude(0.06)
76 .with_enabled(false);
77 let osc_noise_comp =
78 universe
79 .world
80 .add_component(engine::ecs::component::AudioOscillatorComponent::single(
81 osc_noise,
82 ));
83 let _ = universe.attach(audio_out, osc_noise_comp);
84
85 let osc_drum = engine::ecs::component::AudioOscillator::drum()
87 .with_frequency(32.0)
89 .with_amplitude(0.40)
90 .with_enabled(false);
91 let osc_drum_comp =
92 universe
93 .world
94 .add_component(engine::ecs::component::AudioOscillatorComponent::single(
95 osc_drum,
96 ));
97
98 let _ = universe.attach(audio_out, osc_drum_comp);
99
100 fn spawn_text(universe: &mut engine::Universe, pos: (f32, f32, f32), scale: f32, text: &str) {
102 let tx = universe.world.add_component(
103 engine::ecs::component::TransformComponent::new()
104 .with_position(pos.0, pos.1, pos.2)
105 .with_scale(scale, scale, 1.0),
106 );
107 let t =
108 universe
109 .world
110 .add_component(engine::ecs::component::TextComponent::with_word_wrap(
111 text, 38,
112 ));
113 let _ = universe.attach(tx, t);
114 universe.add(tx);
115 }
116
117 fn spawn_emissive_cube(
118 universe: &mut engine::Universe,
119 parent: engine::ecs::ComponentId,
120 pos: (f32, f32, f32),
121 scale: f32,
122 rgba: [f32; 4],
123 ) {
124 let tx = universe.world.add_component(
125 engine::ecs::component::TransformComponent::new()
126 .with_position(pos.0, pos.1, pos.2)
127 .with_scale(scale, scale, scale),
128 );
129 let r = universe
130 .world
131 .add_component(engine::ecs::component::RenderableComponent::cube());
132 let c = universe
133 .world
134 .add_component(engine::ecs::component::ColorComponent::rgba(
135 rgba[0], rgba[1], rgba[2], rgba[3],
136 ));
137 let e = universe
138 .world
139 .add_component(engine::ecs::component::EmissiveComponent::on());
140 let _ = universe.attach(parent, tx);
141 let _ = universe.attach(tx, r);
142 let _ = universe.attach(r, c);
143 let _ = universe.attach(r, e);
144 }
145
146 fn spawn_op_cube(
147 universe: &mut engine::Universe,
148 parent: engine::ecs::ComponentId,
149 pos: (f32, f32, f32),
150 scale: f32,
151 base_rgba: [f32; 4],
152 ) -> engine::ecs::ComponentId {
153 let tx = universe.world.add_component(
154 engine::ecs::component::TransformComponent::new()
155 .with_position(pos.0, pos.1, pos.2)
156 .with_scale(scale, scale, scale),
157 );
158 let r = universe
159 .world
160 .add_component(engine::ecs::component::RenderableComponent::cube());
161 let c = universe
162 .world
163 .add_component(engine::ecs::component::ColorComponent::rgba(
164 base_rgba[0],
165 base_rgba[1],
166 base_rgba[2],
167 base_rgba[3],
168 ));
169 let e = universe
170 .world
171 .add_component(engine::ecs::component::EmissiveComponent::on());
172
173 let _ = universe.attach(parent, tx);
174 let _ = universe.attach(tx, r);
175 let _ = universe.attach(r, c);
176 let _ = universe.attach(r, e);
177
178 tx
179 }
180
181 let lane_x = -2.6_f32;
183 let lane_title_z = -0.4_f32;
184 let lane_cfg_z = -0.4_f32;
185 let _lane_cube_z = -0.8_f32;
186 let drum_y = 0.9_f32;
187 let noise_y = -0.4_f32;
188
189 spawn_text(
190 &mut universe,
191 (lane_x, drum_y + 0.55, lane_title_z),
192 0.09,
193 "Audio Source A: Drum (kick)",
194 );
195 spawn_text(
196 &mut universe,
197 (lane_x, drum_y + 0.25, lane_cfg_z),
198 0.07,
199 "type=Drum\namp=0.40\nbase_freq=32Hz\nkick: C0 dur=0.12 vel=0.90\nlookahead=0.10s",
200 );
201
202 spawn_text(
203 &mut universe,
204 (lane_x, noise_y + 0.55, lane_title_z),
205 0.09,
206 "Audio Source B: Noise",
207 );
208 spawn_text(
209 &mut universe,
210 (lane_x, noise_y + 0.25, lane_cfg_z),
211 0.07,
212 "type=Noise\namp=0.06\nnoise: C9 dur=0.06 vel=0.25\noffset=+0.5 beats\nlookahead=0.10s",
213 );
214
215 let viz_root = universe.world.add_component(
217 engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 0.0),
218 );
219 universe.add(viz_root);
220 spawn_emissive_cube(
221 &mut universe,
222 viz_root,
223 (lane_x - 0.28, drum_y + 0.55, lane_title_z),
224 0.16,
225 [1.00, 0.90, 0.10, 1.0],
226 );
227 spawn_emissive_cube(
228 &mut universe,
229 viz_root,
230 (lane_x - 0.28, noise_y + 0.55, lane_title_z),
231 0.16,
232 [1.00, 1.00, 1.00, 1.0],
233 );
234
235 let kick_timeline_root = universe.world.add_component(
237 engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 0.0),
238 );
239 let noise_timeline_root = universe.world.add_component(
240 engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 0.0),
241 );
242 let _ = universe.attach(viz_root, kick_timeline_root);
243 let _ = universe.attach(viz_root, noise_timeline_root);
244
245 let anim = universe
248 .world
249 .add_component(engine::ecs::component::AnimationComponent::new());
250
251 let kick_dur = 0.12_f32;
252 let noise_dur = 0.06_f32;
253
254 let kick_dark = [0.35, 0.28, 0.02, 1.0];
258 let kick_bright = [1.00, 0.90, 0.10, 1.0];
259 let noise_dark = [0.35, 0.35, 0.35, 1.0];
260 let noise_bright = [1.00, 1.00, 1.00, 1.0];
261
262 let beat_spacing = 0.38_f32;
263 for i in 0..16 {
264 let kf_beat = i as f64;
265 let kf = universe
266 .world
267 .add_component(engine::ecs::component::KeyframeComponent::new(kf_beat));
268
269 let kick = universe
270 .world
271 .add_component(engine::ecs::component::ActionComponent::new(
272 engine::ecs::IntentValue::AudioSchedulePlay {
273 component_ids: vec![osc_drum_comp],
274 beat_offset: 0.0,
275 beat_context: None,
276 note: Some(
277 engine::ecs::component::MusicNote::c(0, kick_dur).with_velocity(0.9),
278 ),
279 gain: None,
280 rate: None,
281 duration: None,
282 },
283 ));
284
285 let noise = universe
286 .world
287 .add_component(engine::ecs::component::ActionComponent::new(
288 engine::ecs::IntentValue::AudioSchedulePlay {
289 component_ids: vec![osc_noise_comp],
290 beat_offset: 0.5,
291 beat_context: None,
292 note: Some(
293 engine::ecs::component::MusicNote::c(9, noise_dur).with_velocity(0.25),
294 ),
295 gain: None,
296 rate: None,
297 duration: None,
298 },
299 ));
300
301 let _ = universe.attach(anim, kf);
302 let _ = universe.attach(kf, kick);
303 let _ = universe.attach(kf, noise);
304
305 let reset_kick_lane =
309 universe
310 .world
311 .add_component(engine::ecs::component::ActionComponent::new(
312 engine::ecs::IntentValue::SetColor {
313 component_ids: vec![kick_timeline_root],
314 rgba: kick_dark,
315 },
316 ));
317 let reset_noise_lane =
318 universe
319 .world
320 .add_component(engine::ecs::component::ActionComponent::new(
321 engine::ecs::IntentValue::SetColor {
322 component_ids: vec![noise_timeline_root],
323 rgba: noise_dark,
324 },
325 ));
326 let _ = universe.attach(kf, reset_kick_lane);
327 let _ = universe.attach(kf, reset_noise_lane);
328
329 let kick_x = (kf_beat as f32) * beat_spacing;
332 let kick_cube = spawn_op_cube(
333 &mut universe,
334 kick_timeline_root,
335 (kick_x, drum_y, -1.3),
336 0.10,
337 kick_dark,
338 );
339
340 let noise_x = ((kf_beat + 0.5) as f32) * beat_spacing;
342 let noise_cube = spawn_op_cube(
343 &mut universe,
344 noise_timeline_root,
345 (noise_x, noise_y, -1.3),
346 0.10,
347 noise_dark,
348 );
349
350 let brighten_kick =
351 universe
352 .world
353 .add_component(engine::ecs::component::ActionComponent::new(
354 engine::ecs::IntentValue::SetColor {
355 component_ids: vec![kick_cube],
356 rgba: kick_bright,
357 },
358 ));
359 let brighten_noise =
360 universe
361 .world
362 .add_component(engine::ecs::component::ActionComponent::new(
363 engine::ecs::IntentValue::SetColor {
364 component_ids: vec![noise_cube],
365 rgba: noise_bright,
366 },
367 ));
368 let _ = universe.attach(kf, brighten_kick);
369 let _ = universe.attach(kf, brighten_noise);
370 }
371
372 universe.add(anim);
373
374 universe.systems.process_commands(
375 &mut universe.world,
376 &mut universe.visuals,
377 &mut universe.render_assets,
378 &mut universe.command_queue,
379 );
380
381 engine::Windowing::run_app(universe).expect("Windowing failed");
382}