Add new occluders to ShadowedRenderPass

Hi Folks,



I am using SimplePassGame with a RenderPass and a ShadowedRenderPass.  I'd like the ability to add new occluder geometry.  For example, in simpleUpdate() I am calling this function (not every frame, just when I want to add a box at x, y):



    private void addCube(float x, float y)
    {
        Spatial b = new Box("BOX", new Vector3f(-1, -1, -1), new Vector3f(1, 1, 1));
        b.setLocalScale(0.25f);
        b.setLocalTranslation(x, y, 0.5f);
        b.setModelBound(new BoundingSphere());
        b.updateModelBound();
        rootNode.attachChild(b);
        shadowPass.addOccluder(b);
    }



When I do this, I get the following stack dump:


SEVERE: Exception in game loop
java.lang.NullPointerException
   at com.jme.scene.shadow.MeshShadows.createGeometry(MeshShadows.java:129)
   at com.jme.renderer.pass.ShadowedRenderPass.generateVolumes(ShadowedRenderPass.java:525)
   at com.jme.renderer.pass.ShadowedRenderPass.doRender(ShadowedRenderPass.java:313)
   at com.jme.renderer.pass.Pass.renderPass(Pass.java:90)
   at com.jme.renderer.pass.BasicPassManager.renderPasses(BasicPassManager.java:89)
   at com.jme.app.SimplePassGame.render(SimplePassGame.java:84)
   at com.jme.app.BaseGame.start(BaseGame.java:82)
   at jmestuff.JmeHexViewer.main(JmeHexViewer.java:35)



I suspect I have a threading issue in which I am trying to add geometry at the same time it's being rendered.  However, I don't see an obvious hook for adding further occlusion geometry.  Is there a particular pass or function I should be calling to add the new occluder?

Thanks!

You could probably move that into a callable class and call it through the GameTaskQueueManager to ensure it runs in the opengl thread.  Something like:


    private void addCube(final float x, final float y)
    {
        Callable<?> exe = new Callable() {
            public Object call() {
                Spatial b = new Box("BOX", new Vector3f(-1, -1, -1), new Vector3f(1, 1, 1));
                b.setLocalScale(0.25f);
                b.setLocalTranslation(x, y, 0.5f);
                b.setModelBound(new BoundingSphere());
                b.updateModelBound();
                rootNode.attachChild(b);
                shadowPass.addOccluder(b);
                return null;
            }
        };
        GameTaskQueueManager.getManager()
                        .getQueue(GameTaskQueue.RENDER).enqueue(exe);
    }

Thanks for the suggestion.  I tried it out and I still get a crash.  Here's a complete program for anyone out there willing to try it.  I'd at least like to know if you are getting a crash or not.  If you know what I can do to actually fix the problem and post it here, that would be great.  In the mean, time, I hope the code is helpful to someone out there.



package jmestuff;

import static java.lang.Math.*;

import java.util.concurrent.Callable;

import com.jme.app.SimplePassGame;
import com.jme.bounding.BoundingSphere;
import com.jme.image.Texture;
import com.jme.input.*;
import com.jme.input.action.*;
import com.jme.intersection.*;
import com.jme.light.PointLight;
import com.jme.math.*;
import com.jme.renderer.*;
import com.jme.renderer.pass.*;
import com.jme.scene.*;
import com.jme.scene.batch.GeomBatch;
import com.jme.scene.shape.*;
import com.jme.scene.state.*;
import com.jme.system.DisplaySystem;
import com.jme.util.*;

public class OccluderAdder extends SimplePassGame
{
    private RenderPass renderPass = new RenderPass();
    private ShadowedRenderPass shadowPass = new ShadowedRenderPass();
    private AbsoluteMouse mouse;
   
    public static void main(String[] args)
    {
        OccluderAdder app = new OccluderAdder();
        app.start();
    }

    public OccluderAdder()
    {
        stencilBits = 8;
    }
   
