ianmackenzie / elm-geometry-prerelease / Vector2d

A Vector2d represents a quantity such as a displacement or velocity in 2D, and is defined by its X and Y components. This module contains a variety of vector-related functionality, such as

Note that unlike in many other geometry packages where vectors are used as a general-purpose data type, elm-geometry has separate data types for vectors, directions and points. In most code it is actually more common to use Point2d and Direction2d than Vector2d, and much code can avoid working directly with Vector2d values at all!


type alias Vector2d =
Geometry.Types.Vector2d

Constants

zero : Vector2d

The zero vector.

Vector2d.zero
--> Vector2d.fromComponents ( 0, 0 )

Constructors

fromComponents : ( Basics.Float, Basics.Float ) -> Vector2d

Construct a vector from its X and Y components.

vector =
    Vector2d.fromComponents ( 2, 3 )

fromPolarComponents : ( Basics.Float, Basics.Float ) -> Vector2d

Construct a vector from a length and angle. The angle is measured counterclockwise from the positive X direction.

Vector2d.fromPolarComponents ( 2, degrees 135 )
-->Vector2d.fromComponents ( -1.4142, 1.4142 )

from : Geometry.Types.Point2d -> Geometry.Types.Point2d -> Vector2d

Construct a vector from the first given point to the second.

startPoint =
    Point2d.fromCoordinates ( 1, 1 )

endPoint =
    Point2d.fromCoordinates ( 4, 5 )

Vector2d.from startPoint endPoint
--> Vector2d.fromComponents ( 3, 4 )

withLength : Basics.Float -> Geometry.Types.Direction2d -> Vector2d

Construct a vector with the given length in the given direction.

Vector2d.withLength 5 Direction2d.y
--> Vector2d.fromComponents ( 0, 5 )

perpendicularTo : Vector2d -> Vector2d

Construct a vector perpendicular to the given vector, by rotating the given vector 90 degrees counterclockwise. The constructed vector will have the same length as the given vector. Alias for Vector2d.rotateCounterclockwise.

Vector2d.perpendicularTo
    (Vector2d.fromComponents ( 1, 0 ))
--> Vector2d.fromComponents ( 0, 1 )

Vector2d.perpendicularTo
    (Vector2d.fromComponents ( 0, 2 ))
--> Vector2d.fromComponents ( -2, 0 )

Vector2d.perpendicularTo
    (Vector2d.fromComponents ( 3, 1 ))
--> Vector2d.fromComponents ( -1, 3 )

Vector2d.perpendicularTo Vector2d.zero
--> Vector2d.zero

interpolateFrom : Vector2d -> Vector2d -> Basics.Float -> Vector2d

Construct a vector by interpolating from the first given vector to the second, based on a parameter that ranges from zero to one.

startVector =
    Vector2d.zero

endVector =
    Vector2d.fromComponents ( 8, 12 )

Vector2d.interpolateFrom startVector endVector 0.25
--> Vector2d.fromComponents ( 2, 3 )

Partial application may be useful:

interpolatedVector : Float -> Vector2d
interpolatedVector =
    Vector2d.interpolateFrom startVector endVector

List.map interpolatedVector [ 0, 0.5, 1 ]
--> [ Vector2d.fromComponents ( 0, 0 )
--> , Vector2d.fromComponents ( 4, 6 )
--> , Vector2d.fromComponents ( 8, 12 )
--> ]

You can pass values less than zero or greater than one to extrapolate:

interpolatedVector -0.5
--> Vector2d.fromComponents ( -4, -6 )

interpolatedVector 1.25
--> Vector2d.fromComponents ( 10, 15 )

Properties

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

Extract the components of a vector.

Vector2d.components (Vector2d.fromComponents ( 2, 3 ))
--> ( 2, 3 )

This combined with Elm's built-in tuple destructuring provides a convenient way to extract both the X and Y components of a vector in one line of code:

( x, y ) =
    Vector2d.components vector

xComponent : Vector2d -> Basics.Float

Get the X component of a vector.

Vector2d.xComponent (Vector2d.fromComponents ( 2, 3 ))
--> 2

yComponent : Vector2d -> Basics.Float

Get the Y component of a vector.

Vector2d.yComponent (Vector2d.fromComponents ( 2, 3 ))
--> 3

polarComponents : Vector2d -> ( Basics.Float, Basics.Float )

Get the polar components (length, polar angle) of a vector.

Vector2d.polarComponents
    (Vector2d.fromComponents ( 1, 1 ))
