diff --git a/packages/model-viewer/src/features/ar.ts b/packages/model-viewer/src/features/ar.ts index 1993eeaa47..431c5ef0d0 100644 --- a/packages/model-viewer/src/features/ar.ts +++ b/packages/model-viewer/src/features/ar.ts @@ -15,9 +15,10 @@ import {property} from 'lit-element'; import {Event as ThreeEvent} from 'three'; +import {USDZExporter} from 'three/examples/jsm/exporters/USDZExporter'; import {IS_AR_QUICKLOOK_CANDIDATE, IS_SCENEVIEWER_CANDIDATE, IS_WEBXR_AR_CANDIDATE} from '../constants.js'; -import ModelViewerElementBase, {$needsRender, $renderer, $scene, $shouldAttemptPreload, $updateSource} from '../model-viewer-base.js'; +import ModelViewerElementBase, {$needsRender, $progressTracker, $renderer, $scene, $shouldAttemptPreload, $updateSource} from '../model-viewer-base.js'; import {enumerationDeserializer} from '../styles/deserializers.js'; import {ARStatus, ARTracking} from '../three-components/ARRenderer.js'; import {Constructor, waitForEvent} from '../utilities.js'; @@ -31,7 +32,7 @@ export type ARMode = 'quick-look'|'scene-viewer'|'webxr'|'none'; const deserializeARModes = enumerationDeserializer( ['quick-look', 'scene-viewer', 'webxr', 'none']); -const DEFAULT_AR_MODES = 'webxr scene-viewer quick-look'; +const DEFAULT_AR_MODES = 'webxr scene-viewer'; const ARMode: {[index: string]: ARMode} = { QUICK_LOOK: 'quick-look', @@ -57,6 +58,7 @@ const $arMode = Symbol('arMode'); const $arModes = Symbol('arModes'); const $arAnchor = Symbol('arAnchor'); const $preload = Symbol('preload'); +const $generatedIosUrl = Symbol('generatedIosUrl'); const $onARButtonContainerClick = Symbol('onARButtonContainerClick'); const $onARStatus = Symbol('onARStatus'); @@ -105,6 +107,8 @@ export const ARMixin = >( protected[$arMode]: ARMode = ARMode.NONE; protected[$preload] = false; + private[$generatedIosUrl]: string|null = null; + private[$onARButtonContainerClick] = (event: Event) => { event.preventDefault(); this.activateAR(); @@ -226,13 +230,17 @@ configuration or device capabilities'); !isSceneViewerBlocked) { this[$arMode] = ARMode.SCENE_VIEWER; break; - } else if ( - value === 'quick-look' && !!this.iosSrc && - IS_AR_QUICKLOOK_CANDIDATE) { + } else if (value === 'quick-look' && IS_AR_QUICKLOOK_CANDIDATE) { this[$arMode] = ARMode.QUICK_LOOK; break; } } + + // The presence of ios-src overrides the absence of quick-look ar-mode. + if (!this.canActivateAR && this.iosSrc != null && + IS_AR_QUICKLOOK_CANDIDATE) { + this[$arMode] = ARMode.QUICK_LOOK; + } } if (this.canActivateAR) { @@ -352,23 +360,76 @@ configuration or device capabilities'); * Takes a URL to a USDZ file and sets the appropriate fields so that Safari * iOS can intent to their AR Quick Look. */ - [$openIOSARQuickLook]() { - const modelUrl = new URL(this.iosSrc!, self.location.toString()); + async[$openIOSARQuickLook]() { + const generateUsdz = !this.iosSrc; + + this[$arButtonContainer].classList.remove('enabled'); + + const modelUrl = new URL( + generateUsdz ? await this.prepareUSDZ() : this.iosSrc!, + self.location.toString()); + + this[$arButtonContainer].classList.add('enabled'); + if (this.arScale === 'fixed') { if (modelUrl.hash) { modelUrl.hash += '&'; } modelUrl.hash += 'allowsContentScaling=0'; } + + const anchor = this[$arAnchor]; anchor.setAttribute('rel', 'ar'); const img = document.createElement('img'); anchor.appendChild(img); anchor.setAttribute('href', modelUrl.toString()); + if (generateUsdz) { + anchor.setAttribute('download', 'model.usdz'); + } console.log('Attempting to present in AR with Quick Look...'); anchor.click(); anchor.removeChild(img); } + + async prepareUSDZ(): Promise { + const updateSourceProgress = this[$progressTracker].beginActivity(); + + if (this[$generatedIosUrl] != null) { + URL.revokeObjectURL(this[$generatedIosUrl]!); + this[$generatedIosUrl] = null; + } + + const scene = this[$scene]; + + const shadow = scene.shadow; + let visible = false; + + // Remove shadow from export + if (shadow != null) { + visible = shadow.visible; + shadow.visible = false; + } + + updateSourceProgress(0.2); + + const exporter = new USDZExporter(); + const arraybuffer = await exporter.parse(scene.modelContainer); + const blob = new Blob([arraybuffer], { + type: 'application/octet-stream', + }); + + const url = URL.createObjectURL(blob); + this[$generatedIosUrl] = url; + + updateSourceProgress(1); + + if (shadow != null) { + shadow.visible = visible; + } + + return url; + } } return ARModelViewerElement; diff --git a/packages/modelviewer.dev/data/docs.json b/packages/modelviewer.dev/data/docs.json index be70e9eca7..0cd6d497c2 100644 --- a/packages/modelviewer.dev/data/docs.json +++ b/packages/modelviewer.dev/data/docs.json @@ -243,13 +243,13 @@ { "name": "ar-modes", "htmlName": "arModes", - "description": "A prioritized list of the types of AR experiences to enable. Allowed values are \"webxr\", to launch the AR experience in the browser, \"scene-viewer\", to launch the Scene Viewer app, \"quick-look\", to launch the iOS Quick Look app. You can specify any number of modes separated by whitespace.", + "description": "A prioritized list of the types of AR experiences to enable. Allowed values are \"webxr\", to launch the AR experience in the browser, \"scene-viewer\", to launch the Scene Viewer app, \"quick-look\", to launch the iOS Quick Look app. You can specify any number of modes separated by whitespace. Note that the presence of an ios-src will enable quick-look by itself; specifying quick-look here allows us to generate a USDZ on the fly rather than downloading a separate ios-src file.", "links": [ "Related examples" ], "default": { - "default": "webxr scene-viewer quick-look", - "options": "prioritized list of allowed modes" + "default": "webxr scene-viewer", + "options": "prioritized list possible AR modes: webxr, scene-viewer, and quick-look" } }, { @@ -279,7 +279,7 @@ { "name": "ios-src", "htmlName": "iosSrc", - "description": "The url to a USDZ model which will be used on supported iOS 12+ devices via AR Quick Look on Safari.", + "description": "The url to a USDZ model which will be used on supported iOS 12+ devices via AR Quick Look on Safari. The presence of this attribute will automatically enable the quick-look ar-mode, however it is no longer necessary. If instead the quick-look ar-mode is specified and ios-src is not specified, then we will generate a USDZ on the fly when the AR button is pressed. This means modifications via the scene-graph API will now be reflected in Quick Look. Hoowever, USDZ generation is not perfect, for instance animations are not yet supported, so in some cases supplying ios-src may give better results.", "links": [ "ios-src example" ] diff --git a/packages/modelviewer.dev/examples/augmentedreality/index.html b/packages/modelviewer.dev/examples/augmentedreality/index.html index 464bfd4997..85b1de566e 100644 --- a/packages/modelviewer.dev/examples/augmentedreality/index.html +++ b/packages/modelviewer.dev/examples/augmentedreality/index.html @@ -75,7 +75,7 @@

Customize a WebXR Augmented Reality session with HTML, CSS, and JS in Chrome