defaultViewer.ts 16 KB

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