瀏覽代碼

Merge remote-tracking branch 'upstream/lcs-v0.8.1'

Nikolay Suslov 3 年之前
父節點
當前提交
fbaa7bc982
共有 100 個文件被更改,包括 47235 次插入346 次删除
  1. 4 1
      .gitignore
  2. 2 6
      package-lock.json
  3. 1 1
      package.json
  4. 76 5
      public/core/app.js
  5. 0 0
      public/defaults/proxy/pts/form.js
  6. 9 0
      public/defaults/proxy/pts/form.vwf.json
  7. 4 0
      public/defaults/proxy/pts/node.vwf.json
  8. 0 0
      public/defaults/proxy/pts/player.js
  9. 9 0
      public/defaults/proxy/pts/player.vwf.json
  10. 0 0
      public/defaults/proxy/pts/pt.js
  11. 12 0
      public/defaults/proxy/pts/pt.vwf.json
  12. 57 0
      public/defaults/proxy/pts/scene.js
  13. 11 0
      public/defaults/proxy/pts/scene.vwf.json
  14. 0 0
      public/defaults/proxy/tonejs/node.js
  15. 31 0
      public/defaults/proxy/tonejs/player.js
  16. 46 0
      public/defaults/proxy/tonejs/player.vwf.json
  17. 2 0
      public/defaults/proxy/tonejs/polySynth.vwf.json
  18. 29 0
      public/defaults/proxy/tonejs/transport.js
  19. 40 0
      public/defaults/proxy/tonejs/transport.vwf.json
  20. 0 0
      public/defaults/proxy/two/anchor.js
  21. 18 0
      public/defaults/proxy/two/anchor.vwf.json
  22. 0 0
      public/defaults/proxy/two/curve.js
  23. 13 0
      public/defaults/proxy/two/curve.vwf.json
  24. 0 0
      public/defaults/proxy/two/ellipse.js
  25. 15 0
      public/defaults/proxy/two/ellipse.vwf.json
  26. 7 0
      public/defaults/proxy/two/group.js
  27. 29 0
      public/defaults/proxy/two/group.vwf.json
  28. 79 0
      public/defaults/proxy/two/node.js
  29. 56 0
      public/defaults/proxy/two/node.vwf.json
  30. 107 0
      public/defaults/proxy/two/objects/transport.js
  31. 33 0
      public/defaults/proxy/two/objects/transport.vwf.json
  32. 18 0
      public/defaults/proxy/two/path.js
  33. 39 0
      public/defaults/proxy/two/path.vwf.json
  34. 62 0
      public/defaults/proxy/two/player.js
  35. 21 0
      public/defaults/proxy/two/player.vwf.json
  36. 0 0
      public/defaults/proxy/two/rectangle.js
  37. 15 0
      public/defaults/proxy/two/rectangle.vwf.json
  38. 55 0
      public/defaults/proxy/two/scene.js
  39. 11 0
      public/defaults/proxy/two/scene.vwf.json
  40. 0 0
      public/defaults/proxy/two/text.js
  41. 23 0
      public/defaults/proxy/two/text.vwf.json
  42. 0 0
      public/defaults/proxy/two/texture.js
  43. 11 0
      public/defaults/proxy/two/texture.vwf.json
  44. 15 0
      public/defaults/worlds/concert/appui.js
  45. 566 0
      public/defaults/worlds/concert/concert.js
  46. 13 0
      public/defaults/worlds/concert/index.vwf.config.json
  47. 230 0
      public/defaults/worlds/concert/index.vwf.json
  48. 14 0
      public/defaults/worlds/concert/info.json
  49. 二進制
      public/defaults/worlds/concert/webimg.jpg
  50. 15 0
      public/defaults/worlds/pts/appui.js
  51. 3 0
      public/defaults/worlds/pts/canvas.js
  52. 12 0
      public/defaults/worlds/pts/index.vwf.config.json
  53. 11 0
      public/defaults/worlds/pts/index.vwf.json
  54. 14 0
      public/defaults/worlds/pts/info.json
  55. 二進制
      public/defaults/worlds/pts/webimg.jpg
  56. 5 5
      public/defaults/worlds/rubik/robot.js
  57. 2 2
      public/defaults/worlds/tone/generate.js
  58. 2 2
      public/defaults/worlds/tone/info.json
  59. 1 1
      public/defaults/worlds/tone/instrument.js
  60. 2 2
      public/defaults/worlds/tone/transportLine.js
  61. 185 0
      public/defaults/worlds/two/2d.js
  62. 15 0
      public/defaults/worlds/two/appui.js
  63. 14 0
      public/defaults/worlds/two/index.vwf.config.json
  64. 210 0
      public/defaults/worlds/two/index.vwf.json
  65. 14 0
      public/defaults/worlds/two/info.json
  66. 二進制
      public/defaults/worlds/two/webimg.jpg
  67. 284 87
      public/drivers/model/aframe/aframe-master.js
  68. 1 0
      public/drivers/model/aframe/aframe-master.js.map
  69. 0 0
      public/drivers/model/aframe/aframe-master.min.js
  70. 0 0
      public/drivers/model/aframe/aframe-master.min.js.map
  71. 380 0
      public/drivers/model/pts.js
  72. 325 77
      public/drivers/model/tone.js
  73. 1098 0
      public/drivers/model/two.js
  74. 951 0
      public/drivers/view/pts.js
  75. 6439 0
      public/drivers/view/ptsjs/pts.js
  76. 5 0
      public/drivers/view/ptsjs/pts.min.js
  77. 576 156
      public/drivers/view/tone.js
  78. 0 0
      public/drivers/view/tonejs/Tone.js
  79. 0 0
      public/drivers/view/tonejs/Tone.js.map
  80. 1065 0
      public/drivers/view/two.js
  81. 16411 0
      public/drivers/view/twojs/two.js
  82. 23 0
      public/drivers/view/twojs/two.min.js
  83. 16403 0
      public/drivers/view/twojs/two.module.js
  84. 2 1
      public/index.js
  85. 21 0
      public/lib/ui/goober/LICENSE
  86. 786 0
      public/lib/ui/goober/README.md
  87. 0 0
      public/lib/ui/goober/dist/goober.esm.js
  88. 0 0
      public/lib/ui/goober/dist/goober.js
  89. 0 0
      public/lib/ui/goober/dist/goober.modern.js
  90. 0 0
      public/lib/ui/goober/dist/goober.umd.js
  91. 1 0
      public/lib/ui/goober/global/dist/goober-global.esm.js
  92. 1 0
      public/lib/ui/goober/global/dist/goober-global.js
  93. 1 0
      public/lib/ui/goober/global/dist/goober-global.modern.js
  94. 1 0
      public/lib/ui/goober/global/dist/goober-global.umd.js
  95. 19 0
      public/lib/ui/goober/global/global.d.ts
  96. 41 0
      public/lib/ui/goober/global/package.json
  97. 9 0
      public/lib/ui/goober/global/src/__tests__/__snapshots__/integration.test.js.snap
  98. 22 0
      public/lib/ui/goober/global/src/__tests__/global.test.js
  99. 56 0
      public/lib/ui/goober/global/src/__tests__/integration.test.js
  100. 26 0
      public/lib/ui/goober/global/src/index.js

+ 4 - 1
.gitignore

@@ -21,4 +21,7 @@ config.json
 
 npm-debug.log
 error.log
-combined.log
+combined.log
+
+public/defaults/assets/test
+public/defaults/assets/concert

