Skip to content

Commit

Permalink
add 3d testbed example
Browse files Browse the repository at this point in the history
  • Loading branch information
mockersf committed Oct 18, 2024
1 parent 624f573 commit 49975ed
Show file tree
Hide file tree
Showing 3 changed files with 318 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .github/example-run/testbed_3d.ron
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
(
events: [
(100, Screenshot),
(200, Custom("switch_scene")),
(300, Screenshot),
(400, Custom("switch_scene")),
(500, Screenshot),
(600, Custom("switch_scene")),
(700, Screenshot),
(800, AppExit),
]
)
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3844,3 +3844,11 @@ doc-scrape-examples = true

[package.metadata.example.testbed_2d]
hidden = true

[[example]]
name = "testbed_3d"
path = "examples/testbed/3d.rs"
doc-scrape-examples = true

[package.metadata.example.testbed_3d]
hidden = true
298 changes: 298 additions & 0 deletions examples/testbed/3d.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
//! 3d testbed
//!
//! You can switch scene by pressing the spacebar
#[cfg(feature = "bevy_ci_testing")]
use bevy::dev_tools::ci_testing::CiTestingCustomEvent;
use bevy::prelude::*;

fn main() {
let mut app = App::new();
app.add_plugins((DefaultPlugins,))
.init_state::<Scene>()
.enable_state_scoped_entities::<Scene>()
.add_systems(OnEnter(Scene::Light), light::setup)
.add_systems(OnEnter(Scene::Bloom), bloom::setup)
.add_systems(OnEnter(Scene::Gltf), gltf::setup)
.add_systems(OnEnter(Scene::Animation), animation::setup)
.add_systems(Update, switch_scene);
app.run();
}

#[derive(Debug, Clone, Eq, PartialEq, Hash, States, Default)]
enum Scene {
#[default]
Light,
Bloom,
Gltf,
Animation,
}

fn switch_scene(
keyboard: Res<ButtonInput<KeyCode>>,
#[cfg(feature = "bevy_ci_testing")] mut ci_events: EventReader<CiTestingCustomEvent>,
scene: Res<State<Scene>>,
mut next_scene: ResMut<NextState<Scene>>,
) {
let mut should_switch = false;
should_switch |= keyboard.just_pressed(KeyCode::Space);
#[cfg(feature = "bevy_ci_testing")]
{
should_switch |= ci_events.read().any(|event| match event {
CiTestingCustomEvent(event) => event == "switch_scene",
});
}
if should_switch {
info!("Switching scene");
next_scene.set(match scene.get() {
Scene::Light => Scene::Bloom,
Scene::Bloom => Scene::Gltf,
Scene::Gltf => Scene::Animation,
Scene::Animation => Scene::Light,
});
}
}

mod light {
use std::f32::consts::PI;

use bevy::{
color::palettes::css::{DEEP_PINK, LIME, RED},
prelude::*,
};

const CURRENT_SCENE: super::Scene = super::Scene::Light;

pub fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn((
Mesh3d(meshes.add(Plane3d::default().mesh().size(10.0, 10.0))),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::WHITE,
perceptual_roughness: 1.0,
..default()
})),
StateScoped(CURRENT_SCENE),
));

commands.spawn((
Mesh3d(meshes.add(Cuboid::default())),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: DEEP_PINK.into(),
..default()
})),
Transform::from_xyz(0.0, 1.0, 0.0),
StateScoped(CURRENT_SCENE),
));

commands.spawn((
PointLight {
intensity: 100_000.0,
color: RED.into(),
shadows_enabled: true,
..default()
},
Transform::from_xyz(1.0, 2.0, 0.0),
StateScoped(CURRENT_SCENE),
));

commands.spawn((
SpotLight {
intensity: 100_000.0,
color: LIME.into(),
shadows_enabled: true,
inner_angle: 0.6,
outer_angle: 0.8,
..default()
},
Transform::from_xyz(-1.0, 2.0, 0.0).looking_at(Vec3::new(-1.0, 0.0, 0.0), Vec3::Z),
StateScoped(CURRENT_SCENE),
));

