/* MIT License Copyright (c) 2012 - 2021 jonobr1 / http://jonobr1.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * @name Two.Commands * @property {Object} - Map of possible path commands. Taken from the SVG specification. */ var Commands = { move: 'M', line: 'L', curve: 'C', arc: 'A', close: 'Z' }; var root; if (typeof window !== 'undefined') { root = window; } else if (typeof global !== 'undefined') { root = global; } else if (typeof self !== 'undefined') { root = self; } var root$1 = root; var Matrix$1; /** * @name Two.Utils.decomposeMatrix * @function * @param {Two.Matrix} matrix - The matrix to decompose. * @returns {Object} An object containing relevant skew values. * @description Decompose a 2D 3x3 Matrix to find the skew. */ var decomposeMatrix = function(matrix) { // TODO: Include skewX, skewY // https://math.stackexchange.com/questions/237369/given-this-transformation-matrix-how-do-i-decompose-it-into-translation-rotati/417813 // https://stackoverflow.com/questions/45159314/decompose-2d-transformation-matrix return { translateX: matrix.e, translateY: matrix.f, scaleX: Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b), scaleY: Math.sqrt(matrix.c * matrix.c + matrix.d * matrix.d), rotation: 180 * Math.atan2(matrix.b, matrix.a) / Math.PI }; }; var setMatrix = function(M) { Matrix$1 = M; }; /** * @name Two.Utils.getComputedMatrix * @function * @param {Two.Shape} object - The Two.js object that has a matrix property to calculate from. * @param {Two.Matrix} [matrix] - The matrix to apply calculated transformations to if available. * @returns {Two.Matrix} The computed matrix of a nested object. If no `matrix` was passed in arguments then a `new Two.Matrix` is returned. * @description Method to get the world space transformation of a given object in a Two.js scene. */ var getComputedMatrix = function(object, matrix) { matrix = (matrix && matrix.identity()) || new Matrix$1(); var parent = object, matrices = []; while (parent && parent._matrix) { matrices.push(parent._matrix); parent = parent.parent; } matrices.reverse(); for (var i = 0; i < matrices.length; i++) { var m = matrices[i]; var e = m.elements; matrix.multiply( e[0], e[1], e[2], e[3], e[4], e[5], e[6], e[7], e[8], e[9]); } return matrix; }; /** * @name Two.Utils.lerp * @function * @param {Number} a - Start value. * @param {Number} b - End value. * @param {Number} t - Zero-to-one value describing percentage between a and b. * @returns {Number} * @description Linear interpolation between two values `a` and `b` by an amount `t`. */ var lerp = function(a, b, t) { return t * (b - a) + a; }; /** * @name Two.Utils.mod * @function * @param {Number} v - The value to modulo * @param {Number} l - The value to modulo by * @returns {Number} * @description Modulo with added functionality to handle negative values in a positive manner. */ var mod = function(v, l) { while (v < 0) { v += l; } return v % l; }; var NumArray = root$1.Float32Array || Array; /** * @name Two.Utils.toFixed * @function * @param {Number} v - Any float * @returns {Number} That float trimmed to the third decimal place. * @description A pretty fast toFixed(3) alternative. * @see {@link http://jsperf.com/parsefloat-tofixed-vs-math-round/18} */ var toFixed = function(v) { return Math.floor(v * 1000000) / 1000000; }; var math = /*#__PURE__*/Object.freeze({ __proto__: null, decomposeMatrix: decomposeMatrix, getComputedMatrix: getComputedMatrix, setMatrix: setMatrix, lerp: lerp, mod: mod, NumArray: NumArray, toFixed: toFixed }); var slice = Array.prototype.slice; var isArrayLike = function(collection) { if (collection === null || collection === undefined) return false; var length = collection.length; // Arrays cannot hold more than 2^32 - 1 items return (typeof length == 'number' && length >= 0 && length < 4294967296); }; var _ = { isNaN: function(obj) { return typeof obj === 'number' && obj !== +obj; }, isElement: function(obj) { return !!(obj && obj.nodeType === 1); }, isObject: function(obj) { var type = typeof obj; return type === 'function' || type === 'object' && !!obj; }, extend: function(base) { var sources = slice.call(arguments, 1); for (var i = 0; i < sources.length; i++) { var obj = sources[i]; for (var k in obj) { base[k] = obj[k]; } } return base; }, defaults: function(base) { var sources = slice.call(arguments, 1); for (var i = 0; i < sources.length; i++) { var obj = sources[i]; for (var k in obj) { if (base[k] === void 0) { base[k] = obj[k]; } } } return base; }, each: function(obj, iteratee, context) { var ctx = context || this; var keys = !isArrayLike(obj) && Object.keys(obj); var length = (keys || obj).length; for (var i = 0; i < length; i++) { var k = keys ? keys[i] : i; iteratee.call(ctx, obj[k], k, obj); } return obj; }, /** * @name Two.Utils.performance * @property {Date} - A special `Date` like object to get the current millis of the session. Used internally to calculate time between frames. * e.g: `Utils.performance.now() // milliseconds since epoch` */ performance: ((root$1.performance && root$1.performance.now) ? root$1.performance : Date), }; /** * @name Two.Events * @class * @description Object inherited by many Two.js objects in order to facilitate custom events. */ var Events = { /** * @name Two.Events#on * @function * @param {String} [name] - The name of the event to bind a function to. * @param {Function} [handler] - The function to be invoked when the event is dispatched. * @description Call to add a listener to a specific event name. */ on: addEventListener, /** * @name Two.Events#off * @function * @param {String} [name] - The name of the event intended to be removed. * @param {Function} [handler] - The handler intended to be reomved. * @description Call to remove listeners from a specific event. If only `name` is passed then all the handlers attached to that `name` will be removed. If no arguments are passed then all handlers for every event on the obejct are removed. */ off: removeEventListener, /** * @name Two.Events#trigger * @function * @param {String} name - The name of the event to dispatch. * @param arguments - Anything can be passed after the name and those will be passed on to handlers attached to the event in the order they are passed. * @description Call to trigger a custom event. Any additional arguments passed after the name will be passed along to the attached handlers. */ trigger: function(name) { var scope = this; if (!scope._events) return scope; var args = Array.prototype.slice.call(arguments, 1); var events = scope._events[name]; if (events) dispatch(scope, events, args); return scope; }, listen: function(obj, name, handler) { var bound = this; if (obj) { var event = function () { handler.apply(bound, arguments); }; // Add references about the object that assigned this listener event.obj = obj; event.name = name; event.handler = handler; obj.on(name, event); } return bound; }, ignore: function(obj, name, handler) { var scope = this; obj.off(name, handler); return scope; }, /** * @name Two.Events.Types * @property {Object} - Object of different types of Two.js specific events. */ Types: { play: 'play', pause: 'pause', update: 'update', render: 'render', resize: 'resize', change: 'change', remove: 'remove', insert: 'insert', order: 'order', load: 'load' } }; /** * @name Two.Events.bind * @function * @description Alias for {@link Two.Events.on}. */ Events.bind = addEventListener; /** * @name Two.Events.unbind * @function * @description Alias for {@link Two.Events.off}. */ Events.unbind = removeEventListener; /** * @private * @returns {Two.Events} - Returns an instance of self for the purpose of chaining. */ function addEventListener(name, handler) { var scope = this; scope._events || (scope._events = {}); var list = scope._events[name] || (scope._events[name] = []); list.push(handler); return scope; } /** * @private * @returns {Two.Events} - Returns an instance of self for the purpose of chaining. */ function removeEventListener(name, handler) { var scope = this; if (!scope._events) { return scope; } if (!name && !handler) { scope._events = {}; return scope; } var names = name ? [name] : Object.keys(scope._events); for (var i = 0, l = names.length; i < l; i++) { name = names[i]; var list = scope._events[name]; if (list) { var events = []; if (handler) { for (var j = 0, k = list.length; j < k; j++) { var ev = list[j]; ev = ev.handler ? ev.handler : ev; if (handler && handler !== ev) { events.push(ev); } } } scope._events[name] = events; } } return scope; } function dispatch(obj, events, args) { var method; switch (args.length) { case 0: method = function(i) { events[i].call(obj, args[0]); }; break; case 1: method = function(i) { events[i].call(obj, args[0], args[1]); }; break; case 2: method = function(i) { events[i].call(obj, args[0], args[1], args[2]); }; break; case 3: method = function(i) { events[i].call(obj, args[0], args[1], args[2], args[3]); }; break; default: method = function(i) { events[i].apply(obj, args); }; } for (var i = 0; i < events.length; i++) { method(i); } } /** * @name Two.Vector * @class * @param {Number} [x=0] - Any number to represent the horizontal x-component of the vector. * @param {Number} [y=0] - Any number to represent the vertical y-component of the vector. * @description A class to store x / y component vector data. In addition to storing data `Two.Vector` has suped up methods for commonplace mathematical operations. */ function Vector(x, y) { /** * @name Two.Vector#x * @property {Number} - The horizontal x-component of the vector. */ this.x = x || 0; /** * @name Two.Vector#y * @property {Number} - The vertical y-component of the vector. */ this.y = y || 0; } _.extend(Vector, { /** * @name Two.Vector.zero * @readonly * @property {Two.Vector} - Handy reference to a vector with component values 0, 0 at all times. */ zero: new Vector(), /** * @name Two.Vector.add * @function * @param {Two.Vector} v1 * @param {Two.Vector} v2 * @returns {Two.Vector} * @description Add two vectors together. */ add: function(v1, v2) { return new Vector(v1.x + v2.x, v1.y + v2.y); }, /** * @name Two.Vector.sub * @function * @param {Two.Vector} v1 * @param {Two.Vector} v2 * @returns {Two.Vector} * @description Subtract two vectors: `v2` from `v1`. */ sub: function(v1, v2) { return new Vector(v1.x - v2.x, v1.y - v2.y); }, /** * @name Two.Vector.subtract * @function * @description Alias for {@link Two.Vector.sub}. */ subtract: function(v1, v2) { return Vector.sub(v1, v2); }, /** * @name Two.Vector.ratioBetween * @function * @param {Two.Vector} A * @param {Two.Vector} B * @returns {Number} The ratio betwen two points `v1` and `v2`. */ ratioBetween: function(v1, v2) { return (v1.x * v2.x + v1.y * v2.y) / (v1.length() * v2.length()); }, /** * @name Two.Vector.angleBetween * @function * @param {Two.Vector} v1 * @param {Two.Vector} v2 * @returns {Number} The angle between points `v1` and `v2`. */ angleBetween: function(v1, v2) { var dx, dy; if (arguments.length >= 4) { dx = arguments[0] - arguments[2]; dy = arguments[1] - arguments[3]; return Math.atan2(dy, dx); } dx = v1.x - v2.x; dy = v1.y - v2.y; return Math.atan2(dy, dx); }, /** * @name Two.Vector.distanceBetween * @function * @param {Two.Vector} v1 * @param {Two.Vector} v2 * @returns {Number} The distance between points `v1` and `v2`. Distance is always positive. */ distanceBetween: function(v1, v2) { return Math.sqrt(Vector.distanceBetweenSquared(v1, v2)); }, /** * @name Two.Vector.distanceBetweenSquared * @function * @param {Two.Vector} v1 * @param {Two.Vector} v2 * @returns {Number} The squared distance between points `v1` and `v2`. */ distanceBetweenSquared: function(v1, v2) { var dx = v1.x - v2.x; var dy = v1.y - v2.y; return dx * dx + dy * dy; }, /** * @name Two.Vector.MakeObservable * @function * @param {Object} object - The object to make observable. * @description Convenience function to apply observable qualities of a {@link Two.Vector} to any object. Handy if you'd like to extend the {@link Two.Vector} class on a custom class. */ MakeObservable: function(object) { // /** // * Override Backbone bind / on in order to add properly broadcasting. // * This allows Two.Vector to not broadcast events unless event listeners // * are explicity bound to it. // */ object.bind = object.on = function() { if (!this._bound) { this._x = this.x; this._y = this.y; Object.defineProperty(this, 'x', xgs); Object.defineProperty(this, 'y', ygs); _.extend(this, BoundProto); this._bound = true; // Reserved for event initialization check } Events.bind.apply(this, arguments); return this; }; } }); _.extend(Vector.prototype, Events, { constructor: Vector, /** * @name Two.Vector#set * @function * @param {Number} x * @param {Number} y * @description Set the x / y components of a vector to specific number values. */ set: function(x, y) { this.x = x; this.y = y; return this; }, /** * @name Two.Vector#copy * @function * @param {Two.Vector} v * @description Copy the x / y components of another object `v`. */ copy: function(v) { this.x = v.x; this.y = v.y; return this; }, /** * @name Two.Vector#clear * @function * @description Set the x / y component values of the vector to zero. */ clear: function() { this.x = 0; this.y = 0; return this; }, /** * @name Two.Vector#clone * @function * @description Create a new vector and copy the existing values onto the newly created instance. */ clone: function() { return new Vector(this.x, this.y); }, /** * @name Two.Vector#add * @function * @param {Two.Vector} v * @description Add an object with x / y component values to the instance. * @overloaded */ /** * @name Two.Vector#add * @function * @param {Number} v * @description Add the **same** number to both x / y component values of the instance. * @overloaded */ /** * @name Two.Vector#add * @function * @param {Number} x * @param {Number} y * @description Add `x` / `y` values to their respective component value on the instance. * @overloaded */ add: function(x, y) { if (arguments.length <= 0) { return this; } else if (arguments.length <= 1) { if (typeof x === 'number') { this.x += x; this.y += x; } else if (x && typeof x.x === 'number' && typeof x.y === 'number') { this.x += x.x; this.y += x.y; } } else { this.x += x; this.y += y; } return this; }, /** * @name Two.Vector#addSelf * @function * @description Alias for {@link Two.Vector.add}. */ addSelf: function(v) { return this.add.apply(this, arguments); }, /** * @name Two.Vector#sub * @function * @param {Two.Vector} v * @description Subtract an object with x / y component values to the instance. * @overloaded */ /** * @name Two.Vector#sub * @function * @param {Number} v * @description Subtract the **same** number to both x / y component values of the instance. * @overloaded */ /** * @name Two.Vector#sub * @function * @param {Number} x * @param {Number} y * @description Subtract `x` / `y` values to their respective component value on the instance. * @overloaded */ sub: function(x, y) { if (arguments.length <= 0) { return this; } else if (arguments.length <= 1) { if (typeof x === 'number') { this.x -= x; this.y -= x; } else if (x && typeof x.x === 'number' && typeof x.y === 'number') { this.x -= x.x; this.y -= x.y; } } else { this.x -= x; this.y -= y; } return this; }, /** * @name Two.Vector#subtract * @function * @description Alias for {@link Two.Vector.sub}. */ subtract: function() { return this.sub.apply(this, arguments); }, /** * @name Two.Vector#subSelf * @function * @description Alias for {@link Two.Vector.sub}. */ subSelf: function(v) { return this.sub.apply(this, arguments); }, /** * @name Two.Vector#subtractSelf * @function * @description Alias for {@link Two.Vector.sub}. */ subtractSelf: function(v) { return this.sub.apply(this, arguments); }, /** * @name Two.Vector#multiply * @function * @param {Two.Vector} v * @description Multiply an object with x / y component values to the instance. * @overloaded */ /** * @name Two.Vector#multiply * @function * @param {Number} v * @description Multiply the **same** number to both x / y component values of the instance. * @overloaded */ /** * @name Two.Vector#multiply * @function * @param {Number} x * @param {Number} y * @description Multiply `x` / `y` values to their respective component value on the instance. * @overloaded */ multiply: function(x, y) { if (arguments.length <= 0) { return this; } else if (arguments.length <= 1) { if (typeof x === 'number') { this.x *= x; this.y *= x; } else if (x && typeof x.x === 'number' && typeof x.y === 'number') { this.x *= x.x; this.y *= x.y; } } else { this.x *= x; this.y *= y; } return this; }, /** * @name Two.Vector#multiplySelf * @function * @description Alias for {@link Two.Vector.multiply}. */ multiplySelf: function(v) { return this.multiply.apply(this, arguments); }, /** * @name Two.Vector#multiplyScalar * @function * @param {Number} s - The scalar to multiply by. * @description Mulitiply the vector by a single number. Shorthand to call {@link Two.Vector#multiply} directly. */ multiplyScalar: function(s) { return this.multiply(s); }, /** * @name Two.Vector#divide * @function * @param {Two.Vector} v * @description Divide an object with x / y component values to the instance. * @overloaded */ /** * @name Two.Vector#divide * @function * @param {Number} v * @description Divide the **same** number to both x / y component values of the instance. * @overloaded */ /** * @name Two.Vector#divide * @function * @param {Number} x * @param {Number} y * @description Divide `x` / `y` values to their respective component value on the instance. * @overloaded */ divide: function(x, y) { if (arguments.length <= 0) { return this; } else if (arguments.length <= 1) { if (typeof x === 'number') { this.x /= x; this.y /= x; } else if (x && typeof x.x === 'number' && typeof x.y === 'number') { this.x /= x.x; this.y /= x.y; } } else { this.x /= x; this.y /= y; } if (_.isNaN(this.x)) { this.x = 0; } if (_.isNaN(this.y)) { this.y = 0; } return this; }, /** * @name Two.Vector#divideSelf * @function * @description Alias for {@link Two.Vector.divide}. */ divideSelf: function(v) { return this.divide.apply(this, arguments); }, /** * @name Two.Vector#divideScalar * @function * @param {Number} s - The scalar to divide by. * @description Divide the vector by a single number. Shorthand to call {@link Two.Vector#divide} directly. */ divideScalar: function(s) { return this.divide(s); }, /** * @name Two.Vector#negate * @function * @description Invert each component's sign value. */ negate: function() { return this.multiply(-1); }, /** * @name Two.Vector#negate * @function * @returns {Number} * @description Get the [dot product](https://en.wikipedia.org/wiki/Dot_product) of the vector. */ dot: function(v) { return this.x * v.x + this.y * v.y; }, /** * @name Two.Vector#length * @function * @returns {Number} * @description Get the length of a vector. */ length: function() { return Math.sqrt(this.lengthSquared()); }, /** * @name Two.Vector#lengthSquared * @function * @returns {Number} * @description Get the length of the vector to the power of two. Widely used as less expensive than {@link Two.Vector#length}, because it isn't square-rooting any numbers. */ lengthSquared: function() { return this.x * this.x + this.y * this.y; }, /** * @name Two.Vector#normalize * @function * @description Normalize the vector from negative one to one. */ normalize: function() { return this.divideScalar(this.length()); }, /** * @name Two.Vector#distanceTo * @function * @returns {Number} * @description Get the distance between two vectors. */ distanceTo: function(v) { return Math.sqrt(this.distanceToSquared(v)); }, /** * @name Two.Vector#distanceToSquared * @function * @returns {Number} * @description Get the distance between two vectors to the power of two. Widely used as less expensive than {@link Two.Vector#distanceTo}, because it isn't square-rooting any numbers. */ distanceToSquared: function(v) { var dx = this.x - v.x, dy = this.y - v.y; return dx * dx + dy * dy; }, /** * @name Two.Vector#setLength * @function * @param {Number} l - length to set vector to. * @description Set the length of a vector. */ setLength: function(l) { return this.normalize().multiplyScalar(l); }, /** * @name Two.Vector#equals * @function * @param {Two.Vector} v - The vector to compare against. * @param {Number} [eps=0.0001] - An options epsilon for precision. * @returns {Boolean} * @description Qualify if one vector roughly equal another. With a margin of error defined by epsilon. */ equals: function(v, eps) { eps = (typeof eps === 'undefined') ? 0.0001 : eps; return (this.distanceTo(v) < eps); }, /** * @name Two.Vector#lerp * @function * @param {Two.Vector} v - The destination vector to step towards. * @param {Number} t - The zero to one value of how close the current vector gets to the destination vector. * @description Linear interpolate one vector to another by an amount `t` defined as a zero to one number. * @see [Matt DesLauriers](https://twitter.com/mattdesl/status/1031305279227478016) has a good thread about this. */ lerp: function(v, t) { var x = (v.x - this.x) * t + this.x; var y = (v.y - this.y) * t + this.y; return this.set(x, y); }, /** * @name Two.Vector#isZero * @function * @param {Number} [eps=0.0001] - Optional precision amount to check against. * @returns {Boolean} * @description Check to see if vector is roughly zero, based on the `epsilon` precision value. */ isZero: function(eps) { eps = (typeof eps === 'undefined') ? 0.0001 : eps; return (this.length() < eps); }, /** * @name Two.Vector#toString * @function * @returns {String} * @description Return a comma-separated string of x, y value. Great for storing in a database. */ toString: function() { return this.x + ', ' + this.y; }, /** * @name Two.Vector#toObject * @function * @returns {Object} * @description Return a JSON compatible plain object that represents the vector. */ toObject: function() { return { x: this.x, y: this.y }; }, /** * @name Two.Vector#rotate * @function * @param {Number} Number - The amoun to rotate the vector by. * @description Rotate a vector. */ rotate: function(Number) { var cos = Math.cos(Number); var sin = Math.sin(Number); this.x = this.x * cos - this.y * sin; this.y = this.x * sin + this.y * cos; return this; } }); // The same set of prototypical functions, but using the underlying // getter or setter for `x` and `y` values. This set of functions // is used instead of the previously documented ones above when // Two.Vector#bind is invoked and there is event dispatching processed // on x / y property changes. var BoundProto = { constructor: Vector, set: function(x, y) { this._x = x; this._y = y; return this.trigger(Events.Types.change); }, copy: function(v) { this._x = v.x; this._y = v.y; return this.trigger(Events.Types.change); }, clear: function() { this._x = 0; this._y = 0; return this.trigger(Events.Types.change); }, clone: function() { return new Vector(this._x, this._y); }, add: function(x, y) { if (arguments.length <= 0) { return this; } else if (arguments.length <= 1) { if (typeof x === 'number') { this._x += x; this._y += x; } else if (x && typeof x.x === 'number' && typeof x.y === 'number') { this._x += x.x; this._y += x.y; } } else { this._x += x; this._y += y; } return this.trigger(Events.Types.change); }, sub: function(x, y) { if (arguments.length <= 0) { return this; } else if (arguments.length <= 1) { if (typeof x === 'number') { this._x -= x; this._y -= x; } else if (x && typeof x.x === 'number' && typeof x.y === 'number') { this._x -= x.x; this._y -= x.y; } } else { this._x -= x; this._y -= y; } return this.trigger(Events.Types.change); }, multiply: function(x, y) { if (arguments.length <= 0) { return this; } else if (arguments.length <= 1) { if (typeof x === 'number') { this._x *= x; this._y *= x; } else if (x && typeof x.x === 'number' && typeof x.y === 'number') { this._x *= x.x; this._y *= x.y; } } else { this._x *= x; this._y *= y; } return this.trigger(Events.Types.change); }, divide: function(x, y) { if (arguments.length <= 0) { return this; } else if (arguments.length <= 1) { if (typeof x === 'number') { this._x /= x; this._y /= x; } else if (x && typeof x.x === 'number' && typeof x.y === 'number') { this._x /= x.x; this._y /= x.y; } } else { this._x /= x; this._y /= y; } if (_.isNaN(this._x)) { this._x = 0; } if (_.isNaN(this._y)) { this._y = 0; } return this.trigger(Events.Types.change); }, dot: function(v) { return this._x * v.x + this._y * v.y; }, lengthSquared: function() { return this._x * this._x + this._y * this._y; }, distanceToSquared: function(v) { var dx = this._x - v.x, dy = this._y - v.y; return dx * dx + dy * dy; }, lerp: function(v, t) { var x = (v.x - this._x) * t + this._x; var y = (v.y - this._y) * t + this._y; return this.set(x, y); }, toString: function() { return this._x + ', ' + this._y; }, toObject: function() { return { x: this._x, y: this._y }; }, rotate: function (Number) { var cos = Math.cos(Number); var sin = Math.sin(Number); this._x = this._x * cos - this._y * sin; this._y = this._x * sin + this._y * cos; return this; } }; var xgs = { enumerable: true, get: function() { return this._x; }, set: function(v) { this._x = v; this.trigger(Events.Types.change, 'x'); } }; var ygs = { enumerable: true, get: function() { return this._y; }, set: function(v) { this._y = v; this.trigger(Events.Types.change, 'y'); } }; Vector.MakeObservable(Vector.prototype); /** * @class * @name Two.Anchor * @param {Number} [x=0] - The x position of the root anchor point. * @param {Number} [y=0] - The y position of the root anchor point. * @param {Number} [lx=0] - The x position of the left handle point. * @param {Number} [ly=0] - The y position of the left handle point. * @param {Number} [rx=0] - The x position of the right handle point. * @param {Number} [ry=0] - The y position of the right handle point. * @param {String} [command=Two.Commands.move] - The command to describe how to render. Applicable commands are {@link Two.Commands} * @extends Two.Vector * @description An object that holds 3 {@link Two.Vector}s, the anchor point and its corresponding handles: `left` and `right`. In order to properly describe the bezier curve about the point there is also a command property to describe what type of drawing should occur when Two.js renders the anchors. */ function Anchor(x, y, lx, ly, rx, ry, command) { Vector.call(this, x, y); this._broadcast = (function() { this.trigger(Events.Types.change); }).bind(this); this._command = command || Commands.move; this._relative = true; var ilx = typeof lx === 'number'; var ily = typeof ly === 'number'; var irx = typeof rx === 'number'; var iry = typeof ry === 'number'; // Append the `controls` object only if control points are specified, // keeping the Two.Anchor inline with a Two.Vector until it needs to // evolve beyond those functions - e.g: a simple 2 component vector. if (ilx || ily || irx || iry) { Anchor.AppendCurveProperties(this); } if (ilx) { this.controls.left.x = lx; } if (ily) { this.controls.left.y = ly; } if (irx) { this.controls.right.x = rx; } if (iry) { this.controls.right.y = ry; } } _.extend(Anchor, { /** * @name Two.Anchor.AppendCurveProperties * @function * @param {Two.Anchor} anchor - The instance to append the `control`object to. * @description Adds the `controls` property as an object with `left` and `right` properties to access the bezier control handles that define how the curve is drawn. It also sets the `relative` property to `true` making vectors in the `controls` object relative to their corresponding root anchor point. */ AppendCurveProperties: function(anchor) { anchor.relative = true; /** * @name Two.Anchor#controls * @property {Object} controls * @description An plain object that holds the controls handles for a {@link Two.Anchor}. */ anchor.controls = {}; /** * @name Two.Anchor#controls#left * @property {Two.Vector} left * @description The "left" control point to define handles on a bezier curve. */ anchor.controls.left = new Vector(0, 0); /** * @name Two.Anchor#controls#right * @property {Two.Vector} right * @description The "left" control point to define handles on a bezier curve. */ anchor.controls.right = new Vector(0, 0); }, /** * @name Two.Anchor.MakeObservable * @function * @param {Object} object - The object to make observable. * @description Convenience function to apply observable qualities of a {@link Two.Anchor} to any object. Handy if you'd like to extend the {@link Two.Anchor} class on a custom class. */ MakeObservable: function(object) { /** * @name Two.Anchor#command * @property {Two.Commands} * @description A draw command associated with the anchor point. */ Object.defineProperty(object, 'command', { enumerable: true, get: function() { return this._command; }, set: function(c) { this._command = c; if (this._command === Commands.curve && !_.isObject(this.controls)) { Anchor.AppendCurveProperties(this); } this.trigger(Events.Types.change); } }); /** * @name Two.Anchor#relative * @property {Boolean} * @description A boolean to render control points relative to the root anchor point or in global coordinate-space to the rest of the scene. */ Object.defineProperty(object, 'relative', { enumerable: true, get: function() { return this._relative; }, set: function(b) { if (this._relative != b) { this._relative = !!b; this.trigger(Events.Types.change); } } }); _.extend(object, Vector.prototype, AnchorProto); // Make it possible to bind and still have the Anchor specific // inheritance from Two.Vector. In this case relying on `Two.Vector` // to do much of the heavy event-listener binding / unbinding. object.bind = object.on = function() { var bound = this._bound; Vector.prototype.bind.apply(this, arguments); if (!bound) { _.extend(this, AnchorProto); } }; } }); var AnchorProto = { constructor: Anchor, /** * @name Two.Anchor#listen * @function * @description Convenience method used mainly by {@link Two.Path#vertices} to listen and propagate changes from control points up to their respective anchors and further if necessary. */ listen: function() { if (!_.isObject(this.controls)) { Anchor.AppendCurveProperties(this); } this.controls.left.bind(Events.Types.change, this._broadcast); this.controls.right.bind(Events.Types.change, this._broadcast); return this; }, /** * @name Two.Anchor#ignore * @function * @description Convenience method used mainly by {@link Two.Path#vertices} to ignore changes from a specific anchor's control points. */ ignore: function() { this.controls.left.unbind(Events.Types.change, this._broadcast); this.controls.right.unbind(Events.Types.change, this._broadcast); return this; }, /** * @name Two.Anchor#copy * @function * @param {Two.Anchor} v - The anchor to apply values to. * @description Copy the properties of one {@link Two.Anchor} onto another. */ copy: function(v) { this.x = v.x; this.y = v.y; if (typeof v.command === 'string') { this.command = v.command; } if (_.isObject(v.controls)) { if (!_.isObject(this.controls)) { Anchor.AppendCurveProperties(this); } // TODO: Do we need to listen here? this.controls.left.copy(v.controls.left); this.controls.right.copy(v.controls.right); } if (typeof v.relative === 'boolean') { this.relative = v.relative; } // TODO: Hack for `Two.Commands.arc` if (this.command === Commands.arc) { this.rx = v.rx; this.ry = v.ry; this.xAxisRotation = v.xAxisRotation; this.largeArcFlag = v.largeArcFlag; this.sweepFlag = v.sweepFlag; } return this; }, /** * @name Two.Anchor#clone * @function * @returns {Two.Anchor} * @description Create a new {@link Two.Anchor}, set all its values to the current instance and return it for use. */ clone: function() { var controls = this.controls; var clone = new Anchor( this.x, this.y, controls && controls.left.x, controls && controls.left.y, controls && controls.right.x, controls && controls.right.y, this.command ); clone.relative = this._relative; return clone; }, /** * @name Two.Anchor#toObject * @function * @returns {Object} - An object with properties filled out to mirror {@link Two.Anchor}. * @description Create a JSON compatible plain object of the current instance. Intended for use with storing values in a database. */ toObject: function() { var o = { x: this.x, y: this.y }; if (this._command) { o.command = this._command; } if (this._relative) { o.relative = this._relative; } if (this.controls) { o.controls = { left: this.controls.left.toObject(), right: this.controls.right.toObject() }; } return o; }, /** * @name Two.Anchor#toString * @function * @returns {String} - A String with comma-separated values reflecting the various values on the current instance. * @description Create a string form of the current instance. Intended for use with storing values in a database. This is lighter to store than the JSON compatible {@link Two.Anchor#toObject}. */ toString: function() { if (!this.controls) { return [this._x, this._y].join(', '); } return [this._x, this._y, this.controls.left.x, this.controls.left.y, this.controls.right.x, this.controls.right.y, this._command, this._relative ? 1 : 0].join(', '); } }; Anchor.MakeObservable(Anchor.prototype); var count = 0; var Constants = { /** * @name Two.nextFrameID * @property {Number} * @description The id of the next requestAnimationFrame function. */ nextFrameID: null, // Primitive /** * @name Two.Types * @property {Object} - The different rendering types available in the library. */ Types: { webgl: 'WebGLRenderer', svg: 'SVGRenderer', canvas: 'CanvasRenderer' }, /** * @name Two.Version * @property {String} - The current working version of the library. */ Version: 'v0.7.6', /** * @name Two.PublishDate * @property {String} - The automatically generated publish date in the build process to verify version release candidates. */ PublishDate: '2021-06-08T20:19:33.699Z', /** * @name Two.Identifier * @property {String} - String prefix for all Two.js object's ids. This trickles down to SVG ids. */ Identifier: 'two-', /** * @name Two.Resolution * @property {Number} - Default amount of vertices to be used for interpreting Arcs and ArcSegments. */ Resolution: 12, /** * @name Two.AutoCalculateImportedMatrices * @property {Boolean} - When importing SVGs through the {@link two#interpret} and {@link two#load}, this boolean determines whether Two.js infers and then overrides the exact transformation matrix of the reference SVG. * @nota-bene `false` copies the exact transformation matrix values, but also sets the path's `matrix.manual = true`. */ AutoCalculateImportedMatrices: true, /** * @name Two.Instances * @property {Two[]} - Registered list of all Two.js instances in the current session. */ Instances: [], /** * @function Two.uniqueId * @description Simple method to access an incrementing value. Used for `id` allocation on all Two.js objects. * @returns {Number} Ever increasing Number. */ uniqueId: function() { return count++; } }; var HALF_PI$3 = Math.PI / 2; /** * @name Two.Utils.Curve * @property {Object} - Additional utility constant variables related to curve math and calculations. */ var Curve = { CollinearityEpsilon: Math.pow(10, -30), RecursionLimit: 16, CuspLimit: 0, Tolerance: { distance: 0.25, angle: 0, epsilon: Number.EPSILON }, // Lookup tables for abscissas and weights with values for n = 2 .. 16. // As values are symmetric, only store half of them and adapt algorithm // to factor in symmetry. abscissas: [ [ 0.5773502691896257645091488], [0,0.7745966692414833770358531], [ 0.3399810435848562648026658,0.8611363115940525752239465], [0,0.5384693101056830910363144,0.9061798459386639927976269], [ 0.2386191860831969086305017,0.6612093864662645136613996,0.9324695142031520278123016], [0,0.4058451513773971669066064,0.7415311855993944398638648,0.9491079123427585245261897], [ 0.1834346424956498049394761,0.5255324099163289858177390,0.7966664774136267395915539,0.9602898564975362316835609], [0,0.3242534234038089290385380,0.6133714327005903973087020,0.8360311073266357942994298,0.9681602395076260898355762], [ 0.1488743389816312108848260,0.4333953941292471907992659,0.6794095682990244062343274,0.8650633666889845107320967,0.9739065285171717200779640], [0,0.2695431559523449723315320,0.5190961292068118159257257,0.7301520055740493240934163,0.8870625997680952990751578,0.9782286581460569928039380], [ 0.1252334085114689154724414,0.3678314989981801937526915,0.5873179542866174472967024,0.7699026741943046870368938,0.9041172563704748566784659,0.9815606342467192506905491], [0,0.2304583159551347940655281,0.4484927510364468528779129,0.6423493394403402206439846,0.8015780907333099127942065,0.9175983992229779652065478,0.9841830547185881494728294], [ 0.1080549487073436620662447,0.3191123689278897604356718,0.5152486363581540919652907,0.6872929048116854701480198,0.8272013150697649931897947,0.9284348836635735173363911,0.9862838086968123388415973], [0,0.2011940939974345223006283,0.3941513470775633698972074,0.5709721726085388475372267,0.7244177313601700474161861,0.8482065834104272162006483,0.9372733924007059043077589,0.9879925180204854284895657], [ 0.0950125098376374401853193,0.2816035507792589132304605,0.4580167776572273863424194,0.6178762444026437484466718,0.7554044083550030338951012,0.8656312023878317438804679,0.9445750230732325760779884,0.9894009349916499325961542] ], weights: [ [1], [0.8888888888888888888888889,0.5555555555555555555555556], [0.6521451548625461426269361,0.3478548451374538573730639], [0.5688888888888888888888889,0.4786286704993664680412915,0.2369268850561890875142640], [0.4679139345726910473898703,0.3607615730481386075698335,0.1713244923791703450402961], [0.4179591836734693877551020,0.3818300505051189449503698,0.2797053914892766679014678,0.1294849661688696932706114], [0.3626837833783619829651504,0.3137066458778872873379622,0.2223810344533744705443560,0.1012285362903762591525314], [0.3302393550012597631645251,0.3123470770400028400686304,0.2606106964029354623187429,0.1806481606948574040584720,0.0812743883615744119718922], [0.2955242247147528701738930,0.2692667193099963550912269,0.2190863625159820439955349,0.1494513491505805931457763,0.0666713443086881375935688], [0.2729250867779006307144835,0.2628045445102466621806889,0.2331937645919904799185237,0.1862902109277342514260976,0.1255803694649046246346943,0.0556685671161736664827537], [0.2491470458134027850005624,0.2334925365383548087608499,0.2031674267230659217490645,0.1600783285433462263346525,0.1069393259953184309602547,0.0471753363865118271946160], [0.2325515532308739101945895,0.2262831802628972384120902,0.2078160475368885023125232,0.1781459807619457382800467,0.1388735102197872384636018,0.0921214998377284479144218,0.0404840047653158795200216], [0.2152638534631577901958764,0.2051984637212956039659241,0.1855383974779378137417166,0.1572031671581935345696019,0.1215185706879031846894148,0.0801580871597602098056333,0.0351194603317518630318329], [0.2025782419255612728806202,0.1984314853271115764561183,0.1861610000155622110268006,0.1662692058169939335532009,0.1395706779261543144478048,0.1071592204671719350118695,0.0703660474881081247092674,0.0307532419961172683546284], [0.1894506104550684962853967,0.1826034150449235888667637,0.1691565193950025381893121,0.1495959888165767320815017,0.1246289712555338720524763,0.0951585116824927848099251,0.0622535239386478928628438,0.0271524594117540948517806] ] }; /** * @name Two.Utils.getComponentOnCubicBezier * @function * @param {Number} t - Zero-to-one value describing what percentage to calculate. * @param {Number} a - The firt point's component value. * @param {Number} b - The first point's bezier component value. * @param {Number} c - The second point's bezier component value. * @param {Number} d - The second point's component value. * @returns {Number} The coordinate value for a specific component along a cubic bezier curve by `t`. */ var getComponentOnCubicBezier = function(t, a, b, c, d) { var k = 1 - t; return (k * k * k * a) + (3 * k * k * t * b) + (3 * k * t * t * c) + (t * t * t * d); }; /** * @name Two.Utils.subdivide * @function * @param {Number} x1 - x position of first anchor point. * @param {Number} y1 - y position of first anchor point. * @param {Number} x2 - x position of first anchor point's "right" bezier handle. * @param {Number} y2 - y position of first anchor point's "right" bezier handle. * @param {Number} x3 - x position of second anchor point's "left" bezier handle. * @param {Number} y3 - y position of second anchor point's "left" bezier handle. * @param {Number} x4 - x position of second anchor point. * @param {Number} y4 - y position of second anchor point. * @param {Number} [limit=Two.Utils.Curve.RecursionLimit] - The amount of vertices to create by subdividing. * @returns {Anchor[]} A list of anchor points ordered in between `x1`, `y1` and `x4`, `y4` * @description Given 2 points (a, b) and corresponding control point for each return an array of points that represent points plotted along the curve. The number of returned points is determined by `limit`. */ var subdivide = function(x1, y1, x2, y2, x3, y3, x4, y4, limit) { limit = limit || Curve.RecursionLimit; var amount = limit + 1; // TODO: Abstract 0.001 to a limiting variable // Don't recurse if the end points are identical if (Math.abs(x1 - x4) < 0.001 && Math.abs(y1 - y4) < 0.001) { return [new Anchor(x4, y4)]; } var result = []; for (var i = 0; i < amount; i++) { var t = i / amount; var x = getComponentOnCubicBezier(t, x1, x2, x3, x4); var y = getComponentOnCubicBezier(t, y1, y2, y3, y4); result.push(new Anchor(x, y)); } return result; }; /** * @name Two.Utils.getCurveLength * @function * @param {Number} x1 - x position of first anchor point. * @param {Number} y1 - y position of first anchor point. * @param {Number} x2 - x position of first anchor point's "right" bezier handle. * @param {Number} y2 - y position of first anchor point's "right" bezier handle. * @param {Number} x3 - x position of second anchor point's "left" bezier handle. * @param {Number} y3 - y position of second anchor point's "left" bezier handle. * @param {Number} x4 - x position of second anchor point. * @param {Number} y4 - y position of second anchor point. * @param {Number} [limit=Two.Utils.Curve.RecursionLimit] - The amount of vertices to create by subdividing. * @returns {Number} The length of a curve. * @description Given 2 points (a, b) and corresponding control point for each, return a float that represents the length of the curve using Gauss-Legendre algorithm. Limit iterations of calculation by `limit`. */ var getCurveLength$1 = function(x1, y1, x2, y2, x3, y3, x4, y4, limit) { // TODO: Better / fuzzier equality check // Linear calculation if (x1 === x2 && y1 === y2 && x3 === x4 && y3 === y4) { var dx = x4 - x1; var dy = y4 - y1; return Math.sqrt(dx * dx + dy * dy); } // Calculate the coefficients of a Bezier derivative. var ax = 9 * (x2 - x3) + 3 * (x4 - x1), bx = 6 * (x1 + x3) - 12 * x2, cx = 3 * (x2 - x1), ay = 9 * (y2 - y3) + 3 * (y4 - y1), by = 6 * (y1 + y3) - 12 * y2, cy = 3 * (y2 - y1); var integrand = function(t) { // Calculate quadratic equations of derivatives for x and y var dx = (ax * t + bx) * t + cx, dy = (ay * t + by) * t + cy; return Math.sqrt(dx * dx + dy * dy); }; return integrate( integrand, 0, 1, limit || Curve.RecursionLimit ); }; /** * @name Two.Utils.getCurveBoundingBox * @function * @param {Number} x1 - x position of first anchor point. * @param {Number} y1 - y position of first anchor point. * @param {Number} x2 - x position of first anchor point's "right" bezier handle. * @param {Number} y2 - y position of first anchor point's "right" bezier handle. * @param {Number} x3 - x position of second anchor point's "left" bezier handle. * @param {Number} y3 - y position of second anchor point's "left" bezier handle. * @param {Number} x4 - x position of second anchor point. * @param {Number} y4 - y position of second anchor point. * @returns {Object} Object contains min and max `x` / `y` bounds. * @see {@link https://github.com/adobe-webplatform/Snap.svg/blob/master/src/path.js#L856} */ var getCurveBoundingBox = function(x1, y1, x2, y2, x3, y3, x4, y4) { var tvalues = []; var bounds = [[], []]; var a, b, c, t, t1, t2, b2ac, sqrtb2ac; for (var i = 0; i < 2; ++i) { if (i == 0) { b = 6 * x1 - 12 * x2 + 6 * x3; a = -3 * x1 + 9 * x2 - 9 * x3 + 3 * x4; c = 3 * x2 - 3 * x1; } else { b = 6 * y1 - 12 * y2 + 6 * y3; a = -3 * y1 + 9 * y2 - 9 * y3 + 3 * y4; c = 3 * y2 - 3 * y1; } if (Math.abs(a) < 1e-12) { if (Math.abs(b) < 1e-12) { continue; } t = -c / b; if (0 < t && t < 1) { tvalues.push(t); } continue; } b2ac = b * b - 4 * c * a; sqrtb2ac = Math.sqrt(b2ac); if (b2ac < 0) { continue; } t1 = (-b + sqrtb2ac) / (2 * a); if (0 < t1 && t1 < 1) { tvalues.push(t1); } t2 = (-b - sqrtb2ac) / (2 * a); if (0 < t2 && t2 < 1) { tvalues.push(t2); } } var j = tvalues.length; var jlen = j; var mt; while (j--) { t = tvalues[j]; mt = 1 - t; bounds[0][j] = mt * mt * mt * x1 + 3 * mt * mt * t * x2 + 3 * mt * t * t * x3 + t * t * t * x4; bounds[1][j] = mt * mt * mt * y1 + 3 * mt * mt * t * y2 + 3 * mt * t * t * y3 + t * t * t * y4; } bounds[0][jlen] = x1; bounds[1][jlen] = y1; bounds[0][jlen + 1] = x4; bounds[1][jlen + 1] = y4; bounds[0].length = bounds[1].length = jlen + 2; return { min: { x: Math.min.apply(0, bounds[0]), y: Math.min.apply(0, bounds[1]) }, max: { x: Math.max.apply(0, bounds[0]), y: Math.max.apply(0, bounds[1]) } }; }; /** * @name Two.Utils.integrate * @function * @param {Function} f * @param {Number} a * @param {Number} b * @param {Number} n * @description Integration for `getCurveLength` calculations. * @see [Paper.js](@link https://github.com/paperjs/paper.js/blob/master/src/util/Numerical.js#L101) */ var integrate = function(f, a, b, n) { var x = Curve.abscissas[n - 2], w = Curve.weights[n - 2], A = 0.5 * (b - a), B = A + a, i = 0, m = (n + 1) >> 1, sum = n & 1 ? w[i++] * f(B) : 0; // Handle odd n while (i < m) { var Ax = A * x[i]; sum += w[i++] * (f(B + Ax) + f(B - Ax)); } return A * sum; }; /** * @name Two.Utils.getCurveFromPoints * @function * @param {Anchor[]} points * @param {Boolean} closed * @description Sets the bezier handles on {@link Anchor}s in the `points` list with estimated values to create a catmull-rom like curve. Used by {@link Two.Path#plot}. */ var getCurveFromPoints = function(points, closed) { var l = points.length, last = l - 1; for (var i = 0; i < l; i++) { var point = points[i]; if (!_.isObject(point.controls)) { Anchor.AppendCurveProperties(point); } var prev = closed ? mod(i - 1, l) : Math.max(i - 1, 0); var next = closed ? mod(i + 1, l) : Math.min(i + 1, last); var a = points[prev]; var b = point; var c = points[next]; getControlPoints(a, b, c); b.command = i === 0 ? Commands.move : Commands.curve; } }; /** * @name Two.Utils.getControlPoints * @function * @param {Anchor} a * @param {Anchor} b * @param {Anchor} c * @returns {Anchor} Returns the passed middle point `b`. * @description Given three coordinates set the control points for the middle, b, vertex based on its position with the adjacent points. */ var getControlPoints = function(a, b, c) { var a1 = Vector.angleBetween(a, b); var a2 = Vector.angleBetween(c, b); var d1 = Vector.distanceBetween(a, b); var d2 = Vector.distanceBetween(c, b); var mid = (a1 + a2) / 2; // TODO: Issue 73 if (d1 < 0.0001 || d2 < 0.0001) { if (typeof b.relative === 'boolean' && !b.relative) { b.controls.left.copy(b); b.controls.right.copy(b); } return b; } d1 *= 0.33; // Why 0.33? d2 *= 0.33; if (a2 < a1) { mid += HALF_PI$3; } else { mid -= HALF_PI$3; } b.controls.left.x = Math.cos(mid) * d1; b.controls.left.y = Math.sin(mid) * d1; mid -= Math.PI; b.controls.right.x = Math.cos(mid) * d2; b.controls.right.y = Math.sin(mid) * d2; if (typeof b.relative === 'boolean' && !b.relative) { b.controls.left.x += b.x; b.controls.left.y += b.y; b.controls.right.x += b.x; b.controls.right.y += b.y; } return b; }; /** * @name Two.Utils.getReflection * @function * @param {Vector} a * @param {Vector} b * @param {Boolean} [relative=false] * @returns {Vector} New {@link Vector} that represents the reflection point. * @description Get the reflection of a point `b` about point `a`. Where `a` is in absolute space and `b` is relative to `a`. * @see {@link http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes} */ var getReflection = function(a, b, relative) { return new Vector( 2 * a.x - (b.x + a.x) - (relative ? a.x : 0), 2 * a.y - (b.y + a.y) - (relative ? a.y : 0) ); }; /** * @name Two.Utils.getAnchorsFromArcData * @function * @param {Vector} center * @param {Number} xAxisRotation * @param {Number} rx - x radius * @param {Number} ry - y radius * @param {Number} ts * @param {Number} td * @param {Boolean} [ccw=false] - Set path traversal to counter-clockwise */ var getAnchorsFromArcData = function(center, xAxisRotation, rx, ry, ts, td, ccw) { var resolution = Constants.Resolution; for (var i = 0; i < resolution; i++) { var pct = (i + 1) / resolution; if (ccw) { pct = 1 - pct; } var theta = pct * td + ts; var x = rx * Math.cos(theta); var y = ry * Math.sin(theta); // x += center.x; // y += center.y; var anchor = new Anchor(x, y); Anchor.AppendCurveProperties(anchor); anchor.command = Commands.line; } }; var Curves = /*#__PURE__*/Object.freeze({ __proto__: null, Curve: Curve, getComponentOnCubicBezier: getComponentOnCubicBezier, subdivide: subdivide, getCurveLength: getCurveLength$1, getCurveBoundingBox: getCurveBoundingBox, integrate: integrate, getCurveFromPoints: getCurveFromPoints, getControlPoints: getControlPoints, getReflection: getReflection, getAnchorsFromArcData: getAnchorsFromArcData }); var devicePixelRatio = root$1.devicePixelRatio || 1; var getBackingStoreRatio = function(ctx) { return ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1; }; /** * @name Two.Utils.getRatio * @function * @param {CanvasRenderingContext2D} ctx * @returns {Number} The ratio of a unit in Two.js to the pixel density of a session's screen. * @see [High DPI Rendering](http://www.html5rocks.com/en/tutorials/canvas/hidpi/) */ var getRatio = function(ctx) { return devicePixelRatio / getBackingStoreRatio(ctx); }; // Constants var cos$5 = Math.cos, sin$5 = Math.sin, tan = Math.tan; var array = []; /** * @name Two.Matrix * @class * @param {Number} [a=1] - The value for element at the first column and first row. * @param {Number} [b=0] - The value for element at the second column and first row. * @param {Number} [c=0] - The value for element at the third column and first row. * @param {Number} [d=0] - The value for element at the first column and second row. * @param {Number} [e=1] - The value for element at the second column and second row. * @param {Number} [f=0] - The value for element at the third column and second row. * @param {Number} [g=0] - The value for element at the first column and third row. * @param {Number} [h=0] - The value for element at the second column and third row. * @param {Number} [i=1] - The value for element at the third column and third row. * @description A class to store 3 x 3 transformation matrix information. In addition to storing data `Two.Matrix` has suped up methods for commonplace mathematical operations. * @nota-bene Order is based on how to construct transformation strings for the browser. */ function Matrix(a, b, c, d, e, f) { /** * @name Two.Matrix#elements * @property {Number[]} - The underlying data stored as an array. */ this.elements = new NumArray(9); var elements = a; if (!Array.isArray(elements)) { elements = Array.prototype.slice.call(arguments); } // initialize the elements with default values. this.identity(); if (elements.length > 0) { this.set(elements); } } setMatrix(Matrix); _.extend(Matrix, { /** * @name Two.Matrix.Identity * @property {Number[]} - A stored reference to the default value of a 3 x 3 matrix. */ Identity: [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ], /** * @name Two.Matrix.Multiply * @function * @param {Two.Matrix} A * @param {Two.Matrix} B * @param {Two.Matrix} [C] - An optional matrix to apply the multiplication to. * @returns {Two.Matrix} - If an optional `C` matrix isn't passed then a new one is created and returned. * @description Multiply two matrices together and return the result. */ Multiply: function(A, B, C) { if (B.length <= 3) { // Multiply Vector var x, y, z, e = A; var a = B[0] || 0, b = B[1] || 0, c = B[2] || 0; // Go down rows first // a, d, g, b, e, h, c, f, i x = e[0] * a + e[1] * b + e[2] * c; y = e[3] * a + e[4] * b + e[5] * c; z = e[6] * a + e[7] * b + e[8] * c; return { x: x, y: y, z: z }; } var A0 = A[0], A1 = A[1], A2 = A[2]; var A3 = A[3], A4 = A[4], A5 = A[5]; var A6 = A[6], A7 = A[7], A8 = A[8]; var B0 = B[0], B1 = B[1], B2 = B[2]; var B3 = B[3], B4 = B[4], B5 = B[5]; var B6 = B[6], B7 = B[7], B8 = B[8]; C = C || new NumArray(9); C[0] = A0 * B0 + A1 * B3 + A2 * B6; C[1] = A0 * B1 + A1 * B4 + A2 * B7; C[2] = A0 * B2 + A1 * B5 + A2 * B8; C[3] = A3 * B0 + A4 * B3 + A5 * B6; C[4] = A3 * B1 + A4 * B4 + A5 * B7; C[5] = A3 * B2 + A4 * B5 + A5 * B8; C[6] = A6 * B0 + A7 * B3 + A8 * B6; C[7] = A6 * B1 + A7 * B4 + A8 * B7; C[8] = A6 * B2 + A7 * B5 + A8 * B8; return C; } }); _.extend(Matrix.prototype, Events, { constructor: Matrix, /** * @name Two.Matrix#manual * @property {Boolean} - Determines whether Two.js automatically calculates the values for the matrix or if the developer intends to manage the matrix. * @nota-bene - Setting to `true` nullifies {@link Two.Shape#translation}, {@link Two.Shape#rotation}, and {@link Two.Shape#scale}. */ manual: false, /** * @name Two.Matrix#set * @function * @param {Number} a - The value for element at the first column and first row. * @param {Number} b - The value for element at the second column and first row. * @param {Number} c - The value for element at the third column and first row. * @param {Number} d - The value for element at the first column and second row. * @param {Number} e - The value for element at the second column and second row. * @param {Number} f - The value for element at the third column and second row. * @param {Number} g - The value for element at the first column and third row. * @param {Number} h - The value for element at the second column and third row. * @param {Number} i - The value for element at the third column and third row. * @description Set an array of values onto the matrix. Order described in {@link Two.Matrix}. */ /** * @name Two.Matrix#set * @function * @param {Number[]} a - The array of elements to apply. * @description Set an array of values onto the matrix. Order described in {@link Two.Matrix}. */ set: function(a, b, c, d, e, f, g, h, i) { var elements; if (typeof b === 'undefined') { elements = a; a = elements[0]; b = elements[1]; c = elements[2]; d = elements[3]; e = elements[4]; f = elements[5]; g = elements[6]; h = elements[7]; i = elements[8]; } this.elements[0] = a; this.elements[1] = b; this.elements[2] = c; this.elements[3] = d; this.elements[4] = e; this.elements[5] = f; this.elements[6] = g; this.elements[7] = h; this.elements[8] = i; return this.trigger(Events.Types.change); }, /** * @name Two.Matrix#copy * @function * @description Copy the matrix of one to the current instance. */ copy: function(m) { this.elements[0] = m.elements[0]; this.elements[1] = m.elements[1]; this.elements[2] = m.elements[2]; this.elements[3] = m.elements[3]; this.elements[4] = m.elements[4]; this.elements[5] = m.elements[5]; this.elements[6] = m.elements[6]; this.elements[7] = m.elements[7]; this.elements[8] = m.elements[8]; this.manual = m.manual; return this.trigger(Events.Types.change); }, /** * @name Two.Matrix#identity * @function * @description Turn matrix to the identity, like resetting. */ identity: function() { this.elements[0] = Matrix.Identity[0]; this.elements[1] = Matrix.Identity[1]; this.elements[2] = Matrix.Identity[2]; this.elements[3] = Matrix.Identity[3]; this.elements[4] = Matrix.Identity[4]; this.elements[5] = Matrix.Identity[5]; this.elements[6] = Matrix.Identity[6]; this.elements[7] = Matrix.Identity[7]; this.elements[8] = Matrix.Identity[8]; return this.trigger(Events.Types.change); }, /** * @name Two.Matrix#multiply * @function * @param {Number} a - The scalar to be multiplied. * @description Multiply all components of the matrix against a single scalar value. * @overloaded */ /** * @name Two.Matrix#multiply * @function * @param {Number} a - The x component to be multiplied. * @param {Number} b - The y component to be multiplied. * @param {Number} c - The z component to be multiplied. * @description Multiply all components of a matrix against a 3 component vector. * @overloaded */ /** * @name Two.Matrix#multiply * @function * @param {Number} a - The value at the first column and first row of the matrix to be multiplied. * @param {Number} b - The value at the second column and first row of the matrix to be multiplied. * @param {Number} c - The value at the third column and first row of the matrix to be multiplied. * @param {Number} d - The value at the first column and second row of the matrix to be multiplied. * @param {Number} e - The value at the second column and second row of the matrix to be multiplied. * @param {Number} f - The value at the third column and second row of the matrix to be multiplied. * @param {Number} g - The value at the first column and third row of the matrix to be multiplied. * @param {Number} h - The value at the second column and third row of the matrix to be multiplied. * @param {Number} i - The value at the third column and third row of the matrix to be multiplied. * @description Multiply all components of a matrix against another matrix. * @overloaded */ multiply: function(a, b, c, d, e, f, g, h, i) { // Multiply scalar if (typeof b === 'undefined') { this.elements[0] *= a; this.elements[1] *= a; this.elements[2] *= a; this.elements[3] *= a; this.elements[4] *= a; this.elements[5] *= a; this.elements[6] *= a; this.elements[7] *= a; this.elements[8] *= a; return this.trigger(Events.Types.change); } if (typeof d === 'undefined') { // Multiply Vector var x, y, z; a = a || 0; b = b || 0; c = c || 0; e = this.elements; // Go down rows first // a, d, g, b, e, h, c, f, i x = e[0] * a + e[1] * b + e[2] * c; y = e[3] * a + e[4] * b + e[5] * c; z = e[6] * a + e[7] * b + e[8] * c; return { x: x, y: y, z: z }; } // Multiple matrix var A = this.elements; var B = [a, b, c, d, e, f, g, h, i]; var A0 = A[0], A1 = A[1], A2 = A[2]; var A3 = A[3], A4 = A[4], A5 = A[5]; var A6 = A[6], A7 = A[7], A8 = A[8]; var B0 = B[0], B1 = B[1], B2 = B[2]; var B3 = B[3], B4 = B[4], B5 = B[5]; var B6 = B[6], B7 = B[7], B8 = B[8]; this.elements[0] = A0 * B0 + A1 * B3 + A2 * B6; this.elements[1] = A0 * B1 + A1 * B4 + A2 * B7; this.elements[2] = A0 * B2 + A1 * B5 + A2 * B8; this.elements[3] = A3 * B0 + A4 * B3 + A5 * B6; this.elements[4] = A3 * B1 + A4 * B4 + A5 * B7; this.elements[5] = A3 * B2 + A4 * B5 + A5 * B8; this.elements[6] = A6 * B0 + A7 * B3 + A8 * B6; this.elements[7] = A6 * B1 + A7 * B4 + A8 * B7; this.elements[8] = A6 * B2 + A7 * B5 + A8 * B8; return this.trigger(Events.Types.change); }, /** * @name Two.Matrix#inverse * @function * @param {Two.Matrix} [out] - The optional matrix to apply the inversion to. * @description Return an inverted version of the matrix. If no optional one is passed a new matrix is created and returned. */ inverse: function(out) { var a = this.elements; out = out || new Matrix(); var a00 = a[0], a01 = a[1], a02 = a[2]; var a10 = a[3], a11 = a[4], a12 = a[5]; var a20 = a[6], a21 = a[7], a22 = a[8]; var b01 = a22 * a11 - a12 * a21; var b11 = -a22 * a10 + a12 * a20; var b21 = a21 * a10 - a11 * a20; // Calculate the determinant var det = a00 * b01 + a01 * b11 + a02 * b21; if (!det) { return null; } det = 1.0 / det; out.elements[0] = b01 * det; out.elements[1] = (-a22 * a01 + a02 * a21) * det; out.elements[2] = (a12 * a01 - a02 * a11) * det; out.elements[3] = b11 * det; out.elements[4] = (a22 * a00 - a02 * a20) * det; out.elements[5] = (-a12 * a00 + a02 * a10) * det; out.elements[6] = b21 * det; out.elements[7] = (-a21 * a00 + a01 * a20) * det; out.elements[8] = (a11 * a00 - a01 * a10) * det; return out; }, /** * @name Two.Matrix#scale * @function * @param {Number} scale - The one dimensional scale to apply to the matrix. * @description Uniformly scale the transformation matrix. */ /** * @name Two.Matrix#scale * @function * @param {Number} sx - The horizontal scale factor. * @param {Number} sy - The vertical scale factor * @description Scale the transformation matrix in two dimensions. */ scale: function(sx, sy) { var l = arguments.length; if (l <= 1) { sy = sx; } return this.multiply(sx, 0, 0, 0, sy, 0, 0, 0, 1); }, /** * @name Two.Matrix#rotate * @function * @param {Number} Number - The amount to rotate in Number. * @description Rotate the matrix. */ rotate: function(Number) { var c = cos$5(Number); var s = sin$5(Number); return this.multiply(c, -s, 0, s, c, 0, 0, 0, 1); }, /** * @name Two.Matrix#translate * @function * @param {Number} x - The horizontal translation value to apply. * @param {Number} y - The vertical translation value to apply. * @description Translate the matrix. */ translate: function(x, y) { return this.multiply(1, 0, x, 0, 1, y, 0, 0, 1); }, /** * @name Two.Matrix#skewX * @function * @param {Number} Number - The amount to skew in Number. * @description Skew the matrix by an angle in the x axis direction. */ skewX: function(Number) { var a = tan(Number); return this.multiply(1, a, 0, 0, 1, 0, 0, 0, 1); }, /** * @name Two.Matrix#skewY * @function * @param {Number} Number - The amount to skew in Number. * @description Skew the matrix by an angle in the y axis direction. */ skewY: function(Number) { var a = tan(Number); return this.multiply(1, 0, 0, a, 1, 0, 0, 0, 1); }, /** * @name Two.Matrix#toString * @function * @param {Boolean} [fullMatrix=false] - Return the full 9 elements of the matrix or just 6 for 2D transformations. * @returns {String} - The transformation matrix as a 6 component string separated by spaces. * @description Create a transform string. Used for the Two.js rendering APIs. */ toString: function(fullMatrix) { array.length = 0; this.toTransformArray(fullMatrix, array); return array.map(toFixed).join(' '); }, /** * @name Two.Matrix#toTransformArray * @function * @param {Boolean} [fullMatrix=false] - Return the full 9 elements of the matrix or just 6 in the format for 2D transformations. * @param {Number[]} [output] - An array empty or otherwise to apply the values to. * @description Create a transform array. Used for the Two.js rendering APIs. */ toTransformArray: function(fullMatrix, output) { var elements = this.elements; var hasOutput = !!output; var a = elements[0]; var b = elements[1]; var c = elements[2]; var d = elements[3]; var e = elements[4]; var f = elements[5]; if (fullMatrix) { var g = elements[6]; var h = elements[7]; var i = elements[8]; if (hasOutput) { output[0] = a; output[1] = d; output[2] = g; output[3] = b; output[4] = e; output[5] = h; output[6] = c; output[7] = f; output[8] = i; return; } return [ a, d, g, b, e, h, c, f, i ]; } if (hasOutput) { output[0] = a; output[1] = d; output[2] = b; output[3] = e; output[4] = c; output[5] = f; return; } return [ a, d, b, e, c, f // Specific format see LN:19 ]; }, /** * @name Two.Matrix#toArray * @function * @param {Boolean} [fullMatrix=false] - Return the full 9 elements of the matrix or just 6 for 2D transformations. * @param {Number[]} [output] - An array empty or otherwise to apply the values to. * @description Create a transform array. Used for the Two.js rendering APIs. */ toArray: function(fullMatrix, output) { var elements = this.elements; var hasOutput = !!output; var a = elements[0]; var b = elements[1]; var c = elements[2]; var d = elements[3]; var e = elements[4]; var f = elements[5]; if (fullMatrix) { var g = elements[6]; var h = elements[7]; var i = elements[8]; if (hasOutput) { output[0] = a; output[1] = b; output[2] = c; output[3] = d; output[4] = e; output[5] = f; output[6] = g; output[7] = h; output[8] = i; return; } return [ a, b, c, d, e, f, g, h, i ]; } if (hasOutput) { output[0] = a; output[1] = b; output[2] = c; output[3] = d; output[4] = e; output[5] = f; return; } return [ a, b, c, d, e, f ]; }, /** * @name Two.Matrix#toObject * @function * @description Create a JSON compatible object that represents information of the matrix. */ toObject: function() { return { elements: this.toArray(true), manual: !!this.manual }; }, /** * @name Two.Matrix#clone * @function * @description Clone the current matrix. */ clone: function() { return new Matrix().copy(this); } }); /** * @name Two.Shape * @class * @extends Two.Events * @description The foundational transformation object for the Two.js scenegraph. */ function Shape() { /** * @name Two.Shape#renderer * @property {Object} * @description Object access to store relevant renderer specific variables. Warning: manipulating this object can create unintended consequences. * @nota-bene With the {@link Two.SvgRenderer} you can access the underlying SVG element created via `shape.renderer.elem`. */ this.renderer = {}; this._renderer.flagMatrix = Shape.FlagMatrix.bind(this); this.isShape = true; /** * @name Two.Shape#id * @property {String} - Session specific unique identifier. * @nota-bene In the {@link Two.SvgRenderer} change this to change the underlying SVG element's id too. */ this.id = Constants.Identifier + Constants.uniqueId(); /** * @name Two.Shape#classList * @property {String[]} * @description A list of class strings stored if imported / interpreted from an SVG element. */ this.classList = []; /** * @name Two.Shape#matrix * @property {Two.Matrix} * @description The transformation matrix of the shape. * @nota-bene {@link Two.Shape#translation}, {@link Two.Shape#rotation}, {@link Two.Shape#scale}, {@link Two.Shape#skewX}, and {@link Two.Shape#skewY} apply their values to the matrix when changed. The matrix is what is sent to the renderer to be drawn. */ this.matrix = new Matrix(); /** * @name Two.Shape#translation * @property {Two.Vector} - The x and y value for where the shape is placed relative to its parent. */ this.translation = new Vector(); /** * @name Two.Shape#rotation * @property {Number} - The value in Number for how much the shape is rotated relative to its parent. */ this.rotation = 0; /** * @name Two.Shape#scale * @property {Number} - The value for how much the shape is scaled relative to its parent. * @nota-bene This value can be replaced with a {@link Two.Vector} to do non-uniform scaling. e.g: `shape.scale = new Two.Vector(2, 1);` */ this.scale = 1; /** * @name Two.Shape#skewX * @property {Number} - The value in Number for how much the shape is skewed relative to its parent. * @description Skew the shape by an angle in the x axis direction. */ this.skewX = 0; /** * @name Two.Shape#skewY * @property {Number} - The value in Number for how much the shape is skewed relative to its parent. * @description Skew the shape by an angle in the y axis direction. */ this.skewY = 0; } _.extend(Shape, { /** * @name Two.Shape.FlagMatrix * @function * @description Utility function used in conjunction with event handlers to update the flagMatrix of a shape. */ FlagMatrix: function() { this._flagMatrix = true; }, /** * @name Two.Shape.MakeObservable * @function * @param {Object} object - The object to make observable. * @description Convenience function to apply observable qualities of a {@link Two.Shape} to any object. Handy if you'd like to extend the {@link Two.Shape} class on a custom class. */ MakeObservable: function(object) { var translation = { enumerable: false, get: function() { return this._translation; }, set: function(v) { if (this._translation) { this._translation.unbind(Events.Types.change, this._renderer.flagMatrix); } this._translation = v; this._translation.bind(Events.Types.change, this._renderer.flagMatrix); Shape.FlagMatrix.call(this); } }; Object.defineProperty(object, 'translation', translation); Object.defineProperty(object, 'position', translation); Object.defineProperty(object, 'rotation', { enumerable: true, get: function() { return this._rotation; }, set: function(v) { this._rotation = v; this._flagMatrix = true; } }); Object.defineProperty(object, 'scale', { enumerable: true, get: function() { return this._scale; }, set: function(v) { if (this._scale instanceof Vector) { this._scale.unbind(Events.Types.change, this._renderer.flagMatrix); } this._scale = v; if (this._scale instanceof Vector) { this._scale.bind(Events.Types.change, this._renderer.flagMatrix); } this._flagMatrix = true; this._flagScale = true; } }); Object.defineProperty(object, 'skewX', { enumerable: true, get: function() { return this._skewX; }, set: function(v) { this._skewX = v; this._flagMatrix = true; } }); Object.defineProperty(object, 'skewY', { enumerable: true, get: function() { return this._skewY; }, set: function(v) { this._skewY = v; this._flagMatrix = true; } }); Object.defineProperty(object, 'matrix', { enumerable: true, get: function() { return this._matrix; }, set: function(v) { this._matrix = v; this._flagMatrix = true; } }); Object.defineProperty(object, 'id', { enumerable: true, get: function() { return this._id; }, set: function(v) { var id = this._id; if (v === this._id) { return; } this._id = v; this._flagId = true; if (this.parent) { delete this.parent.children.ids[id]; this.parent.children.ids[this._id] = this; } } }); Object.defineProperty(object, 'className', { enumerable: true, get: function() { return this._className; }, set: function(v) { this._flagClassName = this._className !== v; if (this._flagClassName) { var prev = this._className.split(/\s+?/); var dest = v.split(/\s+?/); for (var i = 0; i < prev.length; i++) { var className = prev[i]; var index = Array.prototype.indexOf.call(this.classList, className); if (index >= 0) { this.classList.splice(index, 1); } } this.classList = this.classList.concat(dest); } this._className = v; } }); Object.defineProperty(object, 'renderer', { enumerable: false, get: function() { return this._renderer; }, set: function(obj) { this._renderer = obj; } }); } }); _.extend(Shape.prototype, Events, { constructor: Shape, // Flags /** * @name Two.Shape#_id * @private * @property {Boolean} - Determines whether the id needs updating. */ _flagId: true, /** * @name Two.Shape#_flagMatrix * @private * @property {Boolean} - Determines whether the matrix needs updating. */ _flagMatrix: true, /** * @name Two.Shape#_flagScale * @private * @property {Boolean} - Determines whether the scale needs updating. */ _flagScale: false, // _flagMask: false, // _flagClip: false, /** * @name Two.Shape#_flagClassName * @private * @property {Boolean} - Determines whether the {@link Two.Group#className} need updating. */ _flagClassName: false, // Underlying Properties _id: '', /** * @name Two.Shape#_translation * @private * @property {Two.Vector} - The translation values as a {@link Two.Vector}. */ _translation: null, /** * @name Two.Shape#_rotation * @private * @property {Number} - The rotation value in Number. */ _rotation: 0, /** * @name Two.Shape#_translation * @private * @property {Two.Vector} - The translation values as a {@link Two.Vector}. */ _scale: 1, /** * @name Two.Shape#_skewX * @private * @property {Number} - The rotation value in Number. */ _skewX: 0, /** * @name Two.Shape#_skewY * @private * @property {Number} - The rotation value in Number. */ _skewY: 0, /** * @name Two.Shape#className * @property {String} - A class to be applied to the element to be compatible with CSS styling. * @nota-bene Only available for the SVG renderer. */ _className: '', /** * @name Two.Shape#addTo * @function * @param {Two.Group} group - The parent the shape adds itself to. * @description Convenience method to add itself to the scenegraph. */ addTo: function(group) { group.add(this); return this; }, /** * @name Two.Shape#clone * @function * @param {Two.Group} [parent] - Optional argument to automatically add the shape to a scenegraph. * @returns {Two.Shape} * @description Create a new {@link Two.Shape} with the same values as the current shape. */ clone: function(parent) { var clone = new Shape(); clone.translation.copy(this.translation); clone.rotation = this.rotation; clone.scale = this.scale; clone.skewX = this.skewX; clone.skewY = this.skewY; if (this.matrix.manual) { clone.matrix.copy(this.matrix); } if (parent) { parent.add(clone); } return clone._update(); }, /** * @name Two.Shape#_update * @function * @private * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well. * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be. * @nota-bene Try not to call this method more than once a frame. */ _update: function(bubbles) { if (!this._matrix.manual && this._flagMatrix) { this._matrix .identity() .translate(this.translation.x, this.translation.y); if (this._scale instanceof Vector) { this._matrix.scale(this._scale.x, this._scale.y); } else { this._matrix.scale(this._scale); } this._matrix.rotate(this.rotation); this._matrix.skewX(this.skewX); this._matrix.skewY(this.skewY); } if (bubbles) { if (this.parent && this.parent._update) { this.parent._update(); } } return this; }, /** * @name Two.Shape#flagReset * @function * @private * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer. */ flagReset: function() { this._flagId = this._flagMatrix = this._flagScale = this._flagClassName = false; return this; } }); Shape.MakeObservable(Shape.prototype); /** * @name Two.Collection * @class * @extends Two.Events * @description An `Array` like object with additional event propagation on actions. `pop`, `shift`, and `splice` trigger `removed` events. `push`, `unshift`, and `splice` with more than 2 arguments trigger 'inserted'. Finally, `sort` and `reverse` trigger `order` events. */ function Collection() { Array.call(this); if (arguments[0] && Array.isArray(arguments[0])) { if (arguments[0].length > 0) { Array.prototype.push.apply(this, arguments[0]); } } else if (arguments.length > 0) { Array.prototype.push.apply(this, arguments); } } Collection.prototype = new Array(); _.extend(Collection.prototype, Events, { constructor: Collection, pop: function() { var popped = Array.prototype.pop.apply(this, arguments); this.trigger(Events.Types.remove, [popped]); return popped; }, shift: function() { var shifted = Array.prototype.shift.apply(this, arguments); this.trigger(Events.Types.remove, [shifted]); return shifted; }, push: function() { var pushed = Array.prototype.push.apply(this, arguments); this.trigger(Events.Types.insert, arguments); return pushed; }, unshift: function() { var unshifted = Array.prototype.unshift.apply(this, arguments); this.trigger(Events.Types.insert, arguments); return unshifted; }, splice: function() { var spliced = Array.prototype.splice.apply(this, arguments); var inserted; this.trigger(Events.Types.remove, spliced); if (arguments.length > 2) { inserted = this.slice(arguments[0], arguments[0] + arguments.length - 2); this.trigger(Events.Types.insert, inserted); this.trigger(Events.Types.order); } return spliced; }, sort: function() { Array.prototype.sort.apply(this, arguments); this.trigger(Events.Types.order); return this; }, reverse: function() { Array.prototype.reverse.apply(this, arguments); this.trigger(Events.Types.order); return this; }, indexOf: function() { return Array.prototype.indexOf.apply(this, arguments); } }); /** * @class * @name Two.Group.Children * @extends Two.Collection * @description A children collection which is accesible both by index and by object `id`. */ function Children(children) { Collection.apply(this, arguments); Object.defineProperty(this, '_events', { value : {}, enumerable: false }); /** * @name Two.Group.Children#ids * @property {Object} - Map of all elements in the list keyed by `id`s. */ this.ids = {}; this.attach( Array.isArray(children) ? children : Array.prototype.slice.call(arguments) ); this.on(Events.Types.insert, this.attach); this.on(Events.Types.remove, this.detach); } Children.prototype = new Collection(); _.extend(Children.prototype, { constructor: Children, /** * @function * @name Two.Group.Children#attach * @param {Two.Shape[]} children - The objects which extend {@link Two.Shape} to be added. * @description Adds elements to the `ids` map. */ attach: function(children) { for (var i = 0; i < children.length; i++) { var child = children[i]; if (child && child.id) { this.ids[child.id] = child; } } return this; }, /** * @function * @name Two.Group.Children#detach * @param {Two.Shape[]} children - The objects which extend {@link Two.Shape} to be removed. * @description Removes elements to the `ids` map. */ detach: function(children) { for (var i = 0; i < children.length; i++) { delete this.ids[children[i].id]; } return this; } }); // Constants var min$3 = Math.min, max$3 = Math.max; /** * @name Two.Group * @class * @extends Two.Shape * @param {Two.Shape[]} [children] - A list of objects that inherit {@link Two.Shape}. For instance, the array could be a {@link Two.Path}, {@link Two.Text}, and {@link Two.RoundedRectangle}. * @description This is the primary class for grouping objects that are then drawn in Two.js. In Illustrator this is a group, in After Effects it would be a Null Object. Whichever the case, the `Two.Group` contains a transformation matrix and commands to style its children, but it by itself doesn't render to the screen. * @nota-bene The {@link Two#scene} is an instance of `Two.Group`. */ function Group(children) { Shape.call(this, true); this._renderer.type = 'group'; /** * @name Two.Group#additions * @property {Two.Shape[]} * @description An automatically updated list of children that need to be appended to the renderer's scenegraph. */ this.additions = []; /** * @name Two.Group#subtractions * @property {Two.Shape[]} * @description An automatically updated list of children that need to be removed from the renderer's scenegraph. */ this.subtractions = []; /** * @name Two.Group#children * @property {Two.Group.Children} * @description A list of all the children in the scenegraph. * @nota-bene Ther order of this list indicates the order each element is rendered to the screen. */ this.children = Array.isArray(children) ? children : Array.prototype.slice.call(arguments); } _.extend(Group, { Children: Children, /** * @name Two.Group.InsertChildren * @function * @param {Two.Shape[]} children - The objects to be inserted. * @description Cached method to let renderers know children have been added to a {@link Two.Group}. */ InsertChildren: function(children) { for (var i = 0; i < children.length; i++) { replaceParent.call(this, children[i], this); } }, /** * @name Two.Group.RemoveChildren * @function * @param {Two.Shape[]} children - The objects to be removed. * @description Cached method to let renderers know children have been removed from a {@link Two.Group}. */ RemoveChildren: function(children) { for (var i = 0; i < children.length; i++) { replaceParent.call(this, children[i]); } }, /** * @name Two.Group.OrderChildren * @function * @description Cached method to let renderers know order has been updated on a {@link Two.Group}. */ OrderChildren: function(children) { this._flagOrder = true; }, /** * @name Two.Group.Properties * @property {String[]} - A list of properties that are on every {@link Two.Group}. */ Properties: [ 'fill', 'stroke', 'linewidth', 'cap', 'join', 'miter', 'closed', 'curved', 'automatic' ], /** * @name Two.Group.MakeObservable * @function * @param {Object} object - The object to make observable. * @description Convenience function to apply observable qualities of a {@link Two.Group} to any object. Handy if you'd like to extend the {@link Two.Group} class on a custom class. */ MakeObservable: function(object) { var properties = Group.Properties; Object.defineProperty(object, 'visible', { enumerable: true, get: function() { return this._visible; }, set: function(v) { this._flagVisible = this._visible !== v || this._flagVisible; this._visible = v; } }); Object.defineProperty(object, 'opacity', { enumerable: true, get: function() { return this._opacity; }, set: function(v) { this._flagOpacity = this._opacity !== v || this._flagOpacity; this._opacity = v; } }); Object.defineProperty(object, 'beginning', { enumerable: true, get: function() { return this._beginning; }, set: function(v) { this._flagBeginning = this._beginning !== v || this._flagBeginning; this._beginning = v; } }); Object.defineProperty(object, 'ending', { enumerable: true, get: function() { return this._ending; }, set: function(v) { this._flagEnding = this._ending !== v || this._flagEnding; this._ending = v; } }); Object.defineProperty(object, 'length', { enumerable: true, get: function() { if (this._flagLength || this._length <= 0) { this._length = 0; if (!this.children) { return this._length; } for (var i = 0; i < this.children.length; i++) { var child = this.children[i]; this._length += child.length; } } return this._length; } }); Shape.MakeObservable(object); Group.MakeGetterSetters(object, properties); Object.defineProperty(object, 'children', { enumerable: true, get: function() { return this._children; }, set: function(children) { var insertChildren = Group.InsertChildren.bind(this); var removeChildren = Group.RemoveChildren.bind(this); var orderChildren = Group.OrderChildren.bind(this); if (this._children) { this._children.unbind(); if (this._children.length > 0) { removeChildren(this._children); } } this._children = new Children(children); this._children.bind(Events.Types.insert, insertChildren); this._children.bind(Events.Types.remove, removeChildren); this._children.bind(Events.Types.order, orderChildren); if (children.length > 0) { insertChildren(children); } } }); Object.defineProperty(object, 'mask', { enumerable: true, get: function() { return this._mask; }, set: function(v) { this._mask = v; this._flagMask = true; if (!v.clip) { v.clip = true; } } }); }, /** * @name Two.Group.MakeGetterSetters * @function * @param {Two.Group} group - The group to apply getters and setters. * @param {Object} properties - A key / value object containing properties to inherit. * @description Convenience method to apply getter / setter logic on an array of properties. Used in {@link Two.Group.MakeObservable}. */ MakeGetterSetters: function(group, properties) { if (!Array.isArray(properties)) { properties = [properties]; } _.each(properties, function(k) { Group.MakeGetterSetter(group, k); }); }, /** * @name Two.Group.MakeGetterSetter * @function * @param {Two.Group} group - The group to apply getters and setters. * @param {String} key - The key which will become a property on the group. * @description Convenience method to apply getter / setter logic specific to how `Two.Group`s trickle down styles to their children. Used in {@link Two.Group.MakeObservable}. */ MakeGetterSetter: function(group, key) { var secret = '_' + key; Object.defineProperty(group, key, { enumerable: true, get: function() { return this[secret]; }, set: function(v) { this[secret] = v; // Trickle down styles for (var i = 0; i < this.children.length; i++) { var child = this.children[i]; child[key] = v; } } }); } }); _.extend(Group.prototype, Shape.prototype, { constructor: Group, // Flags // http://en.wikipedia.org/wiki/Flag /** * @name Two.Group#_flagAdditions * @private * @property {Boolean} - Determines whether the {@link Two.Group#additions} needs updating. */ _flagAdditions: false, /** * @name Two.Group#_flagSubtractions * @private * @property {Boolean} - Determines whether the {@link Two.Group#subtractions} needs updating. */ _flagSubtractions: false, /** * @name Two.Group#_flagOrder * @private * @property {Boolean} - Determines whether the {@link Two.Group#order} needs updating. */ _flagOrder: false, /** * @name Two.Group#_flagVisible * @private * @property {Boolean} - Determines whether the {@link Two.Group#visible} needs updating. */ /** * @name Two.Group#_flagOpacity * @private * @property {Boolean} - Determines whether the {@link Two.Group#opacity} needs updating. */ _flagOpacity: true, /** * @name Two.Group#_flagBeginning * @private * @property {Boolean} - Determines whether the {@link Two.Group#beginning} needs updating. */ _flagBeginning: false, /** * @name Two.Group#_flagEnding * @private * @property {Boolean} - Determines whether the {@link Two.Group#ending} needs updating. */ _flagEnding: false, /** * @name Two.Group#_flagLength * @private * @property {Boolean} - Determines whether the {@link Two.Group#length} needs updating. */ _flagLength: false, /** * @name Two.Group#_flagMask * @private * @property {Boolean} - Determines whether the {@link Two.Group#mask} needs updating. */ _flagMask: false, // Underlying Properties /** * @name Two.Group#fill * @property {(String|Two.Gradient|Two.Texture)} - The value of what all child shapes should be filled in with. * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/color_value} for more information on CSS's colors as `String`. */ _fill: '#fff', /** * @name Two.Group#stroke * @property {(String|Two.Gradient|Two.Texture)} - The value of what all child shapes should be outlined in with. * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/color_value} for more information on CSS's colors as `String`. */ _stroke: '#000', /** * @name Two.Group#linewidth * @property {Number} - The thickness in pixels of the stroke for all child shapes. */ _linewidth: 1.0, /** * @name Two.Group#opacity * @property {Number} - The opaqueness of all child shapes. * @nota-bene Becomes multiplied by the individual child's opacity property. */ _opacity: 1.0, /** * @name Two.Group#visible * @property {Boolean} - Display the path or not. * @nota-bene For {@link Two.CanvasRenderer} and {@link Two.WebGLRenderer} when set to false all updating is disabled improving performance dramatically with many objects in the scene. */ _visible: true, /** * @name Two.Group#cap * @property {String} * @see {@link https://www.w3.org/TR/SVG11/painting.html#StrokeLinecapProperty} */ _cap: 'round', /** * @name Two.Group#join * @property {String} * @see {@link https://www.w3.org/TR/SVG11/painting.html#StrokeLinejoinProperty} */ _join: 'round', /** * @name Two.Group#miter * @property {String} * @see {@link https://www.w3.org/TR/SVG11/painting.html#StrokeMiterlimitProperty} */ _miter: 4, /** * @name Two.Group#closed * @property {Boolean} - Determines whether a final line is drawn between the final point in the `vertices` array and the first point of all child shapes. */ _closed: true, /** * @name Two.Group#curved * @property {Boolean} - When the child's path is `automatic = true` this boolean determines whether the lines between the points are curved or not. */ _curved: false, /** * @name Two.Group#automatic * @property {Boolean} - Determines whether or not Two.js should calculate curves, lines, and commands automatically for you or to let the developer manipulate them for themselves. */ _automatic: true, /** * @name Two.Group#beginning * @property {Number} - Number between zero and one to state the beginning of where the path is rendered. * @description {@link Two.Group#beginning} is a percentage value that represents at what percentage into all child shapes should the renderer start drawing. * @nota-bene This is great for animating in and out stroked paths in conjunction with {@link Two.Group#ending}. */ _beginning: 0, /** * @name Two.Group#ending * @property {Number} - Number between zero and one to state the ending of where the path is rendered. * @description {@link Two.Group#ending} is a percentage value that represents at what percentage into all child shapes should the renderer start drawing. * @nota-bene This is great for animating in and out stroked paths in conjunction with {@link Two.Group#beginning}. */ _ending: 1.0, /** * @name Two.Group#length * @property {Number} - The sum of distances between all child lengths. */ _length: 0, /** * @name Two.Group#mask * @property {Two.Shape} - The Two.js object to clip from a group's rendering. */ _mask: null, /** * @name Two.Group#clone * @function * @param {Two.Group} [parent] - The parent group or scene to add the clone to. * @returns {Two.Group} * @description Create a new instance of {@link Two.Group} with the same properties of the current group. */ clone: function(parent) { // /** // * TODO: Group has a gotcha in that it's at the moment required to be bound to // * an instance of two in order to add elements correctly. This needs to // * be rethought and fixed. // */ var clone = new Group(); var children = this.children.map(function(child) { return child.clone(); }); clone.add(children); clone.opacity = this.opacity; if (this.mask) { clone.mask = this.mask; } clone.translation.copy(this.translation); clone.rotation = this.rotation; clone.scale = this.scale; clone.className = this.className; if (this.matrix.manual) { clone.matrix.copy(this.matrix); } if (parent) { parent.add(clone); } return clone._update(); }, /** * @name Two.Group#toObject * @function * @returns {Object} * @description Return a JSON compatible plain object that represents the group. */ toObject: function() { var result = { children: [], translation: this.translation.toObject(), rotation: this.rotation, scale: this.scale instanceof Vector ? this.scale.toObject() : this.scale, opacity: this.opacity, className: this.className, mask: (this.mask ? this.mask.toObject() : null) }; if (this.matrix.manual) { result.matrix = this.matrix.toObject(); } _.each(this.children, function(child, i) { result.children[i] = child.toObject(); }, this); return result; }, /** * @name Two.Group#corner * @function * @description Orient the children of the group to the upper left-hand corner of that group. */ corner: function() { var rect = this.getBoundingClientRect(true); for (var i = 0; i < this.children.length; i++) { var child = this.children[i]; child.translation.x -= rect.left; child.translation.y -= rect.top; } return this; }, /** * @name Two.Group#center * @function * @description Orient the children of the group to the center of that group. */ center: function() { var rect = this.getBoundingClientRect(true); var cx = rect.left + rect.width / 2 - this.translation.x; var cy = rect.top + rect.height / 2 - this.translation.y; for (var i = 0; i < this.children.length; i++) { var child = this.children[i]; if (child.isShape) { child.translation.x -= cx; child.translation.y -= cy; } } return this; }, /** * @name Two.Group#getById * @function * @description Recursively search for id. Returns the first element found. * @returns {Two.Shape} - Or `null` if nothing is found. */ getById: function (id) { var found = null; function search(node) { if (node.id === id) { return node; } else if (node.children) { for (var i = 0; i < node.children.length; i++) { found = search(node.children[i]); if (found) { return found; } } } return null; } return search(this); }, /** * @name Two.Group#getByClassName * @function * @description Recursively search for classes. Returns an array of matching elements. * @returns {Two.Shape[]} - Or empty array if nothing is found. */ getByClassName: function(className) { var found = []; function search(node) { if (Array.prototype.indexOf.call(node.classList, className) >= 0) { found.push(node); } if (node.children) { for (var i = 0; i < node.children.length; i++) { var child = node.children[i]; search(child); } } return found; } return search(this); }, /** * @name Two.Group#getByType * @function * @description Recursively search for children of a specific type, e.g. {@link Two.Path}. Pass a reference to this type as the param. Returns an array of matching elements. * @returns {Two.Shape[]} - Empty array if nothing is found. */ getByType: function(type) { var found = []; function search(node) { if (node instanceof type) { found.push(node); } if (node.children) { for (var i = 0; i < node.children.length; i++) { var child = node.children[i]; search(child); } } return found; } return search(this); }, /** * @name Two.Group#add * @function * @param {Two.Shape[]} objects - An array of objects to be added. Can be also be supplied as individual arguments. * @description Add objects to the group. */ add: function(objects) { // Allow to pass multiple objects either as array or as multiple arguments // If it's an array also create copy of it in case we're getting passed // a childrens array directly. if (!(objects instanceof Array)) { objects = Array.prototype.slice.call(arguments); } else { objects = objects.slice(); } // Add the objects for (var i = 0; i < objects.length; i++) { var child = objects[i]; if (!(child && child.id)) { continue; } var index = Array.prototype.indexOf.call(this.children, child); if (index >= 0) { this.children.splice(index, 1); } this.children.push(child); } return this; }, /** * @name Two.Group#add * @function * @param {Two.Shape[]} objects - An array of objects to be removed. Can be also removed as individual arguments. * @description Remove objects from the group. */ remove: function(objects) { var l = arguments.length, grandparent = this.parent; // Allow to call remove without arguments // This will detach the object from its own parent. if (l <= 0 && grandparent) { grandparent.remove(this); return this; } // Allow to pass multiple objects either as array or as multiple arguments // If it's an array also create copy of it in case we're getting passed // a childrens array directly. if (!(objects instanceof Array)) { objects = Array.prototype.slice.call(arguments); } else { objects = objects.slice(); } // Remove the objects for (var i = 0; i < objects.length; i++) { var object = objects[i]; if (!object || !this.children.ids[object.id]) { continue; } var index = this.children.indexOf(object); if (index >= 0) { this.children.splice(index, 1); } } return this; }, /** * @name Two.Group#getBoundingClientRect * @function * @param {Boolean} [shallow=false] - Describes whether to calculate off local matrix or world matrix. * @returns {Object} - Returns object with top, left, right, bottom, width, height attributes. * @description Return an object with top, left, right, bottom, width, and height parameters of the group. */ getBoundingClientRect: function(shallow) { var rect, matrix, a, b, c, d, tc, lc, rc, bc; // TODO: Update this to not __always__ update. Just when it needs to. this._update(true); // Variables need to be defined here, because of nested nature of groups. var left = Infinity, right = -Infinity, top = Infinity, bottom = -Infinity; var regex = /texture|gradient/i; matrix = shallow ? this._matrix : getComputedMatrix(this); for (var i = 0; i < this.children.length; i++) { var child = this.children[i]; if (!child.visible || regex.test(child._renderer.type)) { continue; } rect = child.getBoundingClientRect(shallow); tc = typeof rect.top !== 'number' || _.isNaN(rect.top) || !isFinite(rect.top); lc = typeof rect.left !== 'number' || _.isNaN(rect.left) || !isFinite(rect.left); rc = typeof rect.right !== 'number' || _.isNaN(rect.right) || !isFinite(rect.right); bc = typeof rect.bottom !== 'number' || _.isNaN(rect.bottom) || !isFinite(rect.bottom); if (tc || lc || rc || bc) { continue; } top = min$3(rect.top, top); left = min$3(rect.left, left); right = max$3(rect.right, right); bottom = max$3(rect.bottom, bottom); } if (shallow) { a = matrix.multiply(left, top, 1); b = matrix.multiply(left, bottom, 1); c = matrix.multiply(right, top, 1); d = matrix.multiply(right, bottom, 1); top = min$3(a.y, b.y, c.y, d.y); left = min$3(a.x, b.x, c.x, d.x); right = max$3(a.x, b.x, c.x, d.x); bottom = max$3(a.y, b.y, c.y, d.y); } return { top: top, left: left, right: right, bottom: bottom, width: right - left, height: bottom - top }; }, /** * @name Two.Group#noFill * @function * @description Apply `noFill` method to all child shapes. */ noFill: function() { this.children.forEach(function(child) { child.noFill(); }); return this; }, /** * @name Two.Group#noStroke * @function * @description Apply `noStroke` method to all child shapes. */ noStroke: function() { this.children.forEach(function(child) { child.noStroke(); }); return this; }, /** * @name Two.Group#subdivide * @function * @description Apply `subdivide` method to all child shapes. */ subdivide: function() { var args = arguments; this.children.forEach(function(child) { child.subdivide.apply(child, args); }); return this; }, /** * @name Two.Group#_update * @function * @private * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well. * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be. * @nota-bene Try not to call this method more than once a frame. */ _update: function() { var i, l, child; if (this._flagBeginning || this._flagEnding) { var beginning = Math.min(this._beginning, this._ending); var ending = Math.max(this._beginning, this._ending); var length = this.length; var sum = 0; var bd = beginning * length; var ed = ending * length; for (i = 0; i < this.children.length; i++) { child = this.children[i]; l = child.length; if (bd > sum + l) { child.beginning = 1; child.ending = 1; } else if (ed < sum) { child.beginning = 0; child.ending = 0; } else if (bd > sum && bd < sum + l) { child.beginning = (bd - sum) / l; child.ending = 1; } else if (ed > sum && ed < sum + l) { child.beginning = 0; child.ending = (ed - sum) / l; } else { child.beginning = 0; child.ending = 1; } sum += l; } } return Shape.prototype._update.apply(this, arguments); }, /** * @name Two.Group#flagReset * @function * @private * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer. */ flagReset: function() { if (this._flagAdditions) { this.additions.length = 0; this._flagAdditions = false; } if (this._flagSubtractions) { this.subtractions.length = 0; this._flagSubtractions = false; } this._flagOrder = this._flagMask = this._flagOpacity = this._flagBeginning = this._flagEnding = false; Shape.prototype.flagReset.call(this); return this; } }); Group.MakeObservable(Group.prototype); // /** // * Helper function used to sync parent-child relationship within the // * `Two.Group.children` object. // * // * Set the parent of the passed object to another object // * and updates parent-child relationships // * Calling with one arguments will simply remove the parenting // */ function replaceParent(child, newParent) { var parent = child.parent; var index; if (parent === newParent) { add(); return; } if (parent && parent.children.ids[child.id]) { index = Array.prototype.indexOf.call(parent.children, child); parent.children.splice(index, 1); splice(); } if (newParent) { add(); return; } splice(); if (parent._flagAdditions && parent.additions.length === 0) { parent._flagAdditions = false; } if (parent._flagSubtractions && parent.subtractions.length === 0) { parent._flagSubtractions = false; } delete child.parent; function add() { if (newParent.subtractions.length > 0) { index = Array.prototype.indexOf.call(newParent.subtractions, child); if (index >= 0) { newParent.subtractions.splice(index, 1); } } if (newParent.additions.length > 0) { index = Array.prototype.indexOf.call(newParent.additions, child); if (index >= 0) { newParent.additions.splice(index, 1); } } child.parent = newParent; newParent.additions.push(child); newParent._flagAdditions = true; } function splice() { index = Array.prototype.indexOf.call(parent.additions, child); if (index >= 0) { parent.additions.splice(index, 1); } index = Array.prototype.indexOf.call(parent.subtractions, child); if (index < 0) { parent.subtractions.push(child); parent._flagSubtractions = true; } } } // Constants var emptyArray = []; var TWO_PI$5 = Math.PI * 2, max$2 = Math.max, min$2 = Math.min, abs = Math.abs, sin$4 = Math.sin, cos$4 = Math.cos, acos = Math.acos, sqrt = Math.sqrt; // Returns true if this is a non-transforming matrix var isDefaultMatrix = function (m) { return (m[0] == 1 && m[3] == 0 && m[1] == 0 && m[4] == 1 && m[2] == 0 && m[5] == 0); }; var canvas = { isHidden: /(undefined|none|transparent)/i, alignments: { left: 'start', middle: 'center', right: 'end' }, shim: function(elem, name) { elem.tagName = elem.nodeName = name || 'canvas'; elem.nodeType = 1; elem.getAttribute = function(prop) { return this[prop]; }; elem.setAttribute = function(prop, val) { this[prop] = val; return this; }; return elem; }, group: { renderChild: function(child) { canvas[child._renderer.type].render.call(child, this.ctx, true, this.clip); }, render: function(ctx) { if (!this._visible) { return this; } this._update(); var matrix = this._matrix.elements; var parent = this.parent; this._renderer.opacity = this._opacity * (parent && parent._renderer ? parent._renderer.opacity : 1); var mask = this._mask; // var clip = this._clip; var defaultMatrix = isDefaultMatrix(matrix); var shouldIsolate = !defaultMatrix || !!mask; if (!this._renderer.context) { this._renderer.context = {}; } this._renderer.context.ctx = ctx; // this._renderer.context.clip = clip; if (shouldIsolate) { ctx.save(); if (!defaultMatrix) { ctx.transform(matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]); } } if (mask) { canvas[mask._renderer.type].render.call(mask, ctx, true); } if (this._opacity > 0 && this._scale !== 0) { for (var i = 0; i < this.children.length; i++) { var child = this.children[i]; canvas[child._renderer.type].render.call(child, ctx); } } if (shouldIsolate) { ctx.restore(); } // Commented two-way functionality of clips / masks with groups and // polygons. Uncomment when this bug is fixed: // https://code.google.com/p/chromium/issues/detail?id=370951 // if (clip) { // ctx.clip(); // } return this.flagReset(); } }, path: { render: function(ctx, forced, parentClipped) { var matrix, stroke, linewidth, fill, opacity, visible, cap, join, miter, closed, commands, length, last, next, prev, a, b, c, d, ux, uy, vx, vy, ar, bl, br, cl, x, y, mask, clip, defaultMatrix, isOffset, dashes, po; po = (this.parent && this.parent._renderer) ? this.parent._renderer.opacity : 1; mask = this._mask; clip = this._clip; opacity = this._opacity * (po || 1); visible = this._visible; if (!forced && (!visible || clip || opacity === 0)) { return this; } this._update(); matrix = this._matrix.elements; stroke = this._stroke; linewidth = this._linewidth; fill = this._fill; cap = this._cap; join = this._join; miter = this._miter; closed = this._closed; commands = this._renderer.vertices; // Commands length = commands.length; last = length - 1; defaultMatrix = isDefaultMatrix(matrix); dashes = this.dashes; // Transform if (!defaultMatrix) { ctx.save(); ctx.transform(matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]); } // Commented two-way functionality of clips / masks with groups and // polygons. Uncomment when this bug is fixed: // https://code.google.com/p/chromium/issues/detail?id=370951 if (mask) { canvas[mask._renderer.type].render.call(mask, ctx, true); } // Styles if (fill) { if (typeof fill === 'string') { ctx.fillStyle = fill; } else { canvas[fill._renderer.type].render.call(fill, ctx); ctx.fillStyle = fill._renderer.effect; } } if (stroke) { if (typeof stroke === 'string') { ctx.strokeStyle = stroke; } else { canvas[stroke._renderer.type].render.call(stroke, ctx); ctx.strokeStyle = stroke._renderer.effect; } if (linewidth) { ctx.lineWidth = linewidth; } if (miter) { ctx.miterLimit = miter; } if (join) { ctx.lineJoin = join; } if (!closed && cap) { ctx.lineCap = cap; } } if (typeof opacity === 'number') { ctx.globalAlpha = opacity; } if (dashes && dashes.length > 0) { ctx.lineDashOffset = dashes.offset || 0; ctx.setLineDash(dashes); } ctx.beginPath(); for (var i = 0; i < commands.length; i++) { b = commands[i]; x = b.x; y = b.y; switch (b.command) { case Commands.close: ctx.closePath(); break; case Commands.arc: var rx = b.rx; var ry = b.ry; var xAxisRotation = b.xAxisRotation; var largeArcFlag = b.largeArcFlag; var sweepFlag = b.sweepFlag; prev = closed ? mod(i - 1, length) : max$2(i - 1, 0); a = commands[prev]; var ax = a.x; var ay = a.y; canvas.renderSvgArcCommand(ctx, ax, ay, rx, ry, largeArcFlag, sweepFlag, xAxisRotation, x, y); break; case Commands.curve: prev = closed ? mod(i - 1, length) : Math.max(i - 1, 0); next = closed ? mod(i + 1, length) : Math.min(i + 1, last); a = commands[prev]; c = commands[next]; ar = (a.controls && a.controls.right) || Vector.zero; bl = (b.controls && b.controls.left) || Vector.zero; if (a._relative) { vx = (ar.x + a.x); vy = (ar.y + a.y); } else { vx = ar.x; vy = ar.y; } if (b._relative) { ux = (bl.x + b.x); uy = (bl.y + b.y); } else { ux = bl.x; uy = bl.y; } ctx.bezierCurveTo(vx, vy, ux, uy, x, y); if (i >= last && closed) { c = d; br = (b.controls && b.controls.right) || Vector.zero; cl = (c.controls && c.controls.left) || Vector.zero; if (b._relative) { vx = (br.x + b.x); vy = (br.y + b.y); } else { vx = br.x; vy = br.y; } if (c._relative) { ux = (cl.x + c.x); uy = (cl.y + c.y); } else { ux = cl.x; uy = cl.y; } x = c.x; y = c.y; ctx.bezierCurveTo(vx, vy, ux, uy, x, y); } break; case Commands.line: ctx.lineTo(x, y); break; case Commands.move: d = b; ctx.moveTo(x, y); break; } } // Loose ends if (closed) { ctx.closePath(); } if (!clip && !parentClipped) { if (!canvas.isHidden.test(fill)) { isOffset = fill._renderer && fill._renderer.offset; if (isOffset) { ctx.save(); ctx.translate( - fill._renderer.offset.x, - fill._renderer.offset.y); ctx.scale(fill._renderer.scale.x, fill._renderer.scale.y); } ctx.fill(); if (isOffset) { ctx.restore(); } } if (!canvas.isHidden.test(stroke)) { isOffset = stroke._renderer && stroke._renderer.offset; if (isOffset) { ctx.save(); ctx.translate( - stroke._renderer.offset.x, - stroke._renderer.offset.y); ctx.scale(stroke._renderer.scale.x, stroke._renderer.scale.y); ctx.lineWidth = linewidth / stroke._renderer.scale.x; } ctx.stroke(); if (isOffset) { ctx.restore(); } } } if (!defaultMatrix) { ctx.restore(); } if (clip && !parentClipped) { ctx.clip(); } if (dashes && dashes.length > 0) { ctx.setLineDash(emptyArray); } return this.flagReset(); } }, text: { render: function(ctx, forced, parentClipped) { var po = (this.parent && this.parent._renderer) ? this.parent._renderer.opacity : 1; var opacity = this._opacity * po; var visible = this._visible; var mask = this._mask; var clip = this._clip; if (!forced && (!visible || clip || opacity === 0)) { return this; } this._update(); var matrix = this._matrix.elements; var stroke = this._stroke; var linewidth = this._linewidth; var fill = this._fill; var decoration = this._decoration; var defaultMatrix = isDefaultMatrix(matrix); var isOffset = fill._renderer && fill._renderer.offset && stroke._renderer && stroke._renderer.offset; var dashes = this.dashes; var alignment = canvas.alignments[this._alignment] || this._alignment; var baseline = this._baseline; var a, b, c, d, e, sx, sy, x1, y1, x2, y2; // Transform if (!defaultMatrix) { ctx.save(); ctx.transform(matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]); } // Commented two-way functionality of clips / masks with groups and // polygons. Uncomment when this bug is fixed: // https://code.google.com/p/chromium/issues/detail?id=370951 if (mask) { canvas[mask._renderer.type].render.call(mask, ctx, true); } if (!isOffset) { ctx.font = [this._style, this._weight, this._size + 'px/' + this._leading + 'px', this._family].join(' '); } ctx.textAlign = alignment; ctx.textBaseline = baseline; // Styles if (fill) { if (typeof fill === 'string') { ctx.fillStyle = fill; } else { canvas[fill._renderer.type].render.call(fill, ctx); ctx.fillStyle = fill._renderer.effect; } } if (stroke) { if (typeof stroke === 'string') { ctx.strokeStyle = stroke; } else { canvas[stroke._renderer.type].render.call(stroke, ctx); ctx.strokeStyle = stroke._renderer.effect; } if (linewidth) { ctx.lineWidth = linewidth; } } if (typeof opacity === 'number') { ctx.globalAlpha = opacity; } if (dashes && dashes.length > 0) { ctx.lineDashOffset = dashes.offset || 0; ctx.setLineDash(dashes); } if (!clip && !parentClipped) { if (!canvas.isHidden.test(fill)) { if (fill._renderer && fill._renderer.offset) { sx = fill._renderer.scale.x; sy = fill._renderer.scale.y; ctx.save(); ctx.translate( - fill._renderer.offset.x, - fill._renderer.offset.y); ctx.scale(sx, sy); a = this._size / fill._renderer.scale.y; b = this._leading / fill._renderer.scale.y; ctx.font = [this._style, this._weight, a + 'px/', b + 'px', this._family].join(' '); c = fill._renderer.offset.x / fill._renderer.scale.x; d = fill._renderer.offset.y / fill._renderer.scale.y; ctx.fillText(this.value, c, d); ctx.restore(); } else { ctx.fillText(this.value, 0, 0); } } if (!canvas.isHidden.test(stroke)) { if (stroke._renderer && stroke._renderer.offset) { sx = stroke._renderer.scale.x; sy = stroke._renderer.scale.y; ctx.save(); ctx.translate(- stroke._renderer.offset.x, - stroke._renderer.offset.y); ctx.scale(sx, sy); a = this._size / stroke._renderer.scale.y; b = this._leading / stroke._renderer.scale.y; ctx.font = [this._style, this._weight, a + 'px/', b + 'px', this._family].join(' '); c = stroke._renderer.offset.x / stroke._renderer.scale.x; d = stroke._renderer.offset.y / stroke._renderer.scale.y; e = linewidth / stroke._renderer.scale.x; ctx.lineWidth = e; ctx.strokeText(this.value, c, d); ctx.restore(); } else { ctx.strokeText(this.value, 0, 0); } } } // Handle text-decoration if (/(underline|strikethrough)/i.test(decoration)) { var metrics = ctx.measureText(this.value); var scalar = 1; switch (decoration) { case 'underline': y1 = metrics.actualBoundingBoxAscent; y2 = metrics.actualBoundingBoxAscent; break; case 'strikethrough': y1 = 0; y2 = 0; scalar = 0.5; break; } switch (baseline) { case 'top': y1 += this._size * scalar; y2 += this._size * scalar; break; case 'baseline': case 'bottom': y1 -= this._size * scalar; y2 -= this._size * scalar; break; } switch (alignment) { case 'left': case 'start': x1 = 0; x2 = metrics.width; break; case 'right': case 'end': x1 = - metrics.width; x2 = 0; break; default: x1 = - metrics.width / 2; x2 = metrics.width / 2; } ctx.lineWidth = Math.max(Math.floor(this._size / 15), 1); ctx.strokeStyle = ctx.fillStyle; ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); } if (!defaultMatrix) { ctx.restore(); } // TODO: Test for text if (clip && !parentClipped) { ctx.clip(); } if (dashes && dashes.length > 0) { ctx.setLineDash(emptyArray); } return this.flagReset(); } }, 'linear-gradient': { render: function(ctx) { this._update(); if (!this._renderer.effect || this._flagEndPoints || this._flagStops) { this._renderer.effect = ctx.createLinearGradient( this.left._x, this.left._y, this.right._x, this.right._y ); for (var i = 0; i < this.stops.length; i++) { var stop = this.stops[i]; this._renderer.effect.addColorStop(stop._offset, stop._color); } } return this.flagReset(); } }, 'radial-gradient': { render: function(ctx) { this._update(); if (!this._renderer.effect || this._flagCenter || this._flagFocal || this._flagRadius || this._flagStops) { this._renderer.effect = ctx.createRadialGradient( this.center._x, this.center._y, 0, this.focal._x, this.focal._y, this._radius ); for (var i = 0; i < this.stops.length; i++) { var stop = this.stops[i]; this._renderer.effect.addColorStop(stop._offset, stop._color); } } return this.flagReset(); } }, texture: { render: function(ctx) { this._update(); var image = this.image; if (!this._renderer.effect || ((this._flagLoaded || this._flagImage || this._flagVideo || this._flagRepeat) && this.loaded)) { this._renderer.effect = ctx.createPattern(this.image, this._repeat); } if (this._flagOffset || this._flagLoaded || this._flagScale) { if (!(this._renderer.offset instanceof Vector)) { this._renderer.offset = new Vector(); } this._renderer.offset.x = - this._offset.x; this._renderer.offset.y = - this._offset.y; if (image) { this._renderer.offset.x += image.width / 2; this._renderer.offset.y += image.height / 2; if (this._scale instanceof Vector) { this._renderer.offset.x *= this._scale.x; this._renderer.offset.y *= this._scale.y; } else { this._renderer.offset.x *= this._scale; this._renderer.offset.y *= this._scale; } } } if (this._flagScale || this._flagLoaded) { if (!(this._renderer.scale instanceof Vector)) { this._renderer.scale = new Vector(); } if (this._scale instanceof Vector) { this._renderer.scale.copy(this._scale); } else { this._renderer.scale.set(this._scale, this._scale); } } return this.flagReset(); } }, renderSvgArcCommand: function(ctx, ax, ay, rx, ry, largeArcFlag, sweepFlag, xAxisRotation, x, y) { xAxisRotation = xAxisRotation * Math.PI / 180; // Ensure radii are positive rx = abs(rx); ry = abs(ry); // Compute (x1′, y1′) var dx2 = (ax - x) / 2.0; var dy2 = (ay - y) / 2.0; var x1p = cos$4(xAxisRotation) * dx2 + sin$4(xAxisRotation) * dy2; var y1p = - sin$4(xAxisRotation) * dx2 + cos$4(xAxisRotation) * dy2; // Compute (cx′, cy′) var rxs = rx * rx; var rys = ry * ry; var x1ps = x1p * x1p; var y1ps = y1p * y1p; // Ensure radii are large enough var cr = x1ps / rxs + y1ps / rys; if (cr > 1) { // scale up rx,ry equally so cr == 1 var s = sqrt(cr); rx = s * rx; ry = s * ry; rxs = rx * rx; rys = ry * ry; } var dq = (rxs * y1ps + rys * x1ps); var pq = (rxs * rys - dq) / dq; var q = sqrt(max$2(0, pq)); if (largeArcFlag === sweepFlag) q = - q; var cxp = q * rx * y1p / ry; var cyp = - q * ry * x1p / rx; // Step 3: Compute (cx, cy) from (cx′, cy′) var cx = cos$4(xAxisRotation) * cxp - sin$4(xAxisRotation) * cyp + (ax + x) / 2; var cy = sin$4(xAxisRotation) * cxp + cos$4(xAxisRotation) * cyp + (ay + y) / 2; // Step 4: Compute θ1 and Δθ var startAngle = svgAngle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry); var delta = svgAngle((x1p - cxp) / rx, (y1p - cyp) / ry, (- x1p - cxp) / rx, (- y1p - cyp) / ry) % TWO_PI$5; var endAngle = startAngle + delta; var clockwise = sweepFlag === 0; renderArcEstimate(ctx, cx, cy, rx, ry, startAngle, endAngle, clockwise, xAxisRotation); } }; /** * @name Two.CanvasRenderer * @class * @extends Two.Events * @param {Object} [parameters] - This object is inherited when constructing a new instance of {@link Two}. * @param {Element} [parameters.domElement] - The `` to draw to. If none given a new one will be constructed. * @param {Boolean} [parameters.overdraw] - Determines whether the canvas should clear the background or not. Defaults to `true`. * @param {Boolean} [parameters.smoothing=true] - Determines whether the canvas should antialias drawing. Set it to `false` when working with pixel art. `false` can lead to better performance, since it would use a cheaper interpolation algorithm. * @description This class is used by {@link Two} when constructing with `type` of `Two.Types.canvas`. It takes Two.js' scenegraph and renders it to a ``. */ function Renderer$2(params) { // It might not make a big difference on GPU backed canvases. var smoothing = (params.smoothing !== false); /** * @name Two.CanvasRenderer#domElement * @property {Element} - The `` associated with the Two.js scene. */ this.domElement = params.domElement || document.createElement('canvas'); /** * @name Two.CanvasRenderer#ctx * @property {Canvas2DContext} - Associated two dimensional context to render on the ``. */ this.ctx = this.domElement.getContext('2d'); /** * @name Two.CanvasRenderer#overdraw * @property {Boolean} - Determines whether the canvas clears the background each draw call. * @default true */ this.overdraw = params.overdraw || false; if (typeof this.ctx.imageSmoothingEnabled !== 'undefined') { this.ctx.imageSmoothingEnabled = smoothing; } /** * @name Two.CanvasRenderer#scene * @property {Two.Group} - The root group of the scenegraph. */ this.scene = new Group(); this.scene.parent = this; } _.extend(Renderer$2, { /** * @name Two.CanvasRenderer.Utils * @property {Object} - A massive object filled with utility functions and properties to render Two.js objects to a ``. */ Utils: canvas }); _.extend(Renderer$2.prototype, Events, { constructor: Renderer$2, /** * @name Two.CanvasRenderer#setSize * @function * @fires resize * @param {Number} width - The new width of the renderer. * @param {Number} height - The new height of the renderer. * @param {Number} [ratio] - The new pixel ratio (pixel density) of the renderer. Defaults to calculate the pixel density of the user's screen. * @description Change the size of the renderer. */ setSize: function(width, height, ratio) { this.width = width; this.height = height; this.ratio = typeof ratio === 'undefined' ? getRatio(this.ctx) : ratio; this.domElement.width = width * this.ratio; this.domElement.height = height * this.ratio; if (this.domElement.style) { _.extend(this.domElement.style, { width: width + 'px', height: height + 'px' }); } return this.trigger(Events.Types.resize, width, height, ratio); }, /** * @name Two.CanvasRenderer#render * @function * @description Render the current scene to the ``. */ render: function() { var isOne = this.ratio === 1; if (!isOne) { this.ctx.save(); this.ctx.scale(this.ratio, this.ratio); } if (!this.overdraw) { this.ctx.clearRect(0, 0, this.width, this.height); } canvas.group.render.call(this.scene, this.ctx); if (!isOne) { this.ctx.restore(); } return this; } }); function renderArcEstimate(ctx, ox, oy, rx, ry, startAngle, endAngle, clockwise, xAxisRotation) { var epsilon = Curve.Tolerance.epsilon; var deltaAngle = endAngle - startAngle; var samePoints = Math.abs(deltaAngle) < epsilon; // ensures that deltaAngle is 0 .. 2 PI deltaAngle = mod(deltaAngle, TWO_PI$5); if (deltaAngle < epsilon) { if (samePoints) { deltaAngle = 0; } else { deltaAngle = TWO_PI$5; } } if (clockwise === true && ! samePoints) { if (deltaAngle === TWO_PI$5) { deltaAngle = - TWO_PI$5; } else { deltaAngle = deltaAngle - TWO_PI$5; } } for (var i = 0; i < Constants.Resolution; i++) { var t = i / (Constants.Resolution - 1); var angle = startAngle + t * deltaAngle; var x = ox + rx * Math.cos(angle); var y = oy + ry * Math.sin(angle); if (xAxisRotation !== 0) { var cos = Math.cos(xAxisRotation); var sin = Math.sin(xAxisRotation); var tx = x - ox; var ty = y - oy; // Rotate the point about the center of the ellipse. x = tx * cos - ty * sin + ox; y = tx * sin + ty * cos + oy; } ctx.lineTo(x, y); } } function svgAngle(ux, uy, vx, vy) { var dot = ux * vx + uy * vy; var len = sqrt(ux * ux + uy * uy) * sqrt(vx * vx + vy * vy); // floating point precision, slightly over values appear var ang = acos(max$2(-1, min$2(1, dot / len))); if ((ux * vy - uy * vx) < 0) { ang = - ang; } return ang; } var CanvasShim = { Image: null, isHeadless: false, /** * @name Two.Utils.shim * @function * @param {canvas} canvas - The instanced `Canvas` object provided by `node-canvas`. * @param {Image} [Image] - The prototypical `Image` object provided by `node-canvas`. This is only necessary to pass if you're going to load bitmap imagery. * @returns {canvas} Returns the instanced canvas object you passed from with additional attributes needed for Two.js. * @description Convenience method for defining all the dependencies from the npm package `node-canvas`. See [node-canvas](https://github.com/Automattic/node-canvas) for additional information on setting up HTML5 `` drawing in a node.js environment. */ shim: function(canvas, Image) { Renderer$2.Utils.shim(canvas); if (typeof Image !== 'undefined') { CanvasShim.Image = Image; } CanvasShim.isHeadless = true; return canvas; } }; var dom = { hasEventListeners: typeof root$1.addEventListener === 'function', bind: function(elem, event, func, bool) { if (this.hasEventListeners) { elem.addEventListener(event, func, !!bool); } else { elem.attachEvent('on' + event, func); } return dom; }, unbind: function(elem, event, func, bool) { if (dom.hasEventListeners) { elem.removeEventListeners(event, func, !!bool); } else { elem.detachEvent('on' + event, func); } return dom; }, getRequestAnimationFrame: function() { var lastTime = 0; var vendors = ['ms', 'moz', 'webkit', 'o']; var request = root$1.requestAnimationFrame, cancel; if(!request) { for (var i = 0; i < vendors.length; i++) { request = root$1[vendors[i] + 'RequestAnimationFrame'] || request; cancel = root$1[vendors[i] + 'CancelAnimationFrame'] || root$1[vendors[i] + 'CancelRequestAnimationFrame'] || cancel; } request = request || function(callback, element) { var currTime = new Date().getTime(); var timeToCall = Math.max(0, 16 - (currTime - lastTime)); var id = root$1.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); lastTime = currTime + timeToCall; return id; }; } return request; } }; var temp = (root$1.document ? root$1.document.createElement('div') : {}); temp.id = 'help-two-load'; Object.defineProperty(dom, 'temp', { enumerable: true, get: function() { if (_.isElement(temp) && !root$1.document.head.contains(temp)) { _.extend(temp.style, { display: 'none' }); root$1.document.head.appendChild(temp); } return temp; } }); /** * @name Two.Utils.Error * @class * @description Custom error throwing for Two.js specific identification. */ function TwoError(message) { this.name = 'Two.js'; this.message = message; } TwoError.prototype = new Error(); _.extend(TwoError.prototype, { constructor: TwoError }); /** * @name Two.Utils.defineGetterSetter * @function * @this Two# * @param {String} property - The property to add an enumerable getter / setter to. * @description Convenience function to setup the flag based getter / setter that most properties are defined as in Two.js. */ var defineGetterSetter = function(property) { var object = this; var secret = '_' + property; var flag = '_flag' + property.charAt(0).toUpperCase() + property.slice(1); Object.defineProperty(object, property, { enumerable: true, get: function() { return this[secret]; }, set: function(v) { this[secret] = v; this[flag] = true; } }); }; /** * @name Two.Registry * @class * @description An arbitrary class to manage a directory of things. Mainly used for keeping tabs of textures in Two.js. */ function Registry() { this.map = {}; } _.extend(Registry.prototype, { constructor: Registry, /** * @name Two.Registry#add * @function * @param {String} id - A unique identifier. * @param value - Any type of variable to be registered to the directory. * @description Adds any value to the directory. Assigned by the `id`. */ add: function(id, obj) { this.map[id] = obj; return this; }, /** * @name Two.Registry#remove * @function * @param {String} id - A unique identifier. * @description Remove any value from the directory by its `id`. */ remove: function(id) { delete this.map[id]; return this; }, /** * @name Two.Registry#get * @function * @param {String} id - A unique identifier. * @returns {?Object} The associated value. If unavailable then `undefined` is returned. * @description Get a registered value by its `id`. */ get: function(id) { return this.map[id]; }, /** * @name Two.Registry#contains * @function * @param {String} id - A unique identifier. * @returns {Boolean} * @description Convenience method to see if a value is registered to an `id` already. */ contains: function(id) { return id in this.map; } }); /** * @name Two.Stop * @class * @param {Number} [offset] - The offset percentage of the stop represented as a zero-to-one value. Default value flip flops from zero-to-one as new stops are created. * @param {String} [color] - The color of the stop. Default value flip flops from white to black as new stops are created. * @param {Number} [opacity] - The opacity value. Default value is 1, cannot be lower than 0. * @nota-bene Used specifically in conjunction with {@link Two.Gradient}s to control color graduation. */ function Stop(offset, color, opacity) { /** * @name Two.Stop#renderer * @property {Object} * @description Object access to store relevant renderer specific variables. Warning: manipulating this object can create unintended consequences. * @nota-bene With the {@link Two.SvgRenderer} you can access the underlying SVG element created via `shape.renderer.elem`. */ this.renderer = {}; this._renderer.type = 'stop'; /** * @name Two.Stop#offset * @property {Number} - The offset percentage of the stop represented as a zero-to-one value. */ this.offset = typeof offset === 'number' ? offset : Stop.Index <= 0 ? 0 : 1; /** * @name Two.Stop#opacity * @property {Number} - The alpha percentage of the stop represented as a zero-to-one value. */ this.opacity = typeof opacity === 'number' ? opacity : 1; /** * @name Two.Stop#color * @property {String} - The color of the stop. */ this.color = (typeof color === 'string') ? color : Stop.Index <= 0 ? '#fff' : '#000'; Stop.Index = (Stop.Index + 1) % 2; } _.extend(Stop, { /** * @name Two.Stop.Index * @property {Number} - The current index being referenced for calculating a stop's default offset value. */ Index: 0, /** * @name Two.Stop.Properties * @property {String[]} - A list of properties that are on every {@link Two.Stop}. */ Properties: [ 'offset', 'opacity', 'color' ], /** * @name Two.Stop.MakeObservable * @function * @param {Object} object - The object to make observable. * @description Convenience function to apply observable qualities of a {@link Two.Stop} to any object. Handy if you'd like to extend the {@link Two.Stop} class on a custom class. */ MakeObservable: function(object) { _.each(Stop.Properties, function(property) { var object = this; var secret = '_' + property; var flag = '_flag' + property.charAt(0).toUpperCase() + property.slice(1); Object.defineProperty(object, property, { enumerable: true, get: function() { return this[secret]; }, set: function(v) { this[secret] = v; this[flag] = true; if (this.parent) { this.parent._flagStops = true; } } }); }, object); Object.defineProperty(object, 'renderer', { enumerable: false, get: function() { return this._renderer; }, set: function(obj) { this._renderer = obj; } }); } }); _.extend(Stop.prototype, Events, { constructor: Stop, /** * @name Two.Stop#clone * @function * @param {Two.Group} [parent] - The parent group or scene to add the clone to. * @returns {Two.Stop} * @description Create a new instance of {@link Two.Stop} with the same properties of the current path. */ clone: function() { var clone = new Stop(); _.each(Stop.Properties, function(property) { clone[property] = this[property]; }, this); return clone; }, /** * @name Two.Stop#toObject * @function * @returns {Object} * @description Return a JSON compatible plain object that represents the path. */ toObject: function() { var result = {}; _.each(Stop.Properties, function(k) { result[k] = this[k]; }, this); return result; }, /** * @name Two.Stop#flagReset * @function * @private * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer. */ flagReset: function() { this._flagOffset = this._flagColor = this._flagOpacity = false; return this; } }); Stop.MakeObservable(Stop.prototype); /** * @name Two.Gradient * @class * @param {Two.Stop[]} [stops] - A list of {@link Two.Stop}s that contain the gradient fill pattern for the gradient. * @description This is the base class for constructing different types of gradients with Two.js. The two common gradients are {@link Two.LinearGradient} and {@link Two.RadialGradient}. */ function Gradient(stops) { /** * @name Two.Gradient#renderer * @property {Object} * @description Object access to store relevant renderer specific variables. Warning: manipulating this object can create unintended consequences. * @nota-bene With the {@link Two.SvgRenderer} you can access the underlying SVG element created via `shape.renderer.elem`. */ this.renderer = {}; this._renderer.type = 'gradient'; /** * @name Two.Gradient#id * @property {String} - Session specific unique identifier. * @nota-bene In the {@link Two.SvgRenderer} change this to change the underlying SVG element's id too. */ this.id = Constants.Identifier + Constants.uniqueId(); this.classList = []; this._renderer.flagStops = Gradient.FlagStops.bind(this); this._renderer.bindStops = Gradient.BindStops.bind(this); this._renderer.unbindStops = Gradient.UnbindStops.bind(this); /** * @name Two.Gradient#spread * @property {String} - Indicates what happens if the gradient starts or ends inside the bounds of the target rectangle. Possible values are `'pad'`, `'reflect'`, and `'repeat'`. * @see {@link https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementSpreadMethodAttribute} for more information */ this.spread = 'pad'; /** * @name Two.Gradient#stops * @property {Two.Stop[]} - An ordered list of {@link Two.Stop}s for rendering the gradient. */ if (stops) { this.stops = stops; } } _.extend(Gradient, { /** * @name Two.Gradient.Stop * @see {@link Two.Stop} */ Stop: Stop, /** * @name Two.Gradient.Properties * @property {String[]} - A list of properties that are on every {@link Two.Gradient}. */ Properties: [ 'spread' ], /** * @name Two.Gradient.MakeObservable * @function * @param {Object} object - The object to make observable. * @description Convenience function to apply observable qualities of a {@link Two.Gradient} to any object. Handy if you'd like to extend the {@link Two.Gradient} class on a custom class. */ MakeObservable: function(object) { _.each(Gradient.Properties, defineGetterSetter, object); Object.defineProperty(object, 'stops', { enumerable: true, get: function() { return this._stops; }, set: function(stops) { var bindStops = this._renderer.bindStops; var unbindStops = this._renderer.unbindStops; // Remove previous listeners if (this._stops) { this._stops .unbind(Events.Types.insert, bindStops) .unbind(Events.Types.remove, unbindStops); } // Create new Collection with copy of Stops this._stops = new Collection((stops || []).slice(0)); // Listen for Collection changes and bind / unbind this._stops .bind(Events.Types.insert, bindStops) .bind(Events.Types.remove, unbindStops); // Bind Initial Stops bindStops(this._stops); } }); Object.defineProperty(object, 'renderer', { enumerable: false, get: function() { return this._renderer; }, set: function(obj) { this._renderer = obj; } }); Object.defineProperty(object, 'id', { enumerable: true, get: function() { return this._id; }, set: function(v) { this._id = v; } }); }, /** * @name Two.Gradient.FlagStops * @function * @description Cached method to let renderers know stops have been updated on a {@link Two.Gradient}. */ FlagStops: function() { this._flagStops = true; }, /** * @name Two.Gradient.BindVertices * @function * @description Cached method to let {@link Two.Gradient} know vertices have been added to the instance. */ BindStops: function(items) { // This function is called a lot // when importing a large SVG var i = items.length; while(i--) { items[i].bind(Events.Types.change, this._renderer.flagStops); items[i].parent = this; } this._renderer.flagStops(); }, /** * @name Two.Gradient.UnbindStops * @function * @description Cached method to let {@link Two.Gradient} know vertices have been removed from the instance. */ UnbindStops: function(items) { var i = items.length; while(i--) { items[i].unbind(Events.Types.change, this._renderer.flagStops); delete items[i].parent; } this._renderer.flagStops(); } }); _.extend(Gradient.prototype, Events, { constructor: Gradient, /** * @name Two.Gradient#_flagId * @private * @property {Boolean} - Determines whether the {@link Two.Gradient#id} needs updating. */ _flagId: false, /** * @name Two.Gradient#_flagStops * @private * @property {Boolean} - Determines whether the {@link Two.Gradient#stops} needs updating. */ _flagStops: false, /** * @name Two.Gradient#_flagSpread * @private * @property {Boolean} - Determines whether the {@link Two.Gradient#spread} needs updating. */ _flagSpread: false, _id: '', /** * @name Two.Gradient#clone * @function * @param {Two.Group} [parent] - The parent group or scene to add the clone to. * @returns {Two.Gradient} * @description Create a new instance of {@link Two.Gradient} with the same properties of the current path. */ clone: function(parent) { var stops = this.stops.map(function(s) { return s.clone(); }); var clone = new Gradient(stops); _.each(Gradient.Properties, function(k) { clone[k] = this[k]; }, this); if (parent) { parent.add(clone); } return clone; }, /** * @name Two.Gradient#toObject * @function * @returns {Object} * @description Return a JSON compatible plain object that represents the path. */ toObject: function() { var result = { stops: this.stops.map(function(s) { return s.toObject(); }) }; _.each(Gradient.Properties, function(k) { result[k] = this[k]; }, this); return result; }, /** * @name Two.Gradient#_update * @function * @private * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well. * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be. * @nota-bene Try not to call this method more than once a frame. */ _update: function() { if (this._flagSpread || this._flagStops) { this.trigger(Events.Types.change); } return this; }, /** * @name Two.Gradient#flagReset * @function * @private * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer. */ flagReset: function() { this._flagSpread = this._flagStops = false; return this; } }); Gradient.MakeObservable(Gradient.prototype); /** * @name Two.LinearGradient * @class * @extends Two.Gradient * @param {Number} [x1=0] - The x position of the first end point of the linear gradient. * @param {Number} [y1=0] - The y position of the first end point of the linear gradient. * @param {Number} [x2=0] - The x position of the second end point of the linear gradient. * @param {Number} [y2=0] - The y position of the second end point of the linear gradient. * @param {Two.Stop[]} [stops] - A list of {@link Two.Stop}s that contain the gradient fill pattern for the gradient. * @nota-bene The linear gradient lives within the space of the parent object's matrix space. */ function LinearGradient(x1, y1, x2, y2, stops) { Gradient.call(this, stops); this._renderer.type = 'linear-gradient'; var flagEndPoints = LinearGradient.FlagEndPoints.bind(this); /** * @name Two.LinearGradient#left * @property {Two.Vector} - The x and y value for where the first end point is placed on the canvas. */ this.left = new Vector().bind(Events.Types.change, flagEndPoints); /** * @name Two.LinearGradient#right * @property {Two.Vector} - The x and y value for where the second end point is placed on the canvas. */ this.right = new Vector().bind(Events.Types.change, flagEndPoints); if (typeof x1 === 'number') { this.left.x = x1; } if (typeof y1 === 'number') { this.left.y = y1; } if (typeof x2 === 'number') { this.right.x = x2; } if (typeof y2 === 'number') { this.right.y = y2; } } _.extend(LinearGradient, { /** * @name Two.LinearGradient.Stop * @see {@link Two.Stop} */ Stop: Stop, /** * @name Two.LinearGradient.MakeObservable * @function * @param {Object} object - The object to make observable. * @description Convenience function to apply observable qualities of a {@link Two.LinearGradient} to any object. Handy if you'd like to extend the {@link Two.LinearGradient} class on a custom class. */ MakeObservable: function(object) { Gradient.MakeObservable(object); }, /** * @name Two.LinearGradient.FlagEndPoints * @function * @description Cached method to let renderers know end points have been updated on a {@link Two.LinearGradient}. */ FlagEndPoints: function() { this._flagEndPoints = true; } }); _.extend(LinearGradient.prototype, Gradient.prototype, { constructor: LinearGradient, /** * @name Two.LinearGradient#_flagEndPoints * @private * @property {Boolean} - Determines whether the {@link Two.LinearGradient#left} or {@link Two.LinearGradient#right} changed and needs to update. */ _flagEndPoints: false, /** * @name Two.LinearGradient#clone * @function * @param {Two.Group} [parent] - The parent group or scene to add the clone to. * @returns {Two.Gradient} * @description Create a new instance of {@link Two.LinearGradient} with the same properties of the current path. */ clone: function(parent) { var stops = this.stops.map(function(stop) { return stop.clone(); }); var clone = new LinearGradient(this.left._x, this.left._y, this.right._x, this.right._y, stops); _.each(Gradient.Properties, function(k) { clone[k] = this[k]; }, this); if (parent) { parent.add(clone); } return clone; }, /** * @name Two.LinearGradient#toObject * @function * @returns {Object} * @description Return a JSON compatible plain object that represents the path. */ toObject: function() { var result = Gradient.prototype.toObject.call(this); result.left = this.left.toObject(); result.right = this.right.toObject(); return result; }, /** * @name Two.LinearGradient#_update * @function * @private * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well. * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be. * @nota-bene Try not to call this method more than once a frame. */ _update: function() { if (this._flagEndPoints || this._flagSpread || this._flagStops) { this.trigger(Events.Types.change); } return this; }, /** * @name Two.LinearGradient#flagReset * @function * @private * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer. */ flagReset: function() { this._flagEndPoints = false; Gradient.prototype.flagReset.call(this); return this; } }); LinearGradient.MakeObservable(LinearGradient.prototype); /** * @name Two.RadialGradient * @class * @extends Two.Gradient * @param {Number} [x=0] - The x position of the origin of the radial gradient. * @param {Number} [y=0] - The y position of the origin of the radial gradient. * @param {Number} [radius=0] - The radius of the radial gradient. * @param {Two.Stop[]} [stops] - A list of {@link Two.Stop}s that contain the gradient fill pattern for the gradient. * @param {Number} [focalX=0] - The x position of the focal point on the radial gradient. * @param {Number} [focalY=0] - The y position of the focal point on the radial gradient. * @nota-bene The radial gradient lives within the space of the parent object's matrix space. */ function RadialGradient(cx, cy, r, stops, fx, fy) { Gradient.call(this, stops); this._renderer.type = 'radial-gradient'; /** * @name Two.RadialGradient#center * @property {Two.Vector} - The x and y value for where the origin of the radial gradient is. */ this.center = new Vector() .bind(Events.Types.change, (function() { this._flagCenter = true; }).bind(this)); this.radius = typeof r === 'number' ? r : 20; /** * @name Two.RadialGradient#focal * @property {Two.Vector} - The x and y value for where the focal point of the radial gradient is. * @nota-bene This effects the spray or spread of the radial gradient. */ this.focal = new Vector() .bind(Events.Types.change, (function() { this._flagFocal = true; }).bind(this)); if (typeof cx === 'number') { this.center.x = cx; } if (typeof cy === 'number') { this.center.y = cy; } this.focal.copy(this.center); if (typeof fx === 'number') { this.focal.x = fx; } if (typeof fy === 'number') { this.focal.y = fy; } } _.extend(RadialGradient, { /** * @name Two.RadialGradient.Stop * @see {@link Two.Stop} */ Stop: Stop, /** * @name Two.RadialGradient.Properties * @property {String[]} - A list of properties that are on every {@link Two.RadialGradient}. */ Properties: [ 'radius' ], /** * @name Two.RadialGradient.MakeObservable * @function * @param {Object} object - The object to make observable. * @description Convenience function to apply observable qualities of a {@link Two.RadialGradient} to any object. Handy if you'd like to extend the {@link Two.RadialGradient} class on a custom class. */ MakeObservable: function(object) { Gradient.MakeObservable(object); _.each(RadialGradient.Properties, defineGetterSetter, object); } }); _.extend(RadialGradient.prototype, Gradient.prototype, { constructor: RadialGradient, /** * @name Two.RadialGradient#_flagRadius * @private * @property {Boolean} - Determines whether the {@link Two.RadialGradient#radius} changed and needs to update. */ _flagRadius: false, /** * @name Two.RadialGradient#_flagCenter * @private * @property {Boolean} - Determines whether the {@link Two.RadialGradient#center} changed and needs to update. */ _flagCenter: false, /** * @name Two.RadialGradient#_flagFocal * @private * @property {Boolean} - Determines whether the {@link Two.RadialGradient#focal} changed and needs to update. */ _flagFocal: false, /** * @name Two.RadialGradient#clone * @function * @param {Two.Group} [parent] - The parent group or scene to add the clone to. * @returns {Two.Gradient} * @description Create a new instance of {@link Two.RadialGradient} with the same properties of the current path. */ clone: function(parent) { var stops = this.stops.map(function(stop) { return stop.clone(); }); var clone = new RadialGradient(this.center._x, this.center._y, this._radius, stops, this.focal._x, this.focal._y); _.each(Gradient.Properties.concat(RadialGradient.Properties), function(k) { clone[k] = this[k]; }, this); if (parent) { parent.add(clone); } return clone; }, /** * @name Two.RadialGradient#toObject * @function * @returns {Object} * @description Return a JSON compatible plain object that represents the path. */ toObject: function() { var result = Gradient.prototype.toObject.call(this); _.each(RadialGradient.Properties, function(k) { result[k] = this[k]; }, this); result.center = this.center.toObject(); result.focal = this.focal.toObject(); return result; }, /** * @name Two.RadialGradient#_update * @function * @private * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well. * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be. * @nota-bene Try not to call this method more than once a frame. */ _update: function() { if (this._flagRadius || this._flatCenter || this._flagFocal || this._flagSpread || this._flagStops) { this.trigger(Events.Types.change); } return this; }, /** * @name Two.RadialGradient#flagReset * @function * @private * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer. */ flagReset: function() { this._flagRadius = this._flagCenter = this._flagFocal = false; Gradient.prototype.flagReset.call(this); return this; } }); RadialGradient.MakeObservable(RadialGradient.prototype); var anchor; var regex$1 = { video: /\.(mp4|webm|ogg)$/i, image: /\.(jpe?g|png|gif|tiff|webp)$/i, effect: /texture|gradient/i }; if (root$1.document) { anchor = document.createElement('a'); } /** * @name Two.Texture * @class * @extends Two.Shape * @param {String|HTMLImageElement} [src] - The URL path to an image file or an `` element. * @param {Function} [callback] - An optional callback function once the image has been loaded. * @description Fundamental to work with bitmap data, a.k.a. pregenerated imagery, in Two.js. Supported formats include jpg, png, gif, and tiff. See {@link Two.Texture.RegularExpressions} for a full list of supported formats. */ function Texture(src, callback) { /** * @name Two.Texture#renderer * @property {Object} * @description Object access to store relevant renderer specific variables. Warning: manipulating this object can create unintended consequences. * @nota-bene With the {@link Two.SvgRenderer} you can access the underlying SVG element created via `shape.renderer.elem`. */ this.renderer = {}; this._renderer.type = 'texture'; this._renderer.flagOffset = Texture.FlagOffset.bind(this); this._renderer.flagScale = Texture.FlagScale.bind(this); this.id = Constants.Identifier + Constants.uniqueId(); this.classList = []; /** * @name Two.Texture#loaded * @property {Boolean} - Shorthand value to determine if image has been loaded into the texture. */ this.loaded = false; /** * @name Two.Texture#repeat * @property {String} - CSS style declaration to tile {@link Two.Path}. Valid values include: `'no-repeat'`, `'repeat'`, `'repeat-x'`, `'repeat-y'`. * @see {@link https://www.w3.org/TR/2dcontext/#dom-context-2d-createpattern} */ this.repeat = 'no-repeat'; /** * @name Two.Texture#offset * @property {Two.Vector} - A two-component vector describing any pixel offset of the texture when applied to a {@link Two.Path}. */ this.offset = new Vector(); if (typeof callback === 'function') { var loaded = (function() { this.unbind(Events.Types.load, loaded); if (typeof callback === 'function') { callback(); } }).bind(this); this.bind(Events.Types.load, loaded); } /** * @name Two.Texture#src * @property {String} - The URL path to the image data. * @nota-bene This property is ultimately serialized in a {@link Two.Registry} to cache retrieval. */ if (typeof src === 'string') { this.src = src; } else if (typeof src === 'object') { var elemString = Object.prototype.toString.call(src); if ( elemString === '[object HTMLImageElement]' || elemString === '[object HTMLCanvasElement]' || elemString === '[object HTMLVideoElement]' || elemString === '[object Image]' ) { /** * @name Two.Texture#image * @property {Element} - The corresponding DOM Element of the texture. Can be a ``, ``, or `