[翻译]XNA 3.0 Game Programming Recipes之twentyfour

PS:自己翻译的,转载请著明出处格
                                              4-17 根据地形下的正确倾斜模型
问题
       当在一个地形上移动一个汽车模型时,使用4-2节你可以调节汽车的高度,使用5-9节你可以找到在地形下的汽车的高度。但是,如果你不倾斜这车来响应车下面的斜面,结果看起来很糟糕在丘陵地图上。
       正确定位和倾斜你的车,使它在地形上放置合适。
解决方案
       这个问题可以分解为四个部分:
       1。你想找到模型的四个轮子中最低的顶点。
       2。其次,要在四个点下的正确地形的高度。
       3。接下来,需要得到旋转沿着模型的正向和侧面向量去恰当的倾斜模型。
       4。最后,您应该找到模型和地形的高度差异,弥补这一不同。
       不用黑客的方式运行你的代码解决第一步,你应该编写一个小的自定义模型处理器,它存储最低的顶点为模型的每个ModelMeshModelMeshesTag属性中。由于四个轮子的最低点会被移动同时游戏正在运行,每次更新你都要变换这些位置用这些顶点的当前世界位置。
       为找到准备的高度在某一位置的三角形表面,你可以使用GetExactHeightAt方法来创建在5-9节。
       方法是找一个旋转角度,是基于一个单一的数学规则上(你应该从高中的时候就记得!)
       最后一步是增加一个垂直的转换到模型的世界矩阵。
它是如何工作的
编码自定义模型处理器寻找每个ModelMesh的最低位置    
       第一步是找到模型的车轮的最低的顶点,由于这些顶点应该根本上与地图相连接。你可以建立个模型处理器,它是处理器的一个单一化的版本代码在4-14。
       对于模型的每一个ModelMesh,保存它们最低的位置在ModelMeshTag属性中。注意,你想要这个位置被界定与ModelMesh初始状态相关,这一节仍然有效即使你喜欢使用模型的Bones(参看4-9节)
       在4-14节里开始代码解释。使一些镜子改变到模型的处理器的Process方法:
 1 public override ModelContent Process(NodeContent input,ContentProcessorContext context)
 2 {
 3     List<Vector3> lowestVertices=new List<Vector3>();
 4     lowestVertices=FindLowestVectors(input,lowestVertices);
 5     ModelContent usualModel=base.Process(input,context);
 6     int i=0;
 7     foreach(ModelMeshContent mesh in usualModel.Meshes)
 8          mesh.Tag=lowestVertices[i++];
 9     return usualModel;
10 }
        FindLowestVertices方法缓慢的方式通过所有模型的节点和保存每一个ModeMesh,lowestVertices表单的最低的位置。一旦你有这个表单,你用它相应的ModelMeshTag属性保存每一个位置。
        基于AddVertices方法所描述的在4-14节,FindLowestVertices方法添加它的位置到表单,传递表单在所有的节点的子节点:
 1 Private List<Vector3> FindLowestVectors(NodeContent node,List<Vector3> lowestVertices)
 2 {
 3      Vector3? lowestPos=null;
 4      MeshContent mesh=node as MeshContent;
 5      foreach(NodeContent child in node.Children)
 6          lowestVertices=FindLowestVectors(child,lowestVectices);
 7      if(mesh!=null)
 8          foreach(GeometryContent geo in mesh.Geometry)
 9                foreach(Vector3 vertexPos in geo.Vertices.Positions)
10                      if((lowestPos==null)||(vertexPos.Y<lowestPos.Value.Y))
11                           lowestPos=vertexPos;
12      lowestVertices.Add(lowestPos.Value);
13      return lowestVertices;
14 }
         你开始调用这个方法在节点的子节点上,同样它保存它们的最低位置在表单里。
         每一个节点,检查节点是否包含任何几何信息。如果它包含,要检阅所有的顶点,定义几何图形定义在节点里。如果lowestPosnull(这将是遇到无效的首次检查)或者当前的位置比上一次存储在lowestPos的低,那么保存当前的位置到lowestPos中。
         最后,顶点中y轴最低的坐标被保存在lowestPos中。你添加这个到lowestVertices表单中,返回表单到父节点。
