package toritools.physics; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import org.jbox2d.callbacks.ContactImpulse; import org.jbox2d.callbacks.ContactListener; import org.jbox2d.collision.Manifold; import org.jbox2d.collision.shapes.CircleShape; import org.jbox2d.collision.shapes.PolygonShape; import org.jbox2d.collision.shapes.Shape; import org.jbox2d.common.Transform; import org.jbox2d.common.Vec2; import org.jbox2d.dynamics.Body; import org.jbox2d.dynamics.BodyDef; import org.jbox2d.dynamics.BodyType; import org.jbox2d.dynamics.FixtureDef; import org.jbox2d.dynamics.World; import org.jbox2d.dynamics.contacts.Contact; import org.jbox2d.dynamics.contacts.ContactEdge; import org.jbox2d.dynamics.joints.Joint; import org.jbox2d.dynamics.joints.RevoluteJointDef; import org.jbox2d.dynamics.joints.WeldJointDef; import toritools.entity.Entity; import toritools.entity.Level; import toritools.math.Vector2; import toritools.scripting.EntityScript; import toritools.scripting.EntityScript.EntityScriptAdapter; /** * A wrapper for Box2d's world. * * @author toriscope * */ public class Universe { final private World world; final private HashMap<Entity, Body> bodyMap; final private List<ContactListener> contactListeners; final public float PTM_RATIO = 32; public Universe(final Vector2 gravity) { world = new World(new Vec2(gravity.x, gravity.y), true); bodyMap = new HashMap<Entity, Body>(); contactListeners = new LinkedList<ContactListener>(); world.setContactListener(new ContactListener() { @Override public void beginContact(Contact arg0) { for (ContactListener c : contactListeners) c.beginContact(arg0); } @Override public void endContact(Contact arg0) { for (ContactListener c : contactListeners) c.endContact(arg0); } @Override public void postSolve(Contact arg0, ContactImpulse arg1) { for (ContactListener c : contactListeners) c.postSolve(arg0, arg1); } @Override public void preSolve(Contact arg0, Manifold arg1) { for (ContactListener c : contactListeners) c.preSolve(arg0, arg1); } }); } /** * Add a contact listener to the physics universe. * * @param listener */ public void addContactListener(final ContactListener listener) { contactListeners.add(listener); } public void step(final float totaldt, int numSteps) { /* * Step the world */ float tinyStep = totaldt / numSteps; for (float i = 0; i < totaldt; i += tinyStep) world.step(tinyStep, 20, 20); } public Body addEntity(final Entity ent, final BodyType bodyType, final boolean allowRotation, final boolean spherical, final float density, final float friction, final Vector2[] points, final boolean isSensor) { BodyDef bd = new BodyDef(); bd.fixedRotation = !allowRotation; bd.type = bodyType; Shape p = null; if (points == null) { bd.position.set(ent.getPos().x / PTM_RATIO + ent.getDim().x / 2 / PTM_RATIO, ent.getPos().y / PTM_RATIO + ent.getDim().y / 2 / PTM_RATIO); if (!spherical) { p = new PolygonShape(); ((PolygonShape) p).setAsBox(ent.getDim().x / PTM_RATIO / 2, ent.getDim().y / PTM_RATIO / 2); } else { p = new CircleShape(); p.m_radius = ent.getDim().x / PTM_RATIO / 2; } } else { bd.position.set(ent.getPos().add(ent.getDim().scale(.5f)).scale(1f / PTM_RATIO).toVec()); PolygonShape ss = new PolygonShape(); ss.setAsEdge(points[0].scale(1f / PTM_RATIO).toVec(), points[1].scale(1f / PTM_RATIO).toVec()); p = ss; } // Create a fixture for ball FixtureDef fd = new FixtureDef(); fd.shape = p; fd.density = density; fd.friction = friction; fd.restitution = .3f; // fd.filter.categoryBits = cat; fd.userData = ent; fd.isSensor = isSensor; fd.filter.categoryBits = 1; Body body = world.createBody(bd); body.createFixture(fd); body.m_userData = ent; bodyMap.put(ent, body); if (bodyType == BodyType.DYNAMIC) ent.addScript(serviceScript); return body; } /** * Add an entity to the simulation. The entity will be given an additional * script that keeps it synced with its simulation representation.. * * @param ent * the entity to add. * @param bodyType * the Body type * @param allowRotation * true if rotation should be allowed. * @param density * default is 1, adjust accordingly. */ public Body addEntity(final Entity ent, final BodyType bodyType, final boolean allowRotation, final boolean spherical, final float density, final float friction) { return addEntity(ent, bodyType, allowRotation, spherical, density, friction, null, false); } /** * Construct a hinge. Be sure the shapes are overlapping at the given point. * * @param a * @param b * @param hingePosition */ public Joint addHinge(Entity a, Entity b, final Vector2 hingePosition) { RevoluteJointDef def = new RevoluteJointDef(); def.initialize(bodyMap.get(a), bodyMap.get(b), hingePosition.scale(1f / PTM_RATIO).toVec()); return world.createJoint(def); } public Joint addWeld(Entity a, Entity b) { WeldJointDef def = new WeldJointDef(); def.initialize(bodyMap.get(a), bodyMap.get(b), bodyMap.get(a).getWorldCenter()); return world.createJoint(def); } /** * This script keeps the entity model synced with the physics model. */ private final EntityScript serviceScript = new EntityScriptAdapter() { @Override public void onDeath(Entity self, Level level, boolean isRoomExit) { if (!isRoomExit) { removeEntity(self); } } @Override public void onUpdate(Entity self, float time, Level level) { Transform body = bodyMap.get(self).getTransform(); Vector2 newPos = new Vector2((body.position.x * PTM_RATIO) - self.getDim().x / 2, body.position.y * PTM_RATIO - self.getDim().y / 2); // System.out.println(newPos + " | " + self.getPos()); self.setPos(newPos); self.setDirection((int) (body.getAngle() * 57.3)); } }; /** * Remove an entity, and corresponding body from the map and the simulation. * * @param ent */ public void removeEntity(Entity ent) { if (bodyMap.containsKey(ent)) { world.destroyBody(bodyMap.get(ent)); bodyMap.remove(ent); } } public void applyForce(final Entity e, final Vector2 force) { Body b = bodyMap.get(e); if (b != null) b.applyForce(force.scale(1f / b.m_mass).toVec(), b.getWorldCenter()); } public void applyLinearImpulse(final Entity e, final Vector2 force) { Body b = bodyMap.get(e); if (b != null) b.applyLinearImpulse(force.scale(1f / b.m_mass).toVec(), b.getWorldCenter()); } public void setVelocity(Entity dragging, Vector2 scale) { Body b = bodyMap.get(dragging); if (b != null) b.setLinearVelocity(scale.toVec()); } public boolean isCollidingWithType(final Entity e, final String type) { ContactEdge edge = bodyMap.get(e).getContactList(); while (edge != null) { if (((Entity) edge.other.m_userData).getType().equals(type)) return true; edge = edge.next; } return false; } public void setAngularDamping(final Entity e, final int damping) { bodyMap.get(e).setAngularDamping(.5f); } public void setRotationDeg(final Entity e, final float rot) { Body b = bodyMap.get(e); b.setTransform(b.getWorldCenter(), rot / 57.3f); } public void destroyJoint(final Joint joint) { world.destroyJoint(joint); } public void setTransform(final Entity e, final Vector2 pos, final float angleDeg) { bodyMap.get(e).setTransform(pos.scale(1f / PTM_RATIO).toVec(), angleDeg / 57.3f); } }