@egjs/view3d
Version:
Fast & Customizable glTF 3D model viewer, packed with full of features!
175 lines (155 loc) • 9.59 kB
text/typescript
/*
* Copyright (c) 2020 NAVER Corp.
* egjs projects are licensed under the MIT license
*/
import View3D from "../View3D";
import View3DError from "../core/View3DError";
import { IS_ANDROID } from "../const/browser";
import { AR_SESSION_TYPE, AUTO } from "../const/external";
import { SCENE_VIEWER_MODE } from "../const/external";
import ERROR from "../const/error";
import * as XR from "../const/xr";
import { toBooleanString } from "../utils";
import { LiteralUnion, OptionGetters, ValueOf } from "../type/utils";
import ARSession from "./ARSession";
/**
* Options for the {@link SceneViewerSession}
* @interface
* @param {string|null} [file=null] This URL specifies the glTF or glb file that should be loaded into Scene Viewer. This should be URL-escaped. If `null` is given, it will try to use current model shown on the canvas. This behavior only works when the format of the model shown is either "glTF" or "glb".
* @param {string|null} [fallbackURL=null] This is a Google Chrome feature supported only for web-based implementations. When the Google app com.google.android.googlequicksearchbox is not present on the device, this is the URL that Google Chrome navigates to.
* @param {string} [mode="ar_only"] See [SCENE_VIEWER_MODE](/docs/api/SCENE_VIEWER_MODE) for available modes (also check their [official page](https://developers.google.com/ar/develop/java/scene-viewer) for details).
* @param {string|null} [title=null] A name for the model. If present, it will be displayed in the UI. The name will be truncated with ellipses after 60 characters.
* @param {string|null} [link=null] A URL for an external webpage. If present, a button will be surfaced in the UI that intents to this URL when clicked.
* @param {string|null} [sound=null] A URL to a looping audio track that is synchronized with the first animation embedded in a glTF file. It should be provided alongside a glTF with an animation of matching length. If present, the sound is looped after the model is loaded. This should be URL-escaped.
* @param {boolean} [resizable=true] When set to false, users will not be able to scale the model in the AR experience. Scaling works normally in the 3D experience.
* @param {boolean} [vertical=false] When set to true, users will be able to place the model on a vertical surface.
* @param {boolean} [disableOcclusion=false] When set to true, SceneViewer will disable {@link https://developers.google.com/ar/develop/java/depth/introduction object blending}
* @param {string|null} [initialScale="auto"] Initial scale of the 3D model. If set to `null`, 3D model will shown as its original size and will disable the "View actual size" button. Default value is "auto", and "1" will show model size in 100%, "2" in 200%, "0.5" in 50% and so on.
* @param {string|null} [shareText=null] A text that will be displayed when user clicked the share button.
*/
export interface SceneViewerSessionOptions {
file: string | null;
fallbackURL: string | null;
mode: LiteralUnion<ValueOf<typeof SCENE_VIEWER_MODE>, string>;
title: string | null;
link: string | null;
sound: string | null;
resizable: boolean;
vertical: boolean;
disableOcclusion: boolean;
initialScale: LiteralUnion<typeof AUTO, string> | null;
shareText: string | null;
[key: string]: any;
}
/**
* AR session using Google's scene-viewer
* @see https://developers.google.com/ar/develop/java/scene-viewer
*/
class SceneViewerSession implements ARSession, OptionGetters<SceneViewerSessionOptions> {
/**
* Return the availability of SceneViewerSession.
* Scene-viewer is available on all android devices with google ARCore installed.
* @returns {Promise} A Promise that resolves availability of this session(boolean).
*/
public static isAvailable() {
return Promise.resolve(IS_ANDROID());
}
public static readonly type = AR_SESSION_TYPE.SCENE_VIEWER;
// Options
// As those values are referenced only while entering the session, so I'm leaving this values public
public file: SceneViewerSessionOptions["file"];
public mode: SceneViewerSessionOptions["mode"];
public fallbackURL: SceneViewerSessionOptions["fallbackURL"];
public title: SceneViewerSessionOptions["title"];
public link: SceneViewerSessionOptions["link"];
public sound: SceneViewerSessionOptions["sound"];
public resizable: SceneViewerSessionOptions["resizable"];
public vertical: SceneViewerSessionOptions["vertical"];
public disableOcclusion: SceneViewerSessionOptions["disableOcclusion"];
public initialScale: SceneViewerSessionOptions["initialScale"];
public shareText: SceneViewerSessionOptions["shareText"];
public otherParams: Record<string, any>;
private _view3D: View3D;
/**
* Create new instance of SceneViewerSession
* @see https://developers.google.com/ar/develop/java/scene-viewer
* @param {View3D} view3D Instance of the View3D
* @param {object} [params={}] Session params
* @param {string} [params.file=null] This URL specifies the glTF or glb file that should be loaded into Scene Viewer. This should be URL-escaped. If `null` is given, it will try to use current model shown on the canvas. This behavior only works when the format of the model shown is either "glTF" or "glb".
* @param {string} [params.mode="ar_only"] See [SCENE_VIEWER_MODE](/docs/api/SCENE_VIEWER_MODE) for available modes (also check their [official page](https://developers.google.com/ar/develop/java/scene-viewer) for details).
* @param {string} [params.fallbackURL=null] This is a Google Chrome feature supported only for web-based implementations. When the Google app com.google.android.googlequicksearchbox is not present on the device, this is the URL that Google Chrome navigates to.
* @param {string} [params.title=null] A name for the model. If present, it will be displayed in the UI. The name will be truncated with ellipses after 60 characters.
* @param {string} [params.link=null] A URL for an external webpage. If present, a button will be surfaced in the UI that intents to this URL when clicked.
* @param {string} [params.sound=null] A URL to a looping audio track that is synchronized with the first animation embedded in a glTF file. It should be provided alongside a glTF with an animation of matching length. If present, the sound is looped after the model is loaded. This should be URL-escaped.
* @param {boolean} [params.resizable=true] When set to false, users will not be able to scale the model in the AR experience. Scaling works normally in the 3D experience.
* @param {boolean} [params.vertical=false] When set to true, users will be able to place the model on a vertical surface.
* @param {boolean} [params.disableOcclusion=false] When set to true, SceneViewer will disable {@link https://developers.google.com/ar/develop/java/depth/introduction object blending}
* @param {string} [params.initialScale="auto"] Initial scale of the 3D model. If set to `null`, 3D model will shown as its original size and will disable the "View actual size" button. Default value is "auto", and "1" will show model size in 100%, "2" in 200%, "0.5" in 50% and so on.
* @param {string} [params.shareText=null] A text that will be displayed when user clicked the share button.
*/
public constructor(view3D: View3D, {
file = null,
mode = SCENE_VIEWER_MODE.ONLY_AR,
fallbackURL = null,
title = null,
link = null,
sound = null,
resizable = true,
vertical = false,
disableOcclusion = false,
initialScale = AUTO,
shareText = null,
...otherParams
}: Partial<SceneViewerSessionOptions> = {}) {
this._view3D = view3D;
this.file = file;
this.fallbackURL = fallbackURL;
this.mode = mode;
this.title = title;
this.link = link;
this.sound = sound;
this.resizable = resizable;
this.vertical = vertical;
this.disableOcclusion = disableOcclusion;
this.initialScale = initialScale;
this.shareText = shareText;
this.otherParams = otherParams;
}
/**
* Enter Scene-viewer AR session
*/
public async enter() {
const model = this._view3D.model!;
const params: Record<string, string | null> = {
title: this.title,
link: this.link,
sound: this.sound,
mode: this.mode as string,
initial_scale: this.initialScale as string, // eslint-disable-line @typescript-eslint/naming-convention
...this.otherParams
};
params.resizable = toBooleanString(this.resizable);
params.enable_vertical_placement = toBooleanString(this.vertical);
params.disable_occlusion = toBooleanString(this.disableOcclusion);
params.share_text = this.shareText ? encodeURIComponent(this.shareText) : null;
const file = this.file ?? model.src;
if (!file) {
return Promise.reject(new View3DError(ERROR.MESSAGES.FILE_NOT_SUPPORTED(this.file ?? model.src), ERROR.CODES.FILE_NOT_SUPPORTED));
}
params.file = new URL(file, window.location.href).href;
const fallbackURL = this.fallbackURL;
const queryString = Object.keys(params)
.filter(key => params[key] != null)
.map(key => `${key}=${params[key]}`).join("&");
const intentURL = params.mode === SCENE_VIEWER_MODE.ONLY_AR
? XR.SCENE_VIEWER.INTENT_AR_CORE(queryString, fallbackURL)
: XR.SCENE_VIEWER.INTENT_SEARCHBOX(queryString, fallbackURL || XR.SCENE_VIEWER.FALLBACK_DEFAULT(queryString));
const anchor = document.createElement("a") ;
anchor.href = intentURL;
anchor.click();
}
public exit() {
return Promise.resolve();
}
}
export default SceneViewerSession;