PhysX3 User Guide

http://www.cnblogs.com/mumuliang/archive/2011/06/02/2068453.html


1 The SDK Object  
首先用全局函数 PxCreatePhysics创建一个PxPhysics对象。
复制代码
#include  " PxPhysicsAPI.h "

bool recordMemoryAllocations =  true;

static PxDefaultErrorCallback gDefaultErrorCallback;
static PxDefaultAllocator gDefaultAllocatorCallback;

mSDK = PxCreatePhysics(PX_PHYSICS_VERSION, gDefaultAllocatorCallback, gDefaultErrorCallback, PxTolerancesScale(), recordMemoryAllocations );
if(!mSDK)
        fatalError( " PxCreatePhysics failed! ");
复制代码

这部分和SDK 2.x稍微有些不同,必须设置allocator callback和error callback. physx3的extensions提供了一组默认的 PxDefaultAllocatorPxDefaultErrorCallback

倒数第二个参数,PxTolerancesScale, 是创建场景中的物体的默认Tolerances。


最后一个bool,true表示可以 memory usage debugging 。

 

 2 The Allocator Callback    
The allocator callback是需要自己实现的动态内存管理的接口类, PxAllocatorCallback。physx实现了一个, PxDefaultAllocator
复制代码
#include 
class PxDefaultAllocator :  public PxAllocatorCallback
{
         void* allocate(size_t size,  const  char*,  const  char*,  int)
        {
                 return _aligned_malloc(size,  16);
        }

         void deallocate( void* ptr)
        {
                _aligned_free(ptr);
        }
};
复制代码
 

Note 与2.x不同的是,3.要求分配的内存是16位对齐(We now require that the memory that is returned be 16-byte aligned! )。windows上可以用 _aligned_malloc. 控制台系统(console systems)上的malloc()返回的已经是16位对其的空间,因此不许特别处理。


If you want to track the SDK’s use of dynamic memory, you can put your own custom instrumentation code into the allocate and deallocate functions.

If you are curious about the three unused parameters of allocate, you can refer to PxAllocatorCallback::allocate to find out more about them. Basically, we support a system of named allocations which let us identify types of memory allocation so that you can allocate them from special heaps. The last two parameters are the __FILE__ and __LINE__ inside the SDK code where the allocation was made.

 3 The Error Callback   
the error callback也是要自己实现的一个接口 PxErrorCallback。这个接口类只需要实现一个方法, reportError. physx3也已经实现了一个。
复制代码
class PxDefaultErrorCallback :  public PxErrorCallback
{
public:
        PxDefaultErrorCallback();
        ~PxDefaultErrorCallback();

         virtual  void reportError(PxErrorCode::Enum code,  const  char* message,  const  char* file,  int line);
};

void PxDefaultErrorCallback::reportError(PxErrorCode::Enum e,  const  char* message,  const  char* file,  int line)
{
         const  char* errorCode = NULL;

         switch (e)
        {
         case PxErrorCode::eINVALID_PARAMETER:
                errorCode =  " invalid parameter ";
                 break;
         case PxErrorCode::eINVALID_OPERATION:
                errorCode =  " invalid operation ";
                 break;
         case PxErrorCode::eOUT_OF_MEMORY:
                errorCode =  " out of memory ";
                 break;
         case PxErrorCode::eDEBUG_INFO:
                errorCode =  " info ";
                 break;
         case PxErrorCode::eDEBUG_WARNING:
                errorCode =  " warning ";
                 break;
         default:
                errorCode =  " unknown error ";
                 break;
        }

        printf( " %s (%d) : ", file, line);
        printf( " %s ", errorCode);
        printf( "  : %s\n ", message);
}
复制代码

 4 Cooking and Extensions  
PhysX有俩可选库供使用:extensions 和 cooking. 应该在创建了SDK(也就是PxPhysics)对象后要首先初始化他们。他俩都需要一个东东,SDK的foundation对象。
if (!PxInitExtensions(*mSDK))
        fatalError( " PxInitExtensions failed! ");

mCooking = PxCreateCooking(PX_PHYSICS_VERSION, &mSDK->getFoundation(), PxCookingParams());
if (!mCooking)
        fatalError( " PxCreateCooking failed! ");
PxCreateCooking的第三个参数是可以设置的, 为了省事儿这儿也有默认值。

 5 The Scene  
PxScene是PhysX的体现. 这个必须有。
复制代码
static PxDefaultSimulationFilterShader gDefaultFilterShader;
PxScene* mScene;

PxSceneDesc sceneDesc(mSDK->getTolerancesScale());
sceneDesc.gravity = PxVec3( 0.0f, - 9.81f0.0f);
customizeSceneDesc(sceneDesc);

if(!sceneDesc.cpuDispatcher)
{
        mCpuDispatcher = PxDefaultCpuDispatcherCreate(mNbThreads);
         if(!mCpuDispatcher)
                fatalError( " PxDefaultCpuDispatcherCreate failed! ");
        sceneDesc.cpuDispatcher = mCpuDispatcher;
}
if(!sceneDesc.filterShader)
        sceneDesc.filterShader  = &gDefaultFilterShader;

#ifdef PX_WINDOWS
if(!sceneDesc.gpuDispatcher && mCudaContextManager)
{
        sceneDesc.gpuDispatcher = mCudaContextManager->getGpuDispatcher();
}
#endif
mScene = mSDK->createScene(sceneDesc);
if (!mScene)
        fatalError( " createScene failed! ");
复制代码

PxScene的属性在创建时设置,创建后不能修改,PxSceneDesc就是描述其属性的结构体. 

PxDefaultCpuDispatcherCreate 返回一个默认的 CpuDispatcher对象. 线程数俺们设成1。当然这个 CpuDispatcher也是可以自己去实现的。

PxDefaultSimulationFilterShader ,又一个physx3的默认对象,实现接口 PxSimulationFilterShader.

 

 6 Basic Actors  

空场景有了,接下来添加物体。所有物体都必须有材质,因此俺们先造个材质 PxMeterial
PxMaterial*     mMaterial;

mMaterial = mSDK->createMaterial( 0.5f0.5f0.1f);      // static friction, dynamic friction, restitution
if(!mMaterial)
        fatalError( " createMaterial failed! ");
材质定义了物体的摩擦力和惯性系数(friction and restitution coefficients )。
 

然后就可以用这个材质创建个最简单的static geometry,地面。 

复制代码
PxReal d =  0.0f;
PxU32 axis =  1;
PxTransform pose;

if(axis ==  0)
        pose = PxTransform(PxVec3(d,  0.0f0.0f));
else  if(axis ==  1)
        pose = PxTransform(PxVec3( 0.0f, d,  0.0f),PxQuat(PxHalfPi, PxVec3( 0.0f0.0f1.0f)));
else  if(axis ==  2)
        pose = PxTransform(PxVec3( 0.0f0.0f, d), PxQuat(-PxHalfPi, PxVec3( 0.0f1.0f0.0f)));

