Add examples for Transforms (#2441)

# Add Transform Examples

- Adding examples for moving/rotating entities (with its own section) to resolve #2400 

I've stumbled upon this project and been fiddling around a little. Saw the issue and thought I might just add some examples for the proposed transformations.
Mind to check if I got the gist correctly and suggest anything I can improve?
This commit is contained in:
Mizu 2022-03-15 05:49:49 +00:00
parent 6e61fef67d
commit 9dfd4e4b08
7 changed files with 595 additions and 0 deletions

View file

@ -502,6 +502,27 @@ path = "examples/shader/compute_shader_game_of_life.rs"
name = "bevymark"
path = "examples/tools/bevymark.rs"
# Transforms
[[example]]
name = "global_vs_local_translation"
path = "examples/transforms/global_vs_local_translation.rs"
[[example]]
name = "3d_rotation"
path = "examples/transforms/3d_rotation.rs"
[[example]]
name = "scale"
path = "examples/transforms/scale.rs"
[[example]]
name = "transform"
path = "examples/transforms/transform.rs"
[[example]]
name = "translation"
path = "examples/transforms/translation.rs"
# UI (User Interface)
[[example]]
name = "button"

View file

@ -52,6 +52,7 @@ git checkout v0.4.0
- [Shaders](#shaders)
- [Tests](#tests)
- [Tools](#tools)
- [Transforms](#transforms)
- [UI (User Interface)](#ui-user-interface)
- [Window](#window)
- [Platform-Specific Examples](#platform-specific-examples)
@ -247,6 +248,16 @@ Example | File | Description
--- | --- | ---
`bevymark` | [`tools/bevymark.rs`](./tools/bevymark.rs) | A heavy sprite rendering workload to benchmark your system with Bevy
## Transforms
Example | File | Description
--- | --- | ---
`global_vs_local_translation` | [`transforms/global_vs_local_translation.rs`](./transforms/global_vs_local_translation.rs) | Illustrates the difference between direction of a translation in respect to local object or global object Transform
`3d_rotation` | [`transforms/3d_rotation.rs`](./transforms/3d_rotation.rs) | Illustrates how to (constantly) rotate an object around an axis
`scale` | [`transforms/scale.rs`](./transforms/scale.rs) | Illustrates how to scale an object in each direction
`transform` | [`transforms/transfrom.rs`](./transforms/transform.rs) | Shows multiple transformations of objects
`translation` | [`transforms/translation.rs`](./transforms/translation.rs) | Illustrates how to move an object along an axis
## UI (User Interface)
Example | File | Description

View file

@ -0,0 +1,57 @@
use bevy::prelude::*;
use std::f32::consts::PI;
const FULL_TURN: f32 = 2.0 * PI;
// Define a component to designate a rotation speed to an entity.
#[derive(Component)]
struct Rotatable {
speed: f32,
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.add_system(rotate_cube)
.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// Spawn a cube to rotate.
commands
.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(Color::WHITE.into()),
transform: Transform::from_translation(Vec3::ZERO),
..Default::default()
})
.insert(Rotatable { speed: 0.3 });
// Spawn a camera looking at the entities to show what's happening in this example.
commands.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_xyz(0.0, 10.0, 20.0).looking_at(Vec3::ZERO, Vec3::Y),
..Default::default()
});
// Add a light source for better 3d visibility.
commands.spawn_bundle(PointLightBundle {
transform: Transform::from_translation(Vec3::ONE * 3.0),
..Default::default()
});
}
// This system will rotate any entity in the scene with an assigned Rotatable around its z-axis.
fn rotate_cube(mut cubes: Query<(&mut Transform, &Rotatable)>, timer: Res<Time>) {
for (mut transform, cube) in cubes.iter_mut() {
// The speed is taken as a percentage of a full 360 degree turn.
// The timers delta_seconds is used to smooth out the movement.
let rotation_change = Quat::from_rotation_y(FULL_TURN * cube.speed * timer.delta_seconds());
transform.rotate(rotation_change);
}
}

View file

@ -0,0 +1,188 @@
use bevy::prelude::*;
// Define a marker for entities that should be changed via their global transform.
#[derive(Component)]
struct ChangeGlobal;
// Define a marker for entities that should be changed via their local transform.
#[derive(Component)]
struct ChangeLocal;
// Define a marker for entities that should move.
#[derive(Component)]
struct Move;
// Define a resource for the current movement direction;
#[derive(Default)]
struct Direction(Vec3);
// Define component to decide when an entity should be ignored by the movement systems.
#[derive(Component)]
struct ToggledBy(KeyCode);
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.init_resource::<Direction>()
.add_system(move_cubes_according_to_global_transform)
.add_system(move_cubes_according_to_local_transform)
.add_system(update_directional_input)
.add_system(toggle_movement)
.run();
}
// Startup system to setup the scene and spawn all relevant entities.
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
asset_server: Res<AssetServer>,
) {
// To show the difference between a local transform (rotation, scale and position in respect to a given entity)
// and global transform (rotation, scale and position in respect to the base coordinate system of the visible scene)
// it's helpful to add multiple entities that are attached to each other.
// This way we'll see that the transform in respect to an entity's parent is different to the
// global transform within the visible scene.
// This example focuses on translation only to clearly demonstrate the differences.
// Spawn a basic cube to have an entity as reference.
let mut main_entity = commands.spawn();
main_entity
.insert_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(Color::YELLOW.into()),
..Default::default()
})
.insert(ChangeGlobal)
.insert(Move)
.insert(ToggledBy(KeyCode::Key1));
// Spawn two entities as children above the original main entity.
// The red entity spawned here will be changed via its global transform
// where the green one will be changed via its local transform.
main_entity.with_children(|child_builder| {
// also see parenting example
child_builder
.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.5 })),
material: materials.add(Color::RED.into()),
transform: Transform::from_translation(Vec3::Y - Vec3::Z),
..Default::default()
})
.insert(ChangeGlobal)
.insert(Move)
.insert(ToggledBy(KeyCode::Key2));
child_builder
.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.5 })),
material: materials.add(Color::GREEN.into()),
transform: Transform::from_translation(Vec3::Y + Vec3::Z),
..Default::default()
})
.insert(ChangeLocal)
.insert(Move)
.insert(ToggledBy(KeyCode::Key3));
});
// Spawn a camera looking at the entities to show what's happening in this example.
commands.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_xyz(0.0, 10.0, 20.0).looking_at(Vec3::ZERO, Vec3::Y),
..Default::default()
});
// Add a light source for better 3d visibility.
commands.spawn_bundle(PointLightBundle {
transform: Transform::from_translation(Vec3::splat(3.0)),
..Default::default()
});
// Add a UI cam and text to explain inputs and what is happening.
commands.spawn_bundle(UiCameraBundle::default());
commands.spawn_bundle(TextBundle {
text: Text::with_section(
"Press the arrow keys to move the cubes. Toggle movement for yellow (1), red (2) and green (3) cubes via number keys.
Notice how the green cube will translate further in respect to the yellow in contrast to the red cube.
This is due to the use of its LocalTransform that is relative to the yellow cubes transform instead of the GlobalTransform as in the case of the red cube.
The red cube is moved through its GlobalTransform and thus is unaffected by the yellows transform.",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 22.0,
color: Color::WHITE,
},
TextAlignment {
horizontal: HorizontalAlign::Left,
..Default::default()
}
),
..Default::default()});
}
// This system will move all cubes that are marked as ChangeGlobal according to their global transform.
fn move_cubes_according_to_global_transform(
mut cubes: Query<&mut GlobalTransform, (With<ChangeGlobal>, With<Move>)>,
direction: Res<Direction>,
timer: Res<Time>,
) {
for mut global_transform in cubes.iter_mut() {
global_transform.translation += direction.0 * timer.delta_seconds();
}
}
// This system will move all cubes that are marked as ChangeLocal according to their local transform.
fn move_cubes_according_to_local_transform(
mut cubes: Query<&mut Transform, (With<ChangeLocal>, With<Move>)>,
direction: Res<Direction>,
timer: Res<Time>,
) {
for mut transform in cubes.iter_mut() {
transform.translation += direction.0 * timer.delta_seconds();
}
}
// This system updates a resource that defines in which direction the cubes should move.
// The direction is defined by the input of arrow keys and is only in left/right and up/down direction.
fn update_directional_input(mut direction: ResMut<Direction>, keyboard_input: Res<Input<KeyCode>>) {
let horizontal_movement = Vec3::X
* (keyboard_input.pressed(KeyCode::Right) as i32
- keyboard_input.pressed(KeyCode::Left) as i32) as f32;
let vertical_movement = Vec3::Y
* (keyboard_input.pressed(KeyCode::Up) as i32
- keyboard_input.pressed(KeyCode::Down) as i32) as f32;
direction.0 = horizontal_movement + vertical_movement;
}
// This system enables and disables the movement for each entity if their assigned key is pressed.
fn toggle_movement(
mut commands: Commands,
movable_entities: Query<(Entity, &Handle<StandardMaterial>, &ToggledBy), With<Move>>,
static_entities: Query<(Entity, &Handle<StandardMaterial>, &ToggledBy), Without<Move>>,
mut materials: ResMut<Assets<StandardMaterial>>,
keyboard_input: Res<Input<KeyCode>>,
) {
// Update the currently movable entities and remove their Move component if the assigned key was pressed to disable their movement.
// This will also make them transparent so they can be identified as 'disabled' in the scene.
for (entity, material_handle, toggled_by) in movable_entities.iter() {
if keyboard_input.just_pressed(toggled_by.0) {
materials
.get_mut(material_handle)
.unwrap()
.base_color
.set_a(0.5);
commands.entity(entity).remove::<Move>();
}
}
// Update the currently non-movable entities and add a Move component if the assigned key was pressed to enable their movement.
// This will also make them opaque so they can be identified as 'enabled' in the scene.
for (entity, material_handle, toggled_by) in static_entities.iter() {
if keyboard_input.just_pressed(toggled_by.0) {
materials
.get_mut(material_handle)
.unwrap()
.base_color
.set_a(1.0);
commands.entity(entity).insert(Move);
}
}
}

