UNPKG

@giro3d/giro3d

Version:

A JS/WebGL framework for 3D geospatial data visualization

135 lines (126 loc) 6.01 kB
/* * Copyright (c) 2015-2018, IGN France. * Copyright (c) 2018-2026, Giro3D team. * SPDX-License-Identifier: MIT */ import { Float32BufferAttribute, FloatType, Int16BufferAttribute, Int32BufferAttribute, Int8BufferAttribute, IntType, MathUtils, Uint16BufferAttribute, Uint32BufferAttribute, Uint8BufferAttribute, Vector2, Vector4 } from 'three'; import PointCloudMaterial from '../../renderer/PointCloudMaterial'; import { enablePointCloudPostProcessing } from '../../renderer/RenderPipeline'; export function isPNTSScene(obj) { return obj.isPoints && 'batchTable' in obj; } /** * A plugin that applies some post-processing to point-based scenes. */ export default class PointCloudPlugin { constructor(parameters) { this._parameters = parameters; } processBufferAttribute(geometry, batchTable, sourceAttribute, targetAttribute) { const count = batchTable.count; const array = batchTable.getPropertyArray(sourceAttribute); if (array == null) { // Attribute not present in the batch table. return; } let bufferAttribute; const itemSize = array.length / count; if (array instanceof Uint8Array || array instanceof Uint8ClampedArray) { bufferAttribute = new Uint8BufferAttribute(array, itemSize, false); bufferAttribute.gpuType = IntType; } else if (array instanceof Uint16Array) { bufferAttribute = new Uint16BufferAttribute(array, itemSize, false); bufferAttribute.gpuType = IntType; } else if (array instanceof Uint32Array) { bufferAttribute = new Uint32BufferAttribute(array, itemSize, false); bufferAttribute.gpuType = IntType; } else if (array instanceof Int8Array) { bufferAttribute = new Int8BufferAttribute(array, itemSize, false); bufferAttribute.gpuType = IntType; } else if (array instanceof Int16Array) { bufferAttribute = new Int16BufferAttribute(array, itemSize, false); bufferAttribute.gpuType = IntType; } else if (array instanceof Int32Array) { bufferAttribute = new Int32BufferAttribute(array, itemSize, false); bufferAttribute.gpuType = IntType; } else if (array instanceof Float32Array) { bufferAttribute = new Float32BufferAttribute(array, itemSize, false); bufferAttribute.gpuType = FloatType; } else if (array instanceof Float64Array) { bufferAttribute = new Float32BufferAttribute(new Float32Array(array), itemSize, false); bufferAttribute.gpuType = FloatType; } else { throw new Error('invalid array type'); } geometry.setAttribute(targetAttribute, bufferAttribute); } updateMaterial(material) { material.size = this._parameters.pointSize; material.elevationColorMap = this._parameters.pointCloudColorMap; material.attributesState = { scalars: [{ colorMap: this._parameters.pointCloudColorMap }], classifications: [{ classifications: this._parameters.classifications }] }; material.mode = this._parameters.pointCloudMode; material.brightness = this._parameters.colorimetry.brightness; material.contrast = this._parameters.colorimetry.contrast; material.saturation = this._parameters.colorimetry.saturation; if (this._parameters.overlayColor != null) { material.overlayColor = new Vector4(this._parameters.overlayColor.r, this._parameters.overlayColor.g, this._parameters.overlayColor.b, 1); } else { material.overlayColor = new Vector4(0, 0, 0, 0); } material.updateUniforms(); } processTileModel(scene, tile) { if (isPNTSScene(scene)) { const batchTable = scene.batchTable; const mapping = this._parameters.attributeMapping; this.processBufferAttribute(scene.geometry, batchTable, mapping['scalar'], 'scalar'); this.processBufferAttribute(scene.geometry, batchTable, mapping['classification'], 'classification'); const material = new PointCloudMaterial({ mode: this._parameters.pointCloudMode }); scene.material = material; material.setupFromGeometry(scene.geometry); this.updateMaterial(material); // For compatibility with point-cloud post processing enablePointCloudPostProcessing(scene); if (scene.geometry.boundingBox == null) { scene.geometry.computeBoundingBox(); } // Let's make this point cloud compatible with the LayerNode // interface so that it can receive coloring from a color layer. // Normally, we would be using a PointCloud mesh type, but we cannot // change the type of the mesh here since the plugin cannot return // any value, so we will be patching the default Points mesh instead. // @ts-expect-error scene is not a LayerNode const layerNode = scene; layerNode.lod = tile.internal.depth; // Let's compute a texture size from the point density. // We asssume that the point density is homogenous and that the shape // is rougly cube-shaped so that we can assign the same size in both dimensions. const pointCount = scene.geometry.getAttribute('position').count; const pixels = MathUtils.clamp(Math.round(Math.sqrt(pointCount)), 32, 512); layerNode.textureSize = new Vector2(pixels, pixels); // This optimization mechanism does not apply to point clouds layerNode.canProcessColorLayer = () => true; // We will handle manually the disposal of nodes, without // letting the layer listening to the dispose event. layerNode.disposed = false; // The local bounding box that will be later used to compute the world space // bounding box and eventually the extent provided by getExtent(). layerNode.userData.boundingBox = scene.geometry.boundingBox; layerNode.getExtent = () => { // Note that this extent must be computed once the object // has been added to the hierarchy (since we need the world matrix), // so we cannot do that here. return layerNode.userData.extent; }; } } }