PxRigidStatic* plane = mSDK->createRigidStatic(pose);
if (!plane)
        fatalError( " create plane failed! ");
PxShape* shape = plane->createShape(PxPlaneGeometry(), *mMaterial);
if (!shape)
        fatalError( " create shape failed! ");
mScene->addActor(*plane);
复制代码
axis的部分给出了分别设置x、y、z方向为竖直方向的做法。(Physx默认姿态?是,X正方向。)

第一个PxRigidStatic actor就酱(sdk调用 createRigidStatic())创建了, 然后往里扔了一个平面shape, 这个shape使用了前边儿创建的material。最后,把这个static actor添加到场景中。 

再使用extensions中的全局函数 PxCreateDynamic()创建一个盒子. 这个函数会把shape和actor一并创建,并根据传入的密度density计算其质量mass和惯性inertia。
复制代码
PxReal density =  1.0f;
PxTransform(PxVec3( 0.0f5.0f0.0f), PxQuat::createIdentity());
PxVec3 dimensions( 1.0f1.0f1.0f);
PxBoxGeometry geometry(dimensions);

PxRigidDynamic *actor = PxCreateDynamic(*mSDK, transform, geometry, *mMaterial, density);
if (!actor)
        fatalError( " create actor failed! ");
mScene->addActor(*actor);
复制代码

这儿还是用的那个材质。最后表忘了mScene->addActor()。

 7 The Simulation Loop  
场景和其中的物体都创建好了,要让他们动起来,需要在仿真循环中往前推进一个时间段。blabla...

一个最简单的仿真循环:
复制代码
mAccumulator =  0.0f;
mStepSize =  1.0f /  60.0f;

virtual  bool advance(PxReal dt)
{
        mAccumulator  += dt;
         if(mAccumulator < mStepSize)
                 return  false;

        mAccumulator -= mStepSize;

        mScene->simulate(mStepSize);
         return  true;
}
复制代码

mScene->simulate()的参数是往前推进的时间长度。这个函数极有可能是异步调用的。因此在return的时候可能还没有算完。

假使这回想在某个图形引擎中使用PhysX,抓取物体的位置和朝向 (positions and orientations) 以刷新图形引擎中对应的数据,可以使用PxShapeExt下的getGlobalPose。
PxTransform newPose = PxShapeExt::getGlobalPose(*mPhysicsShape);

图形引擎run的时候PhysX的也在run,因此应该询问simulate有否完成。
mScene->fetchResults( true);

true表示如果还没完成就block直到完成。在调用fetchResults之前使用getGlobalPose() (或其他获取状态的函数) 获取actor或其他对象的数据,得到的是上一次simulate完成后的数据。

fetchResults, 会调用所有已定义的event callback。参考 ref:callbacks章节。

 8 Shutting Down  
PhysX不用了以后,可以手动release。凡是形如 PxCreate... 创建的对象都有release方法,用来销毁相关的数据。关闭整个场景的话,也同(调用PxPhysicx的release)。
mSDK->release();

 9 PhysX Visual Debugger Support  
PVD (PhysX Visual Debugger) 可直观的查看仿真场景中的情形。

PhysX一次只能连接?一个debugger, PvdConnectionFactoryManager管理这些连接. 

有几种方式连接debugger. 最简单的方法是使用a network stream. extensions提供了一个功能函数 PxExtensionVisualDebugger::connect. 调用是要确保debugger已经运行. 

另一种可能更高效的方式把debug信息写到一个文件,然后用debugger打开。 PvdConnectionFactoryManager 类提供了一个完成该功能的函数。

 

略 



PhysX3 User Guide 02 - Shape

Posted on 2011-06-02 16:07 mumuliang 阅读( 1658) 评论( 4) 编辑 收藏
Shape是PxGeometry的体现。一个Shape包括:PxGeometry, PxMetarial,和shape相对它所属的actor的pose(orientation and positionActor),肯定是这个PxActor创建的。多个shape可以凑成一组(成为一个Compound)在同一个Actor中。
 
和PhysX 2.x类似, 创建Shape是需定义一个PxGeometry. 也就是说每一个Shape都有它自己的PxGeometry类型. PhysX既有的PxGeometry如下.

1)for static and dynamic Actors:Spheres, Capsules, Boxes, ConvexMeshes

2)for static Actors only:  Planes, HeightFields, TriangleMeshes

 (although TriangleMeshes can be used as kinematics in scene queries)

 

创建 simulation object or Shape 的步骤:

  1. 用SDK对象创建一个Actor,  (要给出它的pose (orientation and position))。
  2. 创建Shape需要的Geometry
  3. Actor创建Shape,们。(别忘了材质和shape的相对pose)
  4. 更新mass、inertia 等属性。 (PhysX Extensions中有相关函数)
  5. 向Scene添加Actor。

 1 Spheres  

在某位置以某初始化速度扔出一个球体。
首先,俺们需要一个dynamic的actor
PxRigidDynamic* aSphereActor = thePhysics->createRigidDynamic(PxTransform(position));
 

然后是一个shape。描述sphere的geomety只需要半径足够了,第二个材质是必须要的。

PxShape* aSphereShape = aSphereActor->createShape(PxSphereGeometry(radius), aMaterial);
 

Actor加完所有的shape以后,就计算mass和inertia。

PxRigidBodyExt::updateMassAndInertia(*aSphereActor, sphereDensity); 
 

用Shape设置初始速度,这是个linear velocity vector:

aSphereShape->setLinearVelocity(velocity);

将Actor加到场景中。
aScene->addActor(aSphereActor);

 2 Capsules  

和Sphere相比, Capsule还需要一个height.   注意,初始化Capsule设置的是half height。创建Shape和Actor的方式同Sphere,略。
PxTransform pose;
pose.q = PxQuat(PxHalfPi, PxVec( 0, 0, 1));
PxShape* aCapsuleShape = aCapsuleActor->createShape(PxCapsuleGeometry(radius, halfHeight), aMaterial, pose);
 

capsule的height默认是y方向的,要让Capsule沿X方向延伸,将其绕Z轴旋转90度。

 

 3 Boxes  


描述一个Box的只需要3个参数。
PxShape* aBoxShape = aBoxActor->createShape(PxBoxGeometry(a/ 2, b/ 2, c/ 2), aMaterial);
 

a, b , c代表其边长。

 

 4 Convex Meshes  


使用顶点vertex来描述Convex Mesh。创建一个金字塔形试试看.
首先定义其边缘的顶点(extreme verteices)。
static  const PxVec3 convexVerts[] = {PxVec3( 0, 1, 0),PxVec3( 1, 0, 0),PxVec3(- 1, 0, 0),PxVec3( 0, 0, 1),PxVec3( 0, 0,- 1)};
 

然后描述面上的点:

PxConvexMeshDesc convexDesc;
convexDesc.points.count =  5;
convexDesc.points.stride =  sizeof(PxVec3);
convexDesc.points.data = convexVerts;
convexDesc.flags = PxConvexFlag::eCOMPUTE_CONVEX;
 

