Getting Physics right for blocks

In my attempt to create a Minecraft-like block world I am playing around with some basic ideas. So far I have a plane of 16x1x16 boxes, which worked fine until I added physics.

I decided to start with a naive approach to see how things work out. So I followed the Hello Collision and Adv. Physics tutorial, added a RigidBodyControl to each block with a weight of 0, added that to the global Physics control, and the FPS dropped from 2500 to 14. After playing around with the code a bit I learned that as soon as I use a Box as mesh, everything works fine, however my 1x1x1 cube, which is nearly identical to a Box except that it ranges from 0->1 (instead of -0.5->0.5).

My theory is, that for my own mesh the mesh collision shape calculation seems to calculate something that causes those boxes to try to push each other. But since each has a weight of 0 (and even kinematic set to true), they won’t move and physics keep pushing. I tried several things to avoid that but with no avail. I removed the control from each box, set kinematic to true, tried several weights aside from 0, but nothing works. But as soon as I manually add a box collision shape everything immediately works perfectly, aside from that the shape no longer matches the mesh.

Also I noticed that the player is constantly drifting on the surface of that box-plain and tries to get on to the contact point between two boxes. This happens no matter which collision shape I use. I at first thought the collision shape was more like a sphere (because that’s how the player moves), but the debug-mode clearly showed boxes as they should be.

That brings me to several questions:

  1. Why is the collision shape of a box-mesh calculated in a way that it does not resemble the box but something bigger?

  2. Why are those things trying to push each other despite all of them being set to ignore physics (even if I don’t add the control).

  3. Why is the player constantly slipping over the surface?

  4. Is there a better approach for Minecraft-like cube worlds than adding each block as a collision shape?

Thanks in advance!

They probably aren’t pushing each other around, but Bullet is still doing broadphase between all your cubes and probably the fine checks between the adjacent ones.
And there probably is a better approach, just search for the Cubes library in the forums.

Yeah, as we used to say quite often here at one time: “block worlds are not made of blocks”

They are made of visible quads batched into a single mesh. As mentioned, see the cubes library for an example.

Thank you for pointing me towards the Cubes library, I will definitely have a look at that.

Aside from that I am still trying to understand what I did wrong in my approach to learn. I actually decided for the naive approach to learn the basic mistakes, I just didn’t expect it to fail so spectacular right away. :wink:

I’m going to venture a guess that perhaps friction was set to zero for the cubes/player? That could cause the slipping. I’ve heard of others having similar issues with things inexplicably sliding over an apparently level surface. Unfortunately I don’t remember how they solved it.

A prototype has to be at least representative to be useful. Making a block world out of actual blocks will start to fail really quickly. You don’t learn anything at all useful from it other than that none of it really works. There is literally nothing else you can take forward from that approach.

It’s like trying to decide to prototype a car with tin foil, some twigs for axles, and some paper plates for wheels. 100% of the failures you will hit will have no bearing on building an actual car.

@danielp I have not changed friction at all, mostly everything is at the default setting. The slipping also stops after a second.

@pspeed Well so far I learned - for example - that there is something wrong with physics. If I implement such a primitive example by the book and encounter so many issues, then there is either a bug in the physics system, or the documentation isn’t properly describing how to use it. And the fact that no one so far could explain to my why these things happen lead me to believe that they are not happening intentionally.

Well, to me it seems like you are treating what should be a static world mesh as a bunch of separate rigid bodies… probably with the wrong sized collision mesh or at the very least not centered properly.

When you solve those issues then you will be left with nothing but a warm feeling that you solved a problem that you won’t have to solve.

I am using a 1x1x1 cube and let the collision shape generate from the mesh by setting no weight or body - as in the tutorial. The result looks perfectly as it should be as far as I can tell from the physic’s debug visualization, yet the player is slipping over the floor like it is made out of ice, drops of the corner of the shape like it is a sphere, and fps drop like crazy.

This is the block class: VoxelTerrainBlock - Pastebin.com (implementing class just adds material)
This is the mesh class: VoxelBlockMesh - Pastebin.com

Then all I do is to align them in a 16x1x16 plane and start the game. Rest of the code is essentially the collision tutorial.

This is probably because your “voxels” are touching. The easiest way to do it like this (which is kinda like how terraria does it) is by creating a 16x16x16 mesh and using some kind of density field or 3D noise function to determine whether a block exists or not, and modifying the mesh buffers (which will be of constant size) based on that.

