ianmackenzie / elm-geometry-prerelease / Direction3d

A Direction3d represents a direction like 'up' or 'north' or 'forwards'. They are represented using X, Y and Z components, and can be converted to vectors if necessary, but should be thought of as conceptually different. Directions have several uses, such as:


type alias Direction3d =
Geometry.Types.Direction3d

Constants

x : Direction3d

Synonym for Direction3d.positiveX.

y : Direction3d

Synonym for Direction3d.positiveY.

z : Direction3d

Synonym for Direction3d.positiveZ.

positiveX : Direction3d

The positive X direction.

Direction3d.components Direction3d.positiveX
--> ( 1, 0, 0 )

negativeX : Direction3d

The negative X direction.

Direction3d.components Direction3d.negativeX
--> ( -1, 0, 0 )

positiveY : Direction3d

The positive Y direction.

Direction3d.components Direction3d.positiveY
--> ( 0, 1, 0 )

negativeY : Direction3d

The negative Y direction.

Direction3d.components Direction3d.negativeY
--> ( 0, -1, 0 )

positiveZ : Direction3d

The positive Z direction.

Direction3d.components Direction3d.positiveZ
--> ( 0, 0, 1 )

negativeZ : Direction3d

The negative Z direction.

Direction3d.components Direction3d.negativeZ
--> ( 0, 0, -1 )

Constructors

from : Geometry.Types.Point3d -> Geometry.Types.Point3d -> Maybe Direction3d

Attempt to construct the direction from the first given point to the second. If the two points are coincident, returns Nothing.

point =
    Point3d.fromCoordinates ( 1, 0, 1 )

Direction3d.from Point3d.origin point
--> Just
-->     (Direction3d.fromAzimuthAndElevation
-->         (degrees 0)
-->         (degrees 45)
-->     )

Direction3d.from point Point3d.origin
--> Just
-->     (Direction3d.fromAzimuthAndElevation
-->         (degrees 180)
-->         (degrees -45)
-->     )

Direction3d.from point point
--> Nothing

on : Geometry.Types.SketchPlane3d -> Direction2d -> Direction3d

Construct a 3D direction lying on a sketch plane by providing a 2D direction specified in XY coordinates within the sketch plane.

horizontalDirection =
    Direction3d.on SketchPlane3d.xy <|
        Direction2d.fromAngle (degrees 45)

Direction3d.components horizontalDirection
--> ( 0.7071, 0.7071, 0 )

thirtyDegreesFromZ =
    Direction3d.on SketchPlane3d.zx <|
        Direction2d.fromAngle (degrees 30)

Direction3d.components thirtyDegreesFromZ
--> ( 0.5, 0, 0.866 )

fromAzimuthAndElevation : Basics.Float -> Basics.Float -> Direction3d

Construct a direction using azimuthal and elevation angles relative to the global XYZ frame. The azimuth defines the direction's polar angle on the global XY plane (from X towards Y) and the elevation defines its angle out of the XY plane towards positive Z.

Direction3d.components
    (Direction3d.fromAzimuthAndElevation
        (degrees 45)
        (degrees 45)
    )
--> ( 0.5, 0.5, 0.7071 )

perpendicularTo : Direction3d -> Direction3d

Construct an arbitrary direction perpendicular to the given direction. The exact resulting direction is not specified, but it is guaranteed to be perpendicular to the given direction.

Direction3d.perpendicularTo Direction3d.x
--> Direction3d.negativeZ

Direction3d.perpendicularTo Direction3d.y
--> Direction3d.positiveZ

direction =
    Direction3d.fromAzimuthAndElevation
        (degrees 0)
        (degrees 60)

Direction3d.perpendicularTo direction
--> Direction3d.fromAzimuthAndElevation
-->     (degrees 0)
-->     (degrees -30)

perpendicularBasis : Direction3d -> ( Direction3d, Direction3d )

Construct a pair of directions that are perpendicular to each other and both perpendicular to the given direction.

The given direction and the two returned directions will form a right-handed system (that is, a right-handed Frame3d could be constructed by using the given direction as the X direction and the two returned directions as the Y and Z directions, or the given direction as the Z direction and the two returned directions as the X and Y directions).

Direction3d.perpendicularBasis Direction3d.x
--> ( Direction3d.negativeZ
--> , Direction3d.positiveY
--> )

Direction3d.perpendicularBasis Direction3d.y
--> ( Direction3d.positiveZ
--> , Direction3d.positiveX
--> )

orthonormalize : Vector3d -> Vector3d -> Vector3d -> Maybe ( Direction3d, Direction3d, Direction3d )

Attempt to form a set of three mutually perpendicular directions from the three given vectors by performing Gram-Schmidt normalization:

If any of the given vectors are zero, any two of them are parallel, or the three are coplanar, returns Nothing.

Direction3d.orthonormalize
    (Vector3d.fromComponents ( 3, 3, 0 ))
    (Vector3d.fromComponents ( 0, 2, 0 ))
    (Vector3d.fromComponents ( 1, 2, 3 ))
