Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(tile-converter): i3s-converter - mitigate "tiles" module dependency #2517

Merged
merged 4 commits into from
Jun 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions modules/3d-tiles/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ export {getIonTilesetMetadata as _getIonTilesetMetadata} from './lib/ion/ion';
export type {
FeatureTableJson,
B3DMContent,
Tile3DBoundingVolume,
Tiles3DTileJSON,
Tiles3DTileJSONPostprocessed,
Tiles3DTilesetJSON,
Tiles3DTilesetJSONPostprocessed,
Tiles3DTileContent,
ImplicitTilingExensionData
} from './types';
6 changes: 5 additions & 1 deletion modules/3d-tiles/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ export type Tiles3DTilesetJSON = {
extensions?: object;
/** Application-specific data. */
extras?: any;
/** Not mentioned in 1.0 spec but some tilesets contain this option */
gltfUpAxis?: string;
};
/** A dictionary object of metadata about per-feature properties. */
properties?: Record<string, TilesetProperty>;
Expand Down Expand Up @@ -128,7 +130,7 @@ export type Tiles3DTileJSON = {
implicitTiling?: ImplicitTilingData;
};

export type Tiles3DTileJSONPostprocessed = Omit<Tiles3DTileJSON, 'refine'> & {
export type Tiles3DTileJSONPostprocessed = Omit<Tiles3DTileJSON, 'refine' | 'children'> & {
/** Unique ID */
id?: string;
/** Content full URL */
Expand All @@ -146,6 +148,8 @@ export type Tiles3DTileJSONPostprocessed = Omit<Tiles3DTileJSON, 'refine'> & {
* The default is to inherit from the parent tile.
*/
refine?: TILE_REFINEMENT | string;
/** An array of objects that define child tiles. */
children: Tiles3DTileJSONPostprocessed[];
};

/** Metadata about the tile's content and a link to the content. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,35 @@ import {
makeBoundingSphereFromPoints,
BoundingSphere
} from '@math.gl/culling';
import {Tile3D} from '@loaders.gl/tiles';
import {Geoid} from '@math.gl/geoid';

/**
* Create bounding volumes object from tile and geoid height model.
* @param tile
* @param geoidHeightModel
* @param sourceBoundingVolume - initialized bounding volume of the source tile
* @param geoidHeightModel - instance of Geoid class that converts elevation from geoidal to ellipsoidal and back
* @returns - Bounding volumes object
*/
export function createBoundingVolumes(tile: Tile3D, geoidHeightModel: Geoid): BoundingVolumes {
export function createBoundingVolumes(
sourceBoundingVolume: OrientedBoundingBox | BoundingSphere,
geoidHeightModel: Geoid
): BoundingVolumes {
let radius;
let halfSize;
let quaternion;

const boundingVolume = tile.boundingVolume;
const cartographicCenter = Ellipsoid.WGS84.cartesianToCartographic(
boundingVolume.center,
sourceBoundingVolume.center,
new Vector3()
);
cartographicCenter[2] =
cartographicCenter[2] -
geoidHeightModel.getHeight(cartographicCenter[1], cartographicCenter[0]);
if (boundingVolume instanceof OrientedBoundingBox) {
halfSize = boundingVolume.halfSize;
if (sourceBoundingVolume instanceof OrientedBoundingBox) {
halfSize = sourceBoundingVolume.halfSize;
radius = new Vector3(halfSize[0], halfSize[1], halfSize[2]).len();
quaternion = boundingVolume.quaternion;
quaternion = sourceBoundingVolume.quaternion;
} else {
radius = tile.boundingVolume.radius;
radius = sourceBoundingVolume.radius;
halfSize = [radius, radius, radius];
quaternion = new Quaternion()
.fromMatrix3(new Matrix3([halfSize[0], 0, 0, 0, halfSize[1], 0, 0, 0, halfSize[2]]))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {B3DMContent, FeatureTableJson} from '@loaders.gl/3d-tiles';
import type {FeatureTableJson, Tiles3DTileContent} from '@loaders.gl/3d-tiles';
import type {
GLTF_EXT_feature_metadata,
GLTF_EXT_mesh_features,
Expand Down Expand Up @@ -52,6 +52,7 @@ import {GL} from '@loaders.gl/math';
*/
import type {TypedArrayConstructor} from '../types';
import {generateSyntheticIndices} from '../../lib/utils/geometry-utils';
import {BoundingSphere, OrientedBoundingBox} from '@math.gl/culling';

// Spec - https://github.com/Esri/i3s-spec/blob/master/docs/1.7/pbrMetallicRoughness.cmn.md
const DEFAULT_ROUGHNESS_FACTOR = 1;
Expand Down Expand Up @@ -81,6 +82,9 @@ let scratchVector = new Vector3();
* Convert binary data from b3dm file to i3s resources
*
* @param tileContent - 3d tile content
* @param tileTransform - transformation matrix of the tile, calculated recursively multiplying
* transform of all parent tiles and transform of the current tile
* @param tileBoundingVolume - initialized bounding volume of the source tile
* @param addNodeToNodePage - function to add new node to node pages
* @param propertyTable - batch table (corresponding to feature attributes data)
* @param featuresHashArray - hash array of features that is needed to not to mix up same features in parent and child nodes
Expand All @@ -93,7 +97,9 @@ let scratchVector = new Vector3();
* @returns Array of node resources to create one or more i3s nodes
*/
export default async function convertB3dmToI3sGeometry(
tileContent: B3DMContent,
tileContent: Tiles3DTileContent,
tileTransform: Matrix4,
tileBoundingVolume: OrientedBoundingBox | BoundingSphere,
addNodeToNodePage: () => Promise<number>,
propertyTable: FeatureTableJson | null,
featuresHashArray: string[],
Expand All @@ -110,7 +116,11 @@ export default async function convertB3dmToI3sGeometry(
shouldMergeMaterials
);

const dataForAttributesConversion = prepareDataForAttributesConversion(tileContent);
const dataForAttributesConversion = prepareDataForAttributesConversion(
tileContent,
tileTransform,
tileBoundingVolume
);
const convertedAttributesMap: Map<string, ConvertedAttributes> = await convertAttributes(
dataForAttributesConversion,
materialAndTextureList,
Expand Down Expand Up @@ -198,7 +208,7 @@ function _generateBoundingVolumesFromGeometry(
* @param params.convertedAttributes - Converted geometry attributes
* @param params.material - I3S PBR-like material definition
* @param params.texture - texture content
* @param params.tileContent - B3DM decoded content
* @param params.tileContent - 3DTiles decoded content
* @param params.nodeId - new node ID
* @param params.featuresHashArray - hash array of features that is needed to not to mix up same features in parent and child nodes
* @param params.propertyTable - batch table (corresponding to feature attributes data)
Expand All @@ -222,7 +232,7 @@ async function _makeNodeResources({
convertedAttributes: ConvertedAttributes;
material: I3SMaterialDefinition;
texture?: {};
tileContent: B3DMContent;
tileContent: Tiles3DTileContent;
nodeId: number;
featuresHashArray: string[];
propertyTable: FeatureTableJson | null;
Expand Down Expand Up @@ -1545,10 +1555,14 @@ function generateFeatureIndexAttribute(
/**
* Find property table in tile
* For example it can be batchTable for b3dm files or property table in gLTF extension.
* @param sourceTile
* @param tileContent - 3DTiles tile content
* @return batch table from b3dm / feature properties from EXT_FEATURE_METADATA
*/
export function getPropertyTable(tileContent: B3DMContent): FeatureTableJson | null {
export function getPropertyTable(tileContent: Tiles3DTileContent | null): FeatureTableJson | null {
if (!tileContent) {
return null;
}

const batchTableJson = tileContent?.batchTableJson;

if (batchTableJson) {
Expand All @@ -1572,10 +1586,10 @@ export function getPropertyTable(tileContent: B3DMContent): FeatureTableJson | n

/**
* Check extensions which can be with property table inside.
* @param sourceTile
* @param tileContent - 3DTiles tile content
*/
function getPropertyTableExtension(
tileContent: B3DMContent
tileContent: Tiles3DTileContent
): GLTF_EXT_feature_metadata | GLTF_EXT_mesh_features {
const extensionsWithPropertyTables = [EXT_FEATURE_METADATA, EXT_MESH_FEATURES];
const extensionsUsed = tileContent?.gltf?.extensionsUsed;
Expand Down
105 changes: 84 additions & 21 deletions modules/tile-converter/src/i3s-converter/helpers/gltf-attributes.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,27 @@
import type {B3DMContent} from '@loaders.gl/3d-tiles';
import type {Tiles3DTileContent} from '@loaders.gl/3d-tiles';
import type {GLTFAccessorPostprocessed, GLTFNodePostprocessed} from '@loaders.gl/gltf';
import type {B3DMAttributesData} from '../../i3s-attributes-worker';
import {Matrix4, Vector3} from '@math.gl/core';
import {BoundingSphere, OrientedBoundingBox} from '@math.gl/culling';
import {Ellipsoid} from '@math.gl/geospatial';

type AttributesObject = {
[k: string]: GLTFAccessorPostprocessed;
};

/**
* Keep only values for B3DM attributes to pass data to worker thread.
* @param attributes
*/
function getB3DMAttributesWithoutBufferView(attributes: AttributesObject): AttributesObject {
const attributesWithoutBufferView = {};

for (const attributeName in attributes) {
attributesWithoutBufferView[attributeName] = {
value: attributes[attributeName].value
};
}

return attributesWithoutBufferView;
}

/**
* Prepare attributes for conversion to avoid binary data breaking in worker thread.
* @param tileContent
* @param tileContent - 3DTiles tile content
* @param tileTransform - transformation matrix of the tile, calculated recursively multiplying
* transform of all parent tiles and transform of the current tile
* @param boundingVolume - initialized bounding volume of the source tile
* @returns
*/
export function prepareDataForAttributesConversion(tileContent: B3DMContent): B3DMAttributesData {
export function prepareDataForAttributesConversion(
tileContent: Tiles3DTileContent,
tileTransform: Matrix4,
boundingVolume: OrientedBoundingBox | BoundingSphere
): B3DMAttributesData {
let nodes =
tileContent.gltf?.scene?.nodes ||
tileContent.gltf?.scenes?.[0]?.nodes ||
Expand Down Expand Up @@ -56,8 +50,11 @@ export function prepareDataForAttributesConversion(tileContent: B3DMContent): B3

prepareNodes(nodes);

const cartographicOrigin = tileContent.cartographicOrigin;
const cartesianModelMatrix = tileContent.cartesianModelMatrix;
const {cartographicOrigin, modelMatrix: cartesianModelMatrix} = calculateTransformProps(
tileContent,
tileTransform,
boundingVolume
);

return {
nodes,
Expand All @@ -67,6 +64,72 @@ export function prepareDataForAttributesConversion(tileContent: B3DMContent): B3
};
}

/**
* Keep only values for glTF attributes to pass data to worker thread.
* @param attributes - geometry attributes
*/
function getB3DMAttributesWithoutBufferView(attributes: AttributesObject): AttributesObject {
const attributesWithoutBufferView = {};

for (const attributeName in attributes) {
attributesWithoutBufferView[attributeName] = {
value: attributes[attributeName].value
};
}

return attributesWithoutBufferView;
}

/**
* Calculate transformation properties to transform vertex attributes (POSITION, NORMAL, etc.)
* from METER_OFFSET coorditantes to LNGLAT_OFFSET coordinates
* @param tileContent - 3DTiles tile content
* @param tileTransform - transformation matrix of the tile, calculated recursively multiplying
* transform of all parent tiles and transform of the current tile
* @param boundingVolume - initialized bounding volume of the source tile
* @returns modelMatrix - transformation matrix to transform coordinates to cartographic coordinates
* cartographicOrigin - tile origin coordinates to calculate offsets
*/
export function calculateTransformProps(
tileContent: Tiles3DTileContent,
tileTransform: Matrix4,
boundingVolume: OrientedBoundingBox | BoundingSphere
): {modelMatrix: Matrix4; cartographicOrigin: Vector3} {
const {rtcCenter, gltfUpAxis} = tileContent;
const {center} = boundingVolume;

let modelMatrix = new Matrix4(tileTransform);

// Translate if appropriate
if (rtcCenter) {
modelMatrix.translate(rtcCenter);
}

// glTF models need to be rotated from Y to Z up
// https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification#y-up-to-z-up
switch (gltfUpAxis) {
case 'Z':
break;
case 'Y':
const rotationY = new Matrix4().rotateX(Math.PI / 2);
modelMatrix = modelMatrix.multiplyRight(rotationY);
break;
case 'X':
const rotationX = new Matrix4().rotateY(-Math.PI / 2);
modelMatrix = modelMatrix.multiplyRight(rotationX);
break;
default:
break;
}

const cartesianOrigin = new Vector3(center);
const cartographicOrigin = Ellipsoid.WGS84.cartesianToCartographic(
cartesianOrigin,
new Vector3()
);
return {modelMatrix, cartographicOrigin};
}

/**
* Traverse all nodes to replace all sensible data with copy to avoid data corruption in worker.
* @param nodes
Expand Down
67 changes: 67 additions & 0 deletions modules/tile-converter/src/i3s-converter/helpers/load-3d-tiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type {
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
* @returns nothing
*/
export const loadNestedTileset = async (
sourceTileset: Tiles3DTilesetJSONPostprocessed | null,
sourceTile: Tiles3DTileJSONPostprocessed,
globalLoadOptions: Tiles3DLoadOptions
): Promise<void> => {
const isTileset = sourceTile.type === 'json';
if (!sourceTileset || !sourceTile.contentUrl || !isTileset) {
return;
}

const loadOptions = {
...globalLoadOptions,
[sourceTileset.loader.id]: {
isTileset,
assetGltfUpAxis: (sourceTileset.asset && sourceTileset.asset.gltfUpAxis) || 'Y'
}
};
const tileContent = await load(sourceTile.contentUrl, sourceTileset.loader, loadOptions);

if (tileContent.root) {
sourceTile.children = [tileContent.root];
}
};

/**
* 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
* @returns - 3DTiles tile content or null
*/
export const loadTile3DContent = async (
sourceTileset: Tiles3DTilesetJSONPostprocessed | null,
sourceTile: Tiles3DTileJSONPostprocessed,
globalLoadOptions: Tiles3DLoadOptions
): Promise<Tiles3DTileContent | null> => {
const isTileset = sourceTile.type === 'json';
if (!sourceTileset || !sourceTile.contentUrl || isTileset) {
return null;
}

const loadOptions = {
...globalLoadOptions,
[sourceTileset.loader.id]: {
isTileset,
assetGltfUpAxis: (sourceTileset.asset && sourceTileset.asset.gltfUpAxis) || 'Y'
}
};
const tileContent = await load(sourceTile.contentUrl, sourceTileset.loader, loadOptions);

return tileContent;
};
Loading
Loading