Rigid Body Dynamics
在本章中,我們將介紹一些主題,一旦您熟悉了設置基本的剛體模擬世界,這些主題也很重要。
Velocity
剛體的運動分為線性和角速度(linear and angular velocity)分量。在仿真過程中, PhysX
將根據重力(gravity)、其他施加的力(other applied forces)和扭矩(torques)以及各種約束(如碰撞或joint)來修改物體的速度。
可以使用以下方法讀取物體的線性和角速度:
PxVec3 PxRigidBody::getLinearVelocity();
PxVec3 PxRigidBody::getAngularVelocity();
可以使用以下方法設置物體的線性和角速度:
void PxRigidBody::setLinearVelocity(const PxVec3& linVel, bool autowake);
void PxRigidBody::setAngularVelocity(const PxVec3& angVel, bool autowake);
Mass Properties
dynamic actor
需要質量屬性,包括:質量(mass)、慣性矩(moment of inertia)和質量frame的中心(the center of mass frame),center of mass frame
指定Actor
的質心位置及其主慣性軸(principal inertia axes)。計算質量屬性的最簡單方法是使用 PxRigidBodyExt::updateMassAndInertia()
幫助程序函數,該函數將根據Actor
的Shape
和均勻的密度值設置所有三個屬性。此功能的變體允許組合每個Shape
的密度和手動指定某些質量屬性。有關更多詳細信息,請參閱 PxRigidBodyExt
的參考。
North Pole Sample
中搖搖晃晃的雪人說明了不同質量屬性的使用。雪人就像羅利聚乙烯玩具,通常只是一個空殼,底部裝滿了一些沉重的材料。低質心導致它們在傾斜后移回直立位置。它們有不同的風格,具體取決於質量屬性的設置方式:
-
第一種基本上是無質量的。在
Actor
的底部只有一個質量相對較高的小球體。由於產生的慣性矩很小,這導致相當快速的運動。雪人感覺很輕。 -
第二個僅使用底部雪球的質量,導致更大的慣性。后來,質心被移動到
Actor
的底部。這種近似在物理上絕是不正確的,但由此產生的雪人感覺更飽滿一些。 -
第三個和第四個雪人使用
Shape
來計算質量。不同之處在於,人們首先計算慣性矩(從實際質心),然后將質心移動到底部。另一個計算我們傳遞給計算例程的低質心的慣性矩。請注意第二種情況的擺動速度要慢得多,盡管兩者的質量相同。這是因為頭部在慣性矩(與質心的距離平方)中占了更多。 -
最后一個雪人的質量屬性是手動設置的。該示例使用慣性矩的粗略值來創建特定的所需行為。對角線張量在 X 中具有低值,在 Y 和 Z 中具有高值,從而產生圍繞 X 軸旋轉的低阻力和圍繞 Y 和 Z 的高阻力。因此,雪人只會圍繞X軸來回擺動。
如果您有一個 3x3 慣性矩陣(例如,您的對象有現實中的慣性張量),請使用 PxDiagonalize()
函數獲取主軸和對角慣性張量來初始化 PxRigidDynamic actor
。
當手動設置物體的質量/慣性張量時,PhysX
需要質量和每個主慣性軸的正值。但是,在這些值中提供 0 是合法的。當提供0質量或慣性值時, PhysX
將其解釋為表示圍繞該主軸的無限質量或慣性。這可用於創建抵抗所有線性運動或抵抗所有或某些角度運動的物體。使用此方法可以實現的效果示例如下:
- Bodies that behave as if they were kinematic.
- Bodies whose translation behaves kinematically but whose rotation is dynamic.
- Bodies whose translation is dynamic but whose rotation is kinematic.
- Bodies which can only rotate around a specific axis.
下面詳細介紹了可以取得的成就的一些例子。首先,讓我們假設我們正在創建一個共同的結構 - 一個風車。下面提供了構建將成為風車一部分的body的代碼:
PxRigidDynamic* dyn = physics.createRigidDynamic(PxTransform(PxVec3(0.f, 2.5f, 0.f)));
PxRigidActorExt::createExclusiveShape(*dyn, PxBoxGeometry(2.f, 0.2f, 0.1f), material);
PxRigidActorExt::createExclusiveShape(*dyn, PxBoxGeometry(0.2f, 2.f, 0.1f), material);
dyn->setActorFlag(PxActorFlag::eDISABLE_GRAVITY, true);
dyn->setAngularVelocity(PxVec3(0.f, 0.f, 5.f));
dyn->setAngularDamping(0.f);
PxRigidStatic* st = mPhysics.createRigidStatic(PxTransform(PxVec3(0.f, 1.5f, -1.f)));
PxRigidActorExt::createExclusiveShape(*t, PxBoxGeometry(0.5f, 1.5f, 0.8f), material);
scene.addActor(dyn);
scene.addActor(st);
上面的代碼為風車創建了一個靜態箱形框架,並創建了一個十字形來表示風車的葉片。我們關閉風車葉片上的重力和角度阻尼,並賦予其初始角速度。因此,該風車葉片將無限期地以恆定的角速度旋轉。但是,如果另一個物體與風車相撞,我們的風車將停止正常運行,因為風車葉片將被撞出原位。有幾種選擇可以使風車葉片在其他物體與之相互作用時保持在正確的位置。其中一種方法可能是使風車具有無限的質量和慣性。在這種情況下,與物體的任何相互作用都不會影響風車:
dyn->setMass(0.f);
dyn->setMassSpaceInertiaTensor(PxVec3(0.f));
此示例無限期地保留了風車以恆定角速度旋轉的先前行為。然而,現在body
的速度不能受到任何約束的影響,因為body
有無限的質量和慣性。如果一個物體與風車葉片相撞,碰撞的行為就好像風車葉片是一個kinematic
物體。
另一種選擇是使風車具有無限的質量,並將其旋轉限制在body
的局部z軸周圍。這將提供與在風車和靜態風車框架之間應用旋轉接頭相同的效果:
dyn->setMass(0.f);
dyn->setMassSpaceInertiaTensor(PxVec3(0.f, 0.f, 10.f));
在這兩個例子中,body
的質量都設置為0,表明body
具有無限的質量,因此其線速度不能被任何約束改變。但是,在此示例中,body 的慣性被配置為允許body 的角速度受圍繞一個主軸或慣性的約束的影響。這提供了與引入旋轉接頭(a revolute joint)類似的效果。z軸周圍的慣性值可以增加或減少,以使風車對運動的抵抗力更大/更小。
Applying Forces and Torques
與物體相互作用的最物理友好的方式是向它施加力。在經典力學中,物體之間的大多數相互作用通常通過使用力來解決。由於定理:
$ f = m * a(force = mass * acceleration) $
力直接控制物體的加速度,但其速度和位置只是間接的。因此,如果您需要立即響應,通過強制控制可能會不方便。力的優點是,無論您對場景中的實體施加何種力,模擬都能夠保持所有定義的約束(joint和接觸)得到滿足。例如,重力的工作原理是對物體施加力。
不幸的是,以系統的共振頻率對鉸接體(articulated bodies)施加大的力可能會導致速度不斷增加,並最終導致求解器無法維持joint約束。這與現實世界的系統沒有什么不同,在現實世界中,joint最終會斷裂。
作用在body上的力在每個模擬幀之前累積,施加到模擬中,然后重置為零,為下一幀做准備。下面列出了 PxRigidBody
和 PxRigidBodyExt
的相關方法。有關更多詳細信息,請參閱 API 參考:
void PxRigidBody::addForce(const PxVec3& force, PxForceMode::Enum mode, bool autowake);
void PxRigidBody::addTorque(const PxVec3& torque, PxForceMode::Enum mode, bool autowake);
void PxRigidBodyExt::addForceAtPos(PxRigidBody& body, const PxVec3& force,
const PxVec3& pos, PxForceMode::Enum mode, bool wakeup);
void PxRigidBodyExt::addForceAtLocalPos(PxRigidBody& body, const PxVec3& force,
const PxVec3& pos, PxForceMode::Enum mode, bool wakeup);
void PxRigidBodyExt::addLocalForceAtPos(PxRigidBody& body, const PxVec3& force,
const PxVec3& pos, PxForceMode::Enum mode, bool wakeup);
void PxRigidBodyExt::addLocalForceAtLocalPos(PxRigidBody& body, const PxVec3& force,
const PxVec3& pos, PxForceMode::Enum mode, bool wakeup);
PxForceMode
成員默認為 PxForceMode::eFORCE
以應用簡單力。還有其他可能性。例如,PxForceMode::eIMPULSE
將施加沖動力。 PxForceMode::eVELOCITY_CHANGE
也會做同樣的事情,但也會忽略body
的質量,有效地導致瞬間的速度變化。請參閱 PxForceMode
的 API 文檔,了解其他可能性。
注意: PxRigidBodyExt
中的方法僅支持力模式 eFORCE
和 eIMPULSE
。
還有其他擴展函數,用於計算在下一個仿真幀中,如果要施加脈沖力或脈沖扭矩,則會出現線性和角速度變化:
void PxRigidBodyExt::computeVelocityDeltaFromImpulse(const PxRigidBody& body,
const PxVec3& impulsiveForce, const PxVec3& impulsiveTorque, PxVec3& deltaLinearVelocity,
PxVec3& deltaAngularVelocity);
此函數的一個用例可能是預測游戲對象的更新速度,以便在body 可能超過幀末尾的閾值速度時,可以在模擬幀之前啟動資產加載。脈沖力和扭矩只是施加到body
上的力和扭矩乘以模擬框架的時間步長。忽略約束力和接觸力的影響,預計在下一個仿真幀中出現的線性和角速度的變化在 delta
線性 Velocity
和 deltaAngularVelocity
中返回。然后可以使用 body.getLinearVelocity() + deltaLinearVelocity
計算預測的線速度,而預測的角速度可以使用 body.getAngularVelocity() + deltaAngularVelocity
來計算。如果需要,可以使用 body.setLinearVelocity(body.getLinearVelocity() + deltaLinearVelocity)
和 body.setAngularVelocity(body.getAngularVelocity() + deltaAngularVelocity)
立即更新 body
的速度。
Gravity
重力是模擬中常見的力, PhysX
使其特別容易應用。對於場景范圍的重力效果或任何其他均勻的力場,請使用 PxScene::setGravity()
設置 PxScene
類的重力矢量。
參數是重力引起的加速度。以米和秒為單位,這在地球上的星等約為9.8,並且應該指向下方。將在場景中每個物體的質心處施加的力是這個加速度矢量乘以Actor
的質量。
某些特效可能要求一些動態Actor
不受重力的影響。要指定此標志,請設置標志:
PxActor::setActorFlag(PxActorFlag::eDISABLE_GRAVITY, true);
注意:在模擬過程中更改重力(或啟用/禁用重力)時要小心。出於性能原因,更改不會自動喚醒睡眠中的Actor
。因此,可能需要遍歷所有Actor
並手動調用PxRigidDynamic::wakeUp()
。
PxActorFlag::eDISABLE_GRAVITY
的替代方法是對整個場景使用零重力矢量,然后每幀將自己的重力施加到剛體上。這可用於創建徑向重力場,如 SampleCustomGravity
中所示。
Friction and Restitution
所有物理對象都至少有一種材質,該材質定義了用於解決與對象碰撞的摩擦和恢復屬性。
要創建材質,請調用 PxPhysics::createMaterial()
:
PxMaterial* mMaterial;
mMaterial = mPhysics->createMaterial(0.5f, 0.5f, 0.1f); // static friction, dynamic friction,
// restitution
if(!mMaterial)
fatalError("createMaterial failed!");
材質歸 PxPhysics
對象所有,並且可以在多個場景中的對象之間共享。碰撞中涉及的兩個物體的材料屬性可以通過各種方式組合。有關更多詳細信息,請參閱 PxMaterial
的參考文檔。
碰撞幾何體為三角形網格(triangle mesh )或高度場(heightfield)(請參見Shape)的 PhysX
對象可以為每個三角形提供材質。
摩擦使用庫侖摩擦模型,該模型基於2個系數的概念:靜態摩擦系數和動態摩擦系數(有時稱為動摩擦)。摩擦抵抗兩個接觸的固體表面的相對橫向運動。這兩個系數定義了每個表面施加在另一個表面上的法向力與施加的摩擦力與抵抗橫向運動的摩擦力之間的關系。靜態摩擦力定義了在不橫向相互移動的曲面之間施加的摩擦量。動態摩擦力定義了彼此相對移動的表面之間施加的摩擦量。
兩個碰撞物體的恢復系數是一個分數值,表示撞擊后和撞擊前的速度比,沿撞擊線取。恢復系數為1的彈性碰撞,而恢復系數<1則稱為無彈性的。
Sleeping
當一個Actor
在一段時間內不移動時,假設它將來也不會移動,直到一些外力作用於它,使其失去平衡。在此之前,不再模擬以節省資源。此狀態稱為睡眠(sleeping)。您可以使用以下方法查詢執行組件的睡眠狀態:
bool PxRigidDynamic::isSleeping() const;
但是,在Actor
fall asleep 或 wake up時偵聽 SDK 發送的事件通常更方便。要接收以下事件,必須為執行組件設置 PxActorFlag::eSEND_SLEEP_NOTIFIES
:
void PxSimulationEventCallback::onWake(PxActor** actors, PxU32 count) = 0;
void PxSimulationEventCallback::onSleep(PxActor** actors, PxU32 count) = 0;
有關詳細信息,請參閱 Callback Sequence 和 Sleep state change events 小節。
當Actor
的動能在一段時間內低於給定 Threshold
時,它就會進入睡眠狀態。基本上,每個動態剛性 Actor
都有一個wake counter
,當 Actor
的動能低於指定的threshold
時,該計數器會因仿真時間 step
而遞減。但是,如果在仿真step
后能量高於threshold
,則wake counter
將重置為最小默認值,並且整個過程將重新開始。一旦wake counter
達到零,它就不會進一步減少,並且 Actor
可以進入睡眠狀態。請注意,零wake counter
並不意味着Actor
必須處於睡眠狀態,它僅表示它已准備好進入睡眠狀態。還有其他因素可能會讓Actor
保持更長時間的清醒。
能量閾值以及 Actor
保持清醒的最短時間可以使用以下方法進行操作:
void PxRigidDynamic::setSleepThreshold(PxReal threshold);
PxReal PxRigidDynamic::getSleepThreshold() const;
void PxRigidDynamic::setWakeCounter(PxReal wakeCounterValue);
PxReal PxRigidDynamic::getWakeCounter() const;
注意:對於kinematic actors
,特殊的睡眠規則適用。除非設置了目標姿勢(target pose),否則kinematic actors
處於睡眠狀態(在這種情況下,它將保持清醒狀態,直到下一個模擬步驟結束,其中不再設置目標姿勢)。因此,不允許對kinematic actors
使用 setWakeCounter()
。
如果dynamic rigid actor
正在休眠,則保證以下狀態:
- The wake counter is zero(喚醒計數器為零).
- The linear and angular velocity is zero(線性和角速度為零).
- There is no force update pending(沒有掛起的強制更新).
當Actor
入到場景中時,如果上述所有點都保持不變,它將被視為睡眠,否則它將被視為清醒。
通常,如果至少存在以下一項,則動態剛性actor(dynamic rigid actor)保證處於清醒狀態:
- The wake counter is positive(喚醒計數器為正).
- The linear or angular velocity is non-zero(線性或角速度為非零).
- A non-zero force or torque has been applied(施加了非零力或扭矩).
因此,以下調用將自動喚醒執行組件:
PxRigidDynamic::setWakeCounter()
, if the wake counter value is larger than zero(喚醒計數器大於0).PxRigidBody::setLinearVelocity(), ::setAngularVelocity()
, if the velocity is non-zero(速度非0).PxRigidBody::addForce(), ::addTorque()
, if the torque is non-zero(扭矩非0).
此外,以下調用和事件會喚醒執行組件:
PxRigidDynamic::setKinematicTarget()
in the case of a kinematic actor (因為這還會將喚醒計數器設置為正值).PxRigidActor::setGlobalPose()
, if the autowake parameter is set to true (default).- Simulation gets disabled for a
PxRigidActor
by raisingPxActorFlag::eDISABLE_SIMULATION
. PxScene::resetFiltering()
.PxShape::setSimulationFilterData()
, if the subsequent re-filtering causes the type of the shape pair to transition between suppressed, trigger and contact(如果隨后的重新過濾導致Shape對的類型在suppressed、trigger和contact之間轉換).- 與awake的
Actor
touch。 - 一個
touching rigid actor
從場景中移除(這是默認行為,但可以由用戶指定,請參閱下面的注釋)。 - 與
static rigid actor
的Contact丟失。 - 與
dynamic rigid actor
的Contact丟失,該Actor
在下一個模擬步驟中醒來。 Actor
被雙向交互粒子(two-way interaction particle)擊中。
從場景中移除Rigid Actor
或從 Actor
移除Shape時,可以指定是否喚醒在上一個模擬步驟中接觸已移除對象的對象。有關詳細信息,請參閱 PxScene::removeActor()
和 PxRigidActor::detachShape()
中的 API 注釋。
要顯式喚醒睡眠對象或強制對象進入睡眠狀態,請使用:
void PxRigidDynamic::wakeUp();
void PxRigidDynamic::putToSleep();
** 注意:不允許將這些方法用於kinematic actors。kinematic actors的睡眠狀態僅根據是否設置了目標位置來定義。 **
API 參考准確記錄了哪些方法會導致執行組件被喚醒。
Sleep state change events
如上所述, PhysX
提供了一個事件系統,用於報告在PxScene::fetchResults()
期間動態剛體的睡眠狀態的變化:
void PxSimulationEventCallback::onWake(PxActor** actors, PxU32 count) = 0;
void PxSimulationEventCallback::onSleep(PxActor** actors, PxU32 count) = 0;
了解這些事件的正確用法及其局限性非常重要:
- 自上一次
fetchResults()
或flushSimulation()
以來添加的body
將始終生成事件,即使沒有發生睡眠狀態轉換也是如此。 - 如果自之前的
fetchResults()
或flushSimulation()
以來,body
的睡眠狀態發生了多次變化,PhysX
將僅報告最近的更改。
有時需要檢測 awake 和 asleep 之間的轉換,例如在跟蹤awake body的數量時。假設一個asleep的body B 被應用程序喚醒,計數器遞增,並且在下一個模擬步驟 B 保持awake。盡管 B 的睡眠狀態在模擬期間沒有改變,但自之前的 fetchResults()
以來它已經發生了變化,因此將為它生成一個 onWake()
事件。如果計數器再次遞增以響應此事件,則其值將不正確。
若要使用睡眠狀態事件來檢測轉換,必須保留感興趣對象的睡眠狀態記錄,例如在哈希中。處理事件時,此記錄可用於檢查是否存在轉換。
Kinematic Actors
有時,使用力或約束來控制Actor
不夠強大、精確或靈活。例如,moving platforms或character controllers通常需要操縱Actor
的位置,或者讓它完全遵循特定的路徑。這種控制方案由kinematic actors
提供。
kinematic actor
使用PxRigidDynamic::setKinematicTarget()
函數進行控制。每個模擬步驟 PhysX
都會將Actor
移動到其目標位置,而不管外力,重力,碰撞等。因此,對於每個kinematic actor
,每次都必須不斷調用setKinematicTarget()
,以使它們沿着所需的路徑移動。kinematic actor
的運動會影響與它碰撞或被joint約束的動態參與者。Actor
看起來有無限的質量,並將把常規的dynamic actors
推開。
要創建kinematic actor
,只需創建一個常規的dynamic actors
,然后設置其kinematic標志:
PxRigidBody::setRigidBodyFlag(PxRigidBodyFlag::eKINEMATIC, true);
使用相同的函數將kinematic actor
轉換回常規dynamic actor
。雖然您確實需要像為所有dynamic actor
一樣為kinematic actor
提供質量,但當Actor
處於運動學模式時,這個質量實際上不會用於任何事情。
警告:
- 重要的是要了解
PxRigidDynamic::setKinematicTarget()
和PxRigidActor::setGlobalPose()
之間的區別。雖然setGlobalPose()
也會將actor
移動到所需的位置,但它不會使該actor
與其他對象正確交互。特別是,在setGlobalPose()
中,kinematic actor
不會推開其他dynamic actors
,而是會直接穿過它們。不過,setGlobalPose()
函數仍然可以使用,如果只是想將kinematic actor
傳送到一個新的位置。 kinematic Actor
可以推開動態對象,但沒有什么能把它推回去。因此,kinematic可以很容易地將dynamic Actor
擠壓到static Actor
或另一個kinematic Actor
身上。因此,被擠壓的動態物體可以深深地穿透它被推入的幾何Shape。kinematic Actor
和static Actor
之間沒有交互或碰撞。但是,可以使用PxSceneDesc::kineKineFilteringMode
和PxSceneDesc::staticKineFilteringMode
請求這些情況的聯系信息。
Active Transforms
注意:active transforms當前已棄用。請參閱下一段關於"Active Actors "的替代內容。
Active Transforms API 提供了一種有效的方法,可以將 PhysX
場景中的 actor transform
更改反映為關聯的外部對象(associated external object)(如render mesh)。
當調用場景的 fetchResults()
方法時,將生成一個 PxActiveTransform
結構數組,數組中的每個條目都包含一個指向移動的Actor
、其用戶數據及其新的transform
。因為只有移動過的Actor
才會被包括在列表中,所以這種方法可能比單獨分析場景中的每個Actor
更有效。
下面的示例演示如何使用Active Transforms來更新呈現對象:
// update scene
scene.simulate(dt);
scene.fetchResults();
// retrieve array of actors that moved
PxU32 nbActiveTransforms;
PxActiveTransform* activeTransforms = scene.getActiveTransforms(nbActiveTransforms);
// update each render object with the new transform
for (PxU32 i=0; i < nbActiveTransforms; ++i)
{
MyRenderObject* renderObject = static_cast<MyRenderObject*>(activeTransforms[i].userData);
renderObject->setTransform(activeTransforms[i].actor2World);
}
注意:必須在場景中設置 PxSceneFlag::eENABLE_ACTIVETRANSFORMS
,才能生成Active Transforms數組。
注意:由於kinematic rigid bodies的目標變換由用戶設置,因此可以通過設置標志PxSceneFlag::eEXCLUDE_KINEMATICS_FROM_ACTIVE_ACTORS
將kinematics從列表中排除。
Active Actors
Active Actors API 提供了一種有效的方法來將 PhysX
場景中的更改轉換為關聯的外部對象(associated external object)(如render mesh)。
當場景的 fetchResults()
方法被調用時,將生成一個活動 PxActor
數組。因為只有移動過的 Actor
才會被包括在列表中,所以這種方法可能比單獨分析場景中的每個Actor
更有效。
下面的示例演示如何使用Active Actors更新渲染對象:
// update scene
scene.simulate(dt);
scene.fetchResults();
// retrieve array of actors that moved
PxU32 nbActiveActors;
PxActor** activeActors = scene.getActiveActors(nbActiveActors);
// update each render object with the new transform
for (PxU32 i=0; i < nbActiveActors; ++i)
{
MyRenderObject* renderObject = static_cast<MyRenderObject*>(activeActors[i]->userData);
renderObject->setTransform(activeActors[i]->getGlobalPose());
}
必須在場景中設置 PxSceneFlag::eENABLE_ACTIVE_ACTORS
,才能生成Active Actors數組。
由於kinematic rigid bodies的目標變換(target transform)由用戶設置,因此可以通過設置標志PxSceneFlag::eEXCLUDE_KINEMATICS_FROM_ACTIVE_ACTORS
將kinematic從列表中排除。
Dominance
Dominance是一種使dynamic bodies能夠相互支配的機制。Dominance有效地將dominant body注入無限質量對(pair with infinite mass)中。這是約束求解器(constraint solver)內局部質量修改的一種形式,因此可以覆蓋一對中一個物體的質量。通過接觸修改(contact modification)中的局部大規模修改(local mass modification)可以達到類似的效果,但dominance的優勢在於在SDK中自動處理,因此不會產生接觸修改(contact modification)的額外內存和性能開銷。
必須為每個Actor
分配一個dominance group ID。這是一個范圍 [0, 31] 中的 5 位值。因此,您最多只能使用 32 個dominance group。默認情況下,所有body都放置在dominance group 0 中。可以在 PxActor
上使用以下方法將執行組件分配到dominance group:
virtual void setDominanceGroup(PxDominanceGroup dominanceGroup) = 0;
Dominance由以下結構中的 2 個實數定義:
struct PxDominanceGroupPair
{
PxDominanceGroupPair(PxReal a, PxReal b)
: dominance0(a), dominance1(b) {}
PxReal dominance0;
PxReal dominance1;
};
在 PxScene
上可以使用以下方法配置兩個dominance group之間的支配關系:
virtual void setDominanceGroupPair(PxDominanceGroup group1, PxDominanceGroup group2,
const PxDominanceGroupPair& dominance) = 0;
用戶可以為給定的 PxDominanceGroupPair
定義3種不同的狀態:
- 1:1。這表明兩個機構具有同等的支配地位(equal dominance)。這是默認行為。
- 1 : 0.這表明
body
B支配body
A. - 0:1。這表明
body
A主導了body
B。
除 0 和 1 以外的任何值在PxDominanceGroupPair
中無效。將 0 分配給 PxDominanceGroupPair
的兩端也是無效的。這些值可以被認為是應用於物體各自的逆質量和反慣性的尺度。因此,支配值為0將等同於無限質量體。
下面的示例將兩個 actor(actor A 和 actor B)設置為不同的dominance group,並配置dominance group以使 actorA 支配 actorB:
PxRigidDynamic* actorA = mPhysics->createRigidDynamic(PxTransform(PxIdentity));
PxRigidDynamic* actorB = mPhysics->createRigidDynamic(PxTransform(PxIdentity));
actorA->setDominanceGroup(1);
actorB->setDominanceGroup(2);
mScene->setDominanceGroupPair(1, 2, PxDominanceGroupPair(0.f, 1.f));
Dominance不會影響joint。必須在 PxJoint
上使用以下方法對joints進行局部質量修改:
virtual void setInvMassScale0(PxReal invMassScale) = 0;
virtual void setInvMassScale1(PxReal invMassScale) = 0;
virtual void setInvInertiaScale0(PxReal invInertiaScale) = 0;
virtual void setInvInertiaScale1(PxReal invInertiaScale) = 0;
如前所述,dominance不允許0或1以外的值,並且任何dominance值都統一應用於逆質量和反慣性。通過contact modification的joint和觸點允許定義單獨的反質量和反慣性尺度,它們接受[0,PX_MAX_REAL]范圍內的任何值,因此可用於實現比支配范圍更廣的效果。
如果濫用dominance,可能會產生一些非常奇特的結果。例如,給定的body A、B 和 C 按以下方式配置:
- Body A dominates body B
- Body B dominance body C
- Body C dominates body A
在這種情況下,body A不能直接推動body C。但是,如果它把body B推入body C,它可以推動body C。
Solver Iterations
當剛體的運動受到contacts或joints的約束時,constraint solver就會發揮作用。solver通過迭代限制 body
運動一定次數的所有約束來滿足 body
的約束。迭代次數越多,結果就越准確。solver iteration計數默認為 4 次位置迭代和 1 次速度迭代。可以使用以下函數為每個機構單獨設置這些計數:
void PxRigidDynamic::setSolverIterationCounts(PxU32 minPositionIters, PxU32 minVelocityIters);
通常,對於具有大量joint且joint誤差容差較小的物體,只需要顯着增加這些值。如果發現需要使用高於 30 的設置,則可能需要重新考慮模擬的配置。
Solver將contacts分組為friction patches;friction patches是一組contacts,它們共享相同的材料並具有相似的contact normals。但是,Solver允許每個contact manager(一對Shape)最多有 32 個friction patches。如果產生超過 32 個friction patches(這可能是由於非常復雜的碰撞幾何Shape或非常大的contact offsets),Solver將忽略剩余的friction patches。發生這種情況時,checked/debug版本中將出現警告。
Immediate Mode
除了使用 PxScene
進行模擬外, PhysX
還提供了一個名為"immediate mode"的低級模擬API。這提供了一個 API 來訪問低contact generation和constraint solver。此方法目前僅支持 CPU 剛體,不支持articulations、clothing或particles。
immediate mode API 在 PxImmediateMode.h 中定義,並且有一個代碼段演示了它在"SnippetImmediateMode"中的用法。
該 API 提供了contact generation的函數:
PX_C_EXPORT PX_PHYSX_CORE_API bool PxGenerateContacts(const PxGeometry* geom0, const PxGeometry* geom1, const PxTransform* pose0, const PxTransform* pose1, PxCache* contactCache, const PxU32 nbPairs, PxContactRecorder& contactRecorder,const PxReal contactDistance, const PxReal meshContactMargin, const PxReal toleranceLength, PxCacheAllocator& allocator);
此函數采用一組位於特定位置的 PxGeometry
對象對,並在這些對象之間執行碰撞檢測。如果幾何圖形對發生碰撞,則會生成contacts,這些contacts將報告給 contactRecorder
。此外,信息可以緩存在 contactCache
中,以加速這些幾何體對之間的未來查詢。此緩存信息所需的任何內存都將使用"allocator"進行分配。
此外,immediate mode還為constraint solver提供了 API。這些函數包括用於創建solver使用的實體的函數:
PX_C_EXPORT PX_PHYSX_CORE_API void PxConstructSolverBodies(const PxRigidBodyData* inRigidData, PxSolverBodyData* outSolverBodyData, const PxU32 nbBodies, const PxVec3& gravity, const PxReal dt);
PX_C_EXPORT PX_PHYSX_CORE_API void PxConstructStaticSolverBody(const PxTransform& globalPose,PxSolverBodyData& solverBodyData);
除了構建body之外,PxConstraintSolverBodies
還將提供的重力加速度集成到 body
速度中。
以下函數是可選的,用於批處理約束(batch constraints):
PX_C_EXPORT PX_PHYSX_CORE_API PxU32 PxBatchConstraints(PxSolverConstraintDesc* solverConstraintDescs, const PxU32 nbConstraints, PxSolverBody* solverBodies, PxU32 nbBodies, PxConstraintBatchHeader* outBatchHeaders,PxSolverConstraintDesc* outOrderedConstraintDescs);
batch constraints對提供的constraints重新排序並生成 batchHeaders
,solver可以使用這些batchHeaders
來加速約束求解(constraint solving),方法是將獨立約束組合在一起並使用 SIMD 寄存器中的多個通道並行求解。此過程是完全可選的,如果不需要,可以繞過。請注意,這將更改constraints的處理順序,這可能會更改求解器(solver)的結果。
提供了以下方法來創建接觸約束(contact constraints):
PX_C_EXPORT PX_PHYSX_CORE_API bool PxCreateContactConstraints(PxConstraintBatchHeader* batchHeader, const PxU32 nbHeaders, PxSolverContactDesc* contactDescs,PxConstraintAllocator& allocator, PxReal invDt, PxReal bounceThreshold, PxReal frictionOffsetThreshold, PxReal correlationDistance);
該方法可以與 PxGenerateContacts
產生的contacts或由特定於應用的contact generation approaches產生的contacts一起提供。
提供了以下方法來創建關節約束(joint constraints):
PX_C_EXPORT PX_PHYSX_CORE_API bool PxCreateJointConstraints(PxConstraintBatchHeader* batchHeader, const PxU32 nbHeaders, PxSolverConstraintPrepDesc* jointDescs, PxConstraintAllocator& allocator, PxReal dt, PxReal invDt);
PX_C_EXPORT PX_PHYSX_CORE_API bool PxCreateJointConstraintsWithShaders(PxConstraintBatchHeader* batchHeader, const PxU32 nbBatchHeaders, PxConstraint** constraints, PxSolverConstraintPrepDesc* jointDescs, PxConstraintAllocator& allocator, PxReal dt, PxReal invDt);
這些方法為應用程序定義joint rows或應用程序使用 PhysX PxConstraint
對象(創建constraint rows)提供了一種機制。
以下方法解決了這些約束:
PX_C_EXPORT PX_PHYSX_CORE_API void PxSolveConstraints(PxConstraintBatchHeader* batchHeaders, const PxU32 nbBatchHeaders, PxSolverConstraintDesc* solverConstraintDescs, PxSolverBody* solverBodies,PxVec3* linearMotionVelocity, PxVec3* angularMotionVelocity, const PxU32 nbSolverBodies, const PxU32 nbPositionIterations, const PxU32 nbVelocityIterations);
此方法執行所有必需的位置和速度迭代,並更新對象的 delta 速度和 motion 速度,這些速度分別存儲在 PxSolverBody
和 linear/angularMotionVelocity
中。
提供以下方法來積分物體的最終位置並更新物體的速度以反映constraint solver產生的運動。
在 SnippetImmediateMode
中提供了如何使用即時模式的示例。
Enhanced Determinism
PhysX
提供有限的確定性仿真(Enhanced Determinism)。具體而言,如果使用相同的時間步進方案和在同一平台上運行的相同 PhysX
版本來模擬完全相同的場景(以相同的順序插入相同的 Actor),則在運行之間模擬的結果將是相同的。模擬行為不受所用工作線程數的影響。
但是,如果以不同的順序插入 Actor
,則模擬的結果可能會更改。此外,如果添加了其他 Actor
或從場景中刪除了一些 Actor
,則模擬的整體行為可能會發生變化。這意味着,特定Actor
集合的模擬可能會根據場景中是否存在其他Actor
而變化,而不管這些Actor
是否實際與Actor
集合進行交互。這種行為屬性通常是可以容忍的,但在某些情況下是不可接受的。
為了克服這個問題, PhysX
提供了一個標志:PxSceneFlag::eENABLE_ENHANCED_DETERMINISM
,它提供了額外的確定性級別。具體而言,如果應用程序以確定性順序插入參與者,並且此標志升起,則無論場景中的任何其他islands如何,islands的模擬都將是相同的。但是,此模式會犧牲一些性能來確保這種額外的確定性。
Axis locking
可以使用 PxRigidDynamicLockFlag
在 PhysX
中限制沿特定世界空間軸或圍繞特定世界空間軸的運動。例如,下面的代碼片段演示了如何將 PxRigidDynamic
限制為二維仿真。在這種情況下,我們允許 PxRigidDynamic
僅圍繞Z軸旋轉,並且僅沿X軸和Y軸平移:
PxRigidDynamic* dyn = physics.createRigidDynamic(PxTransform(PxVec3(0.f, 2.5f, 0.f)));
...
//Lock the motion
dyn->setRigidDynamicLockFlags(PxRigidDynamicLockFlag::eLOCK_LINEAR_Z | PxRigidDynamicLockFlag::eLOCK_ANGULAR_X | PxRigidDynamicLockFlag::eLOCK_ANGULAR_Y);
限制圍繞6個自由度的任意組合移動或旋轉是合法的。