folkertdev / one-true-path-experiment / Segment

An alternative view on paths that is convenient for mathematical operations.

When we look at a path as a list of elemental Segments, it becomes easier to reason about it. The segment data type has four segment types:

All four of these are mathematically well-defined primitives. We can uniformly apply functions like:

Segment can also be ArcLengthParameterized, which makes operations based on arc length possible. For instance, the total arc length or the location after walking some distance over the segment.

These operations are backed by the great OpenSolid package, and in turn back many of the operations in SubPath.


type Segment coordinates
    = LineSegment (LineSegment2d Quantity.Unitless coordinates)
    | Quadratic (QuadraticSpline2d Quantity.Unitless coordinates)
    | Cubic (CubicSpline2d Quantity.Unitless coordinates)
    | Arc (EllipticalArc2d Quantity.Unitless coordinates)

The four types of segments.

line : ( Basics.Float, Basics.Float ) -> ( Basics.Float, Basics.Float ) -> Segment coordinates

Make a line segment

quadratic : ( Basics.Float, Basics.Float ) -> ( Basics.Float, Basics.Float ) -> ( Basics.Float, Basics.Float ) -> Segment coordinates

Make a quadratic bezier segment

cubic : ( Basics.Float, Basics.Float ) -> ( Basics.Float, Basics.Float ) -> ( Basics.Float, Basics.Float ) -> ( Basics.Float, Basics.Float ) -> Segment coordinates

Make a cubic bezier segment

ellipticalArc : ( Basics.Float, Basics.Float ) -> Path.LowLevel.EllipticalArcArgument -> Segment coordinates

Make an elliptic arc segment

Operations

at : Basics.Float -> Segment coordinates -> ( Basics.Float, Basics.Float )

Get the location at a point on the curve, only defined in the range [0, 1].

at 0.5 (line ( 0, 0 ) ( 10, 0 )) --> ( 5, 0 )

at 0.5 (quadratic ( 0, 0 ) ( 5, 10 ) ( 10, 0 )) --> ( 5, 5 )

angle : Segment coordinates -> Segment coordinates -> Basics.Float

The signed angle (in radians) between the end of segment1 and the start of segment2

a : Segment
a = line ( 0, 0 ) ( 1, 0 )

b : Segment
b = line ( 0, 0 ) ( 0, 1 )

angle a b --> degrees 90

angle b a --> degrees -90

derivativeAt : Basics.Float -> Segment coordinates -> ( Basics.Float, Basics.Float )

Get the derivative at a point on the curve, only defined in the range [0, 1].

import Vector2
import LowLevel.Command exposing
    ( EllipticalArcArgument
    , smallestArc
    , largestArc
    , clockwise
    )

derivativeAt 0.5 (line (0,0) (1,1))
    |> Vector2.normalize
    --> Vector2.normalize (1,1)

argument : EllipticalArcArgument
argument =
    { target = ( 5, 5 )
    , radii = ( 5, 5 )
    , xAxisRotate = 0
    , arcFlag = smallestArc
    , direction = clockwise
    }

derivativeAt 0.5 (arc (0,0)  argument)
    |> Vector2.normalize
    --> Vector2.normalize (1,1)

derivativeAtFirst : Segment coordinates -> ( Basics.Float, Basics.Float )

The derivative at the starting point of the segment

derivativeAtFinal : Segment coordinates -> ( Basics.Float, Basics.Float )

The derivative at the ending point of the segment

firstPoint : Segment coordinates -> ( Basics.Float, Basics.Float )

Extract the first point from a segment

finalPoint : Segment coordinates -> ( Basics.Float, Basics.Float )

Extract the final point from a segment

reverse : Segment coordinates -> Segment coordinates

Reverse a line segment

Arc Length Parameterization


type ArcLengthParameterized coordinates

Opaque type for the arc length parameterization of a segment

arcLengthParameterized : Basics.Float -> Segment coordinates -> Maybe (ArcLengthParameterized coordinates)

arcLength : ArcLengthParameterized coordinates -> Basics.Float

pointAlong : ArcLengthParameterized coordinates -> Basics.Float -> ( Basics.Float, Basics.Float )

tangentAlong : ArcLengthParameterized coordinates -> Basics.Float -> Maybe ( Basics.Float, Basics.Float )

parameterValueToArcLength : ArcLengthParameterized coordinates -> Basics.Float -> Basics.Float

arcLengthToParameterValue : ArcLengthParameterized coordinates -> Basics.Float -> Basics.Float

Conversion

toDrawTo : Segment coordinates -> LowLevel.Command.DrawTo

Convert a segment to a drawto instruction. forgets the starting point.

toSegment : LowLevel.Command.CursorState -> LowLevel.Command.DrawTo -> List (Segment coordinates)

Convert a drawto into a segment

This function needs the previous segment to the starting point and (for bezier curves) the control points

import LowLevel.Command exposing (DrawTo(EllipticalArc), CursorState, clockwise, largestArc)

start : CursorState
start = { start = (0,0), cursor = (0,0), previousControlPoint = Nothing }

drawto : DrawTo
drawto =
        EllipticalArc
            [ { target = (10, 0)
              , radii = (5,5)
              , xAxisRotate = 90
              , arcFlag = largestArc
              , direction = clockwise
              }
            ]

expected : List Segment
expected =
     [ arc (0,0)
        { target = (10, 0)
        , radii = (5,5)
        , xAxisRotate = 90
        , arcFlag = largestArc
        , direction = clockwise
        }
      ]

toSegment start drawto --> expected

toCursorState : Segment coordinates -> LowLevel.Command.CursorState

Convert a Segment to a CursorState

toCursorState (line (0,0) (10, 10))
    --> { start = (0,0) , cursor = (10, 10) , previousControlPoint = Nothing }