Problem with multiple objects in HelloWind.java example using GLTF export from Blender

Hello everyone,

I’m currently working with the HelloWind.java example in jMonkeyEngine, and I’m facing an issue when using a GLTF export from Blender. The problem occurs whenmultiple objects with their respective soft bodies are created. It seems that all the objects are linked to the last soft body in the list.

I have the following code snippet in my implementation:

class SceneGraphVisitorImpl implements SceneGraphVisitor{
	Mesh mesh;
	@Override
	public void visit(Spatial spatial) {
		if(spatial instanceof Geometry) {
			mesh = ((Geometry)spatial).getMesh();
		}
		
	}
	public Mesh getMesh() {
		return mesh;
	}
	
}

public class Main
        extends SimpleApplication
        implements PhysicsTickListener {
    // *************************************************************************
    // constants

    /**
     * wind speed, in psu per second
     */
    final private static float windSpeed = 3f;
    // *************************************************************************
    // fields

    /**
     * true when the left-arrow key is pressed, otherwise false
     */
    private static volatile boolean turnLeft;
    /**
     * true when the right-arrow key is pressed, otherwise false
     */
    private static volatile boolean turnRight;
    /**
     * wind direction (in radians from +X)
     */
    private static float windAzimuth = -0.8f;
    /**
     * temporary storage for velocity vectors
     */
    final private static Vector3f tmpVelocity = new Vector3f();
    
    // *************************************************************************
    // new methods exposed
    List<PhysicsSoftBody> flagList = new ArrayList<PhysicsSoftBody>();
    /**
     * Main entry point for the HelloWind application.
     *
     * @param arguments array of command-line arguments (not null)
     */
    public static void main(String[] arguments) {
        Main application = new Main();
        application.start();
    }
    // *************************************************************************
    // SimpleApplication methods
    private Mesh getMesh(Node node) {
    	SceneGraphVisitorImpl graphVisitorImpl = new SceneGraphVisitorImpl();
    	node.depthFirstTraversal(graphVisitorImpl);
    	return graphVisitorImpl.getMesh();
    	
    }
    PhysicsSoftBody createflag(Vector3f location,Node flagModel) {
    
        // Create a soft rectangle for the flag.
        SoftBodyControl softBodyControl = new SoftBodyControl();
        flagModel.addControl(softBodyControl);
        PhysicsSoftBody flag = softBodyControl.getBody();
        
        NativeSoftBodyUtil.appendFromTriMesh(getMesh(flagModel), flag);
        flag.setMargin(0.1f);
        flag.setMass(1f);

        // Pin the left edge of the flag.
        int nodeIndex = 0; // upper left corner
        flag.setNodeMass(nodeIndex, PhysicsBody.massForStatic);
        nodeIndex = 2; // lower left corner
        flag.setNodeMass(nodeIndex, PhysicsBody.massForStatic);
        /*
         * Make the flag flexible by reducing the angular stiffness
         * of its material.
         */
        SoftBodyMaterial softMaterial = flag.getSoftMaterial();
        softMaterial.setAngularStiffness(0f);

        // Configure the flag's aerodynamics.
        SoftBodyConfig config = flag.getSoftConfig();
        config.setAerodynamics(Aero.F_TwoSidedLiftDrag);
        config.set(Sbcp.Damping, 0.01f); // default = 0
        config.set(Sbcp.Drag, 0.5f); // default = 0.2
        config.set(Sbcp.Lift, 1f); // default = 0
        /*
         * Improve simulation accuracy by increasing
         * the number of position-solver iterations for the flag.
         */
        config.setPositionIterations(3);

        Quaternion rotation
                = new Quaternion().fromAngles(FastMath.HALF_PI, 0f, 0f);
        flag.applyRotation(rotation);
        flag.applyTranslation(location);

        // Initialize the wind velocity.
        tmpVelocity.x = windSpeed * FastMath.cos(windAzimuth);
        tmpVelocity.z = windSpeed * FastMath.sin(windAzimuth);
        flag.setWindVelocity(tmpVelocity);

        flag.setDebugNumSides(2);
        getStateManager().getState(SoftPhysicsAppState.class).getPhysicsSoftSpace().addCollisionObject(flag);
        return flag;
    }
    /**
     * Initialize the application.
     */
    @Override
    public void simpleInitApp() {
        configureCamera();
        configureInput();
        //stateManager.attach(new VideoRecorderAppState());
        // Set the viewport's background color to light blue.
        ColorRGBA skyColor = ColorRGBA.Cyan;
        viewPort.setBackgroundColor(skyColor);

        // Set up Bullet physics (with debug enabled).
        SoftPhysicsAppState bulletAppState = new SoftPhysicsAppState();
        stateManager.attach(bulletAppState);
        bulletAppState.setDebugEnabled(true); // for debug visualization
        bulletAppState.setWindVelocityFilter(new FilterAll(true));
        PhysicsSoftSpace physicsSpace = bulletAppState.getPhysicsSoftSpace();

        physicsSpace.setAccuracy(0.01f); // 10-msec timestep

        Vector3f gravityVector = new Vector3f(0f, -1f, 0f);
        physicsSpace.setGravity(gravityVector);

        // To enable the callbacks, register the application as a tick listener.
        //physicsSpace.addTickListener(this);

        // Generate a subdivided rectangle mesh with alternating diagonals.
//        int xLines = 20;
//        int zLines = 2 * xLines; // 2x as wide as it is tall
//        float width = 2f;
//        float lineSpacing = width / zLines;
//        Mesh mesh = new ClothGrid(xLines, zLines, lineSpacing);
        GltfModelKey  modelKey = new GltfModelKey("/Models/flag.glb");    
        for(int i=0; i <5;i++) {
            Node flagModel = (Node)getAssetManager().loadModel(modelKey);
            flagModel.setLocalTranslation(new Vector3f(0,0,i * 4));
        	flagList.add(createflag(new Vector3f(i * 4,0,0),flagModel));
        	rootNode.attachChild(flagModel);
        }
        rootNode.addLight(new AmbientLight());
        rootNode.addLight(new DirectionalLight(new Vector3f(0,0,-1)));
        ModelKey Key = new ModelKey("/Models/Sky_Cloudy.j3o");
		Node scene = (Node) getAssetManager().loadModel(Key);
		LightProbe lightProbe = (LightProbe) scene.getLocalLightList().get(0);
		lightProbe.setName("Default LightProbe");
		lightProbe.setPosition(new Vector3f());
		lightProbe.getArea().setRadius(1000);
		getRootNode().addLight(lightProbe);
		getRenderManager().setPreferredLightMode(LightMode.SinglePassAndImageBased);
		flyCam.setMoveSpeed(20);
    }

I can’t see what the flag variable is. But looking at the Javadoc (I assume that you are using Minie, this is also not specified), if you give the same parameter to all your flag models… they get fused together.

Maybe you could complete your question a bit more on these details.

1 Like

I edited my question with detailed code

flag is PhysicsSoftBody created for every flagModel

Also I suspect that this is actually from Minie:
TutorialApps/src/main/java/jme3utilities/tutorial/HelloWind.java

There is no HelloWind.java in jMonkeyEngine.

1 Like

Just a note, but as written if the model has more than one mesh (very common) then you will only be getting one of them.

1 Like

Yes, I know that
The model is just a subdivided plane
but the above code snipped I just create multiple object from it

What leads to you believe all objects are linked to the same soft body?

First: without this code flagModel.setLocalTranslation(new Vector3f(0,0,i * 4));
All of the flags are in the same position (Same Place)


Second: You can see all the flags have the same animation of the closer softbody

1 Like

All the flags have identical base meshes and experience the same forces. There’s no randomness that I can see. Why would you expect them to move/animate differently?

First: I’m sorry for late response (in home I have iphone6 which doesn’t allow me to login)
Second indeed all the softbodies have slightly different animation such as here

when I omit flagModel.setLocalTranslation(new Vector3f(0,0,i * 4)); and just depend only on flag.applyTranslation(location); all the meshes placed where the last softbody location as you see here

I guess I don’t understand what the issue is, then. What exactly are you trying to achieve?

I want to create many flag objects and distribute them at fixed distance, and I want to see if my approach wrong
Because all the flags I created following just one softBody, which is the last one when I use flag.applyTranslation(location);

I’m still confused. You say you want:

  • to create many flag objects
  • to distribute them at fixed distance

It appears you’ve done both those things.

You say all the flags follow just one soft body, yet you also say they have different animations. I don’t understand how the flags could show different animations if they are all are following the same body.

I’m sorry if I made you busy with me
As you see I made five flags with each softbody with specific location for each, but they all placed in same location of the last softbody, not in the location of their softbody(that’s the strange thing)
I made experiment to see where is the issue
First: I use this code to see if flags have their softBodyControl

int i = 0;
for(SoftBodyControl softBodyControl:flagList) {
        	System.out.println("softBodyControl number "+ ++i+": ");
        	((Node)softBodyControl.getSpatial()).depthFirstTraversal(new SceneGraphVisitor() {
				
				@Override
				public void visit(Spatial spatial) {
					if(spatial instanceof Geometry)
						System.out.println("\t\t"+((Geometry)spatial).getName());
					
				}
			});
        }

this is the output

softBodyControl number 1:
Plane1
softBodyControl number 2:
Plane2
softBodyControl number 3:
Plane3
softBodyControl number 4:
Plane4
softBodyControl number 5:
Plane5

Second: to check if the softbody for each controller is the same

if(flagList.get(0).getBody()==flagList.get(1).getBody())
        	System.out.println("true"); 

there is no output

third: I used this code in simpleUpdate

flagList.get(4).getBody().applyTranslation(Vector3f.UNIT_Y.mult(tpf));

all the flags acted together

if I change the index for example to 3 just the debug of the softbody is acted without the flags

1 Like

I understand about 90% of your explanation, and I can’t execute your code because I don’t have the “flag.glb” model file. I usually debug code by executing it, not by reading it in a web browser, so I can’t be 100% sure what’s going on here. I’ll do my best, but it might help if you posted your complete application including “flag.glb”.

One oddity I noticed while reading your code is how you’re creating the soft bodies in createflag():

        SoftBodyControl softBodyControl = new SoftBodyControl();
        flagModel.addControl(softBodyControl);
        PhysicsSoftBody flag = softBodyControl.getBody();

        NativeSoftBodyUtil.appendFromTriMesh(getMesh(flagModel), flag);

Adding a SoftBodyControl to a Spatial should append all triangles in the spatial’s first mesh to the soft body, which is also what appendFromTriMesh() does. Either appendFromTriMesh() is redundant or else something is going on here that I don’t understand yet.

Also, it looks to me like your code translates each Spatial

flagModel.setLocalTranslation(new Vector3f(0, 0, i * 4));

and also its corresponding soft body:

flag.applyTranslation(location);

The main purpose of SoftBodyControl is to apply the soft body’s transform to the spatial, so I suspect the initial transform is being applied twice.

third: I used this code in simpleUpdate
flagList.get(4).getBody().applyTranslation(Vector3f.UNIT_Y.mult(tpf));

I’m surprised to learn that your application has a simpleUpdate() method, since the code snippet you posted did not include it. Repeating myself, I recommend sharing your complete application including “flag.glb”.

1 Like

First: This is the simple project

Second: you need to modify the gradle file to your environment
Third:

This code or the 90% of this code from HelloWind example exist in Minie
Forth:

I needed this code to separate the flag models between themselves because they were in the same place (it looked to me at first they were just one object)

This code so I can place each of the softBodies in their own locations (You can see that I code the models and softBodies individually as if they are not related)
Fifth:
Yesterday I tested with SimpleUpdate to see how the objects will react

Update:
This is the whole file code this far

/*
 Copyright (c) 2019-2023, Stephen Gold
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in the
 documentation and/or other materials provided with the distribution.
 * Neither the name of the copyright holder nor the names of its contributors
 may be used to endorse or promote products derived from this software without
 specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package testgradlejme;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import com.jme3.app.SimpleApplication;
import com.jme3.app.state.VideoRecorderAppState;
import com.jme3.asset.ModelKey;
import com.jme3.bullet.PhysicsSoftSpace;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.PhysicsTickListener;
import com.jme3.bullet.SoftPhysicsAppState;
import com.jme3.bullet.control.SoftBodyControl;
import com.jme3.bullet.objects.PhysicsBody;
import com.jme3.bullet.objects.PhysicsSoftBody;
import com.jme3.bullet.objects.infos.Aero;
import com.jme3.bullet.objects.infos.Sbcp;
import com.jme3.bullet.objects.infos.SoftBodyConfig;
import com.jme3.bullet.objects.infos.SoftBodyMaterial;
import com.jme3.bullet.util.NativeSoftBodyUtil;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.InputListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.light.LightProbe;
import com.jme3.material.TechniqueDef.LightMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.SceneGraphVisitor;
import com.jme3.scene.Spatial;

import jme3utilities.math.MyMath;
import jme3utilities.minie.FilterAll;
import com.jme3.scene.plugins.gltf.GltfModelKey;

/**
 * A simple cloth simulation with wind.
 * <p>
 * Builds upon HelloPin.
 *
 * @author Stephen Gold sgold@sonic.net
 */
class SceneGraphVisitorImpl implements SceneGraphVisitor{
	Mesh mesh;
	static int i = 0;
	@Override
	public void visit(Spatial spatial) {
		if(spatial instanceof Geometry) {
			((Geometry)spatial).setName(((Geometry)spatial).getName() + ++SceneGraphVisitorImpl.i);
			mesh = ((Geometry)spatial).getMesh();
			System.out.println(((Geometry)spatial).getName());
		}
		
	}
	public Mesh getMesh() {
		return mesh;
	}
	
}

public class Main
        extends SimpleApplication
        implements PhysicsTickListener {
    // *************************************************************************
    // constants

    /**
     * wind speed, in psu per second
     */
    final private static float windSpeed = 3f;
    // *************************************************************************
    // fields

    /**
     * true when the left-arrow key is pressed, otherwise false
     */
    private static volatile boolean turnLeft;
    /**
     * true when the right-arrow key is pressed, otherwise false
     */
    private static volatile boolean turnRight;
    /**
     * wind direction (in radians from +X)
     */
    private static float windAzimuth = -0.8f;
    /**
     * temporary storage for velocity vectors
     */
    final private static Vector3f tmpVelocity = new Vector3f();
    
    // *************************************************************************
    // new methods exposed
    List<SoftBodyControl> flagList = new ArrayList<SoftBodyControl>();
    /**
     * Main entry point for the HelloWind application.
     *
     * @param arguments array of command-line arguments (not null)
     */
    public static void main(String[] arguments) {
        Main application = new Main();
        application.start();
    }
    // *************************************************************************
    // SimpleApplication methods
    private Mesh getMesh(Node node) {
    	SceneGraphVisitorImpl graphVisitorImpl = new SceneGraphVisitorImpl();
    	node.depthFirstTraversal(graphVisitorImpl);
    	return graphVisitorImpl.getMesh();
    	
    }
    void createflag(Vector3f location,SoftBodyControl softBodyControl,Node flagModel) {
    
        // Create a soft rectangle for the flag.
        
        flagModel.addControl(softBodyControl);
        PhysicsSoftBody flag = softBodyControl.getBody();
        NativeSoftBodyUtil.appendFromTriMesh(getMesh(flagModel), flag);
        flag.setMargin(0.1f);
        flag.setMass(1f);

        // Pin the left edge of the flag.
        int nodeIndex = 0; // upper left corner
        flag.setNodeMass(nodeIndex, PhysicsBody.massForStatic);
        nodeIndex = 2; // lower left corner
        flag.setNodeMass(nodeIndex, PhysicsBody.massForStatic);
        /*
         * Make the flag flexible by reducing the angular stiffness
         * of its material.
         */
        SoftBodyMaterial softMaterial = flag.getSoftMaterial();
        softMaterial.setAngularStiffness(0f);

        // Configure the flag's aerodynamics.
        SoftBodyConfig config = flag.getSoftConfig();
        config.setAerodynamics(Aero.F_TwoSidedLiftDrag);
        config.set(Sbcp.Damping, 0.01f); // default = 0
        config.set(Sbcp.Drag, 0.5f); // default = 0.2
        config.set(Sbcp.Lift, 1f); // default = 0
        /*
         * Improve simulation accuracy by increasing
         * the number of position-solver iterations for the flag.
         */
        config.setPositionIterations(3);

        Quaternion rotation
                = new Quaternion().fromAngles(FastMath.HALF_PI, 0f, 0f);
        flag.applyRotation(rotation);
        flag.applyTranslation(location);

        // Initialize the wind velocity.
        tmpVelocity.x = windSpeed * FastMath.cos(windAzimuth);
        tmpVelocity.z = windSpeed * FastMath.sin(windAzimuth);
        flag.setWindVelocity(tmpVelocity);

        flag.setDebugNumSides(2);
        getStateManager().getState(SoftPhysicsAppState.class).getPhysicsSoftSpace().addCollisionObject(flag);
    }
    /**
     * Initialize the application.
     */
    @Override
    public void simpleInitApp() {
        configureCamera();
        configureInput();
        stateManager.attach(new VideoRecorderAppState(new File("/2.avi")));
        // Set the viewport's background color to light blue.
        ColorRGBA skyColor = ColorRGBA.Cyan;
        viewPort.setBackgroundColor(skyColor);

        // Set up Bullet physics (with debug enabled).
        SoftPhysicsAppState bulletAppState = new SoftPhysicsAppState();
        stateManager.attach(bulletAppState);
        bulletAppState.setDebugEnabled(true); // for debug visualization
        bulletAppState.setWindVelocityFilter(new FilterAll(true));
        PhysicsSoftSpace physicsSpace = bulletAppState.getPhysicsSoftSpace();

        physicsSpace.setAccuracy(0.01f); // 10-msec timestep

        Vector3f gravityVector = new Vector3f(0f, -1f, 0f);
        physicsSpace.setGravity(gravityVector);

        GltfModelKey  modelKey = new GltfModelKey("/Models/flag.glb");    
        for(int i=0; i <5;i++) {
            Node flagModel = (Node)getAssetManager().loadModel(modelKey);
            flagModel.setLocalTranslation(new Vector3f(0,0,i * 4));
        	flagList.add(new SoftBodyControl());
        	createflag(new Vector3f(i * 4,0,0),flagList.get(i),flagModel);
        	rootNode.attachChild(flagModel);
        }
        int i = 0;
        for(SoftBodyControl softBodyControl:flagList) {
        	System.out.println("softBodyControl number "+ ++i+": ");
        	((Node)softBodyControl.getSpatial()).depthFirstTraversal(new SceneGraphVisitor() {
				
				@Override
				public void visit(Spatial spatial) {
					if(spatial instanceof Geometry)
						System.out.println("\t\t"+((Geometry)spatial).getName());
					
				}
			});
        }
        if(flagList.get(0).getBody()==flagList.get(1).getBody())
        	System.out.println("true");
        rootNode.addLight(new AmbientLight());
        rootNode.addLight(new DirectionalLight(new Vector3f(0,0,-1)));
        ModelKey Key = new ModelKey("/Models/Sky_Cloudy.j3o");
		Node scene = (Node) getAssetManager().loadModel(Key);
		LightProbe lightProbe = (LightProbe) scene.getLocalLightList().get(0);
		lightProbe.setName("Default LightProbe");
		lightProbe.setPosition(new Vector3f());
		lightProbe.getArea().setRadius(1000);
		getRootNode().addLight(lightProbe);
		getRenderManager().setPreferredLightMode(LightMode.SinglePassAndImageBased);
		flyCam.setMoveSpeed(20);
    }
    // *************************************************************************
    // PhysicsTickListener methods

    /**
     * Callback from Bullet, invoked just before each simulation step.
     *
     * @param space the space that's about to be stepped (not null)
     * @param timeStep the time per simulation step (in seconds, &ge;0)
     */
    @Override
    public void simpleUpdate(float tpf) {
    	
    	flagList.get(3).getBody().applyTranslation(Vector3f.UNIT_Y.mult(tpf));
    	
    	super.simpleUpdate(tpf);
    }
    @Override
    public void prePhysicsTick(PhysicsSpace space, float timeStep) {
        // Update the flag's wind velocity.
        if (turnLeft) {
            windAzimuth -= timeStep;
        }
        if (turnRight) {
            windAzimuth += timeStep;
        }
        windAzimuth = MyMath.standardizeAngle(windAzimuth);
        tmpVelocity.x = windSpeed * FastMath.cos(windAzimuth);
        tmpVelocity.z = windSpeed * FastMath.sin(windAzimuth);
//        for(PhysicsSoftBody flag:flagList)
//        	flag.setWindVelocity(tmpVelocity);
    }

    /**
     * Callback from Bullet, invoked just after each simulation step.
     *
     * @param space the space that was just stepped (not null)
     * @param timeStep the time per simulation step (in seconds, &ge;0)
     */
    @Override
    public void physicsTick(PhysicsSpace space, float timeStep) {
        // do nothing
    }
    // *************************************************************************
    // private methods

    /**
     * Configure the Camera during startup.
     */
    private void configureCamera() {
        cam.setLocation(new Vector3f(7f, 1.2f, -0.7f));
        cam.setRotation(new Quaternion(0.08619f, -0.68974f, 0.0833f, 0.71407f));
    }

    /**
     * Configure keyboard input during startup.
     */
    private void configureInput() {
        inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_F1));
        inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_F2));
        InputListener input = new ActionListener() {
            @Override
            public void onAction(String action, boolean isPressed, float tpf) {
                switch (action) {
                    case "left":
                        turnLeft = isPressed;
                        return;

                    case "right":
                        turnRight = isPressed;
                        return;

                    default:
                        System.out.println("Unknown action: " + action);
                }
            }
        };
        inputManager.addListener(input, "left", "right");
    }
}
1 Like

