ianmackenzie / elm-geometry-prerelease / CubicSpline2d

A CubicSpline2d is a cubic Bézier curve in 2D defined by a start point, end point and two control points. This module contains functionality for


type alias CubicSpline2d =
Geometry.Types.CubicSpline2d

Constructors

with : { startPoint : Point2d, startControlPoint : Point2d, endControlPoint : Point2d, endPoint : Point2d } -> CubicSpline2d

Construct a spline from its endpoints and control points:

exampleSpline =
    CubicSpline2d.with
        { startPoint =
            Point2d.fromCoordinates ( 1, 1 )
        , startControlPoint =
            Point2d.fromCoordinates ( 3, 4 )
        , endControlPoint =
            Point2d.fromCoordinates ( 5, 1 )
        , endPoint =
            Point2d.fromCoordinates ( 7, 4 )
        }

fromEndpoints : { startPoint : Point2d, startDerivative : Vector2d, endPoint : Point2d, endDerivative : Vector2d } -> CubicSpline2d

Construct a spline from a given start point with a given start derivative, to a given end point with a given end derivative, like so:

Cubic spline from endpoints

The spline is based on a parameter that ranges from 0 to 1; as a result, in most cases the length of each derivative vector should be roughly equal to the length of the resulting spline.

fromQuadraticSpline : QuadraticSpline2d -> CubicSpline2d

Convert a quadratic spline into the equivalent cubic spline (every quadratic spline can be represented exactly as a cubic spline).

quadraticSpline =
    QuadraticSpline2d.with
        { startPoint =
            Point2d.fromCoordinates ( 0, 0  )
        , controlPoint =
            Point2d.fromCoordinates ( 3, 0 )
        , endPoint =
            Point2d.fromCoordinates ( 3, 3 )
        }

CubicSpline2d.fromQuadraticSpline quadraticSpline
--> CubicSpline2d.with
-->     { startPoint =
-->         Point2d.fromCoordinates ( 0, 0 )
-->     , startControlPoint =
-->         Point2d.fromCoordinates ( 2, 0 )
-->     , endControlPoint =
-->         Point2d.fromCoordinates ( 3, 1 )
-->     , endPoint =
-->         Point2d.fromCoordinates ( 3, 3 )
-->     }

Properties

startPoint : CubicSpline2d -> Point2d

Get the start point of a spline.

CubicSpline2d.startPoint exampleSpline
--> Point2d.fromCoordinates ( 1, 1 )

endPoint : CubicSpline2d -> Point2d

Get the end point of a spline.

CubicSpline2d.endPoint exampleSpline
--> Point2d.fromCoordinates ( 7, 4 )

startControlPoint : CubicSpline2d -> Point2d

Get the start control point of a spline (the control point next to the start point).

CubicSpline2d.startControlPoint exampleSpline
--> Point2d.fromCoordinates ( 3, 4 )

endControlPoint : CubicSpline2d -> Point2d

Get the end control point of a spline (the control point next to the end point).

CubicSpline2d.endControlPoint exampleSpline
--> Point2d.fromCoordinates ( 5, 1 )

startDerivative : CubicSpline2d -> Vector2d

Get the start derivative of a spline. This is equal to three times the vector from the spline's start point to its start control point.

CubicSpline2d.startDerivative exampleSpline
--> Vector2d.fromComponents ( 6, 9 )

endDerivative : CubicSpline2d -> Vector2d

Get the end derivative of a spline. This is equal to three times the vector from the spline's end control point to its end point.

CubicSpline2d.endDerivative exampleSpline
--> Vector2d.fromComponents ( 6, 9 )

boundingBox : CubicSpline2d -> BoundingBox2d

Compute a bounding box for a given spline. It is not guaranteed that the result will be the smallest possible bounding box, since for efficiency the bounding box is computed from the spline's control points (which cover a larger area than the spline itself).

CubicSpline2d.boundingBox exampleSpline
--> BoundingBox2d.fromExtrema
-->     { minX = 1
-->     , maxX = 7
-->     , minY = 1
-->     , maxY = 4
-->     }

Evaluation

pointOn : CubicSpline2d -> Curve.ParameterValue.ParameterValue -> Point2d

Get the point along a spline at a given parameter value:

