Affine transformations¶
An affine transformation is a geometrical transformation that preserves lines (they remain straight). It may alter distances, angles, scales, orientations. Affine transforms are linear transformations and commonly implemented using matrix algebra.
The Transform2d
class in djutils-draw performs affine transforms on 2D objects; the Transform3d
class operates on 3D objects. Unlike all other objects in the djutils-draw package, the transform objects are mutable. The djutils-draw transformations are constructed as the identity transformation, then modified by applying some sequence of translations, rotations, shear-operations and reflections. As each of these modifications of a transform returns the resulting transform; these operations can be chained. Finally the transformation can operate on a Point
or Bounds
object or on an Iterator<Point>
. (This will probably be extended to all Drawable
objects.)
Setting up a Transform2d
or Transform3d
involves some math that is relatively expensive when rotations are involved. Subsequently applying such a transformation to coordinates is very fast as that only involves a few multiplications and additions.
Translating and Rotating¶
In this example, a unity Transform3d object is constructed and then a translation is added to it, and then a rotation. The net effect on the Point3d
is that is first rotated and then translated.
Point3d point = new Point3d(1, 0, 0);
System.out.println(point);
Transform3d transform1 = new Transform3d();
transform1.translate(2, 5, 8); // Translate
System.out.println(transform1.transform(point));
transform1.rotY(Math.PI / 2);
System.out.println(transform1.transform(point));
Outputs
Point3d [x=1.000000, y=0.000000, z=0.000000] Point3d [x=3.000000, y=5.000000, z=8.000000] Point3d [x=2.000000, y=5.000000, z=7.000000]
The resulting transformation could have been created in a single line of Java code using chaining:
Transform3d transform2 = new Transform3d().translate(2, 5, 8).rotY(Math.PI / 2);
System.out.println(transform2.transform(point));
Outputs:
Point3d [x=2.000000, y=5.000000, z=7.000000]
Of course, the Transform3d
class also implements rotations around the X and Z axes. As the Transform2d
class only implements rotation perpendicular to the X-Y plane this method is simply named rotation
.
Shearing¶
The shear operator transforms a rectangle into a parallelogram. The Transform2d
class implements one shear(double, double)
method. The Transform3d
class implements three shear methods: shearXY(double, double)
, shearYZ(double, double)
and shearZX(double, double)
.
Transform2d transform = new Transform2d().shear(2, 3);
for (int x = 0; x < 2; x++)
{
for (int y = 0; y < 2; y++)
{
Point2d p = new Point2d(x, y);
System.out.println(p + " -> " + transform.transform(p));
}
}
Outputs:
Point2d [x=0.000000, y=0.000000] -> Point2d [x=0.000000, y=0.000000] Point2d [x=0.000000, y=1.000000] -> Point2d [x=2.000000, y=1.000000] Point2d [x=1.000000, y=0.000000] -> Point2d [x=1.000000, y=3.000000] Point2d [x=1.000000, y=1.000000] -> Point2d [x=3.000000, y=4.000000]
The X-coordinates are incremented by the first parameter of the shear times the Y-coordinate. The Y-coordinates are incremented by the second parameter of the shear times the X-coordinate.
Transform3d transform = new Transform3d().shearXY(2, 3);
for (int x = 0; x < 2; x++)
{
for (int y = 0; y < 2; y++)
{
for (int z = 0; z < 2; z++)
{
Point3d p = new Point3d(x, y, z);
System.out.println(p + " -> " + transform.transform(p));
}
}
}
Outputs:
Point3d [x=0.000000, y=0.000000, z=0.000000] -> Point3d [x=0.000000, y=0.000000, z=0.000000] Point3d [x=0.000000, y=0.000000, z=1.000000] -> Point3d [x=2.000000, y=3.000000, z=1.000000] Point3d [x=0.000000, y=1.000000, z=0.000000] -> Point3d [x=0.000000, y=1.000000, z=0.000000] Point3d [x=0.000000, y=1.000000, z=1.000000] -> Point3d [x=2.000000, y=4.000000, z=1.000000] Point3d [x=1.000000, y=0.000000, z=0.000000] -> Point3d [x=1.000000, y=0.000000, z=0.000000] Point3d [x=1.000000, y=0.000000, z=1.000000] -> Point3d [x=3.000000, y=3.000000, z=1.000000] Point3d [x=1.000000, y=1.000000, z=0.000000] -> Point3d [x=1.000000, y=1.000000, z=0.000000] Point3d [x=1.000000, y=1.000000, z=1.000000] -> Point3d [x=3.000000, y=4.000000, z=1.000000]
The shearXY
operation does not modify the Z coordinates. The X-coordinates are incremented by the first parameter of the shear times the Z value. The Y-coordinates are incremented by the second parameter of the shear times the Z value. Similarly, there shearYZ
operation preserves the X-coordinate values and the shearZX
operation preserves the Y-coordinate values.
Scaling¶
A transform can also perform scaling by independent factors along each axis.
Transform2d transform = new Transform2d().scale(4, 6);
for (int x = 0; x < 2; x++)
{
for (int y = 0; y < 2; y++)
{
Point2d p = new Point2d(x, y);
System.out.println(p + " -> " + transform.transform(p));
}
}
Outputs:
Point2d [x=0.000000, y=0.000000] -> Point2d [x=0.000000, y=0.000000] Point2d [x=0.000000, y=1.000000] -> Point2d [x=0.000000, y=6.000000] Point2d [x=1.000000, y=0.000000] -> Point2d [x=4.000000, y=0.000000] Point2d [x=1.000000, y=1.000000] -> Point2d [x=4.000000, y=6.000000]
Reflecting¶
The Transform2d
object implements reflectX()
and reflectY()
. the Transform3d
object additionally implements reflectZ()
. These methods invert that particular coordinate.
Transforming multi-point Drawable objects¶
All preceding examples of using transforms showed how a transform can be applied to a Point
. Each Drawable
object implements getPoints()
which constructs an Iterator<Point>
that will yields all points of the Drawable
. Also, Drawable
objects that are constructed from a varying number of points have a constructor that takes such an iterator. This can be used to construct a new transformed Drawable
with a single line of code:
PolyLine2d line = new PolyLine2d(new Point2d(1, 2), new Point2d(2, 3), new Point2d(5, 0));
System.out.println(line);
Transform2d transform = new Transform2d().scale(2, 3);
PolyLine2d transformedLine = new PolyLine2d(transform.transform(line.getPoints()));
System.out.println(transformedLine);
Outputs:
PolyLine2d [x=1.000000, y=2.000000, x=2.000000, y=3.000000, x=5.000000, y=0.000000] PolyLine2d [x=2.000000, y=6.000000, x=4.000000, y=9.000000, x=10.000000, y=0.000000]