[SOLVED] Sio2-bullet-char-demo works wired with latest Minie

Hello everyone,

I have a question regarding the Minie physics library.

I’m currently studying the source code of SiO2/bullet-char to understand how to use the BulletSystem in an ECS (Entity Component System).

Originally, Jaime could walk on the ground without any issues.

However, when I upgraded the jme3 version of this project to 3.6.1-stable and replaced jme3-jbullet with the latest version of Minie (7.7.0+big3), Jaime started floating in the air and couldn’t land or move anymore.

I tried debugging the code but couldn’t figure out why this is happening. I’ve also read through some portions of the code and tried different examples, but haven’t achieved the desired result. If anyone could provide guidance or point me in the right direction, I would greatly appreciate it.

Here are my system specifications:

  • Operating System: MacBook Pro
  • Chip: Apple M1 16GB
  • JDK: 1.8 Java™ SE Runtime Environment (build 1.8.0_291-b10)
  • JVM: Java HotSpot™ 64-Bit Server VM (build 25.291-b10, mixed mode)

Thank you in advance for your assistance!

Do other Minie examples work fine on your Mac outside of SiO2 integration?

Not sure if this is related, but in my case I am using a constant timeStep in BulletSystem when using native bullet. (I am using Minie 7.6.0).

I have applied the below changes to BulletSystem:

changed

to

        float t = 1 / 60f;

and

to

pSpace.update(t, 0);

I write an exmaple with minie myself, it works fine.
I can’t run minie examples, even I add -XStartOnFirstThread to vm environment.

I tried to debug and trace the startup process of a certain example in Minie to see where it got stuck. I found that the issue lies in the initialization of the DesktopAssetManager. Specifically, when initializing the AWTLoader, this class declares a static variable called AWT_RGBA4444.

package com.jme3.texture.plugins;

public class AWTLoader implements AssetLoader {

    public static final ColorModel AWT_RGBA4444 = new DirectColorModel(16,
                                                                       0xf000,
                                                                       0x0f00,
                                                                       0x00f0,
                                                                       0x000f);

When java.awt.image.DirectColorModel is instantiated, it executes the static block of code in ColorModel.

package java.awt.image;

public abstract class ColorModel implements Transparency{
    static void loadLibraries() {
        if (!loaded) {
            java.security.AccessController.doPrivileged(
                new java.security.PrivilegedAction<Void>() {
                    public Void run() {
                        System.loadLibrary("awt");
                        return null;
                    }
                });
            loaded = true;
        }
    }
}

This piece of code ultimately causes the entire application to freeze and the screen to turn black.

package java.security;

public final class AccessController {

    @CallerSensitive
    public static native <T> T doPrivileged(PrivilegedAction<T> action);
}

I change JDK from 1.8 to 17.0.2, the native method doPrivileged become a common static method:

package java.security;

@Deprecated(since="17", forRemoval=true)
public final class AccessController {
    @CallerSensitive
    public static <T> T doPrivileged(PrivilegedAction<T> action)
    {
        return executePrivileged(action, null, Reflection.getCallerClass());
    }
}

and Minie example works.

but my problem is still.

Just to help diagnose what might be wrong:
When Jaime is floating in the air then that means he thinks he is not on the ground. I can’t say what in your upgrade might have caused that but that’s why he’s permanently in float mode. Perhaps some physics tolerance defaults changed or something. I don’t have the demo code open in front of me so I’m not sure exactly what.

That’s why I also don’t remember if Jaime can be moved while floating by default. I know there is code in there to support both impulse-while-flying and no-impulse-while-flying but I don’t remember if it’s only switchable in code or can be toggled with a key.

…but if Jaime can normally move while in mid air then it may be the issue that Ali points out. I always had that issue with native bullet and weird time step behavior. I went deep into the C++ source code and could never figure it out.

Also, iirc in that example there was also moving platforms (kinematic boxes), are they also not moving?

When floating, Jaime can move very very slowly.

I tried Ali’s way but it didn’t work for me.

I changed Jaime’s SpawnPosition’s y coordinate from 1 to 10 and observed that he fell from a high altitude to the ground, bounced up a little upon impact, and eventually remained in a floating posture.

By the way, I remembered I also have made other changes, again not sure if this is relevant to your issue.

Instead of updating drivers directly from the update loop, I have registered a PhysicsTickListener to physics space and updating drivers inside the prePhysicsTick() and distributing updates in physicsTick().