注意:正如4-14节所讨论的,一个ModelMesh首先调用这个方法在它的子节点中,然后它添加它自己最低的位置到表单中。更为直观的方法是使一个节点首先保存它自己的Vector3到表单中,然后调用这个方法在它的子节点中。您需要做的是在为了早点显示,虽然,因为这是为了节点转换为ModelMeshes用默认的模型处理器。在Process方法中,它允许你很容易保存正确的Vector3在正确的ModelMeshTag属性中。
         确保你选择这个模型处理器去处理这节你导入的模型。
找到轮子的最低位置的绝对3D坐标
         在你的XNA程序中,你已经保存了四个轮子的位置。它靠你模型的结构和轮子对应的ModelMesh。你可以使用4-8节的代码的模型可视结构。
         对每一个轮子,你应该知道ModelMesh相应的ID。一旦你知道ID,你可以通过每个轮子的最低位置,保存它用一个变量。尽管您可以将下面的代码4倍紧凑如下所示,使用简单for循环, 我会一直使用四个独立的变量与某种直观的名称为每个车轮。 前端左侧是简称fl,后右一br ,等等。
1 int flID=5;
2 int frID=1;
3 int blID=4;
4 int brID=0;
5 Vector3 frontLeftOrig=(Vector3)myModel.Meshes[flID].Tag;
6 Vector3 frontRightOrig=(Vector3)myModel.Meshes[frID].Tag;
7 Vector3 backLeftOrig=(Vector3)myModel.Meshes[blID].Tag;
8 Vector3 backRightOrig=(Vector3)myModel.Meshes[brID].Tag;
         记住,你需要使用正确的ID为你的指定的模型,4-8节你可以找到。
         你已经保存位置在ModelMeshesTag属性中,是与ModelMesh的初始状态相关。你想知道他们在同一个作为您地形的空间,这是在绝对的三维空间。
         第一步将是实现这一目标找到最低的顶点位置相对于模型的初始状态,靠转换他们用绝对的ModeMesh的转换矩阵来实现它们(参看4-9节)。下一步,由于你可能同样使用一个世界矩阵去绘制模型在一个3D世界确定的位置,你应该结合每一个ModelMesh的Bone矩阵用模型的世界矩阵。
1 myModel.CopyAbsoluteBoneTransformsTo(modelTransforms);
2 Matrix frontLeftMatrix=modelTransforms[myModel.Meshes[flID].ParentBone.Index];
3 Matrix frontRightMatrix=modelTransforms[myModel.Meshes[frID].ParentBone.Index];
4 Matrix backLeftMatrix=modelTransforms[myModel.Meshes[blID].ParentBone.Index];
5 Matrix backRightMatrix=modelTransforms[myModel.Meshes[brID].ParentBone.Index];
6 Vector3 frontLeft=Vector3.Transform(frontLeftOrig,frontLeftMatrix*modelWorld);
7 Vector3 frontRight=Vector3.Transform(frontRightOrig,frontRightMatrix*modelWorld);
8 Vector3 backLeft=Vector3.Transform(backLeftOrig,backLeftMatrix*modelWorld);
9 Vector3 backRight=Vector3.Transform(backRightOrig,backRightMatrix*modelWorld);
         首先,你计算绝对变换矩阵为所有模型的Bones(参看4-9)。下一步,每一个轮子,你可以发现绝对变换矩阵保存在相应的轮子的ModelMesh的Bone中。
         一旦你知道每一个轮子的绝对变换矩阵,你结合这些矩阵用模型的世界矩阵和使用结果矩阵去转换你的顶点。由此产生的Vector3包含模型轮子的最低顶点的绝对3D坐标。
注意:正如4-2节中的详细说明,命令矩阵的乘法是非常重要的。由于这些顶点是模型中的部分,首先,你得到在(0,0,0)和绝对3D初始状态之间的偏移量保存在世界矩阵中,后来你转换你的顶点这样它们变得与模型的初始状态有关了。用这个方法做这个事情,你的世界矩阵影响了模型的Bones,这就是你想要的。如果你从相反的方向做这个,任何旋转包含在Bones将会影响世界矩阵。见4-2节得到更多信息关于顺序矩阵的乘法。      
        最后,您已准备好进行四个顶点与您的地形碰撞,因为你的顶点和相应地形的位置都是在绝对3D空间中。
