Removing a node correctly - detach/remove from parent leaves invisible node?

Hi guys,



I am working on a small game as my project and am using bulletmover - (Hello intersection) to move my bullet. I am trying to implement very simple mechanics where int health = 100 initially and bullet on colliding with the node of the enemy (.3ds robot) would take away 10 from health e.g. 10 shots and robot disappears(through detach)… However I have run into couple of unexpected problems…


  1. When I shoot even though the lifeTime of the bullet is set to 0 on collision the bullet does not disappear! I tried with walls node and it does but for these other ones(guardNode and enemyNode which contain .3ds robots) it doesnt??


  2. Once the health reaches 0 the objects contained in guardNode and enemyNode(.3ds robots) disappear but the node is still there(though invisible) - I shoot the bullet and through System.out it says collision detected with guardNode/enemyNode and bullets stick in those areas of their bounding… How can this be? Am I not deleting/removing node properly?




class BulletMover extends Controller
    {

        //private static final long serialVersionUID = 1L;

   TriMesh bullet;
   Vector3f direction;
   float speed = 100;
   float lifeTime = 5;
 
   BulletMover(TriMesh bullet, Vector3f direction)
        {
            this.bullet = bullet;
            this.direction = direction;
            this.direction.normalizeLocal();
   }
 
   public void update(float time)
        {
            lifeTime -= time;
         
            /** If life is gone, remove it */
            if (lifeTime < 0 || lifeTime == 0)
            {
                scene.detachChild(bullet);
                bullet.removeController(this);
                return;
            }

            Vector3f bulletPos = bullet.getLocalTranslation();
            bulletPos.addLocal(direction.mult(time * speed));
            bullet.setLocalTranslation(bulletPos);
         
            if(bullet.hasCollision(walls, false))
            {
                //logger.info("OWCH!!!");
                wallImpactSound.setWorldPosition(bullet.getLocalTranslation());
                wallImpactSound.play();
                lifeTime = 0;               
            }
           
            if(bullet.hasCollision(enemyNode, false))
            {
                System.out.println("Collision detected with enemyNode");
                health = health - 10;
                lifeTime = 0;
                System.out.println("Health of the enemy is: " + health);
            }
       
            if(health == 0)
            {              
                //enemyNode.detachAllChildren();
                scene.detachChild(enemyNode);   
               
            }
       
            if(bullet.hasCollision(guardNode, false))
            {
                System.out.println("Collision detected with guardNode");
                health1 = health1 - 10;
                lifeTime = 0;
                System.out.println("Health of the guard is: " +  health1);
            }
       
            if(health1 == 0)
            {
                //guardNode.detachAllChildren();
                scene.detachChild(guardNode);
               
            }
   }
    }       



Finally which is the best way to destroy a node - detach or removefromParent??

Thanks,

Dev

I don't see any call to UpdateGeometricState in the code… after detaching you need to tell jme something changed.

<offtopic>

Like in the Matrix, where the machines call the famous Matrix.dejaVu() method whenever they change something.

</offtopic>

Precisely!! I have added a call to both bullet.updateGeometricState(…) and scene.updateGeometricState in the shoot() method instead of the mover so now bullet and both enemy and guard nodes when detach are removed and are not just invisible… Although two black cats strutting around the invisible node would have been a good clue… :lol:



Thanks!!!



p.s. still I dont know what is the difference between removeFromParent and detachChild(…). According to definition:



removeFromParent removes this Spatial from its parent.



detachChild(…) removes a given child from the node's list. This child will no longer be maintained.



Sound similar. Also, this doesnt say that the nodes are actually destroyed. They are detached or removed from parent node and are not maintained. Does this mean they still exist in some parallel universe of unwanted nodes or they can be re-attached??  :? Do they still consume memory??

hawk2k8 said:

p.s. still I dont know what is the difference between removeFromParent and detachChild(..). According to definition:

removeFromParent removes this Spatial from its parent.

detachChild(..) removes a given child from the node's list. This child will no longer be maintained.

Sound similar. Also, this doesnt say that the nodes are actually destroyed. They are detached or removed from parent node and are not maintained. Does this mean they still exist in some parallel universe of unwanted nodes or they can be re-attached??  :? Do they still consume memory??


Sounds similar, is similar :)
If you look at the Source of removeFromParent(), you'll see that parent.detachChild(this) is called.
So both Methods really do the same, just from another starting point.

You can do for example rootNode.detachChild(sphere);  or sphere.removeFromParent();

