UNPKG

13.2 kBJavaScriptView Raw
1// Copyright 2014 Google Inc. All rights reserved.
2//
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
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
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.
14
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];
24 }
25
26 // from Wikipedia:
27 //
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 ]
30 //
31 // Therefore
32 //
33 // [A [0]]^-1 = [A^-1 [0]]
34 // [C 1 ] [ -CA^-1 1 ]
35 function inverse(m) {
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];
40 var Ainv = [
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]
47 ];
48 var lastRow = [];
49 for (var i = 0; i < 3; i++) {
50 var val = 0;
51 for (var j = 0; j < 3; j++) {
52 val += m[3][j] * Ainv[j][i];
53 }
54 lastRow.push(val);
55 }
56 lastRow.push(1);
57 Ainv.push(lastRow);
58 return Ainv;
59 }
60
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]]];
66 }
67
68 function multVecMatrix(v, m) {
69 var result = [];
70 for (var i = 0; i < 4; i++) {
71 var val = 0;
72 for (var j = 0; j < 4; j++) {
73 val += v[j] * m[j][i];
74 }
75 result.push(val);
76 }
77 return result;
78 }
79
80 function normalize(v) {
81 var len = length(v);
82 return [v[0] / len, v[1] / len, v[2] / len];
83 }
84
85 function length(v) {
86 return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
87 }
88
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]];
92 }
93
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]];
98 }
99
100 function decomposeMatrix(matrix) {
101 var m3d = [
102 matrix.slice(0, 4),
103 matrix.slice(4, 8),
104 matrix.slice(8, 12),
105 matrix.slice(12, 16)
106 ];
107
108 // skip normalization step as m3d[3][3] should always be 1
109 if (m3d[3][3] !== 1) {
110 return null;
111 }
112
113 var perspectiveMatrix = [];
114 for (var i = 0; i < 4; i++) {
115 perspectiveMatrix.push(m3d[i].slice());
116 }
117
118 for (var i = 0; i < 3; i++) {
119 perspectiveMatrix[i][3] = 0;
120 }
121
122 if (determinant(perspectiveMatrix) === 0) {
123 return null;
124 }
125
126 var rhs = [];
127
128 var perspective;
129 if (m3d[0][3] || m3d[1][3] || m3d[2][3]) {
130 rhs.push(m3d[0][3]);
131 rhs.push(m3d[1][3]);
132 rhs.push(m3d[2][3]);
133 rhs.push(m3d[3][3]);
134
135 var inversePerspectiveMatrix = inverse(perspectiveMatrix);
136 var transposedInversePerspectiveMatrix =
137 transposeMatrix4(inversePerspectiveMatrix);
138 perspective = multVecMatrix(rhs, transposedInversePerspectiveMatrix);
139 } else {
140 perspective = [0, 0, 0, 1];
141 }
142
143 var translate = m3d[3].slice(0, 3);
144
145 var row = [];
146 row.push(m3d[0].slice(0, 3));
147 var scale = [];
148 scale.push(length(row[0]));
149 row[0] = normalize(row[0]);
150
151 var skew = [];
152 row.push(m3d[1].slice(0, 3));
153 skew.push(dot(row[0], row[1]));
154 row[1] = combine(row[1], row[0], 1.0, -skew[0]);
155
156 scale.push(length(row[1]));
157 row[1] = normalize(row[1]);
158 skew[0] /= scale[1];
159
160 row.push(m3d[2].slice(0, 3));
161 skew.push(dot(row[0], row[2]));
162 row[2] = combine(row[2], row[0], 1.0, -skew[1]);
163 skew.push(dot(row[1], row[2]));
164 row[2] = combine(row[2], row[1], 1.0, -skew[2]);
165
166 scale.push(length(row[2]));
167 row[2] = normalize(row[2]);
168 skew[1] /= scale[2];
169 skew[2] /= scale[2];
170
171 var pdum3 = cross(row[1], row[2]);
172 if (dot(row[0], pdum3) < 0) {
173 for (var i = 0; i < 3; i++) {
174 scale[i] *= -1;
175 row[i][0] *= -1;
176 row[i][1] *= -1;
177 row[i][2] *= -1;
178 }
179 }
180
181 var t = row[0][0] + row[1][1] + row[2][2] + 1;
182 var s;
183 var quaternion;
184
185 if (t > 1e-4) {
186 s = 0.5 / Math.sqrt(t);
187 quaternion = [
188 (row[2][1] - row[1][2]) * s,
189 (row[0][2] - row[2][0]) * s,
190 (row[1][0] - row[0][1]) * s,
191 0.25 / s
192 ];
193 } else if (row[0][0] > row[1][1] && row[0][0] > row[2][2]) {
194 s = Math.sqrt(1 + row[0][0] - row[1][1] - row[2][2]) * 2.0;
195 quaternion = [
196 0.25 * s,
197 (row[0][1] + row[1][0]) / s,
198 (row[0][2] + row[2][0]) / s,
199 (row[2][1] - row[1][2]) / s
200 ];
201 } else if (row[1][1] > row[2][2]) {
202 s = Math.sqrt(1.0 + row[1][1] - row[0][0] - row[2][2]) * 2.0;
203 quaternion = [
204 (row[0][1] + row[1][0]) / s,
205 0.25 * s,
206 (row[1][2] + row[2][1]) / s,
207 (row[0][2] - row[2][0]) / s
208 ];
209 } else {
210 s = Math.sqrt(1.0 + row[2][2] - row[0][0] - row[1][1]) * 2.0;
211 quaternion = [
212 (row[0][2] + row[2][0]) / s,
213 (row[1][2] + row[2][1]) / s,
214 0.25 * s,
215 (row[1][0] - row[0][1]) / s
216 ];
217 }
218
219 return [translate, scale, skew, quaternion, perspective];
220 }
221 return decomposeMatrix;
222 })();
223
224 function dot(v1, v2) {
225 var result = 0;
226 for (var i = 0; i < v1.length; i++) {
227 result += v1[i] * v2[i];
228 }
229 return result;
230 }
231
232 function multiplyMatrices(a, b) {
233 return [
234 a[0] * b[0] + a[4] * b[1] + a[8] * b[2] + a[12] * b[3],
235 a[1] * b[0] + a[5] * b[1] + a[9] * b[2] + a[13] * b[3],
236 a[2] * b[0] + a[6] * b[1] + a[10] * b[2] + a[14] * b[3],
237 a[3] * b[0] + a[7] * b[1] + a[11] * b[2] + a[15] * b[3],
238
239 a[0] * b[4] + a[4] * b[5] + a[8] * b[6] + a[12] * b[7],
240 a[1] * b[4] + a[5] * b[5] + a[9] * b[6] + a[13] * b[7],
241 a[2] * b[4] + a[6] * b[5] + a[10] * b[6] + a[14] * b[7],
242 a[3] * b[4] + a[7] * b[5] + a[11] * b[6] + a[15] * b[7],
243
244 a[0] * b[8] + a[4] * b[9] + a[8] * b[10] + a[12] * b[11],
245 a[1] * b[8] + a[5] * b[9] + a[9] * b[10] + a[13] * b[11],
246 a[2] * b[8] + a[6] * b[9] + a[10] * b[10] + a[14] * b[11],
247 a[3] * b[8] + a[7] * b[9] + a[11] * b[10] + a[15] * b[11],
248
249 a[0] * b[12] + a[4] * b[13] + a[8] * b[14] + a[12] * b[15],
250 a[1] * b[12] + a[5] * b[13] + a[9] * b[14] + a[13] * b[15],
251 a[2] * b[12] + a[6] * b[13] + a[10] * b[14] + a[14] * b[15],
252 a[3] * b[12] + a[7] * b[13] + a[11] * b[14] + a[15] * b[15]
253 ];
254 }
255
256 function toRadians(arg) {
257 var rads = arg.rad || 0;
258 var degs = arg.deg || 0;
259 var grads = arg.grad || 0;
260 var turns = arg.turn || 0;
261 var angle = (degs / 360 + grads / 400 + turns) * (2 * Math.PI) + rads;
262 return angle;
263 }
264
265 function convertItemToMatrix(item) {
266 switch (item.t) {
267 case 'rotatex':
268 var angle = toRadians(item.d[0]);
269 return [1, 0, 0, 0,
270 0, Math.cos(angle), Math.sin(angle), 0,
271 0, -Math.sin(angle), Math.cos(angle), 0,
272 0, 0, 0, 1];
273 case 'rotatey':
274 var angle = toRadians(item.d[0]);
275 return [Math.cos(angle), 0, -Math.sin(angle), 0,
276 0, 1, 0, 0,
277 Math.sin(angle), 0, Math.cos(angle), 0,
278 0, 0, 0, 1];
279 case 'rotate':
280 case 'rotatez':
281 var angle = toRadians(item.d[0]);
282 return [Math.cos(angle), Math.sin(angle), 0, 0,
283 -Math.sin(angle), Math.cos(angle), 0, 0,
284 0, 0, 1, 0,
285 0, 0, 0, 1];
286 case 'rotate3d':
287 var x = item.d[0];
288 var y = item.d[1];
289 var z = item.d[2];
290 var angle = toRadians(item.d[3]);
291
292 var sqrLength = x * x + y * y + z * z;
293 if (sqrLength === 0) {
294 x = 1;
295 y = 0;
296 z = 0;
297 } else if (sqrLength !== 1) {
298 var length = Math.sqrt(sqrLength);
299 x /= length;
300 y /= length;
301 z /= length;
302 }
303
304 var s = Math.sin(angle / 2);
305 var sc = s * Math.cos(angle / 2);
306 var sq = s * s;
307 return [
308 1 - 2 * (y * y + z * z) * sq,
309 2 * (x * y * sq + z * sc),
310 2 * (x * z * sq - y * sc),
311 0,
312
313 2 * (x * y * sq - z * sc),
314 1 - 2 * (x * x + z * z) * sq,
315 2 * (y * z * sq + x * sc),
316 0,
317
318 2 * (x * z * sq + y * sc),
319 2 * (y * z * sq - x * sc),
320 1 - 2 * (x * x + y * y) * sq,
321 0,
322
323 0, 0, 0, 1
324 ];
325 case 'scale':
326 return [item.d[0], 0, 0, 0,
327 0, item.d[1], 0, 0,
328 0, 0, 1, 0,
329 0, 0, 0, 1];
330 case 'scalex':
331 return [item.d[0], 0, 0, 0,
332 0, 1, 0, 0,
333 0, 0, 1, 0,
334 0, 0, 0, 1];
335 case 'scaley':
336 return [1, 0, 0, 0,
337 0, item.d[0], 0, 0,
338 0, 0, 1, 0,
339 0, 0, 0, 1];
340 case 'scalez':
341 return [1, 0, 0, 0,
342 0, 1, 0, 0,
343 0, 0, item.d[0], 0,
344 0, 0, 0, 1];
345 case 'scale3d':
346 return [item.d[0], 0, 0, 0,
347 0, item.d[1], 0, 0,
348 0, 0, item.d[2], 0,
349 0, 0, 0, 1];
350 case 'skew':
351 var xAngle = toRadians(item.d[0]);
352 var yAngle = toRadians(item.d[1]);
353 return [1, Math.tan(yAngle), 0, 0,
354 Math.tan(xAngle), 1, 0, 0,
355 0, 0, 1, 0,
356 0, 0, 0, 1];
357 case 'skewx':
358 var angle = toRadians(item.d[0]);
359 return [1, 0, 0, 0,
360 Math.tan(angle), 1, 0, 0,
361 0, 0, 1, 0,
362 0, 0, 0, 1];
363 case 'skewy':
364 var angle = toRadians(item.d[0]);
365 return [1, Math.tan(angle), 0, 0,
366 0, 1, 0, 0,
367 0, 0, 1, 0,
368 0, 0, 0, 1];
369 case 'translate':
370 var x = item.d[0].px || 0;
371 var y = item.d[1].px || 0;
372 return [1, 0, 0, 0,
373 0, 1, 0, 0,
374 0, 0, 1, 0,
375 x, y, 0, 1];
376 case 'translatex':
377 var x = item.d[0].px || 0;
378 return [1, 0, 0, 0,
379 0, 1, 0, 0,
380 0, 0, 1, 0,
381 x, 0, 0, 1];
382 case 'translatey':
383 var y = item.d[0].px || 0;
384 return [1, 0, 0, 0,
385 0, 1, 0, 0,
386 0, 0, 1, 0,
387 0, y, 0, 1];
388 case 'translatez':
389 var z = item.d[0].px || 0;
390 return [1, 0, 0, 0,
391 0, 1, 0, 0,
392 0, 0, 1, 0,
393 0, 0, z, 1];
394 case 'translate3d':
395 var x = item.d[0].px || 0;
396 var y = item.d[1].px || 0;
397 var z = item.d[2].px || 0;
398 return [1, 0, 0, 0,
399 0, 1, 0, 0,
400 0, 0, 1, 0,
401 x, y, z, 1];
402 case 'perspective':
403 var p = item.d[0].px ? (-1 / item.d[0].px) : 0;
404 return [
405 1, 0, 0, 0,
406 0, 1, 0, 0,
407 0, 0, 1, p,
408 0, 0, 0, 1];
409 case 'matrix':
410 return [item.d[0], item.d[1], 0, 0,
411 item.d[2], item.d[3], 0, 0,
412 0, 0, 1, 0,
413 item.d[4], item.d[5], 0, 1];
414 case 'matrix3d':
415 return item.d;
416 default:
417 WEB_ANIMATIONS_TESTING && console.assert(false, 'Transform item type ' + item.t +
418 ' conversion to matrix not yet implemented.');
419 }
420 }
421
422 function convertToMatrix(transformList) {
423 if (transformList.length === 0) {
424 return [1, 0, 0, 0,
425 0, 1, 0, 0,
426 0, 0, 1, 0,
427 0, 0, 0, 1];
428 }
429 return transformList.map(convertItemToMatrix).reduce(multiplyMatrices);
430 }
431
432 function makeMatrixDecomposition(transformList) {
433 return [decomposeMatrix(convertToMatrix(transformList))];
434 }
435
436 scope.dot = dot;
437 scope.makeMatrixDecomposition = makeMatrixDecomposition;
438 scope.transformListToMatrix = convertToMatrix;
439
440})(webAnimations1, webAnimationsTesting);