/* | |
* 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.set(worldMatrix); | |
tempMat4.invertLocal(); | |
u.setValue(VarType.Matrix4, tempMat4); | |
break; | |
case WorldMatrixInverseTranspose: | |
worldMatrix.toRotationMatrix(tempMat3); | |
tempMat3.invertLocal().transposeLocal(); | |
u.setValue(VarType.Matrix3, tempMat3); | |
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 ResolutionInverse: | |
tempVec2.set(1f / viewWidth, 1f / 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 (VertexBuffer vb : mesh.getBufferList().getArray()) { | |
if (vb.getData() != null) { | |
renderer.updateBufferData(vb); | |
} | |
} | |
} | |
} | |
} | |
/** | |
* 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); | |
} | |
} | |
} | |
} |