Skip to content

Commit

Permalink
add ar-tracking attribute (google#2363)
Browse files Browse the repository at this point in the history
* added ar-tracking attribute

* added docs

* scope attribute to XR session
  • Loading branch information
elalish authored May 8, 2021
1 parent 03eadb0 commit 56e9338
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 6 deletions.
23 changes: 22 additions & 1 deletion packages/model-viewer/src/features/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {Event as ThreeEvent} from 'three';
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 {enumerationDeserializer} from '../styles/deserializers.js';
import {ARStatus} from '../three-components/ARRenderer.js';
import {ARStatus, ARTracking} from '../three-components/ARRenderer.js';
import {Constructor, waitForEvent} from '../utilities.js';

let isWebXRBlocked = false;
Expand All @@ -44,6 +44,10 @@ export interface ARStatusDetails {
status: ARStatus;
}

export interface ARTrackingDetails {
status: ARTracking;
}

const $arButtonContainer = Symbol('arButtonContainer');
const $enterARWithWebXR = Symbol('enterARWithWebXR');
export const $openSceneViewer = Symbol('openSceneViewer');
Expand All @@ -56,6 +60,7 @@ const $preload = Symbol('preload');

const $onARButtonContainerClick = Symbol('onARButtonContainerClick');
const $onARStatus = Symbol('onARStatus');
const $onARTracking = Symbol('onARTracking');
const $onARTap = Symbol('onARTap');
const $selectARMode = Symbol('selectARMode');

Expand Down Expand Up @@ -111,9 +116,20 @@ export const ARMixin = <T extends Constructor<ModelViewerElementBase>>(
this.setAttribute('ar-status', status);
this.dispatchEvent(
new CustomEvent<ARStatusDetails>('ar-status', {detail: {status}}));
if (status === ARStatus.NOT_PRESENTING) {
this.removeAttribute('ar-tracking');
} else if (status === ARStatus.SESSION_STARTED) {
this.setAttribute('ar-tracking', ARTracking.TRACKING);
}
}
};

private[$onARTracking] = ({status}: ThreeEvent) => {
this.setAttribute('ar-tracking', status);
this.dispatchEvent(new CustomEvent<ARTrackingDetails>(
'ar-tracking', {detail: {status}}));
};

