diff options
Diffstat (limited to 'engine/src/core/com/jme3/renderer/Camera.java')
-rw-r--r-- | engine/src/core/com/jme3/renderer/Camera.java | 1436 |
1 files changed, 1436 insertions, 0 deletions
diff --git a/engine/src/core/com/jme3/renderer/Camera.java b/engine/src/core/com/jme3/renderer/Camera.java new file mode 100644 index 0000000..74ba6cf --- /dev/null +++ b/engine/src/core/com/jme3/renderer/Camera.java @@ -0,0 +1,1436 @@ +/* + * Copyright (c) 2009-2010 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.*; +import com.jme3.math.*; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * <code>Camera</code> is a standalone, purely mathematical class for doing + * camera-related computations. + * + * <p> + * 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. + * </p> + * <p>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 + * </p> + * + * @author Mark Powell + * @author Joshua Slack + */ +public class Camera implements Savable, Cloneable { + + private static final Logger logger = Logger.getLogger(Camera.class.getName()); + + /** + * The <code>FrustumIntersect</code> enum is returned as a 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; + protected float[] coeffRight; + protected float[] coeffBottom; + 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; + protected int width; + protected int height; + protected boolean viewportChanged = true; + /** + * store the value for field parallelProjection + */ + private boolean parallelProjection; + protected Matrix4f projectionMatrixOverride; + protected Matrix4f viewMatrix = new Matrix4f(); + protected Matrix4f projectionMatrix = new Matrix4f(); + protected Matrix4f viewProjectionMatrix = new Matrix4f(); + private BoundingBox guiBounding = new BoundingBox(); + /** The camera's name. */ + protected String name; + + /** + * Serialization only. Do not use. + */ + public Camera() { + worldPlane = new Plane[MAX_WORLD_PLANES]; + for (int i = 0; i < MAX_WORLD_PLANES; i++) { + worldPlane[i] = new Plane(); + } + } + + /** + * Constructor instantiates a new <code>Camera</code> object. All + * values of the camera are set to default. + */ + 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(); + + logger.log(Level.INFO, "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(); + } + } + + /** + * This method copise 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 = cam.planeState; + this.viewportChanged = cam.viewportChanged; + 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; + if(cam.projectionMatrixOverride != null) { + if(this.projectionMatrixOverride == null) { + this.projectionMatrixOverride = cam.projectionMatrixOverride.clone(); + } else { + this.projectionMatrixOverride.set(cam.projectionMatrixOverride); + } + } else { + this.projectionMatrixOverride = null; + } + 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; + } + + /** + * This method sets the cameras name. + * @param name the cameras name + */ + public void setName(String name) { + this.name = name; + } + + /** + * This method returns the cameras name. + * @return the cameras name + */ + public String getName() { + return name; + } + + /** + * 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 + * <ul> + * <li><a href="http://www.terathon.com/code/oblique.html">http://www.terathon.com/code/oblique.html</a> + * <li><a href="http://aras-p.info/texts/obliqueortho.html">http://aras-p.info/texts/obliqueortho.html</a> + * <li><a href="http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html">http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html</a> + * </ul> + * + * 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; + } + Matrix4f p = projectionMatrix.clone(); + + Matrix4f ivm = viewMatrix.clone(); + + Vector3f point = clipPlane.getNormal().mult(clipPlane.getConstant()); + Vector3f pp = ivm.mult(point); + Vector3f pn = ivm.multNormal(clipPlane.getNormal(), null); + Vector4f clipPlaneV = new Vector4f(pn.x * sideFactor, pn.y * sideFactor, pn.z * sideFactor, -(pp.dot(pn)) * sideFactor); + + Vector4f v = new Vector4f(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.mult(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); + } + + /** + * 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 + * <ul> + * <li><a href="http://www.terathon.com/code/oblique.html">http://www.terathon.com/code/oblique.html</a></li> + * <li><a href="http://aras-p.info/texts/obliqueortho.html">http://aras-p.info/texts/obliqueortho.html</a></li> + * <li><a href="http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html"> + * http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html</a></li> + * </ul> + * + * 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 with the given width and height. This is + * similar to constructing a new camera, but reusing the same Object. This + * method is called by an associated {@link RenderManager} to notify the camera of + * changes in the display dimensions. + * + * @param width the view width + * @param height the view height + * @param fixAspect If true, the camera's aspect ratio will be recomputed. + * Recomputing the aspect ratio requires changing the frustum values. + */ + public void resize(int width, int height, boolean fixAspect) { + this.width = width; + this.height = height; + onViewPortChange(); + + if (fixAspect /*&& !parallelProjection*/) { + frustumRight = frustumTop * ((float) width / height); + frustumLeft = -frustumRight; + onFrustumChange(); + } + } + + /** + * <code>getFrustumBottom</code> returns the value of the bottom frustum + * plane. + * + * @return the value of the bottom frustum plane. + */ + public float getFrustumBottom() { + return frustumBottom; + } + + /** + * <code>setFrustumBottom</code> 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(); + } + + /** + * <code>getFrustumFar</code> gets the value of the far frustum plane. + * + * @return the value of the far frustum plane. + */ + public float getFrustumFar() { + return frustumFar; + } + + /** + * <code>setFrustumFar</code> 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(); + } + + /** + * <code>getFrustumLeft</code> gets the value of the left frustum plane. + * + * @return the value of the left frustum plane. + */ + public float getFrustumLeft() { + return frustumLeft; + } + + /** + * <code>setFrustumLeft</code> 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(); + } + + /** + * <code>getFrustumNear</code> gets the value of the near frustum plane. + * + * @return the value of the near frustum plane. + */ + public float getFrustumNear() { + return frustumNear; + } + + /** + * <code>setFrustumNear</code> 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(); + } + + /** + * <code>getFrustumRight</code> gets the value of the right frustum plane. + * + * @return frustumRight the value of the right frustum plane. + */ + public float getFrustumRight() { + return frustumRight; + } + + /** + * <code>setFrustumRight</code> 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(); + } + + /** + * <code>getFrustumTop</code> gets the value of the top frustum plane. + * + * @return the value of the top frustum plane. + */ + public float getFrustumTop() { + return frustumTop; + } + + /** + * <code>setFrustumTop</code> 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(); + } + + /** + * <code>getLocation</code> retrieves the location vector of the camera. + * + * @return the position of the camera. + * @see Camera#getLocation() + */ + public Vector3f getLocation() { + return location; + } + + /** + * <code>getRotation</code> retrieves the rotation quaternion of the camera. + * + * @return the rotation of the camera. + */ + public Quaternion getRotation() { + return rotation; + } + + /** + * <code>getDirection</code> 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); + } + + /** + * <code>getLeft</code> retrieves the left axis of the camera. + * + * @return the left axis of the camera. + * @see Camera#getLeft() + */ + public Vector3f getLeft() { + return rotation.getRotationColumn(0); + } + + /** + * <code>getUp</code> retrieves the up axis of the camera. + * + * @return the up axis of the camera. + * @see Camera#getUp() + */ + public Vector3f getUp() { + return rotation.getRotationColumn(1); + } + + /** + * <code>getDirection</code> retrieves the direction vector the camera is + * facing. + * + * @return the direction the camera is facing. + * @see Camera#getDirection() + */ + public Vector3f getDirection(Vector3f store) { + return rotation.getRotationColumn(2, store); + } + + /** + * <code>getLeft</code> retrieves the left axis of the camera. + * + * @return the left axis of the camera. + * @see Camera#getLeft() + */ + public Vector3f getLeft(Vector3f store) { + return rotation.getRotationColumn(0, store); + } + + /** + * <code>getUp</code> retrieves the up axis of the camera. + * + * @return the up axis of the camera. + * @see Camera#getUp() + */ + public Vector3f getUp(Vector3f store) { + return rotation.getRotationColumn(1, store); + } + + /** + * <code>setLocation</code> sets the position of the camera. + * + * @param location the position of the camera. + */ + public void setLocation(Vector3f location) { + this.location.set(location); + onFrameChange(); + } + + /** + * <code>setRotation</code> sets the orientation of this camera. + * This will be equivelant to setting each of the axes: + * <code><br> + * cam.setLeft(rotation.getRotationColumn(0));<br> + * cam.setUp(rotation.getRotationColumn(1));<br> + * cam.setDirection(rotation.getRotationColumn(2));<br> + * </code> + * + * @param rotation the rotation of this camera + */ + public void setRotation(Quaternion rotation) { + this.rotation.set(rotation); + onFrameChange(); + } + + /** + * <code>lookAtDirection</code> sets the direction the camera is facing + * given a direction and an up vector. + * + * @param direction the direction this camera is facing. + */ + public void lookAtDirection(Vector3f direction, Vector3f up) { + this.rotation.lookAt(direction, up); + onFrameChange(); + } + + /** + * <code>setAxes</code> 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(); + } + + /** + * <code>setAxes</code> 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(); + } + + /** + * normalize normalizes the camera vectors. + */ + public void normalize() { + this.rotation.normalizeLocal(); + onFrameChange(); + } + + /** + * <code>setFrustum</code> 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(); + } + + /** + * <code>setFrustumPerspective</code> 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; + + onFrustumChange(); + } + + /** + * <code>setFrame</code> 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(); + } + + /** + * <code>lookAt</code> is a convienence 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(); + } + + /** + * <code>setFrame</code> 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(); + } + + /** + * <code>update</code> updates the camera parameters by calling + * <code>onFrustumChange</code>,<code>onViewPortChange</code> and + * <code>onFrameChange</code>. + * + * @see Camera#update() + */ + public void update() { + onFrustumChange(); + onViewPortChange(); + onFrameChange(); + } + + /** + * <code>getPlaneState</code> 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; + } + + /** + * <code>setPlaneState</code> sets the state to keep track of tested + * planes for culling. + * + * @param planeState the updated state. + */ + public void setPlaneState(int planeState) { + this.planeState = planeState; + } + + /** + * <code>getViewPortLeft</code> gets the left boundary of the viewport + * + * @return the left boundary of the viewport + */ + public float getViewPortLeft() { + return viewPortLeft; + } + + /** + * <code>setViewPortLeft</code> sets the left boundary of the viewport + * + * @param left the left boundary of the viewport + */ + public void setViewPortLeft(float left) { + viewPortLeft = left; + onViewPortChange(); + } + + /** + * <code>getViewPortRight</code> gets the right boundary of the viewport + * + * @return the right boundary of the viewport + */ + public float getViewPortRight() { + return viewPortRight; + } + + /** + * <code>setViewPortRight</code> sets the right boundary of the viewport + * + * @param right the right boundary of the viewport + */ + public void setViewPortRight(float right) { + viewPortRight = right; + onViewPortChange(); + } + + /** + * <code>getViewPortTop</code> gets the top boundary of the viewport + * + * @return the top boundary of the viewport + */ + public float getViewPortTop() { + return viewPortTop; + } + + /** + * <code>setViewPortTop</code> sets the top boundary of the viewport + * + * @param top the top boundary of the viewport + */ + public void setViewPortTop(float top) { + viewPortTop = top; + onViewPortChange(); + } + + /** + * <code>getViewPortBottom</code> gets the bottom boundary of the viewport + * + * @return the bottom boundary of the viewport + */ + public float getViewPortBottom() { + return viewPortBottom; + } + + /** + * <code>setViewPortBottom</code> sets the bottom boundary of the viewport + * + * @param bottom the bottom boundary of the viewport + */ + public void setViewPortBottom(float bottom) { + viewPortBottom = bottom; + onViewPortChange(); + } + + /** + * <code>setViewPort</code> 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 far plane to the point. + */ + public float distanceToNearPlane(Vector3f pos) { + return worldPlane[NEAR_PLANE].pseudoDistance(pos); + } + + /** + * <code>contains</code> tests a bounding volume against the planes of the + * camera's frustum. The frustums 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 bounding volume must be saved and restored, e.g: + * <code>BoundingVolume bv;<br/> + * Camera c;<br/> + * int planeState = bv.getPlaneState();<br/> + * bv.setPlaneState(0);<br/> + * c.contains(bv);<br/> + * bv.setPlaneState(plateState);<br/> + * </code> + * + * @param bound the bound to check for culling + * @return See enums in <code>FrustumIntersect</code> + */ + 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; + } + + /** + * <code>containsGui</code> 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) { + return guiBounding.intersects(bound); + } + + /** + * @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 + */ + public void setProjectionMatrix(Matrix4f projMatrix) { + projectionMatrixOverride = projMatrix; + updateViewProjection(); + } + + /** + * @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 (projectionMatrixOverride != null) { + return projectionMatrixOverride; + } + + return projectionMatrix; + } + + /** + * Updates the view projection matrix. + */ + public void updateViewProjection() { + if (projectionMatrixOverride != null) { + viewProjectionMatrix.set(projectionMatrixOverride).multLocal(viewMatrix); + } else { + //viewProjectionMatrix.set(viewMatrix).multLocal(projectionMatrix); + viewProjectionMatrix.set(projectionMatrix).multLocal(viewMatrix); + } + } + + /** + * @return The result of multiplying the projection matrix by the view + * matrix. This matrix is required for rendering an object. It is + * precomputed so as to not compute it every time an object is rendered. + */ + public Matrix4f getViewProjectionMatrix() { + return viewProjectionMatrix; + } + + /** + * @return True if 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. + */ + 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(new Vector3f(sx + xExtent, sy + yExtent, 0)); + guiBounding.setXExtent(xExtent); + guiBounding.setYExtent(yExtent); + guiBounding.setZExtent(Float.MAX_VALUE); + } + + /** + * <code>onFrustumChange</code> 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 effected by the frustum values + // update it as well + onFrameChange(); + } + + /** + * <code>onFrameChange</code> 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(); + } + + /** + * @return true if parallel projection is enable, false if in normal perspective mode + * @see #setParallelProjection(boolean) + */ + public boolean isParallelProjection() { + return this.parallelProjection; + } + + /** + * Enable/disable parallel projection. + * + * @param value true to set up this camera for parallel projection is enable, false to enter normal perspective mode + */ + public void setParallelProjection(final boolean value) { + this.parallelProjection = value; + onFrustumChange(); + } + + /** + * @see Camera#getWorldCoordinates + */ + public Vector3f getWorldCoordinates(Vector2f screenPos, float zPos) { + return getWorldCoordinates(screenPos, zPos, null); + } + + /** + * @see Camera#getWorldCoordinates + */ + public Vector3f getWorldCoordinates(Vector2f screenPosition, + float zPos, 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, + zPos * 2 - 1); + + float w = inverseMat.multProj(store, store); + store.multLocal(1f / w); + + return store; + } + + /** + * Converts the given position from world space to screen space. + * + * @see Camera#getScreenCoordinates + */ + public Vector3f getScreenCoordinates(Vector3f worldPos) { + return getScreenCoordinates(worldPos, null); + } + + /** + * Converts the given position from world space to screen space. + * + * @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; + } + + /** + * @return the width/resolution of the display. + */ + public int getWidth() { + return width; + } + + /** + * @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 + "]"; + } + + 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); + } + + public void read(JmeImporter e) throws IOException { + InputCapsule capsule = e.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(); + } +} |