Then, using that data, build a seperate collision mesh. The code below has some notes, but the code should hopefully speak for itself. The main things you want to look at are the bulidMesh() and buildCollisionMesh methods - but I’ve included the whole class for the sake of it.

But a final note I should really say - This is not the best way to make a “minecraft style” game. This is for a terraria-style game - and you will need to implement it in three dimensions - however I posted it because it explains how to solve your issue in particular - by building a mesh as a whole - and not individually; building a collision mesh seperately; and modifying a mesh on-the-fly instead of re-creating it again and again.

package com.jayfella.survival.chunk;

import com.jayfella.survival.Main;
import com.jayfella.survival.material.Material;
import com.jayfella.survival.material.MaterialFace;
import com.jayfella.survival.material.TexturedMaterial;
import com.jayfella.survival.volume.ArrayDensityVolume;
import com.jme3.bullet.collision.shapes.MeshCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * Represents a section of a chunk
 */
public class ChunkSection {

    public static int SIZE_XY = 16;

    private final WorldChunk chunk;
    private final int y;

    private ArrayDensityVolume densityVolume;
    private Geometry geometry;
    private RigidBodyControl rigidBodyControl;

    private TexturedMaterial[][] blocks;

    public ChunkSection(WorldChunk chunk, int y) {

        this.blocks = new TexturedMaterial[ChunkSection.SIZE_XY][ChunkSection.SIZE_XY];
        this.chunk = chunk;
        this.y = y;
    }

    public int getY() {
        return this.y;
    }

    public Chunk getChunk() {
        return this.chunk;
    }

    public ArrayDensityVolume getDensityVolume() {
        return this.densityVolume;
    }

    protected void setDensityVolume(ArrayDensityVolume densityVolume) {
        this.densityVolume = densityVolume;
    }

    public Geometry getGeometry() {
        return this.geometry;
    }

    public RigidBodyControl getRigidBodyControl() {
        return this.rigidBodyControl;
    }

    public TexturedMaterial getBlock(int x, int y) {
        return this.blocks[x][y];
    }

    void build() {
       this.buildMesh();
       this.buildCollisionMesh();
    }