--> ( 1.4142, degrees 45 )

length : Vector2d -> Basics.Float

Get the length (magnitude) of a vector.

Vector2d.length (Vector2d.fromComponents ( 3, 4 ))
--> 5

squaredLength : Vector2d -> Basics.Float

Get the squared length of a vector. squaredLength is slightly faster than length, so for example

Vector2d.squaredLength vector > tolerance * tolerance

is equivalent to but slightly more efficient than

Vector2d.length vector > tolerance

since the latter requires a square root under the hood. In many cases, however, the speed difference will be negligible and using length is much more readable!

direction : Vector2d -> Maybe Geometry.Types.Direction2d

Attempt to find the direction of a vector. In the case of a zero vector, return Nothing.

Vector2d.direction (Vector2d.fromComponents ( 3, 3 ))
--> Just (Direction2d.fromAngle (degrees 45))

Vector2d.direction Vector2d.zero
--> Nothing

lengthAndDirection : Vector2d -> Maybe ( Basics.Float, Geometry.Types.Direction2d )

Attempt to find the length and direction of a vector. In the case of a zero vector, returns Nothing.

vector =
    Vector2d.fromComponents ( 1, 1 )

Vector2d.lengthAndDirection vector
--> Just
-->     ( 1.4142
-->     , Direction2d.fromAngle (degrees 45)
-->     )

Vector2d.lengthAndDirection Vector2d.zero
--> Nothing

Comparison

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

Compare two vectors within a tolerance. Returns true if the difference between the two given vectors has magnitude less than the given tolerance.

firstVector =
    Vector2d.fromComponents ( 1, 2 )

secondVector =
    Vector2d.fromComponents ( 0.9999, 2.0002 )

Vector2d.equalWithin 1e-3 firstVector secondVector
--> True

Vector2d.equalWithin 1e-6 firstVector secondVector
--> False

Measurement

componentIn : Geometry.Types.Direction2d -> Vector2d -> Basics.Float

Find the component of a vector in an arbitrary direction, for example

forwardSpeed =
    Vector2d.componentIn forwardDirection velocity

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

Vector2d.xComponent vector

is equivalent to

Vector2d.componentIn Direction2d.x vector

Arithmetic

sum : Vector2d -> Vector2d -> Vector2d

Find the sum of two vectors.

firstVector =
    Vector2d.fromComponents ( 1, 2 )

secondVector =
    Vector2d.fromComponents ( 3, 4 )

Vector2d.sum firstVector secondVector
--> Vector2d.fromComponents ( 4, 6 )

difference : Vector2d -> Vector2d -> Vector2d

Find the difference between two vectors (the first vector minus the second).

firstVector =
    Vector2d.fromComponents ( 5, 6 )

secondVector =
    Vector2d.fromComponents ( 1, 3 )

Vector2d.difference firstVector secondVector
--> Vector2d.fromComponents ( 4, 3 )

dotProduct : Vector2d -> Vector2d -> Basics.Float

Find the dot product of two vectors.

firstVector =
    Vector2d.fromComponents ( 1, 2 )

secondVector =
    Vector2d.fromComponents ( 3, 4 )

Vector2d.dotProduct firstVector secondVector
--> 11

crossProduct : Vector2d -> Vector2d -> Basics.Float

Find the scalar 'cross product' of two vectors in 2D. This is defined as

crossProduct firstVector secondVector =
    let
        ( x1, y1 ) =
            components firstVector

        ( x2, y2 ) =
            components secondVector
    in
    x1 * y2 - y1 * x2

and is useful in many of the same ways as the 3D cross product:

Some examples:

firstVector =
    Vector2d.fromComponents ( 2, 0 )

secondVector =
    Vector2d.fromComponents ( 0, 3 )

Vector2d.crossProduct firstVector secondVector
--> 6

Vector2d.crossProduct secondVector firstVector
--> -6

Vector2d.crossProduct firstVector firstVector
--> 0

Transformations

Note that for mirrorAcross and projectOnto, only the direction of the axis affects the result, since vectors are position-independent. Think of mirroring/projecting a vector across/onto an axis as moving the vector so its tail is on the axis, then mirroring/projecting its tip across/onto the axis.

reverse : Vector2d -> Vector2d

Reverse the direction of a vector, negating its components.

Vector2d.reverse (Vector2d.fromComponents ( -1, 2 ))
--> Vector2d.fromComponents ( 1, -2 )

