/* * 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.Material; import com.jme3.material.RenderState; import com.jme3.math.Matrix3f; import com.jme3.math.Matrix4f; import com.jme3.math.Quaternion; import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; 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.scene.Geometry; import com.jme3.scene.Mesh; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.scene.VertexBuffer; import com.jme3.shader.Uniform; 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; /** * RenderManager 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 preViewPorts = new ArrayList(); private ArrayList viewPorts = new ArrayList(); private ArrayList postViewPorts = new ArrayList(); 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; /** * 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); } 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; } public boolean removePreView(ViewPort view) { return preViewPorts.remove(view); } 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; } 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; } public boolean removeMainView(ViewPort view) { return viewPorts.remove(view); } 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; } 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; } public boolean removePostView(ViewPort view) { return postViewPorts.remove(view); } public List getPreViews() { return Collections.unmodifiableList(preViewPorts); } public List getMainViews() { return Collections.unmodifiableList(viewPorts); } public List getPostViews() { return Collections.unmodifiableList(postViewPorts); } /** * Creates a new viewport, to display the given camera's content. * The view will be processed before the primary viewport. * @param viewName * @param cam * @return */ public ViewPort createPreView(String viewName, Camera cam) { ViewPort vp = new ViewPort(viewName, cam); preViewPorts.add(vp); return vp; } public ViewPort createMainView(String viewName, Camera cam) { ViewPort vp = new ViewPort(viewName, cam); viewPorts.add(vp); return vp; } 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 processors = vp.getProcessors(); for (SceneProcessor proc : processors) { if (!proc.isInitialized()) { proc.initialize(this, vp); } else { proc.reshape(vp, w, h); } } } /** * @param w * @param h */ 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); } } public void updateUniformBindings(List params) { // assums worldMatrix is properly set. TempVars vars = TempVars.get(); assert vars.lock(); 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; } } assert vars.unlock(); } /** * 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 */ public void setForcedMaterial(Material mat) { forcedMaterial = mat; } public RenderState getForcedRenderState() { return forcedRenderState; } public void setForcedRenderState(RenderState forcedRenderState) { this.forcedRenderState = forcedRenderState; } public void setWorldMatrix(Matrix4f mat) { if (shader) { worldMatrix.set(mat); } else { renderer.setWorldMatrix(mat); } } 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); } //re applying default render state at the end of the render to avoid depth write issues, MUST BE A BETTER WAY renderer.applyRenderState(RenderState.DEFAULT); } 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 through this * recursive method. * @param s * @param r */ private void renderShadow(Spatial s, RenderQueue rq) { if (s instanceof Node) { Node n = (Node) s; List 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); } } } public void preloadScene(Spatial scene) { if (scene instanceof Node) { // recurse for all children Node n = (Node) scene; List 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 entry : mesh.getBuffers()) { VertexBuffer buf = entry.getValue(); if (buf.getData() != null) { renderer.updateBufferData(buf); } } } } } /** * Render scene graph * @param s * @param r * @param cam */ public void renderScene(Spatial scene, ViewPort vp) { // check culling first. if (!scene.checkCulling(vp.getCamera())) { // move on to shadow-only render if (scene.getShadowMode() != RenderQueue.ShadowMode.Off || scene instanceof Node) { renderShadow(scene, vp.getQueue()); } return; } scene.runControlRender(this, vp); if (scene instanceof Node) { // recurse for all children Node n = (Node) scene; List children = n.getChildren(); for (int i = 0; i < children.size(); i++) { 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); } } } public Camera getCurrentCamera() { return prevCam; } public Renderer getRenderer() { return renderer; } /** * Render the given viewport queues, flushing the geometryList * @param vp the viewport */ public void flushQueue(ViewPort vp) { renderViewPortQueues(vp, true); } public void clearQueue(ViewPort vp) { vp.getQueue().clear(); } //Nehon 08/18/2010 changed flushQueue to renderViewPortQueues with a flush boolean param /** * Render the given viewport queues * @param vp the viewport * @param flush true to flush geometryList */ 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); } } 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()); } } } public void setCamera(Camera cam, boolean ortho) { setViewPort(cam); setViewProjection(cam, ortho); } /** * Draws the viewport but doesn't invoke processors. * @param vp */ public void renderViewPortRaw(ViewPort vp) { setCamera(vp.getCamera(), false); List scenes = vp.getScenes(); for (int i = scenes.size() - 1; i >= 0; i--) { renderScene(scenes.get(i), vp); } flushQueue(vp); } public void renderViewPort(ViewPort vp, float tpf) { if (!vp.isEnabled()) { return; } List processors = vp.getProcessors(); if (processors.size() == 0) { 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 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()); } } // clear any remaining spatials that were not rendered. clearQueue(vp); } public void render(float tpf) { if (renderer instanceof NullRenderer) { return; } this.shader = renderer.getCaps().contains(Caps.GLSL100); for (int i = 0; i < preViewPorts.size(); i++) { renderViewPort(preViewPorts.get(i), tpf); } for (int i = 0; i < viewPorts.size(); i++) { renderViewPort(viewPorts.get(i), tpf); } for (int i = 0; i < postViewPorts.size(); i++) { renderViewPort(postViewPorts.get(i), tpf); } } //Remy - 09/14/2010 - added a setter for the timer in order to correctly populate g_Time and g_Tpf in the shaders public void setTimer(Timer timer) { this.timer = timer; } public String getForcedTechnique() { return forcedTechnique; } public void setForcedTechnique(String forcedTechnique) { this.forcedTechnique = forcedTechnique; } public void setAlphaToCoverage(boolean value) { renderer.setAlphaToCoverage(value); } }