Skip to main content

testbed_3d/
3d.rs

1//! 3d testbed
2//!
3//! You can switch scene by pressing the spacebar
4
5mod helpers;
6
7use argh::FromArgs;
8use bevy::prelude::*;
9use helpers::Next;
10
11#[derive(FromArgs)]
12/// 3d testbed
13pub struct Args {
14    #[argh(positional)]
15    scene: Option<Scene>,
16}
17
18fn main() {
19    #[cfg(not(target_arch = "wasm32"))]
20    let args: Args = argh::from_env();
21    #[cfg(target_arch = "wasm32")]
22    let args: Args = Args::from_args(&[], &[]).unwrap();
23
24    let mut app = App::new();
25    app.add_plugins((DefaultPlugins,))
26        .add_systems(OnEnter(Scene::Light), light::setup)
27        .add_systems(OnEnter(Scene::Bloom), bloom::setup)
28        .add_systems(OnEnter(Scene::Gltf), gltf::setup)
29        .add_systems(OnEnter(Scene::Animation), animation::setup)
30        .add_systems(OnEnter(Scene::Gizmos), gizmos::setup)
31        .add_systems(
32            OnEnter(Scene::GltfCoordinateConversion),
33            gltf_coordinate_conversion::setup,
34        )
35        .add_systems(
36            OnEnter(Scene::WhiteFurnaceSolidColorLight),
37            white_furnace_solid_color_light::setup,
38        )
39        .add_systems(
40            OnEnter(Scene::WhiteFurnaceEnvironmentMapLight),
41            white_furnace_environment_map_light::setup,
42        )
43        .add_systems(OnEnter(Scene::RenderLayers), render_layers::setup)
44        .add_systems(Update, switch_scene)
45        .add_systems(Update, gizmos::draw_gizmos.run_if(in_state(Scene::Gizmos)))
46        .add_systems(
47            Update,
48            gltf_coordinate_conversion::draw_gizmos
49                .run_if(in_state(Scene::GltfCoordinateConversion)),
50        );
51
52    match args.scene {
53        None => app.init_state::<Scene>(),
54        Some(scene) => app.insert_state(scene),
55    };
56
57    #[cfg(feature = "bevy_ci_testing")]
58    app.add_systems(Update, helpers::switch_scene_in_ci::<Scene>);
59
60    app.run();
61}
62
63#[derive(Debug, Clone, Eq, PartialEq, Hash, States, Default)]
64enum Scene {
65    #[default]
66    Light,
67    Bloom,
68    Gltf,
69    Animation,
70    Gizmos,
71    GltfCoordinateConversion,
72    WhiteFurnaceSolidColorLight,
73    WhiteFurnaceEnvironmentMapLight,
74    RenderLayers,
75}
76
77impl std::str::FromStr for Scene {
78    type Err = String;
79
80    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
81        let mut isit = Self::default();
82        while s.to_lowercase() != format!("{isit:?}").to_lowercase() {
83            isit = isit.next();
84            if isit == Self::default() {
85                return Err(format!("Invalid Scene name: {s}"));
86            }
87        }
88        Ok(isit)
89    }
90}
91
92impl Next for Scene {
93    fn next(&self) -> Self {
94        match self {
95            Scene::Light => Scene::Bloom,
96            Scene::Bloom => Scene::Gltf,
97            Scene::Gltf => Scene::Animation,
98            Scene::Animation => Scene::Gizmos,
99            Scene::Gizmos => Scene::GltfCoordinateConversion,
100            Scene::GltfCoordinateConversion => Scene::WhiteFurnaceSolidColorLight,
101            Scene::WhiteFurnaceSolidColorLight => Scene::WhiteFurnaceEnvironmentMapLight,
102            Scene::WhiteFurnaceEnvironmentMapLight => Scene::RenderLayers,
103            Scene::RenderLayers => Scene::Light,
104        }
105    }
106}
107
108fn switch_scene(
109    keyboard: Res<ButtonInput<KeyCode>>,
110    scene: Res<State<Scene>>,
111    mut next_scene: ResMut<NextState<Scene>>,
112) {
113    if keyboard.just_pressed(KeyCode::Space) {
114        info!("Switching scene");
115        next_scene.set(scene.get().next());
116    }
117}
118
119mod light {
120    use std::f32::consts::PI;
121
122    use bevy::{
123        color::palettes::css::{DEEP_PINK, LIME, RED},
124        prelude::*,
125    };
126
127    const CURRENT_SCENE: super::Scene = super::Scene::Light;
128
129    pub fn setup(
130        mut commands: Commands,
131        mut meshes: ResMut<Assets<Mesh>>,
132        mut materials: ResMut<Assets<StandardMaterial>>,
133    ) {
134        commands.spawn((
135            Mesh3d(meshes.add(Plane3d::default().mesh().size(10.0, 10.0))),
136            MeshMaterial3d(materials.add(StandardMaterial {
137                base_color: Color::WHITE,
138                perceptual_roughness: 1.0,
139                ..default()
140            })),
141            DespawnOnExit(CURRENT_SCENE),
142        ));
143
144        commands.spawn((
145            Mesh3d(meshes.add(Cuboid::default())),
146            MeshMaterial3d(materials.add(StandardMaterial {
147                base_color: DEEP_PINK.into(),
148                ..default()
149            })),
150            Transform::from_xyz(0.0, 1.0, 0.0),
151            DespawnOnExit(CURRENT_SCENE),
152        ));
153
154        commands.spawn((
155            PointLight {
156                intensity: 100_000.0,
157                color: RED.into(),
158                shadow_maps_enabled: true,
159                ..default()
160            },
161            Transform::from_xyz(1.0, 2.0, 0.0),
162            DespawnOnExit(CURRENT_SCENE),
163        ));
164
165        commands.spawn((
166            SpotLight {
167                intensity: 100_000.0,
168                color: LIME.into(),
169                shadow_maps_enabled: true,
170                inner_angle: 0.6,
171                outer_angle: 0.8,
172                ..default()
173            },
174            Transform::from_xyz(-1.0, 2.0, 0.0).looking_at(Vec3::new(-1.0, 0.0, 0.0), Vec3::Z),
175            DespawnOnExit(CURRENT_SCENE),
176        ));
177
178        commands.spawn((
179            DirectionalLight {
180                illuminance: light_consts::lux::OVERCAST_DAY,
181                shadow_maps_enabled: true,
182                ..default()
183            },
184            Transform {
185                translation: Vec3::new(0.0, 2.0, 0.0),
186                rotation: Quat::from_rotation_x(-PI / 4.),
187                ..default()
188            },
189            DespawnOnExit(CURRENT_SCENE),
190        ));
191
192        commands.spawn((
193            RectLight {
194                color: Color::srgb(0.5, 0.7, 1.0),
195                intensity: 100_000.0,
196                width: 1.5,
197                height: 4.0,
198                range: 20.0,
199            },
200            Transform::from_xyz(1.0, 2.0, -2.0).looking_at(Vec3::new(-1.0, 0.0, 0.0), Vec3::Y),
201            DespawnOnExit(CURRENT_SCENE),
202        ));
203
204        commands.spawn((
205            Camera3d::default(),
206            Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
207            DespawnOnExit(CURRENT_SCENE),
208        ));
209    }
210}
211
212mod bloom {
213    use bevy::{core_pipeline::tonemapping::Tonemapping, post_process::bloom::Bloom, prelude::*};
214
215    const CURRENT_SCENE: super::Scene = super::Scene::Bloom;
216
217    pub fn setup(
218        mut commands: Commands,
219        mut meshes: ResMut<Assets<Mesh>>,
220        mut materials: ResMut<Assets<StandardMaterial>>,
221    ) {
222        commands.spawn((
223            Camera3d::default(),
224            Tonemapping::TonyMcMapface,
225            Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
226            Bloom::NATURAL,
227            DespawnOnExit(CURRENT_SCENE),
228        ));
229
230        let material_emissive1 = materials.add(StandardMaterial {
231            emissive: LinearRgba::rgb(13.99, 5.32, 2.0),
232            ..default()
233        });
234        let material_emissive2 = materials.add(StandardMaterial {
235            emissive: LinearRgba::rgb(2.0, 13.99, 5.32),
236            ..default()
237        });
238
239        let mesh = meshes.add(Sphere::new(0.5).mesh().ico(5).unwrap());
240
241        for z in -2..3_i32 {
242            let material = match (z % 2).abs() {
243                0 => material_emissive1.clone(),
244                1 => material_emissive2.clone(),
245                _ => unreachable!(),
246            };
247
248            commands.spawn((
249                Mesh3d(mesh.clone()),
250                MeshMaterial3d(material),
251                Transform::from_xyz(z as f32 * 2.0, 0.0, 0.0),
252                DespawnOnExit(CURRENT_SCENE),
253            ));
254        }
255    }
256}
257
258mod gltf {
259    use bevy::prelude::*;
260
261    const CURRENT_SCENE: super::Scene = super::Scene::Gltf;
262
263    pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
264        commands.spawn((
265            Camera3d::default(),
266            Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
267            EnvironmentMapLight {
268                diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
269                specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
270                intensity: 250.0,
271                ..default()
272            },
273            DespawnOnExit(CURRENT_SCENE),
274        ));
275
276        commands.spawn((
277            DirectionalLight {
278                shadow_maps_enabled: true,
279                ..default()
280            },
281            DespawnOnExit(CURRENT_SCENE),
282        ));
283        commands.spawn((
284            WorldAssetRoot(asset_server.load(
285                GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"),
286            )),
287            DespawnOnExit(CURRENT_SCENE),
288        ));
289    }
290}
291
292mod animation {
293    use std::{f32::consts::PI, time::Duration};
294
295    use bevy::{prelude::*, world_serialization::WorldInstanceReady};
296
297    const CURRENT_SCENE: super::Scene = super::Scene::Animation;
298    const FOX_PATH: &str = "models/animated/Fox.glb";
299
300    #[derive(Resource)]
301    struct Animation {
302        animation: AnimationNodeIndex,
303        graph: Handle<AnimationGraph>,
304    }
305
306    pub fn setup(
307        mut commands: Commands,
308        asset_server: Res<AssetServer>,
309        mut graphs: ResMut<Assets<AnimationGraph>>,
310    ) {
311        let (graph, node) = AnimationGraph::from_clip(
312            asset_server.load(GltfAssetLabel::Animation(2).from_asset(FOX_PATH)),
313        );
314
315        let graph_handle = graphs.add(graph);
316        commands.insert_resource(Animation {
317            animation: node,
318            graph: graph_handle,
319        });
320
321        commands.spawn((
322            Camera3d::default(),
323            Transform::from_xyz(100.0, 100.0, 150.0).looking_at(Vec3::new(0.0, 20.0, 0.0), Vec3::Y),
324            DespawnOnExit(CURRENT_SCENE),
325        ));
326
327        commands.spawn((
328            Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)),
329            DirectionalLight {
330                shadow_maps_enabled: true,
331                ..default()
332            },
333            DespawnOnExit(CURRENT_SCENE),
334        ));
335
336        commands
337            .spawn((
338                WorldAssetRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(FOX_PATH))),
339                DespawnOnExit(CURRENT_SCENE),
340            ))
341            .observe(pause_animation_frame);
342    }
343
344    fn pause_animation_frame(
345        scene_ready: On<WorldInstanceReady>,
346        children: Query<&Children>,
347        mut commands: Commands,
348        animation: Res<Animation>,
349        mut players: Query<(Entity, &mut AnimationPlayer)>,
350    ) {
351        for child in children.iter_descendants(scene_ready.entity) {
352            if let Ok((entity, mut player)) = players.get_mut(child) {
353                let mut transitions = AnimationTransitions::new();
354                transitions
355                    .play(&mut player, animation.animation, Duration::ZERO)
356                    .seek_to(0.5)
357                    .pause();
358
359                commands
360                    .entity(entity)
361                    .insert(AnimationGraphHandle(animation.graph.clone()))
362                    .insert(transitions);
363            }
364        }
365    }
366}
367
368mod gizmos {
369    use bevy::{color::palettes::css::*, prelude::*};
370
371    pub fn setup(mut commands: Commands) {
372        commands.spawn((
373            Camera3d::default(),
374            Transform::from_xyz(-1.0, 2.5, 6.5).looking_at(Vec3::ZERO, Vec3::Y),
375            DespawnOnExit(super::Scene::Gizmos),
376        ));
377    }
378
379    pub fn draw_gizmos(mut gizmos: Gizmos) {
380        gizmos.cube(
381            Transform::from_translation(Vec3::X * -1.75).with_scale(Vec3::splat(1.25)),
382            RED,
383        );
384        gizmos
385            .sphere(Isometry3d::from_translation(Vec3::X * -3.5), 0.75, GREEN)
386            .resolution(30_000 / 3);
387
388        gizmos.text(
389            Isometry3d::from_translation(Vec3::Y * 1.5),
390            "text gizmo",
391            0.3,
392            Vec2 { x: 0., y: 0. },
393            Color::WHITE,
394        );
395
396        // 3d grids with all variations of outer edges on or off
397        for i in 0..8 {
398            let x = 1.5 * (i % 4) as f32;
399            let y = 1.0 * (0.5 - (i / 4) as f32);
400            let mut grid = gizmos.grid_3d(
401                Isometry3d::from_translation(Vec3::new(x, y, 0.0)),
402                UVec3::new(5, 4, 3),
403                Vec3::splat(0.175),
404                Color::WHITE,
405            );
406            if i & 1 > 0 {
407                grid = grid.outer_edges_x();
408            }
409            if i & 2 > 0 {
410                grid = grid.outer_edges_y();
411            }
412            if i & 4 > 0 {
413                grid.outer_edges_z();
414            }
415        }
416    }
417}
418
419mod gltf_coordinate_conversion {
420    use bevy::{
421        color::palettes::basic::*,
422        gltf::{convert_coordinates::GltfConvertCoordinates, GltfLoaderSettings},
423        prelude::*,
424        world_serialization::WorldInstanceReady,
425    };
426
427    const CURRENT_SCENE: super::Scene = super::Scene::GltfCoordinateConversion;
428
429    pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
430        commands.spawn((
431            Camera3d::default(),
432            Transform::from_xyz(-4.0, 4.0, -5.0).looking_at(Vec3::ZERO, Vec3::Y),
433            DespawnOnExit(CURRENT_SCENE),
434        ));
435
436        commands.spawn((
437            DirectionalLight {
438                color: BLUE.into(),
439                ..default()
440            },
441            Transform::IDENTITY.looking_to(Dir3::Z, Dir3::Y),
442            DespawnOnExit(CURRENT_SCENE),
443        ));
444
445        commands.spawn((
446            DirectionalLight {
447                color: RED.into(),
448                ..default()
449            },
450            Transform::IDENTITY.looking_to(Dir3::X, Dir3::Y),
451            DespawnOnExit(CURRENT_SCENE),
452        ));
453
454        commands.spawn((
455            DirectionalLight {
456                color: GREEN.into(),
457                ..default()
458            },
459            Transform::IDENTITY.looking_to(Dir3::NEG_Y, Dir3::X),
460            DespawnOnExit(CURRENT_SCENE),
461        ));
462
463        commands
464            .spawn((
465                WorldAssetRoot(
466                    asset_server
467                        .load_builder()
468                        .with_settings(|s: &mut GltfLoaderSettings| {
469                            s.convert_coordinates = Some(GltfConvertCoordinates {
470                                rotate_scene_entity: true,
471                                rotate_meshes: true,
472                            });
473                        })
474                        .load(GltfAssetLabel::Scene(0).from_asset("models/Faces/faces.glb")),
475                ),
476                DespawnOnExit(CURRENT_SCENE),
477            ))
478            .observe(show_aabbs);
479    }
480
481    pub fn show_aabbs(
482        scene_ready: On<WorldInstanceReady>,
483        mut commands: Commands,
484        children: Query<&Children>,
485        meshes: Query<(), With<Mesh3d>>,
486    ) {
487        for child in children
488            .iter_descendants(scene_ready.entity)
489            .filter(|&e| meshes.contains(e))
490        {
491            commands.entity(child).insert(ShowAabbGizmo {
492                color: Some(BLACK.into()),
493            });
494        }
495    }
496
497    pub fn draw_gizmos(mut gizmos: Gizmos) {
498        gizmos.axes(Transform::IDENTITY, 1.0);
499    }
500}
501
502mod white_furnace_solid_color_light {
503    use bevy::{
504        asset::RenderAssetUsages,
505        camera::{Hdr, ScalingMode},
506        core_pipeline::tonemapping::Tonemapping,
507        light::Skybox,
508        prelude::*,
509        render::render_resource::{
510            Extent3d, TextureDimension, TextureFormat, TextureViewDescriptor, TextureViewDimension,
511        },
512    };
513
514    const CURRENT_SCENE: super::Scene = super::Scene::WhiteFurnaceSolidColorLight;
515
516    /// Creates a pure white cubemap
517    fn create_white_cubemap(size: u32) -> Image {
518        // f16 bytes for 1.0 (white): [0, 60] in little-endian
519        const WHITE_F16: [u8; 2] = [0, 60];
520        const WHITE_PIXEL: [u8; 8] = [
521            WHITE_F16[0],
522            WHITE_F16[1], // R
523            WHITE_F16[0],
524            WHITE_F16[1], // G
525            WHITE_F16[0],
526            WHITE_F16[1], // B
527            WHITE_F16[0],
528            WHITE_F16[1], // A
529        ];
530
531        let pixel_data: Vec<u8> = (0..6 * size * size).flat_map(|_| WHITE_PIXEL).collect();
532
533        Image {
534            texture_view_descriptor: Some(TextureViewDescriptor {
535                dimension: Some(TextureViewDimension::Cube),
536                ..Default::default()
537            }),
538            ..Image::new(
539                Extent3d {
540                    width: size,
541                    height: size,
542                    depth_or_array_layers: 6,
543                },
544                TextureDimension::D2,
545                pixel_data,
546                TextureFormat::Rgba16Float,
547                RenderAssetUsages::RENDER_WORLD,
548            )
549        }
550    }
551
552    pub fn setup(
553        mut commands: Commands,
554        mut meshes: ResMut<Assets<Mesh>>,
555        mut materials: ResMut<Assets<StandardMaterial>>,
556        mut images: ResMut<Assets<Image>>,
557    ) {
558        let sphere_mesh = meshes.add(Sphere::new(0.45));
559
560        // Light should come from the environment map only
561        commands.insert_resource(GlobalAmbientLight::NONE);
562
563        // add entities to the world
564        for y in -2..=2 {
565            for x in -5..=5 {
566                let x01 = (x + 5) as f32 / 10.0;
567                let y01 = (y + 2) as f32 / 4.0;
568                // sphere
569                commands.spawn((
570                    Mesh3d(sphere_mesh.clone()),
571                    MeshMaterial3d(materials.add(StandardMaterial {
572                        base_color: LinearRgba::WHITE.into(),
573                        // vary key PBR parameters on a grid of spheres to show the effect
574                        metallic: y01,
575                        perceptual_roughness: x01,
576                        ..default()
577                    })),
578                    Transform::from_xyz(x as f32, y as f32 + 0.5, 0.0),
579                    DespawnOnExit(CURRENT_SCENE),
580                ));
581            }
582        }
583
584        // Create a pure white cubemap
585        let white_cubemap = create_white_cubemap(256);
586        let white_cubemap_handle = images.add(white_cubemap);
587
588        let mut solid_color_light = EnvironmentMapLight::solid_color(&mut images, Color::WHITE);
589        solid_color_light.intensity = 500.0;
590
591        // camera
592        commands.spawn((
593            Camera3d::default(),
594            Hdr,
595            Tonemapping::None,
596            Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::default(), Vec3::Y),
597            Projection::from(OrthographicProjection {
598                scale: 0.01,
599                scaling_mode: ScalingMode::WindowSize,
600                ..OrthographicProjection::default_3d()
601            }),
602            Skybox {
603                image: Some(white_cubemap_handle),
604                // middle gray
605                brightness: 500.0,
606                ..default()
607            },
608            solid_color_light,
609            DespawnOnExit(CURRENT_SCENE),
610        ));
611    }
612}
613
614mod white_furnace_environment_map_light {
615    use bevy::{
616        asset::RenderAssetUsages,
617        camera::{Hdr, ScalingMode},
618        core_pipeline::tonemapping::Tonemapping,
619        light::Skybox,
620        prelude::*,
621        render::render_resource::{
622            Extent3d, TextureDimension, TextureFormat, TextureViewDescriptor, TextureViewDimension,
623        },
624    };
625
626    const CURRENT_SCENE: super::Scene = super::Scene::WhiteFurnaceEnvironmentMapLight;
627
628    /// Creates a pure white cubemap
629    fn create_white_cubemap(size: u32) -> Image {
630        // f16 bytes for 1.0 (white): [0, 60] in little-endian
631        const WHITE_F16: [u8; 2] = [0, 60];
632        const WHITE_PIXEL: [u8; 8] = [
633            WHITE_F16[0],
634            WHITE_F16[1], // R
635            WHITE_F16[0],
636            WHITE_F16[1], // G
637            WHITE_F16[0],
638            WHITE_F16[1], // B
639            WHITE_F16[0],
640            WHITE_F16[1], // A
641        ];
642
643        let pixel_data: Vec<u8> = (0..6 * size * size).flat_map(|_| WHITE_PIXEL).collect();
644
645        Image {
646            texture_view_descriptor: Some(TextureViewDescriptor {
647                dimension: Some(TextureViewDimension::Cube),
648                ..Default::default()
649            }),
650            ..Image::new(
651                Extent3d {
652                    width: size,
653                    height: size,
654                    depth_or_array_layers: 6,
655                },
656                TextureDimension::D2,
657                pixel_data,
658                TextureFormat::Rgba16Float,
659                RenderAssetUsages::RENDER_WORLD,
660            )
661        }
662    }
663
664    pub fn setup(
665        mut commands: Commands,
666        mut meshes: ResMut<Assets<Mesh>>,
667        mut materials: ResMut<Assets<StandardMaterial>>,
668        mut images: ResMut<Assets<Image>>,
669    ) {
670        let sphere_mesh = meshes.add(Sphere::new(0.45));
671
672        // Light should come from the environment map only
673        commands.insert_resource(GlobalAmbientLight::NONE);
674
675        // add entities to the world
676        for y in -2..=2 {
677            for x in -5..=5 {
678                let x01 = (x + 5) as f32 / 10.0;
679                let y01 = (y + 2) as f32 / 4.0;
680                // sphere
681                commands.spawn((
682                    Mesh3d(sphere_mesh.clone()),
683                    MeshMaterial3d(materials.add(StandardMaterial {
684                        base_color: LinearRgba::WHITE.into(),
685                        // vary key PBR parameters on a grid of spheres to show the effect
686                        metallic: y01,
687                        perceptual_roughness: x01,
688                        ..default()
689                    })),
690                    Transform::from_xyz(x as f32, y as f32 + 0.5, 0.0),
691                    DespawnOnExit(CURRENT_SCENE),
692                ));
693            }
694        }
695
696        // Create a pure white cubemap
697        let white_cubemap = create_white_cubemap(256);
698        let white_cubemap_handle = images.add(white_cubemap);
699
700        let generated_light = GeneratedEnvironmentMapLight {
701            environment_map: white_cubemap_handle.clone(),
702            intensity: 500.0,
703            ..default()
704        };
705
706        // camera
707        commands.spawn((
708            Camera3d::default(),
709            Hdr,
710            Tonemapping::None,
711            Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::default(), Vec3::Y),
712            Projection::from(OrthographicProjection {
713                scale: 0.01,
714                scaling_mode: ScalingMode::WindowSize,
715                ..OrthographicProjection::default_3d()
716            }),
717            Skybox {
718                image: Some(white_cubemap_handle),
719                // middle gray
720                brightness: 500.0,
721                ..default()
722            },
723            generated_light,
724            DespawnOnExit(CURRENT_SCENE),
725        ));
726    }
727}
728
729mod render_layers {
730    const CURRENT_SCENE: super::Scene = super::Scene::RenderLayers;
731
732    use bevy::{
733        camera::{visibility::RenderLayers, Viewport},
734        prelude::*,
735        window::PrimaryWindow,
736    };
737
738    pub fn setup(
739        mut commands: Commands,
740        mut meshes: ResMut<Assets<Mesh>>,
741        mut materials: ResMut<Assets<StandardMaterial>>,
742        window: Single<&Window, With<PrimaryWindow>>,
743    ) {
744        // circular base
745        commands.spawn((
746            Mesh3d(meshes.add(Circle::new(4.0))),
747            MeshMaterial3d(materials.add(Color::WHITE)),
748            Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
749            RenderLayers::layer(0).with(1).with(2),
750            DespawnOnExit(CURRENT_SCENE),
751        ));
752
753        // cubes
754        commands.spawn((
755            Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
756            MeshMaterial3d(materials.add(Color::srgb(1.0, 0.0, 0.0))),
757            Transform::from_xyz(-1.5, 0.5, 0.0),
758            // No render layer for this one to test the default case
759            DespawnOnExit(CURRENT_SCENE),
760        ));
761        commands.spawn((
762            Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
763            MeshMaterial3d(materials.add(Color::srgb(0.0, 1.0, 0.0))),
764            Transform::from_xyz(0.0, 0.5, 0.0),
765            RenderLayers::layer(1),
766            DespawnOnExit(CURRENT_SCENE),
767        ));
768        commands.spawn((
769            Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
770            MeshMaterial3d(materials.add(Color::srgb(0.0, 0.0, 1.0))),
771            Transform::from_xyz(1.5, 0.5, 0.0),
772            RenderLayers::layer(2),
773            DespawnOnExit(CURRENT_SCENE),
774        ));
775
776        // Light
777        commands.spawn((
778            PointLight {
779                shadow_maps_enabled: true,
780                ..default()
781            },
782            Transform::from_xyz(4.0, 8.0, 4.0),
783            DespawnOnExit(CURRENT_SCENE),
784        ));
785
786        let window_half_size = window.physical_size() / 2;
787
788        // Split the screen in 4 different viewports with each of them having a specific render
789        // layer
790        for index in 0..4 {
791            let viewport_pos = UVec2::new((index % 2) as u32, (index / 2) as u32);
792            let mut entity_cmds = commands.spawn((
793                Camera3d::default(),
794                Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
795                Camera {
796                    // Renders cameras with different priorities to prevent ambiguities
797                    order: index as isize,
798                    viewport: Some(Viewport {
799                        physical_position: viewport_pos * window_half_size,
800                        physical_size: window_half_size,
801                        ..default()
802                    }),
803                    ..default()
804                },
805                DespawnOnExit(CURRENT_SCENE),
806            ));
807            match index {
808                0 => {}
809                1 => {
810                    entity_cmds.insert(RenderLayers::layer(1));
811                }
812                2 => {
813                    entity_cmds.insert(RenderLayers::layer(2));
814                }
815                3 => {
816                    entity_cmds.insert(RenderLayers::layer(0).with(1).with(2));
817                }
818                _ => warn!("Unexpected index {index}"),
819            }
820        }
821    }
822}