#include "PhysicsBase.h" #include "EntityBase.h" // needed for GetEntityGameState() in ProcessCollisionEvent() #include "ModelBase.h" #include "coll2d.h" #define EPSILON 1.0e-4 namespace Engine { /* * Inherited Module functions */ int PhysicsBase::OnInit (int argc, char* argv[]) { LogDebug ("Physics Init"); mEntities.clear (); return 0; } void PhysicsBase::OnDestroy () { LogDebug ("Physics Destroy"); } /* * Module specific functions */ void PhysicsBase::Move (float delta_msec) { EntityPhysicState* entity = NULL; std::map::iterator entity_iter; std::map::iterator collision_iter; vector3d velocity, orientation; for (entity_iter = mEntities.begin (); entity_iter != mEntities.end(); entity_iter++){ entity = entity_iter->second; velocity = entity->GetVelocity (); if (velocity.length2() == 0.) continue; CheckContactCache (entity); entity->SetPosition (entity->GetPosition () + velocity * delta_msec); entity->SetOrientation (vector3d(0., entity->GetOrientation()[1] + entity->GetAngleVelocity() * delta_msec, 0.)); } } int PhysicsBase::Simulate (float msec, ModelBase* model) { vector3d velocity, orientation; unsigned int entity_a_id, entity_b_id; int resolve_steps = 0;; float current_time = 0, stepsize = msec / (float) 10; while (current_time < msec) { if (msec - current_time < stepsize) stepsize = msec - current_time; coll2d::CollisionInfo info; bool collision_result = false; collision_result = CalcNextCollision (stepsize, entity_a_id, entity_b_id, info); if (collision_result == 0) { // there is no collision, so we integrate to the end of // the timestep current_time += stepsize; Move (stepsize); } else { LogDebug ("Collision between %u and %u", entity_a_id, entity_b_id); float collision_remaining_step = stepsize; resolve_steps = 0; // This is the collision loop. It loops until there is no collision any // more within the stepsize. while (collision_remaining_step > 0.) { // with this we count how often we iterate through and can // detect certain false behaviour resolve_steps ++; // info.time tells us in what time the contact will happen and // theoretically we can move to that point, however due to round-off // errors we might end up in a situation where the Entities overlap // which we must prevent to keep the collision detection working. // // Therefore we only move to a portion of the time and resolve the // collision then. This alpha variable controls how much of the time // we skip. float alpha = 0.001; // If the timestep is already very small, we try to resolve it // immediately. if (info.time < EPSILON ) { // if the timestep is too small we simply resolve the collision and // do not move beforehang LogDebug ("Time step too small ==> Resolving Step immediately"); ResolveCollision (0., entity_a_id, entity_b_id, info); // Send the collision event to the Model (if we have one) if (model) { model->SendEntityCollisionEvent (entity_a_id, entity_b_id, info.time, info.point, info.normal); } // collision_remaining_step -= 1.0e-4; } else { Move (info.time * (1 - alpha)); LogDebug ("Resolving Step between %u and %u t = %f alpha = %f", entity_a_id, entity_b_id, info.time, alpha); ResolveCollision (info.time * (1 - alpha), entity_a_id, entity_b_id, info); // Send the collision event to the Model (if we have one) if (model) { model->SendEntityCollisionEvent (entity_a_id, entity_b_id, info.time, info.point, info.normal); } collision_remaining_step -= info.time * (1 - alpha); } // At this point we resolved the collision we had found. Now we need // to check whether there is a new one collision_result = CalcNextCollision (collision_remaining_step, entity_a_id, entity_b_id, info); // If there was none, we happily move on to the end of the frame and // set collision_remaining_step to 0 so that we jump out of the // collision loop. if (!collision_result) { Move (collision_remaining_step); collision_remaining_step = 0.; break; } assert (resolve_steps <= 50); } } } /* Postcondition: we simulated exactly msec milliseconds, no less, no more! */ assert (current_time == msec); return 0; } void PhysicsBase::RegisterEntity (EntityPhysicState* entity) { unsigned int id = entity->mId; LogDebug ("Registering EntityPhysicState with id '%d'", id); if (mEntities.find (id) == mEntities.end ()) mEntities[id] = entity; else { LogError ("Physics already has registered an Entity with id '%d'", id); } } void PhysicsBase::UnregisterEntity (const unsigned int id) { std::map::iterator iter = mEntities.find(id); LogDebug ("Unregistering EntityPhysicState with id '%d'", id); if (iter != mEntities.end ()) { // Remove all the references of existing contacts to the Entity that is // going to be deleted. std::map::iterator temp_iter, contact_iter = iter->second->mContactNormals.begin(); while (contact_iter != iter->second->mContactNormals.end()) { temp_iter = contact_iter; contact_iter ++; ContactCacheRemove (id, temp_iter->first); } // Remove the Entity from mEntities and erase the Entity EntityPhysicState* entity_physic_state = iter->second; mEntities.erase (iter); // We also have to delete the state! if (entity_physic_state->mShape) delete entity_physic_state->mShape; delete entity_physic_state; } else { LogError ("Could not unegister EntityPhysicState with id '%d': Entity not found!", id); } } /** * This function also sorts out collisions that might be invalid such as * Particle-Particle collision and the like. */ inline bool PhysicsBase::CheckPossibleCollisionPair (EntityPhysicState* entity_a, EntityPhysicState* entity_b) { // no collision checks against itself if (entity_a == entity_b) return false; // no checks if both are static if (entity_a->mStatic && entity_a->mStatic) return false; // no checks if both are particles if (entity_a->mBaseType == EntityBaseTypeParticle && entity_b->mBaseType == EntityBaseTypeParticle) return false; // no checks if both are particles if (entity_a->mBaseType == EntityBaseTypeBlock && entity_b->mBaseType == EntityBaseTypeBlock) return false; return true; } /** * \param stepsize The timestep for which we want to check whether a * collision occurs. * \param reference_entity_id If a collision occurs the Id of the entity that * is the reference entity is stored in this variable. * \param incidence_entity_id If a collision occurs the Id of the entity that * crashes into the reference entity is stored in this variable. * \param info Contains information about the collision (normal, time, point) * * This function calls PhysicsBase::CheckPossibleCollisionPair() to sort out * invalid collision pairs. */ bool PhysicsBase::CalcNextCollision ( float stepsize, unsigned int &reference_entity_id, unsigned int &incidence_entity_id, coll2d::CollisionInfo &info) { std::map::iterator collision_iter; std::map::iterator collision_ref; // We have to find the next collision, so we check everything and // take the first one that will happen. coll2d::CollisionInfo temp_info; info.time = 2.; for (collision_ref = mEntities.begin (); collision_ref != mEntities.end (); collision_ref ++) { if (collision_ref->second->mId != 0) { collision_ref->second->UpdateShape(); } } for (collision_ref = mEntities.begin (); collision_ref != mEntities.end (); collision_ref ++) { if (collision_ref->second->mAlive == false) continue; for (collision_iter = collision_ref; collision_iter != mEntities.end (); collision_iter ++) { if (collision_iter->second->mAlive == false) continue; if (!CheckPossibleCollisionPair(collision_ref->second, collision_iter->second)) continue; EntityPhysicState *entity_a, *entity_b; entity_a = collision_ref->second; entity_b = collision_iter->second; int coll2d_result = coll2d::check_collision (stepsize, entity_a->mShape, entity_b->mShape, &temp_info); if (!HandleColl2dError (coll2d_result, stepsize, entity_a, entity_b, temp_info)) { LogError ("Could not handle coll2d error: %d\n", coll2d_result); } if (coll2d_result > 0 && temp_info.time < info.time ) { info = temp_info; assert (info.reference_shape >= 0); if (info.reference_shape == 0) { reference_entity_id = collision_ref->first; incidence_entity_id = collision_iter->first; } else { reference_entity_id = collision_iter->first; incidence_entity_id = collision_ref->first; } } } } if (info.time != 2.) return true; return false; } /** * This function updates the velocity of the Entity with id of * incidence_entity_id so that it no more is in collision with with Entity * with id reference_entity_id. Additionally to that we also cache the * contact normals and check against them so that the new velocity does not * violate previous contacts. * * We store the cached contact normals both in the incidence and the reference * entity and we have to make sure that these references get cleared once one * of the two get deleted! This is done in PhysicsBase::UnregisterEntity(). */ void PhysicsBase::ResolveCollision (float stepsize, unsigned int reference_entity_id, unsigned int incidence_entity_id, coll2d::CollisionInfo &info) { EntityPhysicState* reference_entity = mEntities[reference_entity_id]; EntityPhysicState* incidence_entity = mEntities[incidence_entity_id]; if (!reference_entity || !incidence_entity) { LogError ("Invalid entity IDs passed to %s: %u and %u", __FUNCTION__, reference_entity_id, incidence_entity_id); } assert (reference_entity && incidence_entity); // So far only resolving of collision of sphere collisions is // allowed assert (dynamic_cast (incidence_entity->mShape)); // First we calculate the velocity along the normal and then we calculate // the velocity that is tangential to the plane and set the new velocity to // it. vector3d new_velocity = incidence_entity->mVelocity; new_velocity -= reference_entity->mVelocity; // It should be greater zero otherwise there was an error in the collision // detection. assert (new_velocity.length2 ()); // The scalar proj tells us how far we went along the normal float proj = new_velocity * info.normal; // As there is a collision, and the incidence_entity is moving towards the // plane, this value must be strictly smaller than zero, otherwise we would // not penetrate the plane. if (proj > 0.) { LogError ("Projection invalid: %e", proj); info.doPrint ("Collision Info:\n"); } assert (proj < 0.); // Collision handling start: // This is the code that tells us how we deal with collisions and how to // prevent them. To get real dynamics one has to adjust this section: vector3d old_velocity = incidence_entity->mVelocity; new_velocity = old_velocity + info.normal * proj * (-1.0); incidence_entity->SetVelocity (new_velocity); // Collision handling end // And we also add the normal to the cached contacts for both incidence // and reference entity ContactCacheAdd (incidence_entity, reference_entity, info.normal); // Now we check whether we are violating any of the cached contacts. For // this we loop through all contacts and try to adjust the velocity of // incidence_entity until we no more violate the cached contacts. std::map::iterator iter = incidence_entity->mContactNormals.begin(); int readjust = 0; while (iter != incidence_entity->mContactNormals.end()) { vector3d contact_normal = iter->second; float contact_velocity = new_velocity * contact_normal; if (contact_velocity < 0.) { if (reference_entity_id == iter->first) { // In this case, the projection was not good enough. We simultaneously // damp the velocity and push a little harder. If it was damped too // much or we had to readjust too often, we set the velocity to zero. LogDebug ("Resolved collision needs to be readjusted"); new_velocity = incidence_entity->GetVelocity(); new_velocity *= 0.5; if (new_velocity.length2() < EPSILON) new_velocity.setValues (0., 0., 0.); else new_velocity += info.normal * 0.001; incidence_entity->SetVelocity (new_velocity); readjust++; // More than 10 readjusts? -> set velocity to zero if (readjust > 10) new_velocity.setValues (0., 0., 0.); // and we redo all the checking: iter = incidence_entity->mContactNormals.begin(); continue; } // In this case the proposed velocity is violating another contact // and we set the velocity to zero. LogDebug ("Cached collision: %e -> resetting velocity", contact_velocity); // contact_normal.print ("contact normal: "); new_velocity.setValues (0., 0., 0.); incidence_entity->mVelocity = new_velocity; } else if (contact_velocity > 0.01) { // If we move sufficiently fast away from a contact we remove the // contact from the cache. // * Attention! * As the function ContactCacheRemove() deletes an entry // of mContactNormals we have to increase the iterator *before* we // remove the cache. Otherwise the iterator might become invalid! unsigned int contact_id = iter->first; iter++; ContactCacheRemove (contact_id, incidence_entity_id); continue; } iter++; } } /* * Contact Cache Functions */ void PhysicsBase::ContactCacheAdd (EntityPhysicState* incidence_entity, EntityPhysicState* reference_entity, vector3d normal) { incidence_entity->mContactNormals[reference_entity->mId] = normal; reference_entity->mContactNormals[incidence_entity->mId] = normal * -1.; LogDebug ("Adding normal (%f,%f,%f) id=%d to entity %d", normal[0], normal[1], normal[2], reference_entity->mId, incidence_entity->mId); LogDebug ("Adding normal (%f,%f,%f) id=%d to entity %d", normal[0] * -1., normal[1] * -1., normal[2] * -1., incidence_entity->mId, reference_entity->mId); } void PhysicsBase::ContactCacheRemove (unsigned int entity_a_id, unsigned int entity_b_id) { assert (entity_a_id != entity_b_id); EntityPhysicState *entity_a, *entity_b; #ifdef WIN32 entity_a = mEntities[entity_a_id]; entity_b = mEntities[entity_b_id]; #else entity_a = mEntities.at(entity_a_id); entity_b = mEntities.at(entity_b_id); #endif // Check the entries exist assert (entity_a->mContactNormals.find (entity_b_id) != entity_a->mContactNormals.end()); assert (entity_b->mContactNormals.find (entity_a_id) != entity_b->mContactNormals.end()); #ifdef WIN32 vector3d contact_normal = entity_a->mContactNormals[entity_b_id]; #else vector3d contact_normal = entity_a->mContactNormals.at(entity_b_id); #endif LogDebug ("Removing normal (%f,%f,%f) id=%d from entity %d", contact_normal[0], contact_normal[1], contact_normal[2], entity_a_id, entity_b_id); entity_a->mContactNormals.erase (entity_a->mContactNormals.find(entity_b_id)); #ifdef WIN32 contact_normal = entity_b->mContactNormals[entity_a_id]; #else contact_normal = entity_b->mContactNormals.at(entity_a_id); #endif LogDebug ("Removing normal (%f,%f,%f) id=%d from entity %d", contact_normal[0], contact_normal[1], contact_normal[2], entity_b_id, entity_a_id); entity_b->mContactNormals.erase (entity_b->mContactNormals.find(entity_a_id)); } /** \brief Checks whether we are still in contact with the entities stored in mContactNormals * * To check whether we still are in contact, we modify temporarily the * velocity of the given Entity that it moves towards the contact point (i.e. * we add the negative normal to the velocity) and re-check for a collision. * If the collision has a time value of 0.0 and the reported normal stays the * same, we know, that the two Entities are still in contact. If not, we lost * contact. * * We can skip the test, if the scalar product of the normal and velocity are * positive (in this case we move away from the plane) */ void PhysicsBase::CheckContactCache (EntityPhysicState* entity) { std::map::iterator contacts_iter, current_iter; contacts_iter = entity->mContactNormals.begin(); while (contacts_iter != entity->mContactNormals.end()) { // * Attention! * // current_iter can be used throughout this environment and contacts_iter // can already now be increased as it *must not* be used! This is due to // the nature of ContactCacheRemove() which might make contacts_iter // invalid if we were using that. current_iter = contacts_iter; contacts_iter ++; unsigned int contact_entity_id = current_iter->first; #ifdef WIN32 EntityPhysicState* contact_entity = mEntities[contact_entity_id]; #else EntityPhysicState* contact_entity = mEntities.at (contact_entity_id); #endif vector3d normal = current_iter->second; vector3d old_velocity = entity->GetVelocity(); // If we already move away from the normal, we delete the contact. if (normal * old_velocity > 0.01) { LogDebug ("Lost Contact with entity %d!", current_iter->first); ContactCacheRemove (entity->mId, current_iter->first); continue; } else { vector3d new_velocity (old_velocity); new_velocity -= normal; entity->SetVelocity (new_velocity); entity->UpdateShape(); contact_entity->UpdateShape(); coll2d::CollisionInfo info; int coll2d_result; coll2d_result = coll2d::check_collision (1.0, entity->mShape, contact_entity->mShape, &info); if (!HandleColl2dError (coll2d_result, 1.0, entity, contact_entity, info)) { // error LogError ("Error when performing collision check: %s\n", __FUNCTION__); } if (coll2d_result > 0) { // no contact, so delete it: LogDebug ("Lost Contact with entity %d!", current_iter->first); ContactCacheRemove (entity->mId, current_iter->first); entity->SetVelocity (old_velocity); continue; } else if ( coll2d_result < 0){ // contact if (info.time != 0. || normal != info.normal) { LogError ("Something strange happened when checking for contacts in %s", __FUNCTION__); } } entity->SetVelocity (old_velocity); } } } /** * So far we ignore overlapping if one entity is an EntityBaseTypeActor and * the other a EntityBaseTypeParticle. * * If this function returns true everything is ok and we can safely continue * otherwise it is recommended to quit the application. * * \returns true if there was no error at all or we were able to deal with it */ bool PhysicsBase::HandleColl2dError (int coll2d_result, float stepsize, EntityPhysicState* entity_a, EntityPhysicState* entity_b, coll2d::CollisionInfo &info) { if (coll2d_result < 0) { if (coll2d_result == CHECK_ERROR_OVERLAP) { // this can happen if an Actor is faster than its thrown Particle, // we ignore this for now /// \todo Handle overlaps of Actors and Particles better or define clear guidelines. Note this also in Entity.h if ( (entity_a->mBaseType == EntityBaseTypeParticle && entity_b->mBaseType == EntityBaseTypeActor) || (entity_b->mBaseType == EntityBaseTypeParticle && entity_a->mBaseType == EntityBaseTypeActor) ) LogDebug ("Ignoring CHECK_ERROR_OVERLAP"); return true; } else { LogError ("coll2d Error: %d (stepsize = %f, id_a = %d, id_b = %d)", coll2d_result, stepsize, entity_a->mId, entity_b->mId); entity_a->mShape->doPrint ("Debug: entity_a"); entity_b->mShape->doPrint ("Debug: entity_b"); return false; } } return true; } EntityPhysicState* CreateEntityPhysicState (EntityBaseType type, unsigned int id) { EntityPhysicState* entity_physics = new EntityPhysicState (); if (!entity_physics) { LogError ("Could not allocate enough memory for EntityPhysicState of type '%d'", type); } // default values for all Entities entity_physics->mId = id; entity_physics->mBaseType = type; // specific values for each Entity type if (type == EntityBaseTypeNone) { entity_physics->mShape = new coll2d::Sphere (0.01); assert (entity_physics->mShape); } else if (type == EntityBaseTypeActor) { entity_physics->mShape = new coll2d::Sphere (0.4); assert (entity_physics->mShape); } else if (type == EntityBaseTypeBlock) { entity_physics->mShape = new coll2d::Polygon (4); assert (entity_physics->mShape); static_cast (entity_physics->mShape)->setVertice (0, vector3d (-0.5, 0., 0.5)); static_cast (entity_physics->mShape)->setVertice (1, vector3d (0.5, 0., 0.5)); static_cast (entity_physics->mShape)->setVertice (2, vector3d (0.5, 0., -0.5)); static_cast (entity_physics->mShape)->setVertice (3, vector3d (-0.5, 0., -0.5)); } else if (type == EntityBaseTypeParticle) { entity_physics->mShape = new coll2d::Sphere (0.05); assert (entity_physics->mShape); } else { LogError ("No EntityPhysicState defined for Entity type '%d'", type); } return entity_physics; } }