Skip to content

Commit

Permalink
Add helper methods for rotating Transforms (bevyengine#5151)
Browse files Browse the repository at this point in the history
# Objective
Users often ask for help with rotations as they struggle with `Quat`s.
`Quat` is rather complex and has a ton of verbose methods.

## Solution
Add rotation helper methods to `Transform`.


Co-authored-by: devil-ira <justthecooldude@gmail.com>
  • Loading branch information
2 people authored and ItsDoot committed Feb 1, 2023
1 parent a34003e commit 6723bad
Show file tree
Hide file tree
Showing 20 changed files with 111 additions and 67 deletions.
4 changes: 2 additions & 2 deletions crates/bevy_animation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,8 @@ pub fn animation_player(
rot_end = -rot_end;
}
// Rotations are using a spherical linear interpolation
transform.rotation = Quat::from_array(rot_start.normalize().into())
.slerp(Quat::from_array(rot_end.normalize().into()), lerp);
transform.rotation =
rot_start.normalize().slerp(rot_end.normalize(), lerp);
}
Keyframes::Translation(keyframes) => {
let translation_start = keyframes[step_start];
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_gltf/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ async fn load_gltf<'a, 'b>(
scale,
} => Transform {
translation: bevy_math::Vec3::from(translation),
rotation: bevy_math::Quat::from_vec4(rotation.into()),
rotation: bevy_math::Quat::from_array(rotation),
scale: bevy_math::Vec3::from(scale),
},
},
Expand Down
77 changes: 64 additions & 13 deletions crates/bevy_transform/src/components/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,20 +195,81 @@ impl Transform {
self.local_z()
}

/// Rotates the transform by the given rotation.
/// Rotates this [`Transform`] by the given rotation.
#[inline]
pub fn rotate(&mut self, rotation: Quat) {
self.rotation = rotation * self.rotation;
}

/// Rotates this [`Transform`] around a point in space.
/// If the point is a zero vector, this will rotate around the parent (if any) or the origin.
/// Rotates this [`Transform`] around the given `axis` by `angle` (in radians).
///
/// If this [`Transform`] has a parent, the `axis` is relative to the rotation of the parent.
#[inline]
pub fn rotate_axis(&mut self, axis: Vec3, angle: f32) {
self.rotate(Quat::from_axis_angle(axis, angle));
}

/// Rotates this [`Transform`] around the X axis by `angle` (in radians).
///
/// If this [`Transform`] has a parent, the axis is relative to the rotation of the parent.
#[inline]
pub fn rotate_x(&mut self, angle: f32) {
self.rotate(Quat::from_rotation_x(angle));
}

/// Rotates this [`Transform`] around the Y axis by `angle` (in radians).
///
/// If this [`Transform`] has a parent, the axis is relative to the rotation of the parent.
#[inline]
pub fn rotate_y(&mut self, angle: f32) {
self.rotate(Quat::from_rotation_y(angle));
}

/// Rotates this [`Transform`] around the Z axis by `angle` (in radians).
///
/// If this [`Transform`] has a parent, the axis is relative to the rotation of the parent.
#[inline]
pub fn rotate_z(&mut self, angle: f32) {
self.rotate(Quat::from_rotation_z(angle));
}

/// Rotates this [`Transform`] around its X axis by `angle` (in radians).
#[inline]
pub fn rotate_local_x(&mut self, angle: f32) {
self.rotate_axis(self.local_x(), angle);
}

/// Rotates this [`Transform`] around its Y axis by `angle` (in radians).
#[inline]
pub fn rotate_local_y(&mut self, angle: f32) {
self.rotate_axis(self.local_y(), angle);
}

/// Rotates this [`Transform`] around its Z axis by `angle` (in radians).
#[inline]
pub fn rotate_local_z(&mut self, angle: f32) {
self.rotate_axis(self.local_z(), angle);
}

/// Rotates this [`Transform`] around a `point` in space.
///
/// If this [`Transform`] has a parent, the `point` is relative to the [`Transform`] of the parent.
#[inline]
pub fn rotate_around(&mut self, point: Vec3, rotation: Quat) {
self.translation = point + rotation * (self.translation - point);
self.rotation *= rotation;
}

/// Rotates this [`Transform`] so that its local negative z direction is toward
/// `target` and its local y direction is toward `up`.
#[inline]
pub fn look_at(&mut self, target: Vec3, up: Vec3) {
let forward = Vec3::normalize(self.translation - target);
let right = up.cross(forward).normalize();
let up = forward.cross(right);
self.rotation = Quat::from_mat3(&Mat3::from_cols(right, up, forward));
}

