Skip to main content

transparent_cutout_example/
transparent-cutout-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, PointLightComponent, RenderableComponent,
9    TextureComponent, TextureFilteringComponent, TransformComponent, TransparentCutoutComponent,
10};
11
12fn spawn_gold_cube(
13    universe: &mut engine::Universe,
14    parent: engine::ecs::ComponentId,
15    position: (f32, f32, f32),
16    scale: f32,
17    color: (f32, f32, f32),
18) {
19    let t = universe.world.add_component(
20        TransformComponent::new()
21            .with_position(position.0, position.1, position.2)
22            .with_scale(scale, scale, scale),
23    );
24    let r = universe.world.add_component(RenderableComponent::cube());
25    let c = universe
26        .world
27        .add_component(ColorComponent::rgba(color.0, color.1, color.2, 1.0));
28
29    let _ = universe.attach(parent, t);
30    let _ = universe.attach(t, r);
31    let _ = universe.attach(r, c);
32}
33
34fn main() {
35    mittens_engine::example_support::ensure_model_assets();
36    utils::logger::init();
37
38    let world = engine::ecs::World::default();
39    let mut universe = engine::Universe::new(world);
40
41    // Orange/yellow-ish clear color so cutout edges read.
42    let clear = universe
43        .world
44        .add_component(BackgroundColorComponent::new());
45    let clear_c = universe
46        .world
47        .add_component(ColorComponent::rgba(0.98, 0.72, 0.22, 1.0));
48    let _ = universe.world.add_child(clear, clear_c);
49    universe.add(clear);
50
51    // Warm-ish ambient so the gold cubes don’t go too dark.
52    let ambient = universe
53        .world
54        .add_component(AmbientLightComponent::rgb(0.22, 0.16, 0.08));
55    universe.add(ambient);
56
57    // --- Camera rig (WASD/QE) ---
58    let input = universe
59        .world
60        .add_component(InputComponent::new().with_speed(2.0));
61    let input_mode = universe
62        .world
63        .add_component(InputTransformModeComponent::forward_z().with_roll_axis_y());
64    let _ = universe.attach(input, input_mode);
65
66    // Start a bit pulled back, looking toward the origin.
67    let rig_transform = universe
68        .world
69        .add_component(TransformComponent::new().with_position(0.0, 0.0, 5.0));
70    let _ = universe.attach(input, rig_transform);
71
72    let camera3d = universe
73        .world
74        .add_component(Camera3DComponent::new().with_far(200.0).with_fov(55.0));
75    let _ = universe.attach(rig_transform, camera3d);
76
77    // Topology: I { T { C3D } } — add a small camera-attached controls hint.
78    example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
79    universe.add(input);
80
81    // Key light for toon shading.
82    let light = universe.world.add_component(
83        PointLightComponent::new()
84            .with_distance(50.0)
85            .with_intensity(2.2)
86            .with_color(1.0, 0.98, 0.92),
87    );
88    let light_transform = universe
89        .world
90        .add_component(TransformComponent::new().with_position(2.0, 3.0, 4.0));
91    let _ = universe.attach(light_transform, light);
92    universe.add(light_transform);
93
94    // --- Transparent cutout: 100 cat faces in a 10x10 grid ---
95    // Place them *behind* the starting camera position (camera starts at z=+5.0).
96    // Not attached to the camera rig.
97    let cat_grid_root = universe
98        .world
99        .add_component(TransformComponent::new().with_position(0.0, 0.0, 9.0));
100    universe.add(cat_grid_root);
101
102    let grid_w: i32 = 10;
103    let grid_h: i32 = 10;
104    let spacing: f32 = 0.7;
105    let half_w = (grid_w as f32 - 1.0) * spacing * 0.5;
106    let half_h = (grid_h as f32 - 1.0) * spacing * 0.5;
107
108    for y in 0..grid_h {
109        for x in 0..grid_w {
110            // Topology: cat_grid_root { T_quad { R_quad { Texture + Filtering + Cutout + Color } } }
111            let px = x as f32 * spacing - half_w;
112            let py = y as f32 * spacing - half_h;
113
114            let pz: f32 = (x as f32) % half_w;
115
116            let quad_t = universe.world.add_component(
117                TransformComponent::new()
118                    .with_position(px, py, pz)
119                    .with_scale(0.55, 0.55, 1.0),
120            );
121            let quad_r = universe.world.add_component(RenderableComponent::square());
122
123            let quad_tex = universe.world.add_component(TextureComponent::with_uri(
124                "assets/textures/cat-face-amused.dds",
125            ));
126            let quad_filtering = universe
127                .world
128                .add_component(TextureFilteringComponent::linear());
129            let quad_cutout = universe
130                .world
131                .add_component(TransparentCutoutComponent::new());
132            let quad_color = universe
133                .world
134                .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
135
136            let _ = universe.attach(cat_grid_root, quad_t);
137            let _ = universe.attach(quad_t, quad_r);
138            let _ = universe.attach(quad_r, quad_tex);
139            let _ = universe.attach(quad_r, quad_filtering);
140            let _ = universe.attach(quad_r, quad_cutout);
141            let _ = universe.attach(quad_r, quad_color);
142        }
143    }
144
145    // --- Gold/yellow cubes behind the quad ---
146    // Parent them under a transform so it’s easy to tweak their depth.
147    let cubes_root = universe
148        .world
149        .add_component(TransformComponent::new().with_position(0.0, 0.0, 4.6));
150
151    // point light for cats
152    let cat_light_tx = universe
153        .world
154        .add_component(TransformComponent::new().with_position(0.0, 2.0, 7.0));
155
156    let cat_light = universe.world.add_component(
157        PointLightComponent::new()
158            .with_distance(150.0)
159            .with_intensity(1.5)
160            .with_color(1.0, 0.98, 0.92),
161    );
162    let _ = universe.attach(cat_light_tx, cat_light);
163    let _ = universe.attach(cubes_root, cat_light_tx);
164
165    universe.add(cubes_root);
166
167    let gold_a = (1.0, 0.86, 0.22);
168    let gold_b = (1.0, 0.74, 0.10);
169    let gold_c = (0.95, 0.92, 0.32);
170
171    // A loose cluster that’s visible through the cutout (transparent) area.
172    spawn_gold_cube(&mut universe, cubes_root, (-1.1, -0.3, -0.2), 0.45, gold_a);
173    spawn_gold_cube(&mut universe, cubes_root, (1.0, -0.4, -0.4), 0.40, gold_b);
174    spawn_gold_cube(&mut universe, cubes_root, (-0.2, 0.9, -0.6), 0.38, gold_c);
175    spawn_gold_cube(&mut universe, cubes_root, (0.7, 0.6, -0.9), 0.32, gold_a);
176    spawn_gold_cube(&mut universe, cubes_root, (-0.8, 0.4, -1.1), 0.36, gold_b);
177
178    // Bigger orange/yellow cubes behind the cluster (to make the cutout depth obvious).
179    let orange_gold_a = (1.0, 0.62, 0.10);
180    let orange_gold_b = (1.0, 0.78, 0.18);
181    spawn_gold_cube(
182        &mut universe,
183        cubes_root,
184        (0.0, -0.1, -2.1),
185        1.15,
186        orange_gold_b,
187    );
188    spawn_gold_cube(
189        &mut universe,
190        cubes_root,
191        (-1.8, 0.6, -2.6),
192        0.95,
193        orange_gold_a,
194    );
195    spawn_gold_cube(
196        &mut universe,
197        cubes_root,
198        (1.9, 0.7, -2.9),
199        1.05,
200        orange_gold_b,
201    );
202    spawn_gold_cube(
203        &mut universe,
204        cubes_root,
205        (0.9, 1.8, -3.2),
206        0.90,
207        orange_gold_a,
208    );
209    spawn_gold_cube(
210        &mut universe,
211        cubes_root,
212        (-0.9, 1.7, -3.5),
213        1.10,
214        orange_gold_b,
215    );
216
217    // Process init-time registrations (loads textures, registers renderables, etc.).
218    universe.systems.process_commands(
219        &mut universe.world,
220        &mut universe.visuals,
221        &mut universe.render_assets,
222        &mut universe.command_queue,
223    );
224
225    universe.enable_repl();
226    engine::Windowing::run_app(universe).expect("Windowing failed");
227}