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

Micheld/gltf instances #7743

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
Draft
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
12 changes: 10 additions & 2 deletions common/config/rush/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions core/frontend/src/test/render/GraphicDescription.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ function expectRange(range: Readonly<Range3d>, translation: XYAndZ | undefined,
translation = { x: 0, y: 0, z: 0 };
}

expect(range.low.x).toEqual(lx - translation.x);
expect(range.low.y).toEqual(ly - translation.y);
expect(range.low.z).toEqual(lz - translation.z);
expect(range.high.x).toEqual(hx - translation.x);
expect(range.high.y).toEqual(hy - translation.y);
expect(range.high.z).toEqual(hz - translation.z);
expect(range.low.x).to.equal(lx);
expect(range.low.y).to.equal(ly);
expect(range.low.z).to.equal(lz);
expect(range.high.x).to.equal(hx);
expect(range.high.y).to.equal(hy);
expect(range.high.z).to.equal(hz);
}

function expectFeature(index: number, featureTable: RenderFeatureTable, expected: ModelFeature): void {
Expand Down
11 changes: 8 additions & 3 deletions test-apps/display-test-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@
},
"private": true,
"scripts": {
"build": "npm run -s build:frontend-vite && npm run -s build:backend",
"build": "npm run -s build:frontend-vite && npm run -s build:backend && npm run -s build:worker",
"build:worker": "tsc -p tsconfig.worker.json && npm run -s webpackWorkers && npm run -s copy:workers",
"build:stats": "cross-env OUTPUT_STATS=TRUE npm run build",
"build:backend": "tsc -p tsconfig.backend.json",
"build:backend-webpack": "backend-webpack-tools build -s ./lib/backend/DtaElectronMain.js -o ./lib/build/",
"build:frontend": "tsc",
"build:frontend-vite": "cross-env NODE_OPTIONS=--max_old_space_size=8192 vite build",
"build:bundle": "npm run build:frontend-vite && npm run build:backend-webpack",
"clean": "rimraf build lib .static-assets .rush/temp/package-deps*.json ios/imodeljs-test-app/build android/imodeljs-test-app/app/build android/imodeljs-test-app/app/src/main/assets",
"copy:workers": "cpx \"./lib/workers/webpack/*.js\" ./lib/scripts",
"docs": "",
"lint": "eslint \"./src/**/*.ts\" 1>&2",
"lint-fix": "eslint --fix -f visualstudio \"./src/**/*.ts\" 1>&2",
Expand All @@ -42,7 +44,8 @@
"ios:all": "npm run build:mobile && npm run build:ios-app && npm run ios",
"build:android-app": "cd android/imodeljs-test-app && ./gradlew --no-daemon build",
"android": "./scripts/runAndroidEmulator.py",
"android:all": "npm run build:mobile && npm run android"
"android:all": "npm run build:mobile && npm run android",
"webpackWorkers": "webpack --config ./src/frontend/workers/webpack.config.js"
},
"repository": {},
"dependencies": {
Expand Down Expand Up @@ -99,6 +102,7 @@
"express": "^4.20.0",
"express-ws": "^5.0.2",
"fs-extra": "^8.1.0",
"glob": "^10.3.12",
"internal-tools": "workspace:*",
"node-simctl": "~7.6.1",
"npm-run-all": "^4.1.5",
Expand All @@ -110,6 +114,7 @@
"rollup-plugin-visualizer": "^5.9.2",
"rollup-plugin-webpack-stats": "^0.2.0",
"rollup-plugin-external-globals": "0.11.0",
"source-map-loader": "^4.0.0",
"typescript": "~5.6.2",
"vite": "^5.4.6",
"vite-plugin-env-compatible": "^2.0.1",
Expand All @@ -126,4 +131,4 @@
"not dead",
"not <0.2%"
]
}
}
6 changes: 3 additions & 3 deletions test-apps/display-test-app/src/frontend/DisplayScale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
*--------------------------------------------------------------------------------------------*/

import { Matrix3d, Point3d, Transform } from "@itwin/core-geometry";
import { IModelApp, ModelDisplayTransformProvider, Tool } from "@itwin/core-frontend";
import { IModelApp, ModelDisplayTransform, ModelDisplayTransformProvider, Tool } from "@itwin/core-frontend";
import { parseArgs } from "@itwin/frontend-devtools";

