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

Obtain Base Url for Texture Requests within Reality Tile Loader #7450

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 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
7 changes: 7 additions & 0 deletions common/api/core-frontend.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2389,6 +2389,9 @@ export function createOrbitGtTileTreeReference(props: OrbitGtTileTree.ReferenceP
// @internal (undocumented)
export function createPrimaryTileTreeReference(view: ViewState, model: GeometricModelState): PrimaryTreeReference;

// @internal
export function createReaderPropsWithBaseUrl(streamBuffer: ByteStream | Uint8Array, yAxisUp: boolean, baseUrl?: string): GltfReaderProps | undefined;

// @internal (undocumented)
export function createRealityTileTreeReference(props: RealityModelTileTree.ReferenceProps): RealityModelTileTree.Reference;

Expand Down Expand Up @@ -9555,6 +9558,8 @@ export class RealityTileTree extends TileTree {
get parentsAndChildrenExclusive(): boolean;
// @internal (undocumented)
prune(): void;
// @internal (undocumented)
readonly rdSourceId?: string;
// @internal
reportTileVisibility(_args: TileDrawArgs, _selected: RealityTile[]): void;
// @internal (undocumented)
Expand Down Expand Up @@ -9584,6 +9589,8 @@ export interface RealityTileTreeParams extends TileTreeParams {
// (undocumented)
readonly loader: RealityTileLoader;
// (undocumented)
readonly rdSourceId?: string;
// (undocumented)
readonly rootTile: RealityTileParams;
// (undocumented)
readonly rootToEcef?: Transform;
Expand Down
1 change: 1 addition & 0 deletions common/api/summary/core-frontend.exports.csv
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ internal;function;createMaskTreeReference
internal;function;createModelMapLayerTileTreeReference
internal;function;createOrbitGtTileTreeReference
internal;function;createPrimaryTileTreeReference
internal;function;createReaderPropsWithBaseUrl
internal;function;createRealityTileTreeReference
beta;interface;CreateRenderInstancesParamsBuilderArgs
public;interface;CreateRenderMaterialArgs
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/core-frontend",
"comment": "",
"type": "none"
}
],
"packageName": "@itwin/core-frontend"
}
96 changes: 96 additions & 0 deletions core/frontend/src/test/tile/RealityTileLoader.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { describe, expect, it } from "vitest";
import { createReaderPropsWithBaseUrl } from "../../tile/RealityTileLoader";
import { GltfV2ChunkTypes, GltfVersions, TileFormat } from "@itwin/core-common";

const minimalBin = new Uint8Array([12, 34, 0xfe, 0xdc]);
const minimalJson = { asset: { version: "02.00" }, meshes: [] };

function jsonToBytes(json: object, alignment = 4): Uint8Array {
let str = JSON.stringify(json);
while (str.length % alignment !== 0)
str += " ";

const bytes = new TextEncoder().encode(str);
expect(bytes.length).toEqual(str.length); // pure ASCII
return bytes;
}

function setHeader(data: Uint8Array | DataView, length: number, format = TileFormat.Gltf, version = GltfVersions.Version2): void {
if (data instanceof Uint8Array)
data = new DataView(data.buffer);

data.setUint32(0, format, true);
data.setUint32(4, version, true);
data.setUint32(8, length, true);
}

interface Chunk {
len?: number;
type: number;
data: Uint8Array;
}

interface Header {
len?: number;
format: number;
version: number;
}
function glbFromChunks(chunks: Chunk[], header?: Header): Uint8Array {
let numBytes = 12;
for (const chunk of chunks)
numBytes += 8 + (chunk.len ?? chunk.data.length);

const glb = new Uint8Array(numBytes);
const view = new DataView(glb.buffer);

header = header ?? { format: TileFormat.Gltf, version: GltfVersions.Version2 };
setHeader(view, header.len ?? numBytes, header.format, header.version);

let chunkStart = 12;
for (const chunk of chunks) {
view.setUint32(chunkStart + 0, chunk.len ?? chunk.data.length, true);
view.setUint32(chunkStart + 4, chunk.type, true);
glb.set(chunk.data, chunkStart + 8);
chunkStart += chunk.data.length + 8;
}

return glb;
}

