Merging on Collision

I’ve got a simple N-body system with gravity and spherical lumps of “matter”. The problem is, when two lumps collide, I want to merge them and have a single new lump with the mass, energy and direction of the two initial lumps.



I’ve tried making a PhysicsCollisionListener, but PhysicsCollisionObject doesn’t appear to have methods to return the mass and suchlike, or modify the nodes. Any ideas, guys?

I guess you can cast the PhysicsCollisionObject to a PhysicsNode?

It works :smiley: thank you for thinking of that for me!

The next issue I have is that my program seems to crash things without any error messages after a few collisions. Sometimes the WorldTranslation of one or both PhysicsNodes is Vector3f.NAN, which I guess might do something. And for some reason, even though I remove the two nodes that are colliding once they’ve collided and before adding the new node, the collision listener can get the same two nodes colliding a couple of times, one after the other.



So my questions are, what does it mean when a node’s WorldTranslation is NAN? And why might a collision fire multiple collision events?



Thanks!



For reference, my (somewhat poor, based heavily on the physics tutorials) code is below:



[java]import java.util.LinkedList;

import java.util.List;

import java.util.Random;



import com.jme3.app.SimpleApplication;

import com.jme3.asset.TextureKey;

import com.jme3.bullet.BulletAppState;

import com.jme3.bullet.collision.PhysicsCollisionEvent;

import com.jme3.bullet.collision.PhysicsCollisionListener;

import com.jme3.bullet.collision.shapes.SphereCollisionShape;

import com.jme3.bullet.nodes.PhysicsNode;

import com.jme3.font.BitmapText;

import com.jme3.input.MouseInput;

import com.jme3.input.controls.ActionListener;

import com.jme3.input.controls.MouseButtonTrigger;

import com.jme3.material.Material;

import com.jme3.math.Vector3f;

import com.jme3.renderer.queue.RenderQueue.ShadowMode;

import com.jme3.scene.Geometry;

import com.jme3.scene.shape.Sphere;

import com.jme3.scene.shape.Sphere.TextureMode;

import com.jme3.shadow.BasicShadowRenderer;

import com.jme3.texture.Texture;



public class GaussianBeginnings extends SimpleApplication {



public static void main(String[] args) {

GaussianBeginnings app = new GaussianBeginnings();

app.start();

}



/** Activate custom rendering of shadows /

BasicShadowRenderer bsr;



/
* geometries and collisions shapes for bricks and dust balls. /

private BulletAppState bulletAppState;

private static final Sphere dustball;

private static final SphereCollisionShape dustballCollisionShape;

private int initialNumberofDustParticles = 200;

private int particleNameNumber = 0;

private Vector3f maxWorldDimensions = new Vector3f(100f, 100f, 100f);



/
* For use with gravity /

private List nodes = new LinkedList();

private float forceOfGravity = 0.5f;

private int maxRange = 100;



/
Materials /

Material stone_mat;

Material floor_mat;

Geometry dustballGeometry;



static {

/
* initialising the dust ball geometry that is reused later /

dustball = new Sphere(32, 32, 0.5f, true, false);

dustball.setTextureMode(TextureMode.Projected);

dustballCollisionShape = new SphereCollisionShape(0.5f);

}



@Override

public void simpleInitApp() {

/
* Set up Physics /

bulletAppState = new BulletAppState();

bulletAppState.setWorldMax(maxWorldDimensions);

bulletAppState.setWorldMin(Vector3f.ZERO);

stateManager.attach(bulletAppState);

/
* Set up camera /

cam.setLocation(maxWorldDimensions);

cam.lookAt(Vector3f.ZERO, new Vector3f(0, 1, 0));

cam.setFrustumFar(1000f);



/
* Add shooting action /

inputManager.addMapping(“shoot”, new MouseButtonTrigger(

MouseInput.BUTTON_LEFT));

inputManager.addListener(actionListener, “shoot”);

/
* Initialise the scene and physics space /

initMaterials();

initDust();

initCrossHairs();

bulletAppState.getPhysicsSpace().addCollisionListener(listener);

bulletAppState.getPhysicsSpace().setGravity(Vector3f.ZERO);

bulletAppState.getPhysicsSpace().setAccuracy(0.005f);

/
* Activate custom shadows */

rootNode.setShadowMode(ShadowMode.Off);

bsr = new BasicShadowRenderer(assetManager, 256);

bsr.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());

