Skip to content

Commit 5dca00c

Browse files
tim-blackbirdinodentry
authored andcommitted
Add helper methods for rotating Transforms (bevyengine#5151)
# 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 <[email protected]>
1 parent 2c6c0eb commit 5dca00c

File tree

20 files changed

+111
-67
lines changed

20 files changed

+111
-67
lines changed

crates/bevy_animation/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,8 @@ pub fn animation_player(
262262
rot_end = -rot_end;
263263
}
264264
// Rotations are using a spherical linear interpolation
265-
transform.rotation = Quat::from_array(rot_start.normalize().into())
266-
.slerp(Quat::from_array(rot_end.normalize().into()), lerp);
265+
transform.rotation =
266+
rot_start.normalize().slerp(rot_end.normalize(), lerp);
267267
}
268268
Keyframes::Translation(keyframes) => {
269269
let translation_start = keyframes[step_start];

crates/bevy_gltf/src/loader.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ async fn load_gltf<'a, 'b>(
368368
scale,
369369
} => Transform {
370370
translation: bevy_math::Vec3::from(translation),
371-
rotation: bevy_math::Quat::from_vec4(rotation.into()),
371+
rotation: bevy_math::Quat::from_array(rotation),
372372
scale: bevy_math::Vec3::from(scale),
373373
},
374374
},

crates/bevy_transform/src/components/transform.rs

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -195,20 +195,81 @@ impl Transform {
195195
self.local_z()
196196
}
197197

198-
/// Rotates the transform by the given rotation.
198+
/// Rotates this [`Transform`] by the given rotation.
199199
#[inline]
200200
pub fn rotate(&mut self, rotation: Quat) {
201201
self.rotation = rotation * self.rotation;
202202
}
203203

204-
/// Rotates this [`Transform`] around a point in space.
205-
/// If the point is a zero vector, this will rotate around the parent (if any) or the origin.
204+
/// Rotates this [`Transform`] around the given `axis` by `angle` (in radians).
205+
///
206+
/// If this [`Transform`] has a parent, the `axis` is relative to the rotation of the parent.
207+
#[inline]
208+
pub fn rotate_axis(&mut self, axis: Vec3, angle: f32) {
209+
self.rotate(Quat::from_axis_angle(axis, angle));
210+
}
211+
212+
/// Rotates this [`Transform`] around the X axis by `angle` (in radians).
213+
///
214+
/// If this [`Transform`] has a parent, the axis is relative to the rotation of the parent.
215+
#[inline]
216+
pub fn rotate_x(&mut self, angle: f32) {
217+
self.rotate(Quat::from_rotation_x(angle));
218+
}
219+
220+
/// Rotates this [`Transform`] around the Y axis by `angle` (in radians).
221+
///
222+
/// If this [`Transform`] has a parent, the axis is relative to the rotation of the parent.
223+
#[inline]
224+
pub fn rotate_y(&mut self, angle: f32) {
225+
self.rotate(Quat::from_rotation_y(angle));
226+
}
227+
228+
/// Rotates this [`Transform`] around the Z axis by `angle` (in radians).
229+
///
230+
/// If this [`Transform`] has a parent, the axis is relative to the rotation of the parent.
231+
#[inline]
232+
pub fn rotate_z(&mut self, angle: f32) {
233+
self.rotate(Quat::from_rotation_z(angle));
234+
}
235+
236+
/// Rotates this [`Transform`] around its X axis by `angle` (in radians).
237+
#[inline]
238+
pub fn rotate_local_x(&mut self, angle: f32) {
239+
self.rotate_axis(self.local_x(), angle);
240+
}
241+
242+
/// Rotates this [`Transform`] around its Y axis by `angle` (in radians).
243+
#[inline]
244+
pub fn rotate_local_y(&mut self, angle: f32) {
245+
self.rotate_axis(self.local_y(), angle);
246+
}
247+
248+
/// Rotates this [`Transform`] around its Z axis by `angle` (in radians).
249+
#[inline]
250+
pub fn rotate_local_z(&mut self, angle: f32) {
251+
self.rotate_axis(self.local_z(), angle);
252+
}
253+
254+
/// Rotates this [`Transform`] around a `point` in space.
255+
///
256+
/// If this [`Transform`] has a parent, the `point` is relative to the [`Transform`] of the parent.
206257
#[inline]
207258
pub fn rotate_around(&mut self, point: Vec3, rotation: Quat) {
208259
self.translation = point + rotation * (self.translation - point);
209260
self.rotation *= rotation;
210261
}
211262