CubicSpline2d.pointOn exampleSpline ParameterValue.zero
--> Point2d.fromCoordinates ( 1, 1 )

CubicSpline2d.pointOn exampleSpline ParameterValue.half
--> Point2d.fromCoordinates ( 4, 2.5 )

CubicSpline2d.pointOn exampleSpline ParameterValue.one
--> Point2d.fromCoordinates ( 7, 4 )

pointsAt : List Curve.ParameterValue.ParameterValue -> CubicSpline2d -> List Point2d

Get points along a spline at a given set of parameter values:

exampleSpline
    |> CubicSpline2d.pointsAt
        (ParameterValue.steps 2)
--> [ Point2d.fromCoordinates ( 1, 1 )
--> , Point2d.fromCoordinates ( 4, 2.5 )
--> , Point2d.fromCoordinates ( 7, 4 )
--> ]


type Nondegenerate

If a curve has zero length (consists of just a single point), then we say that it is 'degenerate'. Some operations such as computing tangent directions are not defined on degenerate curves.

A Nondegenerate value represents a spline that is definitely not degenerate. It is used as input to functions such as CubicSpline2d.tangentDirection and can be constructed using CubicSpline2d.nondegenerate.

nondegenerate : CubicSpline2d -> Result Point2d Nondegenerate

Attempt to construct a nondegenerate spline from a general CubicSpline2d. If the spline is in fact degenerate (consists of a single point), returns an Err with that point.

CubicSpline2d.nondegenerate exampleSpline
--> Ok nondegenerateExampleSpline

fromNondegenerate : Nondegenerate -> CubicSpline2d

Convert a nondegenerate spline back to a general CubicSpline2d.

CubicSpline2d.fromNondegenerate
    nondegenerateExampleSpline
--> exampleSpline

tangentDirection : Nondegenerate -> Curve.ParameterValue.ParameterValue -> Direction2d

Get the tangent direction to a nondegenerate spline at a given parameter value:

CubicSpline2d.tangentDirection
    nondegenerateExampleSpline
    ParameterValue.zero
--> Direction2d.fromAngle (degrees 56.31)

CubicSpline2d.tangentDirection
    nondegenerateExampleSpline
    ParameterValue.half
--> Direction2d.fromAngle (degrees 0)

CubicSpline2d.tangentDirection
    nondegenerateExampleSpline
    ParameterValue.one
--> Direction2d.fromAngle (degrees 56.31)

tangentDirectionsAt : List Curve.ParameterValue.ParameterValue -> Nondegenerate -> List Direction2d

Get tangent directions to a nondegenerate spline at a given set of parameter values:

nondegenerateExampleSpline
    |> CubicSpline2d.tangentDirectionsAt
        (ParameterValue.steps 2)
--> [ Direction2d.fromAngle (degrees 56.31)
--> , Direction2d.fromAngle (degrees 0)
--> , Direction2d.fromAngle (degrees 56.31)
--> ]

sample : Nondegenerate -> Curve.ParameterValue.ParameterValue -> ( Point2d, Direction2d )

Get both the point and tangent direction of a nondegenerate spline at a given parameter value:

CubicSpline2d.sample nondegenerateExampleSpline
    ParameterValue.half
--> ( Point2d.fromCoordinates ( 4, 2.5 )
--> , Direction2d.fromAngle (degrees 0)
--> )

samplesAt : List Curve.ParameterValue.ParameterValue -> Nondegenerate -> List ( Point2d, Direction2d )

Get points and tangent directions of a nondegenerate spline at a given set of parameter values:

nondegenerateExampleSpline
    |> CubicSpline2d.samplesAt
        (ParameterValue.steps 2)
--> [ ( Point2d.fromCoordinates ( 1, 1 )
-->   , Direction2d.fromAngle (degrees 56.31)
-->   )
--> , ( Point2d.fromCoordinates ( 4, 2.5 )
-->   , Direction2d.fromAngle (degrees 0)
-->   )
--> , ( Point2d.fromCoordinates ( 7, 4 )
-->   , Direction2d.fromAngle (degrees 56.31)
-->   )
--> ]

Transformations

reverse : CubicSpline2d -> CubicSpline2d

Reverse a spline so that the start point becomes the end point, and vice versa.

