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

Alignment API for Transforms #12187

Merged
merged 13 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2284,6 +2284,17 @@ description = "Illustrates how to (constantly) rotate an object around an axis"
category = "Transforms"
wasm = true

[[example]]
name = "align"
path = "examples/transforms/align.rs"
doc-scrape-examples = true

[package.metadata.example.align]
name = "Alignment"
description = "A demonstration of Transform's axis-alignment feature"
category = "Transforms"
wasm = true

[[example]]
name = "scale"
path = "examples/transforms/scale.rs"
Expand Down
114 changes: 114 additions & 0 deletions crates/bevy_transform/src/components/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,39 @@ impl Transform {
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`.
///
/// 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`.
///
/// 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)
///
/// 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,
) -> Self {
self.align(
main_axis,
main_direction,
secondary_axis,
secondary_direction,
);
self
}

/// Returns this [`Transform`] with a new translation.
#[inline]
#[must_use]
Expand Down Expand Up @@ -366,6 +399,87 @@ impl Transform {
self.rotation = Quat::from_mat3(&Mat3::from_cols(right, up, back));
}

/// 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
/// 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
/// 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` 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_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;
/// 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);
/// assert_eq!(t1.rotation, t2.rotation);
///
/// t1.align(Vec3::X, Vec3::Z, Vec3::X, Vec3::Y);
/// assert_eq!(t1.rotation, Quat::from_rotation_arc(Vec3::X, Vec3::Z));
/// ```
#[inline]
pub fn align(
&mut self,
main_axis: Vec3,
main_direction: Vec3,
secondary_axis: Vec3,
secondary_direction: Vec3,
) {
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);

// 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'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)
.try_normalize();
let secondary_direction_ortho = secondary_direction
.reject_from_normalized(main_direction)
.try_normalize();

// If one of the two weak vectors was parallel to `main_direction`, then we just do the first part
self.rotation = match (secondary_image_ortho, secondary_direction_ortho) {
(Some(secondary_img_ortho), Some(secondary_dir_ortho)) => {
let second_rotation =
Quat::from_rotation_arc(secondary_img_ortho, secondary_dir_ortho);
second_rotation * first_rotation
}
_ => first_rotation,
};
}

/// Multiplies `self` with `transform` component by component, returning the
/// resulting [`Transform`]
#[inline]
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ Example | Description
Example | Description
--- | ---
[3D Rotation](../examples/transforms/3d_rotation.rs) | Illustrates how to (constantly) rotate an object around an axis
[Alignment](../examples/transforms/align.rs) | A demonstration of Transform's axis-alignment feature
[Scale](../examples/transforms/scale.rs) | Illustrates how to scale an object in each direction
[Transform](../examples/transforms/transform.rs) | Shows multiple transformations of objects
[Translation](../examples/transforms/translation.rs) | Illustrates how to move an object along an axis
Expand Down
Loading