diff --git a/samples-generator/bin/3d-tiles-samples-generator.js b/samples-generator/bin/3d-tiles-samples-generator.js index 13e7841c..a32e5fdc 100644 --- a/samples-generator/bin/3d-tiles-samples-generator.js +++ b/samples-generator/bin/3d-tiles-samples-generator.js @@ -232,6 +232,12 @@ var promises = [ createPointCloudBatched(), createPointCloudWithPerPointProperties(), createPointCloudWithTransform(), + createPointCloudDraco(), + createPointCloudDracoPartial(), + createPointCloudDracoBatched(), + createPointCloudTimeDynamic(), + createPointCloudTimeDynamicWithTransforms(), + createPointCloudTimeDynamicDraco(), // Instanced createInstancedWithBatchTable(), createInstancedWithoutBatchTable(), @@ -594,6 +600,58 @@ 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 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 @@ -918,11 +976,13 @@ function savePointCloudTileset(tilesetName, tileOptions, tilesetOptions) { var result = createPointCloudTile(tileOptions); var pnts = result.pnts; var batchTableJson = result.batchTableJson; + var extensions = result.extensions; tilesetOptions = defaultValue(tilesetOptions, {}); tilesetOptions.contentUri = contentUri; tilesetOptions.properties = getProperties(batchTableJson); tilesetOptions.geometricError = pointCloudGeometricError; + tilesetOptions.extensions = extensions; if (!defined(tilesetOptions.region) && !defined(tilesetOptions.sphere) && !defined(tilesetOptions.box)) { tilesetOptions.sphere = pointCloudSphere; } @@ -934,6 +994,42 @@ 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 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 tilePromises = []; + 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 tilePath = path.join(directory, i + '.pnts'); + tilePromises.push(saveTile(tilePath, pnts, gzip)); + } + + return Promise.all(tilePromises); +} + function createHierarchy() { return createBatchTableHierarchy({ directory : path.join(outputDirectory, 'Hierarchy', 'BatchTableHierarchy'), diff --git a/samples-generator/lib/createPointCloudTile.js b/samples-generator/lib/createPointCloudTile.js index 519f1eed..53afd893 100644 --- a/samples-generator/lib/createPointCloudTile.js +++ b/samples-generator/lib/createPointCloudTile.js @@ -1,16 +1,20 @@ 'use strict'; 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; 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 +26,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 +39,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 +62,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 +113,94 @@ 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 extensions = {}; + + 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)) { + Extensions.addExtension(featureTableJson, '3DTILES_draco_point_compression', dracoFeatureTableJson); + } + if (defined(dracoBatchTableJson)) { + Extensions.addExtension(batchTableJson, '3DTILES_draco_point_compression', dracoBatchTableJson); + } + + Extensions.addExtensionsRequired(extensions, '3DTILES_draco_point_compression'); + Extensions.addExtensionsUsed(extensions, '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,175 @@ function createPointCloudTile(options) { return { pnts : pnts, - batchTableJson : batchTableJson + batchTableJson : batchTableJson, + extensions : extensions + }; +} + +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: Draco encoding failed.'; + } + + var encodedData = Buffer.alloc(encodedLength); + for (i = 0; i < encodedLength; i++) { + encodedData[i] = encodedDracoDataArray.GetValue(i); + } + + encoderModule.destroy(encoder); + encoderModule.destroy(pointCloudBuilder); + encoderModule.destroy(pointCloud); + encoderModule.destroy(encodedDracoDataArray); + + 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 +489,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 +534,8 @@ function getPositions(positions) { return { buffer : buffer, propertyName : 'POSITION', - byteAlignment : sizeOfFloat32 + componentType : 'FLOAT', + type : 'VEC3' }; } @@ -332,7 +558,8 @@ function getPositionsQuantized(positions, radius) { return { buffer : buffer, propertyName : 'POSITION_QUANTIZED', - byteAlignment : sizeOfUint16 + componentType : 'UNSIGNED_SHORT', + type : 'VEC3' }; } @@ -348,7 +575,8 @@ function getNormals(normals) { return { buffer : buffer, propertyName : 'NORMAL', - byteAlignment : sizeOfFloat32 + componentType : 'FLOAT', + type : 'VEC3' }; } @@ -365,7 +593,8 @@ function getNormalsOctEncoded(normals) { return { buffer : buffer, propertyName : 'NORMAL_OCT16P', - byteAlignment : sizeOfUint8 + componentType : 'UNSIGNED_BYTE', + type : 'VEC2' }; } @@ -379,7 +608,6 @@ function getBatchIds(batchIds) { } var buffer; - var byteAlignment; var componentType; if (batchLength <= 256) { buffer = Buffer.alloc(pointsLength * sizeOfUint8); @@ -387,28 +615,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 +653,8 @@ function getColorsRGB(colors) { return { buffer : buffer, propertyName : 'RGB', - byteAlignment : sizeOfUint8 + componentType : 'UNSIGNED_BYTE', + type : 'VEC3' }; } @@ -449,7 +675,8 @@ function getColorsRGBA(colors) { return { buffer : buffer, propertyName : 'RGBA', - byteAlignment : sizeOfUint8 + componentType : 'UNSIGNED_BYTE', + type : 'VEC4' }; } @@ -467,7 +694,8 @@ function getColorsRGB565(colors) { return { buffer : buffer, propertyName : 'RGB565', - byteAlignment : sizeOfUint16 + componentType : 'UNSIGNED_SHORT', + type : 'SCALAR' }; } @@ -508,30 +736,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 +752,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 ccda5bac..6cc1a3eb 100644 --- a/samples-generator/lib/createTilesetJsonSingle.js +++ b/samples-generator/lib/createTilesetJsonSingle.js @@ -21,6 +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 {Object} [options.extensions] An object containing extensionsUsed, extensionsRequired, and extensions properties. * @param {Object} [options.expire] Tile expiration options. * * @returns {Object} The tileset JSON. @@ -29,12 +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 : extensions.extensionsUsed, + extensionsRequired : extensions.extensionsRequired, + extensions : extensions.extensions, geometricError : options.geometricError, root : { transform : transformArray, diff --git a/samples-generator/package.json b/samples-generator/package.json index 6302aaad..a62bdf8a 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.3", "fs-extra": "^4.0.2", "gltf-pipeline": "^2.0.0", "mime": "^2.0.3",