/// Multiplies `self` with `transform` component by component, returning the
/// resulting [`Transform`]
#[inline]
Expand Down Expand Up @@ -239,16 +300,6 @@ impl Transform {
pub fn apply_non_uniform_scale(&mut self, scale_factor: Vec3) {
self.scale *= scale_factor;
}

/// Rotates this [`Transform`] so that its local z direction is toward
/// `target` and its local y direction is toward `up`.
#[inline]
pub fn look_at(&mut self, target: Vec3, up: Vec3) {
let forward = Vec3::normalize(self.translation - target);
let right = up.cross(forward).normalize();
let up = forward.cross(right);
self.rotation = Quat::from_mat3(&Mat3::from_cols(right, up, forward));
}
}

impl Default for Transform {
Expand Down
14 changes: 4 additions & 10 deletions examples/2d/rotation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,8 @@ fn player_movement_system(
movement_factor += 1.0;
}

// create the change in rotation around the Z axis (perpendicular to the 2D plane of the screen)
let rotation_delta = Quat::from_rotation_z(rotation_factor * ship.rotation_speed * TIME_STEP);
// update the ship rotation with our rotation delta
transform.rotation *= rotation_delta;
// update the ship rotation around the Z axis (perpendicular to the 2D plane of the screen)
transform.rotate_z(rotation_factor * ship.rotation_speed * TIME_STEP);

// get the ship's forward vector by applying the current rotation to the ships initial facing vector
let movement_direction = transform.rotation * Vec3::Y;
Expand Down Expand Up @@ -168,7 +166,7 @@ fn snap_to_player_system(

// get the quaternion to rotate from the initial enemy facing direction to the direction
// facing the player
let rotate_to_player = Quat::from_rotation_arc(Vec3::Y, Vec3::from((to_player, 0.0)));
let rotate_to_player = Quat::from_rotation_arc(Vec3::Y, to_player.extend(0.));

// rotate the enemy to face the player
enemy_transform.rotation = rotate_to_player;
Expand Down Expand Up @@ -243,11 +241,7 @@ fn rotate_to_player_system(
// calculate angle of rotation with limit
let rotation_angle = rotation_sign * (config.rotation_speed * TIME_STEP).min(max_angle);

// get the quaternion to rotate from the current enemy facing direction towards the
// direction facing the player
let rotation_delta = Quat::from_rotation_z(rotation_angle);

// rotate the enemy to face the player
enemy_transform.rotation *= rotation_delta;
enemy_transform.rotate_z(rotation_angle);
}
}
6 changes: 3 additions & 3 deletions examples/3d/lighting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ fn setup(

// left wall
let mut transform = Transform::from_xyz(2.5, 2.5, 0.0);
transform.rotate(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2));
transform.rotate_z(std::f32::consts::FRAC_PI_2);
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Box::new(5.0, 0.15, 5.0))),
transform,
Expand All @@ -47,7 +47,7 @@ fn setup(
});
// back (right) wall
let mut transform = Transform::from_xyz(0.0, 2.5, -2.5);
transform.rotate(Quat::from_rotation_x(std::f32::consts::FRAC_PI_2));
transform.rotate_x(std::f32::consts::FRAC_PI_2);
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Box::new(5.0, 0.15, 5.0))),
transform,
Expand Down Expand Up @@ -214,7 +214,7 @@ fn animate_light_direction(
mut query: Query<&mut Transform, With<DirectionalLight>>,
) {
for mut transform in query.iter_mut() {
transform.rotate(Quat::from_rotation_y(time.delta_seconds() * 0.5));
transform.rotate_y(time.delta_seconds() * 0.5);
}
}

Expand Down
2 changes: 1 addition & 1 deletion examples/3d/parenting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct Rotator;
/// rotates the parent, which will result in the child also rotating
fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<Rotator>>) {
for mut transform in query.iter_mut() {
transform.rotation *= Quat::from_rotation_x(3.0 * time.delta_seconds());
transform.rotate_x(3.0 * time.delta_seconds());
}
}

