uglymol
Version:
Macromolecular Viewer for Crystallographers
1,979 lines (1,561 loc) • 71 kB
JavaScript
// Copyright 2010-2023 Three.js Authors
// SPDX-License-Identifier: MIT
/* eslint-disable max-len, one-var, guard-for-in */
/* eslint-disable prefer-rest-params, no-invalid-this, no-useless-escape */
/* eslint-disable new-cap, no-extend-native */
import {
Quaternion, Vector3, Vector4, Matrix4, Color, Ray, generateUUID
} from './math.js';
// constants.js
let NoBlending = 0;
let NormalBlending = 1;
// core/EventDispatcher.js
class EventDispatcher {
addEventListener(type, listener) {
if (this._listeners === undefined) this._listeners = {};
const listeners = this._listeners;
if (listeners[type] === undefined) {
listeners[type] = [];
}
if (listeners[type].indexOf(listener) === -1) {
listeners[type].push(listener);
}
}
removeEventListener(type, listener) {
if (this._listeners === undefined) return;
const listeners = this._listeners;
const listenerArray = listeners[type];
if (listenerArray !== undefined) {
const index = listenerArray.indexOf(listener);
if (index !== -1) {
listenerArray.splice(index, 1);
}
}
}
dispatchEvent(event) {
if (this._listeners === undefined) return;
const listeners = this._listeners;
const listenerArray = listeners[event.type];
if (listenerArray !== undefined) {
event.target = this;
// Make a copy, in case listeners are removed while iterating.
const array = listenerArray.slice(0);
for (let i = 0, l = array.length; i < l; i++) {
array[i].call(this, event);
}
event.target = null;
}
}
}
// textures/Source.js
let _sourceId = 0;
class Source {
constructor(data = null) {
Object.defineProperty(this, 'id', { value: _sourceId++ });
this.uuid = generateUUID();
this.data = data;
this.dataReady = true;
this.version = 0;
}
set needsUpdate( value ) {
if (value === true) this.version++;
}
}
// textures/Texture.js
let _textureId = 0;
class Texture extends EventDispatcher {
constructor(image) {
super();
Object.defineProperty(this, 'id', { value: _textureId++ });
this.uuid = generateUUID();
this.name = '';
this.source = new Source(image);
this.version = 0;
}
get image() {
return this.source.data;
}
set image(value) {
this.source.data = value;
}
dispose() {
this.dispatchEvent({ type: 'dispose' });
}
set needsUpdate(value) {
if (value === true) {
this.version++;
this.source.needsUpdate = true;
}
}
}
// renderers/webgl/WebGLUniforms.js
/**
* Uniforms of a program.
* Those form a tree structure with a special top-level container for the root,
* which you get by calling 'new WebGLUniforms( gl, program )'.
*
*
* Properties of inner nodes including the top-level container:
*
* .seq - array of nested uniforms
* .map - nested uniforms by name
*
*
* Methods of all nodes except the top-level container:
*
* .setValue( gl, value, [textures] )
*
* uploads a uniform value(s)
* the 'textures' parameter is needed for sampler uniforms
*
*
* Static methods of the top-level container (textures factorizations):
*
* .upload( gl, seq, values, textures )
*
* sets uniforms in 'seq' to 'values[id].value'
*
* .seqWithValue( seq, values ) : filteredSeq
*
* filters 'seq' entries with corresponding entry in values
*
*
* Methods of the top-level container (textures factorizations):
*
* .setValue( gl, name, value, textures )
*
* sets uniform with name 'name' to 'value'
*
* .setOptional( gl, obj, prop )
*
* like .set for an optional property of the object
*
*/
const emptyTexture = /*@__PURE__*/ new Texture();
// --- Utilities ---
// Float32Array caches used for uploading Matrix uniforms
const mat4array = new Float32Array(16);
const mat3array = new Float32Array(9);
const mat2array = new Float32Array(4);
function arraysEqual(a, b) {
if (a.length !== b.length) return false;
for (let i = 0, l = a.length; i < l; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
function copyArray(a, b) {
for (let i = 0, l = b.length; i < l; i++) {
a[i] = b[i];
}
}
// --- Setters ---
// Note: Defining these methods externally, because they come in a bunch
// and this way their names minify.
// Single scalar
function setValueV1f(gl, v) {
const cache = this.cache;
if (cache[0] === v) return;
gl.uniform1f(this.addr, v);
cache[0] = v;
}
// Single float vector (from flat array or THREE.VectorN)
function setValueV2f(gl, v) {
const cache = this.cache;
if (v.x !== undefined) {
if (cache[0] !== v.x || cache[1] !== v.y) {
gl.uniform2f(this.addr, v.x, v.y);
cache[0] = v.x;
cache[1] = v.y;
}
} else {
if (arraysEqual(cache, v)) return;
gl.uniform2fv(this.addr, v);
copyArray(cache, v);
}
}
function setValueV3f(gl, v) {
const cache = this.cache;
if (v.x !== undefined) {
if (cache[0] !== v.x || cache[1] !== v.y || cache[2] !== v.z) {
gl.uniform3f(this.addr, v.x, v.y, v.z);
cache[0] = v.x;
cache[1] = v.y;
cache[2] = v.z;
}
} else if (v.r !== undefined) {
if (cache[0] !== v.r || cache[1] !== v.g || cache[2] !== v.b) {
gl.uniform3f(this.addr, v.r, v.g, v.b);
cache[0] = v.r;
cache[1] = v.g;
cache[2] = v.b;
}
} else {
if (arraysEqual(cache, v)) return;
gl.uniform3fv(this.addr, v);
copyArray(cache, v);
}
}
function setValueV4f(gl, v) {
const cache = this.cache;
if (v.x !== undefined) {
if (cache[0] !== v.x || cache[1] !== v.y || cache[2] !== v.z || cache[3] !== v.w) {
gl.uniform4f(this.addr, v.x, v.y, v.z, v.w);
cache[0] = v.x;
cache[1] = v.y;
cache[2] = v.z;
cache[3] = v.w;
}
} else {
if (arraysEqual(cache, v)) return;
gl.uniform4fv(this.addr, v);
copyArray(cache, v);
}
}
// Single matrix (from flat array or THREE.MatrixN)
function setValueM2(gl, v) {
const cache = this.cache;
const elements = v.elements;
if (elements === undefined) {
if (arraysEqual(cache, v)) return;
gl.uniformMatrix2fv(this.addr, false, v);
copyArray(cache, v);
} else {
if (arraysEqual(cache, elements)) return;
mat2array.set(elements);
gl.uniformMatrix2fv(this.addr, false, mat2array);
copyArray(cache, elements);
}
}
function setValueM3(gl, v) {
const cache = this.cache;
const elements = v.elements;
if (elements === undefined) {
if (arraysEqual(cache, v)) return;
gl.uniformMatrix3fv(this.addr, false, v);
copyArray(cache, v);
} else {
if (arraysEqual(cache, elements)) return;
mat3array.set(elements);
gl.uniformMatrix3fv(this.addr, false, mat3array);
copyArray(cache, elements);
}
}
function setValueM4(gl, v) {
const cache = this.cache;
const elements = v.elements;
if (elements === undefined) {
if (arraysEqual(cache, v)) return;
gl.uniformMatrix4fv(this.addr, false, v);
copyArray(cache, v);
} else {
if (arraysEqual(cache, elements)) return;
mat4array.set(elements);
gl.uniformMatrix4fv(this.addr, false, mat4array);
copyArray(cache, elements);
}
}
// Single integer / boolean
function setValueV1i(gl, v) {
const cache = this.cache;
if (cache[0] === v) return;
gl.uniform1i(this.addr, v);
cache[0] = v;
}
// Single integer / boolean vector (from flat array or THREE.VectorN)
function setValueV2i(gl, v) {
const cache = this.cache;
if (v.x !== undefined) {
if (cache[0] !== v.x || cache[1] !== v.y) {
gl.uniform2i(this.addr, v.x, v.y);
cache[0] = v.x;
cache[1] = v.y;
}
} else {
if (arraysEqual(cache, v)) return;
gl.uniform2iv(this.addr, v);
copyArray(cache, v);
}
}
function setValueV3i(gl, v) {
const cache = this.cache;
if (v.x !== undefined) {
if (cache[0] !== v.x || cache[1] !== v.y || cache[2] !== v.z) {
gl.uniform3i(this.addr, v.x, v.y, v.z);
cache[0] = v.x;
cache[1] = v.y;
cache[2] = v.z;
}
} else {
if (arraysEqual(cache, v)) return;
gl.uniform3iv(this.addr, v);
copyArray(cache, v);
}
}
function setValueV4i(gl, v) {
const cache = this.cache;
if (v.x !== undefined) {
if (cache[0] !== v.x || cache[1] !== v.y || cache[2] !== v.z || cache[3] !== v.w) {
gl.uniform4i(this.addr, v.x, v.y, v.z, v.w);
cache[0] = v.x;
cache[1] = v.y;
cache[2] = v.z;
cache[3] = v.w;
}
} else {
if (arraysEqual(cache, v)) return;
gl.uniform4iv(this.addr, v);
copyArray(cache, v);
}
}
// Single unsigned integer
function setValueV1ui(gl, v) {
const cache = this.cache;
if (cache[0] === v) return;
gl.uniform1ui(this.addr, v);
cache[0] = v;
}
// Single unsigned integer vector (from flat array or THREE.VectorN)
function setValueV2ui(gl, v) {
const cache = this.cache;
if (v.x !== undefined) {
if (cache[0] !== v.x || cache[1] !== v.y) {
gl.uniform2ui(this.addr, v.x, v.y);
cache[0] = v.x;
cache[1] = v.y;
}
} else {
if (arraysEqual(cache, v)) return;
gl.uniform2uiv(this.addr, v);
copyArray(cache, v);
}
}
function setValueV3ui(gl, v) {
const cache = this.cache;
if (v.x !== undefined) {
if (cache[0] !== v.x || cache[1] !== v.y || cache[2] !== v.z) {
gl.uniform3ui(this.addr, v.x, v.y, v.z);
cache[0] = v.x;
cache[1] = v.y;
cache[2] = v.z;
}
} else {
if (arraysEqual(cache, v)) return;
gl.uniform3uiv(this.addr, v);
copyArray(cache, v);
}
}
function setValueV4ui(gl, v) {
const cache = this.cache;
if (v.x !== undefined) {
if (cache[0] !== v.x || cache[1] !== v.y || cache[2] !== v.z || cache[3] !== v.w) {
gl.uniform4ui(this.addr, v.x, v.y, v.z, v.w);
cache[0] = v.x;
cache[1] = v.y;
cache[2] = v.z;
cache[3] = v.w;
}
} else {
if (arraysEqual(cache, v)) return;
gl.uniform4uiv(this.addr, v);
copyArray(cache, v);
}
}
// Single texture (2D / Cube)
function setValueT1(gl, v, textures) {
const cache = this.cache;
const unit = textures.allocateTextureUnit();
if (cache[0] !== unit) {
gl.uniform1i(this.addr, unit);
cache[0] = unit;
}
const emptyTexture2D = emptyTexture;
textures.setTexture2D(v || emptyTexture2D, unit);
}
// Helper to pick the right setter for the singular case
function getSingularSetter(type) {
switch (type) {
case 0x1406: return setValueV1f; // FLOAT
case 0x8b50: return setValueV2f; // _VEC2
case 0x8b51: return setValueV3f; // _VEC3
case 0x8b52: return setValueV4f; // _VEC4
case 0x8b5a: return setValueM2; // _MAT2
case 0x8b5b: return setValueM3; // _MAT3
case 0x8b5c: return setValueM4; // _MAT4
case 0x1404: case 0x8b56: return setValueV1i; // INT, BOOL
case 0x8b53: case 0x8b57: return setValueV2i; // _VEC2
case 0x8b54: case 0x8b58: return setValueV3i; // _VEC3
case 0x8b55: case 0x8b59: return setValueV4i; // _VEC4
case 0x1405: return setValueV1ui; // UINT
case 0x8dc6: return setValueV2ui; // _VEC2
case 0x8dc7: return setValueV3ui; // _VEC3
case 0x8dc8: return setValueV4ui; // _VEC4
case 0x8b5e: // SAMPLER_2D
case 0x8d66: // SAMPLER_EXTERNAL_OES
case 0x8dca: // INT_SAMPLER_2D
case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D
return setValueT1;
}
}
// --- Uniform Classes ---
class SingleUniform {
constructor(id, activeInfo, addr) {
this.id = id;
this.addr = addr;
this.cache = [];
this.type = activeInfo.type;
this.setValue = getSingularSetter(activeInfo.type);
// this.path = activeInfo.name; // DEBUG
}
}
class StructuredUniform {
constructor(id) {
this.id = id;
this.seq = [];
this.map = {};
}
setValue(gl, value, textures) {
const seq = this.seq;
for (let i = 0, n = seq.length; i !== n; ++i) {
const u = seq[i];
u.setValue(gl, value[u.id], textures);
}
}
}
// --- Top-level ---
// Parser - builds up the property tree from the path strings
const RePathPart = /(\w+)(\])?(\[|\.)?/g;
// extracts
// - the identifier (member name or array index)
// - followed by an optional right bracket (found when array index)
// - followed by an optional left bracket or dot (type of subscript)
//
// Note: These portions can be read in a non-overlapping fashion and
// allow straightforward parsing of the hierarchy that WebGL encodes
// in the uniform names.
function addUniform(container, uniformObject) {
container.seq.push(uniformObject);
container.map[uniformObject.id] = uniformObject;
}
function parseUniform(activeInfo, addr, container) {
const path = activeInfo.name,
pathLength = path.length;
// reset RegExp object, because of the early exit of a previous run
RePathPart.lastIndex = 0;
while (true) { // eslint-disable-line no-constant-condition
const match = RePathPart.exec(path),
matchEnd = RePathPart.lastIndex;
let id = match[1];
const idIsIndex = match[2] === ']',
subscript = match[3];
if (idIsIndex) id = id | 0; // convert to integer
if (subscript === undefined || (subscript === '[' && matchEnd + 2 === pathLength)) {
// bare name or "pure" bottom-level array "[0]" suffix
if (subscript !== undefined) throw new TypeError('PureArrayUniform?');
addUniform(container, new SingleUniform(id, activeInfo, addr));
break;
} else {
// step into inner node / create it in case it doesn't exist
const map = container.map;
let next = map[id];
if (next === undefined) {
next = new StructuredUniform(id);
addUniform(container, next);
}
container = next;
}
}
}
// Root Container
class WebGLUniforms {
constructor(gl, program) {
this.seq = [];
this.map = {};
const n = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
for (let i = 0; i < n; ++i) {
const info = gl.getActiveUniform(program, i),
addr = gl.getUniformLocation(program, info.name);
parseUniform(info, addr, this);
}
}
setValue(gl, name, value, textures) {
const u = this.map[name];
if (u !== undefined) u.setValue(gl, value, textures);
}
setOptional(gl, object, name) {
const v = object[name];
if (v !== undefined) this.setValue(gl, name, v);
}
static upload(gl, seq, values, textures) {
for (let i = 0, n = seq.length; i !== n; ++i) {
const u = seq[i],
v = values[u.id];
if (v.needsUpdate !== false) {
// note: always updating when .needsUpdate is undefined
u.setValue(gl, v.value, textures);
}
}
}
static seqWithValue(seq, values) {
const r = [];
for (let i = 0, n = seq.length; i !== n; ++i) {
const u = seq[i];
if (u.id in values) r.push(u);
}
return r;
}
}
// materials/Material.js
let _materialId = 0;
class Material extends EventDispatcher {
constructor() {
super();
this.isMaterial = true;
Object.defineProperty(this, 'id', { value: _materialId++ });
this.uuid = generateUUID();
this.name = '';
this.type = 'Material';
this.opacity = 1;
this.transparent = false;
this.depthTest = true;
this.depthWrite = true;
this.precision = null; // override the renderer's default precision for this material
this.premultipliedAlpha = false;
this.visible = true;
//TODO
//this.version = 0;
this._needsUpdate = true;
}
setValues(values) {
if (values === undefined) return;
for (const key in values) {
const newValue = values[key];
if (newValue === undefined) {
console.warn(`THREE.Material: parameter '${key}' has value of undefined.`);
continue;
}
const currentValue = this[key];
if (currentValue === undefined) {
console.warn(`THREE.Material: '${key}' is not a property of THREE.${this.type}.`);
continue;
}
if (currentValue && currentValue.isColor) {
currentValue.set(newValue);
} else if (currentValue && currentValue.isVector3 && newValue && newValue.isVector3) {
currentValue.copy(newValue);
} else {
this[key] = newValue;
}
}
}
dispose() {
this.dispatchEvent({ type: 'dispose' });
}
//TODO
//set needsUpdate(value) {
// if (value === true) this.version++;
//}
//old:
get needsUpdate() {
return this._needsUpdate;
}
set needsUpdate(value) {
if ( value === true ) this.update();
this._needsUpdate = value;
}
update() {
this.dispatchEvent( { type: 'update' } );
}
}
// materials/ShaderMaterial.js
class ShaderMaterial extends Material {
constructor(parameters) {
super();
this.isShaderMaterial = true;
this.type = 'ShaderMaterial';
this.uniforms = {};
this.vertexShader = '';
this.fragmentShader = '';
this.linewidth = 1;
this.fog = false; // set to use scene fog
this.extensions = {
fragDepth: false, // set to use fragment depth values
};
this.setValues(parameters);
}
}
// core/Object3D.js
let _object3DId = 0;
const _addedEvent = { type: 'added' };
const _removedEvent = { type: 'removed' };
class Object3D extends EventDispatcher {
constructor() {
super();
this.isObject3D = true;
Object.defineProperty(this, 'id', { value: _object3DId++ });
this.uuid = generateUUID();
this.name = '';
this.type = 'Object3D';
this.parent = null;
this.children = [];
this.up = Object3D.DEFAULT_UP.clone();
const position = new Vector3();
//const rotation = new Euler();
const quaternion = new Quaternion();
const scale = new Vector3(1, 1, 1);
//function onRotationChange() {
// quaternion.setFromEuler(rotation, false);
//}
//function onQuaternionChange() {
// rotation.setFromQuaternion(quaternion, undefined, false);
//}
//rotation._onChange(onRotationChange);
//quaternion._onChange(onQuaternionChange);
Object.defineProperties(this, {
position: {
configurable: true,
enumerable: true,
value: position,
},
//rotation: {
// configurable: true,
// enumerable: true,
// value: rotation,
//},
quaternion: {
configurable: true,
enumerable: true,
value: quaternion,
},
scale: {
configurable: true,
enumerable: true,
value: scale,
},
modelViewMatrix: {
value: new Matrix4(),
},
//normalMatrix: {
// value: new Matrix3(),
//},
});
this.matrix = new Matrix4();
this.matrixWorld = new Matrix4();
this.matrixAutoUpdate = Object3D.DEFAULT_MATRIX_AUTO_UPDATE;
this.matrixWorldAutoUpdate = Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE; // checked by the renderer
this.matrixWorldNeedsUpdate = false;
//this.layers = new Layers();
this.visible = true;
//this.castShadow = false;
//this.receiveShadow = false;
this.frustumCulled = true;
this.renderOrder = 0;
//this.animations = [];
this.userData = {};
}
add(object) {
if (arguments.length > 1) {
for (let i = 0; i < arguments.length; i++) {
this.add(arguments[i]);
}
return this;
}
if (object && object.isObject3D) {
if (object.parent !== null) {
object.parent.remove(object);
}
object.parent = this;
this.children.push(object);
object.dispatchEvent(_addedEvent);
}
return this;
}
remove(object) {
if (arguments.length > 1) {
for (let i = 0; i < arguments.length; i++) {
this.remove(arguments[i]);
}
return this;
}
const index = this.children.indexOf(object);
if (index !== -1) {
object.parent = null;
this.children.splice(index, 1);
object.dispatchEvent(_removedEvent);
}
return this;
}
updateMatrix() {
this.matrix.compose(this.position, this.quaternion, this.scale);
this.matrixWorldNeedsUpdate = true;
}
updateMatrixWorld(force) {
if (this.matrixAutoUpdate) this.updateMatrix();
if (this.matrixWorldNeedsUpdate || force) {
if (this.parent === null) {
this.matrixWorld.copy(this.matrix);
} else {
this.matrixWorld.multiplyMatrices(this.parent.matrixWorld, this.matrix);
}
this.matrixWorldNeedsUpdate = false;
force = true;
}
// update children
const children = this.children;
for (let i = 0, l = children.length; i < l; i++) {
const child = children[i];
//if (child.matrixWorldAutoUpdate === true || force === true) {
child.updateMatrixWorld(force);
//}
}
}
}
Object3D.DEFAULT_UP = /*@__PURE__*/ new Vector3(0, 1, 0);
Object3D.DEFAULT_MATRIX_AUTO_UPDATE = true;
Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE = true;
// core/BufferAttribute.js
class BufferAttribute {
constructor(array, itemSize, normalized = false) {
if (Array.isArray(array)) {
throw new TypeError('BufferAttribute: array should be a Typed Array.');
}
this.isBufferAttribute = true;
this.array = array;
this.itemSize = itemSize;
this.count = array !== undefined ? array.length / itemSize : 0;
this.normalized = normalized;
// FIXME: new variables
//this.usage = StaticDrawUsage;
//this._updateRange = { offset: 0, count: -1 };
//this.updateRanges = [];
//this.gpuType = FloatType;
// FIXME: old variables
this.dynamic = false;
this.updateRange = { offset: 0, count: - 1 };
this.uuid = generateUUID();
this.version = 0
}
onUploadCallback() {}
}
// core/BufferGeometry.js
let _id = 0;
class BufferGeometry extends EventDispatcher {
constructor() {
super();
this.isBufferGeometry = true;
Object.defineProperty(this, 'id', { value: _id++ });
this.uuid = generateUUID();
this.name = '';
this.type = 'BufferGeometry';
this.index = null;
this.attributes = {};
this.groups = [];
this.boundingBox = null;
this.boundingSphere = null;
this.drawRange = { start: 0, count: Infinity };
}
getIndex() {
return this.index;
}
setIndex(index) {
this.index = index;
}
setAttribute(name, attribute) {
this.attributes[name] = attribute;
return this;
}
dispose() {
this.dispatchEvent({ type: 'dispose' });
}
}
// objects/Mesh.js
class Mesh extends Object3D {
constructor(geometry, material) {
super();
this.isMesh = true;
this.type = 'Mesh';
if (!geometry) throw new TypeError('Mesh: geometry not set');
this.geometry = geometry;
this.material = material;
}
}
// cameras/Camera.js
class Camera extends Object3D {
constructor() {
super();
this.isCamera = true;
this.type = 'Camera';
this.matrixWorldInverse = new Matrix4();
this.projectionMatrix = new Matrix4();
this.projectionMatrixInverse = new Matrix4();
//this.coordinateSystem = WebGLCoordinateSystem;
}
// FIXME
//updateMatrixWorld(force) {
// super.updateMatrixWorld(force);
// this.matrixWorldInverse.copy(this.matrixWorld).invert();
//}
//updateWorldMatrix(updateParents, updateChildren) {
// super.updateWorldMatrix(updateParents, updateChildren);
// this.matrixWorldInverse.copy(this.matrixWorld).invert();
//}
}
// cameras/OrthographicCamera.js
class OrthographicCamera extends Camera {
constructor(left = -1, right = 1, top = 1, bottom = -1, near = 0.1, far = 2000) {
super();
this.type = 'OrthographicCamera';
this.zoom = 1;
//this.view = null;
this.left = left;
this.right = right;
this.top = top;
this.bottom = bottom;
this.near = near;
this.far = far;
this.updateProjectionMatrix();
}
updateProjectionMatrix() {
const dx = (this.right - this.left) / (2 * this.zoom);
const dy = (this.top - this.bottom) / (2 * this.zoom);
const cx = (this.right + this.left) / 2;
const cy = (this.top + this.bottom) / 2;
let left = cx - dx;
let right = cx + dx;
let top = cy + dy;
let bottom = cy - dy;
this.projectionMatrix.makeOrthographic(left, right, top, bottom, this.near, this.far);
this.projectionMatrixInverse.copy(this.projectionMatrix).invert();
}
}
// renderers/webgl/WebGLIndexedBufferRenderer.js (not updated)
function WebGLIndexedBufferRenderer( gl, extensions, infoRender ) {
let mode;
function setMode( value ) {
mode = value;
}
let type, size;
function setIndex( index ) {
if ( index.array instanceof Uint32Array && extensions.get( 'OES_element_index_uint' ) ) {
type = gl.UNSIGNED_INT;
size = 4;
} else if ( index.array instanceof Uint16Array ) {
type = gl.UNSIGNED_SHORT;
size = 2;
} else {
type = gl.UNSIGNED_BYTE;
size = 1;
}
}
function render( start, count ) {
gl.drawElements( mode, count, type, start * size );
infoRender.calls ++;
infoRender.vertices += count;
if ( mode === gl.TRIANGLES ) infoRender.faces += count / 3;
}
return {
setMode: setMode,
setIndex: setIndex,
render: render,
};
}
// renderers/webgl/WebGLBufferRenderer.js (not updated)
function WebGLBufferRenderer( gl, extensions, infoRender ) {
let mode;
function setMode( value ) {
mode = value;
}
function render( start, count ) {
gl.drawArrays( mode, start, count );
infoRender.calls ++;
infoRender.vertices += count;
if ( mode === gl.TRIANGLES ) infoRender.faces += count / 3;
}
return {
setMode: setMode,
render: render,
};
}
// renderers/webgl/WebGLShader.js (not updated)
function WebGLShader( gl, type, string ) {
let shader = gl.createShader( type );
gl.shaderSource( shader, string );
gl.compileShader( shader );
if ( gl.getShaderParameter( shader, gl.COMPILE_STATUS ) === false ) {
console.error( 'WebGLShader: Shader couldn\'t compile.' );
}
if ( gl.getShaderInfoLog( shader ) !== '' ) {
let info = gl.getShaderInfoLog( shader );
// workaround for https://github.com/mrdoob/three.js/issues/9716
if (info.indexOf('GL_ARB_gpu_shader5') === -1) {
console.warn( 'WebGLShader: gl.getShaderInfoLog()', type === gl.VERTEX_SHADER ? 'vertex' : 'fragment', info, string );
}
}
return shader;
}
// renderers/webgl/WebGLProgram.js (not updated)
let programIdCount = 0;
function generateExtensions( extensions, parameters, rendererExtensions ) {
extensions = extensions || {};
let chunks = [
( extensions.fragDepth ) && rendererExtensions.get( 'EXT_frag_depth' ) ? '#extension GL_EXT_frag_depth : enable' : '',
];
return chunks.join( '\n' );
}
function fetchAttributeLocations( gl, program ) {
let attributes = {};
let n = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES );
for ( let i = 0; i < n; i ++ ) {
let info = gl.getActiveAttrib( program, i );
let name = info.name;
// console.log("WebGLProgram: ACTIVE VERTEX ATTRIBUTE:", name, i );
attributes[name] = gl.getAttribLocation( program, name );
}
return attributes;
}
function WebGLProgram( renderer, code, material, parameters ) {
let gl = renderer.context;
let extensions = material.extensions;
let vertexShader = material.__webglShader.vertexShader;
let fragmentShader = material.__webglShader.fragmentShader;
// console.log( 'building new program ' );
//
let customExtensions = generateExtensions( extensions, parameters, renderer.extensions );
//
let program = gl.createProgram();
let prefixVertex, prefixFragment;
prefixVertex = [
'precision ' + parameters.precision + ' float;',
'precision ' + parameters.precision + ' int;',
'#define SHADER_NAME ' + material.__webglShader.name,
'uniform mat4 modelMatrix;',
'uniform mat4 modelViewMatrix;',
'uniform mat4 projectionMatrix;',
'uniform mat4 viewMatrix;',
'attribute vec3 position;',
'',
].join( '\n' );
prefixFragment = [
customExtensions,
'precision ' + parameters.precision + ' float;',
'precision ' + parameters.precision + ' int;',
'#define SHADER_NAME ' + material.__webglShader.name,
( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '',
'',
].join( '\n' );
let vertexGlsl = prefixVertex + vertexShader;
let fragmentGlsl = prefixFragment + fragmentShader;
// console.log( '*VERTEX*', vertexGlsl );
// console.log( '*FRAGMENT*', fragmentGlsl );
let glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl );
let glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl );
gl.attachShader( program, glVertexShader );
gl.attachShader( program, glFragmentShader );
gl.linkProgram( program );
let programLog = gl.getProgramInfoLog( program );
let vertexLog = gl.getShaderInfoLog( glVertexShader );
let fragmentLog = gl.getShaderInfoLog( glFragmentShader );
// console.log( '**VERTEX**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( glVertexShader ) );
// console.log( '**FRAGMENT**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( glFragmentShader ) );
if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === false ) {
console.error( 'WebGLProgram: shader error: ', gl.getError(), 'gl.VALIDATE_STATUS', gl.getProgramParameter( program, gl.VALIDATE_STATUS ), 'gl.getProgramInfoLog', programLog, vertexLog, fragmentLog );
} else if ( programLog !== '' ) {
console.warn( 'WebGLProgram: gl.getProgramInfoLog()', programLog );
}
// clean up
gl.deleteShader( glVertexShader );
gl.deleteShader( glFragmentShader );
// set up caching for uniform locations
let cachedUniforms;
this.getUniforms = function () {
if ( cachedUniforms === undefined ) {
cachedUniforms = new WebGLUniforms( gl, program );
}
return cachedUniforms;
};
// set up caching for attribute locations
let cachedAttributes;
this.getAttributes = function () {
if ( cachedAttributes === undefined ) {
cachedAttributes = fetchAttributeLocations( gl, program );
}
return cachedAttributes;
};
// free resource
this.destroy = function () {
gl.deleteProgram( program );
this.program = undefined;
};
//
this.id = programIdCount ++;
this.code = code;
this.usedTimes = 1;
this.program = program;
this.vertexShader = glVertexShader;
this.fragmentShader = glFragmentShader;
return this;
}
function WebGLPrograms( renderer, capabilities ) {
let programs = [];
let parameterNames = [
'precision',
'fog', 'useFog',
'premultipliedAlpha',
];
this.getParameters = function ( material, fog ) {
let precision = renderer.getPrecision();
if ( material.precision !== null ) {
precision = capabilities.getMaxPrecision( material.precision );
if ( precision !== material.precision ) {
console.warn( 'WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' );
}
}
let parameters = {
precision: precision,
fog: !! fog,
useFog: material.fog,
premultipliedAlpha: material.premultipliedAlpha,
};
return parameters;
};
this.getProgramCode = function ( material, parameters ) {
let array = [];
array.push( material.fragmentShader );
array.push( material.vertexShader );
for ( let i = 0; i < parameterNames.length; i ++ ) {
array.push( parameters[parameterNames[i]] );
}
return array.join();
};
this.acquireProgram = function ( material, parameters, code ) {
let program;
// Check if code has been already compiled
for ( let p = 0, pl = programs.length; p < pl; p ++ ) {
let programInfo = programs[p];
if ( programInfo.code === code ) {
program = programInfo;
++ program.usedTimes;
break;
}
}
if ( program === undefined ) {
program = new WebGLProgram( renderer, code, material, parameters );
programs.push( program );
}
return program;
};
this.releaseProgram = function ( program ) {
if ( -- program.usedTimes === 0 ) {
// Remove from unordered set
let i = programs.indexOf( program );
programs[i] = programs[programs.length - 1];
programs.pop();
// Free WebGL resources
program.destroy();
}
};
// Exposed for resource monitoring & error feedback via renderer.info:
this.programs = programs;
}
// renderers/webgl/WebGLGeometries.js (not updated)
function WebGLGeometries( gl, properties ) {
let geometries = {};
function onGeometryDispose( event ) {
let geometry = event.target;
let buffergeometry = geometries[geometry.id];
if ( buffergeometry.index !== null ) {
deleteAttribute( buffergeometry.index );
}
deleteAttributes( buffergeometry.attributes );
geometry.removeEventListener( 'dispose', onGeometryDispose );
delete geometries[geometry.id];
properties.delete( geometry );
properties.delete( buffergeometry );
}
function getAttributeBuffer( attribute ) {
return properties.get( attribute ).__webglBuffer;
}
function deleteAttribute( attribute ) {
let buffer = getAttributeBuffer( attribute );
if ( buffer !== undefined ) {
gl.deleteBuffer( buffer );
removeAttributeBuffer( attribute );
}
}
function deleteAttributes( attributes ) {
for ( let name in attributes ) {
deleteAttribute( attributes[name] );
}
}
function removeAttributeBuffer( attribute ) {
properties.delete( attribute );
}
return {
get: function ( object ) {
let geometry = object.geometry;
if ( geometries[geometry.id] !== undefined ) {
return geometries[geometry.id];
}
geometry.addEventListener( 'dispose', onGeometryDispose );
let buffergeometry;
if ( geometry.isBufferGeometry ) {
buffergeometry = geometry;
}
geometries[geometry.id] = buffergeometry;
return buffergeometry;
},
};
}
// renderers/webgl/WebGLObjects.js (not updated)
function WebGLObjects( gl, properties, info ) {
let geometries = new WebGLGeometries( gl, properties, info );
//
function update( object ) {
let geometry = geometries.get( object );
let index = geometry.index;
let attributes = geometry.attributes;
if ( index !== null ) {
updateAttribute( index, gl.ELEMENT_ARRAY_BUFFER );
}
for ( let name in attributes ) {
updateAttribute( attributes[name], gl.ARRAY_BUFFER );
}
return geometry;
}
function updateAttribute( attribute, bufferType ) {
let data = attribute;
let attributeProperties = properties.get( data );
if ( attributeProperties.__webglBuffer === undefined ) {
createBuffer( attributeProperties, data, bufferType );
} else if ( attributeProperties.version !== data.version ) {
updateBuffer( attributeProperties, data, bufferType );
}
}
function createBuffer( attributeProperties, data, bufferType ) {
attributeProperties.__webglBuffer = gl.createBuffer();
gl.bindBuffer( bufferType, attributeProperties.__webglBuffer );
let usage = data.dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW;
gl.bufferData( bufferType, data.array, usage );
let type = gl.FLOAT;
let array = data.array;
if ( array instanceof Float32Array ) {
type = gl.FLOAT;
} else if ( array instanceof Float64Array ) {
console.warn( 'Unsupported data buffer format: Float64Array' );
} else if ( array instanceof Uint16Array ) {
type = gl.UNSIGNED_SHORT;
} else if ( array instanceof Int16Array ) {
type = gl.SHORT;
} else if ( array instanceof Uint32Array ) {
type = gl.UNSIGNED_INT;
} else if ( array instanceof Int32Array ) {
type = gl.INT;
} else if ( array instanceof Int8Array ) {
type = gl.BYTE;
} else if ( array instanceof Uint8Array ) {
type = gl.UNSIGNED_BYTE;
}
attributeProperties.bytesPerElement = array.BYTES_PER_ELEMENT;
attributeProperties.type = type;
attributeProperties.version = data.version;
data.onUploadCallback();
}
function updateBuffer( attributeProperties, data, bufferType ) {
gl.bindBuffer( bufferType, attributeProperties.__webglBuffer );
if ( data.dynamic === false ) {
gl.bufferData( bufferType, data.array, gl.STATIC_DRAW );
} else if ( data.updateRange.count === - 1 ) {
// Not using update ranges
gl.bufferSubData( bufferType, 0, data.array );
} else if ( data.updateRange.count === 0 ) {
console.error( 'WebGLObjects.updateBuffer: updateRange.count is 0.' );
} else {
gl.bufferSubData( bufferType, data.updateRange.offset * data.array.BYTES_PER_ELEMENT,
data.array.subarray( data.updateRange.offset, data.updateRange.offset + data.updateRange.count ) );
data.updateRange.count = 0; // reset range
}
attributeProperties.version = data.version;
}
function getAttributeBuffer( attribute ) {
return properties.get( attribute ).__webglBuffer;
}
function getAttributeProperties( attribute ) {
return properties.get( attribute );
}
return {
getAttributeBuffer: getAttributeBuffer,
getAttributeProperties: getAttributeProperties,
update: update,
};
}
// renderers/webgl/WebGLTextures.js (not updated)
function WebGLTextures( _gl, extensions, state, properties, capabilities ) {
function onTextureDispose( event ) {
let texture = event.target;
texture.removeEventListener( 'dispose', onTextureDispose );
deallocateTexture( texture );
}
//
function deallocateTexture( texture ) {
let textureProperties = properties.get( texture );
// 2D texture
if ( textureProperties.__webglInit === undefined ) return;
_gl.deleteTexture( textureProperties.__webglTexture );
// remove all webgl properties
properties.delete( texture );
}
let textureUnits = 0;
function resetTextureUnits() {
textureUnits = 0;
}
function allocateTextureUnit() {
const textureUnit = textureUnits;
if (textureUnit >= capabilities.maxTextures) {
console.warn('WebGLTextures: Trying to use ' + textureUnit +
' texture units while this GPU supports only ' + capabilities.maxTextures);
}
textureUnits += 1;
return textureUnit;
}
function setTexture2D( texture, slot ) {
let textureProperties = properties.get( texture );
if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
let image = texture.image;
if ( image === undefined ) {
console.warn( 'WebGLRenderer: Texture marked for update but image is undefined', texture );
} else if ( image.complete === false ) {
console.warn( 'WebGLRenderer: Texture marked for update but image is incomplete', texture );
} else {
uploadTexture( textureProperties, texture, slot );
return;
}
}
state.activeTexture( _gl.TEXTURE0 + slot );
state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture );
}
function setTextureParameters( textureType ) {
_gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE );
_gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE );
_gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, _gl.LINEAR );
_gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, _gl.LINEAR_MIPMAP_LINEAR );
}
function uploadTexture( textureProperties, texture, slot ) {
if ( textureProperties.__webglInit === undefined ) {
textureProperties.__webglInit = true;
texture.addEventListener( 'dispose', onTextureDispose );
textureProperties.__webglTexture = _gl.createTexture();
}
state.activeTexture( _gl.TEXTURE0 + slot );
state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture );
_gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, true );
_gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false );
_gl.pixelStorei( _gl.UNPACK_ALIGNMENT, 4 );
let image = texture.image;
let glFormat = _gl.RGBA;
let glType = _gl.UNSIGNED_BYTE;
setTextureParameters( _gl.TEXTURE_2D );
state.texImage2D( _gl.TEXTURE_2D, 0, glFormat, glFormat, glType, image );
_gl.generateMipmap( _gl.TEXTURE_2D );
textureProperties.__version = texture.version;
}
this.setTexture2D = setTexture2D;
this.resetTextureUnits = resetTextureUnits;
this.allocateTextureUnit = allocateTextureUnit;
}
// renderers/webgl/WebGLProperties.js (not updated)
function WebGLProperties() {
let properties = {};
return {
get: function ( object ) {
let uuid = object.uuid;
let map = properties[uuid];
if ( map === undefined ) {
map = {};
properties[uuid] = map;
}
return map;
},
delete: function ( object ) {
delete properties[object.uuid];
},
clear: function () {
properties = {};
}
};
}
// renderers/webgl/WebGLState.js (not updated)
function WebGLState( gl ) {
function ColorBuffer() {
let color = new Vector4();
let currentColorClear = new Vector4();
return {
setClear: function ( r, g, b, a, premultipliedAlpha ) {
if ( premultipliedAlpha === true ) {
r *= a; g *= a; b *= a;
}
color.set( r, g, b, a );
if ( currentColorClear.equals( color ) === false ) {
gl.clearColor( r, g, b, a );
currentColorClear.copy( color );
}
},
reset: function () {
currentColorClear.set( 0, 0, 0, 1 );
}
};
}
function DepthBuffer() {
let currentDepthMask = null;
let currentDepthClear = null;
return {
setTest: function ( depthTest ) {
if ( depthTest ) {
enable( gl.DEPTH_TEST );
} else {
disable( gl.DEPTH_TEST );
}
},
setMask: function ( depthMask ) {
if ( currentDepthMask !== depthMask ) {
gl.depthMask( depthMask );
currentDepthMask = depthMask;
}
},
setClear: function ( depth ) {
if ( currentDepthClear !== depth ) {
gl.clearDepth( depth );
currentDepthClear = depth;
}
},
reset: function () {
currentDepthMask = null;
currentDepthClear = null;
},
};
}
//
let colorBuffer = new ColorBuffer();
let depthBuffer = new DepthBuffer();
let maxVertexAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS );
let newAttributes = new Uint8Array( maxVertexAttributes );
let enabledAttributes = new Uint8Array( maxVertexAttributes );
let capabilities = {};
let currentBlending = null;
let currentPremultipledAlpha = false;
let currentLineWidth = null;
const glVersion = gl.getParameter(gl.VERSION);
let lineWidthAvailable = false;
if ( glVersion.indexOf( 'WebGL' ) !== - 1 ) {
let version = parseFloat( /^WebGL\ (\d)/.exec( glVersion )[1] );
lineWidthAvailable = ( version >= 1.0 );
}
let currentTextureSlot = null;
let currentBoundTextures = {};
let currentViewport = new Vector4();
function createTexture( type, target, count ) {
let data = new Uint8Array( 4 ); // 4 is required to match default unpack alignment of 4.
let texture = gl.createTexture();
gl.bindTexture( type, texture );
gl.texParameteri( type, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
gl.texParameteri( type, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
for ( let i = 0; i < count; i ++ ) {
gl.texImage2D( target + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data );
}
return texture;
}
let emptyTextures = {};
emptyTextures[gl.TEXTURE_2D] = createTexture( gl.TEXTURE_2D, gl.TEXTURE_2D, 1 );
//
function init() {
colorBuffer.setClear( 0, 0, 0, 1 );
depthBuffer.setClear( 1 );
enable( gl.DEPTH_TEST );
gl.depthFunc( gl.LEQUAL );
enable( gl.BLEND );
setBlending( NormalBlending );
}
function initAttributes() {
for ( let i = 0, l = newAttributes.length; i < l; i ++ ) {
newAttributes[i] = 0;
}
}
function enableAttribute( attribute ) {
newAttributes[attribute] = 1;
if ( enabledAttributes[attribute] === 0 ) {
gl.enableVertexAttribArray( attribute );
enabledAttributes[attribute] = 1;
}
}
function disableUnusedAttributes() {
for ( let i = 0, l = enabledAttributes.length; i !== l; ++ i ) {
if ( enabledAttributes[i] !== newAttributes[i] ) {
gl.disableVertexAttribArray( i );
enabledAttributes[i] = 0;
}
}
}
function enable( id ) {
if ( capabilities[id] !== true ) {
gl.enable( id );
capabilities[id] = true;
}
}
function disable( id ) {
if ( capabilities[id] !== false ) {
gl.disable( id );
capabilities[id] = false;
}
}
function setBlending( blending, premultipliedAlpha ) {
if ( blending !== NoBlending ) {
enable( gl.BLEND );
} else {
disable( gl.BLEND );
}
if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) {
if ( premultipliedAlpha ) {
gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD );
gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA );
} else {
gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD );
gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA );
}
currentBlending = blending;
currentPremultipledAlpha = premultipliedAlpha;
}
}
function setDepthTest( depthTest ) {
depthBuffer.setTest( depthTest );
}
function setDepthWrite( depthWrite ) {
depthBuffer.setMask( depthWrite );
}
//
function setLineWidth( width ) {
if ( width !== currentLineWidth ) {
if ( lineWidthAvailable ) gl.lineWidth( width );
currentLineWidth = width;
}
}
// texture
function activeTexture( webglSlot ) {
if ( currentTextureSlot !== webglSlot ) {
gl.activeTexture( webglSlot );
currentTextureSlot = webglSlot;
}
}
function bindTexture( webglType, webglTexture ) {
let boundTexture = currentBoundTextures[currentTextureSlot];
if ( boundTexture === undefined ) {
boundTexture = { type: undefined, texture: undefined };
currentBoundTextures[currentTextureSlot] = boundTexture;
}
if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) {
gl.bindTexture( webglType, webglTexture || emptyTextures[webglType] );
boundTexture.type = webglType;
boundTexture.texture = webglTexture;
}
}
function texImage2D() {
try {
gl.texImage2D.apply( gl, arguments );
} catch ( error ) {
console.error( error );
}
}
//
function viewport( viewport ) {
if ( currentViewport.equals( viewport ) === false ) {
gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w );
currentViewport.copy( viewport );
}
}
function reset() {
for ( let i = 0; i < enabledAttributes.length; i ++ ) {
if ( enabledAttributes[ i ] === 1 ) {
gl.disableVertexAttribArray( i );
enabledAttributes[ i ] = 0;
}
}
capabilities = {};
currentTextureSlot = null;
currentBoundTextures = {};
currentBlending = null;
colorBuffer.reset();
depthBuffer.reset();
}
//
return {
buffers: {
color: colorBuffer,
depth: depthBuffer,
},
init: init,
initAttributes: initAttributes,
enableAttribute: enableAttribute,
disableUnusedAttributes: disableUnusedAttributes,
enable: enable,
disable: disable,
setBlending: setBlending,
setDepthTest: setDepthTest,
setDepthWrite: setDepthWrite,
setLineWidth: setLineWidth,
activeTexture: activeTexture,
bindTexture: bindTexture,
texImage2D: texImage2D,
viewport: viewport,
reset: reset
};
}
// renderers/webgl/WebGLCapabilities.js (not updated)
function WebGLCapabilities( gl, extensions, parameters ) {
function getMaxPrecision( precision ) {
if ( precision === 'highp' ) {
if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.HIGH_FLOAT ).precision > 0 &&
gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.HIGH_FLOAT ).precision > 0 ) {
return 'highp';
}
precision = 'mediump';
}
if ( precision === 'mediump' ) {
if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.MEDIUM_FLOAT ).precision > 0 &&
gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT ).precision > 0 ) {
return 'mediump';
}
}
return 'lowp';
}
let precision = parameters.precision !== undefined ? parameters.precision : 'highp';
let maxPrecision = getMaxPrecision( precision );
if ( maxPrecision !== precision ) {
console.warn( 'WebGLRenderer:', precision, 'not supported, using', maxPrecision, 'instead.' );
precision = maxPrecision;
}
let maxTextures = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS );
return {
getMaxPrecision: getMaxPrecision,
precision: precision,
maxTextures: maxTextures,
};
}
// renderers/webgl/WebGLExtensions.js (not updated)
function WebGLExtensions( gl ) {
let extensions = {};
return {
get: function ( name ) {
if ( extensions[name] !== undefined ) {
return extensions[name];
}
let extension;
switch ( name ) {
case 'WEBGL_depth_texture':
extension = gl.getExtension( 'WEBGL_depth_texture' ) || gl.getExtension( 'MOZ_WEBGL_depth_texture' ) || gl.getExtension( 'WEBKIT_WEBGL_depth_texture' );
break;
default:
extension = gl.getExtension( name );
}
if ( extension === null ) {
console.warn( 'WebGLRenderer: ' + name + ' extension not supported.' );
}
extensions[name] = extension;
return