//source:http://www.cgl.ucsf.edu/chimera/webgl/PhiloGL-1.3.0.js
/** @preserve Copyright (c) 2011 Nicolas Garcia Belmonte (http://philogb.github.com/)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- /
(function() { //core.js //Provides general utility methods, module unpacking methods and the PhiloGL app creation method.
//Global this.PhiloGL = null;
//Creates a single application object asynchronously //with a gl context, a camera, a program, a scene, and an event system. (function () {
PhiloGL = function(canvasId, opt) { opt = $.merge({ context: { /* debug: true */ }, camera: { fov: 45, near: 0.1, far: 500 }, program: { from: 'defaults', //(defaults|ids|sources|uris) vs: 'Default', fs: 'Default' }, scene: { /* All the scene.js options: lights: { ... } */ }, textures: { src: [] }, events: { /* All the events.js options: onClick: fn, onTouchStart: fn... */ }, onLoad: $.empty, onError: $.empty
}, opt || {}); var optContext = opt.context, optCamera = opt.camera, optEvents = opt.events, optTextures = opt.textures, optProgram = $.splat(opt.program), optScene = opt.scene; //get Context global to all framework gl = PhiloGL.WebGL.getContext(canvasId, optContext);
if (!gl) { opt.onError("The WebGL context couldn't been initialized"); return null; }
//get Program var popt = { 'defaults': 'fromDefaultShaders', 'ids': 'fromShaderIds', 'sources': 'fromShaderSources', 'uris': 'fromShaderURIs' };
var programLength = optProgram.length, programCallback = (function() { var count = programLength, programs = {}, error = false; return { onSuccess: function(p, popt) { programs[popt.id || (programLength - count)] = p; count--; if (count == 0 && !error) { loadProgramDeps(gl, programLength == 1? p : programs, function(app) { opt.onLoad(app); }); } }, onError: function(p) { count--; opt.onError(opt.id); error = true; } }; })(); optProgram.forEach(function(optProgram, i) { var pfrom = optProgram.from; for (var p in popt) { if (pfrom == p) { program = PhiloGL.Program[popt[p]]($.extend(programCallback, optProgram)); break; } } if (program) { programCallback.onSuccess(program, optProgram); } });
function loadProgramDeps(gl, program, callback) { //get Camera var canvas = gl.canvas, camera = new PhiloGL.Camera(optCamera.fov, canvas.width / canvas.height, optCamera.near, optCamera.far, optCamera); camera.update(); //get Scene var scene = new PhiloGL.Scene(program, camera, optScene); //make app instance global to all framework app = new PhiloGL.WebGL.Application({ gl: gl, canvas: canvas, program: program, scene: scene, camera: camera });
//Use program if (program.$$family == 'program') { program.use(); } //get Events if (optEvents) { PhiloGL.Events.create(app, $.extend(optEvents, { bind: app })); }
//load Textures if (optTextures.src.length) { new PhiloGL.IO.Textures($.extend(optTextures, { onComplete: function() { callback(app); } })); } else { callback(app); } } };
})();
//Unpacks the submodules to the global space.
PhiloGL.unpack = function(branch) {
branch = branch || globalContext; ['Vec3', 'Mat4', 'Quat', 'Camera', 'Program', 'WebGL', 'O3D', 'Scene', 'Shaders', 'IO', 'Events', 'WorkerGroup', 'Fx', 'Media'].forEach(function(module) { branch[module] = PhiloGL[module]; });
};
//Version PhiloGL.version = '1.3.0';
//Holds the 3D context, holds the application var gl, app, globalContext = this;
//Utility functions function $(d) {
return document.getElementById(d);
}
$.empty = function() {};
$.time = Date.now || function() {
return +new Date;
};
$.uid = (function() {
var t = $.time(); return function() { return t++; };
})();
$.extend = function(to, from) {
for (var p in from) { to[p] = from[p]; } return to;
};
$.type = (function() {
var oString = Object.prototype.toString, type = function(e) { var t = oString.call(e); return t.substr(8, t.length - 9).toLowerCase(); };
return function(elem) { var elemType = type(elem); if (elemType != 'object') { return elemType; } if (elem.$$family) return elem.$$family; return (elem && elem.nodeName && elem.nodeType == 1) ? 'element' : elemType; };
})();
(function() {
function detach(elem) { var type = $.type(elem), ans; if (type == 'object') { ans = {}; for (var p in elem) { ans[p] = detach(elem[p]); } return ans; } else if (type == 'array') { ans = []; for (var i = 0, l = elem.length; i < l; i++) { ans[i] = detach(elem[i]); } return ans; } else { return elem; } }
$.merge = function() { var mix = {}; for (var i = 0, l = arguments.length; i < l; i++){ var object = arguments[i]; if ($.type(object) != 'object') continue; for (var key in object){ var op = object[key], mp = mix[key]; if (mp && $.type(op) == 'object' && $.type(mp) == 'object') { mix[key] = $.merge(mp, op); } else{ mix[key] = detach(op); } } } return mix; };
})();
$.splat = (function() {
var isArray = Array.isArray; return function(a) { return isArray(a) && a || [a]; };
})();
//webgl.js
//Checks if WebGL is enabled and creates a context for using WebGL.
(function () {
var WebGL = { getContext: function(canvas, opt) { var canvas = typeof canvas == 'string'? $(canvas) : canvas, ctx; ctx = canvas.getContext('experimental-webgl', opt); if (!ctx) { ctx = canvas.getContext('webgl', opt); } //Set as debug handler if (ctx && opt && opt.debug) { gl = {}; for (var m in ctx) { var f = ctx[m]; if (typeof f == 'function') { gl[m] = (function(k, v) { return function() { console.log(k, Array.prototype.join.call(arguments), Array.prototype.slice.call(arguments)); try { var ans = v.apply(ctx, arguments); } catch (e) { throw k + " " + e; } var errorStack = [], error; while((error = ctx.getError()) !== ctx.NO_ERROR) { errorStack.push(error); } if (errorStack.length) { throw errorStack.join(); } return ans; }; })(m, f); } else { gl[m] = f; } } } else { gl = ctx; }
//add a get by name param if (gl) { gl.get = function(name) { return typeof name == 'string'? gl[name] : name; }; }
return gl; }
}; function Application(options) { //copy program, scene, camera, etc. for (var prop in options) { this[prop] = options[prop]; } //handle buffers this.buffers = {}; this.bufferMemo = {}; //handle framebuffers this.frameBuffers = {}; this.frameBufferMemo = {}; //handle renderbuffers this.renderBuffers = {}; this.renderBufferMemo = {}; //handle textures this.textures = {}; this.textureMemo = {}; }
Application.prototype = { $$family: 'application',
setBuffer: function(program, name, opt) { //unbind buffer if (opt === false || opt === null) { opt = this.bufferMemo[name]; //reset buffer opt && gl.bindBuffer(opt.bufferType, null); //disable vertex attrib array if the buffer maps to an attribute. var attributeName = opt && opt.attribute || name, loc = program.attributes[attributeName]; if (loc !== undefined) { gl.disableVertexAttribArray(loc); } return; } //set defaults opt = $.merge({ bufferType: gl.ARRAY_BUFFER, size: 1, dataType: gl.FLOAT, stride: 0, offset: 0, drawType: gl.STATIC_DRAW }, this.bufferMemo[name] || {}, opt || {});
var attributeName = opt.attribute || name, bufferType = opt.bufferType, hasBuffer = name in this.buffers, buffer = hasBuffer? this.buffers[name] : gl.createBuffer(), hasValue = 'value' in opt, value = opt.value, size = opt.size, dataType = opt.dataType, stride = opt.stride, offset = opt.offset, drawType = opt.drawType, loc = program.attributes[attributeName], isAttribute = loc !== undefined;
if (!hasBuffer) { this.buffers[name] = buffer; } isAttribute && gl.enableVertexAttribArray(loc); gl.bindBuffer(bufferType, buffer); if (hasValue) { gl.bufferData(bufferType, value, drawType); } isAttribute && gl.vertexAttribPointer(loc, size, dataType, false, stride, offset); //set default options so we don't have to next time. //set them under the buffer name and attribute name (if an //attribute is defined) delete opt.value; this.bufferMemo[name] = opt; if (isAttribute) { this.bufferMemo[attributeName] = opt; }
return this; },
setBuffers: function(program, obj) { for (var name in obj) { this.setBuffer(program, name, obj[name]); } return this; },
setFrameBuffer: function(name, opt) { //bind/unbind framebuffer if (typeof opt != 'object') { gl.bindFramebuffer(gl.FRAMEBUFFER, opt? this.frameBuffers[name] : null); return; } //get options opt = $.merge({ width: 0, height: 0, //All texture params bindToTexture: false, textureOptions: { attachment: gl.COLOR_ATTACHMENT0 }, //All render buffer params bindToRenderBuffer: false, renderBufferOptions: { attachment: gl.DEPTH_ATTACHMENT } }, this.frameBufferMemo[name] || {}, opt || {}); var bindToTexture = opt.bindToTexture, bindToRenderBuffer = opt.bindToRenderBuffer, hasBuffer = name in this.frameBuffers, frameBuffer = hasBuffer? this.frameBuffers[name] : gl.createFramebuffer(gl.FRAMEBUFFER);
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
if (!hasBuffer) { this.frameBuffers[name] = frameBuffer; } if (bindToTexture) { var texBindOpt = $.merge({ data: { width: opt.width, height: opt.height } }, $.type(bindToTexture) == 'object'? bindToTexture : {}), texName = name + '-texture', texOpt = opt.textureOptions; this.setTexture(texName, texBindOpt); gl.framebufferTexture2D(gl.FRAMEBUFFER, texOpt.attachment, this.textureMemo[texName].textureType, this.textures[texName], 0); }
if (bindToRenderBuffer) { var rbBindOpt = $.merge({ width: opt.width, height: opt.height }, $.type(bindToRenderBuffer) == 'object'? bindToRenderBuffer : {}), rbName = name + '-renderbuffer', rbOpt = opt.renderBufferOptions; this.setRenderBuffer(rbName, rbBindOpt);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, rbOpt.attachment, gl.RENDERBUFFER, this.renderBuffers[rbName]); }
gl.bindTexture(gl.TEXTURE_2D, null); gl.bindRenderbuffer(gl.RENDERBUFFER, null); gl.bindFramebuffer(gl.FRAMEBUFFER, null);
this.frameBufferMemo[name] = opt;
return this; },
setFrameBuffers: function(obj) { for (var name in obj) { this.setFrameBuffer(name, obj[name]); } return this; },
setRenderBuffer: function(name, opt) { if (typeof opt != 'object') { gl.bindRenderbuffer(gl.RENDERBUFFER, opt? this.renderBufferMemo[name] : null); return; }
opt = $.merge({ storageType: gl.DEPTH_COMPONENT16, width: 0, height: 0 }, this.renderBufferMemo[name] || {}, opt || {});
var hasBuffer = name in this.renderBuffers, renderBuffer = hasBuffer? this.renderBuffers[name] : gl.createRenderbuffer(gl.RENDERBUFFER);
if (!hasBuffer) { this.renderBuffers[name] = renderBuffer; } gl.bindRenderbuffer(gl.RENDERBUFFER, renderBuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, opt.storageType, opt.width, opt.height);
this.renderBufferMemo[name] = opt;
return this; },
setRenderBuffers: function(obj) { for (var name in obj) { this.setRenderBuffer(name, obj[name]); } return this; },
setTexture: function(name, opt) { //bind texture if (!opt || typeof opt != 'object') { gl.activeTexture(opt || gl.TEXTURE0); gl.bindTexture(this.textureMemo[name].textureType || gl.TEXTURE_2D, this.textures[name]); return; } //get defaults opt = $.merge({ textureType: gl.TEXTURE_2D, pixelStore: [{ name: gl.UNPACK_FLIP_Y_WEBGL, value: true }], parameters: [{ name: gl.TEXTURE_MAG_FILTER, value: gl.NEAREST }, { name: gl.TEXTURE_MIN_FILTER, value: gl.NEAREST }], data: { format: gl.RGBA, value: false, width: 0, height: 0, border: 0 }
}, this.textureMemo[name] || {}, opt || {});
var textureType = ('textureType' in opt)? gl.get(opt.textureType) : gl.TEXTURE_2D, textureTarget = ('textureTarget' in opt)? gl.get(opt.textureTarget) : textureType, isCube = textureType == gl.TEXTURE_CUBE_MAP, hasTexture = name in this.textures, texture = hasTexture? this.textures[name] : gl.createTexture(), pixelStore = opt.pixelStore, parameters = opt.parameters, data = opt.data, value = data.value, format = data.format, hasValue = !!data.value;
//save texture if (!hasTexture) { this.textures[name] = texture; } gl.bindTexture(textureType, texture); if (!hasTexture) { //set texture properties pixelStore.forEach(function(opt) { opt.name = typeof opt.name == 'string'? gl[opt.name] : opt.name; gl.pixelStorei(opt.name, opt.value); }); } //load texture if (hasValue) { //beware that we can be loading multiple textures (i.e. it could be a cubemap) if (isCube) { for (var i = 0; i < 6; ++i) {
// gl.texSubImage2D(textureTarget + i, 0, 0, 0, format, gl.UNSIGNED_BYTE, value[i]);
gl.texImage2D(textureTarget[i], 0, format, format, gl.UNSIGNED_BYTE, value[i]); } } else { gl.texImage2D(textureTarget, 0, format, format, gl.UNSIGNED_BYTE, value); } //we're setting a texture to a framebuffer } else if (data.width || data.height) { gl.texImage2D(textureTarget, 0, format, data.width, data.height, data.border, format, gl.UNSIGNED_BYTE, null); } //set texture parameters if (!hasTexture) { parameters.forEach(function(opt) { opt.name = gl.get(opt.name); opt.value = gl.get(opt.value); gl.texParameteri(textureType, opt.name, opt.value); if (opt.generateMipmap) { gl.generateMipmap(textureType); } }); } //remember whether the texture is a cubemap or not opt.isCube = isCube; //set default options so we don't have to next time. delete opt.data; this.textureMemo[name] = opt; return this; },
setTextures: function(obj) { for (var name in obj) { this.setTexture(name, obj[name]); } return this; },
use: function(program) { gl.useProgram(program.program); //remember last used program. this.usedProgram = program; return this; } };
WebGL.Application = Application; //Feature test WebGL (function() { try { var canvas = document.createElement('canvas'); PhiloGL.hasWebGL = function() { return !!(window.WebGLRenderingContext && (canvas.getContext('webgl') || canvas.getContext('experimental-webgl'))); }; } catch(e) { PhiloGL.hasWebGL = function() { return false; }; } })();
PhiloGL.WebGL = WebGL;
})();
//math.js //Vec3, Mat4 and Quat classes
(function() {
var sqrt = Math.sqrt, sin = Math.sin, cos = Math.cos, tan = Math.tan, pi = Math.PI, slice = Array.prototype.slice, typedArray = this.Float32Array, //As of version 12 Chrome does not support call/apply on typed array constructors. ArrayImpl = (function() { if (!typedArray.call) { return Array; } try { typedArray.call({}, 10); } catch (e) { return Array; } return typedArray; })(), typed = ArrayImpl != Array;
//create property descriptor function descriptor(index) { return { get: function() { return this[index]; }, set: function(val) { this[index] = val; }, configurable: false, enumerable: false }; }
//Vec3 Class var Vec3 = function(x, y, z) { if (typed) { typedArray.call(this, 3);
this[0] = x || 0; this[1] = y || 0; this[2] = z || 0; } else { this.push(x || 0, y || 0, z || 0); }
this.typedContainer = new typedArray(3); };
//fast Vec3 create. Vec3.create = function() { return new typedArray(3); };
//create fancy x, y, z setters and getters. Vec3.prototype = Object.create(ArrayImpl.prototype, { $$family: { value: 'Vec3' }, x: descriptor(0), y: descriptor(1), z: descriptor(2) });
var generics = { setVec3: function(dest, vec) { dest[0] = vec[0]; dest[1] = vec[1]; dest[2] = vec[2]; return dest; },
set: function(dest, x, y, z) { dest[0] = x; dest[1] = y; dest[2] = z; return dest; }, add: function(dest, vec) { return new Vec3(dest[0] + vec[0], dest[1] + vec[1], dest[2] + vec[2]); }, $add: function(dest, vec) { dest[0] += vec[0]; dest[1] += vec[1]; dest[2] += vec[2]; return dest; }, add2: function(dest, a, b) { dest[0] = a[0] + b[0]; dest[1] = a[1] + b[1]; dest[2] = a[2] + b[2]; return dest; }, sub: function(dest, vec) { return new Vec3(dest[0] - vec[0], dest[1] - vec[1], dest[2] - vec[2]); }, $sub: function(dest, vec) { dest[0] -= vec[0]; dest[1] -= vec[1]; dest[2] -= vec[2]; return dest; }, sub2: function(dest, a, b) { dest[0] = a[0] - b[0]; dest[1] = a[1] - b[1]; dest[2] = a[2] - b[2]; return dest; }, scale: function(dest, s) { return new Vec3(dest[0] * s, dest[1] * s, dest[2] * s); }, $scale: function(dest, s) { dest[0] *= s; dest[1] *= s; dest[2] *= s; return dest; },
neg: function(dest) { return new Vec3(-dest[0], -dest[1], -dest[2]); },
$neg: function(dest) { dest[0] = -dest[0]; dest[1] = -dest[1]; dest[2] = -dest[2]; return dest; },
unit: function(dest) { var len = Vec3.norm(dest); if (len > 0) { return Vec3.scale(dest, 1 / len); } return Vec3.clone(dest); },
$unit: function(dest) { var len = Vec3.norm(dest);
if (len > 0) { return Vec3.$scale(dest, 1 / len); } return dest; }, cross: function(dest, vec) { var dx = dest[0], dy = dest[1], dz = dest[2], vx = vec[0], vy = vec[1], vz = vec[2]; return new Vec3(dy * vz - dz * vy, dz * vx - dx * vz, dx * vy - dy * vx); }, $cross: function(dest, vec) { var dx = dest[0], dy = dest[1], dz = dest[2], vx = vec[0], vy = vec[1], vz = vec[2];
dest[0] = dy * vz - dz * vy; dest[1] = dz * vx - dx * vz; dest[2] = dx * vy - dy * vx; return dest; },
distTo: function(dest, vec) { var dx = dest[0] - vec[0], dy = dest[1] - vec[1], dz = dest[2] - vec[2]; return sqrt(dx * dx + dy * dy + dz * dz); },
distToSq: function(dest, vec) { var dx = dest[0] - vec[0], dy = dest[1] - vec[1], dz = dest[2] - vec[2];
return dx * dx + dy * dy + dz * dz; },
norm: function(dest) { var dx = dest[0], dy = dest[1], dz = dest[2];
return sqrt(dx * dx + dy * dy + dz * dz); },
normSq: function(dest) { var dx = dest[0], dy = dest[1], dz = dest[2];
return dx * dx + dy * dy + dz * dz; },
dot: function(dest, vec) { return dest[0] * vec[0] + dest[1] * vec[1] + dest[2] * vec[2]; },
clone: function(dest) { if (dest.$$family) { return new Vec3(dest[0], dest[1], dest[2]); } else { return Vec3.setVec3(new typedArray(3), dest); } },
toFloat32Array: function(dest) { var ans = dest.typedContainer;
if (!ans) return dest; ans[0] = dest[0]; ans[1] = dest[1]; ans[2] = dest[2];
return ans; } }; //add generics and instance methods var proto = Vec3.prototype; for (var method in generics) { Vec3[method] = generics[method]; proto[method] = (function (m) { return function() { var args = slice.call(arguments); args.unshift(this); return Vec3[m].apply(Vec3, args); }; })(method); }
//Mat4 Class var Mat4 = function(n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44) { ArrayImpl.call(this, 16);
this.length = 16; if (typeof n11 == 'number') { this.set(n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44); } else { this.id(); }
this.typedContainer = new typedArray(16); };
Mat4.create = function() { return new typedArray(16); };
//create fancy components setters and getters. Mat4.prototype = Object.create(ArrayImpl.prototype, { $$family: { value: 'Mat4' }, n11: descriptor(0), n12: descriptor(4), n13: descriptor(8), n14: descriptor(12), n21: descriptor(1), n22: descriptor(5), n23: descriptor(9), n24: descriptor(13),
n31: descriptor(2), n32: descriptor(6), n33: descriptor(10), n34: descriptor(14),
n41: descriptor(3), n42: descriptor(7), n43: descriptor(11), n44: descriptor(15) });
generics = { id: function(dest) { dest[0 ] = 1; dest[1 ] = 0; dest[2 ] = 0; dest[3 ] = 0; dest[4 ] = 0; dest[5 ] = 1; dest[6 ] = 0; dest[7 ] = 0; dest[8 ] = 0; dest[9 ] = 0; dest[10] = 1; dest[11] = 0; dest[12] = 0; dest[13] = 0; dest[14] = 0; dest[15] = 1; return dest; },
clone: function(dest) { if (dest.$$family) { return new Mat4(dest[0], dest[4], dest[8], dest[12], dest[1], dest[5], dest[9], dest[13], dest[2], dest[6], dest[10], dest[14], dest[3], dest[7], dest[11], dest[15]); } else { return new typedArray(dest); } },
set: function(dest, n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44) { dest[0 ] = n11; dest[4 ] = n12; dest[8 ] = n13; dest[12] = n14; dest[1 ] = n21; dest[5 ] = n22; dest[9 ] = n23; dest[13] = n24; dest[2 ] = n31; dest[6 ] = n32; dest[10] = n33; dest[14] = n34; dest[3 ] = n41; dest[7 ] = n42; dest[11] = n43; dest[15] = n44; return dest; },
mulVec3: function(dest, vec) { var ans = Vec3.clone(vec); return Mat4.$mulVec3(dest, ans); },
$mulVec3: function(dest, vec) { var vx = vec[0], vy = vec[1], vz = vec[2];
vec[0] = dest[0] * vx + dest[4] * vy + dest[8 ] * vz + dest[12]; vec[1] = dest[1] * vx + dest[5] * vy + dest[9 ] * vz + dest[13]; vec[2] = dest[2] * vx + dest[6] * vy + dest[10] * vz + dest[14]; return vec; },
mulMat42: function(dest, a, b) { var a11 = a[0 ], a12 = a[1 ], a13 = a[2 ], a14 = a[3 ], a21 = a[4 ], a22 = a[5 ], a23 = a[6 ], a24 = a[7 ], a31 = a[8 ], a32 = a[9 ], a33 = a[10], a34 = a[11], a41 = a[12], a42 = a[13], a43 = a[14], a44 = a[15], b11 = b[0 ], b12 = b[1 ], b13 = b[2 ], b14 = b[3 ], b21 = b[4 ], b22 = b[5 ], b23 = b[6 ], b24 = b[7 ], b31 = b[8 ], b32 = b[9 ], b33 = b[10], b34 = b[11], b41 = b[12], b42 = b[13], b43 = b[14], b44 = b[15];
dest[0 ] = b11 * a11 + b12 * a21 + b13 * a31 + b14 * a41; dest[1 ] = b11 * a12 + b12 * a22 + b13 * a32 + b14 * a42; dest[2 ] = b11 * a13 + b12 * a23 + b13 * a33 + b14 * a43; dest[3 ] = b11 * a14 + b12 * a24 + b13 * a34 + b14 * a44;
dest[4 ] = b21 * a11 + b22 * a21 + b23 * a31 + b24 * a41; dest[5 ] = b21 * a12 + b22 * a22 + b23 * a32 + b24 * a42; dest[6 ] = b21 * a13 + b22 * a23 + b23 * a33 + b24 * a43; dest[7 ] = b21 * a14 + b22 * a24 + b23 * a34 + b24 * a44;
dest[8 ] = b31 * a11 + b32 * a21 + b33 * a31 + b34 * a41; dest[9 ] = b31 * a12 + b32 * a22 + b33 * a32 + b34 * a42; dest[10] = b31 * a13 + b32 * a23 + b33 * a33 + b34 * a43; dest[11] = b31 * a14 + b32 * a24 + b33 * a34 + b34 * a44;
dest[12] = b41 * a11 + b42 * a21 + b43 * a31 + b44 * a41; dest[13] = b41 * a12 + b42 * a22 + b43 * a32 + b44 * a42; dest[14] = b41 * a13 + b42 * a23 + b43 * a33 + b44 * a43; dest[15] = b41 * a14 + b42 * a24 + b43 * a34 + b44 * a44; return dest; }, mulMat4: function(a, b) { var m = Mat4.clone(a); return Mat4.mulMat42(m, a, b); },
$mulMat4: function(a, b) { return Mat4.mulMat42(a, a, b); },
add: function(dest, m) { var copy = Mat4.clone(dest); return Mat4.$add(copy, m); }, $add: function(dest, m) { dest[0 ] += m[0]; dest[1 ] += m[1]; dest[2 ] += m[2]; dest[3 ] += m[3]; dest[4 ] += m[4]; dest[5 ] += m[5]; dest[6 ] += m[6]; dest[7 ] += m[7]; dest[8 ] += m[8]; dest[9 ] += m[9]; dest[10] += m[10]; dest[11] += m[11]; dest[12] += m[12]; dest[13] += m[13]; dest[14] += m[14]; dest[15] += m[15]; return dest; },
transpose: function(dest) { var m = Mat4.clone(dest); return Mat4.$transpose(m); },
$transpose: function(dest) { var n4 = dest[4], n8 = dest[8], n12 = dest[12], n1 = dest[1], n9 = dest[9], n13 = dest[13], n2 = dest[2], n6 = dest[6], n14 = dest[14], n3 = dest[3], n7 = dest[7], n11 = dest[11];
dest[1] = n4; dest[2] = n8; dest[3] = n12; dest[4] = n1; dest[6] = n9; dest[7] = n13; dest[8] = n2; dest[9] = n6; dest[11] = n14; dest[12] = n3; dest[13] = n7; dest[14] = n11;
return dest; },
rotateAxis: function(dest, theta, vec) { var m = Mat4.clone(dest); return Mat4.$rotateAxis(m, theta, vec); },
$rotateAxis: function(dest, theta, vec) { var s = sin(theta), c = cos(theta), nc = 1 - c, vx = vec[0], vy = vec[1], vz = vec[2], m11 = vx * vx * nc + c, m12 = vx * vy * nc + vz * s, m13 = vx * vz * nc - vy * s, m21 = vy * vx * nc - vz * s, m22 = vy * vy * nc + c, m23 = vy * vz * nc + vx * s, m31 = vx * vz * nc + vy * s, m32 = vy * vz * nc - vx * s, m33 = vz * vz * nc + c, d11 = dest[0], d12 = dest[1], d13 = dest[2], d14 = dest[3], d21 = dest[4], d22 = dest[5], d23 = dest[6], d24 = dest[7], d31 = dest[8], d32 = dest[9], d33 = dest[10], d34 = dest[11], d41 = dest[12], d42 = dest[13], d43 = dest[14], d44 = dest[15]; dest[0 ] = d11 * m11 + d21 * m12 + d31 * m13; dest[1 ] = d12 * m11 + d22 * m12 + d32 * m13; dest[2 ] = d13 * m11 + d23 * m12 + d33 * m13; dest[3 ] = d14 * m11 + d24 * m12 + d34 * m13;
dest[4 ] = d11 * m21 + d21 * m22 + d31 * m23; dest[5 ] = d12 * m21 + d22 * m22 + d32 * m23; dest[6 ] = d13 * m21 + d23 * m22 + d33 * m23; dest[7 ] = d14 * m21 + d24 * m22 + d34 * m23;
dest[8 ] = d11 * m31 + d21 * m32 + d31 * m33; dest[9 ] = d12 * m31 + d22 * m32 + d32 * m33; dest[10] = d13 * m31 + d23 * m32 + d33 * m33; dest[11] = d14 * m31 + d24 * m32 + d34 * m33;
return dest; },
rotateXYZ: function(dest, rx, ry, rz) { var ans = Mat4.clone(dest); return Mat4.$rotateXYZ(ans, rx, ry, rz); },
$rotateXYZ: function(dest, rx, ry, rz) { var d11 = dest[0 ], d12 = dest[1 ], d13 = dest[2 ], d14 = dest[3 ], d21 = dest[4 ], d22 = dest[5 ], d23 = dest[6 ], d24 = dest[7 ], d31 = dest[8 ], d32 = dest[9 ], d33 = dest[10], d34 = dest[11], crx = cos(rx), cry = cos(ry), crz = cos(rz), srx = sin(rx), sry = sin(ry), srz = sin(rz), m11 = cry * crz, m21 = -crx * srz + srx * sry * crz, m31 = srx * srz + crx * sry * crz, m12 = cry * srz, m22 = crx * crz + srx * sry * srz, m32 = -srx * crz + crx * sry * srz, m13 = -sry, m23 = srx * cry, m33 = crx * cry;
dest[0 ] = d11 * m11 + d21 * m12 + d31 * m13; dest[1 ] = d12 * m11 + d22 * m12 + d32 * m13; dest[2 ] = d13 * m11 + d23 * m12 + d33 * m13; dest[3 ] = d14 * m11 + d24 * m12 + d34 * m13; dest[4 ] = d11 * m21 + d21 * m22 + d31 * m23; dest[5 ] = d12 * m21 + d22 * m22 + d32 * m23; dest[6 ] = d13 * m21 + d23 * m22 + d33 * m23; dest[7 ] = d14 * m21 + d24 * m22 + d34 * m23; dest[8 ] = d11 * m31 + d21 * m32 + d31 * m33; dest[9 ] = d12 * m31 + d22 * m32 + d32 * m33; dest[10] = d13 * m31 + d23 * m32 + d33 * m33; dest[11] = d14 * m31 + d24 * m32 + d34 * m33;
return dest; },
translate: function(dest, x, y, z) { var m = Mat4.clone(dest); return Mat4.$translate(m, x, y, z); },
$translate: function(dest, x, y, z) { dest[12] = dest[0 ] * x + dest[4 ] * y + dest[8 ] * z + dest[12]; dest[13] = dest[1 ] * x + dest[5 ] * y + dest[9 ] * z + dest[13]; dest[14] = dest[2 ] * x + dest[6 ] * y + dest[10] * z + dest[14]; dest[15] = dest[3 ] * x + dest[7 ] * y + dest[11] * z + dest[15]; return dest; },
scale: function(dest, x, y, z) { var m = Mat4.clone(dest); return Mat4.$scale(m, x, y, z); },
$scale: function(dest, x, y, z) { dest[0 ] *= x; dest[1 ] *= x; dest[2 ] *= x; dest[3 ] *= x; dest[4 ] *= y; dest[5 ] *= y; dest[6 ] *= y; dest[7 ] *= y; dest[8 ] *= z; dest[9 ] *= z; dest[10] *= z; dest[11] *= z; return dest; },
//Method based on PreGL https://github.com/deanm/pregl/ (c) Dean McNamee. invert: function(dest) { var m = Mat4.clone(dest); return Mat4.$invert(m); },
$invert: function(dest) { var x0 = dest[0], x1 = dest[1], x2 = dest[2], x3 = dest[3], x4 = dest[4], x5 = dest[5], x6 = dest[6], x7 = dest[7], x8 = dest[8], x9 = dest[9], x10 = dest[10], x11 = dest[11], x12 = dest[12], x13 = dest[13], x14 = dest[14], x15 = dest[15];
var a0 = x0*x5 - x1*x4, a1 = x0*x6 - x2*x4, a2 = x0*x7 - x3*x4, a3 = x1*x6 - x2*x5, a4 = x1*x7 - x3*x5, a5 = x2*x7 - x3*x6, b0 = x8*x13 - x9*x12, b1 = x8*x14 - x10*x12, b2 = x8*x15 - x11*x12, b3 = x9*x14 - x10*x13, b4 = x9*x15 - x11*x13, b5 = x10*x15 - x11*x14;
var invdet = 1 / (a0*b5 - a1*b4 + a2*b3 + a3*b2 - a4*b1 + a5*b0);
dest[0 ] = (+ x5*b5 - x6*b4 + x7*b3) * invdet; dest[1 ] = (- x1*b5 + x2*b4 - x3*b3) * invdet; dest[2 ] = (+ x13*a5 - x14*a4 + x15*a3) * invdet; dest[3 ] = (- x9*a5 + x10*a4 - x11*a3) * invdet; dest[4 ] = (- x4*b5 + x6*b2 - x7*b1) * invdet; dest[5 ] = (+ x0*b5 - x2*b2 + x3*b1) * invdet; dest[6 ] = (- x12*a5 + x14*a2 - x15*a1) * invdet; dest[7 ] = (+ x8*a5 - x10*a2 + x11*a1) * invdet; dest[8 ] = (+ x4*b4 - x5*b2 + x7*b0) * invdet; dest[9 ] = (- x0*b4 + x1*b2 - x3*b0) * invdet; dest[10] = (+ x12*a4 - x13*a2 + x15*a0) * invdet; dest[11] = (- x8*a4 + x9*a2 - x11*a0) * invdet; dest[12] = (- x4*b3 + x5*b1 - x6*b0) * invdet; dest[13] = (+ x0*b3 - x1*b1 + x2*b0) * invdet; dest[14] = (- x12*a3 + x13*a1 - x14*a0) * invdet; dest[15] = (+ x8*a3 - x9*a1 + x10*a0) * invdet;
return dest;
}, //TODO(nico) breaking convention here... //because I don't think it's useful to add //two methods for each of these. lookAt: function(dest, eye, center, up) { var z = Vec3.sub(eye, center); z.$unit(); var x = Vec3.cross(up, z); x.$unit(); var y = Vec3.cross(z, x); y.$unit(); return Mat4.set(dest, x[0], x[1], x[2], -x.dot(eye), y[0], y[1], y[2], -y.dot(eye), z[0], z[1], z[2], -z.dot(eye), 0, 0, 0, 1); },
frustum: function(dest, left, right, bottom, top, near, far) { var rl = right - left, tb = top - bottom, fn = far - near; dest[0] = (near * 2) / rl; dest[1] = 0; dest[2] = 0; dest[3] = 0; dest[4] = 0; dest[5] = (near * 2) / tb; dest[6] = 0; dest[7] = 0; dest[8] = (right + left) / rl; dest[9] = (top + bottom) / tb; dest[10] = -(far + near) / fn; dest[11] = -1; dest[12] = 0; dest[13] = 0; dest[14] = -(far * near * 2) / fn; dest[15] = 0;
return dest; },
perspective: function(dest, fov, aspect, near, far) { var ymax = near * tan(fov * pi / 360), ymin = -ymax, xmin = ymin * aspect, xmax = ymax * aspect;
return Mat4.frustum(dest, xmin, xmax, ymin, ymax, near, far); },
ortho: function(dest, left, right, bottom, top, near, far) { var rl = right - left, tb = top - bottom, fn = far - near;
dest[0] = 2 / rl; dest[1] = 0; dest[2] = 0; dest[3] = 0; dest[4] = 0; dest[5] = 2 / tb; dest[6] = 0; dest[7] = 0; dest[8] = 0; dest[9] = 0; dest[10] = -2 / fn; dest[11] = 0; dest[12] = -(left + right) / rl; dest[13] = -(top + bottom) / tb; dest[14] = -(far + near) / fn; dest[15] = 1;
return dest; },
toFloat32Array: function(dest) { var ans = dest.typedContainer;
if (!ans) return dest; ans[0] = dest[0]; ans[1] = dest[1]; ans[2] = dest[2]; ans[3] = dest[3]; ans[4] = dest[4]; ans[5] = dest[5]; ans[6] = dest[6]; ans[7] = dest[7]; ans[8] = dest[8]; ans[9] = dest[9]; ans[10] = dest[10]; ans[11] = dest[11]; ans[12] = dest[12]; ans[13] = dest[13]; ans[14] = dest[14]; ans[15] = dest[15];
return ans; } }; //add generics and instance methods proto = Mat4.prototype; for (method in generics) { Mat4[method] = generics[method]; proto[method] = (function (m) { return function() { var args = slice.call(arguments); args.unshift(this); return Mat4[m].apply(Mat4, args); }; })(method); }
//Quaternion class var Quat = function(x, y, z, w) { ArrayImpl.call(this, 4);
this[0] = x || 0; this[1] = y || 0; this[2] = z || 0; this[3] = w || 0;
this.typedContainer = new typedArray(4); };
Quat.create = function() { return new typedArray(4); };
generics = {
setQuat: function(dest, q) { dest[0] = q[0]; dest[1] = q[1]; dest[2] = q[2]; dest[3] = q[3];
return dest; },
set: function(dest, x, y, z, w) { dest[0] = x || 0; dest[1] = y || 0; dest[2] = z || 0; dest[3] = w || 0;
return dest; }, clone: function(dest) { if (dest.$$family) { return new Quat(dest[0], dest[1], dest[2], dest[3]); } else { return Quat.setQuat(new typedArray(4), dest); } },
neg: function(dest) { return new Quat(-dest[0], -dest[1], -dest[2], -dest[3]); },
$neg: function(dest) { dest[0] = -dest[0]; dest[1] = -dest[1]; dest[2] = -dest[2]; dest[3] = -dest[3]; return dest; },
add: function(dest, q) { return new Quat(dest[0] + q[0], dest[1] + q[1], dest[2] + q[2], dest[3] + q[3]); },
$add: function(dest, q) { dest[0] += q[0]; dest[1] += q[1]; dest[2] += q[2]; dest[3] += q[3]; return dest; },
sub: function(dest, q) { return new Quat(dest[0] - q[0], dest[1] - q[1], dest[2] - q[2], dest[3] - q[3]); },
$sub: function(dest, q) { dest[0] -= q[0]; dest[1] -= q[1]; dest[2] -= q[2]; dest[3] -= q[3]; return dest; },
scale: function(dest, s) { return new Quat(dest[0] * s, dest[1] * s, dest[2] * s, dest[3] * s); },
$scale: function(dest, s) { dest[0] *= s; dest[1] *= s; dest[2] *= s; dest[3] *= s; return dest; },
mulQuat: function(dest, q) { var aX = dest[0], aY = dest[1], aZ = dest[2], aW = dest[3], bX = q[0], bY = q[1], bZ = q[2], bW = q[3];
return new Quat(aW * bX + aX * bW + aY * bZ - aZ * bY, aW * bY + aY * bW + aZ * bX - aX * bZ, aW * bZ + aZ * bW + aX * bY - aY * bX, aW * bW - aX * bX - aY * bY - aZ * bZ); },
$mulQuat: function(dest, q) { var aX = dest[0], aY = dest[1], aZ = dest[2], aW = dest[3], bX = q[0], bY = q[1], bZ = q[2], bW = q[3];
dest[0] = aW * bX + aX * bW + aY * bZ - aZ * bY; dest[1] = aW * bY + aY * bW + aZ * bX - aX * bZ; dest[2] = aW * bZ + aZ * bW + aX * bY - aY * bX; dest[3] = aW * bW - aX * bX - aY * bY - aZ * bZ;
return dest; },
divQuat: function(dest, q) { var aX = dest[0], aY = dest[1], aZ = dest[2], aW = dest[3], bX = q[0], bY = q[1], bZ = q[2], bW = q[3];
var d = 1 / (bW * bW + bX * bX + bY * bY + bZ * bZ); return new Quat((aX * bW - aW * bX - aY * bZ + aZ * bY) * d, (aX * bZ - aW * bY + aY * bW - aZ * bX) * d, (aY * bX + aZ * bW - aW * bZ - aX * bY) * d, (aW * bW + aX * bX + aY * bY + aZ * bZ) * d); },
$divQuat: function(dest, q) { var aX = dest[0], aY = dest[1], aZ = dest[2], aW = dest[3], bX = q[0], bY = q[1], bZ = q[2], bW = q[3];
var d = 1 / (bW * bW + bX * bX + bY * bY + bZ * bZ); dest[0] = (aX * bW - aW * bX - aY * bZ + aZ * bY) * d; dest[1] = (aX * bZ - aW * bY + aY * bW - aZ * bX) * d; dest[2] = (aY * bX + aZ * bW - aW * bZ - aX * bY) * d; dest[3] = (aW * bW + aX * bX + aY * bY + aZ * bZ) * d;
return dest; },
invert: function(dest) { var q0 = dest[0], q1 = dest[1], q2 = dest[2], q3 = dest[3];
var d = 1 / (q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3); return new Quat(-q0 * d, -q1 * d, -q2 * d, q3 * d); },
$invert: function(dest) { var q0 = dest[0], q1 = dest[1], q2 = dest[2], q3 = dest[3];
var d = 1 / (q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
dest[0] = -q0 * d; dest[1] = -q1 * d; dest[2] = -q2 * d; dest[3] = q3 * d; return dest; },
norm: function(dest) { var a = dest[0], b = dest[1], c = dest[2], d = dest[3];
return sqrt(a * a + b * b + c * c + d * d); },
normSq: function(dest) { var a = dest[0], b = dest[1], c = dest[2], d = dest[3];
return a * a + b * b + c * c + d * d; },
unit: function(dest) { return Quat.scale(dest, 1 / Quat.norm(dest)); },
$unit: function(dest) { return Quat.$scale(dest, 1 / Quat.norm(dest)); },
conjugate: function(dest) { return new Quat(-dest[0], -dest[1], -dest[2], dest[3]); },
$conjugate: function(dest) { dest[0] = -dest[0]; dest[1] = -dest[1]; dest[2] = -dest[2]; return dest; } }; //add generics and instance methods proto = Quat.prototype = {}; for (method in generics) { Quat[method] = generics[method]; proto[method] = (function (m) { return function() { var args = slice.call(arguments); args.unshift(this); return Quat[m].apply(Quat, args); }; })(method); } //Add static methods Vec3.fromQuat = function(q) { return new Vec3(q[0], q[1], q[2]); };
Quat.fromVec3 = function(v, r) { return new Quat(v[0], v[1], v[2], r || 0); };
Quat.fromMat4 = function(m) { var u; var v; var w;
// Choose u, v, and w such that u is the index of the biggest diagonal entry // of m, and u v w is an even permutation of 0 1 and 2. if (m[0] > m[5] && m[0] > m[10]) { u = 0; v = 1; w = 2; } else if (m[5] > m[0] && m[5] > m[10]) { u = 1; v = 2; w = 0; } else { u = 2; v = 0; w = 1; }
var r = sqrt(1 + m[u * 5] - m[v * 5] - m[w * 5]); var q = new Quat; q[u] = 0.5 * r; q[v] = 0.5 * (m['n' + v + + u] + m['n' + u + + v]) / r; q[w] = 0.5 * (m['n' + u + + w] + m['n' + w + + u]) / r; q[3] = 0.5 * (m['n' + v + + w] - m['n' + w + + v]) / r;
return q; }; Quat.fromXRotation = function(angle) { return new Quat(sin(angle / 2), 0, 0, cos(angle / 2)); };
Quat.fromYRotation = function(angle) { return new Quat(0, sin(angle / 2), 0, cos(angle / 2)); };
Quat.fromZRotation = function(angle) { return new Quat(0, 0, sin(angle / 2), cos(angle / 2)); };
Quat.fromAxisRotation = function(vec, angle) { var x = vec[0], y = vec[1], z = vec[2], d = 1 / sqrt(x * x + y * y + z * z), s = sin(angle / 2), c = cos(angle / 2);
return new Quat(s * x * d, s * y * d, s * z * d, c); }; Mat4.fromQuat = function(q) { var a = q[3], b = q[0], c = q[1], d = q[2]; return new Mat4(a * a + b * b - c * c - d * d, 2 * b * c - 2 * a * d, 2 * b * d + 2 * a * c, 0, 2 * b * c + 2 * a * d, a * a - b * b + c * c - d * d, 2 * c * d - 2 * a * b, 0, 2 * b * d - 2 * a * c, 2 * c * d + 2 * a * b, a * a - b * b - c * c + d * d, 0, 0, 0, 0, 1); };
PhiloGL.Vec3 = Vec3; PhiloGL.Mat4 = Mat4; PhiloGL.Quat = Quat;
})();
//event.js
//Handle keyboard/mouse/touch events in the Canvas
(function() {
//returns an O3D object or false otherwise. function toO3D(n) { return n !== true ? n : false; } //Returns an element position var getPos = function(elem) { var offset = getOffsets(elem); var scroll = getScrolls(elem); return { x: offset.x - scroll.x, y: offset.y - scroll.y };
function getOffsets(elem) { var position = { x: 0, y: 0 }; while (elem && !isBody(elem)) { position.x += elem.offsetLeft; position.y += elem.offsetTop; elem = elem.offsetParent; } return position; }
function getScrolls(elem) { var position = { x: 0, y: 0 }; while (elem && !isBody(elem)) { position.x += elem.scrollLeft; position.y += elem.scrollTop; elem = elem.parentNode; } return position; }
function isBody(element) { return (/^(?:body|html)$/i).test(element.tagName); } };
//event object wrapper var event = { get: function(e, win) { win = win || window; return e || win.event; }, getWheel: function(e) { return e.wheelDelta? e.wheelDelta / 120 : -(e.detail || 0) / 3; }, getKey: function(e) { var code = e.which || e.keyCode; var key = keyOf(code); //onkeydown var fKey = code - 111; if (fKey > 0 && fKey < 13) key = 'f' + fKey; key = key || String.fromCharCode(code).toLowerCase(); return { code: code, key: key, shift: e.shiftKey, control: e.ctrlKey, alt: e.altKey, meta: e.metaKey }; }, isRightClick: function(e) { return (e.which == 3 || e.button == 2); }, getPos: function(e, win) { // get mouse position win = win || window; e = e || win.event; var doc = win.document; doc = doc.documentElement || doc.body; //TODO(nico): make touch event handling better if(e.touches && e.touches.length) { e = e.touches[0]; } var page = { x: e.pageX || (e.clientX + doc.scrollLeft), y: e.pageY || (e.clientY + doc.scrollTop) }; return page; }, stop: function(e) { if (e.stopPropagation) e.stopPropagation(); e.cancelBubble = true; if (e.preventDefault) e.preventDefault(); else e.returnValue = false; } };
var EventsProxy = function(app, opt) { var domElem = app.canvas; this.scene = app.scene; this.domElem = domElem; this.pos = getPos(domElem); this.opt = this.callbacks = opt;
this.size = { width: domElem.width || domElem.offsetWidth, height: domElem.height || domElem.offsetHeight };
this.attachEvents(); }; EventsProxy.prototype = { hovered: false, pressed: false, touched: false,
touchMoved: false, moved: false, attachEvents: function() { var domElem = this.domElem, opt = this.opt, that = this; if (opt.disableContextMenu) { domElem.oncontextmenu = function() { return false; }; } ['mouseup', 'mousedown', 'mousemove', 'mouseover', 'mouseout', 'touchstart', 'touchmove', 'touchend'].forEach(function(action) { domElem.addEventListener(action, function(e, win) { that[action](that.eventInfo(action, e, win)); }, false); }); ['keydown', 'keyup'].forEach(function(action) { document.addEventListener(action, function(e, win) { that[action](that.eventInfo(action, e, win)); }, false); });
//"well, this is embarrassing..." var type = ; if (!document.getBoxObjectFor && window.mozInnerScreenX == null) { type = 'mousewheel'; } else { type = 'DOMMouseScroll'; } domElem.addEventListener(type, function(e, win) { that['mousewheel'](that.eventInfo('mousewheel', e, win)); }, false); }, eventInfo: function(type, e, win) { var domElem = this.domElem, scene = this.scene, opt = this.opt, size = this.getSize(), relative = opt.relative, centerOrigin = opt.centerOrigin, pos = opt.cachePosition && this.pos || getPos(domElem), ge = event.get(e, win), epos = event.getPos(e, win), evt = {};
//get Position var x = epos.x, y = epos.y; if (relative) { x -= pos.x; y-= pos.y; if (centerOrigin) { x -= size.width / 2; y -= size.height / 2; y *= -1; //y axis now points to the top of the screen } }
switch (type) { case 'mousewheel': evt.wheel = event.getWheel(ge); break; case 'keydown': $.extend(evt, event.getKey(ge)); break; case 'mouseup': evt.isRightClick = event.isRightClick(ge); break; }
var cacheTarget; $.extend(evt, { x: x, y: y, cache: false, //stop event propagation stop: function() { event.stop(ge); }, //get the target element of the event getTarget: function() { if (cacheTarget) return cacheTarget; return (cacheTarget = !opt.picking || scene.pick(epos.x - pos.x, epos.y - pos.y) || true); } }); //wrap native event evt.event = ge; return evt; },
getSize: function() { if (this.cacheSize) { return this.size; } var domElem = this.domElem; return { width: domElem.width || domElem.offsetWidth, height: domElem.height || domElem.offsetHeight }; }, mouseup: function(e) { if(!this.moved) { if(e.isRightClick) { this.callbacks.onRightClick(e, this.hovered); } else { this.callbacks.onClick(e, toO3D(this.pressed)); } } if(this.pressed) { if(this.moved) { this.callbacks.onDragEnd(e, toO3D(this.pressed)); } else { this.callbacks.onDragCancel(e, toO3D(this.pressed)); } this.pressed = this.moved = false; } },
mouseout: function(e) { //mouseout canvas var rt = e.relatedTarget, domElem = this.domElem; while(rt && rt.parentNode) { if(domElem == rt.parentNode) return; rt = rt.parentNode; } if(this.hovered) { this.callbacks.onMouseLeave(e, this.hovered); this.hovered = false; } }, mouseover: function(e) {}, mousemove: function(e) { if(this.pressed) { this.moved = true; this.callbacks.onDragMove(e, toO3D(this.pressed)); return; } if(this.hovered) { var target = toO3D(e.getTarget()); if(!target || target.id != this.hovered.id) { this.callbacks.onMouseLeave(e, this.hovered); this.hovered = target; if(target) { this.callbacks.onMouseEnter(e, this.hovered); } } else { this.callbacks.onMouseMove(e, this.hovered); } } else { this.hovered = toO3D(e.getTarget()); if(this.hovered) { this.callbacks.onMouseEnter(e, this.hovered); } } if (!this.opt.picking) { this.callbacks.onMouseMove(e); } }, mousewheel: function(e) { this.callbacks.onMouseWheel(e); }, mousedown: function(e) { this.pressed = e.getTarget(); this.callbacks.onDragStart(e, toO3D(this.pressed)); }, touchstart: function(e) { this.touched = e.getTarget(); this.callbacks.onTouchStart(e, toO3D(this.touched)); }, touchmove: function(e) { if(this.touched) { this.touchMoved = true; this.callbacks.onTouchMove(e, toO3D(this.touched)); } }, touchend: function(e) { if(this.touched) { if(this.touchMoved) { this.callbacks.onTouchEnd(e, toO3D(this.touched)); } else { this.callbacks.onTouchCancel(e, toO3D(this.touched)); } this.touched = this.touchMoved = false; } },
keydown: function(e) { this.callbacks.onKeyDown(e); },
keyup: function(e) { this.callbacks.onKeyUp(e); } }; var Events = {};
Events.create = function(app, opt) { opt = $.merge({ cachePosition: true, cacheSize: true, relative: true, centerOrigin: true, disableContextMenu: true, bind: false, picking: false, onClick: $.empty, onRightClick: $.empty, onDragStart: $.empty, onDragMove: $.empty, onDragEnd: $.empty, onDragCancel: $.empty, onTouchStart: $.empty, onTouchMove: $.empty, onTouchEnd: $.empty, onTouchCancel: $.empty, onMouseMove: $.empty, onMouseEnter: $.empty, onMouseLeave: $.empty, onMouseWheel: $.empty, onKeyDown: $.empty, onKeyUp: $.empty }, opt || {});
var bind = opt.bind;
if (bind) { for (var name in opt) { if (name.match(/^on[a-zA-Z0-9]+$/)) { (function (name, fn) { opt[name] = function() { return fn.apply(bind, Array.prototype.slice.call(arguments)); }; })(name, opt[name]); } } }
new EventsProxy(app, opt); };
Events.Keys = { 'enter': 13, 'up': 38, 'down': 40, 'left': 37, 'right': 39, 'esc': 27, 'space': 32, 'backspace': 8, 'tab': 9, 'delete': 46 };
function keyOf(code) { var keyMap = Events.Keys; for (var name in keyMap) { if (keyMap[name] == code) { return name; } } }
PhiloGL.Events = Events;
})();
//program.js //Creates programs out of shaders and provides convenient methods for loading //buffers attributes and uniforms
(function() {
//First, some privates to handle compiling/linking shaders to programs. //Creates a shader from a string source. var createShader = function(gl, shaderSource, shaderType) { var shader = gl.createShader(shaderType); if (shader == null) { throw "Error creating the shader with shader type: " + shaderType; //return false; } gl.shaderSource(shader, shaderSource); gl.compileShader(shader); var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); if (!compiled) { var info = gl.getShaderInfoLog(shader); gl.deleteShader(shader); throw "Error while compiling the shader " + info; //return false; } return shader; }; //Creates a program from vertex and fragment shader sources. var createProgram = function(gl, vertexShader, fragmentShader) { var program = gl.createProgram(); gl.attachShader( program, createShader(gl, vertexShader, gl.VERTEX_SHADER)); gl.attachShader( program, createShader(gl, fragmentShader, gl.FRAGMENT_SHADER)); linkProgram(gl, program); return program; }; //Link a program. var linkProgram = function(gl, program) { gl.linkProgram(program); var linked = gl.getProgramParameter(program, gl.LINK_STATUS); if (!linked) { throw "Error linking the shader: " + gl.getProgramInfoLog(program); //return false; } return true; };
//Returns a Magic Uniform Setter var getUniformSetter = function(program, info, isArray) { var name = info.name, loc = gl.getUniformLocation(program, name), type = info.type, matrix = false, vector = true, glFunction, typedArray;
if (info.size > 1 && isArray) { switch(type) { case gl.FLOAT: glFunction = gl.uniform1fv; typedArray = Float32Array; vector = false; break; case gl.INT: case gl.BOOL: case gl.SAMPLER_2D: case gl.SAMPLER_CUBE: glFunction = gl.uniform1iv; typedArray = Uint16Array; vector = false; break; } } if (vector) { switch (type) { case gl.FLOAT: glFunction = gl.uniform1f; break; case gl.FLOAT_VEC2: glFunction = gl.uniform2fv; typedArray = isArray ? Float32Array : new Float32Array(2); break; case gl.FLOAT_VEC3: glFunction = gl.uniform3fv; typedArray = isArray ? Float32Array : new Float32Array(3); break; case gl.FLOAT_VEC4: glFunction = gl.uniform4fv; typedArray = isArray ? Float32Array : new Float32Array(4); break; case gl.INT: case gl.BOOL: case gl.SAMPLER_2D: case gl.SAMPLER_CUBE: glFunction = gl.uniform1i; break; case gl.INT_VEC2: case gl.BOOL_VEC2: glFunction = gl.uniform2iv; typedArray = isArray ? Uint16Array : new Uint16Array(2); break; case gl.INT_VEC3: case gl.BOOL_VEC3: glFunction = gl.uniform3iv; typedArray = isArray ? Uint16Array : new Uint16Array(3); break; case gl.INT_VEC4: case gl.BOOL_VEC4: glFunction = gl.uniform4iv; typedArray = isArray ? Uint16Array : new Uint16Array(4); break; case gl.FLOAT_MAT2: matrix = true; glFunction = gl.uniformMatrix2fv; break; case gl.FLOAT_MAT3: matrix = true; glFunction = gl.uniformMatrix3fv; break; case gl.FLOAT_MAT4: matrix = true; glFunction = gl.uniformMatrix4fv; break; } }
//TODO(nico): Safari 5.1 doesn't have Function.prototype.bind. //remove this check when they implement it. if (glFunction.bind) { glFunction = glFunction.bind(gl); } else { var target = glFunction; glFunction = function() { target.apply(gl, arguments); }; }
//Set a uniform array if (isArray) { return function(val) { glFunction(loc, new typedArray(val)); }; //Set a matrix uniform } else if (matrix) { return function(val) { glFunction(loc, false, val.toFloat32Array()); }; //Set a vector/typed array uniform } else if (typedArray) { return function(val) { typedArray.set(val); glFunction(loc, typedArray); }; //Set a primitive-valued uniform } else { return function(val) { glFunction(loc, val); }; }
throw "Unknown type: " + type;
};
//Program Class: Handles loading of programs and mapping of attributes and uniforms var Program = function(vertexShader, fragmentShader) { var program = createProgram(gl, vertexShader, fragmentShader); if (!program) return false; var attributes = {}, uniforms = {}; //fill attribute locations var len = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); for (var i = 0; i < len; i++) { var info = gl.getActiveAttrib(program, i), name = info.name, index = gl.getAttribLocation(program, info.name); attributes[name] = index; } //create uniform setters len = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); for (i = 0; i < len; i++) { info = gl.getActiveUniform(program, i); name = info.name; //if array name then clean the array brackets name = name[name.length -1] == ']' ? name.substr(0, name.length -3) : name; uniforms[name] = getUniformSetter(program, info, info.name != name); }
this.program = program; //handle attributes and uniforms this.attributes = attributes; this.uniforms = uniforms; };
Program.prototype = { $$family: 'program',
setUniform: function(name, val) { if (this.uniforms[name]) this.uniforms[name](val); return this; },
setUniforms: function(obj) { for (var name in obj) { //this.uniforms[name](obj[name]); this.setUniform(name, obj[name]); } return this; } };
['setBuffer', 'setBuffers', 'use'].forEach(function(name) { Program.prototype[name] = function() { var args = Array.prototype.slice.call(arguments); args.unshift(this); app[name].apply(app, args); return this; }; });
['setFrameBuffer', 'setFrameBuffers', 'setRenderBuffer', 'setRenderBuffers', 'setTexture', 'setTextures'].forEach(function(name) { Program.prototype[name] = function() { app[name].apply(app, arguments); return this; }; });
//Get options in object or arguments function getOptions() { var opt; if (arguments.length == 2) { opt = { vs: arguments[0], fs: arguments[1] }; } else { opt = arguments[0] || {}; } return opt; } //Create a program from vertex and fragment shader node ids Program.fromShaderIds = function() { var opt = getOptions.apply({}, arguments), vs = $(opt.vs), fs = $(opt.fs);
return new Program(vs.innerHTML, fs.innerHTML); };
//Create a program from vs and fs sources Program.fromShaderSources = function(opt) { var opt = getOptions.apply({}, arguments), vs = opt.vs, fs = opt.fs;
return new Program(opt.vs, opt.fs); };
//Build program from default shaders (requires Shaders) Program.fromDefaultShaders = function() { var opt = getOptions.apply({}, arguments), vs = opt.vs || 'Default', fs = opt.fs || 'Default', sh = PhiloGL.Shaders;
return PhiloGL.Program.fromShaderSources(sh.Vertex[vs], sh.Fragment[fs]); }; //Implement Program.fromShaderURIs (requires IO) Program.fromShaderURIs = function(opt) { opt = $.merge({ path: , vs: , fs: , noCache: false, onSuccess: $.empty, onError: $.empty }, opt || {});
var vertexShaderURI = opt.path + opt.vs, fragmentShaderURI = opt.path + opt.fs, XHR = PhiloGL.IO.XHR;
new XHR.Group({ urls: [vertexShaderURI, fragmentShaderURI], noCache: opt.noCache, onError: function(arg) { opt.onError(arg); }, onComplete: function(ans) { opt.onSuccess(Program.fromShaderSources(ans[0], ans[1]), opt); } }).send(); };
PhiloGL.Program = Program;
})();
//io.js //Provides loading of assets with XHR and JSONP methods.
(function () {
var IO = {};
var XHR = function(opt) { opt = $.merge({ url: 'http://philogljs.org/', method: 'GET', async: true, noCache: false, //body: null, sendAsBinary: false, responseType: false, onProgress: $.empty, onSuccess: $.empty, onError: $.empty, onAbort: $.empty, onComplete: $.empty }, opt || {});
this.opt = opt; this.initXHR(); };
XHR.State = {}; ['UNINITIALIZED', 'LOADING', 'LOADED', 'INTERACTIVE', 'COMPLETED'].forEach(function(stateName, i) { XHR.State[stateName] = i; });
XHR.prototype = { initXHR: function() { var req = this.req = new XMLHttpRequest(), that = this;
['Progress', 'Error', 'Abort', 'Load'].forEach(function(event) { req.addEventListener(event.toLowerCase(), function(e) { that['handle' + event](e); }, false); }); }, send: function(body) { var req = this.req, opt = this.opt, async = opt.async; if (opt.noCache) { opt.url += (opt.url.indexOf('?') >= 0? '&' : '?') + $.uid(); }
req.open(opt.method, opt.url, async);
if (opt.responseType) { req.responseType = opt.responseType; } if (async) { req.onreadystatechange = function(e) { if (req.readyState == XHR.State.COMPLETED) { if (req.status == 200) { opt.onSuccess(req.responseType ? req.response : req.responseText); } else { opt.onError(req.status); } } }; } if (opt.sendAsBinary) { req.sendAsBinary(body || opt.body || null); } else { req.send(body || opt.body || null); }
if (!async) { if (req.status == 200) { opt.onSuccess(req.responseType ? req.response : req.responseText); } else { opt.onError(req.status); } } },
setRequestHeader: function(header, value) { this.req.setRequestHeader(header, value); return this; },
handleProgress: function(e) { if (e.lengthComputable) { this.opt.onProgress(e, Math.round(e.loaded / e.total * 100)); } else { this.opt.onProgress(e, -1); } },
handleError: function(e) { this.opt.onError(e); },
handleAbort: function() { this.opt.onAbort(e); },
handleLoad: function(e) { this.opt.onComplete(e); } };
//Make parallel requests and group the responses. XHR.Group = function(opt) { opt = $.merge({ urls: [], onError: $.empty, onSuccess: $.empty, onComplete: $.empty, method: 'GET', async: true, noCache: false, //body: null, sendAsBinary: false, responseType: false }, opt || {});
var urls = $.splat(opt.urls), len = urls.length, ans = Array(len), reqs = urls.map(function(url, i) { return new XHR({ url: url, method: opt.method, async: opt.async, noCache: opt.noCache, sendAsBinary: opt.sendAsBinary, responseType: opt.responseType, body: opt.body, //add callbacks onError: handleError(i), onSuccess: handleSuccess(i) }); });
function handleError(i) { return function(e) { --len; opt.onError(e, i); if (!len) opt.onComplete(ans); }; }
function handleSuccess(i) { return function(response) { --len; ans[i] = response; opt.onSuccess(response, i);
if (!len) opt.onComplete(ans); }; }
this.reqs = reqs; };
XHR.Group.prototype = { send: function() { for (var i = 0, reqs = this.reqs, l = reqs.length; i < l; ++i) { reqs[i].send(); } } };
var JSONP = function(opt) { opt = $.merge({ url: 'http://philogljs.org/', data: {}, noCache: false, onComplete: $.empty, callbackKey: 'callback' }, opt || {}); var index = JSONP.counter++; //create query string var data = []; for(var prop in opt.data) { data.push(prop + '=' + opt.data[prop]); } data = data.join('&'); //append unique id for cache if (opt.noCache) { data += (data.indexOf('?') >= 0? '&' : '?') + $.uid(); } //create source url var src = opt.url + (opt.url.indexOf('?') > -1 ? '&' : '?') + opt.callbackKey + '=PhiloGL.IO.JSONP.requests.request_' + index + (data.length > 0 ? '&' + data : ); //create script var script = document.createElement('script'); script.type = 'text/javascript'; script.src = src; //create callback JSONP.requests['request_' + index] = function(json) { opt.onComplete(json); //remove script if(script.parentNode) { script.parentNode.removeChild(script); } if(script.clearAttributes) { script.clearAttributes(); } }; //inject script document.getElementsByTagName('head')[0].appendChild(script); };
JSONP.counter = 0; JSONP.requests = {};
//Load multiple Image assets async var Images = function(opt) { opt = $.merge({ src: [], noCache: false, onProgress: $.empty, onComplete: $.empty }, opt || {});
var count = 0, l = opt.src.length; //Image onload handler var load = function() { opt.onProgress(Math.round(++count / l * 100)); if (count == l) { opt.onComplete(images); } }; //Image error handler var error = function() { if (++count == l) { opt.onComplete(images); } }; //uid for image sources var noCache = opt.noCache, uid = $.uid(), getSuffix = function(s) { return (s.indexOf('?') >= 0? '&' : '?') + uid; }; //Create image array var images = opt.src.map(function(src, i) { var img = new Image(); img.index = i; img.onload = load; img.onerror = error; img.src = src + (noCache? getSuffix(src) : ); return img; }); return images; };
//Load multiple textures from images var Textures = function(opt) { opt = $.merge({ src: [], noCache: false, onComplete: $.empty }, opt || {});
Images({ src: opt.src, noCache: opt.noCache, onComplete: function(images) { var textures = {}; images.forEach(function(img, i) { textures[opt.src[i]] = $.merge({ data: { value: img } }, opt); }); app.setTextures(textures); opt.onComplete(); } }); }; IO.XHR = XHR; IO.JSONP = JSONP; IO.Images = Images; IO.Textures = Textures; PhiloGL.IO = IO;
})();
//camera.js //Provides a Camera with ModelView and Projection matrices
(function () {
//Define some locals var Vec3 = PhiloGL.Vec3, Mat4 = PhiloGL.Mat4;
//Camera class var Camera = function(fov, aspect, near, far, opt) { opt = opt || {};
var pos = opt.position, target = opt.target, up = opt.up;
this.type = opt.type ? opt.type : 'perspective'; this.fov = fov; this.near = near; this.far = far; this.aspect = aspect; this.position = pos && new Vec3(pos.x, pos.y, pos.z) || new Vec3(); this.target = target && new Vec3(target.x, target.y, target.z) || new Vec3(); this.up = up && new Vec3(up.x, up.y, up.z) || new Vec3(0, 1, 0); if (this.type == 'perspective') { this.projection = new Mat4().perspective(fov, aspect, near, far); } else { var ymax = near * Math.tan(fov * Math.PI / 360), ymin = -ymax, xmin = ymin * aspect, xmax = ymax * aspect;
this.projection = new Mat4().ortho(xmin, xmax, ymin, ymax, near, far); } this.view = new Mat4();
};
Camera.prototype = { update: function() { if (this.type == 'perspective') { this.projection = new Mat4().perspective(this.fov, this.aspect, this.near, this.far); } else { var ymax = this.near * Math.tan(this.fov * Math.PI / 360), ymin = -ymax, xmin = ymin * this.aspect, xmax = ymax * this.aspect;
this.projection = new Mat4().ortho(xmin, xmax, ymin, ymax, this.near, this.far); } this.view.lookAt(this.position, this.target, this.up); } };
PhiloGL.Camera = Camera;
})();
//o3d.js //Scene Objects
(function () {
//Define some locals var Vec3 = PhiloGL.Vec3, Mat4 = PhiloGL.Mat4, cos = Math.cos, sin = Math.sin, pi = Math.PI, max = Math.max, slice = Array.prototype.slice; function normalizeColors(arr, len) { if (arr && arr.length < len) { var a0 = arr[0], a1 = arr[1], a2 = arr[2], a3 = arr[3], ans = [a0, a1, a2, a3], times = len / arr.length, index; while (--times) { index = times * 4; ans[index ] = a0; ans[index + 1] = a1; ans[index + 2] = a2; ans[index + 3] = a3; }
return new Float32Array(ans); } else { return arr; } } //Model repository var O3D = {};
//Model abstract O3D Class O3D.Model = function(opt) { opt = opt || {}; this.id = opt.id || $.uid(); //picking options this.pickable = !!opt.pickable; this.pick = opt.pick || function() { return false; }; if (opt.pickingColors) { this.pickingColors = opt.pickingColors; }
this.vertices = opt.vertices; this.normals = opt.normals; this.textures = opt.textures && $.splat(opt.textures); this.colors = opt.colors; this.indices = opt.indices; this.shininess = opt.shininess || 0; this.reflection = opt.reflection || 0; this.refraction = opt.refraction || 0;
if (opt.texCoords) { this.texCoords = opt.texCoords; }
//extra uniforms this.uniforms = opt.uniforms || {}; //extra attribute descriptors this.attributes = opt.attributes || {}; //override the render method this.render = opt.render; //whether to render as triangles, lines, points, etc. this.drawType = opt.drawType || 'TRIANGLES'; //whether to display the object at all this.display = 'display' in opt? opt.display : true; //before and after render callbacks this.onBeforeRender = opt.onBeforeRender || $.empty; this.onAfterRender = opt.onAfterRender || $.empty; //set a custom program per o3d if (opt.program) { this.program = opt.program; } //model position, rotation, scale and all in all matrix this.position = new Vec3; this.rotation = new Vec3; this.scale = new Vec3(1, 1, 1); this.matrix = new Mat4;
if (opt.computeCentroids) { this.computeCentroids(); }
if (opt.computeNormals) { this.computeNormals(); } };
//Buffer setter mixin var Setters = { setUniforms: function(program) { program.setUniforms(this.uniforms); },
setAttributes: function(program) { var attributes = this.attributes; for (var name in attributes) { var descriptor = attributes[name], bufferId = this.id + '-' + name; if (!Object.keys(descriptor).length) { program.setBuffer(bufferId, true); } else { descriptor.attribute = name; program.setBuffer(bufferId, descriptor); delete descriptor.value; } } }, unsetAttributes: function(program) { var attributes = this.attributes; for (var name in attributes) { var bufferId = this.id + '-' + name; program.setBuffer(bufferId, false); } },
setShininess: function(program) { program.setUniform('shininess', this.shininess || 0); }, setReflection: function(program) { if (this.reflection || this.refraction) { program.setUniforms({ useReflection: true, refraction: this.refraction, reflection: this.reflection }); } else { program.setUniform('useReflection', false); } }, setVertices: function(program, force) { if (!this.vertices) return;
if (force || this.dynamic) { program.setBuffer('vertices-' + this.id, { attribute: 'position', value: this.vertices, size: 3 }); } else { program.setBuffer('vertices-' + this.id); } },
unsetVertices: function(program) { program.setBuffer('vertices-' + this.id, false); }, setNormals: function(program, force) { if (!this.normals) return;
if (force || this.dynamic) { program.setBuffer('normals-' + this.id, { attribute: 'normal', value: this.normals, size: 3 }); } else { program.setBuffer('normals-' + this.id); } },
unsetNormals: function(program) { program.setBuffer('normals-' + this.id, false); },
setIndices: function(program, force) { if (!this.indices) return;
if (force || this.dynamic) { program.setBuffer('indices-' + this.id, { bufferType: gl.ELEMENT_ARRAY_BUFFER, drawType: gl.STATIC_DRAW, value: this.indices, size: 1 }); } else { program.setBuffer('indices-' + this.id); } },
unsetIndices: function(program) { program.setBuffer('indices-' + this.id, false); },
setPickingColors: function(program, force) { if (!this.pickingColors) return;
if (force || this.dynamic) { program.setBuffer('pickingColors-' + this.id, { attribute: 'pickingColor', value: this.pickingColors, size: 4 }); } else { program.setBuffer('pickingColors-' + this.id); } },
unsetPickingColors: function(program) { program.setBuffer('pickingColors-' + this.id, false); }, setColors: function(program, force) { if (!this.colors) return;
if (force || this.dynamic) { program.setBuffer('colors-' + this.id, { attribute: 'color', value: this.colors, size: 4 }); } else { program.setBuffer('colors-' + this.id); } },
unsetColors: function(program) { program.setBuffer('colors-' + this.id, false); },
setTexCoords: function(program, force) { if (!this.texCoords) return;
var id = this.id;
if (force || this.dynamic) { //If is an object containing textureName -> textureCoordArray //Set all textures, samplers and textureCoords. if ($.type(this.texCoords) == 'object') { this.textures.forEach(function(tex, i) { program.setBuffer('texCoords-' + i + '-' + id, { attribute: 'texCoord' + (i + 1), value: this.texCoords[tex], size: 2 }); }); //An array of textureCoordinates } else { program.setBuffer('texCoords-' + id, { attribute: 'texCoord1', value: this.texCoords, size: 2 }); } } else { if ($.type(this.texCoords) == 'object') { this.textures.forEach(function(tex, i) { program.setBuffer('texCoords-' + i + '-' + id); }); } else { program.setBuffer('texCoords-' + id); } } },
unsetTexCoords: function(program) { program.setBuffer('texCoords-' + this.id, false); },
setTextures: function(program, force) { this.textures = this.textures? $.splat(this.textures) : []; var dist = 5; for (var i = 0, texs = this.textures, l = texs.length, mtexs = PhiloGL.Scene.MAX_TEXTURES; i < mtexs; i++) { if (i < l) { var isCube = app.textureMemo[texs[i]].isCube; if (isCube) { program.setUniform('hasTextureCube' + (i + 1), true); program.setTexture(texs[i], gl['TEXTURE' + (i + dist)]); } else { program.setUniform('hasTexture' + (i + 1), true); program.setTexture(texs[i], gl['TEXTURE' + i]); } } else { program.setUniform('hasTextureCube' + (i + 1), false); program.setUniform('hasTexture' + (i + 1), false); } program.setUniform('sampler' + (i + 1), i); program.setUniform('samplerCube' + (i + 1), i + dist); } } }; //ensure known attributes use typed arrays O3D.Model.prototype = Object.create(null, { vertices: { set: function(val) { if (!val) return; var vlen = val.length; if (val.BYTES_PER_ELEMENT) { this.$vertices = val; } else { if (this.$verticesLength == vlen) { this.$vertices.set(val); } else { this.$vertices = new Float32Array(val); } } this.$verticesLength = vlen; }, get: function() { return this.$vertices; } }, normals: { set: function(val) { if (!val) return; var vlen = val.length; if (val.BYTES_PER_ELEMENT) { this.$normals = val; } else { if (this.$normalsLength == vlen) { this.$normals.set(val); } else { this.$normals = new Float32Array(val); } } this.$normalsLength = vlen; }, get: function() { return this.$normals; } }, colors: { set: function(val) { if (!val) return; var vlen = val.length; if (val.BYTES_PER_ELEMENT) { this.$colors = val; } else { if (this.$colorsLength == vlen) { this.$colors.set(val); } else { this.$colors = new Float32Array(val); } } if (this.$vertices && this.$verticesLength / 3 * 4 != vlen) { this.$colors = normalizeColors(slice.call(this.$colors), this.$verticesLength / 3 * 4); } this.$colorsLength = this.$colors.length; }, get: function() { return this.$colors; } }, pickingColors: { set: function(val) { if (!val) return; var vlen = val.length; if (val.BYTES_PER_ELEMENT) { this.$pickingColors = val; } else { if (this.$pickingColorsLength == vlen) { this.$pickingColors.set(val); } else { this.$pickingColors = new Float32Array(val); } } if (this.$vertices && this.$verticesLength / 3 * 4 != vlen) { this.$pickingColors = normalizeColors(slice.call(this.$pickingColors), this.$verticesLength / 3 * 4); } this.$pickingColorsLength = this.$pickingColors.length; }, get: function() { return this.$pickingColors; } }, texCoords: { set: function(val) { if (!val) return; if ($.type(val) == 'object') { var ans = {}; for (var prop in val) { var texCoordArray = val[prop]; ans[prop] = texCoordArray.BYTES_PER_ELEMENT ? texCoordArray : new Float32Array(texCoordArray); } this.$texCoords = ans; } else { var vlen = val.length; if (val.BYTES_PER_ELEMENT) { this.$texCoords = val; } else { if (this.$texCoordsLength == vlen) { this.$texCoords.set(val); } else { this.$texCoords = new Float32Array(val); } } this.$texCoordsLength = vlen; } }, get: function() { return this.$texCoords; } },
indices: { set: function(val) { if (!val) return; var vlen = val.length; if (val.BYTES_PER_ELEMENT) { this.$indices = val; } else { if (this.$indicesLength == vlen) { this.$indices.set(val); } else { this.$indices = new Uint16Array(val); } } this.$indicesLength = vlen; }, get: function() { return this.$indices; } } });
$.extend(O3D.Model.prototype, { $$family: 'model',
update: function() { var matrix = this.matrix, pos = this.position, rot = this.rotation, scale = this.scale;
matrix.id(); matrix.$translate(pos.x, pos.y, pos.z); matrix.$rotateXYZ(rot.x, rot.y, rot.z); matrix.$scale(scale.x, scale.y, scale.z); },
computeCentroids: function() { var faces = this.faces, vertices = this.vertices, centroids = [];
faces.forEach(function(face) { var centroid = [0, 0, 0], acum = 0; face.forEach(function(idx) { var vertex = vertices[idx]; centroid[0] += vertex[0]; centroid[1] += vertex[1]; centroid[2] += vertex[2]; acum++; });
centroid[0] /= acum; centroid[1] /= acum; centroid[2] /= acum;
centroids.push(centroid); });
this.centroids = centroids; },
computeNormals: function() { var faces = this.faces, vertices = this.vertices, normals = [];
faces.forEach(function(face) { var v1 = vertices[face[0]], v2 = vertices[face[1]], v3 = vertices[face[2]], dir1 = { x: v3[0] - v2[0], y: v3[1] - v2[1], z: v3[1] - v2[2] }, dir2 = { x: v1[0] - v2[0], y: v1[1] - v2[1], z: v1[2] - v2[2] };
Vec3.$cross(dir2, dir1); if (Vec3.norm(dir2) > 1e-6) { Vec3.unit(dir2); } normals.push([dir2.x, dir2.y, dir2.z]); });
this.normals = normals; }
}); //Apply our Setters mixin $.extend(O3D.Model.prototype, Setters);
//Now some primitives, Cube, Sphere, Cone, Cylinder //Cube O3D.Cube = function(config) { O3D.Model.call(this, $.extend({ vertices: [-1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1,
-1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1,
-1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1,
-1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1,
1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1,
-1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1],
texCoords: [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
// Back face 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0,
// Top face 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0,
// Bottom face 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
// Right face 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0,
// Left face 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0],
normals: [ // Front face 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,
// Back face 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0,
// Top face 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
// Bottom face 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0,
// Right face 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
// Left face -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0 ], indices: [0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23]
}, config || {})); };
O3D.Cube.prototype = Object.create(O3D.Model.prototype); //Primitives constructors inspired by TDL http://code.google.com/p/webglsamples/, //copyright 2011 Google Inc. new BSD License (http://www.opensource.org/licenses/bsd-license.php). O3D.Sphere = function(opt) { var nlat = opt.nlat || 10, nlong = opt.nlong || 10, radius = opt.radius || 1, startLat = 0, endLat = pi, latRange = endLat - startLat, startLong = 0, endLong = 2 * pi, longRange = endLong - startLong, numVertices = (nlat + 1) * (nlong + 1), vertices = new Float32Array(numVertices * 3), normals = new Float32Array(numVertices * 3), texCoords = new Float32Array(numVertices * 2), indices = new Uint16Array(nlat * nlong * 6);
if (typeof radius == 'number') { var value = radius; radius = function(n1, n2, n3, u, v) { return value; }; } //Create vertices, normals and texCoords for (var y = 0; y <= nlat; y++) { for (var x = 0; x <= nlong; x++) { var u = x / nlong, v = y / nlat, theta = longRange * u, phi = latRange * v, sinTheta = sin(theta), cosTheta = cos(theta), sinPhi = sin(phi), cosPhi = cos(phi), ux = cosTheta * sinPhi, uy = cosPhi, uz = sinTheta * sinPhi, r = radius(ux, uy, uz, u, v), index = x + y * (nlong + 1), i3 = index * 3, i2 = index * 2;
vertices[i3 + 0] = r * ux; vertices[i3 + 1] = r * uy; vertices[i3 + 2] = r * uz;
normals[i3 + 0] = ux; normals[i3 + 1] = uy; normals[i3 + 2] = uz;
texCoords[i2 + 0] = u; texCoords[i2 + 1] = v; } }
//Create indices var numVertsAround = nlat + 1; for (x = 0; x < nlat; x++) { for (y = 0; y < nlong; y++) { var index = (x * nlong + y) * 6; indices[index + 0] = y * numVertsAround + x; indices[index + 1] = y * numVertsAround + x + 1; indices[index + 2] = (y + 1) * numVertsAround + x; indices[index + 3] = (y + 1) * numVertsAround + x; indices[index + 4] = y * numVertsAround + x + 1; indices[index + 5] = (y + 1) * numVertsAround + x + 1; } }
O3D.Model.call(this, $.extend({ vertices: vertices, indices: indices, normals: normals, texCoords: texCoords }, opt || {})); };
O3D.Sphere.prototype = Object.create(O3D.Model.prototype);
//Code based on http://blog.andreaskahler.com/2009/06/creating-icosphere-mesh-in-code.html O3D.IcoSphere = function(opt) { var iterations = opt.iterations || 0, vertices = [], indices = [], sqrt = Math.sqrt, acos = Math.acos, atan2 = Math.atan2, pi = Math.PI, pi2 = pi * 2; //Add a callback for when a vertex is created opt.onAddVertex = opt.onAddVertex || $.empty;
// and Icosahedron vertices var t = (1 + sqrt(5)) / 2, len = sqrt(1 + t * t);
vertices.push(-1 / len, t / len, 0, 1 / len, t / len, 0, -1 / len, -t / len, 0, 1 / len, -t / len, 0,
0, -1 / len, t / len, 0, 1 / len, t / len, 0, -1 / len, -t / len, 0, 1 / len, -t / len,
t / len, 0, -1 / len, t / len, 0, 1 / len, -t / len, 0, -1 / len, -t / len, 0, 1 / len);
indices.push(0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 0, 10, 11,
1, 5, 9, 5, 11, 4, 11, 10, 2, 10, 7, 6, 7, 1, 8,
3, 9, 4, 3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9,
4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9, 8, 1);
var getMiddlePoint = (function() { var pointMemo = {}; return function(i1, i2) { i1 *= 3; i2 *= 3; var mini = i1 < i2 ? i1 : i2, maxi = i1 > i2 ? i1 : i2, key = mini + '|' + maxi;
if (key in pointMemo) { return pointMemo[key]; }
var x1 = vertices[i1 ], y1 = vertices[i1 + 1], z1 = vertices[i1 + 2], x2 = vertices[i2 ], y2 = vertices[i2 + 1], z2 = vertices[i2 + 2], xm = (x1 + x2) / 2, ym = (y1 + y2) / 2, zm = (z1 + z2) / 2, len = sqrt(xm * xm + ym * ym + zm * zm);
xm /= len; ym /= len; zm /= len;
vertices.push(xm, ym, zm);
return (pointMemo[key] = (vertices.length / 3 - 1)); }; })();
for (var i = 0; i < iterations; i++) { var indices2 = []; for (var j = 0, l = indices.length; j < l; j += 3) { var a = getMiddlePoint(indices[j ], indices[j + 1]), b = getMiddlePoint(indices[j + 1], indices[j + 2]), c = getMiddlePoint(indices[j + 2], indices[j ]);
indices2.push(indices[j], a, c, indices[j + 1], b, a, indices[j + 2], c, b, a, b, c); } indices = indices2; }
//Calculate texCoords and normals var l = indices.length, normals = new Float32Array(l * 3), texCoords = new Float32Array(l * 2);
for (var i = 0; i < l; i += 3) { var i1 = indices[i ], i2 = indices[i + 1], i3 = indices[i + 2], in1 = i1 * 3, in2 = i2 * 3, in3 = i3 * 3, iu1 = i1 * 2, iu2 = i2 * 2, iu3 = i3 * 2, x1 = vertices[in1 ], y1 = vertices[in1 + 1], z1 = vertices[in1 + 2], theta1 = acos(z1 / sqrt(x1 * x1 + y1 * y1 + z1 * z1)), phi1 = atan2(y1, x1), v1 = theta1 / pi, u1 = 1 - phi1 / pi2, x2 = vertices[in2 ], y2 = vertices[in2 + 1], z2 = vertices[in2 + 2], theta2 = acos(z2 / sqrt(x2 * x2 + y2 * y2 + z2 * z2)), phi2 = atan2(y2, x2), v2 = theta2 / pi, u2 = 1 - phi2 / pi2, x3 = vertices[in3 ], y3 = vertices[in3 + 1], z3 = vertices[in3 + 2], theta3 = acos(z3 / sqrt(x3 * x3 + y3 * y3 + z3 * z3)), phi3 = atan2(y3, x3), v3 = theta3 / pi, u3 = 1 - phi3 / pi2, vec1 = { x: x3 - x2, y: y3 - y2, z: z3 - z2 }, vec2 = { x: x1 - x2, y: y1 - y2, z: z1 - z2 }, normal = Vec3.cross(vec1, vec2).$unit();
normals[in1 ] = normals[in2 ] = normals[in3 ] = normal.x; normals[in1 + 1] = normals[in2 + 1] = normals[in3 + 1] = normal.y; normals[in1 + 2] = normals[in2 + 2] = normals[in3 + 2] = normal.z; texCoords[iu1 ] = u1; texCoords[iu1 + 1] = v1; texCoords[iu2 ] = u2; texCoords[iu2 + 1] = v2; texCoords[iu3 ] = u3; texCoords[iu3 + 1] = v3; }
O3D.Model.call(this, $.extend({ vertices: vertices, indices: indices, normals: normals, texCoords: texCoords }, opt || {})); };
O3D.IcoSphere.prototype = Object.create(O3D.Model.prototype); O3D.TruncatedCone = function(config) { var bottomRadius = config.bottomRadius || 0, topRadius = config.topRadius || 0, height = config.height || 1, nradial = config.nradial || 10, nvertical = config.nvertical || 10, topCap = !!config.topCap, bottomCap = !!config.bottomCap, extra = (topCap? 2 : 0) + (bottomCap? 2 : 0), numVertices = (nradial + 1) * (nvertical + 1 + extra), vertices = new Float32Array(numVertices * 3), normals = new Float32Array(numVertices * 3), texCoords = new Float32Array(numVertices * 2), indices = new Uint16Array(nradial * (nvertical + extra) * 6), vertsAroundEdge = nradial + 1, math = Math, slant = math.atan2(bottomRadius - topRadius, height), msin = math.sin, mcos = math.cos, mpi = math.PI, cosSlant = mcos(slant), sinSlant = msin(slant), start = topCap? -2 : 0, end = nvertical + (bottomCap? 2 : 0), i3 = 0, i2 = 0;
for (var i = start; i <= end; i++) { var v = i / nvertical, y = height * v, ringRadius; if (i < 0) { y = 0; v = 1; ringRadius = bottomRadius; } else if (i > nvertical) { y = height; v = 1; ringRadius = topRadius; } else { ringRadius = bottomRadius + (topRadius - bottomRadius) * (i / nvertical); } if (i == -2 || i == nvertical + 2) { ringRadius = 0; v = 0; } y -= height / 2; for (var j = 0; j < vertsAroundEdge; j++) { var sin = msin(j * mpi * 2 / nradial); var cos = mcos(j * mpi * 2 / nradial); vertices[i3 + 0] = sin * ringRadius; vertices[i3 + 1] = y; vertices[i3 + 2] = cos * ringRadius; normals[i3 + 0] = (i < 0 || i > nvertical) ? 0 : (sin * cosSlant); normals[i3 + 1] = (i < 0) ? -1 : (i > nvertical ? 1 : sinSlant); normals[i3 + 2] = (i < 0 || i > nvertical) ? 0 : (cos * cosSlant);
texCoords[i2 + 0] = j / nradial; texCoords[i2 + 1] = v;
i2 += 2; i3 += 3; } }
for (i = 0; i < nvertical + extra; i++) { for (j = 0; j < nradial; j++) { var index = (i * nradial + j) * 6; indices[index + 0] = vertsAroundEdge * (i + 0) + 0 + j; indices[index + 1] = vertsAroundEdge * (i + 0) + 1 + j; indices[index + 2] = vertsAroundEdge * (i + 1) + 1 + j; indices[index + 3] = vertsAroundEdge * (i + 0) + 0 + j; indices[index + 4] = vertsAroundEdge * (i + 1) + 1 + j; indices[index + 5] = vertsAroundEdge * (i + 1) + 0 + j; } }
O3D.Model.call(this, $.extend({ vertices: vertices, normals: normals, texCoords: texCoords, indices: indices }, config || {})); }; O3D.TruncatedCone.prototype = Object.create(O3D.Model.prototype); O3D.Cone = function(config) { config.topRadius = 0; config.topCap = !!config.cap; config.bottomCap = !!config.cap; config.bottomRadius = config.radius || 3; O3D.TruncatedCone.call(this, config); };
O3D.Cone.prototype = Object.create(O3D.TruncatedCone.prototype);
O3D.Cylinder = function(config) { config.bottomRadius = config.radius; config.topRadius = config.radius; O3D.TruncatedCone.call(this, config); };
O3D.Cylinder.prototype = Object.create(O3D.TruncatedCone.prototype);
O3D.Plane = function(config) { var type = config.type, coords = type.split(','), c1len = config[coords[0] + 'len'], //width c2len = config[coords[1] + 'len'], //height subdivisions1 = config['n' + coords[0]] || 1, //subdivisionsWidth subdivisions2 = config['n' + coords[1]] || 1, //subdivisionsDepth offset = config.offset numVertices = (subdivisions1 + 1) * (subdivisions2 + 1), positions = new Float32Array(numVertices * 3), normals = new Float32Array(numVertices * 3), texCoords = new Float32Array(numVertices * 2), i2 = 0, i3 = 0;
for (var z = 0; z <= subdivisions2; z++) { for (var x = 0; x <= subdivisions1; x++) { var u = x / subdivisions1, v = z / subdivisions2; texCoords[i2 + 0] = u; texCoords[i2 + 1] = v; i2 += 2; switch (type) { case 'x,y': positions[i3 + 0] = c1len * u - c1len * 0.5; positions[i3 + 1] = c2len * v - c2len * 0.5; positions[i3 + 2] = offset;
normals[i3 + 0] = 0; normals[i3 + 1] = 0; normals[i3 + 2] = 1; break;
case 'x,z': positions[i3 + 0] = c1len * u - c1len * 0.5; positions[i3 + 1] = offset; positions[i3 + 2] = c2len * v - c2len * 0.5;
normals[i3 + 0] = 0; normals[i3 + 1] = 1; normals[i3 + 2] = 0; break;
case 'y,z': positions[i3 + 0] = offset; positions[i3 + 1] = c1len * u - c1len * 0.5; positions[i3 + 2] = c2len * v - c2len * 0.5;
normals[i3 + 0] = 1; normals[i3 + 1] = 0; normals[i3 + 2] = 0; break; } i3 += 3; } }
var numVertsAcross = subdivisions1 + 1, indices = [];
for (z = 0; z < subdivisions2; z++) { for (x = 0; x < subdivisions1; x++) { var index = (z * subdivisions1 + x) * 6; // Make triangle 1 of quad. indices[index + 0] = (z + 0) * numVertsAcross + x; indices[index + 1] = (z + 1) * numVertsAcross + x; indices[index + 2] = (z + 0) * numVertsAcross + x + 1;
// Make triangle 2 of quad. indices[index + 3] = (z + 1) * numVertsAcross + x; indices[index + 4] = (z + 1) * numVertsAcross + x + 1; indices[index + 5] = (z + 0) * numVertsAcross + x + 1; } }
O3D.Model.call(this, $.extend({ vertices: positions, normals: normals, texCoords: texCoords, indices: indices }, config));
};
O3D.Plane.prototype = Object.create(O3D.Model.prototype);
//unique id O3D.id = $.time();
//Assign to namespace PhiloGL.O3D = O3D;
})();
//shaders.js //Default Shaders
(function() {
//Add default shaders var Shaders = { Vertex: {}, Fragment: {} };
var VertexShaders = Shaders.Vertex, FragmentShaders = Shaders.Fragment;
VertexShaders.Default = [ "#define LIGHT_MAX 8", //object attributes "attribute vec3 position;", "attribute vec3 normal;", "attribute vec4 color;", "attribute vec4 pickingColor;", "attribute vec2 texCoord1;", //camera and object matrices "uniform mat4 viewMatrix;", "uniform mat4 viewInverseMatrix;", "uniform mat4 projectionMatrix;", "uniform mat4 viewProjectionMatrix;", //objectMatrix * viewMatrix = worldMatrix "uniform mat4 worldMatrix;", "uniform mat4 worldInverseMatrix;", "uniform mat4 worldInverseTransposeMatrix;", "uniform mat4 objectMatrix;", "uniform vec3 cameraPosition;", //lighting configuration "uniform bool enableLights;", "uniform vec3 ambientColor;", "uniform vec3 directionalColor;", "uniform vec3 lightingDirection;", //point lights configuration "uniform vec3 pointLocation[LIGHT_MAX];", "uniform vec3 pointColor[LIGHT_MAX];", "uniform int numberPoints;", //reflection / refraction configuration
"uniform bool useReflection;",
//varyings
"varying vec3 vReflection;",
"varying vec4 vColor;", "varying vec4 vPickingColor;", "varying vec2 vTexCoord;", "varying vec4 vNormal;", "varying vec3 lightWeighting;",
"void main(void) {", "vec4 mvPosition = worldMatrix * vec4(position, 1.0);", "vec4 transformedNormal = worldInverseTransposeMatrix * vec4(normal, 1.0);", //lighting code "if(!enableLights) {", "lightWeighting = vec3(1.0, 1.0, 1.0);", "} else {", "vec3 plightDirection;", "vec3 pointWeight = vec3(0.0, 0.0, 0.0);", "float directionalLightWeighting = max(dot(transformedNormal.xyz, lightingDirection), 0.0);", "for (int i = 0; i < LIGHT_MAX; i++) {", "if (i < numberPoints) {", "plightDirection = normalize((viewMatrix * vec4(pointLocation[i], 1.0)).xyz - mvPosition.xyz);", "pointWeight += max(dot(transformedNormal.xyz, plightDirection), 0.0) * pointColor[i];", "} else {", "break;", "}", "}",
"lightWeighting = ambientColor + (directionalColor * directionalLightWeighting) + pointWeight;", "}", //refraction / reflection code "if (useReflection) {", "vReflection = (viewInverseMatrix[3] - (worldMatrix * vec4(position, 1.0))).xyz;", "} else {", "vReflection = vec3(1.0, 1.0, 1.0);", "}", //pass results to varyings "vColor = color;", "vPickingColor = pickingColor;", "vTexCoord = texCoord1;", "vNormal = transformedNormal;", "gl_Position = projectionMatrix * worldMatrix * vec4(position, 1.0);", "}" ].join("\n");
FragmentShaders.Default = [
"#ifdef GL_ES", "precision highp float;", "#endif", //varyings "varying vec4 vColor;", "varying vec4 vPickingColor;", "varying vec2 vTexCoord;", "varying vec3 lightWeighting;", "varying vec3 vReflection;", "varying vec4 vNormal;", //texture configs "uniform bool hasTexture1;", "uniform sampler2D sampler1;", "uniform bool hasTextureCube1;",
"uniform samplerCube samplerCube1;",
//picking configs "uniform bool enablePicking;", "uniform bool hasPickingColors;", "uniform vec3 pickColor;",
//reflection / refraction configs "uniform float reflection;", "uniform float refraction;",
//fog configuration "uniform bool hasFog;", "uniform vec3 fogColor;", "uniform float fogNear;", "uniform float fogFar;",
"void main(){", //set color from texture "if (!hasTexture1) {", "gl_FragColor = vec4(vColor.rgb * lightWeighting, vColor.a);", "} else {", "gl_FragColor = vec4(texture2D(sampler1, vec2(vTexCoord.s, vTexCoord.t)).rgb * lightWeighting, 1.0);", "}", //has cube texture then apply reflection "if (hasTextureCube1) {", "vec3 nReflection = normalize(vReflection);", "vec3 reflectionValue;", "if (refraction > 0.0) {", "reflectionValue = refract(nReflection, vNormal.xyz, refraction);", "} else {", "reflectionValue = -reflect(nReflection, vNormal.xyz);", "}", //TODO(nico): check whether this is right. "vec4 cubeColor = textureCube(samplerCube1, vec3(-reflectionValue.x, -reflectionValue.y, reflectionValue.z));", "gl_FragColor = vec4(mix(gl_FragColor.xyz, cubeColor.xyz, reflection), 1.0);", "}", //set picking "if (enablePicking) {", "if (hasPickingColors) {", "gl_FragColor = vPickingColor;", "} else {", "gl_FragColor = vec4(pickColor, 1.0);", "}", "}", //handle fog "if (hasFog) {", "float depth = gl_FragCoord.z / gl_FragCoord.w;", "float fogFactor = smoothstep(fogNear, fogFar, depth);", "gl_FragColor = mix(gl_FragColor, vec4(fogColor, gl_FragColor.w), fogFactor);", "}", "}"
].join("\n");
PhiloGL.Shaders = Shaders;
})();
//scene.js //Scene Object management and rendering
(function () {
//Define some locals var Vec3 = PhiloGL.Vec3, Mat4 = PhiloGL.Mat4;
//Scene class var Scene = function(program, camera, opt) { opt = $.merge({ lights: { enable: false, //ambient light ambient: { r: 0.2, g: 0.2, b: 0.2 }, //directional light directional: { direction: { x: 1, y: 1, z: 1 }, color: { r: 0, g: 0, b: 0 } } //point light //points: [] }, effects: { fog: false // { near, far, color } } }, opt || {}); this.program = opt.program ? program[opt.program] : program; this.camera = camera; this.models = []; this.config = opt; };
Scene.prototype = { add: function() { for (var i = 0, models = this.models, l = arguments.length; i < l; i++) { var model = arguments[i]; //Generate unique id for model model.id = model.id || $.uid(); models.push(model); //Create and load Buffers this.defineBuffers(model); } },
remove: function(model) { var models = this.models, indexOf = models.indexOf(model);
if (indexOf > -1) { models.splice(indexOf, 1); } },
getProgram: function(obj) { var program = this.program; if (program.$$family != 'program' && obj && obj.program) { program = program[obj.program]; program.use(); return program; } return program; },
defineBuffers: function(obj) { var program = this.getProgram(obj); obj.setAttributes(program, true); obj.unsetAttributes(program); obj.setVertices(program, true); obj.unsetVertices(program); obj.setColors(program, true); obj.unsetColors(program); obj.setPickingColors(program, true); obj.unsetPickingColors(program); obj.setNormals(program, true); obj.unsetNormals(program); //obj.setTextures(program, true); obj.setTexCoords(program, true); obj.unsetTexCoords(program); obj.setIndices(program, true); obj.unsetIndices(program); },
beforeRender: function(program) { //Setup lighting and scene effects like fog, etc. this.setupLighting(program); this.setupEffects(program); //Set Camera view and projection matrix var camera = this.camera, pos = camera.position, view = camera.view, projection = camera.projection, viewProjection = view.mulMat4(projection), viewProjectionInverse = viewProjection.invert();
program.setUniforms({ cameraPosition: [pos.x, pos.y, pos.z], projectionMatrix: projection, viewMatrix: view, viewProjectionMatrix: viewProjection, viewInverseMatrix: view.invert(), viewProjectionInverseMatrix: viewProjectionInverse }); },
//Setup the lighting system: ambient, directional, point lights. setupLighting: function(program) { //Setup Lighting var abs = Math.abs, camera = this.camera, cpos = camera.position, light = this.config.lights, ambient = light.ambient, directional = light.directional, dcolor = directional.color, dir = directional.direction, enable = light.enable, points = light.points && $.splat(light.points) || [], numberPoints = points.length, pointLocations = [], pointColors = [], enableSpecular = [], pointSpecularColors = []; //Normalize lighting direction vector dir = new Vec3(dir.x, dir.y, dir.z).$unit().$scale(-1); //Set light uniforms. Ambient and directional lights. program.setUniform('enableLights', enable);
if (!enable) return;
program.setUniform('ambientColor', [ambient.r, ambient.g, ambient.b]); program.setUniform('directionalColor', [dcolor.r, dcolor.g, dcolor.b]); program.setUniform('lightingDirection', [dir.x, dir.y, dir.z]); //Set point lights program.setUniform('numberPoints', numberPoints); for (var i = 0, l = numberPoints; i < l; i++) { var point = points[i], position = point.position, color = point.color || point.diffuse, spec = point.specular; pointLocations.push(position.x, position.y, position.z); pointColors.push(color.r, color.g, color.b); //Add specular color enableSpecular.push(+!!spec); if (spec) { pointSpecularColors.push(spec.r, spec.g, spec.b); } else { pointSpecularColors.push(0, 0, 0); } }
if (pointLocations.length) {
program.setUniforms({ 'pointLocation': pointLocations, 'pointColor': pointColors }); program.setUniforms({ 'enableSpecular': enableSpecular, 'pointSpecularColor': pointSpecularColors });
}
},
//Setup effects like fog, etc. setupEffects: function(program) { var config = this.config.effects, fog = config.fog, color = fog.color || { r: 0.5, g: 0.5, b: 0.5 };
if (fog) { program.setUniforms({ 'hasFog': true, 'fogNear': fog.near, 'fogFar': fog.far, 'fogColor': [color.r, color.g, color.b] }); } else { program.setUniform('hasFog', false); } },
//Renders all objects in the scene. render: function(opt) { opt = opt || {}; var camera = this.camera, program = this.program, renderProgram = opt.renderProgram, pType = $.type(program), multiplePrograms = !renderProgram && pType == 'object', options = $.merge({ onBeforeRender: $.empty, onAfterRender: $.empty }, opt || {});
//If we're just using one program then //execute the beforeRender method once. !multiplePrograms && this.beforeRender(renderProgram || program); //Go through each model and render it. for (var i = 0, models = this.models, l = models.length; i < l; ++i) { var elem = models[i]; if (elem.display) { var program = renderProgram || this.getProgram(elem); //Setup the beforeRender method for each object //when there are multiple programs to be used. multiplePrograms && this.beforeRender(program); elem.onBeforeRender(program, camera); options.onBeforeRender(elem, i); this.renderObject(elem, program); options.onAfterRender(elem, i); elem.onAfterRender(program, camera); } } },
renderToTexture: function(name, opt) { opt = opt || {}; var texture = app.textures[name + '-texture'], texMemo = app.textureMemo[name + '-texture']; this.render(opt);
gl.bindTexture(texMemo.textureType, texture); gl.generateMipmap(texMemo.textureType); gl.bindTexture(texMemo.textureType, null); },
renderObject: function(obj, program) { var camera = this.camera, view = camera.view, projection = camera.projection, object = obj.matrix, world = view.mulMat4(object), worldInverse = world.invert(), worldInverseTranspose = worldInverse.transpose();
obj.setUniforms(program); obj.setAttributes(program); obj.setShininess(program); obj.setReflection(program); obj.setVertices(program); obj.setColors(program); obj.setPickingColors(program); obj.setNormals(program); obj.setTextures(program); obj.setTexCoords(program); obj.setIndices(program);
//Now set view and normal matrices program.setUniforms({ objectMatrix: object, worldMatrix: world, worldInverseMatrix: worldInverse, worldInverseTransposeMatrix: worldInverseTranspose
// worldViewProjection: view.mulMat4(object).$mulMat4(view.mulMat4(projection))
}); //Draw //TODO(nico): move this into O3D, but, somehow, abstract the gl.draw* methods inside that object. if (obj.render) { obj.render(gl, program, camera); } else { if (obj.indices) { gl.drawElements((obj.drawType !== undefined) ? gl.get(obj.drawType) : gl.TRIANGLES, obj.indices.length, gl.UNSIGNED_SHORT, 0); } else { gl.drawArrays((obj.drawType !== undefined) ? gl.get(obj.drawType) : gl.TRIANGLES, 0, obj.vertices.length / 3); } } obj.unsetAttributes(program); obj.unsetVertices(program); obj.unsetColors(program); obj.unsetPickingColors(program); obj.unsetNormals(program); obj.unsetTexCoords(program); obj.unsetIndices(program); }, //setup picking framebuffer setupPicking: function() { //create picking program var program = PhiloGL.Program.fromDefaultShaders(); //create framebuffer app.setFrameBuffer('$picking', { width: 1, height: 1, bindToTexture: { parameters: [{ name: 'TEXTURE_MAG_FILTER', value: 'LINEAR' }, { name: 'TEXTURE_MIN_FILTER', value: 'LINEAR', generateMipmap: false }] }, bindToRenderBuffer: true }); app.setFrameBuffer('$picking', false); this.pickingProgram = program; }, //returns an element at the given position pick: function(x, y) { if (!this.pickingProgram) { this.setupPicking(); }
var o3dHash = {}, o3dList = [], program = app.usedProgram, pickingProgram = this.pickingProgram, camera = this.camera, config = this.config, memoLightEnable = config.lights.enable, memoFog = config.effects.fog, width = gl.canvas.width, height = gl.canvas.height, hash = [], pixel = new Uint8Array(1 * 1 * 4), index = 0, backgroundColor;
//setup the scene for picking config.lights.enable = false; config.effects.fog = false; //enable picking and render to texture pickingProgram.use(); app.setFrameBuffer('$picking', true); pickingProgram.setUniform('enablePicking', true); //render the scene to a texture gl.disable(gl.BLEND); gl.viewport(-x, y - height, width, height); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); //read the background color so we don't step on it gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel); backgroundColor = pixel[0] + pixel[1] * 256 + pixel[2] * 256 * 256;
//render to texture this.renderToTexture('$picking', { renderProgram: pickingProgram, onBeforeRender: function(elem, i) { if (i == backgroundColor) { index = 1; } var suc = i + index, hasPickingColors = !!elem.pickingColors;
pickingProgram.setUniform('hasPickingColors', hasPickingColors);
if (!hasPickingColors) { hash[0] = suc % 256; hash[1] = ((suc / 256) >> 0) % 256; hash[2] = ((suc / (256 * 256)) >> 0) % 256; pickingProgram.setUniform('pickColor', [hash[0] / 255, hash[1] / 255, hash[2] / 255]); o3dHash[hash.join()] = elem; } else { o3dList.push(elem); } } }); //grab the color of the pointed pixel in the texture gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel); var stringColor = [pixel[0], pixel[1], pixel[2]].join(), elem = o3dHash[stringColor], pick;
if (!elem) { for (var i = 0, l = o3dList.length; i < l; i++) { elem = o3dList[i]; pick = elem.pick(pixel); if (pick !== false) { elem.$pickingIndex = pick; } else { elem = false; } } }
//restore all values and unbind buffers app.setFrameBuffer('$picking', false); pickingProgram.setUniform('enablePicking', false); config.lights.enable = memoLightEnable; config.effects.fog = memoFog; //If there was another program then set to reuse that program. if (program) program.use(); return elem && elem.pickable && elem; } }; Scene.MAX_TEXTURES = 10; Scene.MAX_POINT_LIGHTS = 50;
PhiloGL.Scene = Scene;
})();
//workers.js //
(function () {
function WorkerGroup(fileName, n) { var workers = this.workers = []; while (n--) { workers.push(new Worker(fileName)); } }
WorkerGroup.prototype = { map: function(callback) { var workers = this.workers; var configs = this.configs = [];
for (var i = 0, l = workers.length; i < l; i++) { configs.push(callback && callback(i)); }
return this; },
reduce: function(opt) { var fn = opt.reduceFn, workers = this.workers, configs = this.configs, l = workers.length, acum = opt.initialValue, message = function (e) { l--; if (acum === undefined) { acum = e.data; } else { acum = fn(acum, e.data); } if (l == 0) { opt.onComplete(acum); } }; for (var i = 0, ln = l; i < ln; i++) { var w = workers[i]; w.onmessage = message; w.postMessage(configs[i]); }
return this; } };
PhiloGL.WorkerGroup = WorkerGroup;
})();
(function() {
//Timer based animation var Fx = function(options) { this.opt = $.merge({ duration: 1000, transition: function(x) { return x; }, onCompute: $.empty, onComplete: $.empty }, options || {}); };
Fx.prototype = { timer:null, time:null, start: function(options) { this.opt = $.merge(this.opt, options || {}); this.time = $.time(); this.animating = true; },
//perform a step in the animation step: function() { if (!this.animating) return; var currentTime = $.time(), time = this.time, opt = this.opt; if(currentTime < time + opt.duration) { var delta = opt.transition((currentTime - time) / opt.duration); opt.onCompute.call(this, delta); } else { this.animating = false; opt.onCompute.call(this, 1); opt.onComplete.call(this); } } }; Fx.compute = function(from, to, delta) { return from + (to - from) * delta; };
//Easing equations Fx.Transition = { linear: function(p){ return p; } };
var Trans = Fx.Transition;
(function(){
var makeTrans = function(transition, params){ params = $.splat(params); return $.extend(transition, { easeIn: function(pos){ return transition(pos, params); }, easeOut: function(pos){ return 1 - transition(1 - pos, params); }, easeInOut: function(pos){ return (pos <= 0.5)? transition(2 * pos, params) / 2 : (2 - transition( 2 * (1 - pos), params)) / 2; } }); };
var transitions = {
Pow: function(p, x){ return Math.pow(p, x[0] || 6); },
Expo: function(p){ return Math.pow(2, 8 * (p - 1)); },
Circ: function(p){ return 1 - Math.sin(Math.acos(p)); },
Sine: function(p){ return 1 - Math.sin((1 - p) * Math.PI / 2); },
Back: function(p, x){ x = x[0] || 1.618; return Math.pow(p, 2) * ((x + 1) * p - x); },
Bounce: function(p){ var value; for ( var a = 0, b = 1; 1; a += b, b /= 2) { if (p >= (7 - 4 * a) / 11) { value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2); break; } } return value; },
Elastic: function(p, x){ return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3); }
};
for (var t in transitions) { Trans[t] = makeTrans(transitions[t]); }
['Quad', 'Cubic', 'Quart', 'Quint'].forEach(function(elem, i){ Trans[elem] = makeTrans(function(p){ return Math.pow(p, [ i + 2 ]); }); });
})();
//animationTime - function branching var global = self || window; if (global) { var found = false; ['webkitAnimationTime', 'mozAnimationTime', 'animationTime', 'webkitAnimationStartTime', 'mozAnimationStartTime', 'animationStartTime'].forEach(function(impl) { if (impl in global) { Fx.animationTime = function() { return global[impl]; }; found = true; } }); if (!found) { Fx.animationTime = $.time; } //requestAnimationFrame - function branching found = false; ['webkitRequestAnimationFrame', 'mozRequestAnimationFrame', 'requestAnimationFrame'].forEach(function(impl) { if (impl in global) { Fx.requestAnimationFrame = function(callback) { global[impl](function() { callback(); }); }; found = true; } }); if (!found) { Fx.requestAnimationFrame = function(callback) { setTimeout(function() { callback(); }, 1000 / 60); }; } }
PhiloGL.Fx = Fx;
})();
//media.js //media has utility functions for image, video and audio manipulation (and //maybe others like device, etc). (function() {
var Media = {};
var Image = function() {}; //post process an image by setting it to a texture with a specified fragment //and vertex shader. Image.postProcess = function(opt) { var program = app.program[opt.program], textures = Array.isArray(opt.fromTexture) ? opt.fromTexture : [opt.fromTexture], framebuffer = opt.toFrameBuffer, screen = !!opt.toScreen, width = opt.width || app.canvas.width, height = opt.height || app.canvas.height, plane = new PhiloGL.O3D.Plane({ type: 'x,y', xlen: 1, ylen: 1, offset: 0, textures: textures, program: opt.program }), camera = new PhiloGL.Camera(45, 1, 0.1, 100, { position: { x: 0, y: 0, z: 1 } }), scene = new PhiloGL.Scene(program, camera);
camera.update();
if (framebuffer) { //create framebuffer if (!(framebuffer in app.frameBufferMemo)) { app.setFrameBuffer(framebuffer, { width: width, height: height, bindToTexture: { parameters: [{ name: 'TEXTURE_MAG_FILTER', value: 'LINEAR' }, { name: 'TEXTURE_MIN_FILTER', value: 'LINEAR', generateMipmap: false }] }, bindToRenderBuffer: true }); } program.use(); app.setFrameBuffer(framebuffer, true); gl.viewport(0, 0, width, height); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); scene.add(plane); program.setUniforms(opt.uniforms || {}); scene.renderToTexture(framebuffer); app.setFrameBuffer(framebuffer, false); } else if (screen) { program.use(); gl.viewport(0, 0, width, height); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); scene.add(plane); program.setUniforms(opt.uniforms || {}); scene.render(); }
return this; };
Media.Image = Image; PhiloGL.Media = Media;
})();
})();