752 lines
26 KiB
C++
752 lines
26 KiB
C++
#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 ();
|
|
mWorldMin.setValues (-0.5, -0.5, -0.5);
|
|
mWorldMax.setValues (0.5, 0.5, 0.5);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void PhysicsBase::OnDestroy () {
|
|
LogDebug ("Physics Destroy");
|
|
}
|
|
|
|
/*
|
|
* Module specific functions
|
|
*/
|
|
|
|
void PhysicsBase::Move (float delta_msec) {
|
|
EntityPhysicState* entity = NULL;
|
|
std::map<unsigned int, EntityPhysicState*>::iterator entity_iter;
|
|
std::map<unsigned int, EntityPhysicState*>::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");
|
|
// 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);
|
|
}
|
|
ResolveCollision (0., entity_a_id, entity_b_id, info);
|
|
// collision_remaining_step -= 1.0e-4;
|
|
} else {
|
|
Move (info.time * (1 - alpha));
|
|
// 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);
|
|
}
|
|
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);
|
|
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<unsigned int, EntityPhysicState*>::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<unsigned int, vector3d>::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<unsigned int, EntityPhysicState*>::iterator collision_iter;
|
|
std::map<unsigned int, EntityPhysicState*>::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);
|
|
|
|
// Here we check
|
|
if (coll2d_result == 0) {
|
|
// Now we have to check whether a warp is required
|
|
vector3d warp_directions = CheckEntityWarp (entity_a, stepsize);
|
|
|
|
if (warp_directions.length2() == 0.) {
|
|
// If none is reported for entity_a we still have to check whether we might have
|
|
// to warp entity_b. If so, we have to negate the warp_direction as
|
|
// all further processing assumes, that entity_a is being warped.
|
|
// Warping of entity_b by d is equivalent to warping entity_a by -d.
|
|
warp_directions = CheckEntityWarp (entity_b, stepsize);
|
|
if (warp_directions.length2() != 0.)
|
|
warp_directions *= -1.;
|
|
}
|
|
|
|
// if a valid warp direction was found we perform the checks at
|
|
// the warped positions
|
|
if (warp_directions.length2() != 0.) {
|
|
// LogMessage ("doing some warping: %f, %f, %f", warp_directions[0], warp_directions[1], warp_directions[2]);
|
|
coll2d_result = PerformWarpedCollisionChecks (stepsize, entity_a, entity_b, warp_directions, 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;
|
|
}
|
|
|
|
int PhysicsBase::PerformWarpedCollisionChecks (float stepsize, EntityPhysicState* entity_a, EntityPhysicState* entity_b, vector3d warp_directions, coll2d::CollisionInfo &info) {
|
|
int coll2d_result = 0;
|
|
|
|
vector3d entity_original_pos = entity_a->mPosition;
|
|
vector3d test_warp;
|
|
|
|
// LogMessage ("warping directions %f, %f, %f ids %d, %d", warp_directions[0], warp_directions[1], warp_directions[2], entity_a->mId, entity_b->mId);
|
|
|
|
// check for along 1, 0, 0
|
|
if (warp_directions[0] != 0.) {
|
|
test_warp.setValues(warp_directions[0], 0., 0.);
|
|
// LogMessage ("warping position %f, %f, %f", test_warp[0], test_warp[1], test_warp[2]);
|
|
entity_a->mPosition += test_warp;
|
|
entity_a->UpdateShape();
|
|
coll2d_result = coll2d::check_collision (stepsize, entity_a->mShape, entity_b->mShape, &info);
|
|
// LogMessage ("collision result = %d", coll2d_result);
|
|
|
|
if (coll2d_result) {
|
|
// reset the entity
|
|
entity_a->mPosition = entity_original_pos;
|
|
entity_a->UpdateShape();
|
|
return coll2d_result;
|
|
}
|
|
}
|
|
|
|
// reset the entity
|
|
entity_a->mPosition = entity_original_pos;
|
|
entity_a->UpdateShape();
|
|
|
|
// check along 0, 0, 1
|
|
if (warp_directions[2] != 0.) {
|
|
test_warp.setValues(0, 0, warp_directions[2]);
|
|
// LogMessage ("warping position %f, %f, %f", test_warp[0], test_warp[1], test_warp[2]);
|
|
entity_a->mPosition += test_warp;
|
|
entity_a->UpdateShape();
|
|
coll2d_result = coll2d::check_collision (stepsize, entity_a->mShape, entity_b->mShape, &info);
|
|
// LogMessage ("collision result = %d", coll2d_result);
|
|
|
|
if (coll2d_result) {
|
|
// reset the entity
|
|
entity_a->mPosition = entity_original_pos;
|
|
entity_a->UpdateShape();
|
|
return coll2d_result;
|
|
}
|
|
}
|
|
|
|
// reset the entity
|
|
entity_a->mPosition = entity_original_pos;
|
|
entity_a->UpdateShape();
|
|
|
|
// check along 1, 0, 1
|
|
if (warp_directions[0] != 0. && warp_directions[2] != 0.) {
|
|
test_warp.setValues(warp_directions[0], 0, warp_directions[2]);
|
|
// LogMessage ("warping position %f, %f, %f", test_warp[0], test_warp[1], test_warp[2]);
|
|
entity_a->mPosition += test_warp;
|
|
entity_a->UpdateShape();
|
|
coll2d_result = coll2d::check_collision (stepsize, entity_a->mShape, entity_b->mShape, &info);
|
|
// LogMessage ("collision result = %d", coll2d_result);
|
|
|
|
if (coll2d_result) {
|
|
// reset the entity
|
|
entity_a->mPosition = entity_original_pos;
|
|
entity_a->UpdateShape();
|
|
return coll2d_result;
|
|
}
|
|
}
|
|
|
|
// reset the entity
|
|
entity_a->mPosition = entity_original_pos;
|
|
entity_a->UpdateShape();
|
|
|
|
return coll2d_result;
|
|
}
|
|
|
|
/**
|
|
* 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<coll2d::Sphere*> (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<unsigned int, vector3d>::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<unsigned int, vector3d>::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);
|
|
}
|
|
}
|
|
}
|
|
|
|
vector3d PhysicsBase::CheckEntityWarp (EntityPhysicState* entity, float stepsize) {
|
|
vector3d result (0., 0., 0.);
|
|
|
|
float bounding_sphere_radius = entity->mShape->getBoundingRadius();
|
|
vector3d world_size = mWorldMax - mWorldMin;
|
|
|
|
// first we check, whether the entity already overlaps
|
|
if (mWorldWarp.test(WorldWarpModeX)) {
|
|
vector3d test_dir (1., 0., 0.);
|
|
vector3d test_pos = entity->mPosition + test_dir * bounding_sphere_radius;
|
|
if (test_pos[0] > mWorldMax[0])
|
|
result += vector3d (-world_size[0], 0., 0.);
|
|
else {
|
|
test_pos = entity->mPosition - test_dir * bounding_sphere_radius;
|
|
if (test_pos[0] < mWorldMin[0])
|
|
result += vector3d (world_size[0], 0., 0.);
|
|
}
|
|
|
|
if (result[0] == 0.) {
|
|
// then we check whether the entity overlaps at its final destination
|
|
test_dir.setValues (1., 0., 0.);
|
|
test_pos = entity->mPosition + test_dir * bounding_sphere_radius + entity->mVelocity * stepsize;
|
|
if (test_pos[0] > mWorldMax[0])
|
|
result += vector3d (-world_size[0], 0., 0.);
|
|
else {
|
|
test_pos = entity->mPosition - test_dir * bounding_sphere_radius + entity->mVelocity * stepsize;
|
|
if (test_pos[0] < mWorldMin[0])
|
|
result += vector3d (world_size[0], 0., 0.);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mWorldWarp.test(WorldWarpModeZ)) {
|
|
vector3d test_dir (0., 0., 1.);
|
|
vector3d test_pos = entity->mPosition + test_dir * bounding_sphere_radius;
|
|
if (test_pos[2] > mWorldMax[2])
|
|
result += vector3d (0, 0., -world_size[2]);
|
|
else {
|
|
test_pos = entity->mPosition - test_dir * bounding_sphere_radius;
|
|
if (test_pos[2] < mWorldMin[2])
|
|
result += vector3d (0, 0., world_size[2]);
|
|
}
|
|
|
|
if (result[2] == 0.) {
|
|
// then we check whether the entity overlaps at its final destination
|
|
test_dir.setValues (0., 0., 1.);
|
|
test_pos = entity->mPosition + test_dir * bounding_sphere_radius + entity->mVelocity * stepsize;
|
|
if (test_pos[2] > mWorldMax[2])
|
|
result += vector3d (0, 0, -world_size[2]);
|
|
else {
|
|
test_pos = entity->mPosition - test_dir * bounding_sphere_radius + entity->mVelocity * stepsize;
|
|
if (test_pos[2] < mWorldMin[2])
|
|
result += vector3d (0, 0, world_size[2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* 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<coll2d::Polygon*> (entity_physics->mShape)->setVertice (0, vector3d (-0.5, 0., 0.5));
|
|
static_cast<coll2d::Polygon*> (entity_physics->mShape)->setVertice (1, vector3d (0.5, 0., 0.5));
|
|
static_cast<coll2d::Polygon*> (entity_physics->mShape)->setVertice (2, vector3d (0.5, 0., -0.5));
|
|
static_cast<coll2d::Polygon*> (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;
|
|
}
|
|
|
|
}
|
|
|
|
|