Skip to content

Commit

Permalink
Implement interpolation of Quaternions
Browse files Browse the repository at this point in the history
So that we can reuse the code in ComputedRotate.
  • Loading branch information
CJ Ku authored and birtles committed Jan 31, 2018
1 parent 174f5f7 commit 9a62c0b
Showing 1 changed file with 71 additions and 82 deletions.
153 changes: 71 additions & 82 deletions components/style/properties/helpers/animated_properties.mako.rs
Expand Up @@ -1696,7 +1696,7 @@ pub struct Perspective(f32, f32, f32, f32);
pub struct Quaternion(f64, f64, f64, f64);

/// A decomposed 3d matrix.
#[derive(Clone, ComputeSquaredDistance, Copy, Debug)]
#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)]
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
pub struct MatrixDecomposed3D {
/// A translation function.
Expand Down Expand Up @@ -1739,6 +1739,76 @@ impl Quaternion {
}
}

impl Animate for Quaternion {
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
use std::f64;

let (this_weight, other_weight) = procedure.weights();
debug_assert!((this_weight + other_weight - 1.0f64).abs() <= f64::EPSILON ||
other_weight == 1.0f64 || other_weight == 0.0f64,
"animate should only be used for interpolating or accumulating transforms");

// We take a specialized code path for accumulation (where other_weight is 1)
if other_weight == 1.0 {
if this_weight == 0.0 {
return Ok(*other);
}

let clamped_w = self.3.min(1.0).max(-1.0);

// Determine the scale factor.
let mut theta = clamped_w.acos();
let mut scale = if theta == 0.0 { 0.0 } else { 1.0 / theta.sin() };
theta *= this_weight;
scale *= theta.sin();

// Scale the self matrix by this_weight.
let mut scaled_self = *self;
% for i in range(3):
scaled_self.${i} *= scale;
% endfor
scaled_self.3 = theta.cos();

// Multiply scaled-self by other.
let a = &scaled_self;
let b = other;
return Ok(Quaternion(
a.3 * b.0 + a.0 * b.3 + a.1 * b.2 - a.2 * b.1,
a.3 * b.1 - a.0 * b.2 + a.1 * b.3 + a.2 * b.0,
a.3 * b.2 + a.0 * b.1 - a.1 * b.0 + a.2 * b.3,
a.3 * b.3 - a.0 * b.0 - a.1 * b.1 - a.2 * b.2,
));
}

let mut product = self.0 * other.0 +
self.1 * other.1 +
self.2 * other.2 +
self.3 * other.3;

// Clamp product to -1.0 <= product <= 1.0
product = product.min(1.0);
product = product.max(-1.0);

if product == 1.0 {
return Ok(*self);
}

let theta = product.acos();
let w = (other_weight * theta).sin() * 1.0 / (1.0 - product * product).sqrt();

let mut a = *self;
let mut b = *other;
let mut result = Quaternion(0., 0., 0., 0.,);
% for i in range(4):
a.${i} *= (other_weight * theta).cos() - product * w;
b.${i} *= w;
result.${i} = a.${i} + b.${i};
% endfor

Ok(result)
}
}

impl ComputeSquaredDistance for Quaternion {
#[inline]
fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
Expand Down Expand Up @@ -2002,87 +2072,6 @@ impl Animate for Perspective {
}
}

impl Animate for MatrixDecomposed3D {
/// <https://drafts.csswg.org/css-transforms/#interpolation-of-decomposed-3d-matrix-values>
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
use std::f64;

let (this_weight, other_weight) = procedure.weights();

debug_assert!((this_weight + other_weight - 1.0f64).abs() <= f64::EPSILON ||
other_weight == 1.0f64 || other_weight == 0.0f64,
"animate should only be used for interpolating or accumulating transforms");

let mut sum = *self;

// Add translate, scale, skew and perspective components.
sum.translate = self.translate.animate(&other.translate, procedure)?;
sum.scale = self.scale.animate(&other.scale, procedure)?;
sum.skew = self.skew.animate(&other.skew, procedure)?;
sum.perspective = self.perspective.animate(&other.perspective, procedure)?;

// Add quaternions using spherical linear interpolation (Slerp).
//
// We take a specialized code path for accumulation (where other_weight is 1)
if other_weight == 1.0 {
if this_weight == 0.0 {
return Ok(*other)
}

let clamped_w = self.quaternion.3.min(1.0).max(-1.0);

// Determine the scale factor.
let mut theta = clamped_w.acos();
let mut scale = if theta == 0.0 { 0.0 } else { 1.0 / theta.sin() };
theta *= this_weight;
scale *= theta.sin();

// Scale the self matrix by this_weight.
let mut scaled_self = *self;
% for i in range(3):
scaled_self.quaternion.${i} *= scale;
% endfor
scaled_self.quaternion.3 = theta.cos();

// Multiply scaled-self by other.
let a = &scaled_self.quaternion;
let b = &other.quaternion;
sum.quaternion = Quaternion(
a.3 * b.0 + a.0 * b.3 + a.1 * b.2 - a.2 * b.1,
a.3 * b.1 - a.0 * b.2 + a.1 * b.3 + a.2 * b.0,
a.3 * b.2 + a.0 * b.1 - a.1 * b.0 + a.2 * b.3,
a.3 * b.3 - a.0 * b.0 - a.1 * b.1 - a.2 * b.2,
);
} else {
let mut product = self.quaternion.0 * other.quaternion.0 +
self.quaternion.1 * other.quaternion.1 +
self.quaternion.2 * other.quaternion.2 +
self.quaternion.3 * other.quaternion.3;

// Clamp product to -1.0 <= product <= 1.0
product = product.min(1.0);
product = product.max(-1.0);

if product == 1.0 {
return Ok(sum);
}

let theta = product.acos();
let w = (other_weight * theta).sin() * 1.0 / (1.0 - product * product).sqrt();

let mut a = *self;
let mut b = *other;
% for i in range(4):
a.quaternion.${i} *= (other_weight * theta).cos() - product * w;
b.quaternion.${i} *= w;
sum.quaternion.${i} = a.quaternion.${i} + b.quaternion.${i};
% endfor
}

Ok(sum)
}
}

impl From<MatrixDecomposed3D> for Matrix3D {
/// Recompose a 3D matrix.
/// <https://drafts.csswg.org/css-transforms/#recomposing-to-a-3d-matrix>
Expand Down

0 comments on commit 9a62c0b

Please sign in to comment.