/*
* Copyright 2003-2006, 2009, 2017, 2018, 2019 United States Government, as represented by the Administrator of the
* National Aeronautics and Space Administration. All rights reserved.
*
* The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @exports FirstPersonCamera
*/
define([
'./geom/Angle',
'./error/ArgumentError',
'./geom/Line',
'./util/Logger',
'./geom/Matrix',
'./geom/Position',
'./geom/Vec3',
'./util/WWMath',
],
function (
Angle,
ArgumentError,
Line,
Logger,
Matrix,
Position,
Vec3,
WWMath) {
'use strict';
/**
* Constructs a first person camera.
* @alias FirstPersonCamera
* @constructor
* @classdesc Represents a first person camera. It is intended to be used close to the ground.
* @param {WorldWindow} wwd The WorldWindow instance.
* @throws {ArgumentError} If the WorldWindow instance is missing.
*/
var FirstPersonCamera = function (wwd) {
if (!wwd) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "FirstPersonCamera", "constructor",
"missing WorldWindow instance."));
}
/**
* The position of the camera
* @type {Position}
*/
this.position = new Position(30, -110, 1000);
/**
* The heading of the camera, between -180 to +180 degrees.
* It is used for looking left or right.
* @type {Number}
* @default 0
*/
this.heading = 0;
/**
* The tilt of the camera, between -90 to +90 degrees.
* It is for looking up or down.
* @type {Number}
* @default 0
*/
this.tilt = 0;
/**
* This camera's roll, in degrees.
* @type {Number}
* @default 0
*/
this.roll = 0;
/**
* The WorldWindow instance
* @type {WorldWindow}
*/
this.wwd = wwd;
//Internal variables for this class.
this._scratchMatrix = Matrix.fromIdentity();
this._scratchVector = new Vec3(0, 0, 0);
this._ray = new Line(new Vec3(0, 0, 0), new Vec3(0, 0, 0));
};
Object.defineProperties(FirstPersonCamera.prototype, {
/**
* The altitude of the camera.
* @memberof FirstPersonCamera.prototype
* @type {Number}
* @default 1000 meters
*/
range: {
get: function () {
return this.position.altitude;
},
set: function (value) {
this.position.altitude = value;
}
}
});
/**
* Creates a view matrix for this camera.
* @param {Matrix} matrix A matrix in which to set the view matrix.
* @returns {Matrix} The view matrix.
* @throws {ArgumentError} If the specified matrix is missing.
*/
FirstPersonCamera.prototype.createViewMatrix = function (matrix) {
if (!matrix) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "FirstPersonCamera", "createViewMatrix",
"missing matrix"));
}
this.applyLimits();
matrix.setToIdentity();
matrix.multiplyByLookAtModelview(this.position, 0, this.heading, this.tilt + 90, this.roll, this.wwd.globe);
return matrix;
};
/**
* Clones this camera to the specified camera.
* If a camera is not provied a new one will be created.
* @param {FirstPersonCamera | undefined} camera The camera to save the rsults in.
* @returns {FirstPersonCamera} A colne of this camera.
*/
FirstPersonCamera.prototype.clone = function (camera) {
camera = camera || new FirstPersonCamera(this.wwd);
camera.copy(this);
return camera;
};
/**
* Copies this camera to the specified camera.
* @param {FirstPersonCamera} camera The camera to copy.
* @returns {FirstPersonCamera} This camera, set to the values of the specified camera.
* @throws {ArgumentError} If the specified camera is missing.
*/
FirstPersonCamera.prototype.copy = function (camera) {
if (!camera) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "FirstPersonCamera", "copy",
"missing camera"));
}
this.position.copy(camera.position);
this.heading = camera.heading;
this.tilt = camera.tilt;
this.roll = camera.roll;
return this;
};
/**
* Enforces navigation limits for this camera:
* - a valid position: latitude -90 to 90 deg, longitude -180 to 180 deg, altitude, 0 to Number.MAX_VALUE
* - a valid heading angle: -180 to +180 deg
* - a valid tilt angle: -90 to 90 deg
* - a valid roll angle: -180 to +180 deg
*/
FirstPersonCamera.prototype.applyLimits = function () {
// Clamp latitude to between -90 and +90 and normalize longitude to between -180 and +180.
this.position.latitude = WWMath.clamp(this.position.latitude, -90, 90);
this.position.longitude = Angle.normalizedDegreesLongitude(this.position.longitude);
// Clamp altitude to values greater than 0 in order to prevent the camera from going undergound.
this.position.altitude = WWMath.clamp(this.position.altitude, 0, Number.MAX_VALUE);
// Clamp tilt to between 0 and +90 to prevent the viewer from going upside down.
this.tilt = WWMath.clamp(this.tilt, -90, 90);
// Normalize heading to between -180 and +180.
this.heading = Angle.normalizedDegrees(this.heading);
// Normalize roll to between -180 and +180.
this.roll = Angle.normalizedDegrees(this.roll);
};
/**
* Converts this camera to an ArcBallCamera.
* The resulting lookAt position is computed from the range of the specified ArcBallCamera.
* @param {ArcBallCamera} arcBallCamera An arcBallCamera instance in which to save the result of the transformation.
* @returns {ArcBallCamera} The specified arcBallCamera.
* @throws {ArgumentError} If the specified arcBallCamera is missing.
*/
FirstPersonCamera.prototype.toArcBall = function (arcBallCamera) {
if (!arcBallCamera) {
throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "FirstPersonCamera", "toArcBall",
"missing arcBallCamera"));
}
var viewMatrix = this.createViewMatrix(this._scratchMatrix);
viewMatrix.extractEyePoint(this._ray.origin);
viewMatrix.extractForwardVector(this._ray.direction);
var lookAtPoint = this._ray.pointAt(arcBallCamera.range, this._scratchVector);
var params = viewMatrix.extractViewingParameters(lookAtPoint, this.roll, this.wwd.globe, {});
arcBallCamera.position.copy(params.origin);
arcBallCamera.heading = params.heading;
arcBallCamera.tilt = params.tilt;
arcBallCamera.roll = params.roll;
arcBallCamera.applyLimits();
return arcBallCamera;
};
return FirstPersonCamera;
});