#include "../../gameplay/physics.h" #include "../../gameplay/weapons/bullet.h" #include "../../utility/events.h" #include void PhysicsEngine::hookEventManager(const std::shared_ptr& eventManager) { this->eventManager = eventManager; this->eventManager->subscribe("OnBulletFired", [this](std::shared_ptr e) { auto bulletEvent = std::static_pointer_cast(e); this->addObject(bulletEvent->bullet->getPhysicsComponent()); }); this->eventManager->subscribe("OnBulletDied", [this](std::shared_ptr e) { auto bulletEvent = std::static_pointer_cast(e); this->removeObject(bulletEvent->physObj); }); } std::shared_ptr PhysicsEngine::createObject(const glm::vec3& pos, float mass, PhysicsComponent::Collider::Shape shape, glm::vec3 dimensions, glm::vec3 offset) { auto component = std::make_shared (); component->rigidBody.position = pos; component->rigidBody.mass = mass; component->collider.shape = shape; component->collider.dimensions = dimensions; component->collider.offset = offset; addObject(component); return component; } void PhysicsEngine::loadCollisionMap(const std::vector>& collisionMap, float tileSize) { this->collisionMap = collisionMap; this->tileSize = tileSize; } void PhysicsEngine::addObject(const std::shared_ptr& component) { if (component) objects.emplace_back(component); } void PhysicsEngine::removeObject(const std::shared_ptr& component) { if (std::find(objects.begin(), objects.end(), component) != objects.end()) objects.erase(std::remove(objects.begin(), objects.end(), component)); } int PhysicsEngine::getTileCollider(const glm::vec3& position) { int x = static_cast(position.x / tileSize); int y = static_cast(position.y / tileSize); if (y >= 0 && y < collisionMap.size()) { if (x >= 0 && x < collisionMap[y].size()) return collisionMap[y][x]; } return 0; } void PhysicsEngine::getPossibleCollisions() { objCollisions.clear(); for (size_t i = 0; i < objects.size(); ++i) { auto& obj = objects[i]; for (size_t j = i + 1; j < objects.size(); ++j) { auto& colliderObj = objects[j]; if (obj.get() == colliderObj.get()) continue; float colliderRight = colliderObj->rigidBody.position.x + colliderObj->collider.dimensions.x; float colliderBottom = colliderObj->rigidBody.position.y + colliderObj->collider.dimensions.y; float objectRight = obj->rigidBody.position.x + obj->collider.dimensions.x; float objectBottom = obj->rigidBody.position.y + obj->collider.dimensions.y; if ((obj->rigidBody.position.x <= colliderRight && objectRight >= colliderObj->rigidBody.position.x) || (obj->rigidBody.position.y <= colliderBottom && objectBottom >= colliderObj->rigidBody.position.y)) objCollisions.push_back(std::make_pair(obj.get(), colliderObj.get())); } } } void PhysicsEngine::resolvePossibleCollisions() { for (auto& objs : objCollisions) { // Solve for two circles, we'll need to expand upon this for different colliders... float sumOfRadius = objs.first->collider.dimensions.x + objs.second->collider.dimensions.x; glm::vec3 objFirstCenter = objs.first->rigidBody.position + objs.first->collider.offset; glm::vec3 objSecondCenter = objs.second->rigidBody.position + objs.second->collider.offset; glm::vec3 distance = objFirstCenter - objSecondCenter; if (glm::length(distance) < sumOfRadius) { // We got impact! glm::vec3 normal = distance / glm::length(distance); float penetrationDepth = sumOfRadius - glm::length(distance); glm::vec3 correctionVector = normal * (penetrationDepth / ((1 / objs.first->rigidBody.mass) + (1 / objs.second->rigidBody.mass))); glm::vec3 vrel = objs.first->rigidBody.velocity - objs.second->rigidBody.velocity; float e = std::min(objs.first->rigidBody.elasticity, objs.second->rigidBody.elasticity); float impulseMag = (-(1 + e) * glm::dot(vrel, normal)) / ((1 / objs.first->rigidBody.mass) + (1 / objs.second->rigidBody.mass)); //objs.first->rigidBody.applyForce(normal, impulseMag); //objs.second->rigidBody.applyForce(normal, -impulseMag); objs.first->rigidBody.position += (correctionVector / objs.first->rigidBody.mass); objs.second->rigidBody.position -= (correctionVector / objs.second->rigidBody.mass); objs.first->rigidBody.velocity += impulseMag * normal / objs.first->rigidBody.mass; objs.second->rigidBody.velocity -= impulseMag * normal / objs.second->rigidBody.mass; } } } void PhysicsEngine::resolveWorldCollision(const std::shared_ptr& obj) { switch (obj->collider.shape) { case PhysicsComponent::Collider::Shape::Circle: float radius = obj->collider.dimensions.x; glm::vec3 position = obj->rigidBody.position + obj->collider.offset; int topTile = getTileCollider(position - glm::vec3(0, radius, 0)); int bottomTile = getTileCollider(position + glm::vec3(0, radius, 0)); int leftTile = getTileCollider(position - glm::vec3(radius, 0, 0)); int rightTile = getTileCollider(position + glm::vec3(radius, 0, 0)); if (obj->isBullet) { if (topTile || bottomTile || leftTile || rightTile) { eventManager->notify(std::make_shared(obj)); return; } } if (topTile) { //obj->rigidBody.velocity.y = -obj->rigidBody.velocity.y; int tileY = static_cast((position.y - radius) / tileSize); obj->rigidBody.position.y = (tileY) * tileSize + radius + obj->collider.offset.y; } if (bottomTile) { //obj->rigidBody.velocity.y = -obj->rigidBody.velocity.y; int tileY = static_cast((position.y + radius) / tileSize); obj->rigidBody.position.y = tileY * tileSize - radius - obj->collider.offset.y; } if (leftTile) { //obj->rigidBody.velocity.x = -obj->rigidBody.velocity.x; int tileX = static_cast((position.x - radius) / tileSize); obj->rigidBody.position.x = (tileX) * tileSize + radius + obj->collider.offset.x; } if (rightTile) { //obj->rigidBody.velocity.x = -obj->rigidBody.velocity.x; int tileX = static_cast((position.x + radius) / tileSize); obj->rigidBody.position.x = tileX * tileSize - radius - obj->collider.offset.x; } } } void PhysicsEngine::update(float deltaTime) { for (auto& obj : objects) { if (!obj) continue; glm::vec3 frictionForce = obj->rigidBody.velocity * -0.1f; if (std::abs(obj->rigidBody.acceleration.x) == std::abs(obj->rigidBody.acceleration.y)) { obj->rigidBody.acceleration.x *= 0.75f; obj->rigidBody.acceleration.y *= 0.75f; } if (!obj->isBullet) obj->rigidBody.velocity += (frictionForce); obj->rigidBody.velocity += obj->rigidBody.acceleration; float maxSpeed = 500.f; float curSpeed = glm::length(obj->rigidBody.velocity); if (curSpeed > maxSpeed) { // Move at maxspeed obj->rigidBody.velocity = glm::normalize(obj->rigidBody.velocity) * maxSpeed; } obj->rigidBody.acceleration = glm::vec3(0.f); if (obj->collider.dimensions != glm::vec3(0.f)) { // check map collisions resolveWorldCollision(obj); } obj->rigidBody.position += obj->rigidBody.velocity * deltaTime; } getPossibleCollisions(); if (!objCollisions.empty()) resolvePossibleCollisions(); }