CubicSpline2d.reverse exampleSpline
--> CubicSpline2d.with
-->     { startPoint =
-->         Point2d.fromCoordinates ( 7, 4 )
-->     , startControlPoint =
-->         Point2d.fromCoordinates ( 5, 1 )
-->     , endControlPoint =
-->         Point2d.fromCoordinates ( 3, 4 )
-->     , endPoint =
-->         Point2d.fromCoordinates ( 1, 1 )
-->     }

scaleAbout : Point2d -> Basics.Float -> CubicSpline2d -> CubicSpline2d

Scale a spline about the given center point by the given scale.

CubicSpline2d.scaleAbout Point2d.origin 2 exampleSpline
--> CubicSpline2d.with
-->     { startPoint =
-->         Point2d.fromCoordinates ( 2, 2 )
-->     , startControlPoint =
-->         Point2d.fromCoordinates ( 6, 8 )
-->     , endControlPoint =
-->         Point2d.fromCoordinates ( 10, 2 )
-->     , endPoint =
-->         Point2d.fromCoordinates ( 14, 8 )
-->     }

rotateAround : Point2d -> Basics.Float -> CubicSpline2d -> CubicSpline2d

Rotate a spline counterclockwise around a given center point by a given angle (in radians).

exampleSpline
    |> CubicSpline2d.rotateAround Point2d.origin
        (degrees 90)
--> CubicSpline2d.with
-->     { startPoint =
-->         Point2d.fromCoordinates ( -1, 1 )
-->     , startControlPoint =
-->         Point2d.fromCoordinates ( -4, 3 )
-->     , endControlPoint =
-->         Point2d.fromCoordinates ( -1, 5 )
-->     , endPoint =
-->         Point2d.fromCoordinates ( -4, 7 )
-->     }

translateBy : Vector2d -> CubicSpline2d -> CubicSpline2d

Translate a spline by a given displacement.

displacement =
    Vector2d.fromComponents ( 2, 3 )

CubicSpline2d.translateBy displacement exampleSpline
--> CubicSpline2d.with
-->     { startPoint =
-->         Point2d.fromCoordinates ( 3, 4 )
-->     , startControlPoint =
-->         Point2d.fromCoordinates ( 5, 7 )
-->     , endControlPoint =
-->         Point2d.fromCoordinates ( 7, 4 )
-->     , endPoint =
-->         Point2d.fromCoordinates ( 9, 7 )
-->     }

translateIn : Direction2d -> Basics.Float -> CubicSpline2d -> CubicSpline2d

Translate a spline in a given direction by a given distance;

CubicSpline2d.translateIn direction distance

is equivalent to

CubicSpline2d.translateBy
    (Vector2d.withLength distance direction)

mirrorAcross : Axis2d -> CubicSpline2d -> CubicSpline2d

Mirror a spline across an axis.

CubicSpline2d.mirrorAcross Axis2d.x exampleSpline
--> CubicSpline2d.with
-->     { startPoint =
-->         Point2d.fromCoordinates ( 1, -1 )
-->     , startControlPoint =
-->         Point2d.fromCoordinates ( 3, -4 )
-->     , endControlPoint =
-->         Point2d.fromCoordinates ( 5, -1 )
-->     , endPoint =
-->         Point2d.fromCoordinates ( 7, -4 )
-->     }

Coordinate conversions

relativeTo : Frame2d -> CubicSpline2d -> CubicSpline2d

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

localFrame =
    Frame2d.atPoint (Point2d.fromCoordinates ( 1, 2 ))

CubicSpline2d.relativeTo localFrame exampleSpline
--> CubicSpline2d.with
-->     { startPoint =
-->         Point2d.fromCoordinates ( 0, -1 )
-->     , startControlPoint =
-->         Point2d.fromCoordinates ( 2, 2 )
-->     , endControlPoint =
-->         Point2d.fromCoordinates ( 4, -1 )
-->     , endPoint =
-->         Point2d.fromCoordinates ( 6, 2 )
-->     }

placeIn : Frame2d -> CubicSpline2d -> CubicSpline2d

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

localFrame =
    Frame2d.atPoint (Point2d.fromCoordinates ( 1, 2 ))

