Cubes plugin and jumping on blocks

Edit: Resolved. It appears to be a problem in the cubes plugin with chunks that are not the very first chunk (corner at (0, 0, 0))
Edit 2: Even more resolved: the developer of the plugin showed me a better way to handle the bug in the plugin
Edit 3: I now have a question about multithreading this
Edit 4: I’m making a new topic for that question

Well, I have absolutely no idea why, but when I use the cubes plugin to make a minecraft-like engine (Which will be a component of my first game), set a block, and jump on top, I almost always pass through the top. This tends to happen with blocks that are two above the starting surface of the flat world.

here is my code:

package mygame;

import com.jme3.app.SimpleApplication;
import com.cubes.*;
import com.cubes.test.*;
import com.cubes.test.blocks.*;
import com.jme3.bullet.control.*;
import com.jme3.bullet.*;
import com.jme3.bullet.collision.*;
import com.jme3.bullet.collision.shapes.*;
import com.jme3.bullet.util.*;
import com.jme3.collision.CollisionResults;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.*;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.*;
import com.jme3.scene.mesh.*;
import com.jme3.scene.shape.*;

public class Main extends SimpleApplication implements ActionListener
{
    CubesSettings cubesSettings;
    Node blocks;
    BlockTerrainControl blockTerrain;
    BulletAppState bulletAppState;
    CharacterControl player;
    CapsuleCollisionShape capsuleShape;
    Vector3f walkDirection = new Vector3f();
    boolean left = false, right = false, up = false, down = false;
    Vector3f camDir = new Vector3f();
    Vector3f camLeft = new Vector3f();

