defaultViewer.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  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. export class DefaultViewer extends AbstractViewer {
  7. constructor(public containerElement: HTMLElement, initialConfiguration: ViewerConfiguration = { extends: 'default' }) {
  8. super(containerElement, initialConfiguration);
  9. this.onModelLoadedObservable.add(this.onModelLoaded);
  10. }
  11. public initScene(): Promise<Scene> {
  12. return super.initScene().then(() => {
  13. this.extendClassWithConfig(this.scene, this.configuration.scene);
  14. return this.scene;
  15. })
  16. }
  17. protected onTemplatesLoaded() {
  18. this.showLoadingScreen();
  19. // navbar
  20. this.initNavbar();
  21. // close overlay button
  22. let closeButton = document.getElementById('close-button');
  23. if (closeButton) {
  24. closeButton.addEventListener('pointerdown', () => {
  25. this.hideOverlayScreen();
  26. })
  27. }
  28. return super.onTemplatesLoaded();
  29. }
  30. private initNavbar() {
  31. let navbar = this.templateManager.getTemplate('navBar');
  32. if (navbar) {
  33. let navbarHeight = navbar.parent.clientHeight + 'px';
  34. let navbarShown: boolean = true;
  35. let timeoutCancel /*: number*/;
  36. let triggerNavbar = function (show: boolean = false, evt: PointerEvent) {
  37. // only left-click on no-button.
  38. if (!navbar || evt.button > 0) return;
  39. // clear timeout
  40. timeoutCancel && clearTimeout(timeoutCancel);
  41. // if state is the same, do nothing
  42. if (show === navbarShown) return;
  43. //showing? simply show it!
  44. if (show) {
  45. navbar.parent.style.bottom = show ? '0px' : '-' + navbarHeight;
  46. navbarShown = show;
  47. } else {
  48. let visibilityTimeout = 2000;
  49. if (navbar.configuration.params && navbar.configuration.params.visibilityTimeout !== undefined) {
  50. visibilityTimeout = <number>navbar.configuration.params.visibilityTimeout;
  51. }
  52. // not showing? set timeout until it is removed.
  53. timeoutCancel = setTimeout(function () {
  54. if (navbar) {
  55. navbar.parent.style.bottom = '-' + navbarHeight;
  56. }
  57. navbarShown = show;
  58. }, visibilityTimeout);
  59. }
  60. }
  61. this.templateManager.eventManager.registerCallback('viewer', triggerNavbar.bind(this, false), 'pointerout');
  62. this.templateManager.eventManager.registerCallback('viewer', triggerNavbar.bind(this, true), 'pointerdown');
  63. this.templateManager.eventManager.registerCallback('viewer', triggerNavbar.bind(this, false), 'pointerup');
  64. this.templateManager.eventManager.registerCallback('navBar', triggerNavbar.bind(this, true), 'pointerover');
  65. // other events
  66. let viewerTemplate = this.templateManager.getTemplate('viewer');
  67. let viewerElement = viewerTemplate && viewerTemplate.parent;
  68. // full screen
  69. let triggerFullscren = (eventData: EventCallback) => {
  70. if (viewerElement) {
  71. let fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || (<any>document).mozFullScreenElement || (<any>document).msFullscreenElement;
  72. if (!fullscreenElement) {
  73. let requestFullScreen = viewerElement.requestFullscreen || viewerElement.webkitRequestFullscreen || (<any>viewerElement).msRequestFullscreen || (<any>viewerElement).mozRequestFullScreen;
  74. requestFullScreen.call(viewerElement);
  75. } else {
  76. let exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen || (<any>document).msExitFullscreen || (<any>document).mozCancelFullScreen
  77. exitFullscreen.call(document);
  78. }
  79. }
  80. }
  81. this.templateManager.eventManager.registerCallback('navBar', triggerFullscren, 'pointerdown', '#fullscreen-button');
  82. }
  83. }
  84. protected prepareContainerElement() {
  85. this.containerElement.style.position = 'relative';
  86. this.containerElement.style.display = 'flex';
  87. }
  88. protected configureModel(modelConfiguration: Partial<IModelConfiguration>) {
  89. super.configureModel(modelConfiguration);
  90. let navbar = this.templateManager.getTemplate('navBar');
  91. if (!navbar) return;
  92. let metadataContainer = navbar.parent.querySelector('#model-metadata');
  93. if (metadataContainer) {
  94. if (modelConfiguration.title !== undefined) {
  95. let element = metadataContainer.querySelector('span.model-title');
  96. if (element) {
  97. element.innerHTML = modelConfiguration.title;
  98. }
  99. }
  100. if (modelConfiguration.subtitle !== undefined) {
  101. let element = metadataContainer.querySelector('span.model-subtitle');
  102. if (element) {
  103. element.innerHTML = modelConfiguration.subtitle;
  104. }
  105. }
  106. if (modelConfiguration.thumbnail !== undefined) {
  107. (<HTMLDivElement>metadataContainer.querySelector('.thumbnail')).style.backgroundImage = `url('${modelConfiguration.thumbnail}')`;
  108. }
  109. }
  110. }
  111. public loadModel(model: any = this.configuration.model): Promise<Scene> {
  112. this.showLoadingScreen();
  113. return super.loadModel(model, true).catch((error) => {
  114. console.log(error);
  115. this.hideLoadingScreen();
  116. this.showOverlayScreen('error');
  117. return this.scene;
  118. });
  119. }
  120. private onModelLoaded = (meshes: Array<AbstractMesh>) => {
  121. // with a short timeout, making sure everything is there already.
  122. let hideLoadingDelay = 500;
  123. if (this.configuration.lab && this.configuration.lab.hideLoadingDelay !== undefined) {
  124. hideLoadingDelay = this.configuration.lab.hideLoadingDelay;
  125. }
  126. setTimeout(() => {
  127. this.hideLoadingScreen();
  128. }, hideLoadingDelay);
  129. meshes[0].rotation.y += Math.PI;
  130. return; //this.initEnvironment(meshes);
  131. }
  132. /*protected initEnvironment(focusMeshes: Array<AbstractMesh> = []): Promise<Scene> {
  133. if (this.configuration.skybox) {
  134. // Define a general environment textue
  135. let texture;
  136. // this is obligatory, but still - making sure it is there.
  137. if (this.configuration.skybox.cubeTexture) {
  138. if (typeof this.configuration.skybox.cubeTexture.url === 'string') {
  139. texture = CubeTexture.CreateFromPrefilteredData(this.configuration.skybox.cubeTexture.url, this.scene);
  140. } else {
  141. texture = CubeTexture.CreateFromImages(this.configuration.skybox.cubeTexture.url, this.scene, this.configuration.skybox.cubeTexture.noMipMap);
  142. }
  143. }
  144. if (texture) {
  145. this.extendClassWithConfig(texture, this.configuration.skybox.cubeTexture);
  146. let scale = this.configuration.skybox.scale || this.scene.activeCamera && (this.scene.activeCamera.maxZ - this.scene.activeCamera.minZ) / 2 || 1;
  147. let box = this.scene.createDefaultSkybox(texture, this.configuration.skybox.pbr, scale, this.configuration.skybox.blur);
  148. // before extending, set the material's imageprocessing configuration object, if needed:
  149. if (this.configuration.skybox.material && this.configuration.skybox.material.imageProcessingConfiguration && box) {
  150. (<StandardMaterial>box.material).imageProcessingConfiguration = new ImageProcessingConfiguration();
  151. }
  152. this.extendClassWithConfig(box, this.configuration.skybox);
  153. box && focusMeshes.push(box);
  154. }
  155. }
  156. if (this.configuration.ground) {
  157. let groundConfig = (typeof this.configuration.ground === 'boolean') ? {} : this.configuration.ground;
  158. let groundSize = groundConfig.size || (this.configuration.skybox && this.configuration.skybox.scale) || 3000;
  159. let ground = Mesh.CreatePlane("BackgroundPlane", groundSize, this.scene);
  160. let backgroundMaterial = new BackgroundMaterial('groundmat', this.scene);
  161. ground.rotation.x = Math.PI / 2; // Face up by default.
  162. ground.receiveShadows = groundConfig.receiveShadows || false;
  163. // position the ground correctly
  164. let groundPosition = focusMeshes[0].getHierarchyBoundingVectors().min.y;
  165. ground.position.y = groundPosition;
  166. // default values
  167. backgroundMaterial.alpha = 0.9;
  168. backgroundMaterial.alphaMode = Engine.ALPHA_PREMULTIPLIED_PORTERDUFF;
  169. backgroundMaterial.shadowLevel = 0.5;
  170. backgroundMaterial.primaryLevel = 1;
  171. backgroundMaterial.primaryColor = new Color3(0.2, 0.2, 0.3).toLinearSpace().scale(3);
  172. backgroundMaterial.secondaryLevel = 0;
  173. backgroundMaterial.tertiaryLevel = 0;
  174. backgroundMaterial.useRGBColor = false;
  175. backgroundMaterial.enableNoise = true;
  176. // if config provided, extend the default values
  177. if (groundConfig.material) {
  178. this.extendClassWithConfig(ground, ground.material);
  179. }
  180. ground.material = backgroundMaterial;
  181. if (this.configuration.ground === true || groundConfig.shadowOnly) {
  182. // shadow only:
  183. ground.receiveShadows = true;
  184. const diffuseTexture = new Texture("https://assets.babylonjs.com/environments/backgroundGround.png", this.scene);
  185. diffuseTexture.gammaSpace = false;
  186. diffuseTexture.hasAlpha = true;
  187. backgroundMaterial.diffuseTexture = diffuseTexture;
  188. } else if (groundConfig.mirror) {
  189. var mirror = new MirrorTexture("mirror", 512, this.scene);
  190. mirror.mirrorPlane = new Plane(0, -1, 0, 0);
  191. mirror.renderList = mirror.renderList || [];
  192. focusMeshes.length && focusMeshes.forEach(m => {
  193. m && mirror.renderList && mirror.renderList.push(m);
  194. });
  195. backgroundMaterial.reflectionTexture = mirror;
  196. } else {
  197. if (groundConfig.material) {
  198. if (groundConfig.material.diffuseTexture) {
  199. const diffuseTexture = new Texture(groundConfig.material.diffuseTexture, this.scene);
  200. backgroundMaterial.diffuseTexture = diffuseTexture;
  201. }
  202. }
  203. // ground.material = new StandardMaterial('groundmat', this.scene);
  204. }
  205. //default configuration
  206. if (this.configuration.ground === true) {
  207. ground.receiveShadows = true;
  208. if (ground.material)
  209. ground.material.alpha = 0.4;
  210. }
  211. this.extendClassWithConfig(ground, groundConfig);
  212. }
  213. return Promise.resolve(this.scene);
  214. }*/
  215. public showOverlayScreen(subScreen: string) {
  216. let template = this.templateManager.getTemplate('overlay');
  217. if (!template) return Promise.reject('Overlay template not found');
  218. return template.show((template => {
  219. var canvasRect = this.containerElement.getBoundingClientRect();
  220. var canvasPositioning = window.getComputedStyle(this.containerElement).position;
  221. template.parent.style.display = 'flex';
  222. template.parent.style.width = canvasRect.width + "px";
  223. template.parent.style.height = canvasRect.height + "px";
  224. template.parent.style.opacity = "1";
  225. let subTemplate = this.templateManager.getTemplate(subScreen);
  226. if (!subTemplate) {
  227. return Promise.reject(subScreen + ' template not found');
  228. }
  229. return subTemplate.show((template => {
  230. template.parent.style.display = 'flex';
  231. return Promise.resolve(template);
  232. }));
  233. }));
  234. }
  235. public hideOverlayScreen() {
  236. let template = this.templateManager.getTemplate('overlay');
  237. if (!template) return Promise.reject('Overlay template not found');
  238. return template.hide((template => {
  239. template.parent.style.opacity = "0";
  240. let onTransitionEnd = () => {
  241. template.parent.removeEventListener("transitionend", onTransitionEnd);
  242. template.parent.style.display = 'none';
  243. }
  244. template.parent.addEventListener("transitionend", onTransitionEnd);
  245. let overlays = template.parent.querySelectorAll('.overlay');
  246. if (overlays) {
  247. for (let i = 0; i < overlays.length; ++i) {
  248. let htmlElement = <HTMLElement>overlays.item(i);
  249. htmlElement.style.display = 'none';
  250. }
  251. }
  252. /*return this.templateManager.getTemplate(subScreen).show((template => {
  253. template.parent.style.display = 'none';
  254. return Promise.resolve(template);
  255. }));*/
  256. return Promise.resolve(template);
  257. }));
  258. }
  259. public showLoadingScreen() {
  260. let template = this.templateManager.getTemplate('loadingScreen');
  261. if (!template) return Promise.reject('oading Screen template not found');
  262. return template.show((template => {
  263. var canvasRect = this.containerElement.getBoundingClientRect();
  264. var canvasPositioning = window.getComputedStyle(this.containerElement).position;
  265. template.parent.style.display = 'flex';
  266. template.parent.style.width = canvasRect.width + "px";
  267. template.parent.style.height = canvasRect.height + "px";
  268. template.parent.style.opacity = "1";
  269. // from the configuration!!!
  270. template.parent.style.backgroundColor = "black";
  271. return Promise.resolve(template);
  272. }));
  273. }
  274. public hideLoadingScreen() {
  275. let template = this.templateManager.getTemplate('loadingScreen');
  276. if (!template) return Promise.reject('oading Screen template not found');
  277. return template.hide((template => {
  278. template.parent.style.opacity = "0";
  279. let onTransitionEnd = () => {
  280. template.parent.removeEventListener("transitionend", onTransitionEnd);
  281. template.parent.style.display = 'none';
  282. }
  283. template.parent.addEventListener("transitionend", onTransitionEnd);
  284. return Promise.resolve(template);
  285. }));
  286. }
  287. protected configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean } = {}, focusMeshes: Array<AbstractMesh> = this.scene.meshes) {
  288. super.configureLights(lightsConfiguration, focusMeshes);
  289. console.log("flashlight", this.configuration.lab);
  290. // labs feature - flashlight
  291. if (this.configuration.lab && this.configuration.lab.flashlight) {
  292. let pointerPosition = BABYLON.Vector3.Zero();
  293. let lightTarget;
  294. let angle = 0.5;
  295. let exponent = Math.PI / 2;
  296. if (typeof this.configuration.lab.flashlight === "object") {
  297. exponent = this.configuration.lab.flashlight.exponent || exponent;
  298. angle = this.configuration.lab.flashlight.angle || angle;
  299. }
  300. var flashlight = new SpotLight("flashlight", Vector3.Zero(),
  301. Vector3.Zero(), exponent, angle, this.scene);
  302. if (typeof this.configuration.lab.flashlight === "object") {
  303. flashlight.intensity = this.configuration.lab.flashlight.intensity || flashlight.intensity;
  304. if (this.configuration.lab.flashlight.diffuse) {
  305. flashlight.diffuse.r = this.configuration.lab.flashlight.diffuse.r;
  306. flashlight.diffuse.g = this.configuration.lab.flashlight.diffuse.g;
  307. flashlight.diffuse.b = this.configuration.lab.flashlight.diffuse.b;
  308. }
  309. if (this.configuration.lab.flashlight.specular) {
  310. flashlight.specular.r = this.configuration.lab.flashlight.specular.r;
  311. flashlight.specular.g = this.configuration.lab.flashlight.specular.g;
  312. flashlight.specular.b = this.configuration.lab.flashlight.specular.b;
  313. }
  314. }
  315. this.scene.constantlyUpdateMeshUnderPointer = true;
  316. this.scene.onPointerObservable.add((eventData, eventState) => {
  317. if (eventData.type === 4 && eventData.pickInfo) {
  318. lightTarget = (eventData.pickInfo.pickedPoint);
  319. } else {
  320. lightTarget = undefined;
  321. }
  322. });
  323. let updateFlashlightFunction = () => {
  324. if (this.camera && flashlight) {
  325. flashlight.position.copyFrom(this.camera.position);
  326. if (lightTarget) {
  327. lightTarget.subtractToRef(flashlight.position, flashlight.direction);
  328. }
  329. }
  330. }
  331. this.scene.registerBeforeRender(updateFlashlightFunction);
  332. this.registeredOnBeforerenderFunctions.push(updateFlashlightFunction);
  333. }
  334. }
  335. }