utilityLayerRenderer.ts 17 KB

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