    private class MobPhysicsTickListener implements PhysicsTickListener {

        @Override
        public void prePhysicsTick(PhysicsSpace space, float timeStep) {
            if (!impulses.isEmpty()) {
                // We don't really care if the set changed or not, we will
                // always iterate over all items until they are removed.
                // The applyImpulses() method will clear the current impulse
                // if the body exists for the entity.
                applyImpulses(impulses);
            }

            for (EntityRigidBody b : driverBodies.getArray()) {
                b.getControlDriver().prePhysicsTick(space, timeStep);
            }
        }

        @Override
        public void physicsTick(PhysicsSpace space, float timeStep) {

            for (EntityRigidBody b : driverBodies.getArray()) {
                b.getControlDriver().physicsTick(space, timeStep);

                // Distribute updates for bodies that have drivers but are not
                // normal mobs.
                if (b.getMass() == 0) {
                    objectUpdated(b);
                }
            }

            for (EntityPhysicsObject o : mobs.getArray()) {

                if (o instanceof EntityGhostObject g) {
                    // The only reason its in the mobs array is because
                    // is has a parent
                    EntityRigidBody parent = g.getParent();
                    if (parent == null) {
                        // May not have resolved yet
                        g.setParent(bodies.getObject(g.getParentId()));
                    }
                    g.updateToParent();
                }

                objectUpdated(o);

                if (o instanceof EntityRigidBody body) {
                    body.updateLastVelocity();
                }
            }
        }
    }

I am registering the listener in the initialize() method like this:

        pSpace = new PhysicsSpace(worldMin, worldMax, broadphaseType);
        pSpace.addCollisionListener(collisionDispatcher);
        pSpace.addTickListener(tickListener);

I don’t find this prePhysicsTick and physicsTick method, do you add them ur self?

Yes, I changed ControlDriver interface to extend PhysicsTickListener.

public interface ControlDriver extends PhysicsTickListener {

    public void initialize( EntityPhysicsObject body );

    //public void update( SimTime time, EntityPhysicsObject body );

    /**
     * Called before each step, here you apply forces (change the state).
     */
    //public void prePhysicsTick(PhysicsSpace space, float timeStep, EntityPhysicsObject object);

    /**
     * Called after each step, here you poll the results (get the current state).
     */
    //public void physicsTick(PhysicsSpace space, float timeStep, EntityPhysicsObject object);

    public void terminate( EntityPhysicsObject body );

    public void addCollision( EntityPhysicsObject otherBody, PhysicsCollisionEvent event );  
}

Again, I have no idea if this is relevant to your issue at all.

I think i should dig deeeeeeeper into the code.

Years ago I experimented with Sio2-bullet-char-demo and concluded that it relied on certain properties of kinematic rigid bodies that differed between JBullet and native Bullet. I don’t remember the details…

I think the only kinematic rigid bodies are the conveyor belt and the moving platforms.

…that shouldn’t affect OP’s issue.

Currently I made a playground with 3 groups of entities, they have different move speed.

Group: Blue Orange Bullet
Speed: 6.0 2.0 8.0

Rules:

  1. When the orange enters the blue’s range, the orange will be crushed, while the blue will not.
  2. When the blue enters the orange’s range, the orange will shot bullets to the blue and chase
    the blue.
  3. When the blue leaves the orange’s range, the orange will stop.
  4. The bullet can kill the blue, not kill orange.

I’m trying to start add more features like:

  1. BulletSystem : blue and orange entities should move with physics
  2. SpawnSystem: new entity will be spawn when older entity dead. orange will go back to it’s spawn position when lost target.

I am very interested in the performance limit and want to figure out how many entities can withstand without getting stuck (such as 60fps). Currently, Blue 1000, Orange 1000, and Bullets have a random survival time of only 0.5 seconds.

2 Likes

I found some differences between jbullet and Minie.

When Jamie falls onto the floor, PhysicSpace will generate a PhysicsCollisionEvent between the floor and Jamie at

