freeCameraDeviceOrientationInput.js 8.5 KB

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