in XNA

XNA – 3D模型的碰撞檢測 – Part 2

續上篇「Part1



掰了~BoundingBox,哈囉!BoundingSphere

除了BoundingBox之外,XNA還提供了BoundingSphere這個碰撞檢測的類別。再次提醒,BoundingSphere是一個不可見的類別,且有個叫做Intersects()的方法用來告訴你是否有和其他物件碰撞。

例如:

 if (bulletBoundingSphere.Intersects(planeBoundingSphere))
 {
     //plane's been hit. cue smoke, flames, snakes in a panic, singing nuns, whatever…
 }

※Heightlight版

回想一下par1的部分…是否還記得這些??

 這裡有些應該了解的:

  • 當遊戲中的實體物件移動時,同時也要更新對映的碰撞物件。
  • 當你縮放實體物件時,也要同時縮放對映的碰撞物件。
  • 當旋轉你的實體物件時,一樣要旋轉對應的碰撞物件。

一個球體不管怎麼轉,他還是一個球體。所以,旋轉的部分就可以甭管了~不再有AABB的複雜問題。但我們仍需更新BoundingSphere的座標,也需要隨著模型縮放。

BoundingSphere的中心旅行
(意義不明)

比起BoundingBox,要準確的移動BoundingSphere更為簡單。BoundingSphere就只有一個用Vector3代表的中心點,以及一個半徑。當模型移動時,需要將中心點一到正確的地方。當然,如果模型有縮放仍然要跟著縮放。

說完這些,你一定很困惑「OK~那這些球體從哪邊來的?」

嗯~你可以輕易的創造它們,但沒那必要。XNA的Model類別會自動幫Model裡的每個ModelMesh產生一個BoundingSphere放在模型裡。

像是我的Spitfire模型就有幾個ModelMesh,像是機翼、機身、駕駛座艙、螺旋槳、尾翼等等…

這裡範例展示要如何取得Model的BoundingSphere…

 //Load the model using my game's instance of a ContentManager.

 Model myModel = MyGame.ContentManager.Load<Model>("ContentMeshesMyModel");

 foreach (ModelMesh mesh in myModel.Meshes)
 {
     //Do stuff with mesh.BoundingSphere
 }

※Heightlight版

當XNA的ContentManager讀取這個Model時,它會產生包覆每個組件的
BoundingSphere。座標,mesh.BoundSphere.Center,他是相對於整個Nodel的中心點(0, 0, 0)。我相信有時候會參考像是「Object space」的座標。不要和「World space」混淆了,World space是描述這個物件在整個場景中的座標。

如你現在將BoundingSphere畫出來,應該會像是我的Spitfire模型一樣…


你可以看到飛機上幾個組件的球體:

  • 亮綠色是機翼
  • 橙色是尾翼
  • etc…

這些「預設」的BoundingSphere可能非常符合你的模型。以這個Spitfire模型來看,很顯然的不適合。如果用part1題到的半徑檢測技術來看,我的飛機顯然變成一個更大的目標(對子彈來說)。

讓我們來修正他…

拆解與克服

當然,這些「預設」的BoundingSphere並不能真的切成數塊,但它們可以為我們在執行時提供一些資訊。所以,我們要使用這些資訊來產生數個新的BoundingSphere來更適用於我們的模型。

就像是…

我們會在Model讀取完之後,馬上產生新的
BoundingSphere,並且儲存起來以供使用。這裡展示我如何處理他們。我之道還有很大的進步空間,歡迎指教。

接著來看看這些程式碼,它們可以在MyGame.cs裡面找到…

 private void AnalyseModel()
 {
     // Take a copy of the Model's BoneTransforms (transforms to be applied to each part of the model)
     GetBoneTransforms();

     // Build BoundingParts for Collision Detection
     BuildBoundingParts();
 }

 private void GetBoneTransforms()
 {
     _modelBoneTransforms = new Matrix[_model.Bones.Count];
     _model.CopyAbsoluteBoneTransformsTo(_modelBoneTransforms);
 }

※Heightlight版
你可以看到我在ContentManager讀取完Model之後馬上呼叫AnalyseModel()。

GetBoneTransforms()會複製一份Model的BoneTransform矩陣資料。簡而言之,這些BoneTransform是用來告 訴XNA要如何定位、旋轉和縮放模型裡的每個組件。如果忽略它,我們的飛機組件(像是機翼、機身、尾翼)可能就不會依照模型設計者所設計的展現。

