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