CubicSpline2d.placeIn localFrame exampleSpline
--> CubicSpline2d.with
-->     { startPoint =
-->         Point2d.fromCoordinates ( 2, 3 )
-->     , startControlPoint =
-->         Point2d.fromCoordinates ( 4, 6 )
-->     , endControlPoint =
-->         Point2d.fromCoordinates ( 6, 3 )
-->     , endPoint =
-->         Point2d.fromCoordinates ( 8, 6 )
-->     }

Subdivision

bisect : CubicSpline2d -> ( CubicSpline2d, CubicSpline2d )

Split a spline into two roughly equal halves.

CubicSpline2d.bisect exampleSpline
--> ( CubicSpline2d.with
-->     { startPoint =
-->         Point2d.fromCoordinates ( 1, 1 )
-->     , startControlPoint =
-->         Point2d.fromCoordinates ( 2, 2.5 )
-->     , endControlPoint =
-->         Point2d.fromCoordinates ( 3, 2.5 )
-->     , endPoint =
-->         Point2d.fromCoordinates ( 4, 2.5 )
-->     }
--> , CubicSpline2d.with
-->     { startPoint =
-->         Point2d.fromCoordinates ( 4, 2.5 )
-->     , startControlPoint =
-->         Point2d.fromCoordinates ( 5, 2.5 )
-->     , endControlPoint =
-->         Point2d.fromCoordinates ( 6, 2.5 )
-->     , endPoint =
-->         Point2d.fromCoordinates ( 7, 4 )
-->     }
--> )

Equivalent to CubicSpline2d.splitAt ParameterValue.half.

splitAt : Curve.ParameterValue.ParameterValue -> CubicSpline2d -> ( CubicSpline2d, CubicSpline2d )

Split a spline at a particular parameter value, resulting in two smaller splines.

parameterValue =
    ParameterValue.clamped 0.75

CubicSpline2d.splitAt parameterValue exampleSpline
--> ( CubicSpline2d.with
-->     { startPoint =
-->         Point2d.fromCoordinates ( 1, 1 )
-->     , startControlPoint =
-->         Point2d.fromCoordinates ( 2.5, 3.25 )
-->     , endControlPoint =
-->         Point2d.fromCoordinates ( 4, 2.125 )
-->     , endPoint =
-->         Point2d.fromCoordinates ( 5.5, 2.6875 )
-->     }
--> , CubicSpline2d.with
-->     { startPoint =
-->         Point2d.fromCoordinates ( 5.5, 2.6875 )
-->     , startControlPoint =
-->         Point2d.fromCoordinates ( 6, 2.875 )
-->     , endControlPoint =
-->         Point2d.fromCoordinates ( 6.5, 3.25 )
-->     , endPoint =
-->         Point2d.fromCoordinates ( 7, 4 )
-->     }
--> )

Arc length parameterization


type ArcLengthParameterized

A spline that has been parameterized by arc length.

arcLengthParameterized : { maxError : Basics.Float } -> CubicSpline2d -> ArcLengthParameterized

Build an arc length parameterization of the given spline, with a given accuracy. Generally speaking, all operations on the resulting ArcLengthParameterized value will be accurate to within the specified maximum error.

parameterizedSpline =
    exampleSpline
        |> CubicSpline2d.arcLengthParameterized
            { maxError = 1.0e-4 }

The accuracy of the parameterization affects the accuracy of results returned from functions such as arcLength and pointAlong.

arcLength : ArcLengthParameterized -> Basics.Float

Find the total arc length of a spline:

arcLength =
    CubicSpline2d.arcLength parameterizedSpline

arcLength
--> 7.0952

In this example, the result will be accurate to within 1.0e-4 since that was the tolerance used when constructing parameterizedSpline.

pointAlong : ArcLengthParameterized -> Basics.Float -> Maybe Point2d

Try to get the point along a spline at a given arc length. For example, to get the point a quarter of the way along exampleSpline, using arcLength as computed above:

CubicSpline2d.pointAlong parameterizedSpline
    (0.25 * arcLength)
--> Just (Point2d.fromCoordinates ( 2.2681, 2.2114 ))

Note that this is not the same as evaulating at a parameter value of 0.25:

CubicSpline2d.pointOn exampleSpline
    (ParameterValue.clamped 0.25)
--> Point2d.fromCoordinates ( 2.5, 2.3125 )

If the given arc length is less than zero or greater than the arc length of the spline, returns Nothing.