+ 2 - 6
package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "livecodingspace",
-  "version": "0.7.2",
+  "version": "0.8.1",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "livecodingspace",
-      "version": "0.7.2",
+      "version": "0.8.1",
       "license": "MIT",
       "dependencies": {
         "body-parser": "^1.19.0",
@@ -681,10 +681,6 @@
       "resolved": "https://registry.npmjs.org/gun/-/gun-0.2020.520.tgz",
       "integrity": "sha512-hMDmj4QQnejweZEy4njbyNlll+GO7H8XIq/SdQ23kFV29B6HqA0+bQmhLCxWKLwQvfRXx9SgSXG/ImGrER/CqQ==",
       "dependencies": {
-        "@peculiar/webcrypto": "^1.1.1",
-        "buffer": "^5.4.3",
-        "emailjs": "^2.2.0",
-        "text-encoding": "^0.7.0",
         "ws": "^7.2.1"
       },
       "engines": {

+ 1 - 1
package.json

@@ -1,7 +1,7 @@
 {
   "name": "livecodingspace",
   "description": "LiveCoding.Space",
-  "version": "0.8.0",
+  "version": "0.8.1",
   "author": "Nikolai Suslov",
   "homepage": "https://livecoding.space",
   "repository": {

+ 76 - 5
public/core/app.js

@@ -1,6 +1,6 @@
 /*
 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)
+Copyright (c) 2014-2021 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
 
 Virtual World Framework Apache 2.0 license  (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
 */
@@ -12,12 +12,19 @@ import { WorldApp } from '/web/world-app.js';
 import { Widgets } from '/lib/ui/widgets.js';
 import {Spinner} from '/lib/ui/spinjs/spin.js';
 
+import { Standalone } from '/web/standalone.js';
+import '/lib/ui/shoelace/dist/shoelace.js';
+import { setBasePath } from '/lib/ui/shoelace/dist/utilities/base-path.js';
+import { h, text, patch } from '/lib/ui/superfine.js';
+
 import { createAdapter } from '/lib/fun/@most/adapter/dist/index.mjs';
 import *  as mostSubject from '/lib/fun/@most/subject/dist/index.all.js';
 
 class App {
   constructor() {
     console.log("app constructor");
+    setBasePath('/lib/ui/shoelace');
+
     this.widgets = new Widgets;
     //globals
     window._app = this;
@@ -75,6 +82,7 @@ class App {
     page('/debug', this.HandleDebugIndex);
     page('/settings', this.HandleSettingsIndex);
     page('/profile', this.HandleUserIndex);
+    page('/login', this.HandleUserLogin);
     page('/worlds', this.HandleIndex);
     page('/:user', this.HandleUserWorlds);
     page('/:user/worlds', this.HandleUserWorlds);
@@ -111,7 +119,11 @@ class App {
       'd3DoF': false,
       'd6DoF': false,
       'streamMsg': false,
-      'multisocket': false 
+      'multisocket': false,
+      'edit': false,
+      'standalone': false,
+      "standaloneWorldName": "concert",
+      "standaloneUserName": "aaa"
     }
 
     let conf = JSON.parse(localStorage.getItem('lcs_config'));
@@ -186,6 +198,8 @@ class App {
     let manualConfig = localStorage.getItem('lcs_app_manual_settings');
     let lcsappConfig = localStorage.getItem('lcs_app');
 
+    let edit = localStorage.getItem('edit');
+
     localStorage.clear();
 
     if (config)
@@ -200,6 +214,9 @@ class App {
     if (lcsappConfig)
       localStorage.setItem('lcs_app', lcsappConfig);
 
+    if (edit)
+      localStorage.setItem('edit', edit);
+
   }
 
   setupLoadScreen(){
@@ -530,6 +547,32 @@ class App {
 
   }
 
+  HandleUserLogin(ctx) {
+
+    console.log("user login");
+
+    let userAlias = ctx.params.user;
+    let worldName = ctx.params.space;
+    let saveName = ctx.params.savename;
+
+    window._app.hideProgressBar();
+    //window._app.hideUIControl();
+
+
+    _app.generalIndex().then(res=>{
+
+    if (!_app.indexApp) {
+      _app.indexApp = new IndexApp('login');
+    }
+
+    // let worldApp = new WorldApp(userAlias, worldName, saveName);
+    // _app.helpers.getUserPub(userAlias).then(res => {
+    //   worldApp.makeGUI(res)
+    // })
+  })
+
+  }
+
   HandleWorldAbout(ctx) {
 
     console.log("about world");
@@ -1095,7 +1138,7 @@ class App {
     _app.generalIndex().then(r=>{
 
     if (!_app.indexApp) {
-      _app.indexApp = new IndexApp;
+      _app.indexApp = new IndexApp();
     }
     _app.indexApp.allWorldsForUser(user)
 
@@ -1265,6 +1308,8 @@ class App {
       '/lib/ui/ace/ace.js',
       '/lib/ui/drag-drop.js',
       '/lib/buffer5.6.0.min.js',
+      '/lib/ui/shoelace/dist/themes/base.css'
+
     ], {
       async: false,
       returnPromise: true
@@ -1297,14 +1342,31 @@ class App {
     console.log("INDEX");
 
     //window._app.hideUIControl();
+    if(_app.config.standalone){
+      loadjs([
+        '/lib/ui/shoelace/dist/themes/base.css'
+      ], {
+        async: false,
+        returnPromise: true
+      }).then(r=>{
+        _app.indexApp = new Standalone(_app.config.standaloneUserName, _app.config.standaloneWorldName);
+        _app.hideProgressBar();
+      })
+    } else {
+
     _app.generateFrontPage();
 
     _app.generalIndex().then(res=>{
       if (!_app.indexApp) {
-        _app.indexApp = new IndexApp('index');
-        _app.hideProgressBar();
+          _app.indexApp = new IndexApp('index');
+          _app.hideProgressBar();
       }
     })
+    }
+
+    
+
+
 
   }
 
@@ -1494,6 +1556,9 @@ class App {
       }
     } 
 
+    if(JSON.parse(localStorage.getItem('edit')) == true) 
+      vwfApp.conf.view['/drivers/view/editor'] = null;
+
       //check & set default proxy for world
       vwfApp.proxy = val.proxy ? val.proxy : _LCS_WORLD_USER.pub
 
@@ -1611,6 +1676,12 @@ class App {
       ],
       '/drivers/view/tone':[
         '/drivers/view/tonejs/Tone.js'
+      ],
+      '/drivers/view/pts':[
+        '/drivers/view/ptsjs/pts.js'
+      ],
+      '/drivers/view/two':[
+        '/drivers/view/twojs/two.js'
       ]
       
     }

+ 0 - 0
public/defaults/proxy/pts/form.js


+ 9 - 0
public/defaults/proxy/pts/form.vwf.json

@@ -0,0 +1,9 @@
+{
+  "extends": "proxy/pts/node.vwf",
+  "type": "form",
+  "properties": {},
+  "methods": {},
+  "scripts": {
+    "source": "form.js"
+  }
+}

+ 4 - 0
public/defaults/proxy/pts/node.vwf.json

@@ -0,0 +1,4 @@
+{
+    "extends": "proxy/node.vwf",
+    "properties": {}
+}

+ 0 - 0
public/defaults/proxy/pts/player.js


+ 9 - 0
public/defaults/proxy/pts/player.vwf.json

@@ -0,0 +1,9 @@
+{
+  "extends": "proxy/pts/node.vwf",
+  "type": "player",
+  "properties": {},
+  "methods": {},
+  "scripts": {
+    "source": "player.js"
+  }
+}

+ 0 - 0
public/defaults/proxy/pts/pt.js


+ 12 - 0
public/defaults/proxy/pts/pt.vwf.json

@@ -0,0 +1,12 @@
+{
+  "extends": "proxy/pts/node.vwf",
+  "type": "pt",
+  "properties": {
+    "x": null,
+    "y": null
+  },
+  "methods": {},
+  "scripts": {
+    "source": "pt.js"
+  }
+}

+ 57 - 0
public/defaults/proxy/pts/scene.js

@@ -0,0 +1,57 @@
+this.initialize = function () {
+
+    if(Object.getPrototypeOf(this).id.includes('scene.vwf')){
+        console.log("Initialize of Scene...", this.id);
+        this.future(3).clientWatch();
+    } else {
+        console.log("Initialize proto Scene..", this.id);
+    }
+   
+    //this.createDefaultXRCostume();
+};
+
+this.clientWatch = function () {
+
+    var self = this;
+
+    if (this.children.length !== 0) {
+
+        var clients = this.find("doc('proxy/clients.vwf')")[0];
+
+        if (clients !== undefined) {
+            //console.log(clients.children);
+
+            let clientsArray = [];
+
+            clients.children.forEach(function (element) {
+                clientsArray.push(element.name);
+
+            });
+
+            this.children.forEach(function (node) {
+                if (node.id.indexOf('avatar-') != -1) {
+
+                    if (clientsArray.includes(node.id.slice(7))) {
+                        //console.log(node.id + 'is here!');
+                    } else {
+                        //console.log(node.id + " needed to delete!");
+                        let idToDelete = node.id.slice(7);
+                        let nodes = self.children.filter(el=>
+                            (el.id.includes(idToDelete) && 
+                            (   el.id.includes('avatar-') ||
+                                el.id.includes('xrcontroller-') ||
+                                el.id.includes('mouse-') ||
+                                el.id.includes('gearvr-'))) 
+                               
+                            );
+
+                        nodes.forEach(el => {
+                            self.children.delete(self.children[el.id])
+                        })
+                    }
+                }
+            });
+        }
+    }
+    this.future(5).clientWatch();
+};

+ 11 - 0
public/defaults/proxy/pts/scene.vwf.json

@@ -0,0 +1,11 @@
+{
+  "extends": "proxy/pts/node.vwf",
+  "type": "scene",
+  "properties": {},
+  "methods": {
+    "clientWatch": {}
+  },
+  "scripts": {
+    "source": "scene.js"
+  }
+}

+ 0 - 0
public/defaults/proxy/tonejs/node.js


+ 31 - 0
public/defaults/proxy/tonejs/player.js

@@ -0,0 +1,31 @@
+this.start = function(){
+    //on view side
+}
+
+this.syncStart = function(time){
+    //on view side
+}
+
+this.stop = function(){
+    //on view side
+}
+
+this.pause = function(){
+    //on view side
+}
+
+
+this.playBuffer = function(){
+
+}
+this.syncBufferState = function(){
+
+}
+
+this.setBufferState = function(isPlaying, startOffset, pausedTime){
+   
+    this.isPlaying = isPlaying;
+    this.startOffset = startOffset;
+    this.pausedTime = pausedTime;
+
+  }

+ 46 - 0
public/defaults/proxy/tonejs/player.vwf.json

@@ -0,0 +1,46 @@
+{
+    "extends": "proxy/tonejs/node.vwf",
+    "properties": {
+        "url": null,
+        "autostart":null,
+        "fadeIn": null,
+        "fadeOut": null,
+        "mute": null,
+        "loop": null,
+        "loopStart": null,
+        "loopEnd": null,
+        "volume": null,
+        "playbackRate": null,
+        "duration": null,
+        "sampleTime": null,
+        "state":null,
+        "startOffset":null,
+        "pausedTime": null,
+        "isPlaying": null,
+        "startTime": null,
+        "inSync": null
+        },
+    "methods":{
+        "start": {},
+        "syncStart": {
+            "parameters":["time"]
+        },
+        "stop": {},
+        "pause": {},
+        "setLoopPoints":{
+            "parameters":["start", "end"]
+        },
+        "playBuffer":{},
+        "syncBufferState":{},
+        "setBufferState":{
+          "parameters":[
+            "isPlaying",
+            "startOffset",
+            "pausedTime"
+          ]
+        }
+    },
+    "scripts": {
+      "source": "player.js"
+    }
+}

+ 2 - 0
public/defaults/proxy/tonejs/polySynth.vwf.json

@@ -2,6 +2,8 @@
   "extends": "proxy/tonejs/node.vwf",
   "properties": {},
   "methods":{
+    "scheduleRepeat":{},
+    "sync":{},
     "triggerAttackRelease":{
       "parameters": [
         "note", "duration", "time", "velocity"

+ 29 - 0
public/defaults/proxy/tonejs/transport.js

@@ -0,0 +1,29 @@
+this.start = function () {
+    //on view side
+}
+
+this.stop = function () {
+    //on view side
+}
+
+this.pause = function () {
+    //on view side
+}
+
+
+this.toggleTransport = function () {
+
+}
+this.syncTransportState = function () {
+
+}
+
+
+
+this.setTransportState = function (isPlaying, startOffset, pausedTime) {
+
+    this.isPlaying = isPlaying;
+    this.startOffset = startOffset;
+    this.pausedTime = pausedTime;
+
+}

+ 40 - 0
public/defaults/proxy/tonejs/transport.vwf.json

@@ -0,0 +1,40 @@
+{
+    "extends": "proxy/tonejs/node.vwf",
+    "properties": {
+        "bpm":null,
+        "loop": null,
+        "loopStart": null,
+        "loopEnd": null,
+        "position": null,
+        "progress": null,
+        "sampleTime": null,
+        "seconds": null,
+        "ticks": null,
+        "timeSignature": null,
+        "state":null,
+        "startOffset":null,
+        "pausedTime": null,
+        "isPlaying": null,
+        "duration": null
+        },
+    "methods":{
+        "start": {},
+        "stop": {},
+        "pause": {},
+        "setLoopPoints":{
+            "parameters":["start", "end"]
+        },
+        "toggleTransport":{},
+        "syncTransportState":{},
+        "setTransportState":{
+          "parameters":[
+            "isPlaying",
+            "startOffset",
+            "pausedTime"
+          ]
+        }
+    },
+    "scripts": {
+      "source": "transport.js"
+    }
+}

+ 0 - 0
public/defaults/proxy/two/anchor.js


+ 18 - 0
public/defaults/proxy/two/anchor.vwf.json

@@ -0,0 +1,18 @@
+{
+  "extends": "proxy/two/node.vwf",
+  "type": "anchor",
+  "properties": {
+    "x": null,
+    "y": null,
+    "lx": null,
+    "ly": null,
+    "rx": null,
+    "ry": null,
+    "command":null,
+    "controls":null
+  },
+  "methods": {},
+  "scripts": {
+    "source": "anchor.js"
+  }
+}

+ 0 - 0
public/defaults/proxy/two/curve.js


+ 13 - 0
public/defaults/proxy/two/curve.vwf.json

@@ -0,0 +1,13 @@
+{
+  "extends": "proxy/two/path.vwf",
+  "type": "curve",
+  "implements": [
+    "proxy/two/path.vwf"
+  ],
+  "properties": {
+  },
+  "methods": {},
+  "scripts": {
+    "source": "curve.js"
+  }
+}

+ 0 - 0
public/defaults/proxy/two/ellipse.js


+ 15 - 0
public/defaults/proxy/two/ellipse.vwf.json

@@ -0,0 +1,15 @@
+{
+  "extends": "proxy/two/path.vwf",
+  "type": "ellipse",
+  "implements": [
+    "proxy/two/path.vwf"
+  ],
+  "properties": {
+    "width": null,
+    "height": null
+  },
+  "methods": {},
+  "scripts": {
+    "source": "ellipse.js"
+  }
+}

+ 7 - 0
public/defaults/proxy/two/group.js

@@ -0,0 +1,7 @@
+this.setMask = function(nodeID){
+    //set mask
+}
+
+this.setScale = function(x, y){
+    //scale
+}

+ 29 - 0
public/defaults/proxy/two/group.vwf.json

@@ -0,0 +1,29 @@
+{
+  "extends": "proxy/two/node.vwf",
+  "type": "group",
+  "properties": {
+    "x": null,
+    "y": null,
+    "translation": [0,0],
+    "rotation": null,
+    "scale": null,
+    "mask": null,
+    "visible": null,
+    "opacity":null
+  },
+  "methods": {
+    "setMask":{
+      "parameters":[
+        "nodeID"
+      ]
+    },
+    "setScale":{
+      "parameters":[
+        "x","y"
+      ]
+    }
+  },
+  "scripts": {
+    "source": "group.js"
+  }
+}

+ 79 - 0
public/defaults/proxy/two/node.js

@@ -0,0 +1,79 @@
+this.do = function () {
+    //do in step
+}
+
+this.step = function () {
+
+    if (this.stepping) {
+        this.do();
+    }
+
+    let t = this.stepTime ? this.stepTime : 0.05;
+
+    this.future(t).step();
+}
+
+this.clickEvent = function () {
+    //click event
+}
+
+this.mouseupEvent = function () {
+    //click event
+}
+
+this.mousedownEvent = function () {
+    //click event
+}
+
+this.checkForDragStart = function (avatarID) {
+
+    let av = this.getScene().findNodeByID(avatarID);
+    if (this.drag) {
+        //console.log("DRAG START for ", avatarID)
+                av.dragID = this.id;
+                av.delta = [av.x - this.x, av.y - this.y] 
+    }
+
+}
+
+this.checkForDragEnd = function (avatarID) {
+
+    if (this.drag) {
+       // console.log("DRAG END for ", avatarID)
+        let node = this.getScene().findNodeByID(avatarID);
+        node.dragID = null;
+        node.delta = [0, 0]
+    }
+
+}
+
+this.overstartEvent = function (avatarID) {
+
+    //over start
+
+}
+
+this.overendEvent = function (avatarID) {
+
+    //over end
+
+}
+
+
+this.getRandomColor = function () {
+    var letters = '0123456789ABCDEF';
+    var color = '#';
+    for (var i = 0; i < 6; i++) {
+        color += letters[Math.floor(this.random() * 16)];
+    }
+    return color;
+}
+
+this.getScene = function () {
+    let scene = this.find("/")[0];
+    return scene
+}
+
+this.onGlobalBeat = function(obj){
+    //on global beat
+}

+ 56 - 0
public/defaults/proxy/two/node.vwf.json

@@ -0,0 +1,56 @@
+{
+    "extends": "proxy/node.vwf",
+    "implements": [
+        "proxy/animation/animation.vwf"
+      ],
+    "properties": {
+        "displayName": null,
+        "stepping": null,
+        "stepTime": null,
+        "globalBeat": null,
+        "mouseevent": null,
+        "drag": null,
+        "twoWidth": null,
+        "twoHeight": null,
+        "matrixManual": null
+    },
+    "methods":{
+        "step": {},
+        "do": {},
+        "clickEvent":{},
+        "mouseupEvent":{},
+        "mousedownEvent":{},
+        "getRandomColor":{},
+        "checkForDragStart":{
+            "parameters":["avatarID"]
+        },
+        "checkForDragEnd":{
+            "parameters":["avatarID"]
+        },
+        "overstartEvent":{
+            "parameters":["avatarID"]
+        },
+        "overendEvent":{
+            "parameters":["avatarID"]
+        },
+        "getScene": {},
+        "onGlobalBeat": {
+            "parameters": [
+              "obj"
+            ]
+          },
+          "viewTroughFilter":{
+            "parameters": [
+                "nodeID", "param"
+              ]
+          },
+          "checkOver":{
+            "parameters": [
+                "x", "y"
+              ]
+          }
+    },
+    "scripts": {
+        "source": "node.js"
+      }
+}

+ 107 - 0
public/defaults/proxy/two/objects/transport.js

@@ -0,0 +1,107 @@
+this.initialize = function () {
+    //this.createTransportVis();
+
+}
+
+this.simpleVis = function () {
+    return {
+        "extends": "proxy/two/rectangle.vwf",
+        "properties": {
+            "height": 25,
+            "width": 25,
+            "fill": "#ddd"
+        }
+    }
+}
+
+this.stop = function () {
+
+    this.animationStop();
+    this.playing = false;
+}
+
+this.play = function () {
+
+    this.beat = 0;
+    this.animationPlay(0, this.animationDuration);
+    this.playing = true;
+
+}
+
+this.setupTransport = function () {
+    this.beat = 0;
+    this.animationLoop = true;
+    this.animationDuration = 1;
+    this.animationRate = 1;
+    this.animationTPS = 30;
+    //this.animationPlay(0,1);
+}
+
+this.animationUpdate = function (time, duration) {
+           //let sceneID = this.find("/")[0].id
+           let rate = this.animationTPS / this.animationRate; // 60/1 by default
+
+           var b = this.beat;
+           //console.log('b: ' + this.beat + ' t: ' + time + ' ta: ' + this.time);
+        
+           if (time == this.animationDuration) { //time == 1 && 
+               this.beat = 0;
+               //this.doGlobalBeat(time, duration, b);
+           } else {
+            
+            if(this.beat !== rate) {
+                this.doGlobalBeat(time, duration, b)
+            }
+
+            this.beat = this.beat + 1;
+           }
+    
+
+}
+
+this.resetVisual = function(){
+    this.vis.fill = "#ddd";
+}
+
+this.changeVisual = function(){
+    this.vis.fill = "green";
+    //this.future(0.5).resetVisual();
+}
+
+this.doGlobalBeat = function (time, duration, beat) {
+
+this.changeVisual();
+
+   let allChilds = this.find("//element(*,'proxy/two/node.vwf')"); //this.children
+   allChilds.forEach(el => {
+        if (el.globalBeat) {
+            let obj = {
+                name: this.name,
+                time: time,
+                duration: duration,
+                beat: beat
+            }
+            el.onGlobalBeat(obj);
+        }
+    })
+}
+
+this.init = function () {
+
+
+    this.children.create("vis", this.simpleVis(), function (child) {
+
+        child.mousedownEvent = function () {
+            if (this.parent.animationPlaying) {
+                this.parent.stop();
+                this.parent.resetVisual();
+            } else {
+                this.parent.play();
+                this.parent.changeVisual();
+            }
+        }
+
+    });
+    this.setupTransport();
+    //this.play();
+}

+ 33 - 0
public/defaults/proxy/two/objects/transport.vwf.json

@@ -0,0 +1,33 @@
+{
+    "extends": "proxy/two/group.vwf",
+    "type": "transport",
+    "properties": {
+        "beat": null,
+        "playing": null
+    },
+    "methods": {
+        "init": {},
+        "simpleVis": {},
+        "changeVisual": {},
+        "resetVisual": {},
+        "animationUpdate": {
+            "parameters": [
+                "time",
+                "duration"
+            ]
+        },
+        "play": {},
+        "stop": {},
+        "setupTransport": {},
+        "doGlobalBeat": {
+            "parameters": [
+                "time",
+                "duration",
+                "beat"
+            ]
+        }
+    },
+    "scripts": {
+        "source": "transport.js"
+    }
+}

+ 18 - 0
public/defaults/proxy/two/path.js

@@ -0,0 +1,18 @@
+this.playVideo = function(){
+
+}
+this.syncVideoState = function(){
+
+}
+
+this.unmute = function(){
+
+}
+
+this.setVideoState = function(isPlaying, startOffset, pausedTime){
+   
+    this.isPlaying = isPlaying;
+    this.startOffset = startOffset;
+    this.pausedTime = pausedTime;
+
+  }

+ 39 - 0
public/defaults/proxy/two/path.vwf.json

@@ -0,0 +1,39 @@
+{
+  "extends": "proxy/two/node.vwf",
+  "type": "path",
+  "properties": {
+    "x": null,
+    "y": null,
+    "translation": [0,0],
+    "rotation": null,
+    "scale": null,
+    "fill": null,
+    "stroke": null,
+    "linewidth": null,
+    "opacity": null,
+    "clip": null,
+    "startOffset":null,
+    "pausedTime": null,
+    "isPlaying": null,
+    "vertices": [],
+    "curved":null,
+    "closed":null,
+    "join":null,
+    "automatic":null
+  },
+  "methods": {
+    "playVideo":{},
+    "syncVideoState":{},
+    "unmute":{},
+    "setVideoState":{
+      "parameters":[
+        "isPlaying",
+        "startOffset",
+        "pausedTime"
+      ]
+    }
+  },
+  "scripts": {
+    "source": "path.js"
+  }
+}

+ 62 - 0
public/defaults/proxy/two/player.js

@@ -0,0 +1,62 @@
+this.initialize = function() {
+    // this.future(0).updateAvatar();
+  
+ };
+
+ this.createPlayerBody = function() {
+   
+    let color = this.getRandomColor();
+
+    let vis = {
+          "extends": "proxy/two/group.vwf",
+          "properties": {},
+          "children":{
+            "body":{
+              "extends": "proxy/two/ellipse.vwf",
+              "properties": {
+                "width": 30,
+                "height": 30,
+                "fill": color,
+                "opacity": 0.7,
+                "linewidth": 4
+              }
+            }
+          }
+        }
+      
+   
+
+    this.children.create("vis", vis, function(child){
+
+            this.delta = [0,0]
+            this.stepping = true;
+            this.stepTime = 0.2;
+            this.step();
+            // if (!nodeDef) {
+            
+            // }
+    
+        });
+
+ };
+
+ this.move = function(x,y){
+    this.x = x;
+    this.y = y;
+
+    if(this.dragID){
+        let node = this.getScene().findNodeByID(this.dragID);
+        node.x = this.x - this.delta[0];
+        node.y = this.y - this.delta[1]
+    }
+
+ }
+
+ this.checkOver = function(x, y){
+    //console.log("CHECK OVER from ", this.id);
+
+ }
+
+this.do = function(){
+        this.checkOver(this.x, this.y);
+}

+ 21 - 0
public/defaults/proxy/two/player.vwf.json

@@ -0,0 +1,21 @@
+{
+  "extends": "proxy/two/group.vwf",
+  "properties": {
+    "dragID":null,
+    "delta": null
+  },
+  "methods": {
+    "initialize": {},
+    "createPlayerBody": {},
+    "updateAvatar": {},
+    "move":{
+      "parameters":["x","y"]
+    },
+    "checkOver":{
+      "parameters":["x","y"]
+    }
+  },
+  "scripts": {
+    "source": "player.js"
+  }
+}

+ 0 - 0
public/defaults/proxy/two/rectangle.js


+ 15 - 0
public/defaults/proxy/two/rectangle.vwf.json

@@ -0,0 +1,15 @@
+{
+  "extends": "proxy/two/path.vwf",
+  "type": "rectangle",
+  "implements": [
+    "proxy/two/path.vwf"
+  ],
+  "properties": {
+    "width": null,
+    "height": null
+  },
+  "methods": {},
+  "scripts": {
+    "source": "rectangle.js"
+  }
+}

+ 55 - 0
public/defaults/proxy/two/scene.js

@@ -0,0 +1,55 @@
+this.initialize = function () {
+
+    if(Object.getPrototypeOf(this).id.includes('scene.vwf')){
+        console.log("Initialize of Scene...", this.id);
+        this.future(3).clientWatch();
+    } else {
+        console.log("Initialize proto Scene..", this.id);
+    }
+   
+    //this.createDefaultXRCostume();
+};
+
+this.clientWatch = function () {
+
+    var self = this;
+
+    if (this.children.length !== 0) {
+
+        var clients = this.find("doc('proxy/clients.vwf')")[0];
+
+        if (clients !== undefined) {
+            //console.log(clients.children);
+
+            let clientsArray = [];
+
+            clients.children.forEach(function (element) {
+                clientsArray.push(element.name);
+
+            });
+
+            this.children.forEach(function (node) {
+                if (node.id.indexOf('avatar-') != -1) {
+
+                    if (clientsArray.includes(node.id.slice(7))) {
+                        //console.log(node.id + 'is here!');
+                    } else {
+                        //console.log(node.id + " needed to delete!");
+                        let idToDelete = node.id.slice(7);
+                        let nodes = self.children.filter(el=>
+                            (el.id.includes(idToDelete) && 
+                            (   el.id.includes('avatar-') ||
+                                el.id.includes('mouse-'))) 
+                            );
+
+                        nodes.forEach(el => {
+                            self.children[el.id].stepping = false;
+                            self.children.delete(self.children[el.id])
+                        })
+                    }
+                }
+            });
+        }
+    }
+    this.future(5).clientWatch();
+};

+ 11 - 0
public/defaults/proxy/two/scene.vwf.json

@@ -0,0 +1,11 @@
+{
+  "extends": "proxy/two/node.vwf",
+  "type": "scene",
+  "properties": {},
+  "methods": {
+    "clientWatch": {}
+  },
+  "scripts": {
+    "source": "scene.js"
+  }
+}

+ 0 - 0
public/defaults/proxy/two/text.js


+ 23 - 0
public/defaults/proxy/two/text.vwf.json

@@ -0,0 +1,23 @@
+{
+  "extends": "proxy/two/node.vwf",
+  "type": "text",
+  "properties": {
+    "text": null,
+    "family": null,
+    "size": null,
+    "fill": null,
+    "stroke": null,
+    "linewidth": null,
+    "style": null,
+    "weight": null,
+    "opacity": null,
+    "visible": null,
+    "rotation": null,
+    "scale": null,
+    "translation": null
+  },
+  "methods": {},
+  "scripts": {
+    "source": "ellipse.js"
+  }
+}

+ 0 - 0
public/defaults/proxy/two/texture.js


+ 11 - 0
public/defaults/proxy/two/texture.vwf.json

@@ -0,0 +1,11 @@
+{
+  "extends": "proxy/two/node.vwf",
+  "type": "texture",
+  "properties": {
+    "src": null
+  },
+  "methods": {},
+  "scripts": {
+    "source": "texture.js"
+  }
+}

+ 15 - 0
public/defaults/worlds/concert/appui.js

@@ -0,0 +1,15 @@
+//-----App ui-----
+
+function createApp() {
+    
+        let self = this
+    
+        return {
+            $cell: true,
+            $type: "div",
+            class: "propGrid max-width mdc-layout-grid mdc-layout-grid--align-left",
+            $components: []
+        }
+
+    }
+    

+ 566 - 0
public/defaults/worlds/concert/concert.js

@@ -0,0 +1,566 @@
+this.initialize = function () {
+
+    if (Object.getPrototypeOf(this).id.includes('scene.vwf')) {
+        console.log("Initialize of Scene...", this.id);
+
+        this.watchPerformance();
+        //this.globalTransport.init();
+
+        this.back.back1.mask = "filter1";
+        this.back.back2.mask = "filter2";
+        this.back.back3.mask = "filter3";
+        //this.back.back1.rectangle.fill = "https://localhost:3007/defaults/assets/test/test.mp4";
+
+        //this.back.back1.rectangle.playVideo();
+
+
+        this.back.back1.rectangle.setVideoState(true, null, 0);
+        this.back.back1.rectangle.syncVideoState();
+        this.back.back3.rectangle.setVideoState(false, null, 0);
+
+
+        // this.tuning.syncStart();
+        this.toneTransport.setTransportState(false, null, 0);
+
+        this.filter1.el2.step();
+
+        //this.toneGUI.toneText.step();
+
+    } else {
+        console.log("Initialize proto Scene..", this.id);
+    }
+
+}
+
+this.setStartTime = function () {
+
+    this.tuning.startTime = this.toneTransport.position;
+}
+
+this.watchPerformance = function () {
+    let fixedTime = Number.parseFloat(this.time).toFixed();
+    //this.timeText.text = fixedTime.toString();
+
+    let secondsN = fixedTime//Math.floor(this.time/2);
+    let minutesN = Math.floor(secondsN / 60);
+    let hoursN = Math.floor(minutesN / 60);
+
+    let seconds = Number.parseFloat(secondsN % 60).toFixed();
+    let minutes = minutesN % 60;
+    let hours = hoursN % 12;
+
+    this.timeText.text = (hours ? (hours > 9 ? hours : "0" + hours) : "00") + ":" + (minutes ? (minutes > 9 ? minutes : "0" + minutes) : "00") + ":" + (seconds > 9 ? seconds : "0" + seconds);
+
+    if (fixedTime == 45) {
+        if (this.part2 !== "started") {
+            this.part1 = "finished";
+            this.back.back1.rectangle.fill = "black";
+            if (this.toneTransport) {
+                this.toneTransport.syncTransportState();
+                this.toneTransport.toggleTransport();
+                this.future(2).setStartTime();
+                //this.tuning.future(2).syncStart(this.tuning.startTime);
+                this.tuning.volume = -15;
+            }
+            this.part2 = "started";
+
+            let tuningText = {
+                "extends": "proxy/two/text.vwf",
+                "properties": {
+                    "size": 150,
+                    "fill": "white",
+                    "text": "",
+                    "stroke": "normal",
+                    "linewidth": 4,
+                    "translation": [500, 300]
+                }
+            }
+            this.children.create("tuningText", tuningText, function (child) { });
+
+        }
+        //84 - tuning
+    }
+
+    if (fixedTime == 99) {
+
+        this.back.back2.logo_text.text = "";
+        this.tuningText.text = 'what is this?'
+    }
+
+    if (fixedTime == 105) {
+        this.tuningText.text = ''
+    }
+
+    if (fixedTime == 110) {
+        this.tuningText.text = 'I don\'t know'
+    }
+
+    if (fixedTime == 115) {
+        this.tuningText.text = ''
+    }
+
+    if (fixedTime == 126) {
+        this.tuningText.text = ' toi toi toi'
+    }
+
+
+    if (fixedTime == 129) { //129
+
+        if (this.part3 !== "started") {
+            this.part2 = "finished";
+            this.tuningText.text = ''
+
+            if (this.toneTransport) {
+                this.toneTransport.syncTransportState();
+                this.toneTransport.toggleTransport();
+            }
+
+            this.back.back1.rectangle.fill = "black";
+            this.back.back1.rectangle.playVideo();
+            this.back.back1.visible = false;
+            this.filter1.visible = false;
+            this.filter2.visible = false;
+            this.filter1.el1.drag = false;
+            this.filter1.el2.drag = false;
+            this.filter2.el2.drag = false;
+            this.filter2.visible = false;
+
+
+            //this.back.back3.rectangle.setVideoState(true, null, 0);
+            //this.back.back3.rectangle.unmute();
+            this.startPart3();
+
+            this.part3 = "started"
+        }
+
+    }
+
+    if (fixedTime == 202) { //205
+        if (this.part4 !== "started") {
+            this.part3 = "finished";
+            this.back.back3.rectangle.playVideo();
+            //this.back.back3.rectangle.fill = "black";
+            this.back.back3.visible = false;
+            this.body.visible = false;
+            this.tuningText.text = "";
+            this.tuningText.opacity = 0.3;
+            this.tuningText.fill = "grey";
+
+            this.startPart4();
+
+            this.part4 = "started"
+        }
+
+    }
+
+    this.future(1).watchPerformance();
+
+}
+
+this.resize = function () {
+
+}
+
+this.startPart4 = function () {
+
+    let self = this;
+
+    let applause2 = {
+        "extends": "proxy/two/ellipse.vwf",
+        "properties": {
+            "width": 200,
+            "height": 200,
+            "fill": "/defaults/assets/concert/hands.png",
+            x: 220,
+            y: 630,
+            "drag": true,
+            linewidth: 0,
+            stroke: "transparent",
+            opacity: 0.7,
+            scale: 0.4
+        }
+    }
+
+    let applause3 = {
+        "extends": "proxy/two/ellipse.vwf",
+        "properties": {
+            "width": 200,
+            "height": 200,
+            "fill": "/defaults/assets/concert/hands.png",
+            x: 460,
+            y: 560,
+            "drag": true,
+            linewidth: 0,
+            stroke: "transparent",
+            opacity: 0.7,
+            scale: 0.7
+        }
+    }
+    let applause4 = {
+        "extends": "proxy/two/ellipse.vwf",
+        "properties": {
+            "width": 200,
+            "height": 200,
+            "fill": "/defaults/assets/concert/hands.png",
+            x: 740,
+            y: 600,
+            "drag": true,
+            linewidth: 0,
+            stroke: "transparent",
+            opacity: 0.7,
+            scale: 1
+        }
+    }
+
+    let clapping2 = {
+        "extends": "proxy/tonejs/player.vwf",
+        "properties": {
+            "url": "/defaults/assets/concert/clapping2.mp3",
+            "volume": -10
+        }
+    }
+
+    let clapping3 = {
+        "extends": "proxy/tonejs/player.vwf",
+        "properties": {
+            "url": "/defaults/assets/concert/clapping3.mp3",
+            "volume": -10
+        }
+    }
+    let clapping4 = {
+        "extends": "proxy/tonejs/player.vwf",
+        "properties": {
+            "url": "/defaults/assets/concert/clapping4.mp3",
+            "volume": -10
+        }
+    }
+
+    this.children.create("clapping2", clapping2);
+    this.children.create("clapping3", clapping3);
+    this.children.create("clapping4", clapping4);
+
+
+    this.children.create("applause2", applause2, function (child) {
+
+        child.mousedownEvent = function () {
+            this.parent.clapping2.start();
+        }
+
+        child.mouseupEvent = function () {
+            this.parent.clapping2.stop();
+        }
+    });
+
+    this.children.create("applause3", applause3, function (child) {
+
+        child.mousedownEvent = function () {
+            this.parent.clapping3.start();
+        }
+
+        child.mouseupEvent = function () {
+            this.parent.clapping3.stop();
+        }
+    });
+
+    this.children.create("applause4", applause4, function (child) {
+
+        child.mousedownEvent = function () {
+            this.parent.clapping4.start();
+        }
+
+        child.mouseupEvent = function () {
+            this.parent.clapping4.stop();
+        }
+    });
+
+}
+
+this.startPart3 = function () {
+
+    //
+    if (this.body.children.length == 0)
+        this.createBody();
+
+    //this.back.back3.rectangle.setVideoState(true, null, 0);
+    this.back.back3.rectangle.syncVideoState();
+
+    //this.back.back3.rectangle.syncVideoState();
+    //this.back.back3.rectangle.isPlaying = true;
+    this.back.back3.opacity = 1;
+    this.filter3.el1.opacity = 0.35;
+    this.filter3.el2.opacity = 0.35;
+
+    this.filter3.el1.drag = true;
+    this.filter3.el2.drag = true;
+
+    this.back.back3.rectangle.playVideo();
+
+    //this.back.back3.rectangle.unmute();
+
+
+    //this.body.playBodyMotion("https://localhost:3007/defaults/assets/concert/motion.json");
+}
+
+
+this.playBodyMotion = function () {
+
+    let initPose = this.body.getJointsAtTime(0);
+    initPose.forEach((e, i) => {
+
+        this.body.children["joint" + i].x = e.x * 1000;
+        this.body.children["joint" + i].y = e.y * 1000;
+    })
+
+
+}
+
+this.createBody = function () {
+    let self = this;
+
+    // let body = {
+    //     "extends": "proxy/two/group.vwf",
+    //     "properties": {
+    //     "drag": false,
+    //       "x": 239,
+    //       "y": -57,
+    //       "motionData": "https://localhost:3007/defaults/assets/concert/motion.json"
+    //     },
+    //     "methods":{
+    //         "applyBodyMotion":{
+    //             "parameters":["data"]
+    //         },
+    //         "getJointAtTime":{},
+    //         "getJointsAtTime":{}
+    //     }
+    //   }
+
+    let child = this.body;
+    child.motionData = "/defaults/assets/concert/motion.json";
+
+    //this.children.create("body", body, function( child ) {
+
+    let hands = [18, 20, 22, 21, 19, 17, 15, 16];
+    let face = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+
+    let topArr = [16, 14, 12, 11, 13, 15, 24, 23];
+    let botArr = [24, 23];
+
+    //let faceArr = [10,8,6,4,1,3,7,9]
+
+    Array.from({ length: 25 }, (x, i) => {
+
+        let d = 20;
+
+        let joint = {
+            "extends": "proxy/two/ellipse.vwf",
+            "properties": {
+                "width": d,
+                "height": d,
+                "fill": "yellow"
+            }
+        }
+
+        if (face.includes(i)) {
+            joint.properties.width = 5;
+            joint.properties.height = 5;
+            joint.properties.fill = "grey";
+            joint.properties.opacity = 0.2;
+        }
+
+        if (hands.includes(i)) {
+            joint.properties.width = 10;
+            joint.properties.height = 10;
+            joint.properties.fill = "grey";
+            joint.properties.opacity = 0.5;
+        }
+
+        if (topArr.includes(i)) {
+            joint.properties.opacity = 0.2;
+        }
+
+        if (botArr.includes(i)) {
+            joint.properties.opacity = 0;
+        }
+
+        child.children.create("joint" + i, joint);
+    });
+
+    //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 rh = {
+        "extends": "proxy/two/curve.vwf",
+        "properties": {
+            "vertices": [{ x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }],
+            "fill": "transparent",
+            "linewidth": 1,
+            "curved": true
+        }
+    }
+    let rh2 = {
+        "extends": "proxy/two/curve.vwf",
+        "properties": {
+            "vertices": [{ x: 0, y: 0 }, { x: 0, y: 0 }],
+            "fill": "transparent",
+            "linewidth": 1,
+            "curved": true
+        }
+    }
+
+    let lh = {
+        "extends": "proxy/two/curve.vwf",
+        "properties": {
+            "vertices": [{ x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }],
+            "fill": "transparent",
+            "linewidth": 1,
+            "curved": true
+        }
+    }
+
+    let lh2 = {
+        "extends": "proxy/two/curve.vwf",
+        "properties": {
+            "vertices": [{ x: 0, y: 0 }, { x: 0, y: 0 }],
+            "fill": "transparent",
+            "linewidth": 1,
+            "curved": true
+        }
+    }
+
+    let topline = {
+        "extends": "proxy/two/curve.vwf",
+        "properties": {
+            "vertices": [{ x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }],
+            "fill": "transparent",
+            "linewidth": 3,
+            "curved": true
+        }
+    }
+
+    let bottomline = {
+        "extends": "proxy/two/curve.vwf",
+        "properties": {
+            "vertices": [{ x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }], //
+            "fill": "transparent",
+            "linewidth": 3,
+            "curved": true
+        }
+    }
+
+    let faceline = {
+        "extends": "proxy/two/curve.vwf",
+        "properties": {
+            "vertices": [{ x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 },
+            { x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }], //
+            "fill": "transparent",
+            "linewidth": 1.5,
+            "curved": true
+        }
+    }
+
+    child.children.create("topline", topline);
+    child.children.create("bottomline", bottomline);
+    child.children.create("rhand", rh);
+    child.children.create("lhand", lh);
+    child.children.create("rhand2", rh2);
+    child.children.create("lhand2", lh2);
+    child.children.create("faceline", faceline);
+
+
+    child.applyBodyMotion = function (data) {
+        console.log(data);
+    }
+
+    //child.setScale(-1,1);
+
+    self.back.back3.rectangle.bodyTrack = true;
+    self.back.back3.rectangle.bodyNode = "body";
+    //self.future(2).playBodyMotion();
+
+    //});
+}
+
+
+this.filter2.el2.mousedownEvent = function () {
+
+    this.fill = this.getRandomColor();
+    console.log('CLICK ', this.id);
+}
+
+
+this.filter3.el1.overstartEvent = function (avatarID) {
+    let scene = this.getScene();
+    if (scene.part3 == "started") {
+        let nodeName = this.parent.maskedNode;
+        if (nodeName) {
+            let node = scene.findNode(nodeName).rectangle;
+            this.viewTroughFilter(node.id, false);
+            //node.visible = true;
+        }
+    }
+}
+
+this.filter3.el1.overendEvent = function (avatarID) {
+    let scene = this.getScene();
+    if (scene.part3 == "started") {
+        let nodeName = this.parent.maskedNode;
+        if (nodeName) {
+            let node = scene.findNode(nodeName).rectangle;
+            this.viewTroughFilter(node.id, true);
+            //node.visible = false;
+        }
+    }
+}
+
+this.filter1.el2.do = function () {
+    this.rotation = this.rotation + 0.001;
+}
+
+this.filter3.el2.overstartEvent = function (avatarID) {
+    let scene = this.getScene();
+    if (scene.part3 == "started") {
+        let nodeName = this.parent.maskedNode;
+        if (nodeName) {
+            let node = scene.findNode(nodeName).rectangle;
+            this.viewTroughFilter(node.id, false);
+            //node.visible = true;
+        }
+    }
+}
+
+
+this.filter3.el2.overendEvent = function (avatarID) {
+    let scene = this.getScene();
+    if (scene.part3 == "started") {
+        let nodeName = this.parent.maskedNode;
+        if (nodeName) {
+            let node = scene.findNode(nodeName).rectangle;
+            this.viewTroughFilter(node.id, true);
+            //node.visible = false;
+        }
+    }
+}
+
+this.filter1.el2.overstartEvent = function (avatarID) {
+    // let nodeName = this.parent.maskedNode;
+    // if (nodeName) {
+    //     let node = this.getScene().findNode(nodeName);
+    //     //this.viewTroughFilter(node.id, true);
+    //     //node.visible = true;
+    // }
+}
+
+
+this.filter1.el2.overendEvent = function (avatarID) {
+
+    // let nodeName = this.parent.maskedNode;
+    // if (nodeName) {
+    //     let node = this.getScene().findNode(nodeName);
+    //     //this.viewTroughFilter(node.id, false);
+    //     //node.visible = false;
+    // }
+
+}

+ 13 - 0
public/defaults/worlds/concert/index.vwf.config.json

@@ -0,0 +1,13 @@
+{
+  "info":{
+    "title": "Concert"
+  },
+  "model": {
+    "/drivers/model/two": null,
+    "/drivers/model/tone": null
+  },
+  "view": {
+    "/drivers/view/two": null,
+    "/drivers/view/tone": null
+  }
+}

+ 230 - 0
public/defaults/worlds/concert/index.vwf.json

@@ -0,0 +1,230 @@
+{
+  "extends": "proxy/two/scene.vwf",
+  "properties": {
+    "part1": "notstarted",
+    "part2": "notstarted",
+    "part3": "notstarted",
+    "part4": "notstarted"
+  },
+  "methods":{
+    "initialize": {},
+    "watchPerformance": {},
+    "createBody":{},
+    "startPart3":{},
+    "startPart4":{},
+    "playBodyMotion":{},
+    "setStartTime":{}
+  },
+  "children":{
+    "timeText":{
+      "extends": "proxy/two/text.vwf",
+      "properties": {
+        "text": "",
+        "translation": [50,25],
+        "fill": "grey",
+        "opacity": 0.4,
+        "size": 22
+      }
+    },
+    "back":{
+      "extends": "proxy/two/group.vwf",
+      "properties":{
+        "x": 700,
+        "y": 400
+      },
+      "children":{
+        "back1":{
+          "extends": "proxy/two/group.vwf",
+          "properties":{
+            "displayName": "back1"
+          },
+          "children":{
+            "rectangle":{
+              "extends": "proxy/two/rectangle.vwf",
+              "properties": {
+                "width": 1280,
+                "height": 720,
+                "fill": "/defaults/assets/concert/bowing.mp4"
+              }
+            }
+          }
+        },
+        "back2":{
+          "extends": "proxy/two/group.vwf",
+          "properties":{
+            "displayName": "back2"
+          },
+          "children":{
+            "logo_text":{
+              "extends": "proxy/two/text.vwf",
+              "properties": {
+                "size": 70,
+                "fill": "white",
+                "text": "THIS IS NOT A CONCERT",
+                "translation": [-150, -100]
+              }
+            }
+
+          }
+        },
+        "back3":{
+          "extends": "proxy/two/group.vwf",
+          "properties":{
+            "displayName": "back3",
+            "opacity": 0
+          },
+          "children":{
+            "rectangle":{
+              "extends": "proxy/two/rectangle.vwf",
+              "properties": {
+                "stroke": "white",
+                "linewidth": 0,
+                "width": 1280,
+                "height": 720,
+                "fill": "/defaults/assets/concert/playing.mp4",
+                "bodyTrack": false,
+                "bodyNode": null
+              }
+            }
+          }
+        }
+      }
+    },
+    "filter1":{
+      "extends":"proxy/two/group.vwf",
+      "properties":{
+        "x":700,
+        "y":300,
+        "drag": false
+      },
+      "children":
+      { 
+        "el1":{
+        "extends": "proxy/two/ellipse.vwf",
+        "properties": {
+          "width": 400,
+          "height": 400,
+          "fill": "#ccc",
+          "opacity":0.2,
+          "drag": true,
+          "x": 64,
+          "y": -20
+        }
+      },
+      "el2":{
+        "extends": "proxy/two/rectangle.vwf",
+        "properties": {
+          "width": 350,
+          "height": 350,
+          "x": -400,
+          "y": -20,
+          "fill": "#ccc",
+          "opacity":0.2,
+          "drag": true,
+          "stepping": true,
+          "stepTime": 0.01
+        }
+      }
+    }
+    },
+    "filter2":{
+      "extends":"proxy/two/group.vwf",
+      "properties":{
+        "x":700,
+        "y":300,
+        "drag": false
+      },
+      "children":
+      { 
+      "el2":{
+        "extends": "proxy/two/rectangle.vwf",
+        "properties": {
+          "width": 880,
+          "height": 150,
+          "x": -145,
+          "y": 0,
+          "fill": "white",
+          "opacity":0.2,
+          "drag": true
+        }
+      }
+    }
+    },
+    "filter3":{
+      "extends":"proxy/two/group.vwf",
+      "properties":{
+        "x":700,
+        "y":300,
+        "drag": false
+      },
+      "children":
+      { 
+        "el1":{
+        "extends": "proxy/two/ellipse.vwf",
+        "properties": {
+          "width": 350,
+          "height": 350,
+          "fill": "white",
+          "opacity":0,
+          "drag": false,
+          "x": -205,
+          "y": -15,
+          "stroke": "white",
+          "linewidth": 1
+        }
+      },
+      "el2":{
+        "extends": "proxy/two/rectangle.vwf",
+        "properties": {
+          "width": 350,
+          "height": 600,
+          "x": 136,
+          "y": 370,
+          "fill": "white",
+          "opacity":0,
+          "drag": false,
+          "stroke": "white",
+          "linewidth": 1
+        }
+      }
+    }
+    },
+    "toneTransport":{
+      "extends": "proxy/tonejs/transport.vwf",
+      "properties": {
+        "bpm": 100,
+        "loopStart": 0,
+        "loopEnd":  90,
+        "duration": 90,
+        "loop": true
+      }
+    },
+    "tuning":{
+      "extends": "proxy/tonejs/player.vwf",
+      "properties": {
+        "url": "/defaults/assets/concert/tuning.mp3",
+        "volume": -15,
+        "autostart": false
+      }
+    },
+    "body": {
+      "extends": "proxy/two/group.vwf",
+      "properties": {
+      "drag": false,
+        "x": 239,
+        "y": -57,
+        "motionData": null
+      },
+      "methods":{
+          "applyBodyMotion":{
+              "parameters":["data"]
+          },
+          "getJointAtTime":{},
+          "getJointsAtTime":{}
+      }
+    }
+  },
+  "scripts":{
+    "source": "concert.js"
+  }
+}

+ 14 - 0
public/defaults/worlds/concert/info.json

@@ -0,0 +1,14 @@
+{
+    "info": {
+        "en": {
+            "title": "THIS IS NOT A CONCERT",
+            "imgUrl": "/defaults/worlds/concert/webimg.jpg",
+            "text": "BROWSER 2021 | A Festival of Web-based Music | June 11th-13th"
+        },
+        "ru": {
+            "title": "ЭТО НЕ КОНЦЕРТ",
+            "imgUrl": "/defaults/worlds/concert/webimg.jpg",
+            "text": "BROWSER 2021 | A Festival of Web-based Music | June 11th-13th"
+        }
+    }
+}

二進制
public/defaults/worlds/concert/webimg.jpg


+ 15 - 0
public/defaults/worlds/pts/appui.js

@@ -0,0 +1,15 @@
+//-----App ui-----
+
+function createApp() {
+    
+        let self = this
+    
+        return {
+            $cell: true,
+            $type: "div",
+            class: "propGrid max-width mdc-layout-grid mdc-layout-grid--align-left",
+            $components: []
+        }
+
+    }
+    

+ 3 - 0
public/defaults/worlds/pts/canvas.js

@@ -0,0 +1,3 @@
+this.initialize = function () {
+
+}

+ 12 - 0
public/defaults/worlds/pts/index.vwf.config.json

@@ -0,0 +1,12 @@
+{
+  "info":{
+    "title": "2D App"
+  },
+  "model": {
+    "/drivers/model/pts": null
+  },
+  "view": {
+    "/drivers/view/pts": null,
+    "/drivers/view/editor": null
+  }
+}

+ 11 - 0
public/defaults/worlds/pts/index.vwf.json

@@ -0,0 +1,11 @@
+{
+  "extends": "proxy/pts/scene.vwf",
+  "properties": {
+  },
+  "methods":{
+    "initialize": {}
+  },
+  "scripts":{
+    "source": "canvas.js"
+  }
+}

+ 14 - 0
public/defaults/worlds/pts/info.json

@@ -0,0 +1,14 @@
+{
+    "info": {
+        "en": {
+            "title": "2D Canvas PTS.js app",
+            "imgUrl": "/defaults/worlds/pts/webimg.jpg",
+            "text": "Example application of VWF & PTS.js"
+        },
+        "ru": {
+            "title": "2D Canvas PTS.js app",
+            "imgUrl": "/defaults/worlds/pts/webimg.jpg",
+            "text": "Example application of VWF & PTS.js"
+        }
+    }
+}

二進制
public/defaults/worlds/pts/webimg.jpg


+ 5 - 5
public/defaults/worlds/rubik/robot.js

@@ -57,26 +57,26 @@ this.rotateFace = function(faceID){
         'd': {
             'robot': 'left',
             'motor': 'C',
-            'dutyCycle': 100,
+            'dutyCycle': 80,
             'direction': -1*direction
         },
         'b': {
             'robot': 'back',
             'motor': 'A',
-            'dutyCycle': 70,
+            'dutyCycle': 60,
             'direction': direction
         },
         'r': {
             'robot': 'right',
             'motor': 'A',
-            'dutyCycle': 70,
+            'dutyCycle': 60,
             'direction': direction
         },
         'u': {
             'robot': 'back',
             'motor': 'C',
-            'dutyCycle': 100,
-            'direction': direction
+            'dutyCycle': 60,
+            'direction': -1*direction
         },
         'f': {
             'robot': 'front',

+ 2 - 2
public/defaults/worlds/tone/generate.js

@@ -1,5 +1,5 @@
 this.clickEventMethod = function() {
-   this.synth.triggerAttackRelease(['C4'], ['8n'], 0.3);
+   this.synth.triggerAttackRelease(['C4'], ['8n'], null, 0.3);
 }
 
 this.doButtonTriggerdownAction = function(buttonID){
@@ -43,7 +43,7 @@ this.onGlobalBeat = function (obj) {
     //     { beat: 15, msg: "C0" }];
     drumSeq.forEach(el => {
         if (el.beat / rate == obj.beat) {
-            this.synth.triggerAttackRelease([el.msg], ['16n']);
+            this.synth.triggerAttackRelease([el.msg], ['16n'], null);
         }
     })
 

+ 2 - 2
public/defaults/worlds/tone/info.json

@@ -6,9 +6,9 @@
             "text": "Example app with Tone JS"
         },
         "ru":{
-            "title": "Music App",
+            "title": "Музыкальная программа",
             "imgUrl": "/defaults/worlds/tone/webimg.jpg",
-            "text": "Example app with Tone JS"
+            "text": "Пример программы с Tone JS"
         }
        
     }

+ 1 - 1
public/defaults/worlds/tone/instrument.js

@@ -1,5 +1,5 @@
 this.clickEventMethod = function() {
-   this.synth.triggerAttackRelease(['C4'], ['8n'], 0.3);
+   this.synth.triggerAttackRelease(['C4'], ['8n'], null, 0.3);
 }
 
 this.doButtonTriggerdownAction = function(buttonID){

+ 2 - 2
public/defaults/worlds/tone/transportLine.js

@@ -24,7 +24,7 @@ this.fromhitstartEventMethod = function(value){
     if(notes.length == 1){
         let note = notes[0];
         //synth.triggerAttack(note.freq, note.velocity);
-        synth.triggerAttackRelease([note.freq], [note.duration], note.velocity);
+        synth.triggerAttackRelease([note.freq], [note.duration], null, note.velocity);
     } else if (notes.length > 0){
         let chord = notes.map(el=>{
             return el.freq
@@ -34,7 +34,7 @@ this.fromhitstartEventMethod = function(value){
         })
 
          let velocity = notes[0].velocity;
-         synth.triggerAttackRelease(chord, durations, velocity);
+         synth.triggerAttackRelease(chord, durations, null, velocity);
         // synth.triggerAttack(chord, velocity); //, durations
     }
 

+ 185 - 0
public/defaults/worlds/two/2d.js

@@ -0,0 +1,185 @@
+this.initialize = function () {
+
+    if (Object.getPrototypeOf(this).id.includes('scene.vwf')) {
+        console.log("Initialize of Scene...", this.id);
+
+        this.globalTransport.init();
+
+        this.back.back2.mask = "filter2";
+        this.back.back1.mask = "filter1";
+        //this.back.back1.rectangle.fill = "https://localhost:3007/defaults/assets/test/test.mp4";
+
+        //this.back.back1.rectangle.playVideo();
+
+        this.back.back1.rectangle.setVideoState(false, null, 0);
+
+
+
+
+        this.synth.sync();
+
+        this.future(1).setStartTime();
+        this.tuning.syncStart();
+
+
+        //this.synth.scheduleRepeat();
+        this.synth.triggerAttackRelease(["A3"], "32n", "4n", 0.8);
+        this.synth.triggerAttackRelease(["E3"], "32n", "8n", 0.8);
+
+        this.toneTransport.setTransportState(false, null, 0);
+
+
+        this.toneGUI.toneText.step();
+
+    } else {
+        console.log("Initialize proto Scene..", this.id);
+    }
+
+}
+
+this.resize = function () {
+
+}
+
+this.setStartTime = function () {
+
+    this.tuning.startTime = this.toneTransport.position;
+}
+
+this.toneGUI.toneText.do = function () {
+    let scene = this.getScene();
+
+    if (scene && scene.toneTransport) {
+        this.text = scene.toneTransport.position;
+    }
+
+
+}
+
+this.toneGUI.tonePlay.mousedownEvent = function () {
+    let scene = this.getScene();
+    if (scene.toneTransport) {
+
+
+        //scene.tuning.startTime = scene.toneTransport.position;
+        //scene.future(1).setStartTime();
+        //scene.tuning.startTime = scene.toneTransport.position;
+        scene.toneTransport.syncTransportState();
+        scene.toneTransport.toggleTransport();
+
+    }
+
+
+    // let scene = this.getScene();
+    // if(!scene.toneTransport.state || scene.toneTransport.state  == "stopped"){
+    //     scene.toneTransport.start();
+    //     scene.toneTransport.state = "started";
+    //     console.log("START Transport")
+    // } else if(scene.toneTransport.state == "started"){
+    //     scene.toneTransport.stop();
+    //     scene.toneTransport.state = "stopped";
+    //     console.log("STOP Transport")
+    // } else if(scene.toneTransport.state == "paused"){
+    //     scene.toneTransport.state = "paused";
+    // }
+
+
+
+}
+
+this.audio.mousedownEvent = function () {
+
+    this.parent.back.back1.rectangle.unmute();
+}
+
+this.play.mousedownEvent = function () {
+
+    //this.parent.back.back1.rectangle.setVideoState(true, 0, 0);
+    //this.parent.back.back1.rectangle.setVideoState(this.isPlaying, this.startOffset, this.pausedTime);
+    this.parent.back.back1.rectangle.syncVideoState();
+    this.parent.back.back1.rectangle.playVideo();
+}
+
+this.ellipse.clickEvent = function () {
+
+    this.fill = this.getRandomColor();
+    console.log('CLICK ', this.id);
+}
+
+this.ellipse.mousedownEvent = function () {
+
+    this.fill = this.getRandomColor();
+    console.log('Mouse Down ', this.id);
+}
+
+this.ellipse.mouseupEvent = function () {
+
+    this.fill = this.getRandomColor();
+    console.log('Mouse Up ', this.id);
+}
+
+this.ellipse.overstartEvent = function (avatarID) {
+    this.fill = "#e3dd24"
+}
+
+this.ellipse.overendEvent = function (avatarID) {
+    this.fill = "white"
+}
+
+
+this.filter2.el2.overstartEvent = function (avatarID) {
+    let nodeName = this.parent.maskedNode;
+    if (nodeName) {
+        let node = this.getScene().findNode(nodeName);
+        this.viewTroughFilter(node.id, true);
+        //node.visible = true;
+    }
+}
+
+
+this.filter2.el2.overendEvent = function (avatarID) {
+    let nodeName = this.parent.maskedNode;
+    if (nodeName) {
+        let node = this.getScene().findNode(nodeName);
+        this.viewTroughFilter(node.id, false);
+        //node.visible = false;
+    }
+}
+
+this.filter1.el1.overstartEvent = function (avatarID) {
+    let nodeName = this.parent.maskedNode;
+    if (nodeName) {
+        let node = this.getScene().findNode(nodeName);
+        //this.viewTroughFilter(node.id, true);
+        //node.visible = true;
+    }
+}
+
+
+this.filter1.el1.overendEvent = function (avatarID) {
+    let nodeName = this.parent.maskedNode;
+    if (nodeName) {
+        let node = this.getScene().findNode(nodeName);
+        //this.viewTroughFilter(node.id, false);
+        //node.visible = false;
+    }
+}
+
+this.filter1.el2.overstartEvent = function (avatarID) {
+    let nodeName = this.parent.maskedNode;
+    if (nodeName) {
+        let node = this.getScene().findNode(nodeName);
+        //this.viewTroughFilter(node.id, true);
+        //node.visible = true;
+    }
+}
+
+
+this.filter1.el2.overendEvent = function (avatarID) {
+    let nodeName = this.parent.maskedNode;
+    if (nodeName) {
+        let node = this.getScene().findNode(nodeName);
+        //this.viewTroughFilter(node.id, false);
+        //node.visible = false;
+    }
+}

+ 15 - 0
public/defaults/worlds/two/appui.js

@@ -0,0 +1,15 @@
+//-----App ui-----
+
+function createApp() {
+    
+        let self = this
+    
+        return {
+            $cell: true,
+            $type: "div",
+            class: "propGrid max-width mdc-layout-grid mdc-layout-grid--align-left",
+            $components: []
+        }
+
+    }
+    

+ 14 - 0
public/defaults/worlds/two/index.vwf.config.json

@@ -0,0 +1,14 @@
+{
+  "info":{
+    "title": "2D App"
+  },
+  "model": {
+    "/drivers/model/two": null,
+    "/drivers/model/tone": null
+  },
+  "view": {
+    "/drivers/view/two": null,
+    "/drivers/view/tone": null,
+    "/drivers/view/editor": null
+  }
+}

+ 210 - 0
public/defaults/worlds/two/index.vwf.json

@@ -0,0 +1,210 @@
+{
+  "extends": "proxy/two/scene.vwf",
+  "properties": {
+  },
+  "methods":{
+    "initialize": {},
+    "setStartTime":{}
+  },
+  "children":{
+    "play":{
+      "extends": "proxy/two/rectangle.vwf",
+      "properties": {
+        "width": 50,
+        "height": 50,
+        "fill": "#aaa",
+        "x": 1000,
+        "y": 200
+      }
+    },
+    "audio":{
+      "extends": "proxy/two/ellipse.vwf",
+      "properties": {
+        "width": 50,
+        "height": 50,
+        "fill": "#aaa",
+        "x": 1100,
+        "y": 200
+      }
+    },
+    
+    "logo":{
+      "extends": "proxy/two/text.vwf",
+      "properties": {
+        "text": "PROTOTYPE",
+        "translation": [150,110],
+        "fill": "#ddd",
+        "size": 28
+      }
+    },
+    "back":{
+      "extends": "proxy/two/group.vwf",
+      "properties":{
+        "x": 500,
+        "y": 400
+      },
+      "children":{
+        "back1":{
+          "extends": "proxy/two/group.vwf",
+          "properties":{
+            "displayName": "back1"
+          },
+          "children":{
+            "rectangle":{
+              "extends": "proxy/two/rectangle.vwf",
+              "properties": {
+                "width": 800,
+                "height": 500,
+                "fill": "/defaults/assets/test/test.mp4"
+              }
+            }
+          }
+        },
+        "back2":{
+          "extends": "proxy/two/group.vwf",
+          "properties":{
+            "displayName": "back2"
+          },
+          "children":{
+            "rectangle2":{
+              "extends": "proxy/two/rectangle.vwf",
+              "properties": {
+                "width": 600,
+                "height": 40,
+                "fill": "#ddd",
+                "linewidth": 3
+              }
+            }
+          }
+        }
+ 
+      
+      }
+    },
+    "filter1":{
+      "extends":"proxy/two/group.vwf",
+      "properties":{
+        "x":350,
+        "y":800,
+        "drag": false
+      },
+      "children":
+      { 
+        "el1":{
+        "extends": "proxy/two/ellipse.vwf",
+        "properties": {
+          "width": 200,
+          "height": 200,
+          "fill": "#ccc",
+          "opacity":0.2,
+          "drag": true
+        }
+      },
+      "el2":{
+        "extends": "proxy/two/rectangle.vwf",
+        "properties": {
+          "width": 200,
+          "height": 200,
+          "x": 250,
+          "fill": "#ddd",
+          "opacity":0.5,
+          "drag": true
+        }
+      }
+    }
+    },
+    "filter2":{
+      "extends":"proxy/two/group.vwf",
+      "properties":{
+        "x":350,
+        "y":800,
+        "drag": false
+      },
+      "children":
+      { 
+      "el2":{
+        "extends": "proxy/two/rectangle.vwf",
+        "properties": {
+          "width": 200,
+          "height": 200,
+          "x": 250,
+          "fill": "white",
+          "opacity":0.3,
+          "drag": true
+        }
+      }
+    }
+    },
+    "ellipse":{
+      "extends": "proxy/two/ellipse.vwf",
+      "properties": {
+        "width": 100,
+        "height": 100,
+        "fill": "#ccc",
+        "x": 140,
+        "y": 800,
+        "drag": true
+      }
+    },
+    "globalTransport":{
+      "extends": "proxy/two/objects/transport.vwf",
+      "properties": {
+        "x": 50,
+        "y": 170,
+        "drag": false
+      }
+    },
+    "toneGUI":{
+      "extends":"proxy/two/group.vwf",
+      "properties": {
+        "x": 1100,
+        "y": 400
+      },
+      "children":{
+        "tonePlay":{
+          "extends": "proxy/two/ellipse.vwf",
+          "properties": {
+            "width": 50,
+            "height": 50,
+            "fill": "#eee"
+          }
+        },
+        "toneText":{
+          "extends": "proxy/two/text.vwf",
+          "properties": {
+            "size": 28,
+            "fill": "#aaa",
+            "text": "transport",
+            "stepping": true,
+            "stepTime": 0.5,
+            "translation": [0,-100]
+          }
+        }
+      }
+    },
+    "synth":{
+      "extends": "proxy/tonejs/synth.vwf",
+      "properties": {}
+    },
+    "toneTransport":{
+      "extends": "proxy/tonejs/transport.vwf",
+      "properties": {
+        "bpm": 120,
+        "loopStart": "0",
+        "loopEnd":  "60",
+        "duration": "60",
+        "loop": true
+      }
+    },
+    "tuning":{
+      "extends": "proxy/tonejs/player.vwf",
+      "properties": {
+        "url": "/defaults/assets/test/test.mp3",
+        "autostart": false
+      }
+    }
+  },
+  "scripts":{
+    "source": "2d.js"
+  }
+}

+ 14 - 0
public/defaults/worlds/two/info.json

@@ -0,0 +1,14 @@
+{
+    "info": {
+        "en": {
+            "title": "2D Canvas app",
+            "imgUrl": "/defaults/worlds/two/webimg.jpg",
+            "text": "Example application showing Virtual World Framework & TWO.JS framework integration"
+        },
+        "ru": {
+            "title": "2D Холст",
+            "imgUrl": "/defaults/worlds/two/webimg.jpg",
+            "text": "Пример приложения Virtual World Framework & TWO.JS"
+        }
+    }
+}

二進制
public/defaults/worlds/two/webimg.jpg


文件差異過大導致無法顯示
+ 284 - 87
public/drivers/model/aframe/aframe-master.js


文件差異過大導致無法顯示
+ 1 - 0
public/drivers/model/aframe/aframe-master.js.map


文件差異過大導致無法顯示
+ 0 - 0
public/drivers/model/aframe/aframe-master.min.js


文件差異過大導致無法顯示
+ 0 - 0
public/drivers/model/aframe/aframe-master.min.js.map


+ 380 - 0
public/drivers/model/pts.js

@@ -0,0 +1,380 @@
+/*
+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 & PtsJS model driver
+
+import { Fabric } from '/core/vwf/fabric.js';
+
+class PTSModel extends Fabric {
+
+    constructor(module) {
+
+        console.log("PTSModel constructor");
+        super(module, "Model");
+    }
+
+    factory() {
+        let _self_ = this;
+
+        return this.load( this.module, 
+            {
+
+                // == Module Definition ====================================================================
+        
+                // -- pipeline -----------------------------------------------------------------------------
+        
+                // pipeline: [ log ], // vwf <=> log <=> scene
+        
+                // -- initialize ---------------------------------------------------------------------------
+        
+                initialize: function() {
+                    
+                    var self = this;
+        
+                   this.state = {
+                        nodes: {},
+                        scenes: {},
+                        prototypes: {},
+                        createLocalNode: function (nodeID, childID, childExtendsID, childImplementsIDs,
+                            childSource, childType, childIndex, childName, callback) {
+                            return {
+                                "parentID": nodeID,
+                                "ID": childID,
+                                "extendsID": childExtendsID,
+                                "implementsIDs": childImplementsIDs,
+                                "source": childSource,
+                                "type": childType,
+                                "name": childName,
+                                "prototypes": undefined
+                            };
+                        },
+                        isNodeComponent: function (prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] === "proxy/pts/node.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                        isClass: function (prototypes, classID) {
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length; i++) {
+                                    if (prototypes[i] === classID) {
+                                        //console.info( "prototypes[ i ]: " + prototypes[ i ] );
+                                        return true;
+                                    }
+                                }
+                            }
+                            return false;
+                        },
+                        isPTDefinition: function (prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/pts/pt.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                        
+                        createObject: function (node, config) {
+
+                            var protos = node.prototypes;
+                            var obj = undefined;
+    
+                            if (this.isClass(protos, "proxy/pts/scene.vwf")) {
+
+                                let el = document.createElement("space");
+                                el.setAttribute("id", "space");
+                                el.style.height = "600px";
+                                el.style.width = "800px";
+                                document.querySelector("body").appendChild(el);
+                                Pts.namespace( window );
+                                obj = new CanvasSpace("#space").setup(
+                                    { bgcolor: "#99eeff", retina: true, resize: false });
+                               
+                                obj.nodeName = "space";  
+                                obj.nodeID = node.ID; 
+                                this.scenes[node.ID] = node;
+                                node.form = obj.getForm();
+                                
+                            }
+
+                            // if (this.isClass(protos, "proxy/pts/player.vwf")) {
+                            //     toneObj = new Tone.PolySynth(Tone.MembraneSynth);
+
+                            // }
+
+                            if(this.isPTDefinition(protos)) {
+                                obj = new Pt(2);
+                                obj.nodeName = "pt";  
+                                obj.nodeID = node.ID; 
+                                
+                            }
+
+                            return obj
+                            },
+                            addNodeToHierarchy: function (node) {
+
+                                if (node.obj) {
+                                    
+                                    if (node.obj.nodeName !== "space") {
+                                        node.scene = this.scenes[self.kernel.application()];
+                                    }
+        
+                                }
+        
+                            }
+                    };
+        
+                    this.state.kernel = this.kernel.kernel.kernel;
+        
+                    //this.Tone = Tone;
+                    //this.state.kernel = this.kernel.kernel.kernel;
+                    
+                },
+                // == Model API ============================================================================
+        
+                // -- creatingNode -------------------------------------------------------------------------
+        
+                creatingNode: function( nodeID, childID, childExtendsID, childImplementsIDs,
+                    childSource, childType, childIndex, childName, callback /* ( ready ) */ ) {
+        
+                    // If the parent nodeID is 0, this node is attached directly to the root and is therefore either 
+                    // the scene or a prototype.  In either of those cases, save the uri of the new node
+                    var childURI = (nodeID === 0 ? childIndex : undefined);
+                    var appID = this.kernel.application();
+        
+                    // If the node being created is a prototype, construct it and add it to the array of prototypes,
+                    // and then return
+                    var prototypeID = _self_.utility.ifPrototypeGetId(appID, this.state.prototypes, nodeID, childID);
+                    if (prototypeID !== undefined) {
+        
+                        this.state.prototypes[prototypeID] = {
+                            parentID: nodeID,
+                            ID: childID,
+                            extendsID: childExtendsID,
+                            implementsID: childImplementsIDs,
+                            source: childSource,
+                            type: childType,
+                            name: childName
+                        };
+                        return;
+                    }
+        
+                    var protos = _self_.getPrototypes(this.kernel, childExtendsID);
+                    //var kernel = this.kernel.kernel.kernel;
+                    var node;
+        
+                    if (this.state.isNodeComponent(protos)) {
+        
+                        // Create the local copy of the node properties
+                        if (this.state.nodes[childID] === undefined) {
+                            this.state.nodes[childID] = this.state.createLocalNode(nodeID, childID, childExtendsID, childImplementsIDs,
+                                childSource, childType, childIndex, childName, callback);
+                        }
+        
+                        node = this.state.nodes[childID];
+                        node.prototypes = protos;
+        
+                        node.obj = this.state.createObject(node);
+                        this.state.addNodeToHierarchy(node);
+
+                        //let aframeDriver = vwf.views["/drivers/view/aframe"];
+                      
+                        
+                        //notifyDriverOfPrototypeAndBehaviorProps();
+                    }
+                },
+        
+                 // -- initializingNode -------------------------------------------------------------------------
+        
+                //   initializingNode: function( nodeID, childID, childExtendsID, childImplementsIDs,
+                //     childSource, childType, childIndex, childName ) {
+        
+                // },
+        
+                // -- deletingNode -------------------------------------------------------------------------
+        
+                deletingNode: function( nodeID ) {
+
+                    if (nodeID) {
+                        var childNode = this.state.nodes[nodeID];
+                        if (!childNode) return;
+    
+    
+                        if (childNode !== undefined) {
+    
+                            if (childNode.children) {
+    
+                                for (var i = 0; i < childNode.children.length; i++) {
+                                    this.deletingNode(childNode.children[i]);
+                                }
+                            }
+    
+                            if (childNode.obj !== undefined) {
+                                // removes and destroys object
+
+                                let space = childNode.scene.obj;
+                                let player = Object.values(space.players).filter(el=> el.myID == nodeID)[0];
+                                space.remove(player.animateID);
+                                childNode.obj = undefined;
+                            }
+    
+                            delete this.state.nodes[nodeID];
+                        }
+    
+                    }
+
+
+                },
+        
+                 // -- initializingProperty -----------------------------------------------------------------
+        
+                initializingProperty: function( nodeID, propertyName, propertyValue ) {
+        
+                     var value = undefined;
+                    var node = this.state.nodes[nodeID];
+                    if (node !== undefined) {
+                        value = this.settingProperty(nodeID, propertyName, propertyValue);
+                    }
+                    return value;
+                
+            },
+        
+                // -- creatingProperty ---------------------------------------------------------------------
+        
+                creatingProperty: function (nodeID, propertyName, propertyValue) {
+                    return this.initializingProperty(nodeID, propertyName, propertyValue);
+                },
+        
+        
+                // -- settingProperty ----------------------------------------------------------------------
+        
+                settingProperty: function( nodeID, propertyName, propertyValue ) {
+        
+                    let self = this;
+                    let node = this.state.nodes[nodeID];
+                    var value = undefined;
+
+                    if (node && node.obj && _self_.utility.validObject(propertyValue)) {
+
+                        let object = node.obj;
+
+                        // if (self.state.isComponentNodeDefinition(node.prototypes)) {
+
+
+                        //     value = propertyValue;
+                        //     switch (propertyName) {
+
+                        //         default:
+                        //             value = undefined;
+                        //             break;
+                        //     }
+
+                        // }
+
+
+                        if (value === undefined && self.state.isPTDefinition(node.prototypes)) {
+                            
+                            value = propertyValue;
+
+                            switch (propertyName) {
+
+
+                                case "x":
+
+                                    object.x = propertyValue
+
+                                    break;
+
+                                case "y":
+                                        object.y = propertyValue
+
+    
+                                        break;
+
+                                default:
+                                    value = undefined;
+                                    break;
+                            }
+                        }
+                    }
+        
+                     return value;
+        
+                },
+        
+                // -- gettingProperty ----------------------------------------------------------------------
+        
+                gettingProperty: function( nodeID, propertyName, propertyValue ) {
+        
+                    let self = this;
+                    let node = this.state.nodes[nodeID];
+                    let value = undefined;
+                    if (node && node.obj) {
+
+                        let object = node.obj;
+
+                        // if (self.state.isComponentNodeDefinition(node.prototypes)) {
+                        //     switch (propertyName) {
+                        //     }
+                        // }
+
+                        if (value === undefined && self.state.isPTDefinition(node.prototypes)) {
+
+                            switch (propertyName) {
+
+                                case "x":
+                                    value = object.x
+                                    break;
+
+                                case "y":
+                                        value = object.y
+                                        break;
+                            }
+                        }
+
+        
+                    }
+
+                     if ( value !== undefined ) {
+                        propertyValue = value;
+                    }
+        
+                    return value;
+        
+                     
+                }
+
+
+
+
+        
+            } );
+
+        }
+
+
+    getPrototypes(kernel, extendsID) {
+        var prototypes = [];
+        var id = extendsID;
+    
+        while (id !== undefined) {
+            prototypes.push(id);
+            id = kernel.prototype(id);
+        }
+        return prototypes;
+    }
+    
+
+    }
+
+    export {
+        PTSModel as default
+      }
+    

+ 325 - 77
public/drivers/model/tone.js

@@ -19,22 +19,22 @@ class ToneModel extends Fabric {
     factory() {
         let _self_ = this;
 
-        return this.load( this.module, 
+        return this.load(this.module,
             {
 
                 // == Module Definition ====================================================================
-        
+
                 // -- pipeline -----------------------------------------------------------------------------
-        
+
                 // pipeline: [ log ], // vwf <=> log <=> scene
-        
+
                 // -- initialize ---------------------------------------------------------------------------
-        
-                initialize: function() {
-                    
+
+                initialize: function () {
+
                     var self = this;
-        
-                   this.state = {
+
+                    this.state = {
                         nodes: {},
                         scenes: {},
                         prototypes: {},
@@ -71,6 +71,24 @@ class ToneModel extends Fabric {
                             }
                             return false;
                         },
+                        isTransportDefinition: function (prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/tonejs/transport.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                        isPlayerDefinition: function (prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/tonejs/player.vwf");
+                                }
+                            }
+                            return found;
+                        },
                         isSynthDefinition: function (prototypes) {
                             var found = false;
                             if (prototypes) {
@@ -102,7 +120,17 @@ class ToneModel extends Fabric {
 
                             var protos = node.prototypes;
                             var toneObj = undefined;
-    
+
+                            if (this.isToneClass(protos, "proxy/tonejs/transport.vwf")) {
+                                // global transport for now
+                                toneObj = Tone.Transport;
+                            }
+
+                            if (this.isToneClass(protos, "proxy/tonejs/player.vwf")) {
+                                toneObj = new Tone.Player(); //GrainPlayer
+                                toneObj.autostart = false;
+                            }
+
                             if (this.isToneClass(protos, "proxy/tonejs/synth.vwf")) {
                                 toneObj = new Tone.PolySynth(Tone.synth);
                             }
@@ -126,32 +154,32 @@ class ToneModel extends Fabric {
 
 
                             return toneObj
-                            }
+                        }
                     };
-        
+
                     this.state.kernel = this.kernel.kernel.kernel;
-        
+
                     //this.Tone = Tone;
                     //this.state.kernel = this.kernel.kernel.kernel;
-                    
+
                 },
                 // == Model API ============================================================================
-        
+
                 // -- creatingNode -------------------------------------------------------------------------
-        
-                creatingNode: function( nodeID, childID, childExtendsID, childImplementsIDs,
-                    childSource, childType, childIndex, childName, callback /* ( ready ) */ ) {
-        
+
+                creatingNode: function (nodeID, childID, childExtendsID, childImplementsIDs,
+                    childSource, childType, childIndex, childName, callback /* ( ready ) */) {
+
                     // If the parent nodeID is 0, this node is attached directly to the root and is therefore either 
                     // the scene or a prototype.  In either of those cases, save the uri of the new node
                     var childURI = (nodeID === 0 ? childIndex : undefined);
                     var appID = this.kernel.application();
-        
+
                     // If the node being created is a prototype, construct it and add it to the array of prototypes,
                     // and then return
                     var prototypeID = _self_.utility.ifPrototypeGetId(appID, this.state.prototypes, nodeID, childID);
                     if (prototypeID !== undefined) {
-        
+
                         this.state.prototypes[prototypeID] = {
                             parentID: nodeID,
                             ID: childID,
@@ -163,28 +191,28 @@ class ToneModel extends Fabric {
                         };
                         return;
                     }
-        
+
                     var protos = _self_.getPrototypes(this.kernel, childExtendsID);
                     //var kernel = this.kernel.kernel.kernel;
                     var node;
-        
+
                     if (this.state.isToneNodeComponent(protos)) {
-        
+
                         // Create the local copy of the node properties
                         if (this.state.nodes[childID] === undefined) {
                             this.state.nodes[childID] = this.state.createLocalNode(nodeID, childID, childExtendsID, childImplementsIDs,
                                 childSource, childType, childIndex, childName, callback);
                         }
-        
+
                         node = this.state.nodes[childID];
                         node.prototypes = protos;
-        
+
                         //this.state.createToneObject(node);
 
                         let aframeDriver = vwf.views["/drivers/view/aframe"];
-                        if(aframeDriver){
+                        if (aframeDriver) {
                             let parentNode = aframeDriver.state.nodes[nodeID];
-                            if(parentNode.aframeObj){
+                            if (parentNode.aframeObj) {
                                 let sceneEl = aframeDriver.state.nodes[nodeID].scene.sceneEl;
                                 if (!sceneEl.audioListener) {
                                     sceneEl.audioListener = new THREE.AudioListener();
@@ -193,56 +221,68 @@ class ToneModel extends Fabric {
                                     //     evt.detail.cameraEl.getObject3D('camera').add(sceneEl.audioListener);
                                     // });
                                 }
-                                node.sound = new THREE.PositionalAudio( sceneEl.audioListener );
+                                node.sound = new THREE.PositionalAudio(sceneEl.audioListener);
                                 Tone.setContext(node.sound.context);
                                 //node.sound.context.resume();
                                 node.toneObj = this.state.createToneObject(node);
-                                node.sound.setNodeSource(node.toneObj);
-                                parentNode.aframeObj.object3D.add(node.sound);
-                                
-                            } 
+
+                                if (!this.state.isTransportDefinition(node.prototypes)) {
+                                    node.sound.setNodeSource(node.toneObj);
+                                    parentNode.aframeObj.object3D.add(node.sound);
+                                }
+
+                            }
+                        } else {
+
+                            node.toneObj = this.state.createToneObject(node);
+
+                            if (!this.state.isTransportDefinition(node.prototypes)) {
+                                node.toneObj.toDestination();
+                            }
+
+                            //node.sound.setNodeSource(node.toneObj);
                         }
                         //addNodeToHierarchy(node);
                         //notifyDriverOfPrototypeAndBehaviorProps();
                     }
                 },
-        
-                 // -- initializingNode -------------------------------------------------------------------------
-        
+
+                // -- initializingNode -------------------------------------------------------------------------
+
                 //   initializingNode: function( nodeID, childID, childExtendsID, childImplementsIDs,
                 //     childSource, childType, childIndex, childName ) {
-        
+
                 // },
-        
+
                 // -- deletingNode -------------------------------------------------------------------------
-        
+
                 //deletingNode: function( nodeID ) {
                 //},
-        
-                 // -- initializingProperty -----------------------------------------------------------------
-        
-                initializingProperty: function( nodeID, propertyName, propertyValue ) {
-        
-                     var value = undefined;
+
+                // -- initializingProperty -----------------------------------------------------------------
+
+                initializingProperty: function (nodeID, propertyName, propertyValue) {
+
+                    var value = undefined;
                     var node = this.state.nodes[nodeID];
                     if (node !== undefined) {
                         value = this.settingProperty(nodeID, propertyName, propertyValue);
                     }
                     return value;
-                
-            },
-        
+
+                },
+
                 // -- creatingProperty ---------------------------------------------------------------------
-        
+
                 creatingProperty: function (nodeID, propertyName, propertyValue) {
                     return this.initializingProperty(nodeID, propertyName, propertyValue);
                 },
-        
-        
+
+
                 // -- settingProperty ----------------------------------------------------------------------
-        
-                settingProperty: function( nodeID, propertyName, propertyValue ) {
-        
+
+                settingProperty: function (nodeID, propertyName, propertyValue) {
+
                     let self = this;
                     let node = this.state.nodes[nodeID];
                     var value = undefined;
@@ -265,6 +305,132 @@ class ToneModel extends Fabric {
                         // }
 
 
+                        if (value === undefined && self.state.isTransportDefinition(node.prototypes)) {
+
+                            value = propertyValue;
+
+                            switch (propertyName) {
+
+
+                                case "bpm":
+
+                                    toneObject.bpm.value = propertyValue
+                                    toneObject.initbpm = propertyValue
+                                    break;
+
+                                // case "state":
+
+                                //     toneObject.state = propertyValue
+
+                                //     break;
+
+                                case "position":
+
+                                    toneObject.position = propertyValue
+
+                                    break;
+
+                                case "loop":
+                                    toneObject.loop = propertyValue
+                                    break;
+
+                                case "loopStart":
+
+                                    if (typeof propertyValue == "string") {
+                                        propertyValue = Tone.Time(propertyValue).toSeconds();
+                                    }
+
+                                    toneObject.loopStart = propertyValue
+                                    break;
+
+                                case "loopEnd":
+
+                                    if (typeof propertyValue == "string") {
+                                        propertyValue = Tone.Time(propertyValue).toSeconds();
+                                    }
+
+                                    toneObject.loopEnd = propertyValue
+                                    break;
+
+                                case "duration":
+                                    if (typeof propertyValue == "string") {
+                                        propertyValue = Tone.Time(propertyValue).toSeconds();
+                                    }
+
+                                    toneObject.duration = propertyValue
+                                    break;
+
+                                default:
+                                    value = undefined;
+                                    break;
+                            }
+                        }
+
+                        if (value === undefined && self.state.isPlayerDefinition(node.prototypes)) {
+
+                            value = propertyValue;
+
+                            switch (propertyName) {
+
+
+                                // case "url":
+
+                                //     toneObject.load(propertyValue)
+                                //     break;
+
+                                // case "state":
+
+                                //     toneObject.state = propertyValue
+
+                                //     break;
+
+                                case "startTime":
+
+                                    toneObject.startTime = propertyValue
+
+                                    break;
+
+                                case "volume":
+
+                                    toneObject.volume.value = propertyValue
+
+                                    break;
+
+
+                                case "mute":
+
+                                    toneObject.mute = propertyValue
+
+                                    break;
+
+                                case "autostart":
+
+                                    toneObject.autostart = propertyValue
+
+                                    break;
+
+                                case "loop":
+                                    toneObject.loop = propertyValue
+                                    break;
+
+                                case "loopStart":
+                                    toneObject.loopStart = propertyValue
+                                    break;
+
+                                case "loopEnd":
+                                    toneObject.loopEnd = propertyValue
+                                    break;
+
+                                // case "duration":
+                                //     toneObject.duration = propertyValue
+                                //     break;
+
+                                default:
+                                    value = undefined;
+                                    break;
+                            }
+                        }
+
                         if (value === undefined && self.state.isSynthDefinition(node.prototypes)) {
 
                             value = propertyValue;
@@ -274,7 +440,7 @@ class ToneModel extends Fabric {
 
                                 case "type":
 
-                                    toneObject.set({oscillator:{type: propertyValue}})
+                                    toneObject.set({ oscillator: { type: propertyValue } })
                                     //"sine"; "square"; "triangle"; "sawtooth";
 
                                     break;
@@ -284,16 +450,18 @@ class ToneModel extends Fabric {
                                     break;
                             }
                         }
+
+
                     }
-        
-                     return value;
-        
+
+                    return value;
+
                 },
-        
+
                 // -- gettingProperty ----------------------------------------------------------------------
-        
-                gettingProperty: function( nodeID, propertyName, propertyValue ) {
-        
+
+                gettingProperty: function (nodeID, propertyName, propertyValue) {
+
                     let self = this;
                     let node = this.state.nodes[nodeID];
                     let value = undefined;
@@ -306,6 +474,87 @@ class ToneModel extends Fabric {
                         //     }
                         // }
 
+
+                        if (value === undefined && self.state.isTransportDefinition(node.prototypes)) {
+
+                            switch (propertyName) {
+
+                                case "bpm":
+                                    value = toneObject.bpm.value
+                                    break;
+
+                                // case "state":
+                                //     value = toneObject.state
+                                //     break;
+
+                                case "position":
+                                    value = toneObject.position
+                                    break;
+
+                                case "loopStart":
+                                    value = toneObject.loopStart
+                                    break;
+
+                                case "loopEnd":
+                                    value = toneObject.loopEnd
+                                    break;
+
+
+                                case "duration":
+                                    value = toneObject.duration
+                                    break;
+
+                                case "loop":
+                                    value = toneObject.loop
+                                    break;
+
+                            }
+                        }
+
+                        if (value === undefined && self.state.isPlayerDefinition(node.prototypes)) {
+
+                            switch (propertyName) {
+
+                                case "autostart":
+                                    value = toneObject.autostart
+                                    break;
+
+                                // case "state":
+                                //     value = toneObject.state
+                                //     break;
+
+                                case "startTime":
+                                    value = toneObject.startTime
+                                    break;
+
+                                case "volume":
+                                    value = toneObject.volume.value
+                                    break;
+
+                                case "mute":
+                                    value = toneObject.mute
+                                    break;
+
+                                case "loopStart":
+                                    value = toneObject.loopStart
+                                    break;
+
+                                case "loopEnd":
+                                    value = toneObject.loopEnd
+                                    break;
+
+
+                                case "duration":
+                                    value = toneObject.buffer.duration
+                                    break;
+
+                                case "loop":
+                                    value = toneObject.loop
+                                    break;
+
+                            }
+                        }
+
                         if (value === undefined && self.state.isSynthDefinition(node.prototypes)) {
 
                             switch (propertyName) {
@@ -316,42 +565,41 @@ class ToneModel extends Fabric {
                             }
                         }
 
-        
+
                     }
 
-                     if ( value !== undefined ) {
+                    if (value !== undefined) {
                         propertyValue = value;
                     }
-        
+
                     return value;
-        
-                     
+
+
                 }
 
 
 
 
-        
-            } );
 
-        }
+            });
+
+    }
 
 
     getPrototypes(kernel, extendsID) {
         var prototypes = [];
         var id = extendsID;
-    
+
         while (id !== undefined) {
             prototypes.push(id);
             id = kernel.prototype(id);
         }
         return prototypes;
     }
-    
 
-    }
 
-    export {
-        ToneModel as default
-      }
-    
+}
+
+export {
+    ToneModel as default
+}

+ 1098 - 0
public/drivers/model/two.js

@@ -0,0 +1,1098 @@
+/*
+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 & TWO JS model driver
+
+import { Fabric } from '/core/vwf/fabric.js';
+
+class TwoModel extends Fabric {
+
+    constructor(module) {
+
+        console.log("TwoModel constructor");
+        super(module, "Model");
+    }
+
+    factory() {
+        let _self_ = this;
+
+        return this.load(this.module,
+            {
+
+                // == Module Definition ====================================================================
+
+                // -- pipeline -----------------------------------------------------------------------------
+
+                // pipeline: [ log ], // vwf <=> log <=> scene
+
+                // -- initialize ---------------------------------------------------------------------------
+
+                initialize: function () {
+
+                    var self = this;
+
+                    this.state = {
+                        nodes: {},
+                        scenes: {},
+                        prototypes: {},
+                        createLocalNode: function (nodeID, childID, childExtendsID, childImplementsIDs,
+                            childSource, childType, childIndex, childName, callback) {
+                            return {
+                                "parentID": nodeID,
+                                "ID": childID,
+                                "extendsID": childExtendsID,
+                                "implementsIDs": childImplementsIDs,
+                                "source": childSource,
+                                "type": childType,
+                                "name": childName,
+                                "prototypes": undefined
+                            };
+                        },
+                        isNodeComponent: function (prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] === "proxy/two/node.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                        isClass: function (prototypes, classID) {
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length; i++) {
+                                    if (prototypes[i] === classID) {
+                                        //console.info( "prototypes[ i ]: " + prototypes[ i ] );
+                                        return true;
+                                    }
+                                }
+                            }
+                            return false;
+                        },
+                        isTwoTextureDefinition: function (prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/two/texture.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                        isTwoRectangleDefinition: function (prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/two/rectangle.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                        isTwoCurveDefinition: function (prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/two/curve.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                        isTwoEllipseDefinition: function (prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/two/ellipse.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                        isTwoAnchorDefinition: function (prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/two/anchor.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                        isTwoGroupDefinition: function (prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/two/group.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                        isTwoPathDefinition: function (prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/two/path.vwf");
+                                }
+                            }
+                            return found;
+                        },
+                        isTwoTextDefinition: function (prototypes) {
+                            var found = false;
+                            if (prototypes) {
+                                for (var i = 0; i < prototypes.length && !found; i++) {
+                                    found = (prototypes[i] == "proxy/two/text.vwf");
+                                }
+                            }
+                            return found;
+                        },
+
+                        setFromValue: function (propertyValue) {
+
+                            var value = []; //goog.vec.Vec3.create();
+                            if (Array.isArray(propertyValue) || propertyValue instanceof Float32Array) {
+                                value = propertyValue;
+                            } else if (typeof propertyValue === 'string') {
+
+                                if (propertyValue.includes(',')) {
+                                    value = propertyValue.split(',').map(el => { return parseFloat(el) })
+                                }
+                            }
+
+                            return value
+                        },
+
+                        createObject: function (node, config) {
+
+                            var protos = node.prototypes;
+                            var obj = undefined;
+
+                            if (this.isClass(protos, "proxy/two/scene.vwf")) {
+
+                                // Make an instance of two and place it on the page.
+                                // let el = document.createElement("space");
+                                // document.querySelector("body").appendChild(el);
+
+                                // let params = { width: 800, height: 600 };
+                                // obj = new Two(params).appendTo(el);
+
+                                obj = new Two({
+                                    type: Two.Types.webgl, //webgl
+                                    fullscreen: true,
+                                    autostart: true
+                                }).appendTo(document.body);
+
+                                obj.renderer.domElement.style.position = 'absolute';
+                                obj.renderer.domElement.style.top = 0;
+                                obj.renderer.domElement.style.left = 0;
+
+
+                                obj.nodeName = "space";
+                                obj.nodeID = node.ID;
+                                this.scenes[node.ID] = node;
+
+
+                            }
+
+                            if (this.isTwoRectangleDefinition(protos)) {
+
+                                obj = new Two.Rectangle(0, 0, 10, 10);
+                                obj.nodeName = "rectangle";
+                                obj.nodeID = node.ID;
+
+                            }
+
+
+                            if (this.isTwoTextDefinition(protos)) {
+
+                                obj = new Two.Text();
+                                obj.nodeName = "text";
+                                obj.nodeID = node.ID;
+
+                            }
+
+                            if (this.isTwoAnchorDefinition(protos)) {
+
+                                obj = new Two.Anchor();
+                                obj.nodeName = "anchor";
+                                obj.nodeID = node.ID;
+
+                            }
+
+
+                            if (this.isTwoEllipseDefinition(protos)) {
+
+                                obj = new Two.Ellipse(0, 0, 10);
+                                obj.nodeName = "ellipse";
+                                obj.nodeID = node.ID;
+
+                            }
+
+                            if (this.isTwoGroupDefinition(protos)) {
+
+                                obj = new Two.Group();
+                                obj.nodeName = "group";
+                                obj.nodeID = node.ID;
+
+                            }
+
+                            if (this.isTwoCurveDefinition(protos)) {
+
+                                obj = new Two.Path();
+                                obj.nodeName = "path";
+                                obj.nodeID = node.ID;
+
+                            }
+
+                            if (this.isTwoTextureDefinition(protos)) {
+
+                                obj = new Two.Texture();
+                                obj.nodeName = "texture";
+                                obj.nodeID = node.ID;
+
+                            }
+
+                            return obj
+                        },
+                        addNodeToHierarchy: function (node) {
+
+                            if (node.obj) {
+
+                                if (this.nodes[node.parentID] !== undefined) {
+                                    var parent = this.nodes[node.parentID];
+                                    if (parent.obj) {
+
+                                        if (parent.children === undefined) {
+                                            parent.children = [];
+                                        }
+                                        parent.children.push(node.ID);
+                                        //console.info( "Adding child: " + childID + " to " + nodeID );
+                                        if (node.obj.nodeName !== "texture" && node.obj.nodeName !== "anchor") {
+                                            parent.obj.add(node.obj);
+                                        }
+                                    }
+                                }
+
+                                if (node.obj.nodeName !== "space") {
+                                    node.scene = this.scenes[self.kernel.application()];
+                                    // if(node.parentID == self.kernel.application()){
+                                    //     node.scene.obj.add(node.obj);
+                                    // }
+                                }
+
+                            }
+
+                        }
+                    };
+
+                    this.state.kernel = this.kernel.kernel.kernel;
+
+                    //this.Tone = Tone;
+                    //this.state.kernel = this.kernel.kernel.kernel;
+
+                },
+                // == Model API ============================================================================
+
+                // -- creatingNode -------------------------------------------------------------------------
+
+                creatingNode: function (nodeID, childID, childExtendsID, childImplementsIDs,
+                    childSource, childType, childIndex, childName, callback /* ( ready ) */) {
+
+                    // If the parent nodeID is 0, this node is attached directly to the root and is therefore either 
+                    // the scene or a prototype.  In either of those cases, save the uri of the new node
+                    var childURI = (nodeID === 0 ? childIndex : undefined);
+                    var appID = this.kernel.application();
+
+                    // If the node being created is a prototype, construct it and add it to the array of prototypes,
+                    // and then return
+                    var prototypeID = _self_.utility.ifPrototypeGetId(appID, this.state.prototypes, nodeID, childID);
+                    if (prototypeID !== undefined) {
+
+                        this.state.prototypes[prototypeID] = {
+                            parentID: nodeID,
+                            ID: childID,
+                            extendsID: childExtendsID,
+                            implementsID: childImplementsIDs,
+                            source: childSource,
+                            type: childType,
+                            name: childName
+                        };
+                        return;
+                    }
+
+                    var protos = _self_.getPrototypes(this.kernel, childExtendsID);
+                    //var kernel = this.kernel.kernel.kernel;
+                    var node;
+
+                    if (this.state.isNodeComponent(protos)) {
+
+                        // Create the local copy of the node properties
+                        if (this.state.nodes[childID] === undefined) {
+                            this.state.nodes[childID] = this.state.createLocalNode(nodeID, childID, childExtendsID, childImplementsIDs,
+                                childSource, childType, childIndex, childName, callback);
+                        }
+
+                        node = this.state.nodes[childID];
+                        node.prototypes = protos;
+
+                        node.obj = this.state.createObject(node);
+                        this.state.addNodeToHierarchy(node);
+
+                        //let aframeDriver = vwf.views["/drivers/view/aframe"];
+
+
+                        //notifyDriverOfPrototypeAndBehaviorProps();
+                    }
+                },
+
+                // -- initializingNode -------------------------------------------------------------------------
+
+                //   initializingNode: function( nodeID, childID, childExtendsID, childImplementsIDs,
+                //     childSource, childType, childIndex, childName ) {
+
+                // },
+
+                // -- deletingNode -------------------------------------------------------------------------
+
+                deletingNode: function (nodeID) {
+
+                    if (nodeID) {
+                        var childNode = this.state.nodes[nodeID];
+                        if (!childNode) return;
+
+
+                        if (childNode !== undefined) {
+
+                            if (childNode.children) {
+
+                                for (var i = 0; i < childNode.children.length; i++) {
+                                    this.deletingNode(childNode.children[i]);
+                                }
+                            }
+
+                            if (childNode.obj !== undefined) {
+                                // removes and destroys object
+
+                                //let space = childNode.scene.obj;
+                                childNode.obj.parent.remove(childNode.obj);
+                                childNode.obj = undefined;
+                            }
+
+                            delete this.state.nodes[nodeID];
+                        }
+
+                    }
+
+
+                },
+
+                // -- initializingProperty -----------------------------------------------------------------
+
+                initializingProperty: function (nodeID, propertyName, propertyValue) {
+
+                    var value = undefined;
+                    var node = this.state.nodes[nodeID];
+                    if (node !== undefined) {
+                        value = this.settingProperty(nodeID, propertyName, propertyValue);
+                    }
+                    return value;
+
+                },
+
+                //callingMethod
+
+                callingMethod: function (nodeID, methodName, methodParameters) {
+
+                    let self = this;
+                    var node = this.state.nodes[nodeID];
+
+                    if (!node) return;
+
+                    if (node && node.obj) {
+
+
+
+                        if (methodName == "getJointsAtTime") {
+
+                            let time = methodParameters[0];
+                            return node.motionData[time];
+
+                        }
+
+                        if (methodName == "getJointAtTime") {
+
+                            let time = methodParameters[0];
+                            let jointID = methodParameters[1];
+
+                            return node.motionData[time][jointID];
+
+                        }
+
+
+                    }
+                },
+
+
+                // -- creatingProperty ---------------------------------------------------------------------
+
+                creatingProperty: function (nodeID, propertyName, propertyValue) {
+                    return this.initializingProperty(nodeID, propertyName, propertyValue);
+                },
+
+
+                // -- settingProperty ----------------------------------------------------------------------
+
+                settingProperty: function (nodeID, propertyName, propertyValue) {
+
+                    let self = this;
+                    let node = this.state.nodes[nodeID];
+                    var value = undefined;
+
+                    if (node && node.obj && _self_.utility.validObject(propertyValue)) {
+
+                        let object = node.obj;
+
+                        if (value === undefined && self.state.isTwoGroupDefinition(node.prototypes)) {
+
+                            value = propertyValue;
+
+                            switch (propertyName) {
+
+                                case "x":
+
+                                    object.translation.x = propertyValue
+                                    break;
+
+                                case "y":
+                                    object.translation.y = propertyValue
+                                    break;
+
+                                case "translation":
+                                    let translation = this.state.setFromValue(propertyValue);
+                                    object.translation.set(translation[0], translation[1])
+                                    break;
+
+                                case "rotation":
+                                    object.rotation = propertyValue
+                                    break;
+
+                                case "scale":
+                                    object.scale = propertyValue
+                                    break;
+
+                                case "opacity":
+                                    object.opacity = propertyValue
+                                    break;
+
+                                // case "mask":
+                                //     object.mask = propertyValue
+                                // break;
+
+
+                                case "visible":
+                                    object.visible = propertyValue
+                                    break;
+
+                                default:
+                                    value = undefined;
+                                    break;
+                            }
+                        }
+
+
+
+
+                        if (value === undefined && self.state.isTwoTextDefinition(node.prototypes)) {
+
+                            value = propertyValue;
+
+                            switch (propertyName) {
+
+                                case "translation":
+
+                                    let translation = this.state.setFromValue(propertyValue);
+                                    object.translation.set(translation[0], translation[1])
+                                    break;
+
+                                case "rotation":
+                                    object.rotation = propertyValue
+                                    break;
+
+                                case "scale":
+                                    object.scale = propertyValue
+                                    break;
+
+                                case "fill":
+
+                                    object.fill = propertyValue
+
+
+                                    break;
+
+                                case "stroke":
+                                    object.stroke = propertyValue
+                                    break;
+
+                                case "linewidth":
+
+                                    object.linewidth = propertyValue
+                                    break;
+
+                                case "opacity":
+                                    object.opacity = propertyValue
+                                    break;
+
+                                case "text":
+                                    object.value = propertyValue
+                                    break;
+
+                                case "family":
+                                    object.family = propertyValue
+                                    break;
+
+
+                                case "size":
+                                    object.size = propertyValue
+                                    break;
+
+
+                                case "style":
+                                    object.style = propertyValue
+                                    break;
+
+                                case "weight":
+                                    object.weight = propertyValue
+                                    break;
+
+                                case "visible":
+                                    object.visible = propertyValue
+                                    break;
+                                // "text": null,
+                                // "family": null,
+                                // "size": null,
+                                // "style": null,
+                                // "weight": null,
+                                // "visible": null,
+
+
+                                default:
+                                    value = undefined;
+                                    break;
+                            }
+                        }
+
+
+                        if (value === undefined && self.state.isTwoPathDefinition(node.prototypes)) {
+
+                            value = propertyValue;
+
+                            switch (propertyName) {
+
+                                case "x":
+
+                                    object.translation.x = propertyValue
+                                    break;
+
+                                case "y":
+                                    object.translation.y = propertyValue
+                                    break;
+
+                                case "translation":
+
+                                    object.translation.set(propertyValue[0], propertyValue[1])
+                                    break;
+
+                                case "rotation":
+                                    object.rotation = propertyValue
+                                    break;
+
+                                case "scale":
+                                    object.scale = propertyValue
+                                    break;
+
+                                case "fill":
+
+                                    if (propertyValue.includes('https') || propertyValue.startsWith('/')) {
+                                        let name = propertyValue//.split('=')[1];
+                                        if (name.includes('webm') || name.includes('mp4')) {
+                                            var video = document.createElement('video');
+                                            video.src = name;
+                                            video.autoplay = false;
+                                            video.loop = true;
+                                            video.muted = true;
+
+                                            object.fill = new Two.Texture(video);
+                                            node.fillType = "video";
+                                        } else {
+                                            let texture = new Two.Texture(name);
+                                            object.fill = texture
+                                            node.fillType = "image";
+                                        }
+
+                                    } else {
+                                        object.fill = propertyValue
+                                        node.fillType = "color";
+                                    }
+
+
+                                    break;
+
+                                case "stroke":
+                                    object.stroke = propertyValue
+                                    break;
+
+                                case "linewidth":
+
+                                    object.linewidth = propertyValue
+                                    break;
+
+                                case "opacity":
+                                    object.opacity = propertyValue
+                                    break;
+
+                                case "clip":
+                                    object.clip = propertyValue
+                                    break;
+
+                                case "curved":
+                                    object.curved = propertyValue
+                                    break;
+
+                                case "closed":
+                                    object.closed = propertyValue
+                                    break;
+
+                                case "join":
+                                    object.join = propertyValue
+                                    break;
+
+                                case "automatic":
+                                    object.automatic = propertyValue
+                                    break;
+
+                                case "vertices":
+                                    object.vertices = [];
+                                    propertyValue.forEach(v => {
+                                        let a = new Two.Anchor(v.x, v.y);
+                                        object.vertices.push(a);
+                                    })
+                                    break;
+
+                                default:
+                                    value = undefined;
+                                    break;
+                            }
+                        }
+
+                        if (value === undefined && self.state.isTwoAnchorDefinition(node.prototypes)) {
+
+                            value = propertyValue;
+
+                            switch (propertyName) {
+
+
+                                case "x":
+
+                                    object.x = propertyValue
+                                    break;
+
+                                case "y":
+                                    object.y = propertyValue
+                                    break;
+
+                                case "command":
+                                    object.command = propertyValue
+                                    break;
+
+
+                                default:
+                                    value = undefined;
+                                    break;
+                            }
+                        }
+
+                        if (value === undefined && self.state.isTwoRectangleDefinition(node.prototypes)) {
+
+                            value = propertyValue;
+
+                            switch (propertyName) {
+
+
+                                case "height":
+
+                                    object.height = propertyValue
+                                    break;
+
+                                case "width":
+                                    object.width = propertyValue
+                                    break;
+
+
+
+                                default:
+                                    value = undefined;
+                                    break;
+                            }
+                        }
+
+                        if (value === undefined && self.state.isTwoEllipseDefinition(node.prototypes)) {
+
+                            value = propertyValue;
+
+                            switch (propertyName) {
+
+
+                                case "height":
+
+                                    object.height = propertyValue
+                                    break;
+
+                                case "width":
+                                    object.width = propertyValue
+                                    break;
+
+
+
+                                default:
+                                    value = undefined;
+                                    break;
+                            }
+                        }
+
+                        if (value === undefined && self.state.isTwoTextureDefinition(node.prototypes)) {
+
+                            value = propertyValue;
+
+                            switch (propertyName) {
+
+
+                                case "src":
+
+                                    object.src = propertyValue
+                                    break;
+
+
+
+                                default:
+                                    value = undefined;
+                                    break;
+                            }
+                        }
+
+
+
+                    }
+
+                    return value;
+
+                },
+
+                // -- gettingProperty ----------------------------------------------------------------------
+
+                gettingProperty: function (nodeID, propertyName, propertyValue) {
+
+                    let self = this;
+                    let node = this.state.nodes[nodeID];
+                    let value = undefined;
+                    if (node && node.obj) {
+
+                        let object = node.obj;
+
+                        if (value === undefined && self.state.isTwoGroupDefinition(node.prototypes)) {
+
+                            switch (propertyName) {
+
+                                case "x":
+                                    value = object.translation.x
+
+                                    break;
+
+                                case "y":
+                                    value = object.translation.y
+                                    break;
+
+                                case "translation":
+                                    let translation = [object.translation.x, object.translation.y]
+                                    value = translation
+
+                                    break;
+
+                                case "rotation":
+                                    value = object.rotation
+                                    break;
+
+                                case "scale":
+                                    value = object.scale
+                                    break;
+
+                                case "opacity":
+                                    value = object.opacity
+                                    break;
+
+                                case "twoWidth":
+                                    value = node.scene.obj.width
+                                    break;
+
+                                case "twoHeight":
+                                    value = node.scene.obj.height
+                                    break;
+
+
+                                case "visible":
+                                    value = object.visible
+                                    break;
+                                // case "mask":
+                                //     value =  object.mask
+                                //    break;
+
+                            }
+                        }
+
+
+                        if (value === undefined && self.state.isTwoTextDefinition(node.prototypes)) {
+
+                            switch (propertyName) {
+
+
+                                case "translation":
+                                    let translation = [object.translation.x, object.translation.y]
+                                    value = translation
+
+                                    break;
+
+                                case "rotation":
+                                    value = object.rotation
+                                    break;
+
+                                case "scale":
+                                    value = object.scale
+                                    break;
+
+                                case "fill":
+                                    value = object.fill
+                                    break;
+
+                                case "text":
+                                    value = object.value
+                                    break;
+
+                                case "family":
+                                    value = object.family
+                                    break;
+
+                                case "size":
+                                    value = object.size
+                                    break;
+
+                                case "stroke":
+                                    value = object.stroke
+                                    break;
+
+                                case "linewidth":
+
+                                    value = object.linewidth
+
+                                    break;
+
+                                case "opacity":
+                                    value = object.opacity
+                                    break;
+
+                                case "style":
+                                    value = object.style
+                                    break;
+
+
+                                case "weight":
+                                    value = object.weight
+                                    break;
+
+                                case "visible":
+                                    value = object.visible
+                                    break;
+
+                            }
+
+                        }
+
+                        if (value === undefined && self.state.isTwoPathDefinition(node.prototypes)) {
+
+                            switch (propertyName) {
+
+                                case "x":
+                                    value = object.translation.x
+
+                                    break;
+
+                                case "y":
+                                    value = object.translation.y
+                                    break;
+
+
+
+                                case "translation":
+                                    let translation = [object.translation.x, object.translation.y]
+                                    value = translation
+
+                                    break;
+
+                                case "rotation":
+                                    value = object.rotation
+                                    break;
+
+                                case "scale":
+                                    value = object.scale
+                                    break;
+
+                                case "fill":
+
+                                    if (typeof object.fill == 'object' && object.fill.src) {
+                                        value = object.fill.src
+                                    } else {
+                                        value = object.fill
+                                    }
+
+
+                                    break;
+
+                                case "stroke":
+                                    value = object.stroke
+                                    break;
+
+                                case "linewidth":
+
+                                    value = object.linewidth
+
+                                    break;
+
+                                case "opacity":
+                                    value = object.opacity
+                                    break;
+
+                                case "clip":
+                                    value = object.clip
+                                    break;
+
+
+                                case "twoWidth":
+                                    value = node.scene.obj.width
+                                    break;
+
+                                case "twoHeight":
+                                    value = node.scene.obj.height
+                                    break;
+
+                                case "curved":
+                                    value = object.curved
+                                    break;
+
+                                case "automatic":
+                                    value = object.automatic
+                                    break;
+
+                                case "closed":
+                                    value = object.closed
+                                    break;
+
+                                case "join":
+                                    value = object.join
+                                    break;
+
+                                case "vertices":
+                                    let anchors = object.vertices;
+                                    let vertices = anchors.map(a => {
+                                        return { x: a.x, y: a.y }
+                                    })
+                                    value = vertices
+                                    break;
+
+                            }
+                        }
+
+                        if (value === undefined && self.state.isTwoAnchorDefinition(node.prototypes)) {
+
+                            switch (propertyName) {
+
+                                case "x":
+                                    value = object.x
+                                    break;
+
+                                case "y":
+                                    value = object.y
+                                    break;
+
+                                case "command":
+                                    value = object.command
+                                    break;
+
+                            }
+                        }
+
+                        if (value === undefined && self.state.isTwoRectangleDefinition(node.prototypes)) {
+
+                            switch (propertyName) {
+
+                                case "height":
+                                    value = object.height
+                                    break;
+
+                                case "width":
+                                    value = object.width
+                                    break;
+
+                            }
+                        }
+
+                        if (value === undefined && self.state.isTwoEllipseDefinition(node.prototypes)) {
+
+                            switch (propertyName) {
+
+                                case "height":
+                                    value = object.height
+                                    break;
+
+                                case "width":
+                                    value = object.width
+                                    break;
+
+                            }
+                        }
+
+                        if (value === undefined && self.state.isTwoTextureDefinition(node.prototypes)) {
+
+                            switch (propertyName) {
+
+                                case "src":
+                                    value = object.src
+                                    break;
+
+                            }
+                        }
+
+
+                    }
+
+                    if (value !== undefined) {
+                        propertyValue = value;
+                    }
+
+                    return value;
+                }
+
+            });
+
+    }
+
+
+    getPrototypes(kernel, extendsID) {
+        var prototypes = [];
+        var id = extendsID;
+
+        while (id !== undefined) {
+            prototypes.push(id);
+            id = kernel.prototype(id);
+        }
+        return prototypes;
+    }
+}
+
+export {
+    TwoModel as default
+}

+ 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

文件差異過大導致無法顯示
+ 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
+                })
+            }
+
+        }
 
+    }
 
 
 

文件差異過大導致無法顯示
+ 0 - 0
public/drivers/view/tonejs/Tone.js


文件差異過大導致無法顯示
+ 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;
+
+})));

文件差異過大導致無法顯示
+ 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;

+ 2 - 1
public/index.js

@@ -29,7 +29,8 @@ loadjs([
     '/lib/fun/@most/dom-event/dist/index.js',
     '/lib/fun/ramda.min.js',
     '/lib/fun/infestines.js',
-    '/lib/fun/partial.lenses.min.js'
+    '/lib/fun/partial.lenses.min.js',
+    '/lib/ui/solid-js/s-js/dist/S.js'
 ],'fun', {
     async: false
 });

+ 21 - 0
public/lib/ui/goober/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Cristian Bote
+
+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.

+ 786 - 0
public/lib/ui/goober/README.md

@@ -0,0 +1,786 @@
+<p align="center">
+  <img src="./goober_cover.png" width="500" alt="goober" />
+</p>
+
+🥜 goober, a less than 1KB css-in-js solution.
+
+[![Backers on Open Collective](https://opencollective.com/goober/backers/badge.svg)](#backers)
+[![Sponsors on Open Collective](https://opencollective.com/goober/sponsors/badge.svg)](#sponsors)
+
+[![version](https://img.shields.io/npm/v/goober)](https://www.npmjs.com/package/goober)
+[![status](https://travis-ci.org/cristianbote/goober.svg?branch=master)](https://travis-ci.org/cristianbote/goober)
+[![gzip size](https://img.badgesize.io/https://unpkg.com/goober@latest/dist/goober.js?compression=gzip)](https://unpkg.com/goober)
+[![downloads](https://img.shields.io/npm/dm/goober)](https://www.npmjs.com/package/goober)
+[![coverage](https://img.shields.io/codecov/c/github/cristianbote/goober.svg?maxAge=2592000)](https://codecov.io/github/cristianbote/goober?branch=master)
+[![Slack](https://img.shields.io/badge/slack-join-orange)](https://join.slack.com/t/gooberdev/shared_invite/enQtOTM5NjUyOTcwNzI1LWUwNzg0NTQwODY1NDJmMzQ2NzdlODI4YTM3NWUwYjlkY2ZkNGVmMTFlNGMwZGUyOWQyZmI4OTYwYmRiMzE0NGQ)
+[![Greenkeeper badge](https://badges.greenkeeper.io/cristianbote/goober.svg)](https://greenkeeper.io/)
+
+# Motivation
+
+I always wondered, if you can get a working solution for css-in-js with a smaller footprint. So, while I was working on a side-project I wanted a to use styled-components or more accurate the `styled` pattern. Looking at the JavaScript bundled sizes, I quickly realized that I would have to include ~12kB([styled-components](https://github.com/styled-components/styled-components)) or ~11kB([emotion](https://github.com/emotion-js/emotion)) just so I can use the `styled` paradigm. So, I embarked in a mission to create a smaller alternative for these well established apis.
+
+# Why the peanuts emoji?
+
+It's a pun on the tagline.
+
+> css-in-js at the cost of peanuts!
+> 🥜goober
+
+# Table of contents
+
+-   [Usage](#usage)
+-   [Examples](#examples)
+-   [SSR](#ssr)
+-   [Benchmarks](#benchmarks)
+    -   [Browser](#browser)
+    -   [SSR](#ssr-1)
+-   [API](#api)
+    -   [styled](#styledtagname-string--function-forwardref-function)
+    -   [setup](#setuppragma-function-prefixer-function-theme-function-forwardprops-function)
+        -   [With prefixer](#with-prefixer)
+        -   [With theme](#with-theme)
+        -   [With forwardProps](#with-forwardProps)
+    -   [css](#csstaggedtemplate)
+    -   [targets](#targets)
+    -   [extractCss](#extractcsstarget)
+    -   [createGlobalStyles](#createglobalstyles)
+    -   [keyframes](#keyframes)
+    -   [shouldForwardProp](#shouldForwardProp)
+-   [Integrations](#integrations)
+    -   [Babel Plugin](#babel-plugin)
+    -   [Babel Macro Plugin](#babel-macro-plugin)
+    -   [Gatsby](#gatsby)
+    -   [Preact CLI Plugin](#preact-cli-plugin)
+    -   [CSS Prop](#css-prop)
+-   [Features](#features)
+    -   [Sharing Style](#sharing-style)
+    -   [Autoprefixer](#autoprefixer)
+    -   [TypeScript](#typescript)
+-   [Browser Support](#browser-support)
+-   [Contributing](#contributing)
+
+# Usage
+
+The API is inspired by emotion, `styled` function. Meaning, you call it with your `tagName` and returns a vDOM component for that tag. Note, `setup` is needed to be run before the `styled` function is used.
+
+```jsx
+import { h } from 'preact';
+import { styled, setup } from 'goober';
+
+// Should be called here, and just once
+setup(h);
+
+const Icon = styled('span')`
+    display: flex;
+    flex: 1;
+    color: red;
+`;
+
+const Button = styled('button')`
+    background: dodgerblue;
+    color: white;
+    border: ${Math.random()}px solid white;
+
+    &:focus,
+    &:hover {
+        padding: 1em;
+    }
+
+    .otherClass {
+        margin: 0;
+    }
+
+    ${Icon} {
+        color: black;
+    }
+`;
+```
+
+# Examples
+
+-   [Vanilla](https://codesandbox.io/s/qlywyp7z4q)
+-   [React](https://codesandbox.io/s/k0mnp40n7v)
+-   [Preact](https://codesandbox.io/s/r15wj2qm7o)
+-   [SSR with Preact](https://codesandbox.io/s/7m9zzl6746)
+-   [Fre](https://codesandbox.io/s/fre-goober-ffqjv)
+
+# SSR
+
+You can get the critical CSS for SSR, via `extractCss`. Take a look at this example: [CodeSandbox: SSR with Preact and goober](https://codesandbox.io/s/7m9zzl6746) and read the full explanation for `extractCSS` and `targets` below.
+
+# Benchmarks
+
+The results are included inside the build output as well.
+
+## Browser
+
+Comming soon!
+
+## SSR
+
+The benchmark is testing the following scenario:
+
+```jsx
+import styled from 'package';
+
+// Create the dynamic styled component
+const Foo = styled('div')((props) => ({
+    opacity: props.counter > 0.5 ? 1 : 0,
+    '@media (min-width: 1px)': {
+        rule: 'all'
+    },
+    '&:hover': {
+        another: 1,
+        display: 'space'
+    }
+}));
+
+// Serialize the component
+renderToString(<Foo counter={Math.random()} />);
+```
+
+The results are:
+
+```
+goober x 200,437 ops/sec ±1.93% (87 runs sampled)
+styled-components@5.2.1 x 12,650 ops/sec ±9.09% (48 runs sampled)
+emotion@11.0.0 x 104,229 ops/sec ±2.06% (88 runs sampled)
+
+Fastest is: goober
+```
+
+# API
+
+As you can see it supports most of the syntaxes of CSS. If you find any issues, please submit a ticket or even a PR with a fix.
+
+### `styled(tagName: String | Function, forwardRef?: Function)`
+
+-   `@param {String|Function} tagName` The name of the dom element you'd like the styled to be applied to
+-   `@param {Function} forwardRef` Forward ref function. Usually `React.forwardRef`
+-   `@returns {Function}` Returns the tag template function.
+
+```js
+import { styled } from 'goober';
+
+const Btn = styled('button')`
+    border-radius: 4px;
+`;
+```
+
+#### Different ways of customizing the styles
+
+##### Tagged templates functions
+
+```js
+import { styled } from 'goober';
+
+const Btn = styled('button')`
+    border-radius: ${(props) => props.size}px;
+`;
+
+<Btn size={20} />;
+```
+
+##### Function that returns a string
+
+```js
+import { styled } from 'goober';
+
+const Btn = styled('button')(
+    (props) => `
+  border-radius: ${props.size}px;
+`
+);
+
+<Btn size={20} />;
+```
+
+##### JSON/Object
+
+```js
+import { styled } from 'goober';
+
+const Btn = styled('button')((props) => ({
+    borderRadius: props.size + 'px'
+}));
+
+<Btn size={20} />;
+```
+
+##### Arrays
+
+```js
+import { styled } from 'goober';
+
+const Btn = styled('button')([
+    { color: 'tomato' },
+    ({ isPrimary }) => ({ background: isPrimary ? 'cyan' : 'gray' })
+]);
+
+<Btn />; // This will render the `Button` with `background: gray;`
+<Btn isPrimary />; // This will render the `Button` with `background: cyan;`
+```
+
+##### Forward ref function
+
+As goober is JSX library agnostic, you need to pass in the forward ref function for the library you are using. Here's how you do it for React.  
+
+```js
+const Title = styled("h1", React.forwardRef)`
+  font-weight: bold;
+  color: dodgerblue;
+`;
+```
+
+
+### `setup(pragma: Function, prefixer?: Function, theme?: Function, forwardProps?: Function)`
+
+Given the fact that `react` uses `createElement` for the transformed elements and `preact` uses `h`, `setup` should be called with the proper _pragma_ function. This was added to reduce the bundled size and being able to bundle esmodule version. At the moment I think it's the best tradeoff we can have.
+
+```js
+import React from 'react';
+import { setup } from 'goober';
+
+setup(React.createElement);
+```
+
+#### With prefixer
+
+```js
+import React from 'react';
+import { setup } from 'goober';
+
+const customPrefixer = (key, value) => `${key}: ${value};\n`;
+
+setup(React.createElement, customPrefixer);
+```
+
+#### With theme
+
+```js
+import React, { createContext, useContext, createElement } from 'react';
+import { setup, styled } from 'goober';
+
+const theme = { primary: 'blue' };
+const ThemeContext = createContext(theme);
+const useTheme = () => useContext(ThemeContext);
+
+setup(createElement, undefined, useTheme);
+
+const ContainerWithTheme = styled('div')`
+    color: ${(props) => props.theme.primary};
+`;
+```
+
+#### With forwardProps
+
+The `forwardProps` function, offers a way to achieve the same `shouldForwardProps` functionality as emotion and styled-components(with transient props) offer. The difference in here is that the function receives the whole props and you are in charge of removing the props that are should not end-up in the dom.
+
+This is a super useful functionality when paired with theme object, variants or any other customisation one might need.
+
+```js
+import React from 'react';
+import { setup, styled } from 'goober';
+
+setup(React.createElement, undefined, undefined, (props) => {
+    for (let prop in props) {
+        // Or any other conditions.
+        // This could also check if this is a dev build and not remove the props
+        if (prop === 'size') {
+            delete props[prop];
+        }
+    }
+});
+```
+
+The functionality of "transient props" (with a "\$" prefix) can be implemented as follows:
+
+```js
+import React from 'react';
+import { setup, styled } from 'goober';
+
+setup(React.createElement, undefined, undefined, (props) => {
+    for (let prop in props) {
+        if (prop[0] === '$') {
+            delete props[prop];
+        }
+    }
+});
+```
+
+Alternatively you can use `goober/should-forward-prop` addon, to pass only the filter function and not have to deal with the full `props` object.
+
+```js
+import React from 'react';
+import { setup, styled } from 'goober';
+import { shouldForwardProp } from 'goober/should-forward-prop';
+
+setup(
+    React.createElement,
+    undefined,
+    undefined,
+    // This package accepts a `filter` function. If you return false that prop
+    // won't be included in the forwarded props.
+    shouldForwardProp((prop) => {
+        return prop !== 'size';
+    })
+);
+```
+
+### `css(taggedTemplate)`
+
+-   `@returns {String}` Returns the className.
+
+To create a className, you need to call `css` with your style rules in a tagged template.
+
+```js
+import { css } from "goober";
+
+const BtnClassName = css`
+  border-radius: 4px;
+`;
+
+// vanilla JS
+const btn = document.querySelector("#btn");
+// BtnClassName === 'g016232'
+btn.classList.add(BtnClassName);
+
+// JSX
+// BtnClassName === 'g016232'
+const App => <button className={BtnClassName}>click</button>
+```
+
+#### Different ways of customizing `css`
+
+##### Passing props to `css` tagged templates
+
+```js
+import { css } from 'goober';
+
+// JSX
+const CustomButton = (props) => (
+    <button
+        className={css`
+            border-radius: ${props.size}px;
+        `}
+    >
+        click
+    </button>
+);
+```
+
+##### Using `css` with JSON/Object
+
+```js
+import { css } from 'goober';
+const BtnClassName = (props) =>
+    css({
+        background: props.color,
+        borderRadius: props.radius + 'px'
+    });
+```
+
+**Notice:** using `css` with object can reduce your bundle size.
+
+We also can declare the styles at the top of the file by wrapping `css` into a function that we call to get the className.
+
+```js
+import { css } from 'goober';
+
+const BtnClassName = (props) => css`
+    border-radius: ${props.size}px;
+`;
+
+// vanilla JS
+// BtnClassName({size:20}) -> g016360
+const btn = document.querySelector('#btn');
+btn.classList.add(BtnClassName({ size: 20 }));
+
+// JSX
+// BtnClassName({size:20}) -> g016360
+const App = () => <button className={BtnClassName({ size: 20 })}>click</button>;
+```
+
+The difference between calling `css` directly and wrapping into a function is the timing of its execution. The former is when the component(file) is imported, the latter is when it is actually rendered.
+
+If you use `extractCSS` for SSR, you may prefer to use the latter or `styled` api to avoid inconsistent results.
+
+### `targets`
+
+By default, goober will append a style tag to the `<head>` of a document. You might want to target a different node, for instance, when you want to use goober with web components (so you'd want it to append style tags to individual shadowRoots). For this purpose, you can `.bind` a new target to the `styled` and `css` methods:
+
+```js
+import * as goober from 'goober';
+const target = document.getElementById('target');
+const css = goober.css.bind({ target: target });
+const styled = goober.styled.bind({ target: target });
+```
+
+If you don't provide a target, goober always defaults to `<head>` and in environments without a DOM (think certain SSR solutions), it will just use a plain string cache to store generated styles which you can extract with `extractCSS`(see below).
+
+### `extractCss(target?)`
+
+-   `@returns {String}`
+
+Returns the `<style>` tag that is rendered in a target and clears the style sheet. Defaults to `<head>`.
+
+```js
+const { extractCss } = require('goober');
+
+// After your app has rendered, just call it:
+const styleTag = `<style id="_goober">${extractCss()}</style>`;
+
+// Note: To be able to `hydrate` the styles you should use the proper `id` so `goober` can pick it up and use it as the target from now on
+```
+
+### `createGlobalStyles`
+
+To define your global styles you need to create a `GlobalStyles` component and use it as part of your tree. The `createGlobalStyles` is available at `goober/global` addon.
+
+```js
+import { createGlobalStyles } from 'goober/global';
+
+const GlobalStyles = createGlobalStyles`
+  html,
+  body {
+    background: light;
+  }
+
+  * {
+    box-sizing: border-box;
+  }
+`;
+
+export default function App() {
+    return (
+        <div id="root">
+            <GlobalStyles />
+            <Navigation>
+            <RestOfYourApp>
+        </div>
+    )
+}
+```
+
+#### How about using `glob` function directly?
+
+Before the global addon, `goober/global`, there was a method named `glob` that was part of the main package that would do the same thing, more or less. Having only that method to define global styles usually led to missing global styles from the extracted css, since the pattern did not enforced the evaluation of the styles at render time. The `glob` method it is still exported from `goober/global` if you have a hard dependency on it. It still has the same API:
+
+```js
+import { glob } from 'goober';
+
+glob`
+  html,
+  body {
+    background: light;
+  }
+
+  * {
+    box-sizing: border-box;
+  }
+`;
+```
+
+### `keyframes`
+
+`keyframes` is a helpful method to define reusable animations that can be decoupled from the main style declaration and shared across components.
+
+```js
+import { keyframes } from 'goober';
+
+const rotate = keyframes`
+    from, to {
+        transform: rotate(0deg);
+    }
+
+    50% {
+        transform: rotate(180deg);
+    }
+`;
+
+const Wicked = styled('div')`
+    background: tomato;
+    color: white;
+    animation: ${rotate} 1s ease-in-out;
+`;
+```
+
+### `shouldForwardProp`
+
+To seamingly implement the `shouldForwardProp` without the need to provide the full loop over `props` you can use the `goober/should-forward-prop` addon.
+
+```js
+import { h } from 'preact';
+import { setup } from 'goober';
+import { shouldForwardProp } from 'goober/should-forward-prop';
+
+setup(
+    h,
+    undefined,
+    undefined,
+    shouldForwardProp((prop) => {
+        // Do NOT forward props that start with `$` symbol
+        return prop['0'] !== '$';
+    })
+);
+```
+
+# Integrations
+
+## Babel plugin
+
+You're in love with the `styled.div` syntax? Fear no more! We got you covered with a babel plugin that will take your lovely syntax from `styled.tag` and translate it to goober's `styled("tag")` call.
+
+```sh
+npm i --save-dev babel-plugin-transform-goober
+# or
+yarn add --dev babel-plugin-transform-goober
+```
+
+Visit the package in here for more info (https://github.com/cristianbote/goober/tree/master/packages/babel-plugin-transform-goober)
+
+## Babel macro plugin
+
+A babel-plugin-macros macro for [🥜goober][goober], rewriting `styled.div` syntax to `styled('div')` calls.
+
+### Usage
+
+Once you've configured [babel-plugin-macros](https://github.com/kentcdodds/babel-plugin-macros), change your imports from `goober` to `goober/macro`.
+
+Now you can create your components using `styled.*` syntax:.
+
+```js
+import { styled } from 'goober/macro';
+
+const Button = styled.button`
+    margin: 0;
+    padding: 1rem;
+    font-size: 1rem;
+    background-color: tomato;
+`;
+```
+
+## [Gatsby](https://github.com/gatsbyjs/gatsby)
+
+Want to use `goober` with Gatsby? We've got you covered! We have our own plugin to deal with styling your Gatsby projects.
+
+```sh
+npm i --save goober gatsby-plugin-goober
+# or
+yarn add goober gatsby-plugin-goober
+```
+
+## Preact CLI plugin
+
+If you use Goober with Preact CLI, you can use [preact-cli-goober-ssr](https://github.com/gerhardsletten/preact-cli-goober-ssr)
+
+```sh
+npm i --save-dev preact-cli-goober-ssr
+# or
+yarn add --dev preact-cli-goober-ssr
+
+# preact.config.js
+const gooberPlugin = require('preact-cli-goober-ssr')
+
+export default (config, env) => {
+  gooberPlugin(config, env)
+}
+```
+
+When you build your Preact application this will run `extractCss` on your prerendered pages and add critical styles for each page.
+
+## CSS Prop
+
+You can use a custom `css` prop to pass in styles on HTML elements with this Babel plugin.
+
+Installation:
+
+```sh
+npm install --save-dev @agney/babel-plugin-goober-css-prop
+```
+
+List the plugin in `.babelrc`:
+
+```
+{
+  "plugins": [
+    "@agney/babel-plugin-goober-css-prop"
+  ]
+}
+```
+
+Usage:
+
+```javascript
+<main
+    css={`
+        display: flex;
+        min-height: 100vh;
+        justify-content: center;
+        align-items: center;
+    `}
+>
+    <h1 css="color: dodgerblue">Goober</h1>
+</main>
+```
+
+# Features
+
+-   [x] Basic CSS parsing
+-   [x] Nested rules with pseudo selectors
+-   [x] Nested styled components
+-   [x] [Extending Styles](#sharing-style)
+-   [x] Media queries (@media)
+-   [x] Keyframes (@keyframes)
+-   [x] Smart(lazy) client-side hydration
+-   [x] Styling any component
+    -   via `` const Btn = ({className}) => {...}; const TomatoBtn = styled(Btn)`color: tomato;` ``
+-   [x] Vanilla(via `css` function)
+-   [x] `globalStyle`(via `glob`) so one would be able to create global styles
+-   [x] target/extract from elements other than `<head>`
+-   [x] [vendor prefixing](#autoprefixer)
+
+# Sharing style
+
+There are a couple of ways to effectly share/extend styles across components.
+
+## Extending
+
+One can simply extend the desired component that needs to be enrich or overwriten with another set of css rules.
+
+```js
+import { styled } from 'goober';
+
+// Let's declare a primitive for our styled component
+const Primitive = styled('span')`
+    margin: 0;
+    padding: 0;
+`;
+
+// Later on we could get the primitive shared styles and also add our owns
+const Container = styled(Primitive)`
+    padding: 1em;
+`;
+```
+
+## Using `as` prop
+
+Another helpful way to extend a certain component is with the `as` property. Given our example above we could modify it like:
+
+```jsx
+import { styled } from 'goober';
+
+// Our primitive element
+const Primitive = styled('span')`
+    margin: 0;
+    padding: 0;
+`;
+
+const Container = styled('div')`
+    padding: 1em;
+`;
+
+// At composition/render time
+<Primitive as={'div'} /> // <div class="go01234" />
+
+// Or using the `Container`
+<Primitive as={Container} /> // <div class="go01234 go56789" />
+```
+
+# Autoprefixer
+
+Autoprefixing is a helpful way to make sure the generated css will work seamlessly on the whole spectrum of browsers. With that in mind, the core `goober` package can't hold that logic to determine the autoprefixing needs, so we added a new package that you can choose to address them.
+
+```sh
+npm install goober
+# or
+yarn add goober
+```
+
+After the main package is installed it's time to bootstrap goober with it:
+
+```js
+import { setup } from 'goober';
+import { prefix } from 'goober/prefixer';
+
+// Bootstrap goober
+setup(React.createElement, prefix);
+```
+
+And voila! It is done!
+
+# TypeScript
+
+`goober` comes with type definitions build in, making it easy to get started in TypeScript straight away.
+
+## Prop Types
+
+If you're utilising custom props and wish to style based on them, you can do so when initialising as follows:
+
+```ts
+interface Props {
+    size: number;
+}
+
+styled('div')<Props>`
+    border-radius: ${(props) => props.size}px;
+`;
+
+// This also works!
+
+styled<Props>('div')`
+    border-radius: ${(props) => props.size}px;
+`;
+```
+
+## Extending Theme
+
+If you're using a [custom theme](../api/setup.md#with-theme) with goober, to add types to it you should create a declaration file at the base of your project.
+
+```ts
+// goober.d.t.s
+
+import 'goober';
+
+declare module 'goober' {
+    export interface DefaultTheme {
+        colors: {
+            primary: string;
+        };
+    }
+}
+```
+
+You should now have autocompletion for your theme.
+
+```ts
+const ThemeContainer = styled('div')`
+    background-color: ${(props) => props.theme.colors.primary};
+`;
+```
+
+# Browser support
+
+`goober` uses microbundle to bundle and transpile it's src into code that browsers can leverage. As you might figure it out, until now, Internet Explorer was the buggiest of them all. `goober` works on IE9, as we've successfully test it.
+
+```
+IE 9
+iOS 9.3
+Chrome 42
+FF 34
+Safari 9
+```
+
+# Contributing
+
+Feel free to try it out and checkout the examples. If you wanna fix something feel free to open a issue or a PR.
+
+## Backers
+
+Thank you to all our backers! 🙏
+<a href="https://opencollective.com/goober#backers" target="_blank"><img src="https://opencollective.com/goober/backers.svg?width=890"></a>
+
+## Sponsors
+
+Support this project by becoming a sponsor. Your logo will show up here with a link to your website.
+<a href="https://opencollective.com/goober#sponsors" target="_blank"><img src="https://opencollective.com/goober/sponsors.svg?width=890"></a>

文件差異過大導致無法顯示
+ 0 - 0
public/lib/ui/goober/dist/goober.esm.js


文件差異過大導致無法顯示
+ 0 - 0
public/lib/ui/goober/dist/goober.js


文件差異過大導致無法顯示
+ 0 - 0
public/lib/ui/goober/dist/goober.modern.js


文件差異過大導致無法顯示
+ 0 - 0
public/lib/ui/goober/dist/goober.umd.js


+ 1 - 0
public/lib/ui/goober/global/dist/goober-global.esm.js

@@ -0,0 +1 @@
+import{css as n,styled as l}from"goober";let o=n.bind({g:1});function r(){const n=l.call({g:1},"div").apply(null,arguments);return function(l){return n(l),null}}export{r as createGlobalStyles,o as glob};

+ 1 - 0
public/lib/ui/goober/global/dist/goober-global.js

@@ -0,0 +1 @@
+var l=require("goober");let e=l.css.bind({g:1});exports.createGlobalStyles=function(){const e=l.styled.call({g:1},"div").apply(null,arguments);return function(l){return e(l),null}},exports.glob=e;

+ 1 - 0
public/lib/ui/goober/global/dist/goober-global.modern.js

@@ -0,0 +1 @@
+import{css as n,styled as l}from"goober";let o=n.bind({g:1});function r(){const n=l.call({g:1},"div").apply(null,arguments);return function(l){return n(l),null}}export{r as createGlobalStyles,o as glob};

+ 1 - 0
public/lib/ui/goober/global/dist/goober-global.umd.js

@@ -0,0 +1 @@
+!function(e,o){"object"==typeof exports&&"undefined"!=typeof module?o(exports,require("goober")):"function"==typeof define&&define.amd?define(["exports","goober"],o):o((e=e||self).gooberGlobal={},e.goober)}(this,function(e,o){let n=o.css.bind({g:1});e.createGlobalStyles=function(){const e=o.styled.call({g:1},"div").apply(null,arguments);return function(o){return e(o),null}},e.glob=n});

+ 19 - 0
public/lib/ui/goober/global/global.d.ts

@@ -0,0 +1,19 @@
+import { Properties as CSSProperties } from 'csstype';
+export = gooberGlobal;
+
+export as namespace gooberGlobal;
+
+declare namespace gooberGlobal {
+    interface CSSAttribute extends CSSProperties {
+        [key: string]: CSSAttribute | string | number | undefined;
+    }
+
+    function createGlobalStyles(
+        tag: CSSAttribute | TemplateStringsArray | string,
+        ...props: Array<string | number | Function>
+    ): Function;
+    function glob(
+        tag: CSSAttribute | TemplateStringsArray | string,
+        ...props: Array<string | number>
+    ): void;
+}

+ 41 - 0
public/lib/ui/goober/global/package.json

@@ -0,0 +1,41 @@
+{
+  "name": "goober-global",
+  "amdName": "gooberGlobal",
+  "version": "0.0.1",
+  "description": "The createGlobalStyles addon function for goober",
+  "sideEffects": false,
+  "main": "dist/goober-global.js",
+  "module": "dist/goober-global.esm.js",
+  "umd:main": "dist/goober-global.umd.js",
+  "source": "src/index.js",
+  "unpkg": "dist/goober-global.umd.js",
+  "types": "./global.d.ts",
+  "scripts": {
+    "build": "rm -rf dist && microbundle --entry src/index.js --name gooberGlobal --no-sourcemap",
+    "test": "jest"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/cristianbote/goober.git",
+    "directory": "global"
+  },
+  "author": "Cristian <botecristian@yahoo.com>",
+  "keywords": [
+    "goober",
+    "styled",
+    "global"
+  ],
+  "license": "ISC",
+  "peerDependencies": {
+    "goober": "^2.0.29"
+  },
+  "devDependencies": {
+    "goober": "^2.0.29",
+    "microbundle": "^0.12.4",
+    "jest": "^24.1.0",
+    "preact": "^10.5.6",
+    "@babel/plugin-transform-react-jsx": "^7.7.0",
+    "@babel/preset-env": "^7.3.1",
+    "babel-jest": "^24.1.0"
+  }
+}

+ 9 - 0
public/lib/ui/goober/global/src/__tests__/__snapshots__/integration.test.js.snap

@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`createGlobalStyles regular 1`] = `" html, body{background:dodgerblue;}"`;
+
+exports[`createGlobalStyles regular 2`] = `"<div></div>"`;
+
+exports[`createGlobalStyles with theme 1`] = `"html, body{margin:0;background:blue;}"`;
+
+exports[`createGlobalStyles with theme 2`] = `"<div></div>"`;

+ 22 - 0
public/lib/ui/goober/global/src/__tests__/global.test.js

@@ -0,0 +1,22 @@
+import { glob, createGlobalStyles } from '../index';
+import { css, setup } from 'goober';
+
+jest.mock('goober', () => ({
+    css: jest.fn().mockReturnValue('css()')
+}));
+
+describe('global', () => {
+    beforeEach(() => {
+        css.mockClear();
+    });
+
+    it('type', () => {
+        expect(typeof glob).toEqual('function');
+        expect(typeof createGlobalStyles).toEqual('function');
+    });
+
+    it('glob', () => {
+        glob`a:b`;
+        expect(css).toBeCalledWith(['a:b']);
+    });
+});

+ 56 - 0
public/lib/ui/goober/global/src/__tests__/integration.test.js

@@ -0,0 +1,56 @@
+import { h, createContext, render } from 'preact';
+import { useContext } from 'preact/hooks';
+import { setup, extractCss } from 'goober';
+import { createGlobalStyles } from '../index';
+
+describe('createGlobalStyles', () => {
+    it('regular', () => {
+        setup(h);
+
+        const target = document.createElement('div');
+
+        const GlobalStyle = createGlobalStyles`
+            html, body {
+                background: dodgerblue;
+            }
+        `;
+
+        render(
+            <div>
+                <GlobalStyle />
+            </div>,
+            target
+        );
+
+        expect(extractCss()).toMatchSnapshot();
+        expect(target.innerHTML).toMatchSnapshot();
+    });
+
+    it('with theme', () => {
+        const ThemeContext = createContext();
+        const useTheme = () => useContext(ThemeContext);
+
+        setup(h, null, useTheme);
+
+        const target = document.createElement('div');
+
+        const GlobalStyle = createGlobalStyles`
+            html, body {
+                margin: 0;
+                background: ${(props) => props.theme.color};
+            }
+        `;
+
+        render(
+            <ThemeContext.Provider value={{ color: 'blue' }}>
+                <div>
+                    <GlobalStyle />
+                </div>
+            </ThemeContext.Provider>,
+            target
+        );
+
+        expect(extractCss()).toMatchSnapshot();
+        expect(target.innerHTML).toMatchSnapshot();
+    });
+});

+ 26 - 0
public/lib/ui/goober/global/src/index.js

@@ -0,0 +1,26 @@
+import { css, styled } from 'goober';
+
+/**
+ * CSS Global function to declare global styles
+ * @type {Function}
+ */
+export let glob = css.bind({ g: 1 });
+
+/**
+ * Creates the global styles component to be used as part of your tree.
+ * @returns {Function}
+ */
+export function createGlobalStyles() {
+    const fn = styled.call({ g: 1 }, 'div').apply(null, arguments);
+
+    /**
+     * This is the actual component that gets rendered.
+     */
+    return function GlobalStyles(props) {
+        // Call the above styled.
+        fn(props);
+
+        // Returns a hole.
+        return null;
+    };
+}

部分文件因文件數量過多而無法顯示