Skip to content

Commit

Permalink
Add command to split CMPT tiles
Browse files Browse the repository at this point in the history
  • Loading branch information
javagl committed Nov 8, 2023
1 parent dea85cf commit f9546c1
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 2 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,22 @@ Extracts the glb models from a cmpt tile. If multiple models are found a number
npx 3d-tiles-tools cmptToGlb -i ./specs/data/composite.cmpt -o ./output/extracted.glb
```
#### splitCmpt
Split a cmpt tile into its inner tiles. The output file name for each inner tile will be determined by appending a number to the given output file name, and an extension that depends on the type of the inner tile data.
```
npx 3d-tiles-tools cmptToGlb -i ./specs/data/compositeOfComposite.cmpt -o ./output/inner --recursive
```
For an input file `compositeOfComposite.cmpt` that contains a composite tile that contains one B3DM and one I3DM content, this will generate the files `inner_0.b3dm` and `inner_1.i3dm` in the output directory.
Additional command line options:
| Flag | Description | Required |
| ---- | ----------- | -------- |
|`--recursive`|Whether the split operation should be applied to inner tiles that are composite| No, default: `false` |
#### convertB3dmToGlb
Expand Down
24 changes: 24 additions & 0 deletions specs/tileFormats/TileFormatsSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,30 @@ describe("TileFormats", function () {
expect(outputGlbBuffer.length).toEqual(inputGlbBuffer.length);
});

it("splits a composite with splitCmpt", async function () {
const p = "./specs/data/composite.cmpt";
const recursive = false;
const inputBuffer = fs.readFileSync(p);
const outputBuffers = await TileFormats.splitCmpt(inputBuffer, recursive);
expect(outputBuffers.length).toEqual(2);
});

it("splits a composite-of-composite into a single file with non-recursive splitCmpt", async function () {
const p = "./specs/data/compositeOfComposite.cmpt";
const recursive = false;
const inputBuffer = fs.readFileSync(p);
const outputBuffers = await TileFormats.splitCmpt(inputBuffer, recursive);
expect(outputBuffers.length).toEqual(1);
});

it("splits a composite-of-composite into a all 'leaf' tiles with recursive splitCmpt", async function () {
const p = "./specs/data/compositeOfComposite.cmpt";
const recursive = true;
const inputBuffer = fs.readFileSync(p);
const outputBuffers = await TileFormats.splitCmpt(inputBuffer, recursive);
expect(outputBuffers.length).toEqual(2);
});

it("throws an error when trying to read tile data from a buffer that does not contain B3DM, I3DM, or PNTS", function () {
const p = "./specs/data/contentTypes/content.cmpt";
const tileDataBuffer = fs.readFileSync(p);
Expand Down
45 changes: 45 additions & 0 deletions src/ToolsMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { TilesetConverter } from "./tilesetProcessing/TilesetConverter";

import { TilesetJsonCreator } from "./tilesetProcessing/TilesetJsonCreator";

import { ContentDataTypeRegistry } from "./contentTypes/ContentDataTypeRegistry";

import { Loggers } from "./logging/Loggers";
const logger = Loggers.get("CLI");

Expand Down Expand Up @@ -166,6 +168,49 @@ export class ToolsMain {

logger.debug(`Executing cmptToGlb DONE`);
}

static async splitCmpt(
input: string,
output: string,
recursive: boolean,
force: boolean
) {
logger.debug(`Executing splitCmpt`);
logger.debug(` input: ${input}`);
logger.debug(` output: ${output}`);
logger.debug(` recursive: ${recursive}`);
logger.debug(` force: ${force}`);

const inputBuffer = fs.readFileSync(input);
const outputBuffers = await TileFormats.splitCmpt(inputBuffer, recursive);
for (let i = 0; i < outputBuffers.length; i++) {
const outputBuffer = outputBuffers[i];
const prefix = Paths.replaceExtension(output, "");
const extension = await ToolsMain.determineFileExtension(outputBuffer);
const outputPath = `${prefix}_${i}.${extension}`;
ToolsMain.ensureCanWrite(outputPath, force);
fs.writeFileSync(outputPath, outputBuffer);
}

logger.debug(`Executing splitCmpt DONE`);
}

private static async determineFileExtension(data: Buffer): Promise<string> {
const type = await ContentDataTypeRegistry.findType("", data);
switch (type) {
case ContentDataTypes.CONTENT_TYPE_B3DM:
return "b3dm";
case ContentDataTypes.CONTENT_TYPE_I3DM:
return "i3dm";
case ContentDataTypes.CONTENT_TYPE_PNTS:
return "pnts";
case ContentDataTypes.CONTENT_TYPE_CMPT:
return "cmpt";
}
logger.warn("Could not determine type of inner tile");
return "UNKNOWN";
}

static async glbToB3dm(input: string, output: string, force: boolean) {
logger.debug(`Executing glbToB3dm`);
logger.debug(` input: ${input}`);
Expand Down
22 changes: 20 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import yargs from "yargs/yargs";
import { DeveloperError } from "./base/DeveloperError";

import { ToolsMain } from "./ToolsMain";

import { Loggers } from "./logging/Loggers";
let logger = Loggers.get("CLI");

import { ToolsMain } from "./ToolsMain";

// Split the arguments that are intended for the tools
// and the `--options` arguments. Everything behind
// the `--options` will be passed to downstream
Expand Down Expand Up @@ -157,6 +157,20 @@ function parseToolArgs(a: string[]) {
o: outputStringDefinition,
}
)
.command(
"splitCmpt",
"Split the input cmpt and write out its inner tiles.",
{
i: inputStringDefinition,
o: outputStringDefinition,
recursive: {
default: false,
description: "Recursively apply the command to inner CMPT tiles",
global: true,
type: "boolean",
},
}
)
.command(
"convertB3dmToGlb",
"Convert a b3dm file into a glTF asset that uses glTF extensions to " +
Expand Down Expand Up @@ -371,6 +385,7 @@ async function runCommand(command: string, toolArgs: any, optionArgs: any) {
const inputs: string[] = toolArgs.input;
const output = toolArgs.output;
const force = toolArgs.force;
const recursive = toolArgs.recursive;
const tilesOnly = toolArgs.tilesOnly;
const parsedOptionArgs = parseOptionArgs(optionArgs);

Expand All @@ -379,6 +394,7 @@ async function runCommand(command: string, toolArgs: any, optionArgs: any) {
logger.trace(` inputs: ${inputs}`);
logger.trace(` output: ${output}`);
logger.trace(` force: ${force}`);
logger.trace(` recursive: ${recursive}`);
logger.trace(` optionArgs: ${optionArgs}`);
logger.trace(` parsedOptionArgs: ${JSON.stringify(parsedOptionArgs)}`);

Expand All @@ -390,6 +406,8 @@ async function runCommand(command: string, toolArgs: any, optionArgs: any) {
await ToolsMain.i3dmToGlb(input, output, force);
} else if (command === "cmptToGlb") {
await ToolsMain.cmptToGlb(input, output, force);
} else if (command === "splitCmpt") {
await ToolsMain.splitCmpt(input, output, recursive, force);
} else if (command === "convertB3dmToGlb") {
await ToolsMain.convertB3dmToGlb(input, output, force);
} else if (command === "convertPntsToGlb") {
Expand Down
58 changes: 58 additions & 0 deletions src/tileFormats/TileFormats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,64 @@ export class TileFormats {
}
}

/**
* Split the given (CMPT) tile data and return one buffer for each
* of its inner tiles.
*
* If the given tile data is not CMPT data, it is returned directly.
* If `recursive===true`, then any inner tile data that is encountered
* and that also is CMPT data will be split and its elements added
* to the list of results.
*
* @param tileDataBuffer - The tile data
* @param recursive - Whether the tile data should be split recursively
* @returns The inner tile data buffers
*/
static async splitCmpt(
tileDataBuffer: Buffer,
recursive: boolean
): Promise<Buffer[]> {
const resultBuffers: Buffer[] = [];
await TileFormats.splitCmptInternal(
tileDataBuffer,
recursive,
resultBuffers
);
return resultBuffers;
}

/**
* Internal implementation for `splitCmpt`, called recursively
*
* @param tileDataBuffer - The tile data
* @param recursive - Whether the tile data should be split recursively
* @returns resultBuffers - The inner tile data buffers
*/
private static async splitCmptInternal(
tileDataBuffer: Buffer,
recursive: boolean,
resultBuffers: Buffer[]
) {
const isComposite = TileFormats.isComposite(tileDataBuffer);
if (!isComposite) {
resultBuffers.push(tileDataBuffer);
} else {
const compositeTileData =
TileFormats.readCompositeTileData(tileDataBuffer);
if (!recursive) {
resultBuffers.push(...compositeTileData.innerTileBuffers);
} else {
for (const innerTileBuffer of compositeTileData.innerTileBuffers) {
await TileFormats.splitCmptInternal(
innerTileBuffer,
recursive,
resultBuffers
);
}
}
}
}

/**
* Creates a Batched 3D Model (B3DM) `TileData` instance from
* a buffer that is assumed to contain valid binary glTF (GLB)
Expand Down

0 comments on commit f9546c1

Please sign in to comment.