/// /// module BABYLON { export class ArcRotateCamera extends TargetCamera { @serialize() public alpha: number; @serialize() public beta: number; @serialize() public radius: number; @serializeAsVector3() public target: Vector3; @serialize() public inertialAlphaOffset = 0; @serialize() public inertialBetaOffset = 0; @serialize() public inertialRadiusOffset = 0; @serialize() public lowerAlphaLimit = null; @serialize() public upperAlphaLimit = null; @serialize() public lowerBetaLimit = 0.01; @serialize() public upperBetaLimit = Math.PI; @serialize() public lowerRadiusLimit = null; @serialize() public upperRadiusLimit = null; @serialize() public inertialPanningX: number = 0; @serialize() public inertialPanningY: number = 0; //-- begin properties for backward compatibility for inputs public get angularSensibilityX() { var pointers = this.inputs.attached["pointers"]; if (pointers) return pointers.angularSensibilityX; } public set angularSensibilityX(value) { var pointers = this.inputs.attached["pointers"]; if (pointers) { pointers.angularSensibilityX = value; } } public get angularSensibilityY() { var pointers = this.inputs.attached["pointers"]; if (pointers) return pointers.angularSensibilityY; } public set angularSensibilityY(value) { var pointers = this.inputs.attached["pointers"]; if (pointers) { pointers.angularSensibilityY = value; } } public get pinchPrecision() { var pointers = this.inputs.attached["pointers"]; if (pointers) return pointers.pinchPrecision; } public set pinchPrecision(value) { var pointers = this.inputs.attached["pointers"]; if (pointers) { pointers.pinchPrecision = value; } } public get panningSensibility() { var pointers = this.inputs.attached["pointers"]; if (pointers) return pointers.panningSensibility; } public set panningSensibility(value) { var pointers = this.inputs.attached["pointers"]; if (pointers) { pointers.panningSensibility = value; } } public get keysUp() { var keyboard = this.inputs.attached["keyboard"]; if (keyboard) return keyboard.keysUp; } public set keysUp(value) { var keyboard = this.inputs.attached["keyboard"]; if (keyboard) keyboard.keysUp = value; } public get keysDown() { var keyboard = this.inputs.attached["keyboard"]; if (keyboard) return keyboard.keysDown; } public set keysDown(value) { var keyboard = this.inputs.attached["keyboard"]; if (keyboard) keyboard.keysDown = value; } public get keysLeft() { var keyboard = this.inputs.attached["keyboard"]; if (keyboard) return keyboard.keysLeft; } public set keysLeft(value) { var keyboard = this.inputs.attached["keyboard"]; if (keyboard) keyboard.keysLeft = value; } public get keysRight() { var keyboard = this.inputs.attached["keyboard"]; if (keyboard) return keyboard.keysRight; } public set keysRight(value) { var keyboard = this.inputs.attached["keyboard"]; if (keyboard) keyboard.keysRight = value; } public get wheelPrecision() { var mousewheel = this.inputs.attached["mousewheel"]; if (mousewheel) return mousewheel.wheelPrecision; } public set wheelPrecision(value) { var mousewheel = this.inputs.attached["mousewheel"]; if (mousewheel) mousewheel.wheelPrecision = value; } //-- end properties for backward compatibility for inputs @serialize() public zoomOnFactor = 1; public targetScreenOffset = Vector2.Zero(); @serialize() public allowUpsideDown = true; public _viewMatrix = new Matrix(); public _useCtrlForPanning: boolean; public _panningMouseButton: number; public inputs: ArcRotateCameraInputsManager; public _reset: () => void; // Panning public panningAxis: Vector3 = new Vector3(1, 1, 0); private _localDirection: Vector3; private _transformedDirection: Vector3; // Collisions public onCollide: (collidedMesh: AbstractMesh) => void; public checkCollisions = false; public collisionRadius = new Vector3(0.5, 0.5, 0.5); private _collider = new Collider(); private _previousPosition = Vector3.Zero(); private _collisionVelocity = Vector3.Zero(); private _newPosition = Vector3.Zero(); private _previousAlpha: number; private _previousBeta: number; private _previousRadius: number; //due to async collision inspection private _collisionTriggered: boolean; private _targetBoundingCenter: Vector3; constructor(name: string, alpha: number, beta: number, radius: number, target: Vector3, scene: Scene) { super(name, Vector3.Zero(), scene); if (!target) { this.target = Vector3.Zero(); } else { this.target = target; } this.alpha = alpha; this.beta = beta; this.radius = radius; this.getViewMatrix(); this.inputs = new ArcRotateCameraInputsManager(this); this.inputs.addKeyboard().addMouseWheel().addPointers().addGamepad(); } // Cache public _initCache(): void { super._initCache(); this._cache.target = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE); this._cache.alpha = undefined; this._cache.beta = undefined; this._cache.radius = undefined; this._cache.targetScreenOffset = Vector2.Zero(); } public _updateCache(ignoreParentClass?: boolean): void { if (!ignoreParentClass) { super._updateCache(); } this._cache.target.copyFrom(this._getTargetPosition()); this._cache.alpha = this.alpha; this._cache.beta = this.beta; this._cache.radius = this.radius; this._cache.targetScreenOffset.copyFrom(this.targetScreenOffset); } private _getTargetPosition(): Vector3 { if ((this.target).getAbsolutePosition) { var pos : Vector3 = (this.target).getAbsolutePosition(); return this._targetBoundingCenter ? pos.add(this._targetBoundingCenter) : pos; } return this.target; } // Synchronized public _isSynchronizedViewMatrix(): boolean { if (!super._isSynchronizedViewMatrix()) return false; return this._cache.target.equals(this.target) && this._cache.alpha === this.alpha && this._cache.beta === this.beta && this._cache.radius === this.radius && this._cache.targetScreenOffset.equals(this.targetScreenOffset); } // Methods public attachControl(element: HTMLElement, noPreventDefault?: boolean, useCtrlForPanning: boolean = true, panningMouseButton: number = 2): void { this._useCtrlForPanning = useCtrlForPanning; this._panningMouseButton = panningMouseButton; this.inputs.attachElement(element, noPreventDefault); this._reset = () => { this.inertialAlphaOffset = 0; this.inertialBetaOffset = 0; this.inertialRadiusOffset = 0; }; } public detachControl(element: HTMLElement): void { this.inputs.detachElement(element); if (this._reset) { this._reset(); } } public _checkInputs(): void { //if (async) collision inspection was triggered, don't update the camera's position - until the collision callback was called. if (this._collisionTriggered) { return; } this.inputs.checkInputs(); // Inertia if (this.inertialAlphaOffset !== 0 || this.inertialBetaOffset !== 0 || this.inertialRadiusOffset !== 0) { if (this.getScene().useRightHandedSystem) { this.alpha -= this.beta <= 0 ? -this.inertialAlphaOffset : this.inertialAlphaOffset; } else { this.alpha += this.beta <= 0 ? -this.inertialAlphaOffset : this.inertialAlphaOffset; } this.beta += this.inertialBetaOffset; this.radius -= this.inertialRadiusOffset; this.inertialAlphaOffset *= this.inertia; this.inertialBetaOffset *= this.inertia; this.inertialRadiusOffset *= this.inertia; if (Math.abs(this.inertialAlphaOffset) < Epsilon) this.inertialAlphaOffset = 0; if (Math.abs(this.inertialBetaOffset) < Epsilon) this.inertialBetaOffset = 0; if (Math.abs(this.inertialRadiusOffset) < Epsilon) this.inertialRadiusOffset = 0; } // Panning inertia if (this.inertialPanningX !== 0 || this.inertialPanningY !== 0) { if (!this._localDirection) { this._localDirection = Vector3.Zero(); this._transformedDirection = Vector3.Zero(); } this.inertialPanningX *= this.inertia; this.inertialPanningY *= this.inertia; if (Math.abs(this.inertialPanningX) < Epsilon) this.inertialPanningX = 0; if (Math.abs(this.inertialPanningY) < Epsilon) this.inertialPanningY = 0; this._localDirection.copyFromFloats(this.inertialPanningX, this.inertialPanningY, this.inertialPanningY); this._localDirection.multiplyInPlace(this.panningAxis); this._viewMatrix.invertToRef(this._cameraTransformMatrix); Vector3.TransformNormalToRef(this._localDirection, this._cameraTransformMatrix, this._transformedDirection); //Eliminate y if map panning is enabled (panningAxis == 1,0,1) if (!this.panningAxis.y) { this._transformedDirection.y = 0; } if (!(this.target).getAbsolutePosition) { this.target.addInPlace(this._transformedDirection); } } // Limits this._checkLimits(); super._checkInputs(); } private _checkLimits() { if (this.lowerBetaLimit === null || this.lowerBetaLimit === undefined) { if (this.allowUpsideDown && this.beta > Math.PI) { this.beta = this.beta - (2 * Math.PI); } } else { if (this.beta < this.lowerBetaLimit) { this.beta = this.lowerBetaLimit; } } if (this.upperBetaLimit === null || this.upperBetaLimit === undefined) { if (this.allowUpsideDown && this.beta < -Math.PI) { this.beta = this.beta + (2 * Math.PI); } } else { if (this.beta > this.upperBetaLimit) { this.beta = this.upperBetaLimit; } } if (this.lowerAlphaLimit && this.alpha < this.lowerAlphaLimit) { this.alpha = this.lowerAlphaLimit; } if (this.upperAlphaLimit && this.alpha > this.upperAlphaLimit) { this.alpha = this.upperAlphaLimit; } if (this.lowerRadiusLimit && this.radius < this.lowerRadiusLimit) { this.radius = this.lowerRadiusLimit; } if (this.upperRadiusLimit && this.radius > this.upperRadiusLimit) { this.radius = this.upperRadiusLimit; } } public rebuildAnglesAndRadius() { var radiusv3 = this.position.subtract(this._getTargetPosition()); this.radius = radiusv3.length(); // Alpha this.alpha = Math.acos(radiusv3.x / Math.sqrt(Math.pow(radiusv3.x, 2) + Math.pow(radiusv3.z, 2))); if (radiusv3.z < 0) { this.alpha = 2 * Math.PI - this.alpha; } // Beta this.beta = Math.acos(radiusv3.y / this.radius); this._checkLimits(); } public setPosition(position: Vector3): void { if (this.position.equals(position)) { return; } this.position.copyFrom(position); this.rebuildAnglesAndRadius(); } public setTarget(target: Vector3, toBoundingCenter = false, allowSamePosition = false): void { if (!allowSamePosition && this._getTargetPosition().equals(target)) { return; } if (toBoundingCenter && (target).getBoundingInfo){ this._targetBoundingCenter = (target).getBoundingInfo().boundingBox.center.clone(); }else{ this._targetBoundingCenter = null; } this.target = target; this.rebuildAnglesAndRadius(); } public _getViewMatrix(): Matrix { // Compute var cosa = Math.cos(this.alpha); var sina = Math.sin(this.alpha); var cosb = Math.cos(this.beta); var sinb = Math.sin(this.beta); if (sinb === 0) { sinb = 0.0001; } var target = this._getTargetPosition(); target.addToRef(new Vector3(this.radius * cosa * sinb, this.radius * cosb, this.radius * sina * sinb), this._newPosition); if (this.getScene().collisionsEnabled && this.checkCollisions) { this._collider.radius = this.collisionRadius; this._newPosition.subtractToRef(this.position, this._collisionVelocity); this._collisionTriggered = true; this.getScene().collisionCoordinator.getNewPosition(this.position, this._collisionVelocity, this._collider, 3, null, this._onCollisionPositionChange, this.uniqueId); } else { this.position.copyFrom(this._newPosition); var up = this.upVector; if (this.allowUpsideDown && sinb < 0) { up = up.clone(); up = up.negate(); } if (this.getScene().useRightHandedSystem) { Matrix.LookAtRHToRef(this.position, target, up, this._viewMatrix); } else { Matrix.LookAtLHToRef(this.position, target, up, this._viewMatrix); } this._viewMatrix.m[12] += this.targetScreenOffset.x; this._viewMatrix.m[13] += this.targetScreenOffset.y; } this._currentTarget = target; return this._viewMatrix; } private _onCollisionPositionChange = (collisionId: number, newPosition: Vector3, collidedMesh: AbstractMesh = null) => { if (this.getScene().workerCollisions && this.checkCollisions) { newPosition.multiplyInPlace(this._collider.radius); } if (!collidedMesh) { this._previousPosition.copyFrom(this.position); } else { this.setPosition(newPosition); if (this.onCollide) { this.onCollide(collidedMesh); } } // Recompute because of constraints var cosa = Math.cos(this.alpha); var sina = Math.sin(this.alpha); var cosb = Math.cos(this.beta); var sinb = Math.sin(this.beta); if (sinb === 0) { sinb = 0.0001; } var target = this._getTargetPosition(); target.addToRef(new Vector3(this.radius * cosa * sinb, this.radius * cosb, this.radius * sina * sinb), this._newPosition); this.position.copyFrom(this._newPosition); var up = this.upVector; if (this.allowUpsideDown && this.beta < 0) { up = up.clone(); up = up.negate(); } Matrix.LookAtLHToRef(this.position, target, up, this._viewMatrix); this._viewMatrix.m[12] += this.targetScreenOffset.x; this._viewMatrix.m[13] += this.targetScreenOffset.y; this._collisionTriggered = false; } public zoomOn(meshes?: AbstractMesh[], doNotUpdateMaxZ = false): void { meshes = meshes || this.getScene().meshes; var minMaxVector = Mesh.MinMax(meshes); var distance = Vector3.Distance(minMaxVector.min, minMaxVector.max); this.radius = distance * this.zoomOnFactor; this.focusOn({ min: minMaxVector.min, max: minMaxVector.max, distance: distance }, doNotUpdateMaxZ); } public focusOn(meshesOrMinMaxVectorAndDistance, doNotUpdateMaxZ = false): void { var meshesOrMinMaxVector; var distance; if (meshesOrMinMaxVectorAndDistance.min === undefined) { // meshes meshesOrMinMaxVector = meshesOrMinMaxVectorAndDistance || this.getScene().meshes; meshesOrMinMaxVector = Mesh.MinMax(meshesOrMinMaxVector); distance = Vector3.Distance(meshesOrMinMaxVector.min, meshesOrMinMaxVector.max); } else { //minMaxVector and distance meshesOrMinMaxVector = meshesOrMinMaxVectorAndDistance; distance = meshesOrMinMaxVectorAndDistance.distance; } this.target = Mesh.Center(meshesOrMinMaxVector); if (!doNotUpdateMaxZ) { this.maxZ = distance * 2; } } /** * @override * Override Camera.createRigCamera */ public createRigCamera(name: string, cameraIndex: number): Camera { var alphaShift : number; switch (this.cameraRigMode) { case Camera.RIG_MODE_STEREOSCOPIC_ANAGLYPH: case Camera.RIG_MODE_STEREOSCOPIC_SIDEBYSIDE_PARALLEL: case Camera.RIG_MODE_STEREOSCOPIC_OVERUNDER: case Camera.RIG_MODE_VR: alphaShift = this._cameraRigParams.stereoHalfAngle * (cameraIndex === 0 ? 1 : -1); break; case Camera.RIG_MODE_STEREOSCOPIC_SIDEBYSIDE_CROSSEYED: alphaShift = this._cameraRigParams.stereoHalfAngle * (cameraIndex === 0 ? -1 : 1); break; } var rigCam = new ArcRotateCamera(name, this.alpha + alphaShift, this.beta, this.radius, this.target, this.getScene()); rigCam._cameraRigParams = {}; return rigCam; } /** * @override * Override Camera._updateRigCameras */ public _updateRigCameras() { var camLeft = this._rigCameras[0]; var camRight = this._rigCameras[1]; camLeft.beta = camRight.beta = this.beta; camLeft.radius = camRight.radius = this.radius; switch (this.cameraRigMode) { case Camera.RIG_MODE_STEREOSCOPIC_ANAGLYPH: case Camera.RIG_MODE_STEREOSCOPIC_SIDEBYSIDE_PARALLEL: case Camera.RIG_MODE_STEREOSCOPIC_OVERUNDER: case Camera.RIG_MODE_VR: camLeft.alpha = this.alpha - this._cameraRigParams.stereoHalfAngle; camRight.alpha = this.alpha + this._cameraRigParams.stereoHalfAngle; break; case Camera.RIG_MODE_STEREOSCOPIC_SIDEBYSIDE_CROSSEYED: camLeft.alpha = this.alpha + this._cameraRigParams.stereoHalfAngle; camRight.alpha = this.alpha - this._cameraRigParams.stereoHalfAngle; break; } super._updateRigCameras(); } public dispose(): void { this.inputs.clear(); super.dispose(); } public getClassName(): string { return "ArcRotateCamera"; } } }