Skip to content

Philosophy

Ian Mackenzie edited this page Jul 12, 2018 · 4 revisions

Introduction

elm-geometry is functionally similar to other vector/geometry packages, but works differently in a few subtle but meaningful ways. In general, elm-geometry has a more geometric than mathematical focus. For example, distinct types are used for points, vectors and directions which many other packages treat as a single generic vector type.

Directions

elm-geometry uses the concept of a 'direction' where other packages typically use vectors with unit length. Having separate types helps to keep track of whether a vector has already been normalized - no more having to guess whether a function that accepts a vector argument actually needs a unit vector, and if so whether you're expected to normalize the vector yourself or whether the function will do that internally.

You can get a direction from a vector with the Vector2d.direction and Vector3d.direction functions, but they actually return Maybe values since the zero vector has no direction:

Vector2d.direction (Vector2d.fromComponents ( 3, 0 )) ==
    Just Direction2d.x

Vector2d.direction (Vector2d.fromComponents ( -2, 2 )) ==
    Just (Direction2d.fromAngle (degrees 135))

Vector2d.direction Vector2d.zero ==
    Nothing

This takes advantage of Elm's type system to ensure that all code considers the degenerate zero-vector case. For example, given an eye point and a point to look at, the corresponding view direction could be determined with

Vector3d.direction (Vector3d.from eyePoint lookAtPoint)

This would return a Maybe Direction3d, with Nothing corresponding to the case where the eye point and point to look at are coincident (in which case the view direction is not well-defined and some special-case logic is needed).

Coordinates and components

Explicitly working with individual X/Y/Z point coordinates and vector components is easy to do in elm-geometry when necessary:

forwardSpeed =
    Vector3d.xComponent velocityVector

height =
    Point3d.zCoordinate position

point2d =
    let
        ( x, y, z ) =
            Point3d.coordinates point3d
    in
    Point2d.fromCoordinates ( x, z )

In many cases, however, it is equally easy and often advantageous to consider points and vectors as abstract geometric quantities and treat their internal representations by coordinates/components as an implementation detail. For example, the above might be replaced by

forwardSpeed =
    Vector3d.componentIn forwardDirection velocityVector

height =
    Point3d.signedDistanceFrom groundPlane position

point2d =
    Point3d.projectInto sketchPlane point3d

where (perhaps in a Constants.elm file or similar) you would define

forwardDirection =
    Direction3d.x

groundPlane =
    Plane3d.xy

sketchPlane =
    SketchPlane3d.xz

This approach is less coupled to the particular coordinate system being used and adapts more easily to changes - perhaps the forward direction becomes a configuration setting, or the ground plane becomes dynamic and shifts or tilts over time.

Clone this wiki locally