1 | /*!
|
2 | 2D Transformation Matrix v2.7.5
|
3 | (c) Epistemex.com 2014-2018
|
4 | License: MIT
|
5 | */
|
6 |
|
7 | /**
|
8 | * 2D transformation matrix object initialized with identity matrix.
|
9 | *
|
10 | * The matrix can synchronize a canvas 2D context by supplying the context
|
11 | * as an argument, or later apply current absolute transform to an
|
12 | * existing context.
|
13 | *
|
14 | * To synchronize a DOM element you can use [`toCSS()`]{@link Matrix#toCSS} or [`toCSS3D()`]{@link Matrix#toCSS3D}.
|
15 | * together with for example the `style.transform` property.
|
16 | *
|
17 | * @param {CanvasRenderingContext2D} [context] - Optional context to sync with Matrix
|
18 | * @param {HTMLElement} [element=null] - DOM Element to synchronize
|
19 | * @prop {number} a - scale x
|
20 | * @prop {number} b - shear y
|
21 | * @prop {number} c - shear x
|
22 | * @prop {number} d - scale y
|
23 | * @prop {number} e - translate x
|
24 | * @prop {number} f - translate y
|
25 | * @prop {CanvasRenderingContext2D} [context] - set or get current synchronized 2D context
|
26 | * @prop {HTMLElement} [element] - get current synchronized DOM element
|
27 | * @prop {boolean} [useCSS3D=false] - is a DOM element is defined for sync., choose whether to use 2D (false) or 3D (true) matrix to sync it.
|
28 | * @constructor
|
29 | * @license MIT license
|
30 | * @copyright Epistemex.com 2014-2018
|
31 | */
|
32 | function Matrix(context, element) {
|
33 | var me = this,
|
34 | _el;
|
35 | me._t = me.transform;
|
36 |
|
37 | me.a = me.d = 1;
|
38 | me.b = me.c = me.e = me.f = 0;
|
39 |
|
40 | // sync context
|
41 | if (context) (me.context = context).setTransform(1, 0, 0, 1, 0, 0);
|
42 |
|
43 | // sync DOM element
|
44 | Object.defineProperty(me, 'element', {
|
45 | get: function () {
|
46 | return _el;
|
47 | },
|
48 | set: function (el) {
|
49 | if (!_el) {
|
50 | me._px = me._getPX();
|
51 | me.useCSS3D = false;
|
52 | }
|
53 | _el = el;
|
54 | (me._st = _el.style)[me._px] = me.toCSS();
|
55 | },
|
56 | });
|
57 |
|
58 | if (element) me.element = element;
|
59 | }
|
60 |
|
61 | /**
|
62 | * Returns a new matrix that transforms a triangle `t1` into another triangle
|
63 | * `t2`, or throws an exception if it is impossible.
|
64 | *
|
65 | * Note: the method can take both arrays as well as literal objects.
|
66 | * Just make sure that both arguments (`t1`, `t2`) are of the same type.
|
67 | *
|
68 | * @param {{px: number, py: number, qx: number, qy: number, rx: number, ry: number}|Array} t1 - Object or array containing the three points for the triangle.
|
69 | * For object use obj.px, obj.py, obj.qx, obj.qy, obj.rx and obj.ry. For arrays provide the points in the order [px, py, qx, qy, rx, ry], or as point array [{x:,y:}, {x:,y:}, {x:,y:}]
|
70 | * @param {{px: number, py: number, qx: number, qy: number, rx: number, ry: number}|Array} t2 - See description for t1.
|
71 | * @param {CanvasRenderingContext2D} [context] - optional canvas 2D context to use for the matrix
|
72 | * @returns {Matrix}
|
73 | * @throws Exception is matrix becomes not invertible
|
74 | * @static
|
75 | */
|
76 | Matrix.fromTriangles = function (t1, t2, context) {
|
77 | var m1 = new Matrix(),
|
78 | m2 = new Matrix(context),
|
79 | r1,
|
80 | r2,
|
81 | rx1,
|
82 | ry1,
|
83 | rx2,
|
84 | ry2;
|
85 |
|
86 | if (Array.isArray(t1)) {
|
87 | if (typeof t1[0] === 'number') {
|
88 | rx1 = t1[4];
|
89 | ry1 = t1[5];
|
90 | rx2 = t2[4];
|
91 | ry2 = t2[5];
|
92 | r1 = [t1[0] - rx1, t1[1] - ry1, t1[2] - rx1, t1[3] - ry1, rx1, ry1];
|
93 | r2 = [t2[0] - rx2, t2[1] - ry2, t2[2] - rx2, t2[3] - ry2, rx2, ry2];
|
94 | } else {
|
95 | rx1 = t1[2].x;
|
96 | ry1 = t1[2].y;
|
97 | rx2 = t2[2].x;
|
98 | ry2 = t2[2].y;
|
99 | r1 = [
|
100 | t1[0].x - rx1,
|
101 | t1[0].y - ry1,
|
102 | t1[1].x - rx1,
|
103 | t1[1].y - ry1,
|
104 | rx1,
|
105 | ry1,
|
106 | ];
|
107 | r2 = [
|
108 | t2[0].x - rx2,
|
109 | t2[0].y - ry2,
|
110 | t2[1].x - rx2,
|
111 | t2[1].y - ry2,
|
112 | rx2,
|
113 | ry2,
|
114 | ];
|
115 | }
|
116 | } else {
|
117 | r1 = [
|
118 | t1.px - t1.rx,
|
119 | t1.py - t1.ry,
|
120 | t1.qx - t1.rx,
|
121 | t1.qy - t1.ry,
|
122 | t1.rx,
|
123 | t1.ry,
|
124 | ];
|
125 | r2 = [
|
126 | t2.px - t2.rx,
|
127 | t2.py - t2.ry,
|
128 | t2.qx - t2.rx,
|
129 | t2.qy - t2.ry,
|
130 | t2.rx,
|
131 | t2.ry,
|
132 | ];
|
133 | }
|
134 |
|
135 | m1.setTransform.apply(m1, r1);
|
136 | m2.setTransform.apply(m2, r2);
|
137 |
|
138 | return m2.multiply(m1.inverse());
|
139 | };
|
140 |
|
141 | /**
|
142 | * Create a matrix from a transform list from an SVG shape. The list
|
143 | * can be for example baseVal (i.e. `shape.transform.baseVal`).
|
144 | *
|
145 | * The resulting matrix has all transformations from that list applied
|
146 | * in the same order as the list.
|
147 | *
|
148 | * @param {SVGTransformList} tList - transform list from an SVG shape.
|
149 | * @param {CanvasRenderingContext2D} [context] - optional canvas 2D context to use for the matrix
|
150 | * @param {HTMLElement} [dom] - optional DOM element to use for the matrix
|
151 | * @returns {Matrix}
|
152 | * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGTransformList|MDN / SVGTransformList}
|
153 | */
|
154 | Matrix.fromSVGTransformList = function (tList, context, dom) {
|
155 | var m = new Matrix(context, dom),
|
156 | i = 0;
|
157 |
|
158 | while (i < tList.length) m.multiply(tList[i++].matrix);
|
159 |
|
160 | return m;
|
161 | };
|
162 |
|
163 | /**
|
164 | * Create and transform a new matrix based on given matrix values, or
|
165 | * provide SVGMatrix or a (2D) DOMMatrix, WebKitCSSMatrix or another
|
166 | * instance of a generic Matrix.
|
167 | *
|
168 | * @example
|
169 | *
|
170 | * var m = Matrix.from(1, 0.2, 0, 2, 120, 97);
|
171 | * var m = Matrix.from(domMatrix, ctx);
|
172 | * var m = Matrix.from(svgMatrix);
|
173 | * var m = Matrix.from(cssMatrix);
|
174 | * var m = Matrix.from(matrix);
|
175 | * var m = Matrix.from(vector [,pre-x] [,pre-y] [,doScale]);
|
176 | *
|
177 | * @param {*} a - number representing a in [a-f], or a Matrix object containing properties a-f. Vector is given as an object with properties x and y.
|
178 | * @param {*} [b] - b property if a is not a matrix object, or optional canvas 2D context.
|
179 | * If vector is input this will be pre-translate for x.
|
180 | * @param {number} [c] - If vector is input this will be pre-translate for y.
|
181 | * @param {number} [d] - If vector is input, set this to true to use scale and translate of 1,
|
182 | * false to use hypotenuse as translate distance instead and no scale.
|
183 | * @param {number} [e]
|
184 | * @param {number} [f]
|
185 | * @param {CanvasRenderingContext2D} [context] - optional canvas context to synchronize
|
186 | * @param {HTMLElement} [dom] - optional DOM element to use for the matrix
|
187 | * @returns {Matrix}
|
188 | * @static
|
189 | */
|
190 | Matrix.from = function (a, b, c, d, e, f, context, dom) {
|
191 | var m = new Matrix(context, dom),
|
192 | scale,
|
193 | dist,
|
194 | q;
|
195 |
|
196 | if (typeof a === 'number') m.setTransform(a, b, c, d, e, f);
|
197 | else if (typeof a.x === 'number') {
|
198 | // vector
|
199 |
|
200 | q = Math.sqrt(a.x * a.x + a.y * a.y);
|
201 | scale = dist = 1;
|
202 |
|
203 | if (d) scale = q;
|
204 | else dist = q;
|
205 |
|
206 | m.translate(b || 0, c || 0)
|
207 | .rotateFromVector(a)
|
208 | .scaleU(scale)
|
209 | .translate(dist, 0);
|
210 | } else {
|
211 | if (typeof a.is2D === 'boolean' && !a.is2D)
|
212 | throw 'Cannot use 3D DOMMatrix.';
|
213 | if (b) m.context = b;
|
214 | if (c) m.element = c;
|
215 | m.multiply(a);
|
216 | }
|
217 |
|
218 | return m;
|
219 | };
|
220 |
|
221 | Matrix.prototype = {
|
222 | _getPX: function () {
|
223 | var lst = ['t', 'oT', 'msT', 'mozT', 'webkitT', 'khtmlT'],
|
224 | i = 0,
|
225 | p,
|
226 | style = document.createElement('div').style;
|
227 |
|
228 | while ((p = lst[i++]))
|
229 | if (typeof style[p + 'ransform'] !== 'undefined') return p + 'ransform';
|
230 | },
|
231 |
|
232 | /**
|
233 | * Concatenates transforms of this matrix onto the given child matrix and
|
234 | * returns a new matrix. This instance is used on left side.
|
235 | *
|
236 | * @param {Matrix|SVGMatrix} cm - child matrix to apply concatenation to
|
237 | * @returns {Matrix} - new Matrix instance
|
238 | */
|
239 | concat: function (cm) {
|
240 | return this.clone().multiply(cm);
|
241 | },
|
242 |
|
243 | /**
|
244 | * Flips the horizontal values.
|
245 | * @returns {Matrix}
|
246 | */
|
247 | flipX: function () {
|
248 | return this._t(-1, 0, 0, 1, 0, 0);
|
249 | },
|
250 |
|
251 | /**
|
252 | * Flips the vertical values.
|
253 | * @returns {Matrix}
|
254 | */
|
255 | flipY: function () {
|
256 | return this._t(1, 0, 0, -1, 0, 0);
|
257 | },
|
258 |
|
259 | /**
|
260 | * Reflects incoming (velocity) vector on the normal which will be the
|
261 | * current transformed x axis. Call when a trigger condition is met.
|
262 | *
|
263 | * @param {number} x - vector end point for x (start = 0)
|
264 | * @param {number} y - vector end point for y (start = 0)
|
265 | * @returns {{x: number, y: number}}
|
266 | */
|
267 | reflectVector: function (x, y) {
|
268 | var v = this.applyToPoint(0, 1),
|
269 | d = (v.x * x + v.y * y) * 2;
|
270 |
|
271 | x -= d * v.x;
|
272 | y -= d * v.y;
|
273 |
|
274 | return { x: x, y: y };
|
275 | },
|
276 |
|
277 | /**
|
278 | * Short-hand to reset current matrix to an identity matrix.
|
279 | * @returns {Matrix}
|
280 | */
|
281 | reset: function () {
|
282 | return this.setTransform(1, 0, 0, 1, 0, 0);
|
283 | },
|
284 |
|
285 | /**
|
286 | * Rotates current matrix by angle (accumulative).
|
287 | * @param {number} angle - angle in radians
|
288 | * @returns {Matrix}
|
289 | */
|
290 | rotate: function (angle) {
|
291 | var cos = Math.cos(angle),
|
292 | sin = Math.sin(angle);
|
293 | return this._t(cos, sin, -sin, cos, 0, 0);
|
294 | },
|
295 |
|
296 | /**
|
297 | * Converts a vector given as `x` and `y` to angle, and
|
298 | * rotates (accumulative). x can instead contain an object with
|
299 | * properties x and y and if so, y parameter will be ignored.
|
300 | * @param {number|*} x
|
301 | * @param {number} [y]
|
302 | * @returns {Matrix}
|
303 | */
|
304 | rotateFromVector: function (x, y) {
|
305 | return this.rotate(
|
306 | typeof x === 'number' ? Math.atan2(y, x) : Math.atan2(x.y, x.x)
|
307 | );
|
308 | },
|
309 |
|
310 | /**
|
311 | * Helper method to make a rotation based on an angle in degrees.
|
312 | * @param {number} angle - angle in degrees
|
313 | * @returns {Matrix}
|
314 | */
|
315 | rotateDeg: function (angle) {
|
316 | return this.rotate((angle * Math.PI) / 180);
|
317 | },
|
318 |
|
319 | /**
|
320 | * Scales current matrix uniformly and accumulative.
|
321 | * @param {number} f - scale factor for both x and y (1 does nothing)
|
322 | * @returns {Matrix}
|
323 | */
|
324 | scaleU: function (f) {
|
325 | return this._t(f, 0, 0, f, 0, 0);
|
326 | },
|
327 |
|
328 | /**
|
329 | * Scales current matrix accumulative.
|
330 | * @param {number} sx - scale factor x (1 does nothing)
|
331 | * @param {number} sy - scale factor y (1 does nothing)
|
332 | * @returns {Matrix}
|
333 | */
|
334 | scale: function (sx, sy) {
|
335 | return this._t(sx, 0, 0, sy, 0, 0);
|
336 | },
|
337 |
|
338 | /**
|
339 | * Scales current matrix on x axis accumulative.
|
340 | * @param {number} sx - scale factor x (1 does nothing)
|
341 | * @returns {Matrix}
|
342 | */
|
343 | scaleX: function (sx) {
|
344 | return this._t(sx, 0, 0, 1, 0, 0);
|
345 | },
|
346 |
|
347 | /**
|
348 | * Scales current matrix on y axis accumulative.
|
349 | * @param {number} sy - scale factor y (1 does nothing)
|
350 | * @returns {Matrix}
|
351 | */
|
352 | scaleY: function (sy) {
|
353 | return this._t(1, 0, 0, sy, 0, 0);
|
354 | },
|
355 |
|
356 | /**
|
357 | * Converts a vector given as `x` and `y` to normalized scale.
|
358 | * @param x
|
359 | * @param y
|
360 | * @returns {Matrix}
|
361 | */
|
362 | scaleFromVector: function (x, y) {
|
363 | return this.scaleU(Math.sqrt(x * x + y * y));
|
364 | },
|
365 |
|
366 | /**
|
367 | * Apply shear to the current matrix accumulative.
|
368 | * @param {number} sx - amount of shear for x
|
369 | * @param {number} sy - amount of shear for y
|
370 | * @returns {Matrix}
|
371 | */
|
372 | shear: function (sx, sy) {
|
373 | return this._t(1, sy, sx, 1, 0, 0);
|
374 | },
|
375 |
|
376 | /**
|
377 | * Apply shear for x to the current matrix accumulative.
|
378 | * @param {number} sx - amount of shear for x
|
379 | * @returns {Matrix}
|
380 | */
|
381 | shearX: function (sx) {
|
382 | return this._t(1, 0, sx, 1, 0, 0);
|
383 | },
|
384 |
|
385 | /**
|
386 | * Apply shear for y to the current matrix accumulative.
|
387 | * @param {number} sy - amount of shear for y
|
388 | * @returns {Matrix}
|
389 | */
|
390 | shearY: function (sy) {
|
391 | return this._t(1, sy, 0, 1, 0, 0);
|
392 | },
|
393 |
|
394 | /**
|
395 | * Apply skew to the current matrix accumulative. Angles in radians.
|
396 | * Also see [`skewDeg()`]{@link Matrix#skewDeg}.
|
397 | * @param {number} ax - angle of skew for x
|
398 | * @param {number} ay - angle of skew for y
|
399 | * @returns {Matrix}
|
400 | */
|
401 | skew: function (ax, ay) {
|
402 | return this.shear(Math.tan(ax), Math.tan(ay));
|
403 | },
|
404 |
|
405 | /**
|
406 | * Apply skew to the current matrix accumulative. Angles in degrees.
|
407 | * Also see [`skew()`]{@link Matrix#skew}.
|
408 | * @param {number} ax - angle of skew for x
|
409 | * @param {number} ay - angle of skew for y
|
410 | * @returns {Matrix}
|
411 | */
|
412 | skewDeg: function (ax, ay) {
|
413 | return this.shear(
|
414 | Math.tan((ax / 180) * Math.PI),
|
415 | Math.tan((ay / 180) * Math.PI)
|
416 | );
|
417 | },
|
418 |
|
419 | /**
|
420 | * Apply skew for x to the current matrix accumulative. Angles in radians.
|
421 | * Also see [`skewDeg()`]{@link Matrix#skewDeg}.
|
422 | * @param {number} ax - angle of skew for x
|
423 | * @returns {Matrix}
|
424 | */
|
425 | skewX: function (ax) {
|
426 | return this.shearX(Math.tan(ax));
|
427 | },
|
428 |
|
429 | /**
|
430 | * Apply skew for y to the current matrix accumulative. Angles in radians.
|
431 | * Also see [`skewDeg()`]{@link Matrix#skewDeg}.
|
432 | * @param {number} ay - angle of skew for y
|
433 | * @returns {Matrix}
|
434 | */
|
435 | skewY: function (ay) {
|
436 | return this.shearY(Math.tan(ay));
|
437 | },
|
438 |
|
439 | /**
|
440 | * Set current matrix to new absolute matrix.
|
441 | * @param {number} a - scale x
|
442 | * @param {number} b - shear y
|
443 | * @param {number} c - shear x
|
444 | * @param {number} d - scale y
|
445 | * @param {number} e - translate x
|
446 | * @param {number} f - translate y
|
447 | * @returns {Matrix}
|
448 | */
|
449 | setTransform: function (a, b, c, d, e, f) {
|
450 | var me = this;
|
451 | me.a = a;
|
452 | me.b = b;
|
453 | me.c = c;
|
454 | me.d = d;
|
455 | me.e = e;
|
456 | me.f = f;
|
457 | return me._x();
|
458 | },
|
459 |
|
460 | /**
|
461 | * Translate current matrix accumulative.
|
462 | * @param {number} tx - translation for x
|
463 | * @param {number} ty - translation for y
|
464 | * @returns {Matrix}
|
465 | */
|
466 | translate: function (tx, ty) {
|
467 | return this._t(1, 0, 0, 1, tx, ty);
|
468 | },
|
469 |
|
470 | /**
|
471 | * Translate current matrix on x axis accumulative.
|
472 | * @param {number} tx - translation for x
|
473 | * @returns {Matrix}
|
474 | */
|
475 | translateX: function (tx) {
|
476 | return this._t(1, 0, 0, 1, tx, 0);
|
477 | },
|
478 |
|
479 | /**
|
480 | * Translate current matrix on y axis accumulative.
|
481 | * @param {number} ty - translation for y
|
482 | * @returns {Matrix}
|
483 | */
|
484 | translateY: function (ty) {
|
485 | return this._t(1, 0, 0, 1, 0, ty);
|
486 | },
|
487 |
|
488 | /**
|
489 | * Multiplies current matrix with new matrix values. Also see [`multiply()`]{@link Matrix#multiply}.
|
490 | *
|
491 | * @param {number} a2 - scale x
|
492 | * @param {number} b2 - skew y
|
493 | * @param {number} c2 - skew x
|
494 | * @param {number} d2 - scale y
|
495 | * @param {number} e2 - translate x
|
496 | * @param {number} f2 - translate y
|
497 | * @returns {Matrix}
|
498 | */
|
499 | transform: function (a2, b2, c2, d2, e2, f2) {
|
500 | var me = this,
|
501 | a1 = me.a,
|
502 | b1 = me.b,
|
503 | c1 = me.c,
|
504 | d1 = me.d,
|
505 | e1 = me.e,
|
506 | f1 = me.f;
|
507 |
|
508 | /* matrix column order is:
|
509 | * a c e
|
510 | * b d f
|
511 | * 0 0 1
|
512 | */
|
513 | me.a = a1 * a2 + c1 * b2;
|
514 | me.b = b1 * a2 + d1 * b2;
|
515 | me.c = a1 * c2 + c1 * d2;
|
516 | me.d = b1 * c2 + d1 * d2;
|
517 | me.e = a1 * e2 + c1 * f2 + e1;
|
518 | me.f = b1 * e2 + d1 * f2 + f1;
|
519 |
|
520 | return me._x();
|
521 | },
|
522 |
|
523 | /**
|
524 | * Multiplies current matrix with source matrix.
|
525 | * @param {Matrix|DOMMatrix|SVGMatrix} m - source matrix to multiply with.
|
526 | * @returns {Matrix}
|
527 | */
|
528 | multiply: function (m) {
|
529 | return this._t(m.a, m.b, m.c, m.d, m.e, m.f);
|
530 | },
|
531 |
|
532 | /**
|
533 | * Divide this matrix on input matrix which must be invertible.
|
534 | * @param {Matrix} m - matrix to divide on (divisor)
|
535 | * @throws Exception if input matrix is not invertible
|
536 | * @returns {Matrix}
|
537 | */
|
538 | divide: function (m) {
|
539 | return this.multiply(m.inverse());
|
540 | },
|
541 |
|
542 | /**
|
543 | * Divide current matrix on scalar value != 0.
|
544 | * @param {number} d - divisor
|
545 | * @throws Exception if divisor is zero
|
546 | * @returns {Matrix}
|
547 | */
|
548 | divideScalar: function (d) {
|
549 | var me = this;
|
550 |
|
551 | if (!d) throw 'Division on zero';
|
552 |
|
553 | me.a /= d;
|
554 | me.b /= d;
|
555 | me.c /= d;
|
556 | me.d /= d;
|
557 | me.e /= d;
|
558 | me.f /= d;
|
559 |
|
560 | return me._x();
|
561 | },
|
562 |
|
563 | /**
|
564 | * Get an inverse matrix of current matrix. The method returns a new
|
565 | * matrix with values you need to use to get to an identity matrix.
|
566 | * Context from parent matrix is not applied to the returned matrix.
|
567 | *
|
568 | * @param {boolean} [cloneContext=false] - clone current context to resulting matrix
|
569 | * @param {boolean} [cloneDOM=false] - clone current DOM element to resulting matrix
|
570 | * @throws Exception is input matrix is not invertible
|
571 | * @returns {Matrix} - new Matrix instance
|
572 | */
|
573 | inverse: function (cloneContext, cloneDOM) {
|
574 | var me = this,
|
575 | m = new Matrix(
|
576 | cloneContext ? me.context : null,
|
577 | cloneDOM ? me.element : null
|
578 | ),
|
579 | dt = me.determinant();
|
580 |
|
581 | if (!dt) throw 'Matrix not invertible.';
|
582 |
|
583 | m.a = me.d / dt;
|
584 | m.b = -me.b / dt;
|
585 | m.c = -me.c / dt;
|
586 | m.d = me.a / dt;
|
587 | m.e = (me.c * me.f - me.d * me.e) / dt;
|
588 | m.f = -(me.a * me.f - me.b * me.e) / dt;
|
589 |
|
590 | return m;
|
591 | },
|
592 |
|
593 | /**
|
594 | * Interpolate this matrix with another and produce a new matrix.
|
595 | * `t` is a value in the range [0.0, 1.0] where 0 is this instance and
|
596 | * 1 is equal to the second matrix. The `t` value is not clamped.
|
597 | *
|
598 | * Context from parent matrix is not applied to the returned matrix.
|
599 | *
|
600 | * Note: this interpolation is naive. For animation containing rotation,
|
601 | * shear or skew use the [`interpolateAnim()`]{@link Matrix#interpolateAnim} method instead
|
602 | * to avoid unintended flipping.
|
603 | *
|
604 | * @param {Matrix|SVGMatrix} m2 - the matrix to interpolate with.
|
605 | * @param {number} t - interpolation [0.0, 1.0]
|
606 | * @param {CanvasRenderingContext2D} [context] - optional context to affect
|
607 | * @param {HTMLElement} [dom] - optional DOM element to use for the matrix
|
608 | * @returns {Matrix} - new Matrix instance with the interpolated result
|
609 | */
|
610 | interpolate: function (m2, t, context, dom) {
|
611 | var me = this,
|
612 | m = new Matrix(context, dom);
|
613 |
|
614 | m.a = me.a + (m2.a - me.a) * t;
|
615 | m.b = me.b + (m2.b - me.b) * t;
|
616 | m.c = me.c + (m2.c - me.c) * t;
|
617 | m.d = me.d + (m2.d - me.d) * t;
|
618 | m.e = me.e + (m2.e - me.e) * t;
|
619 | m.f = me.f + (m2.f - me.f) * t;
|
620 |
|
621 | return m._x();
|
622 | },
|
623 |
|
624 | /**
|
625 | * Interpolate this matrix with another and produce a new matrix.
|
626 | * `t` is a value in the range [0.0, 1.0] where 0 is this instance and
|
627 | * 1 is equal to the second matrix. The `t` value is not constrained.
|
628 | *
|
629 | * Context from parent matrix is not applied to the returned matrix.
|
630 | *
|
631 | * To obtain easing `t` can be preprocessed using easing-functions
|
632 | * before being passed to this method.
|
633 | *
|
634 | * Note: this interpolation method uses decomposition which makes
|
635 | * it suitable for animations (in particular where rotation takes
|
636 | * places).
|
637 | *
|
638 | * @param {Matrix} m2 - the matrix to interpolate with.
|
639 | * @param {number} t - interpolation [0.0, 1.0]
|
640 | * @param {CanvasRenderingContext2D} [context] - optional context to affect
|
641 | * @param {HTMLElement} [dom] - optional DOM element to use for the matrix
|
642 | * @returns {Matrix} - new Matrix instance with the interpolated result
|
643 | */
|
644 | interpolateAnim: function (m2, t, context, dom) {
|
645 | var m = new Matrix(context, dom),
|
646 | d1 = this.decompose(),
|
647 | d2 = m2.decompose(),
|
648 | t1 = d1.translate,
|
649 | t2 = d2.translate,
|
650 | s1 = d1.scale;
|
651 |
|
652 | // QR order (t-r-s-sk)
|
653 | m.translate(t1.x + (t2.x - t1.x) * t, t1.y + (t2.y - t1.y) * t);
|
654 | m.rotate(d1.rotation + (d2.rotation - d1.rotation) * t);
|
655 | m.scale(s1.x + (d2.scale.x - s1.x) * t, s1.y + (d2.scale.y - s1.y) * t);
|
656 | //todo test skew scenarios
|
657 |
|
658 | return m._x();
|
659 | },
|
660 |
|
661 | /**
|
662 | * Decompose the current matrix into simple transforms using either
|
663 | * QR (default) or LU decomposition.
|
664 | *
|
665 | * @param {boolean} [useLU=false] - set to true to use LU rather than QR decomposition
|
666 | * @returns {*} - an object containing current decomposed values (translate, rotation, scale, skew)
|
667 | * @see {@link https://en.wikipedia.org/wiki/QR_decomposition|More on QR decomposition}
|
668 | * @see {@link https://en.wikipedia.org/wiki/LU_decomposition|More on LU decomposition}
|
669 | */
|
670 | decompose: function (useLU) {
|
671 | var me = this,
|
672 | a = me.a,
|
673 | b = me.b,
|
674 | c = me.c,
|
675 | d = me.d,
|
676 | acos = Math.acos,
|
677 | atan = Math.atan,
|
678 | sqrt = Math.sqrt,
|
679 | pi = Math.PI,
|
680 | translate = { x: me.e, y: me.f },
|
681 | rotation = 0,
|
682 | scale = { x: 1, y: 1 },
|
683 | skew = { x: 0, y: 0 },
|
684 | determ = a * d - b * c, // determinant(), skip DRY here...
|
685 | r,
|
686 | s;
|
687 |
|
688 | if (useLU) {
|
689 | if (a) {
|
690 | skew = { x: atan(c / a), y: atan(b / a) };
|
691 | scale = { x: a, y: determ / a };
|
692 | } else if (b) {
|
693 | rotation = pi * 0.5;
|
694 | scale = { x: b, y: determ / b };
|
695 | skew.x = atan(d / b);
|
696 | } else {
|
697 | // a = b = 0
|
698 | scale = { x: c, y: d };
|
699 | skew.x = pi * 0.25;
|
700 | }
|
701 | } else {
|
702 | // Apply the QR-like decomposition.
|
703 | if (a || b) {
|
704 | r = sqrt(a * a + b * b);
|
705 | rotation = b > 0 ? acos(a / r) : -acos(a / r);
|
706 | scale = { x: r, y: determ / r };
|
707 | skew.x = atan((a * c + b * d) / (r * r));
|
708 | } else if (c || d) {
|
709 | s = sqrt(c * c + d * d);
|
710 | rotation = pi * 0.5 - (d > 0 ? acos(-c / s) : -acos(c / s));
|
711 | scale = { x: determ / s, y: s };
|
712 | skew.y = atan((a * c + b * d) / (s * s));
|
713 | } else {
|
714 | // a = b = c = d = 0
|
715 | scale = { x: 0, y: 0 };
|
716 | }
|
717 | }
|
718 |
|
719 | return {
|
720 | translate: translate,
|
721 | rotation: rotation,
|
722 | scale: scale,
|
723 | skew: skew,
|
724 | };
|
725 | },
|
726 |
|
727 | /**
|
728 | * Returns the determinant of the current matrix.
|
729 | * @returns {number}
|
730 | */
|
731 | determinant: function () {
|
732 | return this.a * this.d - this.b * this.c;
|
733 | },
|
734 |
|
735 | /**
|
736 | * Apply current matrix to `x` and `y` of a point.
|
737 | * Returns a point object.
|
738 | *
|
739 | * @param {number} x - value for x
|
740 | * @param {number} y - value for y
|
741 | * @returns {{x: number, y: number}} A new transformed point object
|
742 | */
|
743 | applyToPoint: function (x, y) {
|
744 | var me = this;
|
745 | return {
|
746 | x: x * me.a + y * me.c + me.e,
|
747 | y: x * me.b + y * me.d + me.f,
|
748 | };
|
749 | },
|
750 |
|
751 | /**
|
752 | * Apply current matrix to array with point objects or point pairs.
|
753 | * Returns a new array with points in the same format as the input array.
|
754 | *
|
755 | * A point object is an object literal:
|
756 | *
|
757 | * {x: x, y: y}
|
758 | *
|
759 | * so an array would contain either:
|
760 | *
|
761 | * [{x: x1, y: y1}, {x: x2, y: y2}, ... {x: xn, y: yn}]
|
762 | *
|
763 | * or
|
764 | *
|
765 | * [x1, y1, x2, y2, ... xn, yn]
|
766 | *
|
767 | * @param {Array} points - array with point objects or pairs
|
768 | * @returns {Array} A new array with transformed points
|
769 | */
|
770 | applyToArray: function (points) {
|
771 | var i = 0,
|
772 | p,
|
773 | l,
|
774 | mxPoints = [];
|
775 |
|
776 | if (typeof points[0] === 'number') {
|
777 | l = points.length;
|
778 |
|
779 | while (i < l) {
|
780 | p = this.applyToPoint(points[i++], points[i++]);
|
781 | mxPoints.push(p.x, p.y);
|
782 | }
|
783 | } else {
|
784 | while ((p = points[i++])) {
|
785 | mxPoints.push(this.applyToPoint(p.x, p.y));
|
786 | }
|
787 | }
|
788 |
|
789 | return mxPoints;
|
790 | },
|
791 |
|
792 | /**
|
793 | * Apply current matrix to a typed array with point pairs. Although
|
794 | * the input array may be an ordinary array, this method is intended
|
795 | * for more performant use where typed arrays are used. The returned
|
796 | * array is regardless always returned as a `Float32Array`.
|
797 | *
|
798 | * @param {*} points - (typed) array with point pairs [x1, y1, ..., xn, yn]
|
799 | * @param {boolean} [use64=false] - use Float64Array instead of Float32Array
|
800 | * @returns {*} A new typed array with transformed points
|
801 | */
|
802 | applyToTypedArray: function (points, use64) {
|
803 | var i = 0,
|
804 | p,
|
805 | l = points.length,
|
806 | mxPoints = use64 ? new Float64Array(l) : new Float32Array(l);
|
807 |
|
808 | while (i < l) {
|
809 | p = this.applyToPoint(points[i], points[i + 1]);
|
810 | mxPoints[i++] = p.x;
|
811 | mxPoints[i++] = p.y;
|
812 | }
|
813 |
|
814 | return mxPoints;
|
815 | },
|
816 |
|
817 | /**
|
818 | * Apply to any canvas 2D context object. This does not affect the
|
819 | * context that optionally was referenced in constructor unless it is
|
820 | * the same context.
|
821 | *
|
822 | * @param {CanvasRenderingContext2D} context - target context
|
823 | * @returns {Matrix}
|
824 | */
|
825 | applyToContext: function (context) {
|
826 | var me = this;
|
827 | context.setTransform(me.a, me.b, me.c, me.d, me.e, me.f);
|
828 | return me;
|
829 | },
|
830 |
|
831 | /**
|
832 | * Apply to any DOM element. This does not affect the DOM element
|
833 | * that optionally was referenced in constructor unless it is
|
834 | * the same element.
|
835 | *
|
836 | * The method will auto-detect the correct browser prefix if any.
|
837 | *
|
838 | * @param {HTMLElement} element - target DOM element
|
839 | * @param {boolean} [use3D=false] - use 3D transformation matrix instead of 2D
|
840 | * @returns {Matrix}
|
841 | */
|
842 | applyToElement: function (element, use3D) {
|
843 | var me = this;
|
844 | if (!me._px) me._px = me._getPX();
|
845 | element.style[me._px] = use3D ? me.toCSS3D() : me.toCSS();
|
846 | return me;
|
847 | },
|
848 |
|
849 | /**
|
850 | * Instead of creating a new instance of a Matrix, DOMMatrix or SVGMatrix
|
851 | * the current settings of this instance can be applied to an external
|
852 | * object of a different (or same) type. You can also pass in an
|
853 | * empty literal object.
|
854 | *
|
855 | * Note that the properties a-f will be set regardless of if they
|
856 | * already exist or not.
|
857 | *
|
858 | * @param {*} obj - target object.
|
859 | * @returns {Matrix}
|
860 | */
|
861 | applyToObject: function (obj) {
|
862 | var me = this;
|
863 | obj.a = me.a;
|
864 | obj.b = me.b;
|
865 | obj.c = me.c;
|
866 | obj.d = me.d;
|
867 | obj.e = me.e;
|
868 | obj.f = me.f;
|
869 | return me;
|
870 | },
|
871 |
|
872 | /**
|
873 | * Returns true if matrix is an identity matrix (no transforms applied).
|
874 | * @returns {boolean}
|
875 | */
|
876 | isIdentity: function () {
|
877 | var me = this;
|
878 | return me.a === 1 && !me.b && !me.c && me.d === 1 && !me.e && !me.f;
|
879 | },
|
880 |
|
881 | /**
|
882 | * Returns true if matrix is invertible
|
883 | * @returns {boolean}
|
884 | */
|
885 | isInvertible: function () {
|
886 | return !this._q(this.determinant(), 0);
|
887 | },
|
888 |
|
889 | /**
|
890 | * The method is intended for situations where scale is accumulated
|
891 | * via multiplications, to detect situations where scale becomes
|
892 | * "trapped" with a value of zero. And in which case scale must be
|
893 | * set explicitly to a non-zero value.
|
894 | *
|
895 | * @returns {boolean}
|
896 | */
|
897 | isValid: function () {
|
898 | return !(this.a * this.d);
|
899 | },
|
900 |
|
901 | /**
|
902 | * Compares current matrix with another matrix. Returns true if equal
|
903 | * (within epsilon tolerance).
|
904 | * @param {Matrix|SVGMatrix} m - matrix to compare this matrix with
|
905 | * @returns {boolean}
|
906 | */
|
907 | isEqual: function (m) {
|
908 | var me = this,
|
909 | q = me._q;
|
910 |
|
911 | return (
|
912 | q(me.a, m.a) &&
|
913 | q(me.b, m.b) &&
|
914 | q(me.c, m.c) &&
|
915 | q(me.d, m.d) &&
|
916 | q(me.e, m.e) &&
|
917 | q(me.f, m.f)
|
918 | );
|
919 | },
|
920 |
|
921 | /**
|
922 | * Clones current instance and returning a new matrix.
|
923 | * @param {boolean} [noContext=false] don't clone context reference if true
|
924 | * @returns {Matrix} - a new Matrix instance with identical transformations as this instance
|
925 | */
|
926 | clone: function (noContext) {
|
927 | return new Matrix(noContext ? null : this.context).multiply(this);
|
928 | },
|
929 |
|
930 | /**
|
931 | * Returns an array with current matrix values.
|
932 | * @returns {Array}
|
933 | */
|
934 | toArray: function () {
|
935 | var me = this;
|
936 | return [me.a, me.b, me.c, me.d, me.e, me.f];
|
937 | },
|
938 |
|
939 | /**
|
940 | * Returns a binary 32-bit floating point typed array.
|
941 | * @returns {*}
|
942 | */
|
943 | toTypedArray: function () {
|
944 | var me = this;
|
945 | return new Float32Array([me.a, me.b, me.c, me.d, me.e, me.f]);
|
946 | },
|
947 |
|
948 | /**
|
949 | * Generates a string that can be used with CSS `transform`.
|
950 | * @example
|
951 | * element.style.transform = m.toCSS();
|
952 | * @returns {string}
|
953 | */
|
954 | toCSS: function () {
|
955 | return 'matrix(' + this.toArray() + ')';
|
956 | },
|
957 |
|
958 | /**
|
959 | * Generates a `matrix3d()` string that can be used with CSS `transform`.
|
960 | * Although the matrix is for 2D use you may see performance benefits
|
961 | * on some devices using a 3D CSS transform instead of a 2D.
|
962 | * @example
|
963 | * element.style.transform = m.toCSS3D();
|
964 | * @returns {string}
|
965 | */
|
966 | toCSS3D: function () {
|
967 | var me = this,
|
968 | n2 = ',0,0,';
|
969 | return (
|
970 | 'matrix3d(' +
|
971 | me.a +
|
972 | ',' +
|
973 | me.b +
|
974 | n2 +
|
975 | me.c +
|
976 | ',' +
|
977 | me.d +
|
978 | n2 +
|
979 | n2 +
|
980 | ',1,0,' +
|
981 | me.e +
|
982 | ',' +
|
983 | me.f +
|
984 | ',0,1)'
|
985 | );
|
986 | },
|
987 |
|
988 | /**
|
989 | * Returns a JSON compatible string of current matrix.
|
990 | * @returns {string}
|
991 | */
|
992 | toJSON: function () {
|
993 | var me = this;
|
994 | return (
|
995 | '{"a":' +
|
996 | me.a +
|
997 | ',"b":' +
|
998 | me.b +
|
999 | ',"c":' +
|
1000 | me.c +
|
1001 | ',"d":' +
|
1002 | me.d +
|
1003 | ',"e":' +
|
1004 | me.e +
|
1005 | ',"f":' +
|
1006 | me.f +
|
1007 | '}'
|
1008 | );
|
1009 | },
|
1010 |
|
1011 | /**
|
1012 | * Returns a string with current matrix as comma-separated list.
|
1013 | * @param {number} [fixLen=4] - truncate decimal values to number of digits
|
1014 | * @returns {string}
|
1015 | */
|
1016 | toString: function (fixLen) {
|
1017 | var me = this;
|
1018 | fixLen = fixLen || 4;
|
1019 | return (
|
1020 | 'a=' +
|
1021 | me.a.toFixed(fixLen) +
|
1022 | ' b=' +
|
1023 | me.b.toFixed(fixLen) +
|
1024 | ' c=' +
|
1025 | me.c.toFixed(fixLen) +
|
1026 | ' d=' +
|
1027 | me.d.toFixed(fixLen) +
|
1028 | ' e=' +
|
1029 | me.e.toFixed(fixLen) +
|
1030 | ' f=' +
|
1031 | me.f.toFixed(fixLen)
|
1032 | );
|
1033 | },
|
1034 |
|
1035 | /**
|
1036 | * Returns a string with current matrix as comma-separated values
|
1037 | * string with line-end (CR+LF).
|
1038 | * @returns {string}
|
1039 | */
|
1040 | toCSV: function () {
|
1041 | return this.toArray().join() + '\r\n';
|
1042 | },
|
1043 |
|
1044 | /**
|
1045 | * Convert current matrix into a `DOMMatrix`. If `DOMMatrix` is not
|
1046 | * supported, a `null` is returned.
|
1047 | *
|
1048 | * @returns {DOMMatrix}
|
1049 | * @see {@link https://drafts.fxtf.org/geometry/#dommatrix|MDN / SVGMatrix}
|
1050 | */
|
1051 | toDOMMatrix: function () {
|
1052 | var m = null;
|
1053 | if ('DOMMatrix' in window) {
|
1054 | m = new DOMMatrix();
|
1055 | m.a = this.a;
|
1056 | m.b = this.b;
|
1057 | m.c = this.c;
|
1058 | m.d = this.d;
|
1059 | m.e = this.e;
|
1060 | m.f = this.f;
|
1061 | }
|
1062 | return m;
|
1063 | },
|
1064 |
|
1065 | /**
|
1066 | * Convert current matrix into a `SVGMatrix`. If `SVGMatrix` is not
|
1067 | * supported, a `null` is returned.
|
1068 | *
|
1069 | * @returns {SVGMatrix}
|
1070 | * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGMatrix|MDN / SVGMatrix}
|
1071 | */
|
1072 | toSVGMatrix: function () {
|
1073 | var me = this,
|
1074 | svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
|
1075 | svgMatrix = null;
|
1076 |
|
1077 | if (svg) {
|
1078 | svgMatrix = svg.createSVGMatrix();
|
1079 | svgMatrix.a = me.a;
|
1080 | svgMatrix.b = me.b;
|
1081 | svgMatrix.c = me.c;
|
1082 | svgMatrix.d = me.d;
|
1083 | svgMatrix.e = me.e;
|
1084 | svgMatrix.f = me.f;
|
1085 | }
|
1086 |
|
1087 | return svgMatrix;
|
1088 | },
|
1089 |
|
1090 | /**
|
1091 | * Compares floating point values with some tolerance (epsilon)
|
1092 | * @param {number} f1 - float 1
|
1093 | * @param {number} f2 - float 2
|
1094 | * @returns {boolean}
|
1095 | * @private
|
1096 | */
|
1097 | _q: function (f1, f2) {
|
1098 | return Math.abs(f1 - f2) < 1e-14;
|
1099 | },
|
1100 |
|
1101 | /**
|
1102 | * Apply current absolute matrix to context if defined, to sync it.
|
1103 | * Apply current absolute matrix to element if defined, to sync it.
|
1104 | * @returns {Matrix}
|
1105 | * @private
|
1106 | */
|
1107 | _x: function () {
|
1108 | var me = this;
|
1109 |
|
1110 | if (me.context) me.context.setTransform(me.a, me.b, me.c, me.d, me.e, me.f);
|
1111 |
|
1112 | if (me._st) me._st[me._px] = me.useCSS3D ? me.toCSS3D() : me.toCSS(); // can be optimized pre-storing func ref.
|
1113 |
|
1114 | return me;
|
1115 | },
|
1116 | };
|
1117 |
|
1118 | // Node support
|
1119 | if (typeof exports !== 'undefined') exports.Matrix = Matrix;
|