UNPKG

uglymol

Version:

Macromolecular Viewer for Crystallographers

1,979 lines (1,561 loc) 71 kB
// 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