--> Just
-->     ( Direction3d.fromAzimuthAndElevation
-->         (degrees 45)
-->         (degrees 0)
-->     , Direction3d.fromAzimuthAndElevation
-->         (degrees 135)
-->         (degrees 0)
-->     , Direction3d.positiveZ
-->     )

-- Three vectors in the XY plane:
Direction3d.orthonormalize
    (Vector3d.fromComponents ( 2, 0, 0 ))
    (Vector3d.fromComponents ( 3, 1, 0 ))
    (Vector3d.fromComponents ( 4, 2, 0 ))
--> Nothing

orthogonalize : Direction3d -> Direction3d -> Direction3d -> Maybe ( Direction3d, Direction3d, Direction3d )

Attempt to form a set of three mutually perpendicular directions from the three given directions;

Direction3d.orthogonalize
    xDirection
    yDirection
    zDirection

is equivalent to

Direction3d.orthonormalize
    (Direction3d.toVector xDirection)
    (Direction3d.toVector yDirection)
    (Direction3d.toVector zDirection)

unsafe : ( Basics.Float, Basics.Float, Basics.Float ) -> Direction3d

Construct a direction directly from its X, Y and Z components. Note that you must ensure that the sum of the squares of the given components is exactly one:

Direction3d.unsafe ( 1, 0, 0 )

Direction3d.unsafe ( 0, -1, 0 )

Direction3d.unsafe ( 0.6, 0, 0.8 )

are all valid but

Direction3d.unsafe ( 2, 0, 0 )

Direction3d.unsafe ( 1, 1, 1 )

are not. Instead of using Direction3d.unsafe, it may be easier to use constructors like Direction3d.on or Direction3d.fromAzimuthAndElevation (which will always result in a valid direction), or start with existing directions and transform them as necessary.

Properties

components : Direction3d -> ( Basics.Float, Basics.Float, Basics.Float )

Get the components of a direction as a tuple (the components it would have as a unit vector, also know as its direction cosines).

( x, y, z ) =
    Direction3d.components direction

xComponent : Direction3d -> Basics.Float

Get the X component of a direction.

Direction3d.xComponent Direction3d.x
--> 1

Direction3d.xComponent Direction3d.y
--> 0

yComponent : Direction3d -> Basics.Float

Get the Y component of a direction.

Direction3d.yComponent Direction3d.y
--> 1

Direction3d.yComponent Direction3d.z
--> 0

zComponent : Direction3d -> Basics.Float

Get the Z component of a direction.

Direction3d.zComponent Direction3d.z
--> 1

Direction3d.zComponent Direction3d.x
--> 0

azimuth : Direction3d -> Basics.Float

Get the angle of a direction in the XY plane, measured from the X axis towards the Y axis (counterclockwise around the Z axis). The result will be in the range -π to π.

Direction3d.azimuth Direction3d.x
--> 0

Direction3d.azimuth Direction3d.y
--> degrees 90

Direction3d.azimuth Direction3d.negativeY
--> degrees -90

Direction3d.azimuth Direction3d.negativeX
--> degrees 180

Vertical directions are considered to have an azimuth of zero:

Direction3d.azimuth Direction3d.z
--> 0

elevation : Direction3d -> Basics.Float

Get the angle of a direction from the XY plane towards positive Z. The result will be in the range -π/2 to π/2.

Direction3d.elevation Direction3d.x
--> 0

Direction3d.elevation Direction3d.negativeY
--> 0

Direction3d.elevation Direction3d.z
--> degrees 90

Direction3d.elevation Direction3d.negativeZ
--> degrees -90

Comparison

equalWithin : Basics.Float -> Direction3d -> Direction3d -> Basics.Bool

Compare two directions within an angular tolerance. Returns true if the angle between the two given directions is less than the given tolerance.

rotatedDirection =
    Direction3d.x
        |> Direction3d.rotateAround Axis3d.z
            (degrees 2)

Direction3d.equalWithin (degrees 5)
    Direction3d.x
    rotatedDirection
--> True

Direction3d.equalWithin (degrees 1)
    Direction3d.x
    rotatedDirection
--> False

Measurement

componentIn : Direction3d -> Direction3d -> Basics.Float

Find the component of one direction in another direction. This is equal to the cosine of the angle between the directions, or equivalently the dot product of the two directions converted to unit vectors.

direction =
    Direction3d.fromAzimuthAndElevation
        (degrees 0)
        (degrees 60)

Direction3d.componentIn Direction3d.x direction
--> 0.5

Direction3d.componentIn Direction3d.z direction
--> 0.866

Direction3d.componentIn direction direction
--> 1

direction
    |> Direction3d.componentIn
        (Direction3d.reverse direction)
--> -1

This is more general and flexible than using xComponent, yComponent or zComponent, all of which can be expressed in terms of componentIn; for example,

Direction3d.zComponent direction

is equivalent to

Direction3d.componentIn Direction3d.z direction

angleFrom : Direction3d -> Direction3d -> Basics.Float

Find the angle from one direction to another. The result will be in the range 0 to π.

Direction3d.angleFrom Direction3d.x Direction3d.x
--> degrees 0

Direction3d.angleFrom Direction3d.x Direction3d.z
--> degrees 90