function makeGlb(json: object | undefined, binary?: Uint8Array, header?: Header): Uint8Array {
const chunks = [];
if (json)
chunks.push({ type: GltfV2ChunkTypes.JSON, data: jsonToBytes(json) });

if (binary)
chunks.push({ type: GltfV2ChunkTypes.Binary, data: binary });

return glbFromChunks(chunks, header);
}

describe("createReaderPropsWithBaseUrl", () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests don't prove much. A much more valuable test would confirm that you successfully resolve a resource inside the glTF with a relative URL where prior to your change you would have failed to do so.

const glb = makeGlb(minimalJson, minimalBin);
it("should add a valid base url to the reader props", {}, () => {
let props = createReaderPropsWithBaseUrl(glb, false, "http://localhost:8080/tileset.json");
expect(props?.baseUrl?.toString()).to.equal("http://localhost:8080/tileset.json");

props = createReaderPropsWithBaseUrl(glb, false, "https://some-blob-storage.com/tileset.json?with-some-query-params");
expect(props?.baseUrl?.toString()).to.equal("https://some-blob-storage.com/tileset.json?with-some-query-params");
});

it("should not add an invalid base url to the reader props", {}, () => {
let props = createReaderPropsWithBaseUrl(glb, false, "someIdThatIsNotAUrl");
expect(props?.baseUrl).to.be.undefined;

props = createReaderPropsWithBaseUrl(glb, false, "");
expect(props?.baseUrl).to.be.undefined;

props = createReaderPropsWithBaseUrl(glb, false, "textures/someRelativePath");
expect(props?.baseUrl).to.be.undefined;
});
});

6 changes: 4 additions & 2 deletions core/frontend/src/tile/RealityModelTileTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,12 +269,13 @@ class RealityModelTileTreeParams implements RealityTileTreeParams {
public is3d = true;
public loader: RealityModelTileLoader;
public rootTile: RealityTileParams;
public rdSourceId?: string;

public get location() { return this.loader.tree.location; }
public get yAxisUp() { return this.loader.tree.yAxisUp; }
public get priority() { return this.loader.priority; }

public constructor(tileTreeId: string, iModel: IModelConnection, modelId: Id64String, loader: RealityModelTileLoader, public readonly gcsConverterAvailable: boolean, public readonly rootToEcef: Transform | undefined) {
public constructor(tileTreeId: string, iModel: IModelConnection, modelId: Id64String, loader: RealityModelTileLoader, public readonly gcsConverterAvailable: boolean, public readonly rootToEcef: Transform | undefined, rdSourceId?: string) {
this.loader = loader;
this.id = tileTreeId;
this.modelId = modelId;
Expand All @@ -287,6 +288,7 @@ class RealityModelTileTreeParams implements RealityTileTreeParams {
additiveRefinement: undefined !== refine ? "ADD" === refine : undefined,
usesGeometricError: loader.tree.rdSource.usesGeometricError,
});
this.rdSourceId = rdSourceId;
}
}

Expand Down Expand Up @@ -711,7 +713,7 @@ export namespace RealityModelTileTree {
const props = await getTileTreeProps(rdSource, tilesetToDb, iModel);
const loader = new RealityModelTileLoader(props, new BatchedTileIdMap(iModel), opts);
const gcsConverterAvailable = await getGcsConverterAvailable(iModel);
const params = new RealityModelTileTreeParams(tileTreeId, iModel, modelId, loader, gcsConverterAvailable, props.tilesetToEcef);
const params = new RealityModelTileTreeParams(tileTreeId, iModel, modelId, loader, gcsConverterAvailable, props.tilesetToEcef, rdSource.key.id);
pmconne marked this conversation as resolved.
Show resolved Hide resolved
return new RealityModelTileTree(params);
}
return undefined;
Expand Down
27 changes: 24 additions & 3 deletions core/frontend/src/tile/RealityTileLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { ScreenViewport, Viewport } from "../Viewport";
import { GltfWrapMode } from "../common/gltf/GltfSchema";
import {
B3dmReader, BatchedTileIdMap, createDefaultViewFlagOverrides, GltfGraphicsReader, GltfReader, GltfReaderProps, I3dmReader, ImdlReader, readPointCloudTileContent,
RealityTile, RealityTileContent, Tile, TileContent, TileDrawArgs, TileLoadPriority, TileRequest, TileRequestChannel, TileUser,
RealityTile, RealityTileContent, RealityTileTree, Tile, TileContent, TileDrawArgs, TileLoadPriority, TileRequest, TileRequestChannel, TileUser,
} from "./internal";