    public void buildCollisionMesh() {

        // we are going to build the collision mesh from the voxel data we have created whilst building
        // the actual mesh.

        // we are building the collision mesh separately for a few reasons:
        // 1) Air voxels should not have collision data, so we can't simply duplicate the surface mesh.
        // 2) We want to re-build the collision mesh every time a block has changed state, but we only want to modify
        //    the surface mesh (material type change or lighting change).

        // remove the old rigidbody because it will either be renewed or not added at all if the collision mesh is empty.
        if (Main.PHYSICS_ENABLED) {
            if (this.rigidBodyControl != null) {
                chunk.getGameWorld().getBulletAppState().getPhysicsSpace().remove(this.rigidBodyControl);
                this.rigidBodyControl = null;
            }
        }


        List<Vector3f> verts = new ArrayList<>();
        List<Integer> indexes = new ArrayList<>();

        int vJump = 0;

        for (int x = 0; x < SIZE_XY; x++) {

            for (int y = 0; y < SIZE_XY; y++) {

                // float density = densityVolume.getDensity(x, y);

                TexturedMaterial material = blocks[x][y];

                if (material.getMaterial() != Material.AIR) {

                    verts.add(new Vector3f(x + 0, y + 0, 0));
                    verts.add(new Vector3f(x + 1, y + 0, 0));
                    verts.add(new Vector3f(x + 0, y + 1, 0));
                    verts.add(new Vector3f(x + 1, y + 1, 0));

                    indexes.add(vJump + 2);
                    indexes.add(vJump + 0);
                    indexes.add(vJump + 1);

                    indexes.add(vJump + 1);
                    indexes.add(vJump + 3);
                    indexes.add(vJump + 2);

                    vJump += 4;
                }
            }
        }

        // if the mesh is empty, we can just return here.
        if (verts.isEmpty()) {
            return;
        }

        Vector3f[] vertArray = new Vector3f[verts.size()];
        vertArray = verts.toArray(vertArray);

        int[] indexArray = new int[indexes.size()];

        for (int i = 0; i < indexes.size(); i++) {
            indexArray[i] = indexes.get(i);
        }

        Mesh mesh = new Mesh();

        mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertArray));
        mesh.setBuffer(Type.Index,    3, BufferUtils.createIntBuffer(indexArray));

        this.rigidBodyControl = new RigidBodyControl(new MeshCollisionShape(mesh), 0f);
        this.geometry.addControl(this.rigidBodyControl);
        // this.rigidBodyControl.setApplyPhysicsLocal(true); // we can maybe stop doing this...

        this.rigidBodyControl.setFriction(2500f);
        this.rigidBodyControl.setRestitution(0f);

        this.rigidBodyControl.setPhysicsLocation(new Vector3f(
                chunk.getGridX() * ChunkSection.SIZE_XY,
                y << 4,
                1));

        // if the geometry has a parent, it means it's in the scene, so re-add the collision mesh too.
        if (this.geometry.getParent() != null) {
            if (Main.PHYSICS_ENABLED) {
                chunk.getGameWorld().getBulletAppState().getPhysicsSpace().add(this.rigidBodyControl);
            }

        }

    }



    private void buildMesh() {

        // we are going to include AIR as a physical block. This means that all meshes have an exact buffer count.
        // This also means that we can simply modify the mesh instead of re-building it every time we break or place a
        // block. While it does increase mesh size, it also speeds up mesh modification.

        List<Vector3f> verts = new ArrayList<>();
        List<Vector2f> texCoords = new ArrayList<>();
        List<Integer> indexes = new ArrayList<>();
        List<Vector2f> voxelData = new ArrayList<>();

        int vJump = 0;

        for (int x = 0; x < SIZE_XY; x++) {

            for (int y = 0; y < SIZE_XY; y++) {

                float density = densityVolume.getDensity(x, y);

                verts.add(new Vector3f(x + 0, y + 0, 0));
                verts.add(new Vector3f(x + 1, y + 0, 0));
                verts.add(new Vector3f(x + 0, y + 1, 0));
                verts.add(new Vector3f(x + 1, y + 1, 0));

                texCoords.add(new Vector2f(x + 0, y + 0));
                texCoords.add(new Vector2f(x + 1, y + 0));
                texCoords.add(new Vector2f(x + 0, y + 1));
                texCoords.add(new Vector2f(x + 1, y + 1));

                indexes.add(vJump + 2);
                indexes.add(vJump + 0);
                indexes.add(vJump + 1);

                indexes.add(vJump + 1);
                indexes.add(vJump + 3);
                indexes.add(vJump + 2);

                vJump += 4;

                Vector2f worldLocation = new Vector2f((chunk.getGridX() << 4) + x, (this.y << 4) + y);

                if (density > 0.0f) {

                    // just set everything to dirt for now.
                    // all other materials and ores will be "injected" afterward.
                    // set the light to pitch black, and let the sun or light sources illuminate it.
                    voxelData.add(new Vector2f(Material.DIRT.getTextureId(), 0f));
                    voxelData.add(new Vector2f(Material.DIRT.getTextureId(), 0f));
                    voxelData.add(new Vector2f(Material.DIRT.getTextureId(), 0f));
                    voxelData.add(new Vector2f(Material.DIRT.getTextureId(), 0f));

                    blocks[x][y] = new TexturedMaterial(chunk, worldLocation, Material.DIRT);
                }
                else {
                    voxelData.add(new Vector2f(Material.AIR.getTextureId(), 0f));
                    voxelData.add(new Vector2f(Material.AIR.getTextureId(), 0f));
                    voxelData.add(new Vector2f(Material.AIR.getTextureId(), 0f));
                    voxelData.add(new Vector2f(Material.AIR.getTextureId(), 0f));

                    blocks[x][y] = new TexturedMaterial(chunk, worldLocation, Material.AIR);
                }

                blocks[x][y].setIndex(voxelData.size() - 4);
            }
        }

        Vector3f[] vertArray = new Vector3f[verts.size()];
        vertArray = verts.toArray(vertArray);

        Vector2f[] texCoordArray = new Vector2f[texCoords.size()];
        texCoordArray = texCoords.toArray(texCoordArray);

        int[] indexArray = new int[indexes.size()];

        for (int i = 0; i < indexes.size(); i++) {
            indexArray[i] = indexes.get(i);
        }

        Vector2f[] voxelDataArray = new Vector2f[voxelData.size()];
        voxelDataArray = voxelData.toArray(voxelDataArray);

        Mesh mesh = new Mesh();

        mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertArray));
        mesh.setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(texCoordArray));
        mesh.setBuffer(Type.Index,    3, BufferUtils.createIntBuffer(indexArray));
        mesh.setBuffer(Type.TexCoord2, 2, BufferUtils.createFloatBuffer(voxelDataArray));

        // we can update the bound here, since the size will never change.
        mesh.updateBound();

        // the mesh is never null, since even air is rendered, albeit transparent.
        this.geometry = new Geometry(String.format("Chunk: %d Section: %d", chunk.getGridX(), getY()), mesh);
        this.geometry.setMaterial(chunk.getAssetLoader().getGroundMaterial());
        this.geometry.setQueueBucket(Bucket.Transparent);

        this.geometry.setLocalTranslation(new Vector3f(
                chunk.getGridX() * ChunkSection.SIZE_XY,
                y << 4,
                1));

    }

    private void destroyMesh(Mesh mesh) {
        for( VertexBuffer vb : mesh.getBufferList() ) {
            BufferUtils.destroyDirectBuffer( vb.getData() );
        }
    }

    protected void destroySurfaceGeometry() {

        if (this.geometry != null) {

            if (this.geometry.getParent() != null) {
                this.geometry.removeFromParent();
            }

            if (Main.PHYSICS_ENABLED) {
                chunk.getGameWorld().getBulletAppState().getPhysicsSpace().remove(this.rigidBodyControl);
            }


            this.rigidBodyControl = null;

            destroyMesh(this.geometry.getMesh());
            this.geometry = null;
        }

    }

}