263+
/// Rotates this [`Transform`] so that its local negative z direction is toward
264+
/// `target` and its local y direction is toward `up`.
265+
#[inline]
266+
pub fn look_at(&mut self, target: Vec3, up: Vec3) {
267+
let forward = Vec3::normalize(self.translation - target);
268+
let right = up.cross(forward).normalize();
269+
let up = forward.cross(right);
270+
self.rotation = Quat::from_mat3(&Mat3::from_cols(right, up, forward));
271+
}
272+
212273
/// Multiplies `self` with `transform` component by component, returning the
213274
/// resulting [`Transform`]
214275
#[inline]
@@ -239,16 +300,6 @@ impl Transform {
239300
pub fn apply_non_uniform_scale(&mut self, scale_factor: Vec3) {
240301
self.scale *= scale_factor;
241302
}
242-
243-
/// Rotates this [`Transform`] so that its local z direction is toward
244-
/// `target` and its local y direction is toward `up`.
245-
#[inline]
246-
pub fn look_at(&mut self, target: Vec3, up: Vec3) {
247-
let forward = Vec3::normalize(self.translation - target);
248-
let right = up.cross(forward).normalize();
249-
let up = forward.cross(right);
250-
self.rotation = Quat::from_mat3(&Mat3::from_cols(right, up, forward));
251-
}
252303
}
253304

254305
impl Default for Transform {

examples/2d/rotation.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,8 @@ fn player_movement_system(
134134
movement_factor += 1.0;
135135
}
136136

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

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

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

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

246-
// get the quaternion to rotate from the current enemy facing direction towards the
247-
// direction facing the player
248-
let rotation_delta = Quat::from_rotation_z(rotation_angle);
249-
250244
// rotate the enemy to face the player
251-
enemy_transform.rotation *= rotation_delta;
245+
enemy_transform.rotate_z(rotation_angle);
252246
}
253247
}

