UNPKG

10.6 kBJavaScriptView Raw
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*/
39function 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 */
67function 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 */
85function 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 */
143function 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 */
174function 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 */
191function 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 */
201function 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 */
218function 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 */
235function 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*/
255function 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*/
270function 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*/
282function 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*/
294function 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*/
309function 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*/
329function 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*/
344function 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 */
362function 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 */
379function 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 */
391function 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 */
403function translateZ (distance) {
404 var matrix = identity();
405 matrix[14] = distance;
406 return matrix
407}
408
409export { format, identity, inverse, multiply, parse, rotate, rotateX, rotateY, rotateZ, scale, scaleX, scaleY, scaleZ, skew, skewX, skewY, translate, translateX, translateY, translateZ };