检索在模型下的地形的高度
        现在,你已经找到四个轮子的绝对3D坐标,你几乎准备找到必须的旋转角。作为开始,你想找到模型围绕侧面向量应该旋转多少,使车的前方降低或提高。不是所有的四个点都要工作,你将你的计算仅仅基于在两个点上。第一个点,front,是前轮之间的位置,同时第二个点,back,是后面轮子之间的位置,如图4-23图示说明。你想要找到旋转,因此虚线frontToBack(连接两个点)如何下面的地形。

        你可以找到两个点的位置,很容易得到相临轮子的中点。你可以找到它们之间的向量,backToFront,by subtracting them from each other:
1 Vector3 front=(frontLeft+frontRight)/2.0f;
2 Vector3 back=(backLeft+backRight)/2.0f
;
3 Vector3 backToFront=front-back;
        记住,你想知道你的车围绕侧面向量应该旋转多少,因此它的front点被朝上或朝下移动。理想上,你想让你的frontToBack向量有同样的弯曲与地形斜面,如4-24图所示。在图4-24中这个角度你想要计算被fbAngle表示。
        您必须先找到地形在这两点的高差。用GetExactHeightAt方法结构在5-9节:
1 float frontTerHeight=terrain.GetExactHeightAt(front.X,-front.Z);
2 float backTerHeight=terrain.GetExactHeightAt(back.X,-back.Z);
3 float fbTerHeightDiff=frontTerHeight-backTerHeight;

计算旋转角度
        现在你知道在地形上的高度差,使用一些三角数学你就可以找到倾角。在一个直角三角形中,你可以找到其他的角度如果你知道对面边长(图4-24的A)和邻边(图4-24B)。角度被弧切线(atan)的它们的长度。第一长度等于高差你刚才计算,而第二长度等于你的frontToBack向量的长度!
        这是你如何找到的角度,构建相应的旋转围绕着(1,0,0)侧面向量。由此产生的旋转是储存在一个四元数(有用的对象存储和结合旋转没有万向节锁,如2-4节解释的那样):
1 float fbAngle=(float)Math.Atan2(fbTerHeighDiff,backToFront.Length());
2 Quaternion bfRot=Quaternion.CreateFromAxisAngle(new Vector3(1,0,0),-fbAngle);
        如果你旋转你的模型使用这个rotation,你模型的front和back点沿着你地形的坡度!
        显然,这只有百分之50个工作,由于你同样想旋转你的模型围绕前进向量,所以它的left和right的点沿着地形。幸运的是,你可以用同样的方法和代码去找到lrAngle.你想确保leftToFront虚线如图4-23,是相一致的地形:
1 Vector3 left=(frontLeft+backLeft)/2.0f;
2 Vector3 right=(frontRight+backRightt)/2.0f;
3 Vector3 rightToLeft=left-right;
4 float leftTerHeight=terrain.GetExactHeightAt(left.X-left.Z);
5 float rightTerHeight=terrain.GetExactHeightAt(lright.X-right.Z);
6 float lrTerheightDiff=leftTerHeight-rightTerHeight;
7 float lrAngle=(float)Math.Atan2(lrTerHeightDiff,rightToLeft.Length());
8 Quaternion lrRot=Quaternion.CreateFromAxisAngle(new Vector3(0,0,-1),-lrAngle);
        现在你已经旋转了,用乘以他们可以很容易地把他结合起来。用世界转换来结合转换的结果:
1 Quaternion combRot=fbRot*lrRot;
2 Matrix rotatedModelWorld=Matrix.CreateFromQuaternion(combRot)*modelWorld;
        如果你使用这rotatedModelWorld矩阵作为世界矩阵为绘制你的模型,它将完美的旋转,以适应您的地形!不过,你仍然需要的放置它在正确的高度。
Positioning the Model at the Correct Height
        由于你旋转你的模型,一些轮子可能会比其他的低。现在你要计算你的旋转,你可以很容易找出你轮子旋转的位置。
