//////////////////////////////////////////////////////////////////////////////////////// // Kara Jensen - mail@karajensen.com - PhysicsEngine.h //////////////////////////////////////////////////////////////////////////////////////// #pragma once #include <vector> #include <memory> #include "glm/glm.hpp" #include "bullet/include/btBulletCollisionCommon.h" #include "bullet/include/btBulletDynamicsCommon.h" struct RigidBody; struct CollisionEvent; /** * Holds the Bullet Physics world for Tiny Toon Tanks */ class PhysicsEngine { public: /** * Constructor */ PhysicsEngine(); /** * Destructor */ ~PhysicsEngine(); /** * Tick the simulation * @Param timestep How much to proceed the physics per iteration */ void Tick(float timestep); /** * Generates a collision event * @param collisionIndex The index from currently occuring collisions * @param collision The collision event to be filled in * @return whether generation was successful */ bool GenerateCollisionEvent(int collisionIndex, CollisionEvent& collision); /** * @return the number of collisions currently occuring */ int GetCollisionAmount() const; /** * Get the friction for the rigid body * @param rigidBody The index for the rigid body * @return the friction of the rigid body */ float GetFriction(int rigidBodyID) const; /** * Set the collision group for the rigid body * @param rigidBody The index for the rigid body * @param group The collision group the rigid body belongs to */ void SetGroup(int rigidBodyID, int group); /** * Gets the collision group for the rigid body * @param rigidBody The index for the rigid body * @return The collision group the rigid body belongs to */ int GetGroup(int rigidBodyID) const; /** * Get the current velocity of the rigid body * @param rigidBody The index for the rigid body * @return The velocity of the rigid body */ glm::vec3 GetVelocity(int rigidBodyID) const; /** * Get the current transform of a rigid body * @param rigidBody The index for the rigid body * @return the transform of the right body */ glm::mat4 GetTransform(int rigidBodyID) const; /** * Reset the simulation world */ void ResetSimulation(); /** * Explicitly sets the transform for a rigid body * @param rigidBody The rigid body to set * @param matrix The transform to set */ void SetMotionState(int rigidBodyID, const glm::mat4& matrix); /** * Explicitly sets the position for a rigid body * @param rigidBody The rigid body to set * @param position The position to set */ void SetPosition(int rigidBodyID, const glm::vec3& position); /** * Explicitly sets the basis for a rigid body * @param rigidBody The rigid body to set * @param mat The basis transform to set */ void SetBasis(int rigidBodyID, const glm::mat4& matrix); /** * Sets the friction for a rigid body * @param rigidbody The rigid body to to set * @param amount The amount of friction to set */ void SetFriction(int rigidBodyID, float amount); /** * Sets the gravity for a rigid body * @param rigidbody The rigid body to to set * @param gravity The amount of gravity to set */ void SetGravity(int rigidBodyID, float gravity); /** * Sets the velocity of a rigid body * @param velocity The velocity to set * @param rigidbody The index for the rigid body to set * @param linearDamping The damping for linear velocity * @param angularDamping The damping for angular velocity */ void SetVelocity(const glm::vec3& velocity, int rigidBodyID, float linearDamping = 0, float angularDamping = 0); /** * Set the mass for the rigid body * @param rigidbody The index for the rigid body to set * @param mass The mass of the rigid body */ void SetMass(int rigidBodyID, float mass); /** * Sets the linear and angular damping of a rigid body * @param rigidbody The index for the rigid body to set * @param linearDamping The damping for linear velocity * @param angularDamping The damping for angular velocity */ void SetInternalDamping(int rigidBodyID, float linearDamping, float angularDamping); /** * Sets whether the rigid body exists in the simulation world or not * @param rigidBody The index for the rigid body to set * @param enable Whether or not to enable the rigid body */ void AddToWorld(int rigidBodyID, bool enable); /** * Resets all velocity and forces for a rigid body * @param rigidBody The index of the rigid body to reset */ void ResetVelocityAndForce(int rigidBodyID); /** * Adds a force to a particular rigid body * @param force The force to add * @param position The position to apply the force at * @param rigidbody The index for the rigid body */ void AddForce(const glm::vec3& force, const glm::vec3& position, int rigidBodyID); /** * Adds an impulse to a particular rigid body * @param force The impulse to add * @param position The position to apply the impulse at * @param rigidbody The index for the rigid body */ void AddImpulse(const glm::vec3& force, const glm::vec3& position, int rigidBodyID); /** * Adds linear damping to the current amount of a rigid body * @param rigidBody The index for the rigid body to set * @param amount The damping for linear velocity */ void AddLinearDamping(int rigidBodyID, float amount); /** * Adds angular damping to the current amount of a rigid body * @param rigidBody The index for the rigid body to set * @param amount The damping for angular velocity */ void AddRotationalDamping(int rigidBodyID, float amount); /** * @param hinge The index for the hinge * @return The current rotation for the hinge in radians */ float GetHingeRotation(int hinge); /** * @param hinge The index of the hinge * @return whether its enabled */ bool HingeEnabled(int hinge); /** * Provide rotation to the hinge constraint * @param hinge The index for the hinge * @param amount The amount of rotation to apply * @param dt Deltatime */ void RotateHinge(int hinge, float amount, float dt); /** * Stop a hinge from rotating * @param hinge The index for the hinge * @param dt Deltatime * @param damping The amount of damping to apply to the stopping motion */ void StopHinge(int hinge, float dt, float damping); /** * Creates a hinge constraint between two rigid bodies * @param rigidBody1 The first rigid body for the hinge * @param rigidBody2 The second rigid body for the hinge * @param pos1local Position local to rigidBody1 to put the constraint * @param pos2local Position local to rigidBody2 to put the constraint * @param axis1 The axis for the first rigid body * @param axis2 The axis for the second rigid body * @param breakthreshold The magnitude of force required for breakage */ int CreateHinge(int rigidBodyID1, int rigidBodyID2, const glm::vec3& pos1local, const glm::vec3& pos2local, const glm::vec3& axis1, const glm::vec3& axis2, float breakthreshold = 0.0f); /** * Load a custom shape from an array of vertices * @param vertices The array of vertex positions * @return the ID of the shape */ int LoadConvexShape(std::vector<glm::vec3> vertices); /** * Create a rigid body * @param mat The initial transform of the body * @param shape The collision shape to use * @param mass The mass of the body * @param group The collision group the body is part of * @param meshID The ID of the graphical mesh for rendering * @param meshInstance The instance to use of the graphical mesh * @param createEvents Whether the body is interested in collision events * @param inertia The inertia of the body * @return the internal index of the rigid body */ int LoadRigidBody(const glm::mat4& matrix, int shape, float mass, int group, int meshID, int meshInstance, bool createEvents, const glm::vec3 inertia = glm::vec3()); private: /** * Collision Masking for rigid bodies */ struct CollisionFilterCallback : public btOverlapFilterCallback { /** * Filters objects on whether they require collision detection * @param proxy0/proxy1 the bodies to filter * @return whether the bodies require collision detection */ virtual bool needBroadphaseCollision(btBroadphaseProxy* proxy0, btBroadphaseProxy* proxy1) const; }; /** * Data for a rigid body */ struct RigidBody { std::unique_ptr<btRigidBody> Body; ///< Bullet rigid body object std::unique_ptr<btDefaultMotionState> State; ///< Motion state for movement interpolation int Mask = NO_MASK; ///< The collision mask of the body int Index = 0; ///< Internal index of the body in the container int Shape = 0; ///< The type of shape of the body int Group = 0; ///< The collision filter group the body belongs to int MeshID = 0; ///< ID of the graphical mesh for rendering int MeshInstance = 0; ///< Associated instance of the graphical mesh bool ProcessEvents = true; ///< Whether or not this body requires collision checking }; /** * Prevent copying */ PhysicsEngine(const PhysicsEngine&) = delete; PhysicsEngine& operator=(const PhysicsEngine&) = delete; float m_sleepvalue = 0.0f; ///< Threshold before a body is asleep int m_iterations = 1; ///< Number of iterations for the world static const int NO_MASK = -1; ///< No collision masking std::vector<std::unique_ptr<btConvexHullShape>> m_shapes; ///< Collision shapes avaliable std::vector<std::unique_ptr<RigidBody>> m_bodies; ///< Rigid bodies that exist in the scene std::vector<std::unique_ptr<btHingeConstraint>> m_hinges; ///< Hinges that exist between rigid bodies std::unique_ptr<btDiscreteDynamicsWorld> m_world; ///< Bullet dynamics world std::unique_ptr<btDefaultCollisionConfiguration> m_collisionConfig; ///< Bullet Collision configuration std::unique_ptr<btCollisionDispatcher> m_dispatcher; ///< Bullet collision dispatcher std::unique_ptr<btBroadphaseInterface> m_overlappingPairCache; ///< Bullet collision cache std::unique_ptr<btSequentialImpulseConstraintSolver> m_solver; ///< Bullet contraint solver std::unique_ptr<btOverlapFilterCallback> m_filterCallback; ///< Custom filter callback }; //////////////////////////////////////////////////////////////////////////////////////// // Kara Jensen - mail@karajensen.com - PhysicsEngine.cpp //////////////////////////////////////////////////////////////////////////////////////// #include "PhysicsEngine.h" #include "bullet/include/linearMath/btTransform.h" #include "CollisionEvent.h" #include "Conversions.h" #include <algorithm> PhysicsEngine::PhysicsEngine() : m_collisionConfig(std::make_unique<btDefaultCollisionConfiguration>()), m_overlappingPairCache(std::make_unique<btDbvtBroadphase>()), m_solver(std::make_unique<btSequentialImpulseConstraintSolver>()), m_filterCallback(std::make_unique<CollisionFilterCallback>()) { m_dispatcher = std::make_unique<btCollisionDispatcher>(m_collisionConfig.get()); m_world = std::make_unique<btDiscreteDynamicsWorld>( m_dispatcher.get(), m_overlappingPairCache.get(), m_solver.get(), m_collisionConfig.get()); m_world->getPairCache()->setOverlapFilterCallback(m_filterCallback.get()); } PhysicsEngine::~PhysicsEngine() { } void PhysicsEngine::ResetSimulation() { for (auto& hinge : m_hinges) { m_world->removeConstraint(hinge.get()); } for (auto& rigidbody : m_bodies) { m_world->removeRigidBody(rigidbody->Body.get()); } m_hinges.clear(); m_bodies.clear(); m_shapes.clear(); m_world->clearForces(); m_world->setGravity(btVector3(0, -9.8f, 0)); } bool PhysicsEngine::CollisionFilterCallback::needBroadphaseCollision(btBroadphaseProxy* proxy0, btBroadphaseProxy* proxy1) const { if(proxy0->m_collisionFilterGroup == proxy1->m_collisionFilterGroup) { //groups: members of a group don't collide with each other return false; } else if(proxy0->m_collisionFilterMask != PhysicsEngine::NO_MASK || proxy1->m_collisionFilterMask != PhysicsEngine::NO_MASK) { //mask: objects with a mask only collide with the corresponding group return proxy0->m_collisionFilterMask == proxy1->m_collisionFilterGroup || proxy1->m_collisionFilterMask == proxy0->m_collisionFilterGroup; } return true; } bool PhysicsEngine::GenerateCollisionEvent(int collisionIndex, CollisionEvent& collision) { btPersistentManifold* contactManifold = m_dispatcher->getManifoldByIndexInternal(collisionIndex); const btCollisionObject* obA = contactManifold->getBody0(); const btCollisionObject* obB = contactManifold->getBody1(); RigidBody* rbA = static_cast<RigidBody*>(obA->getUserPointer()); RigidBody* rbB = static_cast<RigidBody*>(obB->getUserPointer()); //check if any of the bodies don't want events if(rbA->ProcessEvents && rbB->ProcessEvents) { collision.BodyA.MeshID = rbA->MeshID; collision.BodyA.MeshInstance = rbA->MeshInstance; collision.BodyA.RigidBodyID = rbA->Index; collision.BodyB.MeshID = rbB->MeshID; collision.BodyB.MeshInstance = rbB->MeshInstance; collision.BodyB.RigidBodyID = rbB->Index; return true; } return false; } void PhysicsEngine::SetGroup(int rigidBodyID, int group) { m_bodies[rigidBodyID]->Group = group; btBroadphaseProxy* broadphase = m_bodies[rigidBodyID]->Body->getBroadphaseHandle(); if(broadphase != nullptr) { broadphase->m_collisionFilterGroup = group; } } int PhysicsEngine::GetGroup(int rigidBodyID) const { return m_bodies[rigidBodyID]->Group; } void PhysicsEngine::SetMass(int rigidBodyID, float mass) { btVector3 localInertia(0,0,0); const int shapeID = m_bodies[rigidBodyID]->Shape; m_shapes[shapeID]->calculateLocalInertia(mass, localInertia); m_bodies[rigidBodyID]->Body->setMassProps(mass, localInertia); } void PhysicsEngine::AddToWorld(int rigidBodyID, bool enable) { if (enable) { m_world->addRigidBody( m_bodies[rigidBodyID]->Body.get(), m_bodies[rigidBodyID]->Group, m_bodies[rigidBodyID]->Mask); } else { m_world->removeRigidBody(m_bodies[rigidBodyID]->Body.get()); } } int PhysicsEngine::GetCollisionAmount() const { return m_dispatcher->getNumManifolds(); } void PhysicsEngine::AddForce(const glm::vec3& force, const glm::vec3& position, int rigidBodyID) { if(!m_bodies[rigidBodyID]->Body->isActive()) { m_bodies[rigidBodyID]->Body->activate(true); } m_bodies[rigidBodyID]->Body->applyForce( Conversion::Convert(force), Conversion::Convert(position)); } float PhysicsEngine::GetFriction(int rigidBodyID) const { return m_bodies[rigidBodyID]->Body->getFriction(); } void PhysicsEngine::AddImpulse(const glm::vec3& force, const glm::vec3& position, int rigidBodyID) { if(!m_bodies[rigidBodyID]->Body->isActive()) { m_bodies[rigidBodyID]->Body->activate(true); } m_bodies[rigidBodyID]->Body->applyImpulse( Conversion::Convert(force), Conversion::Convert(position)); } void PhysicsEngine::SetVelocity(const glm::vec3& velocity, int rigidBodyID, float linearDamping, float angularDamping) { if(!m_bodies[rigidBodyID]->Body->isActive()) { m_bodies[rigidBodyID]->Body->activate(true); } m_bodies[rigidBodyID]->Body->setLinearVelocity(Conversion::Convert(velocity)); m_bodies[rigidBodyID]->Body->setDamping(linearDamping, angularDamping); } glm::vec3 PhysicsEngine::GetVelocity(int rigidBodyID) const { return Conversion::Convert(m_bodies[rigidBodyID]->Body->getLinearVelocity()); } void PhysicsEngine::SetInternalDamping(int rigidBodyID, float linearDamping, float angularDamping) { m_bodies[rigidBodyID]->Body->setDamping(linearDamping, angularDamping); } void PhysicsEngine::AddLinearDamping(int rigidBodyID, float amount) { m_bodies[rigidBodyID]->Body->setLinearVelocity( m_bodies[rigidBodyID]->Body->getLinearVelocity() * amount); } void PhysicsEngine::AddRotationalDamping(int rigidBodyID, float amount) { m_bodies[rigidBodyID]->Body->setAngularVelocity( m_bodies[rigidBodyID]->Body->getAngularVelocity() * amount); } void PhysicsEngine::SetGravity(int rigidBodyID, float gravity) { m_bodies[rigidBodyID]->Body->setGravity(btVector3(0, gravity, 0)); m_bodies[rigidBodyID]->Body->applyGravity(); } void PhysicsEngine::SetFriction(int rigidBodyID, float amount) { m_bodies[rigidBodyID]->Body->setFriction(amount); } void PhysicsEngine::ResetVelocityAndForce(int rigidBodyID) { m_bodies[rigidBodyID]->Body->clearForces(); m_bodies[rigidBodyID]->Body->setAngularVelocity(btVector3(0,0,0)); m_bodies[rigidBodyID]->Body->setLinearVelocity(btVector3(0,0,0)); } void PhysicsEngine::SetMotionState(int rigidBodyID, const glm::mat4& matrix) { btTransform& transform = m_bodies[rigidBodyID]->Body->getWorldTransform(); btMatrix3x3 basis(Conversion::Convert(matrix).getBasis()); transform.setOrigin(Conversion::Convert(Conversion::Position(matrix))); transform.setBasis(basis); m_bodies[rigidBodyID]->Body->setWorldTransform(transform); m_bodies[rigidBodyID]->State->setWorldTransform(transform); } void PhysicsEngine::SetBasis(int rigidBodyID, const glm::mat4& matrix) { btTransform& transform = m_bodies[rigidBodyID]->Body->getWorldTransform(); btMatrix3x3 basis(Conversion::Convert(matrix).getBasis()); transform.setBasis(basis); m_bodies[rigidBodyID]->Body->setWorldTransform(transform); m_bodies[rigidBodyID]->State->setWorldTransform(transform); } void PhysicsEngine::SetPosition(int rigidBodyID, const glm::vec3& position) { btTransform transform; m_bodies[rigidBodyID]->State->getWorldTransform(transform); transform.setOrigin(Conversion::Convert(position)); m_bodies[rigidBodyID]->Body->setWorldTransform(transform); m_bodies[rigidBodyID]->State->setWorldTransform(transform); } glm::mat4 PhysicsEngine::GetTransform(int rigidBodyID) const { btTransform transform; m_bodies[rigidBodyID]->State->getWorldTransform(transform); return Conversion::Convert(transform); } void PhysicsEngine::Tick(float timestep) { for (int i = 0; i < m_iterations; ++i) { m_world->stepSimulation(timestep); } } int PhysicsEngine::CreateHinge(int rigidBodyID1, int rigidBodyID2, const glm::vec3& pos1local, const glm::vec3& pos2local, const glm::vec3& axis1, const glm::vec3& axis2, float breakthreshold) { const int index = m_hinges.size(); m_hinges.push_back(std::unique_ptr<btHingeConstraint>(new btHingeConstraint( *m_bodies[rigidBodyID1]->Body, *m_bodies[rigidBodyID2]->Body, Conversion::Convert(pos1local), Conversion::Convert(pos2local), Conversion::Convert(axis1), Conversion::Convert(axis2)))); m_hinges[index]->enableAngularMotor(true, 0.0f, 10.0f); if(breakthreshold != 0) { m_hinges[index]->setBreakingImpulseThreshold(breakthreshold); } m_world->addConstraint(m_hinges[index].get()); return index; } bool PhysicsEngine::HingeEnabled(int hinge) { return m_hinges[hinge]->isEnabled(); } void PhysicsEngine::RotateHinge(int hinge, float amount, float dt) { m_hinges[hinge]->setEnabled(true); btRigidBody& bodyA = m_hinges[hinge]->getRigidBodyA(); btRigidBody& bodyB = m_hinges[hinge]->getRigidBodyB(); if(!bodyA.isActive()) { bodyA.activate(true); } if(!bodyB.isActive()) { bodyB.activate(true); } m_hinges[hinge]->setMotorTarget(amount, dt); } float PhysicsEngine::GetHingeRotation(int hinge) { return m_hinges[hinge]->getHingeAngle(); } void PhysicsEngine::StopHinge(int hinge, float dt, float damping) { const float velocity = m_hinges[hinge]->getMotorTargetVelosity(); m_hinges[hinge]->setMotorTarget(m_hinges[hinge]->getHingeAngle() + (velocity * damping), dt); } int PhysicsEngine::LoadConvexShape(std::vector<glm::vec3> vertices) { const int ID = static_cast<int>(m_shapes.size()); m_shapes.push_back(std::make_unique<btConvexHullShape>()); for (const glm::vec3& vertex : vertices) { m_shapes[ID]->addPoint(Conversion::Convert(vertex)); } return ID; } int PhysicsEngine::LoadRigidBody(const glm::mat4& matrix, int shape, float mass, int group, int meshID, int meshInstance, bool createEvents, const glm::vec3 inertia) { btTransform transform = Conversion::Convert(matrix); //Rigidbody is dynamic if and only if mass is non zero, otherwise static const bool isDynamic = mass != 0.0f; btVector3 localInertia(Conversion::Convert(inertia)); if (isDynamic) { m_shapes[shape]->calculateLocalInertia(mass, localInertia); } const int index = m_bodies.size(); m_bodies.push_back(std::unique_ptr<RigidBody>(new RigidBody())); //Motionstate provides interpolation capabilities, and only synchronizes 'active' objects m_bodies[index]->State.reset(new btDefaultMotionState(transform)); btRigidBody::btRigidBodyConstructionInfo rbInfo(mass, m_bodies[index]->State.get(), m_shapes[shape].get(), localInertia); m_bodies[index]->Body.reset(new btRigidBody(rbInfo)); m_bodies[index]->Body->setSleepingThresholds(m_sleepvalue, m_sleepvalue); m_bodies[index]->Body->setCcdMotionThreshold(0); m_bodies[index]->Shape = shape; m_bodies[index]->ProcessEvents = createEvents; m_bodies[index]->Group = group; m_bodies[index]->Mask = NO_MASK; m_bodies[index]->Index = index; m_bodies[index]->MeshID = meshID; m_bodies[index]->MeshInstance = meshInstance; m_bodies[index]->Body->setUserPointer( static_cast<void*>(m_bodies[index].get())); AddToWorld(index, true); return index; }