-
Joseph Walton-Rivers authoredJoseph Walton-Rivers authored
simulation.cpp 9.66 KiB
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
#include "../include/fggl/phys/bullet/types.hpp"
#include "../include/fggl/phys/bullet/motion.hpp"
namespace fggl::phys::bullet {
inline btVector3 to_bullet(const math::vec3 fgglVec) {
return {fgglVec.x, fgglVec.y, fgglVec.z};
}
BulletPhysicsEngine::BulletPhysicsEngine(entity::EntityManager* world) : m_ecs(world), m_config(), m_world(nullptr), m_debug(nullptr) {
m_config.broadphase = new btDbvtBroadphase();
m_config.collisionConfiguration = new btDefaultCollisionConfiguration();
m_config.dispatcher = new btCollisionDispatcher(m_config.collisionConfiguration);
m_config.solver = new btSequentialImpulseConstraintSolver();
m_world = new btDiscreteDynamicsWorld(
m_config.dispatcher,
m_config.broadphase,
m_config.solver,
m_config.collisionConfiguration);
// callbacks (for handling bullet -> ecs)
// ensure we deal with ecs -> bullet changes
//m_ecs->addDeathListener( [this](auto entity) { this->onEntityDeath(entity);} );
}
void BulletPhysicsEngine::step() {
// this is now handled on creation
//checkForPhys();
auto dynamicEnts = m_ecs->find<phys::Dynamics, phys::bullet::BulletBody>();
for (auto& ent : dynamicEnts) {
auto& dynamicComp = m_ecs->get<phys::Dynamics>(ent);
auto& bulletProxy = m_ecs->get<phys::bullet::BulletBody>(ent);
const auto forceVec = dynamicComp.force;
if ( glm::length(forceVec) != 0.F ) {
bulletProxy.body->applyCentralForce({forceVec.x, forceVec.y, forceVec.z});
bulletProxy.body->activate();
dynamicComp.force = math::VEC3_ZERO;
}
}
m_world->stepSimulation(60.0F);
//syncToECS();
pollCollisions();
// if debug draw is enabled, trigger a draw call
if ( m_debug != nullptr ) {
m_world->debugDrawWorld();
}
}
void BulletPhysicsEngine::addBody(entity::EntityID /*entity*/, BulletBody &body) {
// ensure static objects are placed correctly by setting their transforms to the correct value
btTransform transform;
body.body->getMotionState()->getWorldTransform(transform);
body.body->setWorldTransform(transform);
m_world->addRigidBody(body.body);
}
void BulletPhysicsEngine::setDebugDraw(bool active) {
if ( active ) {
m_debug = std::make_unique<debug::BulletDebugDrawList>();
m_world->setDebugDrawer(m_debug.get());
m_debug->setDebugMode(btIDebugDraw::DBG_DrawWireframe);
} else {
m_debug = nullptr;
}
}
void BulletPhysicsEngine::syncTransformToECS() {
const auto physEnts = m_ecs->find<math::Transform, BulletBody>();
for (const auto& ent : physEnts) {
auto& physBody = m_ecs->get<BulletBody>(ent);
// don't bother syncing things with motion states, it's not necessary
if ( physBody.motion != nullptr ) {
continue;
}
auto& transform = m_ecs->get<math::Transform>(ent);
if ( physBody.body->isKinematicObject() ) {
continue;
}
btTransform bTransform;
physBody.motion->getWorldTransform(bTransform);
// set calculate and set ecs position
auto& btPos = bTransform.getOrigin();
math::vec3 pos{btPos.x(), btPos.y(), btPos.z()};
transform.origin( pos );
// set the rotation
const auto& btRot = bTransform.getRotation();
math::vec3 rot;
btRot.getEulerZYX(rot.x, rot.y, rot.z);
transform.euler(rot);
}
}
BulletPhysicsEngine::~BulletPhysicsEngine() {
// clean up the rigid bodies
auto entRB = m_ecs->find<BulletBody>();
for (const auto& ent : entRB) {
auto& bulletBody = m_ecs->get<BulletBody>(ent);
if ( bulletBody.body != nullptr ) {
m_world->removeCollisionObject(bulletBody.body);
}
// release resources and delete
bulletBody.release();
}
// delete the world
delete m_world;
// cleanup the configuration object
delete m_config.solver;
delete m_config.broadphase;
delete m_config.dispatcher;
delete m_config.collisionConfiguration;
}
static void handleCollisionCallbacks(entity::EntityManager* world, entity::EntityID owner, entity::EntityID other) {
if ( !world->has<CollisionCallbacks>(owner) ) {
return;
}
auto* callbacks = world->tryGet<CollisionCallbacks>(owner);
auto* cache = world->tryGet<CollisionCache>(owner);
if ( cache != nullptr ) {
auto itr = cache->collisions.find(other);
if ( itr == cache->collisions.end() ) {
if ( callbacks->onEnter != nullptr ) {
callbacks->onEnter(owner, other);
}
cache->collisions.insert( other );
} else {
if ( callbacks->onStay != nullptr ) {
callbacks->onStay(owner, other);
}
}
}
}
void BulletPhysicsEngine::pollCollisions() {
// flush collision caches
auto caches = m_ecs->find<CollisionCache>();
for( const auto& ent : caches) {
auto& cache = m_ecs->get<CollisionCache>(ent);
std::swap(cache.collisions, cache.lastFrame);
cache.collisions.clear();
}
// deal with the number of manifolds
const auto numManifolds = m_config.dispatcher->getNumManifolds();
for ( int i=0; i< numManifolds; ++i) {
auto* contactManifold = m_config.dispatcher->getManifoldByIndexInternal(i);
const auto* body0 = contactManifold->getBody0();
const auto* body1 = contactManifold->getBody1();
int numContacts = contactManifold->getNumContacts();
for ( auto contactIdx = 0; contactIdx < numContacts; ++contactIdx ) {
auto& point = contactManifold->getContactPoint(contactIdx);
if ( point.getDistance() < 0.0F ) {
auto worldPosA = point.getPositionWorldOnA();
auto worldPosB = point.getPositionWorldOnB();
auto normal = point.m_normalWorldOnB;
}
}
//handleCollisionCallbacks(m_ecs, body0->getUserIndex(), body1->getUserIndex());
//handleCollisionCallbacks(m_ecs, body1->getUserIndex(), body0->getUserIndex());
}
// note contacts that have ended
caches = m_ecs->find<CollisionCache>(); // re-fetch, entities can (and probably do) die in handlers.
for( const auto& ent : caches) {
auto& cache = m_ecs->get<CollisionCache>(ent);
// if we don't have an exit callback, it's pointless checking the cache
auto* callbacks = m_ecs->tryGet<CollisionCallbacks>(ent);
if ( callbacks == nullptr || callbacks->onExit == nullptr){
continue;
}
// check if a collision has ended
for (const auto& other : cache.lastFrame) {
auto itr = cache.collisions.find(other);
if (itr == cache.collisions.end()) {
callbacks->onExit( ent,other);
}
}
}
}
void BulletPhysicsEngine::onEntityDeath(entity::EntityID entity) {
auto* btPhysics = m_ecs->tryGet<BulletBody>(entity);
if ( btPhysics != nullptr) {
// decouple physics from entity
//btPhysics->body->setUserIndex( entity::INVALID );
m_world->removeRigidBody( btPhysics->body );
btPhysics->release();
}
}
void BulletPhysicsEngine::onCollisionCreate(ContactPoint point) {
m_contactCache.created.push_back(point);
}
void BulletPhysicsEngine::onCollisionProcess(ContactPoint point) {
m_contactCache.modified.push_back( point );
}
void BulletPhysicsEngine::onCollisionDestroyed(ContactPoint point) {
m_contactCache.removed.push_back(point);
}
entity::EntityID BulletPhysicsEngine::raycast(math::vec3 from, math::vec3 to) {
const auto btFrom = to_bullet(from);
const auto btTo = to_bullet(to);
btCollisionWorld::ClosestRayResultCallback rayTestResult(btFrom, btTo);
m_world->rayTest(btFrom, btTo, rayTestResult);
if ( !rayTestResult.hasHit() ) {
return entity::INVALID;
}
// tell the user what it hit
return entity::INVALID;
// return rayTestResult.m_collisionObject->getUserIndex();
}
std::vector<ContactPoint> BulletPhysicsEngine::scanCollisions(entity::EntityID entity) {
return std::vector<ContactPoint>();
}
std::vector<entity::EntityID> BulletPhysicsEngine::raycastAll(math::vec3 fromPos, math::vec3 toPos) {
const auto btFrom = to_bullet(fromPos);
const auto btTo = to_bullet(toPos);
btCollisionWorld::AllHitsRayResultCallback rayTestResult(btFrom, btTo);
m_world->rayTest(btFrom, btTo, rayTestResult);
// we didn't hit anything
if ( !rayTestResult.hasHit() ) {
return {};
}
// hit processing
const auto hits = rayTestResult.m_collisionObjects.size();
std::vector<entity::EntityID> hitVec;
hitVec.reserve(hits);
for ( auto i = 0; i < hits; ++i) {
auto entity = rayTestResult.m_collisionObjects[i]->getUserIndex();
//hitVec.push_back(entity);
}
return hitVec;
}
std::vector<entity::EntityID> BulletPhysicsEngine::sweep(PhyShape &shape,
math::Transform &from,
math::Transform &to) {
return std::vector<entity::EntityID>();
}
} // namespace fggl::phys::bullet