View file

@ -0,0 +1,96 @@
use bevy::math::Vec3Swizzles;
use bevy::prelude::*;
use std::f32::consts::PI;
// Define a component to keep information for the scaled object.
#[derive(Component)]
struct Scaling {
scale_direction: Vec3,
scale_speed: f32,
max_element_size: f32,
min_element_size: f32,
}
// Implement a simple initialisation.
impl Scaling {
fn new() -> Self {
Scaling {
scale_direction: Vec3::X,
scale_speed: 2.0,
max_element_size: 5.0,
min_element_size: 1.0,
}
}
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.add_system(change_scale_direction)
.add_system(scale_cube)
.run();
}
// Startup system to setup the scene and spawn all relevant entities.
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// Spawn a cube to scale.
commands
.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(Color::WHITE.into()),
transform: Transform::from_rotation(Quat::from_rotation_y(PI / 4.0)),
..Default::default()
})
.insert(Scaling::new());
// Spawn a camera looking at the entities to show what's happening in this example.
commands.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_xyz(0.0, 10.0, 20.0).looking_at(Vec3::ZERO, Vec3::Y),
..Default::default()
});
// Add a light source for better 3d visibility.
commands.spawn_bundle(PointLightBundle {
transform: Transform::from_translation(Vec3::ONE * 3.0),
..Default::default()
});
}
// This system will check if a scaled entity went above or below the entities scaling bounds
// and change the direction of the scaling vector.
fn change_scale_direction(mut cubes: Query<(&mut Transform, &mut Scaling)>) {
for (mut transform, mut cube) in cubes.iter_mut() {
// If an entity scaled beyond the maximum of its size in any dimension
// the scaling vector is flipped so the scaling is gradually reverted.
// Additionally, to ensure the condition does not trigger again we floor the elements to
// their next full value, which should be max_element_size at max.
if transform.scale.max_element() > cube.max_element_size {
cube.scale_direction *= -1.0;
transform.scale = transform.scale.floor();
}
// If an entity scaled beyond the minimum of its size in any dimension
// the scaling vector is also flipped.
// Additionally the Values are ceiled to be min_element_size at least
// and the scale direction is flipped.
// This way the entity will change the dimension in which it is scaled any time it
// reaches its min_element_size.
if transform.scale.min_element() < cube.min_element_size {
cube.scale_direction *= -1.0;
transform.scale = transform.scale.ceil();
cube.scale_direction = cube.scale_direction.zxy();
}
}
}
// This system will scale any entity with assigned Scaling in each direction
// by cycling through the directions to scale.
fn scale_cube(mut cubes: Query<(&mut Transform, &Scaling)>, timer: Res<Time>) {
for (mut transform, cube) in cubes.iter_mut() {
transform.scale += cube.scale_direction * cube.scale_speed * timer.delta_seconds();
}
}