    @Override
    protected void simpleInitGame()
    {
        input = new InputHandler();
        setupMouse();
       
        //Place 4 tiles here to be our pick targets
        for(int i = 0; i < 4; i++)
        {
            MaterialState mat = DisplaySystem.getDisplaySystem().getRenderer()
            .createMaterialState();
            mat.setDiffuse(new ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f));
       
            Spatial hex = new Hexagon("HEX_" + i, 1.0f);
            hex.setLocalTranslation((float) sin(i * PI * 0.5f), (float) cos(i * PI * 0.5f), 0);
            hex.setLocalScale(0.9f);
            hex.setRenderState(mat);
       
            hex.setModelBound(new BoundingSphere());
            hex.updateModelBound();
       
            rootNode.attachChild(hex);
        }
       
        renderPass.add(rootNode);
        shadowPass.add(rootNode);
       
        shadowPass.setRenderShadows(true);
        shadowPass.setLightingMethod(ShadowedRenderPass.ADDITIVE);

        pManager.add(renderPass);
        pManager.add(shadowPass);

        RenderPass rPass = new RenderPass();
       
        rPass.add(fpsNode);
        pManager.add(rPass);
       
        addCube(0.0f, 0.0f);
       
        setupLights();

        Vector3f loc = new Vector3f(10, 0, 10);
        Vector3f dir = new Vector3f(loc);
        dir.negate();
        dir.normalize();
        Vector3f up = new Vector3f(0, 0, 1);
        Vector3f l = up.cross(dir);
        l.normalize();
        up = dir.cross(l);
        cam.setLocation(loc);
        cam.setAxes(l, up, dir);
        cam.update();

        Vector3f lookAt = new Vector3f(2, 0, 0);
        cam.setLocation(loc);
        cam.lookAt(lookAt, new Vector3f(0, 0, 1));
        cam.update();
       
