From 2dab089509500c167399639e028e887752869607 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 29 May 2018 18:11:37 -0400 Subject: [PATCH 01/35] Added PointCloud class --- Source/Scene/Expression.js | 2 +- Source/Scene/PointCloud.js | 1355 +++++++++++++++++++ Source/Scene/PointCloud3DTileContent.js | 1370 ++------------------ Specs/Scene/ExpressionSpec.js | 2 +- Specs/Scene/PointCloud3DTileContentSpec.js | 47 +- 5 files changed, 1494 insertions(+), 1282 deletions(-) create mode 100644 Source/Scene/PointCloud.js diff --git a/Source/Scene/Expression.js b/Source/Scene/Expression.js index 02a958f6747e..223e177a3070 100644 --- a/Source/Scene/Expression.js +++ b/Source/Scene/Expression.js @@ -1748,7 +1748,7 @@ define([ throw new RuntimeError('Error generating style shader: undefined is not supported.'); case ExpressionNodeType.BUILTIN_VARIABLE: if (value === 'tiles3d_tileset_time') { - return 'u_tilesetTime'; + return 'u_time'; } } }; diff --git a/Source/Scene/PointCloud.js b/Source/Scene/PointCloud.js new file mode 100644 index 000000000000..2f126d33f876 --- /dev/null +++ b/Source/Scene/PointCloud.js @@ -0,0 +1,1355 @@ +define([ + '../Core/arraySlice', + '../Core/Cartesian3', + '../Core/Cartesian4', + '../Core/Check', + '../Core/Color', + '../Core/combine', + '../Core/ComponentDatatype', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/destroyObject', + '../Core/FeatureDetection', + '../Core/getStringFromTypedArray', + '../Core/Matrix4', + '../Core/oneTimeWarning', + '../Core/OrthographicFrustum', + '../Core/Plane', + '../Core/PrimitiveType', + '../Core/RuntimeError', + '../Core/Transforms', + '../Renderer/Buffer', + '../Renderer/BufferUsage', + '../Renderer/DrawCommand', + '../Renderer/Pass', + '../Renderer/RenderState', + '../Renderer/ShaderProgram', + '../Renderer/ShaderSource', + '../Renderer/VertexArray', + '../ThirdParty/when', + './BlendingState', + './Cesium3DTileBatchTable', + './Cesium3DTileFeature', + './Cesium3DTileFeatureTable', + './DracoLoader', + './getClipAndStyleCode', + './getClippingFunction', + './SceneMode', + './ShadowMode' + ], function( + arraySlice, + Cartesian3, + Cartesian4, + Check, + Color, + combine, + ComponentDatatype, + defaultValue, + defined, + defineProperties, + destroyObject, + FeatureDetection, + getStringFromTypedArray, + Matrix4, + oneTimeWarning, + OrthographicFrustum, + Plane, + PrimitiveType, + RuntimeError, + Transforms, + Buffer, + BufferUsage, + DrawCommand, + Pass, + RenderState, + ShaderProgram, + ShaderSource, + VertexArray, + when, + BlendingState, + Cesium3DTileBatchTable, + Cesium3DTileFeature, + Cesium3DTileFeatureTable, + DracoLoader, + getClipAndStyleCode, + getClippingFunction, + SceneMode, + ShadowMode) { + 'use strict'; + + // Bail out if the browser doesn't support typed arrays, to prevent the setup function + // from failing, since we won't be able to create a WebGL context anyway. + if (!FeatureDetection.supportsTypedArrays()) { + return {}; + } + + var DecodingState = { + NEEDS_DECODE : 0, + DECODING : 1, + READY : 2, + FAILED : 3 + }; + + /** + * Represents a point cloud. + * + * @alias PointCloud + * @constructor + * + * @see PointCloud3DTileContent + * @see PointCloudStream + * + * @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._pickCommand = 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; + + this._opaqueRenderState = undefined; + this._translucentRenderState = undefined; + + this._mode = undefined; + + this._readyPromise = when.defer(); + this._pointsLength = 0; + this._geometryByteLength = 0; + + this._vertexShaderLoaded = options.vertexShaderLoaded; + this._fragmentShaderLoaded = options.fragmentShaderLoaded; + this._pickVertexShaderLoaded = options.pickVertexShaderLoaded; + this._pickFragmentShaderLoaded = options.pickFragmentShaderLoaded; + this._uniformMapLoaded = options.uniformMapLoaded; + this._pickUniformMapLoaded = options.pickUniformMapLoaded; + this._batchTableLoaded = options.batchTableLoaded; + this._opaquePass = defaultValue(options.opaquePass, Pass.OPAQUE); + + 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.DISABLED; + this.boundingVolume = undefined; + + this.clippingPlanes = undefined; + this.isClipped = false; + this.clippingPlanesDirty = false; + + 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); + } + + defineProperties(PointCloud.prototype, { + pointsLength : { + get : function() { + return this._pointsLength; + } + }, + + geometryByteLength : { + get : function() { + return this._geometryByteLength; + } + }, + + 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); + } + } + }); + + 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 featureTableString = getStringFromTypedArray(uint8Array, byteOffset, featureTableJsonByteLength); + var featureTableJson = JSON.parse(featureTableString); + 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 + var batchTableString = getStringFromTypedArray(uint8Array, byteOffset, batchTableJsonByteLength); + batchTableJson = JSON.parse(batchTableString); + 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 isQuantizedDraco = false; + var isOctEncodedDraco = 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); + isQuantizedDraco = hasPositions && pointCloud._dequantizeInShader; + isOctEncodedDraco = hasNormals && pointCloud._dequantizeInShader; + pointCloud._decodingState = DecodingState.NEEDS_DECODE; + } + + 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); + + 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 : { + buffer : dracoBuffer, + featureTableProperties : dracoFeatureTableProperties, + batchTableProperties : dracoBatchTableProperties, + properties : combine(dracoFeatureTableProperties, dracoBatchTableProperties), + dequantizeInShader : pointCloud._dequantizeInShader + } + }; + pointCloud._pointsLength = pointsLength; + pointCloud._isQuantized = isQuantized; + pointCloud._isQuantizedDraco = isQuantizedDraco; + pointCloud._isOctEncoded16P = isOctEncoded16P; + pointCloud._isOctEncodedDraco = isOctEncodedDraco; + pointCloud._isRGB565 = isRGB565; + pointCloud._isTranslucent = isTranslucent; + pointCloud._hasColors = hasColors; + pointCloud._hasNormals = hasNormals; + pointCloud._hasBatchIds = hasBatchIds; + } + + function prepareStyleableProperties(styleableProperties) { + // WebGL does not support UNSIGNED_INT, INT, or DOUBLE vertex attributes. Convert these to FLOAT. + for (var name in styleableProperties) { + if (styleableProperties.hasOwnProperty(name)) { + var property = styleableProperties[name]; + var typedArray = property.typedArray; + 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.'); + property.typedArray = new Float32Array(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 scratchClippingPlaneMatrix = 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 normalize; + + var styleableVertexAttributes = []; + var styleableShaderAttributes = {}; + pointCloud._styleableShaderAttributes = styleableShaderAttributes; + + if (hasStyleableProperties) { + prepareStyleableProperties(styleableProperties); + var attributeLocation = numberOfAttributes; + + for (var name in styleableProperties) { + if (styleableProperties.hasOwnProperty(name)) { + var property = styleableProperties[name]; + var typedArray = property.typedArray; + componentsPerAttribute = property.componentCount; + componentDatatype = ComponentDatatype.fromTypedArray(typedArray); + + var vertexBuffer = Buffer.createVertexBuffer({ + context : context, + typedArray : property.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 uniformMap = { + u_pointSizeAndTimeAndGeometricErrorAndDepthMultiplier : function() { + var scratch = scratchPointSizeAndTimeAndGeometricErrorAndDepthMultiplier; + scratch.x = pointCloud._attenuation ? pointCloud.maximumAttenuation : pointCloud._pointSize; + 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 modelViewMatrix = Matrix4.multiply(context.uniformState.view3D, pointCloud._modelMatrix, scratchClippingPlaneMatrix); + return Matrix4.multiply(modelViewMatrix, clippingPlanes.modelMatrix, scratchClippingPlaneMatrix); + } + }; + + if (isQuantized || isQuantizedDraco || isOctEncodedDraco) { + uniformMap = combine(uniformMap, { + u_quantizedVolumeScaleAndOctEncodedRange : function() { + var scratch = scratchQuantizedVolumeScaleAndOctEncodedRange; + if (defined(pointCloud._quantizedVolumeScale)) { + Cartesian3.clone(pointCloud._quantizedVolumeScale, scratch); + } + scratch.w = pointCloud._octEncodedRange; + return scratch; + } + }); + } + + 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) { + batchIdsVertexBuffer = Buffer.createVertexBuffer({ + context : context, + typedArray : batchIds, + usage : BufferUsage.STATIC_DRAW + }); + pointCloud._geometryByteLength += batchIdsVertexBuffer.sizeInBytes; + } + + var attributes = []; + + if (isQuantized) { + componentDatatype = ComponentDatatype.UNSIGNED_SHORT; + normalize = true; // Convert position to 0 to 1 before entering the shader + } else if (isQuantizedDraco) { + componentDatatype = (quantizedRange <= 255) ? ComponentDatatype.UNSIGNED_BYTE : ComponentDatatype.UNSIGNED_SHORT; + normalize = false; // Normalization is done in the shader based on quantizationBits + } else { + componentDatatype = ComponentDatatype.FLOAT; + normalize = false; + } + + attributes.push({ + index : positionLocation, + vertexBuffer : positionsVertexBuffer, + componentsPerAttribute : 3, + componentDatatype : componentDatatype, + normalize : normalize, + offsetInBytes : 0, + strideInBytes : 0 + }); + + 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 drawUniformMap = uniformMap; + + if (defined(pointCloud._uniformMapLoaded)) { + drawUniformMap = pointCloud._uniformMapLoaded(uniformMap); + } + + var pickUniformMap = uniformMap; + + if (defined(pointCloud._pickUniformMapLoaded)) { + pickUniformMap = pointCloud._pickUniformMapLoaded(uniformMap); + } + + pointCloud._opaqueRenderState = RenderState.fromCache({ + depthTest : { + enabled : true + } + }); + + pointCloud._translucentRenderState = RenderState.fromCache({ + depthTest : { + enabled : true + }, + depthMask : false, + blending : BlendingState.ALPHA_BLEND + }); + + pointCloud._drawCommand = new DrawCommand({ + boundingVolume : undefined, // Updated in update + cull : false, // Already culled by 3D Tiles + modelMatrix : new Matrix4(), + primitiveType : PrimitiveType.POINTS, + vertexArray : vertexArray, + count : pointsLength, + shaderProgram : undefined, // Updated in createShaders + uniformMap : drawUniformMap, + renderState : isTranslucent ? pointCloud._translucentRenderState : pointCloud._opaqueRenderState, + pass : isTranslucent ? Pass.TRANSLUCENT : pointCloud._opaquePass, + owner : pointCloud, + castShadows : false, + receiveShadows : false + }); + + pointCloud._pickCommand = new DrawCommand({ + boundingVolume : undefined, // Updated in update + cull : false, // Already culled by 3D Tiles + modelMatrix : new Matrix4(), + primitiveType : PrimitiveType.POINTS, + vertexArray : vertexArray, + count : pointsLength, + shaderProgram : undefined, // Updated in createShaders + uniformMap : pickUniformMap, + renderState : isTranslucent ? pointCloud._translucentRenderState : pointCloud._opaqueRenderState, + pass : isTranslucent ? Pass.TRANSLUCENT : pointCloud._opaquePass, + owner : pointCloud + }); + } + + var defaultProperties = ['POSITION', 'COLOR', 'NORMAL', 'POSITION_ABSOLUTE']; + + function getStyleableProperties(source, properties) { + // Get all the properties used by this style + var regex = /czm_tiles3d_style_(\w+)/g; + var matches = regex.exec(source); + while (matches !== null) { + var name = matches[1]; + if (properties.indexOf(name) === -1) { + properties.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; + } + } + } + + function modifyStyleFunction(source) { + // Replace occurrences of czm_tiles3d_style_DEFAULTPROPERTY + var length = defaultProperties.length; + for (var i = 0; i < length; ++i) { + var property = defaultProperties[i]; + var styleName = 'czm_tiles3d_style_' + property; + var replaceName = property.toLowerCase(); + source = source.replace(new RegExp(styleName + '(\\W)', 'g'), replaceName + '$1'); + } + + // Edit the function header to accept the point position, color, and normal + return source.replace('()', '(vec3 position, vec3 position_absolute, vec4 color, vec3 normal)'); + } + + 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 vertexArray = pointCloud._drawCommand.vertexArray; + var clippingPlanes = pointCloud.clippingPlanes; + var attenuation = pointCloud._attenuation; + + var colorStyleFunction; + var showStyleFunction; + var pointSizeStyleFunction; + var styleTranslucent = isTranslucent; + + if (hasStyle) { + var shaderState = { + translucent : false + }; + colorStyleFunction = style.getColorShaderFunction('getColorFromStyle', 'czm_tiles3d_style_', shaderState); + showStyleFunction = style.getShowShaderFunction('getShowFromStyle', 'czm_tiles3d_style_', shaderState); + pointSizeStyleFunction = style.getPointSizeShaderFunction('getPointSizeFromStyle', 'czm_tiles3d_style_', 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 styleableProperties = []; + + if (hasColorStyle) { + getStyleableProperties(colorStyleFunction, styleableProperties); + colorStyleFunction = modifyStyleFunction(colorStyleFunction); + } + if (hasShowStyle) { + getStyleableProperties(showStyleFunction, styleableProperties); + showStyleFunction = modifyStyleFunction(showStyleFunction); + } + if (hasPointSizeStyle) { + getStyleableProperties(pointSizeStyleFunction, styleableProperties); + pointSizeStyleFunction = modifyStyleFunction(pointSizeStyleFunction); + } + + var usesColorSemantic = (styleableProperties.indexOf('COLOR') >= 0); + var usesNormalSemantic = (styleableProperties.indexOf('NORMAL') >= 0); + + // Split default properties from user properties + var userProperties = styleableProperties.filter(function(property) { return defaultProperties.indexOf(property) === -1; }); + + 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 + var styleableShaderAttributes = pointCloud._styleableShaderAttributes; + for (name in styleableShaderAttributes) { + if (styleableShaderAttributes.hasOwnProperty(name)) { + attribute = styleableShaderAttributes[name]; + var enabled = (userProperties.indexOf(name) >= 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 attributeLocations = { + a_position : positionLocation + }; + if (usesColors) { + attributeLocations.a_color = colorLocation; + } + if (hasNormals) { + attributeLocations.a_normal = normalLocation; + } + if (hasBatchIds) { + attributeLocations.a_batchId = batchIdLocation; + } + + var attributeDeclarations = ''; + + var length = userProperties.length; + for (i = 0; i < length; ++i) { + name = userProperties[i]; + attribute = styleableShaderAttributes[name]; + if (!defined(attribute)) { + throw new RuntimeError('Style references a property "' + name + '" that does not exist or is not styleable.'); + } + + var componentCount = attribute.componentCount; + var attributeName = 'czm_tiles3d_style_' + name; + var attributeType; + if (componentCount === 1) { + attributeType = 'float'; + } else { + attributeType = 'vec' + componentCount; + } + + attributeDeclarations += 'attribute ' + attributeType + ' ' + attributeName + '; \n'; + attributeLocations[attributeName] = attribute.location; + } + + 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 (hasNormals) { + 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 (hasNormals) { + 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'; + } + } 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); \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 (hasNormals) { + vs += ' normal = czm_normal * normal; \n' + + ' float diffuseStrength = czm_getLambertDiffuse(czm_sunDirectionEC, normal); \n' + + ' diffuseStrength = max(diffuseStrength, 0.4); \n' + // Apply some ambient lighting + ' color.xyz *= diffuseStrength; \n'; + } + + vs += ' v_color = color; \n' + + ' gl_Position = czm_modelViewProjection * vec4(position, 1.0); \n'; + + if (hasNormals && backFaceCulling) { + vs += ' float visible = step(-normal.z, 0.0); \n' + + ' gl_Position *= visible; \n' + + ' gl_PointSize *= visible; \n'; + } + + if (hasShowStyle) { + vs += ' gl_Position *= show; \n' + + ' gl_PointSize *= show; \n'; + } + + vs += '} \n'; + + var fs = 'varying vec4 v_color; \n'; + + if (hasClippedContent) { + fs += 'uniform 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 = v_color; \n'; + + if (hasClippedContent) { + fs += getClipAndStyleCode('u_clippingPlanes', 'u_clippingPlanesMatrix', 'u_clippingPlanesEdgeStyle'); + } + + fs += '} \n'; + + var drawVS = vs; + var drawFS = fs; + + if (defined(pointCloud._vertexShaderLoaded)) { + drawVS = pointCloud._vertexShaderLoaded(vs); + } + + if (defined(pointCloud._fragmentShaderLoaded)) { + drawFS = pointCloud._fragmentShaderLoaded(fs); + } + + var pickVS = vs; + var pickFS = fs; + + if (defined(pointCloud._pickVertexShaderLoaded)) { + pickVS = pointCloud._pickVertexShaderLoaded(vs); + } + + if (defined(pointCloud._pickFragmentShaderLoaded)) { + pickFS = pointCloud._pickFragmentShaderLoaded(fs); + } + + var drawCommand = pointCloud._drawCommand; + if (defined(drawCommand.shaderProgram)) { + // Destroy the old shader + drawCommand.shaderProgram.destroy(); + } + drawCommand.shaderProgram = ShaderProgram.fromCache({ + context : context, + vertexShaderSource : drawVS, + fragmentShaderSource : drawFS, + attributeLocations : attributeLocations + }); + + var pickCommand = pointCloud._pickCommand; + if (defined(pickCommand.shaderProgram)) { + // Destroy the old shader + pickCommand.shaderProgram.destroy(); + } + pickCommand.shaderProgram = ShaderProgram.fromCache({ + context : context, + vertexShaderSource : pickVS, + fragmentShaderSource : pickFS, + 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.'); + } + } + + var scratchComputedTranslation = new Cartesian4(); + var scratchComputedMatrixIn2D = new Matrix4(); + var scratchModelMatrix = new Matrix4(); + + 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; + if (defined(decodedPositions) && pointCloud._isQuantizedDraco) { + var quantization = result.POSITION.data.quantization; + var scale = quantization.range / (1 << quantization.quantizationBits); + pointCloud._quantizedVolumeScale = Cartesian3.fromElements(scale, scale, scale); + pointCloud._quantizedVolumeOffset = Cartesian3.unpack(quantization.minValues); + pointCloud._quantizedRange = (1 << quantization.quantizationBits) - 1.0; + } + if (defined(decodedNormals) && pointCloud._isOctEncodedDraco) { + pointCloud._octEncodedRange = (1 << result.NORMAL.data.quantization.quantizationBits) - 1.0; + } + 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; + } + + 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._readyPromise.resolve(this); + this._parsedContent = undefined; // Unload + } + + if (modelMatrixDirty) { + Matrix4.clone(this.modelMatrix, this._modelMatrix); + var modelMatrix = Matrix4.clone(this._modelMatrix, scratchModelMatrix); + + 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); + } else { + var center = this.boundingVolume.center; + var to2D = Transforms.wgs84To2DModelMatrix(projection, center, scratchComputedMatrixIn2D); + Matrix4.multiply(to2D, modelMatrix, modelMatrix); + } + } + + Matrix4.clone(modelMatrix, this._drawCommand.modelMatrix); + Matrix4.clone(modelMatrix, this._pickCommand.modelMatrix); + + this._drawCommand.boundingVolume = this.boundingVolume; + this._pickCommand.boundingVolume = this.boundingVolume; + } + + if (this.clippingPlanesDirty) { + 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._style !== this.style || this.styleDirty) { + this._style = this.style; + 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) { + commandList.push(this._drawCommand); + } + if (passes.pick) { + commandList.push(this._pickCommand); + } + }; + + PointCloud.prototype.isDestroyed = function() { + return false; + }; + + PointCloud.prototype.destroy = function() { + var command = this._drawCommand; + var pickCommand = this._pickCommand; + if (defined(command)) { + command.vertexArray = command.vertexArray && command.vertexArray.destroy(); + command.shaderProgram = command.shaderProgram && command.shaderProgram.destroy(); + pickCommand.shaderProgram = pickCommand.shaderProgram && pickCommand.shaderProgram.destroy(); + } + return destroyObject(this); + }; + + return PointCloud; +}); diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 1baf4ea95e86..9c41565e15df 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -1,89 +1,35 @@ define([ - '../Core/arraySlice', - '../Core/Cartesian2', - '../Core/Cartesian3', - '../Core/Cartesian4', '../Core/Color', '../Core/combine', - '../Core/ComponentDatatype', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', '../Core/destroyObject', '../Core/DeveloperError', '../Core/FeatureDetection', - '../Core/getStringFromTypedArray', '../Core/Math', - '../Core/Matrix3', - '../Core/Matrix4', - '../Core/oneTimeWarning', - '../Core/OrthographicFrustum', - '../Core/Plane', - '../Core/PrimitiveType', - '../Core/RuntimeError', - '../Core/Transforms', - '../Renderer/Buffer', - '../Renderer/BufferUsage', - '../Renderer/DrawCommand', '../Renderer/Pass', - '../Renderer/RenderState', - '../Renderer/ShaderProgram', '../Renderer/ShaderSource', - '../Renderer/VertexArray', - '../ThirdParty/when', - './BlendingState', './Cesium3DTileBatchTable', './Cesium3DTileFeature', - './Cesium3DTileFeatureTable', - './ClippingPlaneCollection', - './DracoLoader', - './getClipAndStyleCode', - './getClippingFunction', - './SceneMode', - './ShadowMode' + './PointCloud', + './SceneMode' ], function( - arraySlice, - Cartesian2, - Cartesian3, - Cartesian4, Color, combine, - ComponentDatatype, defaultValue, defined, defineProperties, destroyObject, DeveloperError, FeatureDetection, - getStringFromTypedArray, CesiumMath, - Matrix3, - Matrix4, - oneTimeWarning, - OrthographicFrustum, - Plane, - PrimitiveType, - RuntimeError, - Transforms, - Buffer, - BufferUsage, - DrawCommand, Pass, - RenderState, - ShaderProgram, ShaderSource, - VertexArray, - when, - BlendingState, Cesium3DTileBatchTable, Cesium3DTileFeature, - Cesium3DTileFeatureTable, - ClippingPlaneCollection, - DracoLoader, - getClipAndStyleCode, - getClippingFunction, - SceneMode, - ShadowMode) { + PointCloud, + SceneMode) { 'use strict'; // Bail out if the browser doesn't support typed arrays, to prevent the setup function @@ -92,13 +38,6 @@ define([ return {}; } - var DecodingState = { - NEEDS_DECODE : 0, - DECODING : 1, - READY : 2, - FAILED : 3 - }; - /** * Represents the contents of a * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/TileFormats/PointCloud/README.md|Points} @@ -116,69 +55,25 @@ define([ this._tileset = tileset; this._tile = tile; this._resource = resource; - - // Hold onto the payload until the render resources are created - this._parsedContent = undefined; - - this._drawCommand = undefined; - this._pickCommand = undefined; this._pickId = undefined; // Only defined when batchTable is undefined - this._isTranslucent = false; - this._styleTranslucent = false; - this._constantColor = Color.clone(Color.DARKGRAY); - this._rtcCenter = undefined; this._batchTable = undefined; // Used when feature table contains BATCH_ID semantic - - // 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; - - this._opaqueRenderState = undefined; - this._translucentRenderState = undefined; - - this._highlightColor = Color.clone(Color.WHITE); - this._pointSize = 1.0; - this._quantizedVolumeScale = undefined; - this._quantizedVolumeOffset = undefined; - - this._modelMatrix = Matrix4.clone(Matrix4.IDENTITY); - this._mode = undefined; - - this._readyPromise = when.defer(); - this._pointsLength = 0; - this._geometryByteLength = 0; - + this._styleDirty = false; this._features = undefined; - this._modelViewMatrix = Matrix4.clone(Matrix4.IDENTITY); - this.featurePropertiesDirty = false; - // Options for geometric error based attenuation - this._attenuation = false; - this._geometricErrorScale = undefined; - this._maximumAttenuation = undefined; - this._baseResolution = undefined; - this._baseResolutionApproximation = undefined; - - initialize(this, arrayBuffer, byteOffset); + this._pointCloud = new PointCloud({ + arrayBuffer : arrayBuffer, + byteOffset : byteOffset, + opaquePass : Pass.CESIUM_3D_TILE, + vertexShaderLoaded : getVertexShaderLoaded(this), + fragmentShaderLoaded : getFragmentShaderLoaded(this), + pickVertexShaderLoaded : getPickVertexShaderLoaded(this), + pickFragmentShaderLoaded : getPickFragmentShaderLoaded(this), + uniformMapLoaded : getUniformMapLoaded(this), + pickUniformMapLoaded : getPickUniformMapLoaded(this), + batchTableLoaded : getBatchTableLoaded(this) + }); } defineProperties(PointCloud3DTileContent.prototype, { @@ -193,7 +88,7 @@ define([ pointsLength : { get : function() { - return this._pointsLength; + return this._pointCloud.pointsLength; } }, @@ -205,7 +100,7 @@ define([ geometryByteLength : { get : function() { - return this._geometryByteLength; + return this._pointCloud.geometryByteLength; } }, @@ -232,7 +127,7 @@ define([ readyPromise : { get : function() { - return this._readyPromise.promise; + return this._pointCloud.readyPromise; } }, @@ -261,992 +156,84 @@ define([ } }); - var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT; - - function initialize(content, arrayBuffer, byteOffset) { - byteOffset = defaultValue(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 featureTableString = getStringFromTypedArray(uint8Array, byteOffset, featureTableJsonByteLength); - var featureTableJson = JSON.parse(featureTableString); - 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 - var batchTableString = getStringFromTypedArray(uint8Array, byteOffset, batchTableJsonByteLength); - batchTableJson = JSON.parse(batchTableString); - 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)) { - content._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 isQuantizedDraco = false; - var isOctEncodedDraco = 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); - isQuantizedDraco = hasPositions && content._dequantizeInShader; - isOctEncodedDraco = hasNormals && content._dequantizeInShader; - content._decodingState = DecodingState.NEEDS_DECODE; - } - - 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.'); - } - content._quantizedVolumeScale = Cartesian3.unpack(quantizedVolumeScale); - - 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.'); - } - content._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); - content._constantColor = Color.fromBytes(constantRGBA[0], constantRGBA[1], constantRGBA[2], constantRGBA[3], content._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); - } - content._batchTable = new Cesium3DTileBatchTable(content, 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); - } - - content._parsedContent = { - positions : positions, - colors : colors, - normals : normals, - batchIds : batchIds, - styleableProperties : styleableProperties, - draco : { - buffer : dracoBuffer, - featureTableProperties : dracoFeatureTableProperties, - batchTableProperties : dracoBatchTableProperties, - properties : combine(dracoFeatureTableProperties, dracoBatchTableProperties), - dequantizeInShader : content._dequantizeInShader + function getVertexShaderLoaded(content) { + return function(vs) { + if (defined(content._batchTable)) { + return content._batchTable.getVertexShaderCallback(false, 'a_batchId', undefined)(vs); } + return vs; }; - content._pointsLength = pointsLength; - content._isQuantized = isQuantized; - content._isQuantizedDraco = isQuantizedDraco; - content._isOctEncoded16P = isOctEncoded16P; - content._isOctEncodedDraco = isOctEncodedDraco; - content._isRGB565 = isRGB565; - content._isTranslucent = isTranslucent; - content._hasColors = hasColors; - content._hasNormals = hasNormals; - content._hasBatchIds = hasBatchIds; - - // Compute an approximation for base resolution in case it isn't given. - // Assume a uniform distribution of points in cubical cells throughout the - // bounding sphere around the tile. - // Typical use case is leaves, where lower estimates of interpoint distance might - // lead to underattenuation. - var sphereVolume = content._tile.contentBoundingVolume.boundingSphere.volume(); - content._baseResolutionApproximation = CesiumMath.cbrt(sphereVolume / pointsLength); } - function prepareStyleableProperties(styleableProperties) { - // WebGL does not support UNSIGNED_INT, INT, or DOUBLE vertex attributes. Convert these to FLOAT. - for (var name in styleableProperties) { - if (styleableProperties.hasOwnProperty(name)) { - var property = styleableProperties[name]; - var typedArray = property.typedArray; - 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.'); - property.typedArray = new Float32Array(typedArray); - } + function getFragmentShaderLoaded(content) { + return function(fs) { + if (defined(content._batchTable)) { + return content._batchTable.getFragmentShaderCallback(false, undefined)(fs); } - } + return fs; + }; } - var scratchPointSizeAndTilesetTimeAndGeometricErrorAndDepthMultiplier = new Cartesian4(); - var scratchQuantizedVolumeScaleAndOctEncodedRange = new Cartesian4(); - - var positionLocation = 0; - var colorLocation = 1; - var normalLocation = 2; - var batchIdLocation = 3; - var numberOfAttributes = 4; - - var scratchClippingPlaneMatrix = new Matrix4(); - function createResources(content, frameState) { - var context = frameState.context; - var parsedContent = content._parsedContent; - var pointsLength = content._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 = content._isQuantized; - var isQuantizedDraco = content._isQuantizedDraco; - var isOctEncoded16P = content._isOctEncoded16P; - var isOctEncodedDraco = content._isOctEncodedDraco; - var quantizedRange = content._quantizedRange; - var octEncodedRange = content._octEncodedRange; - var isRGB565 = content._isRGB565; - var isTranslucent = content._isTranslucent; - var hasColors = content._hasColors; - var hasNormals = content._hasNormals; - var hasBatchIds = content._hasBatchIds; - - var batchTable = content._batchTable; - var hasBatchTable = defined(batchTable); - - var componentsPerAttribute; - var componentDatatype; - var normalize; - - var styleableVertexAttributes = []; - var styleableShaderAttributes = {}; - content._styleableShaderAttributes = styleableShaderAttributes; - - if (hasStyleableProperties) { - prepareStyleableProperties(styleableProperties); - var attributeLocation = numberOfAttributes; - - for (var name in styleableProperties) { - if (styleableProperties.hasOwnProperty(name)) { - var property = styleableProperties[name]; - var typedArray = property.typedArray; - componentsPerAttribute = property.componentCount; - componentDatatype = ComponentDatatype.fromTypedArray(typedArray); - - var vertexBuffer = Buffer.createVertexBuffer({ - context : context, - typedArray : property.typedArray, - usage : BufferUsage.STATIC_DRAW - }); - - content._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; - } + function getPickVertexShaderLoaded(content) { + return function(vs) { + if (defined(content._batchTable)) { + return content._batchTable.getPickVertexShaderCallback('a_batchId')(vs); } - } - var uniformMap = { - u_pointSizeAndTilesetTimeAndGeometricErrorAndDepthMultiplier : function() { - var scratch = scratchPointSizeAndTilesetTimeAndGeometricErrorAndDepthMultiplier; - scratch.x = content._attenuation ? content._maximumAttenuation : content._pointSize; - scratch.y = content._tileset.timeSinceLoad; - - if (content._attenuation) { - var geometricError = content.tile.geometricError; - if (geometricError === 0) { - geometricError = defined(content._baseResolution) ? content._baseResolution : content._baseResolutionApproximation; - } - 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 = geometricError * content._geometricErrorScale; - scratch.w = depthMultiplier; - } - - return scratch; - }, - u_highlightColor : function() { - return content._highlightColor; - }, - u_constantColor : function() { - return content._constantColor; - }, - u_clippingPlanes : function() { - var clippingPlanes = content._tileset.clippingPlanes; - return (!defined(clippingPlanes) || !clippingPlanes.enabled) ? context.defaultTexture : clippingPlanes.texture; - }, - u_clippingPlanesEdgeStyle : function() { - var clippingPlanes = content._tileset.clippingPlanes; - if (!defined(clippingPlanes)) { - return Color.WHITE.withAlpha(0.0); - } + return vs; + }; + } - var style = Color.clone(clippingPlanes.edgeColor); - style.alpha = clippingPlanes.edgeWidth; - return style; - }, - u_clippingPlanesMatrix : function() { - var clippingPlanes = content._tileset.clippingPlanes; - if (!defined(clippingPlanes)) { - return Matrix4.IDENTITY; - } - return Matrix4.multiply(content._modelViewMatrix, clippingPlanes.modelMatrix, scratchClippingPlaneMatrix); + function getPickFragmentShaderLoaded(content) { + return function(fs) { + if (defined(content._batchTable)) { + return content._batchTable.getPickFragmentShaderCallback()(fs); } + return ShaderSource.createPickFragmentShaderSource(fs, 'uniform'); }; + } - if (isQuantized || isQuantizedDraco || isOctEncodedDraco) { - uniformMap = combine(uniformMap, { - u_quantizedVolumeScaleAndOctEncodedRange : function() { - var scratch = scratchQuantizedVolumeScaleAndOctEncodedRange; - if (defined(content._quantizedVolumeScale)) { - Cartesian3.clone(content._quantizedVolumeScale, scratch); - } - scratch.w = content._octEncodedRange; - return scratch; - } - }); - } - - var positionsVertexBuffer = Buffer.createVertexBuffer({ - context : context, - typedArray : positions, - usage : BufferUsage.STATIC_DRAW - }); - content._geometryByteLength += positionsVertexBuffer.sizeInBytes; - - var colorsVertexBuffer; - if (hasColors) { - colorsVertexBuffer = Buffer.createVertexBuffer({ - context : context, - typedArray : colors, - usage : BufferUsage.STATIC_DRAW - }); - content._geometryByteLength += colorsVertexBuffer.sizeInBytes; - } - - var normalsVertexBuffer; - if (hasNormals) { - normalsVertexBuffer = Buffer.createVertexBuffer({ - context : context, - typedArray : normals, - usage : BufferUsage.STATIC_DRAW - }); - content._geometryByteLength += normalsVertexBuffer.sizeInBytes; - } - - var batchIdsVertexBuffer; - if (hasBatchIds) { - batchIdsVertexBuffer = Buffer.createVertexBuffer({ - context : context, - typedArray : batchIds, - usage : BufferUsage.STATIC_DRAW - }); - content._geometryByteLength += batchIdsVertexBuffer.sizeInBytes; - } - - var attributes = []; - - if (isQuantized) { - componentDatatype = ComponentDatatype.UNSIGNED_SHORT; - normalize = true; // Convert position to 0 to 1 before entering the shader - } else if (isQuantizedDraco) { - componentDatatype = (quantizedRange <= 255) ? ComponentDatatype.UNSIGNED_BYTE : ComponentDatatype.UNSIGNED_SHORT; - normalize = false; // Normalization is done in the shader based on quantizationBits - } else { - componentDatatype = ComponentDatatype.FLOAT; - normalize = false; - } - - attributes.push({ - index : positionLocation, - vertexBuffer : positionsVertexBuffer, - componentsPerAttribute : 3, - componentDatatype : componentDatatype, - normalize : normalize, - offsetInBytes : 0, - strideInBytes : 0 - }); - - 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 - }); + function getUniformMapLoaded(content) { + return function(uniformMap) { + if (defined(content._batchTable)) { + return content._batchTable.getUniformMapCallback()(uniformMap); } - } + return uniformMap; + }; + } - 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; + function getPickUniformMapLoaded(content) { + return function(uniformMap) { + if (defined(content._batchTable)) { + return content._batchTable.getPickUniformMapCallback()(uniformMap); } - 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 drawUniformMap = uniformMap; - - if (hasBatchTable) { - drawUniformMap = batchTable.getUniformMapCallback()(uniformMap); - } - - var pickUniformMap; - - if (hasBatchTable) { - pickUniformMap = batchTable.getPickUniformMapCallback()(uniformMap); - } else { - content._pickId = context.createPickId({ - primitive : content._tileset, - content : content - }); - - pickUniformMap = combine(uniformMap, { + return combine(uniformMap, { czm_pickColor : function() { return content._pickId.color; } }); - } - - content._opaqueRenderState = RenderState.fromCache({ - depthTest : { - enabled : true - } - }); - - content._translucentRenderState = RenderState.fromCache({ - depthTest : { - enabled : true - }, - depthMask : false, - blending : BlendingState.ALPHA_BLEND - }); - - content._drawCommand = new DrawCommand({ - boundingVolume : undefined, // Updated in update - cull : false, // Already culled by 3D Tiles - modelMatrix : new Matrix4(), - primitiveType : PrimitiveType.POINTS, - vertexArray : vertexArray, - count : pointsLength, - shaderProgram : undefined, // Updated in createShaders - uniformMap : drawUniformMap, - renderState : isTranslucent ? content._translucentRenderState : content._opaqueRenderState, - pass : isTranslucent ? Pass.TRANSLUCENT : Pass.CESIUM_3D_TILE, - owner : content, - castShadows : false, - receiveShadows : false - }); - - content._pickCommand = new DrawCommand({ - boundingVolume : undefined, // Updated in update - cull : false, // Already culled by 3D Tiles - modelMatrix : new Matrix4(), - primitiveType : PrimitiveType.POINTS, - vertexArray : vertexArray, - count : pointsLength, - shaderProgram : undefined, // Updated in createShaders - uniformMap : pickUniformMap, - renderState : isTranslucent ? content._translucentRenderState : content._opaqueRenderState, - pass : isTranslucent ? Pass.TRANSLUCENT : Pass.CESIUM_3D_TILE, - owner : content - }); - } - - var defaultProperties = ['POSITION', 'COLOR', 'NORMAL', 'POSITION_ABSOLUTE']; - - function getStyleableProperties(source, properties) { - // Get all the properties used by this style - var regex = /czm_tiles3d_style_(\w+)/g; - var matches = regex.exec(source); - while (matches !== null) { - var name = matches[1]; - if (properties.indexOf(name) === -1) { - properties.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; - } - } - } - - function modifyStyleFunction(source) { - // Replace occurrences of czm_tiles3d_style_DEFAULTPROPERTY - var length = defaultProperties.length; - for (var i = 0; i < length; ++i) { - var property = defaultProperties[i]; - var styleName = 'czm_tiles3d_style_' + property; - var replaceName = property.toLowerCase(); - source = source.replace(new RegExp(styleName + '(\\W)', 'g'), replaceName + '$1'); - } - - // Edit the function header to accept the point position, color, and normal - return source.replace('()', '(vec3 position, vec3 position_absolute, vec4 color, vec3 normal)'); + }; } - function createShaders(content, frameState, style) { - var i; - var name; - var attribute; - - var context = frameState.context; - var batchTable = content._batchTable; - var hasBatchTable = defined(batchTable); - var hasStyle = defined(style); - var isQuantized = content._isQuantized; - var isQuantizedDraco = content._isQuantizedDraco; - var isOctEncoded16P = content._isOctEncoded16P; - var isOctEncodedDraco = content._isOctEncodedDraco; - var isRGB565 = content._isRGB565; - var isTranslucent = content._isTranslucent; - var hasColors = content._hasColors; - var hasNormals = content._hasNormals; - var hasBatchIds = content._hasBatchIds; - var backFaceCulling = content._backFaceCulling; - var vertexArray = content._drawCommand.vertexArray; - var clippingPlanes = content._tileset.clippingPlanes; - var attenuation = content._attenuation; - - var colorStyleFunction; - var showStyleFunction; - var pointSizeStyleFunction; - var styleTranslucent = isTranslucent; - - if (hasBatchTable) { - // Styling is handled in the batch table - hasStyle = false; - } - - if (hasStyle) { - var shaderState = { - translucent : false - }; - colorStyleFunction = style.getColorShaderFunction('getColorFromStyle', 'czm_tiles3d_style_', shaderState); - showStyleFunction = style.getShowShaderFunction('getShowFromStyle', 'czm_tiles3d_style_', shaderState); - pointSizeStyleFunction = style.getPointSizeShaderFunction('getPointSizeFromStyle', 'czm_tiles3d_style_', shaderState); - if (defined(colorStyleFunction) && shaderState.translucent) { - styleTranslucent = true; - } - } - - content._styleTranslucent = styleTranslucent; - - var hasColorStyle = defined(colorStyleFunction); - var hasShowStyle = defined(showStyleFunction); - var hasPointSizeStyle = defined(pointSizeStyleFunction); - var hasClippedContent = defined(clippingPlanes) && clippingPlanes.enabled && content._tile._isClipped; - - // Get the properties in use by the style - var styleableProperties = []; - - if (hasColorStyle) { - getStyleableProperties(colorStyleFunction, styleableProperties); - colorStyleFunction = modifyStyleFunction(colorStyleFunction); - } - if (hasShowStyle) { - getStyleableProperties(showStyleFunction, styleableProperties); - showStyleFunction = modifyStyleFunction(showStyleFunction); - } - if (hasPointSizeStyle) { - getStyleableProperties(pointSizeStyleFunction, styleableProperties); - pointSizeStyleFunction = modifyStyleFunction(pointSizeStyleFunction); - } - - var usesColorSemantic = (styleableProperties.indexOf('COLOR') >= 0); - var usesNormalSemantic = (styleableProperties.indexOf('NORMAL') >= 0); - - // Split default properties from user properties - var userProperties = styleableProperties.filter(function(property) { return defaultProperties.indexOf(property) === -1; }); - - 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 - var styleableShaderAttributes = content._styleableShaderAttributes; - for (name in styleableShaderAttributes) { - if (styleableShaderAttributes.hasOwnProperty(name)) { - attribute = styleableShaderAttributes[name]; - var enabled = (userProperties.indexOf(name) >= 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 attributeLocations = { - a_position : positionLocation + function getBatchTableLoaded(content) { + return function(batchLength, batchTableJson, batchTableBinary) { + content._batchTable = new Cesium3DTileBatchTable(content, batchLength, batchTableJson, batchTableBinary); }; - if (usesColors) { - attributeLocations.a_color = colorLocation; - } - if (hasNormals) { - attributeLocations.a_normal = normalLocation; - } - if (hasBatchIds) { - attributeLocations.a_batchId = batchIdLocation; - } - - var attributeDeclarations = ''; - - var length = userProperties.length; - for (i = 0; i < length; ++i) { - name = userProperties[i]; - attribute = styleableShaderAttributes[name]; - if (!defined(attribute)) { - throw new RuntimeError('Style references a property "' + name + '" that does not exist or is not styleable.'); - } - - var componentCount = attribute.componentCount; - var attributeName = 'czm_tiles3d_style_' + name; - var attributeType; - if (componentCount === 1) { - attributeType = 'float'; - } else { - attributeType = 'vec' + componentCount; - } - - attributeDeclarations += 'attribute ' + attributeType + ' ' + attributeName + '; \n'; - attributeLocations[attributeName] = attribute.location; - } - - var vs = 'attribute vec3 a_position; \n' + - 'varying vec4 v_color; \n' + - 'uniform vec4 u_pointSizeAndTilesetTimeAndGeometricErrorAndDepthMultiplier; \n' + - 'uniform vec4 u_constantColor; \n' + - 'uniform vec4 u_highlightColor; \n'; - vs += 'float u_pointSize; \n' + - 'float u_tilesetTime; \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 (hasNormals) { - 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_pointSizeAndTilesetTimeAndGeometricErrorAndDepthMultiplier.x; \n' + - ' u_tilesetTime = u_pointSizeAndTilesetTimeAndGeometricErrorAndDepthMultiplier.y; \n'; - - if (attenuation) { - vs += ' u_geometricError = u_pointSizeAndTilesetTimeAndGeometricErrorAndDepthMultiplier.z; \n' + - ' u_depthMultiplier = u_pointSizeAndTilesetTimeAndGeometricErrorAndDepthMultiplier.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'; + function getGeometricError(content) { + var pointCloudShading = content._tileset.pointCloudShading; + var sphereVolume = content._tile.contentBoundingVolume.boundingSphere.volume(); + var baseResolutionApproximation = CesiumMath.cbrt(sphereVolume / content.pointsLength); - if (hasNormals) { - 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'; + var geometricError = content._tile.geometricError; + if (geometricError === 0) { + if (defined(pointCloudShading) && defined(pointCloudShading.baseResolution)) { + geometricError = pointCloudShading.baseResolution; } else { - vs += ' vec3 normal = a_normal; \n'; + geometricError = baseResolutionApproximation; } - } 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); \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 (hasNormals) { - vs += ' normal = czm_normal * normal; \n' + - ' float diffuseStrength = czm_getLambertDiffuse(czm_sunDirectionEC, normal); \n' + - ' diffuseStrength = max(diffuseStrength, 0.4); \n' + // Apply some ambient lighting - ' color.xyz *= diffuseStrength; \n'; - } - - vs += ' v_color = color; \n' + - ' gl_Position = czm_modelViewProjection * vec4(position, 1.0); \n'; - - if (hasNormals && backFaceCulling) { - vs += ' float visible = step(-normal.z, 0.0); \n' + - ' gl_Position *= visible; \n' + - ' gl_PointSize *= visible; \n'; - } - - if (hasShowStyle) { - vs += ' gl_Position *= show; \n' + - ' gl_PointSize *= show; \n'; - } - - vs += '} \n'; - - var fs = 'varying vec4 v_color; \n'; - - if (hasClippedContent) { - fs += 'uniform 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 = v_color; \n'; - - if (hasClippedContent) { - fs += getClipAndStyleCode('u_clippingPlanes', 'u_clippingPlanesMatrix', 'u_clippingPlanesEdgeStyle'); - } - - fs += '} \n'; - - var drawVS = vs; - var drawFS = fs; - - if (hasBatchTable) { - // Batched points always use the HIGHLIGHT color blend mode - drawVS = batchTable.getVertexShaderCallback(false, 'a_batchId', undefined)(drawVS); - drawFS = batchTable.getFragmentShaderCallback(false, undefined)(drawFS); - } - - var pickVS = vs; - var pickFS = fs; - - if (hasBatchTable) { - pickVS = batchTable.getPickVertexShaderCallback('a_batchId')(pickVS); - pickFS = batchTable.getPickFragmentShaderCallback()(pickFS); - } else { - pickFS = ShaderSource.createPickFragmentShaderSource(pickFS, 'uniform'); - } - - var drawCommand = content._drawCommand; - if (defined(drawCommand.shaderProgram)) { - // Destroy the old shader - drawCommand.shaderProgram.destroy(); - } - drawCommand.shaderProgram = ShaderProgram.fromCache({ - context : context, - vertexShaderSource : drawVS, - fragmentShaderSource : drawFS, - attributeLocations : attributeLocations - }); - - var pickCommand = content._pickCommand; - if (defined(pickCommand.shaderProgram)) { - // Destroy the old shader - pickCommand.shaderProgram.destroy(); - } - pickCommand.shaderProgram = ShaderProgram.fromCache({ - context : context, - vertexShaderSource : pickVS, - fragmentShaderSource : pickFS, - 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.'); } + return geometricError; } function createFeatures(content) { @@ -1294,186 +281,61 @@ define([ }; PointCloud3DTileContent.prototype.applyDebugSettings = function(enabled, color) { - this._highlightColor = enabled ? color : Color.WHITE; + this._pointCloud.color = enabled ? color : Color.WHITE; }; PointCloud3DTileContent.prototype.applyStyle = function(frameState, style) { if (defined(this._batchTable)) { this._batchTable.applyStyle(frameState, style); } else { - createShaders(this, frameState, style); + this._styleDirty = true; } }; - var scratchComputedTranslation = new Cartesian4(); - var scratchComputedMatrixIn2D = new Matrix4(); - var scratchModelMatrix = new Matrix4(); - - function decodeDraco(content, context) { - if (content._decodingState === DecodingState.READY) { - return false; - } - if (content._decodingState === DecodingState.NEEDS_DECODE) { - var parsedContent = content._parsedContent; - var draco = parsedContent.draco; - var decodePromise = DracoLoader.decodePointCloud(draco, context); - if (defined(decodePromise)) { - content._decodingState = DecodingState.DECODING; - decodePromise.then(function(result) { - content._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; - if (defined(decodedPositions) && content._isQuantizedDraco) { - var quantization = result.POSITION.data.quantization; - var scale = quantization.range / (1 << quantization.quantizationBits); - content._quantizedVolumeScale = Cartesian3.fromElements(scale, scale, scale); - content._quantizedVolumeOffset = Cartesian3.unpack(quantization.minValues); - content._quantizedRange = (1 << quantization.quantizationBits) - 1.0; - } - if (defined(decodedNormals) && content._isOctEncodedDraco) { - content._octEncodedRange = (1 << result.NORMAL.data.quantization.quantizationBits) - 1.0; - } - 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) { - content._decodingState = DecodingState.FAILED; - content._readyPromise.reject(error); - }); - } - } - return true; - } - PointCloud3DTileContent.prototype.update = function(tileset, frameState) { - var context = frameState.context; - var decoding = decodeDraco(this, context); - if (decoding) { - return; - } - - var modelMatrix = this._tile.computedTransform; - var modelMatrixChanged = !Matrix4.equals(this._modelMatrix, modelMatrix); - var updateModelMatrix = modelMatrixChanged || this._mode !== frameState.mode; - - this._mode = frameState.mode; - - if (!defined(this._drawCommand)) { - createResources(this, frameState); - createShaders(this, frameState, tileset.style); - updateModelMatrix = true; - - this._readyPromise.resolve(this); - this._parsedContent = undefined; // Unload - } - - // update for clipping planes - if (this._tile.clippingPlanesDirty) { - createShaders(this, frameState, tileset.style); - } - - var clippingPlanes = this._tileset.clippingPlanes; - var clippingEnabled = defined(clippingPlanes) && clippingPlanes.enabled && this._tile._isClipped; - - if (clippingEnabled) { - Matrix4.multiply(context.uniformState.view3D, modelMatrix, this._modelViewMatrix); - } - - // Update attenuation - var pointCloudShading = this._tileset.pointCloudShading; - if (defined(pointCloudShading)) { - var formerAttenuation = this._attenuation; - this._attenuation = pointCloudShading.attenuation; - this._geometricErrorScale = pointCloudShading.geometricErrorScale; - this._maximumAttenuation = defined(pointCloudShading.maximumAttenuation) ? pointCloudShading.maximumAttenuation : tileset.maximumScreenSpaceError; - this._baseResolution = pointCloudShading.baseResolution; - if (this._attenuation !== formerAttenuation) { - createShaders(this, frameState, tileset.style); - } - } - - if (updateModelMatrix) { - Matrix4.clone(modelMatrix, this._modelMatrix); - modelMatrix = Matrix4.clone(modelMatrix, scratchModelMatrix); - - 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); - } else { - var center = this._tile.boundingSphere.center; - var to2D = Transforms.wgs84To2DModelMatrix(projection, center, scratchComputedMatrixIn2D); - Matrix4.multiply(to2D, modelMatrix, modelMatrix); - } - } - - Matrix4.clone(modelMatrix, this._drawCommand.modelMatrix); - Matrix4.clone(modelMatrix, this._pickCommand.modelMatrix); - - var boundingVolume; - if (defined(this._tile._contentBoundingVolume)) { - boundingVolume = this._mode === SceneMode.SCENE3D ? this._tile._contentBoundingVolume.boundingSphere : this._tile._contentBoundingVolume2D.boundingSphere; - } else { - boundingVolume = this._mode === SceneMode.SCENE3D ? this._tile._boundingVolume.boundingSphere : this._tile._boundingVolume2D.boundingSphere; - } - - this._drawCommand.boundingVolume = boundingVolume; - this._pickCommand.boundingVolume = boundingVolume; - } - - this._drawCommand.castShadows = ShadowMode.castShadows(tileset.shadows); - this._drawCommand.receiveShadows = ShadowMode.receiveShadows(tileset.shadows); - - if (this.backFaceCulling !== this._backFaceCulling) { - this._backFaceCulling = this.backFaceCulling; - createShaders(this, frameState, tileset.style); + var pointCloud = this._pointCloud; + var pointCloudShading = tileset.pointCloudShading; + var tile = this._tile; + var batchTable = this._batchTable; + var mode = frameState.mode; + var clippingPlanes = tileset.clippingPlanes; + + if (!defined(this._pickId) && !defined(batchTable)) { + this._pickId = frameState.context.createPickId({ + primitive : tileset, + content : this + }); } - // 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 : Pass.CESIUM_3D_TILE; - - if (defined(this._batchTable)) { - this._batchTable.update(tileset, frameState); + if (defined(batchTable)) { + batchTable.update(tileset, frameState); } - var commandList = frameState.commandList; - - var passes = frameState.passes; - if (passes.render) { - commandList.push(this._drawCommand); - } - if (passes.pick) { - commandList.push(this._pickCommand); - } + var boundingVolume; + if (defined(tile._contentBoundingVolume)) { + boundingVolume = mode === SceneMode.SCENE3D ? tile._contentBoundingVolume.boundingSphere : tile._contentBoundingVolume2D.boundingSphere; + } else { + boundingVolume = mode === SceneMode.SCENE3D ? tile._boundingVolume.boundingSphere : tile._boundingVolume2D.boundingSphere; + } + + var styleDirty = this._styleDirty; + this._styleDirty = false; + + pointCloud.style = defined(batchTable) ? undefined : tileset.style; + pointCloud.styleDirty = styleDirty; + pointCloud.modelMatrix = tile.computedTransform; + pointCloud.time = tileset.timeSinceLoad; + pointCloud.shadows = tileset.shadows; + pointCloud.boundingVolume = boundingVolume; + pointCloud.clippingPlanes = clippingPlanes; + pointCloud.isClipped = defined(clippingPlanes) && clippingPlanes.enabled && tile._isClipped; + pointCloud.clippingPlanesDirty = tile.clippingPlanesDirty; + pointCloud.attenuation = defined(pointCloudShading) ? pointCloudShading.attenuation : false; + pointCloud.geometricError = getGeometricError(this); + pointCloud.geometricErrorScale = defined(pointCloudShading) ? pointCloudShading.geometricErrorScale : 1.0; + pointCloud.maximumAttenuation = (defined(pointCloudShading) && defined(pointCloudShading.maximumAttenuation)) ? pointCloudShading.maximumAttenuation : tileset.maximumScreenSpaceError; + + pointCloud.update(frameState); }; PointCloud3DTileContent.prototype.isDestroyed = function() { @@ -1481,13 +343,7 @@ define([ }; PointCloud3DTileContent.prototype.destroy = function() { - var command = this._drawCommand; - var pickCommand = this._pickCommand; - if (defined(command)) { - command.vertexArray = command.vertexArray && command.vertexArray.destroy(); - command.shaderProgram = command.shaderProgram && command.shaderProgram.destroy(); - pickCommand.shaderProgram = pickCommand.shaderProgram && pickCommand.shaderProgram.destroy(); - } + this._pointCloud = this._pointCloud && this._pointCloud.destroy(); this._batchTable = this._batchTable && this._batchTable.destroy(); return destroyObject(this); }; diff --git a/Specs/Scene/ExpressionSpec.js b/Specs/Scene/ExpressionSpec.js index 60811b57540d..db56c56c1b4d 100644 --- a/Specs/Scene/ExpressionSpec.js +++ b/Specs/Scene/ExpressionSpec.js @@ -3326,7 +3326,7 @@ defineSuite([ it('gets shader expression for tiles3d_tileset_time', function() { var expression = new Expression('${tiles3d_tileset_time}'); var shaderExpression = expression.getShaderExpression('', {}); - var expected = 'u_tilesetTime'; + var expected = 'u_time'; expect(shaderExpression).toEqual(expected); }); diff --git a/Specs/Scene/PointCloud3DTileContentSpec.js b/Specs/Scene/PointCloud3DTileContentSpec.js index 208ef42c846d..b24ec9234bd6 100644 --- a/Specs/Scene/PointCloud3DTileContentSpec.js +++ b/Specs/Scene/PointCloud3DTileContentSpec.js @@ -179,7 +179,7 @@ defineSuite([ } }); var content = Cesium3DTilesTester.loadTile(scene, arrayBuffer, 'pnts'); - expect(content._drawCommand._vertexArray._attributes[1].componentDatatype).toEqual(ComponentDatatype.UNSIGNED_SHORT); + expect(content._pointCloud._drawCommand._vertexArray._attributes[1].componentDatatype).toEqual(ComponentDatatype.UNSIGNED_SHORT); }); it('resolves readyPromise', function() { @@ -451,7 +451,7 @@ defineSuite([ expect(scene).toPickAndCall(function(result) { // Set culling to true - content.backFaceCulling = true; + content._pointCloud.backFaceCulling = true; expect(scene).toPickAndCall(function(result) { picked = result; @@ -474,7 +474,7 @@ defineSuite([ } // Set culling to false - content.backFaceCulling = false; + content._pointCloud.backFaceCulling = false; expect(scene).toPickAndCall(function(result) { picked = result; @@ -602,7 +602,7 @@ defineSuite([ color : 'color("red")' }); expect(scene).toRender([255, 0, 0, 255]); - expect(content._styleTranslucent).toBe(false); + expect(content._pointCloud._styleTranslucent).toBe(false); // Applies translucency tileset.style = new Cesium3DTileStyle({ @@ -614,7 +614,7 @@ defineSuite([ expect(rgba[1]).toBe(0); expect(rgba[2]).toBe(0); expect(rgba[3]).toBe(255); - expect(content._styleTranslucent).toBe(true); + expect(content._pointCloud._styleTranslucent).toBe(true); }); // Style with property @@ -765,22 +765,22 @@ defineSuite([ it('throws if style references the NORMAL semantic but the point cloud does not have per-point normals', function() { return Cesium3DTilesTester.loadTileset(scene, pointCloudRGBUrl).then(function(tileset) { - var content = tileset._root.content; + tileset.style = new Cesium3DTileStyle({ + color : '${NORMAL}[0] > 0.5' + }); expect(function() { - content.applyStyle(scene.frameState, new Cesium3DTileStyle({ - color : '${NORMAL}[0] > 0.5' - })); + scene.renderForSpecs(); }).toThrowRuntimeError(); }); }); it('throws when shader style reference a non-existent property', function() { return Cesium3DTilesTester.loadTileset(scene, pointCloudWithPerPointPropertiesUrl).then(function(tileset) { - var content = tileset._root.content; + tileset.style = new Cesium3DTileStyle({ + color : 'color() * ${non_existent_property}' + }); expect(function() { - content.applyStyle(scene.frameState, new Cesium3DTileStyle({ - color : 'color() * ${non_existent_property}' - })); + scene.renderForSpecs(); }).toThrowRuntimeError(); }); }); @@ -788,11 +788,12 @@ defineSuite([ it('does not apply shader style if the point cloud has a batch table', function() { return Cesium3DTilesTester.loadTileset(scene, pointCloudBatchedUrl).then(function(tileset) { var content = tileset._root.content; - var shaderProgram = content._drawCommand.shaderProgram; + var shaderProgram = content._pointCloud._drawCommand.shaderProgram; tileset.style = new Cesium3DTileStyle({ color:'color("red")' }); - expect(content._drawCommand.shaderProgram).toBe(shaderProgram); + scene.renderForSpecs(); + expect(content._pointCloud._drawCommand.shaderProgram).toBe(shaderProgram); // Point cloud is styled through the batch table expect(scene).notToRender([0, 0, 0, 255]); @@ -801,11 +802,11 @@ defineSuite([ it('throws when shader style is invalid', function() { return Cesium3DTilesTester.loadTileset(scene, pointCloudRGBUrl).then(function(tileset) { - var content = tileset._root.content; + tileset.style = new Cesium3DTileStyle({ + show : '1 < "2"' + }); expect(function() { - content.applyStyle(scene.frameState, new Cesium3DTileStyle({ - show : '1 < "2"' - })); + scene.renderForSpecs(); }).toThrowRuntimeError(); }); }); @@ -874,7 +875,7 @@ defineSuite([ tile._isClipped = true; var content = tile.content; - var noClipFS = content._drawCommand.shaderProgram._fragmentShaderText; + var noClipFS = content._pointCloud._drawCommand.shaderProgram._fragmentShaderText; expect(noClipFS.indexOf('clip') !== -1).toBe(false); var clippingPlanes = new ClippingPlaneCollection({ @@ -887,7 +888,7 @@ defineSuite([ clippingPlanes.update(scene.frameState); tile.update(tileset, scene.frameState); - var clipOneIntersectFS = content._drawCommand.shaderProgram._fragmentShaderText; + var clipOneIntersectFS = content._pointCloud._drawCommand.shaderProgram._fragmentShaderText; expect(clipOneIntersectFS.indexOf('= clip(') !== -1).toBe(true); expect(clipOneIntersectFS.indexOf('float clip') !== -1).toBe(true); @@ -895,7 +896,7 @@ defineSuite([ clippingPlanes.update(scene.frameState); tile.update(tileset, scene.frameState); - var clipOneUnionFS = content._drawCommand.shaderProgram._fragmentShaderText; + var clipOneUnionFS = content._pointCloud._drawCommand.shaderProgram._fragmentShaderText; expect(clipOneUnionFS.indexOf('= clip(') !== -1).toBe(true); expect(clipOneUnionFS.indexOf('float clip') !== -1).toBe(true); expect(clipOneUnionFS).not.toEqual(clipOneIntersectFS); @@ -904,7 +905,7 @@ defineSuite([ clippingPlanes.update(scene.frameState); tile.update(tileset, scene.frameState); - var clipTwoUnionFS = content._drawCommand.shaderProgram._fragmentShaderText; + var clipTwoUnionFS = content._pointCloud._drawCommand.shaderProgram._fragmentShaderText; expect(clipTwoUnionFS.indexOf('= clip(') !== -1).toBe(true); expect(clipTwoUnionFS.indexOf('float clip') !== -1).toBe(true); expect(clipTwoUnionFS).not.toEqual(clipOneIntersectFS); From 61065d821281ed42851c4a1438c03058af2679ec Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 30 May 2018 17:53:25 -0400 Subject: [PATCH 02/35] Added PointCloudStream class --- Source/Scene/Cesium3DTileset.js | 2 +- Source/Scene/PointCloud.js | 6 +- Source/Scene/PointCloud3DTileContent.js | 8 +- Source/Scene/PointCloudEyeDomeLighting.js | 6 +- Source/Scene/PointCloudStream.js | 269 ++++++++++++++++++++++ 5 files changed, 278 insertions(+), 13 deletions(-) create mode 100644 Source/Scene/PointCloudStream.js diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index c4e92f33a863..e2e1d6db040a 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -1730,7 +1730,7 @@ define([ if (tileset.pointCloudShading.attenuation && tileset.pointCloudShading.eyeDomeLighting && (addedCommandsLength > 0)) { - tileset._pointCloudEyeDomeLighting.update(frameState, numberOfInitialCommands, tileset); + tileset._pointCloudEyeDomeLighting.update(frameState, numberOfInitialCommands, tileset.pointCloudShading); } if (tileset.debugShowGeometricError || tileset.debugShowRenderingStatistics || tileset.debugShowMemoryUsage || tileset.debugShowUrl) { diff --git a/Source/Scene/PointCloud.js b/Source/Scene/PointCloud.js index 2f126d33f876..356224b9d516 100644 --- a/Source/Scene/PointCloud.js +++ b/Source/Scene/PointCloud.js @@ -92,7 +92,9 @@ define([ }; /** - * Represents a point cloud. + * Represents the contents of a + * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/TileFormats/PointCloud/README.md|Point Cloud} + * tile. Used internally by {@link PointCloud3DTileContent} and {@link PointCloudStream}. * * @alias PointCloud * @constructor @@ -170,7 +172,7 @@ define([ this._modelMatrix = Matrix4.clone(Matrix4.IDENTITY); this.time = 0.0; // For styling - this.shadows = ShadowMode.DISABLED; + this.shadows = ShadowMode.ENABLED; this.boundingVolume = undefined; this.clippingPlanes = undefined; diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 9c41565e15df..b3df3afe9bf0 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -32,15 +32,9 @@ define([ SceneMode) { 'use strict'; - // Bail out if the browser doesn't support typed arrays, to prevent the setup function - // from failing, since we won't be able to create a WebGL context anyway. - if (!FeatureDetection.supportsTypedArrays()) { - return {}; - } - /** * Represents the contents of a - * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/TileFormats/PointCloud/README.md|Points} + * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/TileFormats/PointCloud/README.md|Point Cloud} * tile in a {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/README.md|3D Tiles} tileset. *

* Implements the {@link Cesium3DTileContent} interface. diff --git a/Source/Scene/PointCloudEyeDomeLighting.js b/Source/Scene/PointCloudEyeDomeLighting.js index 15d9e4a3ee87..7eb8f33daa08 100644 --- a/Source/Scene/PointCloudEyeDomeLighting.js +++ b/Source/Scene/PointCloudEyeDomeLighting.js @@ -251,15 +251,15 @@ define([ return shader; } - PointCloudEyeDomeLighting.prototype.update = function(frameState, commandStart, tileset) { + PointCloudEyeDomeLighting.prototype.update = function(frameState, commandStart, pointCloudShading) { var passes = frameState.passes; var isPick = (passes.pick && !passes.render); if (!isSupported(frameState.context) || isPick) { return; } - this._strength = tileset.pointCloudShading.eyeDomeLightingStrength; - this._radius = tileset.pointCloudShading.eyeDomeLightingRadius; + this._strength = pointCloudShading.eyeDomeLightingStrength; + this._radius = pointCloudShading.eyeDomeLightingRadius; var dirty = createResources(this, frameState.context); diff --git a/Source/Scene/PointCloudStream.js b/Source/Scene/PointCloudStream.js new file mode 100644 index 000000000000..a423f710b57f --- /dev/null +++ b/Source/Scene/PointCloudStream.js @@ -0,0 +1,269 @@ +define([ + '../Core/arraySlice', + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Cartesian4', + '../Core/Check', + '../Core/Color', + '../Core/combine', + '../Core/ComponentDatatype', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/destroyObject', + '../Core/DeveloperError', + '../Core/FeatureDetection', + '../Core/getStringFromTypedArray', + '../Core/JulianDate', + '../Core/Math', + '../Core/Matrix3', + '../Core/Matrix4', + '../Core/oneTimeWarning', + '../Core/OrthographicFrustum', + '../Core/Plane', + '../Core/PrimitiveType', + '../Core/RuntimeError', + '../Core/TaskProcessor', + '../Core/Transforms', + '../Renderer/Buffer', + '../Renderer/BufferUsage', + '../Renderer/DrawCommand', + '../Renderer/Pass', + '../Renderer/RenderState', + '../Renderer/ShaderProgram', + '../Renderer/ShaderSource', + '../Renderer/VertexArray', + '../ThirdParty/when', + './BlendingState', + './Cesium3DTileBatchTable', + './Cesium3DTileFeature', + './Cesium3DTileFeatureTable', + './ClippingPlaneCollection', + './getClipAndStyleCode', + './getClippingFunction', + './PointCloud', + './PointCloudEyeDomeLighting', + './PointCloudShading', + './SceneMode', + './ShadowMode' + ], function( + arraySlice, + Cartesian2, + Cartesian3, + Cartesian4, + Check, + Color, + combine, + ComponentDatatype, + defaultValue, + defined, + defineProperties, + destroyObject, + DeveloperError, + FeatureDetection, + getStringFromTypedArray, + JulianDate, + CesiumMath, + Matrix3, + Matrix4, + oneTimeWarning, + OrthographicFrustum, + Plane, + PrimitiveType, + RuntimeError, + TaskProcessor, + Transforms, + Buffer, + BufferUsage, + DrawCommand, + Pass, + RenderState, + ShaderProgram, + ShaderSource, + VertexArray, + when, + BlendingState, + Cesium3DTileBatchTable, + Cesium3DTileFeature, + Cesium3DTileFeatureTable, + ClippingPlaneCollection, + getClipAndStyleCode, + getClippingFunction, + PointCloud, + PointCloudEyeDomeLighting, + PointCloudShading, + SceneMode, + ShadowMode) { + 'use strict'; + + /** + * A method for streaming time-dynamic point cloud data. + * + * @alias PointCloudStream + * @constructor + * + * @private + */ + function PointCloudStream(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + this.pointCloudShading = new PointCloudShading(options.pointCloudShading); + this.style = options.style; + this.clippingPlanes = options.clippingPlanes; // TODO : getter/setter for ownership + this.shadows = defaultValue(options.shadows, ShadowMode.ENABLED); + this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); + this.show = defaultValue(options.show, true); + this.index = 0; + + this._pointCloudEyeDomeLighting = new PointCloudEyeDomeLighting(); + this._loadTimestamp = undefined; + this._clippingPlanesState = 0; + this._styleDirty = false; + this._pickId = undefined; + this._frames = []; + this._frameTransforms = []; + } + + function getPickFragmentShaderLoaded(stream) { + return function(fs) { + return ShaderSource.createPickFragmentShaderSource(fs, 'uniform'); + }; + } + + function getPickUniformMapLoaded(stream) { + return function(uniformMap) { + return combine(uniformMap, { + czm_pickColor : function() { + return stream._pickId.color; + } + }); + }; + } + + PointCloudStream.prototype.addFrame = function(options) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('options', options); + Check.typeOf.number.greaterThanOrEquals('options.index', options.index, 0); + Check.typeOf.object('options.arrayBuffer', options.arrayBuffer); + //>>includeEnd('debug'); + + this._frameTransforms[options.index] = Matrix4.clone(defaultValue(options.transform, Matrix4.IDENTITY)); + + this._frames[options.index] = new PointCloud({ + arrayBuffer : options.arrayBuffer, + pickFragmentShaderLoaded : getPickFragmentShaderLoaded(this), + pickUniformMapLoaded : getPickUniformMapLoaded(this) + }); + }; + + PointCloudStream.prototype.makeStyleDirty = function() { + this._styleDirty = true; + }; + + PointCloudStream.prototype.update = function(frameState) { + if (frameState.mode === SceneMode.MORPHING) { + return; + } + + if (!this.show) { + return; + } + + var frames = this._frames; + var framesLength = frames.length; + if (framesLength === 0) { + return; + } + + var index = this.index; + if (index < 0 || index > framesLength) { + // Index not in range. + return; + } + + var frame = frames[index]; + if (!defined(frame)) { + return; + } + + if (!defined(this._pickId)) { + this._pickId = frameState.context.createPickId({ + primitive : this + }); + } + + if (!defined(this._loadTimestamp)) { + this._loadTimestamp = JulianDate.clone(frameState.time); + } + + var timeSinceLoad = Math.max(JulianDate.secondsDifference(frameState.time, this._loadTimestamp) * 1000, 0.0); + + // Update clipping planes + var clippingPlanes = this.clippingPlanes; + var clippingPlanesState = 0; + var clippingPlanesDirty = false; + var isClipped = defined(clippingPlanes) && clippingPlanes.enabled; + + if (isClipped) { + clippingPlanes.update(frameState); + clippingPlanesState = clippingPlanes.clippingPlanesState; + } + + if (this._clippingPlanesState !== clippingPlanesState) { + this._clippingPlanesState = clippingPlanesState; + clippingPlanesDirty = true; + } + + var pointCloudShading = this.pointCloudShading; + var eyeDomeLighting = this._pointCloudEyeDomeLighting; + + var commandList = frameState.commandList; + var lengthBeforeUpdate = commandList.length; + + if (defined(frame)) { + var frameTransform = this._frameTransforms[index]; + Matrix4.multiply(this.modelMatrix, frameTransform, frame.modelMatrix); + frame.style = this.style; + frame.styleDirty = this._styleDirty; + frame.time = timeSinceLoad; + frame.shadows = this.shadows; + frame.clippingPlanes = clippingPlanes; + frame.isClipped = isClipped; + frame.clippingPlanesDirty = clippingPlanesDirty; + + if (defined(pointCloudShading)) { + frame.attenuation = pointCloudShading.attenuation; + frame.geometricError = 10.0; // TODO : If we had a bounding volume we could derive it + frame.geometricErrorScale = pointCloudShading.geometricErrorScale; + frame.maximumAttenuation = defined(pointCloudShading.maximumAttenuation) ? pointCloudShading.maximumAttenuation : 10; + } + + frame.update(frameState); + } + + var lengthAfterUpdate = commandList.length; + var addedCommandsLength = lengthAfterUpdate - lengthBeforeUpdate; + + if (defined(pointCloudShading) && pointCloudShading.attenuation && pointCloudShading.eyeDomeLighting && (addedCommandsLength > 0)) { + eyeDomeLighting.update(frameState, lengthBeforeUpdate, pointCloudShading); + } + }; + + PointCloudStream.prototype.isDestroyed = function() { + return false; + }; + + PointCloudStream.prototype.destroy = function() { + var frames = this._frames; + var framesLength = frames.length; + for (var i = 0; i < framesLength; ++i) { + var frame = frames[i]; + if (defined(frame)) { + frame.destroy(); + } + } + this._frames = undefined; + return destroyObject(this); + }; + + return PointCloudStream; +}); From 96cd741ad1eb159baa4b04c0c307c98e9af2e3de Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 31 May 2018 18:12:03 -0400 Subject: [PATCH 03/35] Start of prefetching code --- Source/Scene/PointCloudStream.js | 33 +++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/Source/Scene/PointCloudStream.js b/Source/Scene/PointCloudStream.js index a423f710b57f..56181906586b 100644 --- a/Source/Scene/PointCloudStream.js +++ b/Source/Scene/PointCloudStream.js @@ -112,7 +112,8 @@ define([ this.shadows = defaultValue(options.shadows, ShadowMode.ENABLED); this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); this.show = defaultValue(options.show, true); - this.index = 0; + + this._packets = options.packets; this._pointCloudEyeDomeLighting = new PointCloudEyeDomeLighting(); this._loadTimestamp = undefined; @@ -160,6 +161,33 @@ define([ }; PointCloudStream.prototype.update = function(frameState) { + var time = frameState.time; + var packets = this._packets; + var firstPacket = packets[0]; + var lastPacket = packets[packets.length - 1]; + if (time < firstPacket.time || time > lastPacket.time) { + return; + } + + // Find the current frame. See if it's loaded/ready. If it's ready, render it. + // If it's not ready we shouldn't necesarily load it + + + // If the current frame is not ready - try loading it. + + + + // If the current frame is not ready, start preparing the next frame. + // Figure out how long it takes (can't rely on JulianData time - it might be paused) + + // Find the packet associated with this time + if (time < this._packets[0].time) { + + } + + + + if (frameState.mode === SceneMode.MORPHING) { return; } @@ -219,6 +247,9 @@ define([ var commandList = frameState.commandList; var lengthBeforeUpdate = commandList.length; + // TODO : measure time required to fetch the data and update it + // TODO : synchronous draco faster? + if (defined(frame)) { var frameTransform = this._frameTransforms[index]; Matrix4.multiply(this.modelMatrix, frameTransform, frame.modelMatrix); From a618a7ee7e4abe5a1b7a72bb3add5cbe5cf26466 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 6 Jun 2018 22:34:42 -0400 Subject: [PATCH 04/35] Batch id uint32 fix --- Source/Scene/PointCloud.js | 23 +++++++++-------------- Source/Scene/PointCloud3DTileContent.js | 3 +++ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/Source/Scene/PointCloud.js b/Source/Scene/PointCloud.js index 356224b9d516..a694a6aa9664 100644 --- a/Source/Scene/PointCloud.js +++ b/Source/Scene/PointCloud.js @@ -448,19 +448,14 @@ define([ pointCloud._hasBatchIds = hasBatchIds; } - function prepareStyleableProperties(styleableProperties) { + function prepareVertexAttribute(typedArray) { // WebGL does not support UNSIGNED_INT, INT, or DOUBLE vertex attributes. Convert these to FLOAT. - for (var name in styleableProperties) { - if (styleableProperties.hasOwnProperty(name)) { - var property = styleableProperties[name]; - var typedArray = property.typedArray; - 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.'); - property.typedArray = new Float32Array(typedArray); - } - } + 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(); @@ -505,19 +500,18 @@ define([ pointCloud._styleableShaderAttributes = styleableShaderAttributes; if (hasStyleableProperties) { - prepareStyleableProperties(styleableProperties); var attributeLocation = numberOfAttributes; for (var name in styleableProperties) { if (styleableProperties.hasOwnProperty(name)) { var property = styleableProperties[name]; - var typedArray = property.typedArray; + var typedArray = prepareVertexAttribute(property.typedArray); componentsPerAttribute = property.componentCount; componentDatatype = ComponentDatatype.fromTypedArray(typedArray); var vertexBuffer = Buffer.createVertexBuffer({ context : context, - typedArray : property.typedArray, + typedArray : typedArray, usage : BufferUsage.STATIC_DRAW }); @@ -637,6 +631,7 @@ define([ var batchIdsVertexBuffer; if (hasBatchIds) { + batchIds = prepareVertexAttribute(batchIds); batchIdsVertexBuffer = Buffer.createVertexBuffer({ context : context, typedArray : batchIds, diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index b3df3afe9bf0..a27355b0a461 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -54,6 +54,9 @@ define([ this._styleDirty = false; this._features = undefined; + /** + * @inheritdoc Cesium3DTileContent#featurePropertiesDirty + */ this.featurePropertiesDirty = false; this._pointCloud = new PointCloud({ From 4704934a7b37b3c319d00fc3069bdc5c13fda6f8 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 13 Jun 2018 13:59:36 -0400 Subject: [PATCH 05/35] Rename PointCloudStream to TimeDynamicPointCloud --- Source/Scene/PointCloud.js | 4 ++-- ...CloudStream.js => TimeDynamicPointCloud.js} | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) rename Source/Scene/{PointCloudStream.js => TimeDynamicPointCloud.js} (94%) diff --git a/Source/Scene/PointCloud.js b/Source/Scene/PointCloud.js index a694a6aa9664..6573cda863ff 100644 --- a/Source/Scene/PointCloud.js +++ b/Source/Scene/PointCloud.js @@ -94,13 +94,13 @@ define([ /** * Represents the contents of a * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/TileFormats/PointCloud/README.md|Point Cloud} - * tile. Used internally by {@link PointCloud3DTileContent} and {@link PointCloudStream}. + * tile. Used internally by {@link PointCloud3DTileContent} and {@link TimeDynamicPointCloud}. * * @alias PointCloud * @constructor * * @see PointCloud3DTileContent - * @see PointCloudStream + * @see TimeDynamicPointCloud * * @private */ diff --git a/Source/Scene/PointCloudStream.js b/Source/Scene/TimeDynamicPointCloud.js similarity index 94% rename from Source/Scene/PointCloudStream.js rename to Source/Scene/TimeDynamicPointCloud.js index 56181906586b..48cb1d5e2907 100644 --- a/Source/Scene/PointCloudStream.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -97,14 +97,14 @@ define([ 'use strict'; /** - * A method for streaming time-dynamic point cloud data. + * A method for playing back time-dynamic point cloud data. * - * @alias PointCloudStream + * @alias TimeDynamicPointCloud * @constructor * * @private */ - function PointCloudStream(options) { + function TimeDynamicPointCloud(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); this.pointCloudShading = new PointCloudShading(options.pointCloudShading); this.style = options.style; @@ -140,7 +140,7 @@ define([ }; } - PointCloudStream.prototype.addFrame = function(options) { + TimeDynamicPointCloud.prototype.addFrame = function(options) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object('options', options); Check.typeOf.number.greaterThanOrEquals('options.index', options.index, 0); @@ -156,11 +156,11 @@ define([ }); }; - PointCloudStream.prototype.makeStyleDirty = function() { + TimeDynamicPointCloud.prototype.makeStyleDirty = function() { this._styleDirty = true; }; - PointCloudStream.prototype.update = function(frameState) { + TimeDynamicPointCloud.prototype.update = function(frameState) { var time = frameState.time; var packets = this._packets; var firstPacket = packets[0]; @@ -279,11 +279,11 @@ define([ } }; - PointCloudStream.prototype.isDestroyed = function() { + TimeDynamicPointCloud.prototype.isDestroyed = function() { return false; }; - PointCloudStream.prototype.destroy = function() { + TimeDynamicPointCloud.prototype.destroy = function() { var frames = this._frames; var framesLength = frames.length; for (var i = 0; i < framesLength; ++i) { @@ -296,5 +296,5 @@ define([ return destroyObject(this); }; - return PointCloudStream; + return TimeDynamicPointCloud; }); From ab372232536e9206b43255f9928c40701b9dd79b Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 13 Jun 2018 18:08:30 -0400 Subject: [PATCH 06/35] Updates in preparation for auto-skipping frames --- Source/Scene/PointCloud.js | 8 + Source/Scene/TimeDynamicPointCloud.js | 225 +++++++++++++++++--------- 2 files changed, 158 insertions(+), 75 deletions(-) diff --git a/Source/Scene/PointCloud.js b/Source/Scene/PointCloud.js index 6573cda863ff..8b3b6da567f4 100644 --- a/Source/Scene/PointCloud.js +++ b/Source/Scene/PointCloud.js @@ -151,6 +151,7 @@ define([ this._mode = undefined; + this._ready = false; this._readyPromise = when.defer(); this._pointsLength = 0; this._geometryByteLength = 0; @@ -203,6 +204,12 @@ define([ } }, + ready : { + get : function() { + return this._ready; + } + }, + readyPromise : { get : function() { return this._readyPromise.promise; @@ -1257,6 +1264,7 @@ define([ createResources(this, frameState); modelMatrixDirty = true; shadersDirty = true; + this._ready = true; this._readyPromise.resolve(this); this._parsedContent = undefined; // Unload } diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index 48cb1d5e2907..d6b024317dc5 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -14,6 +14,7 @@ define([ '../Core/DeveloperError', '../Core/FeatureDetection', '../Core/getStringFromTypedArray', + '../Core/getTimestamp', '../Core/JulianDate', '../Core/Math', '../Core/Matrix3', @@ -22,6 +23,7 @@ define([ '../Core/OrthographicFrustum', '../Core/Plane', '../Core/PrimitiveType', + '../Core/Resource', '../Core/RuntimeError', '../Core/TaskProcessor', '../Core/Transforms', @@ -62,6 +64,7 @@ define([ DeveloperError, FeatureDetection, getStringFromTypedArray, + getTimestamp, JulianDate, CesiumMath, Matrix3, @@ -70,6 +73,7 @@ define([ OrthographicFrustum, Plane, PrimitiveType, + Resource, RuntimeError, TaskProcessor, Transforms, @@ -97,15 +101,25 @@ define([ 'use strict'; /** - * A method for playing back time-dynamic point cloud data. + * Provides functionality for playback of time-dynamic point cloud data. * * @alias TimeDynamicPointCloud * @constructor * - * @private + * @param {Object} options Object with the following properties: + * @param {Clock} options.clock A Clock instance that is used when determining the value for the time dimension. + * @param {TimeIntervalCollection} options.times TimeIntervalCollection with its data property being an object containing a url to a Point Cloud tile and an optional transform. + * @param {PointCloudShading} options.pointCloudShading An object to control point attenuation based on geometric error and lighting. + * @param {Cesium3DTileStyle} options.pointCloudShading An object to control point attenuation based on geometric error and lighting. */ function TimeDynamicPointCloud(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object('options.clock', options.clock); + Check.typeOf.object('options.times', options.times); + //>>includeEnd('debug'); + this.pointCloudShading = new PointCloudShading(options.pointCloudShading); this.style = options.style; this.clippingPlanes = options.clippingPlanes; // TODO : getter/setter for ownership @@ -113,15 +127,21 @@ define([ this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); this.show = defaultValue(options.show, true); - this._packets = options.packets; - this._pointCloudEyeDomeLighting = new PointCloudEyeDomeLighting(); this._loadTimestamp = undefined; this._clippingPlanesState = 0; this._styleDirty = false; this._pickId = undefined; + + var clock = options.clock; + + this._clock = clock; + this._times = options.times; + this._frameIndex = -1; this._frames = []; - this._frameTransforms = []; + + clock.onTick.addEventListener(this._clockOnTick, this); + this._clockOnTick(clock); } function getPickFragmentShaderLoaded(stream) { @@ -140,54 +160,117 @@ define([ }; } - TimeDynamicPointCloud.prototype.addFrame = function(options) { - //>>includeStart('debug', pragmas.debug); - Check.typeOf.object('options', options); - Check.typeOf.number.greaterThanOrEquals('options.index', options.index, 0); - Check.typeOf.object('options.arrayBuffer', options.arrayBuffer); - //>>includeEnd('debug'); - - this._frameTransforms[options.index] = Matrix4.clone(defaultValue(options.transform, Matrix4.IDENTITY)); - - this._frames[options.index] = new PointCloud({ - arrayBuffer : options.arrayBuffer, - pickFragmentShaderLoaded : getPickFragmentShaderLoaded(this), - pickUniformMapLoaded : getPickUniformMapLoaded(this) - }); - }; - TimeDynamicPointCloud.prototype.makeStyleDirty = function() { this._styleDirty = true; }; - TimeDynamicPointCloud.prototype.update = function(frameState) { - var time = frameState.time; - var packets = this._packets; - var firstPacket = packets[0]; - var lastPacket = packets[packets.length - 1]; - if (time < firstPacket.time || time > lastPacket.time) { - return; - } + function getApproachingInterval(that) { + var times = that._times; + var clock = that._clock; + var time = clock.currentTime; + var isAnimating = clock.canAnimate && clock.shouldAnimate; + var multiplier = clock.multiplier; - // Find the current frame. See if it's loaded/ready. If it's ready, render it. - // If it's not ready we shouldn't necesarily load it + if (!isAnimating && multiplier !== 0) { + return undefined; + } + var seconds; + var index = times.indexOf(time); + if (index === -1) { + return undefined; + } - // If the current frame is not ready - try loading it. + var interval = times.get(index); + if (multiplier > 0) { // animating forward + seconds = JulianDate.secondsDifference(interval.stop, time); + ++index; + } else { //backwards + seconds = JulianDate.secondsDifference(interval.start, time); // Will be negative + --index; + } + seconds /= multiplier; // Will always be positive + // Less than 5 wall time seconds + return (index >= 0 && seconds <= 5.0) ? times.get(index) : undefined; + } + function getCurrentInterval(that) { + var times = that._times; + var clock = that._clock; + var time = clock.currentTime; + var index = times.indexOf(time); + if (index === -1) { + return undefined; + } + return times.get(index); + } - // If the current frame is not ready, start preparing the next frame. - // Figure out how long it takes (can't rely on JulianData time - it might be paused) + function loadFrame(that, interval) { + var index = that._times.indexOf(interval.start); + var cache = that._cache; + if (!defined(cache[index])) { + cache[index] = { + pointCloud : undefined, + transform : interval.data.transform, + timer : getTimestamp(), + ready : false + }; + Resource.fetchArrayBuffer({ + url : interval.data.url + }).then(function(arrayBuffer) { + cache[index].pointCloud = new PointCloud({ + arrayBuffer : arrayBuffer, + pickFragmentShaderLoaded : getPickFragmentShaderLoaded(that), + pickUniformMapLoaded : getPickUniformMapLoaded(that) + }); + }).otherwise(function(error) { + throw error; + }); + } + return cache[index]; + } - // Find the packet associated with this time - if (time < this._packets[0].time) { + function prepareFrame(that, frame, frameState) { + var pointCloud = frame.pointCloud; + if (!defined(pointCloud)) { + // Still waiting on the request to finish + return; + } + if (!pointCloud.ready) { + var commandList = frameState.commandList; + var lengthBeforeUpdate = commandList.length; + pointCloud.update(frameState); + if (pointCloud.ready) { + // Point cloud became ready this update + frame.ready = true; + frame.timer = getTimestamp() - frame.timer; + commandList.length = lengthBeforeUpdate; // Don't allow preparing frame to insert commands. + } } + } + function preloadFrame(that, interval, frameState) { + var frame = loadFrame(that, interval); + prepareFrame(that, frame, frameState); + return frame; + } + var scratchModelMatrix = new Matrix4(); + // TODO : remove pick shaders in PointCloud + // TODO : merge in master + // TODO : need to take into account current real-time time it takes to process an average tile, because just fetching the next interval is naive + // TODO : make sure it works if clock is stopped + // TODO : measure time required to fetch the data and update it + // TODO : synchronous draco faster? + // TODO : picking code may be obsolete? + // TODO : clear any requests that didn't finish from the previous frame? + // TODO : once a skip factor is supported that introduces a can of worms + // TODO : LRU cache / GPU memory share? + TimeDynamicPointCloud.prototype.update = function(frameState) { if (frameState.mode === SceneMode.MORPHING) { return; } @@ -196,23 +279,6 @@ define([ return; } - var frames = this._frames; - var framesLength = frames.length; - if (framesLength === 0) { - return; - } - - var index = this.index; - if (index < 0 || index > framesLength) { - // Index not in range. - return; - } - - var frame = frames[index]; - if (!defined(frame)) { - return; - } - if (!defined(this._pickId)) { this._pickId = frameState.context.createPickId({ primitive : this @@ -223,6 +289,7 @@ define([ this._loadTimestamp = JulianDate.clone(frameState.time); } + // For styling var timeSinceLoad = Math.max(JulianDate.secondsDifference(frameState.time, this._loadTimestamp) * 1000, 0.0); // Update clipping planes @@ -247,33 +314,41 @@ define([ var commandList = frameState.commandList; var lengthBeforeUpdate = commandList.length; - // TODO : measure time required to fetch the data and update it - // TODO : synchronous draco faster? - - if (defined(frame)) { - var frameTransform = this._frameTransforms[index]; - Matrix4.multiply(this.modelMatrix, frameTransform, frame.modelMatrix); - frame.style = this.style; - frame.styleDirty = this._styleDirty; - frame.time = timeSinceLoad; - frame.shadows = this.shadows; - frame.clippingPlanes = clippingPlanes; - frame.isClipped = isClipped; - frame.clippingPlanesDirty = clippingPlanesDirty; - - if (defined(pointCloudShading)) { - frame.attenuation = pointCloudShading.attenuation; - frame.geometricError = 10.0; // TODO : If we had a bounding volume we could derive it - frame.geometricErrorScale = pointCloudShading.geometricErrorScale; - frame.maximumAttenuation = defined(pointCloudShading.maximumAttenuation) ? pointCloudShading.maximumAttenuation : 10; + var currentInterval = getCurrentInterval(this); + if (defined(currentInterval)) { + var frame = preloadFrame(this, currentInterval, frameState); + if (frame.ready) { + var pointCloud = frame.pointCloud; + var transform = defaultValue(frame.transform, Matrix4.IDENTITY); + var modelMatrix = Matrix4.multiplyTransformation(this.modelMatrix, transform, scratchModelMatrix); + pointCloud.modelMatrix = modelMatrix; + pointCloud.style = this.style; + pointCloud.styleDirty = this._styleDirty; + pointCloud.time = timeSinceLoad; + pointCloud.shadows = this.shadows; + pointCloud.clippingPlanes = clippingPlanes; + pointCloud.isClipped = isClipped; + pointCloud.clippingPlanesDirty = clippingPlanesDirty; + + if (defined(pointCloudShading)) { + pointCloud.attenuation = pointCloudShading.attenuation; + pointCloud.geometricError = 10.0; // TODO : If we had a bounding volume we could derive it + pointCloud.geometricErrorScale = pointCloudShading.geometricErrorScale; + pointCloud.maximumAttenuation = defined(pointCloudShading.maximumAttenuation) ? pointCloudShading.maximumAttenuation : 10; + } + pointCloud.update(frameState); } - - frame.update(frameState); } var lengthAfterUpdate = commandList.length; var addedCommandsLength = lengthAfterUpdate - lengthBeforeUpdate; + // Start loading the approaching frame + var approachingInterval = getApproachingInterval(this); + if (defined(approachingInterval)) { + preloadFrame(this, approachingInterval); + } + if (defined(pointCloudShading) && pointCloudShading.attenuation && pointCloudShading.eyeDomeLighting && (addedCommandsLength > 0)) { eyeDomeLighting.update(frameState, lengthBeforeUpdate, pointCloudShading); } @@ -284,7 +359,7 @@ define([ }; TimeDynamicPointCloud.prototype.destroy = function() { - var frames = this._frames; + var frames = this._cache; var framesLength = frames.length; for (var i = 0; i < framesLength; ++i) { var frame = frames[i]; From cd219a8e359cde220c54386c66fdfaef9ec42ede Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 18 Jun 2018 17:55:48 -0400 Subject: [PATCH 07/35] More picking updates --- Source/Scene/PointCloud3DTileContent.js | 1 + Source/Scene/TimeDynamicPointCloud.js | 16 ++++++---------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 846407783bca..8b3121094e5e 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -317,6 +317,7 @@ define([ }; PointCloud3DTileContent.prototype.destroy = function() { + this._pickId = this._pickId && this._pickId.destroy(); this._pointCloud = this._pointCloud && this._pointCloud.destroy(); this._batchTable = this._batchTable && this._batchTable.destroy(); return destroyObject(this); diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index d6b024317dc5..f217596ff8f2 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -144,13 +144,11 @@ define([ this._clockOnTick(clock); } - function getPickFragmentShaderLoaded(stream) { - return function(fs) { - return ShaderSource.createPickFragmentShaderSource(fs, 'uniform'); - }; + function getFragmentShaderLoaded(fs) { + return 'uniform vec4 czm_pickColor;\n' + fs; } - function getPickUniformMapLoaded(stream) { + function getUniformMapLoaded(stream) { return function(uniformMap) { return combine(uniformMap, { czm_pickColor : function() { @@ -221,8 +219,8 @@ define([ }).then(function(arrayBuffer) { cache[index].pointCloud = new PointCloud({ arrayBuffer : arrayBuffer, - pickFragmentShaderLoaded : getPickFragmentShaderLoaded(that), - pickUniformMapLoaded : getPickUniformMapLoaded(that) + fragmentShaderLoaded : getFragmentShaderLoaded, + uniformMapLoaded : getUniformMapLoaded(that) }); }).otherwise(function(error) { throw error; @@ -259,13 +257,10 @@ define([ var scratchModelMatrix = new Matrix4(); - // TODO : remove pick shaders in PointCloud - // TODO : merge in master // TODO : need to take into account current real-time time it takes to process an average tile, because just fetching the next interval is naive // TODO : make sure it works if clock is stopped // TODO : measure time required to fetch the data and update it // TODO : synchronous draco faster? - // TODO : picking code may be obsolete? // TODO : clear any requests that didn't finish from the previous frame? // TODO : once a skip factor is supported that introduces a can of worms // TODO : LRU cache / GPU memory share? @@ -368,6 +363,7 @@ define([ } } this._frames = undefined; + this._pickId = this._pickId && this._pickId.destroy(); return destroyObject(this); }; From 45382e7aba3be7b6cc9a3446365b9607f3ff76a3 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 18 Jun 2018 21:50:41 -0400 Subject: [PATCH 08/35] Documentation and cleanup --- Source/Scene/Cesium3DTileset.js | 2 +- Source/Scene/TimeDynamicPointCloud.js | 408 +++++++++++++++++--------- 2 files changed, 269 insertions(+), 141 deletions(-) diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 3ce77d1e5a43..1ab36f4ba450 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -110,6 +110,7 @@ define([ * @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the tileset. * @param {ClassificationType} [options.classificationType] Determines whether terrain, 3D Tiles or both will be classified by this tileset. See {@link Cesium3DTileset#classificationType} for details about restrictions and limitations. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid determining the size and shape of the globe. + * @param {Object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation based on geometric error and lighting. * @param {Boolean} [options.debugFreezeFrame=false] For debugging only. Determines if only the tiles from last frame should be used for rendering. * @param {Boolean} [options.debugColorizeTiles=false] For debugging only. When true, assigns a random color to each tile. * @param {Boolean} [options.debugWireframe=false] For debugging only. When true, render's each tile's content as a wireframe. @@ -120,7 +121,6 @@ define([ * @param {Boolean} [options.debugShowRenderingStatistics=false] For debugging only. When true, draws labels to indicate the number of commands, points, triangles and features for each tile. * @param {Boolean} [options.debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile. * @param {Boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. - * @param {Object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation based on geometric error and lighting. * * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. See {@link https://github.com/AnalyticalGraphicsInc/3d-tiles#spec-status} * diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index f217596ff8f2..f0de1f9b5e25 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -1,98 +1,34 @@ define([ - '../Core/arraySlice', - '../Core/Cartesian2', - '../Core/Cartesian3', - '../Core/Cartesian4', '../Core/Check', - '../Core/Color', '../Core/combine', - '../Core/ComponentDatatype', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', '../Core/destroyObject', - '../Core/DeveloperError', - '../Core/FeatureDetection', - '../Core/getStringFromTypedArray', '../Core/getTimestamp', '../Core/JulianDate', '../Core/Math', - '../Core/Matrix3', '../Core/Matrix4', - '../Core/oneTimeWarning', - '../Core/OrthographicFrustum', - '../Core/Plane', - '../Core/PrimitiveType', '../Core/Resource', - '../Core/RuntimeError', - '../Core/TaskProcessor', - '../Core/Transforms', - '../Renderer/Buffer', - '../Renderer/BufferUsage', - '../Renderer/DrawCommand', - '../Renderer/Pass', - '../Renderer/RenderState', - '../Renderer/ShaderProgram', - '../Renderer/ShaderSource', - '../Renderer/VertexArray', - '../ThirdParty/when', - './BlendingState', - './Cesium3DTileBatchTable', - './Cesium3DTileFeature', - './Cesium3DTileFeatureTable', './ClippingPlaneCollection', - './getClipAndStyleCode', - './getClippingFunction', './PointCloud', './PointCloudEyeDomeLighting', './PointCloudShading', './SceneMode', './ShadowMode' ], function( - arraySlice, - Cartesian2, - Cartesian3, - Cartesian4, Check, - Color, combine, - ComponentDatatype, defaultValue, defined, defineProperties, destroyObject, - DeveloperError, - FeatureDetection, - getStringFromTypedArray, getTimestamp, JulianDate, CesiumMath, - Matrix3, Matrix4, - oneTimeWarning, - OrthographicFrustum, - Plane, - PrimitiveType, Resource, - RuntimeError, - TaskProcessor, - Transforms, - Buffer, - BufferUsage, - DrawCommand, - Pass, - RenderState, - ShaderProgram, - ShaderSource, - VertexArray, - when, - BlendingState, - Cesium3DTileBatchTable, - Cesium3DTileFeature, - Cesium3DTileFeatureTable, ClippingPlaneCollection, - getClipAndStyleCode, - getClippingFunction, PointCloud, PointCloudEyeDomeLighting, PointCloudShading, @@ -107,43 +43,145 @@ define([ * @constructor * * @param {Object} options Object with the following properties: - * @param {Clock} options.clock A Clock instance that is used when determining the value for the time dimension. - * @param {TimeIntervalCollection} options.times TimeIntervalCollection with its data property being an object containing a url to a Point Cloud tile and an optional transform. - * @param {PointCloudShading} options.pointCloudShading An object to control point attenuation based on geometric error and lighting. - * @param {Cesium3DTileStyle} options.pointCloudShading An object to control point attenuation based on geometric error and lighting. + * @param {Clock} options.clock A {@link Clock} instance that is used when determining the value for the time dimension. + * @param {TimeIntervalCollection} options.intervals A {@link TimeIntervalCollection} with its data property being an object containing a uri to a Point Cloud tile and an optional transform. + * @param {Boolean} [options.show=true] Determines if the point cloud will be shown. + * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] A 4x4 transformation matrix that transforms the point cloud. + * @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the point cloud casts or receives shadows from each light source. + * @param {Number} [options.maximumMemoryUsage=256] The maximum amount of memory in MB that can be used by the point cloud. + * @param {Object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point size based on geometric error and eye dome lighting. + * @param {Cesium3DTileStyle} [options.style] The style, defined using the {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language}, applied to each point in the point cloud. + * @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the point cloud. */ function TimeDynamicPointCloud(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); //>>includeStart('debug', pragmas.debug); Check.typeOf.object('options.clock', options.clock); - Check.typeOf.object('options.times', options.times); + Check.typeOf.object('options.intervals', options.intervals); //>>includeEnd('debug'); + /** + * Determines if the point cloud will be shown. + * + * @type {Boolean} + * @default true + */ + this.show = defaultValue(options.show, true); + + /** + * A 4x4 transformation matrix that transforms the point cloud. + * + * @type {Matrix4} + * @default Matrix4.IDENTITY + */ + this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); + + /** + * Determines whether the point cloud casts or receives shadows from each light source. + *

+ * Enabling shadows has a performance impact. A point cloud that casts shadows must be rendered twice, once from the camera and again from the light's point of view. + *

+ *

+ * Shadows are rendered only when {@link Viewer#shadows} is true. + *

+ * + * @type {ShadowMode} + * @default ShadowMode.ENABLED + */ + this.shadows = defaultValue(options.shadows, ShadowMode.ENABLED); + + /** + * The maximum amount of GPU memory (in MB) that may be used to cache point cloud frames. + * + * @memberof TimeDynamicPointCloud.prototype + * + * @type {Number} + * @default 256 + * + * @see TimeDynamicPointCloud#totalMemoryUsageInBytes + */ + this.maximumMemoryUsage = defaultValue(options.maximumMemoryUsage, 256); + + /** + * Options for controlling point size based on geometric error and eye dome lighting. + * @type {PointCloudShading} + */ this.pointCloudShading = new PointCloudShading(options.pointCloudShading); + + /** + * The style, defined using the + * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language}, + * applied to each point in the point cloud. + *

+ * Assign undefined to remove the style, which will restore the visual + * appearance of the point cloud to its default when no style was applied. + *

+ * + * @type {Cesium3DTileStyle} + * + * @example + * pointCloud.style = new Cesium.Cesium3DTileStyle({ + * color : { + * conditions : [ + * ['${Classification} === 0', 'color("purple", 0.5)'], + * ['${Classification} === 1', 'color("red")'], + * ['true', '${COLOR}'] + * ] + * }, + * show : '${Classification} !== 2' + * }); + * + * @see {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language} + */ this.style = options.style; - this.clippingPlanes = options.clippingPlanes; // TODO : getter/setter for ownership - this.shadows = defaultValue(options.shadows, ShadowMode.ENABLED); - this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); - this.show = defaultValue(options.show, true); + this._clock = options.clock; + this._intervals = options.intervals; + this._clippingPlanes = options.clippingPlanes; this._pointCloudEyeDomeLighting = new PointCloudEyeDomeLighting(); this._loadTimestamp = undefined; this._clippingPlanesState = 0; this._styleDirty = false; this._pickId = undefined; - - var clock = options.clock; - - this._clock = clock; - this._times = options.times; - this._frameIndex = -1; + this._totalMemoryUsageInBytes = 0; this._frames = []; - - clock.onTick.addEventListener(this._clockOnTick, this); - this._clockOnTick(clock); } + defineProperties(TimeDynamicPointCloud.prototype, { + /** + * The {@link ClippingPlaneCollection} used to selectively disable rendering the point cloud. + * + * @memberof TimeDynamicPointCloud.prototype + * + * @type {ClippingPlaneCollection} + */ + clippingPlanes : { + get : function() { + return this._clippingPlanes; + }, + set : function(value) { + ClippingPlaneCollection.setOwner(value, this, '_clippingPlanes'); + } + }, + + /** + * The total amount of GPU memory in bytes used by the point cloud. + * + * @memberof TimeDynamicPointCloud.prototype + * + * @type {Number} + * @readonly + * + * @see TimeDynamicPointCloud#maximumMemoryUsage + */ + totalMemoryUsageInBytes : { + get : function() { + return this._totalMemoryUsageInBytes; + } + } + }); + function getFragmentShaderLoaded(fs) { return 'uniform vec4 czm_pickColor;\n' + fs; } @@ -158,12 +196,20 @@ define([ }; } + function getPickIdLoaded() { + return 'czm_pickColor'; + } + + /** + * Marks the point cloud's {@link TimeDynamicPointCloud#style} as dirty, which forces all + * points to re-evaluate the style in the next frame. + */ TimeDynamicPointCloud.prototype.makeStyleDirty = function() { this._styleDirty = true; }; function getApproachingInterval(that) { - var times = that._times; + var intervals = that._intervals; var clock = that._clock; var time = clock.currentTime; var isAnimating = clock.canAnimate && clock.shouldAnimate; @@ -174,12 +220,12 @@ define([ } var seconds; - var index = times.indexOf(time); + var index = intervals.indexOf(time); if (index === -1) { return undefined; } - var interval = times.get(index); + var interval = intervals.get(index); if (multiplier > 0) { // animating forward seconds = JulianDate.secondsDifference(interval.stop, time); ++index; @@ -190,43 +236,79 @@ define([ seconds /= multiplier; // Will always be positive // Less than 5 wall time seconds - return (index >= 0 && seconds <= 5.0) ? times.get(index) : undefined; + return (index >= 0 && seconds <= 5.0) ? intervals.get(index) : undefined; + } + + function getLastReadyFrame(that, interval) { + var i; + var frame; + var frames = that._frames; + var clock = that._clock; + var multiplier = clock.multiplier; + var index = getIntervalIndex(that, interval); + + if (multiplier >= 0) { + // Animating forwards, so look backwards + for (i = index - 1; i >= 0; --i) { + frame = frames[i]; + if (defined(frame) && frame.ready) { + return frame; + } + } + } else { + // Animating backwards, so look forwards + var length = frames.length; + for (i = index + 1; i < length; ++i) { + frame = frames[i]; + if (defined(frame) && frame.ready) { + return frame; + } + } + } + } + + function getIntervalIndex(that, interval) { + return that._intervals.indexOf(interval.start); } function getCurrentInterval(that) { - var times = that._times; + var intervals = that._intervals; var clock = that._clock; var time = clock.currentTime; - var index = times.indexOf(time); + var index = intervals.indexOf(time); if (index === -1) { return undefined; } - return times.get(index); + return intervals.get(index); } - function loadFrame(that, interval) { - var index = that._times.indexOf(interval.start); - var cache = that._cache; - if (!defined(cache[index])) { - cache[index] = { + function requestFrame(that, interval) { + var index = getIntervalIndex(that, interval); + var frames = that._frames; + var frame = frames[index]; + if (!defined(frame)) { + frame = { pointCloud : undefined, transform : interval.data.transform, - timer : getTimestamp(), - ready : false + loadDuration : getTimestamp(), // Updated after the frame is loaded + ready : false, + touchedFrameNumber : 0 }; + frames[index] = frame; Resource.fetchArrayBuffer({ - url : interval.data.url + url : interval.data.uri }).then(function(arrayBuffer) { - cache[index].pointCloud = new PointCloud({ + frame.pointCloud = new PointCloud({ arrayBuffer : arrayBuffer, fragmentShaderLoaded : getFragmentShaderLoaded, - uniformMapLoaded : getUniformMapLoaded(that) + uniformMapLoaded : getUniformMapLoaded(that), + pickIdLoaded : getPickIdLoaded }); }).otherwise(function(error) { throw error; }); } - return cache[index]; + return frame; } function prepareFrame(that, frame, frameState) { @@ -236,26 +318,80 @@ define([ return; } - if (!pointCloud.ready) { + if (!frame.ready) { + // Call update to prepare renderer resources. Don't render anything yet. var commandList = frameState.commandList; var lengthBeforeUpdate = commandList.length; pointCloud.update(frameState); if (pointCloud.ready) { // Point cloud became ready this update frame.ready = true; - frame.timer = getTimestamp() - frame.timer; + frame.loadDuration = getTimestamp() - frame.loadDuration; + that._totalMemoryUsageInBytes += pointCloud.geometryByteLength; commandList.length = lengthBeforeUpdate; // Don't allow preparing frame to insert commands. } } } - function preloadFrame(that, interval, frameState) { - var frame = loadFrame(that, interval); + var scratchModelMatrix = new Matrix4(); + + function renderFrame(that, frame, timeSinceLoad, isClipped, clippingPlanesDirty, frameState) { + var pointCloud = frame.pointCloud; + var transform = defaultValue(frame.transform, Matrix4.IDENTITY); + pointCloud.modelMatrix = Matrix4.multiplyTransformation(that.modelMatrix, transform, scratchModelMatrix); + pointCloud.style = that.style; + pointCloud.styleDirty = that._styleDirty; + pointCloud.time = timeSinceLoad; + pointCloud.shadows = that.shadows; + pointCloud.clippingPlanes = that._clippingPlanes; + pointCloud.isClipped = isClipped; + pointCloud.clippingPlanesDirty = clippingPlanesDirty; + + var pointCloudShading = that.pointCloudShading; + if (defined(pointCloudShading)) { + pointCloud.attenuation = pointCloudShading.attenuation; + pointCloud.geometricError = 10.0; // TODO : If we had a bounding volume we could derive it + pointCloud.geometricErrorScale = pointCloudShading.geometricErrorScale; + pointCloud.maximumAttenuation = defined(pointCloudShading.maximumAttenuation) ? pointCloudShading.maximumAttenuation : 10; + } + pointCloud.update(frameState); + frame.touchedFrameNumber = frameState.frameNumber; + } + + function loadFrame(that, interval, frameState) { + var frame = requestFrame(that, interval); prepareFrame(that, frame, frameState); + frame.touchedFrameNumber = frameState.frameNumber; return frame; } - var scratchModelMatrix = new Matrix4(); + function getUnloadCondition(frameState) { + return function(frame) { + // Unload all frames that aren't currently being loaded or rendered + return frame.touchedFrameNumber < frameState.frameNumber; + }; + } + + function unloadFrames(that, unloadCondition) { + var frames = that._frames; + var length = frames.length; + for (var i = 0; i < length; ++i) { + var frame = frames[i]; + if (defined(frame)) { + if (!defined(unloadCondition) || unloadCondition(frame)) { + var pointCloud = frame.pointCloud; + if (frame.ready) { + that._totalMemoryUsageInBytes -= pointCloud.geometryByteLength; + } + if (defined(pointCloud)) { + // TODO : what happens if Draco decoding resolves after a frame is destroyed? + pointCloud.destroy(); + } + frames[i] = undefined; + } + } + } + } // TODO : need to take into account current real-time time it takes to process an average tile, because just fetching the next interval is naive // TODO : make sure it works if clock is stopped @@ -264,6 +400,9 @@ define([ // TODO : clear any requests that didn't finish from the previous frame? // TODO : once a skip factor is supported that introduces a can of worms // TODO : LRU cache / GPU memory share? + // TODO : skipping frames + // TODO : would be helpful to have a bounding sphere associated with the point cloud, better for the draw command to have one + // TODO : czml to TimeDynamicIntervalCollection TimeDynamicPointCloud.prototype.update = function(frameState) { if (frameState.mode === SceneMode.MORPHING) { @@ -288,7 +427,7 @@ define([ var timeSinceLoad = Math.max(JulianDate.secondsDifference(frameState.time, this._loadTimestamp) * 1000, 0.0); // Update clipping planes - var clippingPlanes = this.clippingPlanes; + var clippingPlanes = this._clippingPlanes; var clippingPlanesState = 0; var clippingPlanesDirty = false; var isClipped = defined(clippingPlanes) && clippingPlanes.enabled; @@ -309,41 +448,37 @@ define([ var commandList = frameState.commandList; var lengthBeforeUpdate = commandList.length; + var frame; + var currentInterval = getCurrentInterval(this); if (defined(currentInterval)) { - var frame = preloadFrame(this, currentInterval, frameState); - if (frame.ready) { - var pointCloud = frame.pointCloud; - var transform = defaultValue(frame.transform, Matrix4.IDENTITY); - var modelMatrix = Matrix4.multiplyTransformation(this.modelMatrix, transform, scratchModelMatrix); - pointCloud.modelMatrix = modelMatrix; - pointCloud.style = this.style; - pointCloud.styleDirty = this._styleDirty; - pointCloud.time = timeSinceLoad; - pointCloud.shadows = this.shadows; - pointCloud.clippingPlanes = clippingPlanes; - pointCloud.isClipped = isClipped; - pointCloud.clippingPlanesDirty = clippingPlanesDirty; - - if (defined(pointCloudShading)) { - pointCloud.attenuation = pointCloudShading.attenuation; - pointCloud.geometricError = 10.0; // TODO : If we had a bounding volume we could derive it - pointCloud.geometricErrorScale = pointCloudShading.geometricErrorScale; - pointCloud.maximumAttenuation = defined(pointCloudShading.maximumAttenuation) ? pointCloudShading.maximumAttenuation : 10; - } - pointCloud.update(frameState); + frame = loadFrame(this, currentInterval, frameState); + + if (!frame.ready) { + frame = getLastReadyFrame(this, currentInterval); } - } - var lengthAfterUpdate = commandList.length; - var addedCommandsLength = lengthAfterUpdate - lengthBeforeUpdate; + if (defined(frame)) { + renderFrame(this, frame, timeSinceLoad, isClipped, clippingPlanesDirty, frameState); + } + } // Start loading the approaching frame var approachingInterval = getApproachingInterval(this); if (defined(approachingInterval)) { - preloadFrame(this, approachingInterval); + loadFrame(this, approachingInterval, frameState); } + var totalMemoryUsageInBytes = this._totalMemoryUsageInBytes; + var maximumMemoryUsageInBytes = this.maximumMemoryUsage * 1024 * 1024; + + if (totalMemoryUsageInBytes > maximumMemoryUsageInBytes) { + unloadFrames(this, getUnloadCondition(frameState)); + } + + var lengthAfterUpdate = commandList.length; + var addedCommandsLength = lengthAfterUpdate - lengthBeforeUpdate; + if (defined(pointCloudShading) && pointCloudShading.attenuation && pointCloudShading.eyeDomeLighting && (addedCommandsLength > 0)) { eyeDomeLighting.update(frameState, lengthBeforeUpdate, pointCloudShading); } @@ -354,15 +489,8 @@ define([ }; TimeDynamicPointCloud.prototype.destroy = function() { - var frames = this._cache; - var framesLength = frames.length; - for (var i = 0; i < framesLength; ++i) { - var frame = frames[i]; - if (defined(frame)) { - frame.destroy(); - } - } - this._frames = undefined; + unloadFrames(this); + this._clippingPlanes = this._clippingPlanes && this._clippingPlanes.destroy(); this._pickId = this._pickId && this._pickId.destroy(); return destroyObject(this); }; From 0e44cf8535d508c2adfbe464dfea8642a580ccbc Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 19 Jun 2018 17:14:10 -0400 Subject: [PATCH 09/35] Added Sandcastle demo --- .../gallery/Time Dynamic Point Cloud.html | 78 +++++++++++++++++++ Source/Scene/TimeDynamicPointCloud.js | 1 + 2 files changed, 79 insertions(+) create mode 100644 Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html diff --git a/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html b/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html new file mode 100644 index 000000000000..d648c9ff3a06 --- /dev/null +++ b/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html @@ -0,0 +1,78 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index f0de1f9b5e25..bfe97cf31225 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -403,6 +403,7 @@ define([ // TODO : skipping frames // TODO : would be helpful to have a bounding sphere associated with the point cloud, better for the draw command to have one // TODO : czml to TimeDynamicIntervalCollection + // TODO : getAbsoluteUri removes the trailing dots TimeDynamicPointCloud.prototype.update = function(frameState) { if (frameState.mode === SceneMode.MORPHING) { From 1c6249881e9d2065d4ef2f466d516829e9df6457 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 21 Jun 2018 18:54:17 -0400 Subject: [PATCH 10/35] Frame skipping added --- Source/Scene/TimeDynamicImagery.js | 2 +- Source/Scene/TimeDynamicPointCloud.js | 230 ++++++++++++++++++-------- 2 files changed, 158 insertions(+), 74 deletions(-) diff --git a/Source/Scene/TimeDynamicImagery.js b/Source/Scene/TimeDynamicImagery.js index f51f38657fc0..f760212ee949 100644 --- a/Source/Scene/TimeDynamicImagery.js +++ b/Source/Scene/TimeDynamicImagery.js @@ -249,7 +249,7 @@ define([ var seconds; var index = times.indexOf(time); - if (index === -1) { + if (index < 0) { return undefined; } diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index bfe97cf31225..693180a2e0d7 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -146,6 +146,14 @@ define([ this._pickId = undefined; this._totalMemoryUsageInBytes = 0; this._frames = []; + this._previousInterval = undefined; + this._nextInterval = undefined; + this._clockMultiplier = 0.0; + this._lastRenderedFrame = undefined; + + // For calculation average load time + this._runningLoadTime = 0.0; + this._runningLoadedFramesLength = 0; } defineProperties(TimeDynamicPointCloud.prototype, { @@ -208,91 +216,98 @@ define([ this._styleDirty = true; }; - function getApproachingInterval(that) { - var intervals = that._intervals; + function getAverageLoadTime(that) { + if (that._runningLoadedFramesLength === 0) { + return undefined; + } + + // TODO : the first frame to load will be slower due to initializing Draco, which impacts the average + var averageLoadTime = that._runningLoadTime / that._runningLoadedFramesLength; + return averageLoadTime; // Provide additional buffer since the actual load time fluctuates + } + + var scratchDate = new JulianDate(); + + function getClockMultiplier(that) { var clock = that._clock; - var time = clock.currentTime; var isAnimating = clock.canAnimate && clock.shouldAnimate; var multiplier = clock.multiplier; + return isAnimating ? multiplier : 0.0; + } + + function getNextInterval(that) { + var intervals = that._intervals; + var clock = that._clock; + var multiplier = getClockMultiplier(that); - if (!isAnimating && multiplier !== 0) { + if (multiplier === 0.0) { return undefined; } - var seconds; - var index = intervals.indexOf(time); - if (index === -1) { + var averageLoadTime = getAverageLoadTime(that); + if (!defined(averageLoadTime)) { + // Don't return the next interval until there is an average load time return undefined; } - var interval = intervals.get(index); - if (multiplier > 0) { // animating forward - seconds = JulianDate.secondsDifference(interval.stop, time); + var time = JulianDate.addSeconds(clock.currentTime, averageLoadTime * multiplier, scratchDate); + var index = intervals.indexOf(time); + + if (multiplier >= 0) { ++index; - } else { //backwards - seconds = JulianDate.secondsDifference(interval.start, time); // Will be negative + } else { --index; } - seconds /= multiplier; // Will always be positive - // Less than 5 wall time seconds - return (index >= 0 && seconds <= 5.0) ? intervals.get(index) : undefined; + // Returns undefined if not in range + return intervals.get(index); } - function getLastReadyFrame(that, interval) { - var i; - var frame; - var frames = that._frames; + function getCurrentInterval(that) { + var intervals = that._intervals; var clock = that._clock; - var multiplier = clock.multiplier; - var index = getIntervalIndex(that, interval); + var time = clock.currentTime; + var index = intervals.indexOf(time); + + // Returns undefined if not in range + return intervals.get(index); + } + + function reachedInterval(that, currentInterval, nextInterval) { + var multiplier = getClockMultiplier(that); + var currentIndex = getIntervalIndex(that, currentInterval); + var nextIndex = getIntervalIndex(that, nextInterval); if (multiplier >= 0) { - // Animating forwards, so look backwards - for (i = index - 1; i >= 0; --i) { - frame = frames[i]; - if (defined(frame) && frame.ready) { - return frame; - } - } - } else { - // Animating backwards, so look forwards - var length = frames.length; - for (i = index + 1; i < length; ++i) { - frame = frames[i]; - if (defined(frame) && frame.ready) { - return frame; - } - } + return currentIndex >= nextIndex; } + return currentIndex <= nextIndex; } function getIntervalIndex(that, interval) { return that._intervals.indexOf(interval.start); } - function getCurrentInterval(that) { - var intervals = that._intervals; - var clock = that._clock; - var time = clock.currentTime; - var index = intervals.indexOf(time); - if (index === -1) { - return undefined; - } - return intervals.get(index); + function getFrame(that, interval) { + var index = getIntervalIndex(that, interval); + var frames = that._frames; + return frames[index]; } - function requestFrame(that, interval) { + function requestFrame(that, interval, frameState) { var index = getIntervalIndex(that, interval); var frames = that._frames; var frame = frames[index]; if (!defined(frame)) { + var transformArray = interval.data.transform; + var transform = defined(transformArray) ? Matrix4.fromArray(transformArray) : undefined; frame = { - pointCloud : undefined, - transform : interval.data.transform, - loadDuration : getTimestamp(), // Updated after the frame is loaded - ready : false, - touchedFrameNumber : 0 + pointCloud : undefined, // Created after request resolves + transform : transform, + timestamp : getTimestamp(), + sequential : true, // Whether the frame was loaded in sequential updates + ready : false, // True once point cloud is ready + touchedFrameNumber : frameState.frameNumber }; frames[index] = frame; Resource.fetchArrayBuffer({ @@ -312,23 +327,30 @@ define([ } function prepareFrame(that, frame, frameState) { - var pointCloud = frame.pointCloud; - if (!defined(pointCloud)) { - // Still waiting on the request to finish - return; + if (frame.touchedFrameNumber < frameState.frameNumber - 1) { + // If this frame was not loaded in sequential updates then it can't be used it for calculating average load time. + // For example: selecting a frame on the timeline, selecting another frame before the request finishes, then selecting this frame later. + frame.sequential = false; } - if (!frame.ready) { + var pointCloud = frame.pointCloud; + + if (defined(pointCloud) && !frame.ready) { // Call update to prepare renderer resources. Don't render anything yet. var commandList = frameState.commandList; var lengthBeforeUpdate = commandList.length; pointCloud.update(frameState); + if (pointCloud.ready) { // Point cloud became ready this update frame.ready = true; - frame.loadDuration = getTimestamp() - frame.loadDuration; that._totalMemoryUsageInBytes += pointCloud.geometryByteLength; commandList.length = lengthBeforeUpdate; // Don't allow preparing frame to insert commands. + if (frame.sequential) { + // Update the values used to calculate average load time + that._runningLoadTime += (getTimestamp() - frame.timestamp) / 1000.0; + ++that._runningLoadedFramesLength; + } } } } @@ -359,7 +381,7 @@ define([ } function loadFrame(that, interval, frameState) { - var frame = requestFrame(that, interval); + var frame = requestFrame(that, interval, frameState); prepareFrame(that, frame, frameState); frame.touchedFrameNumber = frameState.frameNumber; return frame; @@ -391,8 +413,38 @@ define([ } } } + that._lastRenderedFrame = undefined; // TODO : better approach than this? Maybe last rendered interval? } + function getLastReadyFrame(that, previousInterval, currentInterval) { + var i; + var frame; + var frames = that._frames; + var clockMultiplier = getClockMultiplier(that); + var currentIndex = getIntervalIndex(that, currentInterval); + var previousIndex = getIntervalIndex(that, previousInterval); + + if (clockMultiplier >= 0) { + // Animating forwards, so look backwards + for (i = currentIndex; i >= previousIndex; --i) { + frame = frames[i]; + if (defined(frame) && frame.ready) { + return frame; + } + } + } else { + // Animating backwards, so look forwards + for (i = currentIndex; i <= previousIndex; ++i) { + frame = frames[i]; + if (defined(frame) && frame.ready) { + return frame; + } + } + } + } + + // TODO : if no frames have been loaded (don't know a load duration yet + // TODO : need to take into account current real-time time it takes to process an average tile, because just fetching the next interval is naive // TODO : make sure it works if clock is stopped // TODO : measure time required to fetch the data and update it @@ -404,6 +456,7 @@ define([ // TODO : would be helpful to have a bounding sphere associated with the point cloud, better for the draw command to have one // TODO : czml to TimeDynamicIntervalCollection // TODO : getAbsoluteUri removes the trailing dots + // TODO : there is a slight jump when the sim is paused because the previous frame is set to the current frame but the current frame isn't loaded TimeDynamicPointCloud.prototype.update = function(frameState) { if (frameState.mode === SceneMode.MORPHING) { @@ -449,27 +502,58 @@ define([ var commandList = frameState.commandList; var lengthBeforeUpdate = commandList.length; - var frame; - + var previousInterval = this._previousInterval; + var nextInterval = this._nextInterval; var currentInterval = getCurrentInterval(this); - if (defined(currentInterval)) { - frame = loadFrame(this, currentInterval, frameState); - if (!frame.ready) { - frame = getLastReadyFrame(this, currentInterval); - } + if (!defined(currentInterval)) { + // Nothing to render this frame + return; + } - if (defined(frame)) { - renderFrame(this, frame, timeSinceLoad, isClipped, clippingPlanesDirty, frameState); - } + var clockStateChanged = false; + var clockMultiplier = getClockMultiplier(this); + var clockPaused = clockMultiplier === 0; + if (clockMultiplier !== this._clockMultiplier) { + clockStateChanged = true; + this._clockMultiplier = clockMultiplier; + } + + if (!defined(previousInterval) || clockPaused) { + previousInterval = currentInterval; } - // Start loading the approaching frame - var approachingInterval = getApproachingInterval(this); - if (defined(approachingInterval)) { - loadFrame(this, approachingInterval, frameState); + if (!defined(nextInterval) || clockStateChanged) { + nextInterval = getNextInterval(this); } + if (defined(nextInterval) && reachedInterval(this, currentInterval, nextInterval)) { + previousInterval = nextInterval; + nextInterval = getNextInterval(this); + } + + var frame = getLastReadyFrame(this, previousInterval, currentInterval); + + if (!defined(frame)) { + loadFrame(this, previousInterval, frameState); + } + + // If the frame we want to render isn't ready for any reason, render the last rendered frame + frame = defaultValue(frame, this._lastRenderedFrame); + + if (defined(frame)) { + renderFrame(this, frame, timeSinceLoad, isClipped, clippingPlanesDirty, frameState); + } + + if (defined(nextInterval)) { + // Start loading the next frame + loadFrame(this, nextInterval, frameState); + } + + this._previousInterval = previousInterval; + this._nextInterval = nextInterval; + this._lastRenderedFrame = frame; + var totalMemoryUsageInBytes = this._totalMemoryUsageInBytes; var maximumMemoryUsageInBytes = this.maximumMemoryUsage * 1024 * 1024; From 23fb23524e572d0a1394ebf56e76c55f73e34fc4 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 21 Jun 2018 22:59:15 -0400 Subject: [PATCH 11/35] Minor wording and organization changes --- Source/Scene/TimeDynamicPointCloud.js | 80 +++++++++------------------ 1 file changed, 25 insertions(+), 55 deletions(-) diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index 693180a2e0d7..6398ec01655b 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -44,7 +44,7 @@ define([ * * @param {Object} options Object with the following properties: * @param {Clock} options.clock A {@link Clock} instance that is used when determining the value for the time dimension. - * @param {TimeIntervalCollection} options.intervals A {@link TimeIntervalCollection} with its data property being an object containing a uri to a Point Cloud tile and an optional transform. + * @param {TimeIntervalCollection} options.intervals A {@link TimeIntervalCollection} with its data property being an object containing a uri to a Point Cloud (.pnts) tile and an optional transform. * @param {Boolean} [options.show=true] Determines if the point cloud will be shown. * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] A 4x4 transformation matrix that transforms the point cloud. * @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the point cloud casts or receives shadows from each light source. @@ -148,10 +148,8 @@ define([ this._frames = []; this._previousInterval = undefined; this._nextInterval = undefined; - this._clockMultiplier = 0.0; this._lastRenderedFrame = undefined; - - // For calculation average load time + this._clockMultiplier = 0.0; this._runningLoadTime = 0.0; this._runningLoadedFramesLength = 0; } @@ -220,10 +218,7 @@ define([ if (that._runningLoadedFramesLength === 0) { return undefined; } - - // TODO : the first frame to load will be slower due to initializing Draco, which impacts the average - var averageLoadTime = that._runningLoadTime / that._runningLoadedFramesLength; - return averageLoadTime; // Provide additional buffer since the actual load time fluctuates + return that._runningLoadTime / that._runningLoadedFramesLength; } var scratchDate = new JulianDate(); @@ -235,6 +230,10 @@ define([ return isAnimating ? multiplier : 0.0; } + function getIntervalIndex(that, interval) { + return that._intervals.indexOf(interval.start); + } + function getNextInterval(that) { var intervals = that._intervals; var clock = that._clock; @@ -246,7 +245,6 @@ define([ var averageLoadTime = getAverageLoadTime(that); if (!defined(averageLoadTime)) { - // Don't return the next interval until there is an average load time return undefined; } @@ -284,16 +282,6 @@ define([ return currentIndex <= nextIndex; } - function getIntervalIndex(that, interval) { - return that._intervals.indexOf(interval.start); - } - - function getFrame(that, interval) { - var index = getIntervalIndex(that, interval); - var frames = that._frames; - return frames[index]; - } - function requestFrame(that, interval, frameState) { var index = getIntervalIndex(that, interval); var frames = that._frames; @@ -302,11 +290,11 @@ define([ var transformArray = interval.data.transform; var transform = defined(transformArray) ? Matrix4.fromArray(transformArray) : undefined; frame = { - pointCloud : undefined, // Created after request resolves + pointCloud : undefined, transform : transform, timestamp : getTimestamp(), - sequential : true, // Whether the frame was loaded in sequential updates - ready : false, // True once point cloud is ready + sequential : true, + ready : false, touchedFrameNumber : frameState.frameNumber }; frames[index] = frame; @@ -328,7 +316,7 @@ define([ function prepareFrame(that, frame, frameState) { if (frame.touchedFrameNumber < frameState.frameNumber - 1) { - // If this frame was not loaded in sequential updates then it can't be used it for calculating average load time. + // If this frame was not loaded in sequential updates then it can't be used it for calculating the average load time. // For example: selecting a frame on the timeline, selecting another frame before the request finishes, then selecting this frame later. frame.sequential = false; } @@ -384,7 +372,6 @@ define([ var frame = requestFrame(that, interval, frameState); prepareFrame(that, frame, frameState); frame.touchedFrameNumber = frameState.frameNumber; - return frame; } function getUnloadCondition(frameState) { @@ -406,34 +393,32 @@ define([ that._totalMemoryUsageInBytes -= pointCloud.geometryByteLength; } if (defined(pointCloud)) { - // TODO : what happens if Draco decoding resolves after a frame is destroyed? pointCloud.destroy(); } + if (frame === that._lastRenderedFrame) { + that._lastRenderedFrame = undefined; + } frames[i] = undefined; } } } - that._lastRenderedFrame = undefined; // TODO : better approach than this? Maybe last rendered interval? } - function getLastReadyFrame(that, previousInterval, currentInterval) { + function getNearestReadyFrame(that, currentInterval, previousInterval) { var i; var frame; var frames = that._frames; - var clockMultiplier = getClockMultiplier(that); var currentIndex = getIntervalIndex(that, currentInterval); var previousIndex = getIntervalIndex(that, previousInterval); - if (clockMultiplier >= 0) { - // Animating forwards, so look backwards + if (currentIndex >= previousIndex) { // look backwards for (i = currentIndex; i >= previousIndex; --i) { frame = frames[i]; if (defined(frame) && frame.ready) { return frame; } } - } else { - // Animating backwards, so look forwards + } else { // look forwards for (i = currentIndex; i <= previousIndex; ++i) { frame = frames[i]; if (defined(frame) && frame.ready) { @@ -443,21 +428,6 @@ define([ } } - // TODO : if no frames have been loaded (don't know a load duration yet - - // TODO : need to take into account current real-time time it takes to process an average tile, because just fetching the next interval is naive - // TODO : make sure it works if clock is stopped - // TODO : measure time required to fetch the data and update it - // TODO : synchronous draco faster? - // TODO : clear any requests that didn't finish from the previous frame? - // TODO : once a skip factor is supported that introduces a can of worms - // TODO : LRU cache / GPU memory share? - // TODO : skipping frames - // TODO : would be helpful to have a bounding sphere associated with the point cloud, better for the draw command to have one - // TODO : czml to TimeDynamicIntervalCollection - // TODO : getAbsoluteUri removes the trailing dots - // TODO : there is a slight jump when the sim is paused because the previous frame is set to the current frame but the current frame isn't loaded - TimeDynamicPointCloud.prototype.update = function(frameState) { if (frameState.mode === SceneMode.MORPHING) { return; @@ -507,15 +477,14 @@ define([ var currentInterval = getCurrentInterval(this); if (!defined(currentInterval)) { - // Nothing to render this frame return; } - var clockStateChanged = false; + var clockMultiplierChanged = false; var clockMultiplier = getClockMultiplier(this); var clockPaused = clockMultiplier === 0; if (clockMultiplier !== this._clockMultiplier) { - clockStateChanged = true; + clockMultiplierChanged = true; this._clockMultiplier = clockMultiplier; } @@ -523,7 +492,7 @@ define([ previousInterval = currentInterval; } - if (!defined(nextInterval) || clockStateChanged) { + if (!defined(nextInterval) || clockMultiplierChanged ) { nextInterval = getNextInterval(this); } @@ -532,15 +501,16 @@ define([ nextInterval = getNextInterval(this); } - var frame = getLastReadyFrame(this, previousInterval, currentInterval); + var frame = getNearestReadyFrame(this, currentInterval, previousInterval); if (!defined(frame)) { + // The frame is not ready to render. This can happen when the simulation starts, when scrubbing the timeline + // to a frame that hasn't loaded yet, or reaching the next interval before its frame has finished loading. + // Just render the last rendered frame in its place until it finishes loading. loadFrame(this, previousInterval, frameState); + frame = this._lastRenderedFrame; } - // If the frame we want to render isn't ready for any reason, render the last rendered frame - frame = defaultValue(frame, this._lastRenderedFrame); - if (defined(frame)) { renderFrame(this, frame, timeSinceLoad, isClipped, clippingPlanesDirty, frameState); } From 59e169a714819c29b6262d2516ea5269e1d0f346 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 22 Jun 2018 10:11:18 -0400 Subject: [PATCH 12/35] Update Sandcastle demo --- Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html b/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html index d648c9ff3a06..7bcd94af7806 100644 --- a/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html +++ b/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html @@ -42,20 +42,19 @@ var properties = entity.properties; var frames = properties.frames; var intervals = frames.intervals; - + // Update uris to be absolute var length = intervals.length; for (var i = 0; i < length; ++i) { var data = intervals.get(i).data; - // TODO : getAbsoluteUri removes the trailing dots data.uri = basePath + data.uri; } - - var timeDynamicPointCloud = viewer.scene.primitives.add(new Cesium.TimeDynamicPointCloud({ + + viewer.scene.primitives.add(new Cesium.TimeDynamicPointCloud({ intervals : intervals, clock : viewer.clock, style : new Cesium.Cesium3DTileStyle({ - pointSize : 10 + pointSize : 5 }) })); }); From f5052076f2908971a7d3f3530847817505afb288 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 22 Jun 2018 14:17:57 -0400 Subject: [PATCH 13/35] Move uniform map creation so it's easier to merge in mutables --- Source/Scene/PointCloud.js | 152 ++++++++++++++++++++----------------- 1 file changed, 82 insertions(+), 70 deletions(-) diff --git a/Source/Scene/PointCloud.js b/Source/Scene/PointCloud.js index f7e8abff3851..ffc18a28e3cc 100644 --- a/Source/Scene/PointCloud.js +++ b/Source/Scene/PointCloud.js @@ -540,71 +540,6 @@ define([ } } } - var uniformMap = { - u_pointSizeAndTimeAndGeometricErrorAndDepthMultiplier : function() { - var scratch = scratchPointSizeAndTimeAndGeometricErrorAndDepthMultiplier; - scratch.x = pointCloud._attenuation ? pointCloud.maximumAttenuation : pointCloud._pointSize; - 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 modelViewMatrix = Matrix4.multiply(context.uniformState.view3D, pointCloud._modelMatrix, scratchClippingPlaneMatrix); - return Matrix4.multiply(modelViewMatrix, clippingPlanes.modelMatrix, scratchClippingPlaneMatrix); - } - }; - - if (isQuantized || isQuantizedDraco || isOctEncodedDraco) { - uniformMap = combine(uniformMap, { - u_quantizedVolumeScaleAndOctEncodedRange : function() { - var scratch = scratchQuantizedVolumeScaleAndOctEncodedRange; - if (defined(pointCloud._quantizedVolumeScale)) { - Cartesian3.clone(pointCloud._quantizedVolumeScale, scratch); - } - scratch.w = pointCloud._octEncodedRange; - return scratch; - } - }); - } var positionsVertexBuffer = Buffer.createVertexBuffer({ context : context, @@ -735,10 +670,6 @@ define([ attributes : attributes }); - if (defined(pointCloud._uniformMapLoaded)) { - uniformMap = pointCloud._uniformMapLoaded(uniformMap); - } - pointCloud._opaqueRenderState = RenderState.fromCache({ depthTest : { enabled : true @@ -761,7 +692,7 @@ define([ vertexArray : vertexArray, count : pointsLength, shaderProgram : undefined, // Updated in createShaders - uniformMap : uniformMap, + uniformMap : undefined, // Updated in createShaders renderState : isTranslucent ? pointCloud._translucentRenderState : pointCloud._opaqueRenderState, pass : isTranslucent ? Pass.TRANSLUCENT : pointCloud._opaquePass, owner : pointCloud, @@ -771,6 +702,85 @@ define([ }); } + 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.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 modelViewMatrix = Matrix4.multiply(context.uniformState.view3D, pointCloud._modelMatrix, scratchClippingPlaneMatrix); + return Matrix4.multiply(modelViewMatrix, clippingPlanes.modelMatrix, scratchClippingPlaneMatrix); + } + }; + + if (isQuantized || isQuantizedDraco || isOctEncodedDraco) { + uniformMap = combine(uniformMap, { + u_quantizedVolumeScaleAndOctEncodedRange : function() { + var scratch = scratchQuantizedVolumeScaleAndOctEncodedRange; + if (defined(pointCloud._quantizedVolumeScale)) { + Cartesian3.clone(pointCloud._quantizedVolumeScale, scratch); + } + scratch.w = pointCloud._octEncodedRange; + return scratch; + } + }); + } + + if (defined(pointCloud._uniformMapLoaded)) { + uniformMap = pointCloud._uniformMapLoaded(uniformMap); + } + + pointCloud._drawCommand.uniformMap = uniformMap; + } + var defaultProperties = ['POSITION', 'COLOR', 'NORMAL', 'POSITION_ABSOLUTE']; function getStyleableProperties(source, properties) { @@ -935,6 +945,8 @@ define([ attributeLocations[attributeName] = attribute.location; } + createUniformMap(pointCloud, frameState); + var vs = 'attribute vec3 a_position; \n' + 'varying vec4 v_color; \n' + 'uniform vec4 u_pointSizeAndTimeAndGeometricErrorAndDepthMultiplier; \n' + From bd67113a5558a2784b715c40b6984d988d3a0f17 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 26 Jun 2018 14:04:54 -0400 Subject: [PATCH 14/35] Avoid skipping requested frames --- Source/Scene/TimeDynamicPointCloud.js | 55 +++++++++++++++++++-------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index 6398ec01655b..81edfe3646d6 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -341,6 +341,8 @@ define([ } } } + + frame.touchedFrameNumber = frameState.frameNumber; } var scratchModelMatrix = new Matrix4(); @@ -371,7 +373,6 @@ define([ function loadFrame(that, interval, frameState) { var frame = requestFrame(that, interval, frameState); prepareFrame(that, frame, frameState); - frame.touchedFrameNumber = frameState.frameNumber; } function getUnloadCondition(frameState) { @@ -404,28 +405,55 @@ define([ } } - function getNearestReadyFrame(that, currentInterval, previousInterval) { + function getFrame(that, interval) { + var index = getIntervalIndex(that, interval); + var frame = that._frames[index]; + if (defined(frame) && frame.ready) { + return frame; + } + } + + function updateInterval(that, interval, frame, frameState) { + if (defined(frame)) { + if (frame.ready) { + return true; + } else { + loadFrame(that, interval, frameState); + return frame.ready; + } + } + return false; + } + + function getNearestReadyInterval(that, previousInterval, currentInterval, frameState) { var i; + var interval; var frame; + var intervals = that._intervals; var frames = that._frames; var currentIndex = getIntervalIndex(that, currentInterval); var previousIndex = getIntervalIndex(that, previousInterval); if (currentIndex >= previousIndex) { // look backwards for (i = currentIndex; i >= previousIndex; --i) { + interval = intervals.get(i); frame = frames[i]; - if (defined(frame) && frame.ready) { - return frame; + if (updateInterval(that, interval, frame, frameState)) { + return interval; } } } else { // look forwards for (i = currentIndex; i <= previousIndex; ++i) { + interval = intervals.get(i); frame = frames[i]; - if (defined(frame) && frame.ready) { - return frame; + if (updateInterval(that, interval, frame, frameState)) { + return interval; } } } + + // If no intervals are ready return the previous interval + return previousInterval; } TimeDynamicPointCloud.prototype.update = function(frameState) { @@ -492,21 +520,16 @@ define([ previousInterval = currentInterval; } - if (!defined(nextInterval) || clockMultiplierChanged ) { - nextInterval = getNextInterval(this); - } - - if (defined(nextInterval) && reachedInterval(this, currentInterval, nextInterval)) { - previousInterval = nextInterval; + if (!defined(nextInterval) || clockMultiplierChanged || reachedInterval(this, currentInterval, nextInterval)) { nextInterval = getNextInterval(this); } - var frame = getNearestReadyFrame(this, currentInterval, previousInterval); + previousInterval = getNearestReadyInterval(this, previousInterval, currentInterval, frameState); + var frame = getFrame(this, previousInterval); if (!defined(frame)) { - // The frame is not ready to render. This can happen when the simulation starts, when scrubbing the timeline - // to a frame that hasn't loaded yet, or reaching the next interval before its frame has finished loading. - // Just render the last rendered frame in its place until it finishes loading. + // The frame is not ready to render. This can happen when the simulation starts or when scrubbing the timeline + // to a frame that hasn't loaded yet. Just render the last rendered frame in its place until it finishes loading. loadFrame(this, previousInterval, frameState); frame = this._lastRenderedFrame; } From f30e6947efcf9b9724b56c3f867af269bd3f1efb Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 26 Jun 2018 14:13:45 -0400 Subject: [PATCH 15/35] Get the next interval more aggressively --- Source/Scene/TimeDynamicPointCloud.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index 81edfe3646d6..bc9d6ebb7668 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -234,7 +234,7 @@ define([ return that._intervals.indexOf(interval.start); } - function getNextInterval(that) { + function getNextInterval(that, currentInterval) { var intervals = that._intervals; var clock = that._clock; var multiplier = getClockMultiplier(that); @@ -251,10 +251,13 @@ define([ var time = JulianDate.addSeconds(clock.currentTime, averageLoadTime * multiplier, scratchDate); var index = intervals.indexOf(time); - if (multiplier >= 0) { - ++index; - } else { - --index; + var currentIndex = getIntervalIndex(that, currentInterval); + if (index === currentIndex) { + if (multiplier >= 0) { + ++index; + } else { + --index; + } } // Returns undefined if not in range @@ -521,7 +524,7 @@ define([ } if (!defined(nextInterval) || clockMultiplierChanged || reachedInterval(this, currentInterval, nextInterval)) { - nextInterval = getNextInterval(this); + nextInterval = getNextInterval(this, currentInterval); } previousInterval = getNearestReadyInterval(this, previousInterval, currentInterval, frameState); From a4f2d21aea3983e94f494c67d17fd5eaff66bfe3 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 26 Jun 2018 16:01:14 -0400 Subject: [PATCH 16/35] Calculate average load time from the last N frames --- Source/Scene/TimeDynamicPointCloud.js | 33 +++++++++++++++++++-------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index bc9d6ebb7668..a0055ad992de 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -1,4 +1,5 @@ define([ + '../Core/arrayFill', '../Core/Check', '../Core/combine', '../Core/defaultValue', @@ -17,6 +18,7 @@ define([ './SceneMode', './ShadowMode' ], function( + arrayFill, Check, combine, defaultValue, @@ -150,8 +152,13 @@ define([ this._nextInterval = undefined; this._lastRenderedFrame = undefined; this._clockMultiplier = 0.0; - this._runningLoadTime = 0.0; - this._runningLoadedFramesLength = 0; + + // For calculating average load time of the last N frames + this._runningSum = 0.0; + this._runningLength = 0; + this._runningIndex = 0; + this._runningSamples = arrayFill(new Array(5), 0.0); + this._runningAverage = 0.0; } defineProperties(TimeDynamicPointCloud.prototype, { @@ -215,10 +222,10 @@ define([ }; function getAverageLoadTime(that) { - if (that._runningLoadedFramesLength === 0) { + if (that._runningLength === 0) { return undefined; } - return that._runningLoadTime / that._runningLoadedFramesLength; + return that._runningAverage; } var scratchDate = new JulianDate(); @@ -317,6 +324,15 @@ define([ return frame; } + function updateAverageLoadTime(that, loadTime) { + that._runningSum += loadTime; + that._runningSum -= that._runningSamples[that._runningIndex]; + that._runningSamples[that._runningIndex] = loadTime; + that._runningLength = Math.min(that._runningLength + 1, that._runningSamples.length); + that._runningIndex = (that._runningIndex + 1) % that._runningSamples.length; + that._runningAverage = that._runningSum / that._runningLength; + } + function prepareFrame(that, frame, frameState) { if (frame.touchedFrameNumber < frameState.frameNumber - 1) { // If this frame was not loaded in sequential updates then it can't be used it for calculating the average load time. @@ -339,8 +355,8 @@ define([ commandList.length = lengthBeforeUpdate; // Don't allow preparing frame to insert commands. if (frame.sequential) { // Update the values used to calculate average load time - that._runningLoadTime += (getTimestamp() - frame.timestamp) / 1000.0; - ++that._runningLoadedFramesLength; + var loadTime = (getTimestamp() - frame.timestamp) / 1000.0; + updateAverageLoadTime(that, loadTime); } } } @@ -420,10 +436,9 @@ define([ if (defined(frame)) { if (frame.ready) { return true; - } else { - loadFrame(that, interval, frameState); - return frame.ready; } + loadFrame(that, interval, frameState); + return frame.ready; } return false; } From 178003f98d2e3d53fe5a322af7391b823781f70c Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 26 Jun 2018 17:11:29 -0400 Subject: [PATCH 17/35] Documentation updates --- Source/Scene/TimeDynamicPointCloud.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index a0055ad992de..fd17a5d19236 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -39,19 +39,24 @@ define([ 'use strict'; /** - * Provides functionality for playback of time-dynamic point cloud data. + * Provides playback of time-dynamic point cloud data. + *

+ * Point cloud frames are prefetched in intervals determined by the average frame load time and the current clock speed. + * If intermediate frames cannot be loaded in time to meet playback speed, they will be skipped. If frames are sufficiently + * small or the clock is sufficiently slow then no frames will be skipped. + *

* * @alias TimeDynamicPointCloud * @constructor * * @param {Object} options Object with the following properties: * @param {Clock} options.clock A {@link Clock} instance that is used when determining the value for the time dimension. - * @param {TimeIntervalCollection} options.intervals A {@link TimeIntervalCollection} with its data property being an object containing a uri to a Point Cloud (.pnts) tile and an optional transform. + * @param {TimeIntervalCollection} options.intervals A {@link TimeIntervalCollection} with its data property being an object containing a uri to a 3D Tiles Point Cloud tile and an optional transform. * @param {Boolean} [options.show=true] Determines if the point cloud will be shown. * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] A 4x4 transformation matrix that transforms the point cloud. * @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the point cloud casts or receives shadows from each light source. * @param {Number} [options.maximumMemoryUsage=256] The maximum amount of memory in MB that can be used by the point cloud. - * @param {Object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point size based on geometric error and eye dome lighting. + * @param {Object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation and eye dome lighting. * @param {Cesium3DTileStyle} [options.style] The style, defined using the {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language}, applied to each point in the point cloud. * @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the point cloud. */ @@ -95,7 +100,12 @@ define([ /** * The maximum amount of GPU memory (in MB) that may be used to cache point cloud frames. - * + *

+ * Frames that are not being loaded or rendered are unloaded to enforce this. + *

+ *

+ * If decreasing this value results in unloading tiles, the tiles are unloaded the next frame. + *

* @memberof TimeDynamicPointCloud.prototype * * @type {Number} From b16676634fae695fc2adaf4b662643990ecbcc57 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 26 Jun 2018 20:21:36 -0400 Subject: [PATCH 18/35] Fix edge case where first frame is not loaded sequentially and an average load time can't be determined --- Source/Scene/TimeDynamicPointCloud.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index fd17a5d19236..0cb944e43f91 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -233,7 +233,8 @@ define([ function getAverageLoadTime(that) { if (that._runningLength === 0) { - return undefined; + // Before any frames have loaded make a best guess about the average load time + return 0.05; } return that._runningAverage; } @@ -261,10 +262,6 @@ define([ } var averageLoadTime = getAverageLoadTime(that); - if (!defined(averageLoadTime)) { - return undefined; - } - var time = JulianDate.addSeconds(clock.currentTime, averageLoadTime * multiplier, scratchDate); var index = intervals.indexOf(time); From 3455dc3dd9941098d37af167d4c02507b73d511c Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 26 Jun 2018 20:22:44 -0400 Subject: [PATCH 19/35] Initial work for computing bounding sphere --- Source/Scene/PointCloud.js | 71 ++++++++++++++++++++----- Source/Scene/PointCloud3DTileContent.js | 9 ++-- Source/Scene/TimeDynamicPointCloud.js | 23 +++++++- 3 files changed, 85 insertions(+), 18 deletions(-) diff --git a/Source/Scene/PointCloud.js b/Source/Scene/PointCloud.js index ffc18a28e3cc..a2ae6f738843 100644 --- a/Source/Scene/PointCloud.js +++ b/Source/Scene/PointCloud.js @@ -1,7 +1,9 @@ define([ '../Core/arraySlice', + '../Core/BoundingSphere', '../Core/Cartesian3', '../Core/Cartesian4', + '../Core/Math', '../Core/Check', '../Core/Color', '../Core/combine', @@ -39,8 +41,10 @@ define([ './ShadowMode' ], function( arraySlice, + BoundingSphere, Cartesian3, Cartesian4, + CesiumMath, Check, Color, combine, @@ -161,6 +165,7 @@ define([ 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; @@ -171,7 +176,7 @@ define([ this.time = 0.0; // For styling this.shadows = ShadowMode.ENABLED; - this.boundingVolume = undefined; + this.boundingSphere = undefined; this.clippingPlanes = undefined; this.isClipped = false; @@ -452,6 +457,31 @@ define([ pointCloud._hasBatchIds = hasBatchIds; } + var scratchMin = new Cartesian3(); + var scratchMax = new Cartesian3(); + var scratchPosition = new Cartesian3(); + + function computeApproximateBoundingSphereFromPositions(positions) { + var pointsLength = positions.length / 3; + var samplesLength = Math.min(pointsLength, 20); + 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(i * pointsLength / samplesLength); + var position = Cartesian3.unpack(positions, index * 3, scratchPosition); + Cartesian3.minimumByComponent(min, position, min); + Cartesian3.maximumByComponent(max, position, max); + } + + var boundingSphere = BoundingSphere.fromCornerPoints(min, max); + if (pointsLength === 1) { + boundingSphere.radius = CesiumMath.EPSILON2; // To avoid radius of zero + } + return boundingSphere; + } + function prepareVertexAttribute(typedArray) { // WebGL does not support UNSIGNED_INT, INT, or DOUBLE vertex attributes. Convert these to FLOAT. var componentDatatype = ComponentDatatype.fromTypedArray(typedArray); @@ -473,6 +503,7 @@ define([ var numberOfAttributes = 4; var scratchClippingPlaneMatrix = new Matrix4(); + function createResources(pointCloud, frameState) { var context = frameState.context; var parsedContent = pointCloud._parsedContent; @@ -602,6 +633,18 @@ define([ strideInBytes : 0 }); + if (pointCloud._cull) { + if (isQuantized || isQuantizedDraco) { + // Quantized volume offset is applied to the model matrix, not the bounding sphere + var scale = pointCloud._quantizedVolumeScale; + var center = Cartesian3.multiplyByScalar(scale, 0.5, new Cartesian3()); + var radius = Cartesian3.maximumComponent(scale) * 0.5; + pointCloud.boundingSphere = new BoundingSphere(center, radius); + } else { + pointCloud.boundingSphere = computeApproximateBoundingSphereFromPositions(positions); + } + } + if (hasColors) { if (isRGB565) { attributes.push({ @@ -685,8 +728,8 @@ define([ }); pointCloud._drawCommand = new DrawCommand({ - boundingVolume : undefined, // Updated in update - cull : false, // Already culled by 3D Tiles + boundingVolume : new BoundingSphere(), + cull : pointCloud._cull, modelMatrix : new Matrix4(), primitiveType : PrimitiveType.POINTS, vertexArray : vertexArray, @@ -1148,10 +1191,6 @@ define([ } } - var scratchComputedTranslation = new Cartesian4(); - var scratchComputedMatrixIn2D = new Matrix4(); - var scratchModelMatrix = new Matrix4(); - function decodeDraco(pointCloud, context) { if (pointCloud._decodingState === DecodingState.READY) { return false; @@ -1207,6 +1246,10 @@ define([ return true; } + var scratchComputedTranslation = new Cartesian4(); + var scratchModelMatrix = new Matrix4(); + var scratchScale = new Cartesian3(); + PointCloud.prototype.update = function(frameState) { var context = frameState.context; var decoding = decodeDraco(this, context); @@ -1247,16 +1290,20 @@ define([ var translation = Matrix4.getColumn(modelMatrix, 3, scratchComputedTranslation); if (!Cartesian4.equals(translation, Cartesian4.UNIT_W)) { Transforms.basisTo2D(projection, modelMatrix, modelMatrix); - } else { - var center = this.boundingVolume.center; - var to2D = Transforms.wgs84To2DModelMatrix(projection, center, scratchComputedMatrixIn2D); - Matrix4.multiply(to2D, modelMatrix, modelMatrix); } } Matrix4.clone(modelMatrix, this._drawCommand.modelMatrix); - this._drawCommand.boundingVolume = this.boundingVolume; + 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) { diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 92c171c8f00e..61b6196c62ab 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -62,6 +62,7 @@ define([ this._pointCloud = new PointCloud({ arrayBuffer : arrayBuffer, byteOffset : byteOffset, + cull : false, opaquePass : Pass.CESIUM_3D_TILE, vertexShaderLoaded : getVertexShaderLoaded(this), fragmentShaderLoaded : getFragmentShaderLoaded(this), @@ -286,11 +287,11 @@ define([ batchTable.update(tileset, frameState); } - var boundingVolume; + var boundingSphere; if (defined(tile._contentBoundingVolume)) { - boundingVolume = mode === SceneMode.SCENE3D ? tile._contentBoundingVolume.boundingSphere : tile._contentBoundingVolume2D.boundingSphere; + boundingSphere = mode === SceneMode.SCENE3D ? tile._contentBoundingVolume.boundingSphere : tile._contentBoundingVolume2D.boundingSphere; } else { - boundingVolume = mode === SceneMode.SCENE3D ? tile._boundingVolume.boundingSphere : tile._boundingVolume2D.boundingSphere; + boundingSphere = mode === SceneMode.SCENE3D ? tile._boundingVolume.boundingSphere : tile._boundingVolume2D.boundingSphere; } var styleDirty = this._styleDirty; @@ -301,7 +302,7 @@ define([ pointCloud.modelMatrix = tile.computedTransform; pointCloud.time = tileset.timeSinceLoad; pointCloud.shadows = tileset.shadows; - pointCloud.boundingVolume = boundingVolume; + pointCloud.boundingSphere = boundingSphere; pointCloud.clippingPlanes = clippingPlanes; pointCloud.isClipped = defined(clippingPlanes) && clippingPlanes.enabled && tile._isClipped; pointCloud.clippingPlanesDirty = tile.clippingPlanesDirty; diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index 0cb944e43f91..113afca4f878 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -320,6 +320,7 @@ define([ }).then(function(arrayBuffer) { frame.pointCloud = new PointCloud({ arrayBuffer : arrayBuffer, + cull : true, fragmentShaderLoaded : getFragmentShaderLoaded, uniformMapLoaded : getUniformMapLoaded(that), pickIdLoaded : getPickIdLoaded @@ -373,6 +374,24 @@ define([ var scratchModelMatrix = new Matrix4(); + function getGeometricError(that, pointCloud) { + var pointCloudShading = that.pointCloudShading; + if (defined(pointCloudShading) && defined(pointCloudShading.baseResolution)) { + return pointCloudShading.baseResolution; + } + return CesiumMath.cbrt(pointCloud.boundingSphere.volume() / pointCloud.pointsLength); + } + + function getMaximumAttenuation(that) { + var pointCloudShading = that.pointCloudShading; + if (defined(pointCloudShading) && defined(pointCloudShading.maximumAttenuation)) { + return pointCloudShading.maximumAttenuation; + } + + // Return a hardcoded maximum attenuation. For a tileset this would instead be the maximum screen space error. + return 10.0; + } + function renderFrame(that, frame, timeSinceLoad, isClipped, clippingPlanesDirty, frameState) { var pointCloud = frame.pointCloud; var transform = defaultValue(frame.transform, Matrix4.IDENTITY); @@ -388,9 +407,9 @@ define([ var pointCloudShading = that.pointCloudShading; if (defined(pointCloudShading)) { pointCloud.attenuation = pointCloudShading.attenuation; - pointCloud.geometricError = 10.0; // TODO : If we had a bounding volume we could derive it + pointCloud.geometricError = getGeometricError(that, pointCloud); pointCloud.geometricErrorScale = pointCloudShading.geometricErrorScale; - pointCloud.maximumAttenuation = defined(pointCloudShading.maximumAttenuation) ? pointCloudShading.maximumAttenuation : 10; + pointCloud.maximumAttenuation = getMaximumAttenuation(that); } pointCloud.update(frameState); frame.touchedFrameNumber = frameState.frameNumber; From 70cac081d4ef89386bb64037a89230c0a86149ee Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 26 Jun 2018 21:57:48 -0400 Subject: [PATCH 20/35] Add PERFORMANCE_IDEA for sharing GPU resources --- Source/Scene/TimeDynamicPointCloud.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index 113afca4f878..559b626806ef 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -318,6 +318,8 @@ define([ Resource.fetchArrayBuffer({ url : interval.data.uri }).then(function(arrayBuffer) { + // PERFORMANCE_IDEA: share a memory pool, render states, shaders, and other resources among all + // frames. Each frame just needs an index/offset into the pool. frame.pointCloud = new PointCloud({ arrayBuffer : arrayBuffer, cull : true, From 9d6552cd7dfebba2a9423f1f76183f8af8955f07 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 26 Jun 2018 22:45:14 -0400 Subject: [PATCH 21/35] Make quantizedVolumeScale consistent across Draco and 16-bit quantized so bounding volume computation is correct --- Source/Scene/PointCloud.js | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/Source/Scene/PointCloud.js b/Source/Scene/PointCloud.js index a2ae6f738843..e98de1dd4e61 100644 --- a/Source/Scene/PointCloud.js +++ b/Source/Scene/PointCloud.js @@ -358,6 +358,7 @@ define([ 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)) { @@ -614,13 +615,10 @@ define([ if (isQuantized) { componentDatatype = ComponentDatatype.UNSIGNED_SHORT; - normalize = true; // Convert position to 0 to 1 before entering the shader } else if (isQuantizedDraco) { componentDatatype = (quantizedRange <= 255) ? ComponentDatatype.UNSIGNED_BYTE : ComponentDatatype.UNSIGNED_SHORT; - normalize = false; // Normalization is done in the shader based on quantizationBits } else { componentDatatype = ComponentDatatype.FLOAT; - normalize = false; } attributes.push({ @@ -628,18 +626,14 @@ define([ vertexBuffer : positionsVertexBuffer, componentsPerAttribute : 3, componentDatatype : componentDatatype, - normalize : normalize, + normalize : false, offsetInBytes : 0, strideInBytes : 0 }); if (pointCloud._cull) { if (isQuantized || isQuantizedDraco) { - // Quantized volume offset is applied to the model matrix, not the bounding sphere - var scale = pointCloud._quantizedVolumeScale; - var center = Cartesian3.multiplyByScalar(scale, 0.5, new Cartesian3()); - var radius = Cartesian3.maximumComponent(scale) * 0.5; - pointCloud.boundingSphere = new BoundingSphere(center, radius); + pointCloud.boundingSphere = BoundingSphere.fromCornerPoints(Cartesian3.ZERO, pointCloud._quantizedVolumeScale); } else { pointCloud.boundingSphere = computeApproximateBoundingSphereFromPositions(positions); } @@ -809,7 +803,8 @@ define([ u_quantizedVolumeScaleAndOctEncodedRange : function() { var scratch = scratchQuantizedVolumeScaleAndOctEncodedRange; if (defined(pointCloud._quantizedVolumeScale)) { - Cartesian3.clone(pointCloud._quantizedVolumeScale, scratch); + var scale = Cartesian3.clone(pointCloud._quantizedVolumeScale, scratch); + Cartesian3.divideByScalar(scale, pointCloud._quantizedRange, scratch); } scratch.w = pointCloud._octEncodedRange; return scratch; @@ -1210,8 +1205,8 @@ define([ var decodedBatchIds = defined(result.BATCH_ID) ? result.BATCH_ID.array : undefined; if (defined(decodedPositions) && pointCloud._isQuantizedDraco) { var quantization = result.POSITION.data.quantization; - var scale = quantization.range / (1 << quantization.quantizationBits); - pointCloud._quantizedVolumeScale = Cartesian3.fromElements(scale, scale, scale); + var range = quantization.range; + pointCloud._quantizedVolumeScale = Cartesian3.fromElements(range, range, range); pointCloud._quantizedVolumeOffset = Cartesian3.unpack(quantization.minValues); pointCloud._quantizedRange = (1 << quantization.quantizationBits) - 1.0; } @@ -1276,7 +1271,8 @@ define([ if (modelMatrixDirty) { Matrix4.clone(this.modelMatrix, this._modelMatrix); - var modelMatrix = Matrix4.clone(this._modelMatrix, scratchModelMatrix); + var modelMatrix = this._drawCommand.modelMatrix; + Matrix4.clone(this._modelMatrix, modelMatrix); if (defined(this._rtcCenter)) { Matrix4.multiplyByTranslation(modelMatrix, this._rtcCenter, modelMatrix); @@ -1293,8 +1289,6 @@ define([ } } - Matrix4.clone(modelMatrix, this._drawCommand.modelMatrix); - var boundingSphere = this._drawCommand.boundingVolume; BoundingSphere.clone(this.boundingSphere, boundingSphere); From e1cf35019ef9bc2e779fd197e86ee46b020521ca Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 26 Jun 2018 23:36:37 -0400 Subject: [PATCH 22/35] Make useNormals more like useColors and backFaceCulling --- Source/Scene/PointCloud.js | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/Source/Scene/PointCloud.js b/Source/Scene/PointCloud.js index e98de1dd4e61..9e3850b581f0 100644 --- a/Source/Scene/PointCloud.js +++ b/Source/Scene/PointCloud.js @@ -149,6 +149,10 @@ define([ this.backFaceCulling = false; this._backFaceCulling = false; + // Whether to enable normal shading + this.normalShading = true; + this._normalShading = true; + this._opaqueRenderState = undefined; this._translucentRenderState = undefined; @@ -875,6 +879,7 @@ define([ 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; @@ -947,13 +952,20 @@ define([ 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 (hasNormals) { + if (usesNormals) { attributeLocations.a_normal = normalLocation; } if (hasBatchIds) { @@ -1015,7 +1027,7 @@ define([ vs += 'attribute vec3 a_color; \n'; } } - if (hasNormals) { + if (usesNormals) { if (isOctEncoded16P || isOctEncodedDraco) { vs += 'attribute vec2 a_normal; \n'; } else { @@ -1079,7 +1091,7 @@ define([ } vs += ' vec3 position_absolute = vec3(czm_model * vec4(position, 1.0)); \n'; - if (hasNormals) { + if (usesNormals) { if (isOctEncoded16P) { vs += ' vec3 normal = czm_octDecode(a_normal); \n'; } else if (isOctEncodedDraco) { @@ -1113,7 +1125,7 @@ define([ vs += ' color = color * u_highlightColor; \n'; - if (hasNormals) { + if (usesNormals && normalShading) { vs += ' normal = czm_normal * normal; \n' + ' float diffuseStrength = czm_getLambertDiffuse(czm_sunDirectionEC, normal); \n' + ' diffuseStrength = max(diffuseStrength, 0.4); \n' + // Apply some ambient lighting @@ -1123,7 +1135,7 @@ define([ vs += ' v_color = color; \n' + ' gl_Position = czm_modelViewProjection * vec4(position, 1.0); \n'; - if (hasNormals && backFaceCulling) { + if (usesNormals && backFaceCulling) { vs += ' float visible = step(-normal.z, 0.0); \n' + ' gl_Position *= visible; \n' + ' gl_PointSize *= visible; \n'; @@ -1242,7 +1254,6 @@ define([ } var scratchComputedTranslation = new Cartesian4(); - var scratchModelMatrix = new Matrix4(); var scratchScale = new Cartesian3(); PointCloud.prototype.update = function(frameState) { @@ -1314,6 +1325,11 @@ define([ shadersDirty = true; } + if (this.normalShading !== this._normalShading) { + this._normalShading = this.normalShading; + shadersDirty = true; + } + if (this._style !== this.style || this.styleDirty) { this._style = this.style; shadersDirty = true; From 52632b3cb11c124627595bca9d34df0ecb714fde Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 27 Jun 2018 15:14:15 -0400 Subject: [PATCH 23/35] Updated demo --- .../PointCloud/PointCloudTimeDynamic/0.pnts | Bin 0 -> 33332 bytes .../PointCloud/PointCloudTimeDynamic/1.pnts | Bin 0 -> 33332 bytes .../PointCloud/PointCloudTimeDynamic/2.pnts | Bin 0 -> 33332 bytes .../PointCloud/PointCloudTimeDynamic/3.pnts | Bin 0 -> 33332 bytes .../PointCloud/PointCloudTimeDynamic/4.pnts | Bin 0 -> 33332 bytes .../gallery/Time Dynamic Point Cloud.html | 74 ++++++++++++------ .../gallery/Time Dynamic Point Cloud.jpg | Bin 0 -> 9306 bytes .../Web Map Tile Service with Time.html | 6 +- 8 files changed, 55 insertions(+), 25 deletions(-) create mode 100644 Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/0.pnts create mode 100644 Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/1.pnts create mode 100644 Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/2.pnts create mode 100644 Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/3.pnts create mode 100644 Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/4.pnts create mode 100644 Apps/Sandcastle/gallery/Time Dynamic Point Cloud.jpg diff --git a/Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/0.pnts b/Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/0.pnts new file mode 100644 index 0000000000000000000000000000000000000000..cc1fb4385b4dcb717b0eba1fa1bb285032327317 GIT binary patch literal 33332 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 literal 0 HcmV?d00001 diff --git a/Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/1.pnts b/Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/1.pnts new file mode 100644 index 0000000000000000000000000000000000000000..9a6c79ec31fc42dc5b3303d139e8bc84c3ae6abe GIT binary patch literal 33332 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 literal 0 HcmV?d00001 diff --git a/Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/2.pnts b/Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/2.pnts new file mode 100644 index 0000000000000000000000000000000000000000..7bcab33a5c45320f0fbe89da1f3d8282a78caf69 GIT binary patch literal 33332 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 a_C5Q){lI=`Ke8X&Pwc1mGyA#y!v23>U@vw6 literal 0 HcmV?d00001 diff --git a/Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/3.pnts b/Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/3.pnts new file mode 100644 index 0000000000000000000000000000000000000000..4c110c36b04d8ce57f2d0989f88cf5282c079806 GIT binary patch literal 33332 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 U`}PC-q5a5yY(KG|+RyC&5A76@dH?_b literal 0 HcmV?d00001 diff --git a/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html b/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html index 7bcd94af7806..c7f79b16c261 100644 --- a/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html +++ b/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html @@ -33,32 +33,62 @@ shouldAnimate : true }); -var basePath = '../../SampleData/test/PointCloudTimeDynamic/'; -var czmlPath = basePath + 'frames.czml'; -var dataSource = viewer.dataSources.add(Cesium.CzmlDataSource.load(czmlPath)); -dataSource.then(function(dataSource) { - var entities = dataSource.entities; - var entity = entities.getById('Time Dynamic Point Cloud'); - var properties = entity.properties; - var frames = properties.frames; - var intervals = frames.intervals; +var dates = [ + '2018-07-19T15:18:00Z', + '2018-07-19T15:18:00.5Z', + '2018-07-19T15:18:01Z', + '2018-07-19T15:18:01.5Z', + '2018-07-19T15:18:02Z', + '2018-07-19T15:18:02.5Z' +]; - // Update uris to be absolute - var length = intervals.length; - for (var i = 0; i < length; ++i) { - var data = intervals.get(i).data; - data.uri = basePath + data.uri; - } +var transforms = [ + Cesium.Matrix4.fromColumnMajorArray([0.968635634376879,0.24848542777253735,0,0,-0.15986460794399626,0.6231776137472074,0.7655670897127491,0,0.190232265775849,-0.7415555636019701,0.6433560687121489,0,1215012.8828876738,-4736313.051199594,4081605.22126042,1]), + Cesium.Matrix4.fromColumnMajorArray([0.968634888916237,0.24848833367832227,0,0,-0.1598664774761181,0.6231771341505793,0.7655670897127493,0,0.19023449044168372,-0.7415549929018358,0.6433560687121489,0,1215027.0918213597,-4736309.406139632,4081605.22126042,1]), + Cesium.Matrix4.fromColumnMajorArray([0.9686341434468771,0.24849123958187078,0,0,-0.1598683470068011,0.6231766545483426,0.7655670897127493,0,0.19023671510580634,-0.7415544221950274,0.6433560687121489,0,1215041.3007441103,-4736305.761037043,4081605.22126042,1]), + Cesium.Matrix4.fromColumnMajorArray([0.9686333979687994,0.24849414548318288,0,0,-0.15987021653604533,0.6231761749404972,0.7655670897127491,0,0.19023893976821685,-0.7415538514815451,0.6433560687121489,0,1215055.5096559257,-4736302.115891827,4081605.22126042,1]), + Cesium.Matrix4.fromColumnMajorArray([0.9686326524820043,0.2484970513822586,0,0,-0.15987208606385075,0.6231756953270434,0.7655670897127492,0,0.19024116442891523,-0.7415532807613887,0.6433560687121489,0,1215069.7185568055,-4736298.470703985,4081605.22126042,1]) +]; - viewer.scene.primitives.add(new Cesium.TimeDynamicPointCloud({ - intervals : intervals, - clock : viewer.clock, - style : new Cesium.Cesium3DTileStyle({ - pointSize : 5 - }) - })); +var uris = [ + '../../SampleData/tilesets/PointCloud/PointCloudTimeDynamic/0.pnts', + '../../SampleData/tilesets/PointCloud/PointCloudTimeDynamic/1.pnts', + '../../SampleData/tilesets/PointCloud/PointCloudTimeDynamic/2.pnts', + '../../SampleData/tilesets/PointCloud/PointCloudTimeDynamic/3.pnts', + '../../SampleData/tilesets/PointCloud/PointCloudTimeDynamic/4.pnts', +]; + +function dataCallback(interval, index) { + return { + uri: uris[index], + transform: transforms[index] + }; +} + +var timeIntervalCollection = Cesium.TimeIntervalCollection.fromIso8601DateArray({ + iso8601Dates: dates, + dataCallback: dataCallback }); +viewer.scene.primitives.add(new Cesium.TimeDynamicPointCloud({ + intervals : timeIntervalCollection, + clock : viewer.clock, + style : new Cesium.Cesium3DTileStyle({ + pointSize : 5 + }) +})); + +var start = Cesium.JulianDate.fromIso8601(dates[0]); +var stop = Cesium.JulianDate.fromIso8601(dates[dates.length - 1]); + +viewer.timeline.zoomTo(start, stop); + +var clock = viewer.clock; +clock.startTime = start; +clock.currentTime = start; +clock.stopTime = stop; +clock.clockRange = Cesium.ClockRange.LOOP_STOP; + viewer.camera.setView({ destination: new Cesium.Cartesian3(1215092.7260503182, -4736533.485206121, 4081535.746782565), orientation: new Cesium.HeadingPitchRoll(6.207713496216043, -0.6084278199488593, 6.282880789149928), diff --git a/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.jpg b/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d4f84a1d5932f61f1beb14ebed4148ff3a4db953 GIT binary patch literal 9306 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 q?|p5~e&hhCQNVWfNO#q^8G;WC> Date: Wed, 27 Jun 2018 15:23:54 -0400 Subject: [PATCH 24/35] Updated CHANGES.md --- CHANGES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 199b25c54fc6..0bf127fd551b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ Change Log ========== +### 1.48 - 2018-08-01 + +##### Additions :tada: +* Added `TimeDynamicPointCloud` for playback of time-dynamic point cloud data, where each frame is a 3D Tiles Point Cloud tile. [#6721](https://github.com/AnalyticalGraphicsInc/cesium/pull/6721) + ### 1.47 - 2018-07-02 ##### Breaking Changes :mega: From ed8624cf7f472d9b5f7f223eb9d5379014fc7a6b Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 28 Jun 2018 14:46:00 -0400 Subject: [PATCH 25/35] Pass in same point cloud state to prepareFrame as renderFrame to minimize the number of resource updates --- Source/Scene/TimeDynamicPointCloud.js | 48 +++++++++++++++++---------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index 559b626806ef..cfeb35404ae2 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -343,7 +343,7 @@ define([ that._runningAverage = that._runningSum / that._runningLength; } - function prepareFrame(that, frame, frameState) { + function prepareFrame(that, frame, updateState, frameState) { if (frame.touchedFrameNumber < frameState.frameNumber - 1) { // If this frame was not loaded in sequential updates then it can't be used it for calculating the average load time. // For example: selecting a frame on the timeline, selecting another frame before the request finishes, then selecting this frame later. @@ -356,7 +356,7 @@ define([ // Call update to prepare renderer resources. Don't render anything yet. var commandList = frameState.commandList; var lengthBeforeUpdate = commandList.length; - pointCloud.update(frameState); + renderFrame(that, frame, updateState, frameState); if (pointCloud.ready) { // Point cloud became ready this update @@ -380,8 +380,10 @@ define([ var pointCloudShading = that.pointCloudShading; if (defined(pointCloudShading) && defined(pointCloudShading.baseResolution)) { return pointCloudShading.baseResolution; + } else if (defined(pointCloud.boundingSphere)) { + return CesiumMath.cbrt(pointCloud.boundingSphere.volume() / pointCloud.pointsLength); } - return CesiumMath.cbrt(pointCloud.boundingSphere.volume() / pointCloud.pointsLength); + return 0.0; } function getMaximumAttenuation(that) { @@ -394,17 +396,17 @@ define([ return 10.0; } - function renderFrame(that, frame, timeSinceLoad, isClipped, clippingPlanesDirty, frameState) { + function renderFrame(that, frame, updateState, frameState) { var pointCloud = frame.pointCloud; var transform = defaultValue(frame.transform, Matrix4.IDENTITY); pointCloud.modelMatrix = Matrix4.multiplyTransformation(that.modelMatrix, transform, scratchModelMatrix); pointCloud.style = that.style; pointCloud.styleDirty = that._styleDirty; - pointCloud.time = timeSinceLoad; + pointCloud.time = updateState.timeSinceLoad; pointCloud.shadows = that.shadows; pointCloud.clippingPlanes = that._clippingPlanes; - pointCloud.isClipped = isClipped; - pointCloud.clippingPlanesDirty = clippingPlanesDirty; + pointCloud.isClipped = updateState.isClipped; + pointCloud.clippingPlanesDirty = updateState.clippingPlanesDirty; var pointCloudShading = that.pointCloudShading; if (defined(pointCloudShading)) { @@ -417,9 +419,9 @@ define([ frame.touchedFrameNumber = frameState.frameNumber; } - function loadFrame(that, interval, frameState) { + function loadFrame(that, interval, updateState, frameState) { var frame = requestFrame(that, interval, frameState); - prepareFrame(that, frame, frameState); + prepareFrame(that, frame, updateState, frameState); } function getUnloadCondition(frameState) { @@ -460,18 +462,18 @@ define([ } } - function updateInterval(that, interval, frame, frameState) { + function updateInterval(that, interval, frame, updateState, frameState) { if (defined(frame)) { if (frame.ready) { return true; } - loadFrame(that, interval, frameState); + loadFrame(that, interval, updateState, frameState); return frame.ready; } return false; } - function getNearestReadyInterval(that, previousInterval, currentInterval, frameState) { + function getNearestReadyInterval(that, previousInterval, currentInterval, updateState, frameState) { var i; var interval; var frame; @@ -484,7 +486,7 @@ define([ for (i = currentIndex; i >= previousIndex; --i) { interval = intervals.get(i); frame = frames[i]; - if (updateInterval(that, interval, frame, frameState)) { + if (updateInterval(that, interval, frame, updateState, frameState)) { return interval; } } @@ -492,7 +494,7 @@ define([ for (i = currentIndex; i <= previousIndex; ++i) { interval = intervals.get(i); frame = frames[i]; - if (updateInterval(that, interval, frame, frameState)) { + if (updateInterval(that, interval, frame, updateState, frameState)) { return interval; } } @@ -502,6 +504,12 @@ define([ return previousInterval; } + var updateState = { + timeSinceLoad : 0, + isClipped : false, + clippingPlanesDirty : false + }; + TimeDynamicPointCloud.prototype.update = function(frameState) { if (frameState.mode === SceneMode.MORPHING) { return; @@ -540,6 +548,10 @@ define([ clippingPlanesDirty = true; } + updateState.timeSinceLoad = timeSinceLoad; + updateState.isClipped = isClipped; + updateState.clippingPlanesDirty = clippingPlanesDirty; + var pointCloudShading = this.pointCloudShading; var eyeDomeLighting = this._pointCloudEyeDomeLighting; @@ -570,23 +582,23 @@ define([ nextInterval = getNextInterval(this, currentInterval); } - previousInterval = getNearestReadyInterval(this, previousInterval, currentInterval, frameState); + previousInterval = getNearestReadyInterval(this, previousInterval, currentInterval, updateState, frameState); var frame = getFrame(this, previousInterval); if (!defined(frame)) { // The frame is not ready to render. This can happen when the simulation starts or when scrubbing the timeline // to a frame that hasn't loaded yet. Just render the last rendered frame in its place until it finishes loading. - loadFrame(this, previousInterval, frameState); + loadFrame(this, previousInterval, updateState, frameState); frame = this._lastRenderedFrame; } if (defined(frame)) { - renderFrame(this, frame, timeSinceLoad, isClipped, clippingPlanesDirty, frameState); + renderFrame(this, frame, updateState, frameState); } if (defined(nextInterval)) { // Start loading the next frame - loadFrame(this, nextInterval, frameState); + loadFrame(this, nextInterval, updateState, frameState); } this._previousInterval = previousInterval; From eacc367521c040aef8b7da4510ae7f784c9c66e4 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 28 Jun 2018 18:13:03 -0400 Subject: [PATCH 26/35] Added tests --- .../PointCloud/PointCloudTimeDynamic/0.pnts | Bin 33332 -> 33404 bytes .../PointCloud/PointCloudTimeDynamic/2.pnts | Bin 33332 -> 33404 bytes .../PointCloud/PointCloudTimeDynamic/3.pnts | Bin 33332 -> 33404 bytes .../PointCloud/PointCloudTimeDynamic/4.pnts | Bin 33332 -> 33404 bytes .../gallery/Time Dynamic Point Cloud.html | 22 +- Source/Scene/PointCloud.js | 7 +- Source/Scene/TimeDynamicPointCloud.js | 116 ++- .../PointCloud/PointCloudTimeDynamic/0.pnts | Bin 0 -> 33404 bytes .../PointCloud/PointCloudTimeDynamic/1.pnts | Bin 0 -> 33404 bytes .../PointCloud/PointCloudTimeDynamic/2.pnts | Bin 0 -> 33404 bytes .../PointCloud/PointCloudTimeDynamic/3.pnts | Bin 0 -> 33404 bytes .../PointCloud/PointCloudTimeDynamic/4.pnts | Bin 0 -> 33404 bytes .../PointCloudTimeDynamicDraco/0.pnts | Bin 0 -> 16676 bytes .../PointCloudTimeDynamicDraco/1.pnts | Bin 0 -> 16660 bytes .../PointCloudTimeDynamicDraco/2.pnts | Bin 0 -> 16668 bytes .../PointCloudTimeDynamicDraco/3.pnts | Bin 0 -> 16684 bytes .../PointCloudTimeDynamicDraco/4.pnts | Bin 0 -> 16676 bytes .../PointCloudTimeDynamicWithTransform/0.pnts | Bin 0 -> 33332 bytes .../1.pnts | Bin .../PointCloudTimeDynamicWithTransform/2.pnts | Bin 0 -> 33332 bytes .../PointCloudTimeDynamicWithTransform/3.pnts | Bin 0 -> 33332 bytes .../PointCloudTimeDynamicWithTransform/4.pnts | Bin 0 -> 33332 bytes Specs/Scene/Cesium3DTilesetSpec.js | 1 - Specs/Scene/PointCloud3DTileContentSpec.js | 10 + Specs/Scene/TimeDynamicPointCloudSpec.js | 726 ++++++++++++++++++ 25 files changed, 848 insertions(+), 34 deletions(-) create mode 100644 Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/0.pnts create mode 100644 Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/1.pnts create mode 100644 Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/2.pnts create mode 100644 Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/3.pnts create mode 100644 Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/4.pnts create mode 100644 Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicDraco/0.pnts create mode 100644 Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicDraco/1.pnts create mode 100644 Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicDraco/2.pnts create mode 100644 Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicDraco/3.pnts create mode 100644 Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicDraco/4.pnts create mode 100644 Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicWithTransform/0.pnts rename {Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic => Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicWithTransform}/1.pnts (100%) create mode 100644 Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicWithTransform/2.pnts create mode 100644 Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicWithTransform/3.pnts create mode 100644 Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicWithTransform/4.pnts create mode 100644 Specs/Scene/TimeDynamicPointCloudSpec.js diff --git a/Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/0.pnts b/Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/0.pnts index cc1fb4385b4dcb717b0eba1fa1bb285032327317..46dc632a2e16e2c816d7b65e5154a2ec56279e5b 100644 GIT binary patch delta 12199 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 * If decreasing this value results in unloading tiles, the tiles are unloaded the next frame. *

- * @memberof TimeDynamicPointCloud.prototype * * @type {Number} * @default 256 @@ -148,9 +149,27 @@ define([ */ this.style = options.style; + /** + * The event fired to indicate that a frame failed to load. + *

+ * If there are no event listeners, error messages will be logged to the console. + *

+ * + * @type {Event} + * @default new Event() + * + * @example + * pointCloud.frameFailed.addEventListener(function(error) { + * console.log('An error occurred loading frame: ' + error.url); + * console.log('Error: ' + error.message); + * }); + */ + this.frameFailed = new Event(); + this._clock = options.clock; this._intervals = options.intervals; - this._clippingPlanes = options.clippingPlanes; + this._clippingPlanes = undefined; + this.clippingPlanes = options.clippingPlanes; // Call setter this._pointCloudEyeDomeLighting = new PointCloudEyeDomeLighting(); this._loadTimestamp = undefined; this._clippingPlanesState = 0; @@ -231,13 +250,18 @@ define([ this._styleDirty = true; }; - function getAverageLoadTime(that) { - if (that._runningLength === 0) { + /** + * Exposed for testing. + * + * @private + */ + TimeDynamicPointCloud.prototype._getAverageLoadTime = function() { + if (this._runningLength === 0) { // Before any frames have loaded make a best guess about the average load time return 0.05; } - return that._runningAverage; - } + return this._runningAverage; + }; var scratchDate = new JulianDate(); @@ -261,7 +285,7 @@ define([ return undefined; } - var averageLoadTime = getAverageLoadTime(that); + var averageLoadTime = that._getAverageLoadTime(); var time = JulianDate.addSeconds(clock.currentTime, averageLoadTime * multiplier, scratchDate); var index = intervals.indexOf(time); @@ -299,6 +323,21 @@ define([ return currentIndex <= nextIndex; } + function handleFrameFailure(that, uri) { + return function(error) { + var message = defined(error.message) ? error.message : error.toString(); + if (that.frameFailed.numberOfListeners > 0) { + that.frameFailed.raiseEvent({ + url : uri, + message : message + }); + } else { + console.log('A frame failed to load: ' + uri); + console.log('Error: ' + message); + } + }; + } + function requestFrame(that, interval, frameState) { var index = getIntervalIndex(that, interval); var frames = that._frames; @@ -306,6 +345,7 @@ define([ if (!defined(frame)) { var transformArray = interval.data.transform; var transform = defined(transformArray) ? Matrix4.fromArray(transformArray) : undefined; + var uri = interval.data.uri; frame = { pointCloud : undefined, transform : transform, @@ -316,7 +356,7 @@ define([ }; frames[index] = frame; Resource.fetchArrayBuffer({ - url : interval.data.uri + url : uri }).then(function(arrayBuffer) { // PERFORMANCE_IDEA: share a memory pool, render states, shaders, and other resources among all // frames. Each frame just needs an index/offset into the pool. @@ -327,9 +367,8 @@ define([ uniformMapLoaded : getUniformMapLoaded(that), pickIdLoaded : getPickIdLoaded }); - }).otherwise(function(error) { - throw error; - }); + return frame.pointCloud.readyPromise; + }).otherwise(handleFrameFailure(that, uri)); } return frame; } @@ -401,12 +440,10 @@ define([ var transform = defaultValue(frame.transform, Matrix4.IDENTITY); pointCloud.modelMatrix = Matrix4.multiplyTransformation(that.modelMatrix, transform, scratchModelMatrix); pointCloud.style = that.style; - pointCloud.styleDirty = that._styleDirty; pointCloud.time = updateState.timeSinceLoad; pointCloud.shadows = that.shadows; pointCloud.clippingPlanes = that._clippingPlanes; pointCloud.isClipped = updateState.isClipped; - pointCloud.clippingPlanesDirty = updateState.clippingPlanesDirty; var pointCloudShading = that.pointCloudShading; if (defined(pointCloudShading)) { @@ -504,12 +541,32 @@ define([ return previousInterval; } + function setFramesDirty(that, clippingPlanesDirty, styleDirty) { + var frames = that._frames; + var framesLength = frames.length; + for (var i = 0; i < framesLength; ++i) { + var frame = frames[i]; + if (defined(frame) && defined(frame.pointCloud)) { + frame.pointCloud.clippingPlanesDirty = clippingPlanesDirty; + frame.pointCloud.styleDirty = styleDirty; + } + } + } + var updateState = { timeSinceLoad : 0, isClipped : false, clippingPlanesDirty : false }; + /** + * Called when {@link Viewer} or {@link CesiumWidget} render the scene to + * get the draw commands needed to render this primitive. + *

+ * Do not call this function directly. This is documented just to + * list the exceptions that may be propagated when the scene is rendered: + *

+ */ TimeDynamicPointCloud.prototype.update = function(frameState) { if (frameState.mode === SceneMode.MORPHING) { return; @@ -548,9 +605,15 @@ define([ clippingPlanesDirty = true; } + var styleDirty = this._styleDirty; + this._styleDirty = false; + + if (clippingPlanesDirty || styleDirty) { + setFramesDirty(this, clippingPlanesDirty, styleDirty); + } + updateState.timeSinceLoad = timeSinceLoad; updateState.isClipped = isClipped; - updateState.clippingPlanesDirty = clippingPlanesDirty; var pointCloudShading = this.pointCloudShading; var eyeDomeLighting = this._pointCloudEyeDomeLighting; @@ -620,10 +683,35 @@ define([ } }; + /** + * Returns true if this object was destroyed; otherwise, false. + *

+ * If this object was destroyed, it should not be used; calling any function other than + * isDestroyed will result in a {@link DeveloperError} exception. + * + * @returns {Boolean} true if this object was destroyed; otherwise, false. + * + * @see TimeDynamicPointCloud#destroy + */ TimeDynamicPointCloud.prototype.isDestroyed = function() { return false; }; + /** + * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic + * release of WebGL resources, instead of relying on the garbage collector to destroy this object. + *

+ * Once an object is destroyed, it should not be used; calling any function other than + * isDestroyed will result in a {@link DeveloperError} exception. Therefore, + * assign the return value (undefined) to the object as done in the example. + * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * + * @example + * pointCloud = pointCloud && pointCloud.destroy(); + * + * @see TimeDynamicPointCloud#isDestroyed + */ TimeDynamicPointCloud.prototype.destroy = function() { unloadFrames(this); this._clippingPlanes = this._clippingPlanes && this._clippingPlanes.destroy(); diff --git a/Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/0.pnts b/Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/0.pnts new file mode 100644 index 0000000000000000000000000000000000000000..46dc632a2e16e2c816d7b65e5154a2ec56279e5b GIT binary patch literal 33404 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

{u{j$_~Vo1tZo>5o;EV7&Jo+ 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 literal 0 HcmV?d00001 diff --git a/Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/1.pnts b/Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/1.pnts new file mode 100644 index 0000000000000000000000000000000000000000..5a1c21cbbe581acc4b47e4e4056538cbdf7414d8 GIT binary patch literal 33404 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 literal 0 HcmV?d00001 diff --git a/Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/2.pnts b/Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/2.pnts new file mode 100644 index 0000000000000000000000000000000000000000..6bef4d11e0def3d93789c2866fe02d30b12c12f8 GIT binary patch literal 33404 zcmaf+by!wQ_xJZkKt-@XF|bfX3>2lgXJ*@CccIvdU}JY9w%Fa>U2xAfu)95WE5~ld zc)xpipPA=+uJ^iL{y4wSGxyrlvu4d&>zrSoUj5^BP1A;t)wBipzYD@O?IHee!y1}4 z%)4QOCXr1e8`Sp>ALiY0NdGPkx^|84(%(D0!iciojcZo<-{OFZ6)IRiHEa-BziE?p zb*tB}*|e5-cmPTv-?VDGs@3Z^t=`x>JSw1KKv0E%ivFRY6+=Tqf-0_VL@SmWdkdO1_W0K@~>Dipki=^z=~~06t(`!-7I1*OI&9k*|UTB@Z3-S zdLI+q?ccq;Z){|WResmKBag|nk63(V-E1osRKuK^CzJW?yf?LYUE~p7FwX+P ztuGDcRgc{le6hfN``!b&nI+SD%XM-(N3h_LgG;dcU0Vr0P-s5;m@-^&z~4J;oXbqX z&HCgt-1e;$+#{%rStNk_`GVjnPb-)gmIbqz z<2lTW@!8GCUs?$+;e3mKtvf>S<`T2HQ{$O}J5TiDGk&iWoLF#PO8$+z1!wyEhOTa! zDmc8uXjV?UDR_64RJMQfOTl?_I~kQ*{1m)-cCe|hNMN4%Uh%iy9_Exf3k7%T{DkNE zwn=c>*kyeAs${{Bt5o8C^)Coc`Rilf{o{e)%o9yY)_xHD)hU*B-k=*+o&BF4WPT%E z1iwlB!`?P=7ks9Q&0PF`H;Z1si)X)*-}G6MDtOh=w|slen}YowZRBB}UJ7piHeu{^UOAbi*o-VS9PnN*BTR54Y#r2e}Kr`C+TQe?2e3UZv{L zu;M;~e@wNpKi|s<{_4A$9lH=LxOdiJ%*nF2=Ki5pKU&DH4P+u>jWRou`Jsy-cf7b93yrWkZlY99Kex81okN+4fxMzU` z9(jrhe%aTNXJ1xJaPwlDY**tO3I6S#gDr?`EqHL;RMxggC&3@;US{53;si(jZETKR zSkWlhYYCk?y@FY@cZA@Jg|eHj)vF2iX?>GlE>KVKmcdDU-uq^PyZRU8Q757WFNvCx zQglf-!GE^Br_cKK5gg_=hHa`jMDVV+sq8@ZF@jgS=Qo{`ZG7Ui+UAAoZ}`W~`2{!a zyn!bdd~LVN_c_(%b9$~7oHN@1`^}L=a9eF5EjZaov1E!>ZO&%LA0igJWZJ?x$7B*K0owEBiD{s23`Ob$Wj1fHN=x$!O%fBgBdC<|e zym7r0o5kb*thfJk@}U+V&EA6g1S}N1dLm=>9y}0ycFh`gxQ08kWU5Vk#V#%l7W|-+ zr}5--E5T3tmN2g^3`sff+Z}dU$Arnzgjj#FdeM(7vowBuCm#Yr2jKQ@Oro0EX%@~ zg6qAU#*(V75`5zH73Kx~VAZqYdNyO-;#9%jm&%)GPx&z4mWO$lc5dc_B9Vd{-Mh{| z59}fMX8KI-_##1Ya$r6_J!zrf6<*y^^3ly1@~ghmRj-l-yGWNNV^S+>+S_=m)F*_JcH|C-j5G9sDEAMH4o& zm^WU0O-eqqL_mt*iwj@#ZFesSZhl}r51sT-@WHLs`O!)r1?S(~+rI9dZdl*8+_9L( zrMd_%vbY7Su-IL2*6lmkmp)#C$Ikr14naRy@}ut=X5fvR>{fZhe!NFv(>~^v;OyVO z@ml551<%d9n|JTwY*;cOv2D12l^lW->#wzc^2{grK(99Rz?-6iSKc?+h+}?&&oo@i zsz5(j-&U=d#xg@cSiE9t4YQlOgYmIhJbhBIgjx2Li(uc)y7|HGF8Er1JKs0QOYq*3 z@qAdUkKhR>uht?7|%~7MF_53RO8cPstNWgQ-Cz4 z^#rf!;?6uhnhUNoXF9w4Dq3*rfU9gJ?suMF``ZK$yOPEJ<_7jO(aMugzALAr5b zV@Qf6ld!J1VSi#L7Ki`!G_xet;{gNmnLoY$<)6b#3my}(jz4_jEI7xWDtux7lXgoc z{&**QpSaP2H?Lkvi+y>XVwIno)0+KqyJ)lcUYT7iC-tQkw{ZK$D%4vfIOba+qhu?xc_4TD9^S4+}>M9@4hB`Dgg!ly_N< z^W@i>Ir;5Fo5g>>C3Ekge$-;ua(#GM++x8Mubs1Z$p2Vy*G_IUwUY<4WX=^V$8H}A z5!|l#V)iCil;B$pA2PlBNWp#!a~m$u50?A_S3k3HjlMK^!8Cq%v9sBz5f@CGpW-Qh z9hg;~+J79s+`Ob<|9U^|&$dPi-jf7?L&sF|mMU-I2tJAsD_mP70JaOmK zd}j&vckoW}yt+#8^6G!-%8`2nKiV~(l_`BfaO*jz+1~3n1*e5)HW>7S)z;0}K=b$P zo-D_mvG#&LbD4<)CJ9c?{eZg{TO_z@z#_i%+-AXZmY3$fgHr^L@0ZEGv-m~9tIu7g z5f>i{uC}osOC9=AaKT_Z^M!t}>OV6@H(tMV5&SKGxVf;vYPR)aIU2sv(=2{skKpt^ zX?$zn6N1OiSj$5S-4dL;O=W)M)GNUwI!D{<^iLPOzR4PzHpAJl>N)W`itXrNQIU3lSVV zxVm}%a5{@wJeCc~T-@BU+u5)99?h;%Q3Z@;G4f5vTGgc37$1In;CxnB%gcU%iIuhh41j& zC%Am`X}sjfHG+G*%E^~pNfPXT@o92y-!X!REOB5tzC{b(SAHT(UmhX2e6F+XMXh{- zoBqgbtb6jxZq=VFwzM%aXtm%S5xLA2;}`R=xw`ps?meFOcB0^3dlqodD4biAr>*tn znH=j0PVsz~@@|Wt;II59z0=HD@S9Zw*!172c1!;KF3!#^8!0%bwuA9){Nog>{KKIV z#)b=`f+(}|_>EIu%d6mA@;w#yT7)=^*!S5D&nKw^3 z@xQg^@veaH zgI_IHEn$h^eqT4R9zjn8|9kTdJM+PVSu(rYn$(R4`Yev2;M+26vwf9SKGV88gDmbp(2|l37Q^6nV zjNunX=V6x2!;K&8k8g$wcJGrwzWTQj{AEW0<}z`V;Ll-6Y(}qHf_oOc$?`)#Sl`C~ zaWht@?h#yneL3@_mm|BFbcxIg&Su_;_Z7Tp%4PoUOl`pxHcjQ3yn6}mH7f_7k~~T9 z(2EC?^TaF?Jm_;Kw%mP-;JtSyGC!Uo*gxeg+Z}sRaMK1^3@ z{`coVc05O#;CIm`s}KEP)gQRR!RP_~VDU7kFmu87MT}jKXP4*XHNB&@2rgCYCEwy~ z7redeY974nqTo0e!p*ji1V1cP&t5z0C&6F7ZlSTQ9Sp0Up0qtHUm=^|9ok;@*~vq& z-{T+b;2kf)TlY}2qIQzCirT^6SM@TJH{TN6ZQUncqeYtFx5u~fOAfyTzdF%`m%Hg~ zSn{tk&#`~pl0)!^^pSM`*nEQDjfrH#TNV@CchyE#*}t^lv~h2lvtuQ}Sx!5e*Ju9X zOW!z|^L8BPb)KaO4qG;g9|^lHcp4r}A=6I^jwZ{=*2jAVFWXdz6%Sb>_|(4HY~hq7 z!BrOAVmTg;7JM%`yW!G3TJWNB0mj!{5rTaOyO zMwT(gJvwf;+N$ZB(_Hy(H9z@d8TaUUhkI5_61;fIJU(r~Xu(yR7U$0Yv=uyl^0}0c zW5Wek>-3P`E8{76&gUU)_Qn@>tDa+bj3SoN z=|+2VuiTAzle5qHkLPs+|MO}ke>S6(;3GN1`Q=b2!4V;q?L&_pvs?1D(st1`rH2a6 z8`_C&SaLhXDjz>$AA29-KrH?fozCi}m7^9%6fJ7hU%gcD#0dq>+e`BDrX5b%ll(sN z$y1)&EzVzSEAM!3xnOp_A&-x)D0q>_bo;a{9x0YgoxT%j>pho<#S4bjWh3?tpcYpz zvYB;DJR+ED@7c+Mj?60e>RZ6L)6ZA%+Q4Gw?6-sLlU=is%L%{vgc8?NEN;2(01qjZ zjaZzL*o{Y*tUxWEH|?;!#gS!#m%T4fNA-RtxBw4g*RtnjmW=a}U|Wrm{M;r-W|b#7 zzp_8nO9`&?dn5VWvX0;-4n^O%_&y*gMP5Sy_Y+Ov8weJ!B3OQ znvh&iuX5{oQ&pLH9Z$1M7Owcw^jp7TwM_X@s4R`S4_ zCj}or5X$WzZwan;Gr}IZEKP9LMSJPh$X|jzW_D)VemWago#XuWv-nFn1pjjR#X{HR z`ww%l2^kI<4jIAtmg8H&sMC%*xg~?PDB2Q?J}~q_F#5*OH^JyLL!Sj>EC$9R7-KXr zM!^`niLsj)JI9=G%n7#`bHy=Nf-#47%%Nb+Ey3Ih#+)9TOaG)m7cuA}OaG)mM;XvjmYzw0?lPgf zEImn~(>QdRVCXsyU1#Y=3LVIy0|i4j+MydQJxQT6DRic#CnVDo!(AyDcPPUhDj0Vw!`&(vcdmguS1|5k6L&GiUCeMt zo4BKy#kjjU?ry=j(>d;R!MN)=?s~zn0c@}V1j9C9unh#mW?--x1jCkKuq6b;#xP)G z2!`!p!uDXWa}3xdCTtSHuvIv06~VA!IBXcfux&VO8^N%7Qeg84hAl*43kime#9$){ zhV8^)I|+tOWx%Eq3|ouC)-qwIaoAuSHke@8W*oMeVAyPS*ldDf%Td^Jf??w^*m#0r z`!U#l+^PpQA%jgQ7`CDTTTw7`!LX%G*wQ9!X$~8k!^Y+o z!}jK|y#>Q2x5FkE3|pPRRu>E#p23C}4BMW;wigVWpTXuAjJJS+w}4>05e&Q$1mo>s z;_blkc1XdSg5ymg7;g=Zw}xQ6K{(zZg7G%7;cX%qZx)6(i(tHE7~V31@y20z;|Rvv z$H3c1Fy2H4-b8}&RxzM&B*a)6pXhd$6Hb`-k2P3 zOu=}2+VS=jj5jI8o78&4GQ3q8-l~G}hGlreTJKqgw=Kil)_Tt}ym<}0d961r!&}(I zTiAMcGrW;G-pGRScIJ3HTkmd$H#NtbS}@+)cD%I(;|)&n22ZwR@HS_7n-h!iW@mV_ zTkmd$w>-mJUNGMH2HyDAyPM(dZ{qE5`3e|(0vtX8!SEGu_zEn)0fP^L!-rt`4H$e2 zcK8-7Ujc*9fx_n?7`_MwUxZ-zC>VSc%#wlcg28to7(NXHJ`KU}b(rvVB*WKXzz1T& z2SO}{Z-m1)LM?{Rgu`bd7`_xcd@0N-hmVE8$08WM7Y5&pVEAMhd@_RJt6}if2!;>G zfDcD7d^;w5I~0B%13n)UJ|DsG1#$R-m{krR5r>aRFnmWGz9Yf#DcRst5)5AxgRe<2 zd{7KND8cYeG5Dqg!)Il{XC)ZEEDm3m311n9kBh^{B^bUh4&Rqx_{8k+i3x_UjKWtY z7(O%xADUqJ));(ig5h&B;BylUUz`D7oM8Cq4EX4{)fRkr9KJgfK0pqi9*0j)FnoO+ zzCOY50ovgM6b#=Wg>O(We1;4@L&5MRGWZe&!^gAW$>vA zhOgCtuT?O7um*gvg5jGr;hQz#r={@Oa`1vOX2$!44*KA zPgpQ~#SFe;!SEq7_>cv|w`{<-EEqm#13qWL@I{;OMRWL~8GO_nK5D`6U32)Z1;eLp zhfiBDeBA`TZo%+@Gx)#-!#B?08y5_pIfKt!aC&W51HN>@@Ua{4u?vRp-GuL*!w=8k zljrcs3x=cOV7KZ= zOu;}*K`>$sCSna7u?B`11V;=)Fk%xNu?fM5S#ZQG1S6JVM=V1yVjL7P4!iX&Vjm2# z55b6uFvLUzBUWM{R+4PVAckTfhC(bxY{f)ug(K#|5Od*(xd=uqh9edu7%>`-7>yOv zfge1^j@XT0#B?ZPI#zs#A=bkX>#^cH3^5>v7?2gyVTcVe#D)YTW@I2{Bp9(I6R{*K z4rL(5#1Uf>jMx)L>`5?UQXDa09k z5es98g$YKC%s`AxFk)vWVrNzy%|J}eL`=`am3oF#fZUi#NY%YHfKj{PB3D2 z6frwy$sm@;5X%#c7#~B7PcUMC46#4KhzS~q2?|E6kRw)TA}+}hL*$4d3Px;^Bep2m zt4x6u#2f`97ReBc6pR=pLyS@|VwViDOTma~8i;8MMy%67tWz*zpax=~f)N|#h>ef)RVAh`kC%OqL-gD;Tj_hFGm&#BdG7a0Mf_Yaq5O z7%^W1F<-%m1#`rLO~isJV#FLVV!?V7fZo!CsbHu()#J(9~;v6w?!HAV}#L5LDhHgg;T`*$n z6tQ)|h`BSw+`08FV(|>Ic)^I#GsNfxBX(~fb}tw)eFHIl!HD&ni1nL@^)uuEaO408 zMs5H{ZUDE+kuzXN&VXR#5>Vt42u6+pLym!9VK`?S5IC3EbH(K@8jvNWW$ekd_oe+$i3Wl5t!N|2>$hF9j zzjvJ>2SYG&GYsTr2u99^ft(G&$mKAR%fXS?!I0y@k>ep4xgQ+4AA*q+!jTgq7`Y-T z$Q2Qc91?~c62ZtVVaP2JjGPmOoD;#wMKO?zVz=r?j*5XC6~V||F_F8%k-K6br-dV@ zMKE$*IC5PCBL{{f2SzY*V^WYCBN#a|6ge}3kxRpnOJld{L5>YWj*VdC-WYHrTX{GJ za&iphltBe#knx5~Uja#6O3Fnj$Adt$YD!H z4x3=)wlUoPiuU!N{F6kUJ+BIdukd>I5U#jw9F3L|z|5 z4jxAio?ztW@jAt-2}aId3Uc-YBbSdNmrpQq{1|fl1S9v4A@@%(asmzH1PVs3pn+UL z!N?&rkV7aKxrH3Lg(mV98FCIeat;L}7m*_uQ8028?Z{CSjNCVB}tM znSzn4Ns+557&)8_Ih@@37P*}axt)TM^U09&DHyq+2690KBS+Lgj;LVdj&kIVn#d_- z$SLK>DHV)dQ;u9y!N@_iBL|gRGRRG($W0ZDoK=RLRl&$*Wyoa}j2u^n99O}}eKnB# zDi}Gj26AErBUjc$t}I7hu7Mm{jvQLS$gSnbtrd)%TRU=Y1tS-iAQx9Ka&#GTbOj@K zmmzmoFmiesa(V?L*VjOhE#yL*$c5&} zqc)Hu&5Pf{`oEkt;43IpiEUI^yRR$jV++;xWBbt@O$Ku)`XoOZz;9v(SzQ>GFnN*IRGtXZ?RZQC|!(j+V_48L~j)TvsvYN)n&@#27%EnBv4-@bYC z<`EGQ=tg2DZTG-%?) ziTKw6&}R$^vw^;0P%T=t7&B(f%$YNX4jqaCVg?EoDufClfTlx2LNFrKT)TE{bPtW< zbJU5N@ozo2xw(0Hd0~K_o}QQ@{1Y|fS9BY_!g4?clBfz_VO;pjs2M*wI5^-ZNMf!q zc1#6I&?VI1>gtNsgd{4-ojZ3-Obj}PRmhq(D+DrrMGg3$-rnB*`}arRP(MCL{b(JH zVnKs~f-q`KRK{%J*Npz4LM#yWz^GB9@Dn~nE2tdRqB9sy{rdIs)7-gpQA5d+C9y!L z5CZ5YD!~Mz9J>hLV)JCo70NM2h@o{Uz!K7<(hfU|%Nk3)e@V)$qs^OkW8A%i}b zD_0Jq&R9^)Fb0d((SLL}V_8v>Fo!zP{Mu=nU%5__aoj8dy7sVeDuQXA|;RXtax|KsoBk=m~ZpnnwK)!>VP> z6@JBFG0Er{CJ-}*53wTX34}4jb?Vf??4bl*#a9?U{xEOoJT@wn09Fv6<5vI_!syYX zr%js%U4&oJ5Ec%9Xe(n&qt42eD`SCBf_hLh>cPgwh%xz?I&>goBjG2UK}`OTAw!^E z;^N{k3iJ&#fQC>r{)rWV-dneB9U8?*F+fZU&Nw77pD39=efpX;Yq0+@!x_I~8ZlmM zY4oIT-@XY63EjJQ$3$T=QIhc?RtimH8Zpn9UUU`h;uB0A{xDG(EEWqp87q=8cI*~3 zjo#u{>??FW;}2tmFlxiE(25u!<`Dlxi&0Tg6DCZ+!GylT2Ez`4BqkQaM_bre*io2& z{9!dQ|JbNFFYVg38#r)amo8l}kc^{;RYP0oJT}R^dGl~nV8|F0x`#?I`KS|>U}Nmr zv**Es2dSy4BS(%z2hbdbh9P9E$oB2q@87?l@hfI8V~H_dsA9A`b?Q{yKByV$3twUL zW1ce<4n~LN!;0hVRIgqghY|fpo!9{AHu`{};Y4HFacVF*_$T@Uy@bhu0RD+WoKs94 z{xT*f*vENgOdGHM<_emttex99*a9ds4lZrHFPwlw}w zEqaT^#ZJJQqGRX)KF3^PBcT-tWB835H^%LWpD>ddQv^xqVf+(a!uiFGhIX;~m;uZ! z{)uye7V%Fk1cr>hp`ncF!dzj_GcGvPhMB}Z!A`(o!6Kq3IEH8xO=GZFIIJ?h#iGxd zGiTw#h3GcU1vUUWfOUs*z{Fw$A3S)_ZntCmWef^U;6Pz!Fsra$@GGVoHQ=OUuCVv8 zJ1K!*|SkIW&i`ng5oR;7%%{RMkN>>77?}wCJnv9=sI@n2=#)qfFp@cVoEbK z80HN_!pds`bi-;+LB*Y+$Az%xkljty-z&vB#Fq2pjh(SeTTxcs}Lt-nUlh_HU3O&SX z!Z^X8Ac;xN=qgkqI*h%I?TSgpFi}6+MGcrMFuujg!jMDF=s()R++sda8Rxh8xTM}*anb6bC?jU7rF;w{GkN(|Ici|ZovX!-=anQK^`{zkU1uYuus<3A{>4rAhSheEr>{6)@bZHez z&pG6y{jcsO?cR4GCuSWbQ4Y1}%_)niQ{R1bPUAK7)|t&A6G5U9J0^=A6)wUal}9 zqWS7`r2V8Uw9vdS@l>CgvRSg(qw>8avj^j_tgws&4TZBHkywdHmS zCBp+E>GQdV=<$Is=hWX%E%a;=`7txbR&Y?1t-Q-ITZJJ`v_rWiw92J-^vKZ6 z>`8qGdLY|1GNsvQGT^38&S&pLN^n1V>+p6Oxk6`m7b6Z?;wzo*KZ>3jP>U95`i8_j zjvz-nW+9mdo+FPowx`|Rucxf^A^NB5JW3b#q0Mtg(02~kNl4^<+nl1cY%>~83mcQr zh^#5|n2cHEO1pGVCiNyCBP9cq$+@z_$=BDWEmx225pVma*jDsdWXom@iJ0LUW$SX% zmAvkLgPa=s&Gx+XQ`@9Iq2!pq8_DB7!S?SU-8SoYU)$kcTO*qMok4hb67?#OkIow& zVk@>hhQ!?%NXpKuN*?DeNd~R0NT%-CXA4c7MG{l5(CW`e($}RXk?~y%lQ9mRNcf!^ zqfp2Nbe;qBpn*~+9qe7VB7G-E#gW2*~GhT3_bn#935HVFD;igi1z<| zmE6Cw)pq{hvE+H@%4FQ{#}Q59UXY0kmrfJQbYG_B_w@+~tZ(>+Vu+P%|AW{>pnrf=tx zT_s%T$xCjueL)*7*RUiF&GCcmyD*V#TW7Q7+rOP`|M`SCc_)#G)vF`i`<$>9`GIuW7GK(=VRd@F>rnb* zNDA@Y=tS?HDY^dGWug^gw%5l`LHX3(tpE7S5`3(4}hL|ffve{4G< zmW75V6(loO-y?aq$I$4&eY8o<`*iyD&BXKBiijx(;tB26n>3w~lSJve!{2W;N%i^T zX|EjjY3gQY78O~a-W`=j{B!)ZjUB$7G}sbL$B*1X-SX|ERpQ3b$vd7C-&6j!mbras zz9tT|?4Elh=H~>GGwqRW`S>ifWPAwi**zO=`_+RCdKp8aH>@GMI(Sgm?^S7?oQ!TB zcb4?3UVv=AosB*#nTuu`HIJN4T^~{GVXUoc%`%jyC((+2bLh!hC8etlsgOUp+pjB~@o*Zw+h!2G@w5vqJ4mCy3SJdJ);c1d%$Zq|u4_D(wwru}9z0f*{(4%EOgNi~Z0_ex zPuAQ8{z?5cdz%Da$-M;m>JOD&KzM)_y;^wE8!pb#9P}=fiE=@0FsX?u?_Z zdHT`V^uJ`}ywbKzGwMYg+EjwZuU$lozu!fl-5yUzJz7f!%w0&1Mmo{$3H!+TI$jY6 z>?a62-<{_7_(VTcaA9GcZqW-58SR_B5b5q#n6_zIoThtTAm>+ietj8j#*=xSTqG`~R$MFrBkmAcTw53@7dN*{Khl*ZcD=|ra*pP@i~ZY?G!|H=Q!K6F%IO{pXX#{i@Y?H zeb` zB7fElA$Kljrj0zaQ{ViLY%gwQrDp~Yr#YJrr^RL#q$z)rZH-%WA%kC*rJrY+H1D{# zbj!yxG^tN3+Mv-b+q{M|NZOQ1en|P zy?*48jegrr!cGn#xd#lTe~Kj1&F=$Q>00fX>+Tw?@3TMDp3_EiOmHK=9{ePaCwwE- zvyG$CYx~l)zzXa_qjqd}ff{TR`9q7138aOl^e0`H`_lN%!F1ce+BEojdD_dxjU_$` zVf!|fV25U3MnBU@yRo;)>oe`>zHS5PoiW$R)?O#bx8u9$*VvDAMB{t3?%c`r&i?k~ z*+w^7-fEFN4>UN$bODgM5j!v(|xl=EzLP|IAE> zg_NP+Iw#SPVb|%6^!L=B?;4$5eloqMUm^dzY(k=21=(f??ju{OxX`beN0N@Qvq;Vd z73uwfj5eu%m)!08Hp15bG2ub)NxrW;=;XWa==tLhX^x?@spF3)fW2$!d3@>~6hK*44<&@&!zz4rAAlnqFE&(S4b1S6vs;kb$k}!k_+Z z)P)#UdvZHgXMG7)iP-zy_AcoBt+K!!CSezZr zvVxwOve@>%ZV~F6-k2s29z-YB{zlxHGyAlGumLqgS-)M5Y*S<#8Z~7znXS#H;q?#E z8bPi!=YU2ePu*2?W$0&m-1tl{tlvf(Pkumd{mM_%V-L{X9pBP6CBjJe%RM4ihkPXM z(wou0D|*mPzM=HT&Bi2mR2KU5@EV$&{G5j7s~zDw^^I-W-Q7roxL9c@+S4mp_n3U!Fj!wN?iVBd#)qD$J$ zppEwZBo6};$mQVUy3F?HHU5VT!H(<>%(5#tdstv z(W7OwXZf|{?(uZmf&hg~-P&t%%E!Z1l^B8nmUYJ{`O0GZ_}#p9J~LrRfWP z(x{c~ZN6zYA{sR)9FZQp({^R`9P)lyZdz!BFa30AIZ11HnXG!hfcl1*beMf9U1Ipt z-`kgx!P^!QKZkqddN~LB{^BKaVp~;P*MXnNwSjHv;8ywR@nxk*(4I8gx)--?V*@@% zRCX^)#`m9U`)AlD(r#c^>eJVOelFS7cBa`S5>q3bJ}X^^)=w%-+t1!dYTlSl(tqWk zEz)66_Fq9jO%|t?$gC zmA*ZtF7?0Cxo1yOlD#V(es&rulIuxCTW7C`EtmVzs_mLa^!l1eUmbl&3lIHF9h_3> zxq^{2@#Z31!{LO!NgGQ&Yu2Ss>k5(Rb^&yG;u@O2LkfM`W*H6h%|m0g4djpa66)IV z866t`gM3`xjW~B5OAcP}r9(Ev(fi^3==Px=)N`*v94E}CQxbmCedE{KmIOrFPW--N zySgZmWDjwr3p`rW@#*d8q&DZt#sh!I=8!|QMBW|bE!#(yUE4_F(x;Q>V_%b7AG=e3 zwwiv`lW4VBcgW)S%rxiA<8;#RX*7Ruc6zB!SJK?SD5>==CrQ-`k?(Dm+ZrzKk9b5R zZT+DR9ed7^c8Quu+Wrn8?`M`L>)z)eLyzyX1$KC4yO_UQT4bvc!r^dbSIOX^-SuFY5Sc6=u4)!+tM zG+_pL=(vH=&b`_nx17-Q*g;S(_zs9Q0i8DXT@{!wWOZnPq4J2>ql3DUB{zb}1$K{gc zo&`_K@)w_a$nq*#n@eszY?ka>0N*0zZ}r(F%NM*=W6!>Ft>wZKf2AfTOZnL)o5?zh zWEmj2R!~jJg%0G9@`VpZ%W@aH;=>b%%JL(_KT7!xbL-0T5n1XAG_`Mt}0FV9oS{(&`QotxL$BqvUNC1u+4wvwNWSL?oNpo=We`DUS9 zuZv&S%JMda3d=g9tL2yF^>=y6^3V;PWO=7|g=G2TN2#)W$U-#_yK;7uGAE|^OTN|a zxs;E~bwKi=52~&8M<2-YMw+VU%lwM6o>5KC%K1#+Hc)a*^}$jmY-ef7p4f*ZWQ7dKnnMDO!aKxud7^B$~>tXDa$=Q<7N5O(@Gb$S?njvmzHfTc|laPJPTRwosj*x z*-!O<#yF)Lzw}rABo~iM`A(PBeq%p|$ogwvZZFyG(>vKu`#=X-?&zlUn`cmGS!b!6 z>Yi%xN1c}r29e)ZNLelW-+o+C`RzO3$FeQ=b2BAZJfYfJ=J!>~7tE^8;Ev_sF9`*m*&%(fzI+WO>a3YR?SI6(`G&w;LzxS?(My>;I>EfRqW{kyG+n zS9M<64r(jQy&ow(d8Uq<&v~DezKz-zE9D<`GG)8JE~)-R^mSJ0qs_xCu4$3~s(?!W=EjLHX@0+Z~IDTG) zEce@fR`Ncdyt1vm6Wk=vKd?sj&G&gJDIXiuRLYEKks`}y++7&)pL_h;=RC5!;{7N-Ls|4??%ebd3W=kEcZD$QgYDD%Ca8UQEFYfoIfLF za?JFUGABBAkz8O@ZpqDNs=Iq!@6PhuS2ksXwRxd*MUi~!Zd&%{sg!5GpGhX$lufu| zoH|GDxhlzatEIn@`)5O>vi&wiRg*H=n>Cc|`Og(u|9JaC8>Q&yVz%d^=|k z+3u9PC1iPnvTD5>oLkBv#oN^K9y_yirWuBRL|L%6(gA?|b>JW6UDS8yl*#L}S`ygDPp*XNYwxdYVw7+m3`Yp4(U>4CV{Ov+TgTvf_s zYoKflk6nFb`NoIgl0W@b=W!=jHhJ?C%5L1yQQhx;>we09j-3@P$1-4sl7D)(u9T^J zE=029;rX(kUY@G06Xom3{`8A{Bjv}{?IJnT({@tk-Q^v!{Q6EcuH<>jei*n<>AEpZ z)w$?y&nN4d)ZIb$|J9OOvV7iJb$1`sl}--IUqs5>XjorU2JcyCyIV1P^my@k!dAUQ0vTq;a zs4U<9TCMdQ7v)F#TwCdr&}K@ea$B_@>UUH7+?cBFoep1>4*zH32-()V4oaWX0_rVb z4plylkzC#Hj@e@5w*gls%6S+!&Q)^VvK!@i2Oo%)BV>8`C^esJ9M!p~y+qli{d={R^^Y#7{21pqILdl@Bz}-GOTw?n zb&M*l_D|OMrc%bGV469@<-{MpxIo?%h0X5q_T_lZB_5bW~Um-Z+|rQko+@E-8JJLD;+g-!(Ta;2f6o2 z`I`SITcV`dN3P@O!9Ud6y;e4WV}zQYxDAT4uU7VRxz_3ow%n|ALdVZ17&&Da_UZLGiR2vaMBLdP?5Z$y3U#A5}({|Ek+ zSy=7QNx#FS%+H@=W#2-}W|H;zulX)z$}jMcw9%6-|az5xu%ZY z)qOqx)K2+rGqa79C%Zglxyu>#jwstot+jiZ;Zpwd>(5gD{VQc(<=ON{mVaM6Uixr` z>`^|DpdX1+rpSKv?#o>EzuqI|)g9_~+d85%%=G>V5vixqW(tAy^sk`j+AQgX{QhAvCc5D^p-}}_?rYwIl zMeW;z+m&v-)Lz+6dFrWk-=FiS{C0K`b*EoAoGSYk7c*JP94awRa?jPyl5-}iw~7Cy zj`G_ppOxRKc$`|(8CTTZ*E&$ynQ=$cWSv!}D4n*ajXj+4Hq( zC_B8P-z+J!|6et4rzfbfpWNC+*1vj`((SafI%|dRt34n4e1{z4#M>&?-0piRSx@&~ zsy~U-n#exPZK-ro))z`2mUmR;(|W2gX1zLIe*1Q9u;f0SmEFa1&yf9j+e6(`K8Fj- zJ=}1+ihW17RlcUUhbgkof}68QUU(`}a?v5GZxQ|emF44Z_K@T4csN-0;d35!kM!W` z9+^`&U4GkQ@lq+Dr=xnObhx7IH`@8ozF zE6Q@uYRX0}-b(Gyz*{+Ge|(oJ+hA|oyYgGVe(FpOnyL0i#XZHP%-bqiWq;XcUQF4;GhEcW()n{H`K_<1^w??F)3W^HWTkWToBd_G>HpOqx|~V*&Ep*< zkBsXk<*NiL`{aIbHd+4sW3rr+I`7qa9FV`elo@nht&!)OjZ(h&1$CAeJyUzYt&`HX z8>T4x#I>Hf3$}FQvY!6ERX>kc`$zVF*xCxR59yH~WqGdxm1Oz31U0VjXVy!ZlTm7a zvgFEA{uI9_&&%^`-m?6TEkg3clLKTu2U=8<<+VGPkmVgS6_xcge5m|g+i$8p{9%gP zpOO80$#2WfQuDU_p(g83sjYNuUSGA=`({T;nSk>e;kZ`JY|4sk^Un$s+RG zUY*rBa(k!lh_i*1eG(O0O!hN**-9UfXJak$rgiQNlf^|@T9M@{c1-l-IDFU;-}?HD&6wR;hgMS&QAs97;F7i{<<66PD`2d^P;89m%-}1 z|42~&sGhbiQocoXb&fuU?3Cq0o2orL{a!ygmKpU5$@&|oHJ2PZB(wb1{g9I^pTA?2 zoS%m+)qASYlsmG{PEksK_K2M#>uC_HbXWI3%GPT3(^1Me4DKlFxjJ6yhnlNYK1RN- z%EoJ1N!dB6D@^(AoJ!SYKNoaU_gdz0uVq^)`0sE3#~*dDse0EupDTjeOX>FtLpReTT13@ zq-yuyLFz3NY@Z!s|6D~pvK7~rk+!@7&=P3NR3o$2MK_E_Ba zB-w72*-8g`JPwrojQeGiTqH)>9xm^n%5QrvRB@RsE`z1~U2nBUHQpbT@~h4$n`&I5 z(t)M>+2kBHC_rRAsX^-8Reh`UUa`KrWS#dvC_TA$rP9v@_l%P=Wg>^mI-Ld{ljW1L zt3E#%q-@$}{=y-(vmt9^B(h1vt(kqiHyJ^VhUyX2yg?(JS>~rZ2 z%9g0|Q^nNI>bm@Pde-MsKF?OQ7PX!%mgPMzbd>V>uPdG0q0}BJv!I`97ypWnlwYt! z*(9aPs3MPFd_F<@3K% z{>=OiWo7vT2QOLA`J(DPo-nV=@|i|?sBJ?^hMo1LyI zo5UehoxyP}=g58A`G}f_dS1)qysgvJS~N@@D%;w+SH(xxxTyJzU83yImHm}XRU%2X zJ8yRzImXsZ`E5!JQ1`6X%uTj+?~J6(LEm~`*wbwdm9kotcXRV9YRqLk3 zXx+6~El%s9_0)Q4y|q4CU#*`Oul3gkXaluD+F)&nHdGs?4cA6!BehZ5Xl;x(RvV{{ z*CuEawFGUFmZ(kErf5^OY1(vchBi~1r6p;zwK>{cZJstC|Np!fYKyeR+7fN4woF^D ztc163YUDK{>H?*7D zE$z1Uk9J49tKHM?YY*`MSNxIoSbL&9)t+h3wHMk;?Uk0M{j0s!-e_;NciMaHgZ5GT zqsj=yx{IDoch%kW?0OD8 zr=CmCt-I?UdLBKm?y2X~^Xmolg1VPpNH44x(TnQEbZ@=5UPAZLOX|LQDcw&mt(Vcu z>g9BQy}VvQ56~;>mGnS8NDtOS^iVxa57#4fn@)79Gu_ZD>s9osdNsYeUPG^`*U}^P z+Ik(mu3k^CuQ$*e>W%cqdK0~=-b`<ft4HhY^!9oOy`$bq@2q#x zyXxKa7`?k5tH;+@2mIIkIUS`XYU?zC>TD zFVmOnEA*B6Dt)!SMqjJ1)7R@8^o{x^eY3tr->PrZx9dCfo%$|)x4uW;tMAkI>j(6M z`XT+Wo~)gV+H`UU->eo4QqU(v7X*YxZ9 z|LN$gwt%1l0Cw&sAuZAk5>nFLEiKXw(%s$Nq0)^godzi=A*pnChtl1Me#G{;9S7U?GcG%o3KejODCg zC97D?8h+#_e&!d}vX1p^U?ZFOmCbD7{S=(7Y-2k+_=BD7VmEu(%Rc_(FZOeQgB;>8 zM>xtcj&p*OoZ>WRILkTCbAgLo;xbpb$~CTYgPYvqZ~oynceu+v?(=|$JmN7=c*--L z^MaSW;x%u0%e(jL5B{H%F@h0|WMn@yicyVbbYmFPSbk(|KQ@kWjc0rln9xKfHi=11 zW^z-Q(o}w8YSWn3bbe}jGnmm#X7)3)nAL1%H-|aRWp4AB*L>!;fCVjNVT)MQVivcA zB`sxX%lNrvEoXTvSkX#W_6w`{rB$tFb!%ADTGqCXb**Q88`#iBHufu<*wkh=w}mZj zWoz5m)^@hHgB|T;XS>+dZg#haJ?&+0``Fih_IH2-9pqq#IMiVdcZ4Gy+4Jc+_Ja_k<@s Z{je<5eoyw?!@j^&U3ci9oSvi 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%0DtEi}`(xpp#czAevdWM9Abm`J%=FFL^R;@}( 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 literal 0 HcmV?d00001 diff --git a/Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicDraco/2.pnts b/Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicDraco/2.pnts new file mode 100644 index 0000000000000000000000000000000000000000..04b6849094e131d45d7860230a46efca2de29b82 GIT binary patch literal 16668 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


QO6{aKDFp{&@N3h!mh5BO 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 literal 0 HcmV?d00001 diff --git a/Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicDraco/3.pnts b/Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicDraco/3.pnts new file mode 100644 index 0000000000000000000000000000000000000000..039ea227a0eb15890f045e304afbb3adbf3dd820 GIT binary patch literal 16684 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 literal 0 HcmV?d00001 diff --git a/Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicDraco/4.pnts b/Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicDraco/4.pnts new file mode 100644 index 0000000000000000000000000000000000000000..2bec0929b1e35cd53defd366d10504b91aeb10d6 GIT binary patch literal 16676 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 literal 0 HcmV?d00001 diff --git a/Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/1.pnts b/Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicWithTransform/1.pnts similarity index 100% rename from Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/1.pnts rename to Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicWithTransform/1.pnts diff --git a/Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicWithTransform/2.pnts b/Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicWithTransform/2.pnts new file mode 100644 index 0000000000000000000000000000000000000000..7bcab33a5c45320f0fbe89da1f3d8282a78caf69 GIT binary patch literal 33332 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 a_C5Q){lI=`Ke8X&Pwc1mGyA#y!v23>U@vw6 literal 0 HcmV?d00001 diff --git a/Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicWithTransform/3.pnts b/Specs/Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicWithTransform/3.pnts new file mode 100644 index 0000000000000000000000000000000000000000..4c110c36b04d8ce57f2d0989f88cf5282c079806 GIT binary patch literal 33332 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 U`}PC-q5a5yY(KG|+RyC&5A76@dH?_b literal 0 HcmV?d00001 diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js index fe2d50b80f7b..9c45206be41f 100644 --- a/Specs/Scene/Cesium3DTilesetSpec.js +++ b/Specs/Scene/Cesium3DTilesetSpec.js @@ -517,7 +517,6 @@ defineSuite([ scene.renderForSpecs(); expect(commandList.length).toBe(0); }); - }); it('renders tileset with empty root tile', function() { diff --git a/Specs/Scene/PointCloud3DTileContentSpec.js b/Specs/Scene/PointCloud3DTileContentSpec.js index 94dea7c4699e..096a638a2a82 100644 --- a/Specs/Scene/PointCloud3DTileContentSpec.js +++ b/Specs/Scene/PointCloud3DTileContentSpec.js @@ -182,6 +182,16 @@ defineSuite([ expect(content._pointCloud._drawCommand._vertexArray._attributes[1].componentDatatype).toEqual(ComponentDatatype.UNSIGNED_SHORT); }); + it('gets tileset properties', function() { + return Cesium3DTilesTester.loadTileset(scene, pointCloudRGBUrl).then(function(tileset) { + var root = tileset._root; + var content = root._content; + expect(content.tileset).toBe(tileset); + expect(content.tile).toBe(root); + expect(content.url.indexOf(root._header.content.url) > -1).toBe(true); + }); + }); + it('resolves readyPromise', function() { return Cesium3DTilesTester.resolvesReadyPromise(scene, pointCloudRGBUrl); }); diff --git a/Specs/Scene/TimeDynamicPointCloudSpec.js b/Specs/Scene/TimeDynamicPointCloudSpec.js new file mode 100644 index 000000000000..66466e48bf8e --- /dev/null +++ b/Specs/Scene/TimeDynamicPointCloudSpec.js @@ -0,0 +1,726 @@ +defineSuite([ + 'Scene/TimeDynamicPointCloud', + 'Core/Cartesian3', + 'Core/Clock', + 'Core/ClockStep', + 'Core/combine', + 'Core/defaultValue', + 'Core/defined', + 'Core/HeadingPitchRange', + 'Core/HeadingPitchRoll', + 'Core/JulianDate', + 'Core/Math', + 'Core/Matrix4', + 'Core/Resource', + 'Core/TimeIntervalCollection', + 'Core/Transforms', + 'Scene/Cesium3DTileStyle', + 'Scene/ClippingPlane', + 'Scene/ClippingPlaneCollection', + 'Scene/DracoLoader', + 'Scene/ShadowMode', + 'Specs/createCanvas', + 'Specs/createScene', + 'Specs/pollToPromise', + 'ThirdParty/when' + ], function( + TimeDynamicPointCloud, + Cartesian3, + Clock, + ClockStep, + combine, + defaultValue, + defined, + HeadingPitchRange, + HeadingPitchRoll, + JulianDate, + CesiumMath, + Matrix4, + Resource, + TimeIntervalCollection, + Transforms, + Cesium3DTileStyle, + ClippingPlane, + ClippingPlaneCollection, + DracoLoader, + ShadowMode, + createCanvas, + createScene, + pollToPromise, + when) { + 'use strict'; + + var scene; + + var center = new Cartesian3(1215012.8828876738, -4736313.051199594, 4081605.22126042); + + var clock = new Clock({ + clockStep : ClockStep.TICK_DEPENDENT, + shouldAnimate : true + }); + + var dates = [ + JulianDate.fromIso8601('2018-07-19T15:18:00Z'), + JulianDate.fromIso8601('2018-07-19T15:18:00.5Z'), + JulianDate.fromIso8601('2018-07-19T15:18:01Z'), + JulianDate.fromIso8601('2018-07-19T15:18:01.5Z'), + JulianDate.fromIso8601('2018-07-19T15:18:02Z'), + JulianDate.fromIso8601('2018-07-19T15:18:02.5Z') + ]; + + var transforms = [ + Matrix4.fromColumnMajorArray([0.968635634376879,0.24848542777253735,0,0,-0.15986460794399626,0.6231776137472074,0.7655670897127491,0,0.190232265775849,-0.7415555636019701,0.6433560687121489,0,1215012.8828876738,-4736313.051199594,4081605.22126042,1]), + Matrix4.fromColumnMajorArray([0.968634888916237,0.24848833367832227,0,0,-0.1598664774761181,0.6231771341505793,0.7655670897127493,0,0.19023449044168372,-0.7415549929018358,0.6433560687121489,0,1215027.0918213597,-4736309.406139632,4081605.22126042,1]), + Matrix4.fromColumnMajorArray([0.9686341434468771,0.24849123958187078,0,0,-0.1598683470068011,0.6231766545483426,0.7655670897127493,0,0.19023671510580634,-0.7415544221950274,0.6433560687121489,0,1215041.3007441103,-4736305.761037043,4081605.22126042,1]), + Matrix4.fromColumnMajorArray([0.9686333979687994,0.24849414548318288,0,0,-0.15987021653604533,0.6231761749404972,0.7655670897127491,0,0.19023893976821685,-0.7415538514815451,0.6433560687121489,0,1215055.5096559257,-4736302.115891827,4081605.22126042,1]), + Matrix4.fromColumnMajorArray([0.9686326524820043,0.2484970513822586,0,0,-0.15987208606385075,0.6231756953270434,0.7655670897127492,0,0.19024116442891523,-0.7415532807613887,0.6433560687121489,0,1215069.7185568055,-4736298.470703985,4081605.22126042,1]) + ]; + + function createIntervals(useTransforms, useDraco) { + var folderName; + if (useTransforms) { + folderName = 'Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicWithTransform/'; + } else if (useDraco) { + folderName = 'Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamicDraco/'; + } else { + folderName = 'Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/'; + } + + var uris = []; + for (var i = 0; i < 5; ++i) { + uris.push(folderName + i + '.pnts'); + } + + function dataCallback(interval, index) { + return { + uri : uris[index], + transform : useTransforms ? transforms[index] : undefined + }; + } + + return TimeIntervalCollection.fromJulianDateArray({ + julianDates : dates, + dataCallback : dataCallback + }); + } + + function createTimeDynamicPointCloud(options) { + options = defaultValue(options, {}); + var useTransforms = defaultValue(options.useTransforms, false); + var useDraco = defaultValue(options.useDraco, false); + options.intervals = createIntervals(useTransforms, useDraco); + options.clock = clock; + if (!defined(options.style)) { + options.style = new Cesium3DTileStyle({ + color : 'color("red")', + pointSize : 10 + }); + } + return scene.primitives.add(new TimeDynamicPointCloud(options)); + } + + function zoomTo(center) { + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 5.0)); + } + + function loadFrame(pointCloud, index) { + index = defaultValue(index, 0); + goToFrame(index); + return pollToPromise(function() { + scene.renderForSpecs(); + var frame = pointCloud._frames[index]; + var ready = defined(frame) && frame.ready; + if (ready) { + scene.renderForSpecs(); + } + return ready; + }); + } + + function getLoadFrameFunction(pointCloud, index) { + return function() { + return loadFrame(pointCloud, index); + }; + } + + function loadFrames(pointCloud, indexes) { + var length = indexes.length; + var promise = getLoadFrameFunction(pointCloud, indexes[0])(); + for (var i = 1; i < length; ++i) { + promise = promise.then(getLoadFrameFunction(pointCloud, indexes[i])); + } + return promise.then(function() { + goToFrame(indexes[0]); + }); + } + + function loadAllFrames(pointCloud) { + return loadFrames(pointCloud, [0, 1, 2, 3, 4]); + } + + function goToFrame(index) { + clock.currentTime = dates[index]; + clock.multiplier = 0.0; + } + + function initializeScene() { + scene.morphTo3D(0.0); + zoomTo(center); + goToFrame(0); + } + + beforeAll(function() { + scene = createScene(); + }); + + afterAll(function() { + scene.destroyForSpecs(); + }); + + beforeEach(function() { + initializeScene(); + }); + + afterEach(function() { + scene.primitives.removeAll(); + }); + + it('throws if options.clock is undefined', function() { + var intervals = createIntervals(); + expect(function(){ + return new TimeDynamicPointCloud({ + intervals : intervals + }); + }).toThrowDeveloperError(); + }); + + it('throws if options.intervals is undefined', function() { + expect(function(){ + return new TimeDynamicPointCloud({ + clock : clock + }); + }).toThrowDeveloperError(); + }); + + it('renders in 3D', function() { + var pointCloud = createTimeDynamicPointCloud(); + return loadFrame(pointCloud).then(function() { + scene.morphTo3D(0.0); + expect(scene).toRender([255, 0, 0, 255]); + goToFrame(1); + expect(scene).toRender([255, 0, 0, 255]); + scene.camera.moveRight(10.0); + expect(scene).toRender([0, 0, 0, 255]); + }); + }); + + it('renders in 2D', function() { + var pointCloud = createTimeDynamicPointCloud(); + return loadFrame(pointCloud).then(function() { + scene.morphTo2D(0.0); + expect(scene).toRender([255, 0, 0, 255]); + goToFrame(1); + expect(scene).toRender([255, 0, 0, 255]); + scene.camera.moveRight(10.0); + expect(scene).toRender([0, 0, 0, 255]); + }); + }); + + it('renders in CV', function() { + var pointCloud = createTimeDynamicPointCloud(); + return loadFrame(pointCloud).then(function() { + scene.morphToColumbusView(0.0); + expect(scene).toRender([255, 0, 0, 255]); + goToFrame(1); + expect(scene).toRender([255, 0, 0, 255]); + scene.camera.moveRight(10.0); + expect(scene).toRender([0, 0, 0, 255]); + }); + }); + + it('sets show', function() { + var pointCloud = createTimeDynamicPointCloud(); + + return loadFrame(pointCloud).then(function() { + expect(scene).toRender([255, 0, 0, 255]); + pointCloud.show = false; + expect(scene).toRender([0, 0, 0, 255]); + }); + }); + + it('sets model matrix', function() { + var translation = new Cartesian3(10000, 2000, 100); + var modelMatrix = Matrix4.fromTranslation(translation); + var newCenter = Cartesian3.add(center, translation, new Cartesian3()); + var pointCloud = createTimeDynamicPointCloud({ + modelMatrix : modelMatrix + }); + return loadFrame(pointCloud).then(function() { + expect(scene).toRender([0, 0, 0, 255]); // Out of view + zoomTo(newCenter); + expect(scene).toRender([255, 0, 0, 255]); + pointCloud.modelMatrix = Matrix4.IDENTITY; + expect(scene).toRender([0, 0, 0, 255]); // Out of view + zoomTo(center); + expect(scene).toRender([255, 0, 0, 255]); + }); + }); + + it('sets shadows', function() { + var pointCloud = createTimeDynamicPointCloud({ + shadows : ShadowMode.DISABLED + }); + return loadFrame(pointCloud).then(function() { + scene.renderForSpecs(); + expect(scene.frameState.commandList[0].castShadows).toBe(false); + expect(scene.frameState.commandList[0].receiveShadows).toBe(false); + pointCloud.shadows = ShadowMode.ENABLED; + scene.renderForSpecs(); + expect(scene.frameState.commandList[0].castShadows).toBe(true); + expect(scene.frameState.commandList[0].receiveShadows).toBe(true); + }); + }); + + it('honors maximumMemoryUsage by unloading all frames not currently being loaded or rendered', function() { + var pointCloud = createTimeDynamicPointCloud(); + return loadAllFrames(pointCloud).then(function() { + var singleFrameMemoryUsage = 33000; + var frames = pointCloud._frames; + var framesLength = frames.length; + expect(pointCloud.totalMemoryUsageInBytes).toBe(singleFrameMemoryUsage * framesLength); + pointCloud.maximumMemoryUsage = 0; + + // Expect all frames except the current frame to be undefined + scene.renderForSpecs(); + expect(pointCloud.totalMemoryUsageInBytes).toBe(singleFrameMemoryUsage); + expect(frames[0].ready).toBe(true); + for (var i = 1; i < length; ++i) { + expect(frames[i]).toBeUndefined(); + } + + // The loading frame and last rendered frame are not unloaded + goToFrame(1); + scene.renderForSpecs(); + expect(pointCloud.totalMemoryUsageInBytes).toBe(singleFrameMemoryUsage); + expect(frames[0].ready).toBe(true); + expect(frames[1].ready).toBe(false); + + // The loaded frame is the only one loaded + return loadFrame(pointCloud, 1).then(function() { + expect(pointCloud.totalMemoryUsageInBytes).toBe(singleFrameMemoryUsage); + expect(frames[0]).toBeUndefined(); + expect(frames[1].ready).toBe(true); + }); + }); + }); + + it('enables attenuation and eye dome lighting', function() { + var oldScene = scene; + scene = createScene({ + canvas : createCanvas(10, 10) + }); + initializeScene(); + + var pointCloud = createTimeDynamicPointCloud({ + pointCloudShading : { + attenuation : true, + eyeDomeLighting : false + }, + style : new Cesium3DTileStyle() + }); + + return loadFrame(pointCloud).then(function() { + var attenuationPixelCount; + expect(scene).toRenderPixelCountAndCall(function(pixelCount) { + attenuationPixelCount = pixelCount; + }); + + // Disable attenuation and expect less pixels to be drawn + pointCloud.pointCloudShading.attenuation = false; + expect(scene).toRenderPixelCountAndCall(function(pixelCount) { + expect(pixelCount).toBeLessThan(attenuationPixelCount); + }); + + // Enable eye dome lighting + expect(scene.frameState.commandList.length).toBe(1); + pointCloud.pointCloudShading.attenuation = true; + pointCloud.pointCloudShading.eyeDomeLighting = true; + scene.renderForSpecs(); + expect(scene.frameState.commandList.length).toBe(3); // Added 2 EDL commands + + scene.destroyForSpecs(); + scene = oldScene; + }); + }); + + it('sets style', function() { + var pointCloud = createTimeDynamicPointCloud({ + style : new Cesium3DTileStyle({ + color : 'color("blue")', + pointSize : 10 + }) + }); + return loadAllFrames(pointCloud).then(function() { + expect(scene).toRender([0, 0, 255, 255]); + pointCloud.style = new Cesium3DTileStyle({ + color : 'color("lime")', + pointSize : 10 + }); + expect(scene).toRender([0, 255, 0, 255]); + goToFrame(1); // Also check that the style is updated for the next frame + expect(scene).toRender([0, 255, 0, 255]); + }); + }); + + it('make style dirty', function() { + var pointCloud = createTimeDynamicPointCloud({ + style : new Cesium3DTileStyle({ + color : 'color("blue")', + pointSize : 10 + }) + }); + return loadAllFrames(pointCloud).then(function() { + expect(scene).toRender([0, 0, 255, 255]); + pointCloud.style.color = 'color("lime")'; + pointCloud.makeStyleDirty(); + expect(scene).toRender([0, 255, 0, 255]); + goToFrame(1); // Also check that the style is updated for the next frame + expect(scene).toRender([0, 255, 0, 255]); + }); + }); + + it('sets clipping planes', function() { + var modelMatrix = new Transforms.headingPitchRollToFixedFrame(center, new HeadingPitchRoll(0, 0, 0)); + var clippingPlanesX = new ClippingPlaneCollection({ + modelMatrix : modelMatrix, + planes : [ + new ClippingPlane(Cartesian3.UNIT_X, 0.0) + ] + }); + var clippingPlanesY = new ClippingPlaneCollection({ + modelMatrix : modelMatrix, + planes : [ + new ClippingPlane(Cartesian3.UNIT_Y, 0.0) + ] + }); + + var pointCloud = createTimeDynamicPointCloud({ + clippingPlanes : clippingPlanesX + }); + return loadAllFrames(pointCloud).then(function() { + // Go to unclipped area (right half) + scene.camera.moveRight(0.1); + goToFrame(0); + expect(scene).toRender([255, 0, 0, 255]); + goToFrame(1); + expect(scene).toRender([255, 0, 0, 255]); + + // Go to clipped area (left half) + scene.camera.moveLeft(0.2); + goToFrame(0); + expect(scene).toRender([0, 0, 0, 255]); + goToFrame(1); + expect(scene).toRender([0, 0, 0, 255]); + + // Same area no longer clipped. Responds to clipping planes updates. + pointCloud.clippingPlanes.enabled = false; + goToFrame(0); + expect(scene).toRender([255, 0, 0, 255]); + goToFrame(1); + expect(scene).toRender([255, 0, 0, 255]); + + // Sets a new clipping plane that uses a different axis + // Go to unclipped area (bottom left) + pointCloud.clippingPlanes = clippingPlanesY; + scene.camera.moveRight(0.2); + scene.camera.moveUp(0.1); + goToFrame(0); + expect(scene).toRender([255, 0, 0, 255]); + goToFrame(1); + expect(scene).toRender([255, 0, 0, 255]); + + // Go to clipped area (bottom right) + scene.camera.moveDown(0.2); + goToFrame(0); + expect(scene).toRender([0, 0, 0, 255]); + goToFrame(1); + expect(scene).toRender([0, 0, 0, 255]); + }); + }); + + it('works with frame transforms', function() { + var pointCloud = createTimeDynamicPointCloud({ + useTransforms : true + }); + return loadAllFrames(pointCloud).then(function() { + goToFrame(0); + expect(scene).toRender([255, 0, 0, 255]); + // The transform shifted the point cloud to the right + goToFrame(1); + expect(scene).toRender([0, 0, 0, 255]); + scene.camera.moveRight(10.0); + expect(scene).toRender([255, 0, 0, 255]); + }); + }); + + it('does not render during morph', function() { + var pointCloud = createTimeDynamicPointCloud(); + return loadFrame(pointCloud).then(function() { + scene.renderForSpecs(); + expect(scene.frameState.commandList.length).toBeGreaterThan(0); + scene.morphToColumbusView(1.0); + scene.renderForSpecs(); + expect(scene.frameState.commandList.length).toBe(0); + }); + }); + + it('renders frames using Draco compression', function() { + var pointCloud = createTimeDynamicPointCloud({ + useDraco : true + }); + return loadFrame(pointCloud).then(function() { + expect(scene).toRender([255, 0, 0, 255]); + }); + }); + + it('picks', function() { + var pointCloud = createTimeDynamicPointCloud(); + return loadFrame(pointCloud).then(function() { + pointCloud.show = false; + expect(scene).toPickPrimitive(undefined); + pointCloud.show = true; + expect(scene).toPickPrimitive(pointCloud); + }); + }); + + it('does not render if current time is out of range', function() { + var pointCloud = createTimeDynamicPointCloud(); + return loadFrame(pointCloud).then(function() { + // Before + clock.currentTime = JulianDate.addSeconds(dates[0], -10.0, new JulianDate()); + scene.renderForSpecs(); + expect(scene.frameState.commandList.length).toBe(0); + // During + clock.currentTime = dates[0]; + scene.renderForSpecs(); + expect(scene.frameState.commandList.length).toBe(1); + // After + clock.currentTime = JulianDate.addSeconds(dates[5], 10.0, new JulianDate()); + scene.renderForSpecs(); + expect(scene.frameState.commandList.length).toBe(0); + }); + }); + + it('prefetches different frame when clock multiplier changes', function() { + var pointCloud = createTimeDynamicPointCloud(); + spyOn(pointCloud, '_getAverageLoadTime').and.returnValue(0.5); + return loadFrame(pointCloud).then(function() { + expect(pointCloud._frames[1]).toBeUndefined(); + clock.multiplier = 1.0; + scene.renderForSpecs(); + expect(pointCloud._frames[1]).toBeDefined(); + clock.multiplier = 4.0; + scene.renderForSpecs(); + expect(pointCloud._frames[2]).toBeUndefined(); + expect(pointCloud._frames[3]).toBeUndefined(); + expect(pointCloud._frames[4]).toBeDefined(); + }); + }); + + it('renders last rendered frame while new frame loads', function() { + var pointCloud = createTimeDynamicPointCloud(); + return loadFrame(pointCloud).then(function() { + var commandList = scene.frameState.commandList; + var firstFrameCommand = commandList[0]; + goToFrame(4); + return pollToPromise(function() { + scene.renderForSpecs(); + var frame = pointCloud._frames[4]; + var ready = defined(frame) && frame.ready; + if (!ready) { + expect(commandList[0]).toBe(firstFrameCommand); + } + return ready; + }).then(function() { + scene.renderForSpecs(); + expect(commandList[0]).toBeDefined(); + expect(commandList[0]).not.toBe(firstFrameCommand); + }); + }); + }); + + it('skips frames based on average load time and clock multiplier', function() { + var pointCloud = createTimeDynamicPointCloud(); + spyOn(pointCloud, '_getAverageLoadTime').and.returnValue(2.0); + scene.renderForSpecs(); // at 0.0 seconds - loads frame 0 + clock.multiplier = 0.6; + scene.renderForSpecs(); // at 0.0 seconds - preloads frame 2 at 1.2 seconds + clock.tick(); + scene.renderForSpecs(); // at 0.6 seconds + clock.tick(); + scene.renderForSpecs(); // at 1.2 seconds - preloads frame 4 at 2.4 seconds + clock.tick(); + scene.renderForSpecs(); // at 1.8 seconds + clock.tick(); + scene.renderForSpecs(); // at 2.4 seconds + + var frames = pointCloud._frames; + expect(frames[0]).toBeDefined(); + expect(frames[1]).toBeUndefined(); + expect(frames[2]).toBeDefined(); + expect(frames[3]).toBeUndefined(); + expect(frames[4]).toBeDefined(); + }); + + it('does not skip frames if clock multiplier is sufficiently slow', function() { + var pointCloud = createTimeDynamicPointCloud(); + spyOn(pointCloud, '_getAverageLoadTime').and.returnValue(0.5); + scene.renderForSpecs(); // at 0.0 seconds - loads frame 0 + clock.multiplier = 0.6; + scene.renderForSpecs(); // at 0.0 seconds - preloads frame 1 + clock.tick(); + scene.renderForSpecs(); // at 0.6 seconds - preloads frame 2 + clock.tick(); + scene.renderForSpecs(); // at 1.2 seconds - preloads frame 3 + clock.tick(); + scene.renderForSpecs(); // at 1.8 seconds - preloads frame 4 + clock.tick(); + scene.renderForSpecs(); // at 2.4 seconds + + var frames = pointCloud._frames; + expect(frames[0]).toBeDefined(); + expect(frames[1]).toBeDefined(); + expect(frames[2]).toBeDefined(); + expect(frames[3]).toBeDefined(); + expect(frames[4]).toBeDefined(); + }); + + it('renders loaded frames between the previous frame and next frame', function() { + var pointCloud = createTimeDynamicPointCloud(); + spyOn(pointCloud, '_getAverageLoadTime').and.returnValue(3.4); + var frames = pointCloud._frames; + return loadFrames(pointCloud, [0, 2]).then(function() { + clock.multiplier = 0.6; + scene.renderForSpecs(); // at 0.0 seconds - preloads frame 4 at 2.04 seconds - renders frame 0 + expect(pointCloud._lastRenderedFrame).toBe(frames[0]); + clock.tick(); + scene.renderForSpecs(); // at 0.6 seconds - renders frame 0 + expect(pointCloud._lastRenderedFrame).toBe(frames[0]); + clock.tick(); + scene.renderForSpecs(); // at 1.2 seconds - renders frame 2 which has already been loaded + expect(pointCloud._lastRenderedFrame).toBe(frames[2]); + clock.tick(); + scene.renderForSpecs(); // at 1.8 seconds - renders frame 2 + expect(pointCloud._lastRenderedFrame).toBe(frames[2]); + }); + }); + + it('works with negative clock multiplier', function() { + var pointCloud = createTimeDynamicPointCloud(); + spyOn(pointCloud, '_getAverageLoadTime').and.returnValue(2.0); + goToFrame(4); + scene.renderForSpecs(); // at 2.0 seconds - loads frame 4 + clock.multiplier = -0.6; + scene.renderForSpecs(); // at 2.0 seconds - preloads frame 1 at 0.8 seconds + clock.tick(); + scene.renderForSpecs(); // at 1.4 seconds + clock.tick(); + scene.renderForSpecs(); // at 0.8 seconds - nothing left to preload + clock.tick(); + scene.renderForSpecs(); // at 0.2 seconds + clock.tick(); + scene.renderForSpecs(); // at -0.4 seconds + + var frames = pointCloud._frames; + expect(frames[0]).toBeUndefined(); + expect(frames[1]).toBeDefined(); + expect(frames[2]).toBeUndefined(); + expect(frames[3]).toBeUndefined(); + expect(frames[4]).toBeDefined(); + }); + + it('frames not loaded in sequential updates do not impact average load time', function() { + var pointCloud = createTimeDynamicPointCloud(); + var initialAverageLoadTime = pointCloud._getAverageLoadTime(); + return loadFrame(pointCloud).then(function() { + var firstFrameLoadTime = pointCloud._getAverageLoadTime(); + expect(firstFrameLoadTime).not.toBe(initialAverageLoadTime); + expect(firstFrameLoadTime).toBeGreaterThan(0.0); + goToFrame(2); // Start loading frame 2, but don't finish loading it now + scene.renderForSpecs(); + return loadFrame(pointCloud, 1).then(function() { + var averageLoadTime = pointCloud._getAverageLoadTime(); + expect(averageLoadTime).not.toBe(firstFrameLoadTime); + expect(averageLoadTime).toBeGreaterThan(0.0); + return loadFrame(pointCloud, 2).then(function() { + expect(pointCloud._frames[2].sequential).toBe(false); + expect(pointCloud._getAverageLoadTime()).toBe(averageLoadTime); + }); + }); + }); + }); + + it('frame failed event is raised from request failure', function() { + var pointCloud = createTimeDynamicPointCloud(); + spyOn(Resource._Implementations, 'loadWithXhr').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) { + deferred.reject('404'); + }); + var spyUpdate = jasmine.createSpy('listener'); + pointCloud.frameFailed.addEventListener(spyUpdate); + + var i; + for (i = 0; i < 5; ++i) { + goToFrame(i); + scene.renderForSpecs(); + } + + for (i = 0; i < 5; ++i) { + var arg = spyUpdate.calls.argsFor(i)[0]; + expect(arg).toBeDefined(); + expect(arg.url).toContain(i + '.pnts'); + expect(arg.message).toBe('404'); + } + }); + + it('failed frame event is raised from Draco failure', function() { + var pointCloud = createTimeDynamicPointCloud({ + useDraco : true + }); + return loadFrame(pointCloud).then(function() { + var decoder = DracoLoader._getDecoderTaskProcessor(); + spyOn(decoder, 'scheduleTask').and.returnValue(when.reject({message : 'my error'})); + var spyUpdate = jasmine.createSpy('listener'); + pointCloud.frameFailed.addEventListener(spyUpdate); + goToFrame(1); + scene.renderForSpecs(); + var failedPromise; + var frameFailed = false; + return pollToPromise(function() { + var contents = pointCloud._frames[1].pointCloud; + if (defined(contents) && !defined(failedPromise)) { + failedPromise = contents.readyPromise.otherwise(function() { + frameFailed = true; + }); + } + scene.renderForSpecs(); + return frameFailed; + }).then(function() { + var arg = spyUpdate.calls.argsFor(0)[0]; + expect(arg).toBeDefined(); + expect(arg.url).toContain('1.pnts'); + expect(arg.message).toBe('my error'); + }); + }); + }); + + it('destroys', function() { + var pointCloud = createTimeDynamicPointCloud(); + return loadAllFrames(pointCloud).then(function() { + expect(pointCloud.isDestroyed()).toEqual(false); + scene.primitives.remove(pointCloud); + expect(pointCloud.isDestroyed()).toEqual(true); + expect(pointCloud.totalMemoryUsageInBytes).toBe(0); + }); + }); + +}, 'WebGL'); From 3739e84638bd85845bd3751a4104799ad8fda8fa Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 2 Jul 2018 17:01:40 -0400 Subject: [PATCH 27/35] Rename tilesets folder to Cesium3DTiles --- .../PointCloud/PointCloudTimeDynamic/0.pnts | Bin .../PointCloud/PointCloudTimeDynamic/1.pnts | Bin 0 -> 33404 bytes .../PointCloud/PointCloudTimeDynamic/2.pnts | Bin .../PointCloud/PointCloudTimeDynamic/3.pnts | Bin .../PointCloud/PointCloudTimeDynamic/4.pnts | Bin .../gallery/Time Dynamic Point Cloud.html | 10 +++++----- 6 files changed, 5 insertions(+), 5 deletions(-) rename Apps/SampleData/{tilesets => Cesium3DTiles}/PointCloud/PointCloudTimeDynamic/0.pnts (100%) create mode 100644 Apps/SampleData/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/1.pnts rename Apps/SampleData/{tilesets => Cesium3DTiles}/PointCloud/PointCloudTimeDynamic/2.pnts (100%) rename Apps/SampleData/{tilesets => Cesium3DTiles}/PointCloud/PointCloudTimeDynamic/3.pnts (100%) rename Apps/SampleData/{tilesets => Cesium3DTiles}/PointCloud/PointCloudTimeDynamic/4.pnts (100%) diff --git a/Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/0.pnts b/Apps/SampleData/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/0.pnts similarity index 100% rename from Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/0.pnts rename to Apps/SampleData/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/0.pnts diff --git a/Apps/SampleData/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/1.pnts b/Apps/SampleData/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/1.pnts new file mode 100644 index 0000000000000000000000000000000000000000..5a1c21cbbe581acc4b47e4e4056538cbdf7414d8 GIT binary patch literal 33404 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 literal 0 HcmV?d00001 diff --git a/Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/2.pnts b/Apps/SampleData/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/2.pnts similarity index 100% rename from Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/2.pnts rename to Apps/SampleData/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/2.pnts diff --git a/Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/3.pnts b/Apps/SampleData/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/3.pnts similarity index 100% rename from Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/3.pnts rename to Apps/SampleData/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/3.pnts diff --git a/Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/4.pnts b/Apps/SampleData/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/4.pnts similarity index 100% rename from Apps/SampleData/tilesets/PointCloud/PointCloudTimeDynamic/4.pnts rename to Apps/SampleData/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/4.pnts diff --git a/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html b/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html index 43a42e4c630f..64a4de65e926 100644 --- a/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html +++ b/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html @@ -43,11 +43,11 @@ ]; var uris = [ - '../../SampleData/tilesets/PointCloud/PointCloudTimeDynamic/0.pnts', - '../../SampleData/tilesets/PointCloud/PointCloudTimeDynamic/1.pnts', - '../../SampleData/tilesets/PointCloud/PointCloudTimeDynamic/2.pnts', - '../../SampleData/tilesets/PointCloud/PointCloudTimeDynamic/3.pnts', - '../../SampleData/tilesets/PointCloud/PointCloudTimeDynamic/4.pnts' + '../../SampleData/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/0.pnts', + '../../SampleData/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/1.pnts', + '../../SampleData/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/2.pnts', + '../../SampleData/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/3.pnts', + '../../SampleData/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/4.pnts' ]; function dataCallback(interval, index) { From 04a87970bcd204500dcb739f321d3213ce54f434 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 2 Jul 2018 21:42:51 -0400 Subject: [PATCH 28/35] Fix EDL test for webgl-stub --- Specs/Scene/TimeDynamicPointCloudSpec.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Specs/Scene/TimeDynamicPointCloudSpec.js b/Specs/Scene/TimeDynamicPointCloudSpec.js index 66466e48bf8e..5b1eec88bf1c 100644 --- a/Specs/Scene/TimeDynamicPointCloudSpec.js +++ b/Specs/Scene/TimeDynamicPointCloudSpec.js @@ -18,6 +18,7 @@ defineSuite([ 'Scene/ClippingPlane', 'Scene/ClippingPlaneCollection', 'Scene/DracoLoader', + 'Scene/PointCloudEyeDomeLighting', 'Scene/ShadowMode', 'Specs/createCanvas', 'Specs/createScene', @@ -43,6 +44,7 @@ defineSuite([ ClippingPlane, ClippingPlaneCollection, DracoLoader, + PointCloudEyeDomeLighting, ShadowMode, createCanvas, createScene, @@ -341,15 +343,23 @@ defineSuite([ expect(pixelCount).toBeLessThan(attenuationPixelCount); }); - // Enable eye dome lighting + scene.destroyForSpecs(); + scene = oldScene; + }); + }); + + it('enabled eye dome lighting', function() { + if (!PointCloudEyeDomeLighting.isSupported(scene.frameState.context)) { + return; + } + + var pointCloud = createTimeDynamicPointCloud(); + return loadFrame(pointCloud).then(function() { expect(scene.frameState.commandList.length).toBe(1); pointCloud.pointCloudShading.attenuation = true; pointCloud.pointCloudShading.eyeDomeLighting = true; scene.renderForSpecs(); expect(scene.frameState.commandList.length).toBe(3); // Added 2 EDL commands - - scene.destroyForSpecs(); - scene = oldScene; }); }); From a9d2be7d5f6f5e1050d7fcd184568c681ef3df5a Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 2 Jul 2018 21:46:16 -0400 Subject: [PATCH 29/35] Rename pointCloudShading to shading --- Source/Scene/TimeDynamicPointCloud.js | 30 ++++++++++++------------ Specs/Scene/TimeDynamicPointCloudSpec.js | 8 +++---- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index 187a17221046..22e9e6093b23 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -58,7 +58,7 @@ define([ * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] A 4x4 transformation matrix that transforms the point cloud. * @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the point cloud casts or receives shadows from each light source. * @param {Number} [options.maximumMemoryUsage=256] The maximum amount of memory in MB that can be used by the point cloud. - * @param {Object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation and eye dome lighting. + * @param {Object} [options.shading] Options for constructing a {@link PointCloudShading} object to control point attenuation and eye dome lighting. * @param {Cesium3DTileStyle} [options.style] The style, defined using the {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language}, applied to each point in the point cloud. * @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the point cloud. */ @@ -120,7 +120,7 @@ define([ * Options for controlling point size based on geometric error and eye dome lighting. * @type {PointCloudShading} */ - this.pointCloudShading = new PointCloudShading(options.pointCloudShading); + this.shading = new PointCloudShading(options.shading); /** * The style, defined using the @@ -416,9 +416,9 @@ define([ var scratchModelMatrix = new Matrix4(); function getGeometricError(that, pointCloud) { - var pointCloudShading = that.pointCloudShading; - if (defined(pointCloudShading) && defined(pointCloudShading.baseResolution)) { - return pointCloudShading.baseResolution; + var shading = that.shading; + if (defined(shading) && defined(shading.baseResolution)) { + return shading.baseResolution; } else if (defined(pointCloud.boundingSphere)) { return CesiumMath.cbrt(pointCloud.boundingSphere.volume() / pointCloud.pointsLength); } @@ -426,9 +426,9 @@ define([ } function getMaximumAttenuation(that) { - var pointCloudShading = that.pointCloudShading; - if (defined(pointCloudShading) && defined(pointCloudShading.maximumAttenuation)) { - return pointCloudShading.maximumAttenuation; + var shading = that.shading; + if (defined(shading) && defined(shading.maximumAttenuation)) { + return shading.maximumAttenuation; } // Return a hardcoded maximum attenuation. For a tileset this would instead be the maximum screen space error. @@ -445,11 +445,11 @@ define([ pointCloud.clippingPlanes = that._clippingPlanes; pointCloud.isClipped = updateState.isClipped; - var pointCloudShading = that.pointCloudShading; - if (defined(pointCloudShading)) { - pointCloud.attenuation = pointCloudShading.attenuation; + var shading = that.shading; + if (defined(shading)) { + pointCloud.attenuation = shading.attenuation; pointCloud.geometricError = getGeometricError(that, pointCloud); - pointCloud.geometricErrorScale = pointCloudShading.geometricErrorScale; + pointCloud.geometricErrorScale = shading.geometricErrorScale; pointCloud.maximumAttenuation = getMaximumAttenuation(that); } pointCloud.update(frameState); @@ -615,7 +615,7 @@ define([ updateState.timeSinceLoad = timeSinceLoad; updateState.isClipped = isClipped; - var pointCloudShading = this.pointCloudShading; + var shading = this.shading; var eyeDomeLighting = this._pointCloudEyeDomeLighting; var commandList = frameState.commandList; @@ -678,8 +678,8 @@ define([ var lengthAfterUpdate = commandList.length; var addedCommandsLength = lengthAfterUpdate - lengthBeforeUpdate; - if (defined(pointCloudShading) && pointCloudShading.attenuation && pointCloudShading.eyeDomeLighting && (addedCommandsLength > 0)) { - eyeDomeLighting.update(frameState, lengthBeforeUpdate, pointCloudShading); + if (defined(shading) && shading.attenuation && shading.eyeDomeLighting && (addedCommandsLength > 0)) { + eyeDomeLighting.update(frameState, lengthBeforeUpdate, shading); } }; diff --git a/Specs/Scene/TimeDynamicPointCloudSpec.js b/Specs/Scene/TimeDynamicPointCloudSpec.js index 5b1eec88bf1c..1fb11177df9a 100644 --- a/Specs/Scene/TimeDynamicPointCloudSpec.js +++ b/Specs/Scene/TimeDynamicPointCloudSpec.js @@ -324,7 +324,7 @@ defineSuite([ initializeScene(); var pointCloud = createTimeDynamicPointCloud({ - pointCloudShading : { + shading : { attenuation : true, eyeDomeLighting : false }, @@ -338,7 +338,7 @@ defineSuite([ }); // Disable attenuation and expect less pixels to be drawn - pointCloud.pointCloudShading.attenuation = false; + pointCloud.shading.attenuation = false; expect(scene).toRenderPixelCountAndCall(function(pixelCount) { expect(pixelCount).toBeLessThan(attenuationPixelCount); }); @@ -356,8 +356,8 @@ defineSuite([ var pointCloud = createTimeDynamicPointCloud(); return loadFrame(pointCloud).then(function() { expect(scene.frameState.commandList.length).toBe(1); - pointCloud.pointCloudShading.attenuation = true; - pointCloud.pointCloudShading.eyeDomeLighting = true; + pointCloud.shading.attenuation = true; + pointCloud.shading.eyeDomeLighting = true; scene.renderForSpecs(); expect(scene.frameState.commandList.length).toBe(3); // Added 2 EDL commands }); From e1fddc026579a5f7d1ad927f2a02eecae701e76f Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 2 Jul 2018 21:52:16 -0400 Subject: [PATCH 30/35] More description for frameFailed event --- Source/Scene/TimeDynamicPointCloud.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index 22e9e6093b23..6022db402b15 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -150,7 +150,8 @@ define([ this.style = options.style; /** - * The event fired to indicate that a frame failed to load. + * The event fired to indicate that a frame failed to load. A frame may fail to load if the + * request for its uri fails or processing fails due to invalid content. *

* If there are no event listeners, error messages will be logged to the console. *

From f531406d9b561412a9beb532657647d189a240fc Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 3 Jul 2018 13:18:42 -0400 Subject: [PATCH 31/35] Added viewer.zoomTo(timeDynamicPointCloud) --- .../gallery/Time Dynamic Point Cloud.html | 6 +- Source/Scene/PointCloud.js | 19 +- Source/Scene/TimeDynamicPointCloud.js | 16 ++ Source/Widgets/Viewer/Viewer.js | 57 +++++- Specs/Scene/TimeDynamicPointCloudSpec.js | 18 ++ Specs/Widgets/Viewer/ViewerSpec.js | 167 +++++++++++++++++- 6 files changed, 264 insertions(+), 19 deletions(-) diff --git a/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html b/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html index 64a4de65e926..eed1f9625dd4 100644 --- a/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html +++ b/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html @@ -81,11 +81,7 @@ clock.stopTime = stop; clock.clockRange = Cesium.ClockRange.LOOP_STOP; -viewer.camera.setView({ - destination: new Cesium.Cartesian3(1215034.013185971, -4736376.364704681, 4081587.528471664), - orientation: new Cesium.HeadingPitchRoll(6.2077134961933265, -0.6084278203800215, 6.282880789189662), - endTransform : Cesium.Matrix4.IDENTITY -}); +viewer.zoomTo(pointCloud, new Cesium.HeadingPitchRange(0.0, -0.5, 50.0)); //Sandcastle_End Sandcastle.finishedLoading(); } diff --git a/Source/Scene/PointCloud.js b/Source/Scene/PointCloud.js index b9049515f892..53bb32d7fe01 100644 --- a/Source/Scene/PointCloud.js +++ b/Source/Scene/PointCloud.js @@ -180,7 +180,7 @@ define([ this.time = 0.0; // For styling this.shadows = ShadowMode.ENABLED; - this.boundingSphere = undefined; + this._boundingSphere = undefined; this.clippingPlanes = undefined; this.isClipped = false; @@ -229,6 +229,17 @@ define([ set : function(value) { this._highlightColor = Color.clone(value, this._highlightColor); } + }, + + boundingSphere : { + get : function() { + if (defined(this._drawCommand)) { + return this._drawCommand.boundingVolume; + } + }, + set : function(value) { + this._boundingSphere = BoundingSphere.clone(value); + } } }); @@ -634,9 +645,9 @@ define([ if (pointCloud._cull) { if (isQuantized || isQuantizedDraco) { - pointCloud.boundingSphere = BoundingSphere.fromCornerPoints(Cartesian3.ZERO, pointCloud._quantizedVolumeScale); + pointCloud._boundingSphere = BoundingSphere.fromCornerPoints(Cartesian3.ZERO, pointCloud._quantizedVolumeScale); } else { - pointCloud.boundingSphere = computeApproximateBoundingSphereFromPositions(positions); + pointCloud._boundingSphere = computeApproximateBoundingSphereFromPositions(positions); } } @@ -1298,7 +1309,7 @@ define([ } var boundingSphere = this._drawCommand.boundingVolume; - BoundingSphere.clone(this.boundingSphere, boundingSphere); + BoundingSphere.clone(this._boundingSphere, boundingSphere); if (this._cull) { var center = boundingSphere.center; diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index 6022db402b15..7e2bc7d7df10 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -222,6 +222,22 @@ define([ get : function() { return this._totalMemoryUsageInBytes; } + }, + + /** + * The bounding sphere of the frame being rendered. Returns undefined if no frame is being rendered. + * + * @memberof TimeDynamicPointCloud.prototype + * + * @type {BoundingSphere} + * @readonly + */ + boundingSphere : { + get : function() { + if (defined(this._lastRenderedFrame)) { + return this._lastRenderedFrame.pointCloud.boundingSphere; + } + } } }); diff --git a/Source/Widgets/Viewer/Viewer.js b/Source/Widgets/Viewer/Viewer.js index 2c85c9fc522e..a33216b3cc97 100644 --- a/Source/Widgets/Viewer/Viewer.js +++ b/Source/Widgets/Viewer/Viewer.js @@ -24,6 +24,7 @@ define([ '../../Scene/Cesium3DTileset', '../../Scene/ImageryLayer', '../../Scene/SceneMode', + '../../Scene/TimeDynamicPointCloud', '../../ThirdParty/knockout', '../../ThirdParty/when', '../Animation/Animation', @@ -71,6 +72,7 @@ define([ Cesium3DTileset, ImageryLayer, SceneMode, + TimeDynamicPointCloud, knockout, when, Animation, @@ -1740,7 +1742,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to * target will be the range. The heading will be determined from the offset. If the heading cannot be * determined from the offset, the heading will be north.

* - * @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset|Promise.} target The entity, array of entities, entity collection, data source, Cesium#DTileset, or imagery layer to view. You can also pass a promise that resolves to one of the previously mentioned types. + * @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset|TimeDynamicPointCloud|Promise.} target The entity, array of entities, entity collection, data source, Cesium3DTileset, point cloud, or imagery layer to view. You can also pass a promise that resolves to one of the previously mentioned types. * @param {HeadingPitchRange} [offset] The offset from the center of the entity in the local east-north-up reference frame. * @returns {Promise.} A Promise that resolves to true if the zoom was successful or false if the target is not currently visualized in the scene or the zoom was cancelled. */ @@ -1766,7 +1768,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to * target will be the range. The heading will be determined from the offset. If the heading cannot be * determined from the offset, the heading will be north.

* - * @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset|Promise.} target The entity, array of entities, entity collection, data source, Cesium3DTileset, or imagery layer to view. You can also pass a promise that resolves to one of the previously mentioned types. + * @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset|TimeDynamicPointCloud|Promise.} target The entity, array of entities, entity collection, data source, Cesium3DTileset, point cloud, or imagery layer to view. You can also pass a promise that resolves to one of the previously mentioned types. * @param {Object} [options] Object with the following properties: * @param {Number} [options.duration=3.0] The duration of the flight in seconds. * @param {Number} [options.maximumHeight] The maximum height at the peak of the flight. @@ -1818,6 +1820,12 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to return; } + //If the zoom target is a TimeDynamicPointCloud + if (zoomTarget instanceof TimeDynamicPointCloud) { + that._zoomTarget = zoomTarget; + return; + } + //If the zoom target is a data source, and it's in the middle of loading, wait for it to finish loading. if (zoomTarget.isLoading && defined(zoomTarget.loadingEvent)) { var removeEvent = zoomTarget.loadingEvent.addEventListener(function() { @@ -1891,12 +1899,13 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to var zoomPromise = viewer._zoomPromise; var zoomOptions = defaultValue(viewer._zoomOptions, {}); var options; + var boundingSphere; // If zoomTarget was Cesium3DTileset if (target instanceof Cesium3DTileset) { return target.readyPromise.then(function() { var boundingSphere = target.boundingSphere; - // if offset was originally undefined then give it base value instead of empty object + // If offset was originally undefined then give it base value instead of empty object if (!defined(zoomOptions.offset)) { zoomOptions.offset = new HeadingPitchRange(0.0, -0.5, boundingSphere.radius); } @@ -1919,7 +1928,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to camera.viewBoundingSphere(boundingSphere, zoomOptions.offset); camera.lookAtTransform(Matrix4.IDENTITY); - // finish the promise + // Finish the promise zoomPromise.resolve(true); } @@ -1927,7 +1936,43 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to }); } - //If zoomTarget was an ImageryLayer + // If zoomTarget was TimeDynamicPointCloud + if (target instanceof TimeDynamicPointCloud) { + boundingSphere = target.boundingSphere; + if (defined(boundingSphere)) { + // If offset was originally undefined then give it base value instead of empty object + if (!defined(zoomOptions.offset)) { + zoomOptions.offset = new HeadingPitchRange(0.0, -0.5, boundingSphere.radius); + } + + options = { + offset : zoomOptions.offset, + duration : zoomOptions.duration, + maximumHeight : zoomOptions.maximumHeight, + complete : function() { + zoomPromise.resolve(true); + }, + cancel : function() { + zoomPromise.resolve(false); + } + }; + + if (viewer._zoomIsFlight) { + camera.flyToBoundingSphere(boundingSphere, options); + } else { + camera.viewBoundingSphere(boundingSphere, zoomOptions.offset); + camera.lookAtTransform(Matrix4.IDENTITY); + + // Finish the promise + zoomPromise.resolve(true); + } + + clearZoom(viewer); + } + return; + } + + // If zoomTarget was an ImageryLayer if (target instanceof Rectangle) { options = { destination : target, @@ -1972,7 +2017,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to //Stop tracking the current entity. viewer.trackedEntity = undefined; - var boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres); + boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres); if (!viewer._zoomIsFlight) { camera.viewBoundingSphere(boundingSphere, viewer._zoomOptions.offset); diff --git a/Specs/Scene/TimeDynamicPointCloudSpec.js b/Specs/Scene/TimeDynamicPointCloudSpec.js index 1fb11177df9a..479198de4056 100644 --- a/Specs/Scene/TimeDynamicPointCloudSpec.js +++ b/Specs/Scene/TimeDynamicPointCloudSpec.js @@ -1,5 +1,6 @@ defineSuite([ 'Scene/TimeDynamicPointCloud', + 'Core/BoundingSphere', 'Core/Cartesian3', 'Core/Clock', 'Core/ClockStep', @@ -26,6 +27,7 @@ defineSuite([ 'ThirdParty/when' ], function( TimeDynamicPointCloud, + BoundingSphere, Cartesian3, Clock, ClockStep, @@ -240,6 +242,22 @@ defineSuite([ }); }); + it('gets bounding sphere of the rendered frame', function() { + var pointCloud = createTimeDynamicPointCloud({ + useTransforms : true + }); + expect(pointCloud.boundingSphere).toBeUndefined(); // Undefined until a frame is rendered + return loadAllFrames(pointCloud).then(function() { + var boundingSphereFrame0 = pointCloud.boundingSphere; + expect(boundingSphereFrame0).toBeDefined(); + goToFrame(1); + scene.renderForSpecs(); + var boundingSphereFrame1 = pointCloud.boundingSphere; + expect(boundingSphereFrame1).toBeDefined(); + expect(BoundingSphere.equals(boundingSphereFrame0, boundingSphereFrame1)).toBe(false); + }); + }); + it('sets show', function() { var pointCloud = createTimeDynamicPointCloud(); diff --git a/Specs/Widgets/Viewer/ViewerSpec.js b/Specs/Widgets/Viewer/ViewerSpec.js index e26ad717d42e..ad3c3543efc1 100644 --- a/Specs/Widgets/Viewer/ViewerSpec.js +++ b/Specs/Widgets/Viewer/ViewerSpec.js @@ -5,10 +5,12 @@ defineSuite([ 'Core/ClockRange', 'Core/ClockStep', 'Core/Color', + 'Core/defined', 'Core/EllipsoidTerrainProvider', 'Core/HeadingPitchRange', 'Core/JulianDate', 'Core/Matrix4', + 'Core/TimeIntervalCollection', 'Core/WebMercatorProjection', 'DataSources/BoundingSphereState', 'DataSources/ConstantPositionProperty', @@ -23,6 +25,7 @@ defineSuite([ 'Scene/ImageryLayerCollection', 'Scene/SceneMode', 'Scene/ShadowMode', + 'Scene/TimeDynamicPointCloud', 'Specs/createViewer', 'Specs/DomEventSimulator', 'Specs/MockDataSource', @@ -46,10 +49,12 @@ defineSuite([ ClockRange, ClockStep, Color, + defined, EllipsoidTerrainProvider, HeadingPitchRange, JulianDate, Matrix4, + TimeIntervalCollection, WebMercatorProjection, BoundingSphereState, ConstantPositionProperty, @@ -64,6 +69,7 @@ defineSuite([ ImageryLayerCollection, SceneMode, ShadowMode, + TimeDynamicPointCloud, createViewer, DomEventSimulator, MockDataSource, @@ -1108,6 +1114,91 @@ defineSuite([ }); }); + function loadTimeDynamicPointCloud(viewer) { + var scene = viewer.scene; + var clock = viewer.clock; + + var uri = './Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/0.pnts'; + var dates = [ + '2018-07-19T15:18:00Z', + '2018-07-19T15:18:00.5Z' + ]; + + function dataCallback() { + return { + uri: uri + }; + } + + var timeIntervalCollection = TimeIntervalCollection.fromIso8601DateArray({ + iso8601Dates: dates, + dataCallback: dataCallback + }); + + var pointCloud = new TimeDynamicPointCloud({ + intervals : timeIntervalCollection, + clock : clock + }); + + var start = JulianDate.fromIso8601(dates[0]); + + clock.startTime = start; + clock.currentTime = start; + clock.multiplier = 0.0; + + return pollToPromise(function() { + pointCloud.update(scene.frameState); + var frame = pointCloud._frames[0]; + return defined(frame) && frame.ready; + }).then(function() { + return pointCloud; + }); + } + + it('zoomTo zooms to TimeDynamicPointCloud with default offset when offset not defined', function() { + viewer = createViewer(container); + return loadTimeDynamicPointCloud(viewer).then(function(pointCloud) { + var expectedBoundingSphere = pointCloud.boundingSphere; + var expectedOffset = new HeadingPitchRange(0.0, -0.5, expectedBoundingSphere.radius); + + var promise = viewer.zoomTo(pointCloud); + var wasCompleted = false; + spyOn(viewer.camera, 'viewBoundingSphere').and.callFake(function(boundingSphere, offset) { + expect(boundingSphere).toEqual(expectedBoundingSphere); + expect(offset).toEqual(expectedOffset); + wasCompleted = true; + }); + + viewer._postRender(); + + return promise.then(function() { + expect(wasCompleted).toEqual(true); + }); + }); + }); + + it('zoomTo zooms to TimeDynamicPointCloud with offset', function() { + viewer = createViewer(container); + return loadTimeDynamicPointCloud(viewer).then(function(pointCloud) { + var expectedBoundingSphere = pointCloud.boundingSphere; + var expectedOffset = new HeadingPitchRange(0.4, 1.2, 4.0 * expectedBoundingSphere.radius); + + var promise = viewer.zoomTo(pointCloud, expectedOffset); + var wasCompleted = false; + spyOn(viewer.camera, 'viewBoundingSphere').and.callFake(function(boundingSphere, offset) { + expect(boundingSphere).toEqual(expectedBoundingSphere); + expect(offset).toEqual(expectedOffset); + wasCompleted = true; + }); + + viewer._postRender(); + + return promise.then(function() { + expect(wasCompleted).toEqual(true); + }); + }); + }); + it('zoomTo zooms to entity with undefined offset when offset not defined', function() { viewer = createViewer(container); viewer.entities.add({ @@ -1208,7 +1299,6 @@ defineSuite([ expect(wasCompleted).toEqual(true); }); }); - }); it('flyTo flies to Cesium3DTileset with default offset when offset not defined', function() { @@ -1239,11 +1329,10 @@ defineSuite([ return promise.then(function() { expect(wasCompleted).toEqual(true); }); - }); }); - it('flyTo flies to target when target is Cesium3DTileset and options are defined', function() { + it('flyTo flies to Cesium3DTileset when options are defined', function() { viewer = createViewer(container); var path = './Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json'; @@ -1275,7 +1364,78 @@ defineSuite([ return promise.then(function() { expect(wasCompleted).toEqual(true); }); + }); + }); + + it('flyTo flies to TimeDynamicPointCloud with default offset when options not defined', function() { + viewer = createViewer(container); + return loadTimeDynamicPointCloud(viewer).then(function(pointCloud) { + var promise = viewer.flyTo(pointCloud); + var wasCompleted = false; + spyOn(viewer.camera, 'flyToBoundingSphere').and.callFake(function(target, options) { + expect(options.offset).toBeDefined(); + expect(options.duration).toBeUndefined(); + expect(options.maximumHeight).toBeUndefined(); + wasCompleted = true; + options.complete(); + }); + + viewer._postRender(); + + return promise.then(function() { + expect(wasCompleted).toEqual(true); + }); + }); + }); + + it('flyTo flies to TimeDynamicPointCloud with default offset when offset not defined', function() { + viewer = createViewer(container); + return loadTimeDynamicPointCloud(viewer).then(function(pointCloud) { + var options = {}; + var promise = viewer.flyTo(pointCloud, options); + var wasCompleted = false; + + spyOn(viewer.camera, 'flyToBoundingSphere').and.callFake(function(target, options) { + expect(options.offset).toBeDefined(); + expect(options.duration).toBeUndefined(); + expect(options.maximumHeight).toBeUndefined(); + wasCompleted = true; + options.complete(); + }); + + viewer._postRender(); + + return promise.then(function() { + expect(wasCompleted).toEqual(true); + }); + }); + }); + + it('flyTo flies to TimeDynamicPointCloud when options are defined', function() { + viewer = createViewer(container); + return loadTimeDynamicPointCloud(viewer).then(function(pointCloud) { + var offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3); + var options = { + offset : offsetVal, + duration : 3.0, + maximumHeight : 5.0 + }; + var promise = viewer.flyTo(pointCloud, options); + var wasCompleted = false; + + spyOn(viewer.camera, 'flyToBoundingSphere').and.callFake(function(target, options) { + expect(options.duration).toBeDefined(); + expect(options.maximumHeight).toBeDefined(); + wasCompleted = true; + options.complete(); + }); + + viewer._postRender(); + + return promise.then(function() { + expect(wasCompleted).toEqual(true); + }); }); }); @@ -1309,7 +1469,6 @@ defineSuite([ return promise.then(function() { expect(wasCompleted).toEqual(true); }); - }); it('flyTo flys to entity with default offset when offset not defined', function() { From adf8f4ea8cc67a00a3a845521036729d8ccb3e82 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 5 Jul 2018 15:50:08 -0400 Subject: [PATCH 32/35] Comment about quantized range and scale --- Source/Scene/PointCloud.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Scene/PointCloud.js b/Source/Scene/PointCloud.js index 95a0e9852989..cb206cb98aa0 100644 --- a/Source/Scene/PointCloud.js +++ b/Source/Scene/PointCloud.js @@ -1225,6 +1225,8 @@ define([ 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); From cb9ee746b0964bc8568580fbb4992f9f9a864e7a Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 9 Jul 2018 22:58:33 -0400 Subject: [PATCH 33/35] frame changed event, ready promise, and other updates --- .../gallery/Time Dynamic Point Cloud.html | 4 +- .../gallery/Time Dynamic Point Cloud.jpg | Bin 9306 -> 46658 bytes Source/Scene/Cesium3DTileset.js | 14 ++-- Source/Scene/PointCloud.js | 19 ++++- Source/Scene/TimeDynamicImagery.js | 4 +- Source/Scene/TimeDynamicPointCloud.js | 65 +++++++++++++++--- .../Scene/WebMapTileServiceImageryProvider.js | 2 +- Source/Widgets/Viewer/Viewer.js | 7 +- Specs/Scene/TimeDynamicPointCloudSpec.js | 39 ++++++++++- 9 files changed, 123 insertions(+), 31 deletions(-) diff --git a/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html b/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html index eed1f9625dd4..4b53c638665d 100644 --- a/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html +++ b/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.html @@ -4,8 +4,8 @@ - - + + Cesium Demo diff --git a/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.jpg b/Apps/Sandcastle/gallery/Time Dynamic Point Cloud.jpg index d4f84a1d5932f61f1beb14ebed4148ff3a4db953..6ec6222069db67ac0e66a8eede8c0ffcc10d6991 100644 GIT binary patch literal 46658 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 q?|p5~e&hhCQNVWfNO#q^8G;WC> * If there are no event listeners, error messages will be logged to the console. *

+ *

+ * The error object passed to the listener contains two properties: + *

    + *
  • url: the url of the failed tile.
  • + *
  • message: the error message.
  • + *
* * @type {Event} * @default new Event() @@ -1839,14 +1845,6 @@ define([ /////////////////////////////////////////////////////////////////////////// - /** - * Called when {@link Viewer} or {@link CesiumWidget} render the scene to - * get the draw commands needed to render this primitive. - *

- * Do not call this function directly. This is documented just to - * list the exceptions that may be propagated when the scene is rendered: - *

- */ Cesium3DTileset.prototype.update = function(frameState) { if (frameState.mode === SceneMode.MORPHING) { return; diff --git a/Source/Scene/PointCloud.js b/Source/Scene/PointCloud.js index cb206cb98aa0..fd66c5834a48 100644 --- a/Source/Scene/PointCloud.js +++ b/Source/Scene/PointCloud.js @@ -475,16 +475,31 @@ define([ 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(20); + 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, 20); + 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(i * pointsLength / samplesLength); + 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); diff --git a/Source/Scene/TimeDynamicImagery.js b/Source/Scene/TimeDynamicImagery.js index f760212ee949..595a6541612a 100644 --- a/Source/Scene/TimeDynamicImagery.js +++ b/Source/Scene/TimeDynamicImagery.js @@ -25,8 +25,8 @@ define([ * @constructor * * @param {Object} options Object with the following properties: - * @param {Clock} options.clock A Clock instance that is used when determining the value for the time dimension. Required when options.times is specified. - * @param {TimeIntervalCollection} options.times TimeIntervalCollection with its data property being an object containing time dynamic dimension and their values. + * @param {Clock} options.clock A Clock instance that is used when determining the value for the time dimension. Required when options.times is specified. + * @param {TimeIntervalCollection} options.times TimeIntervalCollection with its data property being an object containing time dynamic dimension and their values. * @param {Function} options.requestImageFunction A function that will request imagery tiles. * @param {Function} options.reloadFunction A function that will be called when all imagery tiles need to be reloaded. */ diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index 7e2bc7d7df10..80e1f2d93818 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -12,6 +12,7 @@ define([ '../Core/Math', '../Core/Matrix4', '../Core/Resource', + '../ThirdParty/when', './ClippingPlaneCollection', './PointCloud', './PointCloudEyeDomeLighting', @@ -32,6 +33,7 @@ define([ CesiumMath, Matrix4, Resource, + when, ClippingPlaneCollection, PointCloud, PointCloudEyeDomeLighting, @@ -155,18 +157,39 @@ define([ *

* If there are no event listeners, error messages will be logged to the console. *

+ *

+ * The error object passed to the listener contains two properties: + *

    + *
  • uri: the uri of the failed frame.
  • + *
  • message: the error message.
  • + *
* * @type {Event} * @default new Event() * * @example * pointCloud.frameFailed.addEventListener(function(error) { - * console.log('An error occurred loading frame: ' + error.url); + * console.log('An error occurred loading frame: ' + error.uri); * console.log('Error: ' + error.message); * }); */ this.frameFailed = new Event(); + /** + * The event fired to indicate that a new frame was rendered. + *

+ * The time dynamic point cloud {@link TimeDynamicPointCloud} is passed to the event listener. + *

+ * @type {Event} + * @default new Event() + * + * @example + * pointCloud.frameChanged.addEventListener(function(timeDynamicPointCloud) { + * viewer.camera.viewBoundingSphere(timeDynamicPointCloud.boundingSphere); + * }); + */ + this.frameChanged = new Event(); + this._clock = options.clock; this._intervals = options.intervals; this._clippingPlanes = undefined; @@ -182,6 +205,7 @@ define([ this._nextInterval = undefined; this._lastRenderedFrame = undefined; this._clockMultiplier = 0.0; + this._readyPromise = when.defer(); // For calculating average load time of the last N frames this._runningSum = 0.0; @@ -238,6 +262,20 @@ define([ return this._lastRenderedFrame.pointCloud.boundingSphere; } } + }, + + /** + * Gets the promise that will be resolved when the point cloud renders a frame for the first time. + * + * @memberof TimeDynamicPointCloud.prototype + * + * @type {Promise.} + * @readonly + */ + readyPromise : { + get : function() { + return this._readyPromise.promise; + } } }); @@ -345,7 +383,7 @@ define([ var message = defined(error.message) ? error.message : error.toString(); if (that.frameFailed.numberOfListeners > 0) { that.frameFailed.raiseEvent({ - url : uri, + uri : uri, message : message }); } else { @@ -576,14 +614,6 @@ define([ clippingPlanesDirty : false }; - /** - * Called when {@link Viewer} or {@link CesiumWidget} render the scene to - * get the draw commands needed to render this primitive. - *

- * Do not call this function directly. This is documented just to - * list the exceptions that may be propagated when the scene is rendered: - *

- */ TimeDynamicPointCloud.prototype.update = function(frameState) { if (frameState.mode === SceneMode.MORPHING) { return; @@ -681,6 +711,21 @@ define([ loadFrame(this, nextInterval, updateState, frameState); } + var that = this; + if (defined(frame) && !defined(this._lastRenderedFrame)) { + frameState.afterRender.push(function() { + that._readyPromise.resolve(that); + }); + } + + if (defined(frame) && (frame !== this._lastRenderedFrame)) { + if (that.frameChanged.numberOfListeners > 0) { + frameState.afterRender.push(function() { + that.frameChanged.raiseEvent(that); + }); + } + } + this._previousInterval = previousInterval; this._nextInterval = nextInterval; this._lastRenderedFrame = frame; diff --git a/Source/Scene/WebMapTileServiceImageryProvider.js b/Source/Scene/WebMapTileServiceImageryProvider.js index 5d86d834705a..f1cd035cd741 100644 --- a/Source/Scene/WebMapTileServiceImageryProvider.js +++ b/Source/Scene/WebMapTileServiceImageryProvider.js @@ -53,7 +53,7 @@ define([ * @param {String} options.tileMatrixSetID The identifier of the TileMatrixSet to use for WMTS requests. * @param {Array} [options.tileMatrixLabels] A list of identifiers in the TileMatrix to use for WMTS requests, one per TileMatrix level. * @param {Clock} [options.clock] A Clock instance that is used when determining the value for the time dimension. Required when options.times is specified. - * @param {TimeIntervalCollection} [options.times] TimeIntervalCollection with its data property being an object containing time dynamic dimension and their values. + * @param {TimeIntervalCollection} [options.times] TimeIntervalCollection with its data property being an object containing time dynamic dimension and their values. * @param {Object} [options.dimensions] A object containing static dimensions and their values. * @param {Number} [options.tileWidth=256] The tile width in pixels. * @param {Number} [options.tileHeight=256] The tile height in pixels. diff --git a/Source/Widgets/Viewer/Viewer.js b/Source/Widgets/Viewer/Viewer.js index a33216b3cc97..d62b5e560f51 100644 --- a/Source/Widgets/Viewer/Viewer.js +++ b/Source/Widgets/Viewer/Viewer.js @@ -1938,8 +1938,8 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to // If zoomTarget was TimeDynamicPointCloud if (target instanceof TimeDynamicPointCloud) { - boundingSphere = target.boundingSphere; - if (defined(boundingSphere)) { + return target.readyPromise.then(function() { + var boundingSphere = target.boundingSphere; // If offset was originally undefined then give it base value instead of empty object if (!defined(zoomOptions.offset)) { zoomOptions.offset = new HeadingPitchRange(0.0, -0.5, boundingSphere.radius); @@ -1968,8 +1968,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to } clearZoom(viewer); - } - return; + }); } // If zoomTarget was an ImageryLayer diff --git a/Specs/Scene/TimeDynamicPointCloudSpec.js b/Specs/Scene/TimeDynamicPointCloudSpec.js index 479198de4056..03e6fcf4c496 100644 --- a/Specs/Scene/TimeDynamicPointCloudSpec.js +++ b/Specs/Scene/TimeDynamicPointCloudSpec.js @@ -258,6 +258,15 @@ defineSuite([ }); }); + it('resolves ready promise', function() { + var pointCloud = createTimeDynamicPointCloud(); + return loadFrame(pointCloud).then(function() { + return pointCloud.readyPromise.then(function(pointCloud) { + expect(pointCloud.boundingSphere).toBeDefined(); + }); + }); + }); + it('sets show', function() { var pointCloud = createTimeDynamicPointCloud(); @@ -705,7 +714,7 @@ defineSuite([ for (i = 0; i < 5; ++i) { var arg = spyUpdate.calls.argsFor(i)[0]; expect(arg).toBeDefined(); - expect(arg.url).toContain(i + '.pnts'); + expect(arg.uri).toContain(i + '.pnts'); expect(arg.message).toBe('404'); } }); @@ -735,12 +744,38 @@ defineSuite([ }).then(function() { var arg = spyUpdate.calls.argsFor(0)[0]; expect(arg).toBeDefined(); - expect(arg.url).toContain('1.pnts'); + expect(arg.uri).toContain('1.pnts'); expect(arg.message).toBe('my error'); }); }); }); + it('raises frame changed event', function() { + var pointCloud = createTimeDynamicPointCloud(); + var spyFrameChanged = jasmine.createSpy('listener'); + pointCloud.frameChanged.addEventListener(spyFrameChanged); + + return loadAllFrames(pointCloud).then(function() { + expect(spyFrameChanged.calls.count()).toBe(5); + + // Go to random frame + goToFrame(2); + scene.renderForSpecs(); + expect(spyFrameChanged.calls.count()).toBe(6); + + // Go out of range. No event raised. + clock.currentTime = JulianDate.addSeconds(dates[0], -10.0, new JulianDate()); + scene.renderForSpecs(); + expect(spyFrameChanged.calls.count()).toBe(6); + + goToFrame(0); + scene.renderForSpecs(); + expect(spyFrameChanged.calls.count()).toBe(7); + + expect(spyFrameChanged.calls.argsFor(0)[0]).toBe(pointCloud); + }); + }); + it('destroys', function() { var pointCloud = createTimeDynamicPointCloud(); return loadAllFrames(pointCloud).then(function() { From a7baeff7292860b4f54990b71f65be2b7547ae61 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 10 Jul 2018 12:02:04 -0400 Subject: [PATCH 34/35] Updates --- Source/Scene/Cesium3DTileset.js | 3 +++ Source/Scene/PointCloud.js | 2 +- Source/Scene/TimeDynamicPointCloud.js | 3 +++ Specs/Widgets/Viewer/ViewerSpec.js | 5 ++--- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 92031f0995cc..7a2e2c6ad3d1 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -1845,6 +1845,9 @@ define([ /////////////////////////////////////////////////////////////////////////// + /** + * @private + */ Cesium3DTileset.prototype.update = function(frameState) { if (frameState.mode === SceneMode.MORPHING) { return; diff --git a/Source/Scene/PointCloud.js b/Source/Scene/PointCloud.js index fd66c5834a48..b581622b3f33 100644 --- a/Source/Scene/PointCloud.js +++ b/Source/Scene/PointCloud.js @@ -481,7 +481,7 @@ define([ // Use same random values across all runs if (!defined(randomValues)) { CesiumMath.setRandomNumberSeed(0); - randomValues = new Array(20); + randomValues = new Array(samplesLength); for (var i = 0; i < samplesLength; ++i) { randomValues[i] = CesiumMath.nextRandomNumber(); } diff --git a/Source/Scene/TimeDynamicPointCloud.js b/Source/Scene/TimeDynamicPointCloud.js index 80e1f2d93818..684223ac3046 100644 --- a/Source/Scene/TimeDynamicPointCloud.js +++ b/Source/Scene/TimeDynamicPointCloud.js @@ -614,6 +614,9 @@ define([ clippingPlanesDirty : false }; + /** + * @private + */ TimeDynamicPointCloud.prototype.update = function(frameState) { if (frameState.mode === SceneMode.MORPHING) { return; diff --git a/Specs/Widgets/Viewer/ViewerSpec.js b/Specs/Widgets/Viewer/ViewerSpec.js index ad3c3543efc1..b1e1727eb125 100644 --- a/Specs/Widgets/Viewer/ViewerSpec.js +++ b/Specs/Widgets/Viewer/ViewerSpec.js @@ -1148,10 +1148,9 @@ defineSuite([ return pollToPromise(function() { pointCloud.update(scene.frameState); - var frame = pointCloud._frames[0]; - return defined(frame) && frame.ready; + return defined(pointCloud.boundingSphere); }).then(function() { - return pointCloud; + return pointCloud.readyPromise; }); } From b2b94f5129bac72a544d735a8853085ca372f250 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 10 Jul 2018 20:10:58 -0400 Subject: [PATCH 35/35] Fix test --- Specs/Widgets/Viewer/ViewerSpec.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Specs/Widgets/Viewer/ViewerSpec.js b/Specs/Widgets/Viewer/ViewerSpec.js index b1e1727eb125..c6d8e8462491 100644 --- a/Specs/Widgets/Viewer/ViewerSpec.js +++ b/Specs/Widgets/Viewer/ViewerSpec.js @@ -1146,8 +1146,10 @@ defineSuite([ clock.currentTime = start; clock.multiplier = 0.0; + scene.primitives.add(pointCloud); + return pollToPromise(function() { - pointCloud.update(scene.frameState); + scene.render(); return defined(pointCloud.boundingSphere); }).then(function() { return pointCloud.readyPromise; @@ -1172,6 +1174,7 @@ defineSuite([ return promise.then(function() { expect(wasCompleted).toEqual(true); + viewer.scene.primitives.remove(pointCloud); }); }); }); @@ -1194,6 +1197,7 @@ defineSuite([ return promise.then(function() { expect(wasCompleted).toEqual(true); + viewer.scene.primitives.remove(pointCloud); }); }); }); @@ -1384,6 +1388,7 @@ defineSuite([ return promise.then(function() { expect(wasCompleted).toEqual(true); + viewer.scene.primitives.remove(pointCloud); }); }); }); @@ -1407,6 +1412,7 @@ defineSuite([ return promise.then(function() { expect(wasCompleted).toEqual(true); + viewer.scene.primitives.remove(pointCloud); }); }); }); @@ -1434,6 +1440,7 @@ defineSuite([ return promise.then(function() { expect(wasCompleted).toEqual(true); + viewer.scene.primitives.remove(pointCloud); }); }); });