diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index d15e4d28296..384e5436976 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -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 { - 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 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 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 { + // 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 { + 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 { + 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 { + 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 { + 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 { + 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 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 { + 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]