天天看點

PhysX3 User Guide 06 - Callbacks and Customization

原文位址:http://www.cnblogs.com/mumuliang/archive/2011/06/04/2072761.html

本章俺們要看看SDK提供了哪些函數用來監聽模拟事件和自定義部分模拟行為。回調函數需在使用者使用的繼承類中實作。這和第一章談到的自定義配置設定操作allocator和錯誤提示error notification所使用的機制是一樣的。

    Simulation Events        

事件是最簡單的模拟回調。程式可以隻監聽而不做反應。使用者在callback中添加的代碼隻有一個問題:你未必能如願修改SDK中的狀态!(only one restriction? -_-b)背景進行實體模拟時,也可以進行寫操作——這有點意外吧,因為SDK都是雙緩沖形式的,新狀态都是寫入了非活動狀态的後緩沖中。但是,event是在fetchResults()内部被調用的而不是在模拟線程simulation thread,這就有可能後緩沖中有些操作在模拟線程中已經做過了。将來的更新版本可能會基于單個事件在此問題上做一些改進,但目前還是隻有先把需要作為事件結果傳回的寫操作緩存起來,并且在fetchResult()傳回後執行。(糊裡糊塗的#v#)

fetchResults()内部,交換緩存(意味着物體的模拟結果API可見了)的動作并不是在最初或最後,而是在一系列操作的中間,也就是說事件回調被分成了發生在緩存交換之前和之後兩種情況。在之前的有

  • onTrigger 
  • onContactNotify 
  • onConstraintBreak 

收到這些事件event的時候 ,物體的形狀Shape、角色Actor等仍然保持模拟simulate之前的狀态。這樣是對的。因為這些事件的檢測本應在模拟之前。例如,一對引發了onContactNotify()的shape,即使在fetchResult()之後它們可能是被彈開了,但他們也的确那啥了。

位于交換緩存之後的事件有:

  • onSleep 
  • onWake 

Sleep information is updated after objects have been integrated, so that it makes sense to send these events after the swap.

監聽事件有兩步:1,寫一個繼承于 PxSimulationEventCallback的子類,定義需要的回調函數。對Sleep/Wake事件或是限制破壞事件constraint break event, 這是唯一的一個步驟。 

2,onContactNotify 和 onTrigger 事件,還需要在filter shader callback為需要接受該事件的物體設定一個标志。下一節collision filtering會細說。

這是SampleSubmarine工程中的使用contact notify function的例子:

PhysX3 User Guide 06 - Callbacks and Customization

void  SampleSubmarine::onContactNotify(PxContactPair &  pair, PxU32 events)

{

         if (events  &  PxPairFlag::eNOTIFY_TOUCH_FOUND)

        {

                 if ((pair.actors[ 0 ]  ==  mSubmarineActor)  ||  (pair.actors[ 1 ]  ==  mSubmarineActor))

                {

                        PxActor *  otherActor  =  (mSubmarineActor  ==  pair.actors[ 0 ])  ?  pair.actors[ 1 ] : pair.actors[ 0 ];

                        Seamine *  mine  =   reinterpret_cast < Seamine *> (otherActor -> userData);

                         //  insert only once

                         if (std::find(mMinesToExplode.begin(), mMinesToExplode.end(), mine)  ==  mMinesToExplode.end())

                                mMinesToExplode.push_back(mine);

                }

        }

}

PhysX3 User Guide 06 - Callbacks and Customization

SampleSubmarine 是 PxContactNotifyCallback 的子類. onContactNotify 方法接收一個contract pair的事件掩碼. 上面的函數隻處理了eNOTIFY_TOUCH_FOUND事件。事實上它隻關心潛艇的這個事件. 然後它會假設第二個Actor是水雷(可能隻激活了潛艇和地雷的contact report). 然後它把這個地雷添加到一組下一次會爆炸的地雷裡面。

    Collision Filtering        

幾乎所有實際應用中,都需要設定不計算某些互相作用的物體,或者讓SDK以某種特殊的方式進行沖突檢測。在潛水艇的例程中,如上文說道的,需要在潛水艇touch到了一個水雷或水雷鍊的時候得到通知be notified,以便引爆它們。再有,鉗爪AI也需要知道它是不碰touch到了heightfield。

在了解例程是咋做的之前,有必要先了解一下SDK的filter系統能做啥。因為潛在的作用對的filtering操作發生在模拟引擎最deepest(深奧?難懂?深入?)的部分,并且會作用于所有互相靠近的對象對,是以它表現的尤其sensitive。最簡單的一種實作方法所有潛在的作用對都調用一個回調函數,在回調函數中,應用程式采用自定義的邏輯(例如查詢遊戲資料)來判斷是否發生了互相作用,不過這種方法在遊戲世界很大的情況下會很慢。特别是當沖突檢測是由一個遠端處理器(像是GPU或其他矢量處理器)在處理的時候,處理器不得不先挂起它的并行計算,中斷遊戲運作遊戲代碼的主處理器,并在再次運作遊戲代碼之前執行callback。即使是在CPU上,它這樣做的同時很可能是運作在多核心或超線程上,所有的序列化代碼都必須到位以確定能同時通路共享資料。使用可以在遠端處理器上執行的固定的函數邏輯是比較好的方法。2.x中就是這麼做的,但這個基于将過濾規則簡單分組的規則不夠靈活不足以滿足所有應用,是以3.0中,使用了shader(開發者使用矢量處理器支援的代碼實作任意filter規則,但是以無法通路記憶體中的資料)這種比2.x的固定函數filtering更靈活的方式,同時也支援filter shader調用CPU callback函數的機制(它能通路所有應用程式資料,以犧牲性能為代價)。詳情見PxSimulationFilterCallback。最好的是應用程式可以基于作用對設定要速度還是靈活度。

首先俺們看看shader system,SampleSubmarine中實作了一個filter shader 

PhysX3 User Guide 06 - Callbacks and Customization

PxFilterFlags SampleSubmarineFilterShader(

        PxFilterObjectAttributes attributes0, PxFilterData filterData0,

        PxFilterObjectAttributes attributes1, PxFilterData filterData1,

        PxPairFlags &  pairFlags,  const   void *  constantBlock, PxU32 constantBlockSize)

{

         //  let triggers through

         if (PxFilterObjectIsTrigger(attributes0)  ||  PxFilterObjectIsTrigger(attributes1))

        {

                pairFlags  =  PxPairFlag::eTRIGGER_DEFAULT  |  PxPairFlag::eNOTIFY_TOUCH_PERSISTS;

                 return  PxFilterFlag::eDEFAULT;

        }

         //  generate contacts for all that were not filtered above

        pairFlags  =  PxPairFlag::eCONTACT_DEFAULT;

         //  trigger the contact callback for pairs (A,B) where

         //  the filtermask of A contains the ID of B and vice versa.

         if ((filterData0.word0  &  filterData1.word1)  &&  (filterData1.word0  &  filterData0.word1))

                pairFlags  |=  PxPairFlag::eNOTIFY_TOUCH_FOUND;

         return  PxFilterFlag::eDEFAULT;

}

PhysX3 User Guide 06 - Callbacks and Customization

SampleSubmarineFilterShader 的shader函數很簡單,實作了PxFiltering.h中的 PxSimulationFilterShader 原型。shader filter 函數, SampleSubmarineFilterShader ,可能不能引用任何記憶體,除了它的參數和本地棧變量(非new的局部變量),因為它可能是被編譯了運作在遠端處理器上。

SampleSubmarineFilterShader() will be called for all pairs of shapes that come near each other – more precisely: for all pairs of shapes whose axis aligned bounding boxes in world space are found to intersect for the first time. All behavior beyond that is determined by the what SampleSubmarineFilterShader() returns.

The arguments of SampleSubmarineFilterShader() include PxFilterObjectAttributes and PxFilterData for the two shapes, and a constant block of memory. Note that the pointers to the two shapes are NOT passed, because those pointers refer to the computer’s main memory, and that may, as we said, not be available to the shader, so the pointer would not be very useful, as dereferencing them would likely cause a crash. PxFilterObjectAttributes and PxFilterData are intended to contain all the useful information that one could quickly glean from the pointers. PxFilterObjectAttributes are 32 bits of data, that encode the type of object: For example eRIGID_STATIC, eRIGID_DYNAMIC, or even ePARTICLE_SYSTEM. Additionally, it lets you find out if the object is kinematic, or a trigger.

Each PxShape shape in PhysX has a member variable of type PxFilterData. This is 128 bits of user defined data that can be used to store application specific information related to collision filtering. This is the other variable that is passed to SampleSubmarineFilterShader() for each shape.

There is also the constant block. This is a chunk of per-scene global information that the application can give to the shader to operate on. You will want to use this to encode rules about what to filter and what not.

Finall, SampleSubmarineFilterShader() also has a PxPairFlags parameter. This is an output, like the return value PxFilterFlags, though used slightly differently. PxFilterFlags tells the SDK if it should ignore the pair for good (eKILL), ignore the pair while it is overlapping, but ask again, when it starts to overlap again (eSUPPRESS), or call the low performance but more flexible CPU callback if the shader can’t decide (eCALLBACK).

PxPairFlags specifies additional flags that stand for actions that the simulation should take in the future for this pair. For example, eNOTIFY_TOUCH_FOUND means notify the user when the pair really starts to touch, not just potentially.

Let’s look at what the above shader does: //  let triggers through

if (PxFilterObjectIsTrigger(attributes0)  ||  PxFilterObjectIsTrigger(attributes1))

{

        pairFlags  =  PxPairFlag::eTRIGGER_DEFAULT  |  PxPairFlag::eNOTIFY_TOUCH_PERSISTS;

         return  PxFilterFlag::eDEFAULT;

}

This means that if either object is a trigger, then perform default trigger behavior (notify the application while touching each frame), and otherwise perform ‘default’ collision detection between them. The next lines are:

PhysX3 User Guide 06 - Callbacks and Customization

//  generate contacts for all that were not filtered above

pairFlags  =  PxPairFlag::eCONTACT_DEFAULT;

//  trigger the contact callback for pairs (A,B) where

//  the filtermask of A contains the ID of B and vice versa.

if ((filterData0.word0  &  filterData1.word1)  &&  (filterData1.word0  &  filterData0.word1))

        pairFlags  |=  PxPairFlag::eNOTIFY_TOUCH_FOUND;

return  PxFilterFlag::eDEFAULT;

PhysX3 User Guide 06 - Callbacks and Customization

This says that for all other objects, perform ‘default’ collision handling. In addition, if there is a rule based on the filterDatas that determines particular pairs where we ask for touch notifications. To understand what this means, we need to know the special meaning that the sample gives to the filterDatas.

The needs of the sample are very basic, so we will use a very simple scheme to take care of it. The sample first gives named codes to the different object types using a custom enumeration:

PhysX3 User Guide 06 - Callbacks and Customization

struct  FilterGroup

{

         enum  Enum

        {

                eSUBMARINE               =  ( 1   <<   0 ),

                eMINE_HEAD               =  ( 1   <<   1 ),

                eMINE_LINK               =  ( 1   <<   2 ),

                eCRAB                    =  ( 1   <<   3 ),

                eHEIGHTFIELD     =  ( 1   <<   4 ),

        };

};

PhysX3 User Guide 06 - Callbacks and Customization

The sample identifies each shape’s type by assigning its PxFilterData::word0 to this FilterGroup type. Then, it puts a bit mask that specifies each type of object that should generate a report when touched by an object of type word0 into word1. This could be done in the samples whenever a shape is created, but because shape creation is a bit encapsulated in SampleBase, it is done after the fact, using this function:

PhysX3 User Guide 06 - Callbacks and Customization

void  setupFiltering(PxRigidActor *  actor, PxU32 filterGroup, PxU32 filterMask)

{

        PxFilterData filterData;

        filterData.word0  =  filterGroup;  //  word0 = own ID

        filterData.word1  =  filterMask;   //  word1 = ID mask to filter pairs that trigger a contact callback;

         const  PxU32 numShapes  =  actor -> getNbShapes();

        PxShape **  shapes  =   new  PxShape * [numShapes];

        actor -> getShapes(shapes, numShapes);

         for (PxU32 i  =   0 ; i  <  numShapes; i ++ )

        {

                PxShape *  shape  =  shapes[i];

                shape -> setSimulationFilterData(filterData);

        }

        delete [] shapes;

}

PhysX3 User Guide 06 - Callbacks and Customization

This sets up the PxFilterDatas of each shape belonging to the passed actor. Here are some examples how this is used in SampleSubmarine: setupFiltering(mSubmarineActor, FilterGroup::eSUBMARINE, FilterGroup::eMINE_HEAD  |  FilterGroup::eMINE_LINK);

setupFiltering(link, FilterGroup::eMINE_LINK, FilterGroup::eSUBMARINE);

setupFiltering(mineHead, FilterGroup::eMINE_HEAD, FilterGroup::eSUBMARINE);

setupFiltering(heightField, FilterGroup::eHEIGHTFIELD, FilterGroup::eCRAB);

setupFiltering(mCrabBody, FilterGroup::eCRAB, FilterGroup::eHEIGHTFIELD);

This scheme is probably too simplistic to use in a real game, but it shows the basic usage of the filter shader, and it will ensure that SampleSubmarine::onContactNotify() is called for all interesting pairs.

An alteriative group based filtering mechanism is provided with source in the extensions function PxDefaultSimulationFilterShader. And, again, if this shader based system is too inflexible for your needs in general, consider using the callback approach provided with PxSimulationFilterCallback.

    Contact Modification        

Sometimes users would like to have special contact behavior. Some examples to implement sticky contacts, give objects the appearance of floating or swimming inside eachother, or making objects go through apparent holes in walls. A simple approach to achieve such effects is to let the user change the properties of contacts after they have been generated by collision detection, but before the contact solver. Because both of these steps occur within the scene simulate() function, a callback must be used.

The callback occurs for all pairs of colliding shapes for which the user has specified the pair flag PxPairFlag::eMODIFY_CONTACTS in the filter shader.

To listen to these modify callbacks, the user must derive from the class PxContactModifyCallback: class  MyContactModification :  public  PxContactModifyCallback

        {

        ...

         void  onContactModify(PxContactModifyPair  * const  pairs, PxU32 count);

        };

And then implement the function onContactModify of PxContactModifyCallback:

PhysX3 User Guide 06 - Callbacks and Customization

void  MyContactModification::onContactModify(PxContactModifyPair  * const  pairs, PxU32 count)

{

         for (PxU32 i = 0 ; i < count; i ++ )

        {

                ...

        }

}

PhysX3 User Guide 06 - Callbacks and Customization

Basically, every pair of shapes comes with an array of contact points, that have a number of properties that can be modified, such as position, contact normal, and separation. For the time being, friction properties of the contacts cannot be modified. Perhaps we will make this possible in future releases. See PxContactPoint and PxContactPointAux for properties that can be modified.

There are a couple of special requirements for the callback due to the fact that it is coming from deep inside the SDK. In particular, the callback should be thread safe and reentrant. In other words, the SDK may call onContactModify() from any thread and it may be called concurrently (i.e., asked to process sets of contact modification pairs simultaneously).

The contact modification callback can be set using the contactModifyCallback member of PxSceneDesc or the setContactModifyCallback() method of PxScene.

    Custom Constraints        

Constraints like contact filtering, also uses shaders, for the same reason: There is a requirement to inject performance sensitive custom code into the SDK. Constraint is a more general term for joints. While joints were native objects of the PhysX 2.x API, PhysX 3.0 only supports a fully customizeable constraint object in the core API, and all 2.x joint types are implemented using this mechanism as extensions. Let’s take a short look at how this works. Once the reader understands, he will be in a good position to create his own joint types. You should read the chapter on joints before you try to understand their workings, however.

When you call PxJointCreate(), the extensions library first fills out a PxConstraintDesc object, which is a bunch of parameters for constraint creation. Here is the code for a spherical joint:

PhysX3 User Guide 06 - Callbacks and Customization

PxConstraintDesc nxDesc;

nxDesc.actor[ 0 ]                          =  desc.actor[ 0 ];

nxDesc.actor[ 1 ]                          =  desc.actor[ 1 ];

nxDesc.flags                             =  desc.constraintFlags;

nxDesc.linearBreakImpulse        =  desc.breakForce;

nxDesc.angularBreakImpulse       =  desc.breakTorque;

nxDesc.solverPrep                        =  SphericalJointSolverPrep;

nxDesc.project                           =  SphericalJointProject;

nxDesc.visualize                         =  SphericalJointVisualize;

nxDesc.dataSize                          =   sizeof (SphericalJointData);

nxDesc.connector                         =  joint -> getConnector();

PhysX3 User Guide 06 - Callbacks and Customization

The first few settings are self explanatory ... like the actors to connect, when the joint should break, and so on. The next three are three callback functions – user defined shaders! (See the section on filter shaders to find out what shaders are, and the rules that apply to them.) They contain the code that mathematically defines the behavior of the joint. Every time the joint needs to be solved, the simulation will call these functions.

Finally, the ‘connector’ is a class of additional user defined joint specific functionality that are not called from the solver directly, and are not shaders.

Lastly, the filled out descriptor is used to create a the constraint object:

PxConstraint *  constraint  =  physics.createConstraint(nxDesc);

繼續閱讀