1use mittens_engine::{engine, utils};
2
3use mittens_engine::engine::ecs::component::{
4 AmbientLightComponent, BackgroundColorComponent, BackgroundComponent, Camera3DComponent,
5 CameraXRComponent, ColorComponent, InputComponent, InputTransformModeComponent,
6 InputXRComponent, PointerComponent, RaycastableComponent, TextComponent, TextShadowComponent,
7 TextureComponent, TextureFilteringComponent, TransformComponent, TransparentCutoutComponent,
8};
9
10#[path = "example_util/mod.rs"]
11mod example_util;
12
13fn main() {
14 mittens_engine::example_support::ensure_model_assets();
15 utils::logger::init();
16
17 let world = engine::ecs::World::default();
18 let mut universe = engine::Universe::new(world);
19
20 let background = universe
22 .world
23 .add_component(BackgroundColorComponent::new());
24 let background_c = universe
25 .world
26 .add_component(ColorComponent::rgba(0.20, 0.2, 0.20, 1.0));
27 let _ = universe.world.add_child(background, background_c);
28 universe.add(background);
29
30 let ambient = universe
32 .world
33 .add_component(AmbientLightComponent::rgb(0.50, 0.50, 0.50));
34 universe.add(ambient);
35
36 let directional_tx = universe
37 .world
38 .add_component(TransformComponent::new().with_position(0.0, 0.5, 1.0));
39 let directional_light = universe.world.add_component(
40 engine::ecs::component::DirectionalLightComponent::new()
41 .with_color(1.0, 1.0, 1.0)
42 .with_intensity(0.8),
43 );
44 let _ = universe.attach(directional_tx, directional_light);
45 universe.add(directional_tx);
46
47 let bg_root = universe
51 .world
52 .add_component(BackgroundComponent::new().with_occlusion_and_lighting());
53 universe.add(bg_root);
54
55 let mut bg_cloud_params = example_util::CloudRingParams::default();
56 bg_cloud_params.cloud_count = 6;
57 bg_cloud_params.seed = 0xF0_17_C10u32;
58 example_util::spawn_cloud_ring(&mut universe, bg_root, bg_cloud_params);
59
60 let input = universe
67 .world
68 .add_component(InputComponent::new().with_speed(2.0));
69 let input_mode = universe
70 .world
71 .add_component(InputTransformModeComponent::forward_z().with_roll_axis_y());
72 let _ = universe.attach(input, input_mode);
73
74 let rig_transform = universe
75 .world
76 .add_component(TransformComponent::new().with_position(1.8, -0.5, 2.5));
77 let _ = universe.attach(input, rig_transform);
78
79 let camera = universe.world.add_component(Camera3DComponent::new());
80 let _ = universe.attach(rig_transform, camera);
81
82 let pointer = universe.world.add_component(PointerComponent::new());
84 let _ = universe.attach(camera, pointer);
85
86 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
88
89 universe.add(input);
90
91 let fg_cloud_root = universe
95 .world
96 .add_component(TransformComponent::new().with_position(0.0, -6.0, -10.0));
97 universe.add(fg_cloud_root);
98
99 let mut fg_cloud_params = example_util::CloudRingParams::default();
100 fg_cloud_params.cloud_count = 4;
101 fg_cloud_params.radius = 9.0;
102 fg_cloud_params.center_y = 1.0;
103 fg_cloud_params.puffs_per_cloud = 22;
104 fg_cloud_params.angle_jitter = 0.35;
105 fg_cloud_params.high_y_probability = 0.35;
106 fg_cloud_params.high_y_multiplier = 1.4;
107 fg_cloud_params.seed = 0xF0_17_C102u32;
108 example_util::spawn_cloud_ring(&mut universe, fg_cloud_root, fg_cloud_params);
109
110 fn estimate_text_height_world(text: &str, scale: f32) -> f32 {
118 let line_count = text.lines().count().max(1) as f32;
119 let pad_lines = 1.25;
122 (line_count + pad_lines) * scale
123 }
124
125 fn spawn_text_block(
126 universe: &mut engine::Universe,
127 position: (f32, f32, f32),
128 scale: f32,
129 text: &str,
130 font_texture_uri: Option<&str>,
131 text_color_rgba: Option<[f32; 4]>,
132 shadow: Option<TextShadowComponent>,
133 filtering: TextureFilteringComponent,
134 ) -> f32 {
135 let text_root = universe.world.add_component(
137 TransformComponent::new().with_position(position.0, position.1, position.2),
138 );
139
140 let text_scale = universe
141 .world
142 .add_component(TransformComponent::new().with_scale(scale, scale, 1.0));
143 let _ = universe.attach(text_root, text_scale);
144
145 let text_parent = if let Some([r, g, b, a]) = text_color_rgba {
146 let color = universe
147 .world
148 .add_component(ColorComponent::rgba(r, g, b, a));
149 let _ = universe.attach(text_scale, color);
150 color
151 } else {
152 text_scale
153 };
154
155 let text_c = universe.world.add_component(TextComponent::new(text));
156 let _ = universe.attach(text_parent, text_c);
157
158 let raycastable = universe
161 .world
162 .add_component(RaycastableComponent::enabled());
163 let _ = universe.attach(text_c, raycastable);
164
165 let cutout = universe
167 .world
168 .add_component(TransparentCutoutComponent::new());
169 let _ = universe.attach(text_c, cutout);
170
171 if let Some(shadow) = shadow {
172 let shadow_id = universe.world.add_component(shadow);
173 let _ = universe.attach(text_c, shadow_id);
174 }
175
176 if let Some(uri) = font_texture_uri {
179 let tex = universe
180 .world
181 .add_component(TextureComponent::with_uri(uri));
182 let _ = universe.attach(text_c, tex);
183 }
184
185 let filtering_id = universe.world.add_component(filtering);
186 let _ = universe.attach(text_c, filtering_id);
187
188 universe.add(text_root);
189
190 estimate_text_height_world(text, scale)
191 }
192
193 const TEXT_BIG: &str = "CAT ENGINE\nfont example\nBIG TEXT";
197 const TEXT_MED: &str = "multi-line\ntext block\nmedium";
198 const TEXT_SMALL: &str = "small\ntext";
199 const TEXT_TINY: &str = "tiny\ntext\n(zoom in)";
200
201 let x = -1.2;
203 let z = -2.0;
204 let mut y = 1.2;
205 let gap = 0.15;
206
207 let shadow_crisp = Some(
208 TextShadowComponent::new()
209 .with_scale(1.35)
210 .with_offset([0.06, -0.06, 0.0015]),
211 );
212
213 y -= spawn_text_block(
214 &mut universe,
215 (x, y, z),
216 0.55,
217 TEXT_BIG,
218 None,
219 Some([1.0, 1.0, 1.0, 1.0]),
220 shadow_crisp,
221 TextureFilteringComponent::nearest_magnification(),
222 ) + gap;
223 y -= spawn_text_block(
224 &mut universe,
225 (x, y, z),
226 0.25,
227 TEXT_MED,
228 None,
229 Some([0.60, 0.95, 1.00, 1.0]),
230 Some(
231 TextShadowComponent::new()
232 .with_rgba([0.0, 0.0, 0.15, 1.0])
233 .with_scale(1.20)
234 .with_offset([0.05, -0.04, 0.0015]),
235 ),
236 TextureFilteringComponent::linear(),
237 ) + gap;
238 y -= spawn_text_block(
239 &mut universe,
240 (x, y, z),
241 0.14,
242 TEXT_SMALL,
243 None,
244 Some([1.0, 0.88, 0.35, 1.0]),
245 Some(
246 TextShadowComponent::new()
247 .with_rgba([0.15, 0.0, 0.0, 1.0])
248 .with_scale(1.55)
249 .with_offset([0.08, -0.08, 0.0015]),
250 ),
251 TextureFilteringComponent::nearest(),
252 ) + gap;
253 let _ = spawn_text_block(
254 &mut universe,
255 (x, y, z),
256 0.08,
257 TEXT_TINY,
258 None,
259 Some([0.90, 1.0, 0.70, 1.0]),
260 shadow_crisp,
261 TextureFilteringComponent::nearest_magnification(),
262 );
263
264 const TEXT_LEFT: &str = "even though there's hexes\nto the solar plexus in my lexus\ni'm feelin' reckless,\nwhen i'm eating breakfast";
266 let _ = spawn_text_block(
267 &mut universe,
268 (x - 8.1, 1.1, z),
269 0.22,
270 TEXT_LEFT,
271 Some("assets/textures/font_system.dds"),
272 Some([0.95, 0.95, 0.95, 1.0]),
273 Some(
274 TextShadowComponent::new()
275 .with_scale(1.25)
276 .with_offset([0.05, -0.05, 0.0015]),
277 ),
278 TextureFilteringComponent::nearest_magnification(),
279 );
280
281 let alt_atlas = Some("assets/textures/font_system.0.0.dds");
284 let alt_z = z - 0.05;
285 let alt_grey = Some([0.25, 0.25, 0.25, 1.0]);
286
287 let mut y_alt = 1.2;
288 y_alt -= spawn_text_block(
289 &mut universe,
290 (x, y_alt, alt_z),
291 0.55,
292 TEXT_BIG,
293 alt_atlas,
294 alt_grey,
295 Some(
296 TextShadowComponent::new()
297 .with_rgba([0.0, 0.0, 0.0, 1.0])
298 .with_scale(1.15)
299 .with_offset([0.03, -0.03, 0.0015]),
300 ),
301 TextureFilteringComponent::linear(),
302 ) + gap;
303 y_alt -= spawn_text_block(
304 &mut universe,
305 (x, y_alt, alt_z),
306 0.25,
307 TEXT_MED,
308 alt_atlas,
309 alt_grey,
310 Some(
311 TextShadowComponent::new()
312 .with_rgba([0.0, 0.0, 0.0, 1.0])
313 .with_scale(1.15)
314 .with_offset([0.03, -0.03, 0.0015]),
315 ),
316 TextureFilteringComponent::linear(),
317 ) + gap;
318 y_alt -= spawn_text_block(
319 &mut universe,
320 (x, y_alt, alt_z),
321 0.14,
322 TEXT_SMALL,
323 alt_atlas,
324 alt_grey,
325 Some(
326 TextShadowComponent::new()
327 .with_rgba([0.0, 0.0, 0.0, 1.0])
328 .with_scale(1.15)
329 .with_offset([0.03, -0.03, 0.0015]),
330 ),
331 TextureFilteringComponent::linear(),
332 ) + gap;
333 let _ = spawn_text_block(
334 &mut universe,
335 (x, y_alt, alt_z),
336 0.08,
337 TEXT_TINY,
338 alt_atlas,
339 alt_grey,
340 Some(
341 TextShadowComponent::new()
342 .with_rgba([0.0, 0.0, 0.0, 1.0])
343 .with_scale(1.15)
344 .with_offset([0.03, -0.03, 0.0015]),
345 ),
346 TextureFilteringComponent::linear(),
347 );
348
349 universe.enable_repl();
350
351 let xr_input = universe.world.add_component(InputXRComponent::on());
352 let xr_gamepad = universe
353 .world
354 .add_component(engine::ecs::component::InputXRGamepadComponent::new().speed(1.5));
355 let xr_head = universe.world.add_component(TransformComponent::new());
356 let xr_camera = universe.world.add_component(CameraXRComponent::on());
357 let _ = universe.attach(xr_input, xr_head);
358 let _ = universe.attach(xr_input, xr_gamepad);
359 let _ = universe.attach(xr_head, xr_camera);
360 universe.add(xr_input);
361
362 let xr_root = universe
364 .world
365 .add_component(engine::ecs::component::XrComponent::on());
366 universe.add(xr_root);
367
368 universe.systems.process_commands(
370 &mut universe.world,
371 &mut universe.visuals,
372 &mut universe.render_assets,
373 &mut universe.command_queue,
374 );
375
376 engine::Windowing::run_app(universe).expect("Windowing failed");
377}