Zay-ES and Bullet physics best practices

What is best-practice for making Zay-ES en Bullet physics work nicely together? Or isn’t there one?

Currently I have a simple “game” which a car (PhysicsVehicle) and a track (RigidBody). I implemented a basic entity system based on the Space Invaders tutorial.

I integrated the physics system by creating a PhysicsAppState class that extends from BulletAppState. It handles all entities that have a Physics component, as follows:

ed = stateManager.getState(EntityDataState.class).getEntityData();
entities = ed.getEntities(Physics.class, Model.class);
controlTarget = ed.getEntities(ControlTarget.class, Physics.class);

There is also one entity that has a “ControlTarget” component, which determines which PhysicsControl is controlled by the player.

The override of the update method gets the players input from the entity system and applies it to the Physics control:

@Override
public void update(float tpf) {
    if (controlTarget.applyChanges()) {
        controlTarget.stream().findFirst().ifPresent(e -> {
            VehicleControl c = (VehicleControl) controls.get(e.getId());
            ControlTarget t = e.get(ControlTarget.class);
            if (c != null && t != null) {
                c.accelerate(t.getAcceleration());
                c.brake(t.getBraking());
                c.steer(t.getSteeringPosition());
            }
        });
    }
    
    super.update(tpf);
    
    if (entities.applyChanges()) {
        removeControls(entities.getRemovedEntities());
        addControls(entities.getAddedEntities());
        updateControls(entities.getChangedEntities());
    }
}

Since the models are controlled by the physics system, I need to update my entities with new positions, so things like the chase cam can follow the player. I do this in the VisualAppState. There, I filter out all entities that do not have a fixed position (as updating them would be a waste):

    ed = this.app.getStateManager().getState(EntityDataState.class).getEntityData();
    allEntities = ed.getEntities(Position.class, Model.class);
    movingEntities = ed.getEntities(
            Filters.fieldEquals(Position.class, "isFixed", false),
            Position.class, 
            Model.class);

And in the update override I update their positions:

@Override
public void update(float tpf) {
    if (allEntities.applyChanges()) {
        removeModels(allEntities.getRemovedEntities());
        addModels(allEntities.getAddedEntities());
    }
    
    if (movingEntities.applyChanges()) {
        for (Entity e : movingEntities) {
            // update positions for spatials that are controlled by the physics system
            Spatial o = models.get(e.getId());
            e.set(new Position(o.getWorldTranslation().clone()));
        }
    }
}

Is this a sensible way of doing things? I read somewhere else that people are listening to a physics ticker instead. Is that a better approach?

JME’s integration with bullet (the part with RigidBodyControl added to a Spatial, etc.) is kind of doing a lot of the job that an ES would and in a way that’s very non-ES. That makes it hard to recommend using. I understand it’s very tempting because of all of the existing adapter code and certainly the JME documentation is geared that way.

It would be nice if someone could do a proper Bullet to Zay-ES adapter and make it available. I’m not a bullet user myself so I’m ill-suited though I can describe how it should work.

This would take the form of a Physics System that watches for components that indicate that an object should be physical. It would then create and manage the various PhysicsBodies as necessary (like PhysicsRigidBody). At the end of every frame, it would publish the latest position/orientation of these objects back to the entities.

To be the most reusable, the PhysicsSystem shouldn’t implement any particular interface or extend any particular base class. Then it could be easily wrapped in a JME app state or wrapped in a other types of back-end game system code (like SiO2 uses).

This is not dissimilar to how the SimEthereal examples do their physics… it’s just that instead of managing the physics yourself you’d be letting Bullet do it.

I just looked into JME’s PhysicsSpace and I see that this is currently the native-interface to bullet. That’s unfortunate because publicly it only exposes access to Spatials, really. Probably someone needs to flip all of those private “add” methods to protected so subclasses would at least be able to open it up a bit more for apps that don’t want to use Spatials in their physics. (Or add a few more “else” branches to the add(Object)/remove(Object) methods…)

I guess with JME Bullet in the state its in, your only easy path would be to create some dummy spatials that are only used for holding RigidBodyControls and are private to the physics space. These spatials would not be the same spatials that the model state uses and they’d never really be attached to a scene graph.

Edit: added a note about “else” branches being another alternative for opening up PhysicsSpace a bit.

Here is my solution

Yes, I took your suggestion. Thanks for your help. :slight_smile:

Body class is same as yours

*  @author    Paul Speed
 */
public class Body {
    public final EntityId bodyId;
    
    public Vec3d pos = new Vec3d();
    public Vec3d velocity = new Vec3d();
    public Vec3d acceleration = new Vec3d();
    public double radius = 1;
    public double invMass = 1;
    public AaBBox bounds = new AaBBox(radius);
    
