游戲開發中的進階向量數學
游戲開發中的進階向量數學
飛機
到飛機的距離
遠離原點
以2D方式構建平面
飛機的一些例子
3D碰撞檢測
更多信息
飛機
點積具有帶有單位向量的另一個有趣的屬性。想象一下,垂直于該矢量(并通過原點)的平面通過了一個平面。平面將整個空間分為正數(在平面上)和負數(在平面下),并且(與流行的看法相反),您還可以在2D中使用其數學運算:
垂直于曲面的單位向量(因此,它們描述了曲面的方向)稱為單位法向向量。雖然,通常他們只是簡稱為法線。法線出現在飛機,3D幾何(以確定其中每一個面或頂點板壁)等。通常 是一個單位矢量,但它被稱為正常 ,因為它的用法。(就像我們將(0,0)稱為原點)。
看起來很簡單。平面經過原點,并且其表面垂直于單位矢量(或法線)。指向向量的一側為正半空間,而另一側為負半空間。在3D中,這是完全相同的,除了平面是一個無限的表面(想象一個可以定向并固定到原點的無限的平紙)而不是一條線。
到飛機的距離
現在很清楚飛機是什么,讓我們回到點積。單位矢量和空間中任何點之間的點積 (是的,這次我們進行矢量和位置之間的點積),返回從點到平面的距離:
var distance = normal.Dot(point);
1
但是不僅是絕對距離,如果點在負半空間中,則距離也將為負:
這使我們能夠知道一個點在平面的哪一側。
遠離原點
我知道你在想什么!到目前為止,這還不錯,但是真實的飛機在空間中無處不在,不僅經過原點。你想真正的飛機的行動,你想它現在。
請記住,平面不僅將空間分成兩部分,而且還具有極性。這意味著可以有完全重疊的平面,但是它們的負半空間和正半空間會互換。
考慮到這一點,讓我們將整個平面描述為法線 N和距原點標量D的 距離。因此,我們的平面由N和D表示。例如:
對于3D數學,Godot提供了Plane 內置類型來處理。
基本上,N和D可以表示空間中的任何平面,無論是2D還是3D(取決于N的維數),并且兩者的數學公式相同。與以前相同,但是D是從原點到平面的距離,沿N方向行進。例如,假設您想到達飛機上的某個點,您將執行以下操作:
var pointInPlane = N * D;
這將拉伸(調整大小)法線向量并使之接觸平面。這個數學運算看起來似乎很混亂,但是實際上比看起來要簡單得多。如果我們想再次說出點到平面的距離,我們可以做同樣的事情,只是要調整距離:
var distance = N.Dot(point) - D;
1
使用內置函數的相同操作:
var distance = plane.DistanceTo(point);
1
這將再次返回正或負距離。
可以通過使N和D都為負值來翻轉平面的極性。這將導致平面處于相同的位置,但是具有負半角和正半角的反轉:
N = -N; D = -D;
1
2
當然,Godot也可以在Plane中實現此運算符,因此請執行以下操作:
var invertedPlane = -plane;
1
將按預期工作。
因此,請記住,飛機就是這樣,它的主要實際用途是計算到它的距離。那么,為什么計算點到平面的距離有用呢?這非常有用!讓我們看一些簡單的例子。
以2D方式構建平面
平面顯然不會從任何地方冒出來,因此必須進行構建。以2D方式構建它們很容易,可以從法線(單位矢量)和一個點,也可以從空間中的兩個點完成。
對于法線和點,由于已經計算了法線,因此大部分工作都已完成,因此只需根據法線和點的點積計算D。
var N = normal; var D = normal.Dot(point);
1
2
對于空間中的兩個點,實際上有兩個平面穿過它們,它們共享相同的空間,但法線指向相反的方向。要從兩點計算法線,必須首先獲取方向矢量,然后將其向任一側旋轉90°度:
// Calculate vector from `a` to `b`. var dvec = (pointB - pointA).Normalized(); // Rotate 90 degrees. var normal = new Vector2(dvec.y, -dvec.x); // Alternatively (depending the desired side of the normal): // var normal = new Vector2(-dvec.y, dvec.x);
1
2
3
4
5
6
其余部分與前面的示例相同,因為point_a或point_b都在同一平面上,所以它們都可以工作:
var N = normal; var D = normal.Dot(pointA); // this works the same // var D = normal.Dot(pointB);
1
2
3
4
在3D模式下執行相同的操作會稍微復雜一些,將在后面進行詳細說明。
飛機的一些例子
這是平面有用的簡單示例。假設您有一個凸 多邊形。例如,矩形,梯形,三角形或沒有面向內彎曲的任何多邊形。
對于多邊形的每個片段,我們都會計算經過該片段的平面。一旦有了平面列表,我們就可以做整齊的事情,例如檢查點是否在多邊形內。
我們遍歷所有平面,如果可以找到到該點的距離為正的平面,則該點在多邊形之外。如果我們做不到,那么重點就在里面。
代碼應該是這樣的:
var inside = true; foreach (var p in planes) { // check if distance to plane is positive if (p.DistanceTo(point) > 0) { inside = false; break; // with one that fails, it's enough } }
1
2
3
4
5
6
7
8
9
10
太酷了吧?但這會變得更好!稍加努力,當兩個凸多邊形也重疊時,類似的邏輯就會讓我們知道。這稱為分離軸定理(或SAT),大多數物理引擎都使用它來檢測碰撞。
對于一個點,僅檢查飛機是否返回正距離就足以確定該點是否在外面。對于另一個多邊形,我們必須找到一個平面,在該平面上所有 其他多邊形點都將 返回一個正距離。該檢查是使用A的平面相對于B的點進行的,然后使用B的平面相對于A的點進行的:
代碼應該是這樣的:
var overlapping = true; foreach (Plane plane in planesOfA) { var allOut = true; foreach (Vector3 point in pointsOfB) { if (plane.DistanceTo(point) < 0) { allOut = false; break; } } if (allOut) { // a separating plane was found // do not continue testing overlapping = false; break; } } if (overlapping) { // only do this check if no separating plane // was found in planes of A foreach (Plane plane in planesOfB) { var allOut = true; foreach (Vector3 point in pointsOfA) { if (plane.DistanceTo(point) < 0) { allOut = false; break; } } if (allOut) { overlapping = false; break; } } } if (overlapping) { GD.Print("Polygons Collided!"); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
如您所見,飛機非常有用,這是冰山一角。您可能想知道非凸多邊形會發生什么。通常可以通過將凹面多邊形拆分為較小的凸面多邊形,或使用諸如BSP(如今已不多使用)之類的技術來處理。
3D碰撞檢測
這是另外一個獎勵,是對耐心和遵守本篇教程的獎勵。這是另一個智慧。這可能不是直接用例(Godot已經很好地進行了碰撞檢測),但是幾乎所有物理引擎和碰撞檢測庫都在使用它:)
還記得將2D中的凸形轉換為2D平面數組對于碰撞檢測很有用嗎?您可以檢測點是否在任何凸形形狀內,或者兩個2D凸形形狀是否重疊。
好吧,這也適用于3D,如果兩個3D多面體形狀發生碰撞,您將無法找到分離平面。如果找到分離平面,則形狀絕對不會碰撞。
要稍微刷新一點,一個分離平面意味著多邊形A的所有頂點都在該平面的一側,而多邊形B的所有頂點都在另一側。該平面始終是面A或面B的端面之一。
但是在3D中,這種方法存在問題,因為在某些情況下可能找不到分離平面。這是這種情況的一個示例:
為了避免這種情況,需要測試一些額外的平面作為分隔符,這些平面是面A的邊與面B的邊之間的叉積。
所以最終的算法是這樣的:
var overlapping = true; foreach (Plane plane in planesOfA) { var allOut = true; foreach (Vector3 point in pointsOfB) { if (plane.DistanceTo(point) < 0) { allOut = false; break; } } if (allOut) { // a separating plane was found // do not continue testing overlapping = false; break; } } if (overlapping) { // only do this check if no separating plane // was found in planes of A foreach (Plane plane in planesOfB) { var allOut = true; foreach (Vector3 point in pointsOfA) { if (plane.DistanceTo(point) < 0) { allOut = false; break; } } if (allOut) { overlapping = false; break; } } } if (overlapping) { foreach (Vector3 edgeA in edgesOfA) { foreach (Vector3 edgeB in edgesOfB) { var normal = edgeA.Cross(edgeB); if (normal.Length() == 0) { continue; } var maxA = float.MinValue; // tiny number var minA = float.MaxValue; // huge number // we are using the dot product directly // so we can map a maximum and minimum range // for each polygon, then check if they // overlap. foreach (Vector3 point in pointsOfA) { var distance = normal.Dot(point); maxA = Mathf.Max(maxA, distance); minA = Mathf.Min(minA, distance); } var maxB = float.MinValue; // tiny number var minB = float.MaxValue; // huge number foreach (Vector3 point in pointsOfB) { var distance = normal.Dot(point); maxB = Mathf.Max(maxB, distance); minB = Mathf.Min(minB, distance); } if (minA > maxB || minB > maxA) { // not overlapping! overlapping = false; break; } } if (!overlapping) { break; } } } if (overlapping) { GD.Print("Polygons Collided!"); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
更多信息
有關在Godot中使用向量數學的更多信息,請參見以下文章:
矩陣與變換
如果您需要其他說明,請查看3Blue1Brown的精彩視頻系列“線性代數的本質”:https://www.youtube.com/watch?v=fNk_zzaMoSs&list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab
5G游戲
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。