diff --git a/README.md b/README.md index 51e385ef..2918fbff 100644 --- a/README.md +++ b/README.md @@ -265,13 +265,23 @@ This example optimizes the b3dm and compresses the meshes using Draco, with a hi #### optimizeI3dm -Optimize a i3dm using [gltf-pipeline](https://github.com/CesiumGS/gltf-pipeline/blob/main/README.md). +Optimize an i3dm using [gltf-pipeline](https://github.com/CesiumGS/gltf-pipeline/blob/main/README.md). ``` npx 3d-tiles-tools optimizeI3dm -i ./specs/data/instancedWithBatchTableBinary.i3dm -o ./output/optimized.i3dm ``` See [optimizeB3dm](#optimizeb3dm) for further examples. +#### updateAlignment + +Update a B3DM, I3DM, PNTS or CMPT file to ensure that the alignment requirements +for the batch- and feature tables and the tile data as a whole are met. For CMPT +tile data, the data of inner tiles will be updated recursively. + +``` +npx 3d-tiles-tools updateAlignment -i ./specs/data/updateAlignment/testComposite.cmpt -o ./output/testCompositeFixed.cmpt +``` + #### analyze diff --git a/specs/data/updateAlignment/README.md b/specs/data/updateAlignment/README.md new file mode 100644 index 00000000..ead013a2 --- /dev/null +++ b/specs/data/updateAlignment/README.md @@ -0,0 +1,11 @@ + +Individual tile content files (one of each type) for testing the `updateAlignment` +command. These have been taken from the state at +https://github.com/CesiumGS/cesium/blob/83306253e36bbc3fb53801fdae8dee62f1a47be5 +which was before the alignment of the spec data was fixed via +https://github.com/CesiumGS/cesium/commit/4b3b4186b4afa2307d7d1666569826d864b984ca + +The `testComposite.cmpt` was created specifically for this test, +from `batchedColors.b3dm` and `pointCloudRGB.pnts`. + + diff --git a/specs/data/updateAlignment/batchedColors.b3dm b/specs/data/updateAlignment/batchedColors.b3dm new file mode 100644 index 00000000..28cf4bd0 Binary files /dev/null and b/specs/data/updateAlignment/batchedColors.b3dm differ diff --git a/specs/data/updateAlignment/golden/batchedColors.b3dm b/specs/data/updateAlignment/golden/batchedColors.b3dm new file mode 100644 index 00000000..d877294f Binary files /dev/null and b/specs/data/updateAlignment/golden/batchedColors.b3dm differ diff --git a/specs/data/updateAlignment/golden/instancedScale.i3dm b/specs/data/updateAlignment/golden/instancedScale.i3dm new file mode 100644 index 00000000..e6284fe4 Binary files /dev/null and b/specs/data/updateAlignment/golden/instancedScale.i3dm differ diff --git a/specs/data/updateAlignment/golden/pointCloudRGB.pnts b/specs/data/updateAlignment/golden/pointCloudRGB.pnts new file mode 100644 index 00000000..8ab4d265 Binary files /dev/null and b/specs/data/updateAlignment/golden/pointCloudRGB.pnts differ diff --git a/specs/data/updateAlignment/golden/testComposite.cmpt b/specs/data/updateAlignment/golden/testComposite.cmpt new file mode 100644 index 00000000..06ecaa32 Binary files /dev/null and b/specs/data/updateAlignment/golden/testComposite.cmpt differ diff --git a/specs/data/updateAlignment/instancedScale.i3dm b/specs/data/updateAlignment/instancedScale.i3dm new file mode 100644 index 00000000..c6d19ff1 Binary files /dev/null and b/specs/data/updateAlignment/instancedScale.i3dm differ diff --git a/specs/data/updateAlignment/pointCloudRGB.pnts b/specs/data/updateAlignment/pointCloudRGB.pnts new file mode 100644 index 00000000..455d3b84 Binary files /dev/null and b/specs/data/updateAlignment/pointCloudRGB.pnts differ diff --git a/specs/data/updateAlignment/testComposite.cmpt b/specs/data/updateAlignment/testComposite.cmpt new file mode 100644 index 00000000..d01c3798 Binary files /dev/null and b/specs/data/updateAlignment/testComposite.cmpt differ diff --git a/specs/tools/contentProcessing/UpdateAlignmentSpec.ts b/specs/tools/contentProcessing/UpdateAlignmentSpec.ts new file mode 100644 index 00000000..ed3c941e --- /dev/null +++ b/specs/tools/contentProcessing/UpdateAlignmentSpec.ts @@ -0,0 +1,59 @@ +import fs from "fs"; + +import { Paths } from "../../../src/base"; + +import { ContentOps } from "../../../src/tools"; + +import { SpecHelpers } from "../../SpecHelpers"; + +const SPECS_DATA_BASE_DIRECTORY = SpecHelpers.getSpecsDataBaseDirectory(); + +const sourceDir = SPECS_DATA_BASE_DIRECTORY + "/updateAlignment"; +const targetDir = SPECS_DATA_BASE_DIRECTORY + "/updateAlignment/output"; +const goldenDir = SPECS_DATA_BASE_DIRECTORY + "/updateAlignment/golden"; + +function updateAlignmentForSpec(fileName: string) { + const sourceFileName = sourceDir + "/" + fileName; + const targetFileName = targetDir + "/" + fileName; + const goldenFileName = goldenDir + "/" + fileName; + + const sourceData = fs.readFileSync(sourceFileName); + + const targetData = ContentOps.updateAlignment(sourceData); + Paths.ensureDirectoryExists(targetDir); + fs.writeFileSync(targetFileName, targetData); + + const goldenData = fs.readFileSync(goldenFileName); + const passed = targetData.equals(goldenData); + return passed; +} + +describe("ContentOps", function () { + afterEach(function () { + SpecHelpers.forceDeleteDirectory(targetDir); + }); + + it("updateAlignment updates the alignment of a B3DM file", async function () { + const fileName = "batchedColors.b3dm"; + const passed = updateAlignmentForSpec(fileName); + expect(passed).toBe(true); + }); + + it("updateAlignment updates the alignment of an I3DM file", async function () { + const fileName = "instancedScale.i3dm"; + const passed = updateAlignmentForSpec(fileName); + expect(passed).toBe(true); + }); + + it("updateAlignment updates the alignment of a PNTS file", async function () { + const fileName = "pointCloudRGB.pnts"; + const passed = updateAlignmentForSpec(fileName); + expect(passed).toBe(true); + }); + + it("updateAlignment updates the alignment of a CMPT file", async function () { + const fileName = "testComposite.cmpt"; + const passed = updateAlignmentForSpec(fileName); + expect(passed).toBe(true); + }); +}); diff --git a/src/cli/ToolsMain.ts b/src/cli/ToolsMain.ts index bc51c3cc..57050f92 100644 --- a/src/cli/ToolsMain.ts +++ b/src/cli/ToolsMain.ts @@ -282,6 +282,20 @@ export class ToolsMain { logger.debug(`Executing optimizeI3dm DONE`); } + static updateAlignment(input: string, output: string, force: boolean) { + logger.debug(`Executing updateAlignment`); + logger.debug(` input: ${input}`); + logger.debug(` output: ${output}`); + logger.debug(` force: ${force}`); + + ToolsMain.ensureCanWrite(output, force); + const inputBuffer = fs.readFileSync(input); + const outputBuffer = ContentOps.updateAlignment(inputBuffer); + fs.writeFileSync(output, outputBuffer); + + logger.debug(`Executing updateAlignment DONE`); + } + static analyze( inputFileName: string, outputDirectoryName: string, diff --git a/src/cli/main.ts b/src/cli/main.ts index bd2dc99a..d5e41041 100644 --- a/src/cli/main.ts +++ b/src/cli/main.ts @@ -225,6 +225,16 @@ function parseToolArgs(a: string[]) { }, } ) + .command( + "updateAlignment", + "Update the alignment of the batch- and feature table and the tile data" + + "as a whole, to meet the alignment requirements of the specification. " + + "This can be applied to B3DM, I3DM, PNTS, or CMPT tile data.", + { + i: inputStringDefinition, + o: outputStringDefinition, + } + ) .command("gzip", "Gzips the input tileset directory.", { i: inputStringDefinition, o: outputStringDefinition, @@ -490,6 +500,8 @@ async function runCommand(command: string, toolArgs: any, optionArgs: any) { await ToolsMain.optimizeB3dm(input, output, force, parsedOptionArgs); } else if (command === "optimizeI3dm") { await ToolsMain.optimizeI3dm(input, output, force, parsedOptionArgs); + } else if (command === "updateAlignment") { + await ToolsMain.updateAlignment(input, output, force); } else if (command === "gzip") { const tilesOnly = toolArgs.tilesOnly === true; await ToolsMain.gzip(input, output, force, tilesOnly); diff --git a/src/tools/contentProcessing/ContentOps.ts b/src/tools/contentProcessing/ContentOps.ts index 89805ab6..913b996d 100644 --- a/src/tools/contentProcessing/ContentOps.ts +++ b/src/tools/contentProcessing/ContentOps.ts @@ -1,4 +1,5 @@ import { TileFormats } from "../../tilesets"; +import { CompositeTileData } from "../../tilesets"; import { GltfUtilities } from "./GltfUtilities"; @@ -149,4 +150,45 @@ export class ContentOps { const outputBuffer = TileFormats.createTileDataBuffer(outputTileData); return outputBuffer; } + + /** + * Update the alignment of the given tile data buffer. + * + * This can be applied to B3DM, I3DM, PNTS, or CMPT tile data. It will + * read the tile data from the input, and generate a new tile data + * buffer with the same contents, but ensuring that the alignment + * requirements for the batch- and feature tables and the tile data + * as a whole are met. For CMPT tile data, the data of inner tiles + * will be updated recursively. + * + * @param inputBuffer - The input buffer + * @returns The resulting buffer + */ + static updateAlignment(inputBuffer: Buffer): Buffer { + const isComposite = TileFormats.isComposite(inputBuffer); + if (isComposite) { + const inputCompositeTileData = + TileFormats.readCompositeTileData(inputBuffer); + const header = inputCompositeTileData.header; + const inputInnerTileBuffers = inputCompositeTileData.innerTileBuffers; + const outputInnerTileBuffers: Buffer[] = []; + for (let i = 0; i < inputInnerTileBuffers.length; i++) { + const inputInnerTileBuffer = inputInnerTileBuffers[i]; + const outputInnerTileBuffer = + ContentOps.updateAlignment(inputInnerTileBuffer); + outputInnerTileBuffers.push(outputInnerTileBuffer); + } + const outputCompositeTileData: CompositeTileData = { + header: header, + innerTileBuffers: outputInnerTileBuffers, + }; + const outputBuffer = TileFormats.createCompositeTileDataBuffer( + outputCompositeTileData + ); + return outputBuffer; + } + const tileData = TileFormats.readTileData(inputBuffer); + const outputBuffer = TileFormats.createTileDataBuffer(tileData); + return outputBuffer; + } }