UNPKG

@sauskylark/potree

Version:

WebGL point cloud viewer

1,103 lines (897 loc) 26.2 kB
import * as THREE from "../../libs/three.js/build/three.module.js"; import {Utils} from "../utils.js"; import {Gradients} from "./Gradients.js"; import {Shaders} from "../../build/shaders/shaders.js"; import {ClassificationScheme} from "./ClassificationScheme.js"; import {PointSizeType, PointShape, TreeType, ElevationGradientRepeat} from "../defines.js"; // // how to calculate the radius of a projected sphere in screen space // http://stackoverflow.com/questions/21648630/radius-of-projected-sphere-in-screen-space // http://stackoverflow.com/questions/3717226/radius-of-projected-sphere // export class PointCloudMaterial extends THREE.RawShaderMaterial { constructor (parameters = {}) { super(); this.visibleNodesTexture = Utils.generateDataTexture(2048, 1, new THREE.Color(0xffffff)); this.visibleNodesTexture.minFilter = THREE.NearestFilter; this.visibleNodesTexture.magFilter = THREE.NearestFilter; let getValid = (a, b) => { if(a !== undefined){ return a; }else{ return b; } } let pointSize = getValid(parameters.size, 1.0); let minSize = getValid(parameters.minSize, 2.0); let maxSize = getValid(parameters.maxSize, 50.0); let treeType = getValid(parameters.treeType, TreeType.OCTREE); this._pointSizeType = PointSizeType.FIXED; this._shape = PointShape.SQUARE; this._useClipBox = false; this.clipBoxes = []; this.clipPolygons = []; this._weighted = false; this._gradient = Gradients.SPECTRAL; this.gradientTexture = PointCloudMaterial.generateGradientTexture(this._gradient); this._matcap = "matcap.jpg"; this.matcapTexture = Potree.PointCloudMaterial.generateMatcapTexture(this._matcap); this.lights = false; this.fog = false; this._treeType = treeType; this._useEDL = false; this.defines = new Map(); this.ranges = new Map(); this._activeAttributeName = null; this._defaultIntensityRangeChanged = false; this._defaultElevationRangeChanged = false; { const [width, height] = [256, 1]; let data = new Uint8Array(width * 4); let texture = new THREE.DataTexture(data, width, height, THREE.RGBAFormat); texture.magFilter = THREE.NearestFilter; texture.needsUpdate = true; this.classificationTexture = texture; } this.attributes = { position: { type: 'fv', value: [] }, color: { type: 'fv', value: [] }, normal: { type: 'fv', value: [] }, intensity: { type: 'f', value: [] }, classification: { type: 'f', value: [] }, returnNumber: { type: 'f', value: [] }, numberOfReturns: { type: 'f', value: [] }, pointSourceID: { type: 'f', value: [] }, indices: { type: 'fv', value: [] } }; this.uniforms = { level: { type: "f", value: 0.0 }, vnStart: { type: "f", value: 0.0 }, spacing: { type: "f", value: 1.0 }, blendHardness: { type: "f", value: 2.0 }, blendDepthSupplement: { type: "f", value: 0.0 }, fov: { type: "f", value: 1.0 }, screenWidth: { type: "f", value: 1.0 }, screenHeight: { type: "f", value: 1.0 }, near: { type: "f", value: 0.1 }, far: { type: "f", value: 1.0 }, uColor: { type: "c", value: new THREE.Color( 0xffffff ) }, uOpacity: { type: "f", value: 1.0 }, size: { type: "f", value: pointSize }, minSize: { type: "f", value: minSize }, maxSize: { type: "f", value: maxSize }, octreeSize: { type: "f", value: 0 }, bbSize: { type: "fv", value: [0, 0, 0] }, elevationRange: { type: "2fv", value: [0, 0] }, clipBoxCount: { type: "f", value: 0 }, //clipSphereCount: { type: "f", value: 0 }, clipPolygonCount: { type: "i", value: 0 }, clipBoxes: { type: "Matrix4fv", value: [] }, //clipSpheres: { type: "Matrix4fv", value: [] }, clipPolygons: { type: "3fv", value: [] }, clipPolygonVCount: { type: "iv", value: [] }, clipPolygonVP: { type: "Matrix4fv", value: [] }, visibleNodes: { type: "t", value: this.visibleNodesTexture }, pcIndex: { type: "f", value: 0 }, gradient: { type: "t", value: this.gradientTexture }, classificationLUT: { type: "t", value: this.classificationTexture }, uHQDepthMap: { type: "t", value: null }, toModel: { type: "Matrix4f", value: [] }, diffuse: { type: "fv", value: [1, 1, 1] }, transition: { type: "f", value: 0.5 }, intensityRange: { type: "fv", value: [Infinity, -Infinity] }, intensity_gbc: { type: "fv", value: [1, 0, 0]}, uRGB_gbc: { type: "fv", value: [1, 0, 0]}, // intensityGamma: { type: "f", value: 1 }, // intensityContrast: { type: "f", value: 0 }, // intensityBrightness:{ type: "f", value: 0 }, // rgbGamma: { type: "f", value: 1 }, // rgbContrast: { type: "f", value: 0 }, // rgbBrightness: { type: "f", value: 0 }, wRGB: { type: "f", value: 1 }, wIntensity: { type: "f", value: 0 }, wElevation: { type: "f", value: 0 }, wClassification: { type: "f", value: 0 }, wReturnNumber: { type: "f", value: 0 }, wSourceID: { type: "f", value: 0 }, useOrthographicCamera: { type: "b", value: false }, elevationGradientRepat: { type: "i", value: ElevationGradientRepeat.CLAMP }, clipTask: { type: "i", value: 1 }, clipMethod: { type: "i", value: 1 }, uShadowColor: { type: "3fv", value: [0, 0, 0] }, uExtraScale: { type: "f", value: 1}, uExtraOffset: { type: "f", value: 0}, uExtraRange: { type: "2fv", value: [0, 1] }, uExtraGammaBrightContr: { type: "3fv", value: [1, 0, 0] }, uFilterReturnNumberRange: { type: "fv", value: [0, 7]}, uFilterNumberOfReturnsRange: { type: "fv", value: [0, 7]}, uFilterGPSTimeClipRange: { type: "fv", value: [0, 7]}, uFilterPointSourceIDClipRange: { type: "fv", value: [0, 65535]}, matcapTextureUniform: { type: "t", value: this.matcapTexture }, backfaceCulling: { type: "b", value: false }, }; this.classification = ClassificationScheme.DEFAULT; this.defaultAttributeValues.normal = [0, 0, 0]; this.defaultAttributeValues.classification = [0, 0, 0]; this.defaultAttributeValues.indices = [0, 0, 0, 0]; this.vertexShader = Shaders['pointcloud.vs']; this.fragmentShader = Shaders['pointcloud.fs']; this.vertexColors = THREE.VertexColors; this.updateShaderSource(); } setDefine(key, value){ if(value !== undefined && value !== null){ if(this.defines.get(key) !== value){ this.defines.set(key, value); this.updateShaderSource(); } }else{ this.removeDefine(key); } } removeDefine(key){ this.defines.delete(key); } updateShaderSource () { let vs = Shaders['pointcloud.vs']; let fs = Shaders['pointcloud.fs']; let definesString = this.getDefines(); let vsVersionIndex = vs.indexOf("#version "); let fsVersionIndex = fs.indexOf("#version "); if(vsVersionIndex >= 0){ vs = vs.replace(/(#version .*)/, `$1\n${definesString}`) }else{ vs = `${definesString}\n${vs}`; } if(fsVersionIndex >= 0){ fs = fs.replace(/(#version .*)/, `$1\n${definesString}`) }else{ fs = `${definesString}\n${fs}`; } this.vertexShader = vs; this.fragmentShader = fs; if (this.opacity === 1.0) { this.blending = THREE.NoBlending; this.transparent = false; this.depthTest = true; this.depthWrite = true; this.depthFunc = THREE.LessEqualDepth; } else if (this.opacity < 1.0 && !this.useEDL) { this.blending = THREE.AdditiveBlending; this.transparent = true; this.depthTest = false; this.depthWrite = true; this.depthFunc = THREE.AlwaysDepth; } if (this.weighted) { this.blending = THREE.AdditiveBlending; this.transparent = true; this.depthTest = true; this.depthWrite = false; } this.needsUpdate = true; } getDefines () { let defines = []; if (this.pointSizeType === PointSizeType.FIXED) { defines.push('#define fixed_point_size'); } else if (this.pointSizeType === PointSizeType.ATTENUATED) { defines.push('#define attenuated_point_size'); } else if (this.pointSizeType === PointSizeType.ADAPTIVE) { defines.push('#define adaptive_point_size'); } if (this.shape === PointShape.SQUARE) { defines.push('#define square_point_shape'); } else if (this.shape === PointShape.CIRCLE) { defines.push('#define circle_point_shape'); } else if (this.shape === PointShape.PARABOLOID) { defines.push('#define paraboloid_point_shape'); } if (this._useEDL) { defines.push('#define use_edl'); } if(this.activeAttributeName){ let attributeName = this.activeAttributeName.replace(/[^a-zA-Z0-9]/g, '_'); defines.push(`#define color_type_${attributeName}`); } if(this._treeType === TreeType.OCTREE){ defines.push('#define tree_type_octree'); }else if(this._treeType === TreeType.KDTREE){ defines.push('#define tree_type_kdtree'); } if (this.weighted) { defines.push('#define weighted_splats'); } for(let [key, value] of this.defines){ defines.push(value); } return defines.join("\n"); } setClipBoxes (clipBoxes) { if (!clipBoxes) { return; } let doUpdate = (this.clipBoxes.length !== clipBoxes.length) && (clipBoxes.length === 0 || this.clipBoxes.length === 0); this.uniforms.clipBoxCount.value = this.clipBoxes.length; this.clipBoxes = clipBoxes; if (doUpdate) { this.updateShaderSource(); } this.uniforms.clipBoxes.value = new Float32Array(this.clipBoxes.length * 16); for (let i = 0; i < this.clipBoxes.length; i++) { let box = clipBoxes[i]; this.uniforms.clipBoxes.value.set(box.inverse.elements, 16 * i); } for (let i = 0; i < this.uniforms.clipBoxes.value.length; i++) { if (Number.isNaN(this.uniforms.clipBoxes.value[i])) { this.uniforms.clipBoxes.value[i] = Infinity; } } } setClipPolygons(clipPolygons, maxPolygonVertices) { if(!clipPolygons){ return; } this.clipPolygons = clipPolygons; let doUpdate = (this.clipPolygons.length !== clipPolygons.length); if(doUpdate){ this.updateShaderSource(); } } get gradient(){ return this._gradient; } set gradient (value) { if (this._gradient !== value) { this._gradient = value; this.gradientTexture = PointCloudMaterial.generateGradientTexture(this._gradient); this.uniforms.gradient.value = this.gradientTexture; } } get matcap(){ return this._matcap; } set matcap (value) { if (this._matcap !== value) { this._matcap = value; this.matcapTexture = Potree.PointCloudMaterial.generateMatcapTexture(this._matcap); this.uniforms.matcapTextureUniform.value = this.matcapTexture; } } get useOrthographicCamera() { return this.uniforms.useOrthographicCamera.value; } set useOrthographicCamera(value) { if(this.uniforms.useOrthographicCamera.value !== value){ this.uniforms.useOrthographicCamera.value = value; } } get backfaceCulling() { return this.uniforms.backfaceCulling.value; } set backfaceCulling(value) { if(this.uniforms.backfaceCulling.value !== value){ this.uniforms.backfaceCulling.value = value; this.dispatchEvent({type: 'backface_changed', target: this}); } } recomputeClassification () { const classification = this.classification; const data = this.classificationTexture.image.data; let width = 256; const black = [1, 1, 1, 1]; let valuesChanged = false; for (let i = 0; i < width; i++) { let color; let visible = true; if (classification[i]) { color = classification[i].color; visible = classification[i].visible; } else if (classification[i % 32]) { color = classification[i % 32].color; visible = classification[i % 32].visible; } else if(classification.DEFAULT) { color = classification.DEFAULT.color; visible = classification.DEFAULT.visible; }else{ color = black; } const r = parseInt(255 * color[0]); const g = parseInt(255 * color[1]); const b = parseInt(255 * color[2]); const a = visible ? parseInt(255 * color[3]) : 0; if(data[4 * i + 0] !== r){ data[4 * i + 0] = r; valuesChanged = true; } if(data[4 * i + 1] !== g){ data[4 * i + 1] = g; valuesChanged = true; } if(data[4 * i + 2] !== b){ data[4 * i + 2] = b; valuesChanged = true; } if(data[4 * i + 3] !== a){ data[4 * i + 3] = a; valuesChanged = true; } } if(valuesChanged){ this.classificationTexture.needsUpdate = true; this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get spacing () { return this.uniforms.spacing.value; } set spacing (value) { if (this.uniforms.spacing.value !== value) { this.uniforms.spacing.value = value; } } get useClipBox () { return this._useClipBox; } set useClipBox (value) { if (this._useClipBox !== value) { this._useClipBox = value; this.updateShaderSource(); } } get clipTask(){ return this.uniforms.clipTask.value; } set clipTask(mode){ this.uniforms.clipTask.value = mode; } get elevationGradientRepat(){ return this.uniforms.elevationGradientRepat.value; } set elevationGradientRepat(mode){ this.uniforms.elevationGradientRepat.value = mode; } get clipMethod(){ return this.uniforms.clipMethod.value; } set clipMethod(mode){ this.uniforms.clipMethod.value = mode; } get weighted(){ return this._weighted; } set weighted (value) { if (this._weighted !== value) { this._weighted = value; this.updateShaderSource(); } } get fov () { return this.uniforms.fov.value; } set fov (value) { if (this.uniforms.fov.value !== value) { this.uniforms.fov.value = value; // this.updateShaderSource(); } } get screenWidth () { return this.uniforms.screenWidth.value; } set screenWidth (value) { if (this.uniforms.screenWidth.value !== value) { this.uniforms.screenWidth.value = value; // this.updateShaderSource(); } } get screenHeight () { return this.uniforms.screenHeight.value; } set screenHeight (value) { if (this.uniforms.screenHeight.value !== value) { this.uniforms.screenHeight.value = value; // this.updateShaderSource(); } } get near () { return this.uniforms.near.value; } set near (value) { if (this.uniforms.near.value !== value) { this.uniforms.near.value = value; } } get far () { return this.uniforms.far.value; } set far (value) { if (this.uniforms.far.value !== value) { this.uniforms.far.value = value; } } get opacity(){ return this.uniforms.uOpacity.value; } set opacity (value) { if (this.uniforms && this.uniforms.uOpacity) { if (this.uniforms.uOpacity.value !== value) { this.uniforms.uOpacity.value = value; this.updateShaderSource(); this.dispatchEvent({ type: 'opacity_changed', target: this }); this.dispatchEvent({ type: 'material_property_changed', target: this }); } } } get activeAttributeName(){ return this._activeAttributeName; } set activeAttributeName(value){ if (this._activeAttributeName !== value) { this._activeAttributeName = value; this.updateShaderSource(); this.dispatchEvent({ type: 'active_attribute_changed', target: this }); this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get pointSizeType () { return this._pointSizeType; } set pointSizeType (value) { if (this._pointSizeType !== value) { this._pointSizeType = value; this.updateShaderSource(); this.dispatchEvent({ type: 'point_size_type_changed', target: this }); this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get useEDL(){ return this._useEDL; } set useEDL (value) { if (this._useEDL !== value) { this._useEDL = value; this.updateShaderSource(); } } get color () { return this.uniforms.uColor.value; } set color (value) { if (!this.uniforms.uColor.value.equals(value)) { this.uniforms.uColor.value.copy(value); this.dispatchEvent({ type: 'color_changed', target: this }); this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get shape () { return this._shape; } set shape (value) { if (this._shape !== value) { this._shape = value; this.updateShaderSource(); this.dispatchEvent({type: 'point_shape_changed', target: this}); this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get treeType () { return this._treeType; } set treeType (value) { if (this._treeType !== value) { this._treeType = value; this.updateShaderSource(); } } get bbSize () { return this.uniforms.bbSize.value; } set bbSize (value) { this.uniforms.bbSize.value = value; } get size () { return this.uniforms.size.value; } set size (value) { if (this.uniforms.size.value !== value) { this.uniforms.size.value = value; this.dispatchEvent({ type: 'point_size_changed', target: this }); this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get minSize(){ return this.uniforms.minSize.value; } set minSize(value){ if (this.uniforms.minSize.value !== value) { this.uniforms.minSize.value = value; this.dispatchEvent({ type: 'point_size_changed', target: this }); this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get elevationRange () { return this.uniforms.elevationRange.value; } set elevationRange (value) { let changed = this.uniforms.elevationRange.value[0] !== value[0] || this.uniforms.elevationRange.value[1] !== value[1]; if(changed){ this.uniforms.elevationRange.value = value; this._defaultElevationRangeChanged = true; this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get heightMin () { return this.uniforms.elevationRange.value[0]; } set heightMin (value) { this.elevationRange = [value, this.elevationRange[1]]; } get heightMax () { return this.uniforms.elevationRange.value[1]; } set heightMax (value) { this.elevationRange = [this.elevationRange[0], value]; } get transition () { return this.uniforms.transition.value; } set transition (value) { this.uniforms.transition.value = value; } get intensityRange () { return this.uniforms.intensityRange.value; } set intensityRange (value) { if (!(value instanceof Array && value.length === 2)) { return; } if (value[0] === this.uniforms.intensityRange.value[0] && value[1] === this.uniforms.intensityRange.value[1]) { return; } this.uniforms.intensityRange.value = value; this._defaultIntensityRangeChanged = true; this.dispatchEvent({ type: 'material_property_changed', target: this }); } get intensityGamma () { return this.uniforms.intensity_gbc.value[0]; } set intensityGamma (value) { if (this.uniforms.intensity_gbc.value[0] !== value) { this.uniforms.intensity_gbc.value[0] = value; this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get intensityContrast () { return this.uniforms.intensity_gbc.value[2]; } set intensityContrast (value) { if (this.uniforms.intensity_gbc.value[2] !== value) { this.uniforms.intensity_gbc.value[2] = value; this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get intensityBrightness () { return this.uniforms.intensity_gbc.value[1]; } set intensityBrightness (value) { if (this.uniforms.intensity_gbc.value[1] !== value) { this.uniforms.intensity_gbc.value[1] = value; this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get rgbGamma () { return this.uniforms.uRGB_gbc.value[0]; } set rgbGamma (value) { if (this.uniforms.uRGB_gbc.value[0] !== value) { this.uniforms.uRGB_gbc.value[0] = value; this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get rgbContrast () { return this.uniforms.uRGB_gbc.value[2]; } set rgbContrast (value) { if (this.uniforms.uRGB_gbc.value[2] !== value) { this.uniforms.uRGB_gbc.value[2] = value; this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get rgbBrightness () { return this.uniforms.uRGB_gbc.value[1]; } set rgbBrightness (value) { if (this.uniforms.uRGB_gbc.value[1] !== value) { this.uniforms.uRGB_gbc.value[1] = value; this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get extraGamma () { return this.uniforms.uExtraGammaBrightContr.value[0]; } set extraGamma (value) { if (this.uniforms.uExtraGammaBrightContr.value[0] !== value) { this.uniforms.uExtraGammaBrightContr.value[0] = value; this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get extraBrightness () { return this.uniforms.uExtraGammaBrightContr.value[1]; } set extraBrightness (value) { if (this.uniforms.uExtraGammaBrightContr.value[1] !== value) { this.uniforms.uExtraGammaBrightContr.value[1] = value; this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get extraContrast () { return this.uniforms.uExtraGammaBrightContr.value[2]; } set extraContrast (value) { if (this.uniforms.uExtraGammaBrightContr.value[2] !== value) { this.uniforms.uExtraGammaBrightContr.value[2] = value; this.dispatchEvent({ type: 'material_property_changed', target: this }); } } getRange(attributeName){ return this.ranges.get(attributeName); } setRange(attributeName, newRange){ let rangeChanged = false; let oldRange = this.ranges.get(attributeName); if(oldRange != null && newRange != null){ rangeChanged = oldRange[0] !== newRange[0] || oldRange[1] !== newRange[1]; }else{ rangeChanged = true; } this.ranges.set(attributeName, newRange); if(rangeChanged){ this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get extraRange () { return this.uniforms.uExtraRange.value; } set extraRange (value) { if (!(value instanceof Array && value.length === 2)) { return; } if (value[0] === this.uniforms.uExtraRange.value[0] && value[1] === this.uniforms.uExtraRange.value[1]) { return; } this.uniforms.uExtraRange.value = value; this._defaultExtraRangeChanged = true; this.dispatchEvent({ type: 'material_property_changed', target: this }); } get weightRGB () { return this.uniforms.wRGB.value; } set weightRGB (value) { if(this.uniforms.wRGB.value !== value){ this.uniforms.wRGB.value = value; this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get weightIntensity () { return this.uniforms.wIntensity.value; } set weightIntensity (value) { if(this.uniforms.wIntensity.value !== value){ this.uniforms.wIntensity.value = value; this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get weightElevation () { return this.uniforms.wElevation.value; } set weightElevation (value) { if(this.uniforms.wElevation.value !== value){ this.uniforms.wElevation.value = value; this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get weightClassification () { return this.uniforms.wClassification.value; } set weightClassification (value) { if(this.uniforms.wClassification.value !== value){ this.uniforms.wClassification.value = value; this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get weightReturnNumber () { return this.uniforms.wReturnNumber.value; } set weightReturnNumber (value) { if(this.uniforms.wReturnNumber.value !== value){ this.uniforms.wReturnNumber.value = value; this.dispatchEvent({ type: 'material_property_changed', target: this }); } } get weightSourceID () { return this.uniforms.wSourceID.value; } set weightSourceID (value) { if(this.uniforms.wSourceID.value !== value){ this.uniforms.wSourceID.value = value; this.dispatchEvent({ type: 'material_property_changed', target: this }); } } static generateGradientTexture (gradient) { let size = 64; // create canvas let canvas = document.createElement('canvas'); canvas.width = size; canvas.height = size; // get context let context = canvas.getContext('2d'); // draw gradient context.rect(0, 0, size, size); let ctxGradient = context.createLinearGradient(0, 0, size, size); for (let i = 0; i < gradient.length; i++) { let step = gradient[i]; ctxGradient.addColorStop(step[0], '#' + step[1].getHexString()); } context.fillStyle = ctxGradient; context.fill(); //let texture = new THREE.Texture(canvas); let texture = new THREE.CanvasTexture(canvas); texture.needsUpdate = true; texture.minFilter = THREE.LinearFilter; texture.wrap = THREE.RepeatWrapping; texture.repeat = 2; // textureImage = texture.image; return texture; } static generateMatcapTexture (matcap) { var url = new URL(Potree.resourcePath + "/textures/matcap/" + matcap).href; let texture = new THREE.TextureLoader().load( url ); texture.magFilter = texture.minFilter = THREE.LinearFilter; texture.needsUpdate = true; // PotreeConverter_1.6_2018_07_29_windows_x64\PotreeConverter.exe autzen_xyzrgbXYZ_ascii.xyz -f xyzrgbXYZ -a RGB NORMAL -o autzen_xyzrgbXYZ_ascii_a -p index --overwrite // Switch matcap texture on the fly : viewer.scene.pointclouds[0].material.matcap = 'matcap1.jpg'; // For non power of 2, use LinearFilter and dont generate mipmaps, For power of 2, use NearestFilter and generate mipmaps : matcap2.jpg 1 2 8 11 12 13 return texture; } disableEvents(){ if(this._hiddenListeners === undefined){ this._hiddenListeners = this._listeners; this._listeners = {}; } }; enableEvents(){ this._listeners = this._hiddenListeners; this._hiddenListeners = undefined; }; // copyFrom(from){ // var a = 10; // for(let name of Object.keys(this.uniforms)){ // this.uniforms[name].value = from.uniforms[name].value; // } // } // copy(from){ // this.copyFrom(from); // } }