octbox.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. /*
  2. ** Command & Conquer Renegade(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /***********************************************************************************************
  19. *** 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 ***
  20. ***********************************************************************************************
  21. * *
  22. * Project Name : WWPhys *
  23. * *
  24. * $Archive:: /Commando/Code/wwphys/octbox.cpp $*
  25. * *
  26. * Original Author:: Greg Hjelstrom *
  27. * *
  28. * $Author:: Greg_h $*
  29. * *
  30. * $Modtime:: 11/21/01 2:54p $*
  31. * *
  32. * $Revision:: 20 $*
  33. * *
  34. *---------------------------------------------------------------------------------------------*
  35. * Functions: *
  36. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  37. #include "octbox.h"
  38. #include "pscene.h"
  39. #include "physcoltest.h"
  40. #include "physinttest.h"
  41. #include "physcon.h"
  42. #define SHOW_CONTACT_DETECTORS 0
  43. const float DEFAULT_STIFFNESS = 5.0f;
  44. const float DEFAULT_DAMPING = 0.1f;
  45. const float DEFAULT_THICKNESS = 0.1f;
  46. const float CONTACT_GRAVITY_MULTIPLIER = 2.0f; // maximum contact force is multiplier * gravity.
  47. const float CONTACT_DAMPING_FACTOR = 0.33f; // ratio of damping / (critical damping) (less than zero is underdamped)
  48. OctBoxClass::OctBoxClass
  49. (
  50. RigidBodyClass & parent,
  51. const OBBoxClass & box
  52. ) :
  53. Parent(parent),
  54. InnerBox(box),
  55. Thickness(DEFAULT_THICKNESS),
  56. Stiffness(DEFAULT_STIFFNESS),
  57. Damping(DEFAULT_DAMPING)
  58. {
  59. Set_Thickness(Thickness);
  60. }
  61. OctBoxClass::~OctBoxClass(void)
  62. {
  63. }
  64. void OctBoxClass::Set_Thickness(float thickness)
  65. {
  66. Thickness = thickness;
  67. // Outer corner of the "octant boxes" should be at the corner of the box
  68. // Inner corner of the "octant boxes" should be overlapping the neighbor boxes by 'thickness'
  69. Vector3 octant_max = InnerBox.Extent;
  70. Vector3 octant_min(-Thickness,-Thickness,-Thickness);
  71. OctantExtent = (octant_max - octant_min) / 2.0f;
  72. OctantCenter = (octant_max + octant_min) / 2.0f;
  73. }
  74. void OctBoxClass::Update_Contact_Parameters(void)
  75. {
  76. float mass = Parent.Get_Mass();
  77. // Make the contact force have a maximum of MULTIPLIER * gravity
  78. Stiffness = mass * WWMath::Fabs(PhysicsConstants::GravityAcceleration.Z);
  79. Stiffness *= CONTACT_GRAVITY_MULTIPLIER;
  80. // Compute critical damping, achieve equilibrium in minimum time.
  81. Damping = 2.0f * mass * WWMath::Sqrt(Stiffness / mass);
  82. // Critical damping seems to be too stiff so reduce it
  83. Damping *= CONTACT_DAMPING_FACTOR;
  84. }
  85. void OctBoxClass::Get_Outer_Bounds(AABoxClass * set_bounds)
  86. {
  87. WWASSERT(set_bounds != NULL);
  88. OBBoxClass wrld_outer_box;
  89. wrld_outer_box = WrldInnerBox;
  90. wrld_outer_box.Extent += Vector3(Thickness,Thickness,Thickness);
  91. set_bounds->Center = wrld_outer_box.Center;
  92. wrld_outer_box.Compute_Axis_Aligned_Extent(&(set_bounds->Extent));
  93. }
  94. bool OctBoxClass::Is_Intersecting(NonRefPhysListClass * result_list,bool check_static_objs,bool check_dyn_objs)
  95. {
  96. PhysicsSceneClass * the_scene = PhysicsSceneClass::Get_Instance();
  97. WWASSERT(the_scene != NULL);
  98. /*
  99. ** Test inner box for intersection
  100. */
  101. PhysOBBoxIntersectionTestClass test(WrldInnerBox,
  102. Parent.Get_Collision_Group(),
  103. COLLISION_TYPE_PHYSICAL | COLLISION_TYPE_VEHICLE,
  104. result_list);
  105. test.CheckStaticObjs = check_static_objs;
  106. test.CheckDynamicObjs = check_dyn_objs;
  107. Parent.Inc_Ignore_Counter ();
  108. bool intersect = PhysicsSceneClass::Get_Instance ()->Intersection_Test(test);
  109. Parent.Dec_Ignore_Counter ();
  110. return intersect;
  111. }
  112. bool OctBoxClass::Is_In_Contact_Zone(void)
  113. {
  114. PhysicsSceneClass * the_scene = PhysicsSceneClass::Get_Instance();
  115. WWASSERT(the_scene != NULL);
  116. /*
  117. ** Test outer box for intersection
  118. */
  119. OBBoxClass wrld_outer_box = WrldInnerBox;
  120. wrld_outer_box.Extent += Vector3(Thickness,Thickness,Thickness);
  121. int colgroup = Parent.Get_Collision_Group();
  122. int coltype = COLLISION_TYPE_PHYSICAL | COLLISION_TYPE_VEHICLE;
  123. return the_scene->Intersection_Test(wrld_outer_box,colgroup,coltype,true);
  124. }
  125. OctBoxClass::CollisionResult
  126. OctBoxClass::Compute_Contacts(bool lock_to_centroids)
  127. {
  128. Parent.Inc_Ignore_Counter();
  129. CollisionResult result = Internal_Compute_Contacts(lock_to_centroids);
  130. Parent.Dec_Ignore_Counter();
  131. return result;
  132. }
  133. OctBoxClass::CollisionResult
  134. OctBoxClass::Internal_Compute_Contacts(bool lock_to_centroids)
  135. {
  136. /*
  137. ** Algorithm:
  138. ** Quick Rejection:
  139. ** - Try to quick reject the entire collision by checking the outer box
  140. ** - Check if we're going to have to search for TOC by checking the inner box
  141. ** Find Contacts:
  142. ** - For each octant (total of 8)
  143. ** - Sweep the octant along the diagonal and record the contact point (if any)
  144. */
  145. PhysicsSceneClass * the_scene = PhysicsSceneClass::Get_Instance();
  146. WWASSERT(the_scene != NULL);
  147. Reset_Contacts();
  148. /*
  149. ** compute the outer box in WS
  150. */
  151. OBBoxClass wrld_outer_box;
  152. wrld_outer_box = WrldInnerBox;
  153. wrld_outer_box.Extent += Vector3(Thickness,Thickness,Thickness);
  154. /*
  155. ** Check for quick-rejection (intersection or completely separated)
  156. */
  157. int colgroup = Parent.Get_Collision_Group();
  158. int coltype = COLLISION_TYPE_PHYSICAL | COLLISION_TYPE_VEHICLE;
  159. if (!the_scene->Intersection_Test(wrld_outer_box,colgroup,coltype,true)) {
  160. //Parent.Add_Debug_OBBox(wrld_outer_box,Vector3(0,1,0));
  161. return RESULT_NO_COLLISION;
  162. }
  163. if (the_scene->Intersection_Test(WrldInnerBox,colgroup,coltype,true)) {
  164. //Parent.Add_Debug_OBBox(wrld_outer_box,Vector3(1,0,0));
  165. return RESULT_INTERSECTION;
  166. }
  167. /*
  168. ** Check the octants for contact
  169. */
  170. for (int i=0; i<8; i++) {
  171. Compute_Octant_Contact(i,lock_to_centroids);
  172. }
  173. return RESULT_CONTACT;
  174. }
  175. void OctBoxClass::Compute_Octant_Contact(int oi,bool lock_to_centroids)
  176. {
  177. static Vector3 _octant_offset[8] =
  178. {
  179. Vector3( 1, 1, 1),
  180. Vector3(-1, 1, 1),
  181. Vector3(-1,-1, 1),
  182. Vector3( 1,-1, 1),
  183. Vector3( 1, 1,-1),
  184. Vector3(-1, 1,-1),
  185. Vector3(-1,-1,-1),
  186. Vector3( 1,-1,-1),
  187. };
  188. /*
  189. ** Compute the corner contact "hair" which doubles as the
  190. ** move vector for the octant box.
  191. */
  192. CastResultStruct corner_result;
  193. corner_result.ComputeContactPoint = true;
  194. Vector3 corner = _octant_offset[oi];
  195. corner.X *= WrldInnerBox.Extent.X;
  196. corner.Y *= WrldInnerBox.Extent.Y;
  197. corner.Z *= WrldInnerBox.Extent.Z;
  198. Matrix3::Rotate_Vector(WrldInnerBox.Basis,corner,&corner);
  199. corner += WrldInnerBox.Center;
  200. Vector3 corner_move = Thickness * _octant_offset[oi];
  201. Matrix3::Rotate_Vector(WrldInnerBox.Basis,corner_move,&corner_move);
  202. /*
  203. ** Collision detect for the corner line-segment
  204. */
  205. LineSegClass ray(corner,corner + corner_move);
  206. WWASSERT(corner_move.Length2() <= ((3*Thickness*Thickness) + 0.01f));
  207. WWASSERT(ray.Get_DP().Length2() <= ((3*Thickness*Thickness) + 0.01f));
  208. PhysRayCollisionTestClass raytest( ray,
  209. &corner_result,
  210. Parent.Get_Collision_Group(),
  211. COLLISION_TYPE_PHYSICAL | COLLISION_TYPE_VEHICLE);
  212. PhysicsSceneClass::Get_Instance()->Cast_Ray(raytest,true);
  213. #ifdef WWDEBUG
  214. #if SHOW_CONTACT_DETECTORS
  215. Parent.Add_Debug_Vector(corner,corner_move,Vector3(0,1,0));
  216. #endif
  217. #endif
  218. /*
  219. ** Compute the contact for this octant-box.
  220. */
  221. OBBoxClass octbox = WrldInnerBox;
  222. octbox.Extent = OctantExtent;
  223. Vector3 dc = _octant_offset[oi];
  224. dc.X *= OctantCenter.X;
  225. dc.Y *= OctantCenter.Y;
  226. dc.Z *= OctantCenter.Z;
  227. Matrix3::Rotate_Vector(WrldInnerBox.Basis,dc,&dc);
  228. octbox.Center += dc;
  229. /*
  230. ** Collision detect with this octant box
  231. */
  232. CastResultStruct octant_result;
  233. octant_result.ComputeContactPoint = true;
  234. PhysOBBoxCollisionTestClass boxtest( octbox,
  235. corner_move,
  236. &octant_result,
  237. Parent.Get_Collision_Group(),
  238. COLLISION_TYPE_PHYSICAL | COLLISION_TYPE_VEHICLE);
  239. PhysicsSceneClass::Get_Instance()->Cast_OBBox(boxtest,true);
  240. /*
  241. ** Wake up any vehicle in contact with us
  242. */
  243. if ((boxtest.CollidedPhysObj != NULL) && (boxtest.CollidedPhysObj->As_RigidBodyClass() != NULL)) {
  244. boxtest.CollidedPhysObj->Force_Awake();
  245. }
  246. /*
  247. ** Display the octant box
  248. */
  249. #ifdef WWDEBUG
  250. #if SHOW_CONTACT_DETECTORS
  251. static Vector3 _oct_colors[8] = {
  252. Vector3(1,0,0),Vector3(0,1,0),Vector3(0,0,1),Vector3(1,1,1),
  253. Vector3(1,1,1),Vector3(0,0,1),Vector3(0,1,0),Vector3(1,0,0)
  254. };
  255. octbox.Center += octant_result.Fraction * corner_move;
  256. //Parent.Add_Debug_OBBox(octbox,_oct_colors[oi]);
  257. //Parent.Add_Debug_Vector(result.Point,result.Normal,Vector3(0,1,0));
  258. #endif
  259. #endif
  260. /*
  261. ** Now, decide which (if any) contact to use. We prefer the
  262. ** corners but have to use the box's contact if it is significantly
  263. ** closer. If neither hits anything, just bail out of this routine.
  264. */
  265. if ((corner_result.Fraction >= 1.0f) && (octant_result.Fraction >= 1.0f)) {
  266. return;
  267. }
  268. /*
  269. ** If the corner is within 'CORNER_BIAS' of the result of the octant
  270. ** then use its result
  271. */
  272. const float CORNER_BIAS = 0.25f;
  273. if ((corner_result.Fraction < 1.0f) && (corner_result.Fraction - CORNER_BIAS < octant_result.Fraction)) {
  274. if (corner_result.Normal.Length2() > 0.0f) { // the normal will be 0,0,0 when we hit a backside
  275. Add_Contact(corner_result,raytest.CollidedPhysObj);
  276. }
  277. } else {
  278. if ((octant_result.Fraction < 1.0f) && (!octant_result.StartBad)) {
  279. if (octant_result.Normal.Length2() > 0.0f) { // the normal will be 0,0,0 when we hit a backside
  280. Add_Contact(octant_result,boxtest.CollidedPhysObj);
  281. }
  282. }
  283. if (corner_result.Fraction < 1.0f) {
  284. // also add the corner contact!
  285. if (corner_result.Normal.Length2() > 0.0f) { // the normal will be 0,0,0 when we hit a backside
  286. Add_Contact(corner_result,raytest.CollidedPhysObj);
  287. }
  288. }
  289. }
  290. }