commands.spawn((
DirectionalLight {
illuminance: light_consts::lux::OVERCAST_DAY,
shadows_enabled: true,
..default()
},
Transform {
translation: Vec3::new(0.0, 2.0, 0.0),
rotation: Quat::from_rotation_x(-PI / 4.),
..default()
},
StateScoped(CURRENT_SCENE),
));

commands.spawn((
Camera3d::default(),
Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
StateScoped(CURRENT_SCENE),
));
}
}

mod bloom {
use bevy::{
core_pipeline::{bloom::Bloom, tonemapping::Tonemapping},
prelude::*,
};

const CURRENT_SCENE: super::Scene = super::Scene::Bloom;

pub fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn((
Camera3d::default(),
Camera {
hdr: true,
..default()
},
Tonemapping::TonyMcMapface,
Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
Bloom::NATURAL,
StateScoped(CURRENT_SCENE),
));

let material_emissive1 = materials.add(StandardMaterial {
emissive: LinearRgba::rgb(13.99, 5.32, 2.0),
..default()
});
let material_emissive2 = materials.add(StandardMaterial {
emissive: LinearRgba::rgb(2.0, 13.99, 5.32),
..default()
});

let mesh = meshes.add(Sphere::new(0.5).mesh().ico(5).unwrap());

for z in -2..3_i32 {
let material = match (z % 2).abs() {
0 => material_emissive1.clone(),
1 => material_emissive2.clone(),
_ => unreachable!(),
};

commands.spawn((
Mesh3d(mesh.clone()),
MeshMaterial3d(material),
Transform::from_xyz(z as f32 * 2.0, 0.0, 0.0),
StateScoped(CURRENT_SCENE),
));
}
}
}

mod gltf {
use bevy::prelude::*;

const CURRENT_SCENE: super::Scene = super::Scene::Gltf;

pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
EnvironmentMapLight {
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 250.0,
..default()
},
StateScoped(CURRENT_SCENE),
));

commands.spawn((
DirectionalLight {
shadows_enabled: true,
..default()
},
StateScoped(CURRENT_SCENE),
));
commands.spawn((
SceneRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"),
)),
StateScoped(CURRENT_SCENE),
));
}
}

mod animation {
use std::{f32::consts::PI, time::Duration};

use bevy::{prelude::*, scene::SceneInstanceReady};

const CURRENT_SCENE: super::Scene = super::Scene::Animation;
const FOX_PATH: &str = "models/animated/Fox.glb";

#[derive(Resource)]
struct Animation {
animation: AnimationNodeIndex,
graph: Handle<AnimationGraph>,
}

pub fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut graphs: ResMut<Assets<AnimationGraph>>,
) {
let (graph, node) = AnimationGraph::from_clip(
asset_server.load(GltfAssetLabel::Animation(2).from_asset(FOX_PATH)),
);

let graph_handle = graphs.add(graph);
commands.insert_resource(Animation {
animation: node,
graph: graph_handle,
});

commands.spawn((
Camera3d::default(),
Transform::from_xyz(100.0, 100.0, 150.0).looking_at(Vec3::new(0.0, 20.0, 0.0), Vec3::Y),
StateScoped(CURRENT_SCENE),
));

commands.spawn((
Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)),
DirectionalLight {
shadows_enabled: true,
..default()
},
StateScoped(CURRENT_SCENE),
));

commands
.spawn((
SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(FOX_PATH))),
StateScoped(CURRENT_SCENE),
))
.observe(start_animation);
}

fn start_animation(
trigger: Trigger<SceneInstanceReady>,
children: Query<&Children>,
mut commands: Commands,
animation: Res<Animation>,
mut players: Query<(Entity, &mut AnimationPlayer)>,
) {
let entity = children.get(trigger.entity()).unwrap()[0];
let entity = children.get(entity).unwrap()[0];

let (entity, mut player) = players.get_mut(entity).unwrap();
let mut transitions = AnimationTransitions::new();
transitions
.play(&mut player, animation.animation, Duration::ZERO)
.seek_to(0.5)
.pause();

commands
.entity(entity)
.insert(AnimationGraphHandle(animation.graph.clone()))
.insert(transitions);
}
}

0 comments on commit 49975ed

Please sign in to comment.