freeCameraDeviceOrientationInput.ts 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  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 http://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 (<any>DeviceOrientationEvent).requestPermission === 'function') {
  69. (<any>DeviceOrientationEvent).requestPermission()
  70. .then((response: string) => {
  71. if (response == 'granted') {
  72. window.addEventListener("deviceorientation", eventHandler);
  73. } else {
  74. Tools.Warn("Permission not granted.");
  75. }
  76. })
  77. .catch((error: any) => {
  78. Tools.Error(error);
  79. });
  80. } else {
  81. window.addEventListener("deviceorientation", eventHandler);
  82. }
  83. });
  84. }
  85. /**
  86. * @hidden
  87. */
  88. public _onDeviceOrientationChangedObservable = new Observable<void>();
  89. /**
  90. * Instantiates a new input
  91. * @see http://doc.babylonjs.com/how_to/customizing_camera_inputs
  92. */
  93. constructor() {
  94. this._constantTranform = new Quaternion(- Math.sqrt(0.5), 0, 0, Math.sqrt(0.5));
  95. this._orientationChanged();
  96. }
  97. /**
  98. * Define the camera controlled by the input.
  99. */
  100. public get camera(): FreeCamera {
  101. return this._camera;
  102. }
  103. public set camera(camera: FreeCamera) {
  104. this._camera = camera;
  105. if (this._camera != null && !this._camera.rotationQuaternion) {
  106. this._camera.rotationQuaternion = new Quaternion();
  107. }
  108. if (this._camera) {
  109. this._camera.onDisposeObservable.add(() => {
  110. this._onDeviceOrientationChangedObservable.clear();
  111. });
  112. }
  113. }
  114. /**
  115. * Attach the input controls to a specific dom element to get the input from.
  116. * @param element Defines the element the controls should be listened from
  117. * @param noPreventDefault Defines whether event caught by the controls should call preventdefault() (https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault)
  118. */
  119. public attachControl(element: HTMLElement, noPreventDefault?: boolean): void {
  120. let hostWindow = this.camera.getScene().getEngine().getHostWindow();
  121. if (hostWindow) {
  122. const eventHandler = () => {
  123. hostWindow!.addEventListener("orientationchange", this._orientationChanged);
  124. hostWindow!.addEventListener("deviceorientation", this._deviceOrientation);
  125. //In certain cases, the attach control is called AFTER orientation was changed,
  126. //So this is needed.
  127. this._orientationChanged();
  128. };
  129. if (typeof (<any>DeviceOrientationEvent).requestPermission === 'function') {
  130. (<any>DeviceOrientationEvent).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. * @param element Defines the element to stop listening the inputs from
  162. */
  163. public detachControl(element: Nullable<HTMLElement>): void {
  164. window.removeEventListener("orientationchange", this._orientationChanged);
  165. window.removeEventListener("deviceorientation", this._deviceOrientation);
  166. this._alpha = 0;
  167. }
  168. /**
  169. * Update the current camera state depending on the inputs that have been used this frame.
  170. * This is a dynamically created lambda to avoid the performance penalty of looping for inputs in the render loop.
  171. */
  172. public checkInputs(): void {
  173. //if no device orientation provided, don't update the rotation.
  174. //Only testing against alpha under the assumption thatnorientation will never be so exact when set.
  175. if (!this._alpha) { return; }
  176. Quaternion.RotationYawPitchRollToRef(Tools.ToRadians(this._alpha), Tools.ToRadians(this._beta), -Tools.ToRadians(this._gamma), this.camera.rotationQuaternion);
  177. this._camera.rotationQuaternion.multiplyInPlace(this._screenQuaternion);
  178. this._camera.rotationQuaternion.multiplyInPlace(this._constantTranform);
  179. //Mirror on XY Plane
  180. this._camera.rotationQuaternion.z *= -1;
  181. this._camera.rotationQuaternion.w *= -1;
  182. }
  183. /**
  184. * Gets the class name of the current intput.
  185. * @returns the class name
  186. */
  187. public getClassName(): string {
  188. return "FreeCameraDeviceOrientationInput";
  189. }
  190. /**
  191. * Get the friendly name associated with the input class.
  192. * @returns the input friendly name
  193. */
  194. public getSimpleName(): string {
  195. return "deviceOrientation";
  196. }
  197. }
  198. (<any>CameraInputTypes)["FreeCameraDeviceOrientationInput"] = FreeCameraDeviceOrientationInput;