defaultViewer.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. import { ViewerConfiguration, IModelConfiguration, ILightConfiguration } from './../configuration/configuration';
  2. import { Template, EventCallback } from './../templateManager';
  3. import { AbstractViewer } from './viewer';
  4. 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';
  5. import { CameraBehavior } from '../interfaces';
  6. import { ViewerModel } from '../model/viewerModel';
  7. import { extendClassWithConfig } from '../helper';
  8. /**
  9. * The Default viewer is the default implementation of the AbstractViewer.
  10. * It uses the templating system to render a new canvas and controls.
  11. */
  12. export class DefaultViewer extends AbstractViewer {
  13. /**
  14. * Create a new default viewer
  15. * @param containerElement the element in which the templates will be rendered
  16. * @param initialConfiguration the initial configuration. Defaults to extending the default configuration
  17. */
  18. constructor(public containerElement: HTMLElement, initialConfiguration: ViewerConfiguration = { extends: 'default' }) {
  19. super(containerElement, initialConfiguration);
  20. this.onModelLoadedObservable.add(this._onModelLoaded);
  21. this.sceneManager.onSceneInitObservable.add(() => {
  22. // extendClassWithConfig(this.sceneManager.scene, this._configuration.scene);
  23. return this.sceneManager.scene;
  24. });
  25. this.sceneManager.onLightsConfiguredObservable.add((data) => {
  26. this._configureLights(data.newConfiguration, data.model!);
  27. })
  28. }
  29. /**
  30. * This will be executed when the templates initialize.
  31. */
  32. protected _onTemplatesLoaded() {
  33. this.showLoadingScreen();
  34. // navbar
  35. this._initNavbar();
  36. // close overlay button
  37. let closeButton = document.getElementById('close-button');
  38. if (closeButton) {
  39. closeButton.addEventListener('pointerdown', () => {
  40. this.hideOverlayScreen();
  41. })
  42. }
  43. return super._onTemplatesLoaded();
  44. }
  45. private _initNavbar() {
  46. let navbar = this.templateManager.getTemplate('navBar');
  47. if (navbar) {
  48. let navbarHeight = navbar.parent.clientHeight + 'px';
  49. let navbarShown: boolean = true;
  50. let timeoutCancel /*: number*/;
  51. let triggerNavbar = function (show: boolean = false, evt: PointerEvent) {
  52. // only left-click on no-button.
  53. if (!navbar || evt.button > 0) return;
  54. // clear timeout
  55. timeoutCancel && clearTimeout(timeoutCancel);
  56. // if state is the same, do nothing
  57. if (show === navbarShown) return;
  58. //showing? simply show it!
  59. if (show) {
  60. navbar.parent.style.bottom = show ? '0px' : '-' + navbarHeight;
  61. navbarShown = show;
  62. } else {
  63. let visibilityTimeout = 2000;
  64. if (navbar.configuration.params && navbar.configuration.params.visibilityTimeout !== undefined) {
  65. visibilityTimeout = <number>navbar.configuration.params.visibilityTimeout;
  66. }
  67. // not showing? set timeout until it is removed.
  68. timeoutCancel = setTimeout(function () {
  69. if (navbar) {
  70. navbar.parent.style.bottom = '-' + navbarHeight;
  71. }
  72. navbarShown = show;
  73. }, visibilityTimeout);
  74. }
  75. }
  76. this.templateManager.eventManager.registerCallback('viewer', triggerNavbar.bind(this, false), 'pointerout');
  77. this.templateManager.eventManager.registerCallback('viewer', triggerNavbar.bind(this, true), 'pointerdown');
  78. this.templateManager.eventManager.registerCallback('viewer', triggerNavbar.bind(this, false), 'pointerup');
  79. this.templateManager.eventManager.registerCallback('navBar', triggerNavbar.bind(this, true), 'pointerover');
  80. this.templateManager.eventManager.registerCallback('navBar', this.toggleFullscreen, 'pointerdown', '#fullscreen-button');
  81. this.templateManager.eventManager.registerCallback('navBar', (data) => {
  82. if (data && data.event && data.event.target)
  83. this.sceneManager.models[0].playAnimation(data.event.target['value']);
  84. }, 'change', '#animation-selector');
  85. }
  86. }
  87. /**
  88. * Toggle fullscreen of the entire viewer
  89. */
  90. public toggleFullscreen = () => {
  91. let viewerTemplate = this.templateManager.getTemplate('viewer');
  92. let viewerElement = viewerTemplate && viewerTemplate.parent;
  93. if (viewerElement) {
  94. let fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || (<any>document).mozFullScreenElement || (<any>document).msFullscreenElement;
  95. if (!fullscreenElement) {
  96. let requestFullScreen = viewerElement.requestFullscreen || viewerElement.webkitRequestFullscreen || (<any>viewerElement).msRequestFullscreen || (<any>viewerElement).mozRequestFullScreen;
  97. requestFullScreen.call(viewerElement);
  98. } else {
  99. let exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen || (<any>document).msExitFullscreen || (<any>document).mozCancelFullScreen
  100. exitFullscreen.call(document);
  101. }
  102. }
  103. }
  104. /**
  105. * Preparing the container element to present the viewer
  106. */
  107. protected _prepareContainerElement() {
  108. this.containerElement.style.position = 'relative';
  109. this.containerElement.style.display = 'flex';
  110. }
  111. /**
  112. * This function will configure the templates and update them after a model was loaded
  113. * It is mainly responsible to changing the title and subtitle etc'.
  114. * @param model the model to be used to configure the templates by
  115. */
  116. protected _configureTemplate(model: ViewerModel) {
  117. let navbar = this.templateManager.getTemplate('navBar');
  118. if (!navbar) return;
  119. if (model.getAnimationNames().length > 1) {
  120. navbar.updateParams({ animations: model.getAnimationNames() });
  121. }
  122. let modelConfiguration = model.configuration;
  123. let metadataContainer = navbar.parent.querySelector('#model-metadata');
  124. if (metadataContainer) {
  125. if (modelConfiguration.title !== undefined) {
  126. let element = metadataContainer.querySelector('span.model-title');
  127. if (element) {
  128. element.innerHTML = modelConfiguration.title;
  129. }
  130. }
  131. if (modelConfiguration.subtitle !== undefined) {
  132. let element = metadataContainer.querySelector('span.model-subtitle');
  133. if (element) {
  134. element.innerHTML = modelConfiguration.subtitle;
  135. }
  136. }
  137. if (modelConfiguration.thumbnail !== undefined) {
  138. (<HTMLDivElement>metadataContainer.querySelector('.thumbnail')).style.backgroundImage = `url('${modelConfiguration.thumbnail}')`;
  139. }
  140. }
  141. }
  142. /**
  143. * This will load a new model to the default viewer
  144. * overriding the AbstractViewer's loadModel.
  145. * The scene will automatically be cleared of the old models, if exist.
  146. * @param model the configuration object (or URL) to load.
  147. */
  148. public loadModel(model: any = this._configuration.model): Promise<ViewerModel> {
  149. this.showLoadingScreen();
  150. return super.loadModel(model, true).catch((error) => {
  151. console.log(error);
  152. this.hideLoadingScreen();
  153. this.showOverlayScreen('error');
  154. return Promise.reject(error);
  155. });
  156. }
  157. private _onModelLoaded = (model: ViewerModel) => {
  158. this._configureTemplate(model);
  159. // with a short timeout, making sure everything is there already.
  160. let hideLoadingDelay = 500;
  161. if (this._configuration.lab && this._configuration.lab.hideLoadingDelay !== undefined) {
  162. hideLoadingDelay = this._configuration.lab.hideLoadingDelay;
  163. }
  164. setTimeout(() => {
  165. this.hideLoadingScreen();
  166. }, hideLoadingDelay);
  167. return;
  168. }
  169. /**
  170. * Show the overlay and the defined sub-screen.
  171. * Mainly used for help and errors
  172. * @param subScreen the name of the subScreen. Those can be defined in the configuration object
  173. */
  174. public showOverlayScreen(subScreen: string) {
  175. let template = this.templateManager.getTemplate('overlay');
  176. if (!template) return Promise.resolve('Overlay template not found');
  177. return template.show((template => {
  178. var canvasRect = this.containerElement.getBoundingClientRect();
  179. var canvasPositioning = window.getComputedStyle(this.containerElement).position;
  180. template.parent.style.display = 'flex';
  181. template.parent.style.width = canvasRect.width + "px";
  182. template.parent.style.height = canvasRect.height + "px";
  183. template.parent.style.opacity = "1";
  184. let subTemplate = this.templateManager.getTemplate(subScreen);
  185. if (!subTemplate) {
  186. return Promise.reject(subScreen + ' template not found');
  187. }
  188. return subTemplate.show((template => {
  189. template.parent.style.display = 'flex';
  190. return Promise.resolve(template);
  191. }));
  192. }));
  193. }
  194. /**
  195. * Hide the overlay screen.
  196. */
  197. public hideOverlayScreen() {
  198. let template = this.templateManager.getTemplate('overlay');
  199. if (!template) return Promise.resolve('Overlay template not found');
  200. return template.hide((template => {
  201. template.parent.style.opacity = "0";
  202. let onTransitionEnd = () => {
  203. template.parent.removeEventListener("transitionend", onTransitionEnd);
  204. template.parent.style.display = 'none';
  205. }
  206. template.parent.addEventListener("transitionend", onTransitionEnd);
  207. let overlays = template.parent.querySelectorAll('.overlay');
  208. if (overlays) {
  209. for (let i = 0; i < overlays.length; ++i) {
  210. let htmlElement = <HTMLElement>overlays.item(i);
  211. htmlElement.style.display = 'none';
  212. }
  213. }
  214. return Promise.resolve(template);
  215. }));
  216. }
  217. /**
  218. * show the viewer (in case it was hidden)
  219. *
  220. * @param visibilityFunction an optional function to execute in order to show the container
  221. */
  222. public show(visibilityFunction?: ((template: Template) => Promise<Template>)): Promise<Template> {
  223. let template = this.templateManager.getTemplate('main');
  224. //not possible, but yet:
  225. if (!template) return Promise.reject('Main template not found');
  226. return template.show(visibilityFunction);
  227. }
  228. /**
  229. * hide the viewer (in case it is visible)
  230. *
  231. * @param visibilityFunction an optional function to execute in order to hide the container
  232. */
  233. public hide(visibilityFunction?: ((template: Template) => Promise<Template>)) {
  234. let template = this.templateManager.getTemplate('main');
  235. //not possible, but yet:
  236. if (!template) return Promise.reject('Main template not found');
  237. return template.hide(visibilityFunction);
  238. }
  239. /**
  240. * Show the loading screen.
  241. * The loading screen can be configured using the configuration object
  242. */
  243. public showLoadingScreen() {
  244. let template = this.templateManager.getTemplate('loadingScreen');
  245. if (!template) return Promise.resolve('Loading Screen template not found');
  246. return template.show((template => {
  247. var canvasRect = this.containerElement.getBoundingClientRect();
  248. var canvasPositioning = window.getComputedStyle(this.containerElement).position;
  249. template.parent.style.display = 'flex';
  250. template.parent.style.width = canvasRect.width + "px";
  251. template.parent.style.height = canvasRect.height + "px";
  252. template.parent.style.opacity = "1";
  253. // from the configuration!!!
  254. template.parent.style.backgroundColor = "black";
  255. return Promise.resolve(template);
  256. }));
  257. }
  258. /**
  259. * Hide the loading screen
  260. */
  261. public hideLoadingScreen() {
  262. let template = this.templateManager.getTemplate('loadingScreen');
  263. if (!template) return Promise.resolve('Loading Screen template not found');
  264. return template.hide((template => {
  265. template.parent.style.opacity = "0";
  266. let onTransitionEnd = () => {
  267. template.parent.removeEventListener("transitionend", onTransitionEnd);
  268. template.parent.style.display = 'none';
  269. }
  270. template.parent.addEventListener("transitionend", onTransitionEnd);
  271. return Promise.resolve(template);
  272. }));
  273. }
  274. /**
  275. * An extension of the light configuration of the abstract viewer.
  276. * @param lightsConfiguration the light configuration to use
  277. * @param model the model that will be used to configure the lights (if the lights are model-dependant)
  278. */
  279. private _configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean } = {}, model?: ViewerModel) {
  280. // labs feature - flashlight
  281. if (this._configuration.lab && this._configuration.lab.flashlight) {
  282. let pointerPosition = Vector3.Zero();
  283. let lightTarget;
  284. let angle = 0.5;
  285. let exponent = Math.PI / 2;
  286. if (typeof this._configuration.lab.flashlight === "object") {
  287. exponent = this._configuration.lab.flashlight.exponent || exponent;
  288. angle = this._configuration.lab.flashlight.angle || angle;
  289. }
  290. var flashlight = new SpotLight("flashlight", Vector3.Zero(),
  291. Vector3.Zero(), exponent, angle, this.sceneManager.scene);
  292. if (typeof this._configuration.lab.flashlight === "object") {
  293. flashlight.intensity = this._configuration.lab.flashlight.intensity || flashlight.intensity;
  294. if (this._configuration.lab.flashlight.diffuse) {
  295. flashlight.diffuse.r = this._configuration.lab.flashlight.diffuse.r;
  296. flashlight.diffuse.g = this._configuration.lab.flashlight.diffuse.g;
  297. flashlight.diffuse.b = this._configuration.lab.flashlight.diffuse.b;
  298. }
  299. if (this._configuration.lab.flashlight.specular) {
  300. flashlight.specular.r = this._configuration.lab.flashlight.specular.r;
  301. flashlight.specular.g = this._configuration.lab.flashlight.specular.g;
  302. flashlight.specular.b = this._configuration.lab.flashlight.specular.b;
  303. }
  304. }
  305. this.sceneManager.scene.constantlyUpdateMeshUnderPointer = true;
  306. this.sceneManager.scene.onPointerObservable.add((eventData, eventState) => {
  307. if (eventData.type === 4 && eventData.pickInfo) {
  308. lightTarget = (eventData.pickInfo.pickedPoint);
  309. } else {
  310. lightTarget = undefined;
  311. }
  312. });
  313. let updateFlashlightFunction = () => {
  314. if (this.sceneManager.camera && flashlight) {
  315. flashlight.position.copyFrom(this.sceneManager.camera.position);
  316. if (lightTarget) {
  317. lightTarget.subtractToRef(flashlight.position, flashlight.direction);
  318. }
  319. }
  320. }
  321. this.sceneManager.scene.registerBeforeRender(updateFlashlightFunction);
  322. this._registeredOnBeforeRenderFunctions.push(updateFlashlightFunction);
  323. }
  324. }
  325. }