OLD | NEW |
(Empty) | |
| 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 // TODO: Implement 2D matrix decomposition. |
| 101 // http://dev.w3.org/csswg/css-transforms/#decomposing-a-2d-matrix |
| 102 function decomposeMatrix(matrix) { |
| 103 var m3d = [ |
| 104 matrix.slice(0, 4), |
| 105 matrix.slice(4, 8), |
| 106 matrix.slice(8, 12), |
| 107 matrix.slice(12, 16) |
| 108 ]; |
| 109 |
| 110 // skip normalization step as m3d[3][3] should always be 1 |
| 111 if (m3d[3][3] !== 1) { |
| 112 return null; |
| 113 } |
| 114 |
| 115 var perspectiveMatrix = []; |
| 116 for (var i = 0; i < 4; i++) { |
| 117 perspectiveMatrix.push(m3d[i].slice()); |
| 118 } |
| 119 |
| 120 for (var i = 0; i < 3; i++) { |
| 121 perspectiveMatrix[i][3] = 0; |
| 122 } |
| 123 |
| 124 if (determinant(perspectiveMatrix) === 0) { |
| 125 return false; |
| 126 } |
| 127 |
| 128 var rhs = []; |
| 129 |
| 130 var perspective; |
| 131 if (m3d[0][3] || m3d[1][3] || m3d[2][3]) { |
| 132 rhs.push(m3d[0][3]); |
| 133 rhs.push(m3d[1][3]); |
| 134 rhs.push(m3d[2][3]); |
| 135 rhs.push(m3d[3][3]); |
| 136 |
| 137 var inversePerspectiveMatrix = inverse(perspectiveMatrix); |
| 138 var transposedInversePerspectiveMatrix = |
| 139 transposeMatrix4(inversePerspectiveMatrix); |
| 140 perspective = multVecMatrix(rhs, transposedInversePerspectiveMatrix); |
| 141 } else { |
| 142 perspective = [0, 0, 0, 1]; |
| 143 } |
| 144 |
| 145 var translate = m3d[3].slice(0, 3); |
| 146 |
| 147 var row = []; |
| 148 row.push(m3d[0].slice(0, 3)); |
| 149 var scale = []; |
| 150 scale.push(length(row[0])); |
| 151 row[0] = normalize(row[0]); |
| 152 |
| 153 var skew = []; |
| 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]); |
| 157 |
| 158 scale.push(length(row[1])); |
| 159 row[1] = normalize(row[1]); |
| 160 skew[0] /= scale[1]; |
| 161 |
| 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]); |
| 167 |
| 168 scale.push(length(row[2])); |
| 169 row[2] = normalize(row[2]); |
| 170 skew[1] /= scale[2]; |
| 171 skew[2] /= scale[2]; |
| 172 |
| 173 var pdum3 = cross(row[1], row[2]); |
| 174 if (dot(row[0], pdum3) < 0) { |
| 175 for (var i = 0; i < 3; i++) { |
| 176 scale[i] *= -1; |
| 177 row[i][0] *= -1; |
| 178 row[i][1] *= -1; |
| 179 row[i][2] *= -1; |
| 180 } |
| 181 } |
| 182 |
| 183 var t = row[0][0] + row[1][1] + row[2][2] + 1; |
| 184 var s; |
| 185 var quaternion; |
| 186 |
| 187 if (t > 1e-4) { |
| 188 s = 0.5 / Math.sqrt(t); |
| 189 quaternion = [ |
| 190 (row[2][1] - row[1][2]) * s, |
| 191 (row[0][2] - row[2][0]) * s, |
| 192 (row[1][0] - row[0][1]) * s, |
| 193 0.25 / s |
| 194 ]; |
| 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; |
| 197 quaternion = [ |
| 198 0.25 * s, |
| 199 (row[0][1] + row[1][0]) / s, |
| 200 (row[0][2] + row[2][0]) / s, |
| 201 (row[2][1] - row[1][2]) / s |
| 202 ]; |
| 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; |
| 205 quaternion = [ |
| 206 (row[0][1] + row[1][0]) / s, |
| 207 0.25 * s, |
| 208 (row[1][2] + row[2][1]) / s, |
| 209 (row[0][2] - row[2][0]) / s |
| 210 ]; |
| 211 } else { |
| 212 s = Math.sqrt(1.0 + row[2][2] - row[0][0] - row[1][1]) * 2.0; |
| 213 quaternion = [ |
| 214 (row[0][2] + row[2][0]) / s, |
| 215 (row[1][2] + row[2][1]) / s, |
| 216 0.25 * s, |
| 217 (row[1][0] - row[0][1]) / s |
| 218 ]; |
| 219 } |
| 220 |
| 221 return [translate, scale, skew, quaternion, perspective]; |
| 222 } |
| 223 return decomposeMatrix; |
| 224 })(); |
| 225 |
| 226 function dot(v1, v2) { |
| 227 var result = 0; |
| 228 for (var i = 0; i < v1.length; i++) { |
| 229 result += v1[i] * v2[i]; |
| 230 } |
| 231 return result; |
| 232 } |
| 233 |
| 234 function multiplyMatrices(a, b) { |
| 235 return [ |
| 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], |
| 240 |
| 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], |
| 245 |
| 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], |
| 250 |
| 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] |
| 255 ]; |
| 256 } |
| 257 |
| 258 // TODO: This can probably be made smaller. |
| 259 function convertItemToMatrix(item) { |
| 260 switch (item.t) { |
| 261 // TODO: Handle units other than rads and degs. |
| 262 case 'rotatex': |
| 263 var rads = item.d[0].rad || 0; |
| 264 var degs = item.d[0].deg || 0; |
| 265 var angle = (degs * Math.PI / 180) + rads; |
| 266 return [1, 0, 0, 0, |
| 267 0, Math.cos(angle), Math.sin(angle), 0, |
| 268 0, -Math.sin(angle), Math.cos(angle), 0, |
| 269 0, 0, 0, 1]; |
| 270 case 'rotatey': |
| 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, |
| 275 0, 1, 0, 0, |
| 276 Math.sin(angle), 0, Math.cos(angle), 0, |
| 277 0, 0, 0, 1]; |
| 278 case 'rotate': |
| 279 case 'rotatez': |
| 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, |
| 285 0, 0, 1, 0, |
| 286 0, 0, 0, 1]; |
| 287 case 'rotate3d': |
| 288 var x = item.d[0]; |
| 289 var y = item.d[1]; |
| 290 var z = item.d[2]; |
| 291 var rads = item.d[3].rad || 0; |
| 292 var degs = item.d[3].deg || 0; |
| 293 var angle = (degs * Math.PI / 180) + rads; |
| 294 |
| 295 var sqrLength = x * x + y * y + z * z; |
| 296 if (sqrLength === 0) { |
| 297 x = 1; |
| 298 y = 0; |
| 299 z = 0; |
| 300 } else if (sqrLength !== 1) { |
| 301 var length = Math.sqrt(sqrLength); |
| 302 x /= length; |
| 303 y /= length; |
| 304 z /= length; |
| 305 } |
| 306 |
| 307 var s = Math.sin(angle / 2); |
| 308 var sc = s * Math.cos(angle / 2); |
| 309 var sq = s * s; |
| 310 return [ |
| 311 1 - 2 * (y * y + z * z) * sq, |
| 312 2 * (x * y * sq + z * sc), |
| 313 2 * (x * z * sq - y * sc), |
| 314 0, |
| 315 |
| 316 2 * (x * y * sq - z * sc), |
| 317 1 - 2 * (x * x + z * z) * sq, |
| 318 2 * (y * z * sq + x * sc), |
| 319 0, |
| 320 |
| 321 2 * (x * z * sq + y * sc), |
| 322 2 * (y * z * sq - x * sc), |
| 323 1 - 2 * (x * x + y * y) * sq, |
| 324 0, |
| 325 |
| 326 0, 0, 0, 1 |
| 327 ]; |
| 328 case 'scale': |
| 329 return [item.d[0], 0, 0, 0, |
| 330 0, item.d[1], 0, 0, |
| 331 0, 0, 1, 0, |
| 332 0, 0, 0, 1]; |
| 333 case 'scalex': |
| 334 return [item.d[0], 0, 0, 0, |
| 335 0, 1, 0, 0, |
| 336 0, 0, 1, 0, |
| 337 0, 0, 0, 1]; |
| 338 case 'scaley': |
| 339 return [1, 0, 0, 0, |
| 340 0, item.d[0], 0, 0, |
| 341 0, 0, 1, 0, |
| 342 0, 0, 0, 1]; |
| 343 case 'scalez': |
| 344 return [1, 0, 0, 0, |
| 345 0, 1, 0, 0, |
| 346 0, 0, item.d[0], 0, |
| 347 0, 0, 0, 1]; |
| 348 case 'scale3d': |
| 349 return [item.d[0], 0, 0, 0, |
| 350 0, item.d[1], 0, 0, |
| 351 0, 0, item.d[2], 0, |
| 352 0, 0, 0, 1]; |
| 353 // FIXME: Skew behaves differently in Blink, FireFox and here. Need to wor
k out why. |
| 354 case 'skew': |
| 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, |
| 363 0, 0, 1, 0, |
| 364 0, 0, 0, 1]; |
| 365 case 'skewx': |
| 366 var rads = item.d[0].rad || 0; |
| 367 var degs = item.d[0].deg || 0; |
| 368 var angle = (degs * Math.PI / 180) + rads; |
| 369 return [1, 0, 0, 0, |
| 370 Math.tan(angle), 1, 0, 0, |
| 371 0, 0, 1, 0, |
| 372 0, 0, 0, 1]; |
| 373 case 'skewy': |
| 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, |
| 378 0, 1, 0, 0, |
| 379 0, 0, 1, 0, |
| 380 0, 0, 0, 1]; |
| 381 // TODO: Work out what to do with non-px values. |
| 382 case 'translate': |
| 383 var x = item.d[0].px || 0; |
| 384 var y = item.d[1].px || 0; |
| 385 return [1, 0, 0, 0, |
| 386 0, 1, 0, 0, |
| 387 0, 0, 1, 0, |
| 388 x, y, 0, 1]; |
| 389 case 'translatex': |
| 390 var x = item.d[0].px || 0; |
| 391 return [1, 0, 0, 0, |
| 392 0, 1, 0, 0, |
| 393 0, 0, 1, 0, |
| 394 x, 0, 0, 1]; |
| 395 case 'translatey': |
| 396 var y = item.d[0].px || 0; |
| 397 return [1, 0, 0, 0, |
| 398 0, 1, 0, 0, |
| 399 0, 0, 1, 0, |
| 400 0, y, 0, 1]; |
| 401 case 'translatez': |
| 402 var z = item.d[0].px || 0; |
| 403 return [1, 0, 0, 0, |
| 404 0, 1, 0, 0, |
| 405 0, 0, 1, 0, |
| 406 0, 0, z, 1]; |
| 407 case 'translate3d': |
| 408 var x = item.d[0].px || 0; |
| 409 var y = item.d[1].px || 0; |
| 410 var z = item.d[2].px || 0; |
| 411 return [1, 0, 0, 0, |
| 412 0, 1, 0, 0, |
| 413 0, 0, 1, 0, |
| 414 x, y, z, 1]; |
| 415 case 'perspective': |
| 416 var p = item.d[0].px ? (-1 / item.d[0].px) : 0; |
| 417 return [ |
| 418 1, 0, 0, 0, |
| 419 0, 1, 0, 0, |
| 420 0, 0, 1, p, |
| 421 0, 0, 0, 1]; |
| 422 case 'matrix': |
| 423 return [item.d[0], item.d[1], 0, 0, |
| 424 item.d[2], item.d[3], 0, 0, |
| 425 0, 0, 1, 0, |
| 426 item.d[4], item.d[5], 0, 1]; |
| 427 case 'matrix3d': |
| 428 return item.d; |
| 429 default: |
| 430 WEB_ANIMATIONS_TESTING && console.assert(false, 'Transform item type ' +
item.t + |
| 431 ' conversion to matrix not yet implemented.'); |
| 432 } |
| 433 } |
| 434 |
| 435 function convertToMatrix(transformList) { |
| 436 if (transformList.length === 0) { |
| 437 return [1, 0, 0, 0, |
| 438 0, 1, 0, 0, |
| 439 0, 0, 1, 0, |
| 440 0, 0, 0, 1]; |
| 441 } |
| 442 return transformList.map(convertItemToMatrix).reduce(multiplyMatrices); |
| 443 } |
| 444 |
| 445 function makeMatrixDecomposition(transformList) { |
| 446 return [decomposeMatrix(convertToMatrix(transformList))]; |
| 447 } |
| 448 |
| 449 scope.dot = dot; |
| 450 scope.makeMatrixDecomposition = makeMatrixDecomposition; |
| 451 |
| 452 })(webAnimationsMinifill, webAnimationsTesting); |
OLD | NEW |