This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/wwphys/octbox.cpp

350 lines
11 KiB
C++
Raw Permalink Normal View History

/*
** 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);
}
}
}
}