<commit_before>module Arc3d
    exposing
        ( Arc3d
        , axialDirection
        , axis
        , centerPoint
        , endPoint
        , firstDerivative
        , firstDerivativesAt
        , mirrorAcross
        , on
        , placeIn
        , pointOn
        , pointsAt
        , projectInto
        , radius
        , relativeTo
        , reverse
        , rotateAround
        , sampler
        , samplesAt
        , scaleAbout
        , startPoint
        , sweptAngle
        , sweptAround
        , throughPoints
        , toPolyline
        , translateBy
        , translateIn
        )

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

An `Arc3d` is a section of a circle in 3D, defined by its central axis,
start point and swept angle (the counterclockwise angle around the axis from the
start point to the arc's end point). This module includes functionality for

  - Constructing arcs through given points
  - Scaling, rotating, translating and mirroring arcs
  - Converting arcs between different coordinate systems

@docs Arc3d


# Constructors

@docs on, sweptAround, throughPoints


# Properties

@docs axialDirection, axis, centerPoint, radius, startPoint, endPoint, sweptAngle


# Evaluation

@docs pointOn, pointsAt, sampler, samplesAt


# Linear approximation

@docs toPolyline


# Transformations

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


# 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 Arc2d exposing (Arc2d)
import Axis3d exposing (Axis3d)
import Curve.ParameterValue as ParameterValue exposing (ParameterValue)
import Direction2d exposing (Direction2d)
import Direction3d exposing (Direction3d)
import Frame2d exposing (Frame2d)
import Frame3d exposing (Frame3d)
import Geometry.Types as Types
import Plane3d exposing (Plane3d)
import Point3d exposing (Point3d)
import Polyline3d exposing (Polyline3d)
import SketchPlane3d exposing (SketchPlane3d)
import Vector3d exposing (Vector3d)


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


{-| Construct a 3D arc lying _on_ a sketch plane by providing a 2D arc specified
in XY coordinates _within_ the sketch plane.

    arc =
        Arc3d.on SketchPlane3d.xz
            (Point2d.fromCoordinates ( 3, 1 )
                |> Arc2d.sweptAround
                    (Point2d.fromCoordinates ( 1, 1 )
                    (degrees 90)
            )

    Arc3d.centerPoint arc
    --> Point3d.fromCoordinates ( 1, 0, 1 )

    Arc3d.radius arc
    --> 2

    Arc3d.startPoint arc
    --> Point3d.fromCoordinates ( 3, 0, 1 )

    Arc3d.endPoint arc
    --> Point3d.fromCoordinates ( 1, 0, 3 )

-}
on : SketchPlane3d -> Arc2d -> Arc3d
on sketchPlane (Types.Arc2d arc2d) =
    Types.Arc3d
        { startPoint = Point3d.on sketchPlane arc2d.startPoint
        , xDirection = Direction3d.on sketchPlane arc2d.xDirection
        , yDirection =
            Direction3d.on sketchPlane
                (Direction2d.perpendicularTo arc2d.xDirection)
        , sweptAngle = arc2d.sweptAngle
        , signedLength = arc2d.signedLength
        }


{-| Construct an arc by sweeping the given point around the given axis by the
given angle:

    exampleArc =
        Point3d.fromCoordinates ( 1, 1, 0 )
            |> Arc3d.sweptAround Axis3d.z (degrees 90)

    Arc3d.centerPoint exampleArc
    --> Point3d.origin

    Arc3d.endPoint exampleArc
    --> Point3d.fromCoordinates ( -1, 1, 0 )

Positive swept angles result in a counterclockwise (right-handed) rotation
around the given axis and vice versa for negative swept angles. The center point
of the returned arc will lie on the given axis.

-}
sweptAround : Axis3d -> Float -> Point3d -> Arc3d
sweptAround axis_ sweptAngle_ startPoint_ =
    let
        centerPoint_ =
            startPoint_ |> Point3d.projectOntoAxis axis_

        axisDirection =
            Axis3d.direction axis_
    in
    case Vector3d.lengthAndDirection (Vector3d.from startPoint_ centerPoint_) of
        Just ( radius_, yDirection ) ->
            let
                xDirectionVector =
                    Vector3d.crossProduct
                        (Direction3d.toVector yDirection)
                        (Direction3d.toVector axisDirection)

                xDirection =
                    Direction3d.unsafe (Vector3d.components xDirectionVector)
            in
            Types.Arc3d
                { startPoint = startPoint_
                , sweptAngle = sweptAngle_
                , signedLength = radius_ * sweptAngle_
                , xDirection = xDirection
                , yDirection = yDirection
                }

        Nothing ->
            let
                ( xDirection, yDirection ) =
                    Direction3d.perpendicularBasis axisDirection
            in
            Types.Arc3d
                { startPoint = startPoint_
                , sweptAngle = sweptAngle_
                , signedLength = 0
                , xDirection = xDirection
                , yDirection = yDirection
                }


{-| Attempt to construct an arc that starts at the first given point, passes
through the second given point and ends at the third given point. If the three
points are collinear, returns `Nothing`.

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

    p2 =
        Point3d.origin

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

    Arc3d.throughPoints p1 p2 p3
    --> Just
    -->     (Arc3d.on SketchPlane3d.yz
    -->         Point2d.fromCoordinates ( 0, 1 )
    -->             |> Arc2d.sweptAround
    -->                 (Point2d.fromCoordinates
    -->                     ( 0.5, 0.5 )
    -->                 )
    -->                 (degrees 180)
    -->     )

-}
throughPoints : Point3d -> Point3d -> Point3d -> Maybe Arc3d
throughPoints firstPoint secondPoint thirdPoint =
    SketchPlane3d.throughPoints firstPoint secondPoint thirdPoint
        |> Maybe.andThen
            (\sketchPlane ->
                Arc2d.throughPoints
                    (Point3d.projectInto sketchPlane firstPoint)
                    (Point3d.projectInto sketchPlane secondPoint)
                    (Point3d.projectInto sketchPlane thirdPoint)
                    |> Maybe.map (on sketchPlane)
            )


{-| Get the axial direction of an arc.

    Arc3d.axialDirection exampleArc
    --> Direction3d.z

-}
axialDirection : Arc3d -> Direction3d
axialDirection (Types.Arc3d arc) =
    let
        axialDirectionVector =
            Vector3d.crossProduct
                (Direction3d.toVector arc.xDirection)
                (Direction3d.toVector arc.yDirection)
    in
    Direction3d.unsafe (Vector3d.components axialDirectionVector)


{-| Get the central axis of an arc. The origin point of the axis will be equal
to the center point of the arc.

    Arc3d.axis exampleArc
    --> Axis3d.z

-}
axis : Arc3d -> Axis3d
axis arc =
    Axis3d.through (centerPoint arc) (axialDirection arc)


{-| Get the center point of an arc.

    Arc3d.centerPoint exampleArc
    --> Point3d.origin

-}
centerPoint : Arc3d -> Point3d
centerPoint (Types.Arc3d arc) =
    let
        radius_ =
            arc.signedLength / arc.sweptAngle
    in
    arc.startPoint |> Point3d.translateIn arc.yDirection radius_


{-| Get the radius of an arc.

    Arc3d.radius exampleArc
    --> 1.4142

-}
radius : Arc3d -> Float
radius (Types.Arc3d arc) =
    arc.signedLength / arc.sweptAngle


{-| Get the start point of an arc.

    Arc3d.startPoint exampleArc
    --> Point3d.fromCoordinates ( 1, 1, 0 )

-}
startPoint : Arc3d -> Point3d
startPoint (Types.Arc3d arc) =
    arc.startPoint


{-| Get the end point of an arc.

    Arc3d.endPoint exampleArc
    --> Point3d.fromCoordinates ( -1, 1, 0 )

-}
endPoint : Arc3d -> Point3d
endPoint arc =
    pointOn arc ParameterValue.one


{-| Get the point along an arc at a given parameter value. A parameter value of
0 corresponds to the start point of the arc and a value of 1 corresponds to the
end point.

    Arc3d.pointOn exampleArc 0.5
    --> Point3d.fromCoordinates ( 0, 1.4142, 0 )

-}
pointOn : Arc3d -> ParameterValue -> Point3d
pointOn (Types.Arc3d arc) =
    let
        ( x0, y0, z0 ) =
            Point3d.coordinates arc.startPoint

        ( x1, y1, z1 ) =
            Direction3d.components arc.xDirection

        ( x2, y2, z2 ) =
            Direction3d.components arc.yDirection

        arcSignedLength =
            arc.signedLength

        arcSweptAngle =
            arc.sweptAngle
    in
    if arcSweptAngle == 0.0 then
        \parameterValue ->
            let
                t =
                    ParameterValue.value parameterValue

                distance =
                    t * arcSignedLength
            in
            Point3d.fromCoordinates
                ( x0 + distance * x1
                , y0 + distance * y1
                , z0 + distance * z1
                )
    else
        let
            arcRadius =
                arcSignedLength / arcSweptAngle
        in
        \parameterValue ->
            let
                t =
                    ParameterValue.value parameterValue

                theta =
                    t * arcSweptAngle

                x =
                    arcRadius * sin theta

                y =
                    if abs theta < pi / 2 then
                        x * tan (theta / 2)
                    else
                        arcRadius * (1 - cos theta)
            in
            Point3d.fromCoordinates
                ( x0 + x * x1 + y * x2
                , y0 + x * y1 + y * y2
                , z0 + x * z1 + y * z2
                )


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


{-| Get the first derivative of an arc at a given parameter value.

    Arc3d.firstDerivative exampleArc ParameterValue.zero
    --> Vector3d.fromComponents ( -1.5708, 1.5708, 0 )

    Arc3d.firstDerivative exampleArc ParameterValue.one
    --> Vector3d.fromComponents ( -1.5708, -1.5708, 0 )

-}
firstDerivative : Arc3d -> ParameterValue -> Vector3d
firstDerivative (Types.Arc3d arc) =
    let
        ( x1, y1, z1 ) =
            Direction3d.components arc.xDirection

        ( x2, y2, z2 ) =
            Direction3d.components arc.yDirection

        arcSweptAngle =
            arc.sweptAngle

        arcSignedLength =
            arc.signedLength
    in
    \parameterValue ->
        let
            t =
                ParameterValue.value parameterValue

            angle =
                t * arcSweptAngle

            cosAngle =
                cos angle

            sinAngle =
                sin angle
        in
        Vector3d.fromComponents
            ( arcSignedLength * (cosAngle * x1 + sinAngle * x2)
            , arcSignedLength * (cosAngle * y1 + sinAngle * y2)
            , arcSignedLength * (cosAngle * z1 + sinAngle * z2)
            )


firstDerivativesAt : List ParameterValue -> Arc3d -> List Vector3d
firstDerivativesAt parameterValues arc =
    List.map (firstDerivative arc) parameterValues


{-| Sample an arc at a given parameter value to get both the position and
derivative vector at that parameter value. Equivalent to calling `pointOn` and
`derivative` separately.

    Arc3d.sample exampleArc 0
    --> ( Point3d.fromCoordinates ( 1, 1, 0 )
    --> , Vector3d.fromComponents ( -1.5708, 1.5708, 0 )
    --> )

    Arc3d.sample exampleArc 0.5
    --> ( Point3d.fromCoordinates ( 1.4142, 0, 0 )
    --> , Vector3d.fromComponents ( -2.2214, 0, 0 )
    --> )

    Arc3d.sample exampleArc 1
    --> ( Point3d.fromCoordinates ( -1, 1, 0 )
    --> , Vector3d.fromComponents ( -1.5708, -1.5708, 0 )
    --> )

-}
sampler : Arc3d -> Maybe (ParameterValue -> ( Point3d, Direction3d ))
sampler arc =
    let
        (Types.Arc3d properties) =
            arc
    in
    if properties.signedLength == 0 then
        Nothing
    else
        let
            pointOnArc =
                pointOn arc

            ( x1, y1, z1 ) =
                Direction3d.components properties.xDirection

            ( x2, y2, z2 ) =
                Direction3d.components properties.yDirection

            arcSweptAngle =
                properties.sweptAngle
        in
        Just <|
            \parameterValue ->
                let
                    point =
                        pointOnArc parameterValue

                    t =
                        ParameterValue.value parameterValue

                    angle =
                        t * arcSweptAngle

                    cosAngle =
                        cos angle

                    sinAngle =
                        sin angle

                    tangentDirection =
                        Direction3d.unsafe
                            ( cosAngle * x1 + sinAngle * x2
                            , cosAngle * y1 + sinAngle * y2
                            , cosAngle * z1 + sinAngle * z2
                            )
                in
                ( point, tangentDirection )


{-| -}
samplesAt : List ParameterValue -> Arc3d -> List ( Point3d, Direction3d )
samplesAt parameterValues arc =
    case sampler arc of
        Just sampler_ ->
            List.map sampler_ parameterValues

        Nothing ->
            []<commit_msg>Start updating Arc3d curve evaluation documentation<commit_after>module Arc3d
    exposing
        ( Arc3d
        , axialDirection
        , axis
        , centerPoint
        , endPoint
        , firstDerivative
        , firstDerivativesAt
        , mirrorAcross
        , on
        , placeIn
        , pointOn
        , pointsAt
        , projectInto
        , radius
        , relativeTo
        , reverse
        , rotateAround
        , sampler
        , samplesAt
        , scaleAbout
        , startPoint
        , sweptAngle
        , sweptAround
        , throughPoints
        , toPolyline
        , translateBy
        , translateIn
        )

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

An `Arc3d` is a section of a circle in 3D, defined by its central axis,
start point and swept angle (the counterclockwise angle around the axis from the
start point to the arc's end point). This module includes functionality for

  - Constructing arcs through given points
  - Scaling, rotating, translating and mirroring arcs
  - Converting arcs between different coordinate systems

@docs Arc3d


# Constructors

@docs on, sweptAround, throughPoints


# Properties

@docs axialDirection, axis, centerPoint, radius, startPoint, endPoint, sweptAngle


# Evaluation

@docs pointOn, pointsAt, sampler, samplesAt


# Linear approximation

@docs toPolyline


# Transformations

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


# 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 Arc2d exposing (Arc2d)
import Axis3d exposing (Axis3d)
import Curve.ParameterValue as ParameterValue exposing (ParameterValue)
import Direction2d exposing (Direction2d)
import Direction3d exposing (Direction3d)
import Frame2d exposing (Frame2d)
import Frame3d exposing (Frame3d)
import Geometry.Types as Types
import Plane3d exposing (Plane3d)
import Point3d exposing (Point3d)
import Polyline3d exposing (Polyline3d)
import SketchPlane3d exposing (SketchPlane3d)
import Vector3d exposing (Vector3d)


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


{-| Construct a 3D arc lying _on_ a sketch plane by providing a 2D arc specified
in XY coordinates _within_ the sketch plane.

    arc =
        Arc3d.on SketchPlane3d.xz
            (Point2d.fromCoordinates ( 3, 1 )
                |> Arc2d.sweptAround
                    (Point2d.fromCoordinates ( 1, 1 )
                    (degrees 90)
            )

    Arc3d.centerPoint arc
    --> Point3d.fromCoordinates ( 1, 0, 1 )

    Arc3d.radius arc
    --> 2

    Arc3d.startPoint arc
    --> Point3d.fromCoordinates ( 3, 0, 1 )

    Arc3d.endPoint arc
    --> Point3d.fromCoordinates ( 1, 0, 3 )

-}
on : SketchPlane3d -> Arc2d -> Arc3d
on sketchPlane (Types.Arc2d arc2d) =
    Types.Arc3d
        { startPoint = Point3d.on sketchPlane arc2d.startPoint
        , xDirection = Direction3d.on sketchPlane arc2d.xDirection
        , yDirection =
            Direction3d.on sketchPlane
                (Direction2d.perpendicularTo arc2d.xDirection)
        , sweptAngle = arc2d.sweptAngle
        , signedLength = arc2d.signedLength
        }


{-| Construct an arc by sweeping the given point around the given axis by the
given angle:

    exampleArc =
        Point3d.fromCoordinates ( 1, 1, 0 )
            |> Arc3d.sweptAround Axis3d.z (degrees 90)

    Arc3d.centerPoint exampleArc
    --> Point3d.origin

    Arc3d.endPoint exampleArc
    --> Point3d.fromCoordinates ( -1, 1, 0 )

Positive swept angles result in a counterclockwise (right-handed) rotation
around the given axis and vice versa for negative swept angles. The center point
of the returned arc will lie on the given axis.

-}
sweptAround : Axis3d -> Float -> Point3d -> Arc3d
sweptAround axis_ sweptAngle_ startPoint_ =
    let
        centerPoint_ =
            startPoint_ |> Point3d.projectOntoAxis axis_

        axisDirection =
            Axis3d.direction axis_
    in
    case Vector3d.lengthAndDirection (Vector3d.from startPoint_ centerPoint_) of
        Just ( radius_, yDirection ) ->
            let
                xDirectionVector =
                    Vector3d.crossProduct
                        (Direction3d.toVector yDirection)
                        (Direction3d.toVector axisDirection)

                xDirection =
                    Direction3d.unsafe (Vector3d.components xDirectionVector)
            in
            Types.Arc3d
                { startPoint = startPoint_
                , sweptAngle = sweptAngle_
                , signedLength = radius_ * sweptAngle_
                , xDirection = xDirection
                , yDirection = yDirection
                }

        Nothing ->
            let
                ( xDirection, yDirection ) =
                    Direction3d.perpendicularBasis axisDirection
            in
            Types.Arc3d
                { startPoint = startPoint_
                , sweptAngle = sweptAngle_
                , signedLength = 0
                , xDirection = xDirection
                , yDirection = yDirection
                }


{-| Attempt to construct an arc that starts at the first given point, passes
through the second given point and ends at the third given point. If the three
points are collinear, returns `Nothing`.

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

    p2 =
        Point3d.origin

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

    Arc3d.throughPoints p1 p2 p3
    --> Just
    -->     (Arc3d.on SketchPlane3d.yz
    -->         Point2d.fromCoordinates ( 0, 1 )
    -->             |> Arc2d.sweptAround
    -->                 (Point2d.fromCoordinates
    -->                     ( 0.5, 0.5 )
    -->                 )
    -->                 (degrees 180)
    -->     )

-}
throughPoints : Point3d -> Point3d -> Point3d -> Maybe Arc3d
throughPoints firstPoint secondPoint thirdPoint =
    SketchPlane3d.throughPoints firstPoint secondPoint thirdPoint
        |> Maybe.andThen
            (\sketchPlane ->
                Arc2d.throughPoints
                    (Point3d.projectInto sketchPlane firstPoint)
                    (Point3d.projectInto sketchPlane secondPoint)
                    (Point3d.projectInto sketchPlane thirdPoint)
                    |> Maybe.map (on sketchPlane)
            )


{-| Get the axial direction of an arc.

    Arc3d.axialDirection exampleArc
    --> Direction3d.z

-}
axialDirection : Arc3d -> Direction3d
axialDirection (Types.Arc3d arc) =
    let
        axialDirectionVector =
            Vector3d.crossProduct
                (Direction3d.toVector arc.xDirection)
                (Direction3d.toVector arc.yDirection)
    in
    Direction3d.unsafe (Vector3d.components axialDirectionVector)


{-| Get the central axis of an arc. The origin point of the axis will be equal
to the center point of the arc.

    Arc3d.axis exampleArc
    --> Axis3d.z

-}
axis : Arc3d -> Axis3d
axis arc =
    Axis3d.through (centerPoint arc) (axialDirection arc)


{-| Get the center point of an arc.

    Arc3d.centerPoint exampleArc
    --> Point3d.origin

-}
centerPoint : Arc3d -> Point3d
centerPoint (Types.Arc3d arc) =
    let
        radius_ =
            arc.signedLength / arc.sweptAngle
    in
    arc.startPoint |> Point3d.translateIn arc.yDirection radius_


{-| Get the radius of an arc.

    Arc3d.radius exampleArc
    --> 1.4142

-}
radius : Arc3d -> Float
radius (Types.Arc3d arc) =
    arc.signedLength / arc.sweptAngle


{-| Get the start point of an arc.

    Arc3d.startPoint exampleArc
    --> Point3d.fromCoordinates ( 1, 1, 0 )

-}
startPoint : Arc3d -> Point3d
startPoint (Types.Arc3d arc) =
    arc.startPoint


{-| Get the end point of an arc.

    Arc3d.endPoint exampleArc
    --> Point3d.fromCoordinates ( -1, 1, 0 )

-}
endPoint : Arc3d -> Point3d
endPoint arc =
    pointOn arc ParameterValue.one


{-| Get the point along an arc at a given parameter value. A parameter value of
0 corresponds to the start point of the arc and a value of 1 corresponds to the
end point.

    Arc3d.pointOn exampleArc ParameterValue.half
    --> Point3d.fromCoordinates ( 0, 1.4142, 0 )

-}
pointOn : Arc3d -> ParameterValue -> Point3d
pointOn (Types.Arc3d arc) =
    let
        ( x0, y0, z0 ) =
            Point3d.coordinates arc.startPoint

        ( x1, y1, z1 ) =
            Direction3d.components arc.xDirection

        ( x2, y2, z2 ) =
            Direction3d.components arc.yDirection

        arcSignedLength =
            arc.signedLength

        arcSweptAngle =
            arc.sweptAngle
    in
    if arcSweptAngle == 0.0 then
        \parameterValue ->
            let
                t =
                    ParameterValue.value parameterValue

                distance =
                    t * arcSignedLength
            in
            Point3d.fromCoordinates
                ( x0 + distance * x1
                , y0 + distance * y1
                , z0 + distance * z1
                )
    else
        let
            arcRadius =
                arcSignedLength / arcSweptAngle
        in
        \parameterValue ->
            let
                t =
                    ParameterValue.value parameterValue

                theta =
                    t * arcSweptAngle

                x =
                    arcRadius * sin theta

                y =
                    if abs theta < pi / 2 then
                        x * tan (theta / 2)
                    else
                        arcRadius * (1 - cos theta)
            in
            Point3d.fromCoordinates
                ( x0 + x * x1 + y * x2
                , y0 + x * y1 + y * y2
                , z0 + x * z1 + y * z2
                )


{-| Get points along an arc at a given set of parameter values.

    exampleArc |> Arc3d.pointsAt (ParameterValue.steps 2)
    --> [ Point3d ( 1, 1, 0 )
    --> , Point3d ( 0, 1.4142, 0 )
    --> , Point3d ( -1, 1, 0 )
    --> ]

-}
pointsAt : List ParameterValue -> Arc3d -> List Point3d
pointsAt parameterValues arc =
    List.map (pointOn arc) parameterValues


{-| Get the first derivative of an arc at a given parameter value.

    Arc3d.firstDerivative exampleArc ParameterValue.zero
    --> Vector3d.fromComponents ( -1.5708, 1.5708, 0 )

    Arc3d.firstDerivative exampleArc ParameterValue.one
    --> Vector3d.fromComponents ( -1.5708, -1.5708, 0 )

-}
firstDerivative : Arc3d -> ParameterValue -> Vector3d
firstDerivative (Types.Arc3d arc) =
    let
        ( x1, y1, z1 ) =
            Direction3d.components arc.xDirection

        ( x2, y2, z2 ) =
            Direction3d.components arc.yDirection

        arcSweptAngle =
            arc.sweptAngle

        arcSignedLength =
            arc.signedLength
    in
    \parameterValue ->
        let
            t =
                ParameterValue.value parameterValue

            angle =
                t * arcSweptAngle

            cosAngle =
                cos angle

            sinAngle =
                sin angle
        in
        Vector3d.fromComponents
            ( arcSignedLength * (cosAngle * x1 + sinAngle * x2)
            , arcSignedLength * (cosAngle * y1 + sinAngle * y2)
            , arcSignedLength * (cosAngle * z1 + sinAngle * z2)
            )


{-| Evaluate the first derivative of an arc at a range of parameter values.

    exampleArc
        |> Arc3d.firstDerivativesAt
            (ParameterValue.steps 2)
    --> [ Vector3d ( -1.5708, 1.5708, 0 )
    --> , Vector3d ( -2.2214, 0, 0 )
    --> , Vector3d ( -1.5708, -1.5708, 0 )
    --> ]

-}
firstDerivativesAt : List ParameterValue -> Arc3d -> List Vector3d
firstDerivativesAt parameterValues arc =
    List.map (firstDerivative arc) parameterValues


{-| Attempt to construct a function for evaluating points and tangent directions
along an arc:

    Arc3d.sampler exampleArc
    --> Just <function>

If `Just` a function is returned, the function can then be used to evaluate
(point, tangent direction) pairs along the arc. For example, if the return value
above was <code>Just&nbsp;exampleArcSampler</code>, then:

    exampleArcSampler ParameterValue.zero
    --> ( Point3d.fromCoordinates ( 1, 1, 0 )
    --> , Direction3d.fromAzimuthAndElevation
    -->     (degrees 135)
    -->     (degrees 0)
    --> )

    exampleArcSampler ParameterValue.half
    --> ( Point3d.fromCoordinates ( 0, 1.4142, 0 )
    --> , Direction3d.negativeX
    --> )

    exampleArcSampler ParameterValue.one
    --> ( Point3d.fromCoordinates ( -1, 1, 0 )
    --> , Direction3d.fromAzimuthAndElevation
    -->     (degrees 225)
    -->     (degrees 0)
    --> )

If the arc is degenerate (start point and end point are equal), returns
`Nothing`.

-}
sampler : Arc3d -> Maybe (ParameterValue -> ( Point3d, Direction3d ))
sampler arc =
    let
        (Types.Arc3d properties) =
            arc
    in
    if properties.signedLength == 0 then
        Nothing
    else
        let
            pointOnArc =
                pointOn arc

            ( x1, y1, z1 ) =
                Direction3d.components properties.xDirection

            ( x2, y2, z2 ) =
                Direction3d.components properties.yDirection

            arcSweptAngle =
                properties.sweptAngle
        in
        Just <|
            \parameterValue ->
                let
                    point =
                        pointOnArc parameterValue

                    t =
                        ParameterValue.value parameterValue

                    angle =
                        t * arcSweptAngle

                    cosAngle =
                        cos angle

                    sinAngle =
                        sin angle

                    tangentDirection =
                        Direction3d.unsafe
                            ( cosAngle * x1 + sinAngle * x2
                            , cosAngle * y1 + sinAngle * y2
                            , cosAngle * z1 + sinAngle * z2
                            )
                in
                ( point, tangentDirection )


{-| Attempt to find points and tangent directions along an arc at many parameter
values:

    Arc3d.samplesAt (ParameterValue.steps 2) exampleArc
    --> [ ( Point3d.fromCoordinates ( 1, 1, 0 )
    -->   , Direction3d.fromAzimuthAndElevation
    -->         (degrees 135)
    -->         (degrees 0)
    -->   )
    --> , ( Point3d.fromCoordinates ( 0, 1.4142, 0 )
    -->   , Direction3d.negativeX
    -->   )
    --> , ( Point3d.fromCoordinates ( -1, 1, 0 )
    -->   , Direction3d.fromAzimuthAndElevation
    -->         (degrees 225)
    -->         (degrees 0)
    -->   )
    --> ]

If the arc is degenerate (start point and end point are equal), returns an
empty list.

-}
samplesAt : List ParameterValue -> Arc3d -> List ( Point3d, Direction3d )
samplesAt parameterValues arc =
    case sampler arc of
        Just sampler_ ->
            List.map sampler_ parameterValues

        Nothing ->
            []
{'fraction_non_alphanumeric': 0.07638282260037038, 'fraction_numerical': 0.027848632027673924, 'mean_word_length': 1.694915254237288, 'pattern_counts': {'":': 0, '<': 12, '<?xml version=': 0, '>': 187, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4174259', 'n_tokens_mistral': 8811, 'n_tokens_neox': 8060, 'n_words': 2285}