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

Construct n-dimensional rotation matrix. #1366

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
58 changes: 58 additions & 0 deletions src/base/cg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,64 @@ where
}
}

/// # Rotation in any dimension
impl<T, D> OMatrix<T, D, D>
where
T: RealField,
D: DimName,
DefaultAllocator: Allocator<D, D> + Allocator<D, U1> + Allocator<U1, D>,
{
/// The n-dimensional rotation matrix described by an oriented minor arc.
///
/// See [`Rotation::from_arc`](`crate::Rotation::from_arc`) for details.
#[must_use]
pub fn from_arc<SB, SC>(
from: &Unit<Vector<T, D, SB>>,
to: &Unit<Vector<T, D, SC>>,
) -> Option<Self>
where
SB: Storage<T, D> + Clone,
SC: Storage<T, D> + Clone,
{
let (a, b, ab) = (from.as_ref(), to.as_ref(), from.dot(to));
let d = T::one() + ab.clone();
(d > T::default_epsilon().sqrt()).then(|| {
let [at, bt] = &[a.transpose(), b.transpose()];
let [b_at, a_bt, a_at, b_bt] = &[b * at, a * bt, a * at, b * bt];
let [k1, k2] = [b_at - a_bt, (b_at + a_bt) * ab - (a_at + b_bt)];
// Codesido's Rotation Formula
// <https://doi.org/10.14232/ejqtde.2018.1.13>
Self::identity() + k1 + k2 / d
})
}
/// The n-dimensional rotation matrix described by an oriented minor arc and a signed angle.
///
/// See [`Rotation::from_arc_angle`](`crate::Rotation::from_arc_angle`) for details.
#[must_use]
pub fn from_arc_angle<SB, SC>(
from: &Unit<Vector<T, D, SB>>,
to: &Unit<Vector<T, D, SC>>,
angle: T,
) -> Option<Self>
where
SB: Storage<T, D> + Clone,
SC: Storage<T, D> + Clone,
{
let (a, b, ab) = (from.as_ref(), to.as_ref(), from.dot(to));
// Tries making `b` orthonormal to `a` via Gram-Schmidt process.
(b - a * ab)
.try_normalize(T::default_epsilon().sqrt())
.map(|ref b| {
let (sin, cos) = angle.sin_cos();
let [at, bt] = &[a.transpose(), b.transpose()];
let [k1, k2] = [b * at - a * bt, -(a * at + b * bt)];
// Simple rotations / Rotation in a two–plane
// <https://doi.org/10.48550/arXiv.1103.5263>
Self::identity() + k1 * sin + k2 * (T::one() - cos)
})
}
}

