diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..b512c09d476623ff4bf8d0d63c29b784925dbdf8
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1 @@
+node_modules
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f084ac2010fe12bb85d640c634fdda21594c2c04..0ee38faacdd75f02e4d4c8567b72065c7d9ef719 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -39,7 +39,7 @@ jobs:
     - run: |
         if [[ "$GITHUB_REF" = *hotfix* ]] || [[ "$GITHUB_REF" = refs/heads/staging ]]
         then
-          export SIIBRA_API_ENDPOINTS=https://siibra-api-rc.apps.hbp.eu/v2_0
+          export SIIBRA_API_ENDPOINTS=https://siibra-api-rc.apps.hbp.eu/v2_0,https://siibra-api-rc.apps.jsc.hbp.eu/v2_0
           node src/environments/parseEnv.js ./environment.ts
         fi
         npm run test-ci
diff --git a/.github/workflows/docker_img.yml b/.github/workflows/docker_img.yml
index a25d30c92fe0636dc593c859e224eead06025d52..057a93d7864f37820b14b5cf8f6ce141a6a08f85 100644
--- a/.github/workflows/docker_img.yml
+++ b/.github/workflows/docker_img.yml
@@ -55,13 +55,6 @@ jobs:
 
     - name: 'Set version variable & expmt feature flag'
       run: |
-        GIT_HASH=$(git rev-parse --short HEAD)
-        echo "Setting GIT_HASH: $GIT_HASH"
-        echo "GIT_HASH=$GIT_HASH" >> $GITHUB_ENV
-
-        VERSION=$(jq -r '.version' package.json)
-        echo "Setting VERSION: $VERSION"
-        echo "VERSION=$VERSION" >> $GITHUB_ENV
         if [[ "$GITHUB_REF" == 'refs/heads/master' ]] || [[ "$GITHUB_REF" == 'refs/heads/staging' ]]
         then
           echo "prod/staging build, do not enable experimental features"
@@ -74,8 +67,6 @@ jobs:
         DOCKER_BUILT_TAG=${{ env.DOCKER_REGISTRY }}siibra-explorer:$BRANCH_NAME
         echo "Building $DOCKER_BUILT_TAG"
         docker build \
-          --build-arg GIT_HASH=$GIT_HASH \
-          --build-arg VERSION=$VERSION \
           --build-arg MATOMO_URL=$MATOMO_URL \
           --build-arg MATOMO_ID=$MATOMO_ID \
           --build-arg SIIBRA_API_ENDPOINTS=$SIIBRA_API_ENDPOINTS \
diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html
index 392e96571a4090735d8407bf9a93592d0344d34b..0257abdbb7f26fc10a58b7ce42f19c7cd719c4eb 100644
--- a/.storybook/preview-head.html
+++ b/.storybook/preview-head.html
@@ -11,3 +11,4 @@
 </style>
 <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
 <script type="module" src="https://unpkg.com/hbp-connectivity-component@0.6.2/dist/connectivity-component/connectivity-component.js" defer></script>
+<link rel="stylesheet" href="icons/iav-icons.css">
diff --git a/.storybook/preview.js b/.storybook/preview.js
index 957e7cbef107fa2c3191e57245241fb901983f17..52750e8cde9405a8fc3282ed73730bc13101605d 100644
--- a/.storybook/preview.js
+++ b/.storybook/preview.js
@@ -4,6 +4,14 @@ setCompodocJson(docJson);
 
 import 'src/theme.scss'
 
+/**
+ * load custom icons
+ */
+import '!!file-loader?context=src/res&name=icons/iav-icons.css!src/res/icons/iav-icons.css'
+import '!!file-loader?context=src/res&name=icons/iav-icons.ttf!src/res/icons/iav-icons.ttf'
+import '!!file-loader?context=src/res&name=icons/iav-icons.woff!src/res/icons/iav-icons.woff'
+import '!!file-loader?context=src/res&name=icons/iav-icons.svg!src/res/icons/iav-icons.svg'
+
 export const parameters = {
   actions: { argTypesRegex: "^on[A-Z].*" },
   controls: {
diff --git a/Dockerfile b/Dockerfile
index 8ad7624403cf18aa021e133e2926d1fc1f616b04..8425647048ad716c0b86edb66ad609b36aa01a84 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -21,11 +21,9 @@ ENV MATOMO_ID=${MATOMO_ID}
 ARG EXPERIMENTAL_FEATURE_FLAG
 ENV EXPERIMENTAL_FEATURE_FLAG=${EXPERIMENTAL_FEATURE_FLAG:-false}
 
-ARG GIT_HASH
-ENV GIT_HASH=${GIT_HASH:-unknownhash}
+ARG ENABLE_LEAP_MOTION
+ENV ENABLE_LEAP_MOTION=${ENABLE_LEAP_MOTION:-false}
 
-ARG VERSION
-ENV VERSION=${VERSION:-unknownversion}
 
 COPY . /iv
 WORKDIR /iv
diff --git a/angular.json b/angular.json
index 9797b5413cb83b570a3695fe1a1bdca44b6a2de7..48fe4b228e4f2fca1ed066681e73248be78ab271 100644
--- a/angular.json
+++ b/angular.json
@@ -80,6 +80,10 @@
               "input": "export-nehuba/dist/min/chunk_worker.bundle.js",
               "inject": false,
               "bundleName": "chunk_worker.bundle"
+            },{
+              "inject": false,
+              "input": "third_party/leap-0.6.4.js",
+              "bundleName": "leap-0.6.4"
             }]
           },
           "configurations": {
diff --git a/build_env.md b/build_env.md
index fa8ed9e3951cdabd2cfa2f26bd46c5904e74af14..81833e992e2dd7b5e484e0593cee4409e128c603 100644
--- a/build_env.md
+++ b/build_env.md
@@ -4,7 +4,6 @@ As interactive atlas viewer uses [webpack define plugin](https://webpack.js.org/
 
 | name | description | default | example |
 | --- | --- | --- | --- |
-| `VERSION` | printed in console on viewer startup | `GIT_HASH` \|\| unspecificed hash | v2.2.2 |
 | `PRODUCTION` | if the build is for production, toggles optimisations such as minification | `undefined` | true |
 | `BACKEND_URL` | backend that the viewer calls to fetch available template spaces, parcellations, plugins, datasets | `null` | https://interactive-viewer.apps.hbp.eu/ |
 | ~~`BS_REST_URL`~~ _deprecated. use `SIIBRA_API_ENDPOINTS` instead_ | [siibra-api](https://github.com/FZJ-INM1-BDA/siibra-api) used to fetch different resources | `https://siibra-api-stable.apps.hbp.eu/v1_0` |
@@ -13,4 +12,5 @@ As interactive atlas viewer uses [webpack define plugin](https://webpack.js.org/
 | `MATOMO_ID` | application id for matomo analytics | `null` | 6 |
 | `STRICT_LOCAL` | hides **Explore** and **Download** buttons. Useful for offline demo's | `false` | `true` |
 | `KIOSK_MODE` | after 5 minutes of inactivity, shows overlay inviting users to interact | `false` | `true` |
-| `BUILD_TEXT` | overlay text at bottom right of the viewer. set to `''` to hide. | |
+| `EXPERIMENTAL_FEATURE_FLAG` | enabling experimental features | `false` | `true` |
+| `ENABLE_LEAP_MOTION` | enable leap motion controller | `false` | `true` |
diff --git a/common/constants.js b/common/constants.js
index 94a42b81abf59142a3d50f5a175d64208e02fbbd..c1fe2db5b6981123c9811587a0523a859b95c234 100644
--- a/common/constants.js
+++ b/common/constants.js
@@ -136,7 +136,11 @@ If you do not accept the Terms & Conditions you are not permitted to access or u
     LOADING_ANNOTATION_MSG: `Loading annotations... Please wait...`,
 
     ATLAS_SELECTOR_LABEL_SPACES: `Spaces`,
-    ATLAS_SELECTOR_LABEL_PARC_MAPS: `Parcellation maps`
+    ATLAS_SELECTOR_LABEL_PARC_MAPS: `Parcellation maps`,
+
+    TOGGLE_LAYER_VISILITY: 'Toggle layer visility',
+    ORIENT_TO_LAYER: 'Orient to layer native orientation',
+    CONFIGURE_LAYER: 'Configure layer'
   }
 
   exports.QUICKTOUR_DESC ={
diff --git a/common/helpOnePager.md b/common/helpOnePager.md
index a8551e0b2b22258b633459a5854774ac301f8ae0..4fb8f16ec04794c71a642a1d92526a6a2e53800c 100644
--- a/common/helpOnePager.md
+++ b/common/helpOnePager.md
@@ -10,6 +10,7 @@
 | Next slice | `<ctrl>` + `[mousewheel]` | - |
 | Next 10 slice | `<ctrl>` + `<shift>` + `[mousewheel]` | - |
 | Toggle delineation | `[q]` | - |
+| Toggle cross hair | `[a]` | - |
 
 ---
 
diff --git a/common/util.js b/common/util.js
index 198b3f0b6a0de651198849214120e918799e8af9..44f458cafc4884e64f1a225c6d11a3425fbbda68 100644
--- a/common/util.js
+++ b/common/util.js
@@ -239,5 +239,78 @@
   exports.isVec4 = isVec4
   exports.isMat4 = isMat4
 
+  const cipher = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-'
+  const negString = '~'
+
+  const encodeInt = (number) => {
+    if (number % 1 !== 0) { throw new Error('cannot encodeInt on a float. Ensure float flag is set') }
+    if (isNaN(Number(number)) || number === null || number === Number.POSITIVE_INFINITY) { throw new Error('The input is not valid') }
+  
+    let residual
+    let result = ''
+  
+    if (number < 0) {
+      result += negString
+      residual = Math.floor(number * -1)
+    } else {
+      residual = Math.floor(number)
+    }
+  
+    /* eslint-disable-next-line no-constant-condition */
+    while (true) {
+      result = cipher.charAt(residual % 64) + result
+      residual = Math.floor(residual / 64)
+  
+      if (residual === 0) {
+        break
+      }
+    }
+    return result
+  }
+  const decodeToInt = (encodedString) => {
+    let _encodedString
+    let negFlag = false
+    if (encodedString.slice(-1) === negString) {
+      negFlag = true
+      _encodedString = encodedString.slice(0, -1)
+    } else {
+      _encodedString = encodedString
+    }
+    return (negFlag ? -1 : 1) * [..._encodedString].reduce((acc, curr) => {
+      const index = cipher.indexOf(curr)
+      if (index < 0) { throw new Error(`Poisoned b64 encoding ${encodedString}`) }
+      return acc * 64 + index
+    }, 0)
+  }
+
+  exports.sxplrNumB64Enc = {
+    separator: ".",
+    cipher,
+    encodeNumber: (number, opts = { float: false }) => {
+      const { float } = opts
+      if (!float) {
+        return encodeInt(number)
+      } else {
+        const floatArray = new Float32Array(1)
+        floatArray[0] = number
+        const intArray = new Uint32Array(floatArray.buffer)
+        const castedInt = intArray[0]
+        return encodeInt(castedInt)
+      }
+    },
+    decodeToNumber: (encodedString, opts = { float: false }) => {
+      const { float } = opts
+      if (!float) {
+        return decodeToInt(encodedString)
+      } else {
+        const _int = decodeToInt(encodedString)
+        const intArray = new Uint32Array(1)
+        intArray[0] = _int
+        const castedFloat = new Float32Array(intArray.buffer)
+        return castedFloat[0]
+      }
+    }
+  }
+
 
 })(typeof exports === 'undefined' ? module.exports : exports)
diff --git a/deploy/app.js b/deploy/app.js
index 7b48f151dcd9e9e9fc8d9595b37eab27d00e02b4..f4b33a1c39d3904ace5651ca22ed2a909e8d269b 100644
--- a/deploy/app.js
+++ b/deploy/app.js
@@ -7,7 +7,7 @@ const crypto = require('crypto')
 const cookieParser = require('cookie-parser')
 const bkwdMdl = require('./bkwdCompat')()
 
-const LOCAL_CDN_FLAG = !!process.env.PRECOMPUTED_SERVER
+const LOCAL_CDN_FLAG = !!process.env.LOCAL_CDN
 
 if (process.env.NODE_ENV !== 'production') {
   app.use(require('cors')())
@@ -126,7 +126,8 @@ if (LOCAL_CDN_FLAG) {
   const LOCAL_CDN = process.env.LOCAL_CDN
   const CDN_ARRAY = [
     'https://stackpath.bootstrapcdn.com',
-    'https://use.fontawesome.com'
+    'https://use.fontawesome.com',
+    'https://unpkg.com'
   ]
 
   let indexFile
@@ -214,8 +215,7 @@ app.get('/ready', async (req, res) => {
  * only use compression for production
  * this allows locally built aot to be served without errors
  */
-const { compressionMiddleware, setAlwaysOff } = require('nomiseco')
-if (LOCAL_CDN_FLAG) setAlwaysOff(true)
+const { compressionMiddleware } = require('nomiseco')
 
 app.use(compressionMiddleware, express.static(PUBLIC_PATH))
 
diff --git a/deploy/bkwdCompat/urlState.js b/deploy/bkwdCompat/urlState.js
index 64093f9e035e19fc95c9ee8eea8b2caa2303912b..52b245bcc54d7b6c1bc2a479a354651e215143e7 100644
--- a/deploy/bkwdCompat/urlState.js
+++ b/deploy/bkwdCompat/urlState.js
@@ -1,6 +1,12 @@
 // this module is suppose to rewrite state stored in query param
 // and convert it to path based url
-const separator = '.'
+const { sxplrNumB64Enc } = require("../../common/util")
+
+const {
+  encodeNumber,
+  separator
+} = sxplrNumB64Enc
+
 const waxolmObj = {
   aId: 'minds/core/parcellationatlas/v1.0.0/522b368e-49a3-49fa-88d3-0870a307974a',
   id: 'minds/core/referencespace/v1.0.0/d5717c4a-0fa1-46e6-918c-b8003069ade8',
@@ -131,7 +137,7 @@ module.exports = (query, _warningCb) => {
     regionsSelected, // deprecating - check if any one calls this url
     cRegionsSelected,
 
-    navigation, // deprecating - check if any one calls this endpoint
+    navigation,
     cNavigation,
   } = query || {}
 
@@ -146,7 +152,6 @@ module.exports = (query, _warningCb) => {
     if (Object.values(WARNING_STRINGS).includes(arg)) _warningCb(arg)
   }
 
-  if (navigation) console.warn(`navigation has been deprecated`)
   if (regionsSelected) console.warn(`regionSelected has been deprecated`)
   if (niftiLayers) console.warn(`nifitlayers has been deprecated`)
 
@@ -160,6 +165,30 @@ module.exports = (query, _warningCb) => {
 
   // common search param & path
   let nav, dsp, r
+  if (navigation) {
+    try {
+
+      const [
+        _o, _po, _pz, _p, _z
+      ] = navigation.split("__")
+      const o = _o.split("_").map(v => Number(v))
+      const po = _po.split("_").map(v => Number(v))
+      const pz = Number(_pz)
+      const p = _p.split("_").map(v => Number(v))
+      const z = Number(_z)
+      const v = [
+        o.map(n => encodeNumber(n, {float: true})).join(separator),
+        po.map(n => encodeNumber(n, {float: true})).join(separator),
+        encodeNumber(Math.floor(pz)),
+        Array.from(p).map(v => Math.floor(v)).map(n => encodeNumber(n)).join(separator),
+        encodeNumber(Math.floor(z)),
+      ].join(`${separator}${separator}`)
+
+      nav = `/@:${encodeURI(v)}`
+    } catch (e) {
+      console.warn(`Parsing navigation param error`, e)
+    }
+  }
   if (cNavigation) nav = `/@:${encodeURI(cNavigation)}`
   if (previewingDatasetFiles) {
     try {
diff --git a/deploy/saneUrl/index.js b/deploy/saneUrl/index.js
index 3bc0bca9fe6e6eafad1075a0a4b16b2eddddd91e..582113e0c19cc9461c81eb288824e265e0d3bef1 100644
--- a/deploy/saneUrl/index.js
+++ b/deploy/saneUrl/index.js
@@ -104,7 +104,8 @@ router.post('/:name',
   limiterMiddleware(),
   express.json(),
   async (req, res) => {
-    if (req.headers['x-noop']) return res.status(200).end()
+    if (/bot/i.test(req.headers['user-agent'])) return res.status(201).end()
+    if (req.headers['x-noop']) return res.status(201).end()
     const { name } = req.params
     try {
       await proxyStore.set(req, name, req.body)
diff --git a/deploy/util/reconfigPrecomputedServer.js b/deploy/util/reconfigPrecomputedServer.js
deleted file mode 100644
index 98a1932ba74dbdc4361efae7056f876af7141b15..0000000000000000000000000000000000000000
--- a/deploy/util/reconfigPrecomputedServer.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * n.b. trailing slash is required
- * e.g. http://localhost:10080/
- */
-const PRECOMPUTED_SERVER = process.env.PRECOMPUTED_SERVER
-
-const reconfigureFlag = !!PRECOMPUTED_SERVER
-
-exports.reconfigureFlag = reconfigureFlag
-
-exports.reconfigureUrl = (str) => {
-  if (!reconfigureFlag) return str
-  return str.replace(/https?:\/\/.*?\//g, PRECOMPUTED_SERVER)
-}
\ No newline at end of file
diff --git a/deploy_env.md b/deploy_env.md
index bb17c783cbd86d4fa1932a36f44f7a4118012452..1926d4bb3fb5214230399d416954165c7a9b7862 100644
--- a/deploy_env.md
+++ b/deploy_env.md
@@ -8,12 +8,14 @@
 | `HOST_PATHNAME` | pathname to listen on, restrictions: leading slash, no trailing slash | `''` | `/viewer` |
 | `SESSIONSECRET` | session secret for cookie session |
 | `NODE_ENV` | determines where the built viewer will be served from | | `production` |
-| `PRECOMPUTED_SERVER` | redirect data uri to another server. Useful for offline demos | | `http://localhost:8080/precomputed/` |
+| ~~`PRECOMPUTED_SERVER`~~ _deprecated_, use `LOCAL_CDN` instead. | redirect data uri to another server. Useful for offline demos | | `http://localhost:8080/precomputed/` |
 | `LOCAL_CDN` | rewrite cdns to local server. useful for offlnie demo | | `http://localhost:7080/` |
 | `PLUGIN_URLS` | semi colon separated urls to be returned when user queries plugins | `''`
 | `STAGING_PLUGIN_URLS` | semi colon separated urls to be returned when user queries plugins | `''`
 | `USE_LOGO` | possible values are `hbp`, `ebrains`, `fzj` | `hbp` | `ebrains` |
 | `__DEBUG__` | debug flag | 
+| `BUILD_TEXT` | overlay text at bottom right of the viewer. set to `''` to hide. | |
+
 
 ##### ebrains user authentication
 
diff --git a/docs/releases/v2.7.4.md b/docs/releases/v2.7.4.md
new file mode 100644
index 0000000000000000000000000000000000000000..d214b2f110f3848759ec885f36821072f051b987
--- /dev/null
+++ b/docs/releases/v2.7.4.md
@@ -0,0 +1,6 @@
+# v2.7.4
+
+## Bugfix
+
+- Properly use fallback when detecting fault
+- Minor wording/cosmetic change
diff --git a/docs/releases/v2.7.5.md b/docs/releases/v2.7.5.md
new file mode 100644
index 0000000000000000000000000000000000000000..9ce31069890e6e1b5dea48881aba172bd4f0cc66
--- /dev/null
+++ b/docs/releases/v2.7.5.md
@@ -0,0 +1,24 @@
+# v2.7.5
+
+## Features
+
+- experimental support for parsing some swc files.
+- added toggle crosshair in helper one pager
+- allow navigation to VOI's native orientation plane
+- reworked plugin window
+  - it now longer act as if it is a separate window
+  - there are now three forms of the plugin: normal (default), maximized, minimized.
+
+## Bugfix
+
+- saneUrl were not able to correctly save user requests
+
+## Under the hood
+
+- enhancement in `strict local mode`
+  - hide external links in `strict local mode`
+  - do not try to perform interspace translation
+  - do not try to show feature pane
+- direct support for Leap motion controller
+- set git hash and version during build step (no longer requiring it to be set during build-arg step)
+- restores the functionality to parse `navigation` query param
diff --git a/e2e/checklist.md b/e2e/checklist.md
index d03d59b1f8424872c3c88efefa15b01a3b600ecd..fe9d0cb0fa85b4f5eab53e808f4cec5ba91f7bae 100644
--- a/e2e/checklist.md
+++ b/e2e/checklist.md
@@ -68,6 +68,7 @@
 - [ ] saneurl generation functions properly
   - [ ] try existing key (human), and get unavailable error
   - [ ] try non existing key, and get available
+  - [ ] create use key `x-tmp-foo` and new url works
 - [ ] [saneUrl](https://atlases.ebrains.eu/viewer-staging/saneUrl/bigbrainGreyWhite) redirects to big brain
 - [ ] [saneUrl](https://atlases.ebrains.eu/viewer-staging/saneUrl/julichbrain) redirects to julich brain (colin 27)
 - [ ] [saneUrl](https://atlases.ebrains.eu/viewer-staging/saneUrl/whs4) redirects to waxholm v4
diff --git a/mkdocs.yml b/mkdocs.yml
index 645c2483972a50115c13db5bfeecb40e4489fc6d..5372b5da4bf778115d93f5ac54c3894ab8141b15 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -33,6 +33,8 @@ nav:
     - Fetching datasets: 'advanced/datasets.md'
     - Display non-atlas volumes: 'advanced/otherVolumes.md'
   - Release notes:
+    - v2.7.5: 'releases/v2.7.5.md'
+    - v2.7.4: 'releases/v2.7.4.md'
     - v2.7.3: 'releases/v2.7.3.md'
     - v2.7.2: 'releases/v2.7.2.md'
     - v2.7.1: 'releases/v2.7.1.md'
diff --git a/package.json b/package.json
index dd637a495824c81008750313065fd1866e01dadf..64e82a9d397bf02c29c67f449ffd8467b89b1f2d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "interactive-viewer",
-  "version": "2.7.3",
+  "version": "2.7.5",
   "description": "siibra-explorer - explore brain atlases. Based on humanbrainproject/nehuba & google/neuroglancer. Built with angular",
   "scripts": {
     "lint": "eslint src --ext .ts",
diff --git a/src/atlasComponents/sapi/core/sapiParcellation.ts b/src/atlasComponents/sapi/core/sapiParcellation.ts
index 4767cc2897ab0ed6c6567f3ee727ad2f73fece04..085c5a82b8136901136593aa0b2933d992efb22e 100644
--- a/src/atlasComponents/sapi/core/sapiParcellation.ts
+++ b/src/atlasComponents/sapi/core/sapiParcellation.ts
@@ -1,4 +1,5 @@
 import { Observable } from "rxjs"
+import { switchMap } from "rxjs/operators"
 import { SapiVolumeModel } from ".."
 import { SAPI } from "../sapi.service"
 import {SapiParcellationFeatureModel, SapiParcellationModel, SapiQueryPriorityArg, SapiRegionModel} from "../type"
@@ -20,43 +21,53 @@ export class SAPIParcellation{
   }
 
   getDetail(queryParam?: SapiQueryPriorityArg): Observable<SapiParcellationModel>{
-    return this.sapi.httpGet<SapiParcellationModel>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}`,
-      null,
-      queryParam
+    return SAPI.BsEndpoint$.pipe(
+      switchMap(endpt => this.sapi.httpGet<SapiParcellationModel>(
+        `${endpt}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}`,
+        null,
+        queryParam
+      ))
     )
   }
 
   getRegions(spaceId: string, queryParam?: SapiQueryPriorityArg): Observable<SapiRegionModel[]> {
-    return this.sapi.httpGet<SapiRegionModel[]>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/regions`,
-      {
-        space_id: spaceId
-      },
-      queryParam
+    return SAPI.BsEndpoint$.pipe(
+      switchMap(endpt => this.sapi.httpGet<SapiRegionModel[]>(
+        `${endpt}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/regions`,
+        {
+          space_id: spaceId
+        },
+        queryParam
+      ))
     )
   }
   getVolumes(): Observable<SapiVolumeModel[]>{
-    return this.sapi.httpGet<SapiVolumeModel[]>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/volumes`
-    )
+    return SAPI.BsEndpoint$.pipe(
+      switchMap(endpt => this.sapi.httpGet<SapiVolumeModel[]>(
+        `${endpt}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/volumes`
+      ))
+    ) 
   }
 
   getFeatures(parcPagination?: ParcellationPaginationQuery, queryParam?: SapiQueryPriorityArg): Observable<SapiParcellationFeatureModel[]> {
-    return this.sapi.httpGet<SapiParcellationFeatureModel[]>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/features`,
-      {
-        type: parcPagination?.type,
-        size: parcPagination?.size?.toString() || '5',
-        page: parcPagination?.page.toString() || '0',
-      },
-      queryParam
+    return SAPI.BsEndpoint$.pipe(
+      switchMap(endpt => this.sapi.httpGet<SapiParcellationFeatureModel[]>(
+        `${endpt}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/features`,
+        {
+          type: parcPagination?.type,
+          size: parcPagination?.size?.toString() || '5',
+          page: parcPagination?.page.toString() || '0',
+        },
+        queryParam
+      ))
     )
   }
 
   getFeatureInstance(instanceId: string): Observable<SapiParcellationFeatureModel> {
-    return this.sapi.http.get<SapiParcellationFeatureModel>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/features/${encodeURIComponent(instanceId)}`,
+    return SAPI.BsEndpoint$.pipe(
+      switchMap(endpt => this.sapi.http.get<SapiParcellationFeatureModel>(
+        `${endpt}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/features/${encodeURIComponent(instanceId)}`,
+      ))
     )
   }
 }
diff --git a/src/atlasComponents/sapi/core/sapiRegion.ts b/src/atlasComponents/sapi/core/sapiRegion.ts
index d7b82c7d918d3e8209ead4d8033a6a4f12965605..d9263cea03f0d7045e3a1f5043f8a4edc2209929 100644
--- a/src/atlasComponents/sapi/core/sapiRegion.ts
+++ b/src/atlasComponents/sapi/core/sapiRegion.ts
@@ -2,7 +2,7 @@ import { SAPI } from "..";
 import { SapiRegionalFeatureModel, SapiRegionMapInfoModel, SapiRegionModel, cleanIeegSessionDatasets, SapiIeegSessionModel, CleanedIeegDataset, SapiVolumeModel, PaginatedResponse } from "../type";
 import { strToRgb, hexToRgb } from 'common/util'
 import { merge, Observable, of } from "rxjs";
-import { catchError, map, scan } from "rxjs/operators";
+import { catchError, map, scan, switchMap } from "rxjs/operators";
 
 export class SAPIRegion{
 
@@ -16,7 +16,7 @@ export class SAPIRegion{
     return strToRgb(JSON.stringify(region))
   }
 
-  private prefix: string
+  private prefix$: Observable<string>
 
   constructor(
     private sapi: SAPI,
@@ -24,20 +24,26 @@ export class SAPIRegion{
     public parcId: string,
     public id: string,
   ){
-    this.prefix = `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.parcId)}/regions/${encodeURIComponent(this.id)}`
+    this.prefix$ = SAPI.BsEndpoint$.pipe(
+      map(endpt => `${endpt}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.parcId)}/regions/${encodeURIComponent(this.id)}`)
+    )
   }
 
   getFeatures(spaceId: string): Observable<(SapiRegionalFeatureModel | CleanedIeegDataset)[]> {
     return merge(
-      this.sapi.httpGet<SapiRegionalFeatureModel[]>(
-        `${this.prefix}/features`,
-        {
-          space_id: spaceId
-        }
-      ).pipe(
-        catchError((err, obs) => {
-          return of([])
-        })
+      this.prefix$.pipe(
+        switchMap(prefix => 
+          this.sapi.httpGet<SapiRegionalFeatureModel[]>(
+            `${prefix}/features`,
+            {
+              space_id: spaceId
+            }
+          ).pipe(
+            catchError((err, obs) => {
+              return of([])
+            })
+          )
+        )
       ),
       spaceId
         ? this.sapi.getSpace(this.atlasId, spaceId).getFeatures({ parcellationId: this.parcId, region: this.id }).pipe(
@@ -56,50 +62,59 @@ export class SAPIRegion{
   }
 
   getFeatureInstance(instanceId: string, spaceId: string = null): Observable<SapiRegionalFeatureModel> {
-    return this.sapi.httpGet<SapiRegionalFeatureModel>(
-      `${this.prefix}/features/${encodeURIComponent(instanceId)}`,
-      {
-        space_id: spaceId
-      }
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.httpGet<SapiRegionalFeatureModel>(
+        `${prefix}/features/${encodeURIComponent(instanceId)}`,
+        {
+          space_id: spaceId
+        }
+      ))
     )
   }
 
   getMapInfo(spaceId: string): Observable<SapiRegionMapInfoModel> {
-    return this.sapi.http.get<SapiRegionMapInfoModel>(
-      `${this.prefix}/regional_map/info`,
-      {
-        params: {
-          space_id: spaceId
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.http.get<SapiRegionMapInfoModel>(
+        `${prefix}/regional_map/info`,
+        {
+          params: {
+            space_id: spaceId
+          }
         }
-      }
+      ))
     )
   }
 
-  getMapUrl(spaceId: string): string {
-    return `${this.prefix}/regional_map/map?space_id=${encodeURI(spaceId)}`
+  getMapUrl(spaceId: string): Observable<string> {
+    return this.prefix$.pipe(
+      map(prefix => `${prefix}/regional_map/map?space_id=${encodeURI(spaceId)}`)
+    )
   }
 
   getVolumes(): Observable<PaginatedResponse<SapiVolumeModel>>{
-    const url = `${this.prefix}/volumes`
-    return this.sapi.httpGet<PaginatedResponse<SapiVolumeModel>>(
-      url
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.httpGet<PaginatedResponse<SapiVolumeModel>>(
+        `${prefix}/volumes`
+      ))
     )
   }
 
   getVolumeInstance(volumeId: string): Observable<SapiVolumeModel> {
-    const url = `${this.prefix}/volumes/${encodeURIComponent(volumeId)}`
-    return this.sapi.httpGet<SapiVolumeModel>(
-      url
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.httpGet<SapiVolumeModel>(
+        `${prefix}/volumes/${encodeURIComponent(volumeId)}`
+      ))
     )
   }
 
   getDetail(spaceId: string): Observable<SapiRegionModel> {
-    const url = `${this.prefix}`
-    return this.sapi.httpGet<SapiRegionModel>(
-      url,
-      {
-        space_id: spaceId
-      }
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.httpGet<SapiRegionModel>(
+        prefix,
+        {
+          space_id: spaceId
+        }
+      ))
     )
   }
 }
diff --git a/src/atlasComponents/sapi/core/sapiSpace.ts b/src/atlasComponents/sapi/core/sapiSpace.ts
index 3effd6f16345aa82eebd3cf94b9f83118fb9546e..5f61ae6f68240437d2a9510b4744d64d16e54977 100644
--- a/src/atlasComponents/sapi/core/sapiSpace.ts
+++ b/src/atlasComponents/sapi/core/sapiSpace.ts
@@ -2,6 +2,7 @@ import { Observable } from "rxjs"
 import { SAPI } from '../sapi.service'
 import { camelToSnake } from 'common/util'
 import {SapiQueryPriorityArg, SapiSpaceModel, SapiSpatialFeatureModel, SapiVolumeModel} from "../type"
+import { map, switchMap } from "rxjs/operators"
 
 type FeatureResponse = {
   features: {
@@ -22,13 +23,21 @@ type SpatialFeatureOpts = RegionalSpatialFeatureOpts | BBoxSpatialFEatureOpts
 
 export class SAPISpace{
 
-  constructor(private sapi: SAPI, public atlasId: string, public id: string){}
+  constructor(private sapi: SAPI, public atlasId: string, public id: string){
+    this.prefix$ = SAPI.BsEndpoint$.pipe(
+      map(endpt => `${endpt}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}`)
+    )
+  }
+
+  private prefix$: Observable<string>
 
   getModalities(param?: SapiQueryPriorityArg): Observable<FeatureResponse> {
-    return this.sapi.httpGet<FeatureResponse>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}/features`,
-      null,
-      param
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.httpGet<FeatureResponse>(
+        `${prefix}/features`,
+        null,
+        param
+      ))
     )
   }
 
@@ -37,9 +46,11 @@ export class SAPISpace{
     for (const [key, value] of Object.entries(opts)) {
       query[camelToSnake(key)] = value
     }
-    return this.sapi.httpGet<SapiSpatialFeatureModel[]>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}/features`,
-      query
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.httpGet<SapiSpatialFeatureModel[]>(
+        `${prefix}/features`,
+        query
+      ))
     )
   }
 
@@ -48,23 +59,29 @@ export class SAPISpace{
     for (const [key, value] of Object.entries(opts)) {
       query[camelToSnake(key)] = value
     }
-    return this.sapi.httpGet<SapiSpatialFeatureModel>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}/features/${encodeURIComponent(instanceId)}`,
-      query
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.httpGet<SapiSpatialFeatureModel>(
+        `${prefix}/features/${encodeURIComponent(instanceId)}`,
+        query
+      ))
     )
   }
 
   getDetail(param?: SapiQueryPriorityArg): Observable<SapiSpaceModel>{
-    return this.sapi.httpGet<SapiSpaceModel>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}`,
-      null,
-      param
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.httpGet<SapiSpaceModel>(
+        `${prefix}`,
+        null,
+        param
+      ))
     )
   }
 
   getVolumes(): Observable<SapiVolumeModel[]>{
-    return this.sapi.httpGet<SapiVolumeModel[]>(
-      `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}/volumes`,
+    return this.prefix$.pipe(
+      switchMap(prefix => this.sapi.httpGet<SapiVolumeModel[]>(
+        `${prefix}/volumes`,
+      ))
     )
   }
 }
diff --git a/src/atlasComponents/sapi/core/space/interSpaceCoordXform.service.ts b/src/atlasComponents/sapi/core/space/interSpaceCoordXform.service.ts
index 1a0c0c690be0eb7615922a8068ffc86422af54fb..8dff3f67d6bbc69939068718270e487ee3bbeb2c 100644
--- a/src/atlasComponents/sapi/core/space/interSpaceCoordXform.service.ts
+++ b/src/atlasComponents/sapi/core/space/interSpaceCoordXform.service.ts
@@ -57,6 +57,12 @@ export class InterSpaceCoordXformSvc {
   // see https://github.com/ngrx/platform/issues/498#issuecomment-337465179
   // in order to properly test with marble, use obs instead of promise
   transform(srcTmplName: ValidTemplateSpaceName, targetTmplName: ValidTemplateSpaceName, coordinatesInNm: [number, number, number]): Observable<ITemplateCoordXformResp> {
+    if (environment.STRICT_LOCAL) {
+      return of({
+        status: 'error',
+        statusText: 'STRICT_LOCAL mode on, will not transform'
+      } as ITemplateCoordXformResp)
+    }
     if (!srcTmplName || !targetTmplName) {
       return of({
         status: 'error',
diff --git a/src/atlasComponents/sapi/core/space/interspaceLinearXform.ts b/src/atlasComponents/sapi/core/space/interspaceLinearXform.ts
new file mode 100644
index 0000000000000000000000000000000000000000..44c93c74186567e3967d2224cb4937ee391de215
--- /dev/null
+++ b/src/atlasComponents/sapi/core/space/interspaceLinearXform.ts
@@ -0,0 +1,60 @@
+export const VALID_LINEAR_XFORM_SRC = {
+  CCF: "Allen Common Coordination Framework"
+}
+
+export const VALID_LINEAR_XFORM_DST = {
+  NEHUBA: "nehuba"
+}
+
+export type TVALID_LINEAR_XFORM_SRC = keyof typeof VALID_LINEAR_XFORM_SRC
+export type TVALID_LINEAR_XFORM_DST = keyof typeof VALID_LINEAR_XFORM_DST
+
+type TLinearXform = number[][]
+
+const _linearXformDict: Record<
+  keyof typeof VALID_LINEAR_XFORM_SRC,
+  Record<
+    keyof typeof VALID_LINEAR_XFORM_DST,
+    TLinearXform
+  >> = {
+    CCF: {
+      NEHUBA: [
+        [-1e3, 0, 0, 11400000 - 5737500], //
+        [0, 0, -1e3, 13200000 - 6637500], //
+        [0, -1e3, 0, 8000000 - 4037500], //
+        [0, 0, 0, 1],
+      ]
+    }
+  }
+
+const defaultXform = [
+  [1e3, 0, 0, 0],
+  [0, 1e3, 0, 0],
+  [0, 0, 1e3, 0],
+  [0, 0, 0, 1],
+]
+
+
+const getProxyXform = <T>(obj: Record<string, T>, cb: (value: T) => T) => new Proxy({}, {
+  get: (_target, prop: string, _receiver) => {
+    return cb(obj[prop])
+  }
+})
+
+export const linearXformDict = getProxyXform(_linearXformDict, (value: Record<string, TLinearXform>) => {
+  if (!value) return getProxyXform({}, () => defaultXform)
+  return getProxyXform(value, (v: TLinearXform) => {
+    if (v) return v
+    return defaultXform
+  })
+}) as Record<
+  keyof typeof VALID_LINEAR_XFORM_SRC,
+  Record<
+    keyof typeof VALID_LINEAR_XFORM_DST,
+    TLinearXform
+  >>
+
+
+export const linearTransform = async (srcTmplName: keyof typeof VALID_LINEAR_XFORM_SRC, targetTmplName: keyof typeof VALID_LINEAR_XFORM_DST) => {
+  return linearXformDict[srcTmplName][targetTmplName]
+}
diff --git a/src/atlasComponents/sapi/features/sapiFeature.ts b/src/atlasComponents/sapi/features/sapiFeature.ts
index 8290da0cad9b4c8e057845258980ff5091579272..f2f341acce3aca72ba481f3eda6c68083320cfe1 100644
--- a/src/atlasComponents/sapi/features/sapiFeature.ts
+++ b/src/atlasComponents/sapi/features/sapiFeature.ts
@@ -1,3 +1,4 @@
+import { switchMap } from "rxjs/operators";
 import { SAPI } from "../sapi.service";
 import { SapiFeatureModel } from "../type";
 
@@ -6,8 +7,10 @@ export class SAPIFeature {
 
   }
 
-  public detail$ = this.sapi.httpGet<SapiFeatureModel>(
-    `${SAPI.BsEndpoint}/features/${this.id}`,
-    this.opts
+  public detail$ = SAPI.BsEndpoint$.pipe(
+    switchMap(endpt => this.sapi.httpGet<SapiFeatureModel>(
+      `${endpt}/features/${this.id}`,
+      this.opts
+    ))
   )
 }
diff --git a/src/atlasComponents/sapi/module.ts b/src/atlasComponents/sapi/module.ts
index 6d0757bb5e771b5bb18e9009bc9032f399fa0c38..9b9efaf0ee92240c0b27b57b5067acf02dcbd972 100644
--- a/src/atlasComponents/sapi/module.ts
+++ b/src/atlasComponents/sapi/module.ts
@@ -1,5 +1,4 @@
-import { APP_INITIALIZER, NgModule } from "@angular/core";
-import { SAPI } from "./sapi.service";
+import { NgModule } from "@angular/core";
 import { CommonModule } from "@angular/common";
 import { HttpClientModule, HTTP_INTERCEPTORS } from "@angular/common/http";
 import { PriorityHttpInterceptor } from "src/util/priority";
@@ -16,16 +15,10 @@ import { MatSnackBarModule } from "@angular/material/snack-bar";
   exports: [
   ],
   providers: [
-    SAPI,
     {
       provide: HTTP_INTERCEPTORS,
       useClass: PriorityHttpInterceptor,
       multi: true
-    },
-    {
-      provide: APP_INITIALIZER,
-      useValue: () => SAPI.SetBsEndPoint(),
-      multi: true
     }
   ]
 })
diff --git a/src/atlasComponents/sapi/sapi.service.spec.ts b/src/atlasComponents/sapi/sapi.service.spec.ts
index 0448de0ccf6c7c2fcb3d5cd587e996db861a7bf2..20233eca63ce2485db0136ee3702cace46659e96 100644
--- a/src/atlasComponents/sapi/sapi.service.spec.ts
+++ b/src/atlasComponents/sapi/sapi.service.spec.ts
@@ -1,10 +1,10 @@
-import { NEVER } from "rxjs"
+import { finalize } from "rxjs/operators"
 import * as env from "src/environments/environment"
 import { SAPI } from "./sapi.service"
 
 describe("> sapi.service.ts", () => {
   describe("> SAPI", () => {
-    describe("#SetBsEndPoint", () => {
+    describe("#BsEndpoint$", () => {
       let fetchSpy: jasmine.Spy
       let environmentSpy: jasmine.Spy
 
@@ -14,16 +14,10 @@ describe("> sapi.service.ts", () => {
       const atlas1 = 'foo'
       const atlas2 = 'bar'
 
-      let originalBsEndpoint: string
-      beforeAll(() => {
-        originalBsEndpoint = SAPI.BsEndpoint
-      })
+      let subscribedVal: string
 
-      afterAll(() => {
-        SAPI.BsEndpoint = originalBsEndpoint
-      })
-      
       beforeEach(() => {
+        SAPI.ClearBsEndPoint()
         fetchSpy = spyOn(window, 'fetch')
         fetchSpy.and.callThrough()
 
@@ -33,43 +27,70 @@ describe("> sapi.service.ts", () => {
         })
       })
 
+
       afterEach(() => {
+        SAPI.ClearBsEndPoint()
         fetchSpy.calls.reset()
         environmentSpy.calls.reset()
+        subscribedVal = null
       })
 
       describe("> first passes", () => {
-        beforeEach(() => {
+        beforeEach(done => {
           const resp = new Response(JSON.stringify([atlas1]), { headers: { 'content-type': 'application/json' }, status: 200 })
-          fetchSpy.and.resolveTo(resp)
+          fetchSpy.and.callFake(async url => {
+            if (url === `${endpt1}/atlases`) {
+              return resp
+            }
+            throw new Error("controlled throw")
+          })
+          SAPI.BsEndpoint$.pipe(
+            finalize(() => done())
+          ).subscribe(val => {
+            subscribedVal = val
+          })
         })
-        it("> should call fetch once", async () => {
-          await SAPI.SetBsEndPoint()
-          expect(fetchSpy).toHaveBeenCalledTimes(1)
-          expect(fetchSpy).toHaveBeenCalledOnceWith(`${endpt1}/atlases`)
+        it("> should call fetch twice", async () => {
+          expect(fetchSpy).toHaveBeenCalledTimes(2)
+          
+          const allArgs = fetchSpy.calls.allArgs()
+          expect(allArgs.length).toEqual(2)
+          expect(allArgs[0]).toEqual([`${endpt1}/atlases`])
+          expect(allArgs[1]).toEqual([`${endpt2}/atlases`])
         })
 
         it("> endpoint should be set", async () => {
-          await SAPI.SetBsEndPoint()
-          expect(SAPI.BsEndpoint).toBe(endpt1)
+          expect(subscribedVal).toBe(endpt1)
+        })
+
+        it("> additional calls should return cached observable", () => {
+
+          expect(fetchSpy).toHaveBeenCalledTimes(2)
+          SAPI.BsEndpoint$.subscribe()
+          SAPI.BsEndpoint$.subscribe()
+
+          expect(fetchSpy).toHaveBeenCalledTimes(2)
         })
       })
 
       describe("> first fails", () => {
-        beforeEach(() => {
-          let counter = 0
-          fetchSpy.and.callFake(async () => {
-            if (counter === 0) {
-              counter ++
+        beforeEach(done => {
+          fetchSpy.and.callFake(async url => {
+            if (url === `${endpt1}/atlases`) {
               throw new Error(`bla`)
             }
             const resp = new Response(JSON.stringify([atlas1]), { headers: { 'content-type': 'application/json' }, status: 200 })
             return resp
           })
+
+          SAPI.BsEndpoint$.pipe(
+            finalize(() => done())
+          ).subscribe(val => {
+            subscribedVal = val
+          })
         })
 
         it("> should call twice", async () => {
-          await SAPI.SetBsEndPoint()
           expect(fetchSpy).toHaveBeenCalledTimes(2)
           expect(fetchSpy.calls.allArgs()).toEqual([
             [`${endpt1}/atlases`],
@@ -78,18 +99,7 @@ describe("> sapi.service.ts", () => {
         })
 
         it('> should set endpt2', async () => {
-          await SAPI.SetBsEndPoint()
-          expect(SAPI.BsEndpoint).toBe(endpt2)
-        })
-
-        it("> instances bsendpoint should be the updated version", async () => {
-          await SAPI.SetBsEndPoint()
-          const mockHttpClient = {
-            get: jasmine.createSpy()
-          }
-          mockHttpClient.get.and.returnValue(NEVER)
-          const sapi = new SAPI(mockHttpClient as any, null, null)
-          expect(sapi.bsEndpoint).toBe(endpt2)
+          expect(subscribedVal).toBe(endpt2)
         })
       })
     })
diff --git a/src/atlasComponents/sapi/sapi.service.ts b/src/atlasComponents/sapi/sapi.service.ts
index 394acc033c64d0b3567024c4f65f1ddc53dfc3c1..ef3dc46e1e4dd37e1b50206f5b070bc562a270e5 100644
--- a/src/atlasComponents/sapi/sapi.service.ts
+++ b/src/atlasComponents/sapi/sapi.service.ts
@@ -1,6 +1,6 @@
 import { Injectable } from "@angular/core";
 import { HttpClient } from '@angular/common/http';
-import { map, shareReplay } from "rxjs/operators";
+import { catchError, filter, map, shareReplay, switchMap, take, tap } from "rxjs/operators";
 import { SAPIAtlas, SAPISpace } from './core'
 import {
   SapiAtlasModel,
@@ -20,7 +20,7 @@ import { MatSnackBar } from "@angular/material/snack-bar";
 import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service";
 import { EnumColorMapName } from "src/util/colorMaps";
 import { PRIORITY_HEADER } from "src/util/priority";
-import { Observable } from "rxjs";
+import { concat, EMPTY, from, merge, Observable, of } from "rxjs";
 import { SAPIFeature } from "./features";
 import { environment } from "src/environments/environment"
 
@@ -29,34 +29,56 @@ export const SIIBRA_API_VERSION = '0.2.2'
 
 type RegistryType = SAPIAtlas | SAPISpace | SAPIParcellation
 
-@Injectable()
+let BS_ENDPOINT_CACHED_VALUE: Observable<string> = null
+
+@Injectable({
+  providedIn: 'root'
+})
 export class SAPI{
 
-  static async SetBsEndPoint() {
-    let idx = 0
-    const siibraApiEndpts = environment.SIIBRA_API_ENDPOINTS.split(',')
-    while (idx < siibraApiEndpts.length) {
-      const url = siibraApiEndpts[idx]
-      try {
-        const resp = await fetch(`${url}/atlases`)
-        const atlases = await resp.json()
-        if (atlases.length > 0) {
-          SAPI.BsEndpoint = url
-          return
-        }
-      } catch (e) {
-        idx ++
-      }
-    }
-    SAPI.ErrorMessage = `It appears all of our mirrors are not working. The viewer may not be working properly...`
+  /**
+   * Used to clear BsEndPoint, so the next static get BsEndpoints$ will
+   * fetch again. Only used for unit test of BsEndpoint$
+   */
+  static ClearBsEndPoint(){
+    BS_ENDPOINT_CACHED_VALUE = null
   }
 
-  static ErrorMessage = null
-  static BsEndpoint = `https://siibra-api-rc.apps.hbp.eu/v2_0`
-
-  get bsEndpoint() {
-    return SAPI.BsEndpoint
+  /**
+   * BsEndpoint$ is designed as a static getter mainly for unit testing purposes.
+   * see usage of BsEndpoint$ and ClearBsEndPoint in sapi.service.spec.ts
+   */
+  static get BsEndpoint$(): Observable<string> {
+    if (!!BS_ENDPOINT_CACHED_VALUE) return BS_ENDPOINT_CACHED_VALUE
+    BS_ENDPOINT_CACHED_VALUE = concat(
+      merge(
+        ...environment.SIIBRA_API_ENDPOINTS.split(',').map(url => {
+          return from((async () => {
+            const resp = await fetch(`${url}/atlases`)
+            const atlases = await resp.json()
+            if (atlases.length == 0) {
+              throw new Error(`atlas length == 0`)
+            }
+            return url
+          })()).pipe(
+            catchError(() => EMPTY)
+          )
+        })
+      ),
+      of(null).pipe(
+        tap(() => {
+          SAPI.ErrorMessage = `It appears all of our mirrors are not working. The viewer may not be working properly...`
+        }),
+        filter(() => false)
+      )
+    ).pipe(
+      take(1),
+      shareReplay(1),
+    )
+    return BS_ENDPOINT_CACHED_VALUE
   }
+
+  static ErrorMessage = null
   
   registry = {
     _map: {} as Record<string, {
@@ -116,7 +138,9 @@ export class SAPI{
   }
 
   getModalities(): Observable<SapiModalityModel[]> {
-    return this.http.get<SapiModalityModel[]>(`${SAPI.BsEndpoint}/modalities`)
+    return SAPI.BsEndpoint$.pipe(
+      switchMap(endpt => this.http.get<SapiModalityModel[]>(`${endpt}/modalities`))
+    )
   }
 
   httpGet<T>(url: string, params?: Record<string, string>, sapiParam?: SapiQueryPriorityArg){
@@ -133,12 +157,13 @@ export class SAPI{
     )
   }
 
-  public atlases$ = this.http.get<SapiAtlasModel[]>(
-    `${this.bsEndpoint}/atlases`,
-    {
-      observe: "response"
-    }
-  ).pipe(
+  public atlases$ = SAPI.BsEndpoint$.pipe(
+    switchMap(endpt => this.http.get<SapiAtlasModel[]>(
+      `${endpt}/atlases`,
+      {
+        observe: "response"
+      }
+    )),
     map(resp => {
       const respVersion = resp.headers.get(SIIBRA_API_VERSION_HEADER_KEY)
       if (respVersion !== SIIBRA_API_VERSION) {
diff --git a/src/atlasComponents/sapi/stories.base.ts b/src/atlasComponents/sapi/stories.base.ts
index 1fa58bef89b562d5be3ad8ef5383c302bb335649..57e28f20da83acf96a247117f70fb921998c3bf0 100644
--- a/src/atlasComponents/sapi/stories.base.ts
+++ b/src/atlasComponents/sapi/stories.base.ts
@@ -67,22 +67,27 @@ export const parcId = {
 }
 
 export async function getAtlases(): Promise<SapiAtlasModel[]> {
-  return await (await fetch(`${SAPI.BsEndpoint}/atlases`)).json() as SapiAtlasModel[]
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases`)).json() as SapiAtlasModel[]
 }
 
 export async function getAtlas(id: string): Promise<SapiAtlasModel>{
-  return await (await fetch(`${SAPI.BsEndpoint}/atlases/${id}`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${id}`)).json()
 }
 
 export async function getParc(atlasId: string, id: string): Promise<SapiParcellationModel>{
-  return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId}/parcellations/${id}`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${atlasId}/parcellations/${id}`)).json()
 }
 export async function getParcRegions(atlasId: string, id: string, spaceId: string): Promise<SapiRegionModel[]>{
-  return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId}/parcellations/${id}/regions?space_id=${encodeURIComponent(spaceId)}`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${atlasId}/parcellations/${id}/regions?space_id=${encodeURIComponent(spaceId)}`)).json()
 }
 
 export async function getSpace(atlasId: string, id: string): Promise<SapiSpaceModel> {
-  return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId}/spaces/${id}`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${atlasId}/spaces/${id}`)).json()
 }
 
 export async function getHumanAtlas(): Promise<SapiAtlasModel> {
@@ -90,7 +95,8 @@ export async function getHumanAtlas(): Promise<SapiAtlasModel> {
 }
 
 export async function getMni152(): Promise<SapiSpaceModel> {
-  return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}`)).json()
 }
 
 export async function getJba29(): Promise<SapiParcellationModel> {
@@ -103,33 +109,41 @@ export async function getJba29Regions(): Promise<SapiRegionModel[]> {
 
 export async function getHoc1Right(spaceId=null): Promise<SapiRegionModel> {
   if (!spaceId) {
-    return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right`)).json()
+    const endPt = await SAPI.BsEndpoint$.toPromise()
+    return await (await fetch(`${endPt}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right`)).json()
   }
-  return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right?space_id=${encodeURIComponent(spaceId)}`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right?space_id=${encodeURIComponent(spaceId)}`)).json()
 }
 
 export async function get44Left(spaceId=null): Promise<SapiRegionModel> {
   if (!spaceId) {
-    return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/area%2044%20left`)).json()
+    const endPt = await SAPI.BsEndpoint$.toPromise()
+    return await (await fetch(`${endPt}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/area%2044%20left`)).json()
   }
-  return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/area%2044%20left?space_id=${encodeURIComponent(spaceId)}`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/area%2044%20left?space_id=${encodeURIComponent(spaceId)}`)).json()
 }
 
 export async function getHoc1RightSpatialFeatures(): Promise<SxplrCleanedFeatureModel[]> {
-  const json: SapiSpatialFeatureModel[] = await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}/features?parcellation_id=2.9&region=hoc1%20right`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  const json: SapiSpatialFeatureModel[] = await (await fetch(`${endPt}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}/features?parcellation_id=2.9&region=hoc1%20right`)).json()
   return cleanIeegSessionDatasets(json.filter(it => it['@type'] === "siibra/features/ieegSession"))
 }
 
 export async function getHoc1RightFeatures(): Promise<SapiRegionalFeatureModel[]> {
-  return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right/features`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right/features`)).json()
 }
 
 export async function getHoc1RightFeatureDetail(featId: string): Promise<SapiRegionalFeatureModel>{
-  return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right/features/${encodeURIComponent(featId)}`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right/features/${encodeURIComponent(featId)}`)).json()
 }
 
 export async function getJba29Features(): Promise<SapiParcellationFeatureModel[]> {
-  return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/features`)).json()
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  return await (await fetch(`${endPt}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/features`)).json()
 }
 
 export async function getBigbrainSpatialFeatures(): Promise<SapiSpatialFeatureModel[]>{
@@ -137,14 +151,16 @@ export async function getBigbrainSpatialFeatures(): Promise<SapiSpatialFeatureMo
     [-1000, -1000, -1000],
     [1000, 1000, 1000]
   ]
-  const url = new URL(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.bigbrain}/features`)
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  const url = new URL(`${endPt}/atlases/${atlasId.human}/spaces/${spaceId.human.bigbrain}/features`)
   url.searchParams.set(`bbox`, JSON.stringify(bbox))
   return await (await fetch(url.toString())).json()
 }
 
 export async function getMni152SpatialFeatureHoc1Right(): Promise<SapiSpatialFeatureModel[]>{
   
-  const url = new URL(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}/features`)
+  const endPt = await SAPI.BsEndpoint$.toPromise()
+  const url = new URL(`${endPt}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}/features`)
   url.searchParams.set(`parcellation_id`, parcId.human.jba29)
   url.searchParams.set("region", 'hoc1 right')
   return await (await fetch(url.toString())).json()
diff --git a/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.component.ts b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.component.ts
index 88614d6194880e18d3e802de65c1660f75557b13..22813095a37d1c1b8b6a12b44003995d8b7e8db5 100644
--- a/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.component.ts
+++ b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.component.ts
@@ -1,4 +1,4 @@
-import { Component, Input, OnChanges, SimpleChanges } from "@angular/core";
+import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from "@angular/core";
 import { SapiDatasetModel } from "src/atlasComponents/sapi";
 import { CONST } from "common/constants"
 
@@ -9,7 +9,8 @@ const RESTRICTED_ACCESS_ID = "https://nexus.humanbrainproject.org/v0/data/minds/
   templateUrl: './dataset.template.html',
   styleUrls: [
     `./dataset.style.css`
-  ]
+  ],
+  changeDetection: ChangeDetectionStrategy.OnPush
 })
 
 export class DatasetView implements OnChanges{
diff --git a/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.template.html b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.template.html
index f9c37f9907d70750b2de1db334ac2c15949db7ca..a04996afbb8d961cba682ed0cd1cc9d5b2940d31 100644
--- a/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.template.html
+++ b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.template.html
@@ -33,7 +33,7 @@
 
     <mat-divider class="sxplr-pl-1" [vertical]="true"></mat-divider>
 
-    <a mat-icon-button *ngFor="let url of dataset.urls" [href]="url.doi | parseDoi" target="_blank">
+    <a mat-icon-button sxplr-hide-when-local *ngFor="let url of dataset.urls" [href]="url.doi | parseDoi" target="_blank">
       <i class="fas fa-external-link-alt"></i>
     </a>
   </mat-card-subtitle>
diff --git a/src/atlasComponents/sapiViews/core/datasets/module.ts b/src/atlasComponents/sapiViews/core/datasets/module.ts
index b295da6bc25f1a9fdc98031a42810ba907013320..d7b43955b58f954fa02bbbf9000982e5ac382b41 100644
--- a/src/atlasComponents/sapiViews/core/datasets/module.ts
+++ b/src/atlasComponents/sapiViews/core/datasets/module.ts
@@ -2,6 +2,7 @@ import { CommonModule } from "@angular/common";
 import { NgModule } from "@angular/core";
 import { MarkdownModule } from "src/components/markdown";
 import { AngularMaterialModule } from "src/sharedModules";
+import { StrictLocalModule } from "src/strictLocal";
 import { SapiViewsUtilModule } from "../../util/module";
 import { DatasetView } from "./dataset/dataset.component";
 
@@ -10,7 +11,8 @@ import { DatasetView } from "./dataset/dataset.component";
     CommonModule,
     AngularMaterialModule,
     MarkdownModule,
-    SapiViewsUtilModule
+    SapiViewsUtilModule,
+    StrictLocalModule,
   ],
   declarations: [
     DatasetView,
diff --git a/src/atlasComponents/sapiViews/core/parcellation/module.ts b/src/atlasComponents/sapiViews/core/parcellation/module.ts
index 91133d0c156a173c9b9ccf063c2e69094bfee3fc..1fe2e70c09d9a1bda3e3fd3a991d76eac75be09b 100644
--- a/src/atlasComponents/sapiViews/core/parcellation/module.ts
+++ b/src/atlasComponents/sapiViews/core/parcellation/module.ts
@@ -4,6 +4,7 @@ import { Store } from "@ngrx/store";
 import { ComponentsModule } from "src/components";
 import { AngularMaterialModule } from "src/sharedModules";
 import { atlasAppearance } from "src/state";
+import { StrictLocalModule } from "src/strictLocal";
 import { DialogModule } from "src/ui/dialogInfo/module";
 import { UtilModule } from "src/util";
 import { SapiViewsUtilModule } from "../../util";
@@ -25,6 +26,7 @@ import { SapiViewsCoreParcellationParcellationTile } from "./tile/parcellation.t
     UtilModule,
     SapiViewsUtilModule,
     DialogModule,
+    StrictLocalModule
   ],
   declarations: [
     SapiViewsCoreParcellationParcellationTile,
diff --git a/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.spec.ts b/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.spec.ts
index 972e411795a3ac9e3a862a3cb61bc7d403f1a105..1d446f159af3a6cb1e52fc322e560a8eca6ad01b 100644
--- a/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.spec.ts
+++ b/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.spec.ts
@@ -3,15 +3,19 @@ import { SAPI } from "src/atlasComponents/sapi/sapi.service"
 import { SapiParcellationModel } from "src/atlasComponents/sapi/type"
 import { getTraverseFunctions } from "./parcellationVersion.pipe"
 
-describe(`parcellationVersion.pipe.ts (endpoint at ${SAPI.BsEndpoint})`, () => {
+describe(`parcellationVersion.pipe.ts`, () => {
   describe("getTraverseFunctions", () => {
     let julichBrainParcellations: SapiParcellationModel[] = []
+    let endpoint: string
     beforeAll(async () => {
-      const res = await fetch(`${SAPI.BsEndpoint}/atlases/${encodeURIComponent(IDS.ATLAES.HUMAN)}/parcellations`)
+      const bsEndPoint = await SAPI.BsEndpoint$.toPromise()
+      endpoint = bsEndPoint
+      const res = await fetch(`${bsEndPoint}/atlases/${encodeURIComponent(IDS.ATLAES.HUMAN)}/parcellations`)
       const arr: SapiParcellationModel[] = await res.json()
       julichBrainParcellations = arr.filter(it => /Julich-Brain Cytoarchitectonic Maps/.test(it.name))
     })
     it("> should be at least 3 parcellations", () => {
+      console.log(`testing against endpoint: ${endpoint}`)
       expect(julichBrainParcellations.length).toBeGreaterThanOrEqual(3)
     })
 
diff --git a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.template.html b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.template.html
index c15b1d11c2b503b4023ec2b46df3d163bb5f7dab..4dac117899e6a081d5d4309053c67d51ee9e714f 100644
--- a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.template.html
+++ b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.template.html
@@ -121,12 +121,13 @@
   <mat-dialog-actions align="start">
     <a *ngFor="let url of parc | parcellationDoiPipe"
       [href]="url"
+      sxplr-hide-when-local
       target="_blank"
       mat-raised-button
       color="primary">
       <div class="fas fa-external-link-alt"></div>
       <span>
-        Explore
+        Dataset Detail
       </span>
     </a>
 
diff --git a/src/atlasComponents/sapiViews/core/region/module.ts b/src/atlasComponents/sapiViews/core/region/module.ts
index 6c30611aa3b4ce8494f21bc60501da87089b9bb6..60fd2425cc9b9f67008b8caa8c386dcf32614bd8 100644
--- a/src/atlasComponents/sapiViews/core/region/module.ts
+++ b/src/atlasComponents/sapiViews/core/region/module.ts
@@ -3,6 +3,7 @@ import { NgModule } from "@angular/core";
 import { MarkdownModule } from "src/components/markdown";
 import { SpinnerModule } from "src/components/spinner";
 import { AngularMaterialModule } from "src/sharedModules";
+import { StrictLocalModule } from "src/strictLocal";
 import { SapiViewsFeaturesModule } from "../../features";
 import { SapiViewsUtilModule } from "../../util/module";
 import { SapiViewsCoreRegionRegionChip } from "./region/chip/region.chip.component";
@@ -19,6 +20,7 @@ import { SapiViewsCoreRegionRegionRich } from "./region/rich/region.rich.compone
     SapiViewsFeaturesModule,
     SpinnerModule,
     MarkdownModule,
+    StrictLocalModule,
   ],
   declarations: [
     SapiViewsCoreRegionRegionListItem,
diff --git a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts
index 79e4d693d5b23cd7a380f26c6cc8e5b922bba450..0d506febda44ca7366f7366a622403b06a488228 100644
--- a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts
+++ b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts
@@ -5,6 +5,7 @@ import { SapiViewsCoreRegionRegionBase } from "../region.base.directive";
 import { ARIA_LABELS, CONST } from 'common/constants'
 import { SapiRegionalFeatureModel } from "src/atlasComponents/sapi";
 import { SAPI } from "src/atlasComponents/sapi/sapi.service";
+import { environment } from "src/environments/environment";
 
 @Component({
   selector: 'sxplr-sapiviews-core-region-region-rich',
@@ -17,7 +18,8 @@ import { SAPI } from "src/atlasComponents/sapi/sapi.service";
 
 export class SapiViewsCoreRegionRegionRich extends SapiViewsCoreRegionRegionBase {
   
-  shouldFetchDetail = true
+  public environment = environment
+  public shouldFetchDetail = true
   public ARIA_LABELS = ARIA_LABELS
   public CONST = CONST
 
diff --git a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.template.html b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.template.html
index d4a9a79d17698288b49f115c83d895459327bd20..953ac5d04f7a1e89c439c33d952c99ce925f3d48 100644
--- a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.template.html
+++ b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.template.html
@@ -42,6 +42,7 @@
         <!-- explore doi -->
         <a *ngFor="let doi of dois"
           [href]="doi | parseDoi"
+          sxplr-hide-when-local
           [matTooltip]="ARIA_LABELS.EXPLORE_DATASET_IN_KG"
           target="_blank"
           mat-icon-button>
@@ -83,6 +84,7 @@
 
   <mat-accordion class="d-block mt-2">
 
+    <!-- desc -->
     <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: {
       title: CONST.DESCRIPTION,
       iconClass: 'fas fa-info',
@@ -94,46 +96,51 @@
 
     </ng-container>
 
-    <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: {
-      title: CONST.REGIONAL_FEATURES,
-      iconClass: 'fas fa-database',
-      content: kgRegionalFeatureList,
-      desc: '',
-      iconTooltip: 'Regional Features',
-      iavNgIf: true
-    }">
-    </ng-container>
-
-    <!-- connectivity -->
-    <ng-template #sxplrSapiviewsFeaturesConnectivityBrowser>
-      <sxplr-sapiviews-features-connectivity-browser
-        class="pe-all flex-shrink-1"
-        [region]="region"
-        [types]="hasConnectivityDirective.availableModalities"
-        [defaultProfile]="hasConnectivityDirective.defaultProfile"
-        [sxplr-sapiviews-features-connectivity-browser-atlas]="atlas"
-        [sxplr-sapiviews-features-connectivity-browser-parcellation]="parcellation"
-        [accordionExpanded]="expandedPanel === CONST.CONNECTIVITY"
-      >
-      </sxplr-sapiviews-features-connectivity-browser>
-    </ng-template>
+    <!-- only show dynamic data when strict-local is set to false -->
+    <ng-template [ngIf]="!environment.STRICT_LOCAL">
+
+      <!-- feature list -->
+      <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: {
+        title: CONST.REGIONAL_FEATURES,
+        iconClass: 'fas fa-database',
+        content: kgRegionalFeatureList,
+        desc: '',
+        iconTooltip: 'Regional Features',
+        iavNgIf: true
+      }">
+      </ng-container>
 
-    <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: {
-      title: CONST.CONNECTIVITY,
-      iconClass: 'fab fa-connectdevelop',
-      content: sxplrSapiviewsFeaturesConnectivityBrowser,
-      desc: hasConnectivityDirective.connectivityNumber,
-      iconTooltip: hasConnectivityDirective.connectivityNumber + 'Connections',
-      iavNgIf: hasConnectivityDirective.hasConnectivity
-    }">
-    </ng-container>
+      <!-- connectivity -->
+      <ng-template #sxplrSapiviewsFeaturesConnectivityBrowser>
+        <sxplr-sapiviews-features-connectivity-browser
+          class="pe-all flex-shrink-1"
+          [region]="region"
+          [types]="hasConnectivityDirective.availableModalities"
+          [defaultProfile]="hasConnectivityDirective.defaultProfile"
+          [sxplr-sapiviews-features-connectivity-browser-atlas]="atlas"
+          [sxplr-sapiviews-features-connectivity-browser-parcellation]="parcellation"
+          [accordionExpanded]="expandedPanel === CONST.CONNECTIVITY"
+        >
+        </sxplr-sapiviews-features-connectivity-browser>
+      </ng-template>
+
+      <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: {
+        title: CONST.CONNECTIVITY,
+        iconClass: 'fab fa-connectdevelop',
+        content: sxplrSapiviewsFeaturesConnectivityBrowser,
+        desc: hasConnectivityDirective.connectivityNumber,
+        iconTooltip: hasConnectivityDirective.connectivityNumber + 'Connections',
+        iavNgIf: hasConnectivityDirective.hasConnectivity
+      }">
+      </ng-container>
 
-    <div sxplr-sapiviews-features-connectivity-check
-        [sxplr-sapiviews-features-connectivity-check-atlas]="atlas"
-        [sxplr-sapiviews-features-connectivity-check-parcellation]="parcellation"
-        [region]="region"
-        #hasConnectivityDirective="hasConnectivityDirective">
-    </div>
+      <div sxplr-sapiviews-features-connectivity-check
+          [sxplr-sapiviews-features-connectivity-check-atlas]="atlas"
+          [sxplr-sapiviews-features-connectivity-check-parcellation]="parcellation"
+          [region]="region"
+          #hasConnectivityDirective="hasConnectivityDirective">
+      </div>
+    </ng-template>
 
   </mat-accordion>
 
diff --git a/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.stories.ts b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.stories.ts
index 8dbfce6e38bba1201cdd7d94adc310c0381cf1de..5dca7cbc394a3baa2360349fb9aafa7c08d6c5bc 100644
--- a/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.stories.ts
+++ b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.stories.ts
@@ -117,10 +117,16 @@ const asyncLoader = async () => {
     const atlasDetail = await getAtlas(atlasId[species])
     regionsDict[species] = {}
     
-    for (const parc of atlasDetail.parcellations) {
-      const parcDetail = await getParc(atlasDetail['@id'], parc['@id'])
-      regionsDict[species][parcDetail.name] = await getParcRegions(atlasDetail['@id'], parc['@id'], atlasDetail.spaces[0]["@id"] )
-    }
+    await Promise.all(
+      atlasDetail.parcellations.map(async parc => {
+        try {
+          const parcDetail = await getParc(atlasDetail['@id'], parc['@id'])
+          regionsDict[species][parcDetail.name] = await getParcRegions(atlasDetail['@id'], parc['@id'], atlasDetail.spaces[0]["@id"] )
+        } catch (e) {
+          console.warn(`fetching region detail for ${parc["@id"]} failed... Skipping...`)
+        }
+      })
+    )
   }
 
   return {
diff --git a/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component.ts b/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component.ts
index 23d89f519a537c5a296ba90bd1dfd771bc17a3d5..2399e2b668ab21186da82384bd0f2138ec095ff2 100644
--- a/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component.ts
+++ b/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component.ts
@@ -216,10 +216,8 @@ export class ConnectivityBrowserComponent implements AfterViewInit, OnDestroy {
     // ToDo this temporary fix is for the bug existing on siibra api https://github.com/FZJ-INM1-BDA/siibra-api/issues/100
     private fixDatasetFormat = (ds) =>  ds.name.includes('{')? ({
       ...ds,
-      name: ds.name.substr(0, ds.name.indexOf('{')),
-      dataset: JSON.parse(ds.name.substring(ds.name.indexOf('{')).replace(/'/g, '"'))
+      ...JSON.parse(ds.name.substring(ds.name.indexOf('{')).replace(/'/g, '"'))
     }) : ds
-    
 
     fetchConnectivity() {
       this.sapi.getParcellation(this.atlas["@id"], this.parcellation["@id"]).getFeatureInstance(this.selectedDataset['@id'])
@@ -232,7 +230,7 @@ export class ConnectivityBrowserComponent implements AfterViewInit, OnDestroy {
           this.fetching = false
         })
     }
-    
+
     setMatrixData(data) {
       const matrixData = data as SapiParcellationFeatureMatrixModel
       this.regionIndexInMatrix = (matrixData.columns as Array<string>).findIndex(md => md === this.regionName)
@@ -248,7 +246,7 @@ export class ConnectivityBrowserComponent implements AfterViewInit, OnDestroy {
         .then(matrix => {
           const regionProfile = matrix.rawArray[this.regionIndexInMatrix]
 
-          const maxStrength = Math.max(...regionProfile)  
+          const maxStrength = Math.max(...regionProfile)
           this.logChecked = maxStrength > 1
           this.logDisabled = maxStrength <= 1
 
@@ -264,7 +262,7 @@ export class ConnectivityBrowserComponent implements AfterViewInit, OnDestroy {
         })
     }
 
-    
+
     changeLog(checked: boolean) {
       this.logChecked = checked
       this.connectedAreas.next(this.formatConnections(this.pureConnections))
@@ -272,13 +270,18 @@ export class ConnectivityBrowserComponent implements AfterViewInit, OnDestroy {
       this.setCustomLayer()
     }
 
-    //ToDo navigateRegion action does not work any more
+    //ToDo bestViewPoint is null for the most cases
     navigateToRegion(region: SapiRegionModel) {
-      this.store$.dispatch(
-        atlasSelection.actions.navigateToRegion({
-          region
-        })
-      )
+      const regionCentroid = this.region.hasAnnotation?.bestViewPoint?.coordinates
+      if (regionCentroid)
+        this.store$.dispatch(
+          atlasSelection.actions.navigateTo({
+            navigation: {
+              position: regionCentroid.map(v => v.value*1e6),
+            },
+            animation: true
+          })
+        )
     }
 
     getRegionWithName(region: string) {
diff --git a/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.template.html b/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.template.html
index 724f131a5ebcb37af35bbee1a3c30f74b57d345a..fae388859e866bb9de1439337ef741d41ede85fa 100644
--- a/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.template.html
+++ b/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.template.html
@@ -62,10 +62,11 @@
         theme="dark">
     </hbp-connectivity-matrix-row>
     <div *ngIf="noConnectivityForRegion">No connectivity for the region.</div>
+
     <full-connectivity-grid #fullConnectivityGrid
                             [matrix]="matrixString"
-                            [datasetName]="selectedDataset?.dataset?.name"
-                            [datasetDescription]="selectedDataset?.dataset?.description"
+                            [datasetName]="selectedDataset?.name"
+                            [datasetDescription]="selectedDataset?.description"
                             only-export="true">
     </full-connectivity-grid>
 
diff --git a/src/atlasComponents/sapiViews/features/receptors/module.ts b/src/atlasComponents/sapiViews/features/receptors/module.ts
index d5bd04dc6b11f71c1646b4c8b2892c6ddf372f6b..34f29d7f07be5b5df7ddfaad65d9f2c6423fbb04 100644
--- a/src/atlasComponents/sapiViews/features/receptors/module.ts
+++ b/src/atlasComponents/sapiViews/features/receptors/module.ts
@@ -32,21 +32,6 @@ import { Profile } from "./profile/profile.component"
     Profile,
     Entry,
   ],
-  providers: [{
-    provide: APP_INITIALIZER,
-    multi: true,
-    useFactory: (appendScriptFn: (url: string) => Promise<any>) => {
-
-      const libraries = [
-        'https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js',
-        'https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.1.2/es5/tex-svg.js'
-      ]
-      return () => Promise.all(libraries.map(appendScriptFn))
-    },
-    deps: [
-      APPEND_SCRIPT_TOKEN
-    ]
-  }],
   schemas: [
     CUSTOM_ELEMENTS_SCHEMA,
   ]
diff --git a/src/atlasComponents/userAnnotations/filterAnnotationBySpace.pipe.ts b/src/atlasComponents/userAnnotations/filterAnnotationBySpace.pipe.ts
index fb6500281577a0791f6c82b7d53ce6b7aef636a6..d339398c0a55865d2a4fce6e2feb9246e4a5606b 100644
--- a/src/atlasComponents/userAnnotations/filterAnnotationBySpace.pipe.ts
+++ b/src/atlasComponents/userAnnotations/filterAnnotationBySpace.pipe.ts
@@ -14,7 +14,7 @@ export class FilterAnnotationsBySpace implements PipeTransform{
   public transform(annotations: IAnnotationGeometry[], space: { '@id': string }, opts?: TOpts): IAnnotationGeometry[]{
     const { reverse = false } = opts || {}
     return reverse
-      ? annotations.filter(ann => ann.space["@id"] !== space["@id"])
-      : annotations.filter(ann => ann.space["@id"] === space["@id"])
+      ? annotations.filter(ann => ann.space?.["@id"] !== space?.["@id"])
+      : annotations.filter(ann => ann.space?.["@id"] === space?.["@id"])
   }
-}
\ No newline at end of file
+}
diff --git a/src/environments/environment.common.ts b/src/environments/environment.common.ts
index 4f410904c371a0239abecd0e602153b1680fdba3..129a0fe9e32d74a76fcc365fdc33a48e81604afc 100644
--- a/src/environments/environment.common.ts
+++ b/src/environments/environment.common.ts
@@ -17,4 +17,6 @@ export const environment = {
 
   // experimental feature flag
   EXPERIMENTAL_FEATURE_FLAG: false,
+
+  ENABLE_LEAP_MOTION: false,
 }
diff --git a/src/environments/parseEnv.js b/src/environments/parseEnv.js
index 9533d341158797f5da8a604929d9327e8980545f..a9dd24424e3eb5ad2ef58b2396ac793461d8dd78 100644
--- a/src/environments/parseEnv.js
+++ b/src/environments/parseEnv.js
@@ -2,7 +2,24 @@ const fs = require('fs')
 const path = require('path')
 const { promisify } = require('util')
 const asyncWrite = promisify(fs.writeFile)
+const asyncReadFile = promisify(fs.readFile)
 const process = require("process")
+const { exec } = require("child_process")
+
+
+const getGitHead = () => new Promise((rs, rj) => {
+  exec(`git rev-parse --short HEAD`, (err, stdout, stderr) => {
+    if (err) return rj(err)
+    if (stderr) return rj(stderr)
+    rs(stdout)
+  })
+})
+
+const getVersion = async () => {
+  const content = await asyncReadFile("./package.json", "utf-8")
+  const { version } = JSON.parse(content)
+  return version
+}
 
 const main = async () => {
   const target = process.argv[2] || './environment.prod.ts'
@@ -13,27 +30,41 @@ const main = async () => {
     MATOMO_URL,
     MATOMO_ID,
     SIIBRA_API_ENDPOINTS,
-    VERSION,
-    GIT_HASH = 'unknown hash',
-    EXPERIMENTAL_FEATURE_FLAG
+    EXPERIMENTAL_FEATURE_FLAG,
+    ENABLE_LEAP_MOTION,
   } = process.env
   
+  const version = JSON.stringify(
+    await (async () => {
+      try {
+        return await getVersion()
+      } catch (e) {
+        return "unknown version"
+      }
+    })()
+  )
+  const gitHash = JSON.stringify(
+    await (async () => {
+      try {
+        return await getGitHead()
+      } catch (e) {
+        return "unknown git hash"
+      }
+    })()
+  )
+
   console.log(`[parseEnv.js] parse envvar:`, {
     BACKEND_URL,
     STRICT_LOCAL,
     MATOMO_URL,
     MATOMO_ID,
     SIIBRA_API_ENDPOINTS,
-    VERSION,
-    GIT_HASH,
     EXPERIMENTAL_FEATURE_FLAG,
+    ENABLE_LEAP_MOTION,
+
+    VERSION: version,
+    GIT_HASH: gitHash,
   })
-  const version = JSON.stringify(
-    VERSION || 'unknown version'
-  )
-  const gitHash = JSON.stringify(
-    GIT_HASH || 'unknown hash'
-  )
 
   const outputTxt = `
 import { environment as commonEnv } from './environment.common'
@@ -43,10 +74,11 @@ export const environment = {
   VERSION: ${version},
   SIIBRA_API_ENDPOINTS: ${JSON.stringify(SIIBRA_API_ENDPOINTS)},
   BACKEND_URL: ${JSON.stringify(BACKEND_URL)},
-  STRICT_LOCAL: ${JSON.stringify(STRICT_LOCAL)},
+  STRICT_LOCAL: ${STRICT_LOCAL},
   MATOMO_URL: ${JSON.stringify(MATOMO_URL)},
   MATOMO_ID: ${JSON.stringify(MATOMO_ID)},
-  EXPERIMENTAL_FEATURE_FLAG: ${EXPERIMENTAL_FEATURE_FLAG}
+  EXPERIMENTAL_FEATURE_FLAG: ${EXPERIMENTAL_FEATURE_FLAG},
+  ENABLE_LEAP_MOTION: ${ENABLE_LEAP_MOTION}
 }
 `
   await asyncWrite(pathToEnvFile, outputTxt, 'utf-8')
diff --git a/src/index.html b/src/index.html
index 56e924d2b7181e2344f5bb912174a8013a52bde2..3a856105768123945f81f4837560f75d0bdf8f46 100644
--- a/src/index.html
+++ b/src/index.html
@@ -16,6 +16,8 @@
   <script src="https://unpkg.com/three-surfer@0.0.11/dist/bundle.js" defer></script>
   <script type="module" src="https://unpkg.com/ng-layer-tune@0.0.6/dist/ng-layer-tune/ng-layer-tune.esm.js"></script>
   <script type="module" src="https://unpkg.com/hbp-connectivity-component@0.6.2/dist/connectivity-component/connectivity-component.js" ></script>
+  <script defer src="https://unpkg.com/mathjax@3.1.2/es5/tex-svg.js"></script>
+  <script defer src="https://unpkg.com/d3@6.2.0/dist/d3.min.js"></script>
   <title>Siibra Explorer</title>
 </head>
 <body>
diff --git a/src/plugin/request.md b/src/plugin/request.md
index 487c1344f3d785978a99eb61f1ec94ca6b31904e..47daf4525b3873b10f93a87d75464e0e8cb97f13 100644
--- a/src/plugin/request.md
+++ b/src/plugin/request.md
@@ -9,7 +9,7 @@ Be it request the user to select a region, a point, navigate to a specific locat
 ```javascript
 
 let parentWindow
-window.addEventListener('message', ev => {
+window.addEventListener('message', msg => {
   const { source, data, origin } = msg
   const { id, method, params, result, error } = data
 
diff --git a/src/plugin/service.ts b/src/plugin/service.ts
index 19f183189fac54e16b9b5a33be58de632276327a..e4fe49a725535ac36d54daf87b4f79a0aae3f698 100644
--- a/src/plugin/service.ts
+++ b/src/plugin/service.ts
@@ -6,6 +6,7 @@ import { WidgetPortal } from "src/widget/widgetPortal/widgetPortal.component";
 import { setPluginSrc, SET_PLUGIN_NAME } from "./const";
 import { PluginPortal } from "./pluginPortal/pluginPortal.component";
 import { environment } from "src/environments/environment"
+import { startWith } from "rxjs/operators";
 
 @Injectable({
   providedIn: 'root'
@@ -25,7 +26,9 @@ export class PluginService {
     'siibra-explorer': true
     name: string
     iframeUrl: string
-  }[]>(`${environment.BACKEND_URL || ''}plugins/manifests`)
+  }[]>(`${environment.BACKEND_URL || ''}plugins/manifests`).pipe(
+    startWith([])
+  )
 
   async launchPlugin(htmlSrc: string){
     if (this.loadedPlugins.includes(htmlSrc)) return
diff --git a/src/routerModule/cipher.ts b/src/routerModule/cipher.ts
index 9ad21bc8c036336c1bc71f4a2dc0122603a3a681..e98d9b3f39fb8f2193c64da6094fbe0db0fae868 100644
--- a/src/routerModule/cipher.ts
+++ b/src/routerModule/cipher.ts
@@ -10,84 +10,22 @@
  * So performance is not really that important (Also, need to learn bitwise operation)
  */
 
-const cipher = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-'
-export const separator = "."
-const negString = '~'
 
-const encodeInt = (number: number) => {
-  if (number % 1 !== 0) { throw new Error('cannot encodeInt on a float. Ensure float flag is set') }
-  if (isNaN(Number(number)) || number === null || number === Number.POSITIVE_INFINITY) { throw new Error('The input is not valid') }
-
-  let residual: number
-  let result = ''
-
-  if (number < 0) {
-    result += negString
-    residual = Math.floor(number * -1)
-  } else {
-    residual = Math.floor(number)
-  }
-
-  /* eslint-disable-next-line no-constant-condition */
-  while (true) {
-    result = cipher.charAt(residual % 64) + result
-    residual = Math.floor(residual / 64)
-
-    if (residual === 0) {
-      break
-    }
-  }
-  return result
-}
-
-interface IB64EncodingOption {
-  float: boolean
+import { sxplrNumB64Enc } from "common/util"
+const {
+  separator,
+  cipher,
+  encodeNumber,
+  decodeToNumber,
+} = sxplrNumB64Enc
+
+export {
+  separator,
+  cipher,
+  encodeNumber,
+  decodeToNumber,
 }
 
-const defaultB64EncodingOption = {
-  float: false,
-}
-
-export const encodeNumber:
-  (number: number, option?: IB64EncodingOption) => string =
-  (number: number, { float = false }: IB64EncodingOption = defaultB64EncodingOption) => {
-    if (!float) { return encodeInt(number) } else {
-      const floatArray = new Float32Array(1)
-      floatArray[0] = number
-      const intArray = new Uint32Array(floatArray.buffer)
-      const castedInt = intArray[0]
-      return encodeInt(castedInt)
-    }
-  }
-
-const decodetoInt = (encodedString: string) => {
-  let _encodedString
-  let negFlag = false
-  if (encodedString.slice(-1) === negString) {
-    negFlag = true
-    _encodedString = encodedString.slice(0, -1)
-  } else {
-    _encodedString = encodedString
-  }
-  return (negFlag ? -1 : 1) * [..._encodedString].reduce((acc, curr) => {
-    const index = cipher.indexOf(curr)
-    if (index < 0) { throw new Error(`Poisoned b64 encoding ${encodedString}`) }
-    return acc * 64 + index
-  }, 0)
-}
-
-export const decodeToNumber:
-  (encodedString: string, option?: IB64EncodingOption) => number =
-  (encodedString: string, {float = false} = defaultB64EncodingOption) => {
-    if (!float) { return decodetoInt(encodedString) } else {
-      const _int = decodetoInt(encodedString)
-      const intArray = new Uint32Array(1)
-      intArray[0] = _int
-      const castedFloat = new Float32Array(intArray.buffer)
-      return castedFloat[0]
-    }
-  }
-
 /**
  * see https://stackoverflow.com/questions/53051415/can-you-disable-auxiliary-secondary-routes-in-angular
  * need to encode brackets
diff --git a/src/share/share.module.ts b/src/share/share.module.ts
index f3137eaadc83a3aaead101efe1b76cc600301409..ff7011249173857ff344bd58730f7e7881f9e675 100644
--- a/src/share/share.module.ts
+++ b/src/share/share.module.ts
@@ -8,6 +8,7 @@ import { ReactiveFormsModule, FormsModule } from "@angular/forms";
 import { AuthModule } from "src/auth";
 import { ShareSheetComponent } from "./shareSheet/shareSheet.component";
 import { ShareDirective } from "./share.directive";
+import { StateModule } from "src/state";
 
 @NgModule({
   imports: [
@@ -17,6 +18,7 @@ import { ShareDirective } from "./share.directive";
     FormsModule,
     ReactiveFormsModule,
     AuthModule,
+    StateModule,
   ],
   declarations: [
     ClipboardCopy,
diff --git a/src/share/shareSheet/shareSheet.component.ts b/src/share/shareSheet/shareSheet.component.ts
index 8d4b9f963ebafc0b83850010bf8b18e70f7d6a15..664ffd89c8fe93bf1a7886d80bc83145e7c8b03a 100644
--- a/src/share/shareSheet/shareSheet.component.ts
+++ b/src/share/shareSheet/shareSheet.component.ts
@@ -1,7 +1,6 @@
-import { Component } from "@angular/core";
+import { Component, TemplateRef } from "@angular/core";
 import { MatDialog } from "@angular/material/dialog";
 import { ARIA_LABELS } from 'common/constants'
-import { SaneUrl } from "../saneUrl/saneUrl.component"
 
 @Component({
   selector: 'sxplr-share-sheet',
@@ -18,7 +17,7 @@ export class ShareSheetComponent{
 
   }
 
-  openShareSaneUrl(){
-    this.dialog.open(SaneUrl, { ariaLabel: ARIA_LABELS.SHARE_CUSTOM_URL })
+  openDialog(templateRef: TemplateRef<unknown>){
+    this.dialog.open(templateRef, { ariaLabel: ARIA_LABELS.SHARE_CUSTOM_URL })
   }
 }
diff --git a/src/share/shareSheet/shareSheet.template.html b/src/share/shareSheet/shareSheet.template.html
index cea5242302cf57ff1cc718ac1b2d98b7a9d79f0c..bc953043ba622d6af44df97a27215346bd03521b 100644
--- a/src/share/shareSheet/shareSheet.template.html
+++ b/src/share/shareSheet/shareSheet.template.html
@@ -17,8 +17,10 @@
   <mat-list-item
     [attr.aria-label]="ARIA_LABELS.SHARE_CUSTOM_URL"
     [attr.tab-index]="10"
-    (click)="openShareSaneUrl()"
+    (click)="openDialog(saneUrlTmpl)"
     [matTooltip]="ARIA_LABELS.SHARE_CUSTOM_URL"
+    iav-state-aggregator
+    #stateAggregator="iavStateAggregator"
     >
     <mat-icon
       class="mr-4"
@@ -32,3 +34,9 @@
 
   </mat-list-item>
 </mat-nav-list>
+
+
+<ng-template #saneUrlTmpl>
+  <iav-sane-url [stateTobeSaved]="stateAggregator.jsonifiedState$ | async">
+  </iav-sane-url>
+</ng-template>
\ No newline at end of file
diff --git a/src/strictLocal/index.ts b/src/strictLocal/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..df38eaa22fe65187cca51189b4442684509278ed
--- /dev/null
+++ b/src/strictLocal/index.ts
@@ -0,0 +1,2 @@
+export { StrictLocalModule } from "./module"
+export { HideWhenLocal } from "./strictLocal.directive"
\ No newline at end of file
diff --git a/src/strictLocal/module.ts b/src/strictLocal/module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..62db4e8d888eb11107503bcb5b1ce8145b8a3d61
--- /dev/null
+++ b/src/strictLocal/module.ts
@@ -0,0 +1,23 @@
+import { CommonModule } from "@angular/common";
+import { NgModule } from "@angular/core";
+import { MatButtonModule } from "@angular/material/button";
+import { MatTooltipModule } from "@angular/material/tooltip";
+import { HideWhenLocal } from "./strictLocal.directive";
+import { StrictLocalInfo } from "./strictLocalCmp/strictLocalCmp.component";
+
+@NgModule({
+  declarations: [
+    HideWhenLocal,
+    StrictLocalInfo,
+  ],
+  imports: [
+    CommonModule,
+    MatTooltipModule,
+    MatButtonModule,
+  ],
+  exports: [
+    HideWhenLocal,
+  ]
+})
+
+export class StrictLocalModule{}
diff --git a/src/strictLocal/strictLocal.directive.ts b/src/strictLocal/strictLocal.directive.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b5f0c83ad82310ec40b64906c3f6d5c266fb7a2f
--- /dev/null
+++ b/src/strictLocal/strictLocal.directive.ts
@@ -0,0 +1,22 @@
+import { ComponentFactoryResolver, Directive, HostBinding, ViewContainerRef } from "@angular/core";
+import { environment } from "src/environments/environment"
+import { StrictLocalInfo } from "./strictLocalCmp/strictLocalCmp.component";
+
+@Directive({
+  selector: '[sxplr-hide-when-local]',
+  exportAs: 'hideWhenLocal'
+})
+
+export class HideWhenLocal {
+  @HostBinding('style.display')
+  hideWhenLocal = environment.STRICT_LOCAL ? 'none!important' : null
+  constructor(
+    private vc: ViewContainerRef,
+    private cfr: ComponentFactoryResolver,
+  ){
+    if (environment.STRICT_LOCAL) {
+      const cf = this.cfr.resolveComponentFactory(StrictLocalInfo)
+      this.vc.createComponent(cf)
+    }
+  }
+}
diff --git a/src/strictLocal/strictLocalCmp/strictLocalCmp.component.ts b/src/strictLocal/strictLocalCmp/strictLocalCmp.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e75b84c2b782a501996b66ca5a2be98687accda7
--- /dev/null
+++ b/src/strictLocal/strictLocalCmp/strictLocalCmp.component.ts
@@ -0,0 +1,13 @@
+import { Component } from "@angular/core";
+
+@Component({
+  selector: `strict-local-info`,
+  template: `
+  <button mat-icon-button [matTooltip]="tooltip" tabindex="-1">
+    <i class="fas fa-unlink"></i>
+  </button>`,
+})
+
+export class StrictLocalInfo{
+  tooltip = "External links are hidden in strict local mode."
+}
diff --git a/src/ui/help/about/about.template.html b/src/ui/help/about/about.template.html
index 9a9169afd9eb25c1897e14b7dd950a5b55e4d212..380dbf4d2d83fbb0693857a00ac2929e13598905 100644
--- a/src/ui/help/about/about.template.html
+++ b/src/ui/help/about/about.template.html
@@ -1,7 +1,7 @@
 <div class="container-fluid">
   <div class="row mt-4 mb-4">
 
-    <a [href]="userDoc" target="_blank">
+    <a sxplr-hide-when-local [href]="userDoc" target="_blank">
       <button mat-raised-button color="primary">
         <i class="fas fa-book-open"></i>
         <span>
@@ -10,7 +10,7 @@
       </button>
     </a>
 
-    <a [href]="repoUrl" target="_blank">
+    <a sxplr-hide-when-local [href]="repoUrl" target="_blank">
       <button mat-flat-button>
         <i class="fab fa-github"></i>
         <span>
diff --git a/src/ui/help/module.ts b/src/ui/help/module.ts
index b99786401f97aad174c2e5db0398223d377be5c7..5dded09484db45d2ff96e81ac3502f11ae69f737 100644
--- a/src/ui/help/module.ts
+++ b/src/ui/help/module.ts
@@ -7,6 +7,7 @@ import { AboutCmp } from './about/about.component'
 import { HelpOnePager } from "./helpOnePager/helpOnePager.component";
 import {QuickTourModule} from "src/ui/quickTour/module";
 import { HowToCite } from "./howToCite/howToCite.component";
+import { StrictLocalModule } from "src/strictLocal";
 
 @NgModule({
   imports: [
@@ -14,7 +15,8 @@ import { HowToCite } from "./howToCite/howToCite.component";
     AngularMaterialModule,
     ComponentsModule,
     UtilModule,
-    QuickTourModule
+    QuickTourModule,
+    StrictLocalModule,
   ],
   declarations: [
     AboutCmp,
diff --git a/src/util/constants.ts b/src/util/constants.ts
index 9b1700442a02984d4b5a15c73b122d5bcc0cf8ed..7879151a46014b4576f159e55d118cce75aa748c 100644
--- a/src/util/constants.ts
+++ b/src/util/constants.ts
@@ -32,9 +32,12 @@ export const MIN_REQ_EXPLAINER = `
 
 export const APPEND_SCRIPT_TOKEN: InjectionToken<(url: string) => Promise<HTMLScriptElement>> = new InjectionToken(`APPEND_SCRIPT_TOKEN`)
 
-export const appendScriptFactory = (document: Document) => {
+export const appendScriptFactory = (document: Document, defer: boolean = false) => {
   return src => new Promise((rs, rj) => {
     const scriptEl = document.createElement('script')
+    if (defer) {
+      scriptEl.defer = true
+    }
     scriptEl.src = src
     scriptEl.onload = () => rs(scriptEl)
     scriptEl.onerror = (e) => rj(e)
diff --git a/src/viewerModule/leap/index.ts b/src/viewerModule/leap/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/viewerModule/leap/leapSignal/leapSignal.component.ts b/src/viewerModule/leap/leapSignal/leapSignal.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..046998972b022e782e14199028fe144160174363
--- /dev/null
+++ b/src/viewerModule/leap/leapSignal/leapSignal.component.ts
@@ -0,0 +1,40 @@
+import { Component } from "@angular/core";
+import { map } from "rxjs/operators";
+import { LeapService } from "../service";
+import { HandShape } from "../service"
+
+@Component({
+  selector: 'leap-control-signal',
+  templateUrl: './leapSignal.template.html',
+  styleUrls: [
+    './leapSignal.style.css'
+  ]
+})
+export class LeapSignal{
+  public ready$ = this.svc.leapReady$
+  public handDetected$ = this.svc.hand$.pipe(
+    map(hands => hands && hands.length > 0)
+  )
+  public gestureText$ = this.svc.gesture$.pipe(
+    map(gesture => {
+      if (gesture === HandShape.PINCHING) {
+        return "Pinching"
+      }
+      if (gesture === HandShape.PALM_FORWARD) {
+        return "Zooming"
+      }
+      if (gesture === HandShape.POINTING_ONE_FINGER) {
+        return "Translating"
+      }
+      if (gesture === HandShape.POINTING_TWO_FINGERS) {
+        return "Oblique Cutting"
+      }
+      return null
+    })
+  )
+  constructor(
+    private svc: LeapService
+  ){
+
+  }
+}
diff --git a/src/viewerModule/leap/leapSignal/leapSignal.style.css b/src/viewerModule/leap/leapSignal/leapSignal.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..1ca31c86031d27db3c0013d09af57a1e74cacf9c
--- /dev/null
+++ b/src/viewerModule/leap/leapSignal/leapSignal.style.css
@@ -0,0 +1,19 @@
+.leap-spinner
+{
+  animation: spinning 1400ms linear infinite running;
+}
+
+@keyframes spinning
+{
+  from {
+    transform: rotate(0deg);
+  }
+  to{
+    transform: rotate(359deg);
+  }
+}
+
+.leap-muted
+{
+  opacity: 0.5;
+}
diff --git a/src/viewerModule/leap/leapSignal/leapSignal.template.html b/src/viewerModule/leap/leapSignal/leapSignal.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..3a0c1dfb42086f2a47c227572129d61db50187f2
--- /dev/null
+++ b/src/viewerModule/leap/leapSignal/leapSignal.template.html
@@ -0,0 +1,30 @@
+<ng-template [ngIf]="ready$ | async">
+  <ng-template [ngTemplateOutlet]="leapCtrlTmpl"></ng-template>
+</ng-template>
+
+
+<ng-template #leapCtrlTmpl>
+
+  <mat-card>
+    <span *ngIf="gestureText$ | async as text">
+      {{ text }}
+    </span>
+    <!-- detecting hand -->
+    <ng-template [ngTemplateOutlet]="(handDetected$ | async) ? handFound : noHandFound">
+    </ng-template>
+  </mat-card>
+  
+  <ng-template #handFound>
+    <span class="fa-stack fa-2x">
+      <i class="fas fa-hand-paper fa-stack-1x"></i>
+    </span>
+  </ng-template>
+  
+  <ng-template #noHandFound>
+    <span class="fa-stack fa-2x">
+      <i class="leap-muted fas fa-hand-paper fa-stack-1x"></i>
+      <i class="leap-muted leap-spinner fas fa-circle-notch fa-stack-2x"></i>
+    </span>
+  </ng-template>
+  
+</ng-template>
diff --git a/src/viewerModule/leap/module.ts b/src/viewerModule/leap/module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4e7bcbfc54c984cf6bdfa87aa1aeab57ed40f8b2
--- /dev/null
+++ b/src/viewerModule/leap/module.ts
@@ -0,0 +1,36 @@
+import { CommonModule } from "@angular/common";
+import { APP_INITIALIZER, NgModule } from "@angular/core";
+import { MatCardModule } from "@angular/material/card";
+import { APPEND_SCRIPT_TOKEN } from "src/util/constants";
+import { LeapSignal } from "./leapSignal/leapSignal.component";
+import { LeapService } from "./service";
+import { LeapControlViewRef } from "./signal.directive";
+
+@NgModule({
+  imports: [
+    CommonModule,
+    MatCardModule,
+  ],
+  declarations: [
+    LeapSignal,
+    LeapControlViewRef,
+  ],
+  exports: [
+    LeapSignal,
+    LeapControlViewRef,
+  ],
+  providers: [
+    LeapService,
+    {
+      provide: APP_INITIALIZER,
+      useFactory(appendSrc: (src: string, deferFlag?: boolean) => Promise<void>){
+        return () => appendSrc("leap-0.6.4.js", true)
+      },
+      deps: [
+        APPEND_SCRIPT_TOKEN
+      ],
+      multi: true
+    },
+  ],
+})
+export class LeapModule{}
\ No newline at end of file
diff --git a/src/viewerModule/leap/service.ts b/src/viewerModule/leap/service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8ce47c14dccf14a36431e26153d4db723ccbf365
--- /dev/null
+++ b/src/viewerModule/leap/service.ts
@@ -0,0 +1,252 @@
+import { DOCUMENT } from "@angular/common";
+import { Inject, Injectable, Optional } from "@angular/core";
+import { BehaviorSubject, EMPTY, interval, Observable, Subject } from "rxjs";
+import { distinctUntilChanged, filter, map, switchMap, take, takeUntil } from "rxjs/operators";
+import { NehubaViewerUnit } from "../nehuba";
+import { NEHUBA_INSTANCE_INJTKN } from "../nehuba/util";
+
+const PINCH_THRESHOLD = 0.80
+const PALM_Z_THRESHOLD = 0.55
+const ROTATION_SPEED = 0.0001
+const ZOOM_SPEED = 40
+
+type Finger = {
+  extended: boolean
+}
+
+type Hand = {
+  pinchStrength: number
+  palmNormal: [number, number, number]
+  palmVelocity: [number, number, number]
+
+  thumb: Finger
+  indexFinger: Finger
+  middleFinger: Finger
+  ringFinger: Finger
+  pinky: Finger
+}
+
+export enum HandShape {
+  PINCHING="PINCHING",
+  POINTING_ONE_FINGER="POINTING_ONE_FINGER",
+  POINTING_TWO_FINGERS="POINTING_TWO_FINGERS",
+  PALM_FORWARD="PALM_FORWARD",
+}
+
+function getHandShape(hand: Hand): HandShape {
+  if (!hand) return null
+  if (hand.pinchStrength >= PINCH_THRESHOLD) return HandShape.PINCHING
+  if (
+    hand.thumb.extended &&
+    hand.indexFinger.extended &&
+    !hand.middleFinger.extended &&
+    !hand.ringFinger.extended &&
+    !hand.pinky.extended
+  ) return HandShape.POINTING_ONE_FINGER
+  if (
+    hand.thumb.extended &&
+    hand.indexFinger.extended &&
+    hand.middleFinger.extended &&
+    !hand.ringFinger.extended &&
+    !hand.pinky.extended
+  ) return HandShape.POINTING_TWO_FINGERS
+
+  const palmNormalZ = hand.palmNormal[2]
+  if (
+    hand.thumb.extended &&
+    hand.indexFinger.extended &&
+    hand.middleFinger.extended &&
+    hand.ringFinger.extended &&
+    hand.pinky.extended &&
+    palmNormalZ <= -PALM_Z_THRESHOLD
+  ) return HandShape.PALM_FORWARD
+  return null
+}
+
+@Injectable()
+export class LeapService{
+  static initFlag = false
+
+  private destroy$ = new Subject()
+
+  public leapReady$ = new BehaviorSubject(false)
+
+  public hand$ = new Subject<Hand[]>()
+  public gesture$ = new Subject<HandShape>()
+  public palmVelocity$ = new Subject<[number, number, number]>()
+
+  private sliceLock = false
+  private vec3: any
+  private quat: any
+
+  private rotation: any
+  private WORLD_UP: any
+  private WORLD_RIGHT: any
+
+  constructor(
+    @Optional() @Inject(NEHUBA_INSTANCE_INJTKN) private nehubaInst$: Observable<NehubaViewerUnit>,
+    @Inject(DOCUMENT) document: Document,
+  ){
+    if (LeapService.initFlag) {
+      console.error(`LeapService already initialised.`)
+    }
+    if (!(window as any).Leap) {
+      console.error(`Leap not found. Terminating`)
+      return
+    }
+    const lop = (window as any).Leap.loop({
+      frame: frame => {
+        this.leapReady$.next(true)
+        this.hand$.next(frame?.hands)
+        if (frame?.hands) {
+          this.emitHand(frame.hands[0])
+        }
+      }
+    })
+
+    document.addEventListener("visibilitychange", () => {
+      if (document.visibilityState === "hidden") {
+        lop.connection.disconnect()
+      } else {
+        lop.connection.reconnect()
+      }
+    })
+
+    interval(160).pipe(
+      filter(() => !!(window as any).export_nehuba),
+      take(1)
+    ).subscribe(() => {
+      this.vec3 = (window as any).export_nehuba.vec3
+      this.quat = (window as any).export_nehuba.quat
+      
+      this.rotation = this.quat.create()
+      this.WORLD_UP = this.vec3.fromValues(0, 1, 0)
+      this.WORLD_RIGHT = this.vec3.fromValues(1, 0, 0)
+    })
+
+    this.nehubaInst$.pipe(
+      switchMap(nehuba => {
+        if (!nehuba) return EMPTY
+        return this.gesture$.pipe(
+          distinctUntilChanged(),
+          switchMap(gesture => {
+            if (!gesture) return EMPTY
+            return this.palmVelocity$.pipe(
+              map(velocity => {
+                return {
+                  nehuba,
+                  gesture,
+                  velocity
+                }
+              })
+            )
+          })
+        )
+      }),
+      takeUntil(this.destroy$)
+    ).subscribe(({ gesture, nehuba, velocity }) => {
+      if (gesture === HandShape.PINCHING) {
+        const vel = velocity
+        const temp = vel[1]
+        vel[1] = -vel[2]
+        vel[2] = temp
+        this.leapToRotation(
+          vel,
+          ROTATION_SPEED * 4,
+          nehuba.nehubaViewer.ngviewer.perspectiveNavigationState.pose.orientation
+        )
+        return
+      }
+
+      if (gesture === HandShape.PALM_FORWARD) {
+        /**
+         * zooming
+         */
+        const vel = -velocity[2]
+        if (nehuba.nehubaViewer.ngviewer.navigationState.zoomFactor.value >= 10000) {
+          nehuba.nehubaViewer.ngviewer.navigationState.zoomFactor.value += ZOOM_SPEED * vel;
+        } else {
+          nehuba.nehubaViewer.ngviewer.navigationState.zoomFactor.value = 10001;
+        }
+        return
+      }
+
+      if (gesture === HandShape.POINTING_ONE_FINGER) {
+        /**
+         * translating
+         */
+        const SPEED_SCALE = 5 * 8000 * (nehuba.nehubaViewer.ngviewer.navigationState.zoomFactor.value / 400000)
+        const vel = velocity
+        vel[1] = -vel[1]
+        vel[2] = -vel[2]
+        const cur = nehuba.nehubaViewer.ngviewer.perspectiveNavigationState.pose.orientation.orientation
+        this.vec3.transformQuat(vel, vel, cur)
+        const { position } = nehuba.nehubaViewer.ngviewer.navigationState.pose
+        this.vec3.scaleAndAdd(
+          position.spatialCoordinates,
+          position.spatialCoordinates,
+          vel,
+          SPEED_SCALE
+        )
+        position.changed.dispatch();
+      }
+
+      if (gesture === HandShape.POINTING_TWO_FINGERS) {
+        const ROTATION_SPEED = 0.0004;
+        if (!this.sliceLock) {
+          const vel = velocity
+          const temp = vel[1]
+          vel[0] = -vel[0]
+          vel[1] = vel[2]
+          vel[2] = -temp
+          this.leapToRotation(
+            vel,
+            ROTATION_SPEED,
+            nehuba.nehubaViewer.ngviewer.navigationState.pose.orientation,
+            nehuba.nehubaViewer.ngviewer.perspectiveNavigationState.pose.orientation.orientation
+          )
+        }
+      }
+    })
+
+    LeapService.initFlag = true
+  }
+
+  private leapToRotation(vel: [number, number, number], rotSpeed: number, orientationStream: any, camOrientation = null){
+    let cur = orientationStream.orientation;
+    const axis = this.vec3.create()
+    if (camOrientation) {
+      this.vec3.transformQuat(axis, this.WORLD_UP, camOrientation);
+      this.vec3.transformQuat(axis, axis, cur);
+    } else {
+      this.vec3.transformQuat(axis, this.WORLD_UP, cur);
+    }
+    this.quat.setAxisAngle(this.rotation, axis, vel[0] * rotSpeed);
+    this.quat.multiply(orientationStream.orientation, this.rotation, cur);
+    cur = orientationStream.orientation;
+    if (camOrientation) {
+      this.vec3.transformQuat(axis, this.WORLD_RIGHT, camOrientation);
+      this.vec3.transformQuat(axis, axis, cur);
+    } else {
+      this.vec3.transformQuat(axis, this.WORLD_RIGHT, cur);
+    }
+    this.quat.setAxisAngle(this.rotation, axis, vel[2] * rotSpeed);
+    this.quat.multiply(orientationStream.orientation, this.rotation, cur);
+    orientationStream.changed.dispatch();
+  }
+
+  private emitHand(hand: Hand){
+    let handShape: HandShape
+    try{
+      handShape = getHandShape(hand)
+      this.gesture$.next(
+        handShape
+      )
+      this.palmVelocity$.next(
+        hand?.palmVelocity
+      )
+    } catch (e) {
+      console.error(e)
+    }
+  }
+}
diff --git a/src/viewerModule/leap/signal.directive.ts b/src/viewerModule/leap/signal.directive.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4add98f470090b81aeabd7a9ef90ec54cd562dde
--- /dev/null
+++ b/src/viewerModule/leap/signal.directive.ts
@@ -0,0 +1,15 @@
+import { ComponentFactoryResolver, Directive, ViewContainerRef } from "@angular/core";
+import { LeapSignal } from "./leapSignal/leapSignal.component";
+
+@Directive({
+  selector: '[leap-control-view-ref]'
+})
+export class LeapControlViewRef {
+  constructor(
+    private vcr: ViewContainerRef,
+    private cfr: ComponentFactoryResolver,
+  ){
+    const cf = this.cfr.resolveComponentFactory(LeapSignal)
+    this.vcr.createComponent(cf)
+  }
+}
diff --git a/src/viewerModule/module.ts b/src/viewerModule/module.ts
index 14779f2a3f322e0162e88c35a615adf78482aec7..e7107258f1c109ddb6f49266dc710c326ef4cbf9 100644
--- a/src/viewerModule/module.ts
+++ b/src/viewerModule/module.ts
@@ -26,6 +26,9 @@ import { MouseoverModule } from "src/mouseoverModule";
 import { LogoContainer } from "src/ui/logoContainer/logoContainer.component";
 import { FloatingMouseContextualContainerDirective } from "src/util/directives/floatingMouseContextualContainer.directive";
 import { ShareModule } from "src/share";
+import { LeapModule } from "./leap/module";
+
+import { environment } from "src/environments/environment"
 
 @NgModule({
   imports: [
@@ -47,6 +50,7 @@ import { ShareModule } from "src/share";
     DialogModule,
     MouseoverModule,
     ShareModule,
+    ...(environment.ENABLE_LEAP_MOTION ? [LeapModule] : [])
   ],
   declarations: [
     ViewerCmp,
diff --git a/src/viewerModule/nehuba/config.service/util.ts b/src/viewerModule/nehuba/config.service/util.ts
index 7233d0f26bb2bda42bfe3b0141c9c9b64a377c77..8f2b0de2dc144f287815a503dd95e35c741e82fb 100644
--- a/src/viewerModule/nehuba/config.service/util.ts
+++ b/src/viewerModule/nehuba/config.service/util.ts
@@ -409,6 +409,11 @@ export function getNehubaConfig(space: SapiSpaceModel): NehubaConfig {
         "layers": {},
         "navigation": {
           "zoomFactor": 350000 * scale,
+          pose: {
+            position: {
+              voxelSize: [1, 1, 1]
+            }
+          }
         },
         "perspectiveOrientation": [
           0.3140767216682434,
diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts
index 579f7fb79ab3e65547cc420391f2097587eeff18..6886634b6e6c02a6556dcaa8a18c3f4729b7b71a 100644
--- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts
+++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts
@@ -37,17 +37,20 @@ export class LayerCtrlEffects {
     ),
     switchMap(([ regions, { atlas, parcellation, template } ]) => {
       const sapiRegion = this.sapi.getRegion(atlas["@id"], parcellation["@id"], regions[0].name)
-      return sapiRegion.getMapInfo(template["@id"]).pipe(
-        map(val => 
+      return forkJoin([
+        sapiRegion.getMapInfo(template["@id"]),
+        sapiRegion.getMapUrl(template["@id"])
+      ]).pipe(
+        map(([mapInfo, mapUrl]) => 
           atlasAppearance.actions.addCustomLayer({
             customLayer: {
               clType: "customlayer/nglayer",
               id: PMAP_LAYER_NAME,
-              source: `nifti://${sapiRegion.getMapUrl(template["@id"])}`,
+              source: `nifti://${mapUrl}`,
               shader: getShader({
                 colormap: EnumColorMapName.VIRIDIS,
-                highThreshold: val.max,
-                lowThreshold: val.min,
+                highThreshold: mapInfo.max,
+                lowThreshold: mapInfo.min,
                 removeBg: true,
               })
             }
diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts
index 136c0d0a314394628ddccdcd91010e85420f7524..da9855ecb93b0344c2239776590f0625dd20117a 100644
--- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts
+++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts
@@ -176,14 +176,23 @@ export class NehubaLayerControlService implements OnDestroy{
      * on custom landmarks loaded, set mesh transparency
      */
     this.sub.push(
-      this.store$.pipe(
-        select(annotation.selectors.annotations),
+      merge(
+        this.store$.pipe(
+          select(annotation.selectors.annotations),
+          map(landmarks => landmarks.length > 0),
+        ),
+        this.store$.pipe(
+          select(atlasAppearance.selectors.customLayers),
+          map(customLayers => customLayers.filter(l => l.clType === "customlayer/nglayer" && /^swc:\/\//.test(l.source)).length > 0),
+        )
+      ).pipe(
+        startWith(false),
         withLatestFrom(this.defaultNgLayers$)
-      ).subscribe(([landmarks, { tmplAuxNgLayers }]) => {
+      ).subscribe(([flag, { tmplAuxNgLayers }]) => {
         const payload: {
           [key: string]: number
         } = {}
-        const alpha = landmarks.length > 0
+        const alpha = flag
           ? 0.2
           : 1.0
         for (const ngId in tmplAuxNgLayers) {
diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
index fea1911266af6a410bdd339c805c4aeb3129f20c..5c16b0a065f1f5b2d14be81d724230ed30b7b301 100644
--- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
+++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
@@ -20,6 +20,7 @@ import { SapiRegionModel } from "src/atlasComponents/sapi";
 import { NehubaConfig, getParcNgId, getRegionLabelIndex } from "../config.service";
 import { SET_MESHES_TO_LOAD } from "../constants";
 import { annotation, atlasAppearance, atlasSelection, userInteraction } from "src/state";
+import { linearTransform, TVALID_LINEAR_XFORM_DST, TVALID_LINEAR_XFORM_SRC } from "src/atlasComponents/sapi/core/space/interspaceLinearXform";
 
 export const INVALID_FILE_INPUT = `Exactly one (1) nifti file is required!`
 
@@ -287,6 +288,20 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnDestroy, AfterViewIni
     this.dismissAllAddedLayers()
 
     if (/\.swc$/i.test(file.name)) {
+      let message = `The swc rendering is experimental. Please contact us on any feedbacks. `
+      const swcText = await file.text()
+      let src: TVALID_LINEAR_XFORM_SRC
+      const dst: TVALID_LINEAR_XFORM_DST = "NEHUBA"
+      if (/ccf/i.test(swcText)) {
+        src = "CCF"
+        message += `CCF detected, applying known transformation.`
+      }
+      if (!src) {
+        message += `no known space detected. Applying default transformation.`
+      }
+
+      const xform = await linearTransform(src, dst)
+      
       const url = URL.createObjectURL(file)
       this.droppedLayerNames.push({
         layerName: randomUuid,
@@ -298,16 +313,14 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnDestroy, AfterViewIni
             id: randomUuid,
             source: `swc://${url}`,
             segments: ["1"],
-            transform: [
-              [1e3, 0, 0, 0],
-              [0, 1e3, 0, 0],
-              [0, 0, 1e3, 0],
-              [0, 0, 0, 1],
-            ],
+            transform: xform,
             clType: 'customlayer/nglayer' as const
           }
         })
       )
+      this.snackbar.open(message, "Dismiss", {
+        duration: 10000
+      })
       return
     }
      
diff --git a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.component.ts b/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.component.ts
index f2e75e77f501517ac2f166f44a1b851042458489..ea321e012cccbbe69e27f0e8467b14b945876e00 100644
--- a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.component.ts
+++ b/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.component.ts
@@ -1,44 +1,22 @@
 import { ChangeDetectionStrategy, Component, Inject, Input, OnChanges, OnDestroy } from "@angular/core";
 import { Store } from "@ngrx/store";
 import { isMat4 } from "common/util"
+import { CONST } from "common/constants"
 import { Observable } from "rxjs";
-import { atlasAppearance } from "src/state";
+import { atlasAppearance, atlasSelection } from "src/state";
 import { NehubaViewerUnit } from "..";
 import { NEHUBA_INSTANCE_INJTKN } from "../util";
+import { getExportNehuba } from "src/util/fn";
 
 type Vec4 = [number, number, number, number]
 type Mat4 = [Vec4, Vec4, Vec4, Vec4]
 
-const _VOL_DETAIL_MAP: Record<string, { shader: string, opacity: number }> = {
-  "PLI Fiber Orientation Red Channel": {
-    shader: "void main(){ float x = toNormalized(getDataValue()); if (x < 0.1) { emitTransparent(); } else { emitRGB(vec3(1.0 * x, x * 0., 0. * x )); } }",
-    opacity: 1
-  },
-  "PLI Fiber Orientation Green Channel": {
-    shader: "void main(){ float x = toNormalized(getDataValue()); if (x < 0.1) { emitTransparent(); } else { emitRGB(vec3(0. * x, x * 1., 0. * x )); } }",
-    opacity: 0.5
-  },
-  "PLI Fiber Orientation Blue Channel": {
-    shader: "void main(){ float x = toNormalized(getDataValue()); if (x < 0.1) { emitTransparent(); } else { emitRGB(vec3(0. * x, x * 0., 1.0 * x )); } }",
-    opacity: 0.25
-  },
-  "Blockface Image": {
-    shader: "void main(){ float x = toNormalized(getDataValue()); if (x < 0.1) { emitTransparent(); } else { emitRGB(vec3(0.8 * x, x * 1., 0.8 * x )); } }",
-    opacity: 1.0
-  },
-  "PLI Transmittance": {
-    shader: "void main(){ float x = toNormalized(getDataValue()); if (x > 0.9) { emitTransparent(); } else { emitRGB(vec3(x * 1., x * 0.8, x * 0.8 )); } }",
-    opacity: 1.0
-  },
-  "T2w MRI": {
-    shader: "void main(){ float x = toNormalized(getDataValue()); if (x < 0.1) { emitTransparent(); } else { emitRGB(vec3(0.8 * x, 0.8 * x, x * 1. )); } }",
-    opacity: 1
-  },
-  "MRI Labels": {
-    shader: null,
-    opacity: 1
-  }
-}
+export const idMat4: Mat4 = [
+  [1, 0, 0, 0],
+  [0, 1, 0, 0],
+  [0, 0, 1, 0],
+  [0, 0, 0, 1],
+]
 
 @Component({
   selector: 'ng-layer-ctl',
@@ -51,6 +29,8 @@ const _VOL_DETAIL_MAP: Record<string, { shader: string, opacity: number }> = {
 
 export class NgLayerCtrlCmp implements OnChanges, OnDestroy{
 
+  public CONST = CONST
+
   private onDestroyCb: (() => void)[] = []
   private removeLayer: () => void
 
@@ -77,7 +57,8 @@ export class NgLayerCtrlCmp implements OnChanges, OnDestroy{
     this.opacity = Number(val)
   }
   
-  transform: Mat4
+  transform: Mat4 = idMat4
+
   @Input('ng-layer-ctl-transform')
   set _transform(xform: string | Mat4) {
     const parsedResult = typeof xform === "string"
@@ -111,12 +92,6 @@ export class NgLayerCtrlCmp implements OnChanges, OnDestroy{
   }
 
   ngOnChanges(): void {
-    if (this.name in _VOL_DETAIL_MAP) {
-      const { shader, opacity } = _VOL_DETAIL_MAP[this.name]
-      this.shader = shader
-      this.opacity = opacity
-    }
-
     if (this.name && this.source) {
       const { name } = this
       if (this.removeLayer) {
@@ -145,6 +120,28 @@ export class NgLayerCtrlCmp implements OnChanges, OnDestroy{
     }
   }
 
+  setOrientation(): void {
+    const { mat4, quat, vec3 } = getExportNehuba()
+
+    /**
+     * glMatrix seems to store the matrix in transposed format
+     */
+    
+    const incM = mat4.transpose(mat4.create(), mat4.fromValues(...this.transform.reduce((acc, curr) => [...acc, ...curr], [])))
+    const scale = mat4.getScaling(vec3.create(), incM)
+    const scaledM = mat4.scale(mat4.create(), incM, vec3.inverse(vec3.create(), scale))
+    const q = mat4.getRotation(quat.create(0), scaledM)
+
+    this.store.dispatch(
+      atlasSelection.actions.navigateTo({
+        navigation: {
+          orientation: Array.from(q)
+        },
+        animation: true
+      })
+    )
+  }
+
   toggleVisibility(): void{
     this.visible = !this.visible
     this.viewer.nehubaViewer.ngviewer.layerManager.getLayerByName(this.name).setVisible(this.visible)
diff --git a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.stories.ts b/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.stories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b716f7d93824fd1e223450fde56655398aa91d0b
--- /dev/null
+++ b/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.stories.ts
@@ -0,0 +1,60 @@
+import { idMat4, NgLayerCtrlCmp } from "./ngLayerCtrl.component"
+import { Meta, moduleMetadata, Story } from "@storybook/angular"
+import { CommonModule } from "@angular/common"
+import { MatButtonModule } from "@angular/material/button"
+import { NEHUBA_INSTANCE_INJTKN } from "../util"
+import { NEVER } from "rxjs"
+import { action } from "@storybook/addon-actions"
+import { MatTooltipModule } from "@angular/material/tooltip"
+import { Store } from "@ngrx/store"
+
+export default {
+  component: NgLayerCtrlCmp,
+  decorators: [
+    moduleMetadata({
+      imports: [
+        CommonModule,
+        MatButtonModule,
+        MatTooltipModule,
+      ],
+      providers: [
+        {
+          provide: NEHUBA_INSTANCE_INJTKN,
+          useValue: NEVER,
+        },
+        {
+          provide: Store,
+          useValue: {
+            dispatch: action('dispatch')
+          }
+        }
+      ]
+    }),
+  ]
+} as Meta
+
+
+const Template: Story<NgLayerCtrlCmp> = (args: any, { parameters }) => {
+
+  const {
+    'ng-layer-ctl-name': name,
+    'ng-layer-ctl-transform': transform
+  } = args
+
+  const {
+    pName,
+    pXform
+  } = parameters
+  
+  return {
+    props: {
+      name: name || pName || 'default name',
+      transform: transform || pXform || idMat4,
+    }
+  }
+}
+
+export const NgLayerTune = Template.bind({})
+NgLayerTune.parameters = {
+  pXform: [[-0.74000001,0,0,38134608],[0,-0.26530117,-0.6908077,13562314],[0,-0.6908077,0.26530117,-3964904],[0,0,0,1]]
+}
diff --git a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.template.html b/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.template.html
index e3442c7b4323dac373e0b9110549b8c97a206a3e..b53ec540ad7fd2bcf7df004327d9cc3f2b3ba51b 100644
--- a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.template.html
+++ b/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.template.html
@@ -1,6 +1,8 @@
 <div [ngClass]="{ 'text-muted': !visible }">
 
-  <button mat-icon-button (click)="toggleVisibility()">
+  <button mat-icon-button
+    [matTooltip]="CONST.TOGGLE_LAYER_VISILITY"
+    (click)="toggleVisibility()">
     <i [ngClass]="visible ? 'fa-eye' : 'fa-eye-slash'" class="far"></i>
   </button>
   
@@ -8,7 +10,17 @@
     {{ name }}
   </span>
 
-  <button mat-icon-button (click)="showOpacityCtrl = !showOpacityCtrl">
+  <button
+    mat-icon-button
+    [matTooltip]="CONST.ORIENT_TO_LAYER"
+    (click)="setOrientation()">
+    <i class="iavic iavic-rotation"></i>
+  </button>
+
+  <button
+    mat-icon-button
+    [matTooltip]="CONST.CONFIGURE_LAYER"
+    (click)="showOpacityCtrl = !showOpacityCtrl">
     <i class="fas fa-cog"></i>
   </button>
 
diff --git a/src/viewerModule/viewerCmp/viewerCmp.style.css b/src/viewerModule/viewerCmp/viewerCmp.style.css
index 66c272acfcbc1f6fca1419582bde6d98efa53d88..e5a409a28036bfe88dab0ce9457272ed7647cf60 100644
--- a/src/viewerModule/viewerCmp/viewerCmp.style.css
+++ b/src/viewerModule/viewerCmp/viewerCmp.style.css
@@ -129,3 +129,14 @@ mat-list[dense].contextual-block
   transform: scale(0.7);
   margin-right: -0.25rem;
 }
+
+.leap-control-wrapper
+{
+  width: 0;
+  height: 0;
+  overflow: visible;
+  flex-direction: row;
+  display: flex;
+  align-items: flex-end;
+  justify-content: flex-end;
+}
diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html
index f1a6b4faf088a2d733b6c91ff1f9d6c6d53964d5..b0e06fdaab9dcdca386cf69563c8334f2313336e 100644
--- a/src/viewerModule/viewerCmp/viewerCmp.template.html
+++ b/src/viewerModule/viewerCmp/viewerCmp.template.html
@@ -169,6 +169,13 @@
         </ng-template>
         
       </div>
+
+      <!-- buttom right -->
+      <div iavLayoutFourCornersBottomRight>
+        <div class="leap-control-wrapper">
+          <div leap-control-view-ref></div>
+        </div>
+      </div>
     </iav-layout-fourcorners>
   </mat-drawer-content>
 </mat-drawer-container>
diff --git a/src/widget/constants.ts b/src/widget/constants.ts
index a779b079ad01afafeff6bf9e52f0675e4fdad6e4..53cc6568a34d662fa229e00592ca9b92ba0a3e08 100644
--- a/src/widget/constants.ts
+++ b/src/widget/constants.ts
@@ -22,4 +22,10 @@ export type TypeActionToWidget<T> = (type: EnumActionToWidget, obj: T, option: I
 
 export const WIDGET_PORTAL_TOKEN = new InjectionToken<Record<string, unknown>>("WIDGET_PORTAL_TOKEN")
 
-export const RM_WIDGET = new InjectionToken('RM_WIDGET')
\ No newline at end of file
+export const RM_WIDGET = new InjectionToken('RM_WIDGET')
+
+export enum EnumWidgetState {
+  MINIMIZED,
+  NORMAL,
+  MAXIMIZED,
+}
diff --git a/src/widget/widget.module.ts b/src/widget/widget.module.ts
index 138ed66482a78ad917959b416aca333c415c09b0..62b9d65ba70505d09aef098617ec2681bb657294 100644
--- a/src/widget/widget.module.ts
+++ b/src/widget/widget.module.ts
@@ -2,17 +2,20 @@ import { NgModule } from "@angular/core";
 import { CommonModule } from "@angular/common";
 import { ComponentsModule } from "src/components";
 import { WidgetCanvas } from "./widgetCanvas.directive";
-import { WidgetPortal } from "./widgetPortal/widgetPortal.component";
+import { WidgetPortal } from "./widgetPortal/widgetPortal.component"
 import { MatCardModule } from "@angular/material/card";
 import { DragDropModule } from "@angular/cdk/drag-drop";
 import { MatButtonModule } from "@angular/material/button";
 import { PortalModule } from "@angular/cdk/portal";
+import { MatTooltipModule } from "@angular/material/tooltip";
+import { WidgetStateIconPipe } from "./widgetStateIcon.pipe";
 
 @NgModule({
   imports:[
     MatCardModule,
     DragDropModule,
     MatButtonModule,
+    MatTooltipModule,
     PortalModule,
     CommonModule,
     ComponentsModule,
@@ -20,6 +23,7 @@ import { PortalModule } from "@angular/cdk/portal";
   declarations: [
     WidgetCanvas,
     WidgetPortal,
+    WidgetStateIconPipe,
   ],
   providers: [],
   exports: [
diff --git a/src/widget/widgetPortal/widgetPortal.component.ts b/src/widget/widgetPortal/widgetPortal.component.ts
index 5e5cc05fa5fa6f15a139fb415097c3fff4078c48..cf7f0ea9a67e49c27812e85bef586397e84e2905 100644
--- a/src/widget/widgetPortal/widgetPortal.component.ts
+++ b/src/widget/widgetPortal/widgetPortal.component.ts
@@ -1,6 +1,26 @@
 import { ComponentPortal } from "@angular/cdk/portal";
-import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Optional } from "@angular/core";
-import { RM_WIDGET } from "../constants";
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding, Inject, Optional } from "@angular/core";
+import { RM_WIDGET, EnumWidgetState } from "../constants";
+
+type TWidgetCss = {
+  transform: string
+  width: string
+}
+
+const widgetStateToTransform: Record<EnumWidgetState, TWidgetCss> = {
+  [EnumWidgetState.MINIMIZED]: {
+    transform: `translate(100vw, 5vh)`,
+    width: '24rem'
+  },
+  [EnumWidgetState.NORMAL]: {
+    transform: `translate(calc(100vw - 24rem), 5vh)`,
+    width: '24rem'
+  },
+  [EnumWidgetState.MAXIMIZED]: {
+    transform: `translate(5vw, 5vh)`,
+    width: '90vw'
+  },
+}
 
 @Component({
   selector: 'sxplr-widget-portal',
@@ -13,6 +33,8 @@ import { RM_WIDGET } from "../constants";
 
 export class WidgetPortal<T>{
 
+  EnumWidgetState = EnumWidgetState
+
   portal: ComponentPortal<T>
   
   private _name: string
@@ -24,10 +46,30 @@ export class WidgetPortal<T>{
     this.cdr.markForCheck()
   }
 
-  defaultPosition = {
-    x: 200,
-    y: 200,
+  minimizeReturnState: EnumWidgetState.NORMAL | EnumWidgetState.MAXIMIZED
+
+  private _state: EnumWidgetState = EnumWidgetState.NORMAL
+  get state() {
+    return this._state
   }
+  set state(val: EnumWidgetState) {
+    if (val === EnumWidgetState.MINIMIZED) {
+      this.minimizeReturnState = this._state !== EnumWidgetState.MINIMIZED
+        ? this._state
+        : EnumWidgetState.NORMAL
+    }
+    this._state = val
+    this.transform = widgetStateToTransform[this._state]?.transform || widgetStateToTransform[EnumWidgetState.NORMAL].transform
+    this.width = widgetStateToTransform[this._state]?.width || widgetStateToTransform[EnumWidgetState.NORMAL].width
+
+    this.cdr.markForCheck()
+  }
+
+  @HostBinding('style.transform')
+  transform = widgetStateToTransform[ EnumWidgetState.NORMAL ].transform
+
+  @HostBinding('style.width')
+  width = widgetStateToTransform[ EnumWidgetState.NORMAL ].width
 
   constructor(
     private cdr: ChangeDetectorRef,
diff --git a/src/widget/widgetPortal/widgetPortal.style.css b/src/widget/widgetPortal/widgetPortal.style.css
index 12e9c809623d27f95eef3986c869ed9966496328..3b374eee2ca51e3338e64c35f48593e33f66623f 100644
--- a/src/widget/widgetPortal/widgetPortal.style.css
+++ b/src/widget/widgetPortal/widgetPortal.style.css
@@ -2,15 +2,18 @@
 {
   pointer-events: none;
   display: block;
-  max-width: 24rem;
+  
+  width: 24rem;
+  height: 90vh;
+
+  transition: all 160ms cubic-bezier(0.35, 0, 0.25, 1);
 }
 
 mat-card
 {
   pointer-events: all;
-  max-width: 36vw;
-  height: 36rem;
-  max-height: 90vh;
+  width: 100%;
+  height: 100%;
 }
 
 mat-card-content
@@ -24,7 +27,7 @@ mat-card-content
 .widget-portal-header
 {
   display: flex;
-  justify-content: space-between;
+  justify-content: flex-end;
   align-items: center;
 }
 
@@ -33,24 +36,13 @@ mat-card-content
   flex-grow: 1;
 }
 
-.hover-grab
-{
-  opacity: 0.5;
-  transition: opacity 200ms ease-in-out;
-  cursor: move;
-}
-
-.hover-grab:hover
-{
-  opacity: 1.0;
-}
-
-.widget-grab-handle
-{
-  margin-right:1rem;
-}
-
 .widget-name
 {
   flex-grow: 1;
 }
+
+.when-minimized-nub
+{
+  position: absolute;
+  transform: translate(-5rem, 5rem);
+}
\ No newline at end of file
diff --git a/src/widget/widgetPortal/widgetPortal.template.html b/src/widget/widgetPortal/widgetPortal.template.html
index 7aff890d9ecab44127f5b70a0df3a5ba0f5c305c..5e4e495fa8894b2b9f8e81e0ddb67b5120f4c081 100644
--- a/src/widget/widgetPortal/widgetPortal.template.html
+++ b/src/widget/widgetPortal/widgetPortal.template.html
@@ -1,13 +1,41 @@
-<mat-card cdkDrag [cdkDragFreeDragPosition]="defaultPosition">
+<div *ngIf="state === EnumWidgetState.MINIMIZED"
+  class="when-minimized-nub">
+
+  <button mat-mini-fab
+    [matTooltip]="name"
+    color="primary"
+    class="sxplr-pe-all"
+    (click)="state = minimizeReturnState">
+    <i [class]="minimizeReturnState | widgetStateIcon"></i>
+  </button>
+</div>
+
+<mat-card>
   <mat-card-content>
-    <div class="widget-portal-header" cdkDragHandle>
-      <span class="hover-grab widget-grab-handle">
-        <i class="fas fa-grip-vertical"></i>
-      </span>
+    <div class="widget-portal-header">
 
       <span *ngIf="name" class="widget-name">
         {{ name }}
       </span>
+      
+      <!-- state changer -->
+      <ng-template [ngTemplateOutlet]="stateBtnTmpl"
+        [ngTemplateOutletContext]="{
+          $implicit: EnumWidgetState.MINIMIZED
+        }">
+      </ng-template>
+
+      <ng-template [ngTemplateOutlet]="stateBtnTmpl"
+        [ngTemplateOutletContext]="{
+          $implicit: EnumWidgetState.NORMAL
+        }">
+      </ng-template>
+
+      <ng-template [ngTemplateOutlet]="stateBtnTmpl"
+        [ngTemplateOutletContext]="{
+          $implicit: EnumWidgetState.MAXIMIZED
+        }">
+      </ng-template>
 
       <button mat-icon-button (click)="exit()">
         <i class="fas fa-times"></i>
@@ -20,3 +48,13 @@
     </div>
   </mat-card-content>
 </mat-card>
+
+<!-- template for plugin state -->
+<ng-template #stateBtnTmpl let-btnstate>
+  <button
+    *ngIf="state !== btnstate"
+    (click)="state = btnstate"
+    mat-icon-button>
+    <i [class]="btnstate | widgetStateIcon"></i>
+  </button>
+</ng-template>
diff --git a/src/widget/widgetStateIcon.pipe.ts b/src/widget/widgetStateIcon.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b7af88175fe3d66886e68610a296db89c7e79fc6
--- /dev/null
+++ b/src/widget/widgetStateIcon.pipe.ts
@@ -0,0 +1,26 @@
+import { Pipe, PipeTransform } from "@angular/core";
+import { EnumWidgetState } from "./constants"
+
+@Pipe({
+  name: 'widgetStateIcon',
+  pure: true
+})
+
+export class WidgetStateIconPipe implements PipeTransform{
+  public transform(state: EnumWidgetState): string {
+    switch (state) {
+    case EnumWidgetState.MINIMIZED: {
+      return 'fas fa-window-minimize'
+    }
+    case EnumWidgetState.NORMAL: {
+      return 'fas fa-window-restore'
+    }
+    case EnumWidgetState.MAXIMIZED: {
+      return 'fas fa-window-maximize'
+    }
+    default: {
+      return 'fas fa-window-restore'
+    }
+    }
+  }
+}
diff --git a/third_party/leap-0.6.4.js b/third_party/leap-0.6.4.js
new file mode 100644
index 0000000000000000000000000000000000000000..575679f9535222b58114e1617cdd714953e2230a
--- /dev/null
+++ b/third_party/leap-0.6.4.js
@@ -0,0 +1,9420 @@
+/*!                                                              
+ * LeapJS v0.6.4                                                  
+ * http://github.com/leapmotion/leapjs/                                        
+ *                                                                             
+ * Copyright 2013 LeapMotion, Inc. and other contributors                      
+ * Released under the Apache-2.0 license                                     
+ * http://github.com/leapmotion/leapjs/blob/master/LICENSE.txt                 
+ */
+;(function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s<n.length;s++)i(n[s]);return i})({1:[function(require,module,exports){
+var Pointable = require('./pointable'),
+  glMatrix = require("gl-matrix")
+  , vec3 = glMatrix.vec3
+  , mat3 = glMatrix.mat3
+  , mat4 = glMatrix.mat4
+  , _ = require('underscore');
+
+
+var Bone = module.exports = function(finger, data) {
+  this.finger = finger;
+
+  this._center = null, this._matrix = null;
+
+  /**
+  * An integer code for the name of this bone.
+  *
+  * * 0 -- metacarpal
+  * * 1 -- proximal
+  * * 2 -- medial
+  * * 3 -- distal
+  * * 4 -- arm
+  *
+  * @member type
+  * @type {number}
+  * @memberof Leap.Bone.prototype
+  */
+  this.type = data.type;
+
+  /**
+   * The position of the previous, or base joint of the bone closer to the wrist.
+   * @type {vector3}
+   */
+  this.prevJoint = data.prevJoint;
+
+  /**
+   * The position of the next joint, or the end of the bone closer to the finger tip.
+   * @type {vector3}
+   */
+  this.nextJoint = data.nextJoint;
+
+  /**
+   * The estimated width of the tool in millimeters.
+   *
+   * The reported width is the average width of the visible portion of the
+   * tool from the hand to the tip. If the width isn't known,
+   * then a value of 0 is returned.
+   *
+   * Pointable objects representing fingers do not have a width property.
+   *
+   * @member width
+   * @type {number}
+   * @memberof Leap.Pointable.prototype
+   */
+  this.width = data.width;
+
+  var displacement = new Array(3);
+  vec3.sub(displacement, data.nextJoint, data.prevJoint);
+
+  this.length = vec3.length(displacement);
+
+
+  /**
+   *
+   * These fully-specify the orientation of the bone.
+   * See examples/threejs-bones.html for more info
+   * Three vec3s:
+   *  x (red): The rotation axis of the finger, pointing outwards.  (In general, away from the thumb )
+   *  y (green): The "up" vector, orienting the top of the finger
+   *  z (blue): The roll axis of the bone.
+   *
+   *  Most up vectors will be pointing the same direction, except for the thumb, which is more rightwards.
+   *
+   *  The thumb has one fewer bones than the fingers, but there are the same number of joints & joint-bases provided
+   *  the first two appear in the same position, but only the second (proximal) rotates.
+   *
+   *  Normalized.
+   */
+  this.basis = data.basis;
+};
+
+Bone.prototype.left = function(){
+
+  if (this._left) return this._left;
+
+  this._left =  mat3.determinant(this.basis[0].concat(this.basis[1]).concat(this.basis[2])) < 0;
+
+  return this._left;
+
+};
+
+
+/**
+ * The Affine transformation matrix describing the orientation of the bone, in global Leap-space.
+ * It contains a 3x3 rotation matrix (in the "top left"), and center coordinates in the fourth column.
+ *
+ * Unlike the basis, the right and left hands have the same coordinate system.
+ *
+ */
+Bone.prototype.matrix = function(){
+
+  if (this._matrix) return this._matrix;
+
+  var b = this.basis,
+      t = this._matrix = mat4.create();
+
+  // open transform mat4 from rotation mat3
+  t[0] = b[0][0], t[1] = b[0][1], t[2]  = b[0][2];
+  t[4] = b[1][0], t[5] = b[1][1], t[6]  = b[1][2];
+  t[8] = b[2][0], t[9] = b[2][1], t[10] = b[2][2];
+
+  t[3] = this.center()[0];
+  t[7] = this.center()[1];
+  t[11] = this.center()[2];
+
+  if ( this.left() ) {
+    // flip the basis to be right-handed
+    t[0] *= -1;
+    t[1] *= -1;
+    t[2] *= -1;
+  }
+
+  return this._matrix;
+};
+
+/**
+ * Helper method to linearly interpolate between the two ends of the bone.
+ *
+ * when t = 0, the position of prevJoint will be returned
+ * when t = 1, the position of nextJoint will be returned
+ */
+Bone.prototype.lerp = function(out, t){
+
+  vec3.lerp(out, this.prevJoint, this.nextJoint, t);
+
+};
+
+/**
+ *
+ * The center position of the bone
+ * Returns a vec3 array.
+ *
+ */
+Bone.prototype.center = function(){
+
+  if (this._center) return this._center;
+
+  var center = vec3.create();
+  this.lerp(center, 0.5);
+  this._center = center;
+  return center;
+
+};
+
+// The negative of the z-basis
+Bone.prototype.direction = function(){
+
+ return [
+   this.basis[2][0] * -1,
+   this.basis[2][1] * -1,
+   this.basis[2][2] * -1
+ ];
+
+};
+
+},{"./pointable":14,"gl-matrix":23,"underscore":24}],2:[function(require,module,exports){
+var CircularBuffer = module.exports = function(size) {
+  this.pos = 0;
+  this._buf = [];
+  this.size = size;
+}
+
+CircularBuffer.prototype.get = function(i) {
+  if (i == undefined) i = 0;
+  if (i >= this.size) return undefined;
+  if (i >= this._buf.length) return undefined;
+  return this._buf[(this.pos - i - 1) % this.size];
+}
+
+CircularBuffer.prototype.push = function(o) {
+  this._buf[this.pos % this.size] = o;
+  return this.pos++;
+}
+
+},{}],3:[function(require,module,exports){
+var chooseProtocol = require('../protocol').chooseProtocol
+  , EventEmitter = require('events').EventEmitter
+  , _ = require('underscore');
+
+var BaseConnection = module.exports = function(opts) {
+  this.opts = _.defaults(opts || {}, {
+    host : '127.0.0.1',
+    enableGestures: false,
+    scheme: this.getScheme(),
+    port: this.getPort(),
+    background: false,
+    optimizeHMD: false,
+    requestProtocolVersion: BaseConnection.defaultProtocolVersion
+  });
+  this.host = this.opts.host;
+  this.port = this.opts.port;
+  this.scheme = this.opts.scheme;
+  this.protocolVersionVerified = false;
+  this.background = null;
+  this.optimizeHMD = null;
+  this.on('ready', function() {
+    this.enableGestures(this.opts.enableGestures);
+    this.setBackground(this.opts.background);
+    this.setOptimizeHMD(this.opts.optimizeHMD);
+
+    if (this.opts.optimizeHMD){
+      console.log("Optimized for head mounted display usage.");
+    }else {
+      console.log("Optimized for desktop usage.");
+    }
+
+  });
+};
+
+// The latest available:
+BaseConnection.defaultProtocolVersion = 6;
+
+BaseConnection.prototype.getUrl = function() {
+  return this.scheme + "//" + this.host + ":" + this.port + "/v" + this.opts.requestProtocolVersion + ".json";
+}
+
+
+BaseConnection.prototype.getScheme = function(){
+  return 'ws:'
+}
+
+BaseConnection.prototype.getPort = function(){
+  return 6437
+}
+
+
+BaseConnection.prototype.setBackground = function(state) {
+  this.opts.background = state;
+  if (this.protocol && this.protocol.sendBackground && this.background !== this.opts.background) {
+    this.background = this.opts.background;
+    this.protocol.sendBackground(this, this.opts.background);
+  }
+}
+
+BaseConnection.prototype.setOptimizeHMD = function(state) {
+  this.opts.optimizeHMD = state;
+  if (this.protocol && this.protocol.sendOptimizeHMD && this.optimizeHMD !== this.opts.optimizeHMD) {
+    this.optimizeHMD = this.opts.optimizeHMD;
+    this.protocol.sendOptimizeHMD(this, this.opts.optimizeHMD);
+  }
+}
+
+BaseConnection.prototype.handleOpen = function() {
+  if (!this.connected) {
+    this.connected = true;
+    this.emit('connect');
+  }
+}
+
+BaseConnection.prototype.enableGestures = function(enabled) {
+  this.gesturesEnabled = enabled ? true : false;
+  this.send(this.protocol.encode({"enableGestures": this.gesturesEnabled}));
+}
+
+BaseConnection.prototype.handleClose = function(code, reason) {
+  if (!this.connected) return;
+  this.disconnect();
+
+  // 1001 - an active connection is closed
+  // 1006 - cannot connect
+  if (code === 1001 && this.opts.requestProtocolVersion > 1) {
+    if (this.protocolVersionVerified) {
+      this.protocolVersionVerified = false;
+    }else{
+      this.opts.requestProtocolVersion--;
+    }
+  }
+  this.startReconnection();
+}
+
+BaseConnection.prototype.startReconnection = function() {
+  var connection = this;
+  if(!this.reconnectionTimer){
+    (this.reconnectionTimer = setInterval(function() { connection.reconnect() }, 500));
+  }
+}
+
+BaseConnection.prototype.stopReconnection = function() {
+  this.reconnectionTimer = clearInterval(this.reconnectionTimer);
+}
+
+// By default, disconnect will prevent auto-reconnection.
+// Pass in true to allow the reconnection loop not be interrupted continue
+BaseConnection.prototype.disconnect = function(allowReconnect) {
+  if (!allowReconnect) this.stopReconnection();
+  if (!this.socket) return;
+  this.socket.close();
+  delete this.socket;
+  delete this.protocol;
+  delete this.background; // This is not persisted when reconnecting to the web socket server
+  delete this.optimizeHMD;
+  delete this.focusedState;
+  if (this.connected) {
+    this.connected = false;
+    this.emit('disconnect');
+  }
+  return true;
+}
+
+BaseConnection.prototype.reconnect = function() {
+  if (this.connected) {
+    this.stopReconnection();
+  } else {
+    this.disconnect(true);
+    this.connect();
+  }
+}
+
+BaseConnection.prototype.handleData = function(data) {
+  var message = JSON.parse(data);
+
+  var messageEvent;
+  if (this.protocol === undefined) {
+    messageEvent = this.protocol = chooseProtocol(message);
+    this.protocolVersionVerified = true;
+    this.emit('ready');
+  } else {
+    messageEvent = this.protocol(message);
+  }
+  this.emit(messageEvent.type, messageEvent);
+}
+
+BaseConnection.prototype.connect = function() {
+  if (this.socket) return;
+  this.socket = this.setupSocket();
+  return true;
+}
+
+BaseConnection.prototype.send = function(data) {
+  this.socket.send(data);
+}
+
+BaseConnection.prototype.reportFocus = function(state) {
+  if (!this.connected || this.focusedState === state) return;
+  this.focusedState = state;
+  this.emit(this.focusedState ? 'focus' : 'blur');
+  if (this.protocol && this.protocol.sendFocused) {
+    this.protocol.sendFocused(this, this.focusedState);
+  }
+}
+
+_.extend(BaseConnection.prototype, EventEmitter.prototype);
+},{"../protocol":15,"events":21,"underscore":24}],4:[function(require,module,exports){
+var BaseConnection = module.exports = require('./base')
+  , _ = require('underscore');
+
+
+var BrowserConnection = module.exports = function(opts) {
+  BaseConnection.call(this, opts);
+  var connection = this;
+  this.on('ready', function() { connection.startFocusLoop(); })
+  this.on('disconnect', function() { connection.stopFocusLoop(); })
+}
+
+_.extend(BrowserConnection.prototype, BaseConnection.prototype);
+
+BrowserConnection.__proto__ = BaseConnection;
+
+BrowserConnection.prototype.useSecure = function(){
+  return location.protocol === 'https:'
+}
+
+BrowserConnection.prototype.getScheme = function(){
+  return this.useSecure() ? 'wss:' : 'ws:'
+}
+
+BrowserConnection.prototype.getPort = function(){
+  return this.useSecure() ? 6436 : 6437
+}
+
+BrowserConnection.prototype.setupSocket = function() {
+  var connection = this;
+  var socket = new WebSocket(this.getUrl());
+  socket.onopen = function() { connection.handleOpen(); };
+  socket.onclose = function(data) { connection.handleClose(data['code'], data['reason']); };
+  socket.onmessage = function(message) { connection.handleData(message.data) };
+  socket.onerror = function(error) {
+
+    // attempt to degrade to ws: after one failed attempt for older Leap Service installations.
+    if (connection.useSecure() && connection.scheme === 'wss:'){
+      connection.scheme = 'ws:';
+      connection.port = 6437;
+      connection.disconnect();
+      connection.connect();
+    }
+
+  };
+  return socket;
+}
+
+BrowserConnection.prototype.startFocusLoop = function() {
+  if (this.focusDetectorTimer) return;
+  var connection = this;
+  var propertyName = null;
+  if (typeof document.hidden !== "undefined") {
+    propertyName = "hidden";
+  } else if (typeof document.mozHidden !== "undefined") {
+    propertyName = "mozHidden";
+  } else if (typeof document.msHidden !== "undefined") {
+    propertyName = "msHidden";
+  } else if (typeof document.webkitHidden !== "undefined") {
+    propertyName = "webkitHidden";
+  } else {
+    propertyName = undefined;
+  }
+
+  if (connection.windowVisible === undefined) {
+    connection.windowVisible = propertyName === undefined ? true : document[propertyName] === false;
+  }
+
+  var focusListener = window.addEventListener('focus', function(e) {
+    connection.windowVisible = true;
+    updateFocusState();
+  });
+
+  var blurListener = window.addEventListener('blur', function(e) {
+    connection.windowVisible = false;
+    updateFocusState();
+  });
+
+  this.on('disconnect', function() {
+    window.removeEventListener('focus', focusListener);
+    window.removeEventListener('blur', blurListener);
+  });
+
+  var updateFocusState = function() {
+    var isVisible = propertyName === undefined ? true : document[propertyName] === false;
+    connection.reportFocus(isVisible && connection.windowVisible);
+  }
+
+  // save 100ms when resuming focus
+  updateFocusState();
+
+  this.focusDetectorTimer = setInterval(updateFocusState, 100);
+}
+
+BrowserConnection.prototype.stopFocusLoop = function() {
+  if (!this.focusDetectorTimer) return;
+  clearTimeout(this.focusDetectorTimer);
+  delete this.focusDetectorTimer;
+}
+
+},{"./base":3,"underscore":24}],5:[function(require,module,exports){
+var process=require("__browserify_process");var Frame = require('./frame')
+  , Hand = require('./hand')
+  , Pointable = require('./pointable')
+  , Finger = require('./finger')
+  , CircularBuffer = require("./circular_buffer")
+  , Pipeline = require("./pipeline")
+  , EventEmitter = require('events').EventEmitter
+  , gestureListener = require('./gesture').gestureListener
+  , Dialog = require('./dialog')
+  , _ = require('underscore');
+
+/**
+ * Constructs a Controller object.
+ *
+ * When creating a Controller object, you may optionally pass in options
+ * to set the host , set the port, enable gestures, or select the frame event type.
+ *
+ * ```javascript
+ * var controller = new Leap.Controller({
+ *   host: '127.0.0.1',
+ *   port: 6437,
+ *   enableGestures: true,
+ *   frameEventName: 'animationFrame'
+ * });
+ * ```
+ *
+ * @class Controller
+ * @memberof Leap
+ * @classdesc
+ * The Controller class is your main interface to the Leap Motion Controller.
+ *
+ * Create an instance of this Controller class to access frames of tracking data
+ * and configuration information. Frame data can be polled at any time using the
+ * [Controller.frame]{@link Leap.Controller#frame}() function. Call frame() or frame(0) to get the most recent
+ * frame. Set the history parameter to a positive integer to access previous frames.
+ * A controller stores up to 60 frames in its frame history.
+ *
+ * Polling is an appropriate strategy for applications which already have an
+ * intrinsic update loop, such as a game.
+ *
+ * loopWhileDisconnected defaults to true, and maintains a 60FPS frame rate even when Leap Motion is not streaming
+ * data at that rate (such as no hands in frame).  This is important for VR/WebGL apps which rely on rendering for
+ * regular visual updates, including from other input devices.  Flipping this to false should be considered an
+ * optimization for very specific use-cases.
+ *
+ *
+ */
+
+
+var Controller = module.exports = function(opts) {
+  var inNode = (typeof(process) !== 'undefined' && process.versions && process.versions.node),
+    controller = this;
+
+  opts = _.defaults(opts || {}, {
+    inNode: inNode
+  });
+
+  this.inNode = opts.inNode;
+
+  opts = _.defaults(opts || {}, {
+    frameEventName: this.useAnimationLoop() ? 'animationFrame' : 'deviceFrame',
+    suppressAnimationLoop: !this.useAnimationLoop(),
+    loopWhileDisconnected: true,
+    useAllPlugins: false,
+    checkVersion: true
+  });
+
+  this.animationFrameRequested = false;
+  this.onAnimationFrame = function(timestamp) {
+    if (controller.lastConnectionFrame.valid){
+      controller.emit('animationFrame', controller.lastConnectionFrame);
+    }
+    controller.emit('frameEnd', timestamp);
+    if (
+      controller.loopWhileDisconnected &&
+      ( ( controller.connection.focusedState !== false )  // loop while undefined, pre-ready.
+        || controller.connection.opts.background) ){
+      window.requestAnimationFrame(controller.onAnimationFrame);
+    }else{
+      controller.animationFrameRequested = false;
+    }
+  };
+  this.suppressAnimationLoop = opts.suppressAnimationLoop;
+  this.loopWhileDisconnected = opts.loopWhileDisconnected;
+  this.frameEventName = opts.frameEventName;
+  this.useAllPlugins = opts.useAllPlugins;
+  this.history = new CircularBuffer(200);
+  this.lastFrame = Frame.Invalid;
+  this.lastValidFrame = Frame.Invalid;
+  this.lastConnectionFrame = Frame.Invalid;
+  this.accumulatedGestures = [];
+  this.checkVersion = opts.checkVersion;
+  if (opts.connectionType === undefined) {
+    this.connectionType = (this.inBrowser() ? require('./connection/browser') : require('./connection/node'));
+  } else {
+    this.connectionType = opts.connectionType;
+  }
+  this.connection = new this.connectionType(opts);
+  this.streamingCount = 0;
+  this.devices = {};
+  this.plugins = {};
+  this._pluginPipelineSteps = {};
+  this._pluginExtendedMethods = {};
+  if (opts.useAllPlugins) this.useRegisteredPlugins();
+  this.setupFrameEvents(opts);
+  this.setupConnectionEvents();
+  
+  this.startAnimationLoop(); // immediately when started
+}
+
+Controller.prototype.gesture = function(type, cb) {
+  var creator = gestureListener(this, type);
+  if (cb !== undefined) {
+    creator.stop(cb);
+  }
+  return creator;
+}
+
+/*
+ * @returns the controller
+ */
+Controller.prototype.setBackground = function(state) {
+  this.connection.setBackground(state);
+  return this;
+}
+
+Controller.prototype.setOptimizeHMD = function(state) {
+  this.connection.setOptimizeHMD(state);
+  return this;
+}
+
+Controller.prototype.inBrowser = function() {
+  return !this.inNode;
+}
+
+Controller.prototype.useAnimationLoop = function() {
+  return this.inBrowser() && !this.inBackgroundPage();
+}
+
+Controller.prototype.inBackgroundPage = function(){
+  // http://developer.chrome.com/extensions/extension#method-getBackgroundPage
+  return (typeof(chrome) !== "undefined") &&
+    chrome.extension &&
+    chrome.extension.getBackgroundPage &&
+    (chrome.extension.getBackgroundPage() === window)
+}
+
+/*
+ * @returns the controller
+ */
+Controller.prototype.connect = function() {
+  this.connection.connect();
+  return this;
+}
+
+Controller.prototype.streaming = function() {
+  return this.streamingCount > 0;
+}
+
+Controller.prototype.connected = function() {
+  return !!this.connection.connected;
+}
+
+Controller.prototype.startAnimationLoop = function(){
+  if (!this.suppressAnimationLoop && !this.animationFrameRequested) {
+    this.animationFrameRequested = true;
+    window.requestAnimationFrame(this.onAnimationFrame);
+  }
+}
+
+/*
+ * @returns the controller
+ */
+Controller.prototype.disconnect = function() {
+  this.connection.disconnect();
+  return this;
+}
+
+/**
+ * Returns a frame of tracking data from the Leap.
+ *
+ * Use the optional history parameter to specify which frame to retrieve.
+ * Call frame() or frame(0) to access the most recent frame; call frame(1) to
+ * access the previous frame, and so on. If you use a history value greater
+ * than the number of stored frames, then the controller returns an invalid frame.
+ *
+ * @method frame
+ * @memberof Leap.Controller.prototype
+ * @param {number} history The age of the frame to return, counting backwards from
+ * the most recent frame (0) into the past and up to the maximum age (59).
+ * @returns {Leap.Frame} The specified frame; or, if no history
+ * parameter is specified, the newest frame. If a frame is not available at
+ * the specified history position, an invalid Frame is returned.
+ **/
+Controller.prototype.frame = function(num) {
+  return this.history.get(num) || Frame.Invalid;
+}
+
+Controller.prototype.loop = function(callback) {
+  if (callback) {
+    if (typeof callback === 'function'){
+      this.on(this.frameEventName, callback);
+    }else{
+      // callback is actually of the form: {eventName: callback}
+      this.setupFrameEvents(callback);
+    }
+  }
+
+  return this.connect();
+}
+
+Controller.prototype.addStep = function(step) {
+  if (!this.pipeline) this.pipeline = new Pipeline(this);
+  this.pipeline.addStep(step);
+}
+
+// this is run on every deviceFrame
+Controller.prototype.processFrame = function(frame) {
+  if (frame.gestures) {
+    this.accumulatedGestures = this.accumulatedGestures.concat(frame.gestures);
+  }
+  // lastConnectionFrame is used by the animation loop
+  this.lastConnectionFrame = frame;
+  this.startAnimationLoop(); // Only has effect if loopWhileDisconnected: false
+  this.emit('deviceFrame', frame);
+}
+
+// on a this.deviceEventName (usually 'animationFrame' in browsers), this emits a 'frame'
+Controller.prototype.processFinishedFrame = function(frame) {
+  this.lastFrame = frame;
+  if (frame.valid) {
+    this.lastValidFrame = frame;
+  }
+  frame.controller = this;
+  frame.historyIdx = this.history.push(frame);
+  if (frame.gestures) {
+    frame.gestures = this.accumulatedGestures;
+    this.accumulatedGestures = [];
+    for (var gestureIdx = 0; gestureIdx != frame.gestures.length; gestureIdx++) {
+      this.emit("gesture", frame.gestures[gestureIdx], frame);
+    }
+  }
+  if (this.pipeline) {
+    frame = this.pipeline.run(frame);
+    if (!frame) frame = Frame.Invalid;
+  }
+  this.emit('frame', frame);
+  this.emitHandEvents(frame);
+}
+
+/**
+ * The controller will emit 'hand' events for every hand on each frame.  The hand in question will be passed
+ * to the event callback.
+ *
+ * @param frame
+ */
+Controller.prototype.emitHandEvents = function(frame){
+  for (var i = 0; i < frame.hands.length; i++){
+    this.emit('hand', frame.hands[i]);
+  }
+}
+
+Controller.prototype.setupFrameEvents = function(opts){
+  if (opts.frame){
+    this.on('frame', opts.frame);
+  }
+  if (opts.hand){
+    this.on('hand', opts.hand);
+  }
+}
+
+/**
+  Controller events.  The old 'deviceConnected' and 'deviceDisconnected' have been depricated -
+  use 'deviceStreaming' and 'deviceStopped' instead, except in the case of an unexpected disconnect.
+
+  There are 4 pairs of device events recently added/changed:
+  -deviceAttached/deviceRemoved - called when a device's physical connection to the computer changes
+  -deviceStreaming/deviceStopped - called when a device is paused or resumed.
+  -streamingStarted/streamingStopped - called when there is/is no longer at least 1 streaming device.
+									  Always comes after deviceStreaming.
+  
+  The first of all of the above event pairs is triggered as appropriate upon connection.  All of
+  these events receives an argument with the most recent info about the device that triggered it.
+  These events will always be fired in the order they are listed here, with reverse ordering for the
+  matching shutdown call. (ie, deviceStreaming always comes after deviceAttached, and deviceStopped 
+  will come before deviceRemoved).
+  
+  -deviceConnected/deviceDisconnected - These are considered deprecated and will be removed in
+  the next revision.  In contrast to the other events and in keeping with it's original behavior,
+  it will only be fired when a device begins streaming AFTER a connection has been established.
+  It is not paired, and receives no device info.  Nearly identical functionality to
+  streamingStarted/Stopped if you need to port.
+*/
+Controller.prototype.setupConnectionEvents = function() {
+  var controller = this;
+  this.connection.on('frame', function(frame) {
+    controller.processFrame(frame);
+  });
+  // either deviceFrame or animationFrame:
+  this.on(this.frameEventName, function(frame) {
+    controller.processFinishedFrame(frame);
+  });
+
+
+  // here we backfill the 0.5.0 deviceEvents as best possible
+  // backfill begin streaming events
+  var backfillStreamingStartedEventsHandler = function(){
+    if (controller.connection.opts.requestProtocolVersion < 5 && controller.streamingCount == 0){
+      controller.streamingCount = 1;
+      var info = {
+        attached: true,
+        streaming: true,
+        type: 'unknown',
+        id: "Lx00000000000"
+      };
+      controller.devices[info.id] = info;
+
+      controller.emit('deviceAttached', info);
+      controller.emit('deviceStreaming', info);
+      controller.emit('streamingStarted', info);
+      controller.connection.removeListener('frame', backfillStreamingStartedEventsHandler)
+    }
+  }
+
+  var backfillStreamingStoppedEvents = function(){
+    if (controller.streamingCount > 0) {
+      for (var deviceId in controller.devices){
+        controller.emit('deviceStopped', controller.devices[deviceId]);
+        controller.emit('deviceRemoved', controller.devices[deviceId]);
+      }
+      // only emit streamingStopped once, with the last device
+      controller.emit('streamingStopped', controller.devices[deviceId]);
+
+      controller.streamingCount = 0;
+
+      for (var deviceId in controller.devices){
+        delete controller.devices[deviceId];
+      }
+    }
+  }
+  // Delegate connection events
+  this.connection.on('focus', function() {
+
+    if ( controller.loopWhileDisconnected ){
+
+      controller.startAnimationLoop();
+
+    }
+
+    controller.emit('focus');
+
+  });
+  this.connection.on('blur', function() { controller.emit('blur') });
+  this.connection.on('protocol', function(protocol) {
+
+    protocol.on('beforeFrameCreated', function(frameData){
+      controller.emit('beforeFrameCreated', frameData)
+    });
+
+    protocol.on('afterFrameCreated', function(frame, frameData){
+      controller.emit('afterFrameCreated', frame, frameData)
+    });
+
+    controller.emit('protocol', protocol); 
+  });
+
+  this.connection.on('ready', function() {
+
+    if (controller.checkVersion && !controller.inNode){
+      // show dialog only to web users
+      controller.checkOutOfDate();
+    }
+
+    controller.emit('ready');
+  });
+
+  this.connection.on('connect', function() {
+    controller.emit('connect');
+    controller.connection.removeListener('frame', backfillStreamingStartedEventsHandler)
+    controller.connection.on('frame', backfillStreamingStartedEventsHandler);
+  });
+
+  this.connection.on('disconnect', function() {
+    controller.emit('disconnect');
+    backfillStreamingStoppedEvents();
+  });
+
+  // this does not fire when the controller is manually disconnected
+  // or for Leap Service v1.2.0+
+  this.connection.on('deviceConnect', function(evt) {
+    if (evt.state){
+      controller.emit('deviceConnected');
+      controller.connection.removeListener('frame', backfillStreamingStartedEventsHandler)
+      controller.connection.on('frame', backfillStreamingStartedEventsHandler);
+    }else{
+      controller.emit('deviceDisconnected');
+      backfillStreamingStoppedEvents();
+    }
+  });
+
+  // Does not fire for Leap Service pre v1.2.0
+  this.connection.on('deviceEvent', function(evt) {
+    var info = evt.state,
+        oldInfo = controller.devices[info.id];
+
+    //Grab a list of changed properties in the device info
+    var changed = {};
+    for(var property in info) {
+      //If a property i doesn't exist the cache, or has changed...
+      if( !oldInfo || !oldInfo.hasOwnProperty(property) || oldInfo[property] != info[property] ) {
+        changed[property] = true;
+      }
+    }
+
+    //Update the device list
+    controller.devices[info.id] = info;
+
+    //Fire events based on change list
+    if(changed.attached) {
+      controller.emit(info.attached ? 'deviceAttached' : 'deviceRemoved', info);
+    }
+
+    if(!changed.streaming) return;
+
+    if(info.streaming) {
+      controller.streamingCount++;
+      controller.emit('deviceStreaming', info);
+      if( controller.streamingCount == 1 ) {
+        controller.emit('streamingStarted', info);
+      }
+      //if attached & streaming both change to true at the same time, that device was streaming
+      //already when we connected.
+      if(!changed.attached) {
+        controller.emit('deviceConnected');
+      }
+    }
+    //Since when devices are attached all fields have changed, don't send events for streaming being false.
+    else if(!(changed.attached && info.attached)) {
+      controller.streamingCount--;
+      controller.emit('deviceStopped', info);
+      if(controller.streamingCount == 0){
+        controller.emit('streamingStopped', info);
+      }
+      controller.emit('deviceDisconnected');
+    }
+
+  });
+
+
+  this.on('newListener', function(event, listener) {
+    if( event == 'deviceConnected' || event == 'deviceDisconnected' ) {
+      console.warn(event + " events are depricated.  Consider using 'streamingStarted/streamingStopped' or 'deviceStreaming/deviceStopped' instead");
+    }
+  });
+
+};
+
+
+
+
+// Checks if the protocol version is the latest, if if not, shows the dialog.
+Controller.prototype.checkOutOfDate = function(){
+  console.assert(this.connection && this.connection.protocol);
+
+  var serviceVersion = this.connection.protocol.serviceVersion;
+  var protocolVersion = this.connection.protocol.version;
+  var defaultProtocolVersion = this.connectionType.defaultProtocolVersion;
+
+  if (defaultProtocolVersion > protocolVersion){
+
+    console.warn("Your Protocol Version is v" + protocolVersion +
+        ", this app was designed for v" + defaultProtocolVersion);
+
+    Dialog.warnOutOfDate({
+      sV: serviceVersion,
+      pV: protocolVersion
+    });
+    return true
+  }else{
+    return false
+  }
+
+};
+
+
+
+Controller._pluginFactories = {};
+
+/*
+ * Registers a plugin, making is accessible to controller.use later on.
+ *
+ * @member plugin
+ * @memberof Leap.Controller.prototype
+ * @param {String} name The name of the plugin (usually camelCase).
+ * @param {function} factory A factory method which will return an instance of a plugin.
+ * The factory receives an optional hash of options, passed in via controller.use.
+ *
+ * Valid keys for the object include frame, hand, finger, tool, and pointable.  The value
+ * of each key can be either a function or an object.  If given a function, that function
+ * will be called once for every instance of the object, with that instance injected as an
+ * argument.  This allows decoration of objects with additional data:
+ *
+ * ```javascript
+ * Leap.Controller.plugin('testPlugin', function(options){
+ *   return {
+ *     frame: function(frame){
+ *       frame.foo = 'bar';
+ *     }
+ *   }
+ * });
+ * ```
+ *
+ * When hand is used, the callback is called for every hand in `frame.hands`.  Note that
+ * hand objects are recreated with every new frame, so that data saved on the hand will not
+ * persist.
+ *
+ * ```javascript
+ * Leap.Controller.plugin('testPlugin', function(){
+ *   return {
+ *     hand: function(hand){
+ *       console.log('testPlugin running on hand ' + hand.id);
+ *     }
+ *   }
+ * });
+ * ```
+ *
+ * A factory can return an object to add custom functionality to Frames, Hands, or Pointables.
+ * The methods are added directly to the object's prototype.  Finger and Tool cannot be used here, Pointable
+ * must be used instead.
+ * This is encouraged for calculations which may not be necessary on every frame.
+ * Memoization is also encouraged, for cases where the method may be called many times per frame by the application.
+ *
+ * ```javascript
+ * // This plugin allows hand.usefulData() to be called later.
+ * Leap.Controller.plugin('testPlugin', function(){
+ *   return {
+ *     hand: {
+ *       usefulData: function(){
+ *         console.log('usefulData on hand', this.id);
+ *         // memoize the results on to the hand, preventing repeat work:
+ *         this.x || this.x = someExpensiveCalculation();
+ *         return this.x;
+ *       }
+ *     }
+ *   }
+ * });
+ *
+ * Note that the factory pattern allows encapsulation for every plugin instance.
+ *
+ * ```javascript
+ * Leap.Controller.plugin('testPlugin', function(options){
+ *   options || options = {}
+ *   options.center || options.center = [0,0,0]
+ *
+ *   privatePrintingMethod = function(){
+ *     console.log('privatePrintingMethod - options', options);
+ *   }
+ *
+ *   return {
+ *     pointable: {
+ *       publicPrintingMethod: function(){
+ *         privatePrintingMethod();
+ *       }
+ *     }
+ *   }
+ * });
+ *
+ */
+Controller.plugin = function(pluginName, factory) {
+  if (this._pluginFactories[pluginName]) {
+    console.warn("Plugin \"" + pluginName + "\" already registered");
+  }
+  return this._pluginFactories[pluginName] = factory;
+};
+
+/*
+ * Returns a list of registered plugins.
+ * @returns {Array} Plugin Factories.
+ */
+Controller.plugins = function() {
+  return _.keys(this._pluginFactories);
+};
+
+
+
+var setPluginCallbacks = function(pluginName, type, callback){
+  
+  if ( ['beforeFrameCreated', 'afterFrameCreated'].indexOf(type) != -1 ){
+    
+      // todo - not able to "unuse" a plugin currently
+      this.on(type, callback);
+      
+    }else {
+      
+      if (!this.pipeline) this.pipeline = new Pipeline(this);
+    
+      if (!this._pluginPipelineSteps[pluginName]) this._pluginPipelineSteps[pluginName] = [];
+
+      this._pluginPipelineSteps[pluginName].push(
+        
+        this.pipeline.addWrappedStep(type, callback)
+        
+      );
+      
+    }
+  
+};
+
+var setPluginMethods = function(pluginName, type, hash){
+  var klass;
+  
+  if (!this._pluginExtendedMethods[pluginName]) this._pluginExtendedMethods[pluginName] = [];
+
+  switch (type) {
+    case 'frame':
+      klass = Frame;
+      break;
+    case 'hand':
+      klass = Hand;
+      break;
+    case 'pointable':
+      klass = Pointable;
+      _.extend(Finger.prototype, hash);
+      _.extend(Finger.Invalid,   hash);
+      break;
+    case 'finger':
+      klass = Finger;
+      break;
+    default:
+      throw pluginName + ' specifies invalid object type "' + type + '" for prototypical extension'
+  }
+
+  _.extend(klass.prototype, hash);
+  _.extend(klass.Invalid, hash);
+  this._pluginExtendedMethods[pluginName].push([klass, hash])
+  
+}
+
+
+
+/*
+ * Begin using a registered plugin.  The plugin's functionality will be added to all frames
+ * returned by the controller (and/or added to the objects within the frame).
+ *  - The order of plugin execution inside the loop will match the order in which use is called by the application.
+ *  - The plugin be run for both deviceFrames and animationFrames.
+ *
+ *  If called a second time, the options will be merged with those of the already instantiated plugin.
+ *
+ * @method use
+ * @memberOf Leap.Controller.prototype
+ * @param pluginName
+ * @param {Hash} Options to be passed to the plugin's factory.
+ * @returns the controller
+ */
+Controller.prototype.use = function(pluginName, options) {
+  var functionOrHash, pluginFactory, key, pluginInstance;
+
+  pluginFactory = (typeof pluginName == 'function') ? pluginName : Controller._pluginFactories[pluginName];
+
+  if (!pluginFactory) {
+    throw 'Leap Plugin ' + pluginName + ' not found.';
+  }
+
+  options || (options = {});
+
+  if (this.plugins[pluginName]){
+    _.extend(this.plugins[pluginName], options);
+    return this;
+  }
+
+  this.plugins[pluginName] = options;
+
+  pluginInstance = pluginFactory.call(this, options);
+
+  for (key in pluginInstance) {
+
+    functionOrHash = pluginInstance[key];
+
+    if (typeof functionOrHash === 'function') {
+      
+      setPluginCallbacks.call(this, pluginName, key, functionOrHash);
+      
+    } else {
+      
+      setPluginMethods.call(this, pluginName, key, functionOrHash);
+      
+    }
+
+  }
+
+  return this;
+};
+
+
+
+
+/*
+ * Stop using a used plugin.  This will remove any of the plugin's pipeline methods (those called on every frame)
+ * and remove any methods which extend frame-object prototypes.
+ *
+ * @method stopUsing
+ * @memberOf Leap.Controller.prototype
+ * @param pluginName
+ * @returns the controller
+ */
+Controller.prototype.stopUsing = function (pluginName) {
+  var steps = this._pluginPipelineSteps[pluginName],
+      extMethodHashes = this._pluginExtendedMethods[pluginName],
+      i = 0, klass, extMethodHash;
+
+  if (!this.plugins[pluginName]) return;
+
+  if (steps) {
+    for (i = 0; i < steps.length; i++) {
+      this.pipeline.removeStep(steps[i]);
+    }
+  }
+
+  if (extMethodHashes){
+    for (i = 0; i < extMethodHashes.length; i++){
+      klass = extMethodHashes[i][0];
+      extMethodHash = extMethodHashes[i][1];
+      for (var methodName in extMethodHash) {
+        delete klass.prototype[methodName];
+        delete klass.Invalid[methodName];
+      }
+    }
+  }
+
+  delete this.plugins[pluginName];
+
+  return this;
+}
+
+Controller.prototype.useRegisteredPlugins = function(){
+  for (var plugin in Controller._pluginFactories){
+    this.use(plugin);
+  }
+}
+
+
+_.extend(Controller.prototype, EventEmitter.prototype);
+
+},{"./circular_buffer":2,"./connection/browser":4,"./connection/node":20,"./dialog":6,"./finger":7,"./frame":8,"./gesture":9,"./hand":10,"./pipeline":13,"./pointable":14,"__browserify_process":22,"events":21,"underscore":24}],6:[function(require,module,exports){
+var process=require("__browserify_process");var Dialog = module.exports = function(message, options){
+  this.options = (options || {});
+  this.message = message;
+
+  this.createElement();
+};
+
+Dialog.prototype.createElement = function(){
+  this.element = document.createElement('div');
+  this.element.className = "leapjs-dialog";
+  this.element.style.position = "fixed";
+  this.element.style.top = '8px';
+  this.element.style.left = 0;
+  this.element.style.right = 0;
+  this.element.style.textAlign = 'center';
+  this.element.style.zIndex = 1000;
+
+  var dialog  = document.createElement('div');
+  this.element.appendChild(dialog);
+  dialog.style.className = "leapjs-dialog";
+  dialog.style.display = "inline-block";
+  dialog.style.margin = "auto";
+  dialog.style.padding = "8px";
+  dialog.style.color = "#222";
+  dialog.style.background = "#eee";
+  dialog.style.borderRadius = "4px";
+  dialog.style.border = "1px solid #999";
+  dialog.style.textAlign = "left";
+  dialog.style.cursor = "pointer";
+  dialog.style.whiteSpace = "nowrap";
+  dialog.style.transition = "box-shadow 1s linear";
+  dialog.innerHTML = this.message;
+
+
+  if (this.options.onclick){
+    dialog.addEventListener('click', this.options.onclick);
+  }
+
+  if (this.options.onmouseover){
+    dialog.addEventListener('mouseover', this.options.onmouseover);
+  }
+
+  if (this.options.onmouseout){
+    dialog.addEventListener('mouseout', this.options.onmouseout);
+  }
+
+  if (this.options.onmousemove){
+    dialog.addEventListener('mousemove', this.options.onmousemove);
+  }
+};
+
+Dialog.prototype.show = function(){
+  document.body.appendChild(this.element);
+  return this;
+};
+
+Dialog.prototype.hide = function(){
+  document.body.removeChild(this.element);
+  return this;
+};
+
+
+
+
+// Shows a DOM dialog box with links to developer.leapmotion.com to upgrade
+// This will work whether or not the Leap is plugged in,
+// As long as it is called after a call to .connect() and the 'ready' event has fired.
+Dialog.warnOutOfDate = function(params){
+  params || (params = {});
+
+  var url = "http://developer.leapmotion.com?";
+
+  params.returnTo = window.location.href;
+
+  for (var key in params){
+    url += key + '=' + encodeURIComponent(params[key]) + '&';
+  }
+
+  var dialog,
+    onclick = function(event){
+
+       if (event.target.id != 'leapjs-decline-upgrade'){
+
+         var popup = window.open(url,
+           '_blank',
+           'height=800,width=1000,location=1,menubar=1,resizable=1,status=1,toolbar=1,scrollbars=1'
+         );
+
+         if (window.focus) {popup.focus()}
+
+       }
+
+       dialog.hide();
+
+       return true;
+    },
+
+
+    message = "This site requires Leap Motion Tracking V2." +
+      "<button id='leapjs-accept-upgrade'  style='color: #444; transition: box-shadow 100ms linear; cursor: pointer; vertical-align: baseline; margin-left: 16px;'>Upgrade</button>" +
+      "<button id='leapjs-decline-upgrade' style='color: #444; transition: box-shadow 100ms linear; cursor: pointer; vertical-align: baseline; margin-left: 8px; '>Not Now</button>";
+
+  dialog = new Dialog(message, {
+      onclick: onclick,
+      onmousemove: function(e){
+        if (e.target == document.getElementById('leapjs-decline-upgrade')){
+          document.getElementById('leapjs-decline-upgrade').style.color = '#000';
+          document.getElementById('leapjs-decline-upgrade').style.boxShadow = '0px 0px 2px #5daa00';
+
+          document.getElementById('leapjs-accept-upgrade').style.color = '#444';
+          document.getElementById('leapjs-accept-upgrade').style.boxShadow = 'none';
+        }else{
+          document.getElementById('leapjs-accept-upgrade').style.color = '#000';
+          document.getElementById('leapjs-accept-upgrade').style.boxShadow = '0px 0px 2px #5daa00';
+
+          document.getElementById('leapjs-decline-upgrade').style.color = '#444';
+          document.getElementById('leapjs-decline-upgrade').style.boxShadow = 'none';
+        }
+      },
+      onmouseout: function(){
+        document.getElementById('leapjs-decline-upgrade').style.color = '#444';
+        document.getElementById('leapjs-decline-upgrade').style.boxShadow = 'none';
+        document.getElementById('leapjs-accept-upgrade').style.color = '#444';
+        document.getElementById('leapjs-accept-upgrade').style.boxShadow = 'none';
+      }
+    }
+  );
+
+  return dialog.show();
+};
+
+
+// Tracks whether we've warned for lack of bones API.  This will be shown only for early private-beta members.
+Dialog.hasWarnedBones = false;
+
+Dialog.warnBones = function(){
+  if (this.hasWarnedBones) return;
+  this.hasWarnedBones = true;
+
+  console.warn("Your Leap Service is out of date");
+
+  if ( !(typeof(process) !== 'undefined' && process.versions && process.versions.node) ){
+    this.warnOutOfDate({reason: 'bones'});
+  }
+
+}
+},{"__browserify_process":22}],7:[function(require,module,exports){
+var Pointable = require('./pointable'),
+  Bone = require('./bone')
+  , Dialog = require('./dialog')
+  , _ = require('underscore');
+
+/**
+* Constructs a Finger object.
+*
+* An uninitialized finger is considered invalid.
+* Get valid Finger objects from a Frame or a Hand object.
+*
+* @class Finger
+* @memberof Leap
+* @classdesc
+* The Finger class reports the physical characteristics of a finger.
+*
+* Both fingers and tools are classified as Pointable objects. Use the
+* Pointable.tool property to determine whether a Pointable object represents a
+* tool or finger. The Leap classifies a detected entity as a tool when it is
+* thinner, straighter, and longer than a typical finger.
+*
+* Note that Finger objects can be invalid, which means that they do not
+* contain valid tracking data and do not correspond to a physical entity.
+* Invalid Finger objects can be the result of asking for a Finger object
+* using an ID from an earlier frame when no Finger objects with that ID
+* exist in the current frame. A Finger object created from the Finger
+* constructor is also invalid. Test for validity with the Pointable.valid
+* property.
+*/
+var Finger = module.exports = function(data) {
+  Pointable.call(this, data); // use pointable as super-constructor
+  
+  /**
+  * The position of the distal interphalangeal joint of the finger.
+  * This joint is closest to the tip.
+  * 
+  * The distal interphalangeal joint is located between the most extreme segment
+  * of the finger (the distal phalanx) and the middle segment (the medial
+  * phalanx).
+  *
+  * @member dipPosition
+  * @type {number[]}
+  * @memberof Leap.Finger.prototype
+  */  
+  this.dipPosition = data.dipPosition;
+
+  /**
+  * The position of the proximal interphalangeal joint of the finger. This joint is the middle
+  * joint of a finger.
+  *
+  * The proximal interphalangeal joint is located between the two finger segments
+  * closest to the hand (the proximal and the medial phalanges). On a thumb,
+  * which lacks an medial phalanx, this joint index identifies the knuckle joint
+  * between the proximal phalanx and the metacarpal bone.
+  *
+  * @member pipPosition
+  * @type {number[]}
+  * @memberof Leap.Finger.prototype
+  */  
+  this.pipPosition = data.pipPosition;
+
+  /**
+  * The position of the metacarpopophalangeal joint, or knuckle, of the finger.
+  *
+  * The metacarpopophalangeal joint is located at the base of a finger between
+  * the metacarpal bone and the first phalanx. The common name for this joint is
+  * the knuckle.
+  *
+  * On a thumb, which has one less phalanx than a finger, this joint index
+  * identifies the thumb joint near the base of the hand, between the carpal
+  * and metacarpal bones.
+  *
+  * @member mcpPosition
+  * @type {number[]}
+  * @memberof Leap.Finger.prototype
+  */  
+  this.mcpPosition = data.mcpPosition;
+
+  /**
+   * The position of the Carpometacarpal joint
+   *
+   * This is at the distal end of the wrist, and has no common name.
+   *
+   */
+  this.carpPosition = data.carpPosition;
+
+  /**
+  * Whether or not this finger is in an extended posture.
+  *
+  * A finger is considered extended if it is extended straight from the hand as if
+  * pointing. A finger is not extended when it is bent down and curled towards the 
+  * palm.
+  * @member extended
+  * @type {Boolean}
+  * @memberof Leap.Finger.prototype
+  */
+  this.extended = data.extended;
+
+  /**
+  * An integer code for the name of this finger.
+  * 
+  * * 0 -- thumb
+  * * 1 -- index finger
+  * * 2 -- middle finger
+  * * 3 -- ring finger
+  * * 4 -- pinky
+  *
+  * @member type
+  * @type {number}
+  * @memberof Leap.Finger.prototype
+  */
+  this.type = data.type;
+
+  this.finger = true;
+  
+  /**
+  * The joint positions of this finger as an array in the order base to tip.
+  *
+  * @member positions
+  * @type {array[]}
+  * @memberof Leap.Finger.prototype
+  */
+  this.positions = [this.carpPosition, this.mcpPosition, this.pipPosition, this.dipPosition, this.tipPosition];
+
+  if (data.bases){
+    this.addBones(data);
+  } else {
+    Dialog.warnBones();
+  }
+
+};
+
+_.extend(Finger.prototype, Pointable.prototype);
+
+
+Finger.prototype.addBones = function(data){
+  /**
+  * Four bones per finger, from wrist outwards:
+  * metacarpal, proximal, medial, and distal.
+  *
+  * See http://en.wikipedia.org/wiki/Interphalangeal_articulations_of_hand
+  */
+  this.metacarpal   = new Bone(this, {
+    type: 0,
+    width: this.width,
+    prevJoint: this.carpPosition,
+    nextJoint: this.mcpPosition,
+    basis: data.bases[0]
+  });
+
+  this.proximal     = new Bone(this, {
+    type: 1,
+    width: this.width,
+    prevJoint: this.mcpPosition,
+    nextJoint: this.pipPosition,
+    basis: data.bases[1]
+  });
+
+  this.medial = new Bone(this, {
+    type: 2,
+    width: this.width,
+    prevJoint: this.pipPosition,
+    nextJoint: this.dipPosition,
+    basis: data.bases[2]
+  });
+
+  /**
+   * Note that the `distal.nextJoint` position is slightly different from the `finger.tipPosition`.
+   * The former is at the very end of the bone, where the latter is the center of a sphere positioned at
+   * the tip of the finger.  The btipPosition "bone tip position" is a few mm closer to the wrist than
+   * the tipPosition.
+   * @type {Bone}
+   */
+  this.distal       = new Bone(this, {
+    type: 3,
+    width: this.width,
+    prevJoint: this.dipPosition,
+    nextJoint: data.btipPosition,
+    basis: data.bases[3]
+  });
+
+  this.bones = [this.metacarpal, this.proximal, this.medial, this.distal];
+};
+
+Finger.prototype.toString = function() {
+    return "Finger [ id:" + this.id + " " + this.length + "mmx | width:" + this.width + "mm | direction:" + this.direction + ' ]';
+};
+
+Finger.Invalid = { valid: false };
+
+},{"./bone":1,"./dialog":6,"./pointable":14,"underscore":24}],8:[function(require,module,exports){
+var Hand = require("./hand")
+  , Pointable = require("./pointable")
+  , createGesture = require("./gesture").createGesture
+  , glMatrix = require("gl-matrix")
+  , mat3 = glMatrix.mat3
+  , vec3 = glMatrix.vec3
+  , InteractionBox = require("./interaction_box")
+  , Finger = require('./finger')
+  , _ = require("underscore");
+
+/**
+ * Constructs a Frame object.
+ *
+ * Frame instances created with this constructor are invalid.
+ * Get valid Frame objects by calling the
+ * [Controller.frame]{@link Leap.Controller#frame}() function.
+ *<C-D-Space>
+ * @class Frame
+ * @memberof Leap
+ * @classdesc
+ * The Frame class represents a set of hand and finger tracking data detected
+ * in a single frame.
+ *
+ * The Leap detects hands, fingers and tools within the tracking area, reporting
+ * their positions, orientations and motions in frames at the Leap frame rate.
+ *
+ * Access Frame objects using the [Controller.frame]{@link Leap.Controller#frame}() function.
+ */
+var Frame = module.exports = function(data) {
+  /**
+   * Reports whether this Frame instance is valid.
+   *
+   * A valid Frame is one generated by the Controller object that contains
+   * tracking data for all detected entities. An invalid Frame contains no
+   * actual tracking data, but you can call its functions without risk of a
+   * undefined object exception. The invalid Frame mechanism makes it more
+   * convenient to track individual data across the frame history. For example,
+   * you can invoke:
+   *
+   * ```javascript
+   * var finger = controller.frame(n).finger(fingerID);
+   * ```
+   *
+   * for an arbitrary Frame history value, "n", without first checking whether
+   * frame(n) returned a null object. (You should still check that the
+   * returned Finger instance is valid.)
+   *
+   * @member valid
+   * @memberof Leap.Frame.prototype
+   * @type {Boolean}
+   */
+  this.valid = true;
+  /**
+   * A unique ID for this Frame. Consecutive frames processed by the Leap
+   * have consecutive increasing values.
+   * @member id
+   * @memberof Leap.Frame.prototype
+   * @type {String}
+   */
+  this.id = data.id;
+  /**
+   * The frame capture time in microseconds elapsed since the Leap started.
+   * @member timestamp
+   * @memberof Leap.Frame.prototype
+   * @type {number}
+   */
+  this.timestamp = data.timestamp;
+  /**
+   * The list of Hand objects detected in this frame, given in arbitrary order.
+   * The list can be empty if no hands are detected.
+   *
+   * @member hands[]
+   * @memberof Leap.Frame.prototype
+   * @type {Leap.Hand}
+   */
+  this.hands = [];
+  this.handsMap = {};
+  /**
+   * The list of Pointable objects (fingers and tools) detected in this frame,
+   * given in arbitrary order. The list can be empty if no fingers or tools are
+   * detected.
+   *
+   * @member pointables[]
+   * @memberof Leap.Frame.prototype
+   * @type {Leap.Pointable}
+   */
+  this.pointables = [];
+  /**
+   * The list of Tool objects detected in this frame, given in arbitrary order.
+   * The list can be empty if no tools are detected.
+   *
+   * @member tools[]
+   * @memberof Leap.Frame.prototype
+   * @type {Leap.Pointable}
+   */
+  this.tools = [];
+  /**
+   * The list of Finger objects detected in this frame, given in arbitrary order.
+   * The list can be empty if no fingers are detected.
+   * @member fingers[]
+   * @memberof Leap.Frame.prototype
+   * @type {Leap.Pointable}
+   */
+  this.fingers = [];
+
+  /**
+   * The InteractionBox associated with the current frame.
+   *
+   * @member interactionBox
+   * @memberof Leap.Frame.prototype
+   * @type {Leap.InteractionBox}
+   */
+  if (data.interactionBox) {
+    this.interactionBox = new InteractionBox(data.interactionBox);
+  }
+  this.gestures = [];
+  this.pointablesMap = {};
+  this._translation = data.t;
+  this._rotation = _.flatten(data.r);
+  this._scaleFactor = data.s;
+  this.data = data;
+  this.type = 'frame'; // used by event emitting
+  this.currentFrameRate = data.currentFrameRate;
+
+  if (data.gestures) {
+   /**
+    * The list of Gesture objects detected in this frame, given in arbitrary order.
+    * The list can be empty if no gestures are detected.
+    *
+    * Circle and swipe gestures are updated every frame. Tap gestures
+    * only appear in the list for a single frame.
+    * @member gestures[]
+    * @memberof Leap.Frame.prototype
+    * @type {Leap.Gesture}
+    */
+    for (var gestureIdx = 0, gestureCount = data.gestures.length; gestureIdx != gestureCount; gestureIdx++) {
+      this.gestures.push(createGesture(data.gestures[gestureIdx]));
+    }
+  }
+  this.postprocessData(data);
+};
+
+Frame.prototype.postprocessData = function(data){
+  if (!data) {
+    data = this.data;
+  }
+
+  for (var handIdx = 0, handCount = data.hands.length; handIdx != handCount; handIdx++) {
+    var hand = new Hand(data.hands[handIdx]);
+    hand.frame = this;
+    this.hands.push(hand);
+    this.handsMap[hand.id] = hand;
+  }
+
+  data.pointables = _.sortBy(data.pointables, function(pointable) { return pointable.id });
+
+  for (var pointableIdx = 0, pointableCount = data.pointables.length; pointableIdx != pointableCount; pointableIdx++) {
+    var pointableData = data.pointables[pointableIdx];
+    var pointable = pointableData.dipPosition ? new Finger(pointableData) : new Pointable(pointableData);
+    pointable.frame = this;
+    this.addPointable(pointable);
+  }
+};
+
+/**
+ * Adds data from a pointable element into the pointablesMap; 
+ * also adds the pointable to the frame.handsMap hand to which it belongs,
+ * and to the hand's tools or hand's fingers map.
+ * 
+ * @param pointable {Object} a Pointable
+ */
+Frame.prototype.addPointable = function (pointable) {
+  this.pointables.push(pointable);
+  this.pointablesMap[pointable.id] = pointable;
+  (pointable.tool ? this.tools : this.fingers).push(pointable);
+  if (pointable.handId !== undefined && this.handsMap.hasOwnProperty(pointable.handId)) {
+    var hand = this.handsMap[pointable.handId];
+    hand.pointables.push(pointable);
+    (pointable.tool ? hand.tools : hand.fingers).push(pointable);
+    switch (pointable.type){
+      case 0:
+        hand.thumb = pointable;
+        break;
+      case 1:
+        hand.indexFinger = pointable;
+        break;
+      case 2:
+        hand.middleFinger = pointable;
+        break;
+      case 3:
+        hand.ringFinger = pointable;
+        break;
+      case 4:
+        hand.pinky = pointable;
+        break;
+    }
+  }
+};
+
+/**
+ * The tool with the specified ID in this frame.
+ *
+ * Use the Frame tool() function to retrieve a tool from
+ * this frame using an ID value obtained from a previous frame.
+ * This function always returns a Pointable object, but if no tool
+ * with the specified ID is present, an invalid Pointable object is returned.
+ *
+ * Note that ID values persist across frames, but only until tracking of a
+ * particular object is lost. If tracking of a tool is lost and subsequently
+ * regained, the new Pointable object representing that tool may have a
+ * different ID than that representing the tool in an earlier frame.
+ *
+ * @method tool
+ * @memberof Leap.Frame.prototype
+ * @param {String} id The ID value of a Tool object from a previous frame.
+ * @returns {Leap.Pointable} The tool with the
+ * matching ID if one exists in this frame; otherwise, an invalid Pointable object
+ * is returned.
+ */
+Frame.prototype.tool = function(id) {
+  var pointable = this.pointable(id);
+  return pointable.tool ? pointable : Pointable.Invalid;
+};
+
+/**
+ * The Pointable object with the specified ID in this frame.
+ *
+ * Use the Frame pointable() function to retrieve the Pointable object from
+ * this frame using an ID value obtained from a previous frame.
+ * This function always returns a Pointable object, but if no finger or tool
+ * with the specified ID is present, an invalid Pointable object is returned.
+ *
+ * Note that ID values persist across frames, but only until tracking of a
+ * particular object is lost. If tracking of a finger or tool is lost and subsequently
+ * regained, the new Pointable object representing that finger or tool may have
+ * a different ID than that representing the finger or tool in an earlier frame.
+ *
+ * @method pointable
+ * @memberof Leap.Frame.prototype
+ * @param {String} id The ID value of a Pointable object from a previous frame.
+ * @returns {Leap.Pointable} The Pointable object with
+ * the matching ID if one exists in this frame;
+ * otherwise, an invalid Pointable object is returned.
+ */
+Frame.prototype.pointable = function(id) {
+  return this.pointablesMap[id] || Pointable.Invalid;
+};
+
+/**
+ * The finger with the specified ID in this frame.
+ *
+ * Use the Frame finger() function to retrieve the finger from
+ * this frame using an ID value obtained from a previous frame.
+ * This function always returns a Finger object, but if no finger
+ * with the specified ID is present, an invalid Pointable object is returned.
+ *
+ * Note that ID values persist across frames, but only until tracking of a
+ * particular object is lost. If tracking of a finger is lost and subsequently
+ * regained, the new Pointable object representing that physical finger may have
+ * a different ID than that representing the finger in an earlier frame.
+ *
+ * @method finger
+ * @memberof Leap.Frame.prototype
+ * @param {String} id The ID value of a finger from a previous frame.
+ * @returns {Leap.Pointable} The finger with the
+ * matching ID if one exists in this frame; otherwise, an invalid Pointable
+ * object is returned.
+ */
+Frame.prototype.finger = function(id) {
+  var pointable = this.pointable(id);
+  return !pointable.tool ? pointable : Pointable.Invalid;
+};
+
+/**
+ * The Hand object with the specified ID in this frame.
+ *
+ * Use the Frame hand() function to retrieve the Hand object from
+ * this frame using an ID value obtained from a previous frame.
+ * This function always returns a Hand object, but if no hand
+ * with the specified ID is present, an invalid Hand object is returned.
+ *
+ * Note that ID values persist across frames, but only until tracking of a
+ * particular object is lost. If tracking of a hand is lost and subsequently
+ * regained, the new Hand object representing that physical hand may have
+ * a different ID than that representing the physical hand in an earlier frame.
+ *
+ * @method hand
+ * @memberof Leap.Frame.prototype
+ * @param {String} id The ID value of a Hand object from a previous frame.
+ * @returns {Leap.Hand} The Hand object with the matching
+ * ID if one exists in this frame; otherwise, an invalid Hand object is returned.
+ */
+Frame.prototype.hand = function(id) {
+  return this.handsMap[id] || Hand.Invalid;
+};
+
+/**
+ * The angle of rotation around the rotation axis derived from the overall
+ * rotational motion between the current frame and the specified frame.
+ *
+ * The returned angle is expressed in radians measured clockwise around
+ * the rotation axis (using the right-hand rule) between the start and end frames.
+ * The value is always between 0 and pi radians (0 and 180 degrees).
+ *
+ * The Leap derives frame rotation from the relative change in position and
+ * orientation of all objects detected in the field of view.
+ *
+ * If either this frame or sinceFrame is an invalid Frame object, then the
+ * angle of rotation is zero.
+ *
+ * @method rotationAngle
+ * @memberof Leap.Frame.prototype
+ * @param {Leap.Frame} sinceFrame The starting frame for computing the relative rotation.
+ * @param {number[]} [axis] The axis to measure rotation around.
+ * @returns {number} A positive value containing the heuristically determined
+ * rotational change between the current frame and that specified in the sinceFrame parameter.
+ */
+Frame.prototype.rotationAngle = function(sinceFrame, axis) {
+  if (!this.valid || !sinceFrame.valid) return 0.0;
+
+  var rot = this.rotationMatrix(sinceFrame);
+  var cs = (rot[0] + rot[4] + rot[8] - 1.0)*0.5;
+  var angle = Math.acos(cs);
+  angle = isNaN(angle) ? 0.0 : angle;
+
+  if (axis !== undefined) {
+    var rotAxis = this.rotationAxis(sinceFrame);
+    angle *= vec3.dot(rotAxis, vec3.normalize(vec3.create(), axis));
+  }
+
+  return angle;
+};
+
+/**
+ * The axis of rotation derived from the overall rotational motion between
+ * the current frame and the specified frame.
+ *
+ * The returned direction vector is normalized.
+ *
+ * The Leap derives frame rotation from the relative change in position and
+ * orientation of all objects detected in the field of view.
+ *
+ * If either this frame or sinceFrame is an invalid Frame object, or if no
+ * rotation is detected between the two frames, a zero vector is returned.
+ *
+ * @method rotationAxis
+ * @memberof Leap.Frame.prototype
+ * @param {Leap.Frame} sinceFrame The starting frame for computing the relative rotation.
+ * @returns {number[]} A normalized direction vector representing the axis of the heuristically determined
+ * rotational change between the current frame and that specified in the sinceFrame parameter.
+ */
+Frame.prototype.rotationAxis = function(sinceFrame) {
+  if (!this.valid || !sinceFrame.valid) return vec3.create();
+  return vec3.normalize(vec3.create(), [
+    this._rotation[7] - sinceFrame._rotation[5],
+    this._rotation[2] - sinceFrame._rotation[6],
+    this._rotation[3] - sinceFrame._rotation[1]
+  ]);
+}
+
+/**
+ * The transform matrix expressing the rotation derived from the overall
+ * rotational motion between the current frame and the specified frame.
+ *
+ * The Leap derives frame rotation from the relative change in position and
+ * orientation of all objects detected in the field of view.
+ *
+ * If either this frame or sinceFrame is an invalid Frame object, then
+ * this method returns an identity matrix.
+ *
+ * @method rotationMatrix
+ * @memberof Leap.Frame.prototype
+ * @param {Leap.Frame} sinceFrame The starting frame for computing the relative rotation.
+ * @returns {number[]} A transformation matrix containing the heuristically determined
+ * rotational change between the current frame and that specified in the sinceFrame parameter.
+ */
+Frame.prototype.rotationMatrix = function(sinceFrame) {
+  if (!this.valid || !sinceFrame.valid) return mat3.create();
+  var transpose = mat3.transpose(mat3.create(), this._rotation)
+  return mat3.multiply(mat3.create(), sinceFrame._rotation, transpose);
+}
+
+/**
+ * The scale factor derived from the overall motion between the current frame and the specified frame.
+ *
+ * The scale factor is always positive. A value of 1.0 indicates no scaling took place.
+ * Values between 0.0 and 1.0 indicate contraction and values greater than 1.0 indicate expansion.
+ *
+ * The Leap derives scaling from the relative inward or outward motion of all
+ * objects detected in the field of view (independent of translation and rotation).
+ *
+ * If either this frame or sinceFrame is an invalid Frame object, then this method returns 1.0.
+ *
+ * @method scaleFactor
+ * @memberof Leap.Frame.prototype
+ * @param {Leap.Frame} sinceFrame The starting frame for computing the relative scaling.
+ * @returns {number} A positive value representing the heuristically determined
+ * scaling change ratio between the current frame and that specified in the sinceFrame parameter.
+ */
+Frame.prototype.scaleFactor = function(sinceFrame) {
+  if (!this.valid || !sinceFrame.valid) return 1.0;
+  return Math.exp(this._scaleFactor - sinceFrame._scaleFactor);
+}
+
+/**
+ * The change of position derived from the overall linear motion between the
+ * current frame and the specified frame.
+ *
+ * The returned translation vector provides the magnitude and direction of the
+ * movement in millimeters.
+ *
+ * The Leap derives frame translation from the linear motion of all objects
+ * detected in the field of view.
+ *
+ * If either this frame or sinceFrame is an invalid Frame object, then this
+ * method returns a zero vector.
+ *
+ * @method translation
+ * @memberof Leap.Frame.prototype
+ * @param {Leap.Frame} sinceFrame The starting frame for computing the relative translation.
+ * @returns {number[]} A vector representing the heuristically determined change in
+ * position of all objects between the current frame and that specified in the sinceFrame parameter.
+ */
+Frame.prototype.translation = function(sinceFrame) {
+  if (!this.valid || !sinceFrame.valid) return vec3.create();
+  return vec3.subtract(vec3.create(), this._translation, sinceFrame._translation);
+}
+
+/**
+ * A string containing a brief, human readable description of the Frame object.
+ *
+ * @method toString
+ * @memberof Leap.Frame.prototype
+ * @returns {String} A brief description of this frame.
+ */
+Frame.prototype.toString = function() {
+  var str = "Frame [ id:"+this.id+" | timestamp:"+this.timestamp+" | Hand count:("+this.hands.length+") | Pointable count:("+this.pointables.length+")";
+  if (this.gestures) str += " | Gesture count:("+this.gestures.length+")";
+  str += " ]";
+  return str;
+}
+
+/**
+ * Returns a JSON-formatted string containing the hands, pointables and gestures
+ * in this frame.
+ *
+ * @method dump
+ * @memberof Leap.Frame.prototype
+ * @returns {String} A JSON-formatted string.
+ */
+Frame.prototype.dump = function() {
+  var out = '';
+  out += "Frame Info:<br/>";
+  out += this.toString();
+  out += "<br/><br/>Hands:<br/>"
+  for (var handIdx = 0, handCount = this.hands.length; handIdx != handCount; handIdx++) {
+    out += "  "+ this.hands[handIdx].toString() + "<br/>";
+  }
+  out += "<br/><br/>Pointables:<br/>";
+  for (var pointableIdx = 0, pointableCount = this.pointables.length; pointableIdx != pointableCount; pointableIdx++) {
+      out += "  "+ this.pointables[pointableIdx].toString() + "<br/>";
+  }
+  if (this.gestures) {
+    out += "<br/><br/>Gestures:<br/>";
+    for (var gestureIdx = 0, gestureCount = this.gestures.length; gestureIdx != gestureCount; gestureIdx++) {
+        out += "  "+ this.gestures[gestureIdx].toString() + "<br/>";
+    }
+  }
+  out += "<br/><br/>Raw JSON:<br/>";
+  out += JSON.stringify(this.data);
+  return out;
+}
+
+/**
+ * An invalid Frame object.
+ *
+ * You can use this invalid Frame in comparisons testing
+ * whether a given Frame instance is valid or invalid. (You can also check the
+ * [Frame.valid]{@link Leap.Frame#valid} property.)
+ *
+ * @static
+ * @type {Leap.Frame}
+ * @name Invalid
+ * @memberof Leap.Frame
+ */
+Frame.Invalid = {
+  valid: false,
+  hands: [],
+  fingers: [],
+  tools: [],
+  gestures: [],
+  pointables: [],
+  pointable: function() { return Pointable.Invalid },
+  finger: function() { return Pointable.Invalid },
+  hand: function() { return Hand.Invalid },
+  toString: function() { return "invalid frame" },
+  dump: function() { return this.toString() },
+  rotationAngle: function() { return 0.0; },
+  rotationMatrix: function() { return mat3.create(); },
+  rotationAxis: function() { return vec3.create(); },
+  scaleFactor: function() { return 1.0; },
+  translation: function() { return vec3.create(); }
+};
+
+},{"./finger":7,"./gesture":9,"./hand":10,"./interaction_box":12,"./pointable":14,"gl-matrix":23,"underscore":24}],9:[function(require,module,exports){
+var glMatrix = require("gl-matrix")
+  , vec3 = glMatrix.vec3
+  , EventEmitter = require('events').EventEmitter
+  , _ = require('underscore');
+
+/**
+ * Constructs a new Gesture object.
+ *
+ * An uninitialized Gesture object is considered invalid. Get valid instances
+ * of the Gesture class, which will be one of the Gesture subclasses, from a
+ * Frame object.
+ *
+ * @class Gesture
+ * @abstract
+ * @memberof Leap
+ * @classdesc
+ * The Gesture class represents a recognized movement by the user.
+ *
+ * The Leap watches the activity within its field of view for certain movement
+ * patterns typical of a user gesture or command. For example, a movement from side to
+ * side with the hand can indicate a swipe gesture, while a finger poking forward
+ * can indicate a screen tap gesture.
+ *
+ * When the Leap recognizes a gesture, it assigns an ID and adds a
+ * Gesture object to the frame gesture list. For continuous gestures, which
+ * occur over many frames, the Leap updates the gesture by adding
+ * a Gesture object having the same ID and updated properties in each
+ * subsequent frame.
+ *
+ * **Important:** Recognition for each type of gesture must be enabled;
+ * otherwise **no gestures are recognized or reported**.
+ *
+ * Subclasses of Gesture define the properties for the specific movement patterns
+ * recognized by the Leap.
+ *
+ * The Gesture subclasses for include:
+ *
+ * * CircleGesture -- A circular movement by a finger.
+ * * SwipeGesture -- A straight line movement by the hand with fingers extended.
+ * * ScreenTapGesture -- A forward tapping movement by a finger.
+ * * KeyTapGesture -- A downward tapping movement by a finger.
+ *
+ * Circle and swipe gestures are continuous and these objects can have a
+ * state of start, update, and stop.
+ *
+ * The screen tap gesture is a discrete gesture. The Leap only creates a single
+ * ScreenTapGesture object appears for each tap and it always has a stop state.
+ *
+ * Get valid Gesture instances from a Frame object. You can get a list of gestures
+ * from the Frame gestures array. You can also use the Frame gesture() method
+ * to find a gesture in the current frame using an ID value obtained in a
+ * previous frame.
+ *
+ * Gesture objects can be invalid. For example, when you get a gesture by ID
+ * using Frame.gesture(), and there is no gesture with that ID in the current
+ * frame, then gesture() returns an Invalid Gesture object (rather than a null
+ * value). Always check object validity in situations where a gesture might be
+ * invalid.
+ */
+var createGesture = exports.createGesture = function(data) {
+  var gesture;
+  switch (data.type) {
+    case 'circle':
+      gesture = new CircleGesture(data);
+      break;
+    case 'swipe':
+      gesture = new SwipeGesture(data);
+      break;
+    case 'screenTap':
+      gesture = new ScreenTapGesture(data);
+      break;
+    case 'keyTap':
+      gesture = new KeyTapGesture(data);
+      break;
+    default:
+      throw "unknown gesture type";
+  }
+
+ /**
+  * The gesture ID.
+  *
+  * All Gesture objects belonging to the same recognized movement share the
+  * same ID value. Use the ID value with the Frame::gesture() method to
+  * find updates related to this Gesture object in subsequent frames.
+  *
+  * @member id
+  * @memberof Leap.Gesture.prototype
+  * @type {number}
+  */
+  gesture.id = data.id;
+ /**
+  * The list of hands associated with this Gesture, if any.
+  *
+  * If no hands are related to this gesture, the list is empty.
+  *
+  * @member handIds
+  * @memberof Leap.Gesture.prototype
+  * @type {Array}
+  */
+  gesture.handIds = data.handIds.slice();
+ /**
+  * The list of fingers and tools associated with this Gesture, if any.
+  *
+  * If no Pointable objects are related to this gesture, the list is empty.
+  *
+  * @member pointableIds
+  * @memberof Leap.Gesture.prototype
+  * @type {Array}
+  */
+  gesture.pointableIds = data.pointableIds.slice();
+ /**
+  * The elapsed duration of the recognized movement up to the
+  * frame containing this Gesture object, in microseconds.
+  *
+  * The duration reported for the first Gesture in the sequence (with the
+  * start state) will typically be a small positive number since
+  * the movement must progress far enough for the Leap to recognize it as
+  * an intentional gesture.
+  *
+  * @member duration
+  * @memberof Leap.Gesture.prototype
+  * @type {number}
+  */
+  gesture.duration = data.duration;
+ /**
+  * The gesture ID.
+  *
+  * Recognized movements occur over time and have a beginning, a middle,
+  * and an end. The 'state()' attribute reports where in that sequence this
+  * Gesture object falls.
+  *
+  * Possible values for the state field are:
+  *
+  * * start
+  * * update
+  * * stop
+  *
+  * @member state
+  * @memberof Leap.Gesture.prototype
+  * @type {String}
+  */
+  gesture.state = data.state;
+ /**
+  * The gesture type.
+  *
+  * Possible values for the type field are:
+  *
+  * * circle
+  * * swipe
+  * * screenTap
+  * * keyTap
+  *
+  * @member type
+  * @memberof Leap.Gesture.prototype
+  * @type {String}
+  */
+  gesture.type = data.type;
+  return gesture;
+}
+
+/*
+ * Returns a builder object, which uses method chaining for gesture callback binding.
+ */
+var gestureListener = exports.gestureListener = function(controller, type) {
+  var handlers = {};
+  var gestureMap = {};
+
+  controller.on('gesture', function(gesture, frame) {
+    if (gesture.type == type) {
+      if (gesture.state == "start" || gesture.state == "stop") {
+        if (gestureMap[gesture.id] === undefined) {
+          var gestureTracker = new Gesture(gesture, frame);
+          gestureMap[gesture.id] = gestureTracker;
+          _.each(handlers, function(cb, name) {
+            gestureTracker.on(name, cb);
+          });
+        }
+      }
+      gestureMap[gesture.id].update(gesture, frame);
+      if (gesture.state == "stop") {
+        delete gestureMap[gesture.id];
+      }
+    }
+  });
+  var builder = {
+    start: function(cb) {
+      handlers['start'] = cb;
+      return builder;
+    },
+    stop: function(cb) {
+      handlers['stop'] = cb;
+      return builder;
+    },
+    complete: function(cb) {
+      handlers['stop'] = cb;
+      return builder;
+    },
+    update: function(cb) {
+      handlers['update'] = cb;
+      return builder;
+    }
+  }
+  return builder;
+}
+
+var Gesture = exports.Gesture = function(gesture, frame) {
+  this.gestures = [gesture];
+  this.frames = [frame];
+}
+
+Gesture.prototype.update = function(gesture, frame) {
+  this.lastGesture = gesture;
+  this.lastFrame = frame;
+  this.gestures.push(gesture);
+  this.frames.push(frame);
+  this.emit(gesture.state, this);
+}
+
+Gesture.prototype.translation = function() {
+  return vec3.subtract(vec3.create(), this.lastGesture.startPosition, this.lastGesture.position);
+}
+
+_.extend(Gesture.prototype, EventEmitter.prototype);
+
+/**
+ * Constructs a new CircleGesture object.
+ *
+ * An uninitialized CircleGesture object is considered invalid. Get valid instances
+ * of the CircleGesture class from a Frame object.
+ *
+ * @class CircleGesture
+ * @memberof Leap
+ * @augments Leap.Gesture
+ * @classdesc
+ * The CircleGesture classes represents a circular finger movement.
+ *
+ * A circle movement is recognized when the tip of a finger draws a circle
+ * within the Leap field of view.
+ *
+ * ![CircleGesture](images/Leap_Gesture_Circle.png)
+ *
+ * Circle gestures are continuous. The CircleGesture objects for the gesture have
+ * three possible states:
+ *
+ * * start -- The circle gesture has just started. The movement has
+ *  progressed far enough for the recognizer to classify it as a circle.
+ * * update -- The circle gesture is continuing.
+ * * stop -- The circle gesture is finished.
+ */
+var CircleGesture = function(data) {
+ /**
+  * The center point of the circle within the Leap frame of reference.
+  *
+  * @member center
+  * @memberof Leap.CircleGesture.prototype
+  * @type {number[]}
+  */
+  this.center = data.center;
+ /**
+  * The normal vector for the circle being traced.
+  *
+  * If you draw the circle clockwise, the normal vector points in the same
+  * general direction as the pointable object drawing the circle. If you draw
+  * the circle counterclockwise, the normal points back toward the
+  * pointable. If the angle between the normal and the pointable object
+  * drawing the circle is less than 90 degrees, then the circle is clockwise.
+  *
+  * ```javascript
+  *    var clockwiseness;
+  *    if (circle.pointable.direction.angleTo(circle.normal) <= PI/4) {
+  *        clockwiseness = "clockwise";
+  *    }
+  *    else
+  *    {
+  *        clockwiseness = "counterclockwise";
+  *    }
+  * ```
+  *
+  * @member normal
+  * @memberof Leap.CircleGesture.prototype
+  * @type {number[]}
+  */
+  this.normal = data.normal;
+ /**
+  * The number of times the finger tip has traversed the circle.
+  *
+  * Progress is reported as a positive number of the number. For example,
+  * a progress value of .5 indicates that the finger has gone halfway
+  * around, while a value of 3 indicates that the finger has gone around
+  * the the circle three times.
+  *
+  * Progress starts where the circle gesture began. Since the circle
+  * must be partially formed before the Leap can recognize it, progress
+  * will be greater than zero when a circle gesture first appears in the
+  * frame.
+  *
+  * @member progress
+  * @memberof Leap.CircleGesture.prototype
+  * @type {number}
+  */
+  this.progress = data.progress;
+ /**
+  * The radius of the circle in mm.
+  *
+  * @member radius
+  * @memberof Leap.CircleGesture.prototype
+  * @type {number}
+  */
+  this.radius = data.radius;
+}
+
+CircleGesture.prototype.toString = function() {
+  return "CircleGesture ["+JSON.stringify(this)+"]";
+}
+
+/**
+ * Constructs a new SwipeGesture object.
+ *
+ * An uninitialized SwipeGesture object is considered invalid. Get valid instances
+ * of the SwipeGesture class from a Frame object.
+ *
+ * @class SwipeGesture
+ * @memberof Leap
+ * @augments Leap.Gesture
+ * @classdesc
+ * The SwipeGesture class represents a swiping motion of a finger or tool.
+ *
+ * ![SwipeGesture](images/Leap_Gesture_Swipe.png)
+ *
+ * Swipe gestures are continuous.
+ */
+var SwipeGesture = function(data) {
+ /**
+  * The starting position within the Leap frame of
+  * reference, in mm.
+  *
+  * @member startPosition
+  * @memberof Leap.SwipeGesture.prototype
+  * @type {number[]}
+  */
+  this.startPosition = data.startPosition;
+ /**
+  * The current swipe position within the Leap frame of
+  * reference, in mm.
+  *
+  * @member position
+  * @memberof Leap.SwipeGesture.prototype
+  * @type {number[]}
+  */
+  this.position = data.position;
+ /**
+  * The unit direction vector parallel to the swipe motion.
+  *
+  * You can compare the components of the vector to classify the swipe as
+  * appropriate for your application. For example, if you are using swipes
+  * for two dimensional scrolling, you can compare the x and y values to
+  * determine if the swipe is primarily horizontal or vertical.
+  *
+  * @member direction
+  * @memberof Leap.SwipeGesture.prototype
+  * @type {number[]}
+  */
+  this.direction = data.direction;
+ /**
+  * The speed of the finger performing the swipe gesture in
+  * millimeters per second.
+  *
+  * @member speed
+  * @memberof Leap.SwipeGesture.prototype
+  * @type {number}
+  */
+  this.speed = data.speed;
+}
+
+SwipeGesture.prototype.toString = function() {
+  return "SwipeGesture ["+JSON.stringify(this)+"]";
+}
+
+/**
+ * Constructs a new ScreenTapGesture object.
+ *
+ * An uninitialized ScreenTapGesture object is considered invalid. Get valid instances
+ * of the ScreenTapGesture class from a Frame object.
+ *
+ * @class ScreenTapGesture
+ * @memberof Leap
+ * @augments Leap.Gesture
+ * @classdesc
+ * The ScreenTapGesture class represents a tapping gesture by a finger or tool.
+ *
+ * A screen tap gesture is recognized when the tip of a finger pokes forward
+ * and then springs back to approximately the original postion, as if
+ * tapping a vertical screen. The tapping finger must pause briefly before beginning the tap.
+ *
+ * ![ScreenTap](images/Leap_Gesture_Tap2.png)
+ *
+ * ScreenTap gestures are discrete. The ScreenTapGesture object representing a tap always
+ * has the state, STATE_STOP. Only one ScreenTapGesture object is created for each
+ * screen tap gesture recognized.
+ */
+var ScreenTapGesture = function(data) {
+ /**
+  * The position where the screen tap is registered.
+  *
+  * @member position
+  * @memberof Leap.ScreenTapGesture.prototype
+  * @type {number[]}
+  */
+  this.position = data.position;
+ /**
+  * The direction of finger tip motion.
+  *
+  * @member direction
+  * @memberof Leap.ScreenTapGesture.prototype
+  * @type {number[]}
+  */
+  this.direction = data.direction;
+ /**
+  * The progess value is always 1.0 for a screen tap gesture.
+  *
+  * @member progress
+  * @memberof Leap.ScreenTapGesture.prototype
+  * @type {number}
+  */
+  this.progress = data.progress;
+}
+
+ScreenTapGesture.prototype.toString = function() {
+  return "ScreenTapGesture ["+JSON.stringify(this)+"]";
+}
+
+/**
+ * Constructs a new KeyTapGesture object.
+ *
+ * An uninitialized KeyTapGesture object is considered invalid. Get valid instances
+ * of the KeyTapGesture class from a Frame object.
+ *
+ * @class KeyTapGesture
+ * @memberof Leap
+ * @augments Leap.Gesture
+ * @classdesc
+ * The KeyTapGesture class represents a tapping gesture by a finger or tool.
+ *
+ * A key tap gesture is recognized when the tip of a finger rotates down toward the
+ * palm and then springs back to approximately the original postion, as if
+ * tapping. The tapping finger must pause briefly before beginning the tap.
+ *
+ * ![KeyTap](images/Leap_Gesture_Tap.png)
+ *
+ * Key tap gestures are discrete. The KeyTapGesture object representing a tap always
+ * has the state, STATE_STOP. Only one KeyTapGesture object is created for each
+ * key tap gesture recognized.
+ */
+var KeyTapGesture = function(data) {
+    /**
+     * The position where the key tap is registered.
+     *
+     * @member position
+     * @memberof Leap.KeyTapGesture.prototype
+     * @type {number[]}
+     */
+    this.position = data.position;
+    /**
+     * The direction of finger tip motion.
+     *
+     * @member direction
+     * @memberof Leap.KeyTapGesture.prototype
+     * @type {number[]}
+     */
+    this.direction = data.direction;
+    /**
+     * The progess value is always 1.0 for a key tap gesture.
+     *
+     * @member progress
+     * @memberof Leap.KeyTapGesture.prototype
+     * @type {number}
+     */
+    this.progress = data.progress;
+}
+
+KeyTapGesture.prototype.toString = function() {
+  return "KeyTapGesture ["+JSON.stringify(this)+"]";
+}
+
+},{"events":21,"gl-matrix":23,"underscore":24}],10:[function(require,module,exports){
+var Pointable = require("./pointable")
+  , Bone = require('./bone')
+  , glMatrix = require("gl-matrix")
+  , mat3 = glMatrix.mat3
+  , vec3 = glMatrix.vec3
+  , _ = require("underscore");
+
+/**
+ * Constructs a Hand object.
+ *
+ * An uninitialized hand is considered invalid.
+ * Get valid Hand objects from a Frame object.
+ * @class Hand
+ * @memberof Leap
+ * @classdesc
+ * The Hand class reports the physical characteristics of a detected hand.
+ *
+ * Hand tracking data includes a palm position and velocity; vectors for
+ * the palm normal and direction to the fingers; properties of a sphere fit
+ * to the hand; and lists of the attached fingers and tools.
+ *
+ * Note that Hand objects can be invalid, which means that they do not contain
+ * valid tracking data and do not correspond to a physical entity. Invalid Hand
+ * objects can be the result of asking for a Hand object using an ID from an
+ * earlier frame when no Hand objects with that ID exist in the current frame.
+ * A Hand object created from the Hand constructor is also invalid.
+ * Test for validity with the [Hand.valid]{@link Leap.Hand#valid} property.
+ */
+var Hand = module.exports = function(data) {
+  /**
+   * A unique ID assigned to this Hand object, whose value remains the same
+   * across consecutive frames while the tracked hand remains visible. If
+   * tracking is lost (for example, when a hand is occluded by another hand
+   * or when it is withdrawn from or reaches the edge of the Leap field of view),
+   * the Leap may assign a new ID when it detects the hand in a future frame.
+   *
+   * Use the ID value with the {@link Frame.hand}() function to find this
+   * Hand object in future frames.
+   *
+   * @member id
+   * @memberof Leap.Hand.prototype
+   * @type {String}
+   */
+  this.id = data.id;
+  /**
+   * The center position of the palm in millimeters from the Leap origin.
+   * @member palmPosition
+   * @memberof Leap.Hand.prototype
+   * @type {number[]}
+   */
+  this.palmPosition = data.palmPosition;
+  /**
+   * The direction from the palm position toward the fingers.
+   *
+   * The direction is expressed as a unit vector pointing in the same
+   * direction as the directed line from the palm position to the fingers.
+   *
+   * @member direction
+   * @memberof Leap.Hand.prototype
+   * @type {number[]}
+   */
+  this.direction = data.direction;
+  /**
+   * The rate of change of the palm position in millimeters/second.
+   *
+   * @member palmVeclocity
+   * @memberof Leap.Hand.prototype
+   * @type {number[]}
+   */
+  this.palmVelocity = data.palmVelocity;
+  /**
+   * The normal vector to the palm. If your hand is flat, this vector will
+   * point downward, or "out" of the front surface of your palm.
+   *
+   * ![Palm Vectors](images/Leap_Palm_Vectors.png)
+   *
+   * The direction is expressed as a unit vector pointing in the same
+   * direction as the palm normal (that is, a vector orthogonal to the palm).
+   * @member palmNormal
+   * @memberof Leap.Hand.prototype
+   * @type {number[]}
+   */
+  this.palmNormal = data.palmNormal;
+  /**
+   * The center of a sphere fit to the curvature of this hand.
+   *
+   * This sphere is placed roughly as if the hand were holding a ball.
+   *
+   * ![Hand Ball](images/Leap_Hand_Ball.png)
+   * @member sphereCenter
+   * @memberof Leap.Hand.prototype
+   * @type {number[]}
+   */
+  this.sphereCenter = data.sphereCenter;
+  /**
+   * The radius of a sphere fit to the curvature of this hand, in millimeters.
+   *
+   * This sphere is placed roughly as if the hand were holding a ball. Thus the
+   * size of the sphere decreases as the fingers are curled into a fist.
+   *
+   * @member sphereRadius
+   * @memberof Leap.Hand.prototype
+   * @type {number}
+   */
+  this.sphereRadius = data.sphereRadius;
+  /**
+   * Reports whether this is a valid Hand object.
+   *
+   * @member valid
+   * @memberof Leap.Hand.prototype
+   * @type {boolean}
+   */
+  this.valid = true;
+  /**
+   * The list of Pointable objects (fingers and tools) detected in this frame
+   * that are associated with this hand, given in arbitrary order. The list
+   * can be empty if no fingers or tools associated with this hand are detected.
+   *
+   * Use the {@link Pointable} tool property to determine
+   * whether or not an item in the list represents a tool or finger.
+   * You can also get only the tools using the Hand.tools[] list or
+   * only the fingers using the Hand.fingers[] list.
+   *
+   * @member pointables[]
+   * @memberof Leap.Hand.prototype
+   * @type {Leap.Pointable[]}
+   */
+  this.pointables = [];
+  /**
+   * The list of fingers detected in this frame that are attached to
+   * this hand, given in arbitrary order.
+   *
+   * The list can be empty if no fingers attached to this hand are detected.
+   *
+   * @member fingers[]
+   * @memberof Leap.Hand.prototype
+   * @type {Leap.Pointable[]}
+   */
+  this.fingers = [];
+  
+  if (data.armBasis){
+    this.arm = new Bone(this, {
+      type: 4,
+      width: data.armWidth,
+      prevJoint: data.elbow,
+      nextJoint: data.wrist,
+      basis: data.armBasis
+    });
+  }else{
+    this.arm = null;
+  }
+  
+  /**
+   * The list of tools detected in this frame that are held by this
+   * hand, given in arbitrary order.
+   *
+   * The list can be empty if no tools held by this hand are detected.
+   *
+   * @member tools[]
+   * @memberof Leap.Hand.prototype
+   * @type {Leap.Pointable[]}
+   */
+  this.tools = [];
+  this._translation = data.t;
+  this._rotation = _.flatten(data.r);
+  this._scaleFactor = data.s;
+
+  /**
+   * Time the hand has been visible in seconds.
+   *
+   * @member timeVisible
+   * @memberof Leap.Hand.prototype
+   * @type {number}
+   */
+   this.timeVisible = data.timeVisible;
+
+  /**
+   * The palm position with stabalization
+   * @member stabilizedPalmPosition
+   * @memberof Leap.Hand.prototype
+   * @type {number[]}
+   */
+   this.stabilizedPalmPosition = data.stabilizedPalmPosition;
+
+   /**
+   * Reports whether this is a left or a right hand.
+   *
+   * @member type
+   * @type {String}
+   * @memberof Leap.Hand.prototype
+   */
+   this.type = data.type;
+   this.grabStrength = data.grabStrength;
+   this.pinchStrength = data.pinchStrength;
+   this.confidence = data.confidence;
+}
+
+/**
+ * The finger with the specified ID attached to this hand.
+ *
+ * Use this function to retrieve a Pointable object representing a finger
+ * attached to this hand using an ID value obtained from a previous frame.
+ * This function always returns a Pointable object, but if no finger
+ * with the specified ID is present, an invalid Pointable object is returned.
+ *
+ * Note that the ID values assigned to fingers persist across frames, but only
+ * until tracking of a particular finger is lost. If tracking of a finger is
+ * lost and subsequently regained, the new Finger object representing that
+ * finger may have a different ID than that representing the finger in an
+ * earlier frame.
+ *
+ * @method finger
+ * @memberof Leap.Hand.prototype
+ * @param {String} id The ID value of a finger from a previous frame.
+ * @returns {Leap.Pointable} The Finger object with
+ * the matching ID if one exists for this hand in this frame; otherwise, an
+ * invalid Finger object is returned.
+ */
+Hand.prototype.finger = function(id) {
+  var finger = this.frame.finger(id);
+  return (finger && (finger.handId == this.id)) ? finger : Pointable.Invalid;
+}
+
+/**
+ * The angle of rotation around the rotation axis derived from the change in
+ * orientation of this hand, and any associated fingers and tools, between the
+ * current frame and the specified frame.
+ *
+ * The returned angle is expressed in radians measured clockwise around the
+ * rotation axis (using the right-hand rule) between the start and end frames.
+ * The value is always between 0 and pi radians (0 and 180 degrees).
+ *
+ * If a corresponding Hand object is not found in sinceFrame, or if either
+ * this frame or sinceFrame are invalid Frame objects, then the angle of rotation is zero.
+ *
+ * @method rotationAngle
+ * @memberof Leap.Hand.prototype
+ * @param {Leap.Frame} sinceFrame The starting frame for computing the relative rotation.
+ * @param {numnber[]} [axis] The axis to measure rotation around.
+ * @returns {number} A positive value representing the heuristically determined
+ * rotational change of the hand between the current frame and that specified in
+ * the sinceFrame parameter.
+ */
+Hand.prototype.rotationAngle = function(sinceFrame, axis) {
+  if (!this.valid || !sinceFrame.valid) return 0.0;
+  var sinceHand = sinceFrame.hand(this.id);
+  if(!sinceHand.valid) return 0.0;
+  var rot = this.rotationMatrix(sinceFrame);
+  var cs = (rot[0] + rot[4] + rot[8] - 1.0)*0.5
+  var angle = Math.acos(cs);
+  angle = isNaN(angle) ? 0.0 : angle;
+  if (axis !== undefined) {
+    var rotAxis = this.rotationAxis(sinceFrame);
+    angle *= vec3.dot(rotAxis, vec3.normalize(vec3.create(), axis));
+  }
+  return angle;
+}
+
+/**
+ * The axis of rotation derived from the change in orientation of this hand, and
+ * any associated fingers and tools, between the current frame and the specified frame.
+ *
+ * The returned direction vector is normalized.
+ *
+ * If a corresponding Hand object is not found in sinceFrame, or if either
+ * this frame or sinceFrame are invalid Frame objects, then this method returns a zero vector.
+ *
+ * @method rotationAxis
+ * @memberof Leap.Hand.prototype
+ * @param {Leap.Frame} sinceFrame The starting frame for computing the relative rotation.
+ * @returns {number[]} A normalized direction Vector representing the axis of the heuristically determined
+ * rotational change of the hand between the current frame and that specified in the sinceFrame parameter.
+ */
+Hand.prototype.rotationAxis = function(sinceFrame) {
+  if (!this.valid || !sinceFrame.valid) return vec3.create();
+  var sinceHand = sinceFrame.hand(this.id);
+  if (!sinceHand.valid) return vec3.create();
+  return vec3.normalize(vec3.create(), [
+    this._rotation[7] - sinceHand._rotation[5],
+    this._rotation[2] - sinceHand._rotation[6],
+    this._rotation[3] - sinceHand._rotation[1]
+  ]);
+}
+
+/**
+ * The transform matrix expressing the rotation derived from the change in
+ * orientation of this hand, and any associated fingers and tools, between
+ * the current frame and the specified frame.
+ *
+ * If a corresponding Hand object is not found in sinceFrame, or if either
+ * this frame or sinceFrame are invalid Frame objects, then this method returns
+ * an identity matrix.
+ *
+ * @method rotationMatrix
+ * @memberof Leap.Hand.prototype
+ * @param {Leap.Frame} sinceFrame The starting frame for computing the relative rotation.
+ * @returns {number[]} A transformation Matrix containing the heuristically determined
+ * rotational change of the hand between the current frame and that specified in the sinceFrame parameter.
+ */
+Hand.prototype.rotationMatrix = function(sinceFrame) {
+  if (!this.valid || !sinceFrame.valid) return mat3.create();
+  var sinceHand = sinceFrame.hand(this.id);
+  if(!sinceHand.valid) return mat3.create();
+  var transpose = mat3.transpose(mat3.create(), this._rotation);
+  var m = mat3.multiply(mat3.create(), sinceHand._rotation, transpose);
+  return m;
+}
+
+/**
+ * The scale factor derived from the hand's motion between the current frame and the specified frame.
+ *
+ * The scale factor is always positive. A value of 1.0 indicates no scaling took place.
+ * Values between 0.0 and 1.0 indicate contraction and values greater than 1.0 indicate expansion.
+ *
+ * The Leap derives scaling from the relative inward or outward motion of a hand
+ * and its associated fingers and tools (independent of translation and rotation).
+ *
+ * If a corresponding Hand object is not found in sinceFrame, or if either this frame or sinceFrame
+ * are invalid Frame objects, then this method returns 1.0.
+ *
+ * @method scaleFactor
+ * @memberof Leap.Hand.prototype
+ * @param {Leap.Frame} sinceFrame The starting frame for computing the relative scaling.
+ * @returns {number} A positive value representing the heuristically determined
+ * scaling change ratio of the hand between the current frame and that specified in the sinceFrame parameter.
+ */
+Hand.prototype.scaleFactor = function(sinceFrame) {
+  if (!this.valid || !sinceFrame.valid) return 1.0;
+  var sinceHand = sinceFrame.hand(this.id);
+  if(!sinceHand.valid) return 1.0;
+
+  return Math.exp(this._scaleFactor - sinceHand._scaleFactor);
+}
+
+/**
+ * The change of position of this hand between the current frame and the specified frame
+ *
+ * The returned translation vector provides the magnitude and direction of the
+ * movement in millimeters.
+ *
+ * If a corresponding Hand object is not found in sinceFrame, or if either this frame or
+ * sinceFrame are invalid Frame objects, then this method returns a zero vector.
+ *
+ * @method translation
+ * @memberof Leap.Hand.prototype
+ * @param {Leap.Frame} sinceFrame The starting frame for computing the relative translation.
+ * @returns {number[]} A Vector representing the heuristically determined change in hand
+ * position between the current frame and that specified in the sinceFrame parameter.
+ */
+Hand.prototype.translation = function(sinceFrame) {
+  if (!this.valid || !sinceFrame.valid) return vec3.create();
+  var sinceHand = sinceFrame.hand(this.id);
+  if(!sinceHand.valid) return vec3.create();
+  return [
+    this._translation[0] - sinceHand._translation[0],
+    this._translation[1] - sinceHand._translation[1],
+    this._translation[2] - sinceHand._translation[2]
+  ];
+}
+
+/**
+ * A string containing a brief, human readable description of the Hand object.
+ * @method toString
+ * @memberof Leap.Hand.prototype
+ * @returns {String} A description of the Hand as a string.
+ */
+Hand.prototype.toString = function() {
+  return "Hand (" + this.type + ") [ id: "+ this.id + " | palm velocity:"+this.palmVelocity+" | sphere center:"+this.sphereCenter+" ] ";
+}
+
+/**
+ * The pitch angle in radians.
+ *
+ * Pitch is the angle between the negative z-axis and the projection of
+ * the vector onto the y-z plane. In other words, pitch represents rotation
+ * around the x-axis.
+ * If the vector points upward, the returned angle is between 0 and pi radians
+ * (180 degrees); if it points downward, the angle is between 0 and -pi radians.
+ *
+ * @method pitch
+ * @memberof Leap.Hand.prototype
+ * @returns {number} The angle of this vector above or below the horizon (x-z plane).
+ *
+ */
+Hand.prototype.pitch = function() {
+  return Math.atan2(this.direction[1], -this.direction[2]);
+}
+
+/**
+ *  The yaw angle in radians.
+ *
+ * Yaw is the angle between the negative z-axis and the projection of
+ * the vector onto the x-z plane. In other words, yaw represents rotation
+ * around the y-axis. If the vector points to the right of the negative z-axis,
+ * then the returned angle is between 0 and pi radians (180 degrees);
+ * if it points to the left, the angle is between 0 and -pi radians.
+ *
+ * @method yaw
+ * @memberof Leap.Hand.prototype
+ * @returns {number} The angle of this vector to the right or left of the y-axis.
+ *
+ */
+Hand.prototype.yaw = function() {
+  return Math.atan2(this.direction[0], -this.direction[2]);
+}
+
+/**
+ *  The roll angle in radians.
+ *
+ * Roll is the angle between the y-axis and the projection of
+ * the vector onto the x-y plane. In other words, roll represents rotation
+ * around the z-axis. If the vector points to the left of the y-axis,
+ * then the returned angle is between 0 and pi radians (180 degrees);
+ * if it points to the right, the angle is between 0 and -pi radians.
+ *
+ * @method roll
+ * @memberof Leap.Hand.prototype
+ * @returns {number} The angle of this vector to the right or left of the y-axis.
+ *
+ */
+Hand.prototype.roll = function() {
+  return Math.atan2(this.palmNormal[0], -this.palmNormal[1]);
+}
+
+/**
+ * An invalid Hand object.
+ *
+ * You can use an invalid Hand object in comparisons testing
+ * whether a given Hand instance is valid or invalid. (You can also use the
+ * Hand valid property.)
+ *
+ * @static
+ * @type {Leap.Hand}
+ * @name Invalid
+ * @memberof Leap.Hand
+ */
+Hand.Invalid = {
+  valid: false,
+  fingers: [],
+  tools: [],
+  pointables: [],
+  left: false,
+  pointable: function() { return Pointable.Invalid },
+  finger: function() { return Pointable.Invalid },
+  toString: function() { return "invalid frame" },
+  dump: function() { return this.toString(); },
+  rotationAngle: function() { return 0.0; },
+  rotationMatrix: function() { return mat3.create(); },
+  rotationAxis: function() { return vec3.create(); },
+  scaleFactor: function() { return 1.0; },
+  translation: function() { return vec3.create(); }
+};
+
+},{"./bone":1,"./pointable":14,"gl-matrix":23,"underscore":24}],11:[function(require,module,exports){
+/**
+ * Leap is the global namespace of the Leap API.
+ * @namespace Leap
+ */
+module.exports = {
+  Controller: require("./controller"),
+  Frame: require("./frame"),
+  Gesture: require("./gesture"),
+  Hand: require("./hand"),
+  Pointable: require("./pointable"),
+  Finger: require("./finger"),
+  InteractionBox: require("./interaction_box"),
+  CircularBuffer: require("./circular_buffer"),
+  UI: require("./ui"),
+  JSONProtocol: require("./protocol").JSONProtocol,
+  glMatrix: require("gl-matrix"),
+  mat3: require("gl-matrix").mat3,
+  vec3: require("gl-matrix").vec3,
+  loopController: undefined,
+  version: require('./version.js'),
+
+  /**
+   * Expose utility libraries for convenience
+   * Use carefully - they may be subject to upgrade or removal in different versions of LeapJS.
+   *
+   */
+  _: require('underscore'),
+  EventEmitter: require('events').EventEmitter,
+
+  /**
+   * The Leap.loop() function passes a frame of Leap data to your
+   * callback function and then calls window.requestAnimationFrame() after
+   * executing your callback function.
+   *
+   * Leap.loop() sets up the Leap controller and WebSocket connection for you.
+   * You do not need to create your own controller when using this method.
+   *
+   * Your callback function is called on an interval determined by the client
+   * browser. Typically, this is on an interval of 60 frames/second. The most
+   * recent frame of Leap data is passed to your callback function. If the Leap
+   * is producing frames at a slower rate than the browser frame rate, the same
+   * frame of Leap data can be passed to your function in successive animation
+   * updates.
+   *
+   * As an alternative, you can create your own Controller object and use a
+   * {@link Controller#onFrame onFrame} callback to process the data at
+   * the frame rate of the Leap device. See {@link Controller} for an
+   * example.
+   *
+   * @method Leap.loop
+   * @param {function} callback A function called when the browser is ready to
+   * draw to the screen. The most recent {@link Frame} object is passed to
+   * your callback function.
+   *
+   * ```javascript
+   *    Leap.loop( function( frame ) {
+   *        // ... your code here
+   *    })
+   * ```
+   */
+  loop: function(opts, callback) {
+    if (opts && callback === undefined &&  ( ({}).toString.call(opts) === '[object Function]' ) ) {
+      callback = opts;
+      opts = {};
+    }
+
+    if (this.loopController) {
+      if (opts){
+        this.loopController.setupFrameEvents(opts);
+      }
+    }else{
+      this.loopController = new this.Controller(opts);
+    }
+
+    this.loopController.loop(callback);
+    return this.loopController;
+  },
+
+  /*
+   * Convenience method for Leap.Controller.plugin
+   */
+  plugin: function(name, options){
+    this.Controller.plugin(name, options)
+  }
+}
+
+},{"./circular_buffer":2,"./controller":5,"./finger":7,"./frame":8,"./gesture":9,"./hand":10,"./interaction_box":12,"./pointable":14,"./protocol":15,"./ui":16,"./version.js":19,"events":21,"gl-matrix":23,"underscore":24}],12:[function(require,module,exports){
+var glMatrix = require("gl-matrix")
+  , vec3 = glMatrix.vec3;
+
+/**
+ * Constructs a InteractionBox object.
+ *
+ * @class InteractionBox
+ * @memberof Leap
+ * @classdesc
+ * The InteractionBox class represents a box-shaped region completely within
+ * the field of view of the Leap Motion controller.
+ *
+ * The interaction box is an axis-aligned rectangular prism and provides
+ * normalized coordinates for hands, fingers, and tools within this box.
+ * The InteractionBox class can make it easier to map positions in the
+ * Leap Motion coordinate system to 2D or 3D coordinate systems used
+ * for application drawing.
+ *
+ * ![Interaction Box](images/Leap_InteractionBox.png)
+ *
+ * The InteractionBox region is defined by a center and dimensions along the x, y, and z axes.
+ */
+var InteractionBox = module.exports = function(data) {
+  /**
+   * Indicates whether this is a valid InteractionBox object.
+   *
+   * @member valid
+   * @type {Boolean}
+   * @memberof Leap.InteractionBox.prototype
+   */
+  this.valid = true;
+  /**
+   * The center of the InteractionBox in device coordinates (millimeters).
+   * This point is equidistant from all sides of the box.
+   *
+   * @member center
+   * @type {number[]}
+   * @memberof Leap.InteractionBox.prototype
+   */
+  this.center = data.center;
+
+  this.size = data.size;
+  /**
+   * The width of the InteractionBox in millimeters, measured along the x-axis.
+   *
+   * @member width
+   * @type {number}
+   * @memberof Leap.InteractionBox.prototype
+   */
+  this.width = data.size[0];
+  /**
+   * The height of the InteractionBox in millimeters, measured along the y-axis.
+   *
+   * @member height
+   * @type {number}
+   * @memberof Leap.InteractionBox.prototype
+   */
+  this.height = data.size[1];
+  /**
+   * The depth of the InteractionBox in millimeters, measured along the z-axis.
+   *
+   * @member depth
+   * @type {number}
+   * @memberof Leap.InteractionBox.prototype
+   */
+  this.depth = data.size[2];
+}
+
+/**
+ * Converts a position defined by normalized InteractionBox coordinates
+ * into device coordinates in millimeters.
+ *
+ * This function performs the inverse of normalizePoint().
+ *
+ * @method denormalizePoint
+ * @memberof Leap.InteractionBox.prototype
+ * @param {number[]} normalizedPosition The input position in InteractionBox coordinates.
+ * @returns {number[]} The corresponding denormalized position in device coordinates.
+ */
+InteractionBox.prototype.denormalizePoint = function(normalizedPosition) {
+  return vec3.fromValues(
+    (normalizedPosition[0] - 0.5) * this.size[0] + this.center[0],
+    (normalizedPosition[1] - 0.5) * this.size[1] + this.center[1],
+    (normalizedPosition[2] - 0.5) * this.size[2] + this.center[2]
+  );
+}
+
+/**
+ * Normalizes the coordinates of a point using the interaction box.
+ *
+ * Coordinates from the Leap Motion frame of reference (millimeters) are
+ * converted to a range of [0..1] such that the minimum value of the
+ * InteractionBox maps to 0 and the maximum value of the InteractionBox maps to 1.
+ *
+ * @method normalizePoint
+ * @memberof Leap.InteractionBox.prototype
+ * @param {number[]} position The input position in device coordinates.
+ * @param {Boolean} clamp Whether or not to limit the output value to the range [0,1]
+ * when the input position is outside the InteractionBox. Defaults to true.
+ * @returns {number[]} The normalized position.
+ */
+InteractionBox.prototype.normalizePoint = function(position, clamp) {
+  var vec = vec3.fromValues(
+    ((position[0] - this.center[0]) / this.size[0]) + 0.5,
+    ((position[1] - this.center[1]) / this.size[1]) + 0.5,
+    ((position[2] - this.center[2]) / this.size[2]) + 0.5
+  );
+
+  if (clamp) {
+    vec[0] = Math.min(Math.max(vec[0], 0), 1);
+    vec[1] = Math.min(Math.max(vec[1], 0), 1);
+    vec[2] = Math.min(Math.max(vec[2], 0), 1);
+  }
+  return vec;
+}
+
+/**
+ * Writes a brief, human readable description of the InteractionBox object.
+ *
+ * @method toString
+ * @memberof Leap.InteractionBox.prototype
+ * @returns {String} A description of the InteractionBox object as a string.
+ */
+InteractionBox.prototype.toString = function() {
+  return "InteractionBox [ width:" + this.width + " | height:" + this.height + " | depth:" + this.depth + " ]";
+}
+
+/**
+ * An invalid InteractionBox object.
+ *
+ * You can use this InteractionBox instance in comparisons testing
+ * whether a given InteractionBox instance is valid or invalid. (You can also use the
+ * InteractionBox.valid property.)
+ *
+ * @static
+ * @type {Leap.InteractionBox}
+ * @name Invalid
+ * @memberof Leap.InteractionBox
+ */
+InteractionBox.Invalid = { valid: false };
+
+},{"gl-matrix":23}],13:[function(require,module,exports){
+var Pipeline = module.exports = function (controller) {
+  this.steps = [];
+  this.controller = controller;
+}
+
+Pipeline.prototype.addStep = function (step) {
+  this.steps.push(step);
+}
+
+Pipeline.prototype.run = function (frame) {
+  var stepsLength = this.steps.length;
+  for (var i = 0; i != stepsLength; i++) {
+    if (!frame) break;
+    frame = this.steps[i](frame);
+  }
+  return frame;
+}
+
+Pipeline.prototype.removeStep = function(step){
+  var index = this.steps.indexOf(step);
+  if (index === -1) throw "Step not found in pipeline";
+  this.steps.splice(index, 1);
+}
+
+/*
+ * Wraps a plugin callback method in method which can be run inside the pipeline.
+ * This wrapper method loops the callback over objects within the frame as is appropriate,
+ * calling the callback for each in turn.
+ *
+ * @method createStepFunction
+ * @memberOf Leap.Controller.prototype
+ * @param {Controller} The controller on which the callback is called.
+ * @param {String} type What frame object the callback is run for and receives.
+ *       Can be one of 'frame', 'finger', 'hand', 'pointable', 'tool'
+ * @param {function} callback The method which will be run inside the pipeline loop.  Receives one argument, such as a hand.
+ * @private
+ */
+Pipeline.prototype.addWrappedStep = function (type, callback) {
+  var controller = this.controller,
+    step = function (frame) {
+      var dependencies, i, len;
+      dependencies = (type == 'frame') ? [frame] : (frame[type + 's'] || []);
+
+      for (i = 0, len = dependencies.length; i < len; i++) {
+        callback.call(controller, dependencies[i]);
+      }
+
+      return frame;
+    };
+
+  this.addStep(step);
+  return step;
+};
+},{}],14:[function(require,module,exports){
+var glMatrix = require("gl-matrix")
+  , vec3 = glMatrix.vec3;
+
+/**
+ * Constructs a Pointable object.
+ *
+ * An uninitialized pointable is considered invalid.
+ * Get valid Pointable objects from a Frame or a Hand object.
+ *
+ * @class Pointable
+ * @memberof Leap
+ * @classdesc
+ * The Pointable class reports the physical characteristics of a detected
+ * finger or tool.
+ *
+ * Both fingers and tools are classified as Pointable objects. Use the
+ * Pointable.tool property to determine whether a Pointable object represents a
+ * tool or finger. The Leap classifies a detected entity as a tool when it is
+ * thinner, straighter, and longer than a typical finger.
+ *
+ * Note that Pointable objects can be invalid, which means that they do not
+ * contain valid tracking data and do not correspond to a physical entity.
+ * Invalid Pointable objects can be the result of asking for a Pointable object
+ * using an ID from an earlier frame when no Pointable objects with that ID
+ * exist in the current frame. A Pointable object created from the Pointable
+ * constructor is also invalid. Test for validity with the Pointable.valid
+ * property.
+ */
+var Pointable = module.exports = function(data) {
+  /**
+   * Indicates whether this is a valid Pointable object.
+   *
+   * @member valid
+   * @type {Boolean}
+   * @memberof Leap.Pointable.prototype
+   */
+  this.valid = true;
+  /**
+   * A unique ID assigned to this Pointable object, whose value remains the
+   * same across consecutive frames while the tracked finger or tool remains
+   * visible. If tracking is lost (for example, when a finger is occluded by
+   * another finger or when it is withdrawn from the Leap field of view), the
+   * Leap may assign a new ID when it detects the entity in a future frame.
+   *
+   * Use the ID value with the pointable() functions defined for the
+   * {@link Frame} and {@link Frame.Hand} classes to find this
+   * Pointable object in future frames.
+   *
+   * @member id
+   * @type {String}
+   * @memberof Leap.Pointable.prototype
+   */
+  this.id = data.id;
+  this.handId = data.handId;
+  /**
+   * The estimated length of the finger or tool in millimeters.
+   *
+   * The reported length is the visible length of the finger or tool from the
+   * hand to tip. If the length isn't known, then a value of 0 is returned.
+   *
+   * @member length
+   * @type {number}
+   * @memberof Leap.Pointable.prototype
+   */
+  this.length = data.length;
+  /**
+   * Whether or not the Pointable is believed to be a tool.
+   * Tools are generally longer, thinner, and straighter than fingers.
+   *
+   * If tool is false, then this Pointable must be a finger.
+   *
+   * @member tool
+   * @type {Boolean}
+   * @memberof Leap.Pointable.prototype
+   */
+  this.tool = data.tool;
+  /**
+   * The estimated width of the tool in millimeters.
+   *
+   * The reported width is the average width of the visible portion of the
+   * tool from the hand to the tip. If the width isn't known,
+   * then a value of 0 is returned.
+   *
+   * Pointable objects representing fingers do not have a width property.
+   *
+   * @member width
+   * @type {number}
+   * @memberof Leap.Pointable.prototype
+   */
+  this.width = data.width;
+  /**
+   * The direction in which this finger or tool is pointing.
+   *
+   * The direction is expressed as a unit vector pointing in the same
+   * direction as the tip.
+   *
+   * ![Finger](images/Leap_Finger_Model.png)
+   * @member direction
+   * @type {number[]}
+   * @memberof Leap.Pointable.prototype
+   */
+  this.direction = data.direction;
+  /**
+   * The tip position in millimeters from the Leap origin.
+   * Stabilized
+   *
+   * @member stabilizedTipPosition
+   * @type {number[]}
+   * @memberof Leap.Pointable.prototype
+   */
+  this.stabilizedTipPosition = data.stabilizedTipPosition;
+  /**
+   * The tip position in millimeters from the Leap origin.
+   *
+   * @member tipPosition
+   * @type {number[]}
+   * @memberof Leap.Pointable.prototype
+   */
+  this.tipPosition = data.tipPosition;
+  /**
+   * The rate of change of the tip position in millimeters/second.
+   *
+   * @member tipVelocity
+   * @type {number[]}
+   * @memberof Leap.Pointable.prototype
+   */
+  this.tipVelocity = data.tipVelocity;
+  /**
+   * The current touch zone of this Pointable object.
+   *
+   * The Leap Motion software computes the touch zone based on a floating touch
+   * plane that adapts to the user's finger movement and hand posture. The Leap
+   * Motion software interprets purposeful movements toward this plane as potential touch
+   * points. When a Pointable moves close to the adaptive touch plane, it enters the
+   * "hovering" zone. When a Pointable reaches or passes through the plane, it enters
+   * the "touching" zone.
+   *
+   * The possible states include:
+   *
+   * * "none" -- The Pointable is outside the hovering zone.
+   * * "hovering" -- The Pointable is close to, but not touching the touch plane.
+   * * "touching" -- The Pointable has penetrated the touch plane.
+   *
+   * The touchDistance value provides a normalized indication of the distance to
+   * the touch plane when the Pointable is in the hovering or touching zones.
+   *
+   * @member touchZone
+   * @type {String}
+   * @memberof Leap.Pointable.prototype
+   */
+  this.touchZone = data.touchZone;
+  /**
+   * A value proportional to the distance between this Pointable object and the
+   * adaptive touch plane.
+   *
+   * ![Touch Distance](images/Leap_Touch_Plane.png)
+   *
+   * The touch distance is a value in the range [-1, 1]. The value 1.0 indicates the
+   * Pointable is at the far edge of the hovering zone. The value 0 indicates the
+   * Pointable is just entering the touching zone. A value of -1.0 indicates the
+   * Pointable is firmly within the touching zone. Values in between are
+   * proportional to the distance from the plane. Thus, the touchDistance of 0.5
+   * indicates that the Pointable is halfway into the hovering zone.
+   *
+   * You can use the touchDistance value to modulate visual feedback given to the
+   * user as their fingers close in on a touch target, such as a button.
+   *
+   * @member touchDistance
+   * @type {number}
+   * @memberof Leap.Pointable.prototype
+   */
+  this.touchDistance = data.touchDistance;
+
+  /**
+   * How long the pointable has been visible in seconds.
+   *
+   * @member timeVisible
+   * @type {number}
+   * @memberof Leap.Pointable.prototype
+   */
+  this.timeVisible = data.timeVisible;
+}
+
+/**
+ * A string containing a brief, human readable description of the Pointable
+ * object.
+ *
+ * @method toString
+ * @memberof Leap.Pointable.prototype
+ * @returns {String} A description of the Pointable object as a string.
+ */
+Pointable.prototype.toString = function() {
+  return "Pointable [ id:" + this.id + " " + this.length + "mmx | width:" + this.width + "mm | direction:" + this.direction + ' ]';
+}
+
+/**
+ * Returns the hand which the pointable is attached to.
+ */
+Pointable.prototype.hand = function(){
+  return this.frame.hand(this.handId);
+}
+
+/**
+ * An invalid Pointable object.
+ *
+ * You can use this Pointable instance in comparisons testing
+ * whether a given Pointable instance is valid or invalid. (You can also use the
+ * Pointable.valid property.)
+
+ * @static
+ * @type {Leap.Pointable}
+ * @name Invalid
+ * @memberof Leap.Pointable
+ */
+Pointable.Invalid = { valid: false };
+
+},{"gl-matrix":23}],15:[function(require,module,exports){
+var Frame = require('./frame')
+  , Hand = require('./hand')
+  , Pointable = require('./pointable')
+  , Finger = require('./finger')
+  , _ = require('underscore')
+  , EventEmitter = require('events').EventEmitter;
+
+var Event = function(data) {
+  this.type = data.type;
+  this.state = data.state;
+};
+
+exports.chooseProtocol = function(header) {
+  var protocol;
+  switch(header.version) {
+    case 1:
+    case 2:
+    case 3:
+    case 4:
+    case 5:
+    case 6:
+      protocol = JSONProtocol(header);
+      protocol.sendBackground = function(connection, state) {
+        connection.send(protocol.encode({background: state}));
+      }
+      protocol.sendFocused = function(connection, state) {
+        connection.send(protocol.encode({focused: state}));
+      }
+      protocol.sendOptimizeHMD = function(connection, state) {
+        connection.send(protocol.encode({optimizeHMD: state}));
+      }
+      break;
+    default:
+      throw "unrecognized version";
+  }
+  return protocol;
+}
+
+var JSONProtocol = exports.JSONProtocol = function(header) {
+
+  var protocol = function(frameData) {
+
+    if (frameData.event) {
+
+      return new Event(frameData.event);
+
+    } else {
+
+      protocol.emit('beforeFrameCreated', frameData);
+
+      var frame = new Frame(frameData);
+
+      protocol.emit('afterFrameCreated', frame, frameData);
+
+      return frame;
+
+    }
+
+  };
+
+  protocol.encode = function(message) {
+    return JSON.stringify(message);
+  };
+  protocol.version = header.version;
+  protocol.serviceVersion = header.serviceVersion;
+  protocol.versionLong = 'Version ' + header.version;
+  protocol.type = 'protocol';
+
+  _.extend(protocol, EventEmitter.prototype);
+
+  return protocol;
+};
+
+
+
+},{"./finger":7,"./frame":8,"./hand":10,"./pointable":14,"events":21,"underscore":24}],16:[function(require,module,exports){
+exports.UI = {
+  Region: require("./ui/region"),
+  Cursor: require("./ui/cursor")
+};
+},{"./ui/cursor":17,"./ui/region":18}],17:[function(require,module,exports){
+var Cursor = module.exports = function() {
+  return function(frame) {
+    var pointable = frame.pointables.sort(function(a, b) { return a.z - b.z })[0]
+    if (pointable && pointable.valid) {
+      frame.cursorPosition = pointable.tipPosition
+    }
+    return frame
+  }
+}
+
+},{}],18:[function(require,module,exports){
+var EventEmitter = require('events').EventEmitter
+  , _ = require('underscore')
+
+var Region = module.exports = function(start, end) {
+  this.start = new Vector(start)
+  this.end = new Vector(end)
+  this.enteredFrame = null
+}
+
+Region.prototype.hasPointables = function(frame) {
+  for (var i = 0; i != frame.pointables.length; i++) {
+    var position = frame.pointables[i].tipPosition
+    if (position.x >= this.start.x && position.x <= this.end.x && position.y >= this.start.y && position.y <= this.end.y && position.z >= this.start.z && position.z <= this.end.z) {
+      return true
+    }
+  }
+  return false
+}
+
+Region.prototype.listener = function(opts) {
+  var region = this
+  if (opts && opts.nearThreshold) this.setupNearRegion(opts.nearThreshold)
+  return function(frame) {
+    return region.updatePosition(frame)
+  }
+}
+
+Region.prototype.clipper = function() {
+  var region = this
+  return function(frame) {
+    region.updatePosition(frame)
+    return region.enteredFrame ? frame : null
+  }
+}
+
+Region.prototype.setupNearRegion = function(distance) {
+  var nearRegion = this.nearRegion = new Region(
+    [this.start.x - distance, this.start.y - distance, this.start.z - distance],
+    [this.end.x + distance, this.end.y + distance, this.end.z + distance]
+  )
+  var region = this
+  nearRegion.on("enter", function(frame) {
+    region.emit("near", frame)
+  })
+  nearRegion.on("exit", function(frame) {
+    region.emit("far", frame)
+  })
+  region.on('exit', function(frame) {
+    region.emit("near", frame)
+  })
+}
+
+Region.prototype.updatePosition = function(frame) {
+  if (this.nearRegion) this.nearRegion.updatePosition(frame)
+  if (this.hasPointables(frame) && this.enteredFrame == null) {
+    this.enteredFrame = frame
+    this.emit("enter", this.enteredFrame)
+  } else if (!this.hasPointables(frame) && this.enteredFrame != null) {
+    this.enteredFrame = null
+    this.emit("exit", this.enteredFrame)
+  }
+  return frame
+}
+
+Region.prototype.normalize = function(position) {
+  return new Vector([
+    (position.x - this.start.x) / (this.end.x - this.start.x),
+    (position.y - this.start.y) / (this.end.y - this.start.y),
+    (position.z - this.start.z) / (this.end.z - this.start.z)
+  ])
+}
+
+Region.prototype.mapToXY = function(position, width, height) {
+  var normalized = this.normalize(position)
+  var x = normalized.x, y = normalized.y
+  if (x > 1) x = 1
+  else if (x < -1) x = -1
+  if (y > 1) y = 1
+  else if (y < -1) y = -1
+  return [
+    (x + 1) / 2 * width,
+    (1 - y) / 2 * height,
+    normalized.z
+  ]
+}
+
+_.extend(Region.prototype, EventEmitter.prototype)
+},{"events":21,"underscore":24}],19:[function(require,module,exports){
+// This file is automatically updated from package.json by grunt.
+module.exports = {
+  full: '0.6.4',
+  major: 0,
+  minor: 6,
+  dot: 4
+}
+},{}],20:[function(require,module,exports){
+
+},{}],21:[function(require,module,exports){
+var process=require("__browserify_process");if (!process.EventEmitter) process.EventEmitter = function () {};
+
+var EventEmitter = exports.EventEmitter = process.EventEmitter;
+var isArray = typeof Array.isArray === 'function'
+    ? Array.isArray
+    : function (xs) {
+        return Object.prototype.toString.call(xs) === '[object Array]'
+    }
+;
+function indexOf (xs, x) {
+    if (xs.indexOf) return xs.indexOf(x);
+    for (var i = 0; i < xs.length; i++) {
+        if (x === xs[i]) return i;
+    }
+    return -1;
+}
+
+// By default EventEmitters will print a warning if more than
+// 10 listeners are added to it. This is a useful default which
+// helps finding memory leaks.
+//
+// Obviously not all Emitters should be limited to 10. This function allows
+// that to be increased. Set to zero for unlimited.
+var defaultMaxListeners = 10;
+EventEmitter.prototype.setMaxListeners = function(n) {
+  if (!this._events) this._events = {};
+  this._events.maxListeners = n;
+};
+
+
+EventEmitter.prototype.emit = function(type) {
+  // If there is no 'error' event listener then throw.
+  if (type === 'error') {
+    if (!this._events || !this._events.error ||
+        (isArray(this._events.error) && !this._events.error.length))
+    {
+      if (arguments[1] instanceof Error) {
+        throw arguments[1]; // Unhandled 'error' event
+      } else {
+        throw new Error("Uncaught, unspecified 'error' event.");
+      }
+      return false;
+    }
+  }
+
+  if (!this._events) return false;
+  var handler = this._events[type];
+  if (!handler) return false;
+
+  if (typeof handler == 'function') {
+    switch (arguments.length) {
+      // fast cases
+      case 1:
+        handler.call(this);
+        break;
+      case 2:
+        handler.call(this, arguments[1]);
+        break;
+      case 3:
+        handler.call(this, arguments[1], arguments[2]);
+        break;
+      // slower
+      default:
+        var args = Array.prototype.slice.call(arguments, 1);
+        handler.apply(this, args);
+    }
+    return true;
+
+  } else if (isArray(handler)) {
+    var args = Array.prototype.slice.call(arguments, 1);
+
+    var listeners = handler.slice();
+    for (var i = 0, l = listeners.length; i < l; i++) {
+      listeners[i].apply(this, args);
+    }
+    return true;
+
+  } else {
+    return false;
+  }
+};
+
+// EventEmitter is defined in src/node_events.cc
+// EventEmitter.prototype.emit() is also defined there.
+EventEmitter.prototype.addListener = function(type, listener) {
+  if ('function' !== typeof listener) {
+    throw new Error('addListener only takes instances of Function');
+  }
+
+  if (!this._events) this._events = {};
+
+  // To avoid recursion in the case that type == "newListeners"! Before
+  // adding it to the listeners, first emit "newListeners".
+  this.emit('newListener', type, listener);
+
+  if (!this._events[type]) {
+    // Optimize the case of one listener. Don't need the extra array object.
+    this._events[type] = listener;
+  } else if (isArray(this._events[type])) {
+
+    // Check for listener leak
+    if (!this._events[type].warned) {
+      var m;
+      if (this._events.maxListeners !== undefined) {
+        m = this._events.maxListeners;
+      } else {
+        m = defaultMaxListeners;
+      }
+
+      if (m && m > 0 && this._events[type].length > m) {
+        this._events[type].warned = true;
+        console.error('(node) warning: possible EventEmitter memory ' +
+                      'leak detected. %d listeners added. ' +
+                      'Use emitter.setMaxListeners() to increase limit.',
+                      this._events[type].length);
+        console.trace();
+      }
+    }
+
+    // If we've already got an array, just append.
+    this._events[type].push(listener);
+  } else {
+    // Adding the second element, need to change to array.
+    this._events[type] = [this._events[type], listener];
+  }
+
+  return this;
+};
+
+EventEmitter.prototype.on = EventEmitter.prototype.addListener;
+
+EventEmitter.prototype.once = function(type, listener) {
+  var self = this;
+  self.on(type, function g() {
+    self.removeListener(type, g);
+    listener.apply(this, arguments);
+  });
+
+  return this;
+};
+
+EventEmitter.prototype.removeListener = function(type, listener) {
+  if ('function' !== typeof listener) {
+    throw new Error('removeListener only takes instances of Function');
+  }
+
+  // does not use listeners(), so no side effect of creating _events[type]
+  if (!this._events || !this._events[type]) return this;
+
+  var list = this._events[type];
+
+  if (isArray(list)) {
+    var i = indexOf(list, listener);
+    if (i < 0) return this;
+    list.splice(i, 1);
+    if (list.length == 0)
+      delete this._events[type];
+  } else if (this._events[type] === listener) {
+    delete this._events[type];
+  }
+
+  return this;
+};
+
+EventEmitter.prototype.removeAllListeners = function(type) {
+  if (arguments.length === 0) {
+    this._events = {};
+    return this;
+  }
+
+  // does not use listeners(), so no side effect of creating _events[type]
+  if (type && this._events && this._events[type]) this._events[type] = null;
+  return this;
+};
+
+EventEmitter.prototype.listeners = function(type) {
+  if (!this._events) this._events = {};
+  if (!this._events[type]) this._events[type] = [];
+  if (!isArray(this._events[type])) {
+    this._events[type] = [this._events[type]];
+  }
+  return this._events[type];
+};
+
+EventEmitter.listenerCount = function(emitter, type) {
+  var ret;
+  if (!emitter._events || !emitter._events[type])
+    ret = 0;
+  else if (typeof emitter._events[type] === 'function')
+    ret = 1;
+  else
+    ret = emitter._events[type].length;
+  return ret;
+};
+
+},{"__browserify_process":22}],22:[function(require,module,exports){
+// shim for using process in browser
+
+var process = module.exports = {};
+
+process.nextTick = (function () {
+    var canSetImmediate = typeof window !== 'undefined'
+    && window.setImmediate;
+    var canPost = typeof window !== 'undefined'
+    && window.postMessage && window.addEventListener
+    ;
+
+    if (canSetImmediate) {
+        return function (f) { return window.setImmediate(f) };
+    }
+
+    if (canPost) {
+        var queue = [];
+        window.addEventListener('message', function (ev) {
+            var source = ev.source;
+            if ((source === window || source === null) && ev.data === 'process-tick') {
+                ev.stopPropagation();
+                if (queue.length > 0) {
+                    var fn = queue.shift();
+                    fn();
+                }
+            }
+        }, true);
+
+        return function nextTick(fn) {
+            queue.push(fn);
+            window.postMessage('process-tick', '*');
+        };
+    }
+
+    return function nextTick(fn) {
+        setTimeout(fn, 0);
+    };
+})();
+
+process.title = 'browser';
+process.browser = true;
+process.env = {};
+process.argv = [];
+
+process.binding = function (name) {
+    throw new Error('process.binding is not supported');
+}
+
+// TODO(shtylman)
+process.cwd = function () { return '/' };
+process.chdir = function (dir) {
+    throw new Error('process.chdir is not supported');
+};
+
+},{}],23:[function(require,module,exports){
+/**
+ * @fileoverview gl-matrix - High performance matrix and vector operations
+ * @author Brandon Jones
+ * @author Colin MacKenzie IV
+ * @version 2.2.1
+ */
+
+/* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+  * Redistributions of source code must retain the above copyright notice, this
+    list of conditions and the following disclaimer.
+  * Redistributions in binary form must reproduce the above copyright notice,
+    this list of conditions and the following disclaimer in the documentation
+    and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+
+
+(function(_global) {
+  "use strict";
+
+  var shim = {};
+  if (typeof(exports) === 'undefined') {
+    if(typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+      shim.exports = {};
+      define(function() {
+        return shim.exports;
+      });
+    } else {
+      // gl-matrix lives in a browser, define its namespaces in global
+      shim.exports = typeof(window) !== 'undefined' ? window : _global;
+    }
+  }
+  else {
+    // gl-matrix lives in commonjs, define its namespaces in exports
+    shim.exports = exports;
+  }
+
+  (function(exports) {
+    /* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+  * Redistributions of source code must retain the above copyright notice, this
+    list of conditions and the following disclaimer.
+  * Redistributions in binary form must reproduce the above copyright notice,
+    this list of conditions and the following disclaimer in the documentation 
+    and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+
+
+if(!GLMAT_EPSILON) {
+    var GLMAT_EPSILON = 0.000001;
+}
+
+if(!GLMAT_ARRAY_TYPE) {
+    var GLMAT_ARRAY_TYPE = (typeof Float32Array !== 'undefined') ? Float32Array : Array;
+}
+
+if(!GLMAT_RANDOM) {
+    var GLMAT_RANDOM = Math.random;
+}
+
+/**
+ * @class Common utilities
+ * @name glMatrix
+ */
+var glMatrix = {};
+
+/**
+ * Sets the type of array used when creating new vectors and matricies
+ *
+ * @param {Type} type Array type, such as Float32Array or Array
+ */
+glMatrix.setMatrixArrayType = function(type) {
+    GLMAT_ARRAY_TYPE = type;
+}
+
+if(typeof(exports) !== 'undefined') {
+    exports.glMatrix = glMatrix;
+}
+
+var degree = Math.PI / 180;
+
+/**
+* Convert Degree To Radian
+*
+* @param {Number} Angle in Degrees
+*/
+glMatrix.toRadian = function(a){
+     return a * degree;
+}
+;
+/* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+  * Redistributions of source code must retain the above copyright notice, this
+    list of conditions and the following disclaimer.
+  * Redistributions in binary form must reproduce the above copyright notice,
+    this list of conditions and the following disclaimer in the documentation 
+    and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+
+/**
+ * @class 2 Dimensional Vector
+ * @name vec2
+ */
+
+var vec2 = {};
+
+/**
+ * Creates a new, empty vec2
+ *
+ * @returns {vec2} a new 2D vector
+ */
+vec2.create = function() {
+    var out = new GLMAT_ARRAY_TYPE(2);
+    out[0] = 0;
+    out[1] = 0;
+    return out;
+};
+
+/**
+ * Creates a new vec2 initialized with values from an existing vector
+ *
+ * @param {vec2} a vector to clone
+ * @returns {vec2} a new 2D vector
+ */
+vec2.clone = function(a) {
+    var out = new GLMAT_ARRAY_TYPE(2);
+    out[0] = a[0];
+    out[1] = a[1];
+    return out;
+};
+
+/**
+ * Creates a new vec2 initialized with the given values
+ *
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @returns {vec2} a new 2D vector
+ */
+vec2.fromValues = function(x, y) {
+    var out = new GLMAT_ARRAY_TYPE(2);
+    out[0] = x;
+    out[1] = y;
+    return out;
+};
+
+/**
+ * Copy the values from one vec2 to another
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the source vector
+ * @returns {vec2} out
+ */
+vec2.copy = function(out, a) {
+    out[0] = a[0];
+    out[1] = a[1];
+    return out;
+};
+
+/**
+ * Set the components of a vec2 to the given values
+ *
+ * @param {vec2} out the receiving vector
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @returns {vec2} out
+ */
+vec2.set = function(out, x, y) {
+    out[0] = x;
+    out[1] = y;
+    return out;
+};
+
+/**
+ * Adds two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {vec2} out
+ */
+vec2.add = function(out, a, b) {
+    out[0] = a[0] + b[0];
+    out[1] = a[1] + b[1];
+    return out;
+};
+
+/**
+ * Subtracts vector b from vector a
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {vec2} out
+ */
+vec2.subtract = function(out, a, b) {
+    out[0] = a[0] - b[0];
+    out[1] = a[1] - b[1];
+    return out;
+};
+
+/**
+ * Alias for {@link vec2.subtract}
+ * @function
+ */
+vec2.sub = vec2.subtract;
+
+/**
+ * Multiplies two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {vec2} out
+ */
+vec2.multiply = function(out, a, b) {
+    out[0] = a[0] * b[0];
+    out[1] = a[1] * b[1];
+    return out;
+};
+
+/**
+ * Alias for {@link vec2.multiply}
+ * @function
+ */
+vec2.mul = vec2.multiply;
+
+/**
+ * Divides two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {vec2} out
+ */
+vec2.divide = function(out, a, b) {
+    out[0] = a[0] / b[0];
+    out[1] = a[1] / b[1];
+    return out;
+};
+
+/**
+ * Alias for {@link vec2.divide}
+ * @function
+ */
+vec2.div = vec2.divide;
+
+/**
+ * Returns the minimum of two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {vec2} out
+ */
+vec2.min = function(out, a, b) {
+    out[0] = Math.min(a[0], b[0]);
+    out[1] = Math.min(a[1], b[1]);
+    return out;
+};
+
+/**
+ * Returns the maximum of two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {vec2} out
+ */
+vec2.max = function(out, a, b) {
+    out[0] = Math.max(a[0], b[0]);
+    out[1] = Math.max(a[1], b[1]);
+    return out;
+};
+
+/**
+ * Scales a vec2 by a scalar number
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the vector to scale
+ * @param {Number} b amount to scale the vector by
+ * @returns {vec2} out
+ */
+vec2.scale = function(out, a, b) {
+    out[0] = a[0] * b;
+    out[1] = a[1] * b;
+    return out;
+};
+
+/**
+ * Adds two vec2's after scaling the second operand by a scalar value
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @param {Number} scale the amount to scale b by before adding
+ * @returns {vec2} out
+ */
+vec2.scaleAndAdd = function(out, a, b, scale) {
+    out[0] = a[0] + (b[0] * scale);
+    out[1] = a[1] + (b[1] * scale);
+    return out;
+};
+
+/**
+ * Calculates the euclidian distance between two vec2's
+ *
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {Number} distance between a and b
+ */
+vec2.distance = function(a, b) {
+    var x = b[0] - a[0],
+        y = b[1] - a[1];
+    return Math.sqrt(x*x + y*y);
+};
+
+/**
+ * Alias for {@link vec2.distance}
+ * @function
+ */
+vec2.dist = vec2.distance;
+
+/**
+ * Calculates the squared euclidian distance between two vec2's
+ *
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {Number} squared distance between a and b
+ */
+vec2.squaredDistance = function(a, b) {
+    var x = b[0] - a[0],
+        y = b[1] - a[1];
+    return x*x + y*y;
+};
+
+/**
+ * Alias for {@link vec2.squaredDistance}
+ * @function
+ */
+vec2.sqrDist = vec2.squaredDistance;
+
+/**
+ * Calculates the length of a vec2
+ *
+ * @param {vec2} a vector to calculate length of
+ * @returns {Number} length of a
+ */
+vec2.length = function (a) {
+    var x = a[0],
+        y = a[1];
+    return Math.sqrt(x*x + y*y);
+};
+
+/**
+ * Alias for {@link vec2.length}
+ * @function
+ */
+vec2.len = vec2.length;
+
+/**
+ * Calculates the squared length of a vec2
+ *
+ * @param {vec2} a vector to calculate squared length of
+ * @returns {Number} squared length of a
+ */
+vec2.squaredLength = function (a) {
+    var x = a[0],
+        y = a[1];
+    return x*x + y*y;
+};
+
+/**
+ * Alias for {@link vec2.squaredLength}
+ * @function
+ */
+vec2.sqrLen = vec2.squaredLength;
+
+/**
+ * Negates the components of a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a vector to negate
+ * @returns {vec2} out
+ */
+vec2.negate = function(out, a) {
+    out[0] = -a[0];
+    out[1] = -a[1];
+    return out;
+};
+
+/**
+ * Normalize a vec2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a vector to normalize
+ * @returns {vec2} out
+ */
+vec2.normalize = function(out, a) {
+    var x = a[0],
+        y = a[1];
+    var len = x*x + y*y;
+    if (len > 0) {
+        //TODO: evaluate use of glm_invsqrt here?
+        len = 1 / Math.sqrt(len);
+        out[0] = a[0] * len;
+        out[1] = a[1] * len;
+    }
+    return out;
+};
+
+/**
+ * Calculates the dot product of two vec2's
+ *
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {Number} dot product of a and b
+ */
+vec2.dot = function (a, b) {
+    return a[0] * b[0] + a[1] * b[1];
+};
+
+/**
+ * Computes the cross product of two vec2's
+ * Note that the cross product must by definition produce a 3D vector
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @returns {vec3} out
+ */
+vec2.cross = function(out, a, b) {
+    var z = a[0] * b[1] - a[1] * b[0];
+    out[0] = out[1] = 0;
+    out[2] = z;
+    return out;
+};
+
+/**
+ * Performs a linear interpolation between two vec2's
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the first operand
+ * @param {vec2} b the second operand
+ * @param {Number} t interpolation amount between the two inputs
+ * @returns {vec2} out
+ */
+vec2.lerp = function (out, a, b, t) {
+    var ax = a[0],
+        ay = a[1];
+    out[0] = ax + t * (b[0] - ax);
+    out[1] = ay + t * (b[1] - ay);
+    return out;
+};
+
+/**
+ * Generates a random vector with the given scale
+ *
+ * @param {vec2} out the receiving vector
+ * @param {Number} [scale] Length of the resulting vector. If ommitted, a unit vector will be returned
+ * @returns {vec2} out
+ */
+vec2.random = function (out, scale) {
+    scale = scale || 1.0;
+    var r = GLMAT_RANDOM() * 2.0 * Math.PI;
+    out[0] = Math.cos(r) * scale;
+    out[1] = Math.sin(r) * scale;
+    return out;
+};
+
+/**
+ * Transforms the vec2 with a mat2
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the vector to transform
+ * @param {mat2} m matrix to transform with
+ * @returns {vec2} out
+ */
+vec2.transformMat2 = function(out, a, m) {
+    var x = a[0],
+        y = a[1];
+    out[0] = m[0] * x + m[2] * y;
+    out[1] = m[1] * x + m[3] * y;
+    return out;
+};
+
+/**
+ * Transforms the vec2 with a mat2d
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the vector to transform
+ * @param {mat2d} m matrix to transform with
+ * @returns {vec2} out
+ */
+vec2.transformMat2d = function(out, a, m) {
+    var x = a[0],
+        y = a[1];
+    out[0] = m[0] * x + m[2] * y + m[4];
+    out[1] = m[1] * x + m[3] * y + m[5];
+    return out;
+};
+
+/**
+ * Transforms the vec2 with a mat3
+ * 3rd vector component is implicitly '1'
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the vector to transform
+ * @param {mat3} m matrix to transform with
+ * @returns {vec2} out
+ */
+vec2.transformMat3 = function(out, a, m) {
+    var x = a[0],
+        y = a[1];
+    out[0] = m[0] * x + m[3] * y + m[6];
+    out[1] = m[1] * x + m[4] * y + m[7];
+    return out;
+};
+
+/**
+ * Transforms the vec2 with a mat4
+ * 3rd vector component is implicitly '0'
+ * 4th vector component is implicitly '1'
+ *
+ * @param {vec2} out the receiving vector
+ * @param {vec2} a the vector to transform
+ * @param {mat4} m matrix to transform with
+ * @returns {vec2} out
+ */
+vec2.transformMat4 = function(out, a, m) {
+    var x = a[0], 
+        y = a[1];
+    out[0] = m[0] * x + m[4] * y + m[12];
+    out[1] = m[1] * x + m[5] * y + m[13];
+    return out;
+};
+
+/**
+ * Perform some operation over an array of vec2s.
+ *
+ * @param {Array} a the array of vectors to iterate over
+ * @param {Number} stride Number of elements between the start of each vec2. If 0 assumes tightly packed
+ * @param {Number} offset Number of elements to skip at the beginning of the array
+ * @param {Number} count Number of vec2s to iterate over. If 0 iterates over entire array
+ * @param {Function} fn Function to call for each vector in the array
+ * @param {Object} [arg] additional argument to pass to fn
+ * @returns {Array} a
+ * @function
+ */
+vec2.forEach = (function() {
+    var vec = vec2.create();
+
+    return function(a, stride, offset, count, fn, arg) {
+        var i, l;
+        if(!stride) {
+            stride = 2;
+        }
+
+        if(!offset) {
+            offset = 0;
+        }
+        
+        if(count) {
+            l = Math.min((count * stride) + offset, a.length);
+        } else {
+            l = a.length;
+        }
+
+        for(i = offset; i < l; i += stride) {
+            vec[0] = a[i]; vec[1] = a[i+1];
+            fn(vec, vec, arg);
+            a[i] = vec[0]; a[i+1] = vec[1];
+        }
+        
+        return a;
+    };
+})();
+
+/**
+ * Returns a string representation of a vector
+ *
+ * @param {vec2} vec vector to represent as a string
+ * @returns {String} string representation of the vector
+ */
+vec2.str = function (a) {
+    return 'vec2(' + a[0] + ', ' + a[1] + ')';
+};
+
+if(typeof(exports) !== 'undefined') {
+    exports.vec2 = vec2;
+}
+;
+/* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+  * Redistributions of source code must retain the above copyright notice, this
+    list of conditions and the following disclaimer.
+  * Redistributions in binary form must reproduce the above copyright notice,
+    this list of conditions and the following disclaimer in the documentation 
+    and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+
+/**
+ * @class 3 Dimensional Vector
+ * @name vec3
+ */
+
+var vec3 = {};
+
+/**
+ * Creates a new, empty vec3
+ *
+ * @returns {vec3} a new 3D vector
+ */
+vec3.create = function() {
+    var out = new GLMAT_ARRAY_TYPE(3);
+    out[0] = 0;
+    out[1] = 0;
+    out[2] = 0;
+    return out;
+};
+
+/**
+ * Creates a new vec3 initialized with values from an existing vector
+ *
+ * @param {vec3} a vector to clone
+ * @returns {vec3} a new 3D vector
+ */
+vec3.clone = function(a) {
+    var out = new GLMAT_ARRAY_TYPE(3);
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    return out;
+};
+
+/**
+ * Creates a new vec3 initialized with the given values
+ *
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @returns {vec3} a new 3D vector
+ */
+vec3.fromValues = function(x, y, z) {
+    var out = new GLMAT_ARRAY_TYPE(3);
+    out[0] = x;
+    out[1] = y;
+    out[2] = z;
+    return out;
+};
+
+/**
+ * Copy the values from one vec3 to another
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the source vector
+ * @returns {vec3} out
+ */
+vec3.copy = function(out, a) {
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    return out;
+};
+
+/**
+ * Set the components of a vec3 to the given values
+ *
+ * @param {vec3} out the receiving vector
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @returns {vec3} out
+ */
+vec3.set = function(out, x, y, z) {
+    out[0] = x;
+    out[1] = y;
+    out[2] = z;
+    return out;
+};
+
+/**
+ * Adds two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {vec3} out
+ */
+vec3.add = function(out, a, b) {
+    out[0] = a[0] + b[0];
+    out[1] = a[1] + b[1];
+    out[2] = a[2] + b[2];
+    return out;
+};
+
+/**
+ * Subtracts vector b from vector a
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {vec3} out
+ */
+vec3.subtract = function(out, a, b) {
+    out[0] = a[0] - b[0];
+    out[1] = a[1] - b[1];
+    out[2] = a[2] - b[2];
+    return out;
+};
+
+/**
+ * Alias for {@link vec3.subtract}
+ * @function
+ */
+vec3.sub = vec3.subtract;
+
+/**
+ * Multiplies two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {vec3} out
+ */
+vec3.multiply = function(out, a, b) {
+    out[0] = a[0] * b[0];
+    out[1] = a[1] * b[1];
+    out[2] = a[2] * b[2];
+    return out;
+};
+
+/**
+ * Alias for {@link vec3.multiply}
+ * @function
+ */
+vec3.mul = vec3.multiply;
+
+/**
+ * Divides two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {vec3} out
+ */
+vec3.divide = function(out, a, b) {
+    out[0] = a[0] / b[0];
+    out[1] = a[1] / b[1];
+    out[2] = a[2] / b[2];
+    return out;
+};
+
+/**
+ * Alias for {@link vec3.divide}
+ * @function
+ */
+vec3.div = vec3.divide;
+
+/**
+ * Returns the minimum of two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {vec3} out
+ */
+vec3.min = function(out, a, b) {
+    out[0] = Math.min(a[0], b[0]);
+    out[1] = Math.min(a[1], b[1]);
+    out[2] = Math.min(a[2], b[2]);
+    return out;
+};
+
+/**
+ * Returns the maximum of two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {vec3} out
+ */
+vec3.max = function(out, a, b) {
+    out[0] = Math.max(a[0], b[0]);
+    out[1] = Math.max(a[1], b[1]);
+    out[2] = Math.max(a[2], b[2]);
+    return out;
+};
+
+/**
+ * Scales a vec3 by a scalar number
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the vector to scale
+ * @param {Number} b amount to scale the vector by
+ * @returns {vec3} out
+ */
+vec3.scale = function(out, a, b) {
+    out[0] = a[0] * b;
+    out[1] = a[1] * b;
+    out[2] = a[2] * b;
+    return out;
+};
+
+/**
+ * Adds two vec3's after scaling the second operand by a scalar value
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @param {Number} scale the amount to scale b by before adding
+ * @returns {vec3} out
+ */
+vec3.scaleAndAdd = function(out, a, b, scale) {
+    out[0] = a[0] + (b[0] * scale);
+    out[1] = a[1] + (b[1] * scale);
+    out[2] = a[2] + (b[2] * scale);
+    return out;
+};
+
+/**
+ * Calculates the euclidian distance between two vec3's
+ *
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {Number} distance between a and b
+ */
+vec3.distance = function(a, b) {
+    var x = b[0] - a[0],
+        y = b[1] - a[1],
+        z = b[2] - a[2];
+    return Math.sqrt(x*x + y*y + z*z);
+};
+
+/**
+ * Alias for {@link vec3.distance}
+ * @function
+ */
+vec3.dist = vec3.distance;
+
+/**
+ * Calculates the squared euclidian distance between two vec3's
+ *
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {Number} squared distance between a and b
+ */
+vec3.squaredDistance = function(a, b) {
+    var x = b[0] - a[0],
+        y = b[1] - a[1],
+        z = b[2] - a[2];
+    return x*x + y*y + z*z;
+};
+
+/**
+ * Alias for {@link vec3.squaredDistance}
+ * @function
+ */
+vec3.sqrDist = vec3.squaredDistance;
+
+/**
+ * Calculates the length of a vec3
+ *
+ * @param {vec3} a vector to calculate length of
+ * @returns {Number} length of a
+ */
+vec3.length = function (a) {
+    var x = a[0],
+        y = a[1],
+        z = a[2];
+    return Math.sqrt(x*x + y*y + z*z);
+};
+
+/**
+ * Alias for {@link vec3.length}
+ * @function
+ */
+vec3.len = vec3.length;
+
+/**
+ * Calculates the squared length of a vec3
+ *
+ * @param {vec3} a vector to calculate squared length of
+ * @returns {Number} squared length of a
+ */
+vec3.squaredLength = function (a) {
+    var x = a[0],
+        y = a[1],
+        z = a[2];
+    return x*x + y*y + z*z;
+};
+
+/**
+ * Alias for {@link vec3.squaredLength}
+ * @function
+ */
+vec3.sqrLen = vec3.squaredLength;
+
+/**
+ * Negates the components of a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a vector to negate
+ * @returns {vec3} out
+ */
+vec3.negate = function(out, a) {
+    out[0] = -a[0];
+    out[1] = -a[1];
+    out[2] = -a[2];
+    return out;
+};
+
+/**
+ * Normalize a vec3
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a vector to normalize
+ * @returns {vec3} out
+ */
+vec3.normalize = function(out, a) {
+    var x = a[0],
+        y = a[1],
+        z = a[2];
+    var len = x*x + y*y + z*z;
+    if (len > 0) {
+        //TODO: evaluate use of glm_invsqrt here?
+        len = 1 / Math.sqrt(len);
+        out[0] = a[0] * len;
+        out[1] = a[1] * len;
+        out[2] = a[2] * len;
+    }
+    return out;
+};
+
+/**
+ * Calculates the dot product of two vec3's
+ *
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {Number} dot product of a and b
+ */
+vec3.dot = function (a, b) {
+    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
+};
+
+/**
+ * Computes the cross product of two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @returns {vec3} out
+ */
+vec3.cross = function(out, a, b) {
+    var ax = a[0], ay = a[1], az = a[2],
+        bx = b[0], by = b[1], bz = b[2];
+
+    out[0] = ay * bz - az * by;
+    out[1] = az * bx - ax * bz;
+    out[2] = ax * by - ay * bx;
+    return out;
+};
+
+/**
+ * Performs a linear interpolation between two vec3's
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the first operand
+ * @param {vec3} b the second operand
+ * @param {Number} t interpolation amount between the two inputs
+ * @returns {vec3} out
+ */
+vec3.lerp = function (out, a, b, t) {
+    var ax = a[0],
+        ay = a[1],
+        az = a[2];
+    out[0] = ax + t * (b[0] - ax);
+    out[1] = ay + t * (b[1] - ay);
+    out[2] = az + t * (b[2] - az);
+    return out;
+};
+
+/**
+ * Generates a random vector with the given scale
+ *
+ * @param {vec3} out the receiving vector
+ * @param {Number} [scale] Length of the resulting vector. If ommitted, a unit vector will be returned
+ * @returns {vec3} out
+ */
+vec3.random = function (out, scale) {
+    scale = scale || 1.0;
+
+    var r = GLMAT_RANDOM() * 2.0 * Math.PI;
+    var z = (GLMAT_RANDOM() * 2.0) - 1.0;
+    var zScale = Math.sqrt(1.0-z*z) * scale;
+
+    out[0] = Math.cos(r) * zScale;
+    out[1] = Math.sin(r) * zScale;
+    out[2] = z * scale;
+    return out;
+};
+
+/**
+ * Transforms the vec3 with a mat4.
+ * 4th vector component is implicitly '1'
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the vector to transform
+ * @param {mat4} m matrix to transform with
+ * @returns {vec3} out
+ */
+vec3.transformMat4 = function(out, a, m) {
+    var x = a[0], y = a[1], z = a[2];
+    out[0] = m[0] * x + m[4] * y + m[8] * z + m[12];
+    out[1] = m[1] * x + m[5] * y + m[9] * z + m[13];
+    out[2] = m[2] * x + m[6] * y + m[10] * z + m[14];
+    return out;
+};
+
+/**
+ * Transforms the vec3 with a mat3.
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the vector to transform
+ * @param {mat4} m the 3x3 matrix to transform with
+ * @returns {vec3} out
+ */
+vec3.transformMat3 = function(out, a, m) {
+    var x = a[0], y = a[1], z = a[2];
+    out[0] = x * m[0] + y * m[3] + z * m[6];
+    out[1] = x * m[1] + y * m[4] + z * m[7];
+    out[2] = x * m[2] + y * m[5] + z * m[8];
+    return out;
+};
+
+/**
+ * Transforms the vec3 with a quat
+ *
+ * @param {vec3} out the receiving vector
+ * @param {vec3} a the vector to transform
+ * @param {quat} q quaternion to transform with
+ * @returns {vec3} out
+ */
+vec3.transformQuat = function(out, a, q) {
+    // benchmarks: http://jsperf.com/quaternion-transform-vec3-implementations
+
+    var x = a[0], y = a[1], z = a[2],
+        qx = q[0], qy = q[1], qz = q[2], qw = q[3],
+
+        // calculate quat * vec
+        ix = qw * x + qy * z - qz * y,
+        iy = qw * y + qz * x - qx * z,
+        iz = qw * z + qx * y - qy * x,
+        iw = -qx * x - qy * y - qz * z;
+
+    // calculate result * inverse quat
+    out[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy;
+    out[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz;
+    out[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx;
+    return out;
+};
+
+/*
+* Rotate a 3D vector around the x-axis
+* @param {vec3} out The receiving vec3
+* @param {vec3} a The vec3 point to rotate
+* @param {vec3} b The origin of the rotation
+* @param {Number} c The angle of rotation
+* @returns {vec3} out
+*/
+vec3.rotateX = function(out, a, b, c){
+   var p = [], r=[];
+	  //Translate point to the origin
+	  p[0] = a[0] - b[0];
+	  p[1] = a[1] - b[1];
+  	p[2] = a[2] - b[2];
+
+	  //perform rotation
+	  r[0] = p[0];
+	  r[1] = p[1]*Math.cos(c) - p[2]*Math.sin(c);
+	  r[2] = p[1]*Math.sin(c) + p[2]*Math.cos(c);
+
+	  //translate to correct position
+	  out[0] = r[0] + b[0];
+	  out[1] = r[1] + b[1];
+	  out[2] = r[2] + b[2];
+
+  	return out;
+};
+
+/*
+* Rotate a 3D vector around the y-axis
+* @param {vec3} out The receiving vec3
+* @param {vec3} a The vec3 point to rotate
+* @param {vec3} b The origin of the rotation
+* @param {Number} c The angle of rotation
+* @returns {vec3} out
+*/
+vec3.rotateY = function(out, a, b, c){
+  	var p = [], r=[];
+  	//Translate point to the origin
+  	p[0] = a[0] - b[0];
+  	p[1] = a[1] - b[1];
+  	p[2] = a[2] - b[2];
+  
+  	//perform rotation
+  	r[0] = p[2]*Math.sin(c) + p[0]*Math.cos(c);
+  	r[1] = p[1];
+  	r[2] = p[2]*Math.cos(c) - p[0]*Math.sin(c);
+  
+  	//translate to correct position
+  	out[0] = r[0] + b[0];
+  	out[1] = r[1] + b[1];
+  	out[2] = r[2] + b[2];
+  
+  	return out;
+};
+
+/*
+* Rotate a 3D vector around the z-axis
+* @param {vec3} out The receiving vec3
+* @param {vec3} a The vec3 point to rotate
+* @param {vec3} b The origin of the rotation
+* @param {Number} c The angle of rotation
+* @returns {vec3} out
+*/
+vec3.rotateZ = function(out, a, b, c){
+  	var p = [], r=[];
+  	//Translate point to the origin
+  	p[0] = a[0] - b[0];
+  	p[1] = a[1] - b[1];
+  	p[2] = a[2] - b[2];
+  
+  	//perform rotation
+  	r[0] = p[0]*Math.cos(c) - p[1]*Math.sin(c);
+  	r[1] = p[0]*Math.sin(c) + p[1]*Math.cos(c);
+  	r[2] = p[2];
+  
+  	//translate to correct position
+  	out[0] = r[0] + b[0];
+  	out[1] = r[1] + b[1];
+  	out[2] = r[2] + b[2];
+  
+  	return out;
+};
+
+/**
+ * Perform some operation over an array of vec3s.
+ *
+ * @param {Array} a the array of vectors to iterate over
+ * @param {Number} stride Number of elements between the start of each vec3. If 0 assumes tightly packed
+ * @param {Number} offset Number of elements to skip at the beginning of the array
+ * @param {Number} count Number of vec3s to iterate over. If 0 iterates over entire array
+ * @param {Function} fn Function to call for each vector in the array
+ * @param {Object} [arg] additional argument to pass to fn
+ * @returns {Array} a
+ * @function
+ */
+vec3.forEach = (function() {
+    var vec = vec3.create();
+
+    return function(a, stride, offset, count, fn, arg) {
+        var i, l;
+        if(!stride) {
+            stride = 3;
+        }
+
+        if(!offset) {
+            offset = 0;
+        }
+        
+        if(count) {
+            l = Math.min((count * stride) + offset, a.length);
+        } else {
+            l = a.length;
+        }
+
+        for(i = offset; i < l; i += stride) {
+            vec[0] = a[i]; vec[1] = a[i+1]; vec[2] = a[i+2];
+            fn(vec, vec, arg);
+            a[i] = vec[0]; a[i+1] = vec[1]; a[i+2] = vec[2];
+        }
+        
+        return a;
+    };
+})();
+
+/**
+ * Returns a string representation of a vector
+ *
+ * @param {vec3} vec vector to represent as a string
+ * @returns {String} string representation of the vector
+ */
+vec3.str = function (a) {
+    return 'vec3(' + a[0] + ', ' + a[1] + ', ' + a[2] + ')';
+};
+
+if(typeof(exports) !== 'undefined') {
+    exports.vec3 = vec3;
+}
+;
+/* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+  * Redistributions of source code must retain the above copyright notice, this
+    list of conditions and the following disclaimer.
+  * Redistributions in binary form must reproduce the above copyright notice,
+    this list of conditions and the following disclaimer in the documentation 
+    and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+
+/**
+ * @class 4 Dimensional Vector
+ * @name vec4
+ */
+
+var vec4 = {};
+
+/**
+ * Creates a new, empty vec4
+ *
+ * @returns {vec4} a new 4D vector
+ */
+vec4.create = function() {
+    var out = new GLMAT_ARRAY_TYPE(4);
+    out[0] = 0;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 0;
+    return out;
+};
+
+/**
+ * Creates a new vec4 initialized with values from an existing vector
+ *
+ * @param {vec4} a vector to clone
+ * @returns {vec4} a new 4D vector
+ */
+vec4.clone = function(a) {
+    var out = new GLMAT_ARRAY_TYPE(4);
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    return out;
+};
+
+/**
+ * Creates a new vec4 initialized with the given values
+ *
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @param {Number} w W component
+ * @returns {vec4} a new 4D vector
+ */
+vec4.fromValues = function(x, y, z, w) {
+    var out = new GLMAT_ARRAY_TYPE(4);
+    out[0] = x;
+    out[1] = y;
+    out[2] = z;
+    out[3] = w;
+    return out;
+};
+
+/**
+ * Copy the values from one vec4 to another
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the source vector
+ * @returns {vec4} out
+ */
+vec4.copy = function(out, a) {
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    return out;
+};
+
+/**
+ * Set the components of a vec4 to the given values
+ *
+ * @param {vec4} out the receiving vector
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @param {Number} w W component
+ * @returns {vec4} out
+ */
+vec4.set = function(out, x, y, z, w) {
+    out[0] = x;
+    out[1] = y;
+    out[2] = z;
+    out[3] = w;
+    return out;
+};
+
+/**
+ * Adds two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @returns {vec4} out
+ */
+vec4.add = function(out, a, b) {
+    out[0] = a[0] + b[0];
+    out[1] = a[1] + b[1];
+    out[2] = a[2] + b[2];
+    out[3] = a[3] + b[3];
+    return out;
+};
+
+/**
+ * Subtracts vector b from vector a
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @returns {vec4} out
+ */
+vec4.subtract = function(out, a, b) {
+    out[0] = a[0] - b[0];
+    out[1] = a[1] - b[1];
+    out[2] = a[2] - b[2];
+    out[3] = a[3] - b[3];
+    return out;
+};
+
+/**
+ * Alias for {@link vec4.subtract}
+ * @function
+ */
+vec4.sub = vec4.subtract;
+
+/**
+ * Multiplies two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @returns {vec4} out
+ */
+vec4.multiply = function(out, a, b) {
+    out[0] = a[0] * b[0];
+    out[1] = a[1] * b[1];
+    out[2] = a[2] * b[2];
+    out[3] = a[3] * b[3];
+    return out;
+};
+
+/**
+ * Alias for {@link vec4.multiply}
+ * @function
+ */
+vec4.mul = vec4.multiply;
+
+/**
+ * Divides two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @returns {vec4} out
+ */
+vec4.divide = function(out, a, b) {
+    out[0] = a[0] / b[0];
+    out[1] = a[1] / b[1];
+    out[2] = a[2] / b[2];
+    out[3] = a[3] / b[3];
+    return out;
+};
+
+/**
+ * Alias for {@link vec4.divide}
+ * @function
+ */
+vec4.div = vec4.divide;
+
+/**
+ * Returns the minimum of two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @returns {vec4} out
+ */
+vec4.min = function(out, a, b) {
+    out[0] = Math.min(a[0], b[0]);
+    out[1] = Math.min(a[1], b[1]);
+    out[2] = Math.min(a[2], b[2]);
+    out[3] = Math.min(a[3], b[3]);
+    return out;
+};
+
+/**
+ * Returns the maximum of two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @returns {vec4} out
+ */
+vec4.max = function(out, a, b) {
+    out[0] = Math.max(a[0], b[0]);
+    out[1] = Math.max(a[1], b[1]);
+    out[2] = Math.max(a[2], b[2]);
+    out[3] = Math.max(a[3], b[3]);
+    return out;
+};
+
+/**
+ * Scales a vec4 by a scalar number
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the vector to scale
+ * @param {Number} b amount to scale the vector by
+ * @returns {vec4} out
+ */
+vec4.scale = function(out, a, b) {
+    out[0] = a[0] * b;
+    out[1] = a[1] * b;
+    out[2] = a[2] * b;
+    out[3] = a[3] * b;
+    return out;
+};
+
+/**
+ * Adds two vec4's after scaling the second operand by a scalar value
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @param {Number} scale the amount to scale b by before adding
+ * @returns {vec4} out
+ */
+vec4.scaleAndAdd = function(out, a, b, scale) {
+    out[0] = a[0] + (b[0] * scale);
+    out[1] = a[1] + (b[1] * scale);
+    out[2] = a[2] + (b[2] * scale);
+    out[3] = a[3] + (b[3] * scale);
+    return out;
+};
+
+/**
+ * Calculates the euclidian distance between two vec4's
+ *
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @returns {Number} distance between a and b
+ */
+vec4.distance = function(a, b) {
+    var x = b[0] - a[0],
+        y = b[1] - a[1],
+        z = b[2] - a[2],
+        w = b[3] - a[3];
+    return Math.sqrt(x*x + y*y + z*z + w*w);
+};
+
+/**
+ * Alias for {@link vec4.distance}
+ * @function
+ */
+vec4.dist = vec4.distance;
+
+/**
+ * Calculates the squared euclidian distance between two vec4's
+ *
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @returns {Number} squared distance between a and b
+ */
+vec4.squaredDistance = function(a, b) {
+    var x = b[0] - a[0],
+        y = b[1] - a[1],
+        z = b[2] - a[2],
+        w = b[3] - a[3];
+    return x*x + y*y + z*z + w*w;
+};
+
+/**
+ * Alias for {@link vec4.squaredDistance}
+ * @function
+ */
+vec4.sqrDist = vec4.squaredDistance;
+
+/**
+ * Calculates the length of a vec4
+ *
+ * @param {vec4} a vector to calculate length of
+ * @returns {Number} length of a
+ */
+vec4.length = function (a) {
+    var x = a[0],
+        y = a[1],
+        z = a[2],
+        w = a[3];
+    return Math.sqrt(x*x + y*y + z*z + w*w);
+};
+
+/**
+ * Alias for {@link vec4.length}
+ * @function
+ */
+vec4.len = vec4.length;
+
+/**
+ * Calculates the squared length of a vec4
+ *
+ * @param {vec4} a vector to calculate squared length of
+ * @returns {Number} squared length of a
+ */
+vec4.squaredLength = function (a) {
+    var x = a[0],
+        y = a[1],
+        z = a[2],
+        w = a[3];
+    return x*x + y*y + z*z + w*w;
+};
+
+/**
+ * Alias for {@link vec4.squaredLength}
+ * @function
+ */
+vec4.sqrLen = vec4.squaredLength;
+
+/**
+ * Negates the components of a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a vector to negate
+ * @returns {vec4} out
+ */
+vec4.negate = function(out, a) {
+    out[0] = -a[0];
+    out[1] = -a[1];
+    out[2] = -a[2];
+    out[3] = -a[3];
+    return out;
+};
+
+/**
+ * Normalize a vec4
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a vector to normalize
+ * @returns {vec4} out
+ */
+vec4.normalize = function(out, a) {
+    var x = a[0],
+        y = a[1],
+        z = a[2],
+        w = a[3];
+    var len = x*x + y*y + z*z + w*w;
+    if (len > 0) {
+        len = 1 / Math.sqrt(len);
+        out[0] = a[0] * len;
+        out[1] = a[1] * len;
+        out[2] = a[2] * len;
+        out[3] = a[3] * len;
+    }
+    return out;
+};
+
+/**
+ * Calculates the dot product of two vec4's
+ *
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @returns {Number} dot product of a and b
+ */
+vec4.dot = function (a, b) {
+    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
+};
+
+/**
+ * Performs a linear interpolation between two vec4's
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the first operand
+ * @param {vec4} b the second operand
+ * @param {Number} t interpolation amount between the two inputs
+ * @returns {vec4} out
+ */
+vec4.lerp = function (out, a, b, t) {
+    var ax = a[0],
+        ay = a[1],
+        az = a[2],
+        aw = a[3];
+    out[0] = ax + t * (b[0] - ax);
+    out[1] = ay + t * (b[1] - ay);
+    out[2] = az + t * (b[2] - az);
+    out[3] = aw + t * (b[3] - aw);
+    return out;
+};
+
+/**
+ * Generates a random vector with the given scale
+ *
+ * @param {vec4} out the receiving vector
+ * @param {Number} [scale] Length of the resulting vector. If ommitted, a unit vector will be returned
+ * @returns {vec4} out
+ */
+vec4.random = function (out, scale) {
+    scale = scale || 1.0;
+
+    //TODO: This is a pretty awful way of doing this. Find something better.
+    out[0] = GLMAT_RANDOM();
+    out[1] = GLMAT_RANDOM();
+    out[2] = GLMAT_RANDOM();
+    out[3] = GLMAT_RANDOM();
+    vec4.normalize(out, out);
+    vec4.scale(out, out, scale);
+    return out;
+};
+
+/**
+ * Transforms the vec4 with a mat4.
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the vector to transform
+ * @param {mat4} m matrix to transform with
+ * @returns {vec4} out
+ */
+vec4.transformMat4 = function(out, a, m) {
+    var x = a[0], y = a[1], z = a[2], w = a[3];
+    out[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w;
+    out[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w;
+    out[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w;
+    out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w;
+    return out;
+};
+
+/**
+ * Transforms the vec4 with a quat
+ *
+ * @param {vec4} out the receiving vector
+ * @param {vec4} a the vector to transform
+ * @param {quat} q quaternion to transform with
+ * @returns {vec4} out
+ */
+vec4.transformQuat = function(out, a, q) {
+    var x = a[0], y = a[1], z = a[2],
+        qx = q[0], qy = q[1], qz = q[2], qw = q[3],
+
+        // calculate quat * vec
+        ix = qw * x + qy * z - qz * y,
+        iy = qw * y + qz * x - qx * z,
+        iz = qw * z + qx * y - qy * x,
+        iw = -qx * x - qy * y - qz * z;
+
+    // calculate result * inverse quat
+    out[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy;
+    out[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz;
+    out[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx;
+    return out;
+};
+
+/**
+ * Perform some operation over an array of vec4s.
+ *
+ * @param {Array} a the array of vectors to iterate over
+ * @param {Number} stride Number of elements between the start of each vec4. If 0 assumes tightly packed
+ * @param {Number} offset Number of elements to skip at the beginning of the array
+ * @param {Number} count Number of vec2s to iterate over. If 0 iterates over entire array
+ * @param {Function} fn Function to call for each vector in the array
+ * @param {Object} [arg] additional argument to pass to fn
+ * @returns {Array} a
+ * @function
+ */
+vec4.forEach = (function() {
+    var vec = vec4.create();
+
+    return function(a, stride, offset, count, fn, arg) {
+        var i, l;
+        if(!stride) {
+            stride = 4;
+        }
+
+        if(!offset) {
+            offset = 0;
+        }
+        
+        if(count) {
+            l = Math.min((count * stride) + offset, a.length);
+        } else {
+            l = a.length;
+        }
+
+        for(i = offset; i < l; i += stride) {
+            vec[0] = a[i]; vec[1] = a[i+1]; vec[2] = a[i+2]; vec[3] = a[i+3];
+            fn(vec, vec, arg);
+            a[i] = vec[0]; a[i+1] = vec[1]; a[i+2] = vec[2]; a[i+3] = vec[3];
+        }
+        
+        return a;
+    };
+})();
+
+/**
+ * Returns a string representation of a vector
+ *
+ * @param {vec4} vec vector to represent as a string
+ * @returns {String} string representation of the vector
+ */
+vec4.str = function (a) {
+    return 'vec4(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + a[3] + ')';
+};
+
+if(typeof(exports) !== 'undefined') {
+    exports.vec4 = vec4;
+}
+;
+/* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+  * Redistributions of source code must retain the above copyright notice, this
+    list of conditions and the following disclaimer.
+  * Redistributions in binary form must reproduce the above copyright notice,
+    this list of conditions and the following disclaimer in the documentation 
+    and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+
+/**
+ * @class 2x2 Matrix
+ * @name mat2
+ */
+
+var mat2 = {};
+
+/**
+ * Creates a new identity mat2
+ *
+ * @returns {mat2} a new 2x2 matrix
+ */
+mat2.create = function() {
+    var out = new GLMAT_ARRAY_TYPE(4);
+    out[0] = 1;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 1;
+    return out;
+};
+
+/**
+ * Creates a new mat2 initialized with values from an existing matrix
+ *
+ * @param {mat2} a matrix to clone
+ * @returns {mat2} a new 2x2 matrix
+ */
+mat2.clone = function(a) {
+    var out = new GLMAT_ARRAY_TYPE(4);
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    return out;
+};
+
+/**
+ * Copy the values from one mat2 to another
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {mat2} a the source matrix
+ * @returns {mat2} out
+ */
+mat2.copy = function(out, a) {
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    return out;
+};
+
+/**
+ * Set a mat2 to the identity matrix
+ *
+ * @param {mat2} out the receiving matrix
+ * @returns {mat2} out
+ */
+mat2.identity = function(out) {
+    out[0] = 1;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 1;
+    return out;
+};
+
+/**
+ * Transpose the values of a mat2
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {mat2} a the source matrix
+ * @returns {mat2} out
+ */
+mat2.transpose = function(out, a) {
+    // If we are transposing ourselves we can skip a few steps but have to cache some values
+    if (out === a) {
+        var a1 = a[1];
+        out[1] = a[2];
+        out[2] = a1;
+    } else {
+        out[0] = a[0];
+        out[1] = a[2];
+        out[2] = a[1];
+        out[3] = a[3];
+    }
+    
+    return out;
+};
+
+/**
+ * Inverts a mat2
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {mat2} a the source matrix
+ * @returns {mat2} out
+ */
+mat2.invert = function(out, a) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3],
+
+        // Calculate the determinant
+        det = a0 * a3 - a2 * a1;
+
+    if (!det) {
+        return null;
+    }
+    det = 1.0 / det;
+    
+    out[0] =  a3 * det;
+    out[1] = -a1 * det;
+    out[2] = -a2 * det;
+    out[3] =  a0 * det;
+
+    return out;
+};
+
+/**
+ * Calculates the adjugate of a mat2
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {mat2} a the source matrix
+ * @returns {mat2} out
+ */
+mat2.adjoint = function(out, a) {
+    // Caching this value is nessecary if out == a
+    var a0 = a[0];
+    out[0] =  a[3];
+    out[1] = -a[1];
+    out[2] = -a[2];
+    out[3] =  a0;
+
+    return out;
+};
+
+/**
+ * Calculates the determinant of a mat2
+ *
+ * @param {mat2} a the source matrix
+ * @returns {Number} determinant of a
+ */
+mat2.determinant = function (a) {
+    return a[0] * a[3] - a[2] * a[1];
+};
+
+/**
+ * Multiplies two mat2's
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {mat2} a the first operand
+ * @param {mat2} b the second operand
+ * @returns {mat2} out
+ */
+mat2.multiply = function (out, a, b) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3];
+    var b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
+    out[0] = a0 * b0 + a2 * b1;
+    out[1] = a1 * b0 + a3 * b1;
+    out[2] = a0 * b2 + a2 * b3;
+    out[3] = a1 * b2 + a3 * b3;
+    return out;
+};
+
+/**
+ * Alias for {@link mat2.multiply}
+ * @function
+ */
+mat2.mul = mat2.multiply;
+
+/**
+ * Rotates a mat2 by the given angle
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {mat2} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat2} out
+ */
+mat2.rotate = function (out, a, rad) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3],
+        s = Math.sin(rad),
+        c = Math.cos(rad);
+    out[0] = a0 *  c + a2 * s;
+    out[1] = a1 *  c + a3 * s;
+    out[2] = a0 * -s + a2 * c;
+    out[3] = a1 * -s + a3 * c;
+    return out;
+};
+
+/**
+ * Scales the mat2 by the dimensions in the given vec2
+ *
+ * @param {mat2} out the receiving matrix
+ * @param {mat2} a the matrix to rotate
+ * @param {vec2} v the vec2 to scale the matrix by
+ * @returns {mat2} out
+ **/
+mat2.scale = function(out, a, v) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3],
+        v0 = v[0], v1 = v[1];
+    out[0] = a0 * v0;
+    out[1] = a1 * v0;
+    out[2] = a2 * v1;
+    out[3] = a3 * v1;
+    return out;
+};
+
+/**
+ * Returns a string representation of a mat2
+ *
+ * @param {mat2} mat matrix to represent as a string
+ * @returns {String} string representation of the matrix
+ */
+mat2.str = function (a) {
+    return 'mat2(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + a[3] + ')';
+};
+
+/**
+ * Returns Frobenius norm of a mat2
+ *
+ * @param {mat2} a the matrix to calculate Frobenius norm of
+ * @returns {Number} Frobenius norm
+ */
+mat2.frob = function (a) {
+    return(Math.sqrt(Math.pow(a[0], 2) + Math.pow(a[1], 2) + Math.pow(a[2], 2) + Math.pow(a[3], 2)))
+};
+
+/**
+ * Returns L, D and U matrices (Lower triangular, Diagonal and Upper triangular) by factorizing the input matrix
+ * @param {mat2} L the lower triangular matrix 
+ * @param {mat2} D the diagonal matrix 
+ * @param {mat2} U the upper triangular matrix 
+ * @param {mat2} a the input matrix to factorize
+ */
+
+mat2.LDU = function (L, D, U, a) { 
+    L[2] = a[2]/a[0]; 
+    U[0] = a[0]; 
+    U[1] = a[1]; 
+    U[3] = a[3] - L[2] * U[1]; 
+    return [L, D, U];       
+}; 
+
+if(typeof(exports) !== 'undefined') {
+    exports.mat2 = mat2;
+}
+;
+/* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+  * Redistributions of source code must retain the above copyright notice, this
+    list of conditions and the following disclaimer.
+  * Redistributions in binary form must reproduce the above copyright notice,
+    this list of conditions and the following disclaimer in the documentation 
+    and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+
+/**
+ * @class 2x3 Matrix
+ * @name mat2d
+ * 
+ * @description 
+ * A mat2d contains six elements defined as:
+ * <pre>
+ * [a, c, tx,
+ *  b, d, ty]
+ * </pre>
+ * This is a short form for the 3x3 matrix:
+ * <pre>
+ * [a, c, tx,
+ *  b, d, ty,
+ *  0, 0, 1]
+ * </pre>
+ * The last row is ignored so the array is shorter and operations are faster.
+ */
+
+var mat2d = {};
+
+/**
+ * Creates a new identity mat2d
+ *
+ * @returns {mat2d} a new 2x3 matrix
+ */
+mat2d.create = function() {
+    var out = new GLMAT_ARRAY_TYPE(6);
+    out[0] = 1;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 1;
+    out[4] = 0;
+    out[5] = 0;
+    return out;
+};
+
+/**
+ * Creates a new mat2d initialized with values from an existing matrix
+ *
+ * @param {mat2d} a matrix to clone
+ * @returns {mat2d} a new 2x3 matrix
+ */
+mat2d.clone = function(a) {
+    var out = new GLMAT_ARRAY_TYPE(6);
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    out[4] = a[4];
+    out[5] = a[5];
+    return out;
+};
+
+/**
+ * Copy the values from one mat2d to another
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {mat2d} a the source matrix
+ * @returns {mat2d} out
+ */
+mat2d.copy = function(out, a) {
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    out[4] = a[4];
+    out[5] = a[5];
+    return out;
+};
+
+/**
+ * Set a mat2d to the identity matrix
+ *
+ * @param {mat2d} out the receiving matrix
+ * @returns {mat2d} out
+ */
+mat2d.identity = function(out) {
+    out[0] = 1;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 1;
+    out[4] = 0;
+    out[5] = 0;
+    return out;
+};
+
+/**
+ * Inverts a mat2d
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {mat2d} a the source matrix
+ * @returns {mat2d} out
+ */
+mat2d.invert = function(out, a) {
+    var aa = a[0], ab = a[1], ac = a[2], ad = a[3],
+        atx = a[4], aty = a[5];
+
+    var det = aa * ad - ab * ac;
+    if(!det){
+        return null;
+    }
+    det = 1.0 / det;
+
+    out[0] = ad * det;
+    out[1] = -ab * det;
+    out[2] = -ac * det;
+    out[3] = aa * det;
+    out[4] = (ac * aty - ad * atx) * det;
+    out[5] = (ab * atx - aa * aty) * det;
+    return out;
+};
+
+/**
+ * Calculates the determinant of a mat2d
+ *
+ * @param {mat2d} a the source matrix
+ * @returns {Number} determinant of a
+ */
+mat2d.determinant = function (a) {
+    return a[0] * a[3] - a[1] * a[2];
+};
+
+/**
+ * Multiplies two mat2d's
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {mat2d} a the first operand
+ * @param {mat2d} b the second operand
+ * @returns {mat2d} out
+ */
+mat2d.multiply = function (out, a, b) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5],
+        b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3], b4 = b[4], b5 = b[5];
+    out[0] = a0 * b0 + a2 * b1;
+    out[1] = a1 * b0 + a3 * b1;
+    out[2] = a0 * b2 + a2 * b3;
+    out[3] = a1 * b2 + a3 * b3;
+    out[4] = a0 * b4 + a2 * b5 + a4;
+    out[5] = a1 * b4 + a3 * b5 + a5;
+    return out;
+};
+
+/**
+ * Alias for {@link mat2d.multiply}
+ * @function
+ */
+mat2d.mul = mat2d.multiply;
+
+
+/**
+ * Rotates a mat2d by the given angle
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {mat2d} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat2d} out
+ */
+mat2d.rotate = function (out, a, rad) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5],
+        s = Math.sin(rad),
+        c = Math.cos(rad);
+    out[0] = a0 *  c + a2 * s;
+    out[1] = a1 *  c + a3 * s;
+    out[2] = a0 * -s + a2 * c;
+    out[3] = a1 * -s + a3 * c;
+    out[4] = a4;
+    out[5] = a5;
+    return out;
+};
+
+/**
+ * Scales the mat2d by the dimensions in the given vec2
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {mat2d} a the matrix to translate
+ * @param {vec2} v the vec2 to scale the matrix by
+ * @returns {mat2d} out
+ **/
+mat2d.scale = function(out, a, v) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5],
+        v0 = v[0], v1 = v[1];
+    out[0] = a0 * v0;
+    out[1] = a1 * v0;
+    out[2] = a2 * v1;
+    out[3] = a3 * v1;
+    out[4] = a4;
+    out[5] = a5;
+    return out;
+};
+
+/**
+ * Translates the mat2d by the dimensions in the given vec2
+ *
+ * @param {mat2d} out the receiving matrix
+ * @param {mat2d} a the matrix to translate
+ * @param {vec2} v the vec2 to translate the matrix by
+ * @returns {mat2d} out
+ **/
+mat2d.translate = function(out, a, v) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5],
+        v0 = v[0], v1 = v[1];
+    out[0] = a0;
+    out[1] = a1;
+    out[2] = a2;
+    out[3] = a3;
+    out[4] = a0 * v0 + a2 * v1 + a4;
+    out[5] = a1 * v0 + a3 * v1 + a5;
+    return out;
+};
+
+/**
+ * Returns a string representation of a mat2d
+ *
+ * @param {mat2d} a matrix to represent as a string
+ * @returns {String} string representation of the matrix
+ */
+mat2d.str = function (a) {
+    return 'mat2d(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + 
+                    a[3] + ', ' + a[4] + ', ' + a[5] + ')';
+};
+
+/**
+ * Returns Frobenius norm of a mat2d
+ *
+ * @param {mat2d} a the matrix to calculate Frobenius norm of
+ * @returns {Number} Frobenius norm
+ */
+mat2d.frob = function (a) { 
+    return(Math.sqrt(Math.pow(a[0], 2) + Math.pow(a[1], 2) + Math.pow(a[2], 2) + Math.pow(a[3], 2) + Math.pow(a[4], 2) + Math.pow(a[5], 2) + 1))
+}; 
+
+if(typeof(exports) !== 'undefined') {
+    exports.mat2d = mat2d;
+}
+;
+/* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+  * Redistributions of source code must retain the above copyright notice, this
+    list of conditions and the following disclaimer.
+  * Redistributions in binary form must reproduce the above copyright notice,
+    this list of conditions and the following disclaimer in the documentation 
+    and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+
+/**
+ * @class 3x3 Matrix
+ * @name mat3
+ */
+
+var mat3 = {};
+
+/**
+ * Creates a new identity mat3
+ *
+ * @returns {mat3} a new 3x3 matrix
+ */
+mat3.create = function() {
+    var out = new GLMAT_ARRAY_TYPE(9);
+    out[0] = 1;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 0;
+    out[4] = 1;
+    out[5] = 0;
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = 1;
+    return out;
+};
+
+/**
+ * Copies the upper-left 3x3 values into the given mat3.
+ *
+ * @param {mat3} out the receiving 3x3 matrix
+ * @param {mat4} a   the source 4x4 matrix
+ * @returns {mat3} out
+ */
+mat3.fromMat4 = function(out, a) {
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[4];
+    out[4] = a[5];
+    out[5] = a[6];
+    out[6] = a[8];
+    out[7] = a[9];
+    out[8] = a[10];
+    return out;
+};
+
+/**
+ * Creates a new mat3 initialized with values from an existing matrix
+ *
+ * @param {mat3} a matrix to clone
+ * @returns {mat3} a new 3x3 matrix
+ */
+mat3.clone = function(a) {
+    var out = new GLMAT_ARRAY_TYPE(9);
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    out[4] = a[4];
+    out[5] = a[5];
+    out[6] = a[6];
+    out[7] = a[7];
+    out[8] = a[8];
+    return out;
+};
+
+/**
+ * Copy the values from one mat3 to another
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat3} a the source matrix
+ * @returns {mat3} out
+ */
+mat3.copy = function(out, a) {
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    out[4] = a[4];
+    out[5] = a[5];
+    out[6] = a[6];
+    out[7] = a[7];
+    out[8] = a[8];
+    return out;
+};
+
+/**
+ * Set a mat3 to the identity matrix
+ *
+ * @param {mat3} out the receiving matrix
+ * @returns {mat3} out
+ */
+mat3.identity = function(out) {
+    out[0] = 1;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 0;
+    out[4] = 1;
+    out[5] = 0;
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = 1;
+    return out;
+};
+
+/**
+ * Transpose the values of a mat3
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat3} a the source matrix
+ * @returns {mat3} out
+ */
+mat3.transpose = function(out, a) {
+    // If we are transposing ourselves we can skip a few steps but have to cache some values
+    if (out === a) {
+        var a01 = a[1], a02 = a[2], a12 = a[5];
+        out[1] = a[3];
+        out[2] = a[6];
+        out[3] = a01;
+        out[5] = a[7];
+        out[6] = a02;
+        out[7] = a12;
+    } else {
+        out[0] = a[0];
+        out[1] = a[3];
+        out[2] = a[6];
+        out[3] = a[1];
+        out[4] = a[4];
+        out[5] = a[7];
+        out[6] = a[2];
+        out[7] = a[5];
+        out[8] = a[8];
+    }
+    
+    return out;
+};
+
+/**
+ * Inverts a mat3
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat3} a the source matrix
+ * @returns {mat3} out
+ */
+mat3.invert = function(out, a) {
+    var a00 = a[0], a01 = a[1], a02 = a[2],
+        a10 = a[3], a11 = a[4], a12 = a[5],
+        a20 = a[6], a21 = a[7], a22 = a[8],
+
+        b01 = a22 * a11 - a12 * a21,
+        b11 = -a22 * a10 + a12 * a20,
+        b21 = a21 * a10 - a11 * a20,
+
+        // Calculate the determinant
+        det = a00 * b01 + a01 * b11 + a02 * b21;
+
+    if (!det) { 
+        return null; 
+    }
+    det = 1.0 / det;
+
+    out[0] = b01 * det;
+    out[1] = (-a22 * a01 + a02 * a21) * det;
+    out[2] = (a12 * a01 - a02 * a11) * det;
+    out[3] = b11 * det;
+    out[4] = (a22 * a00 - a02 * a20) * det;
+    out[5] = (-a12 * a00 + a02 * a10) * det;
+    out[6] = b21 * det;
+    out[7] = (-a21 * a00 + a01 * a20) * det;
+    out[8] = (a11 * a00 - a01 * a10) * det;
+    return out;
+};
+
+/**
+ * Calculates the adjugate of a mat3
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat3} a the source matrix
+ * @returns {mat3} out
+ */
+mat3.adjoint = function(out, a) {
+    var a00 = a[0], a01 = a[1], a02 = a[2],
+        a10 = a[3], a11 = a[4], a12 = a[5],
+        a20 = a[6], a21 = a[7], a22 = a[8];
+
+    out[0] = (a11 * a22 - a12 * a21);
+    out[1] = (a02 * a21 - a01 * a22);
+    out[2] = (a01 * a12 - a02 * a11);
+    out[3] = (a12 * a20 - a10 * a22);
+    out[4] = (a00 * a22 - a02 * a20);
+    out[5] = (a02 * a10 - a00 * a12);
+    out[6] = (a10 * a21 - a11 * a20);
+    out[7] = (a01 * a20 - a00 * a21);
+    out[8] = (a00 * a11 - a01 * a10);
+    return out;
+};
+
+/**
+ * Calculates the determinant of a mat3
+ *
+ * @param {mat3} a the source matrix
+ * @returns {Number} determinant of a
+ */
+mat3.determinant = function (a) {
+    var a00 = a[0], a01 = a[1], a02 = a[2],
+        a10 = a[3], a11 = a[4], a12 = a[5],
+        a20 = a[6], a21 = a[7], a22 = a[8];
+
+    return a00 * (a22 * a11 - a12 * a21) + a01 * (-a22 * a10 + a12 * a20) + a02 * (a21 * a10 - a11 * a20);
+};
+
+/**
+ * Multiplies two mat3's
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat3} a the first operand
+ * @param {mat3} b the second operand
+ * @returns {mat3} out
+ */
+mat3.multiply = function (out, a, b) {
+    var a00 = a[0], a01 = a[1], a02 = a[2],
+        a10 = a[3], a11 = a[4], a12 = a[5],
+        a20 = a[6], a21 = a[7], a22 = a[8],
+
+        b00 = b[0], b01 = b[1], b02 = b[2],
+        b10 = b[3], b11 = b[4], b12 = b[5],
+        b20 = b[6], b21 = b[7], b22 = b[8];
+
+    out[0] = b00 * a00 + b01 * a10 + b02 * a20;
+    out[1] = b00 * a01 + b01 * a11 + b02 * a21;
+    out[2] = b00 * a02 + b01 * a12 + b02 * a22;
+
+    out[3] = b10 * a00 + b11 * a10 + b12 * a20;
+    out[4] = b10 * a01 + b11 * a11 + b12 * a21;
+    out[5] = b10 * a02 + b11 * a12 + b12 * a22;
+
+    out[6] = b20 * a00 + b21 * a10 + b22 * a20;
+    out[7] = b20 * a01 + b21 * a11 + b22 * a21;
+    out[8] = b20 * a02 + b21 * a12 + b22 * a22;
+    return out;
+};
+
+/**
+ * Alias for {@link mat3.multiply}
+ * @function
+ */
+mat3.mul = mat3.multiply;
+
+/**
+ * Translate a mat3 by the given vector
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat3} a the matrix to translate
+ * @param {vec2} v vector to translate by
+ * @returns {mat3} out
+ */
+mat3.translate = function(out, a, v) {
+    var a00 = a[0], a01 = a[1], a02 = a[2],
+        a10 = a[3], a11 = a[4], a12 = a[5],
+        a20 = a[6], a21 = a[7], a22 = a[8],
+        x = v[0], y = v[1];
+
+    out[0] = a00;
+    out[1] = a01;
+    out[2] = a02;
+
+    out[3] = a10;
+    out[4] = a11;
+    out[5] = a12;
+
+    out[6] = x * a00 + y * a10 + a20;
+    out[7] = x * a01 + y * a11 + a21;
+    out[8] = x * a02 + y * a12 + a22;
+    return out;
+};
+
+/**
+ * Rotates a mat3 by the given angle
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat3} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat3} out
+ */
+mat3.rotate = function (out, a, rad) {
+    var a00 = a[0], a01 = a[1], a02 = a[2],
+        a10 = a[3], a11 = a[4], a12 = a[5],
+        a20 = a[6], a21 = a[7], a22 = a[8],
+
+        s = Math.sin(rad),
+        c = Math.cos(rad);
+
+    out[0] = c * a00 + s * a10;
+    out[1] = c * a01 + s * a11;
+    out[2] = c * a02 + s * a12;
+
+    out[3] = c * a10 - s * a00;
+    out[4] = c * a11 - s * a01;
+    out[5] = c * a12 - s * a02;
+
+    out[6] = a20;
+    out[7] = a21;
+    out[8] = a22;
+    return out;
+};
+
+/**
+ * Scales the mat3 by the dimensions in the given vec2
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat3} a the matrix to rotate
+ * @param {vec2} v the vec2 to scale the matrix by
+ * @returns {mat3} out
+ **/
+mat3.scale = function(out, a, v) {
+    var x = v[0], y = v[1];
+
+    out[0] = x * a[0];
+    out[1] = x * a[1];
+    out[2] = x * a[2];
+
+    out[3] = y * a[3];
+    out[4] = y * a[4];
+    out[5] = y * a[5];
+
+    out[6] = a[6];
+    out[7] = a[7];
+    out[8] = a[8];
+    return out;
+};
+
+/**
+ * Copies the values from a mat2d into a mat3
+ *
+ * @param {mat3} out the receiving matrix
+ * @param {mat2d} a the matrix to copy
+ * @returns {mat3} out
+ **/
+mat3.fromMat2d = function(out, a) {
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = 0;
+
+    out[3] = a[2];
+    out[4] = a[3];
+    out[5] = 0;
+
+    out[6] = a[4];
+    out[7] = a[5];
+    out[8] = 1;
+    return out;
+};
+
+/**
+* Calculates a 3x3 matrix from the given quaternion
+*
+* @param {mat3} out mat3 receiving operation result
+* @param {quat} q Quaternion to create matrix from
+*
+* @returns {mat3} out
+*/
+mat3.fromQuat = function (out, q) {
+    var x = q[0], y = q[1], z = q[2], w = q[3],
+        x2 = x + x,
+        y2 = y + y,
+        z2 = z + z,
+
+        xx = x * x2,
+        yx = y * x2,
+        yy = y * y2,
+        zx = z * x2,
+        zy = z * y2,
+        zz = z * z2,
+        wx = w * x2,
+        wy = w * y2,
+        wz = w * z2;
+
+    out[0] = 1 - yy - zz;
+    out[3] = yx - wz;
+    out[6] = zx + wy;
+
+    out[1] = yx + wz;
+    out[4] = 1 - xx - zz;
+    out[7] = zy - wx;
+
+    out[2] = zx - wy;
+    out[5] = zy + wx;
+    out[8] = 1 - xx - yy;
+
+    return out;
+};
+
+/**
+* Calculates a 3x3 normal matrix (transpose inverse) from the 4x4 matrix
+*
+* @param {mat3} out mat3 receiving operation result
+* @param {mat4} a Mat4 to derive the normal matrix from
+*
+* @returns {mat3} out
+*/
+mat3.normalFromMat4 = function (out, a) {
+    var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
+        a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
+        a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
+        a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15],
+
+        b00 = a00 * a11 - a01 * a10,
+        b01 = a00 * a12 - a02 * a10,
+        b02 = a00 * a13 - a03 * a10,
+        b03 = a01 * a12 - a02 * a11,
+        b04 = a01 * a13 - a03 * a11,
+        b05 = a02 * a13 - a03 * a12,
+        b06 = a20 * a31 - a21 * a30,
+        b07 = a20 * a32 - a22 * a30,
+        b08 = a20 * a33 - a23 * a30,
+        b09 = a21 * a32 - a22 * a31,
+        b10 = a21 * a33 - a23 * a31,
+        b11 = a22 * a33 - a23 * a32,
+
+        // Calculate the determinant
+        det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
+
+    if (!det) { 
+        return null; 
+    }
+    det = 1.0 / det;
+
+    out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
+    out[1] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
+    out[2] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
+
+    out[3] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
+    out[4] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
+    out[5] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
+
+    out[6] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
+    out[7] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
+    out[8] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
+
+    return out;
+};
+
+/**
+ * Returns a string representation of a mat3
+ *
+ * @param {mat3} mat matrix to represent as a string
+ * @returns {String} string representation of the matrix
+ */
+mat3.str = function (a) {
+    return 'mat3(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + 
+                    a[3] + ', ' + a[4] + ', ' + a[5] + ', ' + 
+                    a[6] + ', ' + a[7] + ', ' + a[8] + ')';
+};
+
+/**
+ * Returns Frobenius norm of a mat3
+ *
+ * @param {mat3} a the matrix to calculate Frobenius norm of
+ * @returns {Number} Frobenius norm
+ */
+mat3.frob = function (a) {
+    return(Math.sqrt(Math.pow(a[0], 2) + Math.pow(a[1], 2) + Math.pow(a[2], 2) + Math.pow(a[3], 2) + Math.pow(a[4], 2) + Math.pow(a[5], 2) + Math.pow(a[6], 2) + Math.pow(a[7], 2) + Math.pow(a[8], 2)))
+};
+
+
+if(typeof(exports) !== 'undefined') {
+    exports.mat3 = mat3;
+}
+;
+/* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+  * Redistributions of source code must retain the above copyright notice, this
+    list of conditions and the following disclaimer.
+  * Redistributions in binary form must reproduce the above copyright notice,
+    this list of conditions and the following disclaimer in the documentation 
+    and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+
+/**
+ * @class 4x4 Matrix
+ * @name mat4
+ */
+
+var mat4 = {};
+
+/**
+ * Creates a new identity mat4
+ *
+ * @returns {mat4} a new 4x4 matrix
+ */
+mat4.create = function() {
+    var out = new GLMAT_ARRAY_TYPE(16);
+    out[0] = 1;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 0;
+    out[4] = 0;
+    out[5] = 1;
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = 0;
+    out[9] = 0;
+    out[10] = 1;
+    out[11] = 0;
+    out[12] = 0;
+    out[13] = 0;
+    out[14] = 0;
+    out[15] = 1;
+    return out;
+};
+
+/**
+ * Creates a new mat4 initialized with values from an existing matrix
+ *
+ * @param {mat4} a matrix to clone
+ * @returns {mat4} a new 4x4 matrix
+ */
+mat4.clone = function(a) {
+    var out = new GLMAT_ARRAY_TYPE(16);
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    out[4] = a[4];
+    out[5] = a[5];
+    out[6] = a[6];
+    out[7] = a[7];
+    out[8] = a[8];
+    out[9] = a[9];
+    out[10] = a[10];
+    out[11] = a[11];
+    out[12] = a[12];
+    out[13] = a[13];
+    out[14] = a[14];
+    out[15] = a[15];
+    return out;
+};
+
+/**
+ * Copy the values from one mat4 to another
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the source matrix
+ * @returns {mat4} out
+ */
+mat4.copy = function(out, a) {
+    out[0] = a[0];
+    out[1] = a[1];
+    out[2] = a[2];
+    out[3] = a[3];
+    out[4] = a[4];
+    out[5] = a[5];
+    out[6] = a[6];
+    out[7] = a[7];
+    out[8] = a[8];
+    out[9] = a[9];
+    out[10] = a[10];
+    out[11] = a[11];
+    out[12] = a[12];
+    out[13] = a[13];
+    out[14] = a[14];
+    out[15] = a[15];
+    return out;
+};
+
+/**
+ * Set a mat4 to the identity matrix
+ *
+ * @param {mat4} out the receiving matrix
+ * @returns {mat4} out
+ */
+mat4.identity = function(out) {
+    out[0] = 1;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 0;
+    out[4] = 0;
+    out[5] = 1;
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = 0;
+    out[9] = 0;
+    out[10] = 1;
+    out[11] = 0;
+    out[12] = 0;
+    out[13] = 0;
+    out[14] = 0;
+    out[15] = 1;
+    return out;
+};
+
+/**
+ * Transpose the values of a mat4
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the source matrix
+ * @returns {mat4} out
+ */
+mat4.transpose = function(out, a) {
+    // If we are transposing ourselves we can skip a few steps but have to cache some values
+    if (out === a) {
+        var a01 = a[1], a02 = a[2], a03 = a[3],
+            a12 = a[6], a13 = a[7],
+            a23 = a[11];
+
+        out[1] = a[4];
+        out[2] = a[8];
+        out[3] = a[12];
+        out[4] = a01;
+        out[6] = a[9];
+        out[7] = a[13];
+        out[8] = a02;
+        out[9] = a12;
+        out[11] = a[14];
+        out[12] = a03;
+        out[13] = a13;
+        out[14] = a23;
+    } else {
+        out[0] = a[0];
+        out[1] = a[4];
+        out[2] = a[8];
+        out[3] = a[12];
+        out[4] = a[1];
+        out[5] = a[5];
+        out[6] = a[9];
+        out[7] = a[13];
+        out[8] = a[2];
+        out[9] = a[6];
+        out[10] = a[10];
+        out[11] = a[14];
+        out[12] = a[3];
+        out[13] = a[7];
+        out[14] = a[11];
+        out[15] = a[15];
+    }
+    
+    return out;
+};
+
+/**
+ * Inverts a mat4
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the source matrix
+ * @returns {mat4} out
+ */
+mat4.invert = function(out, a) {
+    var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
+        a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
+        a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
+        a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15],
+
+        b00 = a00 * a11 - a01 * a10,
+        b01 = a00 * a12 - a02 * a10,
+        b02 = a00 * a13 - a03 * a10,
+        b03 = a01 * a12 - a02 * a11,
+        b04 = a01 * a13 - a03 * a11,
+        b05 = a02 * a13 - a03 * a12,
+        b06 = a20 * a31 - a21 * a30,
+        b07 = a20 * a32 - a22 * a30,
+        b08 = a20 * a33 - a23 * a30,
+        b09 = a21 * a32 - a22 * a31,
+        b10 = a21 * a33 - a23 * a31,
+        b11 = a22 * a33 - a23 * a32,
+
+        // Calculate the determinant
+        det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
+
+    if (!det) { 
+        return null; 
+    }
+    det = 1.0 / det;
+
+    out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
+    out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
+    out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
+    out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
+    out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
+    out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
+    out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
+    out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
+    out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
+    out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
+    out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
+    out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
+    out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
+    out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
+    out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
+    out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
+
+    return out;
+};
+
+/**
+ * Calculates the adjugate of a mat4
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the source matrix
+ * @returns {mat4} out
+ */
+mat4.adjoint = function(out, a) {
+    var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
+        a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
+        a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
+        a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
+
+    out[0]  =  (a11 * (a22 * a33 - a23 * a32) - a21 * (a12 * a33 - a13 * a32) + a31 * (a12 * a23 - a13 * a22));
+    out[1]  = -(a01 * (a22 * a33 - a23 * a32) - a21 * (a02 * a33 - a03 * a32) + a31 * (a02 * a23 - a03 * a22));
+    out[2]  =  (a01 * (a12 * a33 - a13 * a32) - a11 * (a02 * a33 - a03 * a32) + a31 * (a02 * a13 - a03 * a12));
+    out[3]  = -(a01 * (a12 * a23 - a13 * a22) - a11 * (a02 * a23 - a03 * a22) + a21 * (a02 * a13 - a03 * a12));
+    out[4]  = -(a10 * (a22 * a33 - a23 * a32) - a20 * (a12 * a33 - a13 * a32) + a30 * (a12 * a23 - a13 * a22));
+    out[5]  =  (a00 * (a22 * a33 - a23 * a32) - a20 * (a02 * a33 - a03 * a32) + a30 * (a02 * a23 - a03 * a22));
+    out[6]  = -(a00 * (a12 * a33 - a13 * a32) - a10 * (a02 * a33 - a03 * a32) + a30 * (a02 * a13 - a03 * a12));
+    out[7]  =  (a00 * (a12 * a23 - a13 * a22) - a10 * (a02 * a23 - a03 * a22) + a20 * (a02 * a13 - a03 * a12));
+    out[8]  =  (a10 * (a21 * a33 - a23 * a31) - a20 * (a11 * a33 - a13 * a31) + a30 * (a11 * a23 - a13 * a21));
+    out[9]  = -(a00 * (a21 * a33 - a23 * a31) - a20 * (a01 * a33 - a03 * a31) + a30 * (a01 * a23 - a03 * a21));
+    out[10] =  (a00 * (a11 * a33 - a13 * a31) - a10 * (a01 * a33 - a03 * a31) + a30 * (a01 * a13 - a03 * a11));
+    out[11] = -(a00 * (a11 * a23 - a13 * a21) - a10 * (a01 * a23 - a03 * a21) + a20 * (a01 * a13 - a03 * a11));
+    out[12] = -(a10 * (a21 * a32 - a22 * a31) - a20 * (a11 * a32 - a12 * a31) + a30 * (a11 * a22 - a12 * a21));
+    out[13] =  (a00 * (a21 * a32 - a22 * a31) - a20 * (a01 * a32 - a02 * a31) + a30 * (a01 * a22 - a02 * a21));
+    out[14] = -(a00 * (a11 * a32 - a12 * a31) - a10 * (a01 * a32 - a02 * a31) + a30 * (a01 * a12 - a02 * a11));
+    out[15] =  (a00 * (a11 * a22 - a12 * a21) - a10 * (a01 * a22 - a02 * a21) + a20 * (a01 * a12 - a02 * a11));
+    return out;
+};
+
+/**
+ * Calculates the determinant of a mat4
+ *
+ * @param {mat4} a the source matrix
+ * @returns {Number} determinant of a
+ */
+mat4.determinant = function (a) {
+    var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
+        a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
+        a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
+        a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15],
+
+        b00 = a00 * a11 - a01 * a10,
+        b01 = a00 * a12 - a02 * a10,
+        b02 = a00 * a13 - a03 * a10,
+        b03 = a01 * a12 - a02 * a11,
+        b04 = a01 * a13 - a03 * a11,
+        b05 = a02 * a13 - a03 * a12,
+        b06 = a20 * a31 - a21 * a30,
+        b07 = a20 * a32 - a22 * a30,
+        b08 = a20 * a33 - a23 * a30,
+        b09 = a21 * a32 - a22 * a31,
+        b10 = a21 * a33 - a23 * a31,
+        b11 = a22 * a33 - a23 * a32;
+
+    // Calculate the determinant
+    return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
+};
+
+/**
+ * Multiplies two mat4's
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the first operand
+ * @param {mat4} b the second operand
+ * @returns {mat4} out
+ */
+mat4.multiply = function (out, a, b) {
+    var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
+        a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
+        a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
+        a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
+
+    // Cache only the current line of the second matrix
+    var b0  = b[0], b1 = b[1], b2 = b[2], b3 = b[3];  
+    out[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
+    out[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
+    out[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
+    out[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
+
+    b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7];
+    out[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
+    out[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
+    out[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
+    out[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
+
+    b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11];
+    out[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
+    out[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
+    out[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
+    out[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
+
+    b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15];
+    out[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
+    out[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
+    out[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
+    out[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
+    return out;
+};
+
+/**
+ * Alias for {@link mat4.multiply}
+ * @function
+ */
+mat4.mul = mat4.multiply;
+
+/**
+ * Translate a mat4 by the given vector
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to translate
+ * @param {vec3} v vector to translate by
+ * @returns {mat4} out
+ */
+mat4.translate = function (out, a, v) {
+    var x = v[0], y = v[1], z = v[2],
+        a00, a01, a02, a03,
+        a10, a11, a12, a13,
+        a20, a21, a22, a23;
+
+    if (a === out) {
+        out[12] = a[0] * x + a[4] * y + a[8] * z + a[12];
+        out[13] = a[1] * x + a[5] * y + a[9] * z + a[13];
+        out[14] = a[2] * x + a[6] * y + a[10] * z + a[14];
+        out[15] = a[3] * x + a[7] * y + a[11] * z + a[15];
+    } else {
+        a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3];
+        a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7];
+        a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11];
+
+        out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a03;
+        out[4] = a10; out[5] = a11; out[6] = a12; out[7] = a13;
+        out[8] = a20; out[9] = a21; out[10] = a22; out[11] = a23;
+
+        out[12] = a00 * x + a10 * y + a20 * z + a[12];
+        out[13] = a01 * x + a11 * y + a21 * z + a[13];
+        out[14] = a02 * x + a12 * y + a22 * z + a[14];
+        out[15] = a03 * x + a13 * y + a23 * z + a[15];
+    }
+
+    return out;
+};
+
+/**
+ * Scales the mat4 by the dimensions in the given vec3
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to scale
+ * @param {vec3} v the vec3 to scale the matrix by
+ * @returns {mat4} out
+ **/
+mat4.scale = function(out, a, v) {
+    var x = v[0], y = v[1], z = v[2];
+
+    out[0] = a[0] * x;
+    out[1] = a[1] * x;
+    out[2] = a[2] * x;
+    out[3] = a[3] * x;
+    out[4] = a[4] * y;
+    out[5] = a[5] * y;
+    out[6] = a[6] * y;
+    out[7] = a[7] * y;
+    out[8] = a[8] * z;
+    out[9] = a[9] * z;
+    out[10] = a[10] * z;
+    out[11] = a[11] * z;
+    out[12] = a[12];
+    out[13] = a[13];
+    out[14] = a[14];
+    out[15] = a[15];
+    return out;
+};
+
+/**
+ * Rotates a mat4 by the given angle
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @param {vec3} axis the axis to rotate around
+ * @returns {mat4} out
+ */
+mat4.rotate = function (out, a, rad, axis) {
+    var x = axis[0], y = axis[1], z = axis[2],
+        len = Math.sqrt(x * x + y * y + z * z),
+        s, c, t,
+        a00, a01, a02, a03,
+        a10, a11, a12, a13,
+        a20, a21, a22, a23,
+        b00, b01, b02,
+        b10, b11, b12,
+        b20, b21, b22;
+
+    if (Math.abs(len) < GLMAT_EPSILON) { return null; }
+    
+    len = 1 / len;
+    x *= len;
+    y *= len;
+    z *= len;
+
+    s = Math.sin(rad);
+    c = Math.cos(rad);
+    t = 1 - c;
+
+    a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3];
+    a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7];
+    a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11];
+
+    // Construct the elements of the rotation matrix
+    b00 = x * x * t + c; b01 = y * x * t + z * s; b02 = z * x * t - y * s;
+    b10 = x * y * t - z * s; b11 = y * y * t + c; b12 = z * y * t + x * s;
+    b20 = x * z * t + y * s; b21 = y * z * t - x * s; b22 = z * z * t + c;
+
+    // Perform rotation-specific matrix multiplication
+    out[0] = a00 * b00 + a10 * b01 + a20 * b02;
+    out[1] = a01 * b00 + a11 * b01 + a21 * b02;
+    out[2] = a02 * b00 + a12 * b01 + a22 * b02;
+    out[3] = a03 * b00 + a13 * b01 + a23 * b02;
+    out[4] = a00 * b10 + a10 * b11 + a20 * b12;
+    out[5] = a01 * b10 + a11 * b11 + a21 * b12;
+    out[6] = a02 * b10 + a12 * b11 + a22 * b12;
+    out[7] = a03 * b10 + a13 * b11 + a23 * b12;
+    out[8] = a00 * b20 + a10 * b21 + a20 * b22;
+    out[9] = a01 * b20 + a11 * b21 + a21 * b22;
+    out[10] = a02 * b20 + a12 * b21 + a22 * b22;
+    out[11] = a03 * b20 + a13 * b21 + a23 * b22;
+
+    if (a !== out) { // If the source and destination differ, copy the unchanged last row
+        out[12] = a[12];
+        out[13] = a[13];
+        out[14] = a[14];
+        out[15] = a[15];
+    }
+    return out;
+};
+
+/**
+ * Rotates a matrix by the given angle around the X axis
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+mat4.rotateX = function (out, a, rad) {
+    var s = Math.sin(rad),
+        c = Math.cos(rad),
+        a10 = a[4],
+        a11 = a[5],
+        a12 = a[6],
+        a13 = a[7],
+        a20 = a[8],
+        a21 = a[9],
+        a22 = a[10],
+        a23 = a[11];
+
+    if (a !== out) { // If the source and destination differ, copy the unchanged rows
+        out[0]  = a[0];
+        out[1]  = a[1];
+        out[2]  = a[2];
+        out[3]  = a[3];
+        out[12] = a[12];
+        out[13] = a[13];
+        out[14] = a[14];
+        out[15] = a[15];
+    }
+
+    // Perform axis-specific matrix multiplication
+    out[4] = a10 * c + a20 * s;
+    out[5] = a11 * c + a21 * s;
+    out[6] = a12 * c + a22 * s;
+    out[7] = a13 * c + a23 * s;
+    out[8] = a20 * c - a10 * s;
+    out[9] = a21 * c - a11 * s;
+    out[10] = a22 * c - a12 * s;
+    out[11] = a23 * c - a13 * s;
+    return out;
+};
+
+/**
+ * Rotates a matrix by the given angle around the Y axis
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+mat4.rotateY = function (out, a, rad) {
+    var s = Math.sin(rad),
+        c = Math.cos(rad),
+        a00 = a[0],
+        a01 = a[1],
+        a02 = a[2],
+        a03 = a[3],
+        a20 = a[8],
+        a21 = a[9],
+        a22 = a[10],
+        a23 = a[11];
+
+    if (a !== out) { // If the source and destination differ, copy the unchanged rows
+        out[4]  = a[4];
+        out[5]  = a[5];
+        out[6]  = a[6];
+        out[7]  = a[7];
+        out[12] = a[12];
+        out[13] = a[13];
+        out[14] = a[14];
+        out[15] = a[15];
+    }
+
+    // Perform axis-specific matrix multiplication
+    out[0] = a00 * c - a20 * s;
+    out[1] = a01 * c - a21 * s;
+    out[2] = a02 * c - a22 * s;
+    out[3] = a03 * c - a23 * s;
+    out[8] = a00 * s + a20 * c;
+    out[9] = a01 * s + a21 * c;
+    out[10] = a02 * s + a22 * c;
+    out[11] = a03 * s + a23 * c;
+    return out;
+};
+
+/**
+ * Rotates a matrix by the given angle around the Z axis
+ *
+ * @param {mat4} out the receiving matrix
+ * @param {mat4} a the matrix to rotate
+ * @param {Number} rad the angle to rotate the matrix by
+ * @returns {mat4} out
+ */
+mat4.rotateZ = function (out, a, rad) {
+    var s = Math.sin(rad),
+        c = Math.cos(rad),
+        a00 = a[0],
+        a01 = a[1],
+        a02 = a[2],
+        a03 = a[3],
+        a10 = a[4],
+        a11 = a[5],
+        a12 = a[6],
+        a13 = a[7];
+
+    if (a !== out) { // If the source and destination differ, copy the unchanged last row
+        out[8]  = a[8];
+        out[9]  = a[9];
+        out[10] = a[10];
+        out[11] = a[11];
+        out[12] = a[12];
+        out[13] = a[13];
+        out[14] = a[14];
+        out[15] = a[15];
+    }
+
+    // Perform axis-specific matrix multiplication
+    out[0] = a00 * c + a10 * s;
+    out[1] = a01 * c + a11 * s;
+    out[2] = a02 * c + a12 * s;
+    out[3] = a03 * c + a13 * s;
+    out[4] = a10 * c - a00 * s;
+    out[5] = a11 * c - a01 * s;
+    out[6] = a12 * c - a02 * s;
+    out[7] = a13 * c - a03 * s;
+    return out;
+};
+
+/**
+ * Creates a matrix from a quaternion rotation and vector translation
+ * This is equivalent to (but much faster than):
+ *
+ *     mat4.identity(dest);
+ *     mat4.translate(dest, vec);
+ *     var quatMat = mat4.create();
+ *     quat4.toMat4(quat, quatMat);
+ *     mat4.multiply(dest, quatMat);
+ *
+ * @param {mat4} out mat4 receiving operation result
+ * @param {quat4} q Rotation quaternion
+ * @param {vec3} v Translation vector
+ * @returns {mat4} out
+ */
+mat4.fromRotationTranslation = function (out, q, v) {
+    // Quaternion math
+    var x = q[0], y = q[1], z = q[2], w = q[3],
+        x2 = x + x,
+        y2 = y + y,
+        z2 = z + z,
+
+        xx = x * x2,
+        xy = x * y2,
+        xz = x * z2,
+        yy = y * y2,
+        yz = y * z2,
+        zz = z * z2,
+        wx = w * x2,
+        wy = w * y2,
+        wz = w * z2;
+
+    out[0] = 1 - (yy + zz);
+    out[1] = xy + wz;
+    out[2] = xz - wy;
+    out[3] = 0;
+    out[4] = xy - wz;
+    out[5] = 1 - (xx + zz);
+    out[6] = yz + wx;
+    out[7] = 0;
+    out[8] = xz + wy;
+    out[9] = yz - wx;
+    out[10] = 1 - (xx + yy);
+    out[11] = 0;
+    out[12] = v[0];
+    out[13] = v[1];
+    out[14] = v[2];
+    out[15] = 1;
+    
+    return out;
+};
+
+mat4.fromQuat = function (out, q) {
+    var x = q[0], y = q[1], z = q[2], w = q[3],
+        x2 = x + x,
+        y2 = y + y,
+        z2 = z + z,
+
+        xx = x * x2,
+        yx = y * x2,
+        yy = y * y2,
+        zx = z * x2,
+        zy = z * y2,
+        zz = z * z2,
+        wx = w * x2,
+        wy = w * y2,
+        wz = w * z2;
+
+    out[0] = 1 - yy - zz;
+    out[1] = yx + wz;
+    out[2] = zx - wy;
+    out[3] = 0;
+
+    out[4] = yx - wz;
+    out[5] = 1 - xx - zz;
+    out[6] = zy + wx;
+    out[7] = 0;
+
+    out[8] = zx + wy;
+    out[9] = zy - wx;
+    out[10] = 1 - xx - yy;
+    out[11] = 0;
+
+    out[12] = 0;
+    out[13] = 0;
+    out[14] = 0;
+    out[15] = 1;
+
+    return out;
+};
+
+/**
+ * Generates a frustum matrix with the given bounds
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {Number} left Left bound of the frustum
+ * @param {Number} right Right bound of the frustum
+ * @param {Number} bottom Bottom bound of the frustum
+ * @param {Number} top Top bound of the frustum
+ * @param {Number} near Near bound of the frustum
+ * @param {Number} far Far bound of the frustum
+ * @returns {mat4} out
+ */
+mat4.frustum = function (out, left, right, bottom, top, near, far) {
+    var rl = 1 / (right - left),
+        tb = 1 / (top - bottom),
+        nf = 1 / (near - far);
+    out[0] = (near * 2) * rl;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 0;
+    out[4] = 0;
+    out[5] = (near * 2) * tb;
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = (right + left) * rl;
+    out[9] = (top + bottom) * tb;
+    out[10] = (far + near) * nf;
+    out[11] = -1;
+    out[12] = 0;
+    out[13] = 0;
+    out[14] = (far * near * 2) * nf;
+    out[15] = 0;
+    return out;
+};
+
+/**
+ * Generates a perspective projection matrix with the given bounds
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {number} fovy Vertical field of view in radians
+ * @param {number} aspect Aspect ratio. typically viewport width/height
+ * @param {number} near Near bound of the frustum
+ * @param {number} far Far bound of the frustum
+ * @returns {mat4} out
+ */
+mat4.perspective = function (out, fovy, aspect, near, far) {
+    var f = 1.0 / Math.tan(fovy / 2),
+        nf = 1 / (near - far);
+    out[0] = f / aspect;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 0;
+    out[4] = 0;
+    out[5] = f;
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = 0;
+    out[9] = 0;
+    out[10] = (far + near) * nf;
+    out[11] = -1;
+    out[12] = 0;
+    out[13] = 0;
+    out[14] = (2 * far * near) * nf;
+    out[15] = 0;
+    return out;
+};
+
+/**
+ * Generates a orthogonal projection matrix with the given bounds
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {number} left Left bound of the frustum
+ * @param {number} right Right bound of the frustum
+ * @param {number} bottom Bottom bound of the frustum
+ * @param {number} top Top bound of the frustum
+ * @param {number} near Near bound of the frustum
+ * @param {number} far Far bound of the frustum
+ * @returns {mat4} out
+ */
+mat4.ortho = function (out, left, right, bottom, top, near, far) {
+    var lr = 1 / (left - right),
+        bt = 1 / (bottom - top),
+        nf = 1 / (near - far);
+    out[0] = -2 * lr;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 0;
+    out[4] = 0;
+    out[5] = -2 * bt;
+    out[6] = 0;
+    out[7] = 0;
+    out[8] = 0;
+    out[9] = 0;
+    out[10] = 2 * nf;
+    out[11] = 0;
+    out[12] = (left + right) * lr;
+    out[13] = (top + bottom) * bt;
+    out[14] = (far + near) * nf;
+    out[15] = 1;
+    return out;
+};
+
+/**
+ * Generates a look-at matrix with the given eye position, focal point, and up axis
+ *
+ * @param {mat4} out mat4 frustum matrix will be written into
+ * @param {vec3} eye Position of the viewer
+ * @param {vec3} center Point the viewer is looking at
+ * @param {vec3} up vec3 pointing up
+ * @returns {mat4} out
+ */
+mat4.lookAt = function (out, eye, center, up) {
+    var x0, x1, x2, y0, y1, y2, z0, z1, z2, len,
+        eyex = eye[0],
+        eyey = eye[1],
+        eyez = eye[2],
+        upx = up[0],
+        upy = up[1],
+        upz = up[2],
+        centerx = center[0],
+        centery = center[1],
+        centerz = center[2];
+
+    if (Math.abs(eyex - centerx) < GLMAT_EPSILON &&
+        Math.abs(eyey - centery) < GLMAT_EPSILON &&
+        Math.abs(eyez - centerz) < GLMAT_EPSILON) {
+        return mat4.identity(out);
+    }
+
+    z0 = eyex - centerx;
+    z1 = eyey - centery;
+    z2 = eyez - centerz;
+
+    len = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2);
+    z0 *= len;
+    z1 *= len;
+    z2 *= len;
+
+    x0 = upy * z2 - upz * z1;
+    x1 = upz * z0 - upx * z2;
+    x2 = upx * z1 - upy * z0;
+    len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2);
+    if (!len) {
+        x0 = 0;
+        x1 = 0;
+        x2 = 0;
+    } else {
+        len = 1 / len;
+        x0 *= len;
+        x1 *= len;
+        x2 *= len;
+    }
+
+    y0 = z1 * x2 - z2 * x1;
+    y1 = z2 * x0 - z0 * x2;
+    y2 = z0 * x1 - z1 * x0;
+
+    len = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2);
+    if (!len) {
+        y0 = 0;
+        y1 = 0;
+        y2 = 0;
+    } else {
+        len = 1 / len;
+        y0 *= len;
+        y1 *= len;
+        y2 *= len;
+    }
+
+    out[0] = x0;
+    out[1] = y0;
+    out[2] = z0;
+    out[3] = 0;
+    out[4] = x1;
+    out[5] = y1;
+    out[6] = z1;
+    out[7] = 0;
+    out[8] = x2;
+    out[9] = y2;
+    out[10] = z2;
+    out[11] = 0;
+    out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez);
+    out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez);
+    out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez);
+    out[15] = 1;
+
+    return out;
+};
+
+/**
+ * Returns a string representation of a mat4
+ *
+ * @param {mat4} mat matrix to represent as a string
+ * @returns {String} string representation of the matrix
+ */
+mat4.str = function (a) {
+    return 'mat4(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + a[3] + ', ' +
+                    a[4] + ', ' + a[5] + ', ' + a[6] + ', ' + a[7] + ', ' +
+                    a[8] + ', ' + a[9] + ', ' + a[10] + ', ' + a[11] + ', ' + 
+                    a[12] + ', ' + a[13] + ', ' + a[14] + ', ' + a[15] + ')';
+};
+
+/**
+ * Returns Frobenius norm of a mat4
+ *
+ * @param {mat4} a the matrix to calculate Frobenius norm of
+ * @returns {Number} Frobenius norm
+ */
+mat4.frob = function (a) {
+    return(Math.sqrt(Math.pow(a[0], 2) + Math.pow(a[1], 2) + Math.pow(a[2], 2) + Math.pow(a[3], 2) + Math.pow(a[4], 2) + Math.pow(a[5], 2) + Math.pow(a[6], 2) + Math.pow(a[6], 2) + Math.pow(a[7], 2) + Math.pow(a[8], 2) + Math.pow(a[9], 2) + Math.pow(a[10], 2) + Math.pow(a[11], 2) + Math.pow(a[12], 2) + Math.pow(a[13], 2) + Math.pow(a[14], 2) + Math.pow(a[15], 2) ))
+};
+
+
+if(typeof(exports) !== 'undefined') {
+    exports.mat4 = mat4;
+}
+;
+/* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+  * Redistributions of source code must retain the above copyright notice, this
+    list of conditions and the following disclaimer.
+  * Redistributions in binary form must reproduce the above copyright notice,
+    this list of conditions and the following disclaimer in the documentation 
+    and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+
+/**
+ * @class Quaternion
+ * @name quat
+ */
+
+var quat = {};
+
+/**
+ * Creates a new identity quat
+ *
+ * @returns {quat} a new quaternion
+ */
+quat.create = function() {
+    var out = new GLMAT_ARRAY_TYPE(4);
+    out[0] = 0;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 1;
+    return out;
+};
+
+/**
+ * Sets a quaternion to represent the shortest rotation from one
+ * vector to another.
+ *
+ * Both vectors are assumed to be unit length.
+ *
+ * @param {quat} out the receiving quaternion.
+ * @param {vec3} a the initial vector
+ * @param {vec3} b the destination vector
+ * @returns {quat} out
+ */
+quat.rotationTo = (function() {
+    var tmpvec3 = vec3.create();
+    var xUnitVec3 = vec3.fromValues(1,0,0);
+    var yUnitVec3 = vec3.fromValues(0,1,0);
+
+    return function(out, a, b) {
+        var dot = vec3.dot(a, b);
+        if (dot < -0.999999) {
+            vec3.cross(tmpvec3, xUnitVec3, a);
+            if (vec3.length(tmpvec3) < 0.000001)
+                vec3.cross(tmpvec3, yUnitVec3, a);
+            vec3.normalize(tmpvec3, tmpvec3);
+            quat.setAxisAngle(out, tmpvec3, Math.PI);
+            return out;
+        } else if (dot > 0.999999) {
+            out[0] = 0;
+            out[1] = 0;
+            out[2] = 0;
+            out[3] = 1;
+            return out;
+        } else {
+            vec3.cross(tmpvec3, a, b);
+            out[0] = tmpvec3[0];
+            out[1] = tmpvec3[1];
+            out[2] = tmpvec3[2];
+            out[3] = 1 + dot;
+            return quat.normalize(out, out);
+        }
+    };
+})();
+
+/**
+ * Sets the specified quaternion with values corresponding to the given
+ * axes. Each axis is a vec3 and is expected to be unit length and
+ * perpendicular to all other specified axes.
+ *
+ * @param {vec3} view  the vector representing the viewing direction
+ * @param {vec3} right the vector representing the local "right" direction
+ * @param {vec3} up    the vector representing the local "up" direction
+ * @returns {quat} out
+ */
+quat.setAxes = (function() {
+    var matr = mat3.create();
+
+    return function(out, view, right, up) {
+        matr[0] = right[0];
+        matr[3] = right[1];
+        matr[6] = right[2];
+
+        matr[1] = up[0];
+        matr[4] = up[1];
+        matr[7] = up[2];
+
+        matr[2] = -view[0];
+        matr[5] = -view[1];
+        matr[8] = -view[2];
+
+        return quat.normalize(out, quat.fromMat3(out, matr));
+    };
+})();
+
+/**
+ * Creates a new quat initialized with values from an existing quaternion
+ *
+ * @param {quat} a quaternion to clone
+ * @returns {quat} a new quaternion
+ * @function
+ */
+quat.clone = vec4.clone;
+
+/**
+ * Creates a new quat initialized with the given values
+ *
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @param {Number} w W component
+ * @returns {quat} a new quaternion
+ * @function
+ */
+quat.fromValues = vec4.fromValues;
+
+/**
+ * Copy the values from one quat to another
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {quat} a the source quaternion
+ * @returns {quat} out
+ * @function
+ */
+quat.copy = vec4.copy;
+
+/**
+ * Set the components of a quat to the given values
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {Number} x X component
+ * @param {Number} y Y component
+ * @param {Number} z Z component
+ * @param {Number} w W component
+ * @returns {quat} out
+ * @function
+ */
+quat.set = vec4.set;
+
+/**
+ * Set a quat to the identity quaternion
+ *
+ * @param {quat} out the receiving quaternion
+ * @returns {quat} out
+ */
+quat.identity = function(out) {
+    out[0] = 0;
+    out[1] = 0;
+    out[2] = 0;
+    out[3] = 1;
+    return out;
+};
+
+/**
+ * Sets a quat from the given angle and rotation axis,
+ * then returns it.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {vec3} axis the axis around which to rotate
+ * @param {Number} rad the angle in radians
+ * @returns {quat} out
+ **/
+quat.setAxisAngle = function(out, axis, rad) {
+    rad = rad * 0.5;
+    var s = Math.sin(rad);
+    out[0] = s * axis[0];
+    out[1] = s * axis[1];
+    out[2] = s * axis[2];
+    out[3] = Math.cos(rad);
+    return out;
+};
+
+/**
+ * Adds two quat's
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {quat} a the first operand
+ * @param {quat} b the second operand
+ * @returns {quat} out
+ * @function
+ */
+quat.add = vec4.add;
+
+/**
+ * Multiplies two quat's
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {quat} a the first operand
+ * @param {quat} b the second operand
+ * @returns {quat} out
+ */
+quat.multiply = function(out, a, b) {
+    var ax = a[0], ay = a[1], az = a[2], aw = a[3],
+        bx = b[0], by = b[1], bz = b[2], bw = b[3];
+
+    out[0] = ax * bw + aw * bx + ay * bz - az * by;
+    out[1] = ay * bw + aw * by + az * bx - ax * bz;
+    out[2] = az * bw + aw * bz + ax * by - ay * bx;
+    out[3] = aw * bw - ax * bx - ay * by - az * bz;
+    return out;
+};
+
+/**
+ * Alias for {@link quat.multiply}
+ * @function
+ */
+quat.mul = quat.multiply;
+
+/**
+ * Scales a quat by a scalar number
+ *
+ * @param {quat} out the receiving vector
+ * @param {quat} a the vector to scale
+ * @param {Number} b amount to scale the vector by
+ * @returns {quat} out
+ * @function
+ */
+quat.scale = vec4.scale;
+
+/**
+ * Rotates a quaternion by the given angle about the X axis
+ *
+ * @param {quat} out quat receiving operation result
+ * @param {quat} a quat to rotate
+ * @param {number} rad angle (in radians) to rotate
+ * @returns {quat} out
+ */
+quat.rotateX = function (out, a, rad) {
+    rad *= 0.5; 
+
+    var ax = a[0], ay = a[1], az = a[2], aw = a[3],
+        bx = Math.sin(rad), bw = Math.cos(rad);
+
+    out[0] = ax * bw + aw * bx;
+    out[1] = ay * bw + az * bx;
+    out[2] = az * bw - ay * bx;
+    out[3] = aw * bw - ax * bx;
+    return out;
+};
+
+/**
+ * Rotates a quaternion by the given angle about the Y axis
+ *
+ * @param {quat} out quat receiving operation result
+ * @param {quat} a quat to rotate
+ * @param {number} rad angle (in radians) to rotate
+ * @returns {quat} out
+ */
+quat.rotateY = function (out, a, rad) {
+    rad *= 0.5; 
+
+    var ax = a[0], ay = a[1], az = a[2], aw = a[3],
+        by = Math.sin(rad), bw = Math.cos(rad);
+
+    out[0] = ax * bw - az * by;
+    out[1] = ay * bw + aw * by;
+    out[2] = az * bw + ax * by;
+    out[3] = aw * bw - ay * by;
+    return out;
+};
+
+/**
+ * Rotates a quaternion by the given angle about the Z axis
+ *
+ * @param {quat} out quat receiving operation result
+ * @param {quat} a quat to rotate
+ * @param {number} rad angle (in radians) to rotate
+ * @returns {quat} out
+ */
+quat.rotateZ = function (out, a, rad) {
+    rad *= 0.5; 
+
+    var ax = a[0], ay = a[1], az = a[2], aw = a[3],
+        bz = Math.sin(rad), bw = Math.cos(rad);
+
+    out[0] = ax * bw + ay * bz;
+    out[1] = ay * bw - ax * bz;
+    out[2] = az * bw + aw * bz;
+    out[3] = aw * bw - az * bz;
+    return out;
+};
+
+/**
+ * Calculates the W component of a quat from the X, Y, and Z components.
+ * Assumes that quaternion is 1 unit in length.
+ * Any existing W component will be ignored.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {quat} a quat to calculate W component of
+ * @returns {quat} out
+ */
+quat.calculateW = function (out, a) {
+    var x = a[0], y = a[1], z = a[2];
+
+    out[0] = x;
+    out[1] = y;
+    out[2] = z;
+    out[3] = -Math.sqrt(Math.abs(1.0 - x * x - y * y - z * z));
+    return out;
+};
+
+/**
+ * Calculates the dot product of two quat's
+ *
+ * @param {quat} a the first operand
+ * @param {quat} b the second operand
+ * @returns {Number} dot product of a and b
+ * @function
+ */
+quat.dot = vec4.dot;
+
+/**
+ * Performs a linear interpolation between two quat's
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {quat} a the first operand
+ * @param {quat} b the second operand
+ * @param {Number} t interpolation amount between the two inputs
+ * @returns {quat} out
+ * @function
+ */
+quat.lerp = vec4.lerp;
+
+/**
+ * Performs a spherical linear interpolation between two quat
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {quat} a the first operand
+ * @param {quat} b the second operand
+ * @param {Number} t interpolation amount between the two inputs
+ * @returns {quat} out
+ */
+quat.slerp = function (out, a, b, t) {
+    // benchmarks:
+    //    http://jsperf.com/quaternion-slerp-implementations
+
+    var ax = a[0], ay = a[1], az = a[2], aw = a[3],
+        bx = b[0], by = b[1], bz = b[2], bw = b[3];
+
+    var        omega, cosom, sinom, scale0, scale1;
+
+    // calc cosine
+    cosom = ax * bx + ay * by + az * bz + aw * bw;
+    // adjust signs (if necessary)
+    if ( cosom < 0.0 ) {
+        cosom = -cosom;
+        bx = - bx;
+        by = - by;
+        bz = - bz;
+        bw = - bw;
+    }
+    // calculate coefficients
+    if ( (1.0 - cosom) > 0.000001 ) {
+        // standard case (slerp)
+        omega  = Math.acos(cosom);
+        sinom  = Math.sin(omega);
+        scale0 = Math.sin((1.0 - t) * omega) / sinom;
+        scale1 = Math.sin(t * omega) / sinom;
+    } else {        
+        // "from" and "to" quaternions are very close 
+        //  ... so we can do a linear interpolation
+        scale0 = 1.0 - t;
+        scale1 = t;
+    }
+    // calculate final values
+    out[0] = scale0 * ax + scale1 * bx;
+    out[1] = scale0 * ay + scale1 * by;
+    out[2] = scale0 * az + scale1 * bz;
+    out[3] = scale0 * aw + scale1 * bw;
+    
+    return out;
+};
+
+/**
+ * Calculates the inverse of a quat
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {quat} a quat to calculate inverse of
+ * @returns {quat} out
+ */
+quat.invert = function(out, a) {
+    var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3],
+        dot = a0*a0 + a1*a1 + a2*a2 + a3*a3,
+        invDot = dot ? 1.0/dot : 0;
+    
+    // TODO: Would be faster to return [0,0,0,0] immediately if dot == 0
+
+    out[0] = -a0*invDot;
+    out[1] = -a1*invDot;
+    out[2] = -a2*invDot;
+    out[3] = a3*invDot;
+    return out;
+};
+
+/**
+ * Calculates the conjugate of a quat
+ * If the quaternion is normalized, this function is faster than quat.inverse and produces the same result.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {quat} a quat to calculate conjugate of
+ * @returns {quat} out
+ */
+quat.conjugate = function (out, a) {
+    out[0] = -a[0];
+    out[1] = -a[1];
+    out[2] = -a[2];
+    out[3] = a[3];
+    return out;
+};
+
+/**
+ * Calculates the length of a quat
+ *
+ * @param {quat} a vector to calculate length of
+ * @returns {Number} length of a
+ * @function
+ */
+quat.length = vec4.length;
+
+/**
+ * Alias for {@link quat.length}
+ * @function
+ */
+quat.len = quat.length;
+
+/**
+ * Calculates the squared length of a quat
+ *
+ * @param {quat} a vector to calculate squared length of
+ * @returns {Number} squared length of a
+ * @function
+ */
+quat.squaredLength = vec4.squaredLength;
+
+/**
+ * Alias for {@link quat.squaredLength}
+ * @function
+ */
+quat.sqrLen = quat.squaredLength;
+
+/**
+ * Normalize a quat
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {quat} a quaternion to normalize
+ * @returns {quat} out
+ * @function
+ */
+quat.normalize = vec4.normalize;
+
+/**
+ * Creates a quaternion from the given 3x3 rotation matrix.
+ *
+ * NOTE: The resultant quaternion is not normalized, so you should be sure
+ * to renormalize the quaternion yourself where necessary.
+ *
+ * @param {quat} out the receiving quaternion
+ * @param {mat3} m rotation matrix
+ * @returns {quat} out
+ * @function
+ */
+quat.fromMat3 = function(out, m) {
+    // Algorithm in Ken Shoemake's article in 1987 SIGGRAPH course notes
+    // article "Quaternion Calculus and Fast Animation".
+    var fTrace = m[0] + m[4] + m[8];
+    var fRoot;
+
+    if ( fTrace > 0.0 ) {
+        // |w| > 1/2, may as well choose w > 1/2
+        fRoot = Math.sqrt(fTrace + 1.0);  // 2w
+        out[3] = 0.5 * fRoot;
+        fRoot = 0.5/fRoot;  // 1/(4w)
+        out[0] = (m[7]-m[5])*fRoot;
+        out[1] = (m[2]-m[6])*fRoot;
+        out[2] = (m[3]-m[1])*fRoot;
+    } else {
+        // |w| <= 1/2
+        var i = 0;
+        if ( m[4] > m[0] )
+          i = 1;
+        if ( m[8] > m[i*3+i] )
+          i = 2;
+        var j = (i+1)%3;
+        var k = (i+2)%3;
+        
+        fRoot = Math.sqrt(m[i*3+i]-m[j*3+j]-m[k*3+k] + 1.0);
+        out[i] = 0.5 * fRoot;
+        fRoot = 0.5 / fRoot;
+        out[3] = (m[k*3+j] - m[j*3+k]) * fRoot;
+        out[j] = (m[j*3+i] + m[i*3+j]) * fRoot;
+        out[k] = (m[k*3+i] + m[i*3+k]) * fRoot;
+    }
+    
+    return out;
+};
+
+/**
+ * Returns a string representation of a quatenion
+ *
+ * @param {quat} vec vector to represent as a string
+ * @returns {String} string representation of the vector
+ */
+quat.str = function (a) {
+    return 'quat(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + a[3] + ')';
+};
+
+if(typeof(exports) !== 'undefined') {
+    exports.quat = quat;
+}
+;
+
+
+
+
+
+
+
+
+
+
+
+
+
+  })(shim.exports);
+})(this);
+
+},{}],24:[function(require,module,exports){
+//     Underscore.js 1.4.4
+//     http://underscorejs.org
+//     (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
+//     Underscore may be freely distributed under the MIT license.
+
+(function() {
+
+  // Baseline setup
+  // --------------
+
+  // Establish the root object, `window` in the browser, or `global` on the server.
+  var root = this;
+
+  // Save the previous value of the `_` variable.
+  var previousUnderscore = root._;
+
+  // Establish the object that gets returned to break out of a loop iteration.
+  var breaker = {};
+
+  // Save bytes in the minified (but not gzipped) version:
+  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
+
+  // Create quick reference variables for speed access to core prototypes.
+  var push             = ArrayProto.push,
+      slice            = ArrayProto.slice,
+      concat           = ArrayProto.concat,
+      toString         = ObjProto.toString,
+      hasOwnProperty   = ObjProto.hasOwnProperty;
+
+  // All **ECMAScript 5** native function implementations that we hope to use
+  // are declared here.
+  var
+    nativeForEach      = ArrayProto.forEach,
+    nativeMap          = ArrayProto.map,
+    nativeReduce       = ArrayProto.reduce,
+    nativeReduceRight  = ArrayProto.reduceRight,
+    nativeFilter       = ArrayProto.filter,
+    nativeEvery        = ArrayProto.every,
+    nativeSome         = ArrayProto.some,
+    nativeIndexOf      = ArrayProto.indexOf,
+    nativeLastIndexOf  = ArrayProto.lastIndexOf,
+    nativeIsArray      = Array.isArray,
+    nativeKeys         = Object.keys,
+    nativeBind         = FuncProto.bind;
+
+  // Create a safe reference to the Underscore object for use below.
+  var _ = function(obj) {
+    if (obj instanceof _) return obj;
+    if (!(this instanceof _)) return new _(obj);
+    this._wrapped = obj;
+  };
+
+  // Export the Underscore object for **Node.js**, with
+  // backwards-compatibility for the old `require()` API. If we're in
+  // the browser, add `_` as a global object via a string identifier,
+  // for Closure Compiler "advanced" mode.
+  if (typeof exports !== 'undefined') {
+    if (typeof module !== 'undefined' && module.exports) {
+      exports = module.exports = _;
+    }
+    exports._ = _;
+  } else {
+    root._ = _;
+  }
+
+  // Current version.
+  _.VERSION = '1.4.4';
+
+  // Collection Functions
+  // --------------------
+
+  // The cornerstone, an `each` implementation, aka `forEach`.
+  // Handles objects with the built-in `forEach`, arrays, and raw objects.
+  // Delegates to **ECMAScript 5**'s native `forEach` if available.
+  var each = _.each = _.forEach = function(obj, iterator, context) {
+    if (obj == null) return;
+    if (nativeForEach && obj.forEach === nativeForEach) {
+      obj.forEach(iterator, context);
+    } else if (obj.length === +obj.length) {
+      for (var i = 0, l = obj.length; i < l; i++) {
+        if (iterator.call(context, obj[i], i, obj) === breaker) return;
+      }
+    } else {
+      for (var key in obj) {
+        if (_.has(obj, key)) {
+          if (iterator.call(context, obj[key], key, obj) === breaker) return;
+        }
+      }
+    }
+  };
+
+  // Return the results of applying the iterator to each element.
+  // Delegates to **ECMAScript 5**'s native `map` if available.
+  _.map = _.collect = function(obj, iterator, context) {
+    var results = [];
+    if (obj == null) return results;
+    if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
+    each(obj, function(value, index, list) {
+      results[results.length] = iterator.call(context, value, index, list);
+    });
+    return results;
+  };
+
+  var reduceError = 'Reduce of empty array with no initial value';
+
+  // **Reduce** builds up a single result from a list of values, aka `inject`,
+  // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
+  _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
+    var initial = arguments.length > 2;
+    if (obj == null) obj = [];
+    if (nativeReduce && obj.reduce === nativeReduce) {
+      if (context) iterator = _.bind(iterator, context);
+      return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
+    }
+    each(obj, function(value, index, list) {
+      if (!initial) {
+        memo = value;
+        initial = true;
+      } else {
+        memo = iterator.call(context, memo, value, index, list);
+      }
+    });
+    if (!initial) throw new TypeError(reduceError);
+    return memo;
+  };
+
+  // The right-associative version of reduce, also known as `foldr`.
+  // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
+  _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
+    var initial = arguments.length > 2;
+    if (obj == null) obj = [];
+    if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
+      if (context) iterator = _.bind(iterator, context);
+      return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
+    }
+    var length = obj.length;
+    if (length !== +length) {
+      var keys = _.keys(obj);
+      length = keys.length;
+    }
+    each(obj, function(value, index, list) {
+      index = keys ? keys[--length] : --length;
+      if (!initial) {
+        memo = obj[index];
+        initial = true;
+      } else {
+        memo = iterator.call(context, memo, obj[index], index, list);
+      }
+    });
+    if (!initial) throw new TypeError(reduceError);
+    return memo;
+  };
+
+  // Return the first value which passes a truth test. Aliased as `detect`.
+  _.find = _.detect = function(obj, iterator, context) {
+    var result;
+    any(obj, function(value, index, list) {
+      if (iterator.call(context, value, index, list)) {
+        result = value;
+        return true;
+      }
+    });
+    return result;
+  };
+
+  // Return all the elements that pass a truth test.
+  // Delegates to **ECMAScript 5**'s native `filter` if available.
+  // Aliased as `select`.
+  _.filter = _.select = function(obj, iterator, context) {
+    var results = [];
+    if (obj == null) return results;
+    if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
+    each(obj, function(value, index, list) {
+      if (iterator.call(context, value, index, list)) results[results.length] = value;
+    });
+    return results;
+  };
+
+  // Return all the elements for which a truth test fails.
+  _.reject = function(obj, iterator, context) {
+    return _.filter(obj, function(value, index, list) {
+      return !iterator.call(context, value, index, list);
+    }, context);
+  };
+
+  // Determine whether all of the elements match a truth test.
+  // Delegates to **ECMAScript 5**'s native `every` if available.
+  // Aliased as `all`.
+  _.every = _.all = function(obj, iterator, context) {
+    iterator || (iterator = _.identity);
+    var result = true;
+    if (obj == null) return result;
+    if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
+    each(obj, function(value, index, list) {
+      if (!(result = result && iterator.call(context, value, index, list))) return breaker;
+    });
+    return !!result;
+  };
+
+  // Determine if at least one element in the object matches a truth test.
+  // Delegates to **ECMAScript 5**'s native `some` if available.
+  // Aliased as `any`.
+  var any = _.some = _.any = function(obj, iterator, context) {
+    iterator || (iterator = _.identity);
+    var result = false;
+    if (obj == null) return result;
+    if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
+    each(obj, function(value, index, list) {
+      if (result || (result = iterator.call(context, value, index, list))) return breaker;
+    });
+    return !!result;
+  };
+
+  // Determine if the array or object contains a given value (using `===`).
+  // Aliased as `include`.
+  _.contains = _.include = function(obj, target) {
+    if (obj == null) return false;
+    if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
+    return any(obj, function(value) {
+      return value === target;
+    });
+  };
+
+  // Invoke a method (with arguments) on every item in a collection.
+  _.invoke = function(obj, method) {
+    var args = slice.call(arguments, 2);
+    var isFunc = _.isFunction(method);
+    return _.map(obj, function(value) {
+      return (isFunc ? method : value[method]).apply(value, args);
+    });
+  };
+
+  // Convenience version of a common use case of `map`: fetching a property.
+  _.pluck = function(obj, key) {
+    return _.map(obj, function(value){ return value[key]; });
+  };
+
+  // Convenience version of a common use case of `filter`: selecting only objects
+  // containing specific `key:value` pairs.
+  _.where = function(obj, attrs, first) {
+    if (_.isEmpty(attrs)) return first ? null : [];
+    return _[first ? 'find' : 'filter'](obj, function(value) {
+      for (var key in attrs) {
+        if (attrs[key] !== value[key]) return false;
+      }
+      return true;
+    });
+  };
+
+  // Convenience version of a common use case of `find`: getting the first object
+  // containing specific `key:value` pairs.
+  _.findWhere = function(obj, attrs) {
+    return _.where(obj, attrs, true);
+  };
+
+  // Return the maximum element or (element-based computation).
+  // Can't optimize arrays of integers longer than 65,535 elements.
+  // See: https://bugs.webkit.org/show_bug.cgi?id=80797
+  _.max = function(obj, iterator, context) {
+    if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
+      return Math.max.apply(Math, obj);
+    }
+    if (!iterator && _.isEmpty(obj)) return -Infinity;
+    var result = {computed : -Infinity, value: -Infinity};
+    each(obj, function(value, index, list) {
+      var computed = iterator ? iterator.call(context, value, index, list) : value;
+      computed >= result.computed && (result = {value : value, computed : computed});
+    });
+    return result.value;
+  };
+
+  // Return the minimum element (or element-based computation).
+  _.min = function(obj, iterator, context) {
+    if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
+      return Math.min.apply(Math, obj);
+    }
+    if (!iterator && _.isEmpty(obj)) return Infinity;
+    var result = {computed : Infinity, value: Infinity};
+    each(obj, function(value, index, list) {
+      var computed = iterator ? iterator.call(context, value, index, list) : value;
+      computed < result.computed && (result = {value : value, computed : computed});
+    });
+    return result.value;
+  };
+
+  // Shuffle an array.
+  _.shuffle = function(obj) {
+    var rand;
+    var index = 0;
+    var shuffled = [];
+    each(obj, function(value) {
+      rand = _.random(index++);
+      shuffled[index - 1] = shuffled[rand];
+      shuffled[rand] = value;
+    });
+    return shuffled;
+  };
+
+  // An internal function to generate lookup iterators.
+  var lookupIterator = function(value) {
+    return _.isFunction(value) ? value : function(obj){ return obj[value]; };
+  };
+
+  // Sort the object's values by a criterion produced by an iterator.
+  _.sortBy = function(obj, value, context) {
+    var iterator = lookupIterator(value);
+    return _.pluck(_.map(obj, function(value, index, list) {
+      return {
+        value : value,
+        index : index,
+        criteria : iterator.call(context, value, index, list)
+      };
+    }).sort(function(left, right) {
+      var a = left.criteria;
+      var b = right.criteria;
+      if (a !== b) {
+        if (a > b || a === void 0) return 1;
+        if (a < b || b === void 0) return -1;
+      }
+      return left.index < right.index ? -1 : 1;
+    }), 'value');
+  };
+
+  // An internal function used for aggregate "group by" operations.
+  var group = function(obj, value, context, behavior) {
+    var result = {};
+    var iterator = lookupIterator(value || _.identity);
+    each(obj, function(value, index) {
+      var key = iterator.call(context, value, index, obj);
+      behavior(result, key, value);
+    });
+    return result;
+  };
+
+  // Groups the object's values by a criterion. Pass either a string attribute
+  // to group by, or a function that returns the criterion.
+  _.groupBy = function(obj, value, context) {
+    return group(obj, value, context, function(result, key, value) {
+      (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
+    });
+  };
+
+  // Counts instances of an object that group by a certain criterion. Pass
+  // either a string attribute to count by, or a function that returns the
+  // criterion.
+  _.countBy = function(obj, value, context) {
+    return group(obj, value, context, function(result, key) {
+      if (!_.has(result, key)) result[key] = 0;
+      result[key]++;
+    });
+  };
+
+  // Use a comparator function to figure out the smallest index at which
+  // an object should be inserted so as to maintain order. Uses binary search.
+  _.sortedIndex = function(array, obj, iterator, context) {
+    iterator = iterator == null ? _.identity : lookupIterator(iterator);
+    var value = iterator.call(context, obj);
+    var low = 0, high = array.length;
+    while (low < high) {
+      var mid = (low + high) >>> 1;
+      iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
+    }
+    return low;
+  };
+
+  // Safely convert anything iterable into a real, live array.
+  _.toArray = function(obj) {
+    if (!obj) return [];
+    if (_.isArray(obj)) return slice.call(obj);
+    if (obj.length === +obj.length) return _.map(obj, _.identity);
+    return _.values(obj);
+  };
+
+  // Return the number of elements in an object.
+  _.size = function(obj) {
+    if (obj == null) return 0;
+    return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
+  };
+
+  // Array Functions
+  // ---------------
+
+  // Get the first element of an array. Passing **n** will return the first N
+  // values in the array. Aliased as `head` and `take`. The **guard** check
+  // allows it to work with `_.map`.
+  _.first = _.head = _.take = function(array, n, guard) {
+    if (array == null) return void 0;
+    return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
+  };
+
+  // Returns everything but the last entry of the array. Especially useful on
+  // the arguments object. Passing **n** will return all the values in
+  // the array, excluding the last N. The **guard** check allows it to work with
+  // `_.map`.
+  _.initial = function(array, n, guard) {
+    return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
+  };
+
+  // Get the last element of an array. Passing **n** will return the last N
+  // values in the array. The **guard** check allows it to work with `_.map`.
+  _.last = function(array, n, guard) {
+    if (array == null) return void 0;
+    if ((n != null) && !guard) {
+      return slice.call(array, Math.max(array.length - n, 0));
+    } else {
+      return array[array.length - 1];
+    }
+  };
+
+  // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
+  // Especially useful on the arguments object. Passing an **n** will return
+  // the rest N values in the array. The **guard**
+  // check allows it to work with `_.map`.
+  _.rest = _.tail = _.drop = function(array, n, guard) {
+    return slice.call(array, (n == null) || guard ? 1 : n);
+  };
+
+  // Trim out all falsy values from an array.
+  _.compact = function(array) {
+    return _.filter(array, _.identity);
+  };
+
+  // Internal implementation of a recursive `flatten` function.
+  var flatten = function(input, shallow, output) {
+    each(input, function(value) {
+      if (_.isArray(value)) {
+        shallow ? push.apply(output, value) : flatten(value, shallow, output);
+      } else {
+        output.push(value);
+      }
+    });
+    return output;
+  };
+
+  // Return a completely flattened version of an array.
+  _.flatten = function(array, shallow) {
+    return flatten(array, shallow, []);
+  };
+
+  // Return a version of the array that does not contain the specified value(s).
+  _.without = function(array) {
+    return _.difference(array, slice.call(arguments, 1));
+  };
+
+  // Produce a duplicate-free version of the array. If the array has already
+  // been sorted, you have the option of using a faster algorithm.
+  // Aliased as `unique`.
+  _.uniq = _.unique = function(array, isSorted, iterator, context) {
+    if (_.isFunction(isSorted)) {
+      context = iterator;
+      iterator = isSorted;
+      isSorted = false;
+    }
+    var initial = iterator ? _.map(array, iterator, context) : array;
+    var results = [];
+    var seen = [];
+    each(initial, function(value, index) {
+      if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
+        seen.push(value);
+        results.push(array[index]);
+      }
+    });
+    return results;
+  };
+
+  // Produce an array that contains the union: each distinct element from all of
+  // the passed-in arrays.
+  _.union = function() {
+    return _.uniq(concat.apply(ArrayProto, arguments));
+  };
+
+  // Produce an array that contains every item shared between all the
+  // passed-in arrays.
+  _.intersection = function(array) {
+    var rest = slice.call(arguments, 1);
+    return _.filter(_.uniq(array), function(item) {
+      return _.every(rest, function(other) {
+        return _.indexOf(other, item) >= 0;
+      });
+    });
+  };
+
+  // Take the difference between one array and a number of other arrays.
+  // Only the elements present in just the first array will remain.
+  _.difference = function(array) {
+    var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
+    return _.filter(array, function(value){ return !_.contains(rest, value); });
+  };
+
+  // Zip together multiple lists into a single array -- elements that share
+  // an index go together.
+  _.zip = function() {
+    var args = slice.call(arguments);
+    var length = _.max(_.pluck(args, 'length'));
+    var results = new Array(length);
+    for (var i = 0; i < length; i++) {
+      results[i] = _.pluck(args, "" + i);
+    }
+    return results;
+  };
+
+  // Converts lists into objects. Pass either a single array of `[key, value]`
+  // pairs, or two parallel arrays of the same length -- one of keys, and one of
+  // the corresponding values.
+  _.object = function(list, values) {
+    if (list == null) return {};
+    var result = {};
+    for (var i = 0, l = list.length; i < l; i++) {
+      if (values) {
+        result[list[i]] = values[i];
+      } else {
+        result[list[i][0]] = list[i][1];
+      }
+    }
+    return result;
+  };
+
+  // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
+  // we need this function. Return the position of the first occurrence of an
+  // item in an array, or -1 if the item is not included in the array.
+  // Delegates to **ECMAScript 5**'s native `indexOf` if available.
+  // If the array is large and already in sort order, pass `true`
+  // for **isSorted** to use binary search.
+  _.indexOf = function(array, item, isSorted) {
+    if (array == null) return -1;
+    var i = 0, l = array.length;
+    if (isSorted) {
+      if (typeof isSorted == 'number') {
+        i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
+      } else {
+        i = _.sortedIndex(array, item);
+        return array[i] === item ? i : -1;
+      }
+    }
+    if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
+    for (; i < l; i++) if (array[i] === item) return i;
+    return -1;
+  };
+
+  // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
+  _.lastIndexOf = function(array, item, from) {
+    if (array == null) return -1;
+    var hasIndex = from != null;
+    if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
+      return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
+    }
+    var i = (hasIndex ? from : array.length);
+    while (i--) if (array[i] === item) return i;
+    return -1;
+  };
+
+  // Generate an integer Array containing an arithmetic progression. A port of
+  // the native Python `range()` function. See
+  // [the Python documentation](http://docs.python.org/library/functions.html#range).
+  _.range = function(start, stop, step) {
+    if (arguments.length <= 1) {
+      stop = start || 0;
+      start = 0;
+    }
+    step = arguments[2] || 1;
+
+    var len = Math.max(Math.ceil((stop - start) / step), 0);
+    var idx = 0;
+    var range = new Array(len);
+
+    while(idx < len) {
+      range[idx++] = start;
+      start += step;
+    }
+
+    return range;
+  };
+
+  // Function (ahem) Functions
+  // ------------------
+
+  // Create a function bound to a given object (assigning `this`, and arguments,
+  // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
+  // available.
+  _.bind = function(func, context) {
+    if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
+    var args = slice.call(arguments, 2);
+    return function() {
+      return func.apply(context, args.concat(slice.call(arguments)));
+    };
+  };
+
+  // Partially apply a function by creating a version that has had some of its
+  // arguments pre-filled, without changing its dynamic `this` context.
+  _.partial = function(func) {
+    var args = slice.call(arguments, 1);
+    return function() {
+      return func.apply(this, args.concat(slice.call(arguments)));
+    };
+  };
+
+  // Bind all of an object's methods to that object. Useful for ensuring that
+  // all callbacks defined on an object belong to it.
+  _.bindAll = function(obj) {
+    var funcs = slice.call(arguments, 1);
+    if (funcs.length === 0) funcs = _.functions(obj);
+    each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
+    return obj;
+  };
+
+  // Memoize an expensive function by storing its results.
+  _.memoize = function(func, hasher) {
+    var memo = {};
+    hasher || (hasher = _.identity);
+    return function() {
+      var key = hasher.apply(this, arguments);
+      return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
+    };
+  };
+
+  // Delays a function for the given number of milliseconds, and then calls
+  // it with the arguments supplied.
+  _.delay = function(func, wait) {
+    var args = slice.call(arguments, 2);
+    return setTimeout(function(){ return func.apply(null, args); }, wait);
+  };
+
+  // Defers a function, scheduling it to run after the current call stack has
+  // cleared.
+  _.defer = function(func) {
+    return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
+  };
+
+  // Returns a function, that, when invoked, will only be triggered at most once
+  // during a given window of time.
+  _.throttle = function(func, wait) {
+    var context, args, timeout, result;
+    var previous = 0;
+    var later = function() {
+      previous = new Date;
+      timeout = null;
+      result = func.apply(context, args);
+    };
+    return function() {
+      var now = new Date;
+      var remaining = wait - (now - previous);
+      context = this;
+      args = arguments;
+      if (remaining <= 0) {
+        clearTimeout(timeout);
+        timeout = null;
+        previous = now;
+        result = func.apply(context, args);
+      } else if (!timeout) {
+        timeout = setTimeout(later, remaining);
+      }
+      return result;
+    };
+  };
+
+  // Returns a function, that, as long as it continues to be invoked, will not
+  // be triggered. The function will be called after it stops being called for
+  // N milliseconds. If `immediate` is passed, trigger the function on the
+  // leading edge, instead of the trailing.
+  _.debounce = function(func, wait, immediate) {
+    var timeout, result;
+    return function() {
+      var context = this, args = arguments;
+      var later = function() {
+        timeout = null;
+        if (!immediate) result = func.apply(context, args);
+      };
+      var callNow = immediate && !timeout;
+      clearTimeout(timeout);
+      timeout = setTimeout(later, wait);
+      if (callNow) result = func.apply(context, args);
+      return result;
+    };
+  };
+
+  // Returns a function that will be executed at most one time, no matter how
+  // often you call it. Useful for lazy initialization.
+  _.once = function(func) {
+    var ran = false, memo;
+    return function() {
+      if (ran) return memo;
+      ran = true;
+      memo = func.apply(this, arguments);
+      func = null;
+      return memo;
+    };
+  };
+
+  // Returns the first function passed as an argument to the second,
+  // allowing you to adjust arguments, run code before and after, and
+  // conditionally execute the original function.
+  _.wrap = function(func, wrapper) {
+    return function() {
+      var args = [func];
+      push.apply(args, arguments);
+      return wrapper.apply(this, args);
+    };
+  };
+
+  // Returns a function that is the composition of a list of functions, each
+  // consuming the return value of the function that follows.
+  _.compose = function() {
+    var funcs = arguments;
+    return function() {
+      var args = arguments;
+      for (var i = funcs.length - 1; i >= 0; i--) {
+        args = [funcs[i].apply(this, args)];
+      }
+      return args[0];
+    };
+  };
+
+  // Returns a function that will only be executed after being called N times.
+  _.after = function(times, func) {
+    if (times <= 0) return func();
+    return function() {
+      if (--times < 1) {
+        return func.apply(this, arguments);
+      }
+    };
+  };
+
+  // Object Functions
+  // ----------------
+
+  // Retrieve the names of an object's properties.
+  // Delegates to **ECMAScript 5**'s native `Object.keys`
+  _.keys = nativeKeys || function(obj) {
+    if (obj !== Object(obj)) throw new TypeError('Invalid object');
+    var keys = [];
+    for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
+    return keys;
+  };
+
+  // Retrieve the values of an object's properties.
+  _.values = function(obj) {
+    var values = [];
+    for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
+    return values;
+  };
+
+  // Convert an object into a list of `[key, value]` pairs.
+  _.pairs = function(obj) {
+    var pairs = [];
+    for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
+    return pairs;
+  };
+
+  // Invert the keys and values of an object. The values must be serializable.
+  _.invert = function(obj) {
+    var result = {};
+    for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
+    return result;
+  };
+
+  // Return a sorted list of the function names available on the object.
+  // Aliased as `methods`
+  _.functions = _.methods = function(obj) {
+    var names = [];
+    for (var key in obj) {
+      if (_.isFunction(obj[key])) names.push(key);
+    }
+    return names.sort();
+  };
+
+  // Extend a given object with all the properties in passed-in object(s).
+  _.extend = function(obj) {
+    each(slice.call(arguments, 1), function(source) {
+      if (source) {
+        for (var prop in source) {
+          obj[prop] = source[prop];
+        }
+      }
+    });
+    return obj;
+  };
+
+  // Return a copy of the object only containing the whitelisted properties.
+  _.pick = function(obj) {
+    var copy = {};
+    var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
+    each(keys, function(key) {
+      if (key in obj) copy[key] = obj[key];
+    });
+    return copy;
+  };
+
+   // Return a copy of the object without the blacklisted properties.
+  _.omit = function(obj) {
+    var copy = {};
+    var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
+    for (var key in obj) {
+      if (!_.contains(keys, key)) copy[key] = obj[key];
+    }
+    return copy;
+  };
+
+  // Fill in a given object with default properties.
+  _.defaults = function(obj) {
+    each(slice.call(arguments, 1), function(source) {
+      if (source) {
+        for (var prop in source) {
+          if (obj[prop] == null) obj[prop] = source[prop];
+        }
+      }
+    });
+    return obj;
+  };
+
+  // Create a (shallow-cloned) duplicate of an object.
+  _.clone = function(obj) {
+    if (!_.isObject(obj)) return obj;
+    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
+  };
+
+  // Invokes interceptor with the obj, and then returns obj.
+  // The primary purpose of this method is to "tap into" a method chain, in
+  // order to perform operations on intermediate results within the chain.
+  _.tap = function(obj, interceptor) {
+    interceptor(obj);
+    return obj;
+  };
+
+  // Internal recursive comparison function for `isEqual`.
+  var eq = function(a, b, aStack, bStack) {
+    // Identical objects are equal. `0 === -0`, but they aren't identical.
+    // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
+    if (a === b) return a !== 0 || 1 / a == 1 / b;
+    // A strict comparison is necessary because `null == undefined`.
+    if (a == null || b == null) return a === b;
+    // Unwrap any wrapped objects.
+    if (a instanceof _) a = a._wrapped;
+    if (b instanceof _) b = b._wrapped;
+    // Compare `[[Class]]` names.
+    var className = toString.call(a);
+    if (className != toString.call(b)) return false;
+    switch (className) {
+      // Strings, numbers, dates, and booleans are compared by value.
+      case '[object String]':
+        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+        // equivalent to `new String("5")`.
+        return a == String(b);
+      case '[object Number]':
+        // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
+        // other numeric values.
+        return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
+      case '[object Date]':
+      case '[object Boolean]':
+        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+        // millisecond representations. Note that invalid dates with millisecond representations
+        // of `NaN` are not equivalent.
+        return +a == +b;
+      // RegExps are compared by their source patterns and flags.
+      case '[object RegExp]':
+        return a.source == b.source &&
+               a.global == b.global &&
+               a.multiline == b.multiline &&
+               a.ignoreCase == b.ignoreCase;
+    }
+    if (typeof a != 'object' || typeof b != 'object') return false;
+    // Assume equality for cyclic structures. The algorithm for detecting cyclic
+    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+    var length = aStack.length;
+    while (length--) {
+      // Linear search. Performance is inversely proportional to the number of
+      // unique nested structures.
+      if (aStack[length] == a) return bStack[length] == b;
+    }
+    // Add the first object to the stack of traversed objects.
+    aStack.push(a);
+    bStack.push(b);
+    var size = 0, result = true;
+    // Recursively compare objects and arrays.
+    if (className == '[object Array]') {
+      // Compare array lengths to determine if a deep comparison is necessary.
+      size = a.length;
+      result = size == b.length;
+      if (result) {
+        // Deep compare the contents, ignoring non-numeric properties.
+        while (size--) {
+          if (!(result = eq(a[size], b[size], aStack, bStack))) break;
+        }
+      }
+    } else {
+      // Objects with different constructors are not equivalent, but `Object`s
+      // from different frames are.
+      var aCtor = a.constructor, bCtor = b.constructor;
+      if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
+                               _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
+        return false;
+      }
+      // Deep compare objects.
+      for (var key in a) {
+        if (_.has(a, key)) {
+          // Count the expected number of properties.
+          size++;
+          // Deep compare each member.
+          if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
+        }
+      }
+      // Ensure that both objects contain the same number of properties.
+      if (result) {
+        for (key in b) {
+          if (_.has(b, key) && !(size--)) break;
+        }
+        result = !size;
+      }
+    }
+    // Remove the first object from the stack of traversed objects.
+    aStack.pop();
+    bStack.pop();
+    return result;
+  };
+
+  // Perform a deep comparison to check if two objects are equal.
+  _.isEqual = function(a, b) {
+    return eq(a, b, [], []);
+  };
+
+  // Is a given array, string, or object empty?
+  // An "empty" object has no enumerable own-properties.
+  _.isEmpty = function(obj) {
+    if (obj == null) return true;
+    if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
+    for (var key in obj) if (_.has(obj, key)) return false;
+    return true;
+  };
+
+  // Is a given value a DOM element?
+  _.isElement = function(obj) {
+    return !!(obj && obj.nodeType === 1);
+  };
+
+  // Is a given value an array?
+  // Delegates to ECMA5's native Array.isArray
+  _.isArray = nativeIsArray || function(obj) {
+    return toString.call(obj) == '[object Array]';
+  };
+
+  // Is a given variable an object?
+  _.isObject = function(obj) {
+    return obj === Object(obj);
+  };
+
+  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
+  each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
+    _['is' + name] = function(obj) {
+      return toString.call(obj) == '[object ' + name + ']';
+    };
+  });
+
+  // Define a fallback version of the method in browsers (ahem, IE), where
+  // there isn't any inspectable "Arguments" type.
+  if (!_.isArguments(arguments)) {
+    _.isArguments = function(obj) {
+      return !!(obj && _.has(obj, 'callee'));
+    };
+  }
+
+  // Optimize `isFunction` if appropriate.
+  if (typeof (/./) !== 'function') {
+    _.isFunction = function(obj) {
+      return typeof obj === 'function';
+    };
+  }
+
+  // Is a given object a finite number?
+  _.isFinite = function(obj) {
+    return isFinite(obj) && !isNaN(parseFloat(obj));
+  };
+
+  // Is the given value `NaN`? (NaN is the only number which does not equal itself).
+  _.isNaN = function(obj) {
+    return _.isNumber(obj) && obj != +obj;
+  };
+
+  // Is a given value a boolean?
+  _.isBoolean = function(obj) {
+    return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
+  };
+
+  // Is a given value equal to null?
+  _.isNull = function(obj) {
+    return obj === null;
+  };
+
+  // Is a given variable undefined?
+  _.isUndefined = function(obj) {
+    return obj === void 0;
+  };
+
+  // Shortcut function for checking if an object has a given property directly
+  // on itself (in other words, not on a prototype).
+  _.has = function(obj, key) {
+    return hasOwnProperty.call(obj, key);
+  };
+
+  // Utility Functions
+  // -----------------
+
+  // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
+  // previous owner. Returns a reference to the Underscore object.
+  _.noConflict = function() {
+    root._ = previousUnderscore;
+    return this;
+  };
+
+  // Keep the identity function around for default iterators.
+  _.identity = function(value) {
+    return value;
+  };
+
+  // Run a function **n** times.
+  _.times = function(n, iterator, context) {
+    var accum = Array(n);
+    for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
+    return accum;
+  };
+
+  // Return a random integer between min and max (inclusive).
+  _.random = function(min, max) {
+    if (max == null) {
+      max = min;
+      min = 0;
+    }
+    return min + Math.floor(Math.random() * (max - min + 1));
+  };
+
+  // List of HTML entities for escaping.
+  var entityMap = {
+    escape: {
+      '&': '&amp;',
+      '<': '&lt;',
+      '>': '&gt;',
+      '"': '&quot;',
+      "'": '&#x27;',
+      '/': '&#x2F;'
+    }
+  };
+  entityMap.unescape = _.invert(entityMap.escape);
+
+  // Regexes containing the keys and values listed immediately above.
+  var entityRegexes = {
+    escape:   new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
+    unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
+  };
+
+  // Functions for escaping and unescaping strings to/from HTML interpolation.
+  _.each(['escape', 'unescape'], function(method) {
+    _[method] = function(string) {
+      if (string == null) return '';
+      return ('' + string).replace(entityRegexes[method], function(match) {
+        return entityMap[method][match];
+      });
+    };
+  });
+
+  // If the value of the named property is a function then invoke it;
+  // otherwise, return it.
+  _.result = function(object, property) {
+    if (object == null) return null;
+    var value = object[property];
+    return _.isFunction(value) ? value.call(object) : value;
+  };
+
+  // Add your own custom functions to the Underscore object.
+  _.mixin = function(obj) {
+    each(_.functions(obj), function(name){
+      var func = _[name] = obj[name];
+      _.prototype[name] = function() {
+        var args = [this._wrapped];
+        push.apply(args, arguments);
+        return result.call(this, func.apply(_, args));
+      };
+    });
+  };
+
+  // Generate a unique integer id (unique within the entire client session).
+  // Useful for temporary DOM ids.
+  var idCounter = 0;
+  _.uniqueId = function(prefix) {
+    var id = ++idCounter + '';
+    return prefix ? prefix + id : id;
+  };
+
+  // By default, Underscore uses ERB-style template delimiters, change the
+  // following template settings to use alternative delimiters.
+  _.templateSettings = {
+    evaluate    : /<%([\s\S]+?)%>/g,
+    interpolate : /<%=([\s\S]+?)%>/g,
+    escape      : /<%-([\s\S]+?)%>/g
+  };
+
+  // When customizing `templateSettings`, if you don't want to define an
+  // interpolation, evaluation or escaping regex, we need one that is
+  // guaranteed not to match.
+  var noMatch = /(.)^/;
+
+  // Certain characters need to be escaped so that they can be put into a
+  // string literal.
+  var escapes = {
+    "'":      "'",
+    '\\':     '\\',
+    '\r':     'r',
+    '\n':     'n',
+    '\t':     't',
+    '\u2028': 'u2028',
+    '\u2029': 'u2029'
+  };
+
+  var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
+
+  // JavaScript micro-templating, similar to John Resig's implementation.
+  // Underscore templating handles arbitrary delimiters, preserves whitespace,
+  // and correctly escapes quotes within interpolated code.
+  _.template = function(text, data, settings) {
+    var render;
+    settings = _.defaults({}, settings, _.templateSettings);
+
+    // Combine delimiters into one regular expression via alternation.
+    var matcher = new RegExp([
+      (settings.escape || noMatch).source,
+      (settings.interpolate || noMatch).source,
+      (settings.evaluate || noMatch).source
+    ].join('|') + '|$', 'g');
+
+    // Compile the template source, escaping string literals appropriately.
+    var index = 0;
+    var source = "__p+='";
+    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
+      source += text.slice(index, offset)
+        .replace(escaper, function(match) { return '\\' + escapes[match]; });
+
+      if (escape) {
+        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
+      }
+      if (interpolate) {
+        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+      }
+      if (evaluate) {
+        source += "';\n" + evaluate + "\n__p+='";
+      }
+      index = offset + match.length;
+      return match;
+    });
+    source += "';\n";
+
+    // If a variable is not specified, place data values in local scope.
+    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
+
+    source = "var __t,__p='',__j=Array.prototype.join," +
+      "print=function(){__p+=__j.call(arguments,'');};\n" +
+      source + "return __p;\n";
+
+    try {
+      render = new Function(settings.variable || 'obj', '_', source);
+    } catch (e) {
+      e.source = source;
+      throw e;
+    }
+
+    if (data) return render(data, _);
+    var template = function(data) {
+      return render.call(this, data, _);
+    };
+
+    // Provide the compiled function source as a convenience for precompilation.
+    template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
+
+    return template;
+  };
+
+  // Add a "chain" function, which will delegate to the wrapper.
+  _.chain = function(obj) {
+    return _(obj).chain();
+  };
+
+  // OOP
+  // ---------------
+  // If Underscore is called as a function, it returns a wrapped object that
+  // can be used OO-style. This wrapper holds altered versions of all the
+  // underscore functions. Wrapped objects may be chained.
+
+  // Helper function to continue chaining intermediate results.
+  var result = function(obj) {
+    return this._chain ? _(obj).chain() : obj;
+  };
+
+  // Add all of the Underscore functions to the wrapper object.
+  _.mixin(_);
+
+  // Add all mutator Array functions to the wrapper.
+  each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+    var method = ArrayProto[name];
+    _.prototype[name] = function() {
+      var obj = this._wrapped;
+      method.apply(obj, arguments);
+      if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
+      return result.call(this, obj);
+    };
+  });
+
+  // Add all accessor Array functions to the wrapper.
+  each(['concat', 'join', 'slice'], function(name) {
+    var method = ArrayProto[name];
+    _.prototype[name] = function() {
+      return result.call(this, method.apply(this._wrapped, arguments));
+    };
+  });
+
+  _.extend(_.prototype, {
+
+    // Start chaining a wrapped Underscore object.
+    chain: function() {
+      this._chain = true;
+      return this;
+    },
+
+    // Extracts the result from a wrapped and chained object.
+    value: function() {
+      return this._wrapped;
+    }
+
+  });
+
+}).call(this);
+
+},{}],25:[function(require,module,exports){
+if (typeof(window) !== 'undefined' && typeof(window.requestAnimationFrame) !== 'function') {
+  window.requestAnimationFrame = (
+    window.webkitRequestAnimationFrame   ||
+    window.mozRequestAnimationFrame      ||
+    window.oRequestAnimationFrame        ||
+    window.msRequestAnimationFrame       ||
+    function(callback) { setTimeout(callback, 1000 / 60); }
+  );
+}
+
+Leap = require("../lib/index");
+
+},{"../lib/index":11}]},{},[25])
+;
\ No newline at end of file