viewPort.addProcessor(bsr);

}



/**

  • Every time the shoot action is triggered, a new dust ball is produced.
  • The ball is set up to fly from the camera position in the camera
  • direction.

    */

    private ActionListener actionListener = new ActionListener() {

    public void onAction(String name, boolean keyPressed, float tpf) {

    if (name.equals(“shoot”) && !keyPressed) {

    makeDust();

    }

    }

    };



    /**
  • Every time two balls collide they should turn into a single ball with equivalent position, speed, mass, volume and direction.
  • In this way, colliding balls will turn into larger, more massive balls.

    /

    private PhysicsCollisionListener listener = new PhysicsCollisionListener() {

    @Override

    public void collision(PhysicsCollisionEvent arg0) {



    // Check this collision is nonsense…

    if (arg0.getNodeA().getWorldTranslation().equals(Vector3f.NAN) || arg0.getNodeB().getWorldTranslation().equals(Vector3f.NAN)) {

    return;

    }



    Random generator = new Random();



    // Deal with all the casting.

    PhysicsNode a = (PhysicsNode) arg0.getNodeA();

    PhysicsNode b = (PhysicsNode) arg0.getNodeB();



    Geometry ageo = (Geometry) a.getChild(0);

    Geometry bgeo = (Geometry) b.getChild(0);

    Sphere asphere = (Sphere) ageo.getMesh();

    Sphere bsphere = (Sphere) bgeo.getMesh();



    System.out.println("Node at " + a.getWorldTranslation() + " collided with " + b.getWorldTranslation());



    // New SphereCollisionShape of the right size.

    float newSphereSize = getRadiusFromVolume(getVolumeFromRadius(asphere.radius) + getVolumeFromRadius(bsphere.radius));

    SphereCollisionShape tempCollisionShape = new SphereCollisionShape(newSphereSize);



    // New dust ball Geometry and Sphere.

    Sphere newSphere = new Sphere(32, 32, newSphereSize, true, false);

    newSphere.setTextureMode(TextureMode.Projected);

    dustballGeometry = new Geometry(“dust ball”, newSphere);

    dustballGeometry.setMaterial(stone_mat);



    // Get new mass.

    float newMass = a.getMass() + b.getMass();



    PhysicsNode dustNode = new PhysicsNode(dustballGeometry, // geometry

    tempCollisionShape, // collision shape

    newMass); // mass



    dustNode.setLocalTranslation(a.getLocalTranslation());

    dustNode.setShadowMode(ShadowMode.CastAndReceive);



    // Remove old nodes.

    nodes.remove(a);

    nodes.remove(b);

    rootNode.detachChild(a);

    rootNode.detachChild(b);

    bulletAppState.getPhysicsSpace().remove(a);

    bulletAppState.getPhysicsSpace().remove(b);



    // Update the geometry stuff :slight_smile:

    rootNode.updateGeometricState();



    // Add new node.

    nodes.add(dustNode);

    rootNode.attachChild(dustNode);

    bulletAppState.getPhysicsSpace().add(dustNode);

    dustNode.setLinearVelocity(a.getLinearVelocity().add(b.getLinearVelocity()));



    // Update the geometry stuff :slight_smile:

    rootNode.updateGeometricState();



    }



    private float getVolumeFromRadius(float radius) {

    return (float) (4f/3
    Math.PIMath.pow(radius,3));

    }

    private float getRadiusFromVolume(float volume) {

    return (float) Math.cbrt(volume
    3f/4/Math.PI);

    }

    };



    /** Initialise the materials used in this scene. */

    public void initMaterials() {

    stone_mat = new Material(assetManager,

    “Common/MatDefs/Misc/SimpleTextured.j3md”);

    TextureKey key2 = new TextureKey(“Textures/Terrain/Rock/Rock.PNG”);

    key2.setGenerateMips(true);

    Texture tex2 = assetManager.loadTexture(key2);

    stone_mat.setTexture(“m_ColorMap”, tex2);

    }



    /**
  • This method creates one individual physical dust ball. By default, the
  • ball is accelerated and flies from the camera position in the camera
  • direction.

    /

    public void makeDust() {

    /
    * create a new dust ball. /

    dustballGeometry = new Geometry(“dust ball”, dustball);

    dustballGeometry.setMaterial(stone_mat);



    PhysicsNode dustNode = new PhysicsNode(dustballGeometry, // geometry

    dustballCollisionShape, // collision shape

    1.0f); // mass



    /
    * position the dust ball and activate shadows /

    dustNode.setLocalTranslation(cam.getLocation());

    dustNode.setShadowMode(ShadowMode.CastAndReceive);

    /
    * Attach the cannon call to the scene and accelerate it. */

    rootNode.attachChild(dustNode);

    bulletAppState.getPhysicsSpace().add(dustNode);

    dustNode.setName(String.valueOf(particleNameNumber));

    particleNameNumber++;

    dustNode.setLinearVelocity(cam.getDirection().mult(25));

    nodes.add(dustNode);

    }



    private void initDust() {

    Vector3f maxWorldDimensions = bulletAppState.getPhysicsSpace().getWorldMax();

    Random generator = new Random();

    float x;

    float y;

    float z;

    for (int i = 0; i < initialNumberofDustParticles; i++) {

    x = generator.nextFloat()*maxWorldDimensions.getX();

    y = generator.nextFloat()*maxWorldDimensions.getY()/4f + maxWorldDimensions.getY()/2f;

    z = generator.nextFloat()*maxWorldDimensions.getZ();

    makeDust(x,y,z);

    }

    }



    /**
  • This method creates one individual physical dust ball. It will be stationary at the point specified by x, y and z.

    /

    public void makeDust(float x, float y, float z) {

    /
    create a new dust ball. /

    dustballGeometry = new Geometry(“dust ball”, dustball);

    dustballGeometry.setMaterial(stone_mat);



    PhysicsNode dustNode = new PhysicsNode(dustballGeometry, // geometry

    dustballCollisionShape, // collision shape

    1.0f); // mass



    /
    * position the dust ball and activate shadows /

    dustNode.setLocalTranslation(new Vector3f(x, y, z));

    dustNode.setShadowMode(ShadowMode.CastAndReceive);



    /
    * Give the ball a name! /

    dustNode.setName(String.valueOf(particleNameNumber));

    particleNameNumber++;



    /
    * Attach the dust call to the scene. /

    rootNode.attachChild(dustNode);

    bulletAppState.getPhysicsSpace().add(dustNode);

    nodes.add(dustNode);

    }



    /
    * A plus sign used as crosshairs to help the player with aiming. */

    protected void initCrossHairs() {

    guiNode.detachAllChildren();

    guiFont = assetManager.loadFont(“Interface/Fonts/Default.fnt”);

    BitmapText ch = new BitmapText(guiFont, false);

    ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);

    ch.setText("+"); // crosshairs

    ch.setLocalTranslation(

    // center

    settings.getWidth() / 2
  • guiFont.getCharSet().getRenderedSize() / 3 * 2,

    settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);

    guiNode.attachChild(ch);

    }



    @Override

    public void simpleUpdate(float tpf) {

    for (PhysicsNode n: this.nodes) {

    if (!n.isKinematic()) {

    // found a dynamic physics node, apply gravity

    for (PhysicsNode m: this.nodes) {

    if (n.equals(m)) {

    continue;

    }

    float distance = n.getLocalTranslation().distance(m.getLocalTranslation());



    // check in which direction we need to apply the force

    // subtract the location of the dynamic node from the

    // node’s location and normalize it

    Vector3f direction = m.getLocalTranslation().subtract(

    n.getLocalTranslation()).normalize();



    // add the force

    n.applyCentralForce(direction.mult(this.forceOfGravity * n.getMass()).divide(100 / maxRange * distance));

    }

    }

    }

    }

    }

    [/java]

