freeCameraDeviceOrientationInput.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import { Nullable } from "../../types";
  2. import { ICameraInput, CameraInputTypes } from "../../Cameras/cameraInputsManager";
  3. import { FreeCamera } from "../../Cameras/freeCamera";
  4. import { Quaternion } from "../../Maths/math.vector";
  5. import { Tools } from "../../Misc/tools";
  6. import { FreeCameraInputsManager } from "../../Cameras/freeCameraInputsManager";
  7. import { Observable } from "../../Misc/observable";
  8. // Module augmentation to abstract orientation inputs from camera.
  9. declare module "../../Cameras/freeCameraInputsManager" {
  10. export interface FreeCameraInputsManager {
  11. /**
  12. * @hidden
  13. */
  14. _deviceOrientationInput: Nullable<FreeCameraDeviceOrientationInput>;
  15. /**
  16. * Add orientation input support to the input manager.
  17. * @returns the current input manager
  18. */
  19. addDeviceOrientation(): FreeCameraInputsManager;
  20. }
  21. }
  22. /**
  23. * Add orientation input support to the input manager.
  24. * @returns the current input manager
  25. */
  26. FreeCameraInputsManager.prototype.addDeviceOrientation = function (): FreeCameraInputsManager {
  27. if (!this._deviceOrientationInput) {
  28. this._deviceOrientationInput = new FreeCameraDeviceOrientationInput();
  29. this.add(this._deviceOrientationInput);
  30. }
  31. return this;
  32. };
  33. /**
  34. * Takes information about the orientation of the device as reported by the deviceorientation event to orient the camera.
  35. * Screen rotation is taken into account.
  36. * @see https://doc.babylonjs.com/how_to/customizing_camera_inputs
  37. */
  38. export class FreeCameraDeviceOrientationInput implements ICameraInput<FreeCamera> {
  39. private _camera: FreeCamera;
  40. private _screenOrientationAngle: number = 0;
  41. private _constantTranform: Quaternion;
  42. private _screenQuaternion: Quaternion = new Quaternion();
  43. private _alpha: number = 0;
  44. private _beta: number = 0;
  45. private _gamma: number = 0;
  46. /**
  47. * Can be used to detect if a device orientation sensor is available on a device
  48. * @param timeout amount of time in milliseconds to wait for a response from the sensor (default: infinite)
  49. * @returns a promise that will resolve on orientation change
  50. */
  51. public static WaitForOrientationChangeAsync(timeout?: number) {
  52. return new Promise((res, rej) => {
  53. var gotValue = false;
  54. var eventHandler = () => {
  55. window.removeEventListener("deviceorientation", eventHandler);
  56. gotValue = true;
  57. res();
  58. };
  59. // If timeout is populated reject the promise
  60. if (timeout) {
  61. setTimeout(() => {
  62. if (!gotValue) {
  63. window.removeEventListener("deviceorientation", eventHandler);
  64. rej("WaitForOrientationChangeAsync timed out");
  65. }
  66. }, timeout);
  67. }
  68. if (typeof DeviceOrientationEvent !== "undefined" && typeof (<any>DeviceOrientationEvent).requestPermission === "function") {
  69. (<any>DeviceOrientationEvent)
  70. .requestPermission()
  71. .then((response: string) => {
  72. if (response == "granted") {
  73. window.addEventListener("deviceorientation", eventHandler);
  74. } else {
  75. Tools.Warn("Permission not granted.");
  76. }
  77. })
  78. .catch((error: any) => {
  79. Tools.Error(error);
  80. });
  81. } else {
  82. window.addEventListener("deviceorientation", eventHandler);
  83. }
  84. });
  85. }
  86. /**
  87. * @hidden
  88. */
  89. public _onDeviceOrientationChangedObservable = new Observable<void>();
  90. /**
  91. * Instantiates a new input
  92. * @see https://doc.babylonjs.com/how_to/customizing_camera_inputs
  93. */
  94. constructor() {
  95. this._constantTranform = new Quaternion(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5));
  96. this._orientationChanged();
  97. }
  98. /**
  99. * Define the camera controlled by the input.
  100. */
  101. public get camera(): FreeCamera {
  102. return this._camera;
  103. }
  104. public set camera(camera: FreeCamera) {
  105. this._camera = camera;
  106. if (this._camera != null && !this._camera.rotationQuaternion) {
  107. this._camera.rotationQuaternion = new Quaternion();
  108. }
  109. if (this._camera) {
  110. this._camera.onDisposeObservable.add(() => {
  111. this._onDeviceOrientationChangedObservable.clear();
  112. });
  113. }
  114. }
  115. /**
  116. * Attach the input controls to a specific dom element to get the input from.
  117. */
  118. public attachControl(): void {
  119. let hostWindow = this.camera.getScene().getEngine().getHostWindow();
  120. if (hostWindow) {
  121. const eventHandler = () => {
  122. hostWindow!.addEventListener("orientationchange", this._orientationChanged);
  123. hostWindow!.addEventListener("deviceorientation", this._deviceOrientation);
  124. //In certain cases, the attach control is called AFTER orientation was changed,
  125. //So this is needed.
  126. this._orientationChanged();
  127. };
  128. if (typeof DeviceOrientationEvent !== "undefined" && typeof (<any>DeviceOrientationEvent).requestPermission === "function") {
  129. (<any>DeviceOrientationEvent)
  130. .requestPermission()
  131. .then((response: string) => {
  132. if (response === "granted") {
  133. eventHandler();
  134. } else {
  135. Tools.Warn("Permission not granted.");
  136. }
  137. })
  138. .catch((error: any) => {
  139. Tools.Error(error);
  140. });
  141. } else {
  142. eventHandler();
  143. }
  144. }
  145. }
  146. private _orientationChanged = () => {
  147. this._screenOrientationAngle = <any>window.orientation !== undefined ? +(<any>window.orientation) : (<any>window.screen).orientation && (<any>window.screen).orientation["angle"] ? (<any>window.screen).orientation.angle : 0;
  148. this._screenOrientationAngle = -Tools.ToRadians(this._screenOrientationAngle / 2);
  149. this._screenQuaternion.copyFromFloats(0, Math.sin(this._screenOrientationAngle), 0, Math.cos(this._screenOrientationAngle));
  150. };
  151. private _deviceOrientation = (evt: DeviceOrientationEvent) => {
  152. this._alpha = evt.alpha !== null ? evt.alpha : 0;
  153. this._beta = evt.beta !== null ? evt.beta : 0;
  154. this._gamma = evt.gamma !== null ? evt.gamma : 0;
  155. if (evt.alpha !== null) {
  156. this._onDeviceOrientationChangedObservable.notifyObservers();
  157. }
  158. };
  159. /**
  160. * Detach the current controls from the specified dom element.
  161. */
  162. public detachControl(): void;
  163. /**
  164. * Detach the current controls from the specified dom element.
  165. * @param ignored defines an ignored parameter kept for backward compatibility. If you want to define the source input element, you can set engine.inputElement before calling camera.attachControl
  166. */
  167. public detachControl(ignored?: any): void {
  168. window.removeEventListener("orientationchange", this._orientationChanged);
  169. window.removeEventListener("deviceorientation", this._deviceOrientation);
  170. this._alpha = 0;
  171. }
  172. /**
  173. * Update the current camera state depending on the inputs that have been used this frame.
  174. * This is a dynamically created lambda to avoid the performance penalty of looping for inputs in the render loop.
  175. */
  176. public checkInputs(): void {
  177. //if no device orientation provided, don't update the rotation.
  178. //Only testing against alpha under the assumption thatnorientation will never be so exact when set.
  179. if (!this._alpha) {
  180. return;
  181. }
  182. Quaternion.RotationYawPitchRollToRef(Tools.ToRadians(this._alpha), Tools.ToRadians(this._beta), -Tools.ToRadians(this._gamma), this.camera.rotationQuaternion);
  183. this._camera.rotationQuaternion.multiplyInPlace(this._screenQuaternion);
  184. this._camera.rotationQuaternion.multiplyInPlace(this._constantTranform);
  185. //Mirror on XY Plane
  186. this._camera.rotationQuaternion.z *= -1;
  187. this._camera.rotationQuaternion.w *= -1;
  188. }
  189. /**
  190. * Gets the class name of the current intput.
  191. * @returns the class name
  192. */
  193. public getClassName(): string {
  194. return "FreeCameraDeviceOrientationInput";
  195. }
  196. /**
  197. * Get the friendly name associated with the input class.
  198. * @returns the input friendly name
  199. */
  200. public getSimpleName(): string {
  201. return "deviceOrientation";
  202. }
  203. }
  204. (<any>CameraInputTypes)["FreeCameraDeviceOrientationInput"] = FreeCameraDeviceOrientationInput;