This is an alternative site for discovering Elm packages. You may be looking for the official Elm package site instead.

Arc2d

Arc2d

An Arc2d is a section of a circle, defined by its center point, start point and swept angle (the counterclockwise angle from the start point to the end point). This module includes functionality for

  • Constructing arcs through given points and/or with a given radius
  • Scaling, rotating, translating and mirroring arcs
  • Converting arcs between different coordinate systems
type alias Arc2d = Types.Arc2d

Constructors

from : Point2d -> Point2d -> Float -> Arc2d
with : { centerPoint : Point2d, radius : Float, startAngle : Float, sweptAngle : Float } -> Arc2d
sweptAround : Point2d -> Float -> Point2d -> Arc2d
throughPoints : Point2d -> Point2d -> Point2d -> Maybe Arc2d
withRadius : Float -> SweptAngle -> Point2d -> Point2d -> Maybe Arc2d

Properties

centerPoint : Arc2d -> Point2d
radius : Arc2d -> Float
startPoint : Arc2d -> Point2d
endPoint : Arc2d -> Point2d
sweptAngle : Arc2d -> Float

Evaluation

pointOn : Arc2d -> ParameterValue -> Point2d
pointsAt : List ParameterValue -> Arc2d -> List Point2d
type Nondegenerate = Nondegenerate Arc2d
nondegenerate : Arc2d -> Result Point2d Nondegenerate
fromNondegenerate : Nondegenerate -> Arc2d
tangentDirection : Nondegenerate -> ParameterValue -> Direction2d
tangentDirectionsAt : List ParameterValue -> Nondegenerate -> List Direction2d
sample : Nondegenerate -> ParameterValue -> ( Point2d, Direction2d )
samplesAt : List ParameterValue -> Nondegenerate -> List ( Point2d, Direction2d )

Linear approximation

toPolyline : { maxError : Float } -> Arc2d -> Polyline2d

Transformations

reverse : Arc2d -> Arc2d
scaleAbout : Point2d -> Float -> Arc2d -> Arc2d
rotateAround : Point2d -> Float -> Arc2d -> Arc2d
translateBy : Vector2d -> Arc2d -> Arc2d
translateIn : Direction2d -> Float -> Arc2d -> Arc2d
mirrorAcross : Axis2d -> Arc2d -> Arc2d

Coordinate conversions

relativeTo : Frame2d -> Arc2d -> Arc2d
placeIn : Frame2d -> Arc2d -> Arc2d

Differentiation

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

firstDerivative : Arc2d -> ParameterValue -> Vector2d
firstDerivativesAt : List ParameterValue -> Arc2d -> List Vector2d
module Arc2d
    exposing
        ( Arc2d
        , Nondegenerate
        , centerPoint
        , endPoint
        , firstDerivative
        , firstDerivativesAt
        , from
        , fromNondegenerate
        , mirrorAcross
        , nondegenerate
        , placeIn
        , pointOn
        , pointsAt
        , radius
        , relativeTo
        , reverse
        , rotateAround
        , sample
        , samplesAt
        , scaleAbout
        , startPoint
        , sweptAngle
        , sweptAround
        , tangentDirection
        , tangentDirectionsAt
        , throughPoints
        , toPolyline
        , translateBy
        , translateIn
        , with
        , withRadius
        )

{-| <img src="https://ianmackenzie.github.io/elm-geometry/1.0.0/Arc2d/icon.svg" alt="Arc2d" width="160">

An `Arc2d` is a section of a circle, defined by its center point, start
point and swept angle (the counterclockwise angle from the start point to the
end point). This module includes functionality for

  - Constructing arcs through given points and/or with a given radius
  - Scaling, rotating, translating and mirroring arcs
  - Converting arcs between different coordinate systems

@docs Arc2d


# Constructors

@docs from, with, sweptAround, throughPoints, withRadius


# Properties

@docs centerPoint, radius, startPoint, endPoint, sweptAngle


# Evaluation

@docs pointOn, pointsAt
@docs Nondegenerate, nondegenerate, fromNondegenerate
@docs tangentDirection, tangentDirectionsAt, sample, samplesAt


# Linear approximation

@docs toPolyline


# Transformations

@docs reverse, scaleAbout, rotateAround, translateBy, translateIn, mirrorAcross


# Coordinate conversions

@docs relativeTo, placeIn


# Differentiation

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

@docs firstDerivative, firstDerivativesAt

-}

