XNA 中的碰撞检测





0/5 (0投票)
XNA 中的碰撞检测
[本文是关于碰撞检测的序言的延续,该序言发布在此处。它刷新并完成了较早的 XNA 碰撞检测帖子 - 第一部分、第二部分和第三部分,这些部分是很久以前写的,并且多次要求完成。 终于,它来了!]
简单碰撞检测
XNA 包括对形状的简单相交测试,例如:边界球、AABB(轴对齐边界框)、平面、射线、矩形、视锥体等,以及它们的任意组合。 更有用的是,3D 模型已经为其各个部分设置了边界球。
使用这些测试,几乎可以实现任何类型的游戏类相交。 您必须记住,网格-任何东西的相交都很昂贵(当然,取决于多边形的数量),应该留给需要非常高的相交精度的特殊情况。 因此,通常首选使用一堆球体或盒子来近似复杂的几何体,而不是使用真实的三角形(参见第 1 部分)。
在 Sharky 的博客中有一篇关于 XNA 碰撞的非常好的帖子,特别是专注于使用边界球近似通用形状。 您可以在此处找到它。
精确碰撞检测
正如前面所评论的,有时需要更精确的相交方法。 例如,对于光照计算(其中光线追踪是模拟光照的最佳和最常用的方法——参见右图),射线-网格相交测试似乎是最佳选择。 在 D3D 中,有一个 Mesh.Intersect
方法可供您使用,该方法执行所需的相交测试,但在 XNA 中,没有这样的方法,我们将不得不自己完成。
为此,我们需要一个几何体的系统内存副本。 不幸的是,网格通常在 XNA 中使用 ReadOnly
标志创建(以使其管理快速),这不允许我们在运行时访问它们的几何体。 为此,我们将不得不处理自定义内容处理。
注意:此处,您将找到自定义内容处理的介绍。
为碰撞检测实现自定义内容处理器
解决先前问题的方法是创建一个自定义内容处理器,该处理器在构建时存储几何体信息的副本,此时仍然可以访问它。 所有需要的信息将存储在一个名为 MeshData
的新类中。
public class MeshData
{
public VertexPositionNormalTexture[] Vertices;
public int[] Indices;
public Vector3[] FaceNormals;
public MeshData(VertexPositionNormalTexture[] Vertices, int[] Indices,
Vector3[] pFaceNormals)
{
this.Vertices = Vertices;
this.Indices = Indices;
this.FaceNormals = pFaceNormals;
}
}
您可以在此处放入您需要的所有信息。 目前,存储顶点、索引和面法线就足够了。
当 VisualStudio 使用我们的 ContentProcessor
处理每个模型时,它会将模型的数据写入 XNB 文件。 当它找到一个 MeshData
对象时,它会搜索一个能够序列化它的写入器,因此我们必须为 MeshData
类编写我们自己的自定义 ContentTypeWriter
。
[ContentTypeWriter]
public class ModelVertexDataWriter : ContentTypeWriter<MeshData>
{
protected override void Write(ContentWriter output, MeshData value)
{
output.Write((int)value.Vertices.Length);
for (int x = 0; x < value.Vertices.Length; x++)
{
output.Write(value.Vertices[x].Position);
output.Write(value.Vertices[x].Normal);
output.Write(value.Vertices[x].TextureCoordinate);
}
output.Write(value.Indices.Length);
for (int x = 0; x < value.Indices.Length; x++)
output.Write(value.Indices[x]);
output.Write(value.FaceNormals.Length);
for (int x = 0; x < value.FaceNormals.Length; x++)
output.Write(value.FaceNormals[x]);
}
public override string GetRuntimeType(TargetPlatform targetPlatform)
{
return typeof(MeshData).AssemblyQualifiedName;
}
public override string GetRuntimeReader(TargetPlatform targetPlatform)
{
return "ContentProcessors.ModelVertexDataReader, ContentProcessors," +
"Version=1.0.0.0, Culture=neutral";
}
}
以类似的方式,当 ContentPipeline
尝试读回 XNB 文件时,它将搜索 MeshData
类型的反序列化器,因此我们必须编写我们自己的 ContentTypeReader
。
public class ModelVertexDataReader : ContentTypeReader<MeshData>
{
protected override MeshData Read(ContentReader input, MeshData existingInstance)
{
int i = input.ReadInt32();
VertexPositionNormalTexture[] vb = new VertexPositionNormalTexture[i];
for (int x = 0; x < i; x++)
{
vb[x].Position = input.ReadVector3();
vb[x].Normal = input.ReadVector3();
vb[x].TextureCoordinate = input.ReadVector2();
}
i = input.ReadInt32();
int[] ib = new int[i];
for (int x = 0; x < i; x++)
ib[x] = input.ReadInt32();
i = input.ReadInt32();
Vector3[] normals = new Vector3[i];
for (int x = 0; x < i; x++)
normals[x] = input.ReadVector3();
return new MeshData(vb, ib, normals);
}
}
最后,我们的自定义内容处理器会为通过它的每个模型填充 MeshData
对象。 注意:某些部分取自ZiggyWare。
[ContentProcessor(DisplayName = "Custom Mesh Processor")]
public class PositionNormalTexture : ModelProcessor
{
public override ModelContent Process(NodeContent input,
ContentProcessorContext context)
{
ModelContent model = base.Process(input, context);
foreach (ModelMeshContent mesh in model.Meshes)
{
// Put the data in the tag.
VertexPositionNormalTexture[] vb;
MemoryStream ms = new MemoryStream(mesh.VertexBuffer.VertexData);
BinaryReader reader = new BinaryReader(ms);
VertexElement[] elems = mesh.MeshParts[0].GetVertexDeclaration();
int num =
mesh.VertexBuffer.VertexData.Length / VertexDeclaration.GetVertexStrideSize(
elems, 0);
vb = new VertexPositionNormalTexture[num];
for (int i = 0; i < num; i++)
{
foreach (VertexElement e in elems)
{
switch (e.VertexElementUsage)
{
case VertexElementUsage.Position:
vb[i].Position.X = reader.ReadSingle();
vb[i].Position.Y = reader.ReadSingle();
vb[i].Position.Z = reader.ReadSingle();
break;
case VertexElementUsage.Normal:
vb[i].Normal.X = reader.ReadSingle();
vb[i].Normal.Y = reader.ReadSingle();
vb[i].Normal.Z = reader.ReadSingle();
break;
case VertexElementUsage.TextureCoordinate:
if (e.UsageIndex != 0)
continue;
vb[i].TextureCoordinate.X = reader.ReadSingle();
vb[i].TextureCoordinate.Y = reader.ReadSingle();
break;
default:
Console.WriteLine(e.VertexElementFormat.ToString());
switch (e.VertexElementFormat)
{
case VertexElementFormat.Color:
reader.ReadUInt32();
break;
case VertexElementFormat.Vector3:
reader.ReadSingle();
reader.ReadSingle();
reader.ReadSingle();
break;
case VertexElementFormat.Vector2:
reader.ReadSingle();
reader.ReadSingle();
break;
}
break;
}
}
} // for i < num
reader.Close();
int[] ib = new int[mesh.IndexBuffer.Count];
mesh.IndexBuffer.CopyTo(ib, 0);
Vector3[] normals = new Vector3[mesh.IndexBuffer.Count / 3];
for (int i = 0, conta = 0; i < mesh.IndexBuffer.Count; i += 3, conta++)
{
Vector3 v0 = vb[mesh.IndexBuffer[i]].Position;
Vector3 v1 = vb[mesh.IndexBuffer[i + 1]].Position;
Vector3 v2 = vb[mesh.IndexBuffer[i + 2]].Position;
Vector3 edge1 = v1 - v0;
Vector3 edge2 = v2 - v0;
Vector3 normal = Vector3.Cross(edge1, edge2);
normal.Normalize();
normals[conta] = normal;
}
mesh.Tag = new MeshData(vb, ib, normals);
} // foreach mesh
return model;
}
}
现在我们有了所有需要的信息,我们将专注于碰撞检测本身的实现。
使用 MeshData 实现射线-网格测试
许多人认为 D3D Mesh.Intersect
方法会进行某种优化的“魔术”来测试相交,但实际上它只是循环遍历网格的所有三角形,进行三角形-射线相交测试,并跟踪最近的碰撞点(或所有点,具体取决于您使用的重载版本)。 当然,它应用了一些众所周知的优化,例如快速丢弃多边形、背面等。 这正是我们现在必须使用在内容处理器中生成的信息来做的事情。
以下方法执行射线-网格测试,并将先前内容处理器生成的 MeshData
对象作为参数获取。 请注意,此处可以进行大量优化,快速丢弃三角形。 只需要在 Google 上搜索一下即可。
public static bool RayMesh(Vector3 orig, Vector3 dir, MeshData pMesh,
ref Vector3 pContactPoint, ref float pDist, ref int pFaceIdx)
{
Vector3 maxContactPoint = Vector3.Zero;
int maxFaceIdx = -1;
float minT = float.MaxValue;
for (int i = 0, countFace = 0; i < pMesh.Indices.Length; i += 3, countFace++)
{
int ia = pMesh.Indices[i];
int ib = pMesh.Indices[i + 1];
int ic = pMesh.Indices[i + 2];
Vector3 v0 = pMesh.Vertices[ia].Position;
Vector3 v1 = pMesh.Vertices[ib].Position;
Vector3 v2 = pMesh.Vertices[ic].Position;
double t = 0f;
double u = 0f;
double v = 0f;
if (RayTriangle(orig, dir, v0, v1, v2, ref t, ref u, ref v))
{
Vector3 appPoint = orig + (dir * (float)t);
if (t < minT)
{
minT = (float)t;
maxFaceIdx = countFace;
maxContactPoint = appPoint;
}
}
}
pContactPoint = maxContactPoint;
pFaceIdx = maxFaceIdx;
pDist = minT;
return (minT < float.MaxValue);
}
剩下的唯一部分是射线-三角形相交测试,但是网上有很多关于此问题的信息,所以我将其留给您。 但是,您可以查看以下链接
- http://www.devmaster.net/wiki/Ray-triangle_intersection
- http://www.graphics.cornell.edu/pubs/1997/MT97.html
- http://www.graphics.cornell.edu/pubs/1997/MT97.pdf
- http://www.acm.org/pubs/tog/editors/erich/ptinpoly/
希望以上四个参考文献足够,并且您喜欢这篇文章。
尽情享用!