webXREnterExitUI.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import { Nullable } from "../../types";
  2. import { Observable } from "../../Misc/observable";
  3. import { IDisposable, Scene } from "../../scene";
  4. import { WebXRExperienceHelper } from "./webXRExperienceHelper";
  5. import { WebXRState, WebXRRenderTarget } from './webXRTypes';
  6. /**
  7. * Button which can be used to enter a different mode of XR
  8. */
  9. export class WebXREnterExitUIButton {
  10. /**
  11. * Creates a WebXREnterExitUIButton
  12. * @param element button element
  13. * @param sessionMode XR initialization session mode
  14. * @param referenceSpaceType the type of reference space to be used
  15. */
  16. constructor(
  17. /** button element */
  18. public element: HTMLElement,
  19. /** XR initialization options for the button */
  20. public sessionMode: XRSessionMode,
  21. /** Reference space type */
  22. public referenceSpaceType: XRReferenceSpaceType
  23. ) { }
  24. /**
  25. * Overwritable function which can be used to update the button's visuals when the state changes
  26. * @param activeButton the current active button in the UI
  27. */
  28. update(activeButton: Nullable<WebXREnterExitUIButton>) {
  29. }
  30. }
  31. /**
  32. * Options to create the webXR UI
  33. */
  34. export class WebXREnterExitUIOptions {
  35. /**
  36. * Context to enter xr with
  37. */
  38. renderTarget?: Nullable<WebXRRenderTarget>;
  39. /**
  40. * User provided buttons to enable/disable WebXR. The system will provide default if not set
  41. */
  42. customButtons?: Array<WebXREnterExitUIButton>;
  43. }
  44. /**
  45. * UI to allow the user to enter/exit XR mode
  46. */
  47. export class WebXREnterExitUI implements IDisposable {
  48. private _overlay: HTMLDivElement;
  49. private _buttons: Array<WebXREnterExitUIButton> = [];
  50. private _activeButton: Nullable<WebXREnterExitUIButton> = null;
  51. /**
  52. * Fired every time the active button is changed.
  53. *
  54. * When xr is entered via a button that launches xr that button will be the callback parameter
  55. *
  56. * When exiting xr the callback parameter will be null)
  57. */
  58. public activeButtonChangedObservable = new Observable<Nullable<WebXREnterExitUIButton>>();
  59. /**
  60. * Creates UI to allow the user to enter/exit XR mode
  61. * @param scene the scene to add the ui to
  62. * @param helper the xr experience helper to enter/exit xr with
  63. * @param options options to configure the UI
  64. * @returns the created ui
  65. */
  66. public static CreateAsync(scene: Scene, helper: WebXRExperienceHelper, options: WebXREnterExitUIOptions): Promise<WebXREnterExitUI> {
  67. var ui = new WebXREnterExitUI(scene, options);
  68. var supportedPromises = ui._buttons.map((btn) => {
  69. return helper.sessionManager.supportsSessionAsync(btn.sessionMode);
  70. });
  71. helper.onStateChangedObservable.add((state) => {
  72. if (state == WebXRState.NOT_IN_XR) {
  73. ui._updateButtons(null);
  74. }
  75. });
  76. return Promise.all(supportedPromises).then((results) => {
  77. results.forEach((supported, i) => {
  78. if (supported) {
  79. ui._overlay.appendChild(ui._buttons[i].element);
  80. ui._buttons[i].element.onclick = async() => {
  81. if (helper.state == WebXRState.IN_XR) {
  82. ui._updateButtons(null);
  83. await helper.exitXRAsync();
  84. return;
  85. } else if (helper.state == WebXRState.NOT_IN_XR) {
  86. ui._updateButtons(ui._buttons[i]);
  87. if (options.renderTarget) {
  88. await helper.enterXRAsync(ui._buttons[i].sessionMode, ui._buttons[i].referenceSpaceType, options.renderTarget);
  89. }
  90. }
  91. };
  92. }
  93. });
  94. return ui;
  95. });
  96. }
  97. private constructor(private scene: Scene, options: WebXREnterExitUIOptions) {
  98. this._overlay = document.createElement("div");
  99. this._overlay.style.cssText = "z-index:11;position: absolute; right: 20px;bottom: 50px;";
  100. if (options.customButtons) {
  101. this._buttons = options.customButtons;
  102. } else {
  103. var hmdBtn = document.createElement("button");
  104. hmdBtn.style.cssText = "color: #868686; border-color: #868686; border-style: solid; margin-left: 10px; height: 50px; width: 80px; background-color: rgba(51,51,51,0.7); background-repeat:no-repeat; background-position: center; outline: none;";
  105. hmdBtn.innerText = "HMD";
  106. this._buttons.push(new WebXREnterExitUIButton(hmdBtn, "immersive-vr", "local-floor"));
  107. this._buttons[this._buttons.length - 1].update = function(activeButton: WebXREnterExitUIButton) {
  108. this.element.style.display = (activeButton === null || activeButton === this) ? "" : "none";
  109. this.element.innerText = activeButton === this ? "EXIT" : "HMD";
  110. };
  111. this._updateButtons(null);
  112. }
  113. var renderCanvas = scene.getEngine().getRenderingCanvas();
  114. if (renderCanvas && renderCanvas.parentNode) {
  115. renderCanvas.parentNode.appendChild(this._overlay);
  116. scene.onDisposeObservable.addOnce(() => {
  117. this.dispose();
  118. });
  119. }
  120. }
  121. private _updateButtons(activeButton: Nullable<WebXREnterExitUIButton>) {
  122. this._activeButton = activeButton;
  123. this._buttons.forEach((b) => {
  124. b.update(this._activeButton);
  125. });
  126. this.activeButtonChangedObservable.notifyObservers(this._activeButton);
  127. }
  128. /**
  129. * Disposes of the object
  130. */
  131. dispose() {
  132. var renderCanvas = this.scene.getEngine().getRenderingCanvas();
  133. if (renderCanvas && renderCanvas.parentNode && renderCanvas.parentNode.contains(this._overlay)) {
  134. renderCanvas.parentNode.removeChild(this._overlay);
  135. }
  136. this.activeButtonChangedObservable.clear();
  137. }
  138. }