From 7528a840c4905313f27d773e5a1fe477e72c4c97 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 2 May 2018 17:58:18 -0400 Subject: [PATCH 1/5] Draco support for pnts --- .../bin/3d-tiles-samples-generator.js | 40 ++ samples-generator/lib/createPointCloudTile.js | 376 ++++++++++++++---- .../lib/createTilesetJsonSingle.js | 4 + samples-generator/package.json | 1 + 4 files changed, 342 insertions(+), 79 deletions(-) diff --git a/samples-generator/bin/3d-tiles-samples-generator.js b/samples-generator/bin/3d-tiles-samples-generator.js index 37b8c26b..de430dc4 100644 --- a/samples-generator/bin/3d-tiles-samples-generator.js +++ b/samples-generator/bin/3d-tiles-samples-generator.js @@ -236,6 +236,9 @@ var promises = [ createPointCloudBatched(), createPointCloudWithPerPointProperties(), createPointCloudWithTransform(), + createPointCloudDraco(), + createPointCloudDracoPartial(), + createPointCloudDracoBatched(), // Instanced createInstancedWithBatchTable(), createInstancedWithoutBatchTable(), @@ -614,6 +617,40 @@ function createPointCloudWithTransform() { return savePointCloudTileset('PointCloudWithTransform', tileOptions, tilesetOptions); } +function createPointCloudDraco() { + var tileOptions = { + colorMode : 'rgb', + shape : 'sphere', + generateNormals : true, + perPointProperties : true, + draco : true + }; + return savePointCloudTileset('PointCloudDraco', tileOptions); +} + +function createPointCloudDracoPartial() { + var tileOptions = { + colorMode : 'rgb', + shape : 'sphere', + generateNormals : true, + perPointProperties : true, + draco : true, + dracoSemantics : ['POSITION'] + }; + return savePointCloudTileset('PointCloudDracoPartial', tileOptions); +} + +function createPointCloudDracoBatched() { + var tileOptions = { + colorMode : 'rgb', + shape : 'sphere', + generateNormals : true, + batched : true, + draco : true + }; + return savePointCloudTileset('PointCloudDracoBatched', tileOptions); +} + function createInstancedWithBatchTable() { var tileOptions = { createBatchTable : true @@ -956,11 +993,14 @@ function savePointCloudTileset(tilesetName, tileOptions, tilesetOptions) { var result = createPointCloudTile(tileOptions); var pnts = result.pnts; var batchTableJson = result.batchTableJson; + var extensionsUsed = result.extensionsUsed; tilesetOptions = defaultValue(tilesetOptions, {}); tilesetOptions.tileName = tileName; tilesetOptions.properties = getProperties(batchTableJson); tilesetOptions.geometricError = pointCloudGeometricError; + tilesetOptions.extensionsUsed = extensionsUsed; + tilesetOptions.extensionsRequired = extensionsUsed; if (!defined(tilesetOptions.region) && !defined(tilesetOptions.sphere) && !defined(tilesetOptions.box)) { tilesetOptions.sphere = pointCloudSphere; } diff --git a/samples-generator/lib/createPointCloudTile.js b/samples-generator/lib/createPointCloudTile.js index 519f1eed..d9cab199 100644 --- a/samples-generator/lib/createPointCloudTile.js +++ b/samples-generator/lib/createPointCloudTile.js @@ -1,5 +1,6 @@ 'use strict'; var Cesium = require('cesium'); +var draco3d = require('draco3d'); var SimplexNoise = require('simplex-noise'); var createPnts = require('./createPnts'); @@ -8,9 +9,11 @@ var Cartesian2 = Cesium.Cartesian2; var Cartesian3 = Cesium.Cartesian3; var CesiumMath = Cesium.Math; var Color = Cesium.Color; +var ComponentDatatype = Cesium.ComponentDatatype; var defaultValue = Cesium.defaultValue; var defined = Cesium.defined; var Matrix4 = Cesium.Matrix4; +var WebGLConstants = Cesium.WebGLConstants; module.exports = createPointCloudTile; @@ -22,6 +25,8 @@ var sizeOfFloat32 = 4; CesiumMath.setRandomNumberSeed(0); var simplex = new SimplexNoise(CesiumMath.nextRandomNumber); +var encoderModule = draco3d.createEncoderModule({}); + /** * Creates a pnts tile that represents a point cloud. * @@ -33,6 +38,8 @@ var simplex = new SimplexNoise(CesiumMath.nextRandomNumber); * @param {String} [options.color='random'] Determines the method for generating point colors. Possible values are 'random', 'gradient', 'noise'. * @param {String} [options.shape='box'] The shape of the point cloud. Possible values are 'sphere', 'box'. * @param {Boolean} [options.generateNormals=false] Generate per-point normals. + * @param {Boolean} [options.draco=false] Use draco encoding. + * @param [String[]] [options.dracoSemantics] An array of semantics to draco encode. If undefined, all semantics are encoded. * @param {Boolean} [options.octEncodeNormals=false] Apply oct16p encoding on the point normals. * @param {Boolean} [options.quantizePositions=false] Quantize point positions so each x, y, z takes up 16 bits rather than 32 bits. * @param {Boolean} [options.batched=false] Group points together with batch ids and generate per-batch metadata. Good for differentiating different sections of a point cloud. Not compatible with perPointProperties. @@ -54,13 +61,19 @@ function createPointCloudTile(options) { var color = defaultValue(options.color, 'random'); var shape = defaultValue(options.shape, 'box'); var generateNormals = defaultValue(options.generateNormals, false); - var octEncodeNormals = defaultValue(options.octEncodeNormals, false); - var quantizePositions = defaultValue(options.quantizePositions, false); + var draco = defaultValue(options.draco, false); + var dracoSemantics = options.dracoSemantics; + var octEncodeNormals = defaultValue(options.octEncodeNormals, false) && !draco; + var quantizePositions = defaultValue(options.quantizePositions, false) && !draco; var batched = defaultValue(options.batched, false); var perPointProperties = defaultValue(options.perPointProperties, false); var relativeToCenter = defaultValue(options.relativeToCenter, true); var time = defaultValue(options.time, 0.0); + if (colorMode === 'rgb565' && draco) { + colorMode = 'rgb'; + } + var radius = tileWidth / 2.0; var center = Matrix4.getTranslation(transform, new Cartesian3()); @@ -99,31 +112,95 @@ function createPointCloudTile(options) { var colors = points.colors; var noiseValues = points.noiseValues; - var attributes = [positions]; + var featureTableProperties = [positions]; if (defined(colors)) { - attributes.push(colors); + featureTableProperties.push(colors); } if (generateNormals) { - attributes.push(normals); + featureTableProperties.push(normals); } if (batched) { - attributes.push(batchIds); + featureTableProperties.push(batchIds); } - var i; - var attribute; - var byteOffset = 0; - var attributesLength = attributes.length; - for (i = 0; i < attributesLength; ++i) { - attribute = attributes[i]; - var byteAlignment = attribute.byteAlignment; - byteOffset = Math.ceil(byteOffset / byteAlignment) * byteAlignment; // Round up to the required alignment - attribute.byteOffset = byteOffset; - byteOffset += attribute.buffer.length; + var batchTableProperties = []; + if (perPointProperties) { + batchTableProperties = getPerPointBatchTableProperties(pointsLength, noiseValues); } var featureTableJson = {}; - var featureTableBinary = Buffer.alloc(byteOffset); + var featureTableBinary = Buffer.alloc(0); + + var batchTableJson = {}; + var batchTableBinary = Buffer.alloc(0); + + var extensionsUsed; + + var dracoBuffer; + var dracoFeatureTableJson; + var dracoBatchTableJson; + + if (draco) { + var dracoResults = dracoEncode(pointsLength, dracoSemantics, featureTableProperties, batchTableProperties); + dracoBuffer = dracoResults.buffer; + dracoFeatureTableJson = dracoResults.dracoFeatureTableJson; + dracoBatchTableJson = dracoResults.dracoBatchTableJson; + featureTableBinary = Buffer.concat([featureTableBinary, dracoBuffer]); + if (defined(dracoFeatureTableJson)) { + featureTableJson.extensions = { + '3DTILES_draco_point_compression' : dracoFeatureTableJson + }; + } + if (defined(dracoBatchTableJson)) { + batchTableJson.extensions = { + '3DTILES_draco_point_compression' : dracoBatchTableJson + }; + } + extensionsUsed = ['3DTILES_draco_point_compression']; + } + + var i; + var property; + var name; + var componentType; + var byteOffset; + var byteAlignment; + var padding; + + for (i = 0; i < featureTableProperties.length; ++i) { + property = featureTableProperties[i]; + name = property.propertyName; + componentType = property.componentType; + byteOffset = 0; + if (!(defined(dracoFeatureTableJson) && defined(dracoFeatureTableJson.properties[name]))) { + byteAlignment = ComponentDatatype.getSizeInBytes(ComponentDatatype[componentType]); + byteOffset = Math.ceil(featureTableBinary.length / byteAlignment) * byteAlignment; // Round up to the required alignment + padding = Buffer.alloc(byteOffset - featureTableBinary.length); + featureTableBinary = Buffer.concat([featureTableBinary, padding, property.buffer]); + } + featureTableJson[name] = { + byteOffset : byteOffset, + componentType : name === 'BATCH_ID' ? componentType : undefined + }; + } + + for (i = 0; i < batchTableProperties.length; ++i) { + property = batchTableProperties[i]; + name = property.propertyName; + componentType = property.componentType; + byteOffset = 0; + if (!(defined(dracoBatchTableJson) && defined(dracoBatchTableJson.properties[name]))) { + byteAlignment = ComponentDatatype.getSizeInBytes(ComponentDatatype[componentType]); + byteOffset = Math.ceil(batchTableBinary.length / byteAlignment) * byteAlignment; // Round up to the required alignment + padding = Buffer.alloc(byteOffset - batchTableBinary.length); + batchTableBinary = Buffer.concat([batchTableBinary, padding, property.buffer]); + } + batchTableJson[name] = { + byteOffset : byteOffset, + componentType : componentType, + type : property.type + }; + } featureTableJson.POINTS_LENGTH = pointsLength; @@ -140,30 +217,10 @@ function createPointCloudTile(options) { } if (batched) { - featureTableJson.BATCH_LENGTH = batchIds.batchLength; - } - - for (i = 0; i < attributesLength; ++i) { - attribute = attributes[i]; - featureTableJson[attribute.propertyName] = { - byteOffset : attribute.byteOffset, - componentType : attribute.componentType // Only defined for batchIds - }; - attribute.buffer.copy(featureTableBinary, attribute.byteOffset); - } - - var batchTable; - var batchTableJson; - var batchTableBinary; - - if (batched) { - batchTable = getBatchTableForBatchedPoints(batchIds.batchLength); - batchTableJson = batchTable.json; - batchTableBinary = batchTable.binary; - } else if (perPointProperties) { - batchTable = getBatchTableForPerPointProperties(pointsLength, noiseValues); + var batchTable = getBatchTableForBatchedPoints(batchIds.batchLength); batchTableJson = batchTable.json; batchTableBinary = batchTable.binary; + featureTableJson.BATCH_LENGTH = batchIds.batchLength; } var pnts = createPnts({ @@ -175,7 +232,170 @@ function createPointCloudTile(options) { return { pnts : pnts, - batchTableJson : batchTableJson + batchTableJson : batchTableJson, + extensionsUsed : extensionsUsed + }; +} + +function getAddAttributeFunctionName(componentDatatype) { + switch (componentDatatype) { + case WebGLConstants.UNSIGNED_BYTE: + return 'AddUInt8Attribute'; + case WebGLConstants.BYTE: + return 'AddInt8Attribute'; + case WebGLConstants.UNSIGNED_SHORT: + return 'AddUInt16Attribute'; + case WebGLConstants.SHORT: + return 'AddInt16Attribute'; + case WebGLConstants.UNSIGNED_INT: + return 'AddUInt32Attribute'; + case WebGLConstants.INT: + return 'AddInt32Attribute'; + case WebGLConstants.FLOAT: + return 'AddFloatAttribute'; + } +} + +function numberOfComponentsForType(type) { + switch (type) { + case 'SCALAR': + return 1; + case 'VEC2': + return 2; + case 'VEC3': + return 3; + case 'VEC4': + return 4; + } +} + +function getDracoType(name) { + switch (name) { + case 'POSITION': + return encoderModule.POSITION; + case 'NORMAL': + return encoderModule.NORMAL; + case 'RGB': + case 'RGBA': + return encoderModule.COLOR; + default: + return encoderModule.GENERIC; + } +} + +function dracoEncodeProperties(pointsLength, properties, preserveOrder) { + var i; + var encoder = new encoderModule.Encoder(); + var pointCloudBuilder = new encoderModule.PointCloudBuilder(); + var pointCloud = new encoderModule.PointCloud(); + + var attributeIds = {}; + + var length = properties.length; + for (i = 0; i < length; ++i) { + var property = properties[i]; + var componentDatatype = ComponentDatatype[property.componentType]; + var typedArray = ComponentDatatype.createArrayBufferView(componentDatatype, property.buffer.buffer); + var numberOfComponents = numberOfComponentsForType(property.type); + var addAttributeFunctionName = getAddAttributeFunctionName(componentDatatype); + var name = property.propertyName; + var dracoType = getDracoType(name); + attributeIds[name] = pointCloudBuilder[addAttributeFunctionName](pointCloud, dracoType, pointsLength, numberOfComponents, typedArray); + } + + var dracoCompressionSpeed = 7; + var dracoPositionBits = 14; + var dracoNormalBits = 8; + var dracoColorBits = 8; + var dracoGenericBits = 12; + + encoder.SetSpeedOptions(dracoCompressionSpeed); + encoder.SetAttributeQuantization(encoderModule.POSITION, dracoPositionBits); + encoder.SetAttributeQuantization(encoderModule.NORMAL, dracoNormalBits); + encoder.SetAttributeQuantization(encoderModule.COLOR, dracoColorBits); + encoder.SetAttributeQuantization(encoderModule.GENERIC, dracoGenericBits); + + if (preserveOrder) { + encoder.SetEncodingMethod(encoderModule.POINT_CLOUD_SEQUENTIAL_ENCODING); + } + + var encodedDracoDataArray = new encoderModule.DracoInt8Array(); + + var encodedLength = encoder.EncodePointCloudToDracoBuffer(pointCloud, false, encodedDracoDataArray); + if (encodedLength <= 0) { + throw 'Error: Encoding Failed.'; + } + + var encodedData = Buffer.alloc(encodedLength); + for (i = 0; i < encodedLength; i++) { + encodedData[i] = encodedDracoDataArray.GetValue(i); + } + + return { + buffer : encodedData, + attributeIds : attributeIds + }; +} + +function getPropertyByName(properties, name) { + return properties.find(function(element) { + return element.propertyName === name; + }); +} + +function dracoEncode(pointsLength, dracoSemantics, featureTableProperties, batchTableProperties) { + var dracoProperties = []; + if (!defined(dracoSemantics)) { + dracoProperties = dracoProperties.concat(featureTableProperties); + } else { + for (var i = 0; i < dracoSemantics.length; ++i) { + dracoProperties.push(getPropertyByName(featureTableProperties, dracoSemantics[i])); + } + } + dracoProperties = dracoProperties.concat(batchTableProperties); + + // Check if normals are being encoded. + // Currently the octahedron transform for normals only works if preserveOrder is true. + // See https://github.com/google/draco/issues/383 + var encodeNormals = defined(getPropertyByName(dracoProperties, 'NORMAL')); + var hasUncompressedAttributes = dracoProperties.length < (featureTableProperties.length + batchTableProperties.length); + var preserveOrder = encodeNormals || hasUncompressedAttributes; + + var dracoResults = dracoEncodeProperties(pointsLength, dracoProperties, preserveOrder); + var dracoBuffer = dracoResults.buffer; + var dracoAttributeIds = dracoResults.attributeIds; + + var dracoFeatureTableJson = { + properties : {}, + byteOffset : 0, + byteLength : dracoBuffer.length + }; + var dracoBatchTableJson = { + properties : {} + }; + + for (var name in dracoAttributeIds) { + if (dracoAttributeIds.hasOwnProperty(name)) { + if (defined(getPropertyByName(featureTableProperties, name))) { + dracoFeatureTableJson.properties[name] = dracoAttributeIds[name]; + } + if (defined(getPropertyByName(batchTableProperties, name))) { + dracoBatchTableJson.properties[name] = dracoAttributeIds[name]; + } + } + } + + if (Object.keys(dracoFeatureTableJson).length === 0) { + dracoFeatureTableJson = undefined; + } + if (Object.keys(dracoBatchTableJson).length === 0) { + dracoBatchTableJson = undefined; + } + + return { + buffer : dracoBuffer, + dracoFeatureTableJson : dracoFeatureTableJson, + dracoBatchTableJson : dracoBatchTableJson }; } @@ -264,7 +484,7 @@ function getPoints(pointsLength, radius, colorModeFunction, colorFunction, shape } else { normal = Cartesian3.normalize(position, new Cartesian3()); } - var batchId = getBatchId(unitPosition); + var batchId = getBatchId(position); var color = colorFunction(unitPosition); var noise = getNoise(unitPosition, time); @@ -309,7 +529,8 @@ function getPositions(positions) { return { buffer : buffer, propertyName : 'POSITION', - byteAlignment : sizeOfFloat32 + componentType : 'FLOAT', + type : 'VEC3' }; } @@ -332,7 +553,8 @@ function getPositionsQuantized(positions, radius) { return { buffer : buffer, propertyName : 'POSITION_QUANTIZED', - byteAlignment : sizeOfUint16 + componentType : 'UNSIGNED_SHORT', + type : 'VEC3' }; } @@ -348,7 +570,8 @@ function getNormals(normals) { return { buffer : buffer, propertyName : 'NORMAL', - byteAlignment : sizeOfFloat32 + componentType : 'FLOAT', + type : 'VEC3' }; } @@ -365,7 +588,8 @@ function getNormalsOctEncoded(normals) { return { buffer : buffer, propertyName : 'NORMAL_OCT16P', - byteAlignment : sizeOfUint8 + componentType : 'UNSIGNED_BYTE', + type : 'VEC2' }; } @@ -379,7 +603,6 @@ function getBatchIds(batchIds) { } var buffer; - var byteAlignment; var componentType; if (batchLength <= 256) { buffer = Buffer.alloc(pointsLength * sizeOfUint8); @@ -387,28 +610,25 @@ function getBatchIds(batchIds) { buffer.writeUInt8(batchIds[i], i * sizeOfUint8); } componentType = 'UNSIGNED_BYTE'; - byteAlignment = sizeOfUint8; } else if (batchLength <= 65536) { buffer = Buffer.alloc(pointsLength * sizeOfUint16); for (i = 0; i < pointsLength; ++i) { buffer.writeUInt16LE(batchIds[i], i * sizeOfUint16); } componentType = 'UNSIGNED_SHORT'; - byteAlignment = sizeOfUint16; } else { buffer = Buffer.alloc(pointsLength * sizeOfUint32); for (i = 0; i < pointsLength; ++i) { buffer.writeUInt32LE(batchIds[i], i * sizeOfUint32); } componentType = 'UNSIGNED_INT'; - byteAlignment = sizeOfUint32; } return { buffer : buffer, propertyName : 'BATCH_ID', - byteAlignment : byteAlignment, componentType : componentType, + type : 'SCALAR', batchLength : batchLength }; } @@ -428,7 +648,8 @@ function getColorsRGB(colors) { return { buffer : buffer, propertyName : 'RGB', - byteAlignment : sizeOfUint8 + componentType : 'UNSIGNED_BYTE', + type : 'VEC3' }; } @@ -449,7 +670,8 @@ function getColorsRGBA(colors) { return { buffer : buffer, propertyName : 'RGBA', - byteAlignment : sizeOfUint8 + componentType : 'UNSIGNED_BYTE', + type : 'VEC4' }; } @@ -467,7 +689,8 @@ function getColorsRGB565(colors) { return { buffer : buffer, propertyName : 'RGB565', - byteAlignment : sizeOfUint16 + componentType : 'UNSIGNED_SHORT', + type : 'SCALAR' }; } @@ -508,30 +731,12 @@ function getBatchTableForBatchedPoints(batchLength) { }; } -function getBatchTableForPerPointProperties(pointsLength, noiseValues) { +function getPerPointBatchTableProperties(pointsLength, noiseValues) { // Create some sample per-point properties. Each point will have a temperature, secondary color, and id. var temperaturesBuffer = Buffer.alloc(pointsLength * sizeOfFloat32); var secondaryColorBuffer = Buffer.alloc(pointsLength * 3 * sizeOfFloat32); var idBuffer = Buffer.alloc(pointsLength * sizeOfUint16); - var batchTableJson = { - temperature : { - byteOffset : 0, - componentType : 'FLOAT', - type : 'SCALAR' - }, - secondaryColor : { - byteOffset : temperaturesBuffer.length, - componentType : 'FLOAT', - type : 'VEC3' - }, - id : { - byteOffset : temperaturesBuffer.length + secondaryColorBuffer.length, - componentType : 'UNSIGNED_SHORT', - type : 'SCALAR' - } - }; - for (var i = 0; i < pointsLength; ++i) { var temperature = noiseValues[i]; var secondaryColor = [CesiumMath.nextRandomNumber(), 0.0, 0.0]; @@ -542,11 +747,24 @@ function getBatchTableForPerPointProperties(pointsLength, noiseValues) { idBuffer.writeUInt16LE(i, i * sizeOfUint16); } - // No need for padding with these sample properties - var batchTableBinary = Buffer.concat([temperaturesBuffer, secondaryColorBuffer, idBuffer]); - - return { - json : batchTableJson, - binary : batchTableBinary - }; + return [ + { + buffer : temperaturesBuffer, + propertyName : 'temperature', + componentType : 'FLOAT', + type: 'SCALAR' + }, + { + buffer : secondaryColorBuffer, + propertyName : 'secondaryColor', + componentType : 'FLOAT', + type : 'VEC3' + }, + { + buffer : idBuffer, + propertyName : 'id', + componentType : 'UNSIGNED_SHORT', + type : 'SCALAR' + } + ]; } diff --git a/samples-generator/lib/createTilesetJsonSingle.js b/samples-generator/lib/createTilesetJsonSingle.js index 70e62bd8..62e2d9ed 100644 --- a/samples-generator/lib/createTilesetJsonSingle.js +++ b/samples-generator/lib/createTilesetJsonSingle.js @@ -18,6 +18,8 @@ module.exports = createTilesetJsonSingle; * @param {Object} [options.sphere] Bounding sphere of the tile. * @param {Matrix4} [options.transform=Matrix4.IDENTITY] The tile transform. * @param {Object} [options.properties] An object containing the min and max values for each property in the batch table. + * @param {Array} [options.extensionsUsed] An array containing names of extensions used in the tileset. + * @param {Array} [options.extensionsRequired] An array containing names of extensions required by the tileset. * @param {String} [options.gltfUpAxis] Specifies the up-axis of embedded glTF models. * @param {Object} [options.expire] Tile expiration options. * @@ -34,6 +36,8 @@ function createTilesetJsonSingle(options) { gltfUpAxis : options.gltfUpAxis // If undefined, implicitly 'Y' }, properties : options.properties, + extensionsUsed : options.extensionsUsed, + extensionsRequired : options.extensionsRequired, geometricError : options.geometricError, root : { transform : transformArray, diff --git a/samples-generator/package.json b/samples-generator/package.json index 82871752..a320724b 100644 --- a/samples-generator/package.json +++ b/samples-generator/package.json @@ -25,6 +25,7 @@ "dependencies": { "bluebird": "^3.5.1", "cesium": "^1.39", + "draco3d": "^1.3.1", "fs-extra": "^4.0.2", "gltf-pipeline": "^1.0.2", "mime": "^2.0.3", From c20a71fb8b3bc062cc1f52c771b540852f52c5ae Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 19 Jun 2018 12:25:57 -0400 Subject: [PATCH 2/5] Added time dynamic point cloud samples --- .../bin/3d-tiles-samples-generator.js | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/samples-generator/bin/3d-tiles-samples-generator.js b/samples-generator/bin/3d-tiles-samples-generator.js index de430dc4..75f67b7b 100644 --- a/samples-generator/bin/3d-tiles-samples-generator.js +++ b/samples-generator/bin/3d-tiles-samples-generator.js @@ -23,6 +23,7 @@ var CesiumMath = Cesium.Math; var clone = Cesium.clone; var defaultValue = Cesium.defaultValue; var defined = Cesium.defined; +var JulianDate = Cesium.JulianDate; var Matrix4 = Cesium.Matrix4; var Quaternion = Cesium.Quaternion; @@ -239,6 +240,9 @@ var promises = [ createPointCloudDraco(), createPointCloudDracoPartial(), createPointCloudDracoBatched(), + createPointCloudTimeDynamic(), + createPointCloudTimeDynamicWithTransforms(), + createPointCloudTimeDynamicDraco(), // Instanced createInstancedWithBatchTable(), createInstancedWithoutBatchTable(), @@ -651,6 +655,24 @@ function createPointCloudDracoBatched() { return savePointCloudTileset('PointCloudDracoBatched', tileOptions); } +function createPointCloudTimeDynamic() { + return savePointCloudTimeDynamic('PointCloudTimeDynamic'); +} + +function createPointCloudTimeDynamicWithTransforms() { + var options = { + transform : true + }; + return savePointCloudTimeDynamic('PointCloudTimeDynamicWithTransform', options); +} + +function createPointCloudTimeDynamicDraco() { + var options = { + draco : true + }; + return savePointCloudTimeDynamic('PointCloudTimeDynamicDraco', options); +} + function createInstancedWithBatchTable() { var tileOptions = { createBatchTable : true @@ -1012,6 +1034,95 @@ function savePointCloudTileset(tilesetName, tileOptions, tilesetOptions) { ]); } +function savePointCloudTimeDynamic(name, options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var useTransform = defaultValue(options.transform, false); + var directory = path.join(outputDirectory, 'PointCloud', name); + var czmlPath = path.join(directory, 'frames.czml'); + + var transform = pointCloudTransform; + var relativeToCenter = true; + + if (useTransform) { + transform = Matrix4.IDENTITY; + relativeToCenter = false; + } + + var pointCloudOptions = { + tileWidth : pointCloudTileWidth, + pointsLength : pointsLength, + perPointProperties : true, + transform : transform, + relativeToCenter : relativeToCenter, + color : 'noise', + shape : 'box', + draco : options.draco + }; + + var czml = [{ + id : 'document', + version : '1.0', + clock : { + interval : undefined, + currentTime : undefined, + multiplier : 1, + range : 'LOOP_STOP', + step : 'SYSTEM_CLOCK_MULTIPLIER' + } + }, { + id : 'Time Dynamic Point Cloud', + properties : { + frames : [] + } + }]; + + var tilePromises = []; + + var framesLength = 5; + var frameDuration = 0.5; + var frameLongitudeSpacing = 0.000003; + var totalDuration = framesLength * frameDuration; + var startDate = JulianDate.fromDate(new Date(2018, 6, 19, 11, 18, 0)); + var endDate = JulianDate.addSeconds(startDate, totalDuration, new JulianDate()); + + var epoch = startDate.toString(); + var clock = czml[0].clock; + clock.interval = epoch + '/' + endDate.toString(); + clock.currentTime = epoch; + + var frames = czml[1].properties.frames; + + for (var i = 0; i < 5; ++i) { + var tileOptions = clone(pointCloudOptions); + tileOptions.time = i * 0.1; // Seed for noise + var pnts = createPointCloudTile(tileOptions).pnts; + var tilePathRelative = 'frames' + '/' + i + '.pnts'; + var tilePath = path.join(directory, 'frames', i + '.pnts'); + tilePromises.push(saveTile(tilePath, pnts, gzip)); + var frameStartDate = JulianDate.addSeconds(startDate, i * frameDuration, new JulianDate()); + var frameEndDate = JulianDate.addSeconds(frameStartDate, frameDuration, new JulianDate()); + + if (useTransform) { + transform = Matrix4.toArray(wgs84Transform(longitude + i * frameLongitudeSpacing, latitude, pointCloudRadius)); + } else { + transform = undefined; + } + + frames.push({ + interval : frameStartDate.toString() + '/' + frameEndDate.toString(), + object : { + uri : tilePathRelative, + transform : transform + } + }) + } + + return Promise.all([ + fsExtra.outputJson(czmlPath, czml), + Promise.all(tilePromises) + ]); +} + function createHierarchy() { return createBatchTableHierarchy({ directory : path.join(outputDirectory, 'Hierarchy', 'BatchTableHierarchy'), From edcdb32346ea398587c6863a63fec46a6633e367 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 5 Jul 2018 16:11:46 -0400 Subject: [PATCH 3/5] Remove czml writing --- .../bin/3d-tiles-samples-generator.js | 75 +++---------------- 1 file changed, 11 insertions(+), 64 deletions(-) diff --git a/samples-generator/bin/3d-tiles-samples-generator.js b/samples-generator/bin/3d-tiles-samples-generator.js index 75f67b7b..48b5dfcd 100644 --- a/samples-generator/bin/3d-tiles-samples-generator.js +++ b/samples-generator/bin/3d-tiles-samples-generator.js @@ -1038,7 +1038,6 @@ function savePointCloudTimeDynamic(name, options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var useTransform = defaultValue(options.transform, false); var directory = path.join(outputDirectory, 'PointCloud', name); - var czmlPath = path.join(directory, 'frames.czml'); var transform = pointCloudTransform; var relativeToCenter = true; @@ -1049,78 +1048,26 @@ function savePointCloudTimeDynamic(name, options) { } var pointCloudOptions = { - tileWidth : pointCloudTileWidth, - pointsLength : pointsLength, - perPointProperties : true, - transform : transform, - relativeToCenter : relativeToCenter, - color : 'noise', - shape : 'box', - draco : options.draco - }; - - var czml = [{ - id : 'document', - version : '1.0', - clock : { - interval : undefined, - currentTime : undefined, - multiplier : 1, - range : 'LOOP_STOP', - step : 'SYSTEM_CLOCK_MULTIPLIER' - } - }, { - id : 'Time Dynamic Point Cloud', - properties : { - frames : [] - } - }]; + tileWidth: pointCloudTileWidth, + pointsLength: pointsLength, + perPointProperties: true, + transform: transform, + relativeToCenter: relativeToCenter, + color: 'noise', + shape: 'box', + draco: options.draco + }; var tilePromises = []; - - var framesLength = 5; - var frameDuration = 0.5; - var frameLongitudeSpacing = 0.000003; - var totalDuration = framesLength * frameDuration; - var startDate = JulianDate.fromDate(new Date(2018, 6, 19, 11, 18, 0)); - var endDate = JulianDate.addSeconds(startDate, totalDuration, new JulianDate()); - - var epoch = startDate.toString(); - var clock = czml[0].clock; - clock.interval = epoch + '/' + endDate.toString(); - clock.currentTime = epoch; - - var frames = czml[1].properties.frames; - for (var i = 0; i < 5; ++i) { var tileOptions = clone(pointCloudOptions); tileOptions.time = i * 0.1; // Seed for noise var pnts = createPointCloudTile(tileOptions).pnts; - var tilePathRelative = 'frames' + '/' + i + '.pnts'; - var tilePath = path.join(directory, 'frames', i + '.pnts'); + var tilePath = path.join(directory, i + '.pnts'); tilePromises.push(saveTile(tilePath, pnts, gzip)); - var frameStartDate = JulianDate.addSeconds(startDate, i * frameDuration, new JulianDate()); - var frameEndDate = JulianDate.addSeconds(frameStartDate, frameDuration, new JulianDate()); - - if (useTransform) { - transform = Matrix4.toArray(wgs84Transform(longitude + i * frameLongitudeSpacing, latitude, pointCloudRadius)); - } else { - transform = undefined; - } - - frames.push({ - interval : frameStartDate.toString() + '/' + frameEndDate.toString(), - object : { - uri : tilePathRelative, - transform : transform - } - }) } - return Promise.all([ - fsExtra.outputJson(czmlPath, czml), - Promise.all(tilePromises) - ]); + return Promise.all(tilePromises); } function createHierarchy() { From f76b24f837583825e639dfe8c579979e9f841449 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 5 Jul 2018 16:26:00 -0400 Subject: [PATCH 4/5] Other fixes --- samples-generator/bin/3d-tiles-samples-generator.js | 1 - samples-generator/lib/createPointCloudTile.js | 7 ++++++- samples-generator/package.json | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/samples-generator/bin/3d-tiles-samples-generator.js b/samples-generator/bin/3d-tiles-samples-generator.js index 48b5dfcd..4fe02695 100644 --- a/samples-generator/bin/3d-tiles-samples-generator.js +++ b/samples-generator/bin/3d-tiles-samples-generator.js @@ -23,7 +23,6 @@ var CesiumMath = Cesium.Math; var clone = Cesium.clone; var defaultValue = Cesium.defaultValue; var defined = Cesium.defined; -var JulianDate = Cesium.JulianDate; var Matrix4 = Cesium.Matrix4; var Quaternion = Cesium.Quaternion; diff --git a/samples-generator/lib/createPointCloudTile.js b/samples-generator/lib/createPointCloudTile.js index d9cab199..5f285fcb 100644 --- a/samples-generator/lib/createPointCloudTile.js +++ b/samples-generator/lib/createPointCloudTile.js @@ -39,7 +39,7 @@ var encoderModule = draco3d.createEncoderModule({}); * @param {String} [options.shape='box'] The shape of the point cloud. Possible values are 'sphere', 'box'. * @param {Boolean} [options.generateNormals=false] Generate per-point normals. * @param {Boolean} [options.draco=false] Use draco encoding. - * @param [String[]] [options.dracoSemantics] An array of semantics to draco encode. If undefined, all semantics are encoded. + * @param {String[]} [options.dracoSemantics] An array of semantics to draco encode. If undefined, all semantics are encoded. * @param {Boolean} [options.octEncodeNormals=false] Apply oct16p encoding on the point normals. * @param {Boolean} [options.quantizePositions=false] Quantize point positions so each x, y, z takes up 16 bits rather than 32 bits. * @param {Boolean} [options.batched=false] Group points together with batch ids and generate per-batch metadata. Good for differentiating different sections of a point cloud. Not compatible with perPointProperties. @@ -331,6 +331,11 @@ function dracoEncodeProperties(pointsLength, properties, preserveOrder) { encodedData[i] = encodedDracoDataArray.GetValue(i); } + encoderModule.destroy(encoder); + encoderModule.destroy(pointCloudBuilder); + encoderModule.destroy(pointCloud); + encoderModule.destroy(encodedDracoDataArray); + return { buffer : encodedData, attributeIds : attributeIds diff --git a/samples-generator/package.json b/samples-generator/package.json index a320724b..e416aad7 100644 --- a/samples-generator/package.json +++ b/samples-generator/package.json @@ -25,7 +25,7 @@ "dependencies": { "bluebird": "^3.5.1", "cesium": "^1.39", - "draco3d": "^1.3.1", + "draco3d": "^1.3.3", "fs-extra": "^4.0.2", "gltf-pipeline": "^1.0.2", "mime": "^2.0.3", From 0a8975ed3d36d6392f41292abf2183a14375164d Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 16 Oct 2018 15:53:00 -0400 Subject: [PATCH 5/5] Use Extensions helper --- .../bin/3d-tiles-samples-generator.js | 5 ++--- samples-generator/lib/createPointCloudTile.js | 20 +++++++++---------- .../lib/createTilesetJsonSingle.js | 9 +++++---- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/samples-generator/bin/3d-tiles-samples-generator.js b/samples-generator/bin/3d-tiles-samples-generator.js index 2573b141..a32e5fdc 100644 --- a/samples-generator/bin/3d-tiles-samples-generator.js +++ b/samples-generator/bin/3d-tiles-samples-generator.js @@ -976,14 +976,13 @@ function savePointCloudTileset(tilesetName, tileOptions, tilesetOptions) { var result = createPointCloudTile(tileOptions); var pnts = result.pnts; var batchTableJson = result.batchTableJson; - var extensionsUsed = result.extensionsUsed; + var extensions = result.extensions; tilesetOptions = defaultValue(tilesetOptions, {}); tilesetOptions.contentUri = contentUri; tilesetOptions.properties = getProperties(batchTableJson); tilesetOptions.geometricError = pointCloudGeometricError; - tilesetOptions.extensionsUsed = extensionsUsed; - tilesetOptions.extensionsRequired = extensionsUsed; + tilesetOptions.extensions = extensions; if (!defined(tilesetOptions.region) && !defined(tilesetOptions.sphere) && !defined(tilesetOptions.box)) { tilesetOptions.sphere = pointCloudSphere; } diff --git a/samples-generator/lib/createPointCloudTile.js b/samples-generator/lib/createPointCloudTile.js index 5f285fcb..53afd893 100644 --- a/samples-generator/lib/createPointCloudTile.js +++ b/samples-generator/lib/createPointCloudTile.js @@ -3,6 +3,7 @@ var Cesium = require('cesium'); var draco3d = require('draco3d'); var SimplexNoise = require('simplex-noise'); var createPnts = require('./createPnts'); +var Extensions = require('./Extensions'); var AttributeCompression = Cesium.AttributeCompression; var Cartesian2 = Cesium.Cartesian2; @@ -134,7 +135,7 @@ function createPointCloudTile(options) { var batchTableJson = {}; var batchTableBinary = Buffer.alloc(0); - var extensionsUsed; + var extensions = {}; var dracoBuffer; var dracoFeatureTableJson; @@ -146,17 +147,16 @@ function createPointCloudTile(options) { dracoFeatureTableJson = dracoResults.dracoFeatureTableJson; dracoBatchTableJson = dracoResults.dracoBatchTableJson; featureTableBinary = Buffer.concat([featureTableBinary, dracoBuffer]); + if (defined(dracoFeatureTableJson)) { - featureTableJson.extensions = { - '3DTILES_draco_point_compression' : dracoFeatureTableJson - }; + Extensions.addExtension(featureTableJson, '3DTILES_draco_point_compression', dracoFeatureTableJson); } if (defined(dracoBatchTableJson)) { - batchTableJson.extensions = { - '3DTILES_draco_point_compression' : dracoBatchTableJson - }; + Extensions.addExtension(batchTableJson, '3DTILES_draco_point_compression', dracoBatchTableJson); } - extensionsUsed = ['3DTILES_draco_point_compression']; + + Extensions.addExtensionsRequired(extensions, '3DTILES_draco_point_compression'); + Extensions.addExtensionsUsed(extensions, '3DTILES_draco_point_compression'); } var i; @@ -233,7 +233,7 @@ function createPointCloudTile(options) { return { pnts : pnts, batchTableJson : batchTableJson, - extensionsUsed : extensionsUsed + extensions : extensions }; } @@ -323,7 +323,7 @@ function dracoEncodeProperties(pointsLength, properties, preserveOrder) { var encodedLength = encoder.EncodePointCloudToDracoBuffer(pointCloud, false, encodedDracoDataArray); if (encodedLength <= 0) { - throw 'Error: Encoding Failed.'; + throw 'Error: Draco encoding failed.'; } var encodedData = Buffer.alloc(encodedLength); diff --git a/samples-generator/lib/createTilesetJsonSingle.js b/samples-generator/lib/createTilesetJsonSingle.js index 25d2086c..6cc1a3eb 100644 --- a/samples-generator/lib/createTilesetJsonSingle.js +++ b/samples-generator/lib/createTilesetJsonSingle.js @@ -21,8 +21,7 @@ var defaultTilesetVersion = '1.0'; * @param {Object} [options.sphere] Bounding sphere of the tile. * @param {Matrix4} [options.transform=Matrix4.IDENTITY] The tile transform. * @param {Object} [options.properties] An object containing the min and max values for each property in the batch table. - * @param {Array} [options.extensionsUsed] An array containing names of extensions used in the tileset. - * @param {Array} [options.extensionsRequired] An array containing names of extensions required by the tileset. + * @param {Object} [options.extensions] An object containing extensionsUsed, extensionsRequired, and extensions properties. * @param {Object} [options.expire] Tile expiration options. * * @returns {Object} The tileset JSON. @@ -31,14 +30,16 @@ function createTilesetJsonSingle(options) { var transform = defaultValue(options.transform, Matrix4.IDENTITY); var transformArray = (defined(transform) && !Matrix4.equals(transform, Matrix4.IDENTITY)) ? Matrix4.pack(transform, new Array(16)) : undefined; var boundingVolume = getBoundingVolume(options.region, options.box, options.sphere); + var extensions = defaultValue(options.extensions, defaultValue.EMPTY_OBJECT); var tilesetJson = { asset : { version : defaultValue(options.versionNumber, defaultTilesetVersion) }, properties : options.properties, - extensionsUsed : options.extensionsUsed, - extensionsRequired : options.extensionsRequired, + extensionsUsed : extensions.extensionsUsed, + extensionsRequired : extensions.extensionsRequired, + extensions : extensions.extensions, geometricError : options.geometricError, root : { transform : transformArray,