So if I understand you correctly, then my assumption that the physics engine is trying to apply physics to the blocks is correct?

If that is true, then there either is a bug in the physics engine or I am setting things up the wrong way, because those blocks - according to what I read from the documentation - shouldn’t do any physics against each other.

I totally agree that my naive approach isn’t the best solution for a Minecraft-style voxel game, but I intentionally did it this way to learn how JME would deal with that and then improve from that point on. I however start to get a few doubts about the state of JME, when a minor deviation from the tutorial already creates such huge problems.

That’s a bit of a strange thing to say. You have been told that you are doing it wrong by several people, but you insist on continuing, and then blame the engine. Making games is not simple. Far from it. In standard application development, 80% of time coding is spent on stopping the user doing something they shouldn’t. Disabling buttons, requiring things to be done in steps - enforcing those steps in order, etc. In game development it’s a completely new ball game. 80% of the time is reading reading reading. Getting it wrong. Reading again. Finally understanding how and more importantly why it needs to be done that way is where all the time goes. Games are all smoke and daggers. In minecraft you see blocks so you create blocks. Smoke and daggers. It’s a single mesh. A 16x16x16 mesh - stacked 16 high. You see all these block types. They are all on a single image. You see clouds - it’s more than likely a noise algorythm that just so happens to look like clouds.

So in summary - you’re doing it wrong - but that’s ok! We have all been there. Whether or not you wish to accept that and thrive on learning this whole new concept depends entirely on you. Personally - I’ve spent the past 10 days writing part of my game getting things wrong again and again. It’s not the engine’s fault - it’s my lack of knowledge and experience. But I thrive on the challenge. I love reading maths books. Maths explains everything man. It’s a wonderful language.

Just have patience. Don’t give up.

You may get a few more people to look at your code if you can post it here instead of requiring a click-through to pastebin. If you can reduce to a single class test case that illustrates the issue then that’s even better.

I transformed it into a testcase: JME TestCase - Pastebin.com

if you start it and walk around to the edge of the plane you should see a drastic drop in FPS.

The size of the CapsuleCollisionShape (104) is important, if you make it smaller the issue disappears at some point. Also the issue will disappear as soon as a Box is used instead of the VoxelBlockMesh. However that is simply a copy & paste version of the Box with the vertex moved to integer positions.

As a side-note: I tried the Cube-library example with collision and noticed the same slippery effect, just much more subtile. I assume this is somehow related to the shape of the player.

