[SOLVED] SoftBody/Terrain interaction

Hello to everybody,

I was performing a feasibility test using Minie and starting from an example, letting “splash” a soft body on the ground.
I’m facing some pass-trough effect that not happened if I use the box.
Can you help me understand which is the proper way to configure SoftBodyConfig, the terrain and the body to avoid that the body pass through the ground?

import com.jme3.anim.AnimComposer;
import com.jme3.anim.util.AnimMigrationUtils;
import com.jme3.app.SimpleApplication;
import com.jme3.asset.TextureKey;
import com.jme3.bullet.PhysicsSoftSpace;
import com.jme3.bullet.SoftPhysicsAppState;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.control.SoftBodyControl;
import com.jme3.bullet.objects.PhysicsBody;
import com.jme3.bullet.objects.PhysicsSoftBody;
import com.jme3.bullet.objects.infos.Sbcp;
import com.jme3.bullet.objects.infos.SoftBodyConfig;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Spatial;
import com.jme3.scene.debug.custom.ArmatureDebugAppState;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.scene.shape.Sphere.TextureMode;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import java.util.LinkedList;

/**
 * A simple example of a soft body.
 *
 * @author Stephen Gold sgold@sonic.net
 */
public class HelloSoftBody extends SimpleApplication {

    private PhysicsSoftSpace physicsSpace;
    private ArmatureDebugAppState debugAppState;
    private AnimComposer composer;
    final private LinkedList<String> anims = new LinkedList<>();
    private TerrainQuad terrain;
    private Material mat_terrain;
    private RigidBodyControl ball_phy;
    private static Sphere sphere;
    private Material stone_mat;

    public static void main(String[] ignored) {
        HelloSoftBody application = new HelloSoftBody();
        application.start();
    }

    /**
     * Initialize this application.
     */
    @Override
    public void simpleInitApp() {
        flyCam.setMoveSpeed(100);

        // Set up Bullet physics.
        SoftPhysicsAppState bulletAppState = new SoftPhysicsAppState();
        stateManager.attach(bulletAppState);
        bulletAppState.setDebugEnabled(true);
        physicsSpace = bulletAppState.getPhysicsSoftSpace();

        // Add a light to the scene.
        Vector3f direction = new Vector3f(1f, -2f, -4f).normalizeLocal();
        DirectionalLight sun = new DirectionalLight(direction);
        rootNode.addLight(sun);

        // Add a model to the scene.
        Spatial cgModel = assetManager.loadModel("Models/Jaime/Jaime.j3o");
        AnimMigrationUtils.migrate(cgModel);
        //cgModel.scale(0.3f);

        rootNode.attachChild(cgModel);

        // Add a soft-body control to the model.
        SoftBodyControl sbc = new SoftBodyControl();
        cgModel.addControl(sbc);

        // Translate and rotate the model's physics body.  Since the control
        // is "dynamic", the model will follow its body.        
        PhysicsSoftBody body = sbc.getBody();
        //body.applyRotation(new Quaternion().fromAngles(0.4f, 0f, 1f));
        body.applyTranslation(new Vector3f(10f, 50f, 10f));

        // Relocate the camera.        
        cam.setLocation(new Vector3f(-40f, 1f, -40f));
        cam.lookAtDirection(new Vector3f(body.getPhysicsLocation().getX(), 3, body.getPhysicsLocation().getZ()), Vector3f.UNIT_Y);
        // Set the body's default frame pose:  if deformed,
        // it will tend to return to its current shape.
        boolean setVolumePose = true;
        boolean setFramePose = false;
        body.setPose(setVolumePose, setFramePose);

        // Make the body bouncy by enabling pose matching.
        SoftBodyConfig config = body.getSoftConfig();
        config.set(Sbcp.PoseMatching, 0.5f);
        sbc.setPhysicsSpace(physicsSpace);
        
        // Check with a rigid body
        addBall();
        
        // This work
        //addBox();
        
        // This not work perfectly        
        initTerrain();
        
        // Test RBC with HeightfieldCollisionShape
        HeightfieldCollisionShape hcs = new HeightfieldCollisionShape(terrain, new Vector3f(2, 1, 2));
        RigidBodyControl plane_phy = new RigidBodyControl(hcs,PhysicsBody.massForStatic);
        terrain.addControl(plane_phy);
        plane_phy.setPhysicsSpace(physicsSpace);
        
        // Test RBC without HeightfieldCollisionShape
        //RigidBodyControl rbc = new RigidBodyControl(PhysicsBody.massForStatic); 
        //terrain.addControl(rbc);
        //bulletAppState.getPhysicsSpace().add(rbc); 
        
        
    }

    /**
     * Add a large static cube to serve as a platform.
     */
    private void addBox() {
        float halfExtent = 4f; // mesh units
        Mesh mesh = new Box(halfExtent, halfExtent, halfExtent);
        Geometry geometry = new Geometry("cube platform", mesh);
        rootNode.attachChild(geometry);

        geometry.move(10f, -halfExtent - 12f, 10f);
        ColorRGBA color = new ColorRGBA(0.1f, 0.4f, 0.1f, 1f);
        Material material = new Material(assetManager,
                "Common/MatDefs/Light/Lighting.j3md");
        material.setBoolean("UseMaterialColors", true);
        material.setColor("Diffuse", color);
        geometry.setMaterial(material);
        geometry.setShadowMode(RenderQueue.ShadowMode.Receive);

        BoxCollisionShape shape = new BoxCollisionShape(halfExtent);
        RigidBodyControl boxBody
                = new RigidBodyControl(shape, PhysicsBody.massForStatic);
        geometry.addControl(boxBody);
        boxBody.setPhysicsSpace(physicsSpace);
    }

