350 lines
11 KiB
C++
350 lines
11 KiB
C++
|
/*
|
||
|
** Command & Conquer Renegade(tm)
|
||
|
** Copyright 2025 Electronic Arts Inc.
|
||
|
**
|
||
|
** This program is free software: you can redistribute it and/or modify
|
||
|
** it under the terms of the GNU General Public License as published by
|
||
|
** the Free Software Foundation, either version 3 of the License, or
|
||
|
** (at your option) any later version.
|
||
|
**
|
||
|
** This program 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 General Public License for more details.
|
||
|
**
|
||
|
** You should have received a copy of the GNU General Public License
|
||
|
** along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
|
||
|
***********************************************************************************************
|
||
|
* *
|
||
|
* Project Name : WWPhys *
|
||
|
* *
|
||
|
* $Archive:: /Commando/Code/wwphys/octbox.cpp $*
|
||
|
* *
|
||
|
* Original Author:: Greg Hjelstrom *
|
||
|
* *
|
||
|
* $Author:: Greg_h $*
|
||
|
* *
|
||
|
* $Modtime:: 11/21/01 2:54p $*
|
||
|
* *
|
||
|
* $Revision:: 20 $*
|
||
|
* *
|
||
|
*---------------------------------------------------------------------------------------------*
|
||
|
* Functions: *
|
||
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||
|
|
||
|
#include "octbox.h"
|
||
|
#include "pscene.h"
|
||
|
#include "physcoltest.h"
|
||
|
#include "physinttest.h"
|
||
|
#include "physcon.h"
|
||
|
|
||
|
#define SHOW_CONTACT_DETECTORS 0
|
||
|
|
||
|
const float DEFAULT_STIFFNESS = 5.0f;
|
||
|
const float DEFAULT_DAMPING = 0.1f;
|
||
|
const float DEFAULT_THICKNESS = 0.1f;
|
||
|
|
||
|
const float CONTACT_GRAVITY_MULTIPLIER = 2.0f; // maximum contact force is multiplier * gravity.
|
||
|
const float CONTACT_DAMPING_FACTOR = 0.33f; // ratio of damping / (critical damping) (less than zero is underdamped)
|
||
|
|
||
|
|
||
|
OctBoxClass::OctBoxClass
|
||
|
(
|
||
|
RigidBodyClass & parent,
|
||
|
const OBBoxClass & box
|
||
|
) :
|
||
|
Parent(parent),
|
||
|
InnerBox(box),
|
||
|
Thickness(DEFAULT_THICKNESS),
|
||
|
Stiffness(DEFAULT_STIFFNESS),
|
||
|
Damping(DEFAULT_DAMPING)
|
||
|
{
|
||
|
Set_Thickness(Thickness);
|
||
|
}
|
||
|
|
||
|
|
||
|
OctBoxClass::~OctBoxClass(void)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
void OctBoxClass::Set_Thickness(float thickness)
|
||
|
{
|
||
|
Thickness = thickness;
|
||
|
|
||
|
// Outer corner of the "octant boxes" should be at the corner of the box
|
||
|
// Inner corner of the "octant boxes" should be overlapping the neighbor boxes by 'thickness'
|
||
|
Vector3 octant_max = InnerBox.Extent;
|
||
|
Vector3 octant_min(-Thickness,-Thickness,-Thickness);
|
||
|
|
||
|
OctantExtent = (octant_max - octant_min) / 2.0f;
|
||
|
OctantCenter = (octant_max + octant_min) / 2.0f;
|
||
|
}
|
||
|
|
||
|
void OctBoxClass::Update_Contact_Parameters(void)
|
||
|
{
|
||
|
float mass = Parent.Get_Mass();
|
||
|
|
||
|
// Make the contact force have a maximum of MULTIPLIER * gravity
|
||
|
Stiffness = mass * WWMath::Fabs(PhysicsConstants::GravityAcceleration.Z);
|
||
|
Stiffness *= CONTACT_GRAVITY_MULTIPLIER;
|
||
|
|
||
|
// Compute critical damping, achieve equilibrium in minimum time.
|
||
|
Damping = 2.0f * mass * WWMath::Sqrt(Stiffness / mass);
|
||
|
|
||
|
// Critical damping seems to be too stiff so reduce it
|
||
|
Damping *= CONTACT_DAMPING_FACTOR;
|
||
|
}
|
||
|
|
||
|
void OctBoxClass::Get_Outer_Bounds(AABoxClass * set_bounds)
|
||
|
{
|
||
|
WWASSERT(set_bounds != NULL);
|
||
|
OBBoxClass wrld_outer_box;
|
||
|
wrld_outer_box = WrldInnerBox;
|
||
|
wrld_outer_box.Extent += Vector3(Thickness,Thickness,Thickness);
|
||
|
|
||
|
set_bounds->Center = wrld_outer_box.Center;
|
||
|
wrld_outer_box.Compute_Axis_Aligned_Extent(&(set_bounds->Extent));
|
||
|
}
|
||
|
|
||
|
bool OctBoxClass::Is_Intersecting(NonRefPhysListClass * result_list,bool check_static_objs,bool check_dyn_objs)
|
||
|
{
|
||
|
PhysicsSceneClass * the_scene = PhysicsSceneClass::Get_Instance();
|
||
|
WWASSERT(the_scene != NULL);
|
||
|
|
||
|
/*
|
||
|
** Test inner box for intersection
|
||
|
*/
|
||
|
PhysOBBoxIntersectionTestClass test(WrldInnerBox,
|
||
|
Parent.Get_Collision_Group(),
|
||
|
COLLISION_TYPE_PHYSICAL | COLLISION_TYPE_VEHICLE,
|
||
|
result_list);
|
||
|
|
||
|
test.CheckStaticObjs = check_static_objs;
|
||
|
test.CheckDynamicObjs = check_dyn_objs;
|
||
|
|
||
|
Parent.Inc_Ignore_Counter ();
|
||
|
bool intersect = PhysicsSceneClass::Get_Instance ()->Intersection_Test(test);
|
||
|
Parent.Dec_Ignore_Counter ();
|
||
|
return intersect;
|
||
|
}
|
||
|
|
||
|
bool OctBoxClass::Is_In_Contact_Zone(void)
|
||
|
{
|
||
|
PhysicsSceneClass * the_scene = PhysicsSceneClass::Get_Instance();
|
||
|
WWASSERT(the_scene != NULL);
|
||
|
|
||
|
/*
|
||
|
** Test outer box for intersection
|
||
|
*/
|
||
|
OBBoxClass wrld_outer_box = WrldInnerBox;
|
||
|
wrld_outer_box.Extent += Vector3(Thickness,Thickness,Thickness);
|
||
|
|
||
|
int colgroup = Parent.Get_Collision_Group();
|
||
|
int coltype = COLLISION_TYPE_PHYSICAL | COLLISION_TYPE_VEHICLE;
|
||
|
return the_scene->Intersection_Test(wrld_outer_box,colgroup,coltype,true);
|
||
|
}
|
||
|
|
||
|
|
||
|
OctBoxClass::CollisionResult
|
||
|
OctBoxClass::Compute_Contacts(bool lock_to_centroids)
|
||
|
{
|
||
|
Parent.Inc_Ignore_Counter();
|
||
|
CollisionResult result = Internal_Compute_Contacts(lock_to_centroids);
|
||
|
Parent.Dec_Ignore_Counter();
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
OctBoxClass::CollisionResult
|
||
|
OctBoxClass::Internal_Compute_Contacts(bool lock_to_centroids)
|
||
|
{
|
||
|
/*
|
||
|
** Algorithm:
|
||
|
** Quick Rejection:
|
||
|
** - Try to quick reject the entire collision by checking the outer box
|
||
|
** - Check if we're going to have to search for TOC by checking the inner box
|
||
|
** Find Contacts:
|
||
|
** - For each octant (total of 8)
|
||
|
** - Sweep the octant along the diagonal and record the contact point (if any)
|
||
|
*/
|
||
|
PhysicsSceneClass * the_scene = PhysicsSceneClass::Get_Instance();
|
||
|
WWASSERT(the_scene != NULL);
|
||
|
Reset_Contacts();
|
||
|
|
||
|
/*
|
||
|
** compute the outer box in WS
|
||
|
*/
|
||
|
OBBoxClass wrld_outer_box;
|
||
|
wrld_outer_box = WrldInnerBox;
|
||
|
wrld_outer_box.Extent += Vector3(Thickness,Thickness,Thickness);
|
||
|
|
||
|
/*
|
||
|
** Check for quick-rejection (intersection or completely separated)
|
||
|
*/
|
||
|
int colgroup = Parent.Get_Collision_Group();
|
||
|
int coltype = COLLISION_TYPE_PHYSICAL | COLLISION_TYPE_VEHICLE;
|
||
|
|
||
|
if (!the_scene->Intersection_Test(wrld_outer_box,colgroup,coltype,true)) {
|
||
|
//Parent.Add_Debug_OBBox(wrld_outer_box,Vector3(0,1,0));
|
||
|
return RESULT_NO_COLLISION;
|
||
|
}
|
||
|
|
||
|
if (the_scene->Intersection_Test(WrldInnerBox,colgroup,coltype,true)) {
|
||
|
//Parent.Add_Debug_OBBox(wrld_outer_box,Vector3(1,0,0));
|
||
|
return RESULT_INTERSECTION;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** Check the octants for contact
|
||
|
*/
|
||
|
for (int i=0; i<8; i++) {
|
||
|
Compute_Octant_Contact(i,lock_to_centroids);
|
||
|
}
|
||
|
|
||
|
return RESULT_CONTACT;
|
||
|
}
|
||
|
|
||
|
|
||
|
void OctBoxClass::Compute_Octant_Contact(int oi,bool lock_to_centroids)
|
||
|
{
|
||
|
static Vector3 _octant_offset[8] =
|
||
|
{
|
||
|
Vector3( 1, 1, 1),
|
||
|
Vector3(-1, 1, 1),
|
||
|
Vector3(-1,-1, 1),
|
||
|
Vector3( 1,-1, 1),
|
||
|
Vector3( 1, 1,-1),
|
||
|
Vector3(-1, 1,-1),
|
||
|
Vector3(-1,-1,-1),
|
||
|
Vector3( 1,-1,-1),
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
** Compute the corner contact "hair" which doubles as the
|
||
|
** move vector for the octant box.
|
||
|
*/
|
||
|
CastResultStruct corner_result;
|
||
|
corner_result.ComputeContactPoint = true;
|
||
|
|
||
|
Vector3 corner = _octant_offset[oi];
|
||
|
corner.X *= WrldInnerBox.Extent.X;
|
||
|
corner.Y *= WrldInnerBox.Extent.Y;
|
||
|
corner.Z *= WrldInnerBox.Extent.Z;
|
||
|
Matrix3::Rotate_Vector(WrldInnerBox.Basis,corner,&corner);
|
||
|
corner += WrldInnerBox.Center;
|
||
|
|
||
|
Vector3 corner_move = Thickness * _octant_offset[oi];
|
||
|
Matrix3::Rotate_Vector(WrldInnerBox.Basis,corner_move,&corner_move);
|
||
|
|
||
|
/*
|
||
|
** Collision detect for the corner line-segment
|
||
|
*/
|
||
|
LineSegClass ray(corner,corner + corner_move);
|
||
|
WWASSERT(corner_move.Length2() <= ((3*Thickness*Thickness) + 0.01f));
|
||
|
WWASSERT(ray.Get_DP().Length2() <= ((3*Thickness*Thickness) + 0.01f));
|
||
|
|
||
|
PhysRayCollisionTestClass raytest( ray,
|
||
|
&corner_result,
|
||
|
Parent.Get_Collision_Group(),
|
||
|
COLLISION_TYPE_PHYSICAL | COLLISION_TYPE_VEHICLE);
|
||
|
PhysicsSceneClass::Get_Instance()->Cast_Ray(raytest,true);
|
||
|
|
||
|
#ifdef WWDEBUG
|
||
|
#if SHOW_CONTACT_DETECTORS
|
||
|
Parent.Add_Debug_Vector(corner,corner_move,Vector3(0,1,0));
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
** Compute the contact for this octant-box.
|
||
|
*/
|
||
|
OBBoxClass octbox = WrldInnerBox;
|
||
|
octbox.Extent = OctantExtent;
|
||
|
|
||
|
Vector3 dc = _octant_offset[oi];
|
||
|
dc.X *= OctantCenter.X;
|
||
|
dc.Y *= OctantCenter.Y;
|
||
|
dc.Z *= OctantCenter.Z;
|
||
|
Matrix3::Rotate_Vector(WrldInnerBox.Basis,dc,&dc);
|
||
|
octbox.Center += dc;
|
||
|
|
||
|
/*
|
||
|
** Collision detect with this octant box
|
||
|
*/
|
||
|
CastResultStruct octant_result;
|
||
|
octant_result.ComputeContactPoint = true;
|
||
|
PhysOBBoxCollisionTestClass boxtest( octbox,
|
||
|
corner_move,
|
||
|
&octant_result,
|
||
|
Parent.Get_Collision_Group(),
|
||
|
COLLISION_TYPE_PHYSICAL | COLLISION_TYPE_VEHICLE);
|
||
|
PhysicsSceneClass::Get_Instance()->Cast_OBBox(boxtest,true);
|
||
|
|
||
|
/*
|
||
|
** Wake up any vehicle in contact with us
|
||
|
*/
|
||
|
if ((boxtest.CollidedPhysObj != NULL) && (boxtest.CollidedPhysObj->As_RigidBodyClass() != NULL)) {
|
||
|
boxtest.CollidedPhysObj->Force_Awake();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** Display the octant box
|
||
|
*/
|
||
|
#ifdef WWDEBUG
|
||
|
#if SHOW_CONTACT_DETECTORS
|
||
|
static Vector3 _oct_colors[8] = {
|
||
|
Vector3(1,0,0),Vector3(0,1,0),Vector3(0,0,1),Vector3(1,1,1),
|
||
|
Vector3(1,1,1),Vector3(0,0,1),Vector3(0,1,0),Vector3(1,0,0)
|
||
|
};
|
||
|
|
||
|
octbox.Center += octant_result.Fraction * corner_move;
|
||
|
//Parent.Add_Debug_OBBox(octbox,_oct_colors[oi]);
|
||
|
//Parent.Add_Debug_Vector(result.Point,result.Normal,Vector3(0,1,0));
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
** Now, decide which (if any) contact to use. We prefer the
|
||
|
** corners but have to use the box's contact if it is significantly
|
||
|
** closer. If neither hits anything, just bail out of this routine.
|
||
|
*/
|
||
|
if ((corner_result.Fraction >= 1.0f) && (octant_result.Fraction >= 1.0f)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** If the corner is within 'CORNER_BIAS' of the result of the octant
|
||
|
** then use its result
|
||
|
*/
|
||
|
const float CORNER_BIAS = 0.25f;
|
||
|
if ((corner_result.Fraction < 1.0f) && (corner_result.Fraction - CORNER_BIAS < octant_result.Fraction)) {
|
||
|
|
||
|
if (corner_result.Normal.Length2() > 0.0f) { // the normal will be 0,0,0 when we hit a backside
|
||
|
Add_Contact(corner_result,raytest.CollidedPhysObj);
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
|
||
|
if ((octant_result.Fraction < 1.0f) && (!octant_result.StartBad)) {
|
||
|
if (octant_result.Normal.Length2() > 0.0f) { // the normal will be 0,0,0 when we hit a backside
|
||
|
Add_Contact(octant_result,boxtest.CollidedPhysObj);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (corner_result.Fraction < 1.0f) {
|
||
|
// also add the corner contact!
|
||
|
if (corner_result.Normal.Length2() > 0.0f) { // the normal will be 0,0,0 when we hit a backside
|
||
|
Add_Contact(corner_result,raytest.CollidedPhysObj);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|