detachChild() simply sets the parent reference of the child to null.
If no other references exits to this child spatial, it will get garbage collected and its memory free'd.
If you look at the Source of removeFromParent(), you'll see that parent.detachChild(this) is called.


I did! and you are right parent.detachChild(this) is called. Basically its two different ways of doing the same thing(and as you said at different starting points!)..  You learn something new at jme forum everyday!  :lol:


    public boolean removeFromParent() {

        if (parent != null) {

            parent.detachChild(this);

            return true;

        }

        return false;

    }



Thanks!!

Hi guys sorry to be a pain but the "invisible one" still stays… I added call to updateGeometricstate but there is still an invisible node… I checked this with other nodes as well e.g. I use health and ammo packs and when a player collides with those packs their health and ammo increase… on collision the node is detached and the box disappears… however when i pass through that place again System.out indicates I have again collided with that node and my ammo increases again… so node is still there… let me describe these methods briefly…



creating the health and ammo boxes - I am not using SimpleGame but BaseGame





    private Node n1;
    private Node n2;
    private Node n3;
    private Node n4;
    private Node n5;
    private Node n6;

private void createAmmunition()
    {
       
       
        Box b = new Box("ammo" , new Vector3f(), 4f, 4f, 4f);
   b.setModelBound(new BoundingBox());
   b.updateModelBound();
        URL ammoTexture;
        ammoTexture = Ares.class.getClassLoader().getResource("jmetest/data/texture/ammo.jpg");       
        TextureState ts=display.getRenderer().createTextureState();
        Texture t=TextureManager.loadTexture(ammoTexture,Texture.MM_LINEAR,Texture.FM_LINEAR);
        t.setWrap(Texture.WM_WRAP_S_WRAP_T);
        ts.setTexture(t);
        b.setRenderState(ts);
        b.setRenderQueueMode(Renderer.QUEUE_OPAQUE);       
       
        SharedMesh box1 = new SharedMesh("box", b);
        box1.setLocalTranslation((float)( -80 + Math.random()*280), 15f,  (float)(-80 + Math.random()*280));
               
        SharedMesh box2 = new SharedMesh("box", b);
        box2.setLocalTranslation((float)( -80 + Math.random()*280), 15f,  (float)(-80 + Math.random()*280));
       
        SharedMesh box3 = new SharedMesh("box", b);
        box3.setLocalTranslation((float)( -80 + Math.random()*280), 15f,  (float)(-80 + Math.random()*280));
       
        SharedMesh box4 = new SharedMesh("box", b);
        box4.setLocalTranslation((float)( -80 + Math.random()*280), 15f,  (float)(-80 + Math.random()*280));
       
        SharedMesh box5 = new SharedMesh("box", b);
        box5.setLocalTranslation((float)( -80 + Math.random()*280), 15f,  (float)(-80 + Math.random()*280));
       
        n1 = new Node("box");
        n2 = new Node("box");
        n3 = new Node("box");
        n4 = new Node("box");
        n5 = new Node("box");
        n6 = new Node("box");
        n1.attachChild(b);
        n2.attachChild(box1);
        n6.attachChild(box2);
        n3.attachChild(box3);
        n4.attachChild(box4);
        n5.attachChild(box5);
       
        scene.attachChild(n1);
        scene.attachChild(n2);
        scene.attachChild(n3);
        scene.attachChild(n4);
        scene.attachChild(n5);
        scene.attachChild(n6);
        //scene.attachChild(box5);
                                     
    }



This method detects collision, takes appropriate action and then updates scene(rootNode) to acknowledge detaching of any node and is called in update



private void healthAmmoPacks()
    {
       
        if(ammo < 12 || ammo == 12)
        {
            if(player1.hasCollision(n1, false))
            {           
                ammoCollectedSound.play();
                n1.removeFromParent();
                System.out.println("Collision Detected with the box");
                ammo = ammo + 10;
               
            }
       
            if(player1.hasCollision(n2, false))
            {           
                ammoCollectedSound.play();
                n2.removeFromParent();
                System.out.println("Collision Detected with the box");
                ammo = ammo + 10;
               
            }
       
            if(player1.hasCollision(n3, false))
            {           
                ammoCollectedSound.play();
                n3.removeFromParent();
                System.out.println("Collision Detected with the box");
                ammo = ammo + 10;
               
               
                       
            }
       
            if(player1.hasCollision(n4, false))
            {           
                ammoCollectedSound.play();
                n4.removeFromParent();
                System.out.println("Collision Detected with the box");
                ammo = ammo + 10;
               
            }
       
            if(player1.hasCollision(n5, false))
            {           
                ammoCollectedSound.play();
                n5.removeFromParent();
                System.out.println("Collision Detected with the box");
                ammo = ammo + 10;
               
            }
       
            if(player1.hasCollision(n6, false))
            {           
                ammoCollectedSound.play();
                n6.removeFromParent();
                System.out.println("Collision Detected with Ammo Node");
                ammo = ammo + 10;
              
            }
            
             scene.updateGeometricState(0f, true);
        }
    }