import Arc.SweptAngle as SweptAngle exposing (SweptAngle)
import Axis2d exposing (Axis2d)
import Curve.ParameterValue as ParameterValue exposing (ParameterValue)
import Direction2d exposing (Direction2d)
import Frame2d exposing (Frame2d)
import Geometry.Types as Types
import LineSegment2d exposing (LineSegment2d)
import Point2d exposing (Point2d)
import Polyline2d exposing (Polyline2d)
import Vector2d exposing (Vector2d)


{-| -}
type alias Arc2d =
    Types.Arc2d


twoPi : Float
twoPi =
    2 * pi


{-| -}
from : Point2d -> Point2d -> Float -> Arc2d
from startPoint_ endPoint_ sweptAngle_ =
    let
        displacement =
            Vector2d.from startPoint_ endPoint_
    in
    case Vector2d.lengthAndDirection displacement of
        Just ( distance, direction ) ->
            let
                angleModTwoPi =
                    sweptAngle_ - twoPi * toFloat (floor (sweptAngle_ / twoPi))

                radius_ =
                    distance / (2 * abs (sin (sweptAngle_ / 2)))
            in
            Types.Arc2d
                { startPoint = startPoint_
                , sweptAngle = sweptAngle_
                , xDirection =
                    direction |> Direction2d.rotateBy (-angleModTwoPi / 2)
                , signedLength =
                    if sweptAngle_ == 0.0 then
                        distance
                    else
                        radius_ * sweptAngle_
                }

        Nothing ->
            Types.Arc2d
                { startPoint = startPoint_
                , sweptAngle = sweptAngle_
                , xDirection = Direction2d.x
                , signedLength = 0
                }


{-| -}
with : { centerPoint : Point2d, radius : Float, startAngle : Float, sweptAngle : Float } -> Arc2d
with properties =
    let
        ( x0, y0 ) =
            Point2d.coordinates properties.centerPoint
    in
    Types.Arc2d
        { startPoint =
            Point2d.fromCoordinates
                ( x0 + properties.radius * cos properties.startAngle
                , y0 + properties.radius * sin properties.startAngle
                )
        , sweptAngle = properties.sweptAngle
        , xDirection =
            Direction2d.fromAngle (properties.startAngle + degrees 90)
        , signedLength = abs properties.radius * properties.sweptAngle
        }


{-| -}
sweptAround : Point2d -> Float -> Point2d -> Arc2d
sweptAround centerPoint_ sweptAngle_ startPoint_ =
    case Vector2d.lengthAndDirection (Vector2d.from startPoint_ centerPoint_) of
        Just ( radius_, yDirection ) ->
            Types.Arc2d
                { startPoint = startPoint_
                , xDirection = yDirection |> Direction2d.rotateClockwise
                , sweptAngle = sweptAngle_
                , signedLength = radius_ * sweptAngle_
                }

        Nothing ->
            Types.Arc2d
                { startPoint = startPoint_
                , xDirection = Direction2d.x
                , sweptAngle = sweptAngle_
                , signedLength = 0
                }