    public Quatd orientation = new Quatd();
    public volatile ControlDriver driver;
 
    
    public Body( EntityId bodyId ) {
        this.bodyId = bodyId;
        this.bounds = new AaBBox(radius);
    }
    
    public Body( EntityId bodyId, double x, double y, double z ) {
        this.bodyId = bodyId;
        this.pos.set(x, y, z);
    }
    
    public void setPosition( Vec3d position ) {
        this.pos.set(position);
    }
    
    public void setRotation( Quatd rotation ) {
        this.orientation.set(rotation);
    }
 
    public void integrate( double stepTime ) {
        
        // Update the bounds since it's easy to do here and helps
        // other things know where the object is for real
        bounds.setCenter(pos);   
    }

}

Are you effectively keeping a physics-only set of spatials?

Only if we want some kind of mesh accurate collision shapes.
No needs for primitive ones.

Ah, maybe it’s PhysicsControlDriver where the magic happens?

So are you using JME’s physics controls directly without spatials?

I’m trying to understand exactly what you are passing to the PhysicsSpace so that bullet manages your physical objects.

Yep,

Physic control internally adds collision shapes to physic space

/**
     * This is implemented from AbstractPhysicsControl and called when the
     * control is supposed to add all objects to the physics space.
     *
     * @param space
     */
    @Override
    protected void addPhysics(PhysicsSpace space) {
        space.getGravity(localUp).normalizeLocal().negateLocal();
        updateLocalCoordinateSystem();

        space.addCollisionObject(rigidBody);
        space.addTickListener(this);
    }

I only need control to apply my physic forces.

I wish I knew more about Bullet to roll an “official” integration… but not enough to learn bullet. :slight_smile:

1 Like

The whole bullet wrapper works fine without any controls, thats why the controls extend the base physics objects. If you mix an ES and bullet you should definitely NOT use the controls but use the base physics objects and apply the physics transforms yourself via the ES.

That’s what I thought… but I couldn’t find any methods to add the bodies to the PhysicsSpace.

But looking again, I now see:

    public void addCollisionObject(PhysicsCollisionObject obj) {
        if (obj instanceof PhysicsGhostObject) {
            addGhostObject((PhysicsGhostObject) obj);
        } else if (obj instanceof PhysicsRigidBody) {
            addRigidBody((PhysicsRigidBody) obj);
        } else if (obj instanceof PhysicsVehicle) {
            addRigidBody((PhysicsVehicle) obj);
        } else if (obj instanceof PhysicsCharacter) {
            addCharacter((PhysicsCharacter) obj);
        }
    }

Which means add(Object) also works because it delegates to that one.

I somehow missed this before. So yeah, it’s pretty straight forward then.

Edit: even better from a continuity perspective, it would look almost identical to the dyn4j integration I’ve done before. Good stuff.

Great to see this thread actually because it takes a lot of fear away, when the pros work on that integration :smiley:
@Ali_RS I saw you extend BetterCharacterControl though somewhere, but I guess that is only to not re-implement the calculations it does?

You mean this :wink::sweat_smile:

/**
 * BetterCharacterControl with accessible PhysicsRigidBody for doing
 * more advanced controls on character.
 * 
 * @author Ali-RS <ali_codmw@yahoo.com>
 */
public class PhysicsCharacterControl extends BetterCharacterControl {

    public PhysicsCharacterControl(float radius, float height, float mass) {
        super(radius, height, mass);
    }
    
    public PhysicsRigidBody getPhysicsRigidBody(){
        return rigidBody;
    }
   
}

As normen said

I really do not need to have controls and using PhysicsRigidBody will be more pure.
but for now I am keeping them because physic controls (like BetterCharacterControl) will do some premade calculations for me.

1 Like

So, if I understand correcly, it would be better to replace the controls like VehicleControl with their superclasses like PhysicsVehicle, and apply the transformations on the entities manually (I guess you can borrow quite a bit of code from the control to do so).

Yes.

No. That’s the point… the controls really only call spatial.setLocalTranslation, spatial.setLocalRotation, etc… which is not at all the same as updating components on an entity.

Edit: I mean, you can pull up the source code to something like RigidBodyControl and see that 90% of that code is “implementing Control” and the other is doing stuff that you’d do completely different in an ES. (creating the collision shape and updating the entity)

Edit: I mean, you can pull up the source code to something like RigidBodyControl and see that 90% of that code is “implementing Control” and the other is doing stuff that you’d do completely different in an ES. (creating the collision shape and updating the entity)

Sorry, I wasn’t very precise. I understand it’s undesirable to copy the control code. The stuff that needs to be translated to ES is probably mostly in RigidBodyMotionState.

To bad you can’t inject your own RigidBodyMotionState into PhysicsRigidBody. That would probably do the trick. Another route could be subclassing PhysicsVehicle, as motionstate is a protected member. Might try that tomorrow.

