PhysX中raycast和sweep對block和touch的處理邏輯


零、說明

測試代碼基於PhysX_3.4

一、raycast和sweep的特殊性

在場景查詢中,raycast/sweep相對於overlap來說有一個重要的特性,就是前兩者是有明確方向性的,也就是有一個起點加上一個終點。這個和overlap完全不同,因為overlap是在一個范圍內的無差別覆蓋。這一點會引導出查詢時的一個很重要的概念:touching和blocking。
在PhysX的使用手冊中,說明了兩者的區別
https://docs.nvidia.com/gameworks/content/gameworkslibrary/physx/guide/Manual/SceneQueries.html#touching-and-blocking-hits
For queries with multiple results we distinguish between touching and blocking hits. The choice of whether a hit is touching or blocking is made by the user-implemented filtering logic. Intuitively a blocking hit prevents further progress of a raycast or a sweep along its path, and a touching hit is recorded but allows the ray or sweep to continue. So a multiple-hit query will return the closest blocking hit if one exists, together with any touching hits that are closer. If there are no blocking hits, all touching hits will be returned.
在查詢的時候,{pre/post}Filter函數將會通過返回值決定此次碰撞時一個touching還是一個blocking。blocking將會阻止在該路徑上的進一步查詢,而touching則不會。典型的例子是子彈穿過樹葉、玻璃、帷幕等,最后被牆體阻擋。在這個過程中,遇到牆體前的樹葉等都是touching,而牆體則是blocking。這個不難理解。
問題在於:
對於返回的touching和blocking,它們是不是按照碰撞點離起始點的遠近位置順序排列的?先說結論:不是的。
但是背后的原因卻和查詢時候使用的查詢樹有關系。

二、查詢樹

在PhysX中,查詢樹通過AABB樹構建,這是一種基於AABB包圍盒構成的查詢樹。可以看到,這種架構下,並不存在一個所謂的“距離有序”的概念。更為糟糕的是,在PhysX 3.4版本中,對於數量小於NB_OBJECTS_PER_NODE(4)個的時候,這些圖形在葉子節點內部的便利順序是和添加順序相同的
這里舉一個簡單的例子,就是一個傾斜的擋板擋在一個物體之前的場景:
在這種場景中,如果從y坐標(垂直於水平面的海拔方向上)負方向進行射線檢測,那么此時給出的touching順序就是和添加順序相同。下圖是一個例子,在大的擋板后面(相對於raycast方向)添加兩個擋板,AABB查詢樹遍歷的時候按照添加順序遍歷,有興趣的同學可以驗證下查詢結果。

三、如何找到最近blocking

這個沒有辦法,其實需要找到所有的滿足AABB包圍盒的圖形進行判斷,即使找到一個blocking,還是需要繼續處理,因為可能有更近的blocking。

四、如何保證所有touching都比blocking小

這一點其實和第三點有關:因為blocking點可能會變小,那么隨着blocking的變小,那么之前小於blocking的touching可能就不再滿足,所以需要進行再次過濾。
這個過濾發生在兩個位置:
1、當找到的touching數量超過用戶提供的最大容量,此時需要嘗試進行一次修剪(purge):在MultiQueryCallback::invoke函數中mHitCall.nbTouches == mHitCall.maxNbTouches分支內部。
2、當所有AABB都遍歷完畢,在返回給用戶之前進行一次修剪:在IssueCallbacksOnReturn::~IssueCallbacksOnReturn()函數內部。
兩處都是調用了clipHitsToNewMaxDist函數,從這個函數的實現就可以看到,它並沒有保證查詢到的touching的順序,而只是把可以刪除的元素和最后一個元素互換。

五、上圖部分測試代碼

inline PxQuat EulerAngleToQuat(const PxVec3 &rot)
{
PxQuat qx(physx::shdfnd::degToRad(rot.x), PxVec3(1.0f, 0.0f, 0.0f));
PxQuat qy(physx::shdfnd::degToRad(rot.y), PxVec3(0.0f, 1.0f, 0.0f));
PxQuat qz(physx::shdfnd::degToRad(rot.z), PxVec3(0.0f, 0.0f, 1.0f));
return qz * qy * qx;
}

enum
{
TOUCH_SHAPE_1_FLAG = 0x1,
TOUCH_SHAPE_2_FLAG = 0x2,
BLOCK_SHAPE_FLAG = 0x4,
};

