testbed_3d/
3d.rs

1//! 3d testbed
2//!
3//! You can switch scene by pressing the spacebar
4
5mod helpers;
6
7use bevy::prelude::*;
8use helpers::Next;
9
10fn main() {
11    let mut app = App::new();
12    app.add_plugins((DefaultPlugins,))
13        .init_state::<Scene>()
14        .add_systems(OnEnter(Scene::Light), light::setup)
15        .add_systems(OnEnter(Scene::Bloom), bloom::setup)
16        .add_systems(OnEnter(Scene::Gltf), gltf::setup)
17        .add_systems(OnEnter(Scene::Animation), animation::setup)
18        .add_systems(OnEnter(Scene::Gizmos), gizmos::setup)
19        .add_systems(
20            OnEnter(Scene::GltfCoordinateConversion),
21            gltf_coordinate_conversion::setup,
22        )
23        .add_systems(Update, switch_scene)
24        .add_systems(Update, gizmos::draw_gizmos.run_if(in_state(Scene::Gizmos)))
25        .add_systems(
26            Update,
27            gltf_coordinate_conversion::draw_gizmos
28                .run_if(in_state(Scene::GltfCoordinateConversion)),
29        );
30
31    #[cfg(feature = "bevy_ci_testing")]
32    app.add_systems(Update, helpers::switch_scene_in_ci::<Scene>);
33
34    app.run();
35}
36
37#[derive(Debug, Clone, Eq, PartialEq, Hash, States, Default)]
38enum Scene {
39    #[default]
40    Light,
41    Bloom,
42    Gltf,
43    Animation,
44    Gizmos,
45    GltfCoordinateConversion,
46}
47
48impl Next for Scene {
49    fn next(&self) -> Self {
50        match self {
51            Scene::Light => Scene::Bloom,
52            Scene::Bloom => Scene::Gltf,
53            Scene::Gltf => Scene::Animation,
54            Scene::Animation => Scene::Gizmos,
55            Scene::Gizmos => Scene::GltfCoordinateConversion,
56            Scene::GltfCoordinateConversion => Scene::Light,
57        }
58    }
59}
60
61fn switch_scene(
62    keyboard: Res<ButtonInput<KeyCode>>,
63    scene: Res<State<Scene>>,
64    mut next_scene: ResMut<NextState<Scene>>,
65) {
66    if keyboard.just_pressed(KeyCode::Space) {
67        info!("Switching scene");
68        next_scene.set(scene.get().next());
69    }
70}
71
72mod light {
73    use std::f32::consts::PI;
74
75    use bevy::{
76        color::palettes::css::{DEEP_PINK, LIME, RED},
77        prelude::*,
78    };
79
80    const CURRENT_SCENE: super::Scene = super::Scene::Light;
81
82    pub fn setup(
83        mut commands: Commands,
84        mut meshes: ResMut<Assets<Mesh>>,
85        mut materials: ResMut<Assets<StandardMaterial>>,
86    ) {
87        commands.spawn((
88            Mesh3d(meshes.add(Plane3d::default().mesh().size(10.0, 10.0))),
89            MeshMaterial3d(materials.add(StandardMaterial {
90                base_color: Color::WHITE,
91                perceptual_roughness: 1.0,
92                ..default()
93            })),
94            DespawnOnExit(CURRENT_SCENE),
95        ));
96
97        commands.spawn((
98            Mesh3d(meshes.add(Cuboid::default())),
99            MeshMaterial3d(materials.add(StandardMaterial {
100                base_color: DEEP_PINK.into(),
101                ..default()
102            })),
103            Transform::from_xyz(0.0, 1.0, 0.0),
104            DespawnOnExit(CURRENT_SCENE),
105        ));
106
107        commands.spawn((
108            PointLight {
109                intensity: 100_000.0,
110                color: RED.into(),
111                shadows_enabled: true,
112                ..default()
113            },
114            Transform::from_xyz(1.0, 2.0, 0.0),
115            DespawnOnExit(CURRENT_SCENE),
116        ));
117
118        commands.spawn((
119            SpotLight {
120                intensity: 100_000.0,
121                color: LIME.into(),
122                shadows_enabled: true,
123                inner_angle: 0.6,
124                outer_angle: 0.8,
125                ..default()
126            },
127            Transform::from_xyz(-1.0, 2.0, 0.0).looking_at(Vec3::new(-1.0, 0.0, 0.0), Vec3::Z),
128            DespawnOnExit(CURRENT_SCENE),
129        ));
130
131        commands.spawn((
132            DirectionalLight {
133                illuminance: light_consts::lux::OVERCAST_DAY,
134                shadows_enabled: true,
135                ..default()
136            },
137            Transform {
138                translation: Vec3::new(0.0, 2.0, 0.0),
139                rotation: Quat::from_rotation_x(-PI / 4.),
140                ..default()
141            },
142            DespawnOnExit(CURRENT_SCENE),
143        ));
144
145        commands.spawn((
146            Camera3d::default(),
147            Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
148            DespawnOnExit(CURRENT_SCENE),
149        ));
150    }
151}
152
153mod bloom {
154    use bevy::{core_pipeline::tonemapping::Tonemapping, post_process::bloom::Bloom, prelude::*};
155
156    const CURRENT_SCENE: super::Scene = super::Scene::Bloom;
157
158    pub fn setup(
159        mut commands: Commands,
160        mut meshes: ResMut<Assets<Mesh>>,
161        mut materials: ResMut<Assets<StandardMaterial>>,
162    ) {
163        commands.spawn((
164            Camera3d::default(),
165            Tonemapping::TonyMcMapface,
166            Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
167            Bloom::NATURAL,
168            DespawnOnExit(CURRENT_SCENE),
169        ));
170
171        let material_emissive1 = materials.add(StandardMaterial {
172            emissive: LinearRgba::rgb(13.99, 5.32, 2.0),
173            ..default()
174        });
175        let material_emissive2 = materials.add(StandardMaterial {
176            emissive: LinearRgba::rgb(2.0, 13.99, 5.32),
177            ..default()
178        });
179
180        let mesh = meshes.add(Sphere::new(0.5).mesh().ico(5).unwrap());
181
182        for z in -2..3_i32 {
183            let material = match (z % 2).abs() {
184                0 => material_emissive1.clone(),
185                1 => material_emissive2.clone(),
186                _ => unreachable!(),
187            };
188
189            commands.spawn((
190                Mesh3d(mesh.clone()),
191                MeshMaterial3d(material),
192                Transform::from_xyz(z as f32 * 2.0, 0.0, 0.0),
193                DespawnOnExit(CURRENT_SCENE),
194            ));
195        }
196    }
197}
198
199mod gltf {
200    use bevy::prelude::*;
201
202    const CURRENT_SCENE: super::Scene = super::Scene::Gltf;
203
204    pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
205        commands.spawn((
206            Camera3d::default(),
207            Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
208            EnvironmentMapLight {
209                diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
210                specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
211                intensity: 250.0,
212                ..default()
213            },
214            DespawnOnExit(CURRENT_SCENE),
215        ));
216
217        commands.spawn((
218            DirectionalLight {
219                shadows_enabled: true,
220                ..default()
221            },
222            DespawnOnExit(CURRENT_SCENE),
223        ));
224        commands.spawn((
225            SceneRoot(asset_server.load(
226                GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"),
227            )),
228            DespawnOnExit(CURRENT_SCENE),
229        ));
230    }
231}
232
233mod animation {
234    use std::{f32::consts::PI, time::Duration};
235
236    use bevy::{prelude::*, scene::SceneInstanceReady};
237
238    const CURRENT_SCENE: super::Scene = super::Scene::Animation;
239    const FOX_PATH: &str = "models/animated/Fox.glb";
240
241    #[derive(Resource)]
242    struct Animation {
243        animation: AnimationNodeIndex,
244        graph: Handle<AnimationGraph>,
245    }
246
247    pub fn setup(
248        mut commands: Commands,
249        asset_server: Res<AssetServer>,
250        mut graphs: ResMut<Assets<AnimationGraph>>,
251    ) {
252        let (graph, node) = AnimationGraph::from_clip(
253            asset_server.load(GltfAssetLabel::Animation(2).from_asset(FOX_PATH)),
254        );
255
256        let graph_handle = graphs.add(graph);
257        commands.insert_resource(Animation {
258            animation: node,
259            graph: graph_handle,
260        });
261
262        commands.spawn((
263            Camera3d::default(),
264            Transform::from_xyz(100.0, 100.0, 150.0).looking_at(Vec3::new(0.0, 20.0, 0.0), Vec3::Y),
265            DespawnOnExit(CURRENT_SCENE),
266        ));
267
268        commands.spawn((
269            Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)),
270            DirectionalLight {
271                shadows_enabled: true,
272                ..default()
273            },
274            DespawnOnExit(CURRENT_SCENE),
275        ));
276
277        commands
278            .spawn((
279                SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(FOX_PATH))),
280                DespawnOnExit(CURRENT_SCENE),
281            ))
282            .observe(pause_animation_frame);
283    }
284
285    fn pause_animation_frame(
286        scene_ready: On<SceneInstanceReady>,
287        children: Query<&Children>,
288        mut commands: Commands,
289        animation: Res<Animation>,
290        mut players: Query<(Entity, &mut AnimationPlayer)>,
291    ) {
292        for child in children.iter_descendants(scene_ready.entity) {
293            if let Ok((entity, mut player)) = players.get_mut(child) {
294                let mut transitions = AnimationTransitions::new();
295                transitions
296                    .play(&mut player, animation.animation, Duration::ZERO)
297                    .seek_to(0.5)
298                    .pause();
299
300                commands
301                    .entity(entity)
302                    .insert(AnimationGraphHandle(animation.graph.clone()))
303                    .insert(transitions);
304            }
305        }
306    }
307}
308
309mod gizmos {
310    use bevy::{color::palettes::css::*, prelude::*};
311
312    pub fn setup(mut commands: Commands) {
313        commands.spawn((
314            Camera3d::default(),
315            Transform::from_xyz(-1.0, 2.5, 6.5).looking_at(Vec3::ZERO, Vec3::Y),
316            DespawnOnExit(super::Scene::Gizmos),
317        ));
318    }
319
320    pub fn draw_gizmos(mut gizmos: Gizmos) {
321        gizmos.cuboid(
322            Transform::from_translation(Vec3::X * -1.75).with_scale(Vec3::splat(1.25)),
323            RED,
324        );
325        gizmos
326            .sphere(Isometry3d::from_translation(Vec3::X * -3.5), 0.75, GREEN)
327            .resolution(30_000 / 3);
328
329        // 3d grids with all variations of outer edges on or off
330        for i in 0..8 {
331            let x = 1.5 * (i % 4) as f32;
332            let y = 1.0 * (0.5 - (i / 4) as f32);
333            let mut grid = gizmos.grid_3d(
334                Isometry3d::from_translation(Vec3::new(x, y, 0.0)),
335                UVec3::new(5, 4, 3),
336                Vec3::splat(0.175),
337                Color::WHITE,
338            );
339            if i & 1 > 0 {
340                grid = grid.outer_edges_x();
341            }
342            if i & 2 > 0 {
343                grid = grid.outer_edges_y();
344            }
345            if i & 4 > 0 {
346                grid.outer_edges_z();
347            }
348        }
349    }
350}
351
352mod gltf_coordinate_conversion {
353    use bevy::{
354        color::palettes::basic::*, gltf::GltfLoaderSettings, prelude::*, scene::SceneInstanceReady,
355    };
356
357    const CURRENT_SCENE: super::Scene = super::Scene::GltfCoordinateConversion;
358
359    pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
360        commands.spawn((
361            Camera3d::default(),
362            Transform::from_xyz(-4.0, 4.0, -5.0).looking_at(Vec3::ZERO, Vec3::Y),
363            DespawnOnExit(CURRENT_SCENE),
364        ));
365
366        commands.spawn((
367            DirectionalLight {
368                color: BLUE.into(),
369                ..default()
370            },
371            Transform::IDENTITY.looking_to(Dir3::Z, Dir3::Y),
372            DespawnOnExit(CURRENT_SCENE),
373        ));
374
375        commands.spawn((
376            DirectionalLight {
377                color: RED.into(),
378                ..default()
379            },
380            Transform::IDENTITY.looking_to(Dir3::X, Dir3::Y),
381            DespawnOnExit(CURRENT_SCENE),
382        ));
383
384        commands.spawn((
385            DirectionalLight {
386                color: GREEN.into(),
387                ..default()
388            },
389            Transform::IDENTITY.looking_to(Dir3::NEG_Y, Dir3::X),
390            DespawnOnExit(CURRENT_SCENE),
391        ));
392
393        commands
394            .spawn((
395                SceneRoot(asset_server.load_with_settings(
396                    GltfAssetLabel::Scene(0).from_asset("models/Faces/faces.glb"),
397                    |s: &mut GltfLoaderSettings| {
398                        s.use_model_forward_direction = Some(true);
399                    },
400                )),
401                DespawnOnExit(CURRENT_SCENE),
402            ))
403            .observe(show_aabbs);
404    }
405
406    pub fn show_aabbs(
407        scene_ready: On<SceneInstanceReady>,
408        mut commands: Commands,
409        children: Query<&Children>,
410        meshes: Query<(), With<Mesh3d>>,
411    ) {
412        for child in children
413            .iter_descendants(scene_ready.entity)
414            .filter(|&e| meshes.contains(e))
415        {
416            commands.entity(child).insert(ShowAabbGizmo {
417                color: Some(BLACK.into()),
418            });
419        }
420    }
421
422    pub fn draw_gizmos(mut gizmos: Gizmos) {
423        gizmos.axes(Transform::IDENTITY, 1.0);
424    }
425}