package de.fau.cs.mad.fly.game; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.physics.bullet.DebugDrawer; import com.badlogic.gdx.physics.bullet.collision.*; import com.badlogic.gdx.physics.bullet.dynamics.btConstraintSolver; import com.badlogic.gdx.physics.bullet.dynamics.btDiscreteDynamicsWorld; import com.badlogic.gdx.physics.bullet.dynamics.btDynamicsWorld; import com.badlogic.gdx.physics.bullet.dynamics.btRigidBody; import com.badlogic.gdx.physics.bullet.dynamics.btSequentialImpulseConstraintSolver; import com.badlogic.gdx.utils.Disposable; import de.fau.cs.mad.fly.features.ICollisionListener; import de.fau.cs.mad.fly.player.Spaceship; import java.util.ArrayList; /** * The manager for the whole collision detection and handling stuff. * * @author Tobi */ public class CollisionDetector implements Disposable { /** * The collision shape manager used to store the shape for the objects. */ CollisionShapeManager shapeManager; /** * The rigid body info manager used to store the rigid body construction * info for the objects. */ RigidBodyInfoManager rigidBodyInfoManager; /** * Singleton collision detector instance. */ private static CollisionDetector instance; /** * Creates the collision detector if there is not already an instance * created. */ public static void createCollisionDetector() { if (instance == null) { instance = new CollisionDetector(); } } /** * Getter for the collision detector singleton. * * @return instance */ public static CollisionDetector getInstance() { return instance; } /** * The main listener for the collision detection. * <p> * It transmits the collision events to the added collision listeners. * * @author Tobi */ class CollisionContactListener extends ContactListener { private ArrayList<ICollisionListener> listeners; public CollisionContactListener() { listeners = new ArrayList<ICollisionListener>(); } /** * Adds a new collision listener. * * @param listener * Collision listener which has to implement the * ICollisionListener interface. */ public void addListener(ICollisionListener listener) { listeners.add(listener); } /** * Removes a listener from the list. Can be used to save performance if * the interface does not need to get informed for collisions anymore. * * @param listener * Collision listener which has to implement the * ICollisionListener interface. */ public void removeListener(ICollisionListener listener) { listeners.remove(listener); } @Override public void onContactStarted(btCollisionObject o1, btCollisionObject o2) { GameObject g1 = (GameObject) o1.userData; GameObject g2 = (GameObject) o2.userData; if (g2 instanceof Spaceship) { GameObject spaceship = g2; g2 = g1; g1 = spaceship; } Gdx.app.log("CollisionDetector.onContactStarted", "g1 = " + g1.getId() + " (userData = " + g1.getUserData().getClass() + "), g2 = " + g2.getId() + " (userData = " + g2.getUserData().getClass() + ")"); for (ICollisionListener listener : listeners) { listener.onCollision(g1, g2); } } } /** * The flags to filter collisions. */ public final static short DUMMY_FLAG = 1 << 7; public final static short OBJECT_FLAG = 1 << 8; public final static short PLAYER_FLAG = 1 << 9; public final static short ALL_FLAG = -1; btCollisionConfiguration collisionConfig; btDispatcher dispatcher; CollisionContactListener contactListener; btBroadphaseInterface broadphase; btDynamicsWorld dynamicsWorld; btConstraintSolver constraintSolver; DebugDrawer debugDrawer; protected CollisionDetector() { shapeManager = new CollisionShapeManager(); rigidBodyInfoManager = new RigidBodyInfoManager(); collisionConfig = new btDefaultCollisionConfiguration(); dispatcher = new btCollisionDispatcher(collisionConfig); broadphase = new btDbvtBroadphase(); constraintSolver = new btSequentialImpulseConstraintSolver(); dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher, broadphase, constraintSolver, collisionConfig); dynamicsWorld.setGravity(new Vector3(0.0f, 0.0f, 0.0f)); contactListener = new CollisionContactListener(); } /** * Getter for the collision shape manager. * * @return CollisionShapeManager */ public CollisionShapeManager getShapeManager() { return shapeManager; } /** * Getter for the rigid body info manager. * * @return RigidBodyInfoManager */ public RigidBodyInfoManager getRigidBodyInfoManager() { return rigidBodyInfoManager; } /** * Creates a new rigid body. * * @param instance * The game object for which the rigid body has to be created. * @param shape * The collision shape. Should be created with the help of the * collision shape manager. * @param userData * The user data to store in the rigid body. * @param rigidBodyInfo * The rigid body construction info. Should be created with the * help of the rigid body info manager. * @return btRigidBody */ public static btRigidBody createRigidBody(final GameObject instance, final btCollisionShape shape, final GameObject userData, btRigidBody.btRigidBodyConstructionInfo rigidBodyInfo) { btRigidBody rigidBody = new btRigidBody(rigidBodyInfo); rigidBody.setCollisionShape(shape); rigidBody.setCollisionFlags(rigidBody.getCollisionFlags() | btRigidBody.CollisionFlags.CF_CUSTOM_MATERIAL_CALLBACK); rigidBody.setWorldTransform(instance.transform); rigidBody.userData = userData; // TODO: use contact callback filtering // rigidBody.setContactCallbackFlag(GROUND_FLAG); // rigidBody.setContactCallbackFilter(0); return rigidBody; } /** * Adds a rigid body to the dynamics world from a given game object. * <p> * It also sets the filter group and mask. * * @param gameObject * The game object with the rigid body to add. */ public void addRigidBody(final GameObject gameObject) { dynamicsWorld.addRigidBody(gameObject.getRigidBody(), gameObject.getFilterGroup(), gameObject.getFilterMask()); } /** * Removes a rigid body from the dynamics world from a given game object. * * @param gameObject * The game object with the rigid body to remove. */ public void removeRigidBody(final GameObject gameObject) { dynamicsWorld.removeRigidBody(gameObject.getRigidBody()); } /** * Getter for the collision contact listener * * @return CollisionContactListener */ public CollisionContactListener getCollisionContactListener() { return contactListener; } /** * Performs the collision detection and handling. * * @param delta * Time after the last call. */ public void perform(float delta) { // TODO: check if the values are okay dynamicsWorld.stepSimulation(delta, 5, 1f / 60f); } @Override public void dispose() { dynamicsWorld.dispose(); broadphase.dispose(); dispatcher.dispose(); collisionConfig.dispose(); contactListener.dispose(); constraintSolver.dispose(); rigidBodyInfoManager.dispose(); shapeManager.dispose(); Gdx.app.log("CollisionDetector", "Collision disposed."); instance = null; } }