Havok Physics 2012
Chapter 2. Creating a Simulation 创建一个模拟世界
这一章将带您通过整个过程得到一个模拟世界的启动和运行。具体来说,我们将探索如何创建物理对象和约束,然后如何用这些对象填充仿真世界,最后如何更新仿真世界。
1. Creating Physics 2012 Objects
在本节中,我们将解释所有可用的物理对象和约束,以及如何创建它们。我们将首先探索Havok Physics 2012支持的碰撞形状,然后是用于模拟这些形状的刚体。最后,我们将研究Havok Physics 2012中可用的各种约束条件。
1.1. Shapes
形状定义了刚体的碰撞面,从而定义了刚体如何与其他刚体碰撞并响应碰撞查询。在Havok Physics 2012中,有许多类型的形状可用,从简单的隐式球体或盒子,到从建模器导出的三角形网格,甚至是使用形状列表和转换后的形状的分层复合结构。形状可以在刚体之间甚至刚体内部共享,通常在工具管道中创建和序列化,然后在运行时由应用程序加载。
所有形状信息均由hkpShape类及其子类提供。hkpShape提供的重要虚拟功能包括:
-
getType()
: 从hkcdShapeType enum返回形状的类型。例如,hkpCollisionDispatcher使用该方法来选择最合适的冲突检测算法(agent)来处理每一对可能发生冲突的对象。例如,在解析来自用户冲突查询的结果时,还应该使用它来标识并转换为适当的形状类型。形状还可以有一个或多个“备用”分派类型。例如,虽然hkpBoxShape有一种类型的hkcdShapeType::BOX,但是hkpBoxShape和hkpSphereShape都有另一种类型的hkcdShapeType::CONVEX(凸起),因为它们继承自公共的hkpConvexShape父类。这些替代类型由hkpAgentRegisterUtil在dispatcher中动态注册。
-
getAabb()
: 返回形状的轴向对齐的包围盒。例如,hkpWorld使用此函数更新每个对象在粗测阶段的AABB,从而确定在任何时候哪些形状是重叠的。 -
getMaximumProjection()
: 返回给定方向上形状表面上的“最远”点,或其近似值。这用于帮助创建形状的包围盒。 -
castRay()
:对形状执行光线强制转换,返回最近的交点,以及关于分层形状的子形状被击中的附加信息(如果相关)。有关形状查询的更多信息可以在异步查询部分获得。
形状通常可以分为几个类别,本章将对此进行探讨。
- Convex shapes(凸形状): 这些表示简单的凸基本形状,它们是任何层次结构中的叶子形状,用于细测阶段碰撞检测算法。
- Container shapes: 这些表示其他形状的集合,允许创建复合/凹形。这些形状通过hkpShape::getContainer()方法实现hkpShapeContainer接口。hkpShapeContainer中的每个子形状都使用hkpshapeg ——一个32位的值来标识,每种容器类型都可以用自己的方式解释这个值。容器的**getChildShape()**方法可以使用形状键从容器中检索子形状。
- Bounding Volume shapes(包围盒形状): 这些容器可用于包装其他形状容器,通常用于加速查询到这些形状。
- Height Field shapes(高度场的形状): 这些提供了一个替代的碰撞管道适合表示地形
1.1.1. Convex Shapes 凸形状
hkpConvexShape是本节描述的所有原始凸形状的父类。所有hkpConvexShapes都有hkcdShapeType::Convex作为另一种分派类型。
所有的凸形状都可以被认为至少有一个顶点加上一个“凸半径”,在这些顶点周围增加一个额外的壳层。需要注意的是,较大的凸半径有助于最小化碰撞检测器需要做的工作,但是较大的值也会导致更多的圆角。有关凸形半径的进一步信息可在优化和调谐部分获得。
1.1.1.1. Sphere Shape
hkpSphereShape是以形状的局部原点为中心的球体,具有指定的半径。它本质上是一个单顶点和一个大的凸半径,因此对于碰撞检测是非常有效的。
hkpSphereShape* sphereShape = new hkpSphereShape( 2.0f ); // creates a sphere shape with a radius of 2
要创建中心不是原点的球体,应该使其成为hkpConvexTranslateShape的子元素。有关更多信息,请参见下面的凸变换形状部分。
1.1.1.2. Box Shape
hkpBoxShape是一个以形状的本地原点为中心的简单框。要创建hkpBoxShape,必须指定它的半区段,如下面的示例所示。一个X×Y×Z框有半区段(X/2, Y/2, Z/2)。
hkVector4 halfExtent( 1.0f, 2.0f, 3.0f );
hkpBoxShape* boxShape = new hkpBoxShape( halfExtent, 0.0f ); // creates a 2x4x6 box shape, with zero "convex radius"
要创建中心不是原点的框,应该使其成为hkpConvexTranslateShape的子元素。有关更多信息,请参见下面的凸变换形状部分。
1.1.1.3. Capsule Shape
hkpCapsuleShape是由两个顶点和一个半径定义的胶囊。这两个顶点在创建形状时指定在形状的本地空间中。指定半径作为凸形半径;因此,胶囊是非常有效的碰撞检测
hkVector4 start( 0.0f, 0.0f, 0.0f );
hkVector4 end ( 0.0f, 2.0f, 0.0f );
float radius = 0.5f;
hkpCapsuleShape* capsule = new hkpCapsuleShape( start, end, radius ); // creates a capsule with a axis between "start" and "end", and the specified "radius"
1.1.1.4. Cylinder Shape
hkpCylinderShape 是由两点和非零圆柱半径定义的圆柱。这些参数是在创建形状时在形状的本地空间中指定的。注意圆柱半径与“凸半径”是分开的。
hkVector4 start( 0.0f, 0.0f, 0.0f );
hkVector4 end ( 0.0f, 2.0f, 0.0f );
float radius = 0.5f;
float optionalConvexShapeRadius = 0.1f;
// creates a capsule with a axis between "start" and "end", the specified "radius", and an optional additional "shell" radius
hkpCylinderShape* cylinder = new hkpCylinderShape( start, end, radius /*, optionalConvexShapeRadius */ );
1.1.1.5. Triangle Shape
hkpTriangleShape是一个简单的三角形,存储在形状中为三个hkVector4顶点。要创建一个hkpTriangleShape,请在形状的局部空间中指定三角形的三个顶点:
hkVector4 v0( 0.0f, 0.0f, 0.0f );
hkVector4 v1( 0.0f, 0.0f, 5.0f );
hkVector4 v2( 5.0f, 0.0f, 0.0f );
hkpTriangleShape* triangleShape = new hkpTriangleShape( v0, v1, v2 ); // creates a triangle shape using these 3 vertices
可以通过不向构造函数传递参数来创建“空”hkpTriangleShape。稍后可以指定三角形细节。下面的例子使用了这种方法:
hkpTriangleShape* shape = new hkpTriangleShape();
float vertices[] = {
-0.5f, -0.5f, 0.0f, 0.0f, // v0
0.5f, -0.5f, 0.0f, 0.0f, // v1
0.0f, 0.5f, 0.0f, 0.0f, // v2
};
int index = 0;
int stride = 4;
for ( int i = 0; i < 3; i++ )
{
hkVector4 v( vertices[index], vertices[index + 1], vertices[index + 2] );
shape->setVertex( i, v );
index = index + stride;
}
注意,三角形形状通常不是由用户直接创建的,而是由高级容器形状动态创建的。
三角形也有一个“挤压”参数。这是一个hkVector4,它定义了每个三角形的挤压方向和长度。默认情况下,它被设置为零长度向量。当挤压不为零时,碰撞检测器隐式地将三角形转换为由向量挤压的三角形表示的凸对象。挤压可以帮助避免“子弹通过纸张”的问题,在某些情况下,没有诉诸于连续模拟。然而,它并不能保证健壮的结果。
1.1.1.6. Convex Vertices Shape
hkpConvexVerticesShape是由任意数量的顶点表示的一般凸形。顶点是在形状的局部空间中指定的。注意,构造函数中给出的顶点不是共享的,而是复制到形状中,并以优化的换位格式存储。构造函数中给出的顶点云不需要是凸的;它将生成一个合适的凸包。更多信息请参考凸包生成文档。
int numVertices = 4;
// 4 vertices plus padding
float vertices[] =
{
-2.0f, 2.0f, 1.0f, 0.0f, // v0
1.0f, 3.0f, 0.0f, 0.0f, // v1
0.0f, 1.0f, 3.0f, 0.0f, // v2
1.0f, 0.0f, 0.0f, 0.0f // v3
};
// This structure provides the build options
hkpConvexVerticeShape::BuildConfig config;
config.m_convexRadius = 0.1f;
//从这四个顶点和凸半径创建一个凸顶点形状。
// 默认情况下,形状将在构造过程中收缩,以便生成的“shell”与输入顶点匹配。
hkpConvexVerticesShape* shape = new hkpConvexVerticesShape(hkStridedVertices(vertices, numVertices), config);
上面的hkpConvexVerticeShape::BuildConfig结构包含几个选项,控制形状应该如何构造。例如,默认值是“缩小”顶点,使生成的shell(包括凸半径)匹配输入顶点,但如果需要,半径也可以添加到输入顶点。有关详细信息,请参阅hkpConvexVerticeShape::BuildConfig头注释。
1.1.1.7. Convex Transform/Translate Shape
hkpConvexTransformShape和hkpConvexTranslateShape是一种特殊的凸形状,它通过附加的变换将其他凸形状包裹起来。这种转换允许形状从它们的原点偏移(这在球体和盒子形状的情况下特别有用),还允许层次结构中的凸形状实例使用不同的局部转换。凸变换形状有两种:
-
hkpConvexTransformShape允许平移、旋转和缩放(如果可能的话)。这是最常见的凸变换。大多数凸形状都支持非均匀缩放,但由于凸半径值不能非均匀缩放,因此不可能对球面形状进行非均匀缩放。
-
hkpConvexTranslateShape只允许翻译,但是代码更少,它比hkpConvexTransformShape稍微快一些,并且在只需要翻译的情况下更受欢迎。
这些变换形状是凸形状,它们有自己的凸半径值,不同于子形状的凸半径值。通常,凸半径值是在构造过程中从子形状复制的。但是,如果在变换中涉及缩放,也可以对凸半径进行缩放,以保持适当缩放的凸“壳层”。
要创建凸转换形状,只需在构造期间指定子形状和转换:
// 创建子形状
hkpBoxShape* boxShape = [...];
// 创建子形状的翻译版本
{
hkpVector4 translation( 2.0f, 0.0f, 1.0f );
hkpConvexTranslateShape* translatedBoxShape = new hkpConvexTranslateShape( boxShape, translation );
}
// 创建子形状的完全转换版本
{
hkVector4 translation( -0.9f, 0.7f, 0.0f );
hkQuaternion rotation( hkVector4( 0.0f, 0.0f, 1.0f ), -0.4f ); // a rotation about the Z axis
hkVector4 scale( 2.0f, 1.0f, 1.0f );
hkQsTransform transform( translation, rotation, scale );
hkpConvexTransformShape* transformedBoxShape = new hkpConvexTransformShape( boxShape, transform );
}
注意:
这些形状的设计目的不是让它们的转换在运行时动态更改。如果发生这种情况,碰撞检测器将创建新的接触点,具有合适的位置和法线,但不会考虑凸表面有任何速度。所以碰撞反应并不完全正确。此外,如果形状AABB发生变化,则应该更新broad phase以反映任何新的或已删除的重叠(例如,使用hkpRigidBody::setShape()将形状重新插入到broad phase中)。
1.1.2. Mesh Shapes 网格
在Havok Physics 2012中,支持扁平但可能很大的凸原始形状列表的容器形状通常称为“网格”形状。这些设计是为了有效地存储大量的凸子形状,例如代表整个游戏级别的静态景观。由于网格形状不存储容器形状,所以它们不能返回子形状层次结构。但是,它们可以用作更通用的容器形状层次结构的一部分。这通常是一个好主意,因为它允许在一个层次结构f中多次实例化详细的网格。
1.1.2.1. BV Compressed Mesh Shape BV压缩网格形状
hkpBvCompressedMeshShape是一种内存和性能优化的容器形状,用于存储大量静态三角形和/或其他原始凸形状。原语以压缩格式存储,以最小化内存占用,并使用内部静态包围盒树进行包装,以优化空间查询性能。压缩和查询优化的组合使其成为表示详细静态景观时的理想形状选择。
由于此形状以压缩格式在内部存储其所有原始几何图形,因此不能使用它引用外部几何图形。此外,在构造形状之后,不能修改形状的原语和其他属性。但是,可以通过将形状包装为hkpStaticCompoundShape来禁用(并启用)原语。
请参考“demo /Physics2012/Api/ collision /Shapes/BvCompressedMesh”演示,以获得关于该形状构造和使用的详细示例。
注意:
名称中的“BV”指的是管理自己的包围盒的形状。这是为了将它与废弃的hkpCompressedMeshShape区分开来,后者需要一个单独的包围体。
1.1.2.1.1. Construction
通过首先推导hkBvCompressedMeshShapeCinfo结构来描述源几何形状,构造了hkpbvcompressedmeshshapecinfo结构。这个结构提供了几个必须实现的抽象方法来描述:
-
三角形和三角形顶点的数量,以及每个三角形的顶点索引。
-
其他凸原语形状的数目和指向这些形状的指针。
-
每个原语(三角形或凸形)的碰撞过滤信息。
-
每个原语(三角形或凸形)的用户数据值。
该结构还提供了几个控制:
-
所有子形状的默认凸半径。
-
压缩过程的一些公差值。
-
是否应计算三角形的仪表焊接信息。
-
是否应该从输入基元索引构建映射到其结果形状键。
提供了一个默认的hkpDefaultBvCompressedMeshShapeCinfo实现,它使用一个hkGeometry来提供三角形,以及一个用于添加其他凸形状的简单接口。
然后,通过将构造信息结构传递给shape构造函数来创建hkpBvCompressedMeshShape。然后可以处理构造信息和源几何图形,因为形状将所有几何图形内部复制到其压缩格式中。
hkGeometry geometry = [...];
hkpDefaultBvCompressedMeshShapeCinfo cInfo( geometry ); // using the default construction info implementation here
hkpBvCompressedMeshShape* meshShape = new hkpBvCompressedMeshShape( cInfo );
请注意,施工过程是昂贵的,因为它涉及压缩网格,计算包围盒体积信息,并可能计算焊接信息。因此,在大多数情况下,强烈建议离线在工具进程中构造此形状并序列化结果。
1.1.2.1.2. Per-primitive data 基元数据
BV压缩网格形状中的每个基元(三角形或凸形)都有32位元可用来存储每个基元数据。该数据编码如下:
-
下8位:冲突过滤器索引。如果m_collisionFilterInfoMode构造选项设置为PER_PRIMITIVE_DATA_8_BIT,则将此值用作实际的冲突过滤器信息;如果设置了PER_PRIMITIVE_DATA_PALETTE,则将此值用作冲突过滤器信息调色板的索引。
-
接下来的8位:用户数据索引。与冲突筛选器索引类似,这个值可以表示实际的用户数据值,也可以表示用户数据值调色板中的索引,具体取决于m_userDataMode构造选项的值。
-
上16位:仪表焊接信息。如果m_weldingType设置为与WELDING_TYPE_NONE不同的值,则在构造期间计算此值。请记住,计算焊接信息可能是一个昂贵的步骤,这将大大增加施工时间。
如上所述,根据m_collisionFilterInfoMode和m_userDataMode构造选项的值,冲突筛选器信息和用户数据可以直接存储,也可以存储在调色板中。
如果值是在[0,255],他们可以直接存储在per-primitive数据,通过指定PER_PRIMITIVE_DATA_8_BIT建设选项,这将避免在调色板和分配额外的空间,对于碰撞过滤信息,避免额外的程序从SPU访问最后一个值。
如果值超过255,指定PER_PRIMITIVE_DATA_PALETTE构造选项将构建一个包含所有不同值的选项板,每个原始用户数据或冲突过滤器信息将在选项板中存储实际值的索引。请注意,即使使用调色板存储大于255的值,由于索引值的限制,仍然有256个惟一值的限制。
另外,对于用户数据,在构造时指定PER_PRIMITIVE_DATA_STRING_PALETTE选项将允许在选项板中存储用户提供的字符串,而不是整数。这是内容工具过滤器用来存储材料名称;有关更多信息,请参阅Havok内容工具文档。
每个基元数据与几何数据在内部分离,并且是运行长度编码的,以便具有统一值的相邻基元能够有效地存储。例如,如果整个网格中根本不需要每个基元数据,那么所有的每个基元数据都将为零,因此在内部这将很好地压缩到几乎为零。由于这种压缩方案,每个原始数据在构造之后不能更改。但是,当使用调色板来处理冲突筛选信息或用户数据时,可以使用ac修改与具有相同索引的所有原语关联的值
1.1.2.1.3. Limitations and known issues 限制和已知问题
-
动态物体不应采用BV压缩网格形状。任何使用它的动态物体目前都必须PPU在PlayStation®3上进行碰撞检测。请使用列表形状代替动态主体。
-
冲突筛选器信息和用户数据被限制为256个惟一值,即使在使用调色板时也是如此。
-
单个BV压缩网格形状中的原语数量限制为223个。
1.1.2.2. Extended Mesh Shape
hkpExtendedMeshShape是一个形状容器,它引用任意三角形网格和/或其他凸形状的列表。三角形和凸形是由扩展网格形状引用的,而不是存储的,这使得它成为一个灵活的网格形状,适用于您希望重用已经存在的几何形状的情况。例如,为了创建一个匹配的物理几何图形,这个形状可以引用一个现有的渲染几何图形(尽管一般不推荐,因为渲染几何可能非常详细)。
这个形状处理三角形列表和三角形带,以及优雅地处理退化三角形。注意,它不直接处理三角形扇,因为顶点之间的跨行不是常量(第n个三角形的跨行是vertex(0) + vertex(n+1) + vertex(n+2) )。组成hkpExtendedMeshShape的三角形组称为它的“子部件”。每个三角形的子部分都有一个hkQsTransform成员,它允许您对该子部分返回的顶点数据应用平移、旋转和缩放。这是使用形状层次结构的一个很好的替代方法。
这个形状不保留它自己的任何跳跃体积结构,所以应该总是在一个单独的包围盒形状(通常是MOPP形状)包裹。
1.1.2.2.1. Storage Details
每个三角形的子部分结构包含以下信息:
-
(顶点信息)指向顶点数据的指针。
-
(顶点信息)内存中连续顶点之间的字节偏移量,也称为顶点跨行。
-
(顶点信息)子部件中的顶点数。
-
(三角形信息)指向三角形索引数据的指针。
-
(三角形信息)是否使用8、16或32位来索引三角形。
-
(三角信息)三角索引中索引三个一组之间的偏移量,也称为索引跨行。
-
(三角形信息)由子部件组成的三角形数量。
三角形索引(或索引缓冲区)是一个数组,它指示哪个顶点三联体构成三角形。顶点索引支持顶点共享,消除了存储相同数据的多个实例的需要。它还方便使用三角形带,在那里相邻的三角形索引集重叠。三角形带可以被认为是一个窗口3指标进展的一个索引的索引数组每个三角形,在这种情况下,指数大步等于单个索引元素的大小,即2字节或4字节(取决于是否使用16位或32位的指标)。
例如,如果将16位索引存储在一个大的无符号短数组中,一个接一个地存储为三角形带,那么索引的跨距是(1sizeof(无符号短数组)),即2。如果您将索引存储为32位,并表示它们表示一个三角形列表(而不是条),则striding是(3sizeof(int)),即12。
1.1.2.2.2. Per Triangle Material Tagging 每个三角形材质标签
每个三角形和子部件都可以包含特定的材料信息。形状键用于索引网格子部件和三角形上的材料信息。
每个子部件都有成员m_materialIndexBase、m_materialIndexStridingType和m_materialIndexStriding,它们将8位或16位整数索引与每个三角形或整个子部件关联起来(通过将striding设置为零)。注意,对于交叉索引,跨行不需要匹配跨行类型。
//简单八位索引
hkInt8* indices8 = ...;
part.m_materialIndexBase = indices8;
part.m_materialIndexStridingType = hkpExtendedMeshShape::MATERIAL_INDICES_INT8;
part.m_materialIndexStriding = sizeof(hkInt8);
//交错的8位索引
struct Interleaved { hkInt8 materialIndex; hkInt8 morePackedData; } * interleaved;
part.m_materialIndexBase = interleaved;
part.m_materialIndexStridingType = hkpExtendedMeshShape::MATERIAL_INDICES_INT8;
part.m_materialIndexStriding = sizeof(Interleaved);
每个子部件还具有m_materialBase、m_materialStriding和m_numMaterials成员,这些成员引用材料数据。同样,材料阵列也可以交错排列。注意,使用m_materialBase意味着启用冲突过滤,因为基类hkpMeshMaterial包含一个过滤值。
//外部存储的材料——我们将materialIndex使用到外部数组中
part.m_materialBase = HK_NULL;
//隔行扫描的材料
struct MyMaterial : public hkpMeshMaterial
{
// 继承了用于三角形冲突过滤的hkUint32 m_filterInfo
int m_extraData;
};
MyMaterial* interleavedMaterials;
part.m_materialBase = interleavedMaterials;
part.m_materialStriding = sizeof(MyMaterial);
part.m_numMaterials = ...;
1.1.3. Compound Shapes 化合物的形状
网格形状的设计目的是有效地表示凸形状的平面列表,而其他更通用的容器形状可以将容器形状作为子形状,从而允许创建复合多层次的形状层次结构。只要层次结构不是递归的,并且不包含循环,那么它的复杂性就没有理论上的限制。当然,大型层次结构可能会导致性能损失。实际上,形状层次结构最多应该限制在2个级别(例如,转换形状容器的形状容器)。这特别适用于PlayStation®3,因为SPU堆栈可能会由于额外的内存成本而溢出。
1.1.3.1. Transform Shape
在创建复合形状层次结构时,hkpTransformShape允许子形状相对于父形状的定位。这类似于hkpConvexTransformShape,不同之处在于hkpTransformShape支持任何类型的子形状(而不仅仅是凸型),例如,允许转换容器的容器。
但是,这个形状要求为每个冲突创建一个额外的运行时冲突代理,因此会带来相关的内存和性能成本。因此,当子形状为凸时,应该始终使用hkpConvexTransformShape。在优化碰撞检测部分,将更详细地探讨使用变换形状的性能影响。
1.1.3.2. List Shape
hkpListShape是任何类型的hkpShapes的简单列表。它是为相对较少的儿童设计的(也就是说,不是为景观形状设计的),它本身并不维护任何包围盒体块信息。因此,如果列表有大量子列表,建议将列表包装成包围盒形状,以便加速任何查询。
下面的例子说明了如何使用hkpListShape为一个动态体构建一个简单的复合形状,在这种情况下,表格由几个不同的盒子形状组成:
创建列表形状的第一步是创建将成为子元素的hkpShapes:
hkVector4 halfExtent1( 2.0f, 1.0f, 0.1f );
hkVector4 halfExtent2( 0.1f, 0.1f, 1.0f );
hkpBoxShape* box1 = new hkpBoxShape( halfExtent1 );
hkpBoxShape* box2 = new hkpBoxShape( halfExtent2 );
然后,由于hkpListShape构造函数需要一个指向形状数组的指针,我们还创建了一个数组来存储子形状:
hkArray<hkpShape*> shapeArray;
在将子形状添加到数组之前,我们需要重新定位它们,否则将在相同的位置创建所有的框。为此,我们为每个桌腿创建一个hkpConvexTranslateShape;下面的代码片段显示了其中之一的创建。这些新的变换形状在创建时被添加到数组中:
// 按原样将桌顶形状添加到数组中
shapeArray.pushBack( box1 );
// 对于每个桌腿,将另一个形状包装成一个convex translate shape 并将其存储在数组中
hkVector4 offset( -1.0f, -0.5f, -0.5f );
hkpConvexTranslateShape* translateShape = new hkpConvexTranslateShape( box2, offset );
shapeArray.pushBack( translateShape );
最后,使用形状数组创建hkpListShape:
hkpListShape* listShape = new hkpListShape( &shapeArray.begin(), shapeArray.getSize() );
hkpListShape允许动态禁用和重新启用子形状,但是可以禁用的子形状数量上限为128个。
1.1.3.3. Static Compound Shape 静态复合形状
hkpStaticCompoundShape是一个形状容器,用于由其他形状的重复实例组成的景观、建筑物或其他大型静态结构。例如,下面的景观是由三个基本形状的多个转换实例组成的静态复合形状:树资产**(hkpBvCompressedMeshShape**)、圆石(hkpConvexVerticesShape)和地形表面(hkpBvCompressedMeshShape)。
(上面的演示可以在“Demos/Physics2012/Api/ collision /Shapes/StaticCompound/Construction”下找到。)
与列表形状相比,静态复合形状提供:
-
减少嵌套形状容器的运行时内存开销(如下所述)。
-
减少每个实例的静态内存开销(64字节)。
-
支持每个实例的非均匀缩放(以及平移和旋转)。
-
能够禁用任何实例中的任何实例或形状键,而不修改子形状。
-
每个实例的可变用户数据和冲突过滤器信息(带屏蔽)。
-
围绕实例aabb的内部包围盒树,针对此用例进行了优化。
静态复合形状通过将自己呈现为凸形状的单一平面列表(在大多数用例中)来减少嵌套容器的运行时内存开销,同时仍然允许用户将其视为正常的形状层次结构。通过将自己呈现为一个平面列表,它避免了与嵌套冲突代理相关的运行时性能和内存开销,否则将创建嵌套冲突代理。它通过在形状键中编码几个数据片段,并在必要时将叶子形状包装成凸变换形状来实现这一点。下图演示了这个概念
静态复合形状中的子形状的每个实例都有自己的空间转换。这种转换表示为hkQsTransform,支持平移、旋转和非均匀缩放。如果一个实例包含一个形状容器,那么当静态复合形状返回时,它的所有子形状都将受到实例中指定的转换的影响。任何叶子形状的最终转换都是在从叶子到根遍历实例树时遇到的所有实例转换的组合。
但是,出于性能考虑,hkQsTransform不支持涉及倾斜的转换。这意味着,通常,非均匀缩放只能安全地用于不对其子形状应用任何旋转的形状实例,否则在最终的转换中很可能出现倾斜。然而,在实践中,这个倾斜因子将被截断,结果的转换将是错误的或意外的,因此在实例转换中指定非均匀尺度时必须小心。
1.1.3.3.1. Construction
将hkpStaticCompoundShape构造为空形状,然后(外部)子形状作为实例添加,每个实例都有自己的转换。每个实例的形状和转换在添加之后都不能修改。当添加了所有实例后,必须对形状进行烘焙,以完成其缓存并构建内部包围盒。注意,静态复合形状在构造之后维护对外部子形状的引用。在将这些子形状添加到静态复合形状之后,不应该以任何方式修改它们,因为静态复合形状根据子形状配置维护缓存和包围体。
下面的代码展示了一个基本示例,该示例使用两个具有不同转换的相同hkpBvCompressedMeshShape实例创建一个hkpStaticCompoundShape。在最后一次调用bake()之后,不可能再调用addInstance(),子形状不应该被修改。
// 创建子形状
hkpBvCompressedMeshShape* meshShape = [...];
// 创建一些转换以实例化子元素(包括非均匀比例)
hkQsTransform transform1 = [...];
hkQsTransform transform2 = [...];
// 创建hkpStaticCompoundShape并添加实例。
// "meshShape" should not be modified by the user in any way after adding it as an instance.
hkpStaticCompoundShape* staticCompoundShape = new hkpStaticCompoundShape();
int id1 = staticCompoundShape->addInstance( meshShape, transform1 );
int id2 = staticCompoundShape->addInstance( meshShape, transform2 );
// 必须在添加实例之后和使用形状之前调用此函数。
staticCompoundShape->bake();
1.1.3.3.2. Collision Filter Info Masking 碰撞滤波器信息掩蔽
冲突过滤器信息和冲突过滤器“掩码”都可以在静态复合形状中为每个实例指定。这些都可以在任何时候改变,无论是之前和之后的形状拷贝。掩码应用于实例的子形状可能提供的任何冲突筛选器信息。在上面的森林示例中,每棵树都是hkpBvCompressedMeshShape。这个形状可以包含每个三角形的碰撞过滤器信息。我们为每个树指定的掩码将应用于这个子冲突筛选器信息。然后,它的结果与实例本身提供的过滤器信息相结合。其公式为:
filterInfo = ( childFilterInfo & instanceFilterMask ) | instanceFilterInfo
这种结构允许以最小的开销实现灵活的冲突过滤。下面是设置过滤器信息和过滤器掩码的示例。
// 我们希望实例1的所有子元素的筛选信息都完全由它的子元素来证明
staticCompoundShape->setInstanceFilterInfoMask( instanceId1, 0xffffffff );
staticCompoundShape->setInstanceFilterInfo( instanceId1, 0x0 );
// 我们希望实例2的所有子形状的过滤器信息都是组过滤器层2,不管它的子形状如何
staticCompoundShape->setInstanceFilterInfoMask( instanceId2, 0x0 );
staticCompoundShape->setInstanceFilterInfo( instanceId2, hkGroupFilter::calcFilterInfo(2) );
下面的图给出了一个示例,演示了当子形状是容器时如何计算筛选器信息。实例过滤器掩码和过滤器信息分别用红色和蓝色表示。子形状的冲突过滤器信息是用黑色写的。最后的过滤器信息显示在右边。
1.1.3.3.3. Disabling Instances and Shape Keys 禁用实例和形状键
静态复合形状允许动态禁用或重新启用任何实例或单个子形状键,而不需要接触引用的子形状。这意味着,例如,您可以破坏一个多次实例化的建筑物的一个窗格,或者一次破坏整个建筑物实例。要设置实例或形状键状态,只需执行以下操作:
// 禁用整个实例
staticCompoundShape->setInstanceEnabled( instanceId, false );
// 禁用单个形状键
staticCompoundShape->setShapeKeyEnabled( shapeKey, false );
通常,您将从冲突事件或查询中检索静态复合形状的形状键,并希望确定它属于哪个实例(以便禁用它)。静态复合形状提供了一个实用函数来实现这一点。按照上面的forest示例,假设我们已经获得了一个形状键,用于某个被光线投射击中的三角形。现在,我们希望禁用表示包含该三角形的树的实例。这可以通过以下方式实现:
hkpShapeKey hitKey = [...]; // e.g, obtained from ray cast
//将形状键分解为:实例ID;实例形状的子形状键
int instanceId;
int childShapeKey;
staticCompoundShape->decomposeShapeKey( hitKey, instanceId, childShapeKey );
// instanceId现在引用“树”的实例,所以让我们禁用它
staticCompoundShape->setInstanceEnabled( instanceId, false );
// 或者您可能希望从参考的“树”形状中查找一些东西……
const hkpShape* childShape = staticCompoundShape->getInstances()[instanceId]->getShape();
[...];
“Demos/Physics2012/Api/Collide/Shapes/StaticCompound/KeyDisabling
” demo演示了这个概念,如下面的截图所示。单个静态复合形状包含BV压缩网格形状的两个实例(其中包含许多盒子形状)。当光线转换移动时,其中一个实例的单个形状键将被禁用。
1.1.3.3.4. Limitations and known issues
- 这种形状不应该用于动态物体。任何被发现使用它的动态物体目前都必须在PlayStation®3上使用PPU进行碰撞检测。
- 实例转换不支持倾斜,这将被截断。避免倾斜的一种安全方法是仅在不向子形状应用任何额外旋转的形状的实例中使用非均匀缩放。
- 包含焊接信息的三角形网格的形状实例(如hkpBvCompressedMeshShape或hkpExtendedMeshShape)上的不均匀缩放将产生不正确的焊接接触法线,因为预先计算的三角形焊接信息只在应用的任何比例是均匀的情况下有效。目前还不支持对仪表化焊接信息进行不一致的实时缩放。
- 退化三角形可以通过对其他有效三角形的变换(特别是缩小尺度)得到。这样的退化三角形目前没有修剪,并可能导致冲突检测器中的断言。建议避免对网格实例使用缩尺。相反,应该在最小的实例规模上创建网格,并为其他实例将其放大。
- 当实例没有较大的旋转时,形状使用的某些查询更有效。建议在可能的情况下避免使用旋转。
- 在Havok内容工具中,目前还不支持这种形状。
1.1.4. Bounding Volume Shapes 包围盒
PS:在译文中包围盒,包围体,包围卷,边界体都是类似一个意思!!!!
包围盒形状定义围绕复杂形状(如网格形状或形状集合)的简单体。这些包围盒形状可以通过简化的检查过滤精确的碰撞检测,从而显著加快复杂形状的碰撞检测速度。如果简化的检查结果处于非碰撞状态,则不需要进一步检查,我们可以跳过调用精确碰撞检测。具有许多子形状的形状容器应该总是以某种形式的包围体进行包装;否则,对容器的查询可能非常慢。
在Havok Physics 2012中有几种包围盒实现:
-
hkpBvShape
shape wraps a single complex shape with a single simple bounding volume shape (user definable). This should only be used for Phantom Callback Shapes.hkpBvShape用一个简单的包围体形状包装一个复杂的形状(用户可定义)。这应该只用于虚回调形状。
-
hkpBvTreeShape
creates an optimized spatial tree for a large list of children, providing optimized access to children shapes which are in close proximity of the colliding body.hkpBvTreeShape为大量子对象创建了优化的空间树,提供了对接近碰撞体的子对象形状的优化访问。
-
hkpTriSampledHeightFieldBvTreeShape
, which is only used for a specialized implementation of height fields. This shape will be discussed below.hkpTriSampledHeightFieldBvTreeShape,它仅用于高度字段的特殊实现。这个形状将在下面讨论。
-
Some shapes have their own internal bounding volume structure, notably
hkpBvCompressedMeshShape
andhkpStaticCompoundShape
. These should not be wrapped in any other bounding volume.一些形状有自己的内部包围盒结构,特别是hkpBvCompressedMeshShape和hkpStaticCompoundShape。这些不应该封装在任何其他包围盒中。
1.1.4.1. Simple Bounding Volume Shapes 简单包围盒形状
一个hkpBvShape有两个hkpShape成员,一个是封装的子形状,另一个描述用于子形状的包围体。只有当S与包围体相交(穿透)时,才会创建一个用于hkpShape S与子形状之间的冲突代理。如果S与包围体脱节,则删除代理。因此,只有当S也与包围体相交时,才会检测到S与子形状之间的碰撞。
考虑一辆汽车(带有详细的物理表示)在风景上行驶。通过使用一个盒子作为边界体形状,我们可以加快对复杂车辆的碰撞检测,只要盒子不不断与景观发生碰撞。如果我们期望盒子不断地碰撞(与景观),我们仍然必须执行整个复杂的碰撞工作,并且包围体形状变得无用,并且不必要的CPU成本。
要重新迭代,hkpBvShape的主要用途是创建“幻影”回调形状,这里将详细讨论这些回调形状。对于所有其他情况,我们建议使用包围体树形状(hkpBvTreeShape)。
1.1.4.2. Bounding Volume Tree Shapes 包围体树形状
hkpBvTreeShape向hkpshapecection(例如hkpExtendedMeshShape)添加额外的包围盒信息,以支持快速区域查询。事实上,虽然底层的加速结构不一定是树结构,但实际上它通常是树结构(例如AABB树或BSP树),因此得名。边界盒树在碰撞检测器必须检查移动对象与大型静态几何(如景观)之间的碰撞时非常有用。
在分配集合内的所有形状与hkpShape发生冲突之前,可以使用区域查询生成集合中可能与该形状的AABB发生冲突的更小的形状子集。它还经常用于加速其他查询,比如raycast。
如果您对景观使用hkpBvTreeShape,则构成景观的形状按层次结构分组在一个包围体树中。在树的每个节点上都存在一个包围多面体,它封装了树的所有子节点。顶层包围体包含整个景观,而叶子层上的节点封装了一个几何原语,通常是一个三角形。这个边界卷的适合度可以是完美的(就像在一些AABB树中),也可以有额外的余量/公差(例如MOPP)。
就景观而言,不是依次检查移动对象是否与景观中的每个三角形碰撞(这非常耗时),而是根据包围体树检查移动对象的包围框:首先,Havok评估移动对象是否与顶级包围盒相交,然后与它的任何子包围盒相交,以此类推,直到检查到达叶子节点。然后,将任何可能发生碰撞的三角形的列表传递给细测阶段碰撞检测。可以将包围盒树看作细测阶段碰撞检测系统的过滤器。
1.1.4.3. MOPP Bounding Volume Tree Shape MOPP包围体树的形状
hkpMoppBvTreeShape是一种优化的Havok加速树结构(与KDOP树的建模原理相同)。它是一个快速、内存优化的结构,主要用于三角形网格。
hkpMoppBvTreeShape是使用MOPP实现hkpBvTreeShape的。MOPP使用许多优化来加速与底层形状的冲突查询。MOPP树中的每个叶子节点包围框都有一个“适合度公差”,表示该框与封闭形状的匹配程度。提供了一个默认的匹配公差,但是也可以手动指定匹配公差,以根据特定的需要优化树。鼓励用户使用不同的值进行实验,以获得尽可能好的结果。有关自定义配合公差的更多信息,请参阅优化和调优部分。
下面的例子演示了如何使用hkpMoppBvTreeShape创建一个包含额外边界卷信息的景观。首先,为景观的几何形状创建一个hkpExtendedMeshShape。然后创建一个子部分,指定必要的顶点和索引信息,并添加到网格形状中。虽然本例中的网格形状只有一个子部分,但通常使用hkpExtendedMeshShape会包含许多对应于景观不同区域的子部分。
m_mesh = new hkpExtendedMeshShape;
// 通常情况下,景观的凸半径为0(对于每个三角形)
// 以及所有半径不为零的运动物体。
m_mesh->setRadius( 0.0f );
{
hkpExtendedMeshShape::TrianglesSubpart part;
part.m_vertexBase = vertices;
part.m_vertexStriding = sizeof(float) * 3;
part.m_numVertices = numVertices;
part.m_index16Base = indices;
part.m_indexStriding = sizeof( hkUint16 );
part.m_numTriangleShapes = numTriangles;
part.m_stridingType = hkpExtendedMeshShape::HK_INT16_INDICES;
m_mesh->addTriangleSubpart( part );
}
然后使用
hkpMoppUtility::buildCode()
函数为网格形状创建MOPP信息(该函数构建一个hkpMoppCode)。hkpMoppCode包含所有实际的MOPP树和包围盒信息,并在遍历MOPP树时使用。在本例中,使用了默认的fit公差,但是根据使用的数据的类型/规模手动设置这些公差非常重要。请参阅优化细测阶段部分的MOPP配合公差部分。
hkpMoppCompilerInput mci;
m_code = hkpMoppUtility::buildCode( m_mesh, mci );
最后,使用网格形状及其代码创建hkpMoppBvTreeShape。
hkpMoppBvTreeShape* moppShape = new hkpMoppBvTreeShape(m_mesh, m_code);
MOPP通常内存消耗非常低(网格形状中的每个三角形平均约10个字节,尽管这个数字会随着用于公差匹配的参数而变化)。
注意:
一般情况下,建议每个场景/关卡只使用一个MOPP景观。
这是第二章节的第一部分,这一章节开始有点硬核且内容相当多,所以分为几部分翻译,是介绍如何创建模拟世界的,刚入坑havok,发现没有翻译以及教程较少,所以自己翻译之余发上来方便一起学习,若译文内容有不同意见请与我交流,侵权即删,转载请标注出处:https://blog.csdn.net/weixin_42622658/article/details/89740007#1143_MOPP_Bounding_Volume_Tree__Shape__MOPP_596