1 Vector3 rotFrontLeft=Vector3.Transform(frontLeftOrig,frontLeftMatrix*rotateModelWorld);
2 Vector3 rotFrontRight=Vector3.Transform(frontRightOrig,frontRightMatrix*rotateModelWorld);
3 Vector3 rotBackLeft=Vector3.Transform(backLeftOrig,backLeftMatrix*rotateModelWorld);
4 Vector3 rotFBackRight=Vector3.Transform(backRightOrig,backRightMatrix*rotateModelWorld);
         下一步,您可以使用X和Y这些位置的组成部分,找出究竟在何处他们应布置:
1 float flTerHeight=terrain.GetExactHeightAt(rotFrontLeft.X,-rotFrontLeft.Z);
2 float frTerHeight=terrain.GetExactHeightAt(rotFrontRight.X,-rotFrontRight.Z);
3 float blTerHeight=terrain.GetExactHeightAt(rotBackLeft.X,-rotBackLeft.Z);
4 float brTerHeight=terrain.GetExactHeightAt(rotBackRight.X,-rotBackRight.Z);
         你知道你轮子的Y高度坐标,而且你知道Y坐标应该在哪,因此可以很容易地计算出多少他们距离:
1 float flHeightDiff=rotFrontLeft.Y-flTerHeigth;
2 float frHeightDiff=rotFrontRight.Y-frTerHeigth;
3 float blHeightDiff=rotBackLeft.Y-blTerHeigth;
4 float brHeightDiff=rotBackRight.Y-brTerHeigth;
         虽然,你已经获得四个不同的值,它可能非常有用为你移动你的模型到它的正确高度。由于你将会移动你的整个模型,只有一个值,你实际可以使用。
         实际上,你的价值要取决于您的喜好。如果你想确保没有一个车轮曾经粘贴地面哪怕1毫米,都会带来很大的差异。如果你不喜欢你的模型和地形之间有间距,取最小的一个。嘿,这是我这一节, 所以我考虑用他们的平均值作为最终的值:
1 float finalHeightDiff=(blHeightDiff+brHeightDiff+flHeightDiff+frHeightDiff)/4.0f;
2 modelWorld=rotatedModelWorld*Matrix.CreateTranslation(new Vector3(0,-finalHeightDiff,0));
        最后一行添加一个矩阵,它包含这顶点的变换到模型的世界矩阵。
注意:这里你把你的新的计算矩阵放在乘式的右边去确保模型被移动开绝对Up轴。如果把它放在乘式的左边,这模型将会被沿着模型的Up向量移动。它是被旋转的。
        就是这样的。如果你绘制你的模型使用这个世界矩阵,这个模型将很好的沿着地形的斜面。这是一个庞大的代码,但是如果你把它放在一个for循环中,数额已经被4除了。如果你害怕它看起来有点重的计算量,请记住这些计算必须被执行为模型,必须绘制到屏幕上!
Preparing for Animation
        如果你有个活动的模型,你可以运行出一些麻烦。例如,如果你旋转了某个轮子180度,这向量保存在轮子的Tag属性中,它包含了轮子的最高点,而不是最低点!这将造成你的模型的轮子沉入到地面中。
        为了解决这个,你应该重新设置轮子的Bone矩阵到它们的初始值在执行计算之前。这是没有问题的,因为你需要保存这些
无论如何做一个活动的模型(参看4-9节);2,4,6和8 the indices of the Bones attached to the four wheels。
 1 myModel.Bones[2].Transform=originalTransforms[2];   
 2 myModel.Bones[4].Transform=originalTransforms[4
]; 
 3 myModel.Bones[6].Transform=originalTransforms[6
]; 
 4 myModel.Bones[8].Transform=originalTransforms[8
]; 
 5 float time=(float)gameTime.TotalGameTime.TotalMilliseconds/1000.0f
;
 6 Matrix worldMatrix=Matrix.CreateTranslation(new Vector3(10,0,-12));//starting position

 7 worldMatrix=Matrix.CreateRotationY(MathHelper.PiOver4*3)*worldMatrix;
 8 worldMatrix=Matrix.CreateTranslation(0,0,time)*worldMatrix;//move forward

 9 worldMatrix=Matrix.CreateScale(0.001f)*worldMatrix;//scale down a bit
10 worldMatrix=TiltModelAccordingToTerrain(myModel,worldMatrix,5,1,4,0);//do titing magic
代码
   
参看前面的代码!
原文地址:https://www.cnblogs.com/315358525/p/1536609.html