是的,這一切聽起來令人興奮,所以來看看著個好東西。這些程式碼在MyGame.cs約頂部的地方,你可以看到以下有趣的程式碼…

 //Blob mesh
 private string[] _modelBoundingPartNames = new string[]
 {
     "Fuselage",
     "Wing",
     "Tail"
 };

 private Color[] _modelBoundingPartColours = new Color[]
 {   
     Color.Blue,
     Color.Red,
     Color.GreenYellow
 };

 private Vector4[][] _modelBoundingPartSubdivisions = new Vector4[][]
 {
     //Fuselage
     new Vector4[] { new Vector4(0.50f, 0.10f, 0.46f, 0.20f),
                            new Vector4(0.50f, 0.30f, 0.46f, 0.27f),
                            new Vector4(0.50f, 0.60f, 0.48f, 0.26f)
     },
     //Wing
     new Vector4[] { new Vector4(0.07f, 0.51f, 0.50f, 0.20f),
                            new Vector4(0.30f, 0.515f, 0.50f, 0.25f),
                            new Vector4(0.50f, 0.515f, 0.50f, 0.27f),
                            new Vector4(0.70f, 0.515f, 0.50f, 0.25f),
                            new Vector4(0.93f, 0.51f, 0.50f, 0.20f)
     },
     //Tail
     new Vector4[] { new Vector4(0.25f, 0.52f, 0.45f, 0.40f),
                            new Vector4(0.50f, 0.56f, 0.53f, 0.55f),
                            new Vector4(0.75f, 0.52f, 0.45f, 0.40f)
     }
 };

※Heightlight版
這些陣列將在範例裡面使用到,其中最需要注意的是_modelBoundingPartSubdivisions的這個陣列。

他是用來描述如何在Model剛讀取時,利用「預設」的BoundingSphere產生新的BoundingSphere。

接著來看如何利用Vector4的定義來產生機翼的5個BoundingSphere。Vector4的X、Y和Z分別代表在各軸的位移百分比,而W則是代表半徑的縮放百分比。

接下來看BuildBoundingParts()方法會做些甚麼事情!

 /// <summary>
 /// Builds a collection of BoundingParts for each ModelMesh in the Model.
 /// Each BoundingPart will contain one or many BoundingSphere as defined in the _modelBoundingPartSubdivisions array.
 /// </summary>
 private void BuildBoundingParts()
 {
    _modelBoundingParts = new BoundingPartList();

    BoundingPart boundingPart;

    int meshIndex = 0;
    foreach (ModelMesh mesh in _model.Meshes)
    {
        BoundingSphere[] pieces = new BoundingSphere[_modelBoundingPartSubdivisions[meshIndex].Length];
        int pieceIndex = 0;

        //Create, Scale and Position new BoundingSphere's according to the defined subdivisions for this part of the model.
        foreach (Vector4 subdivision in _modelBoundingPartSubdivisions[meshIndex])
        {
            //Determine the new BoundingSphere's Radius
            float radius = subdivision.W * mesh.BoundingSphere.Radius;

            //Determine the new BoundingSphere's Center by interpolating.
            //The subdivision's X, Y, Z represent percentages in each axis.  They will used across the full diameter of XNA's "default" BoundingSphere.
            float x = MathHelper.Lerp(mesh.BoundingSphere.Center.X – mesh.BoundingSphere.Radius, mesh.BoundingSphere.Center.X + mesh.BoundingSphere.Radius, subdivision.X);
            float y = MathHelper.Lerp(mesh.BoundingSphere.Center.Y – mesh.BoundingSphere.Radius, mesh.BoundingSphere.Center.Y + mesh.BoundingSphere.Radius, subdivision.Y);
            float z = MathHelper.Lerp(mesh.BoundingSphere.Center.Z – mesh.BoundingSphere.Radius, mesh.BoundingSphere.Center.Z + mesh.BoundingSphere.Radius, subdivision.Z);
            Vector3 centre = new Vector3(x, y, z);

            pieces[pieceIndex] = new BoundingSphere(centre, radius);

            pieceIndex++;
        }
        boundingPart = new BoundingPart(mesh.BoundingSphere, pieces, _modelBoneTransforms[mesh.ParentBone.Index], _modelBoundingPartNames[meshIndex], _modelBoundingPartColours[meshIndex]);

        _modelBoundingParts.Add(boundingPart);
        meshIndex++;
    }
 }

※Heightlight版
我會在之後解釋BoundingPart類別。現在你只需要知道他是一個外覆類別(wrapper class),它擁有數個BoundingSphere物件。………..

它是這樣運作的…

我會走訪Model裡面的每個ModelMesh。

使用它的索引通過_modelBoundingPartSubdivisions陣列裡,對映的元素。
(這裡不確定怎麼翻…)

他會給我一個Vector4,利用XNA的MathHelper.Lerp() (線性內插)方法和Vector4的X、Y、Z和W,建構新BoundingSphere的中心點和半徑。

我愛Lerp。Lerp比起The Fonz酷多了!基本上它可以讓你要求「給我一個介於100 & 200之間70%的值」。

0% 是 100。
100% 是 200。
所以70%就是170。

在這裡,我使用XNA的「預設」BoundingSphere半徑做為範圍的規範。

例如,最小值為Center.X – Radius。最大值為Center.X + Radius。所以我將這些值對subdivision的X做Lerp。

這個BoundingPart類別可以取得適合的BoundingSphere,並且將它們加入主要的集合中。

OK!所以現在我有一個Winodws Live Writer的技術困難。我想教學太長了些,所以我先在這裡停下來,在Part3繼續…


天…
這篇真有夠長的
重點是我還因為不小心撞到滑鼠上的邊緣鍵(可以用來快速讓網頁切換上下頁)而導致我得整個重打…
那時候已經打到80%了說…
整個超無力…所以拖到現在才打完
先這樣啦~

–End

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google photo

您的留言將使用 Google 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.