1 // Copyright 2014 Google Inc. All rights reserved.
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 (function(scope, testing) {
16 var decomposeMatrix = (function() {
17 function determinant(m) {
18 return m[0][0] * m[1][1] * m[2][2] +
19 m[1][0] * m[2][1] * m[0][2] +
20 m[2][0] * m[0][1] * m[1][2] -
21 m[0][2] * m[1][1] * m[2][0] -
22 m[1][2] * m[2][1] * m[0][0] -
23 m[2][2] * m[0][1] * m[1][0];
28 // [A B]^-1 = [A^-1 + A^-1B(D - CA^-1B)^-1CA^-1 -A^-1B(D - CA^-1B)^-1]
29 // [C D] [-(D - CA^-1B)^-1CA^-1 (D - CA^-1B)^-1 ]
33 // [A [0]]^-1 = [A^-1 [0]]
34 // [C 1 ] [ -CA^-1 1 ]
36 var iDet = 1 / determinant(m);
37 var a = m[0][0], b = m[0][1], c = m[0][2];
38 var d = m[1][0], e = m[1][1], f = m[1][2];
39 var g = m[2][0], h = m[2][1], k = m[2][2];
41 [(e * k - f * h) * iDet, (c * h - b * k) * iDet,
42 (b * f - c * e) * iDet, 0],
43 [(f * g - d * k) * iDet, (a * k - c * g) * iDet,
44 (c * d - a * f) * iDet, 0],
45 [(d * h - e * g) * iDet, (g * b - a * h) * iDet,
46 (a * e - b * d) * iDet, 0]
49 for (var i = 0; i < 3; i++) {
51 for (var j = 0; j < 3; j++) {
52 val += m[3][j] * Ainv[j][i];
61 function transposeMatrix4(m) {
62 return [[m[0][0], m[1][0], m[2][0], m[3][0]],
63 [m[0][1], m[1][1], m[2][1], m[3][1]],
64 [m[0][2], m[1][2], m[2][2], m[3][2]],
65 [m[0][3], m[1][3], m[2][3], m[3][3]]];
68 function multVecMatrix(v, m) {
70 for (var i = 0; i < 4; i++) {
72 for (var j = 0; j < 4; j++) {
73 val += v[j] * m[j][i];
80 function normalize(v) {
82 return [v[0] / len, v[1] / len, v[2] / len];
86 return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
89 function combine(v1, v2, v1s, v2s) {
90 return [v1s * v1[0] + v2s * v2[0], v1s * v1[1] + v2s * v2[1],
91 v1s * v1[2] + v2s * v2[2]];
94 function cross(v1, v2) {
95 return [v1[1] * v2[2] - v1[2] * v2[1],
96 v1[2] * v2[0] - v1[0] * v2[2],
97 v1[0] * v2[1] - v1[1] * v2[0]];
100 // TODO: Implement 2D matrix decomposition.
101 // http://dev.w3.org/csswg/css-transforms/#decomposing-a-2d-matrix
102 function decomposeMatrix(matrix) {
110 // skip normalization step as m3d[3][3] should always be 1
111 if (m3d[3][3] !== 1) {
115 var perspectiveMatrix = [];
116 for (var i = 0; i < 4; i++) {
117 perspectiveMatrix.push(m3d[i].slice());
120 for (var i = 0; i < 3; i++) {
121 perspectiveMatrix[i][3] = 0;
124 if (determinant(perspectiveMatrix) === 0) {
131 if (m3d[0][3] || m3d[1][3] || m3d[2][3]) {
137 var inversePerspectiveMatrix = inverse(perspectiveMatrix);
138 var transposedInversePerspectiveMatrix =
139 transposeMatrix4(inversePerspectiveMatrix);
140 perspective = multVecMatrix(rhs, transposedInversePerspectiveMatrix);
142 perspective = [0, 0, 0, 1];
145 var translate = m3d[3].slice(0, 3);
148 row.push(m3d[0].slice(0, 3));
150 scale.push(length(row[0]));
151 row[0] = normalize(row[0]);
154 row.push(m3d[1].slice(0, 3));
155 skew.push(dot(row[0], row[1]));
156 row[1] = combine(row[1], row[0], 1.0, -skew[0]);
158 scale.push(length(row[1]));
159 row[1] = normalize(row[1]);
162 row.push(m3d[2].slice(0, 3));
163 skew.push(dot(row[0], row[2]));
164 row[2] = combine(row[2], row[0], 1.0, -skew[1]);
165 skew.push(dot(row[1], row[2]));
166 row[2] = combine(row[2], row[1], 1.0, -skew[2]);
168 scale.push(length(row[2]));
169 row[2] = normalize(row[2]);
173 var pdum3 = cross(row[1], row[2]);
174 if (dot(row[0], pdum3) < 0) {
175 for (var i = 0; i < 3; i++) {
183 var t = row[0][0] + row[1][1] + row[2][2] + 1;
188 s = 0.5 / Math.sqrt(t);
190 (row[2][1] - row[1][2]) * s,
191 (row[0][2] - row[2][0]) * s,
192 (row[1][0] - row[0][1]) * s,
195 } else if (row[0][0] > row[1][1] && row[0][0] > row[2][2]) {
196 s = Math.sqrt(1 + row[0][0] - row[1][1] - row[2][2]) * 2.0;
199 (row[0][1] + row[1][0]) / s,
200 (row[0][2] + row[2][0]) / s,
201 (row[2][1] - row[1][2]) / s
203 } else if (row[1][1] > row[2][2]) {
204 s = Math.sqrt(1.0 + row[1][1] - row[0][0] - row[2][2]) * 2.0;
206 (row[0][1] + row[1][0]) / s,
208 (row[1][2] + row[2][1]) / s,
209 (row[0][2] - row[2][0]) / s
212 s = Math.sqrt(1.0 + row[2][2] - row[0][0] - row[1][1]) * 2.0;
214 (row[0][2] + row[2][0]) / s,
215 (row[1][2] + row[2][1]) / s,
217 (row[1][0] - row[0][1]) / s
221 return [translate, scale, skew, quaternion, perspective];
223 return decomposeMatrix;
226 function dot(v1, v2) {
228 for (var i = 0; i < v1.length; i++) {
229 result += v1[i] * v2[i];
234 function multiplyMatrices(a, b) {
236 a[0] * b[0] + a[4] * b[1] + a[8] * b[2] + a[12] * b[3],
237 a[1] * b[0] + a[5] * b[1] + a[9] * b[2] + a[13] * b[3],
238 a[2] * b[0] + a[6] * b[1] + a[10] * b[2] + a[14] * b[3],
239 a[3] * b[0] + a[7] * b[1] + a[11] * b[2] + a[15] * b[3],
241 a[0] * b[4] + a[4] * b[5] + a[8] * b[6] + a[12] * b[7],
242 a[1] * b[4] + a[5] * b[5] + a[9] * b[6] + a[13] * b[7],
243 a[2] * b[4] + a[6] * b[5] + a[10] * b[6] + a[14] * b[7],
244 a[3] * b[4] + a[7] * b[5] + a[11] * b[6] + a[15] * b[7],
246 a[0] * b[8] + a[4] * b[9] + a[8] * b[10] + a[12] * b[11],
247 a[1] * b[8] + a[5] * b[9] + a[9] * b[10] + a[13] * b[11],
248 a[2] * b[8] + a[6] * b[9] + a[10] * b[10] + a[14] * b[11],
249 a[3] * b[8] + a[7] * b[9] + a[11] * b[10] + a[15] * b[11],
251 a[0] * b[12] + a[4] * b[13] + a[8] * b[14] + a[12] * b[15],
252 a[1] * b[12] + a[5] * b[13] + a[9] * b[14] + a[13] * b[15],
253 a[2] * b[12] + a[6] * b[13] + a[10] * b[14] + a[14] * b[15],
254 a[3] * b[12] + a[7] * b[13] + a[11] * b[14] + a[15] * b[15]
258 // TODO: This can probably be made smaller.
259 function convertItemToMatrix(item) {
261 // TODO: Handle units other than rads and degs.
263 var rads = item.d[0].rad || 0;
264 var degs = item.d[0].deg || 0;
265 var angle = (degs * Math.PI / 180) + rads;
267 0, Math.cos(angle), Math.sin(angle), 0,
268 0, -Math.sin(angle), Math.cos(angle), 0,
271 var rads = item.d[0].rad || 0;
272 var degs = item.d[0].deg || 0;
273 var angle = (degs * Math.PI / 180) + rads;
274 return [Math.cos(angle), 0, -Math.sin(angle), 0,
276 Math.sin(angle), 0, Math.cos(angle), 0,
280 var rads = item.d[0].rad || 0;
281 var degs = item.d[0].deg || 0;
282 var angle = (degs * Math.PI / 180) + rads;
283 return [Math.cos(angle), Math.sin(angle), 0, 0,
284 -Math.sin(angle), Math.cos(angle), 0, 0,
291 var rads = item.d[3].rad || 0;
292 var degs = item.d[3].deg || 0;
293 var angle = (degs * Math.PI / 180) + rads;
295 var sqrLength = x * x + y * y + z * z;
296 if (sqrLength === 0) {
300 } else if (sqrLength !== 1) {
301 var length = Math.sqrt(sqrLength);
307 var s = Math.sin(angle / 2);
308 var sc = s * Math.cos(angle / 2);
311 1 - 2 * (y * y + z * z) * sq,
312 2 * (x * y * sq + z * sc),
313 2 * (x * z * sq - y * sc),
316 2 * (x * y * sq - z * sc),
317 1 - 2 * (x * x + z * z) * sq,
318 2 * (y * z * sq + x * sc),
321 2 * (x * z * sq + y * sc),
322 2 * (y * z * sq - x * sc),
323 1 - 2 * (x * x + y * y) * sq,
329 return [item.d[0], 0, 0, 0,
334 return [item.d[0], 0, 0, 0,
349 return [item.d[0], 0, 0, 0,
353 // FIXME: Skew behaves differently in Blink, FireFox and here. Need to work out why.
355 var xDegs = item.d[0].deg || 0;
356 var xRads = item.d[0].rad || 0;
357 var yDegs = item.d[1].deg || 0;
358 var yRads = item.d[1].rad || 0;
359 var xAngle = (xDegs * Math.PI / 180) + xRads;
360 var yAngle = (yDegs * Math.PI / 180) + yRads;
361 return [1, Math.tan(yAngle), 0, 0,
362 Math.tan(xAngle), 1, 0, 0,
366 var rads = item.d[0].rad || 0;
367 var degs = item.d[0].deg || 0;
368 var angle = (degs * Math.PI / 180) + rads;
370 Math.tan(angle), 1, 0, 0,
374 var rads = item.d[0].rad || 0;
375 var degs = item.d[0].deg || 0;
376 var angle = (degs * Math.PI / 180) + rads;
377 return [1, Math.tan(angle), 0, 0,
381 // TODO: Work out what to do with non-px values.
383 var x = item.d[0].px || 0;
384 var y = item.d[1].px || 0;
390 var x = item.d[0].px || 0;
396 var y = item.d[0].px || 0;
402 var z = item.d[0].px || 0;
408 var x = item.d[0].px || 0;
409 var y = item.d[1].px || 0;
410 var z = item.d[2].px || 0;
416 var p = item.d[0].px ? (-1 / item.d[0].px) : 0;
423 return [item.d[0], item.d[1], 0, 0,
424 item.d[2], item.d[3], 0, 0,
426 item.d[4], item.d[5], 0, 1];
430 WEB_ANIMATIONS_TESTING && console.assert(false, 'Transform item type ' + item.t +
431 ' conversion to matrix not yet implemented.');
435 function convertToMatrix(transformList) {
436 if (transformList.length === 0) {
442 return transformList.map(convertItemToMatrix).reduce(multiplyMatrices);
445 function makeMatrixDecomposition(transformList) {
446 return [decomposeMatrix(convertToMatrix(transformList))];
450 scope.makeMatrixDecomposition = makeMatrixDecomposition;
452 })(webAnimations1, webAnimationsTesting);