Skip to main content

opacity_example/
opacity-example.rs

1use mittens_engine::{engine, utils};
2
3#[path = "example_util/mod.rs"]
4mod example_util;
5
6use mittens_engine::engine::ecs::component::{
7    AmbientLightComponent, BackgroundColorComponent, Camera3DComponent, ColorComponent,
8    InputComponent, InputTransformModeComponent, OpacityComponent, PointLightComponent,
9    RenderableComponent, TextComponent, TextureFilteringComponent, TransformComponent,
10};
11
12fn spawn_cube(
13    universe: &mut engine::Universe,
14    parent: engine::ecs::ComponentId,
15    position: (f32, f32, f32),
16    scale: (f32, f32, f32),
17    color: Option<(f32, f32, f32, f32)>,
18    opacity: Option<OpacityComponent>,
19) {
20    let t = universe.world.add_component(
21        TransformComponent::new()
22            .with_position(position.0, position.1, position.2)
23            .with_scale(scale.0, scale.1, scale.2),
24    );
25    let r = universe.world.add_component(RenderableComponent::cube());
26
27    let _ = universe.attach(parent, t);
28    let _ = universe.attach(t, r);
29
30    if let Some((cr, cg, cb, ca)) = color {
31        let c = universe
32            .world
33            .add_component(ColorComponent::rgba(cr, cg, cb, ca));
34        let _ = universe.attach(r, c);
35    }
36
37    if let Some(o) = opacity {
38        let o = universe.world.add_component(o);
39        let _ = universe.attach(r, o);
40    }
41}
42
43fn spawn_text_label(universe: &mut engine::Universe, position: (f32, f32, f32), text: &str) {
44    // T_root { T_scale { TXT { filtering } } }
45    let text_root = universe
46        .world
47        .add_component(TransformComponent::new().with_position(position.0, position.1, position.2));
48
49    let text_scale = universe
50        .world
51        .add_component(TransformComponent::new().with_scale(0.18, 0.18, 1.0));
52    let _ = universe.attach(text_root, text_scale);
53
54    let text_c = universe.world.add_component(TextComponent::new(text));
55    let _ = universe.attach(text_scale, text_c);
56
57    // Keep it crisp.
58    let filtering = universe
59        .world
60        .add_component(TextureFilteringComponent::nearest());
61    let _ = universe.attach(text_c, filtering);
62
63    // White label.
64    let color = universe
65        .world
66        .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
67    let _ = universe.attach(text_c, color);
68
69    universe.add(text_root);
70}
71
72fn text_block_dimensions(text: &str) -> (usize, usize) {
73    let mut max_cols = 0usize;
74    let mut rows = 1usize;
75    let mut cur = 0usize;
76
77    for ch in text.chars() {
78        if ch == '\n' {
79            max_cols = max_cols.max(cur);
80            cur = 0;
81            rows += 1;
82        } else {
83            cur += 1;
84        }
85    }
86    max_cols = max_cols.max(cur);
87
88    (max_cols.max(1), rows.max(1))
89}
90
91fn spawn_text_label_with_bg(
92    universe: &mut engine::Universe,
93    position: (f32, f32, f32),
94    text: &str,
95    bg_opacity: f32,
96) {
97    // T_root {
98    //   T_bg { R_bg { Color black, Opacity } }
99    //   T_scale { TXT { filtering } }
100    // }
101
102    let text_root = universe
103        .world
104        .add_component(TransformComponent::new().with_position(position.0, position.1, position.2));
105
106    // Background quad (slightly behind the glyph quads).
107    let (cols, rows) = text_block_dimensions(text);
108    let text_scale = 0.18_f32;
109    let pad_x = 0.55_f32;
110    let pad_y = 0.45_f32;
111    let bg_w = cols as f32 * text_scale + pad_x;
112    let bg_h = rows as f32 * text_scale + pad_y;
113
114    let bg_t = universe.world.add_component(
115        TransformComponent::new()
116            .with_position(1.5, 0.0, -0.02)
117            .with_scale(bg_w, bg_h, 1.0),
118    );
119    let bg_r = universe.world.add_component(RenderableComponent::square());
120    let _ = universe.attach(text_root, bg_t);
121    let _ = universe.attach(bg_t, bg_r);
122
123    let bg_c = universe
124        .world
125        .add_component(ColorComponent::rgba(0.0, 0.0, 0.0, 1.0));
126    let _ = universe.attach(bg_r, bg_c);
127
128    let bg_o = universe
129        .world
130        .add_component(OpacityComponent::new().with_opacity(bg_opacity));
131    let _ = universe.attach(bg_r, bg_o);
132
133    let text_scale_t = universe
134        .world
135        .add_component(TransformComponent::new().with_scale(text_scale, text_scale, 1.0));
136    let _ = universe.attach(text_root, text_scale_t);
137
138    let text_c = universe.world.add_component(TextComponent::new(text));
139    let _ = universe.attach(text_scale_t, text_c);
140
141    // Keep it crisp.
142    let filtering = universe
143        .world
144        .add_component(TextureFilteringComponent::nearest());
145    let _ = universe.attach(text_c, filtering);
146
147    // Force the label into the transparent pass so it layers correctly with the background.
148    // (Pass selection currently does not consider texture alpha.)
149    let color = universe
150        .world
151        .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 0.998));
152    let _ = universe.attach(text_c, color);
153
154    universe.add(text_root);
155}
156
157fn spawn_demo_spot(
158    universe: &mut engine::Universe,
159    spot_pos: (f32, f32, f32),
160    opacity: f32,
161    multiple_layers: bool,
162    grid_n: i32,
163) {
164    let spot = universe
165        .world
166        .add_component(TransformComponent::new().with_position(spot_pos.0, spot_pos.1, spot_pos.2));
167
168    // All cubes inherit this color.
169    let white = universe
170        .world
171        .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
172    let _ = universe.attach(spot, white);
173
174    // All cubes inherit this opacity (no per-cube OpacityComponent needed).
175    let mut oc = OpacityComponent::new().with_opacity(opacity);
176    if multiple_layers {
177        oc = oc.with_multiple_layers();
178    }
179    let oc = universe.world.add_component(oc);
180    let _ = universe.attach(spot, oc);
181
182    universe.add(spot);
183
184    // Cube grid.
185    let n = grid_n.max(1);
186    let spacing = if n <= 2 { 1.0 } else { 0.6 };
187    let cube_scale = if n <= 2 { 0.8 } else { 0.45 };
188    let half = (n as f32 - 1.0) * spacing * 0.5;
189
190    for z in 0..n {
191        for y in 0..n {
192            for x in 0..n {
193                let px = x as f32 * spacing - half;
194                let py = y as f32 * spacing;
195                let pz = z as f32 * spacing - half;
196
197                spawn_cube(
198                    universe,
199                    spot,
200                    (px, py, pz),
201                    (cube_scale, cube_scale, cube_scale),
202                    None,
203                    None,
204                );
205            }
206        }
207    }
208
209    // Label above.
210    let label = format!(
211        "opacity: {:.2}\nmulti-layer: {}",
212        opacity,
213        if multiple_layers { "true" } else { "false" }
214    );
215    // Keep labels away from the cube volume so they don't intersect.
216    let label_y = spot_pos.1 + (n as f32 * spacing) + 1.8;
217    let label_x = spot_pos.0 - (half + 0.6);
218    let label_z = spot_pos.2 - (half + 1.0);
219    spawn_text_label(universe, (label_x, label_y, label_z), &label);
220}
221
222fn spawn_demo_strip_pair(
223    universe: &mut engine::Universe,
224    spot_pos: (f32, f32, f32),
225    transparent_multiple_layers: bool,
226) {
227    let n_z: i32 = 4;
228    let spacing = 1.0;
229    let cube_scale = 0.8;
230    let half_z = (n_z as f32 - 1.0) * spacing * 0.5;
231
232    // Transparent strip (1x4 along Z).
233    let transparent_root = universe
234        .world
235        .add_component(TransformComponent::new().with_position(spot_pos.0, spot_pos.1, spot_pos.2));
236
237    let white = universe
238        .world
239        .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
240    let _ = universe.attach(transparent_root, white);
241
242    let mut oc = OpacityComponent::new().with_opacity(0.50);
243    if transparent_multiple_layers {
244        oc = oc.with_multiple_layers();
245    }
246    let oc = universe.world.add_component(oc);
247    let _ = universe.attach(transparent_root, oc);
248
249    universe.add(transparent_root);
250
251    for z in 0..n_z {
252        let pz = z as f32 * spacing - half_z;
253        spawn_cube(
254            universe,
255            transparent_root,
256            (0.0, 0.0, pz),
257            (cube_scale, cube_scale, cube_scale),
258            None,
259            None,
260        );
261    }
262
263    // Opaque strip to the left, same spacing/scale.
264    let opaque_root = universe
265        .world
266        .add_component(TransformComponent::new().with_position(
267            spot_pos.0 - spacing,
268            spot_pos.1,
269            spot_pos.2,
270        ));
271    let white = universe
272        .world
273        .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
274    let _ = universe.attach(opaque_root, white);
275    universe.add(opaque_root);
276
277    for z in 0..n_z {
278        let pz = z as f32 * spacing - half_z;
279        spawn_cube(
280            universe,
281            opaque_root,
282            (0.0, 0.0, pz),
283            (cube_scale, cube_scale, cube_scale),
284            None,
285            None,
286        );
287    }
288
289    let label = format!(
290        "mini: opaque + transparent strip\ntransparent opacity: 0.50\nmulti-layer: {}",
291        if transparent_multiple_layers {
292            "true"
293        } else {
294            "false"
295        }
296    );
297    let label_y = spot_pos.1 + spacing + 1.8;
298    let label_x = spot_pos.0 - (spacing * 2.6);
299    let label_z = spot_pos.2 - (half_z + 1.0);
300    spawn_text_label(universe, (label_x, label_y, label_z), &label);
301}
302
303fn spawn_demo_xy_plane(
304    universe: &mut engine::Universe,
305    spot_pos: (f32, f32, f32),
306    opacity: f32,
307    n_x: i32,
308    n_y: i32,
309    z: f32,
310) {
311    let spot = universe
312        .world
313        .add_component(TransformComponent::new().with_position(spot_pos.0, spot_pos.1, spot_pos.2));
314
315    let white = universe
316        .world
317        .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
318    let _ = universe.attach(spot, white);
319
320    let oc = universe
321        .world
322        .add_component(OpacityComponent::new().with_opacity(opacity));
323    let _ = universe.attach(spot, oc);
324
325    universe.add(spot);
326
327    let nx = n_x.max(1);
328    let ny = n_y.max(1);
329    let spacing = 0.45;
330    let cube_scale = 0.35;
331    let half_x = (nx as f32 - 1.0) * spacing * 0.5;
332
333    for y in 0..ny {
334        for x in 0..nx {
335            let px = x as f32 * spacing - half_x;
336            let py = y as f32 * spacing;
337            spawn_cube(
338                universe,
339                spot,
340                (px, py, z),
341                (cube_scale, cube_scale, cube_scale),
342                None,
343                None,
344            );
345        }
346    }
347
348    // Label in front of the plane.
349    let label = format!(
350        "XY plane: {}x{}\nopacity: {:.2}\nmulti-layer: false",
351        nx, ny, opacity
352    );
353    let label_x = spot_pos.0 - (half_x + 0.6);
354    let label_y = spot_pos.1 + (ny as f32 * spacing) + 1.2;
355    let label_z = spot_pos.2 - 2.5;
356    spawn_text_label_with_bg(universe, (label_x, label_y, label_z), &label, 0.50);
357}
358
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}