Skip to content

Instantly share code, notes, and snippets.

@mathdoodle
Last active August 29, 2015 14:27
Show Gist options
  • Save mathdoodle/6c21aa755344be42b359 to your computer and use it in GitHub Desktop.
Save mathdoodle/6c21aa755344be42b359 to your computer and use it in GitHub Desktop.
FUNDAMENTALS
{
"uuid": "1e5f0d4d-0c0c-47d8-b04a-c079502eb408",
"description": "FUNDAMENTALS",
"dependencies": {
"DomReady": "latest"
},
"operatorOverloading": true
}
//======================================
// 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;
}
<!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>
// 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);
}
}
#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