以下为个人学习笔记整理。参考 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 特性的情况下,碰撞才会被拦截。

image-20221011171612511

通过上图的关系,就可以引申出 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 {/* ... */}
}

核心在 staticPrunerdynamicPruner,它们都是 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 提供的高效查询,接下来就可以快速定位需要检测的物体,并进行碰撞检测了。由于篇幅限制这里只简单介绍 Boxoverlap 模式下的检测:

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 结果做 preFilterpreFilter 主要是剔除发生理论会碰撞,但在规则层面需要进行忽略的部分。例如子弹命中友军,正常情况下会打中,但在很多 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;
}

由于 preFilterpostFilter 都是可以定制化规则的,这里也不再展开,下面主要来看一下高精度检测的代码。还是老样子,以 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 操作实际上是计算一个物体是否会和其他空间内的对象发生重叠,本质上并没有 BlockTouch 的概念,因为不论是 Block 还是 Touch,最终都已经重叠了。

但对于 raycastsweep 却不同,它们都有一个起始位置和运动路径,换句话说物体可能运动过程中就会被挡住而停下,因此会有一些不同的逻辑:

  • 需要计算出最近的 Block 碰撞点,这部分的检测算法相对比较复杂,以后有机会单独聊聊。
  • 需要剔除「最近 Block 点」往后的所有命中点。

# 总结

至此,所有关于碰撞检测的执行流程,就已经基本清晰了。剩下的无外乎其他规则下的其他检测手法问题,大体流程近乎一致。

至于未曾展开的 Simulate 中的 Filter 规则以及更为复杂的碰撞实现算法:SAT、PCM、GJK、CCD 之后有机会再来介绍。

更新于 阅读次数

请我[恰饭]~( ̄▽ ̄)~*

鑫酱(●'◡'●) 微信支付

微信支付

鑫酱(●'◡'●) 支付宝

支付宝