--- /dev/null
+.gitattributes export-ignore
+.gitignore export-ignore
--- /dev/null
+*~
+\#*
+.DS_Store
+node_modules
+build
+node-webkit
+
--- /dev/null
+-/node_modules
+-/node-webkit
--- /dev/null
+#!/usr/bin/make -f
+
+node-webkit-version := 0.10.2
+node-webkit-version-win-ia32.zip := 0.8.6
+
+.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 := pwl6
+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
+ cd $@.tmp && $(ZIP) -r ../$(@F) .
+ $(RM) -r $@.tmp
--- /dev/null
+# Yuu - a WebGL game library
+
+Software developers do their best now and are preparing. Please wait
+warmly until it is ready.
+
+* * *
+
+Here are some reasons to avoid this library for now:
+
+* There's no real documentation. There's no API stability promises.
+* No one has actually made a game in it.
+* There's no draw batching. Each quad is its own draw call.
+* In fact, there's been no optimization in general.
+* Before I wrote this I'd never once used the JavaScript `new` keyword.
--- /dev/null
+# 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.")
--- /dev/null
+# 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 $<,$^) $@
+
--- /dev/null
+# 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))
--- /dev/null
+# 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 = $(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
+ 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)
+ $(call node-webkit-package-win,$<,$(word 2,$^))
--- /dev/null
+# 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 $@
--- /dev/null
+# 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
--- /dev/null
+icons.ico
+icons.icns
+book.png
+circle.png
+
--- /dev/null
+Description : Array and textureless GLSL 2D/3D/4D simplex
+ noise functions.
+ Author : Ian McEwan, Ashima Arts.
+ Maintainer : ijm
+ Lastmod : 20110822 (ijm)
+ License : Copyright (C) 2011 Ashima Arts. All rights reserved.
+ Distributed under the MIT License. See LICENSE file.
+ https:github.com/ashima/webgl-noise
+
+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.
--- /dev/null
+//
+// Description : Array and textureless GLSL 2D/3D/4D simplex
+// noise functions.
+// Author : Ian McEwan, Ashima Arts.
+// Maintainer : ijm
+// Lastmod : 20110822 (ijm)
+// License : Copyright (C) 2011 Ashima Arts. All rights reserved.
+// Distributed under the MIT License. See LICENSE file.
+// https://github.com/ashima/webgl-noise
+//
+/*
+ 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.
+*/
+
+precision highp float;
+
+vec2 mod289(vec2 x) {
+ return x - floor(x * (1.0 / 289.0)) * 289.0;
+}
+
+vec3 mod289(vec3 x) {
+ return x - floor(x * (1.0 / 289.0)) * 289.0;
+}
+
+vec4 mod289(vec4 x) {
+ return x - floor(x * (1.0 / 289.0)) * 289.0;
+}
+
+vec3 permute(vec3 x) {
+ return mod289(((x*34.0)+1.0)*x);
+}
+
+vec4 permute(vec4 x) {
+ return mod289(((x*34.0)+1.0)*x);
+}
+
+vec4 taylorInvSqrt(vec4 r)
+{
+ return 1.79284291400159 - 0.85373472095314 * r;
+}
+
+float snoise(vec3 v)
+ {
+ const vec2 C = vec2(1.0/6.0, 1.0/3.0) ;
+ const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
+
+// First corner
+ vec3 i = floor(v + dot(v, C.yyy) );
+ vec3 x0 = v - i + dot(i, C.xxx) ;
+
+// Other corners
+ vec3 g = step(x0.yzx, x0.xyz);
+ vec3 l = 1.0 - g;
+ vec3 i1 = min( g.xyz, l.zxy );
+ vec3 i2 = max( g.xyz, l.zxy );
+
+ // x0 = x0 - 0.0 + 0.0 * C.xxx;
+ // x1 = x0 - i1 + 1.0 * C.xxx;
+ // x2 = x0 - i2 + 2.0 * C.xxx;
+ // x3 = x0 - 1.0 + 3.0 * C.xxx;
+ vec3 x1 = x0 - i1 + C.xxx;
+ vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
+ vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y
+
+// Permutations
+ i = mod289(i);
+ vec4 p = permute( permute( permute(
+ i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
+ + i.y + vec4(0.0, i1.y, i2.y, 1.0 ))
+ + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
+
+// Gradients: 7x7 points over a square, mapped onto an octahedron.
+// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
+ float n_ = 0.142857142857; // 1.0/7.0
+ vec3 ns = n_ * D.wyz - D.xzx;
+
+ vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7)
+
+ vec4 x_ = floor(j * ns.z);
+ vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N)
+
+ vec4 x = x_ *ns.x + ns.yyyy;
+ vec4 y = y_ *ns.x + ns.yyyy;
+ vec4 h = 1.0 - abs(x) - abs(y);
+
+ vec4 b0 = vec4( x.xy, y.xy );
+ vec4 b1 = vec4( x.zw, y.zw );
+
+ //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
+ //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
+ vec4 s0 = floor(b0)*2.0 + 1.0;
+ vec4 s1 = floor(b1)*2.0 + 1.0;
+ vec4 sh = -step(h, vec4(0.0));
+
+ vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
+ vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;
+
+ vec3 p0 = vec3(a0.xy,h.x);
+ vec3 p1 = vec3(a0.zw,h.y);
+ vec3 p2 = vec3(a1.xy,h.z);
+ vec3 p3 = vec3(a1.zw,h.w);
+
+//Normalise gradients
+ vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
+ p0 *= norm.x;
+ p1 *= norm.y;
+ p2 *= norm.z;
+ p3 *= norm.w;
+
+// Mix final noise value
+ vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
+ m = m * m;
+ return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1),
+ dot(p2,x2), dot(p3,x3) ) );
+ }
+
+float snoise(vec2 v)
+ {
+ const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0
+ 0.366025403784439, // 0.5*(sqrt(3.0)-1.0)
+ -0.577350269189626, // -1.0 + 2.0 * C.x
+ 0.024390243902439); // 1.0 / 41.0
+// First corner
+ vec2 i = floor(v + dot(v, C.yy) );
+ vec2 x0 = v - i + dot(i, C.xx);
+
+// Other corners
+ vec2 i1;
+ //i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0
+ //i1.y = 1.0 - i1.x;
+ i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
+ // x0 = x0 - 0.0 + 0.0 * C.xx ;
+ // x1 = x0 - i1 + 1.0 * C.xx ;
+ // x2 = x0 - 1.0 + 2.0 * C.xx ;
+ vec4 x12 = x0.xyxy + C.xxzz;
+ x12.xy -= i1;
+
+// Permutations
+ i = mod289(i); // Avoid truncation effects in permutation
+ vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
++ i.x + vec3(0.0, i1.x, 1.0 ));
+
+ vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
+ m = m*m ;
+ m = m*m ;
+
+// Gradients: 41 points uniformly over a line, mapped onto a diamond.
+// The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287)
+
+ vec3 x = 2.0 * fract(p * C.www) - 1.0;
+ vec3 h = abs(x) - 0.5;
+ vec3 ox = floor(x + 0.5);
+ vec3 a0 = x - ox;
+
+// Normalise gradients implicitly by scaling m
+// Approximation of: m *= inversesqrt( a0*a0 + h*h );
+ m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
+
+// Compute final noise value at P
+ vec3 g;
+ g.x = a0.x * x0.x + h.x * x0.y;
+ g.yz = a0.yz * x12.xz + h.yz * x12.yw;
+ return 130.0 * dot(m, g);
+}
--- /dev/null
+/* 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 highp float;
+
+varying vec2 fTexCoord;
+varying vec4 fColor;
+uniform sampler2D tex;
+
+uniform vec2 resolution;
+uniform float cut;
+uniform float range;
+
+void main(void) {
+ vec2 coord = floor(fTexCoord * resolution);
+ vec3 n = vec3(coord.xy, cut);
+ float p = 1.0 - range * abs(snoise(n));
+ vec3 modulated = fColor.rgb * p;
+ vec4 texColor = texture2D(tex, fTexCoord);
+ gl_FragColor = vec4(modulated * fColor.a, fColor.a) * texColor;
+}
--- /dev/null
+/* 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;
+
+uniform float cut;
+uniform float range;
+
+varying vec4 fColor;
+
+void main(void) {
+ gl_Position = projection * view * model * vec4(position, 1.0);
+ vec2 n = vec2(texCoord.x, cut);
+ float p = 1.0 - range * abs(snoise(n));
+ fColor = vec4(p * color.rgb, color.a);
+}
--- /dev/null
+(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);
+
+})();
--- /dev/null
+/**
+ * @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);
--- /dev/null
+/*! 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
--- /dev/null
+/* string-lerp - progressively turn one string into another
+ Copyright 2014 Joe Wreschnig
+ 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/string-lerp/
+*//*
+ 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.
+
+ As additional permission, you may distribute the program or works
+ based on it without the copy of the GNU GPL normally required,
+ provided you include this license notice and a URL through which
+ recipients can access the corresponding source code.
+*/
+
+/*globals exports, Uint32Array */
+
+(function (exports) {
+ "use strict";
+
+ var MAX_MATRIX_SIZE = 256 * 256;
+
+ function costMatrix(source, target, ins, del, sub) {
+ /** Calculate the Levenshtein cost matrix for source and target
+
+ If source and target are strings, they cannot contain any
+ astral or combining codepoints. Such data must be passed
+ as arrays of strings with one element per glyph.
+
+ ins, del, and sub are the costs for insertion, deletion,
+ and substition respectively. Their default value is 1. If
+ only ins is passed, del and sub are set to the same cost.
+ If ins and del are passed, sub is set to the more
+ expensive of the two.
+
+ The matrix is returned as a flat typed array.
+
+ Following http://en.wikipedia.org/wiki/Levenshtein_distance
+ */
+ ins = ins === undefined ? 1 : (ins | 0);
+ del = (del | 0) || ins;
+ sub = (sub | 0) || Math.max(ins, del);
+ var m = source.length + 1;
+ var n = target.length + 1;
+ var d = new Uint32Array(m * n);
+ var i, j;
+ for (i = 1; i < m; ++i)
+ d[n * i] = i;
+ for (j = 1; j < n; ++j)
+ d[j] = j;
+ for (j = 1; j < n; ++j)
+ for (i = 1; i < m; ++i)
+ if (source[i - 1] === target[j - 1])
+ d[n * i + j] = d[n * (i - 1) + j - 1];
+ else
+ d[n * i + j] = Math.min(del + d[n * (i - 1) + j ],
+ ins + d[n * i + j - 1],
+ sub + d[n * (i - 1) + j - 1]);
+ return d;
+ }
+
+ // First, note that deletion is just substition with nothing, so
+ // any DEL operation can be replaced by a SUB. Second, the
+ // operation code *is* the necessary slice offset for applying the
+ // diff.
+ var INS = 0, SUB = 1;
+
+ function editPath(costs, target) {
+ /** Given a cost matrix and a target, create an edit list */
+ var path = [];
+ var j = target.length;
+ var n = j + 1;
+ var i = costs.length / n - 1;
+ while (i || j) {
+ var sub = (i && j) ? costs[n * (i - 1) + j - 1] : Infinity;
+ var del = i ? costs[n * (i - 1) + j] : Infinity;
+ var ins = j ? costs[n * i + j - 1] : Infinity;
+ if (sub <= ins && sub <= del) {
+ if (costs[n * i + j] !== costs[n * (i - 1) + j - 1])
+ path.push([SUB, i - 1, target[j - 1]]);
+ --i; --j;
+ } else if (ins <= del) {
+ path.push([INS, i, target[j - 1]]);
+ --j;
+ } else {
+ path.push([SUB, i - 1, ""]);
+ --i;
+ }
+ }
+ return path;
+ }
+
+ function diff(source, target, ins, del, sub) {
+ /** Create a list of edits to turn source into target
+
+ ins, del, and sub are as passed to costMatrix.
+ */
+ return editPath(costMatrix(source, target, ins, del, sub), target);
+ }
+
+ function patchArray(diff, source) {
+ for (var i = 0; i < diff.length; ++i) {
+ var edit = diff[i];
+ source.splice(edit[1], edit[0], edit[2]);
+ }
+ return source;
+ }
+
+ function patchString(diff, source) {
+ for (var i = 0; i < diff.length; ++i) {
+ var edit = diff[i];
+ var head = source.slice(0, edit[1]);
+ var tail = source.slice(edit[1] + edit[0]);
+ source = head + edit[2] + tail;
+ }
+ return source;
+ }
+
+ function patch(diff, source) {
+ /** Apply a list of edits to source */
+ var patcher = Array.isArray(source) ? patchArray : patchString;
+ return patcher(diff, source);
+ }
+
+ // Matches if a string contains combining characters or astral
+ // codepoints (technically, the first half surrogate of an astral
+ // codepoint).
+ var MULTI = /[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uD800-\uDBFF\uFE20-\uFE2F]/;
+
+ // Match an entire (potentially astral) codepoint and any
+ // combining characters following it.
+ var GLYPH = /[\0-\u02FF\u0370-\u1DBF\u1E00-\u20CF\u2100-\uD7FF\uD800-\uFE1F\uFE30-\uFFFF][\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uDC00-\uDFFF\uFE20-\uFE2F]*/g;
+
+ function diffLerpAstral(source, target, amount) {
+ // This split is not perfect for all languages, but at least
+ // it won't create invalid surrogate pairs or orphaned
+ // combining characters.
+ var sourceGlyphs = source.match(GLYPH) || [];
+ var targetGlyphs = target.match(GLYPH) || [];
+ var edits = diff(targetGlyphs, sourceGlyphs, 2, 2, 3);
+ // The edit path works from the string end, forwards, because
+ // that's how Levenshtein edits work. To match LTR reading
+ // direction (and the behavior of fastLerp), swap the strings
+ // and invert the parameter when editing.
+ var partial = edits.slice(0, Math.round((1 - amount) * edits.length));
+ return patchArray(partial, targetGlyphs).join("");
+ }
+
+ function diffLerpBasic(source, target, amount) {
+ var edits = diff(target, source, 2, 2, 3);
+ // The edit path works from the string end, forwards, because
+ // that's how Levenshtein edits work. To match LTR reading
+ // direction (and the behavior of fastLerp), swap the strings
+ // and invert the parameter when editing.
+ var partial = edits.slice(0, Math.round((1 - amount) * edits.length));
+ return patchString(partial, target);
+ }
+
+ function diffLerp(source, target, amount) {
+ /** Interpolate between two strings using edit operations
+
+ This interpolation algorithm applys a partial edit of one
+ string into the other. This produces nice looking results,
+ but can take a significant amount of time and memory to
+ compute the edits. It is not recommended for strings
+ longer than a few hundred characters.
+ */
+
+ if (source.match(MULTI) || target.match(MULTI))
+ return diffLerpAstral(source, target, amount);
+ else
+ return diffLerpBasic(source, target, amount);
+ }
+
+ var NUMBERS = /(-?\d{1,20}(?:\.\d{1,20})?)/g;
+
+ function areNumericTwins(source, target) {
+ /** Check if a and b differ only in numerals */
+ return source.replace(NUMBERS, "0") === target.replace(NUMBERS, "0");
+ }
+
+ function nlerp(source, target, amount) {
+ return source + (target - source) * amount;
+ }
+
+ function numericLerp(source, target, amount) {
+ /** Interpolate numerically between strings containing numbers
+
+ Numbers may have a leading "-" and a single "." to mark
+ the decimal point, but something must be after the ".".
+ No other floating point syntax (e.g. 1e6) is supported.
+ They are treated as fixed-point values, with the point's
+ position itself interpolating.
+
+ For example, numericLerp("0.0", "100".0, 0.123) === "12.3"
+ because the "." in "0.0" is interpreted as a decimal
+ point. But numericLerp("0.", "100.", 0.123) === "12."
+ because the strings are interpreted as integers followed
+ by a full stop.
+
+ Calling this functions on strings that differ in more than
+ numerals gives undefined results.
+ */
+
+ var targetParts = target.split(NUMBERS);
+ var match;
+ var i = 1;
+ while ((match = NUMBERS.exec(source))) {
+ var sourcePart = match[0];
+ var targetPart = targetParts[i];
+ var part = nlerp(+sourcePart, +targetPart, amount);
+ var sourcePoint = sourcePart.indexOf(".");
+ var targetPoint = targetPart.indexOf(".");
+ var point = Math.round(nlerp(
+ sourcePoint >= 0 ? (sourcePart.length - 1) - sourcePoint : 0,
+ targetPoint >= 0 ? (targetPart.length - 1) - targetPoint : 0,
+ amount));
+ targetParts[i] = part.toFixed(point);
+ i += 2;
+ }
+ return targetParts.join("");
+ }
+
+ function fastLerpAstral(source, target, amount) {
+ var sourceGlyphs = source.match(GLYPH) || [];
+ var targetGlyphs = target.match(GLYPH) || [];
+ var sourceLength = Math.round(sourceGlyphs.length * amount);
+ var targetLength = Math.round(targetGlyphs.length * amount);
+ var head = targetGlyphs.slice(0, targetLength);
+ var tail = sourceGlyphs.slice(sourceLength, sourceGlyphs.length);
+ head.push.apply(head, tail);
+ return head.join("");
+ }
+
+ function fastLerpBasic(source, target, amount) {
+ var sourceLength = Math.round(source.length * amount);
+ var targetLength = Math.round(target.length * amount);
+ var head = target.substring(0, targetLength);
+ var tail = source.substring(sourceLength, source.length);
+ return head + tail;
+ }
+
+ function fastLerp(source, target, amount) {
+ /** Interpolate between two strings based on length
+
+ This interpolation algorithm progressively replaces the
+ front of one string with another. This approach is fast
+ but does not look good when the strings are similar.
+ */
+
+ // TODO: Consider fast-pathing this even more for very large
+ // strings, e.g. in the megabyte range. These are large enough
+ // that it should be fine to just pick a codepoint and search
+ // for the nearest glyph start.
+ if (source.match(MULTI) || target.match(MULTI))
+ return fastLerpAstral(source, target, amount);
+ else
+ return fastLerpBasic(source, target, amount);
+ }
+
+ function lerp(source, target, amount) {
+ /** Interpolate between two strings as best as possible
+
+ If the strings are identical aside from numbers in them,
+ they are passed through numericLerp.
+
+ If the strings are not numbers and short, they are passed
+ through diffLerp.
+
+ Otherwise, they are passed through fastLerp.
+ */
+ source = source.toString();
+ target = target.toString();
+
+ // Fast path for boundary cases.
+ if (amount === 0) return source;
+ if (amount === 1) return target;
+
+ if (areNumericTwins(source, target))
+ return numericLerp(source, target, amount);
+
+ // Numeric lerps should over- and under-shoot when fed numbers
+ // outside 0 to 1, but other types cannot.
+ if (amount < 0) return source;
+ if (amount > 1) return target;
+
+ var n = source.length * target.length;
+ var appropriate = (n && n < MAX_MATRIX_SIZE) ? diffLerp : fastLerp;
+ return appropriate(source, target, amount);
+ }
+
+ exports.costMatrix = costMatrix;
+ exports.patch = patch;
+ exports.diff = diff;
+ exports.fastLerp = fastLerp;
+ exports.diffLerp = diffLerp;
+ exports.numericLerp = numericLerp;
+ exports.lerp = lerp;
+
+})(typeof exports === "undefined" ? (this.stringLerp = {}) : exports);
--- /dev/null
+<!DOCTYPE html>
+<html lang="en"
+ data-appid="com.yukkurigames.pwl6">
+ <head>
+ <title>Pixel Witch Lesson #6</title>
+ <meta charset="utf-8" />
+ <meta name="viewport"
+ content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
+ <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"
+ type="image/vnd.microsoft.icon">
+ <link rel=icon href=data/images/icons.icns
+ sizes="16x16 32x32 64x64 128x128 256x256"
+ >
+ <link rel="stylesheet" href="yuu/data/yuu.css" type="text/css" />
+ <link rel="stylesheet" href="pwl6.css" type="text/css" />
+ <script type="text/javascript" src="ext/gl-matrix.js"></script>
+ <script type="text/javascript" src="ext/gamepad.js"></script>
+ <script type="text/javascript" src="ext/hammer.js"></script>
+ <script type="text/javascript" src="ext/string-lerp.js"></script>
+ <script type="text/javascript" src="yuu/pre.js"></script>
+ <script type="text/javascript" src="yuu/yf.js"></script>
+ <script type="text/javascript" src="yuu/yT.js"></script>
+ <script type="text/javascript" src="yuu/core.js"></script>
+ <script type="text/javascript" src="yuu/input.js"></script>
+ <script type="text/javascript" src="yuu/ce.js"></script>
+ <script type="text/javascript" src="yuu/gfx.js"></script>
+ <script type="text/javascript" src="yuu/rdr.js"></script>
+ <script type="text/javascript" src="yuu/audio.js"></script>
+ <script type="text/javascript" src="yuu/director.js"></script>
+ <script type="text/javascript" src="yuu/storage.js"></script>
+ <script type="text/javascript" src="pwl6.js"></script>
+ </head>
+
+ <body>
+ <canvas id="yuu-canvas" data-yuu-resize>
+ </canvas>
+ <div id="yuu-error" class="yuu-overlay"
+ data-yuu-animation="yuu-from-top"
+ data-yuu-dismiss-key="escape">
+ <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>
+ Supported browsers include recent versions of
+ <a href="http://www.mozilla.org/firefox/">Mozilla Firefox</a>
+ and <a href="http://www.google.com/chrome/">Google Chrome</a>
+ on most desktop computers,
+ <a href="https://www.google.com/intl/en/chrome/browser/mobile/android.html">Chrome
+ for Android</a>, and Safari on Mac OS X 10.7 and later
+ <a href="https://discussions.apple.com/thread/3300585?start=0">if
+ you enable WebGL manually</a>.
+ </p>
+ <h2>Error Log</h2>
+ <pre id="yuu-fatal-error-stack">
+ </pre>
+ </div>
+ <div id="preferences" class="yuu-overlay"
+ data-yuu-animation="yuu-from-top-right"
+ data-yuu-dismiss-key="f10 escape">
+ <div data-yuu-command="dismiss" tabindex=0></div>
+ <h1>Pixel Witch Lesson #6</h1>
+ <table class="yuu-options">
+ <tr>
+ <td>
+ <input type="checkbox" data-yuu-command="mute" id="mute">
+ <label for="mute" title="Toggle audio mute (Control+S)"></label>
+ </td>
+ <td>
+ <label for="volume">Volume</label>
+ </td>
+ <td>
+ <input type="range" data-yuu-command="volume" id="volume"
+ min="0" max="1.0" step="0.05" style="width: 95%">
+ </td>
+ </tr>
+ <tr>
+ <td>
+ </td>
+ <td>
+ <label for="volume">Music</label>
+ </td>
+ <td>
+ <input type="range" data-yuu-command="musicVolume" id="musicVolume"
+ min="0" max="1.0" step="0.05" style="width: 95%">
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input type="checkbox" data-yuu-command="fullscreen"
+ id="fullscreen">
+ <label for="fullscreen" title="Toggle fullscreen (F11)"></label>
+ </td>
+ <td colspan=2>
+ <label for="fullscreen">Fullscreen</label>
+ </td>
+ </tr>
+ </table>
+ <p style="text-align: center">Press F12 to take a screenshot.</p>
+ </div>
+ <div id="colophon" class="yuu-overlay"
+ data-yuu-animation="yuu-from-top"
+ data-yuu-dismiss-key="escape">
+ <div data-yuu-command="dismiss" tabindex=0></div>
+ <h1><a href="http://yukkurigames.com/pwl6/">Pixel Witch Lesson #6</a></h1>
+ <dl>
+ <dt>Designed & Implemented</dt>
+ <dd>Joe Wreschnig</dd>
+ <dt>Additional Programming</dt>
+ <dd>
+ Brandon Jones & Colin MacKenzie IV
+ (<a href="http://glmatrix.net/">glMatrix</a>)
+ </dd>
+ <dd>
+ Christoph Burgmer
+ (<a href="https://github.com/cburgmer/ayepromise">ayepromise</a>)
+ </dd>
+ <dd>
+ Ian McEwan, Ashima Arts
+ (<a href="https://github.com/ashima/webgl-noise">WebGL Noise</a>)
+ </dd>
+ <dd>
+ Jorik Tangelder
+ (<a href="http://eightmedia.github.io/hammer.js/">Hammer.js</a>)
+ </dd>
+ <dt>Fonts</dt>
+ <dd>
+ Carrois Type Design
+ (<a href="http://www.carrois.com/en/fira-3-1/">Fira</a>)
+ </dd>
+ <dd>
+ Dave Gandy
+ (<a href="http://fortawesome.github.io/">Font Awesome</a>)
+ </dd>
+ <dt>Special Thanks</dt>
+ <dd>Amelia Gorman</dd>
+ <dd>Jessicatz Fairymeadow</dd>
+ <dd><a href="http://www.kenney.nl/">Kenney.nl</a></dd>
+ <dd>
+ Richard
+ <a href="http://www.clockguy.com/SiteRelated/SiteReferencePages/ClockChimeTunes.html">"The Clock Guy"</a>
+ Oliver
+ </dd>
+ </dl>
+ <hr>
+ <div style="text-align: center">
+ <p>
+ Copyright ©2014
+ <a href="http://yukkurigames.com/">Yukkuri Games</a>
+ and others
+ </p>
+ <p>
+ 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
+ <a href="http://www.gnu.org/licenses/gpl-2.0.html">version
+ 2 of the License</a>, or (at your option)
+ <a href="https://www.gnu.org/copyleft/gpl.html">any later version</a>.
+ </p>
+ </div>
+ <hr>
+ <p id="yuu-licensing" data-yuu-command="licensing">
+ View Complete Licensing Text
+ </p>
+ </div>
+
+ <noscript>
+ <div class="yuu-overlay" style="display: block">
+ <p>
+ This game requires JavaScript to play. It's a fun game, and
+ we hope you take your time to enable JS and try it.
+ </p>
+ <p>
+ We promise it doesn't contain any remote tracking cookies or
+ bugs, load any external scripts, or do any of the things
+ you've probably disabled JavaScript to avoid.
+ </p>
+ <p>
+ No, we don't even load the things people lie about when they
+ say they aren't tracking you: No Google APIs, no content
+ distribution network proxies, no analytics scripts. Just the
+ game.
+ </p>
+ <hr>
+ <ul class="link-footer">
+ <li><a href="http://yukkurigames.com/pwl6/">Pixel Witch Lesson #6</a>
+ <li><a href="http://yukkurigames.com/">Yukkuri Games</a>
+ </ul>
+ </div>
+ </noscript>
+ </body>
+</html>
--- /dev/null
+body {
+ background-color: black;
+}
+
+.yuu-toast {
+ background-color: hsla(276, 33%, 10%, 1.0);
+ border: solid hsla(276, 33%, 48%, 1.0) 2px;
+ color: hsla(276, 33%, 95%, 1.0);
+}
+
+.yuu-overlay {
+ background-color: hsla(276, 33%, 10%, 0.9);
+ border: solid hsla(276, 33%, 48%, 1.0) 2px;
+ color: hsla(276, 33%, 95%, 1.0);
+}
+
+.yuu-overlay *:focus {
+ box-shadow: 0 0 5px hsla(276, 66%, 80%, 1.0);
+ -moz-box-shadow: 0 0 5px hsla(276, 66%, 80%, 1.0);
+ -webkit-box-shadow: 0 0 5px hsla(276, 66%, 80%, 1.0);
+ outline: none;
+}
+
+input[type=range][data-yuu-command] {
+ background-color: hsla(276, 33%, 25%, 1.0);
+ border: solid hsla(276, 33%, 48%, 0.33) 1px;
+}
+
+input[type=range][data-yuu-command]::-webkit-slider-thumb {
+ background-color: hsla(276, 33%, 65%, 1.0);
+}
+
+input[type=range][data-yuu-command]::-moz-range-track {
+ background-color: hsla(276, 33%, 25%, 1.0);
+}
+
+input[type=range][data-yuu-command]::-moz-range-thumb {
+ background: hsla(276, 33%, 65%, 1.0);
+}
+
+input[type=checkbox][data-yuu-command]:focus + label[for] {
+ box-shadow: 0 0 5px hsla(276, 66%, 80%, 1.0);
+ -moz-box-shadow: 0 0 5px hsla(276, 66%, 80%, 1.0);
+ -webkit-box-shadow: 0 0 5px hsla(276, 66%, 80%, 1.0);
+ outline: 0;
+}
+
+input[data-yuu-command] + label[for]:before {
+ color: hsla(276, 33%, 65%, 1.0);
+}
+
+a[href], [data-yuu-command] {
+ color: hsla(276, 33%, 65%, 1.0);
+}
+
+hr {
+ border: solid hsla(276, 33%, 48%, 0.75) 1px;
+}
+
+.link-footer {
+ list-style-type: none;
+ text-align: center;
+ margin-left: 0;
+ padding-left: 0;
+}
--- /dev/null
+"use strict";
+
+var storage;
+var SIGILS;
+var BOOK;
+var handScene;
+var circleScene;
+var NOISY_BLOCKS;
+
+var sounds;
+
+var TOP = 0;
+var LEFT = 1;
+var BOTTOM = 2;
+var RIGHT = 3;
+
+var MenuScene, CircleScene, HandScene, BookScene, GridScene;
+
+yuu.Texture.DEFAULTS.magFilter = yuu.Texture.DEFAULTS.minFilter = "nearest";
+
+var TICK_ROT = quat.rotateZ(quat.create(), quat.create(), Math.PI / 30);
+var TICK_REV = quat.invert(quat.create(), TICK_ROT);
+var TICK_ROT2 = quat.rotateZ(quat.create(), quat.create(), Math.PI / 60);
+
+function sawtooth (p) {
+ /** Sawtooth wave, Û = 1, T = 2π, f(0) = 0, f′(0) > 0 */
+ var _2PI = 2 * Math.PI;
+ return 2 * (p / _2PI - Math.floor(0.5 + p / _2PI));
+}
+
+function triangle (p) {
+ /** Triangle wave, Û = 1, T = 2π, f(0) = 0, f′(0) > 0 */
+ return 2 * Math.abs(sawtooth(p + Math.PI / 2)) - 1;
+}
+
+function waveshift (period, peak, xoffset, yoffset) {
+ period /= 2 * Math.PI;
+ xoffset = xoffset || 0;
+ yoffset = yoffset || 0;
+ return function (f) {
+ return function (p) {
+ return yoffset + peak * f.call(this, (p + xoffset) / period);
+ };
+ };
+}
+
+function cycler (scale) {
+ var f = waveshift(scale, 0.5, -Date.now(), 0.5)(triangle);
+ return function () { return f(Date.now()); };
+}
+
+function load () {
+ storage = ystorage.getStorage();
+ yuu.audio.storage = storage;
+
+ NOISY_BLOCKS = new yuu.ShaderProgram(null, ["@noise.glsl", "@noisyblocks"]);
+
+ SIGILS = new yuu.Material("@sigils");
+ BOOK = new yuu.Material("@book", NOISY_BLOCKS);
+ BOOK.uniforms.cut = yf.volatile(cycler(20000));
+ BOOK.uniforms.range = 0.06;
+ BOOK.texture.ready.then(function (texture) {
+ BOOK.uniforms.resolution = new Float32Array(
+ [texture.width / 4, texture.height / 4]);
+ });
+
+ sounds = {
+ tick: new yuu.Instrument("@tick"),
+ tock: new yuu.Instrument("@tock"),
+ regear: new yuu.Instrument("@regear"),
+ winding: new yuu.Instrument("@winding"),
+ slam: new yuu.Instrument("@slam"),
+ switch: new yuu.Instrument("@switch"),
+ clicking: new yuu.Instrument("@clicking"),
+ bookAppear: new yuu.Instrument("@book-appear"),
+ switchBroke: new yuu.Instrument({
+ sample: { "@switch": { duration: 0.27, offset: 0.1 } } }),
+ switchOn: new yuu.Instrument({
+ sample: { "@switch": { duration: 0.2 } } }),
+ switchOff: new yuu.Instrument({
+ sample: { "@switch": { offset: 0.2 } } }),
+ chime: new yuu.Instrument({
+ envelope: { "0": 1, "0.7": 0.2, "3": 0 },
+ modulator: {
+ envelope: { "0": 1, "0.7": 0.2, "3": 0 },
+ frequency: "x1.5",
+ }
+ }),
+ };
+
+ yuu.director.pushScene(circleScene = new CircleScene());
+ yuu.director.pushScene(handScene = new HandScene());
+ yuu.director.pushScene(new MenuScene());
+ if (!storage.getFlag("instructions")) {
+ yuu.director.entity0.attach(new yuu.Ticker(function () {
+ yuu.director.pushScene(new BookScene());
+ }, 60));
+ }
+
+ return yuu.ready(
+ [SIGILS, BOOK]
+ .concat(yf.map(yf.getter.bind(sounds), Object.keys(sounds)))
+ );
+}
+
+function start () {
+ yuu.director.start();
+}
+
+window.addEventListener("load", function() {
+ yuu.registerInitHook(load);
+ yuu.init({ backgroundColor: [0, 0, 0, 1], antialias: false }).then(start);
+});
+
+var PALETTE = [[ 0.76, 0.13, 0.13 ],
+ [ 0.33, 0.49, 0.71 ],
+ [ 0.45, 0.68, 0.32 ],
+ [ 0.51, 0.32, 0.63 ],
+ [ 0.89, 0.49, 0.11 ],
+ [ 1.00, 1.00, 0.30 ]];
+
+var LEVELS = [
+ { name: "12345654321",
+ randomSlammer: [3, 5],
+ deps: "50040@easy 53535@easy 44404@easy 1302@easy 12321@easy 20124@easy"
+ },
+
+ { slammer: [1, 1], sets: "tutorial",
+ scramble: { easy: "01", hard: "0122" } },
+ { slammer: [1, 1, 1], deps: "tutorial",
+ scramble: { easy: "11", hard: "1212" } },
+ { slammer: [2, 1], deps: "tutorial", sets: "asymmetric",
+ scramble: { easy: "32", hard: "3321" } },
+ { slammer: [1, 2, 1], deps: "tutorial", sets: "unequal",
+ scramble: { easy: "112", hard: "3210" } },
+ { slammer: [2, 0], deps: "asymmetric", sets: "zero",
+ scramble: { easy: "23", hard: "032" } },
+ { slammer: [2, 0, 2], deps: "zero",
+ scramble: { easy: "11", hard: "2211" } },
+ { slammer: [1, 1, 1, 1], deps: "tutorial" },
+ { slammer: [2, 1, 1], deps: "asymmetric" },
+ { slammer: [1, 2, 1, 2], deps: "asymmetric",
+ scramble: { easy: "012" } },
+ { slammer: [1, 2, 3, 4], deps: "asymmetric", sets: "solid",
+ scramble: { easy: "110" } },
+ { slammer: [5, 0, 0, 4, 0], deps: "unequal zero",
+ scramble: { easy: "112" } },
+ { slammer: [5, 3, 5, 3, 5], deps: "unequal solid",
+ scramble: { easy: "3232" } },
+ { slammer: [4, 4, 4, 0, 4], deps: "solid zero",
+ scramble: { easy: "0321" } },
+ { slammer: [1, 3, 0, 2], deps: "unequal zero" },
+ { slammer: [1, 2, 3, 2, 1], deps: "unequal",
+ scramble: { easy: "3333" } },
+ { slammer: [2, 0, 1, 2, 4], deps: "unequal zero" },
+];
+
+function levelName (level) {
+ return (level.name || level.slammer.join("")).trim();
+}
+
+function wonLevel (level, difficulty) {
+ if (level.sets)
+ storage.setFlag(level.sets);
+ storage.setFlag(levelName(level) + "@" + difficulty);
+}
+
+function hasBeaten (level, difficulty) {
+ return storage.getFlag(levelName(level) + "@" + difficulty);
+}
+
+function scrambleForLevel (rnd, level, difficulty) {
+ var c = difficulty === "easy" ? 0 : 1;
+ if (difficulty === "random")
+ c = rnd.randrange(2, 5);
+ var length = level.slammer.length;
+ return rnd.randrange(length * c, length * (c + 1)) + 2;
+}
+
+function difficultyForLevel (level) {
+ if (level.deps && !level.deps.split(" ").every(storage.getFlag, storage))
+ return null;
+ if (hasBeaten(level, "hard"))
+ return "random";
+ if (hasBeaten(level, "easy"))
+ return "hard";
+ else
+ return "easy";
+}
+
+function levelRandom (level, difficulty) {
+ if (difficulty === "random")
+ return yuu.random;
+ else
+ return new yuu.Random(yuu.createLCG(+level.slammer.join("")));
+}
+
+function generateBoard (rnd, level) {
+ var size = level.length;
+ var board = new Array(size);
+ for (var i = 0; i < size; ++i)
+ board[i] = yf.repeat(i % PALETTE.length + 1, size);
+ if (rnd.randbool())
+ yuu.transpose2d(board);
+ return board;
+}
+
+function generateSlammer (rnd, level) {
+ var s = new Array(level.length);
+ for (var i = 0; i < s.length; ++i)
+ s[i] = yf.repeat(0, level[i]);
+ if (rnd.randbool())
+ s.reverse();
+ return s;
+}
+
+var AnimationQueue = yT(yuu.C, {
+ constructor: function () {
+ this._queue = [];
+ },
+
+ attached: function () {
+ this._queue = [];
+ },
+
+ _runNext: function () {
+ var next = this._queue[0];
+ if (next && this.entity)
+ this.entity.attach(new yuu.Animation(
+ next.timeline, next.params, this._complete.bind(this)));
+ },
+
+ _complete: function () {
+ var next = this._queue.shift();
+ next.resolve();
+ this._runNext();
+ },
+
+ enqueue: function (timeline, params) {
+ return new Promise(function (resolve) {
+ this._queue.push({
+ timeline: timeline,
+ params: params,
+ resolve: resolve
+ });
+ // FIXME: Simply chaining the promise doesn't work here
+ // because the tick between the two handlers is often long
+ // enough to render a frame, and that frame will have some
+ // undesirable intermediate state.
+ if (this._queue.length === 1)
+ this._runNext();
+ }.bind(this));
+ },
+
+ SLOTS: ["animationQueue"]
+});
+
+var SLAMMER_ROTATE = {
+ 0: { tween1: { yaw: "yaw" }, duration: 10 }
+};
+
+var ROTATE_ALL = {
+ 0: { tweenAll: { yaw: "yaws" }, duration: 10 }
+};
+
+var SLAMMER_BOUNCE = {
+ 0: { tween1: { y: 0.5 }, duration: 5, repeat: -1 }
+};
+
+var SLIDE_BLOCKS = {
+ 0: { tweenAll: { position: "positions" },
+ duration: 8, easing: "linear" },
+};
+
+var SLAMMER_SLAM = {
+ 0: { tween1: { y: -1.5 }, easing: "linear", duration: 6 },
+ 6: { event: "slideBoardBlocks" },
+ 15: { event: "slam",
+ tween1: { y: 0 }, easing: "linear", duration: 8 }
+};
+
+var GRID_DISMISS = {
+ 0: { tween1: { yaw: 2 * Math.PI, x: "x", y: "y", scale: [0.3, 0.3, 1] },
+ duration: 45 }
+};
+
+var GRID_FINISHED = {
+ 0: { tween: { arm: { scale: [0, 0, 1], yaw: "armYaw", y: "armY" },
+ board: { y: "boardY" } },
+ duration: 45 }
+};
+
+function rotateCw (d) { return (--d + 4) % 4; }
+function rotateCcw (d) { return ++d % 4; }
+function opposite (d) { return (d + 2) % 4; }
+
+var FlagSet = yT({
+ /** Manage a set of semaphore-like counting flags. */
+
+ constructor: function () {
+ /** Construct a flag set for the provided flags.
+
+ Flags are initialized to 0 by default.
+ */
+ this._counts = {};
+ for (var i = 0; i < arguments.length; ++i)
+ this._counts[arguments[i]] = 0;
+ },
+
+ increment: function () {
+ /** Increment the provided flags. */
+ for (var i = 0; i < arguments.length; ++i)
+ this._counts[arguments[i]]++;
+ },
+
+ decrement: function () {
+ /** Decrement the provided flags.
+
+ No underflow checks are performed. A flag with a negative
+ value is considered set exactly as a flag with a positive
+ value.
+ */
+ for (var i = 0; i < arguments.length; ++i)
+ this._counts[arguments[i]]--;
+ },
+
+ some: function () {
+ /** Return true if any of the provided flags are set. */
+ return yf.some.call(this._counts, yf.getter, arguments);
+ },
+
+ every: function () {
+ /** Return true if all of the provided flags are set. */
+ return yf.every.call(this._counts, yf.getter, arguments);
+ },
+
+ none: function () {
+ /** Return true if none of the provided flags are set. */
+ return !this.some.apply(this, arguments);
+ },
+
+ incrementer: function () {
+ /** Provide a bound 0-ary function to increment the provided flags.
+
+ Useful for wrapps around context-free callbacks.
+ */
+ var that = this, args = arguments;
+ return function () { that.increment.apply(that, args); };
+ },
+
+ decrementer: function () {
+ /** Provide a bound 0-ary function to decrement the provided flags.
+
+ Useful for wrapps around context-free callbacks.
+ */
+ var that = this, args = arguments;
+ return function () { that.decrement.apply(that, args); };
+ }
+});
+
+var BoardController = yT(yuu.C, {
+ constructor: function (rnd, level, colors) {
+ this.contents = generateBoard(rnd, level.slammer);
+ this.colors = colors;
+ },
+ updateChildren: function () {
+ this.entity.data.quads.forEach(function (q) {
+ q.quad.position = [q.x, q.y];
+ var i = this.contents[q.x][q.y];
+ q.quad.color = this.colors[i];
+ q.quad.texBounds = [i / 6, 0.5, (i + 1) / 6, 1.0];
+ }, this);
+ },
+ isComplete: function() {
+ var x, y;
+ var rows = true, cols = true;
+ for (x = 1; x < this.contents.length && rows; ++x)
+ for (y = 0; y < this.contents[x].length && rows; ++y)
+ rows = this.contents[x - 1][y] === this.contents[x][y];
+ for (x = 0; x < this.contents.length && cols; ++x)
+ for (y = 1; y < this.contents[x].length && cols; ++y)
+ cols = this.contents[x][y - 1] === this.contents[x][y];
+ return rows || cols;
+ },
+
+ shift: [
+ function (x, replacement) {
+ var lost = this.contents[x].pop();
+ this.contents[x].unshift(replacement);
+ return lost;
+ },
+ function (y, replacement) {
+ yuu.transpose2d(this.contents);
+ var lost = this.shift[BOTTOM].call(this, y, replacement);
+ yuu.transpose2d(this.contents);
+ return lost;
+ },
+ function (x, replacement) {
+ var lost = this.contents[x].shift();
+ this.contents[x].push(replacement);
+ return lost;
+ },
+ function (y, replacement) {
+ yuu.transpose2d(this.contents);
+ var lost = this.shift[TOP].call(this, y, replacement);
+ yuu.transpose2d(this.contents);
+ return lost;
+ }
+ ],
+
+ SLOTS: ["controller"]
+});
+
+var SlammerController = yT(yuu.C, {
+ constructor: function (rnd, level, colors) {
+ this.blocks = generateSlammer(rnd, level.slammer);
+ this.orientation = TOP;
+ this.colors = colors;
+ this._undoRecord = [];
+ },
+ isComplete: function() {
+ return yf.none(yf.some.bind(null, null), this.blocks);
+ },
+ updateChildren: function () {
+ this.entity.data.quads.forEach(function (q) {
+ var i = this.blocks[q.x][q.y];
+ q.quad.position = [q.x, q.y];
+ q.quad.color = this.colors[i];
+ q.quad.texBounds = [i / 6, 0.5, (i + 1) / 6, 1.0];
+ }, this);
+ },
+
+ lastUndoRecord: { get: function () {
+ return yf.last(this._undoRecord);
+ } },
+
+ clearUndoRecord: function () {
+ this._undoRecord = [];
+ },
+
+ slam: function (board) {
+ var undoable = (this.orientation !== this.lastUndoRecord);
+ var length = this.blocks.length;
+ this.orientation = opposite(this.orientation);
+ this.blocks = yf.mapr.call(this, function (a, y) {
+ return yf.map(board.shift[this.orientation].bind(board, y), a)
+ .reverse();
+ }, this.blocks, (this.orientation & 2)
+ ? yf.range(length)
+ : yf.range(length - 1, -1, -1));
+ yf.each(function (i) {
+ i.x = length - (i.x + 1);
+ }, this.entity.data.quads);
+ this.updateChildren();
+ board.updateChildren();
+ if (undoable)
+ this._undoRecord.push(this.orientation);
+ else
+ this._undoRecord.pop();
+ },
+
+ SLOTS: ["controller"]
+});
+
+function randSide (rnd, except) {
+ return (rnd || yuu.random).choice(
+ yf.without([TOP, LEFT, BOTTOM, RIGHT], except));
+}
+
+var HANDS_LEFT = {
+ 0: { tween: { left: { yaw: -0.3 } }, duration: 3 },
+ 3: { tween: { left: { yaw: 0.0 } }, duration: 7 },
+};
+
+var HANDS_RIGHT = {
+ 0: { tween: { right: { yaw: -0.3 } }, duration: 3 },
+ 3: { tween: { right: { yaw: 0.0 } }, duration: 7 },
+};
+
+var HANDS_UNDO = {
+ 0: { tween: { left: { yaw: 0.2 }, right: { yaw: 0.2 } },
+ duration: 3 },
+ 3: { tween: { left: { yaw: 0.0 }, right: { yaw: 0.0 } },
+ duration: 7 }
+};
+
+var HANDS_MENU_CHOICE = {
+ 0: { tween: { left: { x: -1.3 },
+ right: { x: -1.3 } },
+ duration: 15, easing: "ease_in"
+ },
+
+ 10: { tween: { left: { scaleX: 1 },
+ right: { scaleX: 1 } },
+ duration: 20 },
+
+ 20: { set: { leftQuad: { color: "frontColor" },
+ rightQuad: { color: "frontColor" } },
+ tween: { left: { x: 0 }, right: { x: 0 } },
+ duration: 15
+ },
+};
+
+var HANDS_RETURN = {
+ 0: { tween: { left: { x: -1.3 },
+ right: { x: -1.3 } },
+ duration: 20
+ },
+
+ 10: { tween: { left: { scaleX: -1 },
+ right: { scaleX: -1 } },
+ duration: 20 },
+
+ 20: { set: { leftQuad: { color: "backColor" },
+ rightQuad: { color: "backColor" } },
+ tween: { left: { x: -1 }, right: { x: -1 } },
+ duration: 10
+ },
+};
+
+var HANDS_SLAM = [
+ // TOP
+ { 0: { tween: { left: { yaw: -0.2, scaleX: 0.8, y: -0.1 },
+ right: { yaw: -0.2, scaleX: 0.8, y: -0.1 },
+ }, duration: 10, repeat: -1 },
+ },
+
+ // LEFT
+ { 0: { tween: { left: { scaleX: 0.8, x: 0.1 },
+ right: { scaleX: 0.9 },
+ }, duration: 10, repeat: -1 },
+ },
+
+ // BOTTOM
+ { 0: { tween: { left: { yaw: 0.2, scaleX: 0.8 },
+ right: { yaw: 0.2, scaleX: 0.8 },
+ }, duration: 10, repeat: -1 },
+ },
+
+ // RIGHT
+ { 0: { tween: { left: { scaleX: 0.9 },
+ right: { scaleX: 0.8, x: 0.1 },
+ }, duration: 10, repeat: -1 },
+ },
+];
+
+var HANDS_ROTATE_CW = {
+ 0: { tween: { left: { scaleX: 0.8 } }, duration: 5 },
+ 5: { tween: { left: { scaleX: 1.0 } }, duration: 5 },
+};
+
+var HANDS_ROTATE_CCW = {
+ 0: { tween: { right: { scaleX: 0.8 } }, duration: 5 },
+ 5: { tween: { right: { scaleX: 1 } }, duration: 5 },
+};
+
+var BUTTONS_IN = {
+ 0: { tween: { a: { x: 0 }, b: { x: 1.5 } }, duration: 25 }
+};
+
+var BUTTONS_OUT = {
+ 0: { tween: { a: { x: -1.5 }, b: { x: 0 } }, duration: 25 }
+};
+
+HandScene = yT(yuu.Scene, {
+ constructor: function () {
+ yuu.Scene.call(this);
+ var hands = new yuu.Material("@hand");
+ this.left = new yuu.E(new yuu.Transform());
+ var l = new yuu.E(
+ new yuu.Transform([-0.5, 0.5, 0]),
+ new yuu.DataC({ command: "left" }),
+ this.leftQuad = new yuu.QuadC(hands));
+ this.left.addChild(l);
+ var r = new yuu.E(
+ new yuu.Transform([-0.5, 0.5, 0]),
+ new yuu.DataC({ command: "right" }),
+ this.rightQuad = new yuu.QuadC(hands));
+ this.right = new yuu.E(new yuu.Transform());
+ this.right.addChild(r);
+ var SIZE_X = yuu.random.gauss(1.2, 0.15) * 0.35;
+ var SIZE_Y = yuu.random.gauss(1.1, 0.05) * 0.51;
+ var hand = yuu.random.randrange(3);
+ this.leftQuad.texBounds = this.rightQuad.texBounds = [
+ hand / 2.99, 0, (hand + 1) / 3.01, 1];
+ this.layer0.resize(-0.75, 0, 1.5, 1.5);
+ var leftWrist = new yuu.E(
+ new yuu.Transform([-0.20, 0, 0], null,
+ [SIZE_X, SIZE_Y, 1]));
+ var rightWrist = new yuu.E(
+ new yuu.Transform([0.20, 0, 0], null,
+ [-SIZE_X, SIZE_Y, 1]));
+ leftWrist.addChild(this.left);
+ rightWrist.addChild(this.right);
+ this.addEntities(leftWrist, rightWrist);
+ this.backColor = yuu.hslToRgb(
+ (yuu.random.gauss(0.1, 0.1) + 10) % 1,
+ yuu.random.uniform(0.2, 0.7),
+ yuu.random.uniform(0.2, 0.6),
+ 1.0);
+ this.leftQuad.alpha = this.rightQuad.alpha = 0.2;
+ var hsl = yuu.rgbToHsl(this.backColor);
+ hsl[2] = hsl[2].lerp(1, 0.15);
+ hsl[1] = hsl[1].lerp(0, 0.30);
+ hsl[3] = 0.4;
+ this.frontColor = yuu.hslToRgb(hsl);
+ this.leftQuad.color = this.rightQuad.color = this.frontColor;
+ this.ready = hands.ready;
+
+ function Button (i, command) {
+ return new yuu.E(
+ new yuu.Transform(),
+ new yuu.DataC({ command: command }),
+ new yuu.QuadC(SIGILS)
+ .setTexBounds([i / 6, 0, (i + 1) / 6, 0.5])
+ .setColor(PALETTE[i]));
+ }
+
+ this.helpButton = new Button(1, "help");
+ this.backButton = new Button(3, "back");
+ this.backButton.transform.x -= 1.5;
+ this.leftButtons = new yuu.E(new yuu.Transform());
+ this.leftButtons.addChildren(this.helpButton, this.backButton);
+ this.rightButton = new Button(2, "showOverlay preferences");
+ this.leftButtons.transform.scale
+ = this.rightButton.transform.scale
+ = [0.075, 0.075, 1];
+ this.entity0.addChildren(this.leftButtons, this.rightButton);
+ this.buttons = [this.helpButton, this.backButton, this.rightButton,
+ l, r];
+ },
+
+ inputs: {
+ resize: function () {
+ var base = new yuu.AABB(-0.75, 0, 0.75, 1.5);
+ var vp = base.matchAspectRatio(yuu.viewport);
+ vp.y1 -= vp.y0;
+ vp.y0 = 0;
+ this.leftButtons.transform.xy = [
+ vp.x0 + this.leftButtons.transform.scaleX,
+ vp.y1 - this.leftButtons.transform.scaleY];
+ this.rightButton.transform.xy = [
+ vp.x1 - this.rightButton.transform.scaleX,
+ vp.y1 - this.rightButton.transform.scaleY];
+ this.layer0.resize(vp.x0, vp.y0, vp.w, vp.h);
+ },
+
+ mousemove: function (p) {
+ p = this.layer0.worldFromDevice(p);
+ this.cursor = "";
+ for (var i = 0; i < this.buttons.length; ++i) {
+ if (this.buttons[i].transform.contains(p)) {
+ this.cursor = "pointer";
+ }
+ }
+ },
+
+ tap: function (p) {
+ p = this.layer0.worldFromDevice(p);
+ for (var i = 0; i < this.buttons.length; ++i) {
+ if (this.buttons[i].transform.contains(p)) {
+ yuu.director.execute(this.buttons[i].data.command);
+ return true;
+ }
+ }
+ },
+
+ doubletap: function () {
+ return this.inputs.tap.apply(this, arguments);
+ },
+ },
+
+ _anim: function (timeline) {
+ this.entity0.attach(new yuu.Animation(
+ timeline, {
+ left: this.left.transform,
+ right: this.right.transform,
+ leftQuad: this.leftQuad,
+ rightQuad: this.rightQuad,
+ frontColor: this.frontColor,
+ backColor: this.backColor
+ }));
+ },
+
+ undo: function () { this._anim(HANDS_UNDO); },
+ movedLeft: function () { this._anim(HANDS_LEFT); },
+ movedRight: function () { this._anim(HANDS_RIGHT); },
+ slam: function (o) { this._anim(HANDS_SLAM[o]); },
+ rotatedCw: function () { this._anim(HANDS_ROTATE_CW); },
+ rotatedCcw: function () { this._anim(HANDS_ROTATE_CCW); },
+ menuChoice: function () {
+ this.entity0.attach(new yuu.Animation(
+ BUTTONS_IN, {
+ a: this.backButton.transform,
+ b: this.helpButton.transform
+ }));
+ this._anim(HANDS_MENU_CHOICE);
+ },
+ finished: function () {
+ this.entity0.attach(new yuu.Animation(
+ BUTTONS_OUT, {
+ a: this.backButton.transform,
+ b: this.helpButton.transform
+ }));
+ this._anim(HANDS_RETURN);
+ },
+});
+
+var GRID_APPEAR = {
+ 0: { set1: { y: 5 },
+ tween1: { y: 0 }, duration: 10 },
+};
+
+GridScene = yT(yuu.Scene, {
+ constructor: function (level, difficulty) {
+ yuu.Scene.call(this);
+ this.entity0.attach(new yuu.Transform());
+ this.level = level;
+ this.difficulty = difficulty;
+ this._locks = new FlagSet("slam", "spin", "quit");
+ var rnd = levelRandom(level, difficulty);
+ var colors = yuu.random.shuffle(PALETTE.slice());
+ colors.unshift([1.0, 1.0, 1.0]);
+ this.board = new yuu.E(new BoardController(rnd, level, colors),
+ new yuu.Transform(),
+ new yuu.DataC({ quads: [] }));
+ this.slammer = new yuu.E(new SlammerController(rnd, level, colors),
+ new yuu.Transform(),
+ new yuu.DataC({ quads: [] }));
+ this.slammerHead = new yuu.E(new yuu.Transform());
+ this.slammerRoot = new yuu.E(new yuu.Transform());
+ var length = level.slammer.length;
+ var maxSize = length * length;
+ var slammerBatch = new yuu.QuadBatchC(maxSize);
+ slammerBatch.material = SIGILS;
+ var boardBatch = new yuu.QuadBatchC(maxSize);
+ boardBatch.material = SIGILS;
+ this.slammerRoot.transform.xy = [length / 2 - 0.5, length / 2 - 0.5];
+ this.slammerHead.transform.xy = [-length / 2 + 0.5, length / 2 + 2];
+ this.slammerRoot.addChild(this.slammerHead);
+ this.slammerHead.addChild(this.slammer);
+ this.slammer.attach(slammerBatch);
+ this.board.attach(boardBatch);
+ yf.irange.call(this, function (x) {
+ yf.irange.call(this, function (y) {
+ var quad = boardBatch.createQuad();
+ quad.color = colors[this.board.controller.contents[x][y]];
+ quad.position = [x, y];
+ this.board.data.quads.push({ quad: quad, x: x, y: y });
+ }, length);
+ }, length);
+
+ for (var x = 0; x < this.slammer.controller.blocks.length; ++x) {
+ for (var y = 0; y < this.slammer.controller.blocks[x].length; ++y) {
+ var quad = slammerBatch.createQuad();
+ quad.color = colors[this.slammer.controller.blocks[x][y]];
+ quad.position = [x, y];
+ this.slammer.data.quads.push({ quad: quad, x: x, y: y });
+ }
+ }
+ this.addEntities(this.board, this.slammerRoot);
+ this.scramble(rnd);
+ if (!(this.cheating = yuu.director.input.pressed["`"])) {
+ this.slammer.controller.clearUndoRecord();
+ }
+ this._dragging = 0;
+
+ this.gridBB = new yuu.AABB(-0.5, -0.5, length - 0.5, length - 0.5);
+ this.leftBB = new yuu.AABB(
+ -Infinity, this.gridBB.y0, this.gridBB.x0, this.gridBB.y1);
+ this.rightBB = new yuu.AABB(
+ this.gridBB.x1, this.gridBB.y0, Infinity, this.gridBB.y1);
+ this.topBB = new yuu.AABB(
+ this.gridBB.x0, this.gridBB.y1, this.gridBB.x1, Infinity);
+ this.bottomBB = new yuu.AABB(
+ this.gridBB.x0, -Infinity, this.gridBB.x1, this.gridBB.y0);
+ },
+
+ init: function () {
+ this._locks.increment("slam");
+ this.entity0.attach(new yuu.Animation(
+ GRID_APPEAR, { $: this.slammer.transform },
+ this._locks.decrementer("slam")));
+ },
+
+ scramble: function (rnd) {
+ var scramble = (this.level.scramble || {})[this.difficulty];
+ var slammerCon = this.slammer.controller;
+ var boardCon = this.board.controller;
+ if (!scramble) {
+ var count = scrambleForLevel(rnd, this.level, this.difficulty);
+ while (this.isComplete()) {
+ var c = count;
+ while (c--) {
+ slammerCon.orientation = randSide(rnd, slammerCon.orientation);
+ slammerCon.slam(boardCon);
+ }
+ }
+ } else {
+ for (var i =0; i < scramble.length; ++i) {
+ slammerCon.orientation = +scramble[i];
+ slammerCon.slam(boardCon);
+ }
+ }
+ slammerCon.orientation = randSide();
+ this.slammerRoot.transform.yaw = slammerCon.orientation * Math.PI / 2;
+ },
+
+ isComplete: function () {
+ return this.slammer.controller.isComplete()
+ && this.board.controller.isComplete();
+ },
+
+ rotateTo: yuu.cmd(function (orientation) {
+ return new Promise(function (resolve) {
+ if (this._locks.some("spin"))
+ return this;
+ this.slammer.controller.orientation = orientation;
+ this._locks.increment("slam");
+ var yaw0 = this.slammerRoot.transform.yaw;
+ var yaw1 = orientation * Math.PI / 2;
+
+ sounds.clicking.play();
+ this.entity0.attach(new yuu.Animation(
+ SLAMMER_ROTATE, {
+ $: this.slammerRoot.transform,
+ yaw: yaw0 + yuu.normalizeRadians(yaw1 - yaw0)
+ }, function () {
+ this._locks.decrement("slam");
+ resolve(this);
+ }.bind(this)));
+ }.bind(this));
+ }, "<top/bottom/left/right>", "move the slammer to the top"),
+
+ rotateCw: yuu.cmd(function () {
+ handScene.rotatedCw();
+ circleScene.rotated();
+ this.rotateTo(rotateCw(this.slammer.controller.orientation));
+ }, "", "rotate the active piece clockwise"),
+
+ rotateCcw: yuu.cmd(function () {
+ handScene.rotatedCcw();
+ circleScene.rotated();
+ this.rotateTo(rotateCcw(this.slammer.controller.orientation));
+ }, "", "rotate the active piece counter-clockwise"),
+
+ left: yuu.cmd(function () { this.rotateCw(); }),
+ right: yuu.cmd(function () { this.rotateCcw(); }),
+
+ undo: yuu.cmd(function (v) {
+ var con = this.slammer.controller;
+ var _ = function () { this.undo(this._undo); }.bind(this);
+ if ((this._undo = v) && con.lastUndoRecord !== undefined) {
+ if (con.orientation !== con.lastUndoRecord) {
+ circleScene.reverse();
+ handScene.undo();
+ this.rotateTo(con.lastUndoRecord)
+ .then(this.slam.bind(this))
+ .then(_);
+ } else {
+ this.slam().then(_);
+ }
+ }
+ }, "", "rotate the active piece counter-clockwise"),
+
+ checkWon: function () {
+ if (this.isComplete() && !this._locks.some("quit")) {
+ this._locks.increment("quit", "slam", "spin");
+ var firstTime = !hasBeaten(this.level, this.difficulty);
+ if (!this.cheating)
+ wonLevel(this.level, this.difficulty);
+ var scene = new MenuScene(this.level);
+ yuu.director.pushScene(scene);
+ scene.didWinLevel(this.level, this.difficulty, firstTime);
+ this.entity0.attach(new yuu.Animation(
+ GRID_FINISHED, {
+ arm: this.slammerRoot.transform,
+ armYaw: this.slammerRoot.transform.yaw + 3 * Math.PI,
+ armY: this.slammerRoot.transform.y + 1.5,
+ board: this.board.transform,
+ boardY: this.level.slammer.length * 3
+ }, function () {
+ yuu.director.removeScene(this);
+ }.bind(this)
+ ));
+ }
+ },
+
+ slideBoardBlocks: function (anim, params) {
+ var dx = 0, dy = 0;
+ var orientation = this.slammer.controller.orientation;
+ switch (orientation) {
+ case LEFT: dx = 1.5; break;
+ case TOP: dy = -1.5; break;
+ case RIGHT: dx = -1.5; break;
+ case BOTTOM: dy = 1.5; break;
+ }
+ var sgnx = Math.sign(dx);
+ var sgny = Math.sign(dy);
+ var $s = [];
+ var positions = [];
+ var blocks = this.slammer.controller.blocks;
+ this.slammer.data.quads.forEach(function (q) {
+ var d = blocks[q.x].length;
+ $s.push(q.quad);
+ positions.push([q.quad.position[0], q.quad.position[1] - d]);
+ }, this);
+ this.board.data.quads.forEach(function (q) {
+ var x = orientation === TOP ? q.x : blocks.length - (q.x + 1);
+ var y = orientation === LEFT ? q.y : blocks.length - (q.y + 1);
+
+ $s.push(q.quad);
+ positions.push([q.quad.position[0] + sgnx * blocks[y].length,
+ q.quad.position[1] + sgny * blocks[x].length]);
+ }, this);
+ this.entity0.attach(new yuu.Animation(SLIDE_BLOCKS, {
+ $s: $s,
+ positions: positions
+ }));
+ },
+
+ slam: yuu.cmd(function () {
+ var r = new Promise(function (resolve, reject) {
+ if (this._locks.some("slam")) {
+ reject("slamming is locked");
+ return;
+ }
+ this._locks.increment("spin", "slam");
+ circleScene.slam();
+ sounds.slam.play();
+ handScene.slam(this.slammer.controller.orientation);
+ this.entity0.attach(new yuu.Animation(
+ SLAMMER_SLAM, {
+ $: this.slammer.transform,
+ slam: function () {
+ this._locks.decrement("spin");
+ this.slammer.controller.slam(this.board.controller);
+ this.slammerRoot.transform.yaw = Math.PI / 2 *
+ this.slammer.controller.orientation;
+ }.bind(this),
+ slideBoardBlocks: this.slideBoardBlocks.bind(this)
+ }, function () {
+ this.checkWon();
+ this._locks.decrement("slam");
+ resolve(this);
+ }.bind(this)));
+ }.bind(this));
+ return r;
+ }, "", "slam the active piece"),
+
+ back: yuu.cmd(function (x, y) {
+ if (this._locks.some("quit"))
+ return;
+ this._locks.increment("quit", "slam", "spin");
+ var scene = new MenuScene(this.level);
+ yuu.director.pushScene(scene);
+ var v = [x || yuu.random.uniform(-1, 1),
+ y || yuu.random.uniform(-1, 1)];
+ var size = this.board.controller.contents.length * 5;
+ vec2.scale(v, vec2.normalize(v, v), size);
+ this.entity0.attach(new yuu.Animation(
+ GRID_DISMISS, {
+ $: this.entity0.transform,
+ x: v[0], y: v[1]
+ }, function () {
+ yuu.director.removeScene(this);
+ }.bind(this)
+ ));
+ circleScene.lose();
+ }, "", "go back to the menu"),
+
+ slammerBB: { get: function (p) {
+ var length = this.level.slammer.length;
+ switch (this.slammer.controller.orientation) {
+ case LEFT:
+ return new yuu.AABB(-Infinity, -0.5, -1, length - 0.5);
+ case RIGHT:
+ return new yuu.AABB(length + 1, -0.5, Infinity, length - 0.5);
+ case TOP:
+ return new yuu.AABB(-0.5, length + 1, length - 0.5, Infinity);
+ case BOTTOM:
+ return new yuu.AABB(-0.5, -Infinity, length - 0.5, -1);
+ }
+ } },
+
+ _swipe: function (p0, p1) {
+ p0 = this.layer0.worldFromDevice(p0);
+ p1 = this.layer0.worldFromDevice(p1);
+ if (this.slammerBB.contains(p0)) {
+ this.slam();
+ return true;
+ }
+ if (this.gridBB.contains(p0) && !this.gridBB.contains(p1)) {
+ this.back(p1.x - p0.x, p1.y - p0.y);
+ return true;
+ }
+ },
+
+ inputs: {
+ resize: function () {
+ var length = this.level.slammer.length;
+ var base = new yuu.AABB(-length - 2.5, -length - 2.5,
+ 2 * length + 1.5, 2 * length + 1.5);
+ var vp = base.matchAspectRatio(yuu.viewport);
+ this.layer0.resize(vp.x0, vp.y0, vp.w, vp.h);
+ },
+
+ tap: function (p) {
+ p = this.layer0.worldFromDevice(p);
+ if (this.gridBB.contains(p)) {
+ this.slam();
+ return true;
+ }
+ },
+
+ touch: function (p) {
+ var length = this.level.slammer.length;
+ var middle = (length - 1) / 2;
+ p = this.layer0.worldFromDevice(p);
+ if (this.slammerBB.contains(p)) {
+ this.slammer.attach(new yuu.Animation(
+ SLAMMER_BOUNCE, { $: this.slammer.transform }));
+ } else if (this.leftBB.contains(p)) {
+ this.rotateTo(LEFT);
+ handScene.rotatedCw();
+ return true;
+ } else if (this.rightBB.contains(p)) {
+ this.rotateTo(RIGHT);
+ handScene.rotatedCcw();
+ return true;
+ } else if (this.topBB.contains(p)) {
+ this.rotateTo(TOP);
+ if (p.x < middle)
+ handScene.rotatedCw();
+ else
+ handScene.rotatedCcw();
+ return true;
+ } else if (this.bottomBB.contains(p)) {
+ this.rotateTo(BOTTOM);
+ if (p.x < middle)
+ handScene.rotatedCw();
+ else
+ handScene.rotatedCcw();
+ return true;
+ }
+ },
+
+ doubletap: function () {
+ return this.inputs.tap.apply(this, arguments);
+ },
+
+ hold: function (p) {
+ p = this.layer0.worldFromDevice(p);
+ if (this.gridBB.contains(p)) {
+ this.undo(true);
+ return true;
+ }
+ },
+
+ dragstart: function (p) {
+ p = this.layer0.worldFromDevice(p);
+ this._dragging = this.slammerBB.contains(p);
+ },
+
+ drag: function (p0, p1) {
+ var p = this.layer0.worldFromDevice(p1);
+ if (this._dragging && !this._locks.some("slam")) {
+ var inGrid = this.gridBB.contains(p);
+ var length = this.level.slammer.length;
+ var o;
+ if (this._dragging === true && inGrid) {
+ this.slam();
+ } else if (p.x > 0 && p.x < length && !inGrid) {
+ o = p.y < 0 ? BOTTOM : TOP;
+ if (o !== this.slammer.controller.orientation) {
+ this.rotateTo(o);
+ this._dragging = 2;
+ }
+ } else if (p.y > 0 && p.y < length && !inGrid) {
+ o = p.x < 0 ? LEFT : RIGHT;
+ if (o !== this.slammer.controller.orientation) {
+ this.rotateTo(o);
+ this._dragging = 2;
+ }
+ }
+ }
+ return this._dragging;
+ },
+
+ dragend: function (p0, p1) {
+ this._dragging = false;
+ },
+
+ release: function () {
+ this.undo(false);
+ },
+
+ swipeleft: function (p0, p1) {
+ return this._swipe(p0, p1);
+ },
+ swiperight: function (p0, p1) {
+ return this._swipe(p0, p1);
+ },
+ swipeup: function (p0, p1) {
+ return this._swipe(p0, p1);
+ },
+ swipedown: function (p0, p1) {
+ return this._swipe(p0, p1);
+ },
+ },
+
+ KEYBINDS: {
+ w: "rotateCw",
+ a: "rotateCcw",
+ s: "rotateCcw",
+ d: "rotateCw",
+ left: "rotateCcw",
+ right: "rotateCw",
+ up: "rotateCw",
+ down: "rotateCcw",
+ shift: "slam",
+ space: "slam",
+ z: "slam",
+ backspace: "+undo",
+ c: "+undo",
+ escape: "back",
+ back: "back",
+ gamepadbutton0: "slam",
+ gamepadbutton1: "+undo",
+ gamepadbutton2: "slam",
+ gamepadbutton3: "+undo",
+ gamepadbutton4: "rotateCcw",
+ gamepadbutton5: "rotateCw",
+ gamepadbutton8: "back",
+ gamepadbutton14: "rotateCcw",
+ gamepadbutton15: "rotateCw",
+ }
+});
+
+var MENU_APPEAR = {
+ 0: [{ set1: { x: 5, y: 5, scaleX: 0, scaleY: 0 } },
+ { tween1: { x: 0, y: 0, scaleX: 1 }, duration: 24 },
+ { tween1: { scaleY: 1 },
+ duration: 55, easing: yuu.Tween.METASPRING(1, 10)}],
+};
+
+var MENU_SLIDE = {
+ 0: { tween1: { x: "x" }, duration: "duration" }
+};
+
+var FLASH = {
+ 0: { tween1: { luminance: 1, alpha: 1 }, duration: 32, repeat: -1 }
+};
+
+var MENU_SLAM = {
+ 0: { tween: { cursor: { y: "mid" } }, duration: 8 },
+ 8: { tween: { cursor: { y: "line" },
+ select: { y: -1.0 }
+ }, duration: 12 },
+ 20: { tween: { scene: { y: 10 },
+ select: { y: -11.5, scale: [3, 3, 1] }
+ }, duration: 18 },
+ 38: { event: "appear",
+ tween: { select: { y: 0, scale: [1, 0, 1] } },
+ duration: 20 }
+};
+
+function menuEntityForLevel (level, i) {
+ var activated = false;
+ function randomizeSlammer () {
+ var min = level.randomSlammer[0];
+ var max = level.randomSlammer[1];
+ var size = yuu.random.randrange(min, max + 1);
+ level.slammer = [];
+ do {
+ for (var i = 0; i < size; ++i)
+ level.slammer[i] = yuu.random.randrange(0, size);
+ } while (Math.min.apply(Math, level.slammer) === max
+ || Math.max.apply(Math, level.slammer) === 0);
+ }
+
+ function generateQuads() {
+ batch.disposeAll();
+ var rgb = PALETTE[i % PALETTE.length];
+ var fit = level.slammer.length + 1;
+ batch.createQuad().color = rgb;
+ level.slammer.forEach(function (size, y) {
+ var c = batch.createQuad();
+ c.color = [0, 0, 0];
+ c.alpha = hasBeaten(level, "easy") ? 0.5 : 1.0;
+ c.size = [size / fit, 1 / fit];
+ c.position = [0, -0.5 + (y + 1) / fit];
+ });
+ ce.data.flasher = batch.createQuad();
+ ce.data.flasher.alpha = 0;
+ return ce;
+ }
+
+ if (level.randomSlammer)
+ randomizeSlammer();
+
+ // 14 = maximum slammer size + 1 background + 1 flasher
+ var batch = new yuu.QuadBatchC(14);
+ var ce = new yuu.E(
+ new yuu.Transform([2 * i, 0, 0]),
+ batch,
+ new yuu.DataC({
+ activate: function () {
+ activated = true;
+ var scene = new GridScene(level, difficultyForLevel(level));
+ yuu.director.insertUnderScene(scene);
+ }
+ })
+ );
+
+ if (level.randomSlammer) {
+ ce.attach(new yuu.Ticker(function () {
+ if (!activated && yuu.random.randbool(0.7)) {
+ randomizeSlammer();
+ ++i;
+ generateQuads();
+ }
+ return !activated;
+ }, 30, 15));
+ }
+
+ generateQuads();
+ return ce;
+}
+
+var HAND_TICK_BACK = {
+ 0: { tween1: { rotation: "rotation" }, duration: 6, repeat: -1 }
+};
+
+MenuScene = yT(yuu.Scene, {
+ constructor: function (initialLevel) {
+ yuu.Scene.call(this);
+ this.entity0.attach(new yuu.Transform(),
+ new AnimationQueue());
+
+ this.pointer = new yuu.E(
+ new yuu.Transform([5, 8, 0]),
+ new yuu.QuadC());
+
+ var menu = this.menu = new yuu.E(new yuu.Transform([5, 6.5, 0]));
+ this.addEntities(menu, this.pointer);
+ this.availableLevels = LEVELS.filter(difficultyForLevel);
+ this.availableLevels
+ .map(menuEntityForLevel)
+ .forEach(menu.addChild, menu);
+
+ var initialIdx = this.availableLevels.indexOf(initialLevel);
+ this._locks = new FlagSet("slam", "move");
+ this.activeIndex = Math.max(initialIdx, 0);
+ menu.transform.x = 5 - 2 * this.activeIndex;
+ this.changeActiveIndex(this.activeIndex, false);
+ this._dragStartX = null;
+
+ this.entity0.attach(
+ new yuu.Ticker(this._animation.bind(this), 60));
+ },
+
+ _animation: function (count) {
+ var length = this.availableLevels.length;
+ var range = Math.pow(2, length);
+ var rand = yuu.random.randrange(range);
+ var targets = [];
+ var yaws = [];
+ for (var i = 0; i < length; ++i) {
+ var child = this.menu.children[i];
+ var level = this.availableLevels[i];
+ var won = hasBeaten(level, "hard");
+ if ((won || ((count ^ i) & 1)) && ((count ^ rand) & (1 << i))) {
+ var dyaw = won
+ ? yuu.random.randsign(Math.PI / 2)
+ : -Math.PI / 2;
+ targets.push(child.transform);
+ yaws.push(child.transform.yaw + dyaw);
+ }
+ }
+ if (targets.length) {
+ this.entity0.attach(new yuu.Animation(
+ ROTATE_ALL, { $s: targets, yaws: yaws }));
+ }
+ circleScene.clockTick(TICK_ROT2, HAND_TICK_BACK);
+ sounds[["tick", "tock"][count & 1]]
+ .createSound(yuu.audio, yuu.audio.currentTime, 0, 0.2, 1.0)
+ .connect(yuu.audio.music);
+
+ return true;
+ },
+
+ init: function () {
+ circleScene.toBottom();
+ handScene.finished();
+ this._locks.increment("slam", "move");
+ this.entity0.animationQueue.enqueue(
+ MENU_APPEAR,
+ { $: this.entity0.transform })
+ .then(this._locks.decrementer("slam", "move"));
+ },
+
+ didWinLevel: function (level, difficulty, firstTime) {
+ var idx = this.availableLevels.indexOf(level);
+ circleScene.win();
+ if (firstTime)
+ this.entity0.animationQueue.enqueue(
+ FLASH, { $: this.menu.children[idx].data.flasher });
+ for (var i = idx; i < this.availableLevels.length; ++i) {
+ if (!hasBeaten(this.availableLevels[i], difficulty)) {
+ this._locks.increment("move");
+ this.changeActiveIndex(i, true)
+ .then(this._locks.decrementer("move"));
+
+ break;
+ }
+ }
+ },
+
+ changeActiveIndex: function (index, animate) {
+ var oldIndex = this.activeIndex;
+ var p;
+ this.activeIndex = index = yf.clamp(
+ index, 0, this.menu.children.length - 1);
+ if (index !== oldIndex && animate) {
+ this._locks.increment("slam");
+ var duration = Math.ceil(8 * Math.abs(oldIndex - index));
+ p = this.entity0.animationQueue.enqueue(
+ MENU_SLIDE, {
+ $: this.menu.transform,
+ x: 5 - 2 * index,
+ duration: duration
+ });
+ p.then(this._locks.decrementer("slam"));
+ }
+ return p || Promise.resolve();
+ },
+
+ left: yuu.cmd(function () {
+ if (!this._locks.some("move")) {
+ sounds[this.activeIndex === 0 ? "switchBroke" : "switch"].play();
+ handScene.movedLeft();
+ this.changeActiveIndex(this.activeIndex - 1, true);
+ }
+ }, "move the cursor left"),
+ right: yuu.cmd(function () {
+ if (!this._locks.some("move")) {
+ sounds[this.activeIndex === this.availableLevels.length - 1
+ ? "switchBroke" : "switch"].play();
+ handScene.movedRight();
+ this.changeActiveIndex(this.activeIndex + 1, true);
+ }
+ }, "move the cursor right"),
+
+ slam: yuu.cmd(function () {
+ if (this._locks.some("slam"))
+ return;
+ var activeChild = this.menu.children[this.activeIndex];
+ this._locks.increment("slam", "move");
+ handScene.menuChoice();
+ circleScene.toBack();
+ circleScene.slam();
+ sounds.winding.play();
+ this.entity0.animationQueue.enqueue(
+ MENU_SLAM, {
+ cursor: this.pointer.transform,
+ select: activeChild.transform,
+ scene: this.entity0.transform,
+ mid: this.pointer.transform.y - 0.5,
+ line: this.pointer.transform.y - 1.5,
+ appear: activeChild.data.activate
+ }).then(function () {
+ this._locks.decrementer("slam", "move");
+ yuu.director.removeScene(this);
+ }.bind(this));
+ }, "choose the active menu item"),
+
+ inputs: {
+ resize: function () {
+ var base = new yuu.AABB(0, 0, 10, 10);
+ var vp = base.matchAspectRatio(yuu.viewport);
+ this.layer0.resize(vp.x0, vp.y0, vp.w, vp.h);
+ },
+
+ pinchout: function (p0, p1) {
+ p0 = this.layer0.worldFromDevice(p0);
+ p1 = this.layer0.worldFromDevice(p1);
+ if (vec2.sqrDist(p0, p1) > 1) {
+ this.slam();
+ return true;
+ }
+ },
+
+ hold: function (p) {
+ return this.inputs.dragstart.call(this, p);
+ },
+
+ release: function (p) {
+ if (this._dragStartX !== null)
+ return this.inputs.dragend.call(this, p);
+ },
+
+ dragstart: function (p) {
+ if (this._locks.some("move"))
+ return false;
+ p = this.layer0.worldFromDevice(p);
+ if (p.y > 6 && p.y < 8.5 && p.inside && this._dragStartX === null) {
+ sounds.switchOn.play();
+ this._locks.increment("move");
+ this._dragStartX = this.menu.transform.x;
+ return true;
+ }
+ },
+
+ dragdown: function (p0, p1) {
+ p0 = this.layer0.worldFromDevice(p0);
+ p1 = this.layer0.worldFromDevice(p1);
+
+ if (p0.x >= 4.5 && p0.x <= 5.5
+ && p0.y >= 6.0 && p0.y <= 8.5
+ && p0.y - p1.y > 1) {
+ this.slam();
+ return true;
+ }
+ },
+
+ drag: function (p0, p1) {
+ if (this._dragStartX !== null) {
+ p0 = this.layer0.worldFromDevice(p0);
+ p1 = this.layer0.worldFromDevice(p1);
+ this.menu.transform.x = this._dragStartX + (p1.x - p0.x);
+ var index = Math.round((5 - this.menu.transform.x) / 2);
+ this.changeActiveIndex(index);
+ return true;
+ }
+ },
+
+ dragend: function (p0, p1) {
+ if (this._dragStartX !== null) {
+ sounds.switchOff.play();
+ this._locks.decrement("move");
+ this._dragStartX = null;
+ var index = this.activeIndex;
+ this.activeIndex = (5 - this.menu.transform.x) / 2;
+ this.changeActiveIndex(index, true);
+ return true;
+ }
+ },
+
+ tap: function (p) {
+ p = this.layer0.worldFromDevice(p);
+ if (p.y > 6 && p.y < 7 && p.inside) {
+ var dx = Math.round((p.x - 5) / 2);
+ if (dx === 0) this.slam();
+ else if (dx < 0) handScene.movedLeft();
+ else if (dx > 0) handScene.movedRight();
+ var idx = this.activeIndex;
+ this.changeActiveIndex(this.activeIndex + dx, true);
+ if (idx !== this.activeIndex)
+ sounds.switch.play();
+ else
+ sounds.switchBroke.play();
+ return true;
+ }
+
+ },
+
+ doubletap: function (p) {
+ p = this.layer0.worldFromDevice(p);
+ if (p.x >= 4.5 && p.x <= 5.5 && p.y >= 6.0 && p.y <= 8.5) {
+ this.slam();
+ return true;
+ }
+ },
+ },
+
+ resetEverything: yuu.cmd(function () {
+ storage.clear();
+ yuu.director.stop();
+ start();
+ }, "reset all saved data"),
+
+ unlock: yuu.cmd(function (d) {
+ LEVELS.forEach(function (level) { wonLevel(level, d); });
+ yuu.director.pushPopScene(new MenuScene());
+ }, "<difficulty>", "unlock all levels to the given difficulty"),
+
+ KEYBINDS: {
+ left: "left",
+ right: "right",
+ up: "right",
+ down: "left",
+ w: "right",
+ a: "left",
+ s: "left",
+ d: "right",
+ shift: "slam",
+ space: "slam",
+ z: "slam",
+ "`+r+e": "resetEverything",
+ "`+u+e": "unlock easy",
+ "`+u+h": "unlock hard",
+ gamepadbutton0: "slam",
+ gamepadbutton8: "help",
+ gamepadbutton9: "slam",
+ gamepadbutton13: "slam",
+ gamepadbutton14: "left",
+ gamepadbutton15: "right",
+ }
+});
+
+
+var BOOK_APPEAR = {
+ 0: { set1: { y: 1.5, x: -1.5 },
+ tween: { bgQuad: { alpha: 0.75 }, $: { y: 0, x: 0 }, },
+ duration: 30 }
+};
+
+var BOOK_DISMISS = {
+ 0: { tween: { bgQuad: { alpha: 0 }, $: { y: 1.5, x: -1.5, } },
+ duration: 30 }
+};
+
+var KEYBOARD_PAGE = [0.25, 0.50, 0.50, 1.00];
+var POINTERS_PAGE = [0.25, 0.00, 0.50, 0.50];
+var GAMEPAD_PAGE = [0.00, 0.00, 0.25, 0.50];
+
+var BOOK_FORWARD = [
+ { 0: { set: { page2Quad: { color: [0.2, 0.2, 0.2, 1], texBounds: "page" } },
+ tween: { page1: { x: -1/3 / 2, scaleX: 0 },
+ page2: { x: +1/3 / 2 },
+ page2Quad: { color: [1, 1, 1, 1] },
+ }, duration: 15, easing: "linear" },
+ 15: { set: { page1Quad: { z: 0, texBounds: [0.25, 0.5, 0.00, 1] },
+ page2Quad: { z: 0, texBounds: "page" } },
+ tween: { page1: { x: -1/3, scaleX: -2/3 },
+ page2: { x: +1/3 }
+ }, duration: 15, easing: "linear" },
+ },
+
+ { 0: { tween: { page1: { x: -1/3 / 2 },
+ page2: { x: +1/3 / 2, scaleX: 0 }
+ }, duration: 15, easing: "linear" },
+ 15: { set: { page1Quad: { z: 0, texBounds: [0.25, 0.5, 0.00, 1] },
+ page2Quad: { z: 1, texBounds: [1.00, 0.5, 0.75, 1] } },
+ tween: { page1Quad: { color: [0.2, 0.2, 0.2, 1] },
+ page1: { x: 0 },
+ page2: { x: 0, scaleX: -2/3 },
+ }, duration: 15, easing: "linear" },
+ },
+
+ BOOK_DISMISS
+];
+
+var BOOK_BACKWARD = [
+ { 0: { tween: { page1: { x: -1/3 / 2, scaleX: 0 },
+ page2: { x: +1/3 / 2 },
+ }, duration: 15, easing: "linear" },
+ 15: { set: { page1Quad: { z: 1, texBounds: [0.50, 0.5, 0.75, 1] },
+ page2Quad: { z: 0 } },
+ tween: { page2Quad: { color: [0.2, 0.2, 0.2, 1] },
+ page1: { x: 0, scaleX: 2/3 },
+ page2: { x: 0 },
+ }, duration: 15, easing: "linear" },
+ },
+
+ { 0: { set: { page1Quad: { color: [0.2, 0.2, 0.2, 1] } },
+ tween: { page1Quad: { color: [1.0, 1.0, 1.0, 1] },
+ page1: { x: -1/3 / 2 },
+ page2: { x: +1/3 / 2, scaleX: 0 }
+ }, duration: 15, easing: "linear" },
+
+ 15: { set: { page1Quad: { z: 0, texBounds: [0.25, 0.5, 0.00, 1] },
+ page2Quad: { z: 0, texBounds: "page" } },
+ tween: { page1: { x: -1/3 },
+ page2: { x: +1/3, scaleX: 2/3 },
+ }, duration: 15, easing: "linear" },
+ },
+];
+
+BookScene = new yT(yuu.Scene, {
+ constructor: function () {
+ yuu.Scene.call(this);
+ var bg = new yuu.E(
+ new yuu.Transform().setScale([20, 20, 1]),
+ this.bgQuad = new yuu.QuadC()
+ .setColor([0, 0, 0, 0])
+ .setZ(-1));
+ this.page1 = new yuu.E(new yuu.Transform(),
+ this.page1Quad = new yuu.QuadC(BOOK));
+ this.page1Quad.texBounds = [0.50, 0.5, 0.75, 1];
+ this.page1Quad.z = 1;
+ this.page2 = new yuu.E(new yuu.Transform(),
+ this.page2Quad = new yuu.QuadC(BOOK));
+ this.page2Quad.texBounds = [0.25, 0.5, 0.50, 1];
+ this.page1.transform.scale = [2/3, 1, 1];
+ this.page2.transform.scale = [2/3, 1, 1];
+ this.entity0.attach(new yuu.Transform());
+ this.current = 0;
+ this._locks = new FlagSet("turn");
+ this.addEntities(bg, this.page1, this.page2);
+
+ this.dismissSound = new yuu.Instrument("@book-dismiss");
+ this.pageSounds = [new yuu.Instrument("@page-turn-1"),
+ new yuu.Instrument("@page-turn-2"),
+ new yuu.Instrument("@page-turn-3")];
+
+ this.ready = yuu.ready([this.dismissSound].concat(this.pageSounds));
+ },
+
+ help: yuu.cmd(function () {
+ this.skip();
+ }, "dismiss the help screen"),
+
+ licensing: yuu.cmd(function () {
+ var licensing = document.getElementById("yuu-licensing");
+ var parent = licensing.parentNode;
+ var spinner = document.createElement("div");
+ spinner.className = "yuu-spinner";
+ spinner.id = licensing.id;
+ parent.replaceChild(spinner, licensing);
+ Promise.all(
+ yf.map(yuu.GET,
+ [yuu.PATH + "data/license.txt", "data/license.txt"]))
+ .then(function (texts) {
+ var text = texts.join("\n-- \n\n");
+ var p = document.createElement("pre");
+ p.textContent = text;
+ p.id = spinner.id;
+ parent.replaceChild(p, spinner);
+ });
+ }, "why would you ever want to run this?"),
+
+ init: function () {
+ this._anim(BOOK_APPEAR);
+ storage.setFlag("instructions");
+ },
+
+ _anim: function (anim) {
+ this._locks.increment("turn");
+ // FIXME: Need hooks from animations to audio
+ var completion = this._locks.decrementer("turn");
+ switch (anim) {
+ case BOOK_DISMISS:
+ this.dismissSound.play();
+ completion = yuu.director.removeScene.bind(yuu.director, this);
+ break;
+ case BOOK_APPEAR:
+ sounds.bookAppear.play();
+ break;
+ default:
+ yuu.random.choice(this.pageSounds).play();
+ break;
+ }
+
+ var device = yuu.director.preferredDevice();
+ this.entity0.attach(new yuu.Animation(
+ anim, {
+ $: this.entity0.transform,
+ page: device === "keyboard" ? KEYBOARD_PAGE
+ : device === "gamepad" ? GAMEPAD_PAGE
+ : POINTERS_PAGE,
+ page1: this.page1.transform,
+ page2: this.page2.transform,
+ page1Quad: this.page1Quad,
+ page2Quad: this.page2Quad,
+ bgQuad: this.bgQuad
+ }, completion));
+ },
+
+ advance: yuu.cmd(function () {
+ if (this._locks.some("turn"))
+ return;
+ this._anim(BOOK_FORWARD[this.current++]);
+ }),
+
+ skip: yuu.cmd(function () {
+ if (this._locks.some("turn"))
+ return;
+ this._anim(BOOK_DISMISS);
+ }),
+
+ back: yuu.cmd(function () {
+ if (this._locks.some("turn"))
+ return;
+ if (this.current > 0)
+ this._anim(BOOK_BACKWARD[--this.current]);
+ }),
+
+ LOGOTYPE: new yuu.AABB(-0.16, -0.41, 0.12, -0.33),
+ COLOPHON: new yuu.AABB(-0.06, -0.41, 0.11, -0.28),
+
+ inputs: {
+ resize: function () {
+ var base = new yuu.AABB(-0.7, -0.55, 0.7, 0.55);
+ 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);
+ if (this.current === BOOK_FORWARD.length - 1
+ && this.LOGOTYPE.contains(p)) {
+ this.cursor = "pointer";
+ } else if (this.current === 0 && this.COLOPHON.contains(p)) {
+ this.cursor = "pointer";
+ } else if (this.current === 0 || p.x >= -0.2) {
+ this.cursor = "";
+ } else {
+ this.cursor = "W-resize";
+ }
+ },
+
+ tap: function (p) {
+ p = this.layer0.worldFromDevice(p);
+ if (this.current === BOOK_FORWARD.length - 1
+ && this.LOGOTYPE.contains(p)) {
+ yuu.openURL("http://www.yukkurigames.com/");
+ } else if (this.current === 0 && this.COLOPHON.contains(p)) {
+ yuu.director.showOverlay("colophon");
+ } else if (this.current === 0 || p.x >= -0.2) {
+ this.advance();
+ } else {
+ this.back();
+ }
+ return true;
+ },
+ swipeleft: function (event) { this.advance(); return true; },
+ swiperight: function (event) { this.back(); return true; },
+ dragleft: function (event) { this.advance(); return true; },
+ dragright: function (event) { this.back(); return true; },
+ swipeup: function (event) { this.skip(); return true; },
+ dragup: function (event) { this.skip(); return true; },
+
+ consume: yuu.Director.prototype.GESTURES
+ .concat(yuu.Director.prototype.CANVAS_EVENTS)
+ },
+
+ KEYBINDS: {
+ space: "advance",
+ shift: "advance",
+ z: "advance",
+ x: "advance",
+ right: "advance",
+ left: "back",
+ back: "skip",
+ escape: "skip",
+ gamepadbutton0: "advance",
+ gamepadbutton1: "skip",
+ gamepadbutton4: "back",
+ gamepadbutton5: "advance",
+ gamepadbutton8: "skip",
+ gamepadbutton9: "skip",
+ gamepadbutton14: "back",
+ gamepadbutton15: "advance",
+ }
+});
+
+var OUTER_FLIP_TICK = {
+ 0: { tween1: { yaw: "yaw" }, duration: 15 }
+};
+
+var CIRCLE_TO_BOTTOM = {
+ 0: { tween1: { pitch: Math.PI * 0.35, y: -0.3 }, duration: 35 }
+};
+
+var CIRCLE_TO_BACK = {
+ 0: { tween1: { pitch: Math.PI * 0.15, y: -0.1 }, duration: 35 }
+};
+
+var CIRCLE_INNER_RATCHET = {
+ 0: { tween1: { rotation: "rotation1" }, duration: 15 },
+ 10: { tween1: { rotation: "rotation2" }, duration: 10 },
+ 20: { tween1: { rotation: "rotation1" }, duration: 20,
+ easing: yuu.Tween.STEPPED(5) },
+ 40: { tween1: { rotation: "rotation2" }, duration: 15 }
+};
+
+var CIRCLE_INNER_WIND = {
+ 0: { tween1: { rotation: "rotation1" }, duration: 8 },
+ 15: { tween1: { rotation: "rotation2" }, duration: 20 },
+};
+
+var BACKGROUND_DRIFT = {
+ 0: [{ tween1: { yaw: Math.PI * 2 },
+ duration: 13 * 60 * 60, repeat: -Infinity, easing: "linear" },
+ { tween1: { scaleX: 0.5 },
+ duration: 11 * 60 * 60, repeat: -Infinity },
+ { tween1: { scaleY: 0.5 },
+ duration: 7 * 60 * 60, repeat: -Infinity }]
+};
+
+var HAND_TICK = {
+ 0: { tween1: { rotation: "rotation" }, duration: 6 }
+};
+
+var CHIMES = [
+ // Nearly all derived from
+ // http://www.clockguy.com/SiteRelated/SiteReferencePages/ClockChimeTunes.html
+ //
+ // All transposition & transcription errors are mine.
+
+ { name: "Westminster",
+ keys: ["D4", "E4"],
+ bars: ["0 2 1 -3",
+ "0 1 2 0",
+ "2 0 1 -3",
+ "2 1 0 -3",
+ "-3 1 2 0"]
+ },
+
+ { name: "Wittington",
+ keys: ["Eb4", "E4"],
+ bars: ["1 2 3 5 4 6 7 0",
+ "1 3 5 7 6 4 2 0",
+ "3 1 2 4 5 6 7 0",
+ "4 3 5 2 6 1 7 0",
+ "6 7 2 5 4 1 3 0",
+ "7 1 6 2 5 3 4 0",
+ "7 3 2 1 4 5 6 0",
+ "7 3 6 2 5 1 4 0",
+ "7 5 3 1 6 4 2 0",
+ "7 5 6 4 1 3 2 0",
+ "7 6 3 2 5 4 1 0",
+ "7 6 5 4 3 2 1 0"]
+ },
+
+ { name: "Canterbury",
+ keys: ["D4", "E4"],
+ bars: ["2 0 5 3 1 4",
+ "3 5 1 4 0 2",
+ "3 5 4 2 1 0",
+ "5 3 1 4 2 0",
+ "1 3 5 2 0 4",
+ "0 5 3 1 2 4",
+ "5 3 1 2 4 0"]
+ },
+
+ { name: "Trinity",
+ keys: ["F3", "D4"],
+ bars: ["5 4 3 2 1 0",
+ "2 4 3 1 2 0",
+ "5 3 4 2 1 0",
+ "4 3 2 1 5 2",
+ "5 0 4 3 2 1"]
+ },
+
+ /*
+ { name: "St. Michael's",
+ keys: ["F3", "C4"],
+ bars: ["7 6 5 4 3 2 1 0",
+ "7 1 2 3 6 4 5 0",
+ "4 3 2 5 1 6 7 0",
+ "6 7 2 3 1 4 5 0",
+ "4 6 2 7 3 1 5 0"]
+ },
+ */
+
+ { name: "Winchester",
+ keys: ["C4", "E4"],
+ bars: ["5 3 1 0 2 4",
+ "0 1 3 5 4 2",
+ "5 3 1 4 2 0",
+ "1 2 5 4 0 1",
+ "5 1 3 2 4 0"]
+ }
+
+];
+
+function third (s) {
+ return "Q " + s.replace(/([^ ]+ [^ ]+)/g, "$1 Z");
+}
+
+function silence (s) {
+ return "Q " + s.replace(/[^ ]+/g, "Z");
+}
+
+var TIMES1 = ["Q", "H", "H", "H", "H.", "H.", "W", "W"];
+var TIMES2 = ["Q", "Q", "Q", "Q", "Q", "Q", "Q", "Q", "Q",
+ third, third, "Q.", "Q.",
+ "H", "H"];
+var TIMES3 = ["Q", "Q", silence, silence,
+ third, third, third, third,
+ "Q.", "Q.",
+ "H", "H"];
+
+function deck (pack, random) {
+ random = random || yuu.random;
+ var stock = [];
+ return function () {
+ if (stock.length === 0)
+ stock = random.shuffle(pack.slice());
+ return stock.pop();
+ };
+}
+
+function generateScore () {
+ var chimes = yuu.random.choice(CHIMES);
+ var bar = deck(chimes.bars);
+ function draw (t) {
+ return yf.isFunction(t) ? t(bar()) : t + " " + bar();
+ }
+
+ function line (times) {
+ return yf.map(draw, yuu.random.shuffle(times)).join(" ");
+ }
+
+ var track = "{ - W HZ " + line(TIMES1)
+ + " { W HZ Z " + line(TIMES2)
+ + " { W HZ Z Z I Z " + line(TIMES3);
+ var key = yuu.random.choice(chimes.keys);
+ yuu.log("messages", "Playing " + chimes.name + " in " + key + " major.");
+ var score = yuu.parseScore(track, yuu.Scales.MAJOR, key);
+ score.key = key;
+ return score;
+}
+
+CircleScene = yT(yuu.Scene, {
+ constructor: function () {
+ yuu.Scene.call(this);
+ this.layer0.resize(-0.6, -0.6, 1.2, 1.2);
+ var arm = this.arm = new yuu.E(new yuu.Transform());
+ this.outer = new yuu.E(
+ new yuu.Transform([Math.sqrt(2) / 5, -Math.sqrt(2) / 5, 0]),
+ this.outerQuad = new yuu.QuadC(new yuu.Material("@circle-outer"))
+ .setZ(1)
+ .setLuminance(0.4)
+ .setSize([0.35417, 0.35417]));
+ arm.addChild(this.outer);
+
+ var rim = new yuu.E(
+ new yuu.Transform(),
+ this.rimQuad = new yuu.QuadC(new yuu.Material("@circle-rim"))
+ .setLuminance(0.2));
+ var inner = this.inner = new yuu.E(
+ new yuu.Transform(),
+ this.innerQuad = new yuu.QuadC(new yuu.Material("@circle-inner"))
+ .setLuminance(0.3));
+
+ var NOISY_QUADS = new yuu.ShaderProgram(
+ ["@noise.glsl", "@noisyquads"], ["yuu/@color"]);
+
+ var bgMat = new yuu.Material(
+ yuu.Texture.DEFAULT, NOISY_QUADS, { range: 0.8 });
+ bgMat.uniforms.cut = yf.volatile(cycler(100000));
+ var DIM = 16;
+ var batch = new yuu.QuadBatchC(DIM * DIM);
+ batch.material = bgMat;
+ var bg = new yuu.E(new yuu.Transform(), batch);
+ yf.irange(function (x) {
+ yf.irange(function (y) {
+ var quad = batch.createQuad();
+ quad.size = [1/4, 1/4];
+ quad.position = [(x - DIM / 2) * 1/4,
+ (y - DIM / 2) * 1/4];
+ quad.color = [0.12, 0.08, 0.16];
+ quad.texBounds = yf.repeat(x * DIM + y, 4);
+ }, DIM);
+ }, DIM);
+
+ this.entity0.addChild(bg);
+ this.entity0.attach(new yuu.Animation(
+ BACKGROUND_DRIFT, { $: bg.transform }));
+
+ this.ground = new yuu.E(new yuu.Transform());
+ this.ground.addChildren(rim, inner, arm);
+ this.entity0.addChild(this.ground);
+
+ this.music = yuu.audio.createGain();
+ this.music.gain.value = 0.3;
+ this.music.connect(yuu.audio.music);
+ this._finished = false;
+
+ this.ready = yuu.ready([
+ this.outerQuad.material,
+ this.innerQuad.material,
+ this.rimQuad.material,
+ bgMat.ready
+ ]);
+ },
+
+ help: yuu.cmd(function () {
+ yuu.director.pushScene(new BookScene());
+ }, "bring up the help screen"),
+
+ yuu: yuu.cmd(function () {
+ this.outerQuad.material = new yuu.Material("@circle-outer-ee");
+ }, "yuu~"),
+
+ KEYBINDS: {
+ slash: "help",
+ f1: "help",
+ gamepadbutton6: "help",
+ f10: "showOverlay preferences",
+ "shift+y+u+`": "yuu",
+ "gamepadbutton10+gamepadbutton11": "yuu",
+ },
+
+ inputs: {
+ resize: function () {
+ var vp = new yuu.AABB(-0.6, -0.6, 0.6, 0.6)
+ .matchAspectRatio(yuu.viewport);
+ this.layer0.resize(vp.x0, vp.y0, vp.w, vp.h);
+ }
+ },
+
+ toBottom: function () {
+ this.entity0.attach(
+ new yuu.Animation(CIRCLE_TO_BOTTOM, { $: this.ground.transform }));
+ },
+
+ wind: function () {
+ var rot1 = this.inner.transform.rotation;
+ quat.rotateZ(rot1, rot1, Math.PI / (2 * Math.E));
+ var rot2 = quat.rotateX(quat.create(), rot1, -Math.PI / 2);
+ quat.rotateY(rot2, rot2, Math.PI / 2);
+ quat.rotateX(rot2, rot2, -Math.PI / 2);
+ this.entity0.attach(
+ new yuu.Animation(CIRCLE_INNER_WIND, {
+ $: this.inner.transform,
+ rotation1: rot1,
+ rotation2: rot2
+ }));
+ this.tension = 0.5;
+ this.reversed = 0;
+ var score = [];
+ score.key = this.score && this.score.key;
+ this.score = score;
+ },
+
+ _musicSchedule: function (count) {
+ var t = yuu.director.currentAudioTime;
+ var note;
+
+ if (this._finished) {
+ if (this._finished === "won" && this.score.key) {
+ var score = yuu.parseScore(
+ yuu.random.choice([
+ "1 3 2 Z 0 { - 1 Z 2 Z 0",
+ "1 2 3 Z 0 { - 1 Z 3 Z 0",
+ "0 1 2 Z 4 { - 0 Z 2 Z 4",
+ ]),
+ yuu.Scales.MAJOR, this.score.key);
+ while ((note = score.shift())) {
+ sounds.chime.createSound(
+ yuu.audio,
+ t + note.time / 4,
+ note.hz,
+ 1.0, note.duration
+ ).connect(this.music);
+ }
+ }
+ this._finished = false;
+ return false;
+ }
+
+ if (!(this.score && this.score.length)) {
+ this.score = generateScore();
+ this.playing = 0;
+ }
+
+ ++this.playing;
+ while (this.score.length && this.score[0].time < this.playing) {
+ note = this.score.shift();
+ sounds.chime.createSound(
+ yuu.audio,
+ t + note.time % 1 + yuu.random.gauss(0, 0.015),
+ note.hz,
+ 1.0, note.duration
+ ).connect(this.music);
+ }
+
+ if ((this.tension *= 0.95) > 1) {
+ this.tension /= 2;
+ sounds.winding.createSound(yuu.audio, t, 0, 1.0, 1.0)
+ .connect(this.music);
+ var flip = !this.outer.transform.yaw * yuu.random.randsign(Math.PI);
+ this.entity0.attach(
+ new yuu.Animation(OUTER_FLIP_TICK, {
+ $: this.outer.transform,
+ yaw: flip
+ }));
+ } else {
+ [sounds.tick, sounds.tock][count & 1]
+ .createSound(yuu.audio, t, 0, 0.5, 1.0)
+ .connect(this.music);
+ }
+
+ this.clockTick(this.reversed-- > 0 ? TICK_REV : TICK_ROT);
+
+ return true;
+ },
+
+ clockTick: function (amount, anim) {
+ var rot = this.arm.transform.rotation;
+ quat.multiply(rot, rot, amount || TICK_ROT);
+ this.arm.attach(new yuu.Animation(
+ anim || HAND_TICK,
+ { $: this.arm.transform, rotation: rot }));
+ },
+
+ toBack: function () {
+ this.wind();
+ this.entity0.attach(
+ new yuu.Animation(CIRCLE_TO_BACK, { $: this.ground.transform }));
+
+ this.playing = 4;
+ this.arm.attach(
+ new yuu.Ticker(this._musicSchedule.bind(this), 60));
+ },
+
+ win: function () {
+ this._finished = "won";
+ this.wind();
+ this.entity0.attach(
+ new yuu.Animation(FLASH, { $: this.innerQuad }),
+ new yuu.Animation(FLASH, { $: this.rimQuad }, null, 32),
+ new yuu.Animation(FLASH, { $: this.outerQuad }, null, 48)
+ );
+ },
+
+ lose: function () {
+ this._finished = "lose";
+ var rot1 = this.inner.transform.rotation;
+ quat.rotateZ(rot1, rot1, -Math.PI / Math.E);
+ var rot2 = quat.rotateZ(quat.create(), rot1, Math.PI / Math.E);
+ this.entity0.attach(
+ new yuu.Animation(CIRCLE_INNER_RATCHET, {
+ $: this.inner.transform,
+ rotation1: rot1,
+ rotation2: rot2
+ }));
+ sounds.regear.play();
+ },
+
+ rotated: function () {
+ this.tension += yuu.random.uniform(0.1);
+ },
+
+ slam: function () {
+ this.tension += yuu.random.uniform(0.2);
+ },
+
+ reverse: function () {
+ this.tension -= yuu.random.uniform(0.1);
+ this.reversed = Math.max(this.reversed, 0) + 1;
+ }
+
+});
--- /dev/null
+/* 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')));
--- /dev/null
+/* 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')));
--- /dev/null
+/* 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
+ */
+
+ 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);
+ });
+ }
+
+ return new Promise(function (resolve) {
+ // TODO: Some kind of loading progress bar.
+ initOptions = options || {};
+ yuu.log("messages", "Initializing Yuu engine.");
+ var promises = [];
+ yf.each(function (hook) {
+ promises.push(hook.call(yuu, initOptions));
+ }, initHooks);
+ 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);
--- /dev/null
+ 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.
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Fira Mono';
+ src: url('../../ext/FiraMono-Bold.woff');
+ font-weight: 700;
+ font-style: normal;
+}
+
+pre, tt, code, kbd {
+ font-family: 'Fira Mono', FontAwesome, monospace;
+}
+
+body {
+ overflow: hidden;
+ margin: 0;
+ padding: 0;
+ font-family: 'Fira Sans', FontAwesome, sans-serif;
+}
+
+#yuu-canvas {
+ /* Specifying only width/height gives incorrect results on Chrome
+ 33.0.1750.152 when fullscreen on > 1 devicePixelRatio. The
+ canvas takes on the correct size, but is centered in a page of
+ e.g. 2x for 2 DPR so you only see the top-left quadrant.
+
+ Specifying only top/bottom/left/right 0 also breaks, because in the
+ absence of a CSS size the browser tries to set the client size to
+ the canvas buffer size, which means it grows/shrinks by the DPR
+ every resize event.
+
+ Specifying all six attributes makes it work as desired. */
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ top: 0;
+}
+
+#yuu-canvas:focus {
+ outline: inherit;
+}
+
+#yuu-licensing {
+ padding-left: 2em;
+ padding-right: 2em;
+ text-align: center;
+ font-size: 0.7em;
+}
+
+pre#yuu-licensing {
+ text-align: left;
+}
+
+[data-yuu-command] {
+ cursor: pointer;
+}
+
+/* Animations */
+
+.yuu-from-top-right {
+ transform: translate(50vw, -110%) !important;
+ -webkit-transform: translate(50vw, -110%) !important;
+}
+
+.yuu-from-top {
+ transform: translate(-50%, -110%) !important;
+ -webkit-transform: translate(-50%, -110%) !important;
+}
+
+.yuu-fade {
+ opacity: 0 !important;
+}
+
+.yuu-squish {
+ margin-left: 0 !important;
+ margin-right: 0 !important;
+ max-width: 0 !important;
+ min-width: 0 !important;
+ overflow: hidden !important;
+ padding-left: 0em !important;
+ padding-right: 0em !important;
+}
+
+/* Toasts are short-lived feedback to user actions. */
+.yuu-toast {
+ pointer-events: none;
+ background-color: rgba(50, 50, 50, 0.5);
+ border-radius: 0.2em;
+ border: solid rgba(255, 255, 255, 0.5) 1px;
+ color: #eee;
+ display: table-cell;
+ float: right;
+ font-size: 3em;
+ margin-left: 0.125em;
+ margin-right: 0.125em;
+ margin-top: 0.25em;
+ max-width: 60%;
+ /* Minimum size is a square: 1.25 + 0.125 * 2 for padding = 1.5em,
+ same as the line height. */
+ min-width: 1.4em;
+ padding: 0 0.125em;
+ padding-top: 0.15em;
+ position: relative;
+ text-align: center;
+ transition: all 0.5s;
+ -webkit-transition: all 0.5s;
+}
+
+/* Overlays are hidden HTML-based scenes that the director can load.
+ These appear over the game, and are modal. The primary use case is
+ configuration menus, copyright information, error feedback,
+ etc. */
+
+.yuu-overlay {
+ background-color: rgba(50, 50, 50, 0.9);
+ border-radius: 0.2em;
+ border: solid rgba(255, 255, 255, 0.9) 1px;
+ color: #eee;
+ display: none;
+ width: 60%;
+ max-width: 600px;
+ min-width: 300px;
+ margin-left: auto;
+ margin-right: auto;
+ left: 50%;
+ max-height: 80%;
+ overflow: auto;
+ padding: 0 1em 1em 1em;
+ position: fixed;
+ transform: translate(-50%, 10vh) scale(1, 1);
+ -webkit-transform: translate(-50%, 10vh) scale(1, 1);
+ transition: transform 0.3s, opacity 0.3s;
+ -webkit-transition: -webkit-transform 0.3s, opacity 0.3s;
+}
+
+/* Overlays are focusable but should not show it - they are always
+ somewhere in the event tree when visible. */
+.yuu-overlay:focus {
+ outline: inherit;
+}
+
+.yuu-overlay h1 {
+ font-size: 1.2em;
+ font-weight: normal;
+ text-align: center;
+}
+
+.yuu-overlay h2 {
+ font-size: 1.1em;
+ font-weight: normal;
+}
+
+.yuu-overlay hr {
+ margin-bottom: 1em;
+ margin-top: 0.5em;
+}
+
+/* For consistency overlays use custom CSS for controls, which
+ means we need a default focused behavior. */
+.yuu-overlay *:focus {
+ outline: solid grey 1px;
+}
+
+div[data-yuu-command=dismiss] {
+ font-size: 1.5em;
+ width: 1.25em;
+ height: 1.25em;
+ text-align: center;
+ position: fixed;
+ margin-left: -0.6667em;
+}
+
+div[data-yuu-command=dismiss]:after {
+ content: "\f00d";
+}
+
+/* Table layout for options screens. In general, two or three columns,
+ the leftmost is a simple control, the middle/last is a label, and
+ the last is a more complicated control like a range or select
+ dropdown. */
+
+.yuu-options {
+ border-collapse: separate;
+ border-spacing: 0.25em;
+}
+
+.yuu-options td:first-child {
+ min-width: 2em;
+ white-space: nowrap;
+}
+
+.yuu-options td:last-child {
+ width: 100%;
+}
+
+/* De/re-style checkboxes. This means hiding the actual
+ checkbox and making it tiny, and instead filling in the
+ label immediately after it. */
+input[type=checkbox][data-yuu-command] {
+ max-width: 0;
+ opacity: 0;
+}
+
+input[type=checkbox][data-yuu-command] + label[for] {
+ cursor: pointer;
+ display: inline-block;
+ text-align: center;
+ width: 1.3333em;
+ font-size: 1.25em;
+}
+
+input[type=checkbox][data-yuu-command] + label[for]:before {
+ display: inline-block;
+ padding-top: 0.2em;
+}
+
+input[type=checkbox][data-yuu-command] + label[for]:before {
+ content: "\f096";
+}
+
+input[type=checkbox][data-yuu-command]:checked + label[for]:before {
+ content: "\f046";
+}
+
+input[type=checkbox][data-yuu-command]:focus + label[for] {
+ outline: solid grey 1px;
+}
+
+/* De/re-style ranges. */
+input[type=range][data-yuu-command] {
+ -webkit-appearance: none;
+ background-color: gray;
+}
+
+input[type=range][data-yuu-command]::-moz-range-track {
+ background: gray;
+ border: none;
+ outline: none;
+}
+
+input[type=range][data-yuu-command]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ background-color: #444;
+ width: 1.5em;
+ height: 1em;
+}
+
+input[type=range][data-yuu-command]::-moz-range-thumb {
+ border: none;
+ background-color: #444;
+ width: 1.5em;
+ height: 1em;
+}
+
+/* Special-case icons for the mute checkbox. */
+
+input[type=checkbox][data-yuu-command=mute]:checked + label[for]:before {
+ content: "\f026";
+}
+
+input[type=checkbox][data-yuu-command=mute] + label[for]:before {
+ content: "\f028";
+}
+
+@-moz-keyframes spin {
+ from { -moz-transform: rotate(0deg); }
+ to { -moz-transform: rotate(360deg); }
+}
+
+@-webkit-keyframes spin {
+ from { -webkit-transform: rotate(0deg); }
+ to { -webkit-transform: rotate(360deg); }
+}
+
+@keyframes spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+}
+
+.yuu-spinner:after {
+ content: "â—”";
+ -webkit-animation: spin 1s linear infinite;
+ -moz-animation: spin 1s linear infinite;
+ animation: spin 1s linear infinite;
+ display: inline-block;
+}
+
+dl {
+ text-align: center;
+}
+
+dt {
+ margin-top: 1em;
+ margin-bottom: 0.25em;
+ font-size: 0.8em;
+ font-weight: 200;
+}
+
+dd {
+ margin-left: 0;
+}
--- /dev/null
+/* 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");
+
+ // It's vaguely plausible to want a director without any scenes
+ // (only entity0 and the canvas), which means the renderer is not
+ // required.
+ if (!yuu.E) require("./ce");
+ if (!yuu.InputState) require("./input");
+ if (!yuu.Material) require("./gfx");
+
+ yuu.Director = yT({
+ constructor: function (commandStack, input, tickHz) {
+ /** Manage and update a set of Scenes
+
+ The director is responsible for calling two functions
+ regularly on the Scene instances it controls, `tick` and
+ `render`. `tick` is called at a regular interval (or at
+ least pretends to be called at one, browser scheduler
+ permitting), and `render` is called when the browser asks
+ for a new display frame.
+ */
+ this._scenes = [];
+ this.entity0 = new yuu.E();
+ this._commandStack = commandStack || yuu.commandStack;
+ this.input = input || new yuu.InputState([yuu.defaultKeybinds]);
+ this._events = {};
+ this._tickCount = 0;
+ this._timerStart = 0;
+ this._audioOffset = 0;
+ this._rafId = null;
+ this._tickHz = tickHz || 60;
+ this._afterRender = [];
+
+ this.commands = yuu.extractCommands(this);
+ this._commandStack.push(this.commands);
+ this._dogesture = this.__dogesture.bind(this);
+ this._gesture = null;
+ this._resized = false;
+ this._toasts = {};
+ this._devices = {};
+ },
+
+ pushScene: function (scene) {
+ /** Add a Scene onto the director's stack */
+ this.insertScene(scene, this._scenes.length);
+ },
+
+ popScene: function () {
+ /** Remove the top scene from the director's stack */
+ this.removeScene(yf.last(this._scenes));
+ },
+
+ pushPopScene: function (scene) {
+ /** Replace the top scene on the stack */
+ this.popScene();
+ this.pushScene(scene);
+ },
+
+ insertScene: function (scene, idx) {
+ var scenes = this._scenes.slice();
+ scenes.splice(idx, 0, scene);
+ this._scenes = scenes;
+ this._commandStack.insertBefore(
+ scene.commands,
+ this._scenes[idx + 1] && this._scenes[idx + 1].commands);
+ this.input.insertBefore(
+ scene.keybinds,
+ this._scenes[idx + 1] && this._scenes[idx + 1].keybinds);
+ scene.init(this);
+ if (scene.inputs.resize)
+ scene.inputs.resize.call(scene, yuu.canvas);
+ },
+
+ insertUnderScene: function (scene, over) {
+ return this.insertScene(scene, this._scenes.indexOf(over));
+ },
+
+ removeScene: function (scene) {
+ /** Remove a Scene onto the director's stack */
+ this._scenes = yf.without(this._scenes, scene);
+ scene.done();
+ this.input.remove(scene.keybinds);
+ this._commandStack.remove(scene.commands);
+ },
+
+ DOCUMENT_EVENTS: [ "keydown", "keyup", "visibilitychange" ],
+
+ CANVAS_EVENTS: [ "mousemove", "mousedown", "mouseup" ],
+
+ WINDOW_EVENTS: [ "popstate", "resize", "pageshow",
+ "yuugamepadbuttondown", "yuugamepadbuttonup" ],
+
+ GESTURES: [
+ "touch", "release", "hold", "tap", "doubletap",
+ "dragstart", "drag", "dragend", "dragleft", "dragright",
+ "dragup", "dragdown", "swipe", "swipeleft", "swiperight",
+ "swipeup", "swipedown", "pinch", "pinchin", "pinchout"
+ ],
+
+ _dispatchSceneInput: function (name, args) {
+ var scenes = this._scenes;
+ for (var i = scenes.length - 1; i >= 0; --i) {
+ var scene = scenes[i];
+ var handler = scene.inputs[name];
+ if (handler && handler.apply(scene, args))
+ return true;
+
+ // FIXME: This may be a heavy ad hoc solution for the
+ // multiple input layer problems in pwl6. Something
+ // like this isn't required or allowed for e.g.
+ // individual keys, why not?
+ //
+ // REALLY FIXME: This doesn't even work correctly for
+ // joystick events, a) because they're prefixed and b)
+ // because they are in WINDOW_EVENTS so you have to
+ // manually enumerate them or you also ignore e.g.
+ // resize.
+ else if (scene.inputs.consume
+ && yf.contains(scene.inputs.consume, name))
+ return false;
+ }
+ return false;
+ },
+
+ // Aside from the performance considerations, deferring
+ // resizing by multiple frames fixes mis-sizing during startup
+ // and fullscreen transition in node-webkit on Windows. (And
+ // probably similar bugs in other configurations.)
+ _doresize: yf.debounce(function () {
+ this._resized = true;
+ }, 500),
+
+ _dovisibilitychange: function (event) {
+ if (event.target.hidden)
+ this._stopRender();
+ else
+ this._startRender();
+ },
+
+ _dopageshow: function (event) {
+ if (!history.state)
+ history.pushState("yuu director", "");
+ this._stopRender();
+ if (!document.hidden)
+ this._startRender();
+ },
+
+ _dopopstate: function (event) {
+ var cmds = [];
+ if (this._dispatchSceneInput("back", [])
+ || (cmds = this.input.change("back"))) {
+ history.pushState("yuu director", "");
+ yf.each.call(this, this.execute, cmds);
+ yuu.stopPropagation(event, true);
+ } else {
+ history.back();
+ }
+ },
+
+ __dogesture: function (event) {
+ this._updateCaps(event.gesture.srcEvent.type.toLowerCase(), true);
+ var type = event.type.toLowerCase();
+ var p0 = yuu.deviceFromCanvas(event.gesture.startEvent.center);
+ var p1 = yuu.deviceFromCanvas(event.gesture.center);
+ if (this._dispatchSceneInput(type, [p0, p1]))
+ yuu.stopPropagation(event, true);
+ },
+
+ // TODO: This munges events, but also, InputState's mousemove
+ // etc. munge events, in a slightly different but still
+ // related and fragile way.
+ //
+ // Additionally, things run in a Scene handler won't
+ // affect the InputState's internal state - good for
+ // avoiding bind execution, bad for consistency. Even if
+ // a scene handles e.g. "keydown a", input.pressed.a
+ // should be true.
+ //
+ // This is compounded by the lack of actual use cases for any
+ // of the non-gesture events other than "back" and
+ // "mousemove".
+
+ _ARGS_FOR: {
+ keydown: function (event) {
+ return [yuu.keyEventName(event), {}];
+ },
+ keyup: function (event) {
+ return [yuu.keyEventName(event), {}];
+ },
+ mousemove: function (event) {
+ return [yuu.deviceFromCanvas(event)];
+ },
+ mouseup: function (event) {
+ return [event.button, yuu.deviceFromCanvas(event)];
+ },
+ mousedown: function (event) {
+ return [event.button, yuu.deviceFromCanvas(event)];
+ },
+ gamepadbuttondown: function (event) {
+ return [event.detail.gamepad,
+ event.detail.button];
+ },
+ gamepadbuttonup: function (event) {
+ return [event.detail.gamepad, event.detail.button];
+ },
+ },
+
+ _updateCaps: function (type, definite) {
+ if (type.startsWith("mouse")) {
+ if (this._devices.mouse === undefined || definite)
+ this._devices.mouse = Date.now();
+ this._devices.touch = this._devices.touch || false;
+ } else if (type.startsWith("touch")) {
+ this._devices.mouse = this._devices.mouse || false;
+ this._devices.touch = Date.now();
+ this._devices.keyboard = this._devices.keyboard || false;
+ } else if (type.startsWith("key")) {
+ this._devices.keyboard = Date.now();
+ } else if (type.startsWith("gamepad")) {
+ this._devices.gamepad = Date.now();
+ this._devices.touch = this._devices.touch || false;
+ }
+ },
+
+ preferredDevice: function (options) {
+ options = options || ["keyboard", "touch", "mouse", "gamepad"];
+ var devices = this._devices;
+ var best = yf.foldl(function (best, option) {
+ var dbest = devices[best];
+ var doption = devices[option];
+ return dbest === undefined && doption ? option
+ : doption > dbest ? option : best;
+ }, options);
+ for (var i = 0; devices[best] === false && i < options.length; ++i)
+ if (devices[options[i]] !== false)
+ best = options[i];
+ return best;
+ },
+
+ _doevent: function (event) {
+ var type = event.type.toLowerCase();
+ if (type.startsWith("yuu"))
+ type = type.slice(3);
+ var args = this._ARGS_FOR[type](event);
+ var cmds;
+ this._updateCaps(type, false);
+ if (this._dispatchSceneInput(type, args))
+ yuu.stopPropagation(event, true);
+ else if ((cmds = this.input[type].apply(this.input, args))) {
+ var ctx = yf.last(args);
+ yf.each.call(this, this.execute, cmds, yf.repeat(ctx, cmds.length));
+ yuu.stopPropagation(event, true);
+ }
+ },
+
+ _addListener: function (target, name, handler) {
+ handler = (handler || this["_do" + name] || this._doevent).bind(this);
+ this._events[name] = { target: target, handler: handler };
+ target.addEventListener(name, handler);
+ },
+
+ _removeListener: function (name) {
+ this._events[name].target.removeEventListener(
+ name, this._events[name].handler);
+ delete this._events[name];
+ },
+
+ tickHz: {
+ get: function () { return this._tickHz; },
+ set: function (hz) {
+ this._tickHz = hz;
+ this._tickCount = 0;
+ this._timerStart = 0;
+ }
+ },
+
+ currentTime: { get: function () {
+ return this._timerStart + 1000 * this._tickCount / this._tickHz;
+ } },
+
+ currentAudioTime: { get: function () {
+ /** Audio time of the current tick.
+ */
+ return (this.currentTime + this._audioOffset) / 1000;
+ } },
+
+ _startRender: function () {
+ if (this._rafId !== null)
+ return;
+ this._tickCount = 0;
+ this._timerStart = 0;
+ // GNU/Linux with node-webkit sizes things incorrectly on
+ // startup, so force a recalculating as soon as the render
+ // loop runs.
+ this._resized = true;
+ var director = this;
+ this._rafId = window.requestAnimationFrame(function _ (t) {
+ if (!director._timerStart) {
+ director._timerStart = t;
+ director._audioOffset = yuu.audio
+ ? yuu.audio.currentTime * 1000 - t
+ : 0;
+ }
+ director._rafId = window.requestAnimationFrame(_);
+ director.render(t);
+ });
+ },
+
+ _stopRender: function () {
+ if (this._rafId !== null)
+ window.cancelAnimationFrame(this._rafId);
+ this._rafId = null;
+ },
+
+ start: function () {
+ /** Begin ticking and rendering scenes */
+ yf.each(this._addListener.bind(this, window),
+ this.WINDOW_EVENTS);
+ yf.each(this._addListener.bind(this, document),
+ this.DOCUMENT_EVENTS);
+ yf.each(this._addListener.bind(this, yuu.canvas),
+ this.CANVAS_EVENTS);
+
+ this._gesture = typeof Hammer !== "undefined"
+ ? new Hammer(yuu.canvas, { "tap_always": false,
+ "hold_timeout": 300 })
+ : { on: function () {}, off: function () {} };
+ this._gesture.on(this.GESTURES.join(" "), this._dogesture);
+
+ // Treat the back button as another kind of input event. Keep
+ // a token state on the stack to catch the event, and if no
+ // scene handles it, just go back one more.
+ //
+ // Because of browser session restore, state might already be
+ // on the stack. Throw it out if so.
+ if (!history.state)
+ history.pushState("yuu director", "");
+ else
+ history.replaceState("yuu director", "");
+ this._startRender();
+ },
+
+ stop: function () {
+ /** Stop ticking and rendering, clear all scenes */
+ this._stopRender();
+ yf.eachr(function (scene) { scene.done(); }, this._scenes);
+ this._scenes = [];
+ yf.each.call(this, this._removeListener, Object.keys(this._events));
+ this._gesture.off(this.GESTURES.join(" "), this._dogesture);
+ this._gesture = null;
+ },
+
+ message: function () {
+ /** Send a message to all entities/scenes, bottom to top */
+ this.entity0.message.apply(this.entity0, arguments);
+ var scenes = this._scenes;
+ for (var i = 0; i < scenes.length; ++i)
+ scenes[i].message.apply(scenes[i], arguments);
+ },
+
+ _takeScreenshot: function () {
+ var date = (new Date()).toLocaleString();
+ try {
+ yuu.downloadURL(
+ yuu.canvas.toDataURL("image/png"),
+ document.title + " (" + date + ").png");
+ this.toast("\uf030", 0.5, "screenshot");
+ } catch (exc) {
+ var dialog = yuu.showError(exc);
+ if (dialog)
+ this.showOverlay(dialog.id);
+ }
+ },
+
+ render: function (t) {
+ /** Tick and render all scenes, bottom to top */
+ var i;
+
+ if (this._resized) {
+ this._dispatchSceneInput("resize", [yuu.canvas]);
+ this._resized = false;
+ }
+
+ t = t - this._timerStart;
+ var oneTick = 1000.0 / this._tickHz;
+ while (oneTick * this._tickCount < t)
+ this.message("tick", oneTick * this._tickCount++, oneTick);
+ this.message("tock", (t % oneTick) / oneTick);
+
+ yuu.gl.clear(yuu.gl.COLOR_BUFFER_BIT);
+ var scenes = this._scenes;
+ var cursor = "default";
+ for (i = 0; i < scenes.length; ++i) {
+ scenes[i].render();
+ cursor = scenes[i].cursor || cursor;
+ }
+
+ if (cursor !== yuu.canvas.style.cursor)
+ yuu.canvas.style.cursor = cursor;
+
+ for (i = 0; i < this._afterRender.length; ++i)
+ this._afterRender[i]();
+ this._afterRender.length = 0;
+ },
+
+ toast: yuu.cmd(function (markup, duration, id) {
+ var toasts = this._toasts;
+ id = "yuu-toast-" + id;
+ var toast = id ? document.querySelector("#" + id) : null;
+ duration = duration || 4;
+
+ if (!toast) {
+ toast = document.createElement("div");
+ toast.id = id;
+ toast.className = "yuu-toast yuu-fade";
+ document.body.appendChild(toast);
+ }
+ if (toasts[id]) {
+ clearTimeout(toasts[id]);
+ delete toasts[id];
+ }
+ toast.innerHTML = markup;
+ yuu.afterAnimationFrame(function () {
+ toast.className = "yuu-toast";
+ });
+
+ var to = setTimeout(function () {
+ toast.className = "yuu-toast yuu-fade";
+ toast.addEventListener("transitionend", function fade () {
+ toast.removeEventListener("transitionend", fade);
+ // Stop if the toast was revived between the
+ // timeout event and transition end, i.e. while it
+ // was fading out.
+ if (id && toasts[id] !== to)
+ return;
+ toast.className += " yuu-squish";
+ toast.addEventListener("transitionend", function squish () {
+ toast.removeEventListener("transitionend", squish);
+ if (id && toasts[id] === to) {
+ delete toasts[id];
+ toast.parentNode.removeChild(toast);
+ }
+ });
+ });
+ }, duration * 1000);
+ if (id)
+ toasts[id] = to;
+ }, "<markup> <duration?>", "show a toast message"),
+
+ showOverlay: yuu.cmd(function (id, animation, dismissKeys) {
+ var overlay = new yuu.Overlay(
+ document.getElementById(id), animation, dismissKeys);
+ this.pushScene(overlay);
+ }, "<overlay ID> <animation?> <dismissKeys?>", "show an HTML overlay"),
+
+ screenshot: yuu.cmd(function () {
+ this._afterRender.push(this._takeScreenshot.bind(this));
+ }, "take a screenshot"),
+
+ fullscreen: yuu.cmd(function (v) {
+ if (arguments.length > 0) {
+ yuu.fullscreen = !!v;
+ // Most browser/OS combinations will drop key events
+ // during the "transition to fullscreen" animation.
+ // This means the key to enter fullscreen is recorded
+ // as "stuck down" inside the input code, and pressing
+ // it again won't trigger exiting fullscreen, just
+ // clear the stuck bit - you would have to press it
+ // *again* to actually transition out of fullscreen.
+ //
+ // Obviously this is not good, and the chance of the
+ // player actually trying to do something meaningful
+ // during fullscreen transition is unlikely, so just
+ // blow away the internal state and act like
+ // everything the player does is new.
+ this.input.reset();
+ }
+ return yuu.fullscreen;
+ }, "<enabled?>", "enable/disable fullscreen"),
+
+ execute: { proxy: "_commandStack.execute" },
+ });
+
+ yuu.Scene = yT({
+ constructor: function () {
+ /** A collection of entities, a layer, keybinds, and commands
+
+ The single argument is as function that will be scalled
+ during construction with `this` as the newly-created
+ scene.
+
+ */
+ this.entity0 = new yuu.E();
+ this.layer0 = new yuu.Layer();
+ this.keybinds = new yuu.KeyBindSet(this.KEYBINDS);
+ this.commands = yuu.extractCommands(this);
+ },
+
+ addEntity: { proxy: "entity0.addChild" },
+ removeEntity: { proxy: "entity0.removeChild" },
+ addEntities: { proxy: "entity0.addChildren" },
+ removeEntities: { proxy: "entity0.removeChildren" },
+ message: { proxy: "entity0.message" },
+
+ init: function (director) {
+ /** Called when the director starts this scene */
+ },
+
+ done: function () {
+ /** Called when the director stops this scene */
+ },
+
+ render: function () {
+ /** Queue renderables from the entities and render each layer */
+ this.message("queueRenderables", this.layer0.rdros);
+ this.layer0.render();
+ this.layer0.clear();
+ },
+
+ inputs: {},
+ KEYBINDS: {}
+ });
+
+ yuu.Overlay = yT(yuu.Scene, {
+ constructor: function (element, animation, dismissKeys) {
+ yuu.Scene.call(this);
+ this.dismissKeys = dismissKeys
+ || (element.getAttribute("data-yuu-dismiss-key") || "").split(" ");
+ this.animation = animation
+ || element.getAttribute("data-yuu-animation")
+ || "yuu-from-top";
+ this.element = element;
+ this.className = element.className;
+ this._keydown = function (event) {
+ var name = yuu.keyEventName(event);
+ if (this.inputs.keydown.call(this, name))
+ yuu.stopPropagation(event);
+ }.bind(this);
+ },
+
+ inputs: {
+ back: function () { this.dismiss(); return true; },
+ keydown: function (key) {
+ if (yf.contains(this.dismissKeys, key))
+ this.dismiss();
+ return true;
+ },
+ touch: function () { this.dismiss(); return true; },
+ mousedown: function () { this.dismiss(); return true; },
+ },
+
+ init: function (director) {
+ var element = this.element;
+ var className = this.className;
+ var elements = element.querySelectorAll("[data-yuu-command]");
+
+ yf.each(function (element) {
+ var command = element.getAttribute("data-yuu-command");
+ switch (element.tagName.toLowerCase()) {
+ case "input":
+ switch (element.type.toLowerCase()) {
+ case "range":
+ element.value = director.execute(command);
+ break;
+ case "checkbox":
+ var res = !!director.execute(command);
+ element.checked = res;
+ break;
+ }
+ break;
+ }
+ }, elements);
+
+ yf.each(function (a) {
+ a.onclick = function (event) {
+ yuu.openURL(this.href);
+ yuu.stopPropagation(event, true);
+ };
+ }, element.querySelectorAll("a[href]:not([yuu-href-internal])"));
+
+ this._director = director;
+
+ element.className = className + " " + this.animation;
+ element.style.display = "block";
+ element.tabIndex = 0;
+ element.focus();
+ element.addEventListener("keydown", this._keydown);
+
+ yuu.afterAnimationFrame(function () {
+ element.className = className;
+ });
+ },
+
+ dismiss: yuu.cmd(function () {
+ var element = this.element;
+ var className = this.className;
+ var director = this._director;
+ var scene = this;
+ element.className = className + " " + this.animation;
+ element.addEventListener("transitionend", function _ () {
+ element.removeEventListener("transitionend", _);
+ director.removeScene(scene);
+ });
+ }, "", "dismiss this overlay"),
+
+ done: function () {
+ this.element.style.display = "none";
+ this.element.tabIndex = -1;
+ this.element.className = this.className;
+ this.element.removeEventListener("keydown", this._keydown);
+ this._director = null;
+ yuu.canvas.focus();
+ },
+
+ KEYBINDS: {
+ "escape": "dismiss"
+ }
+ });
+
+ yuu.registerInitHook(function () {
+ var elements = document.querySelectorAll("[data-yuu-command]");
+
+ function handleElement (event) {
+ /*jshint validthis:true */
+ /* `this` comes from being a DOM element event handler. */
+ var command = this.getAttribute("data-yuu-command");
+ switch (this.tagName.toLowerCase()) {
+ case "input":
+ switch (this.type.toLowerCase()) {
+ case "range":
+ command += " " + this.value;
+ break;
+ case "checkbox":
+ command += " " + (this.checked ? "1" : "0");
+ break;
+ }
+ break;
+ }
+ yuu.director.execute(command);
+ yuu.stopPropagation(event);
+ }
+
+ yf.each(function (element) {
+ switch (element.tagName.toLowerCase()) {
+ case "input":
+ switch (element.type.toLowerCase()) {
+ case "range":
+ element.oninput = handleElement;
+ element.onchange = handleElement;
+ break;
+ case "checkbox":
+ element.onchange = handleElement;
+ break;
+ }
+ break;
+ case "div":
+ case "span":
+ element.onclick = handleElement;
+ element.onkeydown = function (event) {
+ var name = yuu.keyEventName(event);
+ if (name === "space" || name === "return")
+ handleElement.call(this, event);
+ };
+ break;
+ default:
+ element.onclick = handleElement;
+ break;
+ }
+ }, elements);
+
+ yuu.defaultKeybinds.bind("control+`", "showDevTools");
+ yuu.defaultKeybinds.bind("f11", "++fullscreen");
+ yuu.defaultKeybinds.bind("f12", "screenshot");
+ yuu.defaultKeybinds.bind(
+ "control+s", "++mute && toast \uf026 1 mute || toast \uf028 1 mute");
+
+ var director = yuu.director = new yuu.Director();
+ /** The standard director */
+
+ yuu.registerInitHook(function () {
+ return yuu.ready(director.scenes);
+ });
+ });
+
+}).call(typeof exports === "undefined" ? this : exports,
+ typeof exports === "undefined"
+ ? this.yuu : (module.exports = require('./core')));
--- /dev/null
+/* 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");
+ var gl;
+ var canvas;
+
+ var dpr = yuu.DPR = this.devicePixelRatio || 1;
+
+ yT.defineProperty(Int8Array.prototype, "GL_TYPE", 0x1400);
+ yT.defineProperty(Uint8Array.prototype, "GL_TYPE", 0x1401);
+ yT.defineProperty(Int16Array.prototype, "GL_TYPE", 0x1402);
+ yT.defineProperty(Uint16Array.prototype, "GL_TYPE", 0x1403);
+ yT.defineProperty(Int32Array.prototype, "GL_TYPE", 0x1404);
+ yT.defineProperty(Uint32Array.prototype, "GL_TYPE", 0x1405);
+ yT.defineProperty(Float32Array.prototype, "GL_TYPE", 0x1406);
+ /** Patch the WebGL type onto arrays for data-driven access later
+
+ Values from https://www.khronos.org/registry/webgl/specs/1.0/.
+
+ See also notes in pre on Safari's typed array problems.
+ */
+
+ yuu.uniform = function (location, value) {
+ /** Set a uniform in the active program
+
+ The type of the uniform is automatically determined from
+ the value:
+
+ * Typed integer arrays of length 1-4 call uniform[1-4]iv
+ * Other sequences of length 1-4 call uniform[1-4]fv
+ * Sequences of length 9 or 16 call uniformMatrix[3-4]fv
+ * Non-sequences call uniform1fv (even if the parameter
+ is a valid integer)
+ * Sequences of other lengths throw a TypeError
+
+ It is not possible to call uniformMatrix2fv via this
+ function.
+ */
+ switch (value.constructor) {
+ case Int8Array:
+ case Uint8Array:
+ case Int16Array:
+ case Uint16Array:
+ case Int32Array:
+ case Uint32Array:
+ switch (value.length) {
+ case 1: return gl.uniform1iv(location, value);
+ case 2: return gl.uniform2iv(location, value);
+ case 3: return gl.uniform3iv(location, value);
+ case 4: return gl.uniform4iv(location, value);
+ default: throw new TypeError("unexpected array length");
+ }
+ break;
+ default:
+ switch (value.length) {
+ case 1: return gl.uniform1fv(location, value);
+ case 2: return gl.uniform2fv(location, value);
+ case 3: return gl.uniform3fv(location, value);
+ case 4: return gl.uniform4fv(location, value);
+ case 9: return gl.uniformMatrix3fv(location, false, value);
+ case 16: return gl.uniformMatrix4fv(location, false, value);
+ case undefined: return gl.uniform1f(location, value);
+ default: throw new TypeError("unexpected array length");
+ }
+ }
+ };
+
+ function isShaderSource (src) {
+ return src.indexOf("\n") >= 0 || yf.last(src.trim()) === ";";
+ }
+
+ var FRAGMENT_SHADER = 0x8B30;
+ var VERTEX_SHADER = 0x8B31;
+ var EXTS = {};
+ EXTS[FRAGMENT_SHADER] = "frag";
+ EXTS[VERTEX_SHADER] = "vert";
+
+ function compile (type, srcs) {
+ function getSource (src) {
+ return isShaderSource(src)
+ ? Promise.resolve(src)
+ : yuu.GET(yuu.resourcePath(src, "shaders", EXTS[type]));
+ }
+ return Promise.all(yf.map(getSource, srcs))
+ .then(function (srcs) {
+ var src = srcs.join("\n");
+ var shader = gl.createShader(type);
+ gl.shaderSource(shader, src);
+ gl.compileShader(shader);
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
+ var log = gl.getShaderInfoLog(shader);
+ throw new Error(
+ "Shader compile error:\n\n" + src + "\n\n" + log);
+ }
+ return shader;
+ });
+ }
+
+ yuu.ShaderProgram = yT({
+ constructor: function (vs, fs) {
+ /** A linked program of vertex and fragment shaders
+
+ vs and fs are arrays of vertex and fragment shader source
+ code or URLs.
+ */
+ fs = fs || ["yuu/@default"];
+ vs = vs || ["yuu/@default"];
+ var id = this.id = gl.createProgram();
+ var attribs = this.attribs = {};
+ var uniforms = this.uniforms = {};
+ this.ready = Promise.all([compile(VERTEX_SHADER, vs),
+ compile(FRAGMENT_SHADER, fs)])
+ .then(function (shaders) {
+ yf.each(gl.attachShader.bind(gl, id), shaders);
+ gl.linkProgram(id);
+ if (!gl.getProgramParameter(id, gl.LINK_STATUS))
+ throw new Error("Shader link error: "
+ + gl.getProgramInfoLog(id));
+ return id;
+ }).catch(function (exc) {
+ yuu.showError(exc);
+ this.id = yuu.ShaderProgram.DEFAULT.id;
+ this.attribs = yuu.ShaderProgram.DEFAULT.attribs;
+ this.uniforms = yuu.ShaderProgram.DEFAULT.uniforms;
+ throw exc;
+ }.bind(this)).then(function (id) {
+ this.id = id;
+ yf.irange(function (i) {
+ var name = gl.getActiveAttrib(id, i).name;
+ attribs[name] = gl.getAttribLocation(id, name);
+ }, gl.getProgramParameter(id, gl.ACTIVE_ATTRIBUTES));
+ yf.irange(function (i) {
+ var name = gl.getActiveUniform(id, i).name;
+ uniforms[name] = gl.getUniformLocation(id, name);
+ }, gl.getProgramParameter(id, gl.ACTIVE_UNIFORMS));
+ return this;
+ }.bind(this));
+ },
+
+ setUniforms: function () {
+ /** Set the values of program uniforms
+
+ The arguments are any number of objects mapping
+ uniform names to values (floats, vec3s, etc.).
+ */
+ for (var i = 0; i < arguments.length; ++i)
+ for (var name in arguments[i])
+ yuu.uniform(this.uniforms[name], arguments[i][name]);
+ },
+
+ setAttribPointers: function (buffer) {
+ /** Bind the contents of a vertex buffer to attributes
+
+ `buffer` is (or is like) a yuu.VertexBuffer instance.
+ */
+ for (var name in this.attribs)
+ gl.vertexAttribPointer(
+ this.attribs[name],
+ buffer.spec.attribs[name].elements,
+ buffer.spec.attribs[name].View.prototype.GL_TYPE,
+ false, 0, buffer.arrays[name].byteOffset);
+ }
+ });
+
+ // This function is easier to read than a giant lookup table
+ // ({ textureWrapS: "TEXTURE_WRAP_S", ... x100 }) but slower.
+ function glEnum (gl, name) {
+ return gl[name.replace(/([A-Z]+)/g, "_$1").toUpperCase()];
+ }
+
+ function glScopedEnum (scope, gl, name) {
+ var value = glEnum(gl, scope + "_" + name);
+ if (value === undefined)
+ value = glEnum(gl, name);
+ return value;
+ }
+
+ var glTextureEnum = glScopedEnum.bind(null, "texture");
+
+ yuu.Texture = yuu.Caching(yT({
+ constructor: function (path, overrideOptions) {
+ /** A 2D texture
+
+ The texture is set to a 1x1 white texture until it is
+ loaded (or if loading fails).
+ */
+ var options = {};
+ yf.ipairs(function (k, v) {
+ options[glTextureEnum(gl, k)] = glEnum(gl, v);
+ }, TEXTURE_DEFAULTS);
+ yf.ipairs(function (k, v) {
+ options[glTextureEnum(gl, k)] = glEnum(gl, v);
+ }, overrideOptions || {});
+
+ if (!path) {
+ var data = new Uint8Array([255, 255, 255, 255]);
+ this.id = gl.createTexture();
+ this.width = this.height = 1;
+ this.src = "default / fallback 1x1 white texture";
+ gl.bindTexture(gl.TEXTURE_2D, this.id);
+ gl.texImage2D(
+ gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0,
+ gl.RGBA, gl.UNSIGNED_BYTE, data);
+ gl.texParameteri(
+ gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ gl.texParameteri(
+ gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ this.ready = Promise.resolve(this);
+ return;
+ }
+
+ path = yuu.resourcePath(path, "images", "png");
+ this.id = yuu.Texture.DEFAULT.id;
+ this.src = path;
+ this.width = yuu.Texture.DEFAULT.width;
+ this.height = yuu.Texture.DEFAULT.height;
+
+ this.ready = yuu.Image(path).then(function (img) {
+ var id = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, id);
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
+ for (var opt in options)
+ gl.texParameteri(gl.TEXTURE_2D, opt, options[opt]);
+ gl.texImage2D(
+ gl.TEXTURE_2D, 0, gl.RGBA,
+ gl.RGBA, gl.UNSIGNED_BYTE, img);
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ this.id = id;
+ this.width = img.width;
+ this.height = img.height;
+ this.src = img.src;
+ return this;
+ }.bind(this)).catch(function (e) {
+ this.src = "Error loading " + path + ": " + e;
+ yuu.log("errors", this.src);
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ throw e;
+ }.bind(this));
+ }
+ }));
+
+ var TEXTURE_DEFAULTS = yuu.Texture.DEFAULTS = {
+ magFilter: "linear",
+ minFilter: "linear",
+ wrapS: "clampToEdge",
+ wrapT: "clampToEdge"
+ };
+
+ yuu.Material = yuu.Caching(yT({
+ constructor: function (texture, program, uniforms) {
+ /** A material is a combination of a texture and shader program */
+ if (yf.isString(texture))
+ texture = new yuu.Texture(texture);
+ this.texture = texture || yuu.Texture.DEFAULT;
+ this.program = program || yuu.ShaderProgram.DEFAULT;
+ this.ready = yuu.ready([this.texture, this.program], this);
+ this.uniforms = uniforms || {};
+ },
+
+ enable: function (uniforms) {
+ /** Enable this material and its default parameters */
+ gl.bindTexture(gl.TEXTURE_2D, this.texture.id);
+ gl.useProgram(this.program.id);
+ for (var attrib in this.program.attribs)
+ gl.enableVertexAttribArray(this.program.attribs[attrib]);
+ this.program.setUniforms(this.uniforms, uniforms);
+ },
+
+ disable: function () {
+ /** Disable this material */
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ gl.useProgram(null);
+ for (var attrib in this.program.attribs)
+ gl.disableVertexAttribArray(this.program.attribs[attrib]);
+ }
+ }));
+
+ yuu.VertexAttribSpec = function (spec) {
+ /** Ordering and types for vertex buffer layout
+
+ Interleaved vertices (e.g. VTCVTCVTC) are not currently
+ supported, as ArrayBufferViews are not able to manage a buffer
+ with this kind of layout.
+ */
+ var byteOffset = 0;
+ this.attribs = {};
+ spec.forEach(function (a) {
+ var name = a.name;
+ var elements = a.elements;
+ var View = a.View || Float32Array;
+ this.attribs[name] = { elements: elements,
+ byteOffset: byteOffset,
+ View: View };
+ byteOffset += elements * View.BYTES_PER_ELEMENT;
+ }, this);
+ this.bytesPerVertex = byteOffset;
+ };
+
+ yuu.V3T2C4_F = new yuu.VertexAttribSpec([
+ /** vec3 position; vec2 texCoord; vec4 color; */
+ { name: "position", elements: 3 },
+ { name: "texCoord", elements: 2 },
+ { name: "color", elements: 4 }
+ ]);
+
+ yuu.IndexBuffer = yT({
+ constructor: function (maxIndex, length) {
+ this._capacity = -1;
+ this._maxIndex = maxIndex;
+ this.buffer = null;
+ this.type = null;
+ this.length = length;
+ this._glBuffer = gl.createBuffer();
+ this.dirty = true;
+ },
+
+ bindBuffer: function () {
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._glBuffer);
+ if (this.dirty) {
+ this.dirty = false;
+ gl.bufferData(
+ gl.ELEMENT_ARRAY_BUFFER, this.buffer, gl.DYNAMIC_DRAW);
+ }
+ },
+
+ GL_TYPE: { alias: "buffer.GL_TYPE" },
+
+ maxIndex: {
+ get: function () { return this._maxIndex; },
+ set: function (maxIndex) {
+ var Array = yuu.IndexBuffer.Array(maxIndex);
+ if (maxIndex > this._maxIndex
+ && Array !== this.buffer.constructor) {
+ var buffer = new Array(this._capacity);
+ if (this.buffer)
+ buffer.set(this.buffer);
+ this.buffer = buffer;
+ this.dirty = true;
+ }
+ this._maxIndex = maxIndex;
+ }
+ },
+
+ length: {
+ get: function () { return this._length; },
+ set: function (count) {
+ if (count > this._capacity) {
+ var Array = yuu.IndexBuffer.Array(this._maxIndex);
+ var buffer = new Array(count);
+ if (this.buffer)
+ buffer.set(this.buffer);
+ this.buffer = buffer;
+ this._capacity = count;
+ this.dirty = true;
+ }
+ this._length = count;
+ }
+ }
+ });
+
+ yuu.IndexBuffer.Array = function (maxIndex) {
+ if (maxIndex < 0 || maxIndex >= (256 * 256 * 256 * 256))
+ throw new Error("invalid maxIndex index: " + maxIndex);
+ else if (maxIndex < (1 << 8))
+ return Uint8Array;
+ else if (maxIndex < (1 << 16))
+ return Uint16Array;
+ else
+ return Uint32Array;
+ };
+
+ yuu.VertexBuffer = yT({
+ constructor: function (spec, vertexCount) {
+ /** A buffer with a specified vertex format and vertex count
+
+ The individual vertex attribute array views from the
+ attribute specification are available via the .arrays
+ property, e.g. v.arrays.position. The underlying
+ buffer is available as v.buffer.
+
+ The vertex count may be changed after creation and the
+ buffer size and views will be adjusted. If you've
+ grown the buffer, you will need to refill all its
+ data. Shrinking it will truncate it.
+ */
+ this.spec = spec;
+ this._vertexCapacity = -1;
+ this.buffer = null;
+ this.arrays = {};
+ this.vertexCount = vertexCount;
+ this._glBuffer = gl.createBuffer();
+ this.dirty = true;
+ },
+
+ bindBuffer: function () {
+ gl.bindBuffer(gl.ARRAY_BUFFER, this._glBuffer);
+ if (this.dirty) {
+ this.dirty = false;
+ gl.bufferData(gl.ARRAY_BUFFER, this.buffer, gl.DYNAMIC_DRAW);
+ }
+ },
+
+ subdata: function (begin, length) {
+ return new yuu.VertexBuffer.SubData(this, begin, begin + length);
+ },
+
+ vertexCount: {
+ get: function () { return this._vertexCount; },
+ set: function (count) {
+ if (count > this._vertexCapacity) {
+ var buffer = new ArrayBuffer(
+ this.spec.bytesPerVertex * count);
+ var arrays = {};
+ yf.ipairs.call(this, function (name, attrib) {
+ arrays[name] = new attrib.View(
+ buffer, attrib.byteOffset * count,
+ attrib.elements * count);
+ if (this.arrays[name])
+ arrays[name].set(this.arrays[name]);
+ }, this.spec.attribs);
+ this.buffer = buffer;
+ this.arrays = arrays;
+ this._vertexCapacity = count;
+ this.dirty = true;
+ }
+ this._vertexCount = count;
+ }
+ }
+ });
+
+ yuu.VertexBuffer.SubData = yT({
+ constructor: function (parent, begin, end) {
+ var arrays = this.arrays = {};
+ this._parent = parent;
+ this.spec = parent.spec;
+ this.buffer = parent.buffer;
+ yT.defineProperty(this, "vertexCount", end - begin);
+ for (var attrib in parent.arrays) {
+ var s = parent.spec.attribs[attrib].elements;
+ arrays[attrib] = parent.arrays[attrib].subarray(
+ begin * s, end * s);
+ }
+ },
+
+ dirty: { alias: "_parent.dirty" }
+ });
+
+ var rgbToHsl = yuu.rgbToHsl = yf.argcd(
+ /** Convert RBG [0, 1] to HSL [0, 1]. */
+ function (rgb) { return rgbToHsl.apply(null, rgb); },
+ function (r, g, b, a) {
+ var hsl = rgbToHsl(r, g, b);
+ hsl[3] = a;
+ return hsl;
+ },
+ function (r, g, b) {
+ var max = Math.max(r, g, b);
+ var min = Math.min(r, g, b);
+ var h, s, l = (max + min) / 2;
+
+ if (max === min) {
+ h = s = 0;
+ } else {
+ var d = max - min;
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+ switch (max) {
+ case r:
+ h = (g - b) / d + (g < b ? 6 : 0);
+ break;
+ case g:
+ h = (b - r) / d + 2;
+ break;
+ case b:
+ h = (r - g) / d + 4;
+ break;
+ }
+ h /= 6;
+ }
+
+ return [h, s, l];
+ }
+ );
+
+ var hslToRgb = yuu.hslToRgb = yf.argcd(
+ /** Convert HSL [0, 1] to RGB [0, 1]. */
+ function (hsl) { return hslToRgb.apply(null, hsl); },
+ function (h, s, l, a) {
+ var rgb = hslToRgb(h, s, l);
+ rgb[3] = a;
+ return rgb;
+ },
+ function (h, s, l) {
+ var r, g, b;
+
+ function hToC (p, q, t) {
+ if (t < 0)
+ t += 1;
+ if (t > 1)
+ t -= 1;
+ if (t < 1 / 6)
+ return p + (q - p) * 6 * t;
+ else if (t < 1 / 2)
+ return q;
+ else if (t < 2 / 3)
+ return p + (q - p) * (2/3 - t) * 6;
+ else
+ return p;
+ }
+
+ if (s === 0) {
+ r = g = b = l;
+ } else {
+ var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ var p = 2 * l - q;
+ r = hToC(p, q, h + 1 / 3);
+ g = hToC(p, q, h);
+ b = hToC(p, q, h - 1 / 3);
+ }
+
+ return [r, g, b];
+ }
+ );
+
+ var deviceFromCanvas = yuu.deviceFromCanvas = yf.argcd(
+ /** Convert a point from client to normalized device space
+
+ Normalized device space ranges from [-1, -1] at the
+ bottom-left of the viewport to [1, 1] at the top-right.
+ (This is the definition of the space, _not_ bounds on the
+ return value, as events can happen outside the viewport or
+ even outside the canvas.)
+ */
+ function (p) {
+ return deviceFromCanvas(p.x || p.pageX || p[0] || 0,
+ p.y || p.pageY || p[1] || 0);
+ },
+ function (x, y) {
+ x -= canvas.offsetLeft;
+ y -= canvas.offsetTop;
+ x /= canvas.clientWidth;
+ y /= canvas.clientHeight;
+ // xy is now in [0, 1] page space.
+
+ x *= canvas.width;
+ y *= canvas.height;
+ // xy now in canvas buffer space.
+
+ var vp = gl.getParameter(gl.VIEWPORT);
+ var hvpw = vp[2] / 2;
+ var hvph = vp[3] / 2;
+ x = (x - vp[0] - hvpw) / hvpw;
+ y = (y - vp[1] - hvph) / -hvph;
+ // xy now in normalized device space.
+
+ return {
+ x: x, 0: x,
+ y: y, 1: y,
+ inside: Math.abs(x) <= 1 && Math.abs(y) <= 1
+ };
+ }
+ );
+
+ yuu.viewport = new yuu.AABB();
+
+ function onresize () {
+ var resize = canvas.getAttribute("data-yuu-resize") !== null;
+ var width = +canvas.getAttribute("data-yuu-width");
+ var height = +canvas.getAttribute("data-yuu-height");
+
+ if (resize) {
+ canvas.width = canvas.clientWidth * dpr;
+ canvas.height = canvas.clientHeight * dpr;
+ }
+
+ var vw = canvas.width;
+ var vh = canvas.height;
+ if (width && height) {
+ var aspectRatio = width / height;
+ if (vw / vh > aspectRatio)
+ vw = vh * aspectRatio;
+ else
+ vh = vw / aspectRatio;
+ }
+ var vx = (canvas.width - vw) / 2;
+ var vy = (canvas.height - vh) / 2;
+ gl.viewport(vx, vy, vw, vh);
+ yuu.viewport = new yuu.AABB(vx, vy, vx + vw / dpr, vy + vh / dpr);
+ }
+
+ yuu.afterAnimationFrame = function (f) {
+ /* DOM class modifications intended to trigger transitions
+ must be delayed for at least one frame after the element is
+ created, i.e. after it has gone through at least one full
+ repaint.
+ */
+ window.requestAnimationFrame(function () {
+ setTimeout(f, 0);
+ });
+ };
+
+ yuu.registerInitHook(function (options) {
+ var bgColor = options.backgroundColor || [0.0, 0.0, 0.0, 0.0];
+
+ canvas = this.canvas = document.getElementById("yuu-canvas");
+ var glOptions = {
+ alpha: options.hasOwnProperty("alpha")
+ ? options.alpha : bgColor[3] !== 1.0,
+ antialias: options.hasOwnProperty("antialias")
+ ? options.antialias : true
+ };
+ if (!window.HTMLCanvasElement)
+ throw new Error("<canvas> isn't supported.");
+ gl = this.gl = canvas.getContext("webgl", glOptions)
+ || canvas.getContext("experimental-webgl", glOptions);
+ if (!gl)
+ throw new Error("WebGL isn't supported.");
+
+ canvas.focus();
+
+ window.addEventListener('resize', onresize);
+ onresize();
+
+ this.ShaderProgram.DEFAULT = new this.ShaderProgram();
+ this.Texture.DEFAULT = new this.Texture();
+ this.Material.DEFAULT = new this.Material();
+
+ gl.clearColor.apply(gl, bgColor);
+ gl.disable(gl.DEPTH_TEST);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.enable(gl.BLEND);
+ gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
+ gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE);
+ });
+
+ var gui = yuu.require("nw.gui");
+ yT.defineProperty(yuu, "fullscreen", {
+ get: function () {
+ return gui
+ ? gui.Window.get().isFullscreen
+ : !!(document.fullscreenElement
+ || document.mozFullScreenElement);
+ },
+ set: function (v) {
+ if (gui)
+ gui.Window.get().isFullscreen = !!v;
+ else if (v)
+ document.body.requestFullscreen();
+ else
+ document.exitFullscreen();
+ }
+ });
+
+}).call(typeof exports === "undefined" ? this : exports,
+ typeof exports === "undefined"
+ ? this.yuu : (module.exports = require('./core')));
--- /dev/null
+/* 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.KEY_NAMES = {
+ 32: "space",
+ 13: "return",
+ 16: "shift",
+ 18: "alt",
+ 17: "control",
+
+ 37: "left",
+ 38: "up",
+ 39: "right",
+ 40: "down",
+ 9: "tab",
+ 27: "escape",
+ 8: "backspace",
+ 191: "slash",
+ 192: "`",
+ };
+
+ // Fill in the key name tables.
+ for (var i = "A".charCodeAt(0); i <= "Z".charCodeAt(0); ++i)
+ yuu.KEY_NAMES[i] = String.fromCharCode(i).toLowerCase();
+ for (i = "0".charCodeAt(0); i <= "9".charCodeAt(0); ++i)
+ yuu.KEY_NAMES[i] = String.fromCharCode(i);
+ for (i = 1; i <= 12; ++i)
+ yuu.KEY_NAMES[111 + i] = "f" + i;
+
+ function splitKeys (keystring) {
+ return keystring.toLowerCase().split("+").sort();
+ }
+
+ var KeyBind = yT({
+ constructor: function (keystring, command) {
+ /** An individual key to command binding.
+
+ The key string is e.g. "a", "control+f", "left+alt+z".
+ "f+control" is equivalent to "control+f", and binding one
+ in a set will override the other. */
+ this.keystring = keystring;
+ this.command = command;
+ this.keys = splitKeys(keystring);
+ },
+
+ uses: function (name) {
+ /** True if the given key name is relevant to this binding. */
+ return yf.contains(this.keys, name.toLowerCase());
+ },
+
+ on: function (pressed) {
+ /** True if all keys in this binding are pressed. */
+ return yf.every.call(pressed, yf.getter, this.keys);
+ }
+ });
+
+ function longerBind (a, b) {
+ return a.keys.length > b.keys.length ? a : b;
+ }
+
+ function longestBind (binds) {
+ return yf.foldl(longerBind, binds);
+ }
+
+ function isActivate (bind) {
+ return bind.command[0] === '+' && bind.command[1] !== '+';
+ }
+
+ function anticommand (bind) {
+ return "-" + bind.command.substring(1);
+ }
+
+ yuu.KeyBindSet = yT({
+ constructor: function (binds) {
+ /** A group of key bindings.
+
+ A set may only have one bind per key combination
+ (regardless of order) at a time. Binding already-bound
+ keys to a different command will overwrite, not duplicate,
+ that binding.
+ */
+ this.binds = [];
+ yf.ipairs.call(this, this.bind, binds || {});
+ },
+
+ bind: function (keystring, command) {
+ /** Bind keys to a command in this set. */
+ var bind = new KeyBind(keystring, command);
+ this.binds = this.binds.filter(function (b) {
+ return !yf.seqEqual(b.keys, bind.keys);
+ }).concat(bind);
+ },
+
+ unbind: function (keystring) {
+ /** Unbind keys from this set. */
+ var keys = splitKeys(keystring);
+ this.binds = this.binds.filter(function (b) {
+ return !yf.seqEqual(b.keys, keys);
+ });
+ }
+ });
+
+ yuu.keyEventName = function (event) {
+ return yuu.keyCodeName(event.keyCode, event.key || event.keyIdentifier);
+ };
+
+ yuu.keyCodeName = function (code, defaultName) {
+ if (defaultName)
+ defaultName = defaultName.toLowerCase();
+ if (defaultName === "unidentified")
+ defaultName = null;
+ var name = yuu.KEY_NAMES[code] || defaultName;
+ if (!name)
+ name = "key:" + code;
+ return name;
+ };
+
+ yuu.InputState = yT({
+ constructor: function (bindsets) {
+ this._bindsets = yf.slice(bindsets || [yuu.defaultKeybinds]);
+ this.pressed = {};
+ /** The current state of each key; 0 if not pressed,
+ the time it was pressed if it is pressed. */
+ },
+
+ push: function (bindset) {
+ /** Add a key bind set to the handler stack. */
+ this._bindsets = this._bindsets.concat(bindset);
+ },
+
+ remove: function (bindset) {
+ /** Remove a key bind set from the handler stack. */
+ this._bindsets = yf.without(this._bindsets, bindset);
+ },
+
+ insertBefore: function (bindset, before) {
+ this._bindsets = yf.insertBefore(
+ this._bindsets.slice(), bindset, before);
+ },
+
+ _triggeredBinds: function (name) {
+ var pressed = this.pressed;
+ function triggered (bind) {
+ return bind.uses(name) && bind.on(pressed);
+ }
+ var binds = [];
+ yf.each(function (bindset) {
+ binds.push.apply(binds, yf.filter(triggered, bindset.binds));
+ }, this._bindsets);
+ return binds;
+ },
+
+ _down: function (name) {
+ /** Mark the input as down, return an array of commands.
+
+ This array is always of length 1 for down/change events.
+
+ Returns null if no binds were triggered, which is slightly
+ different than binds being triggered but no commands are
+ to be executed.
+ */
+ var pressed = this.pressed[name];
+ this.pressed[name] = Date.now();
+ var bind = longestBind(this._triggeredBinds(name));
+ return bind ? pressed ? [] : [bind.command] : null;
+ },
+
+ _up: function (name) {
+ /** Mark the input as down, return an array of commands.
+
+ Only binds for commands with the special + form are
+ returned on release, and the + is converted to a -.
+
+ Returns null if no binds were triggered, which is slightly
+ different than binds being triggered but no commands are
+ to be executed.
+ */
+ if (!this.pressed[name])
+ return null;
+ var cmds = yf.map(anticommand, yf.filter(
+ isActivate, this._triggeredBinds(name)));
+ this.pressed[name] = 0;
+ return cmds.length ? cmds : null;
+ },
+
+ change: function (name) {
+ /** Mark the input as changed, return an array of commands.
+
+ `change` is for inputs that do not have meaningful
+ "down" or "up" states, like moving a mouse. Instead,
+ it fires when the input's state changes - e.g. when
+ the x and y position change.
+
+ The `pressed` table remains unmodified as a result of
+ `change` inputs. Like `down`, `change` returns only the
+ first match it finds.
+ */
+ this.pressed[name] = Date.now();
+ var bind = longestBind(this._triggeredBinds(name));
+ this.pressed[name] = 0;
+ return bind ? [bind.command] : null;
+ },
+
+ keydown: { proxy: "_down" },
+ keyup: { proxy: "_up" },
+
+ gamepadbuttondown: function (gamepad, button) {
+ return this._down("gamepad" + gamepad.index + "button" + button)
+ || this._down("gamepadbutton" + button);
+ },
+
+ gamepadbuttonup: function (gamepad, button) {
+ return this._up("gamepad" + gamepad.index + "button" + button)
+ || this._up("gamepadbutton" + button);
+ },
+
+ mousemove: function () {
+ return this.change("mousemove");
+ },
+
+ mousedown: function (button) {
+ return this._down("mouse" + button);
+ },
+
+ mouseup: function (button) {
+ return this._up("mouse" + button);
+ },
+
+ reset: function () {
+ this.pressed = {};
+ }
+ });
+
+ yuu.stopPropagation = function stopPropagation (event, preventDefault) {
+ event.stopPropagation();
+ if (preventDefault)
+ event.preventDefault();
+ if (event.stopImmediatePropagation)
+ event.stopImmediatePropagation();
+ if (event.gesture && event.gesture !== event)
+ stopPropagation(event.gesture, preventDefault);
+ };
+
+ yuu.registerInitHook(function () {
+ yuu.defaultCommands.bind = yuu.cmd(function (key, command) {
+ yuu.defaultKeybinds.bind(key, command);
+ }, "<key> <command>", "bind a key to a command");
+
+ yuu.defaultCommands.unbind = yuu.cmd(function (key) {
+ yuu.defaultKeybinds.unbind(key);
+ }, "<key>", "unbind a key");
+
+ yuu.defaultKeybinds = new yuu.KeyBindSet();
+ /** The default / debugging bind set */
+ });
+
+}).call(typeof exports === "undefined" ? this : exports,
+ typeof exports === "undefined"
+ ? this.yuu : (module.exports = require('./core')));
--- /dev/null
+/* 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.
+*/
+
+(function () {
+ "use strict";
+
+ /** Polyfills and cross-browser fixes */
+
+ if (!Math.sign)
+ Math.sign = function (a) { return a && (a > 0) - (a < 0); };
+
+ if (!String.prototype.repeat)
+ Object.defineProperty(String.prototype, "repeat", {
+ value: function (count) {
+ var string = this.toString();
+ var result = '';
+ var n = count | 0;
+ while (n) {
+ if (n % 2 === 1)
+ result += string;
+ if (n > 1)
+ string += string;
+ n >>= 1;
+ }
+ return result;
+ }
+ });
+
+ if (!String.prototype.startsWith)
+ Object.defineProperty(String.prototype, "startsWith", {
+ value: function (sub) {
+ return this.lastIndexOf(sub, 0) !== -1;
+ }
+ });
+
+ if (!String.prototype.endsWith)
+ Object.defineProperty(String.prototype, "endsWith", {
+ value: function (sub) {
+ return this.indexOf(sub, this.length - sub.length) !== -1;
+ }
+ });
+
+ function toObject (o) {
+ if (o === null || o === undefined)
+ throw new TypeError("invalid ToObject cast");
+ return Object(o);
+ }
+
+ if (!Object.assign)
+ Object.defineProperty(Object, "assign", {
+ value: function (target) {
+ target = toObject(target);
+ for (var i = 1; i < arguments.length; ++i) {
+ var source = toObject(arguments[i]);
+ var keys = Object.keys(source);
+ for (var j = 0; j < keys.length; ++j)
+ target[keys[j]] = source[keys[j]];
+ }
+ }
+ });
+
+ if (!Array.prototype.fill)
+ Object.defineProperty(Array.prototype, "fill", {
+ value: function (value) {
+ var beg = arguments.length > 1 ? +arguments[1] : 0;
+ var end = arguments.length > 2 ? +arguments[2] : this.length;
+ if (beg < 0) beg += this.length;
+ if (end < 0) end += this.length;
+ for (var i = beg; i < end; ++i)
+ this[i] = value;
+ return this;
+ }
+ });
+
+ if (typeof window !== "undefined") {
+ window.requestAnimationFrame = (
+ window.requestAnimationFrame
+ || window.mozRequestAnimationFrame
+ || window.webkitRequestAnimationFrame);
+ window.cancelAnimationFrame = (
+ window.cancelAnimationFrame
+ || window.mozCancelAnimationFrame
+ || window.webkitCancelAnimationFrame);
+
+ if (!window.AudioContext)
+ window.AudioContext = (
+ window.webkitAudioContext
+ || window.mozAudioContext);
+
+ if (window.AudioContext && !window.AudioContext.prototype.createGain)
+ window.AudioContext.prototype.createGain =
+ window.AudioContext.prototype.createGainNode;
+
+ /** Canonicalize fullscreen function names if available.
+
+ Based on http://fullscreen.spec.whatwg.org/, June 7th 2013.
+ */
+
+ if (!Element.prototype.requestFullscreen)
+ Element.prototype.requestFullscreen = (
+ Element.prototype.requestFullScreen
+ || Element.prototype.webkitRequestFullscreen
+ || Element.prototype.webkitRequestFullScreen
+ || Element.prototype.mozRequestFullScreen
+ || function () {});
+ if (!document.exitFullscreen)
+ document.exitFullscreen = (
+ document.webkitExitFullscreen
+ || document.webkitCancelFullScreen
+ || document.mozCancelFullScreen
+ || function () {});
+ if (!document.hasOwnProperty("fullscreenEnabled"))
+ Object.defineProperty(document, "fullscreenEnabled", {
+ enumerable: true,
+ get: function () {
+ return (this.webkitFullscreenEnabled
+ || this.mozFullScreenEnabled
+ || false);
+ }
+ });
+ if (!document.hasOwnProperty("fullscreenElement"))
+ Object.defineProperty(document, "fullscreenElement", {
+ enumerable: true,
+ get: function () {
+ return (this.webkitFullscreenElement
+ || this.webkitCurrentFullScreenElement
+ || this.mozFullScreenEleement
+ || null);
+ }
+ });
+ }
+
+ // Check for Promise.all as Chrome 30 shipped an implementation
+ // without it and with some other quirks and we don't want to use
+ // that one.
+ if (typeof Promise === "undefined" || !Promise.all) (function () {
+ /* Polyfill based heavily on Christoph Burgmer's ayepromise
+
+ https://github.com/cburgmer/ayepromise/blob/master/ayepromise.js
+ */
+ /* Wrap an arbitrary number of functions and allow only one of
+ them to be executed and only once */
+ function once () {
+ var wasCalled = false;
+
+ return function (wrappedFunction) {
+ return function () {
+ if (wasCalled) {
+ return;
+ }
+ wasCalled = true;
+ wrappedFunction.apply(null, arguments);
+ };
+ };
+ }
+
+ function getThenableIfExists (obj) {
+ // Make sure we only access the accessor once as required by the spec
+ var then = obj && obj.then;
+
+ if (typeof obj === "object" && typeof then === "function")
+ return then.bind(obj);
+ }
+
+ function aThenHandler (onFulfilled, onRejected) {
+ var deferred = defer();
+
+ function doHandlerCall (func, value) {
+ setTimeout(function () {
+ var returnValue;
+ try {
+ returnValue = func(value);
+ } catch (e) {
+ deferred.reject(e);
+ return;
+ }
+
+ if (returnValue === deferred.promise) {
+ deferred.reject(new TypeError());
+ } else {
+ deferred.resolve(returnValue);
+ }
+ }, 0);
+ }
+
+ return {
+ promise: deferred.promise,
+ callFulfilled: function (value) {
+ if (onFulfilled && onFulfilled.call) {
+ doHandlerCall(onFulfilled, value);
+ } else {
+ deferred.resolve(value);
+ }
+ },
+ callRejected: function (value) {
+ if (onRejected && onRejected.call) {
+ doHandlerCall(onRejected, value);
+ } else {
+ deferred.reject(value);
+ }
+ }
+ };
+ }
+
+ function defer () {
+ // States
+ var PENDING = 0,
+ FULFILLED = 1,
+ REJECTED = 2;
+
+ var state = PENDING,
+ outcome,
+ thenHandlers = [];
+
+ function doFulfill (value) {
+ state = FULFILLED;
+ outcome = value;
+
+ thenHandlers.forEach(function (then) {
+ then.callFulfilled(outcome);
+ });
+ thenHandlers = null;
+ }
+
+ function doReject (error) {
+ state = REJECTED;
+ outcome = error;
+
+ thenHandlers.forEach(function (then) {
+ then.callRejected(outcome);
+ });
+ thenHandlers = null;
+ }
+
+ function registerThenHandler (onFulfilled, onRejected) {
+ var thenHandler = aThenHandler(onFulfilled, onRejected);
+
+ if (state === FULFILLED) {
+ thenHandler.callFulfilled(outcome);
+ } else if (state === REJECTED) {
+ thenHandler.callRejected(outcome);
+ } else {
+ thenHandlers.push(thenHandler);
+ }
+
+ return thenHandler.promise;
+ }
+
+ function safelyResolveThenable (thenable) {
+ // Either fulfill, reject or reject with error
+ var onceWrapper = once();
+ try {
+ thenable(
+ onceWrapper(transparentlyResolveThenablesAndFulfill),
+ onceWrapper(doReject)
+ );
+ } catch (e) {
+ onceWrapper(doReject)(e);
+ }
+ }
+
+ function transparentlyResolveThenablesAndFulfill (value) {
+ var thenable;
+
+ try {
+ thenable = getThenableIfExists(value);
+ } catch (e) {
+ doReject(e);
+ return;
+ }
+
+ if (thenable) {
+ safelyResolveThenable(thenable);
+ } else {
+ doFulfill(value);
+ }
+ }
+
+ var onceWrapper = once();
+ return {
+ resolve: onceWrapper(transparentlyResolveThenablesAndFulfill),
+ reject: onceWrapper(doReject),
+ promise: {
+ then: registerThenHandler,
+ "catch": function (onRejected) {
+ return registerThenHandler(null, onRejected);
+ }
+ }
+ };
+ }
+
+ function Promise (callback) {
+ var deferred = defer();
+ try {
+ callback(deferred.resolve, deferred.reject);
+ } catch (exc) {
+ deferred.reject(exc);
+ }
+ return deferred.promise;
+ }
+
+ Promise.resolve = function (v) {
+ return new Promise(function (resolve) { resolve(v); });
+ };
+
+ Promise.reject = function (error) {
+ return new Promise(function (_, reject) { reject(error); });
+ };
+
+ Promise.all = function (promises) {
+ return new Promise(function (resolve, reject) {
+ var results = [];
+ var remaining = promises.length;
+ if (remaining === 0)
+ return resolve([]);
+
+ promises.forEach(function (promise, i) {
+ var then = getThenableIfExists(promise);
+ function resolve1 (value) {
+ results[i] = value;
+ if (--remaining === 0)
+ resolve(results);
+ }
+ if (then) {
+ then.call(promise, resolve1, reject);
+ } else {
+ --remaining;
+ results[i] = promise;
+ }
+ });
+ });
+ };
+
+ this.Promise = Promise;
+ }).call(this);
+}).call(this);
--- /dev/null
+/* 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");
+
+ if (!yuu.C) require("./ce");
+ if (!yuu.Material) require("./gfx");
+
+ yuu.Renderable = yT({
+ constructor: function (vbuf, primitive, material, uniforms, z) {
+ this.vbuf = vbuf;
+ this.primitive = primitive || yuu.gl.TRIANGLES;
+ this.material = material || yuu.Material.DEFAULT;
+ this.uniforms = uniforms || {};
+ this.z = z || 0.0;
+ },
+
+ bind: function () {
+ this.material.program.setUniforms(this.uniforms);
+ this.vbuf.bindBuffer();
+ this.material.program.setAttribPointers(this.vbuf);
+ },
+
+ draw: function () {
+ this.bind();
+ yuu.gl.drawArrays(this.primitive, 0, this.vbuf.vertexCount);
+ },
+
+ vertexCount: { alias: "vbuf.vertexCount" }
+ });
+
+ yuu.IndexedRenderable = yT(yuu.Renderable, {
+ constructor: function (vbuf, primitive, material, uniforms, z, ibuf) {
+ yuu.Renderable.call(this, vbuf, primitive, material, uniforms, z);
+ this.ibuf = ibuf;
+ },
+
+ bind: function () {
+ yuu.Renderable.prototype.bind.call(this);
+ this.ibuf.bindBuffer();
+ },
+
+ draw: function () {
+ this.bind();
+ yuu.gl.drawElements(
+ this.primitive, this.ibuf.length, this.ibuf.GL_TYPE, 0);
+ },
+
+ vertexCount: {
+ get: function () {
+ return this.ibuf.length;
+ },
+ set: function (vertexCount) {
+ this.vbuf.vertexCount = vertexCount;
+ this.ibuf.maxIndex = vertexCount;
+ }
+ }
+ });
+
+ yuu.Quad = yT({
+ /** A vertex view containing a 2D quadrilateral
+
+ You probably don't want to use this directly. If you want a
+ simple quad, look at QuadC.
+ */
+ constructor: function (vbuf) {
+ this._vbuf = vbuf;
+ this.anchor = "center";
+ this.position = [0.0, 0.0];
+ this.size = [1.0, 1.0];
+ this.texBounds = [0.0, 0.0, 1.0, 1.0];
+ this.color = [1.0, 1.0, 1.0, 1.0];
+ },
+
+ size: {
+ get: function () {
+ var b = this._vbuf.arrays.position;
+ return [b[6] - b[0], b[4] - b[1]];
+ },
+ set: function(size) {
+ var position = this.position;
+ var b = this._vbuf.arrays.position;
+ b[0] = b[3] = b[1] = b[7] = 0;
+ b[6] = b[9] = size[0];
+ b[4] = b[10] = size[1];
+ this.position = position;
+ this._vbuf.dirty = true;
+ }
+ },
+
+ position: {
+ get: function () {
+ var b = this._vbuf.arrays.position;
+ return yuu.anchorPoint(this.anchor, b[0], b[1], b[6], b[4]);
+ },
+ set: function (position) {
+ var size = this.size;
+ var b = this._vbuf.arrays.position;
+ var bottomLeft = yuu.bottomLeft(
+ this.anchor, position[0], position[1], size[0], size[1]);
+ b[0] = b[3] = bottomLeft[0];
+ b[1] = b[7] = bottomLeft[1];
+ b[6] = b[9] = bottomLeft[0] + size[0];
+ b[4] = b[10] = bottomLeft[1] + size[1];
+ this._vbuf.dirty = true;
+ }
+ },
+
+ x: { synthetic: "position[0]" },
+ y: { synthetic: "position[1]" },
+
+ // Texture coordinate vertices: 12 +...
+ // 2,3 6,7
+ // 0,1 4,5
+
+ texBounds: {
+ get: function() {
+ var b = this._vbuf.arrays.texCoord;
+ return [b[0], b[1], b[6], b[7]];
+ },
+ set: function (uv0uv1) {
+ var b = this._vbuf.arrays.texCoord;
+ b[0] = b[2] = uv0uv1[0];
+ b[1] = b[5] = uv0uv1[1];
+ b[6] = b[4] = uv0uv1[2];
+ b[3] = b[7] = uv0uv1[3];
+ this._vbuf.dirty = true;
+ }
+ },
+
+ // Color vertices: 20 +...
+ // 4,5,6,7 12,13,14,15
+ // 0,1,2,3 8,9,10,11
+
+ color: {
+ get: function () {
+ var b = this._vbuf.arrays.color;
+ return [b[0], b[1], b[2], b[3]];
+ },
+ set: function (rgba) {
+ var b = this._vbuf.arrays.color;
+ var a = rgba[3];
+ b[0] = b[4] = b[8] = b[12] = rgba[0];
+ b[1] = b[5] = b[9] = b[13] = rgba[1];
+ b[2] = b[6] = b[10] = b[14] = rgba[2];
+ if (a !== undefined)
+ b[3] = b[7] = b[11] = b[15] = a;
+ this._vbuf.dirty = true;
+ }
+ },
+
+ luminance: {
+ get: function () {
+ var color = this.color;
+ return 0.2126 * color[0]
+ + 0.7152 * color[1]
+ + 0.0722 * color[2];
+ },
+
+ set: function (v) {
+ this.color = [v, v, v];
+ }
+ },
+
+ alpha: {
+ get: function () { return this._vbuf.arrays.color[3]; },
+ set: function (a) {
+ var b = this._vbuf.arrays.color;
+ b[3] = b[7] = b[11] = b[15] = a;
+ this._vbuf.dirty = true;
+ }
+ }
+ });
+
+ yuu.QuadBatch = yT({
+ constructor: function (capacity) {
+ this.vbuf = new yuu.VertexBuffer(yuu.V3T2C4_F, capacity * 4);
+ this.ibuf = new yuu.IndexBuffer(
+ this.vbuf.vertexCount, capacity * 6);
+ this._capacity = capacity;
+ this._resetAllocations();
+ },
+
+ _vbufSlotFromQuad: function (quad) {
+ if (quad._vbuf.arrays.position.buffer !== this.vbuf.buffer)
+ throw new Error("invalid quad buffer");
+ var offset = quad._vbuf.arrays.position.byteOffset;
+ var bytesPerQuad = (
+ this.vbuf.spec.attribs.position.View.BYTES_PER_ELEMENT
+ * this.vbuf.spec.attribs.position.elements
+ * 4 /* vertices per quad */);
+ return offset / bytesPerQuad;
+ },
+
+ createQuad: function () {
+ var slot = this._freeVbufSlots[this._allocated];
+ if (slot === undefined)
+ throw new Error("out of batch slots");
+ var subdata = this.vbuf.subdata(slot * 4, 4);
+ var index = this._allocated++;
+ var n = 6 * index;
+ this.ibuf.buffer[n + 0] = slot * 4 + 0;
+ this.ibuf.buffer[n + 1] = slot * 4 + 1;
+ this.ibuf.buffer[n + 2] = slot * 4 + 2;
+ this.ibuf.buffer[n + 3] = slot * 4 + 2;
+ this.ibuf.buffer[n + 4] = slot * 4 + 1;
+ this.ibuf.buffer[n + 5] = slot * 4 + 3;
+ this.ibuf.length += 6;
+ this.ibuf.dirty = true;
+ this._vbufToIndex[slot] = index;
+ return new yuu.Quad(subdata);
+ },
+
+ disposeQuad: function (quad) {
+ var slot = this._vbufSlotFromQuad(quad);
+ var index = this._vbufToIndex[slot];
+ this._allocated--;
+ if (index !== this._allocated) {
+ // Unless this was the last index, swap the last index
+ // into the new hole.
+ var n = 6 /* indices per quad */ * index;
+ var m = 6 /* indices per quad */ * this._allocated;
+ var b = this.ibuf.buffer;
+ var lastVbufSlot = b[m] / 4 /* vertices per quad */;
+ if (this._vbufToIndex[lastVbufSlot] !== this._allocated)
+ throw new Error("allocation index mismatch");
+ b[n + 0] = b[m + 0];
+ b[n + 1] = b[m + 1];
+ b[n + 2] = b[m + 2];
+ b[n + 3] = b[m + 3];
+ b[n + 4] = b[m + 4];
+ b[n + 5] = b[m + 5];
+ this.ibuf.dirty = true;
+ this._vbufToIndex[lastVbufSlot] = index;
+ }
+ this._freeVbufSlots[this._allocated] = slot;
+ this.ibuf.length -= 6;
+ },
+
+ _resetAllocations: function () {
+ this.ibuf.length = 0;
+ var Array = yuu.IndexBuffer.Array(this._capacity);
+ this._freeVbufSlots = new Array(this._capacity);
+ yf.transform(yf.counter(), this._freeVbufSlots);
+ this._allocated = 0;
+ this._vbufToIndex = new Array(this._capacity);
+ },
+
+ disposeAll: function () {
+ this._resetAllocations();
+ }
+ });
+
+ yuu.QuadC = yT(yuu.C, {
+ /** A 2D quadrilateral that tracks the entity's transform
+
+ By default, the extents of this quad are [-0.5, -0.5] to
+ [0.5, 0.5], and its model matrix is identical to the
+ entity's transform, i.e. it is centered around [0, 0] in
+ the entity's local space, or the entity's nominal location
+ in world space. This can be changed by adjusting the
+ anchor, position, and size properties.
+ */
+
+ constructor: function (material) {
+ var buffer = new yuu.VertexBuffer(yuu.V3T2C4_F, 4);
+ this._quad = new yuu.Quad(buffer);
+ this._rdro = new yuu.Renderable(
+ buffer, yuu.gl.TRIANGLE_STRIP, material,
+ { model: mat4.create() }, 0.0);
+ },
+
+ TAPS: ["queueRenderables"],
+
+ queueRenderables: function (rdros) {
+ mat4.copy(this._rdro.uniforms.model,
+ this.entity.transform.matrix);
+ rdros.push(this._rdro);
+ },
+
+ // TODO: yT should offer some way to specify these in two
+ // lists, i.e. the rdro aliases, and the quad aliases.
+
+ material: { alias: "_rdro.material", chainable: true },
+ z: { alias: "_rdro.z", chainable: true },
+ uniforms: { alias: "_rdro.uniforms" },
+ size: { alias: "_quad.size", chainable: true },
+ position: { alias: "_quad.position", chainable: true },
+ anchor: { alias: "_quad.anchor", chainable: true },
+ xy: { alias: "_quad.position", chainable: true },
+ texBounds: { alias: "_quad.texBounds", chainable: true },
+ color: { alias: "_quad.color", chainable: true },
+ alpha: { alias: "_quad.alpha", chainable: true },
+ luminance: { alias: "_quad.luminance", chainable: true },
+ });
+
+ yuu.QuadBatchC = yT(yuu.C, {
+ /** A 2D quadrilateral batch that tracks the entity's transform
+
+ */
+
+ constructor: function (capacity, material) {
+ this._batch = new yuu.QuadBatch(capacity);
+ this._rdro = new yuu.IndexedRenderable(
+ this._batch.vbuf, yuu.gl.TRIANGLES, material,
+ { model: mat4.create() }, 0.0, this._batch.ibuf);
+ },
+
+ TAPS: ["queueRenderables"],
+
+ queueRenderables: function (rdros) {
+ mat4.copy(this._rdro.uniforms.model,
+ this.entity.transform.matrix);
+ rdros.push(this._rdro);
+ },
+
+ material: { alias: "_rdro.material", chainable: true },
+ z: { alias: "_rdro.z", chainable: true },
+ uniforms: { alias: "_rdro.uniforms" },
+ createQuad: { proxy: "_batch.createQuad" },
+ disposeQuad: { proxy: "_batch.disposeQuad" },
+ disposeAll: { proxy: "_batch.disposeAll" },
+ });
+
+ function sortRenderables(a, b) { return a.z - b.z; }
+
+ yuu.Layer = yT({
+ /** List of renderables and per-layer uniforms
+
+ These uniforms usually include the projection and view
+ matrices, set to a [-1, 1] orthographic projection and the
+ identity view by default.
+ */
+
+ // TODO: This is a bad design. Too powerful to be efficient or
+ // a straightforward part of Scene; not enough to abstract
+ // hard things like render passes.
+
+ constructor: function () {
+ this.rdros = [];
+ this.uniforms = {
+ projection: mat4.ortho(mat4.create(), -1, 1, -1, 1, -1, 1),
+ view: mat4.create()
+ };
+ },
+
+ worldFromDevice: yf.argcd(
+ function (p) {
+ var t = this.worldFromDevice(p.x || p.pageX || p[0] || 0,
+ p.y || p.pageY || p[1] || 0);
+ t.inside = p.inside;
+ return t;
+ },
+ function (x, y) {
+ var p = { 0: x, 1: y };
+ var m = mat4.mul(mat4.create(),
+ this.uniforms.projection, this.uniforms.view);
+ m = mat4.invert(m, m);
+ vec2.transformMat4(p, p, m);
+ p.x = p[0]; p.y = p[1];
+ return p;
+ }
+ ),
+
+ worldFromCanvas: yf.argcd(
+ function (p) {
+ return this.worldFromDevice(yuu.deviceFromCanvas(p));
+ },
+ function (x, y) {
+ return this.worldFromDevice(yuu.deviceFromCanvas(x, y));
+ }
+ ),
+
+ resize: function (x, y, w, h) {
+ /** Set a 2D orthographic project with an origin and size
+
+ Arguments:
+ scene.resize(originX, originY, width, height)
+ scene.resize(width, height) // Origin at 0, 0
+ scene.resize(origin, size)
+ scene.resize(size) // Origin at 0, 0
+ */
+ if (y === undefined) {
+ w = x[0]; h = x[1]; x = y = 0;
+ } else if (w === undefined) {
+ if (x.length === undefined) {
+ w = x; h = y; x = y = 0;
+ } else {
+ w = y[0]; h = y[1];
+ y = x[1]; x = x[0];
+ }
+ }
+ mat4.ortho(this.uniforms.projection, x, x + w, y, y + h, -1, 1);
+ },
+
+ render: function () {
+ /** Render all queued renderables */
+ this.rdros.sort(sortRenderables);
+ var mat = null;
+ for (var j = 0; j < this.rdros.length; ++j) {
+ var rdro = this.rdros[j];
+ if (mat !== rdro.material) {
+ if (mat)
+ mat.disable();
+ mat = rdro.material;
+ rdro.material.enable(this.uniforms);
+ }
+ rdro.draw();
+ }
+ },
+
+ clear: function () {
+ this.rdros.length = 0;
+ }
+ });
+
+}).call(typeof exports === "undefined" ? this : exports,
+ typeof exports === "undefined"
+ ? this.yuu : (module.exports = require('./core')));
--- /dev/null
+/* 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 (exports) {
+ "use strict";
+
+ var yT = this.yT || require('./yT');
+
+ var FakeStorage = exports.FakeStorage = yT({
+ /** Fake, ephemeral storage roughly like Web Storage
+
+ This is the fallback storage when permission is denied or
+ otherwise busted. It just stores a dictionary for as long as
+ the object survives.
+ */
+ constructor: function () {
+ this._storage = {};
+ },
+
+ getItem: function (key) {
+ return (key in this._storage) ? this._storage[key] : null;
+ },
+
+ setItem: function (key, value) {
+ this._storage[key] = value.toString();
+ },
+
+ removeItem: function (key) {
+ delete this._storage[key];
+ },
+
+ clear: function () {
+ this._storage = {};
+ },
+
+ length: {
+ get: function () { return Object.keys(this._storage).length; }
+ },
+
+ key: function (n) {
+ // Object.keys isn't guaranteed to have a consistent order
+ // even when nothing changes, so normalize it by sorting.
+ var keys = Object.keys(this._storage).sort();
+ return (n >= 0 && n < keys.length) ? keys[n] : null;
+ }
+ });
+
+ var PrefixedStorage = exports.PrefixedStorage = yT({
+ /** Per-application storage roughly like Web Storage
+
+ This storage prefixes all keys with a special token, so you
+ can run multiple applications on the same origin without
+ the risk of conflicting keys.
+
+ A caveat of this approach is clear() is not atomic.
+ */
+
+ constructor: function (storage, prefix) {
+ this._storage = storage;
+ this._prefix = prefix + " -- ";
+ },
+
+ _key: function (key) {
+ return this._prefix + key;
+ },
+
+ _unkey: function (key) {
+ return key.substring(this._prefix.length);
+ },
+
+ _iskey: function (key) {
+ return key.startsWith(this._prefix);
+ },
+
+ _keys: function () {
+ var keys = [];
+ var key;
+ var i = 0;
+ while ((key = this._storage.key(i++)) !== null)
+ if (this._iskey(key))
+ keys.push(this._unkey(key));
+ return keys;
+ },
+
+ getItem: function (key) {
+ return this._storage.getItem(this._key(key));
+ },
+
+ setItem: function (key, value) {
+ return this._storage.setItem(this._key(key), value);
+ },
+
+ removeItem: function (key) {
+ return this._storage.removeItem(this._key(key));
+ },
+
+ clear: function () {
+ this._keys().forEach(this.removeItem, this);
+ },
+
+ length: {
+ get: function () { return this._keys().length; }
+ },
+
+ key: function (n) {
+ var keys = this._keys().sort();
+ return (n >= 0 && n < keys.length) ? keys[n] : null;
+ }
+ });
+
+ var Storage = exports.Storage = yT({
+ /** Higher-level access to Web Storage-esque things
+
+ Storage lets you store and retrieve JSON-serializable
+ objects inside a Web Storage container.
+
+ You can specify default values. If you retrieve an object
+ that hasn't been set, you get its default value.
+
+ Storage automatically falls back to an ephemeral storage
+ backend if a SecurityException occurs during startup.
+ */
+
+ constructor: function (storage, defaults) {
+ this._storage = storage || new FakeStorage();
+ this._defaults = defaults || {};
+
+ try {
+ this.setFlag('__ystorage__');
+ } catch (exc) {
+ this._storage = new FakeStorage();
+ console.error("Unable to use provided storage:", exc);
+ }
+ },
+
+ getObject: function (key, fallbackValue) {
+ var v = this._storage.getItem(key);
+ if (v === null) {
+ return (key in this._defaults)
+ ? this._defaults[key]
+ : fallbackValue;
+ }
+ try {
+ return JSON.parse(v);
+ } catch (exc) {
+ console.error("Malformed storage value:", key, v, exc);
+ return (key in this._defaults)
+ ? this._defaults[key]
+ : fallbackValue;
+ }
+ },
+
+ setObject: function (key, value) {
+ this._storage.setItem(key, JSON.stringify(value));
+ },
+
+ removeObject: { proxy: '_storage.removeItem' },
+
+ getFlag: function (key) {
+ return !!this.getObject(key, false);
+ },
+
+ setFlag: function (key) {
+ return this.setObject(key, true);
+ },
+
+ clearFlag: function (key) {
+ return this.setObject(key, false);
+ },
+
+ clear: { proxy: '_storage.clear' }
+ });
+
+ exports.getStorage = function (prefix, defaults, backend) {
+ /** Create a Storage with prefixed access to localStorage. */
+ prefix = prefix
+ || (document &&
+ (document.documentElement.getAttribute('data-appid')
+ || document.title));
+ backend = backend || localStorage;
+ return new Storage(new PrefixedStorage(backend, prefix), defaults);
+ };
+
+}).call(typeof exports === 'undefined' ? this : exports,
+ typeof exports === 'undefined' ? (this.ystorage = {}) : exports);
--- /dev/null
+/* 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 (module) {
+ "use strict";
+
+ /** yT - yuu type creation
+
+ yT is a function like `Object.create`, but with support for
+ more powerful property descriptors (referred to as _extended
+ property descriptors (XPDs)_). Most standard JavaScript
+ property descriptors are valid XPDs, but XPDs allow shortcuts
+ to specify common descriptor patterns.
+
+ Equivalents for `Object.defineProperties` and
+ `Object.defineProperty` are also provided.
+
+ ## Extended Property Descriptors
+
+ Any standard descriptor that has a `get` function (called an
+ 'accessor descriptor') or a `value` property (called a 'data
+ descriptor') is also valid XPD.
+
+ An extended descriptor that does not have either of these and
+ does not meet any of the other conditions below is equivalent
+ to a data descriptor with a `value` of itself, referred to as
+ a 'bare value descriptor'. For example, the following two XPDs
+ are equivalent:
+
+ { x: 1 } { x: { value: 1 } }
+
+ (This means a useless descriptor like `{}` is interpreted as a
+ data descriptor with value `undefined` by `Object.create` but
+ a bare value descriptor with value `{}` by `yT`.)
+
+ In addition, extended descriptors have several other formats
+ which can be used to generate different kinds of idomatic
+ accessors:
+
+ * `alias` - This property is a synonym for a different property.
+ Reads and writes to it will be mapped to reads and writes
+ to the aliased property.
+ { firstChild: { alias: "children[0]" } }
+ is equivalent to
+ { firstChild: {
+ get: function () { return this.children[0]; },
+ set: function (v) { this.children[0] = v; }
+ } }
+
+ * `proxy` - This property is a synonym for a method call
+ on a different object.
+ { start: { proxy: "engine.start" } }
+ is equivalent to
+ { start: { value: function () {
+ return this.engine.start.apply(this.engine, arguments);
+ } } }
+
+ Using `alias` rather than `proxy` would result in the
+ wrong (non-engine) `this` argument being passed to the
+ `start` method.
+
+ * `aliasSynthetic` - Aliases don't work if one of the
+ properties in the lookup chain is a temporary variable.
+ For example, aliasing `x` to `position[0]` is no good if
+ `position` itself has a getter like `transform.slice(12)`
+ because the assignment to the returned value will have
+ no effect.
+
+ `aliasSynthetic` can be used to capture the temporary,
+ assign to it, and then assign the whole temporary back.
+ { x: { aliasSynthetic: "position[0]" } }
+ Generates the same `get` as `alias`, but `set` is
+ function (v) {
+ var t = this.position;
+ t[0] = v;
+ this.position = t;
+ }
+
+ `aliasSynthetic` assumes the next-to-last value is the
+ temporary, e.g. in `a.b.c.d`, `a.b.c` is the temporary.
+ If rather e.g. `a.b` is the temporary, you can separate
+ the `alias` and `synthetic` parts:
+ { x: { alias: "a.b.c.d", synthetic: "a.b" } }
+
+ * `swizzle` - Swizzling lets you treat separate properties
+ as one array property. For example if you have a color
+ class with individual r, g, and b properties,
+ { rgb: { swizzle: ["r", "g", "b"] } }
+ is equivalent to
+ { rgb: {
+ get: function () { return [this.r, this.g, this.b] },
+ set: function (v) {
+ this.r = v[0];
+ this.g = v[1];
+ this.b = v[2];
+ }
+ } }
+
+ Any descriptor may also have the `chainable` property set,
+ which generates a chainable setter function. Chainable data
+ descriptors are writable by default.
+ { x: { value: 0, chainable: true } }
+ is equivalent to
+ { x: { value: 0, writable: true },
+ setX: { value: function (v) { this.x = v; return this; } }
+ }
+
+ ## Example
+
+ An example of a simple 2D Point class using XPDs:
+
+ var Point = yT(Object, {
+ constructor: function (x, y) {
+ this.x = x || 0;
+ this.y = y || 0;
+ },
+
+ 0: { alias: "x" },
+ 1: { alias: "y" },
+ xy: { swizzle: "xy" },
+ yx: { swizzle: "yx" },
+
+ angle: {
+ chainable: true,
+ get: function () {
+ return Math.atan2(this.y, this.x);
+ },
+ set: function (angle) {
+ var magnitude = this.magnitude;
+ this.x = Math.cos(angle) * magnitude;
+ this.y = Math.sin(angle) * magnitude;
+ }
+ },
+
+ magnitude: {
+ chainable: true,
+ get: function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ },
+ set: function (magnitude) {
+ var angle = this.angle;
+ this.x = Math.cos(angle) * magnitude;
+ this.y = Math.sin(angle) * magnitude;
+ }
+ },
+
+ length: 2
+ });
+
+ var p = new Point(3, 0);
+ p[0] === 3; // true
+ p.y = 4; p[1] == 4; // true
+ p.magnitude = 1; // normalize
+ p.xy = p.yx; // transpose
+
+ new Point().setMagnitude(m).setAngle(a);
+ // construct a point from an angle and magnitude in a
+ // single step.
+ */
+
+ /* jshint -W054 */ // Function constructors are the whole point here.
+
+ function isFunction (o) {
+ /** Check if a value is a function */
+ return Object.prototype.toString.call(o) === '[object Function]';
+ }
+
+ function update (dst, src) {
+ /** Copy every enumerable key and its value from src to dst */
+ for (var k in src)
+ dst[k] = src[k];
+ return dst;
+ }
+
+ function rooted (path) {
+ if (typeof path === "number")
+ return "[" + path + "]";
+ else if (path[0] !== "." && path[0] !== "[")
+ return "." + path;
+ else
+ return path;
+ }
+
+ function chainableName (name) {
+ return "set" + name[0].toUpperCase() + name.substring(1);
+ }
+
+ function chainableSetter (name) {
+ return new Function(
+ "value",
+ "this" + rooted(name) + " = value; " + "return this;");
+ }
+
+ function alias (path, readonly) {
+ path = rooted(path);
+ return readonly
+ ? { get: new Function("return this" + path + ";") }
+ : { get: new Function("return this" + path + ";"),
+ set: new Function("v", "return this" + path + " = v;") };
+ }
+
+ function proxy (path) {
+ path = rooted(path);
+ var prop = path.substr(0, path.lastIndexOf("."));
+ return {
+ value: new Function(
+ "return this" + path + ".apply(this" + prop + ", arguments);")
+ };
+ }
+
+ function swizzle (props) {
+ props = Array.prototype.map.call(props, function (p) {
+ return "this" + rooted(p);
+ });
+ var assignments = props.map(function (p, i) {
+ return p + " = x[" + i + "];";
+ });
+ return {
+ get: new Function("return [" + props.join(", ") + "];"),
+ set: new Function("x", assignments.join("\n"))
+ };
+ }
+
+ function aliasSynthetic (path) {
+ var idx = Math.max(path.lastIndexOf("."), path.lastIndexOf("["));
+ return synthetic(path.substring(0, idx), path);
+ }
+
+ function synthetic (synthPath, path) {
+ synthPath = rooted(synthPath);
+ path = rooted(path).substring(synthPath.length);
+ return {
+ get: new Function("return this" + synthPath + path + ";"),
+ set: new Function("v",
+ ["var t = this" + synthPath + ";",
+ "t" + path + " = v; ",
+ "this" + synthPath + " = t;",
+ ].join("\n"))
+ };
+ }
+
+ function isAccessorDescriptor (pd) {
+ /** Check if `pd` is a descriptor describing an accessor */
+ return pd && typeof pd === "object" && isFunction(pd.get);
+ }
+
+ function isDataDescriptor (pd) {
+ /** Check if `pd` is a descriptor describing data */
+ return pd && typeof pd === "object" && ("value" in pd);
+ }
+
+ function isDescriptor (pd) {
+ /** Check if `pd` is any kind of descriptor */
+ return isDataDescriptor(pd) || isAccessorDescriptor(pd);
+ }
+
+ function createDescriptors (name, xpd, pds) {
+ if (xpd.alias !== undefined) {
+ if (xpd.synthetic !== undefined)
+ pds[name] = update(xpd, synthetic(xpd.alias, xpd.synthetic));
+ else
+ pds[name] = update(xpd, alias(xpd.alias, xpd.readonly));
+ } else if (xpd.proxy !== undefined) {
+ pds[name] = update(xpd, proxy(xpd.proxy));
+ } else if (xpd.swizzle !== undefined) {
+ pds[name] = update(xpd, swizzle(xpd.swizzle));
+ } else if (xpd.aliasSynthetic !== undefined) {
+ pds[name] = update(xpd, aliasSynthetic(xpd.aliasSynthetic));
+ }
+
+ pds[name] = isDescriptor(xpd) ? xpd : { value: xpd };
+ if (xpd.chainable) {
+ pds[chainableName(name)] = { value: chainableSetter(name) };
+ if (isDataDescriptor(pds[name]) && !("writable" in pds[name]))
+ pds[name].writable = true;
+ }
+ }
+
+ function xpdsToPds (xpds) {
+ var pds = {};
+ Object.keys(xpds).forEach(function (k) {
+ createDescriptors(k, xpds[k], pds);
+ });
+ return pds;
+ }
+
+ function T (parent, xpds) {
+ /** Create a new class described by XPDs
+
+ This function is similar to `Object.create` but supports
+ extended property descriptors as described above.
+
+ yT(parent, map of extended descriptors)
+ yT(map of extended descriptors)
+ // The parent is assumed to be Object
+
+ This returns a _function_ which acts as a constructor for
+ the provided type. If there is a property descriptor named
+ `constructor` it is returned; otherwise a new function
+ that calls the parent's.
+
+ `parent` may be either the parent constructor or a
+ prototype.
+ */
+ if (!xpds) {
+ xpds = parent;
+ parent = Object;
+ }
+ parent = isFunction(parent) && parent.prototype || parent;
+ var pds = xpdsToPds(xpds);
+ var ctor = pds.constructor && pds.constructor.value;
+ if (!ctor || ctor === {}.constructor) {
+ ctor = parent
+ ? function () { parent.constructor.apply(this, arguments); }
+ : function () { };
+ pds.constructor = { value: ctor };
+ }
+ ctor.prototype = Object.create(parent, pds);
+ return ctor;
+ }
+
+ T.defineProperties = function (o, xpds) {
+ /** Add properties described by XPDs to an object
+
+ This function is similar to`Object.defineProperties`,
+ but supports the same features as `yT`.
+ */
+ return Object.defineProperties(o, xpdsToPds(xpds));
+ };
+
+ T.defineProperty = function (o, name, xpd) {
+ /** Add a property described by an XPD to an object
+
+ This function is similar to`Object.defineProperty`,
+ but supports the same features as `yT`.
+ */
+ var xpds = {};
+ xpds[name] = xpd;
+ return T.defineProperties(o, xpds);
+ };
+
+ T.getPropertyDescriptor = function (o, name) {
+ /** Look up a descriptor from `o`'s prototype chain */
+ var v = null;
+ while (!v && o) {
+ v = Object.getOwnPropertyDescriptor(o, name);
+ o = Object.getPrototypeOf(o);
+ }
+ return v;
+ };
+
+ T.isAccessorDescriptor = isAccessorDescriptor;
+ T.isDataDescriptor = isDataDescriptor;
+ T.isDescriptor = isDescriptor;
+
+ if (module)
+ module.exports = T;
+ else
+ this.yT = T;
+}).call(typeof exports === "undefined" ? this : exports,
+ typeof exports === "undefined" ? null : module);
--- /dev/null
+/* 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 () {
+ "use strict";
+
+ /** yf - yuu foundation
+
+ A collection of tools for functional programming and the kind
+ of sequence (array) manipulations usually associated with
+ functional programming.
+
+ yf doesn't depend on yuu specifically, but may use standard
+ features not yet widely supported provided by yuu/pre.
+ */
+
+ function isFunction (o) {
+ /** Check if a value is a function */
+ return Object.prototype.toString.call(o) === '[object Function]';
+ }
+
+ function isString (s) {
+ /** Check if a value is a string */
+ return Object.prototype.toString.call(s) === "[object String]";
+ }
+
+ function I (x) { return x; }
+
+ function K (x) { return function () { return x; }; }
+
+ /** Get the minimum or maximum of two objects
+
+ The default JavaScript Math.min and Math.max are unsuitable as
+ generic functions. They don't work on strings. Because they
+ support a variable numbers of arguments they don't work
+ correctly when passed directly to higher-order functions that
+ pass "bonus" arguments such as Array.prototype.reduce.
+
+ These functions work on any objects comparable with < and >,
+ and only consider two arguments.
+ */
+ function min (a, b) { return b < a ? b : a; }
+ function max (a, b) { return b > a ? b : a; }
+ function clamp(x, lo, hi) { return max(lo, min(hi, x)); }
+
+ /** Get a property from the this object by name */
+ function getter (key) { return this[key]; }
+
+ /** Set a property value on the this object by name */
+ function setter (key, value) { this[key] = value; }
+
+ function unbind (f) {
+ /** Factor out the special 'this' argument from a function
+
+ unbind(f)(a, b, c) is equivalent to f.call(a, b, c), and
+ you may store the return value for later use without
+ storing the original function.
+ */
+
+ // return f.call.bind(f) is ~15% slower than this closure on
+ // Chrome 31 and negligibly slower on Firefox 25.
+ return function () {
+ return f.call.apply(f, arguments);
+ };
+ }
+
+ /** Unbound versions of useful functions */
+ var slice = unbind(Array.prototype.slice);
+
+ function contains (seq, o) {
+ return Array.prototype.indexOf.call(seq, o) !== -1;
+ }
+
+ function lacks (seq, o) {
+ return !contains(seq, o);
+ }
+
+ /** The first element of a sequence */
+ function head (a) { return a[0]; }
+
+ /** All but the first element of a sequence */
+ function tail (a) { return slice(a, 1); }
+
+ /** The last element of a sequence */
+ function last (a) { return a[a.length - 1]; }
+
+ /** All but the last element of a sequence */
+ function most (a) { return slice(a, 0, -1); }
+
+ /** The length of a sequence */
+ function len (seq) { return seq.length; }
+
+ /** Note that yf's argument order is slightly different from
+ JavaScript's normal order (but the same as most other
+ languages with functional elements).
+
+ This is irrelevant for `fold` and `filter`, but `each` and
+ `map` can take multiple sequences to allow callback functions
+ with arbitrary arity.
+
+ Because of this difference, there is no final `thisArg` as in
+ e.g. the standard Array's forEach. Instead the `this` of
+ the original call is forwarded. For example,
+ someArray.forEach(callback, thisArg)
+ becomes
+ yf.each.call(thisArg, callback, someArray)
+ */
+
+ function foldl (callback, seq, value) {
+ /** Like Array's reduce, but with no extra arguments
+
+ This traverses the sequence in indexed order, low-to-high,
+ and calls the provided function with the accumulated value
+ and the sequence item.
+ */
+ var i = 0;
+ if (arguments.length === 2)
+ value = seq[i++];
+ for (; i < seq.length; ++i)
+ value = callback.call(this, value, seq[i]);
+ return value;
+ }
+
+ function foldr (callback, seq, value) {
+ /** Like foldl, but reversed.
+
+ This traverses the sequence in indexed order, high-to-low,
+ and calls the provided function with the sequence item and
+ the accumulated value.
+ */
+ var i = seq.length - 1;
+ if (arguments.length === 2)
+ value = seq[i--];
+ for (; i >= 0; --i)
+ value = callback.call(this, seq[i], value);
+ return value;
+ }
+
+ function call (f, x) { return f.call(this, x); }
+
+ function compose () {
+ /** Create a function by composing multiple functions
+
+ compose(f, g, h)(x, y, z) is equivalent to f(g(h(x, y, z))).
+ */
+ var inner = last(arguments);
+ var funcs = most(arguments);
+ return function () {
+ return foldr.call(this, call, funcs, inner.apply(this, arguments));
+ };
+ }
+
+ // Generating specialized functions for plucking the nth argument
+ // and passing it to a callback is 100-120x faster than the
+ // generic one in V8.
+ //
+ // So generate specialized functions for small argc, and only
+ // resort to the generic approach when there's many sequences.
+ var CALLS = [];
+
+ /* jshint -W054*/ /* Function constructor is a form of eval */
+ for (var args = []; args.length < 32;
+ args.push(", seqs[" + args.length + "][i]"))
+ CALLS[args.length] = new Function(
+ "callback", "i", "seqs",
+ "return callback.call(this" + args.join("") + ");");
+
+ function lookup (prop) {
+ /** Return a function that gets a particular property */
+
+ // Building this as a closure is ~2-5x faster than writing
+ // a function lookup(prop, o) and binding it.
+ return function (s) { return s[prop]; };
+ }
+
+ function callx (callback, i, seqs) {
+ return callback.apply(this, seqs.map(lookup(i)));
+ }
+
+ function calln (argc) {
+ return CALLS[argc] || callx;
+ }
+
+ function argv (f) {
+ /** Provide a variadic-like version of f
+
+ Arguments passed to the function including and after the
+ final named one are collected and passed as a single
+ sequence (not necessarily Array) value.
+ */
+ var length = f.length;
+ switch (length) {
+ case 0: throw new Error("don't use argv for 0-ary functions");
+ case 1: return function () { return f.call(this, arguments); };
+ default:
+ return function () {
+ arguments[length - 1] = slice(arguments, length - 1);
+ return f.apply(this, arguments);
+ };
+ }
+ }
+
+ var each = argv(function (callback, seqs) {
+ /** Call a function on the given sequences' elements
+
+ If one sequence is longer than the others, `undefined`
+ will be passed for that sequence's argument.
+
+ For example, each(f, [a, b, c], [x, y]) is equivalent to
+ f(a, x);
+ f(b, y);
+ f(c, undefined);
+
+ If you want to pass a thisArg to the callback, use
+ each.call(thisArg, callback, ...).
+ */
+ var length = Math.max.apply(Math, seqs.map(len));
+ var fn = calln(seqs.length);
+ for (var i = 0; i < length; ++i)
+ fn.call(this, callback, i, seqs);
+ });
+
+ var eachr = argv(function (callback, seqs) {
+ /** Call a function on the given sequences' elements, backwards
+
+ If one sequence is longer than the others, `undefined` will be
+ passed for that sequence's argument.
+
+ For example, each(f, [a, b, c], [x, y]) is equivalent to
+ f(c, undefined);
+ f(b, y);
+ f(a, x);
+
+ If you want to pass a thisArg to the callback, use
+ eachr.call(thisArg, callback, ...).
+ */
+ var length = Math.max.apply(Math, seqs.map(len));
+ var fn = calln(seqs.length);
+ for (var i = length - 1; i >= 0; --i)
+ fn.call(this, callback, i, seqs);
+ });
+
+ function pusher (a) {
+ // Ridiculously faster than a.push.bind(a). :(
+ return function (o) { a.push(o); };
+ }
+
+ function map () {
+ /** Build an array by applying a function to sequences
+
+ Similar to Array.prototype.map, but allows passing multiple
+ sequences to call functions of any arity, as with each.
+
+ If you want to pass a thisArg to the callback, use
+ map.call(thisArg, callback, ...).
+ */
+ var a = [];
+ arguments[0] = compose(pusher(a), arguments[0]);
+ each.apply(this, arguments);
+ return a;
+ }
+
+ function mapr () {
+ /** Build an array by applying a function to sequences backwards
+
+ As eachr is to each, so mapr is to map.
+ */
+ var a = [];
+ arguments[0] = compose(pusher(a), arguments[0]);
+ eachr.apply(this, arguments);
+ return a;
+ }
+
+ function filter (callback, seq) {
+ /** Build an array without the elements that fail the predicate
+
+ Like Array.prototype.filter, but if null is passed for the
+ predicate, any false-y values will be removed.
+ */
+ var a = [];
+ callback = callback || I;
+ function _ (o) { if (callback.call(this, o)) a.push(o); }
+ each.call(this, _, seq);
+ return a;
+ }
+
+ function packed (f) {
+ /** Pack arguments to a function into an Array
+
+ packed(f)([a, b, c]) is equivalent to f(a, b, c).
+ */
+ return function (args) { return f.apply(this, args); };
+ }
+
+ function argcd () {
+ /** Create a function that dispatches based on argument count
+
+ Pass a list of functions with varying argument lengths, e.g.
+ var f = argcd(function () { ... },
+ function (a, b) { ... },
+ function (a, b, c) { ... });
+
+ This can be used for function overloading, clearer
+ defaults for positional parameters, or significantly more
+ evil things.
+
+ The functions can also be accessed directly by their
+ argument count, e.g. f[0], f[2], f[3] above.
+
+ If no matching argument count is found for a call, a
+ TypeError is raised.
+ */
+ if (arguments.length === 1)
+ throw new Error("don't use argcd for one function");
+ var table = (function table () {
+ return table[arguments.length].apply(this, arguments);
+ });
+ for (var i = 0; i < arguments.length; ++i)
+ table[arguments[i].length] = arguments[i];
+ return table;
+ }
+
+ var irange = argcd(
+ /** Call the provided callback counting as it goes
+
+ irange(x) counts from [0, x), by 1.
+
+ irange(x, y) counts from [x, y), by 1.
+
+ irange(x, y, s) counts from [x, y), by s.
+ */
+ function (callback, stop) {
+ return irange.call(this, callback, 0, stop, 1);
+ },
+ function (callback, start, stop) {
+ return irange.call(this, callback, start, stop, 1);
+ },
+ function (callback, start, stop, step) {
+ var length = Math.max(Math.ceil((stop - start) / step), 0);
+ for (var i = 0; i < length; ++i)
+ callback.call(this, start + step * i);
+ }
+ );
+
+ function capture1 (ifunc) {
+ /** Build an array from the values of an iterating function
+
+ The elements added to the array are the first arguments
+ the iterating function passes.
+ */
+ return function () {
+ var a = [];
+ var args = slice(arguments);
+ args.unshift(pusher(a));
+ ifunc.apply(this, args);
+ return a;
+ };
+ }
+
+ /** Generate an Array from range of numbers, as irange */
+ var range = capture1(irange);
+
+ function ipairs (callback, o) {
+ /** Call the provided callback with each key and value of the object */
+ each.call(this, function (k) { callback.call(this, k, o[k]); },
+ Object.keys(o));
+ }
+
+ function iprototypeChain (callback, o) {
+ /** Traverse an object and its prototype chain */
+ do {
+ callback.call(this, o);
+ } while ((o = Object.getPrototypeOf(o)));
+ }
+
+ function allKeys (o) {
+ /** Get *ALL* property names. Even unowned non-enumerable ones. */
+ var props = [];
+ iprototypeChain(compose(packed(props.push).bind(props),
+ Object.getOwnPropertyNames),
+ o);
+ return props.sort().filter(function (p, i, a) {
+ return p !== a[i - 1];
+ });
+ }
+
+ var some = argv(function (callback, seqs) {
+ callback = callback || I;
+ var length = Math.max.apply(Math, seqs.map(len));
+ var fn = calln(seqs.length);
+ for (var i = 0; i < length; ++i)
+ if (fn.call(this, callback, i, seqs))
+ return true;
+ return false;
+ });
+
+ var eachrUntil = argv(function (callback, seqs) { // Reverse of `some`
+ callback = callback || I;
+ var length = Math.max.apply(Math, seqs.map(len));
+ var fn = calln(seqs.length);
+ for (var i = length - 1; i >= 0; --i)
+ if (fn.call(this, callback, i, seqs))
+ return true;
+ return false;
+ });
+
+ function not (f) {
+ /** Build a function equivalent to !f(...) */
+ return function () { return !f.apply(this, arguments); };
+ }
+
+ function every () {
+ arguments[0] = not(arguments[0] || I);
+ return !some.apply(this, arguments);
+ }
+
+ function none () {
+ return !some.apply(this, arguments);
+ }
+
+ function repeat (o, n) {
+ return (new Array(n)).fill(o);
+ }
+
+ function arrayify (o) {
+ /** Equivalent to [].concat(o) but faster */
+ return Array.isArray(o) ? o : [o];
+ }
+
+ function stash (key, value, a) {
+ each(function (o) { o[key] = value; }, a);
+ }
+
+ function without (seq, o) {
+ if (arguments.length === 2)
+ return filter(function (a) { return a !== o; }, seq);
+ var os = slice(arguments, 1);
+ return filter(lacks.bind(null, os), seq);
+ }
+
+ function construct (ctr, args) {
+ args = slice(args);
+ args.unshift(ctr);
+ return new (Function.prototype.bind.apply(ctr, args))();
+ }
+
+ function new_ (ctr) {
+ return function () { return construct(ctr, arguments); };
+ }
+
+ function counter (start) {
+ var i = +(start || 0);
+ return function () { return i++; };
+ }
+
+ function volatile (f) {
+ return { valueOf: f };
+ }
+
+ function transform (callback, seq) {
+ irange.call(this, function (i) {
+ seq[i] = callback.call(this, seq[i]);
+ }, seq.length);
+ }
+
+ function mapValues (callback, o) {
+ var r = {};
+ for (var k in o)
+ r[k] = callback.call(this, o[k]);
+ return r;
+ }
+
+ function insertBefore (seq, o, before) {
+ var idx = seq.lastIndexOf(before);
+ if (idx === -1)
+ seq.push(o);
+ else
+ seq.splice(idx, 0, o);
+ return seq;
+ }
+
+ function seqEqual (a, b) {
+ if (a.length !== b.length)
+ return false;
+ for (var i = 0; i < a.length; ++i)
+ if (a[i] !== b[i])
+ return false;
+ return true;
+ }
+
+ function debounce (callback, wait) {
+ wait = wait || 100;
+ var this_ = null;
+ var args = null;
+ var id = null;
+
+ function _ () {
+ callback.apply(this_, args);
+ this_ = null;
+ args = null;
+ id = null;
+ }
+
+ return function () {
+ if (id !== null && this_ !== this)
+ _();
+ clearTimeout(id);
+ this_ = this;
+ args = arguments;
+ id = setTimeout(_, wait);
+ };
+ }
+
+ function pluck (prop, seq) {
+ return map(lookup(prop), seq);
+ }
+
+ Object.assign(this, {
+ allKeys: allKeys,
+ argcd: argcd,
+ argv: argv,
+ arrayify: arrayify,
+ clamp: clamp,
+ compose: compose,
+ construct: construct,
+ contains: contains,
+ counter: counter,
+ debounce: debounce,
+ each: each,
+ eachUntil: some,
+ eachr: eachr,
+ eachrUntil: eachrUntil,
+ every: every,
+ filter: filter,
+ foldl: foldl,
+ foldr: foldr,
+ getter: getter,
+ head: head,
+ I: I,
+ insertBefore: insertBefore,
+ ipairs: ipairs,
+ irange: irange,
+ isFunction: isFunction,
+ isString: isString,
+ K: K,
+ lacks: lacks,
+ last: last,
+ len: len,
+ lookup: lookup,
+ map: map,
+ mapr: mapr,
+ mapValues: mapValues,
+ max: max,
+ min: min,
+ most: most,
+ new_: new_,
+ none: none,
+ pluck: pluck,
+ range: range,
+ repeat: repeat,
+ seqEqual: seqEqual,
+ setter: setter,
+ slice: slice,
+ some: some,
+ stash: stash,
+ tail: tail,
+ transform: transform,
+ unbind: unbind,
+ volatile: volatile,
+ without: without,
+
+ VERSION: 0
+ });
+
+}).call(typeof exports !== "undefined" ? exports : (this.yf = {}));
--- /dev/null
+{
+ "browser": true,
+ "laxbreak": true,
+ "globalstrict": true,
+ "validthis": true,
+ "devel": true,
+ "unused": "vars",
+ "camelcase": true,
+ "eqeqeq": true,
+ "latedef": true,
+ "nonew": true,
+ "undef": true,
+ "trailing": true,
+ "globals": {
+ "Event": false,
+ "Promise": false,
+ "Hammer": false,
+ "Buffer": false,
+ "gl": false,
+ "yf": false,
+ "yT": false,
+ "yuu": false,
+ "ystorage": false,
+ "mat4": false,
+ "vec3": false,
+ "vec2": false,
+ "quat": false,
+ "exports": false,
+ "module": false,
+ "process": false,
+ "require": false,
+ "escape": true
+ }
+}
--- /dev/null
+var JS = this.JS || require('jstest');
+var yuu = require('yuu/core');
+
+JS.Test.describe('yuu core', function () { with (this) {
+ it("knows signum", function () { with (this) {
+ assertEqual(-1, Math.sign(-10));
+ assertEqual(1, Math.sign(10));
+ assertEqual(0, Math.sign(0));
+ }});
+ it("knows signum for weird numbers", function () { with (this) {
+ assertEqual(-1, Math.sign(-Infinity));
+ assertEqual(1, Math.sign(Infinity));
+ assert(Number.isNaN(Math.sign(NaN)));
+ }});
+
+ it("String endsWith", function () { with (this) {
+ assert("12345".endsWith("12345"));
+ assert("12345".endsWith("45"));
+ assert("12345".endsWith("5"));
+ assert("12345".endsWith(""));
+ assertNot("12345".endsWith("0"));
+ assertNot("12345".endsWith("35"));
+ assertNot("12345".endsWith("34"));
+ }});
+
+ it("String startsWith", function () { with (this) {
+ assert("12345".startsWith("12345"));
+ assert("12345".startsWith("12"));
+ assert("12345".startsWith("1"));
+ assert("12345".startsWith(""));
+ assertNot("12345".startsWith("0"));
+ assertNot("12345".startsWith("13"));
+ assertNot("12345".startsWith("23"));
+ }});
+
+ it("splits path extensions", function () { with (this) {
+ assertEqual(["a", ".png"], yuu.splitPathExtension("a.png"));
+ assertEqual(["a.b", ".png"], yuu.splitPathExtension("a.b.png"));
+ assertEqual(["a", ""], yuu.splitPathExtension("a"));
+ assertEqual(["a.b/c", ".png"], yuu.splitPathExtension("a.b/c.png"));
+ assertEqual([".gitignore", ""], yuu.splitPathExtension(".gitignore"));
+ }});
+
+ JS.Test.describe("resourcePath", function () { with (this) {
+ var f = yuu.resourcePath;
+ it("leaves ordinary paths alone", function () { with (this) {
+ assertEqual("foo.png", f("foo.png", "error", "error"));
+ assertEqual("foo/bar.png", f("foo/bar.png", "error", "error"));
+ assertEqual("foo@2x.png", f("foo@2x.png", "error", "error"));
+ }});
+ it("expands @ paths", function () { with (this) {
+ assertEqual("data/images/foo.png", f("@foo", "images", "png"));
+ assertEqual("data/images/foo.png", f("@foo.png", "images", "png"));
+ assertEqual("data/images/foo.jpg", f("@foo.jpg", "images", "png"));
+ assertEqual("data/images/foo/bar.png", f("@foo/bar", "images", "png"));
+ assertEqual("foo/data/images/bar.png", f("foo/@bar", "images", "png"));
+ }});
+ it("expands yuu/@ paths", function () { with (this) {
+ assertEqual(yuu.PATH + "data/images/bar.png",
+ f("yuu/@bar", "images", "png"));
+ }});
+ }});
+
+ JS.Test.describe('lerping', function () { with (this) {
+ it("lerps numbers", function () { with (this) {
+ assertEqual(0.0, (0).lerp(1, 0.0));
+ assertEqual(0.5, (0).lerp(1, 0.5));
+ assertEqual(1.0, (0).lerp(1, 1.0));
+ }});
+
+ var arrayTypes = {
+ "untyped": Array,
+ "float32": Float32Array,
+ "float64": Float64Array,
+ "int8": Int8Array,
+ "uint8": Uint8Array,
+ "int16": Int16Array,
+ "uint16": Uint16Array,
+ "int32": Int32Array,
+ "uint32": Uint32Array
+ };
+
+ Object.keys(arrayTypes).forEach(function (name) {
+ var A = arrayTypes[name];
+ it("lerps " + name + " arrays element-wise", function () { with (this) {
+ var a = new A([0, 0, 0]);
+ var b = new A([0, 10, 20]);
+ assertEqual(a, a.lerp(b, 0));
+ assertEqual(b, a.lerp(b, 1));
+ assertEqual(new A([0, 5, 10]), a.lerp(b, 0.5));
+ }});
+ });
+
+ Object.keys(arrayTypes).forEach(function (name) {
+ var A = arrayTypes[name];
+ it("slices " + name + " arrays", function () { with (this) {
+ var a = new A([0, 1, 2, 3, 4, 5]);
+ var b = a.slice();
+ assertEqual(a, b);
+ assertKindOf(A, b);
+ assertNotSame(a, b);
+ }});
+ });
+ }});
+}});
--- /dev/null
+var JS = this.JS || require('jstest');
+var yuu = require('yuu/input');
+
+JS.Test.describe('yuu input', function () { with (this) {
+ it("has consistent key names", function () { with (this) {
+ Object.keys(yuu.KEY_NAMES).forEach(function (code) {
+ assertEqual(yuu.KEY_NAMES[code], yuu.keyCodeName(code));
+ });
+ }});
+ it("handles unknown key names", function () { with (this) {
+ Object.keys(yuu.KEY_NAMES).forEach(function (name) {
+ assertEqual("key:123456", yuu.keyCodeName(123456));
+ });
+ }});
+ it("handles event-known key names", function () { with (this) {
+ Object.keys(yuu.KEY_NAMES).forEach(function (name) {
+ assertEqual("bogus", yuu.keyCodeName(123456, "BOGUS"));
+ });
+ }});
+ it("ignores browser default names", function () { with (this) {
+ Object.keys(yuu.KEY_NAMES).forEach(function (name) {
+ assertEqual("key:123456", yuu.keyCodeName(123456, "Unidentified"));
+ });
+ }});
+
+ JS.Test.describe('KeyBindSet', function () { with (this) {
+ it("holds binds", function () { with (this) {
+ var bindset = new yuu.KeyBindSet();
+ assertEqual(0, bindset.binds.length);
+ bindset = new yuu.KeyBindSet({ a: 'a', b: 'b' });
+ assertEqual(2, bindset.binds.length);
+ }});
+
+ it("binds new binds", function () { with (this) {
+ var bindset = new yuu.KeyBindSet({ a: 'a', b: 'b' });
+ assertEqual(2, bindset.binds.length);
+ bindset.bind('c', 'c');
+ assertEqual(3, bindset.binds.length);
+ }});
+
+ it("doesn't bind duplicates", function () { with (this) {
+ var bindset = new yuu.KeyBindSet({ a: 'a', b: 'b' });
+ assertEqual(2, bindset.binds.length);
+ bindset.bind("a", "a different a");
+ assertEqual(2, bindset.binds.length);
+ bindset.bind("a+b", "a and b");
+ assertEqual(3, bindset.binds.length);
+ bindset.bind("b+a", "b and a");
+ assertEqual(3, bindset.binds.length);
+ }});
+
+ it("unbinds", function () { with (this) {
+ var bindset = new yuu.KeyBindSet({ a: 'a', b: 'b' });
+ assertEqual(2, bindset.binds.length);
+ bindset.unbind('a');
+ assertEqual(1, bindset.binds.length);
+ bindset.unbind('x');
+ assertEqual(1, bindset.binds.length);
+ bindset.bind('a', 'a');
+ assertEqual(2, bindset.binds.length);
+ }});
+ }});
+
+ JS.Test.describe('InputState', function () { with (this) {
+ var bindset1 = new yuu.KeyBindSet({
+ "a": "a",
+ "b": "++b",
+ "c": "+c",
+ "a+b": "a and b",
+ });
+ var bindset2 = new yuu.KeyBindSet({
+ "a": "a different a",
+ "a+b+c": "a and b and c",
+ "mousemove": "mouse moved",
+ "d+mousemove": "mouse moved and d",
+ });
+
+ it("activates simple binds", function () { with (this) {
+ var input = new yuu.InputState([bindset1]);
+ assertEqual(["a"], input.keydown("a"));
+ assertNot(input.keyup("a"));
+ assertEqual(["a"], input.keydown("a"));
+ assertNot(input.keyup("a"));
+ }});
+
+ it("doesn't activate nothing", function () { with (this) {
+ var input = new yuu.InputState([bindset1]);
+ assertEqual(null, input.keydown("x"));
+ assertEqual(null, input.keyup("x"));
+ }});
+
+ it("doesn't repeat", function () { with (this) {
+ var input = new yuu.InputState([bindset1]);
+ assertEqual(["a"], input.keydown("a"));
+ assertEqual([], input.keydown("a"));
+ input.keyup("a");
+ assertEqual(["a"], input.keydown("a"));
+ }})
+
+ it("activates toggle binds", function () { with (this) {
+ var input = new yuu.InputState([bindset1]);
+ assertEqual(["++b"], input.keydown("b"));
+ }});
+
+ it("activates flag binds", function () { with (this) {
+ var input = new yuu.InputState([bindset1]);
+ assertEqual(["+c"], input.keydown("c"));
+ assertEqual([], input.keydown("c"));
+ assertEqual(["-c"], input.keyup("c"));
+ }});
+
+ it("handles multiple keys", function () { with (this) {
+ var input = new yuu.InputState([bindset1]);
+ assertEqual(["a"], input.keydown("a"));
+ assertEqual(["a and b"], input.keydown("b"));
+ }});
+
+ it("handles change states", function () { with (this) {
+ var input = new yuu.InputState([bindset2]);
+ assertEqual(["mouse moved"], input.mousemove());
+ assertEqual(["mouse moved"], input.mousemove());
+ }});
+
+ it("handles change states with a key", function () { with (this) {
+ var input = new yuu.InputState([bindset2]);
+ assertEqual(null, input.keydown("d"));
+ assertEqual(["mouse moved and d"], input.mousemove());
+ assertEqual(["mouse moved and d"], input.mousemove());
+ assertEqual(null, input.keyup("d"));
+ assertEqual(["mouse moved"], input.mousemove());
+ }});
+
+ it("masks lower sets", function () { with (this) {
+ var input = new yuu.InputState([bindset1, bindset2]);
+ assertEqual(["a different a"], input.keydown("a"));
+ }});
+
+ it("always picks the longest command", function () { with (this) {
+ var input = new yuu.InputState([bindset1, bindset2]);
+ input.keydown("b");
+ assertEqual(["a and b"], input.keydown("a"));
+ assertEqual(["a and b and c"], input.keydown("c"));
+ }});
+
+ }});
+}});
--- /dev/null
+var JS = this.JS || require('jstest');
+require('yuu/pre');
+var ystorage = require('yuu/storage');
+
+JS.Test.describe('ystorage', function () { with (this) {
+ JS.Test.describe("PrefixedStorage", function () { with (this) {
+ before(function () {
+ this.backend = new ystorage.FakeStorage();
+ this.a = new ystorage.PrefixedStorage(this.backend, "a");
+ this.b = new ystorage.PrefixedStorage(this.backend, "b");
+ this.c1 = new ystorage.PrefixedStorage(this.backend, "c");
+ this.c2 = new ystorage.PrefixedStorage(this.backend, "c");
+ });
+
+ it('starts empty', function () { with (this) {
+ assertEqual(0, this.backend.length);
+ assertEqual(0, this.a.length);
+ assertEqual(0, this.b.length);
+ assertEqual(0, this.c1.length);
+ assertEqual(0, this.c2.length);
+ assertEqual(null, this.backend.key(0));
+ assertEqual(null, this.a.key(0));
+ assertEqual(null, this.b.key(0));
+ assertEqual(null, this.c1.key(0));
+ assertEqual(null, this.c2.key(0));
+ }});
+
+ it('stores and retrieves a value', function () { with (this) {
+ this.a.setItem("key", "value");
+ assertEqual(1, this.backend.length);
+ assertEqual(1, this.a.length);
+ assertEqual(0, this.b.length);
+ assertEqual(0, this.c1.length);
+ assertEqual(0, this.c2.length);
+ assertEqual("a -- key", this.backend.key(0));
+ assertEqual("key", this.a.key(0));
+ assertEqual(null, this.b.key(0));
+ assertEqual(null, this.c1.key(0));
+ assertEqual(null, this.c2.key(0));
+ }});
+
+ it('avoids other values when clearing', function () { with (this) {
+ this.a.setItem("key", "value a");
+ this.b.setItem("key", "value b");
+ assertEqual(2, this.backend.length);
+ assertEqual(1, this.a.length);
+ assertEqual(1, this.b.length);
+ this.a.clear();
+ assertEqual(1, this.backend.length);
+ assertEqual(0, this.a.length);
+ assertEqual(1, this.b.length);
+ assertEqual(null, this.a.key(0));
+ assertEqual(null, this.a.getItem("key"));
+ assertEqual("key", this.b.key(0));
+ assertEqual("value b", this.b.getItem("key"));
+ }});
+
+ it('shares with the same prefix', function () { with (this) {
+ this.c1.setItem("key", "value");
+ assertEqual("key", this.c1.key(0));
+ assertEqual("key", this.c2.key(0));
+ assertEqual("value", this.c1.getItem("key"));
+ assertEqual("value", this.c2.getItem("key"));
+ }});
+
+ }});
+
+ JS.Test.describe("PrefixedStorage", function () { with (this) {
+ it('stores objects', function () { with (this) {
+ var storage = new ystorage.Storage();
+ assertEqual(undefined, storage.getObject("key"));
+ storage.setObject("key", [1, 2, 3]);
+ assertEqual([1, 2, 3], storage.getObject("key"));
+ }});
+ it('handles defaults', function () { with (this) {
+ var storage = new ystorage.Storage(null, {
+ 'should be null': null,
+ 'should be undefined': undefined,
+ 'should be false': false
+ });
+ assertEqual(null, storage.getObject('should be null'));
+ assertEqual(false, storage.getObject('should be false'));
+ assertEqual(undefined, storage.getObject('should be undefined'));
+ assertEqual(undefined, storage.getObject('should not exist'));
+ }});
+ it('handles fallbacks', function () { with (this) {
+ var storage = new ystorage.Storage();
+ assertEqual(undefined, storage.getObject('?', undefined));
+ assertEqual(null, storage.getObject('?', null));
+ assertEqual(false, storage.getObject('?', false));
+ }});
+ it('prefers defaults to fallbacks', function () { with (this) {
+ var storage = new ystorage.Storage(null, {
+ 'should be null': null,
+ 'should be undefined': undefined,
+ 'should be false': false
+ });
+ assertEqual(null, storage.getObject('should be null', 0));
+ assertEqual(false, storage.getObject('should be false', 0));
+ assertEqual(undefined, storage.getObject('should be undefined', 0));
+ assertEqual(0, storage.getObject('should not exist', 0));
+ }});
+ it('keeps defaults', function () { with (this) {
+ var storage = new ystorage.Storage(null, { a: 'a' });
+ assertEqual('a', storage.getObject('a'));
+ storage.setObject('a', 'b');
+ assertEqual('b', storage.getObject('a'));
+ storage.removeObject('a');
+ assertEqual('a', storage.getObject('a'));
+ }});
+ }})
+
+}});
--- /dev/null
+var JS = this.JS || require('jstest');
+var yT = this.yT || require('yuu/yT');
+
+JS.Test.describe('yT', function() { with (this) {
+
+ it("makes Object subclasses", function () { with (this) {
+ var A = yT(Object, {});
+ assertKindOf(Object, new A());
+ }});
+ it("makes Object subclasses from a prototype", function () { with (this) {
+ var A = yT(Object.prototype, {});
+ assertKindOf(Object, new A());
+ }});
+ it("makes a non-Object subclass", function () { with (this) {
+ var A = yT(null, {});
+ var a = new A();
+ assertKindOf("object", a);
+ assertNot(a instanceof Object);
+ }});
+ it("makes Object subclasses by default", function () { with (this) {
+ var A = yT({});
+ assertKindOf(Object, new A());
+ }});
+ it("makes nested subclasses", function () { with (this) {
+ var A = yT({});
+ var B = yT(A, {});
+ assertKindOf(Object, new B());
+ assertKindOf(A, new B());
+ assertKindOf(B, new B());
+ }});
+ it("makes nested subclasses from a prototype", function () { with (this) {
+ var A = yT({});
+ var B = yT(A.prototype, {});
+ assertKindOf(Object, new B());
+ assertKindOf(A, new B());
+ assertKindOf(B, new B());
+ }});
+
+ it("sets constructors", function () { with (this) {
+ function ctor () {};
+ var A = yT({ constructor: ctor });
+ var a = new A();
+ assertSame(A, a.constructor);
+ assertSame(ctor, a.constructor);
+ }});
+ it("auto-creates root constructors", function () { with (this) {
+ var A = yT({});
+ var a = new A();
+ assertSame(A, a.constructor);
+ }});
+ it("auto-creates parent-calling constructors", function () { with (this) {
+ var A = yT({ constructor: function (x, y) { this.i = x + y; } });
+ var B = yT(A, {});
+ var b = new B(1, 2);
+ assertEqual(3, b.i);
+ assertSame(B, b.constructor);
+ assertNotSame(A, b.constructor);
+ }});
+
+ it("creates data descriptors", function () { with (this) {
+ var A = yT({ X: { value: 1 } });
+ var a = new A();
+ assertEqual(1, a.X);
+ a.X = 2;
+ assertEqual(1, a.X, "X should not be writable");
+ }});
+ it("creates writable data descriptors", function () { with (this) {
+ var A = yT({ x: { value: 1, writable: true } });
+ var a = new A();
+ assertEqual(1, a.x);
+ a.x = 2;
+ assertEqual(2, a.x);
+ }});
+ it("creates bare value descriptors", function () { with (this) {
+ var A = yT({ X: 1 });
+ var a = new A();
+ assertEqual(1, a.X);
+ a.X = 2;
+ assertEqual(1, a.X, "X should not be writable");
+ }});
+ it("creates read-only aliases", function () { with (this) {
+ var A = yT({ a: { alias: "b", readonly: true } });
+ var a = new A();
+ a.b = "hello";
+ assertEqual("hello", a.a);
+ a.b = "world";
+ assertEqual("world", a.a);
+ a.a = "goodbye";
+ assertEqual("world", a.a);
+ assertEqual("world", a.b);
+ }});
+ it("creates read-write aliases", function () { with (this) {
+ var A = yT({ a: { alias: "b", readonly: false } });
+ var a = new A();
+ a.b = "hello";
+ assertEqual("hello", a.a);
+ a.b = "world";
+ assertEqual("world", a.a);
+ a.a = "goodbye";
+ assertEqual("goodbye", a.a);
+ assertEqual("goodbye", a.b);
+ }});
+ it("creates read-write aliases by default", function () { with (this) {
+ var A = yT({ a: { alias: "b" } });
+ var a = new A();
+ a.b = "hello";
+ assertEqual("hello", a.a);
+ a.b = "world";
+ assertEqual("world", a.a);
+ a.a = "goodbye";
+ assertEqual("goodbye", a.a);
+ assertEqual("goodbye", a.b);
+ }});
+}});
+
+JS.Test.describe('an example Point type', function() { with (this) {
+ var Point = yT({
+ constructor: function (x, y) {
+ this.x = x || 0;
+ this.y = y || 0;
+ },
+
+ x: { value: 0, chainable: true },
+ y: { value: 0, chainable: true },
+ 0: { alias: "x" },
+ 1: { alias: "y" },
+ xy: { swizzle: "xy" },
+ yx: { swizzle: "yx" },
+
+ angle: {
+ chainable: true,
+ get: function () {
+ return Math.atan2(this.y, this.x);
+ },
+ set: function (angle) {
+ var magnitude = this.magnitude;
+ this.x = Math.cos(angle) * magnitude;
+ this.y = Math.sin(angle) * magnitude;
+ }
+ },
+
+ magnitude: {
+ chainable: true,
+ get: function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ },
+ set: function (magnitude) {
+ var angle = this.angle;
+ this.x = Math.cos(angle) * magnitude;
+ this.y = Math.sin(angle) * magnitude;
+ }
+ },
+
+ length: 2
+ });
+
+ it("constructs 0, 0 by default", function () { with (this) {
+ var p = new Point();
+ assertEqual(0, p.x);
+ assertEqual(0, p.y);
+ assertEqual(2, p.length);
+ }});
+
+
+ it("has chainable setters", function () { with (this) {
+ var p = new Point().setX(1).setY(2);
+ assertEqual(1, p.x);
+ assertEqual(2, p.y);
+ }});
+
+ it("x is also 0", function () { with (this) {
+ var p = new Point();
+ assertEqual(0, p.x);
+ assertEqual(0, p[0]);
+ p.x = 1;
+ assertEqual(1, p.x);
+ assertEqual(1, p[0]);
+ p[0] = 2;
+ assertEqual(2, p.x);
+ assertEqual(2, p[0]);
+ }});
+ it("y is also 1", function () { with (this) {
+ var p = new Point();
+ assertEqual(0, p.y);
+ assertEqual(0, p[1]);
+ p.y = 1;
+ assertEqual(1, p.y);
+ assertEqual(1, p[1]);
+ p[1] = 2;
+ assertEqual(2, p.y);
+ assertEqual(2, p[1]);
+ }});
+
+ it("x and y have swizzling", function () { with (this) {
+ var p = new Point();
+ assertEqual([0, 0], p.xy);
+ assertEqual([0, 0], p.yx);
+ p.xy = [1, 0];
+ assertEqual([1, 0], p.xy);
+ assertEqual([0, 1], p.yx);
+ p.xy = p.yx;
+ assertEqual([0, 1], p.xy);
+ assertEqual([1, 0], p.yx);
+ }});
+
+ it("can change magnitude", function () { with (this) {
+ var p = new Point(1, 0);
+ assertEqual(1, p.magnitude);
+ p.magnitude = 10;
+ assertEqual([10, 0], p.xy);
+ }});
+ it("can change angle", function () { with (this) {
+ var delta = 1e-13;
+ var p = new Point(1, 0);
+ assertEqual(0, p.angle);
+ p.angle = Math.PI / 2;
+ assertInDelta(Math.PI / 2, p.angle, delta);
+ assertInDelta(0, p.x, delta);
+ assertInDelta(1, p.y, delta);
+ p.angle = Math.PI / 4;
+ assertInDelta(Math.PI / 4, p.angle, delta);
+ assertInDelta(Math.sqrt(2) / 2, p.x, delta);
+ assertInDelta(Math.sqrt(2) / 2, p.y, delta);
+ assertInDelta(p.x, p.y, delta);
+ }});
+}});
+
--- /dev/null
+var JS = this.JS || require('jstest');
+require('yuu/pre');
+var yf = this.yf || require('yuu/yf');
+
+JS.Test.describe('yf', function() { with (this) {
+
+ it("allKeys", function () { with (this) {
+ assert(yf.contains(yf.allKeys(Object(1)), "hasOwnProperty"));
+ assertNot(yf.contains(yf.allKeys(Object(1)), "not a property"));
+ }});
+
+ it('argcd', function () { with (this) {
+ var f = yf.argcd(
+ function () { return 0; },
+ function (a) { return 1; },
+ function (a, b, c, d) { return 4; }
+ );
+ assertEqual(0, f());
+ assertEqual(1, f(""));
+ assertEqual(4, f("", "", "", ""));
+ assertThrows(TypeError, f.bind(null, "", ""));
+ }});
+
+ it('arrayify', function () { with (this) {
+ assertEqual([1], yf.arrayify(1));
+ assertEqual([1], yf.arrayify([1]));
+ }});
+
+ it('clamp', function () { with (this) {
+ assertEqual(0.5, yf.clamp(0.5, 0, 1));
+ assertEqual(1, yf.clamp(10, 0, 1));
+ assertEqual(0, yf.clamp(-1, 0, 1));
+ }})
+
+ it('compose', function () { with (this) {
+ function inc (a) { return a + 1; }
+ function add (a, b) { return a + b; }
+
+ var plus1 = yf.compose(inc);
+ var plus2 = yf.compose(inc, inc);
+ var plus3 = yf.compose(inc, inc, inc);
+ var addplus3 = yf.compose(plus3, add);
+
+ assertEqual(1, plus1(0));
+ assertEqual(2, plus2(0));
+ assertEqual(3, plus3(0));
+ assertEqual(6, addplus3(1, 2));
+
+ function this_ () { return this; }
+ var f = yf.compose(this_, this_, this_);
+ assertSame(this, f.call(this));
+ }});
+
+ it('construct', function () { with (this) {
+ assertEqual([], yf.construct(Array, []));
+ assertEqual([1, 2, 3], yf.construct(Array, [1, 2, 3]));
+ }});
+
+ it('counter', function () { with (this) {
+ var c0 = yf.counter(0);
+ var c5 = yf.counter(5);
+ assertEqual(0, c0());
+ assertEqual(1, c0());
+ assertEqual(2, c0());
+ assertEqual(5, c5());
+ assertEqual(6, c5());
+ }});
+
+ it('new_', function () { with (this) {
+ var a = yf.new_(Array);
+ assertEqual([], a());
+ assertEqual([1, 2, 3], a(1, 2, 3));
+ }});
+
+ it('foldl', function () { with (this) {
+ function sumString (v, i) { return v + i.toString(); }
+ var a = [1, 2, 3];
+ assertEqual("123", yf.foldl(sumString, a));
+ assertEqual("0123", yf.foldl(sumString, a, "0"));
+ }});
+
+ it('foldr', function () { with (this) {
+ function sumString (i, v) { return v + i.toString(); }
+ var a = [1, 2, 3];
+ assertEqual("321", yf.foldr(sumString, a));
+ assertEqual("4321", yf.foldr(sumString, a, "4"));
+ }});
+
+ JS.Test.describe('isFunction', function() { with (this) {
+ it("detects functions", function () { with (this) {
+ assert(yf.isFunction(Object));
+ assert(yf.isFunction(assert));
+ assert(yf.isFunction(yf.isFunction));
+ }});
+ it("detects non-functions", function () { with (this) {
+ assertNot(yf.isFunction(undefined));
+ assertNot(yf.isFunction(null));
+ assertNot(yf.isFunction(23));
+ assertNot(yf.isFunction(yf));
+ }});
+ }});
+
+ JS.Test.describe('isString', function () { with (this) {
+ it("detects strings", function () { with (this) {
+ assert(yf.isString(""));
+ assert(yf.isString("hello world"));
+ }});
+ it("detects Strings", function () { with (this) {
+ assert(yf.isString(new String("")));
+ assert(yf.isString(new String("hello world")));
+ }});
+ it("detects non-strings", function () { with (this) {
+ assertNot(yf.isString(undefined));
+ assertNot(yf.isString(null));
+ assertNot(yf.isString(23));
+ assertNot(yf.isString([1, 2, 3]));
+ }});
+ }});
+
+ JS.Test.describe('insertBefore', function () { with (this) {
+ it("inserts at start", function () { with (this) {
+ var a = [1, 2];
+ yf.insertBefore(a, 0, 1);
+ assertEqual([0, 1, 2], a);
+ }});
+ it("inserts at middle", function () { with (this) {
+ var a = [0, 2];
+ yf.insertBefore(a, 1, 2);
+ assertEqual([0, 1, 2], a);
+ }});
+ it("inserts at end if missing", function () { with (this) {
+ var a = [0, 1];
+ yf.insertBefore(a, 2, 99);
+ assertEqual([0, 1, 2], a);
+ }});
+ }});
+
+ JS.Test.describe('contains', function () { with (this) {
+ var a = [1, 2, 3];
+ it("finds things", function () { with (this) {
+ assert(yf.contains(a, 1));
+ assert(yf.contains(a, 3));
+ }});
+ it("doesn't find things", function () { with (this) {
+ assertNot(yf.contains(a, -1));
+ assertNot(yf.contains(a, 4));
+ }});
+ it("handles the empty sequence", function () { with (this) {
+ assertNot(yf.contains([], 1));
+ assertNot(yf.contains([], undefined));
+ assertNot(yf.contains([], null));
+ }});
+ it("uses identity, not equality", function () { with (this) {
+ assertNot(yf.contains(a, "1"));
+ }});
+ }});
+
+ JS.Test.describe('repeat', function () { with (this) {
+ it("repeats nothing", function () { with (this) {
+ assertEqual([], yf.repeat("hello", 0));
+ assertEqual([], yf.repeat(1, 0));
+ assertEqual([], yf.repeat(null, 0));
+ }});
+ it("repeats something", function () { with (this) {
+ assertEqual([1], yf.repeat(1, 1));
+ assertEqual([1, 1], yf.repeat(1, 2));
+ assertEqual([1, 1, 1, 1], yf.repeat(1, 4));
+ }});
+ }})
+
+ JS.Test.describe('without', function () { with (this) {
+ var a = [1, 2, 3];
+ it("doesn't remove wrongly", function () { with (this) {
+ assertEqual([1, 2, 3], yf.without(a, 4));
+ }});
+ it("removes by ===", function () { with (this) {
+ assertEqual([1, 2, 3], yf.without(a, "1"));
+ }});
+ it("removes one element", function () { with (this) {
+ assertEqual([2, 3], yf.without(a, 1));
+ }});
+ it("removes multiple elements", function () { with (this) {
+ assertEqual([3], yf.without(a, 1, 2));
+ assertEqual([2], yf.without(a, 1, 3));
+ assertEqual([], yf.without(a, 1, 2, 3));
+ }});
+ it("handles the empty sequence", function () { with (this) {
+ assertEqual([], yf.without([]));
+ assertEqual([], yf.without([], 1));
+ assertEqual([], yf.without([], 1, 2));
+ }});
+ }});
+
+ it("some", function () { with (this) {
+ assert(yf.some(null, [1]));
+ assert(yf.some(null, [1, 2]));
+ assert(yf.some(null, [1, 0]));
+
+ assertNot(yf.some(null, []));
+ assertNot(yf.some(null, [0, NaN]));
+ assertNot(yf.some(null, [0]));
+ }});
+
+ it('every', function () { with (this) {
+ assert(yf.every(null, []));
+ assert(yf.every(null, [1]));
+ assert(yf.every(null, [1, 2]));
+
+ assertNot(yf.every(null, [0]));
+ assertNot(yf.every(null, [1, 0]));
+ assertNot(yf.every(null, [1, 2, NaN]));
+ }});
+
+ it('none', function () { with (this) {
+ assert(yf.none(null, []));
+ assert(yf.none(null, [0]));
+ assert(yf.none(null, [0, NaN]));
+
+ assertNot(yf.none(null, [1]));
+ assertNot(yf.none(null, [1, 0]));
+ }});
+
+ it('pluck', function () { with (this) {
+ assertEqual([], yf.pluck("foo", []));
+ assertEqual([1, 2, 3], yf.pluck("length", ["a", "ab", "abc"]));
+ }});
+}});
--- /dev/null
+Copyright (c) 2013 GitHub Inc.
+https://github.com/atom/rcedit/
+
+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.
+
+Also it's mostly using code from Rescle that GitHub has stripped the
+copyright notice from, because who needs to follow licenses when
+you've got piles of VC money and a terrible culture literally woven
+into your furniture?
+
+--
+
+Copyright (c) 2009 Yoshio Okumura
+https://code.google.com/p/rescle/
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+2. 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.
+
+3. Neither the name of the copyright holder nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+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.
--- /dev/null
+"use strict";
+
+function titleCase (text) {
+ return text.replace("_", " ").replace(/\w\S*/g, function (word) {
+ return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase();
+ });
+}
+
+function setOptions (select, dict) {
+ yf.irange.call(select, select.remove, select.options.length - 1, -1, -1);
+ yf.each(function (name) {
+ var opt = document.createElement("option");
+ opt.text = titleCase(name);
+ opt.id = name;
+ select.appendChild(opt);
+ }, Object.keys(dict).sort());
+}
+
+window.addEventListener("load", function () {
+ var playing;
+
+ yuu.init({}).then(function () {
+ setOptions(document.getElementById("instrument"), yuu.Instruments);
+ setOptions(document.getElementById("scale"), yuu.Scales);
+ });
+
+ function play () {
+ if (playing)
+ playing.disconnect();
+ var instruments = document.getElementById("instrument");
+ var scales = document.getElementById("scale");
+ var instrument = yuu.Instruments[
+ instruments.options[instruments.selectedIndex].id];
+ var scale = yuu.Scales[
+ scales.options[scales.selectedIndex].id];
+ var score = yuu.parseScore(
+ document.getElementById("score").value,
+ scale, document.getElementById("tonic").value);
+ var bps = +document.getElementById("tempo").value / 60;
+ playing = yuu.audio.createGain();
+ playing.gain.value = 0.5;
+ var t = yuu.audio.currentTime + 0.25;
+ yf.each(function (note) {
+ instrument.createSound(
+ yuu.audio,
+ t + note.time / bps,
+ note.hz,
+ 1.0,
+ note.duration / bps
+ ).connect(playing);
+ }, score);
+ playing.connect(yuu.audio.destination);
+ }
+
+ function jam (key) {
+ if (key === "`")
+ key = "0";
+ else if (key === "0")
+ key = "10";
+ var instruments = document.getElementById("instrument");
+ var scales = document.getElementById("scale");
+ var tonic = document.getElementById("tonic").value;
+ var instrument = yuu.Instruments[
+ instruments.options[instruments.selectedIndex].id];
+ var scale = yuu.Scales[scales.options[scales.selectedIndex].id];
+ var note = yuu.parseScore(key, scale, tonic)[0];
+ var bps = +document.getElementById("tempo").value / 60;
+ if (note)
+ instrument.createSound(
+ yuu.audio,
+ yuu.audio.currentTime + 0.01,
+ note.hz,
+ 1.0,
+ note.duration / bps
+ ).connect(yuu.audio.destination);
+ }
+
+ document.getElementById("play").addEventListener("click", play);
+
+ document.getElementById("jam")
+ .addEventListener("keydown", function (event) {
+ jam(yuu.keyEventName(event));
+ });
+});
+
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <title>Yuu Composer</title>
+ <meta charset="utf-8" />
+ <script type="text/javascript" src="../../src/yuu/pre.js"></script>
+ <script type="text/javascript" src="../../src/yuu/yf.js"></script>
+ <script type="text/javascript" src="../../src/yuu/yT.js"></script>
+ <script type="text/javascript" src="../../src/yuu/core.js"></script>
+ <script type="text/javascript" src="../../src/yuu/input.js"></script>
+ <script type="text/javascript" src="../../src/yuu/audio.js"></script>
+ <script type="text/javascript" src="composer.js"></script>
+ </head>
+
+ <body style="margin: 5% auto; max-width: 800px">
+ <div id="jam"
+ style="float: right; width: 30%; text-align: center; line-height: 5em"
+ tabindex=1>
+ click and type ` to 0 to jam
+ </div>
+ <div style="width: 65%">
+ <textarea id="score"
+ style="min-height: 4em; width: 100%"
+ >0 1 2 3 4 5 6 7 6 5 4 3 2 1 0</textarea>
+ <br>
+ <button id="play" style="width: 100%">Play</button>
+ </div>
+ <div style="margin-top: 2em">
+ <span style="display: inline-block; width: 24%">Instrument:<br><select id="instrument"></select></span>
+ <span style="display: inline-block; width: 24%">Tonic:<br><input id="tonic" value="C4"></span>
+ <span style="display: inline-block; width: 24%">Scale:<br><select id="scale"></select></span>
+ <span style="display: inline-block; width: 24%">Tempo (BPM):<br><input id="tempo" value="120"></span>
+ </div>
+ </body>
+</html>
--- /dev/null
+#!/usr/bin/env python
+
+# Turn a website into an HTML5 application with an application cache
+# manifest.
+#
+# http://www.whatwg.org/specs/web-apps/current-work/multipage/offline.html
+# https://developer.mozilla.org/en/docs/HTML/Using_the_application_cache
+
+import os
+import re
+import shutil
+import time
+
+def is_html(filename):
+ return filename.lower().endswith(".html")
+
+def main(appdir):
+ if not os.path.isdir(appdir):
+ raise StandardError("input (%r) is not a directory" % appdir)
+ all_files = []
+ for root, dirnames, filenames in os.walk(appdir):
+ root = os.path.relpath(root, appdir)
+ for filename in filenames:
+ all_files.append(os.path.join(root, filename))
+ all_files.sort()
+ appcache = os.path.join(appdir, "manifest.appcache")
+ with open(appcache, "w") as fobj:
+ fobj.write("CACHE MANIFEST\n")
+ fobj.write("# Generated on %s\n" % time.strftime("%Y %T %z"))
+ for filename in all_files:
+ fobj.write(filename + "\n")
+
+ for filename in filter(is_html, all_files):
+ filename = os.path.join(appdir, filename)
+ # This call to relpath is the entire reason this tool is in
+ # Python and not a shell script.
+ relpath = os.path.relpath(appcache, os.path.dirname(filename))
+ html = open(filename).read()
+ html = re.sub("<html([> ])", '<html manifest="%s"\\1' % relpath, html)
+ with open(filename, "w") as fobj:
+ fobj.write(html)
+
+if __name__ == "__main__":
+ import sys
+ try:
+ appdir = sys.argv[1]
+ except IndexError:
+ raise SystemExit("Usage: %s appdir" %(
+ sys.argv[0]))
+ else:
+ main(appdir)
+
--- /dev/null
+#!/usr/bin/env python
+
+# Generate a node-webkit package.json file for a website.
+#
+# https://github.com/rogerwang/node-webkit/wiki/Manifest-format
+
+import os
+import re
+import shutil
+import time
+import json
+
+def is_html(filename):
+ return filename.lower().endswith(".html")
+
+def attr(name):
+ return "data-" + name + """=["']?((?:.(?!["']?\s+(?:\S+)=|[>"']))+.)["']?"""
+
+def main(appdir):
+ if not os.path.isdir(appdir):
+ raise StandardError("input (%r) is not a directory" % appdir)
+ indexes = []
+ icons = []
+ for root, dirnames, filenames in os.walk(appdir):
+ root = os.path.relpath(root, appdir)
+ for filename in filenames:
+ if filename.lower() == "index.html":
+ indexes.append(os.path.join(root, filename))
+ if ("icon" in filename.lower()
+ and filename.lower().endswith((".ico", ".png"))):
+ icons.append(os.path.join(root, filename))
+
+ indexes.sort(key=lambda fn: (fn.count("/"), fn))
+ icons.sort(key=lambda fn: (fn.count("/"), fn))
+ name = os.path.basename(appdir).split(".")[0]
+ package = {
+ "main": indexes[0],
+ "name": name,
+ "version": "0.0.0",
+ "window": {
+ "show": False,
+ "toolbar": False,
+ "title": name,
+ "min_width": 300,
+ "min_height": 200,
+ }
+ }
+
+ if icons:
+ package["window"]["icon"] = icons[0]
+
+ with open(os.path.join(appdir, indexes[0]), "r") as fobj:
+ header = fobj.read(4096)
+ try:
+ title = re.search("<title>([^<]+)<", header).groups()[0]
+ except AttributeError:
+ print >>sys.stderr, "No <title> found in %r." % fobj.name
+ else:
+ package["window"]["title"] = title
+ try:
+ version = re.search(attr("version"), header).groups()[0]
+ except AttributeError:
+ print >>sys.stderr, "No version found in %r." % fobj.name
+ else:
+ package["version"] = version
+ try:
+ appid = re.search(attr("appid"), header).groups()[0]
+ except AttributeError:
+ print >>sys.stderr, "No appid found in %r." % fobj.name
+ else:
+ package["name"] = appid
+
+ if "+" not in package["version"]:
+ package["version"] += "+"
+ package["version"] += time.strftime("%Y%m%d%H%M%S")
+
+ with open(os.path.join(appdir, "package.json"), "w") as fobj:
+ json.dump(package, fobj)
+
+if __name__ == "__main__":
+ import sys
+ try:
+ appdir = sys.argv[1]
+ except IndexError:
+ raise SystemExit("Usage: %s appdir" %(
+ sys.argv[0]))
+ else:
+ main(appdir)
+
--- /dev/null
+#!/usr/bin/env python
+
+import os
+import zipfile
+import re
+import plistlib
+import shutil
+import json
+import re
+
+from os.path import join, basename
+
+def xp_filename(basename):
+ return re.sub('["<>*?|\\\\]', "_",
+ basename.replace("/", "-").replace(":", "."))
+
+def versionify(version):
+ return ".".join(filter(lambda x: x.isdigit(),
+ re.split("[-+.]", version))[:3])
+
+def main(nwdir, nwpackage):
+ if not os.path.isdir(nwdir):
+ raise StandardError("input (%r) is not a directory" % nwdir)
+ nwzip = zipfile.ZipFile(nwpackage)
+ icnss = filter(lambda f: f.lower().endswith(".icns"),
+ nwzip.namelist())
+ package = json.load(nwzip.open("package.json"))
+ app = join(nwdir, "node-webkit.app")
+ title = package["window"]["title"]
+ exe = package["name"].split(".")[-1]
+ plist = dict(
+ CFBundleDisplayName=title,
+ CFBundleExecutable=exe,
+ CFBundleIdentifier=package["name"],
+ CFBundleInfoDictionaryVersion="6.0",
+ CFBundleName=title,
+ CFBundlePackageType="APPL",
+ CFBundleShortVersionString=package["version"],
+ CFBundleIconFile="nw.icns",
+ CFBundleVersion=versionify(package["version"]),
+ LSMinimumSystemVersion="10.6.0",
+ NSPrincipalClass="NSApplication",
+ NSSupportsAutomaticGraphicsSwitching=True,
+ )
+ if icnss:
+ icnss.sort()
+ os.remove(join(app, "Contents", "Resources", "nw.icns"))
+ nwzip.extract(icnss[0], join(app, "Contents", "Resources"))
+ plist["CFBundleIconFile"] = icnss[0]
+ plistlib.writePlist(plist, join(app, "Contents/Info.plist"))
+ exedir = join(app, "Contents", "MacOS")
+ shutil.move(join(exedir, "node-webkit"), join(exedir, exe))
+ shutil.copy(nwpackage, join(app, "Contents", "Resources", "app.nw"))
+ shutil.move(app, join(app, "..", xp_filename(title) + ".app"))
+
+if __name__ == "__main__":
+ import sys
+ main(*sys.argv[1:])
+
--- /dev/null
+#!/bin/sh
+
+# https://github.com/rogerwang/node-webkit/issues/136
+# https://github.com/rogerwang/node-webkit/wiki/The-solution-of-lacking-libudev.so.0
+#
+# Though that page would be more accurately titled "apparently even
+# Intel, Google, and every commercial distribution can't be bothered
+# to maintain trivial compatibility on desktop Linux."
+
+SELF=$(readlink -f "$0")
+NWDIR=$(dirname "$SELF")/nw
+NWBIN=$(command -v "$NWDIR/nw" nw | head -n 1)
+PACKAGE="$NWDIR/package.nw"
+
+if [ ! -x "$NWBIN" ]; then
+ echo "node-webkit (nw) executable could not be found." >&2
+ exit 127
+fi
+
+"$NWBIN" "$@" "$PACKAGE"
+if [ "$?" = "127" -a -w "$NWBIN" ]; then
+ echo "Applying libudev.so.1 hack." >&2
+ sed -i 's/udev\.so\.0/udev.so.1/g' "$NWBIN"
+ "$NWBIN" "$@" "$PACKAGE"
+fi