1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048 |
- /*
- * encantar.js
- * GPU-accelerated Augmented Reality for the web
- * Copyright (C) 2022-2024 Alexandre Martins <alemartf(at)gmail.com>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published
- * by the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * viewport.ts
- * Viewport
- */
-
- import AR from '../main';
- import Speedy from 'speedy-vision';
- import { SpeedySize } from 'speedy-vision/types/core/speedy-size';
- import { SpeedyPromise } from 'speedy-vision/types/core/speedy-promise';
- import { SessionMode } from './session';
- import { HUD, HUDContainer } from './hud';
- import { FullscreenButton } from '../ui/fullscreen-button';
- import { Vector2 } from '../geometry/vector2';
- import { Resolution } from '../utils/resolution';
- import { Nullable } from '../utils/utils';
- import { Utils } from '../utils/utils';
- import { AREvent, AREventTarget, AREventListener } from '../utils/ar-events';
- import { IllegalArgumentError, IllegalOperationError, NotSupportedError, AccessDeniedError } from '../utils/errors';
-
-
-
-
- /** Viewport container */
- export type ViewportContainer = HTMLDivElement;
-
- /** We admit that the size of the drawing buffer of the background canvas of the viewport may change over time */
- type ViewportSizeGetter = () => SpeedySize;
-
- /** All possible event types emitted by a Viewport */
- type ViewportEventType = 'resize' | 'fullscreenchange';
-
- /** An event emitted by a Viewport */
- class ViewportEvent extends AREvent<ViewportEventType> { }
-
- /** Viewport event target */
- class ViewportEventTarget extends AREventTarget<ViewportEventType> { }
-
- /** Viewport style (immersive mode) */
- type ViewportStyle = 'best-fit' | 'stretch' | 'inline';
-
-
-
-
- /**
- * Viewport constructor settings
- */
- export interface ViewportSettings
- {
- /** Viewport container */
- container: Nullable<ViewportContainer>;
-
- /** HUD container */
- hudContainer?: Nullable<HUDContainer>;
-
- /** Resolution of the canvas on which the virtual scene will be drawn */
- resolution?: Resolution;
-
- /** Viewport style */
- style?: ViewportStyle;
-
- /** An existing <canvas> on which the virtual scene will be drawn */
- canvas?: Nullable<HTMLCanvasElement>;
-
- /** Whether or not to include the built-in fullscreen button */
- fullscreenUI?: boolean;
- }
-
- /** Default viewport constructor settings */
- const DEFAULT_VIEWPORT_SETTINGS: Readonly<Required<ViewportSettings>> = {
- container: null,
- hudContainer: null,
- resolution: 'lg',
- style: 'best-fit',
- canvas: null,
- fullscreenUI: true,
- };
-
-
-
-
- /** Base z-index of the children of the viewport container */
- const BASE_ZINDEX = 0;
-
- /** Z-index of the background canvas */
- const BACKGROUND_ZINDEX = BASE_ZINDEX + 0;
-
- /** Z-index of the foreground canvas */
- const FOREGROUND_ZINDEX = BASE_ZINDEX + 1;
-
- /** Z-index of the HUD */
- const HUD_ZINDEX = BASE_ZINDEX + 2;
-
-
-
-
- /**
- * Helper class to work with the containers of the viewport
- */
- class ViewportContainers
- {
- /** The viewport container */
- private readonly _container: ViewportContainer;
-
- /** A direct child of the viewport container */
- private readonly _subContainer: HTMLDivElement;
-
-
-
-
- /**
- * Constructor
- * @param container viewport container
- */
- constructor(container: Nullable<ViewportContainer>)
- {
- // validate
- if(container == null)
- throw new IllegalArgumentError('Unspecified viewport container');
- else if(!(container instanceof HTMLElement))
- throw new IllegalArgumentError('Invalid viewport container');
-
- // store the viewport container
- this._container = container;
-
- // create the sub-container
- this._subContainer = document.createElement('div') as HTMLDivElement;
- container.appendChild(this._subContainer);
- }
-
- /**
- * The viewport container
- */
- get container(): ViewportContainer
- {
- return this._container;
- }
-
- /**
- * The sub-container
- */
- get subContainer(): HTMLDivElement
- {
- return this._subContainer;
- }
-
- /**
- * Initialize
- */
- init(): void
- {
- this._container.style.touchAction = 'none';
- this._container.style.backgroundColor = 'black';
- }
-
- /**
- * Release
- */
- release(): void
- {
- this._container.style.removeProperty('background-color');
- this._container.style.removeProperty('touch-action');
- }
- }
-
-
-
-
- /**
- * Helper class to work with the canvases of the viewport
- */
- class ViewportCanvases
- {
- /** A canvas used to render the physical scene */
- private readonly _backgroundCanvas: HTMLCanvasElement;
-
- /** A canvas used to render the virtual scene */
- private readonly _foregroundCanvas: HTMLCanvasElement;
-
- /** Original CSS of the foreground canvas */
- private readonly _originalCSSTextOfForegroundCanvas: string;
-
-
-
- /**
- * Constructor
- * @param parent container for the canvases
- * @param initialSize initial size of the canvases
- * @param fgCanvas optional existing foreground canvas
- */
- constructor(parent: HTMLElement, initialSize: SpeedySize, fgCanvas: Nullable<HTMLCanvasElement> = null)
- {
- if(fgCanvas !== null && !(fgCanvas instanceof HTMLCanvasElement))
- throw new IllegalArgumentError('Not a canvas: ' + fgCanvas);
-
- this._originalCSSTextOfForegroundCanvas = fgCanvas ? fgCanvas.style.cssText : '';
-
- this._foregroundCanvas = this._styleCanvas(
- fgCanvas || this._createCanvas(initialSize),
- FOREGROUND_ZINDEX
- );
-
- this._foregroundCanvas.style.background = 'transparent';
-
- this._backgroundCanvas = this._styleCanvas(
- this._createCanvas(initialSize),
- BACKGROUND_ZINDEX
- );
-
- this._backgroundCanvas.hidden = true;
- this._foregroundCanvas.hidden = true;
-
- const engineInfo = 'encantar.js ' + AR.version;
- this._backgroundCanvas.dataset.arEngine = engineInfo;
- this._foregroundCanvas.dataset.arEngine = engineInfo;
-
- parent.appendChild(this._backgroundCanvas);
- parent.appendChild(this._foregroundCanvas);
- }
-
- /**
- * The background canvas
- */
- get backgroundCanvas(): HTMLCanvasElement
- {
- return this._backgroundCanvas;
- }
-
- /**
- * The foreground canvas
- */
- get foregroundCanvas(): HTMLCanvasElement
- {
- return this._foregroundCanvas;
- }
-
- /**
- * Initialize
- */
- init(): void
- {
- this._backgroundCanvas.hidden = false;
- this._foregroundCanvas.hidden = false;
- }
-
- /**
- * Release
- */
- release(): void
- {
- this._backgroundCanvas.hidden = true;
- this._foregroundCanvas.hidden = true;
-
- this._backgroundCanvas.style.cssText = '';
- this._foregroundCanvas.style.cssText = this._originalCSSTextOfForegroundCanvas;
- }
-
- /**
- * Create a canvas
- * @param size size of the drawing buffer
- * @returns a new canvas
- */
- private _createCanvas(size: SpeedySize): HTMLCanvasElement
- {
- const canvas = document.createElement('canvas') as HTMLCanvasElement;
-
- canvas.width = size.width;
- canvas.height = size.height;
-
- return canvas;
- }
-
- /**
- * Add suitable CSS rules to a canvas
- * @param canvas
- * @param zIndex
- * @returns canvas
- */
- private _styleCanvas(canvas: HTMLCanvasElement, zIndex: number): HTMLCanvasElement
- {
- canvas.style.position = 'absolute';
- canvas.style.left = '0px';
- canvas.style.top = '0px';
- canvas.style.width = '100%';
- canvas.style.height = '100%';
- canvas.style.zIndex = String(zIndex);
-
- return canvas;
- }
- }
-
-
-
-
- /**
- * Fullscreen utilities
- */
- class ViewportFullscreenHelper
- {
- /** The viewport */
- private readonly _viewport: Viewport;
-
- /** The container to be put in fullscreen */
- private readonly _container: HTMLElement;
-
- /** Event handler */
- private _boundEventHandler: EventListener;
-
-
-
- /**
- * Constructor
- * @param viewport Viewport
- */
- constructor(viewport: Viewport)
- {
- this._viewport = viewport;
- this._container = viewport.container;
- this._boundEventHandler = this._triggerEvent.bind(this);
- }
-
- /**
- * Initialize
- */
- init(): void
- {
- this._container.addEventListener('fullscreenchange', this._boundEventHandler);
- }
-
- /**
- * Release
- */
- release(): void
- {
- this._container.removeEventListener('fullscreenchange', this._boundEventHandler);
- }
-
- /**
- * Make a request to the user agent so that the viewport container is
- * displayed in fullscreen mode. The container must be a compatible element[1]
- * and the user must interact with the page in order to comply with browser
- * policies[2]. In case of error, the returned promise is rejected.
- * [1] https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen#compatible_elements
- * [2] https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen#security
- * @returns promise
- */
- request(): SpeedyPromise<void>
- {
- const container = this._container;
-
- // fallback for older WebKit versions
- if(container.requestFullscreen === undefined) {
- if((container as any).webkitRequestFullscreen === undefined)
- return Speedy.Promise.reject(new NotSupportedError());
- else if(!(document as any).webkitFullscreenEnabled)
- return Speedy.Promise.reject(new AccessDeniedError());
-
- // webkitRequestFullscreen() does not return a value
- (container as any).webkitRequestFullscreen();
-
- return new Speedy.Promise<void>((resolve, reject) => {
- setTimeout(() => {
- if(container === (document as any).webkitFullscreenElement) {
- Utils.log('Entering fullscreen mode...');
- resolve();
- }
- else
- reject(new TypeError());
- }, 100);
- });
- }
-
- // check if the fullscreen mode is available
- if(!document.fullscreenEnabled)
- return Speedy.Promise.reject(new AccessDeniedError());
-
- // request fullscreen
- return new Speedy.Promise<void>((resolve, reject) => {
- container.requestFullscreen({
- navigationUI: 'hide'
- }).then(() => {
- Utils.log('Entering fullscreen mode...');
- resolve();
- }, reject);
- });
- }
-
- /**
- * Exit fullscreen mode
- * @returns promise
- */
- exit(): SpeedyPromise<void>
- {
- // fallback for older WebKit versions
- if(document.exitFullscreen === undefined) {
- const doc = document as any;
-
- if(doc.webkitExitFullscreen === undefined)
- return Speedy.Promise.reject(new NotSupportedError());
- else if(doc.webkitFullscreenElement === null)
- return Speedy.Promise.reject(new IllegalOperationError('Not in fullscreen mode'));
-
- // webkitExitFullscreen() does not return a value
- doc.webkitExitFullscreen();
-
- return new Speedy.Promise<void>((resolve, reject) => {
- setTimeout(() => {
- if(doc.webkitFullscreenElement === null) {
- Utils.log('Exiting fullscreen mode...');
- resolve();
- }
- else
- reject(new TypeError());
- }, 100);
- });
- }
-
- // error if not in fullscreen mode
- if(document.fullscreenElement === null)
- return Speedy.Promise.reject(new IllegalOperationError('Not in fullscreen mode'));
-
- // exit fullscreen
- return new Speedy.Promise<void>((resolve, reject) => {
- document.exitFullscreen().then(() => {
- Utils.log('Exiting fullscreen mode...');
- resolve();
- }, reject);
- });
- }
-
- /**
- * Is the fullscreen mode available in this platform?
- * @returns true if the fullscreen mode is available in this platform
- */
- isAvailable(): boolean
- {
- return document.fullscreenEnabled ||
- !!((document as any).webkitFullscreenEnabled);
- }
-
- /**
- * Is the container currently being displayed in fullscreen mode?
- * @returns true if the container is currently being displayed in fullscreen mode
- */
- isActivated(): boolean
- {
- if(document.fullscreenElement !== undefined)
- return document.fullscreenElement === this._container;
- else if((document as any).webkitFullscreenElement !== undefined)
- return (document as any).webkitFullscreenElement === this._container;
- else
- return false;
- }
-
- /**
- * Trigger a fullscreenchange event
- */
- _triggerEvent(): void
- {
- const event = new ViewportEvent('fullscreenchange');
- this._viewport.dispatchEvent(event);
- }
- }
-
-
-
-
- /**
- * Helper class to resize the viewport
- */
- class ViewportResizer
- {
- /** the viewport to be resized */
- private readonly _viewport: Viewport;
-
- /** a helper */
- private _timeout: Nullable<ReturnType<typeof setTimeout>>;
-
- /** bound resize method */
- private readonly _resize: () => void;
-
- /** bound event trigger */
- private readonly _triggerResize: () => void;
-
- /** resize strategy */
- private _resizeStrategy: ViewportResizeStrategy;
-
-
-
-
- /**
- * Constructor
- * @param viewport the viewport to be resized
- */
- constructor(viewport: Viewport)
- {
- this._viewport = viewport;
- this._timeout = null;
- this._resize = this._onResize.bind(this);
- this._triggerResize = this.triggerResize.bind(this);
- this._resizeStrategy = new InlineResizeStrategy();
-
- // initial setup
- // (the size is yet unknown)
- this._viewport.addEventListener('resize', this._resize);
- this.triggerResize(0);
- }
-
- /**
- * Initialize
- */
- init(): void
- {
- // Configure the resize listener. We want the viewport to adjust itself
- // if the phone/screen is resized or changes orientation
- window.addEventListener('resize', this._triggerResize); // a delay is welcome
-
- // handle changes of orientation
- // (is this needed? we already listen to resize events)
- if(screen.orientation !== undefined)
- screen.orientation.addEventListener('change', this._triggerResize);
- else
- window.addEventListener('orientationchange', this._triggerResize); // deprecated
-
- // trigger a resize to setup the sizes / the CSS
- this.triggerResize(0);
- }
-
- /**
- * Release
- */
- release(): void
- {
- if(screen.orientation !== undefined)
- screen.orientation.removeEventListener('change', this._triggerResize);
- else
- window.removeEventListener('orientationchange', this._triggerResize);
-
- window.removeEventListener('resize', this._triggerResize);
-
- this._viewport.removeEventListener('resize', this._resize);
- this._resizeStrategy.clear(this._viewport);
- }
-
- /**
- * Trigger a resize event after a delay
- * @param delay in milliseconds
- */
- triggerResize(delay: number = 100): void
- {
- const event = new ViewportEvent('resize');
-
- if(delay <= 0) {
- this._viewport.dispatchEvent(event);
- return;
- }
-
- if(this._timeout !== null)
- clearTimeout(this._timeout);
-
- this._timeout = setTimeout(() => {
- this._timeout = null;
- this._viewport.dispatchEvent(event);
- }, delay);
- }
-
- /**
- * Change the resize strategy
- * @param strategy new strategy
- */
- setStrategy(strategy: ViewportResizeStrategy): void
- {
- this._resizeStrategy.clear(this._viewport);
- this._resizeStrategy = strategy;
- this.triggerResize(0);
- }
-
- /**
- * Change the resize strategy
- * @param strategyName name of the new strategy
- */
- setStrategyByName(strategyName: ViewportStyle): void
- {
- switch(strategyName) {
- case 'best-fit':
- this.setStrategy(new BestFitResizeStrategy());
- break;
-
- case 'stretch':
- this.setStrategy(new StretchResizeStrategy());
- break;
-
- case 'inline':
- this.setStrategy(new InlineResizeStrategy());
- break;
-
- default:
- throw new IllegalArgumentError('Invalid viewport style: ' + strategyName);
- }
- }
-
- /**
- * Resize callback
- */
- private _onResize(): void
- {
- const viewport = this._viewport;
-
- // Resize the drawing buffer of the foreground canvas, so that it
- // matches the desired resolution, as well as the aspect ratio of the
- // background canvas
- const foregroundCanvas = viewport.canvas;
- const virtualSize = viewport.virtualSize;
- foregroundCanvas.width = virtualSize.width;
- foregroundCanvas.height = virtualSize.height;
-
- // Resize the drawing buffer of the background canvas
- const backgroundCanvas = viewport._backgroundCanvas;
- const realSize = viewport._realSize;
- backgroundCanvas.width = realSize.width;
- backgroundCanvas.height = realSize.height;
-
- // Call strategy
- this._resizeStrategy.resize(viewport);
- }
- }
-
-
-
-
- /**
- * Resize strategies
- */
- abstract class ViewportResizeStrategy
- {
- /**
- * Resize the viewport
- * @param viewport
- */
- abstract resize(viewport: Viewport): void;
-
- /**
- * Clear CSS rules
- * @param viewport
- */
- clear(viewport: Viewport): void
- {
- viewport.container.style.cssText = '';
- viewport._subContainer.style.cssText = '';
- }
- }
-
- /**
- * Inline viewport: it follows the typical flow of a web page
- */
- class InlineResizeStrategy extends ViewportResizeStrategy
- {
- /**
- * Resize the viewport
- * @param viewport
- */
- resize(viewport: Viewport): void
- {
- const container = viewport.container;
- const subContainer = viewport._subContainer;
- const virtualSize = viewport.virtualSize;
-
- container.style.display = 'inline-block'; // fixes a potential issue of the viewport not showing up
- container.style.position = 'relative';
- container.style.left = '0px';
- container.style.top = '0px';
- container.style.width = virtualSize.width + 'px';
- container.style.height = virtualSize.height + 'px';
-
- subContainer.style.position = 'absolute';
- subContainer.style.left = '0px';
- subContainer.style.top = '0px';
- subContainer.style.width = '100%';
- subContainer.style.height = '100%';
- }
- }
-
- /**
- * Immersive viewport: it occupies the entire page
- */
- abstract class ImmersiveResizeStrategy extends ViewportResizeStrategy
- {
- /**
- * Resize the viewport
- * @param viewport
- */
- resize(viewport: Viewport): void
- {
- const CONTAINER_ZINDEX = 1000000000;
- const container = viewport.container;
-
- container.style.position = 'fixed';
- container.style.left = '0px';
- container.style.top = '0px';
- container.style.width = '100vw';
- container.style.height = '100vh';
- container.style.zIndex = String(CONTAINER_ZINDEX);
- }
- }
-
- /**
- * Immersive viewport with best-fit style: it occupies the entire page and
- * preserves the aspect ratio of the media
- */
- class BestFitResizeStrategy extends ImmersiveResizeStrategy
- {
- /**
- * Resize the viewport
- * @param viewport
- */
- resize(viewport: Viewport): void
- {
- const subContainer = viewport._subContainer;
- const windowAspectRatio = window.innerWidth / window.innerHeight;
- const viewportAspectRatio = viewport._realSize.width / viewport._realSize.height;
- let width = 1, height = 1, left = '0px', top = '0px';
-
- if(viewportAspectRatio <= windowAspectRatio) {
- height = window.innerHeight;
- width = Math.round(height * viewportAspectRatio);
- width -= width % 2;
- left = `calc(50% - ${width >>> 1}px)`;
- }
- else {
- width = window.innerWidth;
- height = Math.round(width / viewportAspectRatio);
- height -= height % 2;
- top = `calc(50% - ${height >>> 1}px)`;
- }
-
- subContainer.style.position = 'absolute';
- subContainer.style.left = left;
- subContainer.style.top = top;
- subContainer.style.width = width + 'px';
- subContainer.style.height = height + 'px';
-
- super.resize(viewport);
- }
- }
-
- /**
- * Immersive viewport with stretch style: it occupies the entire page and
- * fully stretches the media
- */
- class StretchResizeStrategy extends ImmersiveResizeStrategy
- {
- /**
- * Resize the viewport
- * @param viewport
- */
- resize(viewport: Viewport): void
- {
- const subContainer = viewport._subContainer;
-
- subContainer.style.position = 'absolute';
- subContainer.style.left = '0px';
- subContainer.style.top = '0px';
- subContainer.style.width = window.innerWidth + 'px';
- subContainer.style.height = window.innerHeight + 'px';
-
- super.resize(viewport);
- }
- }
-
-
-
-
- /**
- * Viewport
- */
- export class Viewport extends ViewportEventTarget
- {
- /** Viewport resolution (controls the size of the drawing buffer of the foreground canvas) */
- private readonly _resolution: Resolution;
-
- /** The containers */
- private readonly _containers: ViewportContainers;
-
- /** An overlay displayed in front of the augmented scene */
- private readonly _hud: HUD;
-
- /** Viewport style */
- private _style: ViewportStyle;
-
- /** The canvases of the viewport */
- private readonly _canvases: ViewportCanvases;
-
- /** Resize helper */
- private readonly _resizer: ViewportResizer;
-
- /** The current size of the underlying SpeedyMedia */
- private _mediaSize: ViewportSizeGetter;
-
- /** Fullscreen utilities */
- private readonly _fullscreen: ViewportFullscreenHelper;
-
- /** Built-in fullscreen button */
- private readonly _fullscreenButton: Nullable<FullscreenButton>;
-
-
-
-
- /**
- * Constructor
- * @param viewportSettings
- */
- constructor(viewportSettings: ViewportSettings)
- {
- const settings = Object.assign({}, DEFAULT_VIEWPORT_SETTINGS, viewportSettings);
-
- super();
-
- const guessedAspectRatio = window.innerWidth / window.innerHeight;
- const initialSize = Utils.resolution(settings.resolution, guessedAspectRatio);
- this._mediaSize = () => initialSize;
-
- this._resolution = settings.resolution;
- this._style = settings.style;
-
- this._containers = new ViewportContainers(settings.container);
- this._hud = new HUD(this._subContainer, settings.hudContainer);
- this._canvases = new ViewportCanvases(this._subContainer, initialSize, settings.canvas);
-
- this._resizer = new ViewportResizer(this);
- this._resizer.setStrategyByName(this._style);
-
- this._fullscreen = new ViewportFullscreenHelper(this);
- this._fullscreenButton = null;
-
- if(settings.fullscreenUI && this.fullscreenAvailable)
- this._fullscreenButton = new FullscreenButton(this);
- }
-
- /**
- * Viewport container
- */
- get container(): ViewportContainer
- {
- return this._containers.container;
- }
-
- /**
- * Viewport style
- */
- get style(): ViewportStyle
- {
- return this._style;
- }
-
- /**
- * Set viewport style
- */
- /*
- set style(value: ViewportStyle)
- {
- // note: the viewport style is independent of the session mode!
- if(value !== this._style) {
- this._resizer.setStrategyByName(value);
- this._style = value;
- }
- }
- */
-
- /**
- * HUD
- */
- get hud(): HUD
- {
- return this._hud;
- }
-
- /**
- * Resolution of the virtual scene
- */
- get resolution(): Resolution
- {
- return this._resolution;
- }
-
- /**
- * Size in pixels of the drawing buffer of the canvas
- * on which the virtual scene will be drawn
- */
- get virtualSize(): SpeedySize
- {
- const size = this._realSize;
- const aspectRatio = size.width / size.height;
-
- return Utils.resolution(this._resolution, aspectRatio);
- }
-
- /**
- * Is the viewport currently being displayed in fullscreen mode?
- */
- get fullscreen(): boolean
- {
- return this._fullscreen.isActivated();
- }
-
- /**
- * Is the fullscreen mode available in this platform?
- */
- get fullscreenAvailable(): boolean
- {
- return this._fullscreen.isAvailable();
- }
-
- /**
- * The canvas on which the virtual scene will be drawn
- */
- get canvas(): HTMLCanvasElement
- {
- return this._canvases.foregroundCanvas;
- }
-
- /**
- * The canvas on which the physical scene will be drawn
- * @internal
- */
- get _backgroundCanvas(): HTMLCanvasElement
- {
- return this._canvases.backgroundCanvas;
- }
-
- /**
- * Size of the drawing buffer of the background canvas, in pixels
- * @internal
- */
- get _realSize(): SpeedySize
- {
- return this._mediaSize();
- }
-
- /**
- * Sub-container of the viewport container
- * @internal
- */
- get _subContainer(): HTMLDivElement
- {
- return this._containers.subContainer;
- }
-
- /**
- * Request fullscreen mode
- * @returns promise
- */
- requestFullscreen(): SpeedyPromise<void>
- {
- return this._fullscreen.request();
- }
-
- /**
- * Exit fullscreen mode
- * @returns promise
- */
- exitFullscreen(): SpeedyPromise<void>
- {
- return this._fullscreen.exit();
- }
-
- /**
- * Convert a position given in normalized units to a corresponding pixel
- * position in canvas space. Normalized units range from -1 to +1. The
- * center of the canvas is at (0,0). The top right corner is at (1,1).
- * The bottom left corner is at (-1,-1).
- * @param position in normalized units
- * @returns an equivalent pixel position in canvas space
- */
- convertToPixels(position: Vector2): Vector2
- {
- const canvas = this.canvas;
- const x = 0.5 * (1 + position.x) * canvas.width;
- const y = -0.5 * (1 + position.y) * canvas.height;
-
- return new Vector2(x, y);
- }
-
- /**
- * Initialize the viewport (when the session starts)
- * @param getMediaSize
- * @param sessionMode
- * @internal
- */
- _init(getMediaSize: ViewportSizeGetter, sessionMode: SessionMode): void
- {
- // validate if the viewport style matches the session mode
- if(sessionMode == 'immersive') {
- if(this._style != 'best-fit' && this._style != 'stretch') {
- Utils.warning(`Invalid viewport style \"${this._style}\" for the \"${sessionMode}\" mode`);
- this._style = 'best-fit';
- this._resizer.setStrategyByName(this._style);
- }
- }
- else if(sessionMode == 'inline') {
- if(this._style != 'inline') {
- Utils.warning(`Invalid viewport style \"${this._style}\" for the \"${sessionMode}\" mode`);
- this._style = 'inline';
- this._resizer.setStrategyByName(this._style);
- }
- }
-
- // set the media size getter
- this._mediaSize = getMediaSize;
-
- // initialize the components
- this._containers.init();
- this._hud._init(HUD_ZINDEX);
- this._canvases.init();
- this._resizer.init();
- this._fullscreen.init();
- this._fullscreenButton?.init();
- }
-
- /**
- * Release the viewport (when the session ends)
- * @internal
- */
- _release(): void
- {
- this._fullscreenButton?.release();
- this._fullscreen.release();
- this._resizer.release();
- this._canvases.release();
- this._hud._release();
- this._containers.release();
- }
- }
|