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!");
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提供了一组默认的PxDefaultAllocator和PxDefaultErrorCallback。
倒数第二个参数,PxTolerancesScale, 是创建场景中的物体的默认Tolerances。
最后一个bool,true表示可以 memory usage debugging 。
2 The Allocator Callback
The allocator callback是需要自己实现的动态内存管理的接口类,PxAllocatorCallback。physx实现了一个,PxDefaultAllocator。
#include <malloc.h>
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);
}
};
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);
}
{
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!");
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.81f, 0.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* mScene;
PxSceneDesc sceneDesc(mSDK->getTolerancesScale());
sceneDesc.gravity = PxVec3(0.0f, -9.81f, 0.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.5f, 0.5f, 0.1f); //static friction, dynamic friction, restitution
if(!mMaterial)
fatalError("createMaterial failed!");
mMaterial = mSDK->createMaterial(0.5f, 0.5f, 0.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.0f, 0.0f));
else if(axis == 1)
pose = PxTransform(PxVec3(0.0f, d, 0.0f),PxQuat(PxHalfPi, PxVec3(0.0f, 0.0f, 1.0f)));
else if(axis == 2)
pose = PxTransform(PxVec3(0.0f, 0.0f, d), PxQuat(-PxHalfPi, PxVec3(0.0f, 1.0f, 0.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);
PxU32 axis = 1;
PxTransform pose;
if(axis == 0)
pose = PxTransform(PxVec3(d, 0.0f, 0.0f));
else if(axis == 1)
pose = PxTransform(PxVec3(0.0f, d, 0.0f),PxQuat(PxHalfPi, PxVec3(0.0f, 0.0f, 1.0f)));
else if(axis == 2)
pose = PxTransform(PxVec3(0.0f, 0.0f, d), PxQuat(-PxHalfPi, PxVec3(0.0f, 1.0f, 0.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.0f, 5.0f, 0.0f), PxQuat::createIdentity());
PxVec3 dimensions(1.0f, 1.0f, 1.0f);
PxBoxGeometry geometry(dimensions);
PxRigidDynamic *actor = PxCreateDynamic(*mSDK, transform, geometry, *mMaterial, density);
if (!actor)
fatalError("create actor failed!");
mScene->addActor(*actor);
PxTransform(PxVec3(0.0f, 5.0f, 0.0f), PxQuat::createIdentity());
PxVec3 dimensions(1.0f, 1.0f, 1.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;
}
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 类提供了一个完成该功能的函数。
略