added generalized rotational slerp

This commit is contained in:
Joshua Smith 2022-03-21 15:46:55 -05:00
parent 6a553f1ee2
commit 7b92e114f9
1 changed files with 131 additions and 1 deletions

View File

@ -1,4 +1,6 @@
use crate::{RealField, Rotation2, Rotation3, SimdRealField, UnitComplex, UnitQuaternion};
use crate::{RealField, Rotation, Rotation2, Rotation3, SimdRealField, UnitComplex, UnitQuaternion};
use crate::{Const, U1, DimSub, DimDiff, Storage, ArrayStorage, Allocator, DefaultAllocator};
use crate::SMatrix;
/// # Interpolation
impl<T: SimdRealField> Rotation2<T> {
@ -79,3 +81,131 @@ impl<T: SimdRealField> Rotation3<T> {
q1.try_slerp(&q2, t, epsilon).map(|q| q.into())
}
}
impl<T:SimdRealField, const D: usize> Rotation<T,D> {
#[warn(missing_docs)]
pub fn basis_rot(i:usize, j:usize, angle:T) -> Self {
let mut m = SMatrix::identity();
if i==j { return Self::from_matrix_unchecked(m); }
let (s,c) = angle.simd_sin_cos();
m[(i,i)] = c.clone();
m[(i,j)] = -s.clone();
m[(j,i)] = s;
m[(j,j)] = c;
Self::from_matrix_unchecked(m)
}
}
impl<T:RealField, const D: usize> Rotation<T,D> where
Const<D>: DimSub<U1>,
ArrayStorage<T,D,D>: Storage<T,Const<D>,Const<D>>,
DefaultAllocator: Allocator<T,Const<D>,Const<D>,Buffer=ArrayStorage<T,D,D>> + Allocator<T,Const<D>> +
Allocator<T,Const<D>,DimDiff<Const<D>,U1>> +
Allocator<T,DimDiff<Const<D>,U1>>
{
#[warn(missing_docs)]
pub fn general_slerp(&self, other: &Self, t:T) -> Self {
self * (self/other).general_pow(t)
}
#[warn(missing_docs)]
pub fn general_pow(self, t:T) -> Self {
if D<=1 { return self; }
//taking the (real) schur form is guaranteed to produce a block-diagonal matrix
//where each block is either a 1 (if there's no rotation in that axis) or a 2x2
//rotation matrix in a particular plane
let schur = self.into_inner().schur();
let (q, mut d) = schur.unpack();
//go down the diagonal and pow every block
for i in 0..(D-1) {
//we've found a 2x2 block!
//NOTE: the impl of the schur decomposition always sets the inferior diagonal to 0
if !d[(i+1,i)].is_zero() {
//convert to a complex num and take the arg()
let (c, s) = (d[(i,i)].clone(), d[(i+1,i)].clone());
let angle = s.atan2(c);
//scale the arg and exponentiate back
let angle2 = angle * t.clone();
let (s2, c2) = angle2.sin_cos();
//convert back into a rot block
d[(i, i )] = c2.clone();
d[(i, i+1)] = -s2.clone();
d[(i+1,i )] = s2;
d[(i+1,i+1)] = c2;
}
}
let qt = q.transpose(); //avoids an extra clone
Self::from_matrix_unchecked(q * d * qt)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::PI;
const EPS: f64 = 2E-10;
#[test]
fn rot_pow() {
let r1 = Rotation2::new(0.0);
let r2 = Rotation2::new(PI/4.0);
let r3 = Rotation2::new(PI/2.0);
assert_relative_eq!(r1.general_pow(0.5), r1, epsilon=EPS);
assert_relative_eq!(r3.general_pow(0.5), r2, epsilon=EPS);
}
#[test]
fn basis_rot() {
const D:usize = 4;
let basis_blades = |n| (0..n).flat_map(
move |i| (i..n).map(move |j| (i,j))
).filter(|(i,j)| i!=j);
for (i1,j1) in basis_blades(D) {
for (i2,j2) in basis_blades(D) {
if i1==i2 || j1==j2 || i1==j2 || j1==i2 { continue; }
let r1 = Rotation::<_,D>::basis_rot(i1,j1,PI/4.0) *
Rotation::<_,D>::basis_rot(i2,j2,PI/4.0);
let r2 = Rotation::<_,D>::basis_rot(i1,j1,PI/2.0) *
Rotation::<_,D>::basis_rot(i2,j2,PI/2.0);
println!("{}{}",r1,r2);
assert_relative_eq!(r2.general_pow(0.5), r1, epsilon=EPS);
}
}
}
}