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的例子:

复制代码
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);
                }
        }
}
复制代码

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 

复制代码
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;
}
复制代码

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:
复制代码
//  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;
复制代码

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:
复制代码
struct  FilterGroup
{
        
enum  Enum
        {
                eSUBMARINE              
=  ( 1   <<   0 ),
                eMINE_HEAD              
=  ( 1   <<   1 ),
                eMINE_LINK              
=  ( 1   <<   2 ),
                eCRAB                   
=  ( 1   <<   3 ),
                eHEIGHTFIELD    
=  ( 1   <<   4 ),
        };
};
复制代码

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:
复制代码
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;
}
复制代码

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:
复制代码
void  MyContactModification::onContactModify(PxContactModifyPair  * const  pairs, PxU32 count)
{
        
for (PxU32 i = 0 ; i < count; i ++ )
        {
                ...
        }
}
复制代码

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:
复制代码
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();
复制代码

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);


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值