utilityLayerRenderer.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. import { Scene } from "../scene.js";
  2. import { Observable } from "../Misc/observable.js";
  3. import { PointerInfo, PointerEventTypes } from "../Events/pointerEvents.js";
  4. import { PickingInfo } from "../Collisions/pickingInfo.js";
  5. import { EngineStore } from "../Engines/engineStore.js";
  6. import { HemisphericLight } from "../Lights/hemisphericLight.js";
  7. import { Vector3 } from "../Maths/math.vector.js";
  8. import { Color3 } from "../Maths/math.color.js";
  9. /**
  10. * Renders a layer on top of an existing scene
  11. */
  12. export class UtilityLayerRenderer {
  13. /**
  14. * Gets the camera that is used to render the utility layer (when not set, this will be the last active camera)
  15. * @param getRigParentIfPossible if the current active camera is a rig camera, should its parent camera be returned
  16. * @returns the camera that is used when rendering the utility layer
  17. */
  18. getRenderCamera(getRigParentIfPossible) {
  19. if (this._renderCamera) {
  20. return this._renderCamera;
  21. }
  22. else {
  23. let activeCam;
  24. if (this.originalScene.activeCameras && this.originalScene.activeCameras.length > 1) {
  25. activeCam = this.originalScene.activeCameras[this.originalScene.activeCameras.length - 1];
  26. }
  27. else {
  28. activeCam = this.originalScene.activeCamera;
  29. }
  30. if (getRigParentIfPossible && activeCam && activeCam.isRigCamera) {
  31. return activeCam.rigParent;
  32. }
  33. return activeCam;
  34. }
  35. }
  36. /**
  37. * Sets the camera that should be used when rendering the utility layer (If set to null the last active camera will be used)
  38. * @param cam the camera that should be used when rendering the utility layer
  39. */
  40. setRenderCamera(cam) {
  41. this._renderCamera = cam;
  42. }
  43. /**
  44. * @internal
  45. * Light which used by gizmos to get light shading
  46. */
  47. _getSharedGizmoLight() {
  48. if (!this._sharedGizmoLight) {
  49. this._sharedGizmoLight = new HemisphericLight("shared gizmo light", new Vector3(0, 1, 0), this.utilityLayerScene);
  50. this._sharedGizmoLight.intensity = 2;
  51. this._sharedGizmoLight.groundColor = Color3.Gray();
  52. }
  53. return this._sharedGizmoLight;
  54. }
  55. /**
  56. * A shared utility layer that can be used to overlay objects into a scene (Depth map of the previous scene is cleared before drawing on top of it)
  57. */
  58. static get DefaultUtilityLayer() {
  59. if (UtilityLayerRenderer._DefaultUtilityLayer == null) {
  60. return UtilityLayerRenderer._CreateDefaultUtilityLayerFromScene(EngineStore.LastCreatedScene);
  61. }
  62. return UtilityLayerRenderer._DefaultUtilityLayer;
  63. }
  64. /**
  65. * Creates an utility layer, and set it as a default utility layer
  66. * @param scene associated scene
  67. * @internal
  68. */
  69. static _CreateDefaultUtilityLayerFromScene(scene) {
  70. UtilityLayerRenderer._DefaultUtilityLayer = new UtilityLayerRenderer(scene);
  71. UtilityLayerRenderer._DefaultUtilityLayer.originalScene.onDisposeObservable.addOnce(() => {
  72. UtilityLayerRenderer._DefaultUtilityLayer = null;
  73. });
  74. return UtilityLayerRenderer._DefaultUtilityLayer;
  75. }
  76. /**
  77. * A shared utility layer that can be used to embed objects into a scene (Depth map of the previous scene is not cleared before drawing on top of it)
  78. */
  79. static get DefaultKeepDepthUtilityLayer() {
  80. if (UtilityLayerRenderer._DefaultKeepDepthUtilityLayer == null) {
  81. UtilityLayerRenderer._DefaultKeepDepthUtilityLayer = new UtilityLayerRenderer(EngineStore.LastCreatedScene);
  82. UtilityLayerRenderer._DefaultKeepDepthUtilityLayer.utilityLayerScene.autoClearDepthAndStencil = false;
  83. UtilityLayerRenderer._DefaultKeepDepthUtilityLayer.originalScene.onDisposeObservable.addOnce(() => {
  84. UtilityLayerRenderer._DefaultKeepDepthUtilityLayer = null;
  85. });
  86. }
  87. return UtilityLayerRenderer._DefaultKeepDepthUtilityLayer;
  88. }
  89. /**
  90. * Instantiates a UtilityLayerRenderer
  91. * @param originalScene the original scene that will be rendered on top of
  92. * @param handleEvents boolean indicating if the utility layer should handle events
  93. */
  94. constructor(
  95. /** the original scene that will be rendered on top of */
  96. originalScene, handleEvents = true) {
  97. this.originalScene = originalScene;
  98. this._pointerCaptures = {};
  99. this._lastPointerEvents = {};
  100. this._sharedGizmoLight = null;
  101. this._renderCamera = null;
  102. /**
  103. * If the picking should be done on the utility layer prior to the actual scene (Default: true)
  104. */
  105. this.pickUtilitySceneFirst = true;
  106. /**
  107. * If the utility layer should automatically be rendered on top of existing scene
  108. */
  109. this.shouldRender = true;
  110. /**
  111. * If set to true, only pointer down onPointerObservable events will be blocked when picking is occluded by original scene
  112. */
  113. this.onlyCheckPointerDownEvents = true;
  114. /**
  115. * If set to false, only pointerUp, pointerDown and pointerMove will be sent to the utilityLayerScene (false by default)
  116. */
  117. this.processAllEvents = false;
  118. /**
  119. * Set to false to disable picking
  120. */
  121. this.pickingEnabled = true;
  122. /**
  123. * Observable raised when the pointer moves from the utility layer scene to the main scene
  124. */
  125. this.onPointerOutObservable = new Observable();
  126. // Create scene which will be rendered in the foreground and remove it from being referenced by engine to avoid interfering with existing app
  127. this.utilityLayerScene = new Scene(originalScene.getEngine(), { virtual: true });
  128. this.utilityLayerScene.useRightHandedSystem = originalScene.useRightHandedSystem;
  129. this.utilityLayerScene._allowPostProcessClearColor = false;
  130. // Deactivate post processes
  131. this.utilityLayerScene.postProcessesEnabled = false;
  132. // Detach controls on utility scene, events will be fired by logic below to handle picking priority
  133. this.utilityLayerScene.detachControl();
  134. if (handleEvents) {
  135. this._originalPointerObserver = originalScene.onPrePointerObservable.add((prePointerInfo) => {
  136. if (!this.utilityLayerScene.activeCamera) {
  137. return;
  138. }
  139. if (!this.pickingEnabled) {
  140. return;
  141. }
  142. if (!this.processAllEvents) {
  143. if (prePointerInfo.type !== PointerEventTypes.POINTERMOVE &&
  144. prePointerInfo.type !== PointerEventTypes.POINTERUP &&
  145. prePointerInfo.type !== PointerEventTypes.POINTERDOWN &&
  146. prePointerInfo.type !== PointerEventTypes.POINTERDOUBLETAP) {
  147. return;
  148. }
  149. }
  150. this.utilityLayerScene.pointerX = originalScene.pointerX;
  151. this.utilityLayerScene.pointerY = originalScene.pointerY;
  152. const pointerEvent = prePointerInfo.event;
  153. if (originalScene.isPointerCaptured(pointerEvent.pointerId)) {
  154. this._pointerCaptures[pointerEvent.pointerId] = false;
  155. return;
  156. }
  157. const getNearPickDataForScene = (scene) => {
  158. let scenePick = null;
  159. if (prePointerInfo.nearInteractionPickingInfo) {
  160. if (prePointerInfo.nearInteractionPickingInfo.pickedMesh.getScene() == scene) {
  161. scenePick = prePointerInfo.nearInteractionPickingInfo;
  162. }
  163. else {
  164. scenePick = new PickingInfo();
  165. }
  166. }
  167. else if (scene !== this.utilityLayerScene && prePointerInfo.originalPickingInfo) {
  168. scenePick = prePointerInfo.originalPickingInfo;
  169. }
  170. else {
  171. let previousActiveCamera = null;
  172. // If a camera is set for rendering with this layer
  173. // it will also be used for the ray computation
  174. // To preserve back compat and because scene.pick always use activeCamera
  175. // it's substituted temporarily and a new scenePick is forced.
  176. // otherwise, the ray with previously active camera is always used.
  177. // It's set back to previous activeCamera after operation.
  178. if (this._renderCamera) {
  179. previousActiveCamera = scene._activeCamera;
  180. scene._activeCamera = this._renderCamera;
  181. prePointerInfo.ray = null;
  182. }
  183. scenePick = prePointerInfo.ray ? scene.pickWithRay(prePointerInfo.ray) : scene.pick(originalScene.pointerX, originalScene.pointerY);
  184. if (previousActiveCamera) {
  185. scene._activeCamera = previousActiveCamera;
  186. }
  187. }
  188. return scenePick;
  189. };
  190. const utilityScenePick = getNearPickDataForScene(this.utilityLayerScene);
  191. if (!prePointerInfo.ray && utilityScenePick) {
  192. prePointerInfo.ray = utilityScenePick.ray;
  193. }
  194. // always fire the prepointer observable
  195. this.utilityLayerScene.onPrePointerObservable.notifyObservers(prePointerInfo);
  196. // allow every non pointer down event to flow to the utility layer
  197. if (this.onlyCheckPointerDownEvents && prePointerInfo.type != PointerEventTypes.POINTERDOWN) {
  198. if (!prePointerInfo.skipOnPointerObservable) {
  199. this.utilityLayerScene.onPointerObservable.notifyObservers(new PointerInfo(prePointerInfo.type, prePointerInfo.event, utilityScenePick), prePointerInfo.type);
  200. }
  201. if (prePointerInfo.type === PointerEventTypes.POINTERUP && this._pointerCaptures[pointerEvent.pointerId]) {
  202. this._pointerCaptures[pointerEvent.pointerId] = false;
  203. }
  204. return;
  205. }
  206. if (this.utilityLayerScene.autoClearDepthAndStencil || this.pickUtilitySceneFirst) {
  207. // If this layer is an overlay, check if this layer was hit and if so, skip pointer events for the main scene
  208. if (utilityScenePick && utilityScenePick.hit) {
  209. if (!prePointerInfo.skipOnPointerObservable) {
  210. this.utilityLayerScene.onPointerObservable.notifyObservers(new PointerInfo(prePointerInfo.type, prePointerInfo.event, utilityScenePick), prePointerInfo.type);
  211. }
  212. prePointerInfo.skipOnPointerObservable = true;
  213. }
  214. }
  215. else {
  216. const originalScenePick = getNearPickDataForScene(originalScene);
  217. const pointerEvent = prePointerInfo.event;
  218. // If the layer can be occluded by the original scene, only fire pointer events to the first layer that hit they ray
  219. if (originalScenePick && utilityScenePick) {
  220. // No pick in utility scene
  221. if (utilityScenePick.distance === 0 && originalScenePick.pickedMesh) {
  222. if (this.mainSceneTrackerPredicate && this.mainSceneTrackerPredicate(originalScenePick.pickedMesh)) {
  223. // We touched an utility mesh present in the main scene
  224. this._notifyObservers(prePointerInfo, originalScenePick, pointerEvent);
  225. prePointerInfo.skipOnPointerObservable = true;
  226. }
  227. else if (prePointerInfo.type === PointerEventTypes.POINTERDOWN) {
  228. this._pointerCaptures[pointerEvent.pointerId] = true;
  229. }
  230. else if (prePointerInfo.type === PointerEventTypes.POINTERMOVE || prePointerInfo.type === PointerEventTypes.POINTERUP) {
  231. if (this._lastPointerEvents[pointerEvent.pointerId]) {
  232. // We need to send a last pointerup to the utilityLayerScene to make sure animations can complete
  233. this.onPointerOutObservable.notifyObservers(pointerEvent.pointerId);
  234. delete this._lastPointerEvents[pointerEvent.pointerId];
  235. }
  236. this._notifyObservers(prePointerInfo, originalScenePick, pointerEvent);
  237. }
  238. }
  239. else if (!this._pointerCaptures[pointerEvent.pointerId] && (utilityScenePick.distance < originalScenePick.distance || originalScenePick.distance === 0)) {
  240. // We pick something in utility scene or the pick in utility is closer than the one in main scene
  241. this._notifyObservers(prePointerInfo, utilityScenePick, pointerEvent);
  242. // If a previous utility layer set this, do not unset this
  243. if (!prePointerInfo.skipOnPointerObservable) {
  244. prePointerInfo.skipOnPointerObservable = utilityScenePick.distance > 0;
  245. }
  246. }
  247. else if (!this._pointerCaptures[pointerEvent.pointerId] && utilityScenePick.distance >= originalScenePick.distance) {
  248. // We have a pick in both scenes but main is closer than utility
  249. // We touched an utility mesh present in the main scene
  250. if (this.mainSceneTrackerPredicate && this.mainSceneTrackerPredicate(originalScenePick.pickedMesh)) {
  251. this._notifyObservers(prePointerInfo, originalScenePick, pointerEvent);
  252. prePointerInfo.skipOnPointerObservable = true;
  253. }
  254. else {
  255. if (prePointerInfo.type === PointerEventTypes.POINTERMOVE || prePointerInfo.type === PointerEventTypes.POINTERUP) {
  256. if (this._lastPointerEvents[pointerEvent.pointerId]) {
  257. // We need to send a last pointerup to the utilityLayerScene to make sure animations can complete
  258. this.onPointerOutObservable.notifyObservers(pointerEvent.pointerId);
  259. delete this._lastPointerEvents[pointerEvent.pointerId];
  260. }
  261. }
  262. this._notifyObservers(prePointerInfo, utilityScenePick, pointerEvent);
  263. }
  264. }
  265. if (prePointerInfo.type === PointerEventTypes.POINTERUP && this._pointerCaptures[pointerEvent.pointerId]) {
  266. this._pointerCaptures[pointerEvent.pointerId] = false;
  267. }
  268. }
  269. }
  270. });
  271. // As a newly added utility layer will be rendered over the screen last, it's pointer events should be processed first
  272. if (this._originalPointerObserver) {
  273. originalScene.onPrePointerObservable.makeObserverTopPriority(this._originalPointerObserver);
  274. }
  275. }
  276. // Render directly on top of existing scene without clearing
  277. this.utilityLayerScene.autoClear = false;
  278. this._afterRenderObserver = this.originalScene.onAfterRenderCameraObservable.add((camera) => {
  279. // Only render when the render camera finishes rendering
  280. if (this.shouldRender && camera == this.getRenderCamera()) {
  281. this.render();
  282. }
  283. });
  284. this._sceneDisposeObserver = this.originalScene.onDisposeObservable.add(() => {
  285. this.dispose();
  286. });
  287. this._updateCamera();
  288. }
  289. _notifyObservers(prePointerInfo, pickInfo, pointerEvent) {
  290. if (!prePointerInfo.skipOnPointerObservable) {
  291. this.utilityLayerScene.onPointerObservable.notifyObservers(new PointerInfo(prePointerInfo.type, prePointerInfo.event, pickInfo), prePointerInfo.type);
  292. this._lastPointerEvents[pointerEvent.pointerId] = true;
  293. }
  294. }
  295. /**
  296. * Renders the utility layers scene on top of the original scene
  297. */
  298. render() {
  299. this._updateCamera();
  300. if (this.utilityLayerScene.activeCamera) {
  301. // Set the camera's scene to utility layers scene
  302. const oldScene = this.utilityLayerScene.activeCamera.getScene();
  303. const camera = this.utilityLayerScene.activeCamera;
  304. camera._scene = this.utilityLayerScene;
  305. if (camera.leftCamera) {
  306. camera.leftCamera._scene = this.utilityLayerScene;
  307. }
  308. if (camera.rightCamera) {
  309. camera.rightCamera._scene = this.utilityLayerScene;
  310. }
  311. this.utilityLayerScene.render(false);
  312. // Reset camera's scene back to original
  313. camera._scene = oldScene;
  314. if (camera.leftCamera) {
  315. camera.leftCamera._scene = oldScene;
  316. }
  317. if (camera.rightCamera) {
  318. camera.rightCamera._scene = oldScene;
  319. }
  320. }
  321. }
  322. /**
  323. * Disposes of the renderer
  324. */
  325. dispose() {
  326. this.onPointerOutObservable.clear();
  327. if (this._afterRenderObserver) {
  328. this.originalScene.onAfterCameraRenderObservable.remove(this._afterRenderObserver);
  329. }
  330. if (this._sceneDisposeObserver) {
  331. this.originalScene.onDisposeObservable.remove(this._sceneDisposeObserver);
  332. }
  333. if (this._originalPointerObserver) {
  334. this.originalScene.onPrePointerObservable.remove(this._originalPointerObserver);
  335. }
  336. this.utilityLayerScene.dispose();
  337. }
  338. _updateCamera() {
  339. this.utilityLayerScene.cameraToUseForPointers = this.getRenderCamera();
  340. this.utilityLayerScene.activeCamera = this.getRenderCamera();
  341. }
  342. }
  343. /** @internal */
  344. UtilityLayerRenderer._DefaultUtilityLayer = null;
  345. /** @internal */
  346. UtilityLayerRenderer._DefaultKeepDepthUtilityLayer = null;
  347. //# sourceMappingURL=utilityLayerRenderer.js.map