[cairo-commit] [cairo-www] src/cookbook.mdwn src/matrix_conventions src/matrix_conventions.mdwn

Nis Martensen nmartensen at freedesktop.org
Tue Nov 17 12:01:37 PST 2015


 src/cookbook.mdwn                   |    1 
 src/matrix_conventions.mdwn         |   86 ++++++++++++++++++++++++++++++++++++
 src/matrix_conventions/figure_1.png |binary
 src/matrix_conventions/figure_2.png |binary
 src/matrix_conventions/figure_3.png |binary
 src/matrix_conventions/figure_4.png |binary
 6 files changed, 87 insertions(+)

New commits:
commit f615837716700313aa7693a3a8c1ffd540362800
Author: Lawrence D'Oliveiro <ldo at geek-central.gen.nz>
Date:   Wed Nov 4 23:35:36 2015 +0000

    add my “Keeping Your Matrix Transformations Straight” article

diff --git a/src/cookbook.mdwn b/src/cookbook.mdwn
index 1205376..741bb6d 100644
--- a/src/cookbook.mdwn
+++ b/src/cookbook.mdwn
@@ -10,6 +10,7 @@
 * [[Converting_cairo_code_from_C_to_Python_or_Haskell_and_back|ConvertCtoPyAndBack]]
 * [[Load_jpg/png/gif/bmp/etc_into_CairoContext_with_help_from_gdk-pixbuf_in_Pycairo/Pygtk|gdkpixbufpycairo]] 
 * [[Basic_matrix_transformation_reminder|matrix_transform]]
+* [[Keeping Your Matrix Transformations Straight|matrix_conventions]]
 * [[Python:_Converting_PIL_images_to_Cairo_surfaces_and_back|pythoncairopil]]
 * [[Loading_fonts_using_FreeType_for_cairo_use_in_Python|freetypepython]]
 * [[A_description_of_compositing_operators_in_Cairo|operators]]
