engine.webVR.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. import { Nullable } from "../../types";
  2. import { Engine, IDisplayChangedEventArgs } from "../../Engines/engine";
  3. import { Size } from '../../Maths/math.size';
  4. import { Observable } from '../../Misc/observable';
  5. import { Tools } from '../../Misc/tools';
  6. import { DomManagement } from '../../Misc/domManagement';
  7. import { WebVROptions } from '../../Cameras/VR/webVRCamera';
  8. /**
  9. * Interface used to define additional presentation attributes
  10. */
  11. export interface IVRPresentationAttributes {
  12. /**
  13. * Defines a boolean indicating that we want to get 72hz mode on Oculus Browser (default is off eg. 60hz)
  14. */
  15. highRefreshRate: boolean;
  16. /**
  17. * Enables foveation in VR to improve perf. 0 none, 1 low, 2 medium, 3 high (Default is 1)
  18. */
  19. foveationLevel: number;
  20. }
  21. declare module "../../Engines/engine" {
  22. export interface Engine {
  23. /** @hidden */
  24. _vrDisplay: any;
  25. /** @hidden */
  26. _vrSupported: boolean;
  27. /** @hidden */
  28. _oldSize: Size;
  29. /** @hidden */
  30. _oldHardwareScaleFactor: number;
  31. /** @hidden */
  32. _vrExclusivePointerMode: boolean;
  33. /** @hidden */
  34. _webVRInitPromise: Promise<IDisplayChangedEventArgs>;
  35. /** @hidden */
  36. _onVRDisplayPointerRestricted: () => void;
  37. /** @hidden */
  38. _onVRDisplayPointerUnrestricted: () => void;
  39. /** @hidden */
  40. _onVrDisplayConnect: Nullable<(display: any) => void>;
  41. /** @hidden */
  42. _onVrDisplayDisconnect: Nullable<() => void>;
  43. /** @hidden */
  44. _onVrDisplayPresentChange: Nullable<() => void>;
  45. /**
  46. * Observable signaled when VR display mode changes
  47. */
  48. onVRDisplayChangedObservable: Observable<IDisplayChangedEventArgs>;
  49. /**
  50. * Observable signaled when VR request present is complete
  51. */
  52. onVRRequestPresentComplete: Observable<boolean>;
  53. /**
  54. * Observable signaled when VR request present starts
  55. */
  56. onVRRequestPresentStart: Observable<Engine>;
  57. /**
  58. * Gets a boolean indicating that the engine is currently in VR exclusive mode for the pointers
  59. * @see https://docs.microsoft.com/en-us/microsoft-edge/webvr/essentials#mouse-input
  60. */
  61. isInVRExclusivePointerMode: boolean;
  62. /**
  63. * Gets a boolean indicating if a webVR device was detected
  64. * @returns true if a webVR device was detected
  65. */
  66. isVRDevicePresent(): boolean;
  67. /**
  68. * Gets the current webVR device
  69. * @returns the current webVR device (or null)
  70. */
  71. getVRDevice(): any;
  72. /**
  73. * Initializes a webVR display and starts listening to display change events
  74. * The onVRDisplayChangedObservable will be notified upon these changes
  75. * @returns A promise containing a VRDisplay and if vr is supported
  76. */
  77. initWebVRAsync(): Promise<IDisplayChangedEventArgs>;
  78. /** @hidden */
  79. _getVRDisplaysAsync(): Promise<IDisplayChangedEventArgs>;
  80. /**
  81. * Gets or sets the presentation attributes used to configure VR rendering
  82. */
  83. vrPresentationAttributes?: IVRPresentationAttributes;
  84. /**
  85. * Call this function to switch to webVR mode
  86. * Will do nothing if webVR is not supported or if there is no webVR device
  87. * @param options the webvr options provided to the camera. mainly used for multiview
  88. * @see https://doc.babylonjs.com/how_to/webvr_camera
  89. */
  90. enableVR(options: WebVROptions): void;
  91. /** @hidden */
  92. _onVRFullScreenTriggered(): void;
  93. }
  94. }
  95. Object.defineProperty(Engine.prototype, "isInVRExclusivePointerMode", {
  96. get: function(this: Engine) {
  97. return this._vrExclusivePointerMode;
  98. },
  99. enumerable: true,
  100. configurable: true
  101. });
  102. Engine.prototype._prepareVRComponent = function() {
  103. this._vrSupported = false;
  104. this._vrExclusivePointerMode = false;
  105. this.onVRDisplayChangedObservable = new Observable<IDisplayChangedEventArgs>();
  106. this.onVRRequestPresentComplete = new Observable<boolean>();
  107. this.onVRRequestPresentStart = new Observable<Engine>();
  108. };
  109. Engine.prototype.isVRDevicePresent = function() {
  110. return !!this._vrDisplay;
  111. };
  112. Engine.prototype.getVRDevice = function(): any {
  113. return this._vrDisplay;
  114. };
  115. Engine.prototype.initWebVR = function(): Observable<IDisplayChangedEventArgs> {
  116. this.initWebVRAsync();
  117. return this.onVRDisplayChangedObservable;
  118. };
  119. Engine.prototype.initWebVRAsync = function(): Promise<IDisplayChangedEventArgs> {
  120. var notifyObservers = () => {
  121. var eventArgs = {
  122. vrDisplay: this._vrDisplay,
  123. vrSupported: this._vrSupported
  124. };
  125. this.onVRDisplayChangedObservable.notifyObservers(eventArgs);
  126. this._webVRInitPromise = new Promise((res) => { res(eventArgs); });
  127. };
  128. if (!this._onVrDisplayConnect) {
  129. this._onVrDisplayConnect = (event) => {
  130. this._vrDisplay = event.display;
  131. notifyObservers();
  132. };
  133. this._onVrDisplayDisconnect = () => {
  134. this._vrDisplay.cancelAnimationFrame(this._frameHandler);
  135. this._vrDisplay = undefined;
  136. this._frameHandler = Engine.QueueNewFrame(this._boundRenderFunction);
  137. notifyObservers();
  138. };
  139. this._onVrDisplayPresentChange = () => {
  140. this._vrExclusivePointerMode = this._vrDisplay && this._vrDisplay.isPresenting;
  141. };
  142. let hostWindow = this.getHostWindow();
  143. if (hostWindow) {
  144. hostWindow.addEventListener('vrdisplayconnect', this._onVrDisplayConnect);
  145. hostWindow.addEventListener('vrdisplaydisconnect', this._onVrDisplayDisconnect);
  146. hostWindow.addEventListener('vrdisplaypresentchange', this._onVrDisplayPresentChange);
  147. }
  148. }
  149. this._webVRInitPromise = this._webVRInitPromise || this._getVRDisplaysAsync();
  150. this._webVRInitPromise.then(notifyObservers);
  151. return this._webVRInitPromise;
  152. };
  153. Engine.prototype._getVRDisplaysAsync = function(): Promise<IDisplayChangedEventArgs> {
  154. return new Promise((res) => {
  155. if (navigator.getVRDisplays) {
  156. navigator.getVRDisplays().then((devices: Array<any>) => {
  157. this._vrSupported = true;
  158. // note that devices may actually be an empty array. This is fine;
  159. // we expect this._vrDisplay to be undefined in this case.
  160. this._vrDisplay = devices[0];
  161. res({
  162. vrDisplay: this._vrDisplay,
  163. vrSupported: this._vrSupported
  164. });
  165. });
  166. } else {
  167. this._vrDisplay = undefined;
  168. this._vrSupported = false;
  169. res({
  170. vrDisplay: this._vrDisplay,
  171. vrSupported: this._vrSupported
  172. });
  173. }
  174. });
  175. };
  176. Engine.prototype.enableVR = function(options: WebVROptions) {
  177. if (this._vrDisplay && !this._vrDisplay.isPresenting) {
  178. var onResolved = () => {
  179. this.onVRRequestPresentComplete.notifyObservers(true);
  180. this._onVRFullScreenTriggered();
  181. };
  182. var onRejected = () => {
  183. this.onVRRequestPresentComplete.notifyObservers(false);
  184. };
  185. this.onVRRequestPresentStart.notifyObservers(this);
  186. var presentationAttributes = {
  187. highRefreshRate: this.vrPresentationAttributes ? this.vrPresentationAttributes.highRefreshRate : false,
  188. foveationLevel: this.vrPresentationAttributes ? this.vrPresentationAttributes.foveationLevel : 1,
  189. multiview: (this.getCaps().multiview || this.getCaps().oculusMultiview) && options.useMultiview
  190. };
  191. this._vrDisplay.requestPresent([{
  192. source: this.getRenderingCanvas(),
  193. attributes: presentationAttributes,
  194. ...presentationAttributes
  195. }]).then(onResolved).catch(onRejected);
  196. }
  197. };
  198. Engine.prototype._onVRFullScreenTriggered = function() {
  199. if (this._vrDisplay && this._vrDisplay.isPresenting) {
  200. //get the old size before we change
  201. this._oldSize = new Size(this.getRenderWidth(), this.getRenderHeight());
  202. this._oldHardwareScaleFactor = this.getHardwareScalingLevel();
  203. //get the width and height, change the render size
  204. var leftEye = this._vrDisplay.getEyeParameters('left');
  205. this.setHardwareScalingLevel(1);
  206. this.setSize(leftEye.renderWidth * 2, leftEye.renderHeight);
  207. } else {
  208. this.setHardwareScalingLevel(this._oldHardwareScaleFactor);
  209. this.setSize(this._oldSize.width, this._oldSize.height);
  210. }
  211. };
  212. Engine.prototype.disableVR = function() {
  213. if (this._vrDisplay && this._vrDisplay.isPresenting) {
  214. this._vrDisplay.exitPresent()
  215. .then(() => this._onVRFullScreenTriggered())
  216. .catch(() => this._onVRFullScreenTriggered());
  217. }
  218. if (DomManagement.IsWindowObjectExist()) {
  219. window.removeEventListener('vrdisplaypointerrestricted', this._onVRDisplayPointerRestricted);
  220. window.removeEventListener('vrdisplaypointerunrestricted', this._onVRDisplayPointerUnrestricted);
  221. if (this._onVrDisplayConnect) {
  222. window.removeEventListener('vrdisplayconnect', this._onVrDisplayConnect);
  223. if (this._onVrDisplayDisconnect) {
  224. window.removeEventListener('vrdisplaydisconnect', this._onVrDisplayDisconnect);
  225. }
  226. if (this._onVrDisplayPresentChange) {
  227. window.removeEventListener('vrdisplaypresentchange', this._onVrDisplayPresentChange);
  228. }
  229. this._onVrDisplayConnect = null;
  230. this._onVrDisplayDisconnect = null;
  231. }
  232. }
  233. };
  234. Engine.prototype._connectVREvents = function(canvas?: HTMLCanvasElement, document?: any) {
  235. this._onVRDisplayPointerRestricted = () => {
  236. if (canvas) {
  237. canvas.requestPointerLock();
  238. }
  239. };
  240. this._onVRDisplayPointerUnrestricted = () => {
  241. // Edge fix - for some reason document is not present and this is window
  242. if (!document) {
  243. let hostWindow = this.getHostWindow()!;
  244. if (hostWindow.document && hostWindow.document.exitPointerLock) {
  245. hostWindow.document.exitPointerLock();
  246. }
  247. return;
  248. }
  249. if (!document.exitPointerLock) {
  250. return;
  251. }
  252. document.exitPointerLock();
  253. };
  254. if (DomManagement.IsWindowObjectExist()) {
  255. let hostWindow = this.getHostWindow()!;
  256. hostWindow.addEventListener('vrdisplaypointerrestricted', this._onVRDisplayPointerRestricted, false);
  257. hostWindow.addEventListener('vrdisplaypointerunrestricted', this._onVRDisplayPointerUnrestricted, false);
  258. }
  259. };
  260. Engine.prototype._submitVRFrame = function() {
  261. // Submit frame to the vr device, if enabled
  262. if (this._vrDisplay && this._vrDisplay.isPresenting) {
  263. // TODO: We should only submit the frame if we read frameData successfully.
  264. try {
  265. this._vrDisplay.submitFrame();
  266. } catch (e) {
  267. Tools.Warn("webVR submitFrame has had an unexpected failure: " + e);
  268. }
  269. }
  270. };
  271. Engine.prototype.isVRPresenting = function() {
  272. return this._vrDisplay && this._vrDisplay.isPresenting;
  273. };
  274. Engine.prototype._requestVRFrame = function() {
  275. this._frameHandler = Engine.QueueNewFrame(this._boundRenderFunction, this._vrDisplay);
  276. };