Direction3d.angleFrom
    Direction3d.y
    Direction3d.negativeY
--> degrees 180

Conversion

toVector : Direction3d -> Vector3d

Convert a direction to a unit vector.

Direction3d.toVector Direction3d.y
--> Vector3d.fromComponents ( 0, 1, 0 )

Transformations

reverse : Direction3d -> Direction3d

Reverse a direction.

Direction3d.reverse Direction3d.y
--> Direction3d.negativeY

rotateAround : Geometry.Types.Axis3d -> Basics.Float -> Direction3d -> Direction3d

Rotate a direction around an axis by a given angle.

Direction3d.y
    |> Direction3d.rotateAround Axis3d.x (degrees 90)
--> Direction3d.z

Note that only the direction of the axis affects the result, not the position of its origin point, since directions are position-independent:

offsetAxis =
    Axis3d.withDirection Direction3d.z
        (Point3d.fromCoordinates ( 100, 200, 300 ))

Direction3d.x
    |> Direction3d.rotateAround offsetAxis (degrees 90)
--> Direction3d.y

mirrorAcross : Geometry.Types.Plane3d -> Direction3d -> Direction3d

Mirror a direction across a plane.

direction =
    Direction3d.fromAzimuthAndElevation
        (degrees 30)
        (degrees 60)

Direction3d.mirrorAcross Plane3d.xy direction
--> Direction3d.fromAzimuthAndElevation
-->     (degrees 30)
-->     (degrees -60)

Note that only the normal direction of the plane affects the result, not the position of its origin point, since directions are position-independent:

Direction3d.mirrorAcross Plane3d.yz direction
--> Direction3d.fromAzimuthAndElevation
-->     (degrees 150)
-->     (degrees 60)

offsetPlane =
    Plane3d.offsetBy 10 Plane3d.yz

Direction3d.mirrorAcross offsetPlane direction
--> Direction3d.fromAzimuthAndElevation
-->     (degrees 150)
-->     (degrees 60)

projectOnto : Geometry.Types.Plane3d -> Direction3d -> Maybe Direction3d

Find the orthographic projection of a direction onto a plane (renormalized to have unit length). If the given direction is exactly perpendicular to the given plane, returns Nothing.

direction =
    Direction3d.fromAzimuthAndElevation
        (degrees -60)
        (degrees 0)

Direction3d.projectOnto Plane3d.xy direction
--> Just direction

Direction3d.projectOnto Plane3d.xz direction
--> Just Direction3d.x

Direction3d.projectOnto Plane3d.yz direction
--> Just Direction3d.negativeY

Direction3d.projectOnto Plane3d.xy Direction3d.z
--> Nothing

Coordinate conversions

Like other transformations, coordinate transformations of directions depend only on the orientations of the relevant frames, not their positions.

For the examples, assume the following definition of a local coordinate frame, one that is rotated 30 degrees counterclockwise around the Z axis from the global XYZ frame:

rotatedFrame =
    Frame3d.xyz
        |> Frame3d.rotateAround Axis3d.z (degrees 30)

relativeTo : Geometry.Types.Frame3d -> Direction3d -> Direction3d

Take a direction defined in global coordinates, and return it expressed in local coordinates relative to a given reference frame.

Direction3d.relativeTo rotatedFrame Direction3d.x
--> Direction3d.fromAzimuthAndElevation
-->     (degrees -30)
-->     (degrees 0)

Direction3d.relativeTo rotatedFrame Direction3d.y
--> Direction3d.fromAzimuthAndElevation
-->     (degrees 60)
-->     (degrees 0)

Direction3d.relativeTo rotatedFrame Direction3d.z
--> Direction3d.z

placeIn : Geometry.Types.Frame3d -> Direction3d -> Direction3d

Take a direction defined in local coordinates relative to a given reference frame, and return that direction expressed in global coordinates.

Direction3d.placeIn rotatedFrame Direction3d.x
--> Direction3d.fromAzimuthAndElevation
-->     (degrees 30)
-->     (degrees 0)

Direction3d.placeIn rotatedFrame Direction3d.y
--> Direction3d.fromAzimuthAndElevation
-->     (degrees 120)
-->     (degrees 0)

Direction3d.placeIn rotatedFrame Direction3d.z
--> Direction3d.z

projectInto : Geometry.Types.SketchPlane3d -> Direction3d -> Maybe Direction2d

Project a direction into a given sketch plane. Conceptually, this finds the orthographic projection of the direction onto the plane, re-normalizes it to have unit length, and then expresses the projected direction in 2D sketch coordinates.

This is only possible if the direction is not perpendicular to the sketch plane; if it is perpendicular, Nothing is returned.

direction =
    Direction3d.fromAzimuthAndElevation
        (degrees -60)
        (degrees 0)

Direction3d.projectInto SketchPlane3d.xy direction
--> Just (Direction2d.fromAngle (degrees -60))

Direction3d.projectInto SketchPlane3d.xz direction
--> Just Direction2d.x

Direction3d.projectInto SketchPlane3d.yz direction
--> Just Direction2d.negativeX

Direction3d.projectInto SketchPlane3d.xy Direction3d.z
--> Nothing