tangentDirectionAlong : ArcLengthParameterized -> Basics.Float -> Maybe Direction2d

Try to get the tangent direction along a spline at a given arc length. To get the tangent direction a quarter of the way along exampleSpline:

CubicSpline2d.tangentDirectionAlong parameterizedSpline
    (0.25 * arcLength)
--> Just (Direction2d.fromAngle (degrees 26.5611))

If the given arc length is less than zero or greater than the arc length of the spline (or if the spline is degenerate), returns Nothing.

sampleAlong : ArcLengthParameterized -> Basics.Float -> Maybe ( Point2d, Direction2d )

Try to get the point and tangent direction along a spline at a given arc length. To get the point and tangent direction a quarter of the way along exampleSpline:

CubicSpline2d.sampleAlong parameterizedSpline
    (0.25 * arcLength)
--> Just
-->     ( Point2d.fromCoordinates ( 2.2681, 2.2114 )
-->     , Direction2d.fromAngle (degrees 26.5611)
-->     )

If the given arc length is less than zero or greater than the arc length of the spline (or if the spline is degenerate), returns Nothing.

Low level

An ArcLengthParameterized value is a combination of an ArcLengthParameterization and an underlying CubicSpline2d. If you need to do something fancy, you can extract these two values separately.

arcLengthParameterization : ArcLengthParameterized -> Curve.ArcLengthParameterization.ArcLengthParameterization

fromArcLengthParameterized : ArcLengthParameterized -> CubicSpline2d

Differentiation

You are unlikely to need to use these functions directly, but they are useful if you are writing low-level geometric algorithms.

firstDerivative : CubicSpline2d -> Curve.ParameterValue.ParameterValue -> Vector2d

Get the first derivative of a spline at a given parameter value:

CubicSpline2d.firstDerivative exampleSpline
    ParameterValue.zero
--> Vector2d.fromComponents ( 6, 9 )

CubicSpline2d.firstDerivative exampleSpline
    ParameterValue.half
--> Vector2d.fromComponents ( 6, 0 )

CubicSpline2d.firstDerivative exampleSpline
    ParameterValue.one
--> Vector2d.fromComponents ( 6, 9 )

firstDerivativesAt : List Curve.ParameterValue.ParameterValue -> CubicSpline2d -> List Vector2d

Evaluate the first derivative of a spline at a given set of parameter values:

exampleSpline
    |> CubicSpline2d.firstDerivativesAt
        (ParameterValue.steps 2)
--> [ Vector2d.fromComponents ( 6, 9 )
--> , Vector2d.fromComponents ( 6, 0 )
--> , Vector2d.fromComponents ( 6, 9 )
--> ]

secondDerivative : CubicSpline2d -> Curve.ParameterValue.ParameterValue -> Vector2d

Evaluate the second derivative of a spline at a given parameter value:

CubicSpline2d.secondDerivativeAt 0 exampleSpline
--> Just (Vector2d.fromComponents ( 0, -36 ))

CubicSpline2d.secondDerivativeAt 0.5 exampleSpline
--> Just (Vector2d.fromComponents ( 0, 0 ))

CubicSpline2d.secondDerivativeAt 1 exampleSpline
--> Just (Vector2d.fromComponents ( 0, 36 ))

secondDerivativesAt : List Curve.ParameterValue.ParameterValue -> CubicSpline2d -> List Vector2d

Evaluate the second derivative of a spline at a given set of parameter values:

exampleSpline
    |> CubicSpline2d.secondDerivativesAt
        (ParameterValue.steps 2)
--> [ Vector2d.fromComponents ( 0, -36 )
--> , Vector2d.fromComponents ( 0, 0 )
--> , Vector2d.fromComponents ( 0, 36 )
--> ]

thirdDerivative : CubicSpline2d -> Vector2d

Get the third derivative of a spline (for a cubic spline, this is a constant):

CubicSpline2d.thirdDerivative exampleSpline
--> Vector2d.fromComponents ( 0, 72 )

maxSecondDerivativeMagnitude : CubicSpline2d -> Basics.Float

Find a conservative upper bound on the magnitude of the second derivative of a spline. This can be useful when determining error bounds for various kinds of linear approximations.

exampleSpline
    |> CubicSpline2d.maxSecondDerivativeMagnitude
--> 36