class DisplayScaleTransformProvider implements ModelDisplayTransformProvider {
public constructor(private readonly _models: Set<string>, private readonly _scaleTransform: Transform) { }

public getModelDisplayTransform(modelId: string): Transform | undefined {
return this._models.has(modelId) ? this._scaleTransform.clone() : undefined;
public getModelDisplayTransform(modelId: string): ModelDisplayTransform | undefined {
return this._models.has(modelId) ? {transform:this._scaleTransform.clone()} : undefined;
}

public get transform(): Transform { return this._scaleTransform.clone(); }
Expand Down
239 changes: 239 additions & 0 deletions test-apps/display-test-app/src/frontend/GraphicCreator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

import { ColorDef } from "@itwin/core-common";
import { createWorkerProxy, GraphicBranch, GraphicType, HitDetail, IModelApp, IModelConnection, readGltfGraphics, readGltfTemplate, RenderGraphic, RenderInstancesParamsBuilder, ScreenViewport, TiledGraphicsProvider, TileTreeReference, Viewport } from "@itwin/core-frontend";
import { Arc3d, Point3d, Sphere, Transform } from "@itwin/core-geometry";
import { CreateCirclesArgs, GraphicCreator, GraphicCreatorResult } from "./workers/ExampleWorker";

class FeatureProvider implements TiledGraphicsProvider {
private static _instance?: FeatureProvider;
public trees: TileTreeReference[] = [];

private constructor(private _vp: Viewport) {
}

public static getInstance(vp: Viewport) {
if (this._instance === undefined) {
this._instance = new FeatureProvider(vp);
vp.addTiledGraphicsProvider(this._instance);
}
return this._instance;
}

public forEachTileTreeRef(_viewport: ScreenViewport, func: (ref: TileTreeReference) => void): void {
for (const tree of this.trees)
func(tree);
}
}

export function getCirclesData(center: Point3d) {
const scaleFactor = 5;
const xyzRadius = new Float64Array([
center.x, center.y, center.z, scaleFactor,
center.x + 100, center.y, center.z, scaleFactor,
// center.x + 400, center.y + 400, center.z, scaleFactor]);
center.x + 100, center.y, center.z+50, scaleFactor]);

return {
xyzRadius,
colors: new Uint32Array([ColorDef.from(255, 0 , 0, 150).tbgr, ColorDef.from(0, 0 , 255, 150).tbgr, ColorDef.from(0, 255, 0, 150).tbgr]),
};
}



export async function testGraphicCreatorMain(vp: Viewport) {
// vp.viewFlags = vp.viewFlags.copy({ renderMode: RenderMode.SmoothShade });
// vp.viewFlags = vp.viewFlags.withRenderMode(RenderMode.SmoothShade);

const gltfUrl = "https://publisher.orbitgt.com/objects/bollard.gltf";
// const gltfUrl = "https://publisher.orbitgt.com/objects/cone.gltf";
// const gltfUrl = "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/refs/heads/main/2.0/Duck/glTF-Embedded/Duck.gltf";
// const gltfUrl = "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/refs/heads/main/Models/CesiumMan/glTF/CesiumMan.gltf";
const jsonData = await (await fetch(gltfUrl)).json();
const baseUrl = new URL(gltfUrl, window.location.href);

const getGltf = async (transform?: Transform) => {
return readGltfGraphics({
gltf: jsonData,
iModel: vp.iModel,
baseUrl,
transform,
});
}
const gltf = await readGltfGraphics({
gltf: jsonData,
iModel: vp.iModel,
baseUrl
});
//

const useInstancing = true;
const readGltfForEachInstance = true; // only used when 'useInstancing' is false
const gltfTemplate =
useInstancing
? await readGltfTemplate({
gltf: jsonData,
iModel: vp.iModel,
baseUrl })
: undefined;

const modelId = vp.iModel.transientIds.getNext();
const instanceBuilder = RenderInstancesParamsBuilder.create({modelId});

const builder = IModelApp.renderSystem.createGraphic({
type: GraphicType.Scene,
computeChordTolerance: () => 0.01,
pickable: {
modelId,
id: modelId,
},
});

const circlesData = getCirclesData(vp.view.getCenter());
const circles = circlesData.xyzRadius;
const colors = circlesData.colors;

const numCircles = circlesData.xyzRadius.length / 4;

const mainBranch = new GraphicBranch();
// Add each circle to the builder.
for (let i = 0; i < numCircles; i++) {
// Set the next circle's color.
const color = ColorDef.fromJSON(colors[i]);
builder.setSymbology(color, color, 1);

// Assign a unique Id to the circle so it can be interacted with by the user.
const circleId = vp.iModel.transientIds.getNext();
builder.activatePickableId(circleId);

// Add the circle to the builder.
const offset = i * 4;
const position = new Point3d(circles[offset], circles[offset + 1], circles[offset + 2]);
const scaleFactor = circles[offset + 3];
const radius = circles[offset + 3];
const sphere = Sphere.createCenterRadius(position, radius);
builder.addSolidPrimitive(sphere);
const circle = Arc3d.createXY(position, radius);
builder.addArc(circle, true, true);
mainBranch.add(builder.finish());

if (gltfTemplate) {
const transform = Transform.createTranslation(position);
// const transform = Transform.createScaleAboutPoint(center, scaleFactor).multiplyTransformTransform(Transform.createTranslation(center)),
// const transform = Transform.createIdentity()
instanceBuilder.add({transform, feature: vp.iModel.transientIds.getNext()});
} else {
if (readGltfForEachInstance) {
// No instancing; read gltf from source each time
// NO ISSUES
const transform = Transform.createScaleAboutPoint(position, scaleFactor).multiplyTransformTransform(Transform.createTranslation(position));
const gltfGraphic = await getGltf(transform)
if (gltfGraphic !== undefined)
mainBranch.add(gltfGraphic)
} else {
// // No instancing; re-use gltf graphic
// // NOTHING DISPLAYED
// const gltfBranch = new GraphicBranch();
// gltfBranch.add(gltf)
// const transform = Transform.createScaleAboutPoint(center, scaleFactor).multiplyTransformTransform(Transform.createTranslation(center));
// const gltfGraphic = IModelApp.renderSystem.createGraphicBranch(mainBranch, transform);
// mainBranch.add(gltfGraphic)
}
}
}


// if (gltf !== undefined) {
// branch.add(gltf);
// }

let graphic: RenderGraphic|undefined;
if (gltfTemplate) {
const instancesParams = instanceBuilder.finish();
const renderInstances = IModelApp.renderSystem.createRenderInstances(instancesParams);
const instancesGraphic = IModelApp.renderSystem.createGraphicFromTemplate({ template : gltfTemplate.template, instances: renderInstances });
mainBranch.add(instancesGraphic);
// graphic = IModelApp.renderSystem.createGraphicBranch(branch);
// const center = vp.view.getCenter()
// const transform = Transform.createTranslation(new Point3d(center.x, center.y, center.z+20));
const transform = Transform.createIdentity();
graphic = IModelApp.renderSystem.createGraphicBranch(mainBranch, transform);

} else {
// graphic = IModelApp.renderSystem.createGraphicBranch(branch);
// const transform = Transform.createTranslation(vp.view.getCenter());
const transform = Transform.createIdentity();
graphic = IModelApp.renderSystem.createGraphicBranch(mainBranch, transform);
}




const inst = FeatureProvider.getInstance(vp);
inst.trees.push(TileTreeReference.createFromRenderGraphic({
graphic,
iModel: vp.iModel,
modelId,
getToolTip: async (hit: HitDetail) => {
return `sourceId: ${hit.sourceId}`;
},
}));
// vp.invalidateRenderPlan();
// vp.invalidateSymbologyOverrides();
}


export async function testGraphicCreator(vp: Viewport) {
// Instantiate a reusable WorkerProxy for use by the createCircleGraphic function.
const worker = createWorkerProxy<GraphicCreator>("./lib/scripts/ExampleWorker.js");

// Create a render graphic from a description of a large number of circles, using a WorkerProxy.
async function createCircleGraphic(xyzRadius: Float64Array, color: Uint32Array, chordTolerance: number, iModel: IModelConnection): Promise<GraphicCreatorResult> {
// Package up the RenderSystem's context to be sent to the Worker.
const workerContext = IModelApp.renderSystem.createWorkerGraphicDescriptionContextProps(iModel);

// Transfer the ArrayBuffers to the Worker, instead of making copies.
const transfer: Transferable[] = [xyzRadius.buffer, color.buffer];

// Obtain a GraphicDescription from the Worker.
const args: CreateCirclesArgs = {
xyzRadius,
color,
chordTolerance,
context: workerContext,
};

const result = worker.createCircles(args, transfer);
return result;
}

const circlesData = getCirclesData(vp.view.getCenter());
const graphicResult = await createCircleGraphic(circlesData.xyzRadius, circlesData.colors, 0.01, vp.iModel);

// Unpackage the context from the Worker.
const context = await IModelApp.renderSystem.resolveGraphicDescriptionContext(graphicResult.context, vp.iModel);

// Convert the GraphicDescription into a RenderGraphic.
const graphic = IModelApp.renderSystem.createGraphicFromDescription({
description: graphicResult.description,
context,
});

if(graphic === undefined)
return;

const inst = FeatureProvider.getInstance(vp);

inst.trees.push(TileTreeReference.createFromRenderGraphic({
graphic,
iModel: vp.iModel,
modelId: graphicResult.modelId,
getToolTip: async (hit: HitDetail) => {
return `sourceId: ${hit.sourceId}`;
},
}));
}
Loading