Here is how I am doing physic in server side :
Pretty mush similar to SimplePhysic system in sim-eth-es example.
Here I am having one PhysicSpace, you can have multiple physic spaces. (They can be run multi-threaded)
/**
* Bullet physics system.
*
* @author Ali-RS <ali_codmw@yahoo.com>
*/
public class BulletPhysicsState extends AbstractGameSystem implements PhysicsSystem<PhysicsControlDriver> {
static Logger log = LoggerFactory.getLogger(BulletPhysicsState.class);
private PhysicsSpace physicSpace;
private PhysicsDriverFactory driverFactory;
private EntityData ed;
private BulletPhysicsState.MobPhysicsBodyContainer mobBodies;
private BulletPhysicsState.StaticPhysicsBodyContainer staticBodies;
// Single threaded.... we'll have to take care when adding/removing
// items.
//private SafeArrayList<Body> mobBodies = new SafeArrayList<>(Body.class);
private Map<EntityId, Body> index = new ConcurrentHashMap<>();
private Map<EntityId, PhysicsControlDriver> driverIndex = new ConcurrentHashMap<>();
// Still need these to manage physics listener notifications in a
// thread-consistent way
private ConcurrentLinkedQueue<Body> toAdd = new ConcurrentLinkedQueue<>();
private ConcurrentLinkedQueue<Body> toRemove = new ConcurrentLinkedQueue<>();
private SafeArrayList<PhysicsListener> listeners = new SafeArrayList<>(PhysicsListener.class);
public BulletPhysicsState() {
}
protected void initialize() {
log.info("initialize()");
//The core of Bullet physic simulation.
physicSpace = new PhysicsSpace();
//Get PhysicsDriverFactory in case there is one registered
//so it will auto create physic driver when new entity comes.
driverFactory = getSystem(PhysicsDriverFactory.class);
if(driverFactory != null){
log.info("Found PhysicsDriverFactory.");
}
this.ed = getSystem(EntityData.class);
if (ed == null) {
throw new RuntimeException("Physics system requires an EntityData object.");
}
}
protected void terminate() {
physicSpace.destroy();
}
/**
* Adds a listener that will be notified about physics related updates. This
* is not a thread safe method call so must be called during setup or from
* the physics/simulation thread.
*/
@Override
public void addPhysicsListener(PhysicsListener l) {
listeners.add(l);
}
@Override
public void removePhysicsListener(PhysicsListener l) {
listeners.remove(l);
}
@Override
public Body getBody(EntityId entityId) {
return index.get(entityId);
}
@Override
public void setControlDriver(EntityId entityId, PhysicsControlDriver driver) {
synchronized (this) {
driverIndex.put(entityId, driver);
Body current = getBody(entityId);
if (current != null) {
current.driver = driver;
}
}
}
@Override
public PhysicsControlDriver getControlDriver(EntityId entityId) {
return driverIndex.get(entityId);
}
private void fireBodyListListeners() {
if (!toAdd.isEmpty()) {
Body body = null;
while ((body = toAdd.poll()) != null) {
//bodies.add(body);
for (PhysicsListener l : listeners.getArray()) {
l.addBody(body);
}
}
}
if (!toRemove.isEmpty()) {
Body body = null;
while ((body = toRemove.poll()) != null) {
//bodies.remove(body);
for (PhysicsListener l : listeners.getArray()) {
l.removeBody(body);
}
}
}
}
@Override
public void start() {
physicSpace.setGravity(new Vector3f(0, -5f, 0));
mobBodies = new BulletPhysicsState.MobPhysicsBodyContainer(ed);
mobBodies.start();
staticBodies = new BulletPhysicsState.StaticPhysicsBodyContainer(ed);
staticBodies.start();
}
@Override
public void stop() {
mobBodies.stop();
mobBodies = null;
staticBodies.stop();
staticBodies = null;
}
@Override
public void update(SimTime time) {
physicSpace.distributeEvents();
physicSpace.update((float) time.getTpf());
for (PhysicsListener l : listeners.getArray()) {
l.beginFrame(time);
}
// Update the entity list
mobBodies.update();
staticBodies.update();
// Fire off any pending add/remove events
fireBodyListListeners();
double tpf = time.getTpf();
// Apply control driver changes
for (Body b : mobBodies.getArray()) {
if (b.driver != null) {
b.driver.update(tpf, b);
}
}
// Integrate
for( Body b : mobBodies.getArray() ) {
b.integrate(tpf);
}
// Publish the results
for (PhysicsListener l : listeners.getArray()) {
for (Body b : mobBodies.getArray()) {
l.updateBody(b);
}
}
for (PhysicsListener l : listeners.getArray()) {
l.endFrame(time);
}
}
protected Body createMobBody(EntityId entityId, double invMass, double radius, boolean create) {
Body result = index.get(entityId);
if (result == null && create) {
synchronized (this) {
result = index.get(entityId);
if (result != null) {
return result;
}
result = new Body(entityId);
result.radius = radius;
result.invMass = invMass;
// Hookup the driver if it has one waiting
PhysicsControlDriver driver = driverIndex.get(entityId);
//else ask driver factory(if there is one already registered) to create it
if(driver == null && driverFactory != null){
driver = driverFactory.createDriver(ed, entityId);
setControlDriver(entityId, driver);
}
result.driver = driver;
// Set it up to be managed by physics
toAdd.add(result);
index.put(entityId, result);
}
}
return result;
}
protected boolean removeMobBody(EntityId entityId) {
Body result = index.remove(entityId);
if (result != null) {
toRemove.add(result);
PhysicsControlDriver driver = ((PhysicsControlDriver) result.driver);
driver.removeFromPhysicSpace(physicSpace);
driverIndex.remove(entityId, driver);
return true;
}
return false;
}
protected Body createStaticBody(EntityId entityId, double invMass, boolean create) {
Body result = index.get(entityId);
if (result == null && create) {
synchronized (this) {
result = index.get(entityId);
if (result != null) {
return result;
}
result = new Body(entityId);
result.invMass = invMass;
// Hookup the driver if it has one waiting
PhysicsControlDriver driver = driverIndex.get(entityId);
//else ask driver factory(if there is one already registered) to create it
if(driver == null && driverFactory != null){
driver = driverFactory.createDriver(ed, entityId);
setControlDriver(entityId, driver);
}
result.driver = driver;
index.put(entityId, result);
}
}
return result;
}
protected boolean removeStaticBody(EntityId entityId) {
Body result = index.remove(entityId);
if (result != null) {
PhysicsControlDriver driver = ((PhysicsControlDriver) result.driver);
driver.removeFromPhysicSpace(physicSpace);
driverIndex.remove(entityId, driver);
return true;
}
return false;
}
/**
* Maps the appropriate entities to physics mob bodies.
*/
private class MobPhysicsBodyContainer extends EntityContainer<Body> {
public MobPhysicsBodyContainer(EntityData ed) {
super(ed, OrFilter.create(PhysicsType.class,
Filters.fieldEquals(PhysicsType.class, "type", PhysicsType.DYNAMIC),
Filters.fieldEquals(PhysicsType.class, "type", PhysicsType.KINEMATIC)),
SphereShape.class,
PhysicsDriverInfo.class,
Position.class,
MassProperties.class,
PhysicsType.class
);
}
@Override
protected Body[] getArray() {
return super.getArray();
}
@Override
protected Body addObject(Entity e) {
log.info("Add Mob physic body");
MassProperties mass = e.get(MassProperties.class);
SphereShape shape = e.get(SphereShape.class);
// Right now only works for CoG-centered shapes
Body newBody = createMobBody(e.getId(), mass.getInverseMass(), shape.getRadius(), true);
Position pos = e.get(Position.class);
newBody.setPosition(pos.getLocation());
newBody.setRotation(pos.getFacing());
if(newBody.driver != null){
PhysicsControlDriver driver = ((PhysicsControlDriver) newBody.driver);
driver.updatePhysicalProperties(ed, e.getId());
driver.addToPhysicSpace(physicSpace);
}
return newBody;
}
@Override
protected void updateObject(Body object, Entity e) {
// We don't support live-updating mass or shape right now
}
@Override
protected void removeObject(Body object, Entity e) {
removeMobBody(e.getId());
}
}
/**
* Maps the appropriate entities to physics static bodies.
*/
private class StaticPhysicsBodyContainer extends EntityContainer<Body> {
public StaticPhysicsBodyContainer(EntityData ed) {
super(ed,
Filters.fieldEquals(PhysicsType.class, "type", PhysicsType.STATIC),
PhysicsDriverInfo.class,
Position.class,
MassProperties.class,
PhysicsType.class
);
}
@Override
protected Body[] getArray() {
return super.getArray();
}
@Override
protected Body addObject(Entity e) {
log.info("Add static physic body");
MassProperties mass = e.get(MassProperties.class);
//SphereShape shape = e.get(SphereShape.class);
// Right now only works for CoG-centered shapes
Body newBody = createStaticBody(e.getId(), mass.getInverseMass(), true);
Position pos = e.get(Position.class);
newBody.setPosition(pos.getLocation());
newBody.setRotation(pos.getFacing());
if(newBody.driver != null){
PhysicsControlDriver driver = ((PhysicsControlDriver) newBody.driver);
driver.updatePhysicalProperties(ed, e.getId());
driver.addToPhysicSpace(physicSpace);
}
return newBody;
}
@Override
protected void updateObject(Body object, Entity e) {
// We do support live-updating mass or shape .
((PhysicsControlDriver) object.driver).updatePhysicalProperties(ed, e.getId());
}
@Override
protected void removeObject(Body object, Entity e) {
removeStaticBody(e.getId());
}
}
}
I added a PhysicsControlDriver over ControlDriver in sim-eth-es to support physic controls.
package com.overthemoon.core.physics.bullet;
import com.jme3.bullet.PhysicsSpace;
import com.overthemoon.core.api.physics.ControlDriver;
import com.simsilica.es.EntityData;
import com.simsilica.es.EntityId;
/**
*
* @author Ali-RS <ali_codmw@yahoo.com>
*/
public interface PhysicsControlDriver extends ControlDriver {
/**
* Add your physic objects to physicSpace provided by this method.
* @param physicsSpace
*/
public void addToPhysicSpace(PhysicsSpace physicsSpace);
/**
* Remove physic objects from physicSpace.
* @param physicsSpace
*/
public void removeFromPhysicSpace(PhysicsSpace physicsSpace);
/**
* Update physics state with your physic components.
* Note, this method is supposed to work with EntitySystem
* structure using Zay-ES.
* @param entity
*/
public void updatePhysicalProperties (EntityData ed, EntityId entityId);
}
and as an example here is BetterCharacterDriver
/**
* Basic driver for controlling characters
*
* @author Ali-RS <ali_codmw@yahoo.com>
*/
public class BetterCharacterDriver implements PhysicsControlDriver {
private PhysicsCharacterControl control; // extends BetterCharacterControl
private volatile Vector3f walkDirection = new Vector3f();
private volatile Vector3f viewDirection = new Vector3f();
private Quaternion tempRot = new Quaternion();
private Vector3f tempPos = new Vector3f();
public BetterCharacterDriver(PhysicsCharacterControl control) {
this.control = control;
}
@Override
public void addToPhysicSpace(PhysicsSpace physicsSpace) {
if (control != null && physicsSpace != null) {
control.setPhysicsSpace(physicsSpace);
control.setEnabled(true);
} else {
throw new NullPointerException(" BetterCharacterControl not instantiated !");
}
}
@Override
public void removeFromPhysicSpace(PhysicsSpace physicsSpace) {
if (control != null && physicsSpace != null) {
physicsSpace.remove(control);
control.setEnabled(false);
}
}
@Override
public void update(double tpf, Body body) {
// Grab local versions of the player settings in case another
// thread sets them while we are calculating.
Vector3f walkDir = walkDirection;
Vector3f viewDir = viewDirection;
control.setViewDirection(viewDir);
control.setWalkDirection(walkDir);
// Setup the current world rotation of the body
tempRot.lookAt(control.getViewDirection(), Vector3f.UNIT_Y);
body.orientation.set(tempRot.getX(), tempRot.getY(), tempRot.getZ(), tempRot.getW());
// Apply the accelerated velocity oriented into world space
tempPos = control.getPhysicsRigidBody().getPhysicsLocation();
body.pos.set(tempPos.x, tempPos.y, tempPos.z);
Vector3f tempVel = control.getVelocity();
body.velocity.set(tempVel.x, tempVel.y, tempVel.z);
}
@Override
public void updatePhysicalProperties(EntityData ed, EntityId entityId) {
Position pos = ed.getComponent(entityId, Position.class);
control.warp(pos.getLocation().toVector3f());
control.getPhysicsRigidBody().setDamping(0.2f, 0.2f);
control.getPhysicsRigidBody().setLinearSleepingThreshold(0.1f);
}
public void applyMovementState(Vector3f walkDirection, Vector3f viewDirection) {
this.walkDirection = walkDirection;
this.viewDirection = viewDirection;
}
public PhysicsCharacterControl getControl() {
return control;
}
}
I share the driver with SteerSystem to apply steer force.
One problem I have is I do not know how should i integrate collision shapes which are controlled by animation bones, to server side.