It seems that in a few minutes you could design your own base class app that implements the basic animation loop:

  1. input

  2. update/move

  3. draw
    and then you would have complete control over what is going on in your game. This is really not the right tool for a 2D game. JBullet works for 2.5D but, you can simulate gravity… oh here… ignore the ‘rings’ stuff. probably could take out the ‘synchronized’ modifier.

    /*

    • To change this template, choose Tools | Templates
    • and open the template in the editor.
      */
      package my.game.states;

    import my.game.OrbitMaker;
    import my.game.controls.MassControl;
    import static my.game.OrbitMaker.G;
    import app.util.CollisionGroup;
    import com.jme3.app.Application;
    import com.jme3.app.state.AbstractAppState;
    import com.jme3.app.state.AppStateManager;
    import com.jme3.material.Material;
    import com.jme3.math.ColorRGBA;
    import com.jme3.math.Vector3f;
    import com.jme3.scene.Node;
    import com.jme3.scene.Spatial;
    import com.jme3.scene.Geometry;
    import com.jme3.scene.shape.Line;
    import java.util.ArrayList;
    import java.util.LinkedList;
    import java.util.List;

    /**
    *

    • @author Owner
      */
      public class EnvironmentAppState extends AbstractAppState {
      Node rootNode;
      OrbitMaker app;
      SolarAppState solarAppState;
      LinkedList collisionQueue = new LinkedList();

      public EnvironmentAppState(AppStateManager stateManager, Node rootNode, Application app){
      this.app = (OrbitMaker)app;
      this.rootNode = rootNode;
      }

      public void initialize(AppStateManager stateManager, Node rootNode, Application app) {
      super.initialize(stateManager, app);
      solarAppState = stateManager.getState(SolarAppState.class);
      this.rootNode = rootNode;

       //TODO: initialize your AppState, e.g. attach spatials to rootNode
       //this is called on the OpenGL thread after the AppState has been attached
      

      }

      public ArrayList getMassList(Node parent){
      ArrayList list = new ArrayList();
      MassControl massControl = parent.getControl(MassControl.class);
      if (massControl != null){
      list.add(parent);
      }
      List children = parent.getChildren();
      for (int i = 0;i < children.size();i++){
      Object child = children.get(i);
      if (child instanceof Node){
      getMassList(((Node)child), list);
      }
      }
      //System.out.println();
      return(list);
      }

      public void getMassList(Node parent, ArrayList list){
      //System.out.println(“Environment.getAllChildren()” + “, parent=” + ((Node)parent).getName());
      MassControl massControl = parent.getControl(MassControl.class);
      if (massControl != null){
      //System.out.println(“Environment.getAllChildren()2” + ", add " + parent.getName());
      list.add(parent);
      }
      List children = parent.getChildren();
      for (int i = 0; i < children.size();i++){
      Object child = children.get(i);
      if (child instanceof Node){
      getMassList((Node)child, list);
      }
      }
      }//getMasses

      boolean updateRings = false;
      boolean fixedRing = false, floatingRing = false;
      public void setFixedRing(boolean b){
      fixedRing = b;
      updateRings = true;
      System.out.println(“EAS.setFixedRing()” + “, fixedRing=” + b);
      }

      public void setFloatingRing(boolean b){
      floatingRing = b;
      updateRings = true;
      System.out.println(“EAS.setFloatingRing()” + “, floatingRing=” + b);
      }

      public boolean isFixedRingSelected(){
      return(solarAppState.fixedRingCheckbox.getModel().isSelected());
      }

      public boolean isFloatingRingSelected(){
      return(solarAppState.floatingRingCheckbox.getModel().isSelected());
      }

      private synchronized boolean areClose(Node p1, Node p2){
      boolean collision = false;

       if (p1.getWorldTranslation().equals(p2.getWorldTranslation())){
       	//System.out.println("EAS.areClose(), skipping p1=" + p1.getName() + ", p2=" + p2.getName());
       	return(collision);
       }
      
       Vector3f loc1 = p1.getWorldTranslation();
       Vector3f loc2 = p2.getWorldTranslation();
       
       float r1 = p1.getControl(MassControl.class).getRadius();
       float r2 = p2.getControl(MassControl.class).getRadius();
       float dist = loc2.distance(loc1);
       dist = dist - (r1 + r2);
       if (dist < 0){
       	collision = true;
       	//System.out.println("EAS.areClose()" + ", dist=" + dist + ", p1=" + p1.getName() + ", p2=" + p2.getName() + " are close");
       }
       
       return(collision);
      

      }

      private synchronized void gravity(){
      //delay -= 1.0f;
      //if (delay > 0) return;
      //delay = app.getTimer().getFrameRate();
      //ArrayList bodyList = getBodyList();
      List list = getMassList(rootNode);
      for (int a = 0;a < list.size();a++){
      Node p1 = (Node)list.get(a);
      MassControl massControl = p1.getControl(MassControl.class);
      float m1 = massControl.getMass();
      //System.out.println(“EnvironmentalControl.doGravity()” + “, processing p1=” + p1.getName());
      Vector3f delta = new Vector3f();
      if (list.size() > 1){
      //if (p.getName().equals(“Moon”)) System.out.print(“delta=”);
      for (int b = 0;b < list.size();b++){
      Node p2 = (Node)list.get(b);
      if (p1.equals(p2)) continue;
      Vector3f unit = new Vector3f(p2.getWorldTranslation().subtract(p1.getWorldTranslation()));
      float magnitude = (float)Math.sqrt((unit.x * unit.x) + (unit.y * unit.y) + (unit.z * unit.z));
      float m2 = p2.getControl(MassControl.class).getMass();
      //if (p1.getName().equals(“Ship 01”) && p2.getName().equals(“Earth”) || p2.getName().equals(“Ship 01”) && p1.getName().equals(“Earth”))
      //System.out.println(“OrbitMaker.doGravity(” + p1.getName() + “)(” + p2.getName() + “) m1=” + m1 + “, m2=” + m2 + “, unit=” + unit + “, magnitude=” + magnitude);//do gravity
      if (magnitude != 0.0f && (m1 * m2 != 0.0)){
      float factor = (G * (
      (m1 * m2) / (magnitude * magnitude * magnitude)
      )) / m1;
      //need to add divide by 2 so that each body contributes half the gravity effect
      unit = unit.mult(factor);
      //if (p1.getName().equals(“Ship 01”)) System.out.println(“OrbitMaker.doGravity(” + p1.getName() + “)(” + p2.getName() + “): factor=” + factor + “, unit=” + unit + “, delta=” + delta);
      }
      else{
      unit.set(0.0f, 0.0f, 0.0f);
      }
      delta = delta.add(unit);//.mult(tpf));
      //collision check
      if (areClose(p1, p2)){
      collisionQueue.add(new CollisionGroup(p1, p2));
      }
      }
      }

       	//if (p.getName().equals("Moon")) System.out.println(delta + ", ");
       	//Vector3f velocity = p.getControl(MassControl.class).getVelocity();
       	//if (p.getName().equals("Moon")) System.out.println("OrbitMaker.doGravity(" + p.getName() + "): delta=" + delta);
       	//massControl.addVelocity(delta);//.mult(tpf)
       }
       //System.out.println();
      

      }//doGravity

      private void collisions(){
      if ( collisionQueue.isEmpty()) return;

       while(!collisionQueue.isEmpty()){
       	CollisionGroup cg = collisionQueue.pop();
       	Node p1 = cg.getP1();
       	Node p2 = cg.getP2();
       	//System.out.println("EAS.doCollisions()" + "," + p1.getName() + " and " + p2.getName() + " have collided");
       }
      

      }//doCollision

      private void rings(){
      if (updateRings){
      System.out.println(“EAS.rings()”);
      List list = getMassList(rootNode);
      for (int a = 0;a < list.size();a++){
      Node p1 = (Node)list.get(a);
      MassControl massControl = p1.getControl(MassControl.class);
      //float m1 = massControl.getMass();
      massControl.setFixedRing(fixedRing);
      massControl.setFloatingRing(floatingRing);
      }
      updateRings = false;
      }
      }

    /*

    • doGravigy
    • \locate collisions

    */

     @Override
     public void update(float tpf) {
     	//TODO: implement behavior during runtime
     	gravity();
     	collisions();
     	rings();
     }
    
     
     @Override
     public void cleanup() {
     	super.cleanup();
     	//TODO: clean up what you initialized in the initialize method,
     	//e.g. remove all spatials from rootNode
     	//this is called on the OpenGL thread after the AppState has been detached
     }
     
     
     public void println(String s){
     	System.out.println(s);
     }
    

    }

@Mipada I am not sure what that has to do with my question?