diff --git a/src/matrix_conventions.mdwn b/src/matrix_conventions.mdwn
new file mode 100644
index 0000000..938d695
--- /dev/null
+++ b/src/matrix_conventions.mdwn
@@ -0,0 +1,86 @@
+Matrix transformations are very powerful and useful in computer graphics. But they can be tricky to get right. This article won’t talk about the detailed mathematics of matrices—you can find plenty of descriptions of that elsewhere—but about the practicalities of getting matrix transformations correct in your program code.
+
+First of all, let me offer two important rules when writing matrix-manipulation code:
+
+* **Rule 1: Pick a convention and stick to it.** There are two *self-consistent* ways to write down a matrix transformation that takes a vector in object space (the coordinates in which the object is defined) and transforms it to world space (the coordinates in which the object is viewed): either as *post*-multiplication on a *row* vector, *e.g.*:
+
+       object-space vector → matrix1 → matrix2 → world-space vector
+
+   or as *pre*-multiplication on a *column* vector, *e.g.*:
+
+       world-space vector ← matrix2 ← matrix1 ← object-space vector
+
+   It may look like the first form is more natural (the transformation sequence going left-to-right rather than right-to-left), but in fact most code uses the latter, and it is probably the more convenient form for trying to envision what is going on, as we will see shortly. But the most vital thing is to be *absolutely clear which convention is in effect*.
+
+* **Rule 2: Understand, don’t fiddle.** If your transformations are coming out wrong, *don’t try randomly fiddling with the order of the components to get them right*. You might succeed eventually, but it is quite possible that you made *two* mistakes somewhere that just happen to cancel out. While two wrongs may make a right in this case, you have set a trap for yourself if you (or someone else) have to make an adjustment later: you might try to change the angle of a rotation, for example, only to discover that it has the opposite effect from what you expected; or your *x*-and *y*-scaling might be the wrong way round. So always *try to understand why your transformations are in the order they are*. The effort spent up-front will help keep things less confusing later on.
+
+Now let me give a handy tip for trying to envision the effect of a sequence of matrix transformations. Assuming the usual pre-multiplication-of-column-vectors convention, we can imagine each matrix as a magic box that, when we look through it, transforms the appearance of space on the other side, making things bigger or smaller, rotating or repositioning them and so on. So if we represent object space by the object we are looking at (here a simple line-drawing of a teapot), ahd world space by the eye of the observer, then a transformation like
+
+    world-space vector ← matrix ← object-space vector
+
+can be visualized as
+
+<div align="center">
+[[!img "figure_1.png" link="no"]]
+</div>
+
+where the purple arrows show the flow of geometric information from object space (coordinate system (*x<sub>o</sub>*, *y<sub>o</sub>*)), through the matrix transformation box, to the observer’s eye (coordinate system (*x<sub>w</sub>*, *y<sub>w</sub>*)).
+
+The basic principles apply equally to both 2D and 3D graphics. Here we are dealing with 2D graphics in Cairo. The examples will be in Python, using the high-level [[Qahirah|https://github.com/ldo/qahirah]] binding. This lets us represent the transformation of a <tt>Vector</tt> object by a <tt>Matrix</tt> object directly as a simple Python expression:
+
+    user_space_coord = matrix * object_space_coord
+
+Let us envision what happens if we apply a simple rotational transform to the object.
+
+<div align="center">
+[[!img "figure_2.png" link="no"]]
+</div>
+
+By superimposing both coordinate systems in the upper part of the diagram (the current one in black, the previous one in grey), we can see the differing effects of, for example, moving parallel to the axes in object space coordinates (*x<sub>o</sub>*, *y<sub>o</sub>*) versus world space coordinates (*x<sub>w</sub>*, *y<sub>w</sub>*). In the Python code, we can spread things out across multiple lines, to more closely approximate the arrangement in the diagram:
+
+    user_space_coord = \
+        (
+            Matrix.rotate(45 * deg)
+        *
+            object_space_coord
+        )
+
+Now, what happens if we apply two transformations in succession?
+
+<div align="center">
+[[!img "figure_3.png" link="no"]]
+</div>
+
+Here the transformations (in order from object space to world space) are rotation about the origin, followed by translation along the positive *y*-axis. The rotation converts from object coordinates (*x<sub>o</sub>*, *y<sub>o</sub>*) to the intermediate coordinate system (*x<sub>m</sub>*, *y<sub>m</sub>*). The translation then converts from (*x<sub>m</sub>*, *y<sub>m</sub>*) to (*x<sub>w</sub>*, *y<sub>w</sub>*) coordinates. The equivalent Python code would be something like
+
+    user_space_coord = \
+        (
+            Matrix.translate((0, 10))
+        *
+            Matrix.rotate(45 * deg)
+        *
+            object_space_coord
+        )
+
+Here the order is reversed, the *y*-axis translation being applied first:
+
+<div align="center">
+[[!img "figure_4.png" link="no"]]
+</div>
+
+Thus, the rotation takes place, not about the (*x<sub>o</sub>*, *y<sub>o</sub>*) origin, but about the (*x<sub>m</sub>*, *y<sub>m</sub>*) origin.
+
+**Each transformation (blue background) is applied in the coordinate system of the picture of the object immediately below it (yellow background).**
+
+Note that, while the *orientation* of the teapot ends up the same in both these cases, its *position* is different. The equivalent Python code would be correspondingly rearranged to match the diagram:
+
+    user_space_coord = \
+        (
+            Matrix.rotate(45 * deg)
+        *
+            Matrix.translate((0, 10))
+        *
+            object_space_coord
+        )
+
+So, when you look at the Python code, imagine the eye of the observer on the receiving end of the value of the expression, at the top, while the object coordinates are at the bottom, being processed through successive stages of the transformation until they get to the top. Each individual  <tt>Matrix</tt> object corresponds to one of the boxes with a blue background, while the multiplication asterisk immediately below it corresponds to the picture with the yellow background immediately below that box.
diff --git a/src/matrix_conventions/figure_1.png b/src/matrix_conventions/figure_1.png
new file mode 100644
index 0000000..28c32ec
Binary files /dev/null and b/src/matrix_conventions/figure_1.png differ
diff --git a/src/matrix_conventions/figure_2.png b/src/matrix_conventions/figure_2.png
new file mode 100644
index 0000000..2ce3ca8
Binary files /dev/null and b/src/matrix_conventions/figure_2.png differ
diff --git a/src/matrix_conventions/figure_3.png b/src/matrix_conventions/figure_3.png
new file mode 100644
index 0000000..1177705
Binary files /dev/null and b/src/matrix_conventions/figure_3.png differ
diff --git a/src/matrix_conventions/figure_4.png b/src/matrix_conventions/figure_4.png
new file mode 100644
index 0000000..7d108da
Binary files /dev/null and b/src/matrix_conventions/figure_4.png differ


More information about the cairo-commit mailing list