Skip to content

Commit

Permalink
chore(tile-converter): preprocess stage for I3SConverter (#2520)
Browse files Browse the repository at this point in the history
  • Loading branch information
belom88 committed Jun 27, 2023
1 parent 67105bd commit f82fc25
Show file tree
Hide file tree
Showing 11 changed files with 335 additions and 173 deletions.
1 change: 1 addition & 0 deletions modules/3d-tiles/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ export type {
Tiles3DTileContent,
ImplicitTilingExensionData
} from './types';
export type {Tiles3DLoaderOptions} from './tiles-3d-loader';
16 changes: 10 additions & 6 deletions modules/3d-tiles/src/lib/parsers/parse-3d-tile-gltf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@ export async function parseGltf3DTile(
? options['3d-tiles'].assetGltfUpAxis
: 'Y';

if (!context) {
return;
if (options?.['3d-tiles']?.loadGLTF) {
if (!context) {
return;
}
const {parse} = context;
const gltfWithBuffers = await parse(arrayBuffer, GLTFLoader, options, context);
tile.gltf = postProcessGLTF(gltfWithBuffers);
tile.gpuMemoryUsageInBytes = _getMemoryUsageGLTF(tile.gltf);
} else {
tile.gltfArrayBuffer = arrayBuffer;
}
const {parse} = context;
const gltfWithBuffers = await parse(arrayBuffer, GLTFLoader, options, context);
tile.gltf = postProcessGLTF(gltfWithBuffers);
tile.gpuMemoryUsageInBytes = _getMemoryUsageGLTF(tile.gltf);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,23 @@ import {
GLTFMeshPrimitive
} from '../../types/gltf-json-schema';
import {getComponentTypeFromArray} from '../../gltf-utils/gltf-utils';
import {GLTFLoaderOptions} from '../../../gltf-loader';

/** Extension name */
const EXT_FEATURE_METADATA = 'EXT_feature_metadata';

export const name = EXT_FEATURE_METADATA;

export async function decode(gltfData: {json: GLTF}): Promise<void> {
export async function decode(gltfData: {json: GLTF}, options: GLTFLoaderOptions): Promise<void> {
const scenegraph = new GLTFScenegraph(gltfData);
decodeExtFeatureMetadata(scenegraph);
decodeExtFeatureMetadata(scenegraph, options);
}

/**
* Decodes feature metadata from extension
* @param scenegraph
*/
function decodeExtFeatureMetadata(scenegraph: GLTFScenegraph): void {
function decodeExtFeatureMetadata(scenegraph: GLTFScenegraph, options: GLTFLoaderOptions): void {
const extension: GLTF_EXT_feature_metadata | null = scenegraph.getExtension(EXT_FEATURE_METADATA);
if (!extension) return;

Expand All @@ -47,7 +48,7 @@ function decodeExtFeatureMetadata(scenegraph: GLTFScenegraph): void {
}

const {featureTextures} = extension;
if (schemaClasses && featureTextures) {
if (schemaClasses && featureTextures && options.gltf?.loadImages) {
for (const schemaName in schemaClasses) {
const schemaClass = schemaClasses[schemaName];
const featureTexture = findFeatureTextureByName(featureTextures, schemaName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ test('gltf#EXT_feature_metadata - Should handle feature texture attributes', asy
}
};

await decodeExtensions(GLTF_WITH_TEXTURES);
await decodeExtensions(GLTF_WITH_TEXTURES, {gltf: {loadImages: true}});

const expectedResult = {
buf: {
Expand Down
3 changes: 2 additions & 1 deletion modules/i3s/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export type {
ValueCount,
BuildingSceneSublayer,
DATA_TYPE,
OperationalLayer
OperationalLayer,
TextureSetDefinitionFormats
} from './types';

export {COORDINATE_SYSTEM} from './lib/parsers/constants';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
import type {
Tiles3DLoaderOptions,
Tiles3DTileContent,
Tiles3DTileJSONPostprocessed,
Tiles3DTilesetJSONPostprocessed
} from '@loaders.gl/3d-tiles';
import {load} from '@loaders.gl/core';
import {Tiles3DLoadOptions} from '../types';

/**
* Load nested 3DTiles tileset. If the sourceTile is not nested tileset - do nothing
* @param sourceTileset - source root tileset JSON
* @param sourceTile - source tile JSON that is supposed to has link to nested tileset
* @param globalLoadOptions - load options for Tiles3DLoader
* @param tilesetLoadOptions - load options for Tiles3DLoader
* @returns nothing
*/
export const loadNestedTileset = async (
sourceTileset: Tiles3DTilesetJSONPostprocessed | null,
sourceTile: Tiles3DTileJSONPostprocessed,
globalLoadOptions: Tiles3DLoadOptions
tilesetLoadOptions: Tiles3DLoaderOptions
): Promise<void> => {
const isTileset = sourceTile.type === 'json';
if (!sourceTileset || !sourceTile.contentUrl || !isTileset) {
return;
}

const loadOptions = {
...globalLoadOptions,
...tilesetLoadOptions,
[sourceTileset.loader.id]: {
isTileset,
assetGltfUpAxis: (sourceTileset.asset && sourceTileset.asset.gltfUpAxis) || 'Y'
Expand All @@ -41,22 +41,23 @@ export const loadNestedTileset = async (
* Load 3DTiles tile content, that includes glTF object
* @param sourceTileset - source root tileset JSON
* @param sourceTile - source tile JSON that has link to content data
* @param globalLoadOptions - load options for Tiles3DLoader
* @param tilesetLoadOptions - load options for Tiles3DLoader
* @returns - 3DTiles tile content or null
*/
export const loadTile3DContent = async (
sourceTileset: Tiles3DTilesetJSONPostprocessed | null,
sourceTile: Tiles3DTileJSONPostprocessed,
globalLoadOptions: Tiles3DLoadOptions
tilesetLoadOptions: Tiles3DLoaderOptions
): Promise<Tiles3DTileContent | null> => {
const isTileset = sourceTile.type === 'json';
if (!sourceTileset || !sourceTile.contentUrl || isTileset) {
return null;
}

const loadOptions = {
...globalLoadOptions,
...tilesetLoadOptions,
[sourceTileset.loader.id]: {
...(tilesetLoadOptions[sourceTileset.loader.id] || {}),
isTileset,
assetGltfUpAxis: (sourceTileset.asset && sourceTileset.asset.gltfUpAxis) || 'Y'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ export class NodeIndexDocument {
/** converter instance */
private converter: I3SConverter;

/**
* Finalized property. It means that all child nodes are saved and their data
* is unloaded
*/
private _finalized: boolean = false;
get finalized(): boolean {
return this._finalized;
}

/**
* Constructor
* @param id - id of the node in node pages
Expand Down Expand Up @@ -90,6 +99,9 @@ export class NodeIndexDocument {
* Add neighbors to child nodes of this node
*/
public async addNeighbors(): Promise<void> {
if (this.finalized) {
return;
}
const nodeData = await this.load();
for (const childNode of this.children) {
const childNodeData = await childNode.load();
Expand All @@ -116,9 +128,9 @@ export class NodeIndexDocument {
await childNode.write(childNodeData);
}
await childNode.save();
// The save after adding neighbors is the last one. Flush the the node
childNode.flush();
}
// The save after adding neighbors is the last one. Finalize the the node
this.finalize();
}

/** Save 3DNodeIndexDocument in file on disk */
Expand All @@ -128,6 +140,14 @@ export class NodeIndexDocument {
}
}

/** Finalize the node */
private finalize(): void {
this._finalized = true;
for (const child of this.children) {
child.flush();
}
}

/**
* Write 3DNodeIndexDocument https://github.com/Esri/i3s-spec/blob/master/docs/1.7/3DNodeIndexDocument.cmn.md
* @param node - Node3DIndexDocument object
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {Tiles3DTileContent, Tiles3DTileJSONPostprocessed} from '@loaders.gl/3d-tiles';
import {GltfPrimitiveModeString, PreprocessData} from '../types';
import {GLTF, GLTFLoader} from '@loaders.gl/gltf';
import {parse} from '@loaders.gl/core';

/**
* glTF primitive modes
* @see https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#_mesh_primitive_mode
*/
export const GLTF_PRIMITIVE_MODES = [
GltfPrimitiveModeString.POINTS, // 0
GltfPrimitiveModeString.LINES, // 1
GltfPrimitiveModeString.LINE_LOOP, // 2
GltfPrimitiveModeString.LINE_STRIP, // 3
GltfPrimitiveModeString.TRIANGLES, // 4
GltfPrimitiveModeString.TRIANGLE_STRIP, // 5
GltfPrimitiveModeString.TRIANGLE_FAN // 6
];

/**
* Analyze tile content. This function is used during preprocess stage of
* conversion
* @param tile - 3DTiles tile JSON metadata
* @param tileContent - 3DTiles tile content ArrayBuffer
* @returns
*/
export const analyzeTileContent = async (
tile: Tiles3DTileJSONPostprocessed,
tileContent: Tiles3DTileContent | null
): Promise<PreprocessData> => {
const result: PreprocessData = {
meshTopologyTypes: new Set()
};
if (!tileContent?.gltfArrayBuffer) {
return result;
}

const gltfData = await parse(tileContent.gltfArrayBuffer, GLTFLoader, {
gltf: {normalize: false, loadBuffers: false, loadImages: false, decompressMeshes: false}
});
const gltf = gltfData.json;

if (!gltf) {
return result;
}
const meshTypes = getMeshTypesFromGltf(gltf);
result.meshTopologyTypes = meshTypes;
return result;
};

/**
* Get mesh topology types that the glb content has
* @param gltfJson - JSON part of GLB content
* @returns array of mesh types found
*/
const getMeshTypesFromGltf = (gltfJson: GLTF): Set<GltfPrimitiveModeString> => {
const result: Set<GltfPrimitiveModeString> = new Set();
for (const mesh of gltfJson.meshes || []) {
for (const primitive of mesh.primitives) {
let {mode} = primitive;
if (typeof mode !== 'number') {
mode = 4; // Default is 4 - TRIANGLES
}
result.add(GLTF_PRIMITIVE_MODES[mode]);
}
}
return result;
};

/**
* Merge object2 into object1
* @param object1
* @param object2
* @returns nothing
*/
export const mergePreprocessData = (object1: PreprocessData, object2: PreprocessData): void => {
// Merge topology mesh types info
for (const type of object2.meshTopologyTypes) {
object1.meshTopologyTypes.add(type);
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {Tiles3DTileJSONPostprocessed} from '@loaders.gl/3d-tiles';
import {NodeIndexDocument} from './node-index-document';
import {Matrix4} from '@math.gl/core';

/** Traversal props for the conversion stage */
export type TraversalConversionProps = {
/** Transformation matrix for the specific tile */
transform: Matrix4;
/** Parent nodes of the converted tile. Multiple nodes can be if one tile is converted to multiple nodes*/
parentNodes: NodeIndexDocument[];
};

/**
* Travesal of 3DTile tiles tree with making specific actions with each tile
* @param tile - 3DTiles tile JSON metadata
* @param traversalProps - traversal props used to pass data through recursive calls
* @param processTile - callback to make some actions with the current tile
* @param postprocessTile - callback to make some action after processing of the current tile and all the subtree
* @param maxDepth - max recursive calls number the travesal function will do. If not set, the traversal function will
* go through all the tree.
* This value is used to limit the convertion with only partial number of levels of the tileset
* @param level - counter to keep recursive calls number of the tiles tree. This value used to be able to break
* traversal at the some level of the tree
* @returns void
*/
export const traverseDatasetWith = async <TProps>(
tile: Tiles3DTileJSONPostprocessed,
traversalProps: TProps,
processTile: (tile: Tiles3DTileJSONPostprocessed, traversalProps: TProps) => Promise<TProps>,
postprocessTile?: (processResults: TProps[], currentTraversalProps: TProps) => Promise<void>,
maxDepth?: number,
level = 0
): Promise<void> => {
if (maxDepth && level > maxDepth) {
return;
}
const processResults: TProps[] = [];
const newTraversalProps: TProps = await processTile(tile, traversalProps);
processResults.push(newTraversalProps);
for (const childTile of tile.children) {
await traverseDatasetWith(
childTile,
newTraversalProps,
processTile,
postprocessTile,
maxDepth,
level + 1
);
}
postprocessTile && (await postprocessTile(processResults, traversalProps));
};
Loading

0 comments on commit f82fc25

Please sign in to comment.