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}