View file

@ -0,0 +1,151 @@
use bevy::prelude::*;
use std::f32::consts::PI;
// A struct for additional data of for a moving cube.
#[derive(Component)]
struct CubeState {
start_pos: Vec3,
move_speed: f32,
turn_speed: f32,
}
// A struct adding information to a scalable entity,
// that will be stationary at the center of the scene.
#[derive(Component)]
struct Center {
max_size: f32,
min_size: f32,
scale_factor: f32,
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.add_system(move_cube)
.add_system(rotate_cube)
.add_system(scale_down_sphere_proportional_to_cube_travel_distance)
.run();
}
// Startup system to setup the scene and spawn all relevant entities.
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// Add an object (sphere) for visualizing scaling.
commands
.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Icosphere {
radius: 3.0,
subdivisions: 32,
})),
material: materials.add(Color::YELLOW.into()),
transform: Transform::from_translation(Vec3::ZERO),
..Default::default()
})
.insert(Center {
max_size: 1.0,
min_size: 0.1,
scale_factor: 0.05,
});
// Add the cube to visualize rotation and translation.
// This cube will circle around the center_sphere
// by changing its rotation each frame and moving forward.
// Define a start transform for an orbiting cube, that's away from our central object (sphere)
// and rotate it so it will be able to move around the sphere and not towards it.
let angle_90 = PI / 2.0;
let mut cube_spawn = Transform::from_translation(Vec3::Z * -10.0);
cube_spawn.rotation = Quat::from_rotation_y(angle_90);
commands
.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(Color::WHITE.into()),
transform: cube_spawn,
..Default::default()
})
.insert(CubeState {
start_pos: cube_spawn.translation,
move_speed: 2.0,
turn_speed: 0.2,
});
// Spawn a camera looking at the entities to show what's happening in this example.
commands.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_xyz(0.0, 10.0, 20.0).looking_at(Vec3::ZERO, Vec3::Y),
..Default::default()
});
// Add a light source for better 3d visibility.
commands.spawn_bundle(PointLightBundle {
transform: Transform::from_translation(Vec3::ONE * 3.0),
..Default::default()
});
}
// This system will move the cube forward.
fn move_cube(mut cubes: Query<(&mut Transform, &mut CubeState)>, timer: Res<Time>) {
for (mut transform, cube) in cubes.iter_mut() {
// Move the cube forward smoothly at a given move_speed.
let forward = transform.forward();
transform.translation += forward * cube.move_speed * timer.delta_seconds();
}
}
// This system will rotate the cube slightly towards the center_sphere.
// Due to the forward movement the resulting movement
// will be a circular motion around the center_sphere.
fn rotate_cube(
mut cubes: Query<(&mut Transform, &mut CubeState), Without<Center>>,
center_spheres: Query<&Transform, With<Center>>,
timer: Res<Time>,
) {
// Calculate the point to circle around. (The position of the center_sphere)
let mut center: Vec3 = Vec3::ZERO;
for sphere in center_spheres.iter() {
center += sphere.translation;
}
// Update the rotation of the cube(s).
for (mut transform, cube) in cubes.iter_mut() {
// Calculate the rotation of the cube if it would be looking at the sphere in the center.
let look_at_sphere = transform.looking_at(center, transform.local_y());
// Interpolate between the current rotation and the fully turned rotation
// when looking a the sphere, with a given turn speed to get a smooth motion.
// With higher speed the curvature of the orbit would be smaller.
let incremental_turn_weight = cube.turn_speed * timer.delta_seconds();
let old_rotation = transform.rotation;
transform.rotation = old_rotation.lerp(look_at_sphere.rotation, incremental_turn_weight);
}
}
// This system will scale down the sphere in the center of the scene
// according to the traveling distance of the orbiting cube(s) from their start position(s).
fn scale_down_sphere_proportional_to_cube_travel_distance(
cubes: Query<(&Transform, &CubeState), Without<Center>>,
mut centers: Query<(&mut Transform, &Center)>,
) {
// First we need to calculate the length of between
// the current position of the orbiting cube and the spawn position.
let mut distances = 0.0;
for (cube_transform, cube_state) in cubes.iter() {
distances += (cube_state.start_pos - cube_transform.translation).length();
}
// Now we use the calculated value to scale the sphere in the center accordingly.
for (mut transform, center) in centers.iter_mut() {
// Calculate the new size from the calculated distances and the centers scale_factor.
// Since we want to have the sphere at its max_size at the cubes spawn location we start by
// using the max_size as start value and subtract the distances scaled by a scaling factor.
let mut new_size: f32 = center.max_size - center.scale_factor * distances;
// The new size should also not be smaller than the centers min_size.
// Therefore the max value out of (new_size, center.min_size) is used.
new_size = new_size.max(center.min_size);
// Now scale the sphere uniformly in all directions using new_size.
// Here Vec3:splat is used to create a vector with new_size in x, y and z direction.
transform.scale = Vec3::splat(new_size);
}
}