有了这些点PhysX SDK就可以计算出其余相关的数据。俺们把这个过程叫做cooking。接下来叫你如何用PhysX cook a Convex Mesh,and it is done through a Stream objects。

PxCooking* cooking = PxCreateCooking(PX_PHYSICS_VERSION, thePhysics->getFoundation(), PxCookingParams());
MemoryWriteBuffer buf;
bool status = cooking->cookConvexMesh(convexDesc, buf);
PxConvexMesh* convexMesh = thePhysics->createConvexMesh(MemoryReadBuffer(buf.data));
cooking->release();
 

现在有了ConvexMesh,就可以用它创建一个Convex的Shape了。

PxShape* aConvexShape = aConvexActor->createShape(PxConvexMeshGeometry(convexMesh, PxMeshScale()), aMaterial, aTransform);
PxConvexMeshGeometry的第二个参数用以控制ConvexMesh的缩放。
 

 5 Planes  


Plane把空间分成以上和以下。凡是低于plane物体就会和它碰撞. 俺们常用它来创建地面或者模拟的世界的边界。Plane这种Geometry根本不需要参数.只要把它放到某处就可以了。它是static的。当然是static。
PxRigidStatic* aPlaneActor = thePhysics->createRigidStatic(pose);
PxShape* aPlaneShape = aPlaneActor->createShape(PxPlaneGeometry(), aMaterial);

 6 Heightfields  

如名, 地形就可以用方形网格采样的高度值描述。
PxHeightFieldSample* samples = (PxHeightFieldSample*)alloc( sizeof(PxHeightFieldSample)*(numRows*numCols));
 

每个样本(方格子)都有一个16位的整数值和2个材质(一个方格划分成2个三角面)。这是一种特殊的已经定义了的材质,PxHeightFieldMaterial::eHOLE. 如果你用float型描述高度(也木有关系?),there is the possibility to set the used scale later on with the Geometry. Since there are two ways to split a rectangle into two triangles, the split diagonal can be selected with the tesselation flag.

当然,还要知道每个方向有多少样本高度。
PxHeightFieldDesc hfDesc;
hfDesc.format = PxHeightFieldFormat::eS16_TM;
hfDesc.nbColumns = numCols;
hfDesc.nbRows = numRows;
hfDesc.samples.data = samples;
hfDesc.samples.stride =  sizeof(PxHeightFieldSample);
 

目前只支持提及的格式。eS16_TM?

Heightfields不需要cooking, 但也有一个内部对象需要初始化:
PxHeightField* aHeightField = thePhysics->createHeightField(hfDesc);
 

接下来就可以用Geometry创建shape:

PxHeightFieldGeometry hfGeom(aHeightField, PxMeshGeometryFlags(), heightScale, rowScale, colScale);
 

Heightfield也是static的.

PxRigidStatic* aHeightFieldActor = mSDK->createRigidStatic(pose);
PxShape* aHeightFieldShape = aHeightFieldActor->createShape(hfGeom, aMaterial);
 

如果是 multi-material的heightfields, 需用另一个函数来create Shape

PxShape* aHeightFieldShape = aHeightFieldActor->createShape(hfGeom, aMaterialArray, nbMaterials);
 

 7 Triangle Meshes  


也是要先有三角面的顶点vertices,然后像convexes一样,需要cooking。

复制代码
PxTriangleMeshDesc meshDesc;
meshDesc.points.count = nbVerts;
meshDesc.triangles.count = nbFaces;
meshDesc.points.stride =  4* 3;
meshDesc.triangles.stride =  4* 3;
meshDesc.points.data = verts;
meshDesc.triangles.data = indices;
PxCooking* cooking = PxCreateCooking(PX_PHYSICS_VERSION, thePhysics->getFoundation(), PxCookingParams());
MemoryWriteBuffer buf;
bool status = cooking->cookTriangleMesh(meshDesc, buf);
PxTriangleMesh* triangleMesh = thePhysics->createTriangleMesh(MemoryReadBuffer(buf.data));
cooking->release();
复制代码

 

strides表示points和triangle都是3个一组的4字节长的数据。 
 

Triangle meshe是static. 但是可以用作kinematics。(譬如水面?)

PxRigidStatic* aTriMeshActor = thePhysics->createRigidStatic(pose);
PxShape* aTriMeshShape = aTriMeshActor->createShape(PxTriangleMeshGeometry(triangleMesh), aMaterial);
 

Triangle meshe也可以缩放. 

索引可能是32bit也可以是16bit,视mesh的三角数目而定。用PxTriangleMesh::has16BitTriangleIndices()可以查看其具体情况.

 

 8 Compound Shapes 

Compound组合了多个Shape到一个Actor中. 要创建一个Compound Shap只需多次调用PxRigidActor::createShape()即可。只是不要忘记最后要计算mash和intertia什么的。
在North Pole Sample中的雪人就是compound对象. compound到一起的shape之间有些是可以分离的。

例如雪人中,有些会被雪球砸中分开。这需要设置一个PxSimulationFilterShaderpairFlags. 然后在可分离的shape被击中后在simulation filter function中做如下工作:

if (needsContactReport(filterData0, filterData1))
{
pairFlags |= PxPairFlag::eNOTIFY_TOUCH_FOUND;
}
 

needsContactReport 是一个用来在每个shape的simulationFilterData中测试flags的工具函数helper function. 这些flags是一早在创建Shape时用setDetachable和setSnowball设置的。Contact notification is requested if one of the Shapes is detachable and the other is marked as a snowball. (迷惑 =“=)

That way, the pair will be passed to the PxSimulationEventCallback::onContact() function which is implemented in the Sample. All we want to do here is to remember which detachable Shapes were touched in this frame so far.
After simulation, we can iterate over that list again and actually detach the according Shapes from their respective Actor. Note that the Shape has a local (in Actor space) pose and the Actor has a world pose. Furthermore, a PxShape cannot exist on its own, it is always bound to an Actor.
That means, that when you detach a Shape you have to provide a new Actor for it to continue to exist. The new Actor will have the global pose of the old Shape (in the Compound Actor) and the new local pose is identity. There is a helper extension to calculate the global pose of a Shape. The Geometry and Material needed to create the copy of the Shape in the new Actor can be retreived from the detaching Shape.
复制代码
PxShape* shape = 
PxTransform pose = PxShapeExt::getGlobalPose(*shape);
PxRigidDynamic* newActor = mSDK->createRigidDynamic(pose);
PxMaterial* mat;
shape->getMaterials(&mat, 1);
PxConvexMeshGeometry convexGeom;
if(shape->getConvexMeshGeometry(convexGeom))
{
PxShape* newShape = newActor->createShape(convexGeom,*mat);
PxRigidBodyExt::updateMassAndInertia(*newActor, 1);
aScene->addActor(*newActor));
newActor->addForce(PxVec3( 0,. 1, 0),PxForceMode::eFORCE);
shape->release();
}
复制代码
 

