flyCamera.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. import { __decorate } from "../tslib.es6.js";
  2. import { serialize, serializeAsVector3 } from "../Misc/decorators.js";
  3. import { Vector3 } from "../Maths/math.vector.js";
  4. import { Engine } from "../Engines/engine.js";
  5. import { TargetCamera } from "./targetCamera.js";
  6. import { FlyCameraInputsManager } from "./flyCameraInputsManager.js";
  7. import { Tools } from "../Misc/tools.js";
  8. /**
  9. * This is a flying camera, designed for 3D movement and rotation in all directions,
  10. * such as in a 3D Space Shooter or a Flight Simulator.
  11. */
  12. export class FlyCamera extends TargetCamera {
  13. /**
  14. * Gets the input sensibility for mouse input.
  15. * Higher values reduce sensitivity.
  16. */
  17. get angularSensibility() {
  18. const mouse = this.inputs.attached["mouse"];
  19. if (mouse) {
  20. return mouse.angularSensibility;
  21. }
  22. return 0;
  23. }
  24. /**
  25. * Sets the input sensibility for a mouse input.
  26. * Higher values reduce sensitivity.
  27. */
  28. set angularSensibility(value) {
  29. const mouse = this.inputs.attached["mouse"];
  30. if (mouse) {
  31. mouse.angularSensibility = value;
  32. }
  33. }
  34. /**
  35. * Get the keys for camera movement forward.
  36. */
  37. get keysForward() {
  38. const keyboard = this.inputs.attached["keyboard"];
  39. if (keyboard) {
  40. return keyboard.keysForward;
  41. }
  42. return [];
  43. }
  44. /**
  45. * Set the keys for camera movement forward.
  46. */
  47. set keysForward(value) {
  48. const keyboard = this.inputs.attached["keyboard"];
  49. if (keyboard) {
  50. keyboard.keysForward = value;
  51. }
  52. }
  53. /**
  54. * Get the keys for camera movement backward.
  55. */
  56. get keysBackward() {
  57. const keyboard = this.inputs.attached["keyboard"];
  58. if (keyboard) {
  59. return keyboard.keysBackward;
  60. }
  61. return [];
  62. }
  63. set keysBackward(value) {
  64. const keyboard = this.inputs.attached["keyboard"];
  65. if (keyboard) {
  66. keyboard.keysBackward = value;
  67. }
  68. }
  69. /**
  70. * Get the keys for camera movement up.
  71. */
  72. get keysUp() {
  73. const keyboard = this.inputs.attached["keyboard"];
  74. if (keyboard) {
  75. return keyboard.keysUp;
  76. }
  77. return [];
  78. }
  79. /**
  80. * Set the keys for camera movement up.
  81. */
  82. set keysUp(value) {
  83. const keyboard = this.inputs.attached["keyboard"];
  84. if (keyboard) {
  85. keyboard.keysUp = value;
  86. }
  87. }
  88. /**
  89. * Get the keys for camera movement down.
  90. */
  91. get keysDown() {
  92. const keyboard = this.inputs.attached["keyboard"];
  93. if (keyboard) {
  94. return keyboard.keysDown;
  95. }
  96. return [];
  97. }
  98. /**
  99. * Set the keys for camera movement down.
  100. */
  101. set keysDown(value) {
  102. const keyboard = this.inputs.attached["keyboard"];
  103. if (keyboard) {
  104. keyboard.keysDown = value;
  105. }
  106. }
  107. /**
  108. * Get the keys for camera movement left.
  109. */
  110. get keysLeft() {
  111. const keyboard = this.inputs.attached["keyboard"];
  112. if (keyboard) {
  113. return keyboard.keysLeft;
  114. }
  115. return [];
  116. }
  117. /**
  118. * Set the keys for camera movement left.
  119. */
  120. set keysLeft(value) {
  121. const keyboard = this.inputs.attached["keyboard"];
  122. if (keyboard) {
  123. keyboard.keysLeft = value;
  124. }
  125. }
  126. /**
  127. * Set the keys for camera movement right.
  128. */
  129. get keysRight() {
  130. const keyboard = this.inputs.attached["keyboard"];
  131. if (keyboard) {
  132. return keyboard.keysRight;
  133. }
  134. return [];
  135. }
  136. /**
  137. * Set the keys for camera movement right.
  138. */
  139. set keysRight(value) {
  140. const keyboard = this.inputs.attached["keyboard"];
  141. if (keyboard) {
  142. keyboard.keysRight = value;
  143. }
  144. }
  145. /**
  146. * Instantiates a FlyCamera.
  147. * This is a flying camera, designed for 3D movement and rotation in all directions,
  148. * such as in a 3D Space Shooter or a Flight Simulator.
  149. * @param name Define the name of the camera in the scene.
  150. * @param position Define the starting position of the camera in the scene.
  151. * @param scene Define the scene the camera belongs to.
  152. * @param setActiveOnSceneIfNoneActive Defines whether the camera should be marked as active, if no other camera has been defined as active.
  153. */
  154. constructor(name, position, scene, setActiveOnSceneIfNoneActive = true) {
  155. super(name, position, scene, setActiveOnSceneIfNoneActive);
  156. /**
  157. * Define the collision ellipsoid of the camera.
  158. * This is helpful for simulating a camera body, like a player's body.
  159. * @see https://doc.babylonjs.com/features/featuresDeepDive/cameras/camera_collisions#arcrotatecamera
  160. */
  161. this.ellipsoid = new Vector3(1, 1, 1);
  162. /**
  163. * Define an offset for the position of the ellipsoid around the camera.
  164. * This can be helpful if the camera is attached away from the player's body center,
  165. * such as at its head.
  166. */
  167. this.ellipsoidOffset = new Vector3(0, 0, 0);
  168. /**
  169. * Enable or disable collisions of the camera with the rest of the scene objects.
  170. */
  171. this.checkCollisions = false;
  172. /**
  173. * Enable or disable gravity on the camera.
  174. */
  175. this.applyGravity = false;
  176. /**
  177. * Define the current direction the camera is moving to.
  178. */
  179. this.cameraDirection = Vector3.Zero();
  180. /**
  181. * Track Roll to maintain the wanted Rolling when looking around.
  182. */
  183. this._trackRoll = 0;
  184. /**
  185. * Slowly correct the Roll to its original value after a Pitch+Yaw rotation.
  186. */
  187. this.rollCorrect = 100;
  188. /**
  189. * Mimic a banked turn, Rolling the camera when Yawing.
  190. * It's recommended to use rollCorrect = 10 for faster banking correction.
  191. */
  192. this.bankedTurn = false;
  193. /**
  194. * Limit in radians for how much Roll banking will add. (Default: 90°)
  195. */
  196. this.bankedTurnLimit = Math.PI / 2;
  197. /**
  198. * Value of 0 disables the banked Roll.
  199. * Value of 1 is equal to the Yaw angle in radians.
  200. */
  201. this.bankedTurnMultiplier = 1;
  202. this._needMoveForGravity = false;
  203. this._oldPosition = Vector3.Zero();
  204. this._diffPosition = Vector3.Zero();
  205. this._newPosition = Vector3.Zero();
  206. // Collisions.
  207. this._collisionMask = -1;
  208. /**
  209. * @internal
  210. */
  211. this._onCollisionPositionChange = (collisionId, newPosition, collidedMesh = null) => {
  212. const updatePosition = (newPos) => {
  213. this._newPosition.copyFrom(newPos);
  214. this._newPosition.subtractToRef(this._oldPosition, this._diffPosition);
  215. if (this._diffPosition.length() > Engine.CollisionsEpsilon) {
  216. this.position.addInPlace(this._diffPosition);
  217. if (this.onCollide && collidedMesh) {
  218. this.onCollide(collidedMesh);
  219. }
  220. }
  221. };
  222. updatePosition(newPosition);
  223. };
  224. this.inputs = new FlyCameraInputsManager(this);
  225. this.inputs.addKeyboard().addMouse();
  226. }
  227. /**
  228. * Attached controls to the current camera.
  229. * @param ignored defines an ignored parameter kept for backward compatibility.
  230. * @param noPreventDefault Defines whether event caught by the controls should call preventdefault() (https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault)
  231. */
  232. attachControl(ignored, noPreventDefault) {
  233. // eslint-disable-next-line prefer-rest-params
  234. noPreventDefault = Tools.BackCompatCameraNoPreventDefault(arguments);
  235. this.inputs.attachElement(noPreventDefault);
  236. }
  237. /**
  238. * Detach a control from the HTML DOM element.
  239. * The camera will stop reacting to that input.
  240. */
  241. detachControl() {
  242. this.inputs.detachElement();
  243. this.cameraDirection = new Vector3(0, 0, 0);
  244. }
  245. /**
  246. * Get the mask that the camera ignores in collision events.
  247. */
  248. get collisionMask() {
  249. return this._collisionMask;
  250. }
  251. /**
  252. * Set the mask that the camera ignores in collision events.
  253. */
  254. set collisionMask(mask) {
  255. this._collisionMask = !isNaN(mask) ? mask : -1;
  256. }
  257. /**
  258. * @internal
  259. */
  260. _collideWithWorld(displacement) {
  261. let globalPosition;
  262. if (this.parent) {
  263. globalPosition = Vector3.TransformCoordinates(this.position, this.parent.getWorldMatrix());
  264. }
  265. else {
  266. globalPosition = this.position;
  267. }
  268. globalPosition.subtractFromFloatsToRef(0, this.ellipsoid.y, 0, this._oldPosition);
  269. this._oldPosition.addInPlace(this.ellipsoidOffset);
  270. const coordinator = this.getScene().collisionCoordinator;
  271. if (!this._collider) {
  272. this._collider = coordinator.createCollider();
  273. }
  274. this._collider._radius = this.ellipsoid;
  275. this._collider.collisionMask = this._collisionMask;
  276. // No need for clone, as long as gravity is not on.
  277. let actualDisplacement = displacement;
  278. // Add gravity to direction to prevent dual-collision checking.
  279. if (this.applyGravity) {
  280. // This prevents mending with cameraDirection, a global variable of the fly camera class.
  281. actualDisplacement = displacement.add(this.getScene().gravity);
  282. }
  283. coordinator.getNewPosition(this._oldPosition, actualDisplacement, this._collider, 3, null, this._onCollisionPositionChange, this.uniqueId);
  284. }
  285. /** @internal */
  286. _checkInputs() {
  287. if (!this._localDirection) {
  288. this._localDirection = Vector3.Zero();
  289. this._transformedDirection = Vector3.Zero();
  290. }
  291. this.inputs.checkInputs();
  292. super._checkInputs();
  293. }
  294. /**
  295. * Enable movement without a user input. This allows gravity to always be applied.
  296. */
  297. set needMoveForGravity(value) {
  298. this._needMoveForGravity = value;
  299. }
  300. /**
  301. * When true, gravity is applied whether there is user input or not.
  302. */
  303. get needMoveForGravity() {
  304. return this._needMoveForGravity;
  305. }
  306. /** @internal */
  307. _decideIfNeedsToMove() {
  308. return this._needMoveForGravity || Math.abs(this.cameraDirection.x) > 0 || Math.abs(this.cameraDirection.y) > 0 || Math.abs(this.cameraDirection.z) > 0;
  309. }
  310. /** @internal */
  311. _updatePosition() {
  312. if (this.checkCollisions && this.getScene().collisionsEnabled) {
  313. this._collideWithWorld(this.cameraDirection);
  314. }
  315. else {
  316. super._updatePosition();
  317. }
  318. }
  319. /**
  320. * Restore the Roll to its target value at the rate specified.
  321. * @param rate - Higher means slower restoring.
  322. * @internal
  323. */
  324. restoreRoll(rate) {
  325. const limit = this._trackRoll; // Target Roll.
  326. const z = this.rotation.z; // Current Roll.
  327. const delta = limit - z; // Difference in Roll.
  328. const minRad = 0.001; // Tenth of a radian is a barely noticable difference.
  329. // If the difference is noticable, restore the Roll.
  330. if (Math.abs(delta) >= minRad) {
  331. // Change Z rotation towards the target Roll.
  332. this.rotation.z += delta / rate;
  333. // Match when near enough.
  334. if (Math.abs(limit - this.rotation.z) <= minRad) {
  335. this.rotation.z = limit;
  336. }
  337. }
  338. }
  339. /**
  340. * Destroy the camera and release the current resources held by it.
  341. */
  342. dispose() {
  343. this.inputs.clear();
  344. super.dispose();
  345. }
  346. /**
  347. * Get the current object class name.
  348. * @returns the class name.
  349. */
  350. getClassName() {
  351. return "FlyCamera";
  352. }
  353. }
  354. __decorate([
  355. serializeAsVector3()
  356. ], FlyCamera.prototype, "ellipsoid", void 0);
  357. __decorate([
  358. serializeAsVector3()
  359. ], FlyCamera.prototype, "ellipsoidOffset", void 0);
  360. __decorate([
  361. serialize()
  362. ], FlyCamera.prototype, "checkCollisions", void 0);
  363. __decorate([
  364. serialize()
  365. ], FlyCamera.prototype, "applyGravity", void 0);
  366. //# sourceMappingURL=flyCamera.js.map