View file

@ -0,0 +1,71 @@
use bevy::prelude::*;
// Define a struct to keep some information about our entity.
// Here it's an arbitrary movement speed, the spawn location, and a maximum distance from it.
#[derive(Component)]
struct Movable {
spawn: Vec3,
max_distance: f32,
speed: f32,
}
// Implement a utility function for easier Movable struct creation.
impl Movable {
fn new(spawn: Vec3) -> Self {
Movable {
spawn,
max_distance: 5.0,
speed: 2.0,
}
}
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.add_system(move_cube)
.run();
}
// Startup system to setup the scene and spawn all relevant entities.
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// Add a cube to visualize translation.
let entity_spawn = Vec3::ZERO;
commands
.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(Color::WHITE.into()),
transform: Transform::from_translation(entity_spawn),
..Default::default()
})
.insert(Movable::new(entity_spawn));
// Spawn a camera looking at the entities to show what's happening in this example.
commands.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_xyz(0.0, 10.0, 20.0).looking_at(entity_spawn, Vec3::Y),
..Default::default()
});
// Add a light source for better 3d visibility.
commands.spawn_bundle(PointLightBundle {
transform: Transform::from_translation(Vec3::ONE * 3.0),
..Default::default()
});
}
// This system will move all Movable entities with a Transform
fn move_cube(mut cubes: Query<(&mut Transform, &mut Movable)>, timer: Res<Time>) {
for (mut transform, mut cube) in cubes.iter_mut() {
// Check if the entity moved too far from its spawn, if so invert the moving direction.
if (cube.spawn - transform.translation).length() > cube.max_distance {
cube.speed *= -1.0;
}
let direction = transform.local_x();
transform.translation += direction * cube.speed * timer.delta_seconds();
}
}