matrices_and_transforms.rst 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. .. _doc_matrices_and_transforms:
  2. Matrices and transforms
  3. =======================
  4. Introduction
  5. ------------
  6. Before reading this tutorial, it is advised to read the previous one
  7. about :ref:`doc_vector_math` as this one is a direct continuation.
  8. This tutorial will be about *transformations* and will cover a little
  9. about matrices (but not in-depth).
  10. Transformations are most of the time applied as translation, rotation
  11. and scale so they will be considered as priority here.
  12. Oriented coordinate system (OCS)
  13. --------------------------------
  14. Imagine we have a spaceship somewhere in space. In Godot this is easy,
  15. just move the ship somewhere and rotate it:
  16. .. image:: img/tutomat1.png
  17. Ok, so in 2D this looks simple, a position and an angle for a rotation.
  18. But remember, we are grown ups here and don't use angles (plus, angles
  19. are not really even that useful when working in 3D).
  20. We should realize that at some point, someone *designed* this
  21. spaceship. Be it for 2D in a drawing such as Paint.net, Gimp,
  22. Photoshop, etc. or in 3D through a 3D DCC tool such as Blender, Max,
  23. Maya, etc.
  24. When it was designed, it was not rotated. It was designed in its own
  25. *coordinate system*.
  26. .. image:: img/tutomat2.png
  27. This means that the tip of the ship has a coordinate, the fin has
  28. another, etc. Be it in pixels (2D) or vertices (3D).
  29. So, let's recall again that the ship was somewhere in space:
  30. .. image:: img/tutomat3.png
  31. How did it get there? What moved it and rotated it from the place it was
  32. designed to its current position? The answer is... a **transform**, the
  33. ship was *transformed* from their original position to the new one. This
  34. allows the ship to be displayed where it is.
  35. But transform is too generic of a term to describe this process. To solve this
  36. puzzle, we will superimpose the ship's original design position at their
  37. current position:
  38. .. image:: img/tutomat4.png
  39. So, we can see that the "design space" has been transformed too. How can
  40. we best represent this transformation? Let's use 3 vectors for this (in
  41. 2D), a unit vector pointing towards X positive, a unit vector pointing
  42. towards Y positive and a translation.
  43. .. image:: img/tutomat5.png
  44. Let's call the 3 vectors "X", "Y" and "Origin", and let's also
  45. superimpose them over the ship so it makes more sense:
  46. .. image:: img/tutomat6.png
  47. Ok, this is nicer, but it still does not make sense. What do X,Y and
  48. Origin have to do with how the ship got there?
  49. Well, let's take the point from top tip of the ship as reference:
  50. .. image:: img/tutomat7.png
  51. And let's apply the following operation to it (and to all the points in
  52. the ship too, but we'll track the top tip as our reference point):
  53. .. tabs::
  54. .. code-tab:: gdscript GDScript
  55. var new_pos = pos - origin
  56. .. code-tab:: csharp
  57. var newPosition = pos - origin;
  58. Doing this to the selected point will move it back to the center:
  59. .. image:: img/tutomat8.png
  60. This was expected, but then let's do something more interesting. Use the
  61. dot product of X and the point, and add it to the dot product of Y and
  62. the point:
  63. .. tabs::
  64. .. code-tab:: gdscript GDScript
  65. var final_pos = Vector2(x.dot(new_pos), y.dot(new_pos))
  66. .. code-tab:: csharp
  67. var finalPosition = new Vector2(x.Dot(newPosition), y.Dot(newPosition));
  68. Then what we have is.. wait a minute, it's the ship in its design
  69. position!
  70. .. image:: img/tutomat9.png
  71. How did this black magic happen? The ship was lost in space, and now
  72. it's back home!
  73. It might seem strange, but it does have plenty of logic. Remember, as
  74. we have seen in the :ref:`doc_vector_math`, what
  75. happened is that the distance to X axis, and the distance to Y axis
  76. were computed. Calculating distance in a direction or plane was one of
  77. the uses for the dot product. This was enough to obtain back the
  78. design coordinates for every point in the ship.
  79. So, what we have been working with so far (with X, Y and Origin) is an
  80. *Oriented Coordinate System*. X an Y are the **Basis**, and *Origin*
  81. is the offset.
  82. Basis
  83. -----
  84. We know what the Origin is. It's where the 0,0 (origin) of the design
  85. coordinate system ended up after being transformed to a new position.
  86. This is why it's called *Origin*, But in practice, it's just an offset
  87. to the new position.
  88. The Basis is more interesting. The basis is the direction of X and Y in the OCS
  89. from the new, transformed location. It tells what has changed, in either 2D or
  90. 3D. The Origin (offset) and Basis (direction) communicate "Hey, the original X
  91. and Y axes of your design are *right here*, pointing towards *these
  92. directions*."
  93. So, let's change the representation of the basis. Instead of 2 vectors,
  94. let's use a *matrix*.
  95. .. image:: img/tutomat10.png
  96. The vectors are up there in the matrix, horizontally. The next problem
  97. now is that.. what is this matrix thing? Well, we'll assume you've never
  98. heard of a matrix.
  99. Transforms in Godot
  100. -------------------
  101. This tutorial will not explain matrix math (and their operations) in
  102. depth, only its practical use. There is plenty of material for that,
  103. which should be a lot simpler to understand after completing this
  104. tutorial. We'll just explain how to use transforms.
  105. Transform2D
  106. -----------
  107. :ref:`class_Transform2D` is a 3x2 matrix. It has 3 Vector2 elements and
  108. it's used for 2D. The "X" axis is the element 0, "Y" axis is the element 1 and
  109. "Origin" is element 2. It's not divided in basis/origin for convenience, due to
  110. its simplicity.
  111. .. tabs::
  112. .. code-tab:: gdscript GDScript
  113. var m = Transform2D()
  114. var x = m[0] # 'X'
  115. var y = m[1] # 'Y'
  116. var o = m[2] # 'Origin'
  117. .. code-tab:: csharp
  118. var m = new Transform2D();
  119. Vector2 x = m[0]; // 'X'
  120. Vector2 y = m[1]; // 'Y'
  121. Vector2 o = m[2]; // 'Origin'
  122. Most operations will be explained with this datatype (Transform2D), but the
  123. same logic applies to 3D.
  124. Identity
  125. --------
  126. By default, Transform2D is created as an "identity" matrix. This means:
  127. - 'X' Points right: Vector2(1,0)
  128. - 'Y' Points up (or down in pixels): Vector2(0,1)
  129. - 'Origin' is the origin Vector2(0,0)
  130. .. image:: img/tutomat11.png
  131. It's easy to guess that an *identity* matrix is just a matrix that
  132. aligns the transform to its parent coordinate system. It's an *OCS*
  133. that hasn't been translated, rotated or scaled. All transform types in
  134. Godot are created with *identity*.
  135. Operations
  136. ----------
  137. Rotation
  138. --------
  139. Rotating Transform2D is done by using the "rotated" function:
  140. .. tabs::
  141. .. code-tab:: gdscript GDScript
  142. var m = Transform2D()
  143. m = m.rotated(PI/2) # rotate 90°
  144. .. code-tab:: csharp
  145. var m = new Transform2D();
  146. m = m.Rotated(Mathf.PI / 2); // rotate 90°
  147. .. image:: img/tutomat12.png
  148. Translation
  149. -----------
  150. There are two ways to translate a Transform2D, the first one is just moving
  151. the origin:
  152. .. tabs::
  153. .. code-tab:: gdscript GDScript
  154. # Move 2 units to the right
  155. var m = Transform2D()
  156. m = m.rotated(PI/2) # rotate 90°
  157. m[2] += Vector2(2,0)
  158. .. code-tab:: csharp
  159. // Move 2 units to the right
  160. var m = new Transform2D();
  161. m = m.Rotated(Mathf.PI / 2); // rotate 90°
  162. m[2] += new Vector2(2, 0);
  163. .. image:: img/tutomat13.png
  164. This will always work in global coordinates.
  165. If instead, translation is desired in *local* coordinates of the
  166. matrix (towards where the *basis* is oriented), there is the
  167. :ref:`Transform2D.translated() <class_Transform2D_translated>`
  168. method:
  169. .. tabs::
  170. .. code-tab:: gdscript GDScript
  171. # Move 2 units towards where the basis is oriented
  172. var m = Transform2D()
  173. m = m.rotated(PI/2) # rotate 90°
  174. m = m.translated( Vector2(2,0) )
  175. .. code-tab:: csharp
  176. // Move 2 units towards where the basis is oriented
  177. var m = new Transform2D();
  178. m = m.Rotated(Mathf.PI / 2); // rotate 90°
  179. m = m.Translated(new Vector2(2, 0));
  180. .. image:: img/tutomat14.png
  181. You could also transform the global coordinates to local coordinates manually:
  182. .. tabs::
  183. .. code-tab:: gdscript GDScript
  184. var local_pos = m.xform_inv(point)
  185. .. code-tab:: csharp
  186. var localPosition = m.XformInv(point);
  187. But even better, there are helper functions for this as you can read in the next sections.
  188. Local to global coordinates and vice versa
  189. ------------------------------------------
  190. There are helper methods for converting between local and global coordinates.
  191. There are :ref:`Node2D.to_local() <class_Node2D_to_local>` and :ref:`Node2D.to_global() <class_Node2D_to_global>` for 2D
  192. as well as :ref:`Spatial.to_local() <class_Spatial_to_local>` and :ref:`Spatial.to_global() <class_Spatial_to_global>` for 3D.
  193. Scale
  194. -----
  195. A matrix can be scaled too. Scaling will multiply the basis vectors by a
  196. vector (X vector by x component of the scale, Y vector by y component of
  197. the scale). It will leave the origin alone:
  198. .. tabs::
  199. .. code-tab:: gdscript GDScript
  200. # Make the basis twice its size.
  201. var m = Transform2D()
  202. m = m.scaled( Vector2(2,2) )
  203. .. code-tab:: csharp
  204. // Make the basis twice its size.
  205. var m = new Transform2D();
  206. m = m.Scaled(new Vector2(2, 2));
  207. .. image:: img/tutomat15.png
  208. These kind of operations in matrices are accumulative. It means every
  209. one starts relative to the previous one. For those who have been living
  210. on this planet long enough, a good reference of how transform works is
  211. this:
  212. .. image:: img/tutomat16.png
  213. A matrix is used similarly to a turtle. The turtle most likely had a
  214. matrix inside (and you are likely learning this many years *after*
  215. discovering Santa is not real).
  216. Transform
  217. ---------
  218. Transform is the act of switching between coordinate systems. To convert
  219. a position (either 2D or 3D) from "designer" coordinate system to the
  220. OCS, the "xform" method is used.
  221. .. tabs::
  222. .. code-tab:: gdscript GDScript
  223. var new_pos = m.xform(pos)
  224. .. code-tab:: csharp
  225. var newPosition = m.Xform(position);
  226. And only for basis (no translation):
  227. .. tabs::
  228. .. code-tab:: gdscript GDScript
  229. var new_pos = m.basis_xform(pos)
  230. .. code-tab:: csharp
  231. var newPosition = m.BasisXform(position);
  232. Inverse transform
  233. -----------------
  234. To do the opposite operation (what we did up there with the rocket), the
  235. "xform_inv" method is used:
  236. .. tabs::
  237. .. code-tab:: gdscript GDScript
  238. var new_pos = m.xform_inv(pos)
  239. .. code-tab:: csharp
  240. var newPosition = m.XformInv(position);
  241. Only for Basis:
  242. .. tabs::
  243. .. code-tab:: gdscript GDScript
  244. var new_pos = m.basis_xform_inv(pos)
  245. .. code-tab:: csharp
  246. var newPosition = m.BasisXformInv(position);
  247. Orthonormal matrices
  248. --------------------
  249. However, if the matrix has been scaled (vectors are not unit length),
  250. or the basis vectors are not orthogonal (90°), the inverse transform
  251. will not work.
  252. In other words, inverse transform is only valid in *orthonormal*
  253. matrices. For this, these cases an affine inverse must be computed.
  254. The transform, or inverse transform of an identity matrix will return
  255. the position unchanged:
  256. .. tabs::
  257. .. code-tab:: gdscript GDScript
  258. # Does nothing, pos is unchanged
  259. pos = Transform2D().xform(pos)
  260. .. code-tab:: csharp
  261. // Does nothing, position is unchanged
  262. position = new Transform2D().Xform(position);
  263. Affine inverse
  264. --------------
  265. The affine inverse is a matrix that does the inverse operation of
  266. another matrix, no matter if the matrix has scale or the axis vectors
  267. are not orthogonal. The affine inverse is calculated with the
  268. affine_inverse() method:
  269. .. tabs::
  270. .. code-tab:: gdscript GDScript
  271. var mi = m.affine_inverse()
  272. pos = m.xform(pos)
  273. pos = mi.xform(pos)
  274. # pos is unchanged
  275. .. code-tab:: csharp
  276. var mi = m.AffineInverse();
  277. position = m.Xform(position);
  278. position = mi.Xform(position);
  279. // position is unchanged
  280. If the matrix is orthonormal, then:
  281. .. tabs::
  282. .. code-tab:: gdscript GDScript
  283. # if m is orthonormal, then
  284. pos = mi.xform(pos)
  285. # is the same is
  286. pos = m.xform_inv(pos)
  287. .. code-tab:: csharp
  288. // if m is orthonormal, then
  289. position = mi.Xform(position);
  290. // is the same is
  291. position = m.XformInv(position);
  292. Matrix multiplication
  293. ---------------------
  294. Matrices can be multiplied. Multiplication of two matrices "chains"
  295. (concatenates) their transforms.
  296. However, as per convention, multiplication takes place in reverse
  297. order.
  298. Example:
  299. .. tabs::
  300. .. code-tab:: gdscript GDScript
  301. var m = more_transforms * some_transforms
  302. .. code-tab:: csharp
  303. var m = moreTransforms * someTransforms;
  304. To make it a little clearer, this:
  305. .. tabs::
  306. .. code-tab:: gdscript GDScript
  307. pos = transform1.xform(pos)
  308. pos = transform2.xform(pos)
  309. .. code-tab:: csharp
  310. position = transform1.Xform(position);
  311. position = transform2.Xform(position);
  312. Is the same as:
  313. .. tabs::
  314. .. code-tab:: gdscript GDScript
  315. # note the inverse order
  316. pos = (transform2 * transform1).xform(pos)
  317. .. code-tab:: csharp
  318. // note the inverse order
  319. position = (transform2 * transform1).Xform(position);
  320. However, this is not the same:
  321. .. tabs::
  322. .. code-tab:: gdscript GDScript
  323. # yields a different results
  324. pos = (transform1 * transform2).xform(pos)
  325. .. code-tab:: csharp
  326. // yields a different results
  327. position = (transform1 * transform2).Xform(position);
  328. Because in matrix math, A * B is not the same as B * A.
  329. Multiplication by inverse
  330. -------------------------
  331. Multiplying a matrix by its inverse, results in identity:
  332. .. tabs::
  333. .. code-tab:: gdscript GDScript
  334. # No matter what A is, B will be identity
  335. var B = A.affine_inverse() * A
  336. .. code-tab:: csharp
  337. // No matter what A is, B will be identity
  338. var B = A.AffineInverse() * A;
  339. Multiplication by identity
  340. --------------------------
  341. Multiplying a matrix by identity, will result in the unchanged matrix:
  342. .. tabs::
  343. .. code-tab:: gdscript GDScript
  344. # B will be equal to A
  345. B = A * Transform2D()
  346. .. code-tab:: csharp
  347. // B will be equal to A
  348. var B = A * new Transform2D();
  349. Matrix tips
  350. -----------
  351. When using a transform hierarchy, remember that matrix multiplication is
  352. reversed! To obtain the global transform for a hierarchy, do:
  353. .. tabs::
  354. .. code-tab:: gdscript GDScript
  355. var global_xform = parent_matrix * child_matrix
  356. .. code-tab:: csharp
  357. var globalTransform = parentMatrix * childMatrix;
  358. For 3 levels:
  359. .. tabs::
  360. .. code-tab:: gdscript GDScript
  361. var global_xform = gradparent_matrix * parent_matrix * child_matrix
  362. .. code-tab:: csharp
  363. var globalTransform = grandparentMatrix * parentMatrix * childMatrix;
  364. To make a matrix relative to the parent, use the affine inverse (or
  365. regular inverse for orthonormal matrices).
  366. .. tabs::
  367. .. code-tab:: gdscript GDScript
  368. # transform B from a global matrix to one local to A
  369. var B_local_to_A = A.affine_inverse() * B
  370. .. code-tab:: csharp
  371. // transform B from a global matrix to one local to A
  372. var bLocalToA = A.AffineInverse() * B;
  373. Revert it just like the example above:
  374. .. tabs::
  375. .. code-tab:: gdscript GDScript
  376. # transform back local B to global B
  377. B = A * B_local_to_A
  378. .. code-tab:: csharp
  379. // transform back local B to global B
  380. B = A * bLocalToA;
  381. OK, hopefully this should be enough! Let's complete the tutorial by
  382. moving to 3D matrices.
  383. Matrices & transforms in 3D
  384. ---------------------------
  385. As mentioned before, for 3D, we deal with 3 :ref:`Vector3 <class_Vector3>`
  386. vectors for the rotation matrix, and an extra one for the origin.
  387. Basis
  388. -----
  389. Godot has a special type for a 3x3 matrix, named :ref:`Basis <class_basis>`.
  390. It can be used to represent a 3D rotation and scale. Sub vectors can be
  391. accessed as:
  392. .. tabs::
  393. .. code-tab:: gdscript GDScript
  394. var m = Basis()
  395. var x = m[0] # Vector3
  396. var y = m[1] # Vector3
  397. var z = m[2] # Vector3
  398. .. code-tab:: csharp
  399. var m = new Basis();
  400. Vector3 x = m[0];
  401. Vector3 y = m[1];
  402. Vector3 z = m[2];
  403. Or, alternatively as:
  404. .. tabs::
  405. .. code-tab:: gdscript GDScript
  406. var m = Basis()
  407. var x = m.x # Vector3
  408. var y = m.y # Vector3
  409. var z = m.z # Vector3
  410. .. code-tab:: csharp
  411. var m = new Basis();
  412. Vector3 x = m.x;
  413. Vector3 y = m.y;
  414. Vector3 z = m.z;
  415. Basis is also initialized to Identity by default:
  416. .. image:: img/tutomat17.png
  417. Rotation in 3D
  418. --------------
  419. Rotation in 3D is more complex than in 2D (translation and scale are the
  420. same), because rotation is an implicit 2D operation. To rotate in 3D, an
  421. *axis*, must be picked. Rotation, then, happens around this axis.
  422. The axis for the rotation must be a *normal vector*. As in, a vector
  423. that can point to any direction, but length must be one (1.0).
  424. .. tabs::
  425. .. code-tab:: gdscript GDScript
  426. #rotate in Y axis
  427. var m3 = Basis()
  428. m3 = m3.rotated( Vector3(0,1,0), PI/2 )
  429. .. code-tab:: csharp
  430. // rotate in Y axis
  431. var m3 = new Basis();
  432. m3 = m3.Rotated(new Vector3(0, 1, 0), Mathf.PI / 2);
  433. Transform
  434. ---------
  435. To add the final component to the mix, Godot provides the
  436. :ref:`Transform <class_Transform>` type. Transform has two members:
  437. - *basis* (of type :ref:`Basis <class_Basis>`)
  438. - *origin* (of type :ref:`Vector3 <class_Vector3>`)
  439. Any 3D transform can be represented with Transform, and the separation
  440. of basis and origin makes it easier to work translation and rotation
  441. separately.
  442. An example:
  443. .. tabs::
  444. .. code-tab:: gdscript GDScript
  445. var t = Transform()
  446. pos = t.xform(pos) # transform 3D position
  447. pos = t.basis.xform(pos) # (only rotate)
  448. pos = t.origin + pos # (only translate)
  449. .. code-tab:: csharp
  450. var t = new Transform();
  451. position = t.Xform(position); // transform 3D position
  452. position = t.basis.Xform(position); // (only rotate)
  453. position = t.origin + position; // (only translate)