Skip to content
Snippets Groups Projects
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