1 | /*! @license Rematrix v0.2.1
|
2 |
|
3 | Copyright 2017 Fisssion LLC.
|
4 |
|
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 | of this software and associated documentation files (the "Software"), to deal
|
7 | in the Software without restriction, including without limitation the rights
|
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 | copies of the Software, and to permit persons to whom the Software is
|
10 | furnished to do so, subject to the following conditions:
|
11 |
|
12 | The above copyright notice and this permission notice shall be included in
|
13 | all copies or substantial portions of the Software.
|
14 |
|
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21 | THE SOFTWARE.
|
22 | */
|
23 | /**
|
24 | * @module Rematrix
|
25 | */
|
26 |
|
27 | /**
|
28 | * Transformation matrices in the browser come in two flavors:
|
29 | *
|
30 | * - `matrix` using 6 values (short)
|
31 | * - `matrix3d` using 16 values (long)
|
32 | *
|
33 | * This utility follows this [conversion guide](https://goo.gl/EJlUQ1)
|
34 | * to expand short form matrices to their equivalent long form.
|
35 | *
|
36 | * @param {array} source - Accepts both short and long form matrices.
|
37 | * @return {array}
|
38 | */
|
39 | function format (source) {
|
40 | if (source.constructor !== Array) {
|
41 | throw new TypeError('Expected array.')
|
42 | }
|
43 | if (source.length === 16) {
|
44 | return source
|
45 | }
|
46 | if (source.length === 6) {
|
47 | var matrix = identity();
|
48 | matrix[0] = source[0];
|
49 | matrix[1] = source[1];
|
50 | matrix[4] = source[2];
|
51 | matrix[5] = source[3];
|
52 | matrix[12] = source[4];
|
53 | matrix[13] = source[5];
|
54 | return matrix
|
55 | }
|
56 | throw new RangeError('Expected array with either 6 or 16 values.')
|
57 | }
|
58 |
|
59 | /**
|
60 | * Returns a matrix representing no transformation. The product of any matrix
|
61 | * multiplied by the identity matrix will be the original matrix.
|
62 | *
|
63 | * > **Tip:** Similar to how `5 * 1 === 5`, where `1` is the identity.
|
64 | *
|
65 | * @return {array}
|
66 | */
|
67 | function identity () {
|
68 | var matrix = [];
|
69 | for (var i = 0; i < 16; i++) {
|
70 | i % 5 == 0 ? matrix.push(1) : matrix.push(0);
|
71 | }
|
72 | return matrix
|
73 | }
|
74 |
|
75 | /**
|
76 | * Returns a matrix describing the inverse transformation of the source
|
77 | * matrix. The product of any matrix multiplied by its inverse will be the
|
78 | * identity matrix.
|
79 | *
|
80 | * > **Tip:** Similar to how `5 * (1/5) === 1`, where `1/5` is the inverse.
|
81 | *
|
82 | * @param {array} source - Accepts both short and long form matrices.
|
83 | * @return {array}
|
84 | */
|
85 | function inverse (source) {
|
86 | var m = format(source);
|
87 |
|
88 | var s0 = m[0] * m[5] - m[4] * m[1];
|
89 | var s1 = m[0] * m[6] - m[4] * m[2];
|
90 | var s2 = m[0] * m[7] - m[4] * m[3];
|
91 | var s3 = m[1] * m[6] - m[5] * m[2];
|
92 | var s4 = m[1] * m[7] - m[5] * m[3];
|
93 | var s5 = m[2] * m[7] - m[6] * m[3];
|
94 |
|
95 | var c5 = m[10] * m[15] - m[14] * m[11];
|
96 | var c4 = m[9] * m[15] - m[13] * m[11];
|
97 | var c3 = m[9] * m[14] - m[13] * m[10];
|
98 | var c2 = m[8] * m[15] - m[12] * m[11];
|
99 | var c1 = m[8] * m[14] - m[12] * m[10];
|
100 | var c0 = m[8] * m[13] - m[12] * m[9];
|
101 |
|
102 | var determinant = 1 / (s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0);
|
103 |
|
104 | if (isNaN(determinant) || determinant === Infinity) {
|
105 | throw new Error('Inverse determinant attempted to divide by zero.')
|
106 | }
|
107 |
|
108 | return [
|
109 | (m[5] * c5 - m[6] * c4 + m[7] * c3) * determinant,
|
110 | (-m[1] * c5 + m[2] * c4 - m[3] * c3) * determinant,
|
111 | (m[13] * s5 - m[14] * s4 + m[15] * s3) * determinant,
|
112 | (-m[9] * s5 + m[10] * s4 - m[11] * s3) * determinant,
|
113 |
|
114 | (-m[4] * c5 + m[6] * c2 - m[7] * c1) * determinant,
|
115 | (m[0] * c5 - m[2] * c2 + m[3] * c1) * determinant,
|
116 | (-m[12] * s5 + m[14] * s2 - m[15] * s1) * determinant,
|
117 | (m[8] * s5 - m[10] * s2 + m[11] * s1) * determinant,
|
118 |
|
119 | (m[4] * c4 - m[5] * c2 + m[7] * c0) * determinant,
|
120 | (-m[0] * c4 + m[1] * c2 - m[3] * c0) * determinant,
|
121 | (m[12] * s4 - m[13] * s2 + m[15] * s0) * determinant,
|
122 | (-m[8] * s4 + m[9] * s2 - m[11] * s0) * determinant,
|
123 |
|
124 | (-m[4] * c3 + m[5] * c1 - m[6] * c0) * determinant,
|
125 | (m[0] * c3 - m[1] * c1 + m[2] * c0) * determinant,
|
126 | (-m[12] * s3 + m[13] * s1 - m[14] * s0) * determinant,
|
127 | (m[8] * s3 - m[9] * s1 + m[10] * s0) * determinant ]
|
128 | }
|
129 |
|
130 | /**
|
131 | * Returns a 4x4 matrix describing the combined transformations
|
132 | * of both arguments.
|
133 | *
|
134 | * > **Note:** Order is very important. For example, rotating 45°
|
135 | * along the Z-axis, followed by translating 500 pixels along the
|
136 | * Y-axis... is not the same as translating 500 pixels along the
|
137 | * Y-axis, followed by rotating 45° along on the Z-axis.
|
138 | *
|
139 | * @param {array} m - Accepts both short and long form matrices.
|
140 | * @param {array} x - Accepts both short and long form matrices.
|
141 | * @return {array}
|
142 | */
|
143 | function multiply (m, x) {
|
144 | var fm = format(m);
|
145 | var fx = format(x);
|
146 | var product = [];
|
147 |
|
148 | for (var i = 0; i < 4; i++) {
|
149 | var row = [fm[i], fm[i + 4], fm[i + 8], fm[i + 12]];
|
150 | for (var j = 0; j < 4; j++) {
|
151 | var k = j * 4;
|
152 | var col = [fx[k], fx[k + 1], fx[k + 2], fx[k + 3]];
|
153 | var result = row[0] * col[0] + row[1] * col[1] + row[2] * col[2] + row[3] * col[3];
|
154 |
|
155 | product[i + k] = result;
|
156 | }
|
157 | }
|
158 |
|
159 | return product
|
160 | }
|
161 |
|
162 | /**
|
163 | * Attempts to return a 4x4 matrix describing the CSS transform
|
164 | * matrix passed in, but will return the identity matrix as a
|
165 | * fallback.
|
166 | *
|
167 | * **Tip:** In virtually all cases, this method is used to convert
|
168 | * a CSS matrix (retrieved as a `string` from computed styles) to
|
169 | * its equivalent array format.
|
170 | *
|
171 | * @param {string} source - String containing a valid CSS `matrix` or `matrix3d` property.
|
172 | * @return {array}
|
173 | */
|
174 | function parse (source) {
|
175 | if (typeof source === 'string') {
|
176 | var match = source.match(/matrix(3d)?\(([^)]+)\)/);
|
177 | if (match) {
|
178 | var raw = match[2].split(', ').map(parseFloat);
|
179 | return format(raw)
|
180 | }
|
181 | }
|
182 | return identity()
|
183 | }
|
184 |
|
185 | /**
|
186 | * Returns a 4x4 matrix describing Z-axis rotation.
|
187 | *
|
188 | * @param {number} angle - Measured in degrees.
|
189 | * @return {array}
|
190 | */
|
191 | function rotate (angle) {
|
192 | return rotateZ(angle)
|
193 | }
|
194 |
|
195 | /**
|
196 | * Returns a 4x4 matrix describing X-axis rotation.
|
197 | *
|
198 | * @param {number} angle - Measured in degrees.
|
199 | * @return {array}
|
200 | */
|
201 | function rotateX (angle) {
|
202 | var theta = Math.PI / 180 * angle;
|
203 | var matrix = identity();
|
204 |
|
205 | matrix[5] = matrix[10] = Math.cos(theta);
|
206 | matrix[6] = matrix[9] = Math.sin(theta);
|
207 | matrix[9] *= -1;
|
208 |
|
209 | return matrix
|
210 | }
|
211 |
|
212 | /**
|
213 | * Returns a 4x4 matrix describing Y-axis rotation.
|
214 | *
|
215 | * @param {number} angle - Measured in degrees.
|
216 | * @return {array}
|
217 | */
|
218 | function rotateY (angle) {
|
219 | var theta = Math.PI / 180 * angle;
|
220 | var matrix = identity();
|
221 |
|
222 | matrix[0] = matrix[10] = Math.cos(theta);
|
223 | matrix[2] = matrix[8] = Math.sin(theta);
|
224 | matrix[2] *= -1;
|
225 |
|
226 | return matrix
|
227 | }
|
228 |
|
229 | /**
|
230 | * Returns a 4x4 matrix describing Z-axis rotation.
|
231 | *
|
232 | * @param {number} angle - Measured in degrees.
|
233 | * @return {array}
|
234 | */
|
235 | function rotateZ (angle) {
|
236 | var theta = Math.PI / 180 * angle;
|
237 | var matrix = identity();
|
238 |
|
239 | matrix[0] = matrix[5] = Math.cos(theta);
|
240 | matrix[1] = matrix[4] = Math.sin(theta);
|
241 | matrix[4] *= -1;
|
242 |
|
243 | return matrix
|
244 | }
|
245 |
|
246 | /**
|
247 | * Returns a 4x4 matrix describing 2D scaling. The first argument
|
248 | * is used for both X and Y-axis scaling, unless an optional
|
249 | * second argument is provided to explicitly define Y-axis scaling.
|
250 | *
|
251 | * @param {number} scalar - Decimal multiplier.
|
252 | * @param {number} [scalarY] - Decimal multiplier.
|
253 | * @return {array}
|
254 | */
|
255 | function scale (scalar, scalarY) {
|
256 | var matrix = identity();
|
257 |
|
258 | matrix[0] = scalar;
|
259 | matrix[5] = typeof scalarY === 'number' ? scalarY : scalar;
|
260 |
|
261 | return matrix
|
262 | }
|
263 |
|
264 | /**
|
265 | * Returns a 4x4 matrix describing X-axis scaling.
|
266 | *
|
267 | * @param {number} scalar - Decimal multiplier.
|
268 | * @return {array}
|
269 | */
|
270 | function scaleX (scalar) {
|
271 | var matrix = identity();
|
272 | matrix[0] = scalar;
|
273 | return matrix
|
274 | }
|
275 |
|
276 | /**
|
277 | * Returns a 4x4 matrix describing Y-axis scaling.
|
278 | *
|
279 | * @param {number} scalar - Decimal multiplier.
|
280 | * @return {array}
|
281 | */
|
282 | function scaleY (scalar) {
|
283 | var matrix = identity();
|
284 | matrix[5] = scalar;
|
285 | return matrix
|
286 | }
|
287 |
|
288 | /**
|
289 | * Returns a 4x4 matrix describing Z-axis scaling.
|
290 | *
|
291 | * @param {number} scalar - Decimal multiplier.
|
292 | * @return {array}
|
293 | */
|
294 | function scaleZ (scalar) {
|
295 | var matrix = identity();
|
296 | matrix[10] = scalar;
|
297 | return matrix
|
298 | }
|
299 |
|
300 | /**
|
301 | * Returns a 4x4 matrix describing shear. The first argument
|
302 | * defines X-axis shearing, and an optional second argument
|
303 | * defines Y-axis shearing.
|
304 | *
|
305 | * @param {number} angleX - Measured in degrees.
|
306 | * @param {number} [angleY] - Measured in degrees.
|
307 | * @return {array}
|
308 | */
|
309 | function skew (angleX, angleY) {
|
310 | var thetaX = Math.PI / 180 * angleX;
|
311 | var matrix = identity();
|
312 |
|
313 | matrix[4] = Math.tan(thetaX);
|
314 |
|
315 | if (angleY) {
|
316 | var thetaY = Math.PI / 180 * angleY;
|
317 | matrix[1] = Math.tan(thetaY);
|
318 | }
|
319 |
|
320 | return matrix
|
321 | }
|
322 |
|
323 | /**
|
324 | * Returns a 4x4 matrix describing X-axis shear.
|
325 | *
|
326 | * @param {number} angle - Measured in degrees.
|
327 | * @return {array}
|
328 | */
|
329 | function skewX (angle) {
|
330 | var theta = Math.PI / 180 * angle;
|
331 | var matrix = identity();
|
332 |
|
333 | matrix[4] = Math.tan(theta);
|
334 |
|
335 | return matrix
|
336 | }
|
337 |
|
338 | /**
|
339 | * Returns a 4x4 matrix describing Y-axis shear.
|
340 | *
|
341 | * @param {number} angle - Measured in degrees
|
342 | * @return {array}
|
343 | */
|
344 | function skewY (angle) {
|
345 | var theta = Math.PI / 180 * angle;
|
346 | var matrix = identity();
|
347 |
|
348 | matrix[1] = Math.tan(theta);
|
349 |
|
350 | return matrix
|
351 | }
|
352 |
|
353 | /**
|
354 | * Returns a 4x4 matrix describing 2D translation. The first
|
355 | * argument defines X-axis translation, and an optional second
|
356 | * argument defines Y-axis translation.
|
357 | *
|
358 | * @param {number} distanceX - Measured in pixels.
|
359 | * @param {number} [distanceY] - Measured in pixels.
|
360 | * @return {array}
|
361 | */
|
362 | function translate (distanceX, distanceY) {
|
363 | var matrix = identity();
|
364 | matrix[12] = distanceX;
|
365 |
|
366 | if (distanceY) {
|
367 | matrix[13] = distanceY;
|
368 | }
|
369 |
|
370 | return matrix
|
371 | }
|
372 |
|
373 | /**
|
374 | * Returns a 4x4 matrix describing X-axis translation.
|
375 | *
|
376 | * @param {number} distance - Measured in pixels.
|
377 | * @return {array}
|
378 | */
|
379 | function translateX (distance) {
|
380 | var matrix = identity();
|
381 | matrix[12] = distance;
|
382 | return matrix
|
383 | }
|
384 |
|
385 | /**
|
386 | * Returns a 4x4 matrix describing Y-axis translation.
|
387 | *
|
388 | * @param {number} distance - Measured in pixels.
|
389 | * @return {array}
|
390 | */
|
391 | function translateY (distance) {
|
392 | var matrix = identity();
|
393 | matrix[13] = distance;
|
394 | return matrix
|
395 | }
|
396 |
|
397 | /**
|
398 | * Returns a 4x4 matrix describing Z-axis translation.
|
399 | *
|
400 | * @param {number} distance - Measured in pixels.
|
401 | * @return {array}
|
402 | */
|
403 | function translateZ (distance) {
|
404 | var matrix = identity();
|
405 | matrix[14] = distance;
|
406 | return matrix
|
407 | }
|
408 |
|
409 | export { format, identity, inverse, multiply, parse, rotate, rotateX, rotateY, rotateZ, scale, scaleX, scaleY, scaleZ, skew, skewX, skewY, translate, translateX, translateY, translateZ };
|