webgl-3d-animation
Version:
Interactive 3D animation using WebGL showing a 2D predator prey ecology on a grid which is real-time mapped onto the surface of a 3D torus. node.js server side gives access to WAV format files which are rendered using Web Audio API
1,453 lines (1,365 loc) • 67.5 kB
JavaScript
/*
* lightgl.js
* http://github.com/evanw/lightgl.js/
*
* Copyright 2011 Evan Wallace
* Released under the MIT license
*/
var GL = (function() {
// src/raytracer.js
// Provides a convenient raytracing interface.
// ### new GL.HitTest([t, hit, normal])
//
// This is the object used to return hit test results. If there are no
// arguments, the constructed argument represents a hit infinitely far
// away.
function HitTest(t, hit, normal) {
this.t = arguments.length ? t : Number.MAX_VALUE;
this.hit = hit;
this.normal = normal;
}
// ### .mergeWith(other)
//
// Changes this object to be the closer of the two hit test results.
HitTest.prototype = {
mergeWith: function(other) {
if (other.t > 0 && other.t < this.t) {
this.t = other.t;
this.hit = other.hit;
this.normal = other.normal;
}
}
};
// ### new GL.Raytracer()
//
// This will read the current modelview matrix, projection matrix, and viewport,
// reconstruct the eye position, and store enough information to later generate
// per-pixel rays using `getRayForPixel()`.
//
// Example usage:
//
// var tracer = new GL.Raytracer();
// var ray = tracer.getRayForPixel(
// gl.canvas.width / 2,
// gl.canvas.height / 2);
// var result = GL.Raytracer.hitTestSphere(
// tracer.eye, ray, new GL.Vector(0, 0, 0), 1);
function Raytracer() {
var v = gl.getParameter(gl.VIEWPORT);
var m = gl.modelviewMatrix.m;
var axisX = new Vector(m[0], m[4], m[8]);
var axisY = new Vector(m[1], m[5], m[9]);
var axisZ = new Vector(m[2], m[6], m[10]);
var offset = new Vector(m[3], m[7], m[11]);
this.eye = new Vector(-offset.dot(axisX), -offset.dot(axisY), -offset.dot(axisZ));
var minX = v[0], maxX = minX + v[2];
var minY = v[1], maxY = minY + v[3];
this.ray00 = gl.unProject(minX, minY, 1).subtract(this.eye);
this.ray10 = gl.unProject(maxX, minY, 1).subtract(this.eye);
this.ray01 = gl.unProject(minX, maxY, 1).subtract(this.eye);
this.ray11 = gl.unProject(maxX, maxY, 1).subtract(this.eye);
this.viewport = v;
}
Raytracer.prototype = {
// ### .getRayForPixel(x, y)
//
// Returns the ray originating from the camera and traveling through the pixel `x, y`.
getRayForPixel: function(x, y) {
x = (x - this.viewport[0]) / this.viewport[2];
y = 1 - (y - this.viewport[1]) / this.viewport[3];
var ray0 = Vector.lerp(this.ray00, this.ray10, x);
var ray1 = Vector.lerp(this.ray01, this.ray11, x);
return Vector.lerp(ray0, ray1, y).unit();
}
};
// ### GL.Raytracer.hitTestBox(origin, ray, min, max)
//
// Traces the ray starting from `origin` along `ray` against the axis-aligned box
// whose coordinates extend from `min` to `max`. Returns a `HitTest` with the
// information or `null` for no intersection.
//
// This implementation uses the [slab intersection method](http://www.siggraph.org/education/materials/HyperGraph/raytrace/rtinter3.htm).
Raytracer.hitTestBox = function(origin, ray, min, max) {
var tMin = min.subtract(origin).divide(ray);
var tMax = max.subtract(origin).divide(ray);
var t1 = Vector.min(tMin, tMax);
var t2 = Vector.max(tMin, tMax);
var tNear = t1.max();
var tFar = t2.min();
if (tNear > 0 && tNear < tFar) {
var epsilon = 1.0e-6, hit = origin.add(ray.multiply(tNear));
min = min.add(epsilon);
max = max.subtract(epsilon);
return new HitTest(tNear, hit, new Vector(
(hit.x > max.x) - (hit.x < min.x),
(hit.y > max.y) - (hit.y < min.y),
(hit.z > max.z) - (hit.z < min.z)
));
}
return null;
};
// ### GL.Raytracer.hitTestSphere(origin, ray, center, radius)
//
// Traces the ray starting from `origin` along `ray` against the sphere defined
// by `center` and `radius`. Returns a `HitTest` with the information or `null`
// for no intersection.
Raytracer.hitTestSphere = function(origin, ray, center, radius) {
var offset = origin.subtract(center);
var a = ray.dot(ray);
var b = 2 * ray.dot(offset);
var c = offset.dot(offset) - radius * radius;
var discriminant = b * b - 4 * a * c;
if (discriminant > 0) {
var t = (-b - Math.sqrt(discriminant)) / (2 * a), hit = origin.add(ray.multiply(t));
return new HitTest(t, hit, hit.subtract(center).divide(radius));
}
return null;
};
// ### GL.Raytracer.hitTestTriangle(origin, ray, a, b, c)
//
// Traces the ray starting from `origin` along `ray` against the triangle defined
// by the points `a`, `b`, and `c`. Returns a `HitTest` with the information or
// `null` for no intersection.
Raytracer.hitTestTriangle = function(origin, ray, a, b, c) {
var ab = b.subtract(a);
var ac = c.subtract(a);
var normal = ab.cross(ac).unit();
var t = normal.dot(a.subtract(origin)).divide(normal.dot(ray));
if (t > 0) {
var hit = origin.add(ray.multiply(t));
var toHit = hit.subtract(a);
var dot00 = ac.dot(ac);
var dot01 = ac.dot(ab);
var dot02 = ac.dot(toHit);
var dot11 = ab.dot(ab);
var dot12 = ab.dot(toHit);
var divide = dot00 * dot11 - dot01 * dot01;
var u = (dot11 * dot02 - dot01 * dot12) / divide;
var v = (dot00 * dot12 - dot01 * dot02) / divide;
if (u >= 0 && v >= 0 && u + v <= 1) return new HitTest(t, hit, normal);
}
return null;
};
// src/shader.js
// Provides a convenient wrapper for WebGL shaders. A few uniforms and attributes,
// prefixed with `gl_`, are automatically added to all shader sources to make
// simple shaders easier to write.
//
// Example usage:
//
// var shader = new GL.Shader('\
// void main() {\
// gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\
// }\
// ', '\
// uniform vec4 color;\
// void main() {\
// gl_FragColor = color;\
// }\
// ');
//
// shader.uniforms({
// color: [1, 0, 0, 1]
// }).draw(mesh);
function regexMap(regex, text, callback) {
while ((result = regex.exec(text)) != null) {
callback(result);
}
}
// ### new GL.Shader(vertexSource, fragmentSource)
//
// Compiles a shader program using the provided vertex and fragment shaders.
function Shader(vertexSource, fragmentSource) {
// Allow passing in the id of an HTML script tag with the source
function followScriptTagById(id) {
var element = document.getElementById(id);
return element ? element.text : id;
}
vertexSource = followScriptTagById(vertexSource);
fragmentSource = followScriptTagById(fragmentSource);
// Headers are prepended to the sources to provide some automatic functionality.
var header = '\
uniform mat3 gl_NormalMatrix;\
uniform mat4 gl_ModelViewMatrix;\
uniform mat4 gl_ProjectionMatrix;\
uniform mat4 gl_ModelViewProjectionMatrix;\
uniform mat4 gl_ModelViewMatrixInverse;\
uniform mat4 gl_ProjectionMatrixInverse;\
uniform mat4 gl_ModelViewProjectionMatrixInverse;\
';
var vertexHeader = header + '\
attribute vec4 gl_Vertex;\
attribute vec4 gl_TexCoord;\
attribute vec3 gl_Normal;\
attribute vec4 gl_Color;\
vec4 ftransform() {\
return gl_ModelViewProjectionMatrix * gl_Vertex;\
}\
';
var fragmentHeader = '\
precision highp float;\
' + header;
// Check for the use of built-in matrices that require expensive matrix
// multiplications to compute, and record these in `usedMatrices`.
var source = vertexSource + fragmentSource;
var usedMatrices = {};
regexMap(/\b(gl_[^;]*)\b;/g, header, function(groups) {
var name = groups[1];
if (source.indexOf(name) != -1) {
var capitalLetters = name.replace(/[a-z_]/g, '');
usedMatrices[capitalLetters] = '_' + name;
}
});
if (source.indexOf('ftransform') != -1) usedMatrices.MVPM = '_gl_ModelViewProjectionMatrix';
this.usedMatrices = usedMatrices;
// The `gl_` prefix must be substituted for something else to avoid compile
// errors, since it's a reserved prefix. This prefixes all reserved names with
// `_`. The header is inserted after any extensions, since those must come
// first.
function fix(header, source) {
var replaced = {};
var match = /^((\s*\/\/.*\n|\s*#extension.*\n)+)[^]*$/.exec(source);
source = match ? match[1] + header + source.substr(match[1].length) : header + source;
regexMap(/\bgl_\w+\b/g, header, function(result) {
if (!(result in replaced)) {
source = source.replace(new RegExp('\\b' + result + '\\b', 'g'), '_' + result);
replaced[result] = true;
}
});
return source;
}
vertexSource = fix(vertexHeader, vertexSource);
fragmentSource = fix(fragmentHeader, fragmentSource);
// Compile and link errors are thrown as strings.
function compileSource(type, source) {
var shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw 'compile error: ' + gl.getShaderInfoLog(shader);
}
return shader;
}
this.program = gl.createProgram();
gl.attachShader(this.program, compileSource(gl.VERTEX_SHADER, vertexSource));
gl.attachShader(this.program, compileSource(gl.FRAGMENT_SHADER, fragmentSource));
gl.linkProgram(this.program);
if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
throw 'link error: ' + gl.getProgramInfoLog(this.program);
}
this.attributes = {};
this.uniformLocations = {};
// Sampler uniforms need to be uploaded using `gl.uniform1i()` instead of `gl.uniform1f()`.
// To do this automatically, we detect and remember all uniform samplers in the source code.
var isSampler = {};
regexMap(/uniform\s+sampler(1D|2D|3D|Cube)\s+(\w+)\s*;/g, vertexSource + fragmentSource, function(groups) {
isSampler[groups[2]] = 1;
});
this.isSampler = isSampler;
}
function isArray(obj) {
var str = Object.prototype.toString.call(obj);
return str == '[object Array]' || str == '[object Float32Array]';
}
function isNumber(obj) {
var str = Object.prototype.toString.call(obj);
return str == '[object Number]' || str == '[object Boolean]';
}
var tempMatrix = new Matrix();
var resultMatrix = new Matrix();
Shader.prototype = {
// ### .uniforms(uniforms)
//
// Set a uniform for each property of `uniforms`. The correct `gl.uniform*()` method is
// inferred from the value types and from the stored uniform sampler flags.
uniforms: function(uniforms) {
gl.useProgram(this.program);
for (var name in uniforms) {
var location = this.uniformLocations[name] || gl.getUniformLocation(this.program, name);
if (!location) continue;
this.uniformLocations[name] = location;
var value = uniforms[name];
if (value instanceof Vector) {
value = [value.x, value.y, value.z];
} else if (value instanceof Matrix) {
value = value.m;
}
if (isArray(value)) {
switch (value.length) {
case 1: gl.uniform1fv(location, new Float32Array(value)); break;
case 2: gl.uniform2fv(location, new Float32Array(value)); break;
case 3: gl.uniform3fv(location, new Float32Array(value)); break;
case 4: gl.uniform4fv(location, new Float32Array(value)); break;
// Matrices are automatically transposed, since WebGL uses column-major
// indices instead of row-major indices.
case 9: gl.uniformMatrix3fv(location, false, new Float32Array([
value[0], value[3], value[6],
value[1], value[4], value[7],
value[2], value[5], value[8]
])); break;
case 16: gl.uniformMatrix4fv(location, false, new Float32Array([
value[0], value[4], value[8], value[12],
value[1], value[5], value[9], value[13],
value[2], value[6], value[10], value[14],
value[3], value[7], value[11], value[15]
])); break;
default: throw 'don\'t know how to load uniform "' + name + '" of length ' + value.length;
}
} else if (isNumber(value)) {
(this.isSampler[name] ? gl.uniform1i : gl.uniform1f).call(gl, location, value);
} else {
throw 'attempted to set uniform "' + name + '" to invalid value ' + value;
}
}
return this;
},
// ### .draw(mesh[, mode])
//
// Sets all uniform matrix attributes, binds all relevant buffers, and draws the
// mesh geometry as indexed triangles or indexed lines. Set `mode` to `gl.LINES`
// (and either add indices to `lines` or call `computeWireframe()`) to draw the
// mesh in wireframe.
draw: function(mesh, mode) {
this.drawBuffers(mesh.vertexBuffers,
mesh.indexBuffers[mode == gl.LINES ? 'lines' : 'triangles'],
arguments.length < 2 ? gl.TRIANGLES : mode);
},
// ### .drawBuffers(vertexBuffers, indexBuffer, mode)
//
// Sets all uniform matrix attributes, binds all relevant buffers, and draws the
// indexed mesh geometry. The `vertexBuffers` argument is a map from attribute
// names to `Buffer` objects of type `gl.ARRAY_BUFFER`, `indexBuffer` is a `Buffer`
// object of type `gl.ELEMENT_ARRAY_BUFFER`, and `mode` is a WebGL primitive mode
// like `gl.TRIANGLES` or `gl.LINES`. This method automatically creates and caches
// vertex attribute pointers for attributes as needed.
drawBuffers: function(vertexBuffers, indexBuffer, mode) {
// Only construct up the built-in matrices we need for this shader.
var used = this.usedMatrices;
var MVM = gl.modelviewMatrix;
var PM = gl.projectionMatrix;
var MVMI = (used.MVMI || used.NM) ? MVM.inverse() : null;
var PMI = (used.PMI) ? PM.inverse() : null;
var MVPM = (used.MVPM || used.MVPMI) ? PM.multiply(MVM) : null;
var matrices = {};
if (used.MVM) matrices[used.MVM] = MVM;
if (used.MVMI) matrices[used.MVMI] = MVMI;
if (used.PM) matrices[used.PM] = PM;
if (used.PMI) matrices[used.PMI] = PMI;
if (used.MVPM) matrices[used.MVPM] = MVPM;
if (used.MVPMI) matrices[used.MVPMI] = MVPM.inverse();
if (used.NM) {
var m = MVMI.m;
matrices[used.NM] = [m[0], m[4], m[8], m[1], m[5], m[9], m[2], m[6], m[10]];
}
this.uniforms(matrices);
// Create and enable attribute pointers as necessary.
var length = 0;
for (var attribute in vertexBuffers) {
var buffer = vertexBuffers[attribute];
var location = this.attributes[attribute] ||
gl.getAttribLocation(this.program, attribute.replace(/^gl_/, '_gl_'));
if (location == -1 || !buffer.buffer) continue;
this.attributes[attribute] = location;
gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffer);
gl.enableVertexAttribArray(location);
gl.vertexAttribPointer(location, buffer.buffer.spacing, gl.FLOAT, false, 0, 0);
length = buffer.buffer.length / buffer.buffer.spacing;
}
// Disable unused attribute pointers.
for (var attribute in this.attributes) {
if (!(attribute in vertexBuffers)) {
gl.disableVertexAttribArray(this.attributes[attribute]);
}
}
// Draw the geometry.
if (length && (!indexBuffer || indexBuffer.buffer)) {
if (indexBuffer) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer.buffer);
gl.drawElements(mode, indexBuffer.buffer.length, gl.UNSIGNED_SHORT, 0);
} else {
gl.drawArrays(mode, 0, length);
}
}
return this;
}
};
// src/main.js
// The internal `gl` variable holds the current WebGL context.
var gl;
var GL = {
// ### Initialization
//
// `GL.create()` creates a new WebGL context and augments it with more
// methods. The alpha channel is disabled by default because it usually causes
// unintended transparencies in the canvas.
create: function(options) {
options = options || {};
var canvas = document.createElement('canvas');
canvas.width = 800;
canvas.height = 600;
if (!('alpha' in options)) options.alpha = false;
try { gl = canvas.getContext('webgl', options); } catch (e) {}
try { gl = gl || canvas.getContext('experimental-webgl', options); } catch (e) {}
if (!gl) throw 'WebGL not supported';
addMatrixStack();
addImmediateMode();
addEventListeners();
addOtherMethods();
return gl;
},
// `GL.keys` contains a mapping of key codes to booleans indicating whether
// that key is currently pressed.
keys: {},
// Export all external classes.
Matrix: Matrix,
Indexer: Indexer,
Buffer: Buffer,
Mesh: Mesh,
HitTest: HitTest,
Raytracer: Raytracer,
Shader: Shader,
Texture: Texture,
Vector: Vector
};
// ### Matrix stack
//
// Implement the OpenGL modelview and projection matrix stacks, along with some
// other useful GLU matrix functions.
function addMatrixStack() {
gl.MODELVIEW = ENUM | 1;
gl.PROJECTION = ENUM | 2;
var tempMatrix = new Matrix();
var resultMatrix = new Matrix();
gl.modelviewMatrix = new Matrix();
gl.projectionMatrix = new Matrix();
var modelviewStack = [];
var projectionStack = [];
var matrix, stack;
gl.matrixMode = function(mode) {
switch (mode) {
case gl.MODELVIEW:
matrix = 'modelviewMatrix';
stack = modelviewStack;
break;
case gl.PROJECTION:
matrix = 'projectionMatrix';
stack = projectionStack;
break;
default:
throw 'invalid matrix mode ' + mode;
}
};
gl.loadIdentity = function() {
Matrix.identity(gl[matrix]);
};
gl.loadMatrix = function(m) {
var from = m.m, to = gl[matrix].m;
for (var i = 0; i < 16; i++) {
to[i] = from[i];
}
};
gl.multMatrix = function(m) {
gl.loadMatrix(Matrix.multiply(gl[matrix], m, resultMatrix));
};
gl.perspective = function(fov, aspect, near, far) {
gl.multMatrix(Matrix.perspective(fov, aspect, near, far, tempMatrix));
};
gl.frustum = function(l, r, b, t, n, f) {
gl.multMatrix(Matrix.frustum(l, r, b, t, n, f, tempMatrix));
};
gl.ortho = function(l, r, b, t, n, f) {
gl.multMatrix(Matrix.ortho(l, r, b, t, n, f, tempMatrix));
};
gl.scale = function(x, y, z) {
gl.multMatrix(Matrix.scale(x, y, z, tempMatrix));
};
gl.translate = function(x, y, z) {
gl.multMatrix(Matrix.translate(x, y, z, tempMatrix));
};
gl.rotate = function(a, x, y, z) {
gl.multMatrix(Matrix.rotate(a, x, y, z, tempMatrix));
};
gl.lookAt = function(ex, ey, ez, cx, cy, cz, ux, uy, uz) {
gl.multMatrix(Matrix.lookAt(ex, ey, ez, cx, cy, cz, ux, uy, uz, tempMatrix));
};
gl.pushMatrix = function() {
stack.push(Array.prototype.slice.call(gl[matrix].m));
};
gl.popMatrix = function() {
var m = stack.pop();
gl[matrix].m = hasFloat32Array ? new Float32Array(m) : m;
};
gl.project = function(objX, objY, objZ, modelview, projection, viewport) {
modelview = modelview || gl.modelviewMatrix;
projection = projection || gl.projectionMatrix;
viewport = viewport || gl.getParameter(gl.VIEWPORT);
var point = projection.transformPoint(modelview.transformPoint(new Vector(objX, objY, objZ)));
return new Vector(
viewport[0] + viewport[2] * (point.x * 0.5 + 0.5),
viewport[1] + viewport[3] * (point.y * 0.5 + 0.5),
point.z * 0.5 + 0.5
);
};
gl.unProject = function(winX, winY, winZ, modelview, projection, viewport) {
modelview = modelview || gl.modelviewMatrix;
projection = projection || gl.projectionMatrix;
viewport = viewport || gl.getParameter(gl.VIEWPORT);
var point = new Vector(
(winX - viewport[0]) / viewport[2] * 2 - 1,
(winY - viewport[1]) / viewport[3] * 2 - 1,
winZ * 2 - 1
);
return Matrix.inverse(Matrix.multiply(projection, modelview, tempMatrix), resultMatrix).transformPoint(point);
};
gl.matrixMode(gl.MODELVIEW);
}
// ### Immediate mode
//
// Provide an implementation of OpenGL's deprecated immediate mode. This is
// depricated for a reason: constantly re-specifying the geometry is a bad
// idea for performance. You should use a `GL.Mesh` instead, which specifies
// the geometry once and caches it on the graphics card. Still, nothing
// beats a quick `gl.begin(gl.POINTS); gl.vertex(1, 2, 3); gl.end();` for
// debugging. This intentionally doesn't implement fixed-function lighting
// because it's only meant for quick debugging tasks.
function addImmediateMode() {
var immediateMode = {
mesh: new Mesh({ coords: true, colors: true, triangles: false }),
mode: -1,
coord: [0, 0, 0, 0],
color: [1, 1, 1, 1],
pointSize: 1,
shader: new Shader('\
uniform float pointSize;\
varying vec4 color;\
varying vec4 coord;\
void main() {\
color = gl_Color;\
coord = gl_TexCoord;\
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\
gl_PointSize = pointSize;\
}\
', '\
uniform sampler2D texture;\
uniform float pointSize;\
uniform bool useTexture;\
varying vec4 color;\
varying vec4 coord;\
void main() {\
gl_FragColor = color;\
if (useTexture) gl_FragColor *= texture2D(texture, coord.xy);\
}\
')
};
gl.pointSize = function(pointSize) {
immediateMode.shader.uniforms({ pointSize: pointSize });
};
gl.begin = function(mode) {
if (immediateMode.mode != -1) throw 'mismatched gl.begin() and gl.end() calls';
immediateMode.mode = mode;
immediateMode.mesh.colors = [];
immediateMode.mesh.coords = [];
immediateMode.mesh.vertices = [];
};
gl.color = function(r, g, b, a) {
immediateMode.color = (arguments.length == 1) ? r.toArray().concat(1) : [r, g, b, a || 1];
};
gl.texCoord = function(s, t) {
immediateMode.coord = (arguments.length == 1) ? s.toArray(2) : [s, t];
};
gl.vertex = function(x, y, z) {
immediateMode.mesh.colors.push(immediateMode.color);
immediateMode.mesh.coords.push(immediateMode.coord);
immediateMode.mesh.vertices.push(arguments.length == 1 ? x.toArray() : [x, y, z]);
};
gl.end = function() {
if (immediateMode.mode == -1) throw 'mismatched gl.begin() and gl.end() calls';
immediateMode.mesh.compile();
immediateMode.shader.uniforms({
useTexture: !!gl.getParameter(gl.TEXTURE_BINDING_2D)
}).draw(immediateMode.mesh, immediateMode.mode);
immediateMode.mode = -1;
};
}
// ### Improved mouse events
//
// This adds event listeners on the `gl.canvas` element that call
// `gl.onmousedown()`, `gl.onmousemove()`, and `gl.onmouseup()` with an
// augmented event object. The event object also has the properties `x`, `y`,
// `deltaX`, `deltaY`, and `dragging`.
function addEventListeners() {
var context = gl, oldX = 0, oldY = 0, buttons = {}, hasOld = false;
function isDragging() {
for (var b in buttons) {
if (buttons[b]) return true;
}
return false;
}
function augment(original) {
// Make a copy of original, a native `MouseEvent`, so we can overwrite
// WebKit's non-standard read-only `x` and `y` properties (which are just
// duplicates of `pageX` and `pageY`). We can't just use
// `Object.create(original)` because some `MouseEvent` functions must be
// called in the context of the original event object.
var e = {};
for (var name in original) {
if (typeof original[name] == 'function') {
e[name] = (function(callback) {
return function() {
callback.call(original, arguments);
};
})(original[name]);
} else {
e[name] = original[name];
}
}
e.original = original;
e.x = e.pageX;
e.y = e.pageY;
for (var obj = gl.canvas; obj; obj = obj.offsetParent) {
e.x -= obj.offsetLeft;
e.y -= obj.offsetTop;
}
if (hasOld) {
e.deltaX = e.x - oldX;
e.deltaY = e.y - oldY;
} else {
e.deltaX = 0;
e.deltaY = 0;
hasOld = true;
}
oldX = e.x;
oldY = e.y;
e.dragging = isDragging();
e.preventDefault = function() {
e.original.preventDefault();
};
e.stopPropagation = function() {
e.original.stopPropagation();
};
return e;
}
function mousedown(e) {
gl = context;
if (!isDragging()) {
// Expand the event handlers to the document to handle dragging off canvas.
on(document, 'mousemove', mousemove);
on(document, 'mouseup', mouseup);
off(gl.canvas, 'mousemove', mousemove);
off(gl.canvas, 'mouseup', mouseup);
}
buttons[e.which] = true;
e = augment(e);
if (gl.onmousedown) gl.onmousedown(e);
e.preventDefault();
}
function mousemove(e) {
gl = context;
e = augment(e);
if (gl.onmousemove) gl.onmousemove(e);
e.preventDefault();
}
function mouseup(e) {
gl = context;
buttons[e.which] = false;
if (!isDragging()) {
// Shrink the event handlers back to the canvas when dragging ends.
off(document, 'mousemove', mousemove);
off(document, 'mouseup', mouseup);
on(gl.canvas, 'mousemove', mousemove);
on(gl.canvas, 'mouseup', mouseup);
}
e = augment(e);
if (gl.onmouseup) gl.onmouseup(e);
e.preventDefault();
}
function reset() {
hasOld = false;
}
on(gl.canvas, 'mousedown', mousedown);
on(gl.canvas, 'mousemove', mousemove);
on(gl.canvas, 'mouseup', mouseup);
on(gl.canvas, 'mouseover', reset);
on(gl.canvas, 'mouseout', reset);
}
// ### Automatic keyboard state
//
// The current keyboard state is stored in `GL.keys`, a map of integer key
// codes to booleans indicating whether that key is currently pressed. Certain
// keys also have named identifiers that can be used directly, such as
// `GL.keys.SPACE`. Values in `GL.keys` are initially undefined until that
// key is pressed for the first time. If you need a boolean value, you can
// cast the value to boolean by applying the not operator twice (as in
// `!!GL.keys.SPACE`).
function mapKeyCode(code) {
var named = {
8: 'BACKSPACE',
9: 'TAB',
13: 'ENTER',
16: 'SHIFT',
27: 'ESCAPE',
32: 'SPACE',
37: 'LEFT',
38: 'UP',
39: 'RIGHT',
40: 'DOWN'
};
return named[code] || (code >= 65 && code <= 90 ? String.fromCharCode(code) : null);
}
function on(element, name, callback) {
element.addEventListener(name, callback);
}
function off(element, name, callback) {
element.removeEventListener(name, callback);
}
on(document, 'keydown', function(e) {
if (!e.altKey && !e.ctrlKey && !e.metaKey) {
var key = mapKeyCode(e.keyCode);
if (key) GL.keys[key] = true;
GL.keys[e.keyCode] = true;
}
});
on(document, 'keyup', function(e) {
if (!e.altKey && !e.ctrlKey && !e.metaKey) {
var key = mapKeyCode(e.keyCode);
if (key) GL.keys[key] = false;
GL.keys[e.keyCode] = false;
}
});
function addOtherMethods() {
// ### Multiple contexts
//
// When using multiple contexts in one web page, `gl.makeCurrent()` must be
// called before issuing commands to a different context.
(function(context) {
gl.makeCurrent = function() {
gl = context;
};
})(gl);
// ### Animation
//
// Call `gl.animate()` to provide an animation loop that repeatedly calls
// `gl.onupdate()` and `gl.ondraw()`.
gl.animate = function() {
var post =
window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
function(callback) { setTimeout(callback, 1000 / 60); };
var time = new Date().getTime();
var context = gl;
function update() {
gl = context;
var now = new Date().getTime();
if (gl.onupdate) gl.onupdate((now - time) / 1000);
if (gl.ondraw) gl.ondraw();
post(update);
time = now;
}
update();
};
// ### Fullscreen
//
// Provide an easy way to get a fullscreen app running, including an
// automatic 3D perspective projection matrix by default. This should be
// called once.
//
// Just fullscreen, no automatic camera:
//
// gl.fullscreen({ camera: false });
//
// Adjusting field of view, near plane distance, and far plane distance:
//
// gl.fullscreen({ fov: 45, near: 0.1, far: 1000 });
//
// Adding padding from the edge of the window:
//
// gl.fullscreen({ paddingLeft: 250, paddingBottom: 60 });
//
gl.fullscreen = function(options) {
options = options || {};
var top = options.paddingTop || 0;
var left = options.paddingLeft || 0;
var right = options.paddingRight || 0;
var bottom = options.paddingBottom || 0;
if (!document.body) {
throw 'document.body doesn\'t exist yet (call gl.fullscreen() from ' +
'window.onload() or from inside the <body> tag)';
}
document.body.appendChild(gl.canvas);
document.body.style.overflow = 'hidden';
gl.canvas.style.position = 'absolute';
gl.canvas.style.left = left + 'px';
gl.canvas.style.top = top + 'px';
function resize() {
gl.canvas.width = window.innerWidth - left - right;
gl.canvas.height = window.innerHeight - top - bottom;
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
if (options.camera || !('camera' in options)) {
gl.matrixMode(gl.PROJECTION);
gl.loadIdentity();
gl.perspective(options.fov || 45, gl.canvas.width / gl.canvas.height,
options.near || 0.1, options.far || 1000);
gl.matrixMode(gl.MODELVIEW);
}
if (gl.ondraw) gl.ondraw();
}
on(window, 'resize', resize);
resize();
};
}
// A value to bitwise-or with new enums to make them distinguishable from the
// standard WebGL enums.
var ENUM = 0x12340000;
// src/mesh.js
// Represents indexed triangle geometry with arbitrary additional attributes.
// You need a shader to draw a mesh; meshes can't draw themselves.
//
// A mesh is a collection of `GL.Buffer` objects which are either vertex buffers
// (holding per-vertex attributes) or index buffers (holding the order in which
// vertices are rendered). By default, a mesh has a position vertex buffer called
// `vertices` and a triangle index buffer called `triangles`. New buffers can be
// added using `addVertexBuffer()` and `addIndexBuffer()`. Two strings are
// required when adding a new vertex buffer, the name of the data array on the
// mesh instance and the name of the GLSL attribute in the vertex shader.
//
// Example usage:
//
// var mesh = new GL.Mesh({ coords: true, lines: true });
//
// // Default attribute "vertices", available as "gl_Vertex" in
// // the vertex shader
// mesh.vertices = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]];
//
// // Optional attribute "coords" enabled in constructor,
// // available as "gl_TexCoord" in the vertex shader
// mesh.coords = [[0, 0], [1, 0], [0, 1], [1, 1]];
//
// // Custom attribute "weights", available as "weight" in the
// // vertex shader
// mesh.addVertexBuffer('weights', 'weight');
// mesh.weights = [1, 0, 0, 1];
//
// // Default index buffer "triangles"
// mesh.triangles = [[0, 1, 2], [2, 1, 3]];
//
// // Optional index buffer "lines" enabled in constructor
// mesh.lines = [[0, 1], [0, 2], [1, 3], [2, 3]];
//
// // Upload provided data to GPU memory
// mesh.compile();
// ### new GL.Indexer()
//
// Generates indices into a list of unique objects from a stream of objects
// that may contain duplicates. This is useful for generating compact indexed
// meshes from unindexed data.
function Indexer() {
this.unique = [];
this.indices = [];
this.map = {};
}
Indexer.prototype = {
// ### .add(v)
//
// Adds the object `obj` to `unique` if it hasn't already been added. Returns
// the index of `obj` in `unique`.
add: function(obj) {
var key = JSON.stringify(obj);
if (!(key in this.map)) {
this.map[key] = this.unique.length;
this.unique.push(obj);
}
return this.map[key];
}
};
// ### new GL.Buffer(target, type)
//
// Provides a simple method of uploading data to a GPU buffer. Example usage:
//
// var vertices = new GL.Buffer(gl.ARRAY_BUFFER, Float32Array);
// var indices = new GL.Buffer(gl.ELEMENT_ARRAY_BUFFER, Uint16Array);
// vertices.data = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]];
// indices.data = [[0, 1, 2], [2, 1, 3]];
// vertices.compile();
// indices.compile();
//
function Buffer(target, type) {
this.buffer = null;
this.target = target;
this.type = type;
this.data = [];
}
Buffer.prototype = {
// ### .compile(type)
//
// Upload the contents of `data` to the GPU in preparation for rendering. The
// data must be a list of lists where each inner list has the same length. For
// example, each element of data for vertex normals would be a list of length three.
// This will remember the data length and element length for later use by shaders.
// The type can be either `gl.STATIC_DRAW` or `gl.DYNAMIC_DRAW`, and defaults to
// `gl.STATIC_DRAW`.
//
// This could have used `[].concat.apply([], this.data)` to flatten
// the array but Google Chrome has a maximum number of arguments so the
// concatenations are chunked to avoid that limit.
compile: function(type) {
var data = [];
for (var i = 0, chunk = 10000; i < this.data.length; i += chunk) {
data = Array.prototype.concat.apply(data, this.data.slice(i, i + chunk));
}
var spacing = this.data.length ? data.length / this.data.length : 0;
if (spacing != Math.round(spacing)) throw 'buffer elements not of consistent size, average size is ' + spacing;
this.buffer = this.buffer || gl.createBuffer();
this.buffer.length = data.length;
this.buffer.spacing = spacing;
gl.bindBuffer(this.target, this.buffer);
gl.bufferData(this.target, new this.type(data), type || gl.STATIC_DRAW);
}
};
// ### new GL.Mesh([options])
//
// Represents a collection of vertex buffers and index buffers. Each vertex
// buffer maps to one attribute in GLSL and has a corresponding property set
// on the Mesh instance. There is one vertex buffer by default: `vertices`,
// which maps to `gl_Vertex`. The `coords`, `normals`, and `colors` vertex
// buffers map to `gl_TexCoord`, `gl_Normal`, and `gl_Color` respectively,
// and can be enabled by setting the corresponding options to true. There are
// two index buffers, `triangles` and `lines`, which are used for rendering
// `gl.TRIANGLES` and `gl.LINES`, respectively. Only `triangles` is enabled by
// default, although `computeWireframe()` will add a normal buffer if it wasn't
// initially enabled.
function Mesh(options) {
options = options || {};
this.vertexBuffers = {};
this.indexBuffers = {};
this.addVertexBuffer('vertices', 'gl_Vertex');
if (options.coords) this.addVertexBuffer('coords', 'gl_TexCoord');
if (options.normals) this.addVertexBuffer('normals', 'gl_Normal');
if (options.colors) this.addVertexBuffer('colors', 'gl_Color');
if (!('triangles' in options) || options.triangles) this.addIndexBuffer('triangles');
if (options.lines) this.addIndexBuffer('lines');
}
Mesh.prototype = {
// ### .addVertexBuffer(name, attribute)
//
// Add a new vertex buffer with a list as a property called `name` on this object
// and map it to the attribute called `attribute` in all shaders that draw this mesh.
addVertexBuffer: function(name, attribute) {
var buffer = this.vertexBuffers[attribute] = new Buffer(gl.ARRAY_BUFFER, Float32Array);
buffer.name = name;
this[name] = [];
},
// ### .addIndexBuffer(name)
//
// Add a new index buffer with a list as a property called `name` on this object.
addIndexBuffer: function(name) {
var buffer = this.indexBuffers[name] = new Buffer(gl.ELEMENT_ARRAY_BUFFER, Uint16Array);
this[name] = [];
},
// ### .compile()
//
// Upload all attached buffers to the GPU in preparation for rendering. This
// doesn't need to be called every frame, only needs to be done when the data
// changes.
compile: function() {
for (var attribute in this.vertexBuffers) {
var buffer = this.vertexBuffers[attribute];
buffer.data = this[buffer.name];
buffer.compile();
}
for (var name in this.indexBuffers) {
var buffer = this.indexBuffers[name];
buffer.data = this[name];
buffer.compile();
}
},
// ### .transform(matrix)
//
// Transform all vertices by `matrix` and all normals by the inverse transpose
// of `matrix`.
transform: function(matrix) {
this.vertices = this.vertices.map(function(v) {
return matrix.transformPoint(Vector.fromArray(v)).toArray();
});
if (this.normals) {
var invTrans = matrix.inverse().transpose();
this.normals = this.normals.map(function(n) {
return invTrans.transformVector(Vector.fromArray(n)).unit().toArray();
});
}
this.compile();
return this;
},
// ### .computeNormals()
//
// Computes a new normal for each vertex from the average normal of the
// neighboring triangles. This means adjacent triangles must share vertices
// for the resulting normals to be smooth.
computeNormals: function() {
if (!this.normals) this.addVertexBuffer('normals', 'gl_Normal');
for (var i = 0; i < this.vertices.length; i++) {
this.normals[i] = new Vector();
}
for (var i = 0; i < this.triangles.length; i++) {
var t = this.triangles[i];
var a = Vector.fromArray(this.vertices[t[0]]);
var b = Vector.fromArray(this.vertices[t[1]]);
var c = Vector.fromArray(this.vertices[t[2]]);
var normal = b.subtract(a).cross(c.subtract(a)).unit();
this.normals[t[0]] = this.normals[t[0]].add(normal);
this.normals[t[1]] = this.normals[t[1]].add(normal);
this.normals[t[2]] = this.normals[t[2]].add(normal);
}
for (var i = 0; i < this.vertices.length; i++) {
this.normals[i] = this.normals[i].unit().toArray();
}
this.compile();
return this;
},
// ### .computeWireframe()
//
// Populate the `lines` index buffer from the `triangles` index buffer.
computeWireframe: function() {
var indexer = new Indexer();
for (var i = 0; i < this.triangles.length; i++) {
var t = this.triangles[i];
for (var j = 0; j < t.length; j++) {
var a = t[j], b = t[(j + 1) % t.length];
indexer.add([Math.min(a, b), Math.max(a, b)]);
}
}
if (!this.lines) this.addIndexBuffer('lines');
this.lines = indexer.unique;
this.compile();
return this;
},
// ### .getAABB()
//
// Computes the axis-aligned bounding box, which is an object whose `min` and
// `max` properties contain the minimum and maximum coordinates of all vertices.
getAABB: function() {
var aabb = { min: new Vector(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE) };
aabb.max = aabb.min.negative();
for (var i = 0; i < this.vertices.length; i++) {
var v = Vector.fromArray(this.vertices[i]);
aabb.min = Vector.min(aabb.min, v);
aabb.max = Vector.max(aabb.max, v);
}
return aabb;
},
// ### .getBoundingSphere()
//
// Computes a sphere that contains all vertices (not necessarily the smallest
// sphere). The returned object has two properties, `center` and `radius`.
getBoundingSphere: function() {
var aabb = this.getAABB();
var sphere = { center: aabb.min.add(aabb.max).divide(2), radius: 0 };
for (var i = 0; i < this.vertices.length; i++) {
sphere.radius = Math.max(sphere.radius,
Vector.fromArray(this.vertices[i]).subtract(sphere.center).length());
}
return sphere;
}
};
// ### GL.Mesh.plane([options])
//
// Generates a square 2x2 mesh the xy plane centered at the origin. The
// `options` argument specifies options to pass to the mesh constructor.
// Additional options include `detailX` and `detailY`, which set the tesselation
// in x and y, and `detail`, which sets both `detailX` and `detailY` at once.
// Two triangles are generated by default.
// Example usage:
//
// var mesh1 = GL.Mesh.plane();
// var mesh2 = GL.Mesh.plane({ detail: 5 });
// var mesh3 = GL.Mesh.plane({ detailX: 20, detailY: 40 });
//
Mesh.plane = function(options) {
options = options || {};
var mesh = new Mesh(options);
detailX = options.detailX || options.detail || 1;
detailY = options.detailY || options.detail || 1;
for (var y = 0; y <= detailY; y++) {
var t = y / detailY;
for (var x = 0; x <= detailX; x++) {
var s = x / detailX;
mesh.vertices.push([2 * s - 1, 2 * t - 1, 0]);
if (mesh.coords) mesh.coords.push([s, t]);
if (mesh.normals) mesh.normals.push([0, 0, 1]);
if (x < detailX && y < detailY) {
var i = x + y * (detailX + 1);
mesh.triangles.push([i, i + 1, i + detailX + 1]);
mesh.triangles.push([i + detailX + 1, i + 1, i + detailX + 2]);
}
}
}
mesh.compile();
return mesh;
};
var cubeData = [
[0, 4, 2, 6, -1, 0, 0], // -x
[1, 3, 5, 7, +1, 0, 0], // +x
[0, 1, 4, 5, 0, -1, 0], // -y
[2, 6, 3, 7, 0, +1, 0], // +y
[0, 2, 1, 3, 0, 0, -1], // -z
[4, 5, 6, 7, 0, 0, +1] // +z
];
function pickOctant(i) {
return new Vector((i & 1) * 2 - 1, (i & 2) - 1, (i & 4) / 2 - 1);
}
// ### GL.Mesh.cube([options])
//
// Generates a 2x2x2 box centered at the origin. The `options` argument
// specifies options to pass to the mesh constructor.
Mesh.cube = function(options) {
var mesh = new Mesh(options);
for (var i = 0; i < cubeData.length; i++) {
var data = cubeData[i], v = i * 4;
for (var j = 0; j < 4; j++) {
var d = data[j];
mesh.vertices.push(pickOctant(d).toArray());
if (mesh.coords) mesh.coords.push([j & 1, (j & 2) / 2]);
if (mesh.normals) mesh.normals.push(data.slice(4, 7));
}
mesh.triangles.push([v, v + 1, v + 2]);
mesh.triangles.push([v + 2, v + 1, v + 3]);
}
mesh.compile();
return mesh;
};
// ### GL.Mesh.sphere([options])
//
// Generates a geodesic sphere of radius 1. The `options` argument specifies
// options to pass to the mesh constructor in addition to the `detail` option,
// which controls the tesselation level. The detail is `6` by default.
// Example usage:
//
// var mesh1 = GL.Mesh.sphere();
// var mesh2 = GL.Mesh.sphere({ detail: 2 });
//
Mesh.sphere = function(options) {
function tri(a, b, c) { return flip ? [a, c, b] : [a, b, c]; }
function fix(x) { return x + (x - x * x) / 2; }
options = options || {};
var mesh = new Mesh(options);
var indexer = new Indexer();
detail = options.detail || 6;
for (var octant = 0; octant < 8; octant++) {
var scale = pickOctant(octant);
var flip = scale.x * scale.y * scale.z > 0;
var data = [];
for (var i = 0; i <= detail; i++) {
// Generate a row of vertices on the surface of the sphere
// using barycentric coordinates.
for (var j = 0; i + j <= detail; j++) {
var a = i / detail;
var b = j / detail;
var c = (detail - i - j) / detail;
var vertex = { vertex: new Vector(fix(a), fix(b), fix(c)).unit().multiply(scale).toArray() };
if (mesh.coords) vertex.coord = scale.y > 0 ? [1 - a, c] : [c, 1 - a];
data.push(indexer.add(vertex));
}
// Generate triangles from this row and the previous row.
if (i > 0) {
for (var j = 0; i + j <= detail; j++) {
var a = (i - 1) * (detail + 1) + ((i - 1) - (i - 1) * (i - 1)) / 2 + j;
var b = i * (detail + 1) + (i - i * i) / 2 + j;
mesh.triangles.push(tri(data[a], data[a + 1], data[b]));
if (i + j < detail) {
mesh.triangles.push(tri(data[b], data[a + 1], data[b + 1]));
}
}
}
}
}
// Reconstruct the geometry from the indexer.
mesh.vertices = indexer.unique.map(function(v) { return v.vertex; });
if (mesh.coords) mesh.coords = indexer.unique.map(function(v) { return v.coord; });
if (mesh.normals) mesh.normals = mesh.vertices;
mesh.compile();
return mesh;
};
// ### GL.Mesh.load(json[, options])
//
// Creates a mesh from the JSON generated by the `convert/convert.py` script.
// Example usage:
//
// var data = {
// vertices: [[0, 0, 0], [1, 0, 0], [0, 1, 0]],
// triangles: [[0, 1, 2]]
// };
// var mesh = GL.Mesh.load(data);
//
Mesh.load = function(json, options) {
options = options || {};
if (!('coords' in options)) options.coords = !!json.coords;
if (!('normals' in options)) options.normals = !!json.normals;
if (!('colors' in options)) options.colors = !!json.colors;
if (!('triangles' in options)) options.triangles = !!json.triangles;
if (!('lines' in options)) options.lines = !!json.lines;
var mesh = new Mesh(options);
mesh.vertices = json.vertices;
if (mesh.coords) mesh.coords = json.coords;
if (mesh.normals) mesh.normals = json.normals;
if (mesh.colors) mesh.colors = json.colors;
if (mesh.triangles) mesh.triangles = json.triangles;
if (mesh.lines) mesh.lines = json.lines;
mesh.compile();
return mesh;
};
// src/vector.js
// Provides a simple 3D vector class. Vector operations can be done using member
// functions, which return new vectors, or static functions, which reuse
// existing vectors to avoid generating garbage.
function Vector(x, y, z) {
this.x = x || 0;
this.y = y || 0;
this.z = z || 0;
}
// ### Instance Methods
// The methods `add()`, `subtract()`, `multiply()`, and `divide()` can all
// take either a vector or a number as an argument.
Vector.prototype = {
negative: function() {
return new Vector(-this.x, -this.y, -this.z);
},
add: function(v) {
if (v instanceof Vector) return new Vector(this.x + v.x, this.y + v.y, this.z + v.z);
else return new Vector(this.x + v, this.y + v, this.z + v);
},
subtract: function(v) {
if (v instanceof Vector) return new Vector(this.x - v.x, this.y - v.y, this.z - v.z);
else return new Vector(this.x - v, this.y - v, this.z - v);
},
multiply: function(v) {
if (v instanceof Vector) return new Vector(this.x * v.x, this.y * v.y, this.z * v.z);
else return new Vector(this.x * v, this.y * v, this.z * v);
},
divide: function(v) {
if (v instanceof Vector) return new Vector(this.x / v.x, this.y / v.y, this.z / v.z);
else return new Vector(this.x / v, this.y / v, this.z / v);
},
equals: function(v) {
return this.x == v.x && this.y == v.y && this.z == v.z;
},
dot: function(v) {
return this.x * v.x + this.y * v.y + this.z * v.z;
},
cross: function(v) {
return new Vector(
this.y * v.z - this.z * v.y,
this.z * v.x - this.x * v.z,
this.x * v.y - this.y * v.x
);
},
length: function() {
return Math.sqrt(this.dot(this));
},
unit: function() {
return this.divide(this.length());
},
min: function() {
return Math.min(Math.min(this.x, this.y), this.z);
},
max: function() {
return Math.max(Math.max(this.x, this.y), this.z);
},
toAngles: function() {
return {
theta: Math.atan2(this.z, this.x),
phi: Math.asin(this.y / this.length())
};
},
toArray: function(n) {
return [this.x, this.y, this.z].slice(0, n || 3);
},
clone: function() {
return new Vector(this.x, this.y, this.z);
},
init: function(x, y, z) {
this.x = x; this.y = y; this.z = z;
return this;
}
};
// ### Static Methods
// `Vector.randomDirection()` returns a vector with a length of 1 and a
// statistically uniform direction. `Vector.lerp()` performs linear
// interpolation between two vectors.
Vector.negative = function(a, b) {
b.x = -a.x; b.y = -a.y; b.z = -a.z;
return b;
};
Vector.add = function(a, b, c) {
if (b instanceof Vector) { c.x = a.x + b.x; c.y = a.y + b.y; c.z = a.z + b.z; }
else { c.x = a.x + b; c.y = a.y + b; c.z = a.z + b; }
return c;
};
Vector.subtract = function(a, b, c) {
if (b instanceof Vector) { c.x = a.x - b.x; c.y = a.y - b.y; c.z = a.z - b.z; }
else { c.x = a.x - b; c.y = a.y - b; c.z = a.z - b; }
return c;
};
Vector.multiply = function(a, b, c) {
if (b instanceof Vector) { c.x = a.x * b.x; c.y = a.y * b.y; c.z = a.z * b.z; }
else { c.x = a.x * b; c.y = a.y * b; c.z = a.z * b; }
return c;
};
Vector.divide = function(a, b, c) {
if (b instanceof Vector) { c.x = a.x / b.x; c.y = a.y / b.y; c.z = a.z / b.z; }
else { c.x = a.x / b; c.y = a.y / b; c.z = a.z / b; }
return c;
};
Vector.cross = function(a, b, c) {
c.x = a.y * b.z - a.z * b.y;
c.y = a.z * b.x - a.x * b.z;
c.z = a.x * b.y - a.y * b.x;
return c;
};
Vector.unit = function(a, b) {
var length = a.length();
b.x = a.x / length;
b.y = a.y / length;
b.z = a.z / length;
return b;
};
Vector.fromAngles = function(theta, phi) {
return new Vector(Math.cos(theta) * Math.cos(phi), Math.sin(phi), Math.sin(theta) * Math.cos(phi));
};
Vector.randomDirection = function() {
return Vector.fromAngles(Math.random() * Math.PI * 2, Math.asin(Math.random() * 2 - 1));
};
Vector.min = function(a, b) {
return new Vector(Math.min(a.x, b.x), Math.min(a.y, b.y), Math.min(a.z, b.z));
};
Vector.max = function(a, b) {
return new Vector(Math.max(a.x, b.x), Math.max(a.y, b.y), Math.max(a.z, b.z));
};
Vector.lerp = function(a, b, fraction) {
return b.subtract(a).multiply(fraction).add(a);
};
Vector.fromArray = function(a) {
return new Vector(a[0], a[1], a[2]);
};
// src/texture.js
// Provides a simple wrapper around WebGL textures that supports render-to-texture.
// ### new GL.Texture(width, height[, options])
//
// The arguments `width` and `height` give the size of the texture in texels.
// WebGL texture dimensions must be powers of two unless `filter` is set to
// either `gl.NEAREST` or `gl.REPEAT` and `wrap` is set to `gl.CLAMP_TO_EDGE`
// (which they are by default).
//
// Texture parameters can be passed in via the `options` argument.
// Example usage:
//
// var t = new GL.Texture(256, 256, {
// // Defaults to gl.LINEAR, set both at once with "filter"
// magFilter: gl.NEAREST,
// minFilter: gl.LINEAR,
//
// // Defaults to gl.CLAMP_TO_EDGE, set both at once with "wrap"
// wrapS: gl.REPEAT,
// wrapT: gl.REPEAT,
//
// format: gl.RGB, // Defaults to gl.RGBA
// type: g