Initial import.
authorJoe Wreschnig <joe.wreschnig@gmail.com>
Mon, 22 Sep 2014 00:00:36 +0000 (02:00 +0200)
committerJoe Wreschnig <joe.wreschnig@gmail.com>
Mon, 22 Sep 2014 00:00:36 +0000 (02:00 +0200)
61 files changed:
.gitattributes [new file with mode: 0644]
.gitignore [new file with mode: 0644]
.projectile [new file with mode: 0644]
Makefile [new file with mode: 0644]
README.md [new file with mode: 0644]
rules/git.mk [new file with mode: 0644]
rules/icons.mk [new file with mode: 0644]
rules/javascript.mk [new file with mode: 0644]
rules/node-webkit.mk [new file with mode: 0644]
rules/pngcrush.mk [new file with mode: 0644]
rules/programs.mk [new file with mode: 0644]
src/data/images/face.png [new file with mode: 0644]
src/data/images/logotype_horizontal_1.png [new file with mode: 0644]
src/data/images/logotype_horizontal_2.png [new file with mode: 0644]
src/data/images/text.png [new file with mode: 0644]
src/data/license.txt [new file with mode: 0644]
src/ext/FiraMono-Bold.woff [new file with mode: 0644]
src/ext/FiraMono-Regular.woff [new file with mode: 0644]
src/ext/FiraSans-Bold.woff [new file with mode: 0644]
src/ext/FiraSans-BoldItalic.woff [new file with mode: 0644]
src/ext/FiraSans-Italic.woff [new file with mode: 0644]
src/ext/FiraSans-Regular.woff [new file with mode: 0644]
src/ext/FiraSans-UltraLight.woff [new file with mode: 0644]
src/ext/FiraSans-UltraLightItalic.woff [new file with mode: 0644]
src/ext/font-awesome.woff [new file with mode: 0644]
src/ext/gamepad.js [new file with mode: 0644]
src/ext/gl-matrix.js [new file with mode: 0644]
src/ext/hammer.js [new file with mode: 0644]
src/index.html [new file with mode: 0644]
src/main.css [new file with mode: 0644]
src/main.js [new file with mode: 0644]
src/yuu/audio.js [new file with mode: 0644]
src/yuu/ce.js [new file with mode: 0644]
src/yuu/core.js [new file with mode: 0644]
src/yuu/data/license.txt [new file with mode: 0644]
src/yuu/data/shaders/color.frag [new file with mode: 0644]
src/yuu/data/shaders/default.frag [new file with mode: 0644]
src/yuu/data/shaders/default.vert [new file with mode: 0644]
src/yuu/data/yuu.css [new file with mode: 0644]
src/yuu/director.js [new file with mode: 0644]
src/yuu/gfx.js [new file with mode: 0644]
src/yuu/input.js [new file with mode: 0644]
src/yuu/pre.js [new file with mode: 0644]
src/yuu/rdr.js [new file with mode: 0644]
src/yuu/storage.js [new file with mode: 0644]
src/yuu/yT.js [new file with mode: 0644]
src/yuu/yf.js [new file with mode: 0644]
test/jshint.config [new file with mode: 0644]
test/spec/yuu/core.js [new file with mode: 0644]
test/spec/yuu/input.js [new file with mode: 0644]
test/spec/yuu/storage.js [new file with mode: 0644]
test/spec/yuu/yT.js [new file with mode: 0644]
test/spec/yuu/yf.js [new file with mode: 0644]
tools/LICENSE.rcedit [new file with mode: 0644]
tools/composer/composer.js [new file with mode: 0644]
tools/composer/index.html [new file with mode: 0644]
tools/generate-appcache [new file with mode: 0755]
tools/generate-nw [new file with mode: 0755]
tools/generate-osx-app [new file with mode: 0755]
tools/nw-linux-wrapper [new file with mode: 0755]
tools/rcedit.exe [new file with mode: 0644]

diff --git a/.gitattributes b/.gitattributes
new file mode 100644 (file)
index 0000000..5966153
--- /dev/null
@@ -0,0 +1,2 @@
+.gitattributes export-ignore
+.gitignore export-ignore
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..17918bb
--- /dev/null
@@ -0,0 +1,7 @@
+*~
+\#*
+.DS_Store
+node_modules
+build
+node-webkit
+
diff --git a/.projectile b/.projectile
new file mode 100644 (file)
index 0000000..7865c8b
--- /dev/null
@@ -0,0 +1,2 @@
+-/node_modules
+-/node-webkit
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..ab7e9e6
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,101 @@
+#!/usr/bin/make -f
+
+node-webkit-version := 0.10.4
+
+.DELETE_ON_ERROR:
+include rules/programs.mk
+include rules/javascript.mk
+include rules/icons.mk
+include rules/git.mk
+include rules/node-webkit.mk
+include rules/pngcrush.mk
+
+.PHONY: all check distclean lint test dist clean serve
+
+APPLICATION := yuu
+DISTDIR := build/dist
+VERSION := $(call git-describe)
+DISTROOT := $(DISTDIR)/$(APPLICATION)_$(VERSION)
+
+FIND_FILES := -type f ! -name '.*'
+FIND_JS := -type f -name '*.js'
+
+SOURCES := $(shell cd src && find . $(FIND_FILES))
+MY_SOURCES := $(shell cd src && find . $(FIND_JS) -not -path './ext/*')
+
+TEST_SOURCES := $(shell find test/spec $(FIND_JS))
+
+LINT_TARGETS := $(call jshint-stampify,$(MY_SOURCES))
+TEST_TARGETS := $(call jstest-stampify,$(TEST_SOURCES))
+
+JSHINTCONFIG := test/jshint.config
+JSTEST_NODE_PATH := src
+VPATH := src
+
+ICONSETS := $(shell find src -type d -name '*.iconset')
+ICONS := $(ICONSETS:.iconset=.icns) $(ICONSETS:.iconset=.ico)
+
+IMAGEGZSRC := $(shell find src -type f -name '*.xcf.gz')
+IMAGESRC := $(shell find src -type f -name '*.xcf')
+IMAGES := $(IMAGEGZSRC:.xcf.gz=.png) $(IMAGESRC:.xcf=.png)
+
+BUILT := $(ICONS) $(IMAGES)
+
+HTTP_SERVER_PORT ?= 8000
+
+all: check $(BUILT) $(call pngcrush-stampify,$(IMAGES))
+
+$(DISTDIR):
+       mkdir -p $@
+
+dist: $(addprefix $(DISTROOT),-src.zip -src.tar.gz .appcache .nw -osx-ia32.zip -osx-x64.zip -linux-ia32.tar.gz -linux-x64.tar.gz -win-ia32.zip)
+
+test/spec/%.js: %.js
+       touch $@
+
+lint: $(LINT_TARGETS)
+
+test: $(TEST_TARGETS)
+
+check: lint test
+
+serve: | $(npmbindir)/http-server
+       $(npmbindir)/http-server $(@D) -p $(HTTP_SERVER_PORT) -c-1
+
+clean:
+       $(RM) $(IMAGES)
+       $(RM) $(ICONS)
+       $(RM) -r build
+
+distclean: clean
+       $(RM) -r node_modules
+       $(RM) $(node-webkit-archives)
+
+$(DISTROOT)-src.zip $(DISTROOT)-src.tar.gz: | .git
+       mkdir -p $(@D)
+       $(call git-archive,$@,$(notdir $(DISTROOT))/)
+
+$(DISTROOT).bare.zip: | .git
+       $(RM) $@
+       $(RM) -r $@.tmp
+       mkdir -p $@.tmp
+       cd src && $(GIT) archive $(call git-describe) . | tar -x -C ../$@.tmp
+       $(MAKE) $(BUILT:src/%=$@.tmp/%)
+       $(RM) $(IMAGESRC:src/%=$@.tmp/%) $(IMAGEGZSRC:src/%=$@.tmp/%)
+       cd $@.tmp && $(ZIP) ../$(@F) -r .
+       $(RM) -r $@.tmp
+
+%.appcache: %.bare.zip tools/generate-appcache
+       $(RM) -r $@.tmp $@
+       $(UNZIP) -d $@.tmp $<
+       tools/generate-appcache $@.tmp
+       mv $@.tmp $@
+
+# Python's zipfile module generates zipfiles that node-webkit cannot
+# read, so delegate to a real zip tool.
+%.nw: %.bare.zip tools/generate-nw
+       $(RM) -r $@ $@.tmp
+       $(UNZIP) -d $@.tmp $<
+       tools/generate-nw $@.tmp `echo $(*F) | sed -E 's/.+_[^0-9]+//'`
+       cd $@.tmp && $(ZIP) -r ../$(@F) .
+       $(RM) -r $@.tmp
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..a265ea9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,6 @@
+# Yuu Game Library
+
+This is the library used for several of our games at
+[Yukkuri Games](https://yukkurigames.com).
+
+It is released under the terms of the GNU GPL version 2 or later.
diff --git a/rules/git.mk b/rules/git.mk
new file mode 100644 (file)
index 0000000..e8ceb78
--- /dev/null
@@ -0,0 +1,13 @@
+# This is free and unencumbered software released into the public
+# domain. To the extent possible under law, the author of this file
+# waives all copyright and related or neighboring rights to it.
+
+GIT ?= git
+git-describe = $(shell $(GIT) describe --tags --always $1)
+
+define git-archive
+$(GIT) archive --output '$1' $(if $2,--prefix '$2') '$(call git-describe,$3)'
+endef
+
+.git:
+       $(error "This target must be run inside a git repository.")
diff --git a/rules/icons.mk b/rules/icons.mk
new file mode 100644 (file)
index 0000000..eba5e89
--- /dev/null
@@ -0,0 +1,39 @@
+# This is free and unencumbered software released into the public
+# domain. To the extent possible under law, the author of this file
+# waives all copyright and related or neighboring rights to it.
+
+.DELETE_ON_ERROR:
+
+XCF2PNG ?= xcf2png
+
+%.png: %.xcf
+       $(XCF2PNG) $< > $@
+
+# First: xcf2png by default calls zcat rather than gzcat. This is
+# totally always broken; zcat forces a .Z extension on its input
+# filename. So we can't rely on xcf2png's default behavior. But it
+# offers -Z for a custom decompression program.
+#
+# BUT: Gimp produces gz files with some padding zeros because, I don't
+# know, someone might want to save their compressed xcfs to DECtape.
+# gzip has a -q option to not *print* the warning associated with this
+# harmless thing, but then goes ahead and exits non-zero anyway, which
+# makes xcf2png barf and die before writing anything even though it
+# got perfectly good data.
+#
+# So: 1) use gunzip, 2) manually feed it to xcf2png, 3) hope nothing is
+# set to die based on pipe status, 4) hope that if the xcf data is
+# actually busted xcf2png will do something helpful.
+%.png: %.xcf.gz
+       gunzip -c $< | $(XCF2PNG) - > $@
+
+.SECONDEXPANSION:
+
+ICONUTIL ?= $(firstword $(shell command -v iconutil icnsutil) iconutil)
+
+%.icns: %.iconset $$(wildcard $$(@D)/$$*.iconset/icon_*.png)
+       $(ICONUTIL) -c icns -o $@ $<
+
+%.ico: %.iconset $$(wildcard $$(@D)/$$*.iconset/icon_*[0-9].png)
+       convert -background transparent -colors 256 $(filter-out $<,$^) $@
+
diff --git a/rules/javascript.mk b/rules/javascript.mk
new file mode 100644 (file)
index 0000000..15fb8a5
--- /dev/null
@@ -0,0 +1,62 @@
+# This is free and unencumbered software released into the public
+# domain. To the extent possible under law, the author of this file
+# waives all copyright and related or neighboring rights to it.
+
+.DELETE_ON_ERROR:
+
+javascript>fallback = $(firstword $(shell command -v $1) $2 $1)
+
+NPM ?= npm
+NPMROOT ?= $(CURDIR)
+
+npmbindir = $(NPMROOT)/node_modules/.bin
+.PRECIOUS: $(npmbindir)/%
+
+npmbin = $(call javascript>fallback,$1,$(npmbindir)/$1)
+
+JSTEST ?= $(npmbindir)/jstest
+JSHINT ?= $(call npmbin,jshint)
+
+uglifyjs_npm_package := uglify-js
+
+UGLIFY ?= $(call npmbin,uglifyjs)
+UGLIFYFLAGS ?= --comments \
+               --compress $(UGLIFYCOMPRESSFLAGS) \
+               --mangle $(UGLIFYMANGLEFLAGS)
+
+BUILDDIR ?= build/
+JSSTAMPDIR ?= $(BUILDDIR)/stamp
+JSHINTDIR ?= $(JSSTAMPDIR)
+JSTESTDIR ?= $(JSSTAMPDIR)
+JSUGLYDIR ?= $(CURDIR)
+
+JSHINTFLAGS += $(if $(JSHINTCONFIG),--config $(JSHINTCONFIG))
+JSTESTFLAGS += $(if $(JSTESTFORMAT),--format $(JSTESTFORMAT))
+JSTESTENV += $(if $(JSTEST_NODE_PATH),NODE_PATH=$(JSTEST_NODE_PATH))
+JSTESTFORMAT ?= spec
+
+jshint-stampify = $(patsubst %.js,$(JSHINTDIR)/%.js.lint,$1)
+jstest-stampify = $(patsubst %.js,$(JSTESTDIR)/%.js.test,$1)
+uglify-stampify = $(patsubst %.js,$(JSUGLYDIR)/%.min.js,$1)
+
+javascript>capture-to-target = @echo "$1" && $1 > $@ || (cat $@ && exit 1)
+
+UGLIFY.js = $(UGLIFY) $(UGLIFYFLAGS)
+LINT.js = $(JSHINT) $(JSHINTFLAGS)
+TEST.js = $(JSTESTENV) $(JSTEST) $(JSTESTFLAGS)
+
+$(JSUGLYDIR)/%.min.js: %.js | $(UGLIFY)
+       mkdir -p $(@D)
+       $(UGLIFY.js) < $< > $@
+
+$(JSHINTDIR)/%.js.lint: %.js | $(JSHINT)
+       mkdir -p $(@D)
+       $(LINT.js) $<
+       touch $@
+
+$(JSTESTDIR)/%.js.test: %.js | $(JSTEST)
+       mkdir -p $(@D)
+       $(call javascript>capture-to-target,$(TEST.js) $<)
+
+$(npmbindir)/%:
+       $(NPM) install $(firstword $(value $(@F)_npm_package) $(@F))
diff --git a/rules/node-webkit.mk b/rules/node-webkit.mk
new file mode 100644 (file)
index 0000000..dd46e17
--- /dev/null
@@ -0,0 +1,99 @@
+# This is free and unencumbered software released into the public
+# domain. To the extent possible under law, the author of this file
+# waives all copyright and related or neighboring rights to it.
+
+.DELETE_ON_ERROR:
+
+include $(dir $(realpath $(lastword $(MAKEFILE_LIST))))programs.mk
+
+node-webkit-platforms := \
+       osx-x64.zip osx-ia32.zip \
+       linux-x64.tar.gz linux-ia32.tar.gz \
+       win-ia32.zip
+
+node-webkit-pattern := \
+       $(addprefix node-webkit-v%-,$(node-webkit-platforms))
+
+# These are like 30MB, don't download them every time.
+.PRECIOUS: $(node-webkit-pattern)
+
+$(node-webkit-pattern):
+       mkdir -p $(@D)
+       wget -O $@ http://dl.node-webkit.org/v$(*F)/$(@F) || ($(RM) $@ && exit 1)
+
+node-webkit-version ?= 0.10.2
+node-webkit-prefix ?= node-webkit/
+node-webkit-directx = $(addprefix $(node-webkit-prefix),d3dcompiler_43.dll d3dcompiler_46.dll)
+
+node-webkit = $(node-webkit-prefix)node-webkit-v$(firstword $(value node-webkit-version-$1) $(node-webkit-version))-$1
+
+node-webkit-archives = \
+       $(foreach p,$(node-webkit-platforms),$(call node-webkit,$(p)))
+
+define node-webkit-package-osx
+       $(RM) $@
+       $(RM) -r $(@:.zip=)
+       $(UNZIP) -d $(@D) $2
+       mv $(@D)/$(notdir $(2:.zip=)) $(@:.zip=)
+       tools/generate-osx-app $(@:.zip=) $1
+       $(RM) $(@:.zip=)/nwsnapshot
+       mv $(@:.zip=)/credits.html $(@:.zip=)/node-webkit\ credits.html
+       cd $(@D) && $(ZIP) -r $(@F) $(@F:.zip=)
+       $(RM) -r $(@:.zip=)
+endef
+
+%-osx-ia32.zip: %.nw $(call node-webkit,osx-ia32.zip)
+       $(call node-webkit-package-osx,$<,$(word 2,$^))
+
+%-osx-x64.zip: %.nw $(call node-webkit,osx-x64.zip)
+       $(call node-webkit-package-osx,$<,$(word 2,$^))
+
+define node-webkit-package-linux
+       $(RM) $@
+       $(RM) -r $(@:.tar.gz=)
+       tar -C $(@D) -xzf $2
+       mkdir -p $(@:.tar.gz=)
+       mv $(@D)/$(notdir $(2:.tar.gz=)) $(@:.tar.gz=)/nw
+       cp -a $1 $(@:.tar.gz=)/nw/package.nw
+       cp -a tools/nw-linux-wrapper $(@:.tar.gz=)/`echo $(notdir $1) | sed -E 's/_.+//'`
+       $(RM) $(@:.tar.gz=)/nw/nwsnapshot
+       mv $(@:.tar.gz=)/nw/credits.html $(@:.tar.gz=)/nw/node-webkit\ credits.html
+       tar -czf $@ -C $(@D) $(@F:.tar.gz=)
+       $(RM) -r $(@:.tar.gz=)
+endef
+
+%-linux-ia32.tar.gz: %.nw $(call node-webkit,linux-ia32.tar.gz)
+       $(call node-webkit-package-linux,$<,$(word 2,$^))
+
+%-linux-x64.tar.gz: %.nw $(call node-webkit,linux-x64.tar.gz)
+       $(call node-webkit-package-linux,$<,$(word 2,$^))
+
+WINE ?= wine
+
+node-webkit-icon = $(shell $(UNZIP) -p $1 package.json | grep -Eo '"[^"]+.ico"' -m 1)
+
+define node-webkit-package-win
+       $(RM) $@
+       $(RM) -r $(@:.zip=)
+       if $(UNZIP) -l $2 credits.html > /dev/null; then $(UNZIP) -d $(@D)/$(notdir $(2:.zip=)) $2; else $(UNZIP) -d $(@D) $2; fi
+       mv $(@D)/$(notdir $(2:.zip=)) $(@:.zip=)
+       $(RM) $(@:.zip=)/nwsnapshot.exe
+       $(UNZIP) -p $< $(call node-webkit-icon,$<) > $(@D)/icon.ico
+       $(WINE) tools/rcedit.exe $(@:.zip=)/nw.exe --set-icon $(@D)/icon.ico
+       $(RM) $(@D)/icon.ico
+       mv $(@:.zip=)/credits.html $(@:.zip=)/node-webkit\ credits.html
+       cp -a $< $(@:.zip=)/package.nw
+       cp -a $(node-webkit-directx) $(@:.zip=)
+       mv $(@:.zip=)/nw.exe $(@:.zip=)/`echo $(notdir $1) | sed -E 's/_.+/.exe/'`
+       cd $(@D) && $(ZIP) -r $(@F) $(@F:.zip=)
+       $(RM) -r $(@:.zip=)
+endef
+
+%-win-ia32.zip: %.nw $(call node-webkit,win-ia32.zip) $(node-webkit-directx)
+       $(call node-webkit-package-win,$<,$(word 2,$^))
+
+$(node-webkit-directx):
+       wget -O $@ 'https://github.com/cefsharp/cef-binary/raw/1e51255cf77d267899bf7834768b8774affaad2d/cef_binary_3.y.z_windows32/Release/'$(notdir $@)
+
+$(node-webkit-prefix)dxwebsetup.exe:
+       wget -O $@ http://download.microsoft.com/download/1/7/1/1718CCC4-6315-4D8E-9543-8E28A4E18C4C/dxwebsetup.exe
diff --git a/rules/pngcrush.mk b/rules/pngcrush.mk
new file mode 100644 (file)
index 0000000..39630df
--- /dev/null
@@ -0,0 +1,20 @@
+# This is free and unencumbered software released into the public
+# domain. To the extent possible under law, the author of this file
+# waives all copyright and related or neighboring rights to it.
+
+.DELETE_ON_ERROR:
+
+BUILDDIR ?= build/
+PNGCRUSHSTAMPDIR ?= $(BUILDDIR)/stamp
+
+pngcrush-stampify = $(patsubst %.png,$(PNGCRUSHSTAMPDIR)/%.png.crushed,$1)
+
+PNGCRUSH ?= pngcrush
+PNGCRUSHFLAGS ?= -brute -blacken -reduce -q
+
+CRUSH.png ?= $(PNGCRUSH) $(PNGCRUSHFLAGS)
+
+$(PNGCRUSHSTAMPDIR)/%.png.crushed: %.png
+       $(CRUSH.png) -ow $<
+       mkdir -p $(@D)
+       touch $@
diff --git a/rules/programs.mk b/rules/programs.mk
new file mode 100644 (file)
index 0000000..d706d3f
--- /dev/null
@@ -0,0 +1,13 @@
+# This is free and unencumbered software released into the public
+# domain. To the extent possible under law, the author of this file
+# waives all copyright and related or neighboring rights to it.
+
+ZIPFLAGS ?= -q
+UNZIPFLAGS ?= -q
+
+UNZIP = unzip $(UNZIPFLAGS)
+ZIP = zip $(ZIPFLAGS)
+
+ifneq ($(OS),Windows_NT)
+       WINE ?= wine
+endif
diff --git a/src/data/images/face.png b/src/data/images/face.png
new file mode 100644 (file)
index 0000000..1918897
Binary files /dev/null and b/src/data/images/face.png differ
diff --git a/src/data/images/logotype_horizontal_1.png b/src/data/images/logotype_horizontal_1.png
new file mode 100644 (file)
index 0000000..a845d24
Binary files /dev/null and b/src/data/images/logotype_horizontal_1.png differ
diff --git a/src/data/images/logotype_horizontal_2.png b/src/data/images/logotype_horizontal_2.png
new file mode 100644 (file)
index 0000000..7ae1d09
Binary files /dev/null and b/src/data/images/logotype_horizontal_2.png differ
diff --git a/src/data/images/text.png b/src/data/images/text.png
new file mode 100644 (file)
index 0000000..4982347
Binary files /dev/null and b/src/data/images/text.png differ
diff --git a/src/data/license.txt b/src/data/license.txt
new file mode 100644 (file)
index 0000000..fdd9313
--- /dev/null
@@ -0,0 +1,15 @@
+This demonstration project, aside from the Yukkuri Games logo, is
+released into the public domain.
+
+The person who associated a work with this deed has dedicated the work
+to the public domain by waiving all of his or her rights to the work
+worldwide under copyright law, including all related and neighboring
+rights, to the extent allowed by law.
+
+You can copy, modify, distribute and perform the work, even for
+commercial purposes, all without asking permission.
+
+See https://creativecommons.org/publicdomain/zero/1.0/ for details.
+
+The source code under src/yuu remains under the terms of the GNU GPL
+versions 2 or later, unless specified otherwise in the files.
diff --git a/src/ext/FiraMono-Bold.woff b/src/ext/FiraMono-Bold.woff
new file mode 100644 (file)
index 0000000..0155c48
Binary files /dev/null and b/src/ext/FiraMono-Bold.woff differ
diff --git a/src/ext/FiraMono-Regular.woff b/src/ext/FiraMono-Regular.woff
new file mode 100644 (file)
index 0000000..29db168
Binary files /dev/null and b/src/ext/FiraMono-Regular.woff differ
diff --git a/src/ext/FiraSans-Bold.woff b/src/ext/FiraSans-Bold.woff
new file mode 100644 (file)
index 0000000..404f7cf
Binary files /dev/null and b/src/ext/FiraSans-Bold.woff differ
diff --git a/src/ext/FiraSans-BoldItalic.woff b/src/ext/FiraSans-BoldItalic.woff
new file mode 100644 (file)
index 0000000..d919d01
Binary files /dev/null and b/src/ext/FiraSans-BoldItalic.woff differ
diff --git a/src/ext/FiraSans-Italic.woff b/src/ext/FiraSans-Italic.woff
new file mode 100644 (file)
index 0000000..1ef173c
Binary files /dev/null and b/src/ext/FiraSans-Italic.woff differ
diff --git a/src/ext/FiraSans-Regular.woff b/src/ext/FiraSans-Regular.woff
new file mode 100644 (file)
index 0000000..7fc569b
Binary files /dev/null and b/src/ext/FiraSans-Regular.woff differ
diff --git a/src/ext/FiraSans-UltraLight.woff b/src/ext/FiraSans-UltraLight.woff
new file mode 100644 (file)
index 0000000..0d00757
Binary files /dev/null and b/src/ext/FiraSans-UltraLight.woff differ
diff --git a/src/ext/FiraSans-UltraLightItalic.woff b/src/ext/FiraSans-UltraLightItalic.woff
new file mode 100644 (file)
index 0000000..5a001cb
Binary files /dev/null and b/src/ext/FiraSans-UltraLightItalic.woff differ
diff --git a/src/ext/font-awesome.woff b/src/ext/font-awesome.woff
new file mode 100644 (file)
index 0000000..9eaecb3
Binary files /dev/null and b/src/ext/font-awesome.woff differ
diff --git a/src/ext/gamepad.js b/src/ext/gamepad.js
new file mode 100644 (file)
index 0000000..62e8856
--- /dev/null
@@ -0,0 +1,172 @@
+(function () {
+    "use strict";
+    /** DOM Event shim for Web Gamepad API
+        https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html
+
+        This adds three new custom events to the window object:
+
+        yuugamepadbuttondown, yuugamepadbuttonup
+            Dispatched when a button on a gamepad is pressed
+            or released.
+        
+            .detail.gamepad - the Gamepad instance with the button
+            .detail.button - the numeric index of the button
+            .detail.mapped - true if the button is known to fit the
+                             "standard" mapping, even if gamepad.mapping
+                             says otherwise
+
+        yuugamepadaxismove
+            Dispatched when an axis on a gamepad changes.
+
+            .detail.gamepad - the Gamepad instance with the axis
+            .detail.axis - the numeric index of the axis
+            .detail.value - the value of the axis
+            .detail.mapped - true if the axis is known to fit the
+                             "standard" mapping, even if gamepad.mapping
+                             says otherwise
+
+        It also polyfills navigator.getGamepads.
+
+        Including this file (e.g. in a <script> tag) is all that's
+        necessary to enable these.
+
+        Because the underlying API is poll-based, events will be
+        dispatched at most at the animation refresh rate. Inputs that
+        exist for less than this time may be dropped.
+     */
+
+    function empty () { return []; }
+
+    if (!navigator.getGamepads)
+        navigator.getGamepads = (
+            navigator.webkitGetGamepads
+            || navigator.mozGetGamepads
+            || empty);
+    if (navigator.getGamepads === empty)
+        return;
+
+    var requestFrame = window.requestAnimationFrame
+        || window.mozRequestAnimationFrame
+        || window.webkitRequestAnimationFrame;
+
+    var PLATFORM = (navigator.platform || "Unknown") + ".";
+
+    var MAPPINGS = {
+        // Maps the logical controller button/axis (the index) to the
+        // equivalent standard controller button/axis (the value),
+        // based on ID.
+        //
+        // This needs to be keyed to the underlying platform as well
+        // because mapping varies based on the USB stack in question.
+        // In theory this can go as far as multiple drivers for one OS
+        // with different mappings, but most platforms / devices have
+        // one canonical driver.
+        'MacIntel.54c-268-PLAYSTATION(R)3 Controller': {
+            buttons: [8, 10, 11, 9, 12, 15, 13, 14, 6, 7,
+                      4, 5, 3, 1, 0, 2, 16],
+            axes: [0, 1, 2, 3]
+        }
+    };
+
+    var states = {};
+
+    function isPressed (button) {
+        return isFinite(button) ? button > 0.1 : button.pressed;
+    }
+
+    function isAppropriateState (state, gamepad) {
+        return state
+            && state.id === gamepad.id
+            && state.buttons.length === gamepad.buttons.length
+            && state.axes.length === gamepad.axes.length;
+    }
+
+    function makeState (gamepad) {
+        var buttons = [];
+        for (var i = 0; i < gamepad.buttons.length; ++i)
+            buttons.push(isPressed(gamepad.buttons[i]));
+        return {
+            buttons: buttons,
+            axes: gamepad.axes.slice(),
+            id: gamepad.id
+        };
+    }
+
+    function getState (gamepad) {
+        var state = states[gamepad.index];
+        // Browsers that do not support the connected/disconnected
+        // events will silently swap in a new gamepad. Try to detect
+        // that and reset the state.
+        if (!isAppropriateState(state, gamepad))
+            state = states[gamepad.index] = makeState(gamepad);
+        return state;
+    }
+
+    function connected (event) {
+        states[event.gamepad.index] = makeState(event.gamepad);
+    }
+
+    function disconnected (event) {
+        delete states[event.gamepad.index];
+    }
+
+    function buttonEvent (gamepad, button, pressed) {
+        var mapping = MAPPINGS[PLATFORM + gamepad.id];
+        var mapped = gamepad.mapping === "standard" || !!mapping;
+        if (gamepad.mapping !== "standard" && mapping)
+            button = mapping.buttons[button];
+
+        return new CustomEvent(
+            pressed ? 'yuugamepadbuttondown' : 'yuugamepadbuttonup',
+            { detail: { gamepad: gamepad, mapped: mapped, button: button } });
+    }
+
+    function axisEvent (gamepad, axis, value) {
+        var mapping = MAPPINGS[PLATFORM + gamepad.id];
+        var mapped = gamepad.mapping === "standard" || !!mapping;
+        if (gamepad.mapping !== "standard" && mapping)
+            axis = mapping.axes[axis];
+
+        return new CustomEvent(
+            'yuugamepadaxismove',
+            { detail: { gamepad: gamepad, mapped: mapped,
+                        axis: axis, value: value } });
+    }
+
+    function pumpGamepad (gamepad) {
+        var state = getState(gamepad);
+        for (var b = 0; b < gamepad.buttons.length; ++b) {
+            var wasPressed = isPressed(state.buttons[b]);
+            var nowPressed = isPressed(gamepad.buttons[b]);
+            if (wasPressed !== nowPressed) {
+                state.buttons[b] = nowPressed;
+                window.dispatchEvent(buttonEvent(gamepad, b, nowPressed));
+            }
+        }
+        for (var a = 0; a < gamepad.axes.length; ++a) {
+            if (gamepad.axes[a] !== state.axes[a]) {
+                var value = state.axes[a] = gamepad.axes[a];
+                window.dispatchEvent(axisEvent(gamepad, a, value));
+            }
+        }
+    }
+
+    function pump () {
+        var gamepads = navigator.getGamepads();
+        for (var i = 0; i < gamepads.length; ++i) {
+            var gamepad = gamepads[i];
+            if (!gamepad)
+                continue;
+            pumpGamepad(gamepad);
+        }
+    }
+
+    requestFrame.call(window, function _ () {
+        pump();
+        requestFrame.call(window, _);
+    });
+
+    window.addEventListener('gamepadconnected', connected);
+    window.addEventListener('gamepaddisconnected', disconnected);
+
+})();
diff --git a/src/ext/gl-matrix.js b/src/ext/gl-matrix.js
new file mode 100644 (file)
index 0000000..3965a55
--- /dev/null
@@ -0,0 +1,4248 @@
+/**
+ * @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);
diff --git a/src/ext/hammer.js b/src/ext/hammer.js
new file mode 100644 (file)
index 0000000..053ae57
--- /dev/null
@@ -0,0 +1,1545 @@
+/*! Hammer.JS - v1.0.10 - 2014-03-28
+ * http://eightmedia.github.io/hammer.js
+ *
+ * Copyright (c) 2014 Jorik Tangelder <j.tangelder@gmail.com>;
+ * Licensed under the MIT license */
+
+(function(window, undefined) {
+  'use strict';
+
+/**
+ * Hammer
+ * use this to create instances
+ * @param   {HTMLElement}   element
+ * @param   {Object}        options
+ * @returns {Hammer.Instance}
+ * @constructor
+ */
+var Hammer = function(element, options) {
+  return new Hammer.Instance(element, options || {});
+};
+
+Hammer.VERSION = '1.0.10';
+
+// default settings
+Hammer.defaults = {
+  // add styles and attributes to the element to prevent the browser from doing
+  // its native behavior. this doesnt prevent the scrolling, but cancels
+  // the contextmenu, tap highlighting etc
+  // set to false to disable this
+  stop_browser_behavior: {
+    // this also triggers onselectstart=false for IE
+    userSelect       : 'none',
+    // this makes the element blocking in IE10>, you could experiment with the value
+    // see for more options this issue; https://github.com/EightMedia/hammer.js/issues/241
+    touchAction      : 'none',
+    touchCallout     : 'none',
+    contentZooming   : 'none',
+    userDrag         : 'none',
+    tapHighlightColor: 'rgba(0,0,0,0)'
+  }
+
+  //
+  // more settings are defined per gesture at /gestures
+  //
+};
+
+
+// detect touchevents
+Hammer.HAS_POINTEREVENTS = window.navigator.pointerEnabled || window.navigator.msPointerEnabled;
+Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window);
+
+// dont use mouseevents on mobile devices
+Hammer.MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android|silk/i;
+Hammer.NO_MOUSEEVENTS = Hammer.HAS_TOUCHEVENTS && window.navigator.userAgent.match(Hammer.MOBILE_REGEX);
+
+// eventtypes per touchevent (start, move, end)
+// are filled by Event.determineEventTypes on setup
+Hammer.EVENT_TYPES = {};
+
+// interval in which Hammer recalculates current velocity in ms
+Hammer.UPDATE_VELOCITY_INTERVAL = 16;
+
+// hammer document where the base events are added at
+Hammer.DOCUMENT = window.document;
+
+// define these also as vars, for better minification
+// direction defines
+var DIRECTION_DOWN = Hammer.DIRECTION_DOWN = 'down';
+var DIRECTION_LEFT = Hammer.DIRECTION_LEFT = 'left';
+var DIRECTION_UP = Hammer.DIRECTION_UP = 'up';
+var DIRECTION_RIGHT = Hammer.DIRECTION_RIGHT = 'right';
+
+// pointer type
+var POINTER_MOUSE = Hammer.POINTER_MOUSE = 'mouse';
+var POINTER_TOUCH = Hammer.POINTER_TOUCH = 'touch';
+var POINTER_PEN = Hammer.POINTER_PEN = 'pen';
+
+// touch event defines
+var EVENT_START = Hammer.EVENT_START = 'start';
+var EVENT_MOVE = Hammer.EVENT_MOVE = 'move';
+var EVENT_END = Hammer.EVENT_END = 'end';
+
+
+// plugins and gestures namespaces
+Hammer.plugins = Hammer.plugins || {};
+Hammer.gestures = Hammer.gestures || {};
+
+
+// if the window events are set...
+Hammer.READY = false;
+
+
+/**
+ * setup events to detect gestures on the document
+ */
+function setup() {
+  if(Hammer.READY) {
+    return;
+  }
+
+  // find what eventtypes we add listeners to
+  Event.determineEventTypes();
+
+  // Register all gestures inside Hammer.gestures
+  Utils.each(Hammer.gestures, function(gesture){
+    Detection.register(gesture);
+  });
+
+  // Add touch events on the document
+  Event.onTouch(Hammer.DOCUMENT, EVENT_MOVE, Detection.detect);
+  Event.onTouch(Hammer.DOCUMENT, EVENT_END, Detection.detect);
+
+  // Hammer is ready...!
+  Hammer.READY = true;
+}
+
+var Utils = Hammer.utils = {
+  /**
+   * extend method,
+   * also used for cloning when dest is an empty object
+   * @param   {Object}    dest
+   * @param   {Object}    src
+   * @parm  {Boolean}  merge    do a merge
+   * @returns {Object}    dest
+   */
+  extend: function extend(dest, src, merge) {
+    for(var key in src) {
+      if(dest[key] !== undefined && merge) {
+        continue;
+      }
+      dest[key] = src[key];
+    }
+    return dest;
+  },
+
+
+  /**
+   * for each
+   * @param obj
+   * @param iterator
+   */
+  each: function each(obj, iterator, context) {
+    var i, o;
+    // native forEach on arrays
+    if ('forEach' in obj) {
+      obj.forEach(iterator, context);
+    }
+    // arrays
+    else if(obj.length !== undefined) {
+      for(i=-1; (o=obj[++i]);) {
+        if (iterator.call(context, o, i, obj) === false) {
+          return;
+        }
+      }
+    }
+    // objects
+    else {
+      for(i in obj) {
+        if(obj.hasOwnProperty(i) &&
+            iterator.call(context, obj[i], i, obj) === false) {
+          return;
+        }
+      }
+    }
+  },
+
+
+  /**
+   * find if a string contains the needle
+   * @param   {String}  src
+   * @param   {String}  needle
+   * @returns {Boolean} found
+   */
+  inStr: function inStr(src, needle) {
+    return src.indexOf(needle) > -1;
+  },
+
+
+  /**
+   * find if a node is in the given parent
+   * used for event delegation tricks
+   * @param   {HTMLElement}   node
+   * @param   {HTMLElement}   parent
+   * @returns {boolean}       has_parent
+   */
+  hasParent: function hasParent(node, parent) {
+    while(node) {
+      if(node == parent) {
+        return true;
+      }
+      node = node.parentNode;
+    }
+    return false;
+  },
+
+
+  /**
+   * get the center of all the touches
+   * @param   {Array}     touches
+   * @returns {Object}    center pageXY clientXY
+   */
+  getCenter: function getCenter(touches) {
+    var pageX = []
+      , pageY = []
+      , clientX = []
+      , clientY = []
+      , min = Math.min
+      , max = Math.max;
+
+    // no need to loop when only one touch
+    if(touches.length === 1) {
+      return {
+        pageX: touches[0].pageX,
+        pageY: touches[0].pageY,
+        clientX: touches[0].clientX,
+        clientY: touches[0].clientY
+      };
+    }
+
+    Utils.each(touches, function(touch) {
+      pageX.push(touch.pageX);
+      pageY.push(touch.pageY);
+      clientX.push(touch.clientX);
+      clientY.push(touch.clientY);
+    });
+
+    return {
+      pageX: (min.apply(Math, pageX) + max.apply(Math, pageX)) / 2,
+      pageY: (min.apply(Math, pageY) + max.apply(Math, pageY)) / 2,
+      clientX: (min.apply(Math, clientX) + max.apply(Math, clientX)) / 2,
+      clientY: (min.apply(Math, clientY) + max.apply(Math, clientY)) / 2
+    };
+  },
+
+
+  /**
+   * calculate the velocity between two points
+   * @param   {Number}    delta_time
+   * @param   {Number}    delta_x
+   * @param   {Number}    delta_y
+   * @returns {Object}    velocity
+   */
+  getVelocity: function getVelocity(delta_time, delta_x, delta_y) {
+    return {
+      x: Math.abs(delta_x / delta_time) || 0,
+      y: Math.abs(delta_y / delta_time) || 0
+    };
+  },
+
+
+  /**
+   * calculate the angle between two coordinates
+   * @param   {Touch}     touch1
+   * @param   {Touch}     touch2
+   * @returns {Number}    angle
+   */
+  getAngle: function getAngle(touch1, touch2) {
+    var x = touch2.clientX - touch1.clientX
+      , y = touch2.clientY - touch1.clientY;
+    return Math.atan2(y, x) * 180 / Math.PI;
+  },
+
+
+  /**
+   * angle to direction define
+   * @param   {Touch}     touch1
+   * @param   {Touch}     touch2
+   * @returns {String}    direction constant, like DIRECTION_LEFT
+   */
+  getDirection: function getDirection(touch1, touch2) {
+    var x = Math.abs(touch1.clientX - touch2.clientX)
+      , y = Math.abs(touch1.clientY - touch2.clientY);
+    if(x >= y) {
+      return touch1.clientX - touch2.clientX > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
+    }
+    return touch1.clientY - touch2.clientY > 0 ? DIRECTION_UP : DIRECTION_DOWN;
+  },
+
+
+  /**
+   * calculate the distance between two touches
+   * @param   {Touch}     touch1
+   * @param   {Touch}     touch2
+   * @returns {Number}    distance
+   */
+  getDistance: function getDistance(touch1, touch2) {
+    var x = touch2.clientX - touch1.clientX
+      , y = touch2.clientY - touch1.clientY;
+    return Math.sqrt((x * x) + (y * y));
+  },
+
+
+  /**
+   * calculate the scale factor between two touchLists (fingers)
+   * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
+   * @param   {Array}     start
+   * @param   {Array}     end
+   * @returns {Number}    scale
+   */
+  getScale: function getScale(start, end) {
+    // need two fingers...
+    if(start.length >= 2 && end.length >= 2) {
+      return this.getDistance(end[0], end[1]) / this.getDistance(start[0], start[1]);
+    }
+    return 1;
+  },
+
+
+  /**
+   * calculate the rotation degrees between two touchLists (fingers)
+   * @param   {Array}     start
+   * @param   {Array}     end
+   * @returns {Number}    rotation
+   */
+  getRotation: function getRotation(start, end) {
+    // need two fingers
+    if(start.length >= 2 && end.length >= 2) {
+      return this.getAngle(end[1], end[0]) - this.getAngle(start[1], start[0]);
+    }
+    return 0;
+  },
+
+
+  /**
+   * boolean if the direction is vertical
+   * @param    {String}    direction
+   * @returns  {Boolean}   is_vertical
+   */
+  isVertical: function isVertical(direction) {
+    return direction == DIRECTION_UP || direction == DIRECTION_DOWN;
+  },
+
+
+  /**
+   * toggle browser default behavior with css props
+   * @param   {HtmlElement}   element
+   * @param   {Object}        css_props
+   * @param   {Boolean}       toggle
+   */
+  toggleDefaultBehavior: function toggleDefaultBehavior(element, css_props, toggle) {
+    if(!css_props || !element || !element.style) {
+      return;
+    }
+
+    // with css properties for modern browsers
+    Utils.each(['webkit', 'moz', 'Moz', 'ms', 'o', ''], function setStyle(vendor) {
+      Utils.each(css_props, function(value, prop) {
+          // vender prefix at the property
+          if(vendor) {
+            prop = vendor + prop.substring(0, 1).toUpperCase() + prop.substring(1);
+          }
+          // set the style
+          if(prop in element.style) {
+            element.style[prop] = !toggle && value;
+          }
+      });
+    });
+
+    var false_fn = function(){ return false; };
+
+    // also the disable onselectstart
+    if(css_props.userSelect == 'none') {
+      element.onselectstart = !toggle && false_fn;
+    }
+    // and disable ondragstart
+    if(css_props.userDrag == 'none') {
+      element.ondragstart = !toggle && false_fn;
+    }
+  }
+};
+
+
+/**
+ * create new hammer instance
+ * all methods should return the instance itself, so it is chainable.
+ * @param   {HTMLElement}       element
+ * @param   {Object}            [options={}]
+ * @returns {Hammer.Instance}
+ * @constructor
+ */
+Hammer.Instance = function(element, options) {
+  var self = this;
+
+  // setup HammerJS window events and register all gestures
+  // this also sets up the default options
+  setup();
+
+  this.element = element;
+
+  // start/stop detection option
+  this.enabled = true;
+
+  // merge options
+  this.options = Utils.extend(
+    Utils.extend({}, Hammer.defaults),
+    options || {});
+
+  // add some css to the element to prevent the browser from doing its native behavoir
+  if(this.options.stop_browser_behavior) {
+    Utils.toggleDefaultBehavior(this.element, this.options.stop_browser_behavior, false);
+  }
+
+  // start detection on touchstart
+  this.eventStartHandler = Event.onTouch(element, EVENT_START, function(ev) {
+    if(self.enabled) {
+      Detection.startDetect(self, ev);
+    }
+  });
+
+  // keep a list of user event handlers which needs to be removed when calling 'dispose'
+  this.eventHandlers = [];
+
+  // return instance
+  return this;
+};
+
+
+Hammer.Instance.prototype = {
+  /**
+   * bind events to the instance
+   * @param   {String}      gesture
+   * @param   {Function}    handler
+   * @returns {Hammer.Instance}
+   */
+  on: function onEvent(gesture, handler) {
+    var gestures = gesture.split(' ');
+    Utils.each(gestures, function(gesture) {
+      this.element.addEventListener(gesture, handler, false);
+      this.eventHandlers.push({ gesture: gesture, handler: handler });
+    }, this);
+    return this;
+  },
+
+
+  /**
+   * unbind events to the instance
+   * @param   {String}      gesture
+   * @param   {Function}    handler
+   * @returns {Hammer.Instance}
+   */
+  off: function offEvent(gesture, handler) {
+    var gestures = gesture.split(' ')
+      , i, eh;
+    Utils.each(gestures, function(gesture) {
+      this.element.removeEventListener(gesture, handler, false);
+
+      // remove the event handler from the internal list
+      for(i=-1; (eh=this.eventHandlers[++i]);) {
+        if(eh.gesture === gesture && eh.handler === handler) {
+          this.eventHandlers.splice(i, 1);
+        }
+      }
+    }, this);
+    return this;
+  },
+
+
+  /**
+   * trigger gesture event
+   * @param   {String}      gesture
+   * @param   {Object}      [eventData]
+   * @returns {Hammer.Instance}
+   */
+  trigger: function triggerEvent(gesture, eventData) {
+    // optional
+    if(!eventData) {
+      eventData = {};
+    }
+
+    // create DOM event
+    var event = Hammer.DOCUMENT.createEvent('Event');
+    event.initEvent(gesture, true, true);
+    event.gesture = eventData;
+
+    // trigger on the target if it is in the instance element,
+    // this is for event delegation tricks
+    var element = this.element;
+    if(Utils.hasParent(eventData.target, element)) {
+      element = eventData.target;
+    }
+
+    element.dispatchEvent(event);
+    return this;
+  },
+
+
+  /**
+   * enable of disable hammer.js detection
+   * @param   {Boolean}   state
+   * @returns {Hammer.Instance}
+   */
+  enable: function enable(state) {
+    this.enabled = state;
+    return this;
+  },
+
+
+  /**
+   * dispose this hammer instance
+   * @returns {Hammer.Instance}
+   */
+  dispose: function dispose() {
+    var i, eh;
+
+    // undo all changes made by stop_browser_behavior
+    if(this.options.stop_browser_behavior) {
+      Utils.toggleDefaultBehavior(this.element, this.options.stop_browser_behavior, true);
+    }
+
+    // unbind all custom event handlers
+    for(i=-1; (eh=this.eventHandlers[++i]);) {
+      this.element.removeEventListener(eh.gesture, eh.handler, false);
+    }
+    this.eventHandlers = [];
+
+    // unbind the start event listener
+    Event.unbindDom(this.element, Hammer.EVENT_TYPES[EVENT_START], this.eventStartHandler);
+
+    return null;
+  }
+};
+
+
+/**
+ * this holds the last move event,
+ * used to fix empty touchend issue
+ * see the onTouch event for an explanation
+ * @type {Object}
+ */
+var last_move_event = null;
+
+/**
+ * when the mouse is hold down, this is true
+ * @type {Boolean}
+ */
+var should_detect = false;
+
+/**
+ * when touch events have been fired, this is true
+ * @type {Boolean}
+ */
+var touch_triggered = false;
+
+
+var Event = Hammer.event = {
+  /**
+   * simple addEventListener
+   * @param   {HTMLElement}   element
+   * @param   {String}        type
+   * @param   {Function}      handler
+   */
+  bindDom: function(element, type, handler) {
+    var types = type.split(' ');
+    Utils.each(types, function(type){
+      element.addEventListener(type, handler, false);
+    });
+  },
+
+
+  /**
+   * simple removeEventListener
+   * @param   {HTMLElement}   element
+   * @param   {String}        type
+   * @param   {Function}      handler
+   */
+  unbindDom: function(element, type, handler) {
+    var types = type.split(' ');
+    Utils.each(types, function(type){
+      element.removeEventListener(type, handler, false);
+    });
+  },
+
+
+  /**
+   * touch events with mouse fallback
+   * @param   {HTMLElement}   element
+   * @param   {String}        eventType        like EVENT_MOVE
+   * @param   {Function}      handler
+   */
+  onTouch: function onTouch(element, eventType, handler) {
+    var self = this;
+
+
+    var bindDomOnTouch = function bindDomOnTouch(ev) {
+      var srcEventType = ev.type.toLowerCase();
+
+      // onmouseup, but when touchend has been fired we do nothing.
+      // this is for touchdevices which also fire a mouseup on touchend
+      if(Utils.inStr(srcEventType, 'mouse') && touch_triggered) {
+        return;
+      }
+
+      // mousebutton must be down or a touch event
+      else if(Utils.inStr(srcEventType, 'touch') ||   // touch events are always on screen
+        Utils.inStr(srcEventType, 'pointerdown') || // pointerevents touch
+        (Utils.inStr(srcEventType, 'mouse') && ev.which === 1)   // mouse is pressed
+        ) {
+        should_detect = true;
+      }
+
+      // mouse isn't pressed
+      else if(Utils.inStr(srcEventType, 'mouse') && !ev.which) {
+        should_detect = false;
+      }
+
+
+      // we are in a touch event, set the touch triggered bool to true,
+      // this for the conflicts that may occur on ios and android
+      if(Utils.inStr(srcEventType, 'touch') || Utils.inStr(srcEventType, 'pointer')) {
+        touch_triggered = true;
+      }
+
+      // count the total touches on the screen
+      var count_touches = 0;
+
+      // when touch has been triggered in this detection session
+      // and we are now handling a mouse event, we stop that to prevent conflicts
+      if(should_detect) {
+        // update pointerevent
+        if(Hammer.HAS_POINTEREVENTS && eventType != EVENT_END) {
+          count_touches = PointerEvent.updatePointer(eventType, ev);
+        }
+        // touch
+        else if(Utils.inStr(srcEventType, 'touch')) {
+          count_touches = ev.touches.length;
+        }
+        // mouse
+        else if(!touch_triggered) {
+          count_touches = Utils.inStr(srcEventType, 'up') ? 0 : 1;
+        }
+
+
+        // if we are in a end event, but when we remove one touch and
+        // we still have enough, set eventType to move
+        if(count_touches > 0 && eventType == EVENT_END) {
+          eventType = EVENT_MOVE;
+        }
+        // no touches, force the end event
+        else if(!count_touches) {
+          eventType = EVENT_END;
+        }
+
+        // store the last move event
+        if(count_touches || last_move_event === null) {
+          last_move_event = ev;
+        }
+
+
+        // trigger the handler
+        handler.call(Detection, self.collectEventData(element, eventType,
+                                  self.getTouchList(last_move_event, eventType),
+                                  ev) );
+
+        // remove pointerevent from list
+        if(Hammer.HAS_POINTEREVENTS && eventType == EVENT_END) {
+          count_touches = PointerEvent.updatePointer(eventType, ev);
+        }
+      }
+
+      // on the end we reset everything
+      if(!count_touches) {
+        last_move_event = null;
+        should_detect = false;
+        touch_triggered = false;
+        PointerEvent.reset();
+      }
+    };
+
+    this.bindDom(element, Hammer.EVENT_TYPES[eventType], bindDomOnTouch);
+
+    // return the bound function to be able to unbind it later
+    return bindDomOnTouch;
+  },
+
+
+  /**
+   * we have different events for each device/browser
+   * determine what we need and set them in the Hammer.EVENT_TYPES constant
+   */
+  determineEventTypes: function determineEventTypes() {
+    // determine the eventtype we want to set
+    var types;
+
+    // pointerEvents magic
+    if(Hammer.HAS_POINTEREVENTS) {
+      types = PointerEvent.getEvents();
+    }
+    // on Android, iOS, blackberry, windows mobile we dont want any mouseevents
+    else if(Hammer.NO_MOUSEEVENTS) {
+      types = [
+        'touchstart',
+        'touchmove',
+        'touchend touchcancel'];
+    }
+    // for non pointer events browsers and mixed browsers,
+    // like chrome on windows8 touch laptop
+    else {
+      types = [
+        'touchstart mousedown',
+        'touchmove mousemove',
+        'touchend touchcancel mouseup'];
+    }
+
+    Hammer.EVENT_TYPES[EVENT_START] = types[0];
+    Hammer.EVENT_TYPES[EVENT_MOVE] = types[1];
+    Hammer.EVENT_TYPES[EVENT_END] = types[2];
+  },
+
+
+  /**
+   * create touchlist depending on the event
+   * @param   {Object}    ev
+   * @param   {String}    eventType   used by the fakemultitouch plugin
+   */
+  getTouchList: function getTouchList(ev/*, eventType*/) {
+    // get the fake pointerEvent touchlist
+    if(Hammer.HAS_POINTEREVENTS) {
+      return PointerEvent.getTouchList();
+    }
+
+    // get the touchlist
+    if(ev.touches) {
+      return ev.touches;
+    }
+
+    // make fake touchlist from mouse position
+    ev.identifier = 1;
+    return [ev];
+  },
+
+
+  /**
+   * collect event data for Hammer js
+   * @param   {HTMLElement}   element
+   * @param   {String}        eventType        like EVENT_MOVE
+   * @param   {Object}        eventData
+   */
+  collectEventData: function collectEventData(element, eventType, touches, ev) {
+    // find out pointerType
+    var pointerType = POINTER_TOUCH;
+    if(Utils.inStr(ev.type, 'mouse') || PointerEvent.matchType(POINTER_MOUSE, ev)) {
+      pointerType = POINTER_MOUSE;
+    }
+
+    return {
+      center     : Utils.getCenter(touches),
+      timeStamp  : Date.now(),
+      target     : ev.target,
+      touches    : touches,
+      eventType  : eventType,
+      pointerType: pointerType,
+      srcEvent   : ev,
+
+      /**
+       * prevent the browser default actions
+       * mostly used to disable scrolling of the browser
+       */
+      preventDefault: function() {
+        var srcEvent = this.srcEvent;
+        srcEvent.preventManipulation && srcEvent.preventManipulation();
+        srcEvent.preventDefault && srcEvent.preventDefault();
+      },
+
+      /**
+       * stop bubbling the event up to its parents
+       */
+      stopPropagation: function() {
+        this.srcEvent.stopPropagation();
+      },
+
+      /**
+       * immediately stop gesture detection
+       * might be useful after a swipe was detected
+       * @return {*}
+       */
+      stopDetect: function() {
+        return Detection.stopDetect();
+      }
+    };
+  }
+};
+
+var PointerEvent = Hammer.PointerEvent = {
+  /**
+   * holds all pointers
+   * @type {Object}
+   */
+  pointers: {},
+
+  /**
+   * get a list of pointers
+   * @returns {Array}     touchlist
+   */
+  getTouchList: function getTouchList() {
+    var touchlist = [];
+    // we can use forEach since pointerEvents only is in IE10
+    Utils.each(this.pointers, function(pointer){
+      touchlist.push(pointer);
+    });
+
+    return touchlist;
+  },
+
+  /**
+   * update the position of a pointer
+   * @param   {String}   type             EVENT_END
+   * @param   {Object}   pointerEvent
+   */
+  updatePointer: function updatePointer(type, pointerEvent) {
+    if(type == EVENT_END) {
+      delete this.pointers[pointerEvent.pointerId];
+    }
+    else {
+      pointerEvent.identifier = pointerEvent.pointerId;
+      this.pointers[pointerEvent.pointerId] = pointerEvent;
+    }
+
+    // it's save to use Object.keys, since pointerEvents are only in newer browsers
+    return Object.keys(this.pointers).length;
+  },
+
+  /**
+   * check if ev matches pointertype
+   * @param   {String}        pointerType     POINTER_MOUSE
+   * @param   {PointerEvent}  ev
+   */
+  matchType: function matchType(pointerType, ev) {
+    if(!ev.pointerType) {
+      return false;
+    }
+
+    var pt = ev.pointerType
+      , types = {};
+
+    types[POINTER_MOUSE] = (pt === POINTER_MOUSE);
+    types[POINTER_TOUCH] = (pt === POINTER_TOUCH);
+    types[POINTER_PEN] = (pt === POINTER_PEN);
+    return types[pointerType];
+  },
+
+
+  /**
+   * get events
+   */
+  getEvents: function getEvents() {
+    return [
+      'pointerdown MSPointerDown',
+      'pointermove MSPointerMove',
+      'pointerup pointercancel MSPointerUp MSPointerCancel'
+    ];
+  },
+
+  /**
+   * reset the list
+   */
+  reset: function resetList() {
+    this.pointers = {};
+  }
+};
+
+
+var Detection = Hammer.detection = {
+  // contains all registred Hammer.gestures in the correct order
+  gestures: [],
+
+  // data of the current Hammer.gesture detection session
+  current : null,
+
+  // the previous Hammer.gesture session data
+  // is a full clone of the previous gesture.current object
+  previous: null,
+
+  // when this becomes true, no gestures are fired
+  stopped : false,
+
+
+  /**
+   * start Hammer.gesture detection
+   * @param   {Hammer.Instance}   inst
+   * @param   {Object}            eventData
+   */
+  startDetect: function startDetect(inst, eventData) {
+    // already busy with a Hammer.gesture detection on an element
+    if(this.current) {
+      return;
+    }
+
+    this.stopped = false;
+
+    // holds current session
+    this.current = {
+      inst              : inst, // reference to HammerInstance we're working for
+      startEvent        : Utils.extend({}, eventData), // start eventData for distances, timing etc
+      lastEvent         : false, // last eventData
+      lastVelocityEvent : false, // last eventData for velocity.
+      velocity          : false, // current velocity
+      name              : '' // current gesture we're in/detected, can be 'tap', 'hold' etc
+    };
+
+    this.detect(eventData);
+  },
+
+
+  /**
+   * Hammer.gesture detection
+   * @param   {Object}    eventData
+   */
+  detect: function detect(eventData) {
+    if(!this.current || this.stopped) {
+      return;
+    }
+
+    // extend event data with calculations about scale, distance etc
+    eventData = this.extendEventData(eventData);
+
+    // hammer instance and instance options
+    var inst = this.current.inst,
+        inst_options = inst.options;
+
+    // call Hammer.gesture handlers
+    Utils.each(this.gestures, function triggerGesture(gesture) {
+      // only when the instance options have enabled this gesture
+      if(!this.stopped && inst_options[gesture.name] !== false && inst.enabled !== false ) {
+        // if a handler returns false, we stop with the detection
+        if(gesture.handler.call(gesture, eventData, inst) === false) {
+          this.stopDetect();
+          return false;
+        }
+      }
+    }, this);
+
+    // store as previous event event
+    if(this.current) {
+      this.current.lastEvent = eventData;
+    }
+
+    // end event, but not the last touch, so dont stop
+    if(eventData.eventType == EVENT_END && !eventData.touches.length - 1) {
+      this.stopDetect();
+    }
+
+    return eventData;
+  },
+
+
+  /**
+   * clear the Hammer.gesture vars
+   * this is called on endDetect, but can also be used when a final Hammer.gesture has been detected
+   * to stop other Hammer.gestures from being fired
+   */
+  stopDetect: function stopDetect() {
+    // clone current data to the store as the previous gesture
+    // used for the double tap gesture, since this is an other gesture detect session
+    this.previous = Utils.extend({}, this.current);
+
+    // reset the current
+    this.current = null;
+
+    // stopped!
+    this.stopped = true;
+  },
+
+
+  /**
+   * calculate velocity
+   * @param   {Object}  ev
+   * @param   {Number}  delta_time
+   * @param   {Number}  delta_x
+   * @param   {Number}  delta_y
+   */
+  getVelocityData: function getVelocityData(ev, delta_time, delta_x, delta_y) {
+    var cur = this.current
+      , velocityEv = cur.lastVelocityEvent
+      , velocity = cur.velocity;
+
+    // calculate velocity every x ms
+    if (velocityEv && ev.timeStamp - velocityEv.timeStamp > Hammer.UPDATE_VELOCITY_INTERVAL) {
+      velocity = Utils.getVelocity(ev.timeStamp - velocityEv.timeStamp,
+                                   ev.center.clientX - velocityEv.center.clientX,
+                                  ev.center.clientY - velocityEv.center.clientY);
+      cur.lastVelocityEvent = ev;
+    }
+    else if(!cur.velocity) {
+      velocity = Utils.getVelocity(delta_time, delta_x, delta_y);
+      cur.lastVelocityEvent = ev;
+    }
+
+    cur.velocity = velocity;
+
+    ev.velocityX = velocity.x;
+    ev.velocityY = velocity.y;
+  },
+
+
+  /**
+   * calculate interim angle and direction
+   * @param   {Object}  ev
+   */
+  getInterimData: function getInterimData(ev) {
+    var lastEvent = this.current.lastEvent
+      , angle
+      , direction;
+
+    // end events (e.g. dragend) don't have useful values for interimDirection & interimAngle
+    // because the previous event has exactly the same coordinates
+    // so for end events, take the previous values of interimDirection & interimAngle
+    // instead of recalculating them and getting a spurious '0'
+    if(ev.eventType == EVENT_END) {
+      angle = lastEvent && lastEvent.interimAngle;
+      direction = lastEvent && lastEvent.interimDirection;
+    }
+    else {
+      angle = lastEvent && Utils.getAngle(lastEvent.center, ev.center);
+      direction = lastEvent && Utils.getDirection(lastEvent.center, ev.center);
+    }
+
+    ev.interimAngle = angle;
+    ev.interimDirection = direction;
+  },
+
+
+  /**
+   * extend eventData for Hammer.gestures
+   * @param   {Object}   evData
+   * @returns {Object}   evData
+   */
+  extendEventData: function extendEventData(ev) {
+    var cur = this.current
+      , startEv = cur.startEvent;
+
+    // if the touches change, set the new touches over the startEvent touches
+    // this because touchevents don't have all the touches on touchstart, or the
+    // user must place his fingers at the EXACT same time on the screen, which is not realistic
+    // but, sometimes it happens that both fingers are touching at the EXACT same time
+    if(ev.touches.length != startEv.touches.length || ev.touches === startEv.touches) {
+      // extend 1 level deep to get the touchlist with the touch objects
+      startEv.touches = [];
+      Utils.each(ev.touches, function(touch) {
+        startEv.touches.push(Utils.extend({}, touch));
+      });
+    }
+
+    var delta_time = ev.timeStamp - startEv.timeStamp
+      , delta_x = ev.center.clientX - startEv.center.clientX
+      , delta_y = ev.center.clientY - startEv.center.clientY;
+
+    this.getVelocityData(ev, delta_time, delta_x, delta_y);
+    this.getInterimData(ev);
+
+    Utils.extend(ev, {
+      startEvent: startEv,
+
+      deltaTime : delta_time,
+      deltaX    : delta_x,
+      deltaY    : delta_y,
+
+      distance  : Utils.getDistance(startEv.center, ev.center),
+      angle     : Utils.getAngle(startEv.center, ev.center),
+      direction : Utils.getDirection(startEv.center, ev.center),
+
+      scale     : Utils.getScale(startEv.touches, ev.touches),
+      rotation  : Utils.getRotation(startEv.touches, ev.touches)
+    });
+
+    return ev;
+  },
+
+
+  /**
+   * register new gesture
+   * @param   {Object}    gesture object, see gestures.js for documentation
+   * @returns {Array}     gestures
+   */
+  register: function register(gesture) {
+    // add an enable gesture options if there is no given
+    var options = gesture.defaults || {};
+    if(options[gesture.name] === undefined) {
+      options[gesture.name] = true;
+    }
+
+    // extend Hammer default options with the Hammer.gesture options
+    Utils.extend(Hammer.defaults, options, true);
+
+    // set its index
+    gesture.index = gesture.index || 1000;
+
+    // add Hammer.gesture to the list
+    this.gestures.push(gesture);
+
+    // sort the list by index
+    this.gestures.sort(function(a, b) {
+      if(a.index < b.index) { return -1; }
+      if(a.index > b.index) { return 1; }
+      return 0;
+    });
+
+    return this.gestures;
+  }
+};
+
+
+/**
+ * Drag
+ * Move with x fingers (default 1) around on the page. Blocking the scrolling when
+ * moving left and right is a good practice. When all the drag events are blocking
+ * you disable scrolling on that area.
+ * @events  drag, drapleft, dragright, dragup, dragdown
+ */
+Hammer.gestures.Drag = {
+  name     : 'drag',
+  index    : 50,
+  defaults : {
+    drag_min_distance            : 10,
+
+    // Set correct_for_drag_min_distance to true to make the starting point of the drag
+    // be calculated from where the drag was triggered, not from where the touch started.
+    // Useful to avoid a jerk-starting drag, which can make fine-adjustments
+    // through dragging difficult, and be visually unappealing.
+    correct_for_drag_min_distance: true,
+
+    // set 0 for unlimited, but this can conflict with transform
+    drag_max_touches             : 1,
+
+    // prevent default browser behavior when dragging occurs
+    // be careful with it, it makes the element a blocking element
+    // when you are using the drag gesture, it is a good practice to set this true
+    drag_block_horizontal        : false,
+    drag_block_vertical          : false,
+
+    // drag_lock_to_axis keeps the drag gesture on the axis that it started on,
+    // It disallows vertical directions if the initial direction was horizontal, and vice versa.
+    drag_lock_to_axis            : false,
+
+    // drag lock only kicks in when distance > drag_lock_min_distance
+    // This way, locking occurs only when the distance has become large enough to reliably determine the direction
+    drag_lock_min_distance       : 25
+  },
+
+  triggered: false,
+  handler  : function dragGesture(ev, inst) {
+    var cur = Detection.current;
+
+    // current gesture isnt drag, but dragged is true
+    // this means an other gesture is busy. now call dragend
+    if(cur.name != this.name && this.triggered) {
+      inst.trigger(this.name + 'end', ev);
+      this.triggered = false;
+      return;
+    }
+
+    // max touches
+    if(inst.options.drag_max_touches > 0 &&
+      ev.touches.length > inst.options.drag_max_touches) {
+      return;
+    }
+
+    switch(ev.eventType) {
+      case EVENT_START:
+        this.triggered = false;
+        break;
+
+      case EVENT_MOVE:
+        // when the distance we moved is too small we skip this gesture
+        // or we can be already in dragging
+        if(ev.distance < inst.options.drag_min_distance &&
+          cur.name != this.name) {
+          return;
+        }
+
+        var startCenter = cur.startEvent.center;
+
+        // we are dragging!
+        if(cur.name != this.name) {
+          cur.name = this.name;
+          if(inst.options.correct_for_drag_min_distance && ev.distance > 0) {
+            // When a drag is triggered, set the event center to drag_min_distance pixels from the original event center.
+            // Without this correction, the dragged distance would jumpstart at drag_min_distance pixels instead of at 0.
+            // It might be useful to save the original start point somewhere
+            var factor = Math.abs(inst.options.drag_min_distance / ev.distance);
+            startCenter.pageX += ev.deltaX * factor;
+            startCenter.pageY += ev.deltaY * factor;
+            startCenter.clientX += ev.deltaX * factor;
+            startCenter.clientY += ev.deltaY * factor;
+
+            // recalculate event data using new start point
+            ev = Detection.extendEventData(ev);
+          }
+        }
+
+        // lock drag to axis?
+        if(cur.lastEvent.drag_locked_to_axis ||
+            ( inst.options.drag_lock_to_axis &&
+              inst.options.drag_lock_min_distance <= ev.distance
+            )) {
+          ev.drag_locked_to_axis = true;
+        }
+        var last_direction = cur.lastEvent.direction;
+        if(ev.drag_locked_to_axis && last_direction !== ev.direction) {
+          // keep direction on the axis that the drag gesture started on
+          if(Utils.isVertical(last_direction)) {
+            ev.direction = (ev.deltaY < 0) ? DIRECTION_UP : DIRECTION_DOWN;
+          }
+          else {
+            ev.direction = (ev.deltaX < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
+          }
+        }
+
+        // first time, trigger dragstart event
+        if(!this.triggered) {
+          inst.trigger(this.name + 'start', ev);
+          this.triggered = true;
+        }
+
+        // trigger events
+        inst.trigger(this.name, ev);
+        inst.trigger(this.name + ev.direction, ev);
+
+        var is_vertical = Utils.isVertical(ev.direction);
+
+        // block the browser events
+        if((inst.options.drag_block_vertical && is_vertical) ||
+          (inst.options.drag_block_horizontal && !is_vertical)) {
+          ev.preventDefault();
+        }
+        break;
+
+      case EVENT_END:
+        // trigger dragend
+        if(this.triggered) {
+          inst.trigger(this.name + 'end', ev);
+        }
+
+        this.triggered = false;
+        break;
+    }
+  }
+};
+
+/**
+ * Hold
+ * Touch stays at the same place for x time
+ * @events  hold
+ */
+Hammer.gestures.Hold = {
+  name    : 'hold',
+  index   : 10,
+  defaults: {
+    hold_timeout  : 500,
+    hold_threshold: 2
+  },
+  timer   : null,
+
+  handler : function holdGesture(ev, inst) {
+    switch(ev.eventType) {
+      case EVENT_START:
+        // clear any running timers
+        clearTimeout(this.timer);
+
+        // set the gesture so we can check in the timeout if it still is
+        Detection.current.name = this.name;
+
+        // set timer and if after the timeout it still is hold,
+        // we trigger the hold event
+        this.timer = setTimeout(function() {
+          if(Detection.current.name == 'hold') {
+            inst.trigger('hold', ev);
+          }
+        }, inst.options.hold_timeout);
+        break;
+
+      // when you move or end we clear the timer
+      case EVENT_MOVE:
+        if(ev.distance > inst.options.hold_threshold) {
+          clearTimeout(this.timer);
+        }
+        break;
+
+      case EVENT_END:
+        clearTimeout(this.timer);
+        break;
+    }
+  }
+};
+
+/**
+ * Release
+ * Called as last, tells the user has released the screen
+ * @events  release
+ */
+Hammer.gestures.Release = {
+  name   : 'release',
+  index  : Infinity,
+  handler: function releaseGesture(ev, inst) {
+    if(ev.eventType == EVENT_END) {
+      inst.trigger(this.name, ev);
+    }
+  }
+};
+
+/**
+ * Swipe
+ * triggers swipe events when the end velocity is above the threshold
+ * for best usage, set prevent_default (on the drag gesture) to true
+ * @events  swipe, swipeleft, swiperight, swipeup, swipedown
+ */
+Hammer.gestures.Swipe = {
+  name    : 'swipe',
+  index   : 40,
+  defaults: {
+    swipe_min_touches: 1,
+    swipe_max_touches: 1,
+    swipe_velocity   : 0.7
+  },
+  handler : function swipeGesture(ev, inst) {
+    if(ev.eventType == EVENT_END) {
+      // max touches
+      if(ev.touches.length < inst.options.swipe_min_touches ||
+        ev.touches.length > inst.options.swipe_max_touches) {
+        return;
+      }
+
+      // when the distance we moved is too small we skip this gesture
+      // or we can be already in dragging
+      if(ev.velocityX > inst.options.swipe_velocity ||
+        ev.velocityY > inst.options.swipe_velocity) {
+        // trigger swipe events
+        inst.trigger(this.name, ev);
+        inst.trigger(this.name + ev.direction, ev);
+      }
+    }
+  }
+};
+
+/**
+ * Tap/DoubleTap
+ * Quick touch at a place or double at the same place
+ * @events  tap, doubletap
+ */
+Hammer.gestures.Tap = {
+  name    : 'tap',
+  index   : 100,
+  defaults: {
+    tap_max_touchtime : 250,
+    tap_max_distance  : 10,
+    tap_always        : true,
+    doubletap_distance: 20,
+    doubletap_interval: 300
+  },
+
+  has_moved: false,
+
+  handler : function tapGesture(ev, inst) {
+    var prev, since_prev, did_doubletap;
+
+    // reset moved state
+    if(ev.eventType == EVENT_START) {
+      this.has_moved = false;
+    }
+
+    // Track the distance we've moved. If it's above the max ONCE, remember that (fixes #406).
+    else if(ev.eventType == EVENT_MOVE && !this.moved) {
+      this.has_moved = (ev.distance > inst.options.tap_max_distance);
+    }
+
+    else if(ev.eventType == EVENT_END &&
+        ev.srcEvent.type != 'touchcancel' &&
+        ev.deltaTime < inst.options.tap_max_touchtime && !this.has_moved) {
+
+      // previous gesture, for the double tap since these are two different gesture detections
+      prev = Detection.previous;
+      since_prev = prev && prev.lastEvent && ev.timeStamp - prev.lastEvent.timeStamp;
+      did_doubletap = false;
+
+      // check if double tap
+      if(prev && prev.name == 'tap' &&
+          (since_prev && since_prev < inst.options.doubletap_interval) &&
+          ev.distance < inst.options.doubletap_distance) {
+        inst.trigger('doubletap', ev);
+        did_doubletap = true;
+      }
+
+      // do a single tap
+      if(!did_doubletap || inst.options.tap_always) {
+        Detection.current.name = 'tap';
+        inst.trigger(Detection.current.name, ev);
+      }
+    }
+  }
+};
+
+/**
+ * Touch
+ * Called as first, tells the user has touched the screen
+ * @events  touch
+ */
+Hammer.gestures.Touch = {
+  name    : 'touch',
+  index   : -Infinity,
+  defaults: {
+    // call preventDefault at touchstart, and makes the element blocking by
+    // disabling the scrolling of the page, but it improves gestures like
+    // transforming and dragging.
+    // be careful with using this, it can be very annoying for users to be stuck
+    // on the page
+    prevent_default    : false,
+
+    // disable mouse events, so only touch (or pen!) input triggers events
+    prevent_mouseevents: false
+  },
+  handler : function touchGesture(ev, inst) {
+    if(inst.options.prevent_mouseevents &&
+        ev.pointerType == POINTER_MOUSE) {
+      ev.stopDetect();
+      return;
+    }
+
+    if(inst.options.prevent_default) {
+      ev.preventDefault();
+    }
+
+    if(ev.eventType == EVENT_START) {
+      inst.trigger(this.name, ev);
+    }
+  }
+};
+
+
+/**
+ * Transform
+ * User want to scale or rotate with 2 fingers
+ * @events  transform, pinch, pinchin, pinchout, rotate
+ */
+Hammer.gestures.Transform = {
+  name     : 'transform',
+  index    : 45,
+  defaults : {
+    // factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
+    transform_min_scale      : 0.01,
+    // rotation in degrees
+    transform_min_rotation   : 1,
+    // prevent default browser behavior when two touches are on the screen
+    // but it makes the element a blocking element
+    // when you are using the transform gesture, it is a good practice to set this true
+    transform_always_block   : false,
+    // ensures that all touches occurred within the instance element
+    transform_within_instance: false
+  },
+
+  triggered: false,
+
+  handler  : function transformGesture(ev, inst) {
+    // current gesture isnt drag, but dragged is true
+    // this means an other gesture is busy. now call dragend
+    if(Detection.current.name != this.name && this.triggered) {
+      inst.trigger(this.name + 'end', ev);
+      this.triggered = false;
+      return;
+    }
+
+    // at least multitouch
+    if(ev.touches.length < 2) {
+      return;
+    }
+
+    // prevent default when two fingers are on the screen
+    if(inst.options.transform_always_block) {
+      ev.preventDefault();
+    }
+
+    // check if all touches occurred within the instance element
+    if(inst.options.transform_within_instance) {
+      for(var i=-1; ev.touches[++i];) {
+        if(!Utils.hasParent(ev.touches[i].target, inst.element)) {
+          return;
+        }
+      }
+    }
+
+    switch(ev.eventType) {
+      case EVENT_START:
+        this.triggered = false;
+        break;
+
+      case EVENT_MOVE:
+        var scale_threshold = Math.abs(1 - ev.scale);
+        var rotation_threshold = Math.abs(ev.rotation);
+
+        // when the distance we moved is too small we skip this gesture
+        // or we can be already in dragging
+        if(scale_threshold < inst.options.transform_min_scale &&
+          rotation_threshold < inst.options.transform_min_rotation) {
+          return;
+        }
+
+        // we are transforming!
+        Detection.current.name = this.name;
+
+        // first time, trigger dragstart event
+        if(!this.triggered) {
+          inst.trigger(this.name + 'start', ev);
+          this.triggered = true;
+        }
+
+        inst.trigger(this.name, ev); // basic transform event
+
+        // trigger rotate event
+        if(rotation_threshold > inst.options.transform_min_rotation) {
+          inst.trigger('rotate', ev);
+        }
+
+        // trigger pinch event
+        if(scale_threshold > inst.options.transform_min_scale) {
+          inst.trigger('pinch', ev);
+          inst.trigger('pinch' + (ev.scale<1 ? 'in' : 'out'), ev);
+        }
+        break;
+
+      case EVENT_END:
+        // trigger dragend
+        if(this.triggered) {
+          inst.trigger(this.name + 'end', ev);
+        }
+
+        this.triggered = false;
+        break;
+    }
+  }
+};
+
+// AMD export
+if(typeof define == 'function' && define.amd) {
+  define(function(){
+    return Hammer;
+  });
+}
+// commonjs export
+else if(typeof module == 'object' && module.exports) {
+  module.exports = Hammer;
+}
+// browser export
+else {
+  window.Hammer = Hammer;
+}
+
+})(window);
\ No newline at end of file
diff --git a/src/index.html b/src/index.html
new file mode 100644 (file)
index 0000000..96f72fe
--- /dev/null
@@ -0,0 +1,160 @@
+<!DOCTYPE html>
+<html data-appid=com.yukkurigames.yuu>
+  <head>
+    <title>Yuu ~ Yukkuri Games</title>
+    <meta charset=utf-8>
+    <meta name=viewport
+          content='user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1, minimal-ui'>
+    <meta name=apple-mobile-web-app-capable content=yes>
+    <meta name=apple-mobile-web-app-status-bar-style
+          content=black-translucent />
+    <link rel=icon href='data/images/icons.ico'
+          sizes='16x16 32x32 64x64 128x128 256x256'>
+    <link rel=icon href='data/images/icons.icns'
+          sizes='16x16 32x32 64x64 128x128 256x256'>
+    <link rel=stylesheet href='yuu/data/yuu.css'>
+    <link rel=stylesheet href='main.css'>
+    <script src='ext/gl-matrix.js'></script>
+    <script src='ext/hammer.js'></script>
+    <script src='yuu/pre.js'></script>
+    <script src='yuu/yf.js'></script>
+    <script src='yuu/yT.js'></script>
+    <script src='yuu/core.js'></script>
+    <script src='yuu/input.js'></script>
+    <script src='yuu/ce.js'></script>
+    <script src='yuu/gfx.js'></script>
+    <script src='yuu/rdr.js'></script>
+    <script src='yuu/director.js'></script>
+    <script src='main.js'></script>
+  </head>
+  
+  <body>
+    <header>
+      <a href="/">
+        <img src="data/images/logotype_horizontal_1.png"
+             class=logo alt="(◕ ヮ ◕)">
+        <img src="data/images/logotype_horizontal_2.png"
+             class=optional alt="Yukkuri Games">
+      </a>
+      <h1>Yuu</h1>
+    </header>
+    <main>
+      <noscript>
+        <p>
+          Games made with Yuu require JavaScript to play. We don't use
+          any remote tracking cookies, load any external scripts, or
+          do any of the things you've probably disabled JavaScript to
+          avoid.
+          <a href="https://yukkurigames.com/privacy.html">Check our
+          privacy policy for more details.</a>
+        </p>
+      </noscript>
+      <canvas id=yuu-canvas data-yuu-resize>
+        Yuu requires JavaScript, canvas, and WebGL support. Your
+        browser seems to lack canvas support.
+      </canvas>
+      <div id=yuu-error class=yuu-overlay>
+        <div data-yuu-command=dismiss tabindex=0></div>
+        <p>There was a problem. Sorry about that.</p>
+        <p id=yuu-error-message></p>
+        <h2>Error Log</h2>
+        <pre id=yuu-error-stack>
+        </pre>
+      </div>
+      <div id=yuu-fatal-error class=yuu-overlay>
+        <p>
+          There was a serious problem. You'll have to restart. Sorry
+          about that.
+        </p>
+        <p id=yuu-fatal-error-message></p>
+        <p class=standalone>
+          If this problem continues,
+          <a target=_blank href='https://yukkurigames.com/support.html'>check our
+            support page</a>. If it doesn't help,
+          <a href='mailto:yuuSPAM@MAPSyukkurigames.com'
+             onclick='this.href=this.href.replace(/[A-Z]/g, "")'>email us</a>.
+          Please include the rest of the text in this error message and
+          <a target=_blank href='chrome://gpu'>information about your
+            system's GPU</a>.
+        </p>
+        <p class=browser>
+          Supported browsers include recent versions of
+          <a target=_blank href='http://www.mozilla.org/firefox/'>Mozilla Firefox</a>
+          and <a target=_blank href='http://www.google.com/chrome/'>Google Chrome</a>
+          on most desktop computers,
+          and <a target=_blank href='https://www.google.com/intl/en/chrome/browser/mobile/android.html'>Chrome
+          for Android</a>
+        </p>
+        <p class=browser>
+          If you are running one of these browsers and still have a problem,
+          <a target=_blank href='https://yukkurigames.com/support.html'>check our
+            support page</a>. If it doesn't help,
+          <a href='mailto:yuuSPAM@MAPSyukkurigames.com'
+             onclick='this.href=this.href.replace(/[A-Z]/g, "")'>email us</a>.
+          Please include the rest of the text in this error message and
+          as much information as you can about your operating system,
+          computer, and especially GPU.
+        </p>
+        <h2>Error Log</h2>
+        <pre id=yuu-fatal-error-stack>
+        </pre>
+      </div>
+      <p>
+        Yuu is our JavaScript/<a href="http://get.webgl.org/">WebGL</a> game
+        engine. It is currently being used in
+        <a href="http://yukkuri.itch.io/pwl6"><em>Pixel Witch Lesson #6</em></a>
+        and other games in development, as well as the live demo
+        below. (In other words, if you can see the demo you can play
+        our games &mdash; and if there's an error, please
+        <a href='mailto:yuuSPAM@MAPSyukkurigames.com'
+           onclick='this.href=this.href.replace(/[A-Z]/g, "")'>let us know</a>.)
+      </p>
+      <p>
+        Games made with Yuu run in Chromium and Firefox, and can also
+        be automatically packaged with
+        <a href="https://github.com/rogerwang/node-webkit">node-webkit</a>
+        for Windows, Mac OS X, and GNU/Linux. It exclusively targets
+        "modern" JavaScript implementations because there's already a
+        hard requirement on WebGL.
+      </p>
+      <p>
+        Because it's based on WebGL it <em>theoretically</em> supports
+        any platform implementing HTML5, ES5, and WebGL. In practice,
+        many such implementations are buggy and idiosyncratic. Android
+        4.4 support is good on several phone lines; iOS 8 (and Safari
+        on OS X) support is not.
+      </p>
+      <h2>Getting It &amp; Using It</h2>
+      <p>
+        Yuu provides <em>no</em> stability guarantees. It's an
+        immature library and likely to see major breaking changes
+        regularly. It is a relatively low-level library, and there is
+        little documentation. (Hopefully, these issues are going to
+        shake out as we make more games.)
+      </p>
+      <p>
+        Because of this, it's currently only distributed with games
+        using it, or from our Git repository:
+      </p>
+      <ul class=download>
+        <li class=sh><span data-optional>git clone
+          </span><a href="http://git.yukkurigames.com/yuu.git">http://git.yukkurigames.com/yuu.git</a>
+      </ul>
+      <p>
+        Yuu is free software. You can redistribute it and/or
+        modify it under the terms of the GNU General Public License
+        as published by the Free Software Foundation;
+        either <a href="http://www.gnu.org/licenses/gpl-2.0.html">version
+        2 of the License</a>, or (at your
+        option) <a href="http://www.gnu.org/licenses/gpl.html">any
+        later version</a>.
+      </p>
+      <p>
+        Yuu also contains code from other projects, all of which is
+        licensed under GPL-compatible terms. If you're having trouble
+        sleeping, it includes
+        <a href="yuu/data/license.txt">complete license text</a>.
+      </p>
+    </main>
+  </body>
+</html>
diff --git a/src/main.css b/src/main.css
new file mode 100644 (file)
index 0000000..fa4d395
--- /dev/null
@@ -0,0 +1,527 @@
+@font-face {
+    font-family: 'Fira Sans';
+    font-style: normal;
+    font-weight: 300;
+    src: local('Fira Sans OT Light'), local('Fira Sans Light'), url(FiraSans-Light.woff) format('woff');
+}
+
+@font-face {
+    font-family: 'Fira Sans';
+    font-style: italic;
+    font-weight: 300;
+    src: local('Fira Sans OT Light Italic'), local('Fira Sans Light Italic'), url(FiraSans-LightItalic.woff) format('woff');
+}
+
+@font-face {
+    font-family: 'Fira Sans';
+    font-style: normal;
+    font-weight: 400;
+    src: local('Fira Sans OT'), local('Fira Sans'), url(FiraSans-Regular.woff) format('woff');
+}
+
+@font-face {
+    font-family: 'Fira Sans';
+    font-style: normal;
+    font-weight: 700;
+    src: local('Fira Sans OT Bold'), local('Fira Sans Bold'), url(FiraSans-Bold.woff) format('woff');
+}
+
+@font-face {
+    font-family: 'Fira Sans';
+    font-style: italic;
+    font-weight: 400;
+    src: local('Fira Sans OT Italic'), local('Fira Sans Italic'), url(FiraSans-Italic.woff) format('woff');
+}
+
+@font-face {
+    font-family: 'Fira Sans';
+    font-style: italic;
+    font-weight: 700;
+    src: local('Fira Sans OT Bold Italic'), local('Fira Sans Bold Italic'), url(FiraSans-BoldItalic.woff) format('woff');
+}
+
+@font-face {
+    font-family: 'Fira Mono';
+    font-style: normal;
+    font-weight: 400;
+    src: local('Fira Mono OT'), local('Fira Mono'), url(FiraMono-Regular.woff) format('woff');
+}
+
+@font-face {
+    font-family: 'Fira Mono';
+    font-style: normal;
+    font-weight: 700;
+    src: local('Fira Mono OT Bold'), local('Fira Mono Bold'), url(FiraMono-Bold.woff) format('woff');
+}
+
+* {
+    padding: 0;
+    margin: 0;
+    font-weight: normal;
+    text-decoration: none;
+}
+
+header * {
+    outline: none;
+}
+
+strong { font-weight: bold; }
+em { font-style: italic; }
+
+a:link, [onclick] {
+    text-decoration: underline;
+    cursor: pointer;
+}
+
+html {
+    font-family: "Fira Sans", sans-serif;
+    font-size: 16px;
+    background-color: rgb(226, 192, 242);
+    background-image: linear-gradient(rgb(226, 192, 242), rgb(244, 199, 199) 100%);
+    background-attachment: fixed;
+    padding: 0 1em;
+    height: 100%;
+    background-repeat: no-repeat;
+}
+
+body {
+    border-top: solid rgb(206, 132, 242) 0.75em;
+    border-bottom: solid rgb(244, 126, 126) 0.75em;
+    border-radius: 2em;
+    margin: 0.25em auto;
+    color: black;
+    background-color: white;
+    max-width: 56em;
+    padding: 0 1em;
+}
+
+header {
+    border-top: solid rgb(206, 132, 242) 0.5em;
+    border-bottom: solid rgb(244, 126, 126) 0.5em;
+    border-radius: 2em;
+    height: 6em;
+    transition: border-color 0.5s;
+    white-space: nowrap;
+    text-align: right;
+    margin: 0 -1em;
+    margin-top: -0.75em;
+    background-color: white;
+}
+
+header img {
+    float: left;
+    height: 100%;
+    width: auto;
+}
+
+header h1 {
+    font-size: 3em;
+    font-weight: normal;
+    margin-right: 0.25em;
+    display: inline-block;
+    height: 100%;
+    line-height: 2em;
+}
+
+header:hover {
+    border-top-color: rgb(244, 126, 126);
+    border-bottom-color: rgb(206, 132, 242);
+}
+
+img.logo {
+    -webkit-transition: -webkit-transform 1.5s;
+    transition: transform 1.5s;
+    -webkit-transition-delay: 0.5s;
+    transition-delay: 0.5s;
+    -webkit-transition-timing-function: cubic-bezier(0.4, 0.2, 0.5, 1.3);
+    transition-timing-function: cubic-bezier(0.4, 0.2, 0.5, 1.3);
+}
+
+img.logo:hover {
+    -webkit-transform: rotate(360deg);
+    transform: rotate(360deg);
+}
+
+h2 {
+    border-top: solid rgb(206, 132, 242) 1px;
+    border-left: solid rgb(206, 132, 242) 1px;
+    border-radius: 1em 0 0 1em;
+    font-size: 1.25em;
+    font-weight: 300;
+    margin-left: -0.67em;
+    margin-right: -0.67em;
+    margin-top: 1.5em;
+    margin-bottom: 0.5em;
+    padding-left: 0.5em;
+    padding-top: 0.125em;
+}
+
+h3 {
+    border-top: solid rgb(206, 132, 242) 1px;
+    border-left: solid rgb(206, 132, 242) 1px;
+    border-radius: 1.5em;
+    display: inline-block;
+    font-size: 1.125em;
+    font-weight: 300;
+    margin-bottom: 0;
+    margin-left: -0.445em;
+    padding-left: 0.67em;
+    padding-right: 0.67em;
+}
+
+a:link, [onclick] {
+    color: rgb(206, 132, 242);
+    transition: color 0.3s;
+}
+
+a:visited {
+    color: rgb(206, 132, 242);
+}
+
+a:hover, [onclick]:hover {
+    color: rgb(244, 126, 126);
+}
+
+main {
+    display: block;
+    padding: 1em;
+    max-width: 50em;
+    margin: auto;
+}
+
+main > *:first-child {
+    margin-top: 0;
+}
+
+p, li {
+    line-height: 125%;
+}
+
+main > p {
+    max-width: 45em;
+    margin: 1em auto;
+}
+
+main > ul {
+    max-width: 40em;
+    margin: 1em auto;
+}
+
+main > ul > li {
+    margin: 0.5em auto;
+}
+
+hr {
+    margin-top: 0;
+    margin-bottom: 1em;
+    border-bottom: solid rgb(206, 132, 242) 1px;
+    height: 2em;
+}
+
+.highlight {
+    border-bottom: solid rgb(206, 132, 242) 0.125em;
+    border-top: solid rgb(206, 132, 242) 0.125em;
+    border-radius: 1em;
+    font-weight: 300;
+    padding: 1em 3em;
+    transition: border-radius 0.3s;
+    box-sizing: border-box;
+}
+
+.highlight:hover {
+    border-radius: 3em;
+}
+
+.game-preview {
+    padding: 0;
+    text-align: center;
+    width: 100%;
+    max-width: 37em;
+    margin: auto;
+    list-style-type: none;
+}
+
+.game-preview > li {
+    position: relative;
+    border: solid rgb(206, 132, 242) 1px;
+    margin-bottom: 1.5em;
+    margin-top: 1em;
+    margin-left: 2.5em;
+    border-left-width: 0;
+    height: 8em;
+    transition: border-radius 0.3s, border-color 0.3s;
+    padding-left: 4em;
+    box-sizing: border-box;
+    border-radius: 0 3em 3em 0;
+}
+
+.game-preview > li:nth-child(even) {
+    padding-left: 0;
+    padding-right: 4em;
+    margin-right: 2.5em;
+    margin-left: 0;
+    border-left-width: 1px;
+    border-right-width: 0;
+    border-radius: 3em 0 0 3em;
+}
+
+.game-preview > li:hover {
+    border-radius: 0 0.25em 0.25em 0;
+    border-color: rgb(244, 126, 126);
+}
+
+.game-preview > li img {
+    border: solid rgb(206, 132, 242) 1px;
+    position: absolute;
+    border-radius: 50%;
+    top: -1px;
+    left: -4em;
+    height: 8em;
+    width: 8em;
+    opacity: 0.75;
+    transition: border-radius 0.3s, border-color 0.3s, opacity 0.3s;
+    box-sizing: border-box;
+}
+
+.game-preview > li:nth-child(even) img {
+    left: auto;
+    right: -4em;
+}
+
+.game-preview > li:hover img {
+    border-radius: 0;
+    border-color: rgb(244, 126, 126);
+    opacity: 1.0;
+}
+
+.game-preview .info {
+    position: relative;
+    height: 100%;
+}
+
+.game-preview .info h4 {
+    color: inherit;
+    font-style: italic;
+    padding-left: 0.125em;
+    padding-right: 0.125em;
+    text-decoration: none;
+    text-align: center;
+    box-sizing: border-box;
+    padding-top: 0.25em;
+    font-size: 1.125em;
+}
+
+.game-preview li:nth-child(odd) .info h4,
+.game-preview li:nth-child(odd) .info ul {
+    margin-right: 6.5rem;
+}
+
+.game-preview li:nth-child(even) .info h4,
+.game-preview li:nth-child(even) .info ul {
+    margin-right: 0em;
+    margin-left: 6.5rem;
+}
+
+h4 a:link {
+    text-decoration: none;
+}
+
+.game-preview .info p {
+    padding: 0.25em;
+    text-align: center;
+    position: absolute;
+    left: 0;
+    right: 0;
+    top: 50%;
+    transform: translateY(-50%);
+    -webkit-transform: translateY(-50%);
+    margin-top: 0.125em;
+}
+
+.game-preview .info ul {
+    bottom: 0.25em;
+    font-size: 0.75em;
+    position: absolute;
+    right: 0.125em;
+    left: 0.125em;
+    text-align: center;
+    list-style-type: none;
+}
+
+.game-preview .info li {
+    white-space: nowrap;
+}
+
+.game-preview .info li a:link {
+    text-decoration: none;
+    font-weight: inherit;
+}
+
+.game-preview .info li:before {
+    content: ' ~ ';
+}
+
+.game-preview .info li:first-child:before {
+    content: '';
+}
+
+.game-preview .info li {
+    display: inline;
+    font-weight: 300;
+}
+
+ul.download {
+    border-radius: 1em;
+    border: solid rgb(206, 132, 242) 0.125em;
+    font-weight: 300;
+    padding: 0.5em;
+    margin: 1em auto;
+    transition: border-radius 0.3s, border-color 0.3s;
+    white-space: nowrap;
+    display: table;
+}
+
+ul.download:hover {
+    border-radius: 4px;
+    border-color: rgb(244, 126, 126);
+}
+
+ul.download li {
+    list-style-type: none;
+    font-size: 1.25em;
+    margin-bottom: 0.8em;
+    text-align: center;
+}
+
+ul.download li a {
+    text-decoration: none;
+}
+
+ul.download li.sh {
+    margin-bottom: 0;
+    font-size: 0.75em;
+    font-family: "Fira Mono", monospace;
+    text-align: left;
+}
+
+ul.download li:last-child {
+    margin-bottom: 0;
+}
+
+.sh:before {
+    content: "$ ";
+}
+
+input {
+    font-family: inherit;
+    font-size: 1em;
+}
+
+.copyright {
+    margin: auto;
+    text-align: justify;
+    width: 75%;
+    font-size: 0.875em;
+    font-weight: 400;
+}
+
+.copyright > p {
+    font-size: 0.875em;
+    font-weight: 300;
+}
+
+.copyright:before {
+    content: "Copyright ©";
+}
+
+#yuu-canvas {
+    margin: 1em auto;
+    width: 100%;
+    height: 20em;
+    position: static;
+    border: solid rgba(206, 132, 242, 0.5) 2px;
+    border-radius: 2em;
+}
+
+pre {
+    border-left: solid rgba(206, 132, 242, 0.5) 8px;
+    border-radius: 8px;
+    font-family: "Fira Mono", monospace;
+    overflow: auto;
+    padding-left: 2em;
+    padding: 0.5em;
+    max-width: 40em;
+    margin: auto;
+    transition: background-color 0.3s, border-color 0.3s, border-radius 0.3s;
+}
+
+pre:hover {
+    background-color: rgba(206, 132, 242, 0.125);
+    border-color: rgb(206, 132, 242);
+    border-radius: 16px;
+}
+
+code {
+    font-family: "Fira Mono", monospace;
+    transition: background-color 0.3s;
+}
+
+code:hover {
+    background-color: rgba(206, 132, 242, 0.125);
+}
+
+pre code:hover {
+    background-color: transparent;
+}
+
+footer {
+    font-weight: 300;
+    text-align: center;
+    font-size: 0.75em;
+}
+
+footer a:link {
+    display: inline-block;
+    margin: 0 1em;
+    text-decoration: none;
+}
+
+@media (max-width: 767px) {
+    html { font-size: 13px; }
+
+    .optional {
+        display: none;
+    }
+
+    ul.download li.sh:before {
+        content: "";
+    }
+
+    pre {
+        font-size: 0.875em;
+    }
+}
+
+@media (max-width: 479px) {
+    html { font-size: 10px; padding: 0 0.5em; }
+    h4 { font-size: 1em; }
+}
+
+body {
+    background-color: white;
+    overflow: auto;
+}
+
+.yuu-toast {
+    background-color: transparent;
+    border: solid rgba(206, 132, 242, 0.5) 2px;
+    color: inherit;
+}
+
+.yuu-overlay {
+    background-color: transparent;
+    border: solid rgb(244, 126, 126) 2px;
+    color: inherit;
+    position: static;
+    transform: none;
+}
diff --git a/src/main.js b/src/main.js
new file mode 100644 (file)
index 0000000..6b2cb7b
--- /dev/null
@@ -0,0 +1,83 @@
+"use strict";
+
+var DISAPPEAR = {
+     0: { tween: { transform: { yaw: Math.PI } },
+          duration: 35 },
+    10: { tween: { quad: { alpha: 0 } },
+          duration: 25 }
+};
+
+var JUMP = {
+    0:   { tween1: { scale: [0.6, 0.3, 1.0] }, duration: 20 },
+    20: [{ tween1: { scale: [0.3, 0.6, 1.0] }, duration: 18 },
+         { tween1: { xy: 'target' }, duration: 20 }],
+    38:  { tween1: { scale: [0.5, 0.5, 1.0] }, duration: 5 },
+};
+
+var FollowScene = yT(yuu.Scene, {
+    constructor: function () {
+        yuu.Scene.call(this);
+        var textMat = new yuu.Material("@text");
+        var faceMat = new yuu.Material("@face");
+
+        var textQuad;
+        var textEntity = new yuu.E(
+            new yuu.Transform().setScale([1, 1, 1]),
+            textQuad = new yuu.QuadC(textMat));
+        this.faceEntity = new yuu.E(
+            new yuu.Transform().setScale([0.5, 0.5, 1]),
+            new yuu.QuadC(faceMat));
+        this.entity0.addChild(textEntity);
+        this.entity0.addChild(this.faceEntity);
+
+        this.disappear = function () {
+            textEntity.attach(new yuu.Animation(DISAPPEAR, {
+                transform: textEntity.transform,
+                quad: textQuad
+            }));
+        };
+
+        this.ready = yuu.ready([textMat, faceMat], this);
+     },
+
+    inputs: {
+        resize: function () {
+            var base = new yuu.AABB(-1.5, -1.5, 1.5, 1.5);
+            var vp = base.matchAspectRatio(yuu.viewport);
+            this.layer0.resize(vp.x0, vp.y0, vp.w, vp.h);
+        },
+
+        mousemove: function (p) {
+            p = this.layer0.worldFromDevice(p);
+            var d = 0.75 * (p.x - this.faceEntity.transform.x);
+            var angle = Math.PI / 2 * d;
+            if (!angle)
+                angle = 0;
+            angle = yf.clamp(angle, -Math.PI / 2, Math.PI / 2);
+            this.faceEntity.transform.roll = angle + Math.PI / 2;
+        },
+
+        touch: function (p) { this.inputs.mousemove.call(this, p); },
+
+        tap: function (p) {
+            this.disappear();
+            this.disappear = yf.I;
+            p = this.layer0.worldFromDevice(p);
+            this.faceEntity.attach(new yuu.Animation(JUMP, {
+                $: this.faceEntity.transform,
+                target: [p.x, p.y]
+            }));
+        },
+    },
+});
+
+function load () {
+    yuu.director.pushScene(new FollowScene());
+}
+
+window.addEventListener("load", function() {
+    yuu.registerInitHook(load);
+    yuu.init({ backgroundColor: [1, 1, 1, 0] })
+        .then(function () { yuu.director.start(); });
+});
+
diff --git a/src/yuu/audio.js b/src/yuu/audio.js
new file mode 100644 (file)
index 0000000..881924e
--- /dev/null
@@ -0,0 +1,524 @@
+/* Copyright 2014 Yukkuri Games
+   Licensed under the terms of the GNU GPL v2 or later
+   @license http://www.gnu.org/licenses/gpl-2.0.html
+   @source: http://yukkurigames.com/yuu/
+*/
+
+(function (yuu) {
+    "use strict";
+
+    var yT = this.yT || require("./yT");
+    var yf = this.yf || require("./yf");
+
+    yuu.Audio = yT({
+        /** Audio context/source/buffer accessor
+
+            You probably don't need to make this yourself; one is made
+            named yuu.audio during initialization.
+
+            You can set the master volume with yuu.audio.masterVolume.
+        */
+        constructor: function () {
+            this._ctx = new window.AudioContext();
+            this._compressor = this._ctx.createDynamicsCompressor();
+            this._masterVolume = this._ctx.createGain();
+            this._masterVolume.connect(this._compressor);
+            this._compressor.connect(this._ctx.destination);
+            this._musicVolume = this._ctx.createGain();
+            this._musicVolume.connect(this._masterVolume);
+            this._masterVolume.gain.value = 0.5;
+            this._musicVolume.gain.value = 0.5;
+
+            this._bufferCache = {};
+            this._mute = false;
+            this._storage = null;
+            this._volume = this._masterVolume.gain.value;
+        },
+
+        destination: { alias: "_masterVolume", readonly: true },
+        music: { alias: "_musicVolume", readonly: true },
+
+        _readStorage: function () {
+            if (!this._storage)
+                return;
+            yf.each.call(this, function (prop) {
+                this[prop] = this._storage.getObject(prop, this[prop]);
+            }, ["volume", "musicVolume", "mute"]);
+        },
+
+        _writeStorage: yf.debounce(function () {
+            if (!this._storage)
+                return;
+            yf.each.call(this, function (prop) {
+                this._storage.setObject(prop, this[prop]);
+            }, ["volume", "musicVolume", "mute"]);
+        }),
+
+        storage: {
+            get: function () { return this._storage; },
+            set: function (v) {
+                this._storage = v;
+                this._readStorage();
+            }
+        },
+
+        mute: {
+            get: function () { return this._mute; },
+            set: function (v) {
+                this._mute = !!v;
+                this.volume = this.volume;
+            }
+        },
+
+        volume: {
+            get: function () { return this._volume; },
+            set: function (v) {
+                this._volume = v;
+                v = this._mute ? 0 : v;
+                this._masterVolume.gain.value = v;
+                this._writeStorage();
+            }
+        },
+
+        musicVolume: {
+            get: function () { return this._musicVolume.gain.value; },
+            set: function (v) {
+                this._musicVolume.gain.value = v;
+                this._writeStorage();
+            }
+        },
+
+        currentTime: { alias: "_ctx.currentTime" },
+
+        decodeAudioData: function (data) {
+            var ctx = this._ctx;
+            try {
+                return ctx.decodeAudioData(data);
+            } catch (exc) {
+                return new Promise(function (resolve, reject) {
+                    ctx.decodeAudioData(data, function (buffer) {
+                        resolve(buffer);
+                    }, function () {
+                        reject(new Error("Error decoding audio buffer"));
+                    });
+                });
+            }
+        },
+
+        createBufferSource: function (path) {
+            var source = this._ctx.createBufferSource();
+            var sample = new yuu.AudioSample(path, this);
+            if ((source.buffer = sample.buffer) === null) {
+                sample.ready.then(function () {
+                    source.buffer = sample.buffer;
+                });
+            }
+            return source;
+        },
+
+        sampleRate: { alias: "_ctx.sampleRate" },
+        createGain: { proxy: "_ctx.createGain" },
+        createOscillator: { proxy: "_ctx.createOscillator" },
+    });
+
+    // FIXME: This parsing is garbagey, would be better to parse when
+    // first handed a dfn and turn everything into a function.
+    function applyMod (s, v) {
+        if (yf.isFunction(s))
+            return s(v);
+        else if (s === +s)
+            return s;
+        else if (s[0] === "-" || s[0] === "+")
+            return v + (+s);
+        else if (s[0] === "x" || s[0] === "*")
+            return v * +s.substring(1);
+        else if (s[s.length - 1] === "%")
+            return v * (parseFloat(s) / 100);
+        else
+            return +s;
+    }
+
+    var Envelope = yuu.Envelope = yT({
+        constructor: yf.argcd(
+            function (pairs) {
+                Envelope.call(this, pairs, 1);
+            },
+            function (pairs, scale) {
+                pairs = pairs || { "0": 1, "100%": 1 };
+                this.ts = Object.keys(pairs);
+                this.vs = yf.map.call(pairs, yf.getter, this.ts);
+                this.scale = scale;
+                var a = 0, b = 0;
+                var unlimited = false;
+                yf.each(function (t) {
+                    if (+t) {
+                        a = Math.max(+t, a);
+                        b = Math.min(+t, b);
+                    }
+                    unlimited = unlimited || (t[t.length - 1] === "%");
+                }, this.ts);
+                this.minDuration = a - b;
+                this.maxDuration = (unlimited || a === b)
+                    ? Infinity
+                    : this.minDuration;
+                var vMin = Math.min.apply(Math, this.vs);
+                var vMax = Math.max.apply(Math, this.vs);
+                this.constant = vMin === vMax && this.vs[0] * this.scale;
+            }
+        ),
+
+        schedule: function (param, t0, scale, duration) {
+            if (this.constant !== false) {
+                param.setValueAtTime(scale * this.constant, t0);
+            } else {
+                yf.each.call(this, function (s, v) {
+                    v = v * scale * this.scale;
+                    var t = t0 + applyMod(s, duration);
+                    if (t === t0)
+                        param.setValueAtTime(v, t);
+                    else
+                        param.linearRampToValueAtTime(v, t);
+                }, this.ts, this.vs);
+            }
+        }
+    });
+
+    yuu.AudioSample = yuu.Caching(yT({
+        constructor: function (path, ctx) {
+            ctx = ctx || yuu.audio;
+            var url = yuu.resourcePath(path, "sound", "wav");
+            this.data = null;
+            this.ready = yuu.GET(url, { responseType: "arraybuffer" })
+                .then(ctx.decodeAudioData.bind(ctx))
+                .then(yf.setter.bind(this, "buffer"))
+                .then(yf.K(this));
+        }
+    }), function (args) { return args.length <= 2 ? args[0] : null; });
+
+    yuu.Modulator = yT({
+        constructor: function (dfn) {
+            this.envelope = new yuu.Envelope(dfn.envelope);
+            this.frequency = dfn.frequency;
+            this.index = dfn.index || 1.0;
+        },
+
+        createModulator: function (ctx, t0, fundamental, duration) {
+            var modulator = ctx.createOscillator();
+            modulator.frequency.value = applyMod(
+                this.frequency, fundamental);
+            modulator.start(t0);
+            modulator.stop(t0 + duration);
+            var modulatorG = ctx.createGain();
+            modulator.connect(modulatorG);
+            this.envelope.schedule(
+                modulatorG.gain, t0, this.index * fundamental, duration);
+            return modulatorG;
+        }
+    });
+
+    yuu.Instrument = yT({
+        constructor: function (dfn) {
+            if (yf.isString(dfn)) {
+                var sampleName = dfn;
+                dfn = { sample: {} };
+                dfn.sample[sampleName] = {};
+            }
+            this.envelope = new yuu.Envelope(dfn.envelope);
+            this.frequency = dfn.frequency || (dfn.sample ? {} : { "x1": 1.0 });
+            this.modulator = yf.map(
+                yf.new_(yuu.Modulator), yf.arrayify(dfn.modulator || []));
+            this.sample = dfn.sample || {};
+            this.ready = yuu.ready(
+                yf.map(yf.new_(yuu.AudioSample), Object.keys(this.sample)),
+                this);
+        },
+
+        createSound: function (ctx, t0, fundamental, amplitude, duration) {
+            // TODO: In the case of exactly one sample with a constant
+            // envelope, optimize out the extra gain node.
+            duration = yf.clamp(duration || 0,
+                                this.envelope.minDuration,
+                                this.envelope.maxDuration);
+            var ret = ctx.createGain();
+            var dst = ret;
+
+            yf.ipairs(function (name, params) {
+                var buffer = new yuu.AudioSample(name).buffer;
+                if (buffer && !params.loop)
+                    duration = Math.max(buffer.duration, duration);
+            }, this.sample);
+
+            var modulators = yf.map(function (modulator) {
+                return modulator.createModulator(
+                    ctx, t0, fundamental, duration);
+            }, this.modulator);
+
+            yf.ipairs.call(this, function (name, params) {
+                var src = ctx.createBufferSource(name);
+                src.loop = params.loop || false;
+                src.playbackRate.value = applyMod(
+                    params.playbackRate || 1, fundamental || ctx.sampleRate);
+                yf.each(function (mod) { mod.connect(src.playbackRate); },
+                        modulators);
+                if (params.duration)
+                    src.start(t0, params.offset || 0, params.duration);
+                else
+                    src.start(t0, params.offset || 0);
+                src.stop(t0 + duration);
+                src.connect(dst);
+            }, this.sample);
+
+            yf.ipairs.call(this, function (mfreq, mamp) {
+                var osc = ctx.createOscillator();
+                osc.frequency.value = applyMod(mfreq, fundamental);
+                osc.start(t0);
+                osc.stop(t0 + duration);
+                yf.each(function (mod) { mod.connect(osc.frequency); },
+                        modulators);
+                if (mamp !== 1) {
+                    var gain = ctx.createGain();
+                    gain.gain.value = mamp;
+                    osc.connect(gain);
+                    gain.connect(dst);
+                } else {
+                    osc.connect(dst);
+                }
+            }, this.frequency);
+
+            ret.gain.value = 0;
+            this.envelope.schedule(ret.gain, t0, amplitude, duration);
+            return ret;
+        },
+
+        play: yf.argcd(
+            function () {
+                return this.play(null, 0, 0, 1, 1);
+            },
+            function (ctx, t, freq, amp, duration) {
+                ctx = ctx || yuu.audio;
+                t = t || ctx.currentTime;
+                var g = this.createSound(ctx, t, freq, amp, duration);
+                g.connect(ctx.destination);
+                return g;
+            }
+        )
+    });
+
+    yuu.Instruments = yf.mapValues(yf.new_(yuu.Instrument), {
+        SINE: {
+            envelope: { "0": 0, "0.016": 1, "-0.016": 1, "100%": 0 },
+        },
+
+        ORGAN: {
+            envelope: { "0": 0, "0.016": 1, "-0.016": 1, "100%": 0 },
+            frequency: { "x1": 0.83, "x1.5": 0.17 }
+        },
+
+        SIREN: {
+            envelope: { "0": 1 },
+            modulator: {
+                envelope: { "0": 1 },
+                frequency: "1",
+                index: 0.2
+            }
+        },
+
+        BELL: {
+            envelope: { "0": 1, "2.5": 0.2, "5": 0 },
+            modulator: {
+                envelope: { "0": 1, "2.5": 0.2, "5": 0 },
+                frequency: "x1.5",
+            }
+        },
+
+        BRASS: {
+            envelope: { "0": 0, "0.2": 1, "0.4": 0.6, "-0.1": 0.5, "100%": 0 },
+            modulator: {
+                envelope: { "0": 0, "0.2": 1, "0.4": 0.6,
+                            "-0.1": 0.5, "100%": 0 },
+                frequency: "x1",
+                index: 5.0
+            }
+        },
+    });
+
+    // Tune to A440 by default, although every interface should provide
+    // some ways to work around this. This gives C4 = ~261.63 Hz.
+    // https://en.wikipedia.org/wiki/Scientific_pitch_notation
+    yuu.C4_HZ = 440 * Math.pow(2, -9/12);
+
+    yuu.Scale = yT({
+        constructor: function (intervals) {
+            this.intervals = intervals;
+            this.length = this.intervals.length;
+            this.span = yf.foldl(function (a, b) { return a + b; }, intervals);
+        },
+
+        hz: function (tonic, degree, accidental) {
+            accidental = accidental || 0;
+            var s = this.span * ((degree / this.intervals.length) | 0)
+                + accidental;
+            degree %= this.intervals.length;
+            if (degree < 0) {
+                degree += this.intervals.length;
+                s -= this.span;
+            }
+            var i = 0;
+            while (degree >= 1) {
+                degree -= 1;
+                s += this.intervals[i];
+                i++;
+            }
+            if (degree > 0)
+                s += this.intervals[i] * degree;
+            return tonic * Math.pow(2, s / 1200.0);
+        }
+    });
+
+    yuu.Scales = yf.mapValues(yf.new_(yuu.Scale), {
+        CHROMATIC: yf.repeat(100, 12),
+        MINOR: [200, 100, 200, 200, 100, 200, 200],
+        MAJOR: [200, 200, 100, 200, 200, 200, 100],
+        WHOLE_TONE: [200, 200, 200, 200, 200, 200],
+        AUGMENTED: [300, 100, 300, 100, 300, 100],
+        _17ET: yf.repeat(1200 / 17, 17),
+        DOUBLE_HARMONIC: [100, 300, 100, 200, 100, 300, 100],
+    });
+
+    var DURATION = { T: 1/8, S: 1/4, I: 1/2, Q: 1, H: 2, W: 4,
+                     ".": 1.5, "/": 1/3, "<": -1 };
+    var ACCIDENTAL = { b: -1, "#": 1, t: 0.5, d: -0.5 };
+
+    var NOTE = /([TSIQHW][<.\/]*)?(?:([XZ]|(?:[A-G][b#dt]*[0-9]+))|([+-]?[0-9.]+)([b#dt]*))|([-+<>{]|(?:[TSIQHW][.\/]?))/g;
+
+    var LETTERS = { Z: null, X: null };
+
+    yuu.parseNote = function (note, scale, C4) {
+        return (C4 || yuu.C4_HZ) * Math.pow(2, LETTERS[note] / 12);
+    };
+
+    yuu.parseScore = function (score, scale, tonic, C4) {
+        // Note language:
+        //
+        // To play a scientific pitch note and advance the time, just
+        // use its name: G4, Cb2, A#0
+        //
+        // To adjust the length of the note, use T, S, I, Q (default),
+        // H, W for 32nd through whole. Append . to do
+        // time-and-a-half. Append / to cut into a third. Append < to
+        // go back in time.
+        //
+        // To play a note on the provided scale, use a 0-based number
+        // (which can be negative). To move the current scale up or
+        // down, use + or -. For example, in C major, 0 and C4 produce
+        // the same note; after a -, 0 and C3 produce the same note.
+        //
+        // To rest, use Z or X.
+        //
+        // To play multiple notes at the same time, enclose them all with
+        // < ... >. The time will advance in accordance with the shortest
+        // one.
+        //
+        // To reset the time, scale offset, and duration, use a {.
+        // This can be more convenient when writing pieces with
+        // multiple parts than grouping, e.g.
+        //     H < 1 8 > < 2 7 > < 3 6 > < 4 5 >
+        // is easier to understand when split into multiple lines:
+        //       H 1 2 3 4
+        //     { H 8 7 6 5
+
+        scale = scale || yuu.Scales.MAJOR;
+        C4 = C4 || yuu.C4_HZ;
+        tonic = tonic || scale.tonic || C4;
+        if (yf.isString(tonic))
+            tonic = yuu.parseNote(tonic, C4);
+
+        var t = 0;
+        var notes = [];
+        var degree = 0;
+        var groupLength = 0;
+        var defaultDuration = "Q";
+        var match;
+
+        function calcDuration (d, m) { return d * DURATION[m]; }
+        function calcAccidental (d, m) { return d * ACCIDENTAL[m]; }
+
+        while ((match = NOTE.exec(score))) {
+            switch (match[5]) {
+            case "<":
+                groupLength = Infinity;
+                break;
+            case ">":
+                t += groupLength === Infinity ? 0 : groupLength;
+                groupLength = 0;
+                break;
+            case "+":
+                degree += scale.length;
+                break;
+            case "-":
+                degree -= scale.length;
+                break;
+            case "{":
+                t = 0;
+                degree = 0;
+                groupLength = 0;
+                defaultDuration = "Q";
+                break;
+            default:
+                if (match[5]) {
+                    defaultDuration = match[5];
+                    continue;
+                }
+                var letter = match[2];
+                var duration = yf.foldl(
+                    calcDuration, match[1] || defaultDuration, 1);
+                if (LETTERS[letter] !== null) {
+                    var offset = match[3];
+                    var accidental = yf.foldl(
+                        calcAccidental, match[4] || "", 0) * 100;
+                    notes.push({
+                        time: t,
+                        duration: duration,
+                        hz: letter
+                            ? C4 * Math.pow(2, LETTERS[letter]/12.0)
+                            : scale.hz(tonic, degree + (+offset || 0), accidental)
+                    });
+                }
+                if (groupLength && duration > 0)
+                    groupLength = Math.min(groupLength, duration);
+                else
+                    t += duration;
+            }
+        }
+
+        notes.sort(function (a, b) { return a.time - b.time; });
+        return notes;
+    };
+
+    yf.irange.call(LETTERS, function (i) {
+        yf.ipairs.call(this, function (l, o) {
+            var b = o + 12 * (i - 4);
+            this[l + i] = b;
+            yf.ipairs.call(this, function (s, m) {
+                this[l + s + i] = b + m;
+            }, ACCIDENTAL);
+        }, { C: 0, D: 2, E: 4, F: 5, G: 7, A: 9, B: 11 });
+    }, 11);
+
+    yuu.registerInitHook(function () {
+        if (!window.AudioContext)
+            throw new Error("Web Audio isn't supported.");
+        yuu.audio = new yuu.Audio();
+        yuu.defaultCommands.volume = yuu.propcmd(
+            yuu.audio, "volume",
+            "get/set the current master audio volume", "0...1");
+        yuu.defaultCommands.musicVolume = yuu.propcmd(
+            yuu.audio, "musicVolume",
+            "get/set the current music volume", "0...1");
+        yuu.defaultCommands.mute = yuu.propcmd(
+            yuu.audio, "mute", "mute or unmute audio");
+    });
+
+}).call(typeof exports === "undefined" ? this : exports,
+        typeof exports === "undefined"
+        ? this.yuu : (module.exports = require('./core')));
diff --git a/src/yuu/ce.js b/src/yuu/ce.js
new file mode 100644 (file)
index 0000000..bb5c6e5
--- /dev/null
@@ -0,0 +1,646 @@
+/* Copyright 2014 Yukkuri Games
+   Licensed under the terms of the GNU GPL v2 or later
+   @license http://www.gnu.org/licenses/gpl-2.0.html
+   @source: http://yukkurigames.com/yuu/
+*/
+
+(function (yuu) {
+    "use strict";
+
+    /** yuu-ce - entity/component system for the Yuu engine
+
+        Game logic in Yuu is implemented via entities and components.
+        Entities (yuu.E) represent "things" in the world, and
+        components (yuu.C) individual properties or abilities of those
+        things. By attaching and detaching components, entities gain
+        and lose those abilities.
+
+        This system prioritizes for convenience and simplicity over
+        performance. Many common optimizations in E/C systems, like
+        component pooling and array-of-structures, are not
+        implemented. (And are of questionable value in a language like
+        JavaScript.)
+    */
+
+    var yT = this.yT || require("./yT");
+    var yf = this.yf || require("./yf");
+
+    yuu.E = yT({
+        constructor: function () {
+            /** Entity, a shell to customize with components
+
+                Entities exist as an aggregate of components (yuu.C).
+                They are used for components to talk to each other, or
+                other systems to handle systems in aggregate without
+                caring about the underlying details.
+
+                Entities expose components in two ways.
+                
+                First, components may have one or more slots; when a
+                component is attached to an entity it slots itself
+                into those properties on that entity. For example, if
+                you attach a Transform component to an entity, you can
+                retrieve it via e.transform.
+
+                Second, components may have one or more message
+                taps. This allows them to listen for, and respond to,
+                messages sent to the entity. Unlike slots many
+                attached components may have the same tap.
+            */
+            this.parent = null;
+            this.children = [];
+            this.taps = {};
+            this.attach.apply(this, arguments);
+        },
+
+        addChild: function (child) { this.addChildren(child); },
+        removeChild: function (child) { this.removeChildren(child); },
+
+        addChildren: function () {
+            yf.stash("parent", this, arguments);
+            this.children = this.children.concat(yf.slice(arguments));
+        },
+
+        removeChildren: function () {
+            this.children = yf.filter(
+                yf.lacks.bind(null, arguments), this.children);
+            yf.stash("parent", null, arguments);
+        },
+
+        attach: function () {
+            /** Attach a component to this entity.
+
+                If the entity already has a component in the same slots,
+                an error will be thrown.
+            */
+            for (var j = 0; j < arguments.length; ++j) {
+                var c = arguments[j];
+                var i;
+                for (i = 0; i < c.SLOTS.length; ++i)
+                    if (this[c.SLOTS[i].slot])
+                        throw new Error("Entity already has a " + c.SLOTS[i]);
+                for (i = 0; i < c.SLOTS.length; ++i)
+                    this[c.SLOTS[i]] = c;
+                for (i = 0; i < c.TAPS.length; ++i)
+                    this.taps[c.TAPS[i]] = (this.taps[c.TAPS[i]] || [])
+                        .concat(c);
+                c.entity = this;
+                c.attached(this);
+            }
+        },
+
+        detach: function () {
+            /** Detach a component from this entity */
+            for (var j = 0; j < arguments.length; ++j) {
+                var c = arguments[j];
+                var i;
+                for (i = 0; i < c.SLOTS.length; ++i)
+                    if (this[c.SLOTS[i].slot] !== c)
+                        throw new Error("Entity has a wrong " + c.SLOTS[i]);
+                for (i = 0; i < c.SLOTS.length; ++i)
+                    delete this[c.SLOTS[i]];
+                for (i = 0; i < c.TAPS.length; ++i)
+                    this.taps[c.TAPS[i]] = yf.without(this.taps[c.TAPS[i]], c);
+                c.entity = null;
+                c.detached(this);
+            }
+        },
+
+        _message: function (name, params) {
+            var taps = this.taps[name];
+            var children = this.children;
+            var i;
+            if (taps)
+                for (i = 0; i < taps.length; ++i)
+                    taps[i][name].apply(taps[i], params);
+            for (i = 0; i < children.length; ++i)
+                children[i]._message(name, params);
+        },
+        message: function (name) {
+            /** Message components listening on the named tap */
+            this._message(name, yf.tail(arguments));
+        },
+    });
+
+    yuu.C = yT({
+        entity: { value: null, writable: true },
+        SLOTS: { value: [], configurable: true },
+        TAPS: { value: [], configurable: true },
+
+        attached: function (entity) { },
+        detached: function (entity) { },
+    });
+
+    yuu.DataC = yT(yuu.C, {
+        /** A component for random scratch data
+
+            Storing this in a separate component rather than on the
+            entity directly reduces the chance of naming conflicts and
+            also the number of hidden classes.
+        */
+
+        constructor: function (data) {
+            Object.assign(this, data || {});
+        },
+
+        SLOTS: ["data"]
+    });
+
+
+    yuu.Animation = yT(yuu.C, {
+        constructor: function (timeline, params, completionHandler, delay) {
+            this.timeline = yf.mapValues(yf.arrayify, timeline);
+            this.params = params;
+            this.completionHandler = completionHandler;
+            this.keys = Object.keys(timeline)
+                .sort(function (a, b) {
+                    return +this._lookup(a) - +this._lookup(b);
+                }.bind(this));
+            this._t1 = +this._lookup(yf.last(this.keys)) + 1;
+            this._t = -(delay || 0);
+            this._pc = 0;
+            this._tweens = [];
+        },
+
+        attached: function () {
+            this.tick();
+        },
+
+        _lookup: function (k) {
+            return (k in this.params) ? this.params[k] : k;
+        },
+
+        set1: function (setter) {
+            var $ = this.params.$;
+            yf.ipairs.call(this, function (k, v) {
+                $[k] = this._lookup(v);
+            }, setter);
+        },
+
+        set: function (setters) {
+            yf.ipairs.call(this, function (name, setter) {
+                var $ = this._lookup(name);
+                yf.ipairs.call(this, function (k, v) {
+                    $[k] = this._lookup(v);
+                }, setter);
+            }, setters);
+        },
+
+        _addTween: function (tweens, instr) {
+            var repeat = instr.repeat || 0;
+            var cycles = Math.abs(repeat) + 1;
+            var easing = yf.isFunction(instr.easing)
+                ? instr.easing
+                : yuu.Tween[(instr.easing || "ease").toUpperCase()];
+            var duration, complete;
+
+            if ("complete" in instr) {
+                complete = this._lookup(instr.complete);
+                duration = (complete - this._t) / cycles;
+            } else if ("duration" in instr) {
+                duration = this._lookup(instr.duration);
+                complete = this._t + duration * cycles;
+            }
+
+            if (isFinite(cycles)) {
+                this._tweens.push(
+                    new yuu.Tween(tweens, duration, repeat, easing));
+                this._t1 = Math.max(complete + 1, this._t1);
+            } else {
+                this.entity.attach(new yuu.TweenC(
+                    tweens, duration, repeat, easing));
+            }
+        },
+
+        tween1: function (tween, instr) {
+            var nt = { $: this._lookup(instr.$) || this.params.$ };
+            yf.ipairs.call(this, function (k, v) {
+                nt[k] = [nt.$[k], this._lookup(v)];
+            }, tween);
+            this._addTween([nt], instr);
+        },
+
+        tween: function (targets, instr) {
+            var tweens = [];
+            yf.ipairs.call(this, function (name, tween) {
+                var nt = { $: this._lookup(name) || this.params.$ };
+                yf.ipairs.call(this, function (k, v) {
+                    nt[k] = [nt.$[k], this._lookup(v)];
+                }, tween);
+                tweens.push(nt);
+            }, targets);
+            this._addTween(tweens, instr);
+        },
+
+        tweenAll: function (tween, instr) {
+            var tweens = [];
+            var $s = this._lookup(instr.$s) || this.params.$s;
+            yf.irange.call(this, function (i) {
+                var nt = { $: $s[i] };
+                yf.ipairs.call(this, function (k, v) {
+                    nt[k] = [nt.$[k], this._lookup(v)[i]];
+                }, tween);
+                tweens.push(nt);
+            }, $s.length);
+            this._addTween(tweens, instr);
+        },
+
+        event: function (name) {
+            this.params[name](this, this.params);
+        },
+
+        _dispatch: function (instr) {
+            if (instr.set1)
+                this.set1(instr.set1);
+            if (instr.set)
+                this.set(instr.set);
+            if (instr.tween1)
+                this.tween1(instr.tween1, instr);
+            if (instr.tween)
+                this.tween(instr.tween, instr);
+            if (instr.tweenAll)
+                this.tweenAll(instr.tweenAll, instr);
+            if (instr.event)
+                this.event(instr.event);
+        },
+
+        tick: function () {
+            var t = this._t;
+            var i;
+            for (var key = this.keys[this._pc];
+                 this._lookup(key) <= t;
+                 key = this.keys[++this._pc]) {
+                yf.each.call(this, this._dispatch, this.timeline[key]);
+            }
+
+            for (i = this._tweens.length - 1; i >= 0; --i ) {
+                if (this._tweens[i].tick())
+                    this._tweens.splice(i, 1);
+            }
+
+            if (++this._t > this._t1) {
+                if (this.completionHandler)
+                    this.completionHandler(this);
+                this.entity.detach(this);
+            }
+        },
+
+        tock: function (p) {
+            for (var i = this._tweens.length - 1; i >= 0; --i)
+                this._tweens[i].tock(p);
+        },
+
+        TAPS: ["tick", "tock"]
+    });
+
+    yuu.Tween = yT({
+        /** Tween object properties over time
+
+            This component changes properties over time, and can
+            handle synchronizing multiple objects and multiple
+            properties.
+
+            The `property` is either a single object with the special
+            `$` property set to the object to tween and every other
+            property set to the properties to tween with values [min,
+            max], or a list of such objects. For example, to tween a.x
+            from 0 to 1, a.y from 2 to 3, and b.z from 1 to 2, you
+            would pass
+
+                [{ $: a, x: [0, 1], y: [2, 3] },
+                 { $: b, z: [1, 2] }]
+
+            The `duration` is specified in ticks (e.g. calls to
+            director.tick).
+
+            `repeat` may be a positive number to repeat the tween that
+            many times, or a negative number to cycle back to the
+            minimum (and then back to the maximum, etc.) that many
+            times. `Infinity` will repeat the tween forever and
+            `-Infinity` will cycle the tween back and forth forever.
+
+            A custom easing equation may be provided. This is a
+            function which takes a p = [0, 1] and returns the eased p.
+        */
+
+        constructor: function (props, duration, repeat, easing) {
+            this._object = [];
+            this._property = [];
+            this._a = [];
+            this._b = [];
+            this._count = 0;
+            this.duration = duration || 60;
+            this.repeat = repeat || 0;
+            this.easing = easing || yuu.Tween.LINEAR;
+            yf.each.call(this, function (oab) {
+                yf.ipairs.call(this, function (name, ab) {
+                    if (name !== "$") {
+                        this._object.push(oab.$);
+                        this._property.push(name);
+                        this._a.push(ab[0]);
+                        this._b.push(ab[1]);
+                    }
+                }, oab);
+            }, yf.arrayify(props));
+            this._updateAt(0);
+        },
+
+        tick: function () {
+            var t = this._count / this.duration;
+            ++this._count;
+            if (t > 1 && !this.repeat)
+                return true;
+            else if (t >= 1 && this.repeat) {
+                if (this.repeat < 0) {
+                    var n = this._a;
+                    this._a = this._b;
+                    this._b = n;
+                }
+                this._count = 1;
+                t = 0;
+
+                if (this.repeat < 0) {
+                    this.repeat++;
+                } else if (this.repeat > 0) {
+                    this.repeat--;
+                }
+            }
+            this._updateAt(t);
+        },
+
+        tock: function (p) {
+            var t = (this._count + p - 1) / this.duration;
+            if (t <= 1)
+                this._updateAt(t);
+        },
+
+        _updateAt: function (t) {
+            var p = this.easing ? this.easing(t) : t;
+            for (var i = 0; i < this._object.length; ++i) {
+                // a was the existing property, b was the one provided
+                // by the user.  By lerping from b to a, the user can
+                // control the lerp type in some awkward cases -
+                // e.g. CSS DOM values are all exposed as strings so a
+                // will be a string/String, but if b is provided as a
+                // number/Number, this will lerp numerically.
+                //
+                // FIXME: This still doesn't work right if the lerp is
+                // later reversed due to negative repeats.
+                var object = this._object[i];
+                var property = this._property[i];
+                var a = this._a[i];
+                var b = this._b[i];
+                object[property] = yuu.lerp(b, a, 1 - p);
+            }
+        },
+
+        count: {
+            get: function () { return this._count; },
+            set: function (v) { this._count = Math.round(v); },
+        },
+
+        duration: { value: 60, chainable: true },
+        repeat: { value: 0, chainable: true },
+        easing: { value: null, chainable: true },
+    });
+
+    yuu.TweenC = yT(yuu.C, {
+        constructor: function () {
+            this._tween = yf.construct(yuu.Tween, arguments);
+        },
+
+        tick: function () {
+            if (this._tween.tick())
+                this.entity.detach();
+        },
+
+        tock: { proxy: "_tween.tock" },
+        count: { alias: "_tween.count" },
+        duration: { alias: "_tween.duration", chainable: true },
+        repeat: { alias: "_tween.repeat", chainable: true },
+        easing: { alias: "_tween.easing", chainable: true },
+
+        TAPS: ["tick", "tock"]
+    });
+
+    yuu.Tween.LINEAR = null;
+        /** No easing */
+
+    yuu.Tween.EASE = function (p) {
+        /** Ease in and out
+
+            This equation is from _Improving Noise_ (Perlin, 2002). It
+            is symmetrical around p=0.5 and has zero first and second
+            derivatives at p=0 and p=1.
+        */
+        return p * p * p * (p * (p * 6.0 - 15.0) + 10.0);
+    };
+
+    yuu.Tween.EASE_IN = function (p) {
+        return p * p * p;
+    };
+
+    yuu.Tween.METASPRING = function (amplitude, pulsation) {
+        /** A generator for springy tweens
+
+            The amplitude controls how far from the final position the
+            spring will bounce, as a multiple of the distance between the
+            start and end. A "normal" amplitude is around 0.5 to 1.5.
+
+            The pulsation constant controls the rigidity of the spring;
+            higher pulsation results in a spring that bounces more quickly
+            and more often during a fixed interval. A "normal" pulsation
+            constant is around 15 to 30.
+        */
+        return function (p) {
+            return 1 + Math.cos(pulsation * p + Math.PI) * (1 - p) * amplitude;
+        };
+    };
+
+    yuu.Tween.STEPPED = function (segments, alpha) {
+        return function (p) {
+            p = p * segments;
+            var lower = Math.floor(p);
+            var upper = Math.floor((p + alpha));
+            if (upper > lower) {
+                var p1 = 1 - (upper - p) / alpha;
+                return (lower + p1) / segments;
+            } else {
+                return lower / segments;
+            }
+        };
+    };
+
+    yuu.Transform = yT(yuu.C, {
+        /** A 3D position, rotation (as quaternion), and scale
+
+            This also serves as an object lesson for a simple slotted
+            component.
+        */
+        constructor: function (position, rotation, scale) {
+            this._position = vec3.clone(position || [0, 0, 0]);
+            this._rotation = quat.clone(rotation || [0, 0, 0, 1]);
+            this._scale = vec3.clone(scale || [1, 1, 1]);
+            this._matrix = mat4.create();
+            this._dirty = true;
+            this._version = 0;
+            this._parentVersion = null;
+        },
+
+        SLOTS: ["transform"],
+
+        position: {
+            chainable: true,
+            get: function () { return this._position.slice(); },
+            set: function (v) { this._dirty = true;
+                                vec3.copy(this._position, v); }
+        },
+        rotation: {
+            chainable: true,
+            get: function () { return this._rotation.slice(); },
+            set: function (v) { this._dirty = true;
+                                quat.normalize(this._rotation, v); }
+        },
+        scale: {
+            chainable: true,
+            get: function () { return this._scale.slice(); },
+            set: function (v) { this._dirty = true;
+                                vec3.copy(this._scale, v); }
+        },
+        x: {
+            chainable: true,
+            get: function () { return this._position[0]; },
+            set: function (x) { this._dirty = true; this._position[0] = x; }
+        },
+        y: {
+            chainable: true,
+            get: function () { return this._position[1]; },
+            set: function (x) { this._dirty = true; this._position[1] = x; }
+        },
+        z: {
+            chainable: true,
+            get: function () { return this._position[2]; },
+            set: function (x) { this._dirty = true; this._position[2] = x; }
+        },
+        xy: { swizzle: "xy", chainable: true },
+
+        scaleX: {
+            chainable: true,
+            get: function () { return this._scale[0]; },
+            set: function (x) { this._dirty = true; this._scale[0] = x; }
+        },
+        scaleY: {
+            chainable: true,
+            get: function () { return this._scale[1]; },
+            set: function (x) { this._dirty = true; this._scale[1] = x; }
+        },
+        scaleZ: {
+            chainable: true,
+            get: function () { return this._scale[2]; },
+            set: function (x) { this._dirty = true; this._scale[2] = x; }
+        },
+
+        worldToLocal: function (p) {
+            var x = (p.x || p[0] || 0);
+            var y = (p.y || p[1] || 0);
+            var z = (p.z || p[2] || 0);
+            var local = [x, y, z];
+            var matrix = mat4.clone(this.matrix);
+            return vec3.transformMat4(local, local, mat4.invert(matrix, matrix));
+        },
+
+        contains: function (p) {
+            p = this.worldToLocal(p);
+            return p[0] >= -0.5 && p[0] < 0.5
+                && p[1] >= -0.5 && p[1] < 0.5
+                && p[2] >= -0.5 && p[2] < 0.5;
+        },
+
+        ypr: {
+            chainable: true,
+            get: function () {
+                var q = this._rotation;
+                var x = q[0]; var sqx = x * x;
+                var y = q[1]; var sqy = y * y;
+                var z = q[2]; var sqz = z * z;
+                var w = q[3];
+                var abcd = w * x + y * z;
+                if (abcd > 0.499)
+                    return [2 * Math.atan2(x, w), Math.PI / 2, 0];
+                else if (abcd < -0.499)
+                    return [-2 * Math.atan2(x, w), -Math.PI / 2, 0];
+                else {
+                    var adbc = w * z - x * y;
+                    var acbd = w * y - x * z;
+                    return [Math.atan2(2 * adbc, 1 - 2 * (sqz + sqx)),
+                            Math.asin(2 * abcd),
+                            Math.atan2(2 * acbd, 1 - 2 * (sqy + sqx))];
+                }
+                
+            },
+            set: function (ypr) {
+                var q = this._rotation;
+                quat.identity(q);
+                quat.rotateZ(q, q, ypr[0]);
+                quat.rotateY(q, q, ypr[2]);
+                quat.rotateX(q, q, ypr[1]);
+                this._dirty = true;
+            }
+        },
+
+        yaw: { aliasSynthetic: "ypr[0]", chainable: true },
+        pitch: { aliasSynthetic: "ypr[1]", chainable: true },
+        roll: { aliasSynthetic: "ypr[2]", chainable: true },
+
+        matrix: {
+            get: function () {
+                var pt = this.entity.parent && this.entity.parent.transform;
+                var pm = pt && pt.matrix;
+                var ptVersion = pt && pt._version;
+                if (this._dirty || (ptVersion !== this._parentVersion)) {
+                    var m = this._matrix;
+                    mat4.identity(m);
+                    mat4.fromRotationTranslation(
+                        m, this._rotation, this._position);
+                    mat4.scale(m, m, this._scale);
+                    if (pm)
+                        mat4.multiply(m, pm, m);
+                    this._dirty = false;
+                    this._matrix = m;
+                    this._parentVersion = ptVersion;
+                    this._version = (this._version + 1) | 0;
+                }
+                return this._matrix;
+            }
+        }
+    });
+
+    yuu.Ticker = yT(yuu.C, {
+        /** Set a callback to run every n ticks
+
+            If the callback returns true, it is rescheduled for
+            execution (like setInterval). If it returns false, this
+            component is removed from the entity.
+        */
+        constructor: function (callback, interval, delay) {
+            this.callback = callback;
+            this.interval = interval;
+            this._accum = 0;
+            this._count = -(delay || 0);
+        },
+
+        tick: function () {
+            this._accum += 1;
+            if (this._accum === this.interval) {
+                this._accum = 0;
+                if (!this.callback(this._count++))
+                    this.entity.detach(this);
+            }
+        },
+
+        TAPS: ["tick"]
+    });
+
+}).call(typeof exports === "undefined" ? this : exports,
+        typeof exports === "undefined"
+        ? this.yuu : (module.exports = require('./core')));
diff --git a/src/yuu/core.js b/src/yuu/core.js
new file mode 100644 (file)
index 0000000..cbeb2a0
--- /dev/null
@@ -0,0 +1,908 @@
+/* Copyright 2014 Yukkuri Games
+   Licensed under the terms of the GNU GPL v2 or later
+   @license http://www.gnu.org/licenses/gpl-2.0.html
+   @source: http://yukkurigames.com/yuu/
+*/
+
+(function (yuu) {
+    "use strict";
+
+
+    yuu.require = function (m) {
+        try { return require(m); }
+        catch (exc) { return null; }
+    };
+
+    if (!Math.sign)
+        require("./pre");
+
+    var yT = this.yT || require("./yT");
+    var yf = this.yf || require("./yf");
+    var gui = yuu.require("nw.gui");
+    var fs = yuu.require("fs");
+    var stringLerp = this.stringLerp || yuu.require("string-lerp");
+
+    var initHooks = [];
+    var initOptions = null;
+
+    if (typeof document !== "undefined") {
+        var scripts = document.getElementsByTagName('script');
+        var path = yf.last(scripts).src.split('?')[0];
+        yuu.PATH = path.split('/').slice(0, -1).join('/') + '/';
+    } else {
+        yuu.PATH = "file://" + escape(module.__dirname) + "/";
+    }
+
+    yuu.registerInitHook = initHooks.push.bind(initHooks);
+        /** Register a hook to be called during Yuu initialization
+
+            Hooks are called in registration order with the module and
+            the options dictionary passed to the init method. (This is
+            also set to the module.)
+        */
+
+    function showError (exc, kind) {
+        var prefix = "yuu-" + (kind || "") + "error";
+        yuu.logError(exc);
+        var dialog = document.getElementById(prefix);
+        var errorMessage = document.getElementById(prefix + "-message");
+        if (errorMessage)
+            errorMessage.textContent = exc.message;
+        var errorStack = document.getElementById(prefix + "-stack");
+        if (errorStack)
+            errorStack.textContent = exc.message + "\n\n" + exc.stack;
+        return dialog;
+    }
+    yuu.showError = showError;
+
+    function fatalError (exc) {
+        var dialog = showError(exc, "fatal-");
+        if (dialog)
+            dialog.style.display = "block";
+        if (gui) {
+            gui.Window.get().show();
+            gui.Window.get().focus();
+        }
+        throw exc;
+    }
+
+    yuu.init = function (options) {
+        /** Initialize Yuu and call all registered hooks
+         */
+
+        document.body.className += (navigator.standalone || gui)
+            ? " standalone" : " browser";
+
+        if (gui) {
+            var win = gui.Window.get();
+            var nativeMenuBar = new gui.Menu({ type: "menubar" });
+            if (nativeMenuBar.createMacBuiltin) {
+                nativeMenuBar.createMacBuiltin(
+                    document.title, { hideEdit: true });
+                win.menu = nativeMenuBar;
+            }
+            var wkdoc = document;
+            win.on("minimize", function () {
+                var ev = new Event("visibilitychange");
+                wkdoc.hidden = true;
+                wkdoc.dispatchEvent(ev);
+            });
+            win.on("restore", function () {
+                var ev = new Event("visibilitychange");
+                wkdoc.hidden = false;
+                wkdoc.dispatchEvent(ev);
+            });
+            win.on('new-win-policy', function (frame, url, policy) {
+                if (url.startsWith('chrome')) {
+                    policy.forceNewPopup();
+                } else {
+                    policy.ignore();
+                    gui.Shell.openExternal(url);
+                }
+            });
+        }
+
+        return new Promise(function (resolve) {
+            // TODO: Some kind of loading progress bar.
+            initOptions = options || {};
+            yuu.log("messages", "Initializing Yuu engine.");
+            var promises = [];
+            // initHooks can be pushed to while iterating, so iterate
+            // by index, not a foreach loop.
+            for (var i = 0; i < initHooks.length; ++i)
+                promises.push(initHooks[i].call(yuu, initOptions));
+            initHooks = null; // Bust future registerInitHook calls.
+            yuu.log("messages", "Initialization hooks complete.");
+            if (gui) {
+                gui.Window.get().show();
+                gui.Window.get().focus();
+            }
+            resolve(Promise.all(yf.filter(null, promises)));
+        }).then(function () {
+            yuu.log("messages", "Loading complete.");
+        }).catch(fatalError);
+    };
+
+    yuu.log = yf.argv(function (category, args) {
+        /** Log a message to the console.
+
+            This supports simple filtering by setting e.g.
+            `yuu.log.errors = true` to log anything with the
+            `"errors"` category.
+        */
+        if (!category || this.log[category]) {
+            switch (category) {
+            case "errors": return console.error.apply(console, args);
+            case "warnings": return console.warn.apply(console, args);
+            default: return console.log.apply(console, args);
+            }
+        }
+    });
+
+    yuu.log.errors = true;
+    yuu.log.warnings = true;
+    yuu.log.messages = true;
+
+    yuu.logError = function (e) {
+        yuu.log("errors", e.message || "unknown error", e);
+    };
+
+    yuu.GET = function (url, params) {
+        /** Promise the HTTP GET the contents of a URL. */
+        return new Promise(function (resolve, reject) {
+            var req = new XMLHttpRequest();
+            req.open("GET", url, true);
+            for (var k in params)
+                req[k] = params[k];
+            req.onload = function () {
+                var status = this.status;
+                // status === 0 is given by node-webkit for success.
+                if ((status >= 200 && status < 300) || status === 0)
+                    resolve(this.response);
+                else
+                    reject(new Error(
+                        url + ": " + status + ": " + this.statusText));
+            };
+            req.onabort = function () { reject(new Error("aborted")); };
+            req.onerror = function () { reject(new Error("network error")); };
+            req.ontimeout = function () { reject(new Error("timed out")); };
+            req.send(null);
+        });
+    };
+
+    yuu.Image = function (src) {
+        /** Promises a DOM Image. */
+        return new Promise(function (resolve, reject) {
+            var img = new Image();
+            img.onload = function () {
+                resolve(img);
+            };
+            img.onerror = function () {
+                var msg = "Unable to load " + img.src;
+                yuu.log("errors", msg);
+                reject(new Error(msg));
+            };
+            img.src = src;
+        });
+    };
+
+    /** Command parsing and execution
+
+        The command API serves several roles. It is a way to enable or
+        disable different game logic within different scenes; capture
+        and replay or automate game events; loosely or late-bind game
+        modules; customize input mappings; and a debugging tool to
+        help inspect or modify the state of a running program.
+
+        A command is a string of a command name followed by arguments
+        separated by whitespace. It's similar to a fully bound
+        closure. It is less flexible but easier to inspect, store,
+        replay, and pass around.
+
+        Command names are mapped to functions, grouped into sets, and
+        the sets pushed onto a stack. They are executed by passing the
+        command string to the execute function which walks the stack
+        looking for the matching command.
+
+        If the command string is prefaced with + or -, true or false
+        are appended to the argument list. e.g. `+command` is
+        equivalent to `command true` and `-command 1 b` is equivalent
+        to `command 1 b false`. By convention, commands of those forms
+        return their internal state when called with neither true or
+        false. This is useful for another special prefix, ++. When
+        called as `++command`, it is executed with no arguments, the
+        result inverted (with !), and then called again passing that
+        inverted value as the last argument.
+    */
+
+    function isCommand (f) {
+        return yf.isFunction(f) && f._isCommandFunction;
+    }
+
+    function cmdbind () {
+        // Commands are practically a subtype of functions. Binding
+        // them (which happens often, e.g. when Scenes register
+        // commands) should also return a command.
+        var f = Function.prototype.bind.apply(this, arguments);
+        // usage is still valid iff no new arguments were given.
+        return cmd(f, arguments.length <= 1 && this.usage, this.description);
+    }
+
+    var cmd = yuu.cmd = yf.argcd(
+        /** Decorate a function for command execution
+
+            Command functions need some special attributes to work
+            correctly. This decorator makes sure they have them.
+        */
+        function (f) { return yuu.cmd(f, null, null); },
+        function (f, description) { return yuu.cmd(f, null, description); },
+        function (f, usage, description) {
+            f._isCommandFunction = true;
+            f.usage = usage || " <value>".repeat(f.length).substring(1);
+            f.description = description || "no description provided";
+            f.bind = cmdbind;
+            return f;
+        }
+    );
+
+    yuu.propcmd = function (o, prop, desc, valspec) {
+        /** Generate a command function that controls a property
+
+            A common pattern for command functions is to simply get or
+            set a single object property. This wrapper will generate a
+            correct function to do that.
+        */
+        valspec = valspec || typeof o[prop];
+        desc = desc || "Retrieve or modify the value of " + prop;
+        return cmd(function () {
+            if (arguments.length)
+                o[prop] = arguments[0];
+            return o[prop];
+        }, "<" + valspec + "?>", desc);
+    };
+
+    var QUOTED_SPLIT = /[^"\s]+|"(?:\\"|[^"])+"/g;
+    var COMMAND_SPLIT = /\s+(&&|\|\||;)\s+/g;
+
+    function parseParam (param) {
+        if (yf.head(param) === "{" && yf.last(param) === "}")
+            return resolvePropertyPath(
+                this, param.substr(1, param.length - 2));
+        try { return JSON.parse(param); }
+        catch (exc) { return param; }
+    }
+
+    function parseCommand (cmdstring, ctx) {
+        /** Parse a command string into an invocation object.
+
+            The command string has a form like `+quux 1 2 3` or
+            `foobar "hello world"`.
+
+            Multiple commands can be joined in one string with &&, ||,
+            or ;. To use these characters literally as a command
+            argument place them in quotes.
+
+            Arguments wrapped in {}s are interpreted as property paths
+            for the provided context object. `{x[0].y}` will resolve
+            `ctx.x[0].y` and put that into the arguments array. To
+            avoid this behavior and get a literal string bounded by
+            {}, JSON-encode the string beforehand (e.g. `"{x[0].y}"`).
+
+            The returned array contains objects with three properties:
+            `name` - the command name to execute
+            `args` - an array of objects to pass as arguments
+            `toggle` - if the command value should be toggled ('++')
+                       and pushed into args
+            `cond` - "&&", "||", or ";", indicating what kind of
+                     conditional should be applied.
+        */
+
+        var invs = [];
+        var conds = cmdstring.split(COMMAND_SPLIT);
+        for (var i = -1; i < conds.length; i += 2) {
+            var args = conds[i + 1].match(QUOTED_SPLIT).map(parseParam, ctx);
+            var name = args.shift();
+            var toggle = false;
+            if (name[0] === "+" && name[1] === "+") {
+                name = name.substring(2);
+                toggle = true;
+            } else if (name[0] === "+") {
+                name = name.substring(1);
+                args.push(true);
+            } else if (name[0] === "-") {
+                name = name.substring(1);
+                args.push(false);
+            }
+            invs.push({ name: name, args: args, toggle: toggle,
+                        cond: conds[i] || ";"});
+        }
+        return invs;
+    }
+
+    yuu.CommandStack = yT({
+        constructor: function () {
+            /** A stack of command sets for command lookup and execution */
+            this._cmdsets = yf.slice(arguments);
+        },
+
+        push: function (cmdset) {
+            /** Add a command set to the lookup stack. */
+            this._cmdsets = this._cmdsets.concat(cmdset);
+        },
+
+        remove: function (cmdset) {
+            /** Remove a command set from the lookup stack. */
+            this._cmdsets = yf.without(this._cmdsets, cmdset);
+        },
+
+        insertBefore: function (cmdset, before) {
+            this._cmdsets = yf.insertBefore(
+                this._cmdsets.slice(), cmdset, before);
+        },
+
+        execute: function (cmdstring, ctx) {
+            /* Execute a command given a command string.
+
+               The command stack is searched top-down for the first
+               command with a matching name, and it is invoked.  No
+               other commands are called.
+
+               A command set may also provide a special function named
+               `$`. If no matching command name is found, this
+               function is called with the raw invocation object (the
+               result of yuu.parseCommand) and may return true to stop
+               processing as if the command had been found.
+            */
+            var invs = parseCommand(cmdstring, ctx);
+            var cond;
+            var res;
+            yf.each.call(this, function (inv) {
+                if ((inv.cond === "&&" && !cond) || (inv.cond === "||" && cond))
+                    return;
+                if (!yf.eachrUntil(function (cmdset) {
+                    var cmd = cmdset[inv.name];
+                    if (cmd) {
+                        if (inv.toggle)
+                            inv.args.push(!cmd.apply(null, inv.args));
+                        yuu.log("commands", "Executing:", inv.name,
+                                inv.args.map(JSON.stringify).join(" "));
+                        res = cmd.apply(null, inv.args);
+                        cond = res === undefined ? cond : !!res;
+                        yuu.log("commands", "Result:", JSON.stringify(res));
+                        return true;
+                    }
+                    return cmdset.$ && cmdset.$(inv);
+                }, this._cmdsets))
+                    yuu.log("errors", "Unknown command", inv.name);
+            }, invs);
+            return res;
+        }
+    });
+
+    yuu.extractCommands = function (object) {
+        var commands = {};
+        yf.each(function (prop) {
+            // Check the descriptor before checking the value, because
+            // checking the value of accessors (which should never be
+            // stable commands) is generally a bad idea during
+            // constructors, and command sets are often filled in during
+            // constructors.
+            if (yT.isDataDescriptor(yT.getPropertyDescriptor(object, prop))
+                && isCommand(object[prop]))
+                commands[prop] = object[prop].bind(object);
+        }, yf.allKeys(object));
+        return commands;
+    };
+
+    yuu.commandStack = new yuu.CommandStack(yuu.defaultCommands = {
+        /** The default command stack and set. */
+        cmds: yuu.cmd(function (term) {
+            term = term || "";
+            var cmds = [];
+            yuu.commandStack._cmdsets.forEach(function (cmdset) {
+                for (var cmdname in cmdset) {
+                    if (cmdname.indexOf(term) >= 0) {
+                        var cmd = cmdset[cmdname];
+                        var msg;
+                        if (cmd.usage)
+                            msg = [cmdname, cmd.usage, "--", cmd.description];
+                        else
+                            msg = [cmdname, "--", cmd.description];
+                        cmds.push(msg.join(" "));
+                    }
+                }
+            });
+            yuu.log("messages", cmds.join("\n"));
+        }, "<term?>", "display available commands (matching the term)"),
+
+        echo: yuu.cmd(function () {
+            yuu.log("messages", arguments);
+        }, "...", "echo arguments to the console"),
+        
+        log: yuu.cmd(function (name, state) {
+            if (state !== undefined)
+                yuu.log[name] = !!state;
+            return yuu.log[name];
+        }, "<category> <boolean?>", "enable/disable a logging category")
+
+    });
+
+    yuu.defaultCommands.showDevTools = yuu.cmd(function () {
+        if (gui)
+            gui.Window.get().showDevTools();
+    }, "show developer tools");
+
+    yuu.anchorPoint = function (anchor, x0, y0, x1, y1) {
+        /** Calculate the anchor point for a box given extents and anchor mode
+
+            This function is the inverse of yuu.bottomLeft.
+        */
+        switch (anchor) {
+        case "center": return [(x0 + x1) / 2, (y0 + y1) / 2];
+        case "top": return [(x0 + x1) / 2, y1];
+        case "bottom": return [(x0 + x1) / 2, y0];
+        case "left": return [x0, (y0 + y1) / 2];
+        case "right": return [x1, (y0 + y1) / 2];
+
+        case "topleft": return [x0, y1];
+        case "topright": return [x1, y1];
+        case "bottomleft": return [x0, y0];
+        case "bottomright": return [x0, y0];
+        default: return [anchor[0], anchor[1]];
+        }
+    };
+
+    yuu.bottomLeft = function (anchor, x, y, w, h) {
+        /** Calculate the bottom-left for a box given size and anchor mode
+
+            This function is the inverse of yuu.anchorPoint.
+        */
+        switch (anchor) {
+        case "center": return [x - w / 2, y - h / 2];
+        case "top": return [x - w / 2, y - h];
+        case "bottom": return [x - w / 2, y];
+        case "left": return [x, y - h / 2];
+        case "right": return [x - w, y - h / 2];
+
+        case "topleft": return [x, y - h];
+        case "topright": return [x - w, y - h];
+        case "bottomleft": return [x, y];
+        case "bottomright": return [x - w, y];
+        default: return [anchor[0], anchor[1]];
+        }
+    };
+
+    yuu.lerp = function (a, b, p) {
+        return (a !== null && a !== undefined && a.lerp)
+            ? a.lerp(b, p) : (b !== null && b !== undefined && b.lerp)
+            ? b.lerp(a, 1 - p) : p < 0.5 ? a : b;
+    };
+
+    yuu.bilerp = function (x0y0, x1y0, x0y1, x1y1, px, py) {
+        /** Bilinearly interpolate between four values in two dimensions */
+        return yuu.lerp(yuu.lerp(x0y0, x1y0, px), yuu.lerp(x0y1, x1y1, px), py);
+    };
+
+    function resolvePropertyPath (object, path) {
+        /** Look up a full property path
+
+            If a null is encountered in the path, this function returns
+            null. If undefined is encountered or a property is missing, it
+            returns undefined.
+        */
+        var parts = path.replace(/\[(\w+)\]/g, '.$1').split('.');
+        for (var i = 0;
+             i < parts.length && object !== undefined && object !== null;
+             ++i) {
+            object = object[parts[i]];
+        }
+        return object;
+    }
+
+    yuu.Random = yT({
+        /** Somewhat like Python's random.Random.
+
+            Passed a function that returns a uniform random variable in
+            [0, 1) it can do other useful randomization algorithms.
+
+            Its methods are implemented straightforwardly rather than
+            rigorously - this means they may not behave correctly in
+            common edge cases like precision loss.
+        */
+        constructor: function (generator) {
+            this.random = generator || Math.random;
+            this._spareGauss = null;
+        },
+
+        choice: function (seq) {
+            /** Return a random element from the provided array. */
+            return seq[this.randrange(0, seq.length)];
+        },
+
+        randrange: yf.argcd(
+            function (a) {
+                /** Return a uniform random integer in [0, a). */
+                return (this.random() * a) | 0;
+            },
+
+            function (a, b) {
+                /** Return a uniform random integer in [a, b). */
+                a = a | 0;
+                b = b | 0;
+                return a + ((this.random() * (b - a)) | 0);
+            },
+
+            function (a, b, step) {
+                /** Return a uniform random number in [a, b).
+
+                    The number is constrained to values of a + i * step
+                    where i is a non-negative integer.
+                */
+                var i = Math.ceil((b - a) / step);
+                return a + this.randrange(i) * step;
+            }
+        ),
+
+        uniform: yf.argcd(
+            function (a) {
+                /** Return a uniform random variable in [0, a). */
+                return a * this.random();
+            },
+            function (a, b) {
+                /** Return a uniform random variable in [a, b). */
+                return a + (b - a) * this.random();
+            }
+        ),
+
+        gauss: function (mean, sigma) {
+            var u = this._spareGauss, v, s;
+            this._spareGauss = null;
+
+            if (u === null) {
+                do {
+                    u = this.uniform(-1, 1);
+                    v = this.uniform(-1, 1);
+                    s = u * u + v * v;
+                } while (s >= 1.0 || s === 0.0);
+                var t = Math.sqrt(-2.0 * Math.log(s) / s);
+                this._spareGauss = v * t;
+                u *= t;
+            }
+            return mean + sigma * u;
+        },
+
+        randbool: yf.argcd(
+            /** Return true the given percent of the time (default 50%). */
+            function () { return this.random() < 0.5; },
+            function (a) { return this.random() < a; }
+        ),
+
+        randsign: function (v) {
+            return this.randbool() ? v : -v;
+        },
+
+        shuffle: function (seq) {
+            for (var i = seq.length - 1; i > 0; --i) {
+                var index = this.randrange(i + 1);
+                var temp = seq[i];
+                seq[i] = seq[index];
+                seq[index] = temp;
+            }
+            return seq;
+        },
+
+        discard: function (z) {
+            z = z | 0;
+            while (z-- > 0)
+                this.random();
+        }
+    });
+
+    yuu.createLCG = yf.argcd(
+        /** Linear congruential random generator
+
+            This returns a function that generates numbers [0, 1) as
+            with Math.random. You can also read or assign the `state`
+            attribute to set the internal state.
+        */
+        function () { return yuu.createLCG(Math.random() * 2147483647); },
+        function (seed) {
+            var state = seed | 0;
+            return function generator () {
+                state = (state * 1664525 + 1013904223) % 4294967296;
+                return state / 4294967296;
+            };
+        }
+    );
+
+    yuu.random = new yuu.Random();
+
+    function defaultKey (args) {
+        // Cache things that can be constructed with one string.
+        return args.length === 1 && yf.isString(args[0]) ? args[0] : null;
+    }
+
+    yuu.Caching = function (Type, cacheKey) {
+        function ctor () {
+            var k = ctor._cacheKey(arguments);
+            var o = k && ctor._cache[k];
+            if (!o)
+                o = ctor._cache[k] = yf.construct(ctor.Uncached, arguments);
+            return o;
+        }
+        ctor._cacheKey = cacheKey || defaultKey;
+        ctor._cache = {};
+        ctor.Uncached = Type;
+        return ctor;
+    };
+
+    yuu.transpose2d = function (a) {
+        for (var x = 0; x < a.length; ++x) {
+            for (var y = 0; y < x; ++y) {
+                var t = a[x][y];
+                a[x][y] = a[y][x];
+                a[y][x] = t;
+            }
+        }
+    };
+
+    yuu.normalizeRadians = function (theta) {
+        var PI = Math.PI;
+        return (theta + 3 * PI) % (2 * PI) - PI;
+    };
+
+    yuu.radians = function (v) {
+        return v * (Math.PI / 180.0);
+    };
+
+    yuu.degrees = function (v) {
+        return v * (180.0 / Math.PI);
+    };
+
+    var SHORT = /(\/|^)@(.+)$/;
+    yuu.resourcePath = function (path, category, ext) {
+        var match;
+        if ((match = path.match(SHORT))) {
+            path = path.replace(/^yuu\/@/, yuu.PATH + "@")
+                .replace(SHORT, "$1data/" + category + "/$2");
+            if (match[2].indexOf(".") === -1)
+                path += "." + ext;
+        }
+        return path;
+    };
+
+    yuu.ready = function (resources, result) {
+        return Promise.all(yf.filter(null, yf.pluck("ready", resources)))
+            .then(yf.K(result));
+    };
+
+    yuu.openURL = function (url) {
+        if (gui && gui.Shell)
+            gui.Shell.openExternal(url);
+        else
+            window.open(url);
+    };
+
+    function crossPlatformFilename (basename) {
+        return basename
+            // Replace D/M/Y with D-M-Y, and H:M:S with H.M.S.
+            .replace(/\//g, "-").replace(/:/g, ".")
+            // Replace all other problematic characters with _.
+            .replace(/["<>*?|\\]/g, "_");
+    }
+
+    yuu.downloadURL = function (url, suggestedName) {
+        var regex = /^data:[^;+]+;base64,(.*)$/;
+        var matches = url.match(regex);
+        suggestedName = crossPlatformFilename(suggestedName);
+        if (matches && fs) {
+            var data = matches[1];
+            var buffer = new Buffer(data, 'base64');
+            var HOME = process.env.HOME
+                || process.env.HOMEPATH
+                || process.env.USERPROFILE;
+            var filename = HOME + "/" + suggestedName;
+            console.log("Saving to", filename);
+            fs.writeFileSync(filename, buffer);
+        } else {
+            var link = document.createElement('a');
+            link.style.display = "none";
+            link.href = url;
+            link.download = suggestedName;
+            // Firefox (as of 28) won't download from a link not rooted in
+            // the document; so, root it and then remove it when done.
+            document.body.appendChild(link);
+            link.click();
+            document.body.removeChild(link);
+        }
+    };
+
+    yuu.AABB = yT({
+        constructor: yf.argcd(
+            function () { this.constructor(0, 0, 0, 0); },
+            function (w, h) { this.constructor(0, 0, w, h); },
+            function (x0, y0, x1, y1) {
+                this.x0 = x0;
+                this.y0 = y0;
+                this.x1 = x1;
+                this.y1 = y1;
+            }
+        ),
+
+        w: {
+            get: function () { return this.x1 - this.x0; },
+            set: function (w) { this.x1 = this.x0 + w; }
+        },
+
+        h: {
+            get: function () { return this.y1 - this.y0; },
+            set: function (h) { this.y1 = this.y0 + h; }
+        },
+
+        size: { swizzle: "wh" },
+
+        contains: yf.argcd(
+            function (p) { return this.contains(p.x, p.y); },
+            function (x, y) {
+                return x >= this.x0 && x < this.x1
+                    && y >= this.y0 && y < this.y1;
+            }
+        ),
+
+        matchAspectRatio: function (outer) {
+            var matched = new this.constructor(
+                this.x0, this.y0, this.x1, this.y1);
+            var aRatio = matched.w / matched.h;
+            var bRatio = outer.w / outer.h;
+            if (aRatio > bRatio) {
+                // too wide, must be taller
+                var h = matched.w / bRatio;
+                var dh = h - matched.h;
+                matched.y0 -= dh / 2;
+                matched.y1 += dh / 2;
+            } else {
+                // too tall, must be wider
+                var w = matched.h * bRatio;
+                var dw = w - matched.w;
+                matched.x0 -= dw / 2;
+                matched.x1 += dw / 2;
+            }
+            return matched;
+        },
+
+        alignedInside: function (outer, alignment) {
+            var x0, y0;
+            switch (alignment) {
+            case "bottomleft":
+                x0 = outer.x0;
+                y0 = outer.y0;
+                break;
+            case "bottom":
+                x0 = outer.x0 + (outer.w - this.w) / 2;
+                y0 = outer.y0;
+                break;
+            case "bottomright":
+                x0 = outer.x0 - this.w;
+                y0 = outer.y0;
+                break;
+            case "left":
+                x0 = outer.x0;
+                y0 = outer.x0 + (outer.h - this.h) / 2;
+                break;
+            case "center":
+                x0 = outer.x0 + (outer.w - this.w) / 2;
+                y0 = outer.x0 + (outer.h - this.h) / 2;
+                break;
+            case "right":
+                x0 = outer.x1 - this.w;
+                y0 = outer.x0 + (outer.h - this.h) / 2;
+                break;
+            case "topleft":
+                x0 = outer.x0;
+                y0 = outer.y1 - this.h;
+                break;
+            case "top":
+                x0 = outer.x0 + (outer.w - this.w) / 2;
+                y0 = outer.y1 - this.h;
+                break;
+            case "topright":
+                x0 = outer.x1 - this.w;
+                y0 = outer.y1 - this.h;
+                break;
+            }
+            return new this.constructor(x0, y0, x0 + this.w, y0 + this.h);
+        }
+    });
+
+    function splitPathExtension (path) {
+        var dot = path.lastIndexOf(".");
+        if (dot <= 0) return [path, ""];
+
+        var dir = path.lastIndexOf("/");
+        if (dot < dir) return [path, ""];
+
+        return [path.substring(0, dot), path.substring(dot)];
+    }
+    yuu.splitPathExtension = splitPathExtension;
+
+    if (stringLerp) {
+        yT.defineProperty(String.prototype, "lerp", function (b, p) {
+            b = b.toString();
+            // Never numericLerp - if that's desired force Numbers.
+            // Be more conservative than stringLerp since this runs
+            // often and the diff can't be easily hoisted.
+            return this.length * b.length > 256
+                ? stringLerp.fastLerp(this, b, p)
+                : stringLerp.diffLerp(this, b, p);
+        });
+    }
+
+    yT.defineProperties(Number.prototype, {
+        lerp: function (b, p) { return this + (b - this) * p; }
+    });
+
+    yT.defineProperties(Array.prototype, {
+        lerp: function (b, p) {
+            var length = Math.round(this.length.lerp(b.length, p));
+            var c = new this.constructor(length);
+            for (var i = 0; i < length; ++i) {
+                if (i >= this.length)
+                    c[i] = b[i];
+                else if (i >= b.length)
+                    c[i] = this[i];
+                else
+                    c[i] = this[i].lerp(b[i], p);
+            }
+            return c;
+        }
+    });
+
+    /** Typed array extensions
+
+        https://www.khronos.org/registry/typedarray/specs/1.0/
+        BUT: Read on for fun times in browser land~
+
+        Ideally we could just set these once on ArrayBufferView, but
+        the typed array specification doesn't require that such a
+        constructor actually exist. And in Firefox (18), it doesn't.
+        
+        More infurating, in Safari (7.0.3) Int8Array etc. are not
+        functions so this needs to be added to the prototype
+        directly. This is a violation of the specification which
+        requires such constructors, and ECMA which requires
+        constructors be functions, and common decency.
+    */
+
+    [ Float32Array, Float64Array, Int8Array, Uint8Array,
+      Int16Array, Uint16Array, Int32Array, Uint32Array
+    ].forEach(function (A) {
+        yT.defineProperties(A.prototype, {
+            slice: yf.argcd(
+                /** Like Array's slice, but for typed arrays */
+                function () { return new this.constructor(this); },
+                function (begin) {
+                    return new this.constructor(this.subarray(begin));
+                },
+                function (begin, end) {
+                    return new this.constructor(this.subarray(begin, end));
+                }
+            ),
+
+            fill: Array.prototype.fill,
+            reverse: Array.prototype.reverse,
+
+            lerp: function (b, p) {
+                if (p === 0)
+                    return this.slice();
+                else if (p === 1)
+                    return b.slice();
+                var c = new this.constructor(this.length);
+                for (var i = 0; i < this.length; ++i)
+                    c[i] = this[i] + (b[i] - this[i]) * p;
+                return c;
+            }
+        });
+    });
+
+}).call(typeof exports === "undefined" ? this : exports,
+        typeof exports === "undefined" ? (this.yuu = {}) : exports);
diff --git a/src/yuu/data/license.txt b/src/yuu/data/license.txt
new file mode 100644 (file)
index 0000000..6c2b4e7
--- /dev/null
@@ -0,0 +1,507 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
+-- 
+
+glMatrix - http://glmatrix.net/
+
+Copyright 2013 Brandon Jones, Colin MacKenzie IV
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must
+     not claim that you wrote the original software. If you use this
+     software in a product, an acknowledgment in the product
+     documentation would be appreciated but is not required.
+
+  2. Altered source versions must be plainly marked as such, and must
+     not be misrepresented as being the original software.
+
+  3. This notice may not be removed or altered from any source
+     distribution.
+
+-- 
+     
+Hammer.js - http://eightmedia.github.io/hammer.js/
+
+Copyright 2011-2014 by Jorik Tangelder (Eight Media)
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-- 
+
+Fonts
+
+Copyright (c) 2014, Mozilla Foundation https://mozilla.org/
+Copyright (c) 2014, Telefonica S.A.
+with Reserved Font Name Fira Sans.
+
+Copyright (c) 2014, Mozilla Foundation https://mozilla.org/
+Copyright (c) 2014, Telefonica S.A.
+with Reserved Font Name Fira Mono.
+
+Copyright (c) 2014, Dave Gandy http://fontawesome.io/
+with Reserved Font Name Font Awesome
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
+
+-- 
+
+This program may be distributed along with node-webkit, a wrapper for
+packaging web applications for standalone use. If so, the accompanying
+`node-webkit credits.html' contains its licensing information.
+
+This program is not a derivative work of node-webkit but rather "mere
+aggregation." You do not need to account for node-webkit's licensing
+terms to modify and/or redistribute parts of this program unless you
+also modify and/or redistribute node-webkit.
diff --git a/src/yuu/data/shaders/color.frag b/src/yuu/data/shaders/color.frag
new file mode 100644 (file)
index 0000000..3d6f67e
--- /dev/null
@@ -0,0 +1,12 @@
+/* This is free and unencumbered software released into the public
+   domain. To the extent possible under law, the author of this file
+   waives all copyright and related or neighboring rights to it.
+*/
+
+precision mediump float;
+
+varying vec4 fColor;
+
+void main(void) {
+    gl_FragColor = fColor;
+}
diff --git a/src/yuu/data/shaders/default.frag b/src/yuu/data/shaders/default.frag
new file mode 100644 (file)
index 0000000..712aa80
--- /dev/null
@@ -0,0 +1,15 @@
+/* This is free and unencumbered software released into the public
+   domain. To the extent possible under law, the author of this file
+   waives all copyright and related or neighboring rights to it.
+*/
+
+precision mediump float;
+
+varying vec2 fTexCoord;
+varying vec4 fColor;
+uniform sampler2D tex;
+
+void main(void) {
+    gl_FragColor = vec4(fColor.rgb * fColor.a, fColor.a)
+        * texture2D(tex, fTexCoord);
+}
diff --git a/src/yuu/data/shaders/default.vert b/src/yuu/data/shaders/default.vert
new file mode 100644 (file)
index 0000000..5a21e7d
--- /dev/null
@@ -0,0 +1,22 @@
+/* This is free and unencumbered software released into the public
+   domain. To the extent possible under law, the author of this file
+   waives all copyright and related or neighboring rights to it.
+*/
+
+precision mediump float;
+
+attribute vec3 position;
+attribute vec2 texCoord;
+attribute vec4 color;
+
+uniform mat4 model;
+uniform mat4 view;
+uniform mat4 projection;
+varying vec2 fTexCoord;
+varying vec4 fColor;
+
+void main(void) {
+    gl_Position = projection * view * model * vec4(position, 1.0);
+    fTexCoord = texCoord;
+    fColor = color;
+}
diff --git a/src/yuu/data/yuu.css b/src/yuu/data/yuu.css
new file mode 100644 (file)
index 0000000..86b933f
--- /dev/null
@@ -0,0 +1,369 @@
+/* Copyright 2014 Yukkuri Games
+   Licensed under the terms of the GNU GPL v2 or later
+   @license http://www.gnu.org/licenses/gpl-2.0.html
+   @source: http://yukkurigames.com/yuu/
+*/
+
+@font-face {
+  font-family: 'FontAwesome';
+  src: url('../../ext/font-awesome.woff') format('woff');
+  font-weight: normal;
+  font-style: normal;
+}
+
+@font-face {
+    font-family: 'Fira Sans';
+    src: url('../../ext/FiraSans-UltraLight.woff');
+    font-weight: 200;
+    font-style: normal;
+}
+
+@font-face {
+    font-family: 'Fira Sans';
+    src: url('../../ext/FiraSans-UltraLightItalic.woff');
+    font-weight: 200;
+    font-style: italic;
+}
+
+@font-face {
+    font-family: 'Fira Sans';
+    src: url('../../ext/FiraSans-Regular.woff');
+    font-weight: 400;
+    font-style: normal;
+}
+
+@font-face {
+    font-family: 'Fira Sans';
+    src: url('../../ext/FiraSans-Italic.woff');
+    font-weight: 400;
+    font-style: italic;
+}
+
+@font-face {
+    font-family: 'Fira Sans';
+    src: url('../../ext/FiraSans-Bold.woff');
+    font-weight: 700;
+    font-style: normal;
+}
+
+@font-face {
+    font-family: 'Fira Sans';
+    src: url('../../ext/FiraSans-BoldItalic.woff');
+    font-weight: 700;
+    font-style: italic;
+}
+
+@font-face {
+    font-family: 'Fira Mono';
+    src: url('../../ext/FiraMono-Regular.woff');
+    font-weight: 400;
+