    private void addBall() {
        stone_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG");
        key2.setGenerateMips(true);
        Texture tex2 = assetManager.loadTexture(key2);
        stone_mat.setTexture("ColorMap", tex2);
        sphere = new Sphere(32, 32, 0.4f, true, false);
        sphere.setTextureMode(TextureMode.Projected);
        Geometry ball_geo = new Geometry("ball", sphere);
        ball_geo.setMaterial(stone_mat);
        rootNode.attachChild(ball_geo);
        ball_geo.setLocalTranslation(new Vector3f(11f, 50f, 11f));
        ball_phy = new RigidBodyControl(1f);
        ball_geo.addControl(ball_phy);
        ball_phy.setPhysicsSpace(physicsSpace);
    }

    private void initTerrain() {
        mat_terrain = new Material(assetManager,
                "Common/MatDefs/Terrain/Terrain.j3md");
        mat_terrain.setTexture("Alpha", assetManager.loadTexture(
                "Textures/Terrain/splat/alphamap.png"));
        Texture grass = assetManager.loadTexture(
                "Textures/Terrain/splat/grass.jpg");
        grass.setWrap(Texture.WrapMode.Repeat);
        mat_terrain.setTexture("Tex1", grass);
        mat_terrain.setFloat("Tex1Scale", 64f);
        Texture dirt = assetManager.loadTexture(
                "Textures/Terrain/splat/dirt.jpg");
        dirt.setWrap(Texture.WrapMode.Repeat);
        mat_terrain.setTexture("Tex2", dirt);
        mat_terrain.setFloat("Tex2Scale", 32f);
        Texture rock = assetManager.loadTexture(
                "Textures/Terrain/splat/road.jpg");
        rock.setWrap(Texture.WrapMode.Repeat);
        mat_terrain.setTexture("Tex3", rock);
        mat_terrain.setFloat("Tex3Scale", 128f);
        Texture heightMapImage = assetManager.loadTexture(
                "Textures/Terrain/splat/mountains512.png");
        AbstractHeightMap heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
        heightmap.load();
        terrain = new TerrainQuad("my terrain", 65, 513, heightmap.getHeightMap());
        terrain.setMaterial(mat_terrain);
        terrain.setLocalTranslation(0, -100, 0);
        terrain.setLocalScale(2f, 1f, 2f);
        rootNode.attachChild(terrain);
    }
}

Here is a video of the code running :slight_smile:
SoftMonkey

1 Like

If you haven’t already, it could help to see if a non-soft body physics object properly collides with the terrain.

If the problem persists between the terrain and a normal RigidBodyControl, then I could maybe suggest trying this code to let the rigid body control generate a normal collision shape (instead of declaring and preparing a HeightfieldCollisionShape) to see if it fixes it:

   RigidBodyControl rbc = new RigidBodyControl(0); 
   (TerrainQuad)terrain.addControl(rbc);
   bulletAppState.getPhysicsSpace().add(rbc); 

I never used the HeightfieldCollisionShape from your code to vouche for it working or not, but I can confirm this code without it has worked for adding non-softbody physics to my terrains in the past.

1 Like

Thank you for the answered, I just tried as you suggested:

  • Adding a non-soft body → it work: it stop on the terrain surface
  • Avoid HeightfieldCollisionShape → don’t change the interaction of the soft-body

I updated the code with the try you suggested me :slight_smile:

1 Like

So then that sounds like it would confirm that the Terrain physics are working, and soft body physics are working - but specifically not with one another. I was going to suggest it could be an issue with your terrain as well, but I’d think that is unlikely if the terrain still works with rigid body controls.

Maybe @sgold would be able to help troubleshoot further

1 Like

Yes, as you said, I confirm that are both working (I added a video to highlight it), just there are some issues when interact with one another.

1 Like

Not sure what you’re trying to achieve here.

The monkey falls apart because instead of

        boolean setVolumePose = false;
        boolean setFramePose = true;
        body.setPose(setVolumePose, setFramePose);

your test used

        boolean setVolumePose = true;
        boolean setFramePose = false;
        body.setPose(setVolumePose, setFramePose);

Was that intentional?

1 Like

Hi sgold,

Thank you for for the assistance.

Yes it is intentional, I would like to achieve the effect of the soft body that “lose” the volume (and don’t try to get it back). What happen on the cube is perfect, and is not far with the terrain too. But I would like that the body (in this example Jaime ) don’t over-cross the terrain.
Before open this topic, I tested also the default setup in your example but, also with this settings, the terrain don’t stop the body, it continues falling down.
I also done some test on a flat terrain but i wasn’t able to prevent the body to falling.

If you have some suggestion I will be glad to test it and share the results.

1 Like

I think terrain differs from cube because it is one-sided.

A few suggestions for you to try:

  • a shorter physics timestep with physicsSpace.setAccuracy()
  • thicker collision margins for both the terrain and the soft body
  • more position iterations with softBody.getSoftConfig().setPositionIterations()
2 Likes

made the trick :slight_smile:

I set the margins at PhysicsSoftBody (in the example is named body)
body.setMargin(0.1f);
and the body stop to at the Terrain

Thank you very much for the assistance!!

PS
I also played with Accuracy and Iterations and increased the quality of the effect.

3 Likes
  • The greatness of Bullet Physics is having so many tuning parameters to experiment with.
  • The agony of Bullet Physics is having so many tuning parameters to experiment with.
    :wink:

Glad I was able to help!

4 Likes