Skip to content
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
5 changes: 5 additions & 0 deletions packages/model-viewer/src/features/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,10 @@ configuration or device capabilities');

updateSourceProgress(0.2);

const usdzOptions = this.arPlacement === 'wall' ?
{includeAnchoringProperties: false} :
{};

const exporter = new USDZExporter();

const exportGroup = new Object3D();
Expand All @@ -496,6 +500,7 @@ configuration or device capabilities');
let arraybuffer: ArrayBuffer;
try {
arraybuffer = await exporter.parseAsync(exportGroup, {
...usdzOptions,
maxTextureSize: isNaN(this.arUsdzMaxTextureSize as any) ?
Infinity :
Math.max(parseInt(this.arUsdzMaxTextureSize), 16),
Expand Down
59 changes: 59 additions & 0 deletions packages/model-viewer/src/test/features/ar-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import '../renderer-gate.js';

import {expect} from 'chai';
import {strFromU8, unzipSync} from 'three/examples/jsm/libs/fflate.module.js';

import {IS_ANDROID, IS_IOS} from '../../constants.js';
import {$openIOSARQuickLook, $openSceneViewer} from '../../features/ar.js';
Expand Down Expand Up @@ -250,6 +251,64 @@ suite('AR', () => {
});
});

suite('prepareUSDZ placement', () => {
const readUsda = async (url: string) => {
try {
const response = await fetch(url);
const files = unzipSync(new Uint8Array(await response.arrayBuffer()));
return strFromU8(files['model.usda']);
} finally {
URL.revokeObjectURL(url);
}
};

const transforms = (usda: string) => usda.split('\n').filter(
line => line.includes('matrix4d xformOp:transform'));

for (const src
of [assetPath('models/cube.gltf'),
assetPath('models/offcenter-cube.gltf'),
'/packages/modelviewer.dev/assets/boom_2_.glb']) {
test(`preserves transforms and viewer state for ${src}`, async () => {
element.src = src;
element.orientation = '15deg 25deg 35deg';
await waitForEvent(element, 'load');
await element.updateComplete;

const scene = element[$scene];
const model = scene.models[0];
const position = model.position.clone();
const quaternion = model.quaternion.clone();
const scale = model.scale.clone();
const targetPosition = scene.target.position.clone();

const floor = await readUsda(await (element as any).prepareUSDZ());
expect(floor).to.include('preliminary:anchoring:type = "plane"');
expect(floor).to.include(
'preliminary:planeAnchoring:alignment = "horizontal"');
expect(transforms(floor)).not.to.be.empty;

element.arPlacement = 'wall';
element.resetTurntableRotation(0.7);
const wall = await readUsda(await (element as any).prepareUSDZ());
expect(wall).not.to.include('preliminary:anchoring:type');
expect(wall).not.to.include('preliminary:planeAnchoring:alignment');
expect(transforms(wall)).to.deep.equal(transforms(floor));

element.resetTurntableRotation(1.3);
const rotatedWall =
await readUsda(await (element as any).prepareUSDZ());
expect(transforms(rotatedWall)).to.deep.equal(transforms(wall));
expect(element.turntableRotation).to.equal(1.3);
expect(model.parent).to.equal(scene.target);
expect(model.position.equals(position)).to.be.true;
expect(model.quaternion.equals(quaternion)).to.be.true;
expect(model.scale.equals(scale)).to.be.true;
expect(scene.target.position.equals(targetPosition)).to.be.true;
});
}
});

suite('shows the AR button', () => {
setup(async () => {
element.ar = true;
Expand Down
Loading