Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Dir3 in Transform APIs #12530

Merged
merged 3 commits into from
Mar 17, 2024
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 50 additions & 50 deletions crates/bevy_transform/src/components/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,11 @@ impl Transform {
///
/// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases:
/// * if `target` is the same as the transform translation, `Vec3::Z` is used instead
/// * if `up` is zero, `Vec3::Y` is used instead
/// * if `up` fails converting to `Dir3`, `Dir3::Y` is used instead
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
/// * if the resulting forward direction is parallel with `up`, an orthogonal vector is used as the "right" direction
#[inline]
#[must_use]
pub fn looking_at(mut self, target: Vec3, up: Vec3) -> Self {
pub fn looking_at(mut self, target: Vec3, up: impl TryInto<Dir3>) -> Self {
self.look_at(target, up);
self
}
Expand All @@ -135,39 +135,39 @@ impl Transform {
/// points in the given `direction` and [`Transform::up`] points towards `up`.
///
/// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases:
/// * if `direction` is zero, `Vec3::Z` is used instead
/// * if `up` is zero, `Vec3::Y` is used instead
/// * if `direction` fails converting to `Dir3` (e.g if it is `Vec3::ZERO`), `Dir3::Z` is used instead
/// * if `up` fails converting to `Dir3`, `Dir3::Y` is used instead
/// * if `direction` is parallel with `up`, an orthogonal vector is used as the "right" direction
#[inline]
#[must_use]
pub fn looking_to(mut self, direction: Vec3, up: Vec3) -> Self {
pub fn looking_to(mut self, direction: impl TryInto<Dir3>, up: impl TryInto<Dir3>) -> Self {
self.look_to(direction, up);
self
}

/// Returns this [`Transform`] with a rotation so that the `handle` vector, reinterpreted in local coordinates,
/// points in the given `direction`, while `weak_handle` points towards `weak_direction`.
///
/// Rotates this [`Transform`] so that the `main_axis` vector, reinterpreted in local coordinates, points
/// in the given `main_direction`, while `secondary_axis` points towards `secondary_direction`.
/// For example, if a spaceship model has its nose pointing in the X-direction in its own local coordinates
/// and its dorsal fin pointing in the Y-direction, then `Transform::aligned_by(Vec3::X, v, Vec3::Y, w)` will
/// make the spaceship's nose point in the direction of `v`, while the dorsal fin does its best to point in the
/// direction `w`.
/// and its dorsal fin pointing in the Y-direction, then `align(Dir3::X, v, Dir3::Y, w)` will make the spaceship's
/// nose point in the direction of `v`, while the dorsal fin does its best to point in the direction `w`.
///
///
/// In some cases a rotation cannot be constructed. Another axis will be picked in those cases:
/// * if `handle` or `direction` is zero, `Vec3::X` takes its place
/// * if `weak_handle` or `weak_direction` is zero, `Vec3::Y` takes its place
/// * if `handle` is parallel with `weak_handle` or `direction` is parallel with `weak_direction`, a rotation is
/// constructed which takes `handle` to `direction` but ignores the weak counterparts (i.e. is otherwise unspecified)
/// * if `main_axis` or `main_direction` fail converting to `Dir3` (e.g are zero), `Dir3::X` takes their place
/// * if `secondary_axis` or `secondary_direction` fail converting, `Dir3::Y` takes their place
/// * if `main_axis` is parallel with `secondary_axis` or `main_direction` is parallel with `secondary_direction`,
/// a rotation is constructed which takes `main_axis` to `main_direction` along a great circle, ignoring the secondary
/// counterparts
///
/// See [`Transform::align`] for additional details.
#[inline]
#[must_use]
pub fn aligned_by(
mut self,
main_axis: Vec3,
main_direction: Vec3,
secondary_axis: Vec3,
secondary_direction: Vec3,
main_axis: impl TryInto<Dir3>,
main_direction: impl TryInto<Dir3>,
secondary_axis: impl TryInto<Dir3>,
secondary_direction: impl TryInto<Dir3>,
) -> Self {
self.align(
main_axis,
Expand Down Expand Up @@ -373,100 +373,100 @@ impl Transform {
///
/// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases:
/// * if `target` is the same as the transform translation, `Vec3::Z` is used instead
/// * if `up` is zero, `Vec3::Y` is used instead
/// * if `up` fails converting to `Dir3` (e.g if it is `Vec3::ZERO`), `Dir3::Y` is used instead
/// * if the resulting forward direction is parallel with `up`, an orthogonal vector is used as the "right" direction
#[inline]
pub fn look_at(&mut self, target: Vec3, up: Vec3) {
pub fn look_at(&mut self, target: Vec3, up: impl TryInto<Dir3>) {
self.look_to(target - self.translation, up);
}

/// Rotates this [`Transform`] so that [`Transform::forward`] points in the given `direction`
/// and [`Transform::up`] points towards `up`.
///
/// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases:
/// * if `direction` is zero, `Vec3::NEG_Z` is used instead
/// * if `up` is zero, `Vec3::Y` is used instead
/// * if `direction` fails converting to `Dir3` (e.g if it is `Vec3::ZERO`), `Dir3::NEG_Z` is used instead
/// * if `up` fails converting to `Dir3`, `Dir3::Y` is used instead
/// * if `direction` is parallel with `up`, an orthogonal vector is used as the "right" direction
#[inline]
pub fn look_to(&mut self, direction: Vec3, up: Vec3) {
let back = -direction.try_normalize().unwrap_or(Vec3::NEG_Z);
let up = up.try_normalize().unwrap_or(Vec3::Y);
pub fn look_to(&mut self, direction: impl TryInto<Dir3>, up: impl TryInto<Dir3>) {
let back = -direction.try_into().unwrap_or(Dir3::NEG_Z);
let up = up.try_into().unwrap_or(Dir3::Y);
let right = up
.cross(back)
.cross(back.into())
.try_normalize()
.unwrap_or_else(|| up.any_orthonormal_vector());
let up = back.cross(right);
self.rotation = Quat::from_mat3(&Mat3::from_cols(right, up, back));
self.rotation = Quat::from_mat3(&Mat3::from_cols(right, up, back.into()));
}

/// Rotates this [`Transform`] so that the `main_axis` vector, reinterpreted in local coordinates, points
/// in the given `main_direction`, while `secondary_axis` points towards `secondary_direction`.
///
/// For example, if a spaceship model has its nose pointing in the X-direction in its own local coordinates
/// and its dorsal fin pointing in the Y-direction, then `align(Vec3::X, v, Vec3::Y, w)` will make the spaceship's
/// and its dorsal fin pointing in the Y-direction, then `align(Dir3::X, v, Dir3::Y, w)` will make the spaceship's
/// nose point in the direction of `v`, while the dorsal fin does its best to point in the direction `w`.
///
/// More precisely, the [`Transform::rotation`] produced will be such that:
/// * applying it to `main_axis` results in `main_direction`
/// * applying it to `secondary_axis` produces a vector that lies in the half-plane generated by `main_direction` and
/// `secondary_direction` (with positive contribution by `secondary_direction`)
///
/// [`Transform::look_to`] is recovered, for instance, when `main_axis` is `Vec3::NEG_Z` (the [`Transform::forward`]
/// direction in the default orientation) and `secondary_axis` is `Vec3::Y` (the [`Transform::up`] direction in the default
/// [`Transform::look_to`] is recovered, for instance, when `main_axis` is `Dir3::NEG_Z` (the [`Transform::forward`]
/// direction in the default orientation) and `secondary_axis` is `Dir3::Y` (the [`Transform::up`] direction in the default
/// orientation). (Failure cases may differ somewhat.)
///
/// In some cases a rotation cannot be constructed. Another axis will be picked in those cases:
/// * if `main_axis` or `main_direction` is zero, `Vec3::X` takes its place
/// * if `secondary_axis` or `secondary_direction` is zero, `Vec3::Y` takes its place
/// * if `main_axis` or `main_direction` fail converting to `Dir3` (e.g are zero), `Dir3::X` takes their place
/// * if `secondary_axis` or `secondary_direction` fail converting, `Dir3::Y` takes their place
/// * if `main_axis` is parallel with `secondary_axis` or `main_direction` is parallel with `secondary_direction`,
/// a rotation is constructed which takes `main_axis` to `main_direction` along a great circle, ignoring the secondary
/// counterparts
///
/// Example
/// ```
/// # use bevy_math::{Vec3, Quat};
/// # use bevy_math::{Dir3, Vec3, Quat};
/// # use bevy_transform::components::Transform;
/// # let mut t1 = Transform::IDENTITY;
/// # let mut t2 = Transform::IDENTITY;
/// t1.align(Vec3::X, Vec3::Y, Vec3::new(1., 1., 0.), Vec3::Z);
/// let main_axis_image = t1.rotation * Vec3::X;
/// t1.align(Dir3::X, Dir3::Y, Vec3::new(1., 1., 0.), Dir3::Z);
/// let main_axis_image = t1.rotation * Dir3::X;
/// let secondary_axis_image = t1.rotation * Vec3::new(1., 1., 0.);
/// assert!(main_axis_image.abs_diff_eq(Vec3::Y, 1e-5));
/// assert!(secondary_axis_image.abs_diff_eq(Vec3::new(0., 1., 1.), 1e-5));
///
/// t1.align(Vec3::ZERO, Vec3::Z, Vec3::ZERO, Vec3::X);
/// t2.align(Vec3::X, Vec3::Z, Vec3::Y, Vec3::X);
/// t1.align(Vec3::ZERO, Dir3::Z, Vec3::ZERO, Dir3::X);
/// t2.align(Dir3::X, Dir3::Z, Dir3::Y, Dir3::X);
/// assert_eq!(t1.rotation, t2.rotation);
///
/// t1.align(Vec3::X, Vec3::Z, Vec3::X, Vec3::Y);
/// t1.align(Dir3::X, Dir3::Z, Dir3::X, Dir3::Y);
/// assert_eq!(t1.rotation, Quat::from_rotation_arc(Vec3::X, Vec3::Z));
/// ```
Comment on lines 425 to 443
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice if the other methods also gotten an example like this, showcasing how you can mix&match vectors and directions to improve discoverability of the fact that both Dir3 and Vec3 are valid arguments, for new users.

Not a necessary change though.

#[inline]
pub fn align(
&mut self,
main_axis: Vec3,
main_direction: Vec3,
secondary_axis: Vec3,
secondary_direction: Vec3,
main_axis: impl TryInto<Dir3>,
main_direction: impl TryInto<Dir3>,
secondary_axis: impl TryInto<Dir3>,
secondary_direction: impl TryInto<Dir3>,
) {
let main_axis = main_axis.try_normalize().unwrap_or(Vec3::X);
let main_direction = main_direction.try_normalize().unwrap_or(Vec3::X);
let secondary_axis = secondary_axis.try_normalize().unwrap_or(Vec3::Y);
let secondary_direction = secondary_direction.try_normalize().unwrap_or(Vec3::Y);
let main_axis = main_axis.try_into().unwrap_or(Dir3::X);
let main_direction = main_direction.try_into().unwrap_or(Dir3::X);
let secondary_axis = secondary_axis.try_into().unwrap_or(Dir3::Y);
let secondary_direction = secondary_direction.try_into().unwrap_or(Dir3::Y);

// The solution quaternion will be constructed in two steps.
// First, we start with a rotation that takes `main_axis` to `main_direction`.
let first_rotation = Quat::from_rotation_arc(main_axis, main_direction);
let first_rotation = Quat::from_rotation_arc(main_axis.into(), main_direction.into());

// Let's follow by rotating about the `main_direction` axis so that the image of `secondary_axis`
// is taken to something that lies in the plane of `main_direction` and `secondary_direction`. Since
// `main_direction` is fixed by this rotation, the first criterion is still satisfied.
let secondary_image = first_rotation * secondary_axis;
let secondary_image_ortho = secondary_image
.reject_from_normalized(main_direction)
.reject_from_normalized(main_direction.into())
.try_normalize();
let secondary_direction_ortho = secondary_direction
.reject_from_normalized(main_direction)
.reject_from_normalized(main_direction.into())
.try_normalize();

// If one of the two weak vectors was parallel to `main_direction`, then we just do the first part
Expand Down