const defaultViewFlagOverrides = createDefaultViewFlagOverrides({});
Expand Down Expand Up @@ -143,7 +143,9 @@ export abstract class RealityTileLoader {
reader = I3dmReader.create(streamBuffer, iModel, modelId, is3d, tile.contentRange, system, yAxisUp, tile.isLeaf, isCanceled, undefined, this.wantDeduplicatedVertices);
break;
case TileFormat.Gltf:
const props = GltfReaderProps.create(streamBuffer.nextBytes(streamBuffer.arrayBuffer.byteLength), yAxisUp);
const tree = tile.tree as RealityTileTree;
const baseUrl = tree.rdSourceId;
const props = createReaderPropsWithBaseUrl(streamBuffer, yAxisUp, baseUrl);
if (props) {
reader = new GltfGraphicsReader(props, {
iModel,
Expand All @@ -155,7 +157,6 @@ export abstract class RealityTileLoader {
idMap: this.getBatchIdMap(),
});
}

break;
case TileFormat.Cmpt:
const header = new CompositeTileHeader(streamBuffer);
Expand Down Expand Up @@ -242,3 +243,23 @@ export abstract class RealityTileLoader {
return minDistance;
}
}

/** Exposed strictly for testing purposes.
* @internal
*/
export function createReaderPropsWithBaseUrl(streamBuffer: ByteStream | Uint8Array, yAxisUp: boolean, baseUrl?: string): GltfReaderProps | undefined {
if(streamBuffer instanceof ByteStream) {
return (!baseUrl || !isValidURL(baseUrl)) ? GltfReaderProps.create(streamBuffer.nextBytes(streamBuffer.arrayBuffer.byteLength), yAxisUp) : GltfReaderProps.create(streamBuffer.nextBytes(streamBuffer.arrayBuffer.byteLength), yAxisUp, new URL(baseUrl));
}
return (!baseUrl || !isValidURL(baseUrl)) ? GltfReaderProps.create(streamBuffer, yAxisUp) : GltfReaderProps.create(streamBuffer, yAxisUp, new URL(baseUrl));

}

function isValidURL(url: string){
try {
new URL(url);
} catch {
return false;
}
return true;
}
andremig-bentley marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 4 additions & 0 deletions core/frontend/src/tile/RealityTileTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ export interface RealityTileTreeParams extends TileTreeParams {
readonly rootTile: RealityTileParams;
readonly rootToEcef?: Transform;
readonly gcsConverterAvailable: boolean;
readonly rdSourceId?: string;
}

/** Base class for a [[TileTree]] representing a reality model (e.g., a point cloud or photogrammetry mesh) or 3d terrain with map imagery.
Expand All @@ -189,6 +190,8 @@ export class RealityTileTree extends TileTree {
protected _rootToEcef?: Transform;
/** @internal */
protected _ecefToDb?: Transform;
/** @internal */
public readonly rdSourceId?: string;

/** @internal */
public constructor(params: RealityTileTreeParams) {
Expand All @@ -207,6 +210,7 @@ export class RealityTileTree extends TileTree {
this._ecefToDb = dbToEcef.inverse();
}
}
this.rdSourceId = params.rdSourceId;
}

/** The mapping of per-feature JSON properties from this tile tree's batch table, if one is defined.
Expand Down
Loading