Created
August 11, 2015 05:07
-
-
Save bradley/638583ee76ff9ee7283e to your computer and use it in GitHub Desktop.
This file contains hidden or 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
/* requestAnimationFrame polyfill | |
---------------------------------------------------------------------------------*/ | |
(function() { | |
var lastTime = 0; | |
var vendors = ['ms', 'moz', 'webkit', 'o']; | |
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { | |
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; | |
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] | |
|| window[vendors[x]+'CancelRequestAnimationFrame']; | |
} | |
if (!window.requestAnimationFrame) { | |
window.requestAnimationFrame = function(callback, element) { | |
var currTime = new Date().getTime(); | |
var timeToCall = Math.max(0, 16 - (currTime - lastTime)); | |
var id = window.setTimeout(function() { callback(currTime + timeToCall); }, | |
timeToCall); | |
lastTime = currTime + timeToCall; | |
return id; | |
}; | |
} | |
if (!window.cancelAnimationFrame) { | |
window.cancelAnimationFrame = function(id) { | |
clearTimeout(id); | |
}; | |
} | |
}()); | |
var DEAD = {}; | |
/* POSITION | |
---------------------------------------------------------------------------------*/ | |
DEAD.Position = function () { | |
} | |
DEAD.Position.prototype = { | |
constructor: DEAD.Position, | |
x: 0.0, y: 0.0, z: 0.0, | |
set: function(x, y, z) { | |
this.x = x; | |
this.y = y; | |
this.z = z; | |
}, | |
getVec3: function() { | |
return [this.x, this.y, this.z]; | |
} | |
} | |
/* RENDERER | |
---------------------------------------------------------------------------------*/ | |
DEAD.Renderer = function (canvas, opts) { | |
this.canvas = canvas; | |
this.errors = []; | |
if (!canvas || canvas.nodeName != "CANVAS") { | |
this.errors.push("No valid canvas passed to DEAD.Renderer"); | |
return; | |
} | |
this.gl = this.load(opts); | |
this.clear(); | |
} | |
DEAD.Renderer.prototype = { | |
constructor: DEAD.Renderer, | |
load : function(options) { | |
var gl = null; | |
try { | |
// Try to grab the standard context. If it fails, fallback to experimental. | |
gl = this.canvas.getContext("webgl", options) || cthis.anvas.getContext("experimental-webgl", options); | |
} | |
catch(e) {} | |
// If we don't have a GL context, give up now | |
if (!gl) { | |
this.errors.push("Unable to initialize WebGL. Your browser may not support it."); | |
} | |
return gl; | |
}, | |
clear : function(color) { | |
color = color || [0.0, 0.0, 0.0, 1.0] | |
this.gl.clearColor.apply(this.gl, color); // Set clear color to black, fully opaque | |
this.gl.enable(this.gl.DEPTH_TEST); // Enable depth testing (hide pixels that should be behind others) | |
this.gl.depthFunc(this.gl.LEQUAL); // Near things obscure far things | |
this.gl.clear(this.gl.COLOR_BUFFER_BIT|this.gl.DEPTH_BUFFER_BIT); // Clear the color as well as the depth buffer. | |
}, | |
draw : function(scene, opts) { | |
opts = opts || {}; | |
_.defaults(opts, { | |
clear: true | |
}); | |
if (!(scene instanceof DEAD.Scene)) { | |
return console.error("scene must be of type DEAD.Scene"); | |
} | |
if (opts.clear) { | |
this.clear(); | |
} | |
scene.render(this); | |
} | |
} | |
/* RENDER CONTEXT | |
---------------------------------------------------------------------------------*/ | |
DEAD.RenderContext = function () { | |
} | |
DEAD.RenderContext.prototype = (function() { | |
var _renderer; | |
return { | |
constructor: DEAD.RenderContext, | |
set: function(renderer) { | |
if (!(renderer instanceof DEAD.Renderer)) { | |
return console.error("renderer must be of type DEAD.Renderer"); | |
} | |
_renderer = renderer; | |
}, | |
getGl : function() { | |
return this.getRenderer().gl; | |
}, | |
getRenderer : function() { | |
if (!(_renderer instanceof DEAD.Renderer)) { | |
return console.error("renderer of type DEAD.Renderer has not yet been set for DEAD.RenderContext"); | |
} | |
return _renderer; | |
} | |
} | |
})(); | |
/* SCENE | |
---------------------------------------------------------------------------------*/ | |
DEAD.Scene = function () { | |
this.materials = []; | |
this.camera; | |
} | |
DEAD.Scene.prototype = (function() { | |
function drawChildren(renderer) { | |
var gl = renderer.gl; | |
_.each(this.materials, _.bind(function(material) { | |
material.context.set(renderer); | |
material.draw(this.camera); | |
}, this)); | |
} | |
return { | |
constructor: DEAD.Scene, | |
add : function(addon) { | |
if (addon instanceof DEAD.Material) { | |
this.materials.push(addon); | |
} | |
else if (addon instanceof DEAD.Camera) { | |
this.camera = addon; | |
} | |
else { | |
return console.error("elements added to DEAD.Scene may only be of type DEAD.Material or DEAD.Camera"); | |
} | |
}, | |
remove : function(addon) { | |
if (addon instanceof DEAD.Material) { | |
var index = this.materials.indexOf(material); | |
if (index > -1) { | |
this.materials.splice(index, 1); | |
} | |
} | |
else if (addon instanceof DEAD.Camera) { | |
this.camera = null; | |
} | |
}, | |
render : function(renderer) { | |
if (!(renderer instanceof DEAD.Renderer)) { | |
return console.error("scene cannot render without a valid DEAD.Renderer"); | |
} | |
drawChildren.apply(this, [renderer]); | |
} | |
} | |
})(); | |
/* CAMERA | |
---------------------------------------------------------------------------------*/ | |
DEAD.Camera = function(fov, aspectRatio, near, far) { | |
this.position = new DEAD.Position(); | |
this.position.set(0, 0, 6); | |
this.focus = new DEAD.Position(); | |
this.viewMatrix = mat4.create(); | |
this.projectionMatrix = this.setProjectionMatrix(fov, aspectRatio, near, far); | |
this.setProjectionMatrix(); | |
this.update(); | |
} | |
DEAD.Camera.prototype = (function() { | |
return { | |
constructor: DEAD.Camera, | |
setProjectionMatrix: function(fov, aspectRatio, near, far) { | |
var matrix = mat4.create(); | |
mat4.perspective(matrix, fov, aspectRatio, near, far); | |
return matrix; | |
}, | |
update: function() { | |
var position = this.position.getVec3(), | |
focus = this.focus.getVec3(), | |
up = vec3.fromValues(0, 1, 0); | |
// Update View Matrix (camera translation). | |
mat4.lookAt(this.viewMatrix, position, focus, up); | |
} | |
} | |
})(); | |
/* PROGRAM | |
---------------------------------------------------------------------------------*/ | |
DEAD.Program = function (vertexSrc, fragmentSrc, opts) { | |
opts = opts || {} | |
_.defaults(opts, { | |
defaultUniforms : { | |
time: true | |
} | |
}); | |
this.vertexSrc = vertexSrc; | |
this.fragmentSrc = fragmentSrc; | |
this.opts = opts; | |
this.glslProgram; | |
this.programAttributes; | |
this.programUniforms; | |
this.context = new DEAD.RenderContext(); | |
this.buffersToBind = []; | |
this.startTime = null; | |
} | |
DEAD.Program.prototype = (function() { | |
function compileShader(gl, type, shaderSrc) { | |
var shader = gl.createShader(type); | |
gl.shaderSource(shader, shaderSrc); | |
gl.compileShader(shader); | |
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { | |
throw new Error(gl.getShaderInfoLog(shader)); | |
} | |
return shader; | |
} | |
function buildProgram(gl, vertexSrc, fragmentSrc) { | |
var vertexShader = compileShader(gl, gl.VERTEX_SHADER, vertexSrc), | |
fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc), | |
program = gl.createProgram(); | |
gl.attachShader(program, vertexShader); | |
gl.attachShader(program, fragmentShader); | |
gl.linkProgram(program); | |
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { | |
throw new Error(gl.getProgramInfoLog(program)); | |
} | |
return program; | |
} | |
function buildAttributesObject(gl, program) { | |
var attributesObject = {}, | |
numAttributes; | |
numAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); | |
for (var i = 0; i < numAttributes; i++) { | |
var name = gl.getActiveAttrib(program, i).name; | |
attributesObject[name] = gl.getAttribLocation(program, name); | |
} | |
return attributesObject; | |
} | |
function buildUniformsObject(gl, program) { | |
var uniformsObject = {}, | |
numUniforms; | |
numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); | |
for (var i = 0; i < numUniforms; i++) { | |
var name = gl.getActiveUniform(program, i).name; | |
uniformsObject[name] = gl.getUniformLocation(program, name); | |
} | |
return uniformsObject; | |
} | |
function bindBuffers(gl, buffersToBind, attributes) { | |
_.each(buffersToBind, function(bufferData) { | |
var buffer = gl.createBuffer(); | |
if (!buffer) { | |
return console.error("unable to create vertex buffer"); | |
} | |
if (!attributes[bufferData.attribute]) { | |
return console.error("attribute " + bufferData.attribute + " not found in program"); | |
} | |
gl.bindBuffer(gl.ARRAY_BUFFER, buffer); | |
gl.bufferData(gl.ARRAY_BUFFER, bufferData.data, gl.STATIC_DRAW); | |
gl.vertexAttribPointer(attributes[bufferData.attribute], bufferData.elemPerVertex, gl.FLOAT, false, 0, 0); | |
gl.enableVertexAttribArray(attributes[bufferData.attribute]); | |
}); | |
} | |
return { | |
constructor: DEAD.Program, | |
build : function() { | |
var gl = this.context.getGl(); | |
this.glslProgram = buildProgram(gl, this.vertexSrc, this.fragmentSrc); | |
this.programAttributes = buildAttributesObject(gl, this.glslProgram); | |
this.programUniforms = buildUniformsObject(gl, this.glslProgram); | |
}, | |
bindBuffer : function(data, elemPerVertex, attribute) { | |
// Ex: | |
// program.bindBuffer(new Float32Array([ | |
// 1.0, 0.0, 0.0, 0.0, | |
// 0.0, 1.0, 0.0, 0.0, | |
// 0.1, 0.4, 0.6, 0.0, | |
// 0.0, 0.0, 1.0, 0.0]), 4, program.attributes.aVertColor); | |
if (!(data instanceof Float32Array)) { | |
return console.error("data must be of type Float32Array"); | |
} | |
this.buffersToBind.push({ | |
data: data, | |
elemPerVertex: elemPerVertex, | |
attribute: attribute | |
}); | |
}, | |
update : function() { | |
if (!this.startTime) { | |
this.startTime = new Date().getTime() | |
} | |
bindBuffers(this.context.getGl(), this.buffersToBind, this.programAttributes); | |
if (this.opts.defaultUniforms.time && this.programUniforms.uTime) { | |
var time = ((new Date()).getTime() - this.startTime) / 1000; | |
this.context.getGl().uniform1f(this.programUniforms.uTime, time); | |
} | |
} | |
} | |
})(); | |
/* MATERIAL | |
---------------------------------------------------------------------------------*/ | |
DEAD.Material = function (program, geometry) { | |
this.program = program; | |
this.geometry = geometry; | |
this.context = new DEAD.RenderContext(); | |
if (!this.program || !(this.program instanceof DEAD.Program)) { | |
return console.error("program must be of type DEAD.Program"); | |
} | |
if (!this.geometry || !(this.geometry instanceof DEAD.Geometry)) { | |
return console.error("geometry must be of type DEAD.Geometry"); | |
} | |
} | |
DEAD.Material.prototype = (function() { | |
function bindBuffers() { | |
var gl = this.context.getGl(), | |
bytes = this.geometry.vertices.BYTES_PER_ELEMENT, | |
stride = 10 * bytes, | |
vertexBuffer = gl.createBuffer(), | |
indexBuffer = gl.createBuffer(); | |
if (!vertexBuffer) { | |
return console.error("unable to create vertex buffer"); | |
} | |
if (!vertexBuffer) { | |
return console.error("unable to create index buffer"); | |
} | |
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, this.geometry.vertices, gl.STATIC_DRAW); | |
// TODO: If we add a 'prefix' attribute to DEAD.Material, we could mitigate | |
// the risk that these attributes may be overwritten by other materials working | |
// within the same program. | |
setVertexAttribute(gl, this.program.programAttributes.aPosition, 4, stride, 0); | |
setVertexAttribute(gl, this.program.programAttributes.aColor, 4, stride, 4 * bytes); | |
setVertexAttribute(gl, this.program.programAttributes.aTexCoord, 2, stride, 8 * bytes); | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); | |
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.geometry.indices, gl.STATIC_DRAW); | |
} | |
function setVertexAttribute(gl, attributeIndex, size, stride, offset) { | |
gl.vertexAttribPointer(attributeIndex, size, gl.FLOAT, false, stride, offset); | |
gl.enableVertexAttribArray(attributeIndex); | |
} | |
function updateModelViewProjectionMatrixForCamera(camera) { | |
if (camera instanceof DEAD.Camera && this.program.programUniforms.uMVPMatrix) { | |
var matrix = this.geometry.modelViewProjectionMatrix( | |
camera.viewMatrix, | |
camera.projectionMatrix, | |
this.context.getRenderer().delta | |
); | |
this.context.getGl().uniformMatrix4fv(this.program.programUniforms.uMVPMatrix, false, matrix) | |
} | |
} | |
return { | |
constructor: DEAD.Material, | |
draw: function(camera) { | |
var gl = this.context.getGl(); | |
this.program.context = this.context; | |
this.program.build(); | |
gl.useProgram(this.program.glslProgram); | |
this.program.update(); | |
bindBuffers.apply(this); | |
updateModelViewProjectionMatrixForCamera.apply(this, [camera]); | |
gl.drawElements(gl.TRIANGLES, this.geometry.indices.length, gl.UNSIGNED_SHORT, 0); | |
} | |
} | |
})(); | |
/* GEOMETRIES | |
---------------------------------------------------------------------------------*/ | |
DEAD.Geometry = function () { | |
this.type = "Geometry" | |
this.vertices = new Float32Array([]); | |
this.indices = new Int16Array([]); | |
this.position = new DEAD.Position(); | |
this.rotation = new DEAD.Position(); | |
this.scale = new DEAD.Position(); | |
this.scale.set(1, 1, 1); | |
this.modelMatrix = this.currentModelMatrix(); | |
} | |
DEAD.Geometry.prototype = (function() { | |
function degreesToRadians(d) { | |
return d * Math.PI / 180; | |
} | |
return { | |
constructor: DEAD.Geometry, | |
currentModelMatrix: function(delta) { | |
var m = mat4.create(), | |
rotation = this.rotation.getVec3(); | |
position = this.position.getVec3(); | |
scale = this.scale.getVec3(); | |
mat4.rotateX(m, m, degreesToRadians(rotation[0])); | |
mat4.rotateY(m, m, degreesToRadians(rotation[1])); | |
mat4.rotateZ(m, m, degreesToRadians(rotation[2])); | |
mat4.translate(m, m, position); | |
mat4.scale(m, m, scale); | |
return m; | |
}, | |
modelViewProjectionMatrix: function(viewMatrix, projectionMatrix, delta) { | |
var m = mat4.create(); | |
mat4.multiply(m, projectionMatrix, viewMatrix); | |
mat4.multiply(m, m, this.currentModelMatrix(delta)); | |
return m; | |
} | |
} | |
})(); | |
DEAD.PlaneGeometry = function () { | |
DEAD.Geometry.call(this); | |
this.type = 'PlaneGeometry'; | |
this.vertices = new Float32Array([ | |
// x y z r g b a uvX uvY | |
-0.5, -0.5, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, | |
0.5, -0.5, 0.0, 1.0, 0.1, 0.4, 0.6, 1.0, 1.0, 1.0, | |
0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, | |
-0.5, 0.5, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0 | |
]); | |
/* | |
3.......2 | |
. .. | |
. . . | |
.. . | |
0.......1 | |
*/ | |
this.indices = new Int16Array([ | |
0, 1, 2, 2, 3, 0 | |
]); | |
}; | |
DEAD.PlaneGeometry.prototype = Object.create( DEAD.Geometry.prototype ); | |
DEAD.PlaneGeometry.prototype.constructor = DEAD.PlaneGeometry; | |
DEAD.TriangleGeometry = function () { | |
DEAD.Geometry.call(this); | |
this.type = 'TriangleGeometry'; | |
this.vertices = new Float32Array([ | |
// x y z r g b a uvX uvY | |
-0.5, -0.5, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, | |
0.5, -0.5, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, | |
0.0, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0 | |
]); | |
/* | |
2 | |
.. | |
. . | |
. . | |
0......1 | |
*/ | |
this.indices = new Int16Array([ | |
0, 1, 2 | |
]); | |
} | |
DEAD.TriangleGeometry.prototype = Object.create( DEAD.Geometry.prototype ); | |
DEAD.TriangleGeometry.prototype.constructor = DEAD.TriangleGeometry; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment