Skip to content

Commit

Permalink
AR status event and attribute (google#1237)
Browse files Browse the repository at this point in the history
* added AR events

* added ar-status attribute

* fixed AR return bug

* fixed not-presenting bug

* simplified events

* added docs
  • Loading branch information
elalish authored Jun 5, 2020
1 parent 7526477 commit 66744c0
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 15 deletions.
48 changes: 37 additions & 11 deletions packages/model-viewer/src/features/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
*/

import {property} from 'lit-element';
import {Event as ThreeEvent} from 'three';

import {IS_ANDROID, IS_AR_QUICKLOOK_CANDIDATE, IS_IOS_CHROME, IS_IOS_SAFARI, IS_WEBXR_AR_CANDIDATE} from '../constants.js';
import ModelViewerElementBase, {$renderer, $scene} from '../model-viewer-base.js';
import {enumerationDeserializer} from '../styles/deserializers.js';
import {ARStatus} from '../three-components/ARRenderer.js';
import {Constructor, deserializeUrl} from '../utilities.js';

/**
Expand Down Expand Up @@ -112,6 +114,10 @@ const ARMode: {[index: string]: ARMode} = {
NONE: 'none'
};

export interface ARStatusDetails {
status: ARStatus;
}

const $arButtonContainer = Symbol('arButtonContainer');
const $enterARWithWebXR = Symbol('enterARWithWebXR');
const $canActivateAR = Symbol('canActivateAR');
Expand All @@ -120,8 +126,8 @@ const $arModes = Symbol('arModes');
const $canLaunchQuickLook = Symbol('canLaunchQuickLook');
const $quickLookBrowsers = Symbol('quickLookBrowsers');

const $arButtonContainerClickHandler = Symbol('arButtonContainerClickHandler');
const $onARButtonContainerClick = Symbol('onARButtonContainerClick');
const $onARStatus = Symbol('onARStatus');

export declare interface ARInterface {
ar: boolean;
Expand Down Expand Up @@ -161,14 +167,25 @@ export const ARMixin = <T extends Constructor<ModelViewerElementBase>>(
protected[$arButtonContainer]: HTMLElement =
this.shadowRoot!.querySelector('.ar-button') as HTMLElement;

protected[$arButtonContainerClickHandler]: (event: Event) => void =
(event) => this[$onARButtonContainerClick](event);

protected[$arModes]: Set<ARMode> = new Set();
protected[$arMode]: ARMode = ARMode.NONE;

protected[$quickLookBrowsers]: Set<QuickLookBrowser> = new Set();

private[$onARButtonContainerClick] = (event: Event) => {
event.preventDefault();
this.activateAR();
};

private[$onARStatus] = ({status}: ThreeEvent) => {
if (status === ARStatus.NOT_PRESENTING ||
this[$renderer].arRenderer.presentedScene === this[$scene]) {
this.setAttribute('ar-status', status);
this.dispatchEvent(
new CustomEvent<ARStatusDetails>('ar-status', {detail: {status}}));
}
};

/**
* Activates AR. Note that for any mode that is not WebXR-based, this
* method most likely has to be called synchronous from a user
Expand Down Expand Up @@ -205,6 +222,20 @@ configuration or device capabilities');
}
}

connectedCallback() {
super.connectedCallback();

this[$renderer].arRenderer.addEventListener('status', this[$onARStatus]);
this.setAttribute('ar-status', ARStatus.NOT_PRESENTING);
}

disconnectedCallback() {
super.disconnectedCallback();

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

async update(changedProperties: Map<string, any>) {
super.update(changedProperties);

Expand Down Expand Up @@ -253,19 +284,14 @@ configuration or device capabilities');
if (this.canActivateAR) {
this[$arButtonContainer].classList.add('enabled');
this[$arButtonContainer].addEventListener(
'click', this[$arButtonContainerClickHandler]);
'click', this[$onARButtonContainerClick]);
} else {
this[$arButtonContainer].removeEventListener(
'click', this[$arButtonContainerClickHandler]);
'click', this[$onARButtonContainerClick]);
this[$arButtonContainer].classList.remove('enabled');
}
}

[$onARButtonContainerClick](event: Event) {
event.preventDefault();
this.activateAR();
}

get[$canLaunchQuickLook](): boolean {
if (IS_IOS_CHROME) {
return this[$quickLookBrowsers].has('chrome');
Expand Down
22 changes: 20 additions & 2 deletions packages/model-viewer/src/three-components/ARRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import '../types/webxr.js';

import {EventDispatcher, Matrix4, PerspectiveCamera, Ray, Vector3, WebGLRenderer} from 'three';
import {Event as ThreeEvent, EventDispatcher, Matrix4, PerspectiveCamera, Ray, Vector3, WebGLRenderer} from 'three';

import {$onResize} from '../model-viewer-base.js';
import {assertIsArCandidate} from '../utilities.js';
Expand All @@ -41,6 +41,18 @@ const INTRO_DAMPER_RATE = 0.4;
const SCALE_SNAP_HIGH = 1.2;
const SCALE_SNAP_LOW = 1 / SCALE_SNAP_HIGH;

export type ARStatus = 'not-presenting'|'session-started'|'object-placed';

export const ARStatus: {[index: string]: ARStatus} = {
NOT_PRESENTING: 'not-presenting',
SESSION_STARTED: 'session-started',
OBJECT_PLACED: 'object-placed'
};

export interface ARStatusEvent extends ThreeEvent {
status: ARStatus,
}

const $presentedScene = Symbol('presentedScene');
const $placementBox = Symbol('placementBox');
const $lastTick = Symbol('lastTick');
Expand Down Expand Up @@ -332,6 +344,9 @@ export class ARRenderer extends EventDispatcher {
element[$onResize](element.getBoundingClientRect());
}

// Force the Renderer to update its size
this.renderer.height = 0;

const exitButton = this[$exitWebXRButtonContainer];
if (exitButton != null) {
exitButton.classList.remove('enabled');
Expand Down Expand Up @@ -371,6 +386,8 @@ export class ARRenderer extends EventDispatcher {
if (this[$resolveCleanup] != null) {
this[$resolveCleanup]!();
}

this.dispatchEvent({type: 'status', status: ARStatus.NOT_PRESENTING});
}

/**
Expand Down Expand Up @@ -428,6 +445,7 @@ export class ARRenderer extends EventDispatcher {
this[$initialModelToWorld].copy(scene.model.matrixWorld);
scene.model.setHotspotsVisibility(true);
this[$initialized] = true;
this.dispatchEvent({type: 'status', status: ARStatus.SESSION_STARTED});
}

this[$presentedScene]!.model.orientHotspots(
Expand Down Expand Up @@ -534,7 +552,7 @@ export class ARRenderer extends EventDispatcher {
// Ignore the y-coordinate and set on the floor instead.
goal.y = floor;

this.dispatchEvent({type: 'modelmove'});
this.dispatchEvent({type: 'status', status: ARStatus.OBJECT_PLACED});
}

[$onSelectStart] = (event: Event) => {
Expand Down
4 changes: 2 additions & 2 deletions packages/model-viewer/src/three-components/Renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,15 @@ export class Renderer extends EventDispatcher {
public canvas3D: HTMLCanvasElement|OffscreenCanvas;
public textureUtils: TextureUtils|null;
public arRenderer: ARRenderer;
public width = 0;
public height = 0;
public dpr = 1;
public minScale = DEFAULT_MIN_SCALE;

protected debugger: Debugger|null = null;
private scenes: Set<ModelScene> = new Set();
private multipleScenesVisible = false;
private lastTick: number;
private width = 0;
private height = 0;
private scale = 1;
private avgFrameDuration =
(HIGH_FRAME_DURATION_MS + LOW_FRAME_DURATION_MS) / 2;
Expand Down
18 changes: 18 additions & 0 deletions packages/modelviewer.dev/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,16 @@ <h3 class="grouping-title">CSS Custom Properties</h3>
<p>Sets the display property of the AR button. Intended to be used
to force the button to be hidden. Defaults to block.</p>
</li>
<li>
<div>ar-status</div>
<p>This read-only attribute enables DOM content to be styled based
on the status of the WebXR AR presentation. Possible values include
'not-presenting', 'session-started', and 'object-placed'. For instance,
a prompt for the user to move their phone until the object is
successfully placed in their space can be shown by scoping a CSS rule
to model-viewer[ar-status="session-started"]. Setting this attribute
has no effect.</p>
</li>
<li>
<div>--interaction-prompt-display</div>
<p>Sets the display property of the interaction prompt. Intended
Expand Down Expand Up @@ -630,6 +640,14 @@ <h3 class="grouping-title">Methods</h3>
<h3 class="grouping-title">Events</h3>

<ul class="list-attribute">
<li>
<div>ar-status</div>
<p>Fired when the ar-status attribute above fires. The event.detail.status
property will be set to the same value as the ar-status attribute, either
'not-presenting', 'session-started', or 'object-placed'. This event is
only enabled for WebXR AR sessions.
</p>
</li>
<li>
<div>camera-change</div>
<p>Fired when the camera position and/or field of view have
Expand Down

0 comments on commit 66744c0

Please sign in to comment.