JMonkeyEngine’s smart asset cache doesn’t clone vertex buffers unless it knows they’re used for animation. So although your loop invokes loadModel() 5 times, you don’t obtain five 100% independent copies of the “flag.glb” model. Since the cache has no way of knowing that “flag.glb” will be animated by soft-body physics, all 5 copies of the model refer to the same vertex buffers.

An easy way to obtain independent copies of the model would be to invoke Cloner.deepClone() thus:

GltfModelKey  modelKey = new GltfModelKey("/Models/flag.glb");    
Node model = (Node) getAssetManager().loadModel(modelKey);
for(int i=0; i <5;i++) {
    Node flagModel = Cloner.deepClone(model);

The next issue is that SoftBodyControl expects to add the softbody to the physics space; you shouldn’t be adding it explicitly. So replace:

getStateManager().getState(SoftPhysicsAppState.class).getPhysicsSoftSpace().addCollisionObject(flag);

with

PhysicsSoftSpace physicsSpace = stateManager.getState(SoftPhysicsAppState.class).getPhysicsSoftSpace();
softBodyControl.setPhysicsSpace(physicsSpace);

Finally, once physics simulation begins you should never invoke applyTranslation() on a softbody. Doing so (in simpleUpdate()) is tearing the body apart. A better way to apply kinematic motion to a flag would be to create a kinematic rigid body and attach the soft body to it using using 2 anchors in place of the 2 pins.

For an example of attaching a soft body to a rigid body using anchors, see TestSoftBody:

3 Likes

Thank you, it works :smile:

1 Like