游戏开发中的进阶向量数学

网友投稿 579 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小时内删除侵权内容。

上一篇:Linux系统编程-(pthread)线程创建与使用
下一篇:Docker核心技术之容器详解
相关文章