DLOD Spheres being Culled when visible

So, I have been noticing a very strange problem with the DLOD objects,  basically I have created several DLOD spheres and when any sphere is outside the frustum and moves back into view on it's own the object is ALWAYS culled.  Turning on the physics reveals, that yes the object is there and interacting with the other objects (the physics wireframe is always visible).  However the textured sphere itself is culled. 



These spheres are all created and then added to a single node then added to the rootNode.  Is this an incorrect approach


/*

import com.jme.bounding.BoundingSphere;
import com.jme.image.Texture;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.DistanceSwitchModel;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.lod.DiscreteLodNode;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jmex.physics.DynamicPhysicsNode;
import com.jmex.physics.callback.FrictionCallback;
import com.jmex.physics.contact.MutableContactInfo;
import com.jmex.physics.material.Material;


public class Balls {
   
    Node ballsNode = null;
    Ball balls[] = null;
   
    Material ballMaterial = null;
    MutableContactInfo ballContactDetails = null;
   
    Jme_Driver jme_Driver = null;
   
   
    protected Balls( Jme_Driver parent ) {
        jme_Driver = parent;
    }
   
   
    protected void createBalls() {
       
        ballsNode = new Node();
       
        int numBalls = jme_Driver.getDriver().getNumberOfBalls();
       
        setMaterial();
       
        balls = new Ball[ numBalls ];
       
        for ( int i=0; i < numBalls; ++i ) {
            balls[i] = new Ball( i );
            ballsNode.attachChild( balls[i].getBall() );
        }
       
        rackEm();
    }
   
   
    protected Node getBalls() {
        return ballsNode;
    }
   
   
    protected void rackEm() {
       
        Vector3f position = new Vector3f();
        float [] ballPosition;
       
        for ( int i=0; i < balls.length; ++i ) {
           
            ballPosition = jme_Driver.getDriver().getLogic().getInitPosition( i );
            position.x = ballPosition[0];
            position.z = ballPosition[1];
            balls[i].setLocation( position );
        }
    }
   
   
   
    protected Vector3f getLocation( int ballNumber ) {
        return balls[0].getLocation();
    }
   
   
    protected Material getMaterial() {
        return ballMaterial;
    }
   
    protected MutableContactInfo getContactDetails() {
        return ballContactDetails;
    }
   
   
    private void setMaterial() {
        ballMaterial = new Material( "Ball Material" );
        ballMaterial.setDensity( 1.25f );
        ballMaterial.setSpringPenetrationDepth( 0 );
       
        ballContactDetails = new MutableContactInfo();
        ballContactDetails.setBounce( 0.35f );
        ballContactDetails.setMu( 0.15f );
        ballContactDetails.setSpringConstant( 0.01f );
        ballContactDetails.setDampingCoefficient( 10000 );
        ballContactDetails.setMinimumBounceVelocity( 0.01f );
    }
   
   
   
   
   
    private class Ball {
       
        private String ballName = "";
       
        private DynamicPhysicsNode physicsBall = null;
       
       
        private Ball( int number ) {
            ballName = "ball_" + number;
            DiscreteLodNode dlod = buildBall();
            textureBall( dlod, number );
           
            Utils.color( dlod, ColorRGBA.white, 128 );
            dlod.setLocalRotation( new Quaternion().fromAngleAxis(
                    -90 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X ) );
           
            physicsBall = jme_Driver.getPhysics().createDynamicNode();
            physicsBall.setName( "Physics_" + ballName );
            physicsBall.attachChild( dlod );
            physicsBall.setMaterial( ballMaterial );
           
            physicsBall.generatePhysicsGeometry();
           
         
            physicsBall.setMass( Globals.ballMass );
            
            FrictionCallback callback = new FrictionCallback();
            callback.add( physicsBall, 0.15f, 0.15f );
           
            jme_Driver.getPhysics().addToUpdateCallbacks(callback);
           
            physicsBall.lockMeshes();
        }
       
       
        private DynamicPhysicsNode getBall() {
            return physicsBall;
        }
       
        private void setLocation( Vector3f location ) {
            physicsBall.getLocalTranslation().set( location );
        }
       
        private Vector3f getLocation() {
            return physicsBall.getLocalTranslation();
        }
       
       
        private void shootBall() {
            physicsBall.addForce( new Vector3f( 0, 0, 400000 ) );
        }
       
       
       
       
        private DiscreteLodNode buildBall() {
           
            Sphere s1 = new Sphere( ballName, 50, 50, Globals.ballSize );
            s1.setModelBound(new BoundingSphere() );
            s1.updateModelBound();
            s1.setTextureMode( Sphere.TEX_PROJECTED );
            // s1.setVBOInfo(new VBOInfo(true));
           
            Sphere s2 = new Sphere( ballName, 25, 25, Globals.ballSize );
            s2.setModelBound(new BoundingSphere() );
            s2.updateModelBound();
            s2.setTextureMode( Sphere.TEX_PROJECTED );
            // s2.setVBOInfo(new VBOInfo(true));
           
            Sphere s3 = new Sphere( ballName, 12, 12, Globals.ballSize );
            s3.setModelBound(new BoundingSphere() );
            s3.updateModelBound();
            s3.setTextureMode( Sphere.TEX_PROJECTED );
            //   s3.setVBOInfo(new VBOInfo(true));
           
            Sphere s4 = new Sphere( ballName, 6, 6, Globals.ballSize );
            s4.setModelBound(new BoundingSphere() );
            s4.updateModelBound();
            s4.setTextureMode( Sphere.TEX_PROJECTED );
            //   s4.setVBOInfo(new VBOInfo(true));
           
            DistanceSwitchModel m = new DistanceSwitchModel(4);
            m.setModelDistance( 0, 0, 50 );
            m.setModelDistance( 1, 50, 200);
            m.setModelDistance( 2, 200, 500);
            m.setModelDistance( 3, 500, 10000 );
           
            DiscreteLodNode dlod = new DiscreteLodNode("DLOD", m);
            dlod.attachChild(s1);
            dlod.attachChild(s2);
            dlod.attachChild(s3);
            dlod.attachChild(s4);
            dlod.setActiveChild(0);
           
            return dlod;
        }
       
       
        protected void textureBall( Spatial spatial, int ballNumber ) {
           
            DisplaySystem display = DisplaySystem.getDisplaySystem();
           
            TextureState textureState = display.getRenderer().createTextureState();
            Texture texture = TextureManager.loadTexture(
                    getClass().getResource( "/Images/Ball_Textures/ball_" + ballNumber + ".png" ),
                    Texture.MM_LINEAR_LINEAR,
                    Texture.FM_LINEAR );
            textureState.setTexture( texture );
           
            textureState.getTexture().setScale( new Vector3f( 2, 1, 1 ) );
            textureState.getTexture().setWrap( Texture.WM_WRAP_S_CLAMP_T );
           
            spatial.setRenderState(textureState);
        }
       
       
    }
   
}




Here is the simpleInitGame code that loads the balls.


    @Override
    protected void simpleInitGame() {
       
        DisplaySystem display = DisplaySystem.getDisplaySystem();
       
       
        table.buildTable();
        balls.createBalls();

       
        // attach everything to the root node
        rootNode.attachChild( balls.getBalls() );
        rootNode.attachChild( table.getTable() );

       
        rootNode.updateGeometricState( 0, true );
        rootNode.updateRenderState();

       
        // Set our keys
        input.addAction( new InputAction() {
            public void performAction( InputActionEvent evt ) {
                if ( evt.getTriggerPressed() ) {
                    balls.shootBall( "Ball" );
                }
            }
        }, InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_SPACE, InputHandler.AXIS_NONE, false );
       
       
        Text label = Text.createDefaultTextLabel( "instructions", "Press [space] to spawn a ball" );
        label.setLocalTranslation( 0, 20, 0 );
        fpsNode.attachChild( label );

    }




When a single sphere is substituted for the DLOD ones, the problem completly vanishes and the sphere is always visible when it should be.

If this is unclear and someone would like me to make a demonstration I would be glad to.

Your setup of the DLOD balls themselves seems fine (almost the same as the test.)  If you are using eclipse, you might add a breakpoint in the draw method of the DLOD node and see what's going on there.



My guess is that the distance to the model is for some reason coming back as either < 0 or greater than your 10000 max.  That would effectively turn off the children.

OUCH!!!





Setting a break point on the draw command (which is called 500*16 times per second) kinda makes actually looking at graphics impossible  :wink:



Also, break points don't seem to work with Netbeans on the jME ANT built graphics, but they do work in the non-ANT physics.



But your advice did get me looking in the right place and I thank you,  I just overrided the draw command and just printed the values.



Right now I am looking at the getLastFrustumIntersection() value which does indeed say to (frustum) cull the object even when the object is visible.  When the user (me) manually moves the camera (fast enough) the object magically reappears.  All the Dlod nodes do this.



Is there a specific value or place I should be looking at besides the draw object?  To me it seems that since the objects level of detail is indeed being altered correctly as the camera moves; it would indicate that the distance value is being correctly determined.  So I would think that somewhere in the Frustum stuff would be a more logical place to look.  Also, as I said in an earlier post, when a single sphere is used in place of the Dlod the object is always visible when it should be.

I forgot to mention that yes, it is very similar to the Dlod test except for one major difference.  The test does not have the ball moving in and out of the view frustum it's own, only the 'camera' is moving.

Okay, so it's a bounding problem.  An additional bounding sphere is placed around all the balls, if I place a bounding sphere on any of the ball parts (sphere, dlod or physics node); if I don't use bounding at all then the problem goes away.  I will probably figure out what I am doing wrong, but if anyone has any advice I would gladly take it.

So here is some more information  :| 





Here is the original DiscreeteLodNode code, that culls objects when they move back into a stationary frustum.



public class Dlod extends SwitchNode implements Savable {
    private static final long serialVersionUID = 1L;
    private Vector3f modelCenter;
    private Vector3f worldCenter=new Vector3f();
   
    private Vector3f tmpVs=new Vector3f();
   
    private float lastUpdate;
    private SwitchModel model;
   
    public Dlod() {}
   
    public Dlod(String name, SwitchModel model) {
        super(name);
        this.model = model;
       
        modelCenter = new Vector3f();
       
    }
   
    public void selectLevelOfDetail(Camera camera) {
        super.updateWorldData(lastUpdate);
        if(model == null) {
            return;
        }
        // compute world LOD center
        worldCenter = worldRotation.multLocal(worldCenter.set(modelCenter)).multLocal(worldScale).addLocal(worldTranslation);
       
        // compute world squared distance intervals
       
        float worldSqrScale = tmpVs.set(worldScale).multLocal(worldScale).length();
        model.set(worldCenter.subtractLocal(camera.getLocation()));
        model.set(new Float(worldSqrScale));
        setActiveChild(model.getSwitchChild());
       
    }
   
   
   
    public void updateWorldData(float time) {
        lastUpdate = time;
       
        updateWorldBound();
    }
   
    public void draw(Renderer r) {
        selectLevelOfDetail(r.getCamera());
        super.draw(r);
    }
   
    public void write(JMEExporter e) throws IOException {
        super.write(e);
        OutputCapsule capsule = e.getCapsule(this);
        capsule.write(modelCenter, "modelCenter", Vector3f.ZERO);
        capsule.write(worldCenter, "worldCenter", Vector3f.ZERO);
        capsule.write(model, "model", null);
    }
   
    public void read(JMEImporter e) throws IOException {
        super.read(e);
        InputCapsule capsule = e.getCapsule(this);
        modelCenter = (Vector3f)capsule.readSavable("modelCenter", Vector3f.ZERO.clone());
        worldCenter = (Vector3f)capsule.readSavable("worldCenter", Vector3f.ZERO.clone());
        model = (SwitchModel)capsule.readSavable("model", null);
    }
   
}




And here I have altered the code and moved the call to super.updateWorldData into the local updateWorldData, and MAGICALLY the problem goes away  :|


import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.Renderer;
import com.jme.scene.SwitchModel;
import com.jme.scene.SwitchNode;
import com.jme.scene.lod.DiscreteLodNode;
import com.jme.util.export.InputCapsule;
import com.jme.util.export.JMEExporter;
import com.jme.util.export.JMEImporter;
import com.jme.util.export.OutputCapsule;
import com.jme.util.export.Savable;
import java.io.IOException;


public class Dlod extends SwitchNode implements Savable {
    private static final long serialVersionUID = 1L;
    private Vector3f modelCenter;
    private Vector3f worldCenter=new Vector3f();
   
    private Vector3f tmpVs=new Vector3f();
   
    private float lastUpdate;
    private SwitchModel model;
   
    public Dlod() {}
   
    public Dlod(String name, SwitchModel model) {
        super(name);
        this.model = model;
       
        modelCenter = new Vector3f();
       
    }
   
    public void selectLevelOfDetail(Camera camera) {
       
        if(model == null) {
            return;
        }
        // compute world LOD center
        worldCenter = worldRotation.multLocal(worldCenter.set(modelCenter)).multLocal(worldScale).addLocal(worldTranslation);
       
        // compute world squared distance intervals
       
        float worldSqrScale = tmpVs.set(worldScale).multLocal(worldScale).length();
        model.set(worldCenter.subtractLocal(camera.getLocation()));
        model.set(new Float(worldSqrScale));
        setActiveChild(model.getSwitchChild());
       
    }
   
   
   
    public void updateWorldData(float time) {
        super.updateWorldData( time );
        lastUpdate = time;
       
        updateWorldBound();
    }
   
    public void draw(Renderer r) {
        selectLevelOfDetail(r.getCamera());
        super.draw(r);
    }
   
    public void write(JMEExporter e) throws IOException {
        super.write(e);
        OutputCapsule capsule = e.getCapsule(this);
        capsule.write(modelCenter, "modelCenter", Vector3f.ZERO);
        capsule.write(worldCenter, "worldCenter", Vector3f.ZERO);
        capsule.write(model, "model", null);
    }
   
    public void read(JMEImporter e) throws IOException {
        super.read(e);
        InputCapsule capsule = e.getCapsule(this);
        modelCenter = (Vector3f)capsule.readSavable("modelCenter", Vector3f.ZERO.clone());
        worldCenter = (Vector3f)capsule.readSavable("worldCenter", Vector3f.ZERO.clone());
        model = (SwitchModel)capsule.readSavable("model", null);
    }
   
}





OLD:


    public void selectLevelOfDetail(Camera camera) {
      super.updateWorldData( lastUpdate );
        if(model == null) {
            return;
        }
        // compute world LOD center
        worldCenter = worldRotation.multLocal(worldCenter.set(modelCenter)).multLocal(worldScale).addLocal(worldTranslation);
       
        // compute world squared distance intervals
       
        float worldSqrScale = tmpVs.set(worldScale).multLocal(worldScale).length();
        model.set(worldCenter.subtractLocal(camera.getLocation()));
        model.set(new Float(worldSqrScale));
        setActiveChild(model.getSwitchChild());
       
    }

    public void updateWorldData(float time) {
       
        lastUpdate = time;
       
        updateWorldBound();
    }




New:


    public void selectLevelOfDetail(Camera camera) {

        if(model == null) {
            return;
        }
        // compute world LOD center
        worldCenter = worldRotation.multLocal(worldCenter.set(modelCenter)).multLocal(worldScale).addLocal(worldTranslation);
       
        // compute world squared distance intervals
       
        float worldSqrScale = tmpVs.set(worldScale).multLocal(worldScale).length();
        model.set(worldCenter.subtractLocal(camera.getLocation()));
        model.set(new Float(worldSqrScale));
        setActiveChild(model.getSwitchChild());
       
    }

    public void updateWorldData(float time) {
        super.updateWorldData( time );
        lastUpdate = time;
       
        updateWorldBound();
    }


Please someone, take this seriously, I have spent many hours figuring out the problem and really feel that there is a bug in the DiscreeteLodNode class.

It looks like the original intention was to make sure the world vectors and bounds were correct before choosing a model.  For most uses, this will be already the case simply by relying on the normal update loop, so removing the overriding updateWorldData method will take care of the issue.  Please update from cvs and confirm.  (I've locally run your test to confirm as well.)



The only major drawback is that if you paused the update loop and flew around in the world, the DLOD node will now no longer change levels, however pausing the update loop would cause other issues and this scenario seems a bit unlikely.