[css3-2d-transforms] matrix decomposition (for animation)

http://dev.w3.org/csswg/css3-2d-transforms/#matrix-decomposition has
the pseudo-code:
  #  // 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.
  #  pdum3 = cross(row[1], row[2])
  #  if (dot(row[0], pdum3) < 0)
  #      for (i = 0; i < 3; i++) {
  #          scale[0] *= -1;
  #          row[i][0] *= -1
  #          row[i][1] *= -1
  #          row[i][2] *= -1
Comparing to the original code in unmatrix.c, I think scale[0]
should be scale[i].

However, that still leaves the question about what to do for this
code for 2D transforms.  In this case, the full 3D decomposition
(when given 0 0 1 0 for the third row of the matrix) produces a
scaleZ(-1) and a rotateX(180deg) in addition to the terms relevant
to 2D.  These two are equivalent to a scaleX(-1).  In the 3D
decomposition, the rotateX comes before the skewX, and therefore
changes its sign.  Or something like that.  I haven't quite worked
it out to my satisfaction.

But in the end, I think there are two possibilities for what to do
in the 2-D algorithm when the determinant is negative (I've tested
these):
 (a) Invert scaleY and XYshear.
 (b) Invert scaleX, XYshear, A, B, C, and D. (so that the rotation,
     computed from A and B, ends up in the opposite quadrant).
These appear to be the only options that produce correct
decompositions for the intermediate stages of the transitions in
http://dbaron.org/css/test/2010/transition-negative-determinant .
And I'm not sure if either is really compatible with what 3D does,
but I haven't yet worked that out.  (At some point I could test
which of the two behaviors is compatible with what Safari does.)

In any case, that gives a 2D decomposition algorithm that looks
something like:
 # For CSS 2D transforms, we have a 2D matrix with the bottom row constant:
 #
 # [ A C E ]
 # [ B D F ]
 # [ 0 0 1 ]
 #
 # For that case, I believe the algorithm in unmatrix reduces to:
 #
 #  (1) If A * D - B * C == 0, the matrix is singular.  Fail.
 #
 #  (2) Set translation components (Tx and Ty) to the translation parts of
 #      the matrix (E and F) and then ignore them for the rest of the time.
 #      (For us, E and F each actually consist of three constants:  a
 #      length, a multiplier for the width, and a multiplier for the
 #      height.  This actually requires its own decomposition, but I'll
 #      keep that separate.)
 #
 #  (3) Let the X scale factor (Sx) be sqrt(A^2 + B^2).  Then divide both A
 #      and B by it.
 #
 #  (4) Let the XY shear (K) be A * C + B * D.  From C, subtract A times
 #      the XY shear.  From D, subtract B times the XY shear.
 #
 #  (5) Let the Y scale (Sy) be sqrt(C^2 + D^2).  Divide C, D, and the XY
 #      shear (K) by it.
 #
 #  (6a) At this point, A * D - B * C is either 1 or -1.  If it is -1,
 #       negate the XY shear (K) and the Y scale (Sy).
 #   (or (6b) from above)
 #
 #  (7) Let the rotation be R = atan2(B, A).
 #
 # Then the resulting decomposed transformation is:
 #
 #   translate(Tx, Ty) rotate(R) skewX(atan(K)) scale(Sx, Sy)

(I wonder also if it might be nice to use a decomposition that would
be more likely to produce a scale(-1) than a rotate(180deg); this
could be done by multiplying scaleX by the sign of A right after
computing it, and likewise scaleY by the sign of D.  But I'm not
sure how to extend this idea into 3D, and it would be an
incompatible change to the current algorithm.)

-David

-- 
L. David Baron                                 http://dbaron.org/
Mozilla Corporation                       http://www.mozilla.com/

Received on Friday, 25 June 2010 06:19:21 UTC