1293 lines
46 KiB
JavaScript
1293 lines
46 KiB
JavaScript
/*
|
|
* Copyright 2012, Gregg Tavares.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following disclaimer
|
|
* in the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* * Neither the name of Gregg Tavares. nor the names of his
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
(function(root, factory) { // eslint-disable-line
|
|
if (typeof define === 'function' && define.amd) {
|
|
// AMD. Register as an anonymous module.
|
|
define([], function() {
|
|
return factory.call(root);
|
|
});
|
|
} else {
|
|
// Browser globals
|
|
root.webglUtils = factory.call(root);
|
|
}
|
|
}(this, function() {
|
|
"use strict";
|
|
|
|
var topWindow = this;
|
|
|
|
/** @module webgl-utils */
|
|
|
|
function isInIFrame(w) {
|
|
w = w || topWindow;
|
|
return w !== w.top;
|
|
}
|
|
|
|
if (!isInIFrame()) {
|
|
console.log("%c%s", 'color:blue;font-weight:bold;', 'for more about webgl-utils.js see:'); // eslint-disable-line
|
|
console.log("%c%s", 'color:blue;font-weight:bold;', 'http://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html'); // eslint-disable-line
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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, shaders, opt_attribs, opt_locations, 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, ndx) {
|
|
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 = document.getElementById(scriptId);
|
|
if (!shaderScript) {
|
|
console.log ("*** Error: unknown script element" + scriptId);
|
|
}
|
|
shaderSource = shaderScript.text;
|
|
|
|
if (!opt_shaderType) {
|
|
if (shaderScript.type === "x-shader/x-vertex") {
|
|
shaderType = 35633;
|
|
} else if (shaderScript.type === "x-shader/x-fragment") {
|
|
shaderType = 35632;
|
|
} else if (shaderType !== gl.VERTEX_SHADER && shaderType !== gl.FRAGMENT_SHADER) {
|
|
console.log ("*** 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, shaderScriptIds, 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
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* @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, program) {
|
|
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, uniformInfo) {
|
|
var location = 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) {
|
|
gl.uniform1fv(location, v);
|
|
};
|
|
}
|
|
if (type === gl.FLOAT) {
|
|
return function(v) {
|
|
gl.uniform1f(location, v);
|
|
};
|
|
}
|
|
if (type === gl.FLOAT_VEC2) {
|
|
return function(v) {
|
|
gl.uniform2fv(location, v);
|
|
};
|
|
}
|
|
if (type === gl.FLOAT_VEC3) {
|
|
return function(v) {
|
|
gl.uniform3fv(location, v);
|
|
};
|
|
}
|
|
if (type === gl.FLOAT_VEC4) {
|
|
return function(v) {
|
|
gl.uniform4fv(location, v);
|
|
};
|
|
}
|
|
if (type === gl.INT && isArray) {
|
|
return function(v) {
|
|
gl.uniform1iv(location, v);
|
|
};
|
|
}
|
|
if (type === gl.INT) {
|
|
return function(v) {
|
|
gl.uniform1i(location, v);
|
|
};
|
|
}
|
|
if (type === gl.INT_VEC2) {
|
|
return function(v) {
|
|
gl.uniform2iv(location, v);
|
|
};
|
|
}
|
|
if (type === gl.INT_VEC3) {
|
|
return function(v) {
|
|
gl.uniform3iv(location, v);
|
|
};
|
|
}
|
|
if (type === gl.INT_VEC4) {
|
|
return function(v) {
|
|
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) {
|
|
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 < info.size; ++ii) {
|
|
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);
|
|
});
|
|
};
|
|
}(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 = { };
|
|
var numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
|
|
|
|
for (var ii = 0; ii < numUniforms; ++ii) {
|
|
var uniformInfo = 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>|module:webgl-utils.ProgramInfo} setters the setters returned from
|
|
* `createUniformSetters` or a ProgramInfo from {@link module:webgl-utils.createProgramInfo}.
|
|
* @param {Object.<string, value>} an object with values for the
|
|
* uniforms.
|
|
* @memberOf module:webgl-utils
|
|
*/
|
|
function setUniforms(setters, values) {
|
|
setters = setters.uniformSetters || setters;
|
|
Object.keys(values).forEach(function(name) {
|
|
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, program) {
|
|
var attribSetters = {
|
|
};
|
|
|
|
function createAttribSetter(index) {
|
|
return function(b) {
|
|
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);
|
|
};
|
|
}
|
|
|
|
var numAttribs = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
|
|
for (var ii = 0; ii < numAttribs; ++ii) {
|
|
var attribInfo = 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>|model:webgl-utils.ProgramInfo} setters Attribute setters as returned from createAttributeSetters or a ProgramInfo as returned {@link module:webgl-utils.createProgramInfo}
|
|
* @param {Object.<string, module:webgl-utils.AttribInfo>} attribs AttribInfos mapped by attribute name.
|
|
* @memberOf module:webgl-utils
|
|
* @deprecated use {@link module:webgl-utils.setBuffersAndAttributes}
|
|
*/
|
|
function setAttributes(setters, attribs) {
|
|
setters = setters.attribSetters || setters;
|
|
Object.keys(attribs).forEach(function(name) {
|
|
var setter = setters[name];
|
|
if (setter) {
|
|
setter(attribs[name]);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates a vertex array object and then sets the attributes
|
|
* on it
|
|
*
|
|
* @param {WebGLRenderingContext} gl The WebGLRenderingContext
|
|
* to use.
|
|
* @param {Object.<string, function>} setters Attribute setters as returned from createAttributeSetters
|
|
* @param {Object.<string, module:webgl-utils.AttribInfo>} attribs AttribInfos mapped by attribute name.
|
|
* @param {WebGLBuffer} [indices] an optional ELEMENT_ARRAY_BUFFER of indices
|
|
*/
|
|
function createVAOAndSetAttributes(gl, setters, attribs, indices) {
|
|
var vao = gl.createVertexArray();
|
|
gl.bindVertexArray(vao);
|
|
setAttributes(setters, attribs);
|
|
if (indices) {
|
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices);
|
|
}
|
|
// We unbind this because otherwise any change to ELEMENT_ARRAY_BUFFER
|
|
// like when creating buffers for other stuff will mess up this VAO's binding
|
|
gl.bindVertexArray(null);
|
|
return vao;
|
|
}
|
|
|
|
/**
|
|
* Creates a vertex array object and then sets the attributes
|
|
* on it
|
|
*
|
|
* @param {WebGLRenderingContext} gl The WebGLRenderingContext
|
|
* to use.
|
|
* @param {Object.<string, function>| module:webgl-utils.ProgramInfo} programInfo as returned from createProgramInfo or Attribute setters as returned from createAttributeSetters
|
|
* @param {module:webgl-utils:BufferInfo} bufferInfo BufferInfo as returned from createBufferInfoFromArrays etc...
|
|
* @param {WebGLBuffer} [indices] an optional ELEMENT_ARRAY_BUFFER of indices
|
|
*/
|
|
function createVAOFromBufferInfo(gl, programInfo, bufferInfo) {
|
|
return createVAOAndSetAttributes(gl, programInfo.attribSetters || programInfo, bufferInfo.attribs, bufferInfo.indices);
|
|
}
|
|
|
|
/**
|
|
* @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 = document.getElementById(source);
|
|
return script ? script.text : source;
|
|
});
|
|
var program = webglUtils.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, 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;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Resize a canvas to match the size its displayed.
|
|
* @param {HTMLCanvasElement} canvas The canvas to resize.
|
|
* @param {number} [multiplier] amount to multiply by.
|
|
* Pass in window.devicePixelRatio for native pixels.
|
|
* @return {boolean} true if the canvas was resized.
|
|
* @memberOf module:webgl-utils
|
|
*/
|
|
function resizeCanvasToDisplaySize(canvas, multiplier) {
|
|
multiplier = multiplier || 1;
|
|
var width = canvas.clientWidth * multiplier | 0;
|
|
var height = canvas.clientHeight * multiplier | 0;
|
|
if (canvas.width !== width || canvas.height !== height) {
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// 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, numElements, opt_type) {
|
|
var Type = opt_type || Float32Array;
|
|
return augmentTypedArray(new Type(numComponents * numElements), numComponents);
|
|
}
|
|
|
|
function createBufferFromTypedArray(gl, array, type, drawType) {
|
|
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) {
|
|
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, 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, length) {
|
|
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.data && isArrayBuffer(array.data)) {
|
|
return array.data;
|
|
}
|
|
|
|
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, arrays, opt_mapping) {
|
|
var mapping = opt_mapping || createMapping(arrays);
|
|
var attribs = {};
|
|
Object.keys(mapping).forEach(function(attribName) {
|
|
var bufferName = mapping[attribName];
|
|
var origArray = arrays[bufferName];
|
|
var array = makeTypedArray(origArray, bufferName);
|
|
attribs[attribName] = {
|
|
buffer: createBufferFromTypedArray(gl, array),
|
|
numComponents: origArray.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 attribute
|
|
* 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, arrays, opt_mapping) {
|
|
var bufferInfo = {
|
|
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, arrays) {
|
|
var buffers = { };
|
|
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 {module:webgl-utils.BufferInfo} bufferInfo as returned from createBufferInfoFromArrays
|
|
* @param {enum} [primitiveType] eg (gl.TRIANGLES, gl.LINES, gl.POINTS, gl.TRIANGLE_STRIP, ...)
|
|
* @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, bufferInfo, primitiveType, count, offset) {
|
|
var indices = bufferInfo.indices;
|
|
primitiveType = primitiveType === undefined ? gl.TRIANGLES : primitiveType;
|
|
var numElements = count === undefined ? bufferInfo.numElements : count;
|
|
offset = offset === undefined ? offset : 0;
|
|
if (indices) {
|
|
gl.drawElements(primitiveType, numElements, gl.UNSIGNED_SHORT, offset);
|
|
} else {
|
|
gl.drawArrays(primitiveType, 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, bufferInfo);
|
|
});
|
|
}
|
|
|
|
var isIE = /*@cc_on!@*/false || !!document.documentMode;
|
|
// Edge 20+
|
|
var isEdge = !isIE && !!window.StyleMedia;
|
|
if (isEdge) {
|
|
// Hack for Edge. Edge's WebGL implmentation is crap still and so they
|
|
// only respond to "experimental-webgl". I don't want to clutter the
|
|
// examples with that so his hack works around it
|
|
HTMLCanvasElement.prototype.getContext = function(origFn) {
|
|
return function() {
|
|
var args = arguments;
|
|
var type = args[0];
|
|
if (type === "webgl") {
|
|
args = [].slice.call(arguments);
|
|
args[0] = "experimental-webgl";
|
|
}
|
|
return origFn.apply(this, args);
|
|
};
|
|
}(HTMLCanvasElement.prototype.getContext);
|
|
}
|
|
|
|
return {
|
|
createAugmentedTypedArray: createAugmentedTypedArray,
|
|
createAttribsFromArrays: createAttribsFromArrays,
|
|
createBuffersFromArrays: createBuffersFromArrays,
|
|
createBufferInfoFromArrays: createBufferInfoFromArrays,
|
|
createAttributeSetters: createAttributeSetters,
|
|
createProgram: createProgram,
|
|
createProgramFromScripts: createProgramFromScripts,
|
|
createProgramFromSources: createProgramFromSources,
|
|
createProgramInfo: createProgramInfo,
|
|
createUniformSetters: createUniformSetters,
|
|
createVAOAndSetAttributes: createVAOAndSetAttributes,
|
|
createVAOFromBufferInfo: createVAOFromBufferInfo,
|
|
drawBufferInfo: drawBufferInfo,
|
|
drawObjectList: drawObjectList,
|
|
getExtensionWithKnownPrefixes: getExtensionWithKnownPrefixes,
|
|
resizeCanvasToDisplaySize: resizeCanvasToDisplaySize,
|
|
setAttributes: setAttributes,
|
|
setBuffersAndAttributes: setBuffersAndAttributes,
|
|
setUniforms: setUniforms,
|
|
};
|
|
|
|
}));
|