@giro3d/giro3d
Version:
A JS/WebGL framework for 3D geospatial data visualization
462 lines (442 loc) • 24.8 kB
JavaScript
/*
* Copyright (c) 2015-2018, IGN France.
* Copyright (c) 2018-2026, Giro3D team.
* SPDX-License-Identifier: MIT
*/
import { Color, GLSL3, Matrix4, NoBlending, NormalBlending, ShaderMaterial, Uniform, Vector2, Vector3, Vector4 } from 'three';
import OffsetScale from '../core/OffsetScale';
import MaterialUtils from './MaterialUtils';
import { ASPRS_CLASSIFICATIONS, Classification } from './pointcloudmaterial/Classification';
import { buildColorMapUniform, createDefaultColorMap } from './pointcloudmaterial/ColorMapUniform';
import { ClassificationSlot } from './pointcloudmaterial/slots/ClassificationSlot';
import { ColorSlot } from './pointcloudmaterial/slots/ColorSlot';
import { ScalarSlot } from './pointcloudmaterial/slots/ScalarSlot';
/* babel-plugin-inline-import './shader/PointsFS.glsl' */
const PointsFS = "#include <giro3d_precision_qualifiers>\n#include <giro3d_fragment_shader_header>\n#include <giro3d_common>\n\n#include <common>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\n#include <fog_pars_fragment>\n\nvarying vec4 vColor;\nuniform vec3 brightnessContrastSaturation;\n\nconst float HALF_LENGTH = 0.5;\nconst vec2 POINT_CENTER = vec2(HALF_LENGTH, HALF_LENGTH);\nconst float HALF_LENGTH_SQUARED = HALF_LENGTH * HALF_LENGTH;\n\nfloat sqLength(in vec2 v) {\n return v.x * v.x + v.y * v.y;\n}\n\nvoid main() {\n if (vColor.a < 0.001) {\n discard;\n return;\n }\n\n // circular point rendering\n if (sqLength(gl_PointCoord - POINT_CENTER) > HALF_LENGTH_SQUARED){\n discard;\n return;\n }\n\n #include <clipping_planes_fragment>\n\n gl_FragColor = vec4(adjustBrightnessContrastSaturation(vColor.rgb, brightnessContrastSaturation), vColor.a);\n\n #include <colorspace_fragment>\n #include <fog_fragment>\n #include <logdepthbuf_fragment>\n}\n";
/* babel-plugin-inline-import './shader/PointsVS.glsl' */
const PointsVS = "#include <giro3d_precision_qualifiers>\n#include <giro3d_common>\n#include <giro3d_intersecting_volume_pars>\n\n#include <common>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\n#include <fog_pars_vertex>\n\n#define EPSILON 1e-6\n\nuniform float size;\n\nuniform uint pickingId;\nuniform int mode;\nuniform float opacity;\nuniform vec4 overlayColor;\n\nstruct PointCloudColorMap {\n float min;\n float max;\n sampler2D lut;\n};\n\nuniform PointCloudColorMap elevationColorMap;\n\nstruct ColorProperties {\n float weight;\n};\nuniform ColorProperties colorProperties[3];\nattribute vec3 color;\n#if defined(COLOR_1)\nattribute vec3 color_1;\n#endif\n#if defined(COLOR_2)\nattribute vec3 color_2;\n#endif\n\nstruct ScalarProperties {\n float weight;\n PointCloudColorMap colorMap;\n};\nuniform ScalarProperties scalarProperties[3];\n#if defined(SCALAR_0)\nattribute SCALAR_0_TYPE scalar;\n#endif\n#if defined(SCALAR_1)\nattribute SCALAR_1_TYPE scalar_1;\n#endif\n#if defined(SCALAR_2)\nattribute SCALAR_2_TYPE scalar_2;\n#endif\n\nstruct ClassificationProperties {\n float weight;\n sampler2D lut;\n};\nuniform ClassificationProperties classificationProperties[3];\n#if defined(CLASSIFICATION_0)\nattribute uint classification;\n#endif\n#if defined(CLASSIFICATION_1)\nattribute uint classification_1;\n#endif\n#if defined(CLASSIFICATION_2)\nattribute uint classification_2;\n#endif\n\n#if defined(NORMAL_OCT16)\nattribute vec2 oct16Normal;\n#elif defined(NORMAL_SPHEREMAPPED)\nattribute vec2 sphereMappedNormal;\n#endif\n\nuniform sampler2D overlayTexture;\nuniform int decimation;\nuniform float hasOverlayTexture;\nuniform vec4 offsetScale;\nuniform vec2 extentBottomLeft;\nuniform vec2 extentSize;\n\nvarying vec4 vColor;\n\n// see https://web.archive.org/web/20150303053317/http://lgdv.cs.fau.de/get/1602\n// and implementation in PotreeConverter (BINPointReader.cpp) and potree (BinaryDecoderWorker.js)\n#if defined(NORMAL_OCT16)\nvec3 decodeOct16Normal(vec2 encodedNormal) {\n vec2 nNorm = 2. * (encodedNormal / 255.) - 1.;\n vec3 n;\n n.z = 1. - abs(nNorm.x) - abs(nNorm.y);\n if (n.z >= 0.) {\n n.x = nNorm.x;\n n.y = nNorm.y;\n } else {\n n.x = sign(nNorm.x) - sign(nNorm.x) * sign(nNorm.y) * nNorm.y;\n n.y = sign(nNorm.y) - sign(nNorm.y) * sign(nNorm.x) * nNorm.x;\n }\n return normalize(n);\n}\n#elif defined(NORMAL_SPHEREMAPPED)\n// see http://aras-p.info/texts/CompactNormalStorage.html method #4\n// or see potree's implementation in BINPointReader.cpp\nvec3 decodeSphereMappedNormal(vec2 encodedNormal) {\n vec2 fenc = 2. * encodedNormal / 255. - 1.;\n float f = dot(fenc,fenc);\n float g = 2. * sqrt(1. - f);\n vec3 n;\n n.xy = fenc * g;\n n.z = 1. - 2. * f;\n return n;\n}\n#endif\n\n#ifdef DEFORMATION_SUPPORT\nuniform int enableDeformations;\nstruct Deformation {\n mat4 transformation;\n vec3 vec;\n vec2 origin;\n vec2 influence;\n vec4 colors;\n};\n\nuniform Deformation deformations[NUM_TRANSFO];\n#endif\n\nvoid discardPoint() {\n // Move the vertex out of the render area to prevent calling the fragment shader for\n // this point, saving a lot of GPU processing time when millions of points are displayed.\n gl_PointSize = 0.0;\n gl_Position = vec4(-9999.0, -9999.0, -9999.0, 0.0);\n}\n\nvec4 computeColorFromScalar(const float value, const PointCloudColorMap colorMap) {\n return sampleColorMap(value, colorMap.min, colorMap.max, colorMap.lut, 0.0);\n}\n\nvoid addScalarContribution(const float value, const ScalarProperties properties, inout vec4 color) {\n if (properties.weight > 0.0) {\n color += computeColorFromScalar(value, properties.colorMap) * properties.weight;\n }\n}\n\nvoid addClassificationContribution(const uint classification, const ClassificationProperties properties, inout vec4 color) {\n if (properties.weight > 0.0) {\n color += texelFetch(properties.lut, ivec2(classification, 0), 0) * properties.weight;\n }\n}\n\nvoid addColorContribution(const vec3 value, const ColorProperties properties, inout vec4 color) {\n if (properties.weight > 0.0) {\n // We need to convert to linear color space because the colors are in sRGB and they\n // are not automatically converted to sRGB-linear. This is due to the fact that those\n // colors come from a vertex buffer and not from a texture (automatically converted)\n // or a single color uniform (also automatically converted).\n vec4 linear = sRGBToLinear(vec4(value, 1.0));\n color += vec4(mix(linear.rgb, overlayColor.rgb, overlayColor.a), 1) * properties.weight;\n }\n}\n\nvoid main() {\n if (decimation > 1 && gl_VertexID % decimation != 0) {\n discardPoint();\n return;\n }\n\n#if defined(NORMAL_OCT16)\n vec3 normal = decodeOct16Normal(oct16Normal);\n#elif defined(NORMAL_SPHEREMAPPED)\n vec3 normal = decodeSphereMappedNormal(sphereMappedNormal);\n#elif defined(NORMAL)\n // nothing to do\n#else\n // default to color\n vec3 normal = color;\n#endif\n\n if (mode == MODE_NORMAL) {\n vColor = vec4(abs(normal), 1);\n } else if (mode == MODE_TEXTURE) {\n vec2 pp = (modelMatrix * vec4(position, 1.0)).xy;\n // offsetScale is from bottomleft\n pp.x -= extentBottomLeft.x;\n pp.y -= extentBottomLeft.y;\n pp *= offsetScale.zw / extentSize;\n pp += offsetScale.xy;\n vec3 textureColor = texture2D(overlayTexture, pp).rgb;\n vColor = vec4(mix(textureColor, overlayColor.rgb, overlayColor.a), hasOverlayTexture);\n } else if (mode == MODE_ELEVATION) {\n float z = (modelMatrix * vec4(position, 1.0)).z;\n vColor = computeColorFromScalar(z, elevationColorMap);\n } else {\n vColor = vec4(0);\n \n #if defined(SCALAR_0)\n addScalarContribution(float(scalar), scalarProperties[0], vColor);\n #endif\n #if defined(SCALAR_1)\n addScalarContribution(float(scalar_1), scalarProperties[1], vColor);\n #endif\n #if defined(SCALAR_2)\n addScalarContribution(float(scalar_2), scalarProperties[2], vColor);\n #endif\n\n #if defined(CLASSIFICATION_0)\n addClassificationContribution(classification, classificationProperties[0], vColor);\n #endif\n #if defined(CLASSIFICATION_1)\n addClassificationContribution(classification_1, classificationProperties[1], vColor);\n #endif\n #if defined(CLASSIFICATION_2)\n addClassificationContribution(classification_2, classificationProperties[2], vColor);\n #endif\n\n addColorContribution(color, colorProperties[0], vColor);\n #if defined(COLOR_1)\n addColorContribution(color_1, colorProperties[1], vColor);\n #endif\n #if defined(COLOR_2)\n addColorContribution(color_2, colorProperties[2], vColor);\n #endif\n }\n\n vColor.a *= opacity;\n \n if (pickingId > 0u) {\n if (vColor.a <= EPSILON) {\n discardPoint();\n return;\n }\n\n // In picking mode, we simply output the point id in the red channel and the object id in the green channel.\n // No need to encode them because we are rendering to a float texture.\n vColor = vec4(float(gl_VertexID), float(pickingId), 0, 1);\n }\n\n mat4 mvMatrix = modelViewMatrix;\n\n #ifdef DEFORMATION_SUPPORT\n if (!pickingMode) {\n vColor = enableDeformations > 0 ?\n vec4(0.0, 1.0, 1.0, 1.0):\n vec4(1.0, 0.0, 1.0, 1.0);\n }\n if (enableDeformations > 0) {\n vec4 mPosition = modelMatrix * vec4(position, 1.0);\n float minDistance = 1000.0;\n int bestChoice = -1;\n for (int i = 0; i < NUM_TRANSFO; i++) {\n if (i >= enableDeformations) {\n break;\n }\n vec2 v = deformations[i].vec.xy;\n float length = deformations[i].vec.z;\n float depassement_x =\n length * (deformations[i].influence.x - 1.0);\n\n vec2 diff = mPosition.xy - origin[i];\n float distance_x = dot(diff, v);\n\n if (-depassement_x <= distance_x &&\n distance_x <= (length + depassement_x)) {\n vec2 normal = vec2(-v.y, v.x);\n float d = abs(dot(diff, normal));\n if (d < minDistance && d <= deformations[i].influence.y) {\n minDistance = d;\n bestChoice = i;\n }\n }\n }\n\n if (bestChoice >= 0) {\n // override modelViewMatrix\n mvMatrix = deformations[bestChoice].transformation;\n vColor = mix(\n deformations[bestChoice].color,\n vec4(color, 1.0),\n 0.5);\n }\n }\n #endif\n\n #include <begin_vertex>\n #include <project_vertex>\n\n if (size > 0.) {\n gl_PointSize = size;\n } else {\n gl_PointSize = clamp(-size / gl_Position.w, 3.0, 10.0);\n }\n\n #ifdef INTERSECTING_VOLUMES_SUPPORT\n // don't break picking mode\n if (pickingId == 0u) {\n vec4 viewPosition = mvMatrix * vec4(position, 1);\n viewPosition.xyz / viewPosition.w;\n applyIntersectingVolumes(viewPosition, vColor);\n }\n #endif\n\n #include <fog_vertex>\n #include <logdepthbuf_vertex>\n #include <clipping_planes_vertex>\n}\n";
export { ASPRS_CLASSIFICATIONS, Classification };
const tmpDims = new Vector2();
/**
* Specifies the way points are colored.
*/
export let MODE = /*#__PURE__*/function (MODE) {
/** The points are colored using their own color */
MODE[MODE["COLOR"] = 0] = "COLOR";
/** The points are colored using one of their attributes */
MODE[MODE["SCALAR"] = 1] = "SCALAR";
/** The points are colored using their classification */
MODE[MODE["CLASSIFICATION"] = 2] = "CLASSIFICATION";
/** The points are colored using their normal */
MODE[MODE["NORMAL"] = 3] = "NORMAL";
/** The points are colored using an external texture, such as a color layer */
MODE[MODE["TEXTURE"] = 4] = "TEXTURE";
/** The points are colored using their elevation */
MODE[MODE["ELEVATION"] = 5] = "ELEVATION";
/** The points are colored using a mix of their attributes */
MODE[MODE["ATTRIBUTES"] = 6] = "ATTRIBUTES";
return MODE;
}({});
const NUM_TRANSFO = 16;
/**
* Material used for point clouds.
*/
class PointCloudMaterial extends ShaderMaterial {
// This is an arbitrary limit, only there to prevent running out of uniform slots.
static maxIntersectingVolumesCount = 8;
isPointCloudMaterial = true;
disposed = false;
intersectingVolumes = [];
_elevationColorMap = createDefaultColorMap();
/**
* @internal
*/
/**
* @internal
*/
/**
* Gets or sets the point size.
*/
get size() {
return this.uniforms.size.value;
}
set size(value) {
this.uniforms.size.value = value;
}
/**
* Gets or sets the point decimation value.
* A decimation value of N means that we take every Nth point and discard the rest.
*/
get decimation() {
return this.uniforms.decimation.value;
}
set decimation(value) {
this.uniforms.decimation.value = value;
}
/**
* Gets or sets the display mode (color, classification...)
*/
get mode() {
return this.uniforms.mode.value;
}
set mode(mode) {
if (mode === MODE.COLOR || mode === MODE.CLASSIFICATION || mode === MODE.SCALAR) {
this.attributesState = {
colors: [{
weight: mode === MODE.COLOR ? 1 : 0
}, {
weight: 0
}, {
weight: 0
}],
scalars: [{
weight: mode === MODE.SCALAR ? 1 : 0
}, {
weight: 0
}, {
weight: 0
}],
classifications: [{
weight: mode === MODE.CLASSIFICATION ? 1 : 0
}, {
weight: 0
}, {
weight: 0
}]
};
}
this.uniforms.mode.value = mode;
}
/**
* Update material uniforms related to scalar and classification attributes.
*/
setupFromGeometry(geometry) {
for (const slot of this._classificationSlots) {
slot.hasAttribute = geometry.hasAttribute(slot.attributeName);
}
for (const slot of this._scalarSlots) {
slot.hasAttribute = geometry.hasAttribute(slot.attributeName);
if (slot.hasAttribute) {
slot.attributeType = MaterialUtils.getVertexAttributeType(geometry.getAttribute(slot.attributeName));
}
}
for (let i = 1; i < this._colorSlots.length; i++) {
const slot = this._colorSlots[i];
slot.hasAttribute = geometry.hasAttribute(slot.attributeName);
}
}
/**
* @internal
*/
get pickingId() {
return this.uniforms.pickingId.value;
}
/**
* @internal
*/
set pickingId(id) {
this.uniforms.pickingId.value = id;
}
/**
* Gets or sets the overlay color (default color).
*/
get overlayColor() {
return this.uniforms.overlayColor.value;
}
set overlayColor(color) {
this.uniforms.overlayColor.value = color;
}
/**
* Gets or sets the brightness of the points.
*/
get brightness() {
return this.uniforms.brightnessContrastSaturation.value.x;
}
set brightness(v) {
this.uniforms.brightnessContrastSaturation.value.setX(v);
}
/**
* Gets or sets the contrast of the points.
*/
get contrast() {
return this.uniforms.brightnessContrastSaturation.value.y;
}
set contrast(v) {
this.uniforms.brightnessContrastSaturation.value.setY(v);
}
/**
* Gets or sets the saturation of the points.
*/
get saturation() {
return this.uniforms.brightnessContrastSaturation.value.z;
}
set saturation(v) {
this.uniforms.brightnessContrastSaturation.value.setZ(v);
}
get elevationColorMap() {
return this._elevationColorMap;
}
set elevationColorMap(colorMap) {
this._elevationColorMap = colorMap;
}
/**
* Creates a PointsMaterial using the specified options.
*
* @param options - The options.
*/
constructor(options = {}) {
super({
clipping: true,
glslVersion: GLSL3
});
this.vertexShader = PointsVS;
this.fragmentShader = PointsFS;
// Default
this.defines = {
SCALAR_0_TYPE: 'uint',
SCALAR_1_TYPE: 'uint',
SCALAR_2_TYPE: 'uint',
MAX_INTERSECTING_VOLUMES_COUNT: PointCloudMaterial.maxIntersectingVolumesCount
};
for (const key of Object.keys(MODE)) {
if (Object.prototype.hasOwnProperty.call(MODE, key)) {
// @ts-expect-error a weird pattern indeed
this.defines[`MODE_${key}`] = MODE[key];
}
}
this.fog = true;
this.colorLayer = null;
this.needsUpdate = true;
this._colorSlots = [new ColorSlot(this, 0), new ColorSlot(this, 1), new ColorSlot(this, 2)];
this._scalarSlots = [new ScalarSlot(this, 0), new ScalarSlot(this, 1), new ScalarSlot(this, 2)];
this._classificationSlots = [new ClassificationSlot(this, 0), new ClassificationSlot(this, 1), new ClassificationSlot(this, 2)];
this.uniforms = {
fogDensity: new Uniform(0.00025),
fogNear: new Uniform(1),
fogFar: new Uniform(2000),
decimation: new Uniform(1),
fogColor: new Uniform(new Color(0xffffff)),
colorProperties: new Uniform(this._colorSlots.map(slot => slot.uniform)),
scalarProperties: new Uniform(this._scalarSlots.map(slot => slot.uniform)),
classificationProperties: new Uniform(this._classificationSlots.map(slot => slot.uniform)),
// Texture-related uniforms
extentBottomLeft: new Uniform(new Vector2(0, 0)),
extentSize: new Uniform(new Vector2(0, 0)),
overlayTexture: new Uniform(null),
hasOverlayTexture: new Uniform(0),
offsetScale: new Uniform(new OffsetScale(0, 0, 1, 1)),
elevationColorMap: new Uniform(buildColorMapUniform(this.elevationColorMap)),
size: new Uniform(options.size ?? 0),
mode: new Uniform(options.mode ?? MODE.COLOR),
pickingId: new Uniform(0),
opacity: new Uniform(this.opacity),
overlayColor: new Uniform(options.overlayColor ?? new Vector4(0, 0, 0, 0)),
brightnessContrastSaturation: new Uniform(new Vector3(0, 1, 1)),
enableDeformations: new Uniform(false),
deformations: new Uniform([]),
intersectingVolumes: new Uniform({
count: 0,
volumes: []
})
};
for (let i = 0; i < NUM_TRANSFO; i++) {
this.uniforms.deformations.value.push({
transformation: new Matrix4(),
vec: new Vector3(),
origin: new Vector2(),
influence: new Vector2(),
color: new Color()
});
}
for (let i = 0; i < PointCloudMaterial.maxIntersectingVolumesCount; i++) {
this.uniforms.intersectingVolumes.value.volumes.push({
viewToBoxNc: new Matrix4(),
color: new Color()
});
}
}
dispose() {
if (this.disposed) {
return;
}
for (const slot of this._classificationSlots) {
slot.dispose();
}
this.dispatchEvent({
type: 'dispose'
});
this.disposed = true;
}
/**
* Internally used for picking.
* @internal
*/
enablePicking(picking) {
this.pickingId = picking;
this.blending = picking ? NoBlending : NormalBlending;
}
hasColorLayer(layer) {
return this.colorLayer === layer;
}
updateUniforms() {
this.uniforms.opacity.value = this.opacity;
this.uniforms.elevationColorMap.value = buildColorMapUniform(this.elevationColorMap);
for (const slot of this._scalarSlots) {
slot.update();
}
}
/** @internal */
onBeforeRender(_renderer, _scene, camera) {
this.uniforms.opacity.value = this.opacity;
this.transparent = this.opacity < 1 || this.elevationColorMap.opacity != null;
this.updateAttributesWeights();
this.updateIntersectingVolumes(camera);
for (const slot of this._classificationSlots) {
slot.update();
}
}
/** @internal */
copy(source) {
super.copy(source);
this.needsUpdate = true;
this.size = source.size;
this.mode = source.mode;
this.overlayColor.copy(source.overlayColor);
this.attributesState = source.attributesState;
this.brightness = source.brightness;
this.contrast = source.contrast;
this.saturation = source.saturation;
this.elevationColorMap = source.elevationColorMap;
this.decimation = source.decimation;
this.updateUniforms();
return this;
}
removeColorLayer() {
this.mode = MODE.COLOR;
this.colorLayer = null;
this.uniforms.overlayTexture.value = null;
this.needsUpdate = true;
this.uniforms.hasOverlayTexture.value = 0;
}
pushColorLayer(layer, extent) {
this.mode = MODE.TEXTURE;
this.colorLayer = layer;
this.uniforms.extentBottomLeft.value.set(extent.minX, extent.minY);
const dim = extent.dimensions(tmpDims);
this.uniforms.extentSize.value.copy(dim);
this.needsUpdate = true;
}
indexOfColorLayer(layer) {
if (layer === this.colorLayer) {
return 0;
}
return -1;
}
getColorTexture(layer) {
if (layer !== this.colorLayer) {
return null;
}
return this.uniforms.overlayTexture?.value;
}
setColorTextures(layer, textureAndPitch) {
const {
texture
} = textureAndPitch;
this.uniforms.overlayTexture.value = texture;
this.uniforms.hasOverlayTexture.value = 1;
}
setLayerVisibility() {
// no-op
}
setLayerOpacity() {
// no-op
}
setLayerElevationRange() {
// no-op
}
setColorimetry() {
// Not implemented because the points have their own BCS controls
}
get attributesState() {
return {
colors: [this._colorSlots[0].state, this._colorSlots[1].state, this._colorSlots[2].state],
scalars: [this._scalarSlots[0].state, this._scalarSlots[1].state, this._scalarSlots[2].state],
classifications: [this._classificationSlots[0].state, this._classificationSlots[1].state, this._classificationSlots[2].state]
};
}
set attributesState(state) {
if (typeof state.colors !== 'undefined') {
const colors = state.colors;
this._colorSlots.forEach((slot, index) => {
if (typeof colors[index] !== 'undefined') {
slot.state = colors[index];
}
});
}
if (typeof state.scalars !== 'undefined') {
const scalars = state.scalars;
this._scalarSlots.forEach((slot, index) => {
if (typeof scalars[index] !== 'undefined') {
slot.state = scalars[index];
}
});
}
if (typeof state.classifications !== 'undefined') {
const classifications = state.classifications;
this._classificationSlots.forEach((slot, index) => {
if (typeof classifications[index] !== 'undefined') {
slot.state = classifications[index];
}
});
}
}
/**
* Unused for now.
* @internal
*/
enableTransfo(v) {
if (v) {
this.defines.DEFORMATION_SUPPORT = 1;
this.defines.NUM_TRANSFO = NUM_TRANSFO;
} else {
delete this.defines.DEFORMATION_SUPPORT;
delete this.defines.NUM_TRANSFO;
}
this.needsUpdate = true;
}
updateIntersectingVolumes(camera) {
const hasIntersectingVolumes = this.intersectingVolumes.length > 0;
MaterialUtils.setDefine(this, 'INTERSECTING_VOLUMES_SUPPORT', hasIntersectingVolumes);
if (hasIntersectingVolumes) {
if (this.intersectingVolumes.length > PointCloudMaterial.maxIntersectingVolumesCount) {
throw new Error(`Too many intersecting volumes (${this.intersectingVolumes.length}, max is ${PointCloudMaterial.maxIntersectingVolumesCount}).`);
}
const invViewMatrix = camera.matrixWorld;
for (let i = 0; i < this.intersectingVolumes.length; i++) {
const volumeUniform = this.uniforms.intersectingVolumes.value.volumes[i];
const volumeDefinition = this.intersectingVolumes[i];
volumeUniform.viewToBoxNc.multiplyMatrices(volumeDefinition.worldToBoxNdc, invViewMatrix);
volumeUniform.color.copy(volumeDefinition.color);
}
this.uniforms.intersectingVolumes.value.count = this.intersectingVolumes.length;
}
}
updateAttributesWeights() {
const allSlots = [...this._scalarSlots, ...this._classificationSlots, ...this._colorSlots];
let totalWeight = 0;
for (const slot of allSlots) {
totalWeight += slot.actualWeight;
}
if (totalWeight > 0) {
// normalize attributes
for (const slot of allSlots) {
slot.actualWeight /= totalWeight;
}
} else {
// default to color
this._colorSlots[0].weight = 1;
}
}
static isPointCloudMaterial = obj => obj?.isPointCloudMaterial;
}
export default PointCloudMaterial;