/// # 2D transformations as a Matrix3
impl<T: RealField> Matrix3<T> {
/// Builds a 2 dimensional homogeneous rotation matrix from an angle in radian.
Expand Down
1 change: 1 addition & 0 deletions src/base/matrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ pub type MatrixCross<T, R1, C1, R2, C2> =
/// - [2D transformations as a Matrix3 <span style="float:right;">`new_rotation`…</span>](#2d-transformations-as-a-matrix3)
/// - [3D transformations as a Matrix4 <span style="float:right;">`new_rotation`, `new_perspective`, `look_at_rh`…</span>](#3d-transformations-as-a-matrix4)
/// - [Translation and scaling in any dimension <span style="float:right;">`new_scaling`, `new_translation`…</span>](#translation-and-scaling-in-any-dimension)
/// - [Rotation in any dimension <span style="float:right;">`from_arc`, `from_arc_angle`</span>](#rotation-in-any-dimension)
/// - [Append/prepend translation and scaling <span style="float:right;">`append_scaling`, `prepend_translation_mut`…</span>](#appendprepend-translation-and-scaling)
/// - [Transformation of vectors and points <span style="float:right;">`transform_vector`, `transform_point`…</span>](#transformation-of-vectors-and-points)
///
Expand Down
15 changes: 8 additions & 7 deletions src/geometry/rotation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ use rkyv::bytecheck;

/// A rotation matrix.
///
/// This is also known as an element of a Special Orthogonal (SO) group.
/// The `Rotation` type can either represent a 2D or 3D rotation, represented as a matrix.
/// For a rotation based on quaternions, see [`UnitQuaternion`](crate::UnitQuaternion) instead.
/// This is also known as an element of a Special Orthogonal (SO) group. The [`Rotation`] type can
/// represent an n-dimensional rotation as a matrix. For a rotation based on quaternions, see
/// [`UnitQuaternion`](crate::UnitQuaternion) instead.
///
/// Note that instead of using the [`Rotation`](crate::Rotation) type in your code directly, you should use one
/// of its aliases: [`Rotation2`](crate::Rotation2), or [`Rotation3`](crate::Rotation3). Though
/// keep in mind that all the documentation of all the methods of these aliases will also appears on
/// this page.
/// For fixed dimensions, you should use its aliases (e.g., [`Rotation2`](crate::Rotation2) or
/// [`Rotation3`](crate::Rotation3)) instead of the dimension-generic [`Rotation`](crate::Rotation)
/// type. Though keep in mind that all the documentation of all the methods of these aliases will
/// also appears on this page.
///
/// # Construction
/// * [Identity <span style="float:right;">`identity`</span>](#identity)
Expand All @@ -38,6 +38,7 @@ use rkyv::bytecheck;
/// * [From a 3D axis and/or angles <span style="float:right;">`new`, `from_euler_angles`, `from_axis_angle`…</span>](#construction-from-a-3d-axis-andor-angles)
/// * [From a 3D eye position and target point <span style="float:right;">`look_at`, `look_at_lh`, `rotation_between`…</span>](#construction-from-a-3d-eye-position-and-target-point)
/// * [From an existing 3D matrix or rotations <span style="float:right;">`from_matrix`, `rotation_between`, `powf`…</span>](#construction-from-an-existing-3d-matrix-or-rotations)
/// * [From an arc in any dimension <span style="float:right;">`from_arc`, `from_arc_angle`…</span>](#construction-in-any-dimension)
///
/// # Transformation and composition
/// Note that transforming vectors and points can be done by multiplication, e.g., `rotation * point`.
Expand Down
19 changes: 17 additions & 2 deletions src/geometry/rotation_alias.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,25 @@ use crate::geometry::Rotation;

/// A 2-dimensional rotation matrix.
///
/// **Because this is an alias, not all its methods are listed here. See the [`Rotation`](crate::Rotation) type too.**
/// **Because this is an alias, not all its methods are listed here. See the [`Rotation`](crate::Rotation) type, too.**
pub type Rotation2<T> = Rotation<T, 2>;

/// A 3-dimensional rotation matrix.
///
/// **Because this is an alias, not all its methods are listed here. See the [`Rotation`](crate::Rotation) type too.**
/// **Because this is an alias, not all its methods are listed here. See the [`Rotation`](crate::Rotation) type, too.**
pub type Rotation3<T> = Rotation<T, 3>;

/// A 4-dimensional rotation matrix.
///
/// **Because this is an alias, not all its methods are listed here. See the [`Rotation`](crate::Rotation) type, too.**
pub type Rotation4<T> = Rotation<T, 4>;

/// A 5-dimensional rotation matrix.
///
/// **Because this is an alias, not all its methods are listed here. See the [`Rotation`](crate::Rotation) type, too.**
pub type Rotation5<T> = Rotation<T, 5>;

/// A 6-dimensional rotation matrix.
///
/// **Because this is an alias, not all its methods are listed here. See the [`Rotation`](crate::Rotation) type, too.**
pub type Rotation6<T> = Rotation<T, 6>;
97 changes: 95 additions & 2 deletions src/geometry/rotation_construction.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use num::{One, Zero};

use simba::scalar::{ClosedAddAssign, ClosedMulAssign, SupersetOf};
use simba::scalar::{ClosedAddAssign, ClosedMulAssign, RealField, SupersetOf};

use crate::base::{SMatrix, Scalar};
use crate::base::{storage::Storage, Const, SMatrix, Scalar, Unit, Vector};

use crate::geometry::Rotation;

Expand Down Expand Up @@ -44,6 +44,99 @@ where
}
}

