babylon.utilityLayerRenderer.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. module BABYLON {
  2. /**
  3. * Renders a layer on top of an existing scene
  4. */
  5. export class UtilityLayerRenderer implements IDisposable {
  6. private _pointerCaptures: { [pointerId: number]: boolean } = {};
  7. private _lastPointerEvents: { [pointerId: number]: boolean } = {};
  8. private static _DefaultUtilityLayer: Nullable<UtilityLayerRenderer> = null;
  9. private static _DefaultKeepDepthUtilityLayer: Nullable<UtilityLayerRenderer> = null;
  10. /**
  11. * If the picking should be done on the utility layer prior to the actual scene (Default: true)
  12. */
  13. public pickUtilitySceneFirst = true;
  14. /**
  15. * 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)
  16. */
  17. public static get DefaultUtilityLayer(): UtilityLayerRenderer {
  18. if (UtilityLayerRenderer._DefaultUtilityLayer == null) {
  19. UtilityLayerRenderer._DefaultUtilityLayer = new UtilityLayerRenderer(BABYLON.Engine.LastCreatedScene!);
  20. UtilityLayerRenderer._DefaultUtilityLayer.originalScene.onDisposeObservable.addOnce(() => {
  21. UtilityLayerRenderer._DefaultUtilityLayer = null;
  22. });
  23. }
  24. return UtilityLayerRenderer._DefaultUtilityLayer;
  25. }
  26. /**
  27. * 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)
  28. */
  29. public static get DefaultKeepDepthUtilityLayer(): UtilityLayerRenderer {
  30. if (UtilityLayerRenderer._DefaultKeepDepthUtilityLayer == null) {
  31. UtilityLayerRenderer._DefaultKeepDepthUtilityLayer = new UtilityLayerRenderer(BABYLON.Engine.LastCreatedScene!);
  32. UtilityLayerRenderer._DefaultKeepDepthUtilityLayer.utilityLayerScene.autoClearDepthAndStencil = false;
  33. UtilityLayerRenderer._DefaultKeepDepthUtilityLayer.originalScene.onDisposeObservable.addOnce(() => {
  34. UtilityLayerRenderer._DefaultKeepDepthUtilityLayer = null;
  35. });
  36. }
  37. return UtilityLayerRenderer._DefaultKeepDepthUtilityLayer;
  38. }
  39. /**
  40. * The scene that is rendered on top of the original scene
  41. */
  42. public utilityLayerScene: Scene;
  43. /**
  44. * If the utility layer should automatically be rendered on top of existing scene
  45. */
  46. public shouldRender: boolean = true;
  47. /**
  48. * If set to true, only pointer down onPointerObservable events will be blocked when picking is occluded by original scene
  49. */
  50. public onlyCheckPointerDownEvents = true;
  51. /**
  52. * If set to false, only pointerUp, pointerDown and pointerMove will be sent to the utilityLayerScene (false by default)
  53. */
  54. public processAllEvents = false;
  55. /**
  56. * Observable raised when the pointer move from the utility layer scene to the main scene
  57. */
  58. public onPointerOutObservable = new Observable<number>();
  59. /** Gets or sets a predicate that will be used to indicate utility meshes present in the main scene */
  60. public mainSceneTrackerPredicate: (mesh: Nullable<AbstractMesh>) => boolean;
  61. private _afterRenderObserver: Nullable<Observer<Scene>>;
  62. private _sceneDisposeObserver: Nullable<Observer<Scene>>;
  63. private _originalPointerObserver: Nullable<Observer<PointerInfoPre>>;
  64. /**
  65. * Instantiates a UtilityLayerRenderer
  66. * @param originalScene the original scene that will be rendered on top of
  67. * @param handleEvents boolean indicating if the utility layer should handle events
  68. */
  69. constructor(
  70. /** the original scene that will be rendered on top of */
  71. public originalScene: Scene,
  72. handleEvents: boolean = true) {
  73. // Create scene which will be rendered in the foreground and remove it from being referenced by engine to avoid interfering with existing app
  74. this.utilityLayerScene = new BABYLON.Scene(originalScene.getEngine());
  75. this.utilityLayerScene.useRightHandedSystem = originalScene.useRightHandedSystem;
  76. this.utilityLayerScene._allowPostProcessClearColor = false;
  77. originalScene.getEngine().scenes.pop();
  78. // Detach controls on utility scene, events will be fired by logic below to handle picking priority
  79. this.utilityLayerScene.detachControl();
  80. if (handleEvents) {
  81. this._originalPointerObserver = originalScene.onPrePointerObservable.add((prePointerInfo, eventState) => {
  82. if (!this.processAllEvents) {
  83. if (prePointerInfo.type !== BABYLON.PointerEventTypes.POINTERMOVE
  84. && prePointerInfo.type !== BABYLON.PointerEventTypes.POINTERUP
  85. && prePointerInfo.type !== BABYLON.PointerEventTypes.POINTERDOWN) {
  86. return;
  87. }
  88. }
  89. let pointerEvent = <PointerEvent>(prePointerInfo.event);
  90. if (originalScene!.isPointerCaptured(pointerEvent.pointerId)) {
  91. this._pointerCaptures[pointerEvent.pointerId] = false;
  92. return;
  93. }
  94. var utilityScenePick = prePointerInfo.ray ? this.utilityLayerScene.pickWithRay(prePointerInfo.ray) : this.utilityLayerScene.pick(originalScene.pointerX, originalScene.pointerY);
  95. if (!prePointerInfo.ray && utilityScenePick) {
  96. prePointerInfo.ray = utilityScenePick.ray;
  97. }
  98. // always fire the prepointer oversvable
  99. this.utilityLayerScene.onPrePointerObservable.notifyObservers(prePointerInfo);
  100. // allow every non pointer down event to flow to the utility layer
  101. if (this.onlyCheckPointerDownEvents && prePointerInfo.type != BABYLON.PointerEventTypes.POINTERDOWN) {
  102. if (!prePointerInfo.skipOnPointerObservable) {
  103. this.utilityLayerScene.onPointerObservable.notifyObservers(new PointerInfo(prePointerInfo.type, prePointerInfo.event, utilityScenePick));
  104. }
  105. if (prePointerInfo.type === BABYLON.PointerEventTypes.POINTERUP && this._pointerCaptures[pointerEvent.pointerId]) {
  106. this._pointerCaptures[pointerEvent.pointerId] = false;
  107. }
  108. return;
  109. }
  110. if (this.utilityLayerScene.autoClearDepthAndStencil || this.pickUtilitySceneFirst) {
  111. // If this layer is an overlay, check if this layer was hit and if so, skip pointer events for the main scene
  112. if (utilityScenePick && utilityScenePick.hit) {
  113. if (!prePointerInfo.skipOnPointerObservable) {
  114. this.utilityLayerScene.onPointerObservable.notifyObservers(new PointerInfo(prePointerInfo.type, prePointerInfo.event, utilityScenePick));
  115. }
  116. prePointerInfo.skipOnPointerObservable = true;
  117. }
  118. } else {
  119. var originalScenePick = prePointerInfo.ray ? originalScene.pickWithRay(prePointerInfo.ray) : originalScene.pick(originalScene.pointerX, originalScene.pointerY);
  120. let pointerEvent = <PointerEvent>(prePointerInfo.event);
  121. // If the layer can be occluded by the original scene, only fire pointer events to the first layer that hit they ray
  122. if (originalScenePick && utilityScenePick) {
  123. // No pick in utility scene
  124. if (utilityScenePick.distance === 0 && originalScenePick.pickedMesh) {
  125. if (this.mainSceneTrackerPredicate && this.mainSceneTrackerPredicate(originalScenePick.pickedMesh)) {
  126. // We touched an utility mesh present in the main scene
  127. this._notifyObservers(prePointerInfo, originalScenePick, pointerEvent);
  128. prePointerInfo.skipOnPointerObservable = true;
  129. } else if (prePointerInfo.type === BABYLON.PointerEventTypes.POINTERDOWN) {
  130. this._pointerCaptures[pointerEvent.pointerId] = true;
  131. } else if (this._lastPointerEvents[pointerEvent.pointerId]) {
  132. // We need to send a last pointerup to the utilityLayerScene to make sure animations can complete
  133. this.onPointerOutObservable.notifyObservers(pointerEvent.pointerId);
  134. delete this._lastPointerEvents[pointerEvent.pointerId];
  135. }
  136. } else if (!this._pointerCaptures[pointerEvent.pointerId] && (utilityScenePick.distance < originalScenePick.distance || originalScenePick.distance === 0)) {
  137. // We pick something in utility scene or the pick in utility is closer than the one in main scene
  138. this._notifyObservers(prePointerInfo, utilityScenePick, pointerEvent);
  139. // If a previous utility layer set this, do not unset this
  140. if (!prePointerInfo.skipOnPointerObservable) {
  141. prePointerInfo.skipOnPointerObservable = utilityScenePick.distance > 0;
  142. }
  143. } else if (!this._pointerCaptures[pointerEvent.pointerId] && (utilityScenePick.distance > originalScenePick.distance)) {
  144. // We have a pick in both scenes but main is closer than utility
  145. // We touched an utility mesh present in the main scene
  146. if (this.mainSceneTrackerPredicate && this.mainSceneTrackerPredicate(originalScenePick.pickedMesh)) {
  147. this._notifyObservers(prePointerInfo, originalScenePick, pointerEvent);
  148. prePointerInfo.skipOnPointerObservable = true;
  149. } else if (this._lastPointerEvents[pointerEvent.pointerId]) {
  150. // We need to send a last pointerup to the utilityLayerScene to make sure animations can complete
  151. this.onPointerOutObservable.notifyObservers(pointerEvent.pointerId);
  152. delete this._lastPointerEvents[pointerEvent.pointerId];
  153. }
  154. }
  155. if (prePointerInfo.type === BABYLON.PointerEventTypes.POINTERUP && this._pointerCaptures[pointerEvent.pointerId]) {
  156. this._pointerCaptures[pointerEvent.pointerId] = false;
  157. }
  158. }
  159. }
  160. });
  161. }
  162. // Render directly on top of existing scene without clearing
  163. this.utilityLayerScene.autoClear = false;
  164. this._afterRenderObserver = this.originalScene.onAfterRenderObservable.add(() => {
  165. if (this.shouldRender) {
  166. this.render();
  167. }
  168. });
  169. this._sceneDisposeObserver = this.originalScene.onDisposeObservable.add(() => {
  170. this.dispose();
  171. });
  172. this._updateCamera();
  173. }
  174. private _notifyObservers(prePointerInfo: PointerInfoPre, pickInfo: PickingInfo, pointerEvent: PointerEvent) {
  175. if (!prePointerInfo.skipOnPointerObservable) {
  176. this.utilityLayerScene.onPointerObservable.notifyObservers(new PointerInfo(prePointerInfo.type, prePointerInfo.event, pickInfo));
  177. this._lastPointerEvents[pointerEvent.pointerId] = true;
  178. }
  179. }
  180. /**
  181. * Renders the utility layers scene on top of the original scene
  182. */
  183. public render() {
  184. this._updateCamera();
  185. if (this.utilityLayerScene.activeCamera) {
  186. // Set the camera's scene to utility layers scene
  187. var oldScene = this.utilityLayerScene.activeCamera.getScene();
  188. var camera = this.utilityLayerScene.activeCamera;
  189. camera._scene = this.utilityLayerScene;
  190. if (camera.leftCamera) {
  191. camera.leftCamera._scene = this.utilityLayerScene;
  192. }
  193. if (camera.rightCamera) {
  194. camera.rightCamera._scene = this.utilityLayerScene;
  195. }
  196. this.utilityLayerScene.render(false);
  197. // Reset camera's scene back to original
  198. camera._scene = oldScene;
  199. if (camera.leftCamera) {
  200. camera.leftCamera._scene = oldScene;
  201. }
  202. if (camera.rightCamera) {
  203. camera.rightCamera._scene = oldScene;
  204. }
  205. }
  206. }
  207. /**
  208. * Disposes of the renderer
  209. */
  210. public dispose() {
  211. this.onPointerOutObservable.clear();
  212. if (this._afterRenderObserver) {
  213. this.originalScene.onAfterRenderObservable.remove(this._afterRenderObserver);
  214. }
  215. if (this._sceneDisposeObserver) {
  216. this.originalScene.onDisposeObservable.remove(this._sceneDisposeObserver);
  217. }
  218. if (this._originalPointerObserver) {
  219. this.originalScene.onPrePointerObservable.remove(this._originalPointerObserver);
  220. }
  221. this.utilityLayerScene.dispose();
  222. }
  223. private _updateCamera() {
  224. this.utilityLayerScene.activeCamera = this.originalScene.activeCamera;
  225. }
  226. }
  227. }