以下为个人学习笔记整理。参考 PhysX SDK 3.4.0 文档,部分代码可能来源于更高版本。
# PhysX——Collision 篇
碰撞检测常常用来计算物体之间的碰撞,在动作游戏的攻击受击、射击游戏中子弹命中等有广泛的运用。要让游戏更加趋近真实,碰撞是其中必不可少的一环。
常规的检测方式有以下三种:
- raycasts:射线检测。
- sweeps:扫描检测。
- overlaps:重叠检测。
由于 PhysX 的 Collision 实在过于复杂,因此本文主要以 overlap 检测作为入口,一步步分析其中的原理。
//--------------------- 官方用法 --------------------- | |
PxOverlapBuffer hit; // [out] Overlap results | |
PxGeometry overlapShape = ...; // [in] shape to test for overlaps | |
PxTransform shapePose = ...; // [in] initial shape pose (at distance=0) | |
PxOverlapBuffer hit; | |
bool status = scene->overlap(overlapShape, shapePose, hit); |
overlap 函数的定义如下:
//--------------------- overlap 函数定义 --------------------- | |
bool NpSceneQueries::overlap( | |
const PxGeometry& geometry, const PxTransform& pose, PxOverlapCallback& hits, | |
const PxQueryFilterData& filterData, PxQueryFilterCallback* filterCall) const | |
{ | |
PX_PROFILE_ZONE("SceneQuery.overlap", getContextId()); | |
NP_READ_CHECK(this); | |
PX_SIMD_GUARD; | |
MultiQueryInput input(&geometry, &pose); | |
// we are not supporting cache for overlaps for some reason | |
return multiQuery<PxOverlapHit>(input, hits, PxHitFlags(), NULL, filterData, filterCall, NULL); | |
} |
其中涉及到的几个参数:
- PxGeometry:检测物体本身的几何数据。
- PxTransform:检测物体的坐标及朝向。
- PxOverlapCallback:获取碰撞检测结果的容器,其中会保存命中的详细检测结果如法线、坐标、距离等。
- PxQueryFilterData:过滤标记数据。
- PxQueryFilterCallback:过滤处理函数,用来处理过滤规则,支持开发者定制。
# 碰撞过滤(Filter)
对于复杂场景下的碰撞检测,往往需要检测的物体数量众多,如何精确的筛选出需要被检测的物体,从而减少检测次数提高运行效率尤为重要。PhysX 通过 PxFilterData 四个通道标记来筛选出需要进行检测的物体,至于具体的筛选规则和各个通道的定义则交给用户自己。
PxFilterData 定义非常简单:总共 128 bit,通过 4 个 32bit 的字段表示。
struct PxFilterData | |
{ | |
PxU32 word0; | |
PxU32 word1; | |
PxU32 word2; | |
PxU32 word3; | |
}; |
# 自定义过滤字段
其中的每个 Word,PhysX 并没有给出定义,需要开发者自己定制规则。这里以 UE4 中的定义作为例子进行介绍:
UE4 中使用碰撞检测和碰撞过滤的地方有三大类:
- QueryFilterData:查询操作预设的过滤项。
- ShapeFilterData:对象参与查询时自身具备的过滤项。
- SimulateFilterData:对象参与模拟时自身具备的过滤项。
# QueryFilterData
QueryFilterData 用来定义查询操作的过滤规则,比较常见的应用场景是计算光线命中 (LineTrace),LineTrace 有两种查询的过滤方式:
enum class ECollisionQuery : uint8 | |
{ | |
ObjectQuery = 0, | |
TraceQuery = 1 | |
}; |
# ObjectQuery
ObjectQuery 中 FilterData 中每个字段的含义:
- Word0:ObjectQuery
- Word1:ECollisionChannel
- Word2:无意义。
- Word3:FMaskFilter [6 位] + ECollisionChannel [5 位] + EPhysXFilterDataFlags [21 位]
enum { NumCollisionChannelBits = 5 }; | |
enum { NumExtraFilterBits = 6 }; | |
enum { NumFilterDataFlagBits = 32 - NumExtraFilterBits - NumCollisionChannelBits }; | |
// Word3 计算:MaskFilter | |
inline uint32 CreateChannelAndFilter(ECollisionChannel CollisionChannel, FMaskFilter MaskFilter) | |
{ | |
uint32 ResultMask = (uint32(MaskFilter) << NumCollisionChannelBits) | (uint32)CollisionChannel; | |
return ResultMask << NumFilterDataFlagBits; | |
} |
其中 FMaskFilter 提供给开发者定义的屏蔽 Mask。如果 Query 的 Mask 和对象本身的 Mask 与操作不等于 0 的情况下将被忽略。
if ((QuerierMaskFilter & ShapeMaskFilter) != 0) //If ignore mask hit something, ignore it | |
{ | |
return ECollisionQueryHitType::None; | |
} |
ECollisionChannel 可以理解为碰撞检测的通道:
enum ECollisionChannel | |
{ | |
/** 已定义的 8 个通道 */ | |
ECC_WorldStatic UMETA(DisplayName="WorldStatic"), | |
ECC_WorldDynamic UMETA(DisplayName="WorldDynamic"), | |
ECC_Pawn UMETA(DisplayName="Pawn"), | |
ECC_Visibility UMETA(DisplayName="Visibility" , TraceQuery="1"), | |
ECC_Camera UMETA(DisplayName="Camera" , TraceQuery="1"), | |
ECC_PhysicsBody UMETA(DisplayName="PhysicsBody"), | |
ECC_Vehicle UMETA(DisplayName="Vehicle"), | |
ECC_Destructible UMETA(DisplayName="Destructible"), | |
/** 引擎预留的 6 个通道 */ | |
ECC_EngineTraceChannel1 UMETA(Hidden), | |
// ... | |
ECC_EngineTraceChannel6 UMETA(Hidden), | |
/** 未定义的 18 个通道 */ | |
ECC_GameTraceChannel1 UMETA(Hidden), | |
// ... | |
ECC_GameTraceChannel18 UMETA(Hidden), | |
}; |
通过定义不同的通道来对不同类别的对象做筛选,例如 ECC_Visibility 通道的对象会被「看见」,会在计算光线命中的时候参与检测。
EPhysXFilterDataFlags 标识本次碰撞检测需要采用的检测方法。
enum EPhysXFilterDataFlags | |
{ | |
EPDF_SimpleCollision = 0x0001, | |
EPDF_ComplexCollision = 0x0002, | |
EPDF_CCD = 0x0004, | |
EPDF_ContactNotify = 0x0008, | |
EPDF_StaticShape = 0x0010, | |
EPDF_ModifyContacts = 0x0020, | |
EPDF_KinematicKinematicPairs = 0x0040, | |
}; |
ObjectQuery 中 Word3 的 ECollisionChannel 用来标识 MultiTrace,并不具体指代对象的碰撞通道。
const int32 MultiTrace; /*=1 if multi. 0 otherwise*/ | |
NewData.Word3 |= CreateChannelAndFilter((ECollisionChannel)MultiTrace, ObjectParam.IgnoreMask); |
ObjectQuery 会通过 Actor 本身的 ECollisionChannel 和自身的 QueryFilter.Word1 做 & 运算来过滤出所需的对象。
# TraceQuery
TraceQuery 中 FilterData 中每个字段的含义:
- Word0:TraceQuery
- Word1:BlockingBits
- Word2:TouchingBits
- Word3:FMaskFilter[6] + ECollisionChannel[5] + EPhysXFilterDataFlags[21]
在介绍 BlockingBits && TouchingBits 之前,先来了解几个概念:
UE4 中描述碰撞的类型分为两种:Block && Touch
enum class ECollisionQueryHitType : uint8 | |
{ | |
None = 0, | |
Touch = 1, | |
Block = 2 | |
}; |
- Block:表示碰撞会被拦截:如人和墙壁之间的碰撞,相撞后就会被阻挡,如果看到一个物体,那么它后面的内容将被挡住。
- Touch:表示碰撞但会穿过物体:如人和水、云等碰撞,如果看到一个半透明或者透明物体,那么它后面的内容也可以被看见。
这是一张碰撞关系表,只有当两个物体都具备 Block 特性的情况下,碰撞才会被拦截。
通过上图的关系,就可以引申出 word1 && word2 的定义。
- Word1:在 UE4 中也被称为 BlockingBits。代表该对象会和哪些 ECollisionChannel 发生 Block 碰撞。
- Word2:在 UE4 中也被称为 TouchingBits。代表该对象会和哪些 ECollisionChannel 发生 Touch 碰撞。
FPhysicsFilterBuilder::FPhysicsFilterBuilder(TEnumAsByte<enum ECollisionChannel> InObjectType, FMaskFilter MaskFilter, const struct FCollisionResponseContainer& ResponseToChannels) | |
: BlockingBits(0) | |
, TouchingBits(0) | |
, Word3(0) | |
{ | |
for (int32 i = 0; i < UE_ARRAY_COUNT(ResponseToChannels.EnumArray); i++) | |
{ | |
if (ResponseToChannels.EnumArray[i] == ECR_Block) | |
{ | |
const uint32 ChannelBit = CRC_TO_BITFIELD(i); | |
BlockingBits |= ChannelBit; | |
} | |
else if (ResponseToChannels.EnumArray[i] == ECR_Overlap) | |
{ | |
const uint32 ChannelBit = CRC_TO_BITFIELD(i); | |
TouchingBits |= ChannelBit; | |
} | |
} | |
// ... | |
} |
TraceQuery 则相对复杂一些:需要分别用两者的 ECollisionChannel 和对方的 BlockingBits && TouchingBits 做计算,得到最终的碰撞类型,然后通过上述碰撞关系表得到最终的碰撞结果,例如人和雾的碰撞是 Block 的,而雾和人的碰撞是 Touch 的,那么最终结果也是 Touch,人和雾会互相穿过。
其实可以简单把 ObjectQuery 和 TraceQuery 理解成:一个是单向查询,一个是双向查询。单向查询相对简单适用于一些简单场景,如射线检测。双向查询则相对复杂,需要考虑查询方和被查询方两者的关系,应用于物体间碰撞的检测比较合适。
# ShapeFilterData
ShapeFilterData 则是用来定义对象自身的过滤规则,相比于 QueryFilterData 侧重于描述本次查询的规则信息,ShapeFilterData 更侧重描述 Shape 本身的性质:
inline void GetQueryData(uint32 ActorID, uint32& OutWord0, uint32& OutWord1, uint32& OutWord2, uint32& OutWord3) const | |
{ | |
/** | |
* Format for QueryData : | |
* word0 (object ID) | |
* word1 (blocking channels) | |
* word2 (touching channels) | |
* word3 (ExtraFilter (top NumExtraFilterBits) + MyChannel (next NumCollisionChannelBits) as ECollisionChannel + Flags (remaining NumFilterDataFlagBits) | |
*/ | |
OutWord0 = ActorID; | |
OutWord1 = BlockingBits; | |
OutWord2 = TouchingBits; | |
OutWord3 = Word3; | |
} |
- Word0:ActorID
- Word1:BlockingBits
- Word2:TouchingBits
- Word3:FMaskFilter[6] + ECollisionChannel[5] + EPhysXFilterDataFlags[21]
# SimulateFilterData
SimulateFilterData 是用在 simulate 物理碰撞中采用的过滤规则。
inline void GetSimData(uint32 BodyIndex, uint32 ComponentID, uint32& OutWord0, uint32& OutWord1, uint32& OutWord2, uint32& OutWord3) const | |
{ | |
/** | |
* Format for SimData : | |
* word0 (body index) | |
* word1 (blocking channels) | |
* word2 (skeletal mesh component ID) | |
* word3 (ExtraFilter (top NumExtraFilterBits) + MyChannel (next NumCollisionChannelBits) as ECollisionChannel + Flags (remaining NumFilterDataFlagBits) | |
*/ | |
OutWord0 = BodyIndex; | |
OutWord1 = BlockingBits; | |
OutWord2 = ComponentID; | |
OutWord3 = Word3; | |
} |
- Word0:BodyIndex。表示 Body 编号。
- Word1:BlockingBits
- Word2:ComponentID。表示 Body 中的 Component 编号。
- Word3:FMaskFilter[6] + ECollisionChannel[5] + EPhysXFilterDataFlags[21]
# 自定义过滤规则
同样就像 PhysX 没有给出 Word 的定义一样,PhysX 也没有给出使用 Word 的过滤规则,而是提供了一个过滤类 ——PxQueyFilterCallback:
class PxQueryFilterCallback | |
{ | |
public: | |
virtual PxQueryHitType::Enum preFilter( | |
const PxFilterData& filterData, const PxShape* shape, const PxRigidActor* actor, PxHitFlags& queryFlags) = 0; | |
virtual PxQueryHitType::Enum postFilter(const PxFilterData& filterData, const PxQueryHit& hit) = 0; | |
virtual ~PxQueryFilterCallback() {} | |
}; |
这个类中的函数会在调用 raycast、overlap、sweep 等操作时调用,后面会详细介绍这里不做展开。因此可以通过继承该类来实现自己的 Filter 规则。
- preFilter:该过滤会在低精度检测命中后、高精度检测前执行。用来剔除不必要的检测,减少高精度检测开销。
- postFilter:该过滤会在高精度检测命中后执行。对高精度检测的命中结果做最后的筛选。
上面提到的 UE4 中两种 Query 方式,便是通过重写 preFilter 来实现的:
class ICollisionQueryFilterCallbackBase: public PxQueryFilterCallback | |
{ | |
// ... | |
virtual ECollisionQueryHitType PostFilter(const FCollisionFilterData& FilterData, const physx::PxQueryHit& Hit) = 0; | |
virtual ECollisionQueryHitType PreFilter(const FCollisionFilterData& FilterData, const physx::PxShape& Shape, physx::PxRigidActor& Actor) = 0; | |
} | |
// QueryFilter | |
class FCollisionQueryFilterCallback : public ICollisionQueryFilterCallbackBase | |
{ | |
static ECollisionQueryHitType CalcQueryHitType(const FCollisionFilterData& QueryFilter, const FCollisionFilterData& ShapeFilter, bool bPreFilter = false); | |
ECollisionQueryHitType PreFilterImp(const FCollisionFilterData& FilterData, const FCollisionFilterData& ShapeFilterData, uint32 ComponentID, const FBodyInstance* BodyInstance); | |
} | |
// 过滤规则核心实现 | |
ECollisionQueryHitType FCollisionQueryFilterCallback::CalcQueryHitType(const FCollisionFilterData& QueryFilter, const FCollisionFilterData& ShapeFilter, bool bPreFilter) | |
{ | |
ECollisionQuery QueryType = (ECollisionQuery)QueryFilter.Word0; | |
FMaskFilter QuerierMaskFilter; | |
const ECollisionChannel QuerierChannel = GetCollisionChannelAndExtraFilter(QueryFilter.Word3, QuerierMaskFilter); | |
FMaskFilter ShapeMaskFilter; | |
const ECollisionChannel ShapeChannel = GetCollisionChannelAndExtraFilter(ShapeFilter.Word3, ShapeMaskFilter); | |
if ((QuerierMaskFilter & ShapeMaskFilter) != 0) //If ignore mask hit something, ignore it | |
{ | |
return ECollisionQueryHitType::None; | |
} | |
const uint32 ShapeBit = ECC_TO_BITFIELD(ShapeChannel); | |
if (QueryType == ECollisionQuery::ObjectQuery) | |
{ | |
// ObjectQuery filter | |
} | |
else | |
{ | |
// TraceQuery filter | |
} | |
return ECollisionQueryHitType::None; | |
} |
# 场景查询(SceneQuery)
通过 Filter 我们可以知道哪些 Shape 的碰撞我们可以忽略,但我们仍然避免不了需要先对所有需要参与检测的 Shape 做一次低精度的碰撞检测;这显然是不必要的,有些明显就不可能发生的碰撞该如何尽可能的忽略呢?
让我们回到 overlap 的代码:
return multiQuery<PxOverlapHit>(input, hits, PxHitFlags(), NULL, filterData, filterCall, NULL); |
NpSceneQueries 里有我们想要的答案:
// 只保留了和 overlap 有关的核心代码,有兴趣的可以自行翻阅源码 | |
template<typename HitType> | |
bool NpSceneQueries::multiQuery( | |
const MultiQueryInput& input, PxHitCallback<HitType>& hits, PxHitFlags hitFlags, const PxQueryCache* cache, | |
const PxQueryFilterData& filterData, PxQueryFilterCallback* filterCall, BatchQueryFilterData* bfd) const | |
{ | |
const Pruner* staticPruner = mSQManager.get(PruningIndex::eSTATIC).pruner(); | |
const Pruner* dynamicPruner = mSQManager.get(PruningIndex::eDYNAMIC).pruner(); | |
if(HitTypeSupport<HitType>::IsRaycast){/* ... */} | |
else if(HitTypeSupport<HitType>::IsOverlap) | |
{ | |
const ShapeData sd(*input.geometry, *input.pose, input.inflation); | |
pcb.mShapeData = &sd; | |
PxAgain again = doStatics ? staticPruner->overlap(sd, pcb) : true; | |
if(!again) | |
return hits.hasAnyHits(); | |
if(doDynamics) | |
again = dynamicPruner->overlap(sd, pcb); | |
cbr.again = again; | |
return hits.hasAnyHits(); | |
}else {/* ... */} | |
} |
核心在 staticPruner 和 dynamicPruner,它们都是 AABBPruner 的实例:
SceneQueryManager::SceneQueryManager(Scb::Scene& scene, PxPruningStructureType::Enum staticStructure, | |
PxPruningStructureType::Enum dynamicStructure, | |
PxU32 dynamicTreeRebuildRateHint, const PxSceneLimits& limits) : mScene(scene) | |
{ | |
mPrunerExt[PruningIndex::eSTATIC].init(staticStructure, scene.getContextId()); | |
mPrunerExt[PruningIndex::eDYNAMIC].init(dynamicStructure, scene.getContextId()); | |
// ... | |
} | |
void PrunerExt::init(PxPruningStructureType::Enum type, PxU64 contextID) | |
{ | |
switch(type) | |
{ | |
case PxPruningStructureType::eDYNAMIC_AABB_TREE: { pruner = PX_NEW(AABBPruner)(true, contextID); break; } | |
case PxPruningStructureType::eSTATIC_AABB_TREE: { pruner = PX_NEW(AABBPruner)(false, contextID); break; } | |
// ... | |
} | |
} |
唯一的区别就是 staticPruner 不支持增量重建(mIncrementalRebuild),因此 staticPruner 内任意对象的修改都需要对整个 staticPruner 进行重建。
void AABBPruner::commit() | |
{ | |
if(!mAABBTree || !mIncrementalRebuild) | |
{ | |
fullRebuildAABBTree(); | |
return; | |
} | |
} |
AABBPruner 可以简单理解成一个复杂的 BVH,细节可以参考之前的文章,由于篇幅限制,这里不做更细致的介绍。
# 碰撞检测(CollisionDetection)
有了 AABBTree 提供的高效查询,接下来就可以快速定位需要检测的物体,并进行碰撞检测了。由于篇幅限制这里只简单介绍 Box 在 overlap 模式下的检测:
PxAgain AABBPruner::overlap(const ShapeData& queryVolume, PrunerCallback& pcb) const | |
{ | |
// ... | |
switch(queryVolume.getType()) | |
{ | |
case PxGeometryType::eBOX: // box(aabb) overlap | |
// 核心部分 | |
const Gu::AABBAABBTest test(queryVolume.getPrunerInflatedWorldAABB()); | |
again = AABBTreeOverlap<Gu::AABBAABBTest, AABBTree, AABBTreeRuntimeNode>()(mPool.getObjects(), mPool.getCurrentWorldBoxes(), *mAABBTree, test, pcb); | |
// 增量的 AABBPruner 会用 mBucketPruner 存储增量节点,这部分内容也需要进行碰撞检测 | |
if(again && mIncrementalRebuild && mBucketPruner.getNbObjects()) | |
again = mBucketPruner.overlap(queryVolume, pcb); | |
// ... | |
return again; | |
} |
其中 AABBAABBTest 用来存储测试的包围盒数据,测试两个包围盒覆盖的代码如下:
// (box_1_extent + box_2_extent) >= dist(box_1_center, box_2_center) | |
// 中心点 x,y,z 的距离要小于两个包围盒长宽高的和 | |
V3AllGrtrOrEq(V3Add(mExtents, extents), V3Abs(V3Sub(center, mCenter))); |
整个查询流程大致如下:
【step.1】从根节点开始依次进行 AABBAABBTest,满足重叠条件情况下 DFS 子节点,直到遇到叶子节点。
【step.2】对叶子节点内的 Shape 做 AABBAABBTest,这里的测试是低精度检测 Broad-Phase,只是判断对象的包围盒是否和检测物发生碰撞。
【step.3】对 Broad-Phase 结果做 preFilter。preFilter 主要是剔除发生理论会碰撞,但在规则层面需要进行忽略的部分。例如子弹命中友军,正常情况下会打中,但在很多 PVE 游戏中会忽略友军的命中。
【step.4】对过滤后的结果做高精度检测 Narrow-Phase。场景查询使用的检测算法是 GJK,由于篇幅限制这里不做展开。
【step.5】对高精度检测结果再做一次 postFilter,这种过滤可以对命中的部位做过滤,应用场景还是比较少的,因为很多过滤其实都可以在 preFilter 完成。
【step.6】通过检查以后,会把命中结果存储在 PxOverlapCallback 返回给上层,根据各项条件参数来控制检查是否继续下去。
// 代码大量删减,如果感兴趣请自行查阅源代码 | |
virtual PxAgain invoke(PxReal& aDist, const PrunerPayload& aPayload) | |
{ | |
// 【step.3】进行预过滤 pre-filter | |
PxHitFlags filteredHitFlags = mHitFlags; | |
if(!applyAllPreFiltersSQ(&actorShape, shapeHitType/*in&out*/, filterFlags, mFilterData, mFilterCall, | |
mScene, mBfd, filteredHitFlags, mHitCall.maxNbTouches)) | |
return true; // skip this shape from reporting if prefilter said to do so | |
// 【step.4】高精度检测 | |
PxU32 nbSubHits = GeomQueryAny<HitType>::geomHit( | |
mScene, mInput, *mShapeData, shapeGeom, globalPose, | |
filteredHitFlags | mMeshAnyHitFlags, | |
maxSubHits1, subHits1, mShrunkDistance, mQueryShapeBoundsValid ? &mQueryShapeBounds : NULL); | |
// ------------------------- iterate over geometry subhits ----------------------------------- | |
for (PxU32 iSubHit = 0; iSubHit < nbSubHits; iSubHit++) | |
{ | |
// 【step.5】进行检测后的再过滤 post-filter | |
if(!mIsCached && (mFilterCall || mBfd) && (filterFlags & PxQueryFlag::ePOSTFILTER)) | |
{ | |
if(mFilterCall) hitType = mFilterCall->postFilter(mFilterData.data, hit); | |
// ... | |
} | |
} // for iSubHit | |
return true; | |
} |
由于 preFilter 和 postFilter 都是可以定制化规则的,这里也不再展开,下面主要来看一下高精度检测的代码。还是老样子,以 overlap 为例:
struct GeomQueryAny | |
{ | |
static PX_FORCE_INLINE PxU32 geomHit( | |
const NpSceneQueries& sceneQueries, const MultiQueryInput& input, const ShapeData& sd, | |
const PxGeometry& sceneGeom, const PxTransform& pose, PxHitFlags hitFlags, | |
PxU32 maxHits, HitType* hits, const PxReal shrunkMaxDistance, PxBounds3* precomputedBounds) | |
{ | |
if(HitTypeSupport<HitType>::IsOverlap) | |
{ | |
const GeomOverlapTable* overlapFuncs = sceneQueries.mCachedOverlapFuncs; | |
return PxU32(Gu::overlap(geom0, pose0, geom1, pose1, overlapFuncs)); | |
} | |
else | |
{ | |
PX_ALWAYS_ASSERT_MESSAGE("Unexpected template expansion in GeomQueryAny::geomHit"); | |
return 0; | |
} | |
} | |
}; |
所有类型之间的 overlap 计算都存储在 sceneQueries.mCachedOverlapFuncs
函数表里:
// 目前官方支持的种类如下,有兴趣可以一个个深挖的看实现 | |
g_SweepMethodTable | |
g_ContactMethodTable | |
g_PCMContactMethodTable | |
GeomOverlapTable gGeomOverlapMethodTable[] = | |
{ | |
//PxGeometryType::eSPHERE | |
{ | |
GeomOverlapCallback_SphereSphere, //PxGeometryType::eSPHERE | |
GeomOverlapCallback_SpherePlane, //PxGeometryType::ePLANE | |
GeomOverlapCallback_SphereCapsule, //PxGeometryType::eCAPSULE | |
GeomOverlapCallback_SphereBox, //PxGeometryType::eBOX | |
GeomOverlapCallback_SphereConvex, //PxGeometryType::eCONVEXMESH | |
GeomOverlapCallback_SphereMesh, //PxGeometryType::eTRIANGLEMESH | |
GeomOverlapCallback_HeightfieldUnregistered, //PxGeometryType::eHEIGHTFIELD | |
}, | |
//PxGeometryType::ePLANE | |
{ | |
0, //PxGeometryType::eSPHERE | |
GeomOverlapCallback_NotSupported, //PxGeometryType::ePLANE | |
GeomOverlapCallback_PlaneCapsule, //PxGeometryType::eCAPSULE | |
GeomOverlapCallback_PlaneBox, //PxGeometryType::eBOX | |
GeomOverlapCallback_PlaneConvex, //PxGeometryType::eCONVEXMESH | |
GeomOverlapCallback_NotSupported, //PxGeometryType::eTRIANGLEMESH | |
GeomOverlapCallback_NotSupported, //PxGeometryType::eHEIGHTFIELD | |
}, | |
//PxGeometryType::eCAPSULE | |
{ | |
0, //PxGeometryType::eSPHERE | |
0, //PxGeometryType::ePLANE | |
GeomOverlapCallback_CapsuleCapsule, //PxGeometryType::eCAPSULE | |
GeomOverlapCallback_CapsuleBox, //PxGeometryType::eBOX | |
GeomOverlapCallback_CapsuleConvex, //PxGeometryType::eCONVEXMESH | |
GeomOverlapCallback_CapsuleMesh, //PxGeometryType::eTRIANGLEMESH | |
GeomOverlapCallback_HeightfieldUnregistered, //PxGeometryType::eHEIGHTFIELD | |
}, | |
//PxGeometryType::eBOX | |
{ | |
0, //PxGeometryType::eSPHERE | |
0, //PxGeometryType::ePLANE | |
0, //PxGeometryType::eCAPSULE | |
GeomOverlapCallback_BoxBox, //PxGeometryType::eBOX | |
GeomOverlapCallback_BoxConvex, //PxGeometryType::eCONVEXMESH | |
GeomOverlapCallback_BoxMesh, //PxGeometryType::eTRIANGLEMESH | |
GeomOverlapCallback_HeightfieldUnregistered, //PxGeometryType::eHEIGHTFIELD | |
}, | |
//PxGeometryType::eCONVEXMESH | |
{ | |
0, //PxGeometryType::eSPHERE | |
0, //PxGeometryType::ePLANE | |
0, //PxGeometryType::eCAPSULE | |
0, //PxGeometryType::eBOX | |
GeomOverlapCallback_ConvexConvex, //PxGeometryType::eCONVEXMESH | |
GeomOverlapCallback_ConvexMesh, //PxGeometryType::eTRIANGLEMESH //not used: mesh always uses swept method for midphase. | |
GeomOverlapCallback_HeightfieldUnregistered, //PxGeometryType::eHEIGHTFIELD //TODO: make HF midphase that will mask this | |
}, | |
//PxGeometryType::eTRIANGLEMESH | |
{ | |
0, //PxGeometryType::eSPHERE | |
0, //PxGeometryType::ePLANE | |
0, //PxGeometryType::eCAPSULE | |
0, //PxGeometryType::eBOX | |
0, //PxGeometryType::eCONVEXMESH | |
GeomOverlapCallback_NotSupported, //PxGeometryType::eTRIANGLEMESH | |
GeomOverlapCallback_NotSupported, //PxGeometryType::eHEIGHTFIELD | |
}, | |
//PxGeometryType::eHEIGHTFIELD | |
{ | |
0, //PxGeometryType::eSPHERE | |
0, //PxGeometryType::ePLANE | |
0, //PxGeometryType::eCAPSULE | |
0, //PxGeometryType::eBOX | |
0, //PxGeometryType::eCONVEXMESH | |
0, //PxGeometryType::eTRIANGLEMESH | |
GeomOverlapCallback_NotSupported, //PxGeometryType::eHEIGHTFIELD | |
}, | |
}; |
试想一下,overlap 操作实际上是计算一个物体是否会和其他空间内的对象发生重叠,本质上并没有 Block 和 Touch 的概念,因为不论是 Block 还是 Touch,最终都已经重叠了。
但对于 raycast 和 sweep 却不同,它们都有一个起始位置和运动路径,换句话说物体可能运动过程中就会被挡住而停下,因此会有一些不同的逻辑:
- 需要计算出最近的 Block 碰撞点,这部分的检测算法相对比较复杂,以后有机会单独聊聊。
- 需要剔除「最近 Block 点」往后的所有命中点。
# 总结
至此,所有关于碰撞检测的执行流程,就已经基本清晰了。剩下的无外乎其他规则下的其他检测手法问题,大体流程近乎一致。
至于未曾展开的 Simulate 中的 Filter 规则以及更为复杂的碰撞实现算法:SAT、PCM、GJK、CCD 之后有机会再来介绍。