{-| -}
throughPoints : Point2d -> Point2d -> Point2d -> Maybe Arc2d
throughPoints firstPoint secondPoint thirdPoint =
    Point2d.circumcenter firstPoint secondPoint thirdPoint
        |> Maybe.andThen
            (\centerPoint_ ->
                let
                    firstVector =
                        Vector2d.from centerPoint_ firstPoint

                    secondVector =
                        Vector2d.from centerPoint_ secondPoint

                    thirdVector =
                        Vector2d.from centerPoint_ thirdPoint
                in
                Maybe.map3
                    (\firstDirection secondDirection thirdDirection ->
                        let
                            partial =
                                Direction2d.angleFrom firstDirection
                                    secondDirection

                            full =
                                Direction2d.angleFrom firstDirection
                                    thirdDirection

                            sweptAngle_ =
                                if partial >= 0 && full >= partial then
                                    full
                                else if partial <= 0 && full <= partial then
                                    full
                                else if full >= 0 then
                                    full - 2 * pi
                                else
                                    full + 2 * pi
                        in
                        firstPoint |> sweptAround centerPoint_ sweptAngle_
                    )
                    (Vector2d.direction firstVector)
                    (Vector2d.direction secondVector)
                    (Vector2d.direction thirdVector)
            )


{-| -}
withRadius : Float -> SweptAngle -> Point2d -> Point2d -> Maybe Arc2d
withRadius radius_ sweptAngle_ startPoint_ endPoint_ =
    let
        chord =
            LineSegment2d.from startPoint_ endPoint_

        squaredRadius =
            radius_ * radius_

        squaredHalfLength =
            LineSegment2d.squaredLength chord / 4
    in
    if squaredRadius >= squaredHalfLength then
        LineSegment2d.perpendicularDirection chord
            |> Maybe.map
                (\offsetDirection ->
                    let
                        offsetMagnitude =
                            sqrt (squaredRadius - squaredHalfLength)

                        offsetDistance =
                            case sweptAngle_ of
                                Types.SmallPositive ->
                                    offsetMagnitude

                                Types.SmallNegative ->
                                    -offsetMagnitude

                                Types.LargeNegative ->
                                    offsetMagnitude

                                Types.LargePositive ->
                                    -offsetMagnitude

                        offset =
                            Vector2d.withLength offsetDistance offsetDirection

                        midpoint =
                            LineSegment2d.midpoint chord

                        centerPoint_ =
                            Point2d.translateBy offset midpoint

                        halfLength =
                            sqrt squaredHalfLength

                        shortAngle =
                            2 * asin (halfLength / radius_)

                        sweptAngleInRadians =
                            case sweptAngle_ of
                                Types.SmallPositive ->
                                    shortAngle

                                Types.SmallNegative ->
                                    -shortAngle

                                Types.LargePositive ->
                                    2 * pi - shortAngle

                                Types.LargeNegative ->
                                    shortAngle - 2 * pi
                    in
                    startPoint_ |> sweptAround centerPoint_ sweptAngleInRadians
                )
    else
        Nothing


{-| -}
centerPoint : Arc2d -> Point2d
centerPoint (Types.Arc2d arc) =
    let
        ( x0, y0 ) =
            Point2d.coordinates arc.startPoint

        ( dx, dy ) =
            Direction2d.components arc.xDirection

        r =
            arc.signedLength / arc.sweptAngle
    in
    Point2d.fromCoordinates ( x0 - r * dy, y0 + r * dx )


{-| -}
radius : Arc2d -> Float
radius (Types.Arc2d arc) =
    arc.signedLength / arc.sweptAngle


{-| -}
startPoint : Arc2d -> Point2d
startPoint (Types.Arc2d properties) =
    properties.startPoint


{-| -}
endPoint : Arc2d -> Point2d
endPoint arc =
    pointOn arc ParameterValue.one


{-| -}
sweptAngle : Arc2d -> Float
sweptAngle (Types.Arc2d properties) =
    properties.sweptAngle


{-| -}
pointOn : Arc2d -> ParameterValue -> Point2d
pointOn (Types.Arc2d arc) parameterValue =
    let
        ( x0, y0 ) =
            Point2d.coordinates arc.startPoint

        ( dx, dy ) =
            Direction2d.components arc.xDirection

        arcSignedLength =
            arc.signedLength

        arcSweptAngle =
            arc.sweptAngle

        t =
            ParameterValue.value parameterValue
    in
    if arcSweptAngle == 0.0 then
        let
            distance =
                t * arcSignedLength
        in
        Point2d.fromCoordinates
            ( x0 + distance * dx
            , y0 + distance * dy
            )
    else
        let
            theta =
                t * arcSweptAngle

            arcRadius =
                arcSignedLength / arcSweptAngle

            x =
                arcRadius * sin theta

            y =
                if abs theta < pi / 2 then
                    x * tan (theta / 2)
                else
                    arcRadius * (1 - cos theta)
        in
        Point2d.fromCoordinates
            ( x0 + x * dx - y * dy
            , y0 + x * dy + y * dx
            )


{-| -}
pointsAt : List ParameterValue -> Arc2d -> List Point2d
pointsAt parameterValues arc =
    List.map (pointOn arc) parameterValues


{-| -}
firstDerivative : Arc2d -> ParameterValue -> Vector2d
firstDerivative (Types.Arc2d arc) =
    let
        startDerivative =
            Vector2d.withLength arc.signedLength arc.xDirection
    in
    \parameterValue ->
        let
            t =
                ParameterValue.value parameterValue
        in
        startDerivative |> Vector2d.rotateBy (t * arc.sweptAngle)


{-| -}
firstDerivativesAt : List ParameterValue -> Arc2d -> List Vector2d
firstDerivativesAt parameterValues arc =
    List.map (firstDerivative arc) parameterValues


{-| -}
type Nondegenerate
    = Nondegenerate Arc2d


{-| -}
nondegenerate : Arc2d -> Result Point2d Nondegenerate
nondegenerate arc =
    let
        (Types.Arc2d properties) =
            arc
    in
    if properties.signedLength == 0 then
        Err (startPoint arc)
    else
        Ok (Nondegenerate arc)


{-| -}
fromNondegenerate : Nondegenerate -> Arc2d
fromNondegenerate (Nondegenerate arc) =
    arc


{-| -}
tangentDirection : Nondegenerate -> ParameterValue -> Direction2d
tangentDirection (Nondegenerate (Types.Arc2d arc)) parameterValue =
    let
        t =
            ParameterValue.value parameterValue
    in
    arc.xDirection |> Direction2d.rotateBy (t * arc.sweptAngle)


{-| -}
tangentDirectionsAt : List ParameterValue -> Nondegenerate -> List Direction2d
tangentDirectionsAt parameterValues nondegenerateArc =
    List.map (tangentDirection nondegenerateArc) parameterValues


{-| -}
sample : Nondegenerate -> ParameterValue -> ( Point2d, Direction2d )
sample nondegenerateArc parameterValue =
    ( pointOn (fromNondegenerate nondegenerateArc) parameterValue
    , tangentDirection nondegenerateArc parameterValue
    )


{-| -}
samplesAt : List ParameterValue -> Nondegenerate -> List ( Point2d, Direction2d )
samplesAt parameterValues nondegenerateArc =
    List.map (sample nondegenerateArc) parameterValues


numApproximationSegments : Float -> Arc2d -> Int
numApproximationSegments maxError arc =
    if sweptAngle arc == 0 then
        1
    else if maxError <= 0 then
        0
    else if maxError >= 2 * radius arc then
        1
    else
        let
            maxSegmentAngle =
                2 * acos (1 - maxError / radius arc)
        in
        ceiling (abs (sweptAngle arc) / maxSegmentAngle)


{-| -}
toPolyline : { maxError : Float } -> Arc2d -> Polyline2d
toPolyline { maxError } arc =
    let
        numSegments =
            numApproximationSegments maxError arc

        points =
            arc |> pointsAt (ParameterValue.steps numSegments)
    in
    Polyline2d.fromVertices points


