import { ViewerConfiguration, IModelConfiguration, ILightConfiguration } from './../configuration/configuration'; import { Template, EventCallback } from './../templateManager'; import { AbstractViewer } from './viewer'; import { SpotLight, MirrorTexture, Plane, ShadowGenerator, Texture, BackgroundMaterial, Observable, ShadowLight, CubeTexture, BouncingBehavior, FramingBehavior, Behavior, Light, Engine, Scene, AutoRotationBehavior, AbstractMesh, Quaternion, StandardMaterial, ArcRotateCamera, ImageProcessingConfiguration, Color3, Vector3, SceneLoader, Mesh, HemisphericLight } from 'babylonjs'; import { CameraBehavior } from '../interfaces'; import { ViewerModel } from '../model/viewerModel'; import { extendClassWithConfig } from '../helper'; /** * The Default viewer is the default implementation of the AbstractViewer. * It uses the templating system to render a new canvas and controls. */ export class DefaultViewer extends AbstractViewer { /** * Create a new default viewer * @param containerElement the element in which the templates will be rendered * @param initialConfiguration the initial configuration. Defaults to extending the default configuration */ constructor(public containerElement: HTMLElement, initialConfiguration: ViewerConfiguration = { extends: 'default' }) { super(containerElement, initialConfiguration); this.onModelLoadedObservable.add(this._onModelLoaded); this.sceneManager.onSceneInitObservable.add(() => { // extendClassWithConfig(this.sceneManager.scene, this._configuration.scene); return this.sceneManager.scene; }); this.sceneManager.onLightsConfiguredObservable.add((data) => { this._configureLights(data.newConfiguration, data.model!); }) } /** * This will be executed when the templates initialize. */ protected _onTemplatesLoaded() { this.showLoadingScreen(); // navbar this._initNavbar(); // close overlay button let closeButton = document.getElementById('close-button'); if (closeButton) { closeButton.addEventListener('pointerdown', () => { this.hideOverlayScreen(); }) } return super._onTemplatesLoaded(); } private _initNavbar() { let navbar = this.templateManager.getTemplate('navBar'); if (navbar) { let navbarHeight = navbar.parent.clientHeight + 'px'; let navbarShown: boolean = true; let timeoutCancel /*: number*/; let triggerNavbar = function (show: boolean = false, evt: PointerEvent) { // only left-click on no-button. if (!navbar || evt.button > 0) return; // clear timeout timeoutCancel && clearTimeout(timeoutCancel); // if state is the same, do nothing if (show === navbarShown) return; //showing? simply show it! if (show) { navbar.parent.style.bottom = show ? '0px' : '-' + navbarHeight; navbarShown = show; } else { let visibilityTimeout = 2000; if (navbar.configuration.params && navbar.configuration.params.visibilityTimeout !== undefined) { visibilityTimeout = navbar.configuration.params.visibilityTimeout; } // not showing? set timeout until it is removed. timeoutCancel = setTimeout(function () { if (navbar) { navbar.parent.style.bottom = '-' + navbarHeight; } navbarShown = show; }, visibilityTimeout); } } this.templateManager.eventManager.registerCallback('viewer', triggerNavbar.bind(this, false), 'pointerout'); this.templateManager.eventManager.registerCallback('viewer', triggerNavbar.bind(this, true), 'pointerdown'); this.templateManager.eventManager.registerCallback('viewer', triggerNavbar.bind(this, false), 'pointerup'); this.templateManager.eventManager.registerCallback('navBar', triggerNavbar.bind(this, true), 'pointerover'); this.templateManager.eventManager.registerCallback('navBar', this.toggleFullscreen, 'pointerdown', '#fullscreen-button'); this.templateManager.eventManager.registerCallback('navBar', (data) => { if (data && data.event && data.event.target) this.sceneManager.models[0].playAnimation(data.event.target['value']); }, 'change', '#animation-selector'); } } /** * Toggle fullscreen of the entire viewer */ public toggleFullscreen = () => { let viewerTemplate = this.templateManager.getTemplate('viewer'); let viewerElement = viewerTemplate && viewerTemplate.parent; if (viewerElement) { let fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || (document).mozFullScreenElement || (document).msFullscreenElement; if (!fullscreenElement) { let requestFullScreen = viewerElement.requestFullscreen || viewerElement.webkitRequestFullscreen || (viewerElement).msRequestFullscreen || (viewerElement).mozRequestFullScreen; requestFullScreen.call(viewerElement); } else { let exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen || (document).msExitFullscreen || (document).mozCancelFullScreen exitFullscreen.call(document); } } } /** * Preparing the container element to present the viewer */ protected _prepareContainerElement() { this.containerElement.style.position = 'relative'; this.containerElement.style.display = 'flex'; } /** * This function will configure the templates and update them after a model was loaded * It is mainly responsible to changing the title and subtitle etc'. * @param model the model to be used to configure the templates by */ protected _configureTemplate(model: ViewerModel) { let navbar = this.templateManager.getTemplate('navBar'); if (!navbar) return; if (model.getAnimationNames().length > 1) { navbar.updateParams({ animations: model.getAnimationNames() }); } let modelConfiguration = model.configuration; let metadataContainer = navbar.parent.querySelector('#model-metadata'); if (metadataContainer) { if (modelConfiguration.title !== undefined) { let element = metadataContainer.querySelector('span.model-title'); if (element) { element.innerHTML = modelConfiguration.title; } } if (modelConfiguration.subtitle !== undefined) { let element = metadataContainer.querySelector('span.model-subtitle'); if (element) { element.innerHTML = modelConfiguration.subtitle; } } if (modelConfiguration.thumbnail !== undefined) { (metadataContainer.querySelector('.thumbnail')).style.backgroundImage = `url('${modelConfiguration.thumbnail}')`; } } } /** * This will load a new model to the default viewer * overriding the AbstractViewer's loadModel. * The scene will automatically be cleared of the old models, if exist. * @param model the configuration object (or URL) to load. */ public loadModel(model: any = this._configuration.model): Promise { this.showLoadingScreen(); return super.loadModel(model, true).catch((error) => { console.log(error); this.hideLoadingScreen(); this.showOverlayScreen('error'); return Promise.reject(error); }); } private _onModelLoaded = (model: ViewerModel) => { this._configureTemplate(model); // with a short timeout, making sure everything is there already. let hideLoadingDelay = 500; if (this._configuration.lab && this._configuration.lab.hideLoadingDelay !== undefined) { hideLoadingDelay = this._configuration.lab.hideLoadingDelay; } setTimeout(() => { this.hideLoadingScreen(); }, hideLoadingDelay); return; } /** * Show the overlay and the defined sub-screen. * Mainly used for help and errors * @param subScreen the name of the subScreen. Those can be defined in the configuration object */ public showOverlayScreen(subScreen: string) { let template = this.templateManager.getTemplate('overlay'); if (!template) return Promise.resolve('Overlay template not found'); return template.show((template => { var canvasRect = this.containerElement.getBoundingClientRect(); var canvasPositioning = window.getComputedStyle(this.containerElement).position; template.parent.style.display = 'flex'; template.parent.style.width = canvasRect.width + "px"; template.parent.style.height = canvasRect.height + "px"; template.parent.style.opacity = "1"; let subTemplate = this.templateManager.getTemplate(subScreen); if (!subTemplate) { return Promise.reject(subScreen + ' template not found'); } return subTemplate.show((template => { template.parent.style.display = 'flex'; return Promise.resolve(template); })); })); } /** * Hide the overlay screen. */ public hideOverlayScreen() { let template = this.templateManager.getTemplate('overlay'); if (!template) return Promise.resolve('Overlay template not found'); return template.hide((template => { template.parent.style.opacity = "0"; let onTransitionEnd = () => { template.parent.removeEventListener("transitionend", onTransitionEnd); template.parent.style.display = 'none'; } template.parent.addEventListener("transitionend", onTransitionEnd); let overlays = template.parent.querySelectorAll('.overlay'); if (overlays) { for (let i = 0; i < overlays.length; ++i) { let htmlElement = overlays.item(i); htmlElement.style.display = 'none'; } } return Promise.resolve(template); })); } /** * show the viewer (in case it was hidden) * * @param visibilityFunction an optional function to execute in order to show the container */ public show(visibilityFunction?: ((template: Template) => Promise