Incidentally, commenting out the CollisionListener makes the program run perfectly, and demonstrates gravity quite well. By changing the number of dust balls and the force of gravity you can change how the resulting system looks.

Part of the problem was failing to use clone() on getLocalTranslation(). I’m still looking into the rest of the problem.

Things move around and collide fine in my game and behave as they should but the following code isn’t outputting something when a collision occurs. It doesn’t seem like it is ever going inside the listener. :S



[java]

private PhysicsCollisionListener physicsCollisionListener = new PhysicsCollisionListener() {

//@Override

public void collision(PhysicsCollisionEvent event) {



System.out.println(“Collision with a briiiick!!!”);



String a = event.getNodeA().getName();

String b = event.getNodeB().getName();



if ((a.equals(“brick”)) || (b.equals(“brick”))){

System.out.println(“Collision with a briiiick!!!”);

}

}



};[/java]



Maybe we are not setting up the PhysicsCollisionListener correctly?

You just have to enable it via PhysicsSpace.addCollisionListener(); normally?

1 Like

When I went through some of the library files trying to understand this a bit better I found PhysicsSpace.java references a bunch of non-existent library files. Is this supposed to be like that?



Okay, thanks again Normen. I got it going into the collision listener now! :smiley:



I’m still getting some weird behaviour though. It seems as though the value returned from the name of collision node B is 'causing it to crash. It runs fine with the first print statement, but with the second one included, it crashes. :S Any ideas why this is happening?



