/* Copyright (c) 2002-2012 Croteam Ltd. This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // these are used for making projections for converting from X space to Y space this way: // MatrixMulT(mY, mX, mXToY); // VectMulT(mY, vX-vY, vXToY); // C=AtxB static inline void MatrixMulT(const FLOATmatrix3D &mA, const FLOATmatrix3D &mB, FLOATmatrix3D &mC) { mC(1,1) = mA(1,1)*mB(1,1)+mA(2,1)*mB(2,1)+mA(3,1)*mB(3,1); mC(1,2) = mA(1,1)*mB(1,2)+mA(2,1)*mB(2,2)+mA(3,1)*mB(3,2); mC(1,3) = mA(1,1)*mB(1,3)+mA(2,1)*mB(2,3)+mA(3,1)*mB(3,3); mC(2,1) = mA(1,2)*mB(1,1)+mA(2,2)*mB(2,1)+mA(3,2)*mB(3,1); mC(2,2) = mA(1,2)*mB(1,2)+mA(2,2)*mB(2,2)+mA(3,2)*mB(3,2); mC(2,3) = mA(1,2)*mB(1,3)+mA(2,2)*mB(2,3)+mA(3,2)*mB(3,3); mC(3,1) = mA(1,3)*mB(1,1)+mA(2,3)*mB(2,1)+mA(3,3)*mB(3,1); mC(3,2) = mA(1,3)*mB(1,2)+mA(2,3)*mB(2,2)+mA(3,3)*mB(3,2); mC(3,3) = mA(1,3)*mB(1,3)+mA(2,3)*mB(2,3)+mA(3,3)*mB(3,3); } // v2 = Mt*v1 static inline void VectMulT(const FLOATmatrix3D &mM, const FLOAT3D &vV1, FLOAT3D &vV2) { vV2(1) = vV1(1)*mM(1,1)+vV1(2)*mM(2,1)+vV1(3)*mM(3,1); vV2(2) = vV1(1)*mM(1,2)+vV1(2)*mM(2,2)+vV1(3)*mM(3,2); vV2(3) = vV1(1)*mM(1,3)+vV1(2)*mM(2,3)+vV1(3)*mM(3,3); } ///////////////////////////////////////////////////////////////////// // CClipMove // get start and end positions of an entity in this tick inline void CClipMove::GetPositionsOfEntity( CEntity *pen, FLOAT3D &v0, FLOATmatrix3D &m0, FLOAT3D &v1, FLOATmatrix3D &m1) { _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_GETPOSITIONSOFENTITY); // start is where entity is now v0 = pen->en_plPlacement.pl_PositionVector; m0 = pen->en_mRotation; // if entity is movable if (pen->en_ulPhysicsFlags&EPF_MOVABLE) { // get end position from movable entity CMovableEntity *penMovable = (CMovableEntity*)pen; v1 = penMovable->en_vNextPosition; m1 = penMovable->en_mNextRotation; // NOTE: this prevents movable entities from hanging in the air when a brush moves // beneath their feet // if moving entity is reference of this entity if (penMovable->en_penReference == cm_penMoving) { // add this entity to list of movers penMovable->AddToMoversDuringMoving(); } // if entity is not movable } else { // end position is same as start v1 = v0; m1 = m0; } _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_GETPOSITIONSOFENTITY); } /* * Constructor. */ CClipMove::CClipMove(CMovableEntity *penEntity) { _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_PREPARECLIPMOVE); // clear last-hit statistics cm_penHit = NULL; cm_pbpoHit = NULL; cm_fMovementFraction = 2.0f; cm_penMoving = penEntity; // if the entity is deleted, or couldn't possible collide with anything if ((cm_penMoving->en_ulFlags&ENF_DELETED) ||!(cm_penMoving->en_ulCollisionFlags&ECF_TESTMASK) ||cm_penMoving->en_pciCollisionInfo==NULL) { // do nothing _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_PREPARECLIPMOVE); return; } // if entity is model if (penEntity->en_RenderType==CEntity::RT_MODEL || penEntity->en_RenderType==CEntity::RT_EDITORMODEL || penEntity->en_RenderType==CEntity::RT_SKAMODEL || penEntity->en_RenderType==CEntity::RT_SKAEDITORMODEL ) { cm_bMovingBrush = FALSE; // remember entity and placements cm_penA = penEntity; GetPositionsOfEntity(cm_penA, cm_vA0, cm_mA0, cm_vA1, cm_mA1); // create spheres for the entity ASSERT(penEntity->en_pciCollisionInfo!=NULL); cm_pamsA = &penEntity->en_pciCollisionInfo->ci_absSpheres; // create aabbox for entire movement path FLOATaabbox3D box0, box1; penEntity->en_pciCollisionInfo->MakeBoxAtPlacement(cm_vA0, cm_mA0, box0); penEntity->en_pciCollisionInfo->MakeBoxAtPlacement(cm_vA1, cm_mA1, box1); cm_boxMovementPath = box0; cm_boxMovementPath |= box1; // if entity is brush } else if (penEntity->en_RenderType==CEntity::RT_BRUSH) { cm_bMovingBrush = TRUE; // remember entity and placements cm_penB = penEntity; GetPositionsOfEntity(cm_penB, cm_vB0, cm_mB0, cm_vB1, cm_mB1); // create spheres for the entity ASSERT(penEntity->en_pciCollisionInfo!=NULL); // create aabbox for entire movement path FLOATaabbox3D box0, box1; penEntity->en_pciCollisionInfo->MakeBoxAtPlacement(cm_vB0, cm_mB0, box0); penEntity->en_pciCollisionInfo->MakeBoxAtPlacement(cm_vB1, cm_mB1, box1); cm_boxMovementPath = box0; cm_boxMovementPath |= box1; } else { ASSERT(FALSE); } _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_PREPARECLIPMOVE); } // send pass if needed inline BOOL CClipMove::SendPassEvent(CEntity *penTested) { BOOL bSent = FALSE; if (cm_ulPassMaskA & penTested->en_ulCollisionFlags) { EPass ePassA; ePassA.penOther = penTested; ePassA.bThisMoved = TRUE; cm_penMoving->SendEvent(ePassA); bSent = TRUE; } if (cm_ulPassMaskB & penTested->en_ulCollisionFlags) { EPass ePassB; ePassB.penOther = cm_penMoving; ePassB.bThisMoved = FALSE; penTested->SendEvent(ePassB); bSent = TRUE; } return bSent; } /* * Clip a moving point to a sphere, update collision data. */ inline void CClipMove::ClipMovingPointToSphere( const FLOAT3D &vStart, const FLOAT3D &vEnd, const FLOAT3D &vSphereCenter, const FLOAT fSphereRadius) { const FLOAT3D vSphereCenterToStart = vStart - vSphereCenter; const FLOAT3D vStartToEnd = vEnd - vStart; // calculate discriminant for intersection parameters const FLOAT fP = ((vStartToEnd%vSphereCenterToStart)/(vStartToEnd%vStartToEnd)); const FLOAT fQ = (((vSphereCenterToStart%vSphereCenterToStart) - (fSphereRadius*fSphereRadius))/(vStartToEnd%vStartToEnd)); const FLOAT fD = fP*fP-fQ; // if it is less than zero if (fD<0) { // no collision will occur return; } // calculate intersection parameters const FLOAT fSqrtD = sqrt(fD); const FLOAT fLambda1 = -fP+fSqrtD; const FLOAT fLambda2 = -fP-fSqrtD; // use lower one const FLOAT fMinLambda = Min(fLambda1, fLambda2); // if it is betwen zero and last collision found if (0.0f<=fMinLambda && fMinLambdabpo_pbplPlane->bpl_plRelative; // calculate point distances from polygon plane FLOAT fDistance0 = plPolygon.PointDistance(msMoving.ms_vRelativeCenter0)-msMoving.ms_fR; FLOAT fDistance1 = plPolygon.PointDistance(msMoving.ms_vRelativeCenter1)-msMoving.ms_fR; // if first point is in front and second point is behind if (fDistance0>=0 && fDistance1<0) { // calculate fraction of line before intersection FLOAT fFraction = fDistance0/(fDistance0-fDistance1); ASSERT(fFraction>=0.0f && fFraction<=1.0f); // if fraction is less than minimum found fraction if (fFractionbpo_abpePolygonEdges, CBrushPolygonEdge, itbpePolygonEdge) {*/ CBrushPolygonEdge *itbpePolygonEdge = pbpoPolygon->bpo_abpePolygonEdges.sa_Array; int i; const int count = pbpoPolygon->bpo_abpePolygonEdges.sa_Count; for (i = 0; i < count; i++, itbpePolygonEdge++) { // get edge vertices (edge direction is irrelevant here!) const FLOAT3D &vVertex0 = itbpePolygonEdge->bpe_pbedEdge->bed_pbvxVertex0->bvx_vRelative; const FLOAT3D &vVertex1 = itbpePolygonEdge->bpe_pbedEdge->bed_pbvxVertex1->bvx_vRelative; // pass the edge to the intersector isIntersector.AddEdge( vVertex0(iMajorAxis1), vVertex0(iMajorAxis2), vVertex1(iMajorAxis1), vVertex1(iMajorAxis2)); } // if the polygon is intersected by the ray if (isIntersector.IsIntersecting()) { // if cannot pass if (!SendPassEvent(cm_penTested)) { // mark this as the new closest found collision point cm_fMovementFraction = fFraction; cm_vClippedLine = msMoving.ms_vRelativeCenter1 - vPosMid; ASSERT(cm_vClippedLine.Length()<100.0f); // project the collision plane from space B to absolute space // only the normal of the plane is correct, not the distance!!!! cm_plClippedPlane = plPolygon*cm_mBToAbsolute+cm_vBToAbsolute; // remember hit entity cm_penHit = cm_penTested; cm_pbpoHit = cm_pbpoTested; } } } } // for each edge in polygon //FOREACHINSTATICARRAY(pbpoPolygon->bpo_abpePolygonEdges, CBrushPolygonEdge, itbpe) { CBrushPolygonEdge *itbpe = pbpoPolygon->bpo_abpePolygonEdges.sa_Array; int i; const int count = pbpoPolygon->bpo_abpePolygonEdges.sa_Count; for (i = 0; i < count; i++, itbpe++) { // get edge vertices (edge direction is important here!) FLOAT3D vVertex0, vVertex1; itbpe->GetVertexCoordinatesRelative(vVertex0, vVertex1); // clip moving sphere to the edge (moving point to the edge cylinder) ClipMovingPointToCylinder( msMoving.ms_vRelativeCenter0, // start, msMoving.ms_vRelativeCenter1, // end, vVertex0, // cylinder bottom center, vVertex1, // cylinder top center, msMoving.ms_fR // cylinder radius ); // clip moving sphere to the first vertex // NOTE: use moving point to sphere collision ClipMovingPointToSphere( msMoving.ms_vRelativeCenter0, // start msMoving.ms_vRelativeCenter1, // end vVertex0, // sphere center msMoving.ms_fR // sphere radius ); } } /* Clip a moving sphere to a terrain polygon, update collision data. */ void CClipMove::ClipMovingSphereToTerrainPolygon( const CMovingSphere &msMoving, const FLOAT3D &v0, const FLOAT3D &v1, const FLOAT3D &v2) { _pfPhysicsProfile.IncrementCounter(CPhysicsProfile::PCI_SPHERETOPOLYGONTESTS); cm_pbpoTested = NULL; const FLOATplane3D plPolygon = FLOATplane3D(v0,v1,v2); // calculate point distances from polygon plane FLOAT fDistance0 = plPolygon.PointDistance(msMoving.ms_vRelativeCenter0)-msMoving.ms_fR; FLOAT fDistance1 = plPolygon.PointDistance(msMoving.ms_vRelativeCenter1)-msMoving.ms_fR; // if first point is in front and second point is behind if (fDistance0>=0 && fDistance1<0) { // calculate fraction of line before intersection FLOAT fFraction = fDistance0/(fDistance0-fDistance1); ASSERT(fFraction>=0.0f && fFraction<=1.0f); // if fraction is less than minimum found fraction if (fFractionsa_Array; int i; const int count = cm_pamsA->sa_Count; for (i = 0; i < count; i++, itmsMoving++) { // clip moving sphere to the polygon ClipMovingSphereToTerrainPolygon(*itmsMoving, v0, v1, v2); } _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CLIPMOVETOBRUSHPOLYGON); } /* * Clip movement to a brush polygon. */ void CClipMove::ClipMoveToBrushPolygon(CBrushPolygon *pbpoPolygon) { _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_CLIPMOVETOBRUSHPOLYGON); // for each sphere of entity A //FOREACHINSTATICARRAY(*cm_pamsA, CMovingSphere, itmsMoving) { CMovingSphere *itmsMoving = cm_pamsA->sa_Array; int i; const int count = cm_pamsA->sa_Count; for (i = 0; i < count; i++, itmsMoving++) { // clip moving sphere to the polygon ClipMovingSphereToBrushPolygon(*itmsMoving, pbpoPolygon); } _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CLIPMOVETOBRUSHPOLYGON); } /* * Project spheres of moving entity to standing entity space. */ void CClipMove::ProjectASpheresToB(void) { _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_PROJECTASPHERESTOB); // for each sphere FOREACHINSTATICARRAY(*cm_pamsA, CMovingSphere, itmsA) { // project it in start point itmsA->ms_vRelativeCenter0 = itmsA->ms_vCenter*cm_mAToB0+cm_vAToB0; // project it in end point itmsA->ms_vRelativeCenter1 = itmsA->ms_vCenter*cm_mAToB1+cm_vAToB1; // make bounding box itmsA->ms_boxMovement = FLOATaabbox3D(itmsA->ms_vRelativeCenter0, itmsA->ms_vRelativeCenter1); itmsA->ms_boxMovement.Expand(itmsA->ms_fR); } _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_PROJECTASPHERESTOB); } /* Find movement box in absolute space for A entity. */ void CClipMove::FindAbsoluteMovementBoxForA(void) { /* // position at beginning of movement is absolute position FLOAT3D &vPosition0 = cm_vA0; FLOATmatrix3D &mRotation0 = cm_mA0; FLOATmatrix3D mB0Abs, mB1Abs; FLOAT3D vB0Abs, vB1Abs; // make absolute positions of B0 and B1 mB0Abs.Diagonal(1.0f); vB0Abs = FLOAT3D(0,0,0); vAToB0 mAToB0 MatrixMulT(cm_mB1, cm_mB0, mB1Abs); VectMulT(cm_mB1, cm_vB0-cm_vB1, vB1Abs); // these are used for making projections for converting from X space to Y space this way: // MatrixMulT(mY, mX, mXToY); // VectMulT(mY, vX-vY, vXToY); FLOAT3D vPosition1; FLOATmatrix3D mRotation1; MatrixMulT(mB1Abs, cm_mA0, mRotation1); VectMulT(mB1Abs, cm_vA0-vB1Abs, vPosition1); FLOATaabbox3D box0, box1; cm_penA->en_pciCollisionInfo->MakeBoxAtPlacement(vPosition0, mRotation0, box0); cm_penA->en_pciCollisionInfo->MakeBoxAtPlacement(vPosition1, mRotation1, box1); cm_boxMovementPathAbsoluteA = box0; cm_boxMovementPathAbsoluteA |= box1; */ cm_boxMovementPathAbsoluteA = FLOATaabbox3D(); // for each sphere FOREACHINSTATICARRAY(*cm_pamsA, CMovingSphere, itmsA) { // project it in start point FLOAT3D v0 = (itmsA->ms_vCenter*cm_mAToB0+cm_vAToB0)*cm_mB0+cm_vB0; // project it in end point FLOAT3D v1 = (itmsA->ms_vCenter*cm_mAToB1+cm_vAToB1)*cm_mB0+cm_vB0; // make bounding box FLOATaabbox3D box = FLOATaabbox3D(v0, v1); box.Expand(itmsA->ms_fR); cm_boxMovementPathAbsoluteA|=box; } } /* * Clip movement if B is a model. */ void CClipMove::ClipModelMoveToModel(void) { _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_CLIPMODELMOVETOMODEL); _pfPhysicsProfile.IncrementCounter(CPhysicsProfile::PCI_MODELMODELTESTS); // assumes that all spheres in one entity have same radius FLOAT fRB = (*cm_pamsB)[0].ms_fR; // for each sphere in entity A FOREACHINSTATICARRAY(*cm_pamsA, CMovingSphere, itmsA) { CMovingSphere &msA = *itmsA; FLOATaabbox3D &boxMovingSphere = msA.ms_boxMovement; // for each sphere in entity B FOREACHINSTATICARRAY(*cm_pamsB, CMovingSphere, itmsB) { CMovingSphere &msB = *itmsB; // if the sphere is too far if ( (boxMovingSphere.Min()(1)>msB.ms_vCenter(1)+fRB) || (boxMovingSphere.Max()(1)msB.ms_vCenter(2)+fRB) || (boxMovingSphere.Max()(2)msB.ms_vCenter(3)+fRB) || (boxMovingSphere.Max()(3)en_pbrBrush->GetFirstMip(); // for each sector in the brush mip FOREACHINDYNAMICARRAY(pbmMip->bm_abscSectors, CBrushSector, itbsc) { // if the sector's bbox has no contact with bbox of movement path if ( !itbsc->bsc_boxBoundingBox.HasContactWith(cm_boxMovementPathAbsoluteA, 0.01f) ) { // skip it continue; } // for each polygon in the sector FOREACHINSTATICARRAY(itbsc->bsc_abpoPolygons, CBrushPolygon, itbpo) { // if it is passable or its bbox has no contact with bbox of movement path if ((itbpo->bpo_ulFlags&BPOF_PASSABLE) ||!itbpo->bpo_boxBoundingBox.HasContactWith(cm_boxMovementPathAbsoluteA, 0.01f) ) { // skip it continue; } // clip movement to the polygon ClipMoveToBrushPolygon(itbpo); } } _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CLIPBRUSHMOVETOMODEL); } /* * Prepare projections and spheres for movement clipping. */ void CClipMove::PrepareProjectionsAndSpheres(void) { // Formula: C=AxB --> Cij=Sum(s=1..k)(Ais*Bsj) _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_PREPAREPROJECTIONSANDSPHERES); // make projections for converting from A space to B space MatrixMulT(cm_mB0, cm_mA0, cm_mAToB0); VectMulT(cm_mB0, cm_vA0-cm_vB0, cm_vAToB0); MatrixMulT(cm_mB1, cm_mA1, cm_mAToB1); VectMulT(cm_mB1, cm_vA1-cm_vB1, cm_vAToB1); // projection for converting from B space to absolute space cm_mBToAbsolute = cm_mB0; cm_vBToAbsolute = cm_vB0; // project spheres of entity A to space B ProjectASpheresToB(); _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_PREPAREPROJECTIONSANDSPHERES); } /* * Clip movement to a model entity. */ void CClipMove::ClipMoveToModel(CEntity *penModel) { _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_CLIPMOVETOMODEL); _pfPhysicsProfile.IncrementCounter(CPhysicsProfile::PCI_MODELXTESTS); // if not possibly colliding ASSERT(penModel->en_pciCollisionInfo!=NULL); const FLOATaabbox3D &boxModel = penModel->en_pciCollisionInfo->ci_boxCurrent; if ( (cm_boxMovementPath.Min()(1)>boxModel.Max()(1)) || (cm_boxMovementPath.Max()(1)boxModel.Max()(2)) || (cm_boxMovementPath.Max()(2)boxModel.Max()(3)) || (cm_boxMovementPath.Max()(3)en_pciCollisionInfo!=NULL); cm_pamsB = &penModel->en_pciCollisionInfo->ci_absSpheres; _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_CLIPMOVETOMODELNONTRIVIAL); // prepare new projections and spheres PrepareProjectionsAndSpheres(); // clip model to model ClipModelMoveToModel(); _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CLIPMOVETOMODELNONTRIVIAL); // if clipping a moving brush } else { // moving brush is B and still model is A cm_penA = penModel; GetPositionsOfEntity(cm_penA, cm_vA0, cm_mA0, cm_vA1, cm_mA1); // create bounding spheres for the model ASSERT(penModel->en_pciCollisionInfo!=NULL); cm_pamsA = &penModel->en_pciCollisionInfo->ci_absSpheres; // prepare new projections and spheres PrepareProjectionsAndSpheres(); FindAbsoluteMovementBoxForA(); // clip brush to model ClipBrushMoveToModel(); } _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CLIPMOVETOMODEL); } /* Cache near polygons of movable entity. */ void CClipMove::CacheNearPolygons(void) { // if movement box is still inside cached box if (cm_boxMovementPath<=cm_penMoving->en_boxNearCached) { // do nothing return; } _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_CACHENEARPOLYGONS); _pfPhysicsProfile.IncrementTimerAveragingCounter( CPhysicsProfile::PTI_CACHENEARPOLYGONS, 1); FLOATaabbox3D &box = cm_penMoving->en_boxNearCached; CStaticStackArray &apbpo = cm_penMoving->en_apbpoNearPolygons; // flush old cached polygons apbpo.PopAll(); // set new box to union of movement box and future estimate box = cm_boxMovementPath; box |= cm_penMoving->en_boxMovingEstimate; _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_CACHENEARPOLYGONS_ADDINITIAL); // for each zoning sector that this entity is in {FOREACHSRCOFDST(cm_penMoving->en_rdSectors, CBrushSector, bsc_rsEntities, pbsc) // add it to list of active sectors cm_lhActiveSectors.AddTail(pbsc->bsc_lnInActiveSectors); ENDFOR} _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CACHENEARPOLYGONS_ADDINITIAL); _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_CACHENEARPOLYGONS_MAINLOOP); // for each active sector FOREACHINLIST(CBrushSector, bsc_lnInActiveSectors, cm_lhActiveSectors, itbsc) { _pfPhysicsProfile.IncrementTimerAveragingCounter( CPhysicsProfile::PTI_CACHENEARPOLYGONS_MAINLOOP, 1); // for each polygon in the sector FOREACHINSTATICARRAY(itbsc->bsc_abpoPolygons, CBrushPolygon, itbpo) { CBrushPolygon *pbpo = itbpo; // if its bbox has no contact with bbox to cache if (!pbpo->bpo_boxBoundingBox.HasContactWith(box) ) { // skip it continue; } _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_CACHENEARPOLYGONS_MAINLOOPFOUND); _pfPhysicsProfile.IncrementTimerAveragingCounter( CPhysicsProfile::PTI_CACHENEARPOLYGONS_MAINLOOPFOUND, 1); // add it to cache apbpo.Push() = pbpo; // if it is passable if (pbpo->bpo_ulFlags&BPOF_PASSABLE) { // for each sector related to the portal {FOREACHDSTOFSRC(pbpo->bpo_rsOtherSideSectors, CBrushSector, bsc_rdOtherSidePortals, pbscRelated) // if the sector is not active if (!pbscRelated->bsc_lnInActiveSectors.IsLinked()) { // add it to active list cm_lhActiveSectors.AddTail(pbscRelated->bsc_lnInActiveSectors); } ENDFOR} } _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CACHENEARPOLYGONS_MAINLOOPFOUND); } // for non-zoning non-movable brush entities in the sector {FOREACHDSTOFSRC(itbsc->bsc_rsEntities, CEntity, en_rdSectors, pen) if (pen->en_RenderType==CEntity::RT_TERRAIN) { continue; } if (pen->en_RenderType!=CEntity::RT_BRUSH&& pen->en_RenderType!=CEntity::RT_FIELDBRUSH) { break; // brushes are sorted first in list } if(pen->en_ulPhysicsFlags&EPF_MOVABLE) { continue; } if(!MustTest(pen)) { continue; } _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_CLIPMOVETOBRUSHES_ADDNONZONING); // get first mip CBrushMip *pbm = pen->en_pbrBrush->GetFirstMip(); // if brush mip exists for that mip factor if (pbm!=NULL) { // for each sector in the mip {FOREACHINDYNAMICARRAY(pbm->bm_abscSectors, CBrushSector, itbscNonZoning) { CBrushSector &bscNonZoning = *itbscNonZoning; // add it to list of active sectors if(!bscNonZoning.bsc_lnInActiveSectors.IsLinked()) { cm_lhActiveSectors.AddTail(bscNonZoning.bsc_lnInActiveSectors); } }} } _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CLIPMOVETOBRUSHES_ADDNONZONING); ENDFOR} _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CLIPMOVETOBRUSHES_FINDNONZONING); } _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CACHENEARPOLYGONS_MAINLOOP); _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_CACHENEARPOLYGONS_CLEANUP); // clear list of active sectors {FORDELETELIST(CBrushSector, bsc_lnInActiveSectors, cm_lhActiveSectors, itbsc) { itbsc->bsc_lnInActiveSectors.Remove(); }} _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CACHENEARPOLYGONS_CLEANUP); _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CACHENEARPOLYGONS); } void CClipMove::ClipToNonZoningSector(CBrushSector *pbsc) { _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_CLIPTONONZONINGSECTOR); _pfPhysicsProfile.IncrementTimerAveragingCounter( CPhysicsProfile::PTI_CLIPTONONZONINGSECTOR, pbsc->bsc_abpoPolygons.Count()); // for each polygon in the sector //FOREACHINSTATICARRAY(pbsc->bsc_abpoPolygons, CBrushPolygon, itbpo) { CBrushPolygon *itbpo = pbsc->bsc_abpoPolygons.sa_Array; int i; const int count = pbsc->bsc_abpoPolygons.sa_Count; for (i = 0; i < count; i++, itbpo++) { // if its bbox has no contact with bbox of movement path, or it is passable __builtin_prefetch(&itbpo[1].bpo_ulFlags); if ((itbpo->bpo_ulFlags&BPOF_PASSABLE) ||!itbpo->bpo_boxBoundingBox.HasContactWith(cm_boxMovementPath)) { // skip it continue; } // clip movement to the polygon ClipMoveToBrushPolygon(itbpo); } _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CLIPTONONZONINGSECTOR); } void CClipMove::ClipToTerrain(CEntity *pen) { _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_CLIPTONONZONINGSECTOR); // _pfPhysicsProfile.IncrementTimerAveragingCounter( // CPhysicsProfile::PTI_CLIPTONONZONINGSECTOR, pbsc->bsc_abpoPolygons.Count()); CTerrain &tr = *pen->en_ptrTerrain; GFXVertex4 *pavVertices; INDEX_T *paiIndices; INDEX ctVertices,ctIndices; FLOAT3D vMin = cm_boxMovementPath.Min(); FLOAT3D vMax = cm_boxMovementPath.Max(); FLOATaabbox3D boxMovementPath; #define TRANSPT(x) (x-pen->en_plPlacement.pl_PositionVector) * !pen->en_mRotation boxMovementPath = TRANSPT(FLOAT3D(vMin(1),vMin(2),vMin(3))); boxMovementPath |= TRANSPT(FLOAT3D(vMin(1),vMin(2),vMax(3))); boxMovementPath |= TRANSPT(FLOAT3D(vMax(1),vMin(2),vMin(3))); boxMovementPath |= TRANSPT(FLOAT3D(vMax(1),vMin(2),vMax(3))); boxMovementPath |= TRANSPT(FLOAT3D(vMin(1),vMax(2),vMin(3))); boxMovementPath |= TRANSPT(FLOAT3D(vMin(1),vMax(2),vMax(3))); boxMovementPath |= TRANSPT(FLOAT3D(vMax(1),vMax(2),vMin(3))); boxMovementPath |= TRANSPT(FLOAT3D(vMax(1),vMax(2),vMax(3))); /* boxMovementPath.minvect(1) /= tr.tr_vStretch(1); boxMovementPath.minvect(3) /= tr.tr_vStretch(3); boxMovementPath.maxvect(1) /= tr.tr_vStretch(1); boxMovementPath.maxvect(3) /= tr.tr_vStretch(3); */ ExtractPolygonsInBox(&tr,boxMovementPath,&pavVertices,&paiIndices,ctVertices,ctIndices); // for each triangle for(INDEX iTri=0;iTri &apbpo = cm_penMoving->en_apbpoNearPolygons; _pfPhysicsProfile.IncrementTimerAveragingCounter( CPhysicsProfile::PTI_CLIPTOZONINGSECTOR, apbpo.Count()); // for each cached polygon for(INDEX iPolygon=0; iPolygonbpo_pbscSector != pbsc || !pbpo->bpo_boxBoundingBox.HasContactWith(cm_boxMovementPath)) { // skip it continue; } // if it is not passable if (!(pbpo->bpo_ulFlags&BPOF_PASSABLE)) { // clip movement to the polygon ClipMoveToBrushPolygon(pbpo); // if it is passable } else { // for each sector related to the portal {FOREACHDSTOFSRC(pbpo->bpo_rsOtherSideSectors, CBrushSector, bsc_rdOtherSidePortals, pbscRelated) // if the sector is not active if (pbscRelated->bsc_pbmBrushMip->IsFirstMip() && !pbscRelated->bsc_lnInActiveSectors.IsLinked()) { // add it to active list cm_lhActiveSectors.AddTail(pbscRelated->bsc_lnInActiveSectors); } ENDFOR} } } _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CLIPTOZONINGSECTOR); } /* Clip movement to brush sectors near the entity. */ void CClipMove::ClipMoveToBrushes(void) { // we never clip moving brush to a brush if (cm_bMovingBrush) { return; } if (cm_penMoving->en_ulCollisionFlags&ECF_IGNOREBRUSHES) { return; } _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_CLIPMOVETOBRUSHES); _pfPhysicsProfile.IncrementTimerAveragingCounter(CPhysicsProfile::PTI_CLIPMOVETOBRUSHES, 1); _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_CLIPMOVETOBRUSHES_ADDINITIAL); // for each zoning sector that this entity is in {FOREACHSRCOFDST(cm_penMoving->en_rdSectors, CBrushSector, bsc_rsEntities, pbsc) _pfPhysicsProfile.IncrementTimerAveragingCounter( CPhysicsProfile::PTI_CLIPMOVETOBRUSHES_ADDINITIAL, 1); // if it collides with this one if (pbsc->bsc_pbmBrushMip->IsFirstMip() && pbsc->bsc_pbmBrushMip->bm_pbrBrush->br_pfsFieldSettings==NULL && MustTest(pbsc->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity)) { // add it to list of active sectors cm_lhActiveSectors.AddTail(pbsc->bsc_lnInActiveSectors); } ENDFOR} _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CLIPMOVETOBRUSHES_ADDINITIAL); _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_CLIPMOVETOBRUSHES_MAINLOOP); // for each active sector FOREACHINLIST(CBrushSector, bsc_lnInActiveSectors, cm_lhActiveSectors, itbsc) { _pfPhysicsProfile.IncrementTimerAveragingCounter( CPhysicsProfile::PTI_CLIPMOVETOBRUSHES_MAINLOOP, 1); _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_CLIPMOVETOBRUSHES_FINDNONZONING); // for non-zoning brush entities in the sector {FOREACHDSTOFSRC(itbsc->bsc_rsEntities, CEntity, en_rdSectors, pen) if (pen->en_RenderType!=CEntity::RT_BRUSH&& pen->en_RenderType!=CEntity::RT_FIELDBRUSH&& pen->en_RenderType!=CEntity::RT_TERRAIN) { break; // brushes are sorted first in list } if(!MustTest(pen)) { continue; } if (pen->en_RenderType==CEntity::RT_TERRAIN) { // remember currently tested entity cm_penTested = pen; // moving model is A and still terrain is B cm_penB = pen; GetPositionsOfEntity(cm_penB, cm_vB0, cm_mB0, cm_vB1, cm_mB1); // prepare new projections and spheres PrepareProjectionsAndSpheres(); // clip movement to the terrain ClipToTerrain(pen); // don't process as brush continue; } _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_CLIPMOVETOBRUSHES_ADDNONZONING); // get first mip CBrushMip *pbm = pen->en_pbrBrush->GetFirstMip(); // if brush mip exists for that mip factor if (pbm!=NULL) { // for each sector in the mip {FOREACHINDYNAMICARRAY(pbm->bm_abscSectors, CBrushSector, itbscNonZoning) { CBrushSector &bscNonZoning = *itbscNonZoning; // add it to list of active sectors if(!bscNonZoning.bsc_lnInActiveSectors.IsLinked()) { cm_lhActiveSectors.AddTail(bscNonZoning.bsc_lnInActiveSectors); } }} } _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CLIPMOVETOBRUSHES_ADDNONZONING); ENDFOR} _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CLIPMOVETOBRUSHES_FINDNONZONING); // get the sector's brush mip, brush and entity CBrushMip *pbmBrushMip = itbsc->bsc_pbmBrushMip; CBrush3D *pbrBrush = pbmBrushMip->bm_pbrBrush; ASSERT(pbrBrush!=NULL); CEntity *penBrush = pbrBrush->br_penEntity; ASSERT(penBrush!=NULL); // remember currently tested entity cm_penTested = penBrush; // moving model is A and still brush is B cm_penB = penBrush; GetPositionsOfEntity(cm_penB, cm_vB0, cm_mB0, cm_vB1, cm_mB1); // prepare new projections and spheres PrepareProjectionsAndSpheres(); // clip movement to the sector if (penBrush->en_ulFlags&ENF_ZONING) { ClipToZoningSector(itbsc); } else { ClipToNonZoningSector(itbsc); } } _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CLIPMOVETOBRUSHES_MAINLOOP); _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_CLIPMOVETOBRUSHES_CLEANUP); // clear list of active sectors {FORDELETELIST(CBrushSector, bsc_lnInActiveSectors, cm_lhActiveSectors, itbsc) { itbsc->bsc_lnInActiveSectors.Remove(); }} _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CLIPMOVETOBRUSHES_CLEANUP); _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CLIPMOVETOBRUSHES); } /* Clip movement to models near the entity. */ void CClipMove::ClipMoveToModels(void) { if (cm_penMoving->en_ulCollisionFlags&ECF_IGNOREMODELS) { return; } // create mask for skipping deleted entities ULONG ulSkipMask = ENF_DELETED; // if the moving entity is predictor if (cm_penMoving->IsPredictor()) { // add predicted entities to the mask ulSkipMask |= ENF_PREDICTED; } _pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_CLIPMOVETOMODELS); // find colliding entities near the box of movement path static CStaticStackArray apenNearEntities; cm_pwoWorld->FindEntitiesNearBox(cm_boxMovementPath, apenNearEntities); // for each of the found entities {for(INDEX ienFound=0; ienFounden_ulFlags&ENF_DELETED) ||!(cm_penMoving->en_ulCollisionFlags&ECF_TESTMASK)) { // skip clipping _pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_CLIPMOVETOWORLD); return; } cm_pwoWorld = pwoWorld; // prepare flags masks for testing which entities collide with this cm_ulTestMask1 = ((cm_penMoving->en_ulCollisionFlags&ECF_TESTMASK)>>ECB_TEST)<en_ulCollisionFlags&ECF_ISMASK )>>ECB_IS )<en_ulCollisionFlags&ECF_PASSMASK)>>ECB_PASS)<en_ulCollisionFlags&ECF_ISMASK )>>ECB_IS )<