Skip to content

Commit

Permalink
Viewer: fix camera-orbit and camera-target attributes (#16116)
Browse files Browse the repository at this point in the history
* Improve handling of for camera interpolation while loading

* Add some initial automated tests
  • Loading branch information
ryantrem authored Jan 28, 2025
1 parent 0e23e9b commit cbe8bad
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 12 deletions.
24 changes: 14 additions & 10 deletions packages/tools/viewer/src/viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import { registerBuiltInLoaders } from "loaders/dynamic";
const toneMappingOptions = ["none", "standard", "aces", "neutral"] as const;
export type ToneMapping = (typeof toneMappingOptions)[number];

export type LoadModelOptions = LoadAssetContainerOptions & {
type UpdateModelOptions = {
/**
* The default animation index.
*/
Expand All @@ -58,6 +58,8 @@ export type LoadModelOptions = LoadAssetContainerOptions & {
animationAutoPlay?: boolean;
};

export type LoadModelOptions = LoadAssetContainerOptions & UpdateModelOptions;

export type CameraAutoOrbit = {
/**
* Whether the camera should automatically orbit around the model when idle.
Expand Down Expand Up @@ -705,15 +707,21 @@ export class Viewer implements IDisposable {
return this._modelInfo;
}

protected _setModel(...args: [model: null] | [model: Model, source?: string | File | ArrayBufferView]): void {
const [model, source] = args;
protected _setModel(
...args: [model: null] | [model: Model, options?: UpdateModelOptions & Partial<{ source: string | File | ArrayBufferView; interpolateCamera: boolean }>]
): void {
const [model, options] = args;
if (model !== this._modelInfo) {
this._modelInfo = model;
this._updateCamera(true);
this._updateLight();
this._applyAnimationSpeed();
this._selectAnimation(options?.defaultAnimation ?? 0, false);
if (options?.animationAutoPlay) {
this.playAnimation();
}
this.onSelectedMaterialVariantChanged.notifyObservers();
this.onModelChanged.notifyObservers(source ?? null);
this._updateCamera(options?.interpolateCamera);
this.onModelChanged.notifyObservers(options?.source ?? null);
}
}

Expand Down Expand Up @@ -973,11 +981,7 @@ export class Viewer implements IDisposable {
this.selectedAnimation = -1;

if (source) {
this._setModel(await this._loadModel(source, options, abortController.signal), source);
this._selectAnimation(options?.defaultAnimation ?? 0, false);
if (options?.animationAutoPlay) {
this.playAnimation();
}
this._setModel(await this._loadModel(source, options, abortController.signal), Object.assign({ source, interpolateCamera: false }, options));
}
});
}
Expand Down
26 changes: 26 additions & 0 deletions packages/tools/viewer/test/apps/web/test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Viewer Local Development</title>

<style>
html,
body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}

/* This keeps child nodes hidden while custom elements load */
:not(:defined) > * {
display: none;
}
</style>

<script type="module" src="/packages/tools/viewer/src/index.ts"></script>

</head>
</html>
108 changes: 108 additions & 0 deletions packages/tools/viewer/test/viewer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { test, expect, Page } from "@playwright/test";
import { getGlobalConfig } from "@tools/test-tools";
import { ViewerElement } from "viewer/viewerElement";

// if running in the CI we need to use the babylon snapshot when loading the tools
const snapshot = process.env.SNAPSHOT ? "?snapshot=" + process.env.SNAPSHOT : "";
const viewerUrl =
(process.env.VIEWER_BASE_URL || getGlobalConfig().baseUrl.replace(":1337", process.env.VIEWER_PORT || ":1342")) + "/packages/tools/viewer/test/apps/web/test.html" + snapshot;

async function attachViewerElement(page: Page, viewerHtml: string) {
await page.goto(viewerUrl, {
waitUntil: "networkidle",
});

await page.evaluate((viewerHtml) => {
const container = document.createElement("div");
container.innerHTML = viewerHtml;
document.body.appendChild(container);
}, viewerHtml);

const viewerElementLocator = page.locator("babylon-viewer");
await viewerElementLocator.waitFor();
return await viewerElementLocator.elementHandle();
}

test("viewerDetails available", async ({ page }) => {
const viewerElementHandle = await attachViewerElement(
page,
`
<babylon-viewer>
</babylon-viewer>
`
);

// Wait for the viewerDetails property to become defined
const viewerDetails = await page.waitForFunction((viewerElement) => {
return (viewerElement as ViewerElement).viewerDetails;
}, viewerElementHandle);

expect(viewerDetails).toBeDefined();
expect(viewerDetails.getProperty("scene")).toBeDefined();
});

test("animation-auto-play", async ({ page }) => {
const viewerElementHandle = await attachViewerElement(
page,
`
<babylon-viewer
source="https://raw.githubusercontent.com/BabylonJS/Assets/master/meshes/ufo.glb"
animation-auto-play
>
</babylon-viewer>
`
);

// Wait for the viewerDetails property to become defined
const isAnimationPlaying = await page.waitForFunction((viewerElement) => {
return (viewerElement as ViewerElement).viewerDetails?.viewer.isAnimationPlaying;
}, viewerElementHandle);

expect(isAnimationPlaying).toBeTruthy();
});

test("camera-orbit", async ({ page }) => {
const viewerElementHandle = await attachViewerElement(
page,
`
<babylon-viewer
source="https://raw.githubusercontent.com/BabylonJS/Assets/master/meshes/ufo.glb"
camera-orbit="1 2 3"
>
</babylon-viewer>
`
);

// Wait for the viewerDetails property to become defined
const cameraPose = await page.waitForFunction((viewerElement) => {
const viewerDetails = (viewerElement as ViewerElement).viewerDetails;
if (viewerDetails?.model) {
return [viewerDetails.camera.alpha, viewerDetails.camera.beta, viewerDetails.camera.radius];
}
}, viewerElementHandle);

expect(await cameraPose.jsonValue()).toEqual([1, 2, 3]);
});

test("camera-target", async ({ page }) => {
const viewerElementHandle = await attachViewerElement(
page,
`
<babylon-viewer
source="https://raw.githubusercontent.com/BabylonJS/Assets/master/meshes/ufo.glb"
camera-target="1 2 3"
>
</babylon-viewer>
`
);

// Wait for the viewerDetails property to become defined
const cameraPose = await page.waitForFunction((viewerElement) => {
const viewerDetails = (viewerElement as ViewerElement).viewerDetails;
if (viewerDetails?.model) {
return [viewerDetails.camera.target.x, viewerDetails.camera.target.y, viewerDetails.camera.target.z];
}
}, viewerElementHandle);

expect(await cameraPose.jsonValue()).toEqual([1, 2, 3]);
});
5 changes: 5 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ export default defineConfig({
testMatch: "**/*.tools.test.ts",
use: getUseDefinition("Graph Tools"),
},
{
name: "viewer",
testMatch: "packages/tools/viewer/test/viewer.test.ts",
use: getUseDefinition("Viewer"),
},
],

snapshotPathTemplate: "packages/tools/tests/test/visualization/ReferenceImages/{arg}{ext}",
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"node-render-graph-editor/*": ["tools/nodeRenderGraphEditor/dist/*"],
"gui-editor/*": ["tools/guiEditor/dist/*"],
"accessibility/*": ["tools/accessibility/dist/*"],
"viewer-legacy/*": ["tools/viewer-legacy/dist/*"],
"viewer/*": ["tools/viewer/dist/*"],
"ktx2decoder/*": ["tools/ktx2Decoder/dist/*"],
"vsm": ["tools/vsm/dist/*"]
}
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"node-render-graph-editor/*": ["tools/nodeRenderGraphEditor/src/*"],
"gui-editor/*": ["tools/guiEditor/src/*"],
"accessibility/*": ["tools/accessibility/src/*"],
"viewer-legacy/*": ["tools/viewer-legacy/src/*"],
"viewer/*": ["tools/viewer/src/*"],
"ktx2decoder/*": ["tools/ktx2Decoder/src/*"],
"shared-ui-components/*": ["dev/sharedUiComponents/src/*"],
"@tools/gui-editor/*": ["tools/guiEditor/src/*"],
Expand Down

0 comments on commit cbe8bad

Please sign in to comment.