Obviously, you need several PxGeometry types and a selection code if you don’t know in advance which kind of Shape you are going to detach. As usual the mass properties need to be set after adding Shapes to an Actor. And eventually you can release the old detaching Shape completely.

Note here that you can only change dynamic properties of an Actor after it has been added to a Scene. Kinematic properties like velocities can be set before that though.

 

 9 Mass Computation  

恩,看出来了,actor的mass是需要计算的。用它, PxRigidBodyExt::updateMassAndInertia().
它会计算和设置
  1. actor的mass
  2. 重心,center of mass
  3. 最大静摩擦力? the principal moments of inertia
To illustrate different mass properties we will look at the Wobbly Snowmen in the North Pole Sample. The principle of a roly-poly toy is the low center of mass, moving the object back in upright position after it has been tilted. Usually, most of the body is just an empty shell, and the bottom is filled with some heavy material.
The Wobbly Snowmen come in different flavors, depending on how the mass properties are set:
The first Snowman is basically mass-less. There is just a little sphere with a relatively high mass at the bottom of the Actor. This results in a quite rapid movement due to the small resulting moments of inertia. The Snowman feels light.
The second example uses the mass of the bottom snowball only, resulting in a bigger inertia. Later on, the center of mass is moved to the bottom of the actor. This is by no means physically correct, but we only approximate things here. The resulting Snowman feels a bit more filled.
The third and fourth example use all basic shapes to calculate the mass. The difference is that one calculates the moments of inertia first (from the real center of mass) and then the center of mass is moved to the bottom. The other calculates the moments of inertia about the low center of mass that we pass to the calculation routine. Note how much slower the wobbling is for the second case although both have the same mass. This is because the head accounts for much more in the moment of inertia (the distance from the center of mass squared).
The last Snowmans mass properties are set up manually. After constructing the compound, we set the mass and center of mass. For the moments of inertia, which tell us ‘how much resistance there is to change the orientation about an axis’, we use rough values which create the desired behavior (of course, for real you would integrate the moments properly!). The Snowman shall wobble back and forth only, which is around the X axis in this case. The resulting tensor diagonal values will have a small value in X, and high values in Y and Z: There is small resistance to rotate about X, but a high resistance to rotate about Y and Z.

 



PhysX3 User Guide 03 - Joint

Posted on 2011-06-02 16:28 mumuliang 阅读( 1798) 评论( 1) 编辑 收藏
 1 Joint Creation  
Joint提供了一种联系俩Actor的途径。典型的例子如门上的合页,人物的肩膀。.

创建Joint的方法:
PxRevoluteJointCreate(PxPhysics& physics,
                      PxRigidActor* actor0,  const PxTransform& localFrame0,
                      PxRigidActor* actor1,  const PxTransform& localFrame1);

必须有一个Actor可以动,是PxRigidDynamic,要不就是PxArticulationLink. 另一个Actor无所谓。

localFrame参数表示Actor的相对PxTransform,包括位置和姿态,position and orientation. 上面的例子是一个revolute joint,localFrame须指定原点和旋转轴。

PhysX提供了6中Joint:
  1. 固定。不能相对运动。a fixed joint locks the orientations and origins rigidly together 
  2. 距离。Actor之间像栓了绳子。a distance joint keeps the origins within a certain distance range 
  3. 球体。原点重合,姿态自由。a spherical joint (sometimes known as as a ball-and-socket) keeps the origins together, but allows the orientations to vary freely. 
  4. 开阖。门的合页那样的。a revolute joint (often referred to as a hinge) keeps the origins and x-axes of the frames together, and allows free rotation around this common axis. 
  5. 截面。移门那样的。a prismatic joint keeps the orientations identical, but allows the origin of one actor to slide freely along the line defined by the origin and x-axis of the other. This kind of joint models a sliding door. 
  6. D6。高度自定义的一类,可以高度自由也可以高度固定。a D6 joint is a highly configurable joint that allows specification of individual degrees of freedom either to move freely or be locked together. It can be used to implement a wide variety of mechanical and anatomical joints, but is somewhat less intuitive to configure than the other joint types. 
应用程序可以添加向框架添加新的Joint扩展现有类型。

Note: PhysX中的角度是以弧度为单位。

 2 Beyond the Basics  

 

  Visualization  
所有PhysX的标准joints都支持debug visualization.因此..

但 joint本身是不可见的. 为使其可见, 要将设置其constraint flag为visualization,并改变场景的visualization parameters:
scene->setVisualizationParameter(PxVisualizationParameter::eJOINT_FRAMES,  1.0f);
scene->setVisualizationParameter(PxVisualizationParameter::eJOINT_LIMITS,  1.0f);

...

joint->setConstraintFlag(PxConstraintFlag::eVISUALIZATION)

  Force Reporting  
Jonit连接的Actor每帧都要保存连接它俩的force的信息。通过设置reporting constraint flag启用该功能,然后就可以用Joint对象的getForce方法获得force信息。
joint->setConstraintFlag(PxConstraintFlag::eREPORTING)
...

scene->fetchResults(...)
joint->getForce(force, torque);

The force is resolved at the origin of actor1’s joint frame.

  Breakage  
PhysX的标准joint都是可以被掰开的breakable. 可以定义这个临界force和torgue。 joint被掰开会引发一个simulate event(参看 PxSimulationEventCallback::onJointBreak), 这个joint尽管可能仍然存在与场景中,但也不再参与运算。

但joint默认是不可掰开的。开启该属性,方法如下,首先设置一个临界force和torgue,然后设置breakable constraint flag为true。
joint->setBreakForce( 100.0f100.0f);
joint->setConstraintFlag(PxConstraintFlag::eBREAKABLE,  true);

  Projection  
Under stressful conditions, PhysX’ dynamics solver may not be able to accurately enforce the constraints specified by the joint. For joints where this is problematic you can enable kinematic projection, which tries to bring violated constraints back into alignment. Projection is not a physical process and doesn’t preserve momentum or respect collision geometry. It should be avoided where practical, but it can be useful in improving simulation quality where joint separation results in unacceptable artifacts.(这段很迷糊)

在突发情况(有外力情况?)下,PhysX 的dynamics slover(dynamic物体的计算器?)可能会没办法正确操作joint定义的Actor之间约束关系。这显然不太妙。你可以启用kinematic projection(运动投影?),它会尝试纠正。但Projection并非物理过程,它不会遵守动量守恒也不会管图形是否碰撞。实际应用中应该避免使用projection,但它在改善什么什么效果时很有用。-_-b

 

projection也是默认不可用的。启用方法如下,首先设置会引起joint被project的临界linear tolerance和angular tolerance,然后开启那啥constraint flag.

joint->setProjectionLinearTolerance( 0.1f);
joint->setConstraintFlag(PxConstraintFlag::ePROJECTION,  true);

Very small tolerance values for projection may result in jittering around the joint.

  Limits  
joint限制相关的Actor如何旋转或只能沿某方向平移,同时可进一步设置这种可旋转和可移动的范围。例如revolute joint,合页关节,默认是绕轴的任意角度的旋转,如果需要,你可以开启limit,然后定义个旋转的最大角度和最小角度。