1 Like

RigidBodyMotionState seems to have a native component. So I guess it can’t really be worked around.

So you’d just use the getters to grab the rotation and location. Everything else can be ignored.

I mean the bulk of the ‘meat’ of that class is:

    public boolean applyTransform(Spatial spatial) {
        Vector3f localLocation = spatial.getLocalTranslation();
        Quaternion localRotationQuat = spatial.getLocalRotation();
        boolean physicsLocationDirty = applyTransform(motionStateId, localLocation, localRotationQuat);
        if (!physicsLocationDirty) {
            return false;
        }
        if (!applyPhysicsLocal && spatial.getParent() != null) {
            localLocation.subtractLocal(spatial.getParent().getWorldTranslation());
            localLocation.divideLocal(spatial.getParent().getWorldScale());
            tmp_inverseWorldRotation.set(spatial.getParent().getWorldRotation()).inverseLocal().multLocal(localLocation);

//            localRotationQuat.set(worldRotationQuat);
            tmp_inverseWorldRotation.mult(localRotationQuat, localRotationQuat);

            spatial.setLocalTranslation(localLocation);
            spatial.setLocalRotation(localRotationQuat);
        } else {
            spatial.setLocalTranslation(localLocation);
            spatial.setLocalRotation(localRotationQuat);
//            spatial.setLocalTranslation(worldLocation);
//            spatial.setLocalRotation(worldRotationQuat);
        }
        if (vehicle != null) {
            vehicle.updateWheels();
        }
        return true;
    }

Which is not relevant in this case.

I think you end up just using the PhysicsRigidBody “as is”. Ignore the internal motion state and just grab the physics location and rotation when you want to update the entity after the physics step is performed.

Actually, it is that meat that calls:

boolean physicsLocationDirty = applyTransform(motionStateId, localLocation, localRotationQuat);

which is what calls native Bullet. So unfortunately, it is very relevant.

Ugh… seems like maybe that class needs some patching.

I’ve been trying to get it to work without using controls yesterday. Something is not quite right. My vehicle falls through the track.

This is how I create the track and vehicle:

public PhysicsCollisionObject create(Spatial s, Physics p) {      
    switch(p.getType()) {
        case RigidBody:
            return createRigidBody(s);
            
        case Vehicle:
            return createVehicle(s);   
            
        default:
            return null;
    }
}

private PhysicsCollisionObject createRigidBody(Spatial visual) {
    CollisionShape shape = CollisionShapeFactory.createMeshShape(visual);        
    return new PhysicsRigidBody(shape);
}

private PhysicsCollisionObject createVehicle(Spatial visual) {
    float stiffness = 60.0f;//200=f1 car
    float compValue = 0.29f; //(lower than damp!)
    float dampValue = 0.3f;
    final float mass = 400;
    
    Spatial carSpatial = findSpatial(visual, "car");
    Geometry carBody = (Geometry)((Node)carSpatial).getChild(0);
    
    HullCollisionShape vehicleShape = new HullCollisionShape(carBody.getMesh());
    
    //Create a vehicle control
    PhysicsVehicle car = new PhysicsVehicle(vehicleShape, mass);
    
    car.setSuspensionCompression(compValue * 0.1f * FastMath.sqrt(stiffness));
    car.setSuspensionDamping(dampValue * 3f * FastMath.sqrt(stiffness));
    car.setSuspensionStiffness(stiffness);
    car.setMaxSuspensionForce(10000);
       
    setupWheel(car, visual, "fl", true);
    setupWheel(car, visual, "fr", true);
    setupWheel(car, visual, "rl", false).setFrictionSlip(6f);
    setupWheel(car, visual, "rr", false).setFrictionSlip(6f);
    
    return car;
}

private VehicleWheel setupWheel(PhysicsVehicle car, Spatial carModel, String suffix, boolean isFrontWheel) {
    Spatial wheel = findSpatial(carModel, "wheel_" + suffix);
            
    BoundingBox box = ((BoundingBox)wheel.getWorldBound());
    Vector3f wheelPosition = box.getCenter();
    float radius = box.getYExtent();
    float maxSuspensionTravel = 10f;
    float restLength = maxSuspensionTravel / 50.0f;
    
    List<Spatial> list = ((Node)wheel).getChildren();
    for (int i = 0; i < list.size(); i++) {
        list.get(i).setLocalTranslation(wheelPosition.negate());
    }
    
    VehicleWheel vw = car.addWheel(wheel, wheelPosition.add(0, restLength, 0),
            WHEEL_DIRECTION, WHEEL_AXLE, restLength, radius, isFrontWheel);
    vw.setMaxSuspensionTravelCm(maxSuspensionTravel);
    
    return vw;
}

This is how they are added to the physics space by the physics system:

private void addControls(Set<Entity> entities) {
    for (Entity e : entities) {
        PhysicsCollisionObject c = createControl(e);
        physicsObjects.put(e.getId(), c);
        
        if (c instanceof PhysicsVehicle) {
            movingObjects.put(e.getId(), c);
            ((PhysicsVehicle) c).createVehicle(space);
        }

        space.addCollisionObject(c);
    }
}

private PhysicsCollisionObject createControl(Entity e) {
    return controlFactory.create(visualAppState.getVisual(e), e.get(Physics.class));
}

And this is how the new position of the vehicle is determined:

@Override
public void update(float tpf) {
    if (controlTarget.applyChanges()) {
        controlTarget.stream().findFirst().ifPresent(e -> {
            PhysicsVehicle c = (PhysicsVehicle) physicsObjects.get(e.getId());
            ControlTarget t = e.get(ControlTarget.class);
            if (c != null && t != null) {
                c.accelerate(t.getAcceleration());
                c.brake(t.getBraking());
                c.steer(t.getSteeringPosition());
            }
        });
    }
    
    super.update(tpf);
    
    movingObjects.forEach((key, obj) -> {
        Entity e = entities.getEntity(key);
        if (obj instanceof PhysicsVehicle) {
            PhysicsVehicle v = (PhysicsVehicle)obj;
            RigidBodyMotionState state = v.getMotionState();
            Position currentPosition = e.get(Position.class);
            
            Node n = new Node();                
            n.setLocalRotation(currentPosition.getRotation());
            n.setLocalTranslation(currentPosition.getLocation());
            if (state.applyTransform(n)) {
                e.set(new Position(n.getLocalTranslation(), n.getLocalRotation()));
            }                
        }           
    });
    
    if (entities.applyChanges()) {
        removeControls(entities.getRemovedEntities());
        addControls(entities.getAddedEntities());
    }
}

Maybe the trick of calling the RigidBodyMotionState.applyTransform with a newly instanciated Node does not work. Although the coordinates I get back from Bullet seem to be quite sensible. It’s more like collision detection is no longer working. Must be missing something. Haven’t guessed what though.

I’m not sure I’m qualified to help but to those who are it will probably be important for them to see where you’ve added the ‘thing you are trying to collide with’, too.

The thing I’m trying to collide with is defined in the initialize method of my game app state:

@Override
public void initialize(AppStateManager stateManager, Application app) {

    this.app = (SimpleApplication) app;
    this.app.setPauseOnLostFocus(true);
    this.app.setDisplayStatView(true);

    this.ed = this.app.getStateManager().getState(EntityDataState.class).getEntityData();

    EntityId track = ed.createEntity();
    this.ed.setComponents(track,
            new Position(new Vector3f(0, 0, 0)),
            new Model(Model.TRACK),
            new Physics(PhysicsControlType.RigidBody));
    
    EntityId car = ed.createEntity();
    this.ed.setComponents(car,
            new Position(new Vector3f(0, 0, 0)),
            new Model(Model.COUPEGREEN),
            new CameraTarget(20f),
            new ControlTarget(0f, 0f, 0f),
            new Physics(PhysicsControlType.Vehicle));
}

In the update of the PhysicsAppState, the addControls method is called which creates the physics objects, including the track:

private void addControls(Set<Entity> entities) {
    for (Entity e : entities) {
        PhysicsCollisionObject c = createControl(e);
        physicsObjects.put(e.getId(), c);
    
        if (c instanceof PhysicsVehicle) {
            movingObjects.put(e.getId(), c);
            ((PhysicsVehicle) c).createVehicle(space);
        }

        space.addCollisionObject(c);
    }
}

The createControl mthoed calls the PhysicsObjectFactory:

private PhysicsCollisionObject createControl(Entity e) {
    return controlFactory.create(visualAppState.getVisual(e), e.get(Physics.class));
}

The physicsobject factory creates the track as follows:

private PhysicsCollisionObject createRigidBody(Spatial visual) {
    CollisionShape shape = CollisionShapeFactory.createMeshShape(visual);        
    return new PhysicsRigidBody(shape);
}

When I was using controls, the code was mostly the same, except instead of a PhysicsRigidBody I created a RigidBodyControl, and instead of PhysicsVehicle I created VehicleControl.

The controls do some additional physics related magic. For example, the VehicleControl calls PhysicsVehicle.createVehicle for you. If you create the PhysicsVehicle yourself, you need to call createVehicle manually.

I get the idea concerns are not always separated that well in the physics code. For example, PhysicsSpace and RigidBodyMotionState both have detailed knowledge about PhysicsVehicles. And PhysicsSpace also knows PhysicsControls and treats them in a special way.

Yeah, it seems that way to me, too.