scenes/scenes.rs
1//! Scene management demo - demonstrates switching between independent scenes.
2//!
3//! This example shows:
4//! - Two separate scenes with independent cameras
5//! - Per-scene render pipelines (different post-processing per scene)
6//! - Instant and animated transitions (fade-to-black, crossfade)
7//!
8//! Controls:
9//! - ENTER: Switch to cockpit scene
10//! - ESCAPE: Switch back to external scene
11//! - 1-3: Change transition type (instant, fade, crossfade)
12
13use hoplite::{
14 AppConfig, Color, KeyCode, OrbitCamera, OrbitMode, Quat, Transform, Vec3,
15 run_with_scenes_config, scene::Transition,
16};
17
18fn main() {
19 run_with_scenes_config(
20 AppConfig::new()
21 .title("Scene Management Demo")
22 .size(1280, 720),
23 |ctx| {
24 ctx.default_font(18.0);
25
26 // Shared assets - available to all scenes
27 let cube = ctx.mesh_cube();
28 let plane = ctx.mesh_plane(30.0);
29
30 // Track current transition type
31 let transition_type = std::rc::Rc::new(std::cell::RefCell::new(TransitionType::Fade));
32 let transition_type_1 = transition_type.clone();
33 let transition_type_2 = transition_type.clone();
34
35 // =========================================================
36 // Scene 1: External view (ship floating in space)
37 // =========================================================
38 ctx.scene("external", |scene| {
39 scene.background_color(Color::rgb(0.02, 0.02, 0.05));
40 scene.enable_mesh_rendering();
41
42 let mut orbit = OrbitCamera::new()
43 .target(Vec3::ZERO)
44 .distance(8.0)
45 .elevation(0.3)
46 .fov(75.0)
47 .mode(OrbitMode::AutoRotate { speed: 0.2 });
48
49 let cube = cube;
50 let transition_type = transition_type_1;
51
52 move |frame| {
53 orbit.update(frame.input, frame.dt);
54 frame.set_camera(orbit.camera());
55
56 // Update transition type based on key presses
57 if frame.input.key_pressed(KeyCode::Digit1) {
58 *transition_type.borrow_mut() = TransitionType::Instant;
59 }
60 if frame.input.key_pressed(KeyCode::Digit2) {
61 *transition_type.borrow_mut() = TransitionType::Fade;
62 }
63 if frame.input.key_pressed(KeyCode::Digit3) {
64 *transition_type.borrow_mut() = TransitionType::Crossfade;
65 }
66
67 // Capture time before building meshes
68 let time = frame.time;
69 let ship_rotation = Quat::from_rotation_y(time * 0.1);
70
71 // Draw a simple "ship" - main body
72 frame
73 .mesh(cube)
74 .transform(
75 Transform::new()
76 .scale(Vec3::new(2.0, 0.5, 3.0))
77 .rotation(ship_rotation),
78 )
79 .color(Color::rgb(0.4, 0.4, 0.5))
80 .draw();
81
82 // Wings
83 for x in [-1.5, 1.5] {
84 frame
85 .mesh(cube)
86 .transform(
87 Transform::new()
88 .position(Vec3::new(x, 0.0, 0.0))
89 .scale(Vec3::new(1.5, 0.1, 1.0))
90 .rotation(ship_rotation),
91 )
92 .color(Color::rgb(0.3, 0.3, 0.4))
93 .draw();
94 }
95
96 // Cockpit (blue tinted)
97 frame
98 .mesh(cube)
99 .transform(
100 Transform::new()
101 .position(Vec3::new(0.0, 0.3, 0.8))
102 .scale(Vec3::new(0.6, 0.3, 0.4))
103 .rotation(ship_rotation),
104 )
105 .color(Color::rgb(0.2, 0.3, 0.5))
106 .draw();
107
108 // Engine glow
109 frame
110 .mesh(cube)
111 .transform(
112 Transform::new()
113 .position(Vec3::new(0.0, 0.0, -1.6))
114 .scale(Vec3::new(0.4, 0.3, 0.2))
115 .rotation(ship_rotation),
116 )
117 .color(Color::rgb(0.8, 0.4, 0.2))
118 .draw();
119
120 // Some "stars" (small cubes in the distance)
121 let star_positions = [
122 Vec3::new(10.0, 5.0, -15.0),
123 Vec3::new(-12.0, 8.0, -20.0),
124 Vec3::new(8.0, -3.0, -18.0),
125 Vec3::new(-6.0, 10.0, -25.0),
126 Vec3::new(15.0, -5.0, -22.0),
127 ];
128 for pos in star_positions {
129 frame
130 .mesh(cube)
131 .transform(Transform::new().position(pos).scale(Vec3::splat(0.1)))
132 .color(Color::rgb(0.9, 0.9, 1.0))
133 .draw();
134 }
135
136 // UI
137 frame.text(10.0, 10.0, "EXTERNAL VIEW");
138 frame.text(10.0, 35.0, "Press ENTER to enter cockpit");
139 frame.text_color(
140 10.0,
141 60.0,
142 &format!(
143 "Transition: {} (1-3 to change)",
144 transition_type.borrow().name()
145 ),
146 Color::rgb(0.6, 0.6, 0.6),
147 );
148
149 // Switch to cockpit on Enter
150 if frame.input.key_pressed(KeyCode::Enter) {
151 let transition = transition_type.borrow().to_transition();
152 frame.switch_to_with("cockpit", transition);
153 }
154 }
155 });
156
157 // =========================================================
158 // Scene 2: Cockpit interior view
159 // =========================================================
160 ctx.scene("cockpit", |scene| {
161 scene.background_color(Color::rgb(0.05, 0.03, 0.03));
162 scene.enable_mesh_rendering();
163
164 let cube = cube;
165 let plane = plane;
166 let transition_type = transition_type_2;
167 let mut look_yaw = 0.0_f32;
168
169 move |frame| {
170 // Simple look around with arrow keys
171 if frame.input.key_down(KeyCode::ArrowLeft) {
172 look_yaw += 1.5 * frame.dt;
173 }
174 if frame.input.key_down(KeyCode::ArrowRight) {
175 look_yaw -= 1.5 * frame.dt;
176 }
177
178 // Build camera with current look direction
179 let camera = hoplite::Camera::new()
180 .at(Vec3::new(0.0, 1.0, 0.0))
181 .looking_at(Vec3::new(look_yaw.sin(), 1.0, -look_yaw.cos()))
182 .with_fov(90.0);
183 frame.set_camera(camera);
184
185 // Update transition type based on key presses
186 if frame.input.key_pressed(KeyCode::Digit1) {
187 *transition_type.borrow_mut() = TransitionType::Instant;
188 }
189 if frame.input.key_pressed(KeyCode::Digit2) {
190 *transition_type.borrow_mut() = TransitionType::Fade;
191 }
192 if frame.input.key_pressed(KeyCode::Digit3) {
193 *transition_type.borrow_mut() = TransitionType::Crossfade;
194 }
195
196 // Capture time for animations
197 let time = frame.time;
198
199 // Draw cockpit interior
200 // Floor
201 frame
202 .mesh(plane)
203 .transform(Transform::new().scale(Vec3::splat(0.3)))
204 .color(Color::rgb(0.15, 0.12, 0.12))
205 .draw();
206
207 // Console in front
208 frame
209 .mesh(cube)
210 .transform(
211 Transform::new()
212 .position(Vec3::new(0.0, 0.6, -1.5))
213 .scale(Vec3::new(2.0, 0.8, 0.3)),
214 )
215 .color(Color::rgb(0.2, 0.2, 0.25))
216 .draw();
217
218 // Display screens on console
219 let screen_positions = [-0.6, 0.0, 0.6];
220 for (i, x) in screen_positions.iter().enumerate() {
221 let glow = (time * 2.0 + i as f32).sin() * 0.1 + 0.5;
222 frame
223 .mesh(cube)
224 .transform(
225 Transform::new()
226 .position(Vec3::new(*x, 0.9, -1.3))
227 .scale(Vec3::new(0.4, 0.3, 0.05)),
228 )
229 .color(Color::rgb(0.1, glow * 0.5, glow))
230 .draw();
231 }
232
233 // Side panels
234 for x in [-1.2, 1.2] {
235 frame
236 .mesh(cube)
237 .transform(
238 Transform::new()
239 .position(Vec3::new(x, 1.0, -0.5))
240 .scale(Vec3::new(0.1, 1.5, 2.0)),
241 )
242 .color(Color::rgb(0.12, 0.1, 0.1))
243 .draw();
244 }
245
246 // Ceiling
247 frame
248 .mesh(cube)
249 .transform(
250 Transform::new()
251 .position(Vec3::new(0.0, 2.2, -0.5))
252 .scale(Vec3::new(2.5, 0.1, 3.0)),
253 )
254 .color(Color::rgb(0.08, 0.08, 0.1))
255 .draw();
256
257 // Window (brighter area showing "space")
258 frame
259 .mesh(cube)
260 .transform(
261 Transform::new()
262 .position(Vec3::new(0.0, 1.5, -2.0))
263 .scale(Vec3::new(1.8, 0.8, 0.02)),
264 )
265 .color(Color::rgb(0.02, 0.02, 0.08))
266 .draw();
267
268 // Blinking warning lights
269 let blink = if (time * 3.0).sin() > 0.0 { 0.8 } else { 0.2 };
270 for x in [-0.9, 0.9] {
271 frame
272 .mesh(cube)
273 .transform(
274 Transform::new()
275 .position(Vec3::new(x, 1.8, -1.4))
276 .scale(Vec3::splat(0.08)),
277 )
278 .color(Color::rgb(blink, blink * 0.2, 0.0))
279 .draw();
280 }
281
282 // UI
283 frame.text(10.0, 10.0, "COCKPIT VIEW");
284 frame.text(10.0, 35.0, "Arrow keys to look around");
285 frame.text(10.0, 60.0, "Press ESCAPE for external view");
286 frame.text_color(
287 10.0,
288 85.0,
289 &format!(
290 "Transition: {} (1-3 to change)",
291 transition_type.borrow().name()
292 ),
293 Color::rgb(0.6, 0.6, 0.6),
294 );
295
296 // Switch back to external on Escape
297 if frame.input.key_pressed(KeyCode::Escape) {
298 let transition = transition_type.borrow().to_transition();
299 frame.switch_to_with("external", transition);
300 }
301 }
302 });
303
304 // Start in external view
305 ctx.start_scene("external");
306 },
307 );
308}
309
310#[derive(Clone, Copy)]
311enum TransitionType {
312 Instant,
313 Fade,
314 Crossfade,
315}
316
317impl TransitionType {
318 fn name(&self) -> &'static str {
319 match self {
320 TransitionType::Instant => "Instant",
321 TransitionType::Fade => "Fade to Black",
322 TransitionType::Crossfade => "Crossfade",
323 }
324 }
325
326 fn to_transition(&self) -> Transition {
327 match self {
328 TransitionType::Instant => Transition::instant(),
329 TransitionType::Fade => Transition::fade_to_black(0.5),
330 TransitionType::Crossfade => Transition::crossfade(0.8),
331 }
332 }
333}