normalize : Vector2d -> Vector2d

Normalize a vector to have a length of one. Zero vectors are left as-is.

vector =
    Vector2d.fromComponents ( 3, 4 )

Vector2d.normalize vector
--> Vector2d.fromComponents ( 0.6, 0.8 )

Vector2d.normalize Vector2d.zero
--> Vector2d.zero

Warning: Vector2d.direction is safer since it forces you to explicitly consider the case where the given vector is zero. Vector2d.normalize is primarily useful for cases like generating WebGL meshes, where defaulting to a zero vector for degenerate cases is acceptable, and the overhead of something like

Vector2d.direction vector
    |> Maybe.map Direction2d.toVector
    |> Maybe.withDefault Vector2d.zero

(which is functionally equivalent to Vector2d.normalize vector) is too high.

scaleBy : Basics.Float -> Vector2d -> Vector2d

Scale the length of a vector by a given scale.

Vector2d.scaleBy 3 (Vector2d.fromComponents ( 1, 2 ))
--> Vector2d.fromComponents ( 3, 6 )

rotateBy : Basics.Float -> Vector2d -> Vector2d

Rotate a vector counterclockwise by a given angle (in radians).

Vector2d.fromComponents ( 1, 1 )
    |> Vector2d.rotateBy (degrees 45)
--> Vector2d.fromComponents ( 0, 1.4142 )

Vector2d.fromComponents ( 1, 0 )
    |> Vector2d.rotateBy pi
--> Vector2d.fromComponents ( -1, 0 )

rotateClockwise : Vector2d -> Vector2d

Rotate the given vector 90 degrees clockwise;

Vector2d.rotateClockwise vector

is equivalent to

Vector2d.rotateBy (degrees -90) vector

but is more efficient.

rotateCounterclockwise : Vector2d -> Vector2d

Rotate the given vector 90 degrees counterclockwise;

Vector2d.rotateCounterclockwise vector

is equivalent to

Vector2d.rotateBy (degrees 90) vector

but is more efficient.

mirrorAcross : Geometry.Types.Axis2d -> Vector2d -> Vector2d

Mirror a vector across a given axis.

vector =
    Vector2d.fromComponents ( 2, 3 )

Vector2d.mirrorAcross Axis2d.y vector
--> Vector2d.fromComponents ( -2, 3 )

The position of the axis doesn't matter, only its orientation:

horizontalAxis =
    Axis2d.withDirection Direction2d.x
        (Point2d.fromCoordinates ( 100, 200 ))

Vector2d.mirrorAcross horizontalAxis vector
--> Vector2d.fromComponents ( 2, -3 )

projectionIn : Geometry.Types.Direction2d -> Vector2d -> Vector2d

Find the projection of a vector in a particular direction. Conceptually, this means splitting the original vector into a portion parallel to the given direction and a portion perpendicular to it, then returning the parallel portion.

vector =
    Vector2d.fromComponents ( 2, 3 )

Vector2d.projectionIn Direction2d.x vector
--> Vector2d.fromComponents ( 2, 0 )

Vector2d.projectionIn Direction2d.y vector
--> Vector2d.fromComponents ( 0, 3 )

projectOnto : Geometry.Types.Axis2d -> Vector2d -> Vector2d

Project a vector onto an axis.

Vector2d.projectOnto Axis2d.y
    (Vector2d.fromComponents ( 3, 4 ))
--> Vector2d.fromComponents ( 0, 4 )

Vector2d.projectOnto Axis2d.x
    (Vector2d.fromComponents ( -1, 2 ))
--> Vector2d.fromComponents ( -1, 0 )

This is equivalent to finding the projection in the axis' direction.

Coordinate conversions

Like other transformations, coordinate conversions of vectors depend only on the orientations of the relevant frames, not the positions of their origin points.

For the examples, assume the following frame has been defined:

rotatedFrame =
    Frame2d.rotateBy (degrees 30) Frame2d.xy

relativeTo : Geometry.Types.Frame2d -> Vector2d -> Vector2d

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

Vector2d.fromComponents ( 2, 0 )
    |> Vector2d.relativeTo rotatedFrame
--> Vector2d.fromComponents ( 1.732, -1 )

placeIn : Geometry.Types.Frame2d -> Vector2d -> Vector2d

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

Vector2d.fromComponents ( 2, 0 )
    |> Vector2d.placeIn rotatedFrame
--> Vector2d.fromComponents ( 1.732, 1 )