engine.webVR.ts 12 KB

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