Skip to main content

background_occlusion_example/
background-occlusion-example.rs

1use mittens_engine::{engine, utils};
2
3#[path = "example_util/mod.rs"]
4mod example_util;
5
6fn hash_u32(mut x: u32) -> u32 {
7    x ^= x >> 16;
8    x = x.wrapping_mul(0x7feb_352d);
9    x ^= x >> 15;
10    x = x.wrapping_mul(0x846c_a68b);
11    x ^= x >> 16;
12    x
13}
14
15fn rand01(seed: u32) -> f32 {
16    (hash_u32(seed) as f32) / (u32::MAX as f32)
17}
18
19fn main() {
20    mittens_engine::example_support::ensure_model_assets();
21    utils::logger::init();
22
23    let world = engine::ecs::World::default();
24    let mut universe = engine::Universe::new(world);
25
26    // Light purple-red background so the occlusion reads.
27    let clear = universe
28        .world
29        .add_component(engine::ecs::component::BackgroundColorComponent::new());
30    let clear_c = universe
31        .world
32        .add_component(engine::ecs::component::ColorComponent::rgba(
33            0.62, 0.38, 0.56, 1.0,
34        ));
35    let _ = universe.world.add_child(clear, clear_c);
36    universe.add(clear);
37
38    // A bit of ambient so the cluster volume reads.
39    let ambient = universe
40        .world
41        .add_component(engine::ecs::component::AmbientLightComponent::rgb(
42            0.20, 0.15, 0.3,
43        ));
44    universe.add(ambient);
45
46    // --- Camera rig (WASD/QE) ---
47    let input = universe
48        .world
49        .add_component(engine::ecs::component::InputComponent::new().with_speed(2.0));
50    let input_mode = universe.world.add_component(
51        engine::ecs::component::InputTransformModeComponent::forward_z().with_roll_axis_y(),
52    );
53    let _ = universe.attach(input, input_mode);
54
55    let rig_transform = universe.world.add_component(
56        engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 6.0),
57    );
58    let _ = universe.attach(input, rig_transform);
59
60    let camera3d = universe.world.add_component(
61        engine::ecs::component::Camera3DComponent::new()
62            .with_far(200.0)
63            .with_fov(70.0),
64    );
65    let _ = universe.attach(rig_transform, camera3d);
66
67    // Topology: I { T { C3D } } — add a small camera-attached controls hint.
68    example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
69
70    // Key directional light toward the cloud volume.
71    //
72    // Directional lights encode their direction in the node's world position.
73    // The shader normalizes this vector.
74    let sun = universe.world.add_component(
75        engine::ecs::component::DirectionalLightComponent::new()
76            .with_intensity(1.6)
77            .with_color(1.0, 0.98, 0.92),
78    );
79    // Pointing from the clouds back toward the camera a bit (+Z), and slightly from above.
80    let sun_dir = universe.world.add_component(
81        engine::ecs::component::TransformComponent::new().with_position(0.15, 0.65, 0.75),
82    );
83    let _ = universe.attach(sun_dir, sun);
84
85    // A couple point lights to fill/shade the cloud volume.
86    let light_a = universe.world.add_component(
87        engine::ecs::component::PointLightComponent::new()
88            .with_distance(80.0)
89            .with_intensity(3.0)
90            .with_color(0.9, 0.95, 1.0),
91    );
92    let light_a_tx = universe.world.add_component(
93        engine::ecs::component::TransformComponent::new().with_position(8.0, 8.0, 5.0),
94    );
95    let _ = universe.attach(light_a_tx, light_a);
96
97    let light_b = universe.world.add_component(
98        engine::ecs::component::PointLightComponent::new()
99            .with_distance(80.0)
100            .with_intensity(2.2)
101            .with_color(1.0, 0.9, 0.85),
102    );
103    let light_b_tx = universe.world.add_component(
104        engine::ecs::component::TransformComponent::new().with_position(-10.0, 4.0, -6.0),
105    );
106    let _ = universe.attach(light_b_tx, light_b);
107
108    universe.add(input);
109    universe.add(sun_dir);
110    universe.add(light_a_tx);
111    universe.add(light_b_tx);
112
113    let cube_mesh = universe
114        .render_assets
115        .get_mesh(engine::graphics::BuiltinMeshType::Cube);
116
117    // --- Background occluded+lit world ---
118    // Renderables under this node participate in a background stage that depth-writes for
119    // self-occlusion + uses the normal lighting shader inputs.
120    let bg_root = universe.world.add_component(
121        engine::ecs::component::BackgroundComponent::new().with_occlusion_and_lighting(),
122    );
123    universe.add(bg_root);
124
125    // Create overlapping cube clusters like cloud puffs.
126    // Place them generally in front of the camera (negative Z).
127    // Spawn two groups on opposite X sides.
128    let cluster_count: u32 = 9;
129    for (group_i, group_x) in [-16.0_f32, 16.0_f32].into_iter().enumerate() {
130        let group_tx = universe.world.add_component(
131            engine::ecs::component::TransformComponent::new().with_position(group_x, 0.0, 0.0),
132        );
133        let _ = universe.attach(bg_root, group_tx);
134
135        for cluster_i in 0..cluster_count {
136            let seed = (0xC10u32 ^ (group_i as u32).wrapping_mul(0x9E37_79B9))
137                ^ (cluster_i.wrapping_mul(7919));
138
139            let cx = (rand01(seed ^ 0xA341_316C) - 0.5) * 18.0;
140            let cy = (rand01(seed ^ 0xC801_3EA4) - 0.5) * 10.0 + 2.0;
141            let cz = -18.0 - rand01(seed ^ 0xB529_7A4D) * 26.0;
142
143            let center_tx = universe.world.add_component(
144                engine::ecs::component::TransformComponent::new().with_position(cx, cy, cz),
145            );
146            let _ = universe.attach(group_tx, center_tx);
147
148            let puffs = 18u32;
149            for puff_i in 0..puffs {
150                let puff_seed = seed ^ puff_i.wrapping_mul(1_103_515_245);
151
152                // Slightly ellipsoidal distribution.
153                let ox = (rand01(puff_seed ^ 0x68bc_21eb) - 0.5) * 7.0;
154                let oy = (rand01(puff_seed ^ 0x02e5_be93) - 0.5) * 3.0;
155                let oz = (rand01(puff_seed ^ 0xa1d3_4f2b) - 0.5) * 7.0;
156
157                let base = 0.7 + rand01(puff_seed ^ 0x9e37_79b9) * 2.6;
158                let sx = base * (0.7 + rand01(puff_seed ^ 0x243f_6a88) * 0.8);
159                let sy = base * (0.6 + rand01(puff_seed ^ 0x85a3_08d3) * 0.9);
160                let sz = base * (0.7 + rand01(puff_seed ^ 0x1319_8a2e) * 0.8);
161
162                let tx = universe.world.add_component(
163                    engine::ecs::component::TransformComponent::new()
164                        .with_position(ox, oy, oz)
165                        .with_scale(sx, sy, sz),
166                );
167                let renderable =
168                    universe
169                        .world
170                        .add_component(engine::ecs::component::RenderableComponent::new(
171                            engine::graphics::primitives::Renderable::new(
172                                cube_mesh,
173                                engine::graphics::primitives::MaterialHandle::TOON_MESH,
174                            ),
175                        ));
176
177                // Slight blue-grey variation.
178                let t = rand01(puff_seed ^ 0x7f4a_7c15);
179                let r = 0.55 + 0.10 * t;
180                let g = 0.58 + 0.10 * t;
181                let b = 0.66 + 0.12 * t;
182                let color = universe
183                    .world
184                    .add_component(engine::ecs::component::ColorComponent::rgba(r, g, b, 1.0));
185
186                let _ = universe.attach(center_tx, tx);
187                let _ = universe.attach(tx, renderable);
188                let _ = universe.attach(renderable, color);
189            }
190        }
191    }
192
193    // Foreground reference cube (should never be occluded by background depth).
194    let fg_tx = universe.world.add_component(
195        engine::ecs::component::TransformComponent::new()
196            .with_position(0.0, -0.5, -4.0)
197            .with_scale(1.2, 0.8, 1.2),
198    );
199    let fg_renderable =
200        universe
201            .world
202            .add_component(engine::ecs::component::RenderableComponent::new(
203                engine::graphics::primitives::Renderable::new(
204                    cube_mesh,
205                    engine::graphics::primitives::MaterialHandle::TOON_MESH,
206                ),
207            ));
208    let fg_color = universe
209        .world
210        .add_component(engine::ecs::component::ColorComponent::rgba(
211            0.9, 0.2, 0.2, 1.0,
212        ));
213
214    universe.add(fg_tx);
215    let _ = universe.attach(fg_tx, fg_renderable);
216    let _ = universe.attach(fg_renderable, fg_color);
217
218    // Foreground opaque floor: 16x16 larger white cubes, 10 units below the reference cube.
219    let floor_root = universe.world.add_component(
220        engine::ecs::component::TransformComponent::new().with_position(0.0, -10.5, -10.0),
221    );
222    universe.add(floor_root);
223
224    let spacing = 10_f32;
225    let half = 5_f32;
226    for x in 0..16u32 {
227        for z in 0..64u32 {
228            let px = (x as f32 - half) * spacing;
229            let pz = (z as f32 * -1.0 + half) * spacing;
230            let tx = universe.world.add_component(
231                engine::ecs::component::TransformComponent::new()
232                    .with_position(px, -5.0, pz)
233                    .with_scale(5.0, 0.5, 5.0),
234            );
235            let renderable =
236                universe
237                    .world
238                    .add_component(engine::ecs::component::RenderableComponent::new(
239                        engine::graphics::primitives::Renderable::new(
240                            cube_mesh,
241                            engine::graphics::primitives::MaterialHandle::TOON_MESH,
242                        ),
243                    ));
244            let color = universe
245                .world
246                .add_component(engine::ecs::component::ColorComponent::rgba(
247                    1.0, 1.0, 1.0, 1.0,
248                ));
249
250            let _ = universe.attach(floor_root, tx);
251            let _ = universe.attach(tx, renderable);
252            let _ = universe.attach(renderable, color);
253        }
254    }
255
256    universe.systems.process_commands(
257        &mut universe.world,
258        &mut universe.visuals,
259        &mut universe.render_assets,
260        &mut universe.command_queue,
261    );
262
263    universe.enable_repl();
264    engine::Windowing::run_app(universe).expect("Windowing failed");
265}