p.s. createAmmunition is called in initGame where scene.updateGeometricState(0f, true) and scene.updateRenderState are called and hence they are not called in createAmmunition.

Hi,



<offtopic>

Thanks with regards to you help with collision detection - I figured the bug out. Collision detection is fine but the problem seems to be that on collision all I am doing is just resetting the location without resetting the velocity. I need to reset the velocity to 0 and then reset the location. I am looking at various ways of doing it but best one it seems is rewrite the handler. Anyway thanks!!

</offtopic>



I would just like to say I am happy to listen to all the comments and criticisms that you guys have and am grateful that you take time out to do so especially as you dont have to. I know I am not a strong programmer but am practicing and working on it at all times now and in the short time 4-6 weeks that I have left my primary target is delivering a working game for my 60 credit project and in that sometimes I dont pay attention to general coding conventions as long as something works - which is wrong but I try and avoid that mostly.



Now I rewritten the code and now I check for the objects/trimesh in a scene/world colliding with nodes/spatial as the method is set out to do instead of node colliding with other nodes. But pardon my ignorance here but is it not the point here that the collision is being detected but the node is not being removed properly?? I mean it detects that the object and spatial collided and the object disappears/becomes invisible but when i go to the place it is still there when it should have been completely wiped?? By the way the even after rewriting the code to check for collision between object and spatial the problem still persists… and I have been murdering scene node as you pointed out and have changed it to appear at the end of the method… The revised code for the my "collision method" is as below, removeFromParent/detachChild just makes the box invisible instead of removing it thats what is surprising me at this minute…




 private Box b;
 private SharedMesh box1;
 private SharedMesh box2;
 private SharedMesh box3;
 private SharedMesh box4;
 private SharedMesh box5;

private void healthAmmoPacks()
{

 if(ammo < 12 || ammo == 12)
        {
            if(b.hasCollision(player1, true))
            {           
                ammoCollectedSound.play();
                b.removeFromParent();
                System.out.println("Collision Detected with the box");
                ammo = ammo + 10;
               
            }
       
            if(box1.hasCollision(player1, true))
            {           
                ammoCollectedSound.play();
                box1.removeFromParent();
                System.out.println("Collision Detected with the box");
                ammo = ammo + 10;
               
            }
       
            if(box2.hasCollision(player1, true))
            {           
                ammoCollectedSound.play();
                box2.removeFromParent();
                System.out.println("Collision Detected with the box");
                ammo = ammo + 10;
               
               
                       
            }
       
            if(box3.hasCollision(player1, true))
            {           
                ammoCollectedSound.play();
                box3.removeFromParent();
                System.out.println("Collision Detected with the box");
                ammo = ammo + 10;
               
            }
       
            if(box4.hasCollision(player1, true))
            {           
                ammoCollectedSound.play();
                box4.removeFromParent();
                System.out.println("Collision Detected with the box");
                ammo = ammo + 10;
               
            }
       
            if(box5.hasCollision(player1, false))
            {           
                ammoCollectedSound.play();
                box5.removeFromParent();
                System.out.println("Collision Detected with Ammo Node");
                ammo = ammo + 10;
               
            }
           
            scene.updateGeometricState(0f, true);
        }
}

Well, I have some comments about that code, but the first one is the only one on topic… so:



Notice that you disregard the scene object completely in your collision code. You are simply asking if two nodes (not necessarily live/attached) have a collision. The scene graph system does not check for live objects, just for objects itself. What you need is to iterate over the children of scene to find collisions, instead.

What I was trying to say in my last reply is that you must test against the scene graph is you want to know if there is a collision. You don't really wipe out or completely remove a node in your code… The is because you always keep a local copy of that object in your class, and you test against it.



For instance, I would, instead do the following:



/*
 private Box b;
 private SharedMesh box1;
 private SharedMesh box2;
 private SharedMesh box3;
 private SharedMesh box4;
 private SharedMesh box5;
*/

//Assume you have ALL these objects under the node:
 private Node node;