Limits可以看做collision碰撞的一种。stable limit需要定义最大最小距离,和一个tolerance。blabla..要说的是limit的模拟开销是很昂贵的。

设置limit要配置limit的geometry,然后设置joint的limit flag为true。
revolute->setLimit(PxJointLimitPair(-PxPi/ 4, PxPi/ 40.1f));  //  upper, lower, tolerance
revolute->setRevoluteJointFlag(PxRevoluteJointFlag::eLIMIT_ENABLED,  true);

Limit可硬可软。硬就是让joint的运动立刻停下来,如果这是limit被设置为non-zero restitution,会被弹回。软的就是有阻尼效果。默认的limit是不会回弹的hard。

Note: Limits are not projected.

  Actuation  
有些joint是被spring和motor驱动的。这种模拟的开销比简单用force驱动的要大,它要求有更多的 stable control ,更多细节参看D6和revolute joint的文档。

Note: The force generated by actuation is not included in the force reported by the solver, nor does it contribute towards exceeding the joint’s breakage force threshold.

  3 Fixed Joint  
image:: ../images/fixedJoint.png

The fixed joint has no additional characteristics.

  4 Spherical Joint  
image:: ../images/sphericalJoint.png

A spherical joint constrains a point on each actor to be coincident.

The spherical joint supports a cone limit, which constrains the direction of the x-axis of actor1’s constraint frame. The axis of the limit cone is the x-axis of actor0’s constraint frame, and the allowed limit values are the maximum rotation around the the y- and z- axes of that frame. Different values for the y- and z- axes may be specified, in which case the limit takes the form of an elliptical angular cone:
joint->setLimitCone(PxJointLimitCone(PxPi/ 2, PxPi/ 60.01f);
joint->setSphericalJointFlag(PxSphericalJointFlag::eLIMIT_ENABLED,  true);

Note that very small or highly elliptical limit cones may result in solver jitter.

Note Visualization of the limit surface can help considerably in understanding its shape.

  5 Revolute Joint  
image:: ../images/revoluteJoint.png

A revolute joint removes all but a single rotational degree of freedom from two objects. The axis along which the two bodies may rotate is specified by the common origin of the joint frames and their common x-axis. In theory, all origin points along the axis of rotation are equivalent, but simulation stability is best in practice when the point is near where the bodies are closest.

The joint supports a rotational limit with upper and lower extents. The angle is zero where the y- and z- axes of the joint frames are coincident, and increases moving from the y-axis towards the z-axis:
joint->setLimit(PxJointLimitPair(-PxPi/ 4, PxPi/ 40.01f);
joint->setRevoluteJointFlag(PxRevoluteJointFlag::eLIMIT_ENABLED,  true);

The joint also supports a motor which drives the relative angular velocity of the two actors towards a user-specified target velocity. The magnitude of the force applied by the motor may be limited to a specified maximum:
joint->setDriveVelocity( 10.0f);
joint->setRevoluteJointFlag(PxRevoluteJointFlag::eDRIVE_ENABLED,  true);

By default, when the angular velocity at the joint exceeds the target velocity the motor acts as a brake; a freespin flag disables this braking behavior

  6 Prismatic Joint  
image:: ../images/prismaticJoint.png

A prismatic joint prevents all rotational motion, but allows the origin of actor1’s constraint frame to move freely along the x-axis of actor0’s constraint frame. The primatic joint supports a single limit with upper and lower bounds on the distance between the two constraint frames’ origin points:
joint->setLimit(PxJointLimitPair(- 10.0f20.0f0.01f);
joint->setPrismaticJointFlag(PxPrismaticJointFlag::eLIMIT_ENABLED,  true);

  7 Distance Joint  
image:: ../images/distanceJoint.png

The distance joint keeps the origins of the constraint frames within a certain range of distance. The range may have both upper and lower bounds, which are enabled separately by flags:
joint->setMaxDistance( 10.0f);
joint->setDistanceJointFlag(eMAX_DISTANCE_ENABLED,  true);

In addition, when the joint reaches the limits of its range motion beyond this distance may either be entirely prevented by the solver, or pushed back towards its range with an implicit spring, for which spring and damping paramters may be specified.

  8 D6 Joint  
  Locking and Unlocking Axes  
D6是最复杂的标准Joint. 它默认表现的像fixed Joint,两个物体像黏在一起一样运动。但使用Joint的 setMotion方法,你可以设置允许旋转和平移。
d6joint->setMotion(PxD6Axis::eX, PxD6Motion::eFREE);

如果设置了平移自由translational degrees of freedom,即允许actor1的原点沿着actor0的constraint frame定义的轴移动。

旋转自由Rotational degrees of freedom分为两种:扭twist (around the x-axis of actor0’s constraint frame);和摆 swing (around the y- and z- axes.) 通过控制twist和swing参数的开关,可以得到很多效果:
  1. if just a single degree of angular freedom is unlocked, the result is always equivalent to a revolute joint. It is recommended that if just one angular freedom is unlocked, it should be the twist degree, because the joint has various configuration options and optimizations that are designed for this case. 
  2. if both swing degrees of freedom are unlocked but the twist degree remains locked, the result is a zero-twist joint. The x-axis of actor1 swings freely away from the x-axis of actor0 but twists to minimize the rotation required to align the two frames. This creates a kind of isotropic universal joint which avoids the problems of the usual ‘engineering style’ universal joint (see below) that is sometimes used as a kind of twist constraint. There is a nasty singularity at π radians (180 degrees) swing, so a swing limit should be used to avoid the singularity. 
  3. if one swing and one twist degree of freedom are unlocked but the remaining swing is kept locked, a zero-swing joint results (often also called a universal joint.) If for example the SWING1 (y-axis rotation) is unlocked, the x-axis of actor1 is constrained to remain orthogonal to the z-axis of actor0. In character applications, this joint can be used to model an elbow swing joint incorporating the twist freedom of the lower arm or a knee swing joint incorporating the twist freedom of the lower leg. In vehicle applications, these joints can be used as ‘steered wheel’ joints in which the child actor is the wheel, free to rotate about its twist axis, while the free swing axis in the parent acts as the steering axis. Care must be taken with this combination because of anisotropic behavior and singularities (beware the dreaded gimbal lock) at angles of π/2 radians (90 degrees), making the zero-twist joint a better behaved alternative for most use cases. 
  4. if all three angular degrees are unlocked, the result is equivalent to a spherical joint. 
有三种PhysX2的joint在physX3已经木有了,但你可以酱来实现他们。

  • The cylindrical joint (with axis along the common x-axis of the two constraint frames) is given by the combination:
d6joint->setMotion(PxD6Axis::eX,     PxD6Motion::eFREE);
d6joint->setMotion(PxD6Axis::eTWIST, PxD6Motion::eFREE);
  • the point-on-plane joint (with plane axis along the x-axis of actor0’s constraint frame) is given by the combination:
d6joint->setMotion(PxD6Axis::eY,      PxD6Motion::eFREE);
d6joint->setMotion(PxD6Axis::eZ,      PxD6Motion::eFREE);
d6joint->setMotion(PxD6Axis::eTWIST,  PxD6Motion::eFREE);
d6joint->setMotion(PxD6Axis::eSWING1, PxD6Motion::eFREE);
d6joint->setMotion(PxD6Axis::eSWING2, PxD6Motion::eFREE);
  • the point-on-line joint (with axis along the x-axis of actor0’s constraint frame) is given by the combination:
d6joint->setMotion(PxD6Axis::eX,      PxD6Motion::eFREE);
d6joint->setMotion(PxD6Axis::eTWIST,  PxD6Motion::eFREE);
d6joint->setMotion(PxD6Axis::eSWING1, PxD6Motion::eFREE);
d6joint->setMotion(PxD6Axis::eSWING2, PxD6Motion::eFREE);

  Limits  
除了可以定义轴是自由还是锁定( axis is free or locked), 还可以定义其受限范围limit. D6有三种limit,并且可以组合使用。

A single linear limit with only an upper bound is used to constrain any of the translational degrees of freedom. The limit constrains the distance between the origins of the constraint frames when projected onto these axes. For example, the combination:
d6joint->setMotion(PxD6Axis::eX, PxD6Motion::eFREE);
d6joint->setMotion(PxD6Axis::eY, PxD6Motion::eLIMITED);
d6joint->setMotion(PxD6Axis::eZ, PxD6Motion::eLIMITED);
d6joint->setLinearLimit(PxJointLimit( 1.0f0.1f));

constrains the y- and z- coordinates of actor1’s constraint frame to lie within the unit disc. Since the x-axis is unconstrained, the effect is to constrain the origin of actor1’s constraint frame to lie within a cylinder of radius 1 extending along the x-axis of actor0’s constraint frame.

The twist degree of freedom is limited by a pair limit with upper and lower bounds, identical to the limit of the revolute joint.

If both swing degrees of freedom are limited, a limit cone is generated, identical to the limit of the spherical joint. As with the spherical joint, very small or highly elliptical limit cones may result in solver jitter.

If only one swing degree of freedom is limited, the corresponding angle from the cone limit is used to limit rotation. If the other swing degree is locked, the maximum value of the limit is π radians (180 degrees). If the other swing degree is free, the maximum value of the limit is π/2 radians (90 degrees.)

  Drives  
D6有1个linear drive model, 和2个angular drive models. The drive is a proportional derivative drive, which applies a force as follows:

force = spring * (targetPosition - position) + damping * (targetVelocity - velocity)

The drive model 也可以代替force用来引起加速。Acceleration drive 调整比force drive要容易些。

linear drive model 有如下参数: 
  • 目标位置,在actor0的constraint frame中定义。target position, specified in actor0’s constraint frame 
  • 目标速度,也定义在actor0的constraint frame中。target velocity, specified in actor0’s constraint frame 
  • 弹性?spring 
  • 阻尼。damping 
  • 能提供的最大力。forceLimit - the maximum force the drive can apply 
  • 加速标志。acceleration drive flag 
它会以设置的阻尼和倔强系数stiffness?往目标位置运动。A physical lag due to the inertia of the driven body acting through the drive spring will occur; therefore, sudden step changes will result over a number of time steps. Physical lag can be reduced by stiffening the spring or supplying a velocity target.

如果位置固定目标速度为0, a position drive will spring about that drive position with the specified springing/damping characteristics:
复制代码
//  set all translational degrees free

d6joint->setMotion(PxD6Axis::eX, PxD6Motion::eFREE);
d6joint->setMotion(PxD6Axis::eY, PxD6Motion::eFREE);
d6joint->setMotion(PxD6Axis::eZ, PxD6Motion::eFREE);

//  set all translation degrees driven:

PxD6Drive drive( 10.0f, - 20.0f, PX_MAX_F32,  true);
d6joint->setDrive(PxD6JointDrive::eX, drive);
d6joint->setDrive(PxD6JointDrive::eY, drive);
d6joint->setDrive(PxD6JointDrive::eZ, drive);

// Drive the joint to the local(actor[0]) origin - since no angular dofs are free, the angular part of the transform is ignored

d6joint->setDrivePosition(PxTransform::createIdentity());
d6joint->setDriveVelocity(PxVec3::createZero());
复制代码

Angular drive和linear drive完全不同,angular drive木有一种简单直接的方式用以描述它相对于奇点(singularities)是怎么自由运动的, 因此D6 joint提供了两种angular drive models——扭摆 和 球面插值SLERP (Spherical Linear Interpolation).

这俩angular drive model最大的区别在于他们使用四元数的方式。SLERP中直接使用四元数,扭摆模型里面会吧四元数分解成blabla. 扭摆模型多数情况下的行为都是比较一致的,但要注意180度的摆动,这时候actor并不会沿着两个角度之间的最短弧线运动(球体表面的最短路径). SLERP模型总是会让actor沿着最短对角弧运动。

只有有任何一个方向的角度不能改变, SLERP drive 参数就会被忽略。当所有方向上都是角度自由,and parameters are set for multiple angular drives, the SLERP parameters will be used.

The angular drive model has the following parameters:
  • 角速度,定义在actor0的constraint frame。An angular velocity target specified relative to actor0’s constraint frame 
  • 目标姿态。定义在同上。An orientation target specified relative to actor0’s constraint frame 
  • SLERP或扭摆drive的配置。drive specifications for SLERP (slerpDrive), swing (swingDrive) and twist (twistDrive): 
  • 弹性。spring - amount of torque needed to move the joint to its target orientation proportional to the angle from the target (not used for a velocity drive). 
  • 阻尼。damping - applied to the drive spring (used to smooth out oscillations about the drive target). 
  • 那啥。forceLimit - maximum torque applied when driving towards a velocity target (not used for an orientation drive) 
  • 那啥。acceleration drive flag 
Best results will be achieved when the drive target inputs are consistent with the joint freedom and limit constraints. 


PhysX3 User Guide 04 - Rigid Body Dynamics

Posted on 2011-06-03 16:06 mumuliang 阅读( 1349) 评论( 0) 编辑 收藏
本章涉及了了不少模拟刚体物体必须了解的点。

  Applying Forces and Torques      
 
物理学上和物体交互的方式常是对其施以外力。在经典力学中,很多物体之间的交互都是采用力来求解。因为力遵守如下法则:

f = m*a (force = mass * acceleration) 力= 质量*加速度 0-0

 

力直接控制物体的加速度,间接影响其位置和速度。因此,如果你需要立刻获得反应的话,使用力Force可能不太方便。力的好处是无需考虑场景中受力物体Body的情况,模拟程序会计算定义所有的约束条件。重力就是这么干的。

不幸的是,大量力会导致共振,使速度越来越大,最后系统solver因为无法maintain the joint constraints而崩溃。不像现实世界,joint break就是了。(joint的breakable打开也不行么?)

同一模拟帧内的力可以累加的,下一帧会全部置零。你可以设置力和力矩torque。 PxRigidBodyPxRigidBodyExt中的相关函数如下. 

 

复制代码
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::eIMPULSEs, impulsive force. PxForceMode::eVELOCITY_CHANGE 会无视物体的质量...更多信息查看API手册中的PxForceMode章节.

  Gravity      
 
重力是最常用的。如果需要在整个场景内都使用重力效果,使用 PxScene类的 setGravity()进行设置。

使用米和秒为单位的话,重力值为9.8,方向为场景的竖直向下。

某些效果可能要求一些dynamic actor暂时不受重力影响(真想不出是啥时候啥情况),你可以这么干:

PxActor::setActorFlag(PxActorFlag::eDISABLE_GRAVITY,  true);

 

Note: 改变重力效果的时候要严重当心。记得要手动恢复失重的物体,用 PxRigidDynamic::wakeUp()。因为系统如果要自动唤醒失重物体的话,势必遍历场景中的所有物体,性能不佳。.

    Setting the Velocity      
 

如果是想立刻让一个物体运动,给它初速度是最直接的。还有个更直接的办法是设动量momentum,如果你不知道物体的质量的话,这种方法可能更方便, 只是要知道速度的话还得要质量。


场景中设了初速度的物体, 你可能会遇到在第一帧结束以后速度就没了的情况。这是因为如果这个初速度和其他什么constraint冲突的话,就会被simulation以其他的设置重写。例如,如果一个球静止在桌子上,同时给了它一个向下的初速度,那么这个初速度就会,变成0. 如果给链条上某链节一个初速度,这个链节上的初速度会减小,其他链节的初速度会增大,以保证所有链节仍然连接在一起.

初速度的设置方法简单的说,如下:

 

void PxRigidBody::setLinearVelocity( const PxVec3& linVel,  bool autowake);
void PxRigidBody::setAngularVelocity( const PxVec3& angVel,  bool autowake);

   Kinematic Actors        
 
dynamic Actor受力force影响,也受冲量impulse影响。通常俺们用 PxRigidBody::addForce()PxRigidBody::addTorque()函数的时候,用这俩枚举量 PxForceMode::eIMPULSE , PxForceMode::eFORCE 对其设置. 但是这些都是在以二手或者三手方式在控制actor,不够灵活。例如当想要移动舞台和角色的时候,直接改变它们的Actor的位置Position是最直接的。kinematic Acotor就提供了这样一种一手方式.

使用 PxRigidDynamic::moveKinematic()可以尽情操作kinematic Actor. 除了这个函数Kinematic Actor对不管是力,重力,碰撞啥的,都不起反应。这条命令先被暂存起来,当内部的simulate完成后,它会计算出一个速度,然后在本帧内以该速度向目标位置移动,本帧内的这一步移动完成后,速度会被归零。因此需要在反复调用该函数才能完成沿着某路径运动的效果。也就是在移动过程中,kinematic actor表现得像是质量为无穷大,因此总是马上停下来.

创建kinematic actor的方法只是在创建一个普通dynamic actor之后打开它的kinematic flag:
 
PxRigidDynamic* rigidDynamic;
rigidDynamic->setRigidDynamicFlag(PxRigidDynamicFlag::eKINEMATIC,  true);


只要把这个kinematic flag关闭,actor就又变成一个普通的dynamic actor. 如果你向一堆dynamic中的一个kinematic增加mass时,其实mass并没有增加,因为这个mass被用在了kinematic上,相当于沧海一粟,这一堆dynamic的总mass也不会变化。

附加说明:

  • 注意 PxRigidDynamic::moveKinematic() PxRigidBody::setGlobalPose() 的区别. setGlobalPose()也会将Actor移动到指定位置,但不会引起其他对象的变化。尤其是,它根本不会阻挡别的物体运动,而是会穿墙而过。但这个setGlobalPose()还是会被用到, 如果仅仅是想把某个东西移动到某个位置时。. 
  • kinematic actor是会推开别人的,如果有人挡道的话,但是其他物体不会反作用于kinematic物体。也就是说kinematic actor的出现,让其他dynamic actor表现为static actor或者kinematic actor. 譬如,一个dynamic豆腐会把冲过来的kinematic脑袋撞个坑?。
  • kinematic 和 static actor之间木有相互作用和碰撞。 

 

   Sleeping     
 
actor若持续一段时间木有动作, 可以看做将来它也不会再动作,除非再受到了外力。因此在下一次受到外力之前,它都不必参与simulate.这种状态就是Sleeping. 查询一个物体是不是睡了可以酱:

 

bool PxRigidDynamic::isSleeping();

actor的动能kinematic energy低于某个值的话就会进入睡眠状态。这个门槛值可这样设置:

 

void PxRigidDynamic::setSleepEnergyThreshold(PxReal threshold);
PxReal PxRigidDynamic::getSleepEnergyThreshold()  const;

一个沉睡物体的醒来是因为有其他醒着的物体touch了它,或者它的什么属性发生了变化,睡还是没睡通常不应该是应用程序关心的问题,也不必。但如果非要人肉改变睡还是不睡,也不是没有办法:

 

void PxRigidDynamic::wakeUp(PxReal wakeCounterValue=PX_SLEEP_INTERVAL);
void PxRigidDynamic::putToSleep();

但是,也有一些特殊情况要求用户必须手动唤醒Sleeping actor。哪些函数有这样的要求,API参考文档有详情。

    Solver Accuracy      
 
求解器精确度。当刚体rigid body的运动受到关节jount或和其他物体的接触contract的约束时,约束求解器constraint solver就会自动运行。求解器为了满足所有的约束条件,会有限次遍历运动物体上的所有约束条件。这次数越大,求解的结果越精确越接近现实。默认是4个位置和1个速度的遍历(?!?!)。The solver iteration count defaults to 4 position iterations and 1 velocity iteration. 可以单独设置每个刚体物体的遍历次数:

 

void PxRigidDynamic::setSolverIterationCounts(PxU32 minPositionIters, PxU32 minVelocityIters);

眼目前俺们只发现了在一个关联了太多Joint并且这些joint的tolerance都很小的物体上才有必要把这个值设的比较大。如果该值需要设置到为30以上才能满足需求,基本上是整个模拟从根子上就有问题。

    Fast Rotation      
 
像铅笔这样的长条形状的物体模拟起来比较有难度,因为它们绕短轴旋转时会存储大量能量,而后若绕长轴旋转会变成一个很大的角速度。这样会出问题,因为旋转运动中的某些线性逼近米有办法完成。因此PhysX SDK中限制了角速度的最大值。当然这个值也是可以修改的。

void PxRigidDynamic::setMaxAngularVelocity(PxReal maxAngVel);


PhysX3 User Guide 05 - Scene Queries

Posted on 2011-06-04 16:26 mumuliang 阅读( 1030) 评论( 3) 编辑 收藏

有两种方式查询场景中的碰撞collision:批处理batched和非批处理non-batched。非批处理查询又称single shot queries,使用PxScene类的接口,是PxScene的一项常规功能。批处理查询是使用PxBatchQuery对象,PS3上只有批处理查询一种方式。

 

    Raycast queries      

 


查询中用户定义的射线ray会与整个场景相交intersected with the whole scene。PhysX有三种射线:

  • raycastAny
  • raycastSingle
  • raycastMultiple

 

查询碰撞collision时,返回的最主要的信息是一个布尔是非值,表示碰没碰上,若需获取更多信息,比如射线是不是穿过了什么之类的,就可以用raycastAny。至于交点的确切信息、相交的是个啥形状,这些都不是raycastAny关心的内容,它只关心一件事,是不是有击中发生(there was a hit)。人工智能中的视线查询(line-of-sight queries)就是一个典型应用。

 

raycastSingleraycastAny先进一些,通过它可以知道第一个被击中的是个嘛。譬如说子弹击中的情形。

 

raycastMultiple是最高级的。ray会hit到的所有物体都可以得到。好比说穿甲子弹。

 

注意:* solid物体(sphere, capsule, box, convex)是封闭的closed(即它们包括它们的边界boundaries)

        * plane是封闭的半空间 closed halfspace

        * heightfield也是封闭的,并且是solid物体。 

 

射线投射在solid物体上,射线是有终点的。射线ray和solid物体的相交intersection会引发一个击中报告hit report。

 

下表显示了不同物体对射线是否会产生hit report报告的反应是不同的。比如只要起点位于plane内部,即平面以下,无论射线终点在哪儿,也不会有plane被hit的report返回。

 PhysX3 User Guide_第1张图片 

     Sweep Queries     

 

 

Queries中Shape会以一指定的方向扫sweep过去,并报告是否与场景Scene有碰撞collision。也有三种sweep方式:

  • sweepAny
  • sweepSingle
  • sweepMultiple

 

 

它们的区别也和3种ray之间的区别一样。 

目前支持sweep的形状Shape有box, sphere和capsule。

 

    Overlap Queries     

 

 

overlap query返回的是和场景发生碰撞的物体所接触到的其他物体。有两种overlap

  • overapAny 
  • overlapMultiple

 

 

overlapAny 也叫placement API,特别适用于只知道场景的体积是不是空的情况。

 

overlapMultiple并不会在第一次hit后停下来,而是会返回其后所有被碰到的物体。

 

没有overlapSingle,因为没必要。overlap并没固定一个具体的方向,因此也就不能得到某个方向上的最近或最远,因此single木有存在的意义。(难道时间上也木有排序的意义么?)

 

overlap接受box,sphere和capsule三种形状。

 

    Filtering     

 

 

有几种方法可以把场景中不需要的Shape过滤掉。query与filter有关的参数有:

  • 结构体PxSceneQueryFilterData,包括PxSceneQueryFilterFlagsPxFilterData
  • PxSceneQueryFilterCallback选项

 

 

这些灵活多变的filter机制允许用户锁心所欲的自定义过滤行为。俺们来看几个例子:

 

首先PxSceneQueryFilterFlag::eSTATIC PxSceneQueryFilterFlag::eDYNAMIC 标志提供了第一层过滤。它们表示query是针对场景中的static还是dynamic对象。如果想query全部static或dynamic对象时,这显然比一个一个往filtering callback中加对象要方便。例如,爆炸效果可以球体的overlapMultiple来扫全场景中的dynamic对象(打开PxSceneQueryFilterFlag::eDYNAMIC标志),对static对象只需要给他们加个外力什么的就可以了。

 

第二个层次上的过滤由PxFilterData提供,它是一个128位的掩码。query会拿shape的掩码跟自己比较,通过才将shape纳入本次query。shape通过的情况有两种:1,query的掩码为零;2,shape的掩码和query的掩码按位与的结果非零(其实非零即等于掩码,掩码通常是只有1位为1)。

 

filter callback可以建立更灵活的过滤规则。filter callback需实现PxSceneQueryFilterCallback后传给query。然后场景查询可以在需要时调用其。调用时机可能是计算碰撞前也可能是其后,视实际情况。当然在计算之前就决定是不是要丢弃shape的开销是要小一些,但有时候计算以后的结果是你的判断条件之一。设置callback调用时机使用这俩filter标志:PxSceneQueryFilterFlag::ePREFILTERPxSceneQueryFilterFlag::ePOSTFILTER .

 

filter callback会返回一个PxSceneQueryHitType 。该值可能为:

  • eNONE,表示shape不许参与后面的query处理。
  • eBLOCK,表示shape是hit最终结束在的物体blocking hit,凡是比该shape更远的shape都不必参与query。(从后文来看该shape并没有被过滤掉,仍然是参与query的。)
  • eTOUCH,表示shape是一个touching hit,参与query处理,除非后续filter callback给它过滤掉了。 

 

 

eNONEeBLOCK比较简单。NONE就丢弃shape不参与query,BLOCK就将shape加入query。eTOUCH是新增加的值,为了处理像子弹穿过窗户(打碎窗户但继续向前)这样的情形。显而易见eTOUCH只对造成Multiple hits的query有效。

 

    Caching      

 

 

PxSceneQueryCache 可以用来加速某些情况下的query,尤其是raycastAny, raycastSinglesweepSingle .它的工作方式是:在缓存中定义了一些shape,甚至三角面(三角面和shape都会被首先测试)。高度时序性的query会因此得到显著的效率提升。解决这类时序问题的一个好方法是将上一帧的query结果放在query缓存对象中。

 

例如,人工智能的可见度query很可能连续几帧都返回同一个遮挡物blocking shape。那俺们就可以给到这个raycastAny(视线的query常用racycatAny,前面有讲)一个恰当的PxSceneQueryCache,然后呢,query会在检查其他shape之前就马上发现它hit了缓存中的这个shape,然后query就返回了。

 

再譬如,在缓存中存放上一次的closest hit,这会大大提高query查找closest hit的效率。

 

 

    Batched Queries     

 

 

批查询使用的是PxPatchQuery对象的接口。 顾名思义,就是可以把多个Query组合在一起,然后一次执行(调用PxBatchQuery::execute())。批查询基本上和非批查询是一样的。他俩的区别主要是:

  • PS3的SPUs只能跑批查询
  • 硬编码的过滤方程(hardcoded filtering equation) 不能用于批查询。取而代之的是两个filter shader:PxBatchQueryPreFilterShader 和 PxBatchQueryPostFilterShader

 

查询结果先写入PxPatchQueryDesc中用户指定的缓冲区,后以同样的顺序输出到PxPatchQuery对象。



PhysX3 User Guide 06 - Callbacks and Customization

Posted on 2011-06-04 18:49 mumuliang 阅读( 1235) 评论( 1) 编辑 收藏
本章俺们要看看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(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         {
                ...
        }
}
复制代码

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





你可能感兴趣的:(引擎开发,物理引擎,PhysX,游戏开发,游戏引擎)