    public static void main(String[] args)
    {
        Main app = new Main();
        app.start();
    }

@Override
public void simpleInitApp()
{
    bulletAppState = new BulletAppState();
    bulletAppState.setThreadingType(BulletAppState.ThreadingType.SEQUENTIAL);
    stateManager.attach(bulletAppState);
    bulletAppState.getPhysicsSpace().setWorldMin(new Vector3f(0, 0, 0));
    bulletAppState.getPhysicsSpace().setWorldMax(new Vector3f(400, 400, 400));
    bulletAppState.getPhysicsSpace().setMaxSubSteps(4);
    bulletAppState.getPhysicsSpace().enableDebug(assetManager);
    
    setUpKeys();
    //setUpLight();
    setUpBlocks();
    initCrossHairs();

    capsuleShape = new CapsuleCollisionShape(1.5f, 5f, 1);
    player = new CharacterControl(capsuleShape, 0.05f);
    player.setJumpSpeed(17);
    player.setFallSpeed(32);
    player.setGravity(32);
    player.setPhysicsLocation(new Vector3f(200, 50, 200));

    bulletAppState.getPhysicsSpace().add(player);
}

private void setUpBlocks()
{
    blocks = new Node();

    CubesTestAssets.registerBlocks();
    CubesTestAssets.initializeEnvironment(this);
    
    cubesSettings = new CubesSettings(this);
    cubesSettings.setBlockMaterial(CubesTestAssets.getSettings(this).getBlockMaterial());
    cubesSettings.setBlockSize(4);
    blockTerrain = new BlockTerrainControl(cubesSettings, new Vector3Int(7, 1, 7));
    
    for(int x = 0; x < 100; x++)
    {
        for(int z = 0; z < 100; z++)
        {
            blockTerrain.setBlock(x, 0, z, Block_Stone.class);
            for(int y = 1; y < 30; y++)
            {
                if (y <= 5)
                {
                    blockTerrain.setBlock(x, y, z, Block_Grass.class);
                }
                else
                {
                    blockTerrain.setBlock(x, y, 0, Block_Stone.class);
                    blockTerrain.setBlock(x, y, 100, Block_Stone.class);
                    blockTerrain.setBlock(1, y, z, Block_Stone.class);
                    blockTerrain.setBlock(99, y, z, Block_Stone.class);
                }
            }
        }
    }
    
    blockTerrain.addChunkListener(new BlockChunkListener()
    {
        @Override
        public void onSpatialUpdated(BlockChunkControl blockChunk)
        {
            Geometry optimizedGeometry = blockChunk.getOptimizedGeometry_Opaque();
            RigidBodyControl rigidBodyControl = optimizedGeometry.getControl(RigidBodyControl.class);
            if(rigidBodyControl == null)
            {
                rigidBodyControl = new RigidBodyControl(0);
                optimizedGeometry.addControl(rigidBodyControl);
                bulletAppState.getPhysicsSpace().add(rigidBodyControl);
            }
            rigidBodyControl.setCollisionShape(new MeshCollisionShape(optimizedGeometry.getMesh()));
        }
    });
    blocks.addControl(blockTerrain);
    blocks.setShadowMode(ShadowMode.CastAndReceive);
    
    rootNode.attachChild(blocks);
}

//private void setUpLight()
//{
//    AmbientLight al = new AmbientLight();
//    al.setColor(ColorRGBA.White.mult(1.3f));
//    rootNode.addLight(al);
//
//    DirectionalLight dl = new DirectionalLight();
//    dl.setColor(ColorRGBA.White);
//    dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal());
//    rootNode.addLight(dl);
//}

private CollisionResults getRayCastingResults(Node node)
{
    Vector3f origin = cam.getWorldCoordinates(new Vector2f((settings.getWidth() / 2), (settings.getHeight() / 2)), 0.0f);
    Vector3f direction = cam.getWorldCoordinates(new Vector2f((settings.getWidth() / 2), (settings.getHeight() / 2)), 0.3f);
    direction.subtractLocal(origin).normalizeLocal();
    Ray ray = new Ray(origin, direction);
    CollisionResults results = new CollisionResults();
    node.collideWith(ray, results);
    return results;
}

private Vector3Int getCurrentPointedBlockLocation(boolean getNeighborLocation)
{
    CollisionResults results = getRayCastingResults(blocks);
    if (results.size() > 0)
    {
        Vector3f collisionContactPoint = results.getClosestCollision().getContactPoint();
        return BlockNavigator.getPointedBlockLocation(blockTerrain, collisionContactPoint, getNeighborLocation);
    }
    return null;
}

private void setUpKeys()
{
    inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_A));
    inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_D));
    inputManager.addMapping("Up", new KeyTrigger(KeyInput.KEY_W));
    inputManager.addMapping("Down", new KeyTrigger(KeyInput.KEY_S));
    inputManager.addMapping("Jump", new KeyTrigger(KeyInput.KEY_SPACE));
    inputManager.addMapping("Add", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
    inputManager.addMapping("Destroy", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
    inputManager.addListener(this, "Left");
    inputManager.addListener(this, "Right");
    inputManager.addListener(this, "Up");
    inputManager.addListener(this, "Down");
    inputManager.addListener(this, "Jump");
    inputManager.addListener(this, "Add");
    inputManager.addListener(this, "Destroy");
}

public void onAction(String binding, boolean value, float tpf)
{
    if (binding.equals("Left"))
    {
        if (value)
        {
            left = true;
        }
        else
        {
            left = false;
        }
    }
    else if (binding.equals("Right"))
    {
        if (value)
        {
            right = true;
        }
        else
        {
            right = false;
        }
    }
    else if (binding.equals("Up"))
    {
        if (value)
        {
            up = true;
        }
        else
        {
            up = false;
        }
    }
    else if (binding.equals("Down"))
    {
        if (value)
        {
            down = true;
        }
        else
        {
            down = false;
        }
    }
    else if ((binding.equals("Jump")) && !value)
    {
        player.jump();
    }
    if (((binding.equals("Add")) && !value))
    {
        Vector3Int blockLocation = getCurrentPointedBlockLocation(true);
        if (blockLocation != null)
        {
            if(!blockLocation.equals(new Vector3Int((int)(player.getPhysicsLocation().getX() / 4), blockLocation.getY(), (int)(player.getPhysicsLocation().getZ() / 4))))
            {
                blockTerrain.setBlock(blockLocation, Block_Grass.class);
                System.out.println("physicsLocation: " + player.getPhysicsLocation().getX() + ", " + player.getPhysicsLocation().getY() + ", " + player.getPhysicsLocation().getZ());
                System.out.println("blockLocation: " + blockLocation.getX() + ", " + blockLocation.getY() + ", " + blockLocation.getZ());
            }
        }
    }
    else if ((binding.equals("Destroy")) && !value)
    {
        Vector3Int blockLocation = getCurrentPointedBlockLocation(false);
        if ((blockLocation != null) && blockTerrain.getBlock(blockLocation).equals(new Block_Grass().getType()))
        {
            blockTerrain.removeBlock(blockLocation);
        }
    }
}

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("+");
    ch.setLocalTranslation(settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2, settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
    guiNode.attachChild(ch);
}

@Override
public void simpleUpdate(float tpf)
{
    camDir.set(new Vector3f(cam.getDirection().multLocal(0.2f).getX(), 0, cam.getDirection().multLocal(0.2f).getZ()));
    camLeft.set(cam.getLeft()).multLocal(0.15f);
    walkDirection.set(0, 0, 0);
    if (left)
    {
        walkDirection.addLocal(camLeft);
    }
    if (right)
    {
        walkDirection.addLocal(camLeft.negate());
    }
    if (up)
    {
        walkDirection.addLocal(camDir);
    }
    if (down)
    {
        walkDirection.addLocal(camDir.negate());
    }
    if (!right && !left && !up && !down)
    {
        player.setWalkDirection(Vector3f.ZERO);
    }
    player.setWalkDirection(walkDirection);
    cam.setLocation(player.getPhysicsLocation());
}
}

I tend to not comment at all in my code, it’s true. I have absolutely no idea even where to start looking for the problem. Any help would be appreciated, thanks in advance.

Have you tried turning on the physics debugging shapes so you can see the physics shapes in your scene?

Yes I did.

Line 57: bulletAppState.getPhysicsSpace().enableDebug(assetManager);

The physics shapes appear to be there, so I really have no idea what causes this behavior

Also: thanks to the mod who put the code in a code box. I had no idea how to do that in a non-bbcode forum.

EDIT: oh, now I see how… I was using a quick post and I didn’t no about the appropriate tag. thanks.

If anyone can try to run this code and replicate the behavior to try and debug, that would be helpful. Thanks again.

Hey, :slight_smile:
I’m the developer of the Cubes plugin. There is currently some trouble due to the new server of the jMonkeyEngine.

Anyway, I ecountered the same problem some time ago. It seems, you can’t reuse the same RigidBodyControl and just update the collision mesh (As I wrote somewhere else, sorry :(). My fix was to use a new RigidBodyControl - The following code should work fine:

blockTerrain.addChunkListener(new BlockChunkListener(){

    @Override
    public void onSpatialUpdated(BlockChunkControl blockChunk){
        updateCollisionShape(blockChunk.getOptimizedGeometry_Opaque());
        updateCollisionShape(blockChunk.getOptimizedGeometry_Transparent());
    }
});

… with:

private void updateCollisionShape(Geometry chunkGeometry){
    RigidBodyControl rigidBodyControl = chunkGeometry.getControl(RigidBodyControl.class);
    if(chunkGeometry.getTriangleCount() > 0){
        if(rigidBodyControl != null){
            chunkGeometry.removeControl(rigidBodyControl);
            bulletAppState.getPhysicsSpace().remove(rigidBodyControl);
        }
        rigidBodyControl = new RigidBodyControl(0);
        chunkGeometry.addControl(rigidBodyControl);
        bulletAppState.getPhysicsSpace().add(rigidBodyControl);
    }
    else{
        if(rigidBodyControl != null){
        chunkGeometry.removeControl(rigidBodyControl);
    }
}

Yours,
destro :slight_smile:

EDIT: As soon as the jME-plugin system will work again, I will put some time again in Cubes. There are already a few local bugfixes and improvements I’d like to commit. :slight_smile:

1 Like

Okay, thanks.

I love your plugin, by the way. It makes my life a TON easier for my first game.

@destroflyer @normen Whats is wrong with the plugin system? destro is really contributing to a lot of our games and if not able to push updates through the plugin system then how is it any good?

regards

@sebasoft said: @destroflyer @normen Whats is wrong with the plugin system? destro is really contributing to a lot of our games and if not able to push updates through the plugin system then how is it any good?

regards

I think there are three or four threads a week created on this subject so I don’t think it’s any good answering again buried here. If the other threads weren’t noticed then this one probably won’t be either. :wink:

Edit: one thread here that gets bumped a lot: http://hub.jmonkeyengine.org/forum/topic/server-migration-thursday-may-30/

Question for @destroflyer :

How should I go about multithreading this (as in the BlockChunkListener)? I am trying to make my base engine more efficient before I begin my next phase (multiplayer, which includes multithreading anyway)

Edit: I’m already multithreading the main part of the physics, so I just need to know how to get the BlockChunkListener to be on a new thread, if that clears my question up

Edit 2: I’ll make a new topic for this because I realize I shouldn’t have put it in this thread