http://blog.csdn.net/kun1234567/article/details/2580500
PhysX官方手册翻译
本人水平有限,翻译中如果出现比较恶的句子...大家一定要查阅原文。
更新:2008-7-1 22:22 Raycasting(射线查询)
更新:2008-6-26 23:33
更新:2008-6-26 0:38
更新:2008-6-25 0:50
Broad Phase Collision Detection
翻译:kun
碰撞检测的第一步是找出场景中可能发生碰撞的碰撞对。因为存在n*n/2对可能的碰撞对,如果场景很大,或者对象很多的话,全部检测将花费相当多的时间。物理引擎将自动的为各种几何体划分空间,通过这种方式,一个形状将只检测在它空间附近的几何体。(空间分割)注意:在以前的版本中,SDK允许使用者自己定义空间划分算法,后来发现不提供这种特性反而更好。
几何体对过滤如果一对几何体被判定为可能相交,将做3次进一步的判定,根据结果来确定这对几何体是不是使用者所关心的几何体对。只有通过这3次检测之后,才会花时间进行碰撞模拟的计算。如果以下条件为true,则表示几何体A和几何体B需要进行碰撞检测:
(a->getActor()->isDynamic() || b->getActor()->isDynamic())
&& NxScene::getGroupCollisionFlag(a->getGroup(), b->getGroup())
&& (!(NxScene::getShapePairFlags(a,b) & NX_IGNORE_PAIR))
第1个检测的含义是静态对象不会主动引起碰撞,所以静态对象不会进行碰撞检测。(但不表示动态对象不撞它)
第2个检测的含义是只有Actor的group掩码一致才能发生碰撞。
第3个检测的含义是两个被连接杆连接的对象将不会发生碰撞检测。如果需要该特性,需要设置变量 NX_COLL_VETO_JOINTED 为false.
碰撞组 首先将检测2个几何体的碰撞组是否一致。所有的几何体都可以通过以下形式指派碰撞组。
NxShape::setGroup(NxCollisionGroup)
碰撞组是一个0到31的整数。下面的代码表示将一个几何体设置到11组。
myShape->setGroup(11);
所有的几何体的碰撞组都默认为0,but this does not really come with any built in meaning(不懂啥意思...)。SDK维护着一张32*32的表格,该表格精确的指示了各个组之间的碰撞关系。默认情况下,任意组之间都可以发生碰撞。你可以通过以下调用来更改该表的条目:
NxScene::setGroupCollisionFlag(CollisionGroup g1, CollisionGroup g2, bool enable);
例如,以下代码表示设置组11和组0之间不碰撞。
gScene->setGroupCollisionFlag(11, 0, false);
如果事先知道各个组之间的关系,那么这张表会十分有用。 Actor对象也可以指派组。Actor组和Shape组将同时起作用,(但是谁覆盖谁?)虽然它们是出于同一个目的。它们的工作方式相同,但是Actor可以具有更大的范围,最多支持0x7FFF个组,而Shape组则只有32个。Actor组将更加灵活。首先通过以下代码可以设置Actor的组:
myActor->setGroup(333); 每个Actor默认组为0。
接下来是设置组之间的关系:
gScene->setActorGroupPairFlags(333,334, NX_NOTIFY_ON_START_TOUCH | NX_NOTIFY_ON_END_TOUCH);
以下标志量可以被联合使用:
NX_NOTIFY_ON_START_TOUCH, NX_NOTIFY_ON_END_TOUCH, NX_NOTIFY_ON_TOUCH.
在触发器章节中,可以得到以上3个标志量的详细解释。
禁止检测的几何体对
判断一对几何体之间的碰撞检测是否被禁止。任意一对Actor或Shape都可以通过以下代码来设置:
NxScene::setActorPairFlags(NxActor&, NxActor&, NxU32 flags);
NxScene::setShapePairFlags(NxShape&, NxShape&, NxU32 flags);
如果一个Actor有多个Shape,显然你不希望这些Shape相互之间进行碰撞检测,这个时候上面的第2种调用就十分有用了。在这种情况下,只需要设置相交标志量为 NX_IGNORE_PAIR。以下代码表示将忽略这2个Actor之间的碰撞。
gScene->setActorPairFlags(a1, a2, NX_IGNORE_PAIR);
注意:其他的标志量将在之后的章节里讨论。
自定义Actor过滤规则
可以自定义Actor对的过滤规则,你完全可以自己决定哪些Actor可以相交。如果需要使用这个特性,需要做以下步骤:
1、实现NxUserActorPairFiltering 接口,包括onActorPairs 回调函数。这个回调函数将会在决定是否将 Actor对 存入碰撞检测数组时被调用。
2、为每一个应用你的划分策略的Actor设置NX_AF_USER_ACTOR_PAIR_FILTERING 标志量。
3、当你更换划分策略的时候,你需要显示的调用那些应用自定义规则的Actor的NxActor::resetUserActorPairFiltering() 方法,这样他们就会更新自己的Actor碰撞对数组。否则只有等到下一次碰撞发生的时候新规则才能起作用。
例子:Sample Filtering
Contact Filtering
翻译:kun
除了NxScene::setGroupCollisionFlag() 和 NxScene::setShapePairFlags() 提供的过滤机制,SDK还提供了一种久经考验的过滤机制,它基于布尔值和标志量。使用者可以为每一个几何体指定一个128位的组掩码。该掩码将与用户指定常数和操作进行混合,并且它的结果将决定一对几何体是否发生接触。剔除算法的伪代码如下:
bool shouldGenerateContacts(shape0Bits, shape1Bits)
{
value = (shape0Bits op0 constant0) op2 (shape1Bits op1 constant1)
return ((bool)value)==filterBool;
}
也就是说,shape0的掩码将和[常数0]以[操作规则0]的方式进行混合,shape1的掩码将和[常数1]以[操作规则1]的方式进行混合。最后,2个结果以[操作规则2]的方式进行混合,并且结果将和变量filterBool进行比较。如果最后的结果为true,则判定该对几何体将发生接触。
有效的操作(NxFilterOp的成员)
NX_FILTEROP_AND - result = A & B
NX_FILTEROP_OR - result = A | B
NX_FILTEROP_XOR - result = A ^ B
NX_FILTEROP_NAND - result = ~ (A & B)
NX_FILTEROP_NOR - result = ~ (A | B)
NX_FILTEROP_NXOR - result = ~ (A ^ B)
NX_FILTEROP_SWAP_AND -
results.bits0 = A.bits0 & B.bits2;
results.bits1 = A.bits1 & B.bits3;
results.bits2 = A.bits2 & B.bits0;
results.bits3 = A.bits3 & B.bits1;
可以通过以下方式设置 操作规则/常数/过滤布尔值(filterBool):
void NxScene::setFilterOps(NxFilterOp op0, NxFilterOp op1, NxFilterOp op2);
void NxScene::setFilterBool(bool flag);
void NxScene::setFilterConstant0(const NxGroupsMask& mask);
void NxScene::setFilterConstant1(const NxGroupsMask& mask);
NxGroupsMask 是一个将128位的值封装成4个32位的值的类:
class NxGroupsMask
{
public: NxU32 bits0, bits1, bits2, bits3;
};
最后,NxShape 提供了设置/获取 组掩码的方法:
void NxShape::setGroupsMask(const NxGroupsMask& mask);
const NxGroupsMask NxShape::getGroupsMask() const;
另外,组掩码也可以通过 shape 描述符在创建的时候指定。
NxShapeDesc::groupsMask;
下面以4个几何体为例子,说明碰撞检测的过程。(*表示将发生接触)
(表略)
首先规定4个基本掩码,然后以合适的方式进行混合,并分别设置到4个几何体上,其中几何体D的组掩码为混合的。如下表:
(表略)
然后设置 常量/操作规则,确保 如果2个几何体的组掩码进行ANDed 操作的时候产生一个非0的值,则判定为发生接触。
gScene->setFilterOps(NX_FILTEROP_OR, NX_FILTEROP_OR, NX_FILTEROP_AND);
void NxScene::setFilterBool(true);
NxGroupsMask zeroMask;
zeroMask.bits0=zeroMask.bits1=zeroMask.bits2=zeroMask.bits3=0;
void NxScene::setFilterConstant0(zeroMask);
void NxScene::setFilterConstant1(zeroMask);
设置常数0和常数1为0,并且设置操作规则0和操作规则1为NX_FILTEROP_OR,等价于不对参与计算的几何体的组掩码做计算,然后这2个掩码执行NX_FILTEROP_AND 操作,最后的结果和filterBool 进行比较。也就是前面所说的判定规则。
注意1:对于任意两个参与碰撞检测的几何体,如果设计出了A碰B 和B碰A 不相等的结果,那这种过滤器就要不得。(规则必须满足交换律)
例子: Sample Filtering
Triggers
翻译:kun
触发器是一个几何体,它允许其他几何体穿越自己。每一个几何体在穿过它的时候可以产生相对应的事件,如进入、离开、或者停留。触发器可以用来实现感应门之类的东西。
触发器可以是任意形状的,除了环行。Triangle mesh triggers count as hollow surfaces for collision detection, not volumes. Note that convex vs. triangle mesh and mesh vs. mesh triggers are not implemented. 创建一个触发器,并将它绑定到某个Actor上的步骤,跟添加一个几何体形状到Actor差不多。唯一的不同就是需要在Shape描述符里标记成触发器:
//This trigger is a cube.
NxBoxShapeDesc boxDesc;
boxDesc.dimensions = NxVec3(10.0f, 10.0f, 10.0f);
boxDesc.shapeFlags |= NX_TRIGGER_ENABLE;
NxActorDesc actorDesc;
actorDesc.shapes.pushBack(&boxDesc);
NxActor * triggerActor = gScene->createActor(actorDesc);
在上面的例子中,我们指定了 NX_TRIGGER_ENABLE 标志,这表示触发器将产生所有的事件。当然也可以通过以下标志来进行选择:NX_TRIGGER_ON_ENTER, NX_TRIGGER_ON_LEAVE, 和 NX_TRIGGER_ON_STAY.
要接收触发器事件,就应该实现事件回调函数。回调函数可以通过派生 NxUserTriggerReport 类来实现。下面是个例子:
class TriggerCallback : public NxUserTriggerReport
{
void onTrigger(NxShape& triggerShape, NxShape& otherShape, NxTriggerFlag status) {
if(status & NX_TRIGGER_ON_ENTER)
{
//A body entered the trigger area for the first time
gNbTouchedBodies++;
}
if(status & NX_TRIGGER_ON_LEAVE)
{
//A body left the trigger area
gNbTouchedBodies--;
}
//Should not go negative
NX_ASSERT(gNbTouchedBodies>=0);
}
} myTriggerCallback;
gScene->setUserTriggerReport(&myTriggerCallback);
警告:
1、对Actor执行“根据Shape计算mass”的操作时,被标记为触发器的几何体,不会参与其中的计算。因此,如果创建了一个动态Actor,它只有一个Shape,而这个Shape又刚好被标记为触发器,则必须为Actor指定一个mass和惯性。
2、触发器对射线和扫描检测透明。
3、SDK全局变量 NX_TRIGGER_TRIGGER_CALLBACK 决定是否在2个触发器几何体接触的时候产生触发器事件。
4、一个静态几何体不会触发一个静态触发器。
5、其他任意的组合都会产生触发器事件。比如 运动对象&动态对象,运动对象&动态对象,动态触发器&运动对象等.
6、不能在OnTrigger()函数中改变物理环境,具体的说就是不能创建或销毁对象。如果必须改变模拟环境,也要等到本次时间片模拟完,才可以进行相关操作。
7、凸多边形&三角形网格 和 网格&网格触发器 还没实现。
8、触发器不参与连续检测(CCD)。
线程安全
NxUserTriggerReport 对象只会在用户线程被调用,因此不需要保证它的线程安全。
Continuous Collision Detection
翻译:kun
连续检测对快速移动的对象十分有用。在非连续检测的方案里,如果一个对象足够的快,那它就可以在一个时间片里穿越一些足够薄的障碍,就象隧道效应。想象一颗子弹飞向一个薄片,前一贞里子弹还在片的左边,当进行当前贞位置计算的时候,子弹速度*时间片>片的厚度,那么子弹的位置就直接被设置到薄片的另一边了。在这种情况下,SDK将无法检测出子弹和片的碰撞。
为了解决这个问题,PhysX采用了连续检测(CCD)策略。该策略不是检测一些离散的点,而是构造一个几何体,该几何体是对象在整个时间片内的路径几何体。检测这个几何体,如果发现有碰撞,那么就可以计算出实际发生碰撞的时间,并且根据计算结果产生一些合理的动作。
在当前版本,CCD支持 动态对象&动态对象,动态对象&静态对象之间的检测。
CCD 骨骼 CCD是通过内嵌到对象的骨骼网格实现的。它比离散检测更加简洁。骨骼会阻止一个对象穿越另外一个对象。当一个对象静止或者套在另外一个对象上,更高频率的离散检测可以提供更加符合现实的行为。
CCD骨骼是一种网格,不过较之标准的网格来说,CCD骨骼的限制更少。它可以使用游离的顶点,也就是说,它的顶点不会拿来形成三角面,但是不能给出一个退化的三角形。因此你可以使用单独的顶点来做CCD射线检测。
另外,一个单独的CCD骨骼,可以关联到多种shape,这样可以减少内存开销。
SDK全局变量 NX_CCD_EPSILON 用来指定CCD骨骼的厚度,从而改善计算的精确度。当一个点试图穿越一个三角面,CCD将阻止它,并且把它的位置设置成贴着三角面的位置。不过,由于计算精度问题,这个点可能会被设置到三角形的另外一个面帖着。这样一来它便穿越了这个三角面,就象有隧道效应一样。因此 NX_CCD_EPSILON 在判断这个问题上就十分有用。因为 NX_CCD_EPSILON 实际上就是数学上的 “极小的正数”,它可以被设置成极小的值。
要创建一个CCD骨骼,可以通过使用NxPhysicsSDK::createCCDSkeleton() 。该函数接受一个 NxSimpleTriangle 对象,该对象用来描述网格。要关联一个CCD骨骼和一个shape,使用 NxShape::setCCDSkeleton() 方法。
CCD骨骼指导方针:
1、CCD的开销比普通的碰撞检测要大的多,因此使用尽量少的三角面和顶点。
2、对于多shape的Actor,每一个形状的子骨骼将合并成一个单独的骨骼。下面的约束将影响如何计算这个单独的骨骼的顶点和边。
3、骨骼不支持超过64个顶点。
4、骨骼最多只能有256个边
5、静态对象不需要CCD骨骼,并且也不使用它们,因为直接使用形状的mesh要好的多。(注意,只有顶点和三角形网格才参与CCD)
6、CCD骨骼几何体应该完全包含在它关联的shape内部,实际上,最好能跟外边框保持一定距离。
对子弹例子的进一步说明。有时候你可能为了一些特定的表现效果,需要给一些对象指定精确的CCD骨骼,好让它们可以在某些情况下利用隧道效应发生穿越,但是需要注意以下几点:
1、你是希望CCD骨骼越精确越好呢,还是希望简单而有效呢?只有一个顶点的CCD骨骼也许是效率最高的选择。
2、一个小的CCD骨骼可以让Actor对象通过一个一般情况下都不能走进去的狭长通道。 3、CCD骨骼越大,那它被CCD算法检测出来的几率就越大?
CCD可以有效的检测Actor的碰撞,从而使它的动作停下来。但是CCD把碰撞后的侧滑动作也给停了,可能这并不是你想要的效果。为了防止这样的事情发生的频率过快,可能你会使CCD骨骼比它关联的形状要小(形状要减去皮肤厚度),这样一来CCD骨骼会被用来检测一个对象是不是嵌入另外一个对象太深(正常情况下,只会嵌2个对象的皮肤厚度)。
//-------------------------------------------------------------------
// 这里翻译的有问题
One situation where penetration depths normally increase past the skin width is in large stacks, where the iterative solver is working hard to de-penetrate objects. There are two main ways of finding a CCD skeleton that fits your game scenario, depending on what you are optimizing for:
Accurate collision A good way to find a CCD skeleton offset that fits your game scenario, is to start with a big CCD skeleton and make it smaller until you no longer see too many CCD blockages at low velocities.
Fast collision A good way to find a CCD skeleton for you scenario is to start off with a one-vertex skeleton and grow it until objects stop escaping through narrow passages, or stop missing in dynamic/dynamic collision scenarios.
这个方法比离散检测要有效的多。有2种方法来确定合适的CCD骨骼大小:
1、先给一个很大的CCD骨骼,然后逐渐调小
2、先给一个很小的CCD骨骼,然后逐渐调大
//-------------------------------------------------------------------
你可以通过 NX_VISUALIZE_COLLISION_SKELETONS 参数来显示CCD骨骼。注意,由于CCD骨骼一般都比shape小,因此可能需要在半透明/线框模式下才能看到他们。
NxSimpleTriangleMesh triMesh; //Fill in triMesh...
NxCCDSkeleton *newSkeleton=gPhysicsSDK->createCCDSkeleton(triMesh);
NxShape *myShape=...
myShape->setCCDSkeleton(newSkeleton);
什么时候使用CCD?
通过设置 NX_CONTINUOUS_CD 变量来启用CCD计算。因为CCD比离散检测要慢,这里有几个优化方法。首先,只有快速运动的对象才需要使用CCD,如果一个对象很容易被一个表面所阻挡,离散检测对它来说就足够了。
为了控制什么时候开始进行CCD,可以通过NxActor::setCCDMotionThreshold() (或者通过 NxBodyDesc 描述符 的 CCDMotionTreshold 字段) 来指定一个启用CCD的最小速度。换句话说,如果低于这个最小速度,则只会使用离散检测。这个规则对绝对速度和相对速度都有用。(比如 动态对象&动态对象 之间的CCD)
如果一个shape要参与 动态对象&动态对象 的CCD,那这个shape 需要设置 NX_SF_DYNAMIC_DYNAMIC_CCD 标志。
/// 不知道为什么有两段一样的话... NX_CCD_EPSILON 变量为CCD骨骼提供了一个非常小的厚度,从而可以提高计算精度。当一个点试图穿越一个三角面,CCD将阻止它,并且把它的位置设置成贴着三角面的位置。不过,由于计算精度问题,这个点可能会被设置到三角形的另外一个面帖着。这样一来它便穿越了这个三角面,就象有隧道效应一样。因此 NX_CCD_EPSILON 在判断这个问题上就十分有用。因为 NX_CCD_EPSILON 实际上就是数学上的 “极小的正数”,它可以被设置成极小的值。
下面的条件用来决定 是否在 动态对象和静态对象之间进行CCD:
1、NX_CONTINUOUS_CD 被设置,或者在某个地方 NX_CF_INHERIT_SETTINGS 标志被清除 ,NX_CONTINUOUS_CD 被设置。
2、动态对象有骨骼。
3、对象的点速度超过CCDMotionThreshold.
4、静态对象是一个 凸多边形网格,或者是三角面网格。
下面的条件用来决定 是否在 动态对象和动态对象之间进行CCD:
1、NX_CONTINUOUS_CD 被设置,或者在某个地方 NX_CF_INHERIT_SETTINGS 标志被清除 ,NX_CONTINUOUS_CD 被设置。
2、两个都有body,是动态对象
3、至少存在一对被设置了 NX_SF_DYNAMIC_DYNAMIC_CCD 的几何体对 存在。
4、2个对象都有CCD骨骼
5、2个对象的点速度都超过CCDMotionThreshold.
6、2个对象的相对速度都超过CCDMotionThreshold.
串行化 CCD骨骼可以使用以下函数来保存到内存中。
NxU32 NxCCDSkeleton::save(void * destBuffer, NxU32 bufferSize);
NxU32 NxCCDSkeleton::getDataSize();
可以用以下的方式来加载一个骨骼。
NxCCDSkeleton *NxPhysicsSDK::createCCDSkeleton(const void * memoryBuffer, NxU32 bufferSize);
直接使用内存数据比手动构造CCD骨骼要更快,因为这样可以预先储存特定的骨骼数据格式。
限制:
1、CCD骨骼最多64个顶点
2、骨骼最多256条边
3、当静态对象是 球、胶囊、片等形状的时候,不会在动态对象和静态对象之间发生CCD。因为静态对象一般都不会使用这些形状,而是使用多边形。(CCD骨骼基于网格工作)
4、如果静态对象的几何体非常复杂,有很多细节(高模,一般不会在游戏中使用),动态对象可能会在处理接触点的时候犯迷糊。一般可以通过增大CCD骨骼的尺寸来减小这方面的影响,但是这取决于模型网格,并不能完全的消除影响。
5、动态对象和动态对象之间的CCD 使用近似值,它假定一个形状 正在飞快的旋转。如果2个对象的形状都在非常快的旋转,那CCD的结果可能就会出现很大偏差。
6、CCD 假设模型的旋转 在一个时间片里不会超过180度。
7、CCD会停止对象的动作,因此当一个渗透被检测到,对象就停止了,甚至它只是模拟了时间片的一半。这个可能会造成对象在某贞里被挂在空中。
8、触发器不参与CCD
9、在将一个CCD骨骼关联到一个动态对象的Shape上的时候,需要十分小心。因为可能会产生 一个非CCD形状已经检测到了碰撞,而CCD形状还没有。你需要确定自己不会创建出这样的东西。
10、运动学对象不会被CCD中断动作,因此对一个快速移动的运动学对象使用CCD是没有效果的。(除非这个运动学对象正在快速移动,同时另外一个具有CCD形状的动态对象也在高速运动,那这个动态对象的行为可能会被运动学对象中断)
11、CCD在受限制的硬件场景中不工作。
例子:略
例子: Sample CCD Explosion Sample CCD Dynamic
Dominance Groups
翻译:kun
通过 “组间优势”(组的优先级),可以在Actor对象之间设置单向约束。比如 骑手和坐骑,角色和它的尾巴,你不希望这些对象之间相互影响,或者说你不希望马尾巴摇摆的时候,还带动马屁股摇摆(但是马屁股摇摆的时候,马尾巴会做出相应的动作),对于这种情况,“组间优势”所规定的单向影响就十分有用。
一个 NxDominanceGroup 对象 是个5位的组标志量(范围从0-31)。默认情况下所有的Actor对象都是0号组,静态对象永远都是0号组。你可以通过 描述符(dominanceGroup 字段)或者调用NxActor::setDominanceGroup().来改变它。
含义:
任何时候,如果需要确定两个对象(a0,a1)之间的约束条件,就需要这两个对象的 组参数。然后可以使用 getDominanceGroupPair(g0, g1) ,算出一个 NxConstraintDominance 对象。
在约束条件里,NxConstraintDominance::dominance0 是a0的优势值,NxConstraintDominance::dominance1 是代表a1的优势值。 默认情况下 NxConstraintDominance::dominanceN 的值为 1.0f,根据约束条件,这表示 aN 可以被 a1-aN 推或者拉动。而如果值为0.0,表示 a1 - aN 不会对它产生影响。这就是说,(1.f,1.f)表示互相影响,(0.f, 0.f)表示互不影响,而(1.f,0.f)就产生了单向影响。
默认行为:
getDominanceGroupPair(g1, g2)的返回值被定义如下:
1、 g1 == g2 返回 (1.f, 1.f)
2、 g1 < g2 返回 (0.f, 1.f)
3、 g1 < g2 返回 (1.f, 0.f)
换句话说,默认情况下,组 编号高的对象会被组对象低的对象影响。也就是说 组号越低,优先级越高。
可以通过使用 NxScene::setDominanceGroupPair() 来更改设置。
It is not possible to make the matrix asymmetric, or to change the diagonal. 换句话说:
1、如果(g1 == g2),无法更改 (g1,g2) 的结果。
2、如果你将 ( g1, g2) 的结果设置为 X,那么(g2,g1) 的结果将自动被设置为 ~X:
~(1.f, 1.f) == (1.f, 1.f)
~(0.f, 1.f) == (1.f, 0.f)
~(1.f, 0.f) == (0.f, 1s.f)
这两个约束条件保证了 g1,g2 的顺序无关性。
优先级设置目前只能设置为 0.f 或者 1.f。将来会允许使用者设置任意的小数来表达“部分单向”交互。
Raycasting 翻译:kun
射线查询是一种基本的碰撞检测手段。和关联到Actor或者Shape上的线段不同,射线查询由用户发起,在查询过程中穿透1个或多个shape。它很有用,比如你想实现“拾取”效果的时候。
下面是一些射线查询相关的函数,每一种所使用的射线都有一些不同,他们都是NxScene的成员函数,并且长的都象 raycast*():
1、raycastAnyBounds, raycastAnyShape -- 这2个方法是最原始的,但是也是最快的。它们简单的返回射线查询碰到的第1个AABB或者Shape对象。
2、raycastClosestBounds, raycastClosestShape -- 这2个方法跟前面2个方法差不多,不过同时还会返回射线点和交点的距离。
3、raycastAllBounds, raycastAllShapes -- 这2个方法是最复杂的版本,它们返回所有被射线穿透的对象,同时也包含到各个对象的距离。为了使用这些信息,你需要实现一个叫做 NxUserRaycastReport 的接口,它会由每一个被射线穿透的对象调用。
下面是一个实现 NxUserRaycastReport 使用 raycastAllShapes 的例子:
class myRaycastReport : public NxUserRaycastReport
{ virtual bool onHit(const NxRaycastHit& hits)
{
//Record information here.
return true; //Or false to stop the raycast.
}
}gMyReport;
NxRay worldRay;
worldRay.orig= cstart;
worldRay.dir= cend - cstart;
worldRay.dir.normalize(); //Important!!
NxU32 nbShapes = gScene->raycastAllShapes(worldRay, gMyReport, NxUserRaycastReport::ALL_SHAPES);
警告:
1、轮子型形状不会被 射线查询 检测到。
2、被射线查询检测到的 几何体 进行onHit() 回调的时候,SDK不保证它们是按照真实的几何顺序来进行的。
3、SDK 在做射线查询的时候会返回 2个面索引 。 faceID 是 mesh 被 cooked 之前的索引,而 internalFaceID 则是已经 cook 后的索引(cook 会导致重排面索引)(译者:SDK可能将三角面重新排列--以更加适合物理计算的方式)。只有在使用 被cooked 版本的 mesh 的函数--比如 readback系列函数 或者 saveToDesc() --才使用 internalFaceID.(参见Reading Back Mesh Data)
4、在 onHit() 的执行流程中, 不应该更改SDK的状态。也就是说,不要创建或销毁任何对象。如果一定要这样做,那就应该等到射线查询执行完成,并且将SDK的当前状态保存到缓存指针。
5、由于SDK是使用双缓存指针进行异步模拟,而双缓存对一些函数来说是透明的(也就是说它们不知道有双缓存,只知道‘当前缓存’),因此SDK的状态发生了变化之后,应该调用simulate()/fetchResults(),对它们来说SDK状态才有所变化。
多线程
NxUserRaycastReport 类只会在用户线程中调用,因此它不需要保证线程安全。
例子:
Sample Raycast
Basics:
Math Classes
4种数据结构:
NxVec3
NxQuat
NxMat33
NxMat34
Simple Shapes
7种基本物理模型:
NxBounds3 轴对称边界盒
NxBox 对象边界盒
NxCapsule 交囊
NxPlane 任意的面
NxRay 无限射线/直线
NxSegment 有限射线/线段
NxSphere 球
(事实上还有多边形,不过它应该不能称为简单)
SDK Initialization
NxCreatePhysicsSDK: 创建接口指针。
参数一:接口版本
参数二:内存管理类实体
参数三:用于输出的流
要点一:SDK版本要和驱动版本匹配
要点二:可以多次调用NxCreatePhysicsSDK,但是都将得到同一个实体,就象单件模式一样。但是存在引用计数,因此需要严格匹配 call 和 release 的次数。
要点三:多次调用的时候,后设置的内存管理类将被忽略,但是输出流对象则会被覆盖。
Casting and Instancing
创建实例:
1、声明描述符
2、调用PhysX接口创建该实例
释放:
父对象的释放会引起子对象的自动释放。
转换:
NxShape / NxSphereShape / NxMoxsSs
向下转换
Shape->isSphere() shape->isBox()
失败返回NULL。
Memory Management
虽然不是必须的,但是PhysX提供了内存管理接口。(继承NxUserAllocator)
重载了 malloc / realloc / free。
一旦被设置,就不能更改。
该类提供的内存管理函数必须保证是“线程安全的”。
Debug Rendering
不能使对象之间的间距可视化
Debug Information通过 gScene->getDebugRenderable() 返回的 NxDebugRenderable得到。
PhysX不是图象库,使用者需要自己绘制这些信息。
User Data
可以给对象(Actor)关联数据。
如 myShape->userData = ***;
User Defined Classes
8个需要用户自己实现的类:
NxUserAllocator 内存管理 NxUserOutputStream 错误信息 NxUserContactReport 反射(接触)通知NxUserNotify 事件通知 NxUserRaycastReport 射线查询回调 NxUserTriggerReport 触发器回调NxUserEntityReport 实体回调?? NxStream 串行化(用于数据IO)
Error Reporting
使用NxUserOutputStream
Saving the Simulation State
所有通过描述符创建的对象都有2个方法:saveToDesc() 和 loadFromDesc()
可以用于保存对象当前的状态,这个特性可以应用于保存游戏,或者其他目的。
SDK Parameters
PhysX有一套集中管理的全局变量,85个定义,其中变量83个,哑元1个,记录数量1个)
使用者可以通过 setParameters() / getParameters() 来修改他们。
大多数变量的默认值是不需要更改的。
Parameter Summary
Parameter | Default Value | Description |
NX_PENALTY_FORCE |
| DEPRECATED |
NX_SKIN_WIDTH | 0.025 | 皮肤厚度 (range: [0, inf) Unit: distance. |
|
|
|
NX_DEFAULT_SLEEP_LIN_VEL_SQUARED | (0.15*0.15) | The default linear velocity, squared, below which objects start going to sleep. (range: [0, inf)) |
NX_DEFAULT_SLEEP_ANG_VEL_SQUARED | (0.14*0.14) | The default angular velocity, squared, below which objects start going to sleep. (range: [0, inf)) |
|
|
|
NX_BOUNCE_TRESHOLD | -2 | A contact with a relative velocity below this will not bounce. (range: (-inf, 0]) |
|
|
|
NX_DYN_FRICT_SCALING | 1 | This lets the user scale the magnitude of the dynamic friction applied to all objects. (range: [0, inf)) |
NX_STA_FRICT_SCALING | 1 | This lets the user scale the magnitude of the static friction applied to all objects. (range: [0, inf)) |
|
|
|
NX_MAX_ANGULAR_VELOCITY | 7 | See API Reference for NxBody::setMaxAngularVelocity() for details. |
|
|
|
NX_CONTINUOUS_CD | 0 | Enable/disable continuous collision detection (0.0f to disable) |
|
|
|
NX_VISUALIZATION_SCALE | 0 | This overall visualization scale gets multiplied with the individual scales. Setting to zero turns off debug visualizations. |
|
|
|
NX_VISUALIZE_WORLD_AXES | 0 | Visualize the world axes. |
|
|
|
NX_VISUALIZE_BODY_AXES | 0 | Visualize a body's axes. |
NX_VISUALIZE_BODY_MASS_AXES | 0 | Visualize a body's mass axes. |
NX_VISUALIZE_BODY_LIN_VELOCITY | 0 | Visualize the bodies' linear velocity. |
NX_VISUALIZE_BODY_ANG_VELOCITY | 0 | Visualize the bodies' angular velocity. |
NX_VISUALIZE_BODY_JOINT_GROUPS | 0 | Visualize joint groups. |
NX_VISUALIZE_JOINT_LOCAL_AXES | 0 | Visualize local joint axes. |
NX_VISUALIZE_JOINT_WORLD_AXES | 0 | Visualize joint world axes. |
NX_VISUALIZE_JOINT_LIMITS | 0 | Visualize joint limits. |
|
|
|
NX_VISUALIZE_CONTACT_POINT | 0 | Visualize contact points. |
NX_VISUALIZE_CONTACT_NORMAL | 0 | Visualize contact normals. |
NX_VISUALIZE_CONTACT_ERROR | 0 | Visualize contact errors. |
NX_VISUALIZE_CONTACT_FORCE | 0 | Visualize contact forces. |
|
|
|
NX_VISUALIZE_ACTOR_AXES | 0 | Visualize actor axes. |
|
|
|
NX_VISUALIZE_COLLISION_AABBS | 0 | Visualize bounds (AABBs in world space). |
NX_VISUALIZE_COLLISION_SHAPES | 0 | Visualize shape. |
NX_VISUALIZE_COLLISION_AXES | 0 | Visualize shape axes. |
NX_VISUALIZE_COLLISION_COMPOUNDS | 0 | Visualize compound (compound AABBs in world space). |
NX_VISUALIZE_COLLISION_VNORMALS | 0 | Visualize mesh & convex vertex normals. |
NX_VISUALIZE_COLLISION_FNORMALS | 0 | Visualize mesh & convex face normals. |
NX_VISUALIZE_COLLISION_EDGES | 0 | Visualize active edges for meshes. |
NX_VISUALIZE_COLLISION_SPHERES | 0 | Visualize bounding spheres. |
|
|
|
NX_VISUALIZE_COLLISION_STATIC | 0 | Visualize static pruning structures. |
NX_VISUALIZE_COLLISION_DYNAMIC | 0 | Visualize dynamic pruning structures. |
NX_VISUALIZE_COLLISION_FREE | 0 | Visualize "free" pruning structures. |
NX_VISUALIZE_COLLISION_CCD | 0 | Visualize CCD tests. |
NX_VISUALIZE_COLLISION_SKELETONS | 0 | Visualize CCD skeletons. |
|
|
|
NX_VISUALIZE_FLUID_EMITTERS | 0 | Visualize emitter. |
NX_VISUALIZE_FLUID_POSITION | 0 | Visualize particle position. |
NX_VISUALIZE_FLUID_VELOCITY | 0 | Visualize particle velocity. |
NX_VISUALIZE_FLUID_KERNEL_RADIUS | 0 | Visualize particle kernel radius. |
NX_VISUALIZE_FLUID_BOUNDS | 0 | Visualize fluid AABB. |
NX_VISUALIZE_FLUID_PACKETS | 0 | Visualize fluid packets. |
NX_VISUALIZE_FLUID_MOTION_LIMIT | 0 | Visualize fluid motion limits. |
NX_VISUALIZE_FLUID_DYN_COLLISION | 0 | Visualize fluid dynamic mesh collision. |
NX_VISUALIZE_FLUID_STC_COLLISION | 0 | Not implemented: Visualize fluid static collision. |
NX_VISUALIZE_FLUID_MESH_PACKETS | 0 | Visualize available fluid mesh packets. |
NX_VISUALIZE_FLUID_DRAINS | 0 | Visualize fluid drain shapes. |
NX_VISUALIZE_FLUID_PACKET_DATA | 0 | Visualize fluid data packets. |
|
|
|
NX_VISUALIZE_CLOTH_MESH | 0 | Visualize cloth meshes. |
NX_VISUALIZE_CLOTH_COLLISIONS | 0 | Visualize cloth rigid body collision. |
NX_VISUALIZE_CLOTH_SELFCOLLISIONS | 0 | Visualize cloth self collision. |
NX_VISUALIZE_CLOTH_WORKPACKETS | 0 | Visualize cloth clustering for the PPU. |
NX_VISUALIZE_CLOTH_SLEEP | 0 | Visualize cloth sleeping. |
NX_VISUALIZE_CLOTH_SLEEP_VERTEX | 0 | Visualize cloth sleeping with full per-vertex information. |
NX_VISUALIZE_CLOTH_TEARABLE_VERTICES | 0 | Visualize tearable cloth vertices. |
NX_VISUALIZE_CLOTH_TEARING | 0 | Visualize cloth tearing. |
NX_VISUALIZE_CLOTH_ATTACHMENT | 0 | Visualize cloth attachments. |
NX_VISUALIZE_CLOTH_VALIDBOUNDS | 0 | Visualize cloth valid bounds. |
|
|
|
NX_VISUALIZE_SOFTBODY_MESH | 0 | Visualize soft body meshes. |
NX_VISUALIZE_SOFTBODY_COLLISIONS | 0 | Visualize soft body collisions with rigid bodies. |
NX_VISUALIZE_SOFTBODY_WORKPACKETS | 0 | Visualize soft body clustering for simulation on the PPU. |
NX_VISUALIZE_SOFTBODY_SLEEP | 0 | Visualize soft body sleeping. |
NX_VISUALIZE_SOFTBODY_SLEEP_VERTEX | 0 | Visualize soft body sleeping with full per-vertex information. |
NX_VISUALIZE_SOFTBODY_TEARABLE_VERTICES | 0 | Visualize tearable soft body vertices. |
NX_VISUALIZE_SOFTBODY_TEARING | 0 | Visualize soft body tearing. |
NX_VISUALIZE_SOFTBODY_ATTACHMENT | 0 | Visualize soft body attachments. |
NX_VISUALIZE_SOFTBODY_VALIDBOUNDS | 0 | Visualize soft body valid bounds. |
|
|
|
NX_ADAPTIVE_FORCE | 1 | Used to enable adaptive forces to accelerate convergence of the solver. |
NX_COLL_VETO_JOINTED | 1 | Controls default filtering for jointed bodies (true = collision disabled). |
NX_TRIGGER_TRIGGER_CALLBACK | 1 | Controls whether two touching triggers generate a callback or not. |
|
|
|
NX_SELECT_HW_ALGO | 0 | Internal parameter, used for debugging and testing. Not to be used. |
NX_VISUALIZE_ACTIVE_VERTICES | 0 | Internal parameter, used for debugging and testing. Not to be used. |
|
|
|
NX_CCD_EPSILON | 0.01 | Distance epsilon for CCD algorithm. |
NX_SOLVER_CONVERGENCE_THRESHOLD | 0 | Used to accelerate the solver. |
NX_BBOX_NOISE_LEVEL | 0.001 | Used to accelerate HW Broad Phase. |
NX_IMPLICIT_SWEEP_CACHE_SIZE | 5.0 | Used to set the sweep cache size. |
NX_DEFAULT_SLEEP_ENERGY | 0.005 | The default sleep energy threshold. Objects with an energy below this threshold are allowed to go to sleep. Note: Only used when the NX_BF_ENERGY_SLEEP_TEST flag is set. |
|
|
|
NX_CONSTANT_FLUID_MAX_PACKETS | 925 | Constant for the maximum number of packets per fluid. Used to compute the fluid packet buffer size in NxFluidPacketData. |
NX_CONSTANT_FLUID_MAX_PARTICLES_PER_STEP | 4096 | Constant for the maximum number of new fluid particles per frame. |
|
|
|
NX_VISUALIZE_FORCE_FIELDS | 0 | Force field visualization. |
|
|
|
NX_ASYNCHRONOUS_MESH_CREATION | 0 | [Experimental] Disables scene locks when creating/releasing meshes. |
NX_FORCE_FIELD_CUSTOM_KERNEL_EPSILON | 0.001 | Epsilon for custom force field kernels. |
NX_IMPROVED_SPRING_SOLVER | 1 | Enable/disable improved spring solver for joints and wheelshapes. |
NX_PARAMS_NUM_VALUES |
| This is not a parameter, it just records the current number of parameters. |
Parameter Ranges
- rigid body transform – 一个坐标向量+一个旋转矩阵
- position vector – 一个坐标向量,可能是本地坐标或者是世界坐标
- rotation matrix – 旋转矩阵
- unit quaternion – 4元数
- direction/extents vector - 偏移量/方向向量。
- force vector - Linear force vector. Forces are added to the momentum of a body; care should be taken to keep the momentum from growing too large.
- torque vector - Torque vector. A torque is added to the angular momentum of a body; care should be taken to keep the angular momentum within a reasonable range.
- velocity vector - Linear velocity vector. Consider the magnitude of the velocity when multiplied by the mass, i.e., a large velocity when applied to a body with a large mass will result in a very large momentum.
- angular velocity vector - Angular velocity vector. Consider the magnitude of the angular velocity when multiplied by the inertia tensor, i.e., the angular momentum.
- momentum vector - Linear momentum vector.
- angular momentum vector - Angular momentum vector.
Utility Functions
2.3以前可以直接调用工具函数,但是之后必须通过调用NxGetUtilLib()创建一个工具库指针,通过该指针进行调用。
Prior 2.3: NxSetFPUPrecision24();
After 2.3 NxUtilLib* gUtiLib = NxGetUtilLib(); gUtiLib-> NxSetFPUPrecision24();
Dynamics(动力学)
概念
一共有 种概念,分别是:
Scenes
所有的物理模拟都是在场景中进行的。一个场景包含actors, joints, effectors , 可以同时存在多个场景,每个场景各自进行模拟演算,各个场景之间的对象没有联系。不过你可以通过一些方法使场景之间可以进行通信。
场景没有空间上的范围,属于一个场景中的各种对象实际上构成了逻辑上的“组”。场景的任何特性都将作用于组内的各种对象和动作。
大多数的模拟都只需要在一个场景中进行。如果你在开发网络游戏,那么可能在Server 和Client各自存在一个场景,这些场景共同使用一个CPU(GPU?PPU?)。这个情况下,一个处理器就会创建多个场景:Client场景模拟用户周围的环境,而Server场景则模拟整个世界。
直接使用默认值,而不为场景描述符指定任何属性,就可以创建场景了。不过一般来说,场景总是需要一个重力加速度的,可以通过 NxScene::setGravity()来更改这一属性。
Simulation Timing
场景主要的特性是对物理模拟的实时演算。它将根据时间来计算各种变量的当前值。
模拟演算一针只会进行一次。通常我们会根据FPS来计算时间的流逝,以期望得到更加真实的模拟结果。
注意,时间片不能太长,否则模拟的稳定性就会下降,演算得出的结果也不再可靠。
一般使用以下方法来推进模拟:
Void simulate( NxReal elapsedTime);
通过调用该方法,可以通知引擎根据流逝的时间来进行从上次情况到当前情况的模拟演算。不过该方法的演算精确度还要取决于 setTiming()
Void setTinging ( NxReal maxTimestep = 1.0f / 60.0f,
NxU32 maxIter = 8,
NxTimeStepMethod method = NX_TIMESTEP_FIXED)
时间片变量当然也可以通过场景描述符来指定。默认值可以适用于大多数程序的需求。
“使用修正的时间”这个步骤非常重要,它保证了模拟的稳定性和可重复性,因此在很多程序里,都推荐使用它—通过设置NX_TIMESTEP_FIXED。
在引擎内部,引擎自身可以将流逝的时间划分为更小的时间片。
当调用simulate() 的时候传递了一个 比 maxTimestep还要的大的值,引擎就使用maxTimestep对时间进行划片。剩余部分将累计到下一次的模拟中。
maxIter 变量规定了划分时间片的最大次数。如果超过了划分次数时间仍然有剩余,剩余部分将累计到下一次的模拟中。
不过使用可变长度的时间片依然有效。通过指定 NX_TIMESTEP_VARIABLE,引擎将不会对时间进行分片,而是直接使用用户给出的时间长度作为一个片。
Recommended Time Stepping Method
推荐使用如下方法来划分时间片:
使用定长时间片划分策略,而流逝时间是一个常量,并且是maxTimestep的倍数。通过这种方法,用户可以清楚的知道自己把时间划分成了多少个片,
这样做的好处在于,任何物理上的行为,都是确定的,可以预测的。如果用户使用的是变长时间片模式来进行模拟,一切都将变得不可预测。不过使用变长时间片,用户可以得到多次调用 simulate的能力。比如在 moveGlobalPose() 和 addForce() 这2个函数中分别调用。
Asynchronous Stepping
PhysX 是多线程的。物理演算在独立的线程中进行。演算的状态更新需要按以下顺序进行函数调用:
1、 开始模拟
2、 确保所有必须的数据都已经发送到模拟线程
3、 检查模拟是否完成,如果完成了就把模拟结果写入缓冲区。
4、 交换状态数据指针(双缓存?),将新的结果应用于程序。
函数队列可能长的跟下面差不多:
NxScene* gScene;
NxReal myTimestep = 1.0f / 60.0f
…
Void mySimulationStepFuncion()
{
gScene->simulate( myTimestep);
gScene->flushStream();
// …在这里利用上一贞计算的结果做些事情
gScene->fetchResults( NX_RIGID_BODY_FINISHED, true);
}
这里有一些关于 fetchResults()函数的使用方法的其他版本。通过函数的变量来进行选择,或者通过调用checkResults()来检查是否完成都可以。上面那段函数将发生死锁,直到所有的刚体计算完成(NX_RIGID_BODY_FISHED == true)。使用不会发生死锁的执行流程,可以更加充分的利用资源。
void mySimulationStepFunction() { gScene->simulate(myTimestep); gScene->flushStream();
//...perform useful work here using previous frame's state data
while(!gScene->checkResults(NX_RIGID_BODY_FINISHED, false) { // do something useful }
gScene-> fetchResults(NX_RIGID_BODY_FINISHED, true);
}
或者
void mySimulationStepFunction() { gScene->simulate(myTimestep); gScene->flushStream();
//...perform useful work here using previous frame's state data
while(!gScene->fetchResults(NX_RIGID_BODY_FINISHED, false) { // do something useful }
}
变量 NX_RIGID_BODY_FINISHED 是NxSimulationStatus的一个标志量
注意:在2.1.2之前,对应的函数是 startRun() / finishRun().
注意:在调用 simulate() / fetchResults() 这对函数之前,动态切换双缓存这个事情对一些函数是不可见的,比如 overlap? 和 raycasting?.
Asynchronous Physics Programming
次世代主机正在朝着多处理器体系发展,因此也具有了更加强大的计算能力,这种提升将应用于渲染、动画、AI、物理、声音以及游戏的方方面面。这也是为物理引擎设计多线程模拟能力的基本动机。在多处理器的环境下,运行simulate()/fetchResults()函数块的线程可以从主线程中独立出去。所以就更快。
多线程物理API是为了“完美级”的物理表现效果而设计的,但是这也意味着次世代的开发者们面临着同步、负载平衡、系统瓶颈等等一系列新的问题。AGEIA针对次世代系统的前沿而设计,因此将发挥它们的最大潜力。
Solver Accuracy
1、 PhysX默认计算4级连接以内的效果。这个值最大为30。
2、 旋转太快会出问题,所以应该设置最大值。
actor->setMaxAngularVelocity(maxAV);
3、 应该保证参与物理计算的各项值(如质量、速度、力)都是比较合理的,这样得出的结果的精确度才能够保证。可以参考真实世界的大致比例。
4、 通过gPhysicsSDK->setParameter(NX_ADAPTIVE_FORCE, 0); 关闭各种作用力的影响。
技巧:
1、尽量使用 弹性连接杆 来代替 弹簧 和 泵阀受动器(?)。
2、使用弹簧的时候,保持弹性常量 ,并且将衰减设置的低一些。
3、不要乱设质点,尽量把质点设置到对象的碰撞体内。特别是对那些附载了弹簧的对象。
4、通过调整皮肤厚度,可以让对象更方便的堆砌在一起。
Actors
对象分为两种:静态对象、动态对象。
静态对象主要用来做碰撞检测。有形状属性,没刚体属性。
动态对象,形状属性不是必须的,但是刚体属性是必须的。
Shapes in Actors
静态对象必须要指定形状。
没有固定形状的对象 必须指定刚体属性,包括质点和惯性张力。
一个有固定形状的动态对象应该满足以下的某一条:
1、 有质量,0密度,没有惯性张力(后两者可以计算得到)
2、 0质量,非0密度,没有惯性张力
3、 有质量,0密度,有惯性张力。
不满足以上条件的对象将不会被创建。
Compound shapes
混合形状是自动构造的。
1、 为一个非混合形状的对象添加一个新形状,将产生一个混合形状。
2、 一个个的添加形状,比在构造对象的时候一次指定所有的形状要慢。
3、 应该避免静态对象有一个混合形状。混合形状有3角面限制,超过上限后,再添加的形状将直接忽略。
Reference Frames
对象的一个重要属性是它的pose(位置和朝向)。PhysX在这个问题上提供了一种灵活的解决方法,因此首先应该理解一些空间关系。
举个例子,现在我们要模拟一张桌子。这个桌子有一个Box桌面和4个Box腿。
Rigid Body Properties
动态对象有一系列关于刚体模拟的属性。
线性相关变量:
Mass
Position
Velocity
Force
旋转相关变量:
Inertia
Orientation
Angular velocity
Torque
The Inertia Tensor
开发人员一般都对运动学知之甚少,因此惯性张量可能是刚体属性中最特殊的地方。之前在“刚体属性”这一章节,我们也提到过,惯性张量只用来描述刚体质量分布,即使这个Actor没有Shape,也可以设置Inertia。不过就算Actor有Shape和质量,我们为了实现一些特别的效果,可能会直接修改惯性张量。(也就是说惯性张量的优先级高于质量和形状。不过暂时未确定该说法…)
(译者: 假设物体绕着某个点旋转,物体和点之间的距离为 r, 角速度为 w.
惯性张量 I = m * r*r;
向心力 F = m * r * w*w
旋转纽矩 t = F*r = m * r*r * w*w = I * r;
因此对于旋转来说,惯性张量描述了改变物体旋转状态的难易程度。当物体绕着自己的质心旋转时,就反映了自转的难易程度。)
因此惯性张量 也代表了对象 转向的难易程度。从生活中就可以感觉到:旋转一根棍子很简单,但是旋转一个链条则不那么顺畅,摆动它远比摆动一根棍子要难。
高校的物理课本里曾经告诉你如何计算一个特定形状的瞬间惯性张量。不过这些入门级的知识只讲了计算沿着特定轴的惯性张量。在SDK里,我们使用矩阵(NxMat34),它可以描述对象在任意方向上的惯性张量。可以使用向量或矩阵在任意坐标系(本地-世界)中描述向量。
在做内部计算的时候,矩阵会被分解为 一个旋转矩阵 和一个平移向量。这样可以提高计算速度。
当你创建一个刚体,你也许会想为这个刚体单独指定惯性张量。默认情况下的惯性张量都是一样的,这可能跟你设置的形状不相符。最简单的方法是将惯性张量的大小和Actor的形状进行关联,SDK可以通过密度或总质量来计算出这个形状的惯性张量。当然,更有效的办法是你自己计算惯性张量。SDK提供了大量的相关函数,可以参阅
NxExportedUtils.h
NxInertiaTensor.h
跟mesh相关的,以下两个函数也很有用:
NxConvexMesh::getMassInformation
NxTriangleMesh::getMassInformation
NxActor 的方法:
void setMassSpaceInertiaTensor(const NxVec3 &m); NxVec3 getMassSpaceInertiaTensor();
以上两个函数提供了对惯性张量的访问函数。
旋转的惯性张量可以通过以下函数来访问
void setCMassLocalOrientation (const NxMat33 &); NxMat33 getCMassLocalOrientation ();
以下方法:
void getGlobalInertiaTensor (NxMat33 &dest); NxMat33 getGlobalInertiaTensorInverse();
rotate this tensor using the current actor orientation into world space; as a result, the tensor changes every time the actor moves.不懂..囧
Manual Computation of Inertia Tensors
有时候,手动给Actor对象指定一个质心很有用,可以描述不均匀的质量分布。(比如不倒翁)。指定一个比普通质心低的质心将增强不倒翁效果。
默认情况下,创建一个Actor时SDK自动计算质心和惯性张量。不过用户也可以通过Actor的成员变量 massLocalPose, massSpaceInertia 来重新设置这2个值:
NxReal NxComputeSphereMass (NxReal radius, NxReal density);
NxReal NxComputeSphereDensity (NxReal radius, NxReal mass);
NxReal NxComputeBoxMass (const NxVec3& extents, NxReal density);
NxReal NxComputeEllipsoidMass (const NxVec3& extents, NxReal density);
...
void NxComputeBoxInertiaTensor (NxVec3& diagInertia, NxReal mass, NxReal xlength, NxReal ylength, NxReal zlength);
void NxComputeSphereInertiaTensor(NxVec3& diagInertia, NxReal mass, NxReal radius, bool hollow);
massLocalPose 是一个将惯性张量对角线化的变换矩阵,用于改变对象原有的Pose(Pose,对象在本地坐标系的原点,并且没有旋转的状态)。
译者:如果将惯性张量(有九个分量,其中六个是独立的)对角线化,那么会得到一组主轴,以及一个转动惯量(只有三个分量)
另外一个常用的函数:
bool NxDiagonalizeInertiaTensor(const NxMat33 & denseInertia, NxVec3 & diagonalInertia, NxMat33 & rotation);
该函数根据一个旋转矩阵和一个密度惯性张量(惯性张量的密度?不懂囧)求得一个对角线化的惯性张量。
Applying Forces and Torques
Setting the Velocity
Sleeping
Sleep Events
Active Transform Notification
Static Actors
静态对象跟动态对象不同的地方在于没有任何关于刚体属性的描述。要创建一个静态对象,只需要保持Actor描述符中的刚体字段为空即可。
一旦静态对象被创建,就不要对它进行任何操作。虽然移动位置、添加形状、或者删除静态对象等操作并没有明确被禁止,这里有2个理由说明为什么:
1、 SDK认为静态对象一直都是静态的(不会改变初始状态),因此很多优化策略都是基于这点来进行的。改变静态对象将导致SDK重新计算这些预先优化的数据结构。
2、 编写一些关于Actor或者joints 的控制的代码时,已经假设了静态对象在模拟时间片内是不会发生改变的,因为这会带来极大的性能优化空间。
举个例子:如果移动一个在一堆盒子下面的静态对象,虽然这些盒子都飞到了半空中,但是他们不会被激活,即使他们被激活了,在“移动的静态对象”和动态对象之间进行碰撞检测的结果质量也不会很高。
为了实现可移动的静态对象,应该使用 “运动学对象”。
Kinematic Actors
Adaptive Force
Character Controller
Character Controller – Creation
创建一个角色控制器,第一步要做的事情是决定边界体。就目前来说,PhysX只支持立方体(NxBoxController)和胶囊(NxCapsulesController)。
第二,创建一个控制器管理器(controller manager)。如:
NxControllerManager* gManager = NxCreateControllerManager(myAllocator);
注意:控制器管理器是单件(同一时间只存在一个实例)。
第三、创建一个角色控制器。如:
NxScene* scene;
NxCapsuleControllerDesc desc;
<填充控制器描述符>
NxController* cpController = gManager->createController(scene,desc);
当你不需要控制器管理器的时候,应该释放它。
NxReleaseControllerManager( gManager);
注意:以前的类名“ControllerManager”现在也有效,但是不建议使用,并且它将在下一个版本被取消掉。
Character Controller - Update
每一贞都可以调用以下函数来移动角色。
NxController::move(constNxVec3& disp,
NxU32 activeGroups,NxF32 minDist,NxU32& collisionFlags,NxF32 sharpness=1.0f,const NxGroupsMask* groupsMask=NULL);
Disp: 位移向量
activeGroups: 碰撞组掩码
minDist:最小距离,角色的位移低于这个值将不发生位移。
collisionFlags:碰撞检测类型掩码
sharpness:锐度?
groupsMask:?
Graphics Update
在每一贞里,可以通过角色控制器的position信息来保持外观模型的位置同步。一个控制器从来都不管旋转的事。因此也只能得到位置相关的信息。如:
const NxVec3& NxController::getPosition() const;
把这个信息传递给你的世界坐标矩阵,然后去渲染,这样你的角色位置就是经过碰撞检测修正过的,并且表现出更加平滑的移动。
Character Controller - Volume
角色使用的边界体只限于PhysX提供的标准几何体。
目前只支持2种不同的形状:
AABB:轴对齐的边界盒。由一个position和一个extents向量定义。这个盒子不会随着角色的旋转而旋转,始终是与世界的X、Y、Z 3个轴对齐的。
Capsule:胶囊。由一个position, 一个 height 和 一个 radius定义。(2个球+1个圆柱)。它可以获得更加平滑的移动,但是也会稍微多花些CPU时间。
注意:在版本2.3之前,有一个 球型控制器 NxSphereController,但是自从有了胶囊,它就被抛弃了。
注意:两种控制器都使用 Sweep API ,这就意味着并不是所有的可碰撞的形状都可以用于角色运动,只有那些可以被扫描的形状才能用于角色运动的判定。
角色碰撞体周围有一层被定义为“皮肤”,它被用来决定是否与其他形状有接触。皮肤的厚度由用户定义。如果为了Debug而显示角色碰撞体,别忘了还有个皮肤的概念。
AABB相关变量:
NxControllerDesc::position
NxControllerDesc::skinWidth
NxBoxControllerDesc::extents
Capsule相关变量
NxControllerDesc::position
NxControllerDesc::skinWidth
NxCapsuleControllerDesc::radius
NxCapsuleControllerDesc::height
关于AABB的一些解释:
皮肤:外表面
Extents:内表面
Character Controller - Auto-Stepping
如果没有自动上台阶(跨越障碍)的功能,地面上一个非常小的台阶都会阻挡角色的移动。显然这不自然,因为如果台阶不是很高的话,我们可以轻松的走上去才对。但是台阶太高了的话,就不应该走上去。这个界定值由以下变量给出:
NxControllerDesc::stepOffset
为了实现自动上台阶,PhysX需要知道“上”的方向。当前PhysX只支持3个轴向向量:
NX_X ( 1, 0, 0 )
NX_Y ( 0, 1, 0 )
NX_Z ( 0, 0, 1 )
相关的变量为:
NxControllerDesc::upDirection
Character Controller - Walkable Parts
默认情况下,角色可以走到任何地方。不过在现实世界总是有限制存在。因此,“不允许在角度很大的多边形上走”的限制就有存在的必要。只要用户给出角度界定,PhysX就可以自动的把多边形分为“可以在上面行走的”和“不可以在上面行走的”。将来可能把检测级别推到三角面级别的精确度。
可以通过以下变量来设置界定值:
NxControllerDesc::slopeLimit
它是角度的cos。下面的代码规定了只有小于45度的多边形上才可以行走:
slopeLimit = cosf( NxMath::degToRad(45.0F));
如果值设置为0.0f,该特性就自动关闭(那样的话,角色哪都能走了)
这个特性并不总是需要的。一个更加普遍的做法是关掉这个特性,然后使用看不见的墙来限制角色的运动范围。
Character Controller - Volume Update
有时候实时的改变角色碰撞体的大小非常有用。比如如果你的角色可以蹲下,那么如果可以把碰撞体的高度减少的话,就可以移动到之前不能去的地方了
角色控制库支持动态更新碰撞体。但是如果不经过检测就直接改变碰撞体大小,很可能会使碰撞体在设置完之后与其他几何体相交或重叠。为了避免这种情况,首先应该调用PhysX为此准备的一些API来进行测试,保证碰撞体有足够的空间来更改大小,只有通过这些API检测之后,才对碰撞体进行更新。
跟AABB相关的函数:
bool NxBoxController::setExtents( const NxVec3& extents) = 0;
跟胶囊相关的函数:
bool NxCapsuleController::setRadius(NxF32 radius) = 0;
bool NxCapsuleController::setHeight(NxF32 radius) = 0;
相关的检测函数:
bool NxScene::checkOverlapSphere();
bool NxScene::checkOverlapAABB();
bool NxScene::checkOverlapCapsule();
Character Controller - Callbacks
当控制器发生一些事件的时候,可以设置回调函数。(比如当一个角色撞到了一个形状,或者另外一个角色)
当一个角色碰撞了另外一个形状,可以得到以下信息:
1、 当前控制器
2、 被碰撞的形状
3、 碰撞点
4、 碰撞点的法向量
5、 一个特征码(比如三角形序号)
6、 额外的运动数据。
这些信息可以应用于游戏的其他方面,比如播放音效、渲染细节、应用作用力或者别的什么。
注意:现在还不是所有的信息都可以得到,这取决于角色撞上了什么样的形状。最终会支持所有的形状。
当一个角色碰撞了另外一个角色,比如一个角色撞到了一个NPC,可以得到以下信息:
1、一对控制器
Character Interactions
在游戏中,根据回调函数中返回的不同的动作编号,会产生不用的行为。例如PhysX要是觉得角色足够强壮,那他就可以推动某些箱子。
现在支持的动作编号如下:
NX_ACTION_NONE => 不允许物理(角色推不动对象)
NX_ACTION_PUSH => 允许物理(角色推的动对象)
如果给接触点一个作用力,物理引擎就会模拟出自然的推动过程,听起来是非常Cool的。不过这个特性对游戏来说可能会起反作用。边界体的形状是人为控制的,但是你不希望因为从Box控制器切换到Capsule控制器而导致了推动的效果发生变化。推动的效果应该游戏说了算。因此通常比较好的办法是在回调函数中给接触点一个作用力,而不是完全交给物理引擎去计算。另外如果是个胶囊的话,也很难推动一个盒子,因为你几乎不可能对着盒子的中间,推动盒子就会使盒子旋转,虽然你可能只是想把盒子沿直线推一点点。
注意:NX_ACTION_PUSH 当前还没有实现,例子中的效果是使用碰撞时提供的信息来实现的。
Character Controller - Hidden Kinematic Actors
角色控制库 为每个角色创建了一个隐藏的运动学Actor。有时候这就是引起Actors数量混乱的原因。因此可能得到一个错误的Actor数量,或者在查询Actor的时候,返回了一个未知的Actor。
当角色控制器处理角色移动的时候,也会同步这个Actor。
可以通过以下函数得到这个Actor:
NxActor* NxController::getActor() const;
不过,应该避免直接操作这个Actor,这会导致问题。不过只是读取信息则没有问题。
Character Controller - Time Stepping
NxCharacter 库里对Actor的操作跟普通的Actor没什么区别。精确的讲,他们使用相同的时间片策略(定长/变长)。当使用定长时间片策略时,会造成一些麻烦。因为NxController总是使用变长时间片策略(大多数情况下,是每贞的间隔)。因此在这种情况下NxController对象并不会跟Actor对象总是精确的同步。