utilityLayerRenderer.ts 15 KB

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