EntityCollision.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. /* Copyright (c) 2002-2012 Croteam Ltd.
  2. This program is free software; you can redistribute it and/or modify
  3. it under the terms of version 2 of the GNU General Public License as published by
  4. the Free Software Foundation
  5. This program is distributed in the hope that it will be useful,
  6. but WITHOUT ANY WARRANTY; without even the implied warranty of
  7. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  8. GNU General Public License for more details.
  9. You should have received a copy of the GNU General Public License along
  10. with this program; if not, write to the Free Software Foundation, Inc.,
  11. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
  12. #include "stdh.h"
  13. #include <Engine/Entities/Entity.h>
  14. #include <Engine/Entities/EntityCollision.h>
  15. #include <Engine/Base/ListIterator.inl>
  16. #include <Engine/Math/Geometry.inl>
  17. #include <Engine/Math/Clipping.inl>
  18. #include <Engine/Brushes/Brush.h>
  19. #include <Engine/Templates/DynamicArray.cpp>
  20. #include <Engine/Templates/StaticArray.cpp>
  21. #include <Engine/Network/Network.h>
  22. #include <Engine/Network/SessionState.h>
  23. class CClipTest {
  24. public:
  25. CEntity *ct_penEntity; // the entity
  26. CEntity *ct_penObstacle; // obstacle entity (if cannot change)
  27. INDEX ct_iNewCollisionBox; // index of new collision box to set
  28. CCollisionInfo ct_ciNew; // collision info with new box
  29. FLOATaabbox3D ct_boxTotal; // union box for old and new in absolute coordinates
  30. CListHead ct_lhActiveSectors; // sectors that may be of interest
  31. BOOL PointTouchesSphere(
  32. const FLOAT3D &vPoint,
  33. const FLOAT3D &vSphereCenter,
  34. const FLOAT fSphereRadius);
  35. BOOL PointTouchesCylinder(
  36. const FLOAT3D &vPoint,
  37. const FLOAT3D &vCylinderBottomCenter,
  38. const FLOAT3D &vCylinderTopCenter,
  39. const FLOAT fCylinderRadius);
  40. // project spheres of a collision info to given placement
  41. void ProjectSpheresToPlacement(CCollisionInfo &ci,
  42. FLOAT3D &vPosition, FLOATmatrix3D &mRotation);
  43. // test if a sphere touches brush polygon
  44. BOOL SphereTouchesBrushPolygon(const CMovingSphere &msMoving,
  45. CBrushPolygon *pbpoPolygon);
  46. // test if entity touches brush polygon
  47. BOOL EntityTouchesBrushPolygon(CBrushPolygon *pbpoPolygon);
  48. // test if an entity can change to a new collision box without intersecting anything
  49. BOOL CanChange(CEntity *pen, INDEX iNewCollisionBox);
  50. ~CClipTest(void);
  51. };
  52. // test if an entity can change to a new collision box without intersecting anything
  53. BOOL CanEntityChangeCollisionBox(CEntity *pen, INDEX iNewCollisionBox, CEntity **ppenObstacle)
  54. {
  55. // if the entity is not linked to any sectors
  56. if (pen->en_rdSectors.IsEmpty()) {
  57. // make sure that the classification is ok
  58. pen->FindSectorsAroundEntity();
  59. }
  60. CClipTest ct;
  61. BOOL bCan = ct.CanChange(pen, iNewCollisionBox);
  62. *ppenObstacle = ct.ct_penObstacle;
  63. return bCan;
  64. }
  65. // project spheres of a collision info to given placement
  66. void CClipTest::ProjectSpheresToPlacement(CCollisionInfo &ci,
  67. FLOAT3D &vPosition, FLOATmatrix3D &mRotation)
  68. {
  69. // for each sphere
  70. FOREACHINSTATICARRAY(ci.ci_absSpheres, CMovingSphere, itms) {
  71. // project it in start point
  72. itms->ms_vRelativeCenter0 = itms->ms_vCenter*mRotation+vPosition;
  73. }
  74. }
  75. // test point to a sphere
  76. BOOL CClipTest::PointTouchesSphere(
  77. const FLOAT3D &vPoint,
  78. const FLOAT3D &vSphereCenter,
  79. const FLOAT fSphereRadius)
  80. {
  81. FLOAT fD = (vSphereCenter-vPoint).Length();
  82. return fD<fSphereRadius;
  83. }
  84. // test sphere to the edge (point to the edge cylinder)
  85. BOOL CClipTest::PointTouchesCylinder(
  86. const FLOAT3D &vPoint,
  87. const FLOAT3D &vCylinderBottomCenter,
  88. const FLOAT3D &vCylinderTopCenter,
  89. const FLOAT fCylinderRadius)
  90. {
  91. const FLOAT3D vCylinderBottomToTop = vCylinderTopCenter - vCylinderBottomCenter;
  92. const FLOAT fCylinderBottomToTopLength = vCylinderBottomToTop.Length();
  93. const FLOAT3D vCylinderDirection = vCylinderBottomToTop/fCylinderBottomToTopLength;
  94. FLOAT3D vBottomToPoint = vPoint-vCylinderBottomCenter;
  95. FLOAT fPointL = vBottomToPoint%vCylinderDirection;
  96. // if not between top and bottom
  97. if (fPointL<0 || fPointL>fCylinderBottomToTopLength) {
  98. // doesn't touch
  99. return FALSE;
  100. }
  101. // find distance from point to cylinder axis
  102. FLOAT fD = (vBottomToPoint-vCylinderDirection*fPointL).Length();
  103. return fD<fCylinderRadius;
  104. }
  105. // test if a sphere touches brush polygon
  106. BOOL CClipTest::SphereTouchesBrushPolygon(const CMovingSphere &msMoving,
  107. CBrushPolygon *pbpoPolygon)
  108. {
  109. const FLOATplane3D &plPolygon = pbpoPolygon->bpo_pbplPlane->bpl_plAbsolute;
  110. // calculate point distance from polygon plane
  111. FLOAT fDistance = plPolygon.PointDistance(msMoving.ms_vRelativeCenter0);
  112. // if is further away than sphere radius
  113. if (fDistance>msMoving.ms_fR || fDistance<-msMoving.ms_fR) {
  114. // no collision
  115. return FALSE;
  116. }
  117. // calculate coordinate projected to the polygon plane
  118. FLOAT3D vPosMid = msMoving.ms_vRelativeCenter0;
  119. FLOAT3D vHitPoint = plPolygon.ProjectPoint(vPosMid);
  120. // find major axes of the polygon plane
  121. INDEX iMajorAxis1, iMajorAxis2;
  122. GetMajorAxesForPlane(plPolygon, iMajorAxis1, iMajorAxis2);
  123. // create an intersector
  124. CIntersector isIntersector(vHitPoint(iMajorAxis1), vHitPoint(iMajorAxis2));
  125. // for all edges in the polygon
  126. FOREACHINSTATICARRAY(pbpoPolygon->bpo_abpePolygonEdges, CBrushPolygonEdge,
  127. itbpePolygonEdge) {
  128. // get edge vertices (edge direction is irrelevant here!)
  129. const FLOAT3D &vVertex0 = itbpePolygonEdge->bpe_pbedEdge->bed_pbvxVertex0->bvx_vAbsolute;
  130. const FLOAT3D &vVertex1 = itbpePolygonEdge->bpe_pbedEdge->bed_pbvxVertex1->bvx_vAbsolute;
  131. // pass the edge to the intersector
  132. isIntersector.AddEdge(
  133. vVertex0(iMajorAxis1), vVertex0(iMajorAxis2),
  134. vVertex1(iMajorAxis1), vVertex1(iMajorAxis2));
  135. }
  136. // if the polygon is intersected by the ray
  137. if (isIntersector.IsIntersecting()) {
  138. return TRUE;
  139. }
  140. // for each edge in polygon
  141. FOREACHINSTATICARRAY(pbpoPolygon->bpo_abpePolygonEdges, CBrushPolygonEdge, itbpe) {
  142. // get edge vertices (edge direction is important here!)
  143. FLOAT3D vVertex0, vVertex1;
  144. itbpe->GetVertexCoordinatesAbsolute(vVertex0, vVertex1);
  145. // test sphere to the edge (point to the edge cylinder)
  146. if (PointTouchesCylinder(
  147. msMoving.ms_vRelativeCenter0, // point,
  148. vVertex0, // cylinder bottom center,
  149. vVertex1, // cylinder top center,
  150. msMoving.ms_fR // cylinder radius
  151. )) {
  152. return TRUE;
  153. }
  154. // test sphere to the first vertex
  155. // NOTE: using point to sphere collision
  156. if (PointTouchesSphere(
  157. msMoving.ms_vRelativeCenter0, // pount
  158. vVertex0, // sphere center
  159. msMoving.ms_fR // sphere radius
  160. )) {
  161. return TRUE;
  162. }
  163. }
  164. return FALSE;
  165. }
  166. // test if entity touches brush polygon
  167. BOOL CClipTest::EntityTouchesBrushPolygon(CBrushPolygon *pbpoPolygon)
  168. {
  169. // for each sphere
  170. FOREACHINSTATICARRAY(ct_ciNew.ci_absSpheres, CMovingSphere, itms) {
  171. // if it touches
  172. if (SphereTouchesBrushPolygon(*itms, pbpoPolygon)) {
  173. return TRUE;
  174. }
  175. }
  176. return FALSE;
  177. }
  178. // test if an entity can change to a new collision box without intersecting anything
  179. BOOL CClipTest::CanChange(CEntity *pen, INDEX iNewCollisionBox)
  180. {
  181. // can be used only for models
  182. ASSERT(
  183. pen->en_RenderType==CEntity::RT_MODEL ||
  184. pen->en_RenderType==CEntity::RT_EDITORMODEL ||
  185. pen->en_RenderType==CEntity::RT_SKAMODEL ||
  186. pen->en_RenderType==CEntity::RT_SKAEDITORMODEL);
  187. // safety check
  188. if (pen->en_pciCollisionInfo==NULL) {
  189. return FALSE;
  190. }
  191. // remember parameters
  192. ct_penEntity = pen;
  193. ct_iNewCollisionBox = iNewCollisionBox;
  194. ct_penObstacle = NULL;
  195. // create new temporary collision info
  196. ct_ciNew.FromModel(pen, iNewCollisionBox);
  197. // project it to entity placement
  198. ProjectSpheresToPlacement(ct_ciNew,
  199. pen->en_plPlacement.pl_PositionVector, pen->en_mRotation);
  200. // get total bounding box encompassing both old and new collision boxes
  201. FLOATaabbox3D boxOld, boxNew;
  202. ASSERT(ct_penEntity->en_pciCollisionInfo!=NULL);
  203. CCollisionInfo &ciOld = *ct_penEntity->en_pciCollisionInfo;
  204. ciOld.MakeBoxAtPlacement(ct_penEntity->en_plPlacement.pl_PositionVector,
  205. ct_penEntity->en_mRotation, boxOld);
  206. ct_ciNew.MakeBoxAtPlacement(ct_penEntity->en_plPlacement.pl_PositionVector,
  207. ct_penEntity->en_mRotation, boxNew);
  208. ct_boxTotal = boxOld;
  209. ct_boxTotal |= boxNew;
  210. // for each zoning sector that this entity is in
  211. {FOREACHSRCOFDST(ct_penEntity->en_rdSectors, CBrushSector, bsc_rsEntities, pbsc)
  212. // add it to list of active sectors
  213. ct_lhActiveSectors.AddTail(pbsc->bsc_lnInActiveSectors);
  214. ENDFOR}
  215. // for each active sector
  216. FOREACHINLIST(CBrushSector, bsc_lnInActiveSectors, ct_lhActiveSectors, itbsc) {
  217. // for non-zoning brush entities in the sector
  218. {FOREACHDSTOFSRC(itbsc->bsc_rsEntities, CEntity, en_rdSectors, pen)
  219. if (pen->en_RenderType!=CEntity::RT_BRUSH&&
  220. (_pNetwork->ga_ulDemoMinorVersion<=4 || pen->en_RenderType!=CEntity::RT_FIELDBRUSH)) {
  221. break; // brushes are sorted first in list
  222. }
  223. // get first mip
  224. CBrushMip *pbm = pen->en_pbrBrush->GetFirstMip();
  225. // if brush mip exists for that mip factor
  226. if (pbm!=NULL) {
  227. // for each sector in the mip
  228. {FOREACHINDYNAMICARRAY(pbm->bm_abscSectors, CBrushSector, itbscNonZoning) {
  229. CBrushSector &bscNonZoning = *itbscNonZoning;
  230. // add it to list of active sectors
  231. if(!bscNonZoning.bsc_lnInActiveSectors.IsLinked()) {
  232. ct_lhActiveSectors.AddTail(bscNonZoning.bsc_lnInActiveSectors);
  233. }
  234. }}
  235. }
  236. ENDFOR}
  237. // if the sector's brush doesn't have collision
  238. CEntity *penSectorBrush = itbsc->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity;
  239. if (penSectorBrush->en_ulCollisionFlags==0 ||
  240. (_pNetwork->ga_ulDemoMinorVersion>2 && penSectorBrush->en_RenderType!=CEntity::RT_BRUSH) ) {
  241. // skip it
  242. continue;
  243. }
  244. // for each polygon in the sector
  245. FOREACHINSTATICARRAY(itbsc->bsc_abpoPolygons, CBrushPolygon, itbpo) {
  246. CBrushPolygon *pbpo = itbpo;
  247. // if its bbox has no contact with bbox to test
  248. if (!pbpo->bpo_boxBoundingBox.HasContactWith(ct_boxTotal) ) {
  249. // skip it
  250. continue;
  251. }
  252. // if it is passable
  253. if (pbpo->bpo_ulFlags&BPOF_PASSABLE) {
  254. // for each sector related to the portal
  255. {FOREACHDSTOFSRC(pbpo->bpo_rsOtherSideSectors, CBrushSector, bsc_rdOtherSidePortals, pbscRelated)
  256. // if the sector is not active
  257. if (!pbscRelated->bsc_lnInActiveSectors.IsLinked()) {
  258. // add it to active list
  259. ct_lhActiveSectors.AddTail(pbscRelated->bsc_lnInActiveSectors);
  260. }
  261. ENDFOR}
  262. // if it is not passable
  263. } else {
  264. // if entity touches it
  265. if (EntityTouchesBrushPolygon(pbpo)) {
  266. // test fails
  267. ct_penObstacle = pbpo->bpo_pbscSector->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity;
  268. return FALSE;
  269. }
  270. }
  271. }
  272. }
  273. return TRUE;
  274. }
  275. CClipTest::~CClipTest(void)
  276. {
  277. // clear list of active sectors
  278. {FORDELETELIST(CBrushSector, bsc_lnInActiveSectors, ct_lhActiveSectors, itbsc) {
  279. itbsc->bsc_lnInActiveSectors.Remove();
  280. }}
  281. }