Created
December 7, 2014 13:37
-
-
Save rezoner/d3c561986ec7e7cc8749 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Playground 1.1 | |
http://canvasquery.org | |
(c) 2012-2014 http://rezoner.net | |
Playground may be freely distributed under the MIT license. | |
*/ | |
function playground(args) { | |
return new Playground(args); | |
}; | |
/* utitlities */ | |
playground.extend = function() { | |
for (var i = 1; i < arguments.length; i++) { | |
for (var j in arguments[i]) { | |
arguments[0][j] = arguments[i][j]; | |
} | |
} | |
return arguments[0]; | |
}; | |
playground.throttle = function(fn, threshold) { | |
threshold || (threshold = 250); | |
var last, | |
deferTimer; | |
return function() { | |
var context = this; | |
var now = +new Date, | |
args = arguments; | |
if (last && now < last + threshold) { | |
// hold on to it | |
clearTimeout(deferTimer); | |
deferTimer = setTimeout(function() { | |
last = now; | |
fn.apply(context, args); | |
}, threshold); | |
} else { | |
last = now; | |
fn.apply(context, args); | |
} | |
}; | |
}; | |
/* constructor */ | |
function Playground(args) { | |
/* defaults */ | |
playground.extend(this, { | |
smoothing: 1, | |
scale: 1, | |
preventKeyboardDefault: true, | |
preventContextMenu: true | |
}, args); | |
if (!this.width || !this.height) this.fitToContainer = true; | |
if (!this.container) this.container = document.body; | |
if (this.container !== document.body) this.customContainer = true; | |
if (typeof this.container === "string") this.container = document.querySelector(this.container); | |
/* state */ | |
this.state = {}; | |
/* layer */ | |
if (!args.layer) { | |
cq.smoothing = this.smoothing; | |
if (window.CocoonJS) { | |
this.layer = cq.cocoon(1, 1); | |
this.layer.appendTo(this.container); | |
this.screen = this.layer; | |
} else { | |
this.layer = cq(1, 1); | |
if (this.scaleToFit) { | |
this.screen = cq(1, 1); | |
this.screen.appendTo(this.container); | |
} else { | |
this.layer.appendTo(this.container); | |
this.screen = this.layer; | |
} | |
} | |
} | |
var canvas = this.screen.canvas; | |
/* events */ | |
this.eventsHandler = this.eventsHandler.bind(this); | |
/* mouse */ | |
this.mouse = new playground.Mouse(canvas); | |
this.mouse.on("event", this.eventsHandler); | |
this.mouse.preventContextMenu = this.preventContextMenu; | |
/* touch */ | |
this.touch = new playground.Touch(canvas); | |
this.touch.on("event", this.eventsHandler); | |
/* keyboard */ | |
this.keyboard = new playground.Keyboard(); | |
this.keyboard.preventDefault = this.preventKeyboardDefault; | |
this.keyboard.on("event", this.eventsHandler); | |
/* gamepads */ | |
this.gamepads = new playground.Gamepads(); | |
this.gamepads.on("event", this.eventsHandler); | |
/* window resize */ | |
window.addEventListener("resize", this.resizeHandler.bind(this)); | |
setTimeout(this.resizeHandler.bind(this), 1); | |
/* video recorder */ | |
this.videoRecorder = new playground.VideoRecorder(this); | |
/* game loop */ | |
var self = this; | |
var lastTick = Date.now(); | |
function step() { | |
requestAnimationFrame(step); | |
var delta = Date.now() - lastTick; | |
lastTick = Date.now(); | |
if (delta > 1000) return; | |
var dt = delta / 1000; | |
if (self.loader.count <= 0) { | |
if (self.step) self.step(dt); | |
if (self.state.step) self.state.step(dt); | |
if (self.render) self.render(dt); | |
if (self.state.render) self.state.render(dt); | |
if (self.postrender) self.postrender(dt); | |
if (self.state.postrender) self.state.postrender(dt); | |
} else { | |
self.renderLoader(dt); | |
} | |
if (self.scaleToFit) { | |
self.screen.save(); | |
self.screen.translate(self.offsetX, self.offsetY); | |
self.screen.scale(self.scale, self.scale); | |
// self.layer.drawImage(self.scanlines.canvas, 0, 0); | |
self.screen.drawImage(self.layer.canvas, 0, 0); | |
self.screen.restore(); | |
} | |
self.gamepads.step(dt); | |
self.videoRecorder.step(dt); | |
}; | |
requestAnimationFrame(step); | |
/* assets */ | |
/* default audio format */ | |
var canPlayMp3 = (new Audio).canPlayType("audio/mp3"); | |
var canPlayOgg = (new Audio).canPlayType('audio/ogg; codecs="vorbis"'); | |
if (canPlayMp3) this.audioFormat = "mp3"; | |
else this.audioFormat = "ogg"; | |
this.loader = new playground.Loader(); | |
this.images = {}; | |
this.sound = new Playground.Sound(this); | |
this.loadFoo(0.5); | |
if (this.create) setTimeout(this.create.bind(this)); | |
this.loader.on("ready", function() { | |
if (self.ready) self.ready(); | |
self.ready = function() {}; | |
}); | |
}; | |
Playground.prototype = { | |
setState: function(state) { | |
state.app = this; | |
if (this.state && this.state.leave) this.state.leave(); | |
this.state = state; | |
if (this.state && this.state.enter) this.state.enter(); | |
}, | |
eventsHandler: function(event, data) { | |
if (this[event]) this[event](data); | |
if (this.state[event]) this.state[event](data); | |
}, | |
resizeHandler: function() { | |
if (this.customContainer) { | |
var containerWidth = this.container.offsetWidth; | |
var containerHeight = this.container.offsetHeight; | |
} else { | |
var containerWidth = window.innerWidth; | |
var containerHeight = window.innerHeight; | |
} | |
if (this.fitToContainer) { | |
this.width = this.containerWidth; | |
this.height = this.containerHeight; | |
} | |
if (!this.scaleToFit) { | |
if (this.fitToContainer) { | |
this.width = containerWidth; | |
this.height = containerHeight; | |
} | |
this.offsetX = 0; | |
this.offsetY = 0; | |
} else { | |
this.screen.width = containerWidth; | |
this.screen.height = containerHeight; | |
this.scale = Math.min(containerWidth / this.width, containerHeight / this.height); | |
if (this.roundScale) this.scale = Math.max(1, Math.floor(this.scale)); | |
this.offsetX = containerWidth / 2 - this.scale * (this.width / 2) | 0; | |
this.offsetY = containerHeight / 2 - this.scale * (this.height / 2) | 0; | |
this.mouse.scale = this.scale; | |
this.mouse.offsetX = this.offsetX; | |
this.mouse.offsetY = this.offsetY; | |
this.touch.scale = this.scale; | |
this.touch.offsetX = this.offsetX; | |
this.touch.offsetY = this.offsetY; | |
} | |
this.layer.width = this.width; | |
this.layer.height = this.height; | |
this.center = { | |
x: this.width / 2 | 0, | |
y: this.height / 2 | 0 | |
}; | |
this.screen.clear("#000"); | |
this.eventsHandler("resize"); | |
this.mouse.update(); | |
this.touch.update(); | |
}, | |
renderLoader: function() { | |
var height = this.height / 10 | 0; | |
var x = 32; | |
var width = this.width - x * 2; | |
var y = this.height / 2 - height / 2 | 0; | |
this.layer.clear("#000"); | |
this.layer.strokeStyle("#fff").lineWidth(2).strokeRect(x, y, width, height); | |
this.layer.fillStyle("#fff").fillRect(x, y, width * this.loader.progress | 0, height); | |
}, | |
record: function(args) { | |
this.videoRecorder.toggle(args); | |
}, | |
/* imaginary timeout to delay loading */ | |
loadFoo: function(timeout) { | |
if (!this.foofooLoader) this.foofooLoader = 0; | |
var loader = this.loader; | |
this.loader.add("foo " + timeout); | |
setTimeout(function() { | |
loader.ready("foo " + timeout); | |
}, (this.foofooLoader += timeout * 1000)); | |
}, | |
/* images */ | |
loadImages: function() { | |
for (var i = 0; i < arguments.length; i++) { | |
var arg = arguments[i]; | |
/* polymorphism at its finest */ | |
if (typeof arg === "object") { | |
for (var key in arg) this.addImages(arg[key]); | |
} else { | |
/* if argument is not an object/array let's try to load it */ | |
var filename = arg; | |
var loader = this.loader; | |
var fileinfo = filename.match(/(.*)\..*/); | |
var key = fileinfo ? fileinfo[1] : filename; | |
/* filename defaults to png */ | |
if (!fileinfo) filename += ".png"; | |
var path = "images/" + filename; | |
this.loader.add(path); | |
var image = this.images[key] = new Image; | |
image.addEventListener("load", function() { | |
loader.ready(path); | |
}); | |
image.addEventListener("error", function() { | |
loader.error(path); | |
}); | |
image.src = path; | |
} | |
} | |
}, | |
/* sounds */ | |
loadSounds: function() { | |
for (var i = 0; i < arguments.length; i++) { | |
var arg = arguments[i]; | |
/* polymorphism at its finest */ | |
if (typeof arg === "object") { | |
for (var key in arg) this.loadSounds(arg[key]); | |
} else { | |
this.sound.load(arg); | |
} | |
} | |
}, | |
loadFont: function(name) { | |
var styleNode = document.createElement("style"); | |
styleNode.type = "text/css"; | |
var formats = { | |
"woff": "woff", | |
"ttf": "truetype" | |
}; | |
var sources = ""; | |
for (var ext in formats) { | |
var type = formats[ext]; | |
sources += " url(\"fonts/" + name + "." + ext + "\") format('" + type + "');" | |
} | |
styleNode.textContent = "@font-face { font-family: '" + name + "'; src: " + sources + " }"; | |
document.head.appendChild(styleNode); | |
var layer = cq(32, 32); | |
layer.font("10px huj"); | |
layer.fillText(16, 16, 16).trim(); | |
var width = layer.width; | |
var height = layer.height; | |
this.loader.add("font " + name); | |
var self = this; | |
function check() { | |
var layer = cq(32, 32); | |
layer.font("10px " + name).fillText(16, 16, 16); | |
layer.trim(); | |
if (layer.width !== width || layer.height !== height) { | |
self.loader.ready("font " + name); | |
} else { | |
setTimeout(check, 250); | |
} | |
}; | |
check(); | |
}, | |
playSound: function(key, loop) { | |
if (!this.audioChannels) { | |
this.audioChannels = []; | |
for (var i = 0; i < 16; i++) this.audioChannels.push(new Audio); | |
this.audioChannelIndex = 0; | |
} | |
return this.sound.play(key, loop); | |
}, | |
stopSound: function(sound) { | |
this.sound.stop(sound); | |
} | |
}; | |
playground.Events = function() { | |
this.listeners = {}; | |
}; | |
playground.Events.prototype = { | |
on: function(event, callback) { | |
if (!this.listeners[event]) this.listeners[event] = []; | |
this.listeners[event].push(callback); | |
return callback; | |
}, | |
once: function(event, callback) { | |
callback.once = true; | |
if (!this.listeners[event]) this.listeners[event] = []; | |
this.listeners[event].push(callback); | |
return callback; | |
}, | |
off: function(event, callback) { | |
for (var i = 0, len = this.listeners[event].length; i < len; i++) { | |
if (this.listeners[event][i]._remove) { | |
this.listeners[event].splice(i--, 1); | |
len--; | |
} | |
} | |
}, | |
trigger: function(event, data) { | |
/* if you prefer events pipe */ | |
if (this.listeners["event"]) { | |
for (var i = 0, len = this.listeners["event"].length; i < len; i++) { | |
this.listeners["event"][i](event, data); | |
} | |
} | |
/* or subscribed to single event */ | |
if (this.listeners[event]) { | |
for (var i = 0, len = this.listeners[event].length; i < len; i++) { | |
var listener = this.listeners[event][i]; | |
listener(data); | |
if (listener.once) { | |
this.listeners[event].splice(i--, 1); | |
} | |
} | |
} | |
} | |
}; | |
/* Mouse */ | |
playground.Mouse = function(element) { | |
var self = this; | |
playground.Events.call(this); | |
this.element = element; | |
this.buttons = {}; | |
this.mousemoveEvent = {}; | |
this.mousedownEvent = {}; | |
this.mouseupEvent = {}; | |
this.mousewheelEvent = {}; | |
this.x = 0; | |
this.y = 0; | |
this.offsetX = 0; | |
this.offsetY = 0; | |
this.scale = 1; | |
element.addEventListener("mousemove", this.mousemove.bind(this)); | |
element.addEventListener("mousedown", this.mousedown.bind(this)); | |
element.addEventListener("mouseup", this.mouseup.bind(this)); | |
this.enableMousewheel(); | |
this.element.addEventListener("contextmenu", function(e) { | |
if (self.preventContextMenu) e.preventDefault(); | |
}); | |
}; | |
playground.Mouse.prototype = { | |
getElementOffset: function(element) { | |
var offsetX = 0; | |
var offsetY = 0; | |
do { | |
offsetX += element.offsetLeft; | |
offsetY += element.offsetTop; | |
} | |
while ((element = element.offsetParent)); | |
return { | |
x: offsetX, | |
y: offsetY | |
}; | |
}, | |
update: function() { | |
this.elementOffset = this.getElementOffset(this.element); | |
}, | |
mousemove: function(e) { | |
this.x = this.mousemoveEvent.x = (e.pageX - this.elementOffset.x - this.offsetX) / this.scale | 0; | |
this.y = this.mousemoveEvent.y = (e.pageY - this.elementOffset.y - this.offsetY) / this.scale | 0; | |
this.mousemoveEvent.original = e; | |
this.trigger("mousemove", this.mousemoveEvent); | |
}, | |
mousedown: function(e) { | |
var buttonName = ["left", "middle", "right"][e.button]; | |
this.mousedownEvent.x = this.mousemoveEvent.x; | |
this.mousedownEvent.y = this.mousemoveEvent.y; | |
this.mousedownEvent.button = buttonName; | |
this.mousedownEvent.original = e; | |
this[buttonName] = true; | |
this.trigger("mousedown", this.mousedownEvent); | |
}, | |
mouseup: function(e) { | |
var buttonName = ["left", "middle", "right"][e.button]; | |
this.mouseupEvent.x = this.mousemoveEvent.x; | |
this.mouseupEvent.y = this.mousemoveEvent.y; | |
this.mouseupEvent.button = buttonName; | |
this.mouseupEvent.original = e; | |
this[buttonName] = false; | |
this.trigger("mouseup", this.mouseupEvent); | |
}, | |
mousewheel: function(e) { | |
this.mousewheelEvent.x = this.mousemoveEvent.x; | |
this.mousewheelEvent.y = this.mousemoveEvent.y; | |
this.mousewheelEvent.button = ["none", "left", "middle", "right"][e.button]; | |
this.mousewheelEvent.original = e; | |
this[e.button] = false; | |
this.trigger("mousewheel", this.mousewheelEvent); | |
}, | |
enableMousewheel: function() { | |
var eventNames = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll']; | |
var callback = this.mousewheel.bind(this); | |
var self = this; | |
for (var i = eventNames.length; i;) { | |
self.element.addEventListener(eventNames[--i], playground.throttle(function(event) { | |
var orgEvent = event || window.event, | |
args = [].slice.call(arguments, 1), | |
delta = 0, | |
deltaX = 0, | |
deltaY = 0, | |
absDelta = 0, | |
absDeltaXY = 0, | |
fn; | |
event.type = "mousewheel"; | |
// Old school scrollwheel delta | |
if (orgEvent.wheelDelta) { | |
delta = orgEvent.wheelDelta; | |
} | |
if (orgEvent.detail) { | |
delta = orgEvent.detail * -1; | |
} | |
// New school wheel delta (wheel event) | |
if (orgEvent.deltaY) { | |
deltaY = orgEvent.deltaY * -1; | |
delta = deltaY; | |
} | |
// Webkit | |
if (orgEvent.wheelDeltaY !== undefined) { | |
deltaY = orgEvent.wheelDeltaY; | |
} | |
var result = delta ? delta : deltaY; | |
self.mousewheelEvent.x = self.mousemoveEvent.x; | |
self.mousewheelEvent.y = self.mousemoveEvent.y; | |
self.mousewheelEvent.delta = result / Math.abs(result); | |
self.mousewheelEvent.original = orgEvent; | |
callback(self.mousewheelEvent); | |
event.preventDefault(); | |
}, 40), false); | |
} | |
} | |
}; | |
playground.extend(playground.Mouse.prototype, playground.Events.prototype); | |
/* Touch */ | |
playground.Touch = function(element) { | |
playground.Events.call(this); | |
this.element = element; | |
this.buttons = {}; | |
this.touchmoveEvent = {}; | |
this.touchstartEvent = {}; | |
this.touchendEvent = {}; | |
this.x = 0; | |
this.y = 0; | |
this.offsetX = 0; | |
this.offsetY = 0; | |
this.scale = 1; | |
element.addEventListener("touchmove", this.touchmove.bind(this)); | |
element.addEventListener("touchstart", this.touchstart.bind(this)); | |
element.addEventListener("touchend", this.touchend.bind(this)); | |
}; | |
playground.Touch.prototype = { | |
getElementOffset: function(element) { | |
var offsetX = 0; | |
var offsetY = 0; | |
do { | |
offsetX += element.offsetLeft; | |
offsetY += element.offsetTop; | |
} | |
while ((element = element.offsetParent)); | |
return { | |
x: offsetX, | |
y: offsetY | |
}; | |
}, | |
update: function() { | |
this.elementOffset = this.getElementOffset(this.element); | |
}, | |
touchmove: function(e) { | |
var touch = e.touches[0] || e.changedTouches[0]; | |
this.x = this.touchmoveEvent.x = (touch.pageX - this.elementOffset.x - this.offsetX) / this.scale | 0; | |
this.y = this.touchmoveEvent.y = (touch.pageY - this.elementOffset.y - this.offsetY) / this.scale | 0; | |
this.touchmoveEvent.original = e; | |
this.touchmoveEvent.identifier = e.identifier; | |
this.trigger("touchmove", this.touchmoveEvent); | |
e.preventDefault(); | |
}, | |
touchstart: function(e) { | |
this.touchstartEvent.x = this.touchmoveEvent.x; | |
this.touchstartEvent.y = this.touchmoveEvent.y; | |
this.touchstartEvent.original = e; | |
this.touchstartEvent.identifier = e.identifier; | |
this.pressed = true; | |
this.trigger("touchstart", this.touchstartEvent); | |
}, | |
touchend: function(e) { | |
this.touchendEvent.x = this.touchmoveEvent.x; | |
this.touchendEvent.y = this.touchmoveEvent.y; | |
this.touchendEvent.original = e; | |
this.touchendEvent.identifier = e.identifier; | |
this.pressed = false; | |
this.trigger("touchend", this.touchendEvent); | |
} | |
}; | |
playground.extend(playground.Touch.prototype, playground.Events.prototype); | |
/* Keyboard */ | |
playground.Keyboard = function() { | |
playground.Events.call(this); | |
this.keys = {}; | |
document.addEventListener("keydown", this.keydown.bind(this)); | |
document.addEventListener("keyup", this.keyup.bind(this)); | |
document.addEventListener("keypress", this.keypress.bind(this)); | |
this.keydownEvent = {}; | |
this.keyupEvent = {}; | |
}; | |
playground.Keyboard.prototype = { | |
keycodes: { | |
37: "left", | |
38: "up", | |
39: "right", | |
40: "down", | |
45: "insert", | |
46: "delete", | |
8: "backspace", | |
9: "tab", | |
13: "enter", | |
16: "shift", | |
17: "ctrl", | |
18: "alt", | |
19: "pause", | |
20: "capslock", | |
27: "escape", | |
32: "space", | |
33: "pageup", | |
34: "pagedown", | |
35: "end", | |
36: "home", | |
112: "f1", | |
113: "f2", | |
114: "f3", | |
115: "f4", | |
116: "f5", | |
117: "f6", | |
118: "f7", | |
119: "f8", | |
120: "f9", | |
121: "f10", | |
122: "f11", | |
123: "f12", | |
144: "numlock", | |
145: "scrolllock", | |
186: "semicolon", | |
187: "equal", | |
188: "comma", | |
189: "dash", | |
190: "period", | |
191: "slash", | |
192: "graveaccent", | |
219: "openbracket", | |
220: "backslash", | |
221: "closebraket", | |
222: "singlequote" | |
}, | |
keypress: function(e) { | |
}, | |
keydown: function(e) { | |
if (e.which >= 48 && e.which <= 90) var keyName = String.fromCharCode(e.which).toLowerCase(); | |
else var keyName = this.keycodes[e.which]; | |
if (this.keys[keyName]) return; | |
this.keydownEvent.key = keyName; | |
this.keydownEvent.original = e; | |
this.keys[keyName] = true; | |
this.trigger("keydown", this.keydownEvent); | |
if (this.preventDefault && document.activeElement === document.body) { | |
e.returnValue = false; | |
e.keyCode = 0; | |
e.preventDefault(); | |
e.stopPropagation(); | |
} | |
}, | |
keyup: function(e) { | |
if (e.which >= 48 && e.which <= 90) var keyName = String.fromCharCode(e.which).toLowerCase(); | |
else var keyName = this.keycodes[e.which]; | |
this.keyupEvent.key = keyName; | |
this.keyupEvent.original = e; | |
this.keys[keyName] = false; | |
this.trigger("keyup", this.keyupEvent); | |
} | |
}; | |
playground.extend(playground.Keyboard.prototype, playground.Events.prototype); | |
/* Gamepad */ | |
playground.Gamepads = function() { | |
playground.Events.call(this); | |
this.getGamepads = navigator.getGamepads || navigator.webkitGetGamepads; | |
this.gamepadmoveEvent = {}; | |
this.gamepaddownEvent = {}; | |
this.gamepadupEvent = {}; | |
this.gamepads = {}; | |
}; | |
playground.Gamepads.prototype = { | |
buttons: { | |
0: "1", | |
1: "2", | |
2: "3", | |
3: "4", | |
4: "l1", | |
5: "r1", | |
6: "l2", | |
7: "r2", | |
8: "select", | |
9: "start", | |
12: "up", | |
13: "down", | |
14: "left", | |
15: "right" | |
}, | |
zeroState: function() { | |
var buttons = []; | |
for (var i = 0; i <= 15; i++) { | |
buttons.push({ | |
pressed: false, | |
value: 0 | |
}); | |
} | |
return { | |
axes: [], | |
buttons: buttons | |
}; | |
}, | |
createGamepad: function() { | |
var result = { | |
buttons: {}, | |
sticks: [{ | |
x: 0, | |
y: 0 | |
}, { | |
x: 0, | |
y: 0 | |
}] | |
}; | |
for (var i = 0; i < 16; i++) { | |
var key = this.buttons[i]; | |
result.buttons[key] = false; | |
} | |
return result; | |
}, | |
step: function() { | |
if (!navigator.getGamepads) return; | |
var gamepads = navigator.getGamepads(); | |
for (var i = 0; i < gamepads.length; i++) { | |
var current = gamepads[i]; | |
if (!current) continue; | |
if (!this[i]) this[i] = this.createGamepad(); | |
/* have to concat the current.buttons because the are read-only */ | |
var buttons = [].concat(current.buttons); | |
/* hack for missing dpads */ | |
for (var h = 12; h <= 15; h++) { | |
if (!buttons[h]) buttons[h] = { | |
pressed: false, | |
value: 0 | |
}; | |
} | |
var previous = this[i]; | |
/* axes (sticks) to buttons */ | |
if (current.axes) { | |
if (current.axes[0] < 0) buttons[14].pressed = true; | |
if (current.axes[0] > 0) buttons[15].pressed = true; | |
if (current.axes[1] < 0) buttons[12].pressed = true; | |
if (current.axes[1] > 0) buttons[13].pressed = true; | |
previous.sticks[0].x = current.axes[0].value; | |
previous.sticks[0].y = current.axes[1].value; | |
previous.sticks[1].x = current.axes[2].value; | |
previous.sticks[1].y = current.axes[3].value; | |
} | |
/* check buttons changes */ | |
for (var j = 0; j < buttons.length; j++) { | |
var key = this.buttons[j]; | |
/* gamepad down */ | |
if (buttons[j].pressed && !previous.buttons[key]) { | |
previous.buttons[key] = true; | |
this.gamepaddownEvent.button = this.buttons[j]; | |
this.gamepaddownEvent.gamepad = i; | |
this.trigger("gamepaddown", this.gamepaddownEvent); | |
} | |
/* gamepad up */ | |
else if (!buttons[j].pressed && previous.buttons[key]) { | |
previous.buttons[key] = false; | |
this.gamepadupEvent.button = this.buttons[j]; | |
this.gamepadupEvent.gamepad = i; | |
this.trigger("gamepadup", this.gamepadupEvent); | |
} | |
} | |
} | |
} | |
}; | |
playground.extend(playground.Gamepads.prototype, playground.Events.prototype); | |
/* Loader */ | |
playground.Loader = function() { | |
playground.Events.call(this); | |
this.reset(); | |
}; | |
playground.Loader.prototype = { | |
/* loader */ | |
add: function(id) { | |
this.queue++; | |
this.count++; | |
this.trigger("add", id); | |
}, | |
error: function(id) { | |
console.log("unable to load " + id); | |
this.trigger("error", id); | |
}, | |
ready: function(id) { | |
this.queue--; | |
this.progress = 1 - this.queue / this.count; | |
this.trigger("load", id); | |
if (this.queue <= 0) { | |
this.trigger("ready"); | |
this.reset(); | |
} | |
}, | |
reset: function() { | |
this.progress = 0; | |
this.queue = 0; | |
this.count = 0; | |
} | |
}; | |
playground.extend(playground.Loader.prototype, playground.Events.prototype); | |
CanvasQuery.Layer.prototype.playground = function(args) { | |
args.layer = this; | |
return playground(args); | |
}; | |
/* Video recorder */ | |
/* whammy - https://github.com/antimatter15/whammy */ | |
window.Whammy = function() { | |
function h(a, b) { | |
for (var c = r(a), c = [{ | |
id: 440786851, | |
data: [{ | |
data: 1, | |
id: 17030 | |
}, { | |
data: 1, | |
id: 17143 | |
}, { | |
data: 4, | |
id: 17138 | |
}, { | |
data: 8, | |
id: 17139 | |
}, { | |
data: "webm", | |
id: 17026 | |
}, { | |
data: 2, | |
id: 17031 | |
}, { | |
data: 2, | |
id: 17029 | |
}] | |
}, { | |
id: 408125543, | |
data: [{ | |
id: 357149030, | |
data: [{ | |
data: 1E6, | |
id: 2807729 | |
}, { | |
data: "whammy", | |
id: 19840 | |
}, { | |
data: "whammy", | |
id: 22337 | |
}, { | |
data: s(c.duration), | |
id: 17545 | |
}] | |
}, { | |
id: 374648427, | |
data: [{ | |
id: 174, | |
data: [{ | |
data: 1, | |
id: 215 | |
}, { | |
data: 1, | |
id: 25541 | |
}, { | |
data: 0, | |
id: 156 | |
}, { | |
data: "und", | |
id: 2274716 | |
}, { | |
data: "V_VP8", | |
id: 134 | |
}, { | |
data: "VP8", | |
id: 2459272 | |
}, { | |
data: 1, | |
id: 131 | |
}, { | |
id: 224, | |
data: [{ | |
data: c.width, | |
id: 176 | |
}, { | |
data: c.height, | |
id: 186 | |
}] | |
}] | |
}] | |
}] | |
}], e = 0, d = 0; e < a.length;) { | |
var g = [], | |
f = 0; | |
do g.push(a[e]), f += a[e].duration, e++; while (e < a.length && 3E4 > f); | |
var h = 0, | |
g = { | |
id: 524531317, | |
data: [{ | |
data: d, | |
id: 231 | |
}].concat(g.map(function(a) { | |
var b = t({ | |
discardable: 0, | |
frame: a.data.slice(4), | |
invisible: 0, | |
keyframe: 1, | |
lacing: 0, | |
trackNum: 1, | |
timecode: Math.round(h) | |
}); | |
h += a.duration; | |
return { | |
data: b, | |
id: 163 | |
} | |
})) | |
}; | |
c[1].data.push(g); | |
d += f | |
} | |
return m(c, b) | |
} | |
function r(a) { | |
for (var b = a[0].width, c = a[0].height, e = a[0].duration, | |
d = 1; d < a.length; d++) { | |
if (a[d].width != b) throw "Frame " + (d + 1) + " has a different width"; | |
if (a[d].height != c) throw "Frame " + (d + 1) + " has a different height"; | |
if (0 > a[d].duration || 32767 < a[d].duration) throw "Frame " + (d + 1) + " has a weird duration (must be between 0 and 32767)"; | |
e += a[d].duration | |
} | |
return { | |
duration: e, | |
width: b, | |
height: c | |
} | |
} | |
function u(a) { | |
for (var b = []; 0 < a;) b.push(a & 255), a >>= 8; | |
return new Uint8Array(b.reverse()) | |
} | |
function n(a) { | |
var b = []; | |
a = (a.length % 8 ? Array(9 - a.length % 8).join("0") : "") + a; | |
for (var c = 0; c < a.length; c += 8) b.push(parseInt(a.substr(c, | |
8), 2)); | |
return new Uint8Array(b) | |
} | |
function m(a, b) { | |
for (var c = [], e = 0; e < a.length; e++) { | |
var d = a[e].data; | |
"object" == typeof d && (d = m(d, b)); | |
"number" == typeof d && (d = n(d.toString(2))); | |
if ("string" == typeof d) { | |
for (var g = new Uint8Array(d.length), f = 0; f < d.length; f++) g[f] = d.charCodeAt(f); | |
d = g | |
} | |
f = d.size || d.byteLength || d.length; | |
g = Math.ceil(Math.ceil(Math.log(f) / Math.log(2)) / 8); | |
f = f.toString(2); | |
f = Array(7 * g + 8 - f.length).join("0") + f; | |
g = Array(g).join("0") + "1" + f; | |
c.push(u(a[e].id)); | |
c.push(n(g)); | |
c.push(d) | |
} | |
return b ? (c = p(c), new Uint8Array(c)) : | |
new Blob(c, { | |
type: "video/webm" | |
}) | |
} | |
function p(a, b) { | |
null == b && (b = []); | |
for (var c = 0; c < a.length; c++) "object" == typeof a[c] ? p(a[c], b) : b.push(a[c]); | |
return b | |
} | |
function t(a) { | |
var b = 0; | |
a.keyframe && (b |= 128); | |
a.invisible && (b |= 8); | |
a.lacing && (b |= a.lacing << 1); | |
a.discardable && (b |= 1); | |
if (127 < a.trackNum) throw "TrackNumber > 127 not supported"; | |
return [a.trackNum | 128, a.timecode >> 8, a.timecode & 255, b].map(function(a) { | |
return String.fromCharCode(a) | |
}).join("") + a.frame | |
} | |
function q(a) { | |
for (var b = a.RIFF[0].WEBP[0], c = b.indexOf("\u009d\u0001*"), | |
e = 0, d = []; 4 > e; e++) d[e] = b.charCodeAt(c + 3 + e); | |
e = d[1] << 8 | d[0]; | |
c = e & 16383; | |
e = d[3] << 8 | d[2]; | |
return { | |
width: c, | |
height: e & 16383, | |
data: b, | |
riff: a | |
} | |
} | |
function k(a) { | |
for (var b = 0, c = {}; b < a.length;) { | |
var e = a.substr(b, 4), | |
d = parseInt(a.substr(b + 4, 4).split("").map(function(a) { | |
a = a.charCodeAt(0).toString(2); | |
return Array(8 - a.length + 1).join("0") + a | |
}).join(""), 2), | |
g = a.substr(b + 4 + 4, d), | |
b = b + (8 + d); | |
c[e] = c[e] || []; | |
"RIFF" == e || "LIST" == e ? c[e].push(k(g)) : c[e].push(g) | |
} | |
return c | |
} | |
function s(a) { | |
return [].slice.call(new Uint8Array((new Float64Array([a])).buffer), | |
0).map(function(a) { | |
return String.fromCharCode(a) | |
}).reverse().join("") | |
} | |
function l(a, b) { | |
this.frames = []; | |
this.duration = 1E3 / a; | |
this.quality = b || .8 | |
} | |
l.prototype.add = function(a, b) { | |
if ("undefined" != typeof b && this.duration) throw "you can't pass a duration if the fps is set"; | |
if ("undefined" == typeof b && !this.duration) throw "if you don't have the fps set, you ned to have durations here."; | |
"canvas" in a && (a = a.canvas); | |
if ("toDataURL" in a) a = a.toDataURL("image/webp", this.quality); | |
else if ("string" != typeof a) throw "frame must be a a HTMLCanvasElement, a CanvasRenderingContext2D or a DataURI formatted string"; | |
if (!/^data:image\/webp;base64,/ig.test(a)) throw "Input must be formatted properly as a base64 encoded DataURI of type image/webp"; | |
this.frames.push({ | |
image: a, | |
duration: b || this.duration | |
}) | |
}; | |
l.prototype.compile = function(a) { | |
return new h(this.frames.map(function(a) { | |
var c = q(k(atob(a.image.slice(23)))); | |
c.duration = a.duration; | |
return c | |
}), a) | |
}; | |
return { | |
Video: l, | |
fromImageArray: function(a, b, c) { | |
return h(a.map(function(a) { | |
a = q(k(atob(a.slice(23)))); | |
a.duration = 1E3 / b; | |
return a | |
}), c) | |
}, | |
toWebM: h | |
} | |
}(); | |
playground.VideoRecorder = function(app, args) { | |
this.app = app; | |
}; | |
playground.VideoRecorder.prototype = { | |
setup: function(args) { | |
this.region = false; | |
playground.extend(this, { | |
followMouse: false, | |
framerate: 20, | |
scale: 1 | |
}, args); | |
if (!this.region) { | |
this.region = [0, 0, this.app.layer.width, this.app.layer.height]; | |
} | |
this.playbackRate = this.framerate / 60; | |
this.layer = cq(this.region[2] * this.scale | 0, this.region[3] * this.scale | 0); | |
}, | |
start: function(args) { | |
this.setup(args); | |
this.encoder = new Whammy.Video(this.framerate); | |
this.captureTimeout = 0; | |
this.recording = true; | |
}, | |
step: function(delta) { | |
if (this.encoder) { | |
this.captureTimeout -= delta * 1000; | |
if (this.captureTimeout <= 0) { | |
this.captureTimeout = 1000 / this.framerate + this.captureTimeout; | |
this.layer.drawImage(this.app.layer.canvas, this.region[0], this.region[1], this.region[2], this.region[3], 0, 0, this.layer.width, this.layer.height); | |
this.encoder.add(this.layer.canvas); | |
} | |
this.app.screen.save().lineWidth(8).strokeStyle("#c00").strokeRect(0, 0, this.app.screen.width, this.app.screen.height).restore(); | |
} | |
}, | |
stop: function() { | |
if (!this.encoder) return; | |
var output = this.encoder.compile(); | |
var url = (window.webkitURL || window.URL).createObjectURL(output); | |
window.open(url); | |
this.recording = false; | |
delete this.encoder; | |
}, | |
toggle: function(args) { | |
if (this.encoder) this.stop(); | |
else this.start(args); | |
} | |
}; | |
Playground.Sound = function(parent) { | |
this.parent = parent; | |
var canPlayMp3 = (new Audio).canPlayType("audio/mp3"); | |
var canPlayOgg = (new Audio).canPlayType('audio/ogg; codecs="vorbis"'); | |
if (canPlayMp3) this.audioFormat = "mp3"; | |
else this.audioFormat = "ogg"; | |
var audioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext; | |
if (audioContext) { | |
this.context = new audioContext; | |
this.compressor = this.context.createDynamicsCompressor(); | |
this.compressor.connect(this.context.destination); | |
this.output = this.compressor; | |
} | |
this.buffers = []; | |
this.pool = []; | |
this.volume = 1.0; | |
}; | |
Playground.Sound.prototype = { | |
load: function(file) { | |
var url = "sounds/" + file + "." + this.audioFormat; | |
var sampler = this; | |
var request = new XMLHttpRequest(); | |
request.open("GET", url, true); | |
request.responseType = "arraybuffer"; | |
var id = this.parent.loader.add(); | |
request.onload = function() { | |
sampler.context.decodeAudioData(this.response, function(decodedBuffer) { | |
sampler.buffers[file] = decodedBuffer; | |
sampler.parent.loader.ready(id); | |
}); | |
} | |
request.send(); | |
}, | |
cleanArray: function(array, property) { | |
for (var i = 0, len = array.length; i < len; i++) { | |
if (array[i] === null || (property && array[i][property])) { | |
array.splice(i--, 1); | |
len--; | |
} | |
} | |
}, | |
getSoundBuffer: function() { | |
if (!this.pool.length) { | |
for (var i = 0; i < 100; i++) { | |
var nodes = [ | |
this.context.createBufferSource(), | |
this.context.createGain() | |
]; | |
this.pool.push(nodes); | |
nodes[0].connect(nodes[1]); | |
nodes[1].connect(this.output); | |
} | |
} | |
return this.pool.pop(); | |
}, | |
play: function(name, loop) { | |
var nodes = this.getSoundBuffer(); | |
bufferSource = nodes[0]; | |
bufferSource.gainNode = nodes[1]; | |
bufferSource.buffer = this.buffers[name]; | |
bufferSource.loop = loop || false; | |
bufferSource.key = name; | |
if (this.loop) { | |
// bufferSource.loopStart = this.loopStart; | |
// bufferSource.loopEnd = this.loopEnd; | |
} | |
bufferSource.gainNode.gain.value = this.volume; | |
bufferSource.start(0); | |
bufferSource.volumeLimit = 1; | |
return bufferSource; | |
}, | |
stop: function(what) { | |
if (!what) return; | |
what.stop(0); | |
}, | |
setPlaybackRate: function(sound, rate) { | |
if (!sound) return; | |
return sound.playbackRate.value = rate; | |
}, | |
setVolume: function(sound, volume) { | |
if (!sound) return; | |
return sound.gainNode.gain.value = Math.max(0, this.volume * volume); | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment