/**
* Copyright (c) 2008-2012 Ardor Labs, Inc.
*
* This file is part of Ardor3D.
*
* Ardor3D is free software: you can redistribute it and/or modify it
* under the terms of its license which may be found in the accompanying
* LICENSE file or at <http://www.ardor3d.com/LICENSE>.
*/
package com.ardor3d.renderer;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.nio.FloatBuffer;
import java.util.Arrays;
import java.util.logging.Logger;
import com.ardor3d.bounding.BoundingVolume;
import com.ardor3d.math.MathUtils;
import com.ardor3d.math.Matrix4;
import com.ardor3d.math.Plane;
import com.ardor3d.math.Ray3;
import com.ardor3d.math.Vector2;
import com.ardor3d.math.Vector3;
import com.ardor3d.math.Vector4;
import com.ardor3d.math.type.ReadOnlyMatrix3;
import com.ardor3d.math.type.ReadOnlyMatrix4;
import com.ardor3d.math.type.ReadOnlyVector2;
import com.ardor3d.math.type.ReadOnlyVector3;
import com.ardor3d.util.export.InputCapsule;
import com.ardor3d.util.export.OutputCapsule;
import com.ardor3d.util.export.Savable;
import com.ardor3d.util.geom.BufferUtils;
/**
* This class represents a view into a 3d scene and how that view should map to a 2D rendering surface.
*/
public class Camera implements Savable, Externalizable {
private static final long serialVersionUID = 1L;
private static final Logger _logger = Logger.getLogger(Camera.class.getName());
public enum FrustumIntersect {
/**
* Object being compared to the frustum is completely outside of the frustum.
*/
Outside,
/**
* Object being compared to the frustum is completely inside of the frustum.
*/
Inside,
/**
* Object being compared to the frustum intersects one of the frustum planes and is thus both inside and outside
* of the frustum.
*/
Intersects;
}
// planes of the frustum
/**
* LEFT_PLANE represents the left plane of the camera frustum.
*/
public static final int LEFT_PLANE = 0;
/**
* RIGHT_PLANE represents the right plane of the camera frustum.
*/
public static final int RIGHT_PLANE = 1;
/**
* BOTTOM_PLANE represents the bottom plane of the camera frustum.
*/
public static final int BOTTOM_PLANE = 2;
/**
* TOP_PLANE represents the top plane of the camera frustum.
*/
public static final int TOP_PLANE = 3;
/**
* FAR_PLANE represents the far plane of the camera frustum.
*/
public static final int FAR_PLANE = 4;
/**
* NEAR_PLANE represents the near plane of the camera frustum.
*/
public static final int NEAR_PLANE = 5;
/**
* FRUSTUM_PLANES represents the number of planes of the camera frustum.
*/
public static final int FRUSTUM_PLANES = 6;
/**
* MAX_WORLD_PLANES holds the maximum planes allowed by the system.
*/
public static final int MAX_WORLD_PLANES = 32;
// the location and orientation of the camera.
/**
* Camera's location
*/
protected final Vector3 _location = new Vector3();
/**
* Direction of camera's 'left'
*/
protected final Vector3 _left = new Vector3();
/**
* Direction of 'up' for camera.
*/
protected final Vector3 _up = new Vector3();
/**
* Direction the camera is facing.
*/
protected final Vector3 _direction = new Vector3();
/**
* The near range for mapping depth values from normalized device coordinates to window coordinates.
*/
protected double _depthRangeNear;
/**
* The far range for mapping depth values from normalized device coordinates to window coordinates.
*/
protected double _depthRangeFar;
/**
* Distance from camera to near frustum plane.
*/
protected double _frustumNear;
/**
* Distance from camera to far frustum plane.
*/
protected double _frustumFar;
/**
* Distance from camera to left frustum plane.
*/
protected double _frustumLeft;
/**
* Distance from camera to right frustum plane.
*/
protected double _frustumRight;
/**
* Distance from camera to top frustum plane.
*/
protected double _frustumTop;
/**
* Distance from camera to bottom frustum plane.
*/
protected double _frustumBottom;
/**
* Convenience store for fovY. Only set during setFrustumPerspective and never used. Retrieve by getFovY(). Default
* is NaN.
*/
protected double _fovY = Double.NaN;
// Temporary values computed in onFrustumChange that are needed if a
// call is made to onFrameChange.
protected double _coeffLeft[];
protected double _coeffRight[];
protected double _coeffBottom[];
protected double _coeffTop[];
/** Number of camera planes used by this camera. Default is 6. */
protected int _planeQuantity;
// view port coordinates
/**
* Percent value on display where horizontal viewing starts for this camera. Default is 0.
*/
protected double _viewPortLeft;
/**
* Percent value on display where horizontal viewing ends for this camera. Default is 1.
*/
protected double _viewPortRight;
/**
* Percent value on display where vertical viewing ends for this camera. Default is 1.
*/
protected double _viewPortTop;
/**
* Percent value on display where vertical viewing begins for this camera. Default is 0.
*/
protected double _viewPortBottom;
/**
* Array holding the planes that this camera will check for culling.
*/
protected Plane[] _worldPlane;
protected final FloatBuffer _matrixBuffer = BufferUtils.createFloatBuffer(16);
/**
* Computation vector used in various operations.
*/
protected final Vector3 _tempVector = new Vector3();
/**
* Projection mode used by the camera.
*/
public enum ProjectionMode {
Perspective, Parallel, Custom
}
/**
* Current projection mode.
*/
private ProjectionMode _projectionMode = ProjectionMode.Perspective;
private boolean _updateMVMatrix = true;
private boolean _updatePMatrix = true;
private boolean _updateMVPMatrix = true;
private boolean _updateInverseMVPMatrix = true;
// NB: These matrices are column-major.
protected final Matrix4 _modelView = new Matrix4();
protected final Matrix4 _projection = new Matrix4();
private final Matrix4 _modelViewProjection = new Matrix4();
private final Matrix4 _modelViewProjectionInverse = new Matrix4();
protected boolean _depthRangeDirty;
protected boolean _frustumDirty;
protected boolean _viewPortDirty;
protected boolean _frameDirty;
/**
* A mask value set during contains() that allows fast culling of a Node's children.
*/
private int _planeState;
protected int _width;
protected int _height;
/**
* Construct a new Camera with a width and height of 100.
*/
public Camera() {
this(100, 100);
}
/**
* Construct a new Camera with the given frame width and height.
*
* @param width
* @param height
*/
public Camera(final int width, final int height) {
_width = width;
_height = height;
_location.set(0, 0, 0);
_left.set(-1, 0, 0);
_up.set(0, 1, 0);
_direction.set(0, 0, -1);
_depthRangeNear = 0.0;
_depthRangeFar = 1.0;
_depthRangeDirty = true;
_frustumNear = 1.0;
_frustumFar = 2.0;
_frustumLeft = -0.5;
_frustumRight = 0.5;
_frustumTop = 0.5;
_frustumBottom = -0.5;
_coeffLeft = new double[2];
_coeffRight = new double[2];
_coeffBottom = new double[2];
_coeffTop = new double[2];
_viewPortLeft = 0.0;
_viewPortRight = 1.0;
_viewPortTop = 1.0;
_viewPortBottom = 0.0;
_planeQuantity = 6;
_worldPlane = new Plane[MAX_WORLD_PLANES];
for (int i = 0; i < MAX_WORLD_PLANES; i++) {
_worldPlane[i] = new Plane();
}
onFrustumChange();
onViewPortChange();
onFrameChange();
_logger.fine("Camera created. W: " + width + " H: " + height);
}
/**
* Construct a new camera, using the given source camera's values.
*
* @param source
*/
public Camera(final Camera source) {
_coeffLeft = new double[2];
_coeffRight = new double[2];
_coeffBottom = new double[2];
_coeffTop = new double[2];
_worldPlane = new Plane[MAX_WORLD_PLANES];
for (int i = 0; i < MAX_WORLD_PLANES; i++) {
_worldPlane[i] = new Plane();
}
set(source);
_logger.fine("Camera created. W: " + getWidth() + " H: " + getHeight());
}
/**
* Copy the source camera's fields to this camera
*
* @param source
* the camera to copy from
*/
public void set(final Camera source) {
_width = source.getWidth();
_height = source.getHeight();
_location.set(source.getLocation());
_left.set(source.getLeft());
_up.set(source.getUp());
_direction.set(source.getDirection());
_fovY = source.getFovY();
_depthRangeNear = source.getDepthRangeNear();
_depthRangeFar = source.getDepthRangeFar();
_depthRangeDirty = true;
_frustumNear = source.getFrustumNear();
_frustumFar = source.getFrustumFar();
_frustumLeft = source.getFrustumLeft();
_frustumRight = source.getFrustumRight();
_frustumTop = source.getFrustumTop();
_frustumBottom = source.getFrustumBottom();
_viewPortLeft = source.getViewPortLeft();
_viewPortRight = source.getViewPortRight();
_viewPortTop = source.getViewPortTop();
_viewPortBottom = source.getViewPortBottom();
_planeQuantity = 6;
_projectionMode = source.getProjectionMode();
onFrustumChange();
onViewPortChange();
onFrameChange();
}
public double getDepthRangeFar() {
return _depthRangeFar;
}
/**
* @param depthRangeNear
* the far clipping plane for window coordinates. Should be in the range [0, 1]. Default is 1.
*/
public void setDepthRangeFar(final double depthRangeFar) {
_depthRangeFar = depthRangeFar;
_depthRangeDirty = true;
}
public double getDepthRangeNear() {
return _depthRangeNear;
}
/**
* @param depthRangeNear
* the near clipping plane for window coordinates. Should be in the range [0, 1]. Default is 0.
*/
public void setDepthRangeNear(final double depthRangeNear) {
_depthRangeNear = depthRangeNear;
_depthRangeDirty = true;
}
/**
* @return the value of the bottom frustum plane.
*/
public double getFrustumBottom() {
return _frustumBottom;
}
/**
* @param frustumBottom
* the new value of the bottom frustum plane.
*/
public void setFrustumBottom(final double frustumBottom) {
_frustumBottom = frustumBottom;
onFrustumChange();
}
/**
* = * @return the value of the far frustum plane.
*/
public double getFrustumFar() {
return _frustumFar;
}
/**
* @param frustumFar
* the new value of the far frustum plane.
*/
public void setFrustumFar(final double frustumFar) {
_frustumFar = frustumFar;
onFrustumChange();
}
/**
* @return the value of the left frustum plane.
*/
public double getFrustumLeft() {
return _frustumLeft;
}
/**
* @param frustumLeft
* the new value of the left frustum plane.
*/
public void setFrustumLeft(final double frustumLeft) {
_frustumLeft = frustumLeft;
onFrustumChange();
}
/**
* @return the value of the near frustum plane.
*/
public double getFrustumNear() {
return _frustumNear;
}
/**
* @param frustumNear
* the new value of the near frustum plane.
*/
public void setFrustumNear(final double frustumNear) {
_frustumNear = frustumNear;
onFrustumChange();
}
/**
* @return frustumRight the value of the right frustum plane.
*/
public double getFrustumRight() {
return _frustumRight;
}
/**
* @param frustumRight
* the new value of the right frustum plane.
*/
public void setFrustumRight(final double frustumRight) {
_frustumRight = frustumRight;
onFrustumChange();
}
/**
* @return the value of the top frustum plane.
*/
public double getFrustumTop() {
return _frustumTop;
}
/**
* @param frustumTop
* the new value of the top frustum plane.
*/
public void setFrustumTop(final double frustumTop) {
_frustumTop = frustumTop;
onFrustumChange();
}
/**
* @return the current position of the camera.
*/
public ReadOnlyVector3 getLocation() {
return _location;
}
/**
* @return the current direction the camera is facing.
*/
public ReadOnlyVector3 getDirection() {
return _direction;
}
/**
* @return the left axis of the camera.
*/
public ReadOnlyVector3 getLeft() {
return _left;
}
/**
* @return the up axis of the camera.
*/
public ReadOnlyVector3 getUp() {
return _up;
}
/**
* @param location
* the new location or position of the camera.
*/
public void setLocation(final ReadOnlyVector3 location) {
_location.set(location);
onFrameChange();
}
/**
* @param location
* the new location or position of the camera.
*/
public void setLocation(final double x, final double y, final double z) {
_location.set(x, y, z);
onFrameChange();
}
/**
* Sets the new direction this camera is facing. This does not change left or up axes, so make sure those vectors
* are properly set as well.
*
* @param direction
* the new direction this camera is facing.
*/
public void setDirection(final ReadOnlyVector3 direction) {
_direction.set(direction);
onFrameChange();
}
/**
* Sets the new left axis of this camera. This does not change direction or up axis, so make sure those vectors are
* properly set as well.
*
* @param left
* the new left axis of this camera.
*/
public void setLeft(final ReadOnlyVector3 left) {
_left.set(left);
onFrameChange();
}
/**
* Sets the new up axis of this camera. This does not change direction or left axis, so make sure those vectors are
* properly set as well.
*
* @param up
* the new up axis of this camera.
*/
public void setUp(final ReadOnlyVector3 up) {
_up.set(up);
onFrameChange();
}
/**
* @param left
* the new left axis of the camera.
* @param up
* the new up axis of the camera.
* @param direction
* the new direction the camera is facing.
*/
public void setAxes(final ReadOnlyVector3 left, final ReadOnlyVector3 up, final ReadOnlyVector3 direction) {
_left.set(left);
_up.set(up);
_direction.set(direction);
onFrameChange();
}
/**
* Sets our left, up and direction values from the given rotation matrix.
*
* @param axes
* the matrix that defines the orientation of the camera.
*/
public void setAxes(final ReadOnlyMatrix3 axes) {
axes.getColumn(0, _left);
axes.getColumn(1, _up);
axes.getColumn(2, _direction);
onFrameChange();
}
/**
* Ensure our up, left and direction are unit-length vectors.
*/
public void normalize() {
_left.normalizeLocal();
_up.normalizeLocal();
_direction.normalizeLocal();
onFrameChange();
}
/**
* Sets the frustum plane values of this camera using the given values.
*
* @param near
* @param far
* @param left
* @param right
* @param top
* @param bottom
*/
public void setFrustum(final double near, final double far, final double left, final double right,
final double top, final double bottom) {
_frustumNear = near;
_frustumFar = far;
_frustumLeft = left;
_frustumRight = right;
_frustumTop = top;
_frustumBottom = bottom;
onFrustumChange();
}
/**
* Sets the frustum plane values of this camera using those of a given source camera
*
* @param source
* a source camera.
*/
public void setFrustum(final Camera source) {
_frustumNear = source.getFrustumNear();
_frustumFar = source.getFrustumFar();
_frustumLeft = source.getFrustumLeft();
_frustumRight = source.getFrustumRight();
_frustumTop = source.getFrustumTop();
_frustumBottom = source.getFrustumBottom();
onFrustumChange();
}
/**
* Sets the frustum plane values of this camera using the given perspective values.
*
* @param fovY
* the full angle of view on the Y axis, in degrees.
* @param aspect
* the aspect ratio of our view (generally in [0,1]). Often this is canvas width / canvas height.
* @param near
* our near plane value
* @param far
* our far plane value
*/
public void setFrustumPerspective(final double fovY, final double aspect, final double near, final double far) {
if (Double.isNaN(aspect) || Double.isInfinite(aspect)) {
// ignore.
_logger.warning("Invalid aspect given to setFrustumPerspective: " + aspect);
return;
}
_fovY = fovY;
final double h = Math.tan(_fovY * MathUtils.DEG_TO_RAD * .5) * near;
final double w = h * aspect;
_frustumLeft = -w;
_frustumRight = w;
_frustumBottom = -h;
_frustumTop = h;
_frustumNear = near;
_frustumFar = far;
onFrustumChange();
}
/**
* Accessor for the fovY value. Note that this value is only present if setFrustumPerspective was previously called.
*
* @return the fovY value
*/
public double getFovY() {
return _fovY;
}
/**
* Sets the axes and location of the camera. Similar to
* {@link #setAxes(ReadOnlyVector3, ReadOnlyVector3, ReadOnlyVector3)}, but sets camera location as well.
*
* @param location
* @param left
* @param up
* @param direction
*/
public void setFrame(final ReadOnlyVector3 location, final ReadOnlyVector3 left, final ReadOnlyVector3 up,
final ReadOnlyVector3 direction) {
_left.set(left);
_up.set(up);
_direction.set(direction);
_location.set(location);
onFrameChange();
}
/**
* Sets the axes and location of the camera. Similar to {@link #setAxes(ReadOnlyMatrix3)}, but sets camera location
* as well.
*
* @param location
* the point position of the camera.
* @param axes
* the orientation of the camera.
*/
public void setFrame(final ReadOnlyVector3 location, final ReadOnlyMatrix3 axes) {
axes.getColumn(0, _left);
axes.getColumn(1, _up);
axes.getColumn(2, _direction);
_location.set(location);
onFrameChange();
}
/**
* Sets the axes and location of the camera using those of a given source camera
*
* @param source
* a source camera.
*/
public void setFrame(final Camera source) {
_left.set(source.getLeft());
_up.set(source.getUp());
_direction.set(source.getDirection());
_location.set(source.getLocation());
onFrameChange();
}
/**
* A convenience method for auto-setting the frame based on a world position the user desires the camera to look at.
* It points the camera towards the given position using the difference between that position and the current camera
* location as a direction vector and the general worldUpVector to compute up and left camera vectors.
*
* @param pos
* where to look at in terms of world coordinates
* @param worldUpVector
* a normalized vector indicating the up direction of the world. (often {@link Vector3#UNIT_Y} or
* {@link Vector3#UNIT_Z})
*/
public void lookAt(final ReadOnlyVector3 pos, final ReadOnlyVector3 worldUpVector) {
lookAt(pos.getX(), pos.getY(), pos.getZ(), worldUpVector);
}
/**
* A convenience method for auto-setting the frame based on a world position the user desires the camera to look at.
* It points the camera towards the given position using the difference between that position and the current camera
* location as a direction vector and the general worldUpVector to compute up and left camera vectors.
*
* @param x
* where to look at in terms of world coordinates (x)
* @param y
* where to look at in terms of world coordinates (y)
* @param z
* where to look at in terms of world coordinates (z)
* @param worldUpVector
* a normalized vector indicating the up direction of the world. (often {@link Vector3#UNIT_Y} or
* {@link Vector3#UNIT_Z})
*/
public void lookAt(final double x, final double y, final double z, final ReadOnlyVector3 worldUpVector) {
final Vector3 newDirection = _tempVector;
newDirection.set(x, y, z).subtractLocal(_location).normalizeLocal();
// check to see if we haven't really updated camera -- no need to call sets.
if (newDirection.equals(_direction)) {
return;
}
_direction.set(newDirection);
_up.set(worldUpVector).normalizeLocal();
if (_up.equals(Vector3.ZERO)) {
_up.set(Vector3.UNIT_Y);
}
_left.set(_up).crossLocal(_direction).normalizeLocal();
if (_left.equals(Vector3.ZERO)) {
if (_direction.getX() != 0.0) {
_left.set(_direction.getY(), -_direction.getX(), 0);
} else {
_left.set(0, _direction.getZ(), -_direction.getY());
}
}
_up.set(_direction).crossLocal(_left).normalizeLocal();
onFrameChange();
}
/**
* Forces all aspect of the camera to be updated from internal values, and sets all dirty flags to true so that the
* next apply() call will fully set this camera to the render context.
*/
public void update() {
_depthRangeDirty = true;
onFrustumChange();
onViewPortChange();
onFrameChange();
}
/**
* @return an internally used bitmask describing what frustum planes have been examined for culling thus far.
*/
public int getPlaneState() {
return _planeState;
}
/**
* @param planeState
* a new value for planeState.
* @see #getPlaneState()
*/
public void setPlaneState(final int planeState) {
_planeState = planeState;
}
/**
* @return the left boundary of the viewport
*/
public double getViewPortLeft() {
return _viewPortLeft;
}
/**
* @param left
* the new left boundary of the viewport
*/
public void setViewPortLeft(final double left) {
_viewPortLeft = left;
onViewPortChange();
}
/**
* @return the right boundary of the viewport
*/
public double getViewPortRight() {
return _viewPortRight;
}
/**
* @param right
* the new right boundary of the viewport
*/
public void setViewPortRight(final double right) {
_viewPortRight = right;
onViewPortChange();
}
/**
* @return the top boundary of the viewport
*/
public double getViewPortTop() {
return _viewPortTop;
}
/**
* @param top
* the new top boundary of the viewport
*/
public void setViewPortTop(final double top) {
_viewPortTop = top;
onViewPortChange();
}
/**
* @return the bottom boundary of the viewport
*/
public double getViewPortBottom() {
return _viewPortBottom;
}
/**
* @param bottom
* the new bottom boundary of the viewport
*/
public void setViewPortBottom(final double bottom) {
_viewPortBottom = bottom;
onViewPortChange();
}
/**
* Sets the boundaries of this camera's viewport to the given values
*
* @param left
* @param right
* @param bottom
* @param top
*/
public void setViewPort(final double left, final double right, final double bottom, final double top) {
setViewPortLeft(left);
setViewPortRight(right);
setViewPortBottom(bottom);
setViewPortTop(top);
}
/**
* Checks a bounding volume against the planes of this camera's frustum and returns if it is completely inside of,
* outside of, or intersecting.
*
* @param bound
* the bound to check for culling
* @return intersection type
*/
public Camera.FrustumIntersect contains(final BoundingVolume bound) {
if (bound == null) {
return FrustumIntersect.Inside;
}
int mask;
FrustumIntersect rVal = FrustumIntersect.Inside;
for (int planeCounter = FRUSTUM_PLANES; planeCounter >= 0; planeCounter--) {
if (planeCounter == bound.getCheckPlane()) {
continue; // we have already checked this plane at first iteration
}
final int planeId = (planeCounter == FRUSTUM_PLANES) ? bound.getCheckPlane() : planeCounter;
mask = 1 << (planeId);
if ((_planeState & mask) == 0) {
switch (bound.whichSide(_worldPlane[planeId])) {
case Inside:
// object is outside of frustum
bound.setCheckPlane(planeId);
return FrustumIntersect.Outside;
case Outside:
// object is visible on *this* plane, so mark this plane
// so that we don't check it for sub nodes.
_planeState |= mask;
break;
case Neither:
rVal = FrustumIntersect.Intersects;
break;
}
}
}
return rVal;
}
/**
* Resizes this camera's view with the given width and height.
*
* @param width
* the view width
* @param height
* the view height
*/
public void resize(final int width, final int height) {
_width = width;
_height = height;
onViewPortChange();
}
/**
* Updates internal frustum coefficient values to reflect the current frustum plane values.
*/
public void onFrustumChange() {
if (getProjectionMode() == ProjectionMode.Perspective) {
final double nearSquared = _frustumNear * _frustumNear;
final double leftSquared = _frustumLeft * _frustumLeft;
final double rightSquared = _frustumRight * _frustumRight;
final double bottomSquared = _frustumBottom * _frustumBottom;
final double topSquared = _frustumTop * _frustumTop;
double inverseLength = 1.0 / Math.sqrt(nearSquared + leftSquared);
_coeffLeft[0] = _frustumNear * inverseLength;
_coeffLeft[1] = -_frustumLeft * inverseLength;
inverseLength = 1.0 / Math.sqrt(nearSquared + rightSquared);
_coeffRight[0] = -_frustumNear * inverseLength;
_coeffRight[1] = _frustumRight * inverseLength;
inverseLength = 1.0 / Math.sqrt(nearSquared + bottomSquared);
_coeffBottom[0] = _frustumNear * inverseLength;
_coeffBottom[1] = -_frustumBottom * inverseLength;
inverseLength = 1.0 / Math.sqrt(nearSquared + topSquared);
_coeffTop[0] = -_frustumNear * inverseLength;
_coeffTop[1] = _frustumTop * inverseLength;
} else if (getProjectionMode() == ProjectionMode.Parallel) {
if (_frustumRight > _frustumLeft) {
_coeffLeft[0] = -1;
_coeffLeft[1] = 0;
_coeffRight[0] = 1;
_coeffRight[1] = 0;
} else {
_coeffLeft[0] = 1;
_coeffLeft[1] = 0;
_coeffRight[0] = -1;
_coeffRight[1] = 0;
}
if (_frustumTop > _frustumBottom) {
_coeffBottom[0] = -1;
_coeffBottom[1] = 0;
_coeffTop[0] = 1;
_coeffTop[1] = 0;
} else {
_coeffBottom[0] = 1;
_coeffBottom[1] = 0;
_coeffTop[0] = -1;
_coeffTop[1] = 0;
}
}
_updatePMatrix = true;
_updateMVPMatrix = true;
_updateInverseMVPMatrix = true;
_frustumDirty = true;
}
/**
* Updates the values of the world planes associated with this camera.
*/
public void onFrameChange() {
final double dirDotLocation = _direction.dot(_location);
final Vector3 planeNormal = Vector3.fetchTempInstance();
// left plane
planeNormal.setX(_left.getX() * _coeffLeft[0]);
planeNormal.setY(_left.getY() * _coeffLeft[0]);
planeNormal.setZ(_left.getZ() * _coeffLeft[0]);
planeNormal.addLocal(_direction.getX() * _coeffLeft[1], _direction.getY() * _coeffLeft[1], _direction.getZ()
* _coeffLeft[1]);
_worldPlane[LEFT_PLANE].setNormal(planeNormal);
_worldPlane[LEFT_PLANE].setConstant(_location.dot(planeNormal));
// right plane
planeNormal.setX(_left.getX() * _coeffRight[0]);
planeNormal.setY(_left.getY() * _coeffRight[0]);
planeNormal.setZ(_left.getZ() * _coeffRight[0]);
planeNormal.addLocal(_direction.getX() * _coeffRight[1], _direction.getY() * _coeffRight[1], _direction.getZ()
* _coeffRight[1]);
_worldPlane[RIGHT_PLANE].setNormal(planeNormal);
_worldPlane[RIGHT_PLANE].setConstant(_location.dot(planeNormal));
// bottom plane
planeNormal.setX(_up.getX() * _coeffBottom[0]);
planeNormal.setY(_up.getY() * _coeffBottom[0]);
planeNormal.setZ(_up.getZ() * _coeffBottom[0]);
planeNormal.addLocal(_direction.getX() * _coeffBottom[1], _direction.getY() * _coeffBottom[1],
_direction.getZ() * _coeffBottom[1]);
_worldPlane[BOTTOM_PLANE].setNormal(planeNormal);
_worldPlane[BOTTOM_PLANE].setConstant(_location.dot(planeNormal));
// top plane
planeNormal.setX(_up.getX() * _coeffTop[0]);
planeNormal.setY(_up.getY() * _coeffTop[0]);
planeNormal.setZ(_up.getZ() * _coeffTop[0]);
planeNormal.addLocal(_direction.getX() * _coeffTop[1], _direction.getY() * _coeffTop[1], _direction.getZ()
* _coeffTop[1]);
_worldPlane[TOP_PLANE].setNormal(planeNormal);
_worldPlane[TOP_PLANE].setConstant(_location.dot(planeNormal));
if (getProjectionMode() == ProjectionMode.Parallel) {
if (_frustumRight > _frustumLeft) {
_worldPlane[LEFT_PLANE].setConstant(_worldPlane[LEFT_PLANE].getConstant() + _frustumLeft);
_worldPlane[RIGHT_PLANE].setConstant(_worldPlane[RIGHT_PLANE].getConstant() - _frustumRight);
} else {
_worldPlane[LEFT_PLANE].setConstant(_worldPlane[LEFT_PLANE].getConstant() - _frustumLeft);
_worldPlane[RIGHT_PLANE].setConstant(_worldPlane[RIGHT_PLANE].getConstant() + _frustumRight);
}
if (_frustumBottom > _frustumTop) {
_worldPlane[TOP_PLANE].setConstant(_worldPlane[TOP_PLANE].getConstant() + _frustumTop);
_worldPlane[BOTTOM_PLANE].setConstant(_worldPlane[BOTTOM_PLANE].getConstant() - _frustumBottom);
} else {
_worldPlane[TOP_PLANE].setConstant(_worldPlane[TOP_PLANE].getConstant() - _frustumTop);
_worldPlane[BOTTOM_PLANE].setConstant(_worldPlane[BOTTOM_PLANE].getConstant() + _frustumBottom);
}
}
// far plane
planeNormal.set(_direction).negateLocal();
_worldPlane[FAR_PLANE].setNormal(planeNormal);
_worldPlane[FAR_PLANE].setConstant(-(dirDotLocation + _frustumFar));
// near plane
_worldPlane[NEAR_PLANE].setNormal(_direction);
_worldPlane[NEAR_PLANE].setConstant(dirDotLocation + _frustumNear);
Vector3.releaseTempInstance(planeNormal);
_updateMVMatrix = true;
_updateMVPMatrix = true;
_updateInverseMVPMatrix = true;
_frameDirty = true;
}
/**
* Updates the value of our projection matrix.
*/
protected void updateProjectionMatrix() {
if (getProjectionMode() == ProjectionMode.Parallel) {
_projection.setIdentity();
_projection.setM00(2.0 / (_frustumRight - _frustumLeft));
_projection.setM11(2.0 / (_frustumTop - _frustumBottom));
_projection.setM22(-2.0 / (_frustumFar - _frustumNear));
_projection.setM30(-(_frustumRight + _frustumLeft) / (_frustumRight - _frustumLeft));
_projection.setM31(-(_frustumTop + _frustumBottom) / (_frustumTop - _frustumBottom));
_projection.setM32(-(_frustumFar + _frustumNear) / (_frustumFar - _frustumNear));
} else if (getProjectionMode() == ProjectionMode.Perspective) {
_projection.setIdentity();
_projection.setM00((2.0 * _frustumNear) / (_frustumRight - _frustumLeft));
_projection.setM11((2.0 * _frustumNear) / (_frustumTop - _frustumBottom));
_projection.setM20((_frustumRight + _frustumLeft) / (_frustumRight - _frustumLeft));
_projection.setM21((_frustumTop + _frustumBottom) / (_frustumTop - _frustumBottom));
_projection.setM22(-(_frustumFar + _frustumNear) / (_frustumFar - _frustumNear));
_projection.setM23(-1.0);
_projection.setM32(-(2.0 * _frustumFar * _frustumNear) / (_frustumFar - _frustumNear));
_projection.setM33(-0.0);
}
_updatePMatrix = false;
}
public void setProjectionMatrix(final ReadOnlyMatrix4 projection) {
_projection.set(projection);
_frustumDirty = true;
}
/**
* @return this camera's 4x4 projection matrix.
*/
public ReadOnlyMatrix4 getProjectionMatrix() {
checkProjection();
return _projection;
}
/**
* Updates the value of our model view matrix.
*/
protected void updateModelViewMatrix() {
_modelView.setIdentity();
_modelView.setM00(-_left.getX());
_modelView.setM10(-_left.getY());
_modelView.setM20(-_left.getZ());
_modelView.setM01(_up.getX());
_modelView.setM11(_up.getY());
_modelView.setM21(_up.getZ());
_modelView.setM02(-_direction.getX());
_modelView.setM12(-_direction.getY());
_modelView.setM22(-_direction.getZ());
_modelView.setM30(_left.dot(_location));
_modelView.setM31(-_up.dot(_location));
_modelView.setM32(_direction.dot(_location));
}
/**
* @return this camera's 4x4 model view matrix.
*/
public ReadOnlyMatrix4 getModelViewMatrix() {
checkModelView();
return _modelView;
}
/**
* @return this camera's 4x4 model view X projection matrix.
*/
public ReadOnlyMatrix4 getModelViewProjectionMatrix() {
checkModelViewProjection();
return _modelViewProjection;
}
/**
* @return the inverse of this camera's 4x4 model view X projection matrix.
*/
public ReadOnlyMatrix4 getModelViewProjectionInverseMatrix() {
checkInverseModelViewProjection();
return _modelViewProjectionInverse;
}
/**
* Calculate a Pick Ray using the given screen position at the near plane of this camera and the camera's position
* in space.
*
* @param screenPosition
* the x, y position on the near space to pass the ray through.
* @param flipVertical
* if true, we'll flip the screenPosition on the y axis. This is useful when you are dealing with
* non-opengl coordinate systems.
* @param store
* the Ray to store the result in. If false, a new Ray is created and returned.
* @return the resulting Ray.
*/
public Ray3 getPickRay(final ReadOnlyVector2 screenPosition, final boolean flipVertical, final Ray3 store) {
final Vector2 pos = Vector2.fetchTempInstance().set(screenPosition);
if (flipVertical) {
pos.setY(getHeight() - screenPosition.getY());
}
Ray3 result = store;
if (result == null) {
result = new Ray3();
}
final Vector3 origin = Vector3.fetchTempInstance();
final Vector3 direction = Vector3.fetchTempInstance();
getWorldCoordinates(pos, 0, origin);
getWorldCoordinates(pos, 0.3, direction).subtractLocal(origin).normalizeLocal();
result.setOrigin(origin);
result.setDirection(direction);
Vector2.releaseTempInstance(pos);
Vector3.releaseTempInstance(origin);
Vector3.releaseTempInstance(direction);
return result;
}
/**
* Converts a local x,y screen position and depth value to world coordinates based on the current settings of this
* camera.
*
* @param screenPosition
* the x,y coordinates of the screen position
* @param zDepth
* the depth into the camera view to take our point. 0 indicates the near plane of the camera and 1
* indicates the far plane.
* @return a new vector containing the world coordinates.
*/
public Vector3 getWorldCoordinates(final ReadOnlyVector2 screenPosition, final double zDepth) {
return getWorldCoordinates(screenPosition, zDepth, null);
}
/**
* Converts a local x,y screen position and depth value to world coordinates based on the current settings of this
* camera.
*
* @param screenPosition
* the x,y coordinates of the screen position
* @param zDepth
* the depth into the camera view to take our point. 0 indicates the near plane of the camera and 1
* indicates the far plane.
* @param store
* Use to avoid object creation. if not null, the results are stored in the given vector and returned.
* Otherwise, a new vector is created.
* @return a vector containing the world coordinates.
*/
public Vector3 getWorldCoordinates(final ReadOnlyVector2 screenPosition, final double zDepth, Vector3 store) {
if (store == null) {
store = new Vector3();
}
checkInverseModelViewProjection();
final Vector4 position = Vector4.fetchTempInstance();
position.set((screenPosition.getX() / getWidth() - _viewPortLeft) / (_viewPortRight - _viewPortLeft) * 2 - 1,
(screenPosition.getY() / getHeight() - _viewPortBottom) / (_viewPortTop - _viewPortBottom) * 2 - 1,
zDepth * 2 - 1, 1);
_modelViewProjectionInverse.applyPre(position, position);
position.multiplyLocal(1.0 / position.getW());
store.setX(position.getX());
store.setY(position.getY());
store.setZ(position.getZ());
Vector4.releaseTempInstance(position);
return store;
}
/**
* Converts a position in world coordinate space to an x,y screen position and depth value using the current
* settings of this camera.
*
* @param worldPos
* the position in space to retrieve screen coordinates for.
* @return a new vector containing the screen coordinates as x and y and the distance as a percent between near and
* far planes.
*/
public Vector3 getScreenCoordinates(final ReadOnlyVector3 worldPos) {
return getScreenCoordinates(worldPos, null);
}
/**
* Converts a position in world coordinate space to an x,y screen position and depth value using the current
* settings of this camera.
*
* @param worldPos
* the position in space to retrieve screen coordinates for.
* @param store
* Use to avoid object creation. if not null, the results are stored in the given vector and returned.
* Otherwise, a new vector is created.
* @return a vector containing the screen coordinates as x and y and the distance as a percent between near and far
* planes.
*/
public Vector3 getScreenCoordinates(final ReadOnlyVector3 worldPosition, Vector3 store) {
store = getNormalizedDeviceCoordinates(worldPosition, store);
store.setX(((store.getX() + 1) * (_viewPortRight - _viewPortLeft) / 2) * getWidth());
store.setY(((store.getY() + 1) * (_viewPortTop - _viewPortBottom) / 2) * getHeight());
store.setZ((store.getZ() + 1) / 2);
return store;
}
/**
* Converts a position in world coordinate space to a x,y,z frustum position using the current settings of this
* camera.
*
* @param worldPos
* the position in space to retrieve frustum coordinates for.
* @return a new vector containing the x,y,z frustum position
*/
public Vector3 getFrustumCoordinates(final ReadOnlyVector3 worldPos) {
return getFrustumCoordinates(worldPos, null);
}
/**
* Converts a position in world coordinate space to a x,y,z frustum position using the current settings of this
* camera.
*
* @param worldPos
* the position in space to retrieve frustum coordinates for.
* @param store
* Use to avoid object creation. if not null, the results are stored in the given vector and returned.
* Otherwise, a new vector is created.
* @return a vector containing the x,y,z frustum position
*/
public Vector3 getFrustumCoordinates(final ReadOnlyVector3 worldPosition, Vector3 store) {
store = getNormalizedDeviceCoordinates(worldPosition, store);
store.setX(((store.getX() + 1) * (_frustumRight - _frustumLeft) / 2) + _frustumLeft);
store.setY(((store.getY() + 1) * (_frustumTop - _frustumBottom) / 2) + _frustumBottom);
store.setZ(((store.getZ() + 1) * (_frustumFar - _frustumNear) / 2) + _frustumNear);
return store;
}
public Vector3 getNormalizedDeviceCoordinates(final ReadOnlyVector3 worldPos) {
return getNormalizedDeviceCoordinates(worldPos, null);
}
public Vector3 getNormalizedDeviceCoordinates(final ReadOnlyVector3 worldPosition, Vector3 store) {
if (store == null) {
store = new Vector3();
}
checkModelViewProjection();
final Vector4 position = Vector4.fetchTempInstance();
position.set(worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), 1);
_modelViewProjection.applyPre(position, position);
position.multiplyLocal(1.0 / position.getW());
store.setX(position.getX());
store.setY(position.getY());
store.setZ(position.getZ());
Vector4.releaseTempInstance(position);
return store;
}
/**
* update modelView if necessary.
*/
private void checkModelView() {
if (_updateMVMatrix) {
updateModelViewMatrix();
_updateMVMatrix = false;
}
}
/**
* update projection if necessary.
*/
private void checkProjection() {
if (_updatePMatrix) {
updateProjectionMatrix();
_updatePMatrix = false;
}
}
/**
* update modelViewProjection if necessary.
*/
private void checkModelViewProjection() {
if (_updateMVPMatrix) {
_modelViewProjection.set(getModelViewMatrix()).multiplyLocal(getProjectionMatrix());
_updateMVPMatrix = false;
}
}
/**
* update inverse modelViewProjection if necessary.
*/
private void checkInverseModelViewProjection() {
if (_updateInverseMVPMatrix) {
checkModelViewProjection();
_modelViewProjection.invert(_modelViewProjectionInverse);
_updateInverseMVPMatrix = false;
}
}
/**
* @return the height of the display.
*/
public int getHeight() {
return _height;
}
public ProjectionMode getProjectionMode() {
return _projectionMode;
}
public void setProjectionMode(final ProjectionMode projectionMode) {
_projectionMode = projectionMode;
}
/**
* @return the width of the display.
*/
public int getWidth() {
return _width;
}
/**
* Apply this camera's values to the given Renderer. Only values determined to be dirty (via updates, setters, etc.)
* will be applied. This method must be run in the same thread as a valid OpenGL context.
*
* @param renderer
* the Renderer to use.
*/
public void apply(final Renderer renderer) {
ContextManager.getCurrentContext().setCurrentCamera(this);
if (_depthRangeDirty) {
renderer.setDepthRange(_depthRangeNear, _depthRangeFar);
_depthRangeDirty = false;
}
if (_frustumDirty) {
applyProjectionMatrix(renderer);
_frustumDirty = false;
}
if (_viewPortDirty) {
applyViewport(renderer);
_viewPortDirty = false;
}
if (_frameDirty) {
applyModelViewMatrix(renderer);
_frameDirty = false;
}
}
/**
* Mark view port dirty.
*/
protected void onViewPortChange() {
_viewPortDirty = true;
}
/**
* Apply the camera's projection matrix using the given Renderer.
*
* @param renderer
* the Renderer to use.
*/
protected void applyProjectionMatrix(final Renderer renderer) {
_matrixBuffer.rewind();
getProjectionMatrix().toFloatBuffer(_matrixBuffer);
_matrixBuffer.rewind();
renderer.setProjectionMatrix(_matrixBuffer);
}
/**
* Apply the camera's viewport using the given Renderer.
*
* @param renderer
* the Renderer to use.
*/
protected void applyViewport(final Renderer renderer) {
final int x = (int) (_viewPortLeft * _width);
final int y = (int) (_viewPortBottom * _height);
final int w = (int) ((_viewPortRight - _viewPortLeft) * _width);
final int h = (int) ((_viewPortTop - _viewPortBottom) * _height);
renderer.setViewport(x, y, w, h);
}
/**
* Apply the camera's modelview matrix using the given Renderer.
*
* @param renderer
* the Renderer to use.
*/
protected void applyModelViewMatrix(final Renderer renderer) {
_matrixBuffer.rewind();
getModelViewMatrix().toFloatBuffer(_matrixBuffer);
_matrixBuffer.rewind();
renderer.setModelViewMatrix(_matrixBuffer);
}
public void write(final OutputCapsule capsule) throws IOException {
capsule.write(_location, "location", new Vector3(Vector3.ZERO));
capsule.write(_left, "left", new Vector3(Vector3.UNIT_X));
capsule.write(_up, "up", new Vector3(Vector3.UNIT_Y));
capsule.write(_direction, "direction", new Vector3(Vector3.UNIT_Z));
capsule.write(_frustumNear, "frustumNear", 1);
capsule.write(_frustumFar, "frustumFar", 2);
capsule.write(_frustumLeft, "frustumLeft", -0.5);
capsule.write(_frustumRight, "frustumRight", 0.5);
capsule.write(_frustumTop, "frustumTop", 0.5);
capsule.write(_frustumBottom, "frustumBottom", -0.5);
capsule.write(_coeffLeft, "coeffLeft", new double[2]);
capsule.write(_coeffRight, "coeffRight", new double[2]);
capsule.write(_coeffBottom, "coeffBottom", new double[2]);
capsule.write(_coeffTop, "coeffTop", new double[2]);
capsule.write(_planeQuantity, "planeQuantity", 6);
capsule.write(_viewPortLeft, "viewPortLeft", 0);
capsule.write(_viewPortRight, "viewPortRight", 1);
capsule.write(_viewPortTop, "viewPortTop", 1);
capsule.write(_viewPortBottom, "viewPortBottom", 0);
capsule.write(_width, "width", 0);
capsule.write(_height, "height", 0);
capsule.write(_depthRangeNear, "depthRangeNear", 0.0);
capsule.write(_depthRangeFar, "depthRangeFar", 1.0);
}
public void read(final InputCapsule capsule) throws IOException {
_location.set((Vector3) capsule.readSavable("location", new Vector3(Vector3.ZERO)));
_left.set((Vector3) capsule.readSavable("left", new Vector3(Vector3.UNIT_X)));
_up.set((Vector3) capsule.readSavable("up", new Vector3(Vector3.UNIT_Y)));
_direction.set((Vector3) capsule.readSavable("direction", new Vector3(Vector3.UNIT_Z)));
_frustumNear = capsule.readDouble("frustumNear", 1);
_frustumFar = capsule.readDouble("frustumFar", 2);
_frustumLeft = capsule.readDouble("frustumLeft", -0.5);
_frustumRight = capsule.readDouble("frustumRight", 0.5);
_frustumTop = capsule.readDouble("frustumTop", 0.5);
_frustumBottom = capsule.readDouble("frustumBottom", -0.5);
_coeffLeft = capsule.readDoubleArray("coeffLeft", new double[2]);
_coeffRight = capsule.readDoubleArray("coeffRight", new double[2]);
_coeffBottom = capsule.readDoubleArray("coeffBottom", new double[2]);
_coeffTop = capsule.readDoubleArray("coeffTop", new double[2]);
_planeQuantity = capsule.readInt("planeQuantity", 6);
_viewPortLeft = capsule.readDouble("viewPortLeft", 0);
_viewPortRight = capsule.readDouble("viewPortRight", 1);
_viewPortTop = capsule.readDouble("viewPortTop", 1);
_viewPortBottom = capsule.readDouble("viewPortBottom", 0);
_width = capsule.readInt("width", 0);
_height = capsule.readInt("height", 0);
_depthRangeNear = capsule.readDouble("depthRangeNear", 0.0);
_depthRangeFar = capsule.readDouble("depthRangeFar", 1.0);
}
public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
_location.set((Vector3) in.readObject());
_left.set((Vector3) in.readObject());
_up.set((Vector3) in.readObject());
_direction.set((Vector3) in.readObject());
_frustumNear = in.readDouble();
_frustumFar = in.readDouble();
_frustumLeft = in.readDouble();
_frustumRight = in.readDouble();
_frustumTop = in.readDouble();
_frustumBottom = in.readDouble();
_coeffLeft = (double[]) in.readObject();
_coeffRight = (double[]) in.readObject();
_coeffBottom = (double[]) in.readObject();
_coeffTop = (double[]) in.readObject();
_planeQuantity = in.readInt();
_viewPortLeft = in.readDouble();
_viewPortRight = in.readDouble();
_viewPortTop = in.readDouble();
_viewPortBottom = in.readDouble();
_width = in.readInt();
_height = in.readInt();
_depthRangeNear = in.readDouble();
_depthRangeFar = in.readDouble();
}
public void writeExternal(final ObjectOutput out) throws IOException {
out.writeObject(_location);
out.writeObject(_left);
out.writeObject(_up);
out.writeObject(_direction);
out.writeDouble(_frustumNear);
out.writeDouble(_frustumFar);
out.writeDouble(_frustumLeft);
out.writeDouble(_frustumRight);
out.writeDouble(_frustumTop);
out.writeDouble(_frustumBottom);
out.writeObject(_coeffLeft);
out.writeObject(_coeffRight);
out.writeObject(_coeffBottom);
out.writeObject(_coeffTop);
out.writeInt(_planeQuantity);
out.writeDouble(_viewPortLeft);
out.writeDouble(_viewPortRight);
out.writeDouble(_viewPortTop);
out.writeDouble(_viewPortBottom);
out.writeInt(_width);
out.writeInt(_height);
out.writeDouble(_depthRangeNear);
out.writeDouble(_depthRangeFar);
}
@Override
public String toString() {
return "com.ardor3d.renderer.Camera: loc - " + Arrays.toString(getLocation().toArray(null)) + " dir - "
+ Arrays.toString(getDirection().toArray(null)) + " up - " + Arrays.toString(getUp().toArray(null))
+ " left - " + Arrays.toString(getLeft().toArray(null));
}
public Class<? extends Camera> getClassTag() {
return getClass();
}
/**
* Convenience method for retrieving the Camera set on the current RenderContext. Similar to
* ContextManager.getCurrentContext().getCurrentCamera() but with null checks for current context.
*
* @return the Camera on the current RenderContext.
*/
public static Camera getCurrentCamera() {
if (ContextManager.getCurrentContext() == null) {
return null;
}
return ContextManager.getCurrentContext().getCurrentCamera();
}
public boolean isFrameDirty() {
return _frameDirty;
}
}