com.jme3.renderer.Camera Maven / Gradle / Ivy
Show all versions of jme3-core Show documentation
/*
* Copyright (c) 2009-2021 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.renderer;
import com.jme3.bounding.BoundingBox;
import com.jme3.bounding.BoundingVolume;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f;
import com.jme3.math.Plane;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.math.Vector4f;
import com.jme3.util.TempVars;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A standalone, purely mathematical class for doing
* camera-related computations.
*
* Given input data such as location, orientation (direction, left, up),
* and viewport settings, it can compute data necessary to render objects
* with the graphics library. Two matrices are generated, the view matrix
* transforms objects from world space into eye space, while the projection
* matrix transforms objects from eye space into clip space.
*
* Another purpose of the camera class is to do frustum culling operations,
* defined by six planes which define a 3D frustum shape, it is possible to
* test if an object bounded by a mathematically defined volume is inside
* the camera frustum, and thus to avoid rendering objects that are outside
* the frustum
*
*
* @author Mark Powell
* @author Joshua Slack
*/
public class Camera implements Savable, Cloneable {
private static final Logger logger = Logger.getLogger(Camera.class.getName());
/**
* The result of a culling check operation.
* see {@link #contains(com.jme3.bounding.BoundingVolume) }
*/
public enum FrustumIntersect {
/**
* Defines a constant assigned to spatials that are completely outside
* of this camera's view frustum.
*/
Outside,
/**
* Defines a constant assigned to spatials that are completely inside
* the camera's view frustum.
*/
Inside,
/**
* Defines a constant assigned to spatials that are intersecting one of
* the six planes that define the view frustum.
*/
Intersects;
}
/**
* LEFT_PLANE represents the left plane of the camera frustum.
*/
private static final int LEFT_PLANE = 0;
/**
* RIGHT_PLANE represents the right plane of the camera frustum.
*/
private static final int RIGHT_PLANE = 1;
/**
* BOTTOM_PLANE represents the bottom plane of the camera frustum.
*/
private static final int BOTTOM_PLANE = 2;
/**
* TOP_PLANE represents the top plane of the camera frustum.
*/
private static final int TOP_PLANE = 3;
/**
* FAR_PLANE represents the far plane of the camera frustum.
*/
private static final int FAR_PLANE = 4;
/**
* NEAR_PLANE represents the near plane of the camera frustum.
*/
private static final int NEAR_PLANE = 5;
/**
* FRUSTUM_PLANES represents the number of planes of the camera frustum.
*/
private static final int FRUSTUM_PLANES = 6;
/**
* MAX_WORLD_PLANES holds the maximum planes allowed by the system.
*/
private static final int MAX_WORLD_PLANES = 6;
/**
* Camera's location.
*/
protected Vector3f location;
/**
* The orientation of the camera.
*/
protected Quaternion rotation;
/**
* Distance from camera to near frustum plane.
*/
protected float frustumNear;
/**
* Distance from camera to far frustum plane.
*/
protected float frustumFar;
/**
* Distance from camera to left frustum plane.
*/
protected float frustumLeft;
/**
* Distance from camera to right frustum plane.
*/
protected float frustumRight;
/**
* Distance from camera to top frustum plane.
*/
protected float frustumTop;
/**
* Distance from camera to bottom frustum plane.
*/
protected float frustumBottom;
/**
* Temporary values computed in onFrustumChange that are needed if a call is made to onFrameChange.
*/
protected float[] coeffLeft;
/**
* Temporary values computed in onFrustumChange that are needed if a call is made to onFrameChange.
*/
protected float[] coeffRight;
/**
* Temporary values computed in onFrustumChange that are needed if a call is made to onFrameChange.
*/
protected float[] coeffBottom;
/**
* Temporary values computed in onFrustumChange that are needed if a call is made to onFrameChange.
*/
protected float[] coeffTop;
//view port coordinates
/**
* Percent value on display where horizontal viewing starts for this camera.
* Default is 0.
*/
protected float viewPortLeft;
/**
* Percent value on display where horizontal viewing ends for this camera.
* Default is 1.
*/
protected float viewPortRight;
/**
* Percent value on display where vertical viewing ends for this camera.
* Default is 1.
*/
protected float viewPortTop;
/**
* Percent value on display where vertical viewing begins for this camera.
* Default is 0.
*/
protected float viewPortBottom;
/**
* Array holding the planes that this camera will check for culling.
*/
protected Plane[] worldPlane;
/**
* A mask value set during contains() that allows fast culling of a Node's
* children.
*/
private int planeState;
/**
* The width of the viewport in pixels.
*/
protected int width;
/**
* The height of the viewport in pixels.
*/
protected int height;
/**
* True if the renderer needs to update its viewport boundaries.
*/
protected boolean viewportChanged = true;
/**
* store the value for field parallelProjection
*/
private boolean parallelProjection = true;
/**
* Temporarily overrides the projection matrix.
*/
protected Matrix4f projectionMatrixOverride = new Matrix4f();
private boolean overrideProjection;
/**
* Transforms world space into eye space.
*/
protected Matrix4f viewMatrix = new Matrix4f();
/**
* Transforms eye space into clip space, unless overridden by projectionMatrixOverride.
*/
protected Matrix4f projectionMatrix = new Matrix4f();
/**
* Transforms world space into clip space.
*/
protected Matrix4f viewProjectionMatrix = new Matrix4f();
private BoundingBox guiBounding = new BoundingBox();
/** The camera's name. */
protected String name;
/**
* Serialization only. Do not use.
*/
protected Camera() {
worldPlane = new Plane[MAX_WORLD_PLANES];
for (int i = 0; i < MAX_WORLD_PLANES; i++) {
worldPlane[i] = new Plane();
}
}
/**
* Instantiates a new Camera
object. All
* values of the camera are set to default.
*
* @param width the desired width (in pixels)
* @param height the desired height (in pixels)
*/
public Camera(int width, int height) {
this();
location = new Vector3f();
rotation = new Quaternion();
frustumNear = 1.0f;
frustumFar = 2.0f;
frustumLeft = -0.5f;
frustumRight = 0.5f;
frustumTop = 0.5f;
frustumBottom = -0.5f;
coeffLeft = new float[2];
coeffRight = new float[2];
coeffBottom = new float[2];
coeffTop = new float[2];
viewPortLeft = 0.0f;
viewPortRight = 1.0f;
viewPortTop = 1.0f;
viewPortBottom = 0.0f;
this.width = width;
this.height = height;
onFrustumChange();
onViewPortChange();
onFrameChange();
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Camera created (W: {0}, H: {1})", new Object[]{width, height});
}
}
@Override
public Camera clone() {
try {
Camera cam = (Camera) super.clone();
cam.viewportChanged = true;
cam.planeState = 0;
cam.worldPlane = new Plane[MAX_WORLD_PLANES];
for (int i = 0; i < worldPlane.length; i++) {
cam.worldPlane[i] = worldPlane[i].clone();
}
cam.coeffLeft = new float[2];
cam.coeffRight = new float[2];
cam.coeffBottom = new float[2];
cam.coeffTop = new float[2];
cam.location = location.clone();
cam.rotation = rotation.clone();
if (projectionMatrixOverride != null) {
cam.projectionMatrixOverride = projectionMatrixOverride.clone();
}
cam.viewMatrix = viewMatrix.clone();
cam.projectionMatrix = projectionMatrix.clone();
cam.viewProjectionMatrix = viewProjectionMatrix.clone();
cam.guiBounding = (BoundingBox) guiBounding.clone();
cam.update();
return cam;
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
}
/**
* Copies the settings of the given camera.
*
* @param cam
* the camera we copy the settings from
*/
public void copyFrom(Camera cam) {
location.set(cam.location);
rotation.set(cam.rotation);
frustumNear = cam.frustumNear;
frustumFar = cam.frustumFar;
frustumLeft = cam.frustumLeft;
frustumRight = cam.frustumRight;
frustumTop = cam.frustumTop;
frustumBottom = cam.frustumBottom;
coeffLeft[0] = cam.coeffLeft[0];
coeffLeft[1] = cam.coeffLeft[1];
coeffRight[0] = cam.coeffRight[0];
coeffRight[1] = cam.coeffRight[1];
coeffBottom[0] = cam.coeffBottom[0];
coeffBottom[1] = cam.coeffBottom[1];
coeffTop[0] = cam.coeffTop[0];
coeffTop[1] = cam.coeffTop[1];
viewPortLeft = cam.viewPortLeft;
viewPortRight = cam.viewPortRight;
viewPortTop = cam.viewPortTop;
viewPortBottom = cam.viewPortBottom;
this.width = cam.width;
this.height = cam.height;
this.planeState = 0;
this.viewportChanged = true;
for (int i = 0; i < MAX_WORLD_PLANES; ++i) {
worldPlane[i].setNormal(cam.worldPlane[i].getNormal());
worldPlane[i].setConstant(cam.worldPlane[i].getConstant());
}
this.parallelProjection = cam.parallelProjection;
this.overrideProjection = cam.overrideProjection;
this.projectionMatrixOverride.set(cam.projectionMatrixOverride);
this.viewMatrix.set(cam.viewMatrix);
this.projectionMatrix.set(cam.projectionMatrix);
this.viewProjectionMatrix.set(cam.viewProjectionMatrix);
this.guiBounding.setXExtent(cam.guiBounding.getXExtent());
this.guiBounding.setYExtent(cam.guiBounding.getYExtent());
this.guiBounding.setZExtent(cam.guiBounding.getZExtent());
this.guiBounding.setCenter(cam.guiBounding.getCenter());
this.guiBounding.setCheckPlane(cam.guiBounding.getCheckPlane());
this.name = cam.name;
}
/**
* Sets the camera's name.
*
* @param name the camera's name
*/
public void setName(String name) {
this.name = name;
}
/**
* Returns the camera's name.
*
* @return the camera's name
*/
public String getName() {
return name;
}
/**
* Sets a clipPlane for this camera.
* The clipPlane is used to recompute the
* projectionMatrix using the plane as the near plane
* This technique is known as the oblique near-plane clipping method introduced by Eric Lengyel
* more info here
*
* - http://www.terathon.com/code/oblique.html
*
- http://aras-p.info/texts/obliqueortho.html
*
-
* http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html
*
*
* Note that this will work properly only if it's called on each update,
* and be aware that it won't work properly with the sky bucket.
* if you want to handle the sky bucket, look at how it's done in SimpleWaterProcessor.java
*
* @param clipPlane the plane
* @param side the side the camera stands from the plane
*/
public void setClipPlane(Plane clipPlane, Plane.Side side) {
float sideFactor = 1;
if (side == Plane.Side.Negative) {
sideFactor = -1;
}
//we are on the other side of the plane no need to clip anymore.
if (clipPlane.whichSide(location) == side) {
return;
}
TempVars vars = TempVars.get();
try {
Matrix4f p = projectionMatrixOverride.set(projectionMatrix);
Matrix4f ivm = viewMatrix;
Vector3f point = clipPlane.getNormal().mult(clipPlane.getConstant(), vars.vect1);
Vector3f pp = ivm.mult(point, vars.vect2);
Vector3f pn = ivm.multNormal(clipPlane.getNormal(), vars.vect3);
Vector4f clipPlaneV = vars.vect4f1.set(pn.x * sideFactor, pn.y * sideFactor, pn.z * sideFactor,
-(pp.dot(pn)) * sideFactor);
Vector4f v = vars.vect4f2.set(0, 0, 0, 0);
v.x = (Math.signum(clipPlaneV.x) + p.m02) / p.m00;
v.y = (Math.signum(clipPlaneV.y) + p.m12) / p.m11;
v.z = -1.0f;
v.w = (1.0f + p.m22) / p.m23;
float dot = clipPlaneV.dot(v);
//clipPlaneV.x * v.x + clipPlaneV.y * v.y + clipPlaneV.z * v.z + clipPlaneV.w * v.w;
Vector4f c = clipPlaneV.multLocal(2.0f / dot);
p.m20 = c.x - p.m30;
p.m21 = c.y - p.m31;
p.m22 = c.z - p.m32;
p.m23 = c.w - p.m33;
setProjectionMatrix(p);
} finally {
vars.release();
}
}
/**
* Sets a clipPlane for this camera.
* The cliPlane is used to recompute the projectionMatrix using the plane as the near plane
* This technique is known as the oblique near-plane clipping method introduced by Eric Lengyel
* more info here
*
* -
* http://www.terathon.com/code/oblique.html
* -
* http://aras-p.info/texts/obliqueortho.html
* -
* http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html
*
*
* Note that this will work properly only if it's called on each update,
* and be aware that it won't work properly with the sky bucket.
* if you want to handle the sky bucket, look at how it's done in SimpleWaterProcessor.java
*
* @param clipPlane the plane
*/
public void setClipPlane(Plane clipPlane) {
setClipPlane(clipPlane, clipPlane.whichSide(location));
}
/**
* Resizes this camera's view for the specified display size. Invoked by an
* associated {@link RenderManager} to notify the camera of changes to the
* display dimensions.
*
* @param width the new width of the display, in pixels
* @param height the new height of the display, in pixels
* @param fixAspect if true, recompute the camera's frustum to preserve its
* prior aspect ratio
*/
public void resize(int width, int height, boolean fixAspect) {
this.width = width;
this.height = height;
onViewPortChange();
if (fixAspect) {
float h = height * (viewPortTop - viewPortBottom);
float w = width * (viewPortRight - viewPortLeft);
float aspectRatio = w / h;
frustumRight = frustumTop * aspectRatio;
frustumLeft = -frustumRight;
onFrustumChange();
}
}
/**
* Returns the value of the bottom frustum
* plane.
*
* @return the value of the bottom frustum plane.
*/
public float getFrustumBottom() {
return frustumBottom;
}
/**
* Sets the value of the bottom frustum
* plane.
*
* @param frustumBottom the value of the bottom frustum plane.
*/
public void setFrustumBottom(float frustumBottom) {
this.frustumBottom = frustumBottom;
onFrustumChange();
}
/**
* Gets the value of the far frustum plane.
*
* @return the value of the far frustum plane.
*/
public float getFrustumFar() {
return frustumFar;
}
/**
* Sets the value of the far frustum plane.
*
* @param frustumFar the value of the far frustum plane.
*/
public void setFrustumFar(float frustumFar) {
this.frustumFar = frustumFar;
onFrustumChange();
}
/**
* Gets the value of the left frustum plane.
*
* @return the value of the left frustum plane.
*/
public float getFrustumLeft() {
return frustumLeft;
}
/**
* Sets the value of the left frustum plane.
*
* @param frustumLeft the value of the left frustum plane.
*/
public void setFrustumLeft(float frustumLeft) {
this.frustumLeft = frustumLeft;
onFrustumChange();
}
/**
* Gets the value of the near frustum plane.
*
* @return the value of the near frustum plane.
*/
public float getFrustumNear() {
return frustumNear;
}
/**
* Sets the value of the near frustum plane.
*
* @param frustumNear the value of the near frustum plane.
*/
public void setFrustumNear(float frustumNear) {
this.frustumNear = frustumNear;
onFrustumChange();
}
/**
* Gets the value of the right frustum plane.
*
* @return frustumRight the value of the right frustum plane.
*/
public float getFrustumRight() {
return frustumRight;
}
/**
* Sets the value of the right frustum plane.
*
* @param frustumRight the value of the right frustum plane.
*/
public void setFrustumRight(float frustumRight) {
this.frustumRight = frustumRight;
onFrustumChange();
}
/**
* Gets the value of the top frustum plane.
*
* @return the value of the top frustum plane.
*/
public float getFrustumTop() {
return frustumTop;
}
/**
* Sets the value of the top frustum plane.
*
* @param frustumTop the value of the top frustum plane.
*/
public void setFrustumTop(float frustumTop) {
this.frustumTop = frustumTop;
onFrustumChange();
}
/**
* Obtains field of view when the camera is in perspective mode.
*
* @return Frame of view angle along the Y in degrees, or 0 if the camera is in orthogonal mode.
*/
public float getFov() {
if (!this.parallelProjection) {
float fovY = frustumTop / frustumNear;
fovY = FastMath.atan(fovY);
fovY /= 0.5F * FastMath.DEG_TO_RAD;
return fovY;
}
return 0;
}
/**
* Sets the field of view when the camera is in perspective mode. Note that this method has no
* effect when the camera is in orthogonal mode.
*
* @param fovY Frame of view angle along the Y in degrees. This must be greater than 0.
*/
public void setFov(float fovY) {
if (fovY <= 0) {
throw new IllegalArgumentException("Field of view must be greater than 0");
}
if (this.parallelProjection) {
throw new IllegalArgumentException("Cannot set field of view on orthogonal camera");
}
float h = FastMath.tan(fovY * FastMath.DEG_TO_RAD * .5f) * frustumNear;
float w = h * getAspect();
frustumLeft = -w;
frustumRight = w;
frustumBottom = -h;
frustumTop = h;
onFrustumChange();
}
/**
* Obtains the aspect ratio.
*
* @return Width:Height ratio.
*/
public float getAspect() {
float h = height * (viewPortTop - viewPortBottom);
float w = width * (viewPortRight - viewPortLeft);
return w / h;
}
/**
* Retrieves the location vector of the camera.
*
* @return the position of the camera.
* @see Camera#getLocation()
*/
public Vector3f getLocation() {
return location;
}
/**
* Retrieves the rotation quaternion of the camera.
*
* @return the rotation of the camera.
*/
public Quaternion getRotation() {
return rotation;
}
/**
* Retrieves the direction vector the camera is
* facing.
*
* @return the direction the camera is facing.
* @see Camera#getDirection()
*/
public Vector3f getDirection() {
return rotation.getRotationColumn(2);
}
/**
* Retrieves the left axis of the camera.
*
* @return the left axis of the camera.
* @see Camera#getLeft()
*/
public Vector3f getLeft() {
return rotation.getRotationColumn(0);
}
/**
* Retrieves the up axis of the camera.
*
* @return the up axis of the camera.
* @see Camera#getUp()
*/
public Vector3f getUp() {
return rotation.getRotationColumn(1);
}
/**
* Retrieves the direction vector the camera is
* facing.
*
* @param store storage for the result (modified if not null)
* @return the direction the camera is facing.
* @see Camera#getDirection()
*/
public Vector3f getDirection(Vector3f store) {
return rotation.getRotationColumn(2, store);
}
/**
* Retrieves the left axis of the camera.
*
* @param store storage for the result (modified if not null)
* @return the left axis of the camera.
* @see Camera#getLeft()
*/
public Vector3f getLeft(Vector3f store) {
return rotation.getRotationColumn(0, store);
}
/**
* Retrieves the up axis of the camera.
*
* @param store storage for the result (modified if not null)
* @return the up axis of the camera.
* @see Camera#getUp()
*/
public Vector3f getUp(Vector3f store) {
return rotation.getRotationColumn(1, store);
}
/**
* Sets the position of the camera.
*
* @param location the position of the camera.
*/
public void setLocation(Vector3f location) {
this.location.set(location);
onFrameChange();
}
/**
* Sets the orientation of this camera. This will
* be equivalent to setting each of the axes:
*
* cam.setLeft(rotation.getRotationColumn(0));
* cam.setUp(rotation.getRotationColumn(1));
* cam.setDirection(rotation.getRotationColumn(2));
*
*
* @param rotation the rotation of this camera
*/
public void setRotation(Quaternion rotation) {
this.rotation.set(rotation);
onFrameChange();
}
/**
* Sets the direction the camera is facing
* given a direction and an up vector.
*
* @param direction the direction this camera is facing.
* @param up the desired "up" direction for the camera (not null,
* unaffected, typically (0,1,0))
*/
public void lookAtDirection(Vector3f direction, Vector3f up) {
this.rotation.lookAt(direction, up);
onFrameChange();
}
/**
* Sets the axes (left, up and direction) for this
* camera.
*
* @param left the left axis of the camera.
* @param up the up axis of the camera.
* @param direction the direction the camera is facing.
*
* @see Camera#setAxes(com.jme3.math.Quaternion)
*/
public void setAxes(Vector3f left, Vector3f up, Vector3f direction) {
this.rotation.fromAxes(left, up, direction);
onFrameChange();
}
/**
* Uses a rotational matrix to set the axes of the
* camera.
*
* @param axes the matrix that defines the orientation of the camera.
*/
public void setAxes(Quaternion axes) {
this.rotation.set(axes);
onFrameChange();
}
/**
* Normalizes the camera vectors.
*/
public void normalize() {
this.rotation.normalizeLocal();
onFrameChange();
}
/**
* Sets the frustum of this camera object.
*
* @param near the near plane.
* @param far the far plane.
* @param left the left plane.
* @param right the right plane.
* @param top the top plane.
* @param bottom the bottom plane.
* @see Camera#setFrustum(float, float, float, float,
* float, float)
*/
public void setFrustum(float near, float far, float left, float right,
float top, float bottom) {
frustumNear = near;
frustumFar = far;
frustumLeft = left;
frustumRight = right;
frustumTop = top;
frustumBottom = bottom;
onFrustumChange();
}
/**
* Defines the frustum for the camera. This
* frustum is defined by a viewing angle, aspect ratio, and near/far planes
*
* @param fovY Frame of view angle along the Y in degrees.
* @param aspect Width:Height ratio
* @param near Near view plane distance
* @param far Far view plane distance
*/
public void setFrustumPerspective(float fovY, float aspect, float near,
float far) {
if (Float.isNaN(aspect) || Float.isInfinite(aspect)) {
// ignore.
logger.log(Level.WARNING, "Invalid aspect given to setFrustumPerspective: {0}", aspect);
return;
}
float h = FastMath.tan(fovY * FastMath.DEG_TO_RAD * .5f) * near;
float w = h * aspect;
frustumLeft = -w;
frustumRight = w;
frustumBottom = -h;
frustumTop = h;
frustumNear = near;
frustumFar = far;
// Camera is no longer parallel projection even if it was before
parallelProjection = false;
onFrustumChange();
}
/**
* Sets the orientation and location of the camera.
*
* @param location the point position of the camera.
* @param left the left axis of the camera.
* @param up the up axis of the camera.
* @param direction the facing of the camera.
* @see Camera#setFrame(com.jme3.math.Vector3f,
* com.jme3.math.Vector3f, com.jme3.math.Vector3f, com.jme3.math.Vector3f)
*/
public void setFrame(Vector3f location, Vector3f left, Vector3f up,
Vector3f direction) {
this.location = location;
this.rotation.fromAxes(left, up, direction);
onFrameChange();
}
/**
* A convenience method for auto-setting the frame
* based on a world position the user desires the camera to look at. It
* repoints the camera towards the given position using the difference
* between the position and the current camera location as a direction
* vector and the 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.
* (typically {0, 1, 0} in jME.)
*/
public void lookAt(Vector3f pos, Vector3f worldUpVector) {
TempVars vars = TempVars.get();
Vector3f newDirection = vars.vect1;
Vector3f newUp = vars.vect2;
Vector3f newLeft = vars.vect3;
newDirection.set(pos).subtractLocal(location).normalizeLocal();
newUp.set(worldUpVector).normalizeLocal();
if (newUp.equals(Vector3f.ZERO)) {
newUp.set(Vector3f.UNIT_Y);
}
newLeft.set(newUp).crossLocal(newDirection).normalizeLocal();
if (newLeft.equals(Vector3f.ZERO)) {
if (newDirection.x != 0) {
newLeft.set(newDirection.y, -newDirection.x, 0f);
} else {
newLeft.set(0f, newDirection.z, -newDirection.y);
}
}
newUp.set(newDirection).crossLocal(newLeft).normalizeLocal();
this.rotation.fromAxes(newLeft, newUp, newDirection);
this.rotation.normalizeLocal();
vars.release();
onFrameChange();
}
/**
* Sets the orientation and location of the camera.
*
* @param location
* the point position of the camera.
* @param axes
* the orientation of the camera.
*/
public void setFrame(Vector3f location, Quaternion axes) {
this.location = location;
this.rotation.set(axes);
onFrameChange();
}
/**
* Updates the camera parameters by calling
* onFrustumChange
,onViewPortChange
and
* onFrameChange
.
*
* @see Camera#update()
*/
public void update() {
onFrustumChange();
onViewPortChange();
//...this is always called by onFrustumChange()
//onFrameChange();
}
/**
* Returns the state of the frustum planes. So
* checks can be made as to which frustum plane has been examined for
* culling thus far.
*
* @return the current plane state int.
*/
public int getPlaneState() {
return planeState;
}
/**
* Sets the state to keep track of tested
* planes for culling.
*
* @param planeState the updated state.
*/
public void setPlaneState(int planeState) {
this.planeState = planeState;
}
/**
* Gets the left boundary of the viewport.
*
* @return the left boundary of the viewport
*/
public float getViewPortLeft() {
return viewPortLeft;
}
/**
* Sets the left boundary of the viewport.
*
* @param left the left boundary of the viewport
*/
public void setViewPortLeft(float left) {
viewPortLeft = left;
onViewPortChange();
}
/**
* Gets the right boundary of the viewport.
*
* @return the right boundary of the viewport
*/
public float getViewPortRight() {
return viewPortRight;
}
/**
* Sets the right boundary of the viewport.
*
* @param right the right boundary of the viewport
*/
public void setViewPortRight(float right) {
viewPortRight = right;
onViewPortChange();
}
/**
* Gets the top boundary of the viewport.
*
* @return the top boundary of the viewport
*/
public float getViewPortTop() {
return viewPortTop;
}
/**
* Sets the top boundary of the viewport.
*
* @param top the top boundary of the viewport
*/
public void setViewPortTop(float top) {
viewPortTop = top;
onViewPortChange();
}
/**
* Gets the bottom boundary of the viewport.
*
* @return the bottom boundary of the viewport
*/
public float getViewPortBottom() {
return viewPortBottom;
}
/**
* Sets the bottom boundary of the viewport.
*
* @param bottom the bottom boundary of the viewport
*/
public void setViewPortBottom(float bottom) {
viewPortBottom = bottom;
onViewPortChange();
}
/**
* Sets the boundaries of the viewport.
*
* @param left the left boundary of the viewport (default: 0)
* @param right the right boundary of the viewport (default: 1)
* @param bottom the bottom boundary of the viewport (default: 0)
* @param top the top boundary of the viewport (default: 1)
*/
public void setViewPort(float left, float right, float bottom, float top) {
this.viewPortLeft = left;
this.viewPortRight = right;
this.viewPortBottom = bottom;
this.viewPortTop = top;
onViewPortChange();
}
/**
* Returns the pseudo distance from the given position to the near
* plane of the camera. This is used for render queue sorting.
* @param pos The position to compute a distance to.
* @return Distance from the near plane to the point.
*/
public float distanceToNearPlane(Vector3f pos) {
return worldPlane[NEAR_PLANE].pseudoDistance(pos);
}
/**
* Tests a bounding volume against the planes of the
* camera's frustum. The frustum's planes are set such that the normals all
* face in towards the viewable scene. Therefore, if the bounding volume is
* on the negative side of the plane is can be culled out.
*
* NOTE: This method is used internally for culling, for public usage,
* the plane state of the camera must be saved and restored, e.g:
* BoundingVolume bv;
* Camera c;
* int planeState = c.getPlaneState();
* c.setPlaneState(0);
* c.contains(bv);
* c.setPlaneState(plateState);
*
*
* @param bound the bound to check for culling
* @return See enums in FrustumIntersect
*/
public FrustumIntersect contains(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
}
int planeId = (planeCounter == FRUSTUM_PLANES) ? bound.getCheckPlane() : planeCounter;
// int planeId = planeCounter;
mask = 1 << (planeId);
if ((planeState & mask) == 0) {
Plane.Side side = bound.whichSide(worldPlane[planeId]);
if (side == Plane.Side.Negative) {
//object is outside of frustum
bound.setCheckPlane(planeId);
return FrustumIntersect.Outside;
} else if (side == Plane.Side.Positive) {
//object is visible on *this* plane, so mark this plane
//so that we don't check it for sub nodes.
planeState |= mask;
} else {
rVal = FrustumIntersect.Intersects;
}
}
}
return rVal;
}
/**
* Provides access to one of the planes used for culling.
*
* @param planeId the index of the Plane to access (0→left, 1→right, 2→bottom, 3→top,
* 4→far, 5→near)
* @return the pre-existing instance
*/
public Plane getWorldPlane(int planeId) {
return worldPlane[planeId];
}
/**
* Tests a bounding volume against the ortho
* bounding box of the camera. A bounding box spanning from
* 0, 0 to Width, Height. Constrained by the viewport settings on the
* camera.
*
* @param bound the bound to check for culling
* @return True if the camera contains the gui element bounding volume.
*/
public boolean containsGui(BoundingVolume bound) {
if (bound == null) {
return true;
}
return guiBounding.intersects(bound);
}
/**
* Provides access to the view matrix.
*
* @return the view matrix of the camera.
*
*
The view matrix transforms world space into eye space.
* This matrix is usually defined by the position and
* orientation of the camera.
*/
public Matrix4f getViewMatrix() {
return viewMatrix;
}
/**
* Overrides the projection matrix used by the camera. Will
* use the matrix for computing the view projection matrix as well.
* Use null argument to return to normal functionality.
*
* @param projMatrix the desired projection matrix (unaffected) or null
* to cease the override
*/
public void setProjectionMatrix(Matrix4f projMatrix) {
if (projMatrix == null) {
overrideProjection = false;
projectionMatrixOverride.loadIdentity();
} else {
overrideProjection = true;
projectionMatrixOverride.set(projMatrix);
}
updateViewProjection();
}
/**
* Provides access to the projection matrix.
* @return the projection matrix of the camera.
*
*
The view projection matrix transforms eye space into clip space.
* This matrix is usually defined by the viewport and perspective settings
* of the camera.
*/
public Matrix4f getProjectionMatrix() {
if (overrideProjection) {
return projectionMatrixOverride;
}
return projectionMatrix;
}
/**
* Updates the view projection matrix.
*/
public void updateViewProjection() {
if (overrideProjection) {
viewProjectionMatrix.set(projectionMatrixOverride).multLocal(viewMatrix);
} else {
//viewProjectionMatrix.set(viewMatrix).multLocal(projectionMatrix);
viewProjectionMatrix.set(projectionMatrix).multLocal(viewMatrix);
}
}
/**
* Provides access to the view projection matrix.
*
* @return The result of multiplying the projection matrix by the view
* matrix. This matrix is required for rendering an object. It is
* precomputed to avoid computing it every time an object is rendered.
*/
public Matrix4f getViewProjectionMatrix() {
return viewProjectionMatrix;
}
/**
* Tests whether the viewport (width, height, left, right, bottom, up)
* has been changed. This is needed in the renderer so that the proper
* viewport can be set-up.
*
* @return true if changed, otherwise false
*/
public boolean isViewportChanged() {
return viewportChanged;
}
/**
* Clears the viewport changed flag once it has been updated inside
* the renderer.
*/
public void clearViewportChanged() {
viewportChanged = false;
}
/**
* Called when the viewport has been changed.
*/
public void onViewPortChange() {
viewportChanged = true;
setGuiBounding();
}
private void setGuiBounding() {
float sx = width * viewPortLeft;
float ex = width * viewPortRight;
float sy = height * viewPortBottom;
float ey = height * viewPortTop;
float xExtent = Math.max(0f, (ex - sx) / 2f);
float yExtent = Math.max(0f, (ey - sy) / 2f);
guiBounding.setCenter(sx + xExtent, sy + yExtent, 0);
guiBounding.setXExtent(xExtent);
guiBounding.setYExtent(yExtent);
guiBounding.setZExtent(Float.MAX_VALUE);
}
/**
* Updates the frustum to reflect any changes
* made to the planes. The new frustum values are kept in a temporary
* location for use when calculating the new frame. The projection
* matrix is updated to reflect the current values of the frustum.
*/
public void onFrustumChange() {
if (!isParallelProjection()) {
float nearSquared = frustumNear * frustumNear;
float leftSquared = frustumLeft * frustumLeft;
float rightSquared = frustumRight * frustumRight;
float bottomSquared = frustumBottom * frustumBottom;
float topSquared = frustumTop * frustumTop;
float inverseLength = FastMath.invSqrt(nearSquared + leftSquared);
coeffLeft[0] = -frustumNear * inverseLength;
coeffLeft[1] = -frustumLeft * inverseLength;
inverseLength = FastMath.invSqrt(nearSquared + rightSquared);
coeffRight[0] = frustumNear * inverseLength;
coeffRight[1] = frustumRight * inverseLength;
inverseLength = FastMath.invSqrt(nearSquared + bottomSquared);
coeffBottom[0] = frustumNear * inverseLength;
coeffBottom[1] = -frustumBottom * inverseLength;
inverseLength = FastMath.invSqrt(nearSquared + topSquared);
coeffTop[0] = -frustumNear * inverseLength;
coeffTop[1] = frustumTop * inverseLength;
} else {
coeffLeft[0] = 1;
coeffLeft[1] = 0;
coeffRight[0] = -1;
coeffRight[1] = 0;
coeffBottom[0] = 1;
coeffBottom[1] = 0;
coeffTop[0] = -1;
coeffTop[1] = 0;
}
projectionMatrix.fromFrustum(frustumNear, frustumFar, frustumLeft, frustumRight,
frustumTop, frustumBottom, parallelProjection);
// projectionMatrix.transposeLocal();
// The frame is affected by the frustum values
// update it as well
onFrameChange();
}
/**
* Updates the view frame of the camera.
*/
public void onFrameChange() {
TempVars vars = TempVars.get();
Vector3f left = getLeft(vars.vect1);
Vector3f direction = getDirection(vars.vect2);
Vector3f up = getUp(vars.vect3);
float dirDotLocation = direction.dot(location);
// left plane
Vector3f leftPlaneNormal = worldPlane[LEFT_PLANE].getNormal();
leftPlaneNormal.x = left.x * coeffLeft[0];
leftPlaneNormal.y = left.y * coeffLeft[0];
leftPlaneNormal.z = left.z * coeffLeft[0];
leftPlaneNormal.addLocal(direction.x * coeffLeft[1], direction.y
* coeffLeft[1], direction.z * coeffLeft[1]);
worldPlane[LEFT_PLANE].setConstant(location.dot(leftPlaneNormal));
// right plane
Vector3f rightPlaneNormal = worldPlane[RIGHT_PLANE].getNormal();
rightPlaneNormal.x = left.x * coeffRight[0];
rightPlaneNormal.y = left.y * coeffRight[0];
rightPlaneNormal.z = left.z * coeffRight[0];
rightPlaneNormal.addLocal(direction.x * coeffRight[1], direction.y
* coeffRight[1], direction.z * coeffRight[1]);
worldPlane[RIGHT_PLANE].setConstant(location.dot(rightPlaneNormal));
// bottom plane
Vector3f bottomPlaneNormal = worldPlane[BOTTOM_PLANE].getNormal();
bottomPlaneNormal.x = up.x * coeffBottom[0];
bottomPlaneNormal.y = up.y * coeffBottom[0];
bottomPlaneNormal.z = up.z * coeffBottom[0];
bottomPlaneNormal.addLocal(direction.x * coeffBottom[1], direction.y
* coeffBottom[1], direction.z * coeffBottom[1]);
worldPlane[BOTTOM_PLANE].setConstant(location.dot(bottomPlaneNormal));
// top plane
Vector3f topPlaneNormal = worldPlane[TOP_PLANE].getNormal();
topPlaneNormal.x = up.x * coeffTop[0];
topPlaneNormal.y = up.y * coeffTop[0];
topPlaneNormal.z = up.z * coeffTop[0];
topPlaneNormal.addLocal(direction.x * coeffTop[1], direction.y
* coeffTop[1], direction.z * coeffTop[1]);
worldPlane[TOP_PLANE].setConstant(location.dot(topPlaneNormal));
if (isParallelProjection()) {
worldPlane[LEFT_PLANE].setConstant(worldPlane[LEFT_PLANE].getConstant() + frustumLeft);
worldPlane[RIGHT_PLANE].setConstant(worldPlane[RIGHT_PLANE].getConstant() - frustumRight);
worldPlane[TOP_PLANE].setConstant(worldPlane[TOP_PLANE].getConstant() - frustumTop);
worldPlane[BOTTOM_PLANE].setConstant(worldPlane[BOTTOM_PLANE].getConstant() + frustumBottom);
}
// far plane
worldPlane[FAR_PLANE].setNormal(left);
worldPlane[FAR_PLANE].setNormal(-direction.x, -direction.y, -direction.z);
worldPlane[FAR_PLANE].setConstant(-(dirDotLocation + frustumFar));
// near plane
worldPlane[NEAR_PLANE].setNormal(direction.x, direction.y, direction.z);
worldPlane[NEAR_PLANE].setConstant(dirDotLocation + frustumNear);
viewMatrix.fromFrame(location, direction, up, left);
vars.release();
// viewMatrix.transposeLocal();
updateViewProjection();
}
/**
* Determines whether the projection is parallel or perspective.
*
* @return true if parallel projection is enabled, false if in normal perspective mode
* @see #setParallelProjection(boolean)
*/
public boolean isParallelProjection() {
return this.parallelProjection;
}
/**
* Enables/disables parallel projection.
*
* @param value true to set up this camera for parallel projection,
* false to enter perspective mode
*/
public void setParallelProjection(final boolean value) {
this.parallelProjection = value;
onFrustumChange();
}
/**
* Computes the z value in projection space from the z value in view space
* Note that the returned value goes non-linearly from 0 to 1.
* For more explanation of non-linear z buffer see
* http://www.sjbaker.org/steve/omniv/love_your_z_buffer.html
*
* @param viewZPos the z value in view space.
* @return the z value in projection space.
*/
public float getViewToProjectionZ(float viewZPos) {
float far = getFrustumFar();
float near = getFrustumNear();
float a = far / (far - near);
float b = far * near / (near - far);
return a + b / viewZPos;
}
/**
* Computes a position in World space given a screen position in screen space (0,0 to width, height)
* and a z position in projection space ( 0 to 1 non-linear).
* This former value is also known as the Z buffer value or non-linear depth buffer.
* For more explanation of non-linear Z buffer see
* http://www.sjbaker.org/steve/omniv/love_your_z_buffer.html
*
*
To compute the projection space z from the view space z (distance from cam to object)
* @see Camera#getViewToProjectionZ
*
* @param screenPos 2d coordinate in screen space
* @param projectionZPos non linear z value in projection space
* @return the position in world space.
*/
public Vector3f getWorldCoordinates(Vector2f screenPos, float projectionZPos) {
return getWorldCoordinates(screenPos, projectionZPos, null);
}
/**
* Converts the given position from screen space to world space.
*
* @param screenPosition a (2-D) location in screen space (not null)
* @param projectionZPos a (non-linear) Z value in projection space
* @param store storage for the result (modified if not null)
* @return a location vector (in world coordinates, either
* store
or a new vector)
* @see Camera#getWorldCoordinates
*/
public Vector3f getWorldCoordinates(Vector2f screenPosition,
float projectionZPos, Vector3f store) {
if (store == null) {
store = new Vector3f();
}
Matrix4f inverseMat = new Matrix4f(viewProjectionMatrix);
inverseMat.invertLocal();
store.set(
(screenPosition.x / getWidth() - viewPortLeft) / (viewPortRight - viewPortLeft) * 2 - 1,
(screenPosition.y / getHeight() - viewPortBottom) / (viewPortTop - viewPortBottom) * 2 - 1,
projectionZPos * 2 - 1);
float w = inverseMat.multProj(store, store);
store.multLocal(1f / w);
return store;
}
/**
* Converts the given position from world space to screen space.
*
* @param worldPos a location in world coordinates (not null, unaffected)
* @return a new (3-D) location vector (in screen coordinates)
* @see Camera#getScreenCoordinates
*/
public Vector3f getScreenCoordinates(Vector3f worldPos) {
return getScreenCoordinates(worldPos, null);
}
/**
* Converts the given position from world space to screen space.
*
* @param worldPosition a location in world coordinates (not null, unaffected)
* @param store storage for the result (modified if not null)
* @return a (3-D) location vector (in screen coordinates, either
* store
or a new vector)
* @see Camera#getScreenCoordinates(Vector3f, Vector3f)
*/
public Vector3f getScreenCoordinates(Vector3f worldPosition, Vector3f store) {
if (store == null) {
store = new Vector3f();
}
// TempVars vars = vars.lock();
// Quaternion tmp_quat = vars.quat1;
// tmp_quat.set( worldPosition.x, worldPosition.y, worldPosition.z, 1 );
// viewProjectionMatrix.mult(tmp_quat, tmp_quat);
// tmp_quat.multLocal( 1.0f / tmp_quat.getW() );
// store.x = ( ( tmp_quat.getX() + 1 ) * ( viewPortRight - viewPortLeft ) / 2 + viewPortLeft )
// * getWidth();
// store.y = ( ( tmp_quat.getY() + 1 ) * ( viewPortTop - viewPortBottom ) / 2 + viewPortBottom )
// * getHeight();
// store.z = ( tmp_quat.getZ() + 1 ) / 2;
// vars.release();
float w = viewProjectionMatrix.multProj(worldPosition, store);
store.divideLocal(w);
store.x = ((store.x + 1f) * (viewPortRight - viewPortLeft) / 2f + viewPortLeft) * getWidth();
store.y = ((store.y + 1f) * (viewPortTop - viewPortBottom) / 2f + viewPortBottom) * getHeight();
store.z = (store.z + 1f) / 2f;
return store;
}
/**
* Returns the display width.
*
* @return the width/resolution of the display.
*/
public int getWidth() {
return width;
}
/**
* Returns the display height.
*
* @return the height/resolution of the display.
*/
public int getHeight() {
return height;
}
@Override
public String toString() {
return "Camera[location=" + location + "\n, direction=" + getDirection() + "\n"
+ "res=" + width + "x" + height + ", parallel=" + parallelProjection + "\n"
+ "near=" + frustumNear + ", far=" + frustumFar + "]";
}
@Override
public void write(JmeExporter e) throws IOException {
OutputCapsule capsule = e.getCapsule(this);
capsule.write(location, "location", Vector3f.ZERO);
capsule.write(rotation, "rotation", Quaternion.DIRECTION_Z);
capsule.write(frustumNear, "frustumNear", 1);
capsule.write(frustumFar, "frustumFar", 2);
capsule.write(frustumLeft, "frustumLeft", -0.5f);
capsule.write(frustumRight, "frustumRight", 0.5f);
capsule.write(frustumTop, "frustumTop", 0.5f);
capsule.write(frustumBottom, "frustumBottom", -0.5f);
capsule.write(coeffLeft, "coeffLeft", new float[2]);
capsule.write(coeffRight, "coeffRight", new float[2]);
capsule.write(coeffBottom, "coeffBottom", new float[2]);
capsule.write(coeffTop, "coeffTop", new float[2]);
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(name, "name", null);
}
@Override
public void read(JmeImporter importer) throws IOException {
InputCapsule capsule = importer.getCapsule(this);
location = (Vector3f) capsule.readSavable("location", Vector3f.ZERO.clone());
rotation = (Quaternion) capsule.readSavable("rotation", Quaternion.DIRECTION_Z.clone());
frustumNear = capsule.readFloat("frustumNear", 1);
frustumFar = capsule.readFloat("frustumFar", 2);
frustumLeft = capsule.readFloat("frustumLeft", -0.5f);
frustumRight = capsule.readFloat("frustumRight", 0.5f);
frustumTop = capsule.readFloat("frustumTop", 0.5f);
frustumBottom = capsule.readFloat("frustumBottom", -0.5f);
coeffLeft = capsule.readFloatArray("coeffLeft", new float[2]);
coeffRight = capsule.readFloatArray("coeffRight", new float[2]);
coeffBottom = capsule.readFloatArray("coeffBottom", new float[2]);
coeffTop = capsule.readFloatArray("coeffTop", new float[2]);
viewPortLeft = capsule.readFloat("viewPortLeft", 0);
viewPortRight = capsule.readFloat("viewPortRight", 1);
viewPortTop = capsule.readFloat("viewPortTop", 1);
viewPortBottom = capsule.readFloat("viewPortBottom", 0);
width = capsule.readInt("width", 1);
height = capsule.readInt("height", 1);
name = capsule.readString("name", null);
onFrustumChange();
onViewPortChange();
onFrameChange();
}
}