Add decomposition and interpolation of 3D matrices

This commit is contained in:
Nazım Can Altınova 2016-09-11 03:33:27 +03:00
parent 5ee93b28cb
commit 63c329b69d

View file

@ -703,7 +703,6 @@ impl Interpolate for LengthOrNone {
match (from, to) {
(&TransformOperation::Matrix(from),
&TransformOperation::Matrix(_to)) => {
// TODO: It doesn't yet handle the case where one of the matrices are not 2D
let interpolated = from.interpolate(&_to, time).unwrap();
result.push(TransformOperation::Matrix(interpolated));
}
@ -769,9 +768,9 @@ impl Interpolate for LengthOrNone {
/// https://drafts.csswg.org/css-transforms/#Rotate3dDefined
fn rotate_to_matrix(x: f32, y: f32, z: f32, a: SpecifiedAngle) -> ComputedMatrix {
let rad = a.radians();
let sc = (rad / 2.0).sin() * (rad / 2.0).cos();
let sq = 1.0 / 2.0 * (1.0 - (rad).cos());
let half_rad = a.radians() / 2.0;
let sc = (half_rad).sin() * (half_rad).cos();
let sq = (half_rad).sin().powi(2);
ComputedMatrix {
m11: 1.0 - 2.0 * (y * y + z * z) * sq,
@ -787,8 +786,8 @@ impl Interpolate for LengthOrNone {
m31: 2.0 * (x * z * sq - y * sc),
m32: 2.0 * (y * z * sq + x * sc),
m33: 1.0 - 2.0 * (x * x + y * y) * sq,
m34: 0.0,
m41: 0.0,
m42: 0.0,
m43: 0.0,
@ -897,15 +896,30 @@ impl Interpolate for LengthOrNone {
impl Interpolate for ComputedMatrix {
fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> {
let decomposed_from = MatrixDecomposed2D::from(*self);
let decomposed_to = MatrixDecomposed2D::from(*other);
let interpolated = try!(decomposed_from.interpolate(&decomposed_to, time));
Ok(ComputedMatrix::from(interpolated))
if self.is_3d() || other.is_3d() {
let decomposed_from = decompose_3d_matrix(*self);
let decomposed_to = decompose_3d_matrix(*other);
match (decomposed_from, decomposed_to) {
(Ok(from), Ok(to)) => {
let interpolated = try!(from.interpolate(&to, time));
Ok(ComputedMatrix::from(interpolated))
},
_ => {
let interpolated = if time < 0.5 {*self} else {*other};
Ok(interpolated)
}
}
} else {
let decomposed_from = MatrixDecomposed2D::from(*self);
let decomposed_to = MatrixDecomposed2D::from(*other);
let interpolated = try!(decomposed_from.interpolate(&decomposed_to, time));
Ok(ComputedMatrix::from(interpolated))
}
}
}
impl From<ComputedMatrix> for MatrixDecomposed2D {
/// Decompose a matrix.
/// Decompose a 2D matrix.
/// https://drafts.csswg.org/css-transforms/#decomposing-a-2d-matrix
fn from(matrix: ComputedMatrix) -> MatrixDecomposed2D {
let mut row0x = matrix.m11;
@ -969,7 +983,7 @@ impl Interpolate for LengthOrNone {
}
impl From<MatrixDecomposed2D> for ComputedMatrix {
/// Recompose a matrix.
/// Recompose a 2D matrix.
/// https://drafts.csswg.org/css-transforms/#recomposing-to-a-2d-matrix
fn from(decomposed: MatrixDecomposed2D) -> ComputedMatrix {
let mut computed_matrix = ComputedMatrix::identity();
@ -979,10 +993,8 @@ impl Interpolate for LengthOrNone {
computed_matrix.m22 = decomposed.matrix.m22;
// Translate matrix.
computed_matrix.m41 = decomposed.translate.0 * decomposed.matrix.m11 +
decomposed.translate.1 * decomposed.matrix.m21;
computed_matrix.m42 = decomposed.translate.0 * decomposed.matrix.m12 +
decomposed.translate.1 * decomposed.matrix.m22;
computed_matrix.m41 = decomposed.translate.0;
computed_matrix.m42 = decomposed.translate.1;
// Rotate matrix.
let angle = decomposed.angle.to_radians();
@ -995,16 +1007,8 @@ impl Interpolate for LengthOrNone {
rotate_matrix.m21 = -sin_angle;
rotate_matrix.m22 = cos_angle;
let matrix_clone = computed_matrix;
// Multiplication of computed_matrix and rotate_matrix
% for i in range(1, 5):
% for j in range(1, 5):
computed_matrix.m${i}${j} = (matrix_clone.m${i}1 * rotate_matrix.m1${j}) +
(matrix_clone.m${i}2 * rotate_matrix.m2${j}) +
(matrix_clone.m${i}3 * rotate_matrix.m3${j}) +
(matrix_clone.m${i}4 * rotate_matrix.m4${j});
% endfor
% endfor
computed_matrix = multiply(rotate_matrix, computed_matrix);
// Scale matrix.
computed_matrix.m11 *= decomposed.scale.0;
@ -1015,6 +1019,482 @@ impl Interpolate for LengthOrNone {
}
}
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct Translate3D(f32, f32, f32);
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct Scale3D(f32, f32, f32);
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct Skew(f32, f32, f32);
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct Perspective(f32, f32, f32, f32);
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct Quaternion(f32, f32, f32, f32);
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct MatrixDecomposed3D {
pub translate: Translate3D,
pub scale: Scale3D,
pub skew: Skew,
pub perspective: Perspective,
pub quaternion: Quaternion,
}
/// Decompose a 3D matrix.
/// https://drafts.csswg.org/css-transforms/#decomposing-a-3d-matrix
fn decompose_3d_matrix(mut matrix: ComputedMatrix) -> Result<MatrixDecomposed3D, ()> {
// Normalize the matrix.
if matrix.m44 == 0.0 {
return Err(());
}
let scaling_factor = matrix.m44;
% for i in range(1, 5):
% for j in range(1, 5):
matrix.m${i}${j} /= scaling_factor;
% endfor
% endfor
// perspective_matrix is used to solve for perspective, but it also provides
// an easy way to test for singularity of the upper 3x3 component.
let mut perspective_matrix = matrix;
% for i in range(1, 4):
perspective_matrix.m${i}4 = 0.0;
% endfor
perspective_matrix.m44 = 1.0;
if perspective_matrix.determinant() == 0.0 {
return Err(());
}
// First, isolate perspective.
let perspective = if matrix.m14 != 0.0 || matrix.m24 != 0.0 || matrix.m34 != 0.0 {
let right_hand_side: [f32; 4] = [
matrix.m14,
matrix.m24,
matrix.m34,
matrix.m44
];
perspective_matrix = perspective_matrix.inverse().unwrap();
// Transpose perspective_matrix
perspective_matrix = ComputedMatrix {
% for i in range(1, 5):
% for j in range(1, 5):
m${i}${j}: perspective_matrix.m${j}${i},
% endfor
% endfor
};
// Multiply right_hand_side with perspective_matrix
let mut tmp: [f32; 4] = [0.0; 4];
% for i in range(1, 5):
tmp[${i - 1}] = (right_hand_side[0] * perspective_matrix.m1${i}) +
(right_hand_side[1] * perspective_matrix.m2${i}) +
(right_hand_side[2] * perspective_matrix.m3${i}) +
(right_hand_side[3] * perspective_matrix.m4${i});
% endfor
Perspective(tmp[0], tmp[1], tmp[2], tmp[3])
} else {
Perspective(0.0, 0.0, 0.0, 1.0)
};
// Next take care of translation
let translate = Translate3D (
matrix.m41,
matrix.m42,
matrix.m43
);
// Now get scale and shear. 'row' is a 3 element array of 3 component vectors
let mut row: [[f32; 3]; 3] = [[0.0; 3]; 3];
% for i in range(1, 4):
row[${i - 1}][0] = matrix.m${i}1;
row[${i - 1}][1] = matrix.m${i}2;
row[${i - 1}][2] = matrix.m${i}3;
% endfor
// Compute X scale factor and normalize first row.
let row0len = (row[0][0] * row[0][0] + row[0][1] * row[0][1] + row[0][2] * row[0][2]).sqrt();
let mut scale = Scale3D(row0len, 0.0, 0.0);
row[0] = [row[0][0] / row0len, row[0][1] / row0len, row[0][2] / row0len];
// Compute XY shear factor and make 2nd row orthogonal to 1st.
let mut skew = Skew(dot(row[0], row[1]), 0.0, 0.0);
row[1] = combine(row[1], row[0], 1.0, -skew.0);
// Now, compute Y scale and normalize 2nd row.
let row1len = (row[0][0] * row[0][0] + row[0][1] * row[0][1] + row[0][2] * row[0][2]).sqrt();
scale.1 = row1len;
row[1] = [row[1][0] / row1len, row[1][1] / row1len, row[1][2] / row1len];
skew.0 /= scale.1;
// Compute XZ and YZ shears, orthogonalize 3rd row
skew.1 = dot(row[0], row[2]);
row[2] = combine(row[2], row[0], 1.0, -skew.1);
skew.2 = dot(row[1], row[2]);
row[2] = combine(row[2], row[1], 1.0, -skew.2);
// Next, get Z scale and normalize 3rd row.
let row2len = (row[2][0] * row[2][0] + row[2][1] * row[2][1] + row[2][2] * row[2][2]).sqrt();
scale.2 = row2len;
row[2] = [row[2][0] / row2len, row[2][1] / row2len, row[2][2] / row2len];
skew.1 /= scale.2;
skew.2 /= scale.2;
// At this point, the matrix (in rows) is orthonormal.
// Check for a coordinate system flip. If the determinant
// is -1, then negate the matrix and the scaling factors.
let pdum3 = cross(row[1], row[2]);
if dot(row[0], pdum3) < 0.0 {
% for i in range(3):
scale.${i} *= -1.0;
row[${i}][0] *= -1.0;
row[${i}][1] *= -1.0;
row[${i}][2] *= -1.0;
% endfor
}
// Now, get the rotations out
let mut quaternion = Quaternion (
0.5 * ((1.0 + row[0][0] - row[1][1] - row[2][2]).max(0.0)).sqrt(),
0.5 * ((1.0 - row[0][0] + row[1][1] - row[2][2]).max(0.0)).sqrt(),
0.5 * ((1.0 - row[0][0] - row[1][1] + row[2][2]).max(0.0)).sqrt(),
0.5 * ((1.0 + row[0][0] + row[1][1] + row[2][2]).max(0.0)).sqrt()
);
if row[2][1] > row[1][2] {
quaternion.0 = -quaternion.0
}
if row[0][2] > row[2][0] {
quaternion.1 = -quaternion.1
}
if row[1][0] > row[0][1] {
quaternion.2 = -quaternion.2
}
Ok(MatrixDecomposed3D {
translate: translate,
scale: scale,
skew: skew,
perspective: perspective,
quaternion: quaternion
})
}
// Combine 2 point.
fn combine(a: [f32; 3], b: [f32; 3], ascl: f32, bscl: f32) -> [f32; 3] {
[
(ascl * a[0]) + (bscl * b[0]),
(ascl * a[1]) + (bscl * b[1]),
(ascl * a[2]) + (bscl * b[2])
]
}
// Dot product.
fn dot(a: [f32; 3], b: [f32; 3]) -> f32 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
// Cross product.
fn cross(row1: [f32; 3], row2: [f32; 3]) -> [f32; 3] {
[
row1[1] * row2[2] - row1[2] * row2[1],
row1[2] * row2[0] - row1[0] * row2[2],
row1[0] * row2[1] - row1[1] * row2[0]
]
}
impl Interpolate for Translate3D {
fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> {
Ok(Translate3D(
try!(self.0.interpolate(&other.0, time)),
try!(self.1.interpolate(&other.1, time)),
try!(self.2.interpolate(&other.2, time))
))
}
}
impl Interpolate for Scale3D {
fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> {
Ok(Scale3D(
try!(self.0.interpolate(&other.0, time)),
try!(self.1.interpolate(&other.1, time)),
try!(self.2.interpolate(&other.2, time))
))
}
}
impl Interpolate for Skew {
fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> {
Ok(Skew(
try!(self.0.interpolate(&other.0, time)),
try!(self.1.interpolate(&other.1, time)),
try!(self.2.interpolate(&other.2, time))
))
}
}
impl Interpolate for Perspective {
fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> {
Ok(Perspective(
try!(self.0.interpolate(&other.0, time)),
try!(self.1.interpolate(&other.1, time)),
try!(self.2.interpolate(&other.2, time)),
try!(self.3.interpolate(&other.3, time))
))
}
}
impl Interpolate for MatrixDecomposed3D {
/// https://drafts.csswg.org/css-transforms/#interpolation-of-decomposed-3d-matrix-values
fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> {
let mut interpolated = *self;
// Interpolate translate, scale, skew and perspective components.
interpolated.translate = try!(self.translate.interpolate(&other.translate, time));
interpolated.scale = try!(self.scale.interpolate(&other.scale, time));
interpolated.skew = try!(self.skew.interpolate(&other.skew, time));
interpolated.perspective = try!(self.perspective.interpolate(&other.perspective, time));
// Interpolate quaternions using spherical linear interpolation (Slerp).
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(interpolated);
}
let theta = product.acos();
let w = (time as f32 * 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} *= (time as f32 * theta).cos() - product * w;
b.quaternion.${i} *= w;
interpolated.quaternion.${i} = a.quaternion.${i} + b.quaternion.${i};
% endfor
Ok(interpolated)
}
}
impl From<MatrixDecomposed3D> for ComputedMatrix {
/// Recompose a 3D matrix.
/// https://drafts.csswg.org/css-transforms/#recomposing-to-a-3d-matrix
fn from(decomposed: MatrixDecomposed3D) -> ComputedMatrix {
let mut matrix = ComputedMatrix::identity();
// Apply perspective
% for i in range(1, 5):
matrix.m${i}4 = decomposed.perspective.${i - 1};
% endfor
// Apply translation
% for i in range(1, 4):
% for j in range(1, 4):
matrix.m4${i} += decomposed.translate.${j - 1} * matrix.m${j}${i};
% endfor
% endfor
// Apply rotation
let x = decomposed.quaternion.0;
let y = decomposed.quaternion.1;
let z = decomposed.quaternion.2;
let w = decomposed.quaternion.3;
// Construct a composite rotation matrix from the quaternion values
// rotationMatrix is a identity 4x4 matrix initially
let mut rotation_matrix = ComputedMatrix::identity();
rotation_matrix.m11 = 1.0 - 2.0 * (y * y + z * z);
rotation_matrix.m12 = 2.0 * (x * y + z * w);
rotation_matrix.m13 = 2.0 * (x * z - y * w);
rotation_matrix.m21 = 2.0 * (x * y - z * w);
rotation_matrix.m22 = 1.0 - 2.0 * (x * x + z * z);
rotation_matrix.m23 = 2.0 * (y * z + x * w);
rotation_matrix.m31 = 2.0 * (x * z + y * w);
rotation_matrix.m32 = 2.0 * (y * z - x * w);
rotation_matrix.m33 = 1.0 - 2.0 * (x * x + y * y);
matrix = multiply(rotation_matrix, matrix);
// Apply skew
let mut temp = ComputedMatrix::identity();
if decomposed.skew.2 != 0.0 {
temp.m32 = decomposed.skew.2;
matrix = multiply(matrix, temp);
}
if decomposed.skew.1 != 0.0 {
temp.m32 = 0.0;
temp.m31 = decomposed.skew.1;
matrix = multiply(matrix, temp);
}
if decomposed.skew.0 != 0.0 {
temp.m31 = 0.0;
temp.m21 = decomposed.skew.0;
matrix = multiply(matrix, temp);
}
// Apply scale
% for i in range(1, 4):
% for j in range(1, 4):
matrix.m${i}${j} *= decomposed.scale.${i - 1};
% endfor
% endfor
matrix
}
}
// Multiplication of two 4x4 matrices.
fn multiply(a: ComputedMatrix, b: ComputedMatrix) -> ComputedMatrix {
let mut a_clone = a;
% for i in range(1, 5):
% for j in range(1, 5):
a_clone.m${i}${j} = (a.m${i}1 * b.m1${j}) +
(a.m${i}2 * b.m2${j}) +
(a.m${i}3 * b.m3${j}) +
(a.m${i}4 * b.m4${j});
% endfor
% endfor
a_clone
}
impl ComputedMatrix {
fn is_3d(&self) -> bool {
self.m13 != 0.0 || self.m14 != 0.0 ||
self.m23 != 0.0 || self.m24 != 0.0 ||
self.m31 != 0.0 || self.m32 != 0.0 || self.m33 != 1.0 || self.m34 != 0.0 ||
self.m43 != 0.0 || self.m44 != 1.0
}
pub fn determinant(&self) -> CSSFloat {
self.m14 * self.m23 * self.m32 * self.m41 -
self.m13 * self.m24 * self.m32 * self.m41 -
self.m14 * self.m22 * self.m33 * self.m41 +
self.m12 * self.m24 * self.m33 * self.m41 +
self.m13 * self.m22 * self.m34 * self.m41 -
self.m12 * self.m23 * self.m34 * self.m41 -
self.m14 * self.m23 * self.m31 * self.m42 +
self.m13 * self.m24 * self.m31 * self.m42 +
self.m14 * self.m21 * self.m33 * self.m42 -
self.m11 * self.m24 * self.m33 * self.m42 -
self.m13 * self.m21 * self.m34 * self.m42 +
self.m11 * self.m23 * self.m34 * self.m42 +
self.m14 * self.m22 * self.m31 * self.m43 -
self.m12 * self.m24 * self.m31 * self.m43 -
self.m14 * self.m21 * self.m32 * self.m43 +
self.m11 * self.m24 * self.m32 * self.m43 +
self.m12 * self.m21 * self.m34 * self.m43 -
self.m11 * self.m22 * self.m34 * self.m43 -
self.m13 * self.m22 * self.m31 * self.m44 +
self.m12 * self.m23 * self.m31 * self.m44 +
self.m13 * self.m21 * self.m32 * self.m44 -
self.m11 * self.m23 * self.m32 * self.m44 -
self.m12 * self.m21 * self.m33 * self.m44 +
self.m11 * self.m22 * self.m33 * self.m44
}
fn inverse(&self) -> Option<ComputedMatrix> {
let mut det = self.determinant();
if det == 0.0 {
return None;
}
det = 1.0 / det;
let x = ComputedMatrix {
m11: det *
(self.m23*self.m34*self.m42 - self.m24*self.m33*self.m42 +
self.m24*self.m32*self.m43 - self.m22*self.m34*self.m43 -
self.m23*self.m32*self.m44 + self.m22*self.m33*self.m44),
m12: det *
(self.m14*self.m33*self.m42 - self.m13*self.m34*self.m42 -
self.m14*self.m32*self.m43 + self.m12*self.m34*self.m43 +
self.m13*self.m32*self.m44 - self.m12*self.m33*self.m44),
m13: det *
(self.m13*self.m24*self.m42 - self.m14*self.m23*self.m42 +
self.m14*self.m22*self.m43 - self.m12*self.m24*self.m43 -
self.m13*self.m22*self.m44 + self.m12*self.m23*self.m44),
m14: det *
(self.m14*self.m23*self.m32 - self.m13*self.m24*self.m32 -
self.m14*self.m22*self.m33 + self.m12*self.m24*self.m33 +
self.m13*self.m22*self.m34 - self.m12*self.m23*self.m34),
m21: det *
(self.m24*self.m33*self.m41 - self.m23*self.m34*self.m41 -
self.m24*self.m31*self.m43 + self.m21*self.m34*self.m43 +
self.m23*self.m31*self.m44 - self.m21*self.m33*self.m44),
m22: det *
(self.m13*self.m34*self.m41 - self.m14*self.m33*self.m41 +
self.m14*self.m31*self.m43 - self.m11*self.m34*self.m43 -
self.m13*self.m31*self.m44 + self.m11*self.m33*self.m44),
m23: det *
(self.m14*self.m23*self.m41 - self.m13*self.m24*self.m41 -
self.m14*self.m21*self.m43 + self.m11*self.m24*self.m43 +
self.m13*self.m21*self.m44 - self.m11*self.m23*self.m44),
m24: det *
(self.m13*self.m24*self.m31 - self.m14*self.m23*self.m31 +
self.m14*self.m21*self.m33 - self.m11*self.m24*self.m33 -
self.m13*self.m21*self.m34 + self.m11*self.m23*self.m34),
m31: det *
(self.m22*self.m34*self.m41 - self.m24*self.m32*self.m41 +
self.m24*self.m31*self.m42 - self.m21*self.m34*self.m42 -
self.m22*self.m31*self.m44 + self.m21*self.m32*self.m44),
m32: det *
(self.m14*self.m32*self.m41 - self.m12*self.m34*self.m41 -
self.m14*self.m31*self.m42 + self.m11*self.m34*self.m42 +
self.m12*self.m31*self.m44 - self.m11*self.m32*self.m44),
m33: det *
(self.m12*self.m24*self.m41 - self.m14*self.m22*self.m41 +
self.m14*self.m21*self.m42 - self.m11*self.m24*self.m42 -
self.m12*self.m21*self.m44 + self.m11*self.m22*self.m44),
m34: det *
(self.m14*self.m22*self.m31 - self.m12*self.m24*self.m31 -
self.m14*self.m21*self.m32 + self.m11*self.m24*self.m32 +
self.m12*self.m21*self.m34 - self.m11*self.m22*self.m34),
m41: det *
(self.m23*self.m32*self.m41 - self.m22*self.m33*self.m41 -
self.m23*self.m31*self.m42 + self.m21*self.m33*self.m42 +
self.m22*self.m31*self.m43 - self.m21*self.m32*self.m43),
m42: det *
(self.m12*self.m33*self.m41 - self.m13*self.m32*self.m41 +
self.m13*self.m31*self.m42 - self.m11*self.m33*self.m42 -
self.m12*self.m31*self.m43 + self.m11*self.m32*self.m43),
m43: det *
(self.m13*self.m22*self.m41 - self.m12*self.m23*self.m41 -
self.m13*self.m21*self.m42 + self.m11*self.m23*self.m42 +
self.m12*self.m21*self.m43 - self.m11*self.m22*self.m43),
m44: det *
(self.m12*self.m23*self.m31 - self.m13*self.m22*self.m31 +
self.m13*self.m21*self.m32 - self.m11*self.m23*self.m32 -
self.m12*self.m21*self.m33 + self.m11*self.m22*self.m33),
};
Some(x)
}
}
/// https://drafts.csswg.org/css-transforms/#interpolation-of-transforms
impl Interpolate for TransformList {
#[inline]