[java] private PhysicsCollisionListener physicsCollisionListener = new PhysicsCollisionListener() {

//@Override

public void collision(PhysicsCollisionEvent event) {



String a = event.getNodeA().getName();

String b = event.getNodeB().getName();



System.out.println(a);

System.out.println(b);

}



};[/java]

Probably the node is null, so you get a NPE when you try to execute getName() on null.

The node itself is null or the getName() is null?



From my understanding from the tutorials on this, the collision occurs between two objects, A and B and you have to check both of them to see if it’s the one you want. I’ve named everything in my scene so it shouldn’t be returning a null value for the name. (also, before I named everything, some things were printing out “null” when I only printed out the ‘a’ string)



The issue I’m having is just using ‘b’ at all. It doesn’t crash when I tell it to store the stuff in ‘b’, only when I try to use the value stored in ‘b’ to do something.





Edit: Actually, if I try to do anything with a other than print it doesn’t like it either… crashes with this as well:



[java]

private PhysicsCollisionListener physicsCollisionListener = new PhysicsCollisionListener() {

//@Override

public void collision(PhysicsCollisionEvent event) {



String a = event.getNodeA().getName();

String b = event.getNodeB().getName();



System.out.println(a);

//System.out.println(b);



if (a.equals(“brick”)) {

System.out.println(“Collision with a briiiick!!!”);

}

}



};

[/java]

Yes, the node.

[java]if(event.getNodeA()!=null{

a=event.getNode().getName();

}[/java]

1 Like

There we go, got it working now. :slight_smile: Thanks again Normen, you’re a life-saver.



I was trying things with and without the “@override” statement and sometimes it was fixing it and sometimes it was breaking it, but with the new conditional statement of the nodes not being null coupled with the @override seems to have fixed things such that they work now.



Here’s the code for anyone that’s interested:



[java]

private PhysicsCollisionListener physicsCollisionListener = new PhysicsCollisionListener() {

@Override

public void collision(PhysicsCollisionEvent event) {

if((event.getNodeA()!=null)&&(event.getNodeB()!=null)) {

String a = event.getNodeA().getName();

String b = event.getNodeB().getName();



if (a.equals(“brick”)) {

System.out.println(“Collision with a briiiick!!!”);

} else if (b.equals(“brick”)) {

System.out.println(“Collision with a briiiick!!!”);

}

}

}



};

[/java]