examples/3d/lighting.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ fn setup(
3434

3535
// left wall
3636
let mut transform = Transform::from_xyz(2.5, 2.5, 0.0);
37-
transform.rotate(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2));
37+
transform.rotate_z(std::f32::consts::FRAC_PI_2);
3838
commands.spawn_bundle(PbrBundle {
3939
mesh: meshes.add(Mesh::from(shape::Box::new(5.0, 0.15, 5.0))),
4040
transform,
@@ -47,7 +47,7 @@ fn setup(
4747
});
4848
// back (right) wall
4949
let mut transform = Transform::from_xyz(0.0, 2.5, -2.5);
50-
transform.rotate(Quat::from_rotation_x(std::f32::consts::FRAC_PI_2));
50+
transform.rotate_x(std::f32::consts::FRAC_PI_2);
5151
commands.spawn_bundle(PbrBundle {
5252
mesh: meshes.add(Mesh::from(shape::Box::new(5.0, 0.15, 5.0))),
5353
transform,
@@ -214,7 +214,7 @@ fn animate_light_direction(
214214
mut query: Query<&mut Transform, With<DirectionalLight>>,
215215
) {
216216
for mut transform in query.iter_mut() {
217-
transform.rotate(Quat::from_rotation_y(time.delta_seconds() * 0.5));
217+
transform.rotate_y(time.delta_seconds() * 0.5);
218218
}
219219
}
220220

examples/3d/parenting.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ struct Rotator;
1818
/// rotates the parent, which will result in the child also rotating
1919
fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<Rotator>>) {
2020
for mut transform in query.iter_mut() {
21-
transform.rotation *= Quat::from_rotation_x(3.0 * time.delta_seconds());
21+
transform.rotate_x(3.0 * time.delta_seconds());
2222
}
2323
}
2424

examples/3d/render_to_texture.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,15 +145,15 @@ fn setup(
145145
/// Rotates the inner cube (first pass)
146146
fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<FirstPassCube>>) {
147147
for mut transform in query.iter_mut() {
148-
transform.rotation *= Quat::from_rotation_x(1.5 * time.delta_seconds());
149-
transform.rotation *= Quat::from_rotation_z(1.3 * time.delta_seconds());
148+
transform.rotate_x(1.5 * time.delta_seconds());
149+
transform.rotate_z(1.3 * time.delta_seconds());
150150
}
151151
}
152152

153153
/// Rotates the outer cube (main pass)
154154
fn cube_rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<MainPassCube>>) {
155155
for mut transform in query.iter_mut() {
156-
transform.rotation *= Quat::from_rotation_x(1.0 * time.delta_seconds());
157-
transform.rotation *= Quat::from_rotation_y(0.7 * time.delta_seconds());
156+
transform.rotate_x(1.0 * time.delta_seconds());
157+
transform.rotate_y(0.7 * time.delta_seconds());
158158
}
159159
}

examples/3d/shapes.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ fn setup(
5353
2.0,
5454
0.0,
5555
),
56+
rotation: Quat::from_rotation_x(-std::f32::consts::PI / 4.),
5657
..default()
5758
},
5859
..default()
@@ -86,8 +87,7 @@ fn setup(
8687

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

examples/ecs/hierarchy.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,14 @@ fn rotate(
9494
let angle = std::f32::consts::PI / 2.0;
9595
for (parent, children) in parents_query.iter_mut() {
9696
if let Ok(mut transform) = transform_query.get_mut(parent) {
97-
transform.rotate(Quat::from_rotation_z(-angle * time.delta_seconds()));
97+
transform.rotate_z(-angle * time.delta_seconds());
9898
}
9999

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

examples/games/alien_cake_addict.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ fn spawn_bonus(
348348
fn rotate_bonus(game: Res<Game>, time: Res<Time>, mut transforms: Query<&mut Transform>) {
349349
if let Some(entity) = game.bonus.entity {
350350
if let Ok(mut cake_transform) = transforms.get_mut(entity) {
351-
cake_transform.rotate(Quat::from_rotation_y(time.delta_seconds()));
351+
cake_transform.rotate_y(time.delta_seconds());
352352
cake_transform.scale = Vec3::splat(
353353
1.0 + (game.score as f32 / 10.0 * time.seconds_since_startup().sin() as f32).abs(),
354354
);

examples/games/contributors.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ fn move_system(time: Res<Time>, mut query: Query<(&Velocity, &mut Transform)>) {
301301

302302
for (velocity, mut transform) in query.iter_mut() {
303303
transform.translation += delta * velocity.translation;
304-
transform.rotate(Quat::from_rotation_z(velocity.rotation * delta));
304+
transform.rotate_z(velocity.rotation * delta);
305305
}
306306
}
307307

examples/shader/post_processing.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,8 @@ fn main_camera_cube_rotator_system(
158158
mut query: Query<&mut Transform, With<MainCube>>,
159159
) {
160160
for mut transform in query.iter_mut() {
161-
transform.rotation *= Quat::from_rotation_x(0.55 * time.delta_seconds());
162-
transform.rotation *= Quat::from_rotation_z(0.15 * time.delta_seconds());
161+
transform.rotate_x(0.55 * time.delta_seconds());
162+
transform.rotate_z(0.15 * time.delta_seconds());
163163
}
164164
}
165165

examples/stress_tests/many_cubes.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,9 @@ fn spherical_polar_to_cartesian(p: DVec2) -> DVec3 {
149149
// System for rotating the camera
150150
fn move_camera(time: Res<Time>, mut camera_query: Query<&mut Transform, With<Camera>>) {
151151
let mut camera_transform = camera_query.single_mut();
152-
camera_transform.rotate(Quat::from_rotation_z(time.delta_seconds() * 0.15));
153-
camera_transform.rotate(Quat::from_rotation_x(time.delta_seconds() * 0.15));
152+
let delta = time.delta_seconds() * 0.15;
153+
camera_transform.rotate_z(delta);
154+
camera_transform.rotate_x(delta);
154155
}
155156

156157
// System for printing the number of meshes on every tick of the timer

examples/stress_tests/many_foxes.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,7 @@ fn update_fox_rings(
204204
let dt = time.delta_seconds();
205205
for (ring, rotation_direction, mut transform) in rings.iter_mut() {
206206
let angular_velocity = foxes.speed / ring.radius;
207-
transform.rotate(Quat::from_rotation_y(
208-
rotation_direction.sign() * angular_velocity * dt,
209-
));
207+
transform.rotate_y(rotation_direction.sign() * angular_velocity * dt);
210208
}
211209
}
212210

examples/stress_tests/many_lights.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,9 @@ fn spherical_polar_to_cartesian(p: DVec2) -> DVec3 {
124124
// System for rotating the camera
125125
fn move_camera(time: Res<Time>, mut camera_query: Query<&mut Transform, With<Camera>>) {
126126
let mut camera_transform = camera_query.single_mut();
127-
camera_transform.rotate(Quat::from_rotation_z(time.delta_seconds() * 0.15));
128-
camera_transform.rotate(Quat::from_rotation_x(time.delta_seconds() * 0.15));
127+
let delta = time.delta_seconds() * 0.15;
128+
camera_transform.rotate_z(delta);
129+
camera_transform.rotate_x(delta);
129130
}
130131

131132
// System for printing the number of meshes on every tick of the timer

examples/stress_tests/many_sprites.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ fn setup(mut commands: Commands, assets: Res<AssetServer>) {
7474
// System for rotating and translating the camera
7575
fn move_camera(time: Res<Time>, mut camera_query: Query<&mut Transform, With<Camera>>) {
7676
let mut camera_transform = camera_query.single_mut();
77-
camera_transform.rotate(Quat::from_rotation_z(time.delta_seconds() * 0.5));
77+
camera_transform.rotate_z(time.delta_seconds() * 0.5);
7878
*camera_transform = *camera_transform
7979
* Transform::from_translation(Vec3::X * CAMERA_SPEED * time.delta_seconds());
8080
}

examples/tools/scene_viewer.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ use bevy::{
1414
scene::InstanceId,
1515
};
1616

17+
use std::f32::consts::TAU;
18+
1719
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
1820
struct CameraControllerCheckSystem;
1921

@@ -327,8 +329,8 @@ fn update_lights(
327329
transform.rotation = Quat::from_euler(
328330
EulerRot::ZYX,
329331
0.0,
330-
time.seconds_since_startup() as f32 * std::f32::consts::TAU / 30.0,
331-
-std::f32::consts::FRAC_PI_4,
332+
time.seconds_since_startup() as f32 * TAU / 30.0,
333+
-TAU / 8.,
332334
);
333335
}
334336
}

examples/transforms/3d_rotation.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
//! Illustrates how to (constantly) rotate an object around an axis.
1+
//! Illustrates how to rotate an object around an axis.
22
33
use bevy::prelude::*;
44

5-
use std::f32::consts::PI;
6-
7-
const FULL_TURN: f32 = 2.0 * PI;
5+
use std::f32::consts::TAU;
86

97
// Define a component to designate a rotation speed to an entity.
108
#[derive(Component)]
@@ -41,19 +39,19 @@ fn setup(
4139
..default()
4240
});
4341

44-
// Add a light source for better 3d visibility.
42+
// Add a light source so we can see clearly.
4543
commands.spawn_bundle(PointLightBundle {
4644
transform: Transform::from_translation(Vec3::ONE * 3.0),
4745
..default()
4846
});
4947
}
5048

51-
// This system will rotate any entity in the scene with an assigned Rotatable around its z-axis.
49+
// This system will rotate any entity in the scene with a Rotatable component around its y-axis.
5250
fn rotate_cube(mut cubes: Query<(&mut Transform, &Rotatable)>, timer: Res<Time>) {
5351
for (mut transform, cube) in cubes.iter_mut() {
54-
// The speed is taken as a percentage of a full 360 degree turn.
55-
// The timers delta_seconds is used to smooth out the movement.
56-
let rotation_change = Quat::from_rotation_y(FULL_TURN * cube.speed * timer.delta_seconds());
57-
transform.rotate(rotation_change);
52+
// The speed is first multiplied by TAU which is a full rotation (360deg) in radians,
53+
// and then multiplied by delta_seconds which is the time that passed last frame.
54+
// In other words. Speed is equal to the amount of rotations per second.
55+
transform.rotate_y(cube.speed * TAU * timer.delta_seconds());
5856
}
5957
}

examples/transforms/transform.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ fn setup(
6060
// Define a start transform for an orbiting cube, that's away from our central object (sphere)
6161
// and rotate it so it will be able to move around the sphere and not towards it.
6262
let angle_90 = PI / 2.0;
63-
let mut cube_spawn = Transform::from_translation(Vec3::Z * -10.0);
64-
cube_spawn.rotation = Quat::from_rotation_y(angle_90);
63+
let cube_spawn =
64+
Transform::from_translation(Vec3::Z * -10.0).with_rotation(Quat::from_rotation_y(angle_90));
6565
commands
6666
.spawn_bundle(PbrBundle {
6767
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),

0 commit comments

Comments
 (0)