/// # Construction in any dimension
impl<T: RealField, const D: usize> Rotation<T, D> {
/// The n-dimensional rotation matrix described by an oriented minor arc.
///
/// This is the rotation `rot` aligning `from` with `to` over their minor angle such that
/// `(rot * from).angle(to) == 0` and `(rot * from).dot(to).is_positive()`.
///
/// Returns `None` with `from` and `to` being anti-parallel. In contrast to
/// [`Self::from_arc_angle`], this method is robust for approximately parallel vectors
/// continuously approaching identity.
///
/// See also [`OMatrix::from_arc`](`crate::OMatrix::from_arc`) for owned matrices generic over
/// storage.
///
/// # Example
///
/// ```
/// # #[macro_use] extern crate approx;
/// # use nalgebra::{Rotation, Unit, Vector6};
/// let from = Unit::new_normalize(Vector6::new(-4.0, -2.4, 0.0, -3.3, -1.0, -9.0));
/// let to = Unit::new_normalize(Vector6::new(3.0, 1.0, 2.0, 2.0, 9.0, 6.0));
///
/// // Aligns `from` with `to`.
/// let rot = Rotation::from_arc(&from, &to).unwrap();
/// assert_relative_eq!(rot * from, to, epsilon = 1.0e-6);
/// assert_relative_eq!(rot.inverse() * to, from, epsilon = 1.0e-6);
///
/// // Returns identity with `from` and `to` being parallel.
/// let rot = Rotation::from_arc(&from, &from).unwrap();
/// assert_relative_eq!(rot, Rotation::identity(), epsilon = 1.0e-6);
///
/// // Returns `None` with `from` and `to` being anti-parallel.
/// assert!(Rotation::from_arc(&from, &-from).is_none());
/// ```
#[must_use]
#[inline]
pub fn from_arc<SB, SC>(
from: &Unit<Vector<T, Const<D>, SB>>,
to: &Unit<Vector<T, Const<D>, SC>>,
) -> Option<Self>
where
T: RealField,
SB: Storage<T, Const<D>> + Clone,
SC: Storage<T, Const<D>> + Clone,
{
SMatrix::from_arc(from, to).map(Self::from_matrix_unchecked)
}
/// The n-dimensional rotation matrix described by an oriented minor arc and a signed angle.
///
/// Returns `None` with `from` and `to` being collinear. This method is more robust, the less
/// `from` and `to` are collinear, regardless of `angle`.
///
/// See also [`Self::from_arc`] aligning `from` with `to` over their minor angle.
///
/// See also [`OMatrix::from_arc_angle`](`crate::OMatrix::from_arc_angle`) for owned matrices
/// generic over storage.
///
/// # Example
///
/// ```
/// # #[macro_use] extern crate approx;
/// # use nalgebra::{Rotation, Unit, Vector6};
/// let from = Unit::new_normalize(Vector6::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0));
/// let to = Unit::new_normalize(Vector6::new(3.0, 1.0, 2.0, 5.0, 9.0, 4.0));
///
/// // Rotates by signed angle where `from` and `to` define its orientation.
/// let angle = 70f64.to_radians();
/// let rot = Rotation::from_arc_angle(&from, &to, angle).unwrap();
/// assert_relative_eq!((rot * from).angle(&from), angle, epsilon = 1.0e-6);
/// assert_relative_eq!((rot.inverse() * to).angle(&to), angle, epsilon = 1.0e-6);
/// let inv = Rotation::from_arc_angle(&from, &to, -angle).unwrap();
/// assert_relative_eq!(rot.inverse(), inv, epsilon = 1.0e-6);
///
/// // Returns `None` with `from` and `to` being collinear.
/// assert!(Rotation::from_arc_angle(&from, &from, angle).is_none());
/// assert!(Rotation::from_arc_angle(&from, &-from, angle).is_none());
/// ```
#[must_use]
#[inline]
pub fn from_arc_angle<SB, SC>(
from: &Unit<Vector<T, Const<D>, SB>>,
to: &Unit<Vector<T, Const<D>, SC>>,
angle: T,
) -> Option<Self>
where
T: RealField,
SB: Storage<T, Const<D>> + Clone,
SC: Storage<T, Const<D>> + Clone,
{
SMatrix::from_arc_angle(from, to, angle).map(Self::from_matrix_unchecked)
}
}

impl<T: Scalar, const D: usize> Rotation<T, D> {
/// Cast the components of `self` to another type.
///
Expand Down
Loading