diff options
author | Scott Barta <sbarta@google.com> | 2012-03-01 12:35:35 -0800 |
---|---|---|
committer | Scott Barta <sbarta@google.com> | 2012-03-01 12:40:08 -0800 |
commit | 59b2e6871c65f58fdad78cd7229c292f6a177578 (patch) | |
tree | 2d4e7bfc05b93f40b34675d77e403dd1c25efafd /engine/src/core/com/jme3/renderer | |
parent | f9b30489e75ac1eabc365064959804e99534f7ab (diff) | |
download | jmonkeyengine-59b2e6871c65f58fdad78cd7229c292f6a177578.tar.gz |
Adds the jMonkeyEngine library to the build.
Adds the jMonkeyEngine open source 3D game engine to the build. This
is built as a static library and is only used by the Finsky client.
Change-Id: I06a3f054df7b8a67757267d884854f70c5a16ca0
Diffstat (limited to 'engine/src/core/com/jme3/renderer')
18 files changed, 5324 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(); + } +} diff --git a/engine/src/core/com/jme3/renderer/Caps.java b/engine/src/core/com/jme3/renderer/Caps.java new file mode 100644 index 0000000..4dd1ed8 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/Caps.java @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2009-2012 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.shader.Shader; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.FrameBuffer.RenderBuffer; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; +import java.util.Collection; + +/** + * <code>Caps</code> is an enum specifying a capability that the {@link Renderer} + * supports. + * + * @author Kirill Vainer + */ +public enum Caps { + + /** + * Supports {@link FrameBuffer FrameBuffers}. + * <p> + * OpenGL: Renderer exposes the GL_EXT_framebuffer_object extension.<br> + * OpenGL ES: Renderer supports OpenGL ES 2.0. + */ + FrameBuffer, + + /** + * Supports framebuffer Multiple Render Targets (MRT) + * <p> + * OpenGL: Renderer exposes the GL_ARB_draw_buffers extension + */ + FrameBufferMRT, + + /** + * Supports framebuffer multi-sampling + * <p> + * OpenGL: Renderer exposes the GL EXT framebuffer multisample extension<br> + * OpenGL ES: Renderer exposes GL_APPLE_framebuffer_multisample or + * GL_ANGLE_framebuffer_multisample. + */ + FrameBufferMultisample, + + /** + * Supports texture multi-sampling + * <p> + * OpenGL: Renderer exposes the GL_ARB_texture_multisample extension<br> + * OpenGL ES: Renderer exposes the GL_IMG_multisampled_render_to_texture + * extension. + */ + TextureMultisample, + + /** + * Supports OpenGL 2.0 or OpenGL ES 2.0. + */ + OpenGL20, + + /** + * Supports OpenGL 2.1 + */ + OpenGL21, + + /** + * Supports OpenGL 3.0 + */ + OpenGL30, + + /** + * Supports OpenGL 3.1 + */ + OpenGL31, + + /** + * Supports OpenGL 3.2 + */ + OpenGL32, + + /** + * Supports OpenGL ARB program. + * <p> + * OpenGL: Renderer exposes ARB_vertex_program and ARB_fragment_program + * extensions. + */ + ARBprogram, + + /** + * Supports GLSL 1.0 + */ + GLSL100, + + /** + * Supports GLSL 1.1 + */ + GLSL110, + + /** + * Supports GLSL 1.2 + */ + GLSL120, + + /** + * Supports GLSL 1.3 + */ + GLSL130, + + /** + * Supports GLSL 1.4 + */ + GLSL140, + + /** + * Supports GLSL 1.5 + */ + GLSL150, + + /** + * Supports GLSL 3.3 + */ + GLSL330, + + /** + * Supports reading from textures inside the vertex shader. + */ + VertexTextureFetch, + + /** + * Supports geometry shader. + */ + GeometryShader, + + /** + * Supports texture arrays + */ + TextureArray, + + /** + * Supports texture buffers + */ + TextureBuffer, + + /** + * Supports floating point textures (Format.RGB16F) + */ + FloatTexture, + + /** + * Supports floating point FBO color buffers (Format.RGB16F) + */ + FloatColorBuffer, + + /** + * Supports floating point depth buffer + */ + FloatDepthBuffer, + + /** + * Supports Format.RGB111110F for textures + */ + PackedFloatTexture, + + /** + * Supports Format.RGB9E5 for textures + */ + SharedExponentTexture, + + /** + * Supports Format.RGB111110F for FBO color buffers + */ + PackedFloatColorBuffer, + + /** + * Supports Format.RGB9E5 for FBO color buffers + */ + SharedExponentColorBuffer, + + /** + * Supports Format.LATC for textures, this includes + * support for ATI's 3Dc texture compression. + */ + TextureCompressionLATC, + + /** + * Supports Non-Power-Of-Two (NPOT) textures and framebuffers + */ + NonPowerOfTwoTextures, + + /// Vertex Buffer features + MeshInstancing, + + /** + * Supports VAO, or vertex buffer arrays + */ + VertexBufferArray, + + /** + * Supports multisampling on the screen + */ + Multisample; + + /** + * Returns true if given the renderer capabilities, the texture + * can be supported by the renderer. + * <p> + * This only checks the format of the texture, non-power-of-2 + * textures are scaled automatically inside the renderer + * if are not supported natively. + * + * @param caps The collection of renderer capabilities {@link Renderer#getCaps() }. + * @param tex The texture to check + * @return True if it is supported, false otherwise. + */ + public static boolean supports(Collection<Caps> caps, Texture tex){ + if (tex.getType() == Texture.Type.TwoDimensionalArray + && !caps.contains(Caps.TextureArray)) + return false; + + Image img = tex.getImage(); + if (img == null) + return true; + + Format fmt = img.getFormat(); + switch (fmt){ + case Depth32F: + return caps.contains(Caps.FloatDepthBuffer); + case LATC: + return caps.contains(Caps.TextureCompressionLATC); + case RGB16F_to_RGB111110F: + case RGB111110F: + return caps.contains(Caps.PackedFloatTexture); + case RGB16F_to_RGB9E5: + case RGB9E5: + return caps.contains(Caps.SharedExponentTexture); + default: + if (fmt.isFloatingPont()) + return caps.contains(Caps.FloatTexture); + + return true; + } + } + + /** + * Returns true if given the renderer capabilities, the framebuffer + * can be supported by the renderer. + * + * @param caps The collection of renderer capabilities {@link Renderer#getCaps() }. + * @param fb The framebuffer to check + * @return True if it is supported, false otherwise. + */ + public static boolean supports(Collection<Caps> caps, FrameBuffer fb){ + if (!caps.contains(Caps.FrameBuffer)) + return false; + + if (fb.getSamples() > 1 + && !caps.contains(Caps.FrameBufferMultisample)) + return false; + + RenderBuffer colorBuf = fb.getColorBuffer(); + RenderBuffer depthBuf = fb.getDepthBuffer(); + + if (depthBuf != null){ + Format depthFmt = depthBuf.getFormat(); + if (!depthFmt.isDepthFormat()){ + return false; + }else{ + if (depthFmt == Format.Depth32F + && !caps.contains(Caps.FloatDepthBuffer)) + return false; + } + } + if (colorBuf != null){ + Format colorFmt = colorBuf.getFormat(); + if (colorFmt.isDepthFormat()) + return false; + + if (colorFmt.isCompressed()) + return false; + + switch (colorFmt){ + case RGB111110F: + return caps.contains(Caps.PackedFloatColorBuffer); + case RGB16F_to_RGB111110F: + case RGB16F_to_RGB9E5: + case RGB9E5: + return false; + default: + if (colorFmt.isFloatingPont()) + return caps.contains(Caps.FloatColorBuffer); + + return true; + } + } + return true; + } + + /** + * Returns true if given the renderer capabilities, the shader + * can be supported by the renderer. + * + * @param caps The collection of renderer capabilities {@link Renderer#getCaps() }. + * @param shader The shader to check + * @return True if it is supported, false otherwise. + */ + public static boolean supports(Collection<Caps> caps, Shader shader){ + String lang = shader.getLanguage(); + if (lang.startsWith("GLSL")){ + int ver = Integer.parseInt(lang.substring(4)); + switch (ver){ + case 100: + return caps.contains(Caps.GLSL100); + case 110: + return caps.contains(Caps.GLSL110); + case 120: + return caps.contains(Caps.GLSL120); + case 130: + return caps.contains(Caps.GLSL130); + case 140: + return caps.contains(Caps.GLSL140); + case 150: + return caps.contains(Caps.GLSL150); + case 330: + return caps.contains(Caps.GLSL330); + default: + return false; + } + } + return false; + } + +} diff --git a/engine/src/core/com/jme3/renderer/GL1Renderer.java b/engine/src/core/com/jme3/renderer/GL1Renderer.java new file mode 100644 index 0000000..78df0c6 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/GL1Renderer.java @@ -0,0 +1,26 @@ +package com.jme3.renderer; + +import com.jme3.material.FixedFuncBinding; + +/** + * Renderer sub-interface that is used for non-shader based renderers. + * <p> + * The <code>GL1Renderer</code> provides a single call, + * {@link #setFixedFuncBinding(com.jme3.material.FixedFuncBinding, java.lang.Object) } + * which allows to set fixed functionality state. + * + * @author Kirill Vainer + */ +public interface GL1Renderer extends Renderer { + + /** + * Set the fixed functionality state. + * <p> + * See {@link FixedFuncBinding} for various values that + * can be set. + * + * @param ffBinding The binding to set + * @param val The value + */ + public void setFixedFuncBinding(FixedFuncBinding ffBinding, Object val); +} diff --git a/engine/src/core/com/jme3/renderer/IDList.java b/engine/src/core/com/jme3/renderer/IDList.java new file mode 100644 index 0000000..2db7294 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/IDList.java @@ -0,0 +1,121 @@ +/* + * 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 java.util.Arrays; + +/** + * A specialized data-structure used to optimize state changes of "slot" + * based state. + */ +public class IDList { + + public int[] newList = new int[16]; + public int[] oldList = new int[16]; + public int newLen = 0; + public int oldLen = 0; + + /** + * Reset all states to zero + */ + public void reset(){ + newLen = 0; + oldLen = 0; + Arrays.fill(newList, 0); + Arrays.fill(oldList, 0); + } + + /** + * Adds an index to the new list. + * If the index was not in the old list, false is returned, + * if the index was in the old list, it is removed from the old + * list and true is returned. + * + * @param idx The index to move + * @return True if it existed in old list and was removed + * from there, false otherwise. + */ + public boolean moveToNew(int idx){ + if (newLen == 0 || newList[newLen-1] != idx) + // add item to newList first + newList[newLen++] = idx; + + // find idx in oldList, if removed successfuly, return true. + for (int i = 0; i < oldLen; i++){ + if (oldList[i] == idx){ + // found index in slot i + // delete index from old list + oldLen --; + for (int j = i; j < oldLen; j++){ + oldList[j] = oldList[j+1]; + } + return true; + } + } + return false; + } + + /** + * Copies the new list to the old list, and clears the new list. + */ + public void copyNewToOld(){ + System.arraycopy(newList, 0, oldList, 0, newLen); + oldLen = newLen; + newLen = 0; + } + + /** + * Prints the contents of the lists + */ + public void print(){ + if (newLen > 0){ + System.out.print("New List: "); + for (int i = 0; i < newLen; i++){ + if (i == newLen -1) + System.out.println(newList[i]); + else + System.out.print(newList[i]+", "); + } + } + if (oldLen > 0){ + System.out.print("Old List: "); + for (int i = 0; i < oldLen; i++){ + if (i == oldLen -1) + System.out.println(oldList[i]); + else + System.out.print(oldList[i]+", "); + } + } + } + +} diff --git a/engine/src/core/com/jme3/renderer/RenderContext.java b/engine/src/core/com/jme3/renderer/RenderContext.java new file mode 100644 index 0000000..ee9ff9c --- /dev/null +++ b/engine/src/core/com/jme3/renderer/RenderContext.java @@ -0,0 +1,319 @@ +/* + * 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.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; + +/** + * Represents the current state of the graphics library. This class is used + * internally to reduce state changes. NOTE: This class is specific to OpenGL. + */ +public class RenderContext { + + /** + * @see RenderState#setFaceCullMode(com.jme3.material.RenderState.FaceCullMode) + */ + public RenderState.FaceCullMode cullMode = RenderState.FaceCullMode.Off; + + /** + * @see RenderState#setDepthTest(boolean) + */ + public boolean depthTestEnabled = false; + + /** + * @see RenderState#setAlphaTest(boolean) + */ + public boolean alphaTestEnabled = false; + + /** + * @see RenderState#setDepthWrite(boolean) + */ + public boolean depthWriteEnabled = true; + + /** + * @see RenderState#setColorWrite(boolean) + */ + public boolean colorWriteEnabled = true; + + /** + * @see Renderer#setClipRect(int, int, int, int) + */ + public boolean clipRectEnabled = false; + + /** + * @see RenderState#setPolyOffset(float, float) + */ + public boolean polyOffsetEnabled = false; + + /** + * @see RenderState#setPolyOffset(float, float) + */ + public float polyOffsetFactor = 0; + + /** + * @see RenderState#setPolyOffset(float, float) + */ + public float polyOffsetUnits = 0; + + /** + * For normals only. Uses GL_NORMALIZE. + * + * @see VertexBuffer#setNormalized(boolean) + */ + public boolean normalizeEnabled = false; + + /** + * For glMatrixMode. + * + * @see Renderer#setWorldMatrix(com.jme3.math.Matrix4f) + * @see Renderer#setViewProjectionMatrices(com.jme3.math.Matrix4f, com.jme3.math.Matrix4f) + */ + public int matrixMode = -1; + + /** + * @see Mesh#setPointSize(float) + */ + public float pointSize = 1; + + /** + * @see Mesh#setLineWidth(float) + */ + public float lineWidth = 1; + + /** + * @see RenderState#setBlendMode(com.jme3.material.RenderState.BlendMode) + */ + public RenderState.BlendMode blendMode = RenderState.BlendMode.Off; + + /** + * @see RenderState#setWireframe(boolean) + */ + public boolean wireframe = false; + + /** + * @see RenderState#setPointSprite(boolean) + */ + public boolean pointSprite = false; + + /** + * @see Renderer#setShader(com.jme3.shader.Shader) + */ + public int boundShaderProgram; + + /** + * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) + */ + public int boundFBO = 0; + + /** + * Currently bound Renderbuffer + * + * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) + */ + public int boundRB = 0; + + /** + * Currently bound draw buffer + * -2 = GL_NONE + * -1 = GL_BACK + * 0 = GL_COLOR_ATTACHMENT0 + * n = GL_COLOR_ATTACHMENTn + * where n is an integer greater than 1 + * + * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) + * @see FrameBuffer#setTargetIndex(int) + */ + public int boundDrawBuf = -1; + + /** + * Currently bound read buffer + * + * @see RenderContext#boundDrawBuf + * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) + * @see FrameBuffer#setTargetIndex(int) + */ + public int boundReadBuf = -1; + + /** + * Currently bound element array vertex buffer. + * + * @see Renderer#renderMesh(com.jme3.scene.Mesh, int, int) + */ + public int boundElementArrayVBO; + + /** + * @see Renderer#renderMesh(com.jme3.scene.Mesh, int, int) + */ + public int boundVertexArray; + + /** + * Currently bound array vertex buffer. + * + * @see Renderer#renderMesh(com.jme3.scene.Mesh, int, int) + */ + public int boundArrayVBO; + + public int numTexturesSet = 0; + + /** + * Current bound texture IDs for each texture unit. + * + * @see Renderer#setTexture(int, com.jme3.texture.Texture) + */ + public Image[] boundTextures = new Image[16]; + + /** + * IDList for texture units + * + * @see Renderer#setTexture(int, com.jme3.texture.Texture) + */ + public IDList textureIndexList = new IDList(); + + /** + * Currently bound texture unit + * + * @see Renderer#setTexture(int, com.jme3.texture.Texture) + */ + public int boundTextureUnit = 0; + + /** + * Stencil Buffer state + */ + public boolean stencilTest = false; + public RenderState.StencilOperation frontStencilStencilFailOperation = RenderState.StencilOperation.Keep; + public RenderState.StencilOperation frontStencilDepthFailOperation = RenderState.StencilOperation.Keep; + public RenderState.StencilOperation frontStencilDepthPassOperation = RenderState.StencilOperation.Keep; + public RenderState.StencilOperation backStencilStencilFailOperation = RenderState.StencilOperation.Keep; + public RenderState.StencilOperation backStencilDepthFailOperation = RenderState.StencilOperation.Keep; + public RenderState.StencilOperation backStencilDepthPassOperation = RenderState.StencilOperation.Keep; + public RenderState.TestFunction frontStencilFunction = RenderState.TestFunction.Always; + public RenderState.TestFunction backStencilFunction = RenderState.TestFunction.Always; + + /** + * Vertex attribs currently bound and enabled. If a slot is null, then + * it is disabled. + */ + public VertexBuffer[] boundAttribs = new VertexBuffer[16]; + + /** + * IDList for vertex attributes + */ + public IDList attribIndexList = new IDList(); + + /** + * Ambient color (GL1 only) + */ + public ColorRGBA ambient; + + /** + * Diffuse color (GL1 only) + */ + public ColorRGBA diffuse; + + /** + * Specular color (GL1 only) + */ + public ColorRGBA specular; + + /** + * Material color (GL1 only) + */ + public ColorRGBA color; + + /** + * Shininess (GL1 only) + */ + public float shininess; + + /** + * Use vertex color (GL1 only) + */ + public boolean useVertexColor; + + /** + * Reset the RenderContext to default GL state + */ + public void reset(){ + cullMode = RenderState.FaceCullMode.Off; + depthTestEnabled = false; + alphaTestEnabled = false; + depthWriteEnabled = false; + colorWriteEnabled = false; + clipRectEnabled = false; + polyOffsetEnabled = false; + polyOffsetFactor = 0; + polyOffsetUnits = 0; + normalizeEnabled = false; + matrixMode = -1; + pointSize = 1; + blendMode = RenderState.BlendMode.Off; + wireframe = false; + boundShaderProgram = 0; + boundFBO = 0; + boundRB = 0; + boundDrawBuf = -1; + boundReadBuf = -1; + boundElementArrayVBO = 0; + boundVertexArray = 0; + boundArrayVBO = 0; + numTexturesSet = 0; + for (int i = 0; i < boundTextures.length; i++) + boundTextures[i] = null; + + textureIndexList.reset(); + boundTextureUnit = 0; + for (int i = 0; i < boundAttribs.length; i++) + boundAttribs[i] = null; + + attribIndexList.reset(); + + stencilTest = false; + frontStencilStencilFailOperation = RenderState.StencilOperation.Keep; + frontStencilDepthFailOperation = RenderState.StencilOperation.Keep; + frontStencilDepthPassOperation = RenderState.StencilOperation.Keep; + backStencilStencilFailOperation = RenderState.StencilOperation.Keep; + backStencilDepthFailOperation = RenderState.StencilOperation.Keep; + backStencilDepthPassOperation = RenderState.StencilOperation.Keep; + frontStencilFunction = RenderState.TestFunction.Always; + backStencilFunction = RenderState.TestFunction.Always; + + ambient = diffuse = specular = color = null; + shininess = 0; + useVertexColor = false; + } +} diff --git a/engine/src/core/com/jme3/renderer/RenderManager.java b/engine/src/core/com/jme3/renderer/RenderManager.java new file mode 100644 index 0000000..1d58d22 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/RenderManager.java @@ -0,0 +1,1170 @@ +/* + * Copyright (c) 2009-2012 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.material.Material; +import com.jme3.material.MaterialDef; +import com.jme3.material.RenderState; +import com.jme3.material.Technique; +import com.jme3.math.*; +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.queue.GeometryList; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.*; +import com.jme3.shader.Uniform; +import com.jme3.shader.UniformBinding; +import com.jme3.shader.VarType; +import com.jme3.system.NullRenderer; +import com.jme3.system.Timer; +import com.jme3.util.IntMap.Entry; +import com.jme3.util.TempVars; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.logging.Logger; + +/** + * <code>RenderManager</code> is a high-level rendering interface that is + * above the Renderer implementation. RenderManager takes care + * of rendering the scene graphs attached to each viewport and + * handling SceneProcessors. + * + * @see SceneProcessor + * @see ViewPort + * @see Spatial + */ +public class RenderManager { + + private static final Logger logger = Logger.getLogger(RenderManager.class.getName()); + + private Renderer renderer; + private Timer timer; + private ArrayList<ViewPort> preViewPorts = new ArrayList<ViewPort>(); + private ArrayList<ViewPort> viewPorts = new ArrayList<ViewPort>(); + private ArrayList<ViewPort> postViewPorts = new ArrayList<ViewPort>(); + private Camera prevCam = null; + private Material forcedMaterial = null; + private String forcedTechnique = null; + private RenderState forcedRenderState = null; + private boolean shader; + private int viewX, viewY, viewWidth, viewHeight; + private float near, far; + private Matrix4f orthoMatrix = new Matrix4f(); + private Matrix4f viewMatrix = new Matrix4f(); + private Matrix4f projMatrix = new Matrix4f(); + private Matrix4f viewProjMatrix = new Matrix4f(); + private Matrix4f worldMatrix = new Matrix4f(); + private Vector3f camUp = new Vector3f(), + camLeft = new Vector3f(), + camDir = new Vector3f(), + camLoc = new Vector3f(); + //temp technique + private String tmpTech; + private boolean handleTranlucentBucket = true; + + /** + * Create a high-level rendering interface over the + * low-level rendering interface. + * @param renderer + */ + public RenderManager(Renderer renderer) { + this.renderer = renderer; + //this.shader = renderer.getCaps().contains(Caps.GLSL100); + } + + /** + * Returns the pre ViewPort with the given name. + * + * @param viewName The name of the pre ViewPort to look up + * @return The ViewPort, or null if not found. + * + * @see #createPreView(java.lang.String, com.jme3.renderer.Camera) + */ + public ViewPort getPreView(String viewName) { + for (int i = 0; i < preViewPorts.size(); i++) { + if (preViewPorts.get(i).getName().equals(viewName)) { + return preViewPorts.get(i); + } + } + return null; + } + + /** + * Removes the specified pre ViewPort. + * + * @param view The pre ViewPort to remove + * @return True if the ViewPort was removed successfully. + * + * @see #createPreView(java.lang.String, com.jme3.renderer.Camera) + */ + public boolean removePreView(ViewPort view) { + return preViewPorts.remove(view); + } + + /** + * Returns the main ViewPort with the given name. + * + * @param viewName The name of the main ViewPort to look up + * @return The ViewPort, or null if not found. + * + * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) + */ + public ViewPort getMainView(String viewName) { + for (int i = 0; i < viewPorts.size(); i++) { + if (viewPorts.get(i).getName().equals(viewName)) { + return viewPorts.get(i); + } + } + return null; + } + + /** + * Removes the main ViewPort with the specified name. + * + * @param viewName The main ViewPort name to remove + * @return True if the ViewPort was removed successfully. + * + * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) + */ + public boolean removeMainView(String viewName) { + for (int i = 0; i < viewPorts.size(); i++) { + if (viewPorts.get(i).getName().equals(viewName)) { + viewPorts.remove(i); + return true; + } + } + return false; + } + + /** + * Removes the specified main ViewPort. + * + * @param view The main ViewPort to remove + * @return True if the ViewPort was removed successfully. + * + * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) + */ + public boolean removeMainView(ViewPort view) { + return viewPorts.remove(view); + } + + /** + * Returns the post ViewPort with the given name. + * + * @param viewName The name of the post ViewPort to look up + * @return The ViewPort, or null if not found. + * + * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) + */ + public ViewPort getPostView(String viewName) { + for (int i = 0; i < postViewPorts.size(); i++) { + if (postViewPorts.get(i).getName().equals(viewName)) { + return postViewPorts.get(i); + } + } + return null; + } + + /** + * Removes the post ViewPort with the specified name. + * + * @param viewName The post ViewPort name to remove + * @return True if the ViewPort was removed successfully. + * + * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) + */ + public boolean removePostView(String viewName) { + for (int i = 0; i < postViewPorts.size(); i++) { + if (postViewPorts.get(i).getName().equals(viewName)) { + postViewPorts.remove(i); + + return true; + } + } + return false; + } + + /** + * Removes the specified post ViewPort. + * + * @param view The post ViewPort to remove + * @return True if the ViewPort was removed successfully. + * + * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) + */ + public boolean removePostView(ViewPort view) { + return postViewPorts.remove(view); + } + + /** + * Returns a read-only list of all pre ViewPorts + * @return a read-only list of all pre ViewPorts + * @see #createPreView(java.lang.String, com.jme3.renderer.Camera) + */ + public List<ViewPort> getPreViews() { + return Collections.unmodifiableList(preViewPorts); + } + + /** + * Returns a read-only list of all main ViewPorts + * @return a read-only list of all main ViewPorts + * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) + */ + public List<ViewPort> getMainViews() { + return Collections.unmodifiableList(viewPorts); + } + + /** + * Returns a read-only list of all post ViewPorts + * @return a read-only list of all post ViewPorts + * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) + */ + public List<ViewPort> getPostViews() { + return Collections.unmodifiableList(postViewPorts); + } + + /** + * Creates a new pre ViewPort, to display the given camera's content. + * <p> + * The view will be processed before the main and post viewports. + */ + public ViewPort createPreView(String viewName, Camera cam) { + ViewPort vp = new ViewPort(viewName, cam); + preViewPorts.add(vp); + return vp; + } + + /** + * Creates a new main ViewPort, to display the given camera's content. + * <p> + * The view will be processed before the post viewports but after + * the pre viewports. + */ + public ViewPort createMainView(String viewName, Camera cam) { + ViewPort vp = new ViewPort(viewName, cam); + viewPorts.add(vp); + return vp; + } + + /** + * Creates a new post ViewPort, to display the given camera's content. + * <p> + * The view will be processed after the pre and main viewports. + */ + public ViewPort createPostView(String viewName, Camera cam) { + ViewPort vp = new ViewPort(viewName, cam); + postViewPorts.add(vp); + return vp; + } + + private void notifyReshape(ViewPort vp, int w, int h) { + List<SceneProcessor> processors = vp.getProcessors(); + for (SceneProcessor proc : processors) { + if (!proc.isInitialized()) { + proc.initialize(this, vp); + } else { + proc.reshape(vp, w, h); + } + } + } + + /** + * Internal use only. + * Updates the resolution of all on-screen cameras to match + * the given width and height. + */ + public void notifyReshape(int w, int h) { + for (ViewPort vp : preViewPorts) { + if (vp.getOutputFrameBuffer() == null) { + Camera cam = vp.getCamera(); + cam.resize(w, h, true); + } + notifyReshape(vp, w, h); + } + for (ViewPort vp : viewPorts) { + if (vp.getOutputFrameBuffer() == null) { + Camera cam = vp.getCamera(); + cam.resize(w, h, true); + } + notifyReshape(vp, w, h); + } + for (ViewPort vp : postViewPorts) { + if (vp.getOutputFrameBuffer() == null) { + Camera cam = vp.getCamera(); + cam.resize(w, h, true); + } + notifyReshape(vp, w, h); + } + } + + /** + * Internal use only. + * Updates the given list of uniforms with {@link UniformBinding uniform bindings} + * based on the current world state. + */ + public void updateUniformBindings(List<Uniform> params) { + // assums worldMatrix is properly set. + TempVars vars = TempVars.get(); + + Matrix4f tempMat4 = vars.tempMat4; + Matrix3f tempMat3 = vars.tempMat3; + Vector2f tempVec2 = vars.vect2d; + Quaternion tempVec4 = vars.quat1; + + for (int i = 0; i < params.size(); i++) { + Uniform u = params.get(i); + switch (u.getBinding()) { + case WorldMatrix: + u.setValue(VarType.Matrix4, worldMatrix); + break; + case ViewMatrix: + u.setValue(VarType.Matrix4, viewMatrix); + break; + case ProjectionMatrix: + u.setValue(VarType.Matrix4, projMatrix); + break; + case ViewProjectionMatrix: + u.setValue(VarType.Matrix4, viewProjMatrix); + break; + case WorldViewMatrix: + tempMat4.set(viewMatrix); + tempMat4.multLocal(worldMatrix); + u.setValue(VarType.Matrix4, tempMat4); + break; + case NormalMatrix: + tempMat4.set(viewMatrix); + tempMat4.multLocal(worldMatrix); + tempMat4.toRotationMatrix(tempMat3); + tempMat3.invertLocal(); + tempMat3.transposeLocal(); + u.setValue(VarType.Matrix3, tempMat3); + break; + case WorldViewProjectionMatrix: + tempMat4.set(viewProjMatrix); + tempMat4.multLocal(worldMatrix); + u.setValue(VarType.Matrix4, tempMat4); + break; + case WorldMatrixInverse: + tempMat4.multLocal(worldMatrix); + tempMat4.invertLocal(); + u.setValue(VarType.Matrix4, tempMat4); + break; + case ViewMatrixInverse: + tempMat4.set(viewMatrix); + tempMat4.invertLocal(); + u.setValue(VarType.Matrix4, tempMat4); + break; + case ProjectionMatrixInverse: + tempMat4.set(projMatrix); + tempMat4.invertLocal(); + u.setValue(VarType.Matrix4, tempMat4); + break; + case ViewProjectionMatrixInverse: + tempMat4.set(viewProjMatrix); + tempMat4.invertLocal(); + u.setValue(VarType.Matrix4, tempMat4); + break; + case WorldViewMatrixInverse: + tempMat4.set(viewMatrix); + tempMat4.multLocal(worldMatrix); + tempMat4.invertLocal(); + u.setValue(VarType.Matrix4, tempMat4); + break; + case NormalMatrixInverse: + tempMat4.set(viewMatrix); + tempMat4.multLocal(worldMatrix); + tempMat4.toRotationMatrix(tempMat3); + tempMat3.invertLocal(); + tempMat3.transposeLocal(); + tempMat3.invertLocal(); + u.setValue(VarType.Matrix3, tempMat3); + break; + case WorldViewProjectionMatrixInverse: + tempMat4.set(viewProjMatrix); + tempMat4.multLocal(worldMatrix); + tempMat4.invertLocal(); + u.setValue(VarType.Matrix4, tempMat4); + break; + case ViewPort: + tempVec4.set(viewX, viewY, viewWidth, viewHeight); + u.setValue(VarType.Vector4, tempVec4); + break; + case Resolution: + tempVec2.set(viewWidth, viewHeight); + u.setValue(VarType.Vector2, tempVec2); + break; + case Aspect: + float aspect = ((float) viewWidth) / viewHeight; + u.setValue(VarType.Float, aspect); + break; + case FrustumNearFar: + tempVec2.set(near, far); + u.setValue(VarType.Vector2, tempVec2); + break; + case CameraPosition: + u.setValue(VarType.Vector3, camLoc); + break; + case CameraDirection: + u.setValue(VarType.Vector3, camDir); + break; + case CameraLeft: + u.setValue(VarType.Vector3, camLeft); + break; + case CameraUp: + u.setValue(VarType.Vector3, camUp); + break; + case Time: + u.setValue(VarType.Float, timer.getTimeInSeconds()); + break; + case Tpf: + u.setValue(VarType.Float, timer.getTimePerFrame()); + break; + case FrameRate: + u.setValue(VarType.Float, timer.getFrameRate()); + break; + } + } + + vars.release(); + } + + /** + * Set the material to use to render all future objects. + * This overrides the material set on the geometry and renders + * with the provided material instead. + * Use null to clear the material and return renderer to normal + * functionality. + * @param mat The forced material to set, or null to return to normal + */ + public void setForcedMaterial(Material mat) { + forcedMaterial = mat; + } + + /** + * Returns the forced render state previously set with + * {@link #setForcedRenderState(com.jme3.material.RenderState) }. + * @return the forced render state + */ + public RenderState getForcedRenderState() { + return forcedRenderState; + } + + /** + * Set the render state to use for all future objects. + * This overrides the render state set on the material and instead + * forces this render state to be applied for all future materials + * rendered. Set to null to return to normal functionality. + * + * @param forcedRenderState The forced render state to set, or null + * to return to normal + */ + public void setForcedRenderState(RenderState forcedRenderState) { + this.forcedRenderState = forcedRenderState; + } + + /** + * Set the timer that should be used to query the time based + * {@link UniformBinding}s for material world parameters. + * + * @param timer The timer to query time world parameters + */ + public void setTimer(Timer timer) { + this.timer = timer; + } + + /** + * Returns the forced technique name set. + * + * @return the forced technique name set. + * + * @see #setForcedTechnique(java.lang.String) + */ + public String getForcedTechnique() { + return forcedTechnique; + } + + /** + * Sets the forced technique to use when rendering geometries. + * <p> + * If the specified technique name is available on the geometry's + * material, then it is used, otherwise, the + * {@link #setForcedMaterial(com.jme3.material.Material) forced material} is used. + * If a forced material is not set and the forced technique name cannot + * be found on the material, the geometry will <em>not</em> be rendered. + * + * @param forcedTechnique The forced technique name to use, set to null + * to return to normal functionality. + * + * @see #renderGeometry(com.jme3.scene.Geometry) + */ + public void setForcedTechnique(String forcedTechnique) { + this.forcedTechnique = forcedTechnique; + } + + /** + * Enable or disable alpha-to-coverage. + * <p> + * When alpha to coverage is enabled and the renderer implementation + * supports it, then alpha blending will be replaced with alpha dissolve + * if multi-sampling is also set on the renderer. + * This feature allows avoiding of alpha blending artifacts due to + * lack of triangle-level back-to-front sorting. + * + * @param value True to enable alpha-to-coverage, false otherwise. + */ + public void setAlphaToCoverage(boolean value) { + renderer.setAlphaToCoverage(value); + } + + /** + * True if the translucent bucket should automatically be rendered + * by the RenderManager. + * + * @return Whether or not the translucent bucket is rendered. + * + * @see #setHandleTranslucentBucket(boolean) + */ + public boolean isHandleTranslucentBucket() { + return handleTranlucentBucket; + } + + /** + * Enable or disable rendering of the + * {@link Bucket#Translucent translucent bucket} + * by the RenderManager. The default is enabled. + * + * @param handleTranslucentBucket Whether or not the translucent bucket should + * be rendered. + */ + public void setHandleTranslucentBucket(boolean handleTranslucentBucket) { + this.handleTranlucentBucket = handleTranslucentBucket; + } + + /** + * Internal use only. Sets the world matrix to use for future + * rendering. This has no effect unless objects are rendered manually + * using {@link Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager) }. + * Using {@link #renderGeometry(com.jme3.scene.Geometry) } will + * override this value. + * + * @param mat The world matrix to set + */ + public void setWorldMatrix(Matrix4f mat) { + if (shader) { + worldMatrix.set(mat); + } else { + renderer.setWorldMatrix(mat); + } + } + + /** + * Renders the given geometry. + * <p> + * First the proper world matrix is set, if + * the geometry's {@link Geometry#setIgnoreTransform(boolean) ignore transform} + * feature is enabled, the identity world matrix is used, otherwise, the + * geometry's {@link Geometry#getWorldMatrix() world transform matrix} is used. + * <p> + * Once the world matrix is applied, the proper material is chosen for rendering. + * If a {@link #setForcedMaterial(com.jme3.material.Material) forced material} is + * set on this RenderManager, then it is used for rendering the geometry, + * otherwise, the {@link Geometry#getMaterial() geometry's material} is used. + * <p> + * If a {@link #setForcedTechnique(java.lang.String) forced technique} is + * set on this RenderManager, then it is selected automatically + * on the geometry's material and is used for rendering. Otherwise, one + * of the {@link MaterialDef#getDefaultTechniques() default techniques} is + * used. + * <p> + * If a {@link #setForcedRenderState(com.jme3.material.RenderState) forced + * render state} is set on this RenderManager, then it is used + * for rendering the material, and the material's own render state is ignored. + * Otherwise, the material's render state is used as intended. + * + * @param g The geometry to render + * + * @see Technique + * @see RenderState + * @see Material#selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) + * @see Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager) + */ + public void renderGeometry(Geometry g) { + if (g.isIgnoreTransform()) { + setWorldMatrix(Matrix4f.IDENTITY); + } else { + setWorldMatrix(g.getWorldMatrix()); + } + + //if forcedTechnique we try to force it for render, + //if it does not exists in the mat def, we check for forcedMaterial and render the geom if not null + //else the geom is not rendered + if (forcedTechnique != null) { + if (g.getMaterial().getMaterialDef().getTechniqueDef(forcedTechnique) != null) { + tmpTech = g.getMaterial().getActiveTechnique() != null ? g.getMaterial().getActiveTechnique().getDef().getName() : "Default"; + g.getMaterial().selectTechnique(forcedTechnique, this); + // use geometry's material + g.getMaterial().render(g, this); + g.getMaterial().selectTechnique(tmpTech, this); + //Reverted this part from revision 6197 + //If forcedTechnique does not exists, and frocedMaterial is not set, the geom MUST NOT be rendered + } else if (forcedMaterial != null) { + // use forced material + forcedMaterial.render(g, this); + } + } else if (forcedMaterial != null) { + // use forced material + forcedMaterial.render(g, this); + } else { + g.getMaterial().render(g, this); + } + } + + /** + * Renders the given GeometryList. + * <p> + * For every geometry in the list, the + * {@link #renderGeometry(com.jme3.scene.Geometry) } method is called. + * + * @param gl The geometry list to render. + * + * @see GeometryList + * @see #renderGeometry(com.jme3.scene.Geometry) + */ + public void renderGeometryList(GeometryList gl) { + for (int i = 0; i < gl.size(); i++) { + renderGeometry(gl.get(i)); + } + } + + /** + * If a spatial is not inside the eye frustum, it + * is still rendered in the shadow frustum (shadow casting queue) + * through this recursive method. + */ + private void renderShadow(Spatial s, RenderQueue rq) { + if (s instanceof Node) { + Node n = (Node) s; + List<Spatial> children = n.getChildren(); + for (int i = 0; i < children.size(); i++) { + renderShadow(children.get(i), rq); + } + } else if (s instanceof Geometry) { + Geometry gm = (Geometry) s; + + RenderQueue.ShadowMode shadowMode = s.getShadowMode(); + if (shadowMode != RenderQueue.ShadowMode.Off && shadowMode != RenderQueue.ShadowMode.Receive) { + //forcing adding to shadow cast mode, culled objects doesn't have to be in the receiver queue + rq.addToShadowQueue(gm, RenderQueue.ShadowMode.Cast); + } + } + } + + /** + * Preloads a scene for rendering. + * <p> + * After invocation of this method, the underlying + * renderer would have uploaded any textures, shaders and meshes + * used by the given scene to the video driver. + * Using this method is useful when wishing to avoid the initial pause + * when rendering a scene for the first time. Note that it is not + * guaranteed that the underlying renderer will actually choose to upload + * the data to the GPU so some pause is still to be expected. + * + * @param scene The scene to preload + */ + public void preloadScene(Spatial scene) { + if (scene instanceof Node) { + // recurse for all children + Node n = (Node) scene; + List<Spatial> children = n.getChildren(); + for (int i = 0; i < children.size(); i++) { + preloadScene(children.get(i)); + } + } else if (scene instanceof Geometry) { + // add to the render queue + Geometry gm = (Geometry) scene; + if (gm.getMaterial() == null) { + throw new IllegalStateException("No material is set for Geometry: " + gm.getName()); + } + + gm.getMaterial().preload(this); + Mesh mesh = gm.getMesh(); + if (mesh != null) { + for (Entry<VertexBuffer> entry : mesh.getBuffers()) { + VertexBuffer buf = entry.getValue(); + if (buf.getData() != null) { + renderer.updateBufferData(buf); + } + } + } + } + } + + /** + * Flattens the given scene graph into the ViewPort's RenderQueue, + * checking for culling as the call goes down the graph recursively. + * <p> + * First, the scene is checked for culling based on the <code>Spatial</code>s + * {@link Spatial#setCullHint(com.jme3.scene.Spatial.CullHint) cull hint}, + * if the camera frustum contains the scene, then this method is recursively + * called on its children. + * <p> + * When the scene's leaves or {@link Geometry geometries} are reached, + * they are each enqueued into the + * {@link ViewPort#getQueue() ViewPort's render queue}. + * <p> + * In addition to enqueuing the visible geometries, this method + * also scenes which cast or receive shadows, by putting them into the + * RenderQueue's + * {@link RenderQueue#addToShadowQueue(com.jme3.scene.Geometry, com.jme3.renderer.queue.RenderQueue.ShadowMode) + * shadow queue}. Each Spatial which has its + * {@link Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode) shadow mode} + * set to not off, will be put into the appropriate shadow queue, note that + * this process does not check for frustum culling on any + * {@link ShadowMode#Cast shadow casters}, as they don't have to be + * in the eye camera frustum to cast shadows on objects that are inside it. + * + * @param scene The scene to flatten into the queue + * @param vp The ViewPort provides the {@link ViewPort#getCamera() camera} + * used for culling and the {@link ViewPort#getQueue() queue} used to + * contain the flattened scene graph. + */ + public void renderScene(Spatial scene, ViewPort vp) { + if (scene.getParent() == null) { + vp.getCamera().setPlaneState(0); + } + // check culling first. + if (!scene.checkCulling(vp.getCamera())) { + // move on to shadow-only render + if ((scene.getShadowMode() != RenderQueue.ShadowMode.Off || scene instanceof Node) && scene.getCullHint()!=Spatial.CullHint.Always) { + renderShadow(scene, vp.getQueue()); + } + return; + } + + scene.runControlRender(this, vp); + if (scene instanceof Node) { + // recurse for all children + Node n = (Node) scene; + List<Spatial> children = n.getChildren(); + //saving cam state for culling + int camState = vp.getCamera().getPlaneState(); + for (int i = 0; i < children.size(); i++) { + //restoring cam state before proceeding children recusively + vp.getCamera().setPlaneState(camState); + renderScene(children.get(i), vp); + + } + } else if (scene instanceof Geometry) { + + // add to the render queue + Geometry gm = (Geometry) scene; + if (gm.getMaterial() == null) { + throw new IllegalStateException("No material is set for Geometry: " + gm.getName()); + } + + vp.getQueue().addToQueue(gm, scene.getQueueBucket()); + + // add to shadow queue if needed + RenderQueue.ShadowMode shadowMode = scene.getShadowMode(); + if (shadowMode != RenderQueue.ShadowMode.Off) { + vp.getQueue().addToShadowQueue(gm, shadowMode); + } + } + } + + /** + * Returns the camera currently used for rendering. + * <p> + * The camera can be set with {@link #setCamera(com.jme3.renderer.Camera, boolean) }. + * + * @return the camera currently used for rendering. + */ + public Camera getCurrentCamera() { + return prevCam; + } + + /** + * The renderer implementation used for rendering operations. + * + * @return The renderer implementation + * + * @see #RenderManager(com.jme3.renderer.Renderer) + * @see Renderer + */ + public Renderer getRenderer() { + return renderer; + } + + /** + * Flushes the ViewPort's {@link ViewPort#getQueue() render queue} + * by rendering each of its visible buckets. + * By default the queues will automatically be cleared after rendering, + * so there's no need to clear them manually. + * + * @param vp The ViewPort of which the queue will be flushed + * + * @see RenderQueue#renderQueue(com.jme3.renderer.queue.RenderQueue.Bucket, com.jme3.renderer.RenderManager, com.jme3.renderer.Camera) + * @see #renderGeometryList(com.jme3.renderer.queue.GeometryList) + */ + public void flushQueue(ViewPort vp) { + renderViewPortQueues(vp, true); + } + + /** + * Clears the queue of the given ViewPort. + * Simply calls {@link RenderQueue#clear() } on the ViewPort's + * {@link ViewPort#getQueue() render queue}. + * + * @param vp The ViewPort of which the queue will be cleared. + * + * @see RenderQueue#clear() + * @see ViewPort#getQueue() + */ + public void clearQueue(ViewPort vp) { + vp.getQueue().clear(); + } + + /** + * Render the given viewport queues. + * <p> + * Changes the {@link Renderer#setDepthRange(float, float) depth range} + * appropriately as expected by each queue and then calls + * {@link RenderQueue#renderQueue(com.jme3.renderer.queue.RenderQueue.Bucket, com.jme3.renderer.RenderManager, com.jme3.renderer.Camera, boolean) } + * on the queue. Makes sure to restore the depth range to [0, 1] + * at the end of the call. + * Note that the {@link Bucket#Translucent translucent bucket} is NOT + * rendered by this method. Instead the user should call + * {@link #renderTranslucentQueue(com.jme3.renderer.ViewPort) } + * after this call. + * + * @param vp the viewport of which queue should be rendered + * @param flush If true, the queues will be cleared after + * rendering. + * + * @see RenderQueue + * @see #renderTranslucentQueue(com.jme3.renderer.ViewPort) + */ + public void renderViewPortQueues(ViewPort vp, boolean flush) { + RenderQueue rq = vp.getQueue(); + Camera cam = vp.getCamera(); + boolean depthRangeChanged = false; + + // render opaque objects with default depth range + // opaque objects are sorted front-to-back, reducing overdraw + rq.renderQueue(Bucket.Opaque, this, cam, flush); + + // render the sky, with depth range set to the farthest + if (!rq.isQueueEmpty(Bucket.Sky)) { + renderer.setDepthRange(1, 1); + rq.renderQueue(Bucket.Sky, this, cam, flush); + depthRangeChanged = true; + } + + + // transparent objects are last because they require blending with the + // rest of the scene's objects. Consequently, they are sorted + // back-to-front. + if (!rq.isQueueEmpty(Bucket.Transparent)) { + if (depthRangeChanged) { + renderer.setDepthRange(0, 1); + depthRangeChanged = false; + } + + rq.renderQueue(Bucket.Transparent, this, cam, flush); + } + + if (!rq.isQueueEmpty(Bucket.Gui)) { + renderer.setDepthRange(0, 0); + setCamera(cam, true); + rq.renderQueue(Bucket.Gui, this, cam, flush); + setCamera(cam, false); + depthRangeChanged = true; + } + + // restore range to default + if (depthRangeChanged) { + renderer.setDepthRange(0, 1); + } + } + + /** + * Renders the {@link Bucket#Translucent translucent queue} on the viewPort. + * <p> + * This call does nothing unless {@link #setHandleTranslucentBucket(boolean) } + * is set to true. This method clears the translucent queue after rendering + * it. + * + * @param vp The viewport of which the translucent queue should be rendered. + * + * @see #renderViewPortQueues(com.jme3.renderer.ViewPort, boolean) + * @see #setHandleTranslucentBucket(boolean) + */ + public void renderTranslucentQueue(ViewPort vp) { + RenderQueue rq = vp.getQueue(); + if (!rq.isQueueEmpty(Bucket.Translucent) && handleTranlucentBucket) { + rq.renderQueue(Bucket.Translucent, this, vp.getCamera(), true); + } + } + + private void setViewPort(Camera cam) { + // this will make sure to update viewport only if needed + if (cam != prevCam || cam.isViewportChanged()) { + viewX = (int) (cam.getViewPortLeft() * cam.getWidth()); + viewY = (int) (cam.getViewPortBottom() * cam.getHeight()); + viewWidth = (int) ((cam.getViewPortRight() - cam.getViewPortLeft()) * cam.getWidth()); + viewHeight = (int) ((cam.getViewPortTop() - cam.getViewPortBottom()) * cam.getHeight()); + renderer.setViewPort(viewX, viewY, viewWidth, viewHeight); + renderer.setClipRect(viewX, viewY, viewWidth, viewHeight); + cam.clearViewportChanged(); + prevCam = cam; + +// float translateX = viewWidth == viewX ? 0 : -(viewWidth + viewX) / (viewWidth - viewX); +// float translateY = viewHeight == viewY ? 0 : -(viewHeight + viewY) / (viewHeight - viewY); +// float scaleX = viewWidth == viewX ? 1f : 2f / (viewWidth - viewX); +// float scaleY = viewHeight == viewY ? 1f : 2f / (viewHeight - viewY); +// +// orthoMatrix.loadIdentity(); +// orthoMatrix.setTranslation(translateX, translateY, 0); +// orthoMatrix.setScale(scaleX, scaleY, 0); + + orthoMatrix.loadIdentity(); + orthoMatrix.setTranslation(-1f, -1f, 0f); + orthoMatrix.setScale(2f / cam.getWidth(), 2f / cam.getHeight(), 0f); + } + } + + private void setViewProjection(Camera cam, boolean ortho) { + if (shader) { + if (ortho) { + viewMatrix.set(Matrix4f.IDENTITY); + projMatrix.set(orthoMatrix); + viewProjMatrix.set(orthoMatrix); + } else { + viewMatrix.set(cam.getViewMatrix()); + projMatrix.set(cam.getProjectionMatrix()); + viewProjMatrix.set(cam.getViewProjectionMatrix()); + } + + camLoc.set(cam.getLocation()); + cam.getLeft(camLeft); + cam.getUp(camUp); + cam.getDirection(camDir); + + near = cam.getFrustumNear(); + far = cam.getFrustumFar(); + } else { + if (ortho) { + renderer.setViewProjectionMatrices(Matrix4f.IDENTITY, orthoMatrix); + } else { + renderer.setViewProjectionMatrices(cam.getViewMatrix(), + cam.getProjectionMatrix()); + } + + } + } + + /** + * Set the camera to use for rendering. + * <p> + * First, the camera's + * {@link Camera#setViewPort(float, float, float, float) view port parameters} + * are applied. Then, the camera's {@link Camera#getViewMatrix() view} and + * {@link Camera#getProjectionMatrix() projection} matrices are set + * on the renderer. If <code>ortho</code> is <code>true</code>, then + * instead of using the camera's view and projection matrices, an ortho + * matrix is computed and used instead of the view projection matrix. + * The ortho matrix converts from the range (0 ~ Width, 0 ~ Height, -1 ~ +1) + * to the clip range (-1 ~ +1, -1 ~ +1, -1 ~ +1). + * + * @param cam The camera to set + * @param ortho True if to use orthographic projection (for GUI rendering), + * false if to use the camera's view and projection matrices. + */ + public void setCamera(Camera cam, boolean ortho) { + setViewPort(cam); + setViewProjection(cam, ortho); + } + + /** + * Draws the viewport but without notifying {@link SceneProcessor scene + * processors} of any rendering events. + * + * @param vp The ViewPort to render + * + * @see #renderViewPort(com.jme3.renderer.ViewPort, float) + */ + public void renderViewPortRaw(ViewPort vp) { + setCamera(vp.getCamera(), false); + List<Spatial> scenes = vp.getScenes(); + for (int i = scenes.size() - 1; i >= 0; i--) { + renderScene(scenes.get(i), vp); + } + flushQueue(vp); + } + + /** + * Renders the {@link ViewPort}. + * <p> + * If the ViewPort is {@link ViewPort#isEnabled() disabled}, this method + * returns immediately. Otherwise, the ViewPort is rendered by + * the following process:<br> + * <ul> + * <li>All {@link SceneProcessor scene processors} that are attached + * to the ViewPort are {@link SceneProcessor#initialize(com.jme3.renderer.RenderManager, com.jme3.renderer.ViewPort) initialized}. + * </li> + * <li>The SceneProcessors' {@link SceneProcessor#preFrame(float) } method + * is called.</li> + * <li>The ViewPort's {@link ViewPort#getOutputFrameBuffer() output framebuffer} + * is set on the Renderer</li> + * <li>The camera is set on the renderer, including its view port parameters. + * (see {@link #setCamera(com.jme3.renderer.Camera, boolean) })</li> + * <li>Any buffers that the ViewPort requests to be cleared are cleared + * and the {@link ViewPort#getBackgroundColor() background color} is set</li> + * <li>Every scene that is attached to the ViewPort is flattened into + * the ViewPort's render queue + * (see {@link #renderViewPortQueues(com.jme3.renderer.ViewPort, boolean) }) + * </li> + * <li>The SceneProcessors' {@link SceneProcessor#postQueue(com.jme3.renderer.queue.RenderQueue) } + * method is called.</li> + * <li>The render queue is sorted and then flushed, sending + * rendering commands to the underlying Renderer implementation. + * (see {@link #flushQueue(com.jme3.renderer.ViewPort) })</li> + * <li>The SceneProcessors' {@link SceneProcessor#postFrame(com.jme3.texture.FrameBuffer) } + * method is called.</li> + * <li>The translucent queue of the ViewPort is sorted and then flushed + * (see {@link #renderTranslucentQueue(com.jme3.renderer.ViewPort) })</li> + * <li>If any objects remained in the render queue, they are removed + * from the queue. This is generally objects added to the + * {@link RenderQueue#renderShadowQueue(com.jme3.renderer.queue.RenderQueue.ShadowMode, com.jme3.renderer.RenderManager, com.jme3.renderer.Camera, boolean) + * shadow queue} + * which were not rendered because of a missing shadow renderer.</li> + * </ul> + * + * @param vp + * @param tpf + */ + public void renderViewPort(ViewPort vp, float tpf) { + if (!vp.isEnabled()) { + return; + } + List<SceneProcessor> processors = vp.getProcessors(); + if (processors.isEmpty()) { + processors = null; + } + + if (processors != null) { + for (SceneProcessor proc : processors) { + if (!proc.isInitialized()) { + proc.initialize(this, vp); + } + proc.preFrame(tpf); + } + } + + renderer.setFrameBuffer(vp.getOutputFrameBuffer()); + setCamera(vp.getCamera(), false); + if (vp.isClearDepth() || vp.isClearColor() || vp.isClearStencil()) { + if (vp.isClearColor()) { + renderer.setBackgroundColor(vp.getBackgroundColor()); + } + renderer.clearBuffers(vp.isClearColor(), + vp.isClearDepth(), + vp.isClearStencil()); + } + + List<Spatial> scenes = vp.getScenes(); + for (int i = scenes.size() - 1; i >= 0; i--) { + renderScene(scenes.get(i), vp); + } + + if (processors != null) { + for (SceneProcessor proc : processors) { + proc.postQueue(vp.getQueue()); + } + } + + flushQueue(vp); + + if (processors != null) { + for (SceneProcessor proc : processors) { + proc.postFrame(vp.getOutputFrameBuffer()); + } + } + //renders the translucent objects queue after processors have been rendered + renderTranslucentQueue(vp); + // clear any remaining spatials that were not rendered. + clearQueue(vp); + } + + /** + * Called by the application to render any ViewPorts + * added to this RenderManager. + * <p> + * Renders any viewports that were added using the following methods: + * <ul> + * <li>{@link #createPreView(java.lang.String, com.jme3.renderer.Camera) }</li> + * <li>{@link #createMainView(java.lang.String, com.jme3.renderer.Camera) }</li> + * <li>{@link #createPostView(java.lang.String, com.jme3.renderer.Camera) }</li> + * </ul> + * + * @param tpf Time per frame value + */ + public void render(float tpf, boolean mainFrameBufferActive) { + if (renderer instanceof NullRenderer) { + return; + } + + this.shader = renderer.getCaps().contains(Caps.GLSL100); + + for (int i = 0; i < preViewPorts.size(); i++) { + ViewPort vp = preViewPorts.get(i); + if (vp.getOutputFrameBuffer() != null || mainFrameBufferActive){ + renderViewPort(vp, tpf); + } + } + for (int i = 0; i < viewPorts.size(); i++) { + ViewPort vp = viewPorts.get(i); + if (vp.getOutputFrameBuffer() != null || mainFrameBufferActive){ + renderViewPort(vp, tpf); + } + } + for (int i = 0; i < postViewPorts.size(); i++) { + ViewPort vp = postViewPorts.get(i); + if (vp.getOutputFrameBuffer() != null || mainFrameBufferActive){ + renderViewPort(vp, tpf); + } + } + } +} diff --git a/engine/src/core/com/jme3/renderer/Renderer.java b/engine/src/core/com/jme3/renderer/Renderer.java new file mode 100644 index 0000000..6e366ab --- /dev/null +++ b/engine/src/core/com/jme3/renderer/Renderer.java @@ -0,0 +1,305 @@ +/* + * 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.light.LightList; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix4f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.shader.Shader; +import com.jme3.shader.Shader.ShaderSource; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import java.nio.ByteBuffer; +import java.util.EnumSet; + +/** + * The <code>Renderer</code> is responsible for taking rendering commands and + * executing them on the underlying video hardware. + * + * @author Kirill Vainer + */ +public interface Renderer { + + /** + * Get the capabilities of the renderer. + * @return The capabilities of the renderer. + */ + public EnumSet<Caps> getCaps(); + + /** + * The statistics allow tracking of how data + * per frame, such as number of objects rendered, number of triangles, etc. + * These are updated when the Renderer's methods are used, make sure + * to call {@link Statistics#clearFrame() } at the appropriate time + * to get accurate info per frame. + */ + public Statistics getStatistics(); + + /** + * Invalidates the current rendering state. Should be called after + * the GL state was changed manually or through an external library. + */ + public void invalidateState(); + + /** + * Clears certain channels of the currently bound framebuffer. + * + * @param color True if to clear colors (RGBA) + * @param depth True if to clear depth/z + * @param stencil True if to clear stencil buffer (if available, otherwise + * ignored) + */ + public void clearBuffers(boolean color, boolean depth, boolean stencil); + + /** + * Sets the background (aka clear) color. + * + * @param color The background color to set + */ + public void setBackgroundColor(ColorRGBA color); + + /** + * Applies the given {@link RenderState}, making the necessary + * GL calls so that the state is applied. + */ + public void applyRenderState(RenderState state); + + /** + * Set the range of the depth values for objects. All rendered + * objects will have their depth clamped to this range. + * + * @param start The range start + * @param end The range end + */ + public void setDepthRange(float start, float end); + + /** + * Called when a new frame has been rendered. + */ + public void onFrame(); + + /** + * Set the world matrix to use. Does nothing if the Renderer is + * shader based. + * + * @param worldMatrix World matrix to use. + */ + public void setWorldMatrix(Matrix4f worldMatrix); + + /** + * Sets the view and projection matrices to use. Does nothing if the Renderer + * is shader based. + * + * @param viewMatrix The view matrix to use. + * @param projMatrix The projection matrix to use. + */ + public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix); + + /** + * Set the viewport location and resolution on the screen. + * + * @param x The x coordinate of the viewport + * @param y The y coordinate of the viewport + * @param width Width of the viewport + * @param height Height of the viewport + */ + public void setViewPort(int x, int y, int width, int height); + + /** + * Specifies a clipping rectangle. + * For all future rendering commands, no pixels will be allowed + * to be rendered outside of the clip rectangle. + * + * @param x The x coordinate of the clip rect + * @param y The y coordinate of the clip rect + * @param width Width of the clip rect + * @param height Height of the clip rect + */ + public void setClipRect(int x, int y, int width, int height); + + /** + * Clears the clipping rectangle set with + * {@link #setClipRect(int, int, int, int) }. + */ + public void clearClipRect(); + + /** + * Set lighting state. + * Does nothing if the renderer is shader based. + * The lights should be provided in world space. + * Specify <code>null</code> to disable lighting. + * + * @param lights The light list to set. + */ + public void setLighting(LightList lights); + + /** + * Sets the shader to use for rendering. + * If the shader has not been uploaded yet, it is compiled + * and linked. If it has been uploaded, then the + * uniform data is updated and the shader is set. + * + * @param shader The shader to use for rendering. + */ + public void setShader(Shader shader); + + /** + * Deletes a shader. This method also deletes + * the attached shader sources. + * + * @param shader Shader to delete. + */ + public void deleteShader(Shader shader); + + /** + * Deletes the provided shader source. + * + * @param source The ShaderSource to delete. + */ + public void deleteShaderSource(ShaderSource source); + + /** + * Copies contents from src to dst, scaling if necessary. + */ + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst); + + /** + * Copies contents from src to dst, scaling if necessary. + * set copyDepth to false to only copy the color buffers. + */ + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth); + + /** + * Sets the framebuffer that will be drawn to. + */ + public void setFrameBuffer(FrameBuffer fb); + + /** + * Set the framebuffer that will be set instead of the main framebuffer + * when a call to setFrameBuffer(null) is made. + * + * @param fb + */ + public void setMainFrameBufferOverride(FrameBuffer fb); + + /** + * Reads the pixels currently stored in the specified framebuffer + * into the given ByteBuffer object. + * Only color pixels are transferred, the format is BGRA with 8 bits + * per component. The given byte buffer should have at least + * fb.getWidth() * fb.getHeight() * 4 bytes remaining. + * + * @param fb The framebuffer to read from + * @param byteBuf The bytebuffer to transfer color data to + */ + public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf); + + /** + * Deletes a framebuffer and all attached renderbuffers + */ + public void deleteFrameBuffer(FrameBuffer fb); + + /** + * Sets the texture to use for the given texture unit. + */ + public void setTexture(int unit, Texture tex); + + /** + * Deletes a texture from the GPU. + */ + public void deleteImage(Image image); + + /** + * Uploads a vertex buffer to the GPU. + * + * @param vb The vertex buffer to upload + */ + public void updateBufferData(VertexBuffer vb); + + /** + * Deletes a vertex buffer from the GPU. + * @param vb The vertex buffer to delete + */ + public void deleteBuffer(VertexBuffer vb); + + /** + * Renders <code>count</code> meshes, with the geometry data supplied. + * The shader which is currently set with <code>setShader</code> is + * responsible for transforming the input verticies into clip space + * and shading it based on the given vertex attributes. + * The int variable gl_InstanceID can be used to access the current + * instance of the mesh being rendered inside the vertex shader. + * + * @param mesh The mesh to render + * @param lod The LOD level to use, see {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }. + * @param count Number of mesh instances to render + */ + public void renderMesh(Mesh mesh, int lod, int count); + + /** + * Resets all previously used {@link GLObject}s on this Renderer. + * The state of the GLObjects is reset in such way, that using + * them again will cause the renderer to reupload them. + * Call this method when you know the GL context is going to shutdown. + * + * @see GLObject#resetObject() + */ + public void resetGLObjects(); + + /** + * Deletes all previously used {@link GLObject}s on this Renderer, and + * then resets the GLObjects. + * + * @see #resetGLObjects() + * @see GLObject#deleteObject(com.jme3.renderer.Renderer) + */ + public void cleanup(); + + /** + * Sets the alpha to coverage state. + * <p> + * When alpha coverage and multi-sampling is enabled, + * each pixel will contain alpha coverage in all + * of its subsamples, which is then combined when + * other future alpha-blended objects are rendered. + * </p> + * <p> + * Alpha-to-coverage is useful for rendering transparent objects + * without having to worry about sorting them. + * </p> + */ + public void setAlphaToCoverage(boolean value); +} diff --git a/engine/src/core/com/jme3/renderer/RendererException.java b/engine/src/core/com/jme3/renderer/RendererException.java new file mode 100644 index 0000000..41964a2 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/RendererException.java @@ -0,0 +1,49 @@ +/* + * 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; + +/** + * <code>RendererException</code> is raised when a renderer encounters + * a fatal rendering error. + * + * @author Kirill Vainer + */ +public class RendererException extends RuntimeException { + + /** + * Creates a new instance of <code>RendererException</code> + */ + public RendererException(String message){ + super(message); + } +} diff --git a/engine/src/core/com/jme3/renderer/Statistics.java b/engine/src/core/com/jme3/renderer/Statistics.java new file mode 100644 index 0000000..fb8f2a0 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/Statistics.java @@ -0,0 +1,255 @@ +/* + * 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.scene.Mesh; +import com.jme3.shader.Shader; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; +import java.util.HashSet; + +/** + * The statistics class allows tracking of real-time rendering statistics. + * <p> + * The <code>Statistics</code> can be retrieved by using {@link Renderer#getStatistics() }. + * + * @author Kirill Vainer + */ +public class Statistics { + + protected int numObjects; + protected int numTriangles; + protected int numVertices; + protected int numShaderSwitches; + protected int numTextureBinds; + protected int numFboSwitches; + protected int numUniformsSet; + + protected int memoryShaders; + protected int memoryFrameBuffers; + protected int memoryTextures; + + protected HashSet<Integer> shadersUsed = new HashSet<Integer>(); + protected HashSet<Integer> texturesUsed = new HashSet<Integer>(); + protected HashSet<Integer> fbosUsed = new HashSet<Integer>(); + + /** + * Returns a list of labels corresponding to each statistic. + * + * @return a list of labels corresponding to each statistic. + * + * @see #getData(int[]) + */ + public String[] getLabels(){ + return new String[]{ "Vertices", + "Triangles", + "Uniforms", + + "Objects", + + "Shaders (S)", + "Shaders (F)", + "Shaders (M)", + + "Textures (S)", + "Textures (F)", + "Textures (M)", + + "FrameBuffers (S)", + "FrameBuffers (F)", + "FrameBuffers (M)" }; + + } + + /** + * Retrieves the statistics data into the given array. + * The array should be as large as the array given in + * {@link #getLabels() }. + * + * @param data The data array to write to + */ + public void getData(int[] data){ + data[0] = numVertices; + data[1] = numTriangles; + data[2] = numUniformsSet; + data[3] = numObjects; + + data[4] = numShaderSwitches; + data[5] = shadersUsed.size(); + data[6] = memoryShaders; + + data[7] = numTextureBinds; + data[8] = texturesUsed.size(); + data[9] = memoryTextures; + + data[10] = numFboSwitches; + data[11] = fbosUsed.size(); + data[12] = memoryFrameBuffers; + } + + /** + * Called by the Renderer when a mesh has been drawn. + * + */ + public void onMeshDrawn(Mesh mesh, int lod){ + numObjects ++; + numTriangles += mesh.getTriangleCount(lod); + numVertices += mesh.getVertexCount(); + } + + /** + * Called by the Renderer when a shader has been utilized. + * + * @param shader The shader that was used + * @param wasSwitched If true, the shader has required a state switch + */ + public void onShaderUse(Shader shader, boolean wasSwitched){ + assert shader.getId() >= 1; + + if (!shadersUsed.contains(shader.getId())) + shadersUsed.add(shader.getId()); + + if (wasSwitched) + numShaderSwitches++; + } + + /** + * Called by the Renderer when a uniform was set. + */ + public void onUniformSet(){ + numUniformsSet ++; + } + + /** + * Called by the Renderer when a texture has been set. + * + * @param image The image that was set + * @param wasSwitched If true, the texture has required a state switch + */ + public void onTextureUse(Image image, boolean wasSwitched){ + assert image.getId() >= 1; + + if (!texturesUsed.contains(image.getId())) + texturesUsed.add(image.getId()); + + if (wasSwitched) + numTextureBinds ++; + } + + /** + * Called by the Renderer when a framebuffer has been set. + * + * @param fb The framebuffer that was set + * @param wasSwitched If true, the framebuffer required a state switch + */ + public void onFrameBufferUse(FrameBuffer fb, boolean wasSwitched){ + if (fb != null){ + assert fb.getId() >= 1; + + if (!fbosUsed.contains(fb.getId())) + fbosUsed.add(fb.getId()); + } + + if (wasSwitched) + numFboSwitches ++; + } + + /** + * Clears all frame-specific statistics such as objects used per frame. + */ + public void clearFrame(){ + shadersUsed.clear(); + texturesUsed.clear(); + fbosUsed.clear(); + + numObjects = 0; + numTriangles = 0; + numVertices = 0; + numShaderSwitches = 0; + numTextureBinds = 0; + numFboSwitches = 0; + numUniformsSet = 0; + } + + /** + * Called by the Renderer when it creates a new shader + */ + public void onNewShader(){ + memoryShaders ++; + } + + /** + * Called by the Renderer when it creates a new texture + */ + public void onNewTexture(){ + memoryTextures ++; + } + + /** + * Called by the Renderer when it creates a new framebuffer + */ + public void onNewFrameBuffer(){ + memoryFrameBuffers ++; + } + + /** + * Called by the Renderer when it deletes a shader + */ + public void onDeleteShader(){ + memoryShaders --; + } + + /** + * Called by the Renderer when it deletes a texture + */ + public void onDeleteTexture(){ + memoryTextures --; + } + + /** + * Called by the Renderer when it deletes a framebuffer + */ + public void onDeleteFrameBuffer(){ + memoryFrameBuffers --; + } + + /** + * Called when video memory is cleared. + */ + public void clearMemory(){ + memoryFrameBuffers = 0; + memoryShaders = 0; + memoryTextures = 0; + } + +} diff --git a/engine/src/core/com/jme3/renderer/ViewPort.java b/engine/src/core/com/jme3/renderer/ViewPort.java new file mode 100644 index 0000000..ad9ab2f --- /dev/null +++ b/engine/src/core/com/jme3/renderer/ViewPort.java @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2009-2012 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.math.ColorRGBA; +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Spatial; +import com.jme3.texture.FrameBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * A <code>ViewPort</code> represents a view inside the display + * window or a {@link FrameBuffer} to which scenes will be rendered. + * <p> + * A viewport has a {@link #ViewPort(java.lang.String, com.jme3.renderer.Camera) camera} + * which is used to render a set of {@link #attachScene(com.jme3.scene.Spatial) scenes}. + * A view port has a location on the screen as set by the + * {@link Camera#setViewPort(float, float, float, float) } method. + * By default, a view port does not clear the framebuffer, but it can be + * set to {@link #setClearFlags(boolean, boolean, boolean) clear the framebuffer}. + * The background color which the color buffer is cleared to can be specified + * via the {@link #setBackgroundColor(com.jme3.math.ColorRGBA)} method. + * <p> + * A ViewPort has a list of {@link SceneProcessor}s which can + * control how the ViewPort is rendered by the {@link RenderManager}. + * + * @author Kirill Vainer + * + * @see RenderManager + * @see SceneProcessor + * @see Spatial + * @see Camera + */ +public class ViewPort { + + protected final String name; + protected final Camera cam; + protected final RenderQueue queue = new RenderQueue(); + protected final ArrayList<Spatial> sceneList = new ArrayList<Spatial>(); + protected final ArrayList<SceneProcessor> processors = new ArrayList<SceneProcessor>(); + protected FrameBuffer out = null; + + protected final ColorRGBA backColor = new ColorRGBA(0,0,0,0); + protected boolean clearDepth = false, clearColor = false, clearStencil = false; + private boolean enabled = true; + + /** + * Create a new viewport. User code should generally use these methods instead:<br> + * <ul> + * <li>{@link RenderManager#createPreView(java.lang.String, com.jme3.renderer.Camera) }</li> + * <li>{@link RenderManager#createMainView(java.lang.String, com.jme3.renderer.Camera) }</li> + * <li>{@link RenderManager#createPostView(java.lang.String, com.jme3.renderer.Camera) }</li> + * </ul> + * + * @param name The name of the viewport. Used for debugging only. + * @param cam The camera through which the viewport is rendered. The camera + * cannot be swapped to a different one after creating the viewport. + */ + public ViewPort(String name, Camera cam) { + this.name = name; + this.cam = cam; + } + + /** + * Returns the name of the viewport as set in the constructor. + * + * @return the name of the viewport + * + * @see #ViewPort(java.lang.String, com.jme3.renderer.Camera) + */ + public String getName() { + return name; + } + + /** + * Get the list of {@link SceneProcessor scene processors} that were + * added to this <code>ViewPort</code> + * + * @return the list of processors attached to this ViewPort + * + * @see #addProcessor(com.jme3.post.SceneProcessor) + */ + public List<SceneProcessor> getProcessors(){ + return processors; + } + + /** + * Adds a {@link SceneProcessor} to this ViewPort. + * <p> + * SceneProcessors that are added to the ViewPort will be notified + * of events as the ViewPort is being rendered by the {@link RenderManager}. + * + * @param processor The processor to add + * + * @see SceneProcessor + */ + public void addProcessor(SceneProcessor processor){ + processors.add(processor); + } + + /** + * Removes a {@link SceneProcessor} from this ViewPort. + * <p> + * The processor will no longer receive events occurring to this ViewPort. + * + * @param processor The processor to remove + * + * @see SceneProcessor + */ + public void removeProcessor(SceneProcessor processor){ + processors.remove(processor); + processor.cleanup(); + } + + /** + * Check if depth buffer clearing is enabled. + * + * @return true if depth buffer clearing is enabled. + * + * @see #setClearDepth(boolean) + */ + public boolean isClearDepth() { + return clearDepth; + } + + /** + * Enable or disable clearing of the depth buffer for this ViewPort. + * <p> + * By default depth clearing is disabled. + * + * @param clearDepth Enable/disable depth buffer clearing. + */ + public void setClearDepth(boolean clearDepth) { + this.clearDepth = clearDepth; + } + + /** + * Check if color buffer clearing is enabled. + * + * @return true if color buffer clearing is enabled. + * + * @see #setClearColor(boolean) + */ + public boolean isClearColor() { + return clearColor; + } + + /** + * Enable or disable clearing of the color buffer for this ViewPort. + * <p> + * By default color clearing is disabled. + * + * @param clearColor Enable/disable color buffer clearing. + */ + public void setClearColor(boolean clearColor) { + this.clearColor = clearColor; + } + + /** + * Check if stencil buffer clearing is enabled. + * + * @return true if stencil buffer clearing is enabled. + * + * @see #setClearStencil(boolean) + */ + public boolean isClearStencil() { + return clearStencil; + } + + /** + * Enable or disable clearing of the stencil buffer for this ViewPort. + * <p> + * By default stencil clearing is disabled. + * + * @param clearStencil Enable/disable stencil buffer clearing. + */ + public void setClearStencil(boolean clearStencil) { + this.clearStencil = clearStencil; + } + + /** + * Set the clear flags (color, depth, stencil) in one call. + * + * @param color If color buffer clearing should be enabled. + * @param depth If depth buffer clearing should be enabled. + * @param stencil If stencil buffer clearing should be enabled. + * + * @see #setClearColor(boolean) + * @see #setClearDepth(boolean) + * @see #setClearStencil(boolean) + */ + public void setClearFlags(boolean color, boolean depth, boolean stencil){ + this.clearColor = color; + this.clearDepth = depth; + this.clearStencil = stencil; + } + + /** + * Returns the framebuffer where this ViewPort's scenes are + * rendered to. + * + * @return the framebuffer where this ViewPort's scenes are + * rendered to. + * + * @see #setOutputFrameBuffer(com.jme3.texture.FrameBuffer) + */ + public FrameBuffer getOutputFrameBuffer() { + return out; + } + + /** + * Sets the output framebuffer for the ViewPort. + * <p> + * The output framebuffer specifies where the scenes attached + * to this ViewPort are rendered to. By default this is <code>null</code> + * which indicates the scenes are rendered to the display window. + * + * @param out The framebuffer to render scenes to, or null if to render + * to the screen. + */ + public void setOutputFrameBuffer(FrameBuffer out) { + this.out = out; + } + + /** + * Returns the camera which renders the attached scenes. + * + * @return the camera which renders the attached scenes. + * + * @see Camera + */ + public Camera getCamera() { + return cam; + } + + /** + * Internal use only. + */ + public RenderQueue getQueue() { + return queue; + } + + /** + * Attaches a new scene to render in this ViewPort. + * + * @param scene The scene to attach + * + * @see Spatial + */ + public void attachScene(Spatial scene){ + sceneList.add(scene); + } + + /** + * Detaches a scene from rendering. + * + * @param scene The scene to detach + * + * @see #attachScene(com.jme3.scene.Spatial) + */ + public void detachScene(Spatial scene){ + sceneList.remove(scene); + } + + /** + * Removes all attached scenes. + * + * @see #attachScene(com.jme3.scene.Spatial) + */ + public void clearScenes() { + sceneList.clear(); + } + + /** + * Returns a list of all attached scenes. + * + * @return a list of all attached scenes. + * + * @see #attachScene(com.jme3.scene.Spatial) + */ + public List<Spatial> getScenes(){ + return sceneList; + } + + /** + * Sets the background color. + * <p> + * When the ViewPort's color buffer is cleared + * (if {@link #setClearColor(boolean) color clearing} is enabled), + * this specifies the color to which the color buffer is set to. + * By default the background color is black without alpha. + * + * @param background the background color. + */ + public void setBackgroundColor(ColorRGBA background){ + backColor.set(background); + } + + /** + * Returns the background color of this ViewPort + * + * @return the background color of this ViewPort + * + * @see #setBackgroundColor(com.jme3.math.ColorRGBA) + */ + public ColorRGBA getBackgroundColor(){ + return backColor; + } + + /** + * Enable or disable this ViewPort. + * <p> + * Disabled ViewPorts are skipped by the {@link RenderManager} when + * rendering. By default all ViewPorts are enabled. + * + * @param enable If the viewport should be disabled or enabled. + */ + public void setEnabled(boolean enable) { + this.enabled = enable; + } + + /** + * Returns true if the viewport is enabled, false otherwise. + * @return true if the viewport is enabled, false otherwise. + * @see #setEnabled(boolean) + */ + public boolean isEnabled() { + return enabled; + } + +} diff --git a/engine/src/core/com/jme3/renderer/package.html b/engine/src/core/com/jme3/renderer/package.html new file mode 100644 index 0000000..9d80ec8 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/package.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> + +<head> +<title></title> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> +</head> +<body> + +The <code>com.jme3.renderer</code> package provides classes responsible for +rendering. +<p> +The most critical classes are the {@link com.jme3.renderer.Renderer}, +which is the low-level rendering implementation and is abstract, and the +{@link com.jme3.renderer.RenderManager} class, which provides the high-level +rendering logic on top of the <code>Renderer</code>. +<p> +To accompany rendering, several helper classes are available. +<ul> + <li>The {@link com.jme3.renderer.Camera} is used to specify the point-of-view + from which scenes are rendered.</li> + <li>The {@link com.jme3.renderer.ViewPort} is the +aggregation of a Camera and a set of {@link com.jme3.scene.Spatial scenes} +which are to be rendered, as well as additional info.</li> + <li>The {@link com.jme3.renderer.Caps} class contains renderer capabilities +which the user can query to find out what features are available in the +rendering implementation. </li> + <li>The {@link com.jme3.renderer.Statistics} class is updated in real time + by the Renderer, and is used to find out various statistics about + the rendering</li> + <li>The {@link com.jme3.renderer.GLObjectManager} and {@link com.jme3.renderer.GLObject} classes + provide a link between the renderer's native objects and Java's garbage collected objects, + allowing the engine to track when the Java object counterpart is garbage collected + and then delete the native object counterpart from the renderer.</li> +</ul> + +</body> +</html> diff --git a/engine/src/core/com/jme3/renderer/queue/GeometryComparator.java b/engine/src/core/com/jme3/renderer/queue/GeometryComparator.java new file mode 100644 index 0000000..8f42df7 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/queue/GeometryComparator.java @@ -0,0 +1,53 @@ +/* + * 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.queue; + +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; +import java.util.Comparator; + +/** + * <code>GeometryComparator</code> is a special version of {@link Comparator} + * that is used to sort geometries for rendering in the {@link RenderQueue}. + * + * @author Kirill Vainer + */ +public interface GeometryComparator extends Comparator<Geometry> { + + /** + * Set the camera to use for sorting. + * + * @param cam The camera to use for sorting + */ + public void setCamera(Camera cam); +} diff --git a/engine/src/core/com/jme3/renderer/queue/GeometryList.java b/engine/src/core/com/jme3/renderer/queue/GeometryList.java new file mode 100644 index 0000000..9f36ed9 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/queue/GeometryList.java @@ -0,0 +1,143 @@ +/* + * 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.queue; + +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; +import com.jme3.util.SortUtil; + +/** + * This class is a special purpose list of {@link Geometry} objects for render + * queuing. + * + * @author Jack Lindamood + * @author Three Rings - better sorting alg. + * @author Kirill Vainer + */ +public class GeometryList { + + private static final int DEFAULT_SIZE = 32; + + private Geometry[] geometries; + private Geometry[] geometries2; + private int size; + private GeometryComparator comparator; + + /** + * Initializes the GeometryList to use the given {@link GeometryComparator} + * to use for comparing geometries. + * + * @param comparator The comparator to use. + */ + public GeometryList(GeometryComparator comparator) { + size = 0; + geometries = new Geometry[DEFAULT_SIZE]; + geometries2 = new Geometry[DEFAULT_SIZE]; + this.comparator = comparator; + } + + /** + * Set the camera that will be set on the geometry comparators + * via {@link GeometryComparator#setCamera(com.jme3.renderer.Camera)}. + * + * @param cam Camera to use for sorting. + */ + public void setCamera(Camera cam){ + this.comparator.setCamera(cam); + } + + /** + * Returns the number of elements in this GeometryList. + * + * @return Number of elements in the list + */ + public int size(){ + return size; + } + + /** + * Returns the element at the given index. + * + * @param index The index to lookup + * @return Geometry at the index + */ + public Geometry get(int index){ + return geometries[index]; + } + + /** + * Adds a geometry to the list. + * List size is doubled if there is no room. + * + * @param g + * The geometry to add. + */ + public void add(Geometry g) { + if (size == geometries.length) { + Geometry[] temp = new Geometry[size * 2]; + System.arraycopy(geometries, 0, temp, 0, size); + geometries = temp; // original list replaced by double-size list + + geometries2 = new Geometry[size * 2]; + } + geometries[size++] = g; + } + + /** + * Resets list size to 0. + */ + public void clear() { + for (int i = 0; i < size; i++){ + geometries[i] = null; + } + + size = 0; + } + + /** + * Sorts the elements in the list according to their Comparator. + */ + public void sort() { + if (size > 1) { + // sort the spatial list using the comparator + +// SortUtil.qsort(geometries, 0, size, comparator); +// Arrays.sort(geometries, 0, size, comparator); + + System.arraycopy(geometries, 0, geometries2, 0, size); + SortUtil.msort(geometries2, geometries, 0, size-1, comparator); + + + } + } +}
\ No newline at end of file diff --git a/engine/src/core/com/jme3/renderer/queue/GuiComparator.java b/engine/src/core/com/jme3/renderer/queue/GuiComparator.java new file mode 100644 index 0000000..b35a03c --- /dev/null +++ b/engine/src/core/com/jme3/renderer/queue/GuiComparator.java @@ -0,0 +1,60 @@ +/* + * 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.queue; + +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; + +/** + * <code>GuiComparator</code> sorts geometries back-to-front based + * on their Z position. + * + * @author Kirill Vainer + */ +public class GuiComparator implements GeometryComparator { + + public int compare(Geometry o1, Geometry o2) { + float z1 = o1.getWorldTranslation().getZ(); + float z2 = o2.getWorldTranslation().getZ(); + if (z1 > z2) + return 1; + else if (z1 < z2) + return -1; + else + return 0; + } + + public void setCamera(Camera cam) { + } + +} diff --git a/engine/src/core/com/jme3/renderer/queue/NullComparator.java b/engine/src/core/com/jme3/renderer/queue/NullComparator.java new file mode 100644 index 0000000..6463558 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/queue/NullComparator.java @@ -0,0 +1,51 @@ +/* + * 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.queue; + +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; + +/** + * <code>NullComparator</code> does not sort geometries. They will be in + * arbitrary order. + * + * @author Kirill Vainer + */ +public class NullComparator implements GeometryComparator { + public int compare(Geometry o1, Geometry o2) { + return 0; + } + + public void setCamera(Camera cam) { + } +} diff --git a/engine/src/core/com/jme3/renderer/queue/OpaqueComparator.java b/engine/src/core/com/jme3/renderer/queue/OpaqueComparator.java new file mode 100644 index 0000000..85f4093 --- /dev/null +++ b/engine/src/core/com/jme3/renderer/queue/OpaqueComparator.java @@ -0,0 +1,96 @@ +/*
+ * 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.queue;
+
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Geometry;
+
+public class OpaqueComparator implements GeometryComparator {
+
+ private Camera cam;
+ private final Vector3f tempVec = new Vector3f();
+ private final Vector3f tempVec2 = new Vector3f();
+
+ public void setCamera(Camera cam){
+ this.cam = cam;
+ }
+
+ public float distanceToCam(Geometry spat){
+ if (spat == null)
+ return Float.NEGATIVE_INFINITY;
+
+ if (spat.queueDistance != Float.NEGATIVE_INFINITY)
+ return spat.queueDistance;
+
+ Vector3f camPosition = cam.getLocation();
+ Vector3f viewVector = cam.getDirection(tempVec2);
+ Vector3f spatPosition = null;
+
+ if (spat.getWorldBound() != null){
+ spatPosition = spat.getWorldBound().getCenter();
+ }else{
+ spatPosition = spat.getWorldTranslation();
+ }
+
+ spatPosition.subtract(camPosition, tempVec);
+ spat.queueDistance = tempVec.dot(viewVector);
+
+ return spat.queueDistance;
+ }
+
+ public int compare(Geometry o1, Geometry o2) {
+ Material m1 = o1.getMaterial();
+ Material m2 = o2.getMaterial();
+
+ int sortId = m1.compareTo(m2);
+
+ if (sortId == 0){
+ // use the same shader.
+ // sort front-to-back then.
+ float d1 = distanceToCam(o1);
+ float d2 = distanceToCam(o2);
+
+ if (d1 == d2)
+ return 0;
+ else if (d1 < d2)
+ return -1;
+ else
+ return 1;
+ }else{
+ return sortId;
+ }
+ }
+
+}
diff --git a/engine/src/core/com/jme3/renderer/queue/RenderQueue.java b/engine/src/core/com/jme3/renderer/queue/RenderQueue.java new file mode 100644 index 0000000..dba604e --- /dev/null +++ b/engine/src/core/com/jme3/renderer/queue/RenderQueue.java @@ -0,0 +1,377 @@ +/* + * 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.queue; + +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; + +/** + * <code>RenderQueue</code> is used to queue up and sort + * {@link Geometry geometries} for rendering. + * + * @author Kirill Vainer + */ +public class RenderQueue { + + private GeometryList opaqueList; + private GeometryList guiList; + private GeometryList transparentList; + private GeometryList translucentList; + private GeometryList skyList; + private GeometryList shadowRecv; + private GeometryList shadowCast; + + /** + * Creates a new RenderQueue, the default {@link GeometryComparator comparators} + * are used for all {@link GeometryList geometry lists}. + */ + public RenderQueue() { + this.opaqueList = new GeometryList(new OpaqueComparator()); + this.guiList = new GeometryList(new GuiComparator()); + this.transparentList = new GeometryList(new TransparentComparator()); + this.translucentList = new GeometryList(new TransparentComparator()); + this.skyList = new GeometryList(new NullComparator()); + this.shadowRecv = new GeometryList(new OpaqueComparator()); + this.shadowCast = new GeometryList(new OpaqueComparator()); + } + + /** + * The render queue <code>Bucket</code> specifies the bucket + * to which the spatial will be placed when rendered. + * <p> + * The behavior of the rendering will differ depending on which + * bucket the spatial is placed. A spatial's queue bucket can be set + * via {@link Spatial#setQueueBucket(com.jme3.renderer.queue.RenderQueue.Bucket) }. + */ + public enum Bucket { + /** + * The renderer will try to find the optimal order for rendering all + * objects using this mode. + * You should use this mode for most normal objects, except transparent + * ones, as it could give a nice performance boost to your application. + */ + Opaque, + + /** + * This is the mode you should use for object with + * transparency in them. It will ensure the objects furthest away are + * rendered first. That ensures when another transparent object is drawn on + * top of previously drawn objects, you can see those (and the object drawn + * using Opaque) through the transparent parts of the newly drawn + * object. + */ + Transparent, + + /** + * A special mode used for rendering really far away, flat objects - + * e.g. skies. In this mode, the depth is set to infinity so + * spatials in this bucket will appear behind everything, the downside + * to this bucket is that 3D objects will not be rendered correctly + * due to lack of depth testing. + */ + Sky, + + /** + * A special mode used for rendering transparent objects that + * should not be effected by {@link SceneProcessor}. + * Generally this would contain translucent objects, and + * also objects that do not write to the depth buffer such as + * particle emitters. + */ + Translucent, + + /** + * This is a special mode, for drawing 2D object + * without perspective (such as GUI or HUD parts). + * The spatial's world coordinate system has the range + * of [0, 0, -1] to [Width, Height, 1] where Width/Height is + * the resolution of the screen rendered to. Any spatials + * outside of that range are culled. + */ + Gui, + + /** + * A special mode, that will ensure that this spatial uses the same + * mode as the parent Node does. + */ + Inherit, + } + + /** + * <code>ShadowMode</code> is a marker used to specify how shadow + * effects should treat the spatial. + */ + public enum ShadowMode { + /** + * Disable both shadow casting and shadow receiving for this spatial. + * Generally used for special effects like particle emitters. + */ + Off, + + /** + * Enable casting of shadows but not receiving them. + */ + Cast, + + /** + * Enable receiving of shadows but not casting them. + */ + Receive, + + /** + * Enable both receiving and casting of shadows. + */ + CastAndReceive, + + /** + * Inherit the <code>ShadowMode</code> from the parent node. + */ + Inherit + } + + /** + * Sets a different geometry comparator for the specified bucket, one + * of Gui, Opaque, Sky, or Transparent. The GeometryComparators are + * used to sort the accumulated list of geometries before actual rendering + * occurs. + * + * <p>The most significant comparator is the one for the transparent + * bucket since there is no correct way to sort the transparent bucket + * that will handle all geometry all the time. In certain cases, the + * application may know the best way to sort and now has the option of + * configuring a specific implementation.</p> + * + * <p>The default comparators are:</p> + * <ul> + * <li>Bucket.Opaque: {@link com.jme3.renderer.queue.OpaqueComparator} which sorts + * by material first and front to back within the same material. + * <li>Bucket.Transparent: {@link com.jme3.renderer.queue.TransparentComparator} which + * sorts purely back to front by leading bounding edge with no material sort. + * <li>Bucket.Translucent: {@link com.jme3.renderer.queue.TransparentComparator} which + * sorts purely back to front by leading bounding edge with no material sort. this bucket is rendered after post processors. + * <li>Bucket.Sky: {@link com.jme3.renderer.queue.NullComparator} which does no sorting + * at all. + * <li>Bucket.Gui: {@link com.jme3.renderer.queue.GuiComparator} sorts geometries back to + * front based on their Z values. + */ + public void setGeometryComparator(Bucket bucket, GeometryComparator c) { + switch (bucket) { + case Gui: + guiList = new GeometryList(c); + break; + case Opaque: + opaqueList = new GeometryList(c); + break; + case Sky: + skyList = new GeometryList(c); + break; + case Transparent: + transparentList = new GeometryList(c); + break; + case Translucent: + translucentList = new GeometryList(c); + break; + default: + throw new UnsupportedOperationException("Unknown bucket type: " + bucket); + } + } + + /** + * Adds a geometry to a shadow bucket. + * Note that this operation is done automatically by the + * {@link RenderManager}. {@link SceneProcessor}s that handle + * shadow rendering should fetch the queue by using + * {@link #getShadowQueueContent(com.jme3.renderer.queue.RenderQueue.ShadowMode) }, + * by default no action is taken on the shadow queues. + * + * @param g The geometry to add + * @param shadBucket The shadow bucket type, if it is + * {@link ShadowMode#CastAndReceive}, it is added to both the cast + * and the receive buckets. + */ + public void addToShadowQueue(Geometry g, ShadowMode shadBucket) { + switch (shadBucket) { + case Inherit: + break; + case Off: + break; + case Cast: + shadowCast.add(g); + break; + case Receive: + shadowRecv.add(g); + break; + case CastAndReceive: + shadowCast.add(g); + shadowRecv.add(g); + break; + default: + throw new UnsupportedOperationException("Unrecognized shadow bucket type: " + shadBucket); + } + } + + /** + * Adds a geometry to the given bucket. + * The {@link RenderManager} automatically handles this task + * when flattening the scene graph. The bucket to add + * the geometry is determined by {@link Geometry#getQueueBucket() }. + * + * @param g The geometry to add + * @param bucket The bucket to add to, usually + * {@link Geometry#getQueueBucket() }. + */ + public void addToQueue(Geometry g, Bucket bucket) { + switch (bucket) { + case Gui: + guiList.add(g); + break; + case Opaque: + opaqueList.add(g); + break; + case Sky: + skyList.add(g); + break; + case Transparent: + transparentList.add(g); + break; + case Translucent: + translucentList.add(g); + break; + default: + throw new UnsupportedOperationException("Unknown bucket type: " + bucket); + } + } + + /** + * + * @param shadBucket + * @return + */ + public GeometryList getShadowQueueContent(ShadowMode shadBucket) { + switch (shadBucket) { + case Cast: + return shadowCast; + case Receive: + return shadowRecv; + default: + throw new IllegalArgumentException("Only Cast or Receive are allowed"); + } + } + + private void renderGeometryList(GeometryList list, RenderManager rm, Camera cam, boolean clear) { + list.setCamera(cam); // select camera for sorting + list.sort(); + for (int i = 0; i < list.size(); i++) { + Geometry obj = list.get(i); + assert obj != null; + rm.renderGeometry(obj); + obj.queueDistance = Float.NEGATIVE_INFINITY; + } + if (clear) { + list.clear(); + } + } + + public void renderShadowQueue(GeometryList list, RenderManager rm, Camera cam, boolean clear) { + renderGeometryList(list, rm, cam, clear); + } + + public void renderShadowQueue(ShadowMode shadBucket, RenderManager rm, Camera cam, boolean clear) { + switch (shadBucket) { + case Cast: + renderGeometryList(shadowCast, rm, cam, clear); + break; + case Receive: + renderGeometryList(shadowRecv, rm, cam, clear); + break; + default: + throw new IllegalArgumentException("Unexpected shadow bucket: " + shadBucket); + } + } + + public boolean isQueueEmpty(Bucket bucket) { + switch (bucket) { + case Gui: + return guiList.size() == 0; + case Opaque: + return opaqueList.size() == 0; + case Sky: + return skyList.size() == 0; + case Transparent: + return transparentList.size() == 0; + case Translucent: + return translucentList.size() == 0; + default: + throw new UnsupportedOperationException("Unsupported bucket type: " + bucket); + } + } + + public void renderQueue(Bucket bucket, RenderManager rm, Camera cam) { + renderQueue(bucket, rm, cam, true); + } + + public void renderQueue(Bucket bucket, RenderManager rm, Camera cam, boolean clear) { + switch (bucket) { + case Gui: + renderGeometryList(guiList, rm, cam, clear); + break; + case Opaque: + renderGeometryList(opaqueList, rm, cam, clear); + break; + case Sky: + renderGeometryList(skyList, rm, cam, clear); + break; + case Transparent: + renderGeometryList(transparentList, rm, cam, clear); + break; + case Translucent: + renderGeometryList(translucentList, rm, cam, clear); + break; + + default: + throw new UnsupportedOperationException("Unsupported bucket type: " + bucket); + } + } + + public void clear() { + opaqueList.clear(); + guiList.clear(); + transparentList.clear(); + translucentList.clear(); + skyList.clear(); + shadowCast.clear(); + shadowRecv.clear(); + } +} diff --git a/engine/src/core/com/jme3/renderer/queue/TransparentComparator.java b/engine/src/core/com/jme3/renderer/queue/TransparentComparator.java new file mode 100644 index 0000000..0821eba --- /dev/null +++ b/engine/src/core/com/jme3/renderer/queue/TransparentComparator.java @@ -0,0 +1,102 @@ +/* + * 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.queue; + +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; + +public class TransparentComparator implements GeometryComparator { + + private Camera cam; + private final Vector3f tempVec = new Vector3f(); + + public void setCamera(Camera cam){ + this.cam = cam; + } + + /** + * Calculates the distance from a spatial to the camera. Distance is a + * squared distance. + * + * @param spat + * Spatial to distancize. + * @return Distance from Spatial to camera. + */ + private float distanceToCam2(Geometry spat){ + if (spat == null) + return Float.NEGATIVE_INFINITY; + + if (spat.queueDistance != Float.NEGATIVE_INFINITY) + return spat.queueDistance; + + Vector3f camPosition = cam.getLocation(); + Vector3f viewVector = cam.getDirection(); + Vector3f spatPosition = null; + + if (spat.getWorldBound() != null){ + spatPosition = spat.getWorldBound().getCenter(); + }else{ + spatPosition = spat.getWorldTranslation(); + } + + spatPosition.subtract(camPosition, tempVec); + spat.queueDistance = tempVec.dot(tempVec); + + float retval = Math.abs(tempVec.dot(viewVector) + / viewVector.dot(viewVector)); + viewVector.mult(retval, tempVec); + + spat.queueDistance = tempVec.length(); + + return spat.queueDistance; + } + + private float distanceToCam(Geometry spat){ + // NOTE: It is best to check the distance + // to the bound's closest edge vs. the bound's center here. + return spat.getWorldBound().distanceToEdge(cam.getLocation()); + } + + public int compare(Geometry o1, Geometry o2) { + float d1 = distanceToCam(o1); + float d2 = distanceToCam(o2); + + if (d1 == d2) + return 0; + else if (d1 < d2) + return 1; + else + return -1; + } +} |