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 的步骤:
- 用SDK对象创建一个Actor, (要给出它的pose (orientation and position))。
- 创建Shape需要的Geometry
- Actor创建Shape,们。(别忘了材质和shape的相对pose)
- 更新mass、inertia 等属性。 (PhysX Extensions中有相关函数)
- 向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之间有些是可以分离的。
例如雪人中,有些会被雪球砸中分开。这需要设置一个PxSimulationFilterShader的pairFlags. 然后在可分离的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 = <theDetachingShapeYouKnow>
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().
它会计算和设置
- actor的mass
- 重心,center of mass
- 最大静摩擦力? 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.