Jelajahi Sumber

two.js, pts.js, tone.js view drivers and libs

Nikolay Suslov 3 tahun lalu
induk
melakukan
033879132a

+ 951 - 0
public/drivers/view/pts.js

@@ -0,0 +1,951 @@
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+*/
+
+// VWF & A-Frame view driver
+
+import {Fabric} from '/core/vwf/fabric.js';
+
+class PTSView extends Fabric {
+
+  constructor(module) {
+    console.log("PTSView constructor");
+    super(module, 'View');
+  }
+
+  factory() {
+
+    let _self_ = this;
+
+	return this.load(this.module, 
+        {
+
+            // == Module Definition ====================================================================
+    
+            initialize: function (options) {
+                let self = this;
+                this.fabric = _self_;
+                this.nodes = {};
+                
+               
+    
+                this.state.appInitialized = false;
+
+
+                if (options === undefined) { options = {}; }
+    
+                if (typeof options == "object") {
+    
+                    this.rootSelector = options["application-root"];
+                }
+                else {
+                    this.rootSelector = options;
+                }
+            },
+    
+            createdNode: function (nodeID, childID, childExtendsID, childImplementsIDs,
+                childSource, childType, childIndex, childName, callback /* ( ready ) */) {
+                let self = this;
+                var node = this.state.nodes[childID];
+    
+                // If the "nodes" object does not have this object in it, it must not be one that
+                // this driver cares about
+                if (!node) {
+                    return;
+                }
+    
+                if (this.state.scenes[childID]) {
+                    let scene = this.state.scenes[childID];
+                    let space = scene.obj;
+                    space.bindMouse().bindTouch().play();
+
+                    //TODO: FIX
+                    
+
+                        // let el = document.createElement("space");
+                        // el.setAttribute("id", "space");
+                        // document.querySelector("body").appendChild(el);
+                        // Pts.namespace( window );
+                        //var form = scene.obj.getForm();
+                        //_self_.createAvatar.call(self, childID);    
+
+                      let avatarName = 'avatar-' + self.kernel.moniker();
+
+                        console.log("creating avatar...");
+             
+                        // let avatarID = self.kernel.moniker();
+                        // var nodeName = 'avatar-' + avatarID;
+             
+                        var newNode = {
+                            "id": avatarName,
+                            "uri": avatarName,
+                            "extends": "proxy/pts/pt.vwf",
+                            "properties": {}
+                        }
+             
+                        if (!self.state.nodes[avatarName]) {
+                            vwf_view.kernel.createChild(childID, avatarName, newNode);
+                           // vwf_view.kernel.callMethod(avatarName, "createPlayer", []);
+                        } 
+
+                }
+
+
+    
+                if (this.state.nodes[childID] && this.state.nodes[childID].obj) {
+                    this.nodes[childID] = {
+                        id: childID,
+                        extends: childExtendsID,
+                        liveBindings: {}
+                        // lastTransformStep: 0,
+                        // lastAnimationStep: 0 
+                    };
+
+                    if (this.nodes[childID].extends == "proxy/pts/pt.vwf") {
+
+                        console.log("CREATE PLAYER HERE!!");
+                        let scene = node.scene;
+                        let space = scene.obj;
+                        let form = scene.form;
+                        let color = "09f";
+    
+                        let player = {
+                            myID: childID,
+                            start: (bound) => {},
+                        
+                            animate: (time, ftime) => {
+                                //let radius = Num.cycle( (time%1000)/1000 ) * 20;
+                                //form.fill("#09f").point( node.obj, radius, "circle" );
+                              form.fill(color).point( node.obj, 10 );
+                            },
+                        
+                            action: (type, x, y) => {
+                                if(type == 'click')
+                                  console.log(x, ' - ', y);  
+
+                                  if(type == 'over')
+                                    console.log(x, ' - ', y);  
+                            }
+                          }
+                          space.add(player);
+
+                    }
+
+                }
+    
+    
+                // if(this.state.nodes[childID]) {
+                //     this.nodes[childID] = {id:childID,extends:childExtendsID};
+                // } 
+                // else if (this.state.nodes[childID] && this.state.nodes[childID].aframeObj.object3D instanceof THREE.Object3D) {
+                //     this.nodes[childID] = {id:childID,extends:childExtendsID};
+                // }
+    
+            },
+    
+            executed: function (nodeID, scriptText, scriptType) {
+                let self = this;
+                let node = this.state.nodes[nodeID];
+    
+                if (!(node)) {
+                    return;
+                }
+
+    
+            },
+    
+    
+            initializedNode: function (nodeID, childID) {
+                let self = this;
+                var node = this.state.nodes[childID];
+                if (!node) {
+                    return;
+                }
+    
+    
+            },
+    
+            createdProperty: function (nodeId, propertyName, propertyValue) {
+                return this.satProperty(nodeId, propertyName, propertyValue);
+            },
+    
+            initializedProperty: function (nodeId, propertyName, propertyValue) {
+                return this.satProperty(nodeId, propertyName, propertyValue);
+            },
+    
+            gotProperty: function (nodeId, propertyName, propertyValue) {
+
+                var node = this.state.nodes[nodeId];
+    
+                if (!(node && node.aframeObj)) {
+                    return;
+                }
+    
+    
+    
+    
+            },
+    
+            satProperty: function (nodeId, propertyName, propertyValue) {
+                let self = this;
+    
+                var node = this.state.nodes[nodeId];
+    
+                if (!(node && node.ptsObj)) {
+                    return;
+                }
+    
+            },
+    
+            deletedNode: function (childID) {
+                delete this.nodes[childID];
+            },
+    
+            firedEvent: function (nodeID, eventName, eventParameters) {
+                let self = this;
+                var node = this.state.nodes[nodeID];
+    
+                if (!(node)) {
+                    return;
+                }
+
+                var clientThatSatProperty = self.kernel.client();
+                var me = self.kernel.moniker();
+    
+                if (eventName == "changingTransformFromView") {
+    
+                    // If the transform property was initially updated by this view....
+                    if (clientThatSatProperty == me) {
+                        var node = this.state.nodes[nodeID];
+                        node.ignoreNextTransformUpdate = true;
+                    }
+                }
+    
+                //var avatarID = vwf_view.kernel.find("", avatarName)
+    
+                // if (eventName == "postLoadAction") {
+    
+                //     Object.entries(self.state.nodes).forEach(el => {
+                //         if (el[1].prototypes.includes("proxy/aframe/aentity.vwf")) {
+                //             vwf_view.kernel.callMethod(el[0], "setOwner", [self.kernel.moniker()]);
+                //         }
+    
+                //     });
+                // }
+    
+                 var avatarName = 'avatar-' + self.kernel.moniker();
+    
+                 if (eventName == "clickEvent" ||
+                    eventName == 'mousedownEvent' ||
+                    eventName == 'mouseupEvent'){
+
+                        if (clientThatSatProperty == me) {
+
+                        let methodName = eventName +'Method';
+                        self.kernel.callMethod(nodeID, methodName, eventParameters);
+
+                        if (eventName == "clickEvent"){
+
+                            let mode = vwf.getProperty(avatarName, 'selectMode');
+                            if (mode) {
+                                console.log("allow to click!!!")
+                                vwf_view.kernel.setProperty(avatarName, 'selectMode', false);
+        
+                                let editorDriver = vwf.views["/drivers/view/editor"];
+                                if (editorDriver) {
+                                    let selectSwitch = document.querySelector('#selectNodeSwitch');
+                                    // const selectSwitchComp = new mdc.iconButton.MDCIconButtonToggle(selectSwitch); //new mdc.select.MDCIconToggle
+                                    selectSwitch._comp.on = false;
+        
+                                    let currentNodeDIV = document.querySelector('#currentNode');
+                                    if (currentNodeDIV) currentNodeDIV._setNode(nodeID);
+        
+        
+                                }
+                            }
+
+                        }
+
+                    }
+                    }
+
+
+
+                let intersectEvents = ['fromhitstart', 'fromhitend', 'hitstart', 'hitend', 'intersect', 'clearIntersect']; //'intersect', 
+    
+                let hitEvent = intersectEvents.filter(el=> el == eventName.slice(0,-5))[0]; //slice Event word
+                if (hitEvent)
+                {
+    
+                    // If the transform property was initially updated by this view....
+                    if (clientThatSatProperty == me) {
+                            let methodName = eventName +'Method';
+                            self.kernel.callMethod(nodeID, methodName, eventParameters);
+
+                    }
+    
+                }
+        
+    
+    
+                // if (eventName == "clickEvent") {
+    
+                //     if (self.kernel.moniker() == eventParameters[0]) {
+    
+                //         let avatar = self.nodes[avatarName];
+                //         let mode = vwf.getProperty(avatarName, 'selectMode');
+    
+    
+                //         vwf_view.kernel.callMethod(nodeID, "clickEventMethod", [])
+    
+
+    
+    
+                //     }
+    
+    
+                // }
+            },
+    
+            ticked: function (vwfTime) {
+                let self = this;
+
+
+                _self_.updateAvatarPosition();
+               // _self_.updateFilters();
+    
+                //update vr controllers
+    
+                // if (this.isDesktop){
+                //     _self_.updateDesktopController('mouse-', '#mouse');
+                // }
+        
+                    
+                
+    
+                //lerpTick ();
+            },
+    
+            calledMethod: function (nodeID, methodName, methodParameters, methodValue) {
+                let self = this;
+                var node = this.state.nodes[nodeID];
+    
+                if (!(node && node.obj)) {
+                    return;
+                }
+    
+    
+           
+    
+                if (this.nodes[nodeID].extends == "proxy/pts/pt.vwf") {
+                    if(methodName == "createPlayer"){
+
+                        console.log("CREATE PLAYER HERE!!");
+                        let scene = node.scene;
+                        let space = scene.obj;
+                        let form = scene.form;
+    
+                        let player = {
+                            myID: nodeID,
+                            start: (bound) => {},
+                        
+                            animate: (time, ftime) => {
+                              form.point( node.obj, 10 );
+                            },
+                        
+                            action: (type, x, y) => {
+                                if(type == 'click')
+                                  console.log(x, ' - ', y);  
+                            }
+                          }
+                          space.add(player);
+
+                    }
+                }
+    
+            }
+        });
+    }
+
+
+
+
+    updateAvatarPosition() {
+        let self = this.instance;
+        let avatarName = 'avatar-' + self.kernel.moniker();
+        var node = self.state.nodes[avatarName];
+        var nodeView = self.state.nodes[avatarName];
+        if (!node) return;
+        if (!node.obj) return;
+
+        let space = node.scene.obj;
+        let position = space.pointer;
+
+        if(!nodeView.lastPosition){
+            nodeView.lastPosition = new Pt([position.x, position.y])
+        } 
+
+        let lastPosition = nodeView.lastPosition;
+
+        if(position && !(position.equals(lastPosition))){
+            self.kernel.setProperty(avatarName, "x", position.x);
+            self.kernel.setProperty(avatarName, "y", position.y);
+        }
+
+        nodeView.lastPosition.to([position.x, position.y])
+
+    }
+
+    updateDesktopController(aName, aSelector) {
+        let self = this.instance;
+         //let avatarName = 'avatar-' + self.kernel.moniker();
+ 
+         let delta = 0.001
+        
+         let avatarID = 'avatar-' + self.kernel.moniker();
+         let avatarName = aName + self.kernel.moniker();
+         var node = self.state.nodes[avatarName];
+         if (!node) return;
+         if (!node.aframeObj) return;
+ 
+         //let elA = document.querySelector('#avatarControlParent');
+         let elA = document.querySelector('#avatarControl');
+         let el = document.querySelector(aSelector);
+         let xrController = document.querySelector('#' + avatarName);
+         if (el && elA) {
+ 
+            //  let positionC = el.object3D.position.clone();
+            //  let positionA = elA.object3D.position.clone();
+            //  let position = positionC.add(positionA);
+
+            let position = elA.object3D.position;
+            //new THREE.Vector3(elA.object3D.position.x, elA.object3D.position.y-0.05, elA.object3D.position.z);
+
+            //let position = elA.object3D.position;
+
+            // let mouse = el.components["desktop-controls"]._mouse;
+            // self.kernel.callMethod(avatarName, "trackMouse",[mouse]);
+            var rotation = el.getAttribute('rotation');
+            var headRotation = el.object3D.quaternion;
+
+            if (self.isMobile) {
+                var headWorldQuat = new THREE.Quaternion();
+                elA.object3D.getWorldQuaternion(headWorldQuat);
+
+                rotation = this.getWorldRotation(elA, 'XYZ');
+                headRotation = headWorldQuat;
+            }
+            
+            //let rotation = el.getAttribute('rotation');
+            //((AFRAME.utils.device.isMobile() && !AFRAME.utils.device.isMobileVR()) || self.isDesktop) ? elA.getAttribute('rotation') : el.getAttribute('rotation');
+             //let rotation =  el.getAttribute('rotation'); //this.getWorldRotation(el, 'YXZ');
+
+
+             let lastRotation = self.nodes[avatarName].selfTickRotation;
+             let lastPosition = self.nodes[avatarName].selfTickPosition ? self.nodes[avatarName].selfTickPosition: new THREE.Vector3(0, 0, 0);
+ 
+             // let currentPosition = node.aframeObj.getAttribute('position');
+             //let currentRotation = node.aframeObj.getAttribute('rotation');
+ 
+             if (position && lastPosition) {
+                 let distance = lastPosition.distanceTo(position);
+ 
+                 if (distance > delta)
+                 {
+                    // console.log("position not equal");
+
+                    let idata = el.components["desktop-controls"].intersectionData;
+                    //if(idata) console.log('Point to: ', idata.point, ' intersect ', idata.elID);
+
+                    self.kernel.setProperty(avatarName, "position", position);
+                     self.kernel.callMethod(avatarName, "moveVRController",[idata]);
+                 }
+             }
+ 
+             if (rotation && lastRotation) {
+                 let distance = this.compareCoordinates(rotation, lastRotation, delta)
+ 
+                 if (distance)
+                 {
+                     //console.log("rotation not equal");
+
+
+                    let idata =  el.components["desktop-controls"].intersectionData;
+                    //if(idata) console.log('Point to: ', idata.point, ' intersect ', idata.elID);
+
+                     self.kernel.setProperty(avatarName, "rotation", rotation);
+                     self.kernel.callMethod(avatarName, "moveVRController",[idata]);
+
+                     self.kernel.callMethod(avatarID, "moveHead", [headRotation]);
+                 }
+             }
+ 
+            self.nodes[avatarName].selfTickPosition = position.clone();
+             self.nodes[avatarName].selfTickRotation = Object.assign({}, rotation);
+ 
+         }
+     }
+
+
+   updateHandControllerVR(aName, aSelector) {
+       let self = this.instance;
+        //let avatarName = 'avatar-' + self.kernel.moniker();
+
+        let delta = 0.001
+
+        let avatarName = aName + self.kernel.moniker();
+        var node = self.state.nodes[avatarName];
+        if (!node) return;
+        if (!node.aframeObj) return;
+
+        let elA = document.querySelector('#avatarControlParent');
+        let el = document.querySelector(aSelector);
+        if (el && elA) {
+
+            let positionC = el.object3D.position.clone();
+            let positionA = elA.object3D.position.clone();
+            let position = positionC.add(positionA);
+
+            let rotation = el.getAttribute('rotation'); //getWorldRotation(el, 'YXZ');
+
+            let lastRotation = self.nodes[avatarName].selfTickRotation;
+            let lastPosition = self.nodes[avatarName].selfTickPosition ? self.nodes[avatarName].selfTickPosition: new THREE.Vector3(0, 0, 0);
+
+            // let currentPosition = node.aframeObj.getAttribute('position');
+            //let currentRotation = node.aframeObj.getAttribute('rotation');
+
+            if (position && lastPosition) {
+                let distance = lastPosition.distanceTo(position);
+
+                if (distance > delta)
+                {
+                    //let idata = el.components["xrcontroller"].intersectionData;
+
+                let intersection = el.components.raycaster.intersections[0];
+                let point = intersection ? intersection.point : null;
+                let elID = intersection ? intersection.object.el.id : null;
+                let idata = point ? {
+                    point: point,
+                    elID: elID
+                } : null;
+               
+                   // console.log("position not equal");
+                    self.kernel.setProperty(avatarName, "position", position);
+                    self.kernel.callMethod(avatarName, "moveVRController",[idata]);
+                }
+            }
+
+            if (rotation && lastRotation) {
+                let distance = this.compareCoordinates(rotation, lastRotation, delta)
+
+                if (distance)
+                {
+                    let intersection = el.components.raycaster.intersections[0];
+                    let point = intersection ? intersection.point : null;
+                    let elID = intersection ? intersection.object.el.id : null;
+                    let idata = point ? {
+                        point: point,
+                        elID: elID
+                    } : null;
+                    //console.log("rotation not equal");
+                    self.kernel.setProperty(avatarName, "rotation", rotation);
+                    self.kernel.callMethod(avatarName, "moveVRController",[idata]);
+                }
+            }
+
+            // if (position && rotation && lastRotation && lastPosition) {
+            //     if (compareCoordinates(position, lastPosition, delta) || compareCoordinates(rotation, lastRotation, delta)) {
+            //         console.log("not equal!!");
+            //         vwf_view.kernel.callMethod(avatarName, "updateVRControl", [position, rotation]);
+            //     }
+            // }
+
+
+            //vwf_view.kernel.callMethod(avatarName, "updateVRControl", [position, rotation]);
+
+            self.nodes[avatarName].selfTickPosition = position.clone();
+            self.nodes[avatarName].selfTickRotation = Object.assign({}, rotation);
+
+        }
+    }
+
+
+     getMovementVector(el, vel) {
+        var directionVector = new THREE.Vector3(0, 0, 0);
+        var rotationEuler = new THREE.Euler(0, 0, 0, 'YXZ');
+    
+        
+          var rotation = el.getAttribute('rotation');
+          var velocity = vel;
+    
+          directionVector.copy(velocity);
+          directionVector.multiplyScalar(0.05);
+    
+          // Absolute.
+          if (!rotation) { return directionVector; }
+    
+          //xRotation = this.data.fly ? rotation.x : 0;
+    
+          // Transform direction relative to heading.
+          rotationEuler.set(THREE.Math.degToRad(0), THREE.Math.degToRad(rotation.y), 0);
+          directionVector.applyEuler(rotationEuler);
+          return directionVector;
+      }
+
+    setJoystickMoveInput (event) {
+        const axes = event.detail.axis;
+        //console.log(axes);
+        let el = document.querySelector('#avatarControl');
+        let position = new THREE.Vector3();
+        el.object3D.localToWorld(position);//getWorldPosition(position);
+        let vel = new THREE.Vector3(axes[0], 0, -axes[1]);
+        el.object3D.position.add(this.getMovementVector(el,vel));
+    }
+
+    setJoystickRotateY (event) {
+        const val = event.detail.value;
+        //console.log(val);
+        let el = document.querySelector('#avatarControl');
+        let rotation = el.object3D.rotation;
+        el.object3D.rotation.y += (-val)+rotation.y;
+        //el.object3D.rotation.set(rotation.x,-val+rotation.y, rotation.z)
+    }
+
+    setJoystickRotateX (event) {
+        const val = event.detail.value;
+        //console.log(val);
+        let el = document.querySelector('#avatarControl');
+        let rotation = el.object3D.rotation;
+        el.object3D.rotation.x += val+rotation.x;
+        //el.object3D.rotation.set(val+rotation.x, rotation.y, rotation.z)
+    }
+
+    async createAvatarControl(aScene) {
+        let self = this.instance;
+
+        let avatarName = 'avatar-' + self.kernel.moniker();
+
+        let avatarEl = document.createElement('a-entity');
+        avatarEl.setAttribute('id', 'avatarControlParent');
+
+
+
+       
+
+        //avatarEl.setAttribute('position', '0 1.6 0');
+
+        let controlEl = document.createElement('a-camera');
+
+        controlEl.setAttribute('id', 'avatarControl');
+        controlEl.setAttribute('wasd-controls', {acceleration:20});
+        controlEl.setAttribute('look-controls', { pointerLockEnabled: false});
+        controlEl.setAttribute('look-controls', 'enabled', true );
+
+        controlEl.setAttribute('camera', 'near', 0.1 );
+
+        //controlEl.setAttribute('gamepad-controls', {'controller': 0});
+
+        let cursorEl = document.createElement('a-cursor');
+        cursorEl.setAttribute('id', 'cursor-' + avatarName);
+        cursorEl.setAttribute('raycaster', {});
+        cursorEl.setAttribute('raycaster', 'objects', '.clickable');
+        cursorEl.setAttribute('raycaster', 'showLine', false);
+
+        if (self.d3DoF || _app.config.d3DoF) {
+            //avatarEl.setAttribute('gearvr-controls', {}); 
+            avatarEl.setAttribute('movement-controls', {});//{'controls': 'gamepad'});
+            //avatarEl.setAttribute("gamepad-controls", {});
+            //avatarEl.setAttribute('position', '0 0 0');
+        }
+
+        else if (AFRAME.utils.device.isMobile()) {
+            //self.state.showMobileJoystick()
+
+            //controlEl.setAttribute('look-controls', 'enabled', true );
+            controlEl.setAttribute("virtual-gamepad-controls", {});
+            controlEl.addEventListener("move", this.setJoystickMoveInput.bind(this));
+        }
+        //controlEl.addEventListener("rotateY", setJoystickRotateY);
+        //controlEl.addEventListener("rotateX", setJoystickRotateX);
+        
+
+        //controlEl.setAttribute('gearvr-controls',{});
+
+
+       
+
+        else if (self.isDesktop){
+            cursorEl.setAttribute('cursor',
+             {
+                rayOrigin: 'mouse'
+            });
+            cursorEl.setAttribute('visible', false);
+             
+        }
+
+        // cursorEl.setAttribute('raycaster', {objects: '.intersectable', showLine: true, far: 100});
+        // cursorEl.setAttribute('raycaster', 'showLine', true);
+        controlEl.appendChild(cursorEl);
+
+        avatarEl.appendChild(controlEl);
+
+
+        aScene.appendChild(avatarEl);
+
+        controlEl.setAttribute('camera', 'active', true);
+
+        //avatarEl.setAttribute('avatar', {});
+
+        // let arControl = document.createElement('a-entity');
+        // arControl.setAttribute('id', 'arControlParent');
+        // arControl.setAttribute('camera', {
+        //     active: true,
+        //     "look-controls-enabled": false,
+        //     "wasd-controls-enabled": false,
+        //     "user-height": 0
+        // });
+        // aScene.appendChild(arControl);
+
+
+        return "OK!"
+       // cb();
+
+
+
+    }
+
+    createXR(nodeID, nodeName, props) {
+        let self = this;
+        var newNode = {
+            "id": nodeName,
+            "uri": nodeName,
+            "extends": "proxy/aframe/xrcontroller.vwf",
+            "properties": {
+            }
+        }
+
+        if (!self.state.nodes[nodeName]) {
+
+            vwf_view.kernel.createChild(nodeID, nodeName, newNode);
+            vwf_view.kernel.callMethod(nodeName, "createController", [props.position]);
+            //"/../assets/controller/wmrvr.gltf"
+        }
+    }
+
+
+   createGearVRController(nodeID, nodeName) {
+        let self = this;
+        var newNode = {
+            "id": nodeName,
+            "uri": nodeName,
+            "extends": "proxy/aframe/gearvrcontroller.vwf",
+            "properties": {
+            }
+        }
+
+        if (!self.state.nodes[nodeName]) {
+
+            vwf_view.kernel.createChild(nodeID, nodeName, newNode);
+            vwf_view.kernel.callMethod(nodeName, "createController", []);
+            //"/../assets/controller/gearvr.gltf"
+        }
+    }
+
+    postLoadAction(nodeID) {
+
+        //vwf_view.kernel.fireEvent(nodeID, "postLoadAction")
+    }
+
+   createAvatar(nodeID) {
+        let self = this;
+
+       // vwf_view.kernel.fireEvent(nodeID, "createAvatar");
+
+       var avatarName = 'avatar-' + self.kernel.moniker();
+
+           console.log("creating avatar...");
+
+           // let avatarID = self.kernel.moniker();
+           // var nodeName = 'avatar-' + avatarID;
+
+           var newNode = {
+               "id": avatarName,
+               "uri": avatarName,
+               "extends": "proxy/aframe/avatar.vwf",
+               "properties": {
+                   "localUrl": '',
+                   "remoteUrl": '',
+                  // "displayName": 'Avatar ' + randId(),
+                   "sharing": { audio: true, video: true },
+                   "selectMode": false,
+                   "position": [0, 1.6, 0]
+               }
+           }
+
+           if (!self.state.nodes[avatarName]) {
+              
+
+               if (_LCSDB.user().is) {
+
+                _LCSDB.user().get('profile').get('alias').once(alias => {
+                           if (alias){
+
+                               newNode.properties.displayName = alias;
+                               //vwf_view.kernel.callMethod(avatarName, "setMyName", [alias]);
+                           }
+                           vwf_view.kernel.createChild(nodeID, avatarName, newNode);
+                           });
+
+                           _LCSDB.user().get('profile').get('avatarNode').not(res=>{
+                               //vwf_view.kernel.callMethod(avatarName, "createAvatarBody", []);
+                           })
+
+                           _LCSDB.user().get('profile').get('avatarNode').once(res => {
+                                   var myNode = null;
+                                   if (res) {
+                                       //myNode = JSON.parse(res.avatarNode);
+
+                                       var myNode = res;
+
+                                       if (_app.helpers.testJSON(res)){
+                                           myNode = JSON.parse(res);
+                                       }  
+
+                                       vwf_view.kernel.callMethod(avatarName, "createAvatarBody", [myNode, null]);
+                                   } else {
+                                       vwf_view.kernel.callMethod(avatarName, "createAvatarBody", []);
+                                   }
+                                  // newNode.properties.displayName = res.alias;
+       
+                                   
+                                   //"/../assets/avatars/male/avatar1.gltf"
+       
+       
+                                   //vwf_view.kernel.callMethod(avatarName, 'setUserAvatar', [res] );
+                               });
+
+
+                           
+                 
+               } else {
+
+                   vwf_view.kernel.createChild(nodeID, avatarName, newNode);
+                   vwf_view.kernel.callMethod(avatarName, "createAvatarBody", []);
+
+                   //"/../assets/avatars/male/avatar1.gltf"
+               }
+
+               //
+               
+
+           }
+    }
+
+   createGearVRControls() {
+
+        let avatarControl = document.querySelector('#avatarControlParent');
+
+        let el = document.createElement('a-entity');
+        el.setAttribute('id', 'gearvrcontrol');
+
+        el.setAttribute('gearvr-controls', {
+            'hand': 'right',
+            'model': true
+        });
+        //el.setAttribute('laser-controls', {hand: "right"});
+    
+        // gearvr.setAttribute('gearvr-controls', 'hand', 'right');
+
+        el.setAttribute('teleport-controls', { 
+            cameraRig: '#avatarControlParent',
+            teleportOrigin: '#avatarControl',
+            startEvents: 'teleportstart',
+            endEvents: 'teleportend'
+        });
+
+        el.setAttribute('gearvrcontrol', {});
+        avatarControl.appendChild(el);
+
+    }
+
+    createDesktopControls() {
+
+        let self = this.instance;
+        let avatarControl = document.querySelector('#avatarControlParent');
+
+        let el = document.createElement('a-entity');
+        el.setAttribute('id', 'mouse');
+        // el.setAttribute('geometry', {
+        //     primitive: 'box', width: 0.2, height: 0.2, depth: 1
+        // });
+        // el.setAttribute('position', {
+        //     x: 0, y: 0, z: -1
+        // });
+        el.setAttribute('desktop-controls', {});
+       // el.setAttribute('raycaster', {objects: ".intersectable", far: 1000, showLine: true});
+        avatarControl.appendChild(el);
+    }
+
+    createXRControls(hand) {
+
+        let self = this.instance;
+        let avatarControl = document.querySelector('#avatarControlParent');
+
+        let el = document.createElement('a-entity');
+        el.setAttribute('id', 'xrcontroller' + hand);
+
+        el.setAttribute('hand-controls', {
+            'hand': hand,
+            'handModelStyle': 'lowPoly',
+            'color': '#ffcccc'
+        });
+
+        el.setAttribute('laser-controls', {hand: hand, model:false});
+        //el.setAttribute('raycaster', {objects: ".collidable", far: 5, showLine: false});
+
+        // wmrvr.setAttribute('windows-motion-controls', '');
+        // wmrvr.setAttribute('windows-motion-controls', 'hand', hand);
+
+        el.setAttribute('xrcontroller', { 'hand': hand });
+
+        //add teleport controls
+        el.setAttribute('teleport-controls', { 
+            cameraRig: '#avatarControlParent',
+            teleportOrigin: '#avatarControl',
+            startEvents: 'teleportstart',
+            endEvents: 'teleportend'
+        });
+
+        avatarControl.appendChild(el);
+    }
+
+
+    updateMaterial(node) {
+        let self = this.instance;
+
+        let elID = '#' + node.aframeObj.getAttribute('id');
+        Object.entries(self.state.nodes).forEach(el => {
+            let material = el[1].aframeObj.getAttribute('material');
+            if (material) {
+                if (!material.src) {
+                    let materialID = vwf.find(el[0], 'material');
+                    self.kernel.callMethod(materialID, "refreshSrc", []);
+                }
+                else if (material.src) {
+                    if (material.src !== "") {
+                        let src = '#' + material.src.id;
+                        if (src == elID) {
+                            let materialID = vwf.find(el[0], 'material');
+                            self.kernel.callMethod(materialID, "updateSrc", [elID])
+                        }
+                    }
+                }
+            }
+        })
+
+    }
+
+}
+
+export { PTSView as default }

+ 6439 - 0
public/drivers/view/ptsjs/pts.js

@@ -0,0 +1,6439 @@
+/*!
+ * pts.js 0.10.5 - Copyright © 2017-2021 William Ngan and contributors.
+ * Licensed under Apache 2.0 License.
+ * See https://github.com/williamngan/pts for details.
+ */
+(function webpackUniversalModuleDefinition(root, factory) {
+	if(typeof exports === 'object' && typeof module === 'object')
+		module.exports = factory();
+	else if(typeof define === 'function' && define.amd)
+		define([], factory);
+	else if(typeof exports === 'object')
+		exports["Pts"] = factory();
+	else
+		root["Pts"] = factory();
+})(this, function() {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+/******/
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+/******/
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId]) {
+/******/ 			return installedModules[moduleId].exports;
+/******/ 		}
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			i: moduleId,
+/******/ 			l: false,
+/******/ 			exports: {}
+/******/ 		};
+/******/
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ 		// Flag the module as loaded
+/******/ 		module.l = true;
+/******/
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+/******/
+/******/
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+/******/
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+/******/
+/******/ 	// define getter function for harmony exports
+/******/ 	__webpack_require__.d = function(exports, name, getter) {
+/******/ 		if(!__webpack_require__.o(exports, name)) {
+/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
+/******/ 		}
+/******/ 	};
+/******/
+/******/ 	// define __esModule on exports
+/******/ 	__webpack_require__.r = function(exports) {
+/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
+/******/ 		}
+/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
+/******/ 	};
+/******/
+/******/ 	// create a fake namespace object
+/******/ 	// mode & 1: value is a module id, require it
+/******/ 	// mode & 2: merge all properties of value into the ns
+/******/ 	// mode & 4: return value when already ns object
+/******/ 	// mode & 8|1: behave like require
+/******/ 	__webpack_require__.t = function(value, mode) {
+/******/ 		if(mode & 1) value = __webpack_require__(value);
+/******/ 		if(mode & 8) return value;
+/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
+/******/ 		var ns = Object.create(null);
+/******/ 		__webpack_require__.r(ns);
+/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
+/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
+/******/ 		return ns;
+/******/ 	};
+/******/
+/******/ 	// getDefaultExport function for compatibility with non-harmony modules
+/******/ 	__webpack_require__.n = function(module) {
+/******/ 		var getter = module && module.__esModule ?
+/******/ 			function getDefault() { return module['default']; } :
+/******/ 			function getModuleExports() { return module; };
+/******/ 		__webpack_require__.d(getter, 'a', getter);
+/******/ 		return getter;
+/******/ 	};
+/******/
+/******/ 	// Object.prototype.hasOwnProperty.call
+/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+/******/
+/******/
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(__webpack_require__.s = "./src/_lib.ts");
+/******/ })
+/************************************************************************/
+/******/ ({
+
+/***/ "./src/Canvas.ts":
+/*!***********************!*\
+  !*** ./src/Canvas.ts ***!
+  \***********************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+/*! Source code licensed under Apache License 2.0. Copyright © 2017-current William Ngan and contributors. (https://github.com/williamngan/pts) */
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.CanvasForm = exports.CanvasSpace = void 0;
+const Space_1 = __webpack_require__(/*! ./Space */ "./src/Space.ts");
+const Form_1 = __webpack_require__(/*! ./Form */ "./src/Form.ts");
+const Pt_1 = __webpack_require__(/*! ./Pt */ "./src/Pt.ts");
+const Util_1 = __webpack_require__(/*! ./Util */ "./src/Util.ts");
+const Typography_1 = __webpack_require__(/*! ./Typography */ "./src/Typography.ts");
+const Op_1 = __webpack_require__(/*! ./Op */ "./src/Op.ts");
+const Image_1 = __webpack_require__(/*! ./Image */ "./src/Image.ts");
+class CanvasSpace extends Space_1.MultiTouchSpace {
+    constructor(elem, callback) {
+        super();
+        this._pixelScale = 1;
+        this._autoResize = true;
+        this._bgcolor = "#e1e9f0";
+        this._offscreen = false;
+        this._initialResize = false;
+        var _selector = null;
+        var _existed = false;
+        this.id = "pt";
+        if (elem instanceof Element) {
+            _selector = elem;
+            this.id = "pts_existing_space";
+        }
+        else {
+            let id = elem;
+            id = (elem[0] === "#" || elem[0] === ".") ? elem : "#" + elem;
+            _selector = document.querySelector(id);
+            _existed = true;
+            this.id = id.substr(1);
+        }
+        if (!_selector) {
+            this._container = this._createElement("div", this.id + "_container");
+            this._canvas = this._createElement("canvas", this.id);
+            this._container.appendChild(this._canvas);
+            document.body.appendChild(this._container);
+            _existed = false;
+        }
+        else if (_selector.nodeName.toLowerCase() != "canvas") {
+            this._container = _selector;
+            this._canvas = this._createElement("canvas", this.id + "_canvas");
+            this._container.appendChild(this._canvas);
+            this._initialResize = true;
+        }
+        else {
+            this._canvas = _selector;
+            this._container = _selector.parentElement;
+            this._autoResize = false;
+        }
+        setTimeout(this._ready.bind(this, callback), 100);
+        this._ctx = this._canvas.getContext('2d');
+    }
+    _createElement(elem = "div", id) {
+        let d = document.createElement(elem);
+        d.setAttribute("id", id);
+        return d;
+    }
+    _ready(callback) {
+        if (!this._container)
+            throw new Error(`Cannot initiate #${this.id} element`);
+        this._isReady = true;
+        this._resizeHandler(null);
+        this.clear(this._bgcolor);
+        this._canvas.dispatchEvent(new Event("ready"));
+        for (let k in this.players) {
+            if (this.players.hasOwnProperty(k)) {
+                if (this.players[k].start)
+                    this.players[k].start(this.bound.clone(), this);
+            }
+        }
+        this._pointer = this.center;
+        this._initialResize = false;
+        if (callback)
+            callback(this.bound, this._canvas);
+    }
+    setup(opt) {
+        this._bgcolor = opt.bgcolor ? opt.bgcolor : "transparent";
+        this.autoResize = (opt.resize != undefined) ? opt.resize : false;
+        if (opt.retina !== false) {
+            let r1 = window ? window.devicePixelRatio || 1 : 1;
+            let r2 = this._ctx.webkitBackingStorePixelRatio || this._ctx.mozBackingStorePixelRatio || this._ctx.msBackingStorePixelRatio || this._ctx.oBackingStorePixelRatio || this._ctx.backingStorePixelRatio || 1;
+            this._pixelScale = Math.max(1, r1 / r2);
+        }
+        if (opt.offscreen) {
+            this._offscreen = true;
+            this._offCanvas = this._createElement("canvas", this.id + "_offscreen");
+            this._offCtx = this._offCanvas.getContext('2d');
+        }
+        else {
+            this._offscreen = false;
+        }
+        return this;
+    }
+    set autoResize(auto) {
+        if (!window)
+            return;
+        this._autoResize = auto;
+        if (auto) {
+            window.addEventListener('resize', this._resizeHandler.bind(this));
+        }
+        else {
+            window.removeEventListener('resize', this._resizeHandler.bind(this));
+        }
+    }
+    get autoResize() { return this._autoResize; }
+    resize(b, evt) {
+        this.bound = b;
+        this._canvas.width = this.bound.size.x * this._pixelScale;
+        this._canvas.height = this.bound.size.y * this._pixelScale;
+        this._canvas.style.width = Math.floor(this.bound.size.x) + "px";
+        this._canvas.style.height = Math.floor(this.bound.size.y) + "px";
+        if (this._offscreen) {
+            this._offCanvas.width = this.bound.size.x * this._pixelScale;
+            this._offCanvas.height = this.bound.size.y * this._pixelScale;
+        }
+        if (this._pixelScale != 1) {
+            this._ctx.scale(this._pixelScale, this._pixelScale);
+            if (this._offscreen) {
+                this._offCtx.scale(this._pixelScale, this._pixelScale);
+            }
+        }
+        for (let k in this.players) {
+            if (this.players.hasOwnProperty(k)) {
+                let p = this.players[k];
+                if (p.resize)
+                    p.resize(this.bound, evt);
+            }
+        }
+        this.render(this._ctx);
+        if (evt && !this.isPlaying)
+            this.playOnce(0);
+        return this;
+    }
+    _resizeHandler(evt) {
+        if (!window)
+            return;
+        let b = (this._autoResize || this._initialResize) ? this._container.getBoundingClientRect() : this._canvas.getBoundingClientRect();
+        if (b) {
+            let box = Pt_1.Bound.fromBoundingRect(b);
+            box.center = box.center.add(window.pageXOffset, window.pageYOffset);
+            this.resize(box, evt);
+        }
+    }
+    set background(bg) { this._bgcolor = bg; }
+    get background() { return this._bgcolor; }
+    get pixelScale() {
+        return this._pixelScale;
+    }
+    get hasOffscreen() {
+        return this._offscreen;
+    }
+    get offscreenCtx() { return this._offCtx; }
+    get offscreenCanvas() { return this._offCanvas; }
+    getForm() { return new CanvasForm(this); }
+    get element() {
+        return this._canvas;
+    }
+    get parent() {
+        return this._container;
+    }
+    get ready() {
+        return this._isReady;
+    }
+    get ctx() { return this._ctx; }
+    clear(bg) {
+        if (bg)
+            this._bgcolor = bg;
+        const lastColor = this._ctx.fillStyle;
+        if (!this._bgcolor || this._bgcolor === "transparent") {
+            this._ctx.clearRect(-1, -1, this._canvas.width + 1, this._canvas.height + 1);
+        }
+        else {
+            if (this._bgcolor.indexOf("rgba") === 0 || (this._bgcolor.length === 9 && this._bgcolor.indexOf("#") === 0)) {
+                this._ctx.clearRect(-1, -1, this._canvas.width + 1, this._canvas.height + 1);
+            }
+            this._ctx.fillStyle = this._bgcolor;
+            this._ctx.fillRect(-1, -1, this._canvas.width + 1, this._canvas.height + 1);
+        }
+        this._ctx.fillStyle = lastColor;
+        return this;
+    }
+    clearOffscreen(bg) {
+        if (this._offscreen) {
+            if (bg) {
+                this._offCtx.fillStyle = bg;
+                this._offCtx.fillRect(-1, -1, this._canvas.width + 1, this._canvas.height + 1);
+            }
+            else {
+                this._offCtx.clearRect(-1, -1, this._offCanvas.width + 1, this._offCanvas.height + 1);
+            }
+        }
+        return this;
+    }
+    playItems(time) {
+        if (this._isReady) {
+            this._ctx.save();
+            if (this._offscreen)
+                this._offCtx.save();
+            super.playItems(time);
+            this._ctx.restore();
+            if (this._offscreen)
+                this._offCtx.restore();
+            this.render(this._ctx);
+        }
+    }
+    dispose() {
+        if (!window)
+            return;
+        window.removeEventListener('resize', this._resizeHandler.bind(this));
+        this.stop();
+        this.removeAll();
+        return this;
+    }
+    recorder(downloadOrCallback, filetype = "webm", bitrate = 15000000) {
+        let stream = this._canvas.captureStream();
+        const recorder = new MediaRecorder(stream, { mimeType: `video/${filetype}`, bitsPerSecond: bitrate });
+        recorder.ondataavailable = function (d) {
+            let url = URL.createObjectURL(new Blob([d.data], { type: `video/${filetype}` }));
+            if (typeof downloadOrCallback === "function") {
+                downloadOrCallback(url);
+            }
+            else if (downloadOrCallback) {
+                let a = document.createElement("a");
+                a.href = url;
+                a.download = `canvas_video.${filetype}`;
+                a.click();
+                a.remove();
+            }
+        };
+        return recorder;
+    }
+}
+exports.CanvasSpace = CanvasSpace;
+class CanvasForm extends Form_1.VisualForm {
+    constructor(space) {
+        super();
+        this._style = {
+            fillStyle: "#f03", strokeStyle: "#fff",
+            lineWidth: 1, lineJoin: "bevel", lineCap: "butt",
+            globalAlpha: 1
+        };
+        const _setup = (ctx) => {
+            this._ctx = ctx;
+            this._ctx.fillStyle = this._style.fillStyle;
+            this._ctx.strokeStyle = this._style.strokeStyle;
+            this._ctx.lineJoin = "bevel";
+            this._ctx.font = this._font.value;
+            this._ready = true;
+        };
+        if (space instanceof CanvasRenderingContext2D) {
+            _setup(space);
+        }
+        else {
+            this._space = space;
+            this._space.add({ start: () => {
+                    _setup(this._space.ctx);
+                } });
+        }
+    }
+    get space() { return this._space; }
+    get ctx() { return this._space.ctx; }
+    useOffscreen(off = true, clear = false) {
+        if (clear)
+            this._space.clearOffscreen((typeof clear == "string") ? clear : null);
+        this._ctx = (this._space.hasOffscreen && off) ? this._space.offscreenCtx : this._space.ctx;
+        return this;
+    }
+    renderOffscreen(offset = [0, 0]) {
+        if (this._space.hasOffscreen) {
+            this._space.ctx.drawImage(this._space.offscreenCanvas, offset[0], offset[1], this._space.width, this._space.height);
+        }
+    }
+    alpha(a) {
+        this._ctx.globalAlpha = a;
+        this._style.globalAlpha = a;
+        return this;
+    }
+    fill(c) {
+        if (typeof c == "boolean") {
+            this.filled = c;
+        }
+        else {
+            this.filled = true;
+            this._style.fillStyle = c;
+            this._ctx.fillStyle = c;
+        }
+        return this;
+    }
+    stroke(c, width, linejoin, linecap) {
+        if (typeof c == "boolean") {
+            this.stroked = c;
+        }
+        else {
+            this.stroked = true;
+            this._style.strokeStyle = c;
+            this._ctx.strokeStyle = c;
+            if (width) {
+                this._ctx.lineWidth = width;
+                this._style.lineWidth = width;
+            }
+            if (linejoin) {
+                this._ctx.lineJoin = linejoin;
+                this._style.lineJoin = linejoin;
+            }
+            if (linecap) {
+                this._ctx.lineCap = linecap;
+                this._style.lineCap = linecap;
+            }
+        }
+        return this;
+    }
+    gradient(stops) {
+        let vals = [];
+        if (stops.length < 2)
+            stops.push([0.99, "#000"], [1, "#000"]);
+        for (let i = 0, len = stops.length; i < len; i++) {
+            let t = typeof stops[i] === 'string' ? i * (1 / (stops.length - 1)) : stops[i][0];
+            let v = typeof stops[i] === 'string' ? stops[i] : stops[i][1];
+            vals.push([t, v]);
+        }
+        return (area1, area2) => {
+            area1 = area1.map(a => a.abs());
+            if (area2)
+                area2.map(a => a.abs());
+            let grad = area2
+                ? this.ctx.createRadialGradient(area1[0][0], area1[0][1], area1[1][0], area2[0][0], area2[0][1], area2[1][0])
+                : this.ctx.createLinearGradient(area1[0][0], area1[0][1], area1[1][0], area1[1][1]);
+            for (let i = 0, len = vals.length; i < len; i++) {
+                grad.addColorStop(vals[i][0], vals[i][1]);
+            }
+            return grad;
+        };
+    }
+    composite(mode = 'source-over') {
+        this.ctx.globalCompositeOperation = mode;
+        return this;
+    }
+    clip() {
+        this.ctx.clip();
+        return this;
+    }
+    dash(segments = true, offset = 0) {
+        if (!segments) {
+            this._ctx.setLineDash([]);
+            this._ctx.lineDashOffset = 0;
+        }
+        else {
+            if (segments === true) {
+                segments = [5, 5];
+            }
+            this._ctx.setLineDash([segments[0], segments[1]]);
+            this._ctx.lineDashOffset = offset;
+        }
+        return this;
+    }
+    font(sizeOrFont, weight, style, lineHeight, family) {
+        if (typeof sizeOrFont == "number") {
+            this._font.size = sizeOrFont;
+            if (family)
+                this._font.face = family;
+            if (weight)
+                this._font.weight = weight;
+            if (style)
+                this._font.style = style;
+            if (lineHeight)
+                this._font.lineHeight = lineHeight;
+        }
+        else {
+            this._font = sizeOrFont;
+        }
+        this._ctx.font = this._font.value;
+        if (this._estimateTextWidth)
+            this.fontWidthEstimate(true);
+        return this;
+    }
+    fontWidthEstimate(estimate = true) {
+        this._estimateTextWidth = (estimate) ? Typography_1.Typography.textWidthEstimator(((c) => this._ctx.measureText(c).width)) : undefined;
+        return this;
+    }
+    getTextWidth(c) {
+        return (!this._estimateTextWidth) ? this._ctx.measureText(c + " .").width : this._estimateTextWidth(c);
+    }
+    _textTruncate(str, width, tail = "") {
+        return Typography_1.Typography.truncate(this.getTextWidth.bind(this), str, width, tail);
+    }
+    _textAlign(box, vertical, offset, center) {
+        let _box = Util_1.Util.iterToArray(box);
+        if (!Util_1.Util.arrayCheck(_box))
+            return;
+        if (!center)
+            center = Op_1.Rectangle.center(_box);
+        var px = _box[0][0];
+        if (this._ctx.textAlign == "end" || this._ctx.textAlign == "right") {
+            px = _box[1][0];
+        }
+        else if (this._ctx.textAlign == "center" || this._ctx.textAlign == "middle") {
+            px = center[0];
+        }
+        var py = center[1];
+        if (vertical == "top" || vertical == "start") {
+            py = _box[0][1];
+        }
+        else if (vertical == "end" || vertical == "bottom") {
+            py = _box[1][1];
+        }
+        return (offset) ? new Pt_1.Pt(px + offset[0], py + offset[1]) : new Pt_1.Pt(px, py);
+    }
+    reset() {
+        for (let k in this._style) {
+            if (this._style.hasOwnProperty(k)) {
+                this._ctx[k] = this._style[k];
+            }
+        }
+        this._font = new Form_1.Font();
+        this._ctx.font = this._font.value;
+        return this;
+    }
+    _paint() {
+        if (this._filled)
+            this._ctx.fill();
+        if (this._stroked)
+            this._ctx.stroke();
+    }
+    static point(ctx, p, radius = 5, shape = "square") {
+        if (!p)
+            return;
+        if (!CanvasForm[shape])
+            throw new Error(`${shape} is not a static function of CanvasForm`);
+        CanvasForm[shape](ctx, p, radius);
+    }
+    point(p, radius = 5, shape = "square") {
+        CanvasForm.point(this._ctx, p, radius, shape);
+        this._paint();
+        return this;
+    }
+    static circle(ctx, pt, radius = 10) {
+        if (!pt)
+            return;
+        ctx.beginPath();
+        ctx.arc(pt[0], pt[1], radius, 0, Util_1.Const.two_pi, false);
+        ctx.closePath();
+    }
+    circle(pts) {
+        let p = Util_1.Util.iterToArray(pts);
+        CanvasForm.circle(this._ctx, p[0], p[1][0]);
+        this._paint();
+        return this;
+    }
+    static ellipse(ctx, pt, radius, rotation = 0, startAngle = 0, endAngle = Util_1.Const.two_pi, cc = false) {
+        if (!pt || !radius)
+            return;
+        ctx.beginPath();
+        ctx.ellipse(pt[0], pt[1], radius[0], radius[1], rotation, startAngle, endAngle, cc);
+    }
+    ellipse(pt, radius, rotation = 0, startAngle = 0, endAngle = Util_1.Const.two_pi, cc = false) {
+        CanvasForm.ellipse(this._ctx, pt, radius, rotation, startAngle, endAngle, cc);
+        this._paint();
+        return this;
+    }
+    static arc(ctx, pt, radius, startAngle, endAngle, cc) {
+        if (!pt)
+            return;
+        ctx.beginPath();
+        ctx.arc(pt[0], pt[1], radius, startAngle, endAngle, cc);
+    }
+    arc(pt, radius, startAngle, endAngle, cc) {
+        CanvasForm.arc(this._ctx, pt, radius, startAngle, endAngle, cc);
+        this._paint();
+        return this;
+    }
+    static square(ctx, pt, halfsize) {
+        if (!pt)
+            return;
+        let x1 = pt[0] - halfsize;
+        let y1 = pt[1] - halfsize;
+        let x2 = pt[0] + halfsize;
+        let y2 = pt[1] + halfsize;
+        ctx.beginPath();
+        ctx.moveTo(x1, y1);
+        ctx.lineTo(x1, y2);
+        ctx.lineTo(x2, y2);
+        ctx.lineTo(x2, y1);
+        ctx.closePath();
+    }
+    square(pt, halfsize) {
+        CanvasForm.square(this._ctx, pt, halfsize);
+        this._paint();
+        return this;
+    }
+    static line(ctx, pts) {
+        if (!Util_1.Util.arrayCheck(pts))
+            return;
+        let i = 0;
+        ctx.beginPath();
+        for (let it of pts) {
+            if (it) {
+                if (i++ > 0) {
+                    ctx.lineTo(it[0], it[1]);
+                }
+                else {
+                    ctx.moveTo(it[0], it[1]);
+                }
+            }
+        }
+    }
+    line(pts) {
+        CanvasForm.line(this._ctx, pts);
+        this._paint();
+        return this;
+    }
+    static polygon(ctx, pts) {
+        if (!Util_1.Util.arrayCheck(pts))
+            return;
+        CanvasForm.line(ctx, pts);
+        ctx.closePath();
+    }
+    polygon(pts) {
+        CanvasForm.polygon(this._ctx, pts);
+        this._paint();
+        return this;
+    }
+    static rect(ctx, pts) {
+        let p = Util_1.Util.iterToArray(pts);
+        if (!Util_1.Util.arrayCheck(p))
+            return;
+        ctx.beginPath();
+        ctx.moveTo(p[0][0], p[0][1]);
+        ctx.lineTo(p[0][0], p[1][1]);
+        ctx.lineTo(p[1][0], p[1][1]);
+        ctx.lineTo(p[1][0], p[0][1]);
+        ctx.closePath();
+    }
+    rect(pts) {
+        CanvasForm.rect(this._ctx, pts);
+        this._paint();
+        return this;
+    }
+    static image(ctx, ptOrRect, img, orig) {
+        let t = Util_1.Util.iterToArray(ptOrRect);
+        let pos;
+        if (typeof t[0] === "number") {
+            pos = t;
+        }
+        else {
+            if (orig) {
+                let o = Util_1.Util.iterToArray(orig);
+                pos = [o[0][0], o[0][1], o[1][0] - o[0][0], o[1][1] - o[0][1],
+                    t[0][0], t[0][1], t[1][0] - t[0][0], t[1][1] - t[0][1]];
+            }
+            else {
+                pos = [t[0][0], t[0][1], t[1][0] - t[0][0], t[1][1] - t[0][1]];
+            }
+        }
+        if (img instanceof Image_1.Img) {
+            if (img.loaded) {
+                ctx.drawImage(img.image, ...pos);
+            }
+        }
+        else {
+            ctx.drawImage(img, ...pos);
+        }
+    }
+    image(ptOrRect, img, orig) {
+        if (img instanceof Image_1.Img) {
+            if (img.loaded) {
+                CanvasForm.image(this._ctx, ptOrRect, img.image, orig);
+            }
+        }
+        else {
+            CanvasForm.image(this._ctx, ptOrRect, img, orig);
+        }
+        return this;
+    }
+    static imageData(ctx, ptOrRect, img) {
+        let t = Util_1.Util.iterToArray(ptOrRect);
+        if (typeof t[0] === "number") {
+            ctx.putImageData(img, t[0], t[1]);
+        }
+        else {
+            ctx.putImageData(img, t[0][0], t[0][1], t[0][0], t[0][1], t[1][0], t[1][1]);
+        }
+    }
+    imageData(ptOrRect, img) {
+        CanvasForm.imageData(this._ctx, ptOrRect, img);
+        return this;
+    }
+    static text(ctx, pt, txt, maxWidth) {
+        if (!pt)
+            return;
+        ctx.fillText(txt, pt[0], pt[1], maxWidth);
+    }
+    text(pt, txt, maxWidth) {
+        CanvasForm.text(this._ctx, pt, txt, maxWidth);
+        return this;
+    }
+    textBox(box, txt, verticalAlign = "middle", tail = "", overrideBaseline = true) {
+        if (overrideBaseline)
+            this._ctx.textBaseline = verticalAlign;
+        let size = Op_1.Rectangle.size(box);
+        let t = this._textTruncate(txt, size[0], tail);
+        this.text(this._textAlign(box, verticalAlign), t[0]);
+        return this;
+    }
+    paragraphBox(box, txt, lineHeight = 1.2, verticalAlign = "top", crop = true) {
+        let b = Util_1.Util.iterToArray(box);
+        let size = Op_1.Rectangle.size(b);
+        this._ctx.textBaseline = "top";
+        let lstep = this._font.size * lineHeight;
+        let nextLine = (sub, buffer = [], cc = 0) => {
+            if (!sub)
+                return buffer;
+            if (crop && cc * lstep > size[1] - lstep * 2)
+                return buffer;
+            if (cc > 10000)
+                throw new Error("max recursion reached (10000)");
+            let t = this._textTruncate(sub, size[0], "");
+            let newln = t[0].indexOf("\n");
+            if (newln >= 0) {
+                buffer.push(t[0].substr(0, newln));
+                return nextLine(sub.substr(newln + 1), buffer, cc + 1);
+            }
+            let dt = t[0].lastIndexOf(" ") + 1;
+            if (dt <= 0 || t[1] === sub.length)
+                dt = undefined;
+            let line = t[0].substr(0, dt);
+            buffer.push(line);
+            return (t[1] <= 0 || t[1] === sub.length) ? buffer : nextLine(sub.substr((dt || t[1])), buffer, cc + 1);
+        };
+        let lines = nextLine(txt);
+        let lsize = lines.length * lstep;
+        let lbox = b;
+        if (verticalAlign == "middle" || verticalAlign == "center") {
+            let lpad = (size[1] - lsize) / 2;
+            if (crop)
+                lpad = Math.max(0, lpad);
+            lbox = new Pt_1.Group(b[0].$add(0, lpad), b[1].$subtract(0, lpad));
+        }
+        else if (verticalAlign == "bottom") {
+            lbox = new Pt_1.Group(b[0].$add(0, size[1] - lsize), b[1]);
+        }
+        else {
+            lbox = new Pt_1.Group(b[0], b[0].$add(size[0], lsize));
+        }
+        let center = Op_1.Rectangle.center(lbox);
+        for (let i = 0, len = lines.length; i < len; i++) {
+            this.text(this._textAlign(lbox, "top", [0, i * lstep], center), lines[i]);
+        }
+        return this;
+    }
+    alignText(alignment = "left", baseline = "alphabetic") {
+        if (baseline == "center")
+            baseline = "middle";
+        if (baseline == "baseline")
+            baseline = "alphabetic";
+        this._ctx.textAlign = alignment;
+        this._ctx.textBaseline = baseline;
+        return this;
+    }
+    log(txt) {
+        let w = this._ctx.measureText(txt).width + 20;
+        this.stroke(false).fill("rgba(0,0,0,.4)").rect([[0, 0], [w, 20]]);
+        this.fill("#fff").text([10, 14], txt);
+        return this;
+    }
+}
+exports.CanvasForm = CanvasForm;
+
+
+/***/ }),
+
+/***/ "./src/Color.ts":
+/*!**********************!*\
+  !*** ./src/Color.ts ***!
+  \**********************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+/*! Source code licensed under Apache License 2.0. Copyright © 2017-current William Ngan and contributors. (https://github.com/williamngan/pts) */
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.Color = void 0;
+const Pt_1 = __webpack_require__(/*! ./Pt */ "./src/Pt.ts");
+const Util_1 = __webpack_require__(/*! ./Util */ "./src/Util.ts");
+const Num_1 = __webpack_require__(/*! ./Num */ "./src/Num.ts");
+class Color extends Pt_1.Pt {
+    constructor(...args) {
+        super(...args);
+        this._mode = "rgb";
+        this._isNorm = false;
+    }
+    static from(...args) {
+        let p = [1, 1, 1, 1];
+        let c = Util_1.Util.getArgs(args);
+        for (let i = 0, len = p.length; i < len; i++) {
+            if (i < c.length)
+                p[i] = c[i];
+        }
+        return new Color(p);
+    }
+    static fromHex(hex) {
+        if (hex[0] == "#")
+            hex = hex.substr(1);
+        if (hex.length <= 3) {
+            let fn = (i) => hex[i] || "F";
+            hex = `${fn(0)}${fn(0)}${fn(1)}${fn(1)}${fn(2)}${fn(2)}`;
+        }
+        let alpha = 1;
+        if (hex.length === 8) {
+            alpha = hex.substr(6) && 0xFF / 255;
+            hex = hex.substring(0, 6);
+        }
+        let hexVal = parseInt(hex, 16);
+        return new Color(hexVal >> 16, hexVal >> 8 & 0xFF, hexVal & 0xFF, alpha);
+    }
+    static rgb(...args) { return Color.from(...args).toMode("rgb"); }
+    static hsl(...args) { return Color.from(...args).toMode("hsl"); }
+    static hsb(...args) { return Color.from(...args).toMode("hsb"); }
+    static lab(...args) { return Color.from(...args).toMode("lab"); }
+    static lch(...args) { return Color.from(...args).toMode("lch"); }
+    static luv(...args) { return Color.from(...args).toMode("luv"); }
+    static xyz(...args) { return Color.from(...args).toMode("xyz"); }
+    static maxValues(mode) { return Color.ranges[mode].zipSlice(1).$take([0, 1, 2]); }
+    get hex() { return this.toString("hex"); }
+    get rgb() { return this.toString("rgb"); }
+    get rgba() { return this.toString("rgba"); }
+    clone() {
+        let c = new Color(this);
+        c.toMode(this._mode);
+        return c;
+    }
+    toMode(mode, convert = false) {
+        if (convert) {
+            let fname = this._mode.toUpperCase() + "to" + mode.toUpperCase();
+            if (Color[fname]) {
+                this.to(Color[fname](this, this._isNorm, this._isNorm));
+            }
+            else {
+                throw new Error("Cannot convert color with " + fname);
+            }
+        }
+        this._mode = mode;
+        return this;
+    }
+    get mode() { return this._mode; }
+    get r() { return this[0]; }
+    set r(n) { this[0] = n; }
+    get g() { return this[1]; }
+    set g(n) { this[1] = n; }
+    get b() { return this[2]; }
+    set b(n) { this[2] = n; }
+    get h() { return (this._mode == "lch") ? this[2] : this[0]; }
+    set h(n) {
+        let i = (this._mode == "lch") ? 2 : 0;
+        this[i] = n;
+    }
+    get s() { return this[1]; }
+    set s(n) { this[1] = n; }
+    get l() { return (this._mode == "hsl") ? this[2] : this[0]; }
+    set l(n) {
+        let i = (this._mode == "hsl") ? 2 : 0;
+        this[i] = n;
+    }
+    get a() { return this[1]; }
+    set a(n) { this[1] = n; }
+    get c() { return this[1]; }
+    set c(n) { this[1] = n; }
+    get u() { return this[1]; }
+    set u(n) { this[1] = n; }
+    get v() { return this[2]; }
+    set v(n) { this[2] = n; }
+    set alpha(n) { if (this.length > 3)
+        this[3] = n; }
+    get alpha() { return (this.length > 3) ? this[3] : 1; }
+    get normalized() { return this._isNorm; }
+    set normalized(b) { this._isNorm = b; }
+    normalize(toNorm = true) {
+        if (this._isNorm == toNorm)
+            return this;
+        let ranges = Color.ranges[this._mode];
+        for (let i = 0; i < 3; i++) {
+            this[i] = (!toNorm)
+                ? Num_1.Num.mapToRange(this[i], 0, 1, ranges[i][0], ranges[i][1])
+                : Num_1.Num.mapToRange(this[i], ranges[i][0], ranges[i][1], 0, 1);
+        }
+        this._isNorm = toNorm;
+        return this;
+    }
+    $normalize(toNorm = true) { return this.clone().normalize(toNorm); }
+    toString(format = "mode") {
+        if (format == "hex") {
+            let _hex = (n) => {
+                let s = Math.floor(n).toString(16);
+                return (s.length < 2) ? '0' + s : s;
+            };
+            return `#${_hex(this[0])}${_hex(this[1])}${_hex(this[2])}`;
+        }
+        else if (format == "rgba") {
+            return `rgba(${Math.floor(this[0])},${Math.floor(this[1])},${Math.floor(this[2])},${this.alpha})`;
+        }
+        else if (format == "rgb") {
+            return `rgb(${Math.floor(this[0])},${Math.floor(this[1])},${Math.floor(this[2])})`;
+        }
+        else {
+            return `${this._mode}(${this[0]},${this[1]},${this[2]},${this.alpha})`;
+        }
+    }
+    static RGBtoHSL(rgb, normalizedInput = false, normalizedOutput = false) {
+        let [r, g, b] = (!normalizedInput) ? rgb.$normalize() : rgb;
+        let max = Math.max(r, g, b);
+        let min = Math.min(r, g, b);
+        let h = (max + min) / 2;
+        let s = h;
+        let l = h;
+        if (max == min) {
+            h = 0;
+            s = 0;
+        }
+        else {
+            let d = max - min;
+            s = (l > 0.5) ? d / (2 - max - min) : d / (max + min);
+            h = 0;
+            if (max === r) {
+                h = (g - b) / d + ((g < b) ? 6 : 0);
+            }
+            else if (max === g) {
+                h = (b - r) / d + 2;
+            }
+            else if (max === b) {
+                h = (r - g) / d + 4;
+            }
+        }
+        return Color.hsl(((normalizedOutput) ? h / 60 : h * 60), s, l, rgb.alpha);
+    }
+    static HSLtoRGB(hsl, normalizedInput = false, normalizedOutput = false) {
+        let [h, s, l] = hsl;
+        if (!normalizedInput)
+            h = h / 360;
+        if (s == 0)
+            return Color.rgb(l * 255, l * 255, l * 255, hsl.alpha);
+        let q = (l <= 0.5) ? l * (1 + s) : l + s - (l * s);
+        let p = 2 * l - q;
+        let convert = (t) => {
+            t = (t < 0) ? t + 1 : (t > 1) ? t - 1 : t;
+            if (t * 6 < 1) {
+                return p + (q - p) * t * 6;
+            }
+            else if (t * 2 < 1) {
+                return q;
+            }
+            else if (t * 3 < 2) {
+                return p + (q - p) * ((2 / 3) - t) * 6;
+            }
+            else {
+                return p;
+            }
+        };
+        let sc = (normalizedOutput) ? 1 : 255;
+        return Color.rgb(sc * convert((h + 1 / 3)), sc * convert(h), sc * convert((h - 1 / 3)), hsl.alpha);
+    }
+    static RGBtoHSB(rgb, normalizedInput = false, normalizedOutput = false) {
+        let [r, g, b] = (!normalizedInput) ? rgb.$normalize() : rgb;
+        let max = Math.max(r, g, b);
+        let min = Math.min(r, g, b);
+        let d = max - min;
+        let h = 0;
+        let s = (max === 0) ? 0 : d / max;
+        let v = max;
+        if (max != min) {
+            if (max === r) {
+                h = (g - b) / d + ((g < b) ? 6 : 0);
+            }
+            else if (max === g) {
+                h = (b - r) / d + 2;
+            }
+            else if (max === b) {
+                h = (r - g) / d + 4;
+            }
+        }
+        return Color.hsb(((normalizedOutput) ? h / 60 : h * 60), s, v, rgb.alpha);
+    }
+    static HSBtoRGB(hsb, normalizedInput = false, normalizedOutput = false) {
+        let [h, s, v] = hsb;
+        if (!normalizedInput)
+            h = h / 360;
+        let i = Math.floor(h * 6);
+        let f = h * 6 - i;
+        let p = v * (1 - s);
+        let q = v * (1 - f * s);
+        let t = v * (1 - (1 - f) * s);
+        let pick = [
+            [v, t, p], [q, v, p], [p, v, t],
+            [p, q, v], [t, p, v], [v, p, q]
+        ];
+        let c = pick[i % 6];
+        let sc = (normalizedOutput) ? 1 : 255;
+        return Color.rgb(sc * c[0], sc * c[1], sc * c[2], hsb.alpha);
+    }
+    static RGBtoLAB(rgb, normalizedInput = false, normalizedOutput = false) {
+        let c = (normalizedInput) ? rgb.$normalize(false) : rgb;
+        return Color.XYZtoLAB(Color.RGBtoXYZ(c), false, normalizedOutput);
+    }
+    static LABtoRGB(lab, normalizedInput = false, normalizedOutput = false) {
+        let c = (normalizedInput) ? lab.$normalize(false) : lab;
+        return Color.XYZtoRGB(Color.LABtoXYZ(c), false, normalizedOutput);
+    }
+    static RGBtoLCH(rgb, normalizedInput = false, normalizedOutput = false) {
+        let c = (normalizedInput) ? rgb.$normalize(false) : rgb;
+        return Color.LABtoLCH(Color.RGBtoLAB(c), false, normalizedOutput);
+    }
+    static LCHtoRGB(lch, normalizedInput = false, normalizedOutput = false) {
+        let c = (normalizedInput) ? lch.$normalize(false) : lch;
+        return Color.LABtoRGB(Color.LCHtoLAB(c), false, normalizedOutput);
+    }
+    static RGBtoLUV(rgb, normalizedInput = false, normalizedOutput = false) {
+        let c = (normalizedInput) ? rgb.$normalize(false) : rgb;
+        return Color.XYZtoLUV(Color.RGBtoXYZ(c), false, normalizedOutput);
+    }
+    static LUVtoRGB(luv, normalizedInput = false, normalizedOutput = false) {
+        let c = (normalizedInput) ? luv.$normalize(false) : luv;
+        return Color.XYZtoRGB(Color.LUVtoXYZ(c), false, normalizedOutput);
+    }
+    static RGBtoXYZ(rgb, normalizedInput = false, normalizedOutput = false) {
+        let c = (!normalizedInput) ? rgb.$normalize() : rgb.clone();
+        for (let i = 0; i < 3; i++) {
+            c[i] = (c[i] > 0.04045) ? Math.pow((c[i] + 0.055) / 1.055, 2.4) : c[i] / 12.92;
+            if (!normalizedOutput)
+                c[i] = c[i] * 100;
+        }
+        let cc = Color.xyz(c[0] * 0.4124564 + c[1] * 0.3575761 + c[2] * 0.1804375, c[0] * 0.2126729 + c[1] * 0.7151522 + c[2] * 0.0721750, c[0] * 0.0193339 + c[1] * 0.1191920 + c[2] * 0.9503041, rgb.alpha);
+        return (normalizedOutput) ? cc.normalize() : cc;
+    }
+    static XYZtoRGB(xyz, normalizedInput = false, normalizedOutput = false) {
+        let [x, y, z] = (!normalizedInput) ? xyz.$normalize() : xyz;
+        let rgb = [
+            x * 3.2404542 + y * -1.5371385 + z * -0.4985314,
+            x * -0.9692660 + y * 1.8760108 + z * 0.0415560,
+            x * 0.0556434 + y * -0.2040259 + z * 1.0572252
+        ];
+        for (let i = 0; i < 3; i++) {
+            rgb[i] = (rgb[i] < 0) ? 0 : (rgb[i] > 0.0031308) ? (1.055 * Math.pow(rgb[i], 1 / 2.4) - 0.055) : (12.92 * rgb[i]);
+            rgb[i] = Math.max(0, Math.min(1, rgb[i]));
+            if (!normalizedOutput)
+                rgb[i] = Math.round(rgb[i] * 255);
+        }
+        let cc = Color.rgb(rgb[0], rgb[1], rgb[2], xyz.alpha);
+        return (normalizedOutput) ? cc.normalize() : cc;
+    }
+    static XYZtoLAB(xyz, normalizedInput = false, normalizedOutput = false) {
+        let c = (normalizedInput) ? xyz.$normalize(false) : xyz.clone();
+        c.divide(Color.D65);
+        let fn = (n) => (n > 0.008856) ? Math.pow(n, 1 / 3) : (7.787 * n) + 16 / 116;
+        let cy = fn(c[1]);
+        let cc = Color.lab((116 * cy) - 16, 500 * (fn(c[0]) - cy), 200 * (cy - fn(c[2])), xyz.alpha);
+        return (normalizedOutput) ? cc.normalize() : cc;
+    }
+    static LABtoXYZ(lab, normalizedInput = false, normalizedOutput = false) {
+        let c = (normalizedInput) ? lab.$normalize(false) : lab;
+        let y = (c[0] + 16) / 116;
+        let x = (c[1] / 500) + y;
+        let z = y - c[2] / 200;
+        let fn = (n) => {
+            let nnn = n * n * n;
+            return (nnn > 0.008856) ? nnn : (n - 16 / 116) / 7.787;
+        };
+        let d = Color.D65;
+        let cc = Color.xyz(Math.max(0, d[0] * fn(x)), Math.max(0, d[1] * fn(y)), Math.max(0, d[2] * fn(z)), lab.alpha);
+        return (normalizedOutput) ? cc.normalize() : cc;
+    }
+    static XYZtoLUV(xyz, normalizedInput = false, normalizedOutput = false) {
+        let [x, y, z] = (normalizedInput) ? xyz.$normalize(false) : xyz;
+        let u = (4 * x) / (x + (15 * y) + (3 * z));
+        let v = (9 * y) / (x + (15 * y) + (3 * z));
+        y = y / 100;
+        y = (y > 0.008856) ? Math.pow(y, 1 / 3) : (7.787 * y + 16 / 116);
+        let refU = (4 * Color.D65[0]) / (Color.D65[0] + (15 * Color.D65[1]) + (3 * Color.D65[2]));
+        let refV = (9 * Color.D65[1]) / (Color.D65[0] + (15 * Color.D65[1]) + (3 * Color.D65[2]));
+        let L = (116 * y) - 16;
+        return Color.luv(L, 13 * L * (u - refU), 13 * L * (v - refV), xyz.alpha);
+    }
+    static LUVtoXYZ(luv, normalizedInput = false, normalizedOutput = false) {
+        let [l, u, v] = (normalizedInput) ? luv.$normalize(false) : luv;
+        let y = (l + 16) / 116;
+        let cubeY = y * y * y;
+        y = (cubeY > 0.008856) ? cubeY : (y - 16 / 116) / 7.787;
+        let refU = (4 * Color.D65[0]) / (Color.D65[0] + (15 * Color.D65[1]) + (3 * Color.D65[2]));
+        let refV = (9 * Color.D65[1]) / (Color.D65[0] + (15 * Color.D65[1]) + (3 * Color.D65[2]));
+        u = u / (13 * l) + refU;
+        v = v / (13 * l) + refV;
+        y = y * 100;
+        let x = -1 * (9 * y * u) / ((u - 4) * v - u * v);
+        let z = (9 * y - (15 * v * y) - (v * x)) / (3 * v);
+        return Color.xyz(x, y, z, luv.alpha);
+    }
+    static LABtoLCH(lab, normalizedInput = false, normalizedOutput = false) {
+        let c = (normalizedInput) ? lab.$normalize(false) : lab;
+        let h = Num_1.Geom.toDegree(Num_1.Geom.boundRadian(Math.atan2(c[2], c[1])));
+        return Color.lch(c[0], Math.sqrt(c[1] * c[1] + c[2] * c[2]), h, lab.alpha);
+    }
+    static LCHtoLAB(lch, normalizedInput = false, normalizedOutput = false) {
+        let c = (normalizedInput) ? lch.$normalize(false) : lch;
+        let rad = Num_1.Geom.toRadian(c[2]);
+        return Color.lab(c[0], Math.cos(rad) * c[1], Math.sin(rad) * c[1], lch.alpha);
+    }
+}
+exports.Color = Color;
+Color.D65 = new Pt_1.Pt(95.047, 100, 108.883, 1);
+Color.ranges = {
+    rgb: new Pt_1.Group(new Pt_1.Pt(0, 255), new Pt_1.Pt(0, 255), new Pt_1.Pt(0, 255)),
+    hsl: new Pt_1.Group(new Pt_1.Pt(0, 360), new Pt_1.Pt(0, 1), new Pt_1.Pt(0, 1)),
+    hsb: new Pt_1.Group(new Pt_1.Pt(0, 360), new Pt_1.Pt(0, 1), new Pt_1.Pt(0, 1)),
+    lab: new Pt_1.Group(new Pt_1.Pt(0, 100), new Pt_1.Pt(-128, 127), new Pt_1.Pt(-128, 127)),
+    lch: new Pt_1.Group(new Pt_1.Pt(0, 100), new Pt_1.Pt(0, 100), new Pt_1.Pt(0, 360)),
+    luv: new Pt_1.Group(new Pt_1.Pt(0, 100), new Pt_1.Pt(-134, 220), new Pt_1.Pt(-140, 122)),
+    xyz: new Pt_1.Group(new Pt_1.Pt(0, 100), new Pt_1.Pt(0, 100), new Pt_1.Pt(0, 100))
+};
+
+
+/***/ }),
+
+/***/ "./src/Create.ts":
+/*!***********************!*\
+  !*** ./src/Create.ts ***!
+  \***********************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+/*! Source code licensed under Apache License 2.0. Copyright © 2017-current William Ngan and contributors. (https://github.com/williamngan/pts) */
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.Delaunay = exports.Noise = exports.Create = void 0;
+const Pt_1 = __webpack_require__(/*! ./Pt */ "./src/Pt.ts");
+const Op_1 = __webpack_require__(/*! ./Op */ "./src/Op.ts");
+const Util_1 = __webpack_require__(/*! ./Util */ "./src/Util.ts");
+const Num_1 = __webpack_require__(/*! ./Num */ "./src/Num.ts");
+const LinearAlgebra_1 = __webpack_require__(/*! ./LinearAlgebra */ "./src/LinearAlgebra.ts");
+class Create {
+    static distributeRandom(bound, count, dimensions = 2) {
+        let pts = new Pt_1.Group();
+        for (let i = 0; i < count; i++) {
+            let p = [bound.x + Math.random() * bound.width];
+            if (dimensions > 1)
+                p.push(bound.y + Math.random() * bound.height);
+            if (dimensions > 2)
+                p.push(bound.z + Math.random() * bound.depth);
+            pts.push(new Pt_1.Pt(p));
+        }
+        return pts;
+    }
+    static distributeLinear(line, count) {
+        let _line = Util_1.Util.iterToArray(line);
+        let ln = Op_1.Line.subpoints(_line, count - 2);
+        ln.unshift(_line[0]);
+        ln.push(_line[_line.length - 1]);
+        return ln;
+    }
+    static gridPts(bound, columns, rows, orientation = [0.5, 0.5]) {
+        if (columns === 0 || rows === 0)
+            throw new Error("grid columns and rows cannot be 0");
+        let unit = bound.size.$subtract(1).$divide(columns, rows);
+        let offset = unit.$multiply(orientation);
+        let g = new Pt_1.Group();
+        for (let r = 0; r < rows; r++) {
+            for (let c = 0; c < columns; c++) {
+                g.push(bound.topLeft.$add(unit.$multiply(c, r)).add(offset));
+            }
+        }
+        return g;
+    }
+    static gridCells(bound, columns, rows) {
+        if (columns === 0 || rows === 0)
+            throw new Error("grid columns and rows cannot be 0");
+        let unit = bound.size.$subtract(1).divide(columns, rows);
+        let g = [];
+        for (let r = 0; r < rows; r++) {
+            for (let c = 0; c < columns; c++) {
+                g.push(new Pt_1.Group(bound.topLeft.$add(unit.$multiply(c, r)), bound.topLeft.$add(unit.$multiply(c, r).add(unit))));
+            }
+        }
+        return g;
+    }
+    static radialPts(center, radius, count, angleOffset = -Util_1.Const.half_pi) {
+        let g = new Pt_1.Group();
+        let a = Util_1.Const.two_pi / count;
+        for (let i = 0; i < count; i++) {
+            g.push(new Pt_1.Pt(center).toAngle(a * i + angleOffset, radius, true));
+        }
+        return g;
+    }
+    static noisePts(pts, dx = 0.01, dy = 0.01, rows = 0, columns = 0) {
+        let seed = Math.random();
+        let g = new Pt_1.Group();
+        let i = 0;
+        for (let p of pts) {
+            let np = new Noise(p);
+            let r = (rows && rows > 0) ? Math.floor(i / rows) : i;
+            let c = (columns && columns > 0) ? i % columns : i;
+            np.initNoise(dx * c, dy * r);
+            np.seed(seed);
+            g.push(np);
+            i++;
+        }
+        return g;
+    }
+    static delaunay(pts) {
+        return Delaunay.from(pts);
+    }
+}
+exports.Create = Create;
+const __noise_grad3 = [
+    [1, 1, 0], [-1, 1, 0], [1, -1, 0], [-1, -1, 0],
+    [1, 0, 1], [-1, 0, 1], [1, 0, -1], [-1, 0, -1],
+    [0, 1, 1], [0, -1, 1], [0, 1, -1], [0, -1, -1]
+];
+const __noise_permTable = [151, 160, 137, 91, 90, 15,
+    131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23,
+    190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33,
+    88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166,
+    77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244,
+    102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196,
+    135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123,
+    5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42,
+    223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,
+    129, 22, 39, 253, 9, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228,
+    251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107,
+    49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254,
+    138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180
+];
+class Noise extends Pt_1.Pt {
+    constructor(...args) {
+        super(...args);
+        this.perm = [];
+        this._n = new Pt_1.Pt(0.01, 0.01);
+        this.perm = __noise_permTable.concat(__noise_permTable);
+    }
+    initNoise(...args) {
+        this._n = new Pt_1.Pt(...args);
+        return this;
+    }
+    step(x = 0, y = 0) {
+        this._n.add(x, y);
+        return this;
+    }
+    seed(s) {
+        if (s > 0 && s < 1)
+            s *= 65536;
+        s = Math.floor(s);
+        if (s < 256)
+            s |= s << 8;
+        for (let i = 0; i < 255; i++) {
+            let v = (i & 1) ? __noise_permTable[i] ^ (s & 255) : __noise_permTable[i] ^ ((s >> 8) & 255);
+            this.perm[i] = this.perm[i + 256] = v;
+        }
+        return this;
+    }
+    noise2D() {
+        let i = Math.max(0, Math.floor(this._n[0])) % 255;
+        let j = Math.max(0, Math.floor(this._n[1])) % 255;
+        let x = (this._n[0] % 255) - i;
+        let y = (this._n[1] % 255) - j;
+        let n00 = LinearAlgebra_1.Vec.dot(__noise_grad3[(i + this.perm[j]) % 12], [x, y, 0]);
+        let n01 = LinearAlgebra_1.Vec.dot(__noise_grad3[(i + this.perm[j + 1]) % 12], [x, y - 1, 0]);
+        let n10 = LinearAlgebra_1.Vec.dot(__noise_grad3[(i + 1 + this.perm[j]) % 12], [x - 1, y, 0]);
+        let n11 = LinearAlgebra_1.Vec.dot(__noise_grad3[(i + 1 + this.perm[j + 1]) % 12], [x - 1, y - 1, 0]);
+        let _fade = (f) => f * f * f * (f * (f * 6 - 15) + 10);
+        let tx = _fade(x);
+        return Num_1.Num.lerp(Num_1.Num.lerp(n00, n10, tx), Num_1.Num.lerp(n01, n11, tx), _fade(y));
+    }
+}
+exports.Noise = Noise;
+class Delaunay extends Pt_1.Group {
+    constructor() {
+        super(...arguments);
+        this._mesh = [];
+    }
+    delaunay(triangleOnly = true) {
+        if (this.length < 3)
+            return [];
+        this._mesh = [];
+        let n = this.length;
+        let indices = [];
+        for (let i = 0; i < n; i++)
+            indices[i] = i;
+        indices.sort((i, j) => this[j][0] - this[i][0]);
+        let pts = this.slice();
+        let st = this._superTriangle();
+        pts = pts.concat(st);
+        let opened = [this._circum(n, n + 1, n + 2, st)];
+        let closed = [];
+        let tris = [];
+        for (let i = 0, len = indices.length; i < len; i++) {
+            let c = indices[i];
+            let edges = [];
+            let j = opened.length;
+            if (!this._mesh[c])
+                this._mesh[c] = {};
+            while (j--) {
+                let circum = opened[j];
+                let radius = circum.circle[1][0];
+                let d = pts[c].$subtract(circum.circle[0]);
+                if (d[0] > 0 && d[0] * d[0] > radius * radius) {
+                    closed.push(circum);
+                    tris.push(circum.triangle);
+                    opened.splice(j, 1);
+                    continue;
+                }
+                if (d[0] * d[0] + d[1] * d[1] - radius * radius > Util_1.Const.epsilon) {
+                    continue;
+                }
+                edges.push(circum.i, circum.j, circum.j, circum.k, circum.k, circum.i);
+                opened.splice(j, 1);
+            }
+            Delaunay._dedupe(edges);
+            j = edges.length;
+            while (j > 1) {
+                opened.push(this._circum(edges[--j], edges[--j], c, false, pts));
+            }
+        }
+        for (let i = 0, len = opened.length; i < len; i++) {
+            let o = opened[i];
+            if (o.i < n && o.j < n && o.k < n) {
+                closed.push(o);
+                tris.push(o.triangle);
+                this._cache(o);
+            }
+        }
+        return (triangleOnly) ? tris : closed;
+    }
+    voronoi() {
+        let vs = [];
+        let n = this._mesh;
+        for (let i = 0, len = n.length; i < len; i++) {
+            vs.push(this.neighborPts(i, true));
+        }
+        return vs;
+    }
+    mesh() {
+        return this._mesh;
+    }
+    neighborPts(i, sort = false) {
+        let cs = new Pt_1.Group();
+        let n = this._mesh;
+        for (let k in n[i]) {
+            if (n[i].hasOwnProperty(k))
+                cs.push(n[i][k].circle[0]);
+        }
+        return (sort) ? Num_1.Geom.sortEdges(cs) : cs;
+    }
+    neighbors(i) {
+        let cs = [];
+        let n = this._mesh;
+        for (let k in n[i]) {
+            if (n[i].hasOwnProperty(k))
+                cs.push(n[i][k]);
+        }
+        return cs;
+    }
+    _cache(o) {
+        this._mesh[o.i][`${Math.min(o.j, o.k)}-${Math.max(o.j, o.k)}`] = o;
+        this._mesh[o.j][`${Math.min(o.i, o.k)}-${Math.max(o.i, o.k)}`] = o;
+        this._mesh[o.k][`${Math.min(o.i, o.j)}-${Math.max(o.i, o.j)}`] = o;
+    }
+    _superTriangle() {
+        let minPt = this[0];
+        let maxPt = this[0];
+        for (let i = 1, len = this.length; i < len; i++) {
+            minPt = minPt.$min(this[i]);
+            maxPt = maxPt.$max(this[i]);
+        }
+        let d = maxPt.$subtract(minPt);
+        let mid = minPt.$add(maxPt).divide(2);
+        let dmax = Math.max(d[0], d[1]);
+        return new Pt_1.Group(mid.$subtract(20 * dmax, dmax), mid.$add(0, 20 * dmax), mid.$add(20 * dmax, -dmax));
+    }
+    _triangle(i, j, k, pts = this) {
+        return new Pt_1.Group(pts[i], pts[j], pts[k]);
+    }
+    _circum(i, j, k, tri, pts = this) {
+        let t = tri || this._triangle(i, j, k, pts);
+        return {
+            i: i,
+            j: j,
+            k: k,
+            triangle: t,
+            circle: Op_1.Triangle.circumcircle(t)
+        };
+    }
+    static _dedupe(edges) {
+        let j = edges.length;
+        while (j > 1) {
+            let b = edges[--j];
+            let a = edges[--j];
+            let i = j;
+            while (i > 1) {
+                let n = edges[--i];
+                let m = edges[--i];
+                if ((a == m && b == n) || (a == n && b == m)) {
+                    edges.splice(j, 2);
+                    edges.splice(i, 2);
+                    break;
+                }
+            }
+        }
+        return edges;
+    }
+}
+exports.Delaunay = Delaunay;
+
+
+/***/ }),
+
+/***/ "./src/Dom.ts":
+/*!********************!*\
+  !*** ./src/Dom.ts ***!
+  \********************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+/*! Source code licensed under Apache License 2.0. Copyright © 2017-current William Ngan and contributors. (https://github.com/williamngan/pts) */
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.HTMLForm = exports.HTMLSpace = exports.DOMSpace = void 0;
+const Space_1 = __webpack_require__(/*! ./Space */ "./src/Space.ts");
+const Form_1 = __webpack_require__(/*! ./Form */ "./src/Form.ts");
+const Util_1 = __webpack_require__(/*! ./Util */ "./src/Util.ts");
+const Pt_1 = __webpack_require__(/*! ./Pt */ "./src/Pt.ts");
+class DOMSpace extends Space_1.MultiTouchSpace {
+    constructor(elem, callback) {
+        super();
+        this.id = "domspace";
+        this._autoResize = true;
+        this._bgcolor = "#e1e9f0";
+        this._css = {};
+        var _selector = null;
+        var _existed = false;
+        this.id = "pts";
+        if (elem instanceof Element) {
+            _selector = elem;
+            this.id = "pts_existing_space";
+        }
+        else {
+            _selector = document.querySelector(elem);
+            _existed = true;
+            this.id = elem.substr(1);
+        }
+        if (!_selector) {
+            this._container = DOMSpace.createElement("div", "pts_container");
+            this._canvas = DOMSpace.createElement("div", "pts_element");
+            this._container.appendChild(this._canvas);
+            document.body.appendChild(this._container);
+            _existed = false;
+        }
+        else {
+            this._canvas = _selector;
+            this._container = _selector.parentElement;
+        }
+        setTimeout(this._ready.bind(this, callback), 50);
+    }
+    static createElement(elem = "div", id, appendTo) {
+        let d = document.createElement(elem);
+        if (id)
+            d.setAttribute("id", id);
+        if (appendTo && appendTo.appendChild)
+            appendTo.appendChild(d);
+        return d;
+    }
+    _ready(callback) {
+        if (!this._container)
+            throw new Error(`Cannot initiate #${this.id} element`);
+        this._isReady = true;
+        this._resizeHandler(null);
+        this.clear(this._bgcolor);
+        this._canvas.dispatchEvent(new Event("ready"));
+        for (let k in this.players) {
+            if (this.players.hasOwnProperty(k)) {
+                if (this.players[k].start)
+                    this.players[k].start(this.bound.clone(), this);
+            }
+        }
+        this._pointer = this.center;
+        this.refresh(false);
+        if (callback)
+            callback(this.bound, this._canvas);
+    }
+    setup(opt) {
+        if (opt.bgcolor) {
+            this._bgcolor = opt.bgcolor;
+        }
+        this.autoResize = (opt.resize != undefined) ? opt.resize : false;
+        return this;
+    }
+    getForm() {
+        return null;
+    }
+    set autoResize(auto) {
+        this._autoResize = auto;
+        if (auto) {
+            window.addEventListener('resize', this._resizeHandler.bind(this));
+        }
+        else {
+            delete this._css['width'];
+            delete this._css['height'];
+            window.removeEventListener('resize', this._resizeHandler.bind(this));
+        }
+    }
+    get autoResize() { return this._autoResize; }
+    resize(b, evt) {
+        this.bound = b;
+        this.styles({ width: `${b.width}px`, height: `${b.height}px` }, true);
+        for (let k in this.players) {
+            if (this.players.hasOwnProperty(k)) {
+                let p = this.players[k];
+                if (p.resize)
+                    p.resize(this.bound, evt);
+            }
+        }
+        return this;
+    }
+    _resizeHandler(evt) {
+        let b = Pt_1.Bound.fromBoundingRect(this._container.getBoundingClientRect());
+        if (this._autoResize) {
+            this.styles({ width: "100%", height: "100%" }, true);
+        }
+        else {
+            this.styles({ width: `${b.width}px`, height: `${b.height}px` }, true);
+        }
+        this.resize(b, evt);
+    }
+    get element() {
+        return this._canvas;
+    }
+    get parent() {
+        return this._container;
+    }
+    get ready() { return this._isReady; }
+    clear(bg) {
+        if (bg)
+            this.background = bg;
+        this._canvas.innerHTML = "";
+        return this;
+    }
+    set background(bg) {
+        this._bgcolor = bg;
+        this._container.style.backgroundColor = this._bgcolor;
+    }
+    get background() { return this._bgcolor; }
+    style(key, val, update = false) {
+        this._css[key] = val;
+        if (update)
+            this._canvas.style[key] = val;
+        return this;
+    }
+    styles(styles, update = false) {
+        for (let k in styles) {
+            if (styles.hasOwnProperty(k))
+                this.style(k, styles[k], update);
+        }
+        return this;
+    }
+    static setAttr(elem, data) {
+        for (let k in data) {
+            if (data.hasOwnProperty(k)) {
+                elem.setAttribute(k, data[k]);
+            }
+        }
+        return elem;
+    }
+    static getInlineStyles(data) {
+        let str = "";
+        for (let k in data) {
+            if (data.hasOwnProperty(k)) {
+                if (data[k])
+                    str += `${k}: ${data[k]}; `;
+            }
+        }
+        return str;
+    }
+    dispose() {
+        window.removeEventListener('resize', this._resizeHandler.bind(this));
+        this.stop();
+        this.removeAll();
+        return this;
+    }
+}
+exports.DOMSpace = DOMSpace;
+class HTMLSpace extends DOMSpace {
+    getForm() {
+        return new HTMLForm(this);
+    }
+    static htmlElement(parent, name, id, autoClass = true) {
+        if (!parent || !parent.appendChild)
+            throw new Error("parent is not a valid DOM element");
+        let elem = document.querySelector(`#${id}`);
+        if (!elem) {
+            elem = document.createElement(name);
+            elem.setAttribute("id", id);
+            if (autoClass)
+                elem.setAttribute("class", id.substring(0, id.indexOf("-")));
+            parent.appendChild(elem);
+        }
+        return elem;
+    }
+    remove(player) {
+        let temp = this._container.querySelectorAll("." + HTMLForm.scopeID(player));
+        temp.forEach((el) => {
+            el.parentNode.removeChild(el);
+        });
+        return super.remove(player);
+    }
+    removeAll() {
+        this._container.innerHTML = "";
+        return super.removeAll();
+    }
+}
+exports.HTMLSpace = HTMLSpace;
+class HTMLForm extends Form_1.VisualForm {
+    constructor(space) {
+        super();
+        this._style = {
+            "filled": true,
+            "stroked": true,
+            "background": "#f03",
+            "border-color": "#fff",
+            "color": "#000",
+            "border-width": "1px",
+            "border-radius": "0",
+            "border-style": "solid",
+            "opacity": 1,
+            "position": "absolute",
+            "top": 0,
+            "left": 0,
+            "width": 0,
+            "height": 0
+        };
+        this._ctx = {
+            group: null,
+            groupID: "pts",
+            groupCount: 0,
+            currentID: "pts0",
+            currentClass: "",
+            style: {},
+        };
+        this._ready = false;
+        this._space = space;
+        this._space.add({ start: () => {
+                this._ctx.group = this._space.element;
+                this._ctx.groupID = "pts_dom_" + (HTMLForm.groupID++);
+                this._ctx.style = Object.assign({}, this._style);
+                this._ready = true;
+            } });
+    }
+    get space() { return this._space; }
+    styleTo(k, v, unit = '') {
+        if (this._ctx.style[k] === undefined)
+            throw new Error(`${k} style property doesn't exist`);
+        this._ctx.style[k] = `${v}${unit}`;
+    }
+    alpha(a) {
+        this.styleTo("opacity", a);
+        return this;
+    }
+    fill(c) {
+        if (typeof c == "boolean") {
+            this.styleTo("filled", c);
+            if (!c)
+                this.styleTo("background", "transparent");
+        }
+        else {
+            this.styleTo("filled", true);
+            this.styleTo("background", c);
+        }
+        return this;
+    }
+    stroke(c, width, linejoin, linecap) {
+        if (typeof c == "boolean") {
+            this.styleTo("stroked", c);
+            if (!c)
+                this.styleTo("border-width", 0);
+        }
+        else {
+            this.styleTo("stroked", true);
+            this.styleTo("border-color", c);
+            this.styleTo("border-width", (width || 1) + "px");
+        }
+        return this;
+    }
+    fillText(c) {
+        this.styleTo("color", c);
+        return this;
+    }
+    cls(c) {
+        if (typeof c == "boolean") {
+            this._ctx.currentClass = "";
+        }
+        else {
+            this._ctx.currentClass = c;
+        }
+        return this;
+    }
+    font(sizeOrFont, weight, style, lineHeight, family) {
+        if (typeof sizeOrFont == "number") {
+            this._font.size = sizeOrFont;
+            if (family)
+                this._font.face = family;
+            if (weight)
+                this._font.weight = weight;
+            if (style)
+                this._font.style = style;
+            if (lineHeight)
+                this._font.lineHeight = lineHeight;
+        }
+        else {
+            this._font = sizeOrFont;
+        }
+        this._ctx.style['font'] = this._font.value;
+        return this;
+    }
+    reset() {
+        this._ctx.style = Object.assign({}, this._style);
+        this._font = new Form_1.Font(10, "sans-serif");
+        this._ctx.style['font'] = this._font.value;
+        return this;
+    }
+    updateScope(group_id, group) {
+        this._ctx.group = group;
+        this._ctx.groupID = group_id;
+        this._ctx.groupCount = 0;
+        this.nextID();
+        return this._ctx;
+    }
+    scope(item) {
+        if (!item || item.animateID == null)
+            throw new Error("item not defined or not yet added to Space");
+        return this.updateScope(HTMLForm.scopeID(item), this.space.element);
+    }
+    nextID() {
+        this._ctx.groupCount++;
+        this._ctx.currentID = `${this._ctx.groupID}-${this._ctx.groupCount}`;
+        return this._ctx.currentID;
+    }
+    static getID(ctx) {
+        return ctx.currentID || `p-${HTMLForm.domID++}`;
+    }
+    static scopeID(item) {
+        return `item-${item.animateID}`;
+    }
+    static style(elem, styles) {
+        let st = [];
+        if (!styles["filled"])
+            st.push("background: none");
+        if (!styles["stroked"])
+            st.push("border: none");
+        for (let k in styles) {
+            if (styles.hasOwnProperty(k) && k != "filled" && k != "stroked") {
+                let v = styles[k];
+                if (v) {
+                    if (!styles["filled"] && k.indexOf('background') === 0) {
+                        continue;
+                    }
+                    else if (!styles["stroked"] && k.indexOf('border-width') === 0) {
+                        continue;
+                    }
+                    else {
+                        st.push(`${k}: ${v}`);
+                    }
+                }
+            }
+        }
+        return HTMLSpace.setAttr(elem, { style: st.join(";") });
+    }
+    static rectStyle(ctx, pt, size) {
+        ctx.style["left"] = pt[0] + "px";
+        ctx.style["top"] = pt[1] + "px";
+        ctx.style["width"] = size[0] + "px";
+        ctx.style["height"] = size[1] + "px";
+        return ctx;
+    }
+    static textStyle(ctx, pt) {
+        ctx.style["left"] = pt[0] + "px";
+        ctx.style["top"] = pt[1] + "px";
+        return ctx;
+    }
+    static point(ctx, pt, radius = 5, shape = "square") {
+        if (shape === "circle") {
+            return HTMLForm.circle(ctx, pt, radius);
+        }
+        else {
+            return HTMLForm.square(ctx, pt, radius);
+        }
+    }
+    point(pt, radius = 5, shape = "square") {
+        this.nextID();
+        if (shape == "circle")
+            this.styleTo("border-radius", "100%");
+        HTMLForm.point(this._ctx, pt, radius, shape);
+        return this;
+    }
+    static circle(ctx, pt, radius = 10) {
+        let elem = HTMLSpace.htmlElement(ctx.group, "div", HTMLForm.getID(ctx));
+        HTMLSpace.setAttr(elem, { class: `pts-form pts-circle ${ctx.currentClass}` });
+        HTMLForm.rectStyle(ctx, new Pt_1.Pt(pt).$subtract(radius), new Pt_1.Pt(radius * 2, radius * 2));
+        HTMLForm.style(elem, ctx.style);
+        return elem;
+    }
+    circle(pts) {
+        this.nextID();
+        this.styleTo("border-radius", "100%");
+        HTMLForm.circle(this._ctx, pts[0], pts[1][0]);
+        return this;
+    }
+    static square(ctx, pt, halfsize) {
+        let elem = HTMLSpace.htmlElement(ctx.group, "div", HTMLForm.getID(ctx));
+        HTMLSpace.setAttr(elem, { class: `pts-form pts-square ${ctx.currentClass}` });
+        HTMLForm.rectStyle(ctx, new Pt_1.Pt(pt).$subtract(halfsize), new Pt_1.Pt(halfsize * 2, halfsize * 2));
+        HTMLForm.style(elem, ctx.style);
+        return elem;
+    }
+    square(pt, halfsize) {
+        this.nextID();
+        HTMLForm.square(this._ctx, pt, halfsize);
+        return this;
+    }
+    static rect(ctx, pts) {
+        let p = Util_1.Util.iterToArray(pts);
+        if (!Util_1.Util.arrayCheck(p))
+            return;
+        let elem = HTMLSpace.htmlElement(ctx.group, "div", HTMLForm.getID(ctx));
+        HTMLSpace.setAttr(elem, { class: `pts-form pts-rect ${ctx.currentClass}` });
+        HTMLForm.rectStyle(ctx, p[0], p[1]);
+        HTMLForm.style(elem, ctx.style);
+        return elem;
+    }
+    rect(pts) {
+        this.nextID();
+        this.styleTo("border-radius", "0");
+        HTMLForm.rect(this._ctx, pts);
+        return this;
+    }
+    static text(ctx, pt, txt) {
+        let elem = HTMLSpace.htmlElement(ctx.group, "div", HTMLForm.getID(ctx));
+        HTMLSpace.setAttr(elem, { class: `pts-form pts-text ${ctx.currentClass}` });
+        elem.textContent = txt;
+        HTMLForm.textStyle(ctx, pt);
+        HTMLForm.style(elem, ctx.style);
+        return elem;
+    }
+    text(pt, txt) {
+        this.nextID();
+        HTMLForm.text(this._ctx, pt, txt);
+        return this;
+    }
+    log(txt) {
+        this.fill("#000").stroke("#fff", 0.5).text([10, 14], txt);
+        return this;
+    }
+    arc(pt, radius, startAngle, endAngle, cc) {
+        Util_1.Util.warn("arc is not implemented in HTMLForm");
+        return this;
+    }
+    line(pts) {
+        Util_1.Util.warn("line is not implemented in HTMLForm");
+        return this;
+    }
+    polygon(pts) {
+        Util_1.Util.warn("polygon is not implemented in HTMLForm");
+        return this;
+    }
+}
+exports.HTMLForm = HTMLForm;
+HTMLForm.groupID = 0;
+HTMLForm.domID = 0;
+
+
+/***/ }),
+
+/***/ "./src/Form.ts":
+/*!*********************!*\
+  !*** ./src/Form.ts ***!
+  \*********************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+/*! Source code licensed under Apache License 2.0. Copyright © 2017-current William Ngan and contributors. (https://github.com/williamngan/pts) */
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.Font = exports.VisualForm = exports.Form = void 0;
+class Form {
+    constructor() {
+        this._ready = false;
+    }
+    get ready() { return this._ready; }
+}
+exports.Form = Form;
+class VisualForm extends Form {
+    constructor() {
+        super(...arguments);
+        this._filled = true;
+        this._stroked = true;
+        this._font = new Font(14, "sans-serif");
+    }
+    get filled() { return this._filled; }
+    set filled(b) { this._filled = b; }
+    get stroked() { return this._stroked; }
+    set stroked(b) { this._stroked = b; }
+    get currentFont() { return this._font; }
+    _multiple(groups, shape, ...rest) {
+        if (!groups)
+            return this;
+        for (let i = 0, len = groups.length; i < len; i++) {
+            this[shape](groups[i], ...rest);
+        }
+        return this;
+    }
+    alpha(a) {
+        return this;
+    }
+    fill(c) {
+        return this;
+    }
+    fillOnly(c) {
+        this.stroke(false);
+        return this.fill(c);
+    }
+    stroke(c, width, linejoin, linecap) {
+        return this;
+    }
+    strokeOnly(c, width, linejoin, linecap) {
+        this.fill(false);
+        return this.stroke(c, width, linejoin, linecap);
+    }
+    points(pts, radius, shape) {
+        if (!pts)
+            return;
+        for (let i = 0, len = pts.length; i < len; i++) {
+            this.point(pts[i], radius, shape);
+        }
+        return this;
+    }
+    circles(groups) {
+        return this._multiple(groups, "circle");
+    }
+    squares(groups) {
+        return this._multiple(groups, "square");
+    }
+    lines(groups) {
+        return this._multiple(groups, "line");
+    }
+    polygons(groups) {
+        return this._multiple(groups, "polygon");
+    }
+    rects(groups) {
+        return this._multiple(groups, "rect");
+    }
+}
+exports.VisualForm = VisualForm;
+class Font {
+    constructor(size = 12, face = "sans-serif", weight = "", style = "", lineHeight = 1.5) {
+        this.size = size;
+        this.face = face;
+        this.style = style;
+        this.weight = weight;
+        this.lineHeight = lineHeight;
+    }
+    get value() { return `${this.style} ${this.weight} ${this.size}px/${this.lineHeight} ${this.face}`; }
+    toString() { return this.value; }
+}
+exports.Font = Font;
+
+
+/***/ }),
+
+/***/ "./src/Image.ts":
+/*!**********************!*\
+  !*** ./src/Image.ts ***!
+  \**********************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.Img = void 0;
+const Pt_1 = __webpack_require__(/*! ./Pt */ "./src/Pt.ts");
+class Img {
+    constructor(editable = false, pixelScale = 1, crossOrigin) {
+        this._scale = 1;
+        this._loaded = false;
+        this._editable = editable;
+        this._scale = pixelScale;
+        this._img = new Image();
+        if (crossOrigin)
+            this._img.crossOrigin = "Anonymous";
+    }
+    static load(src, editable = false, pixelScale = 1, ready) {
+        let img = new Img(editable, pixelScale);
+        img.load(src).then(res => {
+            if (ready)
+                ready(res);
+        });
+        return img;
+    }
+    load(src) {
+        return new Promise((resolve, reject) => {
+            this._img.src = src;
+            this._img.onload = () => {
+                if (this._editable) {
+                    if (!this._cv)
+                        this._cv = document.createElement("canvas");
+                    this._drawToScale(this._scale, this._img);
+                    this._data = this._ctx.getImageData(0, 0, this._cv.width, this._cv.height);
+                }
+                this._loaded = true;
+                resolve(this);
+            };
+            this._img.onerror = (evt) => {
+                reject(evt);
+            };
+        });
+    }
+    _drawToScale(canvasScale, img) {
+        const cms = (typeof canvasScale === 'number') ? [canvasScale, canvasScale] : canvasScale;
+        const nw = img.width;
+        const nh = img.height;
+        this._cv.width = nw * cms[0];
+        this._cv.height = nh * cms[1];
+        this._ctx = this._cv.getContext('2d');
+        if (img)
+            this._ctx.drawImage(img, 0, 0, nw, nh, 0, 0, this._cv.width, this._cv.height);
+    }
+    bitmap(size) {
+        const w = (size) ? size[0] : this._cv.width;
+        const h = (size) ? size[1] : this._cv.height;
+        return createImageBitmap(this._cv, 0, 0, w, h);
+    }
+    sync() {
+        if (this._scale !== 1) {
+            this.bitmap().then(b => {
+                this._drawToScale(1 / this._scale, b);
+                this.load(this.toBase64());
+            });
+        }
+        else {
+            this._img.src = this.toBase64();
+        }
+    }
+    pixel(p, rescale = true) {
+        const s = (typeof rescale == 'number') ? rescale : (rescale ? this._scale : 1);
+        return Img.getPixel(this._data, [p[0] * s, p[1] * s]);
+    }
+    static getPixel(imgData, p) {
+        const no = new Pt_1.Pt(0, 0, 0, 0);
+        if (p[0] >= imgData.width || p[1] >= imgData.height)
+            return no;
+        const i = Math.floor(p[1]) * (imgData.width * 4) + (Math.floor(p[0]) * 4);
+        const d = imgData.data;
+        if (i >= d.length - 4)
+            return no;
+        return new Pt_1.Pt(d[i], d[i + 1], d[i + 2], d[i + 3]);
+    }
+    resize(sizeOrScale, asScale = false) {
+        let s = asScale ? sizeOrScale : [sizeOrScale[0] / this._img.naturalWidth, sizeOrScale[1] / this._img.naturalHeight];
+        this._drawToScale(s, this._img);
+        this._data = this._ctx.getImageData(0, 0, this._cv.width, this._cv.height);
+        return this;
+    }
+    crop(box) {
+        let p = box.topLeft.scale(this._scale);
+        let s = box.size.scale(this._scale);
+        return this._ctx.getImageData(p.x, p.y, s.x, s.y);
+    }
+    filter(css) {
+        this._ctx.filter = css;
+        this._ctx.drawImage(this._cv, 0, 0);
+        this._ctx.filter = "none";
+        return this;
+    }
+    cleanup() {
+        if (this._cv)
+            this._cv.remove();
+        if (this._img)
+            this._img.remove();
+        this._data = null;
+    }
+    static fromBlob(blob, editable = false, pixelScale = 1) {
+        let url = URL.createObjectURL(blob);
+        return new Img(editable, pixelScale).load(url);
+    }
+    static imageDataToBlob(data) {
+        return new Promise(function (resolve) {
+            let cv = document.createElement("canvas");
+            cv.width = data.width;
+            cv.height = data.height;
+            cv.getContext("2d").putImageData(data, 0, 0);
+            cv.toBlob(blob => {
+                resolve(blob);
+                cv.remove();
+            });
+        });
+    }
+    toBase64() {
+        return this._cv.toDataURL();
+    }
+    toBlob() {
+        return new Promise((resolve) => {
+            this._cv.toBlob(blob => resolve(blob));
+        });
+    }
+    get current() {
+        return this._editable ? this._cv : this._img;
+    }
+    get image() {
+        return this._img;
+    }
+    get canvas() {
+        return this._cv;
+    }
+    get data() {
+        return this._data;
+    }
+    get ctx() {
+        return this._ctx;
+    }
+    get loaded() {
+        return this._loaded;
+    }
+    get pixelScale() {
+        return this._scale;
+    }
+    get imageSize() {
+        return new Pt_1.Pt(this._img.width, this._img.height);
+    }
+    get canvasSize() {
+        return new Pt_1.Pt(this._cv.width, this._cv.height);
+    }
+}
+exports.Img = Img;
+
+
+/***/ }),
+
+/***/ "./src/LinearAlgebra.ts":
+/*!******************************!*\
+  !*** ./src/LinearAlgebra.ts ***!
+  \******************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+/*! Source code licensed under Apache License 2.0. Copyright © 2017-current William Ngan and contributors. (https://github.com/williamngan/pts) */
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.Mat = exports.Vec = void 0;
+const Pt_1 = __webpack_require__(/*! ./Pt */ "./src/Pt.ts");
+const Op_1 = __webpack_require__(/*! ./Op */ "./src/Op.ts");
+class Vec {
+    static add(a, b) {
+        if (typeof b == "number") {
+            for (let i = 0, len = a.length; i < len; i++)
+                a[i] += b;
+        }
+        else {
+            for (let i = 0, len = a.length; i < len; i++)
+                a[i] += b[i] || 0;
+        }
+        return a;
+    }
+    static subtract(a, b) {
+        if (typeof b == "number") {
+            for (let i = 0, len = a.length; i < len; i++)
+                a[i] -= b;
+        }
+        else {
+            for (let i = 0, len = a.length; i < len; i++)
+                a[i] -= b[i] || 0;
+        }
+        return a;
+    }
+    static multiply(a, b) {
+        if (typeof b == "number") {
+            for (let i = 0, len = a.length; i < len; i++)
+                a[i] *= b;
+        }
+        else {
+            if (a.length != b.length) {
+                throw new Error(`Cannot do element-wise multiply since the array lengths don't match: ${a.toString()} multiply-with ${b.toString()}`);
+            }
+            for (let i = 0, len = a.length; i < len; i++)
+                a[i] *= b[i];
+        }
+        return a;
+    }
+    static divide(a, b) {
+        if (typeof b == "number") {
+            if (b === 0)
+                throw new Error("Cannot divide by zero");
+            for (let i = 0, len = a.length; i < len; i++)
+                a[i] /= b;
+        }
+        else {
+            if (a.length != b.length) {
+                throw new Error(`Cannot do element-wise divide since the array lengths don't match. ${a.toString()} divide-by ${b.toString()}`);
+            }
+            for (let i = 0, len = a.length; i < len; i++)
+                a[i] /= b[i];
+        }
+        return a;
+    }
+    static dot(a, b) {
+        if (a.length != b.length)
+            throw new Error("Array lengths don't match");
+        let d = 0;
+        for (let i = 0, len = a.length; i < len; i++) {
+            d += a[i] * b[i];
+        }
+        return d;
+    }
+    static cross2D(a, b) {
+        return a[0] * b[1] - a[1] * b[0];
+    }
+    static cross(a, b) {
+        return new Pt_1.Pt((a[1] * b[2] - a[2] * b[1]), (a[2] * b[0] - a[0] * b[2]), (a[0] * b[1] - a[1] * b[0]));
+    }
+    static magnitude(a) {
+        return Math.sqrt(Vec.dot(a, a));
+    }
+    static unit(a, magnitude = undefined) {
+        let m = (magnitude === undefined) ? Vec.magnitude(a) : magnitude;
+        if (m === 0)
+            return Pt_1.Pt.make(a.length);
+        return Vec.divide(a, m);
+    }
+    static abs(a) {
+        return Vec.map(a, Math.abs);
+    }
+    static floor(a) {
+        return Vec.map(a, Math.floor);
+    }
+    static ceil(a) {
+        return Vec.map(a, Math.ceil);
+    }
+    static round(a) {
+        return Vec.map(a, Math.round);
+    }
+    static max(a) {
+        let m = Number.MIN_VALUE;
+        let index = 0;
+        for (let i = 0, len = a.length; i < len; i++) {
+            m = Math.max(m, a[i]);
+            if (m === a[i])
+                index = i;
+        }
+        return { value: m, index: index };
+    }
+    static min(a) {
+        let m = Number.MAX_VALUE;
+        let index = 0;
+        for (let i = 0, len = a.length; i < len; i++) {
+            m = Math.min(m, a[i]);
+            if (m === a[i])
+                index = i;
+        }
+        return { value: m, index: index };
+    }
+    static sum(a) {
+        let s = 0;
+        for (let i = 0, len = a.length; i < len; i++)
+            s += a[i];
+        return s;
+    }
+    static map(a, fn) {
+        for (let i = 0, len = a.length; i < len; i++) {
+            a[i] = fn(a[i], i, a);
+        }
+        return a;
+    }
+}
+exports.Vec = Vec;
+class Mat {
+    static add(a, b) {
+        if (typeof b != "number") {
+            if (a[0].length != b[0].length)
+                throw new Error("Cannot add matrix if rows' and columns' size don't match.");
+            if (a.length != b.length)
+                throw new Error("Cannot add matrix if rows' and columns' size don't match.");
+        }
+        let g = new Pt_1.Group();
+        let isNum = typeof b == "number";
+        for (let i = 0, len = a.length; i < len; i++) {
+            g.push(a[i].$add((isNum) ? b : b[i]));
+        }
+        return g;
+    }
+    static multiply(a, b, transposed = false, elementwise = false) {
+        let g = new Pt_1.Group();
+        if (typeof b != "number") {
+            if (elementwise) {
+                if (a.length != b.length)
+                    throw new Error("Cannot multiply matrix element-wise because the matrices' sizes don't match.");
+                for (let ai = 0, alen = a.length; ai < alen; ai++) {
+                    g.push(a[ai].$multiply(b[ai]));
+                }
+            }
+            else {
+                if (!transposed && a[0].length != b.length)
+                    throw new Error("Cannot multiply matrix if rows in matrix-a don't match columns in matrix-b.");
+                if (transposed && a[0].length != b[0].length)
+                    throw new Error("Cannot multiply matrix if transposed and the columns in both matrices don't match.");
+                if (!transposed)
+                    b = Mat.transpose(b);
+                for (let ai = 0, alen = a.length; ai < alen; ai++) {
+                    let p = Pt_1.Pt.make(b.length, 0);
+                    for (let bi = 0, blen = b.length; bi < blen; bi++) {
+                        p[bi] = Vec.dot(a[ai], b[bi]);
+                    }
+                    g.push(p);
+                }
+            }
+        }
+        else {
+            for (let ai = 0, alen = a.length; ai < alen; ai++) {
+                g.push(a[ai].$multiply(b));
+            }
+        }
+        return g;
+    }
+    static zipSlice(g, index, defaultValue = false) {
+        let z = [];
+        for (let i = 0, len = g.length; i < len; i++) {
+            if (g[i].length - 1 < index && defaultValue === false)
+                throw `Index ${index} is out of bounds`;
+            z.push(g[i][index] || defaultValue);
+        }
+        return new Pt_1.Pt(z);
+    }
+    static zip(g, defaultValue = false, useLongest = false) {
+        let ps = new Pt_1.Group();
+        let len = (useLongest) ? g.reduce((a, b) => Math.max(a, b.length), 0) : g[0].length;
+        for (let i = 0; i < len; i++) {
+            ps.push(Mat.zipSlice(g, i, defaultValue));
+        }
+        return ps;
+    }
+    static transpose(g, defaultValue = false, useLongest = false) {
+        return Mat.zip(g, defaultValue, useLongest);
+    }
+    static transform2D(pt, m) {
+        let x = pt[0] * m[0][0] + pt[1] * m[1][0] + m[2][0];
+        let y = pt[0] * m[0][1] + pt[1] * m[1][1] + m[2][1];
+        return new Pt_1.Pt(x, y);
+    }
+    static scale2DMatrix(x, y) {
+        return new Pt_1.Group(new Pt_1.Pt(x, 0, 0), new Pt_1.Pt(0, y, 0), new Pt_1.Pt(0, 0, 1));
+    }
+    static rotate2DMatrix(cosA, sinA) {
+        return new Pt_1.Group(new Pt_1.Pt(cosA, sinA, 0), new Pt_1.Pt(-sinA, cosA, 0), new Pt_1.Pt(0, 0, 1));
+    }
+    static shear2DMatrix(tanX, tanY) {
+        return new Pt_1.Group(new Pt_1.Pt(1, tanX, 0), new Pt_1.Pt(tanY, 1, 0), new Pt_1.Pt(0, 0, 1));
+    }
+    static translate2DMatrix(x, y) {
+        return new Pt_1.Group(new Pt_1.Pt(1, 0, 0), new Pt_1.Pt(0, 1, 0), new Pt_1.Pt(x, y, 1));
+    }
+    static scaleAt2DMatrix(sx, sy, at) {
+        let m = Mat.scale2DMatrix(sx, sy);
+        m[2][0] = -at[0] * sx + at[0];
+        m[2][1] = -at[1] * sy + at[1];
+        return m;
+    }
+    static rotateAt2DMatrix(cosA, sinA, at) {
+        let m = Mat.rotate2DMatrix(cosA, sinA);
+        m[2][0] = at[0] * (1 - cosA) + at[1] * sinA;
+        m[2][1] = at[1] * (1 - cosA) - at[0] * sinA;
+        return m;
+    }
+    static shearAt2DMatrix(tanX, tanY, at) {
+        let m = Mat.shear2DMatrix(tanX, tanY);
+        m[2][0] = -at[1] * tanY;
+        m[2][1] = -at[0] * tanX;
+        return m;
+    }
+    static reflectAt2DMatrix(p1, p2) {
+        let intercept = Op_1.Line.intercept(p1, p2);
+        if (intercept == undefined) {
+            return [
+                new Pt_1.Pt([-1, 0, 0]),
+                new Pt_1.Pt([0, 1, 0]),
+                new Pt_1.Pt([p1[0] + p2[0], 0, 1])
+            ];
+        }
+        else {
+            let yi = intercept.yi;
+            let ang2 = Math.atan(intercept.slope) * 2;
+            let cosA = Math.cos(ang2);
+            let sinA = Math.sin(ang2);
+            return [
+                new Pt_1.Pt([cosA, sinA, 0]),
+                new Pt_1.Pt([sinA, -cosA, 0]),
+                new Pt_1.Pt([-yi * sinA, yi + yi * cosA, 1])
+            ];
+        }
+    }
+}
+exports.Mat = Mat;
+
+
+/***/ }),
+
+/***/ "./src/Num.ts":
+/*!********************!*\
+  !*** ./src/Num.ts ***!
+  \********************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+/*! Source code licensed under Apache License 2.0. Copyright © 2017-current William Ngan and contributors. (https://github.com/williamngan/pts) */
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.Range = exports.Shaping = exports.Geom = exports.Num = void 0;
+const Util_1 = __webpack_require__(/*! ./Util */ "./src/Util.ts");
+const Op_1 = __webpack_require__(/*! ./Op */ "./src/Op.ts");
+const Pt_1 = __webpack_require__(/*! ./Pt */ "./src/Pt.ts");
+const LinearAlgebra_1 = __webpack_require__(/*! ./LinearAlgebra */ "./src/LinearAlgebra.ts");
+class Num {
+    static equals(a, b, threshold = 0.00001) {
+        return Math.abs(a - b) < threshold;
+    }
+    static lerp(a, b, t) {
+        return (1 - t) * a + t * b;
+    }
+    static clamp(val, min, max) {
+        return Math.max(min, Math.min(max, val));
+    }
+    static boundValue(val, min, max) {
+        let len = Math.abs(max - min);
+        let a = val % len;
+        if (a > max)
+            a -= len;
+        else if (a < min)
+            a += len;
+        return a;
+    }
+    static within(p, a, b) {
+        return p >= Math.min(a, b) && p <= Math.max(a, b);
+    }
+    static randomRange(a, b = 0) {
+        let r = (a > b) ? (a - b) : (b - a);
+        return a + Math.random() * r;
+    }
+    static randomPt(a, b) {
+        let p = new Pt_1.Pt(a.length);
+        let range = b ? LinearAlgebra_1.Vec.subtract(b, a) : a;
+        let start = b ? a : new Pt_1.Pt(a.length).fill(0);
+        for (let i = 0, len = p.length; i < len; i++) {
+            p[i] = Math.random() * range[i] + start[i];
+        }
+        return p;
+    }
+    static normalizeValue(n, a, b) {
+        let min = Math.min(a, b);
+        let max = Math.max(a, b);
+        return (n - min) / (max - min);
+    }
+    static sum(pts) {
+        let _pts = Util_1.Util.iterToArray(pts);
+        let c = new Pt_1.Pt(_pts[0]);
+        for (let i = 1, len = _pts.length; i < len; i++) {
+            LinearAlgebra_1.Vec.add(c, _pts[i]);
+        }
+        return c;
+    }
+    static average(pts) {
+        let _pts = Util_1.Util.iterToArray(pts);
+        return Num.sum(_pts).divide(_pts.length);
+    }
+    static cycle(t, method = Shaping.sineInOut) {
+        return method(t > 0.5 ? 2 - t * 2 : t * 2);
+    }
+    static mapToRange(n, currA, currB, targetA, targetB) {
+        if (currA == currB)
+            throw new Error("[currMin, currMax] must define a range that is not zero");
+        let min = Math.min(targetA, targetB);
+        let max = Math.max(targetA, targetB);
+        return Num.normalizeValue(n, currA, currB) * (max - min) + min;
+    }
+}
+exports.Num = Num;
+class Geom {
+    static boundAngle(angle) {
+        return Num.boundValue(angle, 0, 360);
+    }
+    static boundRadian(radian) {
+        return Num.boundValue(radian, 0, Util_1.Const.two_pi);
+    }
+    static toRadian(angle) {
+        return angle * Util_1.Const.deg_to_rad;
+    }
+    static toDegree(radian) {
+        return radian * Util_1.Const.rad_to_deg;
+    }
+    static boundingBox(pts) {
+        let minPt, maxPt;
+        for (let p of pts) {
+            if (minPt == undefined) {
+                minPt = p.clone();
+                maxPt = p.clone();
+            }
+            else {
+                minPt = minPt.$min(p);
+                maxPt = maxPt.$max(p);
+            }
+        }
+        return new Pt_1.Group(minPt, maxPt);
+    }
+    static centroid(pts) {
+        return Num.average(pts);
+    }
+    static anchor(pts, ptOrIndex = 0, direction = "to") {
+        let method = (direction == "to") ? "subtract" : "add";
+        let i = 0;
+        for (let p of pts) {
+            if (typeof ptOrIndex == "number") {
+                if (ptOrIndex !== i)
+                    p[method](pts[ptOrIndex]);
+            }
+            else {
+                p[method](ptOrIndex);
+            }
+            i++;
+        }
+    }
+    static interpolate(a, b, t = 0.5) {
+        let len = Math.min(a.length, b.length);
+        let d = Pt_1.Pt.make(len);
+        for (let i = 0; i < len; i++) {
+            d[i] = a[i] * (1 - t) + b[i] * t;
+        }
+        return d;
+    }
+    static perpendicular(pt, axis = Util_1.Const.xy) {
+        let y = axis[1];
+        let x = axis[0];
+        let p = new Pt_1.Pt(pt);
+        let pa = new Pt_1.Pt(p);
+        pa[x] = -p[y];
+        pa[y] = p[x];
+        let pb = new Pt_1.Pt(p);
+        pb[x] = p[y];
+        pb[y] = -p[x];
+        return new Pt_1.Group(pa, pb);
+    }
+    static isPerpendicular(p1, p2) {
+        return new Pt_1.Pt(p1).dot(p2) === 0;
+    }
+    static withinBound(pt, boundPt1, boundPt2) {
+        for (let i = 0, len = Math.min(pt.length, boundPt1.length, boundPt2.length); i < len; i++) {
+            if (!Num.within(pt[i], boundPt1[i], boundPt2[i]))
+                return false;
+        }
+        return true;
+    }
+    static sortEdges(pts) {
+        let _pts = Util_1.Util.iterToArray(pts);
+        let bounds = Geom.boundingBox(_pts);
+        let center = bounds[1].add(bounds[0]).divide(2);
+        let fn = (a, b) => {
+            if (a.length < 2 || b.length < 2)
+                throw new Error("Pt dimension cannot be less than 2");
+            let da = a.$subtract(center);
+            let db = b.$subtract(center);
+            if (da[0] >= 0 && db[0] < 0)
+                return 1;
+            if (da[0] < 0 && db[0] >= 0)
+                return -1;
+            if (da[0] == 0 && db[0] == 0) {
+                if (da[1] >= 0 || db[1] >= 0)
+                    return (da[1] > db[1]) ? 1 : -1;
+                return (db[1] > da[1]) ? 1 : -1;
+            }
+            let det = da.$cross2D(db);
+            if (det < 0)
+                return 1;
+            if (det > 0)
+                return -1;
+            return (da[0] * da[0] + da[1] * da[1] > db[0] * db[0] + db[1] * db[1]) ? 1 : -1;
+        };
+        return _pts.sort(fn);
+    }
+    static scale(ps, scale, anchor) {
+        let pts = Util_1.Util.iterToArray((ps[0] !== undefined && typeof ps[0] == 'number') ? [ps] : ps);
+        let scs = (typeof scale == "number") ? Pt_1.Pt.make(pts[0].length, scale) : scale;
+        if (!anchor)
+            anchor = Pt_1.Pt.make(pts[0].length, 0);
+        for (let i = 0, len = pts.length; i < len; i++) {
+            let p = pts[i];
+            for (let k = 0, lenP = p.length; k < lenP; k++) {
+                p[k] = (anchor && anchor[k]) ? anchor[k] + (p[k] - anchor[k]) * scs[k] : p[k] * scs[k];
+            }
+        }
+        return Geom;
+    }
+    static rotate2D(ps, angle, anchor, axis) {
+        let pts = Util_1.Util.iterToArray((ps[0] !== undefined && typeof ps[0] == 'number') ? [ps] : ps);
+        let fn = (anchor) ? LinearAlgebra_1.Mat.rotateAt2DMatrix : LinearAlgebra_1.Mat.rotate2DMatrix;
+        if (!anchor)
+            anchor = Pt_1.Pt.make(pts[0].length, 0);
+        let cos = Math.cos(angle);
+        let sin = Math.sin(angle);
+        for (let i = 0, len = pts.length; i < len; i++) {
+            let p = (axis) ? pts[i].$take(axis) : pts[i];
+            p.to(LinearAlgebra_1.Mat.transform2D(p, fn(cos, sin, anchor)));
+            if (axis) {
+                for (let k = 0; k < axis.length; k++) {
+                    pts[i][axis[k]] = p[k];
+                }
+            }
+        }
+        return Geom;
+    }
+    static shear2D(ps, scale, anchor, axis) {
+        let pts = Util_1.Util.iterToArray((ps[0] !== undefined && typeof ps[0] == 'number') ? [ps] : ps);
+        let s = (typeof scale == "number") ? [scale, scale] : scale;
+        if (!anchor)
+            anchor = Pt_1.Pt.make(pts[0].length, 0);
+        let fn = (anchor) ? LinearAlgebra_1.Mat.shearAt2DMatrix : LinearAlgebra_1.Mat.shear2DMatrix;
+        let tanx = Math.tan(s[0]);
+        let tany = Math.tan(s[1]);
+        for (let i = 0, len = pts.length; i < len; i++) {
+            let p = (axis) ? pts[i].$take(axis) : pts[i];
+            p.to(LinearAlgebra_1.Mat.transform2D(p, fn(tanx, tany, anchor)));
+            if (axis) {
+                for (let k = 0; k < axis.length; k++) {
+                    pts[i][axis[k]] = p[k];
+                }
+            }
+        }
+        return Geom;
+    }
+    static reflect2D(ps, line, axis) {
+        let pts = Util_1.Util.iterToArray((ps[0] !== undefined && typeof ps[0] == 'number') ? [ps] : ps);
+        let _line = Util_1.Util.iterToArray(line);
+        let mat = LinearAlgebra_1.Mat.reflectAt2DMatrix(_line[0], _line[1]);
+        for (let i = 0, len = pts.length; i < len; i++) {
+            let p = (axis) ? pts[i].$take(axis) : pts[i];
+            p.to(LinearAlgebra_1.Mat.transform2D(p, mat));
+            if (axis) {
+                for (let k = 0; k < axis.length; k++) {
+                    pts[i][axis[k]] = p[k];
+                }
+            }
+        }
+        return Geom;
+    }
+    static cosTable() {
+        let cos = new Float64Array(360);
+        for (let i = 0; i < 360; i++)
+            cos[i] = Math.cos(i * Math.PI / 180);
+        let find = (rad) => cos[Math.floor(Geom.boundAngle(Geom.toDegree(rad)))];
+        return { table: cos, cos: find };
+    }
+    static sinTable() {
+        let sin = new Float64Array(360);
+        for (let i = 0; i < 360; i++)
+            sin[i] = Math.sin(i * Math.PI / 180);
+        let find = (rad) => sin[Math.floor(Geom.boundAngle(Geom.toDegree(rad)))];
+        return { table: sin, sin: find };
+    }
+}
+exports.Geom = Geom;
+class Shaping {
+    static linear(t, c = 1) {
+        return c * t;
+    }
+    static quadraticIn(t, c = 1) {
+        return c * t * t;
+    }
+    static quadraticOut(t, c = 1) {
+        return -c * t * (t - 2);
+    }
+    static quadraticInOut(t, c = 1) {
+        let dt = t * 2;
+        return (t < 0.5) ? c / 2 * t * t * 4 : -c / 2 * ((dt - 1) * (dt - 3) - 1);
+    }
+    static cubicIn(t, c = 1) {
+        return c * t * t * t;
+    }
+    static cubicOut(t, c = 1) {
+        let dt = t - 1;
+        return c * (dt * dt * dt + 1);
+    }
+    static cubicInOut(t, c = 1) {
+        let dt = t * 2;
+        return (t < 0.5) ? c / 2 * dt * dt * dt : c / 2 * ((dt - 2) * (dt - 2) * (dt - 2) + 2);
+    }
+    static exponentialIn(t, c = 1, p = 0.25) {
+        return c * Math.pow(t, 1 / p);
+    }
+    static exponentialOut(t, c = 1, p = 0.25) {
+        return c * Math.pow(t, p);
+    }
+    static sineIn(t, c = 1) {
+        return -c * Math.cos(t * Util_1.Const.half_pi) + c;
+    }
+    static sineOut(t, c = 1) {
+        return c * Math.sin(t * Util_1.Const.half_pi);
+    }
+    static sineInOut(t, c = 1) {
+        return -c / 2 * (Math.cos(Math.PI * t) - 1);
+    }
+    static cosineApprox(t, c = 1) {
+        let t2 = t * t;
+        let t4 = t2 * t2;
+        let t6 = t4 * t2;
+        return c * (4 * t6 / 9 - 17 * t4 / 9 + 22 * t2 / 9);
+    }
+    static circularIn(t, c = 1) {
+        return -c * (Math.sqrt(1 - t * t) - 1);
+    }
+    static circularOut(t, c = 1) {
+        let dt = t - 1;
+        return c * Math.sqrt(1 - dt * dt);
+    }
+    static circularInOut(t, c = 1) {
+        let dt = t * 2;
+        return (t < 0.5) ? -c / 2 * (Math.sqrt(1 - dt * dt) - 1) : c / 2 * (Math.sqrt(1 - (dt - 2) * (dt - 2)) + 1);
+    }
+    static elasticIn(t, c = 1, p = 0.7) {
+        let dt = t - 1;
+        let s = (p / Util_1.Const.two_pi) * 1.5707963267948966;
+        return c * (-Math.pow(2, 10 * dt) * Math.sin((dt - s) * Util_1.Const.two_pi / p));
+    }
+    static elasticOut(t, c = 1, p = 0.7) {
+        let s = (p / Util_1.Const.two_pi) * 1.5707963267948966;
+        return c * (Math.pow(2, -10 * t) * Math.sin((t - s) * Util_1.Const.two_pi / p)) + c;
+    }
+    static elasticInOut(t, c = 1, p = 0.6) {
+        let dt = t * 2;
+        let s = (p / Util_1.Const.two_pi) * 1.5707963267948966;
+        if (t < 0.5) {
+            dt -= 1;
+            return c * (-0.5 * (Math.pow(2, 10 * dt) * Math.sin((dt - s) * Util_1.Const.two_pi / p)));
+        }
+        else {
+            dt -= 1;
+            return c * (0.5 * (Math.pow(2, -10 * dt) * Math.sin((dt - s) * Util_1.Const.two_pi / p))) + c;
+        }
+    }
+    static bounceIn(t, c = 1) {
+        return c - Shaping.bounceOut((1 - t), c);
+    }
+    static bounceOut(t, c = 1) {
+        if (t < (1 / 2.75)) {
+            return c * (7.5625 * t * t);
+        }
+        else if (t < (2 / 2.75)) {
+            t -= 1.5 / 2.75;
+            return c * (7.5625 * t * t + 0.75);
+        }
+        else if (t < (2.5 / 2.75)) {
+            t -= 2.25 / 2.75;
+            return c * (7.5625 * t * t + 0.9375);
+        }
+        else {
+            t -= 2.625 / 2.75;
+            return c * (7.5625 * t * t + 0.984375);
+        }
+    }
+    static bounceInOut(t, c = 1) {
+        return (t < 0.5) ? Shaping.bounceIn(t * 2, c) / 2 : Shaping.bounceOut(t * 2 - 1, c) / 2 + c / 2;
+    }
+    static sigmoid(t, c = 1, p = 10) {
+        let d = p * (t - 0.5);
+        return c / (1 + Math.exp(-d));
+    }
+    static logSigmoid(t, c = 1, p = 0.7) {
+        p = Math.max(Util_1.Const.epsilon, Math.min(1 - Util_1.Const.epsilon, p));
+        p = 1 / (1 - p);
+        let A = 1 / (1 + Math.exp(((t - 0.5) * p * -2)));
+        let B = 1 / (1 + Math.exp(p));
+        let C = 1 / (1 + Math.exp(-p));
+        return c * (A - B) / (C - B);
+    }
+    static seat(t, c = 1, p = 0.5) {
+        if ((t < 0.5)) {
+            return c * (Math.pow(2 * t, 1 - p)) / 2;
+        }
+        else {
+            return c * (1 - (Math.pow(2 * (1 - t), 1 - p)) / 2);
+        }
+    }
+    static quadraticBezier(t, c = 1, p = [0.05, 0.95]) {
+        let a = (typeof p != "number") ? p[0] : p;
+        let b = (typeof p != "number") ? p[1] : 0.5;
+        let om2a = 1 - 2 * a;
+        if (om2a === 0) {
+            om2a = Util_1.Const.epsilon;
+        }
+        let d = (Math.sqrt(a * a + om2a * t) - a) / om2a;
+        return c * ((1 - 2 * b) * (d * d) + (2 * b) * d);
+    }
+    static cubicBezier(t, c = 1, p1 = [0.1, 0.7], p2 = [0.9, 0.2]) {
+        let curve = new Pt_1.Group(new Pt_1.Pt(0, 0), new Pt_1.Pt(p1), new Pt_1.Pt(p2), new Pt_1.Pt(1, 1));
+        return c * Op_1.Curve.bezierStep(new Pt_1.Pt(t * t * t, t * t, t, 1), Op_1.Curve.controlPoints(curve)).y;
+    }
+    static quadraticTarget(t, c = 1, p1 = [0.2, 0.35]) {
+        let a = Math.min(1 - Util_1.Const.epsilon, Math.max(Util_1.Const.epsilon, p1[0]));
+        let b = Math.min(1, Math.max(0, p1[1]));
+        let A = (1 - b) / (1 - a) - (b / a);
+        let B = (A * (a * a) - b) / a;
+        let y = A * (t * t) - B * t;
+        return c * Math.min(1, Math.max(0, y));
+    }
+    static cliff(t, c = 1, p = 0.5) {
+        return (t > p) ? c : 0;
+    }
+    static step(fn, steps, t, c, ...args) {
+        let s = 1 / steps;
+        let tt = Math.floor(t / s) * s;
+        return fn(tt, c, ...args);
+    }
+}
+exports.Shaping = Shaping;
+class Range {
+    constructor(g) {
+        this._dims = 0;
+        this._source = Pt_1.Group.fromPtArray(g);
+        this.calc();
+    }
+    get max() { return this._max.clone(); }
+    get min() { return this._min.clone(); }
+    get magnitude() { return this._mag.clone(); }
+    calc() {
+        if (!this._source)
+            return;
+        let dims = this._source[0].length;
+        this._dims = dims;
+        let max = new Pt_1.Pt(dims);
+        let min = new Pt_1.Pt(dims);
+        let mag = new Pt_1.Pt(dims);
+        for (let i = 0; i < dims; i++) {
+            max[i] = Util_1.Const.min;
+            min[i] = Util_1.Const.max;
+            mag[i] = 0;
+            let s = this._source.zipSlice(i);
+            for (let k = 0, len = s.length; k < len; k++) {
+                max[i] = Math.max(max[i], s[k]);
+                min[i] = Math.min(min[i], s[k]);
+                mag[i] = max[i] - min[i];
+            }
+        }
+        this._max = max;
+        this._min = min;
+        this._mag = mag;
+        return this;
+    }
+    mapTo(min, max, exclude) {
+        let target = new Pt_1.Group();
+        for (let i = 0, len = this._source.length; i < len; i++) {
+            let g = this._source[i];
+            let n = new Pt_1.Pt(this._dims);
+            for (let k = 0; k < this._dims; k++) {
+                n[k] = (exclude && exclude[k]) ? g[k] : Num.mapToRange(g[k], this._min[k], this._max[k], min, max);
+            }
+            target.push(n);
+        }
+        return target;
+    }
+    append(pts, update = true) {
+        let _pts = Util_1.Util.iterToArray(pts);
+        if (_pts[0].length !== this._dims)
+            throw new Error(`Dimensions don't match. ${this._dims} dimensions in Range and ${_pts[0].length} provided in parameter. `);
+        this._source = this._source.concat(_pts);
+        if (update)
+            this.calc();
+        return this;
+    }
+    ticks(count) {
+        let g = new Pt_1.Group();
+        for (let i = 0; i <= count; i++) {
+            let p = new Pt_1.Pt(this._dims);
+            for (let k = 0, len = this._max.length; k < len; k++) {
+                p[k] = Num.lerp(this._min[k], this._max[k], i / count);
+            }
+            g.push(p);
+        }
+        return g;
+    }
+}
+exports.Range = Range;
+
+
+/***/ }),
+
+/***/ "./src/Op.ts":
+/*!*******************!*\
+  !*** ./src/Op.ts ***!
+  \*******************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+/*! Source code licensed under Apache License 2.0. Copyright © 2017-current William Ngan and contributors. (https://github.com/williamngan/pts) */
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.Curve = exports.Polygon = exports.Triangle = exports.Circle = exports.Rectangle = exports.Line = void 0;
+const Util_1 = __webpack_require__(/*! ./Util */ "./src/Util.ts");
+const Num_1 = __webpack_require__(/*! ./Num */ "./src/Num.ts");
+const Pt_1 = __webpack_require__(/*! ./Pt */ "./src/Pt.ts");
+const LinearAlgebra_1 = __webpack_require__(/*! ./LinearAlgebra */ "./src/LinearAlgebra.ts");
+let _errorLength = (obj, param = "expected") => Util_1.Util.warn("Group's length is less than " + param, obj);
+let _errorOutofBound = (obj, param = "") => Util_1.Util.warn(`Index ${param} is out of bound in Group`, obj);
+class Line {
+    static fromAngle(anchor, angle, magnitude) {
+        let g = new Pt_1.Group(new Pt_1.Pt(anchor), new Pt_1.Pt(anchor));
+        g[1].toAngle(angle, magnitude, true);
+        return g;
+    }
+    static slope(p1, p2) {
+        return (p2[0] - p1[0] === 0) ? undefined : (p2[1] - p1[1]) / (p2[0] - p1[0]);
+    }
+    static intercept(p1, p2) {
+        if (p2[0] - p1[0] === 0) {
+            return undefined;
+        }
+        else {
+            let m = (p2[1] - p1[1]) / (p2[0] - p1[0]);
+            let c = p1[1] - m * p1[0];
+            return { slope: m, yi: c, xi: (m === 0) ? undefined : -c / m };
+        }
+    }
+    static sideOfPt2D(line, pt) {
+        let _line = Util_1.Util.iterToArray(line);
+        return (_line[1][0] - _line[0][0]) * (pt[1] - _line[0][1]) - (pt[0] - _line[0][0]) * (_line[1][1] - _line[0][1]);
+    }
+    static collinear(p1, p2, p3, threshold = 0.01) {
+        let a = new Pt_1.Pt(0, 0, 0).to(p1).$subtract(p2);
+        let b = new Pt_1.Pt(0, 0, 0).to(p1).$subtract(p3);
+        return a.$cross(b).divide(1000).equals(new Pt_1.Pt(0, 0, 0), threshold);
+    }
+    static magnitude(line) {
+        let _line = Util_1.Util.iterToArray(line);
+        return (_line.length >= 2) ? _line[1].$subtract(_line[0]).magnitude() : 0;
+    }
+    static magnitudeSq(line) {
+        let _line = Util_1.Util.iterToArray(line);
+        return (_line.length >= 2) ? _line[1].$subtract(_line[0]).magnitudeSq() : 0;
+    }
+    static perpendicularFromPt(line, pt, asProjection = false) {
+        let _line = Util_1.Util.iterToArray(line);
+        if (_line[0].equals(_line[1]))
+            return undefined;
+        let a = _line[0].$subtract(_line[1]);
+        let b = _line[1].$subtract(pt);
+        let proj = b.$subtract(a.$project(b));
+        return (asProjection) ? proj : proj.$add(pt);
+    }
+    static distanceFromPt(line, pt) {
+        let _line = Util_1.Util.iterToArray(line);
+        let projectionVector = Line.perpendicularFromPt(_line, pt, true);
+        if (projectionVector) {
+            return projectionVector.magnitude();
+        }
+        else {
+            return _line[0].$subtract(pt).magnitude();
+        }
+    }
+    static intersectRay2D(la, lb) {
+        let _la = Util_1.Util.iterToArray(la);
+        let _lb = Util_1.Util.iterToArray(lb);
+        let a = Line.intercept(_la[0], _la[1]);
+        let b = Line.intercept(_lb[0], _lb[1]);
+        let pa = _la[0];
+        let pb = _lb[0];
+        if (a == undefined) {
+            if (b == undefined)
+                return undefined;
+            let y1 = -b.slope * (pb[0] - pa[0]) + pb[1];
+            return new Pt_1.Pt(pa[0], y1);
+        }
+        else {
+            if (b == undefined) {
+                let y1 = -a.slope * (pa[0] - pb[0]) + pa[1];
+                return new Pt_1.Pt(pb[0], y1);
+            }
+            else if (b.slope != a.slope) {
+                let px = (a.slope * pa[0] - b.slope * pb[0] + pb[1] - pa[1]) / (a.slope - b.slope);
+                let py = a.slope * (px - pa[0]) + pa[1];
+                return new Pt_1.Pt(px, py);
+            }
+            else {
+                if (a.yi == b.yi) {
+                    return new Pt_1.Pt(pa[0], pa[1]);
+                }
+                else {
+                    return undefined;
+                }
+            }
+        }
+    }
+    static intersectLine2D(la, lb) {
+        let _la = Util_1.Util.iterToArray(la);
+        let _lb = Util_1.Util.iterToArray(lb);
+        let pt = Line.intersectRay2D(_la, _lb);
+        return (pt && Num_1.Geom.withinBound(pt, _la[0], _la[1]) && Num_1.Geom.withinBound(pt, _lb[0], _lb[1])) ? pt : undefined;
+    }
+    static intersectLineWithRay2D(line, ray) {
+        let _line = Util_1.Util.iterToArray(line);
+        let _ray = Util_1.Util.iterToArray(ray);
+        let pt = Line.intersectRay2D(_line, _ray);
+        return (pt && Num_1.Geom.withinBound(pt, _line[0], _line[1])) ? pt : undefined;
+    }
+    static intersectPolygon2D(lineOrRay, poly, sourceIsRay = false) {
+        let _lineOrRay = Util_1.Util.iterToArray(lineOrRay);
+        let _poly = Util_1.Util.iterToArray(poly);
+        let fn = sourceIsRay ? Line.intersectLineWithRay2D : Line.intersectLine2D;
+        let pts = new Pt_1.Group();
+        for (let i = 0, len = _poly.length; i < len; i++) {
+            let next = (i === len - 1) ? 0 : i + 1;
+            let d = fn([_poly[i], _poly[next]], _lineOrRay);
+            if (d)
+                pts.push(d);
+        }
+        return (pts.length > 0) ? pts : undefined;
+    }
+    static intersectLines2D(lines1, lines2, isRay = false) {
+        let group = new Pt_1.Group();
+        let fn = isRay ? Line.intersectLineWithRay2D : Line.intersectLine2D;
+        for (let l1 of lines1) {
+            for (let l2 of lines2) {
+                let _ip = fn(l1, l2);
+                if (_ip)
+                    group.push(_ip);
+            }
+        }
+        return group;
+    }
+    static intersectGridWithRay2D(ray, gridPt) {
+        let _ray = Util_1.Util.iterToArray(ray);
+        let t = Line.intercept(new Pt_1.Pt(_ray[0]).subtract(gridPt), new Pt_1.Pt(_ray[1]).subtract(gridPt));
+        let g = new Pt_1.Group();
+        if (t && t.xi)
+            g.push(new Pt_1.Pt(gridPt[0] + t.xi, gridPt[1]));
+        if (t && t.yi)
+            g.push(new Pt_1.Pt(gridPt[0], gridPt[1] + t.yi));
+        return g;
+    }
+    static intersectGridWithLine2D(line, gridPt) {
+        let _line = Util_1.Util.iterToArray(line);
+        let g = Line.intersectGridWithRay2D(_line, gridPt);
+        let gg = new Pt_1.Group();
+        for (let i = 0, len = g.length; i < len; i++) {
+            if (Num_1.Geom.withinBound(g[i], _line[0], _line[1]))
+                gg.push(g[i]);
+        }
+        return gg;
+    }
+    static intersectRect2D(line, rect) {
+        let _line = Util_1.Util.iterToArray(line);
+        let _rect = Util_1.Util.iterToArray(rect);
+        let box = Num_1.Geom.boundingBox(Pt_1.Group.fromPtArray(_line));
+        if (!Rectangle.hasIntersectRect2D(box, _rect))
+            return new Pt_1.Group();
+        return Line.intersectLines2D([_line], Rectangle.sides(_rect));
+    }
+    static subpoints(line, num) {
+        let _line = Util_1.Util.iterToArray(line);
+        let pts = new Pt_1.Group();
+        for (let i = 1; i <= num; i++) {
+            pts.push(Num_1.Geom.interpolate(_line[0], _line[1], i / (num + 1)));
+        }
+        return pts;
+    }
+    static crop(line, size, index = 0, cropAsCircle = true) {
+        let _line = Util_1.Util.iterToArray(line);
+        let tdx = (index === 0) ? 1 : 0;
+        let ls = _line[tdx].$subtract(_line[index]);
+        if (ls[0] === 0 || size[0] === 0)
+            return _line[index];
+        if (cropAsCircle) {
+            let d = ls.unit().multiply(size[1]);
+            return _line[index].$add(d);
+        }
+        else {
+            let rect = Rectangle.fromCenter(_line[index], size);
+            let sides = Rectangle.sides(rect);
+            let sideIdx = 0;
+            if (Math.abs(ls[1] / ls[0]) > Math.abs(size[1] / size[0])) {
+                sideIdx = (ls[1] < 0) ? 0 : 2;
+            }
+            else {
+                sideIdx = (ls[0] < 0) ? 3 : 1;
+            }
+            return Line.intersectRay2D(sides[sideIdx], _line);
+        }
+    }
+    static marker(line, size, graphic = ("arrow" || false), atTail = true) {
+        let _line = Util_1.Util.iterToArray(line);
+        let h = atTail ? 0 : 1;
+        let t = atTail ? 1 : 0;
+        let unit = _line[h].$subtract(_line[t]);
+        if (unit.magnitudeSq() === 0)
+            return new Pt_1.Group();
+        unit.unit();
+        let ps = Num_1.Geom.perpendicular(unit).multiply(size[0]).add(_line[t]);
+        if (graphic == "arrow") {
+            ps.add(unit.$multiply(size[1]));
+            return new Pt_1.Group(_line[t], ps[0], ps[1]);
+        }
+        else {
+            return new Pt_1.Group(ps[0], ps[1]);
+        }
+    }
+    static toRect(line) {
+        let _line = Util_1.Util.iterToArray(line);
+        return new Pt_1.Group(_line[0].$min(_line[1]), _line[0].$max(_line[1]));
+    }
+}
+exports.Line = Line;
+class Rectangle {
+    static from(topLeft, widthOrSize, height) {
+        return Rectangle.fromTopLeft(topLeft, widthOrSize, height);
+    }
+    static fromTopLeft(topLeft, widthOrSize, height) {
+        let size = (typeof widthOrSize == "number") ? [widthOrSize, (height || widthOrSize)] : widthOrSize;
+        return new Pt_1.Group(new Pt_1.Pt(topLeft), new Pt_1.Pt(topLeft).add(size));
+    }
+    static fromCenter(center, widthOrSize, height) {
+        let half = (typeof widthOrSize == "number") ? [widthOrSize / 2, (height || widthOrSize) / 2] : new Pt_1.Pt(widthOrSize).divide(2);
+        return new Pt_1.Group(new Pt_1.Pt(center).subtract(half), new Pt_1.Pt(center).add(half));
+    }
+    static toCircle(pts, within = true) {
+        return Circle.fromRect(pts, within);
+    }
+    static toSquare(pts, enclose = false) {
+        let _pts = Util_1.Util.iterToArray(pts);
+        let s = Rectangle.size(_pts);
+        let m = (enclose) ? s.maxValue().value : s.minValue().value;
+        return Rectangle.fromCenter(Rectangle.center(_pts), m, m);
+    }
+    static size(pts) {
+        let p = Util_1.Util.iterToArray(pts);
+        return p[0].$max(p[1]).subtract(p[0].$min(p[1]));
+    }
+    static center(pts) {
+        let p = Util_1.Util.iterToArray(pts);
+        let min = p[0].$min(p[1]);
+        let max = p[0].$max(p[1]);
+        return min.add(max.$subtract(min).divide(2));
+    }
+    static corners(rect) {
+        let _rect = Util_1.Util.iterToArray(rect);
+        let p0 = _rect[0].$min(_rect[1]);
+        let p2 = _rect[0].$max(_rect[1]);
+        return new Pt_1.Group(p0, new Pt_1.Pt(p2.x, p0.y), p2, new Pt_1.Pt(p0.x, p2.y));
+    }
+    static sides(rect) {
+        let [p0, p1, p2, p3] = Rectangle.corners(rect);
+        return [
+            new Pt_1.Group(p0, p1), new Pt_1.Group(p1, p2),
+            new Pt_1.Group(p2, p3), new Pt_1.Group(p3, p0)
+        ];
+    }
+    static boundingBox(rects) {
+        let _rects = Util_1.Util.iterToArray(rects);
+        let merged = Util_1.Util.flatten(_rects, false);
+        let min = Pt_1.Pt.make(2, Number.MAX_VALUE);
+        let max = Pt_1.Pt.make(2, Number.MIN_VALUE);
+        for (let i = 0, len = merged.length; i < len; i++) {
+            let k = 0;
+            for (let m of merged[i]) {
+                min[k] = Math.min(min[k], m[k]);
+                max[k] = Math.max(max[k], m[k]);
+                if (++k >= 2)
+                    break;
+            }
+        }
+        return new Pt_1.Group(min, max);
+    }
+    static polygon(rect) {
+        return Rectangle.corners(rect);
+    }
+    static quadrants(rect, center) {
+        let _rect = Util_1.Util.iterToArray(rect);
+        let corners = Rectangle.corners(_rect);
+        let _center = (center != undefined) ? new Pt_1.Pt(center) : Rectangle.center(_rect);
+        return corners.map((c) => new Pt_1.Group(c, _center).boundingBox());
+    }
+    static halves(rect, ratio = 0.5, asRows = false) {
+        let _rect = Util_1.Util.iterToArray(rect);
+        let min = _rect[0].$min(_rect[1]);
+        let max = _rect[0].$max(_rect[1]);
+        let mid = (asRows) ? Num_1.Num.lerp(min[1], max[1], ratio) : Num_1.Num.lerp(min[0], max[0], ratio);
+        return (asRows)
+            ? [new Pt_1.Group(min, new Pt_1.Pt(max[0], mid)), new Pt_1.Group(new Pt_1.Pt(min[0], mid), max)]
+            : [new Pt_1.Group(min, new Pt_1.Pt(mid, max[1])), new Pt_1.Group(new Pt_1.Pt(mid, min[1]), max)];
+    }
+    static withinBound(rect, pt) {
+        let _rect = Util_1.Util.iterToArray(rect);
+        return Num_1.Geom.withinBound(pt, _rect[0], _rect[1]);
+    }
+    static hasIntersectRect2D(rect1, rect2, resetBoundingBox = false) {
+        let _rect1 = Util_1.Util.iterToArray(rect1);
+        let _rect2 = Util_1.Util.iterToArray(rect2);
+        if (resetBoundingBox) {
+            _rect1 = Num_1.Geom.boundingBox(_rect1);
+            _rect2 = Num_1.Geom.boundingBox(_rect2);
+        }
+        if (_rect1[0][0] > _rect2[1][0] || _rect2[0][0] > _rect1[1][0])
+            return false;
+        if (_rect1[0][1] > _rect2[1][1] || _rect2[0][1] > _rect1[1][1])
+            return false;
+        return true;
+    }
+    static intersectRect2D(rect1, rect2) {
+        let _rect1 = Util_1.Util.iterToArray(rect1);
+        let _rect2 = Util_1.Util.iterToArray(rect2);
+        if (!Rectangle.hasIntersectRect2D(_rect1, _rect2))
+            return new Pt_1.Group();
+        return Line.intersectLines2D(Rectangle.sides(_rect1), Rectangle.sides(_rect2));
+    }
+}
+exports.Rectangle = Rectangle;
+class Circle {
+    static fromRect(pts, enclose = false) {
+        let _pts = Util_1.Util.iterToArray(pts);
+        let r = 0;
+        let min = r = Rectangle.size(_pts).minValue().value / 2;
+        if (enclose) {
+            let max = Rectangle.size(_pts).maxValue().value / 2;
+            r = Math.sqrt(min * min + max * max);
+        }
+        else {
+            r = min;
+        }
+        return new Pt_1.Group(Rectangle.center(_pts), new Pt_1.Pt(r, r));
+    }
+    static fromTriangle(pts, enclose = false) {
+        if (enclose) {
+            return Triangle.circumcircle(pts);
+        }
+        else {
+            return Triangle.incircle(pts);
+        }
+    }
+    static fromCenter(pt, radius) {
+        return new Pt_1.Group(new Pt_1.Pt(pt), new Pt_1.Pt(radius, radius));
+    }
+    static withinBound(pts, pt, threshold = 0) {
+        let _pts = Util_1.Util.iterToArray(pts);
+        let d = _pts[0].$subtract(pt);
+        return d.dot(d) + threshold < _pts[1].x * _pts[1].x;
+    }
+    static intersectRay2D(circle, ray) {
+        let _pts = Util_1.Util.iterToArray(circle);
+        let _ray = Util_1.Util.iterToArray(ray);
+        let d = _ray[0].$subtract(_ray[1]);
+        let f = _pts[0].$subtract(_ray[0]);
+        let a = d.dot(d);
+        let b = f.dot(d);
+        let c = f.dot(f) - _pts[1].x * _pts[1].x;
+        let p = b / a;
+        let q = c / a;
+        let disc = p * p - q;
+        if (disc < 0) {
+            return new Pt_1.Group();
+        }
+        else {
+            let discSqrt = Math.sqrt(disc);
+            let t1 = -p + discSqrt;
+            let p1 = _ray[0].$subtract(d.$multiply(t1));
+            if (disc === 0)
+                return new Pt_1.Group(p1);
+            let t2 = -p - discSqrt;
+            let p2 = _ray[0].$subtract(d.$multiply(t2));
+            return new Pt_1.Group(p1, p2);
+        }
+    }
+    static intersectLine2D(circle, line) {
+        let _pts = Util_1.Util.iterToArray(circle);
+        let _line = Util_1.Util.iterToArray(line);
+        let ps = Circle.intersectRay2D(_pts, _line);
+        let g = new Pt_1.Group();
+        if (ps.length > 0) {
+            for (let i = 0, len = ps.length; i < len; i++) {
+                if (Rectangle.withinBound(_line, ps[i]))
+                    g.push(ps[i]);
+            }
+        }
+        return g;
+    }
+    static intersectCircle2D(circle1, circle2) {
+        let _pts = Util_1.Util.iterToArray(circle1);
+        let _circle = Util_1.Util.iterToArray(circle2);
+        let dv = _circle[0].$subtract(_pts[0]);
+        let dr2 = dv.magnitudeSq();
+        let dr = Math.sqrt(dr2);
+        let ar = _pts[1].x;
+        let br = _circle[1].x;
+        let ar2 = ar * ar;
+        let br2 = br * br;
+        if (dr > ar + br) {
+            return new Pt_1.Group();
+        }
+        else if (dr < Math.abs(ar - br)) {
+            return new Pt_1.Group(_pts[0].clone());
+        }
+        else {
+            let a = (ar2 - br2 + dr2) / (2 * dr);
+            let h = Math.sqrt(ar2 - a * a);
+            let p = dv.$multiply(a / dr).add(_pts[0]);
+            return new Pt_1.Group(new Pt_1.Pt(p.x + h * dv.y / dr, p.y - h * dv.x / dr), new Pt_1.Pt(p.x - h * dv.y / dr, p.y + h * dv.x / dr));
+        }
+    }
+    static intersectRect2D(circle, rect) {
+        let _pts = Util_1.Util.iterToArray(circle);
+        let _rect = Util_1.Util.iterToArray(rect);
+        let sides = Rectangle.sides(_rect);
+        let g = [];
+        for (let i = 0, len = sides.length; i < len; i++) {
+            let ps = Circle.intersectLine2D(_pts, sides[i]);
+            if (ps.length > 0)
+                g.push(ps);
+        }
+        return Util_1.Util.flatten(g);
+    }
+    static toRect(circle, within = false) {
+        let _pts = Util_1.Util.iterToArray(circle);
+        let r = _pts[1][0];
+        if (within) {
+            let half = Math.sqrt(r * r) / 2;
+            return new Pt_1.Group(_pts[0].$subtract(half), _pts[0].$add(half));
+        }
+        else {
+            return new Pt_1.Group(_pts[0].$subtract(r), _pts[0].$add(r));
+        }
+    }
+    static toTriangle(circle, within = true) {
+        let _pts = Util_1.Util.iterToArray(circle);
+        if (within) {
+            let ang = -Math.PI / 2;
+            let inc = Math.PI * 2 / 3;
+            let g = new Pt_1.Group();
+            for (let i = 0; i < 3; i++) {
+                g.push(_pts[0].clone().toAngle(ang, _pts[1][0], true));
+                ang += inc;
+            }
+            return g;
+        }
+        else {
+            return Triangle.fromCenter(_pts[0], _pts[1][0]);
+        }
+    }
+}
+exports.Circle = Circle;
+class Triangle {
+    static fromRect(rect) {
+        let _rect = Util_1.Util.iterToArray(rect);
+        let top = _rect[0].$add(_rect[1]).divide(2);
+        top.y = _rect[0][1];
+        let left = _rect[1].clone();
+        left.x = _rect[0][0];
+        return new Pt_1.Group(top, _rect[1].clone(), left);
+    }
+    static fromCircle(circle) {
+        return Circle.toTriangle(circle, true);
+    }
+    static fromCenter(pt, size) {
+        return Triangle.fromCircle(Circle.fromCenter(pt, size));
+    }
+    static medial(tri) {
+        let _pts = Util_1.Util.iterToArray(tri);
+        if (_pts.length < 3)
+            return _errorLength(new Pt_1.Group(), 3);
+        return Polygon.midpoints(_pts, true);
+    }
+    static oppositeSide(tri, index) {
+        let _pts = Util_1.Util.iterToArray(tri);
+        if (_pts.length < 3)
+            return _errorLength(new Pt_1.Group(), 3);
+        if (index === 0) {
+            return Pt_1.Group.fromPtArray([_pts[1], _pts[2]]);
+        }
+        else if (index === 1) {
+            return Pt_1.Group.fromPtArray([_pts[0], _pts[2]]);
+        }
+        else {
+            return Pt_1.Group.fromPtArray([_pts[0], _pts[1]]);
+        }
+    }
+    static altitude(tri, index) {
+        let _pts = Util_1.Util.iterToArray(tri);
+        let opp = Triangle.oppositeSide(_pts, index);
+        if (opp.length > 1) {
+            return new Pt_1.Group(_pts[index], Line.perpendicularFromPt(opp, _pts[index]));
+        }
+        else {
+            return new Pt_1.Group();
+        }
+    }
+    static orthocenter(tri) {
+        let _pts = Util_1.Util.iterToArray(tri);
+        if (_pts.length < 3)
+            return _errorLength(undefined, 3);
+        let a = Triangle.altitude(_pts, 0);
+        let b = Triangle.altitude(_pts, 1);
+        return Line.intersectRay2D(a, b);
+    }
+    static incenter(tri) {
+        let _pts = Util_1.Util.iterToArray(tri);
+        if (_pts.length < 3)
+            return _errorLength(undefined, 3);
+        let a = Polygon.bisector(_pts, 0).add(_pts[0]);
+        let b = Polygon.bisector(_pts, 1).add(_pts[1]);
+        return Line.intersectRay2D(new Pt_1.Group(_pts[0], a), new Pt_1.Group(_pts[1], b));
+    }
+    static incircle(tri, center) {
+        let _pts = Util_1.Util.iterToArray(tri);
+        let c = (center) ? center : Triangle.incenter(_pts);
+        let area = Polygon.area(_pts);
+        let perim = Polygon.perimeter(_pts, true);
+        let r = 2 * area / perim.total;
+        return Circle.fromCenter(c, r);
+    }
+    static circumcenter(tri) {
+        let _pts = Util_1.Util.iterToArray(tri);
+        let md = Triangle.medial(_pts);
+        let a = [md[0], Num_1.Geom.perpendicular(_pts[0].$subtract(md[0])).p1.$add(md[0])];
+        let b = [md[1], Num_1.Geom.perpendicular(_pts[1].$subtract(md[1])).p1.$add(md[1])];
+        return Line.intersectRay2D(a, b);
+    }
+    static circumcircle(tri, center) {
+        let _pts = Util_1.Util.iterToArray(tri);
+        let c = (center) ? center : Triangle.circumcenter(_pts);
+        let r = _pts[0].$subtract(c).magnitude();
+        return Circle.fromCenter(c, r);
+    }
+}
+exports.Triangle = Triangle;
+class Polygon {
+    static centroid(pts) {
+        return Num_1.Geom.centroid(pts);
+    }
+    static rectangle(center, widthOrSize, height) {
+        return Rectangle.corners(Rectangle.fromCenter(center, widthOrSize, height));
+    }
+    static fromCenter(center, radius, sides) {
+        let g = new Pt_1.Group();
+        for (let i = 0; i < sides; i++) {
+            let ang = Math.PI * 2 * i / sides;
+            g.push(new Pt_1.Pt(Math.cos(ang) * radius, Math.sin(ang) * radius).add(center));
+        }
+        return g;
+    }
+    static lineAt(pts, index) {
+        let _pts = Util_1.Util.iterToArray(pts);
+        if (index < 0 || index >= _pts.length)
+            throw new Error("index out of the Polygon's range");
+        return new Pt_1.Group(_pts[index], (index === _pts.length - 1) ? _pts[0] : _pts[index + 1]);
+    }
+    static lines(poly, closePath = true) {
+        let _pts = Util_1.Util.iterToArray(poly);
+        if (_pts.length < 2)
+            return _errorLength(new Pt_1.Group(), 2);
+        let sp = Util_1.Util.split(_pts, 2, 1);
+        if (closePath)
+            sp.push(new Pt_1.Group(_pts[_pts.length - 1], _pts[0]));
+        return sp.map((g) => g);
+    }
+    static midpoints(poly, closePath = false, t = 0.5) {
+        let sides = Polygon.lines(poly, closePath);
+        let mids = sides.map((s) => Num_1.Geom.interpolate(s[0], s[1], t));
+        return mids;
+    }
+    static adjacentSides(poly, index, closePath = false) {
+        let _pts = Util_1.Util.iterToArray(poly);
+        if (_pts.length < 2)
+            return _errorLength(new Pt_1.Group(), 2);
+        if (index < 0 || index >= _pts.length)
+            return _errorOutofBound(new Pt_1.Group(), index);
+        let gs = [];
+        let left = index - 1;
+        if (closePath && left < 0)
+            left = _pts.length - 1;
+        if (left >= 0)
+            gs.push(new Pt_1.Group(_pts[index], _pts[left]));
+        let right = index + 1;
+        if (closePath && right > _pts.length - 1)
+            right = 0;
+        if (right <= _pts.length - 1)
+            gs.push(new Pt_1.Group(_pts[index], _pts[right]));
+        return gs;
+    }
+    static bisector(poly, index) {
+        let sides = Polygon.adjacentSides(poly, index, true);
+        if (sides.length >= 2) {
+            let a = sides[0][1].$subtract(sides[0][0]).unit();
+            let b = sides[1][1].$subtract(sides[1][0]).unit();
+            return a.add(b).divide(2);
+        }
+        else {
+            return undefined;
+        }
+    }
+    static perimeter(poly, closePath = false) {
+        let lines = Polygon.lines(poly, closePath);
+        let mag = 0;
+        let p = Pt_1.Pt.make(lines.length, 0);
+        for (let i = 0, len = lines.length; i < len; i++) {
+            let m = Line.magnitude(lines[i]);
+            mag += m;
+            p[i] = m;
+        }
+        return {
+            total: mag,
+            segments: p
+        };
+    }
+    static area(pts) {
+        let _pts = Util_1.Util.iterToArray(pts);
+        if (_pts.length < 3)
+            return _errorLength(new Pt_1.Group(), 3);
+        let det = (a, b) => a[0] * b[1] - a[1] * b[0];
+        let area = 0;
+        for (let i = 0, len = _pts.length; i < len; i++) {
+            if (i < _pts.length - 1) {
+                area += det(_pts[i], _pts[i + 1]);
+            }
+            else {
+                area += det(_pts[i], _pts[0]);
+            }
+        }
+        return Math.abs(area / 2);
+    }
+    static convexHull(pts, sorted = false) {
+        let _pts = Util_1.Util.iterToArray(pts);
+        if (_pts.length < 3)
+            return _errorLength(new Pt_1.Group(), 3);
+        if (!sorted) {
+            _pts = _pts.slice();
+            _pts.sort((a, b) => a[0] - b[0]);
+        }
+        let left = (a, b, c) => {
+            return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]) > 0;
+        };
+        let dq = [];
+        let bot = _pts.length - 2;
+        let top = bot + 3;
+        dq[bot] = _pts[2];
+        dq[top] = _pts[2];
+        if (left(_pts[0], _pts[1], _pts[2])) {
+            dq[bot + 1] = _pts[0];
+            dq[bot + 2] = _pts[1];
+        }
+        else {
+            dq[bot + 1] = _pts[1];
+            dq[bot + 2] = _pts[0];
+        }
+        for (let i = 3, len = _pts.length; i < len; i++) {
+            let pt = _pts[i];
+            if (left(dq[bot], dq[bot + 1], pt) && left(dq[top - 1], dq[top], pt)) {
+                continue;
+            }
+            while (!left(dq[bot], dq[bot + 1], pt)) {
+                bot += 1;
+            }
+            bot -= 1;
+            dq[bot] = pt;
+            while (!left(dq[top - 1], dq[top], pt)) {
+                top -= 1;
+            }
+            top += 1;
+            dq[top] = pt;
+        }
+        let hull = new Pt_1.Group();
+        for (let h = 0; h < (top - bot); h++) {
+            hull.push(dq[bot + h]);
+        }
+        return hull;
+    }
+    static network(poly, originIndex = 0) {
+        let _pts = Util_1.Util.iterToArray(poly);
+        let g = [];
+        for (let i = 0, len = _pts.length; i < len; i++) {
+            if (i != originIndex)
+                g.push(new Pt_1.Group(_pts[originIndex], _pts[i]));
+        }
+        return g;
+    }
+    static nearestPt(poly, pt) {
+        let _near = Number.MAX_VALUE;
+        let _item = -1;
+        let i = 0;
+        for (let p of poly) {
+            let d = p.$subtract(pt).magnitudeSq();
+            if (d < _near) {
+                _near = d;
+                _item = i;
+            }
+            i++;
+        }
+        return _item;
+    }
+    static projectAxis(poly, unitAxis) {
+        let _poly = Util_1.Util.iterToArray(poly);
+        let dot = unitAxis.dot(_poly[0]);
+        let d = new Pt_1.Pt(dot, dot);
+        for (let n = 1, len = _poly.length; n < len; n++) {
+            dot = unitAxis.dot(_poly[n]);
+            d = new Pt_1.Pt(Math.min(dot, d[0]), Math.max(dot, d[1]));
+        }
+        return d;
+    }
+    static _axisOverlap(poly1, poly2, unitAxis) {
+        let pa = Polygon.projectAxis(poly1, unitAxis);
+        let pb = Polygon.projectAxis(poly2, unitAxis);
+        return (pa[0] < pb[0]) ? pb[0] - pa[1] : pa[0] - pb[1];
+    }
+    static hasIntersectPoint(poly, pt) {
+        let _poly = Util_1.Util.iterToArray(poly);
+        let c = false;
+        for (let i = 0, len = _poly.length; i < len; i++) {
+            let ln = Polygon.lineAt(_poly, i);
+            if (((ln[0][1] > pt[1]) != (ln[1][1] > pt[1])) &&
+                (pt[0] < (ln[1][0] - ln[0][0]) * (pt[1] - ln[0][1]) / (ln[1][1] - ln[0][1]) + ln[0][0])) {
+                c = !c;
+            }
+        }
+        return c;
+    }
+    static hasIntersectCircle(poly, circle) {
+        let _poly = Util_1.Util.iterToArray(poly);
+        let _circle = Util_1.Util.iterToArray(circle);
+        let info = {
+            which: -1,
+            dist: 0,
+            normal: null,
+            edge: null,
+            vertex: null,
+        };
+        let c = _circle[0];
+        let r = _circle[1][0];
+        let minDist = Number.MAX_SAFE_INTEGER;
+        for (let i = 0, len = _poly.length; i < len; i++) {
+            let edge = Polygon.lineAt(_poly, i);
+            let axis = new Pt_1.Pt(edge[0].y - edge[1].y, edge[1].x - edge[0].x).unit();
+            let poly2 = new Pt_1.Group(c.$add(axis.$multiply(r)), c.$subtract(axis.$multiply(r)));
+            let dist = Polygon._axisOverlap(_poly, poly2, axis);
+            if (dist > 0) {
+                return null;
+            }
+            else if (Math.abs(dist) < minDist) {
+                let check = Rectangle.withinBound(edge, Line.perpendicularFromPt(edge, c)) || Circle.intersectLine2D(circle, edge).length > 0;
+                if (check) {
+                    info.edge = edge;
+                    info.normal = axis;
+                    minDist = Math.abs(dist);
+                    info.which = i;
+                }
+            }
+        }
+        if (!info.edge)
+            return null;
+        let dir = c.$subtract(Polygon.centroid(_poly)).dot(info.normal);
+        if (dir < 0)
+            info.normal.multiply(-1);
+        info.dist = minDist;
+        info.vertex = c;
+        return info;
+    }
+    static hasIntersectPolygon(poly1, poly2) {
+        let _poly1 = Util_1.Util.iterToArray(poly1);
+        let _poly2 = Util_1.Util.iterToArray(poly2);
+        let info = {
+            which: -1,
+            dist: 0,
+            normal: new Pt_1.Pt(),
+            edge: new Pt_1.Group(),
+            vertex: new Pt_1.Pt()
+        };
+        let minDist = Number.MAX_SAFE_INTEGER;
+        for (let i = 0, plen = (_poly1.length + _poly2.length); i < plen; i++) {
+            let edge = (i < _poly1.length) ? Polygon.lineAt(_poly1, i) : Polygon.lineAt(_poly2, i - _poly1.length);
+            let axis = new Pt_1.Pt(edge[0].y - edge[1].y, edge[1].x - edge[0].x).unit();
+            let dist = Polygon._axisOverlap(_poly1, _poly2, axis);
+            if (dist > 0) {
+                return null;
+            }
+            else if (Math.abs(dist) < minDist) {
+                info.edge = edge;
+                info.normal = axis;
+                minDist = Math.abs(dist);
+                info.which = (i < _poly1.length) ? 0 : 1;
+            }
+        }
+        info.dist = minDist;
+        let b1 = (info.which === 0) ? _poly2 : _poly1;
+        let b2 = (info.which === 0) ? _poly1 : _poly2;
+        let c1 = Polygon.centroid(b1);
+        let c2 = Polygon.centroid(b2);
+        let dir = c1.$subtract(c2).dot(info.normal);
+        if (dir < 0)
+            info.normal.multiply(-1);
+        let smallest = Number.MAX_SAFE_INTEGER;
+        for (let i = 0, len = b1.length; i < len; i++) {
+            let d = info.normal.dot(b1[i].$subtract(c2));
+            if (d < smallest) {
+                smallest = d;
+                info.vertex = b1[i];
+            }
+        }
+        return info;
+    }
+    static intersectPolygon2D(poly1, poly2) {
+        let _poly1 = Util_1.Util.iterToArray(poly1);
+        let _poly2 = Util_1.Util.iterToArray(poly2);
+        let lp = Polygon.lines(_poly1);
+        let g = [];
+        for (let i = 0, len = lp.length; i < len; i++) {
+            let ins = Line.intersectPolygon2D(lp[i], _poly2, false);
+            if (ins)
+                g.push(ins);
+        }
+        return Util_1.Util.flatten(g, true);
+    }
+    static toRects(polys) {
+        let boxes = [];
+        for (let g of polys) {
+            boxes.push(Num_1.Geom.boundingBox(g));
+        }
+        let merged = Util_1.Util.flatten(boxes, false);
+        boxes.unshift(Num_1.Geom.boundingBox(merged));
+        return boxes;
+    }
+}
+exports.Polygon = Polygon;
+class Curve {
+    static getSteps(steps) {
+        let ts = new Pt_1.Group();
+        for (let i = 0; i <= steps; i++) {
+            let t = i / steps;
+            ts.push(new Pt_1.Pt(t * t * t, t * t, t, 1));
+        }
+        return ts;
+    }
+    static controlPoints(pts, index = 0, copyStart = false) {
+        let _pts = Util_1.Util.iterToArray(pts);
+        if (index > _pts.length - 1)
+            return new Pt_1.Group();
+        let _index = (i) => (i < _pts.length - 1) ? i : _pts.length - 1;
+        let p0 = _pts[index];
+        index = (copyStart) ? index : index + 1;
+        return new Pt_1.Group(p0, _pts[_index(index++)], _pts[_index(index++)], _pts[_index(index++)]);
+    }
+    static _calcPt(ctrls, params) {
+        let x = ctrls.reduce((a, c, i) => a + c.x * params[i], 0);
+        let y = ctrls.reduce((a, c, i) => a + c.y * params[i], 0);
+        if (ctrls[0].length > 2) {
+            let z = ctrls.reduce((a, c, i) => a + c.z * params[i], 0);
+            return new Pt_1.Pt(x, y, z);
+        }
+        return new Pt_1.Pt(x, y);
+    }
+    static catmullRom(pts, steps = 10) {
+        let _pts = Util_1.Util.iterToArray(pts);
+        if (_pts.length < 2)
+            return new Pt_1.Group();
+        let ps = new Pt_1.Group();
+        let ts = Curve.getSteps(steps);
+        let c = Curve.controlPoints(_pts, 0, true);
+        for (let i = 0; i <= steps; i++) {
+            ps.push(Curve.catmullRomStep(ts[i], c));
+        }
+        let k = 0;
+        while (k < _pts.length - 2) {
+            let cp = Curve.controlPoints(_pts, k);
+            if (cp.length > 0) {
+                for (let i = 0; i <= steps; i++) {
+                    ps.push(Curve.catmullRomStep(ts[i], cp));
+                }
+                k++;
+            }
+        }
+        return ps;
+    }
+    static catmullRomStep(step, ctrls) {
+        let m = new Pt_1.Group(new Pt_1.Pt(-0.5, 1, -0.5, 0), new Pt_1.Pt(1.5, -2.5, 0, 1), new Pt_1.Pt(-1.5, 2, 0.5, 0), new Pt_1.Pt(0.5, -0.5, 0, 0));
+        return Curve._calcPt(ctrls, LinearAlgebra_1.Mat.multiply([step], m, true)[0]);
+    }
+    static cardinal(pts, steps = 10, tension = 0.5) {
+        let _pts = Util_1.Util.iterToArray(pts);
+        if (_pts.length < 2)
+            return new Pt_1.Group();
+        let ps = new Pt_1.Group();
+        let ts = Curve.getSteps(steps);
+        let c = Curve.controlPoints(_pts, 0, true);
+        for (let i = 0; i <= steps; i++) {
+            ps.push(Curve.cardinalStep(ts[i], c, tension));
+        }
+        let k = 0;
+        while (k < _pts.length - 2) {
+            let cp = Curve.controlPoints(_pts, k);
+            if (cp.length > 0) {
+                for (let i = 0; i <= steps; i++) {
+                    ps.push(Curve.cardinalStep(ts[i], cp, tension));
+                }
+                k++;
+            }
+        }
+        return ps;
+    }
+    static cardinalStep(step, ctrls, tension = 0.5) {
+        let m = new Pt_1.Group(new Pt_1.Pt(-1, 2, -1, 0), new Pt_1.Pt(-1, 1, 0, 0), new Pt_1.Pt(1, -2, 1, 0), new Pt_1.Pt(1, -1, 0, 0));
+        let h = LinearAlgebra_1.Mat.multiply([step], m, true)[0].multiply(tension);
+        let h2 = (2 * step[0] - 3 * step[1] + 1);
+        let h3 = -2 * step[0] + 3 * step[1];
+        let pt = Curve._calcPt(ctrls, h);
+        pt.x += h2 * ctrls[1].x + h3 * ctrls[2].x;
+        pt.y += h2 * ctrls[1].y + h3 * ctrls[2].y;
+        if (pt.length > 2)
+            pt.z += h2 * ctrls[1].z + h3 * ctrls[2].z;
+        return pt;
+    }
+    static bezier(pts, steps = 10) {
+        let _pts = Util_1.Util.iterToArray(pts);
+        if (_pts.length < 4)
+            return new Pt_1.Group();
+        let ps = new Pt_1.Group();
+        let ts = Curve.getSteps(steps);
+        let k = 0;
+        while (k < _pts.length - 3) {
+            let c = Curve.controlPoints(_pts, k);
+            if (c.length > 0) {
+                for (let i = 0; i <= steps; i++) {
+                    ps.push(Curve.bezierStep(ts[i], c));
+                }
+                k += 3;
+            }
+        }
+        return ps;
+    }
+    static bezierStep(step, ctrls) {
+        let m = new Pt_1.Group(new Pt_1.Pt(-1, 3, -3, 1), new Pt_1.Pt(3, -6, 3, 0), new Pt_1.Pt(-3, 3, 0, 0), new Pt_1.Pt(1, 0, 0, 0));
+        return Curve._calcPt(ctrls, LinearAlgebra_1.Mat.multiply([step], m, true)[0]);
+    }
+    static bspline(pts, steps = 10, tension = 1) {
+        let _pts = Util_1.Util.iterToArray(pts);
+        if (_pts.length < 2)
+            return new Pt_1.Group();
+        let ps = new Pt_1.Group();
+        let ts = Curve.getSteps(steps);
+        let k = 0;
+        while (k < _pts.length - 3) {
+            let c = Curve.controlPoints(_pts, k);
+            if (c.length > 0) {
+                if (tension !== 1) {
+                    for (let i = 0; i <= steps; i++) {
+                        ps.push(Curve.bsplineTensionStep(ts[i], c, tension));
+                    }
+                }
+                else {
+                    for (let i = 0; i <= steps; i++) {
+                        ps.push(Curve.bsplineStep(ts[i], c));
+                    }
+                }
+                k++;
+            }
+        }
+        return ps;
+    }
+    static bsplineStep(step, ctrls) {
+        let m = new Pt_1.Group(new Pt_1.Pt(-0.16666666666666666, 0.5, -0.5, 0.16666666666666666), new Pt_1.Pt(0.5, -1, 0, 0.6666666666666666), new Pt_1.Pt(-0.5, 0.5, 0.5, 0.16666666666666666), new Pt_1.Pt(0.16666666666666666, 0, 0, 0));
+        return Curve._calcPt(ctrls, LinearAlgebra_1.Mat.multiply([step], m, true)[0]);
+    }
+    static bsplineTensionStep(step, ctrls, tension = 1) {
+        let m = new Pt_1.Group(new Pt_1.Pt(-0.16666666666666666, 0.5, -0.5, 0.16666666666666666), new Pt_1.Pt(-1.5, 2, 0, -0.3333333333333333), new Pt_1.Pt(1.5, -2.5, 0.5, 0.16666666666666666), new Pt_1.Pt(0.16666666666666666, 0, 0, 0));
+        let h = LinearAlgebra_1.Mat.multiply([step], m, true)[0].multiply(tension);
+        let h2 = (2 * step[0] - 3 * step[1] + 1);
+        let h3 = -2 * step[0] + 3 * step[1];
+        let pt = Curve._calcPt(ctrls, h);
+        pt.x += h2 * ctrls[1].x + h3 * ctrls[2].x;
+        pt.y += h2 * ctrls[1].y + h3 * ctrls[2].y;
+        if (pt.length > 2)
+            pt.z += h2 * ctrls[1].z + h3 * ctrls[2].z;
+        return pt;
+    }
+}
+exports.Curve = Curve;
+
+
+/***/ }),
+
+/***/ "./src/Physics.ts":
+/*!************************!*\
+  !*** ./src/Physics.ts ***!
+  \************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+/*! Source code licensed under Apache License 2.0. Copyright © 2017-current William Ngan and contributors. (https://github.com/williamngan/pts) */
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.Body = exports.Particle = exports.World = void 0;
+const Pt_1 = __webpack_require__(/*! ./Pt */ "./src/Pt.ts");
+const Op_1 = __webpack_require__(/*! ./Op */ "./src/Op.ts");
+const Num_1 = __webpack_require__(/*! ./Num */ "./src/Num.ts");
+class World {
+    constructor(bound, friction = 1, gravity = 0) {
+        this._lastTime = null;
+        this._gravity = new Pt_1.Pt();
+        this._friction = 1;
+        this._damping = 0.75;
+        this._particles = [];
+        this._bodies = [];
+        this._pnames = [];
+        this._bnames = [];
+        this._bound = Pt_1.Bound.fromGroup(bound);
+        this._friction = friction;
+        this._gravity = (typeof gravity === "number") ? new Pt_1.Pt(0, gravity) : new Pt_1.Pt(gravity);
+        return this;
+    }
+    get bound() { return this._bound; }
+    set bound(bound) { this._bound = bound; }
+    get gravity() { return this._gravity; }
+    set gravity(g) { this._gravity = g; }
+    get friction() { return this._friction; }
+    set friction(f) { this._friction = f; }
+    get damping() { return this._damping; }
+    set damping(f) { this._damping = f; }
+    get bodyCount() { return this._bodies.length; }
+    get particleCount() { return this._particles.length; }
+    body(id) {
+        let idx = id;
+        if (typeof id === "string" && id.length > 0) {
+            idx = this._bnames.indexOf(id);
+        }
+        if (!(idx >= 0))
+            return undefined;
+        return this._bodies[idx];
+    }
+    particle(id) {
+        let idx = id;
+        if (typeof id === "string" && id.length > 0) {
+            idx = this._pnames.indexOf(id);
+        }
+        if (!(idx >= 0))
+            return undefined;
+        return this._particles[idx];
+    }
+    bodyIndex(name) {
+        return this._bnames.indexOf(name);
+    }
+    particleIndex(name) {
+        return this._pnames.indexOf(name);
+    }
+    update(ms) {
+        let dt = ms / 1000;
+        this._updateParticles(dt);
+        this._updateBodies(dt);
+    }
+    drawParticles(fn) {
+        this._drawParticles = fn;
+    }
+    drawBodies(fn) {
+        this._drawBodies = fn;
+    }
+    add(p, name = '') {
+        if (p instanceof Body) {
+            this._bodies.push(p);
+            this._bnames.push(name);
+        }
+        else {
+            this._particles.push(p);
+            this._pnames.push(name);
+        }
+        return this;
+    }
+    _index(fn, id) {
+        let index = 0;
+        if (typeof id === "string") {
+            index = fn(id);
+            if (index < 0)
+                throw new Error(`Cannot find index of ${id}. You can use particleIndex() or bodyIndex() function to check existence by name.`);
+        }
+        else {
+            index = id;
+        }
+        return index;
+    }
+    removeBody(from, count = 1) {
+        const index = this._index(this.bodyIndex.bind(this), from);
+        const param = (index < 0) ? [index * -1 - 1, count] : [index, count];
+        this._bodies.splice(param[0], param[1]);
+        this._bnames.splice(param[0], param[1]);
+        return this;
+    }
+    removeParticle(from, count = 1) {
+        const index = this._index(this.particleIndex.bind(this), from);
+        const param = (index < 0) ? [index * -1 - 1, count] : [index, count];
+        this._particles.splice(param[0], param[1]);
+        this._pnames.splice(param[0], param[1]);
+        return this;
+    }
+    static edgeConstraint(p1, p2, dist, stiff = 1, precise = false) {
+        const m1 = 1 / (p1.mass || 1);
+        const m2 = 1 / (p2.mass || 1);
+        const mm = m1 + m2;
+        let delta = p2.$subtract(p1);
+        let distSq = dist * dist;
+        let d = (precise) ? (dist / delta.magnitude() - 1) : (distSq / (delta.dot(delta) + distSq) - 0.5);
+        let f = delta.$multiply(d * stiff);
+        p1.subtract(f.$multiply(m1 / mm));
+        p2.add(f.$multiply(m2 / mm));
+        return p1;
+    }
+    static boundConstraint(p, rect, damping = 0.75) {
+        let bound = Num_1.Geom.boundingBox(rect);
+        let np = p.$min(bound[1].subtract(p.radius)).$max(bound[0].add(p.radius));
+        if (np[0] === bound[0][0] || np[0] === bound[1][0]) {
+            let c = p.changed.$multiply(damping);
+            p.previous = np.$subtract(new Pt_1.Pt(-c[0], c[1]));
+        }
+        else if (np[1] === bound[0][1] || np[1] === bound[1][1]) {
+            let c = p.changed.$multiply(damping);
+            p.previous = np.$subtract(new Pt_1.Pt(c[0], -c[1]));
+        }
+        p.to(np);
+    }
+    integrate(p, dt, prevDt) {
+        p.addForce(this._gravity);
+        p.verlet(dt, this._friction, prevDt);
+        return p;
+    }
+    _updateParticles(dt) {
+        for (let i = 0, len = this._particles.length; i < len; i++) {
+            let p = this._particles[i];
+            this.integrate(p, dt, this._lastTime);
+            World.boundConstraint(p, this._bound, this._damping);
+            for (let k = i + 1; k < len; k++) {
+                if (i !== k) {
+                    let p2 = this._particles[k];
+                    p.collide(p2, this._damping);
+                }
+            }
+            if (this._drawParticles)
+                this._drawParticles(p, i);
+        }
+        this._lastTime = dt;
+    }
+    _updateBodies(dt) {
+        for (let i = 0, len = this._bodies.length; i < len; i++) {
+            let bds = this._bodies[i];
+            if (bds) {
+                for (let k = 0, klen = bds.length; k < klen; k++) {
+                    let bk = bds[k];
+                    World.boundConstraint(bk, this._bound, this._damping);
+                    this.integrate(bk, dt, this._lastTime);
+                }
+                for (let k = i + 1; k < len; k++) {
+                    bds.processBody(this._bodies[k]);
+                }
+                for (let m = 0, mlen = this._particles.length; m < mlen; m++) {
+                    bds.processParticle(this._particles[m]);
+                }
+                bds.processEdges();
+                if (this._drawBodies)
+                    this._drawBodies(bds, i);
+            }
+        }
+    }
+}
+exports.World = World;
+class Particle extends Pt_1.Pt {
+    constructor(...args) {
+        super(...args);
+        this._mass = 1;
+        this._radius = 0;
+        this._force = new Pt_1.Pt();
+        this._prev = new Pt_1.Pt();
+        this._lock = false;
+        this._prev = this.clone();
+    }
+    get mass() { return this._mass; }
+    set mass(m) { this._mass = m; }
+    get radius() { return this._radius; }
+    set radius(f) { this._radius = f; }
+    get previous() { return this._prev; }
+    set previous(p) { this._prev = p; }
+    get force() { return this._force; }
+    set force(g) { this._force = g; }
+    get body() { return this._body; }
+    set body(b) { this._body = b; }
+    get lock() { return this._lock; }
+    set lock(b) {
+        this._lock = b;
+        this._lockPt = new Pt_1.Pt(this);
+    }
+    get changed() { return this.$subtract(this._prev); }
+    set position(p) {
+        this.previous.to(this);
+        if (this._lock)
+            this._lockPt = p;
+        this.to(p);
+    }
+    size(r) {
+        this._mass = r;
+        this._radius = r;
+        return this;
+    }
+    addForce(...args) {
+        this._force.add(...args);
+        return this._force;
+    }
+    verlet(dt, friction, lastDt) {
+        if (this._lock) {
+            this.to(this._lockPt);
+        }
+        else {
+            let lt = (lastDt) ? lastDt : dt;
+            let a = this._force.multiply(dt * (dt + lt) / 2);
+            let v = this.changed.multiply(friction * dt / lt).add(a);
+            this._prev = this.clone();
+            this.add(v);
+            this._force = new Pt_1.Pt();
+        }
+        return this;
+    }
+    hit(...args) {
+        this._prev.subtract(new Pt_1.Pt(...args).$divide(Math.sqrt(this._mass)));
+        return this;
+    }
+    collide(p2, damp = 1) {
+        let p1 = this;
+        let dp = p1.$subtract(p2);
+        let distSq = dp.magnitudeSq();
+        let dr = p1.radius + p2.radius;
+        if (distSq < dr * dr) {
+            let c1 = p1.changed;
+            let c2 = p2.changed;
+            let dist = Math.sqrt(distSq);
+            let d = dp.$multiply(((dist - dr) / dist) / 2);
+            let np1 = p1.$subtract(d);
+            let np2 = p2.$add(d);
+            p1.to(np1);
+            p2.to(np2);
+            let f1 = damp * dp.dot(c1) / distSq;
+            let f2 = damp * dp.dot(c2) / distSq;
+            let dm1 = p1.mass / (p1.mass + p2.mass);
+            let dm2 = p2.mass / (p1.mass + p2.mass);
+            c1.add(new Pt_1.Pt(f2 * dp[0] - f1 * dp[0], f2 * dp[1] - f1 * dp[1]).$multiply(dm2));
+            c2.add(new Pt_1.Pt(f1 * dp[0] - f2 * dp[0], f1 * dp[1] - f2 * dp[1]).$multiply(dm1));
+            p1.previous = p1.$subtract(c1);
+            p2.previous = p2.$subtract(c2);
+        }
+    }
+    toString() {
+        return `Particle: ${this[0]} ${this[1]} | previous ${this._prev[0]} ${this._prev[1]} | mass ${this._mass}`;
+    }
+}
+exports.Particle = Particle;
+class Body extends Pt_1.Group {
+    constructor() {
+        super();
+        this._cs = [];
+        this._stiff = 1;
+        this._locks = {};
+        this._mass = 1;
+    }
+    static fromGroup(body, stiff = 1, autoLink = true, autoMass = true) {
+        let b = new Body().init(body);
+        if (autoLink)
+            b.linkAll(stiff);
+        if (autoMass)
+            b.autoMass();
+        return b;
+    }
+    init(body, stiff = 1) {
+        let c = new Pt_1.Pt();
+        for (let li of body) {
+            let p = new Particle(li);
+            p.body = this;
+            c.add(li);
+            this.push(p);
+        }
+        this._stiff = stiff;
+        return this;
+    }
+    get mass() { return this._mass; }
+    set mass(m) {
+        this._mass = m;
+        for (let i = 0, len = this.length; i < len; i++) {
+            this[i].mass = this._mass;
+        }
+    }
+    autoMass() {
+        this.mass = Math.sqrt(Op_1.Polygon.area(this)) / 10;
+        return this;
+    }
+    link(index1, index2, stiff) {
+        if (index1 < 0 || index1 >= this.length)
+            throw new Error("index1 is not in the Group's indices");
+        if (index2 < 0 || index2 >= this.length)
+            throw new Error("index1 is not in the Group's indices");
+        let d = this[index1].$subtract(this[index2]).magnitude();
+        this._cs.push([index1, index2, d, stiff || this._stiff]);
+        return this;
+    }
+    linkAll(stiff) {
+        let half = this.length / 2;
+        for (let i = 0, len = this.length; i < len; i++) {
+            let n = (i >= len - 1) ? 0 : i + 1;
+            this.link(i, n, stiff);
+            if (len > 4) {
+                let nd = (Math.floor(half / 2)) + 1;
+                let n2 = (i >= len - nd) ? i % len : i + nd;
+                this.link(i, n2, stiff);
+            }
+            if (i <= half - 1) {
+                this.link(i, Math.min(this.length - 1, i + Math.floor(half)));
+            }
+        }
+    }
+    linksToLines() {
+        let gs = [];
+        for (let i = 0, len = this._cs.length; i < len; i++) {
+            let ln = this._cs[i];
+            gs.push(new Pt_1.Group(this[ln[0]], this[ln[1]]));
+        }
+        return gs;
+    }
+    processEdges() {
+        for (let i = 0, len = this._cs.length; i < len; i++) {
+            let [m, n, d, s] = this._cs[i];
+            World.edgeConstraint(this[m], this[n], d, s);
+        }
+    }
+    processBody(b) {
+        let b1 = this;
+        let b2 = b;
+        let hit = Op_1.Polygon.hasIntersectPolygon(b1, b2);
+        if (hit) {
+            let cv = hit.normal.$multiply(hit.dist);
+            let t;
+            let eg = hit.edge;
+            if (Math.abs(eg[0][0] - eg[1][0]) > Math.abs(eg[0][1] - eg[1][1])) {
+                t = (hit.vertex[0] - cv[0] - eg[0][0]) / (eg[1][0] - eg[0][0]);
+            }
+            else {
+                t = (hit.vertex[1] - cv[1] - eg[0][1]) / (eg[1][1] - eg[0][1]);
+            }
+            let lambda = 1 / (t * t + (1 - t) * (1 - t));
+            let m0 = hit.vertex.body.mass || 1;
+            let m1 = hit.edge[0].body.mass || 1;
+            let mr0 = m0 / (m0 + m1);
+            let mr1 = m1 / (m0 + m1);
+            eg[0].subtract(cv.$multiply(mr0 * (1 - t) * lambda / 2));
+            eg[1].subtract(cv.$multiply(mr0 * t * lambda / 2));
+            hit.vertex.add(cv.$multiply(mr1));
+        }
+    }
+    processParticle(b) {
+        let b1 = this;
+        let b2 = b;
+        let hit = Op_1.Polygon.hasIntersectCircle(b1, Op_1.Circle.fromCenter(b, b.radius));
+        if (hit) {
+            let cv = hit.normal.$multiply(hit.dist);
+            let t;
+            let eg = hit.edge;
+            if (Math.abs(eg[0][0] - eg[1][0]) > Math.abs(eg[0][1] - eg[1][1])) {
+                t = (hit.vertex[0] - cv[0] - eg[0][0]) / (eg[1][0] - eg[0][0]);
+            }
+            else {
+                t = (hit.vertex[1] - cv[1] - eg[0][1]) / (eg[1][1] - eg[0][1]);
+            }
+            let lambda = 1 / (t * t + (1 - t) * (1 - t));
+            let m0 = hit.vertex.mass || b2.mass || 1;
+            let m1 = hit.edge[0].body.mass || 1;
+            let mr0 = m0 / (m0 + m1);
+            let mr1 = m1 / (m0 + m1);
+            eg[0].subtract(cv.$multiply(mr0 * (1 - t) * lambda / 2));
+            eg[1].subtract(cv.$multiply(mr0 * t * lambda / 2));
+            let c1 = b.changed.add(cv.$multiply(mr1));
+            b.previous = b.$subtract(c1);
+        }
+    }
+}
+exports.Body = Body;
+
+
+/***/ }),
+
+/***/ "./src/Play.ts":
+/*!*********************!*\
+  !*** ./src/Play.ts ***!
+  \*********************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.Sound = exports.Tempo = void 0;
+const Pt_1 = __webpack_require__(/*! ./Pt */ "./src/Pt.ts");
+const Num_1 = __webpack_require__(/*! ./Num */ "./src/Num.ts");
+class Tempo {
+    constructor(bpm) {
+        this._listeners = {};
+        this._listenerInc = 0;
+        this.bpm = bpm;
+    }
+    static fromBeat(ms) {
+        return new Tempo(60000 / ms);
+    }
+    get bpm() { return this._bpm; }
+    set bpm(n) {
+        this._bpm = n;
+        this._ms = 60000 / this._bpm;
+    }
+    get ms() { return this._ms; }
+    set ms(n) {
+        this._bpm = Math.floor(60000 / n);
+        this._ms = 60000 / this._bpm;
+    }
+    _createID(listener) {
+        let id = '';
+        if (typeof listener === 'function') {
+            id = '_b' + (this._listenerInc++);
+        }
+        else {
+            id = listener.name || '_b' + (this._listenerInc++);
+        }
+        return id;
+    }
+    every(beats) {
+        let self = this;
+        let p = Array.isArray(beats) ? beats[0] : beats;
+        return {
+            start: function (fn, offset = 0, name) {
+                let id = name || self._createID(fn);
+                self._listeners[id] = { name: id, beats: beats, period: p, index: 0, offset: offset, duration: -1, continuous: false, fn: fn };
+                return this;
+            },
+            progress: function (fn, offset = 0, name) {
+                let id = name || self._createID(fn);
+                self._listeners[id] = { name: id, beats: beats, period: p, index: 0, offset: offset, duration: -1, continuous: true, fn: fn };
+                return this;
+            }
+        };
+    }
+    track(time) {
+        for (let k in this._listeners) {
+            if (this._listeners.hasOwnProperty(k)) {
+                let li = this._listeners[k];
+                let _t = (li.offset) ? time + li.offset : time;
+                let ms = li.period * this._ms;
+                let isStart = false;
+                if (_t > li.duration + ms) {
+                    li.duration = _t - (_t % this._ms);
+                    if (Array.isArray(li.beats)) {
+                        li.index = (li.index + 1) % li.beats.length;
+                        li.period = li.beats[li.index];
+                    }
+                    isStart = true;
+                }
+                let count = Math.max(0, Math.ceil(Math.floor(li.duration / this._ms) / li.period));
+                let params = (li.continuous) ? [count, Num_1.Num.clamp((_t - li.duration) / ms, 0, 1), _t, isStart] : [count];
+                if (li.continuous || isStart) {
+                    let done = li.fn.apply(li, params);
+                    if (done)
+                        delete this._listeners[li.name];
+                }
+            }
+        }
+    }
+    stop(name) {
+        if (this._listeners[name])
+            delete this._listeners[name];
+    }
+    animate(time, ftime) {
+        this.track(time);
+    }
+    resize(bound, evt) {
+        return;
+    }
+    action(type, px, py, evt) {
+        return;
+    }
+}
+exports.Tempo = Tempo;
+class Sound {
+    constructor(type) {
+        this._playing = false;
+        this._type = type;
+        let _ctx = window.AudioContext || window.webkitAudioContext || false;
+        if (!_ctx)
+            throw (new Error("Your browser doesn't support Web Audio. (No AudioContext)"));
+        this._ctx = (_ctx) ? new _ctx() : undefined;
+    }
+    static from(node, ctx, type = "gen", stream) {
+        let s = new Sound(type);
+        s._node = node;
+        s._ctx = ctx;
+        if (stream)
+            s._stream = stream;
+        return s;
+    }
+    static load(source, crossOrigin = "anonymous") {
+        return new Promise((resolve, reject) => {
+            let s = new Sound("file");
+            s._source = (typeof source === 'string') ? new Audio(source) : source;
+            s._source.autoplay = false;
+            s._source.crossOrigin = crossOrigin;
+            s._source.addEventListener("ended", function () { s._playing = false; });
+            s._source.addEventListener('error', function () { reject("Error loading sound"); });
+            s._source.addEventListener('canplaythrough', function () {
+                s._node = s._ctx.createMediaElementSource(s._source);
+                resolve(s);
+            });
+        });
+    }
+    static loadAsBuffer(url) {
+        return new Promise((resolve, reject) => {
+            let request = new XMLHttpRequest();
+            request.open('GET', url, true);
+            request.responseType = 'arraybuffer';
+            let s = new Sound("file");
+            request.onload = function () {
+                s._ctx.decodeAudioData(request.response, function (buffer) {
+                    s.createBuffer(buffer);
+                    resolve(s);
+                }, (err) => reject("Error decoding audio"));
+            };
+            request.send();
+        });
+    }
+    createBuffer(buf) {
+        this._node = this._ctx.createBufferSource();
+        if (buf !== undefined)
+            this._buffer = buf;
+        this._node.buffer = this._buffer;
+        this._node.onended = () => { this._playing = false; };
+        return this;
+    }
+    static generate(type, val) {
+        let s = new Sound("gen");
+        return s._gen(type, val);
+    }
+    _gen(type, val) {
+        this._node = this._ctx.createOscillator();
+        let osc = this._node;
+        osc.type = type;
+        if (type === 'custom') {
+            osc.setPeriodicWave(val);
+        }
+        else {
+            osc.frequency.value = val;
+        }
+        return this;
+    }
+    static input(constraint) {
+        return __awaiter(this, void 0, void 0, function* () {
+            try {
+                let s = new Sound("input");
+                if (!s)
+                    return undefined;
+                const c = constraint ? constraint : { audio: true, video: false };
+                s._stream = yield navigator.mediaDevices.getUserMedia(c);
+                s._node = s._ctx.createMediaStreamSource(s._stream);
+                return s;
+            }
+            catch (e) {
+                console.error("Cannot get audio from input device.");
+                return Promise.resolve(null);
+            }
+        });
+    }
+    get ctx() { return this._ctx; }
+    get node() { return this._node; }
+    get outputNode() { return this._outputNode; }
+    get stream() { return this._stream; }
+    get source() { return this._source; }
+    get buffer() { return this._buffer; }
+    set buffer(b) { this._buffer = b; }
+    get type() { return this._type; }
+    get playing() { return this._playing; }
+    get progress() {
+        let dur = 0;
+        let curr = 0;
+        if (!!this._buffer) {
+            dur = this._buffer.duration;
+            curr = (this._timestamp) ? this._ctx.currentTime - this._timestamp : 0;
+        }
+        else {
+            dur = this._source.duration;
+            curr = this._source.currentTime;
+        }
+        return curr / dur;
+    }
+    get playable() {
+        return (this._type === "input") ? this._node !== undefined : (!!this._buffer || this._source.readyState === 4);
+    }
+    get binSize() {
+        return this.analyzer.size;
+    }
+    get sampleRate() {
+        return this._ctx.sampleRate;
+    }
+    get frequency() {
+        return (this._type === "gen") ? this._node.frequency.value : 0;
+    }
+    set frequency(f) {
+        if (this._type === "gen")
+            this._node.frequency.value = f;
+    }
+    connect(node) {
+        this._node.connect(node);
+        return this;
+    }
+    setOutputNode(outputNode) {
+        this._outputNode = outputNode;
+        return this;
+    }
+    removeOutputNode() {
+        this._outputNode = null;
+        return this;
+    }
+    analyze(size = 256, minDb = -100, maxDb = -30, smooth = 0.8) {
+        let a = this._ctx.createAnalyser();
+        a.fftSize = size * 2;
+        a.minDecibels = minDb;
+        a.maxDecibels = maxDb;
+        a.smoothingTimeConstant = smooth;
+        this.analyzer = {
+            node: a,
+            size: a.frequencyBinCount,
+            data: new Uint8Array(a.frequencyBinCount)
+        };
+        this._node.connect(this.analyzer.node);
+        return this;
+    }
+    _domain(time) {
+        if (this.analyzer) {
+            if (time) {
+                this.analyzer.node.getByteTimeDomainData(this.analyzer.data);
+            }
+            else {
+                this.analyzer.node.getByteFrequencyData(this.analyzer.data);
+            }
+            return this.analyzer.data;
+        }
+        return new Uint8Array(0);
+    }
+    _domainTo(time, size, position = [0, 0], trim = [0, 0]) {
+        let data = (time) ? this.timeDomain() : this.freqDomain();
+        let g = new Pt_1.Group();
+        for (let i = trim[0], len = data.length - trim[1]; i < len; i++) {
+            g.push(new Pt_1.Pt(position[0] + size[0] * i / len, position[1] + size[1] * data[i] / 255));
+        }
+        return g;
+    }
+    timeDomain() {
+        return this._domain(true);
+    }
+    timeDomainTo(size, position = [0, 0], trim = [0, 0]) {
+        return this._domainTo(true, size, position, trim);
+    }
+    freqDomain() {
+        return this._domain(false);
+    }
+    freqDomainTo(size, position = [0, 0], trim = [0, 0]) {
+        return this._domainTo(false, size, position, trim);
+    }
+    reset() {
+        this.stop();
+        this._node.disconnect();
+        return this;
+    }
+    start(timeAt = 0) {
+        if (this._ctx.state === 'suspended')
+            this._ctx.resume();
+        if (this._type === "file") {
+            if (!!this._buffer) {
+                this._node.start(timeAt);
+                this._timestamp = this._ctx.currentTime + timeAt;
+            }
+            else {
+                this._source.play();
+                if (timeAt > 0)
+                    this._source.currentTime = timeAt;
+            }
+        }
+        else if (this._type === "gen") {
+            this._gen(this._node.type, this._node.frequency.value);
+            this._node.start();
+            if (this.analyzer)
+                this._node.connect(this.analyzer.node);
+        }
+        (this._outputNode || this._node).connect(this._ctx.destination);
+        this._playing = true;
+        return this;
+    }
+    stop() {
+        if (this._playing)
+            (this._outputNode || this._node).disconnect(this._ctx.destination);
+        if (this._type === "file") {
+            if (!!this._buffer) {
+                if (this.progress < 1)
+                    this._node.stop();
+            }
+            else {
+                this._source.pause();
+            }
+        }
+        else if (this._type === "gen") {
+            this._node.stop();
+        }
+        else if (this._type === "input") {
+            this._stream.getAudioTracks().forEach(track => track.stop());
+        }
+        this._playing = false;
+        return this;
+    }
+    toggle() {
+        if (this._playing) {
+            this.stop();
+        }
+        else {
+            this.start();
+        }
+        return this;
+    }
+}
+exports.Sound = Sound;
+
+
+/***/ }),
+
+/***/ "./src/Pt.ts":
+/*!*******************!*\
+  !*** ./src/Pt.ts ***!
+  \*******************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+/*! Source code licensed under Apache License 2.0. Copyright © 2017-current William Ngan and contributors. (https://github.com/williamngan/pts) */
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.Bound = exports.Group = exports.Pt = void 0;
+const Util_1 = __webpack_require__(/*! ./Util */ "./src/Util.ts");
+const Num_1 = __webpack_require__(/*! ./Num */ "./src/Num.ts");
+const LinearAlgebra_1 = __webpack_require__(/*! ./LinearAlgebra */ "./src/LinearAlgebra.ts");
+class Pt extends Float32Array {
+    constructor(...args) {
+        if (args.length === 1 && typeof args[0] == "number") {
+            super(args[0]);
+        }
+        else {
+            super((args.length > 0) ? Util_1.Util.getArgs(args) : [0, 0]);
+        }
+    }
+    static make(dimensions, defaultValue = 0, randomize = false) {
+        let p = new Float32Array(dimensions);
+        if (defaultValue)
+            p.fill(defaultValue);
+        if (randomize) {
+            for (let i = 0, len = p.length; i < len; i++) {
+                p[i] = p[i] * Math.random();
+            }
+        }
+        return new Pt(p);
+    }
+    get id() { return this._id; }
+    set id(s) { this._id = s; }
+    get x() { return this[0]; }
+    set x(n) { this[0] = n; }
+    get y() { return this[1]; }
+    set y(n) { this[1] = n; }
+    get z() { return this[2]; }
+    set z(n) { this[2] = n; }
+    get w() { return this[3]; }
+    set w(n) { this[3] = n; }
+    clone() {
+        return new Pt(this);
+    }
+    equals(p, threshold = 0.000001) {
+        for (let i = 0, len = this.length; i < len; i++) {
+            if (Math.abs(this[i] - p[i]) > threshold)
+                return false;
+        }
+        return true;
+    }
+    to(...args) {
+        let p = Util_1.Util.getArgs(args);
+        for (let i = 0, len = Math.min(this.length, p.length); i < len; i++) {
+            this[i] = p[i];
+        }
+        return this;
+    }
+    $to(...args) {
+        return this.clone().to(...args);
+    }
+    toAngle(radian, magnitude, anchorFromPt = false) {
+        let m = (magnitude != undefined) ? magnitude : this.magnitude();
+        let change = [Math.cos(radian) * m, Math.sin(radian) * m];
+        return (anchorFromPt) ? this.add(change) : this.to(change);
+    }
+    op(fn) {
+        let self = this;
+        return (...params) => {
+            return fn(self, ...params);
+        };
+    }
+    ops(fns) {
+        let _ops = [];
+        for (let i = 0, len = fns.length; i < len; i++) {
+            _ops.push(this.op(fns[i]));
+        }
+        return _ops;
+    }
+    $take(axis) {
+        let p = [];
+        for (let i = 0, len = axis.length; i < len; i++) {
+            p.push(this[axis[i]] || 0);
+        }
+        return new Pt(p);
+    }
+    $concat(...args) {
+        return new Pt(this.toArray().concat(Util_1.Util.getArgs(args)));
+    }
+    add(...args) {
+        (args.length === 1 && typeof args[0] == "number") ? LinearAlgebra_1.Vec.add(this, args[0]) : LinearAlgebra_1.Vec.add(this, Util_1.Util.getArgs(args));
+        return this;
+    }
+    $add(...args) { return this.clone().add(...args); }
+    subtract(...args) {
+        (args.length === 1 && typeof args[0] == "number") ? LinearAlgebra_1.Vec.subtract(this, args[0]) : LinearAlgebra_1.Vec.subtract(this, Util_1.Util.getArgs(args));
+        return this;
+    }
+    $subtract(...args) { return this.clone().subtract(...args); }
+    multiply(...args) {
+        (args.length === 1 && typeof args[0] == "number") ? LinearAlgebra_1.Vec.multiply(this, args[0]) : LinearAlgebra_1.Vec.multiply(this, Util_1.Util.getArgs(args));
+        return this;
+    }
+    $multiply(...args) { return this.clone().multiply(...args); }
+    divide(...args) {
+        (args.length === 1 && typeof args[0] == "number") ? LinearAlgebra_1.Vec.divide(this, args[0]) : LinearAlgebra_1.Vec.divide(this, Util_1.Util.getArgs(args));
+        return this;
+    }
+    $divide(...args) { return this.clone().divide(...args); }
+    magnitudeSq() { return LinearAlgebra_1.Vec.dot(this, this); }
+    magnitude() { return LinearAlgebra_1.Vec.magnitude(this); }
+    unit(magnitude = undefined) {
+        LinearAlgebra_1.Vec.unit(this, magnitude);
+        return this;
+    }
+    $unit(magnitude = undefined) { return this.clone().unit(magnitude); }
+    dot(...args) { return LinearAlgebra_1.Vec.dot(this, Util_1.Util.getArgs(args)); }
+    $cross2D(...args) { return LinearAlgebra_1.Vec.cross2D(this, Util_1.Util.getArgs(args)); }
+    $cross(...args) { return LinearAlgebra_1.Vec.cross(this, Util_1.Util.getArgs(args)); }
+    $project(...args) {
+        return this.$multiply(this.dot(...args) / this.magnitudeSq());
+    }
+    projectScalar(...args) {
+        return this.dot(...args) / this.magnitude();
+    }
+    abs() {
+        LinearAlgebra_1.Vec.abs(this);
+        return this;
+    }
+    $abs() {
+        return this.clone().abs();
+    }
+    floor() {
+        LinearAlgebra_1.Vec.floor(this);
+        return this;
+    }
+    $floor() {
+        return this.clone().floor();
+    }
+    ceil() {
+        LinearAlgebra_1.Vec.ceil(this);
+        return this;
+    }
+    $ceil() {
+        return this.clone().ceil();
+    }
+    round() {
+        LinearAlgebra_1.Vec.round(this);
+        return this;
+    }
+    $round() {
+        return this.clone().round();
+    }
+    minValue() {
+        return LinearAlgebra_1.Vec.min(this);
+    }
+    maxValue() {
+        return LinearAlgebra_1.Vec.max(this);
+    }
+    $min(...args) {
+        let p = Util_1.Util.getArgs(args);
+        let m = this.clone();
+        for (let i = 0, len = Math.min(this.length, p.length); i < len; i++) {
+            m[i] = Math.min(this[i], p[i]);
+        }
+        return m;
+    }
+    $max(...args) {
+        let p = Util_1.Util.getArgs(args);
+        let m = this.clone();
+        for (let i = 0, len = Math.min(this.length, p.length); i < len; i++) {
+            m[i] = Math.max(this[i], p[i]);
+        }
+        return m;
+    }
+    angle(axis = Util_1.Const.xy) {
+        return Math.atan2(this[axis[1]], this[axis[0]]);
+    }
+    angleBetween(p, axis = Util_1.Const.xy) {
+        return Num_1.Geom.boundRadian(this.angle(axis)) - Num_1.Geom.boundRadian(p.angle(axis));
+    }
+    scale(scale, anchor) {
+        Num_1.Geom.scale(this, scale, anchor || Pt.make(this.length, 0));
+        return this;
+    }
+    rotate2D(angle, anchor, axis) {
+        Num_1.Geom.rotate2D(this, angle, anchor || Pt.make(this.length, 0), axis);
+        return this;
+    }
+    shear2D(scale, anchor, axis) {
+        Num_1.Geom.shear2D(this, scale, anchor || Pt.make(this.length, 0), axis);
+        return this;
+    }
+    reflect2D(line, axis) {
+        Num_1.Geom.reflect2D(this, line, axis);
+        return this;
+    }
+    toString() {
+        return `Pt(${this.join(", ")})`;
+    }
+    toArray() {
+        return [].slice.call(this);
+    }
+    toGroup() {
+        return new Group(Pt.make(this.length), this.clone());
+    }
+    toBound() {
+        return new Bound(Pt.make(this.length), this.clone());
+    }
+}
+exports.Pt = Pt;
+class Group extends Array {
+    constructor(...args) {
+        super(...args);
+    }
+    get id() { return this._id; }
+    set id(s) { this._id = s; }
+    get p1() { return this[0]; }
+    get p2() { return this[1]; }
+    get p3() { return this[2]; }
+    get p4() { return this[3]; }
+    get q1() { return this[this.length - 1]; }
+    get q2() { return this[this.length - 2]; }
+    get q3() { return this[this.length - 3]; }
+    get q4() { return this[this.length - 4]; }
+    clone() {
+        let group = new Group();
+        for (let i = 0, len = this.length; i < len; i++) {
+            group.push(this[i].clone());
+        }
+        return group;
+    }
+    static fromArray(list) {
+        let g = new Group();
+        for (let li of list) {
+            let p = (li instanceof Pt) ? li : new Pt(li);
+            g.push(p);
+        }
+        return g;
+    }
+    static fromPtArray(list) {
+        return Group.from(list);
+    }
+    split(chunkSize, stride, loopBack = false) {
+        let sp = Util_1.Util.split(this, chunkSize, stride, loopBack);
+        return sp;
+    }
+    insert(pts, index = 0) {
+        Group.prototype.splice.apply(this, [index, 0, ...pts]);
+        return this;
+    }
+    remove(index = 0, count = 1) {
+        let param = (index < 0) ? [index * -1 - 1, count] : [index, count];
+        return Group.prototype.splice.apply(this, param);
+    }
+    segments(pts_per_segment = 2, stride = 1, loopBack = false) {
+        return this.split(pts_per_segment, stride, loopBack);
+    }
+    lines() { return this.segments(2, 1); }
+    centroid() {
+        return Num_1.Geom.centroid(this);
+    }
+    boundingBox() {
+        return Num_1.Geom.boundingBox(this);
+    }
+    anchorTo(ptOrIndex = 0) { Num_1.Geom.anchor(this, ptOrIndex, "to"); }
+    anchorFrom(ptOrIndex = 0) { Num_1.Geom.anchor(this, ptOrIndex, "from"); }
+    op(fn) {
+        let self = this;
+        return (...params) => {
+            return fn(self, ...params);
+        };
+    }
+    ops(fns) {
+        let _ops = [];
+        for (let i = 0, len = fns.length; i < len; i++) {
+            _ops.push(this.op(fns[i]));
+        }
+        return _ops;
+    }
+    interpolate(t) {
+        t = Num_1.Num.clamp(t, 0, 1);
+        let chunk = this.length - 1;
+        let tc = 1 / (this.length - 1);
+        let idx = Math.floor(t / tc);
+        return Num_1.Geom.interpolate(this[idx], this[Math.min(this.length - 1, idx + 1)], (t - idx * tc) * chunk);
+    }
+    moveBy(...args) {
+        return this.add(...args);
+    }
+    moveTo(...args) {
+        let d = new Pt(Util_1.Util.getArgs(args)).subtract(this[0]);
+        this.moveBy(d);
+        return this;
+    }
+    scale(scale, anchor) {
+        for (let i = 0, len = this.length; i < len; i++) {
+            Num_1.Geom.scale(this[i], scale, anchor || this[0]);
+        }
+        return this;
+    }
+    rotate2D(angle, anchor, axis) {
+        for (let i = 0, len = this.length; i < len; i++) {
+            Num_1.Geom.rotate2D(this[i], angle, anchor || this[0], axis);
+        }
+        return this;
+    }
+    shear2D(scale, anchor, axis) {
+        for (let i = 0, len = this.length; i < len; i++) {
+            Num_1.Geom.shear2D(this[i], scale, anchor || this[0], axis);
+        }
+        return this;
+    }
+    reflect2D(line, axis) {
+        for (let i = 0, len = this.length; i < len; i++) {
+            Num_1.Geom.reflect2D(this[i], line, axis);
+        }
+        return this;
+    }
+    sortByDimension(dim, desc = false) {
+        return this.sort((a, b) => (desc) ? b[dim] - a[dim] : a[dim] - b[dim]);
+    }
+    forEachPt(ptFn, ...args) {
+        if (!this[0][ptFn]) {
+            Util_1.Util.warn(`${ptFn} is not a function of Pt`);
+            return this;
+        }
+        for (let i = 0, len = this.length; i < len; i++) {
+            this[i] = this[i][ptFn](...args);
+        }
+        return this;
+    }
+    add(...args) {
+        return this.forEachPt("add", ...args);
+    }
+    subtract(...args) {
+        return this.forEachPt("subtract", ...args);
+    }
+    multiply(...args) {
+        return this.forEachPt("multiply", ...args);
+    }
+    divide(...args) {
+        return this.forEachPt("divide", ...args);
+    }
+    $matrixAdd(g) {
+        return LinearAlgebra_1.Mat.add(this, g);
+    }
+    $matrixMultiply(g, transposed = false, elementwise = false) {
+        return LinearAlgebra_1.Mat.multiply(this, g, transposed, elementwise);
+    }
+    zipSlice(index, defaultValue = false) {
+        return LinearAlgebra_1.Mat.zipSlice(this, index, defaultValue);
+    }
+    $zip(defaultValue = undefined, useLongest = false) {
+        return LinearAlgebra_1.Mat.zip(this, defaultValue, useLongest);
+    }
+    toString() {
+        return "Group[ " + this.reduce((p, c) => p + c.toString() + " ", "") + " ]";
+    }
+}
+exports.Group = Group;
+class Bound extends Group {
+    constructor(...args) {
+        super(...args);
+        this._center = new Pt();
+        this._size = new Pt();
+        this._topLeft = new Pt();
+        this._bottomRight = new Pt();
+        this._inited = false;
+        this.init();
+    }
+    static fromBoundingRect(rect) {
+        let b = new Bound(new Pt(rect.left || 0, rect.top || 0), new Pt(rect.right || 0, rect.bottom || 0));
+        if (rect.width && rect.height)
+            b.size = new Pt(rect.width, rect.height);
+        return b;
+    }
+    static fromGroup(g) {
+        let _g = Util_1.Util.iterToArray(g);
+        if (_g.length < 2)
+            throw new Error("Cannot create a Bound from a group that has less than 2 Pt");
+        return new Bound(_g[0], _g[_g.length - 1]);
+    }
+    init() {
+        if (this.p1) {
+            this._size = this.p1.clone();
+            this._inited = true;
+        }
+        if (this.p1 && this.p2) {
+            let a = this.p1;
+            let b = this.p2;
+            this.topLeft = a.$min(b);
+            this._bottomRight = a.$max(b);
+            this._updateSize();
+            this._inited = true;
+        }
+    }
+    clone() {
+        return new Bound(this._topLeft.clone(), this._bottomRight.clone());
+    }
+    _updateSize() {
+        this._size = this._bottomRight.$subtract(this._topLeft).abs();
+        this._updateCenter();
+    }
+    _updateCenter() {
+        this._center = this._size.$multiply(0.5).add(this._topLeft);
+    }
+    _updatePosFromTop() {
+        this._bottomRight = this._topLeft.$add(this._size);
+        this._updateCenter();
+    }
+    _updatePosFromBottom() {
+        this._topLeft = this._bottomRight.$subtract(this._size);
+        this._updateCenter();
+    }
+    _updatePosFromCenter() {
+        let half = this._size.$multiply(0.5);
+        this._topLeft = this._center.$subtract(half);
+        this._bottomRight = this._center.$add(half);
+    }
+    get size() { return new Pt(this._size); }
+    set size(p) {
+        this._size = new Pt(p);
+        this._updatePosFromTop();
+    }
+    get center() { return new Pt(this._center); }
+    set center(p) {
+        this._center = new Pt(p);
+        this._updatePosFromCenter();
+    }
+    get topLeft() { return new Pt(this._topLeft); }
+    set topLeft(p) {
+        this._topLeft = new Pt(p);
+        this[0] = this._topLeft;
+        this._updateSize();
+    }
+    get bottomRight() { return new Pt(this._bottomRight); }
+    set bottomRight(p) {
+        this._bottomRight = new Pt(p);
+        this[1] = this._bottomRight;
+        this._updateSize();
+    }
+    get width() { return (this._size.length > 0) ? this._size.x : 0; }
+    set width(w) {
+        this._size.x = w;
+        this._updatePosFromTop();
+    }
+    get height() { return (this._size.length > 1) ? this._size.y : 0; }
+    set height(h) {
+        this._size.y = h;
+        this._updatePosFromTop();
+    }
+    get depth() { return (this._size.length > 2) ? this._size.z : 0; }
+    set depth(d) {
+        this._size.z = d;
+        this._updatePosFromTop();
+    }
+    get x() { return this.topLeft.x; }
+    get y() { return this.topLeft.y; }
+    get z() { return this.topLeft.z; }
+    get inited() { return this._inited; }
+    update() {
+        this._topLeft = this[0];
+        this._bottomRight = this[1];
+        this._updateSize();
+        return this;
+    }
+}
+exports.Bound = Bound;
+
+
+/***/ }),
+
+/***/ "./src/Space.ts":
+/*!**********************!*\
+  !*** ./src/Space.ts ***!
+  \**********************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+/*! Source code licensed under Apache License 2.0. Copyright © 2017-current William Ngan and contributors. (https://github.com/williamngan/pts) */
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.MultiTouchSpace = exports.Space = void 0;
+const Pt_1 = __webpack_require__(/*! ./Pt */ "./src/Pt.ts");
+const UI_1 = __webpack_require__(/*! ./UI */ "./src/UI.ts");
+class Space {
+    constructor() {
+        this.id = "space";
+        this.bound = new Pt_1.Bound();
+        this._time = { prev: 0, diff: 0, end: -1 };
+        this.players = {};
+        this.playerCount = 0;
+        this._animID = -1;
+        this._pause = false;
+        this._refresh = undefined;
+        this._pointer = new Pt_1.Pt();
+        this._isReady = false;
+        this._playing = false;
+    }
+    refresh(b) {
+        this._refresh = b;
+        return this;
+    }
+    add(p) {
+        let player = (typeof p == "function") ? { animate: p } : p;
+        let k = this.playerCount++;
+        let pid = this.id + k;
+        this.players[pid] = player;
+        player.animateID = pid;
+        if (player.resize && this.bound.inited)
+            player.resize(this.bound);
+        if (this._refresh === undefined)
+            this._refresh = true;
+        return this;
+    }
+    remove(player) {
+        delete this.players[player.animateID];
+        return this;
+    }
+    removeAll() {
+        this.players = {};
+        return this;
+    }
+    play(time = 0) {
+        if (time === 0 && this._animID !== -1) {
+            return;
+        }
+        this._animID = requestAnimationFrame(this.play.bind(this));
+        if (this._pause)
+            return this;
+        this._time.diff = time - this._time.prev;
+        this._time.prev = time;
+        try {
+            this.playItems(time);
+        }
+        catch (err) {
+            cancelAnimationFrame(this._animID);
+            this._animID = -1;
+            this._playing = false;
+            throw err;
+        }
+        return this;
+    }
+    replay() {
+        this._time.end = -1;
+        this.play();
+    }
+    playItems(time) {
+        this._playing = true;
+        if (this._refresh)
+            this.clear();
+        if (this._isReady) {
+            for (let k in this.players) {
+                if (this.players[k].animate)
+                    this.players[k].animate(time, this._time.diff, this);
+            }
+        }
+        if (this._time.end >= 0 && time > this._time.end) {
+            cancelAnimationFrame(this._animID);
+            this._animID = -1;
+            this._playing = false;
+        }
+    }
+    pause(toggle = false) {
+        this._pause = (toggle) ? !this._pause : true;
+        return this;
+    }
+    resume() {
+        this._pause = false;
+        return this;
+    }
+    stop(t = 0) {
+        this._time.end = t;
+        return this;
+    }
+    playOnce(duration = 5000) {
+        this.play();
+        this.stop(duration);
+        return this;
+    }
+    render(context) {
+        if (this._renderFunc)
+            this._renderFunc(context, this);
+        return this;
+    }
+    set customRendering(f) { this._renderFunc = f; }
+    get customRendering() { return this._renderFunc; }
+    get isPlaying() { return this._playing; }
+    get outerBound() { return this.bound.clone(); }
+    get innerBound() { return new Pt_1.Bound(Pt_1.Pt.make(this.size.length, 0), this.size.clone()); }
+    get size() { return this.bound.size.clone(); }
+    get center() { return this.size.divide(2); }
+    get width() { return this.bound.width; }
+    get height() { return this.bound.height; }
+}
+exports.Space = Space;
+class MultiTouchSpace extends Space {
+    constructor() {
+        super(...arguments);
+        this._pressed = false;
+        this._dragged = false;
+        this._hasMouse = false;
+        this._hasTouch = false;
+    }
+    get pointer() {
+        let p = this._pointer.clone();
+        p.id = this._pointer.id;
+        return p;
+    }
+    bindCanvas(evt, callback, options = {}) {
+        this._canvas.addEventListener(evt, callback, options);
+    }
+    unbindCanvas(evt, callback) {
+        this._canvas.removeEventListener(evt, callback);
+    }
+    bindMouse(_bind = true) {
+        if (_bind) {
+            this.bindCanvas("mousedown", this._mouseDown.bind(this));
+            this.bindCanvas("mouseup", this._mouseUp.bind(this));
+            this.bindCanvas("mouseover", this._mouseOver.bind(this));
+            this.bindCanvas("mouseout", this._mouseOut.bind(this));
+            this.bindCanvas("mousemove", this._mouseMove.bind(this));
+            this.bindCanvas("click", this._mouseClick.bind(this));
+            this.bindCanvas("contextmenu", this._contextMenu.bind(this));
+            this._hasMouse = true;
+        }
+        else {
+            this.unbindCanvas("mousedown", this._mouseDown.bind(this));
+            this.unbindCanvas("mouseup", this._mouseUp.bind(this));
+            this.unbindCanvas("mouseover", this._mouseOver.bind(this));
+            this.unbindCanvas("mouseout", this._mouseOut.bind(this));
+            this.unbindCanvas("mousemove", this._mouseMove.bind(this));
+            this.unbindCanvas("click", this._mouseClick.bind(this));
+            this.unbindCanvas("contextmenu", this._contextMenu.bind(this));
+            this._hasMouse = false;
+        }
+        return this;
+    }
+    bindTouch(_bind = true) {
+        if (_bind) {
+            this.bindCanvas("touchstart", this._touchStart.bind(this), { passive: true });
+            this.bindCanvas("touchend", this._mouseUp.bind(this));
+            this.bindCanvas("touchmove", this._touchMove.bind(this), { passive: true });
+            this.bindCanvas("touchcancel", this._mouseOut.bind(this));
+            this._hasTouch = true;
+        }
+        else {
+            this.unbindCanvas("touchstart", this._touchStart.bind(this));
+            this.unbindCanvas("touchend", this._mouseUp.bind(this));
+            this.unbindCanvas("touchmove", this._touchMove.bind(this));
+            this.unbindCanvas("touchcancel", this._mouseOut.bind(this));
+            this._hasTouch = false;
+        }
+        return this;
+    }
+    touchesToPoints(evt, which = "touches") {
+        if (!evt || !evt[which])
+            return [];
+        let ts = [];
+        for (var i = 0; i < evt[which].length; i++) {
+            let t = evt[which].item(i);
+            ts.push(new Pt_1.Pt(t.pageX - this.bound.topLeft.x, t.pageY - this.bound.topLeft.y));
+        }
+        return ts;
+    }
+    _mouseAction(type, evt) {
+        let px = 0, py = 0;
+        if (evt instanceof MouseEvent) {
+            for (let k in this.players) {
+                if (this.players.hasOwnProperty(k)) {
+                    let v = this.players[k];
+                    px = evt.pageX - this.outerBound.x;
+                    py = evt.pageY - this.outerBound.y;
+                    if (v.action)
+                        v.action(type, px, py, evt);
+                }
+            }
+        }
+        else {
+            for (let k in this.players) {
+                if (this.players.hasOwnProperty(k)) {
+                    let v = this.players[k];
+                    let c = evt.changedTouches && evt.changedTouches.length > 0;
+                    let touch = evt.changedTouches.item(0);
+                    px = (c) ? touch.pageX - this.outerBound.x : 0;
+                    py = (c) ? touch.pageY - this.outerBound.y : 0;
+                    if (v.action)
+                        v.action(type, px, py, evt);
+                }
+            }
+        }
+        if (type) {
+            this._pointer.to(px, py);
+            this._pointer.id = type;
+        }
+    }
+    _mouseDown(evt) {
+        this._mouseAction(UI_1.UIPointerActions.down, evt);
+        this._pressed = true;
+        return false;
+    }
+    _mouseUp(evt) {
+        if (this._dragged) {
+            this._mouseAction(UI_1.UIPointerActions.drop, evt);
+        }
+        else {
+            this._mouseAction(UI_1.UIPointerActions.up, evt);
+        }
+        this._pressed = false;
+        this._dragged = false;
+        return false;
+    }
+    _mouseMove(evt) {
+        this._mouseAction(UI_1.UIPointerActions.move, evt);
+        if (this._pressed) {
+            this._dragged = true;
+            this._mouseAction(UI_1.UIPointerActions.drag, evt);
+        }
+        return false;
+    }
+    _mouseOver(evt) {
+        this._mouseAction(UI_1.UIPointerActions.over, evt);
+        return false;
+    }
+    _mouseOut(evt) {
+        this._mouseAction(UI_1.UIPointerActions.out, evt);
+        if (this._dragged)
+            this._mouseAction(UI_1.UIPointerActions.drop, evt);
+        this._dragged = false;
+        return false;
+    }
+    _mouseClick(evt) {
+        this._mouseAction(UI_1.UIPointerActions.click, evt);
+        this._pressed = false;
+        this._dragged = false;
+        return false;
+    }
+    _contextMenu(evt) {
+        this._mouseAction(UI_1.UIPointerActions.contextmenu, evt);
+        return false;
+    }
+    _touchMove(evt) {
+        this._mouseMove(evt);
+        evt.preventDefault();
+        return false;
+    }
+    _touchStart(evt) {
+        this._mouseDown(evt);
+        evt.preventDefault();
+        return false;
+    }
+}
+exports.MultiTouchSpace = MultiTouchSpace;
+
+
+/***/ }),
+
+/***/ "./src/Svg.ts":
+/*!********************!*\
+  !*** ./src/Svg.ts ***!
+  \********************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+/*! Source code licensed under Apache License 2.0. Copyright © 2017-current William Ngan and contributors. (https://github.com/williamngan/pts) */
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.SVGForm = exports.SVGSpace = void 0;
+const Form_1 = __webpack_require__(/*! ./Form */ "./src/Form.ts");
+const Num_1 = __webpack_require__(/*! ./Num */ "./src/Num.ts");
+const Util_1 = __webpack_require__(/*! ./Util */ "./src/Util.ts");
+const Pt_1 = __webpack_require__(/*! ./Pt */ "./src/Pt.ts");
+const Op_1 = __webpack_require__(/*! ./Op */ "./src/Op.ts");
+const Dom_1 = __webpack_require__(/*! ./Dom */ "./src/Dom.ts");
+class SVGSpace extends Dom_1.DOMSpace {
+    constructor(elem, callback) {
+        super(elem, callback);
+        this._bgcolor = "#999";
+        if (this._canvas.nodeName.toLowerCase() != "svg") {
+            let s = SVGSpace.svgElement(this._canvas, "svg", `${this.id}_svg`);
+            this._container = this._canvas;
+            this._canvas = s;
+        }
+    }
+    getForm() { return new SVGForm(this); }
+    get element() {
+        return this._canvas;
+    }
+    resize(b, evt) {
+        super.resize(b, evt);
+        SVGSpace.setAttr(this.element, {
+            "viewBox": `0 0 ${this.bound.width} ${this.bound.height}`,
+            "width": `${this.bound.width}`,
+            "height": `${this.bound.height}`,
+            "xmlns": "http://www.w3.org/2000/svg",
+            "version": "1.1"
+        });
+        return this;
+    }
+    static svgElement(parent, name, id) {
+        if (!parent || !parent.appendChild)
+            throw new Error("parent is not a valid DOM element");
+        let elem = document.querySelector(`#${id}`);
+        if (!elem) {
+            elem = document.createElementNS("http://www.w3.org/2000/svg", name);
+            elem.setAttribute("id", id);
+            parent.appendChild(elem);
+        }
+        return elem;
+    }
+    remove(player) {
+        let temp = this._container.querySelectorAll("." + SVGForm.scopeID(player));
+        temp.forEach((el) => {
+            el.parentNode.removeChild(el);
+        });
+        return super.remove(player);
+    }
+    removeAll() {
+        this._container.innerHTML = "";
+        return super.removeAll();
+    }
+}
+exports.SVGSpace = SVGSpace;
+class SVGForm extends Form_1.VisualForm {
+    constructor(space) {
+        super();
+        this._style = {
+            "filled": true,
+            "stroked": true,
+            "fill": "#f03",
+            "stroke": "#fff",
+            "stroke-width": 1,
+            "stroke-linejoin": "bevel",
+            "stroke-linecap": "sqaure",
+            "opacity": 1
+        };
+        this._ctx = {
+            group: null,
+            groupID: "pts",
+            groupCount: 0,
+            currentID: "pts0",
+            currentClass: "",
+            style: {},
+        };
+        this._ready = false;
+        this._space = space;
+        this._space.add({ start: () => {
+                this._ctx.group = this._space.element;
+                this._ctx.groupID = "pts_svg_" + (SVGForm.groupID++);
+                this._ctx.style = Object.assign({}, this._style);
+                this._ready = true;
+            } });
+    }
+    get space() { return this._space; }
+    styleTo(k, v) {
+        if (this._ctx.style[k] === undefined)
+            throw new Error(`${k} style property doesn't exist`);
+        this._ctx.style[k] = v;
+    }
+    alpha(a) {
+        this.styleTo("opacity", a);
+        return this;
+    }
+    fill(c) {
+        if (typeof c == "boolean") {
+            this.styleTo("filled", c);
+        }
+        else {
+            this.styleTo("filled", true);
+            this.styleTo("fill", c);
+        }
+        return this;
+    }
+    stroke(c, width, linejoin, linecap) {
+        if (typeof c == "boolean") {
+            this.styleTo("stroked", c);
+        }
+        else {
+            this.styleTo("stroked", true);
+            this.styleTo("stroke", c);
+            if (width)
+                this.styleTo("stroke-width", width);
+            if (linejoin)
+                this.styleTo("stroke-linejoin", linejoin);
+            if (linecap)
+                this.styleTo("stroke-linecap", linecap);
+        }
+        return this;
+    }
+    cls(c) {
+        if (typeof c == "boolean") {
+            this._ctx.currentClass = "";
+        }
+        else {
+            this._ctx.currentClass = c;
+        }
+        return this;
+    }
+    font(sizeOrFont, weight, style, lineHeight, family) {
+        if (typeof sizeOrFont == "number") {
+            this._font.size = sizeOrFont;
+            if (family)
+                this._font.face = family;
+            if (weight)
+                this._font.weight = weight;
+            if (style)
+                this._font.style = style;
+            if (lineHeight)
+                this._font.lineHeight = lineHeight;
+        }
+        else {
+            this._font = sizeOrFont;
+        }
+        this._ctx.style['font'] = this._font.value;
+        return this;
+    }
+    reset() {
+        this._ctx.style = Object.assign({}, this._style);
+        this._font = new Form_1.Font(10, "sans-serif");
+        this._ctx.style['font'] = this._font.value;
+        return this;
+    }
+    updateScope(group_id, group) {
+        this._ctx.group = group;
+        this._ctx.groupID = group_id;
+        this._ctx.groupCount = 0;
+        this.nextID();
+        return this._ctx;
+    }
+    scope(item) {
+        if (!item || item.animateID == null)
+            throw new Error("item not defined or not yet added to Space");
+        return this.updateScope(SVGForm.scopeID(item), this.space.element);
+    }
+    nextID() {
+        this._ctx.groupCount++;
+        this._ctx.currentID = `${this._ctx.groupID}-${this._ctx.groupCount}`;
+        return this._ctx.currentID;
+    }
+    static getID(ctx) {
+        return ctx.currentID || `p-${SVGForm.domID++}`;
+    }
+    static scopeID(item) {
+        return `item-${item.animateID}`;
+    }
+    static style(elem, styles) {
+        let st = [];
+        if (!styles["filled"])
+            st.push("fill: none");
+        if (!styles["stroked"])
+            st.push("stroke: none");
+        for (let k in styles) {
+            if (styles.hasOwnProperty(k) && k != "filled" && k != "stroked") {
+                let v = styles[k];
+                if (v) {
+                    if (!styles["filled"] && k.indexOf('fill') === 0) {
+                        continue;
+                    }
+                    else if (!styles["stroked"] && k.indexOf('stroke') === 0) {
+                        continue;
+                    }
+                    else {
+                        st.push(`${k}: ${v}`);
+                    }
+                }
+            }
+        }
+        return Dom_1.DOMSpace.setAttr(elem, { style: st.join(";") });
+    }
+    static point(ctx, pt, radius = 5, shape = "square") {
+        if (shape === "circle") {
+            return SVGForm.circle(ctx, pt, radius);
+        }
+        else {
+            return SVGForm.square(ctx, pt, radius);
+        }
+    }
+    point(pt, radius = 5, shape = "square") {
+        this.nextID();
+        SVGForm.point(this._ctx, pt, radius, shape);
+        return this;
+    }
+    static circle(ctx, pt, radius = 10) {
+        let elem = SVGSpace.svgElement(ctx.group, "circle", SVGForm.getID(ctx));
+        Dom_1.DOMSpace.setAttr(elem, {
+            cx: pt[0],
+            cy: pt[1],
+            r: radius,
+            'class': `pts-svgform pts-circle ${ctx.currentClass}`,
+        });
+        SVGForm.style(elem, ctx.style);
+        return elem;
+    }
+    circle(pts) {
+        this.nextID();
+        let p = Util_1.Util.iterToArray(pts);
+        SVGForm.circle(this._ctx, p[0], p[1][0]);
+        return this;
+    }
+    static arc(ctx, pt, radius, startAngle, endAngle, cc) {
+        let elem = SVGSpace.svgElement(ctx.group, "path", SVGForm.getID(ctx));
+        const start = new Pt_1.Pt(pt).toAngle(startAngle, radius, true);
+        const end = new Pt_1.Pt(pt).toAngle(endAngle, radius, true);
+        const diff = Num_1.Geom.boundAngle(endAngle) - Num_1.Geom.boundAngle(startAngle);
+        let largeArc = (diff > Util_1.Const.pi) ? true : false;
+        if (cc)
+            largeArc = !largeArc;
+        const sweep = (cc) ? "0" : "1";
+        const d = `M ${start[0]} ${start[1]} A ${radius} ${radius} 0 ${largeArc ? "1" : "0"} ${sweep} ${end[0]} ${end[1]}`;
+        Dom_1.DOMSpace.setAttr(elem, {
+            d: d,
+            'class': `pts-svgform pts-arc ${ctx.currentClass}`,
+        });
+        SVGForm.style(elem, ctx.style);
+        return elem;
+    }
+    arc(pt, radius, startAngle, endAngle, cc) {
+        this.nextID();
+        SVGForm.arc(this._ctx, pt, radius, startAngle, endAngle, cc);
+        return this;
+    }
+    static square(ctx, pt, halfsize) {
+        let elem = SVGSpace.svgElement(ctx.group, "rect", SVGForm.getID(ctx));
+        Dom_1.DOMSpace.setAttr(elem, {
+            x: pt[0] - halfsize,
+            y: pt[1] - halfsize,
+            width: halfsize * 2,
+            height: halfsize * 2,
+            'class': `pts-svgform pts-square ${ctx.currentClass}`,
+        });
+        SVGForm.style(elem, ctx.style);
+        return elem;
+    }
+    square(pt, halfsize) {
+        this.nextID();
+        SVGForm.square(this._ctx, pt, halfsize);
+        return this;
+    }
+    static line(ctx, pts) {
+        let points = SVGForm.pointsString(pts);
+        if (points.count < 2)
+            return;
+        if (points.count > 2)
+            return SVGForm._poly(ctx, points.string, false);
+        let elem = SVGSpace.svgElement(ctx.group, "line", SVGForm.getID(ctx));
+        let p = Util_1.Util.iterToArray(pts);
+        Dom_1.DOMSpace.setAttr(elem, {
+            x1: p[0][0],
+            y1: p[0][1],
+            x2: p[1][0],
+            y2: p[1][1],
+            'class': `pts-svgform pts-line ${ctx.currentClass}`,
+        });
+        SVGForm.style(elem, ctx.style);
+        return elem;
+    }
+    line(pts) {
+        this.nextID();
+        SVGForm.line(this._ctx, pts);
+        return this;
+    }
+    static _poly(ctx, points, closePath = true) {
+        let elem = SVGSpace.svgElement(ctx.group, ((closePath) ? "polygon" : "polyline"), SVGForm.getID(ctx));
+        Dom_1.DOMSpace.setAttr(elem, {
+            points: points,
+            'class': `pts-svgform pts-polygon ${ctx.currentClass}`,
+        });
+        SVGForm.style(elem, ctx.style);
+        return elem;
+    }
+    static pointsString(pts) {
+        let points = "";
+        let count = 0;
+        for (let p of pts) {
+            points += `${p[0]},${p[1]} `;
+            count++;
+        }
+        return { string: points, count: count };
+    }
+    static polygon(ctx, pts) {
+        let points = SVGForm.pointsString(pts);
+        return SVGForm._poly(ctx, points.string, true);
+    }
+    polygon(pts) {
+        this.nextID();
+        SVGForm.polygon(this._ctx, pts);
+        return this;
+    }
+    static rect(ctx, pts) {
+        if (!Util_1.Util.arrayCheck(pts))
+            return;
+        let elem = SVGSpace.svgElement(ctx.group, "rect", SVGForm.getID(ctx));
+        let bound = Pt_1.Group.fromArray(pts).boundingBox();
+        let size = Op_1.Rectangle.size(bound);
+        Dom_1.DOMSpace.setAttr(elem, {
+            x: bound[0][0],
+            y: bound[0][1],
+            width: size[0],
+            height: size[1],
+            'class': `pts-svgform pts-rect ${ctx.currentClass}`,
+        });
+        SVGForm.style(elem, ctx.style);
+        return elem;
+    }
+    rect(pts) {
+        this.nextID();
+        SVGForm.rect(this._ctx, pts);
+        return this;
+    }
+    static text(ctx, pt, txt) {
+        let elem = SVGSpace.svgElement(ctx.group, "text", SVGForm.getID(ctx));
+        Dom_1.DOMSpace.setAttr(elem, {
+            "pointer-events": "none",
+            x: pt[0],
+            y: pt[1],
+            dx: 0, dy: 0,
+            'class': `pts-svgform pts-text ${ctx.currentClass}`,
+        });
+        elem.textContent = txt;
+        SVGForm.style(elem, ctx.style);
+        return elem;
+    }
+    text(pt, txt) {
+        this.nextID();
+        SVGForm.text(this._ctx, pt, txt);
+        return this;
+    }
+    log(txt) {
+        this.fill("#000").stroke("#fff", 0.5).text([10, 14], txt);
+        return this;
+    }
+}
+exports.SVGForm = SVGForm;
+SVGForm.groupID = 0;
+SVGForm.domID = 0;
+
+
+/***/ }),
+
+/***/ "./src/Types.ts":
+/*!**********************!*\
+  !*** ./src/Types.ts ***!
+  \**********************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+/*! Source code licensed under Apache License 2.0. Copyright © 2017-current William Ngan and contributors. (https://github.com/williamngan/pts) */
+Object.defineProperty(exports, "__esModule", { value: true });
+
+
+/***/ }),
+
+/***/ "./src/Typography.ts":
+/*!***************************!*\
+  !*** ./src/Typography.ts ***!
+  \***************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+/*! Source code licensed under Apache License 2.0. Copyright © 2017-current William Ngan and contributors. (https://github.com/williamngan/pts) */
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.Typography = void 0;
+const Pt_1 = __webpack_require__(/*! ./Pt */ "./src/Pt.ts");
+class Typography {
+    static textWidthEstimator(fn, samples = ["M", "n", "."], distribution = [0.06, 0.8, 0.14]) {
+        let m = samples.map(fn);
+        let avg = new Pt_1.Pt(distribution).dot(m);
+        return (str) => str.length * avg;
+    }
+    static truncate(fn, str, width, tail = "") {
+        let trim = Math.floor(str.length * Math.min(1, width / fn(str)));
+        if (trim < str.length) {
+            trim = Math.max(0, trim - tail.length);
+            return [str.substr(0, trim) + tail, trim];
+        }
+        else {
+            return [str, str.length];
+        }
+    }
+    static fontSizeToBox(box, ratio = 1, byHeight = true) {
+        let bound = Pt_1.Bound.fromGroup(box);
+        let h = byHeight ? bound.height : bound.width;
+        let f = ratio * h;
+        return function (box2) {
+            let bound2 = Pt_1.Bound.fromGroup(box2);
+            let nh = (byHeight ? bound2.height : bound2.width) / h;
+            return f * nh;
+        };
+    }
+    static fontSizeToThreshold(threshold, direction = 0) {
+        return function (defaultSize, val) {
+            let d = defaultSize * val / threshold;
+            if (direction < 0)
+                return Math.min(d, defaultSize);
+            if (direction > 0)
+                return Math.max(d, defaultSize);
+            return d;
+        };
+    }
+}
+exports.Typography = Typography;
+
+
+/***/ }),
+
+/***/ "./src/UI.ts":
+/*!*******************!*\
+  !*** ./src/UI.ts ***!
+  \*******************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+/*! Source code licensed under Apache License 2.0. Copyright © 2017-current William Ngan and contributors. (https://github.com/williamngan/pts) */
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.UIDragger = exports.UIButton = exports.UI = exports.UIPointerActions = exports.UIShape = void 0;
+const Pt_1 = __webpack_require__(/*! ./Pt */ "./src/Pt.ts");
+const Op_1 = __webpack_require__(/*! ./Op */ "./src/Op.ts");
+exports.UIShape = {
+    rectangle: "rectangle", circle: "circle", polygon: "polygon", polyline: "polyline", line: "line"
+};
+exports.UIPointerActions = {
+    up: "up", down: "down", move: "move", drag: "drag", uidrag: "uidrag", drop: "drop", uidrop: "uidrop", over: "over", out: "out", enter: "enter", leave: "leave", click: "click", contextmenu: "contextmenu", all: "all"
+};
+class UI {
+    constructor(group, shape, states = {}, id) {
+        this._holds = new Map();
+        this._group = Pt_1.Group.fromArray(group);
+        this._shape = shape;
+        this._id = id === undefined ? `ui_${(UI._counter++)}` : id;
+        this._states = states;
+        this._actions = {};
+    }
+    static fromRectangle(group, states, id) {
+        return new this(group, exports.UIShape.rectangle, states, id);
+    }
+    static fromCircle(group, states, id) {
+        return new this(group, exports.UIShape.circle, states, id);
+    }
+    static fromPolygon(group, states, id) {
+        return new this(group, exports.UIShape.polygon, states, id);
+    }
+    static fromUI(ui, states, id) {
+        return new this(ui.group, ui.shape, states || ui._states, id);
+    }
+    get id() { return this._id; }
+    set id(d) { this._id = d; }
+    get group() { return this._group; }
+    set group(d) { this._group = d; }
+    get shape() { return this._shape; }
+    set shape(d) { this._shape = d; }
+    state(key, value) {
+        if (!key)
+            return null;
+        if (value !== undefined) {
+            this._states[key] = value;
+            return this;
+        }
+        return this._states[key];
+    }
+    on(type, fn) {
+        if (!this._actions[type])
+            this._actions[type] = [];
+        return UI._addHandler(this._actions[type], fn);
+    }
+    off(type, which) {
+        if (!this._actions[type])
+            return false;
+        if (which === undefined) {
+            delete this._actions[type];
+            return true;
+        }
+        else {
+            return UI._removeHandler(this._actions[type], which);
+        }
+    }
+    listen(type, p, evt) {
+        if (this._actions[type] !== undefined) {
+            if (this._within(p) || Array.from(this._holds.values()).indexOf(type) >= 0) {
+                UI._trigger(this._actions[type], this, p, type, evt);
+                return true;
+            }
+            else if (this._actions['all']) {
+                UI._trigger(this._actions['all'], this, p, type, evt);
+                return true;
+            }
+        }
+        return false;
+    }
+    hold(type) {
+        let newKey = Math.max(0, ...Array.from(this._holds.keys())) + 1;
+        this._holds.set(newKey, type);
+        return newKey;
+    }
+    unhold(key) {
+        if (key !== undefined) {
+            this._holds.delete(key);
+        }
+        else {
+            this._holds.clear();
+        }
+    }
+    static track(uis, type, p, evt) {
+        for (let i = 0, len = uis.length; i < len; i++) {
+            uis[i].listen(type, p, evt);
+        }
+    }
+    render(fn) {
+        fn(this._group, this._states);
+    }
+    toString() {
+        return `UI ${this.group.toString}`;
+    }
+    _within(p) {
+        let fn = null;
+        if (this._shape === exports.UIShape.rectangle) {
+            fn = Op_1.Rectangle.withinBound;
+        }
+        else if (this._shape === exports.UIShape.circle) {
+            fn = Op_1.Circle.withinBound;
+        }
+        else if (this._shape === exports.UIShape.polygon) {
+            fn = Op_1.Polygon.hasIntersectPoint;
+        }
+        else {
+            return false;
+        }
+        return fn(this._group, p);
+    }
+    static _trigger(fns, target, pt, type, evt) {
+        if (fns) {
+            for (let i = 0, len = fns.length; i < len; i++) {
+                if (fns[i])
+                    fns[i](target, pt, type, evt);
+            }
+        }
+    }
+    static _addHandler(fns, fn) {
+        if (fn) {
+            fns.push(fn);
+            return fns.length - 1;
+        }
+        else {
+            return -1;
+        }
+    }
+    static _removeHandler(fns, index) {
+        if (index >= 0 && index < fns.length) {
+            let temp = fns.length;
+            fns.splice(index, 1);
+            return (temp > fns.length);
+        }
+        else {
+            return false;
+        }
+    }
+}
+exports.UI = UI;
+UI._counter = 0;
+class UIButton extends UI {
+    constructor(group, shape, states = {}, id) {
+        super(group, shape, states, id);
+        this._hoverID = -1;
+        if (states.hover === undefined)
+            this._states['hover'] = false;
+        if (states.clicks === undefined)
+            this._states['clicks'] = 0;
+        const UA = exports.UIPointerActions;
+        this.on(UA.up, (target, pt, type, evt) => {
+            this.state('clicks', this._states.clicks + 1);
+        });
+        this.on(UA.move, (target, pt, type, evt) => {
+            let hover = this._within(pt);
+            if (hover && !this._states.hover) {
+                this.state('hover', true);
+                UI._trigger(this._actions[UA.enter], this, pt, UA.enter, evt);
+                var _capID = this.hold(UA.move);
+                this._hoverID = this.on(UA.move, (t, p) => {
+                    if (!this._within(p) && !this.state('dragging')) {
+                        this.state('hover', false);
+                        UI._trigger(this._actions[UA.leave], this, pt, UA.leave, evt);
+                        this.off(UA.move, this._hoverID);
+                        this.unhold(_capID);
+                    }
+                });
+            }
+        });
+    }
+    onClick(fn) {
+        return this.on(exports.UIPointerActions.up, fn);
+    }
+    offClick(id) {
+        return this.off(exports.UIPointerActions.up, id);
+    }
+    onContextMenu(fn) {
+        return this.on(exports.UIPointerActions.contextmenu, fn);
+    }
+    offContextMenu(id) {
+        return this.off(exports.UIPointerActions.contextmenu, id);
+    }
+    onHover(enter, leave) {
+        var ids = [undefined, undefined];
+        if (enter)
+            ids[0] = this.on(exports.UIPointerActions.enter, enter);
+        if (leave)
+            ids[1] = this.on(exports.UIPointerActions.leave, leave);
+        return ids;
+    }
+    offHover(enterID, leaveID) {
+        var s = [false, false];
+        if (enterID === undefined || enterID >= 0)
+            s[0] = this.off(exports.UIPointerActions.enter, enterID);
+        if (leaveID === undefined || leaveID >= 0)
+            s[1] = this.off(exports.UIPointerActions.leave, leaveID);
+        return s;
+    }
+}
+exports.UIButton = UIButton;
+class UIDragger extends UIButton {
+    constructor(group, shape, states = {}, id) {
+        super(group, shape, states, id);
+        this._draggingID = -1;
+        this._moveHoldID = -1;
+        this._dropHoldID = -1;
+        this._upHoldID = -1;
+        if (states.dragging === undefined)
+            this._states['dragging'] = false;
+        if (states.moved === undefined)
+            this._states['moved'] = false;
+        if (states.offset === undefined)
+            this._states['offset'] = new Pt_1.Pt();
+        const UA = exports.UIPointerActions;
+        this.on(UA.down, (target, pt, type, evt) => {
+            if (this._moveHoldID === -1) {
+                this.state('dragging', true);
+                this.state('offset', new Pt_1.Pt(pt).subtract(target.group[0]));
+                this._moveHoldID = this.hold(UA.move);
+            }
+            if (this._dropHoldID === -1) {
+                this._dropHoldID = this.hold(UA.drop);
+            }
+            if (this._upHoldID === -1) {
+                this._upHoldID = this.hold(UA.up);
+            }
+            if (this._draggingID === -1) {
+                this._draggingID = this.on(UA.move, (t, p) => {
+                    if (this.state('dragging')) {
+                        UI._trigger(this._actions[UA.uidrag], t, p, UA.uidrag, evt);
+                        this.state('moved', true);
+                    }
+                });
+            }
+        });
+        const endDrag = (target, pt, type, evt) => {
+            this.state('dragging', false);
+            this.off(UA.move, this._draggingID);
+            this._draggingID = -1;
+            this.unhold(this._moveHoldID);
+            this._moveHoldID = -1;
+            this.unhold(this._dropHoldID);
+            this._dropHoldID = -1;
+            this.unhold(this._upHoldID);
+            this._upHoldID = -1;
+            if (this.state('moved')) {
+                UI._trigger(this._actions[UA.uidrop], target, pt, UA.uidrop, evt);
+                this.state('moved', false);
+            }
+        };
+        this.on(UA.drop, endDrag);
+        this.on(UA.up, endDrag);
+        this.on(UA.out, endDrag);
+    }
+    onDrag(fn) {
+        return this.on(exports.UIPointerActions.uidrag, fn);
+    }
+    offDrag(id) {
+        return this.off(exports.UIPointerActions.uidrag, id);
+    }
+    onDrop(fn) {
+        return this.on(exports.UIPointerActions.uidrop, fn);
+    }
+    offDrop(id) {
+        return this.off(exports.UIPointerActions.uidrop, id);
+    }
+}
+exports.UIDragger = UIDragger;
+
+
+/***/ }),
+
+/***/ "./src/Util.ts":
+/*!*********************!*\
+  !*** ./src/Util.ts ***!
+  \*********************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+/*! Source code licensed under Apache License 2.0. Copyright © 2017-current William Ngan and contributors. (https://github.com/williamngan/pts) */
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.Util = exports.Const = void 0;
+const Pt_1 = __webpack_require__(/*! ./Pt */ "./src/Pt.ts");
+exports.Const = {
+    xy: "xy",
+    yz: "yz",
+    xz: "xz",
+    xyz: "xyz",
+    horizontal: 0,
+    vertical: 1,
+    identical: 0,
+    right: 4,
+    bottom_right: 5,
+    bottom: 6,
+    bottom_left: 7,
+    left: 8,
+    top_left: 1,
+    top: 2,
+    top_right: 3,
+    epsilon: 0.0001,
+    max: Number.MAX_VALUE,
+    min: Number.MIN_VALUE,
+    pi: Math.PI,
+    two_pi: 6.283185307179586,
+    half_pi: 1.5707963267948966,
+    quarter_pi: 0.7853981633974483,
+    one_degree: 0.017453292519943295,
+    rad_to_deg: 57.29577951308232,
+    deg_to_rad: 0.017453292519943295,
+    gravity: 9.81,
+    newton: 0.10197,
+    gaussian: 0.3989422804014327
+};
+class Util {
+    static warnLevel(lv) {
+        if (lv) {
+            Util._warnLevel = lv;
+        }
+        return Util._warnLevel;
+    }
+    static getArgs(args) {
+        if (args.length < 1)
+            return [];
+        let pos = [];
+        let isArray = Array.isArray(args[0]) || ArrayBuffer.isView(args[0]);
+        if (typeof args[0] === 'number') {
+            pos = Array.prototype.slice.call(args);
+        }
+        else if (typeof args[0] === 'object' && !isArray) {
+            let a = ["x", "y", "z", "w"];
+            let p = args[0];
+            for (let i = 0; i < a.length; i++) {
+                if ((p.length && i >= p.length) || !(a[i] in p))
+                    break;
+                pos.push(p[a[i]]);
+            }
+        }
+        else if (isArray) {
+            pos = [].slice.call(args[0]);
+        }
+        return pos;
+    }
+    static warn(message = "error", defaultReturn = undefined) {
+        if (Util.warnLevel() == "error") {
+            throw new Error(message);
+        }
+        else if (Util.warnLevel() == "warn") {
+            console.warn(message);
+        }
+        return defaultReturn;
+    }
+    static randomInt(range, start = 0) {
+        Util.warn("Util.randomInt is deprecated. Please use `Num.randomRange`");
+        return Math.floor(Math.random() * range) + start;
+    }
+    static split(pts, size, stride, loopBack = false, matchSize = true) {
+        let chunks = [];
+        let part = [];
+        let st = stride || size;
+        let index = 0;
+        if (pts.length <= 0 || st <= 0)
+            return [];
+        while (index < pts.length) {
+            part = [];
+            for (let k = 0; k < size; k++) {
+                if (loopBack) {
+                    part.push(pts[(index + k) % pts.length]);
+                }
+                else {
+                    if (index + k >= pts.length)
+                        break;
+                    part.push(pts[index + k]);
+                }
+            }
+            index += st;
+            if (!matchSize || (matchSize && part.length === size))
+                chunks.push(part);
+        }
+        return chunks;
+    }
+    static flatten(pts, flattenAsGroup = true) {
+        let arr = (flattenAsGroup) ? new Pt_1.Group() : new Array();
+        return arr.concat.apply(arr, pts);
+    }
+    static combine(a, b, op) {
+        let result = [];
+        for (let i = 0, len = a.length; i < len; i++) {
+            for (let k = 0, lenB = b.length; k < lenB; k++) {
+                result.push(op(a[i], b[k]));
+            }
+        }
+        return result;
+    }
+    static zip(arrays) {
+        let z = [];
+        for (let i = 0, len = arrays[0].length; i < len; i++) {
+            let p = [];
+            for (let k = 0; k < arrays.length; k++) {
+                p.push(arrays[k][i]);
+            }
+            z.push(p);
+        }
+        return z;
+    }
+    static stepper(max, min = 0, stride = 1, callback) {
+        let c = min;
+        return function () {
+            c += stride;
+            if (c >= max) {
+                c = min + (c - max);
+            }
+            if (callback)
+                callback(c);
+            return c;
+        };
+    }
+    static forRange(fn, range, start = 0, step = 1) {
+        let temp = [];
+        for (let i = start, len = range; i < len; i += step) {
+            temp[i] = fn(i);
+        }
+        return temp;
+    }
+    static load(url, callback) {
+        var request = new XMLHttpRequest();
+        request.open('GET', url, true);
+        request.onload = function () {
+            if (request.status >= 200 && request.status < 400) {
+                callback(request.responseText, true);
+            }
+            else {
+                callback(`Server error (${request.status}) when loading "${url}"`, false);
+            }
+        };
+        request.onerror = function () {
+            callback(`Unknown network error`, false);
+        };
+        request.send();
+    }
+    static performance(avgFrames = 10) {
+        let last = Date.now();
+        let avg = [];
+        return function () {
+            const now = Date.now();
+            avg.push(now - last);
+            if (avg.length >= avgFrames)
+                avg.shift();
+            last = now;
+            return Math.floor(avg.reduce((a, b) => a + b, 0) / avg.length);
+        };
+    }
+    static arrayCheck(pts, minRequired = 2) {
+        if (Array.isArray(pts) && pts.length < minRequired) {
+            Util.warn(`Requires ${minRequired} or more Pts in this Group.`);
+            return false;
+        }
+        return true;
+    }
+    static iterToArray(it) {
+        return (!Array.isArray(it)) ? [...it] : it;
+    }
+    static isMobile() {
+        return /iPhone|iPad|Android/i.test(navigator.userAgent);
+    }
+}
+exports.Util = Util;
+Util._warnLevel = "mute";
+
+
+/***/ }),
+
+/***/ "./src/_lib.ts":
+/*!*********************!*\
+  !*** ./src/_lib.ts ***!
+  \*********************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+    if (k2 === undefined) k2 = k;
+    o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.quickStart = exports.namespace = void 0;
+__exportStar(__webpack_require__(/*! ./Canvas */ "./src/Canvas.ts"), exports);
+__exportStar(__webpack_require__(/*! ./Create */ "./src/Create.ts"), exports);
+__exportStar(__webpack_require__(/*! ./Form */ "./src/Form.ts"), exports);
+__exportStar(__webpack_require__(/*! ./LinearAlgebra */ "./src/LinearAlgebra.ts"), exports);
+__exportStar(__webpack_require__(/*! ./Num */ "./src/Num.ts"), exports);
+__exportStar(__webpack_require__(/*! ./Op */ "./src/Op.ts"), exports);
+__exportStar(__webpack_require__(/*! ./Pt */ "./src/Pt.ts"), exports);
+__exportStar(__webpack_require__(/*! ./Space */ "./src/Space.ts"), exports);
+__exportStar(__webpack_require__(/*! ./Color */ "./src/Color.ts"), exports);
+__exportStar(__webpack_require__(/*! ./Util */ "./src/Util.ts"), exports);
+__exportStar(__webpack_require__(/*! ./Dom */ "./src/Dom.ts"), exports);
+__exportStar(__webpack_require__(/*! ./Svg */ "./src/Svg.ts"), exports);
+__exportStar(__webpack_require__(/*! ./Typography */ "./src/Typography.ts"), exports);
+__exportStar(__webpack_require__(/*! ./Physics */ "./src/Physics.ts"), exports);
+__exportStar(__webpack_require__(/*! ./UI */ "./src/UI.ts"), exports);
+__exportStar(__webpack_require__(/*! ./Play */ "./src/Play.ts"), exports);
+__exportStar(__webpack_require__(/*! ./Image */ "./src/Image.ts"), exports);
+__exportStar(__webpack_require__(/*! ./Types */ "./src/Types.ts"), exports);
+const _Canvas = __webpack_require__(/*! ./Canvas */ "./src/Canvas.ts");
+let namespace = (scope) => {
+    let lib = module.exports;
+    for (let k in lib) {
+        if (k != "namespace") {
+            scope[k] = lib[k];
+        }
+    }
+};
+exports.namespace = namespace;
+let quickStart = (id, bg = "#9ab") => {
+    if (!window)
+        return;
+    let s = window;
+    exports.namespace(s);
+    s.space = new _Canvas.CanvasSpace(id).setup({ bgcolor: bg, resize: true, retina: true });
+    s.form = s.space.getForm();
+    return function (animate = null, start = null, action = null, resize = null) {
+        s.space.add({
+            start: start,
+            animate: animate,
+            resize: resize,
+            action: action,
+        });
+        s.space.bindMouse().bindTouch().play();
+    };
+};
+exports.quickStart = quickStart;
+
+
+/***/ })
+
+/******/ });
+});
+//# sourceMappingURL=pts.js.map

File diff ditekan karena terlalu besar
+ 5 - 0
public/drivers/view/ptsjs/pts.min.js


+ 576 - 156
public/drivers/view/tone.js

@@ -1,170 +1,590 @@
 /*
 The MIT License (MIT)
 Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+This driver includes the port and some code parts from the "Croquet synced video demo" for implementing Player elements syncing within LiveCoding.space applications and LCS Reflector / Luminary.
+
+Croquet synced video demo License 
+Copyright 2020 Croquet Corporation
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
 */
+
 // VWF & Tone driver
 
-import {Fabric} from '/core/vwf/fabric.js';
+
+import { Fabric } from '/core/vwf/fabric.js';
 //import * as Tone from '/drivers/view/tonejs/dist/Tone.js';
 
 class ToneViewDriver extends Fabric {
 
-  constructor(module) {
-    console.log("ToneViewDriver constructor");
-    super(module, 'View');
-  }
-
-  factory() {
-
-    let _self_ = this;
-
-	return this.load(this.module, 
-		{
-
-			initialize: function() {
-				
-				let self = this;
-
-				this.fabric = _self_;
-
-				this.nodes = {};
-
-				// document.querySelector("button").addEventListener("click", async () => {
-				// 	await Tone.start();
-				// 	console.log("context started");
-				// });
-
-
-				//window._Tone = Tone.default;
-
-				// this.osc = osc;
-				// this.portValue = '8081';
-				// this.hostValue = 'localhost';
-				// this.port = null;
-	
-				//window._osc = this.osc;
-				// window._OSCManager = this;
-	
-			},
-
-			createdNode: function (nodeID, childID, childExtendsID, childImplementsIDs,
-                childSource, childType, childIndex, childName, callback /* ( ready ) */) {
-                let self = this;
-                var node = this.state.nodes[childID];
-    
-                // If the "nodes" object does not have this object in it, it must not be one that
-                // this driver cares about
-                if (!node) {
-                    return;
-				}
-		
-				this.nodes[childID] = {
-					id: childID,
-					extends: childExtendsID,
-					parent: nodeID,
-					toneObj: node.toneObj
-				};
-				
-				//parent: this.state.nodes[childID].aframeObj
-
-			},
-	
-			firedEvent: function (nodeID, eventName, eventParameters) {
-			 
-				let self = this;
-					// if (eventName == 'sendOSC'){
-	
-					// var clientThatSatProperty = self.kernel.client();
-					// var me = self.kernel.moniker();
-	
-	
-					// // If the transform property was initially updated by this view....
-					// if (clientThatSatProperty == me) {
-	
-	
-					// 	if (self.osc !== null) {
-					// 		if (self.getStatus() == 1) {
-					// 			self.port.send(eventParameters[0]);
-					// 			console.log('send: ' + eventParameters[0]);
-					// 		}
-					// 	}
-					// }
-	
-	
-	
-					// }
-				
-			},
-	
-	
-			/*
-			 * Receives incoming messages
-			 */
-			calledMethod: function( nodeID, methodName, methodParameters, methodValue ) {
-		
-				let self = this;
-
-				let node = this.state.nodes[nodeID];
-    
-                // If the "nodes" object does not have this object in it, it must not be one that
-                // this driver cares about
-                if (!node) {
-                    return;
-				}
-
-				if (methodName == "triggerAttackRelease") {
-					
-					if(node.toneObj){
-						const now = Tone.now()
-						let notes = methodParameters[0];
-						// let notes = methodParameters[0].map(el=>{
-						// 	return Tone.Frequency(el).toNote();
-						// }) 
-
-						if (self.state.isMembraneSynthDefinition(node.prototypes)) {
-							node.toneObj.triggerAttackRelease(notes[0], methodParameters[1][0], now);
-						} else if(self.state.isNoiseSynthDefinition(node.prototypes)) {
-							node.toneObj.triggerAttackRelease("16n", now)
-						} 
-						else {
-							node.toneObj.triggerAttackRelease(notes, methodParameters[1], now, methodParameters[2])
-						}
-
-						
-					}
-					
-				}
-
-				if (methodName == "triggerAttack") {
-					
-					if(node.toneObj){
-						const now = Tone.now()
-						node.toneObj.triggerAttack(methodParameters[0], now, methodParameters[1])
-					}
-					
-				}
-
-				if (methodName == "triggerRelease") {
-					
-					if(node.toneObj){
-						node.toneObj.triggerRelease(methodParameters[0], "+0.1")
-					}
-					
-				}
-
-	
-				
-			},
-	
-
-
-
-	
-			
-		});
-	
-	}
+    constructor(module) {
+        console.log("ToneViewDriver constructor");
+        super(module, 'View');
+    }
+
+    factory() {
+
+        let _self_ = this;
+
+        return this.load(this.module,
+            {
+
+                initialize: function () {
+
+                    let self = this;
+
+                    this.fabric = _self_;
+
+                    this.nodes = {};
+
+                    this.toneStarted = false;
+
+                    function toneStart() {
+                        if (!self.toneStarted) {
+                            Tone.start().then(r => {
+
+                                let toneTransport = Object.values(self.state.nodes).filter(el => el.extendsID == "proxy/tonejs/transport.vwf")[0];
+                                if (toneTransport) {
+                                    _self_.applyPlayState(toneTransport.ID);
+                                }
+                                console.log("context started");
+                                self.toneStarted = true;
+                            });
+
+                            //document.body.removeEventListener("click", toneStart, false);
+                        }
+                    }
+
+                    document.body.addEventListener("click", toneStart, false);
+                    //window._Tone = Tone.default;
+                },
+
+                createdNode: function (nodeID, childID, childExtendsID, childImplementsIDs,
+                    childSource, childType, childIndex, childName, callback /* ( ready ) */) {
+                    let self = this;
+                    var node = this.state.nodes[childID];
+
+                    // If the "nodes" object does not have this object in it, it must not be one that
+                    // this driver cares about
+                    if (!node) {
+                        return;
+                    }
+
+                    this.nodes[childID] = {
+                        id: childID,
+                        extends: childExtendsID,
+                        parent: nodeID,
+                        toneObj: node.toneObj
+
+                    };
+
+                    if (this.nodes[childID].extends == "proxy/tonejs/transport.vwf") {
+
+                        this.nodes[childID].playbackBoost = 0;
+                        //_self_.applyPlayState(nodeId);    
+                        this.nodes[childID].lastTimingCheck = vwf.time() * 1000;
+
+                    }
+
+
+                },
+
+                firedEvent: function (nodeID, eventName, eventParameters) {
+
+                    //let self = this;
+                },
+
+                initializedProperty: function (nodeId, propertyName, propertyValue) {
+                    return this.satProperty(nodeId, propertyName, propertyValue);
+                },
+
+                satProperty: function (nodeId, propertyName, propertyValue) {
+                    let self = this;
+
+                    var node = this.state.nodes[nodeId];
+                    const viewNode = this.nodes[nodeId];
+
+                    if (!(node && node.toneObj)) {
+                        return;
+                    }
+
+                    // if(propertyName == "state"){
+                    // 	//let toneState = node.toneObj.state;
+                    // 	if(propertyValue == "started"){
+                    // 		node.toneObj.start()
+                    // 	} else if (!propertyValue || propertyValue== "stopped"){
+                    // 		node.toneObj.stop()
+                    // 	} else if (propertyValue == "paused"){
+                    // 		node.toneObj.pause()
+                    // 	}
+                    // }
+
+                    if (viewNode.extends == "proxy/tonejs/player.vwf") {
+
+                        if (propertyName == "url") {
+
+                            node.toneObj.load(propertyValue).then(r => { //buffer.load for GrainPLayer
+                                console.log('LOADED: ', node);
+                                if (node.toneObj.startTime && node.toneObj.state == "stopped") {
+                                    node.toneObj.sync().start(node.toneObj.startTime);
+                                }
+                            })
+                        }
+
+                        if (propertyName == "startTime") {
+                            if (node.toneObj.state == "stopped") {
+                                if (node.toneObj.loaded == true)
+                                    node.toneObj.sync().start(propertyValue);
+                            }
+                        }
+
+                    }
+
+                    if (propertyName == "startOffset" || propertyName == "pausedTime" || propertyName == "isPlaying") {
+
+                        if (!viewNode.latestPlayState) {
+                            viewNode.latestPlayState = {
+                                "startOffset": null,
+                                "pausedTime": null,
+                                "isPlaying": false
+                            }
+                        }
+
+                        viewNode.latestPlayState[propertyName] = propertyValue;
+
+                        if (propertyName == "isPlaying") {
+                            viewNode.isPlaying = propertyValue;
+                            _self_.applyPlayState(nodeId);
+                        }
+                    }
+
+                },
+
+                /*
+                 * Receives incoming messages
+                 */
+                calledMethod: function (nodeID, methodName, methodParameters, methodValue) {
+
+                    let self = this;
+
+                    let node = this.state.nodes[nodeID];
+                    const viewNode = this.nodes[nodeID];
+
+                    // If the "nodes" object does not have this object in it, it must not be one that
+                    // this driver cares about
+                    if (!node) {
+                        return;
+                    }
+
+
+                    if (methodName == "syncTransportState") {
+                        _self_.applyPlayState(nodeID);
+                    }
+
+                    if (methodName == "setTransportState") {
+
+                        if (!viewNode.latestPlayState)
+                            viewNode.latestPlayState = {}
+
+                        // "isPlaying",
+                        // "startOffset",
+                        // "pausedTime"
+                        viewNode.latestPlayState["isPlaying"] = methodParameters[0];
+                        viewNode.latestPlayState["startOffset"] = methodParameters[1];
+                        viewNode.latestPlayState["pausedTime"] = methodParameters[2];
+
+                        _self_.applyPlayState(nodeID);
+
+                    }
+
+                    if (methodName == "toggleTransport") {
+
+                        const obj = node.toneObj;
+
+                        const wantsToPlay = !viewNode.latestPlayState.isPlaying; // toggle
+                        //viewNode.isPlaying = wantsToPlay;
+
+                        if (!wantsToPlay) {
+                            viewNode.isPlaying = false;
+                            _self_.pause(undefined, obj);
+                        } // immediately!
+
+
+                        const objTime = obj.seconds; //obj.position;
+                        const sessionTime = vwf.time() * 1000; // the session time corresponding to the video time
+                        const startOffset = wantsToPlay ? sessionTime - 1000 * objTime : null;
+                        const pausedTime = wantsToPlay ? 0 : objTime;
+
+
+                        vwf_view.kernel.callMethod(nodeID, "setTransportState", [wantsToPlay, startOffset, pausedTime]);
+
+                    }
+
+                    if (self.state.isPlayerDefinition(node.prototypes)) {
+
+                        if (methodName == "syncStart") {
+                            if (node.toneObj.state == "stopped") {
+                                if (methodParameters[0] == "now") {
+                                    node.toneObj.sync().start(Tone.Transport.seconds);
+                                } else {
+                                    node.toneObj.sync().start(methodParameters[0]);
+                                }
+                            }
+
+                        }
+
+                        if (methodName == "start") {
+
+                            node.toneObj.start();
+                        }
+
+                        if (methodName == "stop") {
+
+                            if (node.toneObj.state == "started")
+                                node.toneObj.stop();
+                        }
+
+                        if (methodName == "syncStop") {
+
+                            if (node.toneObj.state == "started")
+                                node.toneObj.sync().stop();
+                        }
+
+                        // if (methodName == "pause") {
+
+                        // 	node.toneObj.pause();
+                        // }
+
+                    }
+
+                    if (self.state.isTransportDefinition(node.prototypes)) {
+
+                        if (methodName == "start") {
+
+                            node.toneObj.start();
+                        }
+
+                        if (methodName == "stop") {
+
+                            if (node.toneObj.state == "started")
+                                node.toneObj.stop();
+                        }
+                        if (methodName == "pause") {
+
+                            node.toneObj.pause();
+                        }
+
+                    }
+
+                    if (methodName == "sync") {
+                        if (node.toneObj) {
+                            node.toneObj.sync();
+                        }
+                    }
+
+                    if (methodName == "scheduleRepeat") {
+                        Tone.Transport.scheduleRepeat((time) => {
+                            // use the callback time to schedule events
+                            //node.toneObj.start(time).stop(time + 0.2);
+                            node.toneObj.triggerAttackRelease("C4", "32n", time);
+                        }, "8n");
+                    }
+
+
+                    if (methodName == "triggerAttackRelease") {
+
+                        if (node.toneObj) {
+                            const now = methodParameters[2] ? methodParameters[2] :
+                                (node.toneObj._synced ? Tone.Transport.seconds : Tone.now());
+
+                            let notes = methodParameters[0];
+                            // let notes = methodParameters[0].map(el=>{
+                            // 	return Tone.Frequency(el).toNote();
+                            // }) 
+
+                            if (self.state.isMembraneSynthDefinition(node.prototypes)) {
+                                node.toneObj.triggerAttackRelease(notes[0], methodParameters[1][0], now);
+                            } else if (self.state.isNoiseSynthDefinition(node.prototypes)) {
+                                node.toneObj.triggerAttackRelease("16n", now)
+                            }
+                            else {
+                                node.toneObj.triggerAttackRelease(notes, methodParameters[1], now, methodParameters[3])
+                            }
+
+
+                        }
+
+                    }
+
+                    if (methodName == "triggerAttack") {
+
+                        if (node.toneObj) {
+                            const now = Tone.now()
+                            node.toneObj.triggerAttack(methodParameters[0], now, methodParameters[1])
+                        }
+
+                    }
+
+                    if (methodName == "triggerRelease") {
+
+                        if (node.toneObj) {
+                            node.toneObj.triggerRelease(methodParameters[0], "+0.1")
+                        }
+
+                    }
+
+
+
+                }
+
+            });
+
+    }
+
+
+    checkPlayStatusForTransportNode(nodeID) {
+
+        const now = vwf.time() * 1000;
+        let self = this.instance;
+        let viewNode = self.nodes[nodeID];
+        let node = self.state.nodes[nodeID];
+
+        // 	let playerNodes = Object.values(self.state.nodes).filter(el=>(el.extendsID == "proxy/tonejs/player.vwf"));
+        // 	let notloaded = playerNodes.filter(el=>(el.toneObj.loaded == false));
+
+        // 	if(notloaded.length > 0) return
+
+        //   let syncedPlayers =  playerNodes.filter(el=>(el.toneObj._synced == true));
+
+        let video = node.toneObj;
+        let duration = video.duration; //(video.loopEnd - video.loopStart); // //
+        let currentTime = video.seconds;
+
+        //if (this.videoView) {
+        // this.adjustPlaybar();
+
+        const lastTimingCheck = viewNode.lastTimingCheck || 0;
+
+        // check video timing every 0.5s
+        if (viewNode.isPlaying && (now - lastTimingCheck >= 500)) {
+            viewNode.lastTimingCheck = now;
+            const expectedTime = this.wrappedTime(this.calculateVideoTime(nodeID), false, duration);
+            //const videoTime = video.seconds;
+            const videoDiff = currentTime - expectedTime;
+            const videoDiffMS = videoDiff * 1000; // +ve means *ahead* of where it should be
+            if (videoDiff < duration / 2) { // otherwise presumably measured across a loop restart; just ignore.
+                if (viewNode.jumpIfNeeded) { //this.jumpIfNeeded
+                    viewNode.jumpIfNeeded = false;
+                    // if there's a difference greater than 500ms, try to jump the video to the right place
+                    if (Math.abs(videoDiffMS) > 500) {
+                        console.log(`jumping video by ${-Math.round(videoDiffMS)}ms`);
+                        video.pause();
+                        video.seconds = this.wrappedTime(currentTime - videoDiff, true, duration); //+ 0.1
+                        video.start();
+
+                        // 0.1 to counteract the delay that the jump itself tends to introduce; true to ensure we're not jumping beyond the last video frame
+                    }
+                } else {
+                    // every 3s, check video lag/advance, and set the playback rate accordingly.
+                    // current adjustment settings:
+                    //   > 150ms off: set playback 3% faster/slower than normal
+                    //   > 50ms: 1% faster/slower
+                    //   < 25ms: normal (i.e., hysteresis between 50ms and 25ms in the same sense)
+                    //////
+                    // const lastRateAdjust = viewNode.lastRateAdjust || 0;
+                    // if (now - lastRateAdjust >= 3000) {
+                    //     //console.log(`${Math.round(videoDiff*1000)}ms`);
+                    //     const oldBoostPercent = viewNode.playbackBoost;
+                    //     const diffAbs = Math.abs(videoDiffMS), diffSign = Math.sign(videoDiffMS);
+                    //     const desiredBoostPercent = -diffSign * (diffAbs > 150 ? 3 : (diffAbs > 50 ? 1 : 0));
+                    //     if (desiredBoostPercent !== oldBoostPercent) {
+                    //         // apply hysteresis on the switch to boost=0.
+                    //         // for example, if old boost was +ve (because video was lagging),
+                    //         // and videoDiff is -ve (i.e., it's still lagging),
+                    //         // and the magnitude (of the lag) is greater than 25ms,
+                    //         // don't remove the boost yet.
+                    //         const hysteresisBlock = desiredBoostPercent === 0 && Math.sign(oldBoostPercent) === -diffSign && diffAbs >= 25;
+                    //         if (!hysteresisBlock) {
+                    //             viewNode.playbackBoost = desiredBoostPercent;
+                    //             const playbackRate = 1 + viewNode.playbackBoost * 0.01;
+                    //             console.log(`video playback rate: ${playbackRate}`);
+
+                    //             //video.bpm.value = playbackRate*video.initbpm;
+
+                    //             //   video.bpm.rampTo(playbackRate*video.initbpm, 0.1);
+                    //             // if(syncedPlayers.length > 0){
+                    //             //     syncedPlayers.map(el=>{
+                    //             //         el.toneObj.playbackRate = playbackRate;
+                    //             //         console.log("change playbackRate for ", el.ID, playbackRate);
+                    //             //     })
+                    //             // }
+
+
+                    //             //player.seek(progress * this.player.buffer.duration)
+
+                    //             //video.playbackRate = playbackRate;
+
+                    //         }
+                    //     }
+                    //     viewNode.lastRateAdjust = now;
+                    // }
+                }
+            }
+        }
+        // }
+    }
+
+
+    wrappedTime(videoTime, guarded, duration) {
+        if (duration) {
+            while (videoTime > duration) videoTime -= duration; // assume it's looping, with no gap between plays
+            if (guarded) videoTime = Math.min(duration, videoTime); // the video element freaks out on being told to seek very close to the end //- 0.1
+        }
+        return videoTime;
+    }
+
+    calculateVideoTime(nodeID) {
+
+        let self = this.instance;
+        const node = self.state.nodes[nodeID];
+        const viewNode = self.nodes[nodeID];
+        //const video = node.obj.fill.image;
+
+        // const { isPlaying, startOffset } = this.latestPlayState;
+        //if (!isPlaying) debugger;
+
+        const sessionNow = vwf.time() * 1000;
+        let t = (sessionNow - viewNode.latestPlayState.startOffset) / 1000;
+        //console.log('Time: ', t)
+        return t;
+    }
+
+    pause(videoTime, video) {
+        //this.isPlaying = this.isBlocked = false; // might not be blocked next time.
+        this.setStatic(videoTime, video);
+    }
+
+    setStatic(videoTime, video) {
+        let duration = video.duration;// video.loopEnd - video.loopStart; //video.duration; //
+
+        if (videoTime !== undefined) {
+
+
+
+            if (video.state == "started") {
+                video.pause(); // no return value; synchronous, instantaneous?
+            }
+            video.seconds = this.wrappedTime(videoTime, true, duration); // true => guarded from values too near the end
+        }
+
+    }
+
+    async play(video, videoTime) {
+
+        let self = this.instance;
+        let playerNodes = Object.values(self.state.nodes).filter(el=>(el.extendsID == "proxy/tonejs/player.vwf"));
+        let notloaded = playerNodes.filter(el=>(el.toneObj.loaded == false));
+        if(notloaded.length > 0) return
+
+
+        // return true if video play started successfully
+        let duration = video.duration; //video.loopEnd - video.loopStart; //
+
+        //this.isPlaying = true; // even if it turns out to be blocked by the browser
+        // following guidelines from https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/play
+        // if(video.state == "stopped" || video.state == "paused") {
+        let position = this.wrappedTime(videoTime, true, duration);
+
+        if (video.state == "started") {
+            video.pause();
+            video.seconds = this.wrappedTime(videoTime, true, duration);
+            video.start();
+        }
+
+        if (video.state == "stopped" || video.state == "paused") {
+            video.start();
+        }
+
+
+        //video.toggle();
+
+        //}
+
+        // try {
+        //     await video.start(); // will throw exception if blocked
+        //     //this.isBlocked = false;
+        // } catch (err) {
+        //     console.warn("video play blocked");
+        //     // this.isBlocked = this.isPlaying; // just in case isPlaying was set false while we were trying
+        // }
+        return true //!this.isBlocked;
+    }
+
+    applyPlayState(nodeID) {
+        let self = this.instance;
+        const node = self.state.nodes[nodeID];
+        const viewNode = self.nodes[nodeID];
+        const video = node.toneObj;
+
+        if (viewNode.latestPlayState) {
+
+            if (!viewNode.latestPlayState.isPlaying) {
+                // this.iconVisible('play', true);
+                // this.iconVisible('enableSound', false);
+                this.pause(viewNode.latestPlayState.pausedTime, video);
+            } else {
+                //video.playbackRate = 1 + viewNode.playbackBoost * 0.01;
+
+                //let playerNodes = Object.values(self.state.nodes).filter(el=>(el.extendsID == "proxy/tonejs/player.vwf"));
+                //let notloaded = playerNodes.filter(el=>(el.toneObj.loaded == false));
+                //if(notloaded.length > 0) return
+
+                // let syncedPlayers =  playerNodes.filter(el=>(el.toneObj._synced == true));
+                // let playbackRate = 1 + viewNode.playbackBoost * 0.01;
+
+                //video.bpm.rampTo(playbackRate*video.initbpm, 0.1);
+                //video.bpm.value = playbackRate*video.initbpm;
+
+                //    video.bpm.rampTo(playbackRate*video.initbpm, 0.1);
+                // if(syncedPlayers.length > 0){
+                //     syncedPlayers.map(el=>{
+                //         el.toneObj.playbackRate = playbackRate;
+                //         console.log("change playbackRate for ", el.ID, playbackRate);
+                //     })
+                // }
+
+                viewNode.lastRateAdjust = vwf.time() * 1000; // make sure we don't adjust rate until playback has settled in, and after any emergency jump we decide to do
+                viewNode.jumpIfNeeded = false;
+                // if the video is blocked from playing, enter a stepping mode in which we move the video forward with successive pause() calls
+                viewNode.isPlaying = true;
+                this.play(video, this.calculateVideoTime(nodeID)).then(playStarted => { // + 0.1
+
+                    if (playStarted) {
+                        // setTimeout(function () {
+                        viewNode.jumpIfNeeded = true;
+                        //  }, 250);
+                    }
+
+                    //this.iconVisible('enableSound', !playStarted || videoElem.muted);
+                    //if (playStarted) this.future(250).triggerJumpCheck(); // leave it a little time to stabilise
+                })
+            }
+
+        }
 
+    }
 
 
 

File diff ditekan karena terlalu besar
+ 0 - 0
public/drivers/view/tonejs/Tone.js


File diff ditekan karena terlalu besar
+ 0 - 0
public/drivers/view/tonejs/Tone.js.map


+ 1065 - 0
public/drivers/view/two.js

@@ -0,0 +1,1065 @@
+/*
+The MIT License (MIT)
+Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
+
+This driver includes the port and some code parts from the "Croquet synced video demo" for implementing video elements syncing within LiveCoding.space applications and LCS Reflector / Luminary.
+
+Croquet synced video demo License 
+Copyright 2020 Croquet Corporation
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+*/
+
+// TWO JS view driver
+
+import { Fabric } from '/core/vwf/fabric.js';
+
+class TwoView extends Fabric {
+
+    constructor(module) {
+        console.log("TwoView constructor");
+        super(module, 'View');
+    }
+
+    factory() {
+
+        let _self_ = this;
+
+        return this.load(this.module,
+            {
+
+                // == Module Definition ====================================================================
+
+                initialize: function (options) {
+                    let self = this;
+                    this.fabric = _self_;
+                    this.nodes = {};
+                    this.overChilds = [];
+
+                    this.state.appInitialized = false;
+
+                    if (options === undefined) { options = {}; }
+
+                    if (typeof options == "object") {
+
+                        this.rootSelector = options["application-root"];
+                    }
+                    else {
+                        this.rootSelector = options;
+                    }
+
+                    this.lastStatusCheck = vwf.time() * 1000 + 500;
+                    this.clicked = false;
+                    this.isIOS = [
+                        'iPad Simulator',
+                        'iPhone Simulator',
+                        'iPod Simulator',
+                        'iPad',
+                        'iPhone',
+                        'iPod'
+                    ].includes(navigator.platform)
+                        // iPad on iOS 13 detection
+                        || (navigator.userAgent.includes("Mac") && "ontouchend" in document);
+
+                    function videostepping() {
+                        let twodriver = vwf.views["/drivers/view/two"];
+
+                        if (!twodriver.clicked)
+                            twodriver.clicked = true;
+
+                        let videos = Object.values(twodriver.state.nodes).filter(el => el.fillType == "video");
+                        videos.forEach(el => {
+                            let viewNode = twodriver.nodes[el.ID];
+                            if (viewNode.isStepping) {
+                                console.log(`exiting step mode`);
+                                el.muted = false;
+                                viewNode.isStepping = false;
+                                _self_.applyPlayState(el.ID);
+                                //document.body.removeEventListener("click", videostepping, false);
+                                //document.body.removeEventListener("touchstart", videostepping, false);
+                                //return;
+                            }
+                        })
+                    }
+
+                    document.body.addEventListener("click", videostepping, false);
+                    document.body.addEventListener("touchstart", videostepping, false);
+
+                },
+
+                createdNode: function (nodeID, childID, childExtendsID, childImplementsIDs,
+                    childSource, childType, childIndex, childName, callback /* ( ready ) */) {
+                    let self = this;
+                    var node = this.state.nodes[childID];
+
+                    // If the "nodes" object does not have this object in it, it must not be one that
+                    // this driver cares about
+                    if (!node) {
+                        return;
+                    }
+
+                    if (this.state.scenes[childID]) {
+                        let scene = this.state.scenes[childID];
+                        let space = scene.obj;
+
+                        _self_.resizeScene(childID);
+                        //
+                        space.bind('update', function (frameCount) {
+                            // This code is called everytime two.update() is called.
+                            // Effectively 60 times per second.
+                            _self_.update(frameCount);
+
+                        }).play();  // Finally, start the animation loop
+
+                        //TODO: FIX
+
+                        let avatarName = 'avatar-' + self.kernel.moniker();
+
+                        console.log("creating avatar...");
+                        var newNode = {
+                            "id": avatarName,
+                            "uri": avatarName,
+                            "extends": "proxy/two/player.vwf",
+                            "properties": {}
+                        }
+
+                        if (!self.state.nodes[avatarName]) {
+                            vwf_view.kernel.createChild(childID, avatarName, newNode);
+                            vwf_view.kernel.callMethod(avatarName, "createPlayerBody", []);
+                        }
+                    }
+
+
+
+                    if (this.state.nodes[childID] && this.state.nodes[childID].obj) {
+                        this.nodes[childID] = {
+                            id: childID,
+                            extends: childExtendsID,
+                            liveBindings: {}
+                            // lastTransformStep: 0,
+                            // lastAnimationStep: 0 
+                        };
+
+                        if (this.nodes[childID].extends == "proxy/two/scene.vwf") {
+
+                            this.nodes[childID].mouse = new Two.Vector();
+
+                            window.addEventListener('mousemove', function (e) {
+                                e.preventDefault();
+                                let scene = self.state.nodes[childID].obj.renderer.scene;
+                                let x = e.offsetX / scene.scale;
+                                let y = e.offsetY / scene.scale;
+                                self.nodes[childID].mouse.set(x, y);
+                                //vwf_view.kernel.callMethod(el.nodeID, "mousedownEvent", []);  
+                            });
+
+                            window.addEventListener('touchstart', function (e) {
+                                //e.preventDefault();
+                                let touch = e.changedTouches[0];
+                                let scene = self.state.nodes[childID].obj.renderer.scene;
+
+                                const { x, y, width, height } = e.target.getBoundingClientRect();
+
+                                self.nodes[childID].mouse.set(touch.pageX / scene.scale, touch.pageY / scene.scale);
+                                _self_.updateAvatarPosition();
+                                _self_.mouseDown(node, touch.pageX, touch.pageY);
+
+                            }, { passive: false });
+
+                            window.addEventListener('touchend', function (e) {
+
+                                //e.preventDefault();
+
+                                let touch = e.changedTouches[0];
+                                //let scene = self.state.nodes[childID].obj.renderer.scene;
+                                let x = touch.pageX;
+                                let y = touch.pageY;
+                                // _self_.updateAvatarPosition();
+                                _self_.mouseUp(node, x, y);
+                                //return false;
+                            }, { passive: false });
+
+                            window.addEventListener('touchmove', function (e) {
+
+                                e.preventDefault();
+                                let touch = e.changedTouches[0];
+                                let scene = self.state.nodes[childID].obj.renderer.scene;
+                                let x = touch.pageX / scene.scale;
+                                let y = touch.pageY / scene.scale;
+                                self.nodes[childID].mouse.set(x, y);
+
+                            }, { passive: false });
+
+                            //resize event
+                            window.addEventListener("resize", function (event) {
+                                _self_.resizeScene(childID);
+                            });
+
+                            //node.scene.obj.update();
+                            node.obj.renderer.domElement.addEventListener('mousedown', function (e) {
+                                var x = e.clientX;
+                                var y = e.clientY;
+                                //let nodes = self.state.nodes;
+                                _self_.mouseDown(node, x, y);
+                            }, false);
+
+                            node.obj.renderer.domElement.addEventListener('mouseup', function (e) {
+                                var x = e.clientX;
+                                var y = e.clientY;
+                                // let nodes = self.state.nodes;
+                                _self_.mouseUp(node, x, y);
+
+                            }, false);
+
+
+                        }
+
+                        // IF RENDERER SVG
+                        // if(node.prototypes.includes("proxy/two/path.vwf")) {
+                        // let elm = node.obj;
+
+                        // node.scene.obj.update();
+                        // elm._renderer.elem.addEventListener('click', function() {
+                        //     vwf_view.kernel.callMethod(childID, "svgClickEvent", []);
+                        //   },false);
+
+                        // }
+
+                    }
+                },
+
+                executed: function (nodeID, scriptText, scriptType) {
+                    let self = this;
+                    let node = this.state.nodes[nodeID];
+
+                    if (!(node)) {
+                        return;
+                    }
+
+
+                },
+
+
+                initializedNode: function (nodeID, childID) {
+                    let self = this;
+                    var node = this.state.nodes[childID];
+                    if (!node) {
+                        return;
+                    }
+
+
+                },
+
+                createdProperty: function (nodeId, propertyName, propertyValue) {
+                    return this.satProperty(nodeId, propertyName, propertyValue);
+                },
+
+                initializedProperty: function (nodeId, propertyName, propertyValue) {
+                    return this.satProperty(nodeId, propertyName, propertyValue);
+                },
+
+                gotProperty: function (nodeId, propertyName, propertyValue) {
+
+                    var node = this.state.nodes[nodeId];
+
+                    if (!(node && node.aframeObj)) {
+                        return;
+                    }
+
+                },
+
+                satProperty: function (nodeId, propertyName, propertyValue) {
+                    let self = this;
+
+                    var node = this.state.nodes[nodeId];
+                    const viewNode = this.nodes[nodeId];
+
+                    if (!(node && node.obj)) {
+                        return;
+                    }
+
+                    if (propertyName == "startOffset" || propertyName == "pausedTime" || propertyName == "isPlaying") {
+
+                        if (!viewNode.latestPlayState) {
+                            viewNode.latestPlayState = {
+                                "startOffset": null,
+                                "pausedTime": null,
+                                "isPlaying": false
+                            }
+                        }
+
+                        viewNode.latestPlayState[propertyName] = propertyValue;
+
+
+                        if (propertyName == "isPlaying") {
+                            viewNode.isPlaying = propertyValue;
+                            _self_.applyPlayState(nodeId);
+                        }
+
+                    }
+
+
+                    if (propertyName == 'mask') {
+
+                        let mask = Object.values(this.state.nodes).filter(el => (el.name == propertyValue))[0];
+                        if (mask) {
+                            node.obj.mask = mask.obj;
+                            self.kernel.setProperty(mask.ID, "maskedNode", node.name)
+                        }
+
+                    }
+
+                    if (propertyName == "bodyNode") {
+
+                        let bodyNode = Object.values(this.state.nodes).filter(el => (el.name == propertyValue) && (el["ID"].includes("avatar") !== true))[0];
+                        if (bodyNode)
+                            node.bodyNode = bodyNode.ID;
+                    }
+
+                    if (propertyName == "motionData") {
+                        let url = propertyValue;
+                        if (url) {
+                            fetch(url)
+                                .then(response => response.json())
+                                .then(data => {
+                                    //console.log(data);
+                                    node.motionData = data;
+                                });
+                        }
+                    }
+
+                    if (propertyName == "bodyTrack") {
+                        viewNode.bodyTrack = propertyValue;
+                    }
+
+
+                    if (propertyName == "fill") {
+
+                        if (node.obj.fill instanceof Two.Texture) {
+                            if (node.obj.fill.image.nodeName == 'VIDEO') {
+                                const video = node.obj.fill.image;
+                                viewNode.playbackBoost = 0;
+                                //_self_.applyPlayState(nodeId);    
+                                viewNode.lastTimingCheck = vwf.time() * 1000 + 500;
+
+                                viewNode.isPlaying = false;
+                                viewNode.isBlocked = false;
+
+                                //video.requestVideoFrameCallback(_self_.animate);
+
+                                // video.addEventListener('timeupdate', (event) => {
+                                //     if(viewNode.bodyTrack){
+                                //        // console.log('time: ', video.currentTime);
+                                //     }
+
+                                //   });
+                            }
+                        }
+                    }
+
+                },
+
+                deletedNode: function (childID) {
+                    delete this.nodes[childID];
+                },
+
+                firedEvent: function (nodeID, eventName, eventParameters) {
+                    let self = this;
+                    var node = this.state.nodes[nodeID];
+
+                    if (!(node)) {
+                        return;
+                    }
+                    var clientThatSatProperty = self.kernel.client();
+                    var me = self.kernel.moniker();
+
+                    var avatarName = 'avatar-' + self.kernel.moniker();
+
+                    // if (eventName == "clickEvent" ||
+                    //     eventName == 'mousedownEvent' ||
+                    //     eventName == 'mouseupEvent') {
+
+                    //     if (clientThatSatProperty == me) {
+
+                    //         let methodName = eventName + 'Method';
+                    //         self.kernel.callMethod(nodeID, methodName, eventParameters);
+
+                    //         if (eventName == "clickEvent") {
+
+                    //             let mode = vwf.getProperty(avatarName, 'selectMode');
+                    //             if (mode) {
+                    //                 console.log("allow to click!!!")
+                    //                 vwf_view.kernel.setProperty(avatarName, 'selectMode', false);
+
+                    //                 let editorDriver = vwf.views["/drivers/view/editor"];
+                    //                 if (editorDriver) {
+                    //                     let selectSwitch = document.querySelector('#selectNodeSwitch');
+                    //                     // const selectSwitchComp = new mdc.iconButton.MDCIconButtonToggle(selectSwitch); //new mdc.select.MDCIconToggle
+                    //                     selectSwitch._comp.on = false;
+
+                    //                     let currentNodeDIV = document.querySelector('#currentNode');
+                    //                     if (currentNodeDIV) currentNodeDIV._setNode(nodeID);
+
+
+                    //                 }
+                    //             }
+
+                    //         }
+
+                    //     }
+                    // }
+
+
+                    // if (eventName == "clickEvent") {
+                    //     if (self.kernel.moniker() == eventParameters[0]) {
+                    //         let avatar = self.nodes[avatarName];
+                    //         let mode = vwf.getProperty(avatarName, 'selectMode');
+                    //         vwf_view.kernel.callMethod(nodeID, "clickEventMethod", [])
+                    //     }
+                    // }
+                },
+
+                ticked: function (vwfTime) {
+                    let self = this;
+
+                    _self_.updateAvatarPosition();
+
+                    // _self_.updateFilters();
+                    //lerpTick ();
+                },
+
+                calledMethod: function (nodeID, methodName, methodParameters, methodValue) {
+                    let self = this;
+                    var node = this.state.nodes[nodeID];
+                    const viewNode = this.nodes[nodeID];
+
+                    if (!(node && node.obj)) {
+                        return;
+                    }
+
+                    // if(methodName == "setMask"){
+                    //     let mask = this.state.nodes[methodParameters[0]];
+                    //     node.obj.mask = mask.obj;
+                    // }
+
+                    if (methodName == "setScale") {
+                        if (!node.obj.matrix.manual)
+                            node.obj.matrix.manual = true;
+
+                        node.obj.matrix.scale(methodParameters[0], methodParameters[1])
+                    }
+
+                    if (methodName == "unmute") {
+                        node.obj.fill.image.muted = false;
+                    }
+
+                    if (methodName == "syncVideoState") {
+                        _self_.applyPlayState(nodeID);
+                    }
+
+                    if (methodName == "setVideoState") {
+
+                        if (!viewNode.latestPlayState)
+                            viewNode.latestPlayState = {}
+
+                        // "isPlaying",
+                        // "startOffset",
+                        // "pausedTime"
+
+                        viewNode.latestPlayState["isPlaying"] = methodParameters[0];
+                        viewNode.latestPlayState["startOffset"] = methodParameters[1];
+                        viewNode.latestPlayState["pausedTime"] = methodParameters[2];
+
+                        _self_.applyPlayState(nodeID);
+
+                    }
+
+                    if (methodName == "playVideo") {
+
+                        if (node.obj.fill instanceof Two.Texture) {
+                            if (node.obj.fill.image.nodeName == 'VIDEO') {
+                                const video = node.obj.fill.image;
+                                //video.currentTime = _self_.wrappedTime(videoTime, true);
+                                //video.play();
+
+                                if (!viewNode.latestPlayState) {
+                                    viewNode.latestPlayState = {
+                                        "isPlaying": false,
+                                        "startOffset": null,
+                                        "pausedTime": 0,
+                                    }
+                                }
+
+                                const wantsToPlay = !viewNode.latestPlayState.isPlaying; // toggle
+                                //viewNode.isPlaying = wantsToPlay;
+
+                                if (!wantsToPlay) {
+                                    viewNode.isPlaying = false;
+                                    _self_.pause(undefined, video, nodeID);
+                                } // immediately!
+
+
+                                const videoTime = video.currentTime;
+                                const sessionTime = vwf.time() * 1000; // the session time corresponding to the video time
+                                const startOffset = wantsToPlay ? sessionTime - 1000 * videoTime : null;
+                                const pausedTime = wantsToPlay ? 0 : videoTime;
+
+                                vwf_view.kernel.callMethod(nodeID, "setVideoState", [wantsToPlay, startOffset, pausedTime]);
+
+                            }
+
+                        }
+                    }
+
+
+                    if (methodName == "viewTroughFilter") {
+
+                        var clientThatSatProperty = self.kernel.client();
+                        var me = self.kernel.moniker();
+                        //let avatarID = methodParameters[0];
+                        //&& avatarID.includes(me)
+                        if (clientThatSatProperty == me) {
+
+                            console.log("MY VIEW!!!");
+                            let maskedNode = self.state.nodes[methodParameters[0]];
+                            if (maskedNode) {
+                                //maskedNode.obj.visible = methodParameters[1]
+                                if (maskedNode.obj.fill.image) {
+
+                                    if (!self.isIOS) {  //TODO: IOS
+                                        if (self.clicked) {
+                                            //maskedNode.obj.fill.image.muted = methodParameters[1]; 
+                                            if (methodParameters[1]) {
+                                                maskedNode.obj.fill.image.volume = 0
+                                            } else {
+
+                                                if (maskedNode.obj.fill.image.muted)
+                                                    maskedNode.obj.fill.image.muted = false
+
+                                                maskedNode.obj.fill.image.volume = 1
+
+                                            }
+                                        }
+                                    }
+
+                                }
+                            }
+                        }
+                    }
+
+                    if (methodName == "checkOver") {
+
+                        var clientThatSatProperty = self.kernel.client();
+                        var me = self.kernel.moniker();
+
+
+                        // If the transform property was initially updated by this view....
+                        if (clientThatSatProperty == me) {
+
+                            let scene = node.scene.obj.scene;
+                            let scale = scene.scale;
+
+                            var x = methodParameters[0] * scale;
+                            var y = methodParameters[1] * scale;
+
+                            let allChilds = _self_.getOverlayChilds(node.scene.obj.scene, x, y).map(x => {
+                                return x.nodeID;
+                            });
+
+                            if (JSON.stringify(allChilds) !== JSON.stringify(self.overChilds)) {
+                                //console.log(allChilds);
+
+                                let end = self.overChilds.filter(x => !allChilds.includes(x));
+                                //console.log("END OVERLAY..", end);
+                                end.map(x => {
+                                    vwf_view.kernel.callMethod(x, "overendEvent", ['avatar-', me]);
+                                })
+
+                                let start = allChilds.filter(x => !self.overChilds.includes(x));
+                                //console.log("START OVERLAY..", start);
+                                start.map(x => {
+                                    vwf_view.kernel.callMethod(x, "overstartEvent", ['avatar-', me]);
+                                })
+
+                                self.overChilds = allChilds;
+                            }
+
+                            //let still = self.overChilds.filter(x => allChilds.includes(x));
+                            //if (still.length > 0)
+                            //    console.log("STILL OVERLAY..", still)
+
+                        }
+                    }
+                }
+            });
+    }
+
+
+    ///
+
+    mouseUp(node, x, y) {
+
+        let self = this.instance;
+
+        let avatarID = "avatar-" + vwf.moniker();
+        vwf_view.kernel.setProperty(avatarID, "mouseevent", "mouseup");
+
+        let allChilds = this.getOverlayChilds(node.obj.scene, x, y).map(x => {
+            return x.nodeID;
+        });
+
+        allChilds.forEach(el => {
+            vwf_view.kernel.callMethod(el, "mouseupEvent", []);
+            vwf_view.kernel.callMethod(el, "checkForDragEnd", [avatarID]);
+        })
+
+    }
+
+    mouseDown(node, x, y) {
+        let self = this.instance;
+
+        let avatarID = "avatar-" + vwf.moniker();
+        vwf_view.kernel.setProperty(avatarID, "mouseevent", "mousedown");
+
+        let allChilds = this.getOverlayChilds(node.obj.scene, x, y).map(x => {
+            return x.nodeID;
+        });
+
+        allChilds.forEach(el => {
+            vwf_view.kernel.callMethod(el, "mousedownEvent", []);
+            vwf_view.kernel.callMethod(el, "checkForDragStart", [avatarID])
+        })
+
+    }
+
+    resizeScene(childID) {
+        let self = this.instance;
+        let scene = self.state.nodes[childID].obj;
+        let renderer = scene.renderer
+        let elem = renderer.domElement;
+        let scale = Math.min(
+            elem.offsetWidth / 1280,
+            elem.offsetHeight / 720
+        );
+        // var scale = //elem.offsetHeight / 1000;
+        renderer.scene.scale = scale;
+        renderer.setSize(elem.offsetWidth, elem.offsetHeight);
+    }
+
+    ///VIDEO & SOUND SYNC//////
+
+    // doSomethingWithTheFrame = (now, metadata) => {
+    //     // Do something with the frame.
+    //     console.log(now, metadata);
+    //     // Re-register the callback to be notified about the next frame.
+    //     video.requestVideoFrameCallback(doSomethingWithTheFrame);
+    //   };
+    //   // Initially register the callback to be notified about the first frame.
+    //   video.requestVideoFrameCallback(doSomethingWithTheFrame);
+
+
+    update(frameCount) {
+        let self = this.instance;
+        const now = vwf.time() * 1000;
+
+        this.animate(frameCount);
+
+        if (now - self.lastStatusCheck > 100) {
+            self.lastStatusCheck = now;
+            this.checkPlayStatus();
+        }
+
+    }
+
+    applyPlayState(nodeID) {
+        let self = this.instance;
+        const node = self.state.nodes[nodeID];
+        const viewNode = self.nodes[nodeID];
+        const video = node.obj.fill.image;
+
+        if (!viewNode.latestPlayState.isPlaying) {
+            // this.iconVisible('play', true);
+            // this.iconVisible('enableSound', false);
+            this.pause(viewNode.latestPlayState.pausedTime, video, nodeID);
+        } else {
+            video.playbackRate = 1 + viewNode.playbackBoost * 0.01;
+            viewNode.lastRateAdjust = vwf.time() * 1000; // make sure we don't adjust rate until playback has settled in, and after any emergency jump we decide to do
+            viewNode.jumpIfNeeded = false;
+            // if the video is blocked from playing, enter a stepping mode in which we move the video forward with successive pause() calls
+            viewNode.isPlaying = true;
+            this.play(video, this.calculateVideoTime(nodeID) + 0.1, nodeID).then(playStarted => {
+
+                if (playStarted) {
+                    setTimeout(function () {
+                        viewNode.jumpIfNeeded = true;
+                    }, 250);
+                }
+                else if (!video.muted) {
+                    console.log(`trying with mute`);
+                    video.muted = true;
+                    this.applyPlayState(nodeID);
+                }
+                else {
+                    console.log(`reverting to stepped display`);
+                    viewNode.isStepping = true;
+                    this.stepWhileBlocked(nodeID);
+                }
+
+                //this.iconVisible('enableSound', !playStarted || videoElem.muted);
+                //if (playStarted) this.future(250).triggerJumpCheck(); // leave it a little time to stabilise
+            })
+        }
+
+    }
+
+    animate(frameCount, metadata) {
+
+        let driver = vwf.views["/drivers/view/two"];
+
+        if (driver) {
+            let self = driver.instance;
+
+            let videos = Object.values(self.state.nodes).filter(el => el.fillType == "video");
+            videos.forEach(el => {
+
+                let viewNode = self.nodes[el.ID];
+                let node = self.state.nodes[el.ID];
+                let video = node.obj.fill.image;
+                let currentTime = video.currentTime;
+                //console.log(video.currentTime);
+
+                if (node.bodyNode) {
+                    let bodyNode = self.state.nodes[node.bodyNode];
+                    //if(metadata.presentedFrames % 5 == 0){
+                    if (viewNode.bodyTrack && bodyNode.motionData) {
+
+                        //console.log(currentTime);
+                        let bodyFrameNumber = Object.keys(bodyNode.motionData).filter(n => (Math.abs(Number.parseFloat(n) - currentTime) < 0.02))[0];
+                        let bodyFrame = bodyNode.motionData[bodyFrameNumber];
+
+                        let mul = 950;
+                        if (bodyFrame) {
+                            bodyNode.obj.children.map((e, i) => {
+                                if (e.nodeID.includes("joint")) {
+                                    e.translation.x = bodyFrame[i].x * mul;
+                                    e.translation.y = bodyFrame[i].y * mul;
+                                }
+                            })
+
+                            //16-14-12-11-13-15 - topline
+                            //12-24-23-11 - bottomline
+
+
+                            //18,16,20 - rh // 16,22
+                            //17,15,19 - lh // 15,21
+                            //let faceArr = [10,8,6,4,1,3,7,9]
+
+                            let ta = [16, 14, 12, 11, 13, 15];
+                            let topline = bodyNode.obj.children.filter(e => (e.nodeID.includes("topline")))[0];
+                            if (topline) {
+                                ta.map((e, i) => {
+                                    topline.vertices[i].set(bodyFrame[e].x * mul, bodyFrame[e].y * mul);
+                                })
+                            }
+
+                            let rh = [18, 16, 20];
+                            let rhline = bodyNode.obj.children.filter(e => (e.nodeID.includes("rhand")))[0];
+                            if (rhline) {
+                                rh.map((e, i) => {
+                                    rhline.vertices[i].set(bodyFrame[e].x * mul, bodyFrame[e].y * mul);
+                                })
+                            }
+
+                            let lh = [17, 15, 19];
+                            let lhline = bodyNode.obj.children.filter(e => (e.nodeID.includes("lhand")))[0];
+                            if (lhline) {
+                                lh.map((e, i) => {
+                                    lhline.vertices[i].set(bodyFrame[e].x * mul, bodyFrame[e].y * mul);
+                                })
+                            }
+
+                            let rh2 = [16, 22];
+                            let rhline2 = bodyNode.obj.children.filter(e => (e.nodeID.includes("rhand2")))[0];
+                            if (rhline2) {
+                                rh2.map((e, i) => {
+                                    rhline2.vertices[i].set(bodyFrame[e].x * mul, bodyFrame[e].y * mul);
+                                })
+                            }
+
+                            let lh2 = [15, 21];
+                            let lhline2 = bodyNode.obj.children.filter(e => (e.nodeID.includes("lhand2")))[0];
+                            if (lhline2) {
+                                lh2.map((e, i) => {
+                                    lhline2.vertices[i].set(bodyFrame[e].x * mul, bodyFrame[e].y * mul);
+                                })
+                            }
+
+                            let faceArr = [10, 8, 6, 4, 1, 3, 7, 9, 10];
+                            let faceline = bodyNode.obj.children.filter(e => (e.nodeID.includes("faceline")))[0];
+                            if (faceline) {
+                                faceArr.map((e, i) => {
+                                    faceline.vertices[i].set(bodyFrame[e].x * mul, bodyFrame[e].y * mul);
+                                })
+                            }
+
+                            // let ba = [11,23]; //[12,24,23,11];
+                            // let bottomline = bodyNode.obj.children.filter(e=>(e.nodeID.includes("bottomline")))[0];
+                            // if(bottomline){
+                            //     ba.map((e,i)=>{
+                            //         bottomline.vertices[i].set(bodyFrame[e].x*mul, bodyFrame[e].y*mul);
+                            //     })
+                            // }
+
+                        }
+                    }
+                }
+                //}
+
+                //video.requestVideoFrameCallback(self.animate);
+            })
+
+        }
+    }
+
+    checkPlayStatus() {
+        let self = this.instance;
+        //let scene = self.nodes[vwf.application()];
+
+        let videos = Object.values(self.state.nodes).filter(el => el.fillType == "video");
+        videos.forEach(el => {
+            this.checkPlayStatusForNode(el.ID);
+        })
+
+        let toneJSDriver = vwf.views["/drivers/view/tone"];
+        if (toneJSDriver) {
+            let toneTransport = Object.values(toneJSDriver.state.nodes).filter(el => el.extendsID == "proxy/tonejs/transport.vwf")[0];
+            if (toneTransport) {
+                toneJSDriver.fabric.checkPlayStatusForTransportNode(toneTransport.ID);
+            }
+        }
+
+
+    }
+
+    checkPlayStatusForNode(nodeID) {
+        let self = this.instance;
+        let viewNode = self.nodes[nodeID];
+        let node = self.state.nodes[nodeID];
+        let video = node.obj.fill.image;
+
+        //if (this.videoView) {
+        // this.adjustPlaybar();
+
+        const lastTimingCheck = viewNode.lastTimingCheck || 0;
+        const now = vwf.time() * 1000;
+        // check video timing every 0.5s
+        if (viewNode.isPlaying && !viewNode.isBlocked && (now - lastTimingCheck >= 500)) {
+            viewNode.lastTimingCheck = now;
+            const expectedTime = this.wrappedTime(this.calculateVideoTime(nodeID), false, video.duration);
+            const videoTime = video.currentTime;
+            const videoDiff = videoTime - expectedTime;
+            const videoDiffMS = videoDiff * 1000; // +ve means *ahead* of where it should be
+            if (videoDiff < video.duration / 2) { // otherwise presumably measured across a loop restart; just ignore.
+                if (viewNode.jumpIfNeeded) { //this.jumpIfNeeded
+                    viewNode.jumpIfNeeded = false;
+                    // if there's a difference greater than 500ms, try to jump the video to the right place
+                    if (Math.abs(videoDiffMS) > 500) {
+                        console.log(`jumping video by ${-Math.round(videoDiffMS)}ms`);
+                        video.currentTime = this.wrappedTime(videoTime - videoDiff + 0.1, true, video.duration); // 0.1 to counteract the delay that the jump itself tends to introduce; true to ensure we're not jumping beyond the last video frame
+                    }
+                } else {
+                    // every 3s, check video lag/advance, and set the playback rate accordingly.
+                    // current adjustment settings:
+                    //   > 150ms off: set playback 3% faster/slower than normal
+                    //   > 50ms: 1% faster/slower
+                    //   < 25ms: normal (i.e., hysteresis between 50ms and 25ms in the same sense)
+                    const lastRateAdjust = viewNode.lastRateAdjust || 0;
+                    if (now - lastRateAdjust >= 3000) {
+                        //console.log(`${Math.round(videoDiff*1000)}ms`);
+                        const oldBoostPercent = viewNode.playbackBoost;
+                        const diffAbs = Math.abs(videoDiffMS), diffSign = Math.sign(videoDiffMS);
+                        const desiredBoostPercent = -diffSign * (diffAbs > 150 ? 3 : (diffAbs > 50 ? 1 : 0));
+                        if (desiredBoostPercent !== oldBoostPercent) {
+                            // apply hysteresis on the switch to boost=0.
+                            // for example, if old boost was +ve (because video was lagging),
+                            // and videoDiff is -ve (i.e., it's still lagging),
+                            // and the magnitude (of the lag) is greater than 25ms,
+                            // don't remove the boost yet.
+                            const hysteresisBlock = desiredBoostPercent === 0 && Math.sign(oldBoostPercent) === -diffSign && diffAbs >= 25;
+                            if (!hysteresisBlock) {
+                                viewNode.playbackBoost = desiredBoostPercent;
+                                const playbackRate = 1 + viewNode.playbackBoost * 0.01;
+                                console.log(`video playback rate: ${playbackRate}`);
+                                video.playbackRate = playbackRate;
+                            }
+                        }
+                        viewNode.lastRateAdjust = now;
+                    }
+                }
+            }
+        }
+        // }
+    }
+
+    wrappedTime(videoTime, guarded, duration) {
+        if (duration) {
+            while (videoTime > duration) videoTime -= duration; // assume it's looping, with no gap between plays
+            if (guarded) videoTime = Math.min(duration - 0.1, videoTime); // the video element freaks out on being told to seek very close to the end
+        }
+        return videoTime;
+    }
+
+    calculateVideoTime(nodeID) {
+
+        let self = this.instance;
+        const node = self.state.nodes[nodeID];
+        const viewNode = self.nodes[nodeID];
+        //const video = node.obj.fill.image;
+
+        // const { isPlaying, startOffset } = this.latestPlayState;
+        //if (!isPlaying) debugger;
+
+        const sessionNow = vwf.time() * 1000;
+        let t = (sessionNow - viewNode.latestPlayState.startOffset) / 1000;
+        // console.log('Time: ', t)
+        return t;
+    }
+
+    pause(videoTime, video, nodeID) {
+        let self = this.instance;
+        const node = self.state.nodes[nodeID];
+        const viewNode = self.nodes[nodeID];
+
+        viewNode.isPlaying = viewNode.isBlocked = false; // might not be blocked next time.
+        this.setStatic(videoTime, video);
+    }
+
+    setStatic(videoTime, video) {
+        if (video) {
+            if (videoTime !== undefined) video.currentTime = this.wrappedTime(videoTime, true, video.duration); // true => guarded from values too near the end
+            video.pause(); // no return value; synchronous, instantaneous?
+        }
+    }
+
+    triggerJumpCheck(nodeID) {
+        let self = this.instance;
+        //const node = self.state.nodes[nodeID];
+        const viewNode = self.nodes[nodeID];
+
+        viewNode.jumpIfNeeded = true;
+    }
+
+    stepWhileBlocked(nodeID) {
+        let _self = this;
+        let self = this.instance;
+        const node = self.state.nodes[nodeID];
+        const viewNode = self.nodes[nodeID];
+        const video = node.obj.fill.image;
+
+        if (!viewNode.isStepping) return; // we've left stepping mode
+        if (!viewNode.isBlocked) {
+            viewNode.isStepping = false;
+            return;
+        }
+        this.setStatic(this.calculateVideoTime(nodeID), video);
+
+        setTimeout(function () {
+            _self.stepWhileBlocked(nodeID)
+        }, 250);
+
+        //this.future(250).stepWhileBlocked(); // jerky, but keeping up
+    }
+
+    async play(video, videoTime, nodeID) {
+
+        let self = this.instance;
+        const node = self.state.nodes[nodeID];
+        const viewNode = self.nodes[nodeID];
+
+        // return true if video play started successfully
+        const duration = video.duration;
+        video.currentTime = this.wrappedTime(videoTime, true, duration);
+        viewNode.isPlaying = true;
+        //this.isPlaying = true; // even if it turns out to be blocked by the browser
+        // following guidelines from https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/play
+        try {
+            await video.play(); // will throw exception if blocked
+            viewNode.isBlocked = false;
+        } catch (err) {
+            console.warn("video play blocked");
+            viewNode.isBlocked = viewNode.isPlaying; // just in case isPlaying was set false while we were trying
+        }
+        return !viewNode.isBlocked;
+    }
+
+    /////
+
+
+    getOverlayChilds(node, x, y) {
+        //let childs = node.scene.obj.scene.children;
+        var childs = [];
+        node.children.forEach(el => {
+            if (!el.nodeID.includes('avatar')) {
+                let rect = el.getBoundingClientRect();
+                if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
+                    childs.push(el);
+                    if (el.nodeName == "group") {
+                        childs = childs.concat(this.getOverlayChilds(el, x, y));
+                    }
+                }
+            }
+        })
+
+        return childs
+
+    }
+
+    updateAvatarPosition() {
+        let self = this.instance;
+        let avatarName = 'avatar-' + self.kernel.moniker();
+        var node = self.state.nodes[avatarName];
+        var nodeView = self.nodes[avatarName];
+        let scene = self.nodes[vwf.application()];
+
+        if (!node) return;
+        if (!node.obj) return;
+
+        let position = scene.mouse;
+
+        if (!nodeView.lastPosition) {
+            nodeView.lastPosition = new Two.Vector(position.x, position.y);
+        }
+
+        let lastPosition = nodeView.lastPosition;
+
+        if (position && !(position.equals(lastPosition))) {
+            // self.kernel.setProperty(avatarName, "x", position.x);
+            // self.kernel.setProperty(avatarName, "y", position.y);
+            self.kernel.callMethod(avatarName, "move", [position.x, position.y]);
+        }
+
+        nodeView.lastPosition.set(position.x, position.y)
+
+    }
+
+
+    postLoadAction(nodeID) {
+
+        //vwf_view.kernel.fireEvent(nodeID, "postLoadAction")
+    }
+
+
+}
+
+export { TwoView as default }

+ 16411 - 0
public/drivers/view/twojs/two.js

@@ -0,0 +1,16411 @@
+/*
+MIT License
+
+Copyright (c) 2012 - 2021 jonobr1 / http://jonobr1.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+(function (global, factory) {
+  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+  typeof define === 'function' && define.amd ? define(factory) :
+  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Two = factory());
+}(this, (function () { 'use strict';
+
+  /**
+   * @name Two.Commands
+   * @property {Object} - Map of possible path commands. Taken from the SVG specification.
+   */
+  var Commands = {
+    move: 'M',
+    line: 'L',
+    curve: 'C',
+    arc: 'A',
+    close: 'Z'
+  };
+
+  var root;
+  if (typeof window !== 'undefined') {
+    root = window;
+  } else if (typeof global !== 'undefined') {
+    root = global;
+  } else if (typeof self !== 'undefined') {
+    root = self;
+  }
+
+  var root$1 = root;
+
+  var Matrix$1;
+
+  /**
+   * @name Two.Utils.decomposeMatrix
+   * @function
+   * @param {Two.Matrix} matrix - The matrix to decompose.
+   * @returns {Object} An object containing relevant skew values.
+   * @description Decompose a 2D 3x3 Matrix to find the skew.
+   */
+  var decomposeMatrix = function(matrix) {
+
+    // TODO: Include skewX, skewY
+    // https://math.stackexchange.com/questions/237369/given-this-transformation-matrix-how-do-i-decompose-it-into-translation-rotati/417813
+    // https://stackoverflow.com/questions/45159314/decompose-2d-transformation-matrix
+
+    return {
+        translateX: matrix.e,
+        translateY: matrix.f,
+        scaleX: Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b),
+        scaleY: Math.sqrt(matrix.c * matrix.c + matrix.d * matrix.d),
+        rotation: 180 * Math.atan2(matrix.b, matrix.a) / Math.PI
+    };
+
+  };
+
+  var setMatrix = function(M) {
+    Matrix$1 = M;
+  };
+
+  /**
+   * @name Two.Utils.getComputedMatrix
+   * @function
+   * @param {Two.Shape} object - The Two.js object that has a matrix property to calculate from.
+   * @param {Two.Matrix} [matrix] - The matrix to apply calculated transformations to if available.
+   * @returns {Two.Matrix} The computed matrix of a nested object. If no `matrix` was passed in arguments then a `new Two.Matrix` is returned.
+   * @description Method to get the world space transformation of a given object in a Two.js scene.
+   */
+  var getComputedMatrix = function(object, matrix) {
+
+    matrix = (matrix && matrix.identity()) || new Matrix$1();
+    var parent = object, matrices = [];
+
+    while (parent && parent._matrix) {
+      matrices.push(parent._matrix);
+      parent = parent.parent;
+    }
+
+    matrices.reverse();
+
+    for (var i = 0; i < matrices.length; i++) {
+
+      var m = matrices[i];
+      var e = m.elements;
+      matrix.multiply(
+        e[0], e[1], e[2], e[3], e[4], e[5], e[6], e[7], e[8], e[9]);
+
+    }
+
+    return matrix;
+
+  };
+
+  /**
+   * @name Two.Utils.lerp
+   * @function
+   * @param {Number} a - Start value.
+   * @param {Number} b - End value.
+   * @param {Number} t - Zero-to-one value describing percentage between a and b.
+   * @returns {Number}
+   * @description Linear interpolation between two values `a` and `b` by an amount `t`.
+   */
+  var lerp = function(a, b, t) {
+    return t * (b - a) + a;
+  };
+
+  /**
+   * @name Two.Utils.mod
+   * @function
+   * @param {Number} v - The value to modulo
+   * @param {Number} l - The value to modulo by
+   * @returns {Number}
+   * @description Modulo with added functionality to handle negative values in a positive manner.
+   */
+  var mod = function(v, l) {
+
+    while (v < 0) {
+      v += l;
+    }
+
+    return v % l;
+
+  };
+
+  var NumArray = root$1.Float32Array || Array;
+
+  /**
+  * @name Two.Utils.toFixed
+  * @function
+  * @param {Number} v - Any float
+  * @returns {Number} That float trimmed to the third decimal place.
+  * @description A pretty fast toFixed(3) alternative.
+  * @see {@link http://jsperf.com/parsefloat-tofixed-vs-math-round/18}
+  */
+  var toFixed = function(v) {
+    return Math.floor(v * 1000000) / 1000000;
+  };
+
+  var math = /*#__PURE__*/Object.freeze({
+    __proto__: null,
+    decomposeMatrix: decomposeMatrix,
+    getComputedMatrix: getComputedMatrix,
+    setMatrix: setMatrix,
+    lerp: lerp,
+    mod: mod,
+    NumArray: NumArray,
+    toFixed: toFixed
+  });
+
+  var slice = Array.prototype.slice;
+
+  var isArrayLike = function(collection) {
+    if (collection === null || collection === undefined) return false;
+    var length = collection.length;
+    // Arrays cannot hold more than 2^32 - 1 items
+    return (typeof length == 'number' && length >= 0 && length < 4294967296);
+  };
+
+  var _ = {
+    isNaN: function(obj) {
+      return typeof obj === 'number' && obj !== +obj;
+    },
+    isElement: function(obj) {
+      return !!(obj && obj.nodeType === 1);
+    },
+    isObject: function(obj) {
+      var type = typeof obj;
+      return type === 'function' || type === 'object' && !!obj;
+    },
+    extend: function(base) {
+      var sources = slice.call(arguments, 1);
+      for (var i = 0; i < sources.length; i++) {
+        var obj = sources[i];
+        for (var k in obj) {
+          base[k] = obj[k];
+        }
+      }
+      return base;
+    },
+    defaults: function(base) {
+      var sources = slice.call(arguments, 1);
+      for (var i = 0; i < sources.length; i++) {
+        var obj = sources[i];
+        for (var k in obj) {
+          if (base[k] === void 0) {
+          base[k] = obj[k];
+          }
+        }
+      }
+      return base;
+    },
+    each: function(obj, iteratee, context) {
+      var ctx = context || this;
+      var keys = !isArrayLike(obj) && Object.keys(obj);
+      var length = (keys || obj).length;
+      for (var i = 0; i < length; i++) {
+        var k = keys ? keys[i] : i;
+        iteratee.call(ctx, obj[k], k, obj);
+      }
+      return obj;
+    },
+    /**
+     * @name Two.Utils.performance
+     * @property {Date} - A special `Date` like object to get the current millis of the session. Used internally to calculate time between frames.
+     * e.g: `Utils.performance.now() // milliseconds since epoch`
+     */
+    performance: ((root$1.performance && root$1.performance.now) ? root$1.performance : Date),
+  };
+
+  /**
+   * @name Two.Events
+   * @class
+   * @description Object inherited by many Two.js objects in order to facilitate custom events.
+   */
+  var Events = {
+
+    /**
+     * @name Two.Events#on
+     * @function
+     * @param {String} [name] - The name of the event to bind a function to.
+     * @param {Function} [handler] - The function to be invoked when the event is dispatched.
+     * @description Call to add a listener to a specific event name.
+     */
+    on: addEventListener,
+
+    /**
+     * @name Two.Events#off
+     * @function
+     * @param {String} [name] - The name of the event intended to be removed.
+     * @param {Function} [handler] - The handler intended to be reomved.
+     * @description Call to remove listeners from a specific event. If only `name` is passed then all the handlers attached to that `name` will be removed. If no arguments are passed then all handlers for every event on the obejct are removed.
+     */
+    off: removeEventListener,
+
+    /**
+     * @name Two.Events#trigger
+     * @function
+     * @param {String} name - The name of the event to dispatch.
+     * @param arguments - Anything can be passed after the name and those will be passed on to handlers attached to the event in the order they are passed.
+     * @description Call to trigger a custom event. Any additional arguments passed after the name will be passed along to the attached handlers.
+     */
+    trigger: function(name) {
+      var scope = this;
+      if (!scope._events) return scope;
+      var args = Array.prototype.slice.call(arguments, 1);
+      var events = scope._events[name];
+      if (events) dispatch(scope, events, args);
+      return scope;
+    },
+
+    listen: function(obj, name, handler) {
+
+      var bound = this;
+
+      if (obj) {
+
+        var event = function () {
+          handler.apply(bound, arguments);
+        };
+
+        // Add references about the object that assigned this listener
+        event.obj = obj;
+        event.name = name;
+        event.handler = handler;
+
+        obj.on(name, event);
+
+      }
+
+      return bound;
+
+    },
+
+    ignore: function(obj, name, handler) {
+
+      var scope = this;
+      obj.off(name, handler);
+      return scope;
+
+    },
+
+    /**
+     * @name Two.Events.Types
+     * @property {Object} - Object of different types of Two.js specific events.
+     */
+    Types: {
+      play: 'play',
+      pause: 'pause',
+      update: 'update',
+      render: 'render',
+      resize: 'resize',
+      change: 'change',
+      remove: 'remove',
+      insert: 'insert',
+      order: 'order',
+      load: 'load'
+    }
+
+  };
+
+
+  /**
+   * @name Two.Events.bind
+   * @function
+   * @description Alias for {@link Two.Events.on}.
+   */
+  Events.bind = addEventListener;
+
+  /**
+   * @name Two.Events.unbind
+   * @function
+   * @description Alias for {@link Two.Events.off}.
+   */
+  Events.unbind = removeEventListener;
+
+  /**
+   * @private
+   * @returns {Two.Events} - Returns an instance of self for the purpose of chaining.
+   */
+  function addEventListener(name, handler) {
+
+    var scope = this;
+
+    scope._events || (scope._events = {});
+    var list = scope._events[name] || (scope._events[name] = []);
+
+    list.push(handler);
+
+    return scope;
+
+  }
+
+  /**
+   * @private
+   * @returns {Two.Events} - Returns an instance of self for the purpose of chaining.
+   */
+  function removeEventListener(name, handler) {
+
+    var scope = this;
+
+    if (!scope._events) {
+      return scope;
+    }
+    if (!name && !handler) {
+      scope._events = {};
+      return scope;
+    }
+
+    var names = name ? [name] : Object.keys(scope._events);
+    for (var i = 0, l = names.length; i < l; i++) {
+
+      name = names[i];
+      var list = scope._events[name];
+
+      if (list) {
+        var events = [];
+        if (handler) {
+          for (var j = 0, k = list.length; j < k; j++) {
+            var ev = list[j];
+            ev = ev.handler ? ev.handler : ev;
+            if (handler && handler !== ev) {
+              events.push(ev);
+            }
+          }
+        }
+        scope._events[name] = events;
+      }
+    }
+
+    return scope;
+  }
+
+  function dispatch(obj, events, args) {
+    var method;
+    switch (args.length) {
+    case 0:
+      method = function(i) {
+        events[i].call(obj, args[0]);
+      };
+      break;
+    case 1:
+      method = function(i) {
+        events[i].call(obj, args[0], args[1]);
+      };
+      break;
+    case 2:
+      method = function(i) {
+        events[i].call(obj, args[0], args[1], args[2]);
+      };
+      break;
+    case 3:
+      method = function(i) {
+        events[i].call(obj, args[0], args[1], args[2], args[3]);
+      };
+      break;
+    default:
+      method = function(i) {
+        events[i].apply(obj, args);
+      };
+    }
+    for (var i = 0; i < events.length; i++) {
+      method(i);
+    }
+  }
+
+  /**
+   * @name Two.Vector
+   * @class
+   * @param {Number} [x=0] - Any number to represent the horizontal x-component of the vector.
+   * @param {Number} [y=0] - Any number to represent the vertical y-component of the vector.
+   * @description A class to store x / y component vector data. In addition to storing data `Two.Vector` has suped up methods for commonplace mathematical operations.
+   */
+  function Vector(x, y) {
+
+    /**
+     * @name Two.Vector#x
+     * @property {Number} - The horizontal x-component of the vector.
+     */
+    this.x = x || 0;
+
+    /**
+     * @name Two.Vector#y
+     * @property {Number} - The vertical y-component of the vector.
+     */
+    this.y = y || 0;
+
+  }
+
+  _.extend(Vector, {
+
+    /**
+     * @name Two.Vector.zero
+     * @readonly
+     * @property {Two.Vector} - Handy reference to a vector with component values 0, 0 at all times.
+     */
+    zero: new Vector(),
+
+    /**
+     * @name Two.Vector.add
+     * @function
+     * @param {Two.Vector} v1
+     * @param {Two.Vector} v2
+     * @returns {Two.Vector}
+     * @description Add two vectors together.
+     */
+    add: function(v1, v2) {
+      return new Vector(v1.x + v2.x, v1.y + v2.y);
+    },
+
+    /**
+     * @name Two.Vector.sub
+     * @function
+     * @param {Two.Vector} v1
+     * @param {Two.Vector} v2
+     * @returns {Two.Vector}
+     * @description Subtract two vectors: `v2` from `v1`.
+     */
+    sub: function(v1, v2) {
+      return new Vector(v1.x - v2.x, v1.y - v2.y);
+    },
+
+    /**
+     * @name Two.Vector.subtract
+     * @function
+     * @description Alias for {@link Two.Vector.sub}.
+     */
+    subtract: function(v1, v2) {
+      return Vector.sub(v1, v2);
+    },
+
+    /**
+     * @name Two.Vector.ratioBetween
+     * @function
+     * @param {Two.Vector} A
+     * @param {Two.Vector} B
+     * @returns {Number} The ratio betwen two points `v1` and `v2`.
+     */
+    ratioBetween: function(v1, v2) {
+
+      return (v1.x * v2.x + v1.y * v2.y) / (v1.length() * v2.length());
+
+    },
+
+    /**
+     * @name Two.Vector.angleBetween
+     * @function
+     * @param {Two.Vector} v1
+     * @param {Two.Vector} v2
+     * @returns {Number} The angle between points `v1` and `v2`.
+     */
+    angleBetween: function(v1, v2) {
+
+      var dx, dy;
+
+      if (arguments.length >= 4) {
+
+        dx = arguments[0] - arguments[2];
+        dy = arguments[1] - arguments[3];
+
+        return Math.atan2(dy, dx);
+
+      }
+
+      dx = v1.x - v2.x;
+      dy = v1.y - v2.y;
+
+      return Math.atan2(dy, dx);
+
+    },
+
+    /**
+     * @name Two.Vector.distanceBetween
+     * @function
+     * @param {Two.Vector} v1
+     * @param {Two.Vector} v2
+     * @returns {Number} The distance between points `v1` and `v2`. Distance is always positive.
+     */
+    distanceBetween: function(v1, v2) {
+
+      return Math.sqrt(Vector.distanceBetweenSquared(v1, v2));
+
+    },
+
+    /**
+     * @name Two.Vector.distanceBetweenSquared
+     * @function
+     * @param {Two.Vector} v1
+     * @param {Two.Vector} v2
+     * @returns {Number} The squared distance between points `v1` and `v2`.
+     */
+    distanceBetweenSquared: function(v1, v2) {
+
+      var dx = v1.x - v2.x;
+      var dy = v1.y - v2.y;
+
+      return dx * dx + dy * dy;
+
+    },
+
+    /**
+     * @name Two.Vector.MakeObservable
+     * @function
+     * @param {Object} object - The object to make observable.
+     * @description Convenience function to apply observable qualities of a {@link Two.Vector} to any object. Handy if you'd like to extend the {@link Two.Vector} class on a custom class.
+     */
+    MakeObservable: function(object) {
+
+      // /**
+      //  * Override Backbone bind / on in order to add properly broadcasting.
+      //  * This allows Two.Vector to not broadcast events unless event listeners
+      //  * are explicity bound to it.
+      //  */
+
+      object.bind = object.on = function() {
+
+        if (!this._bound) {
+          this._x = this.x;
+          this._y = this.y;
+          Object.defineProperty(this, 'x', xgs);
+          Object.defineProperty(this, 'y', ygs);
+          _.extend(this, BoundProto);
+          this._bound = true; // Reserved for event initialization check
+        }
+
+        Events.bind.apply(this, arguments);
+
+        return this;
+
+      };
+
+    }
+
+  });
+
+  _.extend(Vector.prototype, Events, {
+
+    constructor: Vector,
+
+    /**
+     * @name Two.Vector#set
+     * @function
+     * @param {Number} x
+     * @param {Number} y
+     * @description Set the x / y components of a vector to specific number values.
+     */
+    set: function(x, y) {
+      this.x = x;
+      this.y = y;
+      return this;
+    },
+
+    /**
+     * @name Two.Vector#copy
+     * @function
+     * @param {Two.Vector} v
+     * @description Copy the x / y components of another object `v`.
+     */
+    copy: function(v) {
+      this.x = v.x;
+      this.y = v.y;
+      return this;
+    },
+
+    /**
+     * @name Two.Vector#clear
+     * @function
+     * @description Set the x / y component values of the vector to zero.
+     */
+    clear: function() {
+      this.x = 0;
+      this.y = 0;
+      return this;
+    },
+
+    /**
+     * @name Two.Vector#clone
+     * @function
+     * @description Create a new vector and copy the existing values onto the newly created instance.
+     */
+    clone: function() {
+      return new Vector(this.x, this.y);
+    },
+
+    /**
+     * @name Two.Vector#add
+     * @function
+     * @param {Two.Vector} v
+     * @description Add an object with x / y component values to the instance.
+     * @overloaded
+     */
+
+    /**
+     * @name Two.Vector#add
+     * @function
+     * @param {Number} v
+     * @description Add the **same** number to both x / y component values of the instance.
+     * @overloaded
+     */
+
+    /**
+     * @name Two.Vector#add
+     * @function
+     * @param {Number} x
+     * @param {Number} y
+     * @description Add `x` / `y` values to their respective component value on the instance.
+     * @overloaded
+     */
+    add: function(x, y) {
+      if (arguments.length <= 0) {
+        return this;
+      } else if (arguments.length <= 1) {
+        if (typeof x === 'number') {
+          this.x += x;
+          this.y += x;
+        } else if (x && typeof x.x === 'number' && typeof x.y === 'number') {
+          this.x += x.x;
+          this.y += x.y;
+        }
+      } else {
+        this.x += x;
+        this.y += y;
+      }
+      return this;
+    },
+
+    /**
+     * @name Two.Vector#addSelf
+     * @function
+     * @description Alias for {@link Two.Vector.add}.
+     */
+    addSelf: function(v) {
+      return this.add.apply(this, arguments);
+    },
+
+    /**
+     * @name Two.Vector#sub
+     * @function
+     * @param {Two.Vector} v
+     * @description Subtract an object with x / y component values to the instance.
+     * @overloaded
+     */
+
+    /**
+     * @name Two.Vector#sub
+     * @function
+     * @param {Number} v
+     * @description Subtract the **same** number to both x / y component values of the instance.
+     * @overloaded
+     */
+
+    /**
+     * @name Two.Vector#sub
+     * @function
+     * @param {Number} x
+     * @param {Number} y
+     * @description Subtract `x` / `y` values to their respective component value on the instance.
+     * @overloaded
+     */
+    sub: function(x, y) {
+      if (arguments.length <= 0) {
+        return this;
+      } else if (arguments.length <= 1) {
+        if (typeof x === 'number') {
+          this.x -= x;
+          this.y -= x;
+        } else if (x && typeof x.x === 'number' && typeof x.y === 'number') {
+          this.x -= x.x;
+          this.y -= x.y;
+        }
+      } else {
+        this.x -= x;
+        this.y -= y;
+      }
+      return this;
+    },
+
+    /**
+     * @name Two.Vector#subtract
+     * @function
+     * @description Alias for {@link Two.Vector.sub}.
+     */
+    subtract: function() {
+      return this.sub.apply(this, arguments);
+    },
+
+    /**
+     * @name Two.Vector#subSelf
+     * @function
+     * @description Alias for {@link Two.Vector.sub}.
+     */
+    subSelf: function(v) {
+      return this.sub.apply(this, arguments);
+    },
+
+    /**
+     * @name Two.Vector#subtractSelf
+     * @function
+     * @description Alias for {@link Two.Vector.sub}.
+     */
+    subtractSelf: function(v) {
+      return this.sub.apply(this, arguments);
+    },
+
+    /**
+     * @name Two.Vector#multiply
+     * @function
+     * @param {Two.Vector} v
+     * @description Multiply an object with x / y component values to the instance.
+     * @overloaded
+     */
+
+    /**
+     * @name Two.Vector#multiply
+     * @function
+     * @param {Number} v
+     * @description Multiply the **same** number to both x / y component values of the instance.
+     * @overloaded
+     */
+
+    /**
+     * @name Two.Vector#multiply
+     * @function
+     * @param {Number} x
+     * @param {Number} y
+     * @description Multiply `x` / `y` values to their respective component value on the instance.
+     * @overloaded
+     */
+    multiply: function(x, y) {
+      if (arguments.length <= 0) {
+        return this;
+      } else if (arguments.length <= 1) {
+        if (typeof x === 'number') {
+          this.x *= x;
+          this.y *= x;
+        } else if (x && typeof x.x === 'number' && typeof x.y === 'number') {
+          this.x *= x.x;
+          this.y *= x.y;
+        }
+      } else {
+        this.x *= x;
+        this.y *= y;
+      }
+      return this;
+    },
+
+    /**
+     * @name Two.Vector#multiplySelf
+     * @function
+     * @description Alias for {@link Two.Vector.multiply}.
+     */
+    multiplySelf: function(v) {
+      return this.multiply.apply(this, arguments);
+    },
+
+    /**
+     * @name Two.Vector#multiplyScalar
+     * @function
+     * @param {Number} s - The scalar to multiply by.
+     * @description Mulitiply the vector by a single number. Shorthand to call {@link Two.Vector#multiply} directly.
+     */
+    multiplyScalar: function(s) {
+      return this.multiply(s);
+    },
+
+    /**
+     * @name Two.Vector#divide
+     * @function
+     * @param {Two.Vector} v
+     * @description Divide an object with x / y component values to the instance.
+     * @overloaded
+     */
+
+    /**
+     * @name Two.Vector#divide
+     * @function
+     * @param {Number} v
+     * @description Divide the **same** number to both x / y component values of the instance.
+     * @overloaded
+     */
+
+    /**
+     * @name Two.Vector#divide
+     * @function
+     * @param {Number} x
+     * @param {Number} y
+     * @description Divide `x` / `y` values to their respective component value on the instance.
+     * @overloaded
+     */
+    divide: function(x, y) {
+      if (arguments.length <= 0) {
+        return this;
+      } else if (arguments.length <= 1) {
+        if (typeof x === 'number') {
+          this.x /= x;
+          this.y /= x;
+        } else if (x && typeof x.x === 'number' && typeof x.y === 'number') {
+          this.x /= x.x;
+          this.y /= x.y;
+        }
+      } else {
+        this.x /= x;
+        this.y /= y;
+      }
+      if (_.isNaN(this.x)) {
+        this.x = 0;
+      }
+      if (_.isNaN(this.y)) {
+        this.y = 0;
+      }
+      return this;
+    },
+
+    /**
+     * @name Two.Vector#divideSelf
+     * @function
+     * @description Alias for {@link Two.Vector.divide}.
+     */
+    divideSelf: function(v) {
+      return this.divide.apply(this, arguments);
+    },
+
+    /**
+     * @name Two.Vector#divideScalar
+     * @function
+     * @param {Number} s - The scalar to divide by.
+     * @description Divide the vector by a single number. Shorthand to call {@link Two.Vector#divide} directly.
+     */
+    divideScalar: function(s) {
+      return this.divide(s);
+    },
+
+    /**
+     * @name Two.Vector#negate
+     * @function
+     * @description Invert each component's sign value.
+     */
+    negate: function() {
+      return this.multiply(-1);
+    },
+
+    /**
+     * @name Two.Vector#negate
+     * @function
+     * @returns {Number}
+     * @description Get the [dot product](https://en.wikipedia.org/wiki/Dot_product) of the vector.
+     */
+    dot: function(v) {
+      return this.x * v.x + this.y * v.y;
+    },
+
+    /**
+     * @name Two.Vector#length
+     * @function
+     * @returns {Number}
+     * @description Get the length of a vector.
+     */
+    length: function() {
+      return Math.sqrt(this.lengthSquared());
+    },
+
+    /**
+     * @name Two.Vector#lengthSquared
+     * @function
+     * @returns {Number}
+     * @description Get the length of the vector to the power of two. Widely used as less expensive than {@link Two.Vector#length}, because it isn't square-rooting any numbers.
+     */
+    lengthSquared: function() {
+      return this.x * this.x + this.y * this.y;
+    },
+
+    /**
+     * @name Two.Vector#normalize
+     * @function
+     * @description Normalize the vector from negative one to one.
+     */
+    normalize: function() {
+      return this.divideScalar(this.length());
+    },
+
+    /**
+     * @name Two.Vector#distanceTo
+     * @function
+     * @returns {Number}
+     * @description Get the distance between two vectors.
+     */
+    distanceTo: function(v) {
+      return Math.sqrt(this.distanceToSquared(v));
+    },
+
+    /**
+     * @name Two.Vector#distanceToSquared
+     * @function
+     * @returns {Number}
+     * @description Get the distance between two vectors to the power of two. Widely used as less expensive than {@link Two.Vector#distanceTo}, because it isn't square-rooting any numbers.
+     */
+    distanceToSquared: function(v) {
+      var dx = this.x - v.x,
+          dy = this.y - v.y;
+      return dx * dx + dy * dy;
+    },
+
+    /**
+     * @name Two.Vector#setLength
+     * @function
+     * @param {Number} l - length to set vector to.
+     * @description Set the length of a vector.
+     */
+    setLength: function(l) {
+      return this.normalize().multiplyScalar(l);
+    },
+
+    /**
+     * @name Two.Vector#equals
+     * @function
+     * @param {Two.Vector} v - The vector to compare against.
+     * @param {Number} [eps=0.0001] - An options epsilon for precision.
+     * @returns {Boolean}
+     * @description Qualify if one vector roughly equal another. With a margin of error defined by epsilon.
+     */
+    equals: function(v, eps) {
+      eps = (typeof eps === 'undefined') ?  0.0001 : eps;
+      return (this.distanceTo(v) < eps);
+    },
+
+    /**
+     * @name Two.Vector#lerp
+     * @function
+     * @param {Two.Vector} v - The destination vector to step towards.
+     * @param {Number} t - The zero to one value of how close the current vector gets to the destination vector.
+     * @description Linear interpolate one vector to another by an amount `t` defined as a zero to one number.
+     * @see [Matt DesLauriers](https://twitter.com/mattdesl/status/1031305279227478016) has a good thread about this.
+     */
+    lerp: function(v, t) {
+      var x = (v.x - this.x) * t + this.x;
+      var y = (v.y - this.y) * t + this.y;
+      return this.set(x, y);
+    },
+
+    /**
+     * @name Two.Vector#isZero
+     * @function
+     * @param {Number} [eps=0.0001] - Optional precision amount to check against.
+     * @returns {Boolean}
+     * @description Check to see if vector is roughly zero, based on the `epsilon` precision value.
+     */
+    isZero: function(eps) {
+      eps = (typeof eps === 'undefined') ?  0.0001 : eps;
+      return (this.length() < eps);
+    },
+
+    /**
+     * @name Two.Vector#toString
+     * @function
+     * @returns {String}
+     * @description Return a comma-separated string of x, y value. Great for storing in a database.
+     */
+    toString: function() {
+      return this.x + ', ' + this.y;
+    },
+
+    /**
+     * @name Two.Vector#toObject
+     * @function
+     * @returns {Object}
+     * @description Return a JSON compatible plain object that represents the vector.
+     */
+    toObject: function() {
+      return { x: this.x, y: this.y };
+    },
+
+    /**
+     * @name Two.Vector#rotate
+     * @function
+     * @param {Number} Number - The amoun to rotate the vector by.
+     * @description Rotate a vector.
+     */
+    rotate: function(Number) {
+      var cos = Math.cos(Number);
+      var sin = Math.sin(Number);
+      this.x = this.x * cos - this.y * sin;
+      this.y = this.x * sin + this.y * cos;
+      return this;
+    }
+
+  });
+
+  // The same set of prototypical functions, but using the underlying
+  // getter or setter for `x` and `y` values. This set of functions
+  // is used instead of the previously documented ones above when
+  // Two.Vector#bind is invoked and there is event dispatching processed
+  // on x / y property changes.
+  var BoundProto = {
+
+    constructor: Vector,
+
+    set: function(x, y) {
+      this._x = x;
+      this._y = y;
+      return this.trigger(Events.Types.change);
+    },
+
+    copy: function(v) {
+      this._x = v.x;
+      this._y = v.y;
+      return this.trigger(Events.Types.change);
+    },
+
+    clear: function() {
+      this._x = 0;
+      this._y = 0;
+      return this.trigger(Events.Types.change);
+    },
+
+    clone: function() {
+      return new Vector(this._x, this._y);
+    },
+
+    add: function(x, y) {
+      if (arguments.length <= 0) {
+        return this;
+      } else if (arguments.length <= 1) {
+        if (typeof x === 'number') {
+          this._x += x;
+          this._y += x;
+        }  else if (x && typeof x.x === 'number' && typeof x.y === 'number') {
+          this._x += x.x;
+          this._y += x.y;
+        }
+      } else {
+        this._x += x;
+        this._y += y;
+      }
+      return this.trigger(Events.Types.change);
+    },
+
+    sub: function(x, y) {
+      if (arguments.length <= 0) {
+        return this;
+      } else if (arguments.length <= 1) {
+        if (typeof x === 'number') {
+          this._x -= x;
+          this._y -= x;
+        } else if (x && typeof x.x === 'number' && typeof x.y === 'number') {
+          this._x -= x.x;
+          this._y -= x.y;
+        }
+      } else {
+        this._x -= x;
+        this._y -= y;
+      }
+      return this.trigger(Events.Types.change);
+    },
+
+    multiply: function(x, y) {
+      if (arguments.length <= 0) {
+        return this;
+      } else if (arguments.length <= 1) {
+        if (typeof x === 'number') {
+          this._x *= x;
+          this._y *= x;
+        } else if (x && typeof x.x === 'number' && typeof x.y === 'number') {
+          this._x *= x.x;
+          this._y *= x.y;
+        }
+      } else {
+        this._x *= x;
+        this._y *= y;
+      }
+      return this.trigger(Events.Types.change);
+    },
+
+    divide: function(x, y) {
+      if (arguments.length <= 0) {
+        return this;
+      } else if (arguments.length <= 1) {
+        if (typeof x === 'number') {
+          this._x /= x;
+          this._y /= x;
+        } else if (x && typeof x.x === 'number' && typeof x.y === 'number') {
+          this._x /= x.x;
+          this._y /= x.y;
+        }
+      } else {
+        this._x /= x;
+        this._y /= y;
+      }
+      if (_.isNaN(this._x)) {
+        this._x = 0;
+      }
+      if (_.isNaN(this._y)) {
+        this._y = 0;
+      }
+      return this.trigger(Events.Types.change);
+    },
+
+    dot: function(v) {
+      return this._x * v.x + this._y * v.y;
+    },
+
+    lengthSquared: function() {
+      return this._x * this._x + this._y * this._y;
+    },
+
+    distanceToSquared: function(v) {
+      var dx = this._x - v.x,
+          dy = this._y - v.y;
+      return dx * dx + dy * dy;
+    },
+
+    lerp: function(v, t) {
+      var x = (v.x - this._x) * t + this._x;
+      var y = (v.y - this._y) * t + this._y;
+      return this.set(x, y);
+    },
+
+    toString: function() {
+      return this._x + ', ' + this._y;
+    },
+
+    toObject: function() {
+      return { x: this._x, y: this._y };
+    },
+
+    rotate: function (Number) {
+      var cos = Math.cos(Number);
+      var sin = Math.sin(Number);
+      this._x = this._x * cos - this._y * sin;
+      this._y = this._x * sin + this._y * cos;
+      return this;
+    }
+
+  };
+
+  var xgs = {
+    enumerable: true,
+    get: function() {
+      return this._x;
+    },
+    set: function(v) {
+      this._x = v;
+      this.trigger(Events.Types.change, 'x');
+    }
+  };
+
+  var ygs = {
+    enumerable: true,
+    get: function() {
+      return this._y;
+    },
+    set: function(v) {
+      this._y = v;
+      this.trigger(Events.Types.change, 'y');
+    }
+  };
+
+  Vector.MakeObservable(Vector.prototype);
+
+  /**
+   * @class
+   * @name Two.Anchor
+   * @param {Number} [x=0] - The x position of the root anchor point.
+   * @param {Number} [y=0] - The y position of the root anchor point.
+   * @param {Number} [lx=0] - The x position of the left handle point.
+   * @param {Number} [ly=0] - The y position of the left handle point.
+   * @param {Number} [rx=0] - The x position of the right handle point.
+   * @param {Number} [ry=0] - The y position of the right handle point.
+   * @param {String} [command=Two.Commands.move] - The command to describe how to render. Applicable commands are {@link Two.Commands}
+   * @extends Two.Vector
+   * @description An object that holds 3 {@link Two.Vector}s, the anchor point and its corresponding handles: `left` and `right`. In order to properly describe the bezier curve about the point there is also a command property to describe what type of drawing should occur when Two.js renders the anchors.
+   */
+  function Anchor(x, y, lx, ly, rx, ry, command) {
+
+    Vector.call(this, x, y);
+
+    this._broadcast = (function() {
+      this.trigger(Events.Types.change);
+    }).bind(this);
+
+    this._command = command || Commands.move;
+    this._relative = true;
+
+    var ilx = typeof lx === 'number';
+    var ily = typeof ly === 'number';
+    var irx = typeof rx === 'number';
+    var iry = typeof ry === 'number';
+
+    // Append the `controls` object only if control points are specified,
+    // keeping the Two.Anchor inline with a Two.Vector until it needs to
+    // evolve beyond those functions - e.g: a simple 2 component vector.
+    if (ilx || ily || irx || iry) {
+      Anchor.AppendCurveProperties(this);
+    }
+
+    if (ilx) {
+      this.controls.left.x = lx;
+    }
+    if (ily) {
+      this.controls.left.y = ly;
+    }
+    if (irx) {
+      this.controls.right.x = rx;
+    }
+    if (iry) {
+      this.controls.right.y = ry;
+    }
+
+  }
+
+  _.extend(Anchor, {
+
+    /**
+     * @name Two.Anchor.AppendCurveProperties
+     * @function
+     * @param {Two.Anchor} anchor - The instance to append the `control`object to.
+     * @description Adds the `controls` property as an object with `left` and `right` properties to access the bezier control handles that define how the curve is drawn. It also sets the `relative` property to `true` making vectors in the `controls` object relative to their corresponding root anchor point.
+     */
+    AppendCurveProperties: function(anchor) {
+
+      anchor.relative = true;
+
+      /**
+       * @name Two.Anchor#controls
+       * @property {Object} controls
+       * @description An plain object that holds the controls handles for a {@link Two.Anchor}.
+       */
+      anchor.controls = {};
+
+      /**
+       * @name Two.Anchor#controls#left
+       * @property {Two.Vector} left
+       * @description The "left" control point to define handles on a bezier curve.
+       */
+      anchor.controls.left = new Vector(0, 0);
+
+      /**
+       * @name Two.Anchor#controls#right
+       * @property {Two.Vector} right
+       * @description The "left" control point to define handles on a bezier curve.
+       */
+      anchor.controls.right = new Vector(0, 0);
+
+    },
+
+    /**
+     * @name Two.Anchor.MakeObservable
+     * @function
+     * @param {Object} object - The object to make observable.
+     * @description Convenience function to apply observable qualities of a {@link Two.Anchor} to any object. Handy if you'd like to extend the {@link Two.Anchor} class on a custom class.
+     */
+    MakeObservable: function(object) {
+
+      /**
+       * @name Two.Anchor#command
+       * @property {Two.Commands}
+       * @description A draw command associated with the anchor point.
+       */
+      Object.defineProperty(object, 'command', {
+
+        enumerable: true,
+
+        get: function() {
+          return this._command;
+        },
+
+        set: function(c) {
+          this._command = c;
+          if (this._command === Commands.curve && !_.isObject(this.controls)) {
+            Anchor.AppendCurveProperties(this);
+          }
+          this.trigger(Events.Types.change);
+        }
+
+      });
+
+      /**
+       * @name Two.Anchor#relative
+       * @property {Boolean}
+       * @description A boolean to render control points relative to the root anchor point or in global coordinate-space to the rest of the scene.
+       */
+      Object.defineProperty(object, 'relative', {
+
+        enumerable: true,
+
+        get: function() {
+          return this._relative;
+        },
+
+        set: function(b) {
+          if (this._relative != b) {
+            this._relative = !!b;
+            this.trigger(Events.Types.change);
+          }
+        }
+
+      });
+
+      _.extend(object, Vector.prototype, AnchorProto);
+
+      // Make it possible to bind and still have the Anchor specific
+      // inheritance from Two.Vector. In this case relying on `Two.Vector`
+      // to do much of the heavy event-listener binding / unbinding.
+      object.bind = object.on = function() {
+        var bound = this._bound;
+        Vector.prototype.bind.apply(this, arguments);
+        if (!bound) {
+          _.extend(this, AnchorProto);
+        }
+      };
+
+    }
+
+  });
+
+  var AnchorProto = {
+
+    constructor: Anchor,
+
+    /**
+     * @name Two.Anchor#listen
+     * @function
+     * @description Convenience method used mainly by {@link Two.Path#vertices} to listen and propagate changes from control points up to their respective anchors and further if necessary.
+     */
+    listen: function() {
+
+      if (!_.isObject(this.controls)) {
+        Anchor.AppendCurveProperties(this);
+      }
+
+      this.controls.left.bind(Events.Types.change, this._broadcast);
+      this.controls.right.bind(Events.Types.change, this._broadcast);
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Anchor#ignore
+     * @function
+     * @description Convenience method used mainly by {@link Two.Path#vertices} to ignore changes from a specific anchor's control points.
+     */
+    ignore: function() {
+
+      this.controls.left.unbind(Events.Types.change, this._broadcast);
+      this.controls.right.unbind(Events.Types.change, this._broadcast);
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Anchor#copy
+     * @function
+     * @param {Two.Anchor} v - The anchor to apply values to.
+     * @description Copy the properties of one {@link Two.Anchor} onto another.
+     */
+    copy: function(v) {
+
+      this.x = v.x;
+      this.y = v.y;
+
+      if (typeof v.command === 'string') {
+        this.command = v.command;
+      }
+      if (_.isObject(v.controls)) {
+        if (!_.isObject(this.controls)) {
+          Anchor.AppendCurveProperties(this);
+        }
+        // TODO: Do we need to listen here?
+        this.controls.left.copy(v.controls.left);
+        this.controls.right.copy(v.controls.right);
+      }
+      if (typeof v.relative === 'boolean') {
+        this.relative = v.relative;
+      }
+
+      // TODO: Hack for `Two.Commands.arc`
+      if (this.command === Commands.arc) {
+        this.rx = v.rx;
+        this.ry = v.ry;
+        this.xAxisRotation = v.xAxisRotation;
+        this.largeArcFlag = v.largeArcFlag;
+        this.sweepFlag = v.sweepFlag;
+      }
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Anchor#clone
+     * @function
+     * @returns {Two.Anchor}
+     * @description Create a new {@link Two.Anchor}, set all its values to the current instance and return it for use.
+     */
+    clone: function() {
+
+      var controls = this.controls;
+
+      var clone = new Anchor(
+        this.x,
+        this.y,
+        controls && controls.left.x,
+        controls && controls.left.y,
+        controls && controls.right.x,
+        controls && controls.right.y,
+        this.command
+      );
+      clone.relative = this._relative;
+      return clone;
+
+    },
+
+    /**
+     * @name Two.Anchor#toObject
+     * @function
+     * @returns {Object} - An object with properties filled out to mirror {@link Two.Anchor}.
+     * @description Create a JSON compatible plain object of the current instance. Intended for use with storing values in a database.
+     */
+    toObject: function() {
+      var o = {
+        x: this.x,
+        y: this.y
+      };
+      if (this._command) {
+        o.command = this._command;
+      }
+      if (this._relative) {
+        o.relative = this._relative;
+      }
+      if (this.controls) {
+        o.controls = {
+          left: this.controls.left.toObject(),
+          right: this.controls.right.toObject()
+        };
+      }
+      return o;
+    },
+
+    /**
+     * @name Two.Anchor#toString
+     * @function
+     * @returns {String} - A String with comma-separated values reflecting the various values on the current instance.
+     * @description Create a string form of the current instance. Intended for use with storing values in a database. This is lighter to store than the JSON compatible {@link Two.Anchor#toObject}.
+     */
+    toString: function() {
+      if (!this.controls) {
+        return [this._x, this._y].join(', ');
+      }
+      return [this._x, this._y, this.controls.left.x, this.controls.left.y,
+        this.controls.right.x, this.controls.right.y, this._command,
+        this._relative ? 1 : 0].join(', ');
+    }
+
+  };
+
+  Anchor.MakeObservable(Anchor.prototype);
+
+  var count = 0;
+
+  var Constants = {
+
+    /**
+     * @name Two.nextFrameID
+     * @property {Number}
+     * @description The id of the next requestAnimationFrame function.
+     */
+    nextFrameID: null,
+
+    // Primitive
+
+    /**
+     * @name Two.Types
+     * @property {Object} - The different rendering types available in the library.
+     */
+    Types: {
+      webgl: 'WebGLRenderer',
+      svg: 'SVGRenderer',
+      canvas: 'CanvasRenderer'
+    },
+
+    /**
+     * @name Two.Version
+     * @property {String} - The current working version of the library.
+     */
+    Version: 'v0.7.6',
+
+    /**
+     * @name Two.PublishDate
+     * @property {String} - The automatically generated publish date in the build process to verify version release candidates.
+     */
+    PublishDate: '2021-06-08T20:19:33.699Z',
+
+    /**
+     * @name Two.Identifier
+     * @property {String} - String prefix for all Two.js object's ids. This trickles down to SVG ids.
+     */
+    Identifier: 'two-',
+
+    /**
+     * @name Two.Resolution
+     * @property {Number} - Default amount of vertices to be used for interpreting Arcs and ArcSegments.
+     */
+    Resolution: 12,
+
+    /**
+     * @name Two.AutoCalculateImportedMatrices
+     * @property {Boolean} - When importing SVGs through the {@link two#interpret} and {@link two#load}, this boolean determines whether Two.js infers and then overrides the exact transformation matrix of the reference SVG.
+     * @nota-bene `false` copies the exact transformation matrix values, but also sets the path's `matrix.manual = true`.
+     */
+    AutoCalculateImportedMatrices: true,
+
+    /**
+     * @name Two.Instances
+     * @property {Two[]} - Registered list of all Two.js instances in the current session.
+     */
+    Instances: [],
+
+    /**
+     * @function Two.uniqueId
+     * @description Simple method to access an incrementing value. Used for `id` allocation on all Two.js objects.
+     * @returns {Number} Ever increasing Number.
+     */
+    uniqueId: function() {
+      return count++;
+    }
+
+  };
+
+  var HALF_PI$3 = Math.PI / 2;
+
+  /**
+   * @name Two.Utils.Curve
+   * @property {Object} - Additional utility constant variables related to curve math and calculations.
+   */
+  var Curve = {
+
+    CollinearityEpsilon: Math.pow(10, -30),
+
+    RecursionLimit: 16,
+
+    CuspLimit: 0,
+
+    Tolerance: {
+      distance: 0.25,
+      angle: 0,
+      epsilon: Number.EPSILON
+    },
+
+    // Lookup tables for abscissas and weights with values for n = 2 .. 16.
+    // As values are symmetric, only store half of them and adapt algorithm
+    // to factor in symmetry.
+    abscissas: [
+      [  0.5773502691896257645091488],
+      [0,0.7745966692414833770358531],
+      [  0.3399810435848562648026658,0.8611363115940525752239465],
+      [0,0.5384693101056830910363144,0.9061798459386639927976269],
+      [  0.2386191860831969086305017,0.6612093864662645136613996,0.9324695142031520278123016],
+      [0,0.4058451513773971669066064,0.7415311855993944398638648,0.9491079123427585245261897],
+      [  0.1834346424956498049394761,0.5255324099163289858177390,0.7966664774136267395915539,0.9602898564975362316835609],
+      [0,0.3242534234038089290385380,0.6133714327005903973087020,0.8360311073266357942994298,0.9681602395076260898355762],
+      [  0.1488743389816312108848260,0.4333953941292471907992659,0.6794095682990244062343274,0.8650633666889845107320967,0.9739065285171717200779640],
+      [0,0.2695431559523449723315320,0.5190961292068118159257257,0.7301520055740493240934163,0.8870625997680952990751578,0.9782286581460569928039380],
+      [  0.1252334085114689154724414,0.3678314989981801937526915,0.5873179542866174472967024,0.7699026741943046870368938,0.9041172563704748566784659,0.9815606342467192506905491],
+      [0,0.2304583159551347940655281,0.4484927510364468528779129,0.6423493394403402206439846,0.8015780907333099127942065,0.9175983992229779652065478,0.9841830547185881494728294],
+      [  0.1080549487073436620662447,0.3191123689278897604356718,0.5152486363581540919652907,0.6872929048116854701480198,0.8272013150697649931897947,0.9284348836635735173363911,0.9862838086968123388415973],
+      [0,0.2011940939974345223006283,0.3941513470775633698972074,0.5709721726085388475372267,0.7244177313601700474161861,0.8482065834104272162006483,0.9372733924007059043077589,0.9879925180204854284895657],
+      [  0.0950125098376374401853193,0.2816035507792589132304605,0.4580167776572273863424194,0.6178762444026437484466718,0.7554044083550030338951012,0.8656312023878317438804679,0.9445750230732325760779884,0.9894009349916499325961542]
+    ],
+
+    weights: [
+      [1],
+      [0.8888888888888888888888889,0.5555555555555555555555556],
+      [0.6521451548625461426269361,0.3478548451374538573730639],
+      [0.5688888888888888888888889,0.4786286704993664680412915,0.2369268850561890875142640],
+      [0.4679139345726910473898703,0.3607615730481386075698335,0.1713244923791703450402961],
+      [0.4179591836734693877551020,0.3818300505051189449503698,0.2797053914892766679014678,0.1294849661688696932706114],
+      [0.3626837833783619829651504,0.3137066458778872873379622,0.2223810344533744705443560,0.1012285362903762591525314],
+      [0.3302393550012597631645251,0.3123470770400028400686304,0.2606106964029354623187429,0.1806481606948574040584720,0.0812743883615744119718922],
+      [0.2955242247147528701738930,0.2692667193099963550912269,0.2190863625159820439955349,0.1494513491505805931457763,0.0666713443086881375935688],
+      [0.2729250867779006307144835,0.2628045445102466621806889,0.2331937645919904799185237,0.1862902109277342514260976,0.1255803694649046246346943,0.0556685671161736664827537],
+      [0.2491470458134027850005624,0.2334925365383548087608499,0.2031674267230659217490645,0.1600783285433462263346525,0.1069393259953184309602547,0.0471753363865118271946160],
+      [0.2325515532308739101945895,0.2262831802628972384120902,0.2078160475368885023125232,0.1781459807619457382800467,0.1388735102197872384636018,0.0921214998377284479144218,0.0404840047653158795200216],
+      [0.2152638534631577901958764,0.2051984637212956039659241,0.1855383974779378137417166,0.1572031671581935345696019,0.1215185706879031846894148,0.0801580871597602098056333,0.0351194603317518630318329],
+      [0.2025782419255612728806202,0.1984314853271115764561183,0.1861610000155622110268006,0.1662692058169939335532009,0.1395706779261543144478048,0.1071592204671719350118695,0.0703660474881081247092674,0.0307532419961172683546284],
+      [0.1894506104550684962853967,0.1826034150449235888667637,0.1691565193950025381893121,0.1495959888165767320815017,0.1246289712555338720524763,0.0951585116824927848099251,0.0622535239386478928628438,0.0271524594117540948517806]
+    ]
+
+  };
+
+  /**
+   * @name Two.Utils.getComponentOnCubicBezier
+   * @function
+   * @param {Number} t - Zero-to-one value describing what percentage to calculate.
+   * @param {Number} a - The firt point's component value.
+   * @param {Number} b - The first point's bezier component value.
+   * @param {Number} c - The second point's bezier component value.
+   * @param {Number} d - The second point's component value.
+   * @returns {Number} The coordinate value for a specific component along a cubic bezier curve by `t`.
+   */
+  var getComponentOnCubicBezier = function(t, a, b, c, d) {
+    var k = 1 - t;
+    return (k * k * k * a) + (3 * k * k * t * b) + (3 * k * t * t * c) +
+        (t * t * t * d);
+  };
+
+  /**
+   * @name Two.Utils.subdivide
+   * @function
+   * @param {Number} x1 - x position of first anchor point.
+   * @param {Number} y1 - y position of first anchor point.
+   * @param {Number} x2 - x position of first anchor point's "right" bezier handle.
+   * @param {Number} y2 - y position of first anchor point's "right" bezier handle.
+   * @param {Number} x3 - x position of second anchor point's "left" bezier handle.
+   * @param {Number} y3 - y position of second anchor point's "left" bezier handle.
+   * @param {Number} x4 - x position of second anchor point.
+   * @param {Number} y4 - y position of second anchor point.
+   * @param {Number} [limit=Two.Utils.Curve.RecursionLimit] - The amount of vertices to create by subdividing.
+   * @returns {Anchor[]} A list of anchor points ordered in between `x1`, `y1` and `x4`, `y4`
+   * @description Given 2 points (a, b) and corresponding control point for each return an array of points that represent points plotted along the curve. The number of returned points is determined by `limit`.
+   */
+  var subdivide = function(x1, y1, x2, y2, x3, y3, x4, y4, limit) {
+
+    limit = limit || Curve.RecursionLimit;
+    var amount = limit + 1;
+
+    // TODO: Abstract 0.001 to a limiting variable
+    // Don't recurse if the end points are identical
+    if (Math.abs(x1 - x4) < 0.001 && Math.abs(y1 - y4) < 0.001) {
+      return [new Anchor(x4, y4)];
+    }
+
+    var result = [];
+
+    for (var i = 0; i < amount; i++) {
+      var t = i / amount;
+      var x = getComponentOnCubicBezier(t, x1, x2, x3, x4);
+      var y = getComponentOnCubicBezier(t, y1, y2, y3, y4);
+      result.push(new Anchor(x, y));
+    }
+
+    return result;
+
+  };
+
+  /**
+   * @name Two.Utils.getCurveLength
+   * @function
+   * @param {Number} x1 - x position of first anchor point.
+   * @param {Number} y1 - y position of first anchor point.
+   * @param {Number} x2 - x position of first anchor point's "right" bezier handle.
+   * @param {Number} y2 - y position of first anchor point's "right" bezier handle.
+   * @param {Number} x3 - x position of second anchor point's "left" bezier handle.
+   * @param {Number} y3 - y position of second anchor point's "left" bezier handle.
+   * @param {Number} x4 - x position of second anchor point.
+   * @param {Number} y4 - y position of second anchor point.
+   * @param {Number} [limit=Two.Utils.Curve.RecursionLimit] - The amount of vertices to create by subdividing.
+   * @returns {Number} The length of a curve.
+   * @description Given 2 points (a, b) and corresponding control point for each, return a float that represents the length of the curve using Gauss-Legendre algorithm. Limit iterations of calculation by `limit`.
+   */
+  var getCurveLength$1 = function(x1, y1, x2, y2, x3, y3, x4, y4, limit) {
+
+    // TODO: Better / fuzzier equality check
+    // Linear calculation
+    if (x1 === x2 && y1 === y2 && x3 === x4 && y3 === y4) {
+      var dx = x4 - x1;
+      var dy = y4 - y1;
+      return Math.sqrt(dx * dx + dy * dy);
+    }
+
+    // Calculate the coefficients of a Bezier derivative.
+    var ax = 9 * (x2 - x3) + 3 * (x4 - x1),
+      bx = 6 * (x1 + x3) - 12 * x2,
+      cx = 3 * (x2 - x1),
+
+      ay = 9 * (y2 - y3) + 3 * (y4 - y1),
+      by = 6 * (y1 + y3) - 12 * y2,
+      cy = 3 * (y2 - y1);
+
+    var integrand = function(t) {
+      // Calculate quadratic equations of derivatives for x and y
+      var dx = (ax * t + bx) * t + cx,
+        dy = (ay * t + by) * t + cy;
+      return Math.sqrt(dx * dx + dy * dy);
+    };
+
+    return integrate(
+      integrand, 0, 1, limit || Curve.RecursionLimit
+    );
+
+  };
+
+  /**
+   * @name Two.Utils.getCurveBoundingBox
+   * @function
+   * @param {Number} x1 - x position of first anchor point.
+   * @param {Number} y1 - y position of first anchor point.
+   * @param {Number} x2 - x position of first anchor point's "right" bezier handle.
+   * @param {Number} y2 - y position of first anchor point's "right" bezier handle.
+   * @param {Number} x3 - x position of second anchor point's "left" bezier handle.
+   * @param {Number} y3 - y position of second anchor point's "left" bezier handle.
+   * @param {Number} x4 - x position of second anchor point.
+   * @param {Number} y4 - y position of second anchor point.
+   * @returns {Object} Object contains min and max `x` / `y` bounds.
+   * @see {@link https://github.com/adobe-webplatform/Snap.svg/blob/master/src/path.js#L856}
+   */
+  var getCurveBoundingBox = function(x1, y1, x2, y2, x3, y3, x4, y4) {
+
+    var tvalues = [];
+    var bounds = [[], []];
+    var a, b, c, t, t1, t2, b2ac, sqrtb2ac;
+
+    for (var i = 0; i < 2; ++i) {
+        if (i == 0) {
+          b = 6 * x1 - 12 * x2 + 6 * x3;
+          a = -3 * x1 + 9 * x2 - 9 * x3 + 3 * x4;
+          c = 3 * x2 - 3 * x1;
+        } else {
+          b = 6 * y1 - 12 * y2 + 6 * y3;
+          a = -3 * y1 + 9 * y2 - 9 * y3 + 3 * y4;
+          c = 3 * y2 - 3 * y1;
+        }
+        if (Math.abs(a) < 1e-12) {
+          if (Math.abs(b) < 1e-12) {
+            continue;
+          }
+          t = -c / b;
+          if (0 < t && t < 1) {
+            tvalues.push(t);
+          }
+          continue;
+        }
+        b2ac = b * b - 4 * c * a;
+        sqrtb2ac = Math.sqrt(b2ac);
+        if (b2ac < 0) {
+          continue;
+        }
+        t1 = (-b + sqrtb2ac) / (2 * a);
+        if (0 < t1 && t1 < 1) {
+          tvalues.push(t1);
+        }
+        t2 = (-b - sqrtb2ac) / (2 * a);
+        if (0 < t2 && t2 < 1) {
+          tvalues.push(t2);
+        }
+    }
+
+    var j = tvalues.length;
+    var jlen = j;
+    var mt;
+
+    while (j--) {
+      t = tvalues[j];
+      mt = 1 - t;
+      bounds[0][j] = mt * mt * mt * x1 + 3 * mt * mt * t * x2 + 3 * mt * t * t * x3 + t * t * t * x4;
+      bounds[1][j] = mt * mt * mt * y1 + 3 * mt * mt * t * y2 + 3 * mt * t * t * y3 + t * t * t * y4;
+    }
+
+    bounds[0][jlen] = x1;
+    bounds[1][jlen] = y1;
+    bounds[0][jlen + 1] = x4;
+    bounds[1][jlen + 1] = y4;
+    bounds[0].length = bounds[1].length = jlen + 2;
+
+    return {
+      min: { x: Math.min.apply(0, bounds[0]), y: Math.min.apply(0, bounds[1]) },
+      max: { x: Math.max.apply(0, bounds[0]), y: Math.max.apply(0, bounds[1]) }
+    };
+
+  };
+
+  /**
+   * @name Two.Utils.integrate
+   * @function
+   * @param {Function} f
+   * @param {Number} a
+   * @param {Number} b
+   * @param {Number} n
+   * @description Integration for `getCurveLength` calculations.
+   * @see [Paper.js](@link https://github.com/paperjs/paper.js/blob/master/src/util/Numerical.js#L101)
+   */
+  var integrate = function(f, a, b, n) {
+    var x = Curve.abscissas[n - 2],
+      w = Curve.weights[n - 2],
+      A = 0.5 * (b - a),
+      B = A + a,
+      i = 0,
+      m = (n + 1) >> 1,
+      sum = n & 1 ? w[i++] * f(B) : 0; // Handle odd n
+    while (i < m) {
+      var Ax = A * x[i];
+      sum += w[i++] * (f(B + Ax) + f(B - Ax));
+    }
+    return A * sum;
+  };
+
+  /**
+   * @name Two.Utils.getCurveFromPoints
+   * @function
+   * @param {Anchor[]} points
+   * @param {Boolean} closed
+   * @description Sets the bezier handles on {@link Anchor}s in the `points` list with estimated values to create a catmull-rom like curve. Used by {@link Two.Path#plot}.
+   */
+  var getCurveFromPoints = function(points, closed) {
+
+    var l = points.length, last = l - 1;
+
+    for (var i = 0; i < l; i++) {
+
+      var point = points[i];
+
+      if (!_.isObject(point.controls)) {
+        Anchor.AppendCurveProperties(point);
+      }
+
+      var prev = closed ? mod(i - 1, l) : Math.max(i - 1, 0);
+      var next = closed ? mod(i + 1, l) : Math.min(i + 1, last);
+
+      var a = points[prev];
+      var b = point;
+      var c = points[next];
+      getControlPoints(a, b, c);
+
+      b.command = i === 0 ? Commands.move : Commands.curve;
+
+    }
+
+  };
+
+  /**
+   * @name Two.Utils.getControlPoints
+   * @function
+   * @param {Anchor} a
+   * @param {Anchor} b
+   * @param {Anchor} c
+   * @returns {Anchor} Returns the passed middle point `b`.
+   * @description Given three coordinates set the control points for the middle, b, vertex based on its position with the adjacent points.
+   */
+  var getControlPoints = function(a, b, c) {
+
+    var a1 = Vector.angleBetween(a, b);
+    var a2 = Vector.angleBetween(c, b);
+
+    var d1 = Vector.distanceBetween(a, b);
+    var d2 = Vector.distanceBetween(c, b);
+
+    var mid = (a1 + a2) / 2;
+
+    // TODO: Issue 73
+    if (d1 < 0.0001 || d2 < 0.0001) {
+      if (typeof b.relative === 'boolean' && !b.relative) {
+        b.controls.left.copy(b);
+        b.controls.right.copy(b);
+      }
+      return b;
+    }
+
+    d1 *= 0.33; // Why 0.33?
+    d2 *= 0.33;
+
+    if (a2 < a1) {
+      mid += HALF_PI$3;
+    } else {
+      mid -= HALF_PI$3;
+    }
+
+    b.controls.left.x = Math.cos(mid) * d1;
+    b.controls.left.y = Math.sin(mid) * d1;
+
+    mid -= Math.PI;
+
+    b.controls.right.x = Math.cos(mid) * d2;
+    b.controls.right.y = Math.sin(mid) * d2;
+
+    if (typeof b.relative === 'boolean' && !b.relative) {
+      b.controls.left.x += b.x;
+      b.controls.left.y += b.y;
+      b.controls.right.x += b.x;
+      b.controls.right.y += b.y;
+    }
+
+    return b;
+
+  };
+
+  /**
+   * @name Two.Utils.getReflection
+   * @function
+   * @param {Vector} a
+   * @param {Vector} b
+   * @param {Boolean} [relative=false]
+   * @returns {Vector} New {@link Vector} that represents the reflection point.
+   * @description Get the reflection of a point `b` about point `a`. Where `a` is in absolute space and `b` is relative to `a`.
+   * @see {@link http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes}
+   */
+  var getReflection = function(a, b, relative) {
+
+    return new Vector(
+      2 * a.x - (b.x + a.x) - (relative ? a.x : 0),
+      2 * a.y - (b.y + a.y) - (relative ? a.y : 0)
+    );
+
+  };
+
+  /**
+   * @name Two.Utils.getAnchorsFromArcData
+   * @function
+   * @param {Vector} center
+   * @param {Number} xAxisRotation
+   * @param {Number} rx - x radius
+   * @param {Number} ry - y radius
+   * @param {Number} ts
+   * @param {Number} td
+   * @param {Boolean} [ccw=false] - Set path traversal to counter-clockwise
+   */
+  var getAnchorsFromArcData = function(center, xAxisRotation, rx, ry, ts, td, ccw) {
+
+    var resolution = Constants.Resolution;
+
+    for (var i = 0; i < resolution; i++) {
+      var pct = (i + 1) / resolution;
+      if (ccw) {
+        pct = 1 - pct;
+      }
+
+      var theta = pct * td + ts;
+      var x = rx * Math.cos(theta);
+      var y = ry * Math.sin(theta);
+
+      // x += center.x;
+      // y += center.y;
+
+      var anchor = new Anchor(x, y);
+      Anchor.AppendCurveProperties(anchor);
+      anchor.command = Commands.line;
+    }
+
+  };
+
+  var Curves = /*#__PURE__*/Object.freeze({
+    __proto__: null,
+    Curve: Curve,
+    getComponentOnCubicBezier: getComponentOnCubicBezier,
+    subdivide: subdivide,
+    getCurveLength: getCurveLength$1,
+    getCurveBoundingBox: getCurveBoundingBox,
+    integrate: integrate,
+    getCurveFromPoints: getCurveFromPoints,
+    getControlPoints: getControlPoints,
+    getReflection: getReflection,
+    getAnchorsFromArcData: getAnchorsFromArcData
+  });
+
+  var devicePixelRatio = root$1.devicePixelRatio || 1;
+
+  var getBackingStoreRatio = function(ctx) {
+    return ctx.webkitBackingStorePixelRatio ||
+    ctx.mozBackingStorePixelRatio ||
+    ctx.msBackingStorePixelRatio ||
+    ctx.oBackingStorePixelRatio ||
+    ctx.backingStorePixelRatio || 1;
+  };
+
+  /**
+   * @name Two.Utils.getRatio
+   * @function
+   * @param {CanvasRenderingContext2D} ctx
+   * @returns {Number} The ratio of a unit in Two.js to the pixel density of a session's screen.
+   * @see [High DPI Rendering](http://www.html5rocks.com/en/tutorials/canvas/hidpi/)
+   */
+  var getRatio = function(ctx) {
+    return devicePixelRatio / getBackingStoreRatio(ctx);
+  };
+
+  // Constants
+
+  var cos$5 = Math.cos, sin$5 = Math.sin, tan = Math.tan;
+  var array = [];
+
+  /**
+   * @name Two.Matrix
+   * @class
+   * @param {Number} [a=1] - The value for element at the first column and first row.
+   * @param {Number} [b=0] - The value for element at the second column and first row.
+   * @param {Number} [c=0] - The value for element at the third column and first row.
+   * @param {Number} [d=0] - The value for element at the first column and second row.
+   * @param {Number} [e=1] - The value for element at the second column and second row.
+   * @param {Number} [f=0] - The value for element at the third column and second row.
+   * @param {Number} [g=0] - The value for element at the first column and third row.
+   * @param {Number} [h=0] - The value for element at the second column and third row.
+   * @param {Number} [i=1] - The value for element at the third column and third row.
+   * @description A class to store 3 x 3 transformation matrix information. In addition to storing data `Two.Matrix` has suped up methods for commonplace mathematical operations.
+   * @nota-bene Order is based on how to construct transformation strings for the browser.
+   */
+  function Matrix(a, b, c, d, e, f) {
+
+    /**
+     * @name Two.Matrix#elements
+     * @property {Number[]} - The underlying data stored as an array.
+     */
+    this.elements = new NumArray(9);
+
+    var elements = a;
+    if (!Array.isArray(elements)) {
+      elements = Array.prototype.slice.call(arguments);
+    }
+
+    // initialize the elements with default values.
+    this.identity();
+
+    if (elements.length > 0) {
+      this.set(elements);
+    }
+
+  }
+
+  setMatrix(Matrix);
+
+  _.extend(Matrix, {
+
+    /**
+     * @name Two.Matrix.Identity
+     * @property {Number[]} - A stored reference to the default value of a 3 x 3 matrix.
+     */
+    Identity: [
+      1, 0, 0,
+      0, 1, 0,
+      0, 0, 1
+    ],
+
+    /**
+     * @name Two.Matrix.Multiply
+     * @function
+     * @param {Two.Matrix} A
+     * @param {Two.Matrix} B
+     * @param {Two.Matrix} [C] - An optional matrix to apply the multiplication to.
+     * @returns {Two.Matrix} - If an optional `C` matrix isn't passed then a new one is created and returned.
+     * @description Multiply two matrices together and return the result.
+     */
+    Multiply: function(A, B, C) {
+
+      if (B.length <= 3) { // Multiply Vector
+
+        var x, y, z, e = A;
+
+        var a = B[0] || 0,
+            b = B[1] || 0,
+            c = B[2] || 0;
+
+        // Go down rows first
+        // a, d, g, b, e, h, c, f, i
+
+        x = e[0] * a + e[1] * b + e[2] * c;
+        y = e[3] * a + e[4] * b + e[5] * c;
+        z = e[6] * a + e[7] * b + e[8] * c;
+
+        return { x: x, y: y, z: z };
+
+      }
+
+      var A0 = A[0], A1 = A[1], A2 = A[2];
+      var A3 = A[3], A4 = A[4], A5 = A[5];
+      var A6 = A[6], A7 = A[7], A8 = A[8];
+
+      var B0 = B[0], B1 = B[1], B2 = B[2];
+      var B3 = B[3], B4 = B[4], B5 = B[5];
+      var B6 = B[6], B7 = B[7], B8 = B[8];
+
+      C = C || new NumArray(9);
+
+      C[0] = A0 * B0 + A1 * B3 + A2 * B6;
+      C[1] = A0 * B1 + A1 * B4 + A2 * B7;
+      C[2] = A0 * B2 + A1 * B5 + A2 * B8;
+      C[3] = A3 * B0 + A4 * B3 + A5 * B6;
+      C[4] = A3 * B1 + A4 * B4 + A5 * B7;
+      C[5] = A3 * B2 + A4 * B5 + A5 * B8;
+      C[6] = A6 * B0 + A7 * B3 + A8 * B6;
+      C[7] = A6 * B1 + A7 * B4 + A8 * B7;
+      C[8] = A6 * B2 + A7 * B5 + A8 * B8;
+
+      return C;
+
+    }
+
+  });
+
+  _.extend(Matrix.prototype, Events, {
+
+    constructor: Matrix,
+
+    /**
+     * @name Two.Matrix#manual
+     * @property {Boolean} - Determines whether Two.js automatically calculates the values for the matrix or if the developer intends to manage the matrix.
+     * @nota-bene - Setting to `true` nullifies {@link Two.Shape#translation}, {@link Two.Shape#rotation}, and {@link Two.Shape#scale}.
+     */
+    manual: false,
+
+    /**
+     * @name Two.Matrix#set
+     * @function
+     * @param {Number} a - The value for element at the first column and first row.
+     * @param {Number} b - The value for element at the second column and first row.
+     * @param {Number} c - The value for element at the third column and first row.
+     * @param {Number} d - The value for element at the first column and second row.
+     * @param {Number} e - The value for element at the second column and second row.
+     * @param {Number} f - The value for element at the third column and second row.
+     * @param {Number} g - The value for element at the first column and third row.
+     * @param {Number} h - The value for element at the second column and third row.
+     * @param {Number} i - The value for element at the third column and third row.
+     * @description Set an array of values onto the matrix. Order described in {@link Two.Matrix}.
+     */
+
+      /**
+      * @name Two.Matrix#set
+      * @function
+      * @param {Number[]} a - The array of elements to apply.
+      * @description Set an array of values onto the matrix. Order described in {@link Two.Matrix}.
+      */
+    set: function(a, b, c, d, e, f, g, h, i) {
+
+      var elements;
+
+      if (typeof b === 'undefined') {
+        elements = a;
+        a = elements[0];
+        b = elements[1];
+        c = elements[2];
+        d = elements[3];
+        e = elements[4];
+        f = elements[5];
+        g = elements[6];
+        h = elements[7];
+        i = elements[8];
+      }
+
+      this.elements[0] = a;
+      this.elements[1] = b;
+      this.elements[2] = c;
+      this.elements[3] = d;
+      this.elements[4] = e;
+      this.elements[5] = f;
+      this.elements[6] = g;
+      this.elements[7] = h;
+      this.elements[8] = i;
+
+      return this.trigger(Events.Types.change);
+
+    },
+
+    /**
+     * @name Two.Matrix#copy
+     * @function
+     * @description Copy the matrix of one to the current instance.
+     */
+    copy: function(m) {
+
+      this.elements[0] = m.elements[0];
+      this.elements[1] = m.elements[1];
+      this.elements[2] = m.elements[2];
+      this.elements[3] = m.elements[3];
+      this.elements[4] = m.elements[4];
+      this.elements[5] = m.elements[5];
+      this.elements[6] = m.elements[6];
+      this.elements[7] = m.elements[7];
+      this.elements[8] = m.elements[8];
+
+      this.manual = m.manual;
+
+      return this.trigger(Events.Types.change);
+
+    },
+
+    /**
+     * @name Two.Matrix#identity
+     * @function
+     * @description Turn matrix to the identity, like resetting.
+     */
+    identity: function() {
+
+      this.elements[0] = Matrix.Identity[0];
+      this.elements[1] = Matrix.Identity[1];
+      this.elements[2] = Matrix.Identity[2];
+      this.elements[3] = Matrix.Identity[3];
+      this.elements[4] = Matrix.Identity[4];
+      this.elements[5] = Matrix.Identity[5];
+      this.elements[6] = Matrix.Identity[6];
+      this.elements[7] = Matrix.Identity[7];
+      this.elements[8] = Matrix.Identity[8];
+
+      return this.trigger(Events.Types.change);
+
+    },
+
+    /**
+     * @name Two.Matrix#multiply
+     * @function
+     * @param {Number} a - The scalar to be multiplied.
+     * @description Multiply all components of the matrix against a single scalar value.
+     * @overloaded
+     */
+
+    /**
+     * @name Two.Matrix#multiply
+     * @function
+     * @param {Number} a - The x component to be multiplied.
+     * @param {Number} b - The y component to be multiplied.
+     * @param {Number} c - The z component to be multiplied.
+     * @description Multiply all components of a matrix against a 3 component vector.
+     * @overloaded
+     */
+
+    /**
+     * @name Two.Matrix#multiply
+     * @function
+     * @param {Number} a - The value at the first column and first row of the matrix to be multiplied.
+     * @param {Number} b - The value at the second column and first row of the matrix to be multiplied.
+     * @param {Number} c - The value at the third column and first row of the matrix to be multiplied.
+     * @param {Number} d - The value at the first column and second row of the matrix to be multiplied.
+     * @param {Number} e - The value at the second column and second row of the matrix to be multiplied.
+     * @param {Number} f - The value at the third column and second row of the matrix to be multiplied.
+     * @param {Number} g - The value at the first column and third row of the matrix to be multiplied.
+     * @param {Number} h - The value at the second column and third row of the matrix to be multiplied.
+     * @param {Number} i - The value at the third column and third row of the matrix to be multiplied.
+     * @description Multiply all components of a matrix against another matrix.
+     * @overloaded
+     */
+    multiply: function(a, b, c, d, e, f, g, h, i) {
+
+      // Multiply scalar
+
+      if (typeof b === 'undefined') {
+
+        this.elements[0] *= a;
+        this.elements[1] *= a;
+        this.elements[2] *= a;
+        this.elements[3] *= a;
+        this.elements[4] *= a;
+        this.elements[5] *= a;
+        this.elements[6] *= a;
+        this.elements[7] *= a;
+        this.elements[8] *= a;
+
+        return this.trigger(Events.Types.change);
+
+      }
+
+      if (typeof d === 'undefined') { // Multiply Vector
+
+        var x, y, z;
+        a = a || 0;
+        b = b || 0;
+        c = c || 0;
+        e = this.elements;
+
+        // Go down rows first
+        // a, d, g, b, e, h, c, f, i
+
+        x = e[0] * a + e[1] * b + e[2] * c;
+        y = e[3] * a + e[4] * b + e[5] * c;
+        z = e[6] * a + e[7] * b + e[8] * c;
+
+        return { x: x, y: y, z: z };
+
+      }
+
+      // Multiple matrix
+
+      var A = this.elements;
+      var B = [a, b, c, d, e, f, g, h, i];
+
+      var A0 = A[0], A1 = A[1], A2 = A[2];
+      var A3 = A[3], A4 = A[4], A5 = A[5];
+      var A6 = A[6], A7 = A[7], A8 = A[8];
+
+      var B0 = B[0], B1 = B[1], B2 = B[2];
+      var B3 = B[3], B4 = B[4], B5 = B[5];
+      var B6 = B[6], B7 = B[7], B8 = B[8];
+
+      this.elements[0] = A0 * B0 + A1 * B3 + A2 * B6;
+      this.elements[1] = A0 * B1 + A1 * B4 + A2 * B7;
+      this.elements[2] = A0 * B2 + A1 * B5 + A2 * B8;
+
+      this.elements[3] = A3 * B0 + A4 * B3 + A5 * B6;
+      this.elements[4] = A3 * B1 + A4 * B4 + A5 * B7;
+      this.elements[5] = A3 * B2 + A4 * B5 + A5 * B8;
+
+      this.elements[6] = A6 * B0 + A7 * B3 + A8 * B6;
+      this.elements[7] = A6 * B1 + A7 * B4 + A8 * B7;
+      this.elements[8] = A6 * B2 + A7 * B5 + A8 * B8;
+
+      return this.trigger(Events.Types.change);
+
+    },
+
+    /**
+     * @name Two.Matrix#inverse
+     * @function
+     * @param {Two.Matrix} [out] - The optional matrix to apply the inversion to.
+     * @description Return an inverted version of the matrix. If no optional one is passed a new matrix is created and returned.
+     */
+    inverse: function(out) {
+
+      var a = this.elements;
+      out = out || new Matrix();
+
+      var a00 = a[0], a01 = a[1], a02 = a[2];
+      var a10 = a[3], a11 = a[4], a12 = a[5];
+      var a20 = a[6], a21 = a[7], a22 = a[8];
+
+      var b01 = a22 * a11 - a12 * a21;
+      var b11 = -a22 * a10 + a12 * a20;
+      var b21 = a21 * a10 - a11 * a20;
+
+      // Calculate the determinant
+      var det = a00 * b01 + a01 * b11 + a02 * b21;
+
+      if (!det) {
+        return null;
+      }
+
+      det = 1.0 / det;
+
+      out.elements[0] = b01 * det;
+      out.elements[1] = (-a22 * a01 + a02 * a21) * det;
+      out.elements[2] = (a12 * a01 - a02 * a11) * det;
+      out.elements[3] = b11 * det;
+      out.elements[4] = (a22 * a00 - a02 * a20) * det;
+      out.elements[5] = (-a12 * a00 + a02 * a10) * det;
+      out.elements[6] = b21 * det;
+      out.elements[7] = (-a21 * a00 + a01 * a20) * det;
+      out.elements[8] = (a11 * a00 - a01 * a10) * det;
+
+      return out;
+
+    },
+
+    /**
+     * @name Two.Matrix#scale
+     * @function
+     * @param {Number} scale - The one dimensional scale to apply to the matrix.
+     * @description Uniformly scale the transformation matrix.
+     */
+
+    /**
+     * @name Two.Matrix#scale
+     * @function
+     * @param {Number} sx - The horizontal scale factor.
+     * @param {Number} sy - The vertical scale factor
+     * @description Scale the transformation matrix in two dimensions.
+     */
+    scale: function(sx, sy) {
+
+      var l = arguments.length;
+      if (l <= 1) {
+        sy = sx;
+      }
+
+      return this.multiply(sx, 0, 0, 0, sy, 0, 0, 0, 1);
+
+    },
+
+    /**
+     * @name Two.Matrix#rotate
+     * @function
+     * @param {Number} Number - The amount to rotate in Number.
+     * @description Rotate the matrix.
+     */
+    rotate: function(Number) {
+
+      var c = cos$5(Number);
+      var s = sin$5(Number);
+
+      return this.multiply(c, -s, 0, s, c, 0, 0, 0, 1);
+
+    },
+
+    /**
+     * @name Two.Matrix#translate
+     * @function
+     * @param {Number} x - The horizontal translation value to apply.
+     * @param {Number} y - The vertical translation value to apply.
+     * @description Translate the matrix.
+     */
+    translate: function(x, y) {
+
+      return this.multiply(1, 0, x, 0, 1, y, 0, 0, 1);
+
+    },
+
+    /**
+     * @name Two.Matrix#skewX
+     * @function
+     * @param {Number} Number - The amount to skew in Number.
+     * @description Skew the matrix by an angle in the x axis direction.
+     */
+    skewX: function(Number) {
+
+      var a = tan(Number);
+
+      return this.multiply(1, a, 0, 0, 1, 0, 0, 0, 1);
+
+    },
+
+    /**
+     * @name Two.Matrix#skewY
+     * @function
+     * @param {Number} Number - The amount to skew in Number.
+     * @description Skew the matrix by an angle in the y axis direction.
+     */
+    skewY: function(Number) {
+
+      var a = tan(Number);
+
+      return this.multiply(1, 0, 0, a, 1, 0, 0, 0, 1);
+
+    },
+
+    /**
+     * @name Two.Matrix#toString
+     * @function
+     * @param {Boolean} [fullMatrix=false] - Return the full 9 elements of the matrix or just 6 for 2D transformations.
+     * @returns {String} - The transformation matrix as a 6 component string separated by spaces.
+     * @description Create a transform string. Used for the Two.js rendering APIs.
+     */
+    toString: function(fullMatrix) {
+
+      array.length = 0;
+      this.toTransformArray(fullMatrix, array);
+
+      return array.map(toFixed).join(' ');
+
+    },
+
+    /**
+     * @name Two.Matrix#toTransformArray
+     * @function
+     * @param {Boolean} [fullMatrix=false] - Return the full 9 elements of the matrix or just 6 in the format for 2D transformations.
+     * @param {Number[]} [output] - An array empty or otherwise to apply the values to.
+     * @description Create a transform array. Used for the Two.js rendering APIs.
+     */
+    toTransformArray: function(fullMatrix, output) {
+
+      var elements = this.elements;
+      var hasOutput = !!output;
+
+      var a = elements[0];
+      var b = elements[1];
+      var c = elements[2];
+      var d = elements[3];
+      var e = elements[4];
+      var f = elements[5];
+
+      if (fullMatrix) {
+
+        var g = elements[6];
+        var h = elements[7];
+        var i = elements[8];
+
+        if (hasOutput) {
+          output[0] = a;
+          output[1] = d;
+          output[2] = g;
+          output[3] = b;
+          output[4] = e;
+          output[5] = h;
+          output[6] = c;
+          output[7] = f;
+          output[8] = i;
+          return;
+        }
+
+        return [
+          a, d, g, b, e, h, c, f, i
+        ];
+      }
+
+      if (hasOutput) {
+        output[0] = a;
+        output[1] = d;
+        output[2] = b;
+        output[3] = e;
+        output[4] = c;
+        output[5] = f;
+        return;
+      }
+
+      return [
+        a, d, b, e, c, f  // Specific format see LN:19
+      ];
+
+    },
+
+    /**
+     * @name Two.Matrix#toArray
+     * @function
+     * @param {Boolean} [fullMatrix=false] - Return the full 9 elements of the matrix or just 6 for 2D transformations.
+     * @param {Number[]} [output] - An array empty or otherwise to apply the values to.
+     * @description Create a transform array. Used for the Two.js rendering APIs.
+     */
+    toArray: function(fullMatrix, output) {
+
+      var elements = this.elements;
+      var hasOutput = !!output;
+
+      var a = elements[0];
+      var b = elements[1];
+      var c = elements[2];
+      var d = elements[3];
+      var e = elements[4];
+      var f = elements[5];
+
+      if (fullMatrix) {
+
+        var g = elements[6];
+        var h = elements[7];
+        var i = elements[8];
+
+        if (hasOutput) {
+          output[0] = a;
+          output[1] = b;
+          output[2] = c;
+          output[3] = d;
+          output[4] = e;
+          output[5] = f;
+          output[6] = g;
+          output[7] = h;
+          output[8] = i;
+          return;
+        }
+
+        return [
+          a, b, c, d, e, f, g, h, i
+        ];
+      }
+
+      if (hasOutput) {
+        output[0] = a;
+        output[1] = b;
+        output[2] = c;
+        output[3] = d;
+        output[4] = e;
+        output[5] = f;
+        return;
+      }
+
+      return [
+        a, b, c, d, e, f
+      ];
+
+    },
+
+    /**
+     * @name Two.Matrix#toObject
+     * @function
+     * @description Create a JSON compatible object that represents information of the matrix.
+     */
+    toObject: function() {
+      return {
+        elements: this.toArray(true),
+        manual: !!this.manual
+      };
+    },
+
+    /**
+     * @name Two.Matrix#clone
+     * @function
+     * @description Clone the current matrix.
+     */
+    clone: function() {
+
+      return new Matrix().copy(this);
+
+    }
+
+  });
+
+  /**
+   * @name Two.Shape
+   * @class
+   * @extends Two.Events
+   * @description The foundational transformation object for the Two.js scenegraph.
+   */
+  function Shape() {
+
+    /**
+     * @name Two.Shape#renderer
+     * @property {Object}
+     * @description Object access to store relevant renderer specific variables. Warning: manipulating this object can create unintended consequences.
+     * @nota-bene With the {@link Two.SvgRenderer} you can access the underlying SVG element created via `shape.renderer.elem`.
+     */
+    this.renderer = {};
+    this._renderer.flagMatrix = Shape.FlagMatrix.bind(this);
+    this.isShape = true;
+
+    /**
+     * @name Two.Shape#id
+     * @property {String} - Session specific unique identifier.
+     * @nota-bene In the {@link Two.SvgRenderer} change this to change the underlying SVG element's id too.
+     */
+    this.id = Constants.Identifier + Constants.uniqueId();
+
+    /**
+     * @name Two.Shape#classList
+     * @property {String[]}
+     * @description A list of class strings stored if imported / interpreted  from an SVG element.
+     */
+    this.classList = [];
+
+    /**
+     * @name Two.Shape#matrix
+     * @property {Two.Matrix}
+     * @description The transformation matrix of the shape.
+     * @nota-bene {@link Two.Shape#translation}, {@link Two.Shape#rotation}, {@link Two.Shape#scale}, {@link Two.Shape#skewX}, and {@link Two.Shape#skewY} apply their values to the matrix when changed. The matrix is what is sent to the renderer to be drawn.
+     */
+    this.matrix = new Matrix();
+
+    /**
+     * @name Two.Shape#translation
+     * @property {Two.Vector} - The x and y value for where the shape is placed relative to its parent.
+     */
+    this.translation = new Vector();
+
+    /**
+     * @name Two.Shape#rotation
+     * @property {Number} - The value in Number for how much the shape is rotated relative to its parent.
+     */
+    this.rotation = 0;
+
+    /**
+     * @name Two.Shape#scale
+     * @property {Number} - The value for how much the shape is scaled relative to its parent.
+     * @nota-bene This value can be replaced with a {@link Two.Vector} to do non-uniform scaling. e.g: `shape.scale = new Two.Vector(2, 1);`
+     */
+    this.scale = 1;
+
+    /**
+     * @name Two.Shape#skewX
+     * @property {Number} - The value in Number for how much the shape is skewed relative to its parent.
+     * @description Skew the shape by an angle in the x axis direction.
+     */
+    this.skewX = 0;
+
+    /**
+     * @name Two.Shape#skewY
+     * @property {Number} - The value in Number for how much the shape is skewed relative to its parent.
+     * @description Skew the shape by an angle in the y axis direction.
+     */
+    this.skewY = 0;
+
+  }
+
+  _.extend(Shape, {
+
+    /**
+     * @name Two.Shape.FlagMatrix
+     * @function
+     * @description Utility function used in conjunction with event handlers to update the flagMatrix of a shape.
+     */
+    FlagMatrix: function() {
+      this._flagMatrix = true;
+    },
+
+    /**
+     * @name Two.Shape.MakeObservable
+     * @function
+     * @param {Object} object - The object to make observable.
+     * @description Convenience function to apply observable qualities of a {@link Two.Shape} to any object. Handy if you'd like to extend the {@link Two.Shape} class on a custom class.
+     */
+    MakeObservable: function(object) {
+
+      var translation = {
+        enumerable: false,
+        get: function() {
+          return this._translation;
+        },
+        set: function(v) {
+          if (this._translation) {
+            this._translation.unbind(Events.Types.change, this._renderer.flagMatrix);
+          }
+          this._translation = v;
+          this._translation.bind(Events.Types.change, this._renderer.flagMatrix);
+          Shape.FlagMatrix.call(this);
+        }
+      };
+
+      Object.defineProperty(object, 'translation', translation);
+      Object.defineProperty(object, 'position', translation);
+
+      Object.defineProperty(object, 'rotation', {
+        enumerable: true,
+        get: function() {
+          return this._rotation;
+        },
+        set: function(v) {
+          this._rotation = v;
+          this._flagMatrix = true;
+        }
+      });
+
+      Object.defineProperty(object, 'scale', {
+        enumerable: true,
+        get: function() {
+          return this._scale;
+        },
+        set: function(v) {
+
+          if (this._scale instanceof Vector) {
+            this._scale.unbind(Events.Types.change, this._renderer.flagMatrix);
+          }
+
+          this._scale = v;
+
+          if (this._scale instanceof Vector) {
+            this._scale.bind(Events.Types.change, this._renderer.flagMatrix);
+          }
+
+          this._flagMatrix = true;
+          this._flagScale = true;
+
+        }
+      });
+
+      Object.defineProperty(object, 'skewX', {
+        enumerable: true,
+        get: function() {
+          return this._skewX;
+        },
+        set: function(v) {
+          this._skewX = v;
+          this._flagMatrix = true;
+        }
+      });
+
+      Object.defineProperty(object, 'skewY', {
+        enumerable: true,
+        get: function() {
+          return this._skewY;
+        },
+        set: function(v) {
+          this._skewY = v;
+          this._flagMatrix = true;
+        }
+      });
+
+      Object.defineProperty(object, 'matrix', {
+        enumerable: true,
+        get: function() {
+          return this._matrix;
+        },
+        set: function(v) {
+          this._matrix = v;
+          this._flagMatrix = true;
+        }
+      });
+
+      Object.defineProperty(object, 'id', {
+        enumerable: true,
+        get: function() {
+          return this._id;
+        },
+        set: function(v) {
+          var id = this._id;
+          if (v === this._id) {
+            return;
+          }
+          this._id = v;
+          this._flagId = true;
+          if (this.parent) {
+            delete this.parent.children.ids[id];
+            this.parent.children.ids[this._id] = this;
+          }
+        }
+      });
+
+      Object.defineProperty(object, 'className', {
+
+        enumerable: true,
+
+        get: function() {
+          return this._className;
+        },
+
+        set: function(v) {
+
+          this._flagClassName = this._className !== v;
+
+          if (this._flagClassName) {
+
+            var prev = this._className.split(/\s+?/);
+            var dest = v.split(/\s+?/);
+
+            for (var i = 0; i < prev.length; i++) {
+              var className = prev[i];
+              var index = Array.prototype.indexOf.call(this.classList, className);
+              if (index >= 0) {
+                this.classList.splice(index, 1);
+              }
+            }
+
+            this.classList = this.classList.concat(dest);
+
+          }
+
+          this._className = v;
+
+        }
+
+      });
+
+      Object.defineProperty(object, 'renderer', {
+
+        enumerable: false,
+
+        get: function() {
+          return this._renderer;
+        },
+
+        set: function(obj) {
+          this._renderer = obj;
+        }
+
+      });
+
+    }
+
+  });
+
+  _.extend(Shape.prototype, Events, {
+
+    constructor: Shape,
+
+    // Flags
+
+    /**
+     * @name Two.Shape#_id
+     * @private
+     * @property {Boolean} - Determines whether the id needs updating.
+     */
+    _flagId: true,
+
+    /**
+     * @name Two.Shape#_flagMatrix
+     * @private
+     * @property {Boolean} - Determines whether the matrix needs updating.
+     */
+    _flagMatrix: true,
+
+    /**
+     * @name Two.Shape#_flagScale
+     * @private
+     * @property {Boolean} - Determines whether the scale needs updating.
+     */
+    _flagScale: false,
+
+    // _flagMask: false,
+    // _flagClip: false,
+
+    /**
+     * @name Two.Shape#_flagClassName
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Group#className} need updating.
+     */
+    _flagClassName: false,
+
+    // Underlying Properties
+
+    _id: '',
+
+    /**
+     * @name Two.Shape#_translation
+     * @private
+     * @property {Two.Vector} - The translation values as a {@link Two.Vector}.
+     */
+    _translation: null,
+
+    /**
+     * @name Two.Shape#_rotation
+     * @private
+     * @property {Number} - The rotation value in Number.
+     */
+    _rotation: 0,
+
+    /**
+     * @name Two.Shape#_translation
+     * @private
+     * @property {Two.Vector} - The translation values as a {@link Two.Vector}.
+     */
+    _scale: 1,
+
+    /**
+     * @name Two.Shape#_skewX
+     * @private
+     * @property {Number} - The rotation value in Number.
+     */
+    _skewX: 0,
+
+    /**
+     * @name Two.Shape#_skewY
+     * @private
+     * @property {Number} - The rotation value in Number.
+     */
+    _skewY: 0,
+
+    /**
+     * @name Two.Shape#className
+     * @property {String} - A class to be applied to the element to be compatible with CSS styling.
+     * @nota-bene Only available for the SVG renderer.
+     */
+    _className: '',
+
+    /**
+     * @name Two.Shape#addTo
+     * @function
+     * @param {Two.Group} group - The parent the shape adds itself to.
+     * @description Convenience method to add itself to the scenegraph.
+     */
+    addTo: function(group) {
+      group.add(this);
+      return this;
+    },
+
+    /**
+     * @name Two.Shape#clone
+     * @function
+     * @param {Two.Group} [parent] - Optional argument to automatically add the shape to a scenegraph.
+     * @returns {Two.Shape}
+     * @description Create a new {@link Two.Shape} with the same values as the current shape.
+     */
+    clone: function(parent) {
+
+      var clone = new Shape();
+
+      clone.translation.copy(this.translation);
+      clone.rotation = this.rotation;
+      clone.scale = this.scale;
+      clone.skewX = this.skewX;
+      clone.skewY = this.skewY;
+
+      if (this.matrix.manual) {
+        clone.matrix.copy(this.matrix);
+      }
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone._update();
+
+    },
+
+    /**
+     * @name Two.Shape#_update
+     * @function
+     * @private
+     * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+     * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+     * @nota-bene Try not to call this method more than once a frame.
+     */
+    _update: function(bubbles) {
+
+      if (!this._matrix.manual && this._flagMatrix) {
+
+        this._matrix
+          .identity()
+          .translate(this.translation.x, this.translation.y);
+
+          if (this._scale instanceof Vector) {
+            this._matrix.scale(this._scale.x, this._scale.y);
+          } else {
+            this._matrix.scale(this._scale);
+          }
+
+          this._matrix.rotate(this.rotation);
+          this._matrix.skewX(this.skewX);
+          this._matrix.skewY(this.skewY);
+      }
+
+      if (bubbles) {
+        if (this.parent && this.parent._update) {
+          this.parent._update();
+        }
+      }
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Shape#flagReset
+     * @function
+     * @private
+     * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+     */
+    flagReset: function() {
+
+      this._flagId = this._flagMatrix = this._flagScale =
+        this._flagClassName = false;
+
+      return this;
+
+    }
+
+  });
+
+  Shape.MakeObservable(Shape.prototype);
+
+  /**
+   * @name Two.Collection
+   * @class
+   * @extends Two.Events
+   * @description An `Array` like object with additional event propagation on actions. `pop`, `shift`, and `splice` trigger `removed` events. `push`, `unshift`, and `splice` with more than 2 arguments trigger 'inserted'. Finally, `sort` and `reverse` trigger `order` events.
+   */
+  function Collection() {
+
+    Array.call(this);
+
+    if (arguments[0] && Array.isArray(arguments[0])) {
+      if (arguments[0].length > 0) {
+        Array.prototype.push.apply(this, arguments[0]);
+      }
+    } else if (arguments.length > 0) {
+      Array.prototype.push.apply(this, arguments);
+    }
+
+  }
+
+  Collection.prototype = new Array();
+
+  _.extend(Collection.prototype, Events, {
+
+    constructor: Collection,
+
+    pop: function() {
+      var popped = Array.prototype.pop.apply(this, arguments);
+      this.trigger(Events.Types.remove, [popped]);
+      return popped;
+    },
+
+    shift: function() {
+      var shifted = Array.prototype.shift.apply(this, arguments);
+      this.trigger(Events.Types.remove, [shifted]);
+      return shifted;
+    },
+
+    push: function() {
+      var pushed = Array.prototype.push.apply(this, arguments);
+      this.trigger(Events.Types.insert, arguments);
+      return pushed;
+    },
+
+    unshift: function() {
+      var unshifted = Array.prototype.unshift.apply(this, arguments);
+      this.trigger(Events.Types.insert, arguments);
+      return unshifted;
+    },
+
+    splice: function() {
+      var spliced = Array.prototype.splice.apply(this, arguments);
+      var inserted;
+
+      this.trigger(Events.Types.remove, spliced);
+
+      if (arguments.length > 2) {
+        inserted = this.slice(arguments[0], arguments[0] + arguments.length - 2);
+        this.trigger(Events.Types.insert, inserted);
+        this.trigger(Events.Types.order);
+      }
+      return spliced;
+    },
+
+    sort: function() {
+      Array.prototype.sort.apply(this, arguments);
+      this.trigger(Events.Types.order);
+      return this;
+    },
+
+    reverse: function() {
+      Array.prototype.reverse.apply(this, arguments);
+      this.trigger(Events.Types.order);
+      return this;
+    },
+
+    indexOf: function() {
+      return Array.prototype.indexOf.apply(this, arguments);
+    }
+
+  });
+
+  /**
+   * @class
+   * @name Two.Group.Children
+   * @extends Two.Collection
+   * @description A children collection which is accesible both by index and by object `id`.
+   */
+  function Children(children) {
+
+    Collection.apply(this, arguments);
+
+    Object.defineProperty(this, '_events', {
+      value : {},
+      enumerable: false
+    });
+
+    /**
+     * @name Two.Group.Children#ids
+     * @property {Object} - Map of all elements in the list keyed by `id`s.
+     */
+    this.ids = {};
+
+    this.attach(
+      Array.isArray(children) ? children : Array.prototype.slice.call(arguments)
+    );
+
+    this.on(Events.Types.insert, this.attach);
+    this.on(Events.Types.remove, this.detach);
+
+  }
+
+  Children.prototype = new Collection();
+
+  _.extend(Children.prototype, {
+
+    constructor: Children,
+
+    /**
+     * @function
+     * @name Two.Group.Children#attach
+     * @param {Two.Shape[]} children - The objects which extend {@link Two.Shape} to be added.
+     * @description Adds elements to the `ids` map.
+     */
+    attach: function(children) {
+      for (var i = 0; i < children.length; i++) {
+        var child = children[i];
+        if (child && child.id) {
+          this.ids[child.id] = child;
+        }
+      }
+      return this;
+    },
+
+    /**
+     * @function
+     * @name Two.Group.Children#detach
+     * @param {Two.Shape[]} children - The objects which extend {@link Two.Shape} to be removed.
+     * @description Removes elements to the `ids` map.
+     */
+    detach: function(children) {
+      for (var i = 0; i < children.length; i++) {
+        delete this.ids[children[i].id];
+      }
+      return this;
+    }
+
+  });
+
+  // Constants
+
+  var min$3 = Math.min, max$3 = Math.max;
+
+  /**
+   * @name Two.Group
+   * @class
+   * @extends Two.Shape
+   * @param {Two.Shape[]} [children] - A list of objects that inherit {@link Two.Shape}. For instance, the array could be a {@link Two.Path}, {@link Two.Text}, and {@link Two.RoundedRectangle}.
+   * @description This is the primary class for grouping objects that are then drawn in Two.js. In Illustrator this is a group, in After Effects it would be a Null Object. Whichever the case, the `Two.Group` contains a transformation matrix and commands to style its children, but it by itself doesn't render to the screen.
+   * @nota-bene The {@link Two#scene} is an instance of `Two.Group`.
+   */
+  function Group(children) {
+
+    Shape.call(this, true);
+
+    this._renderer.type = 'group';
+
+    /**
+     * @name Two.Group#additions
+     * @property {Two.Shape[]}
+     * @description An automatically updated list of children that need to be appended to the renderer's scenegraph.
+     */
+    this.additions = [];
+
+    /**
+     * @name Two.Group#subtractions
+     * @property {Two.Shape[]}
+     * @description An automatically updated list of children that need to be removed from the renderer's scenegraph.
+     */
+    this.subtractions = [];
+
+    /**
+     * @name Two.Group#children
+     * @property {Two.Group.Children}
+     * @description A list of all the children in the scenegraph.
+     * @nota-bene Ther order of this list indicates the order each element is rendered to the screen.
+     */
+    this.children = Array.isArray(children) ? children : Array.prototype.slice.call(arguments);
+
+  }
+
+  _.extend(Group, {
+
+    Children: Children,
+
+    /**
+     * @name Two.Group.InsertChildren
+     * @function
+     * @param {Two.Shape[]} children - The objects to be inserted.
+     * @description Cached method to let renderers know children have been added to a {@link Two.Group}.
+     */
+    InsertChildren: function(children) {
+      for (var i = 0; i < children.length; i++) {
+        replaceParent.call(this, children[i], this);
+      }
+    },
+
+    /**
+     * @name Two.Group.RemoveChildren
+     * @function
+     * @param {Two.Shape[]} children - The objects to be removed.
+     * @description Cached method to let renderers know children have been removed from a {@link Two.Group}.
+     */
+    RemoveChildren: function(children) {
+      for (var i = 0; i < children.length; i++) {
+        replaceParent.call(this, children[i]);
+      }
+    },
+
+    /**
+     * @name Two.Group.OrderChildren
+     * @function
+     * @description Cached method to let renderers know order has been updated on a {@link Two.Group}.
+     */
+    OrderChildren: function(children) {
+      this._flagOrder = true;
+    },
+
+    /**
+     * @name Two.Group.Properties
+     * @property {String[]} - A list of properties that are on every {@link Two.Group}.
+     */
+    Properties: [
+      'fill',
+      'stroke',
+      'linewidth',
+      'cap',
+      'join',
+      'miter',
+
+      'closed',
+      'curved',
+      'automatic'
+    ],
+
+    /**
+     * @name Two.Group.MakeObservable
+     * @function
+     * @param {Object} object - The object to make observable.
+     * @description Convenience function to apply observable qualities of a {@link Two.Group} to any object. Handy if you'd like to extend the {@link Two.Group} class on a custom class.
+     */
+    MakeObservable: function(object) {
+
+      var properties = Group.Properties;
+
+      Object.defineProperty(object, 'visible', {
+
+        enumerable: true,
+
+        get: function() {
+          return this._visible;
+        },
+
+        set: function(v) {
+          this._flagVisible = this._visible !== v || this._flagVisible;
+          this._visible = v;
+        }
+
+      });
+
+      Object.defineProperty(object, 'opacity', {
+
+        enumerable: true,
+
+        get: function() {
+          return this._opacity;
+        },
+
+        set: function(v) {
+          this._flagOpacity = this._opacity !== v || this._flagOpacity;
+          this._opacity = v;
+        }
+
+      });
+
+      Object.defineProperty(object, 'beginning', {
+
+        enumerable: true,
+
+        get: function() {
+          return this._beginning;
+        },
+
+        set: function(v) {
+          this._flagBeginning = this._beginning !== v || this._flagBeginning;
+          this._beginning = v;
+        }
+
+      });
+
+      Object.defineProperty(object, 'ending', {
+
+        enumerable: true,
+
+        get: function() {
+          return this._ending;
+        },
+
+        set: function(v) {
+          this._flagEnding = this._ending !== v || this._flagEnding;
+          this._ending = v;
+        }
+
+      });
+
+      Object.defineProperty(object, 'length', {
+
+        enumerable: true,
+
+        get: function() {
+          if (this._flagLength || this._length <= 0) {
+            this._length = 0;
+            if (!this.children) {
+              return this._length;
+            }
+            for (var i = 0; i < this.children.length; i++) {
+              var child = this.children[i];
+              this._length += child.length;
+            }
+          }
+          return this._length;
+        }
+
+      });
+
+      Shape.MakeObservable(object);
+      Group.MakeGetterSetters(object, properties);
+
+      Object.defineProperty(object, 'children', {
+
+        enumerable: true,
+
+        get: function() {
+          return this._children;
+        },
+
+        set: function(children) {
+
+          var insertChildren = Group.InsertChildren.bind(this);
+          var removeChildren = Group.RemoveChildren.bind(this);
+          var orderChildren = Group.OrderChildren.bind(this);
+
+          if (this._children) {
+            this._children.unbind();
+            if (this._children.length > 0) {
+              removeChildren(this._children);
+            }
+          }
+
+          this._children = new Children(children);
+          this._children.bind(Events.Types.insert, insertChildren);
+          this._children.bind(Events.Types.remove, removeChildren);
+          this._children.bind(Events.Types.order, orderChildren);
+
+          if (children.length > 0) {
+            insertChildren(children);
+          }
+
+        }
+
+      });
+
+      Object.defineProperty(object, 'mask', {
+
+        enumerable: true,
+
+        get: function() {
+          return this._mask;
+        },
+
+        set: function(v) {
+          this._mask = v;
+          this._flagMask = true;
+          if (!v.clip) {
+            v.clip = true;
+          }
+        }
+
+      });
+
+    },
+
+    /**
+     * @name Two.Group.MakeGetterSetters
+     * @function
+     * @param {Two.Group} group - The group to apply getters and setters.
+     * @param {Object} properties - A key / value object containing properties to inherit.
+     * @description Convenience method to apply getter / setter logic on an array of properties. Used in {@link Two.Group.MakeObservable}.
+     */
+    MakeGetterSetters: function(group, properties) {
+
+      if (!Array.isArray(properties)) {
+        properties = [properties];
+      }
+
+      _.each(properties, function(k) {
+        Group.MakeGetterSetter(group, k);
+      });
+
+    },
+
+    /**
+     * @name Two.Group.MakeGetterSetter
+     * @function
+     * @param {Two.Group} group - The group to apply getters and setters.
+     * @param {String} key - The key which will become a property on the group.
+     * @description Convenience method to apply getter / setter logic specific to how `Two.Group`s trickle down styles to their children. Used in {@link Two.Group.MakeObservable}.
+     */
+    MakeGetterSetter: function(group, key) {
+
+      var secret = '_' + key;
+
+      Object.defineProperty(group, key, {
+
+        enumerable: true,
+
+        get: function() {
+          return this[secret];
+        },
+
+        set: function(v) {
+          this[secret] = v;
+          // Trickle down styles
+          for (var i = 0; i < this.children.length; i++) {
+            var child = this.children[i];
+            child[key] = v;
+          }
+        }
+
+      });
+
+    }
+
+  });
+
+  _.extend(Group.prototype, Shape.prototype, {
+
+    constructor: Group,
+
+    // Flags
+    // http://en.wikipedia.org/wiki/Flag
+
+    /**
+     * @name Two.Group#_flagAdditions
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Group#additions} needs updating.
+     */
+    _flagAdditions: false,
+
+    /**
+     * @name Two.Group#_flagSubtractions
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Group#subtractions} needs updating.
+     */
+    _flagSubtractions: false,
+
+    /**
+     * @name Two.Group#_flagOrder
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Group#order} needs updating.
+     */
+    _flagOrder: false,
+
+    /**
+     * @name Two.Group#_flagVisible
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Group#visible} needs updating.
+     */
+
+    /**
+     * @name Two.Group#_flagOpacity
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Group#opacity} needs updating.
+     */
+    _flagOpacity: true,
+
+    /**
+     * @name Two.Group#_flagBeginning
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Group#beginning} needs updating.
+     */
+    _flagBeginning: false,
+
+    /**
+     * @name Two.Group#_flagEnding
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Group#ending} needs updating.
+     */
+    _flagEnding: false,
+
+    /**
+     * @name Two.Group#_flagLength
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Group#length} needs updating.
+     */
+    _flagLength: false,
+
+    /**
+     * @name Two.Group#_flagMask
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Group#mask} needs updating.
+     */
+    _flagMask: false,
+
+    // Underlying Properties
+
+    /**
+     * @name Two.Group#fill
+     * @property {(String|Two.Gradient|Two.Texture)} - The value of what all child shapes should be filled in with.
+     * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/color_value} for more information on CSS's colors as `String`.
+     */
+    _fill: '#fff',
+
+    /**
+     * @name Two.Group#stroke
+     * @property {(String|Two.Gradient|Two.Texture)} - The value of what all child shapes should be outlined in with.
+     * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/color_value} for more information on CSS's colors as `String`.
+     */
+    _stroke: '#000',
+
+    /**
+     * @name Two.Group#linewidth
+     * @property {Number} - The thickness in pixels of the stroke for all child shapes.
+     */
+    _linewidth: 1.0,
+
+    /**
+     * @name Two.Group#opacity
+     * @property {Number} - The opaqueness of all child shapes.
+     * @nota-bene Becomes multiplied by the individual child's opacity property.
+     */
+    _opacity: 1.0,
+
+    /**
+     * @name Two.Group#visible
+     * @property {Boolean} - Display the path or not.
+     * @nota-bene For {@link Two.CanvasRenderer} and {@link Two.WebGLRenderer} when set to false all updating is disabled improving performance dramatically with many objects in the scene.
+     */
+    _visible: true,
+
+    /**
+     * @name Two.Group#cap
+     * @property {String}
+     * @see {@link https://www.w3.org/TR/SVG11/painting.html#StrokeLinecapProperty}
+     */
+    _cap: 'round',
+
+    /**
+     * @name Two.Group#join
+     * @property {String}
+     * @see {@link https://www.w3.org/TR/SVG11/painting.html#StrokeLinejoinProperty}
+     */
+    _join: 'round',
+
+    /**
+     * @name Two.Group#miter
+     * @property {String}
+     * @see {@link https://www.w3.org/TR/SVG11/painting.html#StrokeMiterlimitProperty}
+     */
+    _miter: 4,
+
+    /**
+     * @name Two.Group#closed
+     * @property {Boolean} - Determines whether a final line is drawn between the final point in the `vertices` array and the first point of all child shapes.
+     */
+    _closed: true,
+
+    /**
+     * @name Two.Group#curved
+     * @property {Boolean} - When the child's path is `automatic = true` this boolean determines whether the lines between the points are curved or not.
+     */
+    _curved: false,
+
+    /**
+     * @name Two.Group#automatic
+     * @property {Boolean} - Determines whether or not Two.js should calculate curves, lines, and commands automatically for you or to let the developer manipulate them for themselves.
+     */
+    _automatic: true,
+
+    /**
+     * @name Two.Group#beginning
+     * @property {Number} - Number between zero and one to state the beginning of where the path is rendered.
+     * @description {@link Two.Group#beginning} is a percentage value that represents at what percentage into all child shapes should the renderer start drawing.
+     * @nota-bene This is great for animating in and out stroked paths in conjunction with {@link Two.Group#ending}.
+     */
+    _beginning: 0,
+
+    /**
+     * @name Two.Group#ending
+     * @property {Number} - Number between zero and one to state the ending of where the path is rendered.
+     * @description {@link Two.Group#ending} is a percentage value that represents at what percentage into all child shapes should the renderer start drawing.
+     * @nota-bene This is great for animating in and out stroked paths in conjunction with {@link Two.Group#beginning}.
+     */
+    _ending: 1.0,
+
+    /**
+     * @name Two.Group#length
+     * @property {Number} - The sum of distances between all child lengths.
+     */
+    _length: 0,
+
+    /**
+     * @name Two.Group#mask
+     * @property {Two.Shape} - The Two.js object to clip from a group's rendering.
+     */
+    _mask: null,
+
+    /**
+     * @name Two.Group#clone
+     * @function
+     * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+     * @returns {Two.Group}
+     * @description Create a new instance of {@link Two.Group} with the same properties of the current group.
+     */
+    clone: function(parent) {
+
+      // /**
+      //  * TODO: Group has a gotcha in that it's at the moment required to be bound to
+      //  * an instance of two in order to add elements correctly. This needs to
+      //  * be rethought and fixed.
+      //  */
+
+      var clone = new Group();
+      var children = this.children.map(function(child) {
+        return child.clone();
+      });
+
+      clone.add(children);
+
+      clone.opacity = this.opacity;
+
+      if (this.mask) {
+        clone.mask = this.mask;
+      }
+
+      clone.translation.copy(this.translation);
+      clone.rotation = this.rotation;
+      clone.scale = this.scale;
+      clone.className = this.className;
+
+      if (this.matrix.manual) {
+        clone.matrix.copy(this.matrix);
+      }
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone._update();
+
+    },
+
+    /**
+     * @name Two.Group#toObject
+     * @function
+     * @returns {Object}
+     * @description Return a JSON compatible plain object that represents the group.
+     */
+    toObject: function() {
+
+      var result = {
+        children: [],
+        translation: this.translation.toObject(),
+        rotation: this.rotation,
+        scale: this.scale instanceof Vector ? this.scale.toObject() : this.scale,
+        opacity: this.opacity,
+        className: this.className,
+        mask: (this.mask ? this.mask.toObject() : null)
+      };
+
+      if (this.matrix.manual) {
+        result.matrix = this.matrix.toObject();
+      }
+
+      _.each(this.children, function(child, i) {
+        result.children[i] = child.toObject();
+      }, this);
+
+      return result;
+
+    },
+
+    /**
+     * @name Two.Group#corner
+     * @function
+     * @description Orient the children of the group to the upper left-hand corner of that group.
+     */
+    corner: function() {
+
+      var rect = this.getBoundingClientRect(true);
+
+      for (var i = 0; i < this.children.length; i++) {
+        var child = this.children[i];
+        child.translation.x -= rect.left;
+        child.translation.y -= rect.top;
+      }
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Group#center
+     * @function
+     * @description Orient the children of the group to the center of that group.
+     */
+    center: function() {
+
+      var rect = this.getBoundingClientRect(true);
+      var cx = rect.left + rect.width / 2 - this.translation.x;
+      var cy = rect.top + rect.height / 2 - this.translation.y;
+
+      for (var i = 0; i < this.children.length; i++) {
+        var child = this.children[i];
+        if (child.isShape) {
+          child.translation.x -= cx;
+          child.translation.y -= cy;
+        }
+      }
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Group#getById
+     * @function
+     * @description Recursively search for id. Returns the first element found.
+     * @returns {Two.Shape} - Or `null` if nothing is found.
+     */
+    getById: function (id) {
+      var found = null;
+      function search(node) {
+        if (node.id === id) {
+          return node;
+        } else if (node.children) {
+          for (var i = 0; i < node.children.length; i++) {
+            found = search(node.children[i]);
+            if (found) {
+              return found;
+            }
+          }
+        }
+        return null;
+      }
+      return search(this);
+    },
+
+    /**
+     * @name Two.Group#getByClassName
+     * @function
+     * @description Recursively search for classes. Returns an array of matching elements.
+     * @returns {Two.Shape[]} - Or empty array if nothing is found.
+     */
+    getByClassName: function(className) {
+      var found = [];
+      function search(node) {
+        if (Array.prototype.indexOf.call(node.classList, className) >= 0) {
+          found.push(node);
+        }
+        if (node.children) {
+          for (var i = 0; i < node.children.length; i++) {
+            var child = node.children[i];
+            search(child);
+          }
+        }
+        return found;
+      }
+      return search(this);
+    },
+
+    /**
+     * @name Two.Group#getByType
+     * @function
+     * @description Recursively search for children of a specific type, e.g. {@link Two.Path}. Pass a reference to this type as the param. Returns an array of matching elements.
+     * @returns {Two.Shape[]} - Empty array if nothing is found.
+     */
+    getByType: function(type) {
+      var found = [];
+      function search(node) {
+        if (node instanceof type) {
+          found.push(node);
+        }
+        if (node.children) {
+          for (var i = 0; i < node.children.length; i++) {
+            var child = node.children[i];
+            search(child);
+          }
+        }
+        return found;
+      }
+      return search(this);
+    },
+
+    /**
+     * @name Two.Group#add
+     * @function
+     * @param {Two.Shape[]} objects - An array of objects to be added. Can be also be supplied as individual arguments.
+     * @description Add objects to the group.
+     */
+    add: function(objects) {
+
+      // Allow to pass multiple objects either as array or as multiple arguments
+      // If it's an array also create copy of it in case we're getting passed
+      // a childrens array directly.
+      if (!(objects instanceof Array)) {
+        objects = Array.prototype.slice.call(arguments);
+      } else {
+        objects = objects.slice();
+      }
+
+      // Add the objects
+      for (var i = 0; i < objects.length; i++) {
+        var child = objects[i];
+        if (!(child && child.id)) {
+          continue;
+        }
+        var index = Array.prototype.indexOf.call(this.children, child);
+        if (index >= 0) {
+          this.children.splice(index, 1);
+        }
+        this.children.push(child);
+      }
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Group#add
+     * @function
+     * @param {Two.Shape[]} objects - An array of objects to be removed. Can be also removed as individual arguments.
+     * @description Remove objects from the group.
+     */
+    remove: function(objects) {
+
+      var l = arguments.length,
+        grandparent = this.parent;
+
+      // Allow to call remove without arguments
+      // This will detach the object from its own parent.
+      if (l <= 0 && grandparent) {
+        grandparent.remove(this);
+        return this;
+      }
+
+      // Allow to pass multiple objects either as array or as multiple arguments
+      // If it's an array also create copy of it in case we're getting passed
+      // a childrens array directly.
+      if (!(objects instanceof Array)) {
+        objects = Array.prototype.slice.call(arguments);
+      } else {
+        objects = objects.slice();
+      }
+
+      // Remove the objects
+      for (var i = 0; i < objects.length; i++) {
+        var object = objects[i];
+        if (!object || !this.children.ids[object.id]) {
+          continue;
+        }
+        var index = this.children.indexOf(object);
+        if (index >= 0) {
+          this.children.splice(index, 1);
+        }
+      }
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Group#getBoundingClientRect
+     * @function
+     * @param {Boolean} [shallow=false] - Describes whether to calculate off local matrix or world matrix.
+     * @returns {Object} - Returns object with top, left, right, bottom, width, height attributes.
+     * @description Return an object with top, left, right, bottom, width, and height parameters of the group.
+     */
+    getBoundingClientRect: function(shallow) {
+      var rect, matrix, a, b, c, d, tc, lc, rc, bc;
+
+      // TODO: Update this to not __always__ update. Just when it needs to.
+      this._update(true);
+
+      // Variables need to be defined here, because of nested nature of groups.
+      var left = Infinity, right = -Infinity,
+          top = Infinity, bottom = -Infinity;
+
+      var regex = /texture|gradient/i;
+
+      matrix = shallow ? this._matrix : getComputedMatrix(this);
+
+      for (var i = 0; i < this.children.length; i++) {
+
+        var child = this.children[i];
+
+        if (!child.visible || regex.test(child._renderer.type)) {
+          continue;
+        }
+
+        rect = child.getBoundingClientRect(shallow);
+
+        tc = typeof rect.top !== 'number' || _.isNaN(rect.top) || !isFinite(rect.top);
+        lc = typeof rect.left !== 'number' || _.isNaN(rect.left) || !isFinite(rect.left);
+        rc = typeof rect.right !== 'number' || _.isNaN(rect.right) || !isFinite(rect.right);
+        bc = typeof rect.bottom !== 'number' || _.isNaN(rect.bottom) || !isFinite(rect.bottom);
+
+        if (tc || lc || rc || bc) {
+          continue;
+        }
+
+        top = min$3(rect.top, top);
+        left = min$3(rect.left, left);
+        right = max$3(rect.right, right);
+        bottom = max$3(rect.bottom, bottom);
+
+      }
+
+      if (shallow) {
+
+        a = matrix.multiply(left, top, 1);
+        b = matrix.multiply(left, bottom, 1);
+        c = matrix.multiply(right, top, 1);
+        d = matrix.multiply(right, bottom, 1);
+
+        top = min$3(a.y, b.y, c.y, d.y);
+        left = min$3(a.x, b.x, c.x, d.x);
+        right = max$3(a.x, b.x, c.x, d.x);
+        bottom = max$3(a.y, b.y, c.y, d.y);
+
+      }
+
+      return {
+        top: top,
+        left: left,
+        right: right,
+        bottom: bottom,
+        width: right - left,
+        height: bottom - top
+      };
+
+    },
+
+    /**
+     * @name Two.Group#noFill
+     * @function
+     * @description Apply `noFill` method to all child shapes.
+     */
+    noFill: function() {
+      this.children.forEach(function(child) {
+        child.noFill();
+      });
+      return this;
+    },
+
+    /**
+     * @name Two.Group#noStroke
+     * @function
+     * @description Apply `noStroke` method to all child shapes.
+     */
+    noStroke: function() {
+      this.children.forEach(function(child) {
+        child.noStroke();
+      });
+      return this;
+    },
+
+    /**
+     * @name Two.Group#subdivide
+     * @function
+     * @description Apply `subdivide` method to all child shapes.
+     */
+    subdivide: function() {
+      var args = arguments;
+      this.children.forEach(function(child) {
+        child.subdivide.apply(child, args);
+      });
+      return this;
+    },
+
+    /**
+     * @name Two.Group#_update
+     * @function
+     * @private
+     * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+     * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+     * @nota-bene Try not to call this method more than once a frame.
+     */
+    _update: function() {
+
+      var i, l, child;
+
+      if (this._flagBeginning || this._flagEnding) {
+
+        var beginning = Math.min(this._beginning, this._ending);
+        var ending = Math.max(this._beginning, this._ending);
+        var length = this.length;
+        var sum = 0;
+
+        var bd = beginning * length;
+        var ed = ending * length;
+
+        for (i = 0; i < this.children.length; i++) {
+
+          child = this.children[i];
+          l = child.length;
+
+          if (bd > sum + l) {
+            child.beginning = 1;
+            child.ending = 1;
+          } else if (ed < sum) {
+            child.beginning = 0;
+            child.ending = 0;
+          } else if (bd > sum && bd < sum + l) {
+            child.beginning = (bd - sum) / l;
+            child.ending = 1;
+          } else if (ed > sum && ed < sum + l) {
+            child.beginning = 0;
+            child.ending = (ed - sum) / l;
+          } else {
+            child.beginning = 0;
+            child.ending = 1;
+          }
+
+          sum += l;
+
+        }
+
+      }
+
+      return Shape.prototype._update.apply(this, arguments);
+
+    },
+
+    /**
+     * @name Two.Group#flagReset
+     * @function
+     * @private
+     * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+     */
+    flagReset: function() {
+
+      if (this._flagAdditions) {
+        this.additions.length = 0;
+        this._flagAdditions = false;
+      }
+
+      if (this._flagSubtractions) {
+        this.subtractions.length = 0;
+        this._flagSubtractions = false;
+      }
+
+      this._flagOrder = this._flagMask = this._flagOpacity =
+        this._flagBeginning = this._flagEnding = false;
+
+      Shape.prototype.flagReset.call(this);
+
+      return this;
+
+    }
+
+  });
+
+  Group.MakeObservable(Group.prototype);
+
+  // /**
+  //  * Helper function used to sync parent-child relationship within the
+  //  * `Two.Group.children` object.
+  //  *
+  //  * Set the parent of the passed object to another object
+  //  * and updates parent-child relationships
+  //  * Calling with one arguments will simply remove the parenting
+  //  */
+  function replaceParent(child, newParent) {
+
+    var parent = child.parent;
+    var index;
+
+    if (parent === newParent) {
+      add();
+      return;
+    }
+
+    if (parent && parent.children.ids[child.id]) {
+
+      index = Array.prototype.indexOf.call(parent.children, child);
+      parent.children.splice(index, 1);
+
+      splice();
+
+    }
+
+    if (newParent) {
+      add();
+      return;
+    }
+
+    splice();
+
+    if (parent._flagAdditions && parent.additions.length === 0) {
+      parent._flagAdditions = false;
+    }
+    if (parent._flagSubtractions && parent.subtractions.length === 0) {
+      parent._flagSubtractions = false;
+    }
+
+    delete child.parent;
+
+    function add() {
+
+      if (newParent.subtractions.length > 0) {
+        index = Array.prototype.indexOf.call(newParent.subtractions, child);
+
+        if (index >= 0) {
+          newParent.subtractions.splice(index, 1);
+        }
+      }
+
+      if (newParent.additions.length > 0) {
+        index = Array.prototype.indexOf.call(newParent.additions, child);
+
+        if (index >= 0) {
+          newParent.additions.splice(index, 1);
+        }
+      }
+
+      child.parent = newParent;
+      newParent.additions.push(child);
+      newParent._flagAdditions = true;
+
+    }
+
+    function splice() {
+
+      index = Array.prototype.indexOf.call(parent.additions, child);
+
+      if (index >= 0) {
+        parent.additions.splice(index, 1);
+      }
+
+      index = Array.prototype.indexOf.call(parent.subtractions, child);
+
+      if (index < 0) {
+        parent.subtractions.push(child);
+        parent._flagSubtractions = true;
+      }
+
+    }
+
+  }
+
+  // Constants
+  var emptyArray = [];
+  var TWO_PI$5 = Math.PI * 2,
+    max$2 = Math.max,
+    min$2 = Math.min,
+    abs = Math.abs,
+    sin$4 = Math.sin,
+    cos$4 = Math.cos,
+    acos = Math.acos,
+    sqrt = Math.sqrt;
+
+  // Returns true if this is a non-transforming matrix
+  var isDefaultMatrix = function (m) {
+    return (m[0] == 1 && m[3] == 0 && m[1] == 0 && m[4] == 1 && m[2] == 0 && m[5] == 0);
+  };
+
+  var canvas = {
+
+    isHidden: /(undefined|none|transparent)/i,
+
+    alignments: {
+      left: 'start',
+      middle: 'center',
+      right: 'end'
+    },
+
+    shim: function(elem, name) {
+      elem.tagName = elem.nodeName = name || 'canvas';
+      elem.nodeType = 1;
+      elem.getAttribute = function(prop) {
+        return this[prop];
+      };
+      elem.setAttribute = function(prop, val) {
+        this[prop] = val;
+        return this;
+      };
+      return elem;
+    },
+
+    group: {
+
+      renderChild: function(child) {
+        canvas[child._renderer.type].render.call(child, this.ctx, true, this.clip);
+      },
+
+      render: function(ctx) {
+
+        if (!this._visible) {
+          return this;
+        }
+
+        this._update();
+
+        var matrix = this._matrix.elements;
+        var parent = this.parent;
+        this._renderer.opacity = this._opacity
+          * (parent && parent._renderer ? parent._renderer.opacity : 1);
+
+        var mask = this._mask;
+        // var clip = this._clip;
+
+        var defaultMatrix = isDefaultMatrix(matrix);
+        var shouldIsolate = !defaultMatrix || !!mask;
+
+        if (!this._renderer.context) {
+          this._renderer.context = {};
+        }
+
+        this._renderer.context.ctx = ctx;
+        // this._renderer.context.clip = clip;
+
+        if (shouldIsolate) {
+          ctx.save();
+          if (!defaultMatrix) {
+            ctx.transform(matrix[0], matrix[3], matrix[1],
+              matrix[4], matrix[2], matrix[5]);
+          }
+        }
+
+        if (mask) {
+          canvas[mask._renderer.type].render.call(mask, ctx, true);
+        }
+
+        if (this._opacity > 0 && this._scale !== 0) {
+          for (var i = 0; i < this.children.length; i++) {
+            var child = this.children[i];
+            canvas[child._renderer.type].render.call(child, ctx);
+          }
+        }
+
+        if (shouldIsolate) {
+          ctx.restore();
+        }
+
+        // Commented two-way functionality of clips / masks with groups and
+        // polygons. Uncomment when this bug is fixed:
+        // https://code.google.com/p/chromium/issues/detail?id=370951
+
+        // if (clip) {
+        //   ctx.clip();
+        // }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    path: {
+
+      render: function(ctx, forced, parentClipped) {
+
+        var matrix, stroke, linewidth, fill, opacity, visible, cap, join, miter,
+            closed, commands, length, last, next, prev, a, b, c, d, ux, uy, vx, vy,
+            ar, bl, br, cl, x, y, mask, clip, defaultMatrix, isOffset, dashes, po;
+
+        po = (this.parent && this.parent._renderer)
+          ? this.parent._renderer.opacity : 1;
+        mask = this._mask;
+        clip = this._clip;
+        opacity = this._opacity * (po || 1);
+        visible = this._visible;
+
+        if (!forced && (!visible || clip || opacity === 0)) {
+          return this;
+        }
+
+        this._update();
+
+        matrix = this._matrix.elements;
+        stroke = this._stroke;
+        linewidth = this._linewidth;
+        fill = this._fill;
+        cap = this._cap;
+        join = this._join;
+        miter = this._miter;
+        closed = this._closed;
+        commands = this._renderer.vertices; // Commands
+        length = commands.length;
+        last = length - 1;
+        defaultMatrix = isDefaultMatrix(matrix);
+        dashes = this.dashes;
+
+        // Transform
+        if (!defaultMatrix) {
+          ctx.save();
+          ctx.transform(matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]);
+        }
+
+        // Commented two-way functionality of clips / masks with groups and
+        // polygons. Uncomment when this bug is fixed:
+        // https://code.google.com/p/chromium/issues/detail?id=370951
+        if (mask) {
+          canvas[mask._renderer.type].render.call(mask, ctx, true);
+        }
+
+        // Styles
+        if (fill) {
+          if (typeof fill === 'string') {
+            ctx.fillStyle = fill;
+          } else {
+            canvas[fill._renderer.type].render.call(fill, ctx);
+            ctx.fillStyle = fill._renderer.effect;
+          }
+        }
+        if (stroke) {
+          if (typeof stroke === 'string') {
+            ctx.strokeStyle = stroke;
+          } else {
+            canvas[stroke._renderer.type].render.call(stroke, ctx);
+            ctx.strokeStyle = stroke._renderer.effect;
+          }
+          if (linewidth) {
+            ctx.lineWidth = linewidth;
+          }
+          if (miter) {
+            ctx.miterLimit = miter;
+          }
+          if (join) {
+            ctx.lineJoin = join;
+          }
+          if (!closed && cap) {
+            ctx.lineCap = cap;
+          }
+        }
+        if (typeof opacity === 'number') {
+          ctx.globalAlpha = opacity;
+        }
+
+        if (dashes && dashes.length > 0) {
+          ctx.lineDashOffset = dashes.offset || 0;
+          ctx.setLineDash(dashes);
+        }
+
+        ctx.beginPath();
+
+        for (var i = 0; i < commands.length; i++) {
+
+          b = commands[i];
+
+          x = b.x;
+          y = b.y;
+
+          switch (b.command) {
+
+            case Commands.close:
+              ctx.closePath();
+              break;
+
+            case Commands.arc:
+
+              var rx = b.rx;
+              var ry = b.ry;
+              var xAxisRotation = b.xAxisRotation;
+              var largeArcFlag = b.largeArcFlag;
+              var sweepFlag = b.sweepFlag;
+
+              prev = closed ? mod(i - 1, length) : max$2(i - 1, 0);
+              a = commands[prev];
+
+              var ax = a.x;
+              var ay = a.y;
+
+              canvas.renderSvgArcCommand(ctx, ax, ay, rx, ry, largeArcFlag, sweepFlag, xAxisRotation, x, y);
+              break;
+
+            case Commands.curve:
+
+              prev = closed ? mod(i - 1, length) : Math.max(i - 1, 0);
+              next = closed ? mod(i + 1, length) : Math.min(i + 1, last);
+
+              a = commands[prev];
+              c = commands[next];
+              ar = (a.controls && a.controls.right) || Vector.zero;
+              bl = (b.controls && b.controls.left) || Vector.zero;
+
+              if (a._relative) {
+                vx = (ar.x + a.x);
+                vy = (ar.y + a.y);
+              } else {
+                vx = ar.x;
+                vy = ar.y;
+              }
+
+              if (b._relative) {
+                ux = (bl.x + b.x);
+                uy = (bl.y + b.y);
+              } else {
+                ux = bl.x;
+                uy = bl.y;
+              }
+
+              ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
+
+              if (i >= last && closed) {
+
+                c = d;
+
+                br = (b.controls && b.controls.right) || Vector.zero;
+                cl = (c.controls && c.controls.left) || Vector.zero;
+
+                if (b._relative) {
+                  vx = (br.x + b.x);
+                  vy = (br.y + b.y);
+                } else {
+                  vx = br.x;
+                  vy = br.y;
+                }
+
+                if (c._relative) {
+                  ux = (cl.x + c.x);
+                  uy = (cl.y + c.y);
+                } else {
+                  ux = cl.x;
+                  uy = cl.y;
+                }
+
+                x = c.x;
+                y = c.y;
+
+                ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
+
+              }
+
+              break;
+
+            case Commands.line:
+              ctx.lineTo(x, y);
+              break;
+
+            case Commands.move:
+              d = b;
+              ctx.moveTo(x, y);
+              break;
+
+          }
+        }
+
+        // Loose ends
+
+        if (closed) {
+          ctx.closePath();
+        }
+
+        if (!clip && !parentClipped) {
+          if (!canvas.isHidden.test(fill)) {
+            isOffset = fill._renderer && fill._renderer.offset;
+            if (isOffset) {
+              ctx.save();
+              ctx.translate(
+                - fill._renderer.offset.x, - fill._renderer.offset.y);
+              ctx.scale(fill._renderer.scale.x, fill._renderer.scale.y);
+            }
+            ctx.fill();
+            if (isOffset) {
+              ctx.restore();
+            }
+          }
+          if (!canvas.isHidden.test(stroke)) {
+            isOffset = stroke._renderer && stroke._renderer.offset;
+            if (isOffset) {
+              ctx.save();
+              ctx.translate(
+                - stroke._renderer.offset.x, - stroke._renderer.offset.y);
+              ctx.scale(stroke._renderer.scale.x, stroke._renderer.scale.y);
+              ctx.lineWidth = linewidth / stroke._renderer.scale.x;
+            }
+            ctx.stroke();
+            if (isOffset) {
+              ctx.restore();
+            }
+          }
+        }
+
+        if (!defaultMatrix) {
+          ctx.restore();
+        }
+
+        if (clip && !parentClipped) {
+          ctx.clip();
+        }
+
+        if (dashes && dashes.length > 0) {
+          ctx.setLineDash(emptyArray);
+        }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    text: {
+
+      render: function(ctx, forced, parentClipped) {
+
+        var po = (this.parent && this.parent._renderer)
+          ? this.parent._renderer.opacity : 1;
+        var opacity = this._opacity * po;
+        var visible = this._visible;
+        var mask = this._mask;
+        var clip = this._clip;
+
+        if (!forced && (!visible || clip || opacity === 0)) {
+          return this;
+        }
+
+        this._update();
+
+        var matrix = this._matrix.elements;
+        var stroke = this._stroke;
+        var linewidth = this._linewidth;
+        var fill = this._fill;
+        var decoration = this._decoration;
+        var defaultMatrix = isDefaultMatrix(matrix);
+        var isOffset = fill._renderer && fill._renderer.offset
+          && stroke._renderer && stroke._renderer.offset;
+        var dashes = this.dashes;
+        var alignment = canvas.alignments[this._alignment] || this._alignment;
+        var baseline = this._baseline;
+
+        var a, b, c, d, e, sx, sy, x1, y1, x2, y2;
+
+        // Transform
+        if (!defaultMatrix) {
+          ctx.save();
+          ctx.transform(matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]);
+        }
+
+        // Commented two-way functionality of clips / masks with groups and
+        // polygons. Uncomment when this bug is fixed:
+        // https://code.google.com/p/chromium/issues/detail?id=370951
+        if (mask) {
+          canvas[mask._renderer.type].render.call(mask, ctx, true);
+        }
+
+        if (!isOffset) {
+          ctx.font = [this._style, this._weight, this._size + 'px/' +
+            this._leading + 'px', this._family].join(' ');
+        }
+
+        ctx.textAlign = alignment;
+        ctx.textBaseline = baseline;
+
+        // Styles
+        if (fill) {
+          if (typeof fill === 'string') {
+            ctx.fillStyle = fill;
+          } else {
+            canvas[fill._renderer.type].render.call(fill, ctx);
+            ctx.fillStyle = fill._renderer.effect;
+          }
+        }
+        if (stroke) {
+          if (typeof stroke === 'string') {
+            ctx.strokeStyle = stroke;
+          } else {
+            canvas[stroke._renderer.type].render.call(stroke, ctx);
+            ctx.strokeStyle = stroke._renderer.effect;
+          }
+          if (linewidth) {
+            ctx.lineWidth = linewidth;
+          }
+        }
+        if (typeof opacity === 'number') {
+          ctx.globalAlpha = opacity;
+        }
+        if (dashes && dashes.length > 0) {
+          ctx.lineDashOffset = dashes.offset || 0;
+          ctx.setLineDash(dashes);
+        }
+
+        if (!clip && !parentClipped) {
+
+          if (!canvas.isHidden.test(fill)) {
+
+            if (fill._renderer && fill._renderer.offset) {
+
+              sx = fill._renderer.scale.x;
+              sy = fill._renderer.scale.y;
+
+              ctx.save();
+              ctx.translate( - fill._renderer.offset.x,
+                - fill._renderer.offset.y);
+              ctx.scale(sx, sy);
+
+              a = this._size / fill._renderer.scale.y;
+              b = this._leading / fill._renderer.scale.y;
+              ctx.font = [this._style, this._weight, a + 'px/',
+                b + 'px', this._family].join(' ');
+
+              c = fill._renderer.offset.x / fill._renderer.scale.x;
+              d = fill._renderer.offset.y / fill._renderer.scale.y;
+
+              ctx.fillText(this.value, c, d);
+              ctx.restore();
+
+            } else {
+              ctx.fillText(this.value, 0, 0);
+            }
+
+          }
+
+          if (!canvas.isHidden.test(stroke)) {
+
+            if (stroke._renderer && stroke._renderer.offset) {
+
+              sx = stroke._renderer.scale.x;
+              sy = stroke._renderer.scale.y;
+
+              ctx.save();
+              ctx.translate(- stroke._renderer.offset.x,
+                - stroke._renderer.offset.y);
+              ctx.scale(sx, sy);
+
+              a = this._size / stroke._renderer.scale.y;
+              b = this._leading / stroke._renderer.scale.y;
+              ctx.font = [this._style, this._weight, a + 'px/',
+                b + 'px', this._family].join(' ');
+
+              c = stroke._renderer.offset.x / stroke._renderer.scale.x;
+              d = stroke._renderer.offset.y / stroke._renderer.scale.y;
+              e = linewidth / stroke._renderer.scale.x;
+
+              ctx.lineWidth = e;
+              ctx.strokeText(this.value, c, d);
+              ctx.restore();
+
+            } else {
+              ctx.strokeText(this.value, 0, 0);
+            }
+          }
+        }
+
+        // Handle text-decoration
+        if (/(underline|strikethrough)/i.test(decoration)) {
+
+          var metrics = ctx.measureText(this.value);
+          var scalar = 1;
+
+          switch (decoration) {
+            case 'underline':
+              y1 = metrics.actualBoundingBoxAscent;
+              y2 = metrics.actualBoundingBoxAscent;
+              break;
+            case 'strikethrough':
+              y1 = 0;
+              y2 = 0;
+              scalar = 0.5;
+              break;
+          }
+
+          switch (baseline) {
+            case 'top':
+              y1 += this._size * scalar;
+              y2 += this._size * scalar;
+              break;
+            case 'baseline':
+            case 'bottom':
+              y1 -= this._size * scalar;
+              y2 -= this._size * scalar;
+              break;
+          }
+
+          switch (alignment) {
+            case 'left':
+            case 'start':
+              x1 = 0;
+              x2 = metrics.width;
+              break;
+            case 'right':
+            case 'end':
+              x1 = - metrics.width;
+              x2 = 0;
+              break;
+            default:
+              x1 = - metrics.width / 2;
+              x2 = metrics.width / 2;
+          }
+
+          ctx.lineWidth = Math.max(Math.floor(this._size / 15), 1);
+          ctx.strokeStyle = ctx.fillStyle;
+
+          ctx.beginPath();
+          ctx.moveTo(x1, y1);
+          ctx.lineTo(x2, y2);
+          ctx.stroke();
+
+        }
+
+        if (!defaultMatrix) {
+          ctx.restore();
+        }
+
+        // TODO: Test for text
+        if (clip && !parentClipped) {
+          ctx.clip();
+        }
+
+        if (dashes && dashes.length > 0) {
+          ctx.setLineDash(emptyArray);
+        }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    'linear-gradient': {
+
+      render: function(ctx) {
+
+        this._update();
+
+        if (!this._renderer.effect || this._flagEndPoints || this._flagStops) {
+
+          this._renderer.effect = ctx.createLinearGradient(
+            this.left._x, this.left._y,
+            this.right._x, this.right._y
+          );
+
+          for (var i = 0; i < this.stops.length; i++) {
+            var stop = this.stops[i];
+            this._renderer.effect.addColorStop(stop._offset, stop._color);
+          }
+
+        }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    'radial-gradient': {
+
+      render: function(ctx) {
+
+        this._update();
+
+        if (!this._renderer.effect || this._flagCenter || this._flagFocal
+            || this._flagRadius || this._flagStops) {
+
+          this._renderer.effect = ctx.createRadialGradient(
+            this.center._x, this.center._y, 0,
+            this.focal._x, this.focal._y, this._radius
+          );
+
+          for (var i = 0; i < this.stops.length; i++) {
+            var stop = this.stops[i];
+            this._renderer.effect.addColorStop(stop._offset, stop._color);
+          }
+
+        }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    texture: {
+
+      render: function(ctx) {
+
+        this._update();
+
+        var image = this.image;
+
+        if (!this._renderer.effect || ((this._flagLoaded || this._flagImage || this._flagVideo || this._flagRepeat) && this.loaded)) {
+          this._renderer.effect = ctx.createPattern(this.image, this._repeat);
+        }
+
+        if (this._flagOffset || this._flagLoaded || this._flagScale) {
+
+          if (!(this._renderer.offset instanceof Vector)) {
+            this._renderer.offset = new Vector();
+          }
+
+          this._renderer.offset.x = - this._offset.x;
+          this._renderer.offset.y = - this._offset.y;
+
+          if (image) {
+
+            this._renderer.offset.x += image.width / 2;
+            this._renderer.offset.y += image.height / 2;
+
+            if (this._scale instanceof Vector) {
+              this._renderer.offset.x *= this._scale.x;
+              this._renderer.offset.y *= this._scale.y;
+            } else {
+              this._renderer.offset.x *= this._scale;
+              this._renderer.offset.y *= this._scale;
+            }
+          }
+
+        }
+
+        if (this._flagScale || this._flagLoaded) {
+
+          if (!(this._renderer.scale instanceof Vector)) {
+            this._renderer.scale = new Vector();
+          }
+
+          if (this._scale instanceof Vector) {
+            this._renderer.scale.copy(this._scale);
+          } else {
+            this._renderer.scale.set(this._scale, this._scale);
+          }
+
+        }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    renderSvgArcCommand: function(ctx, ax, ay, rx, ry, largeArcFlag, sweepFlag, xAxisRotation, x, y) {
+
+      xAxisRotation = xAxisRotation * Math.PI / 180;
+
+      // Ensure radii are positive
+      rx = abs(rx);
+      ry = abs(ry);
+
+      // Compute (x1′, y1′)
+      var dx2 = (ax - x) / 2.0;
+      var dy2 = (ay - y) / 2.0;
+      var x1p = cos$4(xAxisRotation) * dx2 + sin$4(xAxisRotation) * dy2;
+      var y1p = - sin$4(xAxisRotation) * dx2 + cos$4(xAxisRotation) * dy2;
+
+      // Compute (cx′, cy′)
+      var rxs = rx * rx;
+      var rys = ry * ry;
+      var x1ps = x1p * x1p;
+      var y1ps = y1p * y1p;
+
+      // Ensure radii are large enough
+      var cr = x1ps / rxs + y1ps / rys;
+
+      if (cr > 1) {
+
+        // scale up rx,ry equally so cr == 1
+        var s = sqrt(cr);
+        rx = s * rx;
+        ry = s * ry;
+        rxs = rx * rx;
+        rys = ry * ry;
+
+      }
+
+      var dq = (rxs * y1ps + rys * x1ps);
+      var pq = (rxs * rys - dq) / dq;
+      var q = sqrt(max$2(0, pq));
+      if (largeArcFlag === sweepFlag) q = - q;
+      var cxp = q * rx * y1p / ry;
+      var cyp = - q * ry * x1p / rx;
+
+      // Step 3: Compute (cx, cy) from (cx′, cy′)
+      var cx = cos$4(xAxisRotation) * cxp
+        - sin$4(xAxisRotation) * cyp + (ax + x) / 2;
+      var cy = sin$4(xAxisRotation) * cxp
+        + cos$4(xAxisRotation) * cyp + (ay + y) / 2;
+
+      // Step 4: Compute θ1 and Δθ
+      var startAngle = svgAngle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
+      var delta = svgAngle((x1p - cxp) / rx, (y1p - cyp) / ry,
+        (- x1p - cxp) / rx, (- y1p - cyp) / ry) % TWO_PI$5;
+
+      var endAngle = startAngle + delta;
+
+      var clockwise = sweepFlag === 0;
+
+      renderArcEstimate(ctx, cx, cy, rx, ry, startAngle, endAngle,
+        clockwise, xAxisRotation);
+
+    }
+
+  };
+
+  /**
+   * @name Two.CanvasRenderer
+   * @class
+   * @extends Two.Events
+   * @param {Object} [parameters] - This object is inherited when constructing a new instance of {@link Two}.
+   * @param {Element} [parameters.domElement] - The `<canvas />` to draw to. If none given a new one will be constructed.
+   * @param {Boolean} [parameters.overdraw] - Determines whether the canvas should clear the background or not. Defaults to `true`.
+   * @param {Boolean} [parameters.smoothing=true] - Determines whether the canvas should antialias drawing. Set it to `false` when working with pixel art. `false` can lead to better performance, since it would use a cheaper interpolation algorithm.
+   * @description This class is used by {@link Two} when constructing with `type` of `Two.Types.canvas`. It takes Two.js' scenegraph and renders it to a `<canvas />`.
+   */
+  function Renderer$2(params) {
+
+    // It might not make a big difference on GPU backed canvases.
+    var smoothing = (params.smoothing !== false);
+
+    /**
+     * @name Two.CanvasRenderer#domElement
+     * @property {Element} - The `<canvas />` associated with the Two.js scene.
+     */
+    this.domElement = params.domElement || document.createElement('canvas');
+
+    /**
+     * @name Two.CanvasRenderer#ctx
+     * @property {Canvas2DContext} - Associated two dimensional context to render on the `<canvas />`.
+     */
+    this.ctx = this.domElement.getContext('2d');
+
+    /**
+     * @name Two.CanvasRenderer#overdraw
+     * @property {Boolean} - Determines whether the canvas clears the background each draw call.
+     * @default true
+     */
+    this.overdraw = params.overdraw || false;
+
+    if (typeof this.ctx.imageSmoothingEnabled !== 'undefined') {
+      this.ctx.imageSmoothingEnabled = smoothing;
+    }
+
+    /**
+     * @name Two.CanvasRenderer#scene
+     * @property {Two.Group} - The root group of the scenegraph.
+     */
+    this.scene = new Group();
+    this.scene.parent = this;
+  }
+
+
+  _.extend(Renderer$2, {
+
+    /**
+     * @name Two.CanvasRenderer.Utils
+     * @property {Object} - A massive object filled with utility functions and properties to render Two.js objects to a `<canvas />`.
+     */
+    Utils: canvas
+
+  });
+
+  _.extend(Renderer$2.prototype, Events, {
+
+    constructor: Renderer$2,
+
+    /**
+     * @name Two.CanvasRenderer#setSize
+     * @function
+     * @fires resize
+     * @param {Number} width - The new width of the renderer.
+     * @param {Number} height - The new height of the renderer.
+     * @param {Number} [ratio] - The new pixel ratio (pixel density) of the renderer. Defaults to calculate the pixel density of the user's screen.
+     * @description Change the size of the renderer.
+     */
+    setSize: function(width, height, ratio) {
+
+      this.width = width;
+      this.height = height;
+
+      this.ratio = typeof ratio === 'undefined' ? getRatio(this.ctx) : ratio;
+
+      this.domElement.width = width * this.ratio;
+      this.domElement.height = height * this.ratio;
+
+      if (this.domElement.style) {
+        _.extend(this.domElement.style, {
+          width: width + 'px',
+          height: height + 'px'
+        });
+      }
+
+      return this.trigger(Events.Types.resize, width, height, ratio);
+
+    },
+
+    /**
+     * @name Two.CanvasRenderer#render
+     * @function
+     * @description Render the current scene to the `<canvas />`.
+     */
+    render: function() {
+
+      var isOne = this.ratio === 1;
+
+      if (!isOne) {
+        this.ctx.save();
+        this.ctx.scale(this.ratio, this.ratio);
+      }
+
+      if (!this.overdraw) {
+        this.ctx.clearRect(0, 0, this.width, this.height);
+      }
+
+      canvas.group.render.call(this.scene, this.ctx);
+
+      if (!isOne) {
+        this.ctx.restore();
+      }
+
+      return this;
+
+    }
+
+  });
+
+  function renderArcEstimate(ctx, ox, oy, rx, ry, startAngle, endAngle, clockwise, xAxisRotation) {
+
+    var epsilon = Curve.Tolerance.epsilon;
+    var deltaAngle = endAngle - startAngle;
+    var samePoints = Math.abs(deltaAngle) < epsilon;
+
+    // ensures that deltaAngle is 0 .. 2 PI
+    deltaAngle = mod(deltaAngle, TWO_PI$5);
+
+    if (deltaAngle < epsilon) {
+
+      if (samePoints) {
+
+        deltaAngle = 0;
+
+      } else {
+
+        deltaAngle = TWO_PI$5;
+
+      }
+
+    }
+
+    if (clockwise === true && ! samePoints) {
+
+      if (deltaAngle === TWO_PI$5) {
+
+        deltaAngle = - TWO_PI$5;
+
+      } else {
+
+        deltaAngle = deltaAngle - TWO_PI$5;
+
+      }
+
+    }
+
+    for (var i = 0; i < Constants.Resolution; i++) {
+
+      var t = i / (Constants.Resolution - 1);
+
+      var angle = startAngle + t * deltaAngle;
+      var x = ox + rx * Math.cos(angle);
+      var y = oy + ry * Math.sin(angle);
+
+      if (xAxisRotation !== 0) {
+
+        var cos = Math.cos(xAxisRotation);
+        var sin = Math.sin(xAxisRotation);
+
+        var tx = x - ox;
+        var ty = y - oy;
+
+        // Rotate the point about the center of the ellipse.
+        x = tx * cos - ty * sin + ox;
+        y = tx * sin + ty * cos + oy;
+
+      }
+
+      ctx.lineTo(x, y);
+
+    }
+
+  }
+
+  function svgAngle(ux, uy, vx, vy) {
+
+    var dot = ux * vx + uy * vy;
+    var len = sqrt(ux * ux + uy * uy) *  sqrt(vx * vx + vy * vy);
+    // floating point precision, slightly over values appear
+    var ang = acos(max$2(-1, min$2(1, dot / len)));
+    if ((ux * vy - uy * vx) < 0) {
+      ang = - ang;
+    }
+
+    return ang;
+
+  }
+
+  var CanvasShim = {
+
+    Image: null,
+
+    isHeadless: false,
+
+    /**
+     * @name Two.Utils.shim
+     * @function
+     * @param {canvas} canvas - The instanced `Canvas` object provided by `node-canvas`.
+     * @param {Image} [Image] - The prototypical `Image` object provided by `node-canvas`. This is only necessary to pass if you're going to load bitmap imagery.
+     * @returns {canvas} Returns the instanced canvas object you passed from with additional attributes needed for Two.js.
+     * @description Convenience method for defining all the dependencies from the npm package `node-canvas`. See [node-canvas](https://github.com/Automattic/node-canvas) for additional information on setting up HTML5 `<canvas />` drawing in a node.js environment.
+     */
+    shim: function(canvas, Image) {
+      Renderer$2.Utils.shim(canvas);
+      if (typeof Image !== 'undefined') {
+        CanvasShim.Image = Image;
+      }
+      CanvasShim.isHeadless = true;
+      return canvas;
+    }
+
+  };
+
+  var dom = {
+
+    hasEventListeners: typeof root$1.addEventListener === 'function',
+
+    bind: function(elem, event, func, bool) {
+      if (this.hasEventListeners) {
+        elem.addEventListener(event, func, !!bool);
+      } else {
+        elem.attachEvent('on' + event, func);
+      }
+      return dom;
+    },
+
+    unbind: function(elem, event, func, bool) {
+      if (dom.hasEventListeners) {
+        elem.removeEventListeners(event, func, !!bool);
+      } else {
+        elem.detachEvent('on' + event, func);
+      }
+      return dom;
+    },
+
+    getRequestAnimationFrame: function() {
+
+      var lastTime = 0;
+      var vendors = ['ms', 'moz', 'webkit', 'o'];
+      var request = root$1.requestAnimationFrame, cancel;
+
+      if(!request) {
+        for (var i = 0; i < vendors.length; i++) {
+          request = root$1[vendors[i] + 'RequestAnimationFrame'] || request;
+          cancel = root$1[vendors[i] + 'CancelAnimationFrame']
+            || root$1[vendors[i] + 'CancelRequestAnimationFrame'] || cancel;
+        }
+
+        request = request || function(callback, element) {
+          var currTime = new Date().getTime();
+          var timeToCall = Math.max(0, 16 - (currTime - lastTime));
+          var id = root$1.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
+          lastTime = currTime + timeToCall;
+          return id;
+        };
+      }
+
+      return request;
+
+    }
+
+  };
+
+  var temp = (root$1.document ? root$1.document.createElement('div') : {});
+  temp.id = 'help-two-load';
+
+  Object.defineProperty(dom, 'temp', {
+    enumerable: true,
+    get: function() {
+      if (_.isElement(temp) && !root$1.document.head.contains(temp)) {
+        _.extend(temp.style, {
+          display: 'none'
+        });
+        root$1.document.head.appendChild(temp);
+      }
+      return temp;
+    }
+  });
+
+  /**
+   * @name Two.Utils.Error
+   * @class
+   * @description Custom error throwing for Two.js specific identification.
+   */
+  function TwoError(message) {
+    this.name = 'Two.js';
+    this.message = message;
+  }
+
+  TwoError.prototype = new Error();
+
+  _.extend(TwoError.prototype, {
+    constructor: TwoError
+  });
+
+  /**
+   * @name Two.Utils.defineGetterSetter
+   * @function
+   * @this Two#
+   * @param {String} property - The property to add an enumerable getter / setter to.
+   * @description Convenience function to setup the flag based getter / setter that most properties are defined as in Two.js.
+   */
+  var defineGetterSetter = function(property) {
+
+    var object = this;
+    var secret = '_' + property;
+    var flag = '_flag' + property.charAt(0).toUpperCase() + property.slice(1);
+
+    Object.defineProperty(object, property, {
+      enumerable: true,
+      get: function() {
+        return this[secret];
+      },
+      set: function(v) {
+        this[secret] = v;
+        this[flag] = true;
+      }
+    });
+
+  };
+
+  /**
+   * @name Two.Registry
+   * @class
+   * @description An arbitrary class to manage a directory of things. Mainly used for keeping tabs of textures in Two.js.
+   */
+  function Registry() {
+
+    this.map = {};
+
+  }
+
+  _.extend(Registry.prototype, {
+
+    constructor: Registry,
+
+    /**
+     * @name Two.Registry#add
+     * @function
+     * @param {String} id - A unique identifier.
+     * @param value - Any type of variable to be registered to the directory.
+     * @description Adds any value to the directory. Assigned by the `id`.
+     */
+    add: function(id, obj) {
+      this.map[id] = obj;
+      return this;
+    },
+
+    /**
+     * @name Two.Registry#remove
+     * @function
+     * @param {String} id - A unique identifier.
+     * @description Remove any value from the directory by its `id`.
+     */
+    remove: function(id) {
+      delete this.map[id];
+      return this;
+    },
+
+    /**
+     * @name Two.Registry#get
+     * @function
+     * @param {String} id - A unique identifier.
+     * @returns {?Object} The associated value. If unavailable then `undefined` is returned.
+     * @description Get a registered value by its `id`.
+     */
+    get: function(id) {
+      return this.map[id];
+    },
+
+    /**
+     * @name Two.Registry#contains
+     * @function
+     * @param {String} id - A unique identifier.
+     * @returns {Boolean}
+     * @description Convenience method to see if a value is registered to an `id` already.
+     */
+    contains: function(id) {
+      return id in this.map;
+    }
+
+  });
+
+  /**
+   * @name Two.Stop
+   * @class
+   * @param {Number} [offset] - The offset percentage of the stop represented as a zero-to-one value. Default value flip flops from zero-to-one as new stops are created.
+   * @param {String} [color] - The color of the stop. Default value flip flops from white to black as new stops are created.
+   * @param {Number} [opacity] - The opacity value. Default value is 1, cannot be lower than 0.
+   * @nota-bene Used specifically in conjunction with {@link Two.Gradient}s to control color graduation.
+   */
+  function Stop(offset, color, opacity) {
+
+    /**
+     * @name Two.Stop#renderer
+     * @property {Object}
+     * @description Object access to store relevant renderer specific variables. Warning: manipulating this object can create unintended consequences.
+     * @nota-bene With the {@link Two.SvgRenderer} you can access the underlying SVG element created via `shape.renderer.elem`.
+     */
+    this.renderer = {};
+    this._renderer.type = 'stop';
+
+    /**
+     * @name Two.Stop#offset
+     * @property {Number} - The offset percentage of the stop represented as a zero-to-one value.
+     */
+    this.offset = typeof offset === 'number' ? offset
+      : Stop.Index <= 0 ? 0 : 1;
+
+    /**
+     * @name Two.Stop#opacity
+     * @property {Number} - The alpha percentage of the stop represented as a zero-to-one value.
+     */
+    this.opacity = typeof opacity === 'number' ? opacity : 1;
+
+    /**
+     * @name Two.Stop#color
+     * @property {String} - The color of the stop.
+     */
+    this.color = (typeof color === 'string') ? color
+      : Stop.Index <= 0 ? '#fff' : '#000';
+
+    Stop.Index = (Stop.Index + 1) % 2;
+
+  }
+
+  _.extend(Stop, {
+
+    /**
+     * @name Two.Stop.Index
+     * @property {Number} - The current index being referenced for calculating a stop's default offset value.
+     */
+    Index: 0,
+
+    /**
+     * @name Two.Stop.Properties
+     * @property {String[]} - A list of properties that are on every {@link Two.Stop}.
+     */
+    Properties: [
+      'offset',
+      'opacity',
+      'color'
+    ],
+
+    /**
+     * @name Two.Stop.MakeObservable
+     * @function
+     * @param {Object} object - The object to make observable.
+     * @description Convenience function to apply observable qualities of a {@link Two.Stop} to any object. Handy if you'd like to extend the {@link Two.Stop} class on a custom class.
+     */
+    MakeObservable: function(object) {
+
+      _.each(Stop.Properties, function(property) {
+
+        var object = this;
+        var secret = '_' + property;
+        var flag = '_flag' + property.charAt(0).toUpperCase() + property.slice(1);
+
+        Object.defineProperty(object, property, {
+          enumerable: true,
+          get: function() {
+            return this[secret];
+          },
+          set: function(v) {
+            this[secret] = v;
+            this[flag] = true;
+            if (this.parent) {
+              this.parent._flagStops = true;
+            }
+          }
+        });
+
+      }, object);
+
+      Object.defineProperty(object, 'renderer', {
+
+        enumerable: false,
+
+        get: function() {
+          return this._renderer;
+        },
+
+        set: function(obj) {
+          this._renderer = obj;
+        }
+
+      });
+
+    }
+
+  });
+
+  _.extend(Stop.prototype, Events, {
+
+    constructor: Stop,
+
+    /**
+     * @name Two.Stop#clone
+     * @function
+     * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+     * @returns {Two.Stop}
+     * @description Create a new instance of {@link Two.Stop} with the same properties of the current path.
+     */
+    clone: function() {
+
+      var clone = new Stop();
+
+      _.each(Stop.Properties, function(property) {
+        clone[property] = this[property];
+      }, this);
+
+      return clone;
+
+    },
+
+    /**
+     * @name Two.Stop#toObject
+     * @function
+     * @returns {Object}
+     * @description Return a JSON compatible plain object that represents the path.
+     */
+    toObject: function() {
+
+      var result = {};
+
+      _.each(Stop.Properties, function(k) {
+        result[k] = this[k];
+      }, this);
+
+      return result;
+
+    },
+
+    /**
+     * @name Two.Stop#flagReset
+     * @function
+     * @private
+     * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+     */
+    flagReset: function() {
+
+      this._flagOffset = this._flagColor = this._flagOpacity = false;
+
+      return this;
+
+    }
+
+  });
+
+  Stop.MakeObservable(Stop.prototype);
+
+  /**
+   * @name Two.Gradient
+   * @class
+   * @param {Two.Stop[]} [stops] - A list of {@link Two.Stop}s that contain the gradient fill pattern for the gradient.
+   * @description This is the base class for constructing different types of gradients with Two.js. The two common gradients are {@link Two.LinearGradient} and {@link Two.RadialGradient}.
+   */
+  function Gradient(stops) {
+
+    /**
+     * @name Two.Gradient#renderer
+     * @property {Object}
+     * @description Object access to store relevant renderer specific variables. Warning: manipulating this object can create unintended consequences.
+     * @nota-bene With the {@link Two.SvgRenderer} you can access the underlying SVG element created via `shape.renderer.elem`.
+     */
+    this.renderer = {};
+    this._renderer.type = 'gradient';
+
+    /**
+     * @name Two.Gradient#id
+     * @property {String} - Session specific unique identifier.
+     * @nota-bene In the {@link Two.SvgRenderer} change this to change the underlying SVG element's id too.
+     */
+    this.id = Constants.Identifier + Constants.uniqueId();
+    this.classList = [];
+
+    this._renderer.flagStops = Gradient.FlagStops.bind(this);
+    this._renderer.bindStops = Gradient.BindStops.bind(this);
+    this._renderer.unbindStops = Gradient.UnbindStops.bind(this);
+
+    /**
+     * @name Two.Gradient#spread
+     * @property {String} - Indicates what happens if the gradient starts or ends inside the bounds of the target rectangle. Possible values are `'pad'`, `'reflect'`, and `'repeat'`.
+     * @see {@link https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementSpreadMethodAttribute} for more information
+     */
+    this.spread = 'pad';
+
+    /**
+     * @name Two.Gradient#stops
+     * @property {Two.Stop[]} - An ordered list of {@link Two.Stop}s for rendering the gradient.
+     */
+    if (stops) {
+      this.stops = stops;
+    }
+
+  }
+
+  _.extend(Gradient, {
+
+    /**
+     * @name Two.Gradient.Stop
+     * @see {@link Two.Stop}
+     */
+    Stop: Stop,
+
+    /**
+     * @name Two.Gradient.Properties
+     * @property {String[]} - A list of properties that are on every {@link Two.Gradient}.
+     */
+    Properties: [
+      'spread'
+    ],
+
+    /**
+     * @name Two.Gradient.MakeObservable
+     * @function
+     * @param {Object} object - The object to make observable.
+     * @description Convenience function to apply observable qualities of a {@link Two.Gradient} to any object. Handy if you'd like to extend the {@link Two.Gradient} class on a custom class.
+     */
+    MakeObservable: function(object) {
+
+      _.each(Gradient.Properties, defineGetterSetter, object);
+
+      Object.defineProperty(object, 'stops', {
+
+        enumerable: true,
+
+        get: function() {
+          return this._stops;
+        },
+
+        set: function(stops) {
+
+          var bindStops = this._renderer.bindStops;
+          var unbindStops = this._renderer.unbindStops;
+
+          // Remove previous listeners
+          if (this._stops) {
+            this._stops
+              .unbind(Events.Types.insert, bindStops)
+              .unbind(Events.Types.remove, unbindStops);
+          }
+
+          // Create new Collection with copy of Stops
+          this._stops = new Collection((stops || []).slice(0));
+
+          // Listen for Collection changes and bind / unbind
+          this._stops
+            .bind(Events.Types.insert, bindStops)
+            .bind(Events.Types.remove, unbindStops);
+
+          // Bind Initial Stops
+          bindStops(this._stops);
+
+        }
+
+      });
+
+      Object.defineProperty(object, 'renderer', {
+
+        enumerable: false,
+
+        get: function() {
+          return this._renderer;
+        },
+
+        set: function(obj) {
+          this._renderer = obj;
+        }
+
+      });
+
+      Object.defineProperty(object, 'id', {
+
+        enumerable: true,
+
+        get: function() {
+          return this._id;
+        },
+
+        set: function(v) {
+          this._id = v;
+        }
+
+      });
+
+    },
+
+    /**
+     * @name Two.Gradient.FlagStops
+     * @function
+     * @description Cached method to let renderers know stops have been updated on a {@link Two.Gradient}.
+     */
+    FlagStops: function() {
+      this._flagStops = true;
+    },
+
+    /**
+     * @name Two.Gradient.BindVertices
+     * @function
+     * @description Cached method to let {@link Two.Gradient} know vertices have been added to the instance.
+     */
+    BindStops: function(items) {
+
+      // This function is called a lot
+      // when importing a large SVG
+      var i = items.length;
+      while(i--) {
+        items[i].bind(Events.Types.change, this._renderer.flagStops);
+        items[i].parent = this;
+      }
+
+      this._renderer.flagStops();
+
+    },
+
+    /**
+     * @name Two.Gradient.UnbindStops
+     * @function
+     * @description Cached method to let {@link Two.Gradient} know vertices have been removed from the instance.
+     */
+    UnbindStops: function(items) {
+
+      var i = items.length;
+      while(i--) {
+        items[i].unbind(Events.Types.change, this._renderer.flagStops);
+        delete items[i].parent;
+      }
+
+      this._renderer.flagStops();
+
+    }
+
+  });
+
+  _.extend(Gradient.prototype, Events, {
+
+    constructor: Gradient,
+
+    /**
+     * @name Two.Gradient#_flagId
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Gradient#id} needs updating.
+     */
+    _flagId: false,
+
+    /**
+     * @name Two.Gradient#_flagStops
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Gradient#stops} needs updating.
+     */
+    _flagStops: false,
+    /**
+     * @name Two.Gradient#_flagSpread
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Gradient#spread} needs updating.
+     */
+    _flagSpread: false,
+
+    _id: '',
+
+    /**
+     * @name Two.Gradient#clone
+     * @function
+     * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+     * @returns {Two.Gradient}
+     * @description Create a new instance of {@link Two.Gradient} with the same properties of the current path.
+     */
+    clone: function(parent) {
+
+      var stops = this.stops.map(function(s) {
+        return s.clone();
+      });
+
+      var clone = new Gradient(stops);
+
+      _.each(Gradient.Properties, function(k) {
+        clone[k] = this[k];
+      }, this);
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone;
+
+    },
+
+    /**
+     * @name Two.Gradient#toObject
+     * @function
+     * @returns {Object}
+     * @description Return a JSON compatible plain object that represents the path.
+     */
+    toObject: function() {
+
+      var result = {
+        stops: this.stops.map(function(s) {
+          return s.toObject();
+        })
+      };
+
+      _.each(Gradient.Properties, function(k) {
+        result[k] = this[k];
+      }, this);
+
+      return result;
+
+    },
+
+    /**
+     * @name Two.Gradient#_update
+     * @function
+     * @private
+     * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+     * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+     * @nota-bene Try not to call this method more than once a frame.
+     */
+    _update: function() {
+
+      if (this._flagSpread || this._flagStops) {
+        this.trigger(Events.Types.change);
+      }
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Gradient#flagReset
+     * @function
+     * @private
+     * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+     */
+    flagReset: function() {
+
+      this._flagSpread = this._flagStops = false;
+
+      return this;
+
+    }
+
+  });
+
+  Gradient.MakeObservable(Gradient.prototype);
+
+  /**
+   * @name Two.LinearGradient
+   * @class
+   * @extends Two.Gradient
+   * @param {Number} [x1=0] - The x position of the first end point of the linear gradient.
+   * @param {Number} [y1=0] - The y position of the first end point of the linear gradient.
+   * @param {Number} [x2=0] - The x position of the second end point of the linear gradient.
+   * @param {Number} [y2=0] - The y position of the second end point of the linear gradient.
+   * @param {Two.Stop[]} [stops] - A list of {@link Two.Stop}s that contain the gradient fill pattern for the gradient.
+   * @nota-bene The linear gradient lives within the space of the parent object's matrix space.
+   */
+  function LinearGradient(x1, y1, x2, y2, stops) {
+
+    Gradient.call(this, stops);
+
+    this._renderer.type = 'linear-gradient';
+
+    var flagEndPoints = LinearGradient.FlagEndPoints.bind(this);
+
+    /**
+     * @name Two.LinearGradient#left
+     * @property {Two.Vector} - The x and y value for where the first end point is placed on the canvas.
+     */
+    this.left = new Vector().bind(Events.Types.change, flagEndPoints);
+    /**
+     * @name Two.LinearGradient#right
+     * @property {Two.Vector} - The x and y value for where the second end point is placed on the canvas.
+     */
+    this.right = new Vector().bind(Events.Types.change, flagEndPoints);
+
+    if (typeof x1 === 'number') {
+      this.left.x = x1;
+    }
+    if (typeof y1 === 'number') {
+      this.left.y = y1;
+    }
+    if (typeof x2 === 'number') {
+      this.right.x = x2;
+    }
+    if (typeof y2 === 'number') {
+      this.right.y = y2;
+    }
+
+  }
+
+  _.extend(LinearGradient, {
+
+    /**
+     * @name Two.LinearGradient.Stop
+     * @see {@link Two.Stop}
+     */
+    Stop: Stop,
+
+    /**
+     * @name Two.LinearGradient.MakeObservable
+     * @function
+     * @param {Object} object - The object to make observable.
+     * @description Convenience function to apply observable qualities of a {@link Two.LinearGradient} to any object. Handy if you'd like to extend the {@link Two.LinearGradient} class on a custom class.
+     */
+    MakeObservable: function(object) {
+      Gradient.MakeObservable(object);
+    },
+
+    /**
+     * @name Two.LinearGradient.FlagEndPoints
+     * @function
+     * @description Cached method to let renderers know end points have been updated on a {@link Two.LinearGradient}.
+     */
+    FlagEndPoints: function() {
+      this._flagEndPoints = true;
+    }
+
+  });
+
+  _.extend(LinearGradient.prototype, Gradient.prototype, {
+
+    constructor: LinearGradient,
+
+    /**
+     * @name Two.LinearGradient#_flagEndPoints
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.LinearGradient#left} or {@link Two.LinearGradient#right} changed and needs to update.
+     */
+    _flagEndPoints: false,
+
+    /**
+     * @name Two.LinearGradient#clone
+     * @function
+     * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+     * @returns {Two.Gradient}
+     * @description Create a new instance of {@link Two.LinearGradient} with the same properties of the current path.
+     */
+    clone: function(parent) {
+
+      var stops = this.stops.map(function(stop) {
+        return stop.clone();
+      });
+
+      var clone = new LinearGradient(this.left._x, this.left._y,
+        this.right._x, this.right._y, stops);
+
+      _.each(Gradient.Properties, function(k) {
+        clone[k] = this[k];
+      }, this);
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone;
+
+    },
+
+    /**
+     * @name Two.LinearGradient#toObject
+     * @function
+     * @returns {Object}
+     * @description Return a JSON compatible plain object that represents the path.
+     */
+    toObject: function() {
+
+      var result = Gradient.prototype.toObject.call(this);
+
+      result.left = this.left.toObject();
+      result.right = this.right.toObject();
+
+      return result;
+
+    },
+
+    /**
+     * @name Two.LinearGradient#_update
+     * @function
+     * @private
+     * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+     * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+     * @nota-bene Try not to call this method more than once a frame.
+     */
+    _update: function() {
+
+      if (this._flagEndPoints || this._flagSpread || this._flagStops) {
+        this.trigger(Events.Types.change);
+      }
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.LinearGradient#flagReset
+     * @function
+     * @private
+     * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+     */
+    flagReset: function() {
+
+      this._flagEndPoints = false;
+
+      Gradient.prototype.flagReset.call(this);
+
+      return this;
+
+    }
+
+  });
+
+  LinearGradient.MakeObservable(LinearGradient.prototype);
+
+  /**
+   * @name Two.RadialGradient
+   * @class
+   * @extends Two.Gradient
+   * @param {Number} [x=0] - The x position of the origin of the radial gradient.
+   * @param {Number} [y=0] - The y position of the origin of the radial gradient.
+   * @param {Number} [radius=0] - The radius of the radial gradient.
+   * @param {Two.Stop[]} [stops] - A list of {@link Two.Stop}s that contain the gradient fill pattern for the gradient.
+   * @param {Number} [focalX=0] - The x position of the focal point on the radial gradient.
+   * @param {Number} [focalY=0] - The y position of the focal point on the radial gradient.
+   * @nota-bene The radial gradient lives within the space of the parent object's matrix space.
+   */
+  function RadialGradient(cx, cy, r, stops, fx, fy) {
+
+    Gradient.call(this, stops);
+
+    this._renderer.type = 'radial-gradient';
+
+    /**
+     * @name Two.RadialGradient#center
+     * @property {Two.Vector} - The x and y value for where the origin of the radial gradient is.
+     */
+    this.center = new Vector()
+      .bind(Events.Types.change, (function() {
+        this._flagCenter = true;
+      }).bind(this));
+
+    this.radius = typeof r === 'number' ? r : 20;
+
+    /**
+     * @name Two.RadialGradient#focal
+     * @property {Two.Vector} - The x and y value for where the focal point of the radial gradient is.
+     * @nota-bene This effects the spray or spread of the radial gradient.
+     */
+    this.focal = new Vector()
+      .bind(Events.Types.change, (function() {
+        this._flagFocal = true;
+      }).bind(this));
+
+    if (typeof cx === 'number') {
+      this.center.x = cx;
+    }
+    if (typeof cy === 'number') {
+      this.center.y = cy;
+    }
+
+    this.focal.copy(this.center);
+
+    if (typeof fx === 'number') {
+      this.focal.x = fx;
+    }
+    if (typeof fy === 'number') {
+      this.focal.y = fy;
+    }
+
+  }
+
+  _.extend(RadialGradient, {
+
+    /**
+     * @name Two.RadialGradient.Stop
+     * @see {@link Two.Stop}
+     */
+    Stop: Stop,
+
+    /**
+     * @name Two.RadialGradient.Properties
+     * @property {String[]} - A list of properties that are on every {@link Two.RadialGradient}.
+     */
+    Properties: [
+      'radius'
+    ],
+
+    /**
+     * @name Two.RadialGradient.MakeObservable
+     * @function
+     * @param {Object} object - The object to make observable.
+     * @description Convenience function to apply observable qualities of a {@link Two.RadialGradient} to any object. Handy if you'd like to extend the {@link Two.RadialGradient} class on a custom class.
+     */
+    MakeObservable: function(object) {
+
+      Gradient.MakeObservable(object);
+
+      _.each(RadialGradient.Properties, defineGetterSetter, object);
+
+    }
+
+  });
+
+  _.extend(RadialGradient.prototype, Gradient.prototype, {
+
+    constructor: RadialGradient,
+
+    /**
+     * @name Two.RadialGradient#_flagRadius
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.RadialGradient#radius} changed and needs to update.
+     */
+    _flagRadius: false,
+    /**
+     * @name Two.RadialGradient#_flagCenter
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.RadialGradient#center} changed and needs to update.
+     */
+    _flagCenter: false,
+    /**
+     * @name Two.RadialGradient#_flagFocal
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.RadialGradient#focal} changed and needs to update.
+     */
+    _flagFocal: false,
+
+    /**
+     * @name Two.RadialGradient#clone
+     * @function
+     * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+     * @returns {Two.Gradient}
+     * @description Create a new instance of {@link Two.RadialGradient} with the same properties of the current path.
+     */
+    clone: function(parent) {
+
+      var stops = this.stops.map(function(stop) {
+        return stop.clone();
+      });
+
+      var clone = new RadialGradient(this.center._x, this.center._y,
+          this._radius, stops, this.focal._x, this.focal._y);
+
+      _.each(Gradient.Properties.concat(RadialGradient.Properties), function(k) {
+        clone[k] = this[k];
+      }, this);
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone;
+
+    },
+
+    /**
+     * @name Two.RadialGradient#toObject
+     * @function
+     * @returns {Object}
+     * @description Return a JSON compatible plain object that represents the path.
+     */
+    toObject: function() {
+
+      var result = Gradient.prototype.toObject.call(this);
+
+      _.each(RadialGradient.Properties, function(k) {
+        result[k] = this[k];
+      }, this);
+
+      result.center = this.center.toObject();
+      result.focal = this.focal.toObject();
+
+      return result;
+
+    },
+
+    /**
+     * @name Two.RadialGradient#_update
+     * @function
+     * @private
+     * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+     * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+     * @nota-bene Try not to call this method more than once a frame.
+     */
+    _update: function() {
+
+      if (this._flagRadius || this._flatCenter || this._flagFocal
+        || this._flagSpread || this._flagStops) {
+        this.trigger(Events.Types.change);
+      }
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.RadialGradient#flagReset
+     * @function
+     * @private
+     * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+     */
+    flagReset: function() {
+
+      this._flagRadius = this._flagCenter = this._flagFocal = false;
+
+      Gradient.prototype.flagReset.call(this);
+
+      return this;
+
+    }
+
+  });
+
+  RadialGradient.MakeObservable(RadialGradient.prototype);
+
+  var anchor;
+  var regex$1 = {
+    video: /\.(mp4|webm|ogg)$/i,
+    image: /\.(jpe?g|png|gif|tiff|webp)$/i,
+    effect: /texture|gradient/i
+  };
+
+  if (root$1.document) {
+    anchor = document.createElement('a');
+  }
+
+  /**
+   * @name Two.Texture
+   * @class
+   * @extends Two.Shape
+   * @param {String|HTMLImageElement} [src] - The URL path to an image file or an `<img />` element.
+   * @param {Function} [callback] - An optional callback function once the image has been loaded.
+   * @description Fundamental to work with bitmap data, a.k.a. pregenerated imagery, in Two.js. Supported formats include jpg, png, gif, and tiff. See {@link Two.Texture.RegularExpressions} for a full list of supported formats.
+   */
+  function Texture(src, callback) {
+
+    /**
+     * @name Two.Texture#renderer
+     * @property {Object}
+     * @description Object access to store relevant renderer specific variables. Warning: manipulating this object can create unintended consequences.
+     * @nota-bene With the {@link Two.SvgRenderer} you can access the underlying SVG element created via `shape.renderer.elem`.
+     */
+    this.renderer = {};
+    this._renderer.type = 'texture';
+    this._renderer.flagOffset = Texture.FlagOffset.bind(this);
+    this._renderer.flagScale = Texture.FlagScale.bind(this);
+
+    this.id = Constants.Identifier + Constants.uniqueId();
+    this.classList = [];
+
+    /**
+     * @name Two.Texture#loaded
+     * @property {Boolean} - Shorthand value to determine if image has been loaded into the texture.
+     */
+    this.loaded = false;
+
+    /**
+     * @name Two.Texture#repeat
+     * @property {String} - CSS style declaration to tile {@link Two.Path}. Valid values include: `'no-repeat'`, `'repeat'`, `'repeat-x'`, `'repeat-y'`.
+     * @see {@link https://www.w3.org/TR/2dcontext/#dom-context-2d-createpattern}
+     */
+    this.repeat = 'no-repeat';
+
+    /**
+     * @name Two.Texture#offset
+     * @property {Two.Vector} - A two-component vector describing any pixel offset of the texture when applied to a {@link Two.Path}.
+     */
+    this.offset = new Vector();
+
+    if (typeof callback === 'function') {
+      var loaded = (function() {
+        this.unbind(Events.Types.load, loaded);
+        if (typeof callback === 'function') {
+          callback();
+        }
+      }).bind(this);
+      this.bind(Events.Types.load, loaded);
+    }
+
+    /**
+     * @name Two.Texture#src
+     * @property {String} - The URL path to the image data.
+     * @nota-bene This property is ultimately serialized in a {@link Two.Registry} to cache retrieval.
+     */
+    if (typeof src === 'string') {
+      this.src = src;
+    } else if (typeof src === 'object') {
+      var elemString = Object.prototype.toString.call(src);
+      if (
+        elemString === '[object HTMLImageElement]' ||
+        elemString === '[object HTMLCanvasElement]' ||
+        elemString === '[object HTMLVideoElement]' ||
+        elemString === '[object Image]'
+      ) {
+        /**
+         * @name Two.Texture#image
+         * @property {Element} - The corresponding DOM Element of the texture. Can be a `<img />`, `<canvas />`, or `<video />` element. See {@link Two.Texture.RegularExpressions} for a full list of supported elements.
+         * @nota-bene In headless environments this is a `Canvas.Image` object. See {@link https://github.com/Automattic/node-canvas} for more information on headless image objects.
+         */
+        this.image = src;
+      }
+    }
+
+    this._update();
+
+  }
+
+  _.extend(Texture, {
+
+    /**
+     * @name Two.Texture.Properties
+     * @property {String[]} - A list of properties that are on every {@link Two.Texture}.
+     */
+    Properties: [
+      'id',
+      'src',
+      'loaded',
+      'repeat'
+    ],
+
+    /**
+     * @name Two.Texture.RegularExpressions
+     * @property {Object} - A map of compatible DOM Elements categorized by media format.
+     */
+    RegularExpressions: regex$1,
+
+    /**
+     * @name Two.Texture.ImageRegistry
+     * @property {Two.Registry} - A canonical listing of image data used in a single session of Two.js.
+     * @nota-bene This object is used to cache image data between different textures.
+     */
+    ImageRegistry: new Registry(),
+
+    /**
+     * @name Two.Texture.getAbsoluteURL
+     * @property {Function} - Serializes a URL as an absolute path for canonical attribution in {@link Two.ImageRegistry}.
+     * @param {String} path
+     * @returns {String} - The serialized absolute path.
+     */
+    getAbsoluteURL: function(path) {
+      if (!anchor) {
+        // TODO: Fix for headless environments
+        return path;
+      }
+      anchor.href = path;
+      return anchor.href;
+    },
+
+    /**
+     * @name Two.Texture.loadHeadlessBuffer
+     * @property {Function} - Loads an image as a buffer in headless environments.
+     * @param {Two.Texture} texture - The {@link Two.Texture} to be loaded.
+     * @param {Function} loaded - The callback function to be triggered once the image is loaded.
+     * @nota-bene - This function uses node's `fs.readFileSync` to spoof the `<img />` loading process in the browser.
+     */
+    loadHeadlessBuffer: function(texture, loaded) {
+
+      texture.image.onload = loaded;
+      texture.image.src = texture.src;
+
+    },
+
+    /**
+     * @name Two.Texture.getTag
+     * @property {Function} - Retrieves the tag name of an image, video, or canvas node.
+     * @param {HTMLImageElement} - The image to infer the tag name from.
+     * @returns {String} - Returns the tag name of an image, video, or canvas node.
+     */
+    getTag: function(image) {
+      return (image && image.nodeName && image.nodeName.toLowerCase())
+        // Headless environments
+        || 'img';
+    },
+
+    /**
+     * @name Two.Texture.getImage
+     * @property {Function} - Convenience function to set {@link Two.Texture#image} properties with canonincal versions set in {@link Two.Texture.ImageRegistry}.
+     * @param {String} src - The URL path of the image.
+     * @returns {HTMLImageElement} - Returns either a cached version of the image or a new one that is registered in {@link Two.Texture.ImageRegistry}.
+     */
+    getImage: function(src) {
+
+      var absoluteSrc = Texture.getAbsoluteURL(src);
+
+      if (Texture.ImageRegistry.contains(absoluteSrc)) {
+        return Texture.ImageRegistry.get(absoluteSrc);
+      }
+
+      var image;
+
+      if (CanvasShim.Image) {
+
+        // TODO: Fix for headless environments
+        image = new CanvasShim.Image();
+        Renderer$2.Utils.shim(image, 'img');
+
+      } else if (root$1.document) {
+
+        if (regex$1.video.test(absoluteSrc)) {
+          image = document.createElement('video');
+        } else {
+          image = document.createElement('img');
+        }
+
+      } else {
+
+        console.warn('Two.js: no prototypical image defined for Two.Texture');
+
+      }
+
+      image.crossOrigin = 'anonymous';
+
+      return image;
+
+    },
+
+    /**
+     * @name Two.Register
+     * @interface
+     * @description A collection of functions to register different types of textures. Used internally by a {@link Two.Texture}.
+     */
+    Register: {
+      canvas: function(texture, callback) {
+        texture._src = '#' + texture.id;
+        Texture.ImageRegistry.add(texture.src, texture.image);
+        if (typeof callback === 'function') {
+          callback();
+        }
+      },
+      img: function(texture, callback) {
+
+        var image = texture.image;
+
+        var loaded = function(e) {
+          if (!CanvasShim.isHeadless && image.removeEventListener && typeof image.removeEventListener === 'function') {
+            image.removeEventListener('load', loaded, false);
+            image.removeEventListener('error', error, false);
+          }
+          if (typeof callback === 'function') {
+            callback();
+          }
+        };
+        var error = function(e) {
+          if (!CanvasShim.isHeadless && typeof image.removeEventListener === 'function') {
+            image.removeEventListener('load', loaded, false);
+            image.removeEventListener('error', error, false);
+          }
+          throw new TwoError('unable to load ' + texture.src);
+        };
+
+        if (typeof image.width === 'number' && image.width > 0
+          && typeof image.height === 'number' && image.height > 0) {
+            loaded();
+        } else if (!CanvasShim.isHeadless && typeof image.addEventListener === 'function') {
+          image.addEventListener('load', loaded, false);
+          image.addEventListener('error', error, false);
+        }
+
+        texture._src = Texture.getAbsoluteURL(texture._src);
+
+        if (!CanvasShim.isHeadless && image && image.getAttribute('two-src')) {
+          return;
+        }
+
+        if (!CanvasShim.isHeadless) {
+          image.setAttribute('two-src', texture.src);
+        }
+
+        Texture.ImageRegistry.add(texture.src, image);
+
+        if (CanvasShim.isHeadless) {
+
+          Texture.loadHeadlessBuffer(texture, loaded);
+
+        } else {
+
+          texture.image.src = texture.src;
+
+        }
+
+      },
+      video: function(texture, callback) {
+
+        if (CanvasShim.isHeadless) {
+          throw new TwoError('video textures are not implemented in headless environments.');
+        }
+
+        var loaded = function(e) {
+          texture.image.removeEventListener('canplaythrough', loaded, false);
+          texture.image.removeEventListener('error', error, false);
+          texture.image.width = texture.image.videoWidth;
+          texture.image.height = texture.image.videoHeight;
+          if (typeof callback === 'function') {
+            callback();
+          }
+        };
+        var error = function(e) {
+          texture.image.removeEventListener('canplaythrough', loaded, false);
+          texture.image.removeEventListener('error', error, false);
+          throw new TwoError('unable to load ' + texture.src);
+        };
+
+        texture._src = Texture.getAbsoluteURL(texture._src);
+
+        if (!texture.image.getAttribute('two-src')) {
+          texture.image.setAttribute('two-src', texture.src);
+          Texture.ImageRegistry.add(texture.src, texture.image);
+        }
+
+        if (texture.image.readyState >= 4) {
+          loaded();
+        } else {
+          texture.image.addEventListener('canplaythrough', loaded, false);
+          texture.image.addEventListener('error', error, false);
+          texture.image.src = texture.src;
+          texture.image.load();
+        }
+
+      }
+    },
+
+    /**
+     * @name Two.Texture.load
+     * @function
+     * @param {Two.Texture} texture - The texture to load.
+     * @param {Function} callback - The function to be called once the texture is loaded.
+     */
+    load: function(texture, callback) {
+
+      var image = texture.image;
+      var tag = Texture.getTag(image);
+
+      if (texture._flagImage) {
+        if (/canvas/i.test(tag)) {
+          Texture.Register.canvas(texture, callback);
+        } else {
+          texture._src = (!CanvasShim.isHeadless && image.getAttribute('two-src')) || image.src;
+          Texture.Register[tag](texture, callback);
+        }
+      }
+
+      if (texture._flagSrc) {
+        if (!image) {
+          image = Texture.getImage(texture.src);
+          texture.image = image;
+        }
+        tag = Texture.getTag(image);
+        Texture.Register[tag](texture, callback);
+      }
+
+    },
+
+    /**
+     * @name Two.Texture.FlagOffset
+     * @function
+     * @description Cached method to let renderers know `offset` has been updated on a {@link Two.Texture}.
+     */
+    FlagOffset: function() {
+      this._flagOffset = true;
+    },
+
+    /**
+     * @name Two.Texture.FlagScale
+     * @function
+     * @description Cached method to let renderers know `scale` has been updated on a {@link Two.Texture}.
+     */
+    FlagScale: function() {
+      this._flagScale = true;
+    },
+
+    /**
+     * @name Two.Texture.MakeObservable
+     * @function
+     * @param {Object} object - The object to make observable.
+     * @description Convenience function to apply observable qualities of a {@link Two.Texture} to any object. Handy if you'd like to extend or inherit the {@link Two.Texture} class on a custom class.
+     */
+    MakeObservable: function(object) {
+
+      _.each(Texture.Properties, defineGetterSetter, object);
+
+      Object.defineProperty(object, 'image', {
+        enumerable: true,
+        get: function() {
+          return this._image;
+        },
+        set: function(image) {
+
+          var tag = Texture.getTag(image);
+          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(Events.Types.change, this._renderer.flagOffset);
+          }
+          this._offset = v;
+          this._offset.bind(Events.Types.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 Vector) {
+            this._scale.unbind(Events.Types.change, this._renderer.flagScale);
+          }
+
+          this._scale = v;
+
+          if (this._scale instanceof Vector) {
+            this._scale.bind(Events.Types.change, this._renderer.flagScale);
+          }
+
+          this._flagScale = true;
+
+        }
+      });
+
+      Object.defineProperty(object, 'renderer', {
+
+        enumerable: false,
+
+        get: function() {
+          return this._renderer;
+        },
+
+        set: function(obj) {
+          this._renderer = obj;
+        }
+
+      });
+
+    }
+
+  });
+
+  _.extend(Texture.prototype, Events, Shape.prototype, {
+
+    constructor: Texture,
+
+    /**
+     * @name Two.Texture#_flagId
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Texture#id} needs updating.
+     */
+    _flagId: false,
+
+    /**
+     * @name Two.Texture#_flagSrc
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Texture#src} needs updating.
+     */
+    _flagSrc: false,
+
+    /**
+     * @name Two.Texture#_flagImage
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Texture#image} needs updating.
+     */
+    _flagImage: false,
+
+    /**
+     * @name Two.Texture#_flagVideo
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Texture#video} needs updating.
+     */
+    _flagVideo: false,
+
+    /**
+     * @name Two.Texture#_flagLoaded
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Texture#loaded} needs updating.
+     */
+    _flagLoaded: false,
+
+    /**
+     * @name Two.Texture#_flagRepeat
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Texture#repeat} needs updating.
+     */
+    _flagRepeat: false,
+
+    /**
+     * @name Two.Texture#_flagOffset
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Texture#offset} needs updating.
+     */
+    _flagOffset: false,
+
+    /**
+     * @name Two.Texture#_flagScale
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Texture#scale} needs updating.
+     */
+    _flagScale: false,
+
+    /**
+     * @name Two.Texture#_id
+     * @private
+     * @see {@link Two.Texture#id}
+     */
+    _id: '',
+
+    /**
+     * @name Two.Texture#_src
+     * @private
+     * @see {@link Two.Texture#src}
+     */
+    _src: '',
+
+    /**
+     * @name Two.Texture#_image
+     * @private
+     * @see {@link Two.Texture#image}
+     */
+    _image: null,
+
+    /**
+     * @name Two.Texture#_loaded
+     * @private
+     * @see {@link Two.Texture#loaded}
+     */
+    _loaded: false,
+
+    /**
+     * @name Two.Texture#_repeat
+     * @private
+     * @see {@link Two.Texture#repeat}
+     */
+    _repeat: 'no-repeat',
+
+    /**
+     * @name Two.Texture#_scale
+     * @private
+     * @see {@link Two.Texture#scale}
+     */
+    _scale: 1,
+
+    /**
+     * @name Two.Texture#_offset
+     * @private
+     * @see {@link Two.Texture#offset}
+     */
+    _offset: null,
+
+    /**
+     * @name Two.Texture#clone
+     * @function
+     * @returns {Two.Texture}
+     * @description Create a new instance of {@link Two.Texture} with the same properties of the current texture.
+     */
+    clone: function() {
+      var clone = new Texture(this.src);
+      clone.repeat = this.repeat;
+      clone.offset.copy(this.origin);
+      clone.scale = this.scale;
+      return clone;
+    },
+
+    /**
+     * @name Two.Texture#toObject
+     * @function
+     * @returns {Object}
+     * @description Return a JSON compatible plain object that represents the texture.
+     */
+    toObject: function() {
+      return {
+        src: this.src,
+        // image: this.image,
+        repeat: this.repeat,
+        origin: this.origin.toObject(),
+        scale: typeof this.scale === 'number' ? this.scale : this.scale.toObject()
+      };
+    },
+
+    /**
+     * @name Two.Texture#_update
+     * @function
+     * @private
+     * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+     * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+     * @nota-bene Try not to call this method more than once a frame.
+     */
+    _update: function() {
+
+      if (this._flagSrc || this._flagImage) {
+
+        this.trigger(Events.Types.change);
+
+        if (this._flagSrc || this._flagImage) {
+          this.loaded = false;
+          Texture.load(this, (function() {
+            this.loaded = true;
+            this
+              .trigger(Events.Types.change)
+              .trigger(Events.Types.load);
+          }).bind(this));
+        }
+
+      }
+
+      if (this._image && this._image.readyState >= 4) {
+        this._flagVideo = true;
+      }
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Texture#flagReset
+     * @function
+     * @private
+     * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+     */
+    flagReset: function() {
+
+      this._flagSrc = this._flagImage = this._flagLoaded
+        = this._flagVideo = this._flagScale = this._flagOffset = false;
+
+      return this;
+
+    }
+
+  });
+
+  Texture.MakeObservable(Texture.prototype);
+
+  // Constants
+
+  var min$1 = Math.min, max$1 = Math.max,
+    ceil = Math.ceil, floor = Math.floor;
+
+  /**
+   * @name Two.Path
+   * @class
+   * @extends Two.Shape
+   * @param {Two.Anchor[]} [vertices] - A list of {@link Two.Anchor}s that represent the order and coordinates to construct the rendered shape.
+   * @param {Boolean} [closed=false] - Describes whether the shape is closed or open.
+   * @param {Boolean} [curved=false] - Describes whether the shape automatically calculates bezier handles for each vertex.
+   * @param {Boolean} [manual=false] - Describes whether the developer controls how vertices are plotted or if Two.js automatically plots coordinates based on closed and curved booleans.
+   * @description This is the primary primitive class for creating all drawable shapes in Two.js. Unless specified methods return their instance of `Two.Path` for the purpose of chaining.
+   */
+  function Path(vertices, closed, curved, manual) {
+
+    Shape.call(this);
+
+    this._renderer.type = 'path';
+    this._renderer.flagVertices = Path.FlagVertices.bind(this);
+    this._renderer.bindVertices = Path.BindVertices.bind(this);
+    this._renderer.unbindVertices = Path.UnbindVertices.bind(this);
+
+    this._renderer.flagFill = Path.FlagFill.bind(this);
+    this._renderer.flagStroke = Path.FlagStroke.bind(this);
+    this._renderer.vertices = [];
+    this._renderer.collection = [];
+
+    /**
+     * @name Two.Path#closed
+     * @property {Boolean} - Determines whether a final line is drawn between the final point in the `vertices` array and the first point.
+     */
+    this._closed = !!closed;
+
+    /**
+     * @name Two.Path#curved
+     * @property {Boolean} - When the path is `automatic = true` this boolean determines whether the lines between the points are curved or not.
+     */
+    this._curved = !!curved;
+
+    /**
+     * @name Two.Path#beginning
+     * @property {Number} - Number between zero and one to state the beginning of where the path is rendered.
+     * @description {@link Two.Path#beginning} is a percentage value that represents at what percentage into the path should the renderer start drawing.
+     * @nota-bene This is great for animating in and out stroked paths in conjunction with {@link Two.Path#ending}.
+     */
+    this.beginning = 0;
+
+    /**
+     * @name Two.Path#ending
+     * @property {Number} - Number between zero and one to state the ending of where the path is rendered.
+     * @description {@link Two.Path#ending} is a percentage value that represents at what percentage into the path should the renderer start drawing.
+     * @nota-bene This is great for animating in and out stroked paths in conjunction with {@link Two.Path#beginning}.
+     */
+    this.ending = 1;
+
+    // Style properties
+
+    /**
+     * @name Two.Path#fill
+     * @property {(String|Two.Gradient|Two.Texture)} - The value of what the path should be filled in with.
+     * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/color_value} for more information on CSS's colors as `String`.
+     */
+    this.fill = '#fff';
+
+    /**
+     * @name Two.Path#stroke
+     * @property {(String|Two.Gradient|Two.Texture)} - The value of what the path should be outlined in with.
+     * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/color_value} for more information on CSS's colors as `String`.
+     */
+    this.stroke = '#000';
+
+    /**
+     * @name Two.Path#linewidth
+     * @property {Number} - The thickness in pixels of the stroke.
+     */
+    this.linewidth = 1.0;
+
+    /**
+     * @name Two.Path#opacity
+     * @property {Number} - The opaqueness of the path.
+     * @nota-bene Can be used in conjunction with CSS Colors that have an alpha value.
+     */
+    this.opacity = 1.0;
+
+    /**
+     * @name Two.Path#className
+     * @property {String} - A class to be applied to the element to be compatible with CSS styling.
+     * @nota-bene Only available for the SVG renderer.
+     */
+    this.className = '';
+
+    /**
+     * @name Two.Path#visible
+     * @property {Boolean} - Display the path or not.
+     * @nota-bene For {@link Two.CanvasRenderer} and {@link Two.WebGLRenderer} when set to false all updating is disabled improving performance dramatically with many objects in the scene.
+     */
+    this.visible = true;
+
+    /**
+     * @name Two.Path#cap
+     * @property {String}
+     * @see {@link https://www.w3.org/TR/SVG11/painting.html#StrokeLinecapProperty}
+     */
+    this.cap = 'butt';      // Default of Adobe Illustrator
+
+    /**
+     * @name Two.Path#join
+     * @property {String}
+     * @see {@link https://www.w3.org/TR/SVG11/painting.html#StrokeLinejoinProperty}
+     */
+    this.join = 'miter';    // Default of Adobe Illustrator
+
+    /**
+     * @name Two.Path#miter
+     * @property {String}
+     * @see {@link https://www.w3.org/TR/SVG11/painting.html#StrokeMiterlimitProperty}
+     */
+    this.miter = 4;         // Default of Adobe Illustrator
+
+    /**
+     * @name Two.Path#vertices
+     * @property {Two.Anchor[]} - An ordered list of anchor points for rendering the path.
+     * @description A list of {@link Two.Anchor} objects that consist of what form the path takes.
+     * @nota-bene The array when manipulating is actually a {@link Two.Collection}.
+     */
+    this.vertices = vertices;
+
+    /**
+     * @name Two.Path#automatic
+     * @property {Boolean} - Determines whether or not Two.js should calculate curves, lines, and commands automatically for you or to let the developer manipulate them for themselves.
+     */
+    this.automatic = !manual;
+
+    /**
+     * @name Two.Path#dashes
+     * @property {Number[]} - Array of numbers. Odd indices represent dash length. Even indices represent dash space.
+     * @description A list of numbers that represent the repeated dash length and dash space applied to the stroke of the text.
+     * @see {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray} for more information on the SVG stroke-dasharray attribute.
+     */
+    this.dashes = [];
+
+    /**
+     * @name Two.Path#dashes#offset
+     * @property {Number} - A number in pixels to offset {@link Two.Path#dashes} display.
+     */
+    this.dashes.offset = 0;
+
+  }
+
+  _.extend(Path, {
+
+    /**
+     * @name Two.Path.Properties
+     * @property {String[]} - A list of properties that are on every {@link Two.Path}.
+     */
+    Properties: [
+      'fill',
+      'stroke',
+      'linewidth',
+      'opacity',
+      'visible',
+      'cap',
+      'join',
+      'miter',
+
+      'closed',
+      'curved',
+      'automatic',
+      'beginning',
+      'ending'
+    ],
+
+    Utils: {
+      getCurveLength: getCurveLength
+    },
+
+    /**
+     * @name Two.Path.FlagVertices
+     * @function
+     * @description Cached method to let renderers know vertices have been updated on a {@link Two.Path}.
+     */
+    FlagVertices: function() {
+      this._flagVertices = true;
+      this._flagLength = true;
+      if (this.parent) {
+        this.parent._flagLength = true;
+      }
+    },
+
+    /**
+     * @name Two.Path.BindVertices
+     * @function
+     * @description Cached method to let {@link Two.Path} know vertices have been added to the instance.
+     */
+    BindVertices: function(items) {
+
+      // This function is called a lot
+      // when importing a large SVG
+      var i = items.length;
+      while (i--) {
+        items[i].bind(Events.Types.change, this._renderer.flagVertices);
+      }
+
+      this._renderer.flagVertices();
+
+    },
+
+    /**
+     * @name Two.Path.UnbindVertices
+     * @function
+     * @description Cached method to let {@link Two.Path} know vertices have been removed from the instance.
+     */
+    UnbindVertices: function(items) {
+
+      var i = items.length;
+      while (i--) {
+        items[i].unbind(Events.Types.change, this._renderer.flagVertices);
+      }
+
+      this._renderer.flagVertices();
+
+    },
+
+    /**
+     * @name Two.Path.FlagFill
+     * @function
+     * @description Cached method to let {@link Two.Path} know the fill has changed.
+     */
+    FlagFill: function() {
+      this._flagFill = true;
+    },
+
+    /**
+     * @name Two.Path.FlagFill
+     * @function
+     * @description Cached method to let {@link Two.Path} know the stroke has changed.
+     */
+    FlagStroke: function() {
+      this._flagStroke = true;
+    },
+
+    /**
+     * @name Two.Path.MakeObservable
+     * @function
+     * @param {Object} object - The object to make observable.
+     * @description Convenience function to apply observable qualities of a {@link Two.Path} to any object. Handy if you'd like to extend the {@link Two.Path} class on a custom class.
+     */
+    MakeObservable: function(object) {
+
+      Shape.MakeObservable(object);
+
+      // Only the 7 defined properties are flagged like this. The subsequent
+      // properties behave differently and need to be hand written.
+      _.each(Path.Properties.slice(2, 8), defineGetterSetter, object);
+
+      Object.defineProperty(object, 'fill', {
+        enumerable: true,
+        get: function() {
+          return this._fill;
+        },
+        set: function(f) {
+
+          if (this._fill instanceof Gradient
+            || this._fill instanceof LinearGradient
+            || this._fill instanceof RadialGradient
+            || this._fill instanceof Texture) {
+            this._fill.unbind(Events.Types.change, this._renderer.flagFill);
+          }
+
+          this._fill = f;
+          this._flagFill = true;
+
+          if (this._fill instanceof Gradient
+            || this._fill instanceof LinearGradient
+            || this._fill instanceof RadialGradient
+            || this._fill instanceof Texture) {
+            this._fill.bind(Events.Types.change, this._renderer.flagFill);
+          }
+
+        }
+      });
+
+      Object.defineProperty(object, 'stroke', {
+        enumerable: true,
+        get: function() {
+          return this._stroke;
+        },
+        set: function(f) {
+
+          if (this._stroke instanceof Gradient
+            || this._stroke instanceof LinearGradient
+            || this._stroke instanceof RadialGradient
+            || this._stroke instanceof Texture) {
+            this._stroke.unbind(Events.Types.change, this._renderer.flagStroke);
+          }
+
+          this._stroke = f;
+          this._flagStroke = true;
+
+          if (this._stroke instanceof Gradient
+            || this._stroke instanceof LinearGradient
+            || this._stroke instanceof RadialGradient
+            || this._stroke instanceof Texture) {
+            this._stroke.bind(Events.Types.change, this._renderer.flagStroke);
+          }
+
+        }
+      });
+
+      /**
+       * @name Two.Path#length
+       * @property {Number} - The sum of distances between all {@link Two.Path#vertices}.
+       */
+      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 bindVertices = this._renderer.bindVertices;
+          var unbindVertices = this._renderer.unbindVertices;
+
+          // Remove previous listeners
+          if (this._collection) {
+            this._collection
+              .unbind(Events.Types.insert, bindVertices)
+              .unbind(Events.Types.remove, unbindVertices);
+          }
+
+          // Create new Collection with copy of vertices
+          if (vertices instanceof Collection) {
+            this._collection = vertices;
+          } else {
+            this._collection = new Collection(vertices || []);
+          }
+
+
+          // Listen for Collection changes and bind / unbind
+          this._collection
+            .bind(Events.Types.insert, bindVertices)
+            .bind(Events.Types.remove, unbindVertices);
+
+          // Bind Initial Vertices
+          bindVertices(this._collection);
+
+        }
+
+      });
+
+      /**
+       * @name Two.Path#mask
+       * @property {Two.Shape} - The shape whose alpha property becomes a clipping area for the path.
+       * @nota-bene This property is currently not working becuase of SVG spec issues found here {@link https://code.google.com/p/chromium/issues/detail?id=370951}.
+       */
+      Object.defineProperty(object, 'mask', {
+
+        enumerable: true,
+
+        get: function() {
+          return this._mask;
+        },
+
+        set: function(v) {
+          this._mask = v;
+          this._flagMask = true;
+          if (!v.clip) {
+            v.clip = true;
+          }
+        }
+
+      });
+
+      /**
+       * @name Two.Path#clip
+       * @property {Boolean} - Tells Two.js renderer if this object represents a mask for another object (or not).
+       */
+      Object.defineProperty(object, 'clip', {
+        enumerable: true,
+        get: function() {
+          return this._clip;
+        },
+        set: function(v) {
+          this._clip = v;
+          this._flagClip = true;
+        }
+      });
+
+      Object.defineProperty(object, 'dashes', {
+        enumerable: true,
+        get: function() {
+          return this._dashes;
+        },
+        set: function(v) {
+          if (typeof v.offset !== 'number') {
+            v.offset = this._dashes.offset || 0;
+          }
+          this._dashes = v;
+        }
+      });
+
+    }
+
+  });
+
+  _.extend(Path.prototype, Shape.prototype, {
+
+    constructor: Path,
+
+    // Flags
+    // http://en.wikipedia.org/wiki/Flag
+
+    /**
+     * @name Two.Path#_flagVertices
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Path#vertices} need updating.
+     */
+    _flagVertices: true,
+
+    /**
+     * @name Two.Path#_flagLength
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Path#length} needs updating.
+     */
+    _flagLength: true,
+
+    /**
+     * @name Two.Path#_flagFill
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Path#fill} needs updating.
+     */
+    _flagFill: true,
+
+    /**
+     * @name Two.Path#_flagStroke
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Path#stroke} needs updating.
+     */
+    _flagStroke: true,
+
+    /**
+     * @name Two.Path#_flagLinewidth
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Path#linewidth} needs updating.
+     */
+    _flagLinewidth: true,
+
+    /**
+     * @name Two.Path#_flagOpacity
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Path#opacity} needs updating.
+     */
+    _flagOpacity: true,
+
+    /**
+     * @name Two.Path#_flagVisible
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Path#visible} needs updating.
+     */
+    _flagVisible: true,
+
+    /**
+     * @name Two.Path#_flagCap
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Path#cap} needs updating.
+     */
+    _flagCap: true,
+
+    /**
+     * @name Two.Path#_flagJoin
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Path#join} needs updating.
+     */
+    _flagJoin: true,
+
+    /**
+     * @name Two.Path#_flagMiter
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Path#miter} needs updating.
+     */
+    _flagMiter: true,
+
+    /**
+     * @name Two.Path#_flagMask
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Path#mask} needs updating.
+     */
+    _flagMask: false,
+
+    /**
+     * @name Two.Path#_flagClip
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Path#clip} needs updating.
+     */
+    _flagClip: false,
+
+    // Underlying Properties
+
+    /**
+     * @name Two.Path#_length
+     * @private
+     * @see {@link Two.Path#length}
+     */
+    _length: 0,
+
+    /**
+     * @name Two.Path#_fill
+     * @private
+     * @see {@link Two.Path#fill}
+     */
+    _fill: '#fff',
+
+    /**
+     * @name Two.Path#_stroke
+     * @private
+     * @see {@link Two.Path#stroke}
+     */
+    _stroke: '#000',
+
+    /**
+     * @name Two.Path#_linewidth
+     * @private
+     * @see {@link Two.Path#linewidth}
+     */
+    _linewidth: 1.0,
+
+    /**
+     * @name Two.Path#_opacity
+     * @private
+     * @see {@link Two.Path#opacity}
+     */
+    _opacity: 1.0,
+
+    /**
+     * @name Two.Path#_visible
+     * @private
+     * @see {@link Two.Path#visible}
+     */
+    _visible: true,
+
+    /**
+     * @name Two.Path#_cap
+     * @private
+     * @see {@link Two.Path#cap}
+     */
+    _cap: 'round',
+
+    /**
+     * @name Two.Path#_join
+     * @private
+     * @see {@link Two.Path#join}
+     */
+    _join: 'round',
+
+    /**
+     * @name Two.Path#_miter
+     * @private
+     * @see {@link Two.Path#miter}
+     */
+    _miter: 4,
+
+    /**
+     * @name Two.Path#_closed
+     * @private
+     * @see {@link Two.Path#closed}
+     */
+    _closed: true,
+
+    /**
+     * @name Two.Path#_curved
+     * @private
+     * @see {@link Two.Path#curved}
+     */
+    _curved: false,
+
+    /**
+     * @name Two.Path#_automatic
+     * @private
+     * @see {@link Two.Path#automatic}
+     */
+    _automatic: true,
+
+    /**
+     * @name Two.Path#_beginning
+     * @private
+     * @see {@link Two.Path#beginning}
+     */
+    _beginning: 0,
+
+    /**
+     * @name Two.Path#_ending
+     * @private
+     * @see {@link Two.Path#ending}
+     */
+    _ending: 1.0,
+
+    /**
+     * @name Two.Path#_mask
+     * @private
+     * @see {@link Two.Path#mask}
+     */
+    _mask: null,
+
+    /**
+     * @name Two.Path#_clip
+     * @private
+     * @see {@link Two.Path#clip}
+     */
+    _clip: false,
+
+    /**
+     * @name Two.Path#_dashes
+     * @private
+     * @see {@link Two.Path#dashes}
+     */
+    _dashes: [],
+
+    /**
+     * @name Two.Path#clone
+     * @function
+     * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+     * @returns {Two.Path}
+     * @description Create a new instance of {@link Two.Path} with the same properties of the current path.
+     */
+    clone: function(parent) {
+
+      var clone = new Path();
+
+      for (var j = 0; j < this.vertices.length; j++) {
+        clone.vertices.push(this.vertices[j].clone());
+      }
+
+      for (var i = 0; i < Path.Properties.length; i++) {
+        var k = Path.Properties[i];
+        clone[k] = this[k];
+      }
+
+      clone.className = this.className;
+
+      clone.translation.copy(this.translation);
+      clone.rotation = this.rotation;
+      clone.scale = this.scale;
+      clone.skewX = this.skewX;
+      clone.skewY = this.skewY;
+
+      if (this.matrix.manual) {
+        clone.matrix.copy(this.matrix);
+      }
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone._update();
+
+    },
+
+    /**
+     * @name Two.Path#toObject
+     * @function
+     * @returns {Object}
+     * @description Return a JSON compatible plain object that represents the path.
+     */
+    toObject: function() {
+
+      var result = {
+        vertices: this.vertices.map(function(v) {
+          return v.toObject();
+        })
+      };
+
+      _.each(Path.Properties, function(k) {
+        result[k] = this[k];
+      }, this);
+
+      result.className = this.className;
+
+      result.translation = this.translation.toObject();
+      result.rotation = this.rotation;
+      result.scale = this.scale instanceof Vector ? this.scale.toObject() : this.scale;
+      result.skewX = this.skewX;
+      result.skewY = this.skewY;
+
+      if (this.matrix.manual) {
+        result.matrix = this.matrix.toObject();
+      }
+
+      return result;
+
+    },
+
+    /**
+     * @name Two.Path#noFill
+     * @function
+     * @description Short hand method to set fill to `transparent`.
+     */
+    noFill: function() {
+      this.fill = 'transparent';
+      return this;
+    },
+
+    /**
+     * @name Two.Path#noStroke
+     * @function
+     * @description Short hand method to set stroke to `transparent`.
+     */
+    noStroke: function() {
+      this.stroke = undefined;
+      return this;
+    },
+
+    /**
+     * @name Two.Path#corner
+     * @function
+     * @description Orient the vertices of the shape to the upper left-hand corner of the path.
+     */
+    corner: function() {
+
+      var rect = this.getBoundingClientRect(true);
+      var hw = rect.width / 2;
+      var hh = rect.height / 2;
+      var cx = rect.left + rect.width / 2;
+      var cy = rect.top + rect.height / 2;
+
+      for (var i = 0; i < this.vertices.length; i++) {
+        var v = this.vertices[i];
+        v.x -= cx;
+        v.y -= cy;
+        v.x += hw;
+        v.y += hh;
+      }
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Path#center
+     * @function
+     * @description Orient the vertices of the shape to the center of the path.
+     */
+    center: function() {
+
+      var rect = this.getBoundingClientRect(true);
+
+      var cx = rect.left + rect.width / 2 - this.translation.x;
+      var cy = rect.top + rect.height / 2 - this.translation.y;
+
+      for (var i = 0; i < this.vertices.length; i++) {
+        var v = this.vertices[i];
+        v.x -= cx;
+        v.y -= cy;
+      }
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Path#remove
+     * @function
+     * @description Remove self from the scene / parent.
+     */
+    remove: function() {
+
+      if (!this.parent) {
+        return this;
+      }
+
+      this.parent.remove(this);
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Path#getBoundingClientRect
+     * @function
+     * @param {Boolean} [shallow=false] - Describes whether to calculate off local matrix or world matrix.
+     * @returns {Object} - Returns object with top, left, right, bottom, width, height attributes.
+     * @description Return an object with top, left, right, bottom, width, and height parameters of the path.
+     */
+    getBoundingClientRect: function(shallow) {
+      var matrix, border, l, i, v0, v1, c0x, c0y, c1x, c1y, a, b, c, d;
+
+      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 || 0) / 2;
+      l = this._renderer.vertices.length;
+
+      if (l <= 0) {
+        return {
+          width: 0,
+          height: 0
+        };
+      }
+
+      for (i = 0; i < l; i++) {
+
+        v1 = this._renderer.vertices[i];
+        // If i = 0, then this "wraps around" to the last vertex. Otherwise, it's the previous vertex.
+        // This is important for handling cyclic paths.
+        v0 = this._renderer.vertices[(i + l - 1) % l];
+
+        if (v0.controls && v1.controls) {
+
+          c0x = v0.controls.right.x;
+          c0y = v0.controls.right.y;
+
+          if (v0.relative) {
+            c0x += v0.x;
+            c0y += v0.y;
+          }
+
+          c1x = v1.controls.left.x;
+          c1y = v1.controls.left.y;
+
+          if (v1.relative) {
+            c1x += v1.x;
+            c1y += v1.y;
+          }
+
+          var bb = getCurveBoundingBox(v0.x, v0.y,
+            c0x, c0y, c1x, c1y, v1.x, v1.y);
+
+          top = min$1(bb.min.y - border, top);
+          left = min$1(bb.min.x - border, left);
+          right = max$1(bb.max.x + border, right);
+          bottom = max$1(bb.max.y + border, bottom);
+
+        } else {
+
+          if (i <= 1) {
+
+            top = min$1(v0.y - border, top);
+            left = min$1(v0.x - border, left);
+            right = max$1(v0.x + border, right);
+            bottom = max$1(v0.y + border, bottom);
+
+          }
+
+          top = min$1(v1.y - border, top);
+          left = min$1(v1.x - border, left);
+          right = max$1(v1.x + border, right);
+          bottom = max$1(v1.y + border, bottom);
+
+        }
+
+      }
+
+      a = matrix.multiply(left, top, 1);
+      b = matrix.multiply(left, bottom, 1);
+      c = matrix.multiply(right, top, 1);
+      d = matrix.multiply(right, bottom, 1);
+
+      top = min$1(a.y, b.y, c.y, d.y);
+      left = min$1(a.x, b.x, c.x, d.x);
+      right = max$1(a.x, b.x, c.x, d.x);
+      bottom = max$1(a.y, b.y, c.y, d.y);
+
+      return {
+        top: top,
+        left: left,
+        right: right,
+        bottom: bottom,
+        width: right - left,
+        height: bottom - top
+      };
+
+    },
+
+    /**
+     * @name Two.Path#getPointAt
+     * @function
+     * @param {Boolean} t - Percentage value describing where on the Two.Path to estimate and assign coordinate values.
+     * @param {Two.Vector} [obj=undefined] - Object to apply calculated x, y to. If none available returns new Object.
+     * @returns {Object}
+     * @description 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, result;
+      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 = mod(i, length);
+            ib = 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];
+          } else {
+            t = 0;
+          }
+
+          break;
+
+        }
+
+        sum += this._lengths[i];
+
+      }
+
+      if (a === null || b === null) {
+        return null;
+      }
+
+      if (!a) {
+        return b;
+      } else if (!b) {
+        return a;
+      }
+
+      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 = getComponentOnCubicBezier(t, x1, x2, x3, x4);
+      y = getComponentOnCubicBezier(t, y1, y2, y3, y4);
+
+      // Higher order points for control calculation.
+      var t1x = lerp(x1, x2, t);
+      var t1y = lerp(y1, y2, t);
+      var t2x = lerp(x2, x3, t);
+      var t2y = lerp(y2, y3, t);
+      var t3x = lerp(x3, x4, t);
+      var t3y = lerp(y3, y4, t);
+
+      // Calculate the returned points control points.
+      var brx = lerp(t1x, t2x, t);
+      var bry = lerp(t1y, t2y, t);
+      var alx = lerp(t2x, t3x, t);
+      var aly = lerp(t2y, t3y, t);
+
+      if (_.isObject(obj)) {
+
+        obj.x = x;
+        obj.y = y;
+
+        if (!_.isObject(obj.controls)) {
+          Anchor.AppendCurveProperties(obj);
+        }
+
+        obj.controls.left.x = brx;
+        obj.controls.left.y = bry;
+        obj.controls.right.x = alx;
+        obj.controls.right.y = aly;
+
+        if (!typeof obj.relative === 'boolean' || obj.relative) {
+          obj.controls.left.x -= x;
+          obj.controls.left.y -= y;
+          obj.controls.right.x -= x;
+          obj.controls.right.y -= y;
+        }
+
+        obj.t = t;
+
+        return obj;
+
+      }
+
+      result = new Anchor(
+        x, y, brx - x, bry - y, alx - x, aly - y,
+        this._curved ? Commands.curve : Commands.line
+      );
+
+      result.t = t;
+
+      return result;
+
+    },
+
+    /**
+     * @name Two.Path#plot
+     * @function
+     * @description Based on closed / curved and sorting of vertices plot where all points should be and where the respective handles should be too.
+     * @nota-bene While this method is public it is internally called by {@link Two.Path#_update} when `automatic = true`.
+     */
+    plot: function() {
+
+      if (this.curved) {
+        getCurveFromPoints(this._collection, this.closed);
+        return this;
+      }
+
+      for (var i = 0; i < this._collection.length; i++) {
+        this._collection[i].command = i === 0 ? Commands.move : Commands.line;
+      }
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Path#subdivide
+     * @function
+     * @param {Number} limit - How many times to recurse subdivisions.
+     * @description Insert a {@link Two.Anchor} at the midpoint between every item in {@link Two.Path#vertices}.
+     */
+    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 === Commands.close;
+      var points = [];
+      _.each(this.vertices, function(a, i) {
+
+        if (i <= 0 && !closed) {
+          b = a;
+          return;
+        }
+
+        if (a.command === Commands.move) {
+          points.push(new Anchor(b.x, b.y));
+          if (i > 0) {
+            points[points.length - 1].command = 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 === Commands.move) {
+            v.command = Commands.move;
+          } else {
+            v.command = 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 === Commands.move) {
+                v.command = Commands.move;
+              } else {
+                v.command = Commands.line;
+              }
+            });
+
+          } else if (closed) {
+            points.push(new Anchor(a.x, a.y));
+          }
+
+          points[points.length - 1].command = closed
+            ? Commands.close : Commands.line;
+
+        }
+
+        b = a;
+
+      }, this);
+
+      this._automatic = false;
+      this._curved = false;
+      this.vertices = points;
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Path#_updateLength
+     * @function
+     * @private
+     * @param {Number} [limit=] -
+     * @param {Boolean} [silent=false] - If set to `true` then the path isn't updated before calculation. Useful for internal use.
+     * @description Recalculate the {@link Two.Path#length} value.
+     */
+    _updateLength: function(limit, silent) {
+      //TODO: DRYness (function above)
+      if (!silent) {
+        this._update();
+      }
+
+      var length = this.vertices.length;
+      var last = length - 1;
+      var b = this.vertices[last];
+      var closed = false;//this._closed || this.vertices[last]._command === Commands.close;
+      var sum = 0;
+
+      if (typeof this._lengths === 'undefined') {
+        this._lengths = [];
+      }
+
+      _.each(this.vertices, function(a, i) {
+
+        if ((i <= 0 && !closed) || a.command === 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;
+      this._flagLength = false;
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Path#_update
+     * @function
+     * @private
+     * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+     * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+     * @nota-bene Try not to call this method more than once a frame.
+     */
+    _update: function() {
+
+      if (this._flagVertices) {
+
+        if (this._automatic) {
+          this.plot();
+        }
+
+        if (this._flagLength) {
+          this._updateLength(undefined, true);
+        }
+
+        var l = this._collection.length;
+        var closed = this._closed;
+
+        var beginning = Math.min(this._beginning, this._ending);
+        var ending = Math.max(this._beginning, this._ending);
+
+        var bid = getIdByLength(this, beginning * this._length);
+        var eid = getIdByLength(this, ending * this._length);
+
+        var low = ceil(bid);
+        var high = floor(eid);
+
+        var left, right, prev, next, v;
+
+        this._renderer.vertices.length = 0;
+
+        for (var i = 0; i < l; i++) {
+
+          if (this._renderer.collection.length <= i) {
+            // Expected to be `relative` anchor points.
+            this._renderer.collection.push(new Anchor());
+          }
+
+          if (i > high && !right) {
+
+            v = this._renderer.collection[i];
+            v.copy(this._collection[i]);
+            this.getPointAt(ending, v);
+            v.command = this._renderer.collection[i].command;
+            this._renderer.vertices.push(v);
+
+            right = v;
+            prev = this._collection[i - 1];
+
+            // Project control over the percentage `t`
+            // of the in-between point
+            if (prev && prev.controls) {
+
+              v.controls.right.clear();
+
+              this._renderer.collection[i - 1].controls.right
+                .clear()
+                .lerp(prev.controls.right, v.t);
+
+            }
+
+          } else if (i >= low && i <= high) {
+
+            v = this._renderer.collection[i]
+              .copy(this._collection[i]);
+            this._renderer.vertices.push(v);
+
+            if (i === high && contains(this, ending)) {
+              right = v;
+              if (!closed && right.controls) {
+                right.controls.right.clear();
+              }
+            } else if (i === low && contains(this, beginning)) {
+              left = v;
+              left.command = Commands.move;
+              if (!closed && left.controls) {
+                left.controls.left.clear();
+              }
+            }
+
+          }
+
+        }
+
+        // Prepend the trimmed point if necessary.
+        if (low > 0 && !left) {
+
+          i = low - 1;
+
+          v = this._renderer.collection[i];
+          v.copy(this._collection[i]);
+          this.getPointAt(beginning, v);
+          v.command = Commands.move;
+          this._renderer.vertices.unshift(v);
+
+          left = v;
+          next = this._collection[i + 1];
+
+          // Project control over the percentage `t`
+          // of the in-between point
+          if (next && next.controls) {
+
+            v.controls.left.clear();
+
+            this._renderer.collection[i + 1].controls.left
+              .copy(next.controls.left)
+              .lerp(Vector.zero, v.t);
+
+          }
+
+        }
+
+      }
+
+      Shape.prototype._update.apply(this, arguments);
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Path#flagReset
+     * @function
+     * @private
+     * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+     */
+    flagReset: function() {
+
+      this._flagVertices =  this._flagFill =  this._flagStroke =
+          this._flagLinewidth = this._flagOpacity = this._flagVisible =
+          this._flagCap = this._flagJoin = this._flagMiter =
+          this._flagClip = false;
+
+      Shape.prototype.flagReset.call(this);
+
+      return this;
+
+    }
+
+  });
+
+  Path.MakeObservable(Path.prototype);
+
+    // Utility functions
+
+  function contains(path, t) {
+
+    if (t === 0 || t === 1) {
+      return true;
+    }
+
+    var length = path._length;
+    var target = length * t;
+    var elapsed = 0;
+
+    for (var i = 0; i < path._lengths.length; i++) {
+      var dist = path._lengths[i];
+      if (elapsed >= target) {
+        return target - elapsed >= 0;
+      }
+      elapsed += dist;
+    }
+
+    return false;
+
+  }
+
+  /**
+   * @private
+   * @param {Two.Path} path - The path to analyze against.
+   * @param {Number} target - The target length at which to find an anchor.
+   * @returns {Number}
+   * @description Return the id of an anchor based on a target length.
+   */
+  function getIdByLength(path, target) {
+
+    var total = path._length;
+
+    if (target <= 0) {
+      return 0;
+    } else if (target >= total) {
+      return path._lengths.length - 1;
+    }
+
+    for (var i = 0, sum = 0; i < path._lengths.length; i++) {
+
+      if (sum + path._lengths[i] >= target) {
+        target -= sum;
+        return Math.max(i - 1, 0) + target / path._lengths[i];
+      }
+
+      sum += path._lengths[i];
+
+    }
+
+    return - 1;
+
+  }
+
+  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 getCurveLength$1(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 subdivide(x1, y1, x2, y2, x3, y3, x4, y4, limit);
+
+  }
+
+  /**
+   * @name Two.Rectangle
+   * @class
+   * @extends Two.Path
+   * @param {Number} [x=0] - The x position of the rectangle.
+   * @param {Number} [y=0] - The y position of the rectangle.
+   * @param {Number} [width] - The width value of the rectangle.
+   * @param {Number} [height] - The width value of the rectangle.
+   */
+  function Rectangle(x, y, width, height) {
+
+    Path.call(this, [
+      new Anchor(),
+      new Anchor(),
+      new Anchor(),
+      new Anchor()
+      // new Anchor() // TODO: Figure out how to handle this for `beginning` / `ending` animations
+    ], true, false, true);
+
+    /**
+     * @name Two.Rectangle#width
+     * @property {Number} - The size of the width of the rectangle.
+     */
+    this.width = width;
+    /**
+     * @name Two.Rectangle#height
+     * @property {Number} - The size of the height of the rectangle.
+     */
+    this.height = height;
+
+    /**
+     * @name Two.Rectangle#origin
+     * @property {Number} - A two-component vector describing the origin offset to draw the rectangle. Default is `0, 0`.
+     */
+    this.origin = new Vector();
+    this.translation.set(x, y);
+
+    this._update();
+
+  }
+
+  _.extend(Rectangle, {
+
+    /**
+     * @name Two.Rectangle.Properties
+     * @property {String[]} - A list of properties that are on every {@link Two.Rectangle}.
+     */
+    Properties: ['width', 'height'],
+
+    /**
+     * @name Two.Rectangle.MakeObservable
+     * @function
+     * @param {Object} object - The object to make observable.
+     * @description Convenience function to apply observable qualities of a {@link Two.Rectangle} to any object. Handy if you'd like to extend the {@link Two.Rectangle} class on a custom class.
+     */
+    MakeObservable: function(object) {
+
+      Path.MakeObservable(object);
+      _.each(Rectangle.Properties, defineGetterSetter, object);
+
+      Object.defineProperty(object, 'origin', {
+        enumerable: true,
+        get: function() {
+          return this._origin;
+        },
+        set: function(v) {
+          if (this._origin) {
+            this._origin.unbind(Events.Types.change, this._renderer.flagVertices);
+          }
+          this._origin = v;
+          this._origin.bind(Events.Types.change, this._renderer.flagVertices);
+          this._renderer.flagVertices();
+        }
+      });
+
+    }
+
+  });
+
+  _.extend(Rectangle.prototype, Path.prototype, {
+
+    constructor: Rectangle,
+
+    /**
+     * @name Two.Rectangle#_flagWidth
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Rectangle#width} needs updating.
+     */
+    _flagWidth: 0,
+    /**
+     * @name Two.Rectangle#_flagHeight
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Rectangle#height} needs updating.
+     */
+    _flagHeight: 0,
+
+    /**
+     * @name Two.Rectangle#_width
+     * @private
+     * @see {@link Two.Rectangle#width}
+     */
+    _width: 0,
+    /**
+     * @name Two.Rectangle#_height
+     * @private
+     * @see {@link Two.Rectangle#height}
+     */
+    _height: 0,
+
+    _origin: null,
+
+    /**
+     * @name Two.Rectangle#_update
+     * @function
+     * @private
+     * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+     * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+     * @nota-bene Try not to call this method more than once a frame.
+     */
+    _update: function() {
+
+      if (this._flagVertices || this._flagWidth || this._flagHeight) {
+
+        var xr = this._width / 2;
+        var yr = this._height / 2;
+
+        if (!this._closed && this.vertices.length === 4) {
+          this.vertices.push(new Anchor());
+        }
+
+        this.vertices[0].set(-xr, -yr).add(this._origin).command = Commands.move;
+        this.vertices[1].set(xr, -yr).add(this._origin).command = Commands.line;
+        this.vertices[2].set(xr, yr).add(this._origin).command = Commands.line;
+        this.vertices[3].set(-xr, yr).add(this._origin).command = Commands.line;
+        // FYI: Two.Sprite and Two.ImageSequence have 4 verts
+        if (this.vertices[4]) {
+          this.vertices[4].set(-xr, -yr).add(this._origin).command = Commands.line;
+        }
+
+      }
+
+      Path.prototype._update.call(this);
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Rectangle#flagReset
+     * @function
+     * @private
+     * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+     */
+    flagReset: function() {
+
+      this._flagWidth = this._flagHeight = false;
+      Path.prototype.flagReset.call(this);
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Rectangle#clone
+     * @function
+     * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+     * @returns {Two.Rectangle}
+     * @description Create a new instance of {@link Two.Rectangle} with the same properties of the current path.
+     */
+    clone: function(parent) {
+
+      var clone = new Rectangle(0, 0, this.width, this.height);
+
+      clone.translation.copy(this.translation);
+      clone.rotation = this.rotation;
+      clone.scale = this.scale;
+      clone.skewX = this.skewX;
+      clone.skewY = this.skewY;
+
+      if (this.matrix.manual) {
+        clone.matrix.copy(this.matrix);
+      }
+
+      _.each(Path.Properties, function(k) {
+        clone[k] = this[k];
+      }, this);
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone;
+
+    },
+
+    /**
+     * @name Two.Rectangle#toObject
+     * @function
+     * @returns {Object}
+     * @description Return a JSON compatible plain object that represents the path.
+     */
+    toObject: function() {
+
+      var object = Path.prototype.toObject.call(this);
+      object.width = this.width;
+      object.height = this.height;
+      object.origin = this.origin.toObject();
+      return object;
+
+    }
+
+  });
+
+  Rectangle.MakeObservable(Rectangle.prototype);
+
+  /**
+   * @name Two.Sprite
+   * @class
+   * @extends Two.Rectangle
+   * @param {String|Two.Texture} [path] - The URL path or {@link Two.Texture} to be used as the bitmap data displayed on the sprite.
+   * @param {Number} [ox=0] - The initial `x` position of the Two.Sprite.
+   * @param {Number} [oy=0] - The initial `y` position of the Two.Sprite.
+   * @param {Number} [cols=1] - The number of columns the sprite contains.
+   * @param {Number} [rows=1] - The number of rows the sprite contains.
+   * @param {Number} [frameRate=0] - The frame rate at which the partitions of the image should playback at.
+   * @description A convenient package to display still or animated images through a tiled image source. For more information on the principals of animated imagery through tiling see [Texture Atlas](https://en.wikipedia.org/wiki/Texture_atlas) on Wikipedia.
+   */
+  function Sprite(path, ox, oy, cols, rows, frameRate) {
+
+    // Not using default constructor of Rectangle due to odd `beginning` / `ending` behavior.
+    // See: https://github.com/jonobr1/two.js/issues/383
+    Path.call(this, [
+      new Anchor(),
+      new Anchor(),
+      new Anchor(),
+      new Anchor()
+    ], true);
+
+    this.noStroke();
+    this.noFill();
+
+    /**
+     * @name Two.Sprite#texture
+     * @property {Two.Texture} - The texture to be used as bitmap data to display image in the scene.
+     */
+    if (path instanceof Texture) {
+      this.texture = path;
+    } else if (typeof path === 'string') {
+      this.texture = new Texture(path);
+    }
+
+    this.origin = new Vector();
+
+    this._update();
+    this.translation.set(ox || 0, oy || 0);
+
+    /**
+     * @name Two.Sprite#columns
+     * @property {Number} - The number of columns to split the texture into. Defaults to `1`.
+     */
+    if (typeof cols === 'number') {
+      this.columns = cols;
+    }
+
+    /**
+     * @name Two.Sprite#rows
+     * @property {Number} - The number of rows to split the texture into. Defaults to `1`.
+     */
+    if (typeof rows === 'number') {
+      this.rows = rows;
+    }
+
+    /**
+     * @name Two.Sprite#frameRate
+     * @property {Number} - The number of frames to animate against per second. Defaults to `0` for non-animated sprites.
+     */
+    if (typeof frameRate === 'number') {
+      this.frameRate = frameRate;
+    }
+
+    /**
+     * @name Two.Sprite#index
+     * @property {Number} - The index of the current tile of the sprite to display. Defaults to `0`.
+     */
+    this.index = 0;
+
+  }
+
+  _.extend(Sprite, {
+
+    /**
+     * @name Two.Sprite.Properties
+     * @property {String[]} - A list of properties that are on every {@link Two.Sprite}.
+     */
+    Properties: [
+      'texture', 'columns', 'rows', 'frameRate', 'index'
+    ],
+
+    /**
+     * @name Two.Sprite.MakeObservable
+     * @function
+     * @param {Object} object - The object to make observable.
+     * @description Convenience function to apply observable qualities of a {@link Two.Sprite} to any object. Handy if you'd like to extend or inherit the {@link Two.Sprite} class on a custom class.
+     */
+    MakeObservable: function(obj) {
+
+      Rectangle.MakeObservable(obj);
+      _.each(Sprite.Properties, defineGetterSetter, obj);
+
+    }
+
+  });
+
+  _.extend(Sprite.prototype, Rectangle.prototype, {
+
+    constructor: Sprite,
+
+    /**
+     * @name Two.Sprite#_flagTexture
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Sprite#texture} needs updating.
+     */
+    _flagTexture: false,
+
+    /**
+     * @name Two.Sprite#_flagColumns
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Sprite#columns} need updating.
+     */
+    _flagColumns: false,
+
+    /**
+     * @name Two.Sprite#_flagRows
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Sprite#rows} need updating.
+     */
+    _flagRows: false,
+
+    /**
+     * @name Two.Sprite#_flagFrameRate
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Sprite#flagFrameRate} needs updating.
+     */
+    _flagFrameRate: false,
+
+    /**
+     * @name Two.Sprite#_flagIndex
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Sprite#index} needs updating.
+     */
+    flagIndex: false,
+
+    // Private variables
+
+    /**
+     * @name Two.Sprite#_amount
+     * @private
+     * @property {Number} - Number of frames for a given {@link Two.Sprite}.
+     */
+    _amount: 1,
+
+    /**
+     * @name Two.Sprite#_duration
+     * @private
+     * @property {Number} - Number of milliseconds a {@link Two.Sprite}.
+     */
+    _duration: 0,
+
+    /**
+     * @name Two.Sprite#_startTime
+     * @private
+     * @property {Milliseconds} - Epoch time in milliseconds of when the {@link Two.Sprite} started.
+     */
+    _startTime: 0,
+
+    /**
+     * @name Two.Sprite#_playing
+     * @private
+     * @property {Boolean} - Dictates whether the {@link Two.Sprite} is animating or not.
+     */
+    _playing: false,
+
+    /**
+     * @name Two.Sprite#_firstFrame
+     * @private
+     * @property {Number} - The frame the {@link Two.Sprite} should start with.
+     */
+    _firstFrame: 0,
+
+    /**
+     * @name Two.Sprite#_lastFrame
+     * @private
+     * @property {Number} - The frame the {@link Two.Sprite} should end with.
+     */
+    _lastFrame: 0,
+
+    /**
+     * @name Two.Sprite#_playing
+     * @private
+     * @property {Boolean} - Dictates whether the {@link Two.Sprite} should loop or not.
+     */
+    _loop: true,
+
+    // Exposed through getter-setter
+
+    /**
+     * @name Two.Sprite#_texture
+     * @private
+     * @see {@link Two.Sprite#texture}
+     */
+    _texture: null,
+
+    /**
+     * @name Two.Sprite#_columns
+     * @private
+     * @see {@link Two.Sprite#columns}
+     */
+    _columns: 1,
+
+    /**
+     * @name Two.Sprite#_rows
+     * @private
+     * @see {@link Two.Sprite#rows}
+     */
+    _rows: 1,
+
+    /**
+     * @name Two.Sprite#_frameRate
+     * @private
+     * @see {@link Two.Sprite#frameRate}
+     */
+    _frameRate: 0,
+
+    /**
+     * @name Two.Sprite#_index
+     * @private
+     * @property {Number} - The current frame the {@link Two.Sprite} is currently displaying.
+     */
+    _index: 0,
+
+    /**
+     * @name Two.Sprite#_origin
+     * @private
+     * @see {@link Two.Sprite#origin}
+     */
+    _origin: null,
+
+    /**
+     * @name Two.Sprite#play
+     * @function
+     * @param {Number} [firstFrame=0] - The index of the frame to start the animation with.
+     * @param {Number} [lastFrame] - The index of the frame to end the animation with. Defaults to the last item in the {@link Two.Sprite#textures}.
+     * @param {Function} [onLastFrame] - Optional callback function to be triggered after playing the last frame. This fires multiple times when the sprite is looped.
+     * @description Initiate animation playback of a {@link Two.Sprite}.
+     */
+    play: function(firstFrame, lastFrame, onLastFrame) {
+
+      this._playing = true;
+      this._firstFrame = 0;
+      this._lastFrame = this.amount - 1;
+      this._startTime = _.performance.now();
+
+      if (typeof firstFrame === 'number') {
+        this._firstFrame = firstFrame;
+      }
+      if (typeof lastFrame === 'number') {
+        this._lastFrame = lastFrame;
+      }
+      if (typeof onLastFrame === 'function') {
+        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;
+
+    },
+
+    /**
+     * @name Two.Sprite#pause
+     * @function
+     * @description Halt animation playback of a {@link Two.Sprite}.
+     */
+    pause: function() {
+
+      this._playing = false;
+      return this;
+
+    },
+
+    /**
+     * @name Two.Sprite#stop
+     * @function
+     * @description Halt animation playback of a {@link Two.Sprite} and set the current frame back to the first frame.
+     */
+    stop: function() {
+
+      this._playing = false;
+      this._index = 0;
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Sprite#clone
+     * @function
+     * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+     * @returns {Two.Sprite}
+     * @description Create a new instance of {@link Two.Sprite} with the same properties of the current sprite.
+     */
+    clone: function(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;
+
+    },
+
+    /**
+     * @name Two.Sprite#toObject
+     * @function
+     * @returns {Object}
+     * @description Return a JSON compatible plain object that represents the path.
+     */
+    toObject: function() {
+      var object = Rectangle.prototype.toObject.call(this);
+      object.texture = this.texture.toObject();
+      object.columns = this.columns;
+      object.rows = this.rows;
+      object.frameRate = this.frameRate;
+      object.index = this.index;
+      object._firstFrame = this._firstFrame;
+      object._lastFrame = this._lastFrame;
+      object._loop = this._loop;
+      return object;
+    },
+
+    /**
+     * @name Two.Sprite#_update
+     * @function
+     * @private
+     * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+     * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+     * @nota-bene Try not to call this method more than once a frame.
+     */
+    _update: function() {
+
+      var effect = this._texture;
+      var cols = this._columns;
+      var rows = this._rows;
+
+      var width, height, elapsed, amount, duration;
+      var index, iw, ih, 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;
+
+    },
+
+    /**
+     * @name Two.Sprite#flagReset
+     * @function
+     * @private
+     * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+     */
+    flagReset: function() {
+
+      this._flagTexture = this._flagColumns = this._flagRows
+        = this._flagFrameRate = false;
+
+      Rectangle.prototype.flagReset.call(this);
+
+      return this;
+    }
+
+
+  });
+
+  Sprite.MakeObservable(Sprite.prototype);
+
+  var TWO_PI$4 = Math.PI * 2, HALF_PI$2 = Math.PI / 2;
+  var cos$3 = Math.cos, sin$3 = Math.sin;
+
+  /**
+   * @name Two.Circle
+   * @class
+   * @extends Two.Path
+   * @param {Number} [x=0] - The x position of the circle.
+   * @param {Number} [y=0] - The y position of the circle.
+   * @param {Number} [radius=0] - The radius value of the circle.
+   * @param {Number} [resolution=4] - The number of vertices used to construct the circle.
+   */
+  function Circle(ox, oy, r, resolution) {
+
+    // At least 2 vertices are required for proper circlage
+    var amount = resolution ? Math.max(resolution, 2) : 4;
+
+    var points = [];
+    for (var i = 0; i < amount; i++) {
+      points.push(new Anchor(0, 0, 0, 0, 0, 0));
+    }
+
+    Path.call(this, points, true, true, true);
+
+    /**
+     * @name Two.Circle#radius
+     * @property {Number} - The size of the radius of the circle.
+     */
+    if (typeof r === 'number') {
+      this.radius = r;
+    }
+
+    this._update();
+
+    if (typeof ox === 'number') {
+      this.translation.x = ox;
+    }
+    if (typeof oy === 'number') {
+      this.translation.y = oy;
+    }
+
+  }
+
+  _.extend(Circle, {
+
+    /**
+     * @name Two.Circle.Properties
+     * @property {String[]} - A list of properties that are on every {@link Two.Circle}.
+     */
+    Properties: ['radius'],
+
+    /**
+     * @name Two.Circle.MakeObservable
+     * @function
+     * @param {Object} object - The object to make observable.
+     * @description Convenience function to apply observable qualities of a {@link Two.Circle} to any object. Handy if you'd like to extend the {@link Two.Circle} class on a custom class.
+     */
+    MakeObservable: function(obj) {
+
+      Path.MakeObservable(obj);
+      _.each(Circle.Properties, defineGetterSetter, obj);
+
+    }
+
+  });
+
+  _.extend(Circle.prototype, Path.prototype, {
+
+    constructor: Circle,
+
+    /**
+     * @name Two.Circle#_flagRadius
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Circle#radius} needs updating.
+     */
+    _flagRadius: false,
+
+    /**
+     * @name Two.Circle#_radius
+     * @private
+     * @see {@link Two.Circle#radius}
+     */
+    _radius: 0,
+
+    /**
+     * @name Two.Circle#_update
+     * @function
+     * @private
+     * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+     * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+     * @nota-bene Try not to call this method more than once a frame.
+     */
+    _update: function() {
+
+      if (this._flagVertices || this._flagRadius) {
+
+        var length = this.vertices.length;
+
+        if (!this._closed && length > 2) {
+          length -= 1;
+        }
+
+        // Coefficient for approximating circular arcs with Bezier curves
+        var c = (4 / 3) * Math.tan(Math.PI / (length * 2));
+        var radius = this._radius;
+        var rc = radius * c;
+
+        for (var i = 0; i < this.vertices.length; i++) {
+          var pct = i / length;
+          var theta = pct * TWO_PI$4;
+
+          var x = radius * cos$3(theta);
+          var y = radius * sin$3(theta);
+
+          var lx = rc * cos$3(theta - HALF_PI$2);
+          var ly = rc * sin$3(theta - HALF_PI$2);
+
+          var rx = rc * cos$3(theta + HALF_PI$2);
+          var ry = rc * sin$3(theta + HALF_PI$2);
+
+          var v = this.vertices[i];
+
+          v.command = i === 0 ? Commands.move : Commands.curve;
+          v.set(x, y);
+          v.controls.left.set(lx, ly);
+          v.controls.right.set(rx, ry);
+        }
+      }
+
+      Path.prototype._update.call(this);
+      return this;
+
+    },
+
+    /**
+     * @name Two.Circle#flagReset
+     * @function
+     * @private
+     * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+     */
+    flagReset: function() {
+
+      this._flagRadius = false;
+
+      Path.prototype.flagReset.call(this);
+      return this;
+
+    },
+
+    /**
+     * @name Two.Circle#clone
+     * @function
+     * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+     * @returns {Two.Circle}
+     * @description Create a new instance of {@link Two.Circle} with the same properties of the current path.
+     */
+    clone: function(parent) {
+
+      var clone = new Circle(0, 0, this.radius, this.vertices.length);
+
+      clone.translation.copy(this.translation);
+      clone.rotation = this.rotation;
+      clone.scale = this.scale;
+      clone.skewX = this.skewX;
+      clone.skewY = this.skewY;
+
+      if (this.matrix.manual) {
+        clone.matrix.copy(this.matrix);
+      }
+
+      _.each(Path.Properties, function(k) {
+        clone[k] = this[k];
+      }, this);
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone;
+
+    },
+
+    /**
+     * @name Two.Circle#toObject
+     * @function
+     * @returns {Object}
+     * @description Return a JSON compatible plain object that represents the path.
+     */
+    toObject: function() {
+
+      var object = Path.prototype.toObject.call(this);
+
+      _.each(Circle.Properties, function(property) {
+        object[property] = this[property];
+      }, this);
+
+      return object;
+
+    }
+
+  });
+
+  Circle.MakeObservable(Circle.prototype);
+
+  var TWO_PI$3 = Math.PI * 2, HALF_PI$1 = Math.PI / 2;
+  var cos$2 = Math.cos, sin$2 = Math.sin;
+
+  /**
+   * @name Two.Ellipse
+   * @class
+   * @extends Two.Path
+   * @param {Number} [x=0] - The x position of the ellipse.
+   * @param {Number} [y=0] - The y position of the ellipse.
+   * @param {Number} [rx=0] - The radius value of the ellipse in the x direction.
+   * @param {Number} [ry=0] - The radius value of the ellipse in the y direction.
+   * @param {Number} [resolution=4] - The number of vertices used to construct the ellipse.
+   */
+  function Ellipse(ox, oy, rx, ry, resolution) {
+
+    if (typeof ry !== 'number' && typeof rx === 'number') {
+      ry = rx;
+    }
+
+    // At least 2 vertices are required for proper circlage
+    var amount = resolution ? Math.max(resolution, 2) : 4;
+
+    var points = [];
+    for (var i = 0; i < amount; i++) {
+      points.push(new Anchor());
+    }
+
+    Path.call(this, points, true, true, true);
+
+    /**
+     * @name Two.Ellipse#width
+     * @property {Number} - The width of the ellipse.
+     */
+    if (typeof rx === 'number') {
+      this.width = rx * 2;
+    }
+
+    /**
+     * @name Two.Ellipse#height
+     * @property {Number} - The height of the ellipse.
+     */
+    if (typeof ry === 'number') {
+      this.height = ry * 2;
+    }
+
+    this._update();
+    this.translation.set(ox, oy);
+
+  }
+
+  _.extend(Ellipse, {
+
+    /**
+     * @name Two.Ellipse.Properties
+     * @property {String[]} - A list of properties that are on every {@link Two.Ellipse}.
+     */
+    Properties: ['width', 'height'],
+
+    /**
+     * @name Two.Ellipse.MakeObservable
+     * @function
+     * @param {Object} object - The object to make observable.
+     * @description Convenience function to apply observable qualities of a {@link Two.Ellipse} to any object. Handy if you'd like to extend the {@link Two.Ellipse} class on a custom class.
+     */
+    MakeObservable: function(obj) {
+
+      Path.MakeObservable(obj);
+      _.each(Ellipse.Properties, defineGetterSetter, obj);
+
+    }
+
+  });
+
+  _.extend(Ellipse.prototype, Path.prototype, {
+
+    /**
+     * @name Two.Ellipse#_flagWidth
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Ellipse#width} needs updating.
+     */
+    _flagWidth: false,
+    /**
+     * @name Two.Ellipse#_flagHeight
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Ellipse#height} needs updating.
+     */
+    _flagHeight: false,
+
+    /**
+     * @name Two.Polygon#_width
+     * @private
+     * @see {@link Two.Ellipse#width}
+     */
+    _width: 0,
+    /**
+     * @name Two.Polygon#_height
+     * @private
+     * @see {@link Two.Ellipse#height}
+     */
+    _height: 0,
+
+    constructor: Ellipse,
+
+    /**
+     * @name Two.Ellipse#_update
+     * @function
+     * @private
+     * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+     * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+     * @nota-bene Try not to call this method more than once a frame.
+     */
+    _update: function() {
+
+      if (this._flagVertices || this._flagWidth || this._flagHeight) {
+
+        var length = this.vertices.length;
+
+        if (!this._closed && length > 2) {
+          length -= 1;
+        }
+
+        // Coefficient for approximating circular arcs with Bezier curves
+        var c = (4 / 3) * Math.tan(Math.PI / (this.vertices.length * 2));
+        var radiusX = this._width / 2;
+        var radiusY = this._height / 2;
+
+        for (var i = 0; i < this.vertices.length; i++) {
+          var pct = i / length;
+          var theta = pct * TWO_PI$3;
+
+          var x = radiusX * cos$2(theta);
+          var y = radiusY * sin$2(theta);
+
+          var lx = radiusX * c * cos$2(theta - HALF_PI$1);
+          var ly = radiusY * c * sin$2(theta - HALF_PI$1);
+
+          var rx = radiusX * c * cos$2(theta + HALF_PI$1);
+          var ry = radiusY * c * sin$2(theta + HALF_PI$1);
+
+          var v = this.vertices[i];
+
+          v.command = i === 0 ? Commands.move : Commands.curve;
+          v.set(x, y);
+          v.controls.left.set(lx, ly);
+          v.controls.right.set(rx, ry);
+        }
+      }
+
+      Path.prototype._update.call(this);
+      return this;
+
+    },
+
+    /**
+     * @name Two.Ellipse#flagReset
+     * @function
+     * @private
+     * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+     */
+    flagReset: function() {
+
+      this._flagWidth = this._flagHeight = false;
+
+      Path.prototype.flagReset.call(this);
+      return this;
+
+    },
+
+    /**
+     * @name Two.Ellipse#clone
+     * @function
+     * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+     * @returns {Two.Polygon}
+     * @description Create a new instance of {@link Two.Polygon} with the same properties of the current path.
+     */
+    clone: function(parent) {
+
+      var rx = this.width / 2;
+      var ry = this.height / 2;
+      var resolution = this.vertices.length;
+      var clone = new Ellipse(0, 0, rx, ry, resolution);
+
+      clone.translation.copy(this.translation);
+      clone.rotation = this.rotation;
+      clone.scale = this.scale;
+      clone.skewX = this.skewX;
+      clone.skewY = this.skewY;
+
+      if (this.matrix.manual) {
+        clone.matrix.copy(this.matrix);
+      }
+
+      _.each(Path.Properties, function(k) {
+        clone[k] = this[k];
+      }, this);
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone;
+
+    },
+
+    /**
+     * @name Two.Ellipse#toObject
+     * @function
+     * @returns {Object}
+     * @description Return a JSON compatible plain object that represents the path.
+     */
+    toObject: function() {
+
+      var object = Path.prototype.toObject.call(this);
+
+      _.each(Ellipse.Properties, function(property) {
+        object[property] = this[property];
+      }, this);
+
+      return object;
+
+    }
+
+  });
+
+  Ellipse.MakeObservable(Ellipse.prototype);
+
+  /**
+   * @name Two.Line
+   * @class
+   * @extends Two.Path
+   * @param {Number} [x1=0] - The x position of the first vertex on the line.
+   * @param {Number} [y1=0] - The y position of the first vertex on the line.
+   * @param {Number} [x2=0] - The x position of the second vertex on the line.
+   * @param {Number} [y2=0] - The y position of the second vertex on the line.
+   */
+  function Line(x1, y1, x2, y2) {
+
+    Path.call(this, [
+        new Anchor(x1, y1),
+        new Anchor(x2, y2)
+    ]);
+
+    this.vertices[0].command = Commands.move;
+    this.vertices[1].command = Commands.line;
+
+    this.automatic = false;
+
+  }
+
+  _.extend(Line.prototype, Path.prototype, {
+
+    constructor: Line
+
+  });
+
+  Path.MakeObservable(Line.prototype);
+
+  /**
+   * @name Two.RoundedRectangle
+   * @class
+   * @extends Two.Path
+   * @param {Number} [x=0] - The x position of the rounded rectangle.
+   * @param {Number} [y=0] - The y position of the rounded rectangle.
+   * @param {Number} [width=0] - The width value of the rounded rectangle.
+   * @param {Number} [height=0] - The width value of the rounded rectangle.
+   * @param {Number} [radius=0] - The radius value of the rounded rectangle.
+   * @param {Number} [resolution=12] - The number of vertices used to construct the rounded rectangle.
+   */
+  function RoundedRectangle(ox, oy, width, height, radius) {
+
+    if (typeof radius === 'undefined' &&
+      typeof width === 'number' && typeof height === 'number') {
+      radius = Math.floor(Math.min(width, height) / 12);
+    }
+
+    var amount = 10;
+
+    var points = [];
+    for (var i = 0; i < amount; i++) {
+      points.push(
+        new Anchor(0, 0, 0, 0, 0, 0,
+          i === 0 ? Commands.move : Commands.curve)
+      );
+    }
+
+    // points[points.length - 1].command = Two.Commands.close;
+
+    Path.call(this, points);
+
+    this.closed = true;
+    this.automatic = false;
+
+    this._renderer.flagRadius = RoundedRectangle.FlagRadius.bind(this);
+
+    /**
+     * @name Two.RoundedRectangle#width
+     * @property {Number} - The width of the rounded rectangle.
+     */
+    if (typeof width === 'number') {
+      this.width = width;
+    }
+
+    /**
+     * @name Two.RoundedRectangle#height
+     * @property {Number} - The height of the rounded rectangle.
+     */
+    if (typeof height === 'number') {
+      this.height = height;
+    }
+
+    /**
+     * @name Two.RoundedRectangle#radius
+     * @property {Number} - The size of the radius of the rounded rectangle.
+     */
+    if (typeof radius === 'number') {
+      this.radius = radius;
+    }
+
+    this._update();
+    this.translation.set(ox, oy);
+
+  }
+
+  _.extend(RoundedRectangle, {
+
+    /**
+     * @name Two.RoundedRectangle.Properties
+     * @property {String[]} - A list of properties that are on every {@link Two.RoundedRectangle}.
+     */
+    Properties: ['width', 'height'],
+
+    /**
+     * @name Two.RoundedRectangle.FlagRadius
+     * @property {Function} - A convenience function to trigger the flag for radius changing.
+     */
+    FlagRadius: function() {
+      this._flagRadius = true;
+    },
+
+    /**
+     * @name Two.RoundedRectangle.MakeObservable
+     * @function
+     * @param {Object} object - The object to make observable.
+     * @description Convenience function to apply observable qualities of a {@link Two.RoundedRectangle} to any object. Handy if you'd like to extend the {@link Two.RoundedRectangle} class on a custom class.
+     */
+    MakeObservable: function(object) {
+
+      Path.MakeObservable(object);
+      _.each(RoundedRectangle.Properties, defineGetterSetter, object);
+
+      Object.defineProperty(object, 'radius', {
+        enumerable: true,
+        get: function() {
+          return this._radius;
+        },
+        set: function(v) {
+
+          if (this._radius instanceof Vector) {
+            this._radius.unbind(Events.Types.change, this._renderer.flagRadius);
+          }
+
+          this._radius = v;
+
+          if (this._radius instanceof Vector) {
+            this._radius.bind(Events.Types.change, this._renderer.flagRadius);
+          }
+
+          this._flagRadius = true;
+
+        }
+      });
+
+    }
+
+  });
+
+  _.extend(RoundedRectangle.prototype, Path.prototype, {
+
+    constructor: RoundedRectangle,
+
+    /**
+     * @name Two.RoundedRectangle#_flagWidth
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.RoundedRectangle#width} needs updating.
+     */
+    _flagWidth: false,
+    /**
+     * @name Two.RoundedRectangle#_flagHeight
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.RoundedRectangle#height} needs updating.
+     */
+    _flagHeight: false,
+    /**
+     * @name Two.RoundedRectangle#_flagRadius
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.RoundedRectangle#radius} needs updating.
+     */
+    _flagRadius: false,
+
+    /**
+     * @name Two.RoundedRectangle#_width
+     * @private
+     * @see {@link Two.RoundedRectangle#width}
+     */
+    _width: 0,
+    /**
+     * @name Two.RoundedRectangle#_height
+     * @private
+     * @see {@link Two.RoundedRectangle#height}
+     */
+    _height: 0,
+    /**
+     * @name Two.RoundedRectangle#_radius
+     * @private
+     * @see {@link Two.RoundedRectangle#radius}
+     */
+    _radius: 12,
+
+    /**
+     * @name Two.RoundedRectangle#_update
+     * @function
+     * @private
+     * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+     * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+     * @nota-bene Try not to call this method more than once a frame.
+     */
+    _update: function() {
+
+      if (this._flagVertices || this._flagWidth || this._flagHeight || this._flagRadius) {
+
+        var width = this._width;
+        var height = this._height;
+
+        var rx, ry;
+
+        if (this._radius instanceof Vector) {
+          rx = this._radius.x;
+          ry = this._radius.y;
+        } else {
+          rx = this._radius;
+          ry = this._radius;
+        }
+
+        var v;
+        var w = width / 2;
+        var h = height / 2;
+
+        v = this.vertices[0];
+        v.x = - (w - rx);
+        v.y = - h;
+
+        // Upper Right Corner
+
+        v = this.vertices[1];
+        v.x = (w - rx);
+        v.y = - h;
+        v.controls.left.clear();
+        v.controls.right.x = rx;
+        v.controls.right.y = 0;
+
+        v = this.vertices[2];
+        v.x = w;
+        v.y = - (h - ry);
+        v.controls.right.clear();
+        v.controls.left.clear();
+
+        // Bottom Right Corner
+
+        v = this.vertices[3];
+        v.x = w;
+        v.y = (h - ry);
+        v.controls.left.clear();
+        v.controls.right.x = 0;
+        v.controls.right.y = ry;
+
+        v = this.vertices[4];
+        v.x = (w - rx);
+        v.y = h;
+        v.controls.right.clear();
+        v.controls.left.clear();
+
+        // Bottom Left Corner
+
+        v = this.vertices[5];
+        v.x = - (w - rx);
+        v.y = h;
+        v.controls.left.clear();
+        v.controls.right.x = - rx;
+        v.controls.right.y = 0;
+
+        v = this.vertices[6];
+        v.x = - w;
+        v.y = (h - ry);
+        v.controls.left.clear();
+        v.controls.right.clear();
+
+        // Upper Left Corner
+
+        v = this.vertices[7];
+        v.x = - w;
+        v.y = - (h - ry);
+        v.controls.left.clear();
+        v.controls.right.x = 0;
+        v.controls.right.y = - ry;
+
+        v = this.vertices[8];
+        v.x = - (w - rx);
+        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;
+
+    },
+
+    /**
+     * @name Two.RoundedRectangle#flagReset
+     * @function
+     * @private
+     * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+     */
+    flagReset: function() {
+
+      this._flagWidth = this._flagHeight = this._flagRadius = false;
+      Path.prototype.flagReset.call(this);
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.RoundedRectangle#clone
+     * @function
+     * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+     * @returns {Two.RoundedRectangle}
+     * @description Create a new instance of {@link Two.RoundedRectangle} with the same properties of the current path.
+     */
+    clone: function(parent) {
+
+      var width = this.width;
+      var height = this.height;
+      var radius = this.radius;
+
+      var clone = new RoundedRectangle(0, 0, width, height, radius);
+
+      clone.translation.copy(this.translation);
+      clone.rotation = this.rotation;
+      clone.scale = this.scale;
+      clone.skewX = this.skewX;
+      clone.skewY = this.skewY;
+
+      if (this.matrix.manual) {
+        clone.matrix.copy(this.matrix);
+      }
+
+      _.each(Path.Properties, function(k) {
+        clone[k] = this[k];
+      }, this);
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone;
+
+    },
+
+    /**
+     * @name Two.RoundedRectangle#toObject
+     * @function
+     * @returns {Object}
+     * @description Return a JSON compatible plain object that represents the path.
+     */
+    toObject: function() {
+
+      var object = Path.prototype.toObject.call(this);
+
+      _.each(RoundedRectangle.Properties, function(property) {
+        object[property] = this[property];
+      }, this);
+
+      object.radius = typeof this.radius === 'number'
+        ? this.radius : this.radius.toObject();
+
+      return object;
+
+    }
+
+  });
+
+  RoundedRectangle.MakeObservable(RoundedRectangle.prototype);
+
+  var min = Math.min, max = Math.max;
+
+  /**
+   * @name Two.Text
+   * @class
+   * @extends Two.Shape
+   * @param {String} [message] - The String to be rendered to the scene.
+   * @param {Number} [x=0] - The position in the x direction for the object.
+   * @param {Number} [y=0] - The position in the y direction for the object.
+   * @param {Object} [styles] - An object where styles are applied. Attribute must exist in Two.Text.Properties.
+   * @description This is a primitive class for creating drawable text that can be added to the scenegraph.
+   * @returns {Two.Text}
+   */
+  function Text(message, x, y, styles) {
+
+    Shape.call(this);
+
+    this._renderer.type = 'text';
+    this._renderer.flagFill = Text.FlagFill.bind(this);
+    this._renderer.flagStroke = Text.FlagStroke.bind(this);
+
+    this.value = message;
+
+    if (typeof x === 'number') {
+      this.translation.x = x;
+    }
+    if (typeof y === 'number') {
+      this.translation.y = y;
+    }
+
+    /**
+     * @name Two.Text#dashes
+     * @property {Number[]} - Array of numbers. Odd indices represent dash length. Even indices represent dash space.
+     * @description A list of numbers that represent the repeated dash length and dash space applied to the stroke of the text.
+     * @see {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray} for more information on the SVG stroke-dasharray attribute.
+     */
+    this.dashes = [];
+
+    /**
+     * @name Two.Text#dashes#offset
+     * @property {Number} - A number in pixels to offset {@link Two.Text#dashes} display.
+     */
+    this.dashes.offset = 0;
+
+    if (!_.isObject(styles)) {
+      return this;
+    }
+
+    _.each(Text.Properties, function(property) {
+
+      if (property in styles) {
+        this[property] = styles[property];
+      }
+
+    }, this);
+
+  }
+
+  _.extend(Text, {
+
+    /**
+     * @name Two.Text.Ratio
+     * @property {Number} - Approximate aspect ratio of a typeface's character width to height.
+     */
+    Ratio: 0.6,
+
+    /**
+     * @name Two.Text.Properties
+     * @property {String[]} - A list of properties that are on every {@link Two.Text}.
+     */
+    Properties: [
+      'value', 'family', 'size', 'leading', 'alignment', 'linewidth', 'style',
+      'weight', 'decoration', 'baseline', 'opacity', 'visible', 'className',
+      'fill', 'stroke',
+    ],
+
+    /**
+     * @name Two.Text.FlagFill
+     * @function
+     * @description Cached method to let renderers know the fill property have been updated on a {@link Two.Text}.
+     */
+    FlagFill: function() {
+      this._flagFill = true;
+    },
+
+    /**
+     * @name Two.Text.FlagStroke
+     * @function
+     * @description Cached method to let renderers know the stroke property have been updated on a {@link Two.Text}.
+     */
+    FlagStroke: function() {
+      this._flagStroke = true;
+    },
+
+    MakeObservable: function(object) {
+
+      Shape.MakeObservable(object);
+
+      _.each(Text.Properties.slice(0, 12), defineGetterSetter, object);
+
+      Object.defineProperty(object, 'fill', {
+        enumerable: true,
+        get: function() {
+          return this._fill;
+        },
+        set: function(f) {
+
+          if (this._fill instanceof Gradient
+            || this._fill instanceof LinearGradient
+            || this._fill instanceof RadialGradient
+            || this._fill instanceof Texture) {
+            this._fill.unbind(Events.Types.change, this._renderer.flagFill);
+          }
+
+          this._fill = f;
+          this._flagFill = true;
+
+          if (this._fill instanceof Gradient
+            || this._fill instanceof LinearGradient
+            || this._fill instanceof RadialGradient
+            || this._fill instanceof Texture) {
+            this._fill.bind(Events.Types.change, this._renderer.flagFill);
+          }
+
+        }
+      });
+
+      Object.defineProperty(object, 'stroke', {
+        enumerable: true,
+        get: function() {
+          return this._stroke;
+        },
+        set: function(f) {
+
+          if (this._stroke instanceof Gradient
+            || this._stroke instanceof LinearGradient
+            || this._stroke instanceof RadialGradient
+            || this._stroke instanceof Texture) {
+            this._stroke.unbind(Events.Types.change, this._renderer.flagStroke);
+          }
+
+          this._stroke = f;
+          this._flagStroke = true;
+
+          if (this._stroke instanceof Gradient
+            || this._stroke instanceof LinearGradient
+            || this._stroke instanceof RadialGradient
+            || this._stroke instanceof Texture) {
+            this._stroke.bind(Events.Types.change, this._renderer.flagStroke);
+          }
+
+        }
+      });
+
+      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;
+          }
+        }
+
+      });
+
+      Object.defineProperty(object, 'clip', {
+        enumerable: true,
+        get: function() {
+          return this._clip;
+        },
+        set: function(v) {
+          this._clip = v;
+          this._flagClip = true;
+        }
+      });
+
+      Object.defineProperty(object, 'dashes', {
+        enumerable: true,
+        get: function() {
+          return this._dashes;
+        },
+        set: function(v) {
+          if (typeof v.offset !== 'number') {
+            v.offset = this._dashes.offset || 0;
+          }
+          this._dashes = v;
+        }
+      });
+
+    }
+
+  });
+
+  _.extend(Text.prototype, Shape.prototype, {
+
+    constructor: Text,
+
+    // Flags
+    // http://en.wikipedia.org/wiki/Flag
+
+    /**
+     * @name Two.Text#_flagValue
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Text#value} need updating.
+     */
+    _flagValue: true,
+
+    /**
+     * @name Two.Text#_flagFamily
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Text#family} need updating.
+     */
+    _flagFamily: true,
+
+    /**
+     * @name Two.Text#_flagSize
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Text#size} need updating.
+     */
+    _flagSize: true,
+
+    /**
+     * @name Two.Text#_flagLeading
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Text#leading} need updating.
+     */
+    _flagLeading: true,
+
+    /**
+     * @name Two.Text#_flagAlignment
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Text#alignment} need updating.
+     */
+    _flagAlignment: true,
+
+    /**
+     * @name Two.Text#_flagBaseline
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Text#baseline} need updating.
+     */
+    _flagBaseline: true,
+
+    /**
+     * @name Two.Text#_flagStyle
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Text#style} need updating.
+     */
+    _flagStyle: true,
+
+    /**
+     * @name Two.Text#_flagWeight
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Text#weight} need updating.
+     */
+    _flagWeight: true,
+
+    /**
+     * @name Two.Text#_flagDecoration
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Text#decoration} need updating.
+     */
+    _flagDecoration: true,
+
+    /**
+     * @name Two.Text#_flagFill
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Text#fill} need updating.
+     */
+    _flagFill: true,
+
+    /**
+     * @name Two.Text#_flagStroke
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Text#stroke} need updating.
+     */
+    _flagStroke: true,
+
+    /**
+     * @name Two.Text#_flagLinewidth
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Text#linewidth} need updating.
+     */
+    _flagLinewidth: true,
+
+    /**
+     * @name Two.Text#_flagOpacity
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Text#opacity} need updating.
+     */
+    _flagOpacity: true,
+
+    /**
+     * @name Two.Text#_flagClassName
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Text#className} need updating.
+     */
+    _flagClassName: true,
+
+    /**
+     * @name Two.Text#_flagVisible
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Text#visible} need updating.
+     */
+    _flagVisible: true,
+
+    /**
+     * @name Two.Path#_flagMask
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Path#mask} needs updating.
+     */
+    _flagMask: false,
+
+    /**
+     * @name Two.Text#_flagClip
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Text#clip} need updating.
+     */
+    _flagClip: false,
+
+    // Underlying Properties
+
+    /**
+     * @name Two.Text#value
+     * @property {String} - The characters to be rendered to the the screen. Referred to in the documentation sometimes as the `message`.
+     */
+    _value: '',
+
+    /**
+     * @name Two.Text#family
+     * @property {String} - The font family Two.js should attempt to regsiter for rendering. The default value is `'sans-serif'`. Comma separated font names can be supplied as a "stack", similar to the CSS implementation of `font-family`.
+     */
+    _family: 'sans-serif',
+
+    /**
+     * @name Two.Text#size
+     * @property {Number} - The font size in Two.js point space. Defaults to `13`.
+     */
+    _size: 13,
+
+    /**
+     * @name Two.Text#leading
+     * @property {Number} - The height between lines measured from base to base in Two.js point space. Defaults to `17`.
+     */
+    _leading: 17,
+
+    /**
+     * @name Two.Text#alignment
+     * @property {String} - Alignment of text in relation to {@link Two.Text#translation}'s coordinates. Possible values include `'left'`, `'center'`, `'right'`. Defaults to `'center'`.
+     */
+    _alignment: 'center',
+
+    /**
+     * @name Two.Text#baseline
+     * @property {String} - The vertical aligment of the text in relation to {@link Two.Text#translation}'s coordinates. Possible values include `'top'`, `'middle'`, `'bottom'`, and `'baseline'`. Defaults to `'baseline'`.
+     */
+    _baseline: 'middle',
+
+    /**
+     * @name Two.Text#style
+     * @property {String} - The font's style. Possible values include '`normal`', `'italic'`. Defaults to `'normal'`.
+     */
+    _style: 'normal',
+
+    /**
+     * @name Two.Text#weight
+     * @property {Number} - A number at intervals of 100 to describe the font's weight. This compatibility varies with the typeface's variant weights. Larger values are bolder. Smaller values are thinner. Defaults to `'500'`.
+     */
+    _weight: 500,
+
+    /**
+     * @name Two.Text#decoration
+     * @property {String} - String to delineate whether text should be decorated with for instance an `'underline'`. Defaults to `'none'`.
+     */
+    _decoration: 'none',
+
+    /**
+     * @name Two.Text#fill
+     * @property {(String|Two.Gradient|Two.Texture)} - The value of what the text object should be filled in with.
+     * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/color_value} for more information on CSS's colors as `String`.
+     */
+    _fill: '#000',
+
+    /**
+     * @name Two.Text#stroke
+     * @property {(String|Two.Gradient|Two.Texture)} - The value of what the text object should be filled in with.
+     * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/color_value} for more information on CSS's colors as `String`.
+     */
+    _stroke: 'transparent',
+
+    /**
+     * @name Two.Text#linewidth
+     * @property {Number} - The thickness in pixels of the stroke.
+     */
+    _linewidth: 1,
+
+    /**
+     * @name Two.Text#opacity
+     * @property {Number} - The opaqueness of the text object.
+     * @nota-bene Can be used in conjunction with CSS Colors that have an alpha value.
+     */
+    _opacity: 1,
+
+    /**
+     * @name Two.Text#className
+     * @property {String} - A class to be applied to the element to be compatible with CSS styling. Only available for the {@link Two.SvgRenderer}.
+     */
+    _className: '',
+
+    /**
+     * @name Two.Text#visible
+     * @property {Boolean} - Display the text object or not.
+     * @nota-bene For {@link Two.CanvasRenderer} and {@link Two.WebGLRenderer} when set to false all updating is disabled improving performance dramatically with many objects in the scene.
+     */
+    _visible: true,
+
+    /**
+     * @name Two.Text#mask
+     * @property {Two.Shape} - The shape whose alpha property becomes a clipping area for the text.
+     * @nota-bene This property is currently not working becuase of SVG spec issues found here {@link https://code.google.com/p/chromium/issues/detail?id=370951}.
+     */
+    _mask: null,
+
+    /**
+     * @name Two.Text#clip
+     * @property {Two.Shape} - Object to define clipping area.
+     * @nota-bene This property is currently not working becuase of SVG spec issues found here {@link https://code.google.com/p/chromium/issues/detail?id=370951}.
+     */
+    _clip: false,
+
+    /**
+     * @name Two.Text#_dashes
+     * @private
+     * @see {@link Two.Text#dashes}
+     */
+    _dashes: [],
+
+    /**
+     * @name Two.Text#remove
+     * @function
+     * @description Remove self from the scene / parent.
+     */
+    remove: function() {
+
+      if (!this.parent) {
+        return this;
+      }
+
+      this.parent.remove(this);
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Text#clone
+     * @function
+     * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+     * @returns {Two.Text}
+     * @description Create a new instance of {@link Two.Text} with the same properties of the current text object.
+     */
+    clone: function(parent) {
+
+      var clone = new Text(this.value);
+      clone.translation.copy(this.translation);
+      clone.rotation = this.rotation;
+      clone.scale = this.scale;
+
+      _.each(Text.Properties, function(property) {
+        clone[property] = this[property];
+      }, this);
+
+      if (this.matrix.manual) {
+        clone.matrix.copy(this.matrix);
+      }
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone._update();
+
+    },
+
+    /**
+     * @name Two.Text#toObject
+     * @function
+     * @returns {Object}
+     * @description Return a JSON compatible plain object that represents the text object.
+     */
+    toObject: function() {
+
+      var result = {
+        translation: this.translation.toObject(),
+        rotation: this.rotation,
+        scale: this.scale
+      };
+
+      if (this.matrix.manual) {
+        result.matrix = this.matrix.toObject();
+      }
+
+      _.each(Text.Properties, function(property) {
+        result[property] = this[property];
+      }, this);
+
+      return result;
+
+    },
+
+    /**
+     * @name Two.Text#noFill
+     * @function
+     * @description Short hand method to set fill to `transparent`.
+     */
+    noFill: function() {
+      this.fill = 'transparent';
+      return this;
+    },
+
+    /**
+     * @name Two.Text#noStroke
+     * @function
+     * @description Short hand method to set stroke to `transparent`.
+     */
+    noStroke: function() {
+      this.stroke = undefined;
+      this.linewidth = undefined;
+      return this;
+    },
+
+    // A shim to not break `getBoundingClientRect` calls.
+    // TODO: Implement a way to calculate proper bounding
+    // boxes of `Two.Text`.
+
+    /**
+     * @name Two.Text#getBoundingClientRect
+     * @function
+     * @param {Boolean} [shallow=false] - Describes whether to calculate off local matrix or world matrix.
+     * @returns {Object} - Returns object with top, left, right, bottom, width, height attributes.
+     * @description Return an object with top, left, right, bottom, width, and height parameters of the text object.
+     */
+    getBoundingClientRect: function(shallow) {
+
+      var matrix, a, b, c, d;
+      var left, right, top, bottom;
+
+      // TODO: Update this to not __always__ update. Just when it needs to.
+      this._update(true);
+
+      matrix = shallow ? this._matrix : getComputedMatrix(this);
+
+      var height = this.leading;
+      var width = this.value.length * this.size * Text.Ratio;
+      var border = (this._linewidth || 0) / 2;
+
+      switch (this.alignment) {
+        case 'left':
+          left = - border;
+          right = width + border;
+          break;
+        case 'right':
+          left = - (width + border);
+          right = border;
+          break;
+        default:
+          left = - (width / 2 + border);
+          right = width / 2 + border;
+      }
+
+      switch (this.baseline) {
+        case 'top':
+          top = - border;
+          bottom = height + border;
+          break;
+        case 'bottom':
+          top = - (height + border);
+          bottom = border;
+          break;
+        default:
+          top = - (height / 2 + border);
+          bottom = height / 2 + border;
+      }
+
+      a = matrix.multiply(left, top, 1);
+      b = matrix.multiply(left, bottom, 1);
+      c = matrix.multiply(right, top, 1);
+      d = matrix.multiply(right, bottom, 1);
+
+      top = min(a.y, b.y, c.y, d.y);
+      left = min(a.x, b.x, c.x, d.x);
+      right = max(a.x, b.x, c.x, d.x);
+      bottom = max(a.y, b.y, c.y, d.y);
+
+      return {
+        top: top,
+        left: left,
+        right: right,
+        bottom: bottom,
+        width: right - left,
+        height: bottom - top
+      };
+
+    },
+
+    /**
+     * @name Two.Text#flagReset
+     * @function
+     * @private
+     * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+     */
+    flagReset: function() {
+
+      this._flagValue = this._flagFamily = this._flagSize =
+        this._flagLeading = this._flagAlignment = this._flagFill =
+        this._flagStroke = this._flagLinewidth = this._flagOpacity =
+        this._flagVisible = this._flagClip = this._flagDecoration =
+        this._flagClassName = this._flagBaseline = this._flagWeight =
+          this._flagStyle = false;
+
+      Shape.prototype.flagReset.call(this);
+
+      return this;
+
+    }
+
+  });
+
+  Text.MakeObservable(Text.prototype);
+
+  // https://github.com/jonobr1/two.js/issues/507#issuecomment-777159213
+  var regex = {
+    path: /[+-]?(?:\d*\.\d+|\d+)(?:[eE][+-]\d+)?/g
+  };
+
+  var alignments = {
+    start: 'left',
+    middle: 'center',
+    end: 'right'
+  };
+
+  /**
+   * @name Two.Utils.getAlignment
+   * @function
+   * @param {AlignmentString}
+   * @see {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor}
+   */
+  var getAlignment = function(anchor) {
+    return alignments[anchor];
+  };
+
+  var getBaseline = function(node) {
+    var a = node.getAttribute('dominant-baseline');
+    var b = node.getAttribute('alignment-baseline');
+    return a || b;
+  };
+
+  var getTagName = function(tag) {
+    return tag.replace(/svg:/ig, '').toLowerCase();
+  };
+
+  var applyTransformsToVector = function(transforms, vector) {
+
+    vector.x += transforms.translateX;
+    vector.y += transforms.translateY;
+
+    vector.x *= transforms.scaleX;
+    vector.y *= transforms.scaleY;
+
+    if (transforms.rotation !== 0) {
+      // TODO: Test further
+      var l = vector.length();
+      vector.x = l * Math.cos(transforms.rotation);
+      vector.y = l * Math.sin(transforms.rotation);
+    }
+
+  };
+
+  /**
+   * @name Two.Utils.extractCSSText
+   * @function
+   * @param {String} text - The CSS text body to be parsed and extracted.
+   * @param {Object} [styles] - The styles object to apply CSS key values to.
+   * @returns {Object} styles
+   * @description Parse CSS text body and apply them as key value pairs to a JavaScript object.
+   */
+  var extractCSSText = function(text, styles) {
+
+    var commands, command, name, value;
+
+    if (!styles) {
+      styles = {};
+    }
+
+    commands = text.split(';');
+
+    for (var i = 0; i < commands.length; i++) {
+      command = commands[i].split(':');
+      name = command[0];
+      value = command[1];
+      if (typeof name === 'undefined' || typeof value === 'undefined') {
+        continue;
+      }
+      styles[name] = value.replace(/\s/, '');
+    }
+
+    return styles;
+
+  };
+
+  /**
+   * @name Two.Utils.getSvgStyles
+   * @function
+   * @param {SVGElement} node - The SVG node to parse.
+   * @returns {Object} styles
+   * @description Get the CSS comands from the `style` attribute of an SVG node and apply them as key value pairs to a JavaScript object.
+   */
+  var getSvgStyles = function(node) {
+
+    var styles = {};
+    var attributes = getSvgAttributes(node);
+    var length = Math.max(attributes.length, node.style.length);
+
+    for (var i = 0; i < length; i++) {
+
+      var command = node.style[i];
+      var attribute = attributes[i];
+
+      if (command) {
+        styles[command] = node.style[command];
+      }
+      if (attribute) {
+        styles[attribute] = node.getAttribute(attribute);
+      }
+
+    }
+
+    return styles;
+
+  };
+
+  var getSvgAttributes = function(node) {
+
+    var attributes = node.getAttributeNames();
+
+    // Reserved attributes to remove
+    var keywords = ['id', 'class', 'transform', 'xmlns', 'viewBox'];
+
+    for (var i = 0; i < keywords.length; i++) {
+      var keyword = keywords[i];
+      var index = Array.prototype.indexOf.call(attributes, keyword);
+      if (index >= 0) {
+        attributes.splice(index, 1);
+      }
+    }
+
+    return attributes;
+
+  };
+
+  /**
+   * @name Two.Utils.applySvgViewBox
+   * @function
+   * @param {Two.Shape} node - The Two.js object to apply viewbox matrix to
+   * @param {String} value - The viewBox value from the SVG attribute
+   * @returns {Two.Shape} node
+   * @description Applies the transform of the SVG Viewbox on a given node.
+   */
+  var applySvgViewBox = function(node, value) {
+
+    var elements = value.split(/\s/);
+
+    var x = parseFloat(elements[0]);
+    var y = parseFloat(elements[1]);
+    var width = parseFloat(elements[2]);
+    var height = parseFloat(elements[3]);
+
+    var s = Math.min(this.width / width, this.height / height);
+
+    node.translation.x -= x * s;
+    node.translation.y -= y * s;
+    node.scale = s;
+
+    return node;
+
+  };
+
+  /**
+   * @name Two.Utils.applySvgAttributes
+   * @function
+   * @param {SVGElement} node - An SVG Node to extrapolate attributes from.
+   * @param {Two.Shape} elem - The Two.js object to apply extrapolated attributes to.
+   * @returns {Two.Shape} The Two.js object passed now with applied attributes.
+   * @description This function iterates through an SVG Node's properties and stores ones of interest. It tries to resolve styles applied via CSS as well.
+   * @TODO Reverse calculate {@link Two.Gradient}s for fill / stroke of any given path.
+   */
+  var applySvgAttributes = function(node, elem, parentStyles) {
+
+    var styles = {}, attributes = {}, extracted = {}, i, m, key, value, attr;
+    var transforms, x, y;
+    var id, scene, ref, tagName;
+
+    // Not available in non browser environments
+    if (root$1.getComputedStyle) {
+      // Convert CSSStyleDeclaration to a normal object
+      var computedStyles = root$1.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 (typeof value !== 'undefined') {
+          styles[key] = value;
+        }
+      }
+    }
+
+    // Convert NodeMap to a normal object
+    for (i = 0; i < node.attributes.length; i++) {
+      attr = node.attributes[i];
+      if (/style/i.test(attr.nodeName)) {
+        extractCSSText(attr.value, extracted);
+      } else {
+        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 (typeof styles.opacity !== 'undefined') {
+      styles['stroke-opacity'] = styles.opacity;
+      styles['fill-opacity'] = styles.opacity;
+      delete styles.opacity;
+    }
+
+    // Merge attributes and applied styles (attributes take precedence)
+    if (parentStyles) {
+      _.defaults(styles, parentStyles);
+    }
+    _.extend(styles, extracted, attributes);
+
+    // Similarly visibility is influenced by the value of both display and visibility.
+    // Calculate a unified value here which defaults to `true`.
+    styles.visible = !(typeof styles.display === 'undefined' && /none/i.test(styles.display))
+      || (typeof styles.visibility === 'undefined' && /hidden/i.test(styles.visibility));
+
+    // Now iterate the whole thing
+    for (key in styles) {
+      value = styles[key];
+
+      switch (key) {
+        case 'gradientTransform':
+          // TODO: Check this out https://github.com/paperjs/paper.js/blob/develop/src/svg/SvgImport.js#L315
+          if (/none/i.test(value)) break;
+          m = (node.gradientTransform && node.gradientTransform.baseVal && node.gradientTransform.baseVal.length > 0)
+            ? node.gradientTransform.baseVal[0].matrix
+            : (node.getCTM ? node.getCTM() : null);
+
+          if (m === null) break;
+
+          transforms = decomposeMatrix(m);
+
+          switch (elem._renderer.type) {
+            case 'linear-gradient':
+              applyTransformsToVector(transforms, elem.left);
+              applyTransformsToVector(transforms, elem.right);
+              break;
+            case 'radial-gradient':
+              elem.center.x += transforms.translateX;
+              elem.center.y += transforms.translateY;
+
+              elem.focal.x += transforms.translateX;
+              elem.focal.y += transforms.translateY;
+
+              elem.radius *= Math.max(transforms.scaleX, transforms.scaleY);
+              break;
+          }
+
+          break;
+        case 'transform':
+          // TODO: Check this out https://github.com/paperjs/paper.js/blob/develop/src/svg/SvgImport.js#L315
+          if (/none/i.test(value)) break;
+          m = (node.transform && node.transform.baseVal && node.transform.baseVal.length > 0)
+            ? node.transform.baseVal[0].matrix
+            : (node.getCTM ? node.getCTM() : null);
+
+          // Might happen when transform string is empty or not valid.
+          if (m === null) break;
+
+          if (Constants.AutoCalculateImportedMatrices) {
+
+            // Decompose and infer Two.js related properties.
+            transforms = decomposeMatrix(m);
+
+            elem.translation.set(transforms.translateX, transforms.translateY);
+            elem.rotation = Math.PI * (transforms.rotation / 180);
+            elem.scale = new Vector(transforms.scaleX, transforms.scaleY);
+
+            x = parseFloat((styles.x + '').replace('px'));
+            y = parseFloat((styles.y + '').replace('px'));
+
+            // Override based on attributes.
+            if (x) {
+              elem.translation.x = x;
+            }
+
+            if (y) {
+              elem.translation.y = y;
+            }
+
+          } else {
+
+            // Edit the underlying matrix and don't force an auto calc.
+            m = node.getCTM();
+            elem._matrix.manual = true;
+            elem._matrix.set(m.a, m.b, m.c, m.d, m.e, m.f);
+
+          }
+
+          break;
+        case 'viewBox':
+          applySvgViewBox.call(this, elem, value);
+          break;
+        case 'visible':
+          if (elem instanceof Group) {
+            elem._visible = value;
+            break;
+          }
+          elem.visible = value;
+          break;
+        case 'stroke-linecap':
+          if (elem instanceof Group) {
+            elem._cap = value;
+            break;
+          }
+          elem.cap = value;
+          break;
+        case 'stroke-linejoin':
+          if (elem instanceof Group) {
+            elem._join = value;
+            break;
+          }
+          elem.join = value;
+          break;
+        case 'stroke-miterlimit':
+          if (elem instanceof Group) {
+            elem._miter = value;
+            break;
+          }
+          elem.miter = value;
+          break;
+        case 'stroke-width':
+          if (elem instanceof Group) {
+            elem._linewidth = parseFloat(value);
+            break;
+          }
+          elem.linewidth = parseFloat(value);
+          break;
+        case 'opacity':
+        case 'stroke-opacity':
+        case 'fill-opacity':
+          // Only apply styles to rendered shapes
+          // in the scene.
+          if (elem instanceof Group) {
+            elem._opacity = parseFloat(value);
+            break;
+          }
+          elem.opacity = parseFloat(value);
+          break;
+        case 'clip-path':
+          if (/url\(#.*\)/i.test(value)) {
+            id = value.replace(/url\(#(.*)\)/i, '$1');
+            if (read.defs.current && read.defs.current.contains(id)) {
+              ref = read.defs.current.get(id);
+              if (ref && ref.childNodes.length > 0) {
+                ref = ref.childNodes[0];
+                tagName = getTagName(ref.nodeName);
+                elem.mask = read[tagName].call(this, ref, {});
+                switch (elem._renderer.type) {
+                  case 'path':
+                    // The matrix here needs to change to insure that the object
+                    // clipping is in the same coordinate space as the `elem`.
+                    elem.position.add(elem.mask.position);
+                    elem.mask.position.clear();
+                    break;
+                }
+              }
+            }
+          }
+          break;
+        case 'fill':
+        case 'stroke':
+          if (elem instanceof Group) {
+            key = '_' + key;
+          }
+          if (/url\(#.*\)/i.test(value)) {
+            id = value.replace(/url\(#(.*)\)/i, '$1');
+            if (read.defs.current && read.defs.current.contains(id)) {
+              ref = read.defs.current.get(id);
+              tagName = getTagName(ref.nodeName);
+              ref = read[tagName].call(this, ref, {});
+            } else {
+              scene = getScene(this);
+              ref = scene.getById(id);
+            }
+            elem[key] = ref;
+          } else {
+            elem[key] = (/none/i.test(value)) ? 'transparent' : value;
+          }
+          break;
+        case 'id':
+          elem.id = value;
+          // Overwritten id for non-conflicts on same page SVG documents
+          // TODO: Make this non-descructive
+          node.id = value + '-' + Constants.Identifier + 'applied';
+          break;
+        case 'class':
+        case 'className':
+          elem.classList = value.split(' ');
+          break;
+        case 'x':
+        case 'y':
+          var ca = elem instanceof Gradient;
+          var cb = elem instanceof LinearGradient;
+          var cc = elem instanceof RadialGradient;
+          if (ca || cb || cc) {
+            break;
+          }
+          if (value.match('[a-z%]$') && !value.endsWith('px')) {
+            var error = new TwoError(
+              'only pixel values are supported with the ' + key + ' attribute.');
+            console.warn(error.name, error.message);
+          }
+          elem.translation[key] = parseFloat(value);
+          break;
+        case 'font-family':
+          if (elem instanceof Text) {
+            elem.family = value;
+          }
+          break;
+        case 'font-size':
+          if (elem instanceof Text) {
+            elem.size = value;
+          }
+          break;
+        case 'font-weight':
+          if (elem instanceof Text) {
+            elem.weight = value;
+          }
+          break;
+        case 'font-style':
+          if (elem instanceof Text) {
+            elem.style = value;
+          }
+          break;
+        case 'text-decoration':
+          if (elem instanceof Text) {
+            elem.decoration = value;
+          }
+          break;
+        case 'line-height':
+          if (elem instanceof Text) {
+            elem.leading = value;
+          }
+          break;
+      }
+    }
+
+    return styles;
+
+  };
+
+  /**
+   * @name Two.Utils.updateDefsCache
+   * @function
+   * @param {SVGElement} node - The SVG Node with which to update the defs cache.
+   * @param {Object} Object - The defs cache to be updated.
+   * @description Update the cache of children of <defs /> tags.
+   */
+  var updateDefsCache = function(node, defsCache) {
+    for (var i = 0, l = node.childNodes.length; i < l; i++) {
+      var n = node.childNodes[i];
+      if (!n.id) continue;
+
+      var tagName = getTagName(node.nodeName);
+      if (tagName === '#text') continue;
+
+      defsCache.add(n.id, n);
+    }
+  };
+
+  /**
+   * @name Two.Utils.getScene
+   * @param {Two.Shape} node - The currently available object in the scenegraph.
+   * @returns {Group} - The highest order {@link Two.Group} in the scenegraph.
+   * @property {Function}
+   */
+  var getScene = function(node) {
+
+    while (node.parent) {
+      node = node.parent;
+    }
+
+    return node.scene;
+
+  };
+
+  /**
+   * @name Two.Utils.read
+   * @property {Object} read - A map of functions to read any number of SVG node types and create Two.js equivalents of them. Primarily used by the {@link Two#interpret} method.
+   */
+  var read = {
+
+    svg: function(node) {
+
+      var defs = read.defs.current = new Registry();
+      var elements = node.getElementsByTagName('defs');
+
+      for (var i = 0; i < elements.length; i++) {
+        updateDefsCache(elements[i], defs);
+      }
+
+      var svg = read.g.call(this, node);
+      // var viewBox = node.getAttribute('viewBox');
+
+      svg.defs = defs;  // Export out the <defs /> for later use
+      // Utils.applySvgViewBox(svg, viewBox);
+
+      delete read.defs.current;
+
+      return svg;
+
+    },
+
+    defs: function(node) {
+      return null;
+    },
+
+    use: function(node, styles) {
+
+      var error;
+      var href = node.getAttribute('href') || node.getAttribute('xlink:href');
+      if (!href) {
+        error = new TwoError('encountered <use /> with no href.');
+        console.warn(error.name, error.message);
+        return null;
+      }
+
+      var id = href.slice(1);
+      if (!read.defs.current.contains(id)) {
+        error = new TwoError(
+          'unable to find element for reference ' + href + '.');
+        console.warn(error.name, error.message);
+        return null;
+      }
+
+      var template = read.defs.current.get(id);
+      var fullNode = template.cloneNode(true);
+      var overwriteAttrs = ['x', 'y', 'width', 'height', 'href', 'xlink:href'];
+
+      for (var i = 0; i < node.attributes.length; i++) {
+        var attr = node.attributes[i];
+        var ca = overwriteAttrs.includes(attr.nodeName);
+        var cb = !fullNode.hasAttribute(attr.nodeName);
+        if (ca || cb) {
+          fullNode.setAttribute(attr.nodeName, attr.value);
+        }
+      }
+
+      var tagName = getTagName(fullNode.nodeName);
+      return read[tagName].call(this, fullNode, styles);
+
+    },
+
+    g: function(node, parentStyles) {
+
+      var styles;
+      var group = new Group();
+
+      applySvgAttributes.call(this, node, group, parentStyles);
+
+      this.add(group);
+
+      // Switched up order to inherit more specific styles
+      styles = getSvgStyles.call(this, node);
+
+      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 = getTagName(tag);
+
+        if (tagName in read) {
+          var o = read[tagName].call(group, n, styles);
+          if (!!o && !o.parent) {
+            group.add(o);
+          }
+        }
+      }
+
+      return group;
+
+    },
+
+    polygon: function(node, parentStyles) {
+
+      var points = node.getAttribute('points');
+
+      var verts = [];
+      points.replace(/(-?[\d.eE-]+)[,|\s](-?[\d.eE-]+)/g, function(match, p1, p2) {
+        verts.push(new Anchor(parseFloat(p1), parseFloat(p2)));
+      });
+
+      var poly = new Path(verts, true).noStroke();
+      poly.fill = 'black';
+
+      applySvgAttributes.call(this, node, poly, parentStyles);
+
+      return poly;
+
+    },
+
+    polyline: function(node, parentStyles) {
+      var poly = read.polygon.call(this, node, parentStyles);
+      poly.closed = false;
+      return poly;
+    },
+
+    path: function(node, parentStyles) {
+
+      var path = node.getAttribute('d');
+      var points = [];
+      var closed = false, relative = false;
+
+      if (path) {
+
+        // Create a Two.Path from the paths.
+
+        var coord = new Anchor();
+        var control, coords;
+        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 items = command.slice(1).trim().match(regex.path);
+          var type = command[0];
+          var lower = type.toLowerCase();
+          var bin, j, l, ct, times, result = [];
+
+          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':
+              if (items.length > 7) {
+                bin = 7;
+              }
+              break;
+          }
+
+          // This means we have a polybezier.
+          if (bin) {
+
+            for (j = 0, l = items.length, times = 0; j < l; j+=bin) {
+
+              ct = type;
+              if (times > 0) {
+
+                switch (type) {
+                  case 'm':
+                    ct = 'l';
+                    break;
+                  case 'M':
+                    ct = 'L';
+                    break;
+                }
+
+              }
+
+              result.push(ct + 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
+
+        _.each(commands, function(command, i) {
+
+          var result, x, y;
+          var type = command[0];
+          var lower = type.toLowerCase();
+
+          coords = command.slice(1).trim().match(regex.path);
+          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 Anchor(
+                  x, y,
+                  undefined, undefined,
+                  undefined, undefined,
+                  Commands.close
+                );
+                // Make coord be the last `m` command
+                for (var j = points.length - 1; j >= 0; j--) {
+                  var point = points[j];
+                  if (/m/i.test(point.command)) {
+                    coord = point;
+                    break;
+                  }
+                }
+              }
+              break;
+
+            case 'm':
+            case 'l':
+
+              control = undefined;
+
+              x = parseFloat(coords[0]);
+              y = parseFloat(coords[1]);
+
+              result = new Anchor(
+                x, y,
+                undefined, undefined,
+                undefined, undefined,
+                /m/i.test(lower) ? Commands.move : 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 = /h/i.test(lower) ? 'x' : 'y';
+              var b = /x/i.test(a) ? 'y' : 'x';
+
+              result = new Anchor(
+                undefined, undefined,
+                undefined, undefined,
+                undefined, undefined,
+                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 Vector();//.copy(coord);
+              }
+
+              if (/c/i.test(lower)) {
+
+                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)) {
+                Anchor.AppendCurveProperties(coord);
+              }
+
+              coord.controls.right.set(x2 - coord.x, y2 - coord.y);
+              result = new Anchor(
+                x4, y4,
+                x3 - x4, y3 - y4,
+                undefined, undefined,
+                Commands.curve
+              );
+
+              coord = result;
+              control = result.controls.left;
+
+              break;
+
+            case 't':
+            case 'q':
+
+              x1 = coord.x;
+              y1 = coord.y;
+
+              if (!control) {
+                control = new Vector();
+              }
+
+              if (/q/i.test(lower)) {
+
+                x2 = parseFloat(coords[0]);
+                y2 = parseFloat(coords[1]);
+                x3 = parseFloat(coords[0]);
+                y3 = parseFloat(coords[1]);
+                x4 = parseFloat(coords[2]);
+                y4 = parseFloat(coords[3]);
+
+              } else {
+
+                reflection = getReflection(coord, control, relative);
+
+                x2 = reflection.x;
+                y2 = reflection.y;
+                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)) {
+                Anchor.AppendCurveProperties(coord);
+              }
+
+              coord.controls.right.set(
+                (x2 - coord.x) * 0.33, (y2 - coord.y) * 0.33);
+              result = new Anchor(
+                x4, y4,
+                x3 - x4, y3 - y4,
+                undefined, undefined,
+                Commands.curve
+              );
+
+              coord = result;
+              control = result.controls.left;
+
+              break;
+
+            case 'a':
+
+              x1 = coord.x;
+              y1 = coord.y;
+
+              var rx = parseFloat(coords[0]);
+              var ry = parseFloat(coords[1]);
+              var xAxisRotation = parseFloat(coords[2]);// * 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;
+              }
+
+              var anchor = new Anchor(x4, y4);
+              anchor.command = Commands.arc;
+              anchor.rx = rx;
+              anchor.ry = ry;
+              anchor.xAxisRotation = xAxisRotation;
+              anchor.largeArcFlag = largeArcFlag;
+              anchor.sweepFlag = sweepFlag;
+
+              result = anchor;
+
+              coord = anchor;
+              control = undefined;
+
+              break;
+
+          }
+
+          if (result) {
+            if (Array.isArray(result)) {
+              points = points.concat(result);
+            } else {
+              points.push(result);
+            }
+          }
+
+        });
+
+      }
+
+      path = new 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);
+      });
+
+      applySvgAttributes.call(this, node, path, parentStyles);
+
+      path.translation.addSelf(rect.centroid);
+
+      return path;
+
+    },
+
+    circle: function(node, parentStyles) {
+
+      var x = parseFloat(node.getAttribute('cx'));
+      var y = parseFloat(node.getAttribute('cy'));
+      var r = parseFloat(node.getAttribute('r'));
+
+      var circle = new Circle(0, 0, r).noStroke();
+      circle.fill = 'black';
+
+      applySvgAttributes.call(this, node, circle, parentStyles);
+
+      circle.translation.x = x;
+      circle.translation.y = y;
+
+      return circle;
+
+    },
+
+    ellipse: function(node, parentStyles) {
+
+      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 Ellipse(0, 0, width, height).noStroke();
+      ellipse.fill = 'black';
+
+      applySvgAttributes.call(this, node, ellipse, parentStyles);
+
+      ellipse.translation.x = x;
+      ellipse.translation.y = y;
+
+      return ellipse;
+
+    },
+
+    rect: function(node, parentStyles) {
+
+      var rx = parseFloat(node.getAttribute('rx'));
+      var ry = parseFloat(node.getAttribute('ry'));
+
+      if (!_.isNaN(rx) || !_.isNaN(ry)) {
+        return read['rounded-rect'](node);
+      }
+
+      var width = parseFloat(node.getAttribute('width'));
+      var height = parseFloat(node.getAttribute('height'));
+
+      var w2 = width / 2;
+      var h2 = height / 2;
+
+      var rect = new Rectangle(0, 0, width, height)
+        .noStroke();
+      rect.fill = 'black';
+
+      applySvgAttributes.call(this, node, rect, parentStyles);
+
+      // For rectangles, (x, y) is the center of the shape rather than the top
+      // left corner.
+      rect.translation.x += w2;
+      rect.translation.y += h2;
+
+      return rect;
+
+    },
+
+    'rounded-rect': function(node, parentStyles) {
+
+      var rx = parseFloat(node.getAttribute('rx')) || 0;
+      var ry = parseFloat(node.getAttribute('ry')) || 0;
+
+      var width = parseFloat(node.getAttribute('width'));
+      var height = parseFloat(node.getAttribute('height'));
+
+      var w2 = width / 2;
+      var h2 = height / 2;
+      var radius = new Vector(rx, ry);
+
+      var rect = new RoundedRectangle(0, 0, width, height, radius)
+        .noStroke();
+      rect.fill = 'black';
+
+      applySvgAttributes.call(this, node, rect, parentStyles);
+
+      // For rectangles, (x, y) is the center of the shape rather than the top
+      // left corner.
+      rect.translation.x += w2;
+      rect.translation.y += h2;
+
+      return rect;
+
+    },
+
+    line: function(node, parentStyles) {
+
+      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 Line(x1, y1, x2, y2).noFill();
+
+      applySvgAttributes.call(this, node, line, parentStyles);
+
+      return line;
+
+    },
+
+    lineargradient: function(node, parentStyles) {
+
+      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 = child.getAttribute('offset');
+        if (/%/ig.test(offset)) {
+          offset = parseFloat(offset.replace(/%/ig, '')) / 100;
+        }
+        offset = parseFloat(offset);
+
+        var color = child.getAttribute('stop-color');
+        var opacity = child.getAttribute('stop-opacity');
+        var style = child.getAttribute('style');
+
+        var matches;
+        if (color === null) {
+          matches = style ? style.match(/stop-color:\s?([#a-fA-F0-9]*)/) : false;
+          color = matches && matches.length > 1 ? matches[1] : undefined;
+        }
+
+        if (opacity === null) {
+          matches = style ? style.match(/stop-opacity:\s?([0-9.-]*)/) : false;
+          opacity = matches && matches.length > 1 ? parseFloat(matches[1]) : 1;
+        } else {
+          opacity = parseFloat(opacity);
+        }
+
+        stops.push(new Stop(offset, color, opacity));
+
+      }
+
+      var gradient = new LinearGradient(x1 - ox, y1 - oy, x2 - ox,
+        y2 - oy, stops);
+
+      applySvgAttributes.call(this, node, gradient, parentStyles);
+
+      return gradient;
+
+    },
+
+    radialgradient: function(node, parentStyles) {
+
+      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 = child.getAttribute('offset');
+        if (/%/ig.test(offset)) {
+          offset = parseFloat(offset.replace(/%/ig, '')) / 100;
+        }
+        offset = parseFloat(offset);
+
+        var color = child.getAttribute('stop-color');
+        var opacity = child.getAttribute('stop-opacity');
+        var style = child.getAttribute('style');
+
+        var matches;
+        if (color === null) {
+          matches = style ? style.match(/stop-color:\s?([#a-fA-F0-9]*)/) : false;
+          color = matches && matches.length > 1 ? matches[1] : undefined;
+        }
+
+        if (opacity === null) {
+          matches = style ? style.match(/stop-opacity:\s?([0-9.-]*)/) : false;
+          opacity = matches && matches.length > 1 ? parseFloat(matches[1]) : 1;
+        } else {
+          opacity = parseFloat(opacity);
+        }
+
+        stops.push(new Stop(offset, color, opacity));
+
+      }
+
+      var gradient = new RadialGradient(cx - ox, cy - oy, r,
+        stops, fx - ox, fy - oy);
+
+      applySvgAttributes.call(this, node, gradient, parentStyles);
+
+      return gradient;
+
+    },
+
+    text: function(node, parentStyles) {
+
+      var alignment = getAlignment(node.getAttribute('text-anchor')) || 'left';
+      var baseline = getBaseline(node) || 'baseline';
+      var message = node.textContent;
+
+      var text = new Text(message);
+
+      applySvgAttributes.call(this, node, text, parentStyles);
+
+      text.alignment = alignment;
+      text.baseline = baseline;
+
+      return text;
+
+    },
+
+    clippath: function(node, parentStyles) {
+      if (read.defs.current && !read.defs.current.contains(node.id)) {
+        read.defs.current.add(node.id, node);
+      }
+      return null;
+    },
+
+    image: function(node, parentStyles) {
+
+      var href = node.getAttribute('href') || node.getAttribute('xlink:href');
+      if (!href) {
+        var error = new TwoError('encountered <image /> with no href.');
+        console.warn(error.name, error.message);
+        return null;
+      }
+
+      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 sprite = new Sprite(href, x, y);
+
+      if (!_.isNaN(width)) {
+        sprite.width = width;
+      }
+      if (!_.isNaN(height)) {
+        sprite.height = height;
+      }
+
+      applySvgAttributes.call(this, node, sprite, parentStyles);
+
+      return sprite;
+    }
+
+  };
+
+  /**
+   * @name Two.Utils.xhr
+   * @function
+   * @param {String} path
+   * @param {Function} callback
+   * @returns {XMLHttpRequest} The constructed and called XHR request.
+   * @description Canonical method to initiate `GET` requests in the browser. Mainly used by {@link Two#load} method.
+   */
+  function xhr(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;
+
+  }
+
+  /**
+   * @name Two.ImageSequence
+   * @class
+   * @extends Two.Rectangle
+   * @param {String|String[]|Two.Texture|Two.Texture[]} paths - A list of URLs or {@link Two.Texture}s.
+   * @param {Number} [ox=0] - The initial `x` position of the Two.ImageSequence.
+   * @param {Number} [oy=0] - The initial `y` position of the Two.ImageSequence.
+   * @param {Number} [frameRate=30] - The frame rate at which the images should playback at.
+   * @description A convenient package to display still or animated images organized as a series of still images.
+   */
+  function ImageSequence(paths, ox, oy, frameRate) {
+
+    // Not using default constructor of Rectangle due to odd `beginning` / `ending` behavior.
+    // See: https://github.com/jonobr1/two.js/issues/383
+    Path.call(this, [
+      new Anchor(),
+      new Anchor(),
+      new Anchor(),
+      new Anchor()
+    ], true);
+
+    this._renderer.flagTextures = ImageSequence.FlagTextures.bind(this);
+    this._renderer.bindTextures = ImageSequence.BindTextures.bind(this);
+    this._renderer.unbindTextures = ImageSequence.UnbindTextures.bind(this);
+
+    this.noStroke();
+    this.noFill();
+
+    /**
+     * @name Two.ImageSequence#textures
+     * @property {Two.Texture[]} - A list of textures to be used as frames for animating the {@link Two.ImageSequence}.
+     */
+    if (Array.isArray(paths)) {
+      this.textures = paths.map(ImageSequence.GenerateTexture.bind(this));
+    } else {
+      // If just a single path convert into a single Two.Texture
+      this.textures = [ImageSequence.GenerateTexture(paths)];
+    }
+
+    this.origin = new Vector();
+
+    this._update();
+    this.translation.set(ox || 0, oy || 0);
+
+    /**
+     * @name Two.ImageSequence#frameRate
+     * @property {Number} - The number of frames to animate against per second.
+     */
+    if (typeof frameRate === 'number') {
+      this.frameRate = frameRate;
+    } else {
+      this.frameRate = ImageSequence.DefaultFrameRate;
+    }
+
+    /**
+     * @name Two.ImageSequence#index
+     * @property {Number} - The index of the current tile of the sprite to display. Defaults to `0`.
+     */
+    this.index = 0;
+
+  }
+
+  _.extend(ImageSequence, {
+
+    /**
+     * @name Two.ImageSequence.Properties
+     * @property {String[]} - A list of properties that are on every {@link Two.ImageSequence}.
+     */
+    Properties: [
+      'frameRate',
+      'index'
+    ],
+
+    /**
+     * @name Two.ImageSequence.DefaultFrameRate
+     * @property The default frame rate that {@link Two.ImageSequence#frameRate} is set to when instantiated.
+     */
+    DefaultFrameRate: 30,
+
+    /**
+     * @name Two.ImageSequence.FlagTextures
+     * @function
+     * @description Cached method to let renderers know textures have been updated on a {@link Two.ImageSequence}.
+     */
+    FlagTextures: function() {
+      this._flagTextures = true;
+    },
+
+    /**
+     * @name Two.ImageSequence.BindTextures
+     * @function
+     * @description Cached method to let {@link Two.ImageSequence} know textures have been added to the instance.
+     */
+    BindTextures: function(items) {
+
+      var i = items.length;
+      while (i--) {
+        items[i].bind(Events.Types.change, this._renderer.flagTextures);
+      }
+
+      this._renderer.flagTextures();
+
+    },
+
+    /**
+     * @name Two.ImageSequence.UnbindVertices
+     * @function
+     * @description Cached method to let {@link Two.ImageSequence} know textures have been removed from the instance.
+     */
+    UnbindTextures: function(items) {
+
+      var i = items.length;
+      while (i--) {
+        items[i].unbind(Events.Types.change, this._renderer.flagTextures);
+      }
+
+      this._renderer.flagTextures();
+
+    },
+
+    /**
+     * @name Two.ImageSequence.MakeObservable
+     * @function
+     * @param {Object} object - The object to make observable.
+     * @description Convenience function to apply observable qualities of a {@link Two.ImageSequence} to any object. Handy if you'd like to extend or inherit the {@link Two.ImageSequence} class on a custom class.
+     */
+    MakeObservable: function(obj) {
+
+      Rectangle.MakeObservable(obj);
+      _.each(ImageSequence.Properties, defineGetterSetter, obj);
+
+      Object.defineProperty(obj, 'textures', {
+
+        enumerable: true,
+
+        get: function() {
+          return this._textures;
+        },
+
+        set: function(textures) {
+
+          var bindTextures = this._renderer.bindTextures;
+          var unbindTextures = this._renderer.unbindTextures;
+
+          // Remove previous listeners
+          if (this._textures) {
+            this._textures
+              .unbind(Events.Types.insert, bindTextures)
+              .unbind(Events.Types.remove, unbindTextures);
+          }
+
+          // Create new Collection with copy of vertices
+          this._textures = new Collection((textures || []).slice(0));
+
+          // Listen for Collection changes and bind / unbind
+          this._textures
+            .bind(Events.Types.insert, bindTextures)
+            .bind(Events.Types.remove, unbindTextures);
+
+          // Bind Initial Textures
+          bindTextures(this._textures);
+
+        }
+
+      });
+
+    },
+
+    /**
+     * @name Two.ImageSequence.GenerateTexture
+     * @property {Function} - Shorthand function to prepare source image material into readable format by {@link Two.ImageSequence}.
+     * @param {String|Two.Texture} textureOrString - The texture or string to create a {@link Two.Texture} from.
+     * @description Function used internally by {@link Two.ImageSequence} to parse arguments and return {@link Two.Texture}s.
+     * @returns {Two.Texture}
+     */
+    GenerateTexture: function(obj) {
+      if (obj instanceof Texture) {
+        return obj;
+      } else if (typeof obj === 'string') {
+        return new Texture(obj);
+      }
+    }
+
+  });
+
+  _.extend(ImageSequence.prototype, Rectangle.prototype, {
+
+    constructor: ImageSequence,
+
+    /**
+     * @name Two.ImageSequence#_flagTextures
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.ImageSequence#textures} need updating.
+     */
+    _flagTextures: false,
+
+    /**
+     * @name Two.ImageSequence#_flagFrameRate
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.ImageSequence#frameRate} needs updating.
+     */
+    _flagFrameRate: false,
+
+    /**
+     * @name Two.ImageSequence#_flagIndex
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.ImageSequence#index} needs updating.
+     */
+    _flagIndex: false,
+
+    // Private variables
+
+    /**
+     * @name Two.ImageSequence#_amount
+     * @private
+     * @property {Number} - Number of frames for a given {@link Two.ImageSequence}.
+     */
+    _amount: 1,
+
+    /**
+     * @name Two.ImageSequence#_duration
+     * @private
+     * @property {Number} - Number of milliseconds a {@link Two.ImageSequence}.
+     */
+    _duration: 0,
+
+    /**
+     * @name Two.ImageSequence#_index
+     * @private
+     * @property {Number} - The current frame the {@link Two.ImageSequence} is currently displaying.
+     */
+    _index: 0,
+
+    /**
+     * @name Two.ImageSequence#_startTime
+     * @private
+     * @property {Milliseconds} - Epoch time in milliseconds of when the {@link Two.ImageSequence} started.
+     */
+    _startTime: 0,
+
+    /**
+     * @name Two.ImageSequence#_playing
+     * @private
+     * @property {Boolean} - Dictates whether the {@link Two.ImageSequence} is animating or not.
+     */
+    _playing: false,
+
+    /**
+     * @name Two.ImageSequence#_firstFrame
+     * @private
+     * @property {Number} - The frame the {@link Two.ImageSequence} should start with.
+     */
+    _firstFrame: 0,
+
+    /**
+     * @name Two.ImageSequence#_lastFrame
+     * @private
+     * @property {Number} - The frame the {@link Two.ImageSequence} should end with.
+     */
+    _lastFrame: 0,
+
+    /**
+     * @name Two.ImageSequence#_playing
+     * @private
+     * @property {Boolean} - Dictates whether the {@link Two.ImageSequence} should loop or not.
+     */
+    _loop: true,
+
+    // Exposed through getter-setter
+
+    /**
+     * @name Two.ImageSequence#_textures
+     * @private
+     * @see {@link Two.ImageSequence#textures}
+     */
+    _textures: null,
+
+    /**
+     * @name Two.ImageSequence#_frameRate
+     * @private
+     * @see {@link Two.ImageSequence#frameRate}
+     */
+    _frameRate: 0,
+
+    /**
+     * @name Two.ImageSequence#_origin
+     * @private
+     * @see {@link Two.ImageSequence#origin}
+     */
+    _origin: null,
+
+    /**
+     * @name Two.ImageSequence#play
+     * @function
+     * @param {Number} [firstFrame=0] - The index of the frame to start the animation with.
+     * @param {Number} [lastFrame] - The index of the frame to end the animation with. Defaults to the last item in the {@link Two.ImageSequence#textures}.
+     * @param {Function} [onLastFrame] - Optional callback function to be triggered after playing the last frame. This fires multiple times when the image sequence is looped.
+     * @description Initiate animation playback of a {@link Two.ImageSequence}.
+     */
+    play: function(firstFrame, lastFrame, onLastFrame) {
+
+      this._playing = true;
+      this._firstFrame = 0;
+      this._lastFrame = this.amount - 1;
+      this._startTime = _.performance.now();
+
+      if (typeof firstFrame === 'number') {
+        this._firstFrame = firstFrame;
+      }
+      if (typeof lastFrame === 'number') {
+        this._lastFrame = lastFrame;
+      }
+      if (typeof onLastFrame === 'function') {
+        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;
+
+    },
+
+    /**
+     * @name Two.ImageSequence#pause
+     * @function
+     * @description Halt animation playback of a {@link Two.ImageSequence}.
+     */
+    pause: function() {
+
+      this._playing = false;
+      return this;
+
+    },
+
+    /**
+     * @name Two.ImageSequence#stop
+     * @function
+     * @description Halt animation playback of a {@link Two.ImageSequence} and set the current frame back to the first frame.
+     */
+    stop: function() {
+
+      this._playing = false;
+      this._index = this._firstFrame;
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.ImageSequence#clone
+     * @function
+     * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+     * @returns {Two.ImageSequence}
+     * @description Create a new instance of {@link Two.ImageSequence} with the same properties of the current image sequence.
+     */
+    clone: function(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;
+
+    },
+
+    /**
+     * @name Two.ImageSequence#toObject
+     * @function
+     * @returns {Object}
+     * @description Return a JSON compatible plain object that represents the path.
+     */
+    toObject: function() {
+      var object = Rectangle.prototype.toObject.call(this);
+      object.textures = this.textures.map(function(texture) {
+        return texture.toObject();
+      });
+      object.frameRate = this.frameRate;
+      object.index = this.index;
+      object._firstFrame = this._firstFrame;
+      object._lastFrame = this._lastFrame;
+      object._loop = this._loop;
+      return object;
+    },
+
+    /**
+     * @name Two.ImageSequence#_update
+     * @function
+     * @private
+     * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+     * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+     * @nota-bene Try not to call this method more than once a frame.
+     */
+    _update: function() {
+
+      var 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 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;
+
+    },
+
+    /**
+     * @name Two.ImageSequence#flagReset
+     * @function
+     * @private
+     * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+     */
+    flagReset: function() {
+
+      this._flagTextures = this._flagFrameRate = false;
+      Rectangle.prototype.flagReset.call(this);
+
+      return this;
+
+    }
+
+  });
+
+  ImageSequence.MakeObservable(ImageSequence.prototype);
+
+  var TWO_PI$2 = Math.PI * 2, HALF_PI = Math.PI / 2;
+
+  /**
+   * @name Two.ArcSegment
+   * @class
+   * @extends Two.Path
+   * @param {Number} [x=0] - The x position of the arc segment.
+   * @param {Number} [y=0] - The y position of the arc segment.
+   * @param {Number} [innerRadius=0] - The inner radius value of the arc segment.
+   * @param {Number} [outerRadius=0] - The outer radius value of the arc segment.
+   * @param {Number} [startAngle=0] - The start angle of the arc segment in Number.
+   * @param {Number} [endAngle=6.2831] - The end angle of the arc segment in Number.
+   * @param {Number} [resolution=24] - The number of vertices used to construct the arc segment.
+   */
+  function ArcSegment(ox, oy, ir, or, sa, ea, res) {
+
+    var amount = res || (Constants.Resolution * 3);
+    var points = [];
+    for (var i = 0; i < amount; i++) {
+      points.push(new Anchor());
+    }
+
+    Path.call(this, points, true, false, true);
+
+    /**
+     * @name Two.ArcSegment#innerRadius
+     * @property {Number} - The size of the inner radius of the arc segment.
+     */
+    if (typeof ir === 'number') {
+      this.innerRadius = ir;
+    }
+
+    /**
+     * @name Two.ArcSegment#outerRadius
+     * @property {Number} - The size of the outer radius of the arc segment.
+     */
+    if (typeof or === 'number') {
+      this.outerRadius = or;
+    }
+
+    /**
+     * @name Two.ArcSegment#startRadius
+     * @property {Number} - The angle of one side for the arc segment.
+     */
+    if (typeof sa === 'number') {
+      this.startAngle = sa;
+    }
+
+    /**
+     * @name Two.ArcSegment#endAngle
+     * @property {Number} - The angle of the other side for the arc segment.
+     */
+    if (typeof ea === 'number') {
+      this.endAngle = ea;
+    }
+
+    this._update();
+
+    if (typeof ox === 'number') {
+      this.translation.x = ox;
+    }
+    if (typeof oy === 'number') {
+      this.translation.y = oy;
+    }
+
+  }
+
+  _.extend(ArcSegment, {
+
+    /**
+     * @name Two.ArcSegment.Properties
+     * @property {String[]} - A list of properties that are on every {@link Two.ArcSegment}.
+     */
+    Properties: ['startAngle', 'endAngle', 'innerRadius', 'outerRadius'],
+
+    /**
+     * @name Two.ArcSegment.MakeObservable
+     * @function
+     * @param {Object} object - The object to make observable.
+     * @description Convenience function to apply observable qualities of a {@link Two.ArcSegment} to any object. Handy if you'd like to extend the {@link Two.ArcSegment} class on a custom class.
+     */
+    MakeObservable: function(obj) {
+
+      Path.MakeObservable(obj);
+      _.each(ArcSegment.Properties, defineGetterSetter, obj);
+
+    }
+
+  });
+
+  _.extend(ArcSegment.prototype, Path.prototype, {
+
+    constructor: ArcSegment,
+
+    /**
+     * @name Two.ArcSegment#_flagStartAngle
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.ArcSegment#startAngle} needs updating.
+     */
+    _flagStartAngle: false,
+    /**
+     * @name Two.ArcSegment#_flagEndAngle
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.ArcSegment#endAngle} needs updating.
+     */
+    _flagEndAngle: false,
+    /**
+     * @name Two.ArcSegment#_flagInnerRadius
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.ArcSegment#innerRadius} needs updating.
+     */
+    _flagInnerRadius: false,
+    /**
+     * @name Two.ArcSegment#_flagOuterRadius
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.ArcSegment#outerRadius} needs updating.
+     */
+    _flagOuterRadius: false,
+
+    /**
+     * @name Two.ArcSegment#_startAngle
+     * @private
+     * @see {@link Two.ArcSegment#startAngle}
+     */
+    _startAngle: 0,
+    /**
+     * @name Two.ArcSegment#_endAngle
+     * @private
+     * @see {@link Two.ArcSegment#endAngle}
+     */
+    _endAngle: TWO_PI$2,
+    /**
+     * @name Two.ArcSegment#_innerRadius
+     * @private
+     * @see {@link Two.ArcSegment#innerRadius}
+     */
+    _innerRadius: 0,
+    /**
+     * @name Two.ArcSegment#_outerRadius
+     * @private
+     * @see {@link Two.ArcSegment#outerRadius}
+     */
+    _outerRadius: 0,
+
+    /**
+     * @name Two.ArcSegment#_update
+     * @function
+     * @private
+     * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+     * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+     * @nota-bene Try not to call this method more than once a frame.
+     */
+    _update: function() {
+
+      if (this._flagVertices || 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$2) === mod(ea, TWO_PI$2);
+        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 = Commands.move;
+              break;
+            default:
+              command = Commands.curve;
+          }
+
+          v.command = command;
+          v.x = x;
+          v.y = y;
+          v.controls.left.clear();
+          v.controls.right.clear();
+
+          if (v.command === 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 = 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 = Commands.curve;
+            if (i <= 0) {
+              command = connected ? Commands.move : Commands.line;
+            }
+
+            v.command = command;
+            v.x = x;
+            v.y = y;
+            v.controls.left.clear();
+            v.controls.right.clear();
+
+            if (v.command === 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++;
+
+          }
+
+          // Final Point
+          vertices[id].copy(vertices[0]);
+          vertices[id].command = Commands.line;
+
+        } else if (!connected) {
+
+          vertices[id].command = Commands.line;
+          vertices[id].x = 0;
+          vertices[id].y = 0;
+          id++;
+
+          // Final Point
+          vertices[id].copy(vertices[0]);
+          vertices[id].command = Commands.line;
+
+        }
+
+      }
+
+      Path.prototype._update.call(this);
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.ArcSegment#flagReset
+     * @function
+     * @private
+     * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+     */
+    flagReset: function() {
+
+      Path.prototype.flagReset.call(this);
+
+      this._flagStartAngle = this._flagEndAngle
+        = this._flagInnerRadius = this._flagOuterRadius = false;
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.ArcSegment#clone
+     * @function
+     * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+     * @returns {Two.ArcSegment}
+     * @description Create a new instance of {@link Two.ArcSegment} with the same properties of the current path.
+     */
+    clone: function(parent) {
+
+      var ir = this.innerRadius;
+      var or = this.outerRadius;
+      var sa = this.startAngle;
+      var ea = this.endAngle;
+      var resolution = this.vertices.length;
+
+      var clone = new ArcSegment(0, 0, ir, or, sa, ea, resolution);
+
+      clone.translation.copy(this.translation);
+      clone.rotation = this.rotation;
+      clone.scale = this.scale;
+      clone.skewX = this.skewX;
+      clone.skewY = this.skewY;
+
+      if (this.matrix.manual) {
+        clone.matrix.copy(this.matrix);
+      }
+
+      _.each(Path.Properties, function(k) {
+        clone[k] = this[k];
+      }, this);
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone;
+
+    },
+
+    /**
+     * @name Two.ArcSegment#toObject
+     * @function
+     * @returns {Object}
+     * @description Return a JSON compatible plain object that represents the path.
+     */
+    toObject: function() {
+
+      var object = Path.prototype.toObject.call(this);
+
+      _.each(ArcSegment.Properties, function(property) {
+        object[property] = this[property];
+      }, this);
+
+      return object;
+
+    }
+
+  });
+
+  ArcSegment.MakeObservable(ArcSegment.prototype);
+
+  var TWO_PI$1 = Math.PI * 2, cos$1 = Math.cos, sin$1 = Math.sin;
+
+  /**
+   * @name Two.Polygon
+   * @class
+   * @extends Two.Path
+   * @param {Number} [x=0] - The x position of the polygon.
+   * @param {Number} [y=0] - The y position of the polygon.
+   * @param {Number} [radius=0] - The radius value of the polygon.
+   * @param {Number} [sides=12] - The number of vertices used to construct the polygon.
+   */
+  function Polygon(ox, oy, r, sides) {
+
+    sides = Math.max(sides || 0, 3);
+
+    Path.call(this);
+
+    this.closed = true;
+    this.automatic = false;
+
+    /**
+     * @name Two.Polygon#width
+     * @property {Number} - The size of the width of the polygon.
+     */
+    if (typeof r === 'number') {
+      this.width = r * 2;
+    }
+
+    /**
+     * @name Two.Polygon#height
+     * @property {Number} - The size of the height of the polygon.
+     */
+    if (typeof r === 'number') {
+      this.height = r * 2;
+    }
+
+    /**
+     * @name Two.Polygon#sides
+     * @property {Number} - The amount of sides the polyogn has.
+     */
+    if (typeof sides === 'number') {
+      this.sides = sides;
+    }
+
+    this._update();
+
+    if (typeof ox === 'number') {
+      this.translation.x = ox;
+    }
+    if (typeof oy === 'number') {
+      this.translation.y = oy;
+    }
+
+  }
+
+  _.extend(Polygon, {
+
+    /**
+     * @name Two.Polygon.Properties
+     * @property {String[]} - A list of properties that are on every {@link Two.Polygon}.
+     */
+    Properties: ['width', 'height', 'sides'],
+
+    /**
+     * @name Two.Polygon.MakeObservable
+     * @function
+     * @param {Object} object - The object to make observable.
+     * @description Convenience function to apply observable qualities of a {@link Two.Polygon} to any object. Handy if you'd like to extend the {@link Two.Polygon} class on a custom class.
+     */
+    MakeObservable: function(obj) {
+
+      Path.MakeObservable(obj);
+      _.each(Polygon.Properties, defineGetterSetter, obj);
+
+    }
+
+  });
+
+  _.extend(Polygon.prototype, Path.prototype, {
+
+    constructor: Polygon,
+
+    /**
+     * @name Two.Polygon#_flagWidth
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Polygon#width} needs updating.
+     */
+    _flagWidth: false,
+    /**
+     * @name Two.Polygon#_flagHeight
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Polygon#height} needs updating.
+     */
+    _flagHeight: false,
+    /**
+     * @name Two.Polygon#_flagSides
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Polygon#sides} needs updating.
+     */
+    _flagSides: false,
+
+    /**
+     * @name Two.Polygon#_width
+     * @private
+     * @see {@link Two.Polygon#width}
+     */
+    _width: 0,
+    /**
+     * @name Two.Polygon#_height
+     * @private
+     * @see {@link Two.Polygon#height}
+     */
+    _height: 0,
+    /**
+     * @name Two.Polygon#_sides
+     * @private
+     * @see {@link Two.Polygon#sides}
+     */
+    _sides: 0,
+
+    /**
+     * @name Two.Polygon#_update
+     * @function
+     * @private
+     * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+     * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+     * @nota-bene Try not to call this method more than once a frame.
+     */
+    _update: function() {
+
+      if (this._flagVertices || this._flagWidth || this._flagHeight || this._flagSides) {
+
+        var sides = this._sides;
+        var amount = sides + 1;
+        var length = this.vertices.length;
+
+        if (length > sides) {
+          this.vertices.splice(sides - 1, length - sides);
+          length = sides;
+        }
+
+        for (var i = 0; i < amount; i++) {
+
+          var pct = (i + 0.5) / sides;
+          var theta = TWO_PI$1 * pct + Math.PI / 2;
+          var x = this._width * cos$1(theta) / 2;
+          var y = this._height * sin$1(theta) / 2;
+
+          if (i >= length) {
+            this.vertices.push(new Anchor(x, y));
+          } else {
+            this.vertices[i].set(x, y);
+          }
+
+          this.vertices[i].command = i === 0 ? Commands.move : Commands.line;
+
+        }
+
+      }
+
+      Path.prototype._update.call(this);
+      return this;
+
+    },
+
+    /**
+     * @name Two.Polygon#flagReset
+     * @function
+     * @private
+     * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+     */
+    flagReset: function() {
+
+      this._flagWidth = this._flagHeight = this._flagSides = false;
+      Path.prototype.flagReset.call(this);
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Polygon#clone
+     * @function
+     * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+     * @returns {Two.Polygon}
+     * @description Create a new instance of {@link Two.Polygon} with the same properties of the current path.
+     */
+    clone: function(parent) {
+
+      var clone = new Polygon(0, 0, this.radius, this.sides);
+
+      clone.translation.copy(this.translation);
+      clone.rotation = this.rotation;
+      clone.scale = this.scale;
+      clone.skewX = this.skewX;
+      clone.skewY = this.skewY;
+
+      if (this.matrix.manual) {
+        clone.matrix.copy(this.matrix);
+      }
+
+      _.each(Path.Properties, function(k) {
+        clone[k] = this[k];
+      }, this);
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone;
+
+    },
+
+    /**
+     * @name Two.Polygon#toObject
+     * @function
+     * @returns {Object}
+     * @description Return a JSON compatible plain object that represents the path.
+     */
+    toObject: function() {
+
+      var object = Path.prototype.toObject.call(this);
+
+      _.each(Polygon.Properties, function(property) {
+        object[property] = this[property];
+      }, this);
+
+      return object;
+
+    }
+
+  });
+
+  Polygon.MakeObservable(Polygon.prototype);
+
+  var TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin;
+
+  /**
+   * @name Two.Star
+   * @class
+   * @extends Two.Path
+   * @param {Number} [x=0] - The x position of the star.
+   * @param {Number} [y=0] - The y position of the star.
+   * @param {Number} [innerRadius=0] - The inner radius value of the star.
+   * @param {Number} [outerRadius=0] - The outer radius value of the star.
+   * @param {Number} [sides=5] - The number of sides used to construct the star.
+   */
+  function Star(ox, oy, ir, or, sides) {
+
+    if (arguments.length <= 3) {
+      or = ir;
+      ir = or / 2;
+    }
+
+    if (typeof sides !== 'number' || sides <= 0) {
+      sides = 5;
+    }
+
+    Path.call(this);
+    this.closed = true;
+    this.automatic = false;
+
+    /**
+     * @name Two.Star#innerRadius
+     * @property {Number} - The size of the inner radius of the star.
+     */
+    if (typeof ir === 'number') {
+      this.innerRadius = ir;
+    }
+
+    /**
+     * @name Two.Star#outerRadius
+     * @property {Number} - The size of the outer radius of the star.
+     */
+    if (typeof or === 'number') {
+      this.outerRadius = or;
+    }
+
+    /**
+     * @name Two.Star#sides
+     * @property {Number} - The amount of sides the star has.
+     */
+    if (typeof sides === 'number') {
+      this.sides = sides;
+    }
+
+    this._update();
+
+    if (typeof ox === 'number') {
+      this.translation.x = ox;
+    }
+    if (typeof oy === 'number') {
+      this.translation.y = oy;
+    }
+
+  }
+
+  _.extend(Star, {
+
+    /**
+     * @name Two.Star.Properties
+     * @property {String[]} - A list of properties that are on every {@link Two.Star}.
+     */
+    Properties: ['innerRadius', 'outerRadius', 'sides'],
+
+    /**
+     * @name Two.Star.MakeObservable
+     * @function
+     * @param {Object} object - The object to make observable.
+     * @description Convenience function to apply observable qualities of a {@link Two.Star} to any object. Handy if you'd like to extend the {@link Two.Star} class on a custom class.
+     */
+    MakeObservable: function(obj) {
+
+      Path.MakeObservable(obj);
+      _.each(Star.Properties, defineGetterSetter, obj);
+
+    }
+
+  });
+
+  _.extend(Star.prototype, Path.prototype, {
+
+    constructor: Star,
+
+    /**
+     * @name Two.Star#_flagInnerRadius
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Star#innerRadius} needs updating.
+     */
+    _flagInnerRadius: false,
+    /**
+     * @name Two.Star#_flagOuterRadius
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Star#outerRadius} needs updating.
+     */
+    _flagOuterRadius: false,
+    /**
+     * @name Two.Star#_flagSides
+     * @private
+     * @property {Boolean} - Determines whether the {@link Two.Star#sides} needs updating.
+     */
+    _flagSides: false,
+
+    /**
+     * @name Two.Star#_innerRadius
+     * @private
+     * @see {@link Two.Star#innerRadius}
+     */
+    _innerRadius: 0,
+    /**
+     * @name Two.Star#_outerRadius
+     * @private
+     * @see {@link Two.Star#outerRadius}
+     */
+    _outerRadius: 0,
+    /**
+     * @name Two.Star#_sides
+     * @private
+     * @see {@link Two.Star#sides}
+     */
+    _sides: 0,
+
+    /**
+     * @name Two.Star#_update
+     * @function
+     * @private
+     * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+     * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+     * @nota-bene Try not to call this method more than once a frame.
+     */
+    _update: function() {
+
+      if (this._flagVertices || this._flagInnerRadius || this._flagOuterRadius || this._flagSides) {
+
+        var sides = this._sides * 2;
+        var amount = sides + 1;
+        var length = this.vertices.length;
+
+        if (length > sides) {
+          this.vertices.splice(sides - 1, length - sides);
+          length = sides;
+        }
+
+        for (var i = 0; i < amount; i++) {
+
+          var pct = (i + 0.5) / sides;
+          var theta = TWO_PI * pct;
+          var r = (!(i % 2) ? this._innerRadius : this._outerRadius) / 2;
+          var x = r * cos(theta);
+          var y = r * sin(theta);
+
+          if (i >= length) {
+            this.vertices.push(new Anchor(x, y));
+          } else {
+            this.vertices[i].set(x, y);
+          }
+
+          this.vertices[i].command = i === 0 ? Commands.move : Commands.line;
+
+        }
+
+      }
+
+      Path.prototype._update.call(this);
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Star#flagReset
+     * @function
+     * @private
+     * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+     */
+    flagReset: function() {
+
+      this._flagInnerRadius = this._flagOuterRadius = this._flagSides = false;
+      Path.prototype.flagReset.call(this);
+
+      return this;
+
+    },
+
+    /**
+     * @name Two.Star#clone
+     * @function
+     * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+     * @returns {Two.Star}
+     * @description Create a new instance of {@link Two.Star} with the same properties of the current path.
+     */
+    clone: function(parent) {
+
+      var ir = this.innerRadius;
+      var or = this.outerRadius;
+      var sides = this.sides;
+
+      var clone = new Star(0, 0, ir, or, sides);
+
+      clone.translation.copy(this.translation);
+      clone.rotation = this.rotation;
+      clone.scale = this.scale;
+      clone.skewX = this.skewX;
+      clone.skewY = this.skewY;
+
+      if (this.matrix.manual) {
+        clone.matrix.copy(this.matrix);
+      }
+
+      _.each(Path.Properties, function(k) {
+        clone[k] = this[k];
+      }, this);
+
+      if (parent) {
+        parent.add(clone);
+      }
+
+      return clone;
+
+    },
+
+    /**
+     * @name Two.Star#toObject
+     * @function
+     * @returns {Object}
+     * @description Return a JSON compatible plain object that represents the path.
+     */
+    toObject: function() {
+
+      var object = Path.prototype.toObject.call(this);
+
+      _.each(Star.Properties, function(property) {
+        object[property] = this[property];
+      }, this);
+
+      return object;
+
+    }
+
+  });
+
+  Star.MakeObservable(Star.prototype);
+
+  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 (attrs && Object.keys(attrs).length > 0) {
+        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 Commands.move point
+        string = '';
+
+      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;
+        var rx, ry, xAxisRotation, largeArcFlag, sweepFlag;
+
+        // Access x and y directly,
+        // bypassing the getter
+        var x = toFixed(b.x);
+        var y = toFixed(b.y);
+
+        switch (b.command) {
+
+          case Commands.close:
+            command = Commands.close;
+            break;
+
+          case Commands.arc:
+
+            rx = b.rx;
+            ry = b.ry;
+            xAxisRotation = b.xAxisRotation;
+            largeArcFlag = b.largeArcFlag;
+            sweepFlag = b.sweepFlag;
+
+            command = Commands.arc + ' ' + rx + ' ' + ry + ' '
+              + xAxisRotation + ' ' + largeArcFlag + ' ' + sweepFlag + ' '
+              + x + ' ' + y;
+            break;
+
+          case Commands.curve:
+
+            ar = (a.controls && a.controls.right) || Vector.zero;
+            bl = (b.controls && b.controls.left) || 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) ? Commands.move : Commands.curve) +
+              ' ' + vx + ' ' + vy + ' ' + ux + ' ' + uy + ' ' + x + ' ' + y;
+            break;
+
+          case Commands.move:
+            d = b;
+            command = 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 === Commands.curve) {
+
+            // Make sure we close to the most previous 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;
+
+          }
+
+          if (b.command !== Commands.close) {
+            command += ' Z';
+          }
+
+        }
+
+        string += command + ' ';
+
+      }
+
+      return string;
+
+    },
+
+    getClip: function(shape, domElement) {
+
+      var clip = shape._renderer.clip;
+
+      if (!clip) {
+
+        clip = shape._renderer.clip = svg.createElement('clipPath', {
+          'clip-rule': 'nonzero'
+        });
+        domElement.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) {
+
+        // Shortcut for hidden objects.
+        // Doesn't reset the flags, so changes are stored and
+        // applied once the object is visible again
+        if ((!this._visible && !this._flagVisible)
+          || (this._opacity === 0 && !this._flagOpacity)) {
+          return this;
+        }
+
+        this._update();
+
+        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._flagId) {
+          this._renderer.elem.setAttribute('id', this._id);
+        }
+
+        if (this._flagOpacity) {
+          this._renderer.elem.setAttribute('opacity', this._opacity);
+        }
+
+        if (this._flagVisible) {
+          this._renderer.elem.setAttribute('display', this._visible ? 'inline' : 'none');
+        }
+
+        if (this._flagClassName) {
+          this._renderer.elem.setAttribute('class', this.classList.join(' '));
+        }
+
+        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, domElement);
+        //   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) {
+            svg[this._mask._renderer.type].render.call(this._mask, domElement);
+            this._renderer.elem.setAttribute('clip-path', 'url(#' + this._mask.id + ')');
+          } else {
+            this._renderer.elem.removeAttribute('clip-path');
+          }
+        }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    path: {
+
+      render: function(domElement) {
+
+        // 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;
+        }
+
+        this._update();
+
+        // 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._flagId) {
+          changed.id = this._id;
+        }
+
+        if (this._flagVertices) {
+          var vertices = svg.toString(this._renderer.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._flagClassName) {
+          changed['class'] = this.classList.join(' ');
+        }
+
+        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 (this.dashes && this.dashes.length > 0) {
+          changed['stroke-dasharray'] = this.dashes.join(' ');
+          changed['stroke-dashoffset'] = this.dashes.offset || 0;
+        }
+
+        // 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, domElement);
+          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) {
+            svg[this._mask._renderer.type].render.call(this._mask, domElement);
+            this._renderer.elem.setAttribute('clip-path', 'url(#' + this._mask.id + ')');
+          } else {
+            this._renderer.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._flagId) {
+          changed.id = this._id;
+        }
+
+        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._flagClassName) {
+          changed['class'] = this.classList.join(' ');
+        }
+        if (this._flagVisible) {
+          changed.visibility = this._visible ? 'visible' : 'hidden';
+        }
+        if (this.dashes && this.dashes.length > 0) {
+          changed['stroke-dasharray'] = this.dashes.join(' ');
+          changed['stroke-dashoffset'] = this.dashes.offset || 0;
+        }
+
+        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, domElement);
+          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) {
+            svg[this._mask._renderer.type].render.call(this._mask, domElement);
+            this._renderer.elem.setAttribute('clip-path', 'url(#' + this._mask.id + ')');
+          } else {
+            this._renderer.elem.removeAttribute('clip-path');
+          }
+        }
+
+        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._flagId) {
+          changed.id = this._id;
+        }
+
+        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) {
+            while (this._renderer.elem.lastChild) {
+              this._renderer.elem.removeChild(this._renderer.elem.lastChild);
+            }
+          }
+
+          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._flagId) {
+          changed.id = this._id;
+        }
+
+        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) {
+            while (this._renderer.elem.lastChild) {
+              this._renderer.elem.removeChild(this._renderer.elem.lastChild);
+            }
+          }
+
+          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._flagId) {
+          changed.id = this._id;
+        }
+
+        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 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 / Band-aid
+            switch (this._repeat) {
+              case 'no-repeat':
+                changed.width += 1;
+                changed.height += 1;
+                break;
+            }
+
+            if (this._scale instanceof 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 {
+            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 (Object.keys(changed).length !== 0) {
+
+          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();
+
+      }
+
+    }
+
+  };
+
+  /**
+   * @name Two.SVGRenderer
+   * @class
+   * @extends Two.Events
+   * @param {Object} [parameters] - This object is inherited when constructing a new instance of {@link Two}.
+   * @param {Element} [parameters.domElement] - The `<svg />` to draw to. If none given a new one will be constructed.
+   * @description This class is used by {@link Two} when constructing with `type` of `Two.Types.svg` (the default type). It takes Two.js' scenegraph and renders it to a `<svg />`.
+   */
+  function Renderer$1(params) {
+
+    /**
+     * @name Two.SVGRenderer#domElement
+     * @property {Element} - The `<svg />` associated with the Two.js scene.
+     */
+    this.domElement = params.domElement || svg.createElement('svg');
+
+    /**
+     * @name Two.SVGRenderer#scene
+     * @property {Two.Group} - The root group of the scenegraph.
+     */
+    this.scene = new Group();
+    this.scene.parent = this;
+
+    /**
+     * @name Two.SVGRenderer#defs
+     * @property {SvgDefintionsElement} - The `<defs />` to apply gradients, patterns, and bitmap imagery.
+     */
+    this.defs = svg.createElement('defs');
+    this.domElement.appendChild(this.defs);
+    this.domElement.defs = this.defs;
+    this.domElement.style.overflow = 'hidden';
+
+  }
+
+  _.extend(Renderer$1, {
+
+    /**
+     * @name Two.SVGRenderer.Utils
+     * @property {Object} - A massive object filled with utility functions and properties to render Two.js objects to a `<svg />`.
+     */
+    Utils: svg
+
+  });
+
+  _.extend(Renderer$1.prototype, Events, {
+
+    constructor: Renderer$1,
+
+    /**
+     * @name Two.SVGRenderer#setSize
+     * @function
+     * @param {Number} width - The new width of the renderer.
+     * @param {Number} height - The new height of the renderer.
+     * @description Change the size of the renderer.
+     * @nota-bene Triggers a `Two.Events.resize`.
+     */
+    setSize: function(width, height) {
+
+      this.width = width;
+      this.height = height;
+
+      svg.setAttributes(this.domElement, {
+        width: width,
+        height: height
+      });
+
+      return this.trigger(Events.Types.resize, width, height);
+
+    },
+
+    /**
+     * @name Two.SVGRenderer#render
+     * @function
+     * @description Render the current scene to the `<svg />`.
+     */
+    render: function() {
+
+      svg.group.render.call(this.scene, this.domElement);
+
+      return this;
+
+    }
+
+  });
+
+  // Constants
+
+  var multiplyMatrix = Matrix.Multiply,
+    identity = [1, 0, 0, 0, 1, 0, 0, 0, 1],
+    transformation = new NumArray(9),
+    CanvasUtils = Renderer$2.Utils;
+
+  var webgl = {
+
+    isHidden: /(undefined|none|transparent)/i,
+
+    canvas: (root$1.document ? root$1.document.createElement('canvas') : { getContext: function() {} }),
+
+    alignments: {
+      left: 'start',
+      middle: 'center',
+      right: 'end'
+    },
+
+    matrix: new Matrix(),
+
+    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;
+      },
+
+      render: function(gl, program) {
+
+        if (!this._visible) {
+          return;
+        }
+
+        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 NumArray(9);
+          }
+
+          // Reduce amount of object / array creation / deletion
+          this._matrix.toTransformArray(true, transformation);
+
+          multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix);
+
+          if (!(this._renderer.scale instanceof Vector)) {
+            this._renderer.scale = new Vector();
+          }
+
+          if (this._scale instanceof Vector) {
+            this._renderer.scale.x = this._scale.x;
+            this._renderer.scale.y = this._scale.y;
+          } else {
+            this._renderer.scale.x = this._scale;
+            this._renderer.scale.y = this._scale;
+          }
+
+          if (!(/renderer/i.test(parent._renderer.type))) {
+            this._renderer.scale.x *= parent._renderer.scale.x;
+            this._renderer.scale.y *= parent._renderer.scale.y;
+          }
+
+          if (flagParentMatrix) {
+            this._flagMatrix = true;
+          }
+
+        }
+
+        if (this._mask) {
+
+          // Stencil away everything that isn't rendered by the mask
+          gl.clear(gl.STENCIL_BUFFER_BIT);
+          gl.enable(gl.STENCIL_TEST);
+
+          gl.stencilFunc(gl.ALWAYS, 1, 0);
+          gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
+          // Don't draw the element onto the canvas, only onto the stencil buffer
+          gl.colorMask(false, false, false, false);
+
+          webgl[this._mask._renderer.type].render.call(this._mask, gl, program, this);
+
+          gl.stencilFunc(gl.EQUAL, 1, 0xff);
+          gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
+          gl.colorMask(true, true, true, true);
+
+        }
+
+        this._flagOpacity = parent._flagOpacity || this._flagOpacity;
+
+        this._renderer.opacity = this._opacity
+          * (parent && parent._renderer ? parent._renderer.opacity : 1);
+
+        var i;
+        if (this._flagSubtractions) {
+          for (i = 0; i < this.subtractions.length; i++) {
+            webgl.group.removeChild(this.subtractions[i], gl);
+          }
+        }
+
+        for (i = 0; i < this.children.length; i++) {
+          var child = this.children[i];
+          webgl[child._renderer.type].render.call(child, gl, program);
+        }
+
+        if (this._mask) {
+          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._renderer.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 dashes = elem.dashes;
+        var length = commands.length;
+        var last = length - 1;
+
+        canvas.width = Math.max(Math.ceil(elem._renderer.rect.width * scale.x), 1);
+        canvas.height = Math.max(Math.ceil(elem._renderer.rect.height * scale.y), 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 (typeof fill === 'string') {
+            ctx.fillStyle = fill;
+          } else {
+            webgl[fill._renderer.type].render.call(fill, ctx, elem);
+            ctx.fillStyle = fill._renderer.effect;
+          }
+        }
+        if (stroke) {
+          if (typeof stroke === 'string') {
+            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 (!closed && cap) {
+            ctx.lineCap = cap;
+          }
+        }
+        if (typeof opacity === 'number') {
+          ctx.globalAlpha = opacity;
+        }
+
+        if (dashes && dashes.length > 0) {
+          ctx.lineDashOffset = dashes.offset || 0;
+          ctx.setLineDash(dashes);
+        }
+
+        var d;
+        ctx.save();
+        ctx.scale(scale.x, scale.y);
+
+        ctx.translate(cx, cy);
+
+        ctx.beginPath();
+        for (var i = 0; i < commands.length; i++) {
+
+          var b = commands[i];
+
+          x = b.x;
+          y = b.y;
+
+          switch (b.command) {
+
+            case Commands.close:
+              ctx.closePath();
+              break;
+
+            case Commands.arc:
+
+              var rx = b.rx;
+              var ry = b.ry;
+              var xAxisRotation = b.xAxisRotation;
+              var largeArcFlag = b.largeArcFlag;
+              var sweepFlag = b.sweepFlag;
+
+              prev = closed ? mod(i - 1, length) : Math.max(i - 1, 0);
+              a = commands[prev];
+
+              var ax = a.x;
+              var ay = a.y;
+
+              CanvasUtils.renderSvgArcCommand(ctx, ax, ay, rx, ry, largeArcFlag, sweepFlag, xAxisRotation, x, y);
+              break;
+
+            case Commands.curve:
+
+              prev = closed ? mod(i - 1, length) : Math.max(i - 1, 0);
+              next = closed ? mod(i + 1, length) : Math.min(i + 1, last);
+
+              a = commands[prev];
+              c = commands[next];
+              ar = (a.controls && a.controls.right) || Vector.zero;
+              bl = (b.controls && b.controls.left) || Vector.zero;
+
+              if (a._relative) {
+                vx = ar.x + a.x;
+                vy = ar.y + a.y;
+              } else {
+                vx = ar.x;
+                vy = ar.y;
+              }
+
+              if (b._relative) {
+                ux = bl.x + b.x;
+                uy = bl.y + b.y;
+              } else {
+                ux = bl.x;
+                uy = bl.y;
+              }
+
+              ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
+
+              if (i >= last && closed) {
+
+                c = d;
+
+                br = (b.controls && b.controls.right) || Vector.zero;
+                cl = (c.controls && c.controls.left) || Vector.zero;
+
+                if (b._relative) {
+                  vx = br.x + b.x;
+                  vy = br.y + b.y;
+                } else {
+                  vx = br.x;
+                  vy = br.y;
+                }
+
+                if (c._relative) {
+                  ux = cl.x + c.x;
+                  uy = cl.y + c.y;
+                } else {
+                  ux = cl.x;
+                  uy = cl.y;
+                }
+
+                x = c.x;
+                y = c.y;
+
+                ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
+
+              }
+
+              break;
+
+            case Commands.line:
+              ctx.lineTo(x, y);
+              break;
+
+            case Commands.move:
+              d = b;
+              ctx.moveTo(x, y);
+              break;
+
+          }
+
+        }
+
+        // Loose ends
+
+        if (closed) {
+          ctx.closePath();
+        }
+
+        if (!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 (typeof border === 'number') {
+          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 = forcedParent || this.parent;
+        var flagParentMatrix = parent._matrix.manual || parent._flagMatrix;
+        var flagMatrix = this._matrix.manual || this._flagMatrix;
+        var parentChanged = this._renderer.parent !== parent;
+        var flagTexture = this._flagVertices || this._flagFill
+          || (this._fill instanceof LinearGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagEndPoints))
+          || (this._fill instanceof RadialGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagRadius || this._fill._flagCenter || this._fill._flagFocal))
+          || (this._fill instanceof Texture && (this._fill._flagLoaded && this._fill.loaded || this._fill._flagImage || this._fill._flagVideo || this._fill._flagRepeat || this._fill._flagOffset || this._fill._flagScale))
+          || (this._stroke instanceof LinearGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagEndPoints))
+          || (this._stroke instanceof RadialGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagRadius || this._stroke._flagCenter || this._stroke._flagFocal))
+          || (this._stroke instanceof Texture && (this._stroke._flagLoaded && this._stroke.loaded || this._stroke._flagImage || this._stroke._flagVideo || this._stroke._flagRepeat || 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.dashes && this.dashes.length > 0)
+          || !this._renderer.texture;
+
+        if (flagParentMatrix || flagMatrix || parentChanged) {
+
+          if (!this._renderer.matrix) {
+            this._renderer.matrix = new NumArray(9);
+          }
+
+          // Reduce amount of object / array creation / deletion
+
+          this._matrix.toTransformArray(true, transformation);
+
+          multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix);
+
+          if (!(this._renderer.scale instanceof Vector)) {
+            this._renderer.scale = new Vector();
+          }
+          if (this._scale instanceof Vector) {
+            this._renderer.scale.x = this._scale.x * parent._renderer.scale.x;
+            this._renderer.scale.y = this._scale.y * parent._renderer.scale.y;
+          } else {
+            this._renderer.scale.x = this._scale * parent._renderer.scale.x;
+            this._renderer.scale.y = this._scale * parent._renderer.scale.y;
+          }
+
+          if (parentChanged) {
+            this._renderer.parent = parent;
+          }
+        }
+
+        if (this._mask) {
+
+          // Stencil away everything that isn't rendered by the mask
+          gl.clear(gl.STENCIL_BUFFER_BIT);
+          gl.enable(gl.STENCIL_TEST);
+
+          gl.stencilFunc(gl.ALWAYS, 1, 0);
+          gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
+          // Don't draw the element onto the canvas, only onto the stencil buffer
+          gl.colorMask(false, false, false, false);
+
+          webgl[this._mask._renderer.type].render.call(this._mask, gl, program, this);
+
+          gl.stencilFunc(gl.EQUAL, 1, 0xff);
+          gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
+          gl.colorMask(true, true, true, true);
+
+        }
+
+        if (flagTexture) {
+
+          if (!this._renderer.rect) {
+            this._renderer.rect = {};
+          }
+
+          this._renderer.opacity = this._opacity * parent._renderer.opacity;
+
+          webgl.path.getBoundingClientRect(this._renderer.vertices, this._linewidth, this._renderer.rect);
+
+          webgl.updateTexture.call(webgl, gl, this);
+
+        } else {
+
+          // We still need to update child Two elements on the fill and
+          // stroke properties.
+          if (this._fill && this._fill._update) {
+            this._fill._update();
+          }
+          if (this._stroke && this._stroke._update) {
+            this._stroke._update();
+          }
+
+        }
+
+        if (this._clip && !forcedParent) {
+          return;
+        }
+
+        // Draw Texture
+        gl.bindTexture(gl.TEXTURE_2D, this._renderer.texture);
+
+        // Draw Rect
+        var rect = this._renderer.rect;
+        gl.uniformMatrix3fv(program.matrix, false, this._renderer.matrix);
+        gl.uniform4f(program.rect, rect.left, rect.top, rect.right, rect.bottom);
+        gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+        if (this._mask) {
+          gl.disable(gl.STENCIL_TEST);
+        }
+
+        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;
+        var dashes = elem.dashes;
+        var decoration = elem._decoration;
+
+        canvas.width = Math.max(Math.ceil(elem._renderer.rect.width * scale.x), 1);
+        canvas.height = Math.max(Math.ceil(elem._renderer.rect.height * scale.y), 1);
+
+        var centroid = elem._renderer.rect.centroid;
+        var cx = centroid.x;
+        var cy = centroid.y;
+
+        var a, b, c, d, e, sx, sy, x1, y1, x2, y2;
+        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 (typeof fill === 'string') {
+            ctx.fillStyle = fill;
+          } else {
+            webgl[fill._renderer.type].render.call(fill, ctx, elem);
+            ctx.fillStyle = fill._renderer.effect;
+          }
+        }
+        if (stroke) {
+          if (typeof stroke === 'string') {
+            ctx.strokeStyle = stroke;
+          } else {
+            webgl[stroke._renderer.type].render.call(stroke, ctx, elem);
+            ctx.strokeStyle = stroke._renderer.effect;
+          }
+          if (linewidth) {
+            ctx.lineWidth = linewidth;
+          }
+        }
+        if (typeof opacity === 'number') {
+          ctx.globalAlpha = opacity;
+        }
+        if (dashes && dashes.length > 0) {
+          ctx.lineDashOffset = dashes.offset || 0;
+          ctx.setLineDash(dashes);
+        }
+
+        ctx.save();
+        ctx.scale(scale.x, scale.y);
+        ctx.translate(cx, cy);
+
+        if (!webgl.isHidden.test(fill)) {
+
+          if (fill._renderer && fill._renderer.offset) {
+
+            sx = fill._renderer.scale.x;
+            sy = fill._renderer.scale.y;
+
+            ctx.save();
+            ctx.translate( - fill._renderer.offset.x,
+              - fill._renderer.offset.y);
+            ctx.scale(sx, sy);
+
+            a = elem._size / fill._renderer.scale.y;
+            b = elem._leading / fill._renderer.scale.y;
+            ctx.font = [elem._style, elem._weight, a + 'px/',
+              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, c, d);
+            ctx.restore();
+
+          } else {
+            ctx.fillText(elem.value, 0, 0);
+          }
+
+        }
+
+        if (!webgl.isHidden.test(stroke)) {
+
+          if (stroke._renderer && stroke._renderer.offset) {
+
+            sx = stroke._renderer.scale.x;
+            sy = stroke._renderer.scale.y;
+
+            ctx.save();
+            ctx.translate(- stroke._renderer.offset.x,
+              - stroke._renderer.offset.y);
+            ctx.scale(sx, sy);
+
+            a = elem._size / stroke._renderer.scale.y;
+            b = elem._leading / stroke._renderer.scale.y;
+            ctx.font = [elem._style, elem._weight, a + 'px/',
+              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 = e;
+            ctx.strokeText(elem.value, c, d);
+            ctx.restore();
+
+          } else {
+            ctx.strokeText(elem.value, 0, 0);
+          }
+
+        }
+
+        // Handle text-decoration
+        if (/(underline|strikethrough)/i.test(decoration)) {
+
+          var metrics = ctx.measureText(elem.value);
+
+          switch (decoration) {
+            case 'underline':
+              y1 = metrics.actualBoundingBoxAscent;
+              y2 = metrics.actualBoundingBoxAscent;
+              break;
+            case 'strikethrough':
+              y1 = 0;
+              y2 = 0;
+              break;
+          }
+
+          x1 = - metrics.width / 2;
+          x2 = metrics.width / 2;
+
+          ctx.lineWidth = Math.max(Math.floor(elem._size / 15), 1);
+          ctx.strokeStyle = ctx.fillStyle;
+
+          ctx.beginPath();
+          ctx.moveTo(x1, y1);
+          ctx.lineTo(x2, y2);
+          ctx.stroke();
+
+        }
+
+        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 * 1.25;
+        var height = Math.max(elem._size, elem._leading) * 1.25;
+
+        if (this._linewidth && !webgl.isHidden.test(this._stroke)) {
+          width += this._linewidth * 2;
+          height += this._linewidth * 2;
+        }
+
+        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 = forcedParent || this.parent;
+        var flagParentMatrix = parent._matrix.manual || parent._flagMatrix;
+        var flagMatrix = this._matrix.manual || this._flagMatrix;
+        var parentChanged = this._renderer.parent !== parent;
+        var flagTexture = this._flagVertices || this._flagFill
+          || (this._fill instanceof LinearGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagEndPoints))
+          || (this._fill instanceof RadialGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagRadius || this._fill._flagCenter || this._fill._flagFocal))
+          || (this._fill instanceof Texture && (this._fill._flagLoaded && this._fill.loaded || this._fill._flagImage || this._fill._flagVideo || this._fill._flagRepeat || this._fill._flagOffset || this._fill._flagScale))
+          || (this._stroke instanceof LinearGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagEndPoints))
+          || (this._stroke instanceof RadialGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagRadius || this._stroke._flagCenter || this._stroke._flagFocal))
+          || (this._stroke instanceof Texture && (this._stroke._flagLoaded && this._stroke.loaded || this._stroke._flagImage || this._stroke._flagVideo || this._stroke._flagRepeat || this._stroke._flagOffset || this._fill._flagScale))
+          || 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.dashes && this.dashes.length > 0)
+          || !this._renderer.texture;
+
+        if (flagParentMatrix || flagMatrix || parentChanged) {
+
+          if (!this._renderer.matrix) {
+            this._renderer.matrix = new NumArray(9);
+          }
+
+          // Reduce amount of object / array creation / deletion
+
+          this._matrix.toTransformArray(true, transformation);
+
+          multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix);
+
+          if (!(this._renderer.scale instanceof Vector)) {
+            this._renderer.scale = new Vector();
+          }
+          if (this._scale instanceof Vector) {
+            this._renderer.scale.x = this._scale.x * parent._renderer.scale.x;
+            this._renderer.scale.y = this._scale.y * parent._renderer.scale.y;
+          } else {
+            this._renderer.scale.x = this._scale * parent._renderer.scale.x;
+            this._renderer.scale.y = this._scale * parent._renderer.scale.y;
+          }
+
+          if (parentChanged) {
+            this._renderer.parent = parent;
+          }
+        }
+
+        if (this._mask) {
+
+          // Stencil away everything that isn't rendered by the mask
+          gl.clear(gl.STENCIL_BUFFER_BIT);
+          gl.enable(gl.STENCIL_TEST);
+
+          gl.stencilFunc(gl.ALWAYS, 1, 0);
+          gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
+          // Don't draw the element onto the canvas, only onto the stencil buffer
+          gl.colorMask(false, false, false, false);
+
+          webgl[this._mask._renderer.type].render.call(this._mask, gl, program, this);
+
+          gl.stencilFunc(gl.EQUAL, 1, 0xff);
+          gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
+          gl.colorMask(true, true, true, true);
+
+        }
+
+        if (flagTexture) {
+
+          if (!this._renderer.rect) {
+            this._renderer.rect = {};
+          }
+
+          this._renderer.opacity = this._opacity * parent._renderer.opacity;
+
+          webgl.text.getBoundingClientRect(this, this._renderer.rect);
+
+          webgl.updateTexture.call(webgl, gl, this);
+
+        } else {
+
+          // We still need to update child Two elements on the fill and
+          // stroke properties.
+          if (this._fill && this._fill._update) {
+            this._fill._update();
+          }
+          if (this._stroke && this._stroke._update) {
+            this._stroke._update();
+          }
+
+        }
+
+        if (this._clip && !forcedParent) {
+          return;
+        }
+
+        // Draw Texture
+        gl.bindTexture(gl.TEXTURE_2D, this._renderer.texture);
+
+        // Draw Rect
+        var rect = this._renderer.rect;
+        gl.uniformMatrix3fv(program.matrix, false, this._renderer.matrix);
+        gl.uniform4f(program.rect, rect.left, rect.top, rect.right, rect.bottom);
+        gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+        if (this._mask) {
+          gl.disable(gl.STENCIL_TEST);
+        }
+
+        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;
+
+        if (((this._flagLoaded || this._flagImage || this._flagVideo || this._flagRepeat) && this.loaded)) {
+          this._renderer.effect = ctx.createPattern(image, this._repeat);
+        } else if (!this._renderer.effect) {
+          return this.flagReset();
+        }
+
+        if (this._flagOffset || this._flagLoaded || this._flagScale) {
+
+          if (!(this._renderer.offset instanceof Vector)) {
+            this._renderer.offset = new Vector();
+          }
+
+          this._renderer.offset.x = - this._offset.x;
+          this._renderer.offset.y = - this._offset.y;
+
+          if (image) {
+
+            this._renderer.offset.x += image.width / 2;
+            this._renderer.offset.y += image.height / 2;
+
+            if (this._scale instanceof Vector) {
+              this._renderer.offset.x *= this._scale.x;
+              this._renderer.offset.y *= this._scale.y;
+            } else {
+              this._renderer.offset.x *= this._scale;
+              this._renderer.offset.y *= this._scale;
+            }
+          }
+
+        }
+
+        if (this._flagScale || this._flagLoaded) {
+
+          if (!(this._renderer.scale instanceof Vector)) {
+            this._renderer.scale = new Vector();
+          }
+
+          if (this._scale instanceof Vector) {
+            this._renderer.scale.copy(this._scale);
+          } else {
+            this._renderer.scale.set(this._scale, this._scale);
+          }
+
+        }
+
+        return this.flagReset();
+
+      }
+
+    },
+
+    updateTexture: function(gl, elem) {
+
+      this[elem._renderer.type].updateCanvas.call(webgl, elem);
+
+      if (!elem._renderer.texture) {
+        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);
+
+    },
+
+    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 TwoError('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 TwoError('unable to compile shader ' + shader + ': ' + error);
+        }
+
+        return shader;
+
+      },
+
+      types: {
+        vertex: 'VERTEX_SHADER',
+        fragment: 'FRAGMENT_SHADER'
+      },
+
+      vertex: [
+        'precision mediump float;',
+        'attribute vec2 a_position;',
+        '',
+        'uniform mat3 u_matrix;',
+        'uniform vec2 u_resolution;',
+        'uniform vec4 u_rect;',
+        '',
+        'varying vec2 v_textureCoords;',
+        '',
+        'void main() {',
+        '   vec2 rectCoords = (a_position * (u_rect.zw - u_rect.xy)) + u_rect.xy;',
+        '   vec2 projected = (u_matrix * vec3(rectCoords, 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_position;',
+        '}'
+      ].join('\n'),
+
+      fragment: [
+        'precision mediump float;',
+        '',
+        'uniform sampler2D u_image;',
+        'varying vec2 v_textureCoords;',
+        '',
+        'void main() {',
+        '  vec4 texel = texture2D(u_image, v_textureCoords);',
+        '  if (texel.a == 0.0) {',
+        '    discard;',
+        '  }',
+        '  gl_FragColor = texel;',
+        '}'
+      ].join('\n')
+
+    },
+
+    TextureRegistry: new Registry()
+
+  };
+
+  webgl.ctx = webgl.canvas.getContext('2d');
+
+  /**
+   * @name Two.WebGLRenderer
+   * @class
+   * @extends Two.Events
+   * @param {Object} [parameters] - This object is inherited when constructing a new instance of {@link Two}.
+   * @param {Element} [parameters.domElement] - The `<canvas />` to draw to. If none given a new one will be constructed.
+   * @param {HTMLCanvasElement} [parameters.offscreenElement] - The offscreen two dimensional `<canvas />` to render each element on WebGL texture updates.
+   * @param {Boolean} [parameters.antialias] - Determines whether the canvas should clear render with antialias on.
+   * @description This class is used by {@link Two} when constructing with `type` of `Two.Types.webgl`. It takes Two.js' scenegraph and renders it to a `<canvas />` through the WebGL api.
+   * @see {@link https://www.khronos.org/registry/webgl/specs/latest/1.0/}
+   */
+  function Renderer(params) {
+
+    var gl, vs, fs;
+
+    /**
+     * @name Two.WebGLRenderer#domElement
+     * @property {Element} - The `<canvas />` associated with the Two.js scene.
+     */
+    this.domElement = params.domElement || document.createElement('canvas');
+
+    if (typeof params.offscreenElement !== 'undefined') {
+      webgl.canvas = params.offscreenElement;
+      webgl.ctx = webgl.canvas.getContext('2d');
+    }
+
+    /**
+     * @name Two.WebGLRenderer#scene
+     * @property {Two.Group} - The root group of the scenegraph.
+     */
+    this.scene = new Group();
+    this.scene.parent = this;
+
+    this._renderer = {
+      type: 'renderer',
+      matrix: new NumArray(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(params || {}, {
+      antialias: false,
+      alpha: true,
+      premultipliedAlpha: true,
+      stencil: true,
+      preserveDrawingBuffer: true,
+      overdraw: false
+    });
+
+    /**
+     * @name Two.WebGLRenderer#overdraw
+     * @property {Boolean} - Determines whether the canvas clears the background each draw call.
+     * @default true
+     */
+    this.overdraw = params.overdraw;
+
+    /**
+     * @name Two.WebGLRenderer#ctx
+     * @property {WebGLContext} - Associated two dimensional context to render on the `<canvas />`.
+     */
+    gl = this.ctx = this.domElement.getContext('webgl', params) ||
+      this.domElement.getContext('experimental-webgl', params);
+
+    if (!this.ctx) {
+      throw new TwoError(
+        '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);
+
+    /**
+     * @name Two.WebGLRenderer#program
+     * @property {WebGLProgram} - Associated WebGL program to render all elements from the scenegraph.
+     */
+    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.rect = gl.getUniformLocation(this.program, 'u_rect');
+
+    // Bind the vertex buffer
+    var positionBuffer = gl.createBuffer();
+    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
+    gl.vertexAttribPointer(this.program.position, 2, gl.FLOAT, false, 0, 0);
+    gl.enableVertexAttribArray(this.program.position);
+    gl.bufferData(
+      gl.ARRAY_BUFFER,
+      new NumArray([
+        0, 0,
+        1, 0,
+        0, 1,
+        0, 1,
+        1, 0,
+        1, 1
+      ]),
+      gl.STATIC_DRAW);
+
+    // Setup some initial statements of the gl context
+    gl.enable(gl.BLEND);
+
+    gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
+
+    gl.blendEquation(gl.FUNC_ADD);
+    gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
+  }
+
+  _.extend(Renderer, {
+
+    /**
+     * @name Two.WebGLRenderer.Utils
+     * @property {Object} - A massive object filled with utility functions and properties to render Two.js objects to a `<canvas />` through the WebGL API.
+     */
+    Utils: webgl
+
+  });
+
+  _.extend(Renderer.prototype, Events, {
+
+    constructor: Renderer,
+
+    /**
+     * @name Two.WebGLRenderer#setSize
+     * @function
+     * @fires resize
+     * @param {Number} width - The new width of the renderer.
+     * @param {Number} height - The new height of the renderer.
+     * @param {Number} [ratio] - The new pixel ratio (pixel density) of the renderer. Defaults to calculate the pixel density of the user's screen.
+     * @description Change the size of the renderer.
+     */
+    setSize: function(width, height, ratio) {
+
+      this.width = width;
+      this.height = height;
+
+      this.ratio = typeof ratio === 'undefined' ? getRatio(this.ctx) : ratio;
+
+      this.domElement.width = width * this.ratio;
+      this.domElement.height = height * this.ratio;
+
+      if (_.isObject(this.domElement.style)) {
+        _.extend(this.domElement.style, {
+          width: width + 'px',
+          height: height + 'px'
+        });
+      }
+
+      // 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 * this.ratio, height * this.ratio);
+
+      var resolutionLocation = this.ctx.getUniformLocation(
+        this.program, 'u_resolution');
+      this.ctx.uniform2f(resolutionLocation, width * this.ratio, height * this.ratio);
+
+      return this.trigger(Events.Types.resize, width, height, ratio);
+
+    },
+
+    /**
+     * @name Two.WebGLRenderer#render
+     * @function
+     * @description Render the current scene to the `<canvas />`.
+     */
+    render: function() {
+
+      var gl = this.ctx;
+
+      if (!this.overdraw) {
+        gl.clear(gl.COLOR_BUFFER_BIT);
+      }
+
+      webgl.group.render.call(this.scene, gl, this.program);
+      this._flagMatrix = false;
+
+      return this;
+
+    }
+
+  });
+
+  // Utils
+
+  /**
+   * @name Two
+   * @class
+   * @global
+   * @param {Object} [options]
+   * @param {Boolean} [options.fullscreen=false] - Set to `true` to automatically make the stage adapt to the width and height of the parent document. This parameter overrides `width` and `height` parameters if set to `true`. This overrides `options.fitted` as well.
+   * @param {Boolean} [options.fitted=false] = Set to `true` to automatically make the stage adapt to the width and height of the parent element. This parameter overrides `width` and `height` parameters if set to `true`.
+   * @param {Number} [options.width=640] - The width of the stage on construction. This can be set at a later time.
+   * @param {Number} [options.height=480] - The height of the stage on construction. This can be set at a later time.
+   * @param {String} [options.type=Two.Types.svg] - The type of renderer to setup drawing with. See {@link Two.Types} for available options.
+   * @param {Boolean} [options.autostart=false] - Set to `true` to add the instance to draw on `requestAnimationFrame`. This is a convenient substitute for {@link Two#play}.
+   * @param {Element} [options.domElement] - The canvas or SVG element to draw into. This overrides the `options.type` argument.
+   * @description The entrypoint for Two.js. Instantiate a `new Two` in order to setup a scene to render to. `Two` is also the publicly accessible namespace that all other sub-classes, functions, and utilities attach to.
+   */
+  function Two(options) {
+
+    // Determine what Renderer to use and setup a scene.
+
+    var params = _.defaults(options || {}, {
+      fullscreen: false,
+      fitted: false,
+      width: 640,
+      height: 480,
+      type: Two.Types.svg,
+      autostart: false
+    });
+
+    _.each(params, function(v, k) {
+      if (/fullscreen/i.test(k) || /autostart/i.test(k)) {
+        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);
+    this.setPlaying(params.autostart);
+    this.frameCount = 0;
+
+    /**
+     * @name Two#fit
+     * @function
+     * @description If `options.fullscreen` or `options.fitted` in construction create this function. It sets the `width` and `height` of the instance to its respective parent `window` or `element` depending on the `options` passed.
+     */
+    if (params.fullscreen) {
+
+      this.fit = fitToWindow.bind(this);
+      this.fit.domElement = window;
+      this.fit.attached = true;
+      _.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(this.fit.domElement, 'resize', this.fit);
+      this.fit();
+
+    } else if (params.fitted) {
+
+      this.fit = fitToParent.bind(this);
+      _.extend(this.renderer.domElement.style, {
+        display: 'block'
+      });
+
+    } else if (!_.isElement(params.domElement)) {
+
+      this.renderer.setSize(params.width, params.height, this.ratio);
+      this.width = params.width;
+      this.height = params.height;
+
+    }
+
+    this.renderer.bind(Events.Types.resize, updateDimensions.bind(this));
+    this.scene = this.renderer.scene;
+
+    Two.Instances.push(this);
+    if (params.autostart) {
+      raf.init();
+    }
+
+  }
+
+  _.extend(Two, Constants);
+
+  _.extend(Two.prototype, Events, {
+
+    constructor: Two,
+
+    /**
+     * @name Two#type
+     * @property {String} - A string representing which type of renderer the instance has instantiated.
+     */
+    type: '',
+
+    /**
+     * @name Two#renderer
+     * @property {(Two.SVGRenderer|Two.CanvasRenderer|Two.WebGLRenderer)} - The instantiated rendering class for the instance. For a list of possible rendering types check out Two.Types.
+     */
+    renderer: null,
+
+    /**
+     * @name Two#scene
+     * @property {Two.Group} - The base level {@link Two.Group} which houses all objects for the instance. Because it is a {@link Two.Group} transformations can be applied to it that will affect all objects in the instance. This is handy as a makeshift inverted camera.
+     */
+    scene: null,
+
+    /**
+     * @name Two#width
+     * @property {Number} - The width of the instance's dom element.
+     */
+    width: 0,
+
+    /**
+     * @name Two#height
+     * @property {Number} - The height of the instance's dom element.
+     */
+    height: 0,
+
+    /**
+     * @name Two#frameCount
+     * @property {Number} - An integer representing how many frames have elapsed.
+     */
+    frameCount: 0,
+
+    /**
+     * @name Two#timeDelta
+     * @property {Number} - A number representing how much time has elapsed since the last frame in milliseconds.
+     */
+    timeDelta: 0,
+
+    /**
+     * @name Two#playing
+     * @property {Boolean} - A boolean representing whether or not the instance is being updated through the automatic `requestAnimationFrame`.
+     */
+    playing: false,
+
+    /**
+     * @name Two#appendTo
+     * @function
+     * @param {Element} elem - The DOM element to append the Two.js stage to.
+     * @description Shorthand method to append your instance of Two.js to the `document`.
+     */
+    appendTo: function(elem) {
+
+      elem.appendChild(this.renderer.domElement);
+
+      if (this.fit) {
+        if (this.fit.domElement !== window) {
+          this.fit.domElement = elem;
+          this.fit.attached = false;
+        }
+        this.update();
+      }
+
+      return this;
+
+    },
+
+    /**
+     * @name Two#play
+     * @function
+     * @fires Two.Events.Types.play event
+     * @description Call to start an internal animation loop.
+     * @nota-bene This function initiates a `requestAnimationFrame` loop.
+     */
+    play: function() {
+
+      this.playing = true;
+      raf.init();
+      return this.trigger(Events.Types.play);
+
+    },
+
+    /**
+     * @name Two#pause
+     * @function
+     * @fires Two.Events.Types.pause event
+     * @description Call to stop the internal animation loop for a specific instance of Two.js.
+     */
+    pause: function() {
+
+      this.playing = false;
+      return this.trigger(Events.Types.pause);
+
+    },
+
+    setPlaying: function(p) {
+      this.playing = p;
+    },
+
+    /**
+     * @name Two#release
+     * @function
+     * @param {Object} obj
+     * @returns {Object} The object passed for event deallocation.
+     * @description Release an arbitrary class' events from the Two.js corpus and recurse through its children and or vertices.
+     */
+    release: function(obj) {
+
+      var i, v, child;
+
+      if (!_.isObject(obj)) {
+        return;
+      }
+
+      if (typeof obj.unbind === 'function') {
+        obj.unbind();
+      }
+
+      if (obj.vertices) {
+        if (typeof obj.vertices.unbind === 'function') {
+          obj.vertices.unbind();
+        }
+        for (i = 0; i < obj.vertices.length; i++) {
+          v = obj.vertices[i];
+          if (typeof v.unbind === 'function') {
+            v.unbind();
+          }
+        }
+      }
+
+      if (obj.children) {
+        for (i = 0; i < obj.children.length; i++) {
+          child = obj.children[i];
+          this.release(child);
+        }
+      }
+
+      return obj;
+
+    },
+
+    /**
+     * @name Two#update
+     * @function
+     * @fires Two.Events.Types.update event
+     * @description Update positions and calculations in one pass before rendering. Then render to the canvas.
+     * @nota-bene This function is called automatically if using {@link Two#play} or the `autostart` parameter in construction.
+     */
+    update: function() {
+
+      var animated = !!this._lastFrame;
+      var now = _.performance.now();
+
+      if (animated) {
+        this.timeDelta = parseFloat((now - this._lastFrame).toFixed(3));
+      }
+      this._lastFrame = now;
+
+      if (this.fit && this.fit.domElement && !this.fit.attached) {
+          dom.bind(this.fit.domElement, 'resize', this.fit);
+          this.fit.attached = true;
+          this.fit();
+      }
+
+      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(Events.Types.update, this.frameCount, this.timeDelta);
+
+      return this.render();
+
+    },
+
+    /**
+     * @name Two#render
+     * @function
+     * @fires render
+     * @description Render all drawable and visible objects of the scene.
+     */
+    render: function() {
+
+      this.renderer.render();
+      return this.trigger(Events.Types.render, this.frameCount++);
+
+    },
+
+    // Convenience Methods
+
+    /**
+     * @name Two#add
+     * @function
+     * @param {(Two.Shape[]|...Two.Shape)} [objects] - An array of Two.js objects. Alternatively can add objects as individual arguments.
+     * @description A shorthand method to add specific Two.js objects to the scene.
+     */
+    add: function(o) {
+
+      var objects = o;
+      if (!(objects instanceof Array)) {
+        objects = Array.prototype.slice.call(arguments);
+      }
+
+      this.scene.add(objects);
+      return this;
+
+    },
+
+    /**
+     * @name Two#remove
+     * @function
+     * @param {(Two.Shape[]|...Two.Shape)} [objects] - An array of Two.js objects.
+     * @description A shorthand method to remove specific Two.js objects from the scene.
+     */
+    remove: function(o) {
+
+      var objects = o;
+      if (!(objects instanceof Array)) {
+        objects = Array.prototype.slice.call(arguments);
+      }
+
+      this.scene.remove(objects);
+
+      return this;
+
+    },
+
+    /**
+     * @name Two#clear
+     * @function
+     * @description Removes all objects from the instance's scene. If you intend to have the browser garbage collect this, don't forget to delete the references in your application as well.
+     */
+    clear: function() {
+
+      this.scene.remove(this.scene.children);
+      return this;
+
+    },
+
+    /**
+     * @name Two#makeLine
+     * @function
+     * @param {Number} x1
+     * @param {Number} y1
+     * @param {Number} x2
+     * @param {Number} y2
+     * @returns {Two.Line}
+     * @description Creates a Two.js line and adds it to the scene.
+     */
+    makeLine: function(x1, y1, x2, y2) {
+
+      var line = new Line(x1, y1, x2, y2);
+      this.scene.add(line);
+
+      return line;
+
+    },
+
+    /**
+     * @name Two#makeArrow
+     * @function
+     * @param {Number} x1
+     * @param {Number} y1
+     * @param {Number} x2
+     * @param {Number} y2
+     * @returns {Two.Path}
+     * @description Creates a Two.js arrow and adds it to the scene.
+     */
+    makeArrow: function(x1, y1, x2, y2, size) {
+
+      var headlen = typeof size === 'number' ? size : 10;
+
+      var angle = Math.atan2(y2 - y1, x2 - x1);
+
+      var vertices = [
+
+        new Anchor(x1, y1, undefined, undefined, undefined, undefined, Commands.move),
+        new Anchor(x2, y2, undefined, undefined, undefined, undefined, Commands.line),
+        new Anchor(
+          x2 - headlen * Math.cos(angle - Math.PI / 4),
+          y2 - headlen * Math.sin(angle - Math.PI / 4),
+          undefined, undefined, undefined, undefined, Commands.line
+        ),
+
+        new Anchor(x2, y2, undefined, undefined, undefined, undefined, Commands.move),
+        new Anchor(
+          x2 - headlen * Math.cos(angle + Math.PI / 4),
+          y2 - headlen * Math.sin(angle + Math.PI / 4),
+          undefined, undefined, undefined, undefined, Commands.line
+        )
+
+      ];
+
+      var path = new Path(vertices, false, false, true);
+      path.noFill();
+      path.cap = 'round';
+      path.join = 'round';
+
+      this.scene.add(path);
+
+      return path;
+    },
+
+    /**
+     * @name Two#makeRectangle
+     * @function
+     * @param {Number} x
+     * @param {Number} y
+     * @param {Number} width
+     * @param {Number} height
+     * @returns {Two.Rectangle}
+     * @description Creates a Two.js rectangle and adds it to the scene.
+     */
+    makeRectangle: function(x, y, width, height) {
+
+      var rect = new Rectangle(x, y, width, height);
+      this.scene.add(rect);
+
+      return rect;
+
+    },
+
+    /**
+     * @name Two#makeRoundedRectangle
+     * @function
+     * @param {Number} x
+     * @param {Number} y
+     * @param {Number} width
+     * @param {Number} height
+     * @param {Number} sides
+     * @returns {Two.Rectangle}
+     * @description Creates a Two.js rounded rectangle and adds it to the scene.
+     */
+    makeRoundedRectangle: function(x, y, width, height, sides) {
+
+      var rect = new RoundedRectangle(x, y, width, height, sides);
+      this.scene.add(rect);
+
+      return rect;
+
+    },
+
+    /**
+     * @name Two#makeCircle
+     * @function
+     * @param {Number} x
+     * @param {Number} y
+     * @param {Number} radius
+     * @param {Number} [resolution=4]
+     * @returns {Two.Circle}
+     * @description Creates a Two.js circle and adds it to the scene.
+     */
+    makeCircle: function(x, y, radius, resolution) {
+
+      var circle = new Circle(x, y, radius, resolution);
+      this.scene.add(circle);
+
+      return circle;
+
+    },
+
+    /**
+     * @name Two#makeEllipse
+     * @function
+     * @param {Number} x
+     * @param {Number} y
+     * @param {Number} rx
+     * @param {Number} ry
+     * @param {Number} [resolution=4]
+     * @returns {Two.Ellipse}
+     * @description Creates a Two.js ellipse and adds it to the scene.
+     */
+    makeEllipse: function(x, y, rx, ry, resolution) {
+
+      var ellipse = new Ellipse(x, y, rx, ry, resolution);
+      this.scene.add(ellipse);
+
+      return ellipse;
+
+    },
+
+    /**
+     * @name Two#makeStar
+     * @function
+     * @param {Number} x
+     * @param {Number} y
+     * @param {Number} outerRadius
+     * @param {Number} innerRadius
+     * @param {Number} sides
+     * @returns {Two.Star}
+     * @description Creates a Two.js star and adds it to the scene.
+     */
+    makeStar: function(ox, oy, outerRadius, innerRadius, sides) {
+
+      var star = new Star(ox, oy, outerRadius, innerRadius, sides);
+      this.scene.add(star);
+
+      return star;
+
+    },
+
+    /**
+     * @name Two#makeCurve
+     * @function
+     * @param {Two.Anchor[]} [points] - An array of {@link Two.Anchor} points.
+     * @param {...Number} - Alternatively you can pass alternating `x` / `y` coordinate values as individual arguments. These will be combined into {@link Two.Anchor}s for use in the path.
+     * @returns {Two.Path} - Where `path.curved` is set to `true`.
+     * @description Creates a Two.js path that is curved and adds it to the scene.
+     * @nota-bene In either case of passing an array or passing numbered arguments the last argument is an optional `Boolean` that defines whether the path should be open or closed.
+     */
+    makeCurve: function(p) {
+
+      var l = arguments.length, points = p;
+      if (!Array.isArray(p)) {
+        points = [];
+        for (var i = 0; i < l; i+=2) {
+          var x = arguments[i];
+          if (typeof x !== 'number') {
+            break;
+          }
+          var y = arguments[i + 1];
+          points.push(new Anchor(x, y));
+        }
+      }
+
+      var last = arguments[l - 1];
+      var curve = new Path(points, !(typeof last === 'boolean' ? 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;
+
+    },
+
+    /**
+     * @name Two#makePolygon
+     * @function
+     * @param {Number} x
+     * @param {Number} y
+     * @param {Number} radius
+     * @param {Number} sides
+     * @returns {Two.Polygon}
+     * @description Creates a Two.js polygon and adds it to the scene.
+     */
+    makePolygon: function(x, y, radius, sides) {
+
+      var poly = new Polygon(x, y, radius, sides);
+      this.scene.add(poly);
+
+      return poly;
+
+    },
+
+    /**
+     * @name Two#makeArcSegment
+     * @function
+     * @param {Number} x
+     * @param {Number} y
+     * @param {Number} innerRadius
+     * @param {Number} outerRadius
+     * @param {Number} startAngle
+     * @param {Number} endAngle
+     * @param {Number} [resolution=Two.Resolution] - The number of vertices that should comprise the arc segment.
+     */
+    makeArcSegment: function(ox, oy, ir, or, sa, ea, res) {
+      var arcSegment = new ArcSegment(ox, oy, ir, or, sa, ea, res);
+      this.scene.add(arcSegment);
+      return arcSegment;
+    },
+
+    /**
+     * @name Two#makePath
+     * @function
+     * @param {Two.Anchor[]} [points] - An array of {@link Two.Anchor} points.
+     * @param {...Number} - Alternatively you can pass alternating `x` / `y` coordinate values as individual arguments. These will be combined into {@link Two.Anchor}s for use in the path.
+     * @returns {Two.Path}
+     * @description Creates a Two.js path and adds it to the scene.
+     * @nota-bene In either case of passing an array or passing numbered arguments the last argument is an optional `Boolean` that defines whether the path should be open or closed.
+     */
+    makePath: function(p) {
+
+      var l = arguments.length, points = p;
+      if (!Array.isArray(p)) {
+        points = [];
+        for (var i = 0; i < l; i+=2) {
+          var x = arguments[i];
+          if (typeof x !== 'number') {
+            break;
+          }
+          var y = arguments[i + 1];
+          points.push(new Anchor(x, y));
+        }
+      }
+
+      var last = arguments[l - 1];
+      var path = new Path(points, !(typeof last === 'boolean' ? last : undefined));
+      var rect = path.getBoundingClientRect();
+      if (typeof rect.top === 'number'   && typeof rect.left === 'number' &&
+          typeof rect.right === 'number' && typeof rect.bottom === 'number') {
+        path.center().translation
+          .set(rect.left + rect.width / 2, rect.top + rect.height / 2);
+      }
+
+      this.scene.add(path);
+
+      return path;
+
+    },
+
+    /**
+     * @name Two#makeText
+     * @function
+     * @param {String} message
+     * @param {Number} x
+     * @param {Number} y
+     * @param {Object} [styles] - An object to describe any of the {@link Two.Text.Properties} including `fill`, `stroke`, `linewidth`, `family`, `alignment`, `leading`, `opacity`, etc..
+     * @returns {Two.Text}
+     * @description Creates a Two.js text object and adds it to the scene.
+     */
+    makeText: function(message, x, y, styles) {
+      var text = new Text(message, x, y, styles);
+      this.add(text);
+      return text;
+    },
+
+    /**
+     * @name Two#makeLinearGradient
+     * @function
+     * @param {Number} x1
+     * @param {Number} y1
+     * @param {Number} x2
+     * @param {Number} y2
+     * @param {...Two.Stop} stops - Any number of color stops sometimes reffered to as ramp stops. If none are supplied then the default black-to-white two stop gradient is applied.
+     * @returns {Two.LinearGradient}
+     * @description Creates a Two.js linear gradient and ads it to the scene. In the case of an effect it's added to an invisible "definitions" group.
+     */
+    makeLinearGradient: function(x1, y1, x2, y2 /* stops */) {
+
+      var stops = Array.prototype.slice.call(arguments, 4);
+      var gradient = new LinearGradient(x1, y1, x2, y2, stops);
+
+      this.add(gradient);
+
+      return gradient;
+
+    },
+
+    /**
+     * @name Two#makeRadialGradient
+     * @function
+     * @param {Number} x1
+     * @param {Number} y1
+     * @param {Number} radius
+     * @param {...Two.Stop} stops - Any number of color stops sometimes reffered to as ramp stops. If none are supplied then the default black-to-white two stop gradient is applied.
+     * @returns {Two.RadialGradient}
+     * @description Creates a Two.js linear-gradient object and ads it to the scene. In the case of an effect it's added to an invisible "definitions" group.
+     */
+    makeRadialGradient: function(x1, y1, r /* stops */) {
+
+      var stops = Array.prototype.slice.call(arguments, 3);
+      var gradient = new RadialGradient(x1, y1, r, stops);
+
+      this.add(gradient);
+
+      return gradient;
+
+    },
+
+    /**
+     * @name Two#makeSprite
+     * @function
+     * @param {(String|Two.Texture)} pathOrTexture - The URL path to an image or an already created {@link Two.Texture}.
+     * @param {Number} x
+     * @param {Number} y
+     * @param {Number} [columns=1]
+     * @param {Number} [rows=1]
+     * @param {Number} [frameRate=0]
+     * @param {Boolean} [autostart=false]
+     * @returns {Two.Sprite}
+     * @description Creates a Two.js sprite object and adds it to the scene. Sprites can be used for still images as well as animations.
+     */
+    makeSprite: function(path, x, y, cols, rows, frameRate, autostart) {
+
+      var sprite = new Sprite(path, x, y, cols, rows, frameRate);
+      if (autostart) {
+        sprite.play();
+      }
+      this.add(sprite);
+
+      return sprite;
+
+    },
+
+    /**
+     * @name Two#makeImageSequence
+     * @function
+     * @param {(String[]|Two.Texture[])} pathsOrTextures - An array of paths or of {@link Two.Textures}.
+     * @param {Number} x
+     * @param {Number} y
+     * @param {Number} [frameRate=0]
+     * @param {Boolean} [autostart=false]
+     * @returns {Two.ImageSequence}
+     * @description Creates a Two.js image sequence object and adds it to the scene.
+     */
+    makeImageSequence: function(paths, x, y, frameRate, autostart) {
+
+      var imageSequence = new ImageSequence(paths, x, y, frameRate);
+      if (autostart) {
+        imageSequence.play();
+      }
+      this.add(imageSequence);
+
+      return imageSequence;
+
+    },
+
+    /**
+     * @name Two#makeTexture
+     * @function
+     * @param {(String|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement)} [pathOrSource] - The URL path to an image or a DOM image-like element.
+     * @param {Function} [callback] - Function to be invoked when the image is loaded.
+     * @returns {Two.Texture}
+     * @description Creates a Two.js texture object.
+     */
+    makeTexture: function(path, callback) {
+
+      var texture = new Texture(path, callback);
+      return texture;
+
+    },
+
+    /**
+     * @name Two#makeGroup
+     * @function
+     * @param {(Two.Shape[]|...Two.Shape)} [objects] - Two.js objects to be added to the group in the form of an array or as individual arguments.
+     * @returns {Two.Group}
+     * @description Creates a Two.js group object and adds it to the scene.
+     */
+    makeGroup: function(o) {
+
+      var objects = o;
+      if (!(objects instanceof Array)) {
+        objects = Array.prototype.slice.call(arguments);
+      }
+
+      var group = new Group();
+      this.scene.add(group);
+      group.add(objects);
+
+      return group;
+
+    },
+
+    /**
+     * @name Two#interpret
+     * @function
+     * @param {SVGElement} SVGElement - The SVG node to be parsed.
+     * @param {Boolean} shallow - Don't create a top-most group but append all content directly.
+     * @param {Boolean} add – Automatically add the reconstructed SVG node to scene.
+     * @returns {Two.Group}
+     * @description 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.
+     */
+    interpret: function(SVGElement, shallow, add) {
+
+      var tag = SVGElement.tagName.toLowerCase();
+
+      add = (typeof add !== 'undefined') ? add : true;
+
+      if (!(tag in read)) {
+        return null;
+      }
+
+      var node = read[tag].call(this, SVGElement);
+
+      if (add) {
+        this.add(shallow && node instanceof Group ? node.children : node);
+      } else if (node.parent) {
+        // Remove `g` tags that have been added to scenegraph / DOM
+        // in order to be compatible with `getById` methods.
+        node.remove();
+      }
+
+      return node;
+
+    },
+
+    /**
+     * @name Two#load
+     * @function
+     * @param {String|SVGElement} pathOrSVGContent - The URL path of an SVG file or an SVG document as text.
+     * @param {Function} callback - Function to call once loading has completed.
+     * @returns {Two.Group}
+     * @description Load an SVG file or SVG text and interpret it into Two.js legible objects.
+     */
+    load: function(text, callback) {
+
+      var group = new Group();
+      var elem, i, j, child;
+
+      var attach = (function(data) {
+
+        dom.temp.innerHTML = data;
+
+        for (i = 0; i < dom.temp.children.length; i++) {
+          elem = dom.temp.children[i];
+          if (/svg/i.test(elem.nodeName)) {
+            child = this.interpret(elem);
+            // Two.Utils.applySvgViewBox.call(this, group, elem.getAttribute('viewBox'));
+            for (j = 0; j < child.children.length; j++) {
+              group.add(child.children[j]);
+            }
+          } else {
+            group.add(this.interpret(elem));
+          }
+        }
+
+        if (typeof callback === 'function') {
+          var svg = dom.temp.children.length <= 1
+            ? dom.temp.children[0] : dom.temp.children;
+          callback(group, svg);
+        }
+
+      }).bind(this);
+
+      if (/.*\.svg/ig.test(text)) {
+
+        xhr(text, attach);
+
+        return group;
+
+      }
+
+      attach(text);
+
+      return group;
+
+    }
+
+  });
+
+  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);
+
+  }
+
+  function fitToParent() {
+
+    var parent = this.renderer.domElement.parentElement;
+    if (!parent) {
+      console.warn('Two.js: Attempting to fit to parent, but no parent found.');
+      return;
+    }
+    var wr = parent.getBoundingClientRect();
+
+    var width = this.width = wr.width;
+    var height = this.height = wr.height;
+
+    this.renderer.setSize(width, height, this.ratio);
+
+  }
+
+  function updateDimensions(width, height) {
+    this.width = width;
+    this.height = height;
+    this.trigger(Events.Types.resize, width, height);
+  }
+
+  // Request Animation Frame
+
+  var raf = dom.getRequestAnimationFrame();
+
+  function loop() {
+
+    for (var i = 0; i < Two.Instances.length; i++) {
+      var t = Two.Instances[i];
+      if (t.playing) {
+        t.update();
+      }
+    }
+
+    Two.nextFrameID = raf(loop);
+
+  }
+
+  raf.init = function() {
+    loop();
+    raf.init = function() {};
+  };
+
+  _.extend(Two, {
+    Anchor: Anchor,
+    Collection: Collection,
+    Events: Events,
+    Group: Group,
+    Matrix: Matrix,
+    Path: Path,
+    Registry: Registry,
+    Shape: Shape,
+    Text: Text,
+    Vector: Vector,
+
+    Gradient: Gradient,
+    ImageSequence: ImageSequence,
+    LinearGradient: LinearGradient,
+    RadialGradient: RadialGradient,
+    Sprite: Sprite,
+    Stop: Stop,
+    Texture: Texture,
+
+    ArcSegment: ArcSegment,
+    Circle: Circle,
+    Ellipse: Ellipse,
+    Line: Line,
+    Polygon: Polygon,
+    Rectangle: Rectangle,
+    RoundedRectangle: RoundedRectangle,
+    Star: Star,
+
+    CanvasRenderer: Renderer$2,
+    SVGRenderer: Renderer$1,
+    WebGLRenderer: Renderer,
+
+    Commands: Commands,
+
+    /**
+     * @name Two.Utils
+     * @property {Object} - A massive object filled with utility functions and properties.
+     */
+    Utils: _.extend({
+
+      Error: TwoError,
+      getRatio: getRatio,
+      defineGetterSetter: defineGetterSetter,
+      read: read,
+      xhr: xhr
+
+    }, _, CanvasShim, Curves, math)
+
+  });
+
+  return Two;
+
+})));

File diff ditekan karena terlalu besar
+ 23 - 0
public/drivers/view/twojs/two.min.js


+ 16403 - 0
public/drivers/view/twojs/two.module.js

@@ -0,0 +1,16403 @@
+/*
+MIT License
+
+Copyright (c) 2012 - 2021 jonobr1 / http://jonobr1.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+/**
+ * @name Two.Commands
+ * @property {Object} - Map of possible path commands. Taken from the SVG specification.
+ */
+var Commands = {
+  move: 'M',
+  line: 'L',
+  curve: 'C',
+  arc: 'A',
+  close: 'Z'
+};
+
+var root;
+if (typeof window !== 'undefined') {
+  root = window;
+} else if (typeof global !== 'undefined') {
+  root = global;
+} else if (typeof self !== 'undefined') {
+  root = self;
+}
+
+var root$1 = root;
+
+var Matrix$1;
+
+/**
+ * @name Two.Utils.decomposeMatrix
+ * @function
+ * @param {Two.Matrix} matrix - The matrix to decompose.
+ * @returns {Object} An object containing relevant skew values.
+ * @description Decompose a 2D 3x3 Matrix to find the skew.
+ */
+var decomposeMatrix = function(matrix) {
+
+  // TODO: Include skewX, skewY
+  // https://math.stackexchange.com/questions/237369/given-this-transformation-matrix-how-do-i-decompose-it-into-translation-rotati/417813
+  // https://stackoverflow.com/questions/45159314/decompose-2d-transformation-matrix
+
+  return {
+      translateX: matrix.e,
+      translateY: matrix.f,
+      scaleX: Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b),
+      scaleY: Math.sqrt(matrix.c * matrix.c + matrix.d * matrix.d),
+      rotation: 180 * Math.atan2(matrix.b, matrix.a) / Math.PI
+  };
+
+};
+
+var setMatrix = function(M) {
+  Matrix$1 = M;
+};
+
+/**
+ * @name Two.Utils.getComputedMatrix
+ * @function
+ * @param {Two.Shape} object - The Two.js object that has a matrix property to calculate from.
+ * @param {Two.Matrix} [matrix] - The matrix to apply calculated transformations to if available.
+ * @returns {Two.Matrix} The computed matrix of a nested object. If no `matrix` was passed in arguments then a `new Two.Matrix` is returned.
+ * @description Method to get the world space transformation of a given object in a Two.js scene.
+ */
+var getComputedMatrix = function(object, matrix) {
+
+  matrix = (matrix && matrix.identity()) || new Matrix$1();
+  var parent = object, matrices = [];
+
+  while (parent && parent._matrix) {
+    matrices.push(parent._matrix);
+    parent = parent.parent;
+  }
+
+  matrices.reverse();
+
+  for (var i = 0; i < matrices.length; i++) {
+
+    var m = matrices[i];
+    var e = m.elements;
+    matrix.multiply(
+      e[0], e[1], e[2], e[3], e[4], e[5], e[6], e[7], e[8], e[9]);
+
+  }
+
+  return matrix;
+
+};
+
+/**
+ * @name Two.Utils.lerp
+ * @function
+ * @param {Number} a - Start value.
+ * @param {Number} b - End value.
+ * @param {Number} t - Zero-to-one value describing percentage between a and b.
+ * @returns {Number}
+ * @description Linear interpolation between two values `a` and `b` by an amount `t`.
+ */
+var lerp = function(a, b, t) {
+  return t * (b - a) + a;
+};
+
+/**
+ * @name Two.Utils.mod
+ * @function
+ * @param {Number} v - The value to modulo
+ * @param {Number} l - The value to modulo by
+ * @returns {Number}
+ * @description Modulo with added functionality to handle negative values in a positive manner.
+ */
+var mod = function(v, l) {
+
+  while (v < 0) {
+    v += l;
+  }
+
+  return v % l;
+
+};
+
+var NumArray = root$1.Float32Array || Array;
+
+/**
+* @name Two.Utils.toFixed
+* @function
+* @param {Number} v - Any float
+* @returns {Number} That float trimmed to the third decimal place.
+* @description A pretty fast toFixed(3) alternative.
+* @see {@link http://jsperf.com/parsefloat-tofixed-vs-math-round/18}
+*/
+var toFixed = function(v) {
+  return Math.floor(v * 1000000) / 1000000;
+};
+
+var math = /*#__PURE__*/Object.freeze({
+  __proto__: null,
+  decomposeMatrix: decomposeMatrix,
+  getComputedMatrix: getComputedMatrix,
+  setMatrix: setMatrix,
+  lerp: lerp,
+  mod: mod,
+  NumArray: NumArray,
+  toFixed: toFixed
+});
+
+var slice = Array.prototype.slice;
+
+var isArrayLike = function(collection) {
+  if (collection === null || collection === undefined) return false;
+  var length = collection.length;
+  // Arrays cannot hold more than 2^32 - 1 items
+  return (typeof length == 'number' && length >= 0 && length < 4294967296);
+};
+
+var _ = {
+  isNaN: function(obj) {
+    return typeof obj === 'number' && obj !== +obj;
+  },
+  isElement: function(obj) {
+    return !!(obj && obj.nodeType === 1);
+  },
+  isObject: function(obj) {
+    var type = typeof obj;
+    return type === 'function' || type === 'object' && !!obj;
+  },
+  extend: function(base) {
+    var sources = slice.call(arguments, 1);
+    for (var i = 0; i < sources.length; i++) {
+      var obj = sources[i];
+      for (var k in obj) {
+        base[k] = obj[k];
+      }
+    }
+    return base;
+  },
+  defaults: function(base) {
+    var sources = slice.call(arguments, 1);
+    for (var i = 0; i < sources.length; i++) {
+      var obj = sources[i];
+      for (var k in obj) {
+        if (base[k] === void 0) {
+        base[k] = obj[k];
+        }
+      }
+    }
+    return base;
+  },
+  each: function(obj, iteratee, context) {
+    var ctx = context || this;
+    var keys = !isArrayLike(obj) && Object.keys(obj);
+    var length = (keys || obj).length;
+    for (var i = 0; i < length; i++) {
+      var k = keys ? keys[i] : i;
+      iteratee.call(ctx, obj[k], k, obj);
+    }
+    return obj;
+  },
+  /**
+   * @name Two.Utils.performance
+   * @property {Date} - A special `Date` like object to get the current millis of the session. Used internally to calculate time between frames.
+   * e.g: `Utils.performance.now() // milliseconds since epoch`
+   */
+  performance: ((root$1.performance && root$1.performance.now) ? root$1.performance : Date),
+};
+
+/**
+ * @name Two.Events
+ * @class
+ * @description Object inherited by many Two.js objects in order to facilitate custom events.
+ */
+var Events = {
+
+  /**
+   * @name Two.Events#on
+   * @function
+   * @param {String} [name] - The name of the event to bind a function to.
+   * @param {Function} [handler] - The function to be invoked when the event is dispatched.
+   * @description Call to add a listener to a specific event name.
+   */
+  on: addEventListener,
+
+  /**
+   * @name Two.Events#off
+   * @function
+   * @param {String} [name] - The name of the event intended to be removed.
+   * @param {Function} [handler] - The handler intended to be reomved.
+   * @description Call to remove listeners from a specific event. If only `name` is passed then all the handlers attached to that `name` will be removed. If no arguments are passed then all handlers for every event on the obejct are removed.
+   */
+  off: removeEventListener,
+
+  /**
+   * @name Two.Events#trigger
+   * @function
+   * @param {String} name - The name of the event to dispatch.
+   * @param arguments - Anything can be passed after the name and those will be passed on to handlers attached to the event in the order they are passed.
+   * @description Call to trigger a custom event. Any additional arguments passed after the name will be passed along to the attached handlers.
+   */
+  trigger: function(name) {
+    var scope = this;
+    if (!scope._events) return scope;
+    var args = Array.prototype.slice.call(arguments, 1);
+    var events = scope._events[name];
+    if (events) dispatch(scope, events, args);
+    return scope;
+  },
+
+  listen: function(obj, name, handler) {
+
+    var bound = this;
+
+    if (obj) {
+
+      var event = function () {
+        handler.apply(bound, arguments);
+      };
+
+      // Add references about the object that assigned this listener
+      event.obj = obj;
+      event.name = name;
+      event.handler = handler;
+
+      obj.on(name, event);
+
+    }
+
+    return bound;
+
+  },
+
+  ignore: function(obj, name, handler) {
+
+    var scope = this;
+    obj.off(name, handler);
+    return scope;
+
+  },
+
+  /**
+   * @name Two.Events.Types
+   * @property {Object} - Object of different types of Two.js specific events.
+   */
+  Types: {
+    play: 'play',
+    pause: 'pause',
+    update: 'update',
+    render: 'render',
+    resize: 'resize',
+    change: 'change',
+    remove: 'remove',
+    insert: 'insert',
+    order: 'order',
+    load: 'load'
+  }
+
+};
+
+
+/**
+ * @name Two.Events.bind
+ * @function
+ * @description Alias for {@link Two.Events.on}.
+ */
+Events.bind = addEventListener;
+
+/**
+ * @name Two.Events.unbind
+ * @function
+ * @description Alias for {@link Two.Events.off}.
+ */
+Events.unbind = removeEventListener;
+
+/**
+ * @private
+ * @returns {Two.Events} - Returns an instance of self for the purpose of chaining.
+ */
+function addEventListener(name, handler) {
+
+  var scope = this;
+
+  scope._events || (scope._events = {});
+  var list = scope._events[name] || (scope._events[name] = []);
+
+  list.push(handler);
+
+  return scope;
+
+}
+
+/**
+ * @private
+ * @returns {Two.Events} - Returns an instance of self for the purpose of chaining.
+ */
+function removeEventListener(name, handler) {
+
+  var scope = this;
+
+  if (!scope._events) {
+    return scope;
+  }
+  if (!name && !handler) {
+    scope._events = {};
+    return scope;
+  }
+
+  var names = name ? [name] : Object.keys(scope._events);
+  for (var i = 0, l = names.length; i < l; i++) {
+
+    name = names[i];
+    var list = scope._events[name];
+
+    if (list) {
+      var events = [];
+      if (handler) {
+        for (var j = 0, k = list.length; j < k; j++) {
+          var ev = list[j];
+          ev = ev.handler ? ev.handler : ev;
+          if (handler && handler !== ev) {
+            events.push(ev);
+          }
+        }
+      }
+      scope._events[name] = events;
+    }
+  }
+
+  return scope;
+}
+
+function dispatch(obj, events, args) {
+  var method;
+  switch (args.length) {
+  case 0:
+    method = function(i) {
+      events[i].call(obj, args[0]);
+    };
+    break;
+  case 1:
+    method = function(i) {
+      events[i].call(obj, args[0], args[1]);
+    };
+    break;
+  case 2:
+    method = function(i) {
+      events[i].call(obj, args[0], args[1], args[2]);
+    };
+    break;
+  case 3:
+    method = function(i) {
+      events[i].call(obj, args[0], args[1], args[2], args[3]);
+    };
+    break;
+  default:
+    method = function(i) {
+      events[i].apply(obj, args);
+    };
+  }
+  for (var i = 0; i < events.length; i++) {
+    method(i);
+  }
+}
+
+/**
+ * @name Two.Vector
+ * @class
+ * @param {Number} [x=0] - Any number to represent the horizontal x-component of the vector.
+ * @param {Number} [y=0] - Any number to represent the vertical y-component of the vector.
+ * @description A class to store x / y component vector data. In addition to storing data `Two.Vector` has suped up methods for commonplace mathematical operations.
+ */
+function Vector(x, y) {
+
+  /**
+   * @name Two.Vector#x
+   * @property {Number} - The horizontal x-component of the vector.
+   */
+  this.x = x || 0;
+
+  /**
+   * @name Two.Vector#y
+   * @property {Number} - The vertical y-component of the vector.
+   */
+  this.y = y || 0;
+
+}
+
+_.extend(Vector, {
+
+  /**
+   * @name Two.Vector.zero
+   * @readonly
+   * @property {Two.Vector} - Handy reference to a vector with component values 0, 0 at all times.
+   */
+  zero: new Vector(),
+
+  /**
+   * @name Two.Vector.add
+   * @function
+   * @param {Two.Vector} v1
+   * @param {Two.Vector} v2
+   * @returns {Two.Vector}
+   * @description Add two vectors together.
+   */
+  add: function(v1, v2) {
+    return new Vector(v1.x + v2.x, v1.y + v2.y);
+  },
+
+  /**
+   * @name Two.Vector.sub
+   * @function
+   * @param {Two.Vector} v1
+   * @param {Two.Vector} v2
+   * @returns {Two.Vector}
+   * @description Subtract two vectors: `v2` from `v1`.
+   */
+  sub: function(v1, v2) {
+    return new Vector(v1.x - v2.x, v1.y - v2.y);
+  },
+
+  /**
+   * @name Two.Vector.subtract
+   * @function
+   * @description Alias for {@link Two.Vector.sub}.
+   */
+  subtract: function(v1, v2) {
+    return Vector.sub(v1, v2);
+  },
+
+  /**
+   * @name Two.Vector.ratioBetween
+   * @function
+   * @param {Two.Vector} A
+   * @param {Two.Vector} B
+   * @returns {Number} The ratio betwen two points `v1` and `v2`.
+   */
+  ratioBetween: function(v1, v2) {
+
+    return (v1.x * v2.x + v1.y * v2.y) / (v1.length() * v2.length());
+
+  },
+
+  /**
+   * @name Two.Vector.angleBetween
+   * @function
+   * @param {Two.Vector} v1
+   * @param {Two.Vector} v2
+   * @returns {Number} The angle between points `v1` and `v2`.
+   */
+  angleBetween: function(v1, v2) {
+
+    var dx, dy;
+
+    if (arguments.length >= 4) {
+
+      dx = arguments[0] - arguments[2];
+      dy = arguments[1] - arguments[3];
+
+      return Math.atan2(dy, dx);
+
+    }
+
+    dx = v1.x - v2.x;
+    dy = v1.y - v2.y;
+
+    return Math.atan2(dy, dx);
+
+  },
+
+  /**
+   * @name Two.Vector.distanceBetween
+   * @function
+   * @param {Two.Vector} v1
+   * @param {Two.Vector} v2
+   * @returns {Number} The distance between points `v1` and `v2`. Distance is always positive.
+   */
+  distanceBetween: function(v1, v2) {
+
+    return Math.sqrt(Vector.distanceBetweenSquared(v1, v2));
+
+  },
+
+  /**
+   * @name Two.Vector.distanceBetweenSquared
+   * @function
+   * @param {Two.Vector} v1
+   * @param {Two.Vector} v2
+   * @returns {Number} The squared distance between points `v1` and `v2`.
+   */
+  distanceBetweenSquared: function(v1, v2) {
+
+    var dx = v1.x - v2.x;
+    var dy = v1.y - v2.y;
+
+    return dx * dx + dy * dy;
+
+  },
+
+  /**
+   * @name Two.Vector.MakeObservable
+   * @function
+   * @param {Object} object - The object to make observable.
+   * @description Convenience function to apply observable qualities of a {@link Two.Vector} to any object. Handy if you'd like to extend the {@link Two.Vector} class on a custom class.
+   */
+  MakeObservable: function(object) {
+
+    // /**
+    //  * Override Backbone bind / on in order to add properly broadcasting.
+    //  * This allows Two.Vector to not broadcast events unless event listeners
+    //  * are explicity bound to it.
+    //  */
+
+    object.bind = object.on = function() {
+
+      if (!this._bound) {
+        this._x = this.x;
+        this._y = this.y;
+        Object.defineProperty(this, 'x', xgs);
+        Object.defineProperty(this, 'y', ygs);
+        _.extend(this, BoundProto);
+        this._bound = true; // Reserved for event initialization check
+      }
+
+      Events.bind.apply(this, arguments);
+
+      return this;
+
+    };
+
+  }
+
+});
+
+_.extend(Vector.prototype, Events, {
+
+  constructor: Vector,
+
+  /**
+   * @name Two.Vector#set
+   * @function
+   * @param {Number} x
+   * @param {Number} y
+   * @description Set the x / y components of a vector to specific number values.
+   */
+  set: function(x, y) {
+    this.x = x;
+    this.y = y;
+    return this;
+  },
+
+  /**
+   * @name Two.Vector#copy
+   * @function
+   * @param {Two.Vector} v
+   * @description Copy the x / y components of another object `v`.
+   */
+  copy: function(v) {
+    this.x = v.x;
+    this.y = v.y;
+    return this;
+  },
+
+  /**
+   * @name Two.Vector#clear
+   * @function
+   * @description Set the x / y component values of the vector to zero.
+   */
+  clear: function() {
+    this.x = 0;
+    this.y = 0;
+    return this;
+  },
+
+  /**
+   * @name Two.Vector#clone
+   * @function
+   * @description Create a new vector and copy the existing values onto the newly created instance.
+   */
+  clone: function() {
+    return new Vector(this.x, this.y);
+  },
+
+  /**
+   * @name Two.Vector#add
+   * @function
+   * @param {Two.Vector} v
+   * @description Add an object with x / y component values to the instance.
+   * @overloaded
+   */
+
+  /**
+   * @name Two.Vector#add
+   * @function
+   * @param {Number} v
+   * @description Add the **same** number to both x / y component values of the instance.
+   * @overloaded
+   */
+
+  /**
+   * @name Two.Vector#add
+   * @function
+   * @param {Number} x
+   * @param {Number} y
+   * @description Add `x` / `y` values to their respective component value on the instance.
+   * @overloaded
+   */
+  add: function(x, y) {
+    if (arguments.length <= 0) {
+      return this;
+    } else if (arguments.length <= 1) {
+      if (typeof x === 'number') {
+        this.x += x;
+        this.y += x;
+      } else if (x && typeof x.x === 'number' && typeof x.y === 'number') {
+        this.x += x.x;
+        this.y += x.y;
+      }
+    } else {
+      this.x += x;
+      this.y += y;
+    }
+    return this;
+  },
+
+  /**
+   * @name Two.Vector#addSelf
+   * @function
+   * @description Alias for {@link Two.Vector.add}.
+   */
+  addSelf: function(v) {
+    return this.add.apply(this, arguments);
+  },
+
+  /**
+   * @name Two.Vector#sub
+   * @function
+   * @param {Two.Vector} v
+   * @description Subtract an object with x / y component values to the instance.
+   * @overloaded
+   */
+
+  /**
+   * @name Two.Vector#sub
+   * @function
+   * @param {Number} v
+   * @description Subtract the **same** number to both x / y component values of the instance.
+   * @overloaded
+   */
+
+  /**
+   * @name Two.Vector#sub
+   * @function
+   * @param {Number} x
+   * @param {Number} y
+   * @description Subtract `x` / `y` values to their respective component value on the instance.
+   * @overloaded
+   */
+  sub: function(x, y) {
+    if (arguments.length <= 0) {
+      return this;
+    } else if (arguments.length <= 1) {
+      if (typeof x === 'number') {
+        this.x -= x;
+        this.y -= x;
+      } else if (x && typeof x.x === 'number' && typeof x.y === 'number') {
+        this.x -= x.x;
+        this.y -= x.y;
+      }
+    } else {
+      this.x -= x;
+      this.y -= y;
+    }
+    return this;
+  },
+
+  /**
+   * @name Two.Vector#subtract
+   * @function
+   * @description Alias for {@link Two.Vector.sub}.
+   */
+  subtract: function() {
+    return this.sub.apply(this, arguments);
+  },
+
+  /**
+   * @name Two.Vector#subSelf
+   * @function
+   * @description Alias for {@link Two.Vector.sub}.
+   */
+  subSelf: function(v) {
+    return this.sub.apply(this, arguments);
+  },
+
+  /**
+   * @name Two.Vector#subtractSelf
+   * @function
+   * @description Alias for {@link Two.Vector.sub}.
+   */
+  subtractSelf: function(v) {
+    return this.sub.apply(this, arguments);
+  },
+
+  /**
+   * @name Two.Vector#multiply
+   * @function
+   * @param {Two.Vector} v
+   * @description Multiply an object with x / y component values to the instance.
+   * @overloaded
+   */
+
+  /**
+   * @name Two.Vector#multiply
+   * @function
+   * @param {Number} v
+   * @description Multiply the **same** number to both x / y component values of the instance.
+   * @overloaded
+   */
+
+  /**
+   * @name Two.Vector#multiply
+   * @function
+   * @param {Number} x
+   * @param {Number} y
+   * @description Multiply `x` / `y` values to their respective component value on the instance.
+   * @overloaded
+   */
+  multiply: function(x, y) {
+    if (arguments.length <= 0) {
+      return this;
+    } else if (arguments.length <= 1) {
+      if (typeof x === 'number') {
+        this.x *= x;
+        this.y *= x;
+      } else if (x && typeof x.x === 'number' && typeof x.y === 'number') {
+        this.x *= x.x;
+        this.y *= x.y;
+      }
+    } else {
+      this.x *= x;
+      this.y *= y;
+    }
+    return this;
+  },
+
+  /**
+   * @name Two.Vector#multiplySelf
+   * @function
+   * @description Alias for {@link Two.Vector.multiply}.
+   */
+  multiplySelf: function(v) {
+    return this.multiply.apply(this, arguments);
+  },
+
+  /**
+   * @name Two.Vector#multiplyScalar
+   * @function
+   * @param {Number} s - The scalar to multiply by.
+   * @description Mulitiply the vector by a single number. Shorthand to call {@link Two.Vector#multiply} directly.
+   */
+  multiplyScalar: function(s) {
+    return this.multiply(s);
+  },
+
+  /**
+   * @name Two.Vector#divide
+   * @function
+   * @param {Two.Vector} v
+   * @description Divide an object with x / y component values to the instance.
+   * @overloaded
+   */
+
+  /**
+   * @name Two.Vector#divide
+   * @function
+   * @param {Number} v
+   * @description Divide the **same** number to both x / y component values of the instance.
+   * @overloaded
+   */
+
+  /**
+   * @name Two.Vector#divide
+   * @function
+   * @param {Number} x
+   * @param {Number} y
+   * @description Divide `x` / `y` values to their respective component value on the instance.
+   * @overloaded
+   */
+  divide: function(x, y) {
+    if (arguments.length <= 0) {
+      return this;
+    } else if (arguments.length <= 1) {
+      if (typeof x === 'number') {
+        this.x /= x;
+        this.y /= x;
+      } else if (x && typeof x.x === 'number' && typeof x.y === 'number') {
+        this.x /= x.x;
+        this.y /= x.y;
+      }
+    } else {
+      this.x /= x;
+      this.y /= y;
+    }
+    if (_.isNaN(this.x)) {
+      this.x = 0;
+    }
+    if (_.isNaN(this.y)) {
+      this.y = 0;
+    }
+    return this;
+  },
+
+  /**
+   * @name Two.Vector#divideSelf
+   * @function
+   * @description Alias for {@link Two.Vector.divide}.
+   */
+  divideSelf: function(v) {
+    return this.divide.apply(this, arguments);
+  },
+
+  /**
+   * @name Two.Vector#divideScalar
+   * @function
+   * @param {Number} s - The scalar to divide by.
+   * @description Divide the vector by a single number. Shorthand to call {@link Two.Vector#divide} directly.
+   */
+  divideScalar: function(s) {
+    return this.divide(s);
+  },
+
+  /**
+   * @name Two.Vector#negate
+   * @function
+   * @description Invert each component's sign value.
+   */
+  negate: function() {
+    return this.multiply(-1);
+  },
+
+  /**
+   * @name Two.Vector#negate
+   * @function
+   * @returns {Number}
+   * @description Get the [dot product](https://en.wikipedia.org/wiki/Dot_product) of the vector.
+   */
+  dot: function(v) {
+    return this.x * v.x + this.y * v.y;
+  },
+
+  /**
+   * @name Two.Vector#length
+   * @function
+   * @returns {Number}
+   * @description Get the length of a vector.
+   */
+  length: function() {
+    return Math.sqrt(this.lengthSquared());
+  },
+
+  /**
+   * @name Two.Vector#lengthSquared
+   * @function
+   * @returns {Number}
+   * @description Get the length of the vector to the power of two. Widely used as less expensive than {@link Two.Vector#length}, because it isn't square-rooting any numbers.
+   */
+  lengthSquared: function() {
+    return this.x * this.x + this.y * this.y;
+  },
+
+  /**
+   * @name Two.Vector#normalize
+   * @function
+   * @description Normalize the vector from negative one to one.
+   */
+  normalize: function() {
+    return this.divideScalar(this.length());
+  },
+
+  /**
+   * @name Two.Vector#distanceTo
+   * @function
+   * @returns {Number}
+   * @description Get the distance between two vectors.
+   */
+  distanceTo: function(v) {
+    return Math.sqrt(this.distanceToSquared(v));
+  },
+
+  /**
+   * @name Two.Vector#distanceToSquared
+   * @function
+   * @returns {Number}
+   * @description Get the distance between two vectors to the power of two. Widely used as less expensive than {@link Two.Vector#distanceTo}, because it isn't square-rooting any numbers.
+   */
+  distanceToSquared: function(v) {
+    var dx = this.x - v.x,
+        dy = this.y - v.y;
+    return dx * dx + dy * dy;
+  },
+
+  /**
+   * @name Two.Vector#setLength
+   * @function
+   * @param {Number} l - length to set vector to.
+   * @description Set the length of a vector.
+   */
+  setLength: function(l) {
+    return this.normalize().multiplyScalar(l);
+  },
+
+  /**
+   * @name Two.Vector#equals
+   * @function
+   * @param {Two.Vector} v - The vector to compare against.
+   * @param {Number} [eps=0.0001] - An options epsilon for precision.
+   * @returns {Boolean}
+   * @description Qualify if one vector roughly equal another. With a margin of error defined by epsilon.
+   */
+  equals: function(v, eps) {
+    eps = (typeof eps === 'undefined') ?  0.0001 : eps;
+    return (this.distanceTo(v) < eps);
+  },
+
+  /**
+   * @name Two.Vector#lerp
+   * @function
+   * @param {Two.Vector} v - The destination vector to step towards.
+   * @param {Number} t - The zero to one value of how close the current vector gets to the destination vector.
+   * @description Linear interpolate one vector to another by an amount `t` defined as a zero to one number.
+   * @see [Matt DesLauriers](https://twitter.com/mattdesl/status/1031305279227478016) has a good thread about this.
+   */
+  lerp: function(v, t) {
+    var x = (v.x - this.x) * t + this.x;
+    var y = (v.y - this.y) * t + this.y;
+    return this.set(x, y);
+  },
+
+  /**
+   * @name Two.Vector#isZero
+   * @function
+   * @param {Number} [eps=0.0001] - Optional precision amount to check against.
+   * @returns {Boolean}
+   * @description Check to see if vector is roughly zero, based on the `epsilon` precision value.
+   */
+  isZero: function(eps) {
+    eps = (typeof eps === 'undefined') ?  0.0001 : eps;
+    return (this.length() < eps);
+  },
+
+  /**
+   * @name Two.Vector#toString
+   * @function
+   * @returns {String}
+   * @description Return a comma-separated string of x, y value. Great for storing in a database.
+   */
+  toString: function() {
+    return this.x + ', ' + this.y;
+  },
+
+  /**
+   * @name Two.Vector#toObject
+   * @function
+   * @returns {Object}
+   * @description Return a JSON compatible plain object that represents the vector.
+   */
+  toObject: function() {
+    return { x: this.x, y: this.y };
+  },
+
+  /**
+   * @name Two.Vector#rotate
+   * @function
+   * @param {Number} Number - The amoun to rotate the vector by.
+   * @description Rotate a vector.
+   */
+  rotate: function(Number) {
+    var cos = Math.cos(Number);
+    var sin = Math.sin(Number);
+    this.x = this.x * cos - this.y * sin;
+    this.y = this.x * sin + this.y * cos;
+    return this;
+  }
+
+});
+
+// The same set of prototypical functions, but using the underlying
+// getter or setter for `x` and `y` values. This set of functions
+// is used instead of the previously documented ones above when
+// Two.Vector#bind is invoked and there is event dispatching processed
+// on x / y property changes.
+var BoundProto = {
+
+  constructor: Vector,
+
+  set: function(x, y) {
+    this._x = x;
+    this._y = y;
+    return this.trigger(Events.Types.change);
+  },
+
+  copy: function(v) {
+    this._x = v.x;
+    this._y = v.y;
+    return this.trigger(Events.Types.change);
+  },
+
+  clear: function() {
+    this._x = 0;
+    this._y = 0;
+    return this.trigger(Events.Types.change);
+  },
+
+  clone: function() {
+    return new Vector(this._x, this._y);
+  },
+
+  add: function(x, y) {
+    if (arguments.length <= 0) {
+      return this;
+    } else if (arguments.length <= 1) {
+      if (typeof x === 'number') {
+        this._x += x;
+        this._y += x;
+      }  else if (x && typeof x.x === 'number' && typeof x.y === 'number') {
+        this._x += x.x;
+        this._y += x.y;
+      }
+    } else {
+      this._x += x;
+      this._y += y;
+    }
+    return this.trigger(Events.Types.change);
+  },
+
+  sub: function(x, y) {
+    if (arguments.length <= 0) {
+      return this;
+    } else if (arguments.length <= 1) {
+      if (typeof x === 'number') {
+        this._x -= x;
+        this._y -= x;
+      } else if (x && typeof x.x === 'number' && typeof x.y === 'number') {
+        this._x -= x.x;
+        this._y -= x.y;
+      }
+    } else {
+      this._x -= x;
+      this._y -= y;
+    }
+    return this.trigger(Events.Types.change);
+  },
+
+  multiply: function(x, y) {
+    if (arguments.length <= 0) {
+      return this;
+    } else if (arguments.length <= 1) {
+      if (typeof x === 'number') {
+        this._x *= x;
+        this._y *= x;
+      } else if (x && typeof x.x === 'number' && typeof x.y === 'number') {
+        this._x *= x.x;
+        this._y *= x.y;
+      }
+    } else {
+      this._x *= x;
+      this._y *= y;
+    }
+    return this.trigger(Events.Types.change);
+  },
+
+  divide: function(x, y) {
+    if (arguments.length <= 0) {
+      return this;
+    } else if (arguments.length <= 1) {
+      if (typeof x === 'number') {
+        this._x /= x;
+        this._y /= x;
+      } else if (x && typeof x.x === 'number' && typeof x.y === 'number') {
+        this._x /= x.x;
+        this._y /= x.y;
+      }
+    } else {
+      this._x /= x;
+      this._y /= y;
+    }
+    if (_.isNaN(this._x)) {
+      this._x = 0;
+    }
+    if (_.isNaN(this._y)) {
+      this._y = 0;
+    }
+    return this.trigger(Events.Types.change);
+  },
+
+  dot: function(v) {
+    return this._x * v.x + this._y * v.y;
+  },
+
+  lengthSquared: function() {
+    return this._x * this._x + this._y * this._y;
+  },
+
+  distanceToSquared: function(v) {
+    var dx = this._x - v.x,
+        dy = this._y - v.y;
+    return dx * dx + dy * dy;
+  },
+
+  lerp: function(v, t) {
+    var x = (v.x - this._x) * t + this._x;
+    var y = (v.y - this._y) * t + this._y;
+    return this.set(x, y);
+  },
+
+  toString: function() {
+    return this._x + ', ' + this._y;
+  },
+
+  toObject: function() {
+    return { x: this._x, y: this._y };
+  },
+
+  rotate: function (Number) {
+    var cos = Math.cos(Number);
+    var sin = Math.sin(Number);
+    this._x = this._x * cos - this._y * sin;
+    this._y = this._x * sin + this._y * cos;
+    return this;
+  }
+
+};
+
+var xgs = {
+  enumerable: true,
+  get: function() {
+    return this._x;
+  },
+  set: function(v) {
+    this._x = v;
+    this.trigger(Events.Types.change, 'x');
+  }
+};
+
+var ygs = {
+  enumerable: true,
+  get: function() {
+    return this._y;
+  },
+  set: function(v) {
+    this._y = v;
+    this.trigger(Events.Types.change, 'y');
+  }
+};
+
+Vector.MakeObservable(Vector.prototype);
+
+/**
+ * @class
+ * @name Two.Anchor
+ * @param {Number} [x=0] - The x position of the root anchor point.
+ * @param {Number} [y=0] - The y position of the root anchor point.
+ * @param {Number} [lx=0] - The x position of the left handle point.
+ * @param {Number} [ly=0] - The y position of the left handle point.
+ * @param {Number} [rx=0] - The x position of the right handle point.
+ * @param {Number} [ry=0] - The y position of the right handle point.
+ * @param {String} [command=Two.Commands.move] - The command to describe how to render. Applicable commands are {@link Two.Commands}
+ * @extends Two.Vector
+ * @description An object that holds 3 {@link Two.Vector}s, the anchor point and its corresponding handles: `left` and `right`. In order to properly describe the bezier curve about the point there is also a command property to describe what type of drawing should occur when Two.js renders the anchors.
+ */
+function Anchor(x, y, lx, ly, rx, ry, command) {
+
+  Vector.call(this, x, y);
+
+  this._broadcast = (function() {
+    this.trigger(Events.Types.change);
+  }).bind(this);
+
+  this._command = command || Commands.move;
+  this._relative = true;
+
+  var ilx = typeof lx === 'number';
+  var ily = typeof ly === 'number';
+  var irx = typeof rx === 'number';
+  var iry = typeof ry === 'number';
+
+  // Append the `controls` object only if control points are specified,
+  // keeping the Two.Anchor inline with a Two.Vector until it needs to
+  // evolve beyond those functions - e.g: a simple 2 component vector.
+  if (ilx || ily || irx || iry) {
+    Anchor.AppendCurveProperties(this);
+  }
+
+  if (ilx) {
+    this.controls.left.x = lx;
+  }
+  if (ily) {
+    this.controls.left.y = ly;
+  }
+  if (irx) {
+    this.controls.right.x = rx;
+  }
+  if (iry) {
+    this.controls.right.y = ry;
+  }
+
+}
+
+_.extend(Anchor, {
+
+  /**
+   * @name Two.Anchor.AppendCurveProperties
+   * @function
+   * @param {Two.Anchor} anchor - The instance to append the `control`object to.
+   * @description Adds the `controls` property as an object with `left` and `right` properties to access the bezier control handles that define how the curve is drawn. It also sets the `relative` property to `true` making vectors in the `controls` object relative to their corresponding root anchor point.
+   */
+  AppendCurveProperties: function(anchor) {
+
+    anchor.relative = true;
+
+    /**
+     * @name Two.Anchor#controls
+     * @property {Object} controls
+     * @description An plain object that holds the controls handles for a {@link Two.Anchor}.
+     */
+    anchor.controls = {};
+
+    /**
+     * @name Two.Anchor#controls#left
+     * @property {Two.Vector} left
+     * @description The "left" control point to define handles on a bezier curve.
+     */
+    anchor.controls.left = new Vector(0, 0);
+
+    /**
+     * @name Two.Anchor#controls#right
+     * @property {Two.Vector} right
+     * @description The "left" control point to define handles on a bezier curve.
+     */
+    anchor.controls.right = new Vector(0, 0);
+
+  },
+
+  /**
+   * @name Two.Anchor.MakeObservable
+   * @function
+   * @param {Object} object - The object to make observable.
+   * @description Convenience function to apply observable qualities of a {@link Two.Anchor} to any object. Handy if you'd like to extend the {@link Two.Anchor} class on a custom class.
+   */
+  MakeObservable: function(object) {
+
+    /**
+     * @name Two.Anchor#command
+     * @property {Two.Commands}
+     * @description A draw command associated with the anchor point.
+     */
+    Object.defineProperty(object, 'command', {
+
+      enumerable: true,
+
+      get: function() {
+        return this._command;
+      },
+
+      set: function(c) {
+        this._command = c;
+        if (this._command === Commands.curve && !_.isObject(this.controls)) {
+          Anchor.AppendCurveProperties(this);
+        }
+        this.trigger(Events.Types.change);
+      }
+
+    });
+
+    /**
+     * @name Two.Anchor#relative
+     * @property {Boolean}
+     * @description A boolean to render control points relative to the root anchor point or in global coordinate-space to the rest of the scene.
+     */
+    Object.defineProperty(object, 'relative', {
+
+      enumerable: true,
+
+      get: function() {
+        return this._relative;
+      },
+
+      set: function(b) {
+        if (this._relative != b) {
+          this._relative = !!b;
+          this.trigger(Events.Types.change);
+        }
+      }
+
+    });
+
+    _.extend(object, Vector.prototype, AnchorProto);
+
+    // Make it possible to bind and still have the Anchor specific
+    // inheritance from Two.Vector. In this case relying on `Two.Vector`
+    // to do much of the heavy event-listener binding / unbinding.
+    object.bind = object.on = function() {
+      var bound = this._bound;
+      Vector.prototype.bind.apply(this, arguments);
+      if (!bound) {
+        _.extend(this, AnchorProto);
+      }
+    };
+
+  }
+
+});
+
+var AnchorProto = {
+
+  constructor: Anchor,
+
+  /**
+   * @name Two.Anchor#listen
+   * @function
+   * @description Convenience method used mainly by {@link Two.Path#vertices} to listen and propagate changes from control points up to their respective anchors and further if necessary.
+   */
+  listen: function() {
+
+    if (!_.isObject(this.controls)) {
+      Anchor.AppendCurveProperties(this);
+    }
+
+    this.controls.left.bind(Events.Types.change, this._broadcast);
+    this.controls.right.bind(Events.Types.change, this._broadcast);
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Anchor#ignore
+   * @function
+   * @description Convenience method used mainly by {@link Two.Path#vertices} to ignore changes from a specific anchor's control points.
+   */
+  ignore: function() {
+
+    this.controls.left.unbind(Events.Types.change, this._broadcast);
+    this.controls.right.unbind(Events.Types.change, this._broadcast);
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Anchor#copy
+   * @function
+   * @param {Two.Anchor} v - The anchor to apply values to.
+   * @description Copy the properties of one {@link Two.Anchor} onto another.
+   */
+  copy: function(v) {
+
+    this.x = v.x;
+    this.y = v.y;
+
+    if (typeof v.command === 'string') {
+      this.command = v.command;
+    }
+    if (_.isObject(v.controls)) {
+      if (!_.isObject(this.controls)) {
+        Anchor.AppendCurveProperties(this);
+      }
+      // TODO: Do we need to listen here?
+      this.controls.left.copy(v.controls.left);
+      this.controls.right.copy(v.controls.right);
+    }
+    if (typeof v.relative === 'boolean') {
+      this.relative = v.relative;
+    }
+
+    // TODO: Hack for `Two.Commands.arc`
+    if (this.command === Commands.arc) {
+      this.rx = v.rx;
+      this.ry = v.ry;
+      this.xAxisRotation = v.xAxisRotation;
+      this.largeArcFlag = v.largeArcFlag;
+      this.sweepFlag = v.sweepFlag;
+    }
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Anchor#clone
+   * @function
+   * @returns {Two.Anchor}
+   * @description Create a new {@link Two.Anchor}, set all its values to the current instance and return it for use.
+   */
+  clone: function() {
+
+    var controls = this.controls;
+
+    var clone = new Anchor(
+      this.x,
+      this.y,
+      controls && controls.left.x,
+      controls && controls.left.y,
+      controls && controls.right.x,
+      controls && controls.right.y,
+      this.command
+    );
+    clone.relative = this._relative;
+    return clone;
+
+  },
+
+  /**
+   * @name Two.Anchor#toObject
+   * @function
+   * @returns {Object} - An object with properties filled out to mirror {@link Two.Anchor}.
+   * @description Create a JSON compatible plain object of the current instance. Intended for use with storing values in a database.
+   */
+  toObject: function() {
+    var o = {
+      x: this.x,
+      y: this.y
+    };
+    if (this._command) {
+      o.command = this._command;
+    }
+    if (this._relative) {
+      o.relative = this._relative;
+    }
+    if (this.controls) {
+      o.controls = {
+        left: this.controls.left.toObject(),
+        right: this.controls.right.toObject()
+      };
+    }
+    return o;
+  },
+
+  /**
+   * @name Two.Anchor#toString
+   * @function
+   * @returns {String} - A String with comma-separated values reflecting the various values on the current instance.
+   * @description Create a string form of the current instance. Intended for use with storing values in a database. This is lighter to store than the JSON compatible {@link Two.Anchor#toObject}.
+   */
+  toString: function() {
+    if (!this.controls) {
+      return [this._x, this._y].join(', ');
+    }
+    return [this._x, this._y, this.controls.left.x, this.controls.left.y,
+      this.controls.right.x, this.controls.right.y, this._command,
+      this._relative ? 1 : 0].join(', ');
+  }
+
+};
+
+Anchor.MakeObservable(Anchor.prototype);
+
+var count = 0;
+
+var Constants = {
+
+  /**
+   * @name Two.nextFrameID
+   * @property {Number}
+   * @description The id of the next requestAnimationFrame function.
+   */
+  nextFrameID: null,
+
+  // Primitive
+
+  /**
+   * @name Two.Types
+   * @property {Object} - The different rendering types available in the library.
+   */
+  Types: {
+    webgl: 'WebGLRenderer',
+    svg: 'SVGRenderer',
+    canvas: 'CanvasRenderer'
+  },
+
+  /**
+   * @name Two.Version
+   * @property {String} - The current working version of the library.
+   */
+  Version: 'v0.7.6',
+
+  /**
+   * @name Two.PublishDate
+   * @property {String} - The automatically generated publish date in the build process to verify version release candidates.
+   */
+  PublishDate: '2021-06-08T20:19:33.699Z',
+
+  /**
+   * @name Two.Identifier
+   * @property {String} - String prefix for all Two.js object's ids. This trickles down to SVG ids.
+   */
+  Identifier: 'two-',
+
+  /**
+   * @name Two.Resolution
+   * @property {Number} - Default amount of vertices to be used for interpreting Arcs and ArcSegments.
+   */
+  Resolution: 12,
+
+  /**
+   * @name Two.AutoCalculateImportedMatrices
+   * @property {Boolean} - When importing SVGs through the {@link two#interpret} and {@link two#load}, this boolean determines whether Two.js infers and then overrides the exact transformation matrix of the reference SVG.
+   * @nota-bene `false` copies the exact transformation matrix values, but also sets the path's `matrix.manual = true`.
+   */
+  AutoCalculateImportedMatrices: true,
+
+  /**
+   * @name Two.Instances
+   * @property {Two[]} - Registered list of all Two.js instances in the current session.
+   */
+  Instances: [],
+
+  /**
+   * @function Two.uniqueId
+   * @description Simple method to access an incrementing value. Used for `id` allocation on all Two.js objects.
+   * @returns {Number} Ever increasing Number.
+   */
+  uniqueId: function() {
+    return count++;
+  }
+
+};
+
+var HALF_PI$3 = Math.PI / 2;
+
+/**
+ * @name Two.Utils.Curve
+ * @property {Object} - Additional utility constant variables related to curve math and calculations.
+ */
+var Curve = {
+
+  CollinearityEpsilon: Math.pow(10, -30),
+
+  RecursionLimit: 16,
+
+  CuspLimit: 0,
+
+  Tolerance: {
+    distance: 0.25,
+    angle: 0,
+    epsilon: Number.EPSILON
+  },
+
+  // Lookup tables for abscissas and weights with values for n = 2 .. 16.
+  // As values are symmetric, only store half of them and adapt algorithm
+  // to factor in symmetry.
+  abscissas: [
+    [  0.5773502691896257645091488],
+    [0,0.7745966692414833770358531],
+    [  0.3399810435848562648026658,0.8611363115940525752239465],
+    [0,0.5384693101056830910363144,0.9061798459386639927976269],
+    [  0.2386191860831969086305017,0.6612093864662645136613996,0.9324695142031520278123016],
+    [0,0.4058451513773971669066064,0.7415311855993944398638648,0.9491079123427585245261897],
+    [  0.1834346424956498049394761,0.5255324099163289858177390,0.7966664774136267395915539,0.9602898564975362316835609],
+    [0,0.3242534234038089290385380,0.6133714327005903973087020,0.8360311073266357942994298,0.9681602395076260898355762],
+    [  0.1488743389816312108848260,0.4333953941292471907992659,0.6794095682990244062343274,0.8650633666889845107320967,0.9739065285171717200779640],
+    [0,0.2695431559523449723315320,0.5190961292068118159257257,0.7301520055740493240934163,0.8870625997680952990751578,0.9782286581460569928039380],
+    [  0.1252334085114689154724414,0.3678314989981801937526915,0.5873179542866174472967024,0.7699026741943046870368938,0.9041172563704748566784659,0.9815606342467192506905491],
+    [0,0.2304583159551347940655281,0.4484927510364468528779129,0.6423493394403402206439846,0.8015780907333099127942065,0.9175983992229779652065478,0.9841830547185881494728294],
+    [  0.1080549487073436620662447,0.3191123689278897604356718,0.5152486363581540919652907,0.6872929048116854701480198,0.8272013150697649931897947,0.9284348836635735173363911,0.9862838086968123388415973],
+    [0,0.2011940939974345223006283,0.3941513470775633698972074,0.5709721726085388475372267,0.7244177313601700474161861,0.8482065834104272162006483,0.9372733924007059043077589,0.9879925180204854284895657],
+    [  0.0950125098376374401853193,0.2816035507792589132304605,0.4580167776572273863424194,0.6178762444026437484466718,0.7554044083550030338951012,0.8656312023878317438804679,0.9445750230732325760779884,0.9894009349916499325961542]
+  ],
+
+  weights: [
+    [1],
+    [0.8888888888888888888888889,0.5555555555555555555555556],
+    [0.6521451548625461426269361,0.3478548451374538573730639],
+    [0.5688888888888888888888889,0.4786286704993664680412915,0.2369268850561890875142640],
+    [0.4679139345726910473898703,0.3607615730481386075698335,0.1713244923791703450402961],
+    [0.4179591836734693877551020,0.3818300505051189449503698,0.2797053914892766679014678,0.1294849661688696932706114],
+    [0.3626837833783619829651504,0.3137066458778872873379622,0.2223810344533744705443560,0.1012285362903762591525314],
+    [0.3302393550012597631645251,0.3123470770400028400686304,0.2606106964029354623187429,0.1806481606948574040584720,0.0812743883615744119718922],
+    [0.2955242247147528701738930,0.2692667193099963550912269,0.2190863625159820439955349,0.1494513491505805931457763,0.0666713443086881375935688],
+    [0.2729250867779006307144835,0.2628045445102466621806889,0.2331937645919904799185237,0.1862902109277342514260976,0.1255803694649046246346943,0.0556685671161736664827537],
+    [0.2491470458134027850005624,0.2334925365383548087608499,0.2031674267230659217490645,0.1600783285433462263346525,0.1069393259953184309602547,0.0471753363865118271946160],
+    [0.2325515532308739101945895,0.2262831802628972384120902,0.2078160475368885023125232,0.1781459807619457382800467,0.1388735102197872384636018,0.0921214998377284479144218,0.0404840047653158795200216],
+    [0.2152638534631577901958764,0.2051984637212956039659241,0.1855383974779378137417166,0.1572031671581935345696019,0.1215185706879031846894148,0.0801580871597602098056333,0.0351194603317518630318329],
+    [0.2025782419255612728806202,0.1984314853271115764561183,0.1861610000155622110268006,0.1662692058169939335532009,0.1395706779261543144478048,0.1071592204671719350118695,0.0703660474881081247092674,0.0307532419961172683546284],
+    [0.1894506104550684962853967,0.1826034150449235888667637,0.1691565193950025381893121,0.1495959888165767320815017,0.1246289712555338720524763,0.0951585116824927848099251,0.0622535239386478928628438,0.0271524594117540948517806]
+  ]
+
+};
+
+/**
+ * @name Two.Utils.getComponentOnCubicBezier
+ * @function
+ * @param {Number} t - Zero-to-one value describing what percentage to calculate.
+ * @param {Number} a - The firt point's component value.
+ * @param {Number} b - The first point's bezier component value.
+ * @param {Number} c - The second point's bezier component value.
+ * @param {Number} d - The second point's component value.
+ * @returns {Number} The coordinate value for a specific component along a cubic bezier curve by `t`.
+ */
+var getComponentOnCubicBezier = function(t, a, b, c, d) {
+  var k = 1 - t;
+  return (k * k * k * a) + (3 * k * k * t * b) + (3 * k * t * t * c) +
+      (t * t * t * d);
+};
+
+/**
+ * @name Two.Utils.subdivide
+ * @function
+ * @param {Number} x1 - x position of first anchor point.
+ * @param {Number} y1 - y position of first anchor point.
+ * @param {Number} x2 - x position of first anchor point's "right" bezier handle.
+ * @param {Number} y2 - y position of first anchor point's "right" bezier handle.
+ * @param {Number} x3 - x position of second anchor point's "left" bezier handle.
+ * @param {Number} y3 - y position of second anchor point's "left" bezier handle.
+ * @param {Number} x4 - x position of second anchor point.
+ * @param {Number} y4 - y position of second anchor point.
+ * @param {Number} [limit=Two.Utils.Curve.RecursionLimit] - The amount of vertices to create by subdividing.
+ * @returns {Anchor[]} A list of anchor points ordered in between `x1`, `y1` and `x4`, `y4`
+ * @description Given 2 points (a, b) and corresponding control point for each return an array of points that represent points plotted along the curve. The number of returned points is determined by `limit`.
+ */
+var subdivide = function(x1, y1, x2, y2, x3, y3, x4, y4, limit) {
+
+  limit = limit || Curve.RecursionLimit;
+  var amount = limit + 1;
+
+  // TODO: Abstract 0.001 to a limiting variable
+  // Don't recurse if the end points are identical
+  if (Math.abs(x1 - x4) < 0.001 && Math.abs(y1 - y4) < 0.001) {
+    return [new Anchor(x4, y4)];
+  }
+
+  var result = [];
+
+  for (var i = 0; i < amount; i++) {
+    var t = i / amount;
+    var x = getComponentOnCubicBezier(t, x1, x2, x3, x4);
+    var y = getComponentOnCubicBezier(t, y1, y2, y3, y4);
+    result.push(new Anchor(x, y));
+  }
+
+  return result;
+
+};
+
+/**
+ * @name Two.Utils.getCurveLength
+ * @function
+ * @param {Number} x1 - x position of first anchor point.
+ * @param {Number} y1 - y position of first anchor point.
+ * @param {Number} x2 - x position of first anchor point's "right" bezier handle.
+ * @param {Number} y2 - y position of first anchor point's "right" bezier handle.
+ * @param {Number} x3 - x position of second anchor point's "left" bezier handle.
+ * @param {Number} y3 - y position of second anchor point's "left" bezier handle.
+ * @param {Number} x4 - x position of second anchor point.
+ * @param {Number} y4 - y position of second anchor point.
+ * @param {Number} [limit=Two.Utils.Curve.RecursionLimit] - The amount of vertices to create by subdividing.
+ * @returns {Number} The length of a curve.
+ * @description Given 2 points (a, b) and corresponding control point for each, return a float that represents the length of the curve using Gauss-Legendre algorithm. Limit iterations of calculation by `limit`.
+ */
+var getCurveLength$1 = function(x1, y1, x2, y2, x3, y3, x4, y4, limit) {
+
+  // TODO: Better / fuzzier equality check
+  // Linear calculation
+  if (x1 === x2 && y1 === y2 && x3 === x4 && y3 === y4) {
+    var dx = x4 - x1;
+    var dy = y4 - y1;
+    return Math.sqrt(dx * dx + dy * dy);
+  }
+
+  // Calculate the coefficients of a Bezier derivative.
+  var ax = 9 * (x2 - x3) + 3 * (x4 - x1),
+    bx = 6 * (x1 + x3) - 12 * x2,
+    cx = 3 * (x2 - x1),
+
+    ay = 9 * (y2 - y3) + 3 * (y4 - y1),
+    by = 6 * (y1 + y3) - 12 * y2,
+    cy = 3 * (y2 - y1);
+
+  var integrand = function(t) {
+    // Calculate quadratic equations of derivatives for x and y
+    var dx = (ax * t + bx) * t + cx,
+      dy = (ay * t + by) * t + cy;
+    return Math.sqrt(dx * dx + dy * dy);
+  };
+
+  return integrate(
+    integrand, 0, 1, limit || Curve.RecursionLimit
+  );
+
+};
+
+/**
+ * @name Two.Utils.getCurveBoundingBox
+ * @function
+ * @param {Number} x1 - x position of first anchor point.
+ * @param {Number} y1 - y position of first anchor point.
+ * @param {Number} x2 - x position of first anchor point's "right" bezier handle.
+ * @param {Number} y2 - y position of first anchor point's "right" bezier handle.
+ * @param {Number} x3 - x position of second anchor point's "left" bezier handle.
+ * @param {Number} y3 - y position of second anchor point's "left" bezier handle.
+ * @param {Number} x4 - x position of second anchor point.
+ * @param {Number} y4 - y position of second anchor point.
+ * @returns {Object} Object contains min and max `x` / `y` bounds.
+ * @see {@link https://github.com/adobe-webplatform/Snap.svg/blob/master/src/path.js#L856}
+ */
+var getCurveBoundingBox = function(x1, y1, x2, y2, x3, y3, x4, y4) {
+
+  var tvalues = [];
+  var bounds = [[], []];
+  var a, b, c, t, t1, t2, b2ac, sqrtb2ac;
+
+  for (var i = 0; i < 2; ++i) {
+      if (i == 0) {
+        b = 6 * x1 - 12 * x2 + 6 * x3;
+        a = -3 * x1 + 9 * x2 - 9 * x3 + 3 * x4;
+        c = 3 * x2 - 3 * x1;
+      } else {
+        b = 6 * y1 - 12 * y2 + 6 * y3;
+        a = -3 * y1 + 9 * y2 - 9 * y3 + 3 * y4;
+        c = 3 * y2 - 3 * y1;
+      }
+      if (Math.abs(a) < 1e-12) {
+        if (Math.abs(b) < 1e-12) {
+          continue;
+        }
+        t = -c / b;
+        if (0 < t && t < 1) {
+          tvalues.push(t);
+        }
+        continue;
+      }
+      b2ac = b * b - 4 * c * a;
+      sqrtb2ac = Math.sqrt(b2ac);
+      if (b2ac < 0) {
+        continue;
+      }
+      t1 = (-b + sqrtb2ac) / (2 * a);
+      if (0 < t1 && t1 < 1) {
+        tvalues.push(t1);
+      }
+      t2 = (-b - sqrtb2ac) / (2 * a);
+      if (0 < t2 && t2 < 1) {
+        tvalues.push(t2);
+      }
+  }
+
+  var j = tvalues.length;
+  var jlen = j;
+  var mt;
+
+  while (j--) {
+    t = tvalues[j];
+    mt = 1 - t;
+    bounds[0][j] = mt * mt * mt * x1 + 3 * mt * mt * t * x2 + 3 * mt * t * t * x3 + t * t * t * x4;
+    bounds[1][j] = mt * mt * mt * y1 + 3 * mt * mt * t * y2 + 3 * mt * t * t * y3 + t * t * t * y4;
+  }
+
+  bounds[0][jlen] = x1;
+  bounds[1][jlen] = y1;
+  bounds[0][jlen + 1] = x4;
+  bounds[1][jlen + 1] = y4;
+  bounds[0].length = bounds[1].length = jlen + 2;
+
+  return {
+    min: { x: Math.min.apply(0, bounds[0]), y: Math.min.apply(0, bounds[1]) },
+    max: { x: Math.max.apply(0, bounds[0]), y: Math.max.apply(0, bounds[1]) }
+  };
+
+};
+
+/**
+ * @name Two.Utils.integrate
+ * @function
+ * @param {Function} f
+ * @param {Number} a
+ * @param {Number} b
+ * @param {Number} n
+ * @description Integration for `getCurveLength` calculations.
+ * @see [Paper.js](@link https://github.com/paperjs/paper.js/blob/master/src/util/Numerical.js#L101)
+ */
+var integrate = function(f, a, b, n) {
+  var x = Curve.abscissas[n - 2],
+    w = Curve.weights[n - 2],
+    A = 0.5 * (b - a),
+    B = A + a,
+    i = 0,
+    m = (n + 1) >> 1,
+    sum = n & 1 ? w[i++] * f(B) : 0; // Handle odd n
+  while (i < m) {
+    var Ax = A * x[i];
+    sum += w[i++] * (f(B + Ax) + f(B - Ax));
+  }
+  return A * sum;
+};
+
+/**
+ * @name Two.Utils.getCurveFromPoints
+ * @function
+ * @param {Anchor[]} points
+ * @param {Boolean} closed
+ * @description Sets the bezier handles on {@link Anchor}s in the `points` list with estimated values to create a catmull-rom like curve. Used by {@link Two.Path#plot}.
+ */
+var getCurveFromPoints = function(points, closed) {
+
+  var l = points.length, last = l - 1;
+
+  for (var i = 0; i < l; i++) {
+
+    var point = points[i];
+
+    if (!_.isObject(point.controls)) {
+      Anchor.AppendCurveProperties(point);
+    }
+
+    var prev = closed ? mod(i - 1, l) : Math.max(i - 1, 0);
+    var next = closed ? mod(i + 1, l) : Math.min(i + 1, last);
+
+    var a = points[prev];
+    var b = point;
+    var c = points[next];
+    getControlPoints(a, b, c);
+
+    b.command = i === 0 ? Commands.move : Commands.curve;
+
+  }
+
+};
+
+/**
+ * @name Two.Utils.getControlPoints
+ * @function
+ * @param {Anchor} a
+ * @param {Anchor} b
+ * @param {Anchor} c
+ * @returns {Anchor} Returns the passed middle point `b`.
+ * @description Given three coordinates set the control points for the middle, b, vertex based on its position with the adjacent points.
+ */
+var getControlPoints = function(a, b, c) {
+
+  var a1 = Vector.angleBetween(a, b);
+  var a2 = Vector.angleBetween(c, b);
+
+  var d1 = Vector.distanceBetween(a, b);
+  var d2 = Vector.distanceBetween(c, b);
+
+  var mid = (a1 + a2) / 2;
+
+  // TODO: Issue 73
+  if (d1 < 0.0001 || d2 < 0.0001) {
+    if (typeof b.relative === 'boolean' && !b.relative) {
+      b.controls.left.copy(b);
+      b.controls.right.copy(b);
+    }
+    return b;
+  }
+
+  d1 *= 0.33; // Why 0.33?
+  d2 *= 0.33;
+
+  if (a2 < a1) {
+    mid += HALF_PI$3;
+  } else {
+    mid -= HALF_PI$3;
+  }
+
+  b.controls.left.x = Math.cos(mid) * d1;
+  b.controls.left.y = Math.sin(mid) * d1;
+
+  mid -= Math.PI;
+
+  b.controls.right.x = Math.cos(mid) * d2;
+  b.controls.right.y = Math.sin(mid) * d2;
+
+  if (typeof b.relative === 'boolean' && !b.relative) {
+    b.controls.left.x += b.x;
+    b.controls.left.y += b.y;
+    b.controls.right.x += b.x;
+    b.controls.right.y += b.y;
+  }
+
+  return b;
+
+};
+
+/**
+ * @name Two.Utils.getReflection
+ * @function
+ * @param {Vector} a
+ * @param {Vector} b
+ * @param {Boolean} [relative=false]
+ * @returns {Vector} New {@link Vector} that represents the reflection point.
+ * @description Get the reflection of a point `b` about point `a`. Where `a` is in absolute space and `b` is relative to `a`.
+ * @see {@link http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes}
+ */
+var getReflection = function(a, b, relative) {
+
+  return new Vector(
+    2 * a.x - (b.x + a.x) - (relative ? a.x : 0),
+    2 * a.y - (b.y + a.y) - (relative ? a.y : 0)
+  );
+
+};
+
+/**
+ * @name Two.Utils.getAnchorsFromArcData
+ * @function
+ * @param {Vector} center
+ * @param {Number} xAxisRotation
+ * @param {Number} rx - x radius
+ * @param {Number} ry - y radius
+ * @param {Number} ts
+ * @param {Number} td
+ * @param {Boolean} [ccw=false] - Set path traversal to counter-clockwise
+ */
+var getAnchorsFromArcData = function(center, xAxisRotation, rx, ry, ts, td, ccw) {
+
+  var resolution = Constants.Resolution;
+
+  for (var i = 0; i < resolution; i++) {
+    var pct = (i + 1) / resolution;
+    if (ccw) {
+      pct = 1 - pct;
+    }
+
+    var theta = pct * td + ts;
+    var x = rx * Math.cos(theta);
+    var y = ry * Math.sin(theta);
+
+    // x += center.x;
+    // y += center.y;
+
+    var anchor = new Anchor(x, y);
+    Anchor.AppendCurveProperties(anchor);
+    anchor.command = Commands.line;
+  }
+
+};
+
+var Curves = /*#__PURE__*/Object.freeze({
+  __proto__: null,
+  Curve: Curve,
+  getComponentOnCubicBezier: getComponentOnCubicBezier,
+  subdivide: subdivide,
+  getCurveLength: getCurveLength$1,
+  getCurveBoundingBox: getCurveBoundingBox,
+  integrate: integrate,
+  getCurveFromPoints: getCurveFromPoints,
+  getControlPoints: getControlPoints,
+  getReflection: getReflection,
+  getAnchorsFromArcData: getAnchorsFromArcData
+});
+
+var devicePixelRatio = root$1.devicePixelRatio || 1;
+
+var getBackingStoreRatio = function(ctx) {
+  return ctx.webkitBackingStorePixelRatio ||
+  ctx.mozBackingStorePixelRatio ||
+  ctx.msBackingStorePixelRatio ||
+  ctx.oBackingStorePixelRatio ||
+  ctx.backingStorePixelRatio || 1;
+};
+
+/**
+ * @name Two.Utils.getRatio
+ * @function
+ * @param {CanvasRenderingContext2D} ctx
+ * @returns {Number} The ratio of a unit in Two.js to the pixel density of a session's screen.
+ * @see [High DPI Rendering](http://www.html5rocks.com/en/tutorials/canvas/hidpi/)
+ */
+var getRatio = function(ctx) {
+  return devicePixelRatio / getBackingStoreRatio(ctx);
+};
+
+// Constants
+
+var cos$5 = Math.cos, sin$5 = Math.sin, tan = Math.tan;
+var array = [];
+
+/**
+ * @name Two.Matrix
+ * @class
+ * @param {Number} [a=1] - The value for element at the first column and first row.
+ * @param {Number} [b=0] - The value for element at the second column and first row.
+ * @param {Number} [c=0] - The value for element at the third column and first row.
+ * @param {Number} [d=0] - The value for element at the first column and second row.
+ * @param {Number} [e=1] - The value for element at the second column and second row.
+ * @param {Number} [f=0] - The value for element at the third column and second row.
+ * @param {Number} [g=0] - The value for element at the first column and third row.
+ * @param {Number} [h=0] - The value for element at the second column and third row.
+ * @param {Number} [i=1] - The value for element at the third column and third row.
+ * @description A class to store 3 x 3 transformation matrix information. In addition to storing data `Two.Matrix` has suped up methods for commonplace mathematical operations.
+ * @nota-bene Order is based on how to construct transformation strings for the browser.
+ */
+function Matrix(a, b, c, d, e, f) {
+
+  /**
+   * @name Two.Matrix#elements
+   * @property {Number[]} - The underlying data stored as an array.
+   */
+  this.elements = new NumArray(9);
+
+  var elements = a;
+  if (!Array.isArray(elements)) {
+    elements = Array.prototype.slice.call(arguments);
+  }
+
+  // initialize the elements with default values.
+  this.identity();
+
+  if (elements.length > 0) {
+    this.set(elements);
+  }
+
+}
+
+setMatrix(Matrix);
+
+_.extend(Matrix, {
+
+  /**
+   * @name Two.Matrix.Identity
+   * @property {Number[]} - A stored reference to the default value of a 3 x 3 matrix.
+   */
+  Identity: [
+    1, 0, 0,
+    0, 1, 0,
+    0, 0, 1
+  ],
+
+  /**
+   * @name Two.Matrix.Multiply
+   * @function
+   * @param {Two.Matrix} A
+   * @param {Two.Matrix} B
+   * @param {Two.Matrix} [C] - An optional matrix to apply the multiplication to.
+   * @returns {Two.Matrix} - If an optional `C` matrix isn't passed then a new one is created and returned.
+   * @description Multiply two matrices together and return the result.
+   */
+  Multiply: function(A, B, C) {
+
+    if (B.length <= 3) { // Multiply Vector
+
+      var x, y, z, e = A;
+
+      var a = B[0] || 0,
+          b = B[1] || 0,
+          c = B[2] || 0;
+
+      // Go down rows first
+      // a, d, g, b, e, h, c, f, i
+
+      x = e[0] * a + e[1] * b + e[2] * c;
+      y = e[3] * a + e[4] * b + e[5] * c;
+      z = e[6] * a + e[7] * b + e[8] * c;
+
+      return { x: x, y: y, z: z };
+
+    }
+
+    var A0 = A[0], A1 = A[1], A2 = A[2];
+    var A3 = A[3], A4 = A[4], A5 = A[5];
+    var A6 = A[6], A7 = A[7], A8 = A[8];
+
+    var B0 = B[0], B1 = B[1], B2 = B[2];
+    var B3 = B[3], B4 = B[4], B5 = B[5];
+    var B6 = B[6], B7 = B[7], B8 = B[8];
+
+    C = C || new NumArray(9);
+
+    C[0] = A0 * B0 + A1 * B3 + A2 * B6;
+    C[1] = A0 * B1 + A1 * B4 + A2 * B7;
+    C[2] = A0 * B2 + A1 * B5 + A2 * B8;
+    C[3] = A3 * B0 + A4 * B3 + A5 * B6;
+    C[4] = A3 * B1 + A4 * B4 + A5 * B7;
+    C[5] = A3 * B2 + A4 * B5 + A5 * B8;
+    C[6] = A6 * B0 + A7 * B3 + A8 * B6;
+    C[7] = A6 * B1 + A7 * B4 + A8 * B7;
+    C[8] = A6 * B2 + A7 * B5 + A8 * B8;
+
+    return C;
+
+  }
+
+});
+
+_.extend(Matrix.prototype, Events, {
+
+  constructor: Matrix,
+
+  /**
+   * @name Two.Matrix#manual
+   * @property {Boolean} - Determines whether Two.js automatically calculates the values for the matrix or if the developer intends to manage the matrix.
+   * @nota-bene - Setting to `true` nullifies {@link Two.Shape#translation}, {@link Two.Shape#rotation}, and {@link Two.Shape#scale}.
+   */
+  manual: false,
+
+  /**
+   * @name Two.Matrix#set
+   * @function
+   * @param {Number} a - The value for element at the first column and first row.
+   * @param {Number} b - The value for element at the second column and first row.
+   * @param {Number} c - The value for element at the third column and first row.
+   * @param {Number} d - The value for element at the first column and second row.
+   * @param {Number} e - The value for element at the second column and second row.
+   * @param {Number} f - The value for element at the third column and second row.
+   * @param {Number} g - The value for element at the first column and third row.
+   * @param {Number} h - The value for element at the second column and third row.
+   * @param {Number} i - The value for element at the third column and third row.
+   * @description Set an array of values onto the matrix. Order described in {@link Two.Matrix}.
+   */
+
+    /**
+    * @name Two.Matrix#set
+    * @function
+    * @param {Number[]} a - The array of elements to apply.
+    * @description Set an array of values onto the matrix. Order described in {@link Two.Matrix}.
+    */
+  set: function(a, b, c, d, e, f, g, h, i) {
+
+    var elements;
+
+    if (typeof b === 'undefined') {
+      elements = a;
+      a = elements[0];
+      b = elements[1];
+      c = elements[2];
+      d = elements[3];
+      e = elements[4];
+      f = elements[5];
+      g = elements[6];
+      h = elements[7];
+      i = elements[8];
+    }
+
+    this.elements[0] = a;
+    this.elements[1] = b;
+    this.elements[2] = c;
+    this.elements[3] = d;
+    this.elements[4] = e;
+    this.elements[5] = f;
+    this.elements[6] = g;
+    this.elements[7] = h;
+    this.elements[8] = i;
+
+    return this.trigger(Events.Types.change);
+
+  },
+
+  /**
+   * @name Two.Matrix#copy
+   * @function
+   * @description Copy the matrix of one to the current instance.
+   */
+  copy: function(m) {
+
+    this.elements[0] = m.elements[0];
+    this.elements[1] = m.elements[1];
+    this.elements[2] = m.elements[2];
+    this.elements[3] = m.elements[3];
+    this.elements[4] = m.elements[4];
+    this.elements[5] = m.elements[5];
+    this.elements[6] = m.elements[6];
+    this.elements[7] = m.elements[7];
+    this.elements[8] = m.elements[8];
+
+    this.manual = m.manual;
+
+    return this.trigger(Events.Types.change);
+
+  },
+
+  /**
+   * @name Two.Matrix#identity
+   * @function
+   * @description Turn matrix to the identity, like resetting.
+   */
+  identity: function() {
+
+    this.elements[0] = Matrix.Identity[0];
+    this.elements[1] = Matrix.Identity[1];
+    this.elements[2] = Matrix.Identity[2];
+    this.elements[3] = Matrix.Identity[3];
+    this.elements[4] = Matrix.Identity[4];
+    this.elements[5] = Matrix.Identity[5];
+    this.elements[6] = Matrix.Identity[6];
+    this.elements[7] = Matrix.Identity[7];
+    this.elements[8] = Matrix.Identity[8];
+
+    return this.trigger(Events.Types.change);
+
+  },
+
+  /**
+   * @name Two.Matrix#multiply
+   * @function
+   * @param {Number} a - The scalar to be multiplied.
+   * @description Multiply all components of the matrix against a single scalar value.
+   * @overloaded
+   */
+
+  /**
+   * @name Two.Matrix#multiply
+   * @function
+   * @param {Number} a - The x component to be multiplied.
+   * @param {Number} b - The y component to be multiplied.
+   * @param {Number} c - The z component to be multiplied.
+   * @description Multiply all components of a matrix against a 3 component vector.
+   * @overloaded
+   */
+
+  /**
+   * @name Two.Matrix#multiply
+   * @function
+   * @param {Number} a - The value at the first column and first row of the matrix to be multiplied.
+   * @param {Number} b - The value at the second column and first row of the matrix to be multiplied.
+   * @param {Number} c - The value at the third column and first row of the matrix to be multiplied.
+   * @param {Number} d - The value at the first column and second row of the matrix to be multiplied.
+   * @param {Number} e - The value at the second column and second row of the matrix to be multiplied.
+   * @param {Number} f - The value at the third column and second row of the matrix to be multiplied.
+   * @param {Number} g - The value at the first column and third row of the matrix to be multiplied.
+   * @param {Number} h - The value at the second column and third row of the matrix to be multiplied.
+   * @param {Number} i - The value at the third column and third row of the matrix to be multiplied.
+   * @description Multiply all components of a matrix against another matrix.
+   * @overloaded
+   */
+  multiply: function(a, b, c, d, e, f, g, h, i) {
+
+    // Multiply scalar
+
+    if (typeof b === 'undefined') {
+
+      this.elements[0] *= a;
+      this.elements[1] *= a;
+      this.elements[2] *= a;
+      this.elements[3] *= a;
+      this.elements[4] *= a;
+      this.elements[5] *= a;
+      this.elements[6] *= a;
+      this.elements[7] *= a;
+      this.elements[8] *= a;
+
+      return this.trigger(Events.Types.change);
+
+    }
+
+    if (typeof d === 'undefined') { // Multiply Vector
+
+      var x, y, z;
+      a = a || 0;
+      b = b || 0;
+      c = c || 0;
+      e = this.elements;
+
+      // Go down rows first
+      // a, d, g, b, e, h, c, f, i
+
+      x = e[0] * a + e[1] * b + e[2] * c;
+      y = e[3] * a + e[4] * b + e[5] * c;
+      z = e[6] * a + e[7] * b + e[8] * c;
+
+      return { x: x, y: y, z: z };
+
+    }
+
+    // Multiple matrix
+
+    var A = this.elements;
+    var B = [a, b, c, d, e, f, g, h, i];
+
+    var A0 = A[0], A1 = A[1], A2 = A[2];
+    var A3 = A[3], A4 = A[4], A5 = A[5];
+    var A6 = A[6], A7 = A[7], A8 = A[8];
+
+    var B0 = B[0], B1 = B[1], B2 = B[2];
+    var B3 = B[3], B4 = B[4], B5 = B[5];
+    var B6 = B[6], B7 = B[7], B8 = B[8];
+
+    this.elements[0] = A0 * B0 + A1 * B3 + A2 * B6;
+    this.elements[1] = A0 * B1 + A1 * B4 + A2 * B7;
+    this.elements[2] = A0 * B2 + A1 * B5 + A2 * B8;
+
+    this.elements[3] = A3 * B0 + A4 * B3 + A5 * B6;
+    this.elements[4] = A3 * B1 + A4 * B4 + A5 * B7;
+    this.elements[5] = A3 * B2 + A4 * B5 + A5 * B8;
+
+    this.elements[6] = A6 * B0 + A7 * B3 + A8 * B6;
+    this.elements[7] = A6 * B1 + A7 * B4 + A8 * B7;
+    this.elements[8] = A6 * B2 + A7 * B5 + A8 * B8;
+
+    return this.trigger(Events.Types.change);
+
+  },
+
+  /**
+   * @name Two.Matrix#inverse
+   * @function
+   * @param {Two.Matrix} [out] - The optional matrix to apply the inversion to.
+   * @description Return an inverted version of the matrix. If no optional one is passed a new matrix is created and returned.
+   */
+  inverse: function(out) {
+
+    var a = this.elements;
+    out = out || new Matrix();
+
+    var a00 = a[0], a01 = a[1], a02 = a[2];
+    var a10 = a[3], a11 = a[4], a12 = a[5];
+    var a20 = a[6], a21 = a[7], a22 = a[8];
+
+    var b01 = a22 * a11 - a12 * a21;
+    var b11 = -a22 * a10 + a12 * a20;
+    var b21 = a21 * a10 - a11 * a20;
+
+    // Calculate the determinant
+    var det = a00 * b01 + a01 * b11 + a02 * b21;
+
+    if (!det) {
+      return null;
+    }
+
+    det = 1.0 / det;
+
+    out.elements[0] = b01 * det;
+    out.elements[1] = (-a22 * a01 + a02 * a21) * det;
+    out.elements[2] = (a12 * a01 - a02 * a11) * det;
+    out.elements[3] = b11 * det;
+    out.elements[4] = (a22 * a00 - a02 * a20) * det;
+    out.elements[5] = (-a12 * a00 + a02 * a10) * det;
+    out.elements[6] = b21 * det;
+    out.elements[7] = (-a21 * a00 + a01 * a20) * det;
+    out.elements[8] = (a11 * a00 - a01 * a10) * det;
+
+    return out;
+
+  },
+
+  /**
+   * @name Two.Matrix#scale
+   * @function
+   * @param {Number} scale - The one dimensional scale to apply to the matrix.
+   * @description Uniformly scale the transformation matrix.
+   */
+
+  /**
+   * @name Two.Matrix#scale
+   * @function
+   * @param {Number} sx - The horizontal scale factor.
+   * @param {Number} sy - The vertical scale factor
+   * @description Scale the transformation matrix in two dimensions.
+   */
+  scale: function(sx, sy) {
+
+    var l = arguments.length;
+    if (l <= 1) {
+      sy = sx;
+    }
+
+    return this.multiply(sx, 0, 0, 0, sy, 0, 0, 0, 1);
+
+  },
+
+  /**
+   * @name Two.Matrix#rotate
+   * @function
+   * @param {Number} Number - The amount to rotate in Number.
+   * @description Rotate the matrix.
+   */
+  rotate: function(Number) {
+
+    var c = cos$5(Number);
+    var s = sin$5(Number);
+
+    return this.multiply(c, -s, 0, s, c, 0, 0, 0, 1);
+
+  },
+
+  /**
+   * @name Two.Matrix#translate
+   * @function
+   * @param {Number} x - The horizontal translation value to apply.
+   * @param {Number} y - The vertical translation value to apply.
+   * @description Translate the matrix.
+   */
+  translate: function(x, y) {
+
+    return this.multiply(1, 0, x, 0, 1, y, 0, 0, 1);
+
+  },
+
+  /**
+   * @name Two.Matrix#skewX
+   * @function
+   * @param {Number} Number - The amount to skew in Number.
+   * @description Skew the matrix by an angle in the x axis direction.
+   */
+  skewX: function(Number) {
+
+    var a = tan(Number);
+
+    return this.multiply(1, a, 0, 0, 1, 0, 0, 0, 1);
+
+  },
+
+  /**
+   * @name Two.Matrix#skewY
+   * @function
+   * @param {Number} Number - The amount to skew in Number.
+   * @description Skew the matrix by an angle in the y axis direction.
+   */
+  skewY: function(Number) {
+
+    var a = tan(Number);
+
+    return this.multiply(1, 0, 0, a, 1, 0, 0, 0, 1);
+
+  },
+
+  /**
+   * @name Two.Matrix#toString
+   * @function
+   * @param {Boolean} [fullMatrix=false] - Return the full 9 elements of the matrix or just 6 for 2D transformations.
+   * @returns {String} - The transformation matrix as a 6 component string separated by spaces.
+   * @description Create a transform string. Used for the Two.js rendering APIs.
+   */
+  toString: function(fullMatrix) {
+
+    array.length = 0;
+    this.toTransformArray(fullMatrix, array);
+
+    return array.map(toFixed).join(' ');
+
+  },
+
+  /**
+   * @name Two.Matrix#toTransformArray
+   * @function
+   * @param {Boolean} [fullMatrix=false] - Return the full 9 elements of the matrix or just 6 in the format for 2D transformations.
+   * @param {Number[]} [output] - An array empty or otherwise to apply the values to.
+   * @description Create a transform array. Used for the Two.js rendering APIs.
+   */
+  toTransformArray: function(fullMatrix, output) {
+
+    var elements = this.elements;
+    var hasOutput = !!output;
+
+    var a = elements[0];
+    var b = elements[1];
+    var c = elements[2];
+    var d = elements[3];
+    var e = elements[4];
+    var f = elements[5];
+
+    if (fullMatrix) {
+
+      var g = elements[6];
+      var h = elements[7];
+      var i = elements[8];
+
+      if (hasOutput) {
+        output[0] = a;
+        output[1] = d;
+        output[2] = g;
+        output[3] = b;
+        output[4] = e;
+        output[5] = h;
+        output[6] = c;
+        output[7] = f;
+        output[8] = i;
+        return;
+      }
+
+      return [
+        a, d, g, b, e, h, c, f, i
+      ];
+    }
+
+    if (hasOutput) {
+      output[0] = a;
+      output[1] = d;
+      output[2] = b;
+      output[3] = e;
+      output[4] = c;
+      output[5] = f;
+      return;
+    }
+
+    return [
+      a, d, b, e, c, f  // Specific format see LN:19
+    ];
+
+  },
+
+  /**
+   * @name Two.Matrix#toArray
+   * @function
+   * @param {Boolean} [fullMatrix=false] - Return the full 9 elements of the matrix or just 6 for 2D transformations.
+   * @param {Number[]} [output] - An array empty or otherwise to apply the values to.
+   * @description Create a transform array. Used for the Two.js rendering APIs.
+   */
+  toArray: function(fullMatrix, output) {
+
+    var elements = this.elements;
+    var hasOutput = !!output;
+
+    var a = elements[0];
+    var b = elements[1];
+    var c = elements[2];
+    var d = elements[3];
+    var e = elements[4];
+    var f = elements[5];
+
+    if (fullMatrix) {
+
+      var g = elements[6];
+      var h = elements[7];
+      var i = elements[8];
+
+      if (hasOutput) {
+        output[0] = a;
+        output[1] = b;
+        output[2] = c;
+        output[3] = d;
+        output[4] = e;
+        output[5] = f;
+        output[6] = g;
+        output[7] = h;
+        output[8] = i;
+        return;
+      }
+
+      return [
+        a, b, c, d, e, f, g, h, i
+      ];
+    }
+
+    if (hasOutput) {
+      output[0] = a;
+      output[1] = b;
+      output[2] = c;
+      output[3] = d;
+      output[4] = e;
+      output[5] = f;
+      return;
+    }
+
+    return [
+      a, b, c, d, e, f
+    ];
+
+  },
+
+  /**
+   * @name Two.Matrix#toObject
+   * @function
+   * @description Create a JSON compatible object that represents information of the matrix.
+   */
+  toObject: function() {
+    return {
+      elements: this.toArray(true),
+      manual: !!this.manual
+    };
+  },
+
+  /**
+   * @name Two.Matrix#clone
+   * @function
+   * @description Clone the current matrix.
+   */
+  clone: function() {
+
+    return new Matrix().copy(this);
+
+  }
+
+});
+
+/**
+ * @name Two.Shape
+ * @class
+ * @extends Two.Events
+ * @description The foundational transformation object for the Two.js scenegraph.
+ */
+function Shape() {
+
+  /**
+   * @name Two.Shape#renderer
+   * @property {Object}
+   * @description Object access to store relevant renderer specific variables. Warning: manipulating this object can create unintended consequences.
+   * @nota-bene With the {@link Two.SvgRenderer} you can access the underlying SVG element created via `shape.renderer.elem`.
+   */
+  this.renderer = {};
+  this._renderer.flagMatrix = Shape.FlagMatrix.bind(this);
+  this.isShape = true;
+
+  /**
+   * @name Two.Shape#id
+   * @property {String} - Session specific unique identifier.
+   * @nota-bene In the {@link Two.SvgRenderer} change this to change the underlying SVG element's id too.
+   */
+  this.id = Constants.Identifier + Constants.uniqueId();
+
+  /**
+   * @name Two.Shape#classList
+   * @property {String[]}
+   * @description A list of class strings stored if imported / interpreted  from an SVG element.
+   */
+  this.classList = [];
+
+  /**
+   * @name Two.Shape#matrix
+   * @property {Two.Matrix}
+   * @description The transformation matrix of the shape.
+   * @nota-bene {@link Two.Shape#translation}, {@link Two.Shape#rotation}, {@link Two.Shape#scale}, {@link Two.Shape#skewX}, and {@link Two.Shape#skewY} apply their values to the matrix when changed. The matrix is what is sent to the renderer to be drawn.
+   */
+  this.matrix = new Matrix();
+
+  /**
+   * @name Two.Shape#translation
+   * @property {Two.Vector} - The x and y value for where the shape is placed relative to its parent.
+   */
+  this.translation = new Vector();
+
+  /**
+   * @name Two.Shape#rotation
+   * @property {Number} - The value in Number for how much the shape is rotated relative to its parent.
+   */
+  this.rotation = 0;
+
+  /**
+   * @name Two.Shape#scale
+   * @property {Number} - The value for how much the shape is scaled relative to its parent.
+   * @nota-bene This value can be replaced with a {@link Two.Vector} to do non-uniform scaling. e.g: `shape.scale = new Two.Vector(2, 1);`
+   */
+  this.scale = 1;
+
+  /**
+   * @name Two.Shape#skewX
+   * @property {Number} - The value in Number for how much the shape is skewed relative to its parent.
+   * @description Skew the shape by an angle in the x axis direction.
+   */
+  this.skewX = 0;
+
+  /**
+   * @name Two.Shape#skewY
+   * @property {Number} - The value in Number for how much the shape is skewed relative to its parent.
+   * @description Skew the shape by an angle in the y axis direction.
+   */
+  this.skewY = 0;
+
+}
+
+_.extend(Shape, {
+
+  /**
+   * @name Two.Shape.FlagMatrix
+   * @function
+   * @description Utility function used in conjunction with event handlers to update the flagMatrix of a shape.
+   */
+  FlagMatrix: function() {
+    this._flagMatrix = true;
+  },
+
+  /**
+   * @name Two.Shape.MakeObservable
+   * @function
+   * @param {Object} object - The object to make observable.
+   * @description Convenience function to apply observable qualities of a {@link Two.Shape} to any object. Handy if you'd like to extend the {@link Two.Shape} class on a custom class.
+   */
+  MakeObservable: function(object) {
+
+    var translation = {
+      enumerable: false,
+      get: function() {
+        return this._translation;
+      },
+      set: function(v) {
+        if (this._translation) {
+          this._translation.unbind(Events.Types.change, this._renderer.flagMatrix);
+        }
+        this._translation = v;
+        this._translation.bind(Events.Types.change, this._renderer.flagMatrix);
+        Shape.FlagMatrix.call(this);
+      }
+    };
+
+    Object.defineProperty(object, 'translation', translation);
+    Object.defineProperty(object, 'position', translation);
+
+    Object.defineProperty(object, 'rotation', {
+      enumerable: true,
+      get: function() {
+        return this._rotation;
+      },
+      set: function(v) {
+        this._rotation = v;
+        this._flagMatrix = true;
+      }
+    });
+
+    Object.defineProperty(object, 'scale', {
+      enumerable: true,
+      get: function() {
+        return this._scale;
+      },
+      set: function(v) {
+
+        if (this._scale instanceof Vector) {
+          this._scale.unbind(Events.Types.change, this._renderer.flagMatrix);
+        }
+
+        this._scale = v;
+
+        if (this._scale instanceof Vector) {
+          this._scale.bind(Events.Types.change, this._renderer.flagMatrix);
+        }
+
+        this._flagMatrix = true;
+        this._flagScale = true;
+
+      }
+    });
+
+    Object.defineProperty(object, 'skewX', {
+      enumerable: true,
+      get: function() {
+        return this._skewX;
+      },
+      set: function(v) {
+        this._skewX = v;
+        this._flagMatrix = true;
+      }
+    });
+
+    Object.defineProperty(object, 'skewY', {
+      enumerable: true,
+      get: function() {
+        return this._skewY;
+      },
+      set: function(v) {
+        this._skewY = v;
+        this._flagMatrix = true;
+      }
+    });
+
+    Object.defineProperty(object, 'matrix', {
+      enumerable: true,
+      get: function() {
+        return this._matrix;
+      },
+      set: function(v) {
+        this._matrix = v;
+        this._flagMatrix = true;
+      }
+    });
+
+    Object.defineProperty(object, 'id', {
+      enumerable: true,
+      get: function() {
+        return this._id;
+      },
+      set: function(v) {
+        var id = this._id;
+        if (v === this._id) {
+          return;
+        }
+        this._id = v;
+        this._flagId = true;
+        if (this.parent) {
+          delete this.parent.children.ids[id];
+          this.parent.children.ids[this._id] = this;
+        }
+      }
+    });
+
+    Object.defineProperty(object, 'className', {
+
+      enumerable: true,
+
+      get: function() {
+        return this._className;
+      },
+
+      set: function(v) {
+
+        this._flagClassName = this._className !== v;
+
+        if (this._flagClassName) {
+
+          var prev = this._className.split(/\s+?/);
+          var dest = v.split(/\s+?/);
+
+          for (var i = 0; i < prev.length; i++) {
+            var className = prev[i];
+            var index = Array.prototype.indexOf.call(this.classList, className);
+            if (index >= 0) {
+              this.classList.splice(index, 1);
+            }
+          }
+
+          this.classList = this.classList.concat(dest);
+
+        }
+
+        this._className = v;
+
+      }
+
+    });
+
+    Object.defineProperty(object, 'renderer', {
+
+      enumerable: false,
+
+      get: function() {
+        return this._renderer;
+      },
+
+      set: function(obj) {
+        this._renderer = obj;
+      }
+
+    });
+
+  }
+
+});
+
+_.extend(Shape.prototype, Events, {
+
+  constructor: Shape,
+
+  // Flags
+
+  /**
+   * @name Two.Shape#_id
+   * @private
+   * @property {Boolean} - Determines whether the id needs updating.
+   */
+  _flagId: true,
+
+  /**
+   * @name Two.Shape#_flagMatrix
+   * @private
+   * @property {Boolean} - Determines whether the matrix needs updating.
+   */
+  _flagMatrix: true,
+
+  /**
+   * @name Two.Shape#_flagScale
+   * @private
+   * @property {Boolean} - Determines whether the scale needs updating.
+   */
+  _flagScale: false,
+
+  // _flagMask: false,
+  // _flagClip: false,
+
+  /**
+   * @name Two.Shape#_flagClassName
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Group#className} need updating.
+   */
+  _flagClassName: false,
+
+  // Underlying Properties
+
+  _id: '',
+
+  /**
+   * @name Two.Shape#_translation
+   * @private
+   * @property {Two.Vector} - The translation values as a {@link Two.Vector}.
+   */
+  _translation: null,
+
+  /**
+   * @name Two.Shape#_rotation
+   * @private
+   * @property {Number} - The rotation value in Number.
+   */
+  _rotation: 0,
+
+  /**
+   * @name Two.Shape#_translation
+   * @private
+   * @property {Two.Vector} - The translation values as a {@link Two.Vector}.
+   */
+  _scale: 1,
+
+  /**
+   * @name Two.Shape#_skewX
+   * @private
+   * @property {Number} - The rotation value in Number.
+   */
+  _skewX: 0,
+
+  /**
+   * @name Two.Shape#_skewY
+   * @private
+   * @property {Number} - The rotation value in Number.
+   */
+  _skewY: 0,
+
+  /**
+   * @name Two.Shape#className
+   * @property {String} - A class to be applied to the element to be compatible with CSS styling.
+   * @nota-bene Only available for the SVG renderer.
+   */
+  _className: '',
+
+  /**
+   * @name Two.Shape#addTo
+   * @function
+   * @param {Two.Group} group - The parent the shape adds itself to.
+   * @description Convenience method to add itself to the scenegraph.
+   */
+  addTo: function(group) {
+    group.add(this);
+    return this;
+  },
+
+  /**
+   * @name Two.Shape#clone
+   * @function
+   * @param {Two.Group} [parent] - Optional argument to automatically add the shape to a scenegraph.
+   * @returns {Two.Shape}
+   * @description Create a new {@link Two.Shape} with the same values as the current shape.
+   */
+  clone: function(parent) {
+
+    var clone = new Shape();
+
+    clone.translation.copy(this.translation);
+    clone.rotation = this.rotation;
+    clone.scale = this.scale;
+    clone.skewX = this.skewX;
+    clone.skewY = this.skewY;
+
+    if (this.matrix.manual) {
+      clone.matrix.copy(this.matrix);
+    }
+
+    if (parent) {
+      parent.add(clone);
+    }
+
+    return clone._update();
+
+  },
+
+  /**
+   * @name Two.Shape#_update
+   * @function
+   * @private
+   * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+   * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+   * @nota-bene Try not to call this method more than once a frame.
+   */
+  _update: function(bubbles) {
+
+    if (!this._matrix.manual && this._flagMatrix) {
+
+      this._matrix
+        .identity()
+        .translate(this.translation.x, this.translation.y);
+
+        if (this._scale instanceof Vector) {
+          this._matrix.scale(this._scale.x, this._scale.y);
+        } else {
+          this._matrix.scale(this._scale);
+        }
+
+        this._matrix.rotate(this.rotation);
+        this._matrix.skewX(this.skewX);
+        this._matrix.skewY(this.skewY);
+    }
+
+    if (bubbles) {
+      if (this.parent && this.parent._update) {
+        this.parent._update();
+      }
+    }
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Shape#flagReset
+   * @function
+   * @private
+   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+   */
+  flagReset: function() {
+
+    this._flagId = this._flagMatrix = this._flagScale =
+      this._flagClassName = false;
+
+    return this;
+
+  }
+
+});
+
+Shape.MakeObservable(Shape.prototype);
+
+/**
+ * @name Two.Collection
+ * @class
+ * @extends Two.Events
+ * @description An `Array` like object with additional event propagation on actions. `pop`, `shift`, and `splice` trigger `removed` events. `push`, `unshift`, and `splice` with more than 2 arguments trigger 'inserted'. Finally, `sort` and `reverse` trigger `order` events.
+ */
+function Collection() {
+
+  Array.call(this);
+
+  if (arguments[0] && Array.isArray(arguments[0])) {
+    if (arguments[0].length > 0) {
+      Array.prototype.push.apply(this, arguments[0]);
+    }
+  } else if (arguments.length > 0) {
+    Array.prototype.push.apply(this, arguments);
+  }
+
+}
+
+Collection.prototype = new Array();
+
+_.extend(Collection.prototype, Events, {
+
+  constructor: Collection,
+
+  pop: function() {
+    var popped = Array.prototype.pop.apply(this, arguments);
+    this.trigger(Events.Types.remove, [popped]);
+    return popped;
+  },
+
+  shift: function() {
+    var shifted = Array.prototype.shift.apply(this, arguments);
+    this.trigger(Events.Types.remove, [shifted]);
+    return shifted;
+  },
+
+  push: function() {
+    var pushed = Array.prototype.push.apply(this, arguments);
+    this.trigger(Events.Types.insert, arguments);
+    return pushed;
+  },
+
+  unshift: function() {
+    var unshifted = Array.prototype.unshift.apply(this, arguments);
+    this.trigger(Events.Types.insert, arguments);
+    return unshifted;
+  },
+
+  splice: function() {
+    var spliced = Array.prototype.splice.apply(this, arguments);
+    var inserted;
+
+    this.trigger(Events.Types.remove, spliced);
+
+    if (arguments.length > 2) {
+      inserted = this.slice(arguments[0], arguments[0] + arguments.length - 2);
+      this.trigger(Events.Types.insert, inserted);
+      this.trigger(Events.Types.order);
+    }
+    return spliced;
+  },
+
+  sort: function() {
+    Array.prototype.sort.apply(this, arguments);
+    this.trigger(Events.Types.order);
+    return this;
+  },
+
+  reverse: function() {
+    Array.prototype.reverse.apply(this, arguments);
+    this.trigger(Events.Types.order);
+    return this;
+  },
+
+  indexOf: function() {
+    return Array.prototype.indexOf.apply(this, arguments);
+  }
+
+});
+
+/**
+ * @class
+ * @name Two.Group.Children
+ * @extends Two.Collection
+ * @description A children collection which is accesible both by index and by object `id`.
+ */
+function Children(children) {
+
+  Collection.apply(this, arguments);
+
+  Object.defineProperty(this, '_events', {
+    value : {},
+    enumerable: false
+  });
+
+  /**
+   * @name Two.Group.Children#ids
+   * @property {Object} - Map of all elements in the list keyed by `id`s.
+   */
+  this.ids = {};
+
+  this.attach(
+    Array.isArray(children) ? children : Array.prototype.slice.call(arguments)
+  );
+
+  this.on(Events.Types.insert, this.attach);
+  this.on(Events.Types.remove, this.detach);
+
+}
+
+Children.prototype = new Collection();
+
+_.extend(Children.prototype, {
+
+  constructor: Children,
+
+  /**
+   * @function
+   * @name Two.Group.Children#attach
+   * @param {Two.Shape[]} children - The objects which extend {@link Two.Shape} to be added.
+   * @description Adds elements to the `ids` map.
+   */
+  attach: function(children) {
+    for (var i = 0; i < children.length; i++) {
+      var child = children[i];
+      if (child && child.id) {
+        this.ids[child.id] = child;
+      }
+    }
+    return this;
+  },
+
+  /**
+   * @function
+   * @name Two.Group.Children#detach
+   * @param {Two.Shape[]} children - The objects which extend {@link Two.Shape} to be removed.
+   * @description Removes elements to the `ids` map.
+   */
+  detach: function(children) {
+    for (var i = 0; i < children.length; i++) {
+      delete this.ids[children[i].id];
+    }
+    return this;
+  }
+
+});
+
+// Constants
+
+var min$3 = Math.min, max$3 = Math.max;
+
+/**
+ * @name Two.Group
+ * @class
+ * @extends Two.Shape
+ * @param {Two.Shape[]} [children] - A list of objects that inherit {@link Two.Shape}. For instance, the array could be a {@link Two.Path}, {@link Two.Text}, and {@link Two.RoundedRectangle}.
+ * @description This is the primary class for grouping objects that are then drawn in Two.js. In Illustrator this is a group, in After Effects it would be a Null Object. Whichever the case, the `Two.Group` contains a transformation matrix and commands to style its children, but it by itself doesn't render to the screen.
+ * @nota-bene The {@link Two#scene} is an instance of `Two.Group`.
+ */
+function Group(children) {
+
+  Shape.call(this, true);
+
+  this._renderer.type = 'group';
+
+  /**
+   * @name Two.Group#additions
+   * @property {Two.Shape[]}
+   * @description An automatically updated list of children that need to be appended to the renderer's scenegraph.
+   */
+  this.additions = [];
+
+  /**
+   * @name Two.Group#subtractions
+   * @property {Two.Shape[]}
+   * @description An automatically updated list of children that need to be removed from the renderer's scenegraph.
+   */
+  this.subtractions = [];
+
+  /**
+   * @name Two.Group#children
+   * @property {Two.Group.Children}
+   * @description A list of all the children in the scenegraph.
+   * @nota-bene Ther order of this list indicates the order each element is rendered to the screen.
+   */
+  this.children = Array.isArray(children) ? children : Array.prototype.slice.call(arguments);
+
+}
+
+_.extend(Group, {
+
+  Children: Children,
+
+  /**
+   * @name Two.Group.InsertChildren
+   * @function
+   * @param {Two.Shape[]} children - The objects to be inserted.
+   * @description Cached method to let renderers know children have been added to a {@link Two.Group}.
+   */
+  InsertChildren: function(children) {
+    for (var i = 0; i < children.length; i++) {
+      replaceParent.call(this, children[i], this);
+    }
+  },
+
+  /**
+   * @name Two.Group.RemoveChildren
+   * @function
+   * @param {Two.Shape[]} children - The objects to be removed.
+   * @description Cached method to let renderers know children have been removed from a {@link Two.Group}.
+   */
+  RemoveChildren: function(children) {
+    for (var i = 0; i < children.length; i++) {
+      replaceParent.call(this, children[i]);
+    }
+  },
+
+  /**
+   * @name Two.Group.OrderChildren
+   * @function
+   * @description Cached method to let renderers know order has been updated on a {@link Two.Group}.
+   */
+  OrderChildren: function(children) {
+    this._flagOrder = true;
+  },
+
+  /**
+   * @name Two.Group.Properties
+   * @property {String[]} - A list of properties that are on every {@link Two.Group}.
+   */
+  Properties: [
+    'fill',
+    'stroke',
+    'linewidth',
+    'cap',
+    'join',
+    'miter',
+
+    'closed',
+    'curved',
+    'automatic'
+  ],
+
+  /**
+   * @name Two.Group.MakeObservable
+   * @function
+   * @param {Object} object - The object to make observable.
+   * @description Convenience function to apply observable qualities of a {@link Two.Group} to any object. Handy if you'd like to extend the {@link Two.Group} class on a custom class.
+   */
+  MakeObservable: function(object) {
+
+    var properties = Group.Properties;
+
+    Object.defineProperty(object, 'visible', {
+
+      enumerable: true,
+
+      get: function() {
+        return this._visible;
+      },
+
+      set: function(v) {
+        this._flagVisible = this._visible !== v || this._flagVisible;
+        this._visible = v;
+      }
+
+    });
+
+    Object.defineProperty(object, 'opacity', {
+
+      enumerable: true,
+
+      get: function() {
+        return this._opacity;
+      },
+
+      set: function(v) {
+        this._flagOpacity = this._opacity !== v || this._flagOpacity;
+        this._opacity = v;
+      }
+
+    });
+
+    Object.defineProperty(object, 'beginning', {
+
+      enumerable: true,
+
+      get: function() {
+        return this._beginning;
+      },
+
+      set: function(v) {
+        this._flagBeginning = this._beginning !== v || this._flagBeginning;
+        this._beginning = v;
+      }
+
+    });
+
+    Object.defineProperty(object, 'ending', {
+
+      enumerable: true,
+
+      get: function() {
+        return this._ending;
+      },
+
+      set: function(v) {
+        this._flagEnding = this._ending !== v || this._flagEnding;
+        this._ending = v;
+      }
+
+    });
+
+    Object.defineProperty(object, 'length', {
+
+      enumerable: true,
+
+      get: function() {
+        if (this._flagLength || this._length <= 0) {
+          this._length = 0;
+          if (!this.children) {
+            return this._length;
+          }
+          for (var i = 0; i < this.children.length; i++) {
+            var child = this.children[i];
+            this._length += child.length;
+          }
+        }
+        return this._length;
+      }
+
+    });
+
+    Shape.MakeObservable(object);
+    Group.MakeGetterSetters(object, properties);
+
+    Object.defineProperty(object, 'children', {
+
+      enumerable: true,
+
+      get: function() {
+        return this._children;
+      },
+
+      set: function(children) {
+
+        var insertChildren = Group.InsertChildren.bind(this);
+        var removeChildren = Group.RemoveChildren.bind(this);
+        var orderChildren = Group.OrderChildren.bind(this);
+
+        if (this._children) {
+          this._children.unbind();
+          if (this._children.length > 0) {
+            removeChildren(this._children);
+          }
+        }
+
+        this._children = new Children(children);
+        this._children.bind(Events.Types.insert, insertChildren);
+        this._children.bind(Events.Types.remove, removeChildren);
+        this._children.bind(Events.Types.order, orderChildren);
+
+        if (children.length > 0) {
+          insertChildren(children);
+        }
+
+      }
+
+    });
+
+    Object.defineProperty(object, 'mask', {
+
+      enumerable: true,
+
+      get: function() {
+        return this._mask;
+      },
+
+      set: function(v) {
+        this._mask = v;
+        this._flagMask = true;
+        if (!v.clip) {
+          v.clip = true;
+        }
+      }
+
+    });
+
+  },
+
+  /**
+   * @name Two.Group.MakeGetterSetters
+   * @function
+   * @param {Two.Group} group - The group to apply getters and setters.
+   * @param {Object} properties - A key / value object containing properties to inherit.
+   * @description Convenience method to apply getter / setter logic on an array of properties. Used in {@link Two.Group.MakeObservable}.
+   */
+  MakeGetterSetters: function(group, properties) {
+
+    if (!Array.isArray(properties)) {
+      properties = [properties];
+    }
+
+    _.each(properties, function(k) {
+      Group.MakeGetterSetter(group, k);
+    });
+
+  },
+
+  /**
+   * @name Two.Group.MakeGetterSetter
+   * @function
+   * @param {Two.Group} group - The group to apply getters and setters.
+   * @param {String} key - The key which will become a property on the group.
+   * @description Convenience method to apply getter / setter logic specific to how `Two.Group`s trickle down styles to their children. Used in {@link Two.Group.MakeObservable}.
+   */
+  MakeGetterSetter: function(group, key) {
+
+    var secret = '_' + key;
+
+    Object.defineProperty(group, key, {
+
+      enumerable: true,
+
+      get: function() {
+        return this[secret];
+      },
+
+      set: function(v) {
+        this[secret] = v;
+        // Trickle down styles
+        for (var i = 0; i < this.children.length; i++) {
+          var child = this.children[i];
+          child[key] = v;
+        }
+      }
+
+    });
+
+  }
+
+});
+
+_.extend(Group.prototype, Shape.prototype, {
+
+  constructor: Group,
+
+  // Flags
+  // http://en.wikipedia.org/wiki/Flag
+
+  /**
+   * @name Two.Group#_flagAdditions
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Group#additions} needs updating.
+   */
+  _flagAdditions: false,
+
+  /**
+   * @name Two.Group#_flagSubtractions
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Group#subtractions} needs updating.
+   */
+  _flagSubtractions: false,
+
+  /**
+   * @name Two.Group#_flagOrder
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Group#order} needs updating.
+   */
+  _flagOrder: false,
+
+  /**
+   * @name Two.Group#_flagVisible
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Group#visible} needs updating.
+   */
+
+  /**
+   * @name Two.Group#_flagOpacity
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Group#opacity} needs updating.
+   */
+  _flagOpacity: true,
+
+  /**
+   * @name Two.Group#_flagBeginning
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Group#beginning} needs updating.
+   */
+  _flagBeginning: false,
+
+  /**
+   * @name Two.Group#_flagEnding
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Group#ending} needs updating.
+   */
+  _flagEnding: false,
+
+  /**
+   * @name Two.Group#_flagLength
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Group#length} needs updating.
+   */
+  _flagLength: false,
+
+  /**
+   * @name Two.Group#_flagMask
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Group#mask} needs updating.
+   */
+  _flagMask: false,
+
+  // Underlying Properties
+
+  /**
+   * @name Two.Group#fill
+   * @property {(String|Two.Gradient|Two.Texture)} - The value of what all child shapes should be filled in with.
+   * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/color_value} for more information on CSS's colors as `String`.
+   */
+  _fill: '#fff',
+
+  /**
+   * @name Two.Group#stroke
+   * @property {(String|Two.Gradient|Two.Texture)} - The value of what all child shapes should be outlined in with.
+   * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/color_value} for more information on CSS's colors as `String`.
+   */
+  _stroke: '#000',
+
+  /**
+   * @name Two.Group#linewidth
+   * @property {Number} - The thickness in pixels of the stroke for all child shapes.
+   */
+  _linewidth: 1.0,
+
+  /**
+   * @name Two.Group#opacity
+   * @property {Number} - The opaqueness of all child shapes.
+   * @nota-bene Becomes multiplied by the individual child's opacity property.
+   */
+  _opacity: 1.0,
+
+  /**
+   * @name Two.Group#visible
+   * @property {Boolean} - Display the path or not.
+   * @nota-bene For {@link Two.CanvasRenderer} and {@link Two.WebGLRenderer} when set to false all updating is disabled improving performance dramatically with many objects in the scene.
+   */
+  _visible: true,
+
+  /**
+   * @name Two.Group#cap
+   * @property {String}
+   * @see {@link https://www.w3.org/TR/SVG11/painting.html#StrokeLinecapProperty}
+   */
+  _cap: 'round',
+
+  /**
+   * @name Two.Group#join
+   * @property {String}
+   * @see {@link https://www.w3.org/TR/SVG11/painting.html#StrokeLinejoinProperty}
+   */
+  _join: 'round',
+
+  /**
+   * @name Two.Group#miter
+   * @property {String}
+   * @see {@link https://www.w3.org/TR/SVG11/painting.html#StrokeMiterlimitProperty}
+   */
+  _miter: 4,
+
+  /**
+   * @name Two.Group#closed
+   * @property {Boolean} - Determines whether a final line is drawn between the final point in the `vertices` array and the first point of all child shapes.
+   */
+  _closed: true,
+
+  /**
+   * @name Two.Group#curved
+   * @property {Boolean} - When the child's path is `automatic = true` this boolean determines whether the lines between the points are curved or not.
+   */
+  _curved: false,
+
+  /**
+   * @name Two.Group#automatic
+   * @property {Boolean} - Determines whether or not Two.js should calculate curves, lines, and commands automatically for you or to let the developer manipulate them for themselves.
+   */
+  _automatic: true,
+
+  /**
+   * @name Two.Group#beginning
+   * @property {Number} - Number between zero and one to state the beginning of where the path is rendered.
+   * @description {@link Two.Group#beginning} is a percentage value that represents at what percentage into all child shapes should the renderer start drawing.
+   * @nota-bene This is great for animating in and out stroked paths in conjunction with {@link Two.Group#ending}.
+   */
+  _beginning: 0,
+
+  /**
+   * @name Two.Group#ending
+   * @property {Number} - Number between zero and one to state the ending of where the path is rendered.
+   * @description {@link Two.Group#ending} is a percentage value that represents at what percentage into all child shapes should the renderer start drawing.
+   * @nota-bene This is great for animating in and out stroked paths in conjunction with {@link Two.Group#beginning}.
+   */
+  _ending: 1.0,
+
+  /**
+   * @name Two.Group#length
+   * @property {Number} - The sum of distances between all child lengths.
+   */
+  _length: 0,
+
+  /**
+   * @name Two.Group#mask
+   * @property {Two.Shape} - The Two.js object to clip from a group's rendering.
+   */
+  _mask: null,
+
+  /**
+   * @name Two.Group#clone
+   * @function
+   * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+   * @returns {Two.Group}
+   * @description Create a new instance of {@link Two.Group} with the same properties of the current group.
+   */
+  clone: function(parent) {
+
+    // /**
+    //  * TODO: Group has a gotcha in that it's at the moment required to be bound to
+    //  * an instance of two in order to add elements correctly. This needs to
+    //  * be rethought and fixed.
+    //  */
+
+    var clone = new Group();
+    var children = this.children.map(function(child) {
+      return child.clone();
+    });
+
+    clone.add(children);
+
+    clone.opacity = this.opacity;
+
+    if (this.mask) {
+      clone.mask = this.mask;
+    }
+
+    clone.translation.copy(this.translation);
+    clone.rotation = this.rotation;
+    clone.scale = this.scale;
+    clone.className = this.className;
+
+    if (this.matrix.manual) {
+      clone.matrix.copy(this.matrix);
+    }
+
+    if (parent) {
+      parent.add(clone);
+    }
+
+    return clone._update();
+
+  },
+
+  /**
+   * @name Two.Group#toObject
+   * @function
+   * @returns {Object}
+   * @description Return a JSON compatible plain object that represents the group.
+   */
+  toObject: function() {
+
+    var result = {
+      children: [],
+      translation: this.translation.toObject(),
+      rotation: this.rotation,
+      scale: this.scale instanceof Vector ? this.scale.toObject() : this.scale,
+      opacity: this.opacity,
+      className: this.className,
+      mask: (this.mask ? this.mask.toObject() : null)
+    };
+
+    if (this.matrix.manual) {
+      result.matrix = this.matrix.toObject();
+    }
+
+    _.each(this.children, function(child, i) {
+      result.children[i] = child.toObject();
+    }, this);
+
+    return result;
+
+  },
+
+  /**
+   * @name Two.Group#corner
+   * @function
+   * @description Orient the children of the group to the upper left-hand corner of that group.
+   */
+  corner: function() {
+
+    var rect = this.getBoundingClientRect(true);
+
+    for (var i = 0; i < this.children.length; i++) {
+      var child = this.children[i];
+      child.translation.x -= rect.left;
+      child.translation.y -= rect.top;
+    }
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Group#center
+   * @function
+   * @description Orient the children of the group to the center of that group.
+   */
+  center: function() {
+
+    var rect = this.getBoundingClientRect(true);
+    var cx = rect.left + rect.width / 2 - this.translation.x;
+    var cy = rect.top + rect.height / 2 - this.translation.y;
+
+    for (var i = 0; i < this.children.length; i++) {
+      var child = this.children[i];
+      if (child.isShape) {
+        child.translation.x -= cx;
+        child.translation.y -= cy;
+      }
+    }
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Group#getById
+   * @function
+   * @description Recursively search for id. Returns the first element found.
+   * @returns {Two.Shape} - Or `null` if nothing is found.
+   */
+  getById: function (id) {
+    var found = null;
+    function search(node) {
+      if (node.id === id) {
+        return node;
+      } else if (node.children) {
+        for (var i = 0; i < node.children.length; i++) {
+          found = search(node.children[i]);
+          if (found) {
+            return found;
+          }
+        }
+      }
+      return null;
+    }
+    return search(this);
+  },
+
+  /**
+   * @name Two.Group#getByClassName
+   * @function
+   * @description Recursively search for classes. Returns an array of matching elements.
+   * @returns {Two.Shape[]} - Or empty array if nothing is found.
+   */
+  getByClassName: function(className) {
+    var found = [];
+    function search(node) {
+      if (Array.prototype.indexOf.call(node.classList, className) >= 0) {
+        found.push(node);
+      }
+      if (node.children) {
+        for (var i = 0; i < node.children.length; i++) {
+          var child = node.children[i];
+          search(child);
+        }
+      }
+      return found;
+    }
+    return search(this);
+  },
+
+  /**
+   * @name Two.Group#getByType
+   * @function
+   * @description Recursively search for children of a specific type, e.g. {@link Two.Path}. Pass a reference to this type as the param. Returns an array of matching elements.
+   * @returns {Two.Shape[]} - Empty array if nothing is found.
+   */
+  getByType: function(type) {
+    var found = [];
+    function search(node) {
+      if (node instanceof type) {
+        found.push(node);
+      }
+      if (node.children) {
+        for (var i = 0; i < node.children.length; i++) {
+          var child = node.children[i];
+          search(child);
+        }
+      }
+      return found;
+    }
+    return search(this);
+  },
+
+  /**
+   * @name Two.Group#add
+   * @function
+   * @param {Two.Shape[]} objects - An array of objects to be added. Can be also be supplied as individual arguments.
+   * @description Add objects to the group.
+   */
+  add: function(objects) {
+
+    // Allow to pass multiple objects either as array or as multiple arguments
+    // If it's an array also create copy of it in case we're getting passed
+    // a childrens array directly.
+    if (!(objects instanceof Array)) {
+      objects = Array.prototype.slice.call(arguments);
+    } else {
+      objects = objects.slice();
+    }
+
+    // Add the objects
+    for (var i = 0; i < objects.length; i++) {
+      var child = objects[i];
+      if (!(child && child.id)) {
+        continue;
+      }
+      var index = Array.prototype.indexOf.call(this.children, child);
+      if (index >= 0) {
+        this.children.splice(index, 1);
+      }
+      this.children.push(child);
+    }
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Group#add
+   * @function
+   * @param {Two.Shape[]} objects - An array of objects to be removed. Can be also removed as individual arguments.
+   * @description Remove objects from the group.
+   */
+  remove: function(objects) {
+
+    var l = arguments.length,
+      grandparent = this.parent;
+
+    // Allow to call remove without arguments
+    // This will detach the object from its own parent.
+    if (l <= 0 && grandparent) {
+      grandparent.remove(this);
+      return this;
+    }
+
+    // Allow to pass multiple objects either as array or as multiple arguments
+    // If it's an array also create copy of it in case we're getting passed
+    // a childrens array directly.
+    if (!(objects instanceof Array)) {
+      objects = Array.prototype.slice.call(arguments);
+    } else {
+      objects = objects.slice();
+    }
+
+    // Remove the objects
+    for (var i = 0; i < objects.length; i++) {
+      var object = objects[i];
+      if (!object || !this.children.ids[object.id]) {
+        continue;
+      }
+      var index = this.children.indexOf(object);
+      if (index >= 0) {
+        this.children.splice(index, 1);
+      }
+    }
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Group#getBoundingClientRect
+   * @function
+   * @param {Boolean} [shallow=false] - Describes whether to calculate off local matrix or world matrix.
+   * @returns {Object} - Returns object with top, left, right, bottom, width, height attributes.
+   * @description Return an object with top, left, right, bottom, width, and height parameters of the group.
+   */
+  getBoundingClientRect: function(shallow) {
+    var rect, matrix, a, b, c, d, tc, lc, rc, bc;
+
+    // TODO: Update this to not __always__ update. Just when it needs to.
+    this._update(true);
+
+    // Variables need to be defined here, because of nested nature of groups.
+    var left = Infinity, right = -Infinity,
+        top = Infinity, bottom = -Infinity;
+
+    var regex = /texture|gradient/i;
+
+    matrix = shallow ? this._matrix : getComputedMatrix(this);
+
+    for (var i = 0; i < this.children.length; i++) {
+
+      var child = this.children[i];
+
+      if (!child.visible || regex.test(child._renderer.type)) {
+        continue;
+      }
+
+      rect = child.getBoundingClientRect(shallow);
+
+      tc = typeof rect.top !== 'number' || _.isNaN(rect.top) || !isFinite(rect.top);
+      lc = typeof rect.left !== 'number' || _.isNaN(rect.left) || !isFinite(rect.left);
+      rc = typeof rect.right !== 'number' || _.isNaN(rect.right) || !isFinite(rect.right);
+      bc = typeof rect.bottom !== 'number' || _.isNaN(rect.bottom) || !isFinite(rect.bottom);
+
+      if (tc || lc || rc || bc) {
+        continue;
+      }
+
+      top = min$3(rect.top, top);
+      left = min$3(rect.left, left);
+      right = max$3(rect.right, right);
+      bottom = max$3(rect.bottom, bottom);
+
+    }
+
+    if (shallow) {
+
+      a = matrix.multiply(left, top, 1);
+      b = matrix.multiply(left, bottom, 1);
+      c = matrix.multiply(right, top, 1);
+      d = matrix.multiply(right, bottom, 1);
+
+      top = min$3(a.y, b.y, c.y, d.y);
+      left = min$3(a.x, b.x, c.x, d.x);
+      right = max$3(a.x, b.x, c.x, d.x);
+      bottom = max$3(a.y, b.y, c.y, d.y);
+
+    }
+
+    return {
+      top: top,
+      left: left,
+      right: right,
+      bottom: bottom,
+      width: right - left,
+      height: bottom - top
+    };
+
+  },
+
+  /**
+   * @name Two.Group#noFill
+   * @function
+   * @description Apply `noFill` method to all child shapes.
+   */
+  noFill: function() {
+    this.children.forEach(function(child) {
+      child.noFill();
+    });
+    return this;
+  },
+
+  /**
+   * @name Two.Group#noStroke
+   * @function
+   * @description Apply `noStroke` method to all child shapes.
+   */
+  noStroke: function() {
+    this.children.forEach(function(child) {
+      child.noStroke();
+    });
+    return this;
+  },
+
+  /**
+   * @name Two.Group#subdivide
+   * @function
+   * @description Apply `subdivide` method to all child shapes.
+   */
+  subdivide: function() {
+    var args = arguments;
+    this.children.forEach(function(child) {
+      child.subdivide.apply(child, args);
+    });
+    return this;
+  },
+
+  /**
+   * @name Two.Group#_update
+   * @function
+   * @private
+   * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+   * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+   * @nota-bene Try not to call this method more than once a frame.
+   */
+  _update: function() {
+
+    var i, l, child;
+
+    if (this._flagBeginning || this._flagEnding) {
+
+      var beginning = Math.min(this._beginning, this._ending);
+      var ending = Math.max(this._beginning, this._ending);
+      var length = this.length;
+      var sum = 0;
+
+      var bd = beginning * length;
+      var ed = ending * length;
+
+      for (i = 0; i < this.children.length; i++) {
+
+        child = this.children[i];
+        l = child.length;
+
+        if (bd > sum + l) {
+          child.beginning = 1;
+          child.ending = 1;
+        } else if (ed < sum) {
+          child.beginning = 0;
+          child.ending = 0;
+        } else if (bd > sum && bd < sum + l) {
+          child.beginning = (bd - sum) / l;
+          child.ending = 1;
+        } else if (ed > sum && ed < sum + l) {
+          child.beginning = 0;
+          child.ending = (ed - sum) / l;
+        } else {
+          child.beginning = 0;
+          child.ending = 1;
+        }
+
+        sum += l;
+
+      }
+
+    }
+
+    return Shape.prototype._update.apply(this, arguments);
+
+  },
+
+  /**
+   * @name Two.Group#flagReset
+   * @function
+   * @private
+   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+   */
+  flagReset: function() {
+
+    if (this._flagAdditions) {
+      this.additions.length = 0;
+      this._flagAdditions = false;
+    }
+
+    if (this._flagSubtractions) {
+      this.subtractions.length = 0;
+      this._flagSubtractions = false;
+    }
+
+    this._flagOrder = this._flagMask = this._flagOpacity =
+      this._flagBeginning = this._flagEnding = false;
+
+    Shape.prototype.flagReset.call(this);
+
+    return this;
+
+  }
+
+});
+
+Group.MakeObservable(Group.prototype);
+
+// /**
+//  * Helper function used to sync parent-child relationship within the
+//  * `Two.Group.children` object.
+//  *
+//  * Set the parent of the passed object to another object
+//  * and updates parent-child relationships
+//  * Calling with one arguments will simply remove the parenting
+//  */
+function replaceParent(child, newParent) {
+
+  var parent = child.parent;
+  var index;
+
+  if (parent === newParent) {
+    add();
+    return;
+  }
+
+  if (parent && parent.children.ids[child.id]) {
+
+    index = Array.prototype.indexOf.call(parent.children, child);
+    parent.children.splice(index, 1);
+
+    splice();
+
+  }
+
+  if (newParent) {
+    add();
+    return;
+  }
+
+  splice();
+
+  if (parent._flagAdditions && parent.additions.length === 0) {
+    parent._flagAdditions = false;
+  }
+  if (parent._flagSubtractions && parent.subtractions.length === 0) {
+    parent._flagSubtractions = false;
+  }
+
+  delete child.parent;
+
+  function add() {
+
+    if (newParent.subtractions.length > 0) {
+      index = Array.prototype.indexOf.call(newParent.subtractions, child);
+
+      if (index >= 0) {
+        newParent.subtractions.splice(index, 1);
+      }
+    }
+
+    if (newParent.additions.length > 0) {
+      index = Array.prototype.indexOf.call(newParent.additions, child);
+
+      if (index >= 0) {
+        newParent.additions.splice(index, 1);
+      }
+    }
+
+    child.parent = newParent;
+    newParent.additions.push(child);
+    newParent._flagAdditions = true;
+
+  }
+
+  function splice() {
+
+    index = Array.prototype.indexOf.call(parent.additions, child);
+
+    if (index >= 0) {
+      parent.additions.splice(index, 1);
+    }
+
+    index = Array.prototype.indexOf.call(parent.subtractions, child);
+
+    if (index < 0) {
+      parent.subtractions.push(child);
+      parent._flagSubtractions = true;
+    }
+
+  }
+
+}
+
+// Constants
+var emptyArray = [];
+var TWO_PI$5 = Math.PI * 2,
+  max$2 = Math.max,
+  min$2 = Math.min,
+  abs = Math.abs,
+  sin$4 = Math.sin,
+  cos$4 = Math.cos,
+  acos = Math.acos,
+  sqrt = Math.sqrt;
+
+// Returns true if this is a non-transforming matrix
+var isDefaultMatrix = function (m) {
+  return (m[0] == 1 && m[3] == 0 && m[1] == 0 && m[4] == 1 && m[2] == 0 && m[5] == 0);
+};
+
+var canvas = {
+
+  isHidden: /(undefined|none|transparent)/i,
+
+  alignments: {
+    left: 'start',
+    middle: 'center',
+    right: 'end'
+  },
+
+  shim: function(elem, name) {
+    elem.tagName = elem.nodeName = name || 'canvas';
+    elem.nodeType = 1;
+    elem.getAttribute = function(prop) {
+      return this[prop];
+    };
+    elem.setAttribute = function(prop, val) {
+      this[prop] = val;
+      return this;
+    };
+    return elem;
+  },
+
+  group: {
+
+    renderChild: function(child) {
+      canvas[child._renderer.type].render.call(child, this.ctx, true, this.clip);
+    },
+
+    render: function(ctx) {
+
+      if (!this._visible) {
+        return this;
+      }
+
+      this._update();
+
+      var matrix = this._matrix.elements;
+      var parent = this.parent;
+      this._renderer.opacity = this._opacity
+        * (parent && parent._renderer ? parent._renderer.opacity : 1);
+
+      var mask = this._mask;
+      // var clip = this._clip;
+
+      var defaultMatrix = isDefaultMatrix(matrix);
+      var shouldIsolate = !defaultMatrix || !!mask;
+
+      if (!this._renderer.context) {
+        this._renderer.context = {};
+      }
+
+      this._renderer.context.ctx = ctx;
+      // this._renderer.context.clip = clip;
+
+      if (shouldIsolate) {
+        ctx.save();
+        if (!defaultMatrix) {
+          ctx.transform(matrix[0], matrix[3], matrix[1],
+            matrix[4], matrix[2], matrix[5]);
+        }
+      }
+
+      if (mask) {
+        canvas[mask._renderer.type].render.call(mask, ctx, true);
+      }
+
+      if (this._opacity > 0 && this._scale !== 0) {
+        for (var i = 0; i < this.children.length; i++) {
+          var child = this.children[i];
+          canvas[child._renderer.type].render.call(child, ctx);
+        }
+      }
+
+      if (shouldIsolate) {
+        ctx.restore();
+      }
+
+      // Commented two-way functionality of clips / masks with groups and
+      // polygons. Uncomment when this bug is fixed:
+      // https://code.google.com/p/chromium/issues/detail?id=370951
+
+      // if (clip) {
+      //   ctx.clip();
+      // }
+
+      return this.flagReset();
+
+    }
+
+  },
+
+  path: {
+
+    render: function(ctx, forced, parentClipped) {
+
+      var matrix, stroke, linewidth, fill, opacity, visible, cap, join, miter,
+          closed, commands, length, last, next, prev, a, b, c, d, ux, uy, vx, vy,
+          ar, bl, br, cl, x, y, mask, clip, defaultMatrix, isOffset, dashes, po;
+
+      po = (this.parent && this.parent._renderer)
+        ? this.parent._renderer.opacity : 1;
+      mask = this._mask;
+      clip = this._clip;
+      opacity = this._opacity * (po || 1);
+      visible = this._visible;
+
+      if (!forced && (!visible || clip || opacity === 0)) {
+        return this;
+      }
+
+      this._update();
+
+      matrix = this._matrix.elements;
+      stroke = this._stroke;
+      linewidth = this._linewidth;
+      fill = this._fill;
+      cap = this._cap;
+      join = this._join;
+      miter = this._miter;
+      closed = this._closed;
+      commands = this._renderer.vertices; // Commands
+      length = commands.length;
+      last = length - 1;
+      defaultMatrix = isDefaultMatrix(matrix);
+      dashes = this.dashes;
+
+      // Transform
+      if (!defaultMatrix) {
+        ctx.save();
+        ctx.transform(matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]);
+      }
+
+      // Commented two-way functionality of clips / masks with groups and
+      // polygons. Uncomment when this bug is fixed:
+      // https://code.google.com/p/chromium/issues/detail?id=370951
+      if (mask) {
+        canvas[mask._renderer.type].render.call(mask, ctx, true);
+      }
+
+      // Styles
+      if (fill) {
+        if (typeof fill === 'string') {
+          ctx.fillStyle = fill;
+        } else {
+          canvas[fill._renderer.type].render.call(fill, ctx);
+          ctx.fillStyle = fill._renderer.effect;
+        }
+      }
+      if (stroke) {
+        if (typeof stroke === 'string') {
+          ctx.strokeStyle = stroke;
+        } else {
+          canvas[stroke._renderer.type].render.call(stroke, ctx);
+          ctx.strokeStyle = stroke._renderer.effect;
+        }
+        if (linewidth) {
+          ctx.lineWidth = linewidth;
+        }
+        if (miter) {
+          ctx.miterLimit = miter;
+        }
+        if (join) {
+          ctx.lineJoin = join;
+        }
+        if (!closed && cap) {
+          ctx.lineCap = cap;
+        }
+      }
+      if (typeof opacity === 'number') {
+        ctx.globalAlpha = opacity;
+      }
+
+      if (dashes && dashes.length > 0) {
+        ctx.lineDashOffset = dashes.offset || 0;
+        ctx.setLineDash(dashes);
+      }
+
+      ctx.beginPath();
+
+      for (var i = 0; i < commands.length; i++) {
+
+        b = commands[i];
+
+        x = b.x;
+        y = b.y;
+
+        switch (b.command) {
+
+          case Commands.close:
+            ctx.closePath();
+            break;
+
+          case Commands.arc:
+
+            var rx = b.rx;
+            var ry = b.ry;
+            var xAxisRotation = b.xAxisRotation;
+            var largeArcFlag = b.largeArcFlag;
+            var sweepFlag = b.sweepFlag;
+
+            prev = closed ? mod(i - 1, length) : max$2(i - 1, 0);
+            a = commands[prev];
+
+            var ax = a.x;
+            var ay = a.y;
+
+            canvas.renderSvgArcCommand(ctx, ax, ay, rx, ry, largeArcFlag, sweepFlag, xAxisRotation, x, y);
+            break;
+
+          case Commands.curve:
+
+            prev = closed ? mod(i - 1, length) : Math.max(i - 1, 0);
+            next = closed ? mod(i + 1, length) : Math.min(i + 1, last);
+
+            a = commands[prev];
+            c = commands[next];
+            ar = (a.controls && a.controls.right) || Vector.zero;
+            bl = (b.controls && b.controls.left) || Vector.zero;
+
+            if (a._relative) {
+              vx = (ar.x + a.x);
+              vy = (ar.y + a.y);
+            } else {
+              vx = ar.x;
+              vy = ar.y;
+            }
+
+            if (b._relative) {
+              ux = (bl.x + b.x);
+              uy = (bl.y + b.y);
+            } else {
+              ux = bl.x;
+              uy = bl.y;
+            }
+
+            ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
+
+            if (i >= last && closed) {
+
+              c = d;
+
+              br = (b.controls && b.controls.right) || Vector.zero;
+              cl = (c.controls && c.controls.left) || Vector.zero;
+
+              if (b._relative) {
+                vx = (br.x + b.x);
+                vy = (br.y + b.y);
+              } else {
+                vx = br.x;
+                vy = br.y;
+              }
+
+              if (c._relative) {
+                ux = (cl.x + c.x);
+                uy = (cl.y + c.y);
+              } else {
+                ux = cl.x;
+                uy = cl.y;
+              }
+
+              x = c.x;
+              y = c.y;
+
+              ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
+
+            }
+
+            break;
+
+          case Commands.line:
+            ctx.lineTo(x, y);
+            break;
+
+          case Commands.move:
+            d = b;
+            ctx.moveTo(x, y);
+            break;
+
+        }
+      }
+
+      // Loose ends
+
+      if (closed) {
+        ctx.closePath();
+      }
+
+      if (!clip && !parentClipped) {
+        if (!canvas.isHidden.test(fill)) {
+          isOffset = fill._renderer && fill._renderer.offset;
+          if (isOffset) {
+            ctx.save();
+            ctx.translate(
+              - fill._renderer.offset.x, - fill._renderer.offset.y);
+            ctx.scale(fill._renderer.scale.x, fill._renderer.scale.y);
+          }
+          ctx.fill();
+          if (isOffset) {
+            ctx.restore();
+          }
+        }
+        if (!canvas.isHidden.test(stroke)) {
+          isOffset = stroke._renderer && stroke._renderer.offset;
+          if (isOffset) {
+            ctx.save();
+            ctx.translate(
+              - stroke._renderer.offset.x, - stroke._renderer.offset.y);
+            ctx.scale(stroke._renderer.scale.x, stroke._renderer.scale.y);
+            ctx.lineWidth = linewidth / stroke._renderer.scale.x;
+          }
+          ctx.stroke();
+          if (isOffset) {
+            ctx.restore();
+          }
+        }
+      }
+
+      if (!defaultMatrix) {
+        ctx.restore();
+      }
+
+      if (clip && !parentClipped) {
+        ctx.clip();
+      }
+
+      if (dashes && dashes.length > 0) {
+        ctx.setLineDash(emptyArray);
+      }
+
+      return this.flagReset();
+
+    }
+
+  },
+
+  text: {
+
+    render: function(ctx, forced, parentClipped) {
+
+      var po = (this.parent && this.parent._renderer)
+        ? this.parent._renderer.opacity : 1;
+      var opacity = this._opacity * po;
+      var visible = this._visible;
+      var mask = this._mask;
+      var clip = this._clip;
+
+      if (!forced && (!visible || clip || opacity === 0)) {
+        return this;
+      }
+
+      this._update();
+
+      var matrix = this._matrix.elements;
+      var stroke = this._stroke;
+      var linewidth = this._linewidth;
+      var fill = this._fill;
+      var decoration = this._decoration;
+      var defaultMatrix = isDefaultMatrix(matrix);
+      var isOffset = fill._renderer && fill._renderer.offset
+        && stroke._renderer && stroke._renderer.offset;
+      var dashes = this.dashes;
+      var alignment = canvas.alignments[this._alignment] || this._alignment;
+      var baseline = this._baseline;
+
+      var a, b, c, d, e, sx, sy, x1, y1, x2, y2;
+
+      // Transform
+      if (!defaultMatrix) {
+        ctx.save();
+        ctx.transform(matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]);
+      }
+
+      // Commented two-way functionality of clips / masks with groups and
+      // polygons. Uncomment when this bug is fixed:
+      // https://code.google.com/p/chromium/issues/detail?id=370951
+      if (mask) {
+        canvas[mask._renderer.type].render.call(mask, ctx, true);
+      }
+
+      if (!isOffset) {
+        ctx.font = [this._style, this._weight, this._size + 'px/' +
+          this._leading + 'px', this._family].join(' ');
+      }
+
+      ctx.textAlign = alignment;
+      ctx.textBaseline = baseline;
+
+      // Styles
+      if (fill) {
+        if (typeof fill === 'string') {
+          ctx.fillStyle = fill;
+        } else {
+          canvas[fill._renderer.type].render.call(fill, ctx);
+          ctx.fillStyle = fill._renderer.effect;
+        }
+      }
+      if (stroke) {
+        if (typeof stroke === 'string') {
+          ctx.strokeStyle = stroke;
+        } else {
+          canvas[stroke._renderer.type].render.call(stroke, ctx);
+          ctx.strokeStyle = stroke._renderer.effect;
+        }
+        if (linewidth) {
+          ctx.lineWidth = linewidth;
+        }
+      }
+      if (typeof opacity === 'number') {
+        ctx.globalAlpha = opacity;
+      }
+      if (dashes && dashes.length > 0) {
+        ctx.lineDashOffset = dashes.offset || 0;
+        ctx.setLineDash(dashes);
+      }
+
+      if (!clip && !parentClipped) {
+
+        if (!canvas.isHidden.test(fill)) {
+
+          if (fill._renderer && fill._renderer.offset) {
+
+            sx = fill._renderer.scale.x;
+            sy = fill._renderer.scale.y;
+
+            ctx.save();
+            ctx.translate( - fill._renderer.offset.x,
+              - fill._renderer.offset.y);
+            ctx.scale(sx, sy);
+
+            a = this._size / fill._renderer.scale.y;
+            b = this._leading / fill._renderer.scale.y;
+            ctx.font = [this._style, this._weight, a + 'px/',
+              b + 'px', this._family].join(' ');
+
+            c = fill._renderer.offset.x / fill._renderer.scale.x;
+            d = fill._renderer.offset.y / fill._renderer.scale.y;
+
+            ctx.fillText(this.value, c, d);
+            ctx.restore();
+
+          } else {
+            ctx.fillText(this.value, 0, 0);
+          }
+
+        }
+
+        if (!canvas.isHidden.test(stroke)) {
+
+          if (stroke._renderer && stroke._renderer.offset) {
+
+            sx = stroke._renderer.scale.x;
+            sy = stroke._renderer.scale.y;
+
+            ctx.save();
+            ctx.translate(- stroke._renderer.offset.x,
+              - stroke._renderer.offset.y);
+            ctx.scale(sx, sy);
+
+            a = this._size / stroke._renderer.scale.y;
+            b = this._leading / stroke._renderer.scale.y;
+            ctx.font = [this._style, this._weight, a + 'px/',
+              b + 'px', this._family].join(' ');
+
+            c = stroke._renderer.offset.x / stroke._renderer.scale.x;
+            d = stroke._renderer.offset.y / stroke._renderer.scale.y;
+            e = linewidth / stroke._renderer.scale.x;
+
+            ctx.lineWidth = e;
+            ctx.strokeText(this.value, c, d);
+            ctx.restore();
+
+          } else {
+            ctx.strokeText(this.value, 0, 0);
+          }
+        }
+      }
+
+      // Handle text-decoration
+      if (/(underline|strikethrough)/i.test(decoration)) {
+
+        var metrics = ctx.measureText(this.value);
+        var scalar = 1;
+
+        switch (decoration) {
+          case 'underline':
+            y1 = metrics.actualBoundingBoxAscent;
+            y2 = metrics.actualBoundingBoxAscent;
+            break;
+          case 'strikethrough':
+            y1 = 0;
+            y2 = 0;
+            scalar = 0.5;
+            break;
+        }
+
+        switch (baseline) {
+          case 'top':
+            y1 += this._size * scalar;
+            y2 += this._size * scalar;
+            break;
+          case 'baseline':
+          case 'bottom':
+            y1 -= this._size * scalar;
+            y2 -= this._size * scalar;
+            break;
+        }
+
+        switch (alignment) {
+          case 'left':
+          case 'start':
+            x1 = 0;
+            x2 = metrics.width;
+            break;
+          case 'right':
+          case 'end':
+            x1 = - metrics.width;
+            x2 = 0;
+            break;
+          default:
+            x1 = - metrics.width / 2;
+            x2 = metrics.width / 2;
+        }
+
+        ctx.lineWidth = Math.max(Math.floor(this._size / 15), 1);
+        ctx.strokeStyle = ctx.fillStyle;
+
+        ctx.beginPath();
+        ctx.moveTo(x1, y1);
+        ctx.lineTo(x2, y2);
+        ctx.stroke();
+
+      }
+
+      if (!defaultMatrix) {
+        ctx.restore();
+      }
+
+      // TODO: Test for text
+      if (clip && !parentClipped) {
+        ctx.clip();
+      }
+
+      if (dashes && dashes.length > 0) {
+        ctx.setLineDash(emptyArray);
+      }
+
+      return this.flagReset();
+
+    }
+
+  },
+
+  'linear-gradient': {
+
+    render: function(ctx) {
+
+      this._update();
+
+      if (!this._renderer.effect || this._flagEndPoints || this._flagStops) {
+
+        this._renderer.effect = ctx.createLinearGradient(
+          this.left._x, this.left._y,
+          this.right._x, this.right._y
+        );
+
+        for (var i = 0; i < this.stops.length; i++) {
+          var stop = this.stops[i];
+          this._renderer.effect.addColorStop(stop._offset, stop._color);
+        }
+
+      }
+
+      return this.flagReset();
+
+    }
+
+  },
+
+  'radial-gradient': {
+
+    render: function(ctx) {
+
+      this._update();
+
+      if (!this._renderer.effect || this._flagCenter || this._flagFocal
+          || this._flagRadius || this._flagStops) {
+
+        this._renderer.effect = ctx.createRadialGradient(
+          this.center._x, this.center._y, 0,
+          this.focal._x, this.focal._y, this._radius
+        );
+
+        for (var i = 0; i < this.stops.length; i++) {
+          var stop = this.stops[i];
+          this._renderer.effect.addColorStop(stop._offset, stop._color);
+        }
+
+      }
+
+      return this.flagReset();
+
+    }
+
+  },
+
+  texture: {
+
+    render: function(ctx) {
+
+      this._update();
+
+      var image = this.image;
+
+      if (!this._renderer.effect || ((this._flagLoaded || this._flagImage || this._flagVideo || this._flagRepeat) && this.loaded)) {
+        this._renderer.effect = ctx.createPattern(this.image, this._repeat);
+      }
+
+      if (this._flagOffset || this._flagLoaded || this._flagScale) {
+
+        if (!(this._renderer.offset instanceof Vector)) {
+          this._renderer.offset = new Vector();
+        }
+
+        this._renderer.offset.x = - this._offset.x;
+        this._renderer.offset.y = - this._offset.y;
+
+        if (image) {
+
+          this._renderer.offset.x += image.width / 2;
+          this._renderer.offset.y += image.height / 2;
+
+          if (this._scale instanceof Vector) {
+            this._renderer.offset.x *= this._scale.x;
+            this._renderer.offset.y *= this._scale.y;
+          } else {
+            this._renderer.offset.x *= this._scale;
+            this._renderer.offset.y *= this._scale;
+          }
+        }
+
+      }
+
+      if (this._flagScale || this._flagLoaded) {
+
+        if (!(this._renderer.scale instanceof Vector)) {
+          this._renderer.scale = new Vector();
+        }
+
+        if (this._scale instanceof Vector) {
+          this._renderer.scale.copy(this._scale);
+        } else {
+          this._renderer.scale.set(this._scale, this._scale);
+        }
+
+      }
+
+      return this.flagReset();
+
+    }
+
+  },
+
+  renderSvgArcCommand: function(ctx, ax, ay, rx, ry, largeArcFlag, sweepFlag, xAxisRotation, x, y) {
+
+    xAxisRotation = xAxisRotation * Math.PI / 180;
+
+    // Ensure radii are positive
+    rx = abs(rx);
+    ry = abs(ry);
+
+    // Compute (x1′, y1′)
+    var dx2 = (ax - x) / 2.0;
+    var dy2 = (ay - y) / 2.0;
+    var x1p = cos$4(xAxisRotation) * dx2 + sin$4(xAxisRotation) * dy2;
+    var y1p = - sin$4(xAxisRotation) * dx2 + cos$4(xAxisRotation) * dy2;
+
+    // Compute (cx′, cy′)
+    var rxs = rx * rx;
+    var rys = ry * ry;
+    var x1ps = x1p * x1p;
+    var y1ps = y1p * y1p;
+
+    // Ensure radii are large enough
+    var cr = x1ps / rxs + y1ps / rys;
+
+    if (cr > 1) {
+
+      // scale up rx,ry equally so cr == 1
+      var s = sqrt(cr);
+      rx = s * rx;
+      ry = s * ry;
+      rxs = rx * rx;
+      rys = ry * ry;
+
+    }
+
+    var dq = (rxs * y1ps + rys * x1ps);
+    var pq = (rxs * rys - dq) / dq;
+    var q = sqrt(max$2(0, pq));
+    if (largeArcFlag === sweepFlag) q = - q;
+    var cxp = q * rx * y1p / ry;
+    var cyp = - q * ry * x1p / rx;
+
+    // Step 3: Compute (cx, cy) from (cx′, cy′)
+    var cx = cos$4(xAxisRotation) * cxp
+      - sin$4(xAxisRotation) * cyp + (ax + x) / 2;
+    var cy = sin$4(xAxisRotation) * cxp
+      + cos$4(xAxisRotation) * cyp + (ay + y) / 2;
+
+    // Step 4: Compute θ1 and Δθ
+    var startAngle = svgAngle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
+    var delta = svgAngle((x1p - cxp) / rx, (y1p - cyp) / ry,
+      (- x1p - cxp) / rx, (- y1p - cyp) / ry) % TWO_PI$5;
+
+    var endAngle = startAngle + delta;
+
+    var clockwise = sweepFlag === 0;
+
+    renderArcEstimate(ctx, cx, cy, rx, ry, startAngle, endAngle,
+      clockwise, xAxisRotation);
+
+  }
+
+};
+
+/**
+ * @name Two.CanvasRenderer
+ * @class
+ * @extends Two.Events
+ * @param {Object} [parameters] - This object is inherited when constructing a new instance of {@link Two}.
+ * @param {Element} [parameters.domElement] - The `<canvas />` to draw to. If none given a new one will be constructed.
+ * @param {Boolean} [parameters.overdraw] - Determines whether the canvas should clear the background or not. Defaults to `true`.
+ * @param {Boolean} [parameters.smoothing=true] - Determines whether the canvas should antialias drawing. Set it to `false` when working with pixel art. `false` can lead to better performance, since it would use a cheaper interpolation algorithm.
+ * @description This class is used by {@link Two} when constructing with `type` of `Two.Types.canvas`. It takes Two.js' scenegraph and renders it to a `<canvas />`.
+ */
+function Renderer$2(params) {
+
+  // It might not make a big difference on GPU backed canvases.
+  var smoothing = (params.smoothing !== false);
+
+  /**
+   * @name Two.CanvasRenderer#domElement
+   * @property {Element} - The `<canvas />` associated with the Two.js scene.
+   */
+  this.domElement = params.domElement || document.createElement('canvas');
+
+  /**
+   * @name Two.CanvasRenderer#ctx
+   * @property {Canvas2DContext} - Associated two dimensional context to render on the `<canvas />`.
+   */
+  this.ctx = this.domElement.getContext('2d');
+
+  /**
+   * @name Two.CanvasRenderer#overdraw
+   * @property {Boolean} - Determines whether the canvas clears the background each draw call.
+   * @default true
+   */
+  this.overdraw = params.overdraw || false;
+
+  if (typeof this.ctx.imageSmoothingEnabled !== 'undefined') {
+    this.ctx.imageSmoothingEnabled = smoothing;
+  }
+
+  /**
+   * @name Two.CanvasRenderer#scene
+   * @property {Two.Group} - The root group of the scenegraph.
+   */
+  this.scene = new Group();
+  this.scene.parent = this;
+}
+
+
+_.extend(Renderer$2, {
+
+  /**
+   * @name Two.CanvasRenderer.Utils
+   * @property {Object} - A massive object filled with utility functions and properties to render Two.js objects to a `<canvas />`.
+   */
+  Utils: canvas
+
+});
+
+_.extend(Renderer$2.prototype, Events, {
+
+  constructor: Renderer$2,
+
+  /**
+   * @name Two.CanvasRenderer#setSize
+   * @function
+   * @fires resize
+   * @param {Number} width - The new width of the renderer.
+   * @param {Number} height - The new height of the renderer.
+   * @param {Number} [ratio] - The new pixel ratio (pixel density) of the renderer. Defaults to calculate the pixel density of the user's screen.
+   * @description Change the size of the renderer.
+   */
+  setSize: function(width, height, ratio) {
+
+    this.width = width;
+    this.height = height;
+
+    this.ratio = typeof ratio === 'undefined' ? getRatio(this.ctx) : ratio;
+
+    this.domElement.width = width * this.ratio;
+    this.domElement.height = height * this.ratio;
+
+    if (this.domElement.style) {
+      _.extend(this.domElement.style, {
+        width: width + 'px',
+        height: height + 'px'
+      });
+    }
+
+    return this.trigger(Events.Types.resize, width, height, ratio);
+
+  },
+
+  /**
+   * @name Two.CanvasRenderer#render
+   * @function
+   * @description Render the current scene to the `<canvas />`.
+   */
+  render: function() {
+
+    var isOne = this.ratio === 1;
+
+    if (!isOne) {
+      this.ctx.save();
+      this.ctx.scale(this.ratio, this.ratio);
+    }
+
+    if (!this.overdraw) {
+      this.ctx.clearRect(0, 0, this.width, this.height);
+    }
+
+    canvas.group.render.call(this.scene, this.ctx);
+
+    if (!isOne) {
+      this.ctx.restore();
+    }
+
+    return this;
+
+  }
+
+});
+
+function renderArcEstimate(ctx, ox, oy, rx, ry, startAngle, endAngle, clockwise, xAxisRotation) {
+
+  var epsilon = Curve.Tolerance.epsilon;
+  var deltaAngle = endAngle - startAngle;
+  var samePoints = Math.abs(deltaAngle) < epsilon;
+
+  // ensures that deltaAngle is 0 .. 2 PI
+  deltaAngle = mod(deltaAngle, TWO_PI$5);
+
+  if (deltaAngle < epsilon) {
+
+    if (samePoints) {
+
+      deltaAngle = 0;
+
+    } else {
+
+      deltaAngle = TWO_PI$5;
+
+    }
+
+  }
+
+  if (clockwise === true && ! samePoints) {
+
+    if (deltaAngle === TWO_PI$5) {
+
+      deltaAngle = - TWO_PI$5;
+
+    } else {
+
+      deltaAngle = deltaAngle - TWO_PI$5;
+
+    }
+
+  }
+
+  for (var i = 0; i < Constants.Resolution; i++) {
+
+    var t = i / (Constants.Resolution - 1);
+
+    var angle = startAngle + t * deltaAngle;
+    var x = ox + rx * Math.cos(angle);
+    var y = oy + ry * Math.sin(angle);
+
+    if (xAxisRotation !== 0) {
+
+      var cos = Math.cos(xAxisRotation);
+      var sin = Math.sin(xAxisRotation);
+
+      var tx = x - ox;
+      var ty = y - oy;
+
+      // Rotate the point about the center of the ellipse.
+      x = tx * cos - ty * sin + ox;
+      y = tx * sin + ty * cos + oy;
+
+    }
+
+    ctx.lineTo(x, y);
+
+  }
+
+}
+
+function svgAngle(ux, uy, vx, vy) {
+
+  var dot = ux * vx + uy * vy;
+  var len = sqrt(ux * ux + uy * uy) *  sqrt(vx * vx + vy * vy);
+  // floating point precision, slightly over values appear
+  var ang = acos(max$2(-1, min$2(1, dot / len)));
+  if ((ux * vy - uy * vx) < 0) {
+    ang = - ang;
+  }
+
+  return ang;
+
+}
+
+var CanvasShim = {
+
+  Image: null,
+
+  isHeadless: false,
+
+  /**
+   * @name Two.Utils.shim
+   * @function
+   * @param {canvas} canvas - The instanced `Canvas` object provided by `node-canvas`.
+   * @param {Image} [Image] - The prototypical `Image` object provided by `node-canvas`. This is only necessary to pass if you're going to load bitmap imagery.
+   * @returns {canvas} Returns the instanced canvas object you passed from with additional attributes needed for Two.js.
+   * @description Convenience method for defining all the dependencies from the npm package `node-canvas`. See [node-canvas](https://github.com/Automattic/node-canvas) for additional information on setting up HTML5 `<canvas />` drawing in a node.js environment.
+   */
+  shim: function(canvas, Image) {
+    Renderer$2.Utils.shim(canvas);
+    if (typeof Image !== 'undefined') {
+      CanvasShim.Image = Image;
+    }
+    CanvasShim.isHeadless = true;
+    return canvas;
+  }
+
+};
+
+var dom = {
+
+  hasEventListeners: typeof root$1.addEventListener === 'function',
+
+  bind: function(elem, event, func, bool) {
+    if (this.hasEventListeners) {
+      elem.addEventListener(event, func, !!bool);
+    } else {
+      elem.attachEvent('on' + event, func);
+    }
+    return dom;
+  },
+
+  unbind: function(elem, event, func, bool) {
+    if (dom.hasEventListeners) {
+      elem.removeEventListeners(event, func, !!bool);
+    } else {
+      elem.detachEvent('on' + event, func);
+    }
+    return dom;
+  },
+
+  getRequestAnimationFrame: function() {
+
+    var lastTime = 0;
+    var vendors = ['ms', 'moz', 'webkit', 'o'];
+    var request = root$1.requestAnimationFrame, cancel;
+
+    if(!request) {
+      for (var i = 0; i < vendors.length; i++) {
+        request = root$1[vendors[i] + 'RequestAnimationFrame'] || request;
+        cancel = root$1[vendors[i] + 'CancelAnimationFrame']
+          || root$1[vendors[i] + 'CancelRequestAnimationFrame'] || cancel;
+      }
+
+      request = request || function(callback, element) {
+        var currTime = new Date().getTime();
+        var timeToCall = Math.max(0, 16 - (currTime - lastTime));
+        var id = root$1.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
+        lastTime = currTime + timeToCall;
+        return id;
+      };
+    }
+
+    return request;
+
+  }
+
+};
+
+var temp = (root$1.document ? root$1.document.createElement('div') : {});
+temp.id = 'help-two-load';
+
+Object.defineProperty(dom, 'temp', {
+  enumerable: true,
+  get: function() {
+    if (_.isElement(temp) && !root$1.document.head.contains(temp)) {
+      _.extend(temp.style, {
+        display: 'none'
+      });
+      root$1.document.head.appendChild(temp);
+    }
+    return temp;
+  }
+});
+
+/**
+ * @name Two.Utils.Error
+ * @class
+ * @description Custom error throwing for Two.js specific identification.
+ */
+function TwoError(message) {
+  this.name = 'Two.js';
+  this.message = message;
+}
+
+TwoError.prototype = new Error();
+
+_.extend(TwoError.prototype, {
+  constructor: TwoError
+});
+
+/**
+ * @name Two.Utils.defineGetterSetter
+ * @function
+ * @this Two#
+ * @param {String} property - The property to add an enumerable getter / setter to.
+ * @description Convenience function to setup the flag based getter / setter that most properties are defined as in Two.js.
+ */
+var defineGetterSetter = function(property) {
+
+  var object = this;
+  var secret = '_' + property;
+  var flag = '_flag' + property.charAt(0).toUpperCase() + property.slice(1);
+
+  Object.defineProperty(object, property, {
+    enumerable: true,
+    get: function() {
+      return this[secret];
+    },
+    set: function(v) {
+      this[secret] = v;
+      this[flag] = true;
+    }
+  });
+
+};
+
+/**
+ * @name Two.Registry
+ * @class
+ * @description An arbitrary class to manage a directory of things. Mainly used for keeping tabs of textures in Two.js.
+ */
+function Registry() {
+
+  this.map = {};
+
+}
+
+_.extend(Registry.prototype, {
+
+  constructor: Registry,
+
+  /**
+   * @name Two.Registry#add
+   * @function
+   * @param {String} id - A unique identifier.
+   * @param value - Any type of variable to be registered to the directory.
+   * @description Adds any value to the directory. Assigned by the `id`.
+   */
+  add: function(id, obj) {
+    this.map[id] = obj;
+    return this;
+  },
+
+  /**
+   * @name Two.Registry#remove
+   * @function
+   * @param {String} id - A unique identifier.
+   * @description Remove any value from the directory by its `id`.
+   */
+  remove: function(id) {
+    delete this.map[id];
+    return this;
+  },
+
+  /**
+   * @name Two.Registry#get
+   * @function
+   * @param {String} id - A unique identifier.
+   * @returns {?Object} The associated value. If unavailable then `undefined` is returned.
+   * @description Get a registered value by its `id`.
+   */
+  get: function(id) {
+    return this.map[id];
+  },
+
+  /**
+   * @name Two.Registry#contains
+   * @function
+   * @param {String} id - A unique identifier.
+   * @returns {Boolean}
+   * @description Convenience method to see if a value is registered to an `id` already.
+   */
+  contains: function(id) {
+    return id in this.map;
+  }
+
+});
+
+/**
+ * @name Two.Stop
+ * @class
+ * @param {Number} [offset] - The offset percentage of the stop represented as a zero-to-one value. Default value flip flops from zero-to-one as new stops are created.
+ * @param {String} [color] - The color of the stop. Default value flip flops from white to black as new stops are created.
+ * @param {Number} [opacity] - The opacity value. Default value is 1, cannot be lower than 0.
+ * @nota-bene Used specifically in conjunction with {@link Two.Gradient}s to control color graduation.
+ */
+function Stop(offset, color, opacity) {
+
+  /**
+   * @name Two.Stop#renderer
+   * @property {Object}
+   * @description Object access to store relevant renderer specific variables. Warning: manipulating this object can create unintended consequences.
+   * @nota-bene With the {@link Two.SvgRenderer} you can access the underlying SVG element created via `shape.renderer.elem`.
+   */
+  this.renderer = {};
+  this._renderer.type = 'stop';
+
+  /**
+   * @name Two.Stop#offset
+   * @property {Number} - The offset percentage of the stop represented as a zero-to-one value.
+   */
+  this.offset = typeof offset === 'number' ? offset
+    : Stop.Index <= 0 ? 0 : 1;
+
+  /**
+   * @name Two.Stop#opacity
+   * @property {Number} - The alpha percentage of the stop represented as a zero-to-one value.
+   */
+  this.opacity = typeof opacity === 'number' ? opacity : 1;
+
+  /**
+   * @name Two.Stop#color
+   * @property {String} - The color of the stop.
+   */
+  this.color = (typeof color === 'string') ? color
+    : Stop.Index <= 0 ? '#fff' : '#000';
+
+  Stop.Index = (Stop.Index + 1) % 2;
+
+}
+
+_.extend(Stop, {
+
+  /**
+   * @name Two.Stop.Index
+   * @property {Number} - The current index being referenced for calculating a stop's default offset value.
+   */
+  Index: 0,
+
+  /**
+   * @name Two.Stop.Properties
+   * @property {String[]} - A list of properties that are on every {@link Two.Stop}.
+   */
+  Properties: [
+    'offset',
+    'opacity',
+    'color'
+  ],
+
+  /**
+   * @name Two.Stop.MakeObservable
+   * @function
+   * @param {Object} object - The object to make observable.
+   * @description Convenience function to apply observable qualities of a {@link Two.Stop} to any object. Handy if you'd like to extend the {@link Two.Stop} class on a custom class.
+   */
+  MakeObservable: function(object) {
+
+    _.each(Stop.Properties, function(property) {
+
+      var object = this;
+      var secret = '_' + property;
+      var flag = '_flag' + property.charAt(0).toUpperCase() + property.slice(1);
+
+      Object.defineProperty(object, property, {
+        enumerable: true,
+        get: function() {
+          return this[secret];
+        },
+        set: function(v) {
+          this[secret] = v;
+          this[flag] = true;
+          if (this.parent) {
+            this.parent._flagStops = true;
+          }
+        }
+      });
+
+    }, object);
+
+    Object.defineProperty(object, 'renderer', {
+
+      enumerable: false,
+
+      get: function() {
+        return this._renderer;
+      },
+
+      set: function(obj) {
+        this._renderer = obj;
+      }
+
+    });
+
+  }
+
+});
+
+_.extend(Stop.prototype, Events, {
+
+  constructor: Stop,
+
+  /**
+   * @name Two.Stop#clone
+   * @function
+   * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+   * @returns {Two.Stop}
+   * @description Create a new instance of {@link Two.Stop} with the same properties of the current path.
+   */
+  clone: function() {
+
+    var clone = new Stop();
+
+    _.each(Stop.Properties, function(property) {
+      clone[property] = this[property];
+    }, this);
+
+    return clone;
+
+  },
+
+  /**
+   * @name Two.Stop#toObject
+   * @function
+   * @returns {Object}
+   * @description Return a JSON compatible plain object that represents the path.
+   */
+  toObject: function() {
+
+    var result = {};
+
+    _.each(Stop.Properties, function(k) {
+      result[k] = this[k];
+    }, this);
+
+    return result;
+
+  },
+
+  /**
+   * @name Two.Stop#flagReset
+   * @function
+   * @private
+   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+   */
+  flagReset: function() {
+
+    this._flagOffset = this._flagColor = this._flagOpacity = false;
+
+    return this;
+
+  }
+
+});
+
+Stop.MakeObservable(Stop.prototype);
+
+/**
+ * @name Two.Gradient
+ * @class
+ * @param {Two.Stop[]} [stops] - A list of {@link Two.Stop}s that contain the gradient fill pattern for the gradient.
+ * @description This is the base class for constructing different types of gradients with Two.js. The two common gradients are {@link Two.LinearGradient} and {@link Two.RadialGradient}.
+ */
+function Gradient(stops) {
+
+  /**
+   * @name Two.Gradient#renderer
+   * @property {Object}
+   * @description Object access to store relevant renderer specific variables. Warning: manipulating this object can create unintended consequences.
+   * @nota-bene With the {@link Two.SvgRenderer} you can access the underlying SVG element created via `shape.renderer.elem`.
+   */
+  this.renderer = {};
+  this._renderer.type = 'gradient';
+
+  /**
+   * @name Two.Gradient#id
+   * @property {String} - Session specific unique identifier.
+   * @nota-bene In the {@link Two.SvgRenderer} change this to change the underlying SVG element's id too.
+   */
+  this.id = Constants.Identifier + Constants.uniqueId();
+  this.classList = [];
+
+  this._renderer.flagStops = Gradient.FlagStops.bind(this);
+  this._renderer.bindStops = Gradient.BindStops.bind(this);
+  this._renderer.unbindStops = Gradient.UnbindStops.bind(this);
+
+  /**
+   * @name Two.Gradient#spread
+   * @property {String} - Indicates what happens if the gradient starts or ends inside the bounds of the target rectangle. Possible values are `'pad'`, `'reflect'`, and `'repeat'`.
+   * @see {@link https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementSpreadMethodAttribute} for more information
+   */
+  this.spread = 'pad';
+
+  /**
+   * @name Two.Gradient#stops
+   * @property {Two.Stop[]} - An ordered list of {@link Two.Stop}s for rendering the gradient.
+   */
+  if (stops) {
+    this.stops = stops;
+  }
+
+}
+
+_.extend(Gradient, {
+
+  /**
+   * @name Two.Gradient.Stop
+   * @see {@link Two.Stop}
+   */
+  Stop: Stop,
+
+  /**
+   * @name Two.Gradient.Properties
+   * @property {String[]} - A list of properties that are on every {@link Two.Gradient}.
+   */
+  Properties: [
+    'spread'
+  ],
+
+  /**
+   * @name Two.Gradient.MakeObservable
+   * @function
+   * @param {Object} object - The object to make observable.
+   * @description Convenience function to apply observable qualities of a {@link Two.Gradient} to any object. Handy if you'd like to extend the {@link Two.Gradient} class on a custom class.
+   */
+  MakeObservable: function(object) {
+
+    _.each(Gradient.Properties, defineGetterSetter, object);
+
+    Object.defineProperty(object, 'stops', {
+
+      enumerable: true,
+
+      get: function() {
+        return this._stops;
+      },
+
+      set: function(stops) {
+
+        var bindStops = this._renderer.bindStops;
+        var unbindStops = this._renderer.unbindStops;
+
+        // Remove previous listeners
+        if (this._stops) {
+          this._stops
+            .unbind(Events.Types.insert, bindStops)
+            .unbind(Events.Types.remove, unbindStops);
+        }
+
+        // Create new Collection with copy of Stops
+        this._stops = new Collection((stops || []).slice(0));
+
+        // Listen for Collection changes and bind / unbind
+        this._stops
+          .bind(Events.Types.insert, bindStops)
+          .bind(Events.Types.remove, unbindStops);
+
+        // Bind Initial Stops
+        bindStops(this._stops);
+
+      }
+
+    });
+
+    Object.defineProperty(object, 'renderer', {
+
+      enumerable: false,
+
+      get: function() {
+        return this._renderer;
+      },
+
+      set: function(obj) {
+        this._renderer = obj;
+      }
+
+    });
+
+    Object.defineProperty(object, 'id', {
+
+      enumerable: true,
+
+      get: function() {
+        return this._id;
+      },
+
+      set: function(v) {
+        this._id = v;
+      }
+
+    });
+
+  },
+
+  /**
+   * @name Two.Gradient.FlagStops
+   * @function
+   * @description Cached method to let renderers know stops have been updated on a {@link Two.Gradient}.
+   */
+  FlagStops: function() {
+    this._flagStops = true;
+  },
+
+  /**
+   * @name Two.Gradient.BindVertices
+   * @function
+   * @description Cached method to let {@link Two.Gradient} know vertices have been added to the instance.
+   */
+  BindStops: function(items) {
+
+    // This function is called a lot
+    // when importing a large SVG
+    var i = items.length;
+    while(i--) {
+      items[i].bind(Events.Types.change, this._renderer.flagStops);
+      items[i].parent = this;
+    }
+
+    this._renderer.flagStops();
+
+  },
+
+  /**
+   * @name Two.Gradient.UnbindStops
+   * @function
+   * @description Cached method to let {@link Two.Gradient} know vertices have been removed from the instance.
+   */
+  UnbindStops: function(items) {
+
+    var i = items.length;
+    while(i--) {
+      items[i].unbind(Events.Types.change, this._renderer.flagStops);
+      delete items[i].parent;
+    }
+
+    this._renderer.flagStops();
+
+  }
+
+});
+
+_.extend(Gradient.prototype, Events, {
+
+  constructor: Gradient,
+
+  /**
+   * @name Two.Gradient#_flagId
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Gradient#id} needs updating.
+   */
+  _flagId: false,
+
+  /**
+   * @name Two.Gradient#_flagStops
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Gradient#stops} needs updating.
+   */
+  _flagStops: false,
+  /**
+   * @name Two.Gradient#_flagSpread
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Gradient#spread} needs updating.
+   */
+  _flagSpread: false,
+
+  _id: '',
+
+  /**
+   * @name Two.Gradient#clone
+   * @function
+   * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+   * @returns {Two.Gradient}
+   * @description Create a new instance of {@link Two.Gradient} with the same properties of the current path.
+   */
+  clone: function(parent) {
+
+    var stops = this.stops.map(function(s) {
+      return s.clone();
+    });
+
+    var clone = new Gradient(stops);
+
+    _.each(Gradient.Properties, function(k) {
+      clone[k] = this[k];
+    }, this);
+
+    if (parent) {
+      parent.add(clone);
+    }
+
+    return clone;
+
+  },
+
+  /**
+   * @name Two.Gradient#toObject
+   * @function
+   * @returns {Object}
+   * @description Return a JSON compatible plain object that represents the path.
+   */
+  toObject: function() {
+
+    var result = {
+      stops: this.stops.map(function(s) {
+        return s.toObject();
+      })
+    };
+
+    _.each(Gradient.Properties, function(k) {
+      result[k] = this[k];
+    }, this);
+
+    return result;
+
+  },
+
+  /**
+   * @name Two.Gradient#_update
+   * @function
+   * @private
+   * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+   * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+   * @nota-bene Try not to call this method more than once a frame.
+   */
+  _update: function() {
+
+    if (this._flagSpread || this._flagStops) {
+      this.trigger(Events.Types.change);
+    }
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Gradient#flagReset
+   * @function
+   * @private
+   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+   */
+  flagReset: function() {
+
+    this._flagSpread = this._flagStops = false;
+
+    return this;
+
+  }
+
+});
+
+Gradient.MakeObservable(Gradient.prototype);
+
+/**
+ * @name Two.LinearGradient
+ * @class
+ * @extends Two.Gradient
+ * @param {Number} [x1=0] - The x position of the first end point of the linear gradient.
+ * @param {Number} [y1=0] - The y position of the first end point of the linear gradient.
+ * @param {Number} [x2=0] - The x position of the second end point of the linear gradient.
+ * @param {Number} [y2=0] - The y position of the second end point of the linear gradient.
+ * @param {Two.Stop[]} [stops] - A list of {@link Two.Stop}s that contain the gradient fill pattern for the gradient.
+ * @nota-bene The linear gradient lives within the space of the parent object's matrix space.
+ */
+function LinearGradient(x1, y1, x2, y2, stops) {
+
+  Gradient.call(this, stops);
+
+  this._renderer.type = 'linear-gradient';
+
+  var flagEndPoints = LinearGradient.FlagEndPoints.bind(this);
+
+  /**
+   * @name Two.LinearGradient#left
+   * @property {Two.Vector} - The x and y value for where the first end point is placed on the canvas.
+   */
+  this.left = new Vector().bind(Events.Types.change, flagEndPoints);
+  /**
+   * @name Two.LinearGradient#right
+   * @property {Two.Vector} - The x and y value for where the second end point is placed on the canvas.
+   */
+  this.right = new Vector().bind(Events.Types.change, flagEndPoints);
+
+  if (typeof x1 === 'number') {
+    this.left.x = x1;
+  }
+  if (typeof y1 === 'number') {
+    this.left.y = y1;
+  }
+  if (typeof x2 === 'number') {
+    this.right.x = x2;
+  }
+  if (typeof y2 === 'number') {
+    this.right.y = y2;
+  }
+
+}
+
+_.extend(LinearGradient, {
+
+  /**
+   * @name Two.LinearGradient.Stop
+   * @see {@link Two.Stop}
+   */
+  Stop: Stop,
+
+  /**
+   * @name Two.LinearGradient.MakeObservable
+   * @function
+   * @param {Object} object - The object to make observable.
+   * @description Convenience function to apply observable qualities of a {@link Two.LinearGradient} to any object. Handy if you'd like to extend the {@link Two.LinearGradient} class on a custom class.
+   */
+  MakeObservable: function(object) {
+    Gradient.MakeObservable(object);
+  },
+
+  /**
+   * @name Two.LinearGradient.FlagEndPoints
+   * @function
+   * @description Cached method to let renderers know end points have been updated on a {@link Two.LinearGradient}.
+   */
+  FlagEndPoints: function() {
+    this._flagEndPoints = true;
+  }
+
+});
+
+_.extend(LinearGradient.prototype, Gradient.prototype, {
+
+  constructor: LinearGradient,
+
+  /**
+   * @name Two.LinearGradient#_flagEndPoints
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.LinearGradient#left} or {@link Two.LinearGradient#right} changed and needs to update.
+   */
+  _flagEndPoints: false,
+
+  /**
+   * @name Two.LinearGradient#clone
+   * @function
+   * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+   * @returns {Two.Gradient}
+   * @description Create a new instance of {@link Two.LinearGradient} with the same properties of the current path.
+   */
+  clone: function(parent) {
+
+    var stops = this.stops.map(function(stop) {
+      return stop.clone();
+    });
+
+    var clone = new LinearGradient(this.left._x, this.left._y,
+      this.right._x, this.right._y, stops);
+
+    _.each(Gradient.Properties, function(k) {
+      clone[k] = this[k];
+    }, this);
+
+    if (parent) {
+      parent.add(clone);
+    }
+
+    return clone;
+
+  },
+
+  /**
+   * @name Two.LinearGradient#toObject
+   * @function
+   * @returns {Object}
+   * @description Return a JSON compatible plain object that represents the path.
+   */
+  toObject: function() {
+
+    var result = Gradient.prototype.toObject.call(this);
+
+    result.left = this.left.toObject();
+    result.right = this.right.toObject();
+
+    return result;
+
+  },
+
+  /**
+   * @name Two.LinearGradient#_update
+   * @function
+   * @private
+   * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+   * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+   * @nota-bene Try not to call this method more than once a frame.
+   */
+  _update: function() {
+
+    if (this._flagEndPoints || this._flagSpread || this._flagStops) {
+      this.trigger(Events.Types.change);
+    }
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.LinearGradient#flagReset
+   * @function
+   * @private
+   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+   */
+  flagReset: function() {
+
+    this._flagEndPoints = false;
+
+    Gradient.prototype.flagReset.call(this);
+
+    return this;
+
+  }
+
+});
+
+LinearGradient.MakeObservable(LinearGradient.prototype);
+
+/**
+ * @name Two.RadialGradient
+ * @class
+ * @extends Two.Gradient
+ * @param {Number} [x=0] - The x position of the origin of the radial gradient.
+ * @param {Number} [y=0] - The y position of the origin of the radial gradient.
+ * @param {Number} [radius=0] - The radius of the radial gradient.
+ * @param {Two.Stop[]} [stops] - A list of {@link Two.Stop}s that contain the gradient fill pattern for the gradient.
+ * @param {Number} [focalX=0] - The x position of the focal point on the radial gradient.
+ * @param {Number} [focalY=0] - The y position of the focal point on the radial gradient.
+ * @nota-bene The radial gradient lives within the space of the parent object's matrix space.
+ */
+function RadialGradient(cx, cy, r, stops, fx, fy) {
+
+  Gradient.call(this, stops);
+
+  this._renderer.type = 'radial-gradient';
+
+  /**
+   * @name Two.RadialGradient#center
+   * @property {Two.Vector} - The x and y value for where the origin of the radial gradient is.
+   */
+  this.center = new Vector()
+    .bind(Events.Types.change, (function() {
+      this._flagCenter = true;
+    }).bind(this));
+
+  this.radius = typeof r === 'number' ? r : 20;
+
+  /**
+   * @name Two.RadialGradient#focal
+   * @property {Two.Vector} - The x and y value for where the focal point of the radial gradient is.
+   * @nota-bene This effects the spray or spread of the radial gradient.
+   */
+  this.focal = new Vector()
+    .bind(Events.Types.change, (function() {
+      this._flagFocal = true;
+    }).bind(this));
+
+  if (typeof cx === 'number') {
+    this.center.x = cx;
+  }
+  if (typeof cy === 'number') {
+    this.center.y = cy;
+  }
+
+  this.focal.copy(this.center);
+
+  if (typeof fx === 'number') {
+    this.focal.x = fx;
+  }
+  if (typeof fy === 'number') {
+    this.focal.y = fy;
+  }
+
+}
+
+_.extend(RadialGradient, {
+
+  /**
+   * @name Two.RadialGradient.Stop
+   * @see {@link Two.Stop}
+   */
+  Stop: Stop,
+
+  /**
+   * @name Two.RadialGradient.Properties
+   * @property {String[]} - A list of properties that are on every {@link Two.RadialGradient}.
+   */
+  Properties: [
+    'radius'
+  ],
+
+  /**
+   * @name Two.RadialGradient.MakeObservable
+   * @function
+   * @param {Object} object - The object to make observable.
+   * @description Convenience function to apply observable qualities of a {@link Two.RadialGradient} to any object. Handy if you'd like to extend the {@link Two.RadialGradient} class on a custom class.
+   */
+  MakeObservable: function(object) {
+
+    Gradient.MakeObservable(object);
+
+    _.each(RadialGradient.Properties, defineGetterSetter, object);
+
+  }
+
+});
+
+_.extend(RadialGradient.prototype, Gradient.prototype, {
+
+  constructor: RadialGradient,
+
+  /**
+   * @name Two.RadialGradient#_flagRadius
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.RadialGradient#radius} changed and needs to update.
+   */
+  _flagRadius: false,
+  /**
+   * @name Two.RadialGradient#_flagCenter
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.RadialGradient#center} changed and needs to update.
+   */
+  _flagCenter: false,
+  /**
+   * @name Two.RadialGradient#_flagFocal
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.RadialGradient#focal} changed and needs to update.
+   */
+  _flagFocal: false,
+
+  /**
+   * @name Two.RadialGradient#clone
+   * @function
+   * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+   * @returns {Two.Gradient}
+   * @description Create a new instance of {@link Two.RadialGradient} with the same properties of the current path.
+   */
+  clone: function(parent) {
+
+    var stops = this.stops.map(function(stop) {
+      return stop.clone();
+    });
+
+    var clone = new RadialGradient(this.center._x, this.center._y,
+        this._radius, stops, this.focal._x, this.focal._y);
+
+    _.each(Gradient.Properties.concat(RadialGradient.Properties), function(k) {
+      clone[k] = this[k];
+    }, this);
+
+    if (parent) {
+      parent.add(clone);
+    }
+
+    return clone;
+
+  },
+
+  /**
+   * @name Two.RadialGradient#toObject
+   * @function
+   * @returns {Object}
+   * @description Return a JSON compatible plain object that represents the path.
+   */
+  toObject: function() {
+
+    var result = Gradient.prototype.toObject.call(this);
+
+    _.each(RadialGradient.Properties, function(k) {
+      result[k] = this[k];
+    }, this);
+
+    result.center = this.center.toObject();
+    result.focal = this.focal.toObject();
+
+    return result;
+
+  },
+
+  /**
+   * @name Two.RadialGradient#_update
+   * @function
+   * @private
+   * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+   * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+   * @nota-bene Try not to call this method more than once a frame.
+   */
+  _update: function() {
+
+    if (this._flagRadius || this._flatCenter || this._flagFocal
+      || this._flagSpread || this._flagStops) {
+      this.trigger(Events.Types.change);
+    }
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.RadialGradient#flagReset
+   * @function
+   * @private
+   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+   */
+  flagReset: function() {
+
+    this._flagRadius = this._flagCenter = this._flagFocal = false;
+
+    Gradient.prototype.flagReset.call(this);
+
+    return this;
+
+  }
+
+});
+
+RadialGradient.MakeObservable(RadialGradient.prototype);
+
+var anchor;
+var regex$1 = {
+  video: /\.(mp4|webm|ogg)$/i,
+  image: /\.(jpe?g|png|gif|tiff|webp)$/i,
+  effect: /texture|gradient/i
+};
+
+if (root$1.document) {
+  anchor = document.createElement('a');
+}
+
+/**
+ * @name Two.Texture
+ * @class
+ * @extends Two.Shape
+ * @param {String|HTMLImageElement} [src] - The URL path to an image file or an `<img />` element.
+ * @param {Function} [callback] - An optional callback function once the image has been loaded.
+ * @description Fundamental to work with bitmap data, a.k.a. pregenerated imagery, in Two.js. Supported formats include jpg, png, gif, and tiff. See {@link Two.Texture.RegularExpressions} for a full list of supported formats.
+ */
+function Texture(src, callback) {
+
+  /**
+   * @name Two.Texture#renderer
+   * @property {Object}
+   * @description Object access to store relevant renderer specific variables. Warning: manipulating this object can create unintended consequences.
+   * @nota-bene With the {@link Two.SvgRenderer} you can access the underlying SVG element created via `shape.renderer.elem`.
+   */
+  this.renderer = {};
+  this._renderer.type = 'texture';
+  this._renderer.flagOffset = Texture.FlagOffset.bind(this);
+  this._renderer.flagScale = Texture.FlagScale.bind(this);
+
+  this.id = Constants.Identifier + Constants.uniqueId();
+  this.classList = [];
+
+  /**
+   * @name Two.Texture#loaded
+   * @property {Boolean} - Shorthand value to determine if image has been loaded into the texture.
+   */
+  this.loaded = false;
+
+  /**
+   * @name Two.Texture#repeat
+   * @property {String} - CSS style declaration to tile {@link Two.Path}. Valid values include: `'no-repeat'`, `'repeat'`, `'repeat-x'`, `'repeat-y'`.
+   * @see {@link https://www.w3.org/TR/2dcontext/#dom-context-2d-createpattern}
+   */
+  this.repeat = 'no-repeat';
+
+  /**
+   * @name Two.Texture#offset
+   * @property {Two.Vector} - A two-component vector describing any pixel offset of the texture when applied to a {@link Two.Path}.
+   */
+  this.offset = new Vector();
+
+  if (typeof callback === 'function') {
+    var loaded = (function() {
+      this.unbind(Events.Types.load, loaded);
+      if (typeof callback === 'function') {
+        callback();
+      }
+    }).bind(this);
+    this.bind(Events.Types.load, loaded);
+  }
+
+  /**
+   * @name Two.Texture#src
+   * @property {String} - The URL path to the image data.
+   * @nota-bene This property is ultimately serialized in a {@link Two.Registry} to cache retrieval.
+   */
+  if (typeof src === 'string') {
+    this.src = src;
+  } else if (typeof src === 'object') {
+    var elemString = Object.prototype.toString.call(src);
+    if (
+      elemString === '[object HTMLImageElement]' ||
+      elemString === '[object HTMLCanvasElement]' ||
+      elemString === '[object HTMLVideoElement]' ||
+      elemString === '[object Image]'
+    ) {
+      /**
+       * @name Two.Texture#image
+       * @property {Element} - The corresponding DOM Element of the texture. Can be a `<img />`, `<canvas />`, or `<video />` element. See {@link Two.Texture.RegularExpressions} for a full list of supported elements.
+       * @nota-bene In headless environments this is a `Canvas.Image` object. See {@link https://github.com/Automattic/node-canvas} for more information on headless image objects.
+       */
+      this.image = src;
+    }
+  }
+
+  this._update();
+
+}
+
+_.extend(Texture, {
+
+  /**
+   * @name Two.Texture.Properties
+   * @property {String[]} - A list of properties that are on every {@link Two.Texture}.
+   */
+  Properties: [
+    'id',
+    'src',
+    'loaded',
+    'repeat'
+  ],
+
+  /**
+   * @name Two.Texture.RegularExpressions
+   * @property {Object} - A map of compatible DOM Elements categorized by media format.
+   */
+  RegularExpressions: regex$1,
+
+  /**
+   * @name Two.Texture.ImageRegistry
+   * @property {Two.Registry} - A canonical listing of image data used in a single session of Two.js.
+   * @nota-bene This object is used to cache image data between different textures.
+   */
+  ImageRegistry: new Registry(),
+
+  /**
+   * @name Two.Texture.getAbsoluteURL
+   * @property {Function} - Serializes a URL as an absolute path for canonical attribution in {@link Two.ImageRegistry}.
+   * @param {String} path
+   * @returns {String} - The serialized absolute path.
+   */
+  getAbsoluteURL: function(path) {
+    if (!anchor) {
+      // TODO: Fix for headless environments
+      return path;
+    }
+    anchor.href = path;
+    return anchor.href;
+  },
+
+  /**
+   * @name Two.Texture.loadHeadlessBuffer
+   * @property {Function} - Loads an image as a buffer in headless environments.
+   * @param {Two.Texture} texture - The {@link Two.Texture} to be loaded.
+   * @param {Function} loaded - The callback function to be triggered once the image is loaded.
+   * @nota-bene - This function uses node's `fs.readFileSync` to spoof the `<img />` loading process in the browser.
+   */
+  loadHeadlessBuffer: function(texture, loaded) {
+
+    texture.image.onload = loaded;
+    texture.image.src = texture.src;
+
+  },
+
+  /**
+   * @name Two.Texture.getTag
+   * @property {Function} - Retrieves the tag name of an image, video, or canvas node.
+   * @param {HTMLImageElement} - The image to infer the tag name from.
+   * @returns {String} - Returns the tag name of an image, video, or canvas node.
+   */
+  getTag: function(image) {
+    return (image && image.nodeName && image.nodeName.toLowerCase())
+      // Headless environments
+      || 'img';
+  },
+
+  /**
+   * @name Two.Texture.getImage
+   * @property {Function} - Convenience function to set {@link Two.Texture#image} properties with canonincal versions set in {@link Two.Texture.ImageRegistry}.
+   * @param {String} src - The URL path of the image.
+   * @returns {HTMLImageElement} - Returns either a cached version of the image or a new one that is registered in {@link Two.Texture.ImageRegistry}.
+   */
+  getImage: function(src) {
+
+    var absoluteSrc = Texture.getAbsoluteURL(src);
+
+    if (Texture.ImageRegistry.contains(absoluteSrc)) {
+      return Texture.ImageRegistry.get(absoluteSrc);
+    }
+
+    var image;
+
+    if (CanvasShim.Image) {
+
+      // TODO: Fix for headless environments
+      image = new CanvasShim.Image();
+      Renderer$2.Utils.shim(image, 'img');
+
+    } else if (root$1.document) {
+
+      if (regex$1.video.test(absoluteSrc)) {
+        image = document.createElement('video');
+      } else {
+        image = document.createElement('img');
+      }
+
+    } else {
+
+      console.warn('Two.js: no prototypical image defined for Two.Texture');
+
+    }
+
+    image.crossOrigin = 'anonymous';
+
+    return image;
+
+  },
+
+  /**
+   * @name Two.Register
+   * @interface
+   * @description A collection of functions to register different types of textures. Used internally by a {@link Two.Texture}.
+   */
+  Register: {
+    canvas: function(texture, callback) {
+      texture._src = '#' + texture.id;
+      Texture.ImageRegistry.add(texture.src, texture.image);
+      if (typeof callback === 'function') {
+        callback();
+      }
+    },
+    img: function(texture, callback) {
+
+      var image = texture.image;
+
+      var loaded = function(e) {
+        if (!CanvasShim.isHeadless && image.removeEventListener && typeof image.removeEventListener === 'function') {
+          image.removeEventListener('load', loaded, false);
+          image.removeEventListener('error', error, false);
+        }
+        if (typeof callback === 'function') {
+          callback();
+        }
+      };
+      var error = function(e) {
+        if (!CanvasShim.isHeadless && typeof image.removeEventListener === 'function') {
+          image.removeEventListener('load', loaded, false);
+          image.removeEventListener('error', error, false);
+        }
+        throw new TwoError('unable to load ' + texture.src);
+      };
+
+      if (typeof image.width === 'number' && image.width > 0
+        && typeof image.height === 'number' && image.height > 0) {
+          loaded();
+      } else if (!CanvasShim.isHeadless && typeof image.addEventListener === 'function') {
+        image.addEventListener('load', loaded, false);
+        image.addEventListener('error', error, false);
+      }
+
+      texture._src = Texture.getAbsoluteURL(texture._src);
+
+      if (!CanvasShim.isHeadless && image && image.getAttribute('two-src')) {
+        return;
+      }
+
+      if (!CanvasShim.isHeadless) {
+        image.setAttribute('two-src', texture.src);
+      }
+
+      Texture.ImageRegistry.add(texture.src, image);
+
+      if (CanvasShim.isHeadless) {
+
+        Texture.loadHeadlessBuffer(texture, loaded);
+
+      } else {
+
+        texture.image.src = texture.src;
+
+      }
+
+    },
+    video: function(texture, callback) {
+
+      if (CanvasShim.isHeadless) {
+        throw new TwoError('video textures are not implemented in headless environments.');
+      }
+
+      var loaded = function(e) {
+        texture.image.removeEventListener('canplaythrough', loaded, false);
+        texture.image.removeEventListener('error', error, false);
+        texture.image.width = texture.image.videoWidth;
+        texture.image.height = texture.image.videoHeight;
+        if (typeof callback === 'function') {
+          callback();
+        }
+      };
+      var error = function(e) {
+        texture.image.removeEventListener('canplaythrough', loaded, false);
+        texture.image.removeEventListener('error', error, false);
+        throw new TwoError('unable to load ' + texture.src);
+      };
+
+      texture._src = Texture.getAbsoluteURL(texture._src);
+
+      if (!texture.image.getAttribute('two-src')) {
+        texture.image.setAttribute('two-src', texture.src);
+        Texture.ImageRegistry.add(texture.src, texture.image);
+      }
+
+      if (texture.image.readyState >= 4) {
+        loaded();
+      } else {
+        texture.image.addEventListener('canplaythrough', loaded, false);
+        texture.image.addEventListener('error', error, false);
+        texture.image.src = texture.src;
+        texture.image.load();
+      }
+
+    }
+  },
+
+  /**
+   * @name Two.Texture.load
+   * @function
+   * @param {Two.Texture} texture - The texture to load.
+   * @param {Function} callback - The function to be called once the texture is loaded.
+   */
+  load: function(texture, callback) {
+
+    var image = texture.image;
+    var tag = Texture.getTag(image);
+
+    if (texture._flagImage) {
+      if (/canvas/i.test(tag)) {
+        Texture.Register.canvas(texture, callback);
+      } else {
+        texture._src = (!CanvasShim.isHeadless && image.getAttribute('two-src')) || image.src;
+        Texture.Register[tag](texture, callback);
+      }
+    }
+
+    if (texture._flagSrc) {
+      if (!image) {
+        image = Texture.getImage(texture.src);
+        texture.image = image;
+      }
+      tag = Texture.getTag(image);
+      Texture.Register[tag](texture, callback);
+    }
+
+  },
+
+  /**
+   * @name Two.Texture.FlagOffset
+   * @function
+   * @description Cached method to let renderers know `offset` has been updated on a {@link Two.Texture}.
+   */
+  FlagOffset: function() {
+    this._flagOffset = true;
+  },
+
+  /**
+   * @name Two.Texture.FlagScale
+   * @function
+   * @description Cached method to let renderers know `scale` has been updated on a {@link Two.Texture}.
+   */
+  FlagScale: function() {
+    this._flagScale = true;
+  },
+
+  /**
+   * @name Two.Texture.MakeObservable
+   * @function
+   * @param {Object} object - The object to make observable.
+   * @description Convenience function to apply observable qualities of a {@link Two.Texture} to any object. Handy if you'd like to extend or inherit the {@link Two.Texture} class on a custom class.
+   */
+  MakeObservable: function(object) {
+
+    _.each(Texture.Properties, defineGetterSetter, object);
+
+    Object.defineProperty(object, 'image', {
+      enumerable: true,
+      get: function() {
+        return this._image;
+      },
+      set: function(image) {
+
+        var tag = Texture.getTag(image);
+        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(Events.Types.change, this._renderer.flagOffset);
+        }
+        this._offset = v;
+        this._offset.bind(Events.Types.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 Vector) {
+          this._scale.unbind(Events.Types.change, this._renderer.flagScale);
+        }
+
+        this._scale = v;
+
+        if (this._scale instanceof Vector) {
+          this._scale.bind(Events.Types.change, this._renderer.flagScale);
+        }
+
+        this._flagScale = true;
+
+      }
+    });
+
+    Object.defineProperty(object, 'renderer', {
+
+      enumerable: false,
+
+      get: function() {
+        return this._renderer;
+      },
+
+      set: function(obj) {
+        this._renderer = obj;
+      }
+
+    });
+
+  }
+
+});
+
+_.extend(Texture.prototype, Events, Shape.prototype, {
+
+  constructor: Texture,
+
+  /**
+   * @name Two.Texture#_flagId
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Texture#id} needs updating.
+   */
+  _flagId: false,
+
+  /**
+   * @name Two.Texture#_flagSrc
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Texture#src} needs updating.
+   */
+  _flagSrc: false,
+
+  /**
+   * @name Two.Texture#_flagImage
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Texture#image} needs updating.
+   */
+  _flagImage: false,
+
+  /**
+   * @name Two.Texture#_flagVideo
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Texture#video} needs updating.
+   */
+  _flagVideo: false,
+
+  /**
+   * @name Two.Texture#_flagLoaded
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Texture#loaded} needs updating.
+   */
+  _flagLoaded: false,
+
+  /**
+   * @name Two.Texture#_flagRepeat
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Texture#repeat} needs updating.
+   */
+  _flagRepeat: false,
+
+  /**
+   * @name Two.Texture#_flagOffset
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Texture#offset} needs updating.
+   */
+  _flagOffset: false,
+
+  /**
+   * @name Two.Texture#_flagScale
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Texture#scale} needs updating.
+   */
+  _flagScale: false,
+
+  /**
+   * @name Two.Texture#_id
+   * @private
+   * @see {@link Two.Texture#id}
+   */
+  _id: '',
+
+  /**
+   * @name Two.Texture#_src
+   * @private
+   * @see {@link Two.Texture#src}
+   */
+  _src: '',
+
+  /**
+   * @name Two.Texture#_image
+   * @private
+   * @see {@link Two.Texture#image}
+   */
+  _image: null,
+
+  /**
+   * @name Two.Texture#_loaded
+   * @private
+   * @see {@link Two.Texture#loaded}
+   */
+  _loaded: false,
+
+  /**
+   * @name Two.Texture#_repeat
+   * @private
+   * @see {@link Two.Texture#repeat}
+   */
+  _repeat: 'no-repeat',
+
+  /**
+   * @name Two.Texture#_scale
+   * @private
+   * @see {@link Two.Texture#scale}
+   */
+  _scale: 1,
+
+  /**
+   * @name Two.Texture#_offset
+   * @private
+   * @see {@link Two.Texture#offset}
+   */
+  _offset: null,
+
+  /**
+   * @name Two.Texture#clone
+   * @function
+   * @returns {Two.Texture}
+   * @description Create a new instance of {@link Two.Texture} with the same properties of the current texture.
+   */
+  clone: function() {
+    var clone = new Texture(this.src);
+    clone.repeat = this.repeat;
+    clone.offset.copy(this.origin);
+    clone.scale = this.scale;
+    return clone;
+  },
+
+  /**
+   * @name Two.Texture#toObject
+   * @function
+   * @returns {Object}
+   * @description Return a JSON compatible plain object that represents the texture.
+   */
+  toObject: function() {
+    return {
+      src: this.src,
+      // image: this.image,
+      repeat: this.repeat,
+      origin: this.origin.toObject(),
+      scale: typeof this.scale === 'number' ? this.scale : this.scale.toObject()
+    };
+  },
+
+  /**
+   * @name Two.Texture#_update
+   * @function
+   * @private
+   * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+   * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+   * @nota-bene Try not to call this method more than once a frame.
+   */
+  _update: function() {
+
+    if (this._flagSrc || this._flagImage) {
+
+      this.trigger(Events.Types.change);
+
+      if (this._flagSrc || this._flagImage) {
+        this.loaded = false;
+        Texture.load(this, (function() {
+          this.loaded = true;
+          this
+            .trigger(Events.Types.change)
+            .trigger(Events.Types.load);
+        }).bind(this));
+      }
+
+    }
+
+    if (this._image && this._image.readyState >= 4) {
+      this._flagVideo = true;
+    }
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Texture#flagReset
+   * @function
+   * @private
+   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+   */
+  flagReset: function() {
+
+    this._flagSrc = this._flagImage = this._flagLoaded
+      = this._flagVideo = this._flagScale = this._flagOffset = false;
+
+    return this;
+
+  }
+
+});
+
+Texture.MakeObservable(Texture.prototype);
+
+// Constants
+
+var min$1 = Math.min, max$1 = Math.max,
+  ceil = Math.ceil, floor = Math.floor;
+
+/**
+ * @name Two.Path
+ * @class
+ * @extends Two.Shape
+ * @param {Two.Anchor[]} [vertices] - A list of {@link Two.Anchor}s that represent the order and coordinates to construct the rendered shape.
+ * @param {Boolean} [closed=false] - Describes whether the shape is closed or open.
+ * @param {Boolean} [curved=false] - Describes whether the shape automatically calculates bezier handles for each vertex.
+ * @param {Boolean} [manual=false] - Describes whether the developer controls how vertices are plotted or if Two.js automatically plots coordinates based on closed and curved booleans.
+ * @description This is the primary primitive class for creating all drawable shapes in Two.js. Unless specified methods return their instance of `Two.Path` for the purpose of chaining.
+ */
+function Path(vertices, closed, curved, manual) {
+
+  Shape.call(this);
+
+  this._renderer.type = 'path';
+  this._renderer.flagVertices = Path.FlagVertices.bind(this);
+  this._renderer.bindVertices = Path.BindVertices.bind(this);
+  this._renderer.unbindVertices = Path.UnbindVertices.bind(this);
+
+  this._renderer.flagFill = Path.FlagFill.bind(this);
+  this._renderer.flagStroke = Path.FlagStroke.bind(this);
+  this._renderer.vertices = [];
+  this._renderer.collection = [];
+
+  /**
+   * @name Two.Path#closed
+   * @property {Boolean} - Determines whether a final line is drawn between the final point in the `vertices` array and the first point.
+   */
+  this._closed = !!closed;
+
+  /**
+   * @name Two.Path#curved
+   * @property {Boolean} - When the path is `automatic = true` this boolean determines whether the lines between the points are curved or not.
+   */
+  this._curved = !!curved;
+
+  /**
+   * @name Two.Path#beginning
+   * @property {Number} - Number between zero and one to state the beginning of where the path is rendered.
+   * @description {@link Two.Path#beginning} is a percentage value that represents at what percentage into the path should the renderer start drawing.
+   * @nota-bene This is great for animating in and out stroked paths in conjunction with {@link Two.Path#ending}.
+   */
+  this.beginning = 0;
+
+  /**
+   * @name Two.Path#ending
+   * @property {Number} - Number between zero and one to state the ending of where the path is rendered.
+   * @description {@link Two.Path#ending} is a percentage value that represents at what percentage into the path should the renderer start drawing.
+   * @nota-bene This is great for animating in and out stroked paths in conjunction with {@link Two.Path#beginning}.
+   */
+  this.ending = 1;
+
+  // Style properties
+
+  /**
+   * @name Two.Path#fill
+   * @property {(String|Two.Gradient|Two.Texture)} - The value of what the path should be filled in with.
+   * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/color_value} for more information on CSS's colors as `String`.
+   */
+  this.fill = '#fff';
+
+  /**
+   * @name Two.Path#stroke
+   * @property {(String|Two.Gradient|Two.Texture)} - The value of what the path should be outlined in with.
+   * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/color_value} for more information on CSS's colors as `String`.
+   */
+  this.stroke = '#000';
+
+  /**
+   * @name Two.Path#linewidth
+   * @property {Number} - The thickness in pixels of the stroke.
+   */
+  this.linewidth = 1.0;
+
+  /**
+   * @name Two.Path#opacity
+   * @property {Number} - The opaqueness of the path.
+   * @nota-bene Can be used in conjunction with CSS Colors that have an alpha value.
+   */
+  this.opacity = 1.0;
+
+  /**
+   * @name Two.Path#className
+   * @property {String} - A class to be applied to the element to be compatible with CSS styling.
+   * @nota-bene Only available for the SVG renderer.
+   */
+  this.className = '';
+
+  /**
+   * @name Two.Path#visible
+   * @property {Boolean} - Display the path or not.
+   * @nota-bene For {@link Two.CanvasRenderer} and {@link Two.WebGLRenderer} when set to false all updating is disabled improving performance dramatically with many objects in the scene.
+   */
+  this.visible = true;
+
+  /**
+   * @name Two.Path#cap
+   * @property {String}
+   * @see {@link https://www.w3.org/TR/SVG11/painting.html#StrokeLinecapProperty}
+   */
+  this.cap = 'butt';      // Default of Adobe Illustrator
+
+  /**
+   * @name Two.Path#join
+   * @property {String}
+   * @see {@link https://www.w3.org/TR/SVG11/painting.html#StrokeLinejoinProperty}
+   */
+  this.join = 'miter';    // Default of Adobe Illustrator
+
+  /**
+   * @name Two.Path#miter
+   * @property {String}
+   * @see {@link https://www.w3.org/TR/SVG11/painting.html#StrokeMiterlimitProperty}
+   */
+  this.miter = 4;         // Default of Adobe Illustrator
+
+  /**
+   * @name Two.Path#vertices
+   * @property {Two.Anchor[]} - An ordered list of anchor points for rendering the path.
+   * @description A list of {@link Two.Anchor} objects that consist of what form the path takes.
+   * @nota-bene The array when manipulating is actually a {@link Two.Collection}.
+   */
+  this.vertices = vertices;
+
+  /**
+   * @name Two.Path#automatic
+   * @property {Boolean} - Determines whether or not Two.js should calculate curves, lines, and commands automatically for you or to let the developer manipulate them for themselves.
+   */
+  this.automatic = !manual;
+
+  /**
+   * @name Two.Path#dashes
+   * @property {Number[]} - Array of numbers. Odd indices represent dash length. Even indices represent dash space.
+   * @description A list of numbers that represent the repeated dash length and dash space applied to the stroke of the text.
+   * @see {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray} for more information on the SVG stroke-dasharray attribute.
+   */
+  this.dashes = [];
+
+  /**
+   * @name Two.Path#dashes#offset
+   * @property {Number} - A number in pixels to offset {@link Two.Path#dashes} display.
+   */
+  this.dashes.offset = 0;
+
+}
+
+_.extend(Path, {
+
+  /**
+   * @name Two.Path.Properties
+   * @property {String[]} - A list of properties that are on every {@link Two.Path}.
+   */
+  Properties: [
+    'fill',
+    'stroke',
+    'linewidth',
+    'opacity',
+    'visible',
+    'cap',
+    'join',
+    'miter',
+
+    'closed',
+    'curved',
+    'automatic',
+    'beginning',
+    'ending'
+  ],
+
+  Utils: {
+    getCurveLength: getCurveLength
+  },
+
+  /**
+   * @name Two.Path.FlagVertices
+   * @function
+   * @description Cached method to let renderers know vertices have been updated on a {@link Two.Path}.
+   */
+  FlagVertices: function() {
+    this._flagVertices = true;
+    this._flagLength = true;
+    if (this.parent) {
+      this.parent._flagLength = true;
+    }
+  },
+
+  /**
+   * @name Two.Path.BindVertices
+   * @function
+   * @description Cached method to let {@link Two.Path} know vertices have been added to the instance.
+   */
+  BindVertices: function(items) {
+
+    // This function is called a lot
+    // when importing a large SVG
+    var i = items.length;
+    while (i--) {
+      items[i].bind(Events.Types.change, this._renderer.flagVertices);
+    }
+
+    this._renderer.flagVertices();
+
+  },
+
+  /**
+   * @name Two.Path.UnbindVertices
+   * @function
+   * @description Cached method to let {@link Two.Path} know vertices have been removed from the instance.
+   */
+  UnbindVertices: function(items) {
+
+    var i = items.length;
+    while (i--) {
+      items[i].unbind(Events.Types.change, this._renderer.flagVertices);
+    }
+
+    this._renderer.flagVertices();
+
+  },
+
+  /**
+   * @name Two.Path.FlagFill
+   * @function
+   * @description Cached method to let {@link Two.Path} know the fill has changed.
+   */
+  FlagFill: function() {
+    this._flagFill = true;
+  },
+
+  /**
+   * @name Two.Path.FlagFill
+   * @function
+   * @description Cached method to let {@link Two.Path} know the stroke has changed.
+   */
+  FlagStroke: function() {
+    this._flagStroke = true;
+  },
+
+  /**
+   * @name Two.Path.MakeObservable
+   * @function
+   * @param {Object} object - The object to make observable.
+   * @description Convenience function to apply observable qualities of a {@link Two.Path} to any object. Handy if you'd like to extend the {@link Two.Path} class on a custom class.
+   */
+  MakeObservable: function(object) {
+
+    Shape.MakeObservable(object);
+
+    // Only the 7 defined properties are flagged like this. The subsequent
+    // properties behave differently and need to be hand written.
+    _.each(Path.Properties.slice(2, 8), defineGetterSetter, object);
+
+    Object.defineProperty(object, 'fill', {
+      enumerable: true,
+      get: function() {
+        return this._fill;
+      },
+      set: function(f) {
+
+        if (this._fill instanceof Gradient
+          || this._fill instanceof LinearGradient
+          || this._fill instanceof RadialGradient
+          || this._fill instanceof Texture) {
+          this._fill.unbind(Events.Types.change, this._renderer.flagFill);
+        }
+
+        this._fill = f;
+        this._flagFill = true;
+
+        if (this._fill instanceof Gradient
+          || this._fill instanceof LinearGradient
+          || this._fill instanceof RadialGradient
+          || this._fill instanceof Texture) {
+          this._fill.bind(Events.Types.change, this._renderer.flagFill);
+        }
+
+      }
+    });
+
+    Object.defineProperty(object, 'stroke', {
+      enumerable: true,
+      get: function() {
+        return this._stroke;
+      },
+      set: function(f) {
+
+        if (this._stroke instanceof Gradient
+          || this._stroke instanceof LinearGradient
+          || this._stroke instanceof RadialGradient
+          || this._stroke instanceof Texture) {
+          this._stroke.unbind(Events.Types.change, this._renderer.flagStroke);
+        }
+
+        this._stroke = f;
+        this._flagStroke = true;
+
+        if (this._stroke instanceof Gradient
+          || this._stroke instanceof LinearGradient
+          || this._stroke instanceof RadialGradient
+          || this._stroke instanceof Texture) {
+          this._stroke.bind(Events.Types.change, this._renderer.flagStroke);
+        }
+
+      }
+    });
+
+    /**
+     * @name Two.Path#length
+     * @property {Number} - The sum of distances between all {@link Two.Path#vertices}.
+     */
+    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 bindVertices = this._renderer.bindVertices;
+        var unbindVertices = this._renderer.unbindVertices;
+
+        // Remove previous listeners
+        if (this._collection) {
+          this._collection
+            .unbind(Events.Types.insert, bindVertices)
+            .unbind(Events.Types.remove, unbindVertices);
+        }
+
+        // Create new Collection with copy of vertices
+        if (vertices instanceof Collection) {
+          this._collection = vertices;
+        } else {
+          this._collection = new Collection(vertices || []);
+        }
+
+
+        // Listen for Collection changes and bind / unbind
+        this._collection
+          .bind(Events.Types.insert, bindVertices)
+          .bind(Events.Types.remove, unbindVertices);
+
+        // Bind Initial Vertices
+        bindVertices(this._collection);
+
+      }
+
+    });
+
+    /**
+     * @name Two.Path#mask
+     * @property {Two.Shape} - The shape whose alpha property becomes a clipping area for the path.
+     * @nota-bene This property is currently not working becuase of SVG spec issues found here {@link https://code.google.com/p/chromium/issues/detail?id=370951}.
+     */
+    Object.defineProperty(object, 'mask', {
+
+      enumerable: true,
+
+      get: function() {
+        return this._mask;
+      },
+
+      set: function(v) {
+        this._mask = v;
+        this._flagMask = true;
+        if (!v.clip) {
+          v.clip = true;
+        }
+      }
+
+    });
+
+    /**
+     * @name Two.Path#clip
+     * @property {Boolean} - Tells Two.js renderer if this object represents a mask for another object (or not).
+     */
+    Object.defineProperty(object, 'clip', {
+      enumerable: true,
+      get: function() {
+        return this._clip;
+      },
+      set: function(v) {
+        this._clip = v;
+        this._flagClip = true;
+      }
+    });
+
+    Object.defineProperty(object, 'dashes', {
+      enumerable: true,
+      get: function() {
+        return this._dashes;
+      },
+      set: function(v) {
+        if (typeof v.offset !== 'number') {
+          v.offset = this._dashes.offset || 0;
+        }
+        this._dashes = v;
+      }
+    });
+
+  }
+
+});
+
+_.extend(Path.prototype, Shape.prototype, {
+
+  constructor: Path,
+
+  // Flags
+  // http://en.wikipedia.org/wiki/Flag
+
+  /**
+   * @name Two.Path#_flagVertices
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Path#vertices} need updating.
+   */
+  _flagVertices: true,
+
+  /**
+   * @name Two.Path#_flagLength
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Path#length} needs updating.
+   */
+  _flagLength: true,
+
+  /**
+   * @name Two.Path#_flagFill
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Path#fill} needs updating.
+   */
+  _flagFill: true,
+
+  /**
+   * @name Two.Path#_flagStroke
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Path#stroke} needs updating.
+   */
+  _flagStroke: true,
+
+  /**
+   * @name Two.Path#_flagLinewidth
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Path#linewidth} needs updating.
+   */
+  _flagLinewidth: true,
+
+  /**
+   * @name Two.Path#_flagOpacity
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Path#opacity} needs updating.
+   */
+  _flagOpacity: true,
+
+  /**
+   * @name Two.Path#_flagVisible
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Path#visible} needs updating.
+   */
+  _flagVisible: true,
+
+  /**
+   * @name Two.Path#_flagCap
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Path#cap} needs updating.
+   */
+  _flagCap: true,
+
+  /**
+   * @name Two.Path#_flagJoin
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Path#join} needs updating.
+   */
+  _flagJoin: true,
+
+  /**
+   * @name Two.Path#_flagMiter
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Path#miter} needs updating.
+   */
+  _flagMiter: true,
+
+  /**
+   * @name Two.Path#_flagMask
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Path#mask} needs updating.
+   */
+  _flagMask: false,
+
+  /**
+   * @name Two.Path#_flagClip
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Path#clip} needs updating.
+   */
+  _flagClip: false,
+
+  // Underlying Properties
+
+  /**
+   * @name Two.Path#_length
+   * @private
+   * @see {@link Two.Path#length}
+   */
+  _length: 0,
+
+  /**
+   * @name Two.Path#_fill
+   * @private
+   * @see {@link Two.Path#fill}
+   */
+  _fill: '#fff',
+
+  /**
+   * @name Two.Path#_stroke
+   * @private
+   * @see {@link Two.Path#stroke}
+   */
+  _stroke: '#000',
+
+  /**
+   * @name Two.Path#_linewidth
+   * @private
+   * @see {@link Two.Path#linewidth}
+   */
+  _linewidth: 1.0,
+
+  /**
+   * @name Two.Path#_opacity
+   * @private
+   * @see {@link Two.Path#opacity}
+   */
+  _opacity: 1.0,
+
+  /**
+   * @name Two.Path#_visible
+   * @private
+   * @see {@link Two.Path#visible}
+   */
+  _visible: true,
+
+  /**
+   * @name Two.Path#_cap
+   * @private
+   * @see {@link Two.Path#cap}
+   */
+  _cap: 'round',
+
+  /**
+   * @name Two.Path#_join
+   * @private
+   * @see {@link Two.Path#join}
+   */
+  _join: 'round',
+
+  /**
+   * @name Two.Path#_miter
+   * @private
+   * @see {@link Two.Path#miter}
+   */
+  _miter: 4,
+
+  /**
+   * @name Two.Path#_closed
+   * @private
+   * @see {@link Two.Path#closed}
+   */
+  _closed: true,
+
+  /**
+   * @name Two.Path#_curved
+   * @private
+   * @see {@link Two.Path#curved}
+   */
+  _curved: false,
+
+  /**
+   * @name Two.Path#_automatic
+   * @private
+   * @see {@link Two.Path#automatic}
+   */
+  _automatic: true,
+
+  /**
+   * @name Two.Path#_beginning
+   * @private
+   * @see {@link Two.Path#beginning}
+   */
+  _beginning: 0,
+
+  /**
+   * @name Two.Path#_ending
+   * @private
+   * @see {@link Two.Path#ending}
+   */
+  _ending: 1.0,
+
+  /**
+   * @name Two.Path#_mask
+   * @private
+   * @see {@link Two.Path#mask}
+   */
+  _mask: null,
+
+  /**
+   * @name Two.Path#_clip
+   * @private
+   * @see {@link Two.Path#clip}
+   */
+  _clip: false,
+
+  /**
+   * @name Two.Path#_dashes
+   * @private
+   * @see {@link Two.Path#dashes}
+   */
+  _dashes: [],
+
+  /**
+   * @name Two.Path#clone
+   * @function
+   * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+   * @returns {Two.Path}
+   * @description Create a new instance of {@link Two.Path} with the same properties of the current path.
+   */
+  clone: function(parent) {
+
+    var clone = new Path();
+
+    for (var j = 0; j < this.vertices.length; j++) {
+      clone.vertices.push(this.vertices[j].clone());
+    }
+
+    for (var i = 0; i < Path.Properties.length; i++) {
+      var k = Path.Properties[i];
+      clone[k] = this[k];
+    }
+
+    clone.className = this.className;
+
+    clone.translation.copy(this.translation);
+    clone.rotation = this.rotation;
+    clone.scale = this.scale;
+    clone.skewX = this.skewX;
+    clone.skewY = this.skewY;
+
+    if (this.matrix.manual) {
+      clone.matrix.copy(this.matrix);
+    }
+
+    if (parent) {
+      parent.add(clone);
+    }
+
+    return clone._update();
+
+  },
+
+  /**
+   * @name Two.Path#toObject
+   * @function
+   * @returns {Object}
+   * @description Return a JSON compatible plain object that represents the path.
+   */
+  toObject: function() {
+
+    var result = {
+      vertices: this.vertices.map(function(v) {
+        return v.toObject();
+      })
+    };
+
+    _.each(Path.Properties, function(k) {
+      result[k] = this[k];
+    }, this);
+
+    result.className = this.className;
+
+    result.translation = this.translation.toObject();
+    result.rotation = this.rotation;
+    result.scale = this.scale instanceof Vector ? this.scale.toObject() : this.scale;
+    result.skewX = this.skewX;
+    result.skewY = this.skewY;
+
+    if (this.matrix.manual) {
+      result.matrix = this.matrix.toObject();
+    }
+
+    return result;
+
+  },
+
+  /**
+   * @name Two.Path#noFill
+   * @function
+   * @description Short hand method to set fill to `transparent`.
+   */
+  noFill: function() {
+    this.fill = 'transparent';
+    return this;
+  },
+
+  /**
+   * @name Two.Path#noStroke
+   * @function
+   * @description Short hand method to set stroke to `transparent`.
+   */
+  noStroke: function() {
+    this.stroke = undefined;
+    return this;
+  },
+
+  /**
+   * @name Two.Path#corner
+   * @function
+   * @description Orient the vertices of the shape to the upper left-hand corner of the path.
+   */
+  corner: function() {
+
+    var rect = this.getBoundingClientRect(true);
+    var hw = rect.width / 2;
+    var hh = rect.height / 2;
+    var cx = rect.left + rect.width / 2;
+    var cy = rect.top + rect.height / 2;
+
+    for (var i = 0; i < this.vertices.length; i++) {
+      var v = this.vertices[i];
+      v.x -= cx;
+      v.y -= cy;
+      v.x += hw;
+      v.y += hh;
+    }
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Path#center
+   * @function
+   * @description Orient the vertices of the shape to the center of the path.
+   */
+  center: function() {
+
+    var rect = this.getBoundingClientRect(true);
+
+    var cx = rect.left + rect.width / 2 - this.translation.x;
+    var cy = rect.top + rect.height / 2 - this.translation.y;
+
+    for (var i = 0; i < this.vertices.length; i++) {
+      var v = this.vertices[i];
+      v.x -= cx;
+      v.y -= cy;
+    }
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Path#remove
+   * @function
+   * @description Remove self from the scene / parent.
+   */
+  remove: function() {
+
+    if (!this.parent) {
+      return this;
+    }
+
+    this.parent.remove(this);
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Path#getBoundingClientRect
+   * @function
+   * @param {Boolean} [shallow=false] - Describes whether to calculate off local matrix or world matrix.
+   * @returns {Object} - Returns object with top, left, right, bottom, width, height attributes.
+   * @description Return an object with top, left, right, bottom, width, and height parameters of the path.
+   */
+  getBoundingClientRect: function(shallow) {
+    var matrix, border, l, i, v0, v1, c0x, c0y, c1x, c1y, a, b, c, d;
+
+    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 || 0) / 2;
+    l = this._renderer.vertices.length;
+
+    if (l <= 0) {
+      return {
+        width: 0,
+        height: 0
+      };
+    }
+
+    for (i = 0; i < l; i++) {
+
+      v1 = this._renderer.vertices[i];
+      // If i = 0, then this "wraps around" to the last vertex. Otherwise, it's the previous vertex.
+      // This is important for handling cyclic paths.
+      v0 = this._renderer.vertices[(i + l - 1) % l];
+
+      if (v0.controls && v1.controls) {
+
+        c0x = v0.controls.right.x;
+        c0y = v0.controls.right.y;
+
+        if (v0.relative) {
+          c0x += v0.x;
+          c0y += v0.y;
+        }
+
+        c1x = v1.controls.left.x;
+        c1y = v1.controls.left.y;
+
+        if (v1.relative) {
+          c1x += v1.x;
+          c1y += v1.y;
+        }
+
+        var bb = getCurveBoundingBox(v0.x, v0.y,
+          c0x, c0y, c1x, c1y, v1.x, v1.y);
+
+        top = min$1(bb.min.y - border, top);
+        left = min$1(bb.min.x - border, left);
+        right = max$1(bb.max.x + border, right);
+        bottom = max$1(bb.max.y + border, bottom);
+
+      } else {
+
+        if (i <= 1) {
+
+          top = min$1(v0.y - border, top);
+          left = min$1(v0.x - border, left);
+          right = max$1(v0.x + border, right);
+          bottom = max$1(v0.y + border, bottom);
+
+        }
+
+        top = min$1(v1.y - border, top);
+        left = min$1(v1.x - border, left);
+        right = max$1(v1.x + border, right);
+        bottom = max$1(v1.y + border, bottom);
+
+      }
+
+    }
+
+    a = matrix.multiply(left, top, 1);
+    b = matrix.multiply(left, bottom, 1);
+    c = matrix.multiply(right, top, 1);
+    d = matrix.multiply(right, bottom, 1);
+
+    top = min$1(a.y, b.y, c.y, d.y);
+    left = min$1(a.x, b.x, c.x, d.x);
+    right = max$1(a.x, b.x, c.x, d.x);
+    bottom = max$1(a.y, b.y, c.y, d.y);
+
+    return {
+      top: top,
+      left: left,
+      right: right,
+      bottom: bottom,
+      width: right - left,
+      height: bottom - top
+    };
+
+  },
+
+  /**
+   * @name Two.Path#getPointAt
+   * @function
+   * @param {Boolean} t - Percentage value describing where on the Two.Path to estimate and assign coordinate values.
+   * @param {Two.Vector} [obj=undefined] - Object to apply calculated x, y to. If none available returns new Object.
+   * @returns {Object}
+   * @description 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, result;
+    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 = mod(i, length);
+          ib = 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];
+        } else {
+          t = 0;
+        }
+
+        break;
+
+      }
+
+      sum += this._lengths[i];
+
+    }
+
+    if (a === null || b === null) {
+      return null;
+    }
+
+    if (!a) {
+      return b;
+    } else if (!b) {
+      return a;
+    }
+
+    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 = getComponentOnCubicBezier(t, x1, x2, x3, x4);
+    y = getComponentOnCubicBezier(t, y1, y2, y3, y4);
+
+    // Higher order points for control calculation.
+    var t1x = lerp(x1, x2, t);
+    var t1y = lerp(y1, y2, t);
+    var t2x = lerp(x2, x3, t);
+    var t2y = lerp(y2, y3, t);
+    var t3x = lerp(x3, x4, t);
+    var t3y = lerp(y3, y4, t);
+
+    // Calculate the returned points control points.
+    var brx = lerp(t1x, t2x, t);
+    var bry = lerp(t1y, t2y, t);
+    var alx = lerp(t2x, t3x, t);
+    var aly = lerp(t2y, t3y, t);
+
+    if (_.isObject(obj)) {
+
+      obj.x = x;
+      obj.y = y;
+
+      if (!_.isObject(obj.controls)) {
+        Anchor.AppendCurveProperties(obj);
+      }
+
+      obj.controls.left.x = brx;
+      obj.controls.left.y = bry;
+      obj.controls.right.x = alx;
+      obj.controls.right.y = aly;
+
+      if (!typeof obj.relative === 'boolean' || obj.relative) {
+        obj.controls.left.x -= x;
+        obj.controls.left.y -= y;
+        obj.controls.right.x -= x;
+        obj.controls.right.y -= y;
+      }
+
+      obj.t = t;
+
+      return obj;
+
+    }
+
+    result = new Anchor(
+      x, y, brx - x, bry - y, alx - x, aly - y,
+      this._curved ? Commands.curve : Commands.line
+    );
+
+    result.t = t;
+
+    return result;
+
+  },
+
+  /**
+   * @name Two.Path#plot
+   * @function
+   * @description Based on closed / curved and sorting of vertices plot where all points should be and where the respective handles should be too.
+   * @nota-bene While this method is public it is internally called by {@link Two.Path#_update} when `automatic = true`.
+   */
+  plot: function() {
+
+    if (this.curved) {
+      getCurveFromPoints(this._collection, this.closed);
+      return this;
+    }
+
+    for (var i = 0; i < this._collection.length; i++) {
+      this._collection[i].command = i === 0 ? Commands.move : Commands.line;
+    }
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Path#subdivide
+   * @function
+   * @param {Number} limit - How many times to recurse subdivisions.
+   * @description Insert a {@link Two.Anchor} at the midpoint between every item in {@link Two.Path#vertices}.
+   */
+  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 === Commands.close;
+    var points = [];
+    _.each(this.vertices, function(a, i) {
+
+      if (i <= 0 && !closed) {
+        b = a;
+        return;
+      }
+
+      if (a.command === Commands.move) {
+        points.push(new Anchor(b.x, b.y));
+        if (i > 0) {
+          points[points.length - 1].command = 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 === Commands.move) {
+          v.command = Commands.move;
+        } else {
+          v.command = 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 === Commands.move) {
+              v.command = Commands.move;
+            } else {
+              v.command = Commands.line;
+            }
+          });
+
+        } else if (closed) {
+          points.push(new Anchor(a.x, a.y));
+        }
+
+        points[points.length - 1].command = closed
+          ? Commands.close : Commands.line;
+
+      }
+
+      b = a;
+
+    }, this);
+
+    this._automatic = false;
+    this._curved = false;
+    this.vertices = points;
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Path#_updateLength
+   * @function
+   * @private
+   * @param {Number} [limit=] -
+   * @param {Boolean} [silent=false] - If set to `true` then the path isn't updated before calculation. Useful for internal use.
+   * @description Recalculate the {@link Two.Path#length} value.
+   */
+  _updateLength: function(limit, silent) {
+    //TODO: DRYness (function above)
+    if (!silent) {
+      this._update();
+    }
+
+    var length = this.vertices.length;
+    var last = length - 1;
+    var b = this.vertices[last];
+    var closed = false;//this._closed || this.vertices[last]._command === Commands.close;
+    var sum = 0;
+
+    if (typeof this._lengths === 'undefined') {
+      this._lengths = [];
+    }
+
+    _.each(this.vertices, function(a, i) {
+
+      if ((i <= 0 && !closed) || a.command === 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;
+    this._flagLength = false;
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Path#_update
+   * @function
+   * @private
+   * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+   * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+   * @nota-bene Try not to call this method more than once a frame.
+   */
+  _update: function() {
+
+    if (this._flagVertices) {
+
+      if (this._automatic) {
+        this.plot();
+      }
+
+      if (this._flagLength) {
+        this._updateLength(undefined, true);
+      }
+
+      var l = this._collection.length;
+      var closed = this._closed;
+
+      var beginning = Math.min(this._beginning, this._ending);
+      var ending = Math.max(this._beginning, this._ending);
+
+      var bid = getIdByLength(this, beginning * this._length);
+      var eid = getIdByLength(this, ending * this._length);
+
+      var low = ceil(bid);
+      var high = floor(eid);
+
+      var left, right, prev, next, v;
+
+      this._renderer.vertices.length = 0;
+
+      for (var i = 0; i < l; i++) {
+
+        if (this._renderer.collection.length <= i) {
+          // Expected to be `relative` anchor points.
+          this._renderer.collection.push(new Anchor());
+        }
+
+        if (i > high && !right) {
+
+          v = this._renderer.collection[i];
+          v.copy(this._collection[i]);
+          this.getPointAt(ending, v);
+          v.command = this._renderer.collection[i].command;
+          this._renderer.vertices.push(v);
+
+          right = v;
+          prev = this._collection[i - 1];
+
+          // Project control over the percentage `t`
+          // of the in-between point
+          if (prev && prev.controls) {
+
+            v.controls.right.clear();
+
+            this._renderer.collection[i - 1].controls.right
+              .clear()
+              .lerp(prev.controls.right, v.t);
+
+          }
+
+        } else if (i >= low && i <= high) {
+
+          v = this._renderer.collection[i]
+            .copy(this._collection[i]);
+          this._renderer.vertices.push(v);
+
+          if (i === high && contains(this, ending)) {
+            right = v;
+            if (!closed && right.controls) {
+              right.controls.right.clear();
+            }
+          } else if (i === low && contains(this, beginning)) {
+            left = v;
+            left.command = Commands.move;
+            if (!closed && left.controls) {
+              left.controls.left.clear();
+            }
+          }
+
+        }
+
+      }
+
+      // Prepend the trimmed point if necessary.
+      if (low > 0 && !left) {
+
+        i = low - 1;
+
+        v = this._renderer.collection[i];
+        v.copy(this._collection[i]);
+        this.getPointAt(beginning, v);
+        v.command = Commands.move;
+        this._renderer.vertices.unshift(v);
+
+        left = v;
+        next = this._collection[i + 1];
+
+        // Project control over the percentage `t`
+        // of the in-between point
+        if (next && next.controls) {
+
+          v.controls.left.clear();
+
+          this._renderer.collection[i + 1].controls.left
+            .copy(next.controls.left)
+            .lerp(Vector.zero, v.t);
+
+        }
+
+      }
+
+    }
+
+    Shape.prototype._update.apply(this, arguments);
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Path#flagReset
+   * @function
+   * @private
+   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+   */
+  flagReset: function() {
+
+    this._flagVertices =  this._flagFill =  this._flagStroke =
+        this._flagLinewidth = this._flagOpacity = this._flagVisible =
+        this._flagCap = this._flagJoin = this._flagMiter =
+        this._flagClip = false;
+
+    Shape.prototype.flagReset.call(this);
+
+    return this;
+
+  }
+
+});
+
+Path.MakeObservable(Path.prototype);
+
+  // Utility functions
+
+function contains(path, t) {
+
+  if (t === 0 || t === 1) {
+    return true;
+  }
+
+  var length = path._length;
+  var target = length * t;
+  var elapsed = 0;
+
+  for (var i = 0; i < path._lengths.length; i++) {
+    var dist = path._lengths[i];
+    if (elapsed >= target) {
+      return target - elapsed >= 0;
+    }
+    elapsed += dist;
+  }
+
+  return false;
+
+}
+
+/**
+ * @private
+ * @param {Two.Path} path - The path to analyze against.
+ * @param {Number} target - The target length at which to find an anchor.
+ * @returns {Number}
+ * @description Return the id of an anchor based on a target length.
+ */
+function getIdByLength(path, target) {
+
+  var total = path._length;
+
+  if (target <= 0) {
+    return 0;
+  } else if (target >= total) {
+    return path._lengths.length - 1;
+  }
+
+  for (var i = 0, sum = 0; i < path._lengths.length; i++) {
+
+    if (sum + path._lengths[i] >= target) {
+      target -= sum;
+      return Math.max(i - 1, 0) + target / path._lengths[i];
+    }
+
+    sum += path._lengths[i];
+
+  }
+
+  return - 1;
+
+}
+
+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 getCurveLength$1(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 subdivide(x1, y1, x2, y2, x3, y3, x4, y4, limit);
+
+}
+
+/**
+ * @name Two.Rectangle
+ * @class
+ * @extends Two.Path
+ * @param {Number} [x=0] - The x position of the rectangle.
+ * @param {Number} [y=0] - The y position of the rectangle.
+ * @param {Number} [width] - The width value of the rectangle.
+ * @param {Number} [height] - The width value of the rectangle.
+ */
+function Rectangle(x, y, width, height) {
+
+  Path.call(this, [
+    new Anchor(),
+    new Anchor(),
+    new Anchor(),
+    new Anchor()
+    // new Anchor() // TODO: Figure out how to handle this for `beginning` / `ending` animations
+  ], true, false, true);
+
+  /**
+   * @name Two.Rectangle#width
+   * @property {Number} - The size of the width of the rectangle.
+   */
+  this.width = width;
+  /**
+   * @name Two.Rectangle#height
+   * @property {Number} - The size of the height of the rectangle.
+   */
+  this.height = height;
+
+  /**
+   * @name Two.Rectangle#origin
+   * @property {Number} - A two-component vector describing the origin offset to draw the rectangle. Default is `0, 0`.
+   */
+  this.origin = new Vector();
+  this.translation.set(x, y);
+
+  this._update();
+
+}
+
+_.extend(Rectangle, {
+
+  /**
+   * @name Two.Rectangle.Properties
+   * @property {String[]} - A list of properties that are on every {@link Two.Rectangle}.
+   */
+  Properties: ['width', 'height'],
+
+  /**
+   * @name Two.Rectangle.MakeObservable
+   * @function
+   * @param {Object} object - The object to make observable.
+   * @description Convenience function to apply observable qualities of a {@link Two.Rectangle} to any object. Handy if you'd like to extend the {@link Two.Rectangle} class on a custom class.
+   */
+  MakeObservable: function(object) {
+
+    Path.MakeObservable(object);
+    _.each(Rectangle.Properties, defineGetterSetter, object);
+
+    Object.defineProperty(object, 'origin', {
+      enumerable: true,
+      get: function() {
+        return this._origin;
+      },
+      set: function(v) {
+        if (this._origin) {
+          this._origin.unbind(Events.Types.change, this._renderer.flagVertices);
+        }
+        this._origin = v;
+        this._origin.bind(Events.Types.change, this._renderer.flagVertices);
+        this._renderer.flagVertices();
+      }
+    });
+
+  }
+
+});
+
+_.extend(Rectangle.prototype, Path.prototype, {
+
+  constructor: Rectangle,
+
+  /**
+   * @name Two.Rectangle#_flagWidth
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Rectangle#width} needs updating.
+   */
+  _flagWidth: 0,
+  /**
+   * @name Two.Rectangle#_flagHeight
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Rectangle#height} needs updating.
+   */
+  _flagHeight: 0,
+
+  /**
+   * @name Two.Rectangle#_width
+   * @private
+   * @see {@link Two.Rectangle#width}
+   */
+  _width: 0,
+  /**
+   * @name Two.Rectangle#_height
+   * @private
+   * @see {@link Two.Rectangle#height}
+   */
+  _height: 0,
+
+  _origin: null,
+
+  /**
+   * @name Two.Rectangle#_update
+   * @function
+   * @private
+   * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+   * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+   * @nota-bene Try not to call this method more than once a frame.
+   */
+  _update: function() {
+
+    if (this._flagVertices || this._flagWidth || this._flagHeight) {
+
+      var xr = this._width / 2;
+      var yr = this._height / 2;
+
+      if (!this._closed && this.vertices.length === 4) {
+        this.vertices.push(new Anchor());
+      }
+
+      this.vertices[0].set(-xr, -yr).add(this._origin).command = Commands.move;
+      this.vertices[1].set(xr, -yr).add(this._origin).command = Commands.line;
+      this.vertices[2].set(xr, yr).add(this._origin).command = Commands.line;
+      this.vertices[3].set(-xr, yr).add(this._origin).command = Commands.line;
+      // FYI: Two.Sprite and Two.ImageSequence have 4 verts
+      if (this.vertices[4]) {
+        this.vertices[4].set(-xr, -yr).add(this._origin).command = Commands.line;
+      }
+
+    }
+
+    Path.prototype._update.call(this);
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Rectangle#flagReset
+   * @function
+   * @private
+   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+   */
+  flagReset: function() {
+
+    this._flagWidth = this._flagHeight = false;
+    Path.prototype.flagReset.call(this);
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Rectangle#clone
+   * @function
+   * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+   * @returns {Two.Rectangle}
+   * @description Create a new instance of {@link Two.Rectangle} with the same properties of the current path.
+   */
+  clone: function(parent) {
+
+    var clone = new Rectangle(0, 0, this.width, this.height);
+
+    clone.translation.copy(this.translation);
+    clone.rotation = this.rotation;
+    clone.scale = this.scale;
+    clone.skewX = this.skewX;
+    clone.skewY = this.skewY;
+
+    if (this.matrix.manual) {
+      clone.matrix.copy(this.matrix);
+    }
+
+    _.each(Path.Properties, function(k) {
+      clone[k] = this[k];
+    }, this);
+
+    if (parent) {
+      parent.add(clone);
+    }
+
+    return clone;
+
+  },
+
+  /**
+   * @name Two.Rectangle#toObject
+   * @function
+   * @returns {Object}
+   * @description Return a JSON compatible plain object that represents the path.
+   */
+  toObject: function() {
+
+    var object = Path.prototype.toObject.call(this);
+    object.width = this.width;
+    object.height = this.height;
+    object.origin = this.origin.toObject();
+    return object;
+
+  }
+
+});
+
+Rectangle.MakeObservable(Rectangle.prototype);
+
+/**
+ * @name Two.Sprite
+ * @class
+ * @extends Two.Rectangle
+ * @param {String|Two.Texture} [path] - The URL path or {@link Two.Texture} to be used as the bitmap data displayed on the sprite.
+ * @param {Number} [ox=0] - The initial `x` position of the Two.Sprite.
+ * @param {Number} [oy=0] - The initial `y` position of the Two.Sprite.
+ * @param {Number} [cols=1] - The number of columns the sprite contains.
+ * @param {Number} [rows=1] - The number of rows the sprite contains.
+ * @param {Number} [frameRate=0] - The frame rate at which the partitions of the image should playback at.
+ * @description A convenient package to display still or animated images through a tiled image source. For more information on the principals of animated imagery through tiling see [Texture Atlas](https://en.wikipedia.org/wiki/Texture_atlas) on Wikipedia.
+ */
+function Sprite(path, ox, oy, cols, rows, frameRate) {
+
+  // Not using default constructor of Rectangle due to odd `beginning` / `ending` behavior.
+  // See: https://github.com/jonobr1/two.js/issues/383
+  Path.call(this, [
+    new Anchor(),
+    new Anchor(),
+    new Anchor(),
+    new Anchor()
+  ], true);
+
+  this.noStroke();
+  this.noFill();
+
+  /**
+   * @name Two.Sprite#texture
+   * @property {Two.Texture} - The texture to be used as bitmap data to display image in the scene.
+   */
+  if (path instanceof Texture) {
+    this.texture = path;
+  } else if (typeof path === 'string') {
+    this.texture = new Texture(path);
+  }
+
+  this.origin = new Vector();
+
+  this._update();
+  this.translation.set(ox || 0, oy || 0);
+
+  /**
+   * @name Two.Sprite#columns
+   * @property {Number} - The number of columns to split the texture into. Defaults to `1`.
+   */
+  if (typeof cols === 'number') {
+    this.columns = cols;
+  }
+
+  /**
+   * @name Two.Sprite#rows
+   * @property {Number} - The number of rows to split the texture into. Defaults to `1`.
+   */
+  if (typeof rows === 'number') {
+    this.rows = rows;
+  }
+
+  /**
+   * @name Two.Sprite#frameRate
+   * @property {Number} - The number of frames to animate against per second. Defaults to `0` for non-animated sprites.
+   */
+  if (typeof frameRate === 'number') {
+    this.frameRate = frameRate;
+  }
+
+  /**
+   * @name Two.Sprite#index
+   * @property {Number} - The index of the current tile of the sprite to display. Defaults to `0`.
+   */
+  this.index = 0;
+
+}
+
+_.extend(Sprite, {
+
+  /**
+   * @name Two.Sprite.Properties
+   * @property {String[]} - A list of properties that are on every {@link Two.Sprite}.
+   */
+  Properties: [
+    'texture', 'columns', 'rows', 'frameRate', 'index'
+  ],
+
+  /**
+   * @name Two.Sprite.MakeObservable
+   * @function
+   * @param {Object} object - The object to make observable.
+   * @description Convenience function to apply observable qualities of a {@link Two.Sprite} to any object. Handy if you'd like to extend or inherit the {@link Two.Sprite} class on a custom class.
+   */
+  MakeObservable: function(obj) {
+
+    Rectangle.MakeObservable(obj);
+    _.each(Sprite.Properties, defineGetterSetter, obj);
+
+  }
+
+});
+
+_.extend(Sprite.prototype, Rectangle.prototype, {
+
+  constructor: Sprite,
+
+  /**
+   * @name Two.Sprite#_flagTexture
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Sprite#texture} needs updating.
+   */
+  _flagTexture: false,
+
+  /**
+   * @name Two.Sprite#_flagColumns
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Sprite#columns} need updating.
+   */
+  _flagColumns: false,
+
+  /**
+   * @name Two.Sprite#_flagRows
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Sprite#rows} need updating.
+   */
+  _flagRows: false,
+
+  /**
+   * @name Two.Sprite#_flagFrameRate
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Sprite#flagFrameRate} needs updating.
+   */
+  _flagFrameRate: false,
+
+  /**
+   * @name Two.Sprite#_flagIndex
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Sprite#index} needs updating.
+   */
+  flagIndex: false,
+
+  // Private variables
+
+  /**
+   * @name Two.Sprite#_amount
+   * @private
+   * @property {Number} - Number of frames for a given {@link Two.Sprite}.
+   */
+  _amount: 1,
+
+  /**
+   * @name Two.Sprite#_duration
+   * @private
+   * @property {Number} - Number of milliseconds a {@link Two.Sprite}.
+   */
+  _duration: 0,
+
+  /**
+   * @name Two.Sprite#_startTime
+   * @private
+   * @property {Milliseconds} - Epoch time in milliseconds of when the {@link Two.Sprite} started.
+   */
+  _startTime: 0,
+
+  /**
+   * @name Two.Sprite#_playing
+   * @private
+   * @property {Boolean} - Dictates whether the {@link Two.Sprite} is animating or not.
+   */
+  _playing: false,
+
+  /**
+   * @name Two.Sprite#_firstFrame
+   * @private
+   * @property {Number} - The frame the {@link Two.Sprite} should start with.
+   */
+  _firstFrame: 0,
+
+  /**
+   * @name Two.Sprite#_lastFrame
+   * @private
+   * @property {Number} - The frame the {@link Two.Sprite} should end with.
+   */
+  _lastFrame: 0,
+
+  /**
+   * @name Two.Sprite#_playing
+   * @private
+   * @property {Boolean} - Dictates whether the {@link Two.Sprite} should loop or not.
+   */
+  _loop: true,
+
+  // Exposed through getter-setter
+
+  /**
+   * @name Two.Sprite#_texture
+   * @private
+   * @see {@link Two.Sprite#texture}
+   */
+  _texture: null,
+
+  /**
+   * @name Two.Sprite#_columns
+   * @private
+   * @see {@link Two.Sprite#columns}
+   */
+  _columns: 1,
+
+  /**
+   * @name Two.Sprite#_rows
+   * @private
+   * @see {@link Two.Sprite#rows}
+   */
+  _rows: 1,
+
+  /**
+   * @name Two.Sprite#_frameRate
+   * @private
+   * @see {@link Two.Sprite#frameRate}
+   */
+  _frameRate: 0,
+
+  /**
+   * @name Two.Sprite#_index
+   * @private
+   * @property {Number} - The current frame the {@link Two.Sprite} is currently displaying.
+   */
+  _index: 0,
+
+  /**
+   * @name Two.Sprite#_origin
+   * @private
+   * @see {@link Two.Sprite#origin}
+   */
+  _origin: null,
+
+  /**
+   * @name Two.Sprite#play
+   * @function
+   * @param {Number} [firstFrame=0] - The index of the frame to start the animation with.
+   * @param {Number} [lastFrame] - The index of the frame to end the animation with. Defaults to the last item in the {@link Two.Sprite#textures}.
+   * @param {Function} [onLastFrame] - Optional callback function to be triggered after playing the last frame. This fires multiple times when the sprite is looped.
+   * @description Initiate animation playback of a {@link Two.Sprite}.
+   */
+  play: function(firstFrame, lastFrame, onLastFrame) {
+
+    this._playing = true;
+    this._firstFrame = 0;
+    this._lastFrame = this.amount - 1;
+    this._startTime = _.performance.now();
+
+    if (typeof firstFrame === 'number') {
+      this._firstFrame = firstFrame;
+    }
+    if (typeof lastFrame === 'number') {
+      this._lastFrame = lastFrame;
+    }
+    if (typeof onLastFrame === 'function') {
+      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;
+
+  },
+
+  /**
+   * @name Two.Sprite#pause
+   * @function
+   * @description Halt animation playback of a {@link Two.Sprite}.
+   */
+  pause: function() {
+
+    this._playing = false;
+    return this;
+
+  },
+
+  /**
+   * @name Two.Sprite#stop
+   * @function
+   * @description Halt animation playback of a {@link Two.Sprite} and set the current frame back to the first frame.
+   */
+  stop: function() {
+
+    this._playing = false;
+    this._index = 0;
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Sprite#clone
+   * @function
+   * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+   * @returns {Two.Sprite}
+   * @description Create a new instance of {@link Two.Sprite} with the same properties of the current sprite.
+   */
+  clone: function(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;
+
+  },
+
+  /**
+   * @name Two.Sprite#toObject
+   * @function
+   * @returns {Object}
+   * @description Return a JSON compatible plain object that represents the path.
+   */
+  toObject: function() {
+    var object = Rectangle.prototype.toObject.call(this);
+    object.texture = this.texture.toObject();
+    object.columns = this.columns;
+    object.rows = this.rows;
+    object.frameRate = this.frameRate;
+    object.index = this.index;
+    object._firstFrame = this._firstFrame;
+    object._lastFrame = this._lastFrame;
+    object._loop = this._loop;
+    return object;
+  },
+
+  /**
+   * @name Two.Sprite#_update
+   * @function
+   * @private
+   * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+   * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+   * @nota-bene Try not to call this method more than once a frame.
+   */
+  _update: function() {
+
+    var effect = this._texture;
+    var cols = this._columns;
+    var rows = this._rows;
+
+    var width, height, elapsed, amount, duration;
+    var index, iw, ih, 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;
+
+  },
+
+  /**
+   * @name Two.Sprite#flagReset
+   * @function
+   * @private
+   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+   */
+  flagReset: function() {
+
+    this._flagTexture = this._flagColumns = this._flagRows
+      = this._flagFrameRate = false;
+
+    Rectangle.prototype.flagReset.call(this);
+
+    return this;
+  }
+
+
+});
+
+Sprite.MakeObservable(Sprite.prototype);
+
+var TWO_PI$4 = Math.PI * 2, HALF_PI$2 = Math.PI / 2;
+var cos$3 = Math.cos, sin$3 = Math.sin;
+
+/**
+ * @name Two.Circle
+ * @class
+ * @extends Two.Path
+ * @param {Number} [x=0] - The x position of the circle.
+ * @param {Number} [y=0] - The y position of the circle.
+ * @param {Number} [radius=0] - The radius value of the circle.
+ * @param {Number} [resolution=4] - The number of vertices used to construct the circle.
+ */
+function Circle(ox, oy, r, resolution) {
+
+  // At least 2 vertices are required for proper circlage
+  var amount = resolution ? Math.max(resolution, 2) : 4;
+
+  var points = [];
+  for (var i = 0; i < amount; i++) {
+    points.push(new Anchor(0, 0, 0, 0, 0, 0));
+  }
+
+  Path.call(this, points, true, true, true);
+
+  /**
+   * @name Two.Circle#radius
+   * @property {Number} - The size of the radius of the circle.
+   */
+  if (typeof r === 'number') {
+    this.radius = r;
+  }
+
+  this._update();
+
+  if (typeof ox === 'number') {
+    this.translation.x = ox;
+  }
+  if (typeof oy === 'number') {
+    this.translation.y = oy;
+  }
+
+}
+
+_.extend(Circle, {
+
+  /**
+   * @name Two.Circle.Properties
+   * @property {String[]} - A list of properties that are on every {@link Two.Circle}.
+   */
+  Properties: ['radius'],
+
+  /**
+   * @name Two.Circle.MakeObservable
+   * @function
+   * @param {Object} object - The object to make observable.
+   * @description Convenience function to apply observable qualities of a {@link Two.Circle} to any object. Handy if you'd like to extend the {@link Two.Circle} class on a custom class.
+   */
+  MakeObservable: function(obj) {
+
+    Path.MakeObservable(obj);
+    _.each(Circle.Properties, defineGetterSetter, obj);
+
+  }
+
+});
+
+_.extend(Circle.prototype, Path.prototype, {
+
+  constructor: Circle,
+
+  /**
+   * @name Two.Circle#_flagRadius
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Circle#radius} needs updating.
+   */
+  _flagRadius: false,
+
+  /**
+   * @name Two.Circle#_radius
+   * @private
+   * @see {@link Two.Circle#radius}
+   */
+  _radius: 0,
+
+  /**
+   * @name Two.Circle#_update
+   * @function
+   * @private
+   * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+   * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+   * @nota-bene Try not to call this method more than once a frame.
+   */
+  _update: function() {
+
+    if (this._flagVertices || this._flagRadius) {
+
+      var length = this.vertices.length;
+
+      if (!this._closed && length > 2) {
+        length -= 1;
+      }
+
+      // Coefficient for approximating circular arcs with Bezier curves
+      var c = (4 / 3) * Math.tan(Math.PI / (length * 2));
+      var radius = this._radius;
+      var rc = radius * c;
+
+      for (var i = 0; i < this.vertices.length; i++) {
+        var pct = i / length;
+        var theta = pct * TWO_PI$4;
+
+        var x = radius * cos$3(theta);
+        var y = radius * sin$3(theta);
+
+        var lx = rc * cos$3(theta - HALF_PI$2);
+        var ly = rc * sin$3(theta - HALF_PI$2);
+
+        var rx = rc * cos$3(theta + HALF_PI$2);
+        var ry = rc * sin$3(theta + HALF_PI$2);
+
+        var v = this.vertices[i];
+
+        v.command = i === 0 ? Commands.move : Commands.curve;
+        v.set(x, y);
+        v.controls.left.set(lx, ly);
+        v.controls.right.set(rx, ry);
+      }
+    }
+
+    Path.prototype._update.call(this);
+    return this;
+
+  },
+
+  /**
+   * @name Two.Circle#flagReset
+   * @function
+   * @private
+   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+   */
+  flagReset: function() {
+
+    this._flagRadius = false;
+
+    Path.prototype.flagReset.call(this);
+    return this;
+
+  },
+
+  /**
+   * @name Two.Circle#clone
+   * @function
+   * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+   * @returns {Two.Circle}
+   * @description Create a new instance of {@link Two.Circle} with the same properties of the current path.
+   */
+  clone: function(parent) {
+
+    var clone = new Circle(0, 0, this.radius, this.vertices.length);
+
+    clone.translation.copy(this.translation);
+    clone.rotation = this.rotation;
+    clone.scale = this.scale;
+    clone.skewX = this.skewX;
+    clone.skewY = this.skewY;
+
+    if (this.matrix.manual) {
+      clone.matrix.copy(this.matrix);
+    }
+
+    _.each(Path.Properties, function(k) {
+      clone[k] = this[k];
+    }, this);
+
+    if (parent) {
+      parent.add(clone);
+    }
+
+    return clone;
+
+  },
+
+  /**
+   * @name Two.Circle#toObject
+   * @function
+   * @returns {Object}
+   * @description Return a JSON compatible plain object that represents the path.
+   */
+  toObject: function() {
+
+    var object = Path.prototype.toObject.call(this);
+
+    _.each(Circle.Properties, function(property) {
+      object[property] = this[property];
+    }, this);
+
+    return object;
+
+  }
+
+});
+
+Circle.MakeObservable(Circle.prototype);
+
+var TWO_PI$3 = Math.PI * 2, HALF_PI$1 = Math.PI / 2;
+var cos$2 = Math.cos, sin$2 = Math.sin;
+
+/**
+ * @name Two.Ellipse
+ * @class
+ * @extends Two.Path
+ * @param {Number} [x=0] - The x position of the ellipse.
+ * @param {Number} [y=0] - The y position of the ellipse.
+ * @param {Number} [rx=0] - The radius value of the ellipse in the x direction.
+ * @param {Number} [ry=0] - The radius value of the ellipse in the y direction.
+ * @param {Number} [resolution=4] - The number of vertices used to construct the ellipse.
+ */
+function Ellipse(ox, oy, rx, ry, resolution) {
+
+  if (typeof ry !== 'number' && typeof rx === 'number') {
+    ry = rx;
+  }
+
+  // At least 2 vertices are required for proper circlage
+  var amount = resolution ? Math.max(resolution, 2) : 4;
+
+  var points = [];
+  for (var i = 0; i < amount; i++) {
+    points.push(new Anchor());
+  }
+
+  Path.call(this, points, true, true, true);
+
+  /**
+   * @name Two.Ellipse#width
+   * @property {Number} - The width of the ellipse.
+   */
+  if (typeof rx === 'number') {
+    this.width = rx * 2;
+  }
+
+  /**
+   * @name Two.Ellipse#height
+   * @property {Number} - The height of the ellipse.
+   */
+  if (typeof ry === 'number') {
+    this.height = ry * 2;
+  }
+
+  this._update();
+  this.translation.set(ox, oy);
+
+}
+
+_.extend(Ellipse, {
+
+  /**
+   * @name Two.Ellipse.Properties
+   * @property {String[]} - A list of properties that are on every {@link Two.Ellipse}.
+   */
+  Properties: ['width', 'height'],
+
+  /**
+   * @name Two.Ellipse.MakeObservable
+   * @function
+   * @param {Object} object - The object to make observable.
+   * @description Convenience function to apply observable qualities of a {@link Two.Ellipse} to any object. Handy if you'd like to extend the {@link Two.Ellipse} class on a custom class.
+   */
+  MakeObservable: function(obj) {
+
+    Path.MakeObservable(obj);
+    _.each(Ellipse.Properties, defineGetterSetter, obj);
+
+  }
+
+});
+
+_.extend(Ellipse.prototype, Path.prototype, {
+
+  /**
+   * @name Two.Ellipse#_flagWidth
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Ellipse#width} needs updating.
+   */
+  _flagWidth: false,
+  /**
+   * @name Two.Ellipse#_flagHeight
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Ellipse#height} needs updating.
+   */
+  _flagHeight: false,
+
+  /**
+   * @name Two.Polygon#_width
+   * @private
+   * @see {@link Two.Ellipse#width}
+   */
+  _width: 0,
+  /**
+   * @name Two.Polygon#_height
+   * @private
+   * @see {@link Two.Ellipse#height}
+   */
+  _height: 0,
+
+  constructor: Ellipse,
+
+  /**
+   * @name Two.Ellipse#_update
+   * @function
+   * @private
+   * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+   * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+   * @nota-bene Try not to call this method more than once a frame.
+   */
+  _update: function() {
+
+    if (this._flagVertices || this._flagWidth || this._flagHeight) {
+
+      var length = this.vertices.length;
+
+      if (!this._closed && length > 2) {
+        length -= 1;
+      }
+
+      // Coefficient for approximating circular arcs with Bezier curves
+      var c = (4 / 3) * Math.tan(Math.PI / (this.vertices.length * 2));
+      var radiusX = this._width / 2;
+      var radiusY = this._height / 2;
+
+      for (var i = 0; i < this.vertices.length; i++) {
+        var pct = i / length;
+        var theta = pct * TWO_PI$3;
+
+        var x = radiusX * cos$2(theta);
+        var y = radiusY * sin$2(theta);
+
+        var lx = radiusX * c * cos$2(theta - HALF_PI$1);
+        var ly = radiusY * c * sin$2(theta - HALF_PI$1);
+
+        var rx = radiusX * c * cos$2(theta + HALF_PI$1);
+        var ry = radiusY * c * sin$2(theta + HALF_PI$1);
+
+        var v = this.vertices[i];
+
+        v.command = i === 0 ? Commands.move : Commands.curve;
+        v.set(x, y);
+        v.controls.left.set(lx, ly);
+        v.controls.right.set(rx, ry);
+      }
+    }
+
+    Path.prototype._update.call(this);
+    return this;
+
+  },
+
+  /**
+   * @name Two.Ellipse#flagReset
+   * @function
+   * @private
+   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+   */
+  flagReset: function() {
+
+    this._flagWidth = this._flagHeight = false;
+
+    Path.prototype.flagReset.call(this);
+    return this;
+
+  },
+
+  /**
+   * @name Two.Ellipse#clone
+   * @function
+   * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+   * @returns {Two.Polygon}
+   * @description Create a new instance of {@link Two.Polygon} with the same properties of the current path.
+   */
+  clone: function(parent) {
+
+    var rx = this.width / 2;
+    var ry = this.height / 2;
+    var resolution = this.vertices.length;
+    var clone = new Ellipse(0, 0, rx, ry, resolution);
+
+    clone.translation.copy(this.translation);
+    clone.rotation = this.rotation;
+    clone.scale = this.scale;
+    clone.skewX = this.skewX;
+    clone.skewY = this.skewY;
+
+    if (this.matrix.manual) {
+      clone.matrix.copy(this.matrix);
+    }
+
+    _.each(Path.Properties, function(k) {
+      clone[k] = this[k];
+    }, this);
+
+    if (parent) {
+      parent.add(clone);
+    }
+
+    return clone;
+
+  },
+
+  /**
+   * @name Two.Ellipse#toObject
+   * @function
+   * @returns {Object}
+   * @description Return a JSON compatible plain object that represents the path.
+   */
+  toObject: function() {
+
+    var object = Path.prototype.toObject.call(this);
+
+    _.each(Ellipse.Properties, function(property) {
+      object[property] = this[property];
+    }, this);
+
+    return object;
+
+  }
+
+});
+
+Ellipse.MakeObservable(Ellipse.prototype);
+
+/**
+ * @name Two.Line
+ * @class
+ * @extends Two.Path
+ * @param {Number} [x1=0] - The x position of the first vertex on the line.
+ * @param {Number} [y1=0] - The y position of the first vertex on the line.
+ * @param {Number} [x2=0] - The x position of the second vertex on the line.
+ * @param {Number} [y2=0] - The y position of the second vertex on the line.
+ */
+function Line(x1, y1, x2, y2) {
+
+  Path.call(this, [
+      new Anchor(x1, y1),
+      new Anchor(x2, y2)
+  ]);
+
+  this.vertices[0].command = Commands.move;
+  this.vertices[1].command = Commands.line;
+
+  this.automatic = false;
+
+}
+
+_.extend(Line.prototype, Path.prototype, {
+
+  constructor: Line
+
+});
+
+Path.MakeObservable(Line.prototype);
+
+/**
+ * @name Two.RoundedRectangle
+ * @class
+ * @extends Two.Path
+ * @param {Number} [x=0] - The x position of the rounded rectangle.
+ * @param {Number} [y=0] - The y position of the rounded rectangle.
+ * @param {Number} [width=0] - The width value of the rounded rectangle.
+ * @param {Number} [height=0] - The width value of the rounded rectangle.
+ * @param {Number} [radius=0] - The radius value of the rounded rectangle.
+ * @param {Number} [resolution=12] - The number of vertices used to construct the rounded rectangle.
+ */
+function RoundedRectangle(ox, oy, width, height, radius) {
+
+  if (typeof radius === 'undefined' &&
+    typeof width === 'number' && typeof height === 'number') {
+    radius = Math.floor(Math.min(width, height) / 12);
+  }
+
+  var amount = 10;
+
+  var points = [];
+  for (var i = 0; i < amount; i++) {
+    points.push(
+      new Anchor(0, 0, 0, 0, 0, 0,
+        i === 0 ? Commands.move : Commands.curve)
+    );
+  }
+
+  // points[points.length - 1].command = Two.Commands.close;
+
+  Path.call(this, points);
+
+  this.closed = true;
+  this.automatic = false;
+
+  this._renderer.flagRadius = RoundedRectangle.FlagRadius.bind(this);
+
+  /**
+   * @name Two.RoundedRectangle#width
+   * @property {Number} - The width of the rounded rectangle.
+   */
+  if (typeof width === 'number') {
+    this.width = width;
+  }
+
+  /**
+   * @name Two.RoundedRectangle#height
+   * @property {Number} - The height of the rounded rectangle.
+   */
+  if (typeof height === 'number') {
+    this.height = height;
+  }
+
+  /**
+   * @name Two.RoundedRectangle#radius
+   * @property {Number} - The size of the radius of the rounded rectangle.
+   */
+  if (typeof radius === 'number') {
+    this.radius = radius;
+  }
+
+  this._update();
+  this.translation.set(ox, oy);
+
+}
+
+_.extend(RoundedRectangle, {
+
+  /**
+   * @name Two.RoundedRectangle.Properties
+   * @property {String[]} - A list of properties that are on every {@link Two.RoundedRectangle}.
+   */
+  Properties: ['width', 'height'],
+
+  /**
+   * @name Two.RoundedRectangle.FlagRadius
+   * @property {Function} - A convenience function to trigger the flag for radius changing.
+   */
+  FlagRadius: function() {
+    this._flagRadius = true;
+  },
+
+  /**
+   * @name Two.RoundedRectangle.MakeObservable
+   * @function
+   * @param {Object} object - The object to make observable.
+   * @description Convenience function to apply observable qualities of a {@link Two.RoundedRectangle} to any object. Handy if you'd like to extend the {@link Two.RoundedRectangle} class on a custom class.
+   */
+  MakeObservable: function(object) {
+
+    Path.MakeObservable(object);
+    _.each(RoundedRectangle.Properties, defineGetterSetter, object);
+
+    Object.defineProperty(object, 'radius', {
+      enumerable: true,
+      get: function() {
+        return this._radius;
+      },
+      set: function(v) {
+
+        if (this._radius instanceof Vector) {
+          this._radius.unbind(Events.Types.change, this._renderer.flagRadius);
+        }
+
+        this._radius = v;
+
+        if (this._radius instanceof Vector) {
+          this._radius.bind(Events.Types.change, this._renderer.flagRadius);
+        }
+
+        this._flagRadius = true;
+
+      }
+    });
+
+  }
+
+});
+
+_.extend(RoundedRectangle.prototype, Path.prototype, {
+
+  constructor: RoundedRectangle,
+
+  /**
+   * @name Two.RoundedRectangle#_flagWidth
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.RoundedRectangle#width} needs updating.
+   */
+  _flagWidth: false,
+  /**
+   * @name Two.RoundedRectangle#_flagHeight
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.RoundedRectangle#height} needs updating.
+   */
+  _flagHeight: false,
+  /**
+   * @name Two.RoundedRectangle#_flagRadius
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.RoundedRectangle#radius} needs updating.
+   */
+  _flagRadius: false,
+
+  /**
+   * @name Two.RoundedRectangle#_width
+   * @private
+   * @see {@link Two.RoundedRectangle#width}
+   */
+  _width: 0,
+  /**
+   * @name Two.RoundedRectangle#_height
+   * @private
+   * @see {@link Two.RoundedRectangle#height}
+   */
+  _height: 0,
+  /**
+   * @name Two.RoundedRectangle#_radius
+   * @private
+   * @see {@link Two.RoundedRectangle#radius}
+   */
+  _radius: 12,
+
+  /**
+   * @name Two.RoundedRectangle#_update
+   * @function
+   * @private
+   * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+   * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+   * @nota-bene Try not to call this method more than once a frame.
+   */
+  _update: function() {
+
+    if (this._flagVertices || this._flagWidth || this._flagHeight || this._flagRadius) {
+
+      var width = this._width;
+      var height = this._height;
+
+      var rx, ry;
+
+      if (this._radius instanceof Vector) {
+        rx = this._radius.x;
+        ry = this._radius.y;
+      } else {
+        rx = this._radius;
+        ry = this._radius;
+      }
+
+      var v;
+      var w = width / 2;
+      var h = height / 2;
+
+      v = this.vertices[0];
+      v.x = - (w - rx);
+      v.y = - h;
+
+      // Upper Right Corner
+
+      v = this.vertices[1];
+      v.x = (w - rx);
+      v.y = - h;
+      v.controls.left.clear();
+      v.controls.right.x = rx;
+      v.controls.right.y = 0;
+
+      v = this.vertices[2];
+      v.x = w;
+      v.y = - (h - ry);
+      v.controls.right.clear();
+      v.controls.left.clear();
+
+      // Bottom Right Corner
+
+      v = this.vertices[3];
+      v.x = w;
+      v.y = (h - ry);
+      v.controls.left.clear();
+      v.controls.right.x = 0;
+      v.controls.right.y = ry;
+
+      v = this.vertices[4];
+      v.x = (w - rx);
+      v.y = h;
+      v.controls.right.clear();
+      v.controls.left.clear();
+
+      // Bottom Left Corner
+
+      v = this.vertices[5];
+      v.x = - (w - rx);
+      v.y = h;
+      v.controls.left.clear();
+      v.controls.right.x = - rx;
+      v.controls.right.y = 0;
+
+      v = this.vertices[6];
+      v.x = - w;
+      v.y = (h - ry);
+      v.controls.left.clear();
+      v.controls.right.clear();
+
+      // Upper Left Corner
+
+      v = this.vertices[7];
+      v.x = - w;
+      v.y = - (h - ry);
+      v.controls.left.clear();
+      v.controls.right.x = 0;
+      v.controls.right.y = - ry;
+
+      v = this.vertices[8];
+      v.x = - (w - rx);
+      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;
+
+  },
+
+  /**
+   * @name Two.RoundedRectangle#flagReset
+   * @function
+   * @private
+   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+   */
+  flagReset: function() {
+
+    this._flagWidth = this._flagHeight = this._flagRadius = false;
+    Path.prototype.flagReset.call(this);
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.RoundedRectangle#clone
+   * @function
+   * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+   * @returns {Two.RoundedRectangle}
+   * @description Create a new instance of {@link Two.RoundedRectangle} with the same properties of the current path.
+   */
+  clone: function(parent) {
+
+    var width = this.width;
+    var height = this.height;
+    var radius = this.radius;
+
+    var clone = new RoundedRectangle(0, 0, width, height, radius);
+
+    clone.translation.copy(this.translation);
+    clone.rotation = this.rotation;
+    clone.scale = this.scale;
+    clone.skewX = this.skewX;
+    clone.skewY = this.skewY;
+
+    if (this.matrix.manual) {
+      clone.matrix.copy(this.matrix);
+    }
+
+    _.each(Path.Properties, function(k) {
+      clone[k] = this[k];
+    }, this);
+
+    if (parent) {
+      parent.add(clone);
+    }
+
+    return clone;
+
+  },
+
+  /**
+   * @name Two.RoundedRectangle#toObject
+   * @function
+   * @returns {Object}
+   * @description Return a JSON compatible plain object that represents the path.
+   */
+  toObject: function() {
+
+    var object = Path.prototype.toObject.call(this);
+
+    _.each(RoundedRectangle.Properties, function(property) {
+      object[property] = this[property];
+    }, this);
+
+    object.radius = typeof this.radius === 'number'
+      ? this.radius : this.radius.toObject();
+
+    return object;
+
+  }
+
+});
+
+RoundedRectangle.MakeObservable(RoundedRectangle.prototype);
+
+var min = Math.min, max = Math.max;
+
+/**
+ * @name Two.Text
+ * @class
+ * @extends Two.Shape
+ * @param {String} [message] - The String to be rendered to the scene.
+ * @param {Number} [x=0] - The position in the x direction for the object.
+ * @param {Number} [y=0] - The position in the y direction for the object.
+ * @param {Object} [styles] - An object where styles are applied. Attribute must exist in Two.Text.Properties.
+ * @description This is a primitive class for creating drawable text that can be added to the scenegraph.
+ * @returns {Two.Text}
+ */
+function Text(message, x, y, styles) {
+
+  Shape.call(this);
+
+  this._renderer.type = 'text';
+  this._renderer.flagFill = Text.FlagFill.bind(this);
+  this._renderer.flagStroke = Text.FlagStroke.bind(this);
+
+  this.value = message;
+
+  if (typeof x === 'number') {
+    this.translation.x = x;
+  }
+  if (typeof y === 'number') {
+    this.translation.y = y;
+  }
+
+  /**
+   * @name Two.Text#dashes
+   * @property {Number[]} - Array of numbers. Odd indices represent dash length. Even indices represent dash space.
+   * @description A list of numbers that represent the repeated dash length and dash space applied to the stroke of the text.
+   * @see {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray} for more information on the SVG stroke-dasharray attribute.
+   */
+  this.dashes = [];
+
+  /**
+   * @name Two.Text#dashes#offset
+   * @property {Number} - A number in pixels to offset {@link Two.Text#dashes} display.
+   */
+  this.dashes.offset = 0;
+
+  if (!_.isObject(styles)) {
+    return this;
+  }
+
+  _.each(Text.Properties, function(property) {
+
+    if (property in styles) {
+      this[property] = styles[property];
+    }
+
+  }, this);
+
+}
+
+_.extend(Text, {
+
+  /**
+   * @name Two.Text.Ratio
+   * @property {Number} - Approximate aspect ratio of a typeface's character width to height.
+   */
+  Ratio: 0.6,
+
+  /**
+   * @name Two.Text.Properties
+   * @property {String[]} - A list of properties that are on every {@link Two.Text}.
+   */
+  Properties: [
+    'value', 'family', 'size', 'leading', 'alignment', 'linewidth', 'style',
+    'weight', 'decoration', 'baseline', 'opacity', 'visible', 'className',
+    'fill', 'stroke',
+  ],
+
+  /**
+   * @name Two.Text.FlagFill
+   * @function
+   * @description Cached method to let renderers know the fill property have been updated on a {@link Two.Text}.
+   */
+  FlagFill: function() {
+    this._flagFill = true;
+  },
+
+  /**
+   * @name Two.Text.FlagStroke
+   * @function
+   * @description Cached method to let renderers know the stroke property have been updated on a {@link Two.Text}.
+   */
+  FlagStroke: function() {
+    this._flagStroke = true;
+  },
+
+  MakeObservable: function(object) {
+
+    Shape.MakeObservable(object);
+
+    _.each(Text.Properties.slice(0, 12), defineGetterSetter, object);
+
+    Object.defineProperty(object, 'fill', {
+      enumerable: true,
+      get: function() {
+        return this._fill;
+      },
+      set: function(f) {
+
+        if (this._fill instanceof Gradient
+          || this._fill instanceof LinearGradient
+          || this._fill instanceof RadialGradient
+          || this._fill instanceof Texture) {
+          this._fill.unbind(Events.Types.change, this._renderer.flagFill);
+        }
+
+        this._fill = f;
+        this._flagFill = true;
+
+        if (this._fill instanceof Gradient
+          || this._fill instanceof LinearGradient
+          || this._fill instanceof RadialGradient
+          || this._fill instanceof Texture) {
+          this._fill.bind(Events.Types.change, this._renderer.flagFill);
+        }
+
+      }
+    });
+
+    Object.defineProperty(object, 'stroke', {
+      enumerable: true,
+      get: function() {
+        return this._stroke;
+      },
+      set: function(f) {
+
+        if (this._stroke instanceof Gradient
+          || this._stroke instanceof LinearGradient
+          || this._stroke instanceof RadialGradient
+          || this._stroke instanceof Texture) {
+          this._stroke.unbind(Events.Types.change, this._renderer.flagStroke);
+        }
+
+        this._stroke = f;
+        this._flagStroke = true;
+
+        if (this._stroke instanceof Gradient
+          || this._stroke instanceof LinearGradient
+          || this._stroke instanceof RadialGradient
+          || this._stroke instanceof Texture) {
+          this._stroke.bind(Events.Types.change, this._renderer.flagStroke);
+        }
+
+      }
+    });
+
+    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;
+        }
+      }
+
+    });
+
+    Object.defineProperty(object, 'clip', {
+      enumerable: true,
+      get: function() {
+        return this._clip;
+      },
+      set: function(v) {
+        this._clip = v;
+        this._flagClip = true;
+      }
+    });
+
+    Object.defineProperty(object, 'dashes', {
+      enumerable: true,
+      get: function() {
+        return this._dashes;
+      },
+      set: function(v) {
+        if (typeof v.offset !== 'number') {
+          v.offset = this._dashes.offset || 0;
+        }
+        this._dashes = v;
+      }
+    });
+
+  }
+
+});
+
+_.extend(Text.prototype, Shape.prototype, {
+
+  constructor: Text,
+
+  // Flags
+  // http://en.wikipedia.org/wiki/Flag
+
+  /**
+   * @name Two.Text#_flagValue
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Text#value} need updating.
+   */
+  _flagValue: true,
+
+  /**
+   * @name Two.Text#_flagFamily
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Text#family} need updating.
+   */
+  _flagFamily: true,
+
+  /**
+   * @name Two.Text#_flagSize
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Text#size} need updating.
+   */
+  _flagSize: true,
+
+  /**
+   * @name Two.Text#_flagLeading
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Text#leading} need updating.
+   */
+  _flagLeading: true,
+
+  /**
+   * @name Two.Text#_flagAlignment
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Text#alignment} need updating.
+   */
+  _flagAlignment: true,
+
+  /**
+   * @name Two.Text#_flagBaseline
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Text#baseline} need updating.
+   */
+  _flagBaseline: true,
+
+  /**
+   * @name Two.Text#_flagStyle
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Text#style} need updating.
+   */
+  _flagStyle: true,
+
+  /**
+   * @name Two.Text#_flagWeight
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Text#weight} need updating.
+   */
+  _flagWeight: true,
+
+  /**
+   * @name Two.Text#_flagDecoration
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Text#decoration} need updating.
+   */
+  _flagDecoration: true,
+
+  /**
+   * @name Two.Text#_flagFill
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Text#fill} need updating.
+   */
+  _flagFill: true,
+
+  /**
+   * @name Two.Text#_flagStroke
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Text#stroke} need updating.
+   */
+  _flagStroke: true,
+
+  /**
+   * @name Two.Text#_flagLinewidth
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Text#linewidth} need updating.
+   */
+  _flagLinewidth: true,
+
+  /**
+   * @name Two.Text#_flagOpacity
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Text#opacity} need updating.
+   */
+  _flagOpacity: true,
+
+  /**
+   * @name Two.Text#_flagClassName
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Text#className} need updating.
+   */
+  _flagClassName: true,
+
+  /**
+   * @name Two.Text#_flagVisible
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Text#visible} need updating.
+   */
+  _flagVisible: true,
+
+  /**
+   * @name Two.Path#_flagMask
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Path#mask} needs updating.
+   */
+  _flagMask: false,
+
+  /**
+   * @name Two.Text#_flagClip
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Text#clip} need updating.
+   */
+  _flagClip: false,
+
+  // Underlying Properties
+
+  /**
+   * @name Two.Text#value
+   * @property {String} - The characters to be rendered to the the screen. Referred to in the documentation sometimes as the `message`.
+   */
+  _value: '',
+
+  /**
+   * @name Two.Text#family
+   * @property {String} - The font family Two.js should attempt to regsiter for rendering. The default value is `'sans-serif'`. Comma separated font names can be supplied as a "stack", similar to the CSS implementation of `font-family`.
+   */
+  _family: 'sans-serif',
+
+  /**
+   * @name Two.Text#size
+   * @property {Number} - The font size in Two.js point space. Defaults to `13`.
+   */
+  _size: 13,
+
+  /**
+   * @name Two.Text#leading
+   * @property {Number} - The height between lines measured from base to base in Two.js point space. Defaults to `17`.
+   */
+  _leading: 17,
+
+  /**
+   * @name Two.Text#alignment
+   * @property {String} - Alignment of text in relation to {@link Two.Text#translation}'s coordinates. Possible values include `'left'`, `'center'`, `'right'`. Defaults to `'center'`.
+   */
+  _alignment: 'center',
+
+  /**
+   * @name Two.Text#baseline
+   * @property {String} - The vertical aligment of the text in relation to {@link Two.Text#translation}'s coordinates. Possible values include `'top'`, `'middle'`, `'bottom'`, and `'baseline'`. Defaults to `'baseline'`.
+   */
+  _baseline: 'middle',
+
+  /**
+   * @name Two.Text#style
+   * @property {String} - The font's style. Possible values include '`normal`', `'italic'`. Defaults to `'normal'`.
+   */
+  _style: 'normal',
+
+  /**
+   * @name Two.Text#weight
+   * @property {Number} - A number at intervals of 100 to describe the font's weight. This compatibility varies with the typeface's variant weights. Larger values are bolder. Smaller values are thinner. Defaults to `'500'`.
+   */
+  _weight: 500,
+
+  /**
+   * @name Two.Text#decoration
+   * @property {String} - String to delineate whether text should be decorated with for instance an `'underline'`. Defaults to `'none'`.
+   */
+  _decoration: 'none',
+
+  /**
+   * @name Two.Text#fill
+   * @property {(String|Two.Gradient|Two.Texture)} - The value of what the text object should be filled in with.
+   * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/color_value} for more information on CSS's colors as `String`.
+   */
+  _fill: '#000',
+
+  /**
+   * @name Two.Text#stroke
+   * @property {(String|Two.Gradient|Two.Texture)} - The value of what the text object should be filled in with.
+   * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/color_value} for more information on CSS's colors as `String`.
+   */
+  _stroke: 'transparent',
+
+  /**
+   * @name Two.Text#linewidth
+   * @property {Number} - The thickness in pixels of the stroke.
+   */
+  _linewidth: 1,
+
+  /**
+   * @name Two.Text#opacity
+   * @property {Number} - The opaqueness of the text object.
+   * @nota-bene Can be used in conjunction with CSS Colors that have an alpha value.
+   */
+  _opacity: 1,
+
+  /**
+   * @name Two.Text#className
+   * @property {String} - A class to be applied to the element to be compatible with CSS styling. Only available for the {@link Two.SvgRenderer}.
+   */
+  _className: '',
+
+  /**
+   * @name Two.Text#visible
+   * @property {Boolean} - Display the text object or not.
+   * @nota-bene For {@link Two.CanvasRenderer} and {@link Two.WebGLRenderer} when set to false all updating is disabled improving performance dramatically with many objects in the scene.
+   */
+  _visible: true,
+
+  /**
+   * @name Two.Text#mask
+   * @property {Two.Shape} - The shape whose alpha property becomes a clipping area for the text.
+   * @nota-bene This property is currently not working becuase of SVG spec issues found here {@link https://code.google.com/p/chromium/issues/detail?id=370951}.
+   */
+  _mask: null,
+
+  /**
+   * @name Two.Text#clip
+   * @property {Two.Shape} - Object to define clipping area.
+   * @nota-bene This property is currently not working becuase of SVG spec issues found here {@link https://code.google.com/p/chromium/issues/detail?id=370951}.
+   */
+  _clip: false,
+
+  /**
+   * @name Two.Text#_dashes
+   * @private
+   * @see {@link Two.Text#dashes}
+   */
+  _dashes: [],
+
+  /**
+   * @name Two.Text#remove
+   * @function
+   * @description Remove self from the scene / parent.
+   */
+  remove: function() {
+
+    if (!this.parent) {
+      return this;
+    }
+
+    this.parent.remove(this);
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Text#clone
+   * @function
+   * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+   * @returns {Two.Text}
+   * @description Create a new instance of {@link Two.Text} with the same properties of the current text object.
+   */
+  clone: function(parent) {
+
+    var clone = new Text(this.value);
+    clone.translation.copy(this.translation);
+    clone.rotation = this.rotation;
+    clone.scale = this.scale;
+
+    _.each(Text.Properties, function(property) {
+      clone[property] = this[property];
+    }, this);
+
+    if (this.matrix.manual) {
+      clone.matrix.copy(this.matrix);
+    }
+
+    if (parent) {
+      parent.add(clone);
+    }
+
+    return clone._update();
+
+  },
+
+  /**
+   * @name Two.Text#toObject
+   * @function
+   * @returns {Object}
+   * @description Return a JSON compatible plain object that represents the text object.
+   */
+  toObject: function() {
+
+    var result = {
+      translation: this.translation.toObject(),
+      rotation: this.rotation,
+      scale: this.scale
+    };
+
+    if (this.matrix.manual) {
+      result.matrix = this.matrix.toObject();
+    }
+
+    _.each(Text.Properties, function(property) {
+      result[property] = this[property];
+    }, this);
+
+    return result;
+
+  },
+
+  /**
+   * @name Two.Text#noFill
+   * @function
+   * @description Short hand method to set fill to `transparent`.
+   */
+  noFill: function() {
+    this.fill = 'transparent';
+    return this;
+  },
+
+  /**
+   * @name Two.Text#noStroke
+   * @function
+   * @description Short hand method to set stroke to `transparent`.
+   */
+  noStroke: function() {
+    this.stroke = undefined;
+    this.linewidth = undefined;
+    return this;
+  },
+
+  // A shim to not break `getBoundingClientRect` calls.
+  // TODO: Implement a way to calculate proper bounding
+  // boxes of `Two.Text`.
+
+  /**
+   * @name Two.Text#getBoundingClientRect
+   * @function
+   * @param {Boolean} [shallow=false] - Describes whether to calculate off local matrix or world matrix.
+   * @returns {Object} - Returns object with top, left, right, bottom, width, height attributes.
+   * @description Return an object with top, left, right, bottom, width, and height parameters of the text object.
+   */
+  getBoundingClientRect: function(shallow) {
+
+    var matrix, a, b, c, d;
+    var left, right, top, bottom;
+
+    // TODO: Update this to not __always__ update. Just when it needs to.
+    this._update(true);
+
+    matrix = shallow ? this._matrix : getComputedMatrix(this);
+
+    var height = this.leading;
+    var width = this.value.length * this.size * Text.Ratio;
+    var border = (this._linewidth || 0) / 2;
+
+    switch (this.alignment) {
+      case 'left':
+        left = - border;
+        right = width + border;
+        break;
+      case 'right':
+        left = - (width + border);
+        right = border;
+        break;
+      default:
+        left = - (width / 2 + border);
+        right = width / 2 + border;
+    }
+
+    switch (this.baseline) {
+      case 'top':
+        top = - border;
+        bottom = height + border;
+        break;
+      case 'bottom':
+        top = - (height + border);
+        bottom = border;
+        break;
+      default:
+        top = - (height / 2 + border);
+        bottom = height / 2 + border;
+    }
+
+    a = matrix.multiply(left, top, 1);
+    b = matrix.multiply(left, bottom, 1);
+    c = matrix.multiply(right, top, 1);
+    d = matrix.multiply(right, bottom, 1);
+
+    top = min(a.y, b.y, c.y, d.y);
+    left = min(a.x, b.x, c.x, d.x);
+    right = max(a.x, b.x, c.x, d.x);
+    bottom = max(a.y, b.y, c.y, d.y);
+
+    return {
+      top: top,
+      left: left,
+      right: right,
+      bottom: bottom,
+      width: right - left,
+      height: bottom - top
+    };
+
+  },
+
+  /**
+   * @name Two.Text#flagReset
+   * @function
+   * @private
+   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+   */
+  flagReset: function() {
+
+    this._flagValue = this._flagFamily = this._flagSize =
+      this._flagLeading = this._flagAlignment = this._flagFill =
+      this._flagStroke = this._flagLinewidth = this._flagOpacity =
+      this._flagVisible = this._flagClip = this._flagDecoration =
+      this._flagClassName = this._flagBaseline = this._flagWeight =
+        this._flagStyle = false;
+
+    Shape.prototype.flagReset.call(this);
+
+    return this;
+
+  }
+
+});
+
+Text.MakeObservable(Text.prototype);
+
+// https://github.com/jonobr1/two.js/issues/507#issuecomment-777159213
+var regex = {
+  path: /[+-]?(?:\d*\.\d+|\d+)(?:[eE][+-]\d+)?/g
+};
+
+var alignments = {
+  start: 'left',
+  middle: 'center',
+  end: 'right'
+};
+
+/**
+ * @name Two.Utils.getAlignment
+ * @function
+ * @param {AlignmentString}
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor}
+ */
+var getAlignment = function(anchor) {
+  return alignments[anchor];
+};
+
+var getBaseline = function(node) {
+  var a = node.getAttribute('dominant-baseline');
+  var b = node.getAttribute('alignment-baseline');
+  return a || b;
+};
+
+var getTagName = function(tag) {
+  return tag.replace(/svg:/ig, '').toLowerCase();
+};
+
+var applyTransformsToVector = function(transforms, vector) {
+
+  vector.x += transforms.translateX;
+  vector.y += transforms.translateY;
+
+  vector.x *= transforms.scaleX;
+  vector.y *= transforms.scaleY;
+
+  if (transforms.rotation !== 0) {
+    // TODO: Test further
+    var l = vector.length();
+    vector.x = l * Math.cos(transforms.rotation);
+    vector.y = l * Math.sin(transforms.rotation);
+  }
+
+};
+
+/**
+ * @name Two.Utils.extractCSSText
+ * @function
+ * @param {String} text - The CSS text body to be parsed and extracted.
+ * @param {Object} [styles] - The styles object to apply CSS key values to.
+ * @returns {Object} styles
+ * @description Parse CSS text body and apply them as key value pairs to a JavaScript object.
+ */
+var extractCSSText = function(text, styles) {
+
+  var commands, command, name, value;
+
+  if (!styles) {
+    styles = {};
+  }
+
+  commands = text.split(';');
+
+  for (var i = 0; i < commands.length; i++) {
+    command = commands[i].split(':');
+    name = command[0];
+    value = command[1];
+    if (typeof name === 'undefined' || typeof value === 'undefined') {
+      continue;
+    }
+    styles[name] = value.replace(/\s/, '');
+  }
+
+  return styles;
+
+};
+
+/**
+ * @name Two.Utils.getSvgStyles
+ * @function
+ * @param {SVGElement} node - The SVG node to parse.
+ * @returns {Object} styles
+ * @description Get the CSS comands from the `style` attribute of an SVG node and apply them as key value pairs to a JavaScript object.
+ */
+var getSvgStyles = function(node) {
+
+  var styles = {};
+  var attributes = getSvgAttributes(node);
+  var length = Math.max(attributes.length, node.style.length);
+
+  for (var i = 0; i < length; i++) {
+
+    var command = node.style[i];
+    var attribute = attributes[i];
+
+    if (command) {
+      styles[command] = node.style[command];
+    }
+    if (attribute) {
+      styles[attribute] = node.getAttribute(attribute);
+    }
+
+  }
+
+  return styles;
+
+};
+
+var getSvgAttributes = function(node) {
+
+  var attributes = node.getAttributeNames();
+
+  // Reserved attributes to remove
+  var keywords = ['id', 'class', 'transform', 'xmlns', 'viewBox'];
+
+  for (var i = 0; i < keywords.length; i++) {
+    var keyword = keywords[i];
+    var index = Array.prototype.indexOf.call(attributes, keyword);
+    if (index >= 0) {
+      attributes.splice(index, 1);
+    }
+  }
+
+  return attributes;
+
+};
+
+/**
+ * @name Two.Utils.applySvgViewBox
+ * @function
+ * @param {Two.Shape} node - The Two.js object to apply viewbox matrix to
+ * @param {String} value - The viewBox value from the SVG attribute
+ * @returns {Two.Shape} node
+ * @description Applies the transform of the SVG Viewbox on a given node.
+ */
+var applySvgViewBox = function(node, value) {
+
+  var elements = value.split(/\s/);
+
+  var x = parseFloat(elements[0]);
+  var y = parseFloat(elements[1]);
+  var width = parseFloat(elements[2]);
+  var height = parseFloat(elements[3]);
+
+  var s = Math.min(this.width / width, this.height / height);
+
+  node.translation.x -= x * s;
+  node.translation.y -= y * s;
+  node.scale = s;
+
+  return node;
+
+};
+
+/**
+ * @name Two.Utils.applySvgAttributes
+ * @function
+ * @param {SVGElement} node - An SVG Node to extrapolate attributes from.
+ * @param {Two.Shape} elem - The Two.js object to apply extrapolated attributes to.
+ * @returns {Two.Shape} The Two.js object passed now with applied attributes.
+ * @description This function iterates through an SVG Node's properties and stores ones of interest. It tries to resolve styles applied via CSS as well.
+ * @TODO Reverse calculate {@link Two.Gradient}s for fill / stroke of any given path.
+ */
+var applySvgAttributes = function(node, elem, parentStyles) {
+
+  var styles = {}, attributes = {}, extracted = {}, i, m, key, value, attr;
+  var transforms, x, y;
+  var id, scene, ref, tagName;
+
+  // Not available in non browser environments
+  if (root$1.getComputedStyle) {
+    // Convert CSSStyleDeclaration to a normal object
+    var computedStyles = root$1.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 (typeof value !== 'undefined') {
+        styles[key] = value;
+      }
+    }
+  }
+
+  // Convert NodeMap to a normal object
+  for (i = 0; i < node.attributes.length; i++) {
+    attr = node.attributes[i];
+    if (/style/i.test(attr.nodeName)) {
+      extractCSSText(attr.value, extracted);
+    } else {
+      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 (typeof styles.opacity !== 'undefined') {
+    styles['stroke-opacity'] = styles.opacity;
+    styles['fill-opacity'] = styles.opacity;
+    delete styles.opacity;
+  }
+
+  // Merge attributes and applied styles (attributes take precedence)
+  if (parentStyles) {
+    _.defaults(styles, parentStyles);
+  }
+  _.extend(styles, extracted, attributes);
+
+  // Similarly visibility is influenced by the value of both display and visibility.
+  // Calculate a unified value here which defaults to `true`.
+  styles.visible = !(typeof styles.display === 'undefined' && /none/i.test(styles.display))
+    || (typeof styles.visibility === 'undefined' && /hidden/i.test(styles.visibility));
+
+  // Now iterate the whole thing
+  for (key in styles) {
+    value = styles[key];
+
+    switch (key) {
+      case 'gradientTransform':
+        // TODO: Check this out https://github.com/paperjs/paper.js/blob/develop/src/svg/SvgImport.js#L315
+        if (/none/i.test(value)) break;
+        m = (node.gradientTransform && node.gradientTransform.baseVal && node.gradientTransform.baseVal.length > 0)
+          ? node.gradientTransform.baseVal[0].matrix
+          : (node.getCTM ? node.getCTM() : null);
+
+        if (m === null) break;
+
+        transforms = decomposeMatrix(m);
+
+        switch (elem._renderer.type) {
+          case 'linear-gradient':
+            applyTransformsToVector(transforms, elem.left);
+            applyTransformsToVector(transforms, elem.right);
+            break;
+          case 'radial-gradient':
+            elem.center.x += transforms.translateX;
+            elem.center.y += transforms.translateY;
+
+            elem.focal.x += transforms.translateX;
+            elem.focal.y += transforms.translateY;
+
+            elem.radius *= Math.max(transforms.scaleX, transforms.scaleY);
+            break;
+        }
+
+        break;
+      case 'transform':
+        // TODO: Check this out https://github.com/paperjs/paper.js/blob/develop/src/svg/SvgImport.js#L315
+        if (/none/i.test(value)) break;
+        m = (node.transform && node.transform.baseVal && node.transform.baseVal.length > 0)
+          ? node.transform.baseVal[0].matrix
+          : (node.getCTM ? node.getCTM() : null);
+
+        // Might happen when transform string is empty or not valid.
+        if (m === null) break;
+
+        if (Constants.AutoCalculateImportedMatrices) {
+
+          // Decompose and infer Two.js related properties.
+          transforms = decomposeMatrix(m);
+
+          elem.translation.set(transforms.translateX, transforms.translateY);
+          elem.rotation = Math.PI * (transforms.rotation / 180);
+          elem.scale = new Vector(transforms.scaleX, transforms.scaleY);
+
+          x = parseFloat((styles.x + '').replace('px'));
+          y = parseFloat((styles.y + '').replace('px'));
+
+          // Override based on attributes.
+          if (x) {
+            elem.translation.x = x;
+          }
+
+          if (y) {
+            elem.translation.y = y;
+          }
+
+        } else {
+
+          // Edit the underlying matrix and don't force an auto calc.
+          m = node.getCTM();
+          elem._matrix.manual = true;
+          elem._matrix.set(m.a, m.b, m.c, m.d, m.e, m.f);
+
+        }
+
+        break;
+      case 'viewBox':
+        applySvgViewBox.call(this, elem, value);
+        break;
+      case 'visible':
+        if (elem instanceof Group) {
+          elem._visible = value;
+          break;
+        }
+        elem.visible = value;
+        break;
+      case 'stroke-linecap':
+        if (elem instanceof Group) {
+          elem._cap = value;
+          break;
+        }
+        elem.cap = value;
+        break;
+      case 'stroke-linejoin':
+        if (elem instanceof Group) {
+          elem._join = value;
+          break;
+        }
+        elem.join = value;
+        break;
+      case 'stroke-miterlimit':
+        if (elem instanceof Group) {
+          elem._miter = value;
+          break;
+        }
+        elem.miter = value;
+        break;
+      case 'stroke-width':
+        if (elem instanceof Group) {
+          elem._linewidth = parseFloat(value);
+          break;
+        }
+        elem.linewidth = parseFloat(value);
+        break;
+      case 'opacity':
+      case 'stroke-opacity':
+      case 'fill-opacity':
+        // Only apply styles to rendered shapes
+        // in the scene.
+        if (elem instanceof Group) {
+          elem._opacity = parseFloat(value);
+          break;
+        }
+        elem.opacity = parseFloat(value);
+        break;
+      case 'clip-path':
+        if (/url\(#.*\)/i.test(value)) {
+          id = value.replace(/url\(#(.*)\)/i, '$1');
+          if (read.defs.current && read.defs.current.contains(id)) {
+            ref = read.defs.current.get(id);
+            if (ref && ref.childNodes.length > 0) {
+              ref = ref.childNodes[0];
+              tagName = getTagName(ref.nodeName);
+              elem.mask = read[tagName].call(this, ref, {});
+              switch (elem._renderer.type) {
+                case 'path':
+                  // The matrix here needs to change to insure that the object
+                  // clipping is in the same coordinate space as the `elem`.
+                  elem.position.add(elem.mask.position);
+                  elem.mask.position.clear();
+                  break;
+              }
+            }
+          }
+        }
+        break;
+      case 'fill':
+      case 'stroke':
+        if (elem instanceof Group) {
+          key = '_' + key;
+        }
+        if (/url\(#.*\)/i.test(value)) {
+          id = value.replace(/url\(#(.*)\)/i, '$1');
+          if (read.defs.current && read.defs.current.contains(id)) {
+            ref = read.defs.current.get(id);
+            tagName = getTagName(ref.nodeName);
+            ref = read[tagName].call(this, ref, {});
+          } else {
+            scene = getScene(this);
+            ref = scene.getById(id);
+          }
+          elem[key] = ref;
+        } else {
+          elem[key] = (/none/i.test(value)) ? 'transparent' : value;
+        }
+        break;
+      case 'id':
+        elem.id = value;
+        // Overwritten id for non-conflicts on same page SVG documents
+        // TODO: Make this non-descructive
+        node.id = value + '-' + Constants.Identifier + 'applied';
+        break;
+      case 'class':
+      case 'className':
+        elem.classList = value.split(' ');
+        break;
+      case 'x':
+      case 'y':
+        var ca = elem instanceof Gradient;
+        var cb = elem instanceof LinearGradient;
+        var cc = elem instanceof RadialGradient;
+        if (ca || cb || cc) {
+          break;
+        }
+        if (value.match('[a-z%]$') && !value.endsWith('px')) {
+          var error = new TwoError(
+            'only pixel values are supported with the ' + key + ' attribute.');
+          console.warn(error.name, error.message);
+        }
+        elem.translation[key] = parseFloat(value);
+        break;
+      case 'font-family':
+        if (elem instanceof Text) {
+          elem.family = value;
+        }
+        break;
+      case 'font-size':
+        if (elem instanceof Text) {
+          elem.size = value;
+        }
+        break;
+      case 'font-weight':
+        if (elem instanceof Text) {
+          elem.weight = value;
+        }
+        break;
+      case 'font-style':
+        if (elem instanceof Text) {
+          elem.style = value;
+        }
+        break;
+      case 'text-decoration':
+        if (elem instanceof Text) {
+          elem.decoration = value;
+        }
+        break;
+      case 'line-height':
+        if (elem instanceof Text) {
+          elem.leading = value;
+        }
+        break;
+    }
+  }
+
+  return styles;
+
+};
+
+/**
+ * @name Two.Utils.updateDefsCache
+ * @function
+ * @param {SVGElement} node - The SVG Node with which to update the defs cache.
+ * @param {Object} Object - The defs cache to be updated.
+ * @description Update the cache of children of <defs /> tags.
+ */
+var updateDefsCache = function(node, defsCache) {
+  for (var i = 0, l = node.childNodes.length; i < l; i++) {
+    var n = node.childNodes[i];
+    if (!n.id) continue;
+
+    var tagName = getTagName(node.nodeName);
+    if (tagName === '#text') continue;
+
+    defsCache.add(n.id, n);
+  }
+};
+
+/**
+ * @name Two.Utils.getScene
+ * @param {Two.Shape} node - The currently available object in the scenegraph.
+ * @returns {Group} - The highest order {@link Two.Group} in the scenegraph.
+ * @property {Function}
+ */
+var getScene = function(node) {
+
+  while (node.parent) {
+    node = node.parent;
+  }
+
+  return node.scene;
+
+};
+
+/**
+ * @name Two.Utils.read
+ * @property {Object} read - A map of functions to read any number of SVG node types and create Two.js equivalents of them. Primarily used by the {@link Two#interpret} method.
+ */
+var read = {
+
+  svg: function(node) {
+
+    var defs = read.defs.current = new Registry();
+    var elements = node.getElementsByTagName('defs');
+
+    for (var i = 0; i < elements.length; i++) {
+      updateDefsCache(elements[i], defs);
+    }
+
+    var svg = read.g.call(this, node);
+    // var viewBox = node.getAttribute('viewBox');
+
+    svg.defs = defs;  // Export out the <defs /> for later use
+    // Utils.applySvgViewBox(svg, viewBox);
+
+    delete read.defs.current;
+
+    return svg;
+
+  },
+
+  defs: function(node) {
+    return null;
+  },
+
+  use: function(node, styles) {
+
+    var error;
+    var href = node.getAttribute('href') || node.getAttribute('xlink:href');
+    if (!href) {
+      error = new TwoError('encountered <use /> with no href.');
+      console.warn(error.name, error.message);
+      return null;
+    }
+
+    var id = href.slice(1);
+    if (!read.defs.current.contains(id)) {
+      error = new TwoError(
+        'unable to find element for reference ' + href + '.');
+      console.warn(error.name, error.message);
+      return null;
+    }
+
+    var template = read.defs.current.get(id);
+    var fullNode = template.cloneNode(true);
+    var overwriteAttrs = ['x', 'y', 'width', 'height', 'href', 'xlink:href'];
+
+    for (var i = 0; i < node.attributes.length; i++) {
+      var attr = node.attributes[i];
+      var ca = overwriteAttrs.includes(attr.nodeName);
+      var cb = !fullNode.hasAttribute(attr.nodeName);
+      if (ca || cb) {
+        fullNode.setAttribute(attr.nodeName, attr.value);
+      }
+    }
+
+    var tagName = getTagName(fullNode.nodeName);
+    return read[tagName].call(this, fullNode, styles);
+
+  },
+
+  g: function(node, parentStyles) {
+
+    var styles;
+    var group = new Group();
+
+    applySvgAttributes.call(this, node, group, parentStyles);
+
+    this.add(group);
+
+    // Switched up order to inherit more specific styles
+    styles = getSvgStyles.call(this, node);
+
+    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 = getTagName(tag);
+
+      if (tagName in read) {
+        var o = read[tagName].call(group, n, styles);
+        if (!!o && !o.parent) {
+          group.add(o);
+        }
+      }
+    }
+
+    return group;
+
+  },
+
+  polygon: function(node, parentStyles) {
+
+    var points = node.getAttribute('points');
+
+    var verts = [];
+    points.replace(/(-?[\d.eE-]+)[,|\s](-?[\d.eE-]+)/g, function(match, p1, p2) {
+      verts.push(new Anchor(parseFloat(p1), parseFloat(p2)));
+    });
+
+    var poly = new Path(verts, true).noStroke();
+    poly.fill = 'black';
+
+    applySvgAttributes.call(this, node, poly, parentStyles);
+
+    return poly;
+
+  },
+
+  polyline: function(node, parentStyles) {
+    var poly = read.polygon.call(this, node, parentStyles);
+    poly.closed = false;
+    return poly;
+  },
+
+  path: function(node, parentStyles) {
+
+    var path = node.getAttribute('d');
+    var points = [];
+    var closed = false, relative = false;
+
+    if (path) {
+
+      // Create a Two.Path from the paths.
+
+      var coord = new Anchor();
+      var control, coords;
+      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 items = command.slice(1).trim().match(regex.path);
+        var type = command[0];
+        var lower = type.toLowerCase();
+        var bin, j, l, ct, times, result = [];
+
+        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':
+            if (items.length > 7) {
+              bin = 7;
+            }
+            break;
+        }
+
+        // This means we have a polybezier.
+        if (bin) {
+
+          for (j = 0, l = items.length, times = 0; j < l; j+=bin) {
+
+            ct = type;
+            if (times > 0) {
+
+              switch (type) {
+                case 'm':
+                  ct = 'l';
+                  break;
+                case 'M':
+                  ct = 'L';
+                  break;
+              }
+
+            }
+
+            result.push(ct + 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
+
+      _.each(commands, function(command, i) {
+
+        var result, x, y;
+        var type = command[0];
+        var lower = type.toLowerCase();
+
+        coords = command.slice(1).trim().match(regex.path);
+        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 Anchor(
+                x, y,
+                undefined, undefined,
+                undefined, undefined,
+                Commands.close
+              );
+              // Make coord be the last `m` command
+              for (var j = points.length - 1; j >= 0; j--) {
+                var point = points[j];
+                if (/m/i.test(point.command)) {
+                  coord = point;
+                  break;
+                }
+              }
+            }
+            break;
+
+          case 'm':
+          case 'l':
+
+            control = undefined;
+
+            x = parseFloat(coords[0]);
+            y = parseFloat(coords[1]);
+
+            result = new Anchor(
+              x, y,
+              undefined, undefined,
+              undefined, undefined,
+              /m/i.test(lower) ? Commands.move : 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 = /h/i.test(lower) ? 'x' : 'y';
+            var b = /x/i.test(a) ? 'y' : 'x';
+
+            result = new Anchor(
+              undefined, undefined,
+              undefined, undefined,
+              undefined, undefined,
+              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 Vector();//.copy(coord);
+            }
+
+            if (/c/i.test(lower)) {
+
+              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)) {
+              Anchor.AppendCurveProperties(coord);
+            }
+
+            coord.controls.right.set(x2 - coord.x, y2 - coord.y);
+            result = new Anchor(
+              x4, y4,
+              x3 - x4, y3 - y4,
+              undefined, undefined,
+              Commands.curve
+            );
+
+            coord = result;
+            control = result.controls.left;
+
+            break;
+
+          case 't':
+          case 'q':
+
+            x1 = coord.x;
+            y1 = coord.y;
+
+            if (!control) {
+              control = new Vector();
+            }
+
+            if (/q/i.test(lower)) {
+
+              x2 = parseFloat(coords[0]);
+              y2 = parseFloat(coords[1]);
+              x3 = parseFloat(coords[0]);
+              y3 = parseFloat(coords[1]);
+              x4 = parseFloat(coords[2]);
+              y4 = parseFloat(coords[3]);
+
+            } else {
+
+              reflection = getReflection(coord, control, relative);
+
+              x2 = reflection.x;
+              y2 = reflection.y;
+              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)) {
+              Anchor.AppendCurveProperties(coord);
+            }
+
+            coord.controls.right.set(
+              (x2 - coord.x) * 0.33, (y2 - coord.y) * 0.33);
+            result = new Anchor(
+              x4, y4,
+              x3 - x4, y3 - y4,
+              undefined, undefined,
+              Commands.curve
+            );
+
+            coord = result;
+            control = result.controls.left;
+
+            break;
+
+          case 'a':
+
+            x1 = coord.x;
+            y1 = coord.y;
+
+            var rx = parseFloat(coords[0]);
+            var ry = parseFloat(coords[1]);
+            var xAxisRotation = parseFloat(coords[2]);// * 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;
+            }
+
+            var anchor = new Anchor(x4, y4);
+            anchor.command = Commands.arc;
+            anchor.rx = rx;
+            anchor.ry = ry;
+            anchor.xAxisRotation = xAxisRotation;
+            anchor.largeArcFlag = largeArcFlag;
+            anchor.sweepFlag = sweepFlag;
+
+            result = anchor;
+
+            coord = anchor;
+            control = undefined;
+
+            break;
+
+        }
+
+        if (result) {
+          if (Array.isArray(result)) {
+            points = points.concat(result);
+          } else {
+            points.push(result);
+          }
+        }
+
+      });
+
+    }
+
+    path = new 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);
+    });
+
+    applySvgAttributes.call(this, node, path, parentStyles);
+
+    path.translation.addSelf(rect.centroid);
+
+    return path;
+
+  },
+
+  circle: function(node, parentStyles) {
+
+    var x = parseFloat(node.getAttribute('cx'));
+    var y = parseFloat(node.getAttribute('cy'));
+    var r = parseFloat(node.getAttribute('r'));
+
+    var circle = new Circle(0, 0, r).noStroke();
+    circle.fill = 'black';
+
+    applySvgAttributes.call(this, node, circle, parentStyles);
+
+    circle.translation.x = x;
+    circle.translation.y = y;
+
+    return circle;
+
+  },
+
+  ellipse: function(node, parentStyles) {
+
+    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 Ellipse(0, 0, width, height).noStroke();
+    ellipse.fill = 'black';
+
+    applySvgAttributes.call(this, node, ellipse, parentStyles);
+
+    ellipse.translation.x = x;
+    ellipse.translation.y = y;
+
+    return ellipse;
+
+  },
+
+  rect: function(node, parentStyles) {
+
+    var rx = parseFloat(node.getAttribute('rx'));
+    var ry = parseFloat(node.getAttribute('ry'));
+
+    if (!_.isNaN(rx) || !_.isNaN(ry)) {
+      return read['rounded-rect'](node);
+    }
+
+    var width = parseFloat(node.getAttribute('width'));
+    var height = parseFloat(node.getAttribute('height'));
+
+    var w2 = width / 2;
+    var h2 = height / 2;
+
+    var rect = new Rectangle(0, 0, width, height)
+      .noStroke();
+    rect.fill = 'black';
+
+    applySvgAttributes.call(this, node, rect, parentStyles);
+
+    // For rectangles, (x, y) is the center of the shape rather than the top
+    // left corner.
+    rect.translation.x += w2;
+    rect.translation.y += h2;
+
+    return rect;
+
+  },
+
+  'rounded-rect': function(node, parentStyles) {
+
+    var rx = parseFloat(node.getAttribute('rx')) || 0;
+    var ry = parseFloat(node.getAttribute('ry')) || 0;
+
+    var width = parseFloat(node.getAttribute('width'));
+    var height = parseFloat(node.getAttribute('height'));
+
+    var w2 = width / 2;
+    var h2 = height / 2;
+    var radius = new Vector(rx, ry);
+
+    var rect = new RoundedRectangle(0, 0, width, height, radius)
+      .noStroke();
+    rect.fill = 'black';
+
+    applySvgAttributes.call(this, node, rect, parentStyles);
+
+    // For rectangles, (x, y) is the center of the shape rather than the top
+    // left corner.
+    rect.translation.x += w2;
+    rect.translation.y += h2;
+
+    return rect;
+
+  },
+
+  line: function(node, parentStyles) {
+
+    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 Line(x1, y1, x2, y2).noFill();
+
+    applySvgAttributes.call(this, node, line, parentStyles);
+
+    return line;
+
+  },
+
+  lineargradient: function(node, parentStyles) {
+
+    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 = child.getAttribute('offset');
+      if (/%/ig.test(offset)) {
+        offset = parseFloat(offset.replace(/%/ig, '')) / 100;
+      }
+      offset = parseFloat(offset);
+
+      var color = child.getAttribute('stop-color');
+      var opacity = child.getAttribute('stop-opacity');
+      var style = child.getAttribute('style');
+
+      var matches;
+      if (color === null) {
+        matches = style ? style.match(/stop-color:\s?([#a-fA-F0-9]*)/) : false;
+        color = matches && matches.length > 1 ? matches[1] : undefined;
+      }
+
+      if (opacity === null) {
+        matches = style ? style.match(/stop-opacity:\s?([0-9.-]*)/) : false;
+        opacity = matches && matches.length > 1 ? parseFloat(matches[1]) : 1;
+      } else {
+        opacity = parseFloat(opacity);
+      }
+
+      stops.push(new Stop(offset, color, opacity));
+
+    }
+
+    var gradient = new LinearGradient(x1 - ox, y1 - oy, x2 - ox,
+      y2 - oy, stops);
+
+    applySvgAttributes.call(this, node, gradient, parentStyles);
+
+    return gradient;
+
+  },
+
+  radialgradient: function(node, parentStyles) {
+
+    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 = child.getAttribute('offset');
+      if (/%/ig.test(offset)) {
+        offset = parseFloat(offset.replace(/%/ig, '')) / 100;
+      }
+      offset = parseFloat(offset);
+
+      var color = child.getAttribute('stop-color');
+      var opacity = child.getAttribute('stop-opacity');
+      var style = child.getAttribute('style');
+
+      var matches;
+      if (color === null) {
+        matches = style ? style.match(/stop-color:\s?([#a-fA-F0-9]*)/) : false;
+        color = matches && matches.length > 1 ? matches[1] : undefined;
+      }
+
+      if (opacity === null) {
+        matches = style ? style.match(/stop-opacity:\s?([0-9.-]*)/) : false;
+        opacity = matches && matches.length > 1 ? parseFloat(matches[1]) : 1;
+      } else {
+        opacity = parseFloat(opacity);
+      }
+
+      stops.push(new Stop(offset, color, opacity));
+
+    }
+
+    var gradient = new RadialGradient(cx - ox, cy - oy, r,
+      stops, fx - ox, fy - oy);
+
+    applySvgAttributes.call(this, node, gradient, parentStyles);
+
+    return gradient;
+
+  },
+
+  text: function(node, parentStyles) {
+
+    var alignment = getAlignment(node.getAttribute('text-anchor')) || 'left';
+    var baseline = getBaseline(node) || 'baseline';
+    var message = node.textContent;
+
+    var text = new Text(message);
+
+    applySvgAttributes.call(this, node, text, parentStyles);
+
+    text.alignment = alignment;
+    text.baseline = baseline;
+
+    return text;
+
+  },
+
+  clippath: function(node, parentStyles) {
+    if (read.defs.current && !read.defs.current.contains(node.id)) {
+      read.defs.current.add(node.id, node);
+    }
+    return null;
+  },
+
+  image: function(node, parentStyles) {
+
+    var href = node.getAttribute('href') || node.getAttribute('xlink:href');
+    if (!href) {
+      var error = new TwoError('encountered <image /> with no href.');
+      console.warn(error.name, error.message);
+      return null;
+    }
+
+    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 sprite = new Sprite(href, x, y);
+
+    if (!_.isNaN(width)) {
+      sprite.width = width;
+    }
+    if (!_.isNaN(height)) {
+      sprite.height = height;
+    }
+
+    applySvgAttributes.call(this, node, sprite, parentStyles);
+
+    return sprite;
+  }
+
+};
+
+/**
+ * @name Two.Utils.xhr
+ * @function
+ * @param {String} path
+ * @param {Function} callback
+ * @returns {XMLHttpRequest} The constructed and called XHR request.
+ * @description Canonical method to initiate `GET` requests in the browser. Mainly used by {@link Two#load} method.
+ */
+function xhr(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;
+
+}
+
+/**
+ * @name Two.ImageSequence
+ * @class
+ * @extends Two.Rectangle
+ * @param {String|String[]|Two.Texture|Two.Texture[]} paths - A list of URLs or {@link Two.Texture}s.
+ * @param {Number} [ox=0] - The initial `x` position of the Two.ImageSequence.
+ * @param {Number} [oy=0] - The initial `y` position of the Two.ImageSequence.
+ * @param {Number} [frameRate=30] - The frame rate at which the images should playback at.
+ * @description A convenient package to display still or animated images organized as a series of still images.
+ */
+function ImageSequence(paths, ox, oy, frameRate) {
+
+  // Not using default constructor of Rectangle due to odd `beginning` / `ending` behavior.
+  // See: https://github.com/jonobr1/two.js/issues/383
+  Path.call(this, [
+    new Anchor(),
+    new Anchor(),
+    new Anchor(),
+    new Anchor()
+  ], true);
+
+  this._renderer.flagTextures = ImageSequence.FlagTextures.bind(this);
+  this._renderer.bindTextures = ImageSequence.BindTextures.bind(this);
+  this._renderer.unbindTextures = ImageSequence.UnbindTextures.bind(this);
+
+  this.noStroke();
+  this.noFill();
+
+  /**
+   * @name Two.ImageSequence#textures
+   * @property {Two.Texture[]} - A list of textures to be used as frames for animating the {@link Two.ImageSequence}.
+   */
+  if (Array.isArray(paths)) {
+    this.textures = paths.map(ImageSequence.GenerateTexture.bind(this));
+  } else {
+    // If just a single path convert into a single Two.Texture
+    this.textures = [ImageSequence.GenerateTexture(paths)];
+  }
+
+  this.origin = new Vector();
+
+  this._update();
+  this.translation.set(ox || 0, oy || 0);
+
+  /**
+   * @name Two.ImageSequence#frameRate
+   * @property {Number} - The number of frames to animate against per second.
+   */
+  if (typeof frameRate === 'number') {
+    this.frameRate = frameRate;
+  } else {
+    this.frameRate = ImageSequence.DefaultFrameRate;
+  }
+
+  /**
+   * @name Two.ImageSequence#index
+   * @property {Number} - The index of the current tile of the sprite to display. Defaults to `0`.
+   */
+  this.index = 0;
+
+}
+
+_.extend(ImageSequence, {
+
+  /**
+   * @name Two.ImageSequence.Properties
+   * @property {String[]} - A list of properties that are on every {@link Two.ImageSequence}.
+   */
+  Properties: [
+    'frameRate',
+    'index'
+  ],
+
+  /**
+   * @name Two.ImageSequence.DefaultFrameRate
+   * @property The default frame rate that {@link Two.ImageSequence#frameRate} is set to when instantiated.
+   */
+  DefaultFrameRate: 30,
+
+  /**
+   * @name Two.ImageSequence.FlagTextures
+   * @function
+   * @description Cached method to let renderers know textures have been updated on a {@link Two.ImageSequence}.
+   */
+  FlagTextures: function() {
+    this._flagTextures = true;
+  },
+
+  /**
+   * @name Two.ImageSequence.BindTextures
+   * @function
+   * @description Cached method to let {@link Two.ImageSequence} know textures have been added to the instance.
+   */
+  BindTextures: function(items) {
+
+    var i = items.length;
+    while (i--) {
+      items[i].bind(Events.Types.change, this._renderer.flagTextures);
+    }
+
+    this._renderer.flagTextures();
+
+  },
+
+  /**
+   * @name Two.ImageSequence.UnbindVertices
+   * @function
+   * @description Cached method to let {@link Two.ImageSequence} know textures have been removed from the instance.
+   */
+  UnbindTextures: function(items) {
+
+    var i = items.length;
+    while (i--) {
+      items[i].unbind(Events.Types.change, this._renderer.flagTextures);
+    }
+
+    this._renderer.flagTextures();
+
+  },
+
+  /**
+   * @name Two.ImageSequence.MakeObservable
+   * @function
+   * @param {Object} object - The object to make observable.
+   * @description Convenience function to apply observable qualities of a {@link Two.ImageSequence} to any object. Handy if you'd like to extend or inherit the {@link Two.ImageSequence} class on a custom class.
+   */
+  MakeObservable: function(obj) {
+
+    Rectangle.MakeObservable(obj);
+    _.each(ImageSequence.Properties, defineGetterSetter, obj);
+
+    Object.defineProperty(obj, 'textures', {
+
+      enumerable: true,
+
+      get: function() {
+        return this._textures;
+      },
+
+      set: function(textures) {
+
+        var bindTextures = this._renderer.bindTextures;
+        var unbindTextures = this._renderer.unbindTextures;
+
+        // Remove previous listeners
+        if (this._textures) {
+          this._textures
+            .unbind(Events.Types.insert, bindTextures)
+            .unbind(Events.Types.remove, unbindTextures);
+        }
+
+        // Create new Collection with copy of vertices
+        this._textures = new Collection((textures || []).slice(0));
+
+        // Listen for Collection changes and bind / unbind
+        this._textures
+          .bind(Events.Types.insert, bindTextures)
+          .bind(Events.Types.remove, unbindTextures);
+
+        // Bind Initial Textures
+        bindTextures(this._textures);
+
+      }
+
+    });
+
+  },
+
+  /**
+   * @name Two.ImageSequence.GenerateTexture
+   * @property {Function} - Shorthand function to prepare source image material into readable format by {@link Two.ImageSequence}.
+   * @param {String|Two.Texture} textureOrString - The texture or string to create a {@link Two.Texture} from.
+   * @description Function used internally by {@link Two.ImageSequence} to parse arguments and return {@link Two.Texture}s.
+   * @returns {Two.Texture}
+   */
+  GenerateTexture: function(obj) {
+    if (obj instanceof Texture) {
+      return obj;
+    } else if (typeof obj === 'string') {
+      return new Texture(obj);
+    }
+  }
+
+});
+
+_.extend(ImageSequence.prototype, Rectangle.prototype, {
+
+  constructor: ImageSequence,
+
+  /**
+   * @name Two.ImageSequence#_flagTextures
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.ImageSequence#textures} need updating.
+   */
+  _flagTextures: false,
+
+  /**
+   * @name Two.ImageSequence#_flagFrameRate
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.ImageSequence#frameRate} needs updating.
+   */
+  _flagFrameRate: false,
+
+  /**
+   * @name Two.ImageSequence#_flagIndex
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.ImageSequence#index} needs updating.
+   */
+  _flagIndex: false,
+
+  // Private variables
+
+  /**
+   * @name Two.ImageSequence#_amount
+   * @private
+   * @property {Number} - Number of frames for a given {@link Two.ImageSequence}.
+   */
+  _amount: 1,
+
+  /**
+   * @name Two.ImageSequence#_duration
+   * @private
+   * @property {Number} - Number of milliseconds a {@link Two.ImageSequence}.
+   */
+  _duration: 0,
+
+  /**
+   * @name Two.ImageSequence#_index
+   * @private
+   * @property {Number} - The current frame the {@link Two.ImageSequence} is currently displaying.
+   */
+  _index: 0,
+
+  /**
+   * @name Two.ImageSequence#_startTime
+   * @private
+   * @property {Milliseconds} - Epoch time in milliseconds of when the {@link Two.ImageSequence} started.
+   */
+  _startTime: 0,
+
+  /**
+   * @name Two.ImageSequence#_playing
+   * @private
+   * @property {Boolean} - Dictates whether the {@link Two.ImageSequence} is animating or not.
+   */
+  _playing: false,
+
+  /**
+   * @name Two.ImageSequence#_firstFrame
+   * @private
+   * @property {Number} - The frame the {@link Two.ImageSequence} should start with.
+   */
+  _firstFrame: 0,
+
+  /**
+   * @name Two.ImageSequence#_lastFrame
+   * @private
+   * @property {Number} - The frame the {@link Two.ImageSequence} should end with.
+   */
+  _lastFrame: 0,
+
+  /**
+   * @name Two.ImageSequence#_playing
+   * @private
+   * @property {Boolean} - Dictates whether the {@link Two.ImageSequence} should loop or not.
+   */
+  _loop: true,
+
+  // Exposed through getter-setter
+
+  /**
+   * @name Two.ImageSequence#_textures
+   * @private
+   * @see {@link Two.ImageSequence#textures}
+   */
+  _textures: null,
+
+  /**
+   * @name Two.ImageSequence#_frameRate
+   * @private
+   * @see {@link Two.ImageSequence#frameRate}
+   */
+  _frameRate: 0,
+
+  /**
+   * @name Two.ImageSequence#_origin
+   * @private
+   * @see {@link Two.ImageSequence#origin}
+   */
+  _origin: null,
+
+  /**
+   * @name Two.ImageSequence#play
+   * @function
+   * @param {Number} [firstFrame=0] - The index of the frame to start the animation with.
+   * @param {Number} [lastFrame] - The index of the frame to end the animation with. Defaults to the last item in the {@link Two.ImageSequence#textures}.
+   * @param {Function} [onLastFrame] - Optional callback function to be triggered after playing the last frame. This fires multiple times when the image sequence is looped.
+   * @description Initiate animation playback of a {@link Two.ImageSequence}.
+   */
+  play: function(firstFrame, lastFrame, onLastFrame) {
+
+    this._playing = true;
+    this._firstFrame = 0;
+    this._lastFrame = this.amount - 1;
+    this._startTime = _.performance.now();
+
+    if (typeof firstFrame === 'number') {
+      this._firstFrame = firstFrame;
+    }
+    if (typeof lastFrame === 'number') {
+      this._lastFrame = lastFrame;
+    }
+    if (typeof onLastFrame === 'function') {
+      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;
+
+  },
+
+  /**
+   * @name Two.ImageSequence#pause
+   * @function
+   * @description Halt animation playback of a {@link Two.ImageSequence}.
+   */
+  pause: function() {
+
+    this._playing = false;
+    return this;
+
+  },
+
+  /**
+   * @name Two.ImageSequence#stop
+   * @function
+   * @description Halt animation playback of a {@link Two.ImageSequence} and set the current frame back to the first frame.
+   */
+  stop: function() {
+
+    this._playing = false;
+    this._index = this._firstFrame;
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.ImageSequence#clone
+   * @function
+   * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+   * @returns {Two.ImageSequence}
+   * @description Create a new instance of {@link Two.ImageSequence} with the same properties of the current image sequence.
+   */
+  clone: function(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;
+
+  },
+
+  /**
+   * @name Two.ImageSequence#toObject
+   * @function
+   * @returns {Object}
+   * @description Return a JSON compatible plain object that represents the path.
+   */
+  toObject: function() {
+    var object = Rectangle.prototype.toObject.call(this);
+    object.textures = this.textures.map(function(texture) {
+      return texture.toObject();
+    });
+    object.frameRate = this.frameRate;
+    object.index = this.index;
+    object._firstFrame = this._firstFrame;
+    object._lastFrame = this._lastFrame;
+    object._loop = this._loop;
+    return object;
+  },
+
+  /**
+   * @name Two.ImageSequence#_update
+   * @function
+   * @private
+   * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+   * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+   * @nota-bene Try not to call this method more than once a frame.
+   */
+  _update: function() {
+
+    var 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 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;
+
+  },
+
+  /**
+   * @name Two.ImageSequence#flagReset
+   * @function
+   * @private
+   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+   */
+  flagReset: function() {
+
+    this._flagTextures = this._flagFrameRate = false;
+    Rectangle.prototype.flagReset.call(this);
+
+    return this;
+
+  }
+
+});
+
+ImageSequence.MakeObservable(ImageSequence.prototype);
+
+var TWO_PI$2 = Math.PI * 2, HALF_PI = Math.PI / 2;
+
+/**
+ * @name Two.ArcSegment
+ * @class
+ * @extends Two.Path
+ * @param {Number} [x=0] - The x position of the arc segment.
+ * @param {Number} [y=0] - The y position of the arc segment.
+ * @param {Number} [innerRadius=0] - The inner radius value of the arc segment.
+ * @param {Number} [outerRadius=0] - The outer radius value of the arc segment.
+ * @param {Number} [startAngle=0] - The start angle of the arc segment in Number.
+ * @param {Number} [endAngle=6.2831] - The end angle of the arc segment in Number.
+ * @param {Number} [resolution=24] - The number of vertices used to construct the arc segment.
+ */
+function ArcSegment(ox, oy, ir, or, sa, ea, res) {
+
+  var amount = res || (Constants.Resolution * 3);
+  var points = [];
+  for (var i = 0; i < amount; i++) {
+    points.push(new Anchor());
+  }
+
+  Path.call(this, points, true, false, true);
+
+  /**
+   * @name Two.ArcSegment#innerRadius
+   * @property {Number} - The size of the inner radius of the arc segment.
+   */
+  if (typeof ir === 'number') {
+    this.innerRadius = ir;
+  }
+
+  /**
+   * @name Two.ArcSegment#outerRadius
+   * @property {Number} - The size of the outer radius of the arc segment.
+   */
+  if (typeof or === 'number') {
+    this.outerRadius = or;
+  }
+
+  /**
+   * @name Two.ArcSegment#startRadius
+   * @property {Number} - The angle of one side for the arc segment.
+   */
+  if (typeof sa === 'number') {
+    this.startAngle = sa;
+  }
+
+  /**
+   * @name Two.ArcSegment#endAngle
+   * @property {Number} - The angle of the other side for the arc segment.
+   */
+  if (typeof ea === 'number') {
+    this.endAngle = ea;
+  }
+
+  this._update();
+
+  if (typeof ox === 'number') {
+    this.translation.x = ox;
+  }
+  if (typeof oy === 'number') {
+    this.translation.y = oy;
+  }
+
+}
+
+_.extend(ArcSegment, {
+
+  /**
+   * @name Two.ArcSegment.Properties
+   * @property {String[]} - A list of properties that are on every {@link Two.ArcSegment}.
+   */
+  Properties: ['startAngle', 'endAngle', 'innerRadius', 'outerRadius'],
+
+  /**
+   * @name Two.ArcSegment.MakeObservable
+   * @function
+   * @param {Object} object - The object to make observable.
+   * @description Convenience function to apply observable qualities of a {@link Two.ArcSegment} to any object. Handy if you'd like to extend the {@link Two.ArcSegment} class on a custom class.
+   */
+  MakeObservable: function(obj) {
+
+    Path.MakeObservable(obj);
+    _.each(ArcSegment.Properties, defineGetterSetter, obj);
+
+  }
+
+});
+
+_.extend(ArcSegment.prototype, Path.prototype, {
+
+  constructor: ArcSegment,
+
+  /**
+   * @name Two.ArcSegment#_flagStartAngle
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.ArcSegment#startAngle} needs updating.
+   */
+  _flagStartAngle: false,
+  /**
+   * @name Two.ArcSegment#_flagEndAngle
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.ArcSegment#endAngle} needs updating.
+   */
+  _flagEndAngle: false,
+  /**
+   * @name Two.ArcSegment#_flagInnerRadius
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.ArcSegment#innerRadius} needs updating.
+   */
+  _flagInnerRadius: false,
+  /**
+   * @name Two.ArcSegment#_flagOuterRadius
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.ArcSegment#outerRadius} needs updating.
+   */
+  _flagOuterRadius: false,
+
+  /**
+   * @name Two.ArcSegment#_startAngle
+   * @private
+   * @see {@link Two.ArcSegment#startAngle}
+   */
+  _startAngle: 0,
+  /**
+   * @name Two.ArcSegment#_endAngle
+   * @private
+   * @see {@link Two.ArcSegment#endAngle}
+   */
+  _endAngle: TWO_PI$2,
+  /**
+   * @name Two.ArcSegment#_innerRadius
+   * @private
+   * @see {@link Two.ArcSegment#innerRadius}
+   */
+  _innerRadius: 0,
+  /**
+   * @name Two.ArcSegment#_outerRadius
+   * @private
+   * @see {@link Two.ArcSegment#outerRadius}
+   */
+  _outerRadius: 0,
+
+  /**
+   * @name Two.ArcSegment#_update
+   * @function
+   * @private
+   * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+   * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+   * @nota-bene Try not to call this method more than once a frame.
+   */
+  _update: function() {
+
+    if (this._flagVertices || 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$2) === mod(ea, TWO_PI$2);
+      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 = Commands.move;
+            break;
+          default:
+            command = Commands.curve;
+        }
+
+        v.command = command;
+        v.x = x;
+        v.y = y;
+        v.controls.left.clear();
+        v.controls.right.clear();
+
+        if (v.command === 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 = 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 = Commands.curve;
+          if (i <= 0) {
+            command = connected ? Commands.move : Commands.line;
+          }
+
+          v.command = command;
+          v.x = x;
+          v.y = y;
+          v.controls.left.clear();
+          v.controls.right.clear();
+
+          if (v.command === 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++;
+
+        }
+
+        // Final Point
+        vertices[id].copy(vertices[0]);
+        vertices[id].command = Commands.line;
+
+      } else if (!connected) {
+
+        vertices[id].command = Commands.line;
+        vertices[id].x = 0;
+        vertices[id].y = 0;
+        id++;
+
+        // Final Point
+        vertices[id].copy(vertices[0]);
+        vertices[id].command = Commands.line;
+
+      }
+
+    }
+
+    Path.prototype._update.call(this);
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.ArcSegment#flagReset
+   * @function
+   * @private
+   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+   */
+  flagReset: function() {
+
+    Path.prototype.flagReset.call(this);
+
+    this._flagStartAngle = this._flagEndAngle
+      = this._flagInnerRadius = this._flagOuterRadius = false;
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.ArcSegment#clone
+   * @function
+   * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+   * @returns {Two.ArcSegment}
+   * @description Create a new instance of {@link Two.ArcSegment} with the same properties of the current path.
+   */
+  clone: function(parent) {
+
+    var ir = this.innerRadius;
+    var or = this.outerRadius;
+    var sa = this.startAngle;
+    var ea = this.endAngle;
+    var resolution = this.vertices.length;
+
+    var clone = new ArcSegment(0, 0, ir, or, sa, ea, resolution);
+
+    clone.translation.copy(this.translation);
+    clone.rotation = this.rotation;
+    clone.scale = this.scale;
+    clone.skewX = this.skewX;
+    clone.skewY = this.skewY;
+
+    if (this.matrix.manual) {
+      clone.matrix.copy(this.matrix);
+    }
+
+    _.each(Path.Properties, function(k) {
+      clone[k] = this[k];
+    }, this);
+
+    if (parent) {
+      parent.add(clone);
+    }
+
+    return clone;
+
+  },
+
+  /**
+   * @name Two.ArcSegment#toObject
+   * @function
+   * @returns {Object}
+   * @description Return a JSON compatible plain object that represents the path.
+   */
+  toObject: function() {
+
+    var object = Path.prototype.toObject.call(this);
+
+    _.each(ArcSegment.Properties, function(property) {
+      object[property] = this[property];
+    }, this);
+
+    return object;
+
+  }
+
+});
+
+ArcSegment.MakeObservable(ArcSegment.prototype);
+
+var TWO_PI$1 = Math.PI * 2, cos$1 = Math.cos, sin$1 = Math.sin;
+
+/**
+ * @name Two.Polygon
+ * @class
+ * @extends Two.Path
+ * @param {Number} [x=0] - The x position of the polygon.
+ * @param {Number} [y=0] - The y position of the polygon.
+ * @param {Number} [radius=0] - The radius value of the polygon.
+ * @param {Number} [sides=12] - The number of vertices used to construct the polygon.
+ */
+function Polygon(ox, oy, r, sides) {
+
+  sides = Math.max(sides || 0, 3);
+
+  Path.call(this);
+
+  this.closed = true;
+  this.automatic = false;
+
+  /**
+   * @name Two.Polygon#width
+   * @property {Number} - The size of the width of the polygon.
+   */
+  if (typeof r === 'number') {
+    this.width = r * 2;
+  }
+
+  /**
+   * @name Two.Polygon#height
+   * @property {Number} - The size of the height of the polygon.
+   */
+  if (typeof r === 'number') {
+    this.height = r * 2;
+  }
+
+  /**
+   * @name Two.Polygon#sides
+   * @property {Number} - The amount of sides the polyogn has.
+   */
+  if (typeof sides === 'number') {
+    this.sides = sides;
+  }
+
+  this._update();
+
+  if (typeof ox === 'number') {
+    this.translation.x = ox;
+  }
+  if (typeof oy === 'number') {
+    this.translation.y = oy;
+  }
+
+}
+
+_.extend(Polygon, {
+
+  /**
+   * @name Two.Polygon.Properties
+   * @property {String[]} - A list of properties that are on every {@link Two.Polygon}.
+   */
+  Properties: ['width', 'height', 'sides'],
+
+  /**
+   * @name Two.Polygon.MakeObservable
+   * @function
+   * @param {Object} object - The object to make observable.
+   * @description Convenience function to apply observable qualities of a {@link Two.Polygon} to any object. Handy if you'd like to extend the {@link Two.Polygon} class on a custom class.
+   */
+  MakeObservable: function(obj) {
+
+    Path.MakeObservable(obj);
+    _.each(Polygon.Properties, defineGetterSetter, obj);
+
+  }
+
+});
+
+_.extend(Polygon.prototype, Path.prototype, {
+
+  constructor: Polygon,
+
+  /**
+   * @name Two.Polygon#_flagWidth
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Polygon#width} needs updating.
+   */
+  _flagWidth: false,
+  /**
+   * @name Two.Polygon#_flagHeight
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Polygon#height} needs updating.
+   */
+  _flagHeight: false,
+  /**
+   * @name Two.Polygon#_flagSides
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Polygon#sides} needs updating.
+   */
+  _flagSides: false,
+
+  /**
+   * @name Two.Polygon#_width
+   * @private
+   * @see {@link Two.Polygon#width}
+   */
+  _width: 0,
+  /**
+   * @name Two.Polygon#_height
+   * @private
+   * @see {@link Two.Polygon#height}
+   */
+  _height: 0,
+  /**
+   * @name Two.Polygon#_sides
+   * @private
+   * @see {@link Two.Polygon#sides}
+   */
+  _sides: 0,
+
+  /**
+   * @name Two.Polygon#_update
+   * @function
+   * @private
+   * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+   * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+   * @nota-bene Try not to call this method more than once a frame.
+   */
+  _update: function() {
+
+    if (this._flagVertices || this._flagWidth || this._flagHeight || this._flagSides) {
+
+      var sides = this._sides;
+      var amount = sides + 1;
+      var length = this.vertices.length;
+
+      if (length > sides) {
+        this.vertices.splice(sides - 1, length - sides);
+        length = sides;
+      }
+
+      for (var i = 0; i < amount; i++) {
+
+        var pct = (i + 0.5) / sides;
+        var theta = TWO_PI$1 * pct + Math.PI / 2;
+        var x = this._width * cos$1(theta) / 2;
+        var y = this._height * sin$1(theta) / 2;
+
+        if (i >= length) {
+          this.vertices.push(new Anchor(x, y));
+        } else {
+          this.vertices[i].set(x, y);
+        }
+
+        this.vertices[i].command = i === 0 ? Commands.move : Commands.line;
+
+      }
+
+    }
+
+    Path.prototype._update.call(this);
+    return this;
+
+  },
+
+  /**
+   * @name Two.Polygon#flagReset
+   * @function
+   * @private
+   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+   */
+  flagReset: function() {
+
+    this._flagWidth = this._flagHeight = this._flagSides = false;
+    Path.prototype.flagReset.call(this);
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Polygon#clone
+   * @function
+   * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+   * @returns {Two.Polygon}
+   * @description Create a new instance of {@link Two.Polygon} with the same properties of the current path.
+   */
+  clone: function(parent) {
+
+    var clone = new Polygon(0, 0, this.radius, this.sides);
+
+    clone.translation.copy(this.translation);
+    clone.rotation = this.rotation;
+    clone.scale = this.scale;
+    clone.skewX = this.skewX;
+    clone.skewY = this.skewY;
+
+    if (this.matrix.manual) {
+      clone.matrix.copy(this.matrix);
+    }
+
+    _.each(Path.Properties, function(k) {
+      clone[k] = this[k];
+    }, this);
+
+    if (parent) {
+      parent.add(clone);
+    }
+
+    return clone;
+
+  },
+
+  /**
+   * @name Two.Polygon#toObject
+   * @function
+   * @returns {Object}
+   * @description Return a JSON compatible plain object that represents the path.
+   */
+  toObject: function() {
+
+    var object = Path.prototype.toObject.call(this);
+
+    _.each(Polygon.Properties, function(property) {
+      object[property] = this[property];
+    }, this);
+
+    return object;
+
+  }
+
+});
+
+Polygon.MakeObservable(Polygon.prototype);
+
+var TWO_PI = Math.PI * 2, cos = Math.cos, sin = Math.sin;
+
+/**
+ * @name Two.Star
+ * @class
+ * @extends Two.Path
+ * @param {Number} [x=0] - The x position of the star.
+ * @param {Number} [y=0] - The y position of the star.
+ * @param {Number} [innerRadius=0] - The inner radius value of the star.
+ * @param {Number} [outerRadius=0] - The outer radius value of the star.
+ * @param {Number} [sides=5] - The number of sides used to construct the star.
+ */
+function Star(ox, oy, ir, or, sides) {
+
+  if (arguments.length <= 3) {
+    or = ir;
+    ir = or / 2;
+  }
+
+  if (typeof sides !== 'number' || sides <= 0) {
+    sides = 5;
+  }
+
+  Path.call(this);
+  this.closed = true;
+  this.automatic = false;
+
+  /**
+   * @name Two.Star#innerRadius
+   * @property {Number} - The size of the inner radius of the star.
+   */
+  if (typeof ir === 'number') {
+    this.innerRadius = ir;
+  }
+
+  /**
+   * @name Two.Star#outerRadius
+   * @property {Number} - The size of the outer radius of the star.
+   */
+  if (typeof or === 'number') {
+    this.outerRadius = or;
+  }
+
+  /**
+   * @name Two.Star#sides
+   * @property {Number} - The amount of sides the star has.
+   */
+  if (typeof sides === 'number') {
+    this.sides = sides;
+  }
+
+  this._update();
+
+  if (typeof ox === 'number') {
+    this.translation.x = ox;
+  }
+  if (typeof oy === 'number') {
+    this.translation.y = oy;
+  }
+
+}
+
+_.extend(Star, {
+
+  /**
+   * @name Two.Star.Properties
+   * @property {String[]} - A list of properties that are on every {@link Two.Star}.
+   */
+  Properties: ['innerRadius', 'outerRadius', 'sides'],
+
+  /**
+   * @name Two.Star.MakeObservable
+   * @function
+   * @param {Object} object - The object to make observable.
+   * @description Convenience function to apply observable qualities of a {@link Two.Star} to any object. Handy if you'd like to extend the {@link Two.Star} class on a custom class.
+   */
+  MakeObservable: function(obj) {
+
+    Path.MakeObservable(obj);
+    _.each(Star.Properties, defineGetterSetter, obj);
+
+  }
+
+});
+
+_.extend(Star.prototype, Path.prototype, {
+
+  constructor: Star,
+
+  /**
+   * @name Two.Star#_flagInnerRadius
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Star#innerRadius} needs updating.
+   */
+  _flagInnerRadius: false,
+  /**
+   * @name Two.Star#_flagOuterRadius
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Star#outerRadius} needs updating.
+   */
+  _flagOuterRadius: false,
+  /**
+   * @name Two.Star#_flagSides
+   * @private
+   * @property {Boolean} - Determines whether the {@link Two.Star#sides} needs updating.
+   */
+  _flagSides: false,
+
+  /**
+   * @name Two.Star#_innerRadius
+   * @private
+   * @see {@link Two.Star#innerRadius}
+   */
+  _innerRadius: 0,
+  /**
+   * @name Two.Star#_outerRadius
+   * @private
+   * @see {@link Two.Star#outerRadius}
+   */
+  _outerRadius: 0,
+  /**
+   * @name Two.Star#_sides
+   * @private
+   * @see {@link Two.Star#sides}
+   */
+  _sides: 0,
+
+  /**
+   * @name Two.Star#_update
+   * @function
+   * @private
+   * @param {Boolean} [bubbles=false] - Force the parent to `_update` as well.
+   * @description This is called before rendering happens by the renderer. This applies all changes necessary so that rendering is up-to-date but not updated more than it needs to be.
+   * @nota-bene Try not to call this method more than once a frame.
+   */
+  _update: function() {
+
+    if (this._flagVertices || this._flagInnerRadius || this._flagOuterRadius || this._flagSides) {
+
+      var sides = this._sides * 2;
+      var amount = sides + 1;
+      var length = this.vertices.length;
+
+      if (length > sides) {
+        this.vertices.splice(sides - 1, length - sides);
+        length = sides;
+      }
+
+      for (var i = 0; i < amount; i++) {
+
+        var pct = (i + 0.5) / sides;
+        var theta = TWO_PI * pct;
+        var r = (!(i % 2) ? this._innerRadius : this._outerRadius) / 2;
+        var x = r * cos(theta);
+        var y = r * sin(theta);
+
+        if (i >= length) {
+          this.vertices.push(new Anchor(x, y));
+        } else {
+          this.vertices[i].set(x, y);
+        }
+
+        this.vertices[i].command = i === 0 ? Commands.move : Commands.line;
+
+      }
+
+    }
+
+    Path.prototype._update.call(this);
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Star#flagReset
+   * @function
+   * @private
+   * @description Called internally to reset all flags. Ensures that only properties that change are updated before being sent to the renderer.
+   */
+  flagReset: function() {
+
+    this._flagInnerRadius = this._flagOuterRadius = this._flagSides = false;
+    Path.prototype.flagReset.call(this);
+
+    return this;
+
+  },
+
+  /**
+   * @name Two.Star#clone
+   * @function
+   * @param {Two.Group} [parent] - The parent group or scene to add the clone to.
+   * @returns {Two.Star}
+   * @description Create a new instance of {@link Two.Star} with the same properties of the current path.
+   */
+  clone: function(parent) {
+
+    var ir = this.innerRadius;
+    var or = this.outerRadius;
+    var sides = this.sides;
+
+    var clone = new Star(0, 0, ir, or, sides);
+
+    clone.translation.copy(this.translation);
+    clone.rotation = this.rotation;
+    clone.scale = this.scale;
+    clone.skewX = this.skewX;
+    clone.skewY = this.skewY;
+
+    if (this.matrix.manual) {
+      clone.matrix.copy(this.matrix);
+    }
+
+    _.each(Path.Properties, function(k) {
+      clone[k] = this[k];
+    }, this);
+
+    if (parent) {
+      parent.add(clone);
+    }
+
+    return clone;
+
+  },
+
+  /**
+   * @name Two.Star#toObject
+   * @function
+   * @returns {Object}
+   * @description Return a JSON compatible plain object that represents the path.
+   */
+  toObject: function() {
+
+    var object = Path.prototype.toObject.call(this);
+
+    _.each(Star.Properties, function(property) {
+      object[property] = this[property];
+    }, this);
+
+    return object;
+
+  }
+
+});
+
+Star.MakeObservable(Star.prototype);
+
+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 (attrs && Object.keys(attrs).length > 0) {
+      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 Commands.move point
+      string = '';
+
+    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;
+      var rx, ry, xAxisRotation, largeArcFlag, sweepFlag;
+
+      // Access x and y directly,
+      // bypassing the getter
+      var x = toFixed(b.x);
+      var y = toFixed(b.y);
+
+      switch (b.command) {
+
+        case Commands.close:
+          command = Commands.close;
+          break;
+
+        case Commands.arc:
+
+          rx = b.rx;
+          ry = b.ry;
+          xAxisRotation = b.xAxisRotation;
+          largeArcFlag = b.largeArcFlag;
+          sweepFlag = b.sweepFlag;
+
+          command = Commands.arc + ' ' + rx + ' ' + ry + ' '
+            + xAxisRotation + ' ' + largeArcFlag + ' ' + sweepFlag + ' '
+            + x + ' ' + y;
+          break;
+
+        case Commands.curve:
+
+          ar = (a.controls && a.controls.right) || Vector.zero;
+          bl = (b.controls && b.controls.left) || 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) ? Commands.move : Commands.curve) +
+            ' ' + vx + ' ' + vy + ' ' + ux + ' ' + uy + ' ' + x + ' ' + y;
+          break;
+
+        case Commands.move:
+          d = b;
+          command = 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 === Commands.curve) {
+
+          // Make sure we close to the most previous 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;
+
+        }
+
+        if (b.command !== Commands.close) {
+          command += ' Z';
+        }
+
+      }
+
+      string += command + ' ';
+
+    }
+
+    return string;
+
+  },
+
+  getClip: function(shape, domElement) {
+
+    var clip = shape._renderer.clip;
+
+    if (!clip) {
+
+      clip = shape._renderer.clip = svg.createElement('clipPath', {
+        'clip-rule': 'nonzero'
+      });
+      domElement.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) {
+
+      // Shortcut for hidden objects.
+      // Doesn't reset the flags, so changes are stored and
+      // applied once the object is visible again
+      if ((!this._visible && !this._flagVisible)
+        || (this._opacity === 0 && !this._flagOpacity)) {
+        return this;
+      }
+
+      this._update();
+
+      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._flagId) {
+        this._renderer.elem.setAttribute('id', this._id);
+      }
+
+      if (this._flagOpacity) {
+        this._renderer.elem.setAttribute('opacity', this._opacity);
+      }
+
+      if (this._flagVisible) {
+        this._renderer.elem.setAttribute('display', this._visible ? 'inline' : 'none');
+      }
+
+      if (this._flagClassName) {
+        this._renderer.elem.setAttribute('class', this.classList.join(' '));
+      }
+
+      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, domElement);
+      //   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) {
+          svg[this._mask._renderer.type].render.call(this._mask, domElement);
+          this._renderer.elem.setAttribute('clip-path', 'url(#' + this._mask.id + ')');
+        } else {
+          this._renderer.elem.removeAttribute('clip-path');
+        }
+      }
+
+      return this.flagReset();
+
+    }
+
+  },
+
+  path: {
+
+    render: function(domElement) {
+
+      // 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;
+      }
+
+      this._update();
+
+      // 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._flagId) {
+        changed.id = this._id;
+      }
+
+      if (this._flagVertices) {
+        var vertices = svg.toString(this._renderer.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._flagClassName) {
+        changed['class'] = this.classList.join(' ');
+      }
+
+      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 (this.dashes && this.dashes.length > 0) {
+        changed['stroke-dasharray'] = this.dashes.join(' ');
+        changed['stroke-dashoffset'] = this.dashes.offset || 0;
+      }
+
+      // 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, domElement);
+        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) {
+          svg[this._mask._renderer.type].render.call(this._mask, domElement);
+          this._renderer.elem.setAttribute('clip-path', 'url(#' + this._mask.id + ')');
+        } else {
+          this._renderer.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._flagId) {
+        changed.id = this._id;
+      }
+
+      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._flagClassName) {
+        changed['class'] = this.classList.join(' ');
+      }
+      if (this._flagVisible) {
+        changed.visibility = this._visible ? 'visible' : 'hidden';
+      }
+      if (this.dashes && this.dashes.length > 0) {
+        changed['stroke-dasharray'] = this.dashes.join(' ');
+        changed['stroke-dashoffset'] = this.dashes.offset || 0;
+      }
+
+      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, domElement);
+        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) {
+          svg[this._mask._renderer.type].render.call(this._mask, domElement);
+          this._renderer.elem.setAttribute('clip-path', 'url(#' + this._mask.id + ')');
+        } else {
+          this._renderer.elem.removeAttribute('clip-path');
+        }
+      }
+
+      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._flagId) {
+        changed.id = this._id;
+      }
+
+      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) {
+          while (this._renderer.elem.lastChild) {
+            this._renderer.elem.removeChild(this._renderer.elem.lastChild);
+          }
+        }
+
+        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._flagId) {
+        changed.id = this._id;
+      }
+
+      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) {
+          while (this._renderer.elem.lastChild) {
+            this._renderer.elem.removeChild(this._renderer.elem.lastChild);
+          }
+        }
+
+        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._flagId) {
+        changed.id = this._id;
+      }
+
+      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 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 / Band-aid
+          switch (this._repeat) {
+            case 'no-repeat':
+              changed.width += 1;
+              changed.height += 1;
+              break;
+          }
+
+          if (this._scale instanceof 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 {
+          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 (Object.keys(changed).length !== 0) {
+
+        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();
+
+    }
+
+  }
+
+};
+
+/**
+ * @name Two.SVGRenderer
+ * @class
+ * @extends Two.Events
+ * @param {Object} [parameters] - This object is inherited when constructing a new instance of {@link Two}.
+ * @param {Element} [parameters.domElement] - The `<svg />` to draw to. If none given a new one will be constructed.
+ * @description This class is used by {@link Two} when constructing with `type` of `Two.Types.svg` (the default type). It takes Two.js' scenegraph and renders it to a `<svg />`.
+ */
+function Renderer$1(params) {
+
+  /**
+   * @name Two.SVGRenderer#domElement
+   * @property {Element} - The `<svg />` associated with the Two.js scene.
+   */
+  this.domElement = params.domElement || svg.createElement('svg');
+
+  /**
+   * @name Two.SVGRenderer#scene
+   * @property {Two.Group} - The root group of the scenegraph.
+   */
+  this.scene = new Group();
+  this.scene.parent = this;
+
+  /**
+   * @name Two.SVGRenderer#defs
+   * @property {SvgDefintionsElement} - The `<defs />` to apply gradients, patterns, and bitmap imagery.
+   */
+  this.defs = svg.createElement('defs');
+  this.domElement.appendChild(this.defs);
+  this.domElement.defs = this.defs;
+  this.domElement.style.overflow = 'hidden';
+
+}
+
+_.extend(Renderer$1, {
+
+  /**
+   * @name Two.SVGRenderer.Utils
+   * @property {Object} - A massive object filled with utility functions and properties to render Two.js objects to a `<svg />`.
+   */
+  Utils: svg
+
+});
+
+_.extend(Renderer$1.prototype, Events, {
+
+  constructor: Renderer$1,
+
+  /**
+   * @name Two.SVGRenderer#setSize
+   * @function
+   * @param {Number} width - The new width of the renderer.
+   * @param {Number} height - The new height of the renderer.
+   * @description Change the size of the renderer.
+   * @nota-bene Triggers a `Two.Events.resize`.
+   */
+  setSize: function(width, height) {
+
+    this.width = width;
+    this.height = height;
+
+    svg.setAttributes(this.domElement, {
+      width: width,
+      height: height
+    });
+
+    return this.trigger(Events.Types.resize, width, height);
+
+  },
+
+  /**
+   * @name Two.SVGRenderer#render
+   * @function
+   * @description Render the current scene to the `<svg />`.
+   */
+  render: function() {
+
+    svg.group.render.call(this.scene, this.domElement);
+
+    return this;
+
+  }
+
+});
+
+// Constants
+
+var multiplyMatrix = Matrix.Multiply,
+  identity = [1, 0, 0, 0, 1, 0, 0, 0, 1],
+  transformation = new NumArray(9),
+  CanvasUtils = Renderer$2.Utils;
+
+var webgl = {
+
+  isHidden: /(undefined|none|transparent)/i,
+
+  canvas: (root$1.document ? root$1.document.createElement('canvas') : { getContext: function() {} }),
+
+  alignments: {
+    left: 'start',
+    middle: 'center',
+    right: 'end'
+  },
+
+  matrix: new Matrix(),
+
+  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;
+    },
+
+    render: function(gl, program) {
+
+      if (!this._visible) {
+        return;
+      }
+
+      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 NumArray(9);
+        }
+
+        // Reduce amount of object / array creation / deletion
+        this._matrix.toTransformArray(true, transformation);
+
+        multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix);
+
+        if (!(this._renderer.scale instanceof Vector)) {
+          this._renderer.scale = new Vector();
+        }
+
+        if (this._scale instanceof Vector) {
+          this._renderer.scale.x = this._scale.x;
+          this._renderer.scale.y = this._scale.y;
+        } else {
+          this._renderer.scale.x = this._scale;
+          this._renderer.scale.y = this._scale;
+        }
+
+        if (!(/renderer/i.test(parent._renderer.type))) {
+          this._renderer.scale.x *= parent._renderer.scale.x;
+          this._renderer.scale.y *= parent._renderer.scale.y;
+        }
+
+        if (flagParentMatrix) {
+          this._flagMatrix = true;
+        }
+
+      }
+
+      if (this._mask) {
+
+        // Stencil away everything that isn't rendered by the mask
+        gl.clear(gl.STENCIL_BUFFER_BIT);
+        gl.enable(gl.STENCIL_TEST);
+
+        gl.stencilFunc(gl.ALWAYS, 1, 0);
+        gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
+        // Don't draw the element onto the canvas, only onto the stencil buffer
+        gl.colorMask(false, false, false, false);
+
+        webgl[this._mask._renderer.type].render.call(this._mask, gl, program, this);
+
+        gl.stencilFunc(gl.EQUAL, 1, 0xff);
+        gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
+        gl.colorMask(true, true, true, true);
+
+      }
+
+      this._flagOpacity = parent._flagOpacity || this._flagOpacity;
+
+      this._renderer.opacity = this._opacity
+        * (parent && parent._renderer ? parent._renderer.opacity : 1);
+
+      var i;
+      if (this._flagSubtractions) {
+        for (i = 0; i < this.subtractions.length; i++) {
+          webgl.group.removeChild(this.subtractions[i], gl);
+        }
+      }
+
+      for (i = 0; i < this.children.length; i++) {
+        var child = this.children[i];
+        webgl[child._renderer.type].render.call(child, gl, program);
+      }
+
+      if (this._mask) {
+        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._renderer.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 dashes = elem.dashes;
+      var length = commands.length;
+      var last = length - 1;
+
+      canvas.width = Math.max(Math.ceil(elem._renderer.rect.width * scale.x), 1);
+      canvas.height = Math.max(Math.ceil(elem._renderer.rect.height * scale.y), 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 (typeof fill === 'string') {
+          ctx.fillStyle = fill;
+        } else {
+          webgl[fill._renderer.type].render.call(fill, ctx, elem);
+          ctx.fillStyle = fill._renderer.effect;
+        }
+      }
+      if (stroke) {
+        if (typeof stroke === 'string') {
+          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 (!closed && cap) {
+          ctx.lineCap = cap;
+        }
+      }
+      if (typeof opacity === 'number') {
+        ctx.globalAlpha = opacity;
+      }
+
+      if (dashes && dashes.length > 0) {
+        ctx.lineDashOffset = dashes.offset || 0;
+        ctx.setLineDash(dashes);
+      }
+
+      var d;
+      ctx.save();
+      ctx.scale(scale.x, scale.y);
+
+      ctx.translate(cx, cy);
+
+      ctx.beginPath();
+      for (var i = 0; i < commands.length; i++) {
+
+        var b = commands[i];
+
+        x = b.x;
+        y = b.y;
+
+        switch (b.command) {
+
+          case Commands.close:
+            ctx.closePath();
+            break;
+
+          case Commands.arc:
+
+            var rx = b.rx;
+            var ry = b.ry;
+            var xAxisRotation = b.xAxisRotation;
+            var largeArcFlag = b.largeArcFlag;
+            var sweepFlag = b.sweepFlag;
+
+            prev = closed ? mod(i - 1, length) : Math.max(i - 1, 0);
+            a = commands[prev];
+
+            var ax = a.x;
+            var ay = a.y;
+
+            CanvasUtils.renderSvgArcCommand(ctx, ax, ay, rx, ry, largeArcFlag, sweepFlag, xAxisRotation, x, y);
+            break;
+
+          case Commands.curve:
+
+            prev = closed ? mod(i - 1, length) : Math.max(i - 1, 0);
+            next = closed ? mod(i + 1, length) : Math.min(i + 1, last);
+
+            a = commands[prev];
+            c = commands[next];
+            ar = (a.controls && a.controls.right) || Vector.zero;
+            bl = (b.controls && b.controls.left) || Vector.zero;
+
+            if (a._relative) {
+              vx = ar.x + a.x;
+              vy = ar.y + a.y;
+            } else {
+              vx = ar.x;
+              vy = ar.y;
+            }
+
+            if (b._relative) {
+              ux = bl.x + b.x;
+              uy = bl.y + b.y;
+            } else {
+              ux = bl.x;
+              uy = bl.y;
+            }
+
+            ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
+
+            if (i >= last && closed) {
+
+              c = d;
+
+              br = (b.controls && b.controls.right) || Vector.zero;
+              cl = (c.controls && c.controls.left) || Vector.zero;
+
+              if (b._relative) {
+                vx = br.x + b.x;
+                vy = br.y + b.y;
+              } else {
+                vx = br.x;
+                vy = br.y;
+              }
+
+              if (c._relative) {
+                ux = cl.x + c.x;
+                uy = cl.y + c.y;
+              } else {
+                ux = cl.x;
+                uy = cl.y;
+              }
+
+              x = c.x;
+              y = c.y;
+
+              ctx.bezierCurveTo(vx, vy, ux, uy, x, y);
+
+            }
+
+            break;
+
+          case Commands.line:
+            ctx.lineTo(x, y);
+            break;
+
+          case Commands.move:
+            d = b;
+            ctx.moveTo(x, y);
+            break;
+
+        }
+
+      }
+
+      // Loose ends
+
+      if (closed) {
+        ctx.closePath();
+      }
+
+      if (!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 (typeof border === 'number') {
+        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 = forcedParent || this.parent;
+      var flagParentMatrix = parent._matrix.manual || parent._flagMatrix;
+      var flagMatrix = this._matrix.manual || this._flagMatrix;
+      var parentChanged = this._renderer.parent !== parent;
+      var flagTexture = this._flagVertices || this._flagFill
+        || (this._fill instanceof LinearGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagEndPoints))
+        || (this._fill instanceof RadialGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagRadius || this._fill._flagCenter || this._fill._flagFocal))
+        || (this._fill instanceof Texture && (this._fill._flagLoaded && this._fill.loaded || this._fill._flagImage || this._fill._flagVideo || this._fill._flagRepeat || this._fill._flagOffset || this._fill._flagScale))
+        || (this._stroke instanceof LinearGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagEndPoints))
+        || (this._stroke instanceof RadialGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagRadius || this._stroke._flagCenter || this._stroke._flagFocal))
+        || (this._stroke instanceof Texture && (this._stroke._flagLoaded && this._stroke.loaded || this._stroke._flagImage || this._stroke._flagVideo || this._stroke._flagRepeat || 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.dashes && this.dashes.length > 0)
+        || !this._renderer.texture;
+
+      if (flagParentMatrix || flagMatrix || parentChanged) {
+
+        if (!this._renderer.matrix) {
+          this._renderer.matrix = new NumArray(9);
+        }
+
+        // Reduce amount of object / array creation / deletion
+
+        this._matrix.toTransformArray(true, transformation);
+
+        multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix);
+
+        if (!(this._renderer.scale instanceof Vector)) {
+          this._renderer.scale = new Vector();
+        }
+        if (this._scale instanceof Vector) {
+          this._renderer.scale.x = this._scale.x * parent._renderer.scale.x;
+          this._renderer.scale.y = this._scale.y * parent._renderer.scale.y;
+        } else {
+          this._renderer.scale.x = this._scale * parent._renderer.scale.x;
+          this._renderer.scale.y = this._scale * parent._renderer.scale.y;
+        }
+
+        if (parentChanged) {
+          this._renderer.parent = parent;
+        }
+      }
+
+      if (this._mask) {
+
+        // Stencil away everything that isn't rendered by the mask
+        gl.clear(gl.STENCIL_BUFFER_BIT);
+        gl.enable(gl.STENCIL_TEST);
+
+        gl.stencilFunc(gl.ALWAYS, 1, 0);
+        gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
+        // Don't draw the element onto the canvas, only onto the stencil buffer
+        gl.colorMask(false, false, false, false);
+
+        webgl[this._mask._renderer.type].render.call(this._mask, gl, program, this);
+
+        gl.stencilFunc(gl.EQUAL, 1, 0xff);
+        gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
+        gl.colorMask(true, true, true, true);
+
+      }
+
+      if (flagTexture) {
+
+        if (!this._renderer.rect) {
+          this._renderer.rect = {};
+        }
+
+        this._renderer.opacity = this._opacity * parent._renderer.opacity;
+
+        webgl.path.getBoundingClientRect(this._renderer.vertices, this._linewidth, this._renderer.rect);
+
+        webgl.updateTexture.call(webgl, gl, this);
+
+      } else {
+
+        // We still need to update child Two elements on the fill and
+        // stroke properties.
+        if (this._fill && this._fill._update) {
+          this._fill._update();
+        }
+        if (this._stroke && this._stroke._update) {
+          this._stroke._update();
+        }
+
+      }
+
+      if (this._clip && !forcedParent) {
+        return;
+      }
+
+      // Draw Texture
+      gl.bindTexture(gl.TEXTURE_2D, this._renderer.texture);
+
+      // Draw Rect
+      var rect = this._renderer.rect;
+      gl.uniformMatrix3fv(program.matrix, false, this._renderer.matrix);
+      gl.uniform4f(program.rect, rect.left, rect.top, rect.right, rect.bottom);
+      gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+      if (this._mask) {
+        gl.disable(gl.STENCIL_TEST);
+      }
+
+      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;
+      var dashes = elem.dashes;
+      var decoration = elem._decoration;
+
+      canvas.width = Math.max(Math.ceil(elem._renderer.rect.width * scale.x), 1);
+      canvas.height = Math.max(Math.ceil(elem._renderer.rect.height * scale.y), 1);
+
+      var centroid = elem._renderer.rect.centroid;
+      var cx = centroid.x;
+      var cy = centroid.y;
+
+      var a, b, c, d, e, sx, sy, x1, y1, x2, y2;
+      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 (typeof fill === 'string') {
+          ctx.fillStyle = fill;
+        } else {
+          webgl[fill._renderer.type].render.call(fill, ctx, elem);
+          ctx.fillStyle = fill._renderer.effect;
+        }
+      }
+      if (stroke) {
+        if (typeof stroke === 'string') {
+          ctx.strokeStyle = stroke;
+        } else {
+          webgl[stroke._renderer.type].render.call(stroke, ctx, elem);
+          ctx.strokeStyle = stroke._renderer.effect;
+        }
+        if (linewidth) {
+          ctx.lineWidth = linewidth;
+        }
+      }
+      if (typeof opacity === 'number') {
+        ctx.globalAlpha = opacity;
+      }
+      if (dashes && dashes.length > 0) {
+        ctx.lineDashOffset = dashes.offset || 0;
+        ctx.setLineDash(dashes);
+      }
+
+      ctx.save();
+      ctx.scale(scale.x, scale.y);
+      ctx.translate(cx, cy);
+
+      if (!webgl.isHidden.test(fill)) {
+
+        if (fill._renderer && fill._renderer.offset) {
+
+          sx = fill._renderer.scale.x;
+          sy = fill._renderer.scale.y;
+
+          ctx.save();
+          ctx.translate( - fill._renderer.offset.x,
+            - fill._renderer.offset.y);
+          ctx.scale(sx, sy);
+
+          a = elem._size / fill._renderer.scale.y;
+          b = elem._leading / fill._renderer.scale.y;
+          ctx.font = [elem._style, elem._weight, a + 'px/',
+            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, c, d);
+          ctx.restore();
+
+        } else {
+          ctx.fillText(elem.value, 0, 0);
+        }
+
+      }
+
+      if (!webgl.isHidden.test(stroke)) {
+
+        if (stroke._renderer && stroke._renderer.offset) {
+
+          sx = stroke._renderer.scale.x;
+          sy = stroke._renderer.scale.y;
+
+          ctx.save();
+          ctx.translate(- stroke._renderer.offset.x,
+            - stroke._renderer.offset.y);
+          ctx.scale(sx, sy);
+
+          a = elem._size / stroke._renderer.scale.y;
+          b = elem._leading / stroke._renderer.scale.y;
+          ctx.font = [elem._style, elem._weight, a + 'px/',
+            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 = e;
+          ctx.strokeText(elem.value, c, d);
+          ctx.restore();
+
+        } else {
+          ctx.strokeText(elem.value, 0, 0);
+        }
+
+      }
+
+      // Handle text-decoration
+      if (/(underline|strikethrough)/i.test(decoration)) {
+
+        var metrics = ctx.measureText(elem.value);
+
+        switch (decoration) {
+          case 'underline':
+            y1 = metrics.actualBoundingBoxAscent;
+            y2 = metrics.actualBoundingBoxAscent;
+            break;
+          case 'strikethrough':
+            y1 = 0;
+            y2 = 0;
+            break;
+        }
+
+        x1 = - metrics.width / 2;
+        x2 = metrics.width / 2;
+
+        ctx.lineWidth = Math.max(Math.floor(elem._size / 15), 1);
+        ctx.strokeStyle = ctx.fillStyle;
+
+        ctx.beginPath();
+        ctx.moveTo(x1, y1);
+        ctx.lineTo(x2, y2);
+        ctx.stroke();
+
+      }
+
+      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 * 1.25;
+      var height = Math.max(elem._size, elem._leading) * 1.25;
+
+      if (this._linewidth && !webgl.isHidden.test(this._stroke)) {
+        width += this._linewidth * 2;
+        height += this._linewidth * 2;
+      }
+
+      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 = forcedParent || this.parent;
+      var flagParentMatrix = parent._matrix.manual || parent._flagMatrix;
+      var flagMatrix = this._matrix.manual || this._flagMatrix;
+      var parentChanged = this._renderer.parent !== parent;
+      var flagTexture = this._flagVertices || this._flagFill
+        || (this._fill instanceof LinearGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagEndPoints))
+        || (this._fill instanceof RadialGradient && (this._fill._flagSpread || this._fill._flagStops || this._fill._flagRadius || this._fill._flagCenter || this._fill._flagFocal))
+        || (this._fill instanceof Texture && (this._fill._flagLoaded && this._fill.loaded || this._fill._flagImage || this._fill._flagVideo || this._fill._flagRepeat || this._fill._flagOffset || this._fill._flagScale))
+        || (this._stroke instanceof LinearGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagEndPoints))
+        || (this._stroke instanceof RadialGradient && (this._stroke._flagSpread || this._stroke._flagStops || this._stroke._flagRadius || this._stroke._flagCenter || this._stroke._flagFocal))
+        || (this._stroke instanceof Texture && (this._stroke._flagLoaded && this._stroke.loaded || this._stroke._flagImage || this._stroke._flagVideo || this._stroke._flagRepeat || this._stroke._flagOffset || this._fill._flagScale))
+        || 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.dashes && this.dashes.length > 0)
+        || !this._renderer.texture;
+
+      if (flagParentMatrix || flagMatrix || parentChanged) {
+
+        if (!this._renderer.matrix) {
+          this._renderer.matrix = new NumArray(9);
+        }
+
+        // Reduce amount of object / array creation / deletion
+
+        this._matrix.toTransformArray(true, transformation);
+
+        multiplyMatrix(transformation, parent._renderer.matrix, this._renderer.matrix);
+
+        if (!(this._renderer.scale instanceof Vector)) {
+          this._renderer.scale = new Vector();
+        }
+        if (this._scale instanceof Vector) {
+          this._renderer.scale.x = this._scale.x * parent._renderer.scale.x;
+          this._renderer.scale.y = this._scale.y * parent._renderer.scale.y;
+        } else {
+          this._renderer.scale.x = this._scale * parent._renderer.scale.x;
+          this._renderer.scale.y = this._scale * parent._renderer.scale.y;
+        }
+
+        if (parentChanged) {
+          this._renderer.parent = parent;
+        }
+      }
+
+      if (this._mask) {
+
+        // Stencil away everything that isn't rendered by the mask
+        gl.clear(gl.STENCIL_BUFFER_BIT);
+        gl.enable(gl.STENCIL_TEST);
+
+        gl.stencilFunc(gl.ALWAYS, 1, 0);
+        gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
+        // Don't draw the element onto the canvas, only onto the stencil buffer
+        gl.colorMask(false, false, false, false);
+
+        webgl[this._mask._renderer.type].render.call(this._mask, gl, program, this);
+
+        gl.stencilFunc(gl.EQUAL, 1, 0xff);
+        gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
+        gl.colorMask(true, true, true, true);
+
+      }
+
+      if (flagTexture) {
+
+        if (!this._renderer.rect) {
+          this._renderer.rect = {};
+        }
+
+        this._renderer.opacity = this._opacity * parent._renderer.opacity;
+
+        webgl.text.getBoundingClientRect(this, this._renderer.rect);
+
+        webgl.updateTexture.call(webgl, gl, this);
+
+      } else {
+
+        // We still need to update child Two elements on the fill and
+        // stroke properties.
+        if (this._fill && this._fill._update) {
+          this._fill._update();
+        }
+        if (this._stroke && this._stroke._update) {
+          this._stroke._update();
+        }
+
+      }
+
+      if (this._clip && !forcedParent) {
+        return;
+      }
+
+      // Draw Texture
+      gl.bindTexture(gl.TEXTURE_2D, this._renderer.texture);
+
+      // Draw Rect
+      var rect = this._renderer.rect;
+      gl.uniformMatrix3fv(program.matrix, false, this._renderer.matrix);
+      gl.uniform4f(program.rect, rect.left, rect.top, rect.right, rect.bottom);
+      gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+      if (this._mask) {
+        gl.disable(gl.STENCIL_TEST);
+      }
+
+      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;
+
+      if (((this._flagLoaded || this._flagImage || this._flagVideo || this._flagRepeat) && this.loaded)) {
+        this._renderer.effect = ctx.createPattern(image, this._repeat);
+      } else if (!this._renderer.effect) {
+        return this.flagReset();
+      }
+
+      if (this._flagOffset || this._flagLoaded || this._flagScale) {
+
+        if (!(this._renderer.offset instanceof Vector)) {
+          this._renderer.offset = new Vector();
+        }
+
+        this._renderer.offset.x = - this._offset.x;
+        this._renderer.offset.y = - this._offset.y;
+
+        if (image) {
+
+          this._renderer.offset.x += image.width / 2;
+          this._renderer.offset.y += image.height / 2;
+
+          if (this._scale instanceof Vector) {
+            this._renderer.offset.x *= this._scale.x;
+            this._renderer.offset.y *= this._scale.y;
+          } else {
+            this._renderer.offset.x *= this._scale;
+            this._renderer.offset.y *= this._scale;
+          }
+        }
+
+      }
+
+      if (this._flagScale || this._flagLoaded) {
+
+        if (!(this._renderer.scale instanceof Vector)) {
+          this._renderer.scale = new Vector();
+        }
+
+        if (this._scale instanceof Vector) {
+          this._renderer.scale.copy(this._scale);
+        } else {
+          this._renderer.scale.set(this._scale, this._scale);
+        }
+
+      }
+
+      return this.flagReset();
+
+    }
+
+  },
+
+  updateTexture: function(gl, elem) {
+
+    this[elem._renderer.type].updateCanvas.call(webgl, elem);
+
+    if (!elem._renderer.texture) {
+      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);
+
+  },
+
+  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 TwoError('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 TwoError('unable to compile shader ' + shader + ': ' + error);
+      }
+
+      return shader;
+
+    },
+
+    types: {
+      vertex: 'VERTEX_SHADER',
+      fragment: 'FRAGMENT_SHADER'
+    },
+
+    vertex: [
+      'precision mediump float;',
+      'attribute vec2 a_position;',
+      '',
+      'uniform mat3 u_matrix;',
+      'uniform vec2 u_resolution;',
+      'uniform vec4 u_rect;',
+      '',
+      'varying vec2 v_textureCoords;',
+      '',
+      'void main() {',
+      '   vec2 rectCoords = (a_position * (u_rect.zw - u_rect.xy)) + u_rect.xy;',
+      '   vec2 projected = (u_matrix * vec3(rectCoords, 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_position;',
+      '}'
+    ].join('\n'),
+
+    fragment: [
+      'precision mediump float;',
+      '',
+      'uniform sampler2D u_image;',
+      'varying vec2 v_textureCoords;',
+      '',
+      'void main() {',
+      '  vec4 texel = texture2D(u_image, v_textureCoords);',
+      '  if (texel.a == 0.0) {',
+      '    discard;',
+      '  }',
+      '  gl_FragColor = texel;',
+      '}'
+    ].join('\n')
+
+  },
+
+  TextureRegistry: new Registry()
+
+};
+
+webgl.ctx = webgl.canvas.getContext('2d');
+
+/**
+ * @name Two.WebGLRenderer
+ * @class
+ * @extends Two.Events
+ * @param {Object} [parameters] - This object is inherited when constructing a new instance of {@link Two}.
+ * @param {Element} [parameters.domElement] - The `<canvas />` to draw to. If none given a new one will be constructed.
+ * @param {HTMLCanvasElement} [parameters.offscreenElement] - The offscreen two dimensional `<canvas />` to render each element on WebGL texture updates.
+ * @param {Boolean} [parameters.antialias] - Determines whether the canvas should clear render with antialias on.
+ * @description This class is used by {@link Two} when constructing with `type` of `Two.Types.webgl`. It takes Two.js' scenegraph and renders it to a `<canvas />` through the WebGL api.
+ * @see {@link https://www.khronos.org/registry/webgl/specs/latest/1.0/}
+ */
+function Renderer(params) {
+
+  var gl, vs, fs;
+
+  /**
+   * @name Two.WebGLRenderer#domElement
+   * @property {Element} - The `<canvas />` associated with the Two.js scene.
+   */
+  this.domElement = params.domElement || document.createElement('canvas');
+
+  if (typeof params.offscreenElement !== 'undefined') {
+    webgl.canvas = params.offscreenElement;
+    webgl.ctx = webgl.canvas.getContext('2d');
+  }
+
+  /**
+   * @name Two.WebGLRenderer#scene
+   * @property {Two.Group} - The root group of the scenegraph.
+   */
+  this.scene = new Group();
+  this.scene.parent = this;
+
+  this._renderer = {
+    type: 'renderer',
+    matrix: new NumArray(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(params || {}, {
+    antialias: false,
+    alpha: true,
+    premultipliedAlpha: true,
+    stencil: true,
+    preserveDrawingBuffer: true,
+    overdraw: false
+  });
+
+  /**
+   * @name Two.WebGLRenderer#overdraw
+   * @property {Boolean} - Determines whether the canvas clears the background each draw call.
+   * @default true
+   */
+  this.overdraw = params.overdraw;
+
+  /**
+   * @name Two.WebGLRenderer#ctx
+   * @property {WebGLContext} - Associated two dimensional context to render on the `<canvas />`.
+   */
+  gl = this.ctx = this.domElement.getContext('webgl', params) ||
+    this.domElement.getContext('experimental-webgl', params);
+
+  if (!this.ctx) {
+    throw new TwoError(
+      '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);
+
+  /**
+   * @name Two.WebGLRenderer#program
+   * @property {WebGLProgram} - Associated WebGL program to render all elements from the scenegraph.
+   */
+  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.rect = gl.getUniformLocation(this.program, 'u_rect');
+
+  // Bind the vertex buffer
+  var positionBuffer = gl.createBuffer();
+  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
+  gl.vertexAttribPointer(this.program.position, 2, gl.FLOAT, false, 0, 0);
+  gl.enableVertexAttribArray(this.program.position);
+  gl.bufferData(
+    gl.ARRAY_BUFFER,
+    new NumArray([
+      0, 0,
+      1, 0,
+      0, 1,
+      0, 1,
+      1, 0,
+      1, 1
+    ]),
+    gl.STATIC_DRAW);
+
+  // Setup some initial statements of the gl context
+  gl.enable(gl.BLEND);
+
+  gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
+
+  gl.blendEquation(gl.FUNC_ADD);
+  gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
+}
+
+_.extend(Renderer, {
+
+  /**
+   * @name Two.WebGLRenderer.Utils
+   * @property {Object} - A massive object filled with utility functions and properties to render Two.js objects to a `<canvas />` through the WebGL API.
+   */
+  Utils: webgl
+
+});
+
+_.extend(Renderer.prototype, Events, {
+
+  constructor: Renderer,
+
+  /**
+   * @name Two.WebGLRenderer#setSize
+   * @function
+   * @fires resize
+   * @param {Number} width - The new width of the renderer.
+   * @param {Number} height - The new height of the renderer.
+   * @param {Number} [ratio] - The new pixel ratio (pixel density) of the renderer. Defaults to calculate the pixel density of the user's screen.
+   * @description Change the size of the renderer.
+   */
+  setSize: function(width, height, ratio) {
+
+    this.width = width;
+    this.height = height;
+
+    this.ratio = typeof ratio === 'undefined' ? getRatio(this.ctx) : ratio;
+
+    this.domElement.width = width * this.ratio;
+    this.domElement.height = height * this.ratio;
+
+    if (_.isObject(this.domElement.style)) {
+      _.extend(this.domElement.style, {
+        width: width + 'px',
+        height: height + 'px'
+      });
+    }
+
+    // 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 * this.ratio, height * this.ratio);
+
+    var resolutionLocation = this.ctx.getUniformLocation(
+      this.program, 'u_resolution');
+    this.ctx.uniform2f(resolutionLocation, width * this.ratio, height * this.ratio);
+
+    return this.trigger(Events.Types.resize, width, height, ratio);
+
+  },
+
+  /**
+   * @name Two.WebGLRenderer#render
+   * @function
+   * @description Render the current scene to the `<canvas />`.
+   */
+  render: function() {
+
+    var gl = this.ctx;
+
+    if (!this.overdraw) {
+      gl.clear(gl.COLOR_BUFFER_BIT);
+    }
+
+    webgl.group.render.call(this.scene, gl, this.program);
+    this._flagMatrix = false;
+
+    return this;
+
+  }
+
+});
+
+// Utils
+
+/**
+ * @name Two
+ * @class
+ * @global
+ * @param {Object} [options]
+ * @param {Boolean} [options.fullscreen=false] - Set to `true` to automatically make the stage adapt to the width and height of the parent document. This parameter overrides `width` and `height` parameters if set to `true`. This overrides `options.fitted` as well.
+ * @param {Boolean} [options.fitted=false] = Set to `true` to automatically make the stage adapt to the width and height of the parent element. This parameter overrides `width` and `height` parameters if set to `true`.
+ * @param {Number} [options.width=640] - The width of the stage on construction. This can be set at a later time.
+ * @param {Number} [options.height=480] - The height of the stage on construction. This can be set at a later time.
+ * @param {String} [options.type=Two.Types.svg] - The type of renderer to setup drawing with. See {@link Two.Types} for available options.
+ * @param {Boolean} [options.autostart=false] - Set to `true` to add the instance to draw on `requestAnimationFrame`. This is a convenient substitute for {@link Two#play}.
+ * @param {Element} [options.domElement] - The canvas or SVG element to draw into. This overrides the `options.type` argument.
+ * @description The entrypoint for Two.js. Instantiate a `new Two` in order to setup a scene to render to. `Two` is also the publicly accessible namespace that all other sub-classes, functions, and utilities attach to.
+ */
+function Two(options) {
+
+  // Determine what Renderer to use and setup a scene.
+
+  var params = _.defaults(options || {}, {
+    fullscreen: false,
+    fitted: false,
+    width: 640,
+    height: 480,
+    type: Two.Types.svg,
+    autostart: false
+  });
+
+  _.each(params, function(v, k) {
+    if (/fullscreen/i.test(k) || /autostart/i.test(k)) {
+      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);
+  this.setPlaying(params.autostart);
+  this.frameCount = 0;
+
+  /**
+   * @name Two#fit
+   * @function
+   * @description If `options.fullscreen` or `options.fitted` in construction create this function. It sets the `width` and `height` of the instance to its respective parent `window` or `element` depending on the `options` passed.
+   */
+  if (params.fullscreen) {
+
+    this.fit = fitToWindow.bind(this);
+    this.fit.domElement = window;
+    this.fit.attached = true;
+    _.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(this.fit.domElement, 'resize', this.fit);
+    this.fit();
+
+  } else if (params.fitted) {
+
+    this.fit = fitToParent.bind(this);
+    _.extend(this.renderer.domElement.style, {
+      display: 'block'
+    });
+
+  } else if (!_.isElement(params.domElement)) {
+
+    this.renderer.setSize(params.width, params.height, this.ratio);
+    this.width = params.width;
+    this.height = params.height;
+
+  }
+
+  this.renderer.bind(Events.Types.resize, updateDimensions.bind(this));
+  this.scene = this.renderer.scene;
+
+  Two.Instances.push(this);
+  if (params.autostart) {
+    raf.init();
+  }
+
+}
+
+_.extend(Two, Constants);
+
+_.extend(Two.prototype, Events, {
+
+  constructor: Two,
+
+  /**
+   * @name Two#type
+   * @property {String} - A string representing which type of renderer the instance has instantiated.
+   */
+  type: '',
+
+  /**
+   * @name Two#renderer
+   * @property {(Two.SVGRenderer|Two.CanvasRenderer|Two.WebGLRenderer)} - The instantiated rendering class for the instance. For a list of possible rendering types check out Two.Types.
+   */
+  renderer: null,
+
+  /**
+   * @name Two#scene
+   * @property {Two.Group} - The base level {@link Two.Group} which houses all objects for the instance. Because it is a {@link Two.Group} transformations can be applied to it that will affect all objects in the instance. This is handy as a makeshift inverted camera.
+   */
+  scene: null,
+
+  /**
+   * @name Two#width
+   * @property {Number} - The width of the instance's dom element.
+   */
+  width: 0,
+
+  /**
+   * @name Two#height
+   * @property {Number} - The height of the instance's dom element.
+   */
+  height: 0,
+
+  /**
+   * @name Two#frameCount
+   * @property {Number} - An integer representing how many frames have elapsed.
+   */
+  frameCount: 0,
+
+  /**
+   * @name Two#timeDelta
+   * @property {Number} - A number representing how much time has elapsed since the last frame in milliseconds.
+   */
+  timeDelta: 0,
+
+  /**
+   * @name Two#playing
+   * @property {Boolean} - A boolean representing whether or not the instance is being updated through the automatic `requestAnimationFrame`.
+   */
+  playing: false,
+
+  /**
+   * @name Two#appendTo
+   * @function
+   * @param {Element} elem - The DOM element to append the Two.js stage to.
+   * @description Shorthand method to append your instance of Two.js to the `document`.
+   */
+  appendTo: function(elem) {
+
+    elem.appendChild(this.renderer.domElement);
+
+    if (this.fit) {
+      if (this.fit.domElement !== window) {
+        this.fit.domElement = elem;
+        this.fit.attached = false;
+      }
+      this.update();
+    }
+
+    return this;
+
+  },
+
+  /**
+   * @name Two#play
+   * @function
+   * @fires Two.Events.Types.play event
+   * @description Call to start an internal animation loop.
+   * @nota-bene This function initiates a `requestAnimationFrame` loop.
+   */
+  play: function() {
+
+    this.playing = true;
+    raf.init();
+    return this.trigger(Events.Types.play);
+
+  },
+
+  /**
+   * @name Two#pause
+   * @function
+   * @fires Two.Events.Types.pause event
+   * @description Call to stop the internal animation loop for a specific instance of Two.js.
+   */
+  pause: function() {
+
+    this.playing = false;
+    return this.trigger(Events.Types.pause);
+
+  },
+
+  setPlaying: function(p) {
+    this.playing = p;
+  },
+
+  /**
+   * @name Two#release
+   * @function
+   * @param {Object} obj
+   * @returns {Object} The object passed for event deallocation.
+   * @description Release an arbitrary class' events from the Two.js corpus and recurse through its children and or vertices.
+   */
+  release: function(obj) {
+
+    var i, v, child;
+
+    if (!_.isObject(obj)) {
+      return;
+    }
+
+    if (typeof obj.unbind === 'function') {
+      obj.unbind();
+    }
+
+    if (obj.vertices) {
+      if (typeof obj.vertices.unbind === 'function') {
+        obj.vertices.unbind();
+      }
+      for (i = 0; i < obj.vertices.length; i++) {
+        v = obj.vertices[i];
+        if (typeof v.unbind === 'function') {
+          v.unbind();
+        }
+      }
+    }
+
+    if (obj.children) {
+      for (i = 0; i < obj.children.length; i++) {
+        child = obj.children[i];
+        this.release(child);
+      }
+    }
+
+    return obj;
+
+  },
+
+  /**
+   * @name Two#update
+   * @function
+   * @fires Two.Events.Types.update event
+   * @description Update positions and calculations in one pass before rendering. Then render to the canvas.
+   * @nota-bene This function is called automatically if using {@link Two#play} or the `autostart` parameter in construction.
+   */
+  update: function() {
+
+    var animated = !!this._lastFrame;
+    var now = _.performance.now();
+
+    if (animated) {
+      this.timeDelta = parseFloat((now - this._lastFrame).toFixed(3));
+    }
+    this._lastFrame = now;
+
+    if (this.fit && this.fit.domElement && !this.fit.attached) {
+        dom.bind(this.fit.domElement, 'resize', this.fit);
+        this.fit.attached = true;
+        this.fit();
+    }
+
+    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(Events.Types.update, this.frameCount, this.timeDelta);
+
+    return this.render();
+
+  },
+
+  /**
+   * @name Two#render
+   * @function
+   * @fires render
+   * @description Render all drawable and visible objects of the scene.
+   */
+  render: function() {
+
+    this.renderer.render();
+    return this.trigger(Events.Types.render, this.frameCount++);
+
+  },
+
+  // Convenience Methods
+
+  /**
+   * @name Two#add
+   * @function
+   * @param {(Two.Shape[]|...Two.Shape)} [objects] - An array of Two.js objects. Alternatively can add objects as individual arguments.
+   * @description A shorthand method to add specific Two.js objects to the scene.
+   */
+  add: function(o) {
+
+    var objects = o;
+    if (!(objects instanceof Array)) {
+      objects = Array.prototype.slice.call(arguments);
+    }
+
+    this.scene.add(objects);
+    return this;
+
+  },
+
+  /**
+   * @name Two#remove
+   * @function
+   * @param {(Two.Shape[]|...Two.Shape)} [objects] - An array of Two.js objects.
+   * @description A shorthand method to remove specific Two.js objects from the scene.
+   */
+  remove: function(o) {
+
+    var objects = o;
+    if (!(objects instanceof Array)) {
+      objects = Array.prototype.slice.call(arguments);
+    }
+
+    this.scene.remove(objects);
+
+    return this;
+
+  },
+
+  /**
+   * @name Two#clear
+   * @function
+   * @description Removes all objects from the instance's scene. If you intend to have the browser garbage collect this, don't forget to delete the references in your application as well.
+   */
+  clear: function() {
+
+    this.scene.remove(this.scene.children);
+    return this;
+
+  },
+
+  /**
+   * @name Two#makeLine
+   * @function
+   * @param {Number} x1
+   * @param {Number} y1
+   * @param {Number} x2
+   * @param {Number} y2
+   * @returns {Two.Line}
+   * @description Creates a Two.js line and adds it to the scene.
+   */
+  makeLine: function(x1, y1, x2, y2) {
+
+    var line = new Line(x1, y1, x2, y2);
+    this.scene.add(line);
+
+    return line;
+
+  },
+
+  /**
+   * @name Two#makeArrow
+   * @function
+   * @param {Number} x1
+   * @param {Number} y1
+   * @param {Number} x2
+   * @param {Number} y2
+   * @returns {Two.Path}
+   * @description Creates a Two.js arrow and adds it to the scene.
+   */
+  makeArrow: function(x1, y1, x2, y2, size) {
+
+    var headlen = typeof size === 'number' ? size : 10;
+
+    var angle = Math.atan2(y2 - y1, x2 - x1);
+
+    var vertices = [
+
+      new Anchor(x1, y1, undefined, undefined, undefined, undefined, Commands.move),
+      new Anchor(x2, y2, undefined, undefined, undefined, undefined, Commands.line),
+      new Anchor(
+        x2 - headlen * Math.cos(angle - Math.PI / 4),
+        y2 - headlen * Math.sin(angle - Math.PI / 4),
+        undefined, undefined, undefined, undefined, Commands.line
+      ),
+
+      new Anchor(x2, y2, undefined, undefined, undefined, undefined, Commands.move),
+      new Anchor(
+        x2 - headlen * Math.cos(angle + Math.PI / 4),
+        y2 - headlen * Math.sin(angle + Math.PI / 4),
+        undefined, undefined, undefined, undefined, Commands.line
+      )
+
+    ];
+
+    var path = new Path(vertices, false, false, true);
+    path.noFill();
+    path.cap = 'round';
+    path.join = 'round';
+
+    this.scene.add(path);
+
+    return path;
+  },
+
+  /**
+   * @name Two#makeRectangle
+   * @function
+   * @param {Number} x
+   * @param {Number} y
+   * @param {Number} width
+   * @param {Number} height
+   * @returns {Two.Rectangle}
+   * @description Creates a Two.js rectangle and adds it to the scene.
+   */
+  makeRectangle: function(x, y, width, height) {
+
+    var rect = new Rectangle(x, y, width, height);
+    this.scene.add(rect);
+
+    return rect;
+
+  },
+
+  /**
+   * @name Two#makeRoundedRectangle
+   * @function
+   * @param {Number} x
+   * @param {Number} y
+   * @param {Number} width
+   * @param {Number} height
+   * @param {Number} sides
+   * @returns {Two.Rectangle}
+   * @description Creates a Two.js rounded rectangle and adds it to the scene.
+   */
+  makeRoundedRectangle: function(x, y, width, height, sides) {
+
+    var rect = new RoundedRectangle(x, y, width, height, sides);
+    this.scene.add(rect);
+
+    return rect;
+
+  },
+
+  /**
+   * @name Two#makeCircle
+   * @function
+   * @param {Number} x
+   * @param {Number} y
+   * @param {Number} radius
+   * @param {Number} [resolution=4]
+   * @returns {Two.Circle}
+   * @description Creates a Two.js circle and adds it to the scene.
+   */
+  makeCircle: function(x, y, radius, resolution) {
+
+    var circle = new Circle(x, y, radius, resolution);
+    this.scene.add(circle);
+
+    return circle;
+
+  },
+
+  /**
+   * @name Two#makeEllipse
+   * @function
+   * @param {Number} x
+   * @param {Number} y
+   * @param {Number} rx
+   * @param {Number} ry
+   * @param {Number} [resolution=4]
+   * @returns {Two.Ellipse}
+   * @description Creates a Two.js ellipse and adds it to the scene.
+   */
+  makeEllipse: function(x, y, rx, ry, resolution) {
+
+    var ellipse = new Ellipse(x, y, rx, ry, resolution);
+    this.scene.add(ellipse);
+
+    return ellipse;
+
+  },
+
+  /**
+   * @name Two#makeStar
+   * @function
+   * @param {Number} x
+   * @param {Number} y
+   * @param {Number} outerRadius
+   * @param {Number} innerRadius
+   * @param {Number} sides
+   * @returns {Two.Star}
+   * @description Creates a Two.js star and adds it to the scene.
+   */
+  makeStar: function(ox, oy, outerRadius, innerRadius, sides) {
+
+    var star = new Star(ox, oy, outerRadius, innerRadius, sides);
+    this.scene.add(star);
+
+    return star;
+
+  },
+
+  /**
+   * @name Two#makeCurve
+   * @function
+   * @param {Two.Anchor[]} [points] - An array of {@link Two.Anchor} points.
+   * @param {...Number} - Alternatively you can pass alternating `x` / `y` coordinate values as individual arguments. These will be combined into {@link Two.Anchor}s for use in the path.
+   * @returns {Two.Path} - Where `path.curved` is set to `true`.
+   * @description Creates a Two.js path that is curved and adds it to the scene.
+   * @nota-bene In either case of passing an array or passing numbered arguments the last argument is an optional `Boolean` that defines whether the path should be open or closed.
+   */
+  makeCurve: function(p) {
+
+    var l = arguments.length, points = p;
+    if (!Array.isArray(p)) {
+      points = [];
+      for (var i = 0; i < l; i+=2) {
+        var x = arguments[i];
+        if (typeof x !== 'number') {
+          break;
+        }
+        var y = arguments[i + 1];
+        points.push(new Anchor(x, y));
+      }
+    }
+
+    var last = arguments[l - 1];
+    var curve = new Path(points, !(typeof last === 'boolean' ? 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;
+
+  },
+
+  /**
+   * @name Two#makePolygon
+   * @function
+   * @param {Number} x
+   * @param {Number} y
+   * @param {Number} radius
+   * @param {Number} sides
+   * @returns {Two.Polygon}
+   * @description Creates a Two.js polygon and adds it to the scene.
+   */
+  makePolygon: function(x, y, radius, sides) {
+
+    var poly = new Polygon(x, y, radius, sides);
+    this.scene.add(poly);
+
+    return poly;
+
+  },
+
+  /**
+   * @name Two#makeArcSegment
+   * @function
+   * @param {Number} x
+   * @param {Number} y
+   * @param {Number} innerRadius
+   * @param {Number} outerRadius
+   * @param {Number} startAngle
+   * @param {Number} endAngle
+   * @param {Number} [resolution=Two.Resolution] - The number of vertices that should comprise the arc segment.
+   */
+  makeArcSegment: function(ox, oy, ir, or, sa, ea, res) {
+    var arcSegment = new ArcSegment(ox, oy, ir, or, sa, ea, res);
+    this.scene.add(arcSegment);
+    return arcSegment;
+  },
+
+  /**
+   * @name Two#makePath
+   * @function
+   * @param {Two.Anchor[]} [points] - An array of {@link Two.Anchor} points.
+   * @param {...Number} - Alternatively you can pass alternating `x` / `y` coordinate values as individual arguments. These will be combined into {@link Two.Anchor}s for use in the path.
+   * @returns {Two.Path}
+   * @description Creates a Two.js path and adds it to the scene.
+   * @nota-bene In either case of passing an array or passing numbered arguments the last argument is an optional `Boolean` that defines whether the path should be open or closed.
+   */
+  makePath: function(p) {
+
+    var l = arguments.length, points = p;
+    if (!Array.isArray(p)) {
+      points = [];
+      for (var i = 0; i < l; i+=2) {
+        var x = arguments[i];
+        if (typeof x !== 'number') {
+          break;
+        }
+        var y = arguments[i + 1];
+        points.push(new Anchor(x, y));
+      }
+    }
+
+    var last = arguments[l - 1];
+    var path = new Path(points, !(typeof last === 'boolean' ? last : undefined));
+    var rect = path.getBoundingClientRect();
+    if (typeof rect.top === 'number'   && typeof rect.left === 'number' &&
+        typeof rect.right === 'number' && typeof rect.bottom === 'number') {
+      path.center().translation
+        .set(rect.left + rect.width / 2, rect.top + rect.height / 2);
+    }
+
+    this.scene.add(path);
+
+    return path;
+
+  },
+
+  /**
+   * @name Two#makeText
+   * @function
+   * @param {String} message
+   * @param {Number} x
+   * @param {Number} y
+   * @param {Object} [styles] - An object to describe any of the {@link Two.Text.Properties} including `fill`, `stroke`, `linewidth`, `family`, `alignment`, `leading`, `opacity`, etc..
+   * @returns {Two.Text}
+   * @description Creates a Two.js text object and adds it to the scene.
+   */
+  makeText: function(message, x, y, styles) {
+    var text = new Text(message, x, y, styles);
+    this.add(text);
+    return text;
+  },
+
+  /**
+   * @name Two#makeLinearGradient
+   * @function
+   * @param {Number} x1
+   * @param {Number} y1
+   * @param {Number} x2
+   * @param {Number} y2
+   * @param {...Two.Stop} stops - Any number of color stops sometimes reffered to as ramp stops. If none are supplied then the default black-to-white two stop gradient is applied.
+   * @returns {Two.LinearGradient}
+   * @description Creates a Two.js linear gradient and ads it to the scene. In the case of an effect it's added to an invisible "definitions" group.
+   */
+  makeLinearGradient: function(x1, y1, x2, y2 /* stops */) {
+
+    var stops = Array.prototype.slice.call(arguments, 4);
+    var gradient = new LinearGradient(x1, y1, x2, y2, stops);
+
+    this.add(gradient);
+
+    return gradient;
+
+  },
+
+  /**
+   * @name Two#makeRadialGradient
+   * @function
+   * @param {Number} x1
+   * @param {Number} y1
+   * @param {Number} radius
+   * @param {...Two.Stop} stops - Any number of color stops sometimes reffered to as ramp stops. If none are supplied then the default black-to-white two stop gradient is applied.
+   * @returns {Two.RadialGradient}
+   * @description Creates a Two.js linear-gradient object and ads it to the scene. In the case of an effect it's added to an invisible "definitions" group.
+   */
+  makeRadialGradient: function(x1, y1, r /* stops */) {
+
+    var stops = Array.prototype.slice.call(arguments, 3);
+    var gradient = new RadialGradient(x1, y1, r, stops);
+
+    this.add(gradient);
+
+    return gradient;
+
+  },
+
+  /**
+   * @name Two#makeSprite
+   * @function
+   * @param {(String|Two.Texture)} pathOrTexture - The URL path to an image or an already created {@link Two.Texture}.
+   * @param {Number} x
+   * @param {Number} y
+   * @param {Number} [columns=1]
+   * @param {Number} [rows=1]
+   * @param {Number} [frameRate=0]
+   * @param {Boolean} [autostart=false]
+   * @returns {Two.Sprite}
+   * @description Creates a Two.js sprite object and adds it to the scene. Sprites can be used for still images as well as animations.
+   */
+  makeSprite: function(path, x, y, cols, rows, frameRate, autostart) {
+
+    var sprite = new Sprite(path, x, y, cols, rows, frameRate);
+    if (autostart) {
+      sprite.play();
+    }
+    this.add(sprite);
+
+    return sprite;
+
+  },
+
+  /**
+   * @name Two#makeImageSequence
+   * @function
+   * @param {(String[]|Two.Texture[])} pathsOrTextures - An array of paths or of {@link Two.Textures}.
+   * @param {Number} x
+   * @param {Number} y
+   * @param {Number} [frameRate=0]
+   * @param {Boolean} [autostart=false]
+   * @returns {Two.ImageSequence}
+   * @description Creates a Two.js image sequence object and adds it to the scene.
+   */
+  makeImageSequence: function(paths, x, y, frameRate, autostart) {
+
+    var imageSequence = new ImageSequence(paths, x, y, frameRate);
+    if (autostart) {
+      imageSequence.play();
+    }
+    this.add(imageSequence);
+
+    return imageSequence;
+
+  },
+
+  /**
+   * @name Two#makeTexture
+   * @function
+   * @param {(String|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement)} [pathOrSource] - The URL path to an image or a DOM image-like element.
+   * @param {Function} [callback] - Function to be invoked when the image is loaded.
+   * @returns {Two.Texture}
+   * @description Creates a Two.js texture object.
+   */
+  makeTexture: function(path, callback) {
+
+    var texture = new Texture(path, callback);
+    return texture;
+
+  },
+
+  /**
+   * @name Two#makeGroup
+   * @function
+   * @param {(Two.Shape[]|...Two.Shape)} [objects] - Two.js objects to be added to the group in the form of an array or as individual arguments.
+   * @returns {Two.Group}
+   * @description Creates a Two.js group object and adds it to the scene.
+   */
+  makeGroup: function(o) {
+
+    var objects = o;
+    if (!(objects instanceof Array)) {
+      objects = Array.prototype.slice.call(arguments);
+    }
+
+    var group = new Group();
+    this.scene.add(group);
+    group.add(objects);
+
+    return group;
+
+  },
+
+  /**
+   * @name Two#interpret
+   * @function
+   * @param {SVGElement} SVGElement - The SVG node to be parsed.
+   * @param {Boolean} shallow - Don't create a top-most group but append all content directly.
+   * @param {Boolean} add – Automatically add the reconstructed SVG node to scene.
+   * @returns {Two.Group}
+   * @description 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.
+   */
+  interpret: function(SVGElement, shallow, add) {
+
+    var tag = SVGElement.tagName.toLowerCase();
+
+    add = (typeof add !== 'undefined') ? add : true;
+
+    if (!(tag in read)) {
+      return null;
+    }
+
+    var node = read[tag].call(this, SVGElement);
+
+    if (add) {
+      this.add(shallow && node instanceof Group ? node.children : node);
+    } else if (node.parent) {
+      // Remove `g` tags that have been added to scenegraph / DOM
+      // in order to be compatible with `getById` methods.
+      node.remove();
+    }
+
+    return node;
+
+  },
+
+  /**
+   * @name Two#load
+   * @function
+   * @param {String|SVGElement} pathOrSVGContent - The URL path of an SVG file or an SVG document as text.
+   * @param {Function} callback - Function to call once loading has completed.
+   * @returns {Two.Group}
+   * @description Load an SVG file or SVG text and interpret it into Two.js legible objects.
+   */
+  load: function(text, callback) {
+
+    var group = new Group();
+    var elem, i, j, child;
+
+    var attach = (function(data) {
+
+      dom.temp.innerHTML = data;
+
+      for (i = 0; i < dom.temp.children.length; i++) {
+        elem = dom.temp.children[i];
+        if (/svg/i.test(elem.nodeName)) {
+          child = this.interpret(elem);
+          // Two.Utils.applySvgViewBox.call(this, group, elem.getAttribute('viewBox'));
+          for (j = 0; j < child.children.length; j++) {
+            group.add(child.children[j]);
+          }
+        } else {
+          group.add(this.interpret(elem));
+        }
+      }
+
+      if (typeof callback === 'function') {
+        var svg = dom.temp.children.length <= 1
+          ? dom.temp.children[0] : dom.temp.children;
+        callback(group, svg);
+      }
+
+    }).bind(this);
+
+    if (/.*\.svg/ig.test(text)) {
+
+      xhr(text, attach);
+
+      return group;
+
+    }
+
+    attach(text);
+
+    return group;
+
+  }
+
+});
+
+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);
+
+}
+
+function fitToParent() {
+
+  var parent = this.renderer.domElement.parentElement;
+  if (!parent) {
+    console.warn('Two.js: Attempting to fit to parent, but no parent found.');
+    return;
+  }
+  var wr = parent.getBoundingClientRect();
+
+  var width = this.width = wr.width;
+  var height = this.height = wr.height;
+
+  this.renderer.setSize(width, height, this.ratio);
+
+}
+
+function updateDimensions(width, height) {
+  this.width = width;
+  this.height = height;
+  this.trigger(Events.Types.resize, width, height);
+}
+
+// Request Animation Frame
+
+var raf = dom.getRequestAnimationFrame();
+
+function loop() {
+
+  for (var i = 0; i < Two.Instances.length; i++) {
+    var t = Two.Instances[i];
+    if (t.playing) {
+      t.update();
+    }
+  }
+
+  Two.nextFrameID = raf(loop);
+
+}
+
+raf.init = function() {
+  loop();
+  raf.init = function() {};
+};
+
+_.extend(Two, {
+  Anchor: Anchor,
+  Collection: Collection,
+  Events: Events,
+  Group: Group,
+  Matrix: Matrix,
+  Path: Path,
+  Registry: Registry,
+  Shape: Shape,
+  Text: Text,
+  Vector: Vector,
+
+  Gradient: Gradient,
+  ImageSequence: ImageSequence,
+  LinearGradient: LinearGradient,
+  RadialGradient: RadialGradient,
+  Sprite: Sprite,
+  Stop: Stop,
+  Texture: Texture,
+
+  ArcSegment: ArcSegment,
+  Circle: Circle,
+  Ellipse: Ellipse,
+  Line: Line,
+  Polygon: Polygon,
+  Rectangle: Rectangle,
+  RoundedRectangle: RoundedRectangle,
+  Star: Star,
+
+  CanvasRenderer: Renderer$2,
+  SVGRenderer: Renderer$1,
+  WebGLRenderer: Renderer,
+
+  Commands: Commands,
+
+  /**
+   * @name Two.Utils
+   * @property {Object} - A massive object filled with utility functions and properties.
+   */
+  Utils: _.extend({
+
+    Error: TwoError,
+    getRatio: getRatio,
+    defineGetterSetter: defineGetterSetter,
+    read: read,
+    xhr: xhr
+
+  }, _, CanvasShim, Curves, math)
+
+});
+
+export default Two;

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini