import arraySlice from "../Core/arraySlice.js"; import BoundingSphere from "../Core/BoundingSphere.js"; import Cartesian3 from "../Core/Cartesian3.js"; import Cartesian4 from "../Core/Cartesian4.js"; import Check from "../Core/Check.js"; import clone from "../Core/clone.js"; import Color from "../Core/Color.js"; import combine from "../Core/combine.js"; import ComponentDatatype from "../Core/ComponentDatatype.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import destroyObject from "../Core/destroyObject.js"; import getJsonFromTypedArray from "../Core/getJsonFromTypedArray.js"; import CesiumMath from "../Core/Math.js"; import Matrix4 from "../Core/Matrix4.js"; import oneTimeWarning from "../Core/oneTimeWarning.js"; import OrthographicFrustum from "../Core/OrthographicFrustum.js"; import PrimitiveType from "../Core/PrimitiveType.js"; import RuntimeError from "../Core/RuntimeError.js"; import Transforms from "../Core/Transforms.js"; import Buffer from "../Renderer/Buffer.js"; import BufferUsage from "../Renderer/BufferUsage.js"; import DrawCommand from "../Renderer/DrawCommand.js"; import Pass from "../Renderer/Pass.js"; import RenderState from "../Renderer/RenderState.js"; import ShaderProgram from "../Renderer/ShaderProgram.js"; import VertexArray from "../Renderer/VertexArray.js"; import when from "../ThirdParty/when.js"; import BlendingState from "./BlendingState.js"; import Cesium3DTileBatchTable from "./Cesium3DTileBatchTable.js"; import Cesium3DTileFeatureTable from "./Cesium3DTileFeatureTable.js"; import DracoLoader from "./DracoLoader.js"; import getClipAndStyleCode from "./getClipAndStyleCode.js"; import getClippingFunction from "./getClippingFunction.js"; import SceneMode from "./SceneMode.js"; import ShadowMode from "./ShadowMode.js"; import StencilConstants from "./StencilConstants.js"; var DecodingState = { NEEDS_DECODE: 0, DECODING: 1, READY: 2, FAILED: 3, }; /** * Represents the contents of a * {@link https://github.com/CesiumGS/3d-tiles/tree/master/specification/TileFormats/PointCloud|Point Cloud} * tile. Used internally by {@link PointCloud3DTileContent} and {@link TimeDynamicPointCloud}. * * @alias PointCloud * @constructor * * @see PointCloud3DTileContent * @see TimeDynamicPointCloud * * @private */ function PointCloud(options) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object("options", options); Check.typeOf.object("options.arrayBuffer", options.arrayBuffer); //>>includeEnd('debug'); // Hold onto the payload until the render resources are created this._parsedContent = undefined; this._drawCommand = undefined; this._isTranslucent = false; this._styleTranslucent = false; this._constantColor = Color.clone(Color.DARKGRAY); this._highlightColor = Color.clone(Color.WHITE); this._pointSize = 1.0; this._rtcCenter = undefined; this._quantizedVolumeScale = undefined; this._quantizedVolumeOffset = undefined; // These values are used to regenerate the shader when the style changes this._styleableShaderAttributes = undefined; this._isQuantized = false; this._isOctEncoded16P = false; this._isRGB565 = false; this._hasColors = false; this._hasNormals = false; this._hasBatchIds = false; // Draco this._decodingState = DecodingState.READY; this._dequantizeInShader = true; this._isQuantizedDraco = false; this._isOctEncodedDraco = false; this._quantizedRange = 0.0; this._octEncodedRange = 0.0; // Use per-point normals to hide back-facing points. this.backFaceCulling = false; this._backFaceCulling = false; // Whether to enable normal shading this.normalShading = true; this._normalShading = true; this._opaqueRenderState = undefined; this._translucentRenderState = undefined; this._mode = undefined; this._ready = false; this._readyPromise = when.defer(); this._pointsLength = 0; this._geometryByteLength = 0; this._vertexShaderLoaded = options.vertexShaderLoaded; this._fragmentShaderLoaded = options.fragmentShaderLoaded; this._uniformMapLoaded = options.uniformMapLoaded; this._batchTableLoaded = options.batchTableLoaded; this._pickIdLoaded = options.pickIdLoaded; this._opaquePass = defaultValue(options.opaquePass, Pass.OPAQUE); this._cull = defaultValue(options.cull, true); this.style = undefined; this._style = undefined; this.styleDirty = false; this.modelMatrix = Matrix4.clone(Matrix4.IDENTITY); this._modelMatrix = Matrix4.clone(Matrix4.IDENTITY); this.time = 0.0; // For styling this.shadows = ShadowMode.ENABLED; this._boundingSphere = undefined; this.clippingPlanes = undefined; this.isClipped = false; this.clippingPlanesDirty = false; // If defined, use this matrix to position the clipping planes instead of the modelMatrix. // This is so that when point clouds are part of a tileset they all get clipped relative // to the root tile. this.clippingPlanesOriginMatrix = undefined; this.attenuation = false; this._attenuation = false; // Options for geometric error based attenuation this.geometricError = 0.0; this.geometricErrorScale = 1.0; this.maximumAttenuation = this._pointSize; initialize(this, options); } Object.defineProperties(PointCloud.prototype, { pointsLength: { get: function () { return this._pointsLength; }, }, geometryByteLength: { get: function () { return this._geometryByteLength; }, }, ready: { get: function () { return this._ready; }, }, readyPromise: { get: function () { return this._readyPromise.promise; }, }, color: { get: function () { return Color.clone(this._highlightColor); }, set: function (value) { this._highlightColor = Color.clone(value, this._highlightColor); }, }, boundingSphere: { get: function () { if (defined(this._drawCommand)) { return this._drawCommand.boundingVolume; } return undefined; }, set: function (value) { this._boundingSphere = BoundingSphere.clone(value, this._boundingSphere); }, }, }); var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; function initialize(pointCloud, options) { var arrayBuffer = options.arrayBuffer; var byteOffset = defaultValue(options.byteOffset, 0); var uint8Array = new Uint8Array(arrayBuffer); var view = new DataView(arrayBuffer); byteOffset += sizeOfUint32; // Skip magic var version = view.getUint32(byteOffset, true); if (version !== 1) { throw new RuntimeError( "Only Point Cloud tile version 1 is supported. Version " + version + " is not." ); } byteOffset += sizeOfUint32; // Skip byteLength byteOffset += sizeOfUint32; var featureTableJsonByteLength = view.getUint32(byteOffset, true); if (featureTableJsonByteLength === 0) { throw new RuntimeError( "Feature table must have a byte length greater than zero" ); } byteOffset += sizeOfUint32; var featureTableBinaryByteLength = view.getUint32(byteOffset, true); byteOffset += sizeOfUint32; var batchTableJsonByteLength = view.getUint32(byteOffset, true); byteOffset += sizeOfUint32; var batchTableBinaryByteLength = view.getUint32(byteOffset, true); byteOffset += sizeOfUint32; var featureTableJson = getJsonFromTypedArray( uint8Array, byteOffset, featureTableJsonByteLength ); byteOffset += featureTableJsonByteLength; var featureTableBinary = new Uint8Array( arrayBuffer, byteOffset, featureTableBinaryByteLength ); byteOffset += featureTableBinaryByteLength; // Get the batch table JSON and binary var batchTableJson; var batchTableBinary; if (batchTableJsonByteLength > 0) { // Has a batch table JSON batchTableJson = getJsonFromTypedArray( uint8Array, byteOffset, batchTableJsonByteLength ); byteOffset += batchTableJsonByteLength; if (batchTableBinaryByteLength > 0) { // Has a batch table binary batchTableBinary = new Uint8Array( arrayBuffer, byteOffset, batchTableBinaryByteLength ); byteOffset += batchTableBinaryByteLength; } } var featureTable = new Cesium3DTileFeatureTable( featureTableJson, featureTableBinary ); var pointsLength = featureTable.getGlobalProperty("POINTS_LENGTH"); featureTable.featuresLength = pointsLength; if (!defined(pointsLength)) { throw new RuntimeError( "Feature table global property: POINTS_LENGTH must be defined" ); } var rtcCenter = featureTable.getGlobalProperty( "RTC_CENTER", ComponentDatatype.FLOAT, 3 ); if (defined(rtcCenter)) { pointCloud._rtcCenter = Cartesian3.unpack(rtcCenter); } var positions; var colors; var normals; var batchIds; var hasPositions = false; var hasColors = false; var hasNormals = false; var hasBatchIds = false; var isQuantized = false; var isTranslucent = false; var isRGB565 = false; var isOctEncoded16P = false; var dracoBuffer; var dracoFeatureTableProperties; var dracoBatchTableProperties; var featureTableDraco = defined(featureTableJson.extensions) ? featureTableJson.extensions["3DTILES_draco_point_compression"] : undefined; var batchTableDraco = defined(batchTableJson) && defined(batchTableJson.extensions) ? batchTableJson.extensions["3DTILES_draco_point_compression"] : undefined; if (defined(batchTableDraco)) { dracoBatchTableProperties = batchTableDraco.properties; } if (defined(featureTableDraco)) { dracoFeatureTableProperties = featureTableDraco.properties; var dracoByteOffset = featureTableDraco.byteOffset; var dracoByteLength = featureTableDraco.byteLength; if ( !defined(dracoFeatureTableProperties) || !defined(dracoByteOffset) || !defined(dracoByteLength) ) { throw new RuntimeError( "Draco properties, byteOffset, and byteLength must be defined" ); } dracoBuffer = arraySlice( featureTableBinary, dracoByteOffset, dracoByteOffset + dracoByteLength ); hasPositions = defined(dracoFeatureTableProperties.POSITION); hasColors = defined(dracoFeatureTableProperties.RGB) || defined(dracoFeatureTableProperties.RGBA); hasNormals = defined(dracoFeatureTableProperties.NORMAL); hasBatchIds = defined(dracoFeatureTableProperties.BATCH_ID); isTranslucent = defined(dracoFeatureTableProperties.RGBA); pointCloud._decodingState = DecodingState.NEEDS_DECODE; } var draco; if (defined(dracoBuffer)) { draco = { buffer: dracoBuffer, featureTableProperties: dracoFeatureTableProperties, batchTableProperties: dracoBatchTableProperties, properties: combine( dracoFeatureTableProperties, dracoBatchTableProperties ), dequantizeInShader: pointCloud._dequantizeInShader, }; } if (!hasPositions) { if (defined(featureTableJson.POSITION)) { positions = featureTable.getPropertyArray( "POSITION", ComponentDatatype.FLOAT, 3 ); hasPositions = true; } else if (defined(featureTableJson.POSITION_QUANTIZED)) { positions = featureTable.getPropertyArray( "POSITION_QUANTIZED", ComponentDatatype.UNSIGNED_SHORT, 3 ); isQuantized = true; hasPositions = true; var quantizedVolumeScale = featureTable.getGlobalProperty( "QUANTIZED_VOLUME_SCALE", ComponentDatatype.FLOAT, 3 ); if (!defined(quantizedVolumeScale)) { throw new RuntimeError( "Global property: QUANTIZED_VOLUME_SCALE must be defined for quantized positions." ); } pointCloud._quantizedVolumeScale = Cartesian3.unpack( quantizedVolumeScale ); pointCloud._quantizedRange = (1 << 16) - 1; var quantizedVolumeOffset = featureTable.getGlobalProperty( "QUANTIZED_VOLUME_OFFSET", ComponentDatatype.FLOAT, 3 ); if (!defined(quantizedVolumeOffset)) { throw new RuntimeError( "Global property: QUANTIZED_VOLUME_OFFSET must be defined for quantized positions." ); } pointCloud._quantizedVolumeOffset = Cartesian3.unpack( quantizedVolumeOffset ); } } if (!hasColors) { if (defined(featureTableJson.RGBA)) { colors = featureTable.getPropertyArray( "RGBA", ComponentDatatype.UNSIGNED_BYTE, 4 ); isTranslucent = true; hasColors = true; } else if (defined(featureTableJson.RGB)) { colors = featureTable.getPropertyArray( "RGB", ComponentDatatype.UNSIGNED_BYTE, 3 ); hasColors = true; } else if (defined(featureTableJson.RGB565)) { colors = featureTable.getPropertyArray( "RGB565", ComponentDatatype.UNSIGNED_SHORT, 1 ); isRGB565 = true; hasColors = true; } } if (!hasNormals) { if (defined(featureTableJson.NORMAL)) { normals = featureTable.getPropertyArray( "NORMAL", ComponentDatatype.FLOAT, 3 ); hasNormals = true; } else if (defined(featureTableJson.NORMAL_OCT16P)) { normals = featureTable.getPropertyArray( "NORMAL_OCT16P", ComponentDatatype.UNSIGNED_BYTE, 2 ); isOctEncoded16P = true; hasNormals = true; } } if (!hasBatchIds) { if (defined(featureTableJson.BATCH_ID)) { batchIds = featureTable.getPropertyArray( "BATCH_ID", ComponentDatatype.UNSIGNED_SHORT, 1 ); hasBatchIds = true; } } if (!hasPositions) { throw new RuntimeError( "Either POSITION or POSITION_QUANTIZED must be defined." ); } if (defined(featureTableJson.CONSTANT_RGBA)) { var constantRGBA = featureTable.getGlobalProperty( "CONSTANT_RGBA", ComponentDatatype.UNSIGNED_BYTE, 4 ); pointCloud._constantColor = Color.fromBytes( constantRGBA[0], constantRGBA[1], constantRGBA[2], constantRGBA[3], pointCloud._constantColor ); } if (hasBatchIds) { var batchLength = featureTable.getGlobalProperty("BATCH_LENGTH"); if (!defined(batchLength)) { throw new RuntimeError( "Global property: BATCH_LENGTH must be defined when BATCH_ID is defined." ); } if (defined(batchTableBinary)) { // Copy the batchTableBinary section and let the underlying ArrayBuffer be freed batchTableBinary = new Uint8Array(batchTableBinary); } if (defined(pointCloud._batchTableLoaded)) { pointCloud._batchTableLoaded( batchLength, batchTableJson, batchTableBinary ); } } // If points are not batched and there are per-point properties, use these properties for styling purposes var styleableProperties; if (!hasBatchIds && defined(batchTableBinary)) { styleableProperties = Cesium3DTileBatchTable.getBinaryProperties( pointsLength, batchTableJson, batchTableBinary ); } pointCloud._parsedContent = { positions: positions, colors: colors, normals: normals, batchIds: batchIds, styleableProperties: styleableProperties, draco: draco, }; pointCloud._pointsLength = pointsLength; pointCloud._isQuantized = isQuantized; pointCloud._isOctEncoded16P = isOctEncoded16P; pointCloud._isRGB565 = isRGB565; pointCloud._isTranslucent = isTranslucent; pointCloud._hasColors = hasColors; pointCloud._hasNormals = hasNormals; pointCloud._hasBatchIds = hasBatchIds; } var scratchMin = new Cartesian3(); var scratchMax = new Cartesian3(); var scratchPosition = new Cartesian3(); var randomValues; function getRandomValues(samplesLength) { // Use same random values across all runs if (!defined(randomValues)) { CesiumMath.setRandomNumberSeed(0); randomValues = new Array(samplesLength); for (var i = 0; i < samplesLength; ++i) { randomValues[i] = CesiumMath.nextRandomNumber(); } } return randomValues; } function computeApproximateBoundingSphereFromPositions(positions) { var maximumSamplesLength = 20; var pointsLength = positions.length / 3; var samplesLength = Math.min(pointsLength, maximumSamplesLength); var randomValues = getRandomValues(maximumSamplesLength); var maxValue = Number.MAX_VALUE; var minValue = -Number.MAX_VALUE; var min = Cartesian3.fromElements(maxValue, maxValue, maxValue, scratchMin); var max = Cartesian3.fromElements(minValue, minValue, minValue, scratchMax); for (var i = 0; i < samplesLength; ++i) { var index = Math.floor(randomValues[i] * pointsLength); var position = Cartesian3.unpack(positions, index * 3, scratchPosition); Cartesian3.minimumByComponent(min, position, min); Cartesian3.maximumByComponent(max, position, max); } var boundingSphere = BoundingSphere.fromCornerPoints(min, max); boundingSphere.radius += CesiumMath.EPSILON2; // To avoid radius of zero return boundingSphere; } function prepareVertexAttribute(typedArray, name) { // WebGL does not support UNSIGNED_INT, INT, or DOUBLE vertex attributes. Convert these to FLOAT. var componentDatatype = ComponentDatatype.fromTypedArray(typedArray); if ( componentDatatype === ComponentDatatype.INT || componentDatatype === ComponentDatatype.UNSIGNED_INT || componentDatatype === ComponentDatatype.DOUBLE ) { oneTimeWarning( "Cast pnts property to floats", 'Point cloud property "' + name + '" will be casted to a float array because INT, UNSIGNED_INT, and DOUBLE are not valid WebGL vertex attribute types. Some precision may be lost.' ); return new Float32Array(typedArray); } return typedArray; } var scratchPointSizeAndTimeAndGeometricErrorAndDepthMultiplier = new Cartesian4(); var scratchQuantizedVolumeScaleAndOctEncodedRange = new Cartesian4(); var scratchColor = new Color(); var positionLocation = 0; var colorLocation = 1; var normalLocation = 2; var batchIdLocation = 3; var numberOfAttributes = 4; var scratchClippingPlanesMatrix = new Matrix4(); var scratchInverseTransposeClippingPlanesMatrix = new Matrix4(); function createResources(pointCloud, frameState) { var context = frameState.context; var parsedContent = pointCloud._parsedContent; var pointsLength = pointCloud._pointsLength; var positions = parsedContent.positions; var colors = parsedContent.colors; var normals = parsedContent.normals; var batchIds = parsedContent.batchIds; var styleableProperties = parsedContent.styleableProperties; var hasStyleableProperties = defined(styleableProperties); var isQuantized = pointCloud._isQuantized; var isQuantizedDraco = pointCloud._isQuantizedDraco; var isOctEncoded16P = pointCloud._isOctEncoded16P; var isOctEncodedDraco = pointCloud._isOctEncodedDraco; var quantizedRange = pointCloud._quantizedRange; var octEncodedRange = pointCloud._octEncodedRange; var isRGB565 = pointCloud._isRGB565; var isTranslucent = pointCloud._isTranslucent; var hasColors = pointCloud._hasColors; var hasNormals = pointCloud._hasNormals; var hasBatchIds = pointCloud._hasBatchIds; var componentsPerAttribute; var componentDatatype; var styleableVertexAttributes = []; var styleableShaderAttributes = {}; pointCloud._styleableShaderAttributes = styleableShaderAttributes; if (hasStyleableProperties) { var attributeLocation = numberOfAttributes; for (var name in styleableProperties) { if (styleableProperties.hasOwnProperty(name)) { var property = styleableProperties[name]; var typedArray = prepareVertexAttribute(property.typedArray, name); componentsPerAttribute = property.componentCount; componentDatatype = ComponentDatatype.fromTypedArray(typedArray); var vertexBuffer = Buffer.createVertexBuffer({ context: context, typedArray: typedArray, usage: BufferUsage.STATIC_DRAW, }); pointCloud._geometryByteLength += vertexBuffer.sizeInBytes; var vertexAttribute = { index: attributeLocation, vertexBuffer: vertexBuffer, componentsPerAttribute: componentsPerAttribute, componentDatatype: componentDatatype, normalize: false, offsetInBytes: 0, strideInBytes: 0, }; styleableVertexAttributes.push(vertexAttribute); styleableShaderAttributes[name] = { location: attributeLocation, componentCount: componentsPerAttribute, }; ++attributeLocation; } } } var positionsVertexBuffer = Buffer.createVertexBuffer({ context: context, typedArray: positions, usage: BufferUsage.STATIC_DRAW, }); pointCloud._geometryByteLength += positionsVertexBuffer.sizeInBytes; var colorsVertexBuffer; if (hasColors) { colorsVertexBuffer = Buffer.createVertexBuffer({ context: context, typedArray: colors, usage: BufferUsage.STATIC_DRAW, }); pointCloud._geometryByteLength += colorsVertexBuffer.sizeInBytes; } var normalsVertexBuffer; if (hasNormals) { normalsVertexBuffer = Buffer.createVertexBuffer({ context: context, typedArray: normals, usage: BufferUsage.STATIC_DRAW, }); pointCloud._geometryByteLength += normalsVertexBuffer.sizeInBytes; } var batchIdsVertexBuffer; if (hasBatchIds) { batchIds = prepareVertexAttribute(batchIds, "batchIds"); batchIdsVertexBuffer = Buffer.createVertexBuffer({ context: context, typedArray: batchIds, usage: BufferUsage.STATIC_DRAW, }); pointCloud._geometryByteLength += batchIdsVertexBuffer.sizeInBytes; } var attributes = []; if (isQuantized) { componentDatatype = ComponentDatatype.UNSIGNED_SHORT; } else if (isQuantizedDraco) { componentDatatype = quantizedRange <= 255 ? ComponentDatatype.UNSIGNED_BYTE : ComponentDatatype.UNSIGNED_SHORT; } else { componentDatatype = ComponentDatatype.FLOAT; } attributes.push({ index: positionLocation, vertexBuffer: positionsVertexBuffer, componentsPerAttribute: 3, componentDatatype: componentDatatype, normalize: false, offsetInBytes: 0, strideInBytes: 0, }); if (pointCloud._cull) { if (isQuantized || isQuantizedDraco) { pointCloud._boundingSphere = BoundingSphere.fromCornerPoints( Cartesian3.ZERO, pointCloud._quantizedVolumeScale ); } else { pointCloud._boundingSphere = computeApproximateBoundingSphereFromPositions( positions ); } } if (hasColors) { if (isRGB565) { attributes.push({ index: colorLocation, vertexBuffer: colorsVertexBuffer, componentsPerAttribute: 1, componentDatatype: ComponentDatatype.UNSIGNED_SHORT, normalize: false, offsetInBytes: 0, strideInBytes: 0, }); } else { var colorComponentsPerAttribute = isTranslucent ? 4 : 3; attributes.push({ index: colorLocation, vertexBuffer: colorsVertexBuffer, componentsPerAttribute: colorComponentsPerAttribute, componentDatatype: ComponentDatatype.UNSIGNED_BYTE, normalize: true, offsetInBytes: 0, strideInBytes: 0, }); } } if (hasNormals) { if (isOctEncoded16P) { componentsPerAttribute = 2; componentDatatype = ComponentDatatype.UNSIGNED_BYTE; } else if (isOctEncodedDraco) { componentsPerAttribute = 2; componentDatatype = octEncodedRange <= 255 ? ComponentDatatype.UNSIGNED_BYTE : ComponentDatatype.UNSIGNED_SHORT; } else { componentsPerAttribute = 3; componentDatatype = ComponentDatatype.FLOAT; } attributes.push({ index: normalLocation, vertexBuffer: normalsVertexBuffer, componentsPerAttribute: componentsPerAttribute, componentDatatype: componentDatatype, normalize: false, offsetInBytes: 0, strideInBytes: 0, }); } if (hasBatchIds) { attributes.push({ index: batchIdLocation, vertexBuffer: batchIdsVertexBuffer, componentsPerAttribute: 1, componentDatatype: ComponentDatatype.fromTypedArray(batchIds), normalize: false, offsetInBytes: 0, strideInBytes: 0, }); } if (hasStyleableProperties) { attributes = attributes.concat(styleableVertexAttributes); } var vertexArray = new VertexArray({ context: context, attributes: attributes, }); var opaqueRenderState = { depthTest: { enabled: true, }, }; var translucentRenderState = { depthTest: { enabled: true, }, depthMask: false, blending: BlendingState.ALPHA_BLEND, }; if (pointCloud._opaquePass === Pass.CESIUM_3D_TILE) { opaqueRenderState.stencilTest = StencilConstants.setCesium3DTileBit(); opaqueRenderState.stencilMask = StencilConstants.CESIUM_3D_TILE_MASK; translucentRenderState.stencilTest = StencilConstants.setCesium3DTileBit(); translucentRenderState.stencilMask = StencilConstants.CESIUM_3D_TILE_MASK; } pointCloud._opaqueRenderState = RenderState.fromCache(opaqueRenderState); pointCloud._translucentRenderState = RenderState.fromCache( translucentRenderState ); pointCloud._drawCommand = new DrawCommand({ boundingVolume: new BoundingSphere(), cull: pointCloud._cull, modelMatrix: new Matrix4(), primitiveType: PrimitiveType.POINTS, vertexArray: vertexArray, count: pointsLength, shaderProgram: undefined, // Updated in createShaders uniformMap: undefined, // Updated in createShaders renderState: isTranslucent ? pointCloud._translucentRenderState : pointCloud._opaqueRenderState, pass: isTranslucent ? Pass.TRANSLUCENT : pointCloud._opaquePass, owner: pointCloud, castShadows: false, receiveShadows: false, pickId: pointCloud._pickIdLoaded(), }); } function createUniformMap(pointCloud, frameState) { var context = frameState.context; var isQuantized = pointCloud._isQuantized; var isQuantizedDraco = pointCloud._isQuantizedDraco; var isOctEncodedDraco = pointCloud._isOctEncodedDraco; var uniformMap = { u_pointSizeAndTimeAndGeometricErrorAndDepthMultiplier: function () { var scratch = scratchPointSizeAndTimeAndGeometricErrorAndDepthMultiplier; scratch.x = pointCloud._attenuation ? pointCloud.maximumAttenuation : pointCloud._pointSize; scratch.x *= frameState.pixelRatio; scratch.y = pointCloud.time; if (pointCloud._attenuation) { var frustum = frameState.camera.frustum; var depthMultiplier; // Attenuation is maximumAttenuation in 2D/ortho if ( frameState.mode === SceneMode.SCENE2D || frustum instanceof OrthographicFrustum ) { depthMultiplier = Number.POSITIVE_INFINITY; } else { depthMultiplier = context.drawingBufferHeight / frameState.camera.frustum.sseDenominator; } scratch.z = pointCloud.geometricError * pointCloud.geometricErrorScale; scratch.w = depthMultiplier; } return scratch; }, u_highlightColor: function () { return pointCloud._highlightColor; }, u_constantColor: function () { return pointCloud._constantColor; }, u_clippingPlanes: function () { var clippingPlanes = pointCloud.clippingPlanes; var isClipped = pointCloud.isClipped; return isClipped ? clippingPlanes.texture : context.defaultTexture; }, u_clippingPlanesEdgeStyle: function () { var clippingPlanes = pointCloud.clippingPlanes; if (!defined(clippingPlanes)) { return Color.TRANSPARENT; } var style = Color.clone(clippingPlanes.edgeColor, scratchColor); style.alpha = clippingPlanes.edgeWidth; return style; }, u_clippingPlanesMatrix: function () { var clippingPlanes = pointCloud.clippingPlanes; if (!defined(clippingPlanes)) { return Matrix4.IDENTITY; } var clippingPlanesOriginMatrix = defaultValue( pointCloud.clippingPlanesOriginMatrix, pointCloud._modelMatrix ); Matrix4.multiply( context.uniformState.view3D, clippingPlanesOriginMatrix, scratchClippingPlanesMatrix ); var transform = Matrix4.multiply( scratchClippingPlanesMatrix, clippingPlanes.modelMatrix, scratchClippingPlanesMatrix ); return Matrix4.inverseTranspose( transform, scratchInverseTransposeClippingPlanesMatrix ); }, }; if (isQuantized || isQuantizedDraco || isOctEncodedDraco) { uniformMap = combine(uniformMap, { u_quantizedVolumeScaleAndOctEncodedRange: function () { var scratch = scratchQuantizedVolumeScaleAndOctEncodedRange; if (defined(pointCloud._quantizedVolumeScale)) { var scale = Cartesian3.clone( pointCloud._quantizedVolumeScale, scratch ); Cartesian3.divideByScalar(scale, pointCloud._quantizedRange, scratch); } scratch.w = pointCloud._octEncodedRange; return scratch; }, }); } if (defined(pointCloud._uniformMapLoaded)) { uniformMap = pointCloud._uniformMapLoaded(uniformMap); } pointCloud._drawCommand.uniformMap = uniformMap; } function getStyleablePropertyIds(source, propertyIds) { // Get all the property IDs used by this style var regex = /czm_3dtiles_property_(\d+)/g; var matches = regex.exec(source); while (matches !== null) { var id = parseInt(matches[1]); if (propertyIds.indexOf(id) === -1) { propertyIds.push(id); } matches = regex.exec(source); } } function getBuiltinPropertyNames(source, propertyNames) { // Get all the builtin property names used by this style var regex = /czm_3dtiles_builtin_property_(\w+)/g; var matches = regex.exec(source); while (matches !== null) { var name = matches[1]; if (propertyNames.indexOf(name) === -1) { propertyNames.push(name); } matches = regex.exec(source); } } function getVertexAttribute(vertexArray, index) { var numberOfAttributes = vertexArray.numberOfAttributes; for (var i = 0; i < numberOfAttributes; ++i) { var attribute = vertexArray.getAttribute(i); if (attribute.index === index) { return attribute; } } } var builtinPropertyNameMap = { POSITION: "czm_3dtiles_builtin_property_POSITION", POSITION_ABSOLUTE: "czm_3dtiles_builtin_property_POSITION_ABSOLUTE", COLOR: "czm_3dtiles_builtin_property_COLOR", NORMAL: "czm_3dtiles_builtin_property_NORMAL", }; function modifyStyleFunction(source) { // Edit the function header to accept the point position, color, and normal var functionHeader = "(" + "vec3 czm_3dtiles_builtin_property_POSITION, " + "vec3 czm_3dtiles_builtin_property_POSITION_ABSOLUTE, " + "vec4 czm_3dtiles_builtin_property_COLOR, " + "vec3 czm_3dtiles_builtin_property_NORMAL" + ")"; return source.replace("()", functionHeader); } function createShaders(pointCloud, frameState, style) { var i; var name; var attribute; var context = frameState.context; var hasStyle = defined(style); var isQuantized = pointCloud._isQuantized; var isQuantizedDraco = pointCloud._isQuantizedDraco; var isOctEncoded16P = pointCloud._isOctEncoded16P; var isOctEncodedDraco = pointCloud._isOctEncodedDraco; var isRGB565 = pointCloud._isRGB565; var isTranslucent = pointCloud._isTranslucent; var hasColors = pointCloud._hasColors; var hasNormals = pointCloud._hasNormals; var hasBatchIds = pointCloud._hasBatchIds; var backFaceCulling = pointCloud._backFaceCulling; var normalShading = pointCloud._normalShading; var vertexArray = pointCloud._drawCommand.vertexArray; var clippingPlanes = pointCloud.clippingPlanes; var attenuation = pointCloud._attenuation; var colorStyleFunction; var showStyleFunction; var pointSizeStyleFunction; var styleTranslucent = isTranslucent; var propertyNameMap = clone(builtinPropertyNameMap); var propertyIdToAttributeMap = {}; var styleableShaderAttributes = pointCloud._styleableShaderAttributes; for (name in styleableShaderAttributes) { if (styleableShaderAttributes.hasOwnProperty(name)) { attribute = styleableShaderAttributes[name]; propertyNameMap[name] = "czm_3dtiles_property_" + attribute.location; propertyIdToAttributeMap[attribute.location] = attribute; } } if (hasStyle) { var shaderState = { translucent: false, }; colorStyleFunction = style.getColorShaderFunction( "getColorFromStyle", propertyNameMap, shaderState ); showStyleFunction = style.getShowShaderFunction( "getShowFromStyle", propertyNameMap, shaderState ); pointSizeStyleFunction = style.getPointSizeShaderFunction( "getPointSizeFromStyle", propertyNameMap, shaderState ); if (defined(colorStyleFunction) && shaderState.translucent) { styleTranslucent = true; } } pointCloud._styleTranslucent = styleTranslucent; var hasColorStyle = defined(colorStyleFunction); var hasShowStyle = defined(showStyleFunction); var hasPointSizeStyle = defined(pointSizeStyleFunction); var hasClippedContent = pointCloud.isClipped; // Get the properties in use by the style var styleablePropertyIds = []; var builtinPropertyNames = []; if (hasColorStyle) { getStyleablePropertyIds(colorStyleFunction, styleablePropertyIds); getBuiltinPropertyNames(colorStyleFunction, builtinPropertyNames); colorStyleFunction = modifyStyleFunction(colorStyleFunction); } if (hasShowStyle) { getStyleablePropertyIds(showStyleFunction, styleablePropertyIds); getBuiltinPropertyNames(showStyleFunction, builtinPropertyNames); showStyleFunction = modifyStyleFunction(showStyleFunction); } if (hasPointSizeStyle) { getStyleablePropertyIds(pointSizeStyleFunction, styleablePropertyIds); getBuiltinPropertyNames(pointSizeStyleFunction, builtinPropertyNames); pointSizeStyleFunction = modifyStyleFunction(pointSizeStyleFunction); } var usesColorSemantic = builtinPropertyNames.indexOf("COLOR") >= 0; var usesNormalSemantic = builtinPropertyNames.indexOf("NORMAL") >= 0; if (usesNormalSemantic && !hasNormals) { throw new RuntimeError( "Style references the NORMAL semantic but the point cloud does not have normals" ); } // Disable vertex attributes that aren't used in the style, enable attributes that are for (name in styleableShaderAttributes) { if (styleableShaderAttributes.hasOwnProperty(name)) { attribute = styleableShaderAttributes[name]; var enabled = styleablePropertyIds.indexOf(attribute.location) >= 0; var vertexAttribute = getVertexAttribute(vertexArray, attribute.location); vertexAttribute.enabled = enabled; } } var usesColors = hasColors && (!hasColorStyle || usesColorSemantic); if (hasColors) { // Disable the color vertex attribute if the color style does not reference the color semantic var colorVertexAttribute = getVertexAttribute(vertexArray, colorLocation); colorVertexAttribute.enabled = usesColors; } var usesNormals = hasNormals && (normalShading || backFaceCulling || usesNormalSemantic); if (hasNormals) { // Disable the normal vertex attribute if normals are not used var normalVertexAttribute = getVertexAttribute(vertexArray, normalLocation); normalVertexAttribute.enabled = usesNormals; } var attributeLocations = { a_position: positionLocation, }; if (usesColors) { attributeLocations.a_color = colorLocation; } if (usesNormals) { attributeLocations.a_normal = normalLocation; } if (hasBatchIds) { attributeLocations.a_batchId = batchIdLocation; } var attributeDeclarations = ""; var length = styleablePropertyIds.length; for (i = 0; i < length; ++i) { var propertyId = styleablePropertyIds[i]; attribute = propertyIdToAttributeMap[propertyId]; var componentCount = attribute.componentCount; var attributeName = "czm_3dtiles_property_" + propertyId; var attributeType; if (componentCount === 1) { attributeType = "float"; } else { attributeType = "vec" + componentCount; } attributeDeclarations += "attribute " + attributeType + " " + attributeName + "; \n"; attributeLocations[attributeName] = attribute.location; } createUniformMap(pointCloud, frameState); var vs = "attribute vec3 a_position; \n" + "varying vec4 v_color; \n" + "uniform vec4 u_pointSizeAndTimeAndGeometricErrorAndDepthMultiplier; \n" + "uniform vec4 u_constantColor; \n" + "uniform vec4 u_highlightColor; \n"; vs += "float u_pointSize; \n" + "float u_time; \n"; if (attenuation) { vs += "float u_geometricError; \n" + "float u_depthMultiplier; \n"; } vs += attributeDeclarations; if (usesColors) { if (isTranslucent) { vs += "attribute vec4 a_color; \n"; } else if (isRGB565) { vs += "attribute float a_color; \n" + "const float SHIFT_RIGHT_11 = 1.0 / 2048.0; \n" + "const float SHIFT_RIGHT_5 = 1.0 / 32.0; \n" + "const float SHIFT_LEFT_11 = 2048.0; \n" + "const float SHIFT_LEFT_5 = 32.0; \n" + "const float NORMALIZE_6 = 1.0 / 64.0; \n" + "const float NORMALIZE_5 = 1.0 / 32.0; \n"; } else { vs += "attribute vec3 a_color; \n"; } } if (usesNormals) { if (isOctEncoded16P || isOctEncodedDraco) { vs += "attribute vec2 a_normal; \n"; } else { vs += "attribute vec3 a_normal; \n"; } } if (hasBatchIds) { vs += "attribute float a_batchId; \n"; } if (isQuantized || isQuantizedDraco || isOctEncodedDraco) { vs += "uniform vec4 u_quantizedVolumeScaleAndOctEncodedRange; \n"; } if (hasColorStyle) { vs += colorStyleFunction; } if (hasShowStyle) { vs += showStyleFunction; } if (hasPointSizeStyle) { vs += pointSizeStyleFunction; } vs += "void main() \n" + "{ \n" + " u_pointSize = u_pointSizeAndTimeAndGeometricErrorAndDepthMultiplier.x; \n" + " u_time = u_pointSizeAndTimeAndGeometricErrorAndDepthMultiplier.y; \n"; if (attenuation) { vs += " u_geometricError = u_pointSizeAndTimeAndGeometricErrorAndDepthMultiplier.z; \n" + " u_depthMultiplier = u_pointSizeAndTimeAndGeometricErrorAndDepthMultiplier.w; \n"; } if (usesColors) { if (isTranslucent) { vs += " vec4 color = a_color; \n"; } else if (isRGB565) { vs += " float compressed = a_color; \n" + " float r = floor(compressed * SHIFT_RIGHT_11); \n" + " compressed -= r * SHIFT_LEFT_11; \n" + " float g = floor(compressed * SHIFT_RIGHT_5); \n" + " compressed -= g * SHIFT_LEFT_5; \n" + " float b = compressed; \n" + " vec3 rgb = vec3(r * NORMALIZE_5, g * NORMALIZE_6, b * NORMALIZE_5); \n" + " vec4 color = vec4(rgb, 1.0); \n"; } else { vs += " vec4 color = vec4(a_color, 1.0); \n"; } } else { vs += " vec4 color = u_constantColor; \n"; } if (isQuantized || isQuantizedDraco) { vs += " vec3 position = a_position * u_quantizedVolumeScaleAndOctEncodedRange.xyz; \n"; } else { vs += " vec3 position = a_position; \n"; } vs += " vec3 position_absolute = vec3(czm_model * vec4(position, 1.0)); \n"; if (usesNormals) { if (isOctEncoded16P) { vs += " vec3 normal = czm_octDecode(a_normal); \n"; } else if (isOctEncodedDraco) { // Draco oct-encoding decodes to zxy order vs += " vec3 normal = czm_octDecode(a_normal, u_quantizedVolumeScaleAndOctEncodedRange.w).zxy; \n"; } else { vs += " vec3 normal = a_normal; \n"; } vs += " vec3 normalEC = czm_normal * normal; \n"; } else { vs += " vec3 normal = vec3(1.0); \n"; } if (hasColorStyle) { vs += " color = getColorFromStyle(position, position_absolute, color, normal); \n"; } if (hasShowStyle) { vs += " float show = float(getShowFromStyle(position, position_absolute, color, normal)); \n"; } if (hasPointSizeStyle) { vs += " gl_PointSize = getPointSizeFromStyle(position, position_absolute, color, normal) * czm_pixelRatio; \n"; } else if (attenuation) { vs += " vec4 positionEC = czm_modelView * vec4(position, 1.0); \n" + " float depth = -positionEC.z; \n" + // compute SSE for this point " gl_PointSize = min((u_geometricError / depth) * u_depthMultiplier, u_pointSize); \n"; } else { vs += " gl_PointSize = u_pointSize; \n"; } vs += " color = color * u_highlightColor; \n"; if (usesNormals && normalShading) { vs += " float diffuseStrength = czm_getLambertDiffuse(czm_lightDirectionEC, normalEC); \n" + " diffuseStrength = max(diffuseStrength, 0.4); \n" + // Apply some ambient lighting " color.xyz *= diffuseStrength * czm_lightColor; \n"; } vs += " v_color = color; \n" + " gl_Position = czm_modelViewProjection * vec4(position, 1.0); \n"; if (usesNormals && backFaceCulling) { vs += " float visible = step(-normalEC.z, 0.0); \n" + " gl_Position *= visible; \n" + " gl_PointSize *= visible; \n"; } if (hasShowStyle) { vs += " gl_Position.w *= float(show); \n" + " gl_PointSize *= float(show); \n"; } vs += "} \n"; var fs = "varying vec4 v_color; \n"; if (hasClippedContent) { fs += "uniform highp sampler2D u_clippingPlanes; \n" + "uniform mat4 u_clippingPlanesMatrix; \n" + "uniform vec4 u_clippingPlanesEdgeStyle; \n"; fs += "\n"; fs += getClippingFunction(clippingPlanes, context); fs += "\n"; } fs += "void main() \n" + "{ \n" + " gl_FragColor = czm_gammaCorrect(v_color); \n"; if (hasClippedContent) { fs += getClipAndStyleCode( "u_clippingPlanes", "u_clippingPlanesMatrix", "u_clippingPlanesEdgeStyle" ); } fs += "} \n"; if (defined(pointCloud._vertexShaderLoaded)) { vs = pointCloud._vertexShaderLoaded(vs); } if (defined(pointCloud._fragmentShaderLoaded)) { fs = pointCloud._fragmentShaderLoaded(fs); } var drawCommand = pointCloud._drawCommand; if (defined(drawCommand.shaderProgram)) { // Destroy the old shader drawCommand.shaderProgram.destroy(); } drawCommand.shaderProgram = ShaderProgram.fromCache({ context: context, vertexShaderSource: vs, fragmentShaderSource: fs, attributeLocations: attributeLocations, }); try { // Check if the shader compiles correctly. If not there is likely a syntax error with the style. drawCommand.shaderProgram._bind(); } catch (error) { // Rephrase the error. throw new RuntimeError( "Error generating style shader: this may be caused by a type mismatch, index out-of-bounds, or other syntax error." ); } } function decodeDraco(pointCloud, context) { if (pointCloud._decodingState === DecodingState.READY) { return false; } if (pointCloud._decodingState === DecodingState.NEEDS_DECODE) { var parsedContent = pointCloud._parsedContent; var draco = parsedContent.draco; var decodePromise = DracoLoader.decodePointCloud(draco, context); if (defined(decodePromise)) { pointCloud._decodingState = DecodingState.DECODING; decodePromise .then(function (result) { pointCloud._decodingState = DecodingState.READY; var decodedPositions = defined(result.POSITION) ? result.POSITION.array : undefined; var decodedRgb = defined(result.RGB) ? result.RGB.array : undefined; var decodedRgba = defined(result.RGBA) ? result.RGBA.array : undefined; var decodedNormals = defined(result.NORMAL) ? result.NORMAL.array : undefined; var decodedBatchIds = defined(result.BATCH_ID) ? result.BATCH_ID.array : undefined; var isQuantizedDraco = defined(decodedPositions) && defined(result.POSITION.data.quantization); var isOctEncodedDraco = defined(decodedNormals) && defined(result.NORMAL.data.quantization); if (isQuantizedDraco) { // Draco quantization range == quantized volume scale - size in meters of the quantized volume // Internal quantized range is the range of values of the quantized data, e.g. 255 for 8-bit, 1023 for 10-bit, etc var quantization = result.POSITION.data.quantization; var range = quantization.range; pointCloud._quantizedVolumeScale = Cartesian3.fromElements( range, range, range ); pointCloud._quantizedVolumeOffset = Cartesian3.unpack( quantization.minValues ); pointCloud._quantizedRange = (1 << quantization.quantizationBits) - 1.0; pointCloud._isQuantizedDraco = true; } if (isOctEncodedDraco) { pointCloud._octEncodedRange = (1 << result.NORMAL.data.quantization.quantizationBits) - 1.0; pointCloud._isOctEncodedDraco = true; } var styleableProperties = parsedContent.styleableProperties; var batchTableProperties = draco.batchTableProperties; for (var name in batchTableProperties) { if (batchTableProperties.hasOwnProperty(name)) { var property = result[name]; if (!defined(styleableProperties)) { styleableProperties = {}; } styleableProperties[name] = { typedArray: property.array, componentCount: property.data.componentsPerAttribute, }; } } parsedContent.positions = defaultValue( decodedPositions, parsedContent.positions ); parsedContent.colors = defaultValue( defaultValue(decodedRgba, decodedRgb), parsedContent.colors ); parsedContent.normals = defaultValue( decodedNormals, parsedContent.normals ); parsedContent.batchIds = defaultValue( decodedBatchIds, parsedContent.batchIds ); parsedContent.styleableProperties = styleableProperties; }) .otherwise(function (error) { pointCloud._decodingState = DecodingState.FAILED; pointCloud._readyPromise.reject(error); }); } } return true; } var scratchComputedTranslation = new Cartesian4(); var scratchScale = new Cartesian3(); PointCloud.prototype.update = function (frameState) { var context = frameState.context; var decoding = decodeDraco(this, context); if (decoding) { return; } var shadersDirty = false; var modelMatrixDirty = !Matrix4.equals(this._modelMatrix, this.modelMatrix); if (this._mode !== frameState.mode) { this._mode = frameState.mode; modelMatrixDirty = true; } if (!defined(this._drawCommand)) { createResources(this, frameState); modelMatrixDirty = true; shadersDirty = true; this._ready = true; this._readyPromise.resolve(this); this._parsedContent = undefined; // Unload } if (modelMatrixDirty) { Matrix4.clone(this.modelMatrix, this._modelMatrix); var modelMatrix = this._drawCommand.modelMatrix; Matrix4.clone(this._modelMatrix, modelMatrix); if (defined(this._rtcCenter)) { Matrix4.multiplyByTranslation(modelMatrix, this._rtcCenter, modelMatrix); } if (defined(this._quantizedVolumeOffset)) { Matrix4.multiplyByTranslation( modelMatrix, this._quantizedVolumeOffset, modelMatrix ); } if (frameState.mode !== SceneMode.SCENE3D) { var projection = frameState.mapProjection; var translation = Matrix4.getColumn( modelMatrix, 3, scratchComputedTranslation ); if (!Cartesian4.equals(translation, Cartesian4.UNIT_W)) { Transforms.basisTo2D(projection, modelMatrix, modelMatrix); } } var boundingSphere = this._drawCommand.boundingVolume; BoundingSphere.clone(this._boundingSphere, boundingSphere); if (this._cull) { var center = boundingSphere.center; Matrix4.multiplyByPoint(modelMatrix, center, center); var scale = Matrix4.getScale(modelMatrix, scratchScale); boundingSphere.radius *= Cartesian3.maximumComponent(scale); } } if (this.clippingPlanesDirty) { this.clippingPlanesDirty = false; shadersDirty = true; } if (this._attenuation !== this.attenuation) { this._attenuation = this.attenuation; shadersDirty = true; } if (this.backFaceCulling !== this._backFaceCulling) { this._backFaceCulling = this.backFaceCulling; shadersDirty = true; } if (this.normalShading !== this._normalShading) { this._normalShading = this.normalShading; shadersDirty = true; } if (this._style !== this.style || this.styleDirty) { this._style = this.style; this.styleDirty = false; shadersDirty = true; } if (shadersDirty) { createShaders(this, frameState, this._style); } this._drawCommand.castShadows = ShadowMode.castShadows(this.shadows); this._drawCommand.receiveShadows = ShadowMode.receiveShadows(this.shadows); // Update the render state var isTranslucent = this._highlightColor.alpha < 1.0 || this._constantColor.alpha < 1.0 || this._styleTranslucent; this._drawCommand.renderState = isTranslucent ? this._translucentRenderState : this._opaqueRenderState; this._drawCommand.pass = isTranslucent ? Pass.TRANSLUCENT : this._opaquePass; var commandList = frameState.commandList; var passes = frameState.passes; if (passes.render || passes.pick) { commandList.push(this._drawCommand); } }; PointCloud.prototype.isDestroyed = function () { return false; }; PointCloud.prototype.destroy = function () { var command = this._drawCommand; if (defined(command)) { command.vertexArray = command.vertexArray && command.vertexArray.destroy(); command.shaderProgram = command.shaderProgram && command.shaderProgram.destroy(); } return destroyObject(this); }; export default PointCloud;