-
Notifications
You must be signed in to change notification settings - Fork 26
Transformations
Many elm-geometry
functions deal with different kinds of transformations -
translations, rotations, scalings, mirrors, and projections. Unlike most other
geometric libraries, however, elm-geometry
does not use matrices to define
transformations (in fact, matrices are not used anywhere). Instead of having
functions to create transformation matrices which can then later be applied to
values, transformations in elm-geometry
are just functions that can be used
directly:
rotatedPoint =
originalPoint |> Point2d.rotateAround Point2d.origin angle
This has many advantages. First, partial function application means that
transformations can be directly used with higher-order functions like
List.map
:
pointsOnXAxis =
[ Point2d.fromCoordinates ( 1, 0 )
, Point2d.fromCoordinates ( 2, 0 )
, Point2d.fromCoordinates ( 3, 0 )
]
pointsOnXAxis
|> List.map
(Point2d.rotateAround Point2d.origin (degrees 90))
--> [ Point2d.fromCoordinates ( 0, 1 )
--> , Point2d.fromCoordinates ( 0, 2 )
--> , Point2d.fromCoordinates ( 0, 3 )
--> ]
Second, transformations can be composed like any other functions to produce composite transformations (no more having to remember multiplication order of matrices!):
rotate90Degrees : Point2d -> Point2d
rotate90Degrees point =
Point2d.rotateAround Point2d.origin (degrees 90) point
scale150Percent : Point2d -> Point2d
scale150Percent point =
Point2d.scaleAbout Point2d.origin 1.5 point
rotateThenScale : Point2d -> Point2d
rotateThenScale point =
point |> rotate90Degrees |> scale150Percent
rotateThenScale (Point2d.fromCoordinates ( 1, 0 ))
--> Point2d.fromCoordinates ( 0, 1.5 )
rotateThenScale (Point2d.fromCoordinates ( 0, 2 ))
--> Point2d.fromCoordinates ( -3, 0 )
(Yes, in this particular case it doesn't actually matter whether you rotate first and then scale or the other way around, but you get the idea.)
Transformation functions generally work very similarly to each other; for
example, Point2d.mirrorAcross
behaves much like Triangle3d.mirrorAcross
even
though they work on different data types in different dimensions.
The behaviour of a transformation on a complex object is generally the result of
applying the same transformation on its components. For example, rotating a
Frame3d
is equivalent to rotating its origin point and basis directions and
then constructing a new frame from the rotated values.
Scaling works the same way in both 2D and 3D. Various modules include
scaleAbout
functions which all accept the same arguments in the same order: a
point to scale about, a value to scale by, and finally the value to scale:
point =
Point2d.fromCoordinates ( 1, 1 )
lineSegment =
LineSegment2d.fromEndpoints
( Point2d.fromCoordinates ( 1, 1 )
, Point2d.fromCoordinates ( 3, 2 )
)
LineSegment2d.scaleAbout point 2.0 lineSegment
--> LineSegment2d.fromEndpoints
--> ( Point2d.fromCoordinates ( 1, 1 )
--> , Point2d.fromCoordinates ( 5, 3 )
--> )
The point to scale about remains fixed, and all other points expand away from it or contract towards it depending on the given scale.
Translation also works the same way in both 2D and 3D - translateBy
functions
all take a single vector argument specifying the displacement to translate by,
and then the value to translate:
axis =
Axis2d.through (Point2d.fromCoordinates ( 2, 3 ))
Direction2d.x
displacement =
Vector2d.fromComponents ( 1, 2 )
Axis2d.translateBy displacement axis
--> Axis2d.through (Point2d.fromCoordinates ( 3, 5 ))
--> Direction2d.x