        InputAction clickAction = new InputAction()
        {
            public void performAction(InputActionEvent evt)
            {
                // Is button 0 down? Button 0 is left click
                if(MouseInput.get().isButtonDown(0))
                {
                    Vector2f screenPos = new Vector2f();
                    // Get the position that the mouse is pointing to
                    screenPos.set(mouse.getHotSpotPosition().x, mouse
                            .getHotSpotPosition().y);
                    // Get the world location of that X,Y value
                    Vector3f worldCoords = display.getWorldCoordinates(screenPos, 1.0f);
                    // Create a ray starting from the camera, and going in the direction
                    // of the mouse's location
                    final Ray mouseRay = new Ray(cam.getLocation(), worldCoords
                            .subtractLocal(cam.getLocation()));
                    mouseRay.getDirection().normalizeLocal();
                    pick(mouseRay);
                }
            }
        };
        input.addAction(clickAction, InputHandler.DEVICE_MOUSE,
                InputHandler.BUTTON_ALL, InputHandler.AXIS_ALL, false);
    }
   
    private void setupLights()
    {
        lightState.detachAll();
       
        PointLight light = new PointLight();
        light.setAmbient(new ColorRGBA(0f, 0f, 0f, 1.0f));
        light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
        light.setEnabled(true);
        light.setLocation(new Vector3f(10, 10, 10));
        light.setShadowCaster(true);
        lightState.attach(light);

        light = new PointLight();
        light.setAmbient(new ColorRGBA(0f, 0f, 0f, 1.0f));
        light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
        light.setEnabled(true);
        light.setLocation(new Vector3f(-7, -5, 20));
        light.setShadowCaster(true);
        lightState.attach(light);

        lightState.setGlobalAmbient(new ColorRGBA(0.4f, 0.4f, 0.4f, 1.0f));
    }
   
    private void addCubeCallable(final float x, final float y)
    {
        System.out.println("Attempting to add an item centered at " + x + ", " + y + ", 0.5f.");
        Callable<?> exe = new Callable() {
            public Object call() {
                Spatial b = new Box("BOX", new Vector3f(-1, -1, -1), new Vector3f(1, 1, 1));
                b.setLocalScale(0.25f);
                b.setLocalTranslation(x, y, 0.5f);
                b.setModelBound(new BoundingSphere());
                b.updateModelBound();
                rootNode.attachChild(b);
                shadowPass.addOccluder(b);
                return null;
            }
        };
        GameTaskQueueManager.getManager()
                        .getQueue(GameTaskQueue.RENDER).enqueue(exe);
    }
   
    private void addCube(float x, float y)
    {
        Spatial b = new Box("BOX", new Vector3f(-1, -1, -1), new Vector3f(1, 1, 1));
        b.setLocalScale(0.25f);
        b.setLocalTranslation(x, y, 0.5f);
        b.setModelBound(new BoundingSphere());
        b.updateModelBound();
        rootNode.attachChild(b);
        shadowPass.addOccluder(b);
    }
   
    private void setupMouse()
    {
        // Setup software mouse
        mouse = new AbsoluteMouse("Mouse Input", display.getWidth(), display
                .getHeight());
        //mouse.registerWithInputHandler(input);
        TextureState cursor = display.getRenderer().createTextureState();
        cursor.setTexture(TextureManager.loadTexture(JmeHexViewer.class
                .getClassLoader().getResource("textures/cursor1.png"),
                Texture.MM_LINEAR, Texture.FM_LINEAR));
        mouse.setRenderState(cursor);
       
        AlphaState as1 = display.getRenderer().createAlphaState();
        as1.setBlendEnabled(true);
        as1.setSrcFunction(AlphaState.SB_SRC_ALPHA);
        as1.setDstFunction(AlphaState.DB_ONE_MINUS_SRC_ALPHA);
        as1.setTestEnabled(true);
        as1.setTestFunction(AlphaState.TF_GREATER);
        mouse.setRenderState(as1);

        mouse.setCullMode(Spatial.CULL_ALWAYS);
        MouseInput.get().setHardwareCursor(
                JmeHexViewer.class.getClassLoader().getResource(
                        "textures/cursor1.png"));
        // correction due to different hotspot positions between
        // hardware/software. not needed when using only hardware etc.
        MouseInput.get()
                .setCursorPosition(
                        (int) mouse.getLocalTranslation().x
                                - mouse.getImageWidth() / 2,
                        (int) mouse.getLocalTranslation().y
                                + mouse.getImageHeight() / 2);
        mouse.registerWithInputHandler(input);
        rootNode.attachChild(mouse);
    }

    private void pick(Ray ray)
    {
        PickResults results = new TrianglePickResults();
        results.setCheckDistance(true);
        rootNode.findPick(ray, results);
        if (results.getNumber() > 0)
        {
            PickData data = results.getPickData(0);
            GeomBatch mesh = data.getTargetMesh();
            String name = mesh.getName();
            if (name == null && mesh.getParentGeom() != null)
                name = mesh.getParentGeom().getName();
            System.out.println("Clicked on " + name);
            if (name != null)
            {
                String[] s = name.split("_");
                if (s.length == 2)
                {
                    int i= Integer.parseInt(s[1]);
                    float dx = (float) sin(i * PI * 0.5f);
                    float dy =  (float) cos(i * PI * 0.5f);
                    addCubeCallable((float) dx, (float) dy);
                }
            }
        }
        else
        {
            System.out.println("Didn't click on anything.");
        }
    }
   
    @Override
    protected void cleanup()
    {
        super.cleanup();
        if ( input != null ) {
            input.clearActions();
        }
    }
}

