Last active
August 29, 2015 14:27
-
-
Save mathdoodle/6c21aa755344be42b359 to your computer and use it in GitHub Desktop.
FUNDAMENTALS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"uuid": "1e5f0d4d-0c0c-47d8-b04a-c079502eb408", | |
"description": "FUNDAMENTALS", | |
"dependencies": { | |
"DomReady": "latest" | |
}, | |
"operatorOverloading": true | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//====================================== | |
// webgl utils | |
//====================================== | |
var topWindow = this; | |
/** | |
* The specification used for setting an attribute. | |
*/ | |
interface AttribSpec { | |
buffer: WebGLBuffer; | |
numComponents: number; | |
size?: number; | |
type?: number; | |
normalize?: boolean; | |
stride?: number; | |
offset?: number; | |
}; | |
/** @module webgl-utils */ | |
/** | |
* Wrapped logging function. | |
* @param {string} msg The message to log. | |
*/ | |
function error(msg) { | |
if (topWindow.console) { | |
if (topWindow.console.error) { | |
topWindow.console.error(msg); | |
} else if (topWindow.console.log) { | |
topWindow.console.log(msg); | |
} | |
} | |
} | |
/** | |
* Check if the page is embedded. | |
* @param {Window?) w window to check | |
* @return {boolean} True of we are in an iframe | |
*/ | |
function isInIFrame(w?: Window): boolean { | |
w = w || topWindow; | |
return w !== w.top; | |
} | |
/** | |
* Creates the HTLM for a failure message | |
* @param {string} canvasContainerId id of container of th | |
* canvas. | |
* @return {string} The html. | |
*/ | |
function makeFailHTML(msg) { | |
return '' + | |
'<table style="background-color: #8CE; width: 100%; height: 100%;"><tr>' + | |
'<td align="center">' + | |
'<div style="display: table-cell; vertical-align: middle;">' + | |
'<div style="">' + msg + '</div>' + | |
'</div>' + | |
'</td></tr></table>'; | |
} | |
/** | |
* Mesasge for getting a webgl browser | |
* @type {string} | |
*/ | |
var GET_A_WEBGL_BROWSER = '' + | |
'This page requires a browser that supports WebGL.<br/>' + | |
'<a href="http://get.webgl.org">Click here to upgrade your browser.</a>'; | |
/** | |
* Mesasge for need better hardware | |
* @type {string} | |
*/ | |
var OTHER_PROBLEM = '' + | |
"It doesn't appear your computer can support WebGL.<br/>" + | |
'<a href="http://get.webgl.org/troubleshooting/">Click here for more information.</a>'; | |
/** | |
* Creates a webgl context. | |
* @param {HTMLCanvasElement} canvas The canvas tag to get | |
* context from. If one is not passed in one will be | |
* created. | |
* @return {WebGLRenderingContext} The created context. | |
*/ | |
function create3DContext(canvas: HTMLCanvasElement, opt_attribs): WebGLRenderingContext { | |
var names = ["webgl", "experimental-webgl"]; | |
var context: WebGLRenderingContext = null; | |
for (var ii = 0; ii < names.length; ++ii) { | |
try { | |
context = canvas.getContext(names[ii], opt_attribs); | |
} | |
catch(e) {} // eslint-disable-line | |
if (context) { | |
break; | |
} | |
} | |
return context; | |
} | |
/** | |
* Creates a webgl context. If creation fails it will | |
* change the contents of the container of the <canvas> | |
* tag to an error message with the correct links for WebGL. | |
* @param {HTMLCanvasElement} canvas. The canvas element to | |
* create a context from. | |
* @param {WebGLContextCreationAttirbutes} opt_attribs Any | |
* creation attributes you want to pass in. | |
* @return {WebGLRenderingContext} The created context. | |
* @memberOf module:webgl-utils | |
*/ | |
function setupWebGL(canvas: HTMLCanvasElement, opt_attribs): WebGLRenderingContext { | |
function showLink(str) { | |
var container = <HTMLElement>canvas.parentNode; | |
if (container) { | |
container.innerHTML = makeFailHTML(str); | |
} | |
} | |
if (!topWindow.WebGLRenderingContext) { | |
showLink(GET_A_WEBGL_BROWSER); | |
return null; | |
} | |
var context = create3DContext(canvas, opt_attribs); | |
if (!context) { | |
showLink(OTHER_PROBLEM); | |
} | |
return context; | |
} | |
function updateCSSIfInIFrame() { | |
if (isInIFrame()) { | |
document.body.className = "iframe"; | |
} | |
} | |
/** | |
* @typedef {Object} GetWebGLContextOptions | |
* @property {boolean} [dontResize] by default `getWebGLContext` will resize the canvas to match the size it's displayed. | |
* @property {boolean} [noTitle] by default inserts a copy of the `<title>` content into the page | |
* @memberOf module:webgl-utils | |
*/ | |
/** | |
* Gets a WebGL context. | |
* makes its backing store the size it is displayed. | |
* @param {HTMLCanvasElement} canvas a canvas element. | |
* @param {WebGLContextCreationAttirbutes} [opt_attribs] optional webgl context creation attributes | |
* @param {module:webgl-utils.GetWebGLContextOptions} [opt_options] options | |
* @memberOf module:webgl-utils | |
*/ | |
function getWebGLContext(canvas: HTMLCanvasElement, opt_attribs?: WebGLContextAttributes, opt_options?:{dontResize: boolean}): WebGLRenderingContext { | |
var options:{dontResize?: boolean; resize?: boolean; noTitle?: boolean; title?:boolean} = opt_options || {}; | |
if (isInIFrame()) { | |
updateCSSIfInIFrame(); | |
// make the canvas backing store the size it's displayed. | |
if (!options.dontResize && options.resize !== false) { | |
var width = canvas.clientWidth; | |
var height = canvas.clientHeight; | |
canvas.width = width; | |
canvas.height = height; | |
} | |
} else if (!options.noTitle && options.title !== false) { | |
var title = document.title; | |
var h1 = document.createElement("h1"); | |
h1.innerText = title; | |
document.body.insertBefore(h1, document.body.children[0]); | |
} | |
var gl = setupWebGL(canvas, opt_attribs); | |
return gl; | |
} | |
/** | |
* Error Callback | |
* @callback ErrorCallback | |
* @param {string} msg error message. | |
* @memberOf module:webgl-utils | |
*/ | |
/** | |
* Loads a shader. | |
* @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. | |
* @param {string} shaderSource The shader source. | |
* @param {number} shaderType The type of shader. | |
* @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. | |
* @return {WebGLShader} The created shader. | |
*/ | |
function loadShader(gl, shaderSource, shaderType, opt_errorCallback) { | |
var errFn = opt_errorCallback || error; | |
// Create the shader object | |
var shader = gl.createShader(shaderType); | |
// Load the shader source | |
gl.shaderSource(shader, shaderSource); | |
// Compile the shader | |
gl.compileShader(shader); | |
// Check the compile status | |
var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); | |
if (!compiled) { | |
// Something went wrong during compilation; get the error | |
var lastError = gl.getShaderInfoLog(shader); | |
errFn("*** Error compiling shader '" + shader + "':" + lastError); | |
gl.deleteShader(shader); | |
return null; | |
} | |
return shader; | |
} | |
/** | |
* Creates a program, attaches shaders, binds attrib locations, links the | |
* program and calls useProgram. | |
* @param {WebGLShader[]} shaders The shaders to attach | |
* @param {string[]} [opt_attribs] An array of attribs names. Locations will be assigned by index if not passed in | |
* @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations. | |
* @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. By default it just prints an error to the console | |
* on error. If you want something else pass an callback. It's passed an error message. | |
* @memberOf module:webgl-utils | |
*/ | |
function createProgram(gl: WebGLRenderingContext, shaders: WebGLShader[], opt_attribs?: string[], opt_locations?: number[], opt_errorCallback?) { | |
var errFn = opt_errorCallback || error; | |
var program = gl.createProgram(); | |
shaders.forEach(function(shader) { | |
gl.attachShader(program, shader); | |
}); | |
if (opt_attribs) { | |
opt_attribs.forEach(function(attrib: string, ndx: number) { | |
gl.bindAttribLocation( | |
program, | |
opt_locations ? opt_locations[ndx] : ndx, | |
attrib); | |
}); | |
} | |
gl.linkProgram(program); | |
// Check the link status | |
var linked = gl.getProgramParameter(program, gl.LINK_STATUS); | |
if (!linked) { | |
// something went wrong with the link | |
var lastError = gl.getProgramInfoLog(program); | |
errFn("Error in program linking:" + lastError); | |
gl.deleteProgram(program); | |
return null; | |
} | |
return program; | |
} | |
/** | |
* Loads a shader from a script tag. | |
* @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. | |
* @param {string} scriptId The id of the script tag. | |
* @param {number} opt_shaderType The type of shader. If not passed in it will | |
* be derived from the type of the script tag. | |
* @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. | |
* @return {WebGLShader} The created shader. | |
*/ | |
function createShaderFromScript( | |
gl, scriptId, opt_shaderType, opt_errorCallback) { | |
var shaderSource = ""; | |
var shaderType; | |
var shaderScript = <HTMLScriptElement>document.getElementById(scriptId); | |
if (!shaderScript) { | |
throw ("*** Error: unknown script element" + scriptId); | |
} | |
shaderSource = shaderScript.text; | |
if (!opt_shaderType) { | |
if (shaderScript.type === "x-shader/x-vertex") { | |
shaderType = gl.VERTEX_SHADER; | |
} else if (shaderScript.type === "x-shader/x-fragment") { | |
shaderType = gl.FRAGMENT_SHADER; | |
} else if (shaderType !== gl.VERTEX_SHADER && shaderType !== gl.FRAGMENT_SHADER) { | |
throw ("*** Error: unknown shader type"); | |
} | |
} | |
return loadShader( | |
gl, shaderSource, opt_shaderType ? opt_shaderType : shaderType, | |
opt_errorCallback); | |
} | |
var defaultShaderType = [ | |
"VERTEX_SHADER", | |
"FRAGMENT_SHADER", | |
]; | |
/** | |
* Creates a program from 2 script tags. | |
* | |
* @param {WebGLRenderingContext} gl The WebGLRenderingContext | |
* to use. | |
* @param {string[]} shaderScriptIds Array of ids of the script | |
* tags for the shaders. The first is assumed to be the | |
* vertex shader, the second the fragment shader. | |
* @param {string[]} [opt_attribs] An array of attribs names. Locations will be assigned by index if not passed in | |
* @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations. | |
* @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. By default it just prints an error to the console | |
* on error. If you want something else pass an callback. It's passed an error message. | |
* @return {WebGLProgram} The created program. | |
* @memberOf module:webgl-utils | |
*/ | |
function createProgramFromScripts(gl: WebGLRenderingContext, shaderScriptIds: string[], opt_attribs?, opt_locations?, opt_errorCallback?) { | |
var shaders = []; | |
for (var ii = 0; ii < shaderScriptIds.length; ++ii) { | |
shaders.push(createShaderFromScript( | |
gl, shaderScriptIds[ii], gl[defaultShaderType[ii]], opt_errorCallback)); | |
} | |
return createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback); | |
} | |
/** | |
* Creates a program from 2 sources. | |
* | |
* @param {WebGLRenderingContext} gl The WebGLRenderingContext | |
* to use. | |
* @param {string[]} shaderSourcess Array of sources for the | |
* shaders. The first is assumed to be the vertex shader, | |
* the second the fragment shader. | |
* @param {string[]} [opt_attribs] An array of attribs names. Locations will be assigned by index if not passed in | |
* @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations. | |
* @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. By default it just prints an error to the console | |
* on error. If you want something else pass an callback. It's passed an error message. | |
* @return {WebGLProgram} The created program. | |
* @memberOf module:webgl-utils | |
*/ | |
function createProgramFromSources( | |
gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback) { | |
var shaders = []; | |
for (var ii = 0; ii < shaderSources.length; ++ii) { | |
shaders.push(loadShader( | |
gl, shaderSources[ii], gl[defaultShaderType[ii]], opt_errorCallback)); | |
} | |
return createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback); | |
} | |
/** | |
* Returns the corresponding bind point for a given sampler type | |
*/ | |
function getBindPointForSamplerType(gl, type) { | |
if (type === gl.SAMPLER_2D) return gl.TEXTURE_2D; // eslint-disable-line | |
if (type === gl.SAMPLER_CUBE) return gl.TEXTURE_CUBE_MAP; // eslint-disable-line | |
} | |
/** | |
* @typedef {Object.<string, function>} Setters | |
*/ | |
/** | |
* Creates setter functions for all uniforms of a shader | |
* program. | |
* | |
* @see {@link module:webgl-utils.setUniforms} | |
* | |
* @param {WebGLProgram} program the program to create setters for. | |
* @returns {Object.<string, function>} an object with a setter by name for each uniform | |
* @memberOf module:webgl-utils | |
*/ | |
function createUniformSetters(gl: WebGLRenderingContext, program: WebGLProgram):{[name:string]:(v)=>void} { | |
var textureUnit = 0; | |
/** | |
* Creates a setter for a uniform of the given program with it's | |
* location embedded in the setter. | |
* @param {WebGLProgram} program | |
* @param {WebGLUniformInfo} uniformInfo | |
* @returns {function} the created setter. | |
*/ | |
function createUniformSetter(program: WebGLProgram, uniformInfo: WebGLActiveInfo): (v)=>void { | |
var location: WebGLUniformLocation = gl.getUniformLocation(program, uniformInfo.name); | |
var type = uniformInfo.type; | |
// Check if this uniform is an array | |
var isArray = (uniformInfo.size > 1 && uniformInfo.name.substr(-3) === "[0]"); | |
if (type === gl.FLOAT && isArray) { | |
return function(v: number[]) { | |
gl.uniform1fv(location, v); | |
}; | |
} | |
if (type === gl.FLOAT) { | |
return function(x: number) { | |
gl.uniform1f(location, x); | |
}; | |
} | |
if (type === gl.FLOAT_VEC2) { | |
return function(v: number[]) { | |
gl.uniform2fv(location, v); | |
}; | |
} | |
if (type === gl.FLOAT_VEC3) { | |
return function(v: number[]) { | |
gl.uniform3fv(location, v); | |
}; | |
} | |
if (type === gl.FLOAT_VEC4) { | |
return function(v: Float32Array) { | |
gl.uniform4fv(location, v); | |
}; | |
} | |
if (type === gl.INT && isArray) { | |
return function(v) { | |
gl.uniform1iv(location, v); | |
}; | |
} | |
if (type === gl.INT) { | |
return function(v: number) { | |
gl.uniform1i(location, v); | |
}; | |
} | |
if (type === gl.INT_VEC2) { | |
return function(v: Int32Array) { | |
gl.uniform2iv(location, v); | |
}; | |
} | |
if (type === gl.INT_VEC3) { | |
return function(v: number[]) { | |
gl.uniform3iv(location, v); | |
}; | |
} | |
if (type === gl.INT_VEC4) { | |
return function(v: number[]) { | |
gl.uniform4iv(location, v); | |
}; | |
} | |
if (type === gl.BOOL) { | |
return function(v) { | |
gl.uniform1iv(location, v); | |
}; | |
} | |
if (type === gl.BOOL_VEC2) { | |
return function(v) { | |
gl.uniform2iv(location, v); | |
}; | |
} | |
if (type === gl.BOOL_VEC3) { | |
return function(v) { | |
gl.uniform3iv(location, v); | |
}; | |
} | |
if (type === gl.BOOL_VEC4) { | |
return function(v) { | |
gl.uniform4iv(location, v); } | |
; | |
} | |
if (type === gl.FLOAT_MAT2) { | |
return function(v: number[]) { | |
gl.uniformMatrix2fv(location, false, v); | |
}; | |
} | |
if (type === gl.FLOAT_MAT3) { | |
return function(v) { | |
gl.uniformMatrix3fv(location, false, v); | |
}; | |
} | |
if (type === gl.FLOAT_MAT4) { | |
return function(v) { | |
gl.uniformMatrix4fv(location, false, v); | |
}; | |
} | |
if ((type === gl.SAMPLER_2D || type === gl.SAMPLER_CUBE) && isArray) { | |
var units = []; | |
for (var ii = 0; ii < uniformInfo.size; ++ii) { // BUG fixed info | |
units.push(textureUnit++); | |
} | |
return function(bindPoint, units) { | |
return function(textures) { | |
gl.uniform1iv(location, units); | |
textures.forEach(function(texture, index) { | |
gl.activeTexture(gl.TEXTURE0 + units[index]); | |
gl.bindTexture(bindPoint, texture); // BUG fixed tetxure | |
}); | |
}; | |
}(getBindPointForSamplerType(gl, type), units); | |
} | |
if (type === gl.SAMPLER_2D || type === gl.SAMPLER_CUBE) { | |
return function(bindPoint, unit) { | |
return function(texture) { | |
gl.uniform1i(location, unit); | |
gl.activeTexture(gl.TEXTURE0 + unit); | |
gl.bindTexture(bindPoint, texture); | |
}; | |
}(getBindPointForSamplerType(gl, type), textureUnit++); | |
} | |
throw ("unknown type: 0x" + type.toString(16)); // we should never get here. | |
} | |
var uniformSetters:{[name:string]:(v:number[])=>void} = { }; | |
var numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); | |
for (var ii = 0; ii < numUniforms; ++ii) { | |
var uniformInfo: WebGLActiveInfo = gl.getActiveUniform(program, ii); | |
if (!uniformInfo) { | |
break; | |
} | |
var name = uniformInfo.name; | |
// remove the array suffix. | |
if (name.substr(-3) === "[0]") { | |
name = name.substr(0, name.length - 3); | |
} | |
var setter = createUniformSetter(program, uniformInfo); | |
uniformSetters[name] = setter; | |
} | |
return uniformSetters; | |
} | |
/** | |
* Set uniforms and binds related textures. | |
* | |
* example: | |
* | |
* var programInfo = createProgramInfo( | |
* gl, ["some-vs", "some-fs"); | |
* | |
* var tex1 = gl.createTexture(); | |
* var tex2 = gl.createTexture(); | |
* | |
* ... assume we setup the textures with data ... | |
* | |
* var uniforms = { | |
* u_someSampler: tex1, | |
* u_someOtherSampler: tex2, | |
* u_someColor: [1,0,0,1], | |
* u_somePosition: [0,1,1], | |
* u_someMatrix: [ | |
* 1,0,0,0, | |
* 0,1,0,0, | |
* 0,0,1,0, | |
* 0,0,0,0, | |
* ], | |
* }; | |
* | |
* gl.useProgram(program); | |
* | |
* This will automatically bind the textures AND set the | |
* uniforms. | |
* | |
* setUniforms(programInfo.uniformSetters, uniforms); | |
* | |
* For the example above it is equivalent to | |
* | |
* var texUnit = 0; | |
* gl.activeTexture(gl.TEXTURE0 + texUnit); | |
* gl.bindTexture(gl.TEXTURE_2D, tex1); | |
* gl.uniform1i(u_someSamplerLocation, texUnit++); | |
* gl.activeTexture(gl.TEXTURE0 + texUnit); | |
* gl.bindTexture(gl.TEXTURE_2D, tex2); | |
* gl.uniform1i(u_someSamplerLocation, texUnit++); | |
* gl.uniform4fv(u_someColorLocation, [1, 0, 0, 1]); | |
* gl.uniform3fv(u_somePositionLocation, [0, 1, 1]); | |
* gl.uniformMatrix4fv(u_someMatrix, false, [ | |
* 1,0,0,0, | |
* 0,1,0,0, | |
* 0,0,1,0, | |
* 0,0,0,0, | |
* ]); | |
* | |
* Note it is perfectly reasonable to call `setUniforms` multiple times. For example | |
* | |
* var uniforms = { | |
* u_someSampler: tex1, | |
* u_someOtherSampler: tex2, | |
* }; | |
* | |
* var moreUniforms { | |
* u_someColor: [1,0,0,1], | |
* u_somePosition: [0,1,1], | |
* u_someMatrix: [ | |
* 1,0,0,0, | |
* 0,1,0,0, | |
* 0,0,1,0, | |
* 0,0,0,0, | |
* ], | |
* }; | |
* | |
* setUniforms(programInfo.uniformSetters, uniforms); | |
* setUniforms(programInfo.uniformSetters, moreUniforms); | |
* | |
* @param {Object.<string, function>} setters the setters returned from | |
* `createUniformSetters`. | |
* @param {Object.<string, value>} an object with values for the | |
* uniforms. | |
* @memberOf module:webgl-utils | |
*/ | |
function setUniforms(setters:{[name:string]:(value: any) => void}, values:{[name: string]: any}) { | |
Object.keys(values).forEach(function(name: string) { | |
var setter = setters[name]; | |
if (setter) { | |
setter(values[name]); | |
} | |
}); | |
} | |
/** | |
* Creates setter functions for all attributes of a shader | |
* program. You can pass this to {@link module:webgl-utils.setBuffersAndAttributes} to set all your buffers and attributes. | |
* | |
* @see {@link module:webgl-utils.setAttributes} for example | |
* @param {WebGLProgram} program the program to create setters for. | |
* @return {Object.<string, function>} an object with a setter for each attribute by name. | |
* @memberOf module:webgl-utils | |
*/ | |
function createAttributeSetters(gl: WebGLRenderingContext, program: WebGLProgram): {[name: string]:(spec: AttribSpec) => void} { | |
var attribSetters:{[name: string]:(spec: AttribSpec) => void} = { | |
}; | |
// An attribute setter function binds a buffer, enables the attribute and describes the attribute property. | |
// This implementation captures the context so it would have to be refreshed on context gain. | |
// The setter does not actually transfer the attribute data but instead defined how the transfer occurs. | |
// Buffers don't exist before we create the setters, but do when they are called. | |
function createAttribSetter(index: number): (spec: AttribSpec) => void { | |
return function(b: AttribSpec) { | |
gl.bindBuffer(gl.ARRAY_BUFFER, b.buffer); | |
gl.enableVertexAttribArray(index); | |
gl.vertexAttribPointer(index, b.numComponents || b.size, b.type || gl.FLOAT, b.normalize || false, b.stride || 0, b.offset || 0); | |
}; | |
} | |
// We discover the attributesnd create a setter for each one. | |
var numAttribs = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); | |
for (var ii = 0; ii < numAttribs; ++ii) { | |
var attribInfo: WebGLActiveInfo = gl.getActiveAttrib(program, ii); | |
if (!attribInfo) { | |
break; | |
} | |
var index = gl.getAttribLocation(program, attribInfo.name); | |
attribSetters[attribInfo.name] = createAttribSetter(index); | |
} | |
return attribSetters; | |
} | |
/** | |
* Sets attributes and binds buffers (deprecated... use {@link module:webgl-utils.setBuffersAndAttributes}) | |
* | |
* Example: | |
* | |
* var program = createProgramFromScripts( | |
* gl, ["some-vs", "some-fs"); | |
* | |
* var attribSetters = createAttributeSetters(program); | |
* | |
* var positionBuffer = gl.createBuffer(); | |
* var texcoordBuffer = gl.createBuffer(); | |
* | |
* var attribs = { | |
* a_position: {buffer: positionBuffer, numComponents: 3}, | |
* a_texcoord: {buffer: texcoordBuffer, numComponents: 2}, | |
* }; | |
* | |
* gl.useProgram(program); | |
* | |
* This will automatically bind the buffers AND set the | |
* attributes. | |
* | |
* setAttributes(attribSetters, attribs); | |
* | |
* Properties of attribs. For each attrib you can add | |
* properties: | |
* | |
* * type: the type of data in the buffer. Default = gl.FLOAT | |
* * normalize: whether or not to normalize the data. Default = false | |
* * stride: the stride. Default = 0 | |
* * offset: offset into the buffer. Default = 0 | |
* | |
* For example if you had 3 value float positions, 2 value | |
* float texcoord and 4 value uint8 colors you'd setup your | |
* attribs like this | |
* | |
* var attribs = { | |
* a_position: {buffer: positionBuffer, numComponents: 3}, | |
* a_texcoord: {buffer: texcoordBuffer, numComponents: 2}, | |
* a_color: { | |
* buffer: colorBuffer, | |
* numComponents: 4, | |
* type: gl.UNSIGNED_BYTE, | |
* normalize: true, | |
* }, | |
* }; | |
* | |
* @param {Object.<string, function>} setters Attribute setters as returned from createAttributeSetters | |
* @param {Object.<string, module:webgl-utils.AttribInfo>} buffers AttribInfos mapped by attribute name. | |
* @memberOf module:webgl-utils | |
* @deprecated use {@link module:webgl-utils.setBuffersAndAttributes} | |
*/ | |
function setAttributes(setters:{[name:string]:(spec: AttribSpec)=>void}, buffers: {[name:string]: AttribSpec;}) { | |
// setters are defined by the program. buffers are defined for objects but may be consolidated. | |
// But if the buffer spec does not exist in the program as a setter, we ignore it. | |
Object.keys(buffers).forEach(function(name) { | |
var setter: (spec: AttribSpec) => void = setters[name]; | |
if (setter) { | |
setter(buffers[name]); | |
} | |
}); | |
} | |
/** | |
* @typedef {Object} ProgramInfo | |
* @property {WebGLProgram} program A shader program | |
* @property {Object<string, function>} uniformSetters: object of setters as returned from createUniformSetters, | |
* @property {Object<string, function>} attribSetters: object of setters as returned from createAttribSetters, | |
* @memberOf module:webgl-utils | |
*/ | |
/** | |
* Creates a ProgramInfo from 2 sources. | |
* | |
* A ProgramInfo contains | |
* | |
* programInfo = { | |
* program: WebGLProgram, | |
* uniformSetters: object of setters as returned from createUniformSetters, | |
* attribSetters: object of setters as returned from createAttribSetters, | |
* } | |
* | |
* @param {WebGLRenderingContext} gl The WebGLRenderingContext | |
* to use. | |
* @param {string[]} shaderSourcess Array of sources for the | |
* shaders or ids. The first is assumed to be the vertex shader, | |
* the second the fragment shader. | |
* @param {string[]} [opt_attribs] An array of attribs names. Locations will be assigned by index if not passed in | |
* @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations. | |
* @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. By default it just prints an error to the console | |
* on error. If you want something else pass an callback. It's passed an error message. | |
* @return {module:webgl-utils.ProgramInfo} The created program. | |
* @memberOf module:webgl-utils | |
*/ | |
function createProgramInfo( | |
gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback) { | |
shaderSources = shaderSources.map(function(source) { | |
var script = <HTMLScriptElement>document.getElementById(source); | |
return script ? script.text : source; | |
}); | |
var program = createProgramFromSources(gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback); | |
if (!program) { | |
return null; | |
} | |
var uniformSetters = createUniformSetters(gl, program); | |
var attribSetters = createAttributeSetters(gl, program); | |
return { | |
program: program, | |
uniformSetters: uniformSetters, | |
attribSetters: attribSetters, | |
}; | |
} | |
/** | |
* Sets attributes and buffers including the `ELEMENT_ARRAY_BUFFER` if appropriate | |
* | |
* Example: | |
* | |
* var programInfo = createProgramInfo( | |
* gl, ["some-vs", "some-fs"); | |
* | |
* var arrays = { | |
* position: { numComponents: 3, data: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0], }, | |
* texcoord: { numComponents: 2, data: [0, 0, 0, 1, 1, 0, 1, 1], }, | |
* }; | |
* | |
* var bufferInfo = createBufferInfoFromArrays(gl, arrays); | |
* | |
* gl.useProgram(programInfo.program); | |
* | |
* This will automatically bind the buffers AND set the | |
* attributes. | |
* | |
* setBuffersAndAttributes(programInfo.attribSetters, bufferInfo); | |
* | |
* For the example above it is equivilent to | |
* | |
* gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); | |
* gl.enableVertexAttribArray(a_positionLocation); | |
* gl.vertexAttribPointer(a_positionLocation, 3, gl.FLOAT, false, 0, 0); | |
* gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer); | |
* gl.enableVertexAttribArray(a_texcoordLocation); | |
* gl.vertexAttribPointer(a_texcoordLocation, 4, gl.FLOAT, false, 0, 0); | |
* | |
* @param {WebGLRenderingContext} gl A WebGLRenderingContext. | |
* @param {Object.<string, function>} setters Attribute setters as returned from `createAttributeSetters` | |
* @param {module:webgl-utils.BufferInfo} buffers a BufferInfo as returned from `createBufferInfoFromArrays`. | |
* @memberOf module:webgl-utils | |
*/ | |
function setBuffersAndAttributes(gl: WebGLRenderingContext, setters, buffers) { | |
setAttributes(setters, buffers.attribs); | |
if (buffers.indices) { | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.indices); | |
} | |
} | |
// Add your prefix here. | |
var browserPrefixes = [ | |
"", | |
"MOZ_", | |
"OP_", | |
"WEBKIT_", | |
]; | |
/** | |
* Given an extension name like WEBGL_compressed_texture_s3tc | |
* returns the supported version extension, like | |
* WEBKIT_WEBGL_compressed_teture_s3tc | |
* @param {string} name Name of extension to look for | |
* @return {WebGLExtension} The extension or undefined if not | |
* found. | |
* @memberOf module:webgl-utils | |
*/ | |
function getExtensionWithKnownPrefixes(gl, name) { | |
for (var ii = 0; ii < browserPrefixes.length; ++ii) { | |
var prefixedName = browserPrefixes[ii] + name; | |
var ext = gl.getExtension(prefixedName); | |
if (ext) { | |
return ext; | |
} | |
} | |
} | |
/** | |
* Resize a canvas to match the size its displayed. | |
* @param {HTMLCanvasElement} canvas The canvas to resize. | |
* @param {boolean} true if the canvas was resized. | |
* @memberOf module:webgl-utils | |
*/ | |
function resizeCanvasToDisplaySize(canvas) { | |
var width = canvas.clientWidth; | |
var height = canvas.clientHeight; | |
if (canvas.width !== width || | |
canvas.height !== height) { | |
canvas.width = width; | |
canvas.height = height; | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Get's the iframe in the parent document | |
* that is displaying the specified window . | |
* @param {Window} window window to check. | |
* @return {HTMLIFrameElement?) the iframe element if window is in an iframe | |
*/ | |
function getIFrameForWindow(window) { | |
if (!isInIFrame(window)) { | |
return; | |
} | |
var iframes = window.parent.document.getElementsByTagName("iframe"); | |
for (var ii = 0; ii < iframes.length; ++ii) { | |
var iframe = iframes[ii]; | |
if (iframe.contentDocument === window.document) { | |
return iframe; // eslint-disable-line | |
} | |
} | |
} | |
/** | |
* Returns true if window is on screen. The main window is | |
* always on screen windows in iframes might not be. | |
* @param {Window} window the window to check. | |
* @return {boolean} true if window is on screen. | |
*/ | |
function isFrameVisible(window) { | |
try { | |
var iframe = getIFrameForWindow(window); | |
if (!iframe) { | |
return true; | |
} | |
var bounds = iframe.getBoundingClientRect(); | |
var isVisible = bounds.top < window.parent.innerHeight && bounds.bottom >= 0 && | |
bounds.left < window.parent.innerWidth && bounds.right >= 0; | |
return isVisible && isFrameVisible(window.parent); | |
} catch (e) { | |
return true; // We got a security error? | |
} | |
} | |
/** | |
* Returns true if element is on screen. | |
* @param {HTMLElement} element the element to check. | |
* @return {boolean} true if element is on screen. | |
*/ | |
function isOnScreen(element) { | |
var isVisible = true; | |
if (element) { | |
var bounds = element.getBoundingClientRect(); | |
isVisible = bounds.top < topWindow.innerHeight && bounds.bottom >= 0; | |
} | |
return isVisible && isFrameVisible(topWindow); | |
} | |
// Add `push` to a typed array. It just keeps a 'cursor' | |
// and allows use to `push` values into the array so we | |
// don't have to manually compute offsets | |
function augmentTypedArray(typedArray, numComponents) { | |
var cursor = 0; | |
typedArray.push = function() { | |
for (var ii = 0; ii < arguments.length; ++ii) { | |
var value = arguments[ii]; | |
if (value instanceof Array || (value.buffer && value.buffer instanceof ArrayBuffer)) { | |
for (var jj = 0; jj < value.length; ++jj) { | |
typedArray[cursor++] = value[jj]; | |
} | |
} else { | |
typedArray[cursor++] = value; | |
} | |
} | |
}; | |
typedArray.reset = function(opt_index) { | |
cursor = opt_index || 0; | |
}; | |
typedArray.numComponents = numComponents; | |
Object.defineProperty(typedArray, 'numElements', { | |
get: function() { | |
return this.length / this.numComponents | 0; | |
}, | |
}); | |
return typedArray; | |
} | |
/** | |
* creates a typed array with a `push` function attached | |
* so that you can easily *push* values. | |
* | |
* `push` can take multiple arguments. If an argument is an array each element | |
* of the array will be added to the typed array. | |
* | |
* Example: | |
* | |
* var array = createAugmentedTypedArray(3, 2); // creates a Float32Array with 6 values | |
* array.push(1, 2, 3); | |
* array.push([4, 5, 6]); | |
* // array now contains [1, 2, 3, 4, 5, 6] | |
* | |
* Also has `numComponents` and `numElements` properties. | |
* | |
* @param {number} numComponents number of components | |
* @param {number} numElements number of elements. The total size of the array will be `numComponents * numElements`. | |
* @param {constructor} opt_type A constructor for the type. Default = `Float32Array`. | |
* @return {ArrayBuffer} A typed array. | |
* @memberOf module:webgl-utils | |
*/ | |
function createAugmentedTypedArray(numComponents: number, numElements: number, opt_type?) { | |
var Type = opt_type || Float32Array; | |
return augmentTypedArray(new Type(numComponents * numElements), numComponents); | |
} | |
function createBufferFromTypedArray(gl: WebGLRenderingContext, array, type?: number, drawType?:number): WebGLBuffer { | |
type = type || gl.ARRAY_BUFFER; | |
var buffer = gl.createBuffer(); | |
gl.bindBuffer(type, buffer); | |
gl.bufferData(type, array, drawType || gl.STATIC_DRAW); | |
return buffer; | |
} | |
function allButIndices(name: string): boolean { | |
return name !== "indices"; | |
} | |
function createMapping(obj) { | |
var mapping = {}; | |
Object.keys(obj).filter(allButIndices).forEach(function(key) { | |
mapping["a_" + key] = key; | |
}); | |
return mapping; | |
} | |
function getGLTypeForTypedArray(gl: WebGLRenderingContext, typedArray) { | |
if (typedArray instanceof Int8Array) { return gl.BYTE; } // eslint-disable-line | |
if (typedArray instanceof Uint8Array) { return gl.UNSIGNED_BYTE; } // eslint-disable-line | |
if (typedArray instanceof Int16Array) { return gl.SHORT; } // eslint-disable-line | |
if (typedArray instanceof Uint16Array) { return gl.UNSIGNED_SHORT; } // eslint-disable-line | |
if (typedArray instanceof Int32Array) { return gl.INT; } // eslint-disable-line | |
if (typedArray instanceof Uint32Array) { return gl.UNSIGNED_INT; } // eslint-disable-line | |
if (typedArray instanceof Float32Array) { return gl.FLOAT; } // eslint-disable-line | |
throw "unsupported typed array type"; | |
} | |
// This is really just a guess. Though I can't really imagine using | |
// anything else? Maybe for some compression? | |
function getNormalizationForTypedArray(typedArray) { | |
if (typedArray instanceof Int8Array) { return true; } // eslint-disable-line | |
if (typedArray instanceof Uint8Array) { return true; } // eslint-disable-line | |
return false; | |
} | |
function isArrayBuffer(a) { | |
return a.buffer && a.buffer instanceof ArrayBuffer; | |
} | |
function guessNumComponentsFromName(name: string, length?: number) { | |
var numComponents; | |
if (name.indexOf("coord") >= 0) { | |
numComponents = 2; | |
} else if (name.indexOf("color") >= 0) { | |
numComponents = 4; | |
} else { | |
numComponents = 3; // position, normals, indices ... | |
} | |
if (length % numComponents > 0) { | |
throw "can not guess numComponents. You should specify it."; | |
} | |
return numComponents; | |
} | |
function makeTypedArray(array, name) { | |
if (isArrayBuffer(array)) { | |
return array; | |
} | |
if (Array.isArray(array)) { | |
array = { | |
data: array, | |
}; | |
} | |
if (!array.numComponents) { | |
array.numComponents = guessNumComponentsFromName(name, array.length); | |
} | |
var type = array.type; | |
if (!type) { | |
if (name === "indices") { | |
type = Uint16Array; | |
} | |
} | |
var typedArray = createAugmentedTypedArray(array.numComponents, array.data.length / array.numComponents | 0, type); | |
typedArray.push(array.data); | |
return typedArray; | |
} | |
/** | |
* @typedef {Object} AttribInfo | |
* @property {number} [numComponents] the number of components for this attribute. | |
* @property {number} [size] the number of components for this attribute. | |
* @property {number} [type] the type of the attribute (eg. `gl.FLOAT`, `gl.UNSIGNED_BYTE`, etc...) Default = `gl.FLOAT` | |
* @property {boolean} [normalized] whether or not to normalize the data. Default = false | |
* @property {number} [offset] offset into buffer in bytes. Default = 0 | |
* @property {number} [stride] the stride in bytes per element. Default = 0 | |
* @property {WebGLBuffer} buffer the buffer that contains the data for this attribute | |
* @memberOf module:webgl-utils | |
*/ | |
/** | |
* Creates a set of attribute data and WebGLBuffers from set of arrays | |
* | |
* Given | |
* | |
* var arrays = { | |
* position: { numComponents: 3, data: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0], }, | |
* texcoord: { numComponents: 2, data: [0, 0, 0, 1, 1, 0, 1, 1], }, | |
* normal: { numComponents: 3, data: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1], }, | |
* color: { numComponents: 4, data: [255, 255, 255, 255, 255, 0, 0, 255, 0, 0, 255, 255], type: Uint8Array, }, | |
* indices: { numComponents: 3, data: [0, 1, 2, 1, 2, 3], }, | |
* }; | |
* | |
* returns something like | |
* | |
* var attribs = { | |
* a_position: { numComponents: 3, type: gl.FLOAT, normalize: false, buffer: WebGLBuffer, }, | |
* a_texcoord: { numComponents: 2, type: gl.FLOAT, normalize: false, buffer: WebGLBuffer, }, | |
* a_normal: { numComponents: 3, type: gl.FLOAT, normalize: false, buffer: WebGLBuffer, }, | |
* a_color: { numComponents: 4, type: gl.UNSIGNED_BYTE, normalize: true, buffer: WebGLBuffer, }, | |
* }; | |
* | |
* @param {WebGLRenderingContext} gl The webgl rendering context. | |
* @param {Object.<string, array|typedarray>} arrays The arrays | |
* @param {Object.<string, string>} [opt_mapping] mapping from attribute name to array name. | |
* if not specified defaults to "a_name" -> "name". | |
* @return {Object.<string, module:webgl-utils.AttribInfo>} the attribs | |
* @memberOf module:webgl-utils | |
*/ | |
function createAttribsFromArrays(gl: WebGLRenderingContext, arrays, opt_mapping) { | |
var mapping = opt_mapping || createMapping(arrays); | |
var attribs = {}; | |
Object.keys(mapping).forEach(function(attribName) { | |
var bufferName = mapping[attribName]; | |
var array = makeTypedArray(arrays[bufferName], bufferName); | |
attribs[attribName] = { | |
buffer: createBufferFromTypedArray(gl, array), | |
numComponents: array.numComponents || guessNumComponentsFromName(bufferName), | |
type: getGLTypeForTypedArray(gl, array), | |
normalize: getNormalizationForTypedArray(array), | |
}; | |
}); | |
return attribs; | |
} | |
/** | |
* tries to get the number of elements from a set of arrays. | |
*/ | |
function getNumElementsFromNonIndexedArrays(arrays) { | |
var key = Object.keys(arrays)[0]; | |
var array = arrays[key]; | |
if (isArrayBuffer(array)) { | |
return array.numElements; | |
} else { | |
return array.data.length / array.numComponents; | |
} | |
} | |
/** | |
* @typedef {Object} BufferInfo | |
* @property {number} numElements The number of elements to pass to `gl.drawArrays` or `gl.drawElements`. | |
* @property {WebGLBuffer} [indices] The indices `ELEMENT_ARRAY_BUFFER` if any indices exist. | |
* @property {Object.<string, module:webgl-utils.AttribInfo>} attribs The attribs approriate to call `setAttributes` | |
* @memberOf module:webgl-utils | |
*/ | |
/** | |
* Creates a BufferInfo from an object of arrays. | |
* | |
* This can be passed to {@link module:webgl-utils.setBuffersAndAttributes} and to | |
* {@link module:webgl-utils:drawBufferInfo}. | |
* | |
* Given an object like | |
* | |
* var arrays = { | |
* position: { numComponents: 3, data: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0], }, | |
* texcoord: { numComponents: 2, data: [0, 0, 0, 1, 1, 0, 1, 1], }, | |
* normal: { numComponents: 3, data: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1], }, | |
* indices: { numComponents: 3, data: [0, 1, 2, 1, 2, 3], }, | |
* }; | |
* | |
* Creates an BufferInfo like this | |
* | |
* bufferInfo = { | |
* numElements: 4, // or whatever the number of elements is | |
* indices: WebGLBuffer, // this property will not exist if there are no indices | |
* attribs: { | |
* a_position: { buffer: WebGLBuffer, numComponents: 3, }, | |
* a_normal: { buffer: WebGLBuffer, numComponents: 3, }, | |
* a_texcoord: { buffer: WebGLBuffer, numComponents: 2, }, | |
* }, | |
* }; | |
* | |
* The properties of arrays can be JavaScript arrays in which case the number of components | |
* will be guessed. | |
* | |
* var arrays = { | |
* position: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0], | |
* texcoord: [0, 0, 0, 1, 1, 0, 1, 1], | |
* normal: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1], | |
* indices: [0, 1, 2, 1, 2, 3], | |
* }; | |
* | |
* They can also by TypedArrays | |
* | |
* var arrays = { | |
* position: new Float32Array([0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0]), | |
* texcoord: new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]), | |
* normal: new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]), | |
* indices: new Uint16Array([0, 1, 2, 1, 2, 3]), | |
* }; | |
* | |
* Or augmentedTypedArrays | |
* | |
* var positions = createAugmentedTypedArray(3, 4); | |
* var texcoords = createAugmentedTypedArray(2, 4); | |
* var normals = createAugmentedTypedArray(3, 4); | |
* var indices = createAugmentedTypedArray(3, 2, Uint16Array); | |
* | |
* positions.push([0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0]); | |
* texcoords.push([0, 0, 0, 1, 1, 0, 1, 1]); | |
* normals.push([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]); | |
* indices.push([0, 1, 2, 1, 2, 3]); | |
* | |
* var arrays = { | |
* position: positions, | |
* texcoord: texcoords, | |
* normal: normals, | |
* indices: indices, | |
* }; | |
* | |
* For the last example it is equivalent to | |
* | |
* var bufferInfo = { | |
* attribs: { | |
* a_position: { numComponents: 3, buffer: gl.createBuffer(), }, | |
* a_texcoods: { numComponents: 2, buffer: gl.createBuffer(), }, | |
* a_normals: { numComponents: 3, buffer: gl.createBuffer(), }, | |
* }, | |
* indices: gl.createBuffer(), | |
* numElements: 6, | |
* }; | |
* | |
* gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.a_position.buffer); | |
* gl.bufferData(gl.ARRAY_BUFFER, arrays.position, gl.STATIC_DRAW); | |
* gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.a_texcoord.buffer); | |
* gl.bufferData(gl.ARRAY_BUFFER, arrays.texcoord, gl.STATIC_DRAW); | |
* gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.a_normal.buffer); | |
* gl.bufferData(gl.ARRAY_BUFFER, arrays.normal, gl.STATIC_DRAW); | |
* gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferInfo.indices); | |
* gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, arrays.indices, gl.STATIC_DRAW); | |
* | |
* @param {WebGLRenderingContext} gl A WebGLRenderingContext | |
* @param {Object.<string, array|object|typedarray>} arrays Your data | |
* @param {Object.<string, string>} [opt_mapping] an optional mapping of attribute to array name. | |
* If not passed in it's assumed the array names will be mapped to an attibute | |
* of the same name with "a_" prefixed to it. An other words. | |
* | |
* var arrays = { | |
* position: ..., | |
* texcoord: ..., | |
* normal: ..., | |
* indices: ..., | |
* }; | |
* | |
* bufferInfo = createBufferInfoFromArrays(gl, arrays); | |
* | |
* Is the same as | |
* | |
* var arrays = { | |
* position: ..., | |
* texcoord: ..., | |
* normal: ..., | |
* indices: ..., | |
* }; | |
* | |
* var mapping = { | |
* a_position: "position", | |
* a_texcoord: "texcoord", | |
* a_normal: "normal", | |
* }; | |
* | |
* bufferInfo = createBufferInfoFromArrays(gl, arrays, mapping); | |
* | |
* @return {module:webgl-utils.BufferInfo} A BufferInfo | |
* @memberOf module:webgl-utils | |
*/ | |
function createBufferInfoFromArrays(gl: WebGLRenderingContext, arrays, opt_mapping?) { | |
var bufferInfo: {attribs: any; indices?:any; numElements?: any} = { | |
attribs: createAttribsFromArrays(gl, arrays, opt_mapping), | |
}; | |
var indices = arrays.indices; | |
if (indices) { | |
indices = makeTypedArray(indices, "indices"); | |
bufferInfo.indices = createBufferFromTypedArray(gl, indices, gl.ELEMENT_ARRAY_BUFFER); | |
bufferInfo.numElements = indices.length; | |
} | |
else { | |
bufferInfo.numElements = getNumElementsFromNonIndexedArrays(arrays); | |
} | |
return bufferInfo; | |
} | |
/** | |
* Creates buffers from typed arrays | |
* | |
* Given something like this | |
* | |
* var arrays = { | |
* positions: [1, 2, 3], | |
* normals: [0, 0, 1], | |
* } | |
* | |
* returns something like | |
* | |
* buffers = { | |
* positions: WebGLBuffer, | |
* normals: WebGLBuffer, | |
* } | |
* | |
* If the buffer is named 'indices' it will be made an ELEMENT_ARRAY_BUFFER. | |
* | |
* @param {WebGLRenderingContext) gl A WebGLRenderingContext. | |
* @param {Object<string, array|typedarray>} arrays | |
* @return {Object<string, WebGLBuffer>} returns an object with one WebGLBuffer per array | |
* @memberOf module:webgl-utils | |
*/ | |
function createBuffersFromArrays(gl: WebGLRenderingContext, arrays: {[name:string]:number[]}): {[name:string]:WebGLBuffer} { | |
var buffers:{[name:string]:WebGLBuffer} = { }; | |
Object.keys(arrays).forEach(function(key) { | |
var type = key === "indices" ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER; | |
var array = makeTypedArray(arrays[key], name); | |
buffers[key] = createBufferFromTypedArray(gl, array, type); | |
}); | |
// hrm | |
if (arrays['indices']) { | |
buffers['numElements'] = arrays['indices'].length; | |
} | |
else if (arrays['position']) { | |
buffers['numElements'] = arrays['position'].length / 3; | |
} | |
return buffers; | |
} | |
/** | |
* Calls `gl.drawElements` or `gl.drawArrays`, whichever is appropriate | |
* | |
* normally you'd call `gl.drawElements` or `gl.drawArrays` yourself | |
* but calling this means if you switch from indexed data to non-indexed | |
* data you don't have to remember to update your draw call. | |
* | |
* @param {WebGLRenderingContext} gl A WebGLRenderingContext | |
* @param {enum} type eg (gl.TRIANGLES, gl.LINES, gl.POINTS, gl.TRIANGLE_STRIP, ...) | |
* @param {module:webgl-utils.BufferInfo} bufferInfo as returned from createBufferInfoFromArrays | |
* @param {number} [count] An optional count. Defaults to bufferInfo.numElements | |
* @param {number} [offset] An optional offset. Defaults to 0. | |
* @memberOf module:webgl-utils | |
*/ | |
function drawBufferInfo(gl: WebGLRenderingContext, type: number, bufferInfo: {indices; numElements}, count?: number, offset?: number) { | |
var indices = bufferInfo.indices; | |
var numElements = count === undefined ? bufferInfo.numElements : count; | |
offset = offset === undefined ? offset : 0; | |
if (indices) { | |
gl.drawElements(type, numElements, gl.UNSIGNED_SHORT, offset); | |
} else { | |
gl.drawArrays(type, offset, numElements); | |
} | |
} | |
/** | |
* @typedef {Object} DrawObject | |
* @property {module:webgl-utils.ProgramInfo} programInfo A ProgramInfo as returned from createProgramInfo | |
* @property {module:webgl-utils.BufferInfo} bufferInfo A BufferInfo as returned from createBufferInfoFromArrays | |
* @property {Object<string, ?>} uniforms The values for the uniforms | |
* @memberOf module:webgl-utils | |
*/ | |
/** | |
* Draws a list of objects | |
* @param {WebGLRenderingContext} gl A WebGLRenderingContext | |
* @param {DrawObject[]} objectsToDraw an array of objects to draw. | |
* @memberOf module:webgl-utils | |
*/ | |
function drawObjectList(gl, objectsToDraw) { | |
var lastUsedProgramInfo = null; | |
var lastUsedBufferInfo = null; | |
objectsToDraw.forEach(function(object) { | |
var programInfo = object.programInfo; | |
var bufferInfo = object.bufferInfo; | |
var bindBuffers = false; | |
if (programInfo !== lastUsedProgramInfo) { | |
lastUsedProgramInfo = programInfo; | |
gl.useProgram(programInfo.program); | |
bindBuffers = true; | |
} | |
// Setup all the needed attributes. | |
if (bindBuffers || bufferInfo !== lastUsedBufferInfo) { | |
lastUsedBufferInfo = bufferInfo; | |
setBuffersAndAttributes(gl, programInfo.attribSetters, bufferInfo); | |
} | |
// Set the uniforms. | |
setUniforms(programInfo.uniformSetters, object.uniforms); | |
// Draw | |
drawBufferInfo(gl, gl.TRIANGLES, bufferInfo); | |
}); | |
} | |
// Replace requestAnimationFrame. | |
if (topWindow.requestAnimationFrame) { | |
topWindow.requestAnimationFrame = (function(oldRAF) { | |
return function(callback, element) { | |
var handler = function() { | |
if (isOnScreen(element)) { | |
oldRAF(callback, element); | |
} else { | |
oldRAF(handler, element); | |
} | |
}; | |
handler(); | |
}; | |
}(topWindow.requestAnimationFrame)); | |
} | |
//======================================= | |
// Math | |
//======================================= | |
/** | |
* An array or typed array with 3 values. | |
* @typedef {number[]|TypedArray} Vector3 | |
* @memberOf module:webgl-3d-math | |
*/ | |
/** | |
* An array or typed array with 4 values. | |
* @typedef {number[]|TypedArray} Vector4 | |
* @memberOf module:webgl-3d-math | |
*/ | |
/** | |
* An array or typed array with 16 values. | |
* @typedef {number[]|TypedArray} Matrix4 | |
* @memberOf module:webgl-3d-math | |
*/ | |
/** | |
* subtracts 2 vectors3s | |
* @param {Vector3} a a | |
* @param {Vector3} b b | |
* @param {Vector3} dst optional vector3 to store result | |
* @return {Vector3} dst or new Vector3 if not provided | |
* @memberOf module:webgl-3d-math | |
*/ | |
function subtractVectors(a, b, dst?: Float32Array) { | |
dst = dst || new Float32Array(3); | |
dst[0] = a[0] - b[0]; | |
dst[1] = a[1] - b[1]; | |
dst[2] = a[2] - b[2]; | |
return dst; | |
} | |
/** | |
* normalizes a vector. | |
* @param {Vector3} v vector to normalzie | |
* @param {Vector3} dst optional vector3 to store result | |
* @return {Vector3} dst or new Vector3 if not provided | |
* @memberOf module:webgl-3d-math | |
*/ | |
function normalize(v, dst?: Float32Array) { | |
dst = dst || new Float32Array(3); | |
var length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); | |
// make sure we don't divide by 0. | |
if (length > 0.00001) { | |
dst[0] = v[0] / length; | |
dst[1] = v[1] / length; | |
dst[2] = v[2] / length; | |
} | |
return dst; | |
} | |
/** | |
* Computes the cross product of 2 vectors3s | |
* @param {Vector3} a a | |
* @param {Vector3} b b | |
* @param {Vector3} dst optional vector3 to store result | |
* @return {Vector3} dst or new Vector3 if not provided | |
* @memberOf module:webgl-3d-math | |
*/ | |
function cross(a, b, dst?: Float32Array) { | |
dst = dst || new Float32Array(3); | |
dst[0] = a[1] * b[2] - a[2] * b[1]; | |
dst[1] = a[2] * b[0] - a[0] * b[2]; | |
dst[2] = a[0] * b[1] - a[1] * b[0]; | |
return dst; | |
} | |
/** | |
* Makes an identity matrix. | |
* @param {Matrix4} [dst] optional matrix to store result | |
* @return {Matrix4} dst or a new matrix of none provided | |
* @memberOf module:webgl-3d-math | |
*/ | |
function makeIdentity(dst?: Float32Array) { | |
dst = dst || new Float32Array(16); | |
dst[ 0] = 1; | |
dst[ 1] = 0; | |
dst[ 2] = 0; | |
dst[ 3] = 0; | |
dst[ 4] = 0; | |
dst[ 5] = 1; | |
dst[ 6] = 0; | |
dst[ 7] = 0; | |
dst[ 8] = 0; | |
dst[ 9] = 0; | |
dst[10] = 1; | |
dst[11] = 0; | |
dst[12] = 0; | |
dst[13] = 0; | |
dst[14] = 0; | |
dst[15] = 1; | |
return dst; | |
} | |
/** | |
* Transposes a matrix. | |
* @param {Matrix4} m matrix to transpose. | |
* @param {Matrix4} [dst] optional matrix to store result | |
* @return {Matrix4} dst or a new matrix of none provided | |
* @memberOf module:webgl-3d-math | |
*/ | |
function makeTranspose(m, dst?: Float32Array) { | |
dst = dst || new Float32Array(16); | |
dst[ 0] = m[0]; | |
dst[ 1] = m[4]; | |
dst[ 2] = m[8]; | |
dst[ 3] = m[12]; | |
dst[ 4] = m[1]; | |
dst[ 5] = m[5]; | |
dst[ 6] = m[9]; | |
dst[ 7] = m[13]; | |
dst[ 8] = m[2]; | |
dst[ 9] = m[6]; | |
dst[10] = m[10]; | |
dst[11] = m[14]; | |
dst[12] = m[3]; | |
dst[13] = m[7]; | |
dst[14] = m[11]; | |
dst[15] = m[15]; | |
return dst; | |
} | |
/** | |
* Creates a lookAt matrix. | |
* This is a world matrix for a camera. In other words it will transform | |
* from the origin to a place and orientation in the world. For a view | |
* matrix take the inverse of this. | |
* @param {Vector3} cameraPosition position of the camera | |
* @param {Vector3} target position of the target | |
* @param {Vector3} up direction | |
* @param {Matrix4} [dst] optional matrix to store result | |
* @return {Matrix4} dst or a new matrix of none provided | |
* @memberOf module:webgl-3d-math | |
*/ | |
function makeLookAt(cameraPosition, target, up, dst?: Float32Array) { | |
dst = dst || new Float32Array(16); | |
var zAxis = normalize(subtractVectors(cameraPosition, target)); | |
var xAxis = normalize(cross(up, zAxis)); | |
var yAxis = normalize(cross(zAxis, xAxis)); | |
dst[ 0] = xAxis[0]; | |
dst[ 1] = xAxis[1]; | |
dst[ 2] = xAxis[2]; | |
dst[ 3] = 0; | |
dst[ 4] = yAxis[0]; | |
dst[ 5] = yAxis[1]; | |
dst[ 6] = yAxis[2]; | |
dst[ 7] = 0; | |
dst[ 8] = zAxis[0]; | |
dst[ 9] = zAxis[1]; | |
dst[10] = zAxis[2]; | |
dst[11] = 0; | |
dst[12] = cameraPosition[0]; | |
dst[13] = cameraPosition[1]; | |
dst[14] = cameraPosition[2]; | |
dst[15] = 1; | |
return dst; | |
} | |
/** | |
* Computes a 4-by-4 perspective transformation matrix given the angular height | |
* of the frustum, the aspect ratio, and the near and far clipping planes. The | |
* arguments define a frustum extending in the negative z direction. The given | |
* angle is the vertical angle of the frustum, and the horizontal angle is | |
* determined to produce the given aspect ratio. The arguments near and far are | |
* the distances to the near and far clipping planes. Note that near and far | |
* are not z coordinates, but rather they are distances along the negative | |
* z-axis. The matrix generated sends the viewing frustum to the unit box. | |
* We assume a unit box extending from -1 to 1 in the x and y dimensions and | |
* from -1 to 1 in the z dimension. | |
* @param {number} fieldOfViewInRadians field of view in y axis. | |
* @param {number} aspect aspect of viewport (width / height) | |
* @param {number} near near Z clipping plane | |
* @param {number} far far Z clipping plane | |
* @param {Matrix4} [dst] optional matrix to store result | |
* @return {Matrix4} dst or a new matrix of none provided | |
* @memberOf module:webgl-3d-math | |
*/ | |
function makePerspective(fieldOfViewInRadians, aspect, near, far, dst?: Float32Array) { | |
dst = dst || new Float32Array(16); | |
var f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians); | |
var rangeInv = 1.0 / (near - far); | |
dst[ 0] = f / aspect; | |
dst[ 1] = 0; | |
dst[ 2] = 0; | |
dst[ 3] = 0; | |
dst[ 4] = 0; | |
dst[ 5] = f; | |
dst[ 6] = 0; | |
dst[ 7] = 0; | |
dst[ 8] = 0; | |
dst[ 9] = 0; | |
dst[10] = (near + far) * rangeInv; | |
dst[11] = -1; | |
dst[12] = 0; | |
dst[13] = 0; | |
dst[14] = near * far * rangeInv * 2; | |
dst[15] = 0; | |
return dst; | |
} | |
/** | |
* Computes a 4-by-4 orthographic projection matrix given the coordinates of the | |
* planes defining the axis-aligned, box-shaped viewing volume. The matrix | |
* generated sends that box to the unit box. Note that although left and right | |
* are x coordinates and bottom and top are y coordinates, near and far | |
* are not z coordinates, but rather they are distances along the negative | |
* z-axis. We assume a unit box extending from -1 to 1 in the x and y | |
* dimensions and from -1 to 1 in the z dimension. | |
* @param {number} left The x coordinate of the left plane of the box. | |
* @param {number} right The x coordinate of the right plane of the box. | |
* @param {number} bottom The y coordinate of the bottom plane of the box. | |
* @param {number} top The y coordinate of the right plane of the box. | |
* @param {number} near The negative z coordinate of the near plane of the box. | |
* @param {number} far The negative z coordinate of the far plane of the box. | |
* @param {Matrix4} [dst] optional matrix to store result | |
* @return {Matrix4} dst or a new matrix of none provided | |
* @memberOf module:webgl-3d-math | |
*/ | |
function makeOrthographic(left, right, bottom, top, near, far, dst?: Float32Array) { | |
dst = dst || new Float32Array(16); | |
dst[ 0] = 2 / (right - left); | |
dst[ 1] = 0; | |
dst[ 2] = 0; | |
dst[ 3] = 0; | |
dst[ 4] = 0; | |
dst[ 5] = 2 / (top - bottom); | |
dst[ 6] = 0; | |
dst[ 7] = 0; | |
dst[ 8] = 0; | |
dst[ 9] = 0; | |
dst[10] = 2 / (near - far); | |
dst[11] = 0; | |
dst[12] = (left + right) / (left - right); | |
dst[13] = (bottom + top) / (bottom - top); | |
dst[14] = (near + far) / (near - far); | |
dst[15] = 1; | |
return dst; | |
} | |
/** | |
* Computes a 4-by-4 perspective transformation matrix given the left, right, | |
* top, bottom, near and far clipping planes. The arguments define a frustum | |
* extending in the negative z direction. The arguments near and far are the | |
* distances to the near and far clipping planes. Note that near and far are not | |
* z coordinates, but rather they are distances along the negative z-axis. The | |
* matrix generated sends the viewing frustum to the unit box. We assume a unit | |
* box extending from -1 to 1 in the x and y dimensions and from -1 to 1 in the z | |
* dimension. | |
* @param {number} left The x coordinate of the left plane of the box. | |
* @param {number} right The x coordinate of the right plane of the box. | |
* @param {number} bottom The y coordinate of the bottom plane of the box. | |
* @param {number} top The y coordinate of the right plane of the box. | |
* @param {number} near The negative z coordinate of the near plane of the box. | |
* @param {number} far The negative z coordinate of the far plane of the box. | |
* @param {Matrix4} [dst] optional matrix to store result | |
* @return {Matrix4} dst or a new matrix of none provided | |
* @memberOf module:webgl-3d-math | |
*/ | |
function makeFrustum(left, right, bottom, top, near, far, dst?: Float32Array) { | |
dst = dst || new Float32Array(16); | |
var dx = right - left; | |
var dy = top - bottom; | |
var dz = far - near; | |
dst[ 0] = 2 * near / dx; | |
dst[ 1] = 0; | |
dst[ 2] = 0; | |
dst[ 3] = 0; | |
dst[ 4] = 0; | |
dst[ 5] = 2 * near / dy; | |
dst[ 6] = 0; | |
dst[ 7] = 0; | |
dst[ 8] = (left + right) / dx; | |
dst[ 9] = (top + bottom) / dy; | |
dst[10] = -(far + near) / dz; | |
dst[11] = -1; | |
dst[12] = 0; | |
dst[13] = 0; | |
dst[14] = -2 * near * far / dz; | |
dst[15] = 0; | |
return dst; | |
} | |
/** | |
* Makes a translation matrix | |
* @param {number} tx x translation. | |
* @param {number} ty y translation. | |
* @param {number} tz z translation. | |
* @param {Matrix4} [dst] optional matrix to store result | |
* @return {Matrix4} dst or a new matrix of none provided | |
* @memberOf module:webgl-3d-math | |
*/ | |
function makeTranslation(tx, ty, tz, dst?: Float32Array) { | |
dst = dst || new Float32Array(16); | |
dst[ 0] = 1; | |
dst[ 1] = 0; | |
dst[ 2] = 0; | |
dst[ 3] = 0; | |
dst[ 4] = 0; | |
dst[ 5] = 1; | |
dst[ 6] = 0; | |
dst[ 7] = 0; | |
dst[ 8] = 0; | |
dst[ 9] = 0; | |
dst[10] = 1; | |
dst[11] = 0; | |
dst[12] = tx; | |
dst[13] = ty; | |
dst[14] = tz; | |
dst[15] = 1; | |
return dst; | |
} | |
/** | |
* Makes an x rotation matrix | |
* @param {number} angleInRadians amount to rotate | |
* @param {Matrix4} [dst] optional matrix to store result | |
* @return {Matrix4} dst or a new matrix of none provided | |
* @memberOf module:webgl-3d-math | |
*/ | |
function makeXRotation(angleInRadians, dst?: Float32Array) { | |
dst = dst || new Float32Array(16); | |
var c = Math.cos(angleInRadians); | |
var s = Math.sin(angleInRadians); | |
dst[ 0] = 1; | |
dst[ 1] = 0; | |
dst[ 2] = 0; | |
dst[ 3] = 0; | |
dst[ 4] = 0; | |
dst[ 5] = c; | |
dst[ 6] = s; | |
dst[ 7] = 0; | |
dst[ 8] = 0; | |
dst[ 9] = -s; | |
dst[10] = c; | |
dst[11] = 0; | |
dst[12] = 0; | |
dst[13] = 0; | |
dst[14] = 0; | |
dst[15] = 1; | |
return dst; | |
} | |
/** | |
* Makes an y rotation matrix | |
* @param {number} angleInRadians amount to rotate | |
* @param {Matrix4} [dst] optional matrix to store result | |
* @return {Matrix4} dst or a new matrix of none provided | |
* @memberOf module:webgl-3d-math | |
*/ | |
function makeYRotation(angleInRadians, dst?: Float32Array) { | |
dst = dst || new Float32Array(16); | |
var c = Math.cos(angleInRadians); | |
var s = Math.sin(angleInRadians); | |
dst[ 0] = c; | |
dst[ 1] = 0; | |
dst[ 2] = -s; | |
dst[ 3] = 0; | |
dst[ 4] = 0; | |
dst[ 5] = 1; | |
dst[ 6] = 0; | |
dst[ 7] = 0; | |
dst[ 8] = s; | |
dst[ 9] = 0; | |
dst[10] = c; | |
dst[11] = 0; | |
dst[12] = 0; | |
dst[13] = 0; | |
dst[14] = 0; | |
dst[15] = 1; | |
return dst; | |
} | |
/** | |
* Makes an z rotation matrix | |
* @param {number} angleInRadians amount to rotate | |
* @param {Matrix4} [dst] optional matrix to store result | |
* @return {Matrix4} dst or a new matrix of none provided | |
* @memberOf module:webgl-3d-math | |
*/ | |
function makeZRotation(angleInRadians, dst?: Float32Array) { | |
dst = dst || new Float32Array(16); | |
var c = Math.cos(angleInRadians); | |
var s = Math.sin(angleInRadians); | |
dst[ 0] = c; | |
dst[ 1] = s; | |
dst[ 2] = 0; | |
dst[ 3] = 0; | |
dst[ 4] = -s; | |
dst[ 5] = c; | |
dst[ 6] = 0; | |
dst[ 7] = 0; | |
dst[ 8] = 0; | |
dst[ 9] = 0; | |
dst[10] = 1; | |
dst[11] = 0; | |
dst[12] = 0; | |
dst[13] = 0; | |
dst[14] = 0; | |
dst[15] = 1; | |
return dst; | |
} | |
/** | |
* Makes an rotation matrix around an arbitrary axis | |
* @param {Vector3} axis axis to rotate around | |
* @param {number} angleInRadians amount to rotate | |
* @param {Matrix4} [dst] optional matrix to store result | |
* @return {Matrix4} dst or a new matrix of none provided | |
* @memberOf module:webgl-3d-math | |
*/ | |
function makeAxisRotation(axis, angleInRadians, dst?: Float32Array) { | |
dst = dst || new Float32Array(16); | |
var x = axis[0]; | |
var y = axis[1]; | |
var z = axis[2]; | |
var n = Math.sqrt(x * x + y * y + z * z); | |
x /= n; | |
y /= n; | |
z /= n; | |
var xx = x * x; | |
var yy = y * y; | |
var zz = z * z; | |
var c = Math.cos(angleInRadians); | |
var s = Math.sin(angleInRadians); | |
var oneMinusCosine = 1 - c; | |
dst[ 0] = xx + (1 - xx) * c; | |
dst[ 1] = x * y * oneMinusCosine + z * s; | |
dst[ 2] = x * z * oneMinusCosine - y * s; | |
dst[ 3] = 0; | |
dst[ 4] = x * y * oneMinusCosine - z * s; | |
dst[ 5] = yy + (1 - yy) * c; | |
dst[ 6] = y * z * oneMinusCosine + x * s; | |
dst[ 7] = 0; | |
dst[ 8] = x * z * oneMinusCosine + y * s; | |
dst[ 9] = y * z * oneMinusCosine - x * s; | |
dst[10] = zz + (1 - zz) * c; | |
dst[11] = 0; | |
dst[12] = 0; | |
dst[13] = 0; | |
dst[14] = 0; | |
dst[15] = 1; | |
return dst; | |
} | |
/** | |
* Makes a scale matrix | |
* @param {number} sx x scale. | |
* @param {number} sy y scale. | |
* @param {number} sz z scale. | |
* @param {Matrix4} [dst] optional matrix to store result | |
* @return {Matrix4} dst or a new matrix of none provided | |
* @memberOf module:webgl-3d-math | |
*/ | |
function makeScale(sx, sy, sz, dst?: Float32Array) { | |
dst = dst || new Float32Array(16); | |
dst[ 0] = sx; | |
dst[ 1] = 0; | |
dst[ 2] = 0; | |
dst[ 3] = 0; | |
dst[ 4] = 0; | |
dst[ 5] = sy; | |
dst[ 6] = 0; | |
dst[ 7] = 0; | |
dst[ 8] = 0; | |
dst[ 9] = 0; | |
dst[10] = sz; | |
dst[11] = 0; | |
dst[12] = 0; | |
dst[13] = 0; | |
dst[14] = 0; | |
dst[15] = 1; | |
return dst; | |
} | |
/** | |
* Takes two 4-by-4 matrices, a and b, and computes the product in the order | |
* that pre-composes b with a. In other words, the matrix returned will | |
* transform by b first and then a. Note this is subtly different from just | |
* multiplying the matrices together. For given a and b, this function returns | |
* the same object in both row-major and column-major mode. | |
* @param {Matrix4} a A matrix. | |
* @param {Matrix4} b A matrix. | |
* @param {Matrix4} [dst] optional matrix to store result | |
* @return {Matrix4} dst or a new matrix of none provided | |
*/ | |
function matrixMultiply(a, b, dst?: Float32Array) { | |
dst = dst || new Float32Array(16); | |
var a00 = a[0 * 4 + 0]; | |
var a01 = a[0 * 4 + 1]; | |
var a02 = a[0 * 4 + 2]; | |
var a03 = a[0 * 4 + 3]; | |
var a10 = a[1 * 4 + 0]; | |
var a11 = a[1 * 4 + 1]; | |
var a12 = a[1 * 4 + 2]; | |
var a13 = a[1 * 4 + 3]; | |
var a20 = a[2 * 4 + 0]; | |
var a21 = a[2 * 4 + 1]; | |
var a22 = a[2 * 4 + 2]; | |
var a23 = a[2 * 4 + 3]; | |
var a30 = a[3 * 4 + 0]; | |
var a31 = a[3 * 4 + 1]; | |
var a32 = a[3 * 4 + 2]; | |
var a33 = a[3 * 4 + 3]; | |
var b00 = b[0 * 4 + 0]; | |
var b01 = b[0 * 4 + 1]; | |
var b02 = b[0 * 4 + 2]; | |
var b03 = b[0 * 4 + 3]; | |
var b10 = b[1 * 4 + 0]; | |
var b11 = b[1 * 4 + 1]; | |
var b12 = b[1 * 4 + 2]; | |
var b13 = b[1 * 4 + 3]; | |
var b20 = b[2 * 4 + 0]; | |
var b21 = b[2 * 4 + 1]; | |
var b22 = b[2 * 4 + 2]; | |
var b23 = b[2 * 4 + 3]; | |
var b30 = b[3 * 4 + 0]; | |
var b31 = b[3 * 4 + 1]; | |
var b32 = b[3 * 4 + 2]; | |
var b33 = b[3 * 4 + 3]; | |
dst[ 0] = a00 * b00 + a01 * b10 + a02 * b20 + a03 * b30; | |
dst[ 1] = a00 * b01 + a01 * b11 + a02 * b21 + a03 * b31; | |
dst[ 2] = a00 * b02 + a01 * b12 + a02 * b22 + a03 * b32; | |
dst[ 3] = a00 * b03 + a01 * b13 + a02 * b23 + a03 * b33; | |
dst[ 4] = a10 * b00 + a11 * b10 + a12 * b20 + a13 * b30; | |
dst[ 5] = a10 * b01 + a11 * b11 + a12 * b21 + a13 * b31; | |
dst[ 6] = a10 * b02 + a11 * b12 + a12 * b22 + a13 * b32; | |
dst[ 7] = a10 * b03 + a11 * b13 + a12 * b23 + a13 * b33; | |
dst[ 8] = a20 * b00 + a21 * b10 + a22 * b20 + a23 * b30; | |
dst[ 9] = a20 * b01 + a21 * b11 + a22 * b21 + a23 * b31; | |
dst[10] = a20 * b02 + a21 * b12 + a22 * b22 + a23 * b32; | |
dst[11] = a20 * b03 + a21 * b13 + a22 * b23 + a23 * b33; | |
dst[12] = a30 * b00 + a31 * b10 + a32 * b20 + a33 * b30; | |
dst[13] = a30 * b01 + a31 * b11 + a32 * b21 + a33 * b31; | |
dst[14] = a30 * b02 + a31 * b12 + a32 * b22 + a33 * b32; | |
dst[15] = a30 * b03 + a31 * b13 + a32 * b23 + a33 * b33; | |
return dst; | |
} | |
/** | |
* Computes the inverse of a matrix. | |
* @param {Matrix4} m matrix to compute inverse of | |
* @param {Matrix4} [dst] optional matrix to store result | |
* @return {Matrix4} dst or a new matrix of none provided | |
* @memberOf module:webgl-3d-math | |
*/ | |
function makeInverse(m, dst?: Float32Array) { | |
dst = dst || new Float32Array(16); | |
var m00 = m[0 * 4 + 0]; | |
var m01 = m[0 * 4 + 1]; | |
var m02 = m[0 * 4 + 2]; | |
var m03 = m[0 * 4 + 3]; | |
var m10 = m[1 * 4 + 0]; | |
var m11 = m[1 * 4 + 1]; | |
var m12 = m[1 * 4 + 2]; | |
var m13 = m[1 * 4 + 3]; | |
var m20 = m[2 * 4 + 0]; | |
var m21 = m[2 * 4 + 1]; | |
var m22 = m[2 * 4 + 2]; | |
var m23 = m[2 * 4 + 3]; | |
var m30 = m[3 * 4 + 0]; | |
var m31 = m[3 * 4 + 1]; | |
var m32 = m[3 * 4 + 2]; | |
var m33 = m[3 * 4 + 3]; | |
var tmp_0 = m22 * m33; | |
var tmp_1 = m32 * m23; | |
var tmp_2 = m12 * m33; | |
var tmp_3 = m32 * m13; | |
var tmp_4 = m12 * m23; | |
var tmp_5 = m22 * m13; | |
var tmp_6 = m02 * m33; | |
var tmp_7 = m32 * m03; | |
var tmp_8 = m02 * m23; | |
var tmp_9 = m22 * m03; | |
var tmp_10 = m02 * m13; | |
var tmp_11 = m12 * m03; | |
var tmp_12 = m20 * m31; | |
var tmp_13 = m30 * m21; | |
var tmp_14 = m10 * m31; | |
var tmp_15 = m30 * m11; | |
var tmp_16 = m10 * m21; | |
var tmp_17 = m20 * m11; | |
var tmp_18 = m00 * m31; | |
var tmp_19 = m30 * m01; | |
var tmp_20 = m00 * m21; | |
var tmp_21 = m20 * m01; | |
var tmp_22 = m00 * m11; | |
var tmp_23 = m10 * m01; | |
var t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) - | |
(tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31); | |
var t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) - | |
(tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31); | |
var t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) - | |
(tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31); | |
var t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) - | |
(tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21); | |
var d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3); | |
dst[0] = d * t0; | |
dst[1] = d * t1; | |
dst[2] = d * t2; | |
dst[3] = d * t3; | |
dst[4] = d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) - | |
(tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30)); | |
dst[5] = d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) - | |
(tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30)); | |
dst[6] = d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) - | |
(tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30)); | |
dst[7] = d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) - | |
(tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20)); | |
dst[8] = d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) - | |
(tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33)); | |
dst[9] = d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) - | |
(tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33)); | |
dst[10] = d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) - | |
(tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33)); | |
dst[11] = d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) - | |
(tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23)); | |
dst[12] = d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) - | |
(tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22)); | |
dst[13] = d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) - | |
(tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02)); | |
dst[14] = d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) - | |
(tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12)); | |
dst[15] = d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) - | |
(tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02)); | |
return dst; | |
} | |
/** | |
* Takes a matrix and a vector with 4 entries, transforms that vector by | |
* the matrix, and returns the result as a vector with 4 entries. | |
* @param {Matrix4} m The matrix. | |
* @param {Vector4} v The point in homogenous coordinates. | |
* @param {Vector4} dst optional vector4 to store result | |
* @return {Vector4} dst or new Vector4 if not provided | |
* @memberOf module:webgl-3d-math | |
*/ | |
function matrixVectorMultiply(v, m, dst?: Float32Array) { | |
dst = dst || new Float32Array(4); | |
for (var i = 0; i < 4; ++i) { | |
dst[i] = 0.0; | |
for (var j = 0; j < 4; ++j) { | |
dst[i] += v[j] * m[j * 4 + i]; | |
} | |
} | |
return dst; | |
} | |
/** | |
* Takes a 4-by-4 matrix and a vector with 3 entries, | |
* interprets the vector as a point, transforms that point by the matrix, and | |
* returns the result as a vector with 3 entries. | |
* @param {Matrix4} m The matrix. | |
* @param {Vector3} v The point. | |
* @param {Vector4} dst optional vector4 to store result | |
* @return {Vector4} dst or new Vector4 if not provided | |
* @memberOf module:webgl-3d-math | |
*/ | |
function transformPoint(m, v, dst?: Float32Array) { | |
dst = dst || new Float32Array(3); | |
var v0 = v[0]; | |
var v1 = v[1]; | |
var v2 = v[2]; | |
var d = v0 * m[0 * 4 + 3] + v1 * m[1 * 4 + 3] + v2 * m[2 * 4 + 3] + m[3 * 4 + 3]; | |
dst[0] = (v0 * m[0 * 4 + 0] + v1 * m[1 * 4 + 0] + v2 * m[2 * 4 + 0] + m[3 * 4 + 0]) / d; | |
dst[1] = (v0 * m[0 * 4 + 1] + v1 * m[1 * 4 + 1] + v2 * m[2 * 4 + 1] + m[3 * 4 + 1]) / d; | |
dst[2] = (v0 * m[0 * 4 + 2] + v1 * m[1 * 4 + 2] + v2 * m[2 * 4 + 2] + m[3 * 4 + 2]) / d; | |
return dst; | |
} | |
/** | |
* Takes a 4-by-4 matrix and a vector with 3 entries, interprets the vector as a | |
* direction, transforms that direction by the matrix, and returns the result; | |
* assumes the transformation of 3-dimensional space represented by the matrix | |
* is parallel-preserving, i.e. any combination of rotation, scaling and | |
* translation, but not a perspective distortion. Returns a vector with 3 | |
* entries. | |
* @param {Matrix4} m The matrix. | |
* @param {Vector3} v The direction. | |
* @param {Vector4} dst optional vector4 to store result | |
* @return {Vector4} dst or new Vector4 if not provided | |
* @memberOf module:webgl-3d-math | |
*/ | |
function transformDirection(m, v, dst?: Float32Array) { | |
dst = dst || new Float32Array(3); | |
var v0 = v[0]; | |
var v1 = v[1]; | |
var v2 = v[2]; | |
dst[0] = v0 * m[0 * 4 + 0] + v1 * m[1 * 4 + 0] + v2 * m[2 * 4 + 0]; | |
dst[1] = v0 * m[0 * 4 + 1] + v1 * m[1 * 4 + 1] + v2 * m[2 * 4 + 1]; | |
dst[2] = v0 * m[0 * 4 + 2] + v1 * m[1 * 4 + 2] + v2 * m[2 * 4 + 2]; | |
return dst; | |
} | |
/** | |
* Takes a 4-by-4 matrix m and a vector v with 3 entries, interprets the vector | |
* as a normal to a surface, and computes a vector which is normal upon | |
* transforming that surface by the matrix. The effect of this function is the | |
* same as transforming v (as a direction) by the inverse-transpose of m. This | |
* function assumes the transformation of 3-dimensional space represented by the | |
* matrix is parallel-preserving, i.e. any combination of rotation, scaling and | |
* translation, but not a perspective distortion. Returns a vector with 3 | |
* entries. | |
* @param {Matrix4} m The matrix. | |
* @param {Vector3} v The normal. | |
* @param {Vector3} [dst] The direction. | |
* @return {Vector3} The transformed direction. | |
* @memberOf module:webgl-3d-math | |
*/ | |
function transformNormal(m, v, dst?: Float32Array) { | |
dst = dst || new Float32Array(3); | |
var mi = makeInverse(m); | |
var v0 = v[0]; | |
var v1 = v[1]; | |
var v2 = v[2]; | |
dst[0] = v0 * mi[0 * 4 + 0] + v1 * mi[0 * 4 + 1] + v2 * mi[0 * 4 + 2]; | |
dst[1] = v0 * mi[1 * 4 + 0] + v1 * mi[1 * 4 + 1] + v2 * mi[1 * 4 + 2]; | |
dst[2] = v0 * mi[2 * 4 + 0] + v1 * mi[2 * 4 + 1] + v2 * mi[2 * 4 + 2]; | |
return dst; | |
} | |
function copyMatrix(src, dst?: Float32Array) { | |
dst = dst || new Float32Array(16); | |
dst[ 0] = src[ 0]; | |
dst[ 1] = src[ 1]; | |
dst[ 2] = src[ 2]; | |
dst[ 3] = src[ 3]; | |
dst[ 4] = src[ 4]; | |
dst[ 5] = src[ 5]; | |
dst[ 6] = src[ 6]; | |
dst[ 7] = src[ 7]; | |
dst[ 8] = src[ 8]; | |
dst[ 9] = src[ 9]; | |
dst[10] = src[10]; | |
dst[11] = src[11]; | |
dst[12] = src[12]; | |
dst[13] = src[13]; | |
dst[14] = src[14]; | |
dst[15] = src[15]; | |
return dst; | |
} | |
//=================================== | |
// primitives | |
//=================================== | |
/** | |
* Given indexed vertices creates a new set of vertices unindexed by expanding the indexed vertices. | |
* @param {Object.<string, TypedArray>} vertices The indexed vertices to deindex | |
* @return {Object.<string, TypedArray>} The deindexed vertices | |
* @memberOf module:primitives | |
*/ | |
function deindexVertices(vertices) { | |
var indices = vertices.indices; | |
var newVertices = {}; | |
var numElements = indices.length; | |
function expandToUnindexed(channel) { | |
var srcBuffer = vertices[channel]; | |
var numComponents = srcBuffer.numComponents; | |
var dstBuffer = createAugmentedTypedArray(numComponents, numElements, srcBuffer.constructor); | |
for (var ii = 0; ii < numElements; ++ii) { | |
var ndx = indices[ii]; | |
var offset = ndx * numComponents; | |
for (var jj = 0; jj < numComponents; ++jj) { | |
dstBuffer.push(srcBuffer[offset + jj]); | |
} | |
} | |
newVertices[channel] = dstBuffer; | |
} | |
Object.keys(vertices).filter(allButIndices).forEach(expandToUnindexed); | |
return newVertices; | |
} | |
/** | |
* flattens the normals of deindexed vertices in place. | |
* @param {Object.<string, TypedArray>} vertices The deindexed vertices who's normals to flatten | |
* @return {Object.<string, TypedArray>} The flattened vertices (same as was passed in) | |
* @memberOf module:primitives | |
*/ | |
function flattenNormals(vertices) { | |
if (vertices.indices) { | |
throw "can't flatten normals of indexed vertices. deindex them first"; | |
} | |
var normals = vertices.normal; | |
var numNormals = normals.length; | |
for (var ii = 0; ii < numNormals; ii += 9) { | |
// pull out the 3 normals for this triangle | |
var nax = normals[ii + 0]; | |
var nay = normals[ii + 1]; | |
var naz = normals[ii + 2]; | |
var nbx = normals[ii + 3]; | |
var nby = normals[ii + 4]; | |
var nbz = normals[ii + 5]; | |
var ncx = normals[ii + 6]; | |
var ncy = normals[ii + 7]; | |
var ncz = normals[ii + 8]; | |
// add them | |
var nx = nax + nbx + ncx; | |
var ny = nay + nby + ncy; | |
var nz = naz + nbz + ncz; | |
// normalize them | |
var length = Math.sqrt(nx * nx + ny * ny + nz * nz); | |
nx /= length; | |
ny /= length; | |
nz /= length; | |
// copy them back in | |
normals[ii + 0] = nx; | |
normals[ii + 1] = ny; | |
normals[ii + 2] = nz; | |
normals[ii + 3] = nx; | |
normals[ii + 4] = ny; | |
normals[ii + 5] = nz; | |
normals[ii + 6] = nx; | |
normals[ii + 7] = ny; | |
normals[ii + 8] = nz; | |
} | |
return vertices; | |
} | |
function applyFuncToV3Array(array, matrix, fn) { | |
var len = array.length; | |
var tmp = new Float32Array(3); | |
for (var ii = 0; ii < len; ii += 3) { | |
fn(matrix, [array[ii], array[ii + 1], array[ii + 2]], tmp); | |
array[ii ] = tmp[0]; | |
array[ii + 1] = tmp[1]; | |
array[ii + 2] = tmp[2]; | |
} | |
} | |
/** | |
* Reorients directions by the given matrix.. | |
* @param {number[]|TypedArray} array The array. Assumes value floats per element. | |
* @param {Matrix} matrix A matrix to multiply by. | |
* @return {number[]|TypedArray} the same array that was passed in | |
* @memberOf module:primitives | |
*/ | |
function reorientDirections(array, matrix) { | |
applyFuncToV3Array(array, matrix, transformDirection); | |
return array; | |
} | |
/** | |
* Reorients normals by the inverse-transpose of the given | |
* matrix.. | |
* @param {number[]|TypedArray} array The array. Assumes value floats per element. | |
* @param {Matrix} matrix A matrix to multiply by. | |
* @return {number[]|TypedArray} the same array that was passed in | |
* @memberOf module:primitives | |
*/ | |
function reorientNormals(array, matrix) { | |
applyFuncToV3Array(array, makeInverse(matrix), transformNormal); | |
return array; | |
} | |
/** | |
* Reorients positions by the given matrix. In other words, it | |
* multiplies each vertex by the given matrix. | |
* @param {number[]|TypedArray} array The array. Assumes value floats per element. | |
* @param {Matrix} matrix A matrix to multiply by. | |
* @return {number[]|TypedArray} the same array that was passed in | |
* @memberOf module:primitives | |
*/ | |
function reorientPositions(array, matrix) { | |
applyFuncToV3Array(array, matrix, transformPoint); | |
return array; | |
} | |
/** | |
* Reorients arrays by the given matrix. Assumes arrays have | |
* names that contains 'pos' could be reoriented as positions, | |
* 'binorm' or 'tan' as directions, and 'norm' as normals. | |
* | |
* @param {Object.<string, (number[]|TypedArray)>} arrays The vertices to reorient | |
* @param {Matrix} matrix matrix to reorient by. | |
* @return {Object.<string, (number[]|TypedArray)>} same arrays that were passed in. | |
* @memberOf module:primitives | |
*/ | |
function reorientVertices(arrays, matrix) { | |
Object.keys(arrays).forEach(function(name) { | |
var array = arrays[name]; | |
if (name.indexOf("pos") >= 0) { | |
reorientPositions(array, matrix); | |
} else if (name.indexOf("tan") >= 0 || name.indexOf("binorm") >= 0) { | |
reorientDirections(array, matrix); | |
} else if (name.indexOf("norm") >= 0) { | |
reorientNormals(array, matrix); | |
} | |
}); | |
return arrays; | |
} | |
/** | |
* creates a random integer between 0 and range - 1 inclusive. | |
* @param {number} range | |
* @return {number} random value between 0 and range - 1 inclusive. | |
*/ | |
function randInt(range: number): number { | |
return Math.random() * range | 0; | |
} | |
/** | |
* Used to supply random colors | |
* @callback RandomColorFunc | |
* @param {number} ndx index of triangle/quad if unindexed or index of vertex if indexed | |
* @param {number} channel 0 = red, 1 = green, 2 = blue, 3 = alpha | |
* @return {number} a number from 0 to 255 | |
* @memberOf module:primitives | |
*/ | |
/** | |
* @typedef {Object} RandomVerticesOptions | |
* @property {number} [vertsPerColor] Defaults to 3 for non-indexed vertices | |
* @property {module:primitives.RandomColorFunc} [rand] A function to generate random numbers | |
* @memberOf module:primitives | |
*/ | |
/** | |
* Creates an augmentedTypedArray of random vertex colors. | |
* If the vertices are indexed (have an indices array) then will | |
* just make random colors. Otherwise assumes they are triangless | |
* and makes one random color for every 3 vertices. | |
* @param {Object.<string, augmentedTypedArray>} vertices Vertices as returned from one of the createXXXVertices functions. | |
* @param {module:primitives.RandomVerticesOptions} [options] options. | |
* @return {Object.<string, augmentedTypedArray>} same vertices as passed in with `color` added. | |
* @memberOf module:primitives | |
*/ | |
function makeRandomVertexColors(vertices, options) { | |
options = options || {}; | |
var numElements = vertices.position.numElements; | |
var vcolors = createAugmentedTypedArray(4, numElements, Uint8Array); | |
var ii; | |
var rand = options.rand || function(ndx, channel) { | |
return channel < 3 ? randInt(256) : 255; | |
}; | |
vertices.color = vcolors; | |
if (vertices.indices) { | |
// just make random colors if index | |
for (ii = 0; ii < numElements; ++ii) { | |
vcolors.push(rand(ii, 0), rand(ii, 1), rand(ii, 2), rand(ii, 3)); | |
} | |
} else { | |
// make random colors per triangle | |
var numVertsPerColor = options.vertsPerColor || 3; | |
var numSets = numElements / numVertsPerColor; | |
for (ii = 0; ii < numSets; ++ii) { | |
var color = [rand(ii, 0), rand(ii, 1), rand(ii, 2), rand(ii, 3)]; | |
for (var jj = 0; jj < numVertsPerColor; ++jj) { | |
vcolors.push(color); | |
} | |
} | |
} | |
return vertices; | |
} | |
/** | |
* creates a function that calls fn to create vertices and then | |
* creates a buffers for them | |
*/ | |
function createBufferFunc(fn) { | |
return function(gl: WebGLRenderingContext, arg1?: any, arg2?: any, arg3?: any) { | |
var arrays = fn.apply(this, Array.prototype.slice.call(arguments, 1)); | |
return createBuffersFromArrays(gl, arrays); | |
}; | |
} | |
/** | |
* creates a function that calls fn to create vertices and then | |
* creates a bufferInfo object for them | |
*/ | |
function createBufferInfoFunc(fn) { | |
return function(gl) { | |
var arrays = fn.apply(null, Array.prototype.slice.call(arguments, 1)); | |
return createBufferInfoFromArrays(gl, arrays); | |
}; | |
} | |
/** | |
* Creates XZ plane vertices. | |
* The created plane has position, normal and uv streams. | |
* | |
* @param {number} [width] Width of the plane. Default = 1 | |
* @param {number} [depth] Depth of the plane. Default = 1 | |
* @param {number} [subdivisionsWidth] Number of steps across the plane. Default = 1 | |
* @param {number} [subdivisionsDepth] Number of steps down the plane. Default = 1 | |
* @param {Matrix4} [matrix] A matrix by which to multiply all the vertices. | |
* @return {Object.<string, TypedArray>} The | |
* created plane vertices. | |
* @memberOf module:primitives | |
*/ | |
function createPlaneVertices( | |
width, | |
depth, | |
subdivisionsWidth, | |
subdivisionsDepth, | |
matrix) { | |
width = width || 1; | |
depth = depth || 1; | |
subdivisionsWidth = subdivisionsWidth || 1; | |
subdivisionsDepth = subdivisionsDepth || 1; | |
matrix = matrix || makeIdentity(); | |
var numVertices = (subdivisionsWidth + 1) * (subdivisionsDepth + 1); | |
var positions = createAugmentedTypedArray(3, numVertices); | |
var normals = createAugmentedTypedArray(3, numVertices); | |
var texcoords = createAugmentedTypedArray(2, numVertices); | |
for (var z = 0; z <= subdivisionsDepth; z++) { | |
for (var x = 0; x <= subdivisionsWidth; x++) { | |
var u = x / subdivisionsWidth; | |
var v = z / subdivisionsDepth; | |
positions.push( | |
width * u - width * 0.5, | |
0, | |
depth * v - depth * 0.5); | |
normals.push(0, 1, 0); | |
texcoords.push(u, v); | |
} | |
} | |
var numVertsAcross = subdivisionsWidth + 1; | |
var indices = createAugmentedTypedArray( | |
3, subdivisionsWidth * subdivisionsDepth * 2, Uint16Array); | |
for (var z = 0; z < subdivisionsDepth; z++) { | |
for (var x = 0; x < subdivisionsWidth; x++) { | |
// Make triangle 1 of quad. | |
indices.push( | |
(z + 0) * numVertsAcross + x, | |
(z + 1) * numVertsAcross + x, | |
(z + 0) * numVertsAcross + x + 1); | |
// Make triangle 2 of quad. | |
indices.push( | |
(z + 1) * numVertsAcross + x, | |
(z + 1) * numVertsAcross + x + 1, | |
(z + 0) * numVertsAcross + x + 1); | |
} | |
} | |
var arrays = reorientVertices({ | |
position: positions, | |
normal: normals, | |
texcoord: texcoords, | |
indices: indices, | |
}, matrix); | |
return arrays; | |
} | |
/** | |
* Creates sphere vertices. | |
* The created sphere has position, normal and uv streams. | |
* | |
* @param {number} radius radius of the sphere. | |
* @param {number} subdivisionsAxis number of steps around the sphere. | |
* @param {number} subdivisionsHeight number of vertically on the sphere. | |
* @param {number} [opt_startLatitudeInRadians] where to start the | |
* top of the sphere. Default = 0. | |
* @param {number} [opt_endLatitudeInRadians] Where to end the | |
* bottom of the sphere. Default = Math.PI. | |
* @param {number} [opt_startLongitudeInRadians] where to start | |
* wrapping the sphere. Default = 0. | |
* @param {number} [opt_endLongitudeInRadians] where to end | |
* wrapping the sphere. Default = 2 * Math.PI. | |
* @return {Object.<string, TypedArray>} The | |
* created plane vertices. | |
* @memberOf module:primitives | |
*/ | |
function createSphereVertices( | |
radius, | |
subdivisionsAxis, | |
subdivisionsHeight, | |
opt_startLatitudeInRadians, | |
opt_endLatitudeInRadians, | |
opt_startLongitudeInRadians, | |
opt_endLongitudeInRadians) { | |
if (subdivisionsAxis <= 0 || subdivisionsHeight <= 0) { | |
throw Error('subdivisionAxis and subdivisionHeight must be > 0'); | |
} | |
opt_startLatitudeInRadians = opt_startLatitudeInRadians || 0; | |
opt_endLatitudeInRadians = opt_endLatitudeInRadians || Math.PI; | |
opt_startLongitudeInRadians = opt_startLongitudeInRadians || 0; | |
opt_endLongitudeInRadians = opt_endLongitudeInRadians || (Math.PI * 2); | |
var latRange = opt_endLatitudeInRadians - opt_startLatitudeInRadians; | |
var longRange = opt_endLongitudeInRadians - opt_startLongitudeInRadians; | |
// We are going to generate our sphere by iterating through its | |
// spherical coordinates and generating 2 triangles for each quad on a | |
// ring of the sphere. | |
var numVertices = (subdivisionsAxis + 1) * (subdivisionsHeight + 1); | |
var positions = createAugmentedTypedArray(3, numVertices); | |
var normals = createAugmentedTypedArray(3, numVertices); | |
var texCoords = createAugmentedTypedArray(2 , numVertices); | |
// Generate the individual vertices in our vertex buffer. | |
for (var y = 0; y <= subdivisionsHeight; y++) { | |
for (var x = 0; x <= subdivisionsAxis; x++) { | |
// Generate a vertex based on its spherical coordinates | |
var u = x / subdivisionsAxis; | |
var v = y / subdivisionsHeight; | |
var theta = longRange * u; | |
var phi = latRange * v; | |
var sinTheta = Math.sin(theta); | |
var cosTheta = Math.cos(theta); | |
var sinPhi = Math.sin(phi); | |
var cosPhi = Math.cos(phi); | |
var ux = cosTheta * sinPhi; | |
var uy = cosPhi; | |
var uz = sinTheta * sinPhi; | |
positions.push(radius * ux, radius * uy, radius * uz); | |
normals.push(ux, uy, uz); | |
texCoords.push(1 - u, v); | |
} | |
} | |
var numVertsAround = subdivisionsAxis + 1; | |
var indices = createAugmentedTypedArray(3, subdivisionsAxis * subdivisionsHeight * 2, Uint16Array); | |
for (var x = 0; x < subdivisionsAxis; x++) { | |
for (var y = 0; y < subdivisionsHeight; y++) { | |
// Make triangle 1 of quad. | |
indices.push( | |
(y + 0) * numVertsAround + x, | |
(y + 0) * numVertsAround + x + 1, | |
(y + 1) * numVertsAround + x); | |
// Make triangle 2 of quad. | |
indices.push( | |
(y + 1) * numVertsAround + x, | |
(y + 0) * numVertsAround + x + 1, | |
(y + 1) * numVertsAround + x + 1); | |
} | |
} | |
return { | |
position: positions, | |
normal: normals, | |
texcoord: texCoords, | |
indices: indices, | |
}; | |
} | |
/** | |
* Array of the indices of corners of each face of a cube. | |
* @type {Array.<number[]>} | |
*/ | |
var CUBE_FACE_INDICES = [ | |
[3, 7, 5, 1], // right | |
[6, 2, 0, 4], // left | |
[6, 7, 3, 2], // ?? | |
[0, 1, 5, 4], // ?? | |
[7, 6, 4, 5], // front | |
[2, 3, 1, 0], // back | |
]; | |
/** | |
* Creates the vertices and indices for a cube. The | |
* cube will be created around the origin. (-size / 2, size / 2) | |
* | |
* @param {number} size Width, height and depth of the cube. | |
* @return {Object.<string, TypedArray>} The | |
* created plane vertices. | |
* @memberOf module:primitives | |
*/ | |
function createCubeVertices(size) { | |
var k = size / 2; | |
var cornerVertices = [ | |
[-k, -k, -k], | |
[+k, -k, -k], | |
[-k, +k, -k], | |
[+k, +k, -k], | |
[-k, -k, +k], | |
[+k, -k, +k], | |
[-k, +k, +k], | |
[+k, +k, +k], | |
]; | |
var faceNormals = [ | |
[+1, +0, +0], | |
[-1, +0, +0], | |
[+0, +1, +0], | |
[+0, -1, +0], | |
[+0, +0, +1], | |
[+0, +0, -1], | |
]; | |
var uvCoords = [ | |
[1, 0], | |
[0, 0], | |
[0, 1], | |
[1, 1], | |
]; | |
var numVertices = 6 * 4; | |
var positions = createAugmentedTypedArray(3, numVertices); | |
var normals = createAugmentedTypedArray(3, numVertices); | |
var texCoords = createAugmentedTypedArray(2 , numVertices); | |
var indices = createAugmentedTypedArray(3, 6 * 2, Uint16Array); | |
for (var f = 0; f < 6; ++f) { | |
var faceIndices = CUBE_FACE_INDICES[f]; | |
for (var v = 0; v < 4; ++v) { | |
var position = cornerVertices[faceIndices[v]]; | |
var normal = faceNormals[f]; | |
var uv = uvCoords[v]; | |
// Each face needs all four vertices because the normals and texture | |
// coordinates are not all the same. | |
positions.push(position); | |
normals.push(normal); | |
texCoords.push(uv); | |
} | |
// Two triangles make a square face. | |
var offset = 4 * f; | |
indices.push(offset + 0, offset + 1, offset + 2); | |
indices.push(offset + 0, offset + 2, offset + 3); | |
} | |
return { | |
position: positions, | |
normal: normals, | |
texcoord: texCoords, | |
indices: indices, | |
}; | |
} | |
/** | |
* Creates vertices for a truncated cone, which is like a cylinder | |
* except that it has different top and bottom radii. A truncated cone | |
* can also be used to create cylinders and regular cones. The | |
* truncated cone will be created centered about the origin, with the | |
* y axis as its vertical axis. The created cone has position, normal | |
* and uv streams. | |
* | |
* @param {number} bottomRadius Bottom radius of truncated cone. | |
* @param {number} topRadius Top radius of truncated cone. | |
* @param {number} height Height of truncated cone. | |
* @param {number} radialSubdivisions The number of subdivisions around the | |
* truncated cone. | |
* @param {number} verticalSubdivisions The number of subdivisions down the | |
* truncated cone. | |
* @param {boolean} [opt_topCap] Create top cap. Default = true. | |
* @param {boolean} [opt_bottomCap] Create bottom cap. Default = | |
* true. | |
* @return {Object.<string, TypedArray>} The | |
* created plane vertices. | |
* @memberOf module:primitives | |
*/ | |
function createTruncatedConeVertices( | |
bottomRadius, | |
topRadius, | |
height, | |
radialSubdivisions, | |
verticalSubdivisions, | |
opt_topCap, | |
opt_bottomCap) { | |
if (radialSubdivisions < 3) { | |
throw Error('radialSubdivisions must be 3 or greater'); | |
} | |
if (verticalSubdivisions < 1) { | |
throw Error('verticalSubdivisions must be 1 or greater'); | |
} | |
var topCap = (opt_topCap === undefined) ? true : opt_topCap; | |
var bottomCap = (opt_bottomCap === undefined) ? true : opt_bottomCap; | |
var extra = (topCap ? 2 : 0) + (bottomCap ? 2 : 0); | |
var numVertices = (radialSubdivisions + 1) * (verticalSubdivisions + 1 + extra); | |
var positions = createAugmentedTypedArray(3, numVertices); | |
var normals = createAugmentedTypedArray(3, numVertices); | |
var texCoords = createAugmentedTypedArray(2, numVertices); | |
var indices = createAugmentedTypedArray(3, radialSubdivisions * (verticalSubdivisions + extra) * 2, Uint16Array); | |
var vertsAroundEdge = radialSubdivisions + 1; | |
// The slant of the cone is constant across its surface | |
var slant = Math.atan2(bottomRadius - topRadius, height); | |
var cosSlant = Math.cos(slant); | |
var sinSlant = Math.sin(slant); | |
var start = topCap ? -2 : 0; | |
var end = verticalSubdivisions + (bottomCap ? 2 : 0); | |
for (var yy = start; yy <= end; ++yy) { | |
var v = yy / verticalSubdivisions; | |
var y = height * v; | |
var ringRadius; | |
if (yy < 0) { | |
y = 0; | |
v = 1; | |
ringRadius = bottomRadius; | |
} else if (yy > verticalSubdivisions) { | |
y = height; | |
v = 1; | |
ringRadius = topRadius; | |
} else { | |
ringRadius = bottomRadius + | |
(topRadius - bottomRadius) * (yy / verticalSubdivisions); | |
} | |
if (yy === -2 || yy === verticalSubdivisions + 2) { | |
ringRadius = 0; | |
v = 0; | |
} | |
y -= height / 2; | |
for (var ii = 0; ii < vertsAroundEdge; ++ii) { | |
var sin = Math.sin(ii * Math.PI * 2 / radialSubdivisions); | |
var cos = Math.cos(ii * Math.PI * 2 / radialSubdivisions); | |
positions.push(sin * ringRadius, y, cos * ringRadius); | |
normals.push( | |
(yy < 0 || yy > verticalSubdivisions) ? 0 : (sin * cosSlant), | |
(yy < 0) ? -1 : (yy > verticalSubdivisions ? 1 : sinSlant), | |
(yy < 0 || yy > verticalSubdivisions) ? 0 : (cos * cosSlant)); | |
texCoords.push((ii / radialSubdivisions), 1 - v); | |
} | |
} | |
for (var yy = 0; yy < verticalSubdivisions + extra; ++yy) { | |
for (var ii = 0; ii < radialSubdivisions; ++ii) { | |
indices.push(vertsAroundEdge * (yy + 0) + 0 + ii, | |
vertsAroundEdge * (yy + 0) + 1 + ii, | |
vertsAroundEdge * (yy + 1) + 1 + ii); | |
indices.push(vertsAroundEdge * (yy + 0) + 0 + ii, | |
vertsAroundEdge * (yy + 1) + 1 + ii, | |
vertsAroundEdge * (yy + 1) + 0 + ii); | |
} | |
} | |
return { | |
position: positions, | |
normal: normals, | |
texcoord: texCoords, | |
indices: indices, | |
}; | |
} | |
/** | |
* Expands RLE data | |
* @param {number[]} rleData data in format of run-length, x, y, z, run-length, x, y, z | |
* @param {number[]} [padding] value to add each entry with. | |
* @return {number[]} the expanded rleData | |
*/ | |
function expandRLEData(rleData: number[], padding?: number[]): number[] { | |
padding = padding || []; | |
var data: number[] = []; | |
for (var ii = 0; ii < rleData.length; ii += 4) { | |
var runLength = rleData[ii]; | |
var element = rleData.slice(ii + 1, ii + 4); | |
element.push.apply(element, padding); | |
for (var jj = 0; jj < runLength; ++jj) { | |
data.push.apply(data, element); | |
} | |
} | |
return data; | |
} | |
/** | |
* Creates 3D 'F' vertices. | |
* An 'F' is useful because you can easily tell which way it is oriented. | |
* The created 'F' has position, normal and uv streams. | |
* | |
* @return {Object.<string, TypedArray>} The | |
* created plane vertices. | |
* @memberOf module:primitives | |
*/ | |
function create3DFVertices() { | |
var positions = [ | |
// left column front | |
0, 0, 0, | |
0, 150, 0, | |
30, 0, 0, | |
0, 150, 0, | |
30, 150, 0, | |
30, 0, 0, | |
// top rung front | |
30, 0, 0, | |
30, 30, 0, | |
100, 0, 0, | |
30, 30, 0, | |
100, 30, 0, | |
100, 0, 0, | |
// middle rung front | |
30, 60, 0, | |
30, 90, 0, | |
67, 60, 0, | |
30, 90, 0, | |
67, 90, 0, | |
67, 60, 0, | |
// left column back | |
0, 0, 30, | |
30, 0, 30, | |
0, 150, 30, | |
0, 150, 30, | |
30, 0, 30, | |
30, 150, 30, | |
// top rung back | |
30, 0, 30, | |
100, 0, 30, | |
30, 30, 30, | |
30, 30, 30, | |
100, 0, 30, | |
100, 30, 30, | |
// middle rung back | |
30, 60, 30, | |
67, 60, 30, | |
30, 90, 30, | |
30, 90, 30, | |
67, 60, 30, | |
67, 90, 30, | |
// top | |
0, 0, 0, | |
100, 0, 0, | |
100, 0, 30, | |
0, 0, 0, | |
100, 0, 30, | |
0, 0, 30, | |
// top rung front | |
100, 0, 0, | |
100, 30, 0, | |
100, 30, 30, | |
100, 0, 0, | |
100, 30, 30, | |
100, 0, 30, | |
// under top rung | |
30, 30, 0, | |
30, 30, 30, | |
100, 30, 30, | |
30, 30, 0, | |
100, 30, 30, | |
100, 30, 0, | |
// between top rung and middle | |
30, 30, 0, | |
30, 60, 30, | |
30, 30, 30, | |
30, 30, 0, | |
30, 60, 0, | |
30, 60, 30, | |
// top of middle rung | |
30, 60, 0, | |
67, 60, 30, | |
30, 60, 30, | |
30, 60, 0, | |
67, 60, 0, | |
67, 60, 30, | |
// front of middle rung | |
67, 60, 0, | |
67, 90, 30, | |
67, 60, 30, | |
67, 60, 0, | |
67, 90, 0, | |
67, 90, 30, | |
// bottom of middle rung. | |
30, 90, 0, | |
30, 90, 30, | |
67, 90, 30, | |
30, 90, 0, | |
67, 90, 30, | |
67, 90, 0, | |
// front of bottom | |
30, 90, 0, | |
30, 150, 30, | |
30, 90, 30, | |
30, 90, 0, | |
30, 150, 0, | |
30, 150, 30, | |
// bottom | |
0, 150, 0, | |
0, 150, 30, | |
30, 150, 30, | |
0, 150, 0, | |
30, 150, 30, | |
30, 150, 0, | |
// left side | |
0, 0, 0, | |
0, 0, 30, | |
0, 150, 30, | |
0, 0, 0, | |
0, 150, 30, | |
0, 150, 0, | |
]; | |
var texcoords = [ | |
// left column front | |
0.22, 0.19, | |
0.22, 0.79, | |
0.34, 0.19, | |
0.22, 0.79, | |
0.34, 0.79, | |
0.34, 0.19, | |
// top rung front | |
0.34, 0.19, | |
0.34, 0.31, | |
0.62, 0.19, | |
0.34, 0.31, | |
0.62, 0.31, | |
0.62, 0.19, | |
// middle rung front | |
0.34, 0.43, | |
0.34, 0.55, | |
0.49, 0.43, | |
0.34, 0.55, | |
0.49, 0.55, | |
0.49, 0.43, | |
// left column back | |
0, 0, | |
1, 0, | |
0, 1, | |
0, 1, | |
1, 0, | |
1, 1, | |
// top rung back | |
0, 0, | |
1, 0, | |
0, 1, | |
0, 1, | |
1, 0, | |
1, 1, | |
// middle rung back | |
0, 0, | |
1, 0, | |
0, 1, | |
0, 1, | |
1, 0, | |
1, 1, | |
// top | |
0, 0, | |
1, 0, | |
1, 1, | |
0, 0, | |
1, 1, | |
0, 1, | |
// top rung front | |
0, 0, | |
1, 0, | |
1, 1, | |
0, 0, | |
1, 1, | |
0, 1, | |
// under top rung | |
0, 0, | |
0, 1, | |
1, 1, | |
0, 0, | |
1, 1, | |
1, 0, | |
// between top rung and middle | |
0, 0, | |
1, 1, | |
0, 1, | |
0, 0, | |
1, 0, | |
1, 1, | |
// top of middle rung | |
0, 0, | |
1, 1, | |
0, 1, | |
0, 0, | |
1, 0, | |
1, 1, | |
// front of middle rung | |
0, 0, | |
1, 1, | |
0, 1, | |
0, 0, | |
1, 0, | |
1, 1, | |
// bottom of middle rung. | |
0, 0, | |
0, 1, | |
1, 1, | |
0, 0, | |
1, 1, | |
1, 0, | |
// front of bottom | |
0, 0, | |
1, 1, | |
0, 1, | |
0, 0, | |
1, 0, | |
1, 1, | |
// bottom | |
0, 0, | |
0, 1, | |
1, 1, | |
0, 0, | |
1, 1, | |
1, 0, | |
// left side | |
0, 0, | |
0, 1, | |
1, 1, | |
0, 0, | |
1, 1, | |
1, 0, | |
]; | |
var normals = expandRLEData([ | |
// left column front | |
// top rung front | |
// middle rung front | |
18, 0, 0, 1, | |
// left column back | |
// top rung back | |
// middle rung back | |
18, 0, 0, -1, | |
// top | |
6, 0, 1, 0, | |
// top rung front | |
6, 1, 0, 0, | |
// under top rung | |
6, 0, -1, 0, | |
// between top rung and middle | |
6, 1, 0, 0, | |
// top of middle rung | |
6, 0, 1, 0, | |
// front of middle rung | |
6, 1, 0, 0, | |
// bottom of middle rung. | |
6, 0, -1, 0, | |
// front of bottom | |
6, 1, 0, 0, | |
// bottom | |
6, 0, -1, 0, | |
// left side | |
6, -1, 0, 0, | |
]); | |
var colors = expandRLEData([ | |
// left column front | |
// top rung front | |
// middle rung front | |
18, 200, 70, 120, | |
// left column back | |
// top rung back | |
// middle rung back | |
18, 80, 70, 200, | |
// top | |
6, 70, 200, 210, | |
// top rung front | |
6, 200, 200, 70, | |
// under top rung | |
6, 210, 100, 70, | |
// between top rung and middle | |
6, 210, 160, 70, | |
// top of middle rung | |
6, 70, 180, 210, | |
// front of middle rung | |
6, 100, 70, 210, | |
// bottom of middle rung. | |
6, 76, 210, 100, | |
// front of bottom | |
6, 140, 210, 80, | |
// bottom | |
6, 90, 130, 110, | |
// left side | |
6, 160, 160, 220, | |
], [255]); | |
var numVerts = positions.length / 3; | |
var arrays = { | |
position: createAugmentedTypedArray(3, numVerts), | |
texcoord: createAugmentedTypedArray(2, numVerts), | |
normal: createAugmentedTypedArray(3, numVerts), | |
color: createAugmentedTypedArray(4, numVerts, Uint8Array), | |
indices: createAugmentedTypedArray(3, numVerts / 3, Uint16Array), | |
}; | |
arrays.position.push(positions); | |
arrays.texcoord.push(texcoords); | |
arrays.normal.push(normals); | |
arrays.color.push(colors); | |
for (var ii = 0; ii < numVerts; ++ii) { | |
arrays.indices.push(ii); | |
} | |
return arrays; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<style> | |
/* STYLE-MARKER */ | |
</style> | |
<!-- SCRIPTS-MARKER --> | |
</head> | |
<body> | |
<pre id='info'></pre> | |
<script> | |
// LIBS-MARKER | |
</script> | |
<script> | |
// CODE-MARKER | |
</script> | |
<!-- vertex shader --> | |
<script id="3d-vertex-shader" type="x-shader/x-vertex"> | |
uniform mat4 u_worldViewProjection; | |
uniform vec3 u_lightWorldPos; | |
uniform mat4 u_world; | |
uniform mat4 u_viewInverse; | |
uniform mat4 u_worldInverseTranspose; | |
attribute vec4 a_position; | |
attribute vec3 a_normal; | |
attribute vec2 a_texcoord; | |
varying vec4 v_position; | |
varying vec2 v_texCoord; | |
varying vec3 v_normal; | |
varying vec3 v_surfaceToLight; | |
varying vec3 v_surfaceToView; | |
void main() { | |
v_texCoord = a_texcoord; | |
v_position = (u_worldViewProjection * a_position); | |
v_normal = (u_worldInverseTranspose * vec4(a_normal, 0)).xyz; | |
v_surfaceToLight = u_lightWorldPos - (u_world * a_position).xyz; | |
v_surfaceToView = (u_viewInverse[3] - (u_world * a_position)).xyz; | |
gl_Position = v_position; | |
} | |
</script> | |
<!-- fragment shader --> | |
<script id="3d-fragment-shader" type="x-shader/x-fragment"> | |
precision mediump float; | |
varying vec4 v_position; | |
varying vec2 v_texCoord; | |
varying vec3 v_normal; | |
varying vec3 v_surfaceToLight; | |
varying vec3 v_surfaceToView; | |
uniform vec4 u_lightColor; | |
uniform vec4 u_colorMult; | |
uniform sampler2D u_diffuse; | |
uniform vec4 u_specular; | |
uniform float u_shininess; | |
uniform float u_specularFactor; | |
vec4 lit(float l ,float h, float m) { | |
return vec4(1.0, | |
abs(l), | |
(l > 0.0) ? pow(max(0.0, h), m) : 0.0, | |
1.0); | |
} | |
void main() { | |
vec4 diffuseColor = texture2D(u_diffuse, v_texCoord); | |
vec3 a_normal = normalize(v_normal); | |
vec3 surfaceToLight = normalize(v_surfaceToLight); | |
vec3 surfaceToView = normalize(v_surfaceToView); | |
vec3 halfVector = normalize(surfaceToLight + surfaceToView); | |
vec4 litR = lit(dot(a_normal, surfaceToLight), | |
dot(a_normal, halfVector), u_shininess); | |
vec4 outColor = vec4(( | |
u_lightColor * (diffuseColor * litR.y * u_colorMult + | |
u_specular * litR.z * u_specularFactor)).rgb, | |
diffuseColor.a); | |
gl_FragColor = outColor; | |
// gl_FragColor = vec4(litR.yyy, 1); | |
} | |
</script> | |
<canvas id="canvas" width="400" height="300"></canvas> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Wait for the DOM to be loaded. | |
DomReady.ready(function() { | |
try { | |
main(); | |
} | |
catch(e) { | |
} | |
}); | |
function createSphereBuffers(gl: WebGLRenderingContext, arg1, arg2, arg3): (gl: WebGLRenderingContext)=>{[name:string]: WebGLBuffer} { | |
return createBufferFunc(createSphereVertices); | |
} | |
function main() { | |
// Get A WebGL context | |
var canvas = <HTMLCanvasElement>document.getElementById("canvas"); | |
var gl = getWebGLContext(canvas); | |
if (!gl) { | |
return; | |
} | |
gl.enable(gl.CULL_FACE); | |
gl.enable(gl.DEPTH_TEST); | |
var buffers = createSphereBuffers(gl, 10, 48, 24); | |
// setup GLSL program | |
var program = createProgramFromScripts(gl, ["3d-vertex-shader", "3d-fragment-shader"]); | |
// Uniform setters realy do set the uniform value. | |
var uniformSetters = createUniformSetters(gl, program); | |
// Attribute setters indirectly enable setting attributes by specifying how data is transferred from arrays. | |
var attribSetters = createAttributeSetters(gl, program); | |
// Describe the attributes, in this case the same for all objects. | |
var attribs: {[name: string]: AttribSpec} = { | |
a_position: { buffer: buffers['position'], numComponents: 3, }, | |
a_normal: { buffer: buffers['normal'], numComponents: 3, }, | |
a_texcoord: { buffer: buffers['texcoord'], numComponents: 2, }, | |
}; | |
function degToRad(d: number): number { | |
return d * Math.PI / 180; | |
} | |
var cameraAngleRadians = degToRad(0); | |
var fieldOfViewRadians = degToRad(60); | |
var cameraHeight = 50; | |
var uniformsThatAreTheSameForAllObjects:{[name:string]:any} = { | |
u_lightWorldPos: [-50, 30, 100], | |
u_viewInverse: makeIdentity(), | |
u_lightColor: [1, 1, 1, 1], | |
}; | |
var uniformsThatAreComputedForEachObject:{[name:string]:any} = { | |
u_worldViewProjection: makeIdentity(), | |
u_world: makeIdentity(), | |
u_worldInverseTranspose: makeIdentity(), | |
}; | |
var rand = function(min: number, max?: number) { | |
if (max === undefined) { | |
max = min; | |
min = 0; | |
} | |
return min + Math.random() * (max - min); | |
}; | |
var randInt = function(range: number) { | |
return Math.floor(Math.random() * range); | |
}; | |
var textures = [ | |
textureUtils.makeStripeTexture(gl, { color1: "#FFF", color2: "#CCC", }), | |
textureUtils.makeCheckerTexture(gl, { color1: "#FFF", color2: "#CCC", }), | |
textureUtils.makeCircleTexture(gl, { color1: "#FFF", color2: "#CCC", }), | |
]; | |
var objects:{radius: number; xRotation: number; yRotation: number; materialUniforms:{u_specular:number[]; u_shininess:number}}[] = []; | |
var numObjects = 300; | |
var baseColor = rand(240); | |
for (var ii = 0; ii < numObjects; ++ii) { | |
objects.push({ | |
radius: rand(150), | |
xRotation: rand(Math.PI * 2), | |
yRotation: rand(Math.PI), | |
materialUniforms: { | |
// u_colorMult: chroma.hsv(rand(baseColor, baseColor + 120), 0.5, 1).gl(), | |
u_diffuse: textures[randInt(textures.length)], | |
u_specular: [1, 1, 1, 1], | |
u_shininess: rand(500), | |
u_specularFactor: rand(1), | |
}, | |
}); | |
} | |
requestAnimationFrame(drawScene); | |
/** | |
* Draw the scene. | |
*/ | |
function drawScene(time: number) { | |
time *= 0.0001; | |
// Clear the canvas AND the depth buffer. | |
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); | |
// Compute the projection matrix | |
var aspect = canvas.clientWidth / canvas.clientHeight; | |
var projectionMatrix = makePerspective(fieldOfViewRadians, aspect, 1, 2000); | |
// Compute the camera's matrix using look at. | |
var cameraPosition = [0, 0, 100]; | |
var target = [0, 0, 0]; | |
var up = [0, 1, 0]; | |
var cameraMatrix = makeLookAt(cameraPosition, target, up, uniformsThatAreTheSameForAllObjects['u_viewInverse']); | |
// Make a view matrix from the camera matrix. | |
var viewMatrix = makeInverse(cameraMatrix); | |
gl.useProgram(program); | |
// Setup all the needed attributes. | |
setAttributes(attribSetters, attribs); | |
// Bind the indices. | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers['indices']); | |
// Set the uniforms that are the same for all objects. | |
setUniforms(uniformSetters, uniformsThatAreTheSameForAllObjects); | |
// Draw objects | |
objects.forEach(function(object) { | |
// Compute a position for this object based on the time. | |
var xRotationMatrix = makeXRotation(object.xRotation * time); | |
var yRotationMatrix = makeYRotation(object.yRotation * time); | |
var translationMatrix = makeTranslation(0, 0, object.radius); | |
var matrix = matrixMultiply(xRotationMatrix, yRotationMatrix); | |
var worldMatrix = matrixMultiply(translationMatrix, matrix, uniformsThatAreComputedForEachObject['u_world']); | |
// Multiply the matrices. | |
var matrix = matrixMultiply(worldMatrix, viewMatrix); | |
matrixMultiply(matrix, projectionMatrix, uniformsThatAreComputedForEachObject['u_worldViewProjection']); | |
makeTranspose(makeInverse(worldMatrix), uniformsThatAreComputedForEachObject['u_worldInverseTranspose']); | |
// Set the uniforms we just computed | |
setUniforms(uniformSetters, uniformsThatAreComputedForEachObject); | |
// Set the uniforms that are specific to the this object. | |
setUniforms(uniformSetters, object.materialUniforms); | |
// Draw the geometry. | |
gl.drawElements(gl.TRIANGLES, buffers['numElements'], gl.UNSIGNED_SHORT, 0); | |
}); | |
requestAnimationFrame(drawScene); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#info { | |
position: absolute; | |
left: 40%; | |
top: 200px; | |
font-size: 26px; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment