什么是低代码?低代码开发平台靠谱吗?低代码平台优缺点
563
2022-05-30
游戏开发中的进阶向量数学
飞机
到飞机的距离
远离原点
以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小时内删除侵权内容。