您的位置:首页 > 产品设计 > UI/UE

PhysX3 User Guide 06 - Callbacks and Customization

2014-03-03 08:42 871 查看
原文地址: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);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: