//new collision replacemnet #include #include #include "../common/mathlib.h" #include "../common/const.h" #include "../common/com_model.h" #include "../pm_shared/pm_defs.h" #include "../common/vector_util.h" #include "AvHHulls.h" #include "../types.h" #include "../util/MathUtil.h" #include "CollisionChecker.h" using std::auto_ptr; //----------------------------------------------------------------------------- // PlaneTest classes - used by CollisionChecker for testing against hulls //----------------------------------------------------------------------------- class CollisionTest { public: virtual ~CollisionTest(void) {} virtual int GetPlanarIntersectionType(const mplane_t* plane) const = 0; virtual bool GetAABBIntersection(const nspoint_t& mins, const nspoint_t& maxs) const = 0; virtual auto_ptr RotateAndShift(const nspoint_t& forward, const nspoint_t& up, const nspoint_t& right, const nspoint_t& origin) const = 0; virtual auto_ptr Shift(const nspoint_t& origin) const = 0; }; class PointCollisionTest : public CollisionTest { public: PointCollisionTest(const nspoint_t& point) { SetPoint(point); } void SetPoint(const nspoint_t& point) { VectorCopy(point,point_); } int GetPlanarIntersectionType(const mplane_t* plane) const { float d; if(plane->type < 3) // first three plane types are axial by index { d = point_[plane->type]*plane->normal[plane->type] - plane->dist; } else { d = DotProduct(plane->normal,point_) - plane->dist; } return ((d < 0) ? CollisionChecker::INTERSECTION_RELATION_BACK : CollisionChecker::INTERSECTION_RELATION_FRONT); } bool GetAABBIntersection(const nspoint_t& mins, const nspoint_t& maxs) const { return ( (point_[0] >= mins[0]) && (point_[0] <= maxs[0]) && (point_[1] >= mins[1]) && (point_[1] <= maxs[1]) && (point_[2] >= mins[2]) && (point_[2] <= maxs[1]) ); } auto_ptr RotateAndShift(const nspoint_t& forward, const nspoint_t& right, const nspoint_t& up, const nspoint_t& origin) const { return Shift(origin); } auto_ptr Shift(const nspoint_t& origin) const { nspoint_t new_origin; VectorAdd(origin,point_,new_origin); return auto_ptr(new PointCollisionTest(new_origin)); } private: nspoint_t point_; }; //----------------------------------------------------------------------------- class CylinderCollisionTest : public CollisionTest { public: CylinderCollisionTest(const nspoint_t& up, const nspoint_t& base, const float radius, const float height) : point_test_(base) { VectorCopy(up,up_); VectorNormalize(up_); VectorCopy(base,base_); radius_ = radius; height_ = height; } int GetPlanarIntersectionType(const mplane_t* plane) const { nspoint_t in_plane,out_of_plane,point_to_check; CrossProduct(up_,plane->normal,in_plane); CrossProduct(up_,in_plane,out_of_plane); //perp to up and pointing directly out of plane VectorNormalize(out_of_plane); VectorMA(base_,radius_,out_of_plane,point_to_check); point_test_.SetPoint(point_to_check); int intersect = point_test_.GetPlanarIntersectionType(plane); VectorMA(point_to_check,height_,up_,point_to_check); point_test_.SetPoint(point_to_check); intersect |= point_test_.GetPlanarIntersectionType(plane); if(intersect != CollisionChecker::INTERSECTION_RELATION_BOTH) { VectorMA(base_,-radius_,out_of_plane,point_to_check); point_test_.SetPoint(point_to_check); intersect |= point_test_.GetPlanarIntersectionType(plane); } if(intersect != CollisionChecker::INTERSECTION_RELATION_BOTH) { VectorMA(point_to_check,height_,up_,point_to_check); point_test_.SetPoint(point_to_check); intersect |= point_test_.GetPlanarIntersectionType(plane); } return intersect; } bool GetAABBIntersection(const nspoint_t& mins, const nspoint_t& maxs) const { //special case of unrotated cylinder if(up_[2] == 1.0f) { if( maxs[2] < base_[2] || mins[2] > base_[2] + height_ || maxs[0] < base_[0] - radius_ || mins[0] > base_[0] + radius_ || maxs[1] < base_[1] - radius_ || maxs[1] > base_[1] + radius_ ) { return false; } //TODO: tighten definition to use overlap of circle instead of box with circle inscribed return true; } //TODO: handle general case by turning bbox into hull and testing against that hull ASSERT(0 && "Need to handle case of rotated cylinder & AABB collision"); return false; } auto_ptr RotateAndShift(const nspoint_t& forward, const nspoint_t& right, const nspoint_t& up, const nspoint_t& origin) const { nspoint_t new_up, new_base; TransformVector(up_,forward,right,up,new_up); VectorAdd(base_,origin,new_base); return auto_ptr(new CylinderCollisionTest(new_up,new_base,radius_,height_)); } auto_ptr Shift(const nspoint_t& origin) const { nspoint_t new_base; VectorAdd(base_,origin,new_base); return auto_ptr(new CylinderCollisionTest(up_,new_base,radius_,height_)); } private: mutable PointCollisionTest point_test_; nspoint_t up_; nspoint_t base_; float radius_; float height_; }; //----------------------------------------------------------------------------- //TODO: determine if there's a faster way than scanning all 8 corners class OBBCollisionTest : public CollisionTest { public: OBBCollisionTest(const OBBox& box) : point_test_(box.mOrigin) { box_ = box; nspoint_t offset; //calculate eight corners of the box given input angles: for(int index = 0; index < 8; ++index) { offset[0] = box.mExtents[0] * box.mAxis[0][0]; offset[0] += box.mExtents[1] * box.mAxis[1][0]; offset[0] += box.mExtents[2] * box.mAxis[2][0]; if(index % 2) //every other item { offset[0] *= -1; } offset[1] = box.mExtents[0] * box.mAxis[0][1]; offset[1] += box.mExtents[1] * box.mAxis[1][1]; offset[1] += box.mExtents[2] * box.mAxis[2][1]; if((index / 2) % 2) //every other pair of items { offset[1] *= -1; } offset[2] = box.mExtents[0] * box.mAxis[0][2]; offset[2] += box.mExtents[1] * box.mAxis[1][2]; offset[2] += box.mExtents[2] * box.mAxis[2][2]; if(index < 4) //4 false, then 4 true { offset[2] *= -1; } VectorAdd(box.mOrigin,offset,corners[index]); } } int GetPlanarIntersectionType(const mplane_t* plane) const { int intersection_type = CollisionChecker::INTERSECTION_RELATION_NONE; for(int index = 0; index < 8; ++index) { point_test_.SetPoint(corners[index]); intersection_type |= point_test_.GetPlanarIntersectionType(plane); if(intersection_type == CollisionChecker::INTERSECTION_RELATION_BOTH) { break; } } return intersection_type; } bool GetAABBIntersection(const nspoint_t& mins, const nspoint_t& maxs) const { bool intersection = false; for(int index = 0; index < 8; ++index) { point_test_.SetPoint(corners[index]); intersection = point_test_.GetAABBIntersection(mins,maxs); if(intersection) { break; } } return intersection; } auto_ptr RotateAndShift(const nspoint_t& forward, const nspoint_t& right, const nspoint_t& up, const nspoint_t& origin) const { //make this an OBB by rotation around the ent angles - not a cheap operation. OBBox box = box_; VectorAdd(box_.mOrigin,origin,box.mOrigin); TransformVector(box_.mAxis[0],forward,right,up,box.mAxis[0]); TransformVector(box_.mAxis[1],forward,right,up,box.mAxis[1]); TransformVector(box_.mAxis[2],forward,right,up,box.mAxis[2]); //rotate axes! return auto_ptr(new OBBCollisionTest(box)); } auto_ptr Shift(const nspoint_t& origin) const { //make this an OBB by rotation around the ent angles - not a cheap operation. OBBox box = box_; VectorAdd(box_.mOrigin,origin,box.mOrigin); return auto_ptr(new OBBCollisionTest(box)); } private: OBBox box_; nspoint_t corners[8]; mutable PointCollisionTest point_test_; }; //----------------------------------------------------------------------------- class AABBCollisionTest : public CollisionTest { public: AABBCollisionTest(const nspoint_t& mins, const nspoint_t& maxs) : point_test_(mins) { VectorCopy(maxs,maxs_); VectorCopy(mins,mins_); } int GetPlanarIntersectionType(const mplane_t* plane) const { nspoint_t orientedMins = { plane->normal[0] < 0 ? maxs_[0] : mins_[0], plane->normal[1] < 0 ? maxs_[1] : mins_[1], plane->normal[2] < 0 ? maxs_[2] : mins_[2] }; point_test_.SetPoint(orientedMins); int intersect_type = point_test_.GetPlanarIntersectionType(plane); nspoint_t orientedMaxes = { plane->normal[0] < 0 ? mins_[0] : maxs_[0], plane->normal[1] < 0 ? mins_[1] : maxs_[1], plane->normal[2] < 0 ? mins_[2] : maxs_[2] }; point_test_.SetPoint(orientedMaxes); intersect_type |= point_test_.GetPlanarIntersectionType(plane); return intersect_type; } bool GetAABBIntersection(const nspoint_t& mins, const nspoint_t& maxs) const { return ( (maxs_[0] >= mins[0]) && (mins_[0] <= maxs[0]) && (maxs_[1] >= mins[1]) && (mins_[1] <= maxs[1]) && (maxs_[2] >= mins[2]) && (mins_[2] <= maxs[2]) ); } auto_ptr RotateAndShift(const nspoint_t& forward, const nspoint_t& right, const nspoint_t& up, const nspoint_t& origin) const { //make this an OBB by rotation around the ent angles - not a cheap operation. OBBox box; //set up origin = ((mins + maxs) / 2) - ent origin VectorCopy(mins_,box.mOrigin); VectorAdd(maxs_,box.mOrigin,box.mOrigin); VectorScale(box.mOrigin,0.5f,box.mOrigin); VectorSubtract(box.mOrigin,origin,box.mOrigin); //set up extents = ((maxs - mins) / 2) VectorCopy(maxs_,box.mExtents); VectorSubtract(box.mExtents,mins_,box.mExtents); VectorScale(box.mExtents,0.5f,box.mExtents); //set up axis = axis defined by ent angles nspoint_t axis[3] = {{1,0,0},{0,1,0},{0,0,1}}; VectorCopy(forward,box.mAxis[0]); VectorCopy(up,box.mAxis[1]); VectorCopy(right,box.mAxis[2]); return auto_ptr(new OBBCollisionTest(box)); } auto_ptr Shift(const nspoint_t& origin) const { nspoint_t new_mins, new_maxs; VectorAdd(origin,mins_,new_mins); VectorAdd(origin,maxs_,new_maxs); return auto_ptr(new AABBCollisionTest(new_mins,new_maxs)); } private: nspoint_t mins_, maxs_; mutable PointCollisionTest point_test_; }; //----------------------------------------------------------------------------- // CollisionChecker class - making a fresh start on collision checking code // that runs properly on both the server and client; requires that pmove is // filled to function -- won't function fully for initial frames // // TODO: write quick functions for when pmove isn't available // KGP - 4/25/04 //----------------------------------------------------------------------------- const int CollisionChecker::NO_ENTITY = -1; const int CollisionChecker::WORLD_ENTITY = 0; const bool CollisionChecker::IGNORE_PLAYERS_FALSE = false; const bool CollisionChecker::IGNORE_PLAYERS_TRUE = true; const int CollisionChecker::HULL_TYPE_BBOX = 1; const int CollisionChecker::HULL_TYPE_BSP = 2; const int CollisionChecker::HULL_TYPE_ALL = 3; const int CollisionChecker::INTERSECTION_RELATION_NONE = 0; const int CollisionChecker::INTERSECTION_RELATION_FRONT = 1; const int CollisionChecker::INTERSECTION_RELATION_BACK = 2; const int CollisionChecker::INTERSECTION_RELATION_BOTH = 3; CollisionChecker::CollisionChecker(const playermove_t* pmove) : pmove_(pmove) { SetHullNumber(0); SetHullTypeMask(HULL_TYPE_ALL); SetIgnorePlayers(false); SetIgnoreEntityClass(IGNORE_NONE); ClearEntityToIgnore(); } CollisionChecker::CollisionChecker(const playermove_t* pmove, int ns_hull_number, int hull_type_mask, bool ignore_players, int ignore_entity_class, int entity_index_to_ignore) : pmove_(pmove) { SetHullNumber(ns_hull_number); SetHullTypeMask(hull_type_mask); SetIgnorePlayers(ignore_players); SetIgnoreEntityClass(ignore_entity_class); SetEntityToIgnore(entity_index_to_ignore); } //------------------------------------------------------------------- // Configuration //------------------------------------------------------------------- void CollisionChecker::SetHullNumber(int ns_hull_number) { switch(ns_hull_number) { case kHLPointHullIndex: valve_hull_number_ = 0; break; case kHLCrouchHullIndex: valve_hull_number_ = 1; break; case kHLStandHullIndex: valve_hull_number_ = 2; break; case kNSHugeHullIndex: valve_hull_number_ = 3; break; default: ASSERT(0 && "unknown hull index passed to CollisionChecker::SetHullNumber"); valve_hull_number_ = 0; break; } } //------------------------------------------------------------------- void CollisionChecker::SetHullTypeMask(int hull_type_mask) { hull_type_mask_ = hull_type_mask; } //------------------------------------------------------------------- void CollisionChecker::SetEntityToIgnore(int entity_index_to_ignore) { ASSERT(entity_index_to_ignore > 0 || entity_index_to_ignore == NO_ENTITY); entity_index_to_ignore_ = entity_index_to_ignore; } //------------------------------------------------------------------- void CollisionChecker::ClearEntityToIgnore(void) { entity_index_to_ignore_ = NO_ENTITY; } //------------------------------------------------------------------- void CollisionChecker::SetIgnorePlayers(bool ignore_players) { ignore_players_ = ignore_players; } //------------------------------------------------------------------- void CollisionChecker::SetIgnoreEntityClass(int ignore_entity_class) { ignore_entity_class_ = ignore_entity_class; } //------------------------------------------------------------------- // Public point routines //------------------------------------------------------------------- int CollisionChecker::GetContentsAtPoint(const nspoint_t& point) const { PointCollisionTest tmp = PointCollisionTest(point); return GetContents(&tmp); } //------------------------------------------------------------------- int CollisionChecker::GetWorldContentsAtPoint(const nspoint_t& point) const { return GetSingleEntityContentsAtPoint(point, WORLD_ENTITY); } //------------------------------------------------------------------- int CollisionChecker::GetAllEntityContentsAtPoint(const nspoint_t& point) const { PointCollisionTest tmp = PointCollisionTest(point); return GetAllEntityContents(&tmp); } //------------------------------------------------------------------- int CollisionChecker::GetSingleEntityContentsAtPoint(const nspoint_t& point, int entity_index) const { PointCollisionTest tmp = PointCollisionTest(point); return GetSingleEntityContents(&tmp, entity_index); } //------------------------------------------------------------------- // Public Cylinder routines //------------------------------------------------------------------- const static nspoint_t CYLINDER_UP_DEFAULT = {0,0,1.0f}; int CollisionChecker::GetContentsInCylinder(const nspoint_t& base, float radius, float height) const { CylinderCollisionTest tmp = CylinderCollisionTest(CYLINDER_UP_DEFAULT,base,radius,height); return GetContents(&tmp); } //------------------------------------------------------------------- int CollisionChecker::GetWorldContentsInCylinder(const nspoint_t& base, float radius, float height) const { CylinderCollisionTest tmp = CylinderCollisionTest(CYLINDER_UP_DEFAULT,base,radius,height); return GetSingleEntityContents(&tmp, WORLD_ENTITY); } //------------------------------------------------------------------- int CollisionChecker::GetAllEntityContentsInCylinder(const nspoint_t& base, float radius, float height) const { CylinderCollisionTest tmp = CylinderCollisionTest(CYLINDER_UP_DEFAULT,base,radius,height); return GetAllEntityContents(&tmp); } //------------------------------------------------------------------- int CollisionChecker::GetSingleEntityContentsInCylinder(const nspoint_t& base, float radius, float height, int entity_index) const { CylinderCollisionTest tmp = CylinderCollisionTest(CYLINDER_UP_DEFAULT,base,radius,height); return GetSingleEntityContents(&tmp,entity_index); } //------------------------------------------------------------------- // Public AABB routines //------------------------------------------------------------------- int CollisionChecker::GetContentsInAABB(const nspoint_t& mins, const nspoint_t& maxs) const { AABBCollisionTest tmp = AABBCollisionTest(mins,maxs); return GetContents(&tmp); } //------------------------------------------------------------------- int CollisionChecker::GetWorldContentsInAABB(const nspoint_t& mins, const nspoint_t& maxs) const { AABBCollisionTest tmp = AABBCollisionTest(mins,maxs); return GetSingleEntityContents(&tmp, WORLD_ENTITY); } //------------------------------------------------------------------- int CollisionChecker::GetAllEntityContentsInAABB(const nspoint_t& mins, const nspoint_t& maxs) const { AABBCollisionTest tmp = AABBCollisionTest(mins,maxs); return GetAllEntityContents(&tmp); } //------------------------------------------------------------------- int CollisionChecker::GetSingleEntityContentsInAABB(const nspoint_t& mins, const nspoint_t& maxs, int entity_index) const { AABBCollisionTest tmp = AABBCollisionTest(mins,maxs); return GetSingleEntityContents(&tmp,entity_index); } //------------------------------------------------------------------- // Private utility routines //------------------------------------------------------------------- int CollisionChecker::CombineContents(const int old_contents, const int new_contents) const { if(new_contents == CONTENTS_EMPTY) { return old_contents; } if(old_contents == CONTENT_EMPTY) { return new_contents; } return max(old_contents,new_contents); } //------------------------------------------------------------------- // Private content check routines //------------------------------------------------------------------- int CollisionChecker::GetContents(const CollisionTest* test) const { int total_contents = GetSingleEntityContents(test,WORLD_ENTITY); if(total_contents != CONTENTS_SOLID) { total_contents = CombineContents(total_contents,GetAllEntityContents(test)); } return total_contents; } //------------------------------------------------------------------- int CollisionChecker::GetAllEntityContents(const CollisionTest* test) const { int total_contents = CONTENTS_EMPTY; UpdateNumEntities(); for(int counter = WORLD_ENTITY+1; counter < entity_count_; ++counter) { total_contents = CombineContents(total_contents,GetSingleEntityContents(test,counter)); if(total_contents == CONTENTS_SOLID) { break; } } return total_contents; } //------------------------------------------------------------------- int CollisionChecker::GetSingleEntityContents(const CollisionTest* test, int entity_index) const { int total_contents = CONTENTS_EMPTY; const physent_t* ent = GetPhysentForIndex(entity_index); if(ent && ent->info != entity_index_to_ignore_ && (!ignore_players_ || ent->info == WORLD_ENTITY || ent->info > 32)) { if(ent->solid == SOLID_BBOX) { nspoint_t bbox_mins, bbox_maxs; VectorSubtract(ent->mins,ent->origin,bbox_mins); VectorSubtract(ent->maxs,ent->origin,bbox_maxs); if(valve_hull_number_) //adjust for input hull { const hull_t* hull = &(&pmove_->physents[WORLD_ENTITY])->model->hulls[valve_hull_number_]; VectorAdd(bbox_mins,hull->clip_mins,bbox_mins); VectorAdd(bbox_mins,hull->clip_maxs,bbox_maxs); } if(test->GetAABBIntersection(bbox_mins,bbox_maxs)) { total_contents = CONTENTS_SOLID; } } else if(ent->model && ent->model->type == mod_brush) { const hull_t* hull = &ent->model->hulls[valve_hull_number_]; if(ent->angles[0] || ent->angles[1] || ent->angles[2]) { float axes[3][3]; AngleVectors(ent->angles,axes[0],axes[1],axes[2]); auto_ptr rotated_test = test->RotateAndShift(axes[0],axes[1],axes[2],ent->origin); total_contents = GetHullContent(hull,hull->firstclipnode,rotated_test.get()); } else { auto_ptr shifted_test = test->Shift(ent->origin); total_contents = GetHullContent(hull,hull->firstclipnode,shifted_test.get()); } } } return total_contents; } //------------------------------------------------------------------- //DFS search for solid hull leaf with passed in intersection test and short circuit on solid leaf found int CollisionChecker::GetHullContent(const hull_t* hull, int current_node, const CollisionTest* test) const { ASSERT(current_node >= hull->firstclipnode); ASSERT(current_node <= hull->lastclipnode); int total_contents = CONTENTS_EMPTY; if(current_node < 0) { total_contents = current_node; } else { dclipnode_t *node = &hull->clipnodes[current_node]; mplane_t *plane; int intersection; while(true) { ASSERT(total_contents < 0); // 0 + is a node number and means we've had a bad assignment //short circuit on solid found if(total_contents == CONTENTS_SOLID) { this->intersected_nodes_.clear(); break; } plane = &hull->planes[node->planenum]; intersection = test->GetPlanarIntersectionType(&hull->planes[node->planenum]); switch(intersection) { case INTERSECTION_RELATION_BOTH: if(node->children[0] > CONTENTS_EMPTY) //non-leaf { if(node->children[1] > CONTENTS_EMPTY) //non-leaf { this->intersected_nodes_.push_back(node->children[0]); //store one path for later node = &hull->clipnodes[node->children[1]]; //follow other path continue; } total_contents = CombineContents(total_contents,node->children[1]); node = &hull->clipnodes[node->children[0]]; continue; } else { total_contents = CombineContents(total_contents,node->children[0]); if(node->children[1] > CONTENTS_EMPTY) //non-leaf { node = &hull->clipnodes[node->children[1]]; continue; } total_contents = CombineContents(total_contents,node->children[1]); } break; case INTERSECTION_RELATION_FRONT: if(node->children[0] > CONTENTS_EMPTY) { node = &hull->clipnodes[node->children[0]]; continue; } total_contents = CombineContents(total_contents,node->children[0]); break; case INTERSECTION_RELATION_BACK: if(node->children[1] > CONTENTS_EMPTY) { node = &hull->clipnodes[node->children[1]]; continue; } total_contents = CombineContents(total_contents,node->children[1]); break; } //check for other subtrees to process if(this->intersected_nodes_.empty()) { break; } node = &hull->clipnodes[this->intersected_nodes_.back()]; this->intersected_nodes_.pop_back(); } } return total_contents; } //------------------------------------------------------------------------------ // Client/server specific code :( ... client version, server version in own file //------------------------------------------------------------------------------ #ifdef AVH_CLIENT const int CollisionChecker::IGNORE_NONE = 0; const int CollisionChecker::IGNORE_INVISIBLE = 1; const int CollisionChecker::IGNORE_INTANGIBLE = 2; void CollisionChecker::UpdateNumEntities() const { switch(ignore_entity_class_) { case IGNORE_NONE: case IGNORE_INVISIBLE: entity_count_ = pmove_ ? pmove_->numvisent : 0; break; case IGNORE_INTANGIBLE: entity_count_ = pmove_ ? pmove_->numphysent : 0; break; default: ASSERT(0 && "Unknown ignore entity class"); }; } const physent_t* CollisionChecker::GetPhysentForIndex(int entity_index) const { switch(ignore_entity_class_) { case IGNORE_NONE: case IGNORE_INVISIBLE: return pmove_ ? &pmove_->visents[entity_index] : (physent_t*)NULL; case IGNORE_INTANGIBLE: return pmove_ ? &pmove_->physents[entity_index] : (physent_t*)NULL; } ASSERT(0 && "Unknown ignore entity class"); return NULL; } #endif