private[$onARTap] = (event: Event) => {
if ((event as any).data == '_apple_ar_quicklook_button_tapped') {
this.dispatchEvent(new CustomEvent('quick-look-button-tapped'));
Expand All @@ -126,6 +142,9 @@ export const ARMixin = <T extends Constructor<ModelViewerElementBase>>(
this[$renderer].arRenderer.addEventListener('status', this[$onARStatus]);
this.setAttribute('ar-status', ARStatus.NOT_PRESENTING);

this[$renderer].arRenderer.addEventListener(
'tracking', this[$onARTracking]);

this[$arAnchor].addEventListener('message', this[$onARTap]);
}

Expand All @@ -134,6 +153,8 @@ export const ARMixin = <T extends Constructor<ModelViewerElementBase>>(

this[$renderer].arRenderer.removeEventListener(
'status', this[$onARStatus]);
this[$renderer].arRenderer.removeEventListener(
'tracking', this[$onARTracking]);

this[$arAnchor].removeEventListener('message', this[$onARTap]);
}
Expand Down
33 changes: 29 additions & 4 deletions packages/model-viewer/src/three-components/ARRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import {ModelScene} from './ModelScene.js';
import {PlacementBox} from './PlacementBox.js';
import {Renderer} from './Renderer.js';

// number of initial null pose XRFrames allowed before we post not-tracking
const INIT_FRAMES = 30;
// AR shadow is not user-configurable. This is to pave the way for AR lighting
// estimation, which will be used once available in WebXR.
const AR_SHADOW_INTENSITY = 0.3;
Expand Down Expand Up @@ -59,6 +61,17 @@ export interface ARStatusEvent extends ThreeEvent {
status: ARStatus,
}

export type ARTracking = 'tracking'|'not-tracking';

export const ARTracking: {[index: string]: ARTracking} = {
TRACKING: 'tracking',
NOT_TRACKING: 'not-tracking'
}

export interface ARTrackingEvent extends ThreeEvent {
status: ARTracking,
}

const vector3 = new Vector3();
const matrix4 = new Matrix4();
const hitPosition = new Vector3();
Expand All @@ -74,7 +87,6 @@ export class ARRenderer extends EventDispatcher {
private turntableRotation: number|null = null;
private oldShadowIntensity: number|null = null;
private oldBackground: any = null;
private rafId: number|null = null;
private frame: XRFrame|null = null;
private initialHitSource: XRHitTestSource|null = null;
private transientHitTestSource: XRTransientInputHitTestSource|null = null;
Expand All @@ -84,6 +96,8 @@ export class ARRenderer extends EventDispatcher {
private exitWebXRButtonContainer: HTMLElement|null = null;
private overlay: HTMLElement|null = null;

private tracking = true;
private frames = 0;
private initialized = false;
private projectionMatrix = new Matrix4();
private projectionMatrixInverse = new Matrix4();
Expand Down Expand Up @@ -184,6 +198,8 @@ export class ARRenderer extends EventDispatcher {

const viewerRefSpace = await currentSession.requestReferenceSpace('viewer');

this.tracking = true;
this.frames = 0;
this.initialized = false;

this.turntableRotation = scene.yaw;
Expand Down Expand Up @@ -220,6 +236,7 @@ export class ARRenderer extends EventDispatcher {
this.zDamper.setDecayTime(INTRO_DECAY);

this.lastTick = performance.now();
this.dispatchEvent({type: 'status', status: ARStatus.SESSION_STARTED});
}

/**
Expand Down Expand Up @@ -285,7 +302,6 @@ export class ARRenderer extends EventDispatcher {
if (session != null) {
session.removeEventListener('selectstart', this.onSelectStart);
session.removeEventListener('selectend', this.onSelectEnd);
session.cancelAnimationFrame(this.rafId!);
this.currentSession = null;
}

Expand Down Expand Up @@ -350,7 +366,6 @@ export class ARRenderer extends EventDispatcher {
this.turntableRotation = null;
this.oldShadowIntensity = null;
this.oldBackground = null;
this.rafId = null;
this._presentedScene = null;
this.frame = null;
this.inputSource = null;
Expand Down Expand Up @@ -403,7 +418,6 @@ export class ARRenderer extends EventDispatcher {

scene.setHotspotsVisibility(true);
this.initialized = true;
this.dispatchEvent({type: 'status', status: ARStatus.SESSION_STARTED});
}

// Ensure the camera uses the AR projection matrix without inverting on
Expand Down Expand Up @@ -673,15 +687,26 @@ export class ARRenderer extends EventDispatcher {
*/
public onWebXRFrame(time: number, frame: XRFrame) {
this.frame = frame;
++this.frames;
const refSpace = this.threeRenderer.xr.getReferenceSpace()!;
const pose = frame.getViewerPose(refSpace);

if (pose == null && this.tracking === true && this.frames > INIT_FRAMES) {
this.tracking = false;
this.dispatchEvent({type: 'tracking', status: ARTracking.NOT_TRACKING});
}

const scene = this.presentedScene;
if (pose == null || scene == null || !scene.element[$sceneIsReady]()) {
this.threeRenderer.clear();
return;
}

if (this.tracking === false) {
this.tracking = true;
this.dispatchEvent({type: 'tracking', status: ARTracking.TRACKING});
}

// WebXR may return multiple views, i.e. for headset AR. This
// isn't really supported at this point, but make a best-effort
// attempt to render other views also, using the first view
Expand Down
22 changes: 21 additions & 1 deletion packages/modelviewer.dev/data/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,18 @@
"links": [
"<a href=\"../examples/augmentedreality/#webXR\"><span class='attribute'>ar-status</span> example</a>"
]
},
{
"name": "ar-tracking",
"htmlName": "arTracking",
"description": "This read-only attribute enables DOM content to be styled based on the state of the WebXR AR tracking. For instance, a failure message can be shown by scoping a CSS rule to model-viewer[ar-tracking=\"not-tracking\"]. Setting this attribute has no effect. Most AR tracking failures are due to the camera being covered or seeing little discernable texture",
"default": {
"default": "N/A",
"options": "tracking, not-tracking"
},
"links": [
"<a href=\"../examples/augmentedreality/#webXR\"><span class='attribute'>ar-tracking</span> example</a>"
]
}
],
"Methods": [
Expand All @@ -348,6 +360,14 @@
"<a href=\"../examples/augmentedreality/#sceneViewer\"><span class='attribute'>ar-status</span> example</a>"
]
},
{
"name": "ar-tracking",
"htmlName": "arTracking",
"description": "Fired when the <span class='attribute'>ar-tracking</span> attribute above changes. The event.detail.status property will be set to the same value as the <span class='attribute'>ar-tracking</span> attribute, either 'tracking', or 'not-tracking'. This event is only enabled for WebXR AR sessions.",
"links": [
"<a href=\"../examples/augmentedreality/#sceneViewer\"><span class='attribute'>ar-tracking</span> example</a>"
]
},
{
"name": "quick-look-button-tapped",
"htmlName": "quickLookButtonTapped",
Expand Down Expand Up @@ -932,4 +952,4 @@
}
]
}
]
]
16 changes: 16 additions & 0 deletions packages/modelviewer.dev/examples/augmentedreality/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ <h4>Customize a WebXR Augmented Reality session with HTML, CSS, and JS in Chrome
<img src="../../assets/hand.png">
</div>

<button id="ar-failure">
AR is not tracking!
</button>

<div class="slider">
<div class="slides">
<button class="slide selected" onclick="switchSrc(this, 'Chair')"
Expand Down Expand Up @@ -194,6 +198,18 @@ <h4>Customize a WebXR Augmented Reality session with HTML, CSS, and JS in Chrome
animation: circle 4s linear infinite;
}

model-viewer > #ar-failure {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 175px;
display: none;
}

model-viewer[ar-tracking="not-tracking"] > #ar-failure {
display: block;
}

.slider {
width: 100%;
text-align: center;
Expand Down

0 comments on commit 56e9338

Please sign in to comment.