struct MyFilter:public PxQueryFilterCallback
{
virtual PxQueryHitType::Enum preFilter(
const PxFilterData& filterData, const PxShape* shape, const PxRigidActor* actor, PxHitFlags& queryFlags)
{
(void)shape; (void)actor; (void)queryFlags; (void)filterData;
PxU32 stWord0 = shape->getQueryFilterData().word0;
if (stWord0 & (TOUCH_SHAPE_1_FLAG | TOUCH_SHAPE_2_FLAG))
{
return PxQueryHitType::eTOUCH;
}
if (stWord0 & BLOCK_SHAPE_FLAG)
{
return PxQueryHitType::eBLOCK;
}
return PxQueryHitType::eNONE;
}

virtual PxQueryHitType::Enum postFilter(const PxFilterData& filterData, const PxQueryHit& hit)
{
(void)filterData; (void)hit;
return PxQueryHitType::eNONE;
}
};

void initPhysics(bool interactive)
{
(void)interactive;
gFoundation = PxCreateFoundation(PX_FOUNDATION_VERSION, gAllocator, gErrorCallback);

gPvd = PxCreatePvd(*gFoundation);
PxPvdTransport* transport = PxDefaultPvdSocketTransportCreate(PVD_HOST, 5425, 10);
gPvd->connect(*transport, PxPvdInstrumentationFlag::eALL);

gPhysics = PxCreatePhysics(PX_PHYSICS_VERSION, *gFoundation, PxTolerancesScale(), true, gPvd);

PxSceneDesc sceneDesc(gPhysics->getTolerancesScale());
sceneDesc.gravity = PxVec3(0.0f, 0.0f, 0.0f);
gDispatcher = PxDefaultCpuDispatcherCreate(0);
sceneDesc.flags |= PxSceneFlag::eENABLE_CCD;
sceneDesc.cpuDispatcher = gDispatcher;
sceneDesc.filterShader = contactReportFilterShader;
gScene = gPhysics->createScene(sceneDesc);
gScene->setSimulationEventCallback(&gstEvetnCallback);
PxPvdSceneClient* pvdClient = gScene->getScenePvdClient();
if (pvdClient)
{
pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_CONSTRAINTS, true);
pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_CONTACTS, true);
pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_SCENEQUERIES, true);
}
gMaterial = gPhysics->createMaterial(0, 0, 0);

 


PxRigidStatic* pstStaticBox;
PxShape* pxShape = nullptr;
PxFilterData stFilter;

PxBoxGeometry stBox2(10, 1, 10);
pstStaticBox = PxCreateStatic(*gPhysics, PxTransform(PxVec3(0, 100, 0)),
stBox2, *gMaterial);
gScene->addActor(*pstStaticBox);
stFilter.word0 = BLOCK_SHAPE_FLAG;
pstStaticBox->getShapes(&pxShape, 1, 0);
pstStaticBox->userData = (void*)3;
pxShape->setQueryFilterData(stFilter);


PxBoxGeometry stBox1(5, 1, 5);
pstStaticBox = PxCreateStatic(*gPhysics, PxTransform(PxVec3(0, 10, 0)),
stBox1, *gMaterial);
gScene->addActor(*pstStaticBox);
stFilter.word0 = TOUCH_SHAPE_1_FLAG;
pstStaticBox->getShapes(&pxShape, 1, 0);
pstStaticBox->userData = (void*)2;
pxShape->setQueryFilterData(stFilter);


PxBoxGeometry stBox(200, 1, 200);
PxQuat stQuat = EulerAngleToQuat(PxVec3(0, 0 , 45));
pstStaticBox = PxCreateStatic(*gPhysics, PxTransform(PxVec3(100, 100, 100), stQuat),
stBox, *gMaterial);
gScene->addActor(*pstStaticBox);
pstStaticBox->getShapes(&pxShape, 1, 0);
pstStaticBox->userData = (void*)1;
stFilter.word0 = BLOCK_SHAPE_FLAG;
pxShape->setQueryFilterData(stFilter);

 

PxRaycastBufferN<10> stBuff;
PxQueryFilterData stFilterData;
stFilterData.flags = PxQueryFlag::ePREFILTER | PxQueryFlag::eDYNAMIC | PxQueryFlag::eSTATIC;
MyFilter stFilterCallback;
gScene->raycast(PxVec3(0, -10, 0), PxVec3(0, 1, 0), 10000, stBuff, PxHitFlags(PxHitFlag::eDEFAULT), stFilterData, &stFilterCallback);
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM