问题
模型的包围球是完整包围模型的最小球体。在很多情况中,例如碰撞检测,判断大小等,使用包围球是非常有用的。
解决方案
你可以访问模型中的每个ModelMesh的包围球。使用CreateMerged方法,你可以将这个包围球组合起来,获取包围整个模型的包围球。
但是,因为每个ModelMesh的包围球是相对于Bone矩阵定义的,所以你需要进行转换。
工作原理
你将创建一个方法将一个素材加载到一个模型变量中,初始化它的Bone矩阵数组,并将全局包围球保存到模型的Tag属性中。
技巧:你可以使用模型的Tag属性存储任何对象。本例中,你使用Tag属性存储包围球。Tag属性在本章中会经常用到。
private Model LoadModelWithBoundingSphere(ref Matrix[] modelTransforms, string asset, ContentManager content) { Model newModel = content.Load<Model>(asset); modelTransforms = new Matrix[newModel.Bones.Count]; newModel.CopyAbsoluteBoneTransformsTo(modelTransforms); return newModel; }
这个方法加载一个模型,已在教程4-1中解释过了。在返回模型前,你需要遍历模型的查看它们的包围球(这些包围球是由默认模型处理器在编译时创建的,可见教程4-13中的解释)。
foreach (ModelMesh mesh in newModel.Meshes) { BoundingSphere origMeshSphere = mesh.BoundingSphere; }
你想将这些包围球组合在一起获取整个模型的全局包围球。你可以通过保存一个新的包围球并把这些小的包围球组合在一起实现这个功能。
BoundingSphere completeBoundingSphere = new BoundingSphere(); foreach (ModelMesh mesh in newModel.Meshes) { BoundingSphere origMeshSphere = mesh.BoundingSphere; completeBoundingSphere = BoundingSphere.CreateMerged(completeBoundingSphere, origMeshSphere); }
这样completeBoundingSphere就是一个包含所有小球的大球。
但是,当你获取ModelMesh的包围球时,这个包围球是定义在ModelMesh的本地空间中的。例如,一个人的躯干的包围球是80cm,而头的包围球只有30cm,如果你只是简单将两者组合在一起,你获得的是80cm的球,这是因为小球是完整包含在大球中的。
要正确的组合两者,你首先要将头的包围球移动到躯干的顶部然后在进行组合,这样才会获得一个包含两者的大的包围球。
在XNA中,这意味着你必须首先使用ModelMesh包含的Bone矩阵转换这个ModelMesh的包围球,就像以下代码所示:
BoundingSphere completeBoundingSphere = new BoundingSphere(); foreach (ModelMesh mesh in newModel.Meshes) { BoundingSphere origMeshSphere = mesh.BoundingSphere; BoundingSphere transMeshSphere = XNAUtils.TransformBoundingSphere(origMeshSphere, modelTransforms[mesh.ParentBone.Index]); completeBoundingSphere = BoundingSphere.CreateMerged(completeBoundingSphere, transMeshSphere); } newModel.Tag = completeBoundingSphere;
正确的全局包围球会被存储到模型的Tag属性中。
注意:BoundingSphere.Transform方法是XNA Framework自带的,但它不能处理包含旋转的矩阵,所以我在XNAUtils文件中包含了一个扩展版本,你可以在示例中找到这个方法。
用法
当你想从Tag属性中获取全局包围盒时,你需要首先将它转换为BoundingSphere对象,这是因为Tag属性中可以存储任何类型的对象:
BoundingSphere bSphere = (BoundingSphere)myModel.Tag;
代码
下面是完整代码,包括初始化一个模型并将它的包围盒存储到Tag属性中:
private Model LoadModelWithBoundingSphere(ref Matrix[] modelTransforms, string asset, ContentManager content) { Model newModel = content.Load<Model>(asset); modelTransforms = new Matrix[newModel.Bones.Count]; newModel.CopyAbsoluteBoneTransformsTo(modelTransforms); BoundingSphere completeBoundingSphere = new BoundingSphere(); foreach (ModelMesh mesh in newModel.Meshes) { BoundingSphere origMeshSphere = mesh.BoundingSphere; BoundingSphere transMeshSphere = XNAUtils.TransformBoundingSphere(origMeshSphere, modelTransforms[mesh.ParentBone.Index]); completeBoundingSphere = BoundingSphere.CreateMerged(completeBoundingSphere, transMeshSphere); } newModel.Tag = completeBoundingSphere; return newModel; }