Expand Down
8 changes: 4 additions & 4 deletions examples/3d/render_to_texture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,15 @@ fn setup(
/// Rotates the inner cube (first pass)
fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<FirstPassCube>>) {
for mut transform in query.iter_mut() {
transform.rotation *= Quat::from_rotation_x(1.5 * time.delta_seconds());
transform.rotation *= Quat::from_rotation_z(1.3 * time.delta_seconds());
transform.rotate_x(1.5 * time.delta_seconds());
transform.rotate_z(1.3 * time.delta_seconds());
}
}

/// Rotates the outer cube (main pass)
fn cube_rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<MainPassCube>>) {
for mut transform in query.iter_mut() {
transform.rotation *= Quat::from_rotation_x(1.0 * time.delta_seconds());
transform.rotation *= Quat::from_rotation_y(0.7 * time.delta_seconds());
transform.rotate_x(1.0 * time.delta_seconds());
transform.rotate_y(0.7 * time.delta_seconds());
}
}
4 changes: 2 additions & 2 deletions examples/3d/shapes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ fn setup(
2.0,
0.0,
),
rotation: Quat::from_rotation_x(-std::f32::consts::PI / 4.),
..default()
},
..default()
Expand Down Expand Up @@ -86,8 +87,7 @@ fn setup(

fn rotate(mut query: Query<&mut Transform, With<Shape>>, time: Res<Time>) {
for mut transform in query.iter_mut() {
transform.rotation = Quat::from_rotation_y(time.seconds_since_startup() as f32 / 2.)
* Quat::from_rotation_x(-std::f32::consts::PI / 4.);
transform.rotate_y(time.delta_seconds() / 2.);
}
}

Expand Down
4 changes: 2 additions & 2 deletions examples/ecs/hierarchy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,14 @@ fn rotate(
let angle = std::f32::consts::PI / 2.0;
for (parent, children) in parents_query.iter_mut() {
if let Ok(mut transform) = transform_query.get_mut(parent) {
transform.rotate(Quat::from_rotation_z(-angle * time.delta_seconds()));
transform.rotate_z(-angle * time.delta_seconds());
}

// To iterate through the entities children, just treat the Children component as a Vec
// Alternatively, you could query entities that have a Parent component
for child in children.iter() {
if let Ok(mut transform) = transform_query.get_mut(*child) {
transform.rotate(Quat::from_rotation_z(angle * 2.0 * time.delta_seconds()));
transform.rotate_z(angle * 2.0 * time.delta_seconds());
}
}

Expand Down
2 changes: 1 addition & 1 deletion examples/games/alien_cake_addict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ fn spawn_bonus(
fn rotate_bonus(game: Res<Game>, time: Res<Time>, mut transforms: Query<&mut Transform>) {
if let Some(entity) = game.bonus.entity {
if let Ok(mut cake_transform) = transforms.get_mut(entity) {
cake_transform.rotate(Quat::from_rotation_y(time.delta_seconds()));
cake_transform.rotate_y(time.delta_seconds());
cake_transform.scale = Vec3::splat(
1.0 + (game.score as f32 / 10.0 * time.seconds_since_startup().sin() as f32).abs(),
);
Expand Down
2 changes: 1 addition & 1 deletion examples/games/contributors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ fn move_system(time: Res<Time>, mut query: Query<(&Velocity, &mut Transform)>) {

for (velocity, mut transform) in query.iter_mut() {
transform.translation += delta * velocity.translation;
transform.rotate(Quat::from_rotation_z(velocity.rotation * delta));
transform.rotate_z(velocity.rotation * delta);
}
}

Expand Down
4 changes: 2 additions & 2 deletions examples/shader/post_processing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,8 @@ fn main_camera_cube_rotator_system(
mut query: Query<&mut Transform, With<MainCube>>,
) {
for mut transform in query.iter_mut() {
transform.rotation *= Quat::from_rotation_x(0.55 * time.delta_seconds());
transform.rotation *= Quat::from_rotation_z(0.15 * time.delta_seconds());
transform.rotate_x(0.55 * time.delta_seconds());
transform.rotate_z(0.15 * time.delta_seconds());
}
}

Expand Down
5 changes: 3 additions & 2 deletions examples/stress_tests/many_cubes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,9 @@ fn spherical_polar_to_cartesian(p: DVec2) -> DVec3 {
// System for rotating the camera
fn move_camera(time: Res<Time>, mut camera_query: Query<&mut Transform, With<Camera>>) {
let mut camera_transform = camera_query.single_mut();
camera_transform.rotate(Quat::from_rotation_z(time.delta_seconds() * 0.15));
camera_transform.rotate(Quat::from_rotation_x(time.delta_seconds() * 0.15));
let delta = time.delta_seconds() * 0.15;
camera_transform.rotate_z(delta);
camera_transform.rotate_x(delta);
}

// System for printing the number of meshes on every tick of the timer
Expand Down
4 changes: 1 addition & 3 deletions examples/stress_tests/many_foxes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,7 @@ fn update_fox_rings(
let dt = time.delta_seconds();
for (ring, rotation_direction, mut transform) in rings.iter_mut() {
let angular_velocity = foxes.speed / ring.radius;
transform.rotate(Quat::from_rotation_y(
rotation_direction.sign() * angular_velocity * dt,
));
transform.rotate_y(rotation_direction.sign() * angular_velocity * dt);
}
}

Expand Down
5 changes: 3 additions & 2 deletions examples/stress_tests/many_lights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,9 @@ fn spherical_polar_to_cartesian(p: DVec2) -> DVec3 {
// System for rotating the camera
fn move_camera(time: Res<Time>, mut camera_query: Query<&mut Transform, With<Camera>>) {
let mut camera_transform = camera_query.single_mut();
camera_transform.rotate(Quat::from_rotation_z(time.delta_seconds() * 0.15));
camera_transform.rotate(Quat::from_rotation_x(time.delta_seconds() * 0.15));
let delta = time.delta_seconds() * 0.15;
camera_transform.rotate_z(delta);
camera_transform.rotate_x(delta);
}

// System for printing the number of meshes on every tick of the timer
Expand Down
2 changes: 1 addition & 1 deletion examples/stress_tests/many_sprites.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ fn setup(mut commands: Commands, assets: Res<AssetServer>) {
// System for rotating and translating the camera
fn move_camera(time: Res<Time>, mut camera_query: Query<&mut Transform, With<Camera>>) {
let mut camera_transform = camera_query.single_mut();
camera_transform.rotate(Quat::from_rotation_z(time.delta_seconds() * 0.5));
camera_transform.rotate_z(time.delta_seconds() * 0.5);
*camera_transform = *camera_transform
* Transform::from_translation(Vec3::X * CAMERA_SPEED * time.delta_seconds());
}
Expand Down
6 changes: 4 additions & 2 deletions examples/tools/scene_viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use bevy::{
scene::InstanceId,
};

use std::f32::consts::TAU;

#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
struct CameraControllerCheckSystem;

Expand Down Expand Up @@ -327,8 +329,8 @@ fn update_lights(
transform.rotation = Quat::from_euler(
EulerRot::ZYX,
0.0,
time.seconds_since_startup() as f32 * std::f32::consts::TAU / 30.0,
-std::f32::consts::FRAC_PI_4,
time.seconds_since_startup() as f32 * TAU / 30.0,
-TAU / 8.,
);
}
}
Expand Down
18 changes: 8 additions & 10 deletions examples/transforms/3d_rotation.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
//! Illustrates how to (constantly) rotate an object around an axis.
//! Illustrates how to rotate an object around an axis.

use bevy::prelude::*;

use std::f32::consts::PI;

const FULL_TURN: f32 = 2.0 * PI;
use std::f32::consts::TAU;

// Define a component to designate a rotation speed to an entity.
#[derive(Component)]
Expand Down Expand Up @@ -41,19 +39,19 @@ fn setup(
..default()
});

// Add a light source for better 3d visibility.
// Add a light source so we can see clearly.
commands.spawn_bundle(PointLightBundle {
transform: Transform::from_translation(Vec3::ONE * 3.0),
..default()
});
}

// This system will rotate any entity in the scene with an assigned Rotatable around its z-axis.
// This system will rotate any entity in the scene with a Rotatable component around its y-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);
// The speed is first multiplied by TAU which is a full rotation (360deg) in radians,
// and then multiplied by delta_seconds which is the time that passed last frame.
// In other words. Speed is equal to the amount of rotations per second.
transform.rotate_y(cube.speed * TAU * timer.delta_seconds());
}
}
4 changes: 2 additions & 2 deletions examples/transforms/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ fn setup(
// 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);
let cube_spawn =
Transform::from_translation(Vec3::Z * -10.0).with_rotation(Quat::from_rotation_y(angle_90));
commands
.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
Expand Down
Loading

0 comments on commit 6723bad

Please sign in to comment.