Skip to content

Commit

Permalink
Use gltf-transform for reading glTFs.
Browse files Browse the repository at this point in the history
This makes the glTF parsing more robust and we can handle more complex glTFs like
draco compressed ones.
  • Loading branch information
na9da committed Nov 28, 2023
1 parent 0aaa836 commit da17572
Show file tree
Hide file tree
Showing 54 changed files with 1,866 additions and 1,018 deletions.
2 changes: 1 addition & 1 deletion bin/index-3dtiles
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/usr/bin/env node

require("../dist/tsc/3dTiles/indexer.js");
require("../build/3dTiles/indexer.js");
2 changes: 1 addition & 1 deletion bin/index-kml-gltf
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/usr/bin/env node

require("../dist/tsc/kml-gltf/indexer.js");
require("../build/kml-gltf/indexer.js");
4 changes: 2 additions & 2 deletions dist/tsc/3dTiles/b3dms.d.ts → build/3dTiles/b3dms.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/// <reference types="node" />
export declare type FeatureTable = {
export type FeatureTable = {
jsonFeatureTable: any;
binaryFeatureTable: Buffer;
};
export declare type BatchTable = {
export type BatchTable = {
jsonBatchTable: any;
binaryBatchTable: Buffer;
};
Expand Down
27 changes: 24 additions & 3 deletions dist/tsc/3dTiles/b3dms.js → build/3dTiles/b3dms.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
"use strict";
// Various utility functions for working with B3DMs
// See: https://github.com/CesiumGS/3d-tiles/blob/master/specification/TileFormats/Batched3DModel/README.md#feature-table
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.readRtcCenter = exports.getFeatureTableGlobalProperty = exports.getBatchTableProperties = exports.getBatchTable = exports.getBinaryBatchTable = exports.getJSONBatchTable = exports.getFeatureTable = exports.getBinaryFeatureTable = exports.getJSONFeatureTable = exports.getGlb = void 0;
const tslib_1 = require("tslib");
const binaryProperty = tslib_1.__importStar(require("./binaryProperty"));
const binaryProperty = __importStar(require("./binaryProperty"));
function getGlb(b3dm) {
const glbStart = getBodyStart() +
getFeatureTableJSONByteLength(b3dm) +
Expand Down Expand Up @@ -116,4 +138,3 @@ function getBatchTableJSONByteLength(b3dm) {
function getBatchTableBinaryByteLength(b3dm) {
return b3dm.readUInt32LE(24);
}
//# sourceMappingURL=b3dms.js.map
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
declare type BinaryProperty = {
type BinaryProperty = {
byteOffset: number;
componentType: number;
type: BinaryPropertyType;
};
declare type BinaryPropertyType = "SCALAR" | "VEC2" | "VEC3" | "VEC4" | "MAT2" | "MAT3" | "MAT4";
type BinaryPropertyType = "SCALAR" | "VEC2" | "VEC3" | "VEC4" | "MAT2" | "MAT3" | "MAT4";
export declare function parse(json: any): BinaryProperty;
export declare function read(binaryProperty: BinaryProperty, binaryBody: Uint8Array, batchLength: number): any[];
export {};
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ const ClassTypes = {
};
const binaryPropertyTypes = Object.keys(ComponentsPerAttribute);
function parse(json) {
Json_1.assertObject(json, "Object");
Json_1.assertNumber(json.byteOffset, "byteOffset");
(0, Json_1.assertObject)(json, "Object");
(0, Json_1.assertNumber)(json.byteOffset, "byteOffset");
const type = parseBinaryPropertyType(json.type);
const componentType = parseComponentType(json.componentType);
return {
Expand All @@ -35,15 +35,15 @@ function parse(json) {
}
exports.parse = parse;
function parseBinaryPropertyType(json) {
Json_1.assertString(json, "type");
(0, Json_1.assertString)(json, "type");
if (binaryPropertyTypes.includes(json))
return json;
throw new Error(`Expected type to be ${binaryPropertyTypes.join("|")}, got ${json}`);
}
function parseComponentType(json) {
if (typeof json === "string")
return cesium_1.ComponentDatatype.fromName(json);
Json_1.assertNumber(json, "componentType");
(0, Json_1.assertNumber)(json, "componentType");
return json;
}
function read(binaryProperty, binaryBody, batchLength) {
Expand All @@ -60,4 +60,3 @@ function read(binaryProperty, binaryBody, batchLength) {
return values;
}
exports.read = read;
//# sourceMappingURL=binaryProperty.js.map
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
* Runs the indexer with the given arguments
* @params argv An argument array
*/
export default function runIndexer(argv: string[]): void;
export default function runIndexer(argv: string[]): Promise<void>;
101 changes: 56 additions & 45 deletions dist/tsc/3dTiles/indexer.js → build/3dTiles/indexer.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,39 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const cesium_1 = require("cesium");
const fse = tslib_1.__importStar(require("fs-extra"));
const path = tslib_1.__importStar(require("path"));
const fse = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const Config_1 = require("../Config");
const IndexBuilder_1 = require("../IndexBuilder");
const constants_1 = require("../constants");
const gltfs = tslib_1.__importStar(require("../gltfs"));
const gltfs = __importStar(require("../gltfs"));
const gltfs_1 = require("../gltfs");
const IndexBuilder_1 = require("../IndexBuilder");
const utils_1 = require("../utils");
const b3dms = tslib_1.__importStar(require("./b3dms"));
const tiles = tslib_1.__importStar(require("./tiles"));
const b3dms = __importStar(require("./b3dms"));
const tiles = __importStar(require("./tiles"));
const USAGE = "USAGE: npx index-3dtiles <tileset.json file> <config.json file> <index output directory>";
/**
* Generate an index for the given tileset.
Expand All @@ -25,21 +47,24 @@ const USAGE = "USAGE: npx index-3dtiles <tileset.json file> <config.json file> <
* Because we uniquify the features, if there are 2 LOD tiles for the same feature
* has different properties we only index the properties of the highest LOD tile.
*/
function index3dTileset(tileset, tilesetDir, indexesConfig, outDir) {
const indexBuilders = Object.entries(indexesConfig.indexes).map(([property, config]) => IndexBuilder_1.createIndexBuilder(property, config));
const features = readTilesetFeatures(tileset, tilesetDir, indexesConfig);
async function index3dTileset(tileset, tilesetDir, indexesConfig, outDir) {
const indexBuilders = Object.entries(indexesConfig.indexes).map(([property, config]) => (0, IndexBuilder_1.createIndexBuilder)(property, config));
const features = await readTilesetFeatures(tileset, tilesetDir, indexesConfig);
const featuresCount = Object.entries(features).length;
console.log(`\nUnique features found: ${featuresCount}`);
console.log("Building indexes...");
const resultsData = [];
Object.entries(features).forEach(([idValue, { position, properties }]) => {
const positionProperties = {
// rounding to fewer decimal places significantly reduces the size of resultData file
latitude: utils_1.roundToNDecimalPlaces(cesium_1.Math.toDegrees(position.latitude), 5),
longitude: utils_1.roundToNDecimalPlaces(cesium_1.Math.toDegrees(position.longitude), 5),
height: utils_1.roundToNDecimalPlaces(position.height, 3),
latitude: (0, utils_1.roundToNDecimalPlaces)(cesium_1.Math.toDegrees(position.latitude), 5),
longitude: (0, utils_1.roundToNDecimalPlaces)(cesium_1.Math.toDegrees(position.longitude), 5),
height: (0, utils_1.roundToNDecimalPlaces)(position.height, 3),
};
const len = resultsData.push(Object.assign({ [indexesConfig.idProperty]: idValue }, positionProperties));
const len = resultsData.push({
[indexesConfig.idProperty]: idValue,
...positionProperties,
});
const dataRowId = len - 1;
indexBuilders.forEach((b) => {
if (b.property in properties) {
Expand All @@ -51,14 +76,14 @@ function index3dTileset(tileset, tilesetDir, indexesConfig, outDir) {
});
});
console.log("Writing indexes...");
const indexes = IndexBuilder_1.writeIndexes(indexBuilders, outDir);
const resultsDataUrl = IndexBuilder_1.writeResultsData(resultsData, outDir);
const indexes = (0, IndexBuilder_1.writeIndexes)(indexBuilders, outDir);
const resultsDataUrl = (0, IndexBuilder_1.writeResultsData)(resultsData, outDir);
const indexRoot = {
resultsDataUrl,
idProperty: indexesConfig.idProperty,
indexes,
};
IndexBuilder_1.writeIndexRoot(indexRoot, outDir);
(0, IndexBuilder_1.writeIndexRoot)(indexRoot, outDir);
console.log(`Indexes written to ${outDir}/`);
console.log("Done.");
}
Expand All @@ -69,19 +94,18 @@ function index3dTileset(tileset, tilesetDir, indexesConfig, outDir) {
* @param indexesConfig The indexes config object
* @returns An object containing {properties,position} for each feature in the tilset. The object is keyed by the idProperty for the feature.
*/
function readTilesetFeatures(tileset, tilesetDir, indexesConfig) {
async function readTilesetFeatures(tileset, tilesetDir, indexesConfig) {
const uniqueFeatures = {};
let featuresRead = 0;
// The tileset can contain child tilesets. We add any child tilesets that we come
// across to this queue so that they will be processed sequentially.
const tilesetQueue = [tileset];
for (tileset of tilesetQueue) {
for await (tileset of tilesetQueue) {
// For each feature in each tile in the tileset
// 1. read properties for the feature from the batch table
// 2. compute position of the feature from the vertex data
// Then generate a unique list of feature id -> {position, properties} value
tiles.forEachTile(tileset, ({ tile, computedTransform: tileTransform }) => {
var _a;
const promise = tiles.forEachTile(tileset, async ({ tile, computedTransform: tileTransform }) => {
const tileUri = tiles.uri(tile);
if (tileUri === undefined) {
return;
Expand All @@ -105,11 +129,11 @@ function readTilesetFeatures(tileset, tilesetDir, indexesConfig) {
const batchTable = b3dms.getBatchTable(b3dm);
const batchTableProperties = b3dms.getBatchTableProperties(batchTable, batchLength);
let computedFeaturePositions = [];
const gltf = gltfs.parseGlb(b3dms.getGlb(b3dm));
const gltf = await gltfs.parseGlb(b3dms.getGlb(b3dm));
if (gltf !== undefined) {
const rtcTransform = getRtcTransform(featureTable, gltf);
const toZUpTransform = tiles.toZUpTransform(tileset);
computedFeaturePositions = (_a = gltfs_1.computeFeaturePositionsFromGltfVertices(gltf, tileTransform, rtcTransform, toZUpTransform)) !== null && _a !== void 0 ? _a : [];
computedFeaturePositions =
(0, gltfs_1.computeFeaturePositionsFromGltfVertices)(gltf, tileTransform, toZUpTransform) ?? [];
}
for (let batchId = 0; batchId < batchLength; batchId++) {
const batchProperties = {};
Expand All @@ -125,30 +149,18 @@ function readTilesetFeatures(tileset, tilesetDir, indexesConfig) {
properties: batchProperties,
};
featuresRead += 1;
utils_1.logOnSameLine(`Features read: ${featuresRead}`);
(0, utils_1.logOnSameLine)(`Features read: ${featuresRead}`);
}
});
await promise;
}
return uniqueFeatures;
}
/**
* Returns an RTC_CENTER or CESIUM_RTC transformation matrix which ever exists.
*
*/
function getRtcTransform(featureTable, gltf) {
var _a, _b;
const b3dmRtcCenter = b3dms.readRtcCenter(featureTable);
const rtcCenter = b3dmRtcCenter !== null && b3dmRtcCenter !== void 0 ? b3dmRtcCenter : (_b = (_a = gltf.json.extensions) === null || _a === void 0 ? void 0 : _a.CESIUM_RTC) === null || _b === void 0 ? void 0 : _b.center;
const rtcTransform = rtcCenter
? cesium_1.Matrix4.fromTranslation(cesium_1.Cartesian3.fromArray(rtcCenter))
: cesium_1.Matrix4.IDENTITY.clone();
return rtcTransform;
}
/**
* Runs the indexer with the given arguments
* @params argv An argument array
*/
function runIndexer(argv) {
async function runIndexer(argv) {
const [tilesetFile, indexConfigFile, outDir] = argv.slice(2);
let tileset;
let indexesConfig;
Expand All @@ -158,26 +170,25 @@ function runIndexer(argv) {
catch (e) {
console.error(`Failed to read tileset file "${tilesetFile}"`);
console.error(e);
utils_1.printUsageAndExit(USAGE);
(0, utils_1.printUsageAndExit)(USAGE);
}
try {
indexesConfig = Config_1.parseIndexesConfig(JSON.parse(fse.readFileSync(indexConfigFile).toString()));
indexesConfig = (0, Config_1.parseIndexesConfig)(JSON.parse(fse.readFileSync(indexConfigFile).toString()));
}
catch (e) {
console.error(`Failed to read index config file "${indexConfigFile}"`);
console.error(e);
utils_1.printUsageAndExit(USAGE);
(0, utils_1.printUsageAndExit)(USAGE);
return;
}
if (typeof outDir !== "string") {
console.error(`Output directory not specified.`);
utils_1.printUsageAndExit(USAGE);
(0, utils_1.printUsageAndExit)(USAGE);
}
fse.mkdirpSync(outDir);
const tilesetDir = path.dirname(tilesetFile);
index3dTileset(tileset, tilesetDir, indexesConfig, outDir);
await index3dTileset(tileset, tilesetDir, indexesConfig, outDir);
}
exports.default = runIndexer;
// TODO: do not run, instead just export this function
runIndexer(process.argv);
//# sourceMappingURL=indexer.js.map
6 changes: 3 additions & 3 deletions dist/tsc/3dTiles/tiles.d.ts → build/3dTiles/tiles.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Matrix4 } from "cesium";
declare type Tileset = {
type Tileset = {
root?: Tile;
asset?: {
gltfUpAxis?: number;
};
};
declare type Tile = {
type Tile = {
content?: {
uri?: string;
url?: string;
Expand All @@ -16,7 +16,7 @@ declare type Tile = {
export declare function forEachTile(tileset: Tileset, iterFn: (value: {
tile: Tile;
computedTransform: Matrix4;
}) => void): void;
}) => Promise<void>): Promise<void>;
export declare function uri(tile: Tile): string | undefined;
export declare function toZUpTransform(tileset: Tileset): any;
export {};
17 changes: 7 additions & 10 deletions dist/tsc/3dTiles/tiles.js → build/3dTiles/tiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,30 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.toZUpTransform = exports.uri = exports.forEachTile = void 0;
const cesium_1 = require("cesium");
function forEachTile(tileset, iterFn) {
async function forEachTile(tileset, iterFn) {
const root = tileset.root;
if (root === undefined) {
return;
}
const iterTile = (tile, parentTransform) => {
const iterTile = async (tile, parentTransform) => {
const computedTransform = tile.transform !== undefined
? cesium_1.Matrix4.multiply(parentTransform, cesium_1.Matrix4.unpack(tile.transform), new cesium_1.Matrix4())
: parentTransform;
iterFn({ tile, computedTransform });
await iterFn({ tile, computedTransform });
if (Array.isArray(tile.children)) {
tile.children.forEach((child) => iterTile(child, computedTransform));
await Promise.all(tile.children.map((child) => iterTile(child, computedTransform)));
}
};
iterTile(root, cesium_1.Matrix4.IDENTITY.clone());
return iterTile(root, cesium_1.Matrix4.IDENTITY.clone());
}
exports.forEachTile = forEachTile;
function uri(tile) {
var _a, _b, _c;
// older formats use url
return (_b = (_a = tile.content) === null || _a === void 0 ? void 0 : _a.uri) !== null && _b !== void 0 ? _b : (_c = tile.content) === null || _c === void 0 ? void 0 : _c.url;
return tile.content?.uri ?? tile.content?.url;
}
exports.uri = uri;
function toZUpTransform(tileset) {
var _a, _b;
const upAxis = (_b = (_a = tileset.asset) === null || _a === void 0 ? void 0 : _a.gltfUpAxis) !== null && _b !== void 0 ? _b : cesium_1.Axis.Y;
const upAxis = tileset.asset?.gltfUpAxis ?? cesium_1.Axis.Y;
const transform = upAxis === cesium_1.Axis.Y
? cesium_1.Axis.Y_UP_TO_Z_UP.clone()
: upAxis === cesium_1.Axis.X
Expand All @@ -36,4 +34,3 @@ function toZUpTransform(tileset) {
return transform;
}
exports.toZUpTransform = toZUpTransform;
//# sourceMappingURL=tiles.js.map
4 changes: 2 additions & 2 deletions dist/tsc/Config.d.ts → build/Config.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { IndexType } from "./Index";
export declare type IndexesConfig = {
export type IndexesConfig = {
idProperty: string;
indexes: Record<string, IndexConfig>;
};
export declare type IndexConfig = {
export type IndexConfig = {
type: IndexType;
};
export declare function parseIndexesConfig(json: any): IndexesConfig;
Loading

0 comments on commit da17572

Please sign in to comment.