Transformation

It's sometimes convenient, especially in scripts, to be able to move your objects around on the screen. Most important motions are rigid, but from time to time it's useful to have non-rigid motions as well. Geometer provides a simple set of transformations that can be applied to points yielding other points. Let's begin with the simplest set:

[P] = .v.vtranslate([P], [F], [F]);
[P] = .v.vscale([P], [F], [F]);
[P] = .v.vrotate([P], [F]);

These operations yield a new point that's transformed relative to the old one. The .v.vtranslate command, for example, makes a new point that's translated by the given amounts in the x and y directions from the old one. In the following example:

v1 = .free(0, 0);
v2 = .v.vtranslate(v1, .3, .5);

the point v1 will be drawn at the center of the drawing area with coordinates (0,0) and v2 will be translated from v1 by .3 units in the x direction and .5 units in the y direction. If you then grab v1 with your mouse and drag it around, v2 will remain .3 units to the right and .5 units above v1.

The rotation and scaling commands, .v.vrotate and .v.vscale, do their rotation and scaling relative to the origin at the center of the drawing area. So in the following example:

.degreemode;
v1 = .free(.2, .2);
v2 = .v.vscale(v1, 2.0, 1.5);
v3 = .v.vrotate(v1, 45);
the x and y coordinates of v1 are multiplied by 2.0 and 1.5, respectively, to yield the point v2 at (0.4, 0.3). Similarly, the rotation by 45° about the origin will put point v3 at (.28284, 0). The rotation is counter-clockwise about the origin, and since v1 lies on the 45° line, it will be rotated to the 90° line, namely, the y axis.

In case you've never messed with geometric transformations, the order does make a difference -- a rotation followed by a translation is almost never the same as the translation followed by the rotation. Probably the best place to learn about this would be in a textbook on computer graphics.

But just a couple of hints may be enough to get you through. If you'd like to rotate about a point that's not at the origin, you can translate it to the origin, then rotate, and then translate back. So, for example, to rotate the point v1 about the point (.2, .3) by 48 degrees, the following chunk of code will do the trick:

.degreemode;
v2 = .v.vtranslate(v1, -.2, -.3, .in);
v3 = .v.vrotate(v2, 48, .in);
v4 = .v.vtranslate(v3, .2, .3);
The intermediate points v2 and v3 are painted the .in (= invisible) color so they won't appear in your diagram.

The trick above works great if you only have a point or two that you'd like to rotate about an unusual origin. If you'd like to use the same operation on a whole set of points, it would be a pain to make up all the intermediate points for each of the original points.

Geometer has a primitive object called a transformation (listed as [X] in the syntax charts, and as "x" within the command words). Once you build a transformation (like a rotation of 48 degrees about the point (.2, .3), you can then apply it to a large series of points.

Here's the code to do exactly that, and then to transform all the vertices of the triangle v1, v2, v3 to a new triangle v4, v5, v6:

v1 = .free(0, 0);
v2 = .free(.5, 0);
v3 = .free(0, .5);
id = .x.identity();
x1 = .x.xtranslate(id, -.2, -.3);
x2 = .x.xrotate(x1, 48);
x3 = .x.xtranslate(x2, .2, .3);
v4 = .v.vx(v1, x3);
v5 = .v.vx(v2, x3);
v6 = .v.vx(v3, x3);
} The code above builds a transformation starting from the identity, which represents no transformation at all, by applying one transformation after the other to it. The entire transformation that represents a rotation about the point (.2, .3) is saved in the transformation called x3. Then x3 is applied to each of the vertices of the original triangle in turn to make the new, rotated, triangle.

Here's a list of all the commands that deal with transformations:

[X] = .x.identity();
[X] = .x.translate([X], [F], [F]);
[X] = .x.scale([X], [F], [F]);
[X] = .x.rotate([X], [F});
[P] = .v.vx([P], [X]);
[X] = .x.xxf([X], [X], [F]);
[X] = .x.f9([F], [F], [F], [F], [F], [F], [F], [F], [F]);
} The use of the top 5 is obvious, but the final two require some explanation. .x.xxf is a way to choose between two transforms, depending on the value of the number. If the number is zero, the first transform is used; otherwise the second. This can be useful in a script when you want to change from transform to transform in the middle.

The final one, .x.f9 allows you to specify a completely general affine transformation using 9 numbers. The way transformations are handled internally is using 3 by 3 matrices, which are multiplied together to generate more complex transformations. For example, the rotation matrix is as follows, assuming that the angle of rotation is x:

  |  cos x  sin x   0  |
  | -sin x  cos x   0  |
  |     0     0     1  |

Although the points are only two dimensional, a third entry, equal to 1.0 and called the "w" coordinate) is added to them before the matrix multiplication. At the end, the new x and y coordinates are divided by the new w coordinate to get the two dimensional point. Usually this division is unnecessary -- the w coordinate after transformation will be equal to 1.0.

But as an example, let's look at rotation by 30 degrees of the point (2, 1) The matrix multiplication operation looks like this:

  [ 2  1  1 ] |  cos 30°  sin 30°  0  |
              | -sin 30°  cos 30°  0  |
              |    0        0      1  |

Since cos 30° = .866025 and sin 30° = .500000, the operation is:

  [ 2  1  1 ] |  .866025  .500000  0  | = [1.23205  1.8602  1 ]
              | -.500000  .866025  0  |
              |    0        0      1  |

If we divide through by the resulting w coordinate of 1.0 (and it isn't too hard to divide by 1.0), we get the point (1.23205, 1.86602) which is the result of rotating the point (2, 1) by 30° in a counter-clockwise direction.

For completeness, here are the matrices equivalent to translation by t_x in the x direction and t_y in the y direction:

  |   1    0   0  |
  |   0    1   0  |
  |  t_x  t_y  1  |

and of a scale by s_x in the x direction and s_y in the y direction:

  |  s_x    0    0  |
  |   0    s_y   0  |
  |   0     0    1  |