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

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

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