@giro3d/giro3d
Version:
A JS/WebGL framework for 3D geospatial data visualization
135 lines (126 loc) • 6.01 kB
JavaScript
/*
* 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;
};
}
}
}