  • jbullet: each tick
  • Minie: the start tick

This is the difference in com.jme3.bullet.PhysicSpace.

in jme3-jbullet, the collisionEvents will be repopulated by all intersecting entities.

/**
 * queue of collision events not yet distributed to listeners
 */
private ArrayDeque<PhysicsCollisionEvent> collisionEvents = new ArrayDeque<>();

public void distributeEvents() {
    int cListSize = this.collisionListeners.size();

    while(!this.collisionEvents.isEmpty()) {
        PhysicsCollisionEvent physicsCollisionEvent = (PhysicsCollisionEvent)this.collisionEvents.pop();

        for(int i = 0; i < cListSize; ++i) {
            ((PhysicsCollisionListener)this.collisionListeners.get(i)).collision(physicsCollisionEvent);
        }

        this.eventFactory.recycle(physicsCollisionEvent);
    }

}

in Minie, contactStartedEvents only have PhysicsCollisionEvent at the first time.

/**
 * contact-processed events not yet distributed to listeners
 */
final private Deque<PhysicsCollisionEvent> contactProcessedEvents
            = new ArrayDeque<>(20);
/**
 * contact-started events not yet distributed to listeners
 */
final private Deque<PhysicsCollisionEvent> contactStartedEvents
            = new ArrayDeque<>(20);

public void distributeEvents() {
    while (!contactStartedEvents.isEmpty()) {
        PhysicsCollisionEvent event = contactStartedEvents.pop();
        for (PhysicsCollisionListener listener : contactStartedListeners) {
            listener.collision(event);
        }
    }

    while (!contactProcessedEvents.isEmpty()) {
        PhysicsCollisionEvent event = contactProcessedEvents.pop();
        for (PhysicsCollisionListener listener
                : contactProcessedListeners) {
            listener.collision(event);
        }
    }
}

So this is not a Mac OS issue, but rather a difference in the behavior of the same interface (PhysicSpace#addCollisionListener) between Minie and jbullet.

The com.simsilica.demo.bullet.CharInputDriver class calculate groundContactCount at every frame.

private EntityRigidBody body;
private int groundContactCount = 0;

// listen to PhysicsCollisionEvent, only triggers at the start event
public void addCollision( EntityPhysicsObject otherBody, PhysicsCollisionEvent event ) {
    // ....
    if (event.getObjectA() == body || event.getObjectB() == body) {
        groundContactCount++;
    }
    // ....
}

protected void invalidateCollisionData() {
    groundContactCount = 0; // <------- this line makes Jamie floating in air.
}

public void update( SimTime time, EntityRigidBody body ) {
    // ....
    if (groundContactCount > 0) {
        // let Jamie walking
    } else {
        // let Jamie floating
    }
    // ...

    invalidateCollisionData();
}

To fix this issue, I modified BulletSystem a little, use addOngoingCollisionListener() instead of addCollisionListener()

    protected void initialize() {
        if( ed == null ) {
            ed = getSystem(EntityData.class, true);
        }
        if( shapes == null ) {
            shapes = getSystem(CollisionShapes.class, true);
        }
 
        baseThread = Thread.currentThread();
        
        pSpace = new PhysicsSpace(worldMin, worldMax, broadphaseType);
        //pSpace.addCollisionListener(collisionDispatcher); // <----- Changed this line
        pSpace.addOngoingCollisionListener(collisionDispatcher); // <------ to this line

        bodies = new BodyContainer(ed);
        ghosts = new GhostContainer(ed);
    }

this change add collisionDispatcher to contactProcessedListeners in Minie’s contactProcessedListeners, so CharInputDriver can receive PhysicsCollisionEvent at every tick.

That is how I fixed this issue in the demo.

4 Likes
pSpace = new PhysicsSpace(worldMin, worldMax, broadphaseType);

// In jbullet and bullet, this listens for continuous collisions. 
// In Minie, it only listens for the beginning of collisions.
pSpace.addCollisionListener(collisionDispatcher);

// In Minie, this is the listener for continuous collisions.
pSpace.addOngoingCollisionListener(collisionDispatcher);

I think it would be best to unify the behavior of the interface, otherwise it becomes misleading and confusing.

Good detective work, @yan !

this is not a Mac OS issue, but rather a difference in the behavior of the same interface (PhysicSpace#addCollisionListener) between Minie and jbullet.

This difference in behavior can be traced back to an old regression in native Bullet, documented as JME issue #1029.

Sometime around 2016, Sphere-Sphere collisions in Bullet stopped generating the ongoing collision events that jme3-bullet relied on. As a workaround, the semantics of the PhysicsSpace.addCollisionListener() method in jme3-bullet were changed. In jme3-jbullet (which did not experience the regression) the original semantics were retained.

For Minie, I adopted the jme3-bullet semantics instead of the jme3-jbullet ones.

That is how I fixed this issue in the demo.

That seems like a good solution.

3 Likes