{-| -}
reverse : Arc2d -> Arc2d
reverse ((Types.Arc2d arc) as arc_) =
    Types.Arc2d
        { startPoint = endPoint arc_
        , sweptAngle = -arc.sweptAngle
        , signedLength = -arc.signedLength
        , xDirection = arc.xDirection |> Direction2d.rotateBy arc.sweptAngle
        }


{-| -}
scaleAbout : Point2d -> Float -> Arc2d -> Arc2d
scaleAbout point scale (Types.Arc2d arc) =
    Types.Arc2d
        { startPoint = Point2d.scaleAbout point scale arc.startPoint
        , sweptAngle = arc.sweptAngle
        , signedLength = abs scale * arc.signedLength
        , xDirection =
            if scale >= 0 then
                arc.xDirection
            else
                Direction2d.reverse arc.xDirection
        }


{-| -}
rotateAround : Point2d -> Float -> Arc2d -> Arc2d
rotateAround point angle =
    let
        rotatePoint =
            Point2d.rotateAround point angle

        rotateDirection =
            Direction2d.rotateBy angle
    in
    \(Types.Arc2d arc) ->
        Types.Arc2d
            { startPoint = rotatePoint arc.startPoint
            , sweptAngle = arc.sweptAngle
            , signedLength = arc.signedLength
            , xDirection = rotateDirection arc.xDirection
            }


{-| -}
translateBy : Vector2d -> Arc2d -> Arc2d
translateBy displacement (Types.Arc2d arc) =
    Types.Arc2d
        { startPoint = Point2d.translateBy displacement arc.startPoint
        , sweptAngle = arc.sweptAngle
        , signedLength = arc.signedLength
        , xDirection = arc.xDirection
        }


{-| -}
translateIn : Direction2d -> Float -> Arc2d -> Arc2d
translateIn direction distance arc =
    translateBy (Vector2d.withLength distance direction) arc


{-| -}
mirrorAcross : Axis2d -> Arc2d -> Arc2d
mirrorAcross axis =
    let
        mirrorPoint =
            Point2d.mirrorAcross axis

        mirrorDirection =
            Direction2d.mirrorAcross axis
    in
    \(Types.Arc2d arc) ->
        Types.Arc2d
            { startPoint = mirrorPoint arc.startPoint
            , sweptAngle = -arc.sweptAngle
            , signedLength = -arc.signedLength
            , xDirection = Direction2d.reverse (mirrorDirection arc.xDirection)
            }


{-| -}
relativeTo : Frame2d -> Arc2d -> Arc2d
relativeTo frame (Types.Arc2d arc) =
    if Frame2d.isRightHanded frame then
        Types.Arc2d
            { startPoint = Point2d.relativeTo frame arc.startPoint
            , sweptAngle = arc.sweptAngle
            , signedLength = arc.signedLength
            , xDirection = Direction2d.relativeTo frame arc.xDirection
            }
    else
        Types.Arc2d
            { startPoint = Point2d.relativeTo frame arc.startPoint
            , sweptAngle = -arc.sweptAngle
            , signedLength = -arc.signedLength
            , xDirection =
                Direction2d.reverse
                    (Direction2d.relativeTo frame arc.xDirection)
            }


{-| -}
placeIn : Frame2d -> Arc2d -> Arc2d
placeIn frame (Types.Arc2d arc) =
    if Frame2d.isRightHanded frame then
        Types.Arc2d
            { startPoint = Point2d.placeIn frame arc.startPoint
            , sweptAngle = arc.sweptAngle
            , signedLength = arc.signedLength
            , xDirection = Direction2d.placeIn frame arc.xDirection
            }
    else
        Types.Arc2d
            { startPoint = Point2d.placeIn frame arc.startPoint
            , sweptAngle = -arc.sweptAngle
            , signedLength = -arc.signedLength
            , xDirection =
                Direction2d.reverse (Direction2d.placeIn frame arc.xDirection)
            }