vectors_advanced.rst 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. .. _doc_vectors_advanced:
  2. Advanced vector math
  3. ====================
  4. Planes
  5. ~~~~~~
  6. The dot product has another interesting property with unit vectors.
  7. Imagine that perpendicular to that vector (and through the origin)
  8. passes a plane. Planes divide the entire space into positive
  9. (over the plane) and negative (under the plane), and (contrary to
  10. popular belief) you can also use their math in 2D:
  11. .. image:: img/tutovec10.png
  12. Unit vectors that are perpendicular to a surface (so, they describe the
  13. orientation of the surface) are called **unit normal vectors**. Though,
  14. usually they are just abbreviated as *normals*. Normals appear in
  15. planes, 3D geometry (to determine where each face or vertex is siding),
  16. etc. A **normal** *is* a **unit vector**, but it's called *normal*
  17. because of its usage. (Just like we call (0,0) the Origin!).
  18. It's as simple as it looks. The plane passes by the origin and the
  19. surface of it is perpendicular to the unit vector (or *normal*). The
  20. side towards the vector points to is the positive half-space, while the
  21. other side is the negative half-space. In 3D this is exactly the same,
  22. except that the plane is an infinite surface (imagine an infinite, flat
  23. sheet of paper that you can orient and is pinned to the origin) instead
  24. of a line.
  25. Distance to plane
  26. -----------------
  27. Now that it's clear what a plane is, let's go back to the dot product.
  28. The dot product between a **unit vector** and any **point in space**
  29. (yes, this time we do dot product between vector and position), returns
  30. the **distance from the point to the plane**:
  31. .. tabs::
  32. .. code-tab:: gdscript GDScript
  33. var distance = normal.dot(point)
  34. .. code-tab:: csharp
  35. var distance = normal.Dot(point);
  36. But not just the absolute distance, if the point is in the negative half
  37. space the distance will be negative, too:
  38. .. image:: img/tutovec11.png
  39. This allows us to tell which side of the plane a point is.
  40. Away from the origin
  41. --------------------
  42. I know what you are thinking! So far this is nice, but *real* planes are
  43. everywhere in space, not only passing through the origin. You want real
  44. *plane* action and you want it *now*.
  45. Remember that planes not only split space in two, but they also have
  46. *polarity*. This means that it is possible to have perfectly overlapping
  47. planes, but their negative and positive half-spaces are swapped.
  48. With this in mind, let's describe a full plane as a **normal** *N* and a
  49. **distance from the origin** scalar *D*. Thus, our plane is represented
  50. by N and D. For example:
  51. .. image:: img/tutovec12.png
  52. For 3D math, Godot provides a :ref:`Plane <class_Plane>`
  53. built-in type that handles this.
  54. Basically, N and D can represent any plane in space, be it for 2D or 3D
  55. (depending on the amount of dimensions of N) and the math is the same
  56. for both. It's the same as before, but D is the distance from the origin
  57. to the plane, travelling in N direction. As an example, imagine you want
  58. to reach a point in the plane, you will just do:
  59. .. tabs::
  60. .. code-tab:: gdscript GDScript
  61. var point_in_plane = N*D
  62. .. code-tab:: csharp
  63. var pointInPlane = N * D;
  64. This will stretch (resize) the normal vector and make it touch the
  65. plane. This math might seem confusing, but it's actually much simpler
  66. than it seems. If we want to tell, again, the distance from the point to
  67. the plane, we do the same but adjusting for distance:
  68. .. tabs::
  69. .. code-tab:: gdscript GDScript
  70. var distance = N.dot(point) - D
  71. .. code-tab:: csharp
  72. var distance = N.Dot(point) - D;
  73. The same thing, using a built-in function:
  74. .. tabs::
  75. .. code-tab:: gdscript GDScript
  76. var distance = plane.distance_to(point)
  77. .. code-tab:: csharp
  78. var distance = plane.DistanceTo(point);
  79. This will, again, return either a positive or negative distance.
  80. Flipping the polarity of the plane can be done by negating both
  81. N and D. This will result in a plane in the same position, but with
  82. inverted negative and positive half spaces:
  83. .. tabs::
  84. .. code-tab:: gdscript GDScript
  85. N = -N
  86. D = -D
  87. .. code-tab:: csharp
  88. N = -N;
  89. D = -D;
  90. Of course, Godot also implements this operator in :ref:`Plane <class_Plane>`,
  91. so doing:
  92. .. tabs::
  93. .. code-tab:: gdscript GDScript
  94. var inverted_plane = -plane
  95. .. code-tab:: csharp
  96. var invertedPlane = -plane;
  97. Will work as expected.
  98. So, remember, a plane is just that and its main practical use is
  99. calculating the distance to it. So, why is it useful to calculate the
  100. distance from a point to a plane? It's extremely useful! Let's see some
  101. simple examples..
  102. Constructing a plane in 2D
  103. --------------------------
  104. Planes clearly don't come out of nowhere, so they must be built.
  105. Constructing them in 2D is easy, this can be done from either a normal
  106. (unit vector) and a point, or from two points in space.
  107. In the case of a normal and a point, most of the work is done, as the
  108. normal is already computed, so just calculate D from the dot product of
  109. the normal and the point.
  110. .. tabs::
  111. .. code-tab:: gdscript GDScript
  112. var N = normal
  113. var D = normal.dot(point)
  114. .. code-tab:: csharp
  115. var N = normal;
  116. var D = normal.Dot(point);
  117. For two points in space, there are actually two planes that pass through
  118. them, sharing the same space but with normal pointing to the opposite
  119. directions. To compute the normal from the two points, the direction
  120. vector must be obtained first, and then it needs to be rotated 90°
  121. degrees to either side:
  122. .. tabs::
  123. .. code-tab:: gdscript GDScript
  124. # Calculate vector from `a` to `b`.
  125. var dvec = (point_b - point_a).normalized()
  126. # Rotate 90 degrees.
  127. var normal = Vector2(dvec.y, -dvec.x)
  128. # Alternatively (depending the desired side of the normal):
  129. # var normal = Vector2(-dvec.y, dvec.x)
  130. .. code-tab:: csharp
  131. // Calculate vector from `a` to `b`.
  132. var dvec = (pointB - pointA).Normalized();
  133. // Rotate 90 degrees.
  134. var normal = new Vector2(dvec.y, -dvec.x);
  135. // Alternatively (depending the desired side of the normal):
  136. // var normal = new Vector2(-dvec.y, dvec.x);
  137. The rest is the same as the previous example, either point_a or
  138. point_b will work since they are in the same plane:
  139. .. tabs::
  140. .. code-tab:: gdscript GDScript
  141. var N = normal
  142. var D = normal.dot(point_a)
  143. # this works the same
  144. # var D = normal.dot(point_b)
  145. .. code-tab:: csharp
  146. var N = normal;
  147. var D = normal.Dot(pointA);
  148. // this works the same
  149. // var D = normal.Dot(pointB);
  150. Doing the same in 3D is a little more complex and will be explained
  151. further down.
  152. Some examples of planes
  153. -----------------------
  154. Here is a simple example of what planes are useful for. Imagine you have
  155. a `convex <https://www.mathsisfun.com/definitions/convex.html>`__
  156. polygon. For example, a rectangle, a trapezoid, a triangle, or just any
  157. polygon where no faces bend inwards.
  158. For every segment of the polygon, we compute the plane that passes by
  159. that segment. Once we have the list of planes, we can do neat things,
  160. for example checking if a point is inside the polygon.
  161. We go through all planes, if we can find a plane where the distance to
  162. the point is positive, then the point is outside the polygon. If we
  163. can't, then the point is inside.
  164. .. image:: img/tutovec13.png
  165. Code should be something like this:
  166. .. tabs::
  167. .. code-tab:: gdscript GDScript
  168. var inside = true
  169. for p in planes:
  170. # check if distance to plane is positive
  171. if (p.distance_to(point) > 0):
  172. inside = false
  173. break # with one that fails, it's enough
  174. .. code-tab:: csharp
  175. var inside = true;
  176. foreach (var p in planes)
  177. {
  178. // check if distance to plane is positive
  179. if (p.DistanceTo(point) > 0)
  180. {
  181. inside = false;
  182. break; // with one that fails, it's enough
  183. }
  184. }
  185. Pretty cool, huh? But this gets much better! With a little more effort,
  186. similar logic will let us know when two convex polygons are overlapping
  187. too. This is called the Separating Axis Theorem (or SAT) and most
  188. physics engines use this to detect collision.
  189. With a point, just checking if a plane
  190. returns a positive distance is enough to tell if the point is outside.
  191. With another polygon, we must find a plane where *all* *the* *other*
  192. *polygon* *points* return a positive distance to it. This check is
  193. performed with the planes of A against the points of B, and then with
  194. the planes of B against the points of A:
  195. .. image:: img/tutovec14.png
  196. Code should be something like this:
  197. .. tabs::
  198. .. code-tab:: gdscript GDScript
  199. var overlapping = true
  200. for p in planes_of_A:
  201. var all_out = true
  202. for v in points_of_B:
  203. if (p.distance_to(v) < 0):
  204. all_out = false
  205. break
  206. if (all_out):
  207. # a separating plane was found
  208. # do not continue testing
  209. overlapping = false
  210. break
  211. if (overlapping):
  212. # only do this check if no separating plane
  213. # was found in planes of A
  214. for p in planes_of_B:
  215. var all_out = true
  216. for v in points_of_A:
  217. if (p.distance_to(v) < 0):
  218. all_out = false
  219. break
  220. if (all_out):
  221. overlapping = false
  222. break
  223. if (overlapping):
  224. print("Polygons Collided!")
  225. .. code-tab:: csharp
  226. var overlapping = true;
  227. foreach (Plane plane in planesOfA)
  228. {
  229. var allOut = true;
  230. foreach (Vector3 point in pointsOfB)
  231. {
  232. if (plane.DistanceTo(point) < 0)
  233. {
  234. allOut = false;
  235. break;
  236. }
  237. }
  238. if (allOut)
  239. {
  240. // a separating plane was found
  241. // do not continue testing
  242. overlapping = false;
  243. break;
  244. }
  245. }
  246. if (overlapping)
  247. {
  248. // only do this check if no separating plane
  249. // was found in planes of A
  250. foreach (Plane plane in planesOfB)
  251. {
  252. var allOut = true;
  253. foreach (Vector3 point in pointsOfA)
  254. {
  255. if (plane.DistanceTo(point) < 0)
  256. {
  257. allOut = false;
  258. break;
  259. }
  260. }
  261. if (allOut)
  262. {
  263. overlapping = false;
  264. break;
  265. }
  266. }
  267. }
  268. if (overlapping)
  269. {
  270. GD.Print("Polygons Collided!");
  271. }
  272. As you can see, planes are quite useful, and this is the tip of the
  273. iceberg. You might be wondering what happens with non convex polygons.
  274. This is usually just handled by splitting the concave polygon into
  275. smaller convex polygons, or using a technique such as BSP (which is not
  276. used much nowadays).
  277. Collision detection in 3D
  278. ~~~~~~~~~~~~~~~~~~~~~~~~~
  279. This is another bonus bit, a reward for being patient and keeping up
  280. with this long tutorial. Here is another piece of wisdom. This might
  281. not be something with a direct use case (Godot already does collision
  282. detection pretty well) but it's used by almost all physics engines and collision
  283. detection libraries :)
  284. Remember that converting a convex shape in 2D to an array of 2D planes
  285. was useful for collision detection? You could detect if a point was
  286. inside any convex shape, or if two 2D convex shapes were overlapping.
  287. Well, this works in 3D too, if two 3D polyhedral shapes are colliding,
  288. you won't be able to find a separating plane. If a separating plane is
  289. found, then the shapes are definitely not colliding.
  290. To refresh a bit a separating plane means that all vertices of polygon A
  291. are in one side of the plane, and all vertices of polygon B are in the
  292. other side. This plane is always one of the face-planes of either
  293. polygon A or polygon B.
  294. In 3D though, there is a problem to this approach, because it is
  295. possible that, in some cases a separating plane can't be found. This is
  296. an example of such situation:
  297. .. image:: img/tutovec22.png
  298. To avoid it, some extra planes need to be tested as separators, these
  299. planes are the cross product between the edges of polygon A and the
  300. edges of polygon B
  301. .. image:: img/tutovec23.png
  302. So the final algorithm is something like:
  303. .. tabs::
  304. .. code-tab:: gdscript GDScript
  305. var overlapping = true
  306. for p in planes_of_A:
  307. var all_out = true
  308. for v in points_of_B:
  309. if (p.distance_to(v) < 0):
  310. all_out = false
  311. break
  312. if (all_out):
  313. # a separating plane was found
  314. # do not continue testing
  315. overlapping = false
  316. break
  317. if (overlapping):
  318. # only do this check if no separating plane
  319. # was found in planes of A
  320. for p in planes_of_B:
  321. var all_out = true
  322. for v in points_of_A:
  323. if (p.distance_to(v) < 0):
  324. all_out = false
  325. break
  326. if (all_out):
  327. overlapping = false
  328. break
  329. if (overlapping):
  330. for ea in edges_of_A:
  331. for eb in edges_of_B:
  332. var n = ea.cross(eb)
  333. if (n.length() == 0):
  334. continue
  335. var max_A = -1e20 # tiny number
  336. var min_A = 1e20 # huge number
  337. # we are using the dot product directly
  338. # so we can map a maximum and minimum range
  339. # for each polygon, then check if they
  340. # overlap.
  341. for v in points_of_A:
  342. var d = n.dot(v)
  343. max_A = max(max_A, d)
  344. min_A = min(min_A, d)
  345. var max_B = -1e20 # tiny number
  346. var min_B = 1e20 # huge number
  347. for v in points_of_B:
  348. var d = n.dot(v)
  349. max_B = max(max_B, d)
  350. min_B = min(min_B, d)
  351. if (min_A > max_B or min_B > max_A):
  352. # not overlapping!
  353. overlapping = false
  354. break
  355. if (not overlapping):
  356. break
  357. if (overlapping):
  358. print("Polygons collided!")
  359. .. code-tab:: csharp
  360. var overlapping = true;
  361. foreach (Plane plane in planesOfA)
  362. {
  363. var allOut = true;
  364. foreach (Vector3 point in pointsOfB)
  365. {
  366. if (plane.DistanceTo(point) < 0)
  367. {
  368. allOut = false;
  369. break;
  370. }
  371. }
  372. if (allOut)
  373. {
  374. // a separating plane was found
  375. // do not continue testing
  376. overlapping = false;
  377. break;
  378. }
  379. }
  380. if (overlapping)
  381. {
  382. // only do this check if no separating plane
  383. // was found in planes of A
  384. foreach (Plane plane in planesOfB)
  385. {
  386. var allOut = true;
  387. foreach (Vector3 point in pointsOfA)
  388. {
  389. if (plane.DistanceTo(point) < 0)
  390. {
  391. allOut = false;
  392. break;
  393. }
  394. }
  395. if (allOut)
  396. {
  397. overlapping = false;
  398. break;
  399. }
  400. }
  401. }
  402. if (overlapping)
  403. {
  404. foreach (Vector3 edgeA in edgesOfA)
  405. {
  406. foreach (Vector3 edgeB in edgesOfB)
  407. {
  408. var normal = edgeA.Cross(edgeB);
  409. if (normal.Length() == 0)
  410. {
  411. continue;
  412. }
  413. var maxA = float.MinValue; // tiny number
  414. var minA = float.MaxValue; // huge number
  415. // we are using the dot product directly
  416. // so we can map a maximum and minimum range
  417. // for each polygon, then check if they
  418. // overlap.
  419. foreach (Vector3 point in pointsOfA)
  420. {
  421. var distance = normal.Dot(point);
  422. maxA = Mathf.Max(maxA, distance);
  423. minA = Mathf.Min(minA, distance);
  424. }
  425. var maxB = float.MinValue; // tiny number
  426. var minB = float.MaxValue; // huge number
  427. foreach (Vector3 point in pointsOfB)
  428. {
  429. var distance = normal.Dot(point);
  430. maxB = Mathf.Max(maxB, distance);
  431. minB = Mathf.Min(minB, distance);
  432. }
  433. if (minA > maxB || minB > maxA)
  434. {
  435. // not overlapping!
  436. overlapping = false;
  437. break;
  438. }
  439. }
  440. if (!overlapping)
  441. {
  442. break;
  443. }
  444. }
  445. }
  446. if (overlapping)
  447. {
  448. GD.Print("Polygons Collided!");
  449. }
  450. More information
  451. ~~~~~~~~~~~~~~~~
  452. For more information on using vector math in Godot, see the following article:
  453. - :ref:`doc_matrices_and_transforms`
  454. If you would like additional explanation, you should check out
  455. 3Blue1Brown's excellent video series "Essence of Linear Algebra":
  456. https://www.youtube.com/watch?v=fNk_zzaMoSs&list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab