babylon.arcRotateCamera.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. /// <reference path="babylon.targetCamera.ts" />
  2. /// <reference path="..\Tools\babylon.tools.ts" />
  3. module BABYLON {
  4. export class ArcRotateCamera extends TargetCamera {
  5. @serialize()
  6. public alpha: number;
  7. @serialize()
  8. public beta: number;
  9. @serialize()
  10. public radius: number;
  11. @serializeAsVector3()
  12. public target: Vector3;
  13. @serialize()
  14. public inertialAlphaOffset = 0;
  15. @serialize()
  16. public inertialBetaOffset = 0;
  17. @serialize()
  18. public inertialRadiusOffset = 0;
  19. @serialize()
  20. public lowerAlphaLimit = null;
  21. @serialize()
  22. public upperAlphaLimit = null;
  23. @serialize()
  24. public lowerBetaLimit = 0.01;
  25. @serialize()
  26. public upperBetaLimit = Math.PI;
  27. @serialize()
  28. public lowerRadiusLimit = null;
  29. @serialize()
  30. public upperRadiusLimit = null;
  31. @serialize()
  32. public inertialPanningX: number = 0;
  33. @serialize()
  34. public inertialPanningY: number = 0;
  35. //-- begin properties for backward compatibility for inputs
  36. public get angularSensibilityX() {
  37. var pointers = <ArcRotateCameraPointersInput>this.inputs.attached["pointers"];
  38. if (pointers)
  39. return pointers.angularSensibilityX;
  40. }
  41. public set angularSensibilityX(value) {
  42. var pointers = <ArcRotateCameraPointersInput>this.inputs.attached["pointers"];
  43. if (pointers) {
  44. pointers.angularSensibilityX = value;
  45. }
  46. }
  47. public get angularSensibilityY() {
  48. var pointers = <ArcRotateCameraPointersInput>this.inputs.attached["pointers"];
  49. if (pointers)
  50. return pointers.angularSensibilityY;
  51. }
  52. public set angularSensibilityY(value) {
  53. var pointers = <ArcRotateCameraPointersInput>this.inputs.attached["pointers"];
  54. if (pointers) {
  55. pointers.angularSensibilityY = value;
  56. }
  57. }
  58. public get pinchPrecision() {
  59. var pointers = <ArcRotateCameraPointersInput>this.inputs.attached["pointers"];
  60. if (pointers)
  61. return pointers.pinchPrecision;
  62. }
  63. public set pinchPrecision(value) {
  64. var pointers = <ArcRotateCameraPointersInput>this.inputs.attached["pointers"];
  65. if (pointers) {
  66. pointers.pinchPrecision = value;
  67. }
  68. }
  69. public get panningSensibility() {
  70. var pointers = <ArcRotateCameraPointersInput>this.inputs.attached["pointers"];
  71. if (pointers)
  72. return pointers.panningSensibility;
  73. }
  74. public set panningSensibility(value) {
  75. var pointers = <ArcRotateCameraPointersInput>this.inputs.attached["pointers"];
  76. if (pointers) {
  77. pointers.panningSensibility = value;
  78. }
  79. }
  80. public get keysUp() {
  81. var keyboard = <ArcRotateCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
  82. if (keyboard)
  83. return keyboard.keysUp;
  84. }
  85. public set keysUp(value) {
  86. var keyboard = <ArcRotateCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
  87. if (keyboard)
  88. keyboard.keysUp = value;
  89. }
  90. public get keysDown() {
  91. var keyboard = <ArcRotateCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
  92. if (keyboard)
  93. return keyboard.keysDown;
  94. }
  95. public set keysDown(value) {
  96. var keyboard = <ArcRotateCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
  97. if (keyboard)
  98. keyboard.keysDown = value;
  99. }
  100. public get keysLeft() {
  101. var keyboard = <ArcRotateCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
  102. if (keyboard)
  103. return keyboard.keysLeft;
  104. }
  105. public set keysLeft(value) {
  106. var keyboard = <ArcRotateCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
  107. if (keyboard)
  108. keyboard.keysLeft = value;
  109. }
  110. public get keysRight() {
  111. var keyboard = <ArcRotateCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
  112. if (keyboard)
  113. return keyboard.keysRight;
  114. }
  115. public set keysRight(value) {
  116. var keyboard = <ArcRotateCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
  117. if (keyboard)
  118. keyboard.keysRight = value;
  119. }
  120. public get wheelPrecision() {
  121. var mousewheel = <ArcRotateCameraMouseWheelInput>this.inputs.attached["mousewheel"];
  122. if (mousewheel)
  123. return mousewheel.wheelPrecision;
  124. }
  125. public set wheelPrecision(value) {
  126. var mousewheel = <ArcRotateCameraMouseWheelInput>this.inputs.attached["mousewheel"];
  127. if (mousewheel)
  128. mousewheel.wheelPrecision = value;
  129. }
  130. //-- end properties for backward compatibility for inputs
  131. @serialize()
  132. public zoomOnFactor = 1;
  133. public targetScreenOffset = Vector2.Zero();
  134. @serialize()
  135. public allowUpsideDown = true;
  136. public _viewMatrix = new Matrix();
  137. public _useCtrlForPanning: boolean;
  138. public _panningMouseButton: number;
  139. public inputs: ArcRotateCameraInputsManager;
  140. public _reset: () => void;
  141. // Panning
  142. public panningAxis: Vector3 = new Vector3(1, 1, 0);
  143. private _localDirection: Vector3;
  144. private _transformedDirection: Vector3;
  145. // Collisions
  146. public onCollide: (collidedMesh: AbstractMesh) => void;
  147. public checkCollisions = false;
  148. public collisionRadius = new Vector3(0.5, 0.5, 0.5);
  149. private _collider = new Collider();
  150. private _previousPosition = Vector3.Zero();
  151. private _collisionVelocity = Vector3.Zero();
  152. private _newPosition = Vector3.Zero();
  153. private _previousAlpha: number;
  154. private _previousBeta: number;
  155. private _previousRadius: number;
  156. //due to async collision inspection
  157. private _collisionTriggered: boolean;
  158. private _targetBoundingCenter: Vector3;
  159. constructor(name: string, alpha: number, beta: number, radius: number, target: Vector3, scene: Scene) {
  160. super(name, Vector3.Zero(), scene);
  161. if (!target) {
  162. this.target = Vector3.Zero();
  163. } else {
  164. this.target = target;
  165. }
  166. this.alpha = alpha;
  167. this.beta = beta;
  168. this.radius = radius;
  169. this.getViewMatrix();
  170. this.inputs = new ArcRotateCameraInputsManager(this);
  171. this.inputs.addKeyboard().addMouseWheel().addPointers().addGamepad();
  172. }
  173. // Cache
  174. public _initCache(): void {
  175. super._initCache();
  176. this._cache.target = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
  177. this._cache.alpha = undefined;
  178. this._cache.beta = undefined;
  179. this._cache.radius = undefined;
  180. this._cache.targetScreenOffset = Vector2.Zero();
  181. }
  182. public _updateCache(ignoreParentClass?: boolean): void {
  183. if (!ignoreParentClass) {
  184. super._updateCache();
  185. }
  186. this._cache.target.copyFrom(this._getTargetPosition());
  187. this._cache.alpha = this.alpha;
  188. this._cache.beta = this.beta;
  189. this._cache.radius = this.radius;
  190. this._cache.targetScreenOffset.copyFrom(this.targetScreenOffset);
  191. }
  192. private _getTargetPosition(): Vector3 {
  193. if ((<any>this.target).getAbsolutePosition) {
  194. var pos : Vector3 = (<any>this.target).getAbsolutePosition();
  195. return this._targetBoundingCenter ? pos.add(this._targetBoundingCenter) : pos;
  196. }
  197. return this.target;
  198. }
  199. // Synchronized
  200. public _isSynchronizedViewMatrix(): boolean {
  201. if (!super._isSynchronizedViewMatrix())
  202. return false;
  203. return this._cache.target.equals(this.target)
  204. && this._cache.alpha === this.alpha
  205. && this._cache.beta === this.beta
  206. && this._cache.radius === this.radius
  207. && this._cache.targetScreenOffset.equals(this.targetScreenOffset);
  208. }
  209. // Methods
  210. public attachControl(element: HTMLElement, noPreventDefault?: boolean, useCtrlForPanning: boolean = true, panningMouseButton: number = 2): void {
  211. this._useCtrlForPanning = useCtrlForPanning;
  212. this._panningMouseButton = panningMouseButton;
  213. this.inputs.attachElement(element, noPreventDefault);
  214. this._reset = () => {
  215. this.inertialAlphaOffset = 0;
  216. this.inertialBetaOffset = 0;
  217. this.inertialRadiusOffset = 0;
  218. };
  219. }
  220. public detachControl(element: HTMLElement): void {
  221. this.inputs.detachElement(element);
  222. if (this._reset) {
  223. this._reset();
  224. }
  225. }
  226. public _checkInputs(): void {
  227. //if (async) collision inspection was triggered, don't update the camera's position - until the collision callback was called.
  228. if (this._collisionTriggered) {
  229. return;
  230. }
  231. this.inputs.checkInputs();
  232. // Inertia
  233. if (this.inertialAlphaOffset !== 0 || this.inertialBetaOffset !== 0 || this.inertialRadiusOffset !== 0) {
  234. if (this.getScene().useRightHandedSystem) {
  235. this.alpha -= this.beta <= 0 ? -this.inertialAlphaOffset : this.inertialAlphaOffset;
  236. } else {
  237. this.alpha += this.beta <= 0 ? -this.inertialAlphaOffset : this.inertialAlphaOffset;
  238. }
  239. this.beta += this.inertialBetaOffset;
  240. this.radius -= this.inertialRadiusOffset;
  241. this.inertialAlphaOffset *= this.inertia;
  242. this.inertialBetaOffset *= this.inertia;
  243. this.inertialRadiusOffset *= this.inertia;
  244. if (Math.abs(this.inertialAlphaOffset) < Epsilon)
  245. this.inertialAlphaOffset = 0;
  246. if (Math.abs(this.inertialBetaOffset) < Epsilon)
  247. this.inertialBetaOffset = 0;
  248. if (Math.abs(this.inertialRadiusOffset) < Epsilon)
  249. this.inertialRadiusOffset = 0;
  250. }
  251. // Panning inertia
  252. if (this.inertialPanningX !== 0 || this.inertialPanningY !== 0) {
  253. if (!this._localDirection) {
  254. this._localDirection = Vector3.Zero();
  255. this._transformedDirection = Vector3.Zero();
  256. }
  257. this.inertialPanningX *= this.inertia;
  258. this.inertialPanningY *= this.inertia;
  259. if (Math.abs(this.inertialPanningX) < Epsilon)
  260. this.inertialPanningX = 0;
  261. if (Math.abs(this.inertialPanningY) < Epsilon)
  262. this.inertialPanningY = 0;
  263. this._localDirection.copyFromFloats(this.inertialPanningX, this.inertialPanningY, this.inertialPanningY);
  264. this._localDirection.multiplyInPlace(this.panningAxis);
  265. this._viewMatrix.invertToRef(this._cameraTransformMatrix);
  266. Vector3.TransformNormalToRef(this._localDirection, this._cameraTransformMatrix, this._transformedDirection);
  267. //Eliminate y if map panning is enabled (panningAxis == 1,0,1)
  268. if (!this.panningAxis.y) {
  269. this._transformedDirection.y = 0;
  270. }
  271. if (!(<any>this.target).getAbsolutePosition) {
  272. this.target.addInPlace(this._transformedDirection);
  273. }
  274. }
  275. // Limits
  276. this._checkLimits();
  277. super._checkInputs();
  278. }
  279. private _checkLimits() {
  280. if (this.lowerBetaLimit === null || this.lowerBetaLimit === undefined) {
  281. if (this.allowUpsideDown && this.beta > Math.PI) {
  282. this.beta = this.beta - (2 * Math.PI);
  283. }
  284. } else {
  285. if (this.beta < this.lowerBetaLimit) {
  286. this.beta = this.lowerBetaLimit;
  287. }
  288. }
  289. if (this.upperBetaLimit === null || this.upperBetaLimit === undefined) {
  290. if (this.allowUpsideDown && this.beta < -Math.PI) {
  291. this.beta = this.beta + (2 * Math.PI);
  292. }
  293. } else {
  294. if (this.beta > this.upperBetaLimit) {
  295. this.beta = this.upperBetaLimit;
  296. }
  297. }
  298. if (this.lowerAlphaLimit && this.alpha < this.lowerAlphaLimit) {
  299. this.alpha = this.lowerAlphaLimit;
  300. }
  301. if (this.upperAlphaLimit && this.alpha > this.upperAlphaLimit) {
  302. this.alpha = this.upperAlphaLimit;
  303. }
  304. if (this.lowerRadiusLimit && this.radius < this.lowerRadiusLimit) {
  305. this.radius = this.lowerRadiusLimit;
  306. }
  307. if (this.upperRadiusLimit && this.radius > this.upperRadiusLimit) {
  308. this.radius = this.upperRadiusLimit;
  309. }
  310. }
  311. public rebuildAnglesAndRadius() {
  312. var radiusv3 = this.position.subtract(this._getTargetPosition());
  313. this.radius = radiusv3.length();
  314. // Alpha
  315. this.alpha = Math.acos(radiusv3.x / Math.sqrt(Math.pow(radiusv3.x, 2) + Math.pow(radiusv3.z, 2)));
  316. if (radiusv3.z < 0) {
  317. this.alpha = 2 * Math.PI - this.alpha;
  318. }
  319. // Beta
  320. this.beta = Math.acos(radiusv3.y / this.radius);
  321. this._checkLimits();
  322. }
  323. public setPosition(position: Vector3): void {
  324. if (this.position.equals(position)) {
  325. return;
  326. }
  327. this.position.copyFrom(position);
  328. this.rebuildAnglesAndRadius();
  329. }
  330. public setTarget(target: Vector3, toBoundingCenter = false, allowSamePosition = false): void {
  331. if (!allowSamePosition && this._getTargetPosition().equals(target)) {
  332. return;
  333. }
  334. if (toBoundingCenter && (<any>target).getBoundingInfo){
  335. this._targetBoundingCenter = (<any>target).getBoundingInfo().boundingBox.center.clone();
  336. }else{
  337. this._targetBoundingCenter = null;
  338. }
  339. this.target = target;
  340. this.rebuildAnglesAndRadius();
  341. }
  342. public _getViewMatrix(): Matrix {
  343. // Compute
  344. var cosa = Math.cos(this.alpha);
  345. var sina = Math.sin(this.alpha);
  346. var cosb = Math.cos(this.beta);
  347. var sinb = Math.sin(this.beta);
  348. if (sinb === 0) {
  349. sinb = 0.0001;
  350. }
  351. var target = this._getTargetPosition();
  352. target.addToRef(new Vector3(this.radius * cosa * sinb, this.radius * cosb, this.radius * sina * sinb), this._newPosition);
  353. if (this.getScene().collisionsEnabled && this.checkCollisions) {
  354. this._collider.radius = this.collisionRadius;
  355. this._newPosition.subtractToRef(this.position, this._collisionVelocity);
  356. this._collisionTriggered = true;
  357. this.getScene().collisionCoordinator.getNewPosition(this.position, this._collisionVelocity, this._collider, 3, null, this._onCollisionPositionChange, this.uniqueId);
  358. } else {
  359. this.position.copyFrom(this._newPosition);
  360. var up = this.upVector;
  361. if (this.allowUpsideDown && sinb < 0) {
  362. up = up.clone();
  363. up = up.negate();
  364. }
  365. if (this.getScene().useRightHandedSystem) {
  366. Matrix.LookAtRHToRef(this.position, target, up, this._viewMatrix);
  367. } else {
  368. Matrix.LookAtLHToRef(this.position, target, up, this._viewMatrix);
  369. }
  370. this._viewMatrix.m[12] += this.targetScreenOffset.x;
  371. this._viewMatrix.m[13] += this.targetScreenOffset.y;
  372. }
  373. this._currentTarget = target;
  374. return this._viewMatrix;
  375. }
  376. private _onCollisionPositionChange = (collisionId: number, newPosition: Vector3, collidedMesh: AbstractMesh = null) => {
  377. if (this.getScene().workerCollisions && this.checkCollisions) {
  378. newPosition.multiplyInPlace(this._collider.radius);
  379. }
  380. if (!collidedMesh) {
  381. this._previousPosition.copyFrom(this.position);
  382. } else {
  383. this.setPosition(newPosition);
  384. if (this.onCollide) {
  385. this.onCollide(collidedMesh);
  386. }
  387. }
  388. // Recompute because of constraints
  389. var cosa = Math.cos(this.alpha);
  390. var sina = Math.sin(this.alpha);
  391. var cosb = Math.cos(this.beta);
  392. var sinb = Math.sin(this.beta);
  393. if (sinb === 0) {
  394. sinb = 0.0001;
  395. }
  396. var target = this._getTargetPosition();
  397. target.addToRef(new Vector3(this.radius * cosa * sinb, this.radius * cosb, this.radius * sina * sinb), this._newPosition);
  398. this.position.copyFrom(this._newPosition);
  399. var up = this.upVector;
  400. if (this.allowUpsideDown && this.beta < 0) {
  401. up = up.clone();
  402. up = up.negate();
  403. }
  404. Matrix.LookAtLHToRef(this.position, target, up, this._viewMatrix);
  405. this._viewMatrix.m[12] += this.targetScreenOffset.x;
  406. this._viewMatrix.m[13] += this.targetScreenOffset.y;
  407. this._collisionTriggered = false;
  408. }
  409. public zoomOn(meshes?: AbstractMesh[], doNotUpdateMaxZ = false): void {
  410. meshes = meshes || this.getScene().meshes;
  411. var minMaxVector = Mesh.MinMax(meshes);
  412. var distance = Vector3.Distance(minMaxVector.min, minMaxVector.max);
  413. this.radius = distance * this.zoomOnFactor;
  414. this.focusOn({ min: minMaxVector.min, max: minMaxVector.max, distance: distance }, doNotUpdateMaxZ);
  415. }
  416. public focusOn(meshesOrMinMaxVectorAndDistance, doNotUpdateMaxZ = false): void {
  417. var meshesOrMinMaxVector;
  418. var distance;
  419. if (meshesOrMinMaxVectorAndDistance.min === undefined) { // meshes
  420. meshesOrMinMaxVector = meshesOrMinMaxVectorAndDistance || this.getScene().meshes;
  421. meshesOrMinMaxVector = Mesh.MinMax(meshesOrMinMaxVector);
  422. distance = Vector3.Distance(meshesOrMinMaxVector.min, meshesOrMinMaxVector.max);
  423. }
  424. else { //minMaxVector and distance
  425. meshesOrMinMaxVector = meshesOrMinMaxVectorAndDistance;
  426. distance = meshesOrMinMaxVectorAndDistance.distance;
  427. }
  428. this.target = Mesh.Center(meshesOrMinMaxVector);
  429. if (!doNotUpdateMaxZ) {
  430. this.maxZ = distance * 2;
  431. }
  432. }
  433. /**
  434. * @override
  435. * Override Camera.createRigCamera
  436. */
  437. public createRigCamera(name: string, cameraIndex: number): Camera {
  438. var alphaShift : number;
  439. switch (this.cameraRigMode) {
  440. case Camera.RIG_MODE_STEREOSCOPIC_ANAGLYPH:
  441. case Camera.RIG_MODE_STEREOSCOPIC_SIDEBYSIDE_PARALLEL:
  442. case Camera.RIG_MODE_STEREOSCOPIC_OVERUNDER:
  443. case Camera.RIG_MODE_VR:
  444. alphaShift = this._cameraRigParams.stereoHalfAngle * (cameraIndex === 0 ? 1 : -1);
  445. break;
  446. case Camera.RIG_MODE_STEREOSCOPIC_SIDEBYSIDE_CROSSEYED:
  447. alphaShift = this._cameraRigParams.stereoHalfAngle * (cameraIndex === 0 ? -1 : 1);
  448. break;
  449. }
  450. var rigCam = new ArcRotateCamera(name, this.alpha + alphaShift, this.beta, this.radius, this.target, this.getScene());
  451. rigCam._cameraRigParams = {};
  452. return rigCam;
  453. }
  454. /**
  455. * @override
  456. * Override Camera._updateRigCameras
  457. */
  458. public _updateRigCameras() {
  459. var camLeft = <ArcRotateCamera>this._rigCameras[0];
  460. var camRight = <ArcRotateCamera>this._rigCameras[1];
  461. camLeft.beta = camRight.beta = this.beta;
  462. camLeft.radius = camRight.radius = this.radius;
  463. switch (this.cameraRigMode) {
  464. case Camera.RIG_MODE_STEREOSCOPIC_ANAGLYPH:
  465. case Camera.RIG_MODE_STEREOSCOPIC_SIDEBYSIDE_PARALLEL:
  466. case Camera.RIG_MODE_STEREOSCOPIC_OVERUNDER:
  467. case Camera.RIG_MODE_VR:
  468. camLeft.alpha = this.alpha - this._cameraRigParams.stereoHalfAngle;
  469. camRight.alpha = this.alpha + this._cameraRigParams.stereoHalfAngle;
  470. break;
  471. case Camera.RIG_MODE_STEREOSCOPIC_SIDEBYSIDE_CROSSEYED:
  472. camLeft.alpha = this.alpha + this._cameraRigParams.stereoHalfAngle;
  473. camRight.alpha = this.alpha - this._cameraRigParams.stereoHalfAngle;
  474. break;
  475. }
  476. super._updateRigCameras();
  477. }
  478. public dispose(): void {
  479. this.inputs.clear();
  480. super.dispose();
  481. }
  482. public getClassName(): string {
  483. return "ArcRotateCamera";
  484. }
  485. }
  486. }