If I comment out line 160 (shadowPass.addOccluder(b):wink: I can add geometry, but it doesn't occlude.

Oh, sorry, I get a little trigger happy when I see a NPE.  The NPE in your stack is referencing a null LightState.  You can get rid of the addCubeCallable and just add a b.updateRenderStates(); after you attach the box to the rootNode.  This will update the states of the box and the shadow builder will have a lightstate to work with.

Cool!  That works.  If anyone finds it useful, here's the complete working example.



Thanks again for the help!



package jmestuff;

import static java.lang.Math.*;

import java.util.concurrent.Callable;

import com.jme.app.SimplePassGame;
import com.jme.bounding.BoundingSphere;
import com.jme.image.Texture;
import com.jme.input.*;
import com.jme.input.action.*;
import com.jme.intersection.*;
import com.jme.light.PointLight;
import com.jme.math.*;
import com.jme.renderer.*;
import com.jme.renderer.pass.*;
import com.jme.scene.*;
import com.jme.scene.batch.GeomBatch;
import com.jme.scene.shape.*;
import com.jme.scene.state.*;
import com.jme.system.DisplaySystem;
import com.jme.util.*;

public class OccluderAdder extends SimplePassGame
{
    private RenderPass renderPass = new RenderPass();
    private ShadowedRenderPass shadowPass = new ShadowedRenderPass();
    private AbsoluteMouse mouse;
   
    public static void main(String[] args)
    {
        OccluderAdder app = new OccluderAdder();
        app.start();
    }

    public OccluderAdder()
    {
        stencilBits = 8;
    }
   
    @Override
    protected void simpleInitGame()
    {
        input = new InputHandler();
        setupMouse();
       
        //Place 4 tiles here to be our pick targets
        for(int i = 0; i < 4; i++)
        {
            MaterialState mat = DisplaySystem.getDisplaySystem().getRenderer()
            .createMaterialState();
            mat.setDiffuse(new ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f));
       
            Spatial hex = new Hexagon("HEX_" + i, 1.0f);
            hex.setLocalTranslation((float) sin(i * PI * 0.5f), (float) cos(i * PI * 0.5f), 0);
            hex.setLocalScale(0.9f);
            hex.setRenderState(mat);
       
            hex.setModelBound(new BoundingSphere());
            hex.updateModelBound();
       
            rootNode.attachChild(hex);
        }
       
        renderPass.add(rootNode);
        shadowPass.add(rootNode);
       
        shadowPass.setRenderShadows(true);
        shadowPass.setLightingMethod(ShadowedRenderPass.ADDITIVE);

        pManager.add(renderPass);
        pManager.add(shadowPass);

        RenderPass rPass = new RenderPass();
       
        rPass.add(fpsNode);
        pManager.add(rPass);
       
        addCube(0.0f, 0.0f);
       
        setupLights();

        Vector3f loc = new Vector3f(10, 0, 10);
        Vector3f dir = new Vector3f(loc);
        dir.negate();
        dir.normalize();
        Vector3f up = new Vector3f(0, 0, 1);
        Vector3f l = up.cross(dir);
        l.normalize();
        up = dir.cross(l);
        cam.setLocation(loc);
        cam.setAxes(l, up, dir);
        cam.update();

        Vector3f lookAt = new Vector3f(2, 0, 0);
        cam.setLocation(loc);
        cam.lookAt(lookAt, new Vector3f(0, 0, 1));
        cam.update();
       
        InputAction clickAction = new InputAction()
        {
            public void performAction(InputActionEvent evt)
            {
                // Is button 0 down? Button 0 is left click
                if(MouseInput.get().isButtonDown(0))
                {
                    Vector2f screenPos = new Vector2f();
                    // Get the position that the mouse is pointing to
                    screenPos.set(mouse.getHotSpotPosition().x, mouse
                            .getHotSpotPosition().y);
                    // Get the world location of that X,Y value
                    Vector3f worldCoords = display.getWorldCoordinates(screenPos, 1.0f);
                    // Create a ray starting from the camera, and going in the direction
                    // of the mouse's location
                    final Ray mouseRay = new Ray(cam.getLocation(), worldCoords
                            .subtractLocal(cam.getLocation()));
                    mouseRay.getDirection().normalizeLocal();
                    pick(mouseRay);
                }
            }
        };
        input.addAction(clickAction, InputHandler.DEVICE_MOUSE,
                InputHandler.BUTTON_ALL, InputHandler.AXIS_ALL, false);
    }
   
    private void setupLights()
    {
        lightState.detachAll();
       
        PointLight light = new PointLight();
        light.setAmbient(new ColorRGBA(0f, 0f, 0f, 1.0f));
        light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
        light.setEnabled(true);
        light.setLocation(new Vector3f(10, 10, 10));
        light.setShadowCaster(true);
        lightState.attach(light);

        light = new PointLight();
        light.setAmbient(new ColorRGBA(0f, 0f, 0f, 1.0f));
        light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
        light.setEnabled(true);
        light.setLocation(new Vector3f(-7, -5, 20));
        light.setShadowCaster(true);
        lightState.attach(light);

        lightState.setGlobalAmbient(new ColorRGBA(0.4f, 0.4f, 0.4f, 1.0f));
    }
   
    private void addCube(float x, float y)
    {
        Spatial b = new Box("BOX", new Vector3f(-1, -1, -1), new Vector3f(1, 1, 1));
        b.setLocalScale(0.25f);
        b.setLocalTranslation(x, y, 0.5f);
        b.setModelBound(new BoundingSphere());
        b.updateModelBound();
        rootNode.attachChild(b);
        shadowPass.addOccluder(b);
        //Add this line to make the occluder work
        b.updateRenderState();
    }
   
    private void setupMouse()
    {
        // Setup software mouse
        mouse = new AbsoluteMouse("Mouse Input", display.getWidth(), display
                .getHeight());
        //mouse.registerWithInputHandler(input);
        TextureState cursor = display.getRenderer().createTextureState();
        cursor.setTexture(TextureManager.loadTexture(JmeHexViewer.class
                .getClassLoader().getResource("textures/cursor1.png"),
                Texture.MM_LINEAR, Texture.FM_LINEAR));
        mouse.setRenderState(cursor);
       
        AlphaState as1 = display.getRenderer().createAlphaState();
        as1.setBlendEnabled(true);
        as1.setSrcFunction(AlphaState.SB_SRC_ALPHA);
        as1.setDstFunction(AlphaState.DB_ONE_MINUS_SRC_ALPHA);
        as1.setTestEnabled(true);
        as1.setTestFunction(AlphaState.TF_GREATER);
        mouse.setRenderState(as1);

        mouse.setCullMode(Spatial.CULL_ALWAYS);
        MouseInput.get().setHardwareCursor(
                JmeHexViewer.class.getClassLoader().getResource(
                        "textures/cursor1.png"));
        // correction due to different hotspot positions between
        // hardware/software. not needed when using only hardware etc.
        MouseInput.get()
                .setCursorPosition(
                        (int) mouse.getLocalTranslation().x
                                - mouse.getImageWidth() / 2,
                        (int) mouse.getLocalTranslation().y
                                + mouse.getImageHeight() / 2);
        mouse.registerWithInputHandler(input);
        rootNode.attachChild(mouse);
    }

    private void pick(Ray ray)
    {
        PickResults results = new TrianglePickResults();
        results.setCheckDistance(true);
        rootNode.findPick(ray, results);
        if (results.getNumber() > 0)
        {
            PickData data = results.getPickData(0);
            GeomBatch mesh = data.getTargetMesh();
            String name = mesh.getName();
            if (name == null && mesh.getParentGeom() != null)
                name = mesh.getParentGeom().getName();
            System.out.println("Clicked on " + name);
            if (name != null)
            {
                String[] s = name.split("_");
                if (s.length == 2)
                {
                    int i= Integer.parseInt(s[1]);
                    float dx = (float) sin(i * PI * 0.5f);
                    float dy =  (float) cos(i * PI * 0.5f);
                    addCube((float) dx, (float) dy);
                }
            }
        }
        else
        {
            System.out.println("Didn't click on anything.");
        }
    }
   
    @Override
    protected void cleanup()
    {
        super.cleanup();
        if ( input != null ) {
            input.clearActions();
        }
    }
}