private void healthAmmoPacks()
{
 ArrayList<Spatial> children = node.getChildren();
 if( ammo <= 12 ) //less than or equal
    {
        for( Spatial s : children )
        {
            if( s.hasCollision(player1, true) )
            {           
                ammoCollectedSound.play();
                s.removeFromParent();
                System.out.println("Collision Detected with the box");
                ammo = ammo + 10;
            }
        }
        scene.updateGeometricState(0f, true);
    }
 
}



Note that this code might not work out of the box because I wrote it directly here, and not tested it.
You don't really wipe out or completely remove a node in your code... The is because you always keep a local copy of that object in your class, and you test against it.


Hmm... very interesting.. I would certainly have not envisaged it and would have kept banging my head against the wall without success.. The code that you gave was certainly along the right lines although as you predicted it was out of the box and gave me "java.util.ConcurrentModificationException" - which the wisdom of google suggests "when one thread is trying to iterate throught the collection and other trying to modify the values in the same collection". I think it needs a sentence or two but could not figure out which so took on your concept and produced a similar method as below again which arises different problem:


Spatial ammoBox = n1.getChild("guard ammo");
        if(ammoGuard < 12)
        {
            if(guardNode.hasCollision(ammoBox, false))
            {
                System.out.println("collision detected");
                ammoCollectedSound.play();
                ammoBox.removeFromParent();               
                System.out.println("Guard collected ammunition");
                ammoGuard = ammoGuard + 10;               
            }
           
            scene.updateGeometricState(0f, true);
            scene.updateRenderState();
        }



It works well in a sense it sets the "ammoBox" - object of the n1 node being collided to null but for some reason it tries to collide again and it gives nullpointerexception as ammoBox is now null! The console output that demonstrates all the acknowledgements through System.outs is as follows:


Guard collected ammunition

guard ammo (com.jme.scene.SharedMesh)

13-Feb-2008 07:10:19 com.jme.scene.Node attachChild

INFO: Child (bullet from Guard26) attached to this node (Scene graph node)

null

13-Feb-2008 07:10:19 class Ares start()

SEVERE: Exception in game loop
java.lang.NullPointerException
        at com.jme.scene.Node.hasCollision(Unknown Source)
        at Ares.healthAmmoPacks(Ares.java:1468)
        at Ares.update(Ares.java:260)
        at Backup.BaseGame.start(BaseGame.java:98)
        at Ares.main(Ares.java:236)

13-Feb-2008 07:10:19 Backup.BaseGame start
INFO: Application ending.
BUILD SUCCESSFUL (total time: 18 seconds)



I dont get it.. may be the heavy cold and subsequent headaches are blinding something really obvious cant spot why this is happening?

Are you, by any chance doing this in a separate thread from you main application GL thread? (Using StandardGame for instance)



The concurrent exception is a little bit troubling, unless the problem comes from removing children from inside the traversal loop… If so, you could try an alternate attempt… Just to see if it is the case, and later optimize it:


*
 private Box b;
 private SharedMesh box1;
 private SharedMesh box2;
 private SharedMesh box3;
 private SharedMesh box4;
 private SharedMesh box5;
*/

//Assume you have ALL these objects under the node:
 private Node node;

private void healthAmmoPacks()
{
 ArrayList<Spatial> children = node.getChildren();
 ArrayList<Spatial> toKill = new ArrayList<Spatial>();
 if( ammo <= 12 ) //less than or equal
    {
        for( Spatial s : children )
        {
            if( s.hasCollision(player1, true) )
            {           
                ammoCollectedSound.play();
                toKill.add( s );
                System.out.println("Collision Detected with the box");
                ammo = ammo + 10;
            }
        }
        for( Spatial s : toKill )
            s.removeFromParent();
        toKill.clear();
        scene.updateGeometricState(0f, true);
    }
}



BTW, this is because I don't quite understand where the problem would be with your NPE, so I just ignored it, and tried to solve the original prob.  ;)

nice… really nice… the problem was occurring as we were trying to modify the collection from inside the traversing loop as you indicated… neat little solution as well… initially I could not verify this as the collision detection was triangular based as it was s.hasCollision(player1, true) and would get different ammo values. I have tested it a few times now and looks hopeful but will not be jumping up and down just yet until I rewrite similar fragments of code based on such collision detection…  :lol:



Thanks!!!



p.s. I am using BaseGame so if this would not have solved the problem then it would have been more than a little troubling  :smiley:

Cool!, keep us updated  :wink: