10245 lines
247 KiB
JavaScript
10245 lines
247 KiB
JavaScript
/**
|
||
* two.js
|
||
* a two-dimensional drawing api meant for modern browsers. It is renderer
|
||
* agnostic enabling the same api for rendering in multiple contexts: webgl,
|
||
* canvas2d, and svg.
|
||
*
|
||
* Copyright (c) 2012 - 2017 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.
|
||
*
|
||
*/
|
||
|
||
this.Two = (function(previousTwo) {
|
||
|
||
var root = typeof window != 'undefined' ? window : typeof global != 'undefined' ? global : null;
|
||
var toString = Object.prototype.toString;
|
||
var _ = {
|
||
// http://underscorejs.org/ • 1.8.3
|
||
_indexAmount: 0,
|
||
natural: {
|
||
slice: Array.prototype.slice,
|
||
indexOf: Array.prototype.indexOf,
|
||
keys: Object.keys,
|
||
bind: Function.prototype.bind,
|
||
create: Object.create
|
||
},
|
||
identity: function(value) {
|
||
return value;
|
||
},
|
||
isArguments: function(obj) {
|
||
return toString.call(obj) === '[object Arguments]';
|
||
},
|
||
isFunction: function(obj) {
|
||
return toString.call(obj) === '[object Function]';
|
||
},
|
||
isString: function(obj) {
|
||
return toString.call(obj) === '[object String]';
|
||
},
|
||
isNumber: function(obj) {
|
||
return toString.call(obj) === '[object Number]';
|
||
},
|
||
isDate: function(obj) {
|
||
return toString.call(obj) === '[object Date]';
|
||
},
|
||
isRegExp: function(obj) {
|
||
return toString.call(obj) === '[object RegExp]';
|
||
},
|
||
isError: function(obj) {
|
||
return toString.call(obj) === '[object Error]';
|
||
},
|
||
isFinite: function(obj) {
|
||
return isFinite(obj) && !isNaN(parseFloat(obj));
|
||
},
|
||
isNaN: function(obj) {
|
||
return _.isNumber(obj) && obj !== +obj;
|
||
},
|
||
isBoolean: function(obj) {
|
||
return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
|
||
},
|
||
isNull: function(obj) {
|
||
return obj === null;
|
||
},
|
||
isUndefined: function(obj) {
|
||
return obj === void 0;
|
||
},
|
||
isEmpty: function(obj) {
|
||
if (obj == null) return true;
|
||
if (isArrayLike && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
|
||
return _.keys(obj).length === 0;
|
||
},
|
||
isElement: function(obj) {
|
||
return !!(obj && obj.nodeType === 1);
|
||
},
|
||
isArray: Array.isArray || function(obj) {
|
||
return toString.call(obj) === '[object Array]';
|
||
},
|
||
isObject: function(obj) {
|
||
var type = typeof obj;
|
||
return type === 'function' || type === 'object' && !!obj;
|
||
},
|
||
toArray: function(obj) {
|
||
if (!obj) {
|
||
return [];
|
||
}
|
||
if (_.isArray(obj)) {
|
||
return slice.call(obj);
|
||
}
|
||
if (isArrayLike(obj)) {
|
||
return _.map(obj, _.identity);
|
||
}
|
||
return _.values(obj);
|
||
},
|
||
range: function(start, stop, step) {
|
||
if (stop == null) {
|
||
stop = start || 0;
|
||
start = 0;
|
||
}
|
||
step = step || 1;
|
||
|
||
var length = Math.max(Math.ceil((stop - start) / step), 0);
|
||
var range = Array(length);
|
||
|
||
for (var idx = 0; idx < length; idx++, start += step) {
|
||
range[idx] = start;
|
||
}
|
||
|
||
return range;
|
||
},
|
||
indexOf: function(list, item) {
|
||
if (!!_.natural.indexOf) {
|
||
return _.natural.indexOf.call(list, item);
|
||
}
|
||
for (var i = 0; i < list.length; i++) {
|
||
if (list[i] === item) {
|
||
return i;
|
||
}
|
||
}
|
||
return -1;
|
||
},
|
||
has: function(obj, key) {
|
||
return obj != null && hasOwnProperty.call(obj, key);
|
||
},
|
||
bind: function(func, ctx) {
|
||
var natural = _.natural.bind;
|
||
if (natural && func.bind === natural) {
|
||
return natural.apply(func, slice.call(arguments, 1));
|
||
}
|
||
var args = slice.call(arguments, 2);
|
||
return function() {
|
||
func.apply(ctx, args);
|
||
};
|
||
},
|
||
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;
|
||
},
|
||
keys: function(obj) {
|
||
if (!_.isObject(obj)) {
|
||
return [];
|
||
}
|
||
if (_.natural.keys) {
|
||
return _.natural.keys(obj);
|
||
}
|
||
var keys = [];
|
||
for (var k in obj) {
|
||
if (_.has(obj, k)) {
|
||
keys.push(k);
|
||
}
|
||
}
|
||
return keys;
|
||
},
|
||
values: function(obj) {
|
||
var keys = _.keys(obj);
|
||
var values = [];
|
||
for (var i = 0; i < keys.length; i++) {
|
||
var k = keys[i];
|
||
values.push(obj[k]);
|
||
}
|
||
return values;
|
||
},
|
||
each: function(obj, iteratee, context) {
|
||
var ctx = context || this;
|
||
var keys = !isArrayLike(obj) && _.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;
|
||
},
|
||
map: function(obj, iteratee, context) {
|
||
var ctx = context || this;
|
||
var keys = !isArrayLike(obj) && _.keys(obj);
|
||
var length = (keys || obj).length;
|
||
var result = [];
|
||
for (var i = 0; i < length; i++) {
|
||
var k = keys ? keys[i] : i;
|
||
result[i] = iteratee.call(ctx, obj[k], k, obj);
|
||
}
|
||
return result;
|
||
},
|
||
once: function(func) {
|
||
var init = false;
|
||
return function() {
|
||
if (!!init) {
|
||
return func;
|
||
}
|
||
init = true;
|
||
return func.apply(this, arguments);
|
||
}
|
||
},
|
||
after: function(times, func) {
|
||
return function() {
|
||
while (--times < 1) {
|
||
return func.apply(this, arguments);
|
||
}
|
||
}
|
||
},
|
||
uniqueId: function(prefix) {
|
||
var id = ++_._indexAmount + '';
|
||
return prefix ? prefix + id : id;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Constants
|
||
*/
|
||
|
||
var sin = Math.sin,
|
||
cos = Math.cos,
|
||
atan2 = Math.atan2,
|
||
sqrt = Math.sqrt,
|
||
round = Math.round,
|
||
abs = Math.abs,
|
||
PI = Math.PI,
|
||
TWO_PI = PI * 2,
|
||
HALF_PI = PI / 2,
|
||
pow = Math.pow,
|
||
min = Math.min,
|
||
max = Math.max;
|
||
|
||
/**
|
||
* Localized variables
|
||
*/
|
||
|
||
var count = 0;
|
||
var slice = _.natural.slice;
|
||
var perf = ((root.performance && root.performance.now) ? root.performance : Date);
|
||
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
|
||
var getLength = function(obj) {
|
||
return obj == null ? void 0 : obj['length'];
|
||
};
|
||
var isArrayLike = function(collection) {
|
||
var length = getLength(collection);
|
||
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
|
||
};
|
||
|
||
/**
|
||
* Cross browser dom events.
|
||
*/
|
||
var dom = {
|
||
|
||
temp: (root.document ? root.document.createElement('div') : {}),
|
||
|
||
hasEventListeners: _.isFunction(root.addEventListener),
|
||
|
||
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.requestAnimationFrame, cancel;
|
||
|
||
if(!request) {
|
||
for (var i = 0; i < vendors.length; i++) {
|
||
request = root[vendors[i] + 'RequestAnimationFrame'] || request;
|
||
cancel = root[vendors[i] + 'CancelAnimationFrame']
|
||
|| root[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.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
|
||
lastTime = currTime + timeToCall;
|
||
return id;
|
||
};
|
||
// cancel = cancel || function(id) {
|
||
// clearTimeout(id);
|
||
// };
|
||
}
|
||
|
||
request.init = _.once(loop);
|
||
|
||
return request;
|
||
|
||
}
|
||
|
||
};
|
||
|
||
/**
|
||
* @class
|
||
*/
|
||
var Two = root.Two = function(options) {
|
||
|
||
// Determine what Renderer to use and setup a scene.
|
||
|
||
var params = _.defaults(options || {}, {
|
||
fullscreen: false,
|
||
width: 640,
|
||
height: 480,
|
||
type: Two.Types.svg,
|
||
autostart: false
|
||
});
|
||
|
||
_.each(params, function(v, k) {
|
||
if (k === 'fullscreen' || k === 'autostart') {
|
||
return;
|
||
}
|
||
this[k] = v;
|
||
}, this);
|
||
|
||
// Specified domElement overrides type declaration only if the element does not support declared renderer type.
|
||
if (_.isElement(params.domElement)) {
|
||
var tagName = params.domElement.tagName.toLowerCase();
|
||
// TODO: Reconsider this if statement's logic.
|
||
if (!/^(CanvasRenderer-canvas|WebGLRenderer-canvas|SVGRenderer-svg)$/.test(this.type+'-'+tagName)) {
|
||
this.type = Two.Types[tagName];
|
||
}
|
||
}
|
||
|
||
this.renderer = new Two[this.type](this);
|
||
Two.Utils.setPlaying.call(this, params.autostart);
|
||
this.frameCount = 0;
|
||
|
||
if (params.fullscreen) {
|
||
|
||
var fitted = _.bind(fitToWindow, this);
|
||
_.extend(document.body.style, {
|
||
overflow: 'hidden',
|
||
margin: 0,
|
||
padding: 0,
|
||
top: 0,
|
||
left: 0,
|
||
right: 0,
|
||
bottom: 0,
|
||
position: 'fixed'
|
||
});
|
||
_.extend(this.renderer.domElement.style, {
|
||
display: 'block',
|
||
top: 0,
|
||
left: 0,
|
||
right: 0,
|
||
bottom: 0,
|
||
position: 'fixed'
|
||
});
|
||
dom.bind(root, 'resize', fitted);
|
||
fitted();
|
||
|
||
|
||
} else if (!_.isElement(params.domElement)) {
|
||
|
||
this.renderer.setSize(params.width, params.height, this.ratio);
|
||
this.width = params.width;
|
||
this.height = params.height;
|
||
|
||
}
|
||
|
||
this.scene = this.renderer.scene;
|
||
|
||
Two.Instances.push(this);
|
||
raf.init();
|
||
|
||
};
|
||
|
||
_.extend(Two, {
|
||
|
||
/**
|
||
* Access to root in other files.
|
||
*/
|
||
|
||
root: root,
|
||
|
||
/**
|
||
* Primitive
|
||
*/
|
||
|
||
Array: root.Float32Array || Array,
|
||
|
||
Types: {
|
||
webgl: 'WebGLRenderer',
|
||
svg: 'SVGRenderer',
|
||
canvas: 'CanvasRenderer'
|
||
},
|
||
|
||
Version: 'v0.7.0',
|
||
|
||
Identifier: 'two_',
|
||
|
||
Properties: {
|
||
hierarchy: 'hierarchy',
|
||
demotion: 'demotion'
|
||
},
|
||
|
||
Events: {
|
||
play: 'play',
|
||
pause: 'pause',
|
||
update: 'update',
|
||
render: 'render',
|
||
resize: 'resize',
|
||
change: 'change',
|
||
remove: 'remove',
|
||
insert: 'insert',
|
||
order: 'order',
|
||
load: 'load'
|
||
},
|
||
|
||
Commands: {
|
||
move: 'M',
|
||
line: 'L',
|
||
curve: 'C',
|
||
close: 'Z'
|
||
},
|
||
|
||
Resolution: 8,
|
||
|
||
Instances: [],
|
||
|
||
noConflict: function() {
|
||
root.Two = previousTwo;
|
||
return this;
|
||
},
|
||
|
||
uniqueId: function() {
|
||
var id = count;
|
||
count++;
|
||
return id;
|
||
},
|
||
|
||
Utils: _.extend(_, {
|
||
|
||
performance: perf,
|
||
|
||
defineProperty: 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;
|
||
}
|
||
});
|
||
|
||
},
|
||
|
||
/**
|
||
* Release an arbitrary class' events from the two.js corpus and recurse
|
||
* through its children and or vertices.
|
||
*/
|
||
release: function(obj) {
|
||
|
||
if (!_.isObject(obj)) {
|
||
return;
|
||
}
|
||
|
||
if (_.isFunction(obj.unbind)) {
|
||
obj.unbind();
|
||
}
|
||
|
||
if (obj.vertices) {
|
||
if (_.isFunction(obj.vertices.unbind)) {
|
||
obj.vertices.unbind();
|
||
}
|
||
_.each(obj.vertices, function(v) {
|
||
if (_.isFunction(v.unbind)) {
|
||
v.unbind();
|
||
}
|
||
});
|
||
}
|
||
|
||
if (obj.children) {
|
||
_.each(obj.children, function(obj) {
|
||
Two.Utils.release(obj);
|
||
});
|
||
}
|
||
|
||
},
|
||
|
||
xhr: function(path, callback) {
|
||
|
||
var xhr = new XMLHttpRequest();
|
||
xhr.open('GET', path);
|
||
|
||
xhr.onreadystatechange = function() {
|
||
if (xhr.readyState === 4 && xhr.status === 200) {
|
||
callback(xhr.responseText);
|
||
}
|
||
};
|
||
|
||
xhr.send();
|
||
return xhr;
|
||
|
||
},
|
||
|
||
Curve: {
|
||
|
||
CollinearityEpsilon: pow(10, -30),
|
||
|
||
RecursionLimit: 16,
|
||
|
||
CuspLimit: 0,
|
||
|
||
Tolerance: {
|
||
distance: 0.25,
|
||
angle: 0,
|
||
epsilon: 0.01
|
||
},
|
||
|
||
// 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]
|
||
]
|
||
|
||
},
|
||
|
||
/**
|
||
* Account for high dpi rendering.
|
||
* http://www.html5rocks.com/en/tutorials/canvas/hidpi/
|
||
*/
|
||
|
||
devicePixelRatio: root.devicePixelRatio || 1,
|
||
|
||
getBackingStoreRatio: function(ctx) {
|
||
return ctx.webkitBackingStorePixelRatio ||
|
||
ctx.mozBackingStorePixelRatio ||
|
||
ctx.msBackingStorePixelRatio ||
|
||
ctx.oBackingStorePixelRatio ||
|
||
ctx.backingStorePixelRatio || 1;
|
||
},
|
||
|
||
getRatio: function(ctx) {
|
||
return Two.Utils.devicePixelRatio / getBackingStoreRatio(ctx);
|
||
},
|
||
|
||
/**
|
||
* Properly defer play calling until after all objects
|
||
* have been updated with their newest styles.
|
||
*/
|
||
setPlaying: function(b) {
|
||
|
||
this.playing = !!b;
|
||
return this;
|
||
|
||
},
|
||
|
||
/**
|
||
* Return the computed matrix of a nested object.
|
||
* TODO: Optimize traversal.
|
||
*/
|
||
getComputedMatrix: function(object, matrix) {
|
||
|
||
matrix = (matrix && matrix.identity()) || new Two.Matrix();
|
||
var parent = object, matrices = [];
|
||
|
||
while (parent && parent._matrix) {
|
||
matrices.push(parent._matrix);
|
||
parent = parent.parent;
|
||
}
|
||
|
||
matrices.reverse();
|
||
|
||
_.each(matrices, function(m) {
|
||
|
||
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;
|
||
|
||
},
|
||
|
||
deltaTransformPoint: function(matrix, x, y) {
|
||
|
||
var dx = x * matrix.a + y * matrix.c + 0;
|
||
var dy = x * matrix.b + y * matrix.d + 0;
|
||
|
||
return new Two.Vector(dx, dy);
|
||
|
||
},
|
||
|
||
/**
|
||
* https://gist.github.com/2052247
|
||
*/
|
||
decomposeMatrix: function(matrix) {
|
||
|
||
// calculate delta transform point
|
||
var px = Two.Utils.deltaTransformPoint(matrix, 0, 1);
|
||
var py = Two.Utils.deltaTransformPoint(matrix, 1, 0);
|
||
|
||
// calculate skew
|
||
var skewX = ((180 / Math.PI) * Math.atan2(px.y, px.x) - 90);
|
||
var skewY = ((180 / Math.PI) * Math.atan2(py.y, py.x));
|
||
|
||
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),
|
||
skewX: skewX,
|
||
skewY: skewY,
|
||
rotation: skewX // rotation is the same as skew x
|
||
};
|
||
|
||
},
|
||
|
||
/**
|
||
* Walk through item properties and pick the ones of interest.
|
||
* Will try to resolve styles applied via CSS
|
||
*
|
||
* TODO: Reverse calculate `Two.Gradient`s for fill / stroke
|
||
* of any given path.
|
||
*/
|
||
applySvgAttributes: function(node, elem) {
|
||
|
||
var attributes = {}, styles = {}, i, key, value, attr;
|
||
|
||
// Not available in non browser environments
|
||
if (getComputedStyle) {
|
||
// Convert CSSStyleDeclaration to a normal object
|
||
var computedStyles = getComputedStyle(node);
|
||
i = computedStyles.length;
|
||
|
||
while (i--) {
|
||
key = computedStyles[i];
|
||
value = computedStyles[key];
|
||
// Gecko returns undefined for unset properties
|
||
// Webkit returns the default value
|
||
if (value !== undefined) {
|
||
styles[key] = value;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Convert NodeMap to a normal object
|
||
i = node.attributes.length;
|
||
while (i--) {
|
||
attr = node.attributes[i];
|
||
attributes[attr.nodeName] = attr.value;
|
||
}
|
||
|
||
// Getting the correct opacity is a bit tricky, since SVG path elements don't
|
||
// support opacity as an attribute, but you can apply it via CSS.
|
||
// So we take the opacity and set (stroke/fill)-opacity to the same value.
|
||
if (!_.isUndefined(styles.opacity)) {
|
||
styles['stroke-opacity'] = styles.opacity;
|
||
styles['fill-opacity'] = styles.opacity;
|
||
}
|
||
|
||
// Merge attributes and applied styles (attributes take precedence)
|
||
_.extend(styles, attributes);
|
||
|
||
// Similarly visibility is influenced by the value of both display and visibility.
|
||
// Calculate a unified value here which defaults to `true`.
|
||
styles.visible = !(_.isUndefined(styles.display) && styles.display === 'none')
|
||
|| (_.isUndefined(styles.visibility) && styles.visibility === 'hidden');
|
||
|
||
// Now iterate the whole thing
|
||
for (key in styles) {
|
||
value = styles[key];
|
||
|
||
switch (key) {
|
||
case 'transform':
|
||
// TODO: Check this out https://github.com/paperjs/paper.js/blob/master/src/svg/SVGImport.js#L313
|
||
if (value === 'none') break;
|
||
var m = node.getCTM ? node.getCTM() : null;
|
||
|
||
// Might happen when transform string is empty or not valid.
|
||
if (m === null) break;
|
||
|
||
// // Option 1: edit the underlying matrix and don't force an auto calc.
|
||
// var m = node.getCTM();
|
||
// elem._matrix.manual = true;
|
||
// elem._matrix.set(m.a, m.b, m.c, m.d, m.e, m.f);
|
||
|
||
// Option 2: Decompose and infer Two.js related properties.
|
||
var transforms = Two.Utils.decomposeMatrix(node.getCTM());
|
||
|
||
elem.translation.set(transforms.translateX, transforms.translateY);
|
||
elem.rotation = transforms.rotation;
|
||
// Warning: Two.js elements only support uniform scalars...
|
||
elem.scale = transforms.scaleX;
|
||
|
||
var x = parseFloat((styles.x + '').replace('px'));
|
||
var y = parseFloat((styles.y + '').replace('px'));
|
||
|
||
// Override based on attributes.
|
||
if (x) {
|
||
elem.translation.x = x;
|
||
}
|
||
|
||
if (y) {
|
||
elem.translation.y = y;
|
||
}
|
||
|
||
break;
|
||
case 'visible':
|
||
elem.visible = value;
|
||
break;
|
||
case 'stroke-linecap':
|
||
elem.cap = value;
|
||
break;
|
||
case 'stroke-linejoin':
|
||
elem.join = value;
|
||
break;
|
||
case 'stroke-miterlimit':
|
||
elem.miter = value;
|
||
break;
|
||
case 'stroke-width':
|
||
elem.linewidth = parseFloat(value);
|
||
break;
|
||
case 'stroke-opacity':
|
||
case 'fill-opacity':
|
||
case 'opacity':
|
||
elem.opacity = parseFloat(value);
|
||
break;
|
||
case 'fill':
|
||
case 'stroke':
|
||
if (/url\(\#.*\)/i.test(value)) {
|
||
elem[key] = this.getById(
|
||
value.replace(/url\(\#(.*)\)/i, '$1'));
|
||
} else {
|
||
elem[key] = (value === 'none') ? 'transparent' : value;
|
||
}
|
||
break;
|
||
case 'id':
|
||
elem.id = value;
|
||
break;
|
||
case 'class':
|
||
elem.classList = value.split(' ');
|
||
break;
|
||
}
|
||
}
|
||
|
||
return elem;
|
||
|
||
},
|
||
|
||
/**
|
||
* Read any number of SVG node types and create Two equivalents of them.
|
||
*/
|
||
read: {
|
||
|
||
svg: function() {
|
||
return Two.Utils.read.g.apply(this, arguments);
|
||
},
|
||
|
||
g: function(node) {
|
||
|
||
var group = new Two.Group();
|
||
|
||
// Switched up order to inherit more specific styles
|
||
Two.Utils.applySvgAttributes.call(this, node, group);
|
||
|
||
for (var i = 0, l = node.childNodes.length; i < l; i++) {
|
||
var n = node.childNodes[i];
|
||
var tag = n.nodeName;
|
||
if (!tag) return;
|
||
|
||
var tagName = tag.replace(/svg\:/ig, '').toLowerCase();
|
||
|
||
if (tagName in Two.Utils.read) {
|
||
var o = Two.Utils.read[tagName].call(group, n);
|
||
group.add(o);
|
||
}
|
||
}
|
||
|
||
return group;
|
||
|
||
},
|
||
|
||
polygon: function(node, open) {
|
||
|
||
var points = node.getAttribute('points');
|
||
|
||
var verts = [];
|
||
points.replace(/(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g, function(match, p1, p2) {
|
||
verts.push(new Two.Anchor(parseFloat(p1), parseFloat(p2)));
|
||
});
|
||
|
||
var poly = new Two.Path(verts, !open).noStroke();
|
||
poly.fill = 'black';
|
||
|
||
return Two.Utils.applySvgAttributes.call(this, node, poly);
|
||
|
||
},
|
||
|
||
polyline: function(node) {
|
||
return Two.Utils.read.polygon.call(this, node, true);
|
||
},
|
||
|
||
path: function(node) {
|
||
|
||
var path = node.getAttribute('d');
|
||
|
||
// Create a Two.Path from the paths.
|
||
|
||
var coord = new Two.Anchor();
|
||
var control, coords;
|
||
var closed = false, relative = false;
|
||
var commands = path.match(/[a-df-z][^a-df-z]*/ig);
|
||
var last = commands.length - 1;
|
||
|
||
// Split up polybeziers
|
||
|
||
_.each(commands.slice(0), function(command, i) {
|
||
|
||
var type = command[0];
|
||
var lower = type.toLowerCase();
|
||
var items = command.slice(1).trim().split(/[\s,]+|(?=\s?[+\-])/);
|
||
var pre, post, result = [], bin;
|
||
|
||
if (i <= 0) {
|
||
commands = [];
|
||
}
|
||
|
||
switch (lower) {
|
||
case 'h':
|
||
case 'v':
|
||
if (items.length > 1) {
|
||
bin = 1;
|
||
}
|
||
break;
|
||
case 'm':
|
||
case 'l':
|
||
case 't':
|
||
if (items.length > 2) {
|
||
bin = 2;
|
||
}
|
||
break;
|
||
case 's':
|
||
case 'q':
|
||
if (items.length > 4) {
|
||
bin = 4;
|
||
}
|
||
break;
|
||
case 'c':
|
||
if (items.length > 6) {
|
||
bin = 6;
|
||
}
|
||
break;
|
||
case 'a':
|
||
// TODO: Handle Ellipses
|
||
break;
|
||
}
|
||
|
||
if (bin) {
|
||
|
||
for (var j = 0, l = items.length, times = 0; j < l; j+=bin) {
|
||
|
||
var ct = type;
|
||
if (times > 0) {
|
||
|
||
switch (type) {
|
||
case 'm':
|
||
ct = 'l';
|
||
break;
|
||
case 'M':
|
||
ct = 'L';
|
||
break;
|
||
}
|
||
|
||
}
|
||
|
||
result.push([ct].concat(items.slice(j, j + bin)).join(' '));
|
||
times++;
|
||
|
||
}
|
||
|
||
commands = Array.prototype.concat.apply(commands, result);
|
||
|
||
} else {
|
||
|
||
commands.push(command);
|
||
|
||
}
|
||
|
||
});
|
||
|
||
// Create the vertices for our Two.Path
|
||
|
||
var points = [];
|
||
_.each(commands, function(command, i) {
|
||
|
||
var result, x, y;
|
||
var type = command[0];
|
||
var lower = type.toLowerCase();
|
||
|
||
coords = command.slice(1).trim();
|
||
coords = coords.replace(/(-?\d+(?:\.\d*)?)[eE]([+\-]?\d+)/g, function(match, n1, n2) {
|
||
return parseFloat(n1) * pow(10, n2);
|
||
});
|
||
coords = coords.split(/[\s,]+|(?=\s?[+\-])/);
|
||
relative = type === lower;
|
||
|
||
var x1, y1, x2, y2, x3, y3, x4, y4, reflection;
|
||
|
||
switch (lower) {
|
||
|
||
case 'z':
|
||
if (i >= last) {
|
||
closed = true;
|
||
} else {
|
||
x = coord.x;
|
||
y = coord.y;
|
||
result = new Two.Anchor(
|
||
x, y,
|
||
undefined, undefined,
|
||
undefined, undefined,
|
||
Two.Commands.close
|
||
);
|
||
}
|
||
break;
|
||
|
||
case 'm':
|
||
case 'l':
|
||
|
||
x = parseFloat(coords[0]);
|
||
y = parseFloat(coords[1]);
|
||
|
||
result = new Two.Anchor(
|
||
x, y,
|
||
undefined, undefined,
|
||
undefined, undefined,
|
||
lower === 'm' ? Two.Commands.move : Two.Commands.line
|
||
);
|
||
|
||
if (relative) {
|
||
result.addSelf(coord);
|
||
}
|
||
|
||
// result.controls.left.copy(result);
|
||
// result.controls.right.copy(result);
|
||
|
||
coord = result;
|
||
break;
|
||
|
||
case 'h':
|
||
case 'v':
|
||
|
||
var a = lower === 'h' ? 'x' : 'y';
|
||
var b = a === 'x' ? 'y' : 'x';
|
||
|
||
result = new Two.Anchor(
|
||
undefined, undefined,
|
||
undefined, undefined,
|
||
undefined, undefined,
|
||
Two.Commands.line
|
||
);
|
||
result[a] = parseFloat(coords[0]);
|
||
result[b] = coord[b];
|
||
|
||
if (relative) {
|
||
result[a] += coord[a];
|
||
}
|
||
|
||
// result.controls.left.copy(result);
|
||
// result.controls.right.copy(result);
|
||
|
||
coord = result;
|
||
break;
|
||
|
||
case 'c':
|
||
case 's':
|
||
|
||
x1 = coord.x;
|
||
y1 = coord.y;
|
||
|
||
if (!control) {
|
||
control = new Two.Vector();//.copy(coord);
|
||
}
|
||
|
||
if (lower === 'c') {
|
||
|
||
x2 = parseFloat(coords[0]);
|
||
y2 = parseFloat(coords[1]);
|
||
x3 = parseFloat(coords[2]);
|
||
y3 = parseFloat(coords[3]);
|
||
x4 = parseFloat(coords[4]);
|
||
y4 = parseFloat(coords[5]);
|
||
|
||
} else {
|
||
|
||
// Calculate reflection control point for proper x2, y2
|
||
// inclusion.
|
||
|
||
reflection = getReflection(coord, control, relative);
|
||
|
||
x2 = reflection.x;
|
||
y2 = reflection.y;
|
||
x3 = parseFloat(coords[0]);
|
||
y3 = parseFloat(coords[1]);
|
||
x4 = parseFloat(coords[2]);
|
||
y4 = parseFloat(coords[3]);
|
||
|
||
}
|
||
|
||
if (relative) {
|
||
x2 += x1;
|
||
y2 += y1;
|
||
x3 += x1;
|
||
y3 += y1;
|
||
x4 += x1;
|
||
y4 += y1;
|
||
}
|
||
|
||
if (!_.isObject(coord.controls)) {
|
||
Two.Anchor.AppendCurveProperties(coord);
|
||
}
|
||
|
||
coord.controls.right.set(x2 - coord.x, y2 - coord.y);
|
||
result = new Two.Anchor(
|
||
x4, y4,
|
||
x3 - x4, y3 - y4,
|
||
undefined, undefined,
|
||
Two.Commands.curve
|
||
);
|
||
|
||
coord = result;
|
||
control = result.controls.left;
|
||
|
||
break;
|
||
|
||
case 't':
|
||
case 'q':
|
||
|
||
x1 = coord.x;
|
||
y1 = coord.y;
|
||
|
||
if (!control) {
|
||
control = new Two.Vector();//.copy(coord);
|
||
}
|
||
|
||
if (control.isZero()) {
|
||
x2 = x1;
|
||
y2 = y1;
|
||
} else {
|
||
x2 = control.x;
|
||
y1 = control.y;
|
||
}
|
||
|
||
if (lower === 'q') {
|
||
|
||
x3 = parseFloat(coords[0]);
|
||
y3 = parseFloat(coords[1]);
|
||
x4 = parseFloat(coords[1]);
|
||
y4 = parseFloat(coords[2]);
|
||
|
||
} else {
|
||
|
||
reflection = getReflection(coord, control, relative);
|
||
|
||
x3 = reflection.x;
|
||
y3 = reflection.y;
|
||
x4 = parseFloat(coords[0]);
|
||
y4 = parseFloat(coords[1]);
|
||
|
||
}
|
||
|
||
if (relative) {
|
||
x2 += x1;
|
||
y2 += y1;
|
||
x3 += x1;
|
||
y3 += y1;
|
||
x4 += x1;
|
||
y4 += y1;
|
||
}
|
||
|
||
if (!_.isObject(coord.controls)) {
|
||
Two.Anchor.AppendCurveProperties(coord);
|
||
}
|
||
|
||
coord.controls.right.set(x2 - coord.x, y2 - coord.y);
|
||
result = new Two.Anchor(
|
||
x4, y4,
|
||
x3 - x4, y3 - y4,
|
||
undefined, undefined,
|
||
Two.Commands.curve
|
||
);
|
||
|
||
coord = result;
|
||
control = result.controls.left;
|
||
|
||
break;
|
||
|
||
case 'a':
|
||
|
||
// throw new Two.Utils.Error('not yet able to interpret Elliptical Arcs.');
|
||
x1 = coord.x;
|
||
y1 = coord.y;
|
||
|
||
var rx = parseFloat(coords[0]);
|
||
var ry = parseFloat(coords[1]);
|
||
var xAxisRotation = parseFloat(coords[2]) * Math.PI / 180;
|
||
var largeArcFlag = parseFloat(coords[3]);
|
||
var sweepFlag = parseFloat(coords[4]);
|
||
|
||
x4 = parseFloat(coords[5]);
|
||
y4 = parseFloat(coords[6]);
|
||
|
||
if (relative) {
|
||
x4 += x1;
|
||
y4 += y1;
|
||
}
|
||
|
||
// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
|
||
|
||
// Calculate midpoint mx my
|
||
var mx = (x4 - x1) / 2;
|
||
var my = (y4 - y1) / 2;
|
||
|
||
// Calculate x1' y1' F.6.5.1
|
||
var _x = mx * Math.cos(xAxisRotation) + my * Math.sin(xAxisRotation);
|
||
var _y = - mx * Math.sin(xAxisRotation) + my * Math.cos(xAxisRotation);
|
||
|
||
var rx2 = rx * rx;
|
||
var ry2 = ry * ry;
|
||
var _x2 = _x * _x;
|
||
var _y2 = _y * _y;
|
||
|
||
// adjust radii
|
||
var l = _x2 / rx2 + _y2 / ry2;
|
||
if (l > 1) {
|
||
rx *= Math.sqrt(l);
|
||
ry *= Math.sqrt(l);
|
||
}
|
||
|
||
var amp = Math.sqrt((rx2 * ry2 - rx2 * _y2 - ry2 * _x2) / (rx2 * _y2 + ry2 * _x2));
|
||
|
||
if (_.isNaN(amp)) {
|
||
amp = 0;
|
||
} else if (largeArcFlag != sweepFlag && amp > 0) {
|
||
amp *= -1;
|
||
}
|
||
|
||
// Calculate cx' cy' F.6.5.2
|
||
var _cx = amp * rx * _y / ry;
|
||
var _cy = - amp * ry * _x / rx;
|
||
|
||
// Calculate cx cy F.6.5.3
|
||
var cx = _cx * Math.cos(xAxisRotation) - _cy * Math.sin(xAxisRotation) + (x1 + x4) / 2;
|
||
var cy = _cx * Math.sin(xAxisRotation) + _cy * Math.cos(xAxisRotation) + (y1 + y4) / 2;
|
||
|
||
// vector magnitude
|
||
var m = function(v) { return Math.sqrt(Math.pow(v[0], 2) + Math.pow(v[1], 2)); }
|
||
// ratio between two vectors
|
||
var r = function(u, v) { return (u[0] * v[0] + u[1] * v[1]) / (m(u) * m(v)) }
|
||
// angle between two vectors
|
||
var a = function(u, v) { return (u[0] * v[1] < u[1] * v[0] ? - 1 : 1) * Math.acos(r(u,v)); }
|
||
|
||
// Calculate theta1 and delta theta F.6.5.4 + F.6.5.5
|
||
var t1 = a([1, 0], [(_x - _cx) / rx, (_y - _cy) / ry]);
|
||
var u = [(_x - _cx) / rx, (_y - _cy) / ry];
|
||
var v = [( - _x - _cx) / rx, ( - _y - _cy) / ry];
|
||
var dt = a(u, v);
|
||
|
||
if (r(u, v) <= -1) dt = Math.PI;
|
||
if (r(u, v) >= 1) dt = 0;
|
||
|
||
// F.6.5.6
|
||
if (largeArcFlag) {
|
||
dt = mod(dt, Math.PI * 2);
|
||
}
|
||
|
||
if (sweepFlag && dt > 0) {
|
||
dt -= Math.PI * 2;
|
||
}
|
||
|
||
var length = Two.Resolution;
|
||
|
||
// Save a projection of our rotation and translation to apply
|
||
// to the set of points.
|
||
var projection = new Two.Matrix()
|
||
.translate(cx, cy)
|
||
.rotate(xAxisRotation);
|
||
|
||
// Create a resulting array of Two.Anchor's to export to the
|
||
// the path.
|
||
result = _.map(_.range(length), function(i) {
|
||
var pct = 1 - (i / (length - 1));
|
||
var theta = pct * dt + t1;
|
||
var x = rx * Math.cos(theta);
|
||
var y = ry * Math.sin(theta);
|
||
var projected = projection.multiply(x, y, 1);
|
||
return new Two.Anchor(projected.x, projected.y, false, false, false, false, Two.Commands.line);;
|
||
});
|
||
|
||
result.push(new Two.Anchor(x4, y4, false, false, false, false, Two.Commands.line));
|
||
|
||
coord = result[result.length - 1];
|
||
control = coord.controls.left;
|
||
|
||
break;
|
||
|
||
}
|
||
|
||
if (result) {
|
||
if (_.isArray(result)) {
|
||
points = points.concat(result);
|
||
} else {
|
||
points.push(result);
|
||
}
|
||
}
|
||
|
||
});
|
||
|
||
if (points.length <= 1) {
|
||
return;
|
||
}
|
||
|
||
var path = new Two.Path(points, closed, undefined, true).noStroke();
|
||
path.fill = 'black';
|
||
|
||
var rect = path.getBoundingClientRect(true);
|
||
|
||
// Center objects to stay consistent
|
||
// with the rest of the Two.js API.
|
||
rect.centroid = {
|
||
x: rect.left + rect.width / 2,
|
||
y: rect.top + rect.height / 2
|
||
};
|
||
|
||
_.each(path.vertices, function(v) {
|
||
v.subSelf(rect.centroid);
|
||
});
|
||
|
||
path.translation.addSelf(rect.centroid);
|
||
|
||
return Two.Utils.applySvgAttributes.call(this, node, path);
|
||
|
||
},
|
||
|
||
circle: function(node) {
|
||
|
||
var x = parseFloat(node.getAttribute('cx'));
|
||
var y = parseFloat(node.getAttribute('cy'));
|
||
var r = parseFloat(node.getAttribute('r'));
|
||
|
||
var circle = new Two.Circle(x, y, r).noStroke();
|
||
circle.fill = 'black';
|
||
|
||
return Two.Utils.applySvgAttributes.call(this, node, circle);
|
||
|
||
},
|
||
|
||
ellipse: function(node) {
|
||
|
||
var x = parseFloat(node.getAttribute('cx'));
|
||
var y = parseFloat(node.getAttribute('cy'));
|
||
var width = parseFloat(node.getAttribute('rx'));
|
||
var height = parseFloat(node.getAttribute('ry'));
|
||
|
||
var ellipse = new Two.Ellipse(x, y, width, height).noStroke();
|
||
ellipse.fill = 'black';
|
||
|
||
return Two.Utils.applySvgAttributes.call(this, node, ellipse);
|
||
|
||
},
|
||
|
||
rect: function(node) {
|
||
|
||
var x = parseFloat(node.getAttribute('x')) || 0;
|
||
var y = parseFloat(node.getAttribute('y')) || 0;
|
||
var width = parseFloat(node.getAttribute('width'));
|
||
var height = parseFloat(node.getAttribute('height'));
|
||
|
||
var w2 = width / 2;
|
||
var h2 = height / 2;
|
||
|
||
var rect = new Two.Rectangle(x + w2, y + h2, width, height)
|
||
.noStroke();
|
||
rect.fill = 'black';
|
||
|
||
return Two.Utils.applySvgAttributes.call(this, node, rect);
|
||
|
||
},
|
||
|
||
line: function(node) {
|
||
|
||
var x1 = parseFloat(node.getAttribute('x1'));
|
||
var y1 = parseFloat(node.getAttribute('y1'));
|
||
var x2 = parseFloat(node.getAttribute('x2'));
|
||
var y2 = parseFloat(node.getAttribute('y2'));
|
||
|
||
var line = new Two.Line(x1, y1, x2, y2).noFill();
|
||
|
||
return Two.Utils.applySvgAttributes.call(this, node, line);
|
||
|
||
},
|
||
|
||
lineargradient: function(node) {
|
||
|
||
var x1 = parseFloat(node.getAttribute('x1'));
|
||
var y1 = parseFloat(node.getAttribute('y1'));
|
||
var x2 = parseFloat(node.getAttribute('x2'));
|
||
var y2 = parseFloat(node.getAttribute('y2'));
|
||
|
||
var ox = (x2 + x1) / 2;
|
||
var oy = (y2 + y1) / 2;
|
||
|
||
var stops = [];
|
||
for (var i = 0; i < node.children.length; i++) {
|
||
|
||
var child = node.children[i];
|
||
|
||
var offset = parseFloat(child.getAttribute('offset'));
|
||
var color = child.getAttribute('stop-color');
|
||
var opacity = child.getAttribute('stop-opacity');
|
||
var style = child.getAttribute('style');
|
||
|
||
if (_.isNull(color)) {
|
||
var matches = style ? style.match(/stop\-color\:\s?([\#a-fA-F0-9]*)/) : false;
|
||
color = matches && matches.length > 1 ? matches[1] : undefined;
|
||
}
|
||
|
||
if (_.isNull(opacity)) {
|
||
var matches = style ? style.match(/stop\-opacity\:\s?([0-9\.\-]*)/) : false;
|
||
opacity = matches && matches.length > 1 ? parseFloat(matches[1]) : 1;
|
||
}
|
||
|
||
stops.push(new Two.Gradient.Stop(offset, color, opacity));
|
||
|
||
}
|
||
|
||
var gradient = new Two.LinearGradient(x1 - ox, y1 - oy, x2 - ox,
|
||
y2 - oy, stops);
|
||
|
||
return Two.Utils.applySvgAttributes.call(this, node, gradient);
|
||
|
||
},
|
||
|
||
radialgradient: function(node) {
|
||
|
||
var cx = parseFloat(node.getAttribute('cx')) || 0;
|
||
var cy = parseFloat(node.getAttribute('cy')) || 0;
|
||
var r = parseFloat(node.getAttribute('r'));
|
||
|
||
var fx = parseFloat(node.getAttribute('fx'));
|
||
var fy = parseFloat(node.getAttribute('fy'));
|
||
|
||
if (_.isNaN(fx)) {
|
||
fx = cx;
|
||
}
|
||
|
||
if (_.isNaN(fy)) {
|
||
fy = cy;
|
||
}
|
||
|
||
var ox = Math.abs(cx + fx) / 2;
|
||
var oy = Math.abs(cy + fy) / 2;
|
||
|
||
var stops = [];
|
||
for (var i = 0; i < node.children.length; i++) {
|
||
|
||
var child = node.children[i];
|
||
|
||
var offset = parseFloat(child.getAttribute('offset'));
|
||
var color = child.getAttribute('stop-color');
|
||
var opacity = child.getAttribute('stop-opacity');
|
||
var style = child.getAttribute('style');
|
||
|
||
if (_.isNull(color)) {
|
||
var matches = style ? style.match(/stop\-color\:\s?([\#a-fA-F0-9]*)/) : false;
|
||
color = matches && matches.length > 1 ? matches[1] : undefined;
|
||
}
|
||
|
||
if (_.isNull(opacity)) {
|
||
var matches = style ? style.match(/stop\-opacity\:\s?([0-9\.\-]*)/) : false;
|
||
opacity = matches && matches.length > 1 ? parseFloat(matches[1]) : 1;
|
||
}
|
||
|
||
stops.push(new Two.Gradient.Stop(offset, color, opacity));
|
||
|
||
}
|
||
|
||
var gradient = new Two.RadialGradient(cx - ox, cy - oy, r,
|
||
stops, fx - ox, fy - oy);
|
||
|
||
return Two.Utils.applySvgAttributes.call(this, node, gradient);
|
||
|
||
}
|
||
|
||
},
|
||
|
||
/**
|
||
* Given 2 points (a, b) and corresponding control point for each
|
||
* return an array of points that represent points plotted along
|
||
* the curve. Number points determined by limit.
|
||
*/
|
||
subdivide: function(x1, y1, x2, y2, x3, y3, x4, y4, limit) {
|
||
|
||
limit = limit || Two.Utils.Curve.RecursionLimit;
|
||
var amount = limit + 1;
|
||
|
||
// TODO: Issue 73
|
||
// Don't recurse if the end points are identical
|
||
if (x1 === x4 && y1 === y4) {
|
||
return [new Two.Anchor(x4, y4)];
|
||
}
|
||
|
||
return _.map(_.range(0, amount), function(i) {
|
||
|
||
var t = i / amount;
|
||
var x = getPointOnCubicBezier(t, x1, x2, x3, x4);
|
||
var y = getPointOnCubicBezier(t, y1, y2, y3, y4);
|
||
|
||
return new Two.Anchor(x, y);
|
||
|
||
});
|
||
|
||
},
|
||
|
||
getPointOnCubicBezier: 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);
|
||
},
|
||
|
||
/**
|
||
* 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`.
|
||
*/
|
||
getCurveLength: 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 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 sqrt(dx * dx + dy * dy);
|
||
};
|
||
|
||
return integrate(
|
||
integrand, 0, 1, limit || Two.Utils.Curve.RecursionLimit
|
||
);
|
||
|
||
},
|
||
|
||
/**
|
||
* Integration for `getCurveLength` calculations. Referenced from
|
||
* Paper.js: https://github.com/paperjs/paper.js/blob/master/src/util/Numerical.js#L101
|
||
*/
|
||
integrate: function(f, a, b, n) {
|
||
var x = Two.Utils.Curve.abscissas[n - 2],
|
||
w = Two.Utils.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;
|
||
},
|
||
|
||
/**
|
||
* Creates a set of points that have u, v values for anchor positions
|
||
*/
|
||
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)) {
|
||
Two.Anchor.AppendCurveProperties(point);
|
||
}
|
||
|
||
var prev = closed ? mod(i - 1, l) : max(i - 1, 0);
|
||
var next = closed ? mod(i + 1, l) : min(i + 1, last);
|
||
|
||
var a = points[prev];
|
||
var b = point;
|
||
var c = points[next];
|
||
getControlPoints(a, b, c);
|
||
|
||
b._command = i === 0 ? Two.Commands.move : Two.Commands.curve;
|
||
|
||
b.controls.left.x = _.isNumber(b.controls.left.x) ? b.controls.left.x : b.x;
|
||
b.controls.left.y = _.isNumber(b.controls.left.y) ? b.controls.left.y : b.y;
|
||
|
||
b.controls.right.x = _.isNumber(b.controls.right.x) ? b.controls.right.x : b.x;
|
||
b.controls.right.y = _.isNumber(b.controls.right.y) ? b.controls.right.y : b.y;
|
||
|
||
}
|
||
|
||
},
|
||
|
||
/**
|
||
* Given three coordinates return the control points for the middle, b,
|
||
* vertex.
|
||
*/
|
||
getControlPoints: function(a, b, c) {
|
||
|
||
var a1 = angleBetween(a, b);
|
||
var a2 = angleBetween(c, b);
|
||
|
||
var d1 = distanceBetween(a, b);
|
||
var d2 = distanceBetween(c, b);
|
||
|
||
var mid = (a1 + a2) / 2;
|
||
|
||
// So we know which angle corresponds to which side.
|
||
|
||
b.u = _.isObject(b.controls.left) ? b.controls.left : new Two.Vector(0, 0);
|
||
b.v = _.isObject(b.controls.right) ? b.controls.right : new Two.Vector(0, 0);
|
||
|
||
// TODO: Issue 73
|
||
if (d1 < 0.0001 || d2 < 0.0001) {
|
||
if (!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;
|
||
} else {
|
||
mid -= HALF_PI;
|
||
}
|
||
|
||
b.controls.left.x = cos(mid) * d1;
|
||
b.controls.left.y = sin(mid) * d1;
|
||
|
||
mid -= PI;
|
||
|
||
b.controls.right.x = cos(mid) * d2;
|
||
b.controls.right.y = sin(mid) * d2;
|
||
|
||
if (!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;
|
||
|
||
},
|
||
|
||
/**
|
||
* Get the reflection of a point "b" about point "a". Where "a" is in
|
||
* absolute space and "b" is relative to "a".
|
||
*
|
||
* http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes
|
||
*/
|
||
getReflection: function(a, b, relative) {
|
||
|
||
return new Two.Vector(
|
||
2 * a.x - (b.x + a.x) - (relative ? a.x : 0),
|
||
2 * a.y - (b.y + a.y) - (relative ? a.y : 0)
|
||
);
|
||
|
||
},
|
||
|
||
getAnchorsFromArcData: function(center, xAxisRotation, rx, ry, ts, td, ccw) {
|
||
|
||
var matrix = new Two.Matrix()
|
||
.translate(center.x, center.y)
|
||
.rotate(xAxisRotation);
|
||
|
||
var l = Two.Resolution;
|
||
|
||
return _.map(_.range(l), function(i) {
|
||
|
||
var pct = (i + 1) / l;
|
||
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 Two.Anchor(x, y);
|
||
Two.Anchor.AppendCurveProperties(anchor);
|
||
anchor.command = Two.Commands.line;
|
||
|
||
// TODO: Calculate control points here...
|
||
|
||
return anchor;
|
||
|
||
});
|
||
|
||
},
|
||
|
||
ratioBetween: function(A, B) {
|
||
|
||
return (A.x * B.x + A.y * B.y) / (A.length() * B.length());
|
||
|
||
},
|
||
|
||
angleBetween: function(A, B) {
|
||
|
||
var dx, dy;
|
||
|
||
if (arguments.length >= 4) {
|
||
|
||
dx = arguments[0] - arguments[2];
|
||
dy = arguments[1] - arguments[3];
|
||
|
||
return atan2(dy, dx);
|
||
|
||
}
|
||
|
||
dx = A.x - B.x;
|
||
dy = A.y - B.y;
|
||
|
||
return atan2(dy, dx);
|
||
|
||
},
|
||
|
||
distanceBetweenSquared: function(p1, p2) {
|
||
|
||
var dx = p1.x - p2.x;
|
||
var dy = p1.y - p2.y;
|
||
|
||
return dx * dx + dy * dy;
|
||
|
||
},
|
||
|
||
distanceBetween: function(p1, p2) {
|
||
|
||
return sqrt(distanceBetweenSquared(p1, p2));
|
||
|
||
},
|
||
|
||
lerp: function(a, b, t) {
|
||
return t * (b - a) + a;
|
||
},
|
||
|
||
// A pretty fast toFixed(3) alternative
|
||
// See http://jsperf.com/parsefloat-tofixed-vs-math-round/18
|
||
toFixed: function(v) {
|
||
return Math.floor(v * 1000) / 1000;
|
||
},
|
||
|
||
mod: function(v, l) {
|
||
|
||
while (v < 0) {
|
||
v += l;
|
||
}
|
||
|
||
return v % l;
|
||
|
||
},
|
||
|
||
/**
|
||
* Array like collection that triggers inserted and removed events
|
||
* removed : pop / shift / splice
|
||
* inserted : push / unshift / splice (with > 2 arguments)
|
||
*/
|
||
Collection: function() {
|
||
|
||
Array.call(this);
|
||
|
||
if (arguments.length > 1) {
|
||
Array.prototype.push.apply(this, arguments);
|
||
} else if (arguments[0] && Array.isArray(arguments[0])) {
|
||
Array.prototype.push.apply(this, arguments[0]);
|
||
}
|
||
|
||
},
|
||
|
||
// Custom Error Throwing for Two.js
|
||
|
||
Error: function(message) {
|
||
this.name = 'two.js';
|
||
this.message = message;
|
||
},
|
||
|
||
Events: {
|
||
|
||
on: function(name, callback) {
|
||
|
||
this._events || (this._events = {});
|
||
var list = this._events[name] || (this._events[name] = []);
|
||
|
||
list.push(callback);
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
off: function(name, callback) {
|
||
|
||
if (!this._events) {
|
||
return this;
|
||
}
|
||
if (!name && !callback) {
|
||
this._events = {};
|
||
return this;
|
||
}
|
||
|
||
var names = name ? [name] : _.keys(this._events);
|
||
for (var i = 0, l = names.length; i < l; i++) {
|
||
|
||
var name = names[i];
|
||
var list = this._events[name];
|
||
|
||
if (!!list) {
|
||
var events = [];
|
||
if (callback) {
|
||
for (var j = 0, k = list.length; j < k; j++) {
|
||
var ev = list[j];
|
||
ev = ev.callback ? ev.callback : ev;
|
||
if (callback && callback !== ev) {
|
||
events.push(ev);
|
||
}
|
||
}
|
||
}
|
||
this._events[name] = events;
|
||
}
|
||
}
|
||
|
||
return this;
|
||
},
|
||
|
||
trigger: function(name) {
|
||
if (!this._events) return this;
|
||
var args = slice.call(arguments, 1);
|
||
var events = this._events[name];
|
||
if (events) trigger(this, events, args);
|
||
return this;
|
||
},
|
||
|
||
listen: function (obj, name, callback) {
|
||
|
||
var bound = this;
|
||
|
||
if (obj) {
|
||
var ev = function () {
|
||
callback.apply(bound, arguments);
|
||
};
|
||
|
||
// add references about the object that assigned this listener
|
||
ev.obj = obj;
|
||
ev.name = name;
|
||
ev.callback = callback;
|
||
|
||
obj.on(name, ev);
|
||
}
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
ignore: function (obj, name, callback) {
|
||
|
||
obj.off(name, callback);
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
}
|
||
|
||
})
|
||
|
||
});
|
||
|
||
Two.Utils.Events.bind = Two.Utils.Events.on;
|
||
Two.Utils.Events.unbind = Two.Utils.Events.off;
|
||
|
||
var trigger = function(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);
|
||
}
|
||
};
|
||
|
||
Two.Utils.Error.prototype = new Error();
|
||
Two.Utils.Error.prototype.constructor = Two.Utils.Error;
|
||
|
||
Two.Utils.Collection.prototype = new Array();
|
||
Two.Utils.Collection.prototype.constructor = Two.Utils.Collection;
|
||
|
||
_.extend(Two.Utils.Collection.prototype, Two.Utils.Events, {
|
||
|
||
pop: function() {
|
||
var popped = Array.prototype.pop.apply(this, arguments);
|
||
this.trigger(Two.Events.remove, [popped]);
|
||
return popped;
|
||
},
|
||
|
||
shift: function() {
|
||
var shifted = Array.prototype.shift.apply(this, arguments);
|
||
this.trigger(Two.Events.remove, [shifted]);
|
||
return shifted;
|
||
},
|
||
|
||
push: function() {
|
||
var pushed = Array.prototype.push.apply(this, arguments);
|
||
this.trigger(Two.Events.insert, arguments);
|
||
return pushed;
|
||
},
|
||
|
||
unshift: function() {
|
||
var unshifted = Array.prototype.unshift.apply(this, arguments);
|
||
this.trigger(Two.Events.insert, arguments);
|
||
return unshifted;
|
||
},
|
||
|
||
splice: function() {
|
||
var spliced = Array.prototype.splice.apply(this, arguments);
|
||
var inserted;
|
||
|
||
this.trigger(Two.Events.remove, spliced);
|
||
|
||
if (arguments.length > 2) {
|
||
inserted = this.slice(arguments[0], arguments[0] + arguments.length - 2);
|
||
this.trigger(Two.Events.insert, inserted);
|
||
this.trigger(Two.Events.order);
|
||
}
|
||
return spliced;
|
||
},
|
||
|
||
sort: function() {
|
||
Array.prototype.sort.apply(this, arguments);
|
||
this.trigger(Two.Events.order);
|
||
return this;
|
||
},
|
||
|
||
reverse: function() {
|
||
Array.prototype.reverse.apply(this, arguments);
|
||
this.trigger(Two.Events.order);
|
||
return this;
|
||
}
|
||
|
||
});
|
||
|
||
// Localize utils
|
||
|
||
var distanceBetween = Two.Utils.distanceBetween,
|
||
getAnchorsFromArcData = Two.Utils.getAnchorsFromArcData,
|
||
distanceBetweenSquared = Two.Utils.distanceBetweenSquared,
|
||
ratioBetween = Two.Utils.ratioBetween,
|
||
angleBetween = Two.Utils.angleBetween,
|
||
getControlPoints = Two.Utils.getControlPoints,
|
||
getCurveFromPoints = Two.Utils.getCurveFromPoints,
|
||
solveSegmentIntersection = Two.Utils.solveSegmentIntersection,
|
||
decoupleShapes = Two.Utils.decoupleShapes,
|
||
mod = Two.Utils.mod,
|
||
getBackingStoreRatio = Two.Utils.getBackingStoreRatio,
|
||
getPointOnCubicBezier = Two.Utils.getPointOnCubicBezier,
|
||
getCurveLength = Two.Utils.getCurveLength,
|
||
integrate = Two.Utils.integrate,
|
||
getReflection = Two.Utils.getReflection;
|
||
|
||
_.extend(Two.prototype, Two.Utils.Events, {
|
||
|
||
appendTo: function(elem) {
|
||
|
||
elem.appendChild(this.renderer.domElement);
|
||
return this;
|
||
|
||
},
|
||
|
||
play: function() {
|
||
|
||
Two.Utils.setPlaying.call(this, true);
|
||
return this.trigger(Two.Events.play);
|
||
|
||
},
|
||
|
||
pause: function() {
|
||
|
||
this.playing = false;
|
||
return this.trigger(Two.Events.pause);
|
||
|
||
},
|
||
|
||
/**
|
||
* Update positions and calculations in one pass before rendering.
|
||
*/
|
||
update: function() {
|
||
|
||
var animated = !!this._lastFrame;
|
||
var now = perf.now();
|
||
|
||
this.frameCount++;
|
||
|
||
if (animated) {
|
||
this.timeDelta = parseFloat((now - this._lastFrame).toFixed(3));
|
||
}
|
||
this._lastFrame = now;
|
||
|
||
var width = this.width;
|
||
var height = this.height;
|
||
var renderer = this.renderer;
|
||
|
||
// Update width / height for the renderer
|
||
if (width !== renderer.width || height !== renderer.height) {
|
||
renderer.setSize(width, height, this.ratio);
|
||
}
|
||
|
||
this.trigger(Two.Events.update, this.frameCount, this.timeDelta);
|
||
|
||
return this.render();
|
||
|
||
},
|
||
|
||
/**
|
||
* Render all drawable - visible objects of the scene.
|
||
*/
|
||
render: function() {
|
||
|
||
this.renderer.render();
|
||
return this.trigger(Two.Events.render, this.frameCount);
|
||
|
||
},
|
||
|
||
/**
|
||
* Convenience Methods
|
||
*/
|
||
|
||
add: function(o) {
|
||
|
||
var objects = o;
|
||
if (!(objects instanceof Array)) {
|
||
objects = _.toArray(arguments);
|
||
}
|
||
|
||
this.scene.add(objects);
|
||
return this;
|
||
|
||
},
|
||
|
||
remove: function(o) {
|
||
|
||
var objects = o;
|
||
if (!(objects instanceof Array)) {
|
||
objects = _.toArray(arguments);
|
||
}
|
||
|
||
this.scene.remove(objects);
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
clear: function() {
|
||
|
||
this.scene.remove(_.toArray(this.scene.children));
|
||
return this;
|
||
|
||
},
|
||
|
||
makeLine: function(x1, y1, x2, y2) {
|
||
|
||
var line = new Two.Line(x1, y1, x2, y2);
|
||
this.scene.add(line);
|
||
|
||
return line;
|
||
|
||
},
|
||
|
||
makeRectangle: function(x, y, width, height) {
|
||
|
||
var rect = new Two.Rectangle(x, y, width, height);
|
||
this.scene.add(rect);
|
||
|
||
return rect;
|
||
|
||
},
|
||
|
||
makeRoundedRectangle: function(x, y, width, height, sides) {
|
||
|
||
var rect = new Two.RoundedRectangle(x, y, width, height, sides);
|
||
this.scene.add(rect);
|
||
|
||
return rect;
|
||
|
||
},
|
||
|
||
makeCircle: function(ox, oy, r) {
|
||
|
||
var circle = new Two.Circle(ox, oy, r);
|
||
this.scene.add(circle);
|
||
|
||
return circle;
|
||
|
||
},
|
||
|
||
makeEllipse: function(ox, oy, rx, ry) {
|
||
|
||
var ellipse = new Two.Ellipse(ox, oy, rx, ry);
|
||
this.scene.add(ellipse);
|
||
|
||
return ellipse;
|
||
|
||
},
|
||
|
||
makeStar: function(ox, oy, or, ir, sides) {
|
||
|
||
var star = new Two.Star(ox, oy, or, ir, sides);
|
||
this.scene.add(star);
|
||
|
||
return star;
|
||
|
||
},
|
||
|
||
makeCurve: function(p) {
|
||
|
||
var l = arguments.length, points = p;
|
||
if (!_.isArray(p)) {
|
||
points = [];
|
||
for (var i = 0; i < l; i+=2) {
|
||
var x = arguments[i];
|
||
if (!_.isNumber(x)) {
|
||
break;
|
||
}
|
||
var y = arguments[i + 1];
|
||
points.push(new Two.Anchor(x, y));
|
||
}
|
||
}
|
||
|
||
var last = arguments[l - 1];
|
||
var curve = new Two.Path(points, !(_.isBoolean(last) ? last : undefined), true);
|
||
var rect = curve.getBoundingClientRect();
|
||
curve.center().translation
|
||
.set(rect.left + rect.width / 2, rect.top + rect.height / 2);
|
||
|
||
this.scene.add(curve);
|
||
|
||
return curve;
|
||
|
||
},
|
||
|
||
makePolygon: function(ox, oy, r, sides) {
|
||
|
||
var poly = new Two.Polygon(ox, oy, r, sides);
|
||
this.scene.add(poly);
|
||
|
||
return poly;
|
||
|
||
},
|
||
|
||
/*
|
||
* Make an Arc Segment
|
||
*/
|
||
|
||
makeArcSegment: function(ox, oy, ir, or, sa, ea, res) {
|
||
var arcSegment = new Two.ArcSegment(ox, oy, ir, or, sa, ea, res);
|
||
this.scene.add(arcSegment);
|
||
return arcSegment;
|
||
},
|
||
|
||
/**
|
||
* Convenience method to make and draw a Two.Path.
|
||
*/
|
||
makePath: function(p) {
|
||
|
||
var l = arguments.length, points = p;
|
||
if (!_.isArray(p)) {
|
||
points = [];
|
||
for (var i = 0; i < l; i+=2) {
|
||
var x = arguments[i];
|
||
if (!_.isNumber(x)) {
|
||
break;
|
||
}
|
||
var y = arguments[i + 1];
|
||
points.push(new Two.Anchor(x, y));
|
||
}
|
||
}
|
||
|
||
var last = arguments[l - 1];
|
||
var path = new Two.Path(points, !(_.isBoolean(last) ? last : undefined));
|
||
var rect = path.getBoundingClientRect();
|
||
path.center().translation
|
||
.set(rect.left + rect.width / 2, rect.top + rect.height / 2);
|
||
|
||
this.scene.add(path);
|
||
|
||
return path;
|
||
|
||
},
|
||
|
||
/**
|
||
* Convenience method to make and add a Two.Text.
|
||
*/
|
||
makeText: function(message, x, y, styles) {
|
||
var text = new Two.Text(message, x, y, styles);
|
||
this.add(text);
|
||
return text;
|
||
},
|
||
|
||
/**
|
||
* Convenience method to make and add a Two.LinearGradient.
|
||
*/
|
||
makeLinearGradient: function(x1, y1, x2, y2 /* stops */) {
|
||
|
||
var stops = slice.call(arguments, 4);
|
||
var gradient = new Two.LinearGradient(x1, y1, x2, y2, stops);
|
||
|
||
this.add(gradient);
|
||
|
||
return gradient;
|
||
|
||
},
|
||
|
||
/**
|
||
* Convenience method to make and add a Two.RadialGradient.
|
||
*/
|
||
makeRadialGradient: function(x1, y1, r /* stops */) {
|
||
|
||
var stops = slice.call(arguments, 3);
|
||
var gradient = new Two.RadialGradient(x1, y1, r, stops);
|
||
|
||
this.add(gradient);
|
||
|
||
return gradient;
|
||
|
||
},
|
||
|
||
makeSprite: function(path, x, y, cols, rows, frameRate, autostart) {
|
||
|
||
var sprite = new Two.Sprite(path, x, y, cols, rows, frameRate);
|
||
if (!!autostart) {
|
||
sprite.play();
|
||
}
|
||
this.add(sprite);
|
||
|
||
return sprite;
|
||
|
||
},
|
||
|
||
makeImageSequence: function(paths, x, y, frameRate, autostart) {
|
||
|
||
var imageSequence = new Two.ImageSequence(paths, x, y, frameRate);
|
||
if (!!autostart) {
|
||
imageSequence.play();
|
||
}
|
||
this.add(imageSequence);
|
||
|
||
return imageSequence;
|
||
|
||
},
|
||
|
||
makeTexture: function(path, callback) {
|
||
|
||
var texture = new Two.Texture(path, callback);
|
||
return texture;
|
||
|
||
},
|
||
|
||
makeGroup: function(o) {
|
||
|
||
var objects = o;
|
||
if (!(objects instanceof Array)) {
|
||
objects = _.toArray(arguments);
|
||
}
|
||
|
||
var group = new Two.Group();
|
||
this.scene.add(group);
|
||
group.add(objects);
|
||
|
||
return group;
|
||
|
||
},
|
||
|
||
/**
|
||
* Interpret an SVG Node and add it to this instance's scene. The
|
||
* distinction should be made that this doesn't `import` svg's, it solely
|
||
* interprets them into something compatible for Two.js — this is slightly
|
||
* different than a direct transcription.
|
||
*
|
||
* @param {Object} svgNode - The node to be parsed
|
||
* @param {Boolean} shallow - Don't create a top-most group but
|
||
* append all contents directly
|
||
*/
|
||
interpret: function(svgNode, shallow) {
|
||
|
||
var tag = svgNode.tagName.toLowerCase();
|
||
|
||
if (!(tag in Two.Utils.read)) {
|
||
return null;
|
||
}
|
||
|
||
var node = Two.Utils.read[tag].call(this, svgNode);
|
||
|
||
if (shallow && node instanceof Two.Group) {
|
||
this.add(node.children);
|
||
} else {
|
||
this.add(node);
|
||
}
|
||
|
||
return node;
|
||
|
||
},
|
||
|
||
/**
|
||
* Load an SVG file / text and interpret.
|
||
*/
|
||
load: function(text, callback) {
|
||
|
||
var nodes = [], elem, i;
|
||
|
||
if (/.*\.svg/ig.test(text)) {
|
||
|
||
Two.Utils.xhr(text, _.bind(function(data) {
|
||
|
||
dom.temp.innerHTML = data;
|
||
for (i = 0; i < dom.temp.children.length; i++) {
|
||
elem = dom.temp.children[i];
|
||
nodes.push(this.interpret(elem));
|
||
}
|
||
|
||
callback(nodes.length <= 1 ? nodes[0] : nodes,
|
||
dom.temp.children.length <= 1 ? dom.temp.children[0] : dom.temp.children);
|
||
|
||
}, this));
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
dom.temp.innerHTML = text;
|
||
for (i = 0; i < dom.temp.children.length; i++) {
|
||
elem = dom.temp.children[i];
|
||
nodes.push(this.interpret(elem));
|
||
}
|
||
|
||
callback(nodes.length <= 1 ? nodes[0] : nodes,
|
||
dom.temp.children.length <= 1 ? dom.temp.children[0] : dom.temp.children);
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
function fitToWindow() {
|
||
|
||
var wr = document.body.getBoundingClientRect();
|
||
|
||
var width = this.width = wr.width;
|
||
var height = this.height = wr.height;
|
||
|
||
this.renderer.setSize(width, height, this.ratio);
|
||
this.trigger(Two.Events.resize, width, height);
|
||
|
||
}
|
||
|
||
// Request Animation Frame
|
||
|
||
var raf = dom.getRequestAnimationFrame();
|
||
|
||
function loop() {
|
||
|
||
raf(loop);
|
||
|
||
for (var i = 0; i < Two.Instances.length; i++) {
|
||
var t = Two.Instances[i];
|
||
if (t.playing) {
|
||
t.update();
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
if (typeof define === 'function' && define.amd) {
|
||
define('two', [], function() {
|
||
return Two;
|
||
});
|
||
} else if (typeof module != 'undefined' && module.exports) {
|
||
module.exports = Two;
|
||
}
|
||
|
||
return Two;
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
var _ = Two.Utils;
|
||
|
||
var Registry = Two.Registry = function() {
|
||
|
||
this.map = {};
|
||
|
||
};
|
||
|
||
_.extend(Registry, {
|
||
|
||
});
|
||
|
||
_.extend(Registry.prototype, {
|
||
|
||
add: function(id, obj) {
|
||
this.map[id] = obj;
|
||
return this;
|
||
},
|
||
|
||
remove: function(id) {
|
||
delete this.map[id];
|
||
return this;
|
||
},
|
||
|
||
get: function(id) {
|
||
return this.map[id];
|
||
},
|
||
|
||
contains: function(id) {
|
||
return id in this.map;
|
||
}
|
||
|
||
});
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
var _ = Two.Utils;
|
||
|
||
var Vector = Two.Vector = function(x, y) {
|
||
|
||
this.x = x || 0;
|
||
this.y = y || 0;
|
||
|
||
};
|
||
|
||
_.extend(Vector, {
|
||
|
||
zero: new Two.Vector()
|
||
|
||
});
|
||
|
||
_.extend(Vector.prototype, Two.Utils.Events, {
|
||
|
||
set: function(x, y) {
|
||
this.x = x;
|
||
this.y = y;
|
||
return this;
|
||
},
|
||
|
||
copy: function(v) {
|
||
this.x = v.x;
|
||
this.y = v.y;
|
||
return this;
|
||
},
|
||
|
||
clear: function() {
|
||
this.x = 0;
|
||
this.y = 0;
|
||
return this;
|
||
},
|
||
|
||
clone: function() {
|
||
return new Vector(this.x, this.y);
|
||
},
|
||
|
||
add: function(v1, v2) {
|
||
this.x = v1.x + v2.x;
|
||
this.y = v1.y + v2.y;
|
||
return this;
|
||
},
|
||
|
||
addSelf: function(v) {
|
||
this.x += v.x;
|
||
this.y += v.y;
|
||
return this;
|
||
},
|
||
|
||
sub: function(v1, v2) {
|
||
this.x = v1.x - v2.x;
|
||
this.y = v1.y - v2.y;
|
||
return this;
|
||
},
|
||
|
||
subSelf: function(v) {
|
||
this.x -= v.x;
|
||
this.y -= v.y;
|
||
return this;
|
||
},
|
||
|
||
multiplySelf: function(v) {
|
||
this.x *= v.x;
|
||
this.y *= v.y;
|
||
return this;
|
||
},
|
||
|
||
multiplyScalar: function(s) {
|
||
this.x *= s;
|
||
this.y *= s;
|
||
return this;
|
||
},
|
||
|
||
divideScalar: function(s) {
|
||
if (s) {
|
||
this.x /= s;
|
||
this.y /= s;
|
||
} else {
|
||
this.set(0, 0);
|
||
}
|
||
return this;
|
||
},
|
||
|
||
negate: function() {
|
||
return this.multiplyScalar(-1);
|
||
},
|
||
|
||
dot: function(v) {
|
||
return this.x * v.x + this.y * v.y;
|
||
},
|
||
|
||
lengthSquared: function() {
|
||
return this.x * this.x + this.y * this.y;
|
||
},
|
||
|
||
length: function() {
|
||
return Math.sqrt(this.lengthSquared());
|
||
},
|
||
|
||
normalize: function() {
|
||
return this.divideScalar(this.length());
|
||
},
|
||
|
||
distanceTo: function(v) {
|
||
return Math.sqrt(this.distanceToSquared(v));
|
||
},
|
||
|
||
distanceToSquared: function(v) {
|
||
var dx = this.x - v.x,
|
||
dy = this.y - v.y;
|
||
return dx * dx + dy * dy;
|
||
},
|
||
|
||
setLength: function(l) {
|
||
return this.normalize().multiplyScalar(l);
|
||
},
|
||
|
||
equals: function(v, eps) {
|
||
eps = (typeof eps === 'undefined') ? 0.0001 : eps;
|
||
return (this.distanceTo(v) < eps);
|
||
},
|
||
|
||
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);
|
||
},
|
||
|
||
isZero: function(eps) {
|
||
eps = (typeof eps === 'undefined') ? 0.0001 : eps;
|
||
return (this.length() < eps);
|
||
},
|
||
|
||
toString: function() {
|
||
return this.x + ', ' + this.y;
|
||
},
|
||
|
||
toObject: function() {
|
||
return { x: this.x, y: this.y };
|
||
},
|
||
|
||
rotate: function (radians) {
|
||
var cos = Math.cos(radians);
|
||
var sin = Math.sin(radians);
|
||
this.x = this.x * cos - this.y * sin;
|
||
this.y = this.x * sin + this.y * cos;
|
||
return this;
|
||
}
|
||
|
||
});
|
||
|
||
var BoundProto = {
|
||
|
||
set: function(x, y) {
|
||
this._x = x;
|
||
this._y = y;
|
||
return this.trigger(Two.Events.change);
|
||
},
|
||
|
||
copy: function(v) {
|
||
this._x = v.x;
|
||
this._y = v.y;
|
||
return this.trigger(Two.Events.change);
|
||
},
|
||
|
||
clear: function() {
|
||
this._x = 0;
|
||
this._y = 0;
|
||
return this.trigger(Two.Events.change);
|
||
},
|
||
|
||
clone: function() {
|
||
return new Vector(this._x, this._y);
|
||
},
|
||
|
||
add: function(v1, v2) {
|
||
this._x = v1.x + v2.x;
|
||
this._y = v1.y + v2.y;
|
||
return this.trigger(Two.Events.change);
|
||
},
|
||
|
||
addSelf: function(v) {
|
||
this._x += v.x;
|
||
this._y += v.y;
|
||
return this.trigger(Two.Events.change);
|
||
},
|
||
|
||
sub: function(v1, v2) {
|
||
this._x = v1.x - v2.x;
|
||
this._y = v1.y - v2.y;
|
||
return this.trigger(Two.Events.change);
|
||
},
|
||
|
||
subSelf: function(v) {
|
||
this._x -= v.x;
|
||
this._y -= v.y;
|
||
return this.trigger(Two.Events.change);
|
||
},
|
||
|
||
multiplySelf: function(v) {
|
||
this._x *= v.x;
|
||
this._y *= v.y;
|
||
return this.trigger(Two.Events.change);
|
||
},
|
||
|
||
multiplyScalar: function(s) {
|
||
this._x *= s;
|
||
this._y *= s;
|
||
return this.trigger(Two.Events.change);
|
||
},
|
||
|
||
divideScalar: function(s) {
|
||
if (s) {
|
||
this._x /= s;
|
||
this._y /= s;
|
||
return this.trigger(Two.Events.change);
|
||
}
|
||
return this.clear();
|
||
},
|
||
|
||
negate: function() {
|
||
return this.multiplyScalar(-1);
|
||
},
|
||
|
||
dot: function(v) {
|
||
return this._x * v.x + this._y * v.y;
|
||
},
|
||
|
||
lengthSquared: function() {
|
||
return this._x * this._x + this._y * this._y;
|
||
},
|
||
|
||
length: function() {
|
||
return Math.sqrt(this.lengthSquared());
|
||
},
|
||
|
||
normalize: function() {
|
||
return this.divideScalar(this.length());
|
||
},
|
||
|
||
distanceTo: function(v) {
|
||
return Math.sqrt(this.distanceToSquared(v));
|
||
},
|
||
|
||
distanceToSquared: function(v) {
|
||
var dx = this._x - v.x,
|
||
dy = this._y - v.y;
|
||
return dx * dx + dy * dy;
|
||
},
|
||
|
||
setLength: function(l) {
|
||
return this.normalize().multiplyScalar(l);
|
||
},
|
||
|
||
equals: function(v, eps) {
|
||
eps = (typeof eps === 'undefined') ? 0.0001 : eps;
|
||
return (this.distanceTo(v) < eps);
|
||
},
|
||
|
||
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);
|
||
},
|
||
|
||
isZero: function(eps) {
|
||
eps = (typeof eps === 'undefined') ? 0.0001 : eps;
|
||
return (this.length() < eps);
|
||
},
|
||
|
||
toString: function() {
|
||
return this._x + ', ' + this._y;
|
||
},
|
||
|
||
toObject: function() {
|
||
return { x: this._x, y: this._y };
|
||
},
|
||
|
||
rotate: function (radians) {
|
||
var cos = Math.cos(radians);
|
||
var sin = Math.sin(radians);
|
||
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(Two.Events.change, 'x');
|
||
}
|
||
};
|
||
|
||
var ygs = {
|
||
enumerable: true,
|
||
get: function() {
|
||
return this._y;
|
||
},
|
||
set: function(v) {
|
||
this._y = v;
|
||
this.trigger(Two.Events.change, 'y');
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 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.
|
||
*/
|
||
|
||
Two.Vector.prototype.bind = Two.Vector.prototype.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
|
||
}
|
||
|
||
Two.Utils.Events.bind.apply(this, arguments);
|
||
|
||
return this;
|
||
|
||
};
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
// Localized variables
|
||
var commands = Two.Commands;
|
||
var _ = Two.Utils;
|
||
|
||
/**
|
||
* An object that holds 3 `Two.Vector`s, the anchor point and its
|
||
* corresponding handles: `left` and `right`.
|
||
*/
|
||
var Anchor = Two.Anchor = function(x, y, ux, uy, vx, vy, command) {
|
||
|
||
Two.Vector.call(this, x, y);
|
||
|
||
this._broadcast = _.bind(function() {
|
||
this.trigger(Two.Events.change);
|
||
}, this);
|
||
|
||
this._command = command || commands.move;
|
||
this._relative = true;
|
||
|
||
if (!command) {
|
||
return this;
|
||
}
|
||
|
||
Anchor.AppendCurveProperties(this);
|
||
|
||
if (_.isNumber(ux)) {
|
||
this.controls.left.x = ux;
|
||
}
|
||
if (_.isNumber(uy)) {
|
||
this.controls.left.y = uy;
|
||
}
|
||
if (_.isNumber(vx)) {
|
||
this.controls.right.x = vx;
|
||
}
|
||
if (_.isNumber(vy)) {
|
||
this.controls.right.y = vy;
|
||
}
|
||
|
||
};
|
||
|
||
_.extend(Anchor, {
|
||
|
||
AppendCurveProperties: function(anchor) {
|
||
anchor.controls = {
|
||
left: new Two.Vector(0, 0),
|
||
right: new Two.Vector(0, 0)
|
||
};
|
||
}
|
||
|
||
});
|
||
|
||
var AnchorProto = {
|
||
|
||
listen: function() {
|
||
|
||
if (!_.isObject(this.controls)) {
|
||
Anchor.AppendCurveProperties(this);
|
||
}
|
||
|
||
this.controls.left.bind(Two.Events.change, this._broadcast);
|
||
this.controls.right.bind(Two.Events.change, this._broadcast);
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
ignore: function() {
|
||
|
||
this.controls.left.unbind(Two.Events.change, this._broadcast);
|
||
this.controls.right.unbind(Two.Events.change, this._broadcast);
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
clone: function() {
|
||
|
||
var controls = this.controls;
|
||
|
||
var clone = new Two.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;
|
||
|
||
},
|
||
|
||
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;
|
||
},
|
||
|
||
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].join(', ');
|
||
}
|
||
|
||
};
|
||
|
||
Object.defineProperty(Anchor.prototype, '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);
|
||
}
|
||
return this.trigger(Two.Events.change);
|
||
}
|
||
|
||
});
|
||
|
||
Object.defineProperty(Anchor.prototype, 'relative', {
|
||
|
||
enumerable: true,
|
||
|
||
get: function() {
|
||
return this._relative;
|
||
},
|
||
|
||
set: function(b) {
|
||
if (this._relative == b) {
|
||
return this;
|
||
}
|
||
this._relative = !!b;
|
||
return this.trigger(Two.Events.change);
|
||
}
|
||
|
||
});
|
||
|
||
_.extend(Anchor.prototype, Two.Vector.prototype, AnchorProto);
|
||
|
||
// Make it possible to bind and still have the Anchor specific
|
||
// inheritance from Two.Vector
|
||
Two.Anchor.prototype.bind = Two.Anchor.prototype.on = function() {
|
||
Two.Vector.prototype.bind.apply(this, arguments);
|
||
_.extend(this, AnchorProto);
|
||
};
|
||
|
||
Two.Anchor.prototype.unbind = Two.Anchor.prototype.off = function() {
|
||
Two.Vector.prototype.unbind.apply(this, arguments);
|
||
_.extend(this, AnchorProto);
|
||
};
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
/**
|
||
* Constants
|
||
*/
|
||
var cos = Math.cos, sin = Math.sin, tan = Math.tan;
|
||
var _ = Two.Utils;
|
||
|
||
/**
|
||
* Two.Matrix contains an array of elements that represent
|
||
* the two dimensional 3 x 3 matrix as illustrated below:
|
||
*
|
||
* =====
|
||
* a b c
|
||
* d e f
|
||
* g h i // this row is not really used in 2d transformations
|
||
* =====
|
||
*
|
||
* String order is for transform strings: a, d, b, e, c, f
|
||
*
|
||
* @class
|
||
*/
|
||
var Matrix = Two.Matrix = function(a, b, c, d, e, f) {
|
||
|
||
this.elements = new Two.Array(9);
|
||
|
||
var elements = a;
|
||
if (!_.isArray(elements)) {
|
||
elements = _.toArray(arguments);
|
||
}
|
||
|
||
// initialize the elements with default values.
|
||
|
||
this.identity().set(elements);
|
||
|
||
};
|
||
|
||
_.extend(Matrix, {
|
||
|
||
Identity: [
|
||
1, 0, 0,
|
||
0, 1, 0,
|
||
0, 0, 1
|
||
],
|
||
|
||
/**
|
||
* Multiply two matrix 3x3 arrays
|
||
*/
|
||
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 Two.Array(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, Two.Utils.Events, {
|
||
|
||
/**
|
||
* Takes an array of elements or the arguments list itself to
|
||
* set and update the current matrix's elements. Only updates
|
||
* specified values.
|
||
*/
|
||
set: function(a) {
|
||
|
||
var elements = a;
|
||
if (!_.isArray(elements)) {
|
||
elements = _.toArray(arguments);
|
||
}
|
||
|
||
_.extend(this.elements, elements);
|
||
|
||
return this.trigger(Two.Events.change);
|
||
|
||
},
|
||
|
||
/**
|
||
* Turn matrix to identity, like resetting.
|
||
*/
|
||
identity: function() {
|
||
|
||
this.set(Matrix.Identity);
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
/**
|
||
* Multiply scalar or multiply by another matrix.
|
||
*/
|
||
multiply: function(a, b, c, d, e, f, g, h, i) {
|
||
|
||
var elements = arguments, l = elements.length;
|
||
|
||
// Multiply scalar
|
||
|
||
if (l <= 1) {
|
||
|
||
_.each(this.elements, function(v, i) {
|
||
this.elements[i] = v * a;
|
||
}, this);
|
||
|
||
return this.trigger(Two.Events.change);
|
||
|
||
}
|
||
|
||
if (l <= 3) { // 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 = elements;
|
||
|
||
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(Two.Events.change);
|
||
|
||
},
|
||
|
||
inverse: function(out) {
|
||
|
||
var a = this.elements;
|
||
out = out || new Two.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;
|
||
|
||
},
|
||
|
||
/**
|
||
* Set a scalar onto the matrix.
|
||
*/
|
||
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);
|
||
|
||
},
|
||
|
||
/**
|
||
* Rotate the matrix.
|
||
*/
|
||
rotate: function(radians) {
|
||
|
||
var c = cos(radians);
|
||
var s = sin(radians);
|
||
|
||
return this.multiply(c, -s, 0, s, c, 0, 0, 0, 1);
|
||
|
||
},
|
||
|
||
/**
|
||
* Translate the matrix.
|
||
*/
|
||
translate: function(x, y) {
|
||
|
||
return this.multiply(1, 0, x, 0, 1, y, 0, 0, 1);
|
||
|
||
},
|
||
|
||
/*
|
||
* Skew the matrix by an angle in the x axis direction.
|
||
*/
|
||
skewX: function(radians) {
|
||
|
||
var a = tan(radians);
|
||
|
||
return this.multiply(1, a, 0, 0, 1, 0, 0, 0, 1);
|
||
|
||
},
|
||
|
||
/*
|
||
* Skew the matrix by an angle in the y axis direction.
|
||
*/
|
||
skewY: function(radians) {
|
||
|
||
var a = tan(radians);
|
||
|
||
return this.multiply(1, 0, 0, a, 1, 0, 0, 0, 1);
|
||
|
||
},
|
||
|
||
/**
|
||
* Create a transform string to be used with rendering apis.
|
||
*/
|
||
toString: function(fullMatrix) {
|
||
var temp = [];
|
||
|
||
this.toArray(fullMatrix, temp);
|
||
|
||
return temp.join(' ');
|
||
|
||
},
|
||
|
||
/**
|
||
* Create a transform array to be used with rendering apis.
|
||
*/
|
||
toArray: function(fullMatrix, output) {
|
||
|
||
var elements = this.elements;
|
||
var hasOutput = !!output;
|
||
|
||
var a = parseFloat(elements[0].toFixed(3));
|
||
var b = parseFloat(elements[1].toFixed(3));
|
||
var c = parseFloat(elements[2].toFixed(3));
|
||
var d = parseFloat(elements[3].toFixed(3));
|
||
var e = parseFloat(elements[4].toFixed(3));
|
||
var f = parseFloat(elements[5].toFixed(3));
|
||
|
||
if (!!fullMatrix) {
|
||
|
||
var g = parseFloat(elements[6].toFixed(3));
|
||
var h = parseFloat(elements[7].toFixed(3));
|
||
var i = parseFloat(elements[8].toFixed(3));
|
||
|
||
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
|
||
];
|
||
|
||
},
|
||
|
||
/**
|
||
* Clone the current matrix.
|
||
*/
|
||
clone: function() {
|
||
var a, b, c, d, e, f, g, h, i;
|
||
|
||
a = this.elements[0];
|
||
b = this.elements[1];
|
||
c = this.elements[2];
|
||
d = this.elements[3];
|
||
e = this.elements[4];
|
||
f = this.elements[5];
|
||
g = this.elements[6];
|
||
h = this.elements[7];
|
||
i = this.elements[8];
|
||
|
||
return new Two.Matrix(a, b, c, d, e, f, g, h, i);
|
||
|
||
}
|
||
|
||
});
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
// Localize variables
|
||
var mod = Two.Utils.mod, toFixed = Two.Utils.toFixed;
|
||
var _ = Two.Utils;
|
||
|
||
var svg = {
|
||
|
||
version: 1.1,
|
||
|
||
ns: 'http://www.w3.org/2000/svg',
|
||
xlink: 'http://www.w3.org/1999/xlink',
|
||
|
||
alignments: {
|
||
left: 'start',
|
||
center: 'middle',
|
||
right: 'end'
|
||
},
|
||
|
||
/**
|
||
* Create an svg namespaced element.
|
||
*/
|
||
createElement: function(name, attrs) {
|
||
var tag = name;
|
||
var elem = document.createElementNS(svg.ns, tag);
|
||
if (tag === 'svg') {
|
||
attrs = _.defaults(attrs || {}, {
|
||
version: svg.version
|
||
});
|
||
}
|
||
if (!_.isEmpty(attrs)) {
|
||
svg.setAttributes(elem, attrs);
|
||
}
|
||
return elem;
|
||
},
|
||
|
||
/**
|
||
* Add attributes from an svg element.
|
||
*/
|
||
setAttributes: function(elem, attrs) {
|
||
var keys = Object.keys(attrs);
|
||
for (var i = 0; i < keys.length; i++) {
|
||
if (/href/.test(keys[i])) {
|
||
elem.setAttributeNS(svg.xlink, keys[i], attrs[keys[i]]);
|
||
} else {
|
||
elem.setAttribute(keys[i], attrs[keys[i]]);
|
||
}
|
||
}
|
||
return this;
|
||
},
|
||
|
||
/**
|
||
* Remove attributes from an svg element.
|
||
*/
|
||
removeAttributes: function(elem, attrs) {
|
||
for (var key in attrs) {
|
||
elem.removeAttribute(key);
|
||
}
|
||
return this;
|
||
},
|
||
|
||
/**
|
||
* Turn a set of vertices into a string for the d property of a path
|
||
* element. It is imperative that the string collation is as fast as
|
||
* possible, because this call will be happening multiple times a
|
||
* second.
|
||
*/
|
||
toString: function(points, closed) {
|
||
|
||
var l = points.length,
|
||
last = l - 1,
|
||
d, // The elusive last Two.Commands.move point
|
||
ret = '';
|
||
|
||
for (var i = 0; i < l; i++) {
|
||
var b = points[i];
|
||
var command;
|
||
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 c = points[next];
|
||
|
||
var vx, vy, ux, uy, ar, bl, br, cl;
|
||
|
||
// Access x and y directly,
|
||
// bypassing the getter
|
||
var x = toFixed(b._x);
|
||
var y = toFixed(b._y);
|
||
|
||
switch (b._command) {
|
||
|
||
case Two.Commands.close:
|
||
command = Two.Commands.close;
|
||
break;
|
||
|
||
case Two.Commands.curve:
|
||
|
||
ar = (a.controls && a.controls.right) || Two.Vector.zero;
|
||
bl = (b.controls && b.controls.left) || Two.Vector.zero;
|
||
|
||
if (a._relative) {
|
||
vx = toFixed((ar.x + a.x));
|
||
vy = toFixed((ar.y + a.y));
|
||
} else {
|
||
vx = toFixed(ar.x);
|
||
vy = toFixed(ar.y);
|
||
}
|
||
|
||
if (b._relative) {
|
||
ux = toFixed((bl.x + b.x));
|
||
uy = toFixed((bl.y + b.y));
|
||
} else {
|
||
ux = toFixed(bl.x);
|
||
uy = toFixed(bl.y);
|
||
}
|
||
|
||
command = ((i === 0) ? Two.Commands.move : Two.Commands.curve) +
|
||
' ' + vx + ' ' + vy + ' ' + ux + ' ' + uy + ' ' + x + ' ' + y;
|
||
break;
|
||
|
||
case Two.Commands.move:
|
||
d = b;
|
||
command = Two.Commands.move + ' ' + x + ' ' + y;
|
||
break;
|
||
|
||
default:
|
||
command = b._command + ' ' + x + ' ' + y;
|
||
|
||
}
|
||
|
||
// Add a final point and close it off
|
||
|
||
if (i >= last && closed) {
|
||
|
||
if (b._command === Two.Commands.curve) {
|
||
|
||
// Make sure we close to the most previous Two.Commands.move
|
||
c = d;
|
||
|
||
br = (b.controls && b.controls.right) || b;
|
||
cl = (c.controls && c.controls.left) || c;
|
||
|
||
if (b._relative) {
|
||
vx = toFixed((br.x + b.x));
|
||
vy = toFixed((br.y + b.y));
|
||
} else {
|
||
vx = toFixed(br.x);
|
||
vy = toFixed(br.y);
|
||
}
|
||
|
||
if (c._relative) {
|
||
ux = toFixed((cl.x + c.x));
|
||
uy = toFixed((cl.y + c.y));
|
||
} else {
|
||
ux = toFixed(cl.x);
|
||
uy = toFixed(cl.y);
|
||
}
|
||
|
||
x = toFixed(c.x);
|
||
y = toFixed(c.y);
|
||
|
||
command +=
|
||
' C ' + vx + ' ' + vy + ' ' + ux + ' ' + uy + ' ' + x + ' ' + y;
|
||
}
|
||
|
||
command += ' Z';
|
||
|
||
}
|
||
|
||
ret += command + ' ';
|
||
|
||
}
|
||
|
||
return ret;
|
||
|
||
},
|
||
|
||
getClip: function(shape) {
|
||
|
||
var clip = shape._renderer.clip;
|
||
|
||
if (!clip) {
|
||
|
||
var root = shape;
|
||
|
||
while (root.parent) {
|
||
root = root.parent;
|
||
}
|
||
|
||
clip = shape._renderer.clip = svg.createElement('clipPath');
|
||
root.defs.appendChild(clip);
|
||
|
||
}
|
||
|
||
return clip;
|
||
|
||
},
|
||
|
||
group: {
|
||
|
||
// TODO: Can speed up.
|
||
// TODO: How does this effect a f
|
||
appendChild: function(object) {
|
||
|
||
var elem = object._renderer.elem;
|
||
|
||
if (!elem) {
|
||
return;
|
||
}
|
||
|
||
var tag = elem.nodeName;
|
||
|
||
if (!tag || /(radial|linear)gradient/i.test(tag) || object._clip) {
|
||
return;
|
||
}
|
||
|
||
this.elem.appendChild(elem);
|
||
|
||
},
|
||
|
||
removeChild: function(object) {
|
||
|
||
var elem = object._renderer.elem;
|
||
|
||
if (!elem || elem.parentNode != this.elem) {
|
||
return;
|
||
}
|
||
|
||
var tag = elem.nodeName;
|
||
|
||
if (!tag) {
|
||
return;
|
||
}
|
||
|
||
// Defer subtractions while clipping.
|
||
if (object._clip) {
|
||
return;
|
||
}
|
||
|
||
this.elem.removeChild(elem);
|
||
|
||
},
|
||
|
||
orderChild: function(object) {
|
||
this.elem.appendChild(object._renderer.elem);
|
||
},
|
||
|
||
renderChild: function(child) {
|
||
svg[child._renderer.type].render.call(child, this);
|
||
},
|
||
|
||
render: function(domElement) {
|
||
|
||
this._update();
|
||
|
||
// Shortcut for hidden objects.
|
||
// Doesn't reset the flags, so changes are stored and
|
||
// applied once the object is visible again
|
||
if (this._opacity === 0 && !this._flagOpacity) {
|
||
return this;
|
||
}
|
||
|
||
if (!this._renderer.elem) {
|
||
this._renderer.elem = svg.createElement('g', {
|
||
id: this.id
|
||
});
|
||
domElement.appendChild(this._renderer.elem);
|
||
}
|
||
|
||
// _Update styles for the <g>
|
||
var flagMatrix = this._matrix.manual || this._flagMatrix;
|
||
var context = {
|
||
domElement: domElement,
|
||
elem: this._renderer.elem
|
||
};
|
||
|
||
if (flagMatrix) {
|
||
this._renderer.elem.setAttribute('transform', 'matrix(' + this._matrix.toString() + ')');
|
||
}
|
||
|
||
for (var i = 0; i < this.children.length; i++) {
|
||
var child = this.children[i];
|
||
svg[child._renderer.type].render.call(child, domElement);
|
||
}
|
||
|
||
if (this._flagOpacity) {
|
||
this._renderer.elem.setAttribute('opacity', this._opacity);
|
||
}
|
||
|
||
if (this._flagAdditions) {
|
||
this.additions.forEach(svg.group.appendChild, context);
|
||
}
|
||
|
||
if (this._flagSubtractions) {
|
||
this.subtractions.forEach(svg.group.removeChild, context);
|
||
}
|
||
|
||
if (this._flagOrder) {
|
||
this.children.forEach(svg.group.orderChild, context);
|
||
}
|
||
|
||
/**
|
||
* 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 (this._flagClip) {
|
||
|
||
// clip = svg.getClip(this);
|
||
// elem = this._renderer.elem;
|
||
|
||
// if (this._clip) {
|
||
// elem.removeAttribute('id');
|
||
// clip.setAttribute('id', this.id);
|
||
// clip.appendChild(elem);
|
||
// } else {
|
||
// clip.removeAttribute('id');
|
||
// elem.setAttribute('id', this.id);
|
||
// this.parent._renderer.elem.appendChild(elem); // TODO: should be insertBefore
|
||
// }
|
||
|
||
// }
|
||
|
||
if (this._flagMask) {
|
||
if (this._mask) {
|
||
this._renderer.elem.setAttribute('clip-path', 'url(#' + this._mask.id + ')');
|
||
} else {
|
||
this._renderer.elem.removeAttribute('clip-path');
|
||
}
|
||
}
|
||
|
||
return this.flagReset();
|
||
|
||
}
|
||
|
||
},
|
||
|
||
path: {
|
||
|
||
render: function(domElement) {
|
||
|
||
this._update();
|
||
|
||
// Shortcut for hidden objects.
|
||
// Doesn't reset the flags, so changes are stored and
|
||
// applied once the object is visible again
|
||
if (this._opacity === 0 && !this._flagOpacity) {
|
||
return this;
|
||
}
|
||
|
||
// Collect any attribute that needs to be changed here
|
||
var changed = {};
|
||
|
||
var flagMatrix = this._matrix.manual || this._flagMatrix;
|
||
|
||
if (flagMatrix) {
|
||
changed.transform = 'matrix(' + this._matrix.toString() + ')';
|
||
}
|
||
|
||
if (this._flagVertices) {
|
||
var vertices = svg.toString(this._vertices, this._closed);
|
||
changed.d = vertices;
|
||
}
|
||
|
||
if (this._fill && this._fill._renderer) {
|
||
this._fill._update();
|
||
svg[this._fill._renderer.type].render.call(this._fill, domElement, true);
|
||
}
|
||
|
||
if (this._flagFill) {
|
||
changed.fill = this._fill && this._fill.id
|
||
? 'url(#' + this._fill.id + ')' : this._fill;
|
||
}
|
||
|
||
if (this._stroke && this._stroke._renderer) {
|
||
this._stroke._update();
|
||
svg[this._stroke._renderer.type].render.call(this._stroke, domElement, true);
|
||
}
|
||
|
||
if (this._flagStroke) {
|
||
changed.stroke = this._stroke && this._stroke.id
|
||
? 'url(#' + this._stroke.id + ')' : this._stroke;
|
||
}
|
||
|
||
if (this._flagLinewidth) {
|
||
changed['stroke-width'] = this._linewidth;
|
||
}
|
||
|
||
if (this._flagOpacity) {
|
||
changed['stroke-opacity'] = this._opacity;
|
||
changed['fill-opacity'] = this._opacity;
|
||
}
|
||
|
||
if (this._flagVisible) {
|
||
changed.visibility = this._visible ? 'visible' : 'hidden';
|
||
}
|
||
|
||
if (this._flagCap) {
|
||
changed['stroke-linecap'] = this._cap;
|
||
}
|
||
|
||
if (this._flagJoin) {
|
||
changed['stroke-linejoin'] = this._join;
|
||
}
|
||
|
||
if (this._flagMiter) {
|
||
changed['stroke-miterlimit'] = this._miter;
|
||
}
|
||
|
||
// If there is no attached DOM element yet,
|
||
// create it with all necessary attributes.
|
||
if (!this._renderer.elem) {
|
||
|
||
changed.id = this.id;
|
||
this._renderer.elem = svg.createElement('path', changed);
|
||
domElement.appendChild(this._renderer.elem);
|
||
|
||
// Otherwise apply all pending attributes
|
||
} else {
|
||
svg.setAttributes(this._renderer.elem, changed);
|
||
}
|
||
|
||
if (this._flagClip) {
|
||
|
||
var clip = svg.getClip(this);
|
||
var elem = this._renderer.elem;
|
||
|
||
if (this._clip) {
|
||
elem.removeAttribute('id');
|
||
clip.setAttribute('id', this.id);
|
||
clip.appendChild(elem);
|
||
} else {
|
||
clip.removeAttribute('id');
|
||
elem.setAttribute('id', this.id);
|
||
this.parent._renderer.elem.appendChild(elem); // TODO: should be insertBefore
|
||
}
|
||
|
||
}
|
||
|
||
/**
|
||
* 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 (this._flagMask) {
|
||
// if (this._mask) {
|
||
// elem.setAttribute('clip-path', 'url(#' + this._mask.id + ')');
|
||
// } else {
|
||
// elem.removeAttribute('clip-path');
|
||
// }
|
||
// }
|
||
|
||
return this.flagReset();
|
||
|
||
}
|
||
|
||
},
|
||
|
||
text: {
|
||
|
||
render: function(domElement) {
|
||
|
||
this._update();
|
||
|
||
var changed = {};
|
||
|
||
var flagMatrix = this._matrix.manual || this._flagMatrix;
|
||
|
||
if (flagMatrix) {
|
||
changed.transform = 'matrix(' + this._matrix.toString() + ')';
|
||
}
|
||
|
||
if (this._flagFamily) {
|
||
changed['font-family'] = this._family;
|
||
}
|
||
if (this._flagSize) {
|
||
changed['font-size'] = this._size;
|
||
}
|
||
if (this._flagLeading) {
|
||
changed['line-height'] = this._leading;
|
||
}
|
||
if (this._flagAlignment) {
|
||
changed['text-anchor'] = svg.alignments[this._alignment] || this._alignment;
|
||
}
|
||
if (this._flagBaseline) {
|
||
changed['alignment-baseline'] = changed['dominant-baseline'] = this._baseline;
|
||
}
|
||
if (this._flagStyle) {
|
||
changed['font-style'] = this._style;
|
||
}
|
||
if (this._flagWeight) {
|
||
changed['font-weight'] = this._weight;
|
||
}
|
||
if (this._flagDecoration) {
|
||
changed['text-decoration'] = this._decoration;
|
||
}
|
||
if (this._fill && this._fill._renderer) {
|
||
this._fill._update();
|
||
svg[this._fill._renderer.type].render.call(this._fill, domElement, true);
|
||
}
|
||
if (this._flagFill) {
|
||
changed.fill = this._fill && this._fill.id
|
||
? 'url(#' + this._fill.id + ')' : this._fill;
|
||
}
|
||
if (this._stroke && this._stroke._renderer) {
|
||
this._stroke._update();
|
||
svg[this._stroke._renderer.type].render.call(this._stroke, domElement, true);
|
||
}
|
||
if (this._flagStroke) {
|
||
changed.stroke = this._stroke && this._stroke.id
|
||
? 'url(#' + this._stroke.id + ')' : this._stroke;
|
||
}
|
||
if (this._flagLinewidth) {
|
||
changed['stroke-width'] = this._linewidth;
|
||
}
|
||
if (this._flagOpacity) {
|
||
changed.opacity = this._opacity;
|
||
}
|
||
if (this._flagVisible) {
|
||
changed.visibility = this._visible ? 'visible' : 'hidden';
|
||
}
|
||
|
||
if (!this._renderer.elem) {
|
||
|
||
changed.id = this.id;
|
||
|
||
this._renderer.elem = svg.createElement('text', changed);
|
||
domElement.defs.appendChild(this._renderer.elem);
|
||
|
||
} else {
|
||
|
||
svg.setAttributes(this._renderer.elem, changed);
|
||
|
||
}
|
||
|
||
if (this._flagClip) {
|
||
|
||
var clip = svg.getClip(this);
|
||
var elem = this._renderer.elem;
|
||
|
||
if (this._clip) {
|
||
elem.removeAttribute('id');
|
||
clip.setAttribute('id', this.id);
|
||
clip.appendChild(elem);
|
||
} else {
|
||
clip.removeAttribute('id');
|
||
elem.setAttribute('id', this.id);
|
||
this.parent._renderer.elem.appendChild(elem); // TODO: should be insertBefore
|
||
}
|
||
|
||
}
|
||
|
||
if (this._flagValue) {
|
||
this._renderer.elem.textContent = this._value;
|
||
}
|
||
|
||
return this.flagReset();
|
||
|
||
}
|
||
|
||
},
|
||
|
||
'linear-gradient': {
|
||
|
||
render: function(domElement, silent) {
|
||
|
||
if (!silent) {
|
||
this._update();
|
||
}
|
||
|
||
var changed = {};
|
||
|
||
if (this._flagEndPoints) {
|
||
changed.x1 = this.left._x;
|
||
changed.y1 = this.left._y;
|
||
changed.x2 = this.right._x;
|
||
changed.y2 = this.right._y;
|
||
}
|
||
|
||
if (this._flagSpread) {
|
||
changed.spreadMethod = this._spread;
|
||
}
|
||
|
||
// If there is no attached DOM element yet,
|
||
// create it with all necessary attributes.
|
||
if (!this._renderer.elem) {
|
||
|
||
changed.id = this.id;
|
||
changed.gradientUnits = 'userSpaceOnUse';
|
||
this._renderer.elem = svg.createElement('linearGradient', changed);
|
||
domElement.defs.appendChild(this._renderer.elem);
|
||
|
||
// Otherwise apply all pending attributes
|
||
} else {
|
||
|
||
svg.setAttributes(this._renderer.elem, changed);
|
||
|
||
}
|
||
|
||
if (this._flagStops) {
|
||
|
||
var lengthChanged = this._renderer.elem.childNodes.length
|
||
!== this.stops.length;
|
||
|
||
if (lengthChanged) {
|
||
this._renderer.elem.childNodes.length = 0;
|
||
}
|
||
|
||
for (var i = 0; i < this.stops.length; i++) {
|
||
|
||
var stop = this.stops[i];
|
||
var attrs = {};
|
||
|
||
if (stop._flagOffset) {
|
||
attrs.offset = 100 * stop._offset + '%';
|
||
}
|
||
if (stop._flagColor) {
|
||
attrs['stop-color'] = stop._color;
|
||
}
|
||
if (stop._flagOpacity) {
|
||
attrs['stop-opacity'] = stop._opacity;
|
||
}
|
||
|
||
if (!stop._renderer.elem) {
|
||
stop._renderer.elem = svg.createElement('stop', attrs);
|
||
} else {
|
||
svg.setAttributes(stop._renderer.elem, attrs);
|
||
}
|
||
|
||
if (lengthChanged) {
|
||
this._renderer.elem.appendChild(stop._renderer.elem);
|
||
}
|
||
stop.flagReset();
|
||
|
||
}
|
||
|
||
}
|
||
|
||
return this.flagReset();
|
||
|
||
}
|
||
|
||
},
|
||
|
||
'radial-gradient': {
|
||
|
||
render: function(domElement, silent) {
|
||
|
||
if (!silent) {
|
||
this._update();
|
||
}
|
||
|
||
var changed = {};
|
||
|
||
if (this._flagCenter) {
|
||
changed.cx = this.center._x;
|
||
changed.cy = this.center._y;
|
||
}
|
||
if (this._flagFocal) {
|
||
changed.fx = this.focal._x;
|
||
changed.fy = this.focal._y;
|
||
}
|
||
|
||
if (this._flagRadius) {
|
||
changed.r = this._radius;
|
||
}
|
||
|
||
if (this._flagSpread) {
|
||
changed.spreadMethod = this._spread;
|
||
}
|
||
|
||
// If there is no attached DOM element yet,
|
||
// create it with all necessary attributes.
|
||
if (!this._renderer.elem) {
|
||
|
||
changed.id = this.id;
|
||
changed.gradientUnits = 'userSpaceOnUse';
|
||
this._renderer.elem = svg.createElement('radialGradient', changed);
|
||
domElement.defs.appendChild(this._renderer.elem);
|
||
|
||
// Otherwise apply all pending attributes
|
||
} else {
|
||
|
||
svg.setAttributes(this._renderer.elem, changed);
|
||
|
||
}
|
||
|
||
if (this._flagStops) {
|
||
|
||
var lengthChanged = this._renderer.elem.childNodes.length
|
||
!== this.stops.length;
|
||
|
||
if (lengthChanged) {
|
||
this._renderer.elem.childNodes.length = 0;
|
||
}
|
||
|
||
for (var i = 0; i < this.stops.length; i++) {
|
||
|
||
var stop = this.stops[i];
|
||
var attrs = {};
|
||
|
||
if (stop._flagOffset) {
|
||
attrs.offset = 100 * stop._offset + '%';
|
||
}
|
||
if (stop._flagColor) {
|
||
attrs['stop-color'] = stop._color;
|
||
}
|
||
if (stop._flagOpacity) {
|
||
attrs['stop-opacity'] = stop._opacity;
|
||
}
|
||
|
||
if (!stop._renderer.elem) {
|
||
stop._renderer.elem = svg.createElement('stop', attrs);
|
||
} else {
|
||
svg.setAttributes(stop._renderer.elem, attrs);
|
||
}
|
||
|
||
if (lengthChanged) {
|
||
this._renderer.elem.appendChild(stop._renderer.elem);
|
||
}
|
||
stop.flagReset();
|
||
|
||
}
|
||
|
||
}
|
||
|
||
return this.flagReset();
|
||
|
||
}
|
||
|
||
},
|
||
|
||
texture: {
|
||
|
||
render: function(domElement, silent) {
|
||
|
||
if (!silent) {
|
||
this._update();
|
||
}
|
||
|
||
var changed = {};
|
||
var styles = { x: 0, y: 0 };
|
||
var image = this.image;
|
||
|
||
if (this._flagLoaded && this.loaded) {
|
||
|
||
switch (image.nodeName.toLowerCase()) {
|
||
|
||
case 'canvas':
|
||
styles.href = styles['xlink:href'] = image.toDataURL('image/png');
|
||
break;
|
||
case 'img':
|
||
case 'image':
|
||
styles.href = styles['xlink:href'] = this.src;
|
||
break;
|
||
|
||
}
|
||
|
||
}
|
||
|
||
if (this._flagOffset || this._flagLoaded || this._flagScale) {
|
||
|
||
changed.x = this._offset.x;
|
||
changed.y = this._offset.y;
|
||
|
||
if (image) {
|
||
|
||
changed.x -= image.width / 2;
|
||
changed.y -= image.height / 2;
|
||
|
||
if (this._scale instanceof Two.Vector) {
|
||
changed.x *= this._scale.x;
|
||
changed.y *= this._scale.y;
|
||
} else {
|
||
changed.x *= this._scale;
|
||
changed.y *= this._scale;
|
||
}
|
||
}
|
||
|
||
if (changed.x > 0) {
|
||
changed.x *= - 1;
|
||
}
|
||
if (changed.y > 0) {
|
||
changed.y *= - 1;
|
||
}
|
||
|
||
}
|
||
|
||
if (this._flagScale || this._flagLoaded || this._flagRepeat) {
|
||
|
||
changed.width = 0;
|
||
changed.height = 0;
|
||
|
||
if (image) {
|
||
|
||
styles.width = changed.width = image.width;
|
||
styles.height = changed.height = image.height;
|
||
|
||
// TODO: Hack / Bandaid
|
||
switch (this._repeat) {
|
||
case 'no-repeat':
|
||
changed.width += 1;
|
||
changed.height += 1;
|
||
break;
|
||
}
|
||
|
||
if (this._scale instanceof Two.Vector) {
|
||
changed.width *= this._scale.x;
|
||
changed.height *= this._scale.y;
|
||
} else {
|
||
changed.width *= this._scale;
|
||
changed.height *= this._scale;
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
if (this._flagScale || this._flagLoaded) {
|
||
if (!this._renderer.image) {
|
||
this._renderer.image = svg.createElement('image', styles);
|
||
} else if (!_.isEmpty(styles)) {
|
||
svg.setAttributes(this._renderer.image, styles);
|
||
}
|
||
}
|
||
|
||
if (!this._renderer.elem) {
|
||
|
||
changed.id = this.id;
|
||
changed.patternUnits = 'userSpaceOnUse';
|
||
this._renderer.elem = svg.createElement('pattern', changed);
|
||
domElement.defs.appendChild(this._renderer.elem);
|
||
|
||
} else if (!_.isEmpty(changed)) {
|
||
|
||
svg.setAttributes(this._renderer.elem, changed);
|
||
|
||
}
|
||
|
||
if (this._renderer.elem && this._renderer.image && !this._renderer.appended) {
|
||
this._renderer.elem.appendChild(this._renderer.image);
|
||
this._renderer.appended = true;
|
||
}
|
||
|
||
return this.flagReset();
|
||
|
||
}
|
||
|
||
}
|
||
|
||
};
|
||
|
||
/**
|
||
* @class
|
||
*/
|
||
var Renderer = Two[Two.Types.svg] = function(params) {
|
||
|
||
this.domElement = params.domElement || svg.createElement('svg');
|
||
|
||
this.scene = new Two.Group();
|
||
this.scene.parent = this;
|
||
|
||
this.defs = svg.createElement('defs');
|
||
this.domElement.appendChild(this.defs);
|
||
this.domElement.defs = this.defs;
|
||
this.domElement.style.overflow = 'hidden';
|
||
|
||
};
|
||
|
||
_.extend(Renderer, {
|
||
|
||
Utils: svg
|
||
|
||
});
|
||
|
||
_.extend(Renderer.prototype, Two.Utils.Events, {
|
||
|
||
setSize: function(width, height) {
|
||
|
||
this.width = width;
|
||
this.height = height;
|
||
|
||
svg.setAttributes(this.domElement, {
|
||
width: width,
|
||
height: height
|
||
});
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
render: function() {
|
||
|
||
svg.group.render.call(this.scene, this.domElement);
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
/**
|
||
* Constants
|
||
*/
|
||
var mod = Two.Utils.mod, toFixed = Two.Utils.toFixed;
|
||
var getRatio = Two.Utils.getRatio;
|
||
var _ = Two.Utils;
|
||
|
||
// 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: /(none|transparent)/i,
|
||
|
||
alignments: {
|
||
left: 'start',
|
||
middle: 'center',
|
||
right: 'end'
|
||
},
|
||
|
||
shim: function(elem) {
|
||
elem.tagName = 'canvas';
|
||
elem.nodeType = 1;
|
||
return elem;
|
||
},
|
||
|
||
group: {
|
||
|
||
renderChild: function(child) {
|
||
canvas[child._renderer.type].render.call(child, this.ctx, true, this.clip);
|
||
},
|
||
|
||
render: function(ctx) {
|
||
|
||
// TODO: Add a check here to only invoke _update if need be.
|
||
this._update();
|
||
|
||
var matrix = this._matrix.elements;
|
||
var parent = this.parent;
|
||
this._renderer.opacity = this._opacity * (parent && parent._renderer ? parent._renderer.opacity : 1);
|
||
|
||
var defaultMatrix = isDefaultMatrix(matrix);
|
||
|
||
var mask = this._mask;
|
||
// var clip = this._clip;
|
||
|
||
if (!this._renderer.context) {
|
||
this._renderer.context = {};
|
||
}
|
||
|
||
this._renderer.context.ctx = ctx;
|
||
// this._renderer.context.clip = clip;
|
||
|
||
if (!defaultMatrix) {
|
||
ctx.save();
|
||
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 (!defaultMatrix) {
|
||
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;
|
||
|
||
// TODO: Add a check here to only invoke _update if need be.
|
||
this._update();
|
||
|
||
matrix = this._matrix.elements;
|
||
stroke = this._stroke;
|
||
linewidth = this._linewidth;
|
||
fill = this._fill;
|
||
opacity = this._opacity * this.parent._renderer.opacity;
|
||
visible = this._visible;
|
||
cap = this._cap;
|
||
join = this._join;
|
||
miter = this._miter;
|
||
closed = this._closed;
|
||
commands = this._vertices; // Commands
|
||
length = commands.length;
|
||
last = length - 1;
|
||
defaultMatrix = isDefaultMatrix(matrix);
|
||
|
||
// mask = this._mask;
|
||
clip = this._clip;
|
||
|
||
if (!forced && (!visible || clip)) {
|
||
return this;
|
||
}
|
||
|
||
// 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 (_.isString(fill)) {
|
||
ctx.fillStyle = fill;
|
||
} else {
|
||
canvas[fill._renderer.type].render.call(fill, ctx);
|
||
ctx.fillStyle = fill._renderer.effect;
|
||
}
|
||
}
|
||
if (stroke) {
|
||
if (_.isString(stroke)) {
|
||
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 (cap) {
|
||
ctx.lineCap = cap;
|
||
}
|
||
if (_.isNumber(opacity)) {
|
||
ctx.globalAlpha = opacity;
|
||
}
|
||
|
||
ctx.beginPath();
|
||
|
||
for (var i = 0; i < commands.length; i++) {
|
||
|
||
b = commands[i];
|
||
|
||
x = toFixed(b._x);
|
||
y = toFixed(b._y);
|
||
|
||
switch (b._command) {
|
||
|
||
case Two.Commands.close:
|
||
ctx.closePath();
|
||
break;
|
||
|
||
case Two.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) || Two.Vector.zero;
|
||
bl = (b.controls && b.controls.left) || Two.Vector.zero;
|
||
|
||
if (a._relative) {
|
||
vx = (ar.x + toFixed(a._x));
|
||
vy = (ar.y + toFixed(a._y));
|
||
} else {
|
||
vx = toFixed(ar.x);
|
||
vy = toFixed(ar.y);
|
||
}
|
||
|
||
if (b._relative) {
|
||
ux = (bl.x + toFixed(b._x));
|
||
uy = (bl.y + toFixed(b._y));
|
||
} else {
|
||
ux = toFixed(bl.x);
|
||
uy = toFixed(bl.y);
|
||
}
|
||
|
||
ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
|
||
|
||
if (i >= last && closed) {
|
||
|
||
c = d;
|
||
|
||
br = (b.controls && b.controls.right) || Two.Vector.zero;
|
||
cl = (c.controls && c.controls.left) || Two.Vector.zero;
|
||
|
||
if (b._relative) {
|
||
vx = (br.x + toFixed(b._x));
|
||
vy = (br.y + toFixed(b._y));
|
||
} else {
|
||
vx = toFixed(br.x);
|
||
vy = toFixed(br.y);
|
||
}
|
||
|
||
if (c._relative) {
|
||
ux = (cl.x + toFixed(c._x));
|
||
uy = (cl.y + toFixed(c._y));
|
||
} else {
|
||
ux = toFixed(cl.x);
|
||
uy = toFixed(cl.y);
|
||
}
|
||
|
||
x = toFixed(c._x);
|
||
y = toFixed(c._y);
|
||
|
||
ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
|
||
|
||
}
|
||
|
||
break;
|
||
|
||
case Two.Commands.line:
|
||
ctx.lineTo(x, y);
|
||
break;
|
||
|
||
case Two.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();
|
||
}
|
||
|
||
return this.flagReset();
|
||
|
||
}
|
||
|
||
},
|
||
|
||
text: {
|
||
|
||
render: function(ctx, forced, parentClipped) {
|
||
|
||
// TODO: Add a check here to only invoke _update if need be.
|
||
this._update();
|
||
|
||
var matrix = this._matrix.elements;
|
||
var stroke = this._stroke;
|
||
var linewidth = this._linewidth;
|
||
var fill = this._fill;
|
||
var opacity = this._opacity * this.parent._renderer.opacity;
|
||
var visible = this._visible;
|
||
var defaultMatrix = isDefaultMatrix(matrix);
|
||
var isOffset = fill._renderer && fill._renderer.offset
|
||
&& stroke._renderer && stroke._renderer.offset;
|
||
|
||
var a, b, c, d, e, sx, sy;
|
||
|
||
// mask = this._mask;
|
||
var clip = this._clip;
|
||
|
||
if (!forced && (!visible || clip)) {
|
||
return this;
|
||
}
|
||
|
||
// 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 = canvas.alignments[this._alignment] || this._alignment;
|
||
ctx.textBaseline = this._baseline;
|
||
|
||
// Styles
|
||
if (fill) {
|
||
if (_.isString(fill)) {
|
||
ctx.fillStyle = fill;
|
||
} else {
|
||
canvas[fill._renderer.type].render.call(fill, ctx);
|
||
ctx.fillStyle = fill._renderer.effect;
|
||
}
|
||
}
|
||
if (stroke) {
|
||
if (_.isString(stroke)) {
|
||
ctx.strokeStyle = stroke;
|
||
} else {
|
||
canvas[stroke._renderer.type].render.call(stroke, ctx);
|
||
ctx.strokeStyle = stroke._renderer.effect;
|
||
}
|
||
}
|
||
if (linewidth) {
|
||
ctx.lineWidth = linewidth;
|
||
}
|
||
if (_.isNumber(opacity)) {
|
||
ctx.globalAlpha = opacity;
|
||
}
|
||
|
||
if (!clip && !parentClipped) {
|
||
|
||
if (!canvas.isHidden.test(fill)) {
|
||
|
||
if (fill._renderer && fill._renderer.offset) {
|
||
|
||
sx = toFixed(fill._renderer.scale.x);
|
||
sy = toFixed(fill._renderer.scale.y);
|
||
|
||
ctx.save();
|
||
ctx.translate( - toFixed(fill._renderer.offset.x),
|
||
- toFixed(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, toFixed(a) + 'px/',
|
||
toFixed(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, toFixed(c), toFixed(d));
|
||
ctx.restore();
|
||
|
||
} else {
|
||
ctx.fillText(this.value, 0, 0);
|
||
}
|
||
|
||
}
|
||
|
||
if (!canvas.isHidden.test(stroke)) {
|
||
|
||
if (stroke._renderer && stroke._renderer.offset) {
|
||
|
||
sx = toFixed(stroke._renderer.scale.x);
|
||
sy = toFixed(stroke._renderer.scale.y);
|
||
|
||
ctx.save();
|
||
ctx.translate(- toFixed(stroke._renderer.offset.x),
|
||
- toFixed(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, toFixed(a) + 'px/',
|
||
toFixed(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 = toFixed(e);
|
||
ctx.strokeText(this.value, toFixed(c), toFixed(d));
|
||
ctx.restore();
|
||
|
||
} else {
|
||
ctx.strokeText(this.value, 0, 0);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!defaultMatrix) {
|
||
ctx.restore();
|
||
}
|
||
|
||
// TODO: Test for text
|
||
if (clip && !parentClipped) {
|
||
ctx.clip();
|
||
}
|
||
|
||
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;
|
||
var repeat;
|
||
|
||
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 Two.Vector)) {
|
||
this._renderer.offset = new Two.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 Two.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 Two.Vector)) {
|
||
this._renderer.scale = new Two.Vector();
|
||
}
|
||
|
||
if (this._scale instanceof Two.Vector) {
|
||
this._renderer.scale.copy(this._scale);
|
||
} else {
|
||
this._renderer.scale.set(this._scale, this._scale);
|
||
}
|
||
|
||
}
|
||
|
||
return this.flagReset();
|
||
|
||
}
|
||
|
||
}
|
||
|
||
};
|
||
|
||
var Renderer = Two[Two.Types.canvas] = function(params) {
|
||
// Smoothing property. Defaults to true
|
||
// Set it to false when working with pixel art.
|
||
// false can lead to better performance, since it would use a cheaper interpolation algorithm.
|
||
// It might not make a big difference on GPU backed canvases.
|
||
var smoothing = (params.smoothing !== false);
|
||
this.domElement = params.domElement || document.createElement('canvas');
|
||
this.ctx = this.domElement.getContext('2d');
|
||
this.overdraw = params.overdraw || false;
|
||
|
||
if (!_.isUndefined(this.ctx.imageSmoothingEnabled)) {
|
||
this.ctx.imageSmoothingEnabled = smoothing;
|
||
}
|
||
|
||
// Everything drawn on the canvas needs to be added to the scene.
|
||
this.scene = new Two.Group();
|
||
this.scene.parent = this;
|
||
};
|
||
|
||
|
||
_.extend(Renderer, {
|
||
|
||
Utils: canvas
|
||
|
||
});
|
||
|
||
_.extend(Renderer.prototype, Two.Utils.Events, {
|
||
|
||
setSize: function(width, height, ratio) {
|
||
|
||
this.width = width;
|
||
this.height = height;
|
||
|
||
this.ratio = _.isUndefined(ratio) ? 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;
|
||
|
||
},
|
||
|
||
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 resetTransform(ctx) {
|
||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||
}
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
/**
|
||
* Constants
|
||
*/
|
||
|
||
var root = Two.root,
|
||
multiplyMatrix = Two.Matrix.Multiply,
|
||
mod = Two.Utils.mod,
|
||
identity = [1, 0, 0, 0, 1, 0, 0, 0, 1],
|
||
transformation = new Two.Array(9),
|
||
getRatio = Two.Utils.getRatio,
|
||
getComputedMatrix = Two.Utils.getComputedMatrix,
|
||
toFixed = Two.Utils.toFixed,
|
||
_ = Two.Utils;
|
||
|
||
var webgl = {
|
||
|
||
isHidden: /(none|transparent)/i,
|
||
|
||
canvas: (root.document ? root.document.createElement('canvas') : { getContext: _.identity }),
|
||
|
||
alignments: {
|
||
left: 'start',
|
||
middle: 'center',
|
||
right: 'end'
|
||
},
|
||
|
||
matrix: new Two.Matrix(),
|
||
|
||
uv: new Two.Array([
|
||
0, 0,
|
||
1, 0,
|
||
0, 1,
|
||
0, 1,
|
||
1, 0,
|
||
1, 1
|
||
]),
|
||
|
||
group: {
|
||
|
||
removeChild: function(child, gl) {
|
||
if (child.children) {
|
||
for (var i = 0; i < child.children.length; i++) {
|
||
webgl.group.removeChild(child.children[i], gl);
|
||
}
|
||
return;
|
||
}
|
||
// Deallocate texture to free up gl memory.
|
||
gl.deleteTexture(child._renderer.texture);
|
||
delete child._renderer.texture;
|
||
},
|
||
|
||
renderChild: function(child) {
|
||
webgl[child._renderer.type].render.call(child, this.gl, this.program);
|
||
},
|
||
|
||
render: function(gl, program) {
|
||
|
||
this._update();
|
||
|
||
var parent = this.parent;
|
||
var flagParentMatrix = (parent._matrix && parent._matrix.manual) || parent._flagMatrix;
|
||
var flagMatrix = this._matrix.manual || this._flagMatrix;
|
||
|
||
if (flagParentMatrix || flagMatrix) {
|
||
|
||
if (!this._renderer.matrix) {
|
||
this._renderer.matrix = new Two.Array(9);
|
||
}
|
||
|
||
// Reduce amount of object / array creation / deletion
|
||
this._matrix.toArray(true, transformation);
|
||
|
||
multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix);
|
||
this._renderer.scale = this._scale * parent._renderer.scale;
|
||
|
||
if (flagParentMatrix) {
|
||
this._flagMatrix = true;
|
||
}
|
||
|
||
}
|
||
|
||
if (this._mask) {
|
||
|
||
gl.enable(gl.STENCIL_TEST);
|
||
gl.stencilFunc(gl.ALWAYS, 1, 1);
|
||
|
||
gl.colorMask(false, false, false, true);
|
||
gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR);
|
||
|
||
webgl[this._mask._renderer.type].render.call(this._mask, gl, program, this);
|
||
|
||
gl.colorMask(true, true, true, true);
|
||
gl.stencilFunc(gl.NOTEQUAL, 0, 1);
|
||
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
|
||
|
||
}
|
||
|
||
this._flagOpacity = parent._flagOpacity || this._flagOpacity;
|
||
|
||
this._renderer.opacity = this._opacity
|
||
* (parent && parent._renderer ? parent._renderer.opacity : 1);
|
||
|
||
if (this._flagSubtractions) {
|
||
for (var i = 0; i < this.subtractions.length; i++) {
|
||
webgl.group.removeChild(this.subtractions[i], gl);
|
||
}
|
||
}
|
||
|
||
this.children.forEach(webgl.group.renderChild, {
|
||
gl: gl,
|
||
program: program
|
||
});
|
||
|
||
if (this._mask) {
|
||
|
||
gl.colorMask(false, false, false, false);
|
||
gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR);
|
||
|
||
webgl[this._mask._renderer.type].render.call(this._mask, gl, program, this);
|
||
|
||
gl.colorMask(true, true, true, true);
|
||
gl.stencilFunc(gl.NOTEQUAL, 0, 1);
|
||
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
|
||
|
||
gl.disable(gl.STENCIL_TEST);
|
||
|
||
}
|
||
|
||
return this.flagReset();
|
||
|
||
}
|
||
|
||
},
|
||
|
||
path: {
|
||
|
||
updateCanvas: function(elem) {
|
||
|
||
var next, prev, a, c, ux, uy, vx, vy, ar, bl, br, cl, x, y;
|
||
var isOffset;
|
||
|
||
var commands = elem._vertices;
|
||
var canvas = this.canvas;
|
||
var ctx = this.ctx;
|
||
|
||
// Styles
|
||
var scale = elem._renderer.scale;
|
||
var stroke = elem._stroke;
|
||
var linewidth = elem._linewidth;
|
||
var fill = elem._fill;
|
||
var opacity = elem._renderer.opacity || elem._opacity;
|
||
var cap = elem._cap;
|
||
var join = elem._join;
|
||
var miter = elem._miter;
|
||
var closed = elem._closed;
|
||
var length = commands.length;
|
||
var last = length - 1;
|
||
|
||
canvas.width = Math.max(Math.ceil(elem._renderer.rect.width * scale), 1);
|
||
canvas.height = Math.max(Math.ceil(elem._renderer.rect.height * scale), 1);
|
||
|
||
var centroid = elem._renderer.rect.centroid;
|
||
var cx = centroid.x;
|
||
var cy = centroid.y;
|
||
|
||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||
|
||
if (fill) {
|
||
if (_.isString(fill)) {
|
||
ctx.fillStyle = fill;
|
||
} else {
|
||
webgl[fill._renderer.type].render.call(fill, ctx, elem);
|
||
ctx.fillStyle = fill._renderer.effect;
|
||
}
|
||
}
|
||
if (stroke) {
|
||
if (_.isString(stroke)) {
|
||
ctx.strokeStyle = stroke;
|
||
} else {
|
||
webgl[stroke._renderer.type].render.call(stroke, ctx, elem);
|
||
ctx.strokeStyle = stroke._renderer.effect;
|
||
}
|
||
}
|
||
if (linewidth) {
|
||
ctx.lineWidth = linewidth;
|
||
}
|
||
if (miter) {
|
||
ctx.miterLimit = miter;
|
||
}
|
||
if (join) {
|
||
ctx.lineJoin = join;
|
||
}
|
||
if (cap) {
|
||
ctx.lineCap = cap;
|
||
}
|
||
if (_.isNumber(opacity)) {
|
||
ctx.globalAlpha = opacity;
|
||
}
|
||
|
||
var d;
|
||
ctx.save();
|
||
ctx.scale(scale, scale);
|
||
ctx.translate(cx, cy);
|
||
|
||
ctx.beginPath();
|
||
for (var i = 0; i < commands.length; i++) {
|
||
|
||
b = commands[i];
|
||
|
||
x = toFixed(b._x);
|
||
y = toFixed(b._y);
|
||
|
||
switch (b._command) {
|
||
|
||
case Two.Commands.close:
|
||
ctx.closePath();
|
||
break;
|
||
|
||
case Two.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) || Two.Vector.zero;
|
||
bl = (b.controls && b.controls.left) || Two.Vector.zero;
|
||
|
||
if (a._relative) {
|
||
vx = toFixed((ar.x + a._x));
|
||
vy = toFixed((ar.y + a._y));
|
||
} else {
|
||
vx = toFixed(ar.x);
|
||
vy = toFixed(ar.y);
|
||
}
|
||
|
||
if (b._relative) {
|
||
ux = toFixed((bl.x + b._x));
|
||
uy = toFixed((bl.y + b._y));
|
||
} else {
|
||
ux = toFixed(bl.x);
|
||
uy = toFixed(bl.y);
|
||
}
|
||
|
||
ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
|
||
|
||
if (i >= last && closed) {
|
||
|
||
c = d;
|
||
|
||
br = (b.controls && b.controls.right) || Two.Vector.zero;
|
||
cl = (c.controls && c.controls.left) || Two.Vector.zero;
|
||
|
||
if (b._relative) {
|
||
vx = toFixed((br.x + b._x));
|
||
vy = toFixed((br.y + b._y));
|
||
} else {
|
||
vx = toFixed(br.x);
|
||
vy = toFixed(br.y);
|
||
}
|
||
|
||
if (c._relative) {
|
||
ux = toFixed((cl.x + c._x));
|
||
uy = toFixed((cl.y + c._y));
|
||
} else {
|
||
ux = toFixed(cl.x);
|
||
uy = toFixed(cl.y);
|
||
}
|
||
|
||
x = toFixed(c._x);
|
||
y = toFixed(c._y);
|
||
|
||
ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
|
||
|
||
}
|
||
|
||
break;
|
||
|
||
case Two.Commands.line:
|
||
ctx.lineTo(x, y);
|
||
break;
|
||
|
||
case Two.Commands.move:
|
||
d = b;
|
||
ctx.moveTo(x, y);
|
||
break;
|
||
|
||
}
|
||
|
||
}
|
||
|
||
// Loose ends
|
||
|
||
if (closed) {
|
||
ctx.closePath();
|
||
}
|
||
|
||
if (!webgl.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 (!webgl.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();
|
||
}
|
||
}
|
||
|
||
ctx.restore();
|
||
|
||
},
|
||
|
||
/**
|
||
* Returns the rect of a set of verts. Typically takes vertices that are
|
||
* "centered" around 0 and returns them to be anchored upper-left.
|
||
*/
|
||
getBoundingClientRect: function(vertices, border, rect) {
|
||
|
||
var left = Infinity, right = -Infinity,
|
||
top = Infinity, bottom = -Infinity,
|
||
width, height;
|
||
|
||
vertices.forEach(function(v) {
|
||
|
||
var x = v.x, y = v.y, controls = v.controls;
|
||
var a, b, c, d, cl, cr;
|
||
|
||
top = Math.min(y, top);
|
||
left = Math.min(x, left);
|
||
right = Math.max(x, right);
|
||
bottom = Math.max(y, bottom);
|
||
|
||
if (!v.controls) {
|
||
return;
|
||
}
|
||
|
||
cl = controls.left;
|
||
cr = controls.right;
|
||
|
||
if (!cl || !cr) {
|
||
return;
|
||
}
|
||
|
||
a = v._relative ? cl.x + x : cl.x;
|
||
b = v._relative ? cl.y + y : cl.y;
|
||
c = v._relative ? cr.x + x : cr.x;
|
||
d = v._relative ? cr.y + y : cr.y;
|
||
|
||
if (!a || !b || !c || !d) {
|
||
return;
|
||
}
|
||
|
||
top = Math.min(b, d, top);
|
||
left = Math.min(a, c, left);
|
||
right = Math.max(a, c, right);
|
||
bottom = Math.max(b, d, bottom);
|
||
|
||
});
|
||
|
||
// Expand borders
|
||
|
||
if (_.isNumber(border)) {
|
||
top -= border;
|
||
left -= border;
|
||
right += border;
|
||
bottom += border;
|
||
}
|
||
|
||
width = right - left;
|
||
height = bottom - top;
|
||
|
||
rect.top = top;
|
||
rect.left = left;
|
||
rect.right = right;
|
||
rect.bottom = bottom;
|
||
rect.width = width;
|
||
rect.height = height;
|
||
|
||
if (!rect.centroid) {
|
||
rect.centroid = {};
|
||
}
|
||
|
||
rect.centroid.x = - left;
|
||
rect.centroid.y = - top;
|
||
|
||
},
|
||
|
||
render: function(gl, program, forcedParent) {
|
||
|
||
if (!this._visible || !this._opacity) {
|
||
return this;
|
||
}
|
||
|
||
this._update();
|
||
|
||
// Calculate what changed
|
||
|
||
var parent = this.parent;
|
||
var flagParentMatrix = parent._matrix.manual || parent._flagMatrix;
|
||
var flagMatrix = this._matrix.manual || this._flagMatrix;
|
||
var flagTexture = this._flagVertices || this._flagFill
|
||
|| (this._fill instanceof Two.LinearGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagEndPoints))
|
||
|| (this._fill instanceof Two.RadialGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagRadius || this._fill._flagCenter || this._fill._flagFocal))
|
||
|| (this._fill instanceof Two.Texture && (this._fill._flagLoaded && this._fill.loaded || this._fill._flagOffset || this._fill._flagScale))
|
||
|| (this._stroke instanceof Two.LinearGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagEndPoints))
|
||
|| (this._stroke instanceof Two.RadialGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagRadius || this._stroke._flagCenter || this._stroke._flagFocal))
|
||
|| (this._stroke instanceof Two.Texture && (this._stroke._flagLoaded && this._stroke.loaded || this._stroke._flagOffset || this._fill._flagScale))
|
||
|| this._flagStroke || this._flagLinewidth || this._flagOpacity
|
||
|| parent._flagOpacity || this._flagVisible || this._flagCap
|
||
|| this._flagJoin || this._flagMiter || this._flagScale
|
||
|| !this._renderer.texture;
|
||
|
||
if (flagParentMatrix || flagMatrix) {
|
||
|
||
if (!this._renderer.matrix) {
|
||
this._renderer.matrix = new Two.Array(9);
|
||
}
|
||
|
||
// Reduce amount of object / array creation / deletion
|
||
|
||
this._matrix.toArray(true, transformation);
|
||
|
||
multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix);
|
||
this._renderer.scale = this._scale * parent._renderer.scale;
|
||
|
||
}
|
||
|
||
if (flagTexture) {
|
||
|
||
if (!this._renderer.rect) {
|
||
this._renderer.rect = {};
|
||
}
|
||
|
||
if (!this._renderer.triangles) {
|
||
this._renderer.triangles = new Two.Array(12);
|
||
}
|
||
|
||
this._renderer.opacity = this._opacity * parent._renderer.opacity;
|
||
|
||
webgl.path.getBoundingClientRect(this._vertices, this._linewidth, this._renderer.rect);
|
||
webgl.getTriangles(this._renderer.rect, this._renderer.triangles);
|
||
|
||
webgl.updateBuffer.call(webgl, gl, this, program);
|
||
webgl.updateTexture.call(webgl, gl, this);
|
||
|
||
}
|
||
|
||
// if (this._mask) {
|
||
// webgl[this._mask._renderer.type].render.call(mask, gl, program, this);
|
||
// }
|
||
|
||
if (this._clip && !forcedParent) {
|
||
return;
|
||
}
|
||
|
||
// Draw Texture
|
||
|
||
gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.textureCoordsBuffer);
|
||
|
||
gl.vertexAttribPointer(program.textureCoords, 2, gl.FLOAT, false, 0, 0);
|
||
|
||
gl.bindTexture(gl.TEXTURE_2D, this._renderer.texture);
|
||
|
||
|
||
// Draw Rect
|
||
|
||
gl.uniformMatrix3fv(program.matrix, false, this._renderer.matrix);
|
||
|
||
gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.buffer);
|
||
|
||
gl.vertexAttribPointer(program.position, 2, gl.FLOAT, false, 0, 0);
|
||
|
||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||
|
||
return this.flagReset();
|
||
|
||
}
|
||
|
||
},
|
||
|
||
text: {
|
||
|
||
updateCanvas: function(elem) {
|
||
|
||
var canvas = this.canvas;
|
||
var ctx = this.ctx;
|
||
|
||
// Styles
|
||
var scale = elem._renderer.scale;
|
||
var stroke = elem._stroke;
|
||
var linewidth = elem._linewidth * scale;
|
||
var fill = elem._fill;
|
||
var opacity = elem._renderer.opacity || elem._opacity;
|
||
|
||
canvas.width = Math.max(Math.ceil(elem._renderer.rect.width * scale), 1);
|
||
canvas.height = Math.max(Math.ceil(elem._renderer.rect.height * scale), 1);
|
||
|
||
var centroid = elem._renderer.rect.centroid;
|
||
var cx = centroid.x;
|
||
var cy = centroid.y;
|
||
|
||
var a, b, c, d, e, sx, sy;
|
||
var isOffset = fill._renderer && fill._renderer.offset
|
||
&& stroke._renderer && stroke._renderer.offset;
|
||
|
||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||
|
||
if (!isOffset) {
|
||
ctx.font = [elem._style, elem._weight, elem._size + 'px/' +
|
||
elem._leading + 'px', elem._family].join(' ');
|
||
}
|
||
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'middle';
|
||
|
||
// Styles
|
||
if (fill) {
|
||
if (_.isString(fill)) {
|
||
ctx.fillStyle = fill;
|
||
} else {
|
||
webgl[fill._renderer.type].render.call(fill, ctx, elem);
|
||
ctx.fillStyle = fill._renderer.effect;
|
||
}
|
||
}
|
||
if (stroke) {
|
||
if (_.isString(stroke)) {
|
||
ctx.strokeStyle = stroke;
|
||
} else {
|
||
webgl[stroke._renderer.type].render.call(stroke, ctx, elem);
|
||
ctx.strokeStyle = stroke._renderer.effect;
|
||
}
|
||
}
|
||
if (linewidth) {
|
||
ctx.lineWidth = linewidth;
|
||
}
|
||
if (_.isNumber(opacity)) {
|
||
ctx.globalAlpha = opacity;
|
||
}
|
||
|
||
ctx.save();
|
||
ctx.scale(scale, scale);
|
||
ctx.translate(cx, cy);
|
||
|
||
if (!webgl.isHidden.test(fill)) {
|
||
|
||
if (fill._renderer && fill._renderer.offset) {
|
||
|
||
sx = toFixed(fill._renderer.scale.x);
|
||
sy = toFixed(fill._renderer.scale.y);
|
||
|
||
ctx.save();
|
||
ctx.translate( - toFixed(fill._renderer.offset.x),
|
||
- toFixed(fill._renderer.offset.y));
|
||
ctx.scale(sx, sy);
|
||
|
||
a = elem._size / fill._renderer.scale.y;
|
||
b = elem._leading / fill._renderer.scale.y;
|
||
ctx.font = [elem._style, elem._weight, toFixed(a) + 'px/',
|
||
toFixed(b) + 'px', elem._family].join(' ');
|
||
|
||
c = fill._renderer.offset.x / fill._renderer.scale.x;
|
||
d = fill._renderer.offset.y / fill._renderer.scale.y;
|
||
|
||
ctx.fillText(elem.value, toFixed(c), toFixed(d));
|
||
ctx.restore();
|
||
|
||
} else {
|
||
ctx.fillText(elem.value, 0, 0);
|
||
}
|
||
|
||
}
|
||
|
||
if (!webgl.isHidden.test(stroke)) {
|
||
|
||
if (stroke._renderer && stroke._renderer.offset) {
|
||
|
||
sx = toFixed(stroke._renderer.scale.x);
|
||
sy = toFixed(stroke._renderer.scale.y);
|
||
|
||
ctx.save();
|
||
ctx.translate(- toFixed(stroke._renderer.offset.x),
|
||
- toFixed(stroke._renderer.offset.y));
|
||
ctx.scale(sx, sy);
|
||
|
||
a = elem._size / stroke._renderer.scale.y;
|
||
b = elem._leading / stroke._renderer.scale.y;
|
||
ctx.font = [elem._style, elem._weight, toFixed(a) + 'px/',
|
||
toFixed(b) + 'px', elem._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 = toFixed(e);
|
||
ctx.strokeText(elem.value, toFixed(c), toFixed(d));
|
||
ctx.restore();
|
||
|
||
} else {
|
||
ctx.strokeText(elem.value, 0, 0);
|
||
}
|
||
|
||
}
|
||
|
||
ctx.restore();
|
||
|
||
},
|
||
|
||
getBoundingClientRect: function(elem, rect) {
|
||
|
||
var ctx = webgl.ctx;
|
||
|
||
ctx.font = [elem._style, elem._weight, elem._size + 'px/' +
|
||
elem._leading + 'px', elem._family].join(' ');
|
||
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = elem._baseline;
|
||
|
||
// TODO: Estimate this better
|
||
var width = ctx.measureText(elem._value).width;
|
||
var height = Math.max(elem._size || elem._leading);
|
||
|
||
if (this._linewidth && !webgl.isHidden.test(this._stroke)) {
|
||
// width += this._linewidth; // TODO: Not sure if the `measure` calcs this.
|
||
height += this._linewidth;
|
||
}
|
||
|
||
var w = width / 2;
|
||
var h = height / 2;
|
||
|
||
switch (webgl.alignments[elem._alignment] || elem._alignment) {
|
||
|
||
case webgl.alignments.left:
|
||
rect.left = 0;
|
||
rect.right = width;
|
||
break;
|
||
case webgl.alignments.right:
|
||
rect.left = - width;
|
||
rect.right = 0;
|
||
break;
|
||
default:
|
||
rect.left = - w;
|
||
rect.right = w;
|
||
}
|
||
|
||
// TODO: Gradients aren't inherited...
|
||
switch (elem._baseline) {
|
||
case 'bottom':
|
||
rect.top = - height;
|
||
rect.bottom = 0;
|
||
break;
|
||
case 'top':
|
||
rect.top = 0;
|
||
rect.bottom = height;
|
||
break;
|
||
default:
|
||
rect.top = - h;
|
||
rect.bottom = h;
|
||
}
|
||
|
||
rect.width = width;
|
||
rect.height = height;
|
||
|
||
if (!rect.centroid) {
|
||
rect.centroid = {};
|
||
}
|
||
|
||
// TODO:
|
||
rect.centroid.x = w;
|
||
rect.centroid.y = h;
|
||
|
||
},
|
||
|
||
render: function(gl, program, forcedParent) {
|
||
|
||
if (!this._visible || !this._opacity) {
|
||
return this;
|
||
}
|
||
|
||
this._update();
|
||
|
||
// Calculate what changed
|
||
|
||
var parent = this.parent;
|
||
var flagParentMatrix = parent._matrix.manual || parent._flagMatrix;
|
||
var flagMatrix = this._matrix.manual || this._flagMatrix;
|
||
var flagTexture = this._flagVertices || this._flagFill
|
||
|| (this._fill instanceof Two.LinearGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagEndPoints))
|
||
|| (this._fill instanceof Two.RadialGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagRadius || this._fill._flagCenter || this._fill._flagFocal))
|
||
|| (this._fill instanceof Two.Texture && (this._fill._flagLoaded && this._fill.loaded))
|
||
|| (this._stroke instanceof Two.LinearGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagEndPoints))
|
||
|| (this._stroke instanceof Two.RadialGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagRadius || this._stroke._flagCenter || this._stroke._flagFocal))
|
||
|| (this._texture instanceof Two.Texture && (this._texture._flagLoaded && this._texture.loaded))
|
||
|| this._flagStroke || this._flagLinewidth || this._flagOpacity
|
||
|| parent._flagOpacity || this._flagVisible || this._flagScale
|
||
|| this._flagValue || this._flagFamily || this._flagSize
|
||
|| this._flagLeading || this._flagAlignment || this._flagBaseline
|
||
|| this._flagStyle || this._flagWeight || this._flagDecoration
|
||
|| !this._renderer.texture;
|
||
|
||
if (flagParentMatrix || flagMatrix) {
|
||
|
||
if (!this._renderer.matrix) {
|
||
this._renderer.matrix = new Two.Array(9);
|
||
}
|
||
|
||
// Reduce amount of object / array creation / deletion
|
||
|
||
this._matrix.toArray(true, transformation);
|
||
|
||
multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix);
|
||
this._renderer.scale = this._scale * parent._renderer.scale;
|
||
|
||
}
|
||
|
||
if (flagTexture) {
|
||
|
||
if (!this._renderer.rect) {
|
||
this._renderer.rect = {};
|
||
}
|
||
|
||
if (!this._renderer.triangles) {
|
||
this._renderer.triangles = new Two.Array(12);
|
||
}
|
||
|
||
this._renderer.opacity = this._opacity * parent._renderer.opacity;
|
||
|
||
webgl.text.getBoundingClientRect(this, this._renderer.rect);
|
||
webgl.getTriangles(this._renderer.rect, this._renderer.triangles);
|
||
|
||
webgl.updateBuffer.call(webgl, gl, this, program);
|
||
webgl.updateTexture.call(webgl, gl, this);
|
||
|
||
}
|
||
|
||
// if (this._mask) {
|
||
// webgl[this._mask._renderer.type].render.call(mask, gl, program, this);
|
||
// }
|
||
|
||
if (this._clip && !forcedParent) {
|
||
return;
|
||
}
|
||
|
||
// Draw Texture
|
||
|
||
gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.textureCoordsBuffer);
|
||
|
||
gl.vertexAttribPointer(program.textureCoords, 2, gl.FLOAT, false, 0, 0);
|
||
|
||
gl.bindTexture(gl.TEXTURE_2D, this._renderer.texture);
|
||
|
||
|
||
// Draw Rect
|
||
|
||
gl.uniformMatrix3fv(program.matrix, false, this._renderer.matrix);
|
||
|
||
gl.bindBuffer(gl.ARRAY_BUFFER, this._renderer.buffer);
|
||
|
||
gl.vertexAttribPointer(program.position, 2, gl.FLOAT, false, 0, 0);
|
||
|
||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||
|
||
return this.flagReset();
|
||
|
||
}
|
||
|
||
},
|
||
|
||
'linear-gradient': {
|
||
|
||
render: function(ctx, elem) {
|
||
|
||
if (!ctx.canvas.getContext('2d')) {
|
||
return;
|
||
}
|
||
|
||
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, elem) {
|
||
|
||
if (!ctx.canvas.getContext('2d')) {
|
||
return;
|
||
}
|
||
|
||
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, elem) {
|
||
|
||
if (!ctx.canvas.getContext('2d')) {
|
||
return;
|
||
}
|
||
|
||
this._update();
|
||
|
||
var image = this.image;
|
||
var repeat;
|
||
|
||
if (!this._renderer.effect || ((this._flagLoaded || this._flagRepeat) && this.loaded)) {
|
||
this._renderer.effect = ctx.createPattern(image, this._repeat);
|
||
}
|
||
|
||
if (this._flagOffset || this._flagLoaded || this._flagScale) {
|
||
|
||
if (!(this._renderer.offset instanceof Two.Vector)) {
|
||
this._renderer.offset = new Two.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 Two.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 Two.Vector)) {
|
||
this._renderer.scale = new Two.Vector();
|
||
}
|
||
|
||
if (this._scale instanceof Two.Vector) {
|
||
this._renderer.scale.copy(this._scale);
|
||
} else {
|
||
this._renderer.scale.set(this._scale, this._scale);
|
||
}
|
||
|
||
}
|
||
|
||
return this.flagReset();
|
||
|
||
}
|
||
|
||
},
|
||
|
||
getTriangles: function(rect, triangles) {
|
||
|
||
var top = rect.top,
|
||
left = rect.left,
|
||
right = rect.right,
|
||
bottom = rect.bottom;
|
||
|
||
// First Triangle
|
||
|
||
triangles[0] = left;
|
||
triangles[1] = top;
|
||
|
||
triangles[2] = right;
|
||
triangles[3] = top;
|
||
|
||
triangles[4] = left;
|
||
triangles[5] = bottom;
|
||
|
||
// Second Triangle
|
||
|
||
triangles[6] = left;
|
||
triangles[7] = bottom;
|
||
|
||
triangles[8] = right;
|
||
triangles[9] = top;
|
||
|
||
triangles[10] = right;
|
||
triangles[11] = bottom;
|
||
|
||
},
|
||
|
||
updateTexture: function(gl, elem) {
|
||
|
||
this[elem._renderer.type].updateCanvas.call(webgl, elem);
|
||
|
||
if (elem._renderer.texture) {
|
||
gl.deleteTexture(elem._renderer.texture);
|
||
}
|
||
|
||
gl.bindBuffer(gl.ARRAY_BUFFER, elem._renderer.textureCoordsBuffer);
|
||
|
||
// TODO: Is this necessary every time or can we do once?
|
||
// TODO: Create a registry for textures
|
||
elem._renderer.texture = gl.createTexture();
|
||
gl.bindTexture(gl.TEXTURE_2D, elem._renderer.texture);
|
||
|
||
// Set the parameters so we can render any size image.
|
||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||
// gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||
// gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||
// gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||
|
||
if (this.canvas.width <= 0 || this.canvas.height <= 0) {
|
||
return;
|
||
}
|
||
|
||
// Upload the image into the texture.
|
||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.canvas);
|
||
|
||
},
|
||
|
||
updateBuffer: function(gl, elem, program) {
|
||
|
||
if (_.isObject(elem._renderer.buffer)) {
|
||
gl.deleteBuffer(elem._renderer.buffer);
|
||
}
|
||
|
||
elem._renderer.buffer = gl.createBuffer();
|
||
|
||
gl.bindBuffer(gl.ARRAY_BUFFER, elem._renderer.buffer);
|
||
gl.enableVertexAttribArray(program.position);
|
||
|
||
gl.bufferData(gl.ARRAY_BUFFER, elem._renderer.triangles, gl.STATIC_DRAW);
|
||
|
||
if (_.isObject(elem._renderer.textureCoordsBuffer)) {
|
||
gl.deleteBuffer(elem._renderer.textureCoordsBuffer);
|
||
}
|
||
|
||
elem._renderer.textureCoordsBuffer = gl.createBuffer();
|
||
|
||
gl.bindBuffer(gl.ARRAY_BUFFER, elem._renderer.textureCoordsBuffer);
|
||
gl.enableVertexAttribArray(program.textureCoords);
|
||
|
||
gl.bufferData(gl.ARRAY_BUFFER, this.uv, gl.STATIC_DRAW);
|
||
|
||
},
|
||
|
||
program: {
|
||
|
||
create: function(gl, shaders) {
|
||
var program, linked, error;
|
||
program = gl.createProgram();
|
||
_.each(shaders, function(s) {
|
||
gl.attachShader(program, s);
|
||
});
|
||
|
||
gl.linkProgram(program);
|
||
linked = gl.getProgramParameter(program, gl.LINK_STATUS);
|
||
if (!linked) {
|
||
error = gl.getProgramInfoLog(program);
|
||
gl.deleteProgram(program);
|
||
throw new Two.Utils.Error('unable to link program: ' + error);
|
||
}
|
||
|
||
return program;
|
||
|
||
}
|
||
|
||
},
|
||
|
||
shaders: {
|
||
|
||
create: function(gl, source, type) {
|
||
var shader, compiled, error;
|
||
shader = gl.createShader(gl[type]);
|
||
gl.shaderSource(shader, source);
|
||
gl.compileShader(shader);
|
||
|
||
compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
|
||
if (!compiled) {
|
||
error = gl.getShaderInfoLog(shader);
|
||
gl.deleteShader(shader);
|
||
throw new Two.Utils.Error('unable to compile shader ' + shader + ': ' + error);
|
||
}
|
||
|
||
return shader;
|
||
|
||
},
|
||
|
||
types: {
|
||
vertex: 'VERTEX_SHADER',
|
||
fragment: 'FRAGMENT_SHADER'
|
||
},
|
||
|
||
vertex: [
|
||
'attribute vec2 a_position;',
|
||
'attribute vec2 a_textureCoords;',
|
||
'',
|
||
'uniform mat3 u_matrix;',
|
||
'uniform vec2 u_resolution;',
|
||
'',
|
||
'varying vec2 v_textureCoords;',
|
||
'',
|
||
'void main() {',
|
||
' vec2 projected = (u_matrix * vec3(a_position, 1.0)).xy;',
|
||
' vec2 normal = projected / u_resolution;',
|
||
' vec2 clipspace = (normal * 2.0) - 1.0;',
|
||
'',
|
||
' gl_Position = vec4(clipspace * vec2(1.0, -1.0), 0.0, 1.0);',
|
||
' v_textureCoords = a_textureCoords;',
|
||
'}'
|
||
].join('\n'),
|
||
|
||
fragment: [
|
||
'precision mediump float;',
|
||
'',
|
||
'uniform sampler2D u_image;',
|
||
'varying vec2 v_textureCoords;',
|
||
'',
|
||
'void main() {',
|
||
' gl_FragColor = texture2D(u_image, v_textureCoords);',
|
||
'}'
|
||
].join('\n')
|
||
|
||
},
|
||
|
||
TextureRegistry: new Two.Registry()
|
||
|
||
};
|
||
|
||
webgl.ctx = webgl.canvas.getContext('2d');
|
||
|
||
var Renderer = Two[Two.Types.webgl] = function(options) {
|
||
|
||
var params, gl, vs, fs;
|
||
this.domElement = options.domElement || document.createElement('canvas');
|
||
|
||
// Everything drawn on the canvas needs to come from the stage.
|
||
this.scene = new Two.Group();
|
||
this.scene.parent = this;
|
||
|
||
this._renderer = {
|
||
matrix: new Two.Array(identity),
|
||
scale: 1,
|
||
opacity: 1
|
||
};
|
||
this._flagMatrix = true;
|
||
|
||
// http://games.greggman.com/game/webgl-and-alpha/
|
||
// http://www.khronos.org/registry/webgl/specs/latest/#5.2
|
||
params = _.defaults(options || {}, {
|
||
antialias: false,
|
||
alpha: true,
|
||
premultipliedAlpha: true,
|
||
stencil: true,
|
||
preserveDrawingBuffer: true,
|
||
overdraw: false
|
||
});
|
||
|
||
this.overdraw = params.overdraw;
|
||
|
||
gl = this.ctx = this.domElement.getContext('webgl', params) ||
|
||
this.domElement.getContext('experimental-webgl', params);
|
||
|
||
if (!this.ctx) {
|
||
throw new Two.Utils.Error(
|
||
'unable to create a webgl context. Try using another renderer.');
|
||
}
|
||
|
||
// Compile Base Shaders to draw in pixel space.
|
||
vs = webgl.shaders.create(
|
||
gl, webgl.shaders.vertex, webgl.shaders.types.vertex);
|
||
fs = webgl.shaders.create(
|
||
gl, webgl.shaders.fragment, webgl.shaders.types.fragment);
|
||
|
||
this.program = webgl.program.create(gl, [vs, fs]);
|
||
gl.useProgram(this.program);
|
||
|
||
// Create and bind the drawing buffer
|
||
|
||
// look up where the vertex data needs to go.
|
||
this.program.position = gl.getAttribLocation(this.program, 'a_position');
|
||
this.program.matrix = gl.getUniformLocation(this.program, 'u_matrix');
|
||
this.program.textureCoords = gl.getAttribLocation(this.program, 'a_textureCoords');
|
||
|
||
// Copied from Three.js WebGLRenderer
|
||
gl.disable(gl.DEPTH_TEST);
|
||
|
||
// Setup some initial statements of the gl context
|
||
gl.enable(gl.BLEND);
|
||
|
||
// https://code.google.com/p/chromium/issues/detail?id=316393
|
||
// gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, gl.TRUE);
|
||
|
||
gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
|
||
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA,
|
||
gl.ONE, gl.ONE_MINUS_SRC_ALPHA );
|
||
|
||
};
|
||
|
||
_.extend(Renderer, {
|
||
|
||
Utils: webgl
|
||
|
||
});
|
||
|
||
_.extend(Renderer.prototype, Two.Utils.Events, {
|
||
|
||
setSize: function(width, height, ratio) {
|
||
|
||
this.width = width;
|
||
this.height = height;
|
||
|
||
this.ratio = _.isUndefined(ratio) ? getRatio(this.ctx) : ratio;
|
||
|
||
this.domElement.width = width * this.ratio;
|
||
this.domElement.height = height * this.ratio;
|
||
|
||
_.extend(this.domElement.style, {
|
||
width: width + 'px',
|
||
height: height + 'px'
|
||
});
|
||
|
||
width *= this.ratio;
|
||
height *= this.ratio;
|
||
|
||
// Set for this.stage parent scaling to account for HDPI
|
||
this._renderer.matrix[0] = this._renderer.matrix[4] = this._renderer.scale = this.ratio;
|
||
|
||
this._flagMatrix = true;
|
||
|
||
this.ctx.viewport(0, 0, width, height);
|
||
|
||
var resolutionLocation = this.ctx.getUniformLocation(
|
||
this.program, 'u_resolution');
|
||
this.ctx.uniform2f(resolutionLocation, width, height);
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
render: function() {
|
||
|
||
var gl = this.ctx;
|
||
|
||
if (!this.overdraw) {
|
||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||
}
|
||
|
||
webgl.group.render.call(this.scene, gl, this.program);
|
||
this._flagMatrix = false;
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
var _ = Two.Utils;
|
||
|
||
var Shape = Two.Shape = function() {
|
||
|
||
// Private object for renderer specific variables.
|
||
this._renderer = {};
|
||
this._renderer.flagMatrix = _.bind(Shape.FlagMatrix, this);
|
||
this.isShape = true;
|
||
|
||
this.id = Two.Identifier + Two.uniqueId();
|
||
this.classList = [];
|
||
|
||
// Define matrix properties which all inherited
|
||
// objects of Shape have.
|
||
|
||
this._matrix = new Two.Matrix();
|
||
|
||
this.translation = new Two.Vector();
|
||
this.rotation = 0;
|
||
this.scale = 1;
|
||
|
||
};
|
||
|
||
_.extend(Shape, {
|
||
|
||
FlagMatrix: function() {
|
||
this._flagMatrix = true;
|
||
},
|
||
|
||
MakeObservable: function(object) {
|
||
|
||
Object.defineProperty(object, 'translation', {
|
||
enumerable: true,
|
||
get: function() {
|
||
return this._translation;
|
||
},
|
||
set: function(v) {
|
||
if (this._translation) {
|
||
this._translation.unbind(Two.Events.change, this._renderer.flagMatrix);
|
||
}
|
||
this._translation = v;
|
||
this._translation.bind(Two.Events.change, this._renderer.flagMatrix);
|
||
Shape.FlagMatrix.call(this);
|
||
}
|
||
});
|
||
|
||
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 Two.Vector) {
|
||
this._scale.unbind(Two.Events.change, this._renderer.flagMatrix);
|
||
}
|
||
|
||
this._scale = v;
|
||
|
||
if (this._scale instanceof Two.Vector) {
|
||
this._scale.bind(Two.Events.change, this._renderer.flagMatrix);
|
||
}
|
||
|
||
this._flagMatrix = true;
|
||
this._flagScale = true;
|
||
|
||
}
|
||
});
|
||
|
||
}
|
||
|
||
});
|
||
|
||
_.extend(Shape.prototype, Two.Utils.Events, {
|
||
|
||
// Flags
|
||
|
||
_flagMatrix: true,
|
||
_flagScale: false,
|
||
|
||
// _flagMask: false,
|
||
// _flagClip: false,
|
||
|
||
// Underlying Properties
|
||
|
||
_rotation: 0,
|
||
_scale: 1,
|
||
_translation: null,
|
||
|
||
// _mask: null,
|
||
// _clip: false,
|
||
|
||
addTo: function(group) {
|
||
group.add(this);
|
||
return this;
|
||
},
|
||
|
||
clone: function() {
|
||
var clone = new Shape();
|
||
clone.translation.copy(this.translation);
|
||
clone.rotation = this.rotation;
|
||
clone.scale = this.scale;
|
||
_.each(Shape.Properties, function(k) {
|
||
clone[k] = this[k];
|
||
}, this);
|
||
return clone._update();
|
||
},
|
||
|
||
/**
|
||
* To be called before render that calculates and collates all information
|
||
* to be as up-to-date as possible for the render. Called once a frame.
|
||
*/
|
||
_update: function(deep) {
|
||
|
||
if (!this._matrix.manual && this._flagMatrix) {
|
||
|
||
this._matrix
|
||
.identity()
|
||
.translate(this.translation.x, this.translation.y);
|
||
|
||
if (this._scale instanceof Two.Vector) {
|
||
this._matrix.scale(this._scale.x, this._scale.y);
|
||
} else {
|
||
this._matrix.scale(this._scale);
|
||
}
|
||
|
||
this._matrix.rotate(this.rotation);
|
||
|
||
}
|
||
|
||
if (deep) {
|
||
// Bubble up to parents mainly for `getBoundingClientRect` method.
|
||
if (this.parent && this.parent._update) {
|
||
this.parent._update();
|
||
}
|
||
}
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
flagReset: function() {
|
||
|
||
this._flagMatrix = this._flagScale = false;
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
Shape.MakeObservable(Shape.prototype);
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
/**
|
||
* Constants
|
||
*/
|
||
|
||
var min = Math.min, max = Math.max, round = Math.round,
|
||
getComputedMatrix = Two.Utils.getComputedMatrix;
|
||
|
||
var commands = {};
|
||
var _ = Two.Utils;
|
||
|
||
_.each(Two.Commands, function(v, k) {
|
||
commands[k] = new RegExp(v);
|
||
});
|
||
|
||
var Path = Two.Path = function(vertices, closed, curved, manual) {
|
||
|
||
Two.Shape.call(this);
|
||
|
||
this._renderer.type = 'path';
|
||
this._renderer.flagVertices = _.bind(Path.FlagVertices, this);
|
||
this._renderer.bindVertices = _.bind(Path.BindVertices, this);
|
||
this._renderer.unbindVertices = _.bind(Path.UnbindVertices, this);
|
||
|
||
this._renderer.flagFill = _.bind(Path.FlagFill, this);
|
||
this._renderer.flagStroke = _.bind(Path.FlagStroke, this);
|
||
|
||
this._closed = !!closed;
|
||
this._curved = !!curved;
|
||
|
||
this.beginning = 0;
|
||
this.ending = 1;
|
||
|
||
// Style properties
|
||
|
||
this.fill = '#fff';
|
||
this.stroke = '#000';
|
||
this.linewidth = 1.0;
|
||
this.opacity = 1.0;
|
||
this.visible = true;
|
||
|
||
this.cap = 'butt'; // Default of Adobe Illustrator
|
||
this.join = 'miter'; // Default of Adobe Illustrator
|
||
this.miter = 4; // Default of Adobe Illustrator
|
||
|
||
this._vertices = [];
|
||
this.vertices = vertices;
|
||
|
||
// Determines whether or not two.js should calculate curves, lines, and
|
||
// commands automatically for you or to let the developer manipulate them
|
||
// for themselves.
|
||
this.automatic = !manual;
|
||
|
||
};
|
||
|
||
_.extend(Path, {
|
||
|
||
Properties: [
|
||
'fill',
|
||
'stroke',
|
||
'linewidth',
|
||
'opacity',
|
||
'visible',
|
||
'cap',
|
||
'join',
|
||
'miter',
|
||
|
||
'closed',
|
||
'curved',
|
||
'automatic',
|
||
'beginning',
|
||
'ending'
|
||
],
|
||
|
||
FlagVertices: function() {
|
||
this._flagVertices = true;
|
||
this._flagLength = true;
|
||
},
|
||
|
||
BindVertices: function(items) {
|
||
|
||
// This function is called a lot
|
||
// when importing a large SVG
|
||
var i = items.length;
|
||
while (i--) {
|
||
items[i].bind(Two.Events.change, this._renderer.flagVertices);
|
||
}
|
||
|
||
this._renderer.flagVertices();
|
||
|
||
},
|
||
|
||
UnbindVertices: function(items) {
|
||
|
||
var i = items.length;
|
||
while (i--) {
|
||
items[i].unbind(Two.Events.change, this._renderer.flagVertices);
|
||
}
|
||
|
||
this._renderer.flagVertices();
|
||
|
||
},
|
||
|
||
FlagFill: function() {
|
||
this._flagFill = true;
|
||
},
|
||
|
||
FlagStroke: function() {
|
||
this._flagStroke = true;
|
||
},
|
||
|
||
MakeObservable: function(object) {
|
||
|
||
Two.Shape.MakeObservable(object);
|
||
|
||
// Only the 6 defined properties are flagged like this. The subsequent
|
||
// properties behave differently and need to be hand written.
|
||
_.each(Path.Properties.slice(2, 8), Two.Utils.defineProperty, object);
|
||
|
||
Object.defineProperty(object, 'fill', {
|
||
enumerable: true,
|
||
get: function() {
|
||
return this._fill;
|
||
},
|
||
set: function(f) {
|
||
|
||
if (this._fill instanceof Two.Gradient
|
||
|| this._fill instanceof Two.LinearGradient
|
||
|| this._fill instanceof Two.RadialGradient
|
||
|| this._fill instanceof Two.Texture) {
|
||
this._fill.unbind(Two.Events.change, this._renderer.flagFill);
|
||
}
|
||
|
||
this._fill = f;
|
||
this._flagFill = true;
|
||
|
||
if (this._fill instanceof Two.Gradient
|
||
|| this._fill instanceof Two.LinearGradient
|
||
|| this._fill instanceof Two.RadialGradient
|
||
|| this._fill instanceof Two.Texture) {
|
||
this._fill.bind(Two.Events.change, this._renderer.flagFill);
|
||
}
|
||
|
||
}
|
||
});
|
||
|
||
Object.defineProperty(object, 'stroke', {
|
||
enumerable: true,
|
||
get: function() {
|
||
return this._stroke;
|
||
},
|
||
set: function(f) {
|
||
|
||
if (this._stroke instanceof Two.Gradient
|
||
|| this._stroke instanceof Two.LinearGradient
|
||
|| this._stroke instanceof Two.RadialGradient
|
||
|| this._stroke instanceof Two.Texture) {
|
||
this._stroke.unbind(Two.Events.change, this._renderer.flagStroke);
|
||
}
|
||
|
||
this._stroke = f;
|
||
this._flagStroke = true;
|
||
|
||
if (this._stroke instanceof Two.Gradient
|
||
|| this._stroke instanceof Two.LinearGradient
|
||
|| this._stroke instanceof Two.RadialGradient
|
||
|| this._stroke instanceof Two.Texture) {
|
||
this._stroke.bind(Two.Events.change, this._renderer.flagStroke);
|
||
}
|
||
|
||
}
|
||
});
|
||
|
||
Object.defineProperty(object, 'length', {
|
||
get: function() {
|
||
if (this._flagLength) {
|
||
this._updateLength();
|
||
}
|
||
return this._length;
|
||
}
|
||
});
|
||
|
||
Object.defineProperty(object, 'closed', {
|
||
enumerable: true,
|
||
get: function() {
|
||
return this._closed;
|
||
},
|
||
set: function(v) {
|
||
this._closed = !!v;
|
||
this._flagVertices = true;
|
||
}
|
||
});
|
||
|
||
Object.defineProperty(object, 'curved', {
|
||
enumerable: true,
|
||
get: function() {
|
||
return this._curved;
|
||
},
|
||
set: function(v) {
|
||
this._curved = !!v;
|
||
this._flagVertices = true;
|
||
}
|
||
});
|
||
|
||
Object.defineProperty(object, 'automatic', {
|
||
enumerable: true,
|
||
get: function() {
|
||
return this._automatic;
|
||
},
|
||
set: function(v) {
|
||
if (v === this._automatic) {
|
||
return;
|
||
}
|
||
this._automatic = !!v;
|
||
var method = this._automatic ? 'ignore' : 'listen';
|
||
_.each(this.vertices, function(v) {
|
||
v[method]();
|
||
});
|
||
}
|
||
});
|
||
|
||
Object.defineProperty(object, 'beginning', {
|
||
enumerable: true,
|
||
get: function() {
|
||
return this._beginning;
|
||
},
|
||
set: function(v) {
|
||
this._beginning = v;
|
||
this._flagVertices = true;
|
||
}
|
||
});
|
||
|
||
Object.defineProperty(object, 'ending', {
|
||
enumerable: true,
|
||
get: function() {
|
||
return this._ending;
|
||
},
|
||
set: function(v) {
|
||
this._ending = v;
|
||
this._flagVertices = true;
|
||
}
|
||
});
|
||
|
||
Object.defineProperty(object, 'vertices', {
|
||
|
||
enumerable: true,
|
||
|
||
get: function() {
|
||
return this._collection;
|
||
},
|
||
|
||
set: function(vertices) {
|
||
|
||
var updateVertices = this._renderer.flagVertices;
|
||
var bindVertices = this._renderer.bindVertices;
|
||
var unbindVertices = this._renderer.unbindVertices;
|
||
|
||
// Remove previous listeners
|
||
if (this._collection) {
|
||
this._collection
|
||
.unbind(Two.Events.insert, bindVertices)
|
||
.unbind(Two.Events.remove, unbindVertices);
|
||
}
|
||
|
||
// Create new Collection with copy of vertices
|
||
this._collection = new Two.Utils.Collection((vertices || []).slice(0));
|
||
|
||
// Listen for Collection changes and bind / unbind
|
||
this._collection
|
||
.bind(Two.Events.insert, bindVertices)
|
||
.bind(Two.Events.remove, unbindVertices);
|
||
|
||
// Bind Initial Vertices
|
||
bindVertices(this._collection);
|
||
|
||
}
|
||
|
||
});
|
||
|
||
Object.defineProperty(object, 'clip', {
|
||
enumerable: true,
|
||
get: function() {
|
||
return this._clip;
|
||
},
|
||
set: function(v) {
|
||
this._clip = v;
|
||
this._flagClip = true;
|
||
}
|
||
});
|
||
|
||
}
|
||
|
||
});
|
||
|
||
_.extend(Path.prototype, Two.Shape.prototype, {
|
||
|
||
// Flags
|
||
// http://en.wikipedia.org/wiki/Flag
|
||
|
||
_flagVertices: true,
|
||
_flagLength: true,
|
||
|
||
_flagFill: true,
|
||
_flagStroke: true,
|
||
_flagLinewidth: true,
|
||
_flagOpacity: true,
|
||
_flagVisible: true,
|
||
|
||
_flagCap: true,
|
||
_flagJoin: true,
|
||
_flagMiter: true,
|
||
|
||
_flagClip: false,
|
||
|
||
// Underlying Properties
|
||
|
||
_length: 0,
|
||
|
||
_fill: '#fff',
|
||
_stroke: '#000',
|
||
_linewidth: 1.0,
|
||
_opacity: 1.0,
|
||
_visible: true,
|
||
|
||
_cap: 'round',
|
||
_join: 'round',
|
||
_miter: 4,
|
||
|
||
_closed: true,
|
||
_curved: false,
|
||
_automatic: true,
|
||
_beginning: 0,
|
||
_ending: 1.0,
|
||
|
||
_clip: false,
|
||
|
||
clone: function(parent) {
|
||
|
||
parent = parent || this.parent;
|
||
|
||
var points = _.map(this.vertices, function(v) {
|
||
return v.clone();
|
||
});
|
||
|
||
var clone = new Path(points, this.closed, this.curved, !this.automatic);
|
||
|
||
_.each(Two.Path.Properties, function(k) {
|
||
clone[k] = this[k];
|
||
}, this);
|
||
|
||
clone.translation.copy(this.translation);
|
||
clone.rotation = this.rotation;
|
||
clone.scale = this.scale;
|
||
|
||
if (parent) {
|
||
parent.add(clone);
|
||
}
|
||
|
||
return clone;
|
||
|
||
},
|
||
|
||
toObject: function() {
|
||
|
||
var result = {
|
||
vertices: _.map(this.vertices, function(v) {
|
||
return v.toObject();
|
||
})
|
||
};
|
||
|
||
_.each(Two.Shape.Properties, function(k) {
|
||
result[k] = this[k];
|
||
}, this);
|
||
|
||
result.translation = this.translation.toObject;
|
||
result.rotation = this.rotation;
|
||
result.scale = this.scale;
|
||
|
||
return result;
|
||
|
||
},
|
||
|
||
noFill: function() {
|
||
this.fill = 'transparent';
|
||
return this;
|
||
},
|
||
|
||
noStroke: function() {
|
||
this.stroke = 'transparent';
|
||
return this;
|
||
},
|
||
|
||
/**
|
||
* Orient the vertices of the shape to the upper lefthand
|
||
* corner of the path.
|
||
*/
|
||
corner: function() {
|
||
|
||
var rect = this.getBoundingClientRect(true);
|
||
|
||
rect.centroid = {
|
||
x: rect.left + rect.width / 2,
|
||
y: rect.top + rect.height / 2
|
||
};
|
||
|
||
_.each(this.vertices, function(v) {
|
||
v.addSelf(rect.centroid);
|
||
});
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
/**
|
||
* Orient the vertices of the shape to the center of the
|
||
* path.
|
||
*/
|
||
center: function() {
|
||
|
||
var rect = this.getBoundingClientRect(true);
|
||
|
||
rect.centroid = {
|
||
x: rect.left + rect.width / 2,
|
||
y: rect.top + rect.height / 2
|
||
};
|
||
|
||
_.each(this.vertices, function(v) {
|
||
v.subSelf(rect.centroid);
|
||
});
|
||
|
||
// this.translation.addSelf(rect.centroid);
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
/**
|
||
* Remove self from the scene / parent.
|
||
*/
|
||
remove: function() {
|
||
|
||
if (!this.parent) {
|
||
return this;
|
||
}
|
||
|
||
this.parent.remove(this);
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
/**
|
||
* Return an object with top, left, right, bottom, width, and height
|
||
* parameters of the group.
|
||
*/
|
||
getBoundingClientRect: function(shallow) {
|
||
var matrix, border, l, x, y, i, v;
|
||
|
||
var left = Infinity, right = -Infinity,
|
||
top = Infinity, bottom = -Infinity;
|
||
|
||
// TODO: Update this to not __always__ update. Just when it needs to.
|
||
this._update(true);
|
||
|
||
matrix = !!shallow ? this._matrix : getComputedMatrix(this);
|
||
|
||
border = this.linewidth / 2;
|
||
l = this._vertices.length;
|
||
|
||
if (l <= 0) {
|
||
v = matrix.multiply(0, 0, 1);
|
||
return {
|
||
top: v.y,
|
||
left: v.x,
|
||
right: v.x,
|
||
bottom: v.y,
|
||
width: 0,
|
||
height: 0
|
||
};
|
||
}
|
||
|
||
for (i = 0; i < l; i++) {
|
||
v = this._vertices[i];
|
||
|
||
x = v.x;
|
||
y = v.y;
|
||
|
||
v = matrix.multiply(x, y, 1);
|
||
top = min(v.y - border, top);
|
||
left = min(v.x - border, left);
|
||
right = max(v.x + border, right);
|
||
bottom = max(v.y + border, bottom);
|
||
}
|
||
|
||
return {
|
||
top: top,
|
||
left: left,
|
||
right: right,
|
||
bottom: bottom,
|
||
width: right - left,
|
||
height: bottom - top
|
||
};
|
||
|
||
},
|
||
|
||
/**
|
||
* Given a float `t` from 0 to 1, return a point or assign a passed `obj`'s
|
||
* coordinates to that percentage on this Two.Path's curve.
|
||
*/
|
||
getPointAt: function(t, obj) {
|
||
var ia, ib;
|
||
var x, x1, x2, x3, x4, y, y1, y2, y3, y4, left, right;
|
||
var target = this.length * Math.min(Math.max(t, 0), 1);
|
||
var length = this.vertices.length;
|
||
var last = length - 1;
|
||
|
||
var a = null;
|
||
var b = null;
|
||
|
||
for (var i = 0, l = this._lengths.length, sum = 0; i < l; i++) {
|
||
|
||
if (sum + this._lengths[i] >= target) {
|
||
|
||
if (this._closed) {
|
||
ia = Two.Utils.mod(i, length);
|
||
ib = Two.Utils.mod(i - 1, length);
|
||
if (i === 0) {
|
||
ia = ib;
|
||
ib = i;
|
||
}
|
||
} else {
|
||
ia = i;
|
||
ib = Math.min(Math.max(i - 1, 0), last);
|
||
}
|
||
|
||
a = this.vertices[ia];
|
||
b = this.vertices[ib];
|
||
target -= sum;
|
||
if (this._lengths[i] !== 0) {
|
||
t = target / this._lengths[i];
|
||
}
|
||
|
||
break;
|
||
|
||
}
|
||
|
||
sum += this._lengths[i];
|
||
|
||
}
|
||
|
||
// console.log(sum, a.command, b.command);
|
||
|
||
if (_.isNull(a) || _.isNull(b)) {
|
||
return null;
|
||
}
|
||
|
||
right = b.controls && b.controls.right;
|
||
left = a.controls && a.controls.left;
|
||
|
||
x1 = b.x;
|
||
y1 = b.y;
|
||
x2 = (right || b).x;
|
||
y2 = (right || b).y;
|
||
x3 = (left || a).x;
|
||
y3 = (left || a).y;
|
||
x4 = a.x;
|
||
y4 = a.y;
|
||
|
||
if (right && b._relative) {
|
||
x2 += b.x;
|
||
y2 += b.y;
|
||
}
|
||
|
||
if (left && a._relative) {
|
||
x3 += a.x;
|
||
y3 += a.y;
|
||
}
|
||
|
||
x = Two.Utils.getPointOnCubicBezier(t, x1, x2, x3, x4);
|
||
y = Two.Utils.getPointOnCubicBezier(t, y1, y2, y3, y4);
|
||
|
||
if (_.isObject(obj)) {
|
||
obj.x = x;
|
||
obj.y = y;
|
||
return obj;
|
||
}
|
||
|
||
return new Two.Vector(x, y);
|
||
|
||
},
|
||
|
||
/**
|
||
* Based on closed / curved and sorting of vertices plot where all points
|
||
* should be and where the respective handles should be too.
|
||
*/
|
||
plot: function() {
|
||
|
||
if (this.curved) {
|
||
Two.Utils.getCurveFromPoints(this._vertices, this.closed);
|
||
return this;
|
||
}
|
||
|
||
for (var i = 0; i < this._vertices.length; i++) {
|
||
this._vertices[i]._command = i === 0 ? Two.Commands.move : Two.Commands.line;
|
||
}
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
subdivide: function(limit) {
|
||
//TODO: DRYness (function below)
|
||
this._update();
|
||
|
||
var last = this.vertices.length - 1;
|
||
var b = this.vertices[last];
|
||
var closed = this._closed || this.vertices[last]._command === Two.Commands.close;
|
||
var points = [];
|
||
_.each(this.vertices, function(a, i) {
|
||
|
||
if (i <= 0 && !closed) {
|
||
b = a;
|
||
return;
|
||
}
|
||
|
||
if (a.command === Two.Commands.move) {
|
||
points.push(new Two.Anchor(b.x, b.y));
|
||
if (i > 0) {
|
||
points[points.length - 1].command = Two.Commands.line;
|
||
}
|
||
b = a;
|
||
return;
|
||
}
|
||
|
||
var verts = getSubdivisions(a, b, limit);
|
||
points = points.concat(verts);
|
||
|
||
// Assign commands to all the verts
|
||
_.each(verts, function(v, i) {
|
||
if (i <= 0 && b.command === Two.Commands.move) {
|
||
v.command = Two.Commands.move;
|
||
} else {
|
||
v.command = Two.Commands.line;
|
||
}
|
||
});
|
||
|
||
if (i >= last) {
|
||
|
||
// TODO: Add check if the two vectors in question are the same values.
|
||
if (this._closed && this._automatic) {
|
||
|
||
b = a;
|
||
|
||
verts = getSubdivisions(a, b, limit);
|
||
points = points.concat(verts);
|
||
|
||
// Assign commands to all the verts
|
||
_.each(verts, function(v, i) {
|
||
if (i <= 0 && b.command === Two.Commands.move) {
|
||
v.command = Two.Commands.move;
|
||
} else {
|
||
v.command = Two.Commands.line;
|
||
}
|
||
});
|
||
|
||
} else if (closed) {
|
||
points.push(new Two.Anchor(a.x, a.y));
|
||
}
|
||
|
||
points[points.length - 1].command = closed ? Two.Commands.close : Two.Commands.line;
|
||
|
||
}
|
||
|
||
b = a;
|
||
|
||
}, this);
|
||
|
||
this._automatic = false;
|
||
this._curved = false;
|
||
this.vertices = points;
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
_updateLength: function(limit) {
|
||
//TODO: DRYness (function above)
|
||
this._update();
|
||
|
||
var length = this.vertices.length;
|
||
var last = length - 1;
|
||
var b = this.vertices[last];
|
||
var closed = this._closed || this.vertices[last]._command === Two.Commands.close;
|
||
var sum = 0;
|
||
|
||
if (_.isUndefined(this._lengths)) {
|
||
this._lengths = [];
|
||
}
|
||
|
||
_.each(this.vertices, function(a, i) {
|
||
|
||
if ((i <= 0 && !closed) || a.command === Two.Commands.move) {
|
||
b = a;
|
||
this._lengths[i] = 0;
|
||
return;
|
||
}
|
||
|
||
this._lengths[i] = getCurveLength(a, b, limit);
|
||
sum += this._lengths[i];
|
||
|
||
if (i >= last && closed) {
|
||
|
||
b = this.vertices[(i + 1) % length];
|
||
|
||
this._lengths[i + 1] = getCurveLength(a, b, limit);
|
||
sum += this._lengths[i + 1];
|
||
|
||
}
|
||
|
||
b = a;
|
||
|
||
}, this);
|
||
|
||
this._length = sum;
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
_update: function() {
|
||
|
||
if (this._flagVertices) {
|
||
|
||
var l = this.vertices.length;
|
||
var last = l - 1, v;
|
||
|
||
// TODO: Should clamp this so that `ia` and `ib`
|
||
// cannot select non-verts.
|
||
var ia = round((this._beginning) * last);
|
||
var ib = round((this._ending) * last);
|
||
|
||
this._vertices.length = 0;
|
||
|
||
for (var i = ia; i < ib + 1; i++) {
|
||
v = this.vertices[i];
|
||
this._vertices.push(v);
|
||
}
|
||
|
||
if (this._automatic) {
|
||
this.plot();
|
||
}
|
||
|
||
}
|
||
|
||
Two.Shape.prototype._update.apply(this, arguments);
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
flagReset: function() {
|
||
|
||
this._flagVertices = this._flagFill = this._flagStroke =
|
||
this._flagLinewidth = this._flagOpacity = this._flagVisible =
|
||
this._flagCap = this._flagJoin = this._flagMiter =
|
||
this._flagClip = false;
|
||
|
||
Two.Shape.prototype.flagReset.call(this);
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
Path.MakeObservable(Path.prototype);
|
||
|
||
/**
|
||
* Utility functions
|
||
*/
|
||
|
||
function getCurveLength(a, b, limit) {
|
||
// TODO: DRYness
|
||
var x1, x2, x3, x4, y1, y2, y3, y4;
|
||
|
||
var right = b.controls && b.controls.right;
|
||
var left = a.controls && a.controls.left;
|
||
|
||
x1 = b.x;
|
||
y1 = b.y;
|
||
x2 = (right || b).x;
|
||
y2 = (right || b).y;
|
||
x3 = (left || a).x;
|
||
y3 = (left || a).y;
|
||
x4 = a.x;
|
||
y4 = a.y;
|
||
|
||
if (right && b._relative) {
|
||
x2 += b.x;
|
||
y2 += b.y;
|
||
}
|
||
|
||
if (left && a._relative) {
|
||
x3 += a.x;
|
||
y3 += a.y;
|
||
}
|
||
|
||
return Two.Utils.getCurveLength(x1, y1, x2, y2, x3, y3, x4, y4, limit);
|
||
|
||
}
|
||
|
||
function getSubdivisions(a, b, limit) {
|
||
// TODO: DRYness
|
||
var x1, x2, x3, x4, y1, y2, y3, y4;
|
||
|
||
var right = b.controls && b.controls.right;
|
||
var left = a.controls && a.controls.left;
|
||
|
||
x1 = b.x;
|
||
y1 = b.y;
|
||
x2 = (right || b).x;
|
||
y2 = (right || b).y;
|
||
x3 = (left || a).x;
|
||
y3 = (left || a).y;
|
||
x4 = a.x;
|
||
y4 = a.y;
|
||
|
||
if (right && b._relative) {
|
||
x2 += b.x;
|
||
y2 += b.y;
|
||
}
|
||
|
||
if (left && a._relative) {
|
||
x3 += a.x;
|
||
y3 += a.y;
|
||
}
|
||
|
||
return Two.Utils.subdivide(x1, y1, x2, y2, x3, y3, x4, y4, limit);
|
||
|
||
}
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
var Path = Two.Path;
|
||
var _ = Two.Utils;
|
||
|
||
var Line = Two.Line = function(x1, y1, x2, y2) {
|
||
|
||
var width = x2 - x1;
|
||
var height = y2 - y1;
|
||
|
||
var w2 = width / 2;
|
||
var h2 = height / 2;
|
||
|
||
Path.call(this, [
|
||
new Two.Anchor(- w2, - h2),
|
||
new Two.Anchor(w2, h2)
|
||
]);
|
||
|
||
this.translation.set(x1 + w2, y1 + h2);
|
||
|
||
};
|
||
|
||
_.extend(Line.prototype, Path.prototype);
|
||
|
||
Path.MakeObservable(Line.prototype);
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
var Path = Two.Path;
|
||
var _ = Two.Utils;
|
||
|
||
var Rectangle = Two.Rectangle = function(x, y, width, height) {
|
||
|
||
Path.call(this, [
|
||
new Two.Anchor(),
|
||
new Two.Anchor(),
|
||
new Two.Anchor(),
|
||
new Two.Anchor()
|
||
], true);
|
||
|
||
this.width = width;
|
||
this.height = height;
|
||
this._update();
|
||
|
||
this.translation.set(x, y);
|
||
|
||
};
|
||
|
||
_.extend(Rectangle, {
|
||
|
||
Properties: ['width', 'height'],
|
||
|
||
MakeObservable: function(obj) {
|
||
Path.MakeObservable(obj);
|
||
_.each(Rectangle.Properties, Two.Utils.defineProperty, obj);
|
||
}
|
||
|
||
});
|
||
|
||
_.extend(Rectangle.prototype, Path.prototype, {
|
||
|
||
_width: 0,
|
||
_height: 0,
|
||
|
||
_flagWidth: 0,
|
||
_flagHeight: 0,
|
||
|
||
_update: function() {
|
||
|
||
if (this._flagWidth || this._flagHeight) {
|
||
|
||
var xr = this._width / 2;
|
||
var yr = this._height / 2;
|
||
|
||
this.vertices[0].set(-xr, -yr);
|
||
this.vertices[1].set(xr, -yr);
|
||
this.vertices[2].set(xr, yr);
|
||
this.vertices[3].set(-xr, yr);
|
||
|
||
}
|
||
|
||
Path.prototype._update.call(this);
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
flagReset: function() {
|
||
|
||
this._flagWidth = this._flagHeight = false;
|
||
Path.prototype.flagReset.call(this);
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
Rectangle.MakeObservable(Rectangle.prototype);
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin;
|
||
var _ = Two.Utils;
|
||
|
||
var Ellipse = Two.Ellipse = function(ox, oy, rx, ry) {
|
||
|
||
if (!_.isNumber(ry)) {
|
||
ry = rx;
|
||
}
|
||
|
||
var amount = Two.Resolution;
|
||
|
||
var points = _.map(_.range(amount), function(i) {
|
||
return new Two.Anchor();
|
||
}, this);
|
||
|
||
Path.call(this, points, true, true);
|
||
|
||
this.width = rx * 2;
|
||
this.height = ry * 2;
|
||
|
||
this._update();
|
||
this.translation.set(ox, oy);
|
||
|
||
};
|
||
|
||
_.extend(Ellipse, {
|
||
|
||
Properties: ['width', 'height'],
|
||
|
||
MakeObservable: function(obj) {
|
||
|
||
Path.MakeObservable(obj);
|
||
_.each(Ellipse.Properties, Two.Utils.defineProperty, obj);
|
||
|
||
}
|
||
|
||
});
|
||
|
||
_.extend(Ellipse.prototype, Path.prototype, {
|
||
|
||
_width: 0,
|
||
_height: 0,
|
||
|
||
_flagWidth: false,
|
||
_flagHeight: false,
|
||
|
||
_update: function() {
|
||
|
||
if (this._flagWidth || this._flagHeight) {
|
||
for (var i = 0, l = this.vertices.length; i < l; i++) {
|
||
var pct = i / l;
|
||
var theta = pct * TWO_PI;
|
||
var x = this._width * cos(theta) / 2;
|
||
var y = this._height * sin(theta) / 2;
|
||
this.vertices[i].set(x, y);
|
||
}
|
||
}
|
||
|
||
Path.prototype._update.call(this);
|
||
return this;
|
||
|
||
},
|
||
|
||
flagReset: function() {
|
||
|
||
this._flagWidth = this._flagHeight = false;
|
||
|
||
Path.prototype.flagReset.call(this);
|
||
return this;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
Ellipse.MakeObservable(Ellipse.prototype);
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin;
|
||
var _ = Two.Utils;
|
||
|
||
var Circle = Two.Circle = function(ox, oy, r) {
|
||
|
||
var amount = Two.Resolution;
|
||
|
||
var points = _.map(_.range(amount), function(i) {
|
||
return new Two.Anchor();
|
||
}, this);
|
||
|
||
Path.call(this, points, true, true);
|
||
|
||
this.radius = r;
|
||
|
||
this._update();
|
||
this.translation.set(ox, oy);
|
||
|
||
};
|
||
|
||
_.extend(Circle, {
|
||
|
||
Properties: ['radius'],
|
||
|
||
MakeObservable: function(obj) {
|
||
|
||
Path.MakeObservable(obj);
|
||
_.each(Circle.Properties, Two.Utils.defineProperty, obj);
|
||
|
||
}
|
||
|
||
});
|
||
|
||
_.extend(Circle.prototype, Path.prototype, {
|
||
|
||
_radius: 0,
|
||
_flagRadius: false,
|
||
|
||
_update: function() {
|
||
|
||
if (this._flagRadius) {
|
||
for (var i = 0, l = this.vertices.length; i < l; i++) {
|
||
var pct = i / l;
|
||
var theta = pct * TWO_PI;
|
||
var x = this._radius * cos(theta);
|
||
var y = this._radius * sin(theta);
|
||
this.vertices[i].set(x, y);
|
||
}
|
||
}
|
||
|
||
Path.prototype._update.call(this);
|
||
return this;
|
||
|
||
},
|
||
|
||
flagReset: function() {
|
||
|
||
this._flagRadius = false;
|
||
|
||
Path.prototype.flagReset.call(this);
|
||
return this;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
Circle.MakeObservable(Circle.prototype);
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin;
|
||
var _ = Two.Utils;
|
||
|
||
var Polygon = Two.Polygon = function(ox, oy, r, sides) {
|
||
|
||
sides = Math.max(sides || 0, 3);
|
||
|
||
var points = _.map(_.range(sides), function(i) {
|
||
return new Two.Anchor();
|
||
});
|
||
|
||
Path.call(this, points, true);
|
||
|
||
this.width = r * 2;
|
||
this.height = r * 2;
|
||
this.sides = sides;
|
||
|
||
this._update();
|
||
this.translation.set(ox, oy);
|
||
|
||
};
|
||
|
||
_.extend(Polygon, {
|
||
|
||
Properties: ['width', 'height', 'sides'],
|
||
|
||
MakeObservable: function(obj) {
|
||
|
||
Path.MakeObservable(obj);
|
||
_.each(Polygon.Properties, Two.Utils.defineProperty, obj);
|
||
|
||
}
|
||
|
||
});
|
||
|
||
_.extend(Polygon.prototype, Path.prototype, {
|
||
|
||
_width: 0,
|
||
_height: 0,
|
||
_sides: 0,
|
||
|
||
_flagWidth: false,
|
||
_flagHeight: false,
|
||
_flagSides: false,
|
||
|
||
_update: function() {
|
||
|
||
if (this._flagWidth || this._flagHeight || this._flagSides) {
|
||
|
||
var sides = this._sides;
|
||
var amount = this.vertices.length;
|
||
|
||
if (amount > sides) {
|
||
this.vertices.splice(sides - 1, amount - sides);
|
||
}
|
||
|
||
for (var i = 0; i < sides; i++) {
|
||
|
||
var pct = (i + 0.5) / sides;
|
||
var theta = TWO_PI * pct + Math.PI / 2;
|
||
var x = this._width * cos(theta);
|
||
var y = this._height * sin(theta);
|
||
|
||
if (i >= amount) {
|
||
this.vertices.push(new Two.Anchor(x, y));
|
||
} else {
|
||
this.vertices[i].set(x, y);
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
Path.prototype._update.call(this);
|
||
return this;
|
||
|
||
},
|
||
|
||
flagReset: function() {
|
||
|
||
this._flagWidth = this._flagHeight = this._flagSides = false;
|
||
Path.prototype.flagReset.call(this);
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
Polygon.MakeObservable(Polygon.prototype);
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
var Path = Two.Path, PI = Math.PI, TWO_PI = Math.PI * 2, HALF_PI = Math.PI / 2,
|
||
cos = Math.cos, sin = Math.sin, abs = Math.abs, _ = Two.Utils;
|
||
|
||
var ArcSegment = Two.ArcSegment = function(ox, oy, ir, or, sa, ea, res) {
|
||
|
||
var points = _.map(_.range(res || (Two.Resolution * 3)), function() {
|
||
return new Two.Anchor();
|
||
});
|
||
|
||
Path.call(this, points, false, false, true);
|
||
|
||
this.innerRadius = ir;
|
||
this.outerRadius = or;
|
||
|
||
this.startAngle = sa;
|
||
this.endAngle = ea;
|
||
|
||
this._update();
|
||
this.translation.set(ox, oy);
|
||
|
||
}
|
||
|
||
_.extend(ArcSegment, {
|
||
|
||
Properties: ['startAngle', 'endAngle', 'innerRadius', 'outerRadius'],
|
||
|
||
MakeObservable: function(obj) {
|
||
|
||
Path.MakeObservable(obj);
|
||
_.each(ArcSegment.Properties, Two.Utils.defineProperty, obj);
|
||
|
||
}
|
||
|
||
});
|
||
|
||
_.extend(ArcSegment.prototype, Path.prototype, {
|
||
|
||
_flagStartAngle: false,
|
||
_flagEndAngle: false,
|
||
_flagInnerRadius: false,
|
||
_flagOuterRadius: false,
|
||
|
||
_startAngle: 0,
|
||
_endAngle: TWO_PI,
|
||
_innerRadius: 0,
|
||
_outerRadius: 0,
|
||
|
||
_update: function() {
|
||
|
||
if (this._flagStartAngle || this._flagEndAngle || this._flagInnerRadius
|
||
|| this._flagOuterRadius) {
|
||
|
||
var sa = this._startAngle;
|
||
var ea = this._endAngle;
|
||
|
||
var ir = this._innerRadius;
|
||
var or = this._outerRadius;
|
||
|
||
var connected = mod(sa, TWO_PI) === mod(ea, TWO_PI);
|
||
var punctured = ir > 0;
|
||
|
||
var vertices = this.vertices;
|
||
var length = (punctured ? vertices.length / 2 : vertices.length);
|
||
var command, id = 0;
|
||
|
||
if (connected) {
|
||
length--;
|
||
} else if (!punctured) {
|
||
length -= 2;
|
||
}
|
||
|
||
/**
|
||
* Outer Circle
|
||
*/
|
||
for (var i = 0, last = length - 1; i < length; i++) {
|
||
|
||
var pct = i / last;
|
||
var v = vertices[id];
|
||
var theta = pct * (ea - sa) + sa;
|
||
var step = (ea - sa) / length;
|
||
|
||
var x = or * Math.cos(theta);
|
||
var y = or * Math.sin(theta);
|
||
|
||
switch (i) {
|
||
case 0:
|
||
command = Two.Commands.move;
|
||
break;
|
||
default:
|
||
command = Two.Commands.curve;
|
||
}
|
||
|
||
v.command = command;
|
||
v.x = x;
|
||
v.y = y;
|
||
v.controls.left.clear();
|
||
v.controls.right.clear();
|
||
|
||
if (v.command === Two.Commands.curve) {
|
||
var amp = or * step / Math.PI;
|
||
v.controls.left.x = amp * Math.cos(theta - HALF_PI);
|
||
v.controls.left.y = amp * Math.sin(theta - HALF_PI);
|
||
v.controls.right.x = amp * Math.cos(theta + HALF_PI);
|
||
v.controls.right.y = amp * Math.sin(theta + HALF_PI);
|
||
if (i === 1) {
|
||
v.controls.left.multiplyScalar(2);
|
||
}
|
||
if (i === last) {
|
||
v.controls.right.multiplyScalar(2);
|
||
}
|
||
}
|
||
|
||
id++;
|
||
|
||
}
|
||
|
||
if (punctured) {
|
||
|
||
if (connected) {
|
||
vertices[id].command = Two.Commands.close;
|
||
id++;
|
||
} else {
|
||
length--;
|
||
last = length - 1;
|
||
}
|
||
|
||
/**
|
||
* Inner Circle
|
||
*/
|
||
for (i = 0; i < length; i++) {
|
||
|
||
pct = i / last;
|
||
v = vertices[id];
|
||
theta = (1 - pct) * (ea - sa) + sa;
|
||
step = (ea - sa) / length;
|
||
|
||
x = ir * Math.cos(theta);
|
||
y = ir * Math.sin(theta);
|
||
command = Two.Commands.curve;
|
||
if (i <= 0) {
|
||
command = connected ? Two.Commands.move : Two.Commands.line;
|
||
}
|
||
|
||
v.command = command;
|
||
v.x = x;
|
||
v.y = y;
|
||
v.controls.left.clear();
|
||
v.controls.right.clear();
|
||
|
||
if (v.command === Two.Commands.curve) {
|
||
amp = ir * step / Math.PI;
|
||
v.controls.left.x = amp * Math.cos(theta + HALF_PI);
|
||
v.controls.left.y = amp * Math.sin(theta + HALF_PI);
|
||
v.controls.right.x = amp * Math.cos(theta - HALF_PI);
|
||
v.controls.right.y = amp * Math.sin(theta - HALF_PI);
|
||
if (i === 1) {
|
||
v.controls.left.multiplyScalar(2);
|
||
}
|
||
if (i === last) {
|
||
v.controls.right.multiplyScalar(2);
|
||
}
|
||
}
|
||
|
||
id++;
|
||
|
||
}
|
||
|
||
} else if (!connected) {
|
||
|
||
vertices[id].command = Two.Commands.line;
|
||
vertices[id].x = 0;
|
||
vertices[id].y = 0;
|
||
id++;
|
||
|
||
}
|
||
|
||
/**
|
||
* Final Point
|
||
*/
|
||
vertices[id].command = Two.Commands.close;
|
||
|
||
}
|
||
|
||
Path.prototype._update.call(this);
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
flagReset: function() {
|
||
|
||
Path.prototype.flagReset.call(this);
|
||
|
||
this._flagStartAngle = this._flagEndAngle
|
||
= this._flagInnerRadius = this._flagOuterRadius = false;
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
ArcSegment.MakeObservable(ArcSegment.prototype);
|
||
|
||
function mod(v, l) {
|
||
while (v < 0) {
|
||
v += l;
|
||
}
|
||
return v % l;
|
||
}
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
var Path = Two.Path, TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin;
|
||
var _ = Two.Utils;
|
||
|
||
var Star = Two.Star = function(ox, oy, or, ir, sides) {
|
||
|
||
if (!_.isNumber(ir)) {
|
||
ir = or / 2;
|
||
}
|
||
|
||
if (!_.isNumber(sides) || sides <= 0) {
|
||
sides = 5;
|
||
}
|
||
|
||
var length = sides * 2;
|
||
|
||
var points = _.map(_.range(length), function(i) {
|
||
return new Two.Anchor();
|
||
});
|
||
|
||
Path.call(this, points, true);
|
||
|
||
this.innerRadius = ir;
|
||
this.outerRadius = or;
|
||
this.sides = sides;
|
||
|
||
this._update();
|
||
this.translation.set(ox, oy);
|
||
|
||
};
|
||
|
||
_.extend(Star, {
|
||
|
||
Properties: ['innerRadius', 'outerRadius', 'sides'],
|
||
|
||
MakeObservable: function(obj) {
|
||
|
||
Path.MakeObservable(obj);
|
||
_.each(Star.Properties, Two.Utils.defineProperty, obj);
|
||
|
||
}
|
||
|
||
});
|
||
|
||
_.extend(Star.prototype, Path.prototype, {
|
||
|
||
_innerRadius: 0,
|
||
_outerRadius: 0,
|
||
_sides: 0,
|
||
|
||
_flagInnerRadius: false,
|
||
_flagOuterRadius: false,
|
||
_flagSides: false,
|
||
|
||
_update: function() {
|
||
|
||
if (this._flagInnerRadius || this._flagOuterRadius || this._flagSides) {
|
||
|
||
var sides = this._sides * 2;
|
||
var amount = this.vertices.length;
|
||
|
||
if (amount > sides) {
|
||
this.vertices.splice(sides - 1, amount - sides);
|
||
}
|
||
|
||
for (var i = 0; i < sides; i++) {
|
||
|
||
var pct = (i + 0.5) / sides;
|
||
var theta = TWO_PI * pct;
|
||
var r = (i % 2 ? this._innerRadius : this._outerRadius);
|
||
var x = r * cos(theta);
|
||
var y = r * sin(theta);
|
||
|
||
if (i >= amount) {
|
||
this.vertices.push(new Two.Anchor(x, y));
|
||
} else {
|
||
this.vertices[i].set(x, y);
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
Path.prototype._update.call(this);
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
flagReset: function() {
|
||
|
||
this._flagInnerRadius = this._flagOuterRadius = this._flagSides = false;
|
||
Path.prototype.flagReset.call(this);
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
Star.MakeObservable(Star.prototype);
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
var Path = Two.Path;
|
||
var _ = Two.Utils;
|
||
|
||
var RoundedRectangle = Two.RoundedRectangle = function(ox, oy, width, height, radius) {
|
||
|
||
if (!_.isNumber(radius)) {
|
||
radius = Math.floor(Math.min(width, height) / 12);
|
||
}
|
||
|
||
var amount = 10;
|
||
|
||
var points = _.map(_.range(amount), function(i) {
|
||
return new Two.Anchor(0, 0, 0, 0, 0, 0,
|
||
i === 0 ? Two.Commands.move : Two.Commands.curve);
|
||
});
|
||
|
||
points[points.length - 1].command = Two.Commands.close;
|
||
|
||
Path.call(this, points, false, false, true);
|
||
|
||
this.width = width;
|
||
this.height = height;
|
||
this.radius = radius;
|
||
|
||
this._update();
|
||
this.translation.set(ox, oy);
|
||
|
||
};
|
||
|
||
_.extend(RoundedRectangle, {
|
||
|
||
Properties: ['width', 'height', 'radius'],
|
||
|
||
MakeObservable: function(obj) {
|
||
|
||
Path.MakeObservable(obj);
|
||
_.each(RoundedRectangle.Properties, Two.Utils.defineProperty, obj);
|
||
|
||
}
|
||
|
||
});
|
||
|
||
_.extend(RoundedRectangle.prototype, Path.prototype, {
|
||
|
||
_width: 0,
|
||
_height: 0,
|
||
_radius: 0,
|
||
|
||
_flagWidth: false,
|
||
_flagHeight: false,
|
||
_flagRadius: false,
|
||
|
||
_update: function() {
|
||
|
||
if (this._flagWidth || this._flagHeight || this._flagRadius) {
|
||
|
||
var width = this._width;
|
||
var height = this._height;
|
||
var radius = Math.min(Math.max(this._radius, 0),
|
||
Math.min(width, height));
|
||
|
||
var v;
|
||
var w = width / 2;
|
||
var h = height / 2;
|
||
|
||
v = this.vertices[0];
|
||
v.x = - (w - radius);
|
||
v.y = - h;
|
||
|
||
// Upper Right Corner
|
||
|
||
v = this.vertices[1];
|
||
v.x = (w - radius);
|
||
v.y = - h;
|
||
v.controls.left.clear();
|
||
v.controls.right.x = radius;
|
||
v.controls.right.y = 0;
|
||
|
||
v = this.vertices[2];
|
||
v.x = w;
|
||
v.y = - (h - radius);
|
||
v.controls.right.clear();
|
||
v.controls.left.clear();
|
||
|
||
// Bottom Right Corner
|
||
|
||
v = this.vertices[3];
|
||
v.x = w;
|
||
v.y = (h - radius);
|
||
v.controls.left.clear();
|
||
v.controls.right.x = 0;
|
||
v.controls.right.y = radius;
|
||
|
||
v = this.vertices[4];
|
||
v.x = (w - radius);
|
||
v.y = h;
|
||
v.controls.right.clear();
|
||
v.controls.left.clear();
|
||
|
||
// Bottom Left Corner
|
||
|
||
v = this.vertices[5];
|
||
v.x = - (w - radius);
|
||
v.y = h;
|
||
v.controls.left.clear();
|
||
v.controls.right.x = - radius;
|
||
v.controls.right.y = 0;
|
||
|
||
v = this.vertices[6];
|
||
v.x = - w;
|
||
v.y = (h - radius);
|
||
v.controls.left.clear();
|
||
v.controls.right.clear();
|
||
|
||
// Upper Left Corner
|
||
|
||
v = this.vertices[7];
|
||
v.x = - w;
|
||
v.y = - (h - radius);
|
||
v.controls.left.clear();
|
||
v.controls.right.x = 0;
|
||
v.controls.right.y = - radius;
|
||
|
||
v = this.vertices[8];
|
||
v.x = - (w - radius);
|
||
v.y = - h;
|
||
v.controls.left.clear();
|
||
v.controls.right.clear();
|
||
|
||
v = this.vertices[9];
|
||
v.copy(this.vertices[8]);
|
||
|
||
}
|
||
|
||
Path.prototype._update.call(this);
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
flagReset: function() {
|
||
|
||
this._flagWidth = this._flagHeight = this._flagRadius = false;
|
||
Path.prototype.flagReset.call(this);
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
RoundedRectangle.MakeObservable(RoundedRectangle.prototype);
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
var root = Two.root;
|
||
var getComputedMatrix = Two.Utils.getComputedMatrix;
|
||
var _ = Two.Utils;
|
||
|
||
var canvas = (root.document ? root.document.createElement('canvas') : { getContext: _.identity });
|
||
var ctx = canvas.getContext('2d');
|
||
|
||
var Text = Two.Text = function(message, x, y, styles) {
|
||
|
||
Two.Shape.call(this);
|
||
|
||
this._renderer.type = 'text';
|
||
this._renderer.flagFill = _.bind(Text.FlagFill, this);
|
||
this._renderer.flagStroke = _.bind(Text.FlagStroke, this);
|
||
|
||
this.value = message;
|
||
|
||
if (_.isNumber(x)) {
|
||
this.translation.x = x;
|
||
}
|
||
if (_.isNumber(y)) {
|
||
this.translation.y = y;
|
||
}
|
||
|
||
if (!_.isObject(styles)) {
|
||
return this;
|
||
}
|
||
|
||
_.each(Two.Text.Properties, function(property) {
|
||
|
||
if (property in styles) {
|
||
this[property] = styles[property];
|
||
}
|
||
|
||
}, this);
|
||
|
||
};
|
||
|
||
_.extend(Two.Text, {
|
||
|
||
Properties: [
|
||
'value', 'family', 'size', 'leading', 'alignment', 'linewidth', 'style',
|
||
'weight', 'decoration', 'baseline', 'opacity', 'visible', 'fill', 'stroke'
|
||
],
|
||
|
||
FlagFill: function() {
|
||
this._flagFill = true;
|
||
},
|
||
|
||
FlagStroke: function() {
|
||
this._flagStroke = true;
|
||
},
|
||
|
||
MakeObservable: function(object) {
|
||
|
||
Two.Shape.MakeObservable(object);
|
||
|
||
_.each(Two.Text.Properties.slice(0, 12), Two.Utils.defineProperty, object);
|
||
|
||
Object.defineProperty(object, 'fill', {
|
||
enumerable: true,
|
||
get: function() {
|
||
return this._fill;
|
||
},
|
||
set: function(f) {
|
||
|
||
if (this._fill instanceof Two.Gradient
|
||
|| this._fill instanceof Two.LinearGradient
|
||
|| this._fill instanceof Two.RadialGradient
|
||
|| this._fill instanceof Two.Texture) {
|
||
this._fill.unbind(Two.Events.change, this._renderer.flagFill);
|
||
}
|
||
|
||
this._fill = f;
|
||
this._flagFill = true;
|
||
|
||
if (this._fill instanceof Two.Gradient
|
||
|| this._fill instanceof Two.LinearGradient
|
||
|| this._fill instanceof Two.RadialGradient
|
||
|| this._fill instanceof Two.Texture) {
|
||
this._fill.bind(Two.Events.change, this._renderer.flagFill);
|
||
}
|
||
|
||
}
|
||
});
|
||
|
||
Object.defineProperty(object, 'stroke', {
|
||
enumerable: true,
|
||
get: function() {
|
||
return this._stroke;
|
||
},
|
||
set: function(f) {
|
||
|
||
if (this._stroke instanceof Two.Gradient
|
||
|| this._stroke instanceof Two.LinearGradient
|
||
|| this._stroke instanceof Two.RadialGradient
|
||
|| this._stroke instanceof Two.Texture) {
|
||
this._stroke.unbind(Two.Events.change, this._renderer.flagStroke);
|
||
}
|
||
|
||
this._stroke = f;
|
||
this._flagStroke = true;
|
||
|
||
if (this._stroke instanceof Two.Gradient
|
||
|| this._stroke instanceof Two.LinearGradient
|
||
|| this._stroke instanceof Two.RadialGradient
|
||
|| this._stroke instanceof Two.Texture) {
|
||
this._stroke.bind(Two.Events.change, this._renderer.flagStroke);
|
||
}
|
||
|
||
}
|
||
});
|
||
|
||
Object.defineProperty(object, 'clip', {
|
||
enumerable: true,
|
||
get: function() {
|
||
return this._clip;
|
||
},
|
||
set: function(v) {
|
||
this._clip = v;
|
||
this._flagClip = true;
|
||
}
|
||
});
|
||
|
||
}
|
||
|
||
});
|
||
|
||
_.extend(Two.Text.prototype, Two.Shape.prototype, {
|
||
|
||
// Flags
|
||
// http://en.wikipedia.org/wiki/Flag
|
||
|
||
_flagValue: true,
|
||
_flagFamily: true,
|
||
_flagSize: true,
|
||
_flagLeading: true,
|
||
_flagAlignment: true,
|
||
_flagBaseline: true,
|
||
_flagStyle: true,
|
||
_flagWeight: true,
|
||
_flagDecoration: true,
|
||
|
||
_flagFill: true,
|
||
_flagStroke: true,
|
||
_flagLinewidth: true,
|
||
_flagOpacity: true,
|
||
_flagVisible: true,
|
||
|
||
_flagClip: false,
|
||
|
||
// Underlying Properties
|
||
|
||
_value: '',
|
||
_family: 'sans-serif',
|
||
_size: 13,
|
||
_leading: 17,
|
||
_alignment: 'center',
|
||
_baseline: 'middle',
|
||
_style: 'normal',
|
||
_weight: 500,
|
||
_decoration: 'none',
|
||
|
||
_fill: '#000',
|
||
_stroke: 'transparent',
|
||
_linewidth: 1,
|
||
_opacity: 1,
|
||
_visible: true,
|
||
|
||
_clip: false,
|
||
|
||
remove: function() {
|
||
|
||
if (!this.parent) {
|
||
return this;
|
||
}
|
||
|
||
this.parent.remove(this);
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
clone: function(parent) {
|
||
|
||
var parent = parent || this.parent;
|
||
|
||
var clone = new Two.Text(this.value);
|
||
clone.translation.copy(this.translation);
|
||
clone.rotation = this.rotation;
|
||
clone.scale = this.scale;
|
||
|
||
_.each(Two.Text.Properties, function(property) {
|
||
clone[property] = this[property];
|
||
}, this);
|
||
|
||
if (parent) {
|
||
parent.add(clone);
|
||
}
|
||
|
||
return clone;
|
||
|
||
},
|
||
|
||
toObject: function() {
|
||
|
||
var result = {
|
||
translation: this.translation.toObject(),
|
||
rotation: this.rotation,
|
||
scale: this.scale
|
||
};
|
||
|
||
_.each(Two.Text.Properties, function(property) {
|
||
result[property] = this[property];
|
||
}, this);
|
||
|
||
return result;
|
||
|
||
},
|
||
|
||
noStroke: function() {
|
||
this.stroke = 'transparent';
|
||
return this;
|
||
},
|
||
|
||
noFill: function() {
|
||
this.fill = 'transparent';
|
||
return this;
|
||
},
|
||
|
||
/**
|
||
* A shim to not break `getBoundingClientRect` calls. TODO: Implement a
|
||
* way to calculate proper bounding boxes of `Two.Text`.
|
||
*/
|
||
getBoundingClientRect: function(shallow) {
|
||
|
||
var matrix, border, l, x, y, i, v;
|
||
|
||
var left = Infinity, right = -Infinity,
|
||
top = Infinity, bottom = -Infinity;
|
||
|
||
// TODO: Update this to not __always__ update. Just when it needs to.
|
||
this._update(true);
|
||
|
||
matrix = !!shallow ? this._matrix : getComputedMatrix(this);
|
||
|
||
v = matrix.multiply(0, 0, 1);
|
||
|
||
return {
|
||
top: v.x,
|
||
left: v.y,
|
||
right: v.x,
|
||
bottom: v.y,
|
||
width: 0,
|
||
height: 0
|
||
};
|
||
|
||
},
|
||
|
||
flagReset: function() {
|
||
|
||
this._flagValue = this._flagFamily = this._flagSize =
|
||
this._flagLeading = this._flagAlignment = this._flagFill =
|
||
this._flagStroke = this._flagLinewidth = this._flagOpaicty =
|
||
this._flagVisible = this._flagClip = this._flagDecoration =
|
||
this._flagBaseline = false;
|
||
|
||
Two.Shape.prototype.flagReset.call(this);
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
Two.Text.MakeObservable(Two.Text.prototype);
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
var _ = Two.Utils;
|
||
|
||
var Stop = Two.Stop = function(offset, color, opacity) {
|
||
|
||
this._renderer = {};
|
||
this._renderer.type = 'stop';
|
||
|
||
this.offset = _.isNumber(offset) ? offset
|
||
: Stop.Index <= 0 ? 0 : 1;
|
||
|
||
this.opacity = _.isNumber(opacity) ? opacity : 1;
|
||
|
||
this.color = _.isString(color) ? color
|
||
: Stop.Index <= 0 ? '#fff' : '#000';
|
||
|
||
Stop.Index = (Stop.Index + 1) % 2;
|
||
|
||
};
|
||
|
||
_.extend(Stop, {
|
||
|
||
Index: 0,
|
||
|
||
Properties: [
|
||
'offset',
|
||
'opacity',
|
||
'color'
|
||
],
|
||
|
||
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);
|
||
|
||
}
|
||
|
||
});
|
||
|
||
_.extend(Stop.prototype, Two.Utils.Events, {
|
||
|
||
clone: function() {
|
||
|
||
var clone = new Stop();
|
||
|
||
_.each(Stop.Properties, function(property) {
|
||
clone[property] = this[property];
|
||
}, this);
|
||
|
||
return clone;
|
||
|
||
},
|
||
|
||
toObject: function() {
|
||
|
||
var result = {};
|
||
|
||
_.each(Stop.Properties, function(k) {
|
||
result[k] = this[k];
|
||
}, this);
|
||
|
||
return result;
|
||
|
||
},
|
||
|
||
flagReset: function() {
|
||
|
||
this._flagOffset = this._flagColor = this._flagOpacity = false;
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
Stop.MakeObservable(Stop.prototype);
|
||
|
||
var Gradient = Two.Gradient = function(stops) {
|
||
|
||
this._renderer = {};
|
||
this._renderer.type = 'gradient';
|
||
|
||
this.id = Two.Identifier + Two.uniqueId();
|
||
this.classList = [];
|
||
|
||
this._renderer.flagStops = _.bind(Gradient.FlagStops, this);
|
||
this._renderer.bindStops = _.bind(Gradient.BindStops, this);
|
||
this._renderer.unbindStops = _.bind(Gradient.UnbindStops, this);
|
||
|
||
this.spread = 'pad';
|
||
|
||
this.stops = stops;
|
||
|
||
};
|
||
|
||
_.extend(Gradient, {
|
||
|
||
Stop: Stop,
|
||
|
||
Properties: [
|
||
'spread'
|
||
],
|
||
|
||
MakeObservable: function(object) {
|
||
|
||
_.each(Gradient.Properties, Two.Utils.defineProperty, object);
|
||
|
||
Object.defineProperty(object, 'stops', {
|
||
|
||
enumerable: true,
|
||
|
||
get: function() {
|
||
return this._stops;
|
||
},
|
||
|
||
set: function(stops) {
|
||
|
||
var updateStops = this._renderer.flagStops;
|
||
var bindStops = this._renderer.bindStops;
|
||
var unbindStops = this._renderer.unbindStops;
|
||
|
||
// Remove previous listeners
|
||
if (this._stops) {
|
||
this._stops
|
||
.unbind(Two.Events.insert, bindStops)
|
||
.unbind(Two.Events.remove, unbindStops);
|
||
}
|
||
|
||
// Create new Collection with copy of Stops
|
||
this._stops = new Two.Utils.Collection((stops || []).slice(0));
|
||
|
||
// Listen for Collection changes and bind / unbind
|
||
this._stops
|
||
.bind(Two.Events.insert, bindStops)
|
||
.bind(Two.Events.remove, unbindStops);
|
||
|
||
// Bind Initial Stops
|
||
bindStops(this._stops);
|
||
|
||
}
|
||
|
||
});
|
||
|
||
},
|
||
|
||
FlagStops: function() {
|
||
this._flagStops = true;
|
||
},
|
||
|
||
BindStops: function(items) {
|
||
|
||
// This function is called a lot
|
||
// when importing a large SVG
|
||
var i = items.length;
|
||
while(i--) {
|
||
items[i].bind(Two.Events.change, this._renderer.flagStops);
|
||
items[i].parent = this;
|
||
}
|
||
|
||
this._renderer.flagStops();
|
||
|
||
},
|
||
|
||
UnbindStops: function(items) {
|
||
|
||
var i = items.length;
|
||
while(i--) {
|
||
items[i].unbind(Two.Events.change, this._renderer.flagStops);
|
||
delete items[i].parent;
|
||
}
|
||
|
||
this._renderer.flagStops();
|
||
|
||
}
|
||
|
||
});
|
||
|
||
_.extend(Gradient.prototype, Two.Utils.Events, {
|
||
|
||
_flagStops: false,
|
||
_flagSpread: false,
|
||
|
||
clone: function(parent) {
|
||
|
||
parent = parent || this.parent;
|
||
|
||
var stops = _.map(this.stops, function(s) {
|
||
return s.clone();
|
||
});
|
||
|
||
var clone = new Gradient(stops);
|
||
|
||
_.each(Two.Gradient.Properties, function(k) {
|
||
clone[k] = this[k];
|
||
}, this);
|
||
|
||
if (parent) {
|
||
parent.add(clone);
|
||
}
|
||
|
||
return clone;
|
||
|
||
},
|
||
|
||
toObject: function() {
|
||
|
||
var result = {
|
||
stops: _.map(this.stops, function(s) {
|
||
return s.toObject();
|
||
})
|
||
};
|
||
|
||
_.each(Gradient.Properties, function(k) {
|
||
result[k] = this[k];
|
||
}, this);
|
||
|
||
return result;
|
||
|
||
},
|
||
|
||
_update: function() {
|
||
|
||
if (this._flagSpread || this._flagStops) {
|
||
this.trigger(Two.Events.change);
|
||
}
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
flagReset: function() {
|
||
|
||
this._flagSpread = this._flagStops = false;
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
Gradient.MakeObservable(Gradient.prototype);
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
var _ = Two.Utils;
|
||
|
||
var LinearGradient = Two.LinearGradient = function(x1, y1, x2, y2, stops) {
|
||
|
||
Two.Gradient.call(this, stops);
|
||
|
||
this._renderer.type = 'linear-gradient';
|
||
|
||
var flagEndPoints = _.bind(LinearGradient.FlagEndPoints, this);
|
||
this.left = new Two.Vector().bind(Two.Events.change, flagEndPoints);
|
||
this.right = new Two.Vector().bind(Two.Events.change, flagEndPoints);
|
||
|
||
if (_.isNumber(x1)) {
|
||
this.left.x = x1;
|
||
}
|
||
if (_.isNumber(y1)) {
|
||
this.left.y = y1;
|
||
}
|
||
if (_.isNumber(x2)) {
|
||
this.right.x = x2;
|
||
}
|
||
if (_.isNumber(y2)) {
|
||
this.right.y = y2;
|
||
}
|
||
|
||
};
|
||
|
||
_.extend(LinearGradient, {
|
||
|
||
Stop: Two.Gradient.Stop,
|
||
|
||
MakeObservable: function(object) {
|
||
Two.Gradient.MakeObservable(object);
|
||
},
|
||
|
||
FlagEndPoints: function() {
|
||
this._flagEndPoints = true;
|
||
}
|
||
|
||
});
|
||
|
||
_.extend(LinearGradient.prototype, Two.Gradient.prototype, {
|
||
|
||
_flagEndPoints: false,
|
||
|
||
clone: function(parent) {
|
||
|
||
parent = parent || this.parent;
|
||
|
||
var stops = _.map(this.stops, function(stop) {
|
||
return stop.clone();
|
||
});
|
||
|
||
var clone = new LinearGradient(this.left._x, this.left._y,
|
||
this.right._x, this.right._y, stops);
|
||
|
||
_.each(Two.Gradient.Properties, function(k) {
|
||
clone[k] = this[k];
|
||
}, this);
|
||
|
||
if (parent) {
|
||
parent.add(clone);
|
||
}
|
||
|
||
return clone;
|
||
|
||
},
|
||
|
||
toObject: function() {
|
||
|
||
var result = Two.Gradient.prototype.toObject.call(this);
|
||
|
||
result.left = this.left.toObject();
|
||
result.right = this.right.toObject();
|
||
|
||
return result;
|
||
|
||
},
|
||
|
||
_update: function() {
|
||
|
||
if (this._flagEndPoints || this._flagSpread || this._flagStops) {
|
||
this.trigger(Two.Events.change);
|
||
}
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
flagReset: function() {
|
||
|
||
this._flagEndPoints = false;
|
||
|
||
Two.Gradient.prototype.flagReset.call(this);
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
LinearGradient.MakeObservable(LinearGradient.prototype);
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
var _ = Two.Utils;
|
||
|
||
var RadialGradient = Two.RadialGradient = function(cx, cy, r, stops, fx, fy) {
|
||
|
||
Two.Gradient.call(this, stops);
|
||
|
||
this._renderer.type = 'radial-gradient';
|
||
|
||
this.center = new Two.Vector()
|
||
.bind(Two.Events.change, _.bind(function() {
|
||
this._flagCenter = true;
|
||
}, this));
|
||
|
||
this.radius = _.isNumber(r) ? r : 20;
|
||
|
||
this.focal = new Two.Vector()
|
||
.bind(Two.Events.change, _.bind(function() {
|
||
this._flagFocal = true;
|
||
}, this));
|
||
|
||
if (_.isNumber(cx)) {
|
||
this.center.x = cx;
|
||
}
|
||
if (_.isNumber(cy)) {
|
||
this.center.y = cy;
|
||
}
|
||
|
||
this.focal.copy(this.center);
|
||
|
||
if (_.isNumber(fx)) {
|
||
this.focal.x = fx;
|
||
}
|
||
if (_.isNumber(fy)) {
|
||
this.focal.y = fy;
|
||
}
|
||
|
||
};
|
||
|
||
_.extend(RadialGradient, {
|
||
|
||
Stop: Two.Gradient.Stop,
|
||
|
||
Properties: [
|
||
'radius'
|
||
],
|
||
|
||
MakeObservable: function(object) {
|
||
|
||
Two.Gradient.MakeObservable(object);
|
||
|
||
_.each(RadialGradient.Properties, Two.Utils.defineProperty, object);
|
||
|
||
}
|
||
|
||
});
|
||
|
||
_.extend(RadialGradient.prototype, Two.Gradient.prototype, {
|
||
|
||
_flagRadius: false,
|
||
_flagCenter: false,
|
||
_flagFocal: false,
|
||
|
||
clone: function(parent) {
|
||
|
||
parent = parent || this.parent;
|
||
|
||
var stops = _.map(this.stops, 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(Two.Gradient.Properties.concat(RadialGradient.Properties), function(k) {
|
||
clone[k] = this[k];
|
||
}, this);
|
||
|
||
if (parent) {
|
||
parent.add(clone);
|
||
}
|
||
|
||
return clone;
|
||
|
||
},
|
||
|
||
toObject: function() {
|
||
|
||
var result = Two.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;
|
||
|
||
},
|
||
|
||
_update: function() {
|
||
|
||
if (this._flagRadius || this._flatCenter || this._flagFocal
|
||
|| this._flagSpread || this._flagStops) {
|
||
this.trigger(Two.Events.change);
|
||
}
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
flagReset: function() {
|
||
|
||
this._flagRadius = this._flagCenter = this._flagFocal = false;
|
||
|
||
Two.Gradient.prototype.flagReset.call(this);
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
RadialGradient.MakeObservable(RadialGradient.prototype);
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
var _ = Two.Utils;
|
||
var anchor;
|
||
var regex = {
|
||
video: /\.(mp4|webm)$/i,
|
||
image: /\.(jpe?g|png|gif|tiff)$/i
|
||
};
|
||
|
||
if (this.document) {
|
||
anchor = document.createElement('a');
|
||
}
|
||
|
||
var Texture = Two.Texture = function(src, callback) {
|
||
|
||
this._renderer = {};
|
||
this._renderer.type = 'texture';
|
||
this._renderer.flagOffset = _.bind(Texture.FlagOffset, this);
|
||
this._renderer.flagScale = _.bind(Texture.FlagScale, this);
|
||
|
||
this.id = Two.Identifier + Two.uniqueId();
|
||
this.classList = [];
|
||
|
||
this.offset = new Two.Vector();
|
||
|
||
if (_.isFunction(callback)) {
|
||
var loaded = _.bind(function() {
|
||
this.unbind(Two.Events.load, loaded);
|
||
if (_.isFunction(callback)) {
|
||
callback();
|
||
}
|
||
}, this);
|
||
this.bind(Two.Events.load, loaded);
|
||
}
|
||
|
||
if (_.isString(src)) {
|
||
this.src = src;
|
||
} else if (_.isElement(src)) {
|
||
this.image = src;
|
||
}
|
||
|
||
this._update();
|
||
|
||
};
|
||
|
||
_.extend(Texture, {
|
||
|
||
Properties: [
|
||
'src',
|
||
'loaded',
|
||
'repeat'
|
||
],
|
||
|
||
ImageRegistry: new Two.Registry(),
|
||
|
||
getAbsoluteURL: function(path) {
|
||
if (!anchor) {
|
||
// TODO: Fix for headless environment
|
||
return path;
|
||
}
|
||
anchor.href = path;
|
||
return anchor.href;
|
||
},
|
||
|
||
getImage: function(src) {
|
||
|
||
var absoluteSrc = Texture.getAbsoluteURL(src);
|
||
|
||
if (Texture.ImageRegistry.contains(absoluteSrc)) {
|
||
return Texture.ImageRegistry.get(absoluteSrc);
|
||
}
|
||
|
||
var image;
|
||
|
||
if (regex.video.test(absoluteSrc)) {
|
||
image = document.createElement('video');
|
||
} else {
|
||
image = document.createElement('img');
|
||
}
|
||
|
||
image.crossOrigin = 'anonymous';
|
||
|
||
return image;
|
||
|
||
},
|
||
|
||
Register: {
|
||
canvas: function(texture, callback) {
|
||
texture._src = '#' + texture.id;
|
||
Texture.ImageRegistry.add(texture.src, texture.image);
|
||
if (_.isFunction(callback)) {
|
||
callback();
|
||
}
|
||
},
|
||
img: function(texture, callback) {
|
||
|
||
var loaded = function(e) {
|
||
texture.image.removeEventListener('load', loaded, false);
|
||
texture.image.removeEventListener('error', error, false);
|
||
if (_.isFunction(callback)) {
|
||
callback();
|
||
}
|
||
};
|
||
var error = function(e) {
|
||
texture.image.removeEventListener('load', loaded, false);
|
||
texture.image.removeEventListener('error', error, false);
|
||
throw new Two.Utils.Error('unable to load ' + texture.src);
|
||
};
|
||
|
||
if (_.isNumber(texture.image.width) && texture.image.width > 0
|
||
&& _.isNumber(texture.image.height) && texture.image.height > 0) {
|
||
loaded();
|
||
} else {
|
||
texture.image.addEventListener('load', loaded, false);
|
||
texture.image.addEventListener('error', error, false);
|
||
}
|
||
|
||
texture._src = Texture.getAbsoluteURL(texture._src);
|
||
|
||
if (texture.image && texture.image.getAttribute('two-src')) {
|
||
return;
|
||
}
|
||
|
||
texture.image.setAttribute('two-src', texture.src);
|
||
Texture.ImageRegistry.add(texture.src, texture.image);
|
||
texture.image.src = texture.src;
|
||
|
||
},
|
||
video: function(texture, callback) {
|
||
|
||
var loaded = function(e) {
|
||
texture.image.removeEventListener('load', loaded, false);
|
||
texture.image.removeEventListener('error', error, false);
|
||
texture.image.width = texture.image.videoWidth;
|
||
texture.image.height = texture.image.videoHeight;
|
||
texture.image.play();
|
||
if (_.isFunction(callback)) {
|
||
callback();
|
||
}
|
||
};
|
||
var error = function(e) {
|
||
texture.image.removeEventListener('load', loaded, false);
|
||
texture.image.removeEventListener('error', error, false);
|
||
throw new Two.Utils.Error('unable to load ' + texture.src);
|
||
};
|
||
|
||
texture._src = Texture.getAbsoluteURL(texture._src);
|
||
texture.image.addEventListener('canplaythrough', loaded, false);
|
||
texture.image.addEventListener('error', error, false);
|
||
|
||
if (texture.image && texture.image.getAttribute('two-src')) {
|
||
return;
|
||
}
|
||
|
||
texture.image.setAttribute('two-src', texture.src);
|
||
Texture.ImageRegistry.add(texture.src, texture.image);
|
||
texture.image.src = texture.src;
|
||
texture.image.loop = true;
|
||
texture.image.load();
|
||
|
||
}
|
||
},
|
||
|
||
load: function(texture, callback) {
|
||
|
||
var src = texture.src;
|
||
var image = texture.image;
|
||
var tag = image && image.nodeName.toLowerCase();
|
||
|
||
if (texture._flagImage) {
|
||
if (/canvas/i.test(tag)) {
|
||
Texture.Register.canvas(texture, callback);
|
||
} else {
|
||
texture._src = image.getAttribute('two-src') || image.src;
|
||
Texture.Register[tag](texture, callback);
|
||
}
|
||
}
|
||
|
||
if (texture._flagSrc) {
|
||
if (!image) {
|
||
texture.image = Texture.getImage(texture.src);
|
||
}
|
||
tag = texture.image.nodeName.toLowerCase();
|
||
Texture.Register[tag](texture, callback);
|
||
}
|
||
|
||
},
|
||
|
||
FlagOffset: function() {
|
||
this._flagOffset = true;
|
||
},
|
||
|
||
FlagScale: function() {
|
||
this._flagScale = true;
|
||
},
|
||
|
||
MakeObservable: function(object) {
|
||
|
||
_.each(Texture.Properties, Two.Utils.defineProperty, object);
|
||
|
||
Object.defineProperty(object, 'image', {
|
||
enumerable: true,
|
||
get: function() {
|
||
return this._image;
|
||
},
|
||
set: function(image) {
|
||
|
||
var tag = image && image.nodeName.toLowerCase();
|
||
var index;
|
||
|
||
switch (tag) {
|
||
case 'canvas':
|
||
index = '#' + image.id;
|
||
break;
|
||
default:
|
||
index = image.src;
|
||
}
|
||
|
||
if (Texture.ImageRegistry.contains(index)) {
|
||
this._image = Texture.ImageRegistry.get(image.src);
|
||
} else {
|
||
this._image = image;
|
||
}
|
||
|
||
this._flagImage = true;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
Object.defineProperty(object, 'offset', {
|
||
enumerable: true,
|
||
get: function() {
|
||
return this._offset;
|
||
},
|
||
set: function(v) {
|
||
if (this._offset) {
|
||
this._offset.unbind(Two.Events.change, this._renderer.flagOffset);
|
||
}
|
||
this._offset = v;
|
||
this._offset.bind(Two.Events.change, this._renderer.flagOffset);
|
||
this._flagOffset = true;
|
||
}
|
||
});
|
||
|
||
Object.defineProperty(object, 'scale', {
|
||
enumerable: true,
|
||
get: function() {
|
||
return this._scale;
|
||
},
|
||
set: function(v) {
|
||
|
||
if (this._scale instanceof Two.Vector) {
|
||
this._scale.unbind(Two.Events.change, this._renderer.flagScale);
|
||
}
|
||
|
||
this._scale = v;
|
||
|
||
if (this._scale instanceof Two.Vector) {
|
||
this._scale.bind(Two.Events.change, this._renderer.flagScale);
|
||
}
|
||
|
||
this._flagScale = true;
|
||
|
||
}
|
||
});
|
||
|
||
}
|
||
|
||
});
|
||
|
||
_.extend(Texture.prototype, Two.Utils.Events, Two.Shape.prototype, {
|
||
|
||
_flagSrc: false,
|
||
_flagImage: false,
|
||
_flagVideo: false,
|
||
_flagLoaded: false,
|
||
_flagRepeat: false,
|
||
|
||
_flagOffset: false,
|
||
_flagScale: false,
|
||
|
||
_src: '',
|
||
_image: null,
|
||
_loaded: false,
|
||
_repeat: 'no-repeat',
|
||
|
||
_scale: 1,
|
||
_offset: null,
|
||
|
||
clone: function() {
|
||
return new Texture(this.src);
|
||
},
|
||
|
||
toObject: function() {
|
||
return {
|
||
src: this.src,
|
||
image: this.image
|
||
}
|
||
},
|
||
|
||
_update: function() {
|
||
|
||
if (this._flagSrc || this._flagImage || this._flagVideo) {
|
||
|
||
this.trigger(Two.Events.change);
|
||
|
||
if (this._flagSrc || this._flagImage) {
|
||
this.loaded = false;
|
||
Texture.load(this, _.bind(function() {
|
||
this.loaded = true;
|
||
this
|
||
.trigger(Two.Events.change)
|
||
.trigger(Two.Events.load);
|
||
}, this));
|
||
}
|
||
|
||
}
|
||
|
||
if (this._image && this._image.readyState >= 4) {
|
||
this._flagVideo = true;
|
||
}
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
flagReset: function() {
|
||
|
||
this._flagSrc = this._flagImage = this._flagLoaded
|
||
= this._flagVideo = this._flagScale = this._flagOffset = false;
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
Texture.MakeObservable(Texture.prototype);
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
var _ = Two.Utils;
|
||
var Path = Two.Path;
|
||
var Rectangle = Two.Rectangle;
|
||
|
||
var Sprite = Two.Sprite = function(path, ox, oy, cols, rows, frameRate) {
|
||
|
||
Path.call(this, [
|
||
new Two.Anchor(),
|
||
new Two.Anchor(),
|
||
new Two.Anchor(),
|
||
new Two.Anchor()
|
||
], true);
|
||
|
||
this.noStroke();
|
||
this.noFill();
|
||
|
||
if (path instanceof Two.Texture) {
|
||
this.texture = path;
|
||
} else if (_.isString(path)) {
|
||
this.texture = new Two.Texture(path);
|
||
}
|
||
|
||
this._update();
|
||
this.translation.set(ox || 0, oy || 0);
|
||
|
||
if (_.isNumber(cols)) {
|
||
this.columns = cols;
|
||
}
|
||
if (_.isNumber(rows)) {
|
||
this.rows = rows;
|
||
}
|
||
if (_.isNumber(frameRate)) {
|
||
this.frameRate = frameRate;
|
||
}
|
||
|
||
};
|
||
|
||
_.extend(Sprite, {
|
||
|
||
Properties: [
|
||
'texture', 'columns', 'rows', 'frameRate', 'index'
|
||
],
|
||
|
||
MakeObservable: function(obj) {
|
||
|
||
Rectangle.MakeObservable(obj);
|
||
_.each(Sprite.Properties, Two.Utils.defineProperty, obj);
|
||
|
||
}
|
||
|
||
})
|
||
|
||
_.extend(Sprite.prototype, Rectangle.prototype, {
|
||
|
||
_flagTexture: false,
|
||
_flagColumns: false,
|
||
_flagRows: false,
|
||
_flagFrameRate: false,
|
||
flagIndex: false,
|
||
|
||
// Private variables
|
||
_amount: 1,
|
||
_duration: 0,
|
||
_startTime: 0,
|
||
_playing: false,
|
||
_firstFrame: 0,
|
||
_lastFrame: 0,
|
||
_loop: true,
|
||
|
||
// Exposed through getter-setter
|
||
_texture: null,
|
||
_columns: 1,
|
||
_rows: 1,
|
||
_frameRate: 0,
|
||
_index: 0,
|
||
|
||
play: function(firstFrame, lastFrame, onLastFrame) {
|
||
|
||
this._playing = true;
|
||
this._firstFrame = 0;
|
||
this._lastFrame = this.amount - 1;
|
||
this._startTime = _.performance.now();
|
||
|
||
if (_.isNumber(firstFrame)) {
|
||
this._firstFrame = firstFrame;
|
||
}
|
||
if (_.isNumber(lastFrame)) {
|
||
this._lastFrame = lastFrame;
|
||
}
|
||
if (_.isFunction(onLastFrame)) {
|
||
this._onLastFrame = onLastFrame;
|
||
} else {
|
||
delete this._onLastFrame;
|
||
}
|
||
|
||
if (this._index !== this._firstFrame) {
|
||
this._startTime -= 1000 * Math.abs(this._index - this._firstFrame)
|
||
/ this._frameRate;
|
||
}
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
pause: function() {
|
||
|
||
this._playing = false;
|
||
return this;
|
||
|
||
},
|
||
|
||
stop: function() {
|
||
|
||
this._playing = false;
|
||
this._index = 0;
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
clone: function(parent) {
|
||
|
||
parent = parent || this.parent;
|
||
|
||
var clone = new Sprite(
|
||
this.texture, this.translation.x, this.translation.y,
|
||
this.columns, this.rows, this.frameRate
|
||
);
|
||
|
||
if (this.playing) {
|
||
clone.play(this._firstFrame, this._lastFrame);
|
||
clone._loop = this._loop;
|
||
}
|
||
|
||
if (parent) {
|
||
parent.add(clone);
|
||
}
|
||
|
||
return clone;
|
||
|
||
},
|
||
|
||
_update: function() {
|
||
|
||
var effect = this._texture;
|
||
var cols = this._columns;
|
||
var rows = this._rows;
|
||
|
||
var width, height, elapsed, amount, duration;
|
||
var index, iw, ih, isRange, frames;
|
||
|
||
if (this._flagColumns || this._flagRows) {
|
||
this._amount = this._columns * this._rows;
|
||
}
|
||
|
||
if (this._flagFrameRate) {
|
||
this._duration = 1000 * this._amount / this._frameRate;
|
||
}
|
||
|
||
if (this._flagTexture) {
|
||
this.fill = this._texture;
|
||
}
|
||
|
||
if (this._texture.loaded) {
|
||
|
||
iw = effect.image.width;
|
||
ih = effect.image.height;
|
||
|
||
width = iw / cols;
|
||
height = ih / rows;
|
||
amount = this._amount;
|
||
|
||
if (this.width !== width) {
|
||
this.width = width;
|
||
}
|
||
if (this.height !== height) {
|
||
this.height = height;
|
||
}
|
||
|
||
if (this._playing && this._frameRate > 0) {
|
||
|
||
if (_.isNaN(this._lastFrame)) {
|
||
this._lastFrame = amount - 1;
|
||
}
|
||
|
||
// TODO: Offload perf logic to instance of `Two`.
|
||
elapsed = _.performance.now() - this._startTime;
|
||
frames = this._lastFrame + 1;
|
||
duration = 1000 * (frames - this._firstFrame) / this._frameRate;
|
||
|
||
if (this._loop) {
|
||
elapsed = elapsed % duration;
|
||
} else {
|
||
elapsed = Math.min(elapsed, duration);
|
||
}
|
||
|
||
index = _.lerp(this._firstFrame, frames, elapsed / duration);
|
||
index = Math.floor(index);
|
||
|
||
if (index !== this._index) {
|
||
this._index = index;
|
||
if (index >= this._lastFrame - 1 && this._onLastFrame) {
|
||
this._onLastFrame(); // Shortcut for chainable sprite animations
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
var col = this._index % cols;
|
||
var row = Math.floor(this._index / cols);
|
||
|
||
var ox = - width * col + (iw - width) / 2;
|
||
var oy = - height * row + (ih - height) / 2;
|
||
|
||
// TODO: Improve performance
|
||
if (ox !== effect.offset.x) {
|
||
effect.offset.x = ox;
|
||
}
|
||
if (oy !== effect.offset.y) {
|
||
effect.offset.y = oy;
|
||
}
|
||
|
||
}
|
||
|
||
Rectangle.prototype._update.call(this);
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
flagReset: function() {
|
||
|
||
this._flagTexture = this._flagColumns = this._flagRows
|
||
= this._flagFrameRate = false;
|
||
|
||
Rectangle.prototype.flagReset.call(this);
|
||
|
||
return this;
|
||
}
|
||
|
||
|
||
});
|
||
|
||
Sprite.MakeObservable(Sprite.prototype);
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
var _ = Two.Utils;
|
||
var Path = Two.Path;
|
||
var Rectangle = Two.Rectangle;
|
||
|
||
var ImageSequence = Two.ImageSequence = function(paths, ox, oy, frameRate) {
|
||
|
||
Path.call(this, [
|
||
new Two.Anchor(),
|
||
new Two.Anchor(),
|
||
new Two.Anchor(),
|
||
new Two.Anchor()
|
||
], true);
|
||
|
||
this._renderer.flagTextures = _.bind(ImageSequence.FlagTextures, this);
|
||
this._renderer.bindTextures = _.bind(ImageSequence.BindTextures, this);
|
||
this._renderer.unbindTextures = _.bind(ImageSequence.UnbindTextures, this);
|
||
|
||
this.noStroke();
|
||
this.noFill();
|
||
|
||
this.textures = _.map(paths, ImageSequence.GenerateTexture, this);
|
||
|
||
this._update();
|
||
this.translation.set(ox || 0, oy || 0);
|
||
|
||
if (_.isNumber(frameRate)) {
|
||
this.frameRate = frameRate;
|
||
} else {
|
||
this.frameRate = ImageSequence.DefaultFrameRate;
|
||
}
|
||
|
||
};
|
||
|
||
_.extend(ImageSequence, {
|
||
|
||
Properties: [
|
||
'frameRate',
|
||
'index'
|
||
],
|
||
|
||
DefaultFrameRate: 30,
|
||
|
||
FlagTextures: function() {
|
||
this._flagTextures = true;
|
||
},
|
||
|
||
BindTextures: function(items) {
|
||
|
||
var i = items.length;
|
||
while (i--) {
|
||
items[i].bind(Two.Events.change, this._renderer.flagTextures);
|
||
}
|
||
|
||
this._renderer.flagTextures();
|
||
|
||
},
|
||
|
||
UnbindTextures: function(items) {
|
||
|
||
var i = items.length;
|
||
while (i--) {
|
||
items[i].unbind(Two.Events.change, this._renderer.flagTextures);
|
||
}
|
||
|
||
this._renderer.flagTextures();
|
||
|
||
},
|
||
|
||
MakeObservable: function(obj) {
|
||
|
||
Rectangle.MakeObservable(obj);
|
||
_.each(ImageSequence.Properties, Two.Utils.defineProperty, obj);
|
||
|
||
Object.defineProperty(obj, 'textures', {
|
||
|
||
enumerable: true,
|
||
|
||
get: function() {
|
||
return this._textures;
|
||
},
|
||
|
||
set: function(textures) {
|
||
|
||
var updateTextures = this._renderer.flagTextures;
|
||
var bindTextures = this._renderer.bindTextures;
|
||
var unbindTextures = this._renderer.unbindTextures;
|
||
|
||
// Remove previous listeners
|
||
if (this._textures) {
|
||
this._textures
|
||
.unbind(Two.Events.insert, bindTextures)
|
||
.unbind(Two.Events.remove, unbindTextures);
|
||
}
|
||
|
||
// Create new Collection with copy of vertices
|
||
this._textures = new Two.Utils.Collection((textures || []).slice(0));
|
||
|
||
// Listen for Collection changes and bind / unbind
|
||
this._textures
|
||
.bind(Two.Events.insert, bindTextures)
|
||
.bind(Two.Events.remove, unbindTextures);
|
||
|
||
// Bind Initial Textures
|
||
bindTextures(this._textures);
|
||
|
||
}
|
||
|
||
});
|
||
|
||
},
|
||
|
||
GenerateTexture: function(obj) {
|
||
if (obj instanceof Two.Texture) {
|
||
return obj;
|
||
} else if (_.isString(obj)) {
|
||
return new Two.Texture(obj);
|
||
}
|
||
}
|
||
|
||
});
|
||
|
||
_.extend(ImageSequence.prototype, Rectangle.prototype, {
|
||
|
||
_flagTextures: false,
|
||
_flagFrameRate: false,
|
||
_flagIndex: false,
|
||
|
||
// Private variables
|
||
_amount: 1,
|
||
_duration: 0,
|
||
_index: 0,
|
||
_startTime: 0,
|
||
_playing: false,
|
||
_firstFrame: 0,
|
||
_lastFrame: 0,
|
||
_loop: true,
|
||
|
||
// Exposed through getter-setter
|
||
_textures: null,
|
||
_frameRate: 0,
|
||
|
||
play: function(firstFrame, lastFrame, onLastFrame) {
|
||
|
||
this._playing = true;
|
||
this._firstFrame = 0;
|
||
this._lastFrame = this.amount - 1;
|
||
this._startTime = _.performance.now();
|
||
|
||
if (_.isNumber(firstFrame)) {
|
||
this._firstFrame = firstFrame;
|
||
}
|
||
if (_.isNumber(lastFrame)) {
|
||
this._lastFrame = lastFrame;
|
||
}
|
||
if (_.isFunction(onLastFrame)) {
|
||
this._onLastFrame = onLastFrame;
|
||
} else {
|
||
delete this._onLastFrame;
|
||
}
|
||
|
||
if (this._index !== this._firstFrame) {
|
||
this._startTime -= 1000 * Math.abs(this._index - this._firstFrame)
|
||
/ this._frameRate;
|
||
}
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
pause: function() {
|
||
|
||
this._playing = false;
|
||
return this;
|
||
|
||
},
|
||
|
||
stop: function() {
|
||
|
||
this._playing = false;
|
||
this._index = 0;
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
clone: function(parent) {
|
||
|
||
parent = parent || this.parent;
|
||
|
||
var clone = new ImageSequence(this.textures, this.translation.x,
|
||
this.translation.y, this.frameRate)
|
||
|
||
clone._loop = this._loop;
|
||
|
||
if (this._playing) {
|
||
clone.play();
|
||
}
|
||
|
||
if (parent) {
|
||
parent.add(clone);
|
||
}
|
||
|
||
return clone;
|
||
|
||
},
|
||
|
||
_update: function() {
|
||
|
||
var effects = this._textures;
|
||
var width, height, elapsed, amount, duration, texture;
|
||
var index, frames;
|
||
|
||
if (this._flagTextures) {
|
||
this._amount = effects.length;
|
||
}
|
||
|
||
if (this._flagFrameRate) {
|
||
this._duration = 1000 * this._amount / this._frameRate;
|
||
}
|
||
|
||
if (this._playing && this._frameRate > 0) {
|
||
|
||
amount = this._amount;
|
||
|
||
if (_.isNaN(this._lastFrame)) {
|
||
this._lastFrame = amount - 1;
|
||
}
|
||
|
||
// TODO: Offload perf logic to instance of `Two`.
|
||
elapsed = _.performance.now() - this._startTime;
|
||
frames = this._lastFrame + 1;
|
||
duration = 1000 * (frames - this._firstFrame) / this._frameRate;
|
||
|
||
if (this._loop) {
|
||
elapsed = elapsed % duration;
|
||
} else {
|
||
elapsed = Math.min(elapsed, duration);
|
||
}
|
||
|
||
index = _.lerp(this._firstFrame, frames, elapsed / duration);
|
||
index = Math.floor(index);
|
||
|
||
if (index !== this._index) {
|
||
|
||
this._index = index;
|
||
texture = effects[this._index];
|
||
|
||
if (texture.loaded) {
|
||
|
||
width = texture.image.width;
|
||
height = texture.image.height;
|
||
|
||
if (this.width !== width) {
|
||
this.width = width;
|
||
}
|
||
if (this.height !== height) {
|
||
this.height = height;
|
||
}
|
||
|
||
this.fill = texture;
|
||
|
||
if (index >= this._lastFrame - 1 && this._onLastFrame) {
|
||
this._onLastFrame(); // Shortcut for chainable sprite animations
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
} else if (this._flagIndex || !(this.fill instanceof Two.Texture)) {
|
||
|
||
texture = effects[this._index];
|
||
|
||
if (texture.loaded) {
|
||
|
||
width = texture.image.width;
|
||
height = texture.image.height;
|
||
|
||
if (this.width !== width) {
|
||
this.width = width;
|
||
}
|
||
if (this.height !== height) {
|
||
this.height = height;
|
||
}
|
||
|
||
}
|
||
|
||
this.fill = texture;
|
||
|
||
}
|
||
|
||
Rectangle.prototype._update.call(this);
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
flagReset: function() {
|
||
|
||
this._flagTextures = this._flagFrameRate = false;
|
||
Rectangle.prototype.flagReset.call(this);
|
||
|
||
return this;
|
||
|
||
}
|
||
|
||
});
|
||
|
||
ImageSequence.MakeObservable(ImageSequence.prototype);
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|
||
|
||
(function(Two) {
|
||
|
||
/**
|
||
* Constants
|
||
*/
|
||
var min = Math.min, max = Math.max;
|
||
var _ = Two.Utils;
|
||
|
||
/**
|
||
* A children collection which is accesible both by index and by object id
|
||
* @constructor
|
||
*/
|
||
var Children = function() {
|
||
|
||
Two.Utils.Collection.apply(this, arguments);
|
||
|
||
Object.defineProperty(this, '_events', {
|
||
value : {},
|
||
enumerable: false
|
||
});
|
||
|
||
this.ids = {};
|
||
|
||
this.on(Two.Events.insert, this.attach);
|
||
this.on(Two.Events.remove, this.detach);
|
||
Children.prototype.attach.apply(this, arguments);
|
||
|
||
};
|
||
|
||
Children.prototype = new Two.Utils.Collection();
|
||
Children.prototype.constructor = Children;
|
||
|
||
_.extend(Children.prototype, {
|
||
|
||
attach: function(children) {
|
||
for (var i = 0; i < children.length; i++) {
|
||
this.ids[children[i].id] = children[i];
|
||
}
|
||
return this;
|
||
},
|
||
|
||
detach: function(children) {
|
||
for (var i = 0; i < children.length; i++) {
|
||
delete this.ids[children[i].id];
|
||
}
|
||
return this;
|
||
}
|
||
|
||
});
|
||
|
||
var Group = Two.Group = function() {
|
||
|
||
Two.Shape.call(this, true);
|
||
|
||
this._renderer.type = 'group';
|
||
|
||
this.additions = [];
|
||
this.subtractions = [];
|
||
|
||
this.children = arguments;
|
||
|
||
};
|
||
|
||
_.extend(Group, {
|
||
|
||
Children: Children,
|
||
|
||
InsertChildren: function(children) {
|
||
for (var i = 0; i < children.length; i++) {
|
||
replaceParent.call(this, children[i], this);
|
||
}
|
||
},
|
||
|
||
RemoveChildren: function(children) {
|
||
for (var i = 0; i < children.length; i++) {
|
||
replaceParent.call(this, children[i]);
|
||
}
|
||
},
|
||
|
||
OrderChildren: function(children) {
|
||
this._flagOrder = true;
|
||
},
|
||
|
||
MakeObservable: function(object) {
|
||
|
||
var properties = Two.Path.Properties.slice(0);
|
||
var oi = _.indexOf(properties, 'opacity');
|
||
|
||
if (oi >= 0) {
|
||
|
||
properties.splice(oi, 1);
|
||
|
||
Object.defineProperty(object, 'opacity', {
|
||
|
||
enumerable: true,
|
||
|
||
get: function() {
|
||
return this._opacity;
|
||
},
|
||
|
||
set: function(v) {
|
||
// Only set flag if there is an actual difference
|
||
this._flagOpacity = (this._opacity != v);
|
||
this._opacity = v;
|
||
}
|
||
|
||
});
|
||
|
||
}
|
||
|
||
Two.Shape.MakeObservable(object);
|
||
Group.MakeGetterSetters(object, properties);
|
||
|
||
Object.defineProperty(object, 'children', {
|
||
|
||
enumerable: true,
|
||
|
||
get: function() {
|
||
return this._children;
|
||
},
|
||
|
||
set: function(children) {
|
||
|
||
var insertChildren = _.bind(Group.InsertChildren, this);
|
||
var removeChildren = _.bind(Group.RemoveChildren, this);
|
||
var orderChildren = _.bind(Group.OrderChildren, this);
|
||
|
||
if (this._children) {
|
||
this._children.unbind();
|
||
}
|
||
|
||
this._children = new Children(children);
|
||
this._children.bind(Two.Events.insert, insertChildren);
|
||
this._children.bind(Two.Events.remove, removeChildren);
|
||
this._children.bind(Two.Events.order, orderChildren);
|
||
|
||
}
|
||
|
||
});
|
||
|
||
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;
|
||
}
|
||
}
|
||
|
||
});
|
||
|
||
},
|
||
|
||
MakeGetterSetters: function(group, properties) {
|
||
|
||
if (!_.isArray(properties)) {
|
||
properties = [properties];
|
||
}
|
||
|
||
_.each(properties, function(k) {
|
||
Group.MakeGetterSetter(group, k);
|
||
});
|
||
|
||
},
|
||
|
||
MakeGetterSetter: function(group, k) {
|
||
|
||
var secret = '_' + k;
|
||
|
||
Object.defineProperty(group, k, {
|
||
|
||
enumerable: true,
|
||
|
||
get: function() {
|
||
return this[secret];
|
||
},
|
||
|
||
set: function(v) {
|
||
this[secret] = v;
|
||
_.each(this.children, function(child) { // Trickle down styles
|
||
child[k] = v;
|
||
});
|
||
}
|
||
|
||
});
|
||
|
||
}
|
||
|
||
});
|
||
|
||
_.extend(Group.prototype, Two.Shape.prototype, {
|
||
|
||
// Flags
|
||
// http://en.wikipedia.org/wiki/Flag
|
||
|
||
_flagAdditions: false,
|
||
_flagSubtractions: false,
|
||
_flagOrder: false,
|
||
_flagOpacity: true,
|
||
|
||
_flagMask: false,
|
||
|
||
// Underlying Properties
|
||
|
||
_fill: '#fff',
|
||
_stroke: '#000',
|
||
_linewidth: 1.0,
|
||
_opacity: 1.0,
|
||
_visible: true,
|
||
|
||
_cap: 'round',
|
||
_join: 'round',
|
||
_miter: 4,
|
||
|
||
_closed: true,
|
||
_curved: false,
|
||
_automatic: true,
|
||
_beginning: 0,
|
||
_ending: 1.0,
|
||
|
||
_mask: null,
|
||
|
||
/**
|
||
* 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.
|
||
*/
|
||
clone: function(parent) {
|
||
|
||
parent = parent || this.parent;
|
||
|
||
var group = new Group();
|
||
var children = _.map(this.children, function(child) {
|
||
return child.clone(group);
|
||
});
|
||
|
||
group.add(children);
|
||
|
||
group.opacity = this.opacity;
|
||
|
||
if (this.mask) {
|
||
group.mask = this.mask;
|
||
}
|
||
|
||
group.translation.copy(this.translation);
|
||
group.rotation = this.rotation;
|
||
group.scale = this.scale;
|
||
|
||
if (parent) {
|
||
parent.add(group);
|
||
}
|
||
|
||
return group;
|
||
|
||
},
|
||
|
||
/**
|
||
* Export the data from the instance of Two.Group into a plain JavaScript
|
||
* object. This also makes all children plain JavaScript objects. Great
|
||
* for turning into JSON and storing in a database.
|
||
*/
|
||
toObject: function() {
|
||
|
||
var result = {
|
||
children: [],
|
||
translation: this.translation.toObject(),
|
||
rotation: this.rotation,
|
||
scale: this.scale,
|
||
opacity: this.opacity,
|
||
mask: (this.mask ? this.mask.toObject() : null)
|
||
};
|
||
|
||
_.each(this.children, function(child, i) {
|
||
result.children[i] = child.toObject();
|
||
}, this);
|
||
|
||
return result;
|
||
|
||
},
|
||
|
||
/**
|
||
* Anchor all children to the upper left hand corner
|
||
* of the group.
|
||
*/
|
||
corner: function() {
|
||
|
||
var rect = this.getBoundingClientRect(true),
|
||
corner = { x: rect.left, y: rect.top };
|
||
|
||
this.children.forEach(function(child) {
|
||
child.translation.subSelf(corner);
|
||
});
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
/**
|
||
* Anchors all children around the center of the group,
|
||
* effectively placing the shape around the unit circle.
|
||
*/
|
||
center: function() {
|
||
|
||
var rect = this.getBoundingClientRect(true);
|
||
|
||
rect.centroid = {
|
||
x: rect.left + rect.width / 2,
|
||
y: rect.top + rect.height / 2
|
||
};
|
||
|
||
this.children.forEach(function(child) {
|
||
if (child.isShape) {
|
||
child.translation.subSelf(rect.centroid);
|
||
}
|
||
});
|
||
|
||
// this.translation.copy(rect.centroid);
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
/**
|
||
* Recursively search for id. Returns the first element found.
|
||
* Returns null if none found.
|
||
*/
|
||
getById: function (id) {
|
||
var search = function (node, id) {
|
||
if (node.id === id) {
|
||
return node;
|
||
} else if (node.children) {
|
||
var i = node.children.length;
|
||
while (i--) {
|
||
var found = search(node.children[i], id);
|
||
if (found) return found;
|
||
}
|
||
}
|
||
|
||
};
|
||
return search(this, id) || null;
|
||
},
|
||
|
||
/**
|
||
* Recursively search for classes. Returns an array of matching elements.
|
||
* Empty array if none found.
|
||
*/
|
||
getByClassName: function (cl) {
|
||
var found = [];
|
||
var search = function (node, cl) {
|
||
if (node.classList.indexOf(cl) != -1) {
|
||
found.push(node);
|
||
} else if (node.children) {
|
||
node.children.forEach(function (child) {
|
||
search(child, cl);
|
||
});
|
||
}
|
||
return found;
|
||
};
|
||
return search(this, cl);
|
||
},
|
||
|
||
/**
|
||
* Recursively search for children of a specific type,
|
||
* e.g. Two.Polygon. Pass a reference to this type as the param.
|
||
* Returns an empty array if none found.
|
||
*/
|
||
getByType: function(type) {
|
||
var found = [];
|
||
var search = function (node, type) {
|
||
for (var id in node.children) {
|
||
if (node.children[id] instanceof type) {
|
||
found.push(node.children[id]);
|
||
} else if (node.children[id] instanceof Two.Group) {
|
||
search(node.children[id], type);
|
||
}
|
||
}
|
||
return found;
|
||
};
|
||
return search(this, type);
|
||
},
|
||
|
||
/**
|
||
* 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 = _.toArray(arguments);
|
||
} else {
|
||
objects = objects.slice();
|
||
}
|
||
|
||
// Add the objects
|
||
for (var i = 0; i < objects.length; i++) {
|
||
if (!(objects[i] && objects[i].id)) continue;
|
||
this.children.push(objects[i]);
|
||
}
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
/**
|
||
* 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 the scene.
|
||
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 = _.toArray(arguments);
|
||
} else {
|
||
objects = objects.slice();
|
||
}
|
||
|
||
// Remove the objects
|
||
for (var i = 0; i < objects.length; i++) {
|
||
if (!objects[i] || !(this.children.ids[objects[i].id])) continue;
|
||
this.children.splice(_.indexOf(this.children, objects[i]), 1);
|
||
}
|
||
|
||
return this;
|
||
|
||
},
|
||
|
||
/**
|
||
* Return an object with top, left, right, bottom, width, and height
|
||
* parameters of the group.
|
||
*/
|
||
getBoundingClientRect: function(shallow) {
|
||
var rect;
|
||
|
||
// 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;
|
||
|
||
this.children.forEach(function(child) {
|
||
|
||
if (/(linear-gradient|radial-gradient|gradient)/.test(child._renderer.type)) {
|
||
return;
|
||
}
|
||
|
||
rect = child.getBoundingClientRect(shallow);
|
||
|
||
if (!_.isNumber(rect.top) || !_.isNumber(rect.left) ||
|
||
!_.isNumber(rect.right) || !_.isNumber(rect.bottom)) {
|
||
return;
|
||
}
|
||
|
||
top = min(rect.top, top);
|
||
left = min(rect.left, left);
|
||
right = max(rect.right, right);
|
||
bottom = max(rect.bottom, bottom);
|
||
|
||
}, this);
|
||
|
||
return {
|
||
top: top,
|
||
left: left,
|
||
right: right,
|
||
bottom: bottom,
|
||
width: right - left,
|
||
height: bottom - top
|
||
};
|
||
|
||
},
|
||
|
||
/**
|
||
* Trickle down of noFill
|
||
*/
|
||
noFill: function() {
|
||
this.children.forEach(function(child) {
|
||
child.noFill();
|
||
});
|
||
return this;
|
||
},
|
||
|
||
/**
|
||
* Trickle down of noStroke
|
||
*/
|
||
noStroke: function() {
|
||
this.children.forEach(function(child) {
|
||
child.noStroke();
|
||
});
|
||
return this;
|
||
},
|
||
|
||
/**
|
||
* Trickle down subdivide
|
||
*/
|
||
subdivide: function() {
|
||
var args = arguments;
|
||
this.children.forEach(function(child) {
|
||
child.subdivide.apply(child, args);
|
||
});
|
||
return this;
|
||
},
|
||
|
||
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 = false;
|
||
|
||
Two.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) {
|
||
this.additions.push(child);
|
||
this._flagAdditions = true;
|
||
return;
|
||
}
|
||
|
||
if (parent && parent.children.ids[child.id]) {
|
||
|
||
index = _.indexOf(parent.children, child);
|
||
parent.children.splice(index, 1);
|
||
|
||
// If we're passing from one parent to another...
|
||
index = _.indexOf(parent.additions, child);
|
||
|
||
if (index >= 0) {
|
||
parent.additions.splice(index, 1);
|
||
} else {
|
||
parent.subtractions.push(child);
|
||
parent._flagSubtractions = true;
|
||
}
|
||
|
||
}
|
||
|
||
if (newParent) {
|
||
child.parent = newParent;
|
||
this.additions.push(child);
|
||
this._flagAdditions = true;
|
||
return;
|
||
}
|
||
|
||
// If we're passing from one parent to another...
|
||
index = _.indexOf(this.additions, child);
|
||
|
||
if (index >= 0) {
|
||
this.additions.splice(index, 1);
|
||
} else {
|
||
this.subtractions.push(child);
|
||
this._flagSubtractions = true;
|
||
}
|
||
|
||
delete child.parent;
|
||
|
||
}
|
||
|
||
})((typeof global !== 'undefined' ? global : this).Two);
|