folkertdev/one-true-path-experiment/6.0.1

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:

  • line straigt line segment
  • quadratic a quadratic bezier curve segment
  • cubic a cubic bezier curve segment
  • arc an elliptical arc segment

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 Unitless coordinates)
| Quadratic (QuadraticSpline2d Unitless coordinates)
| Cubic (CubicSpline2d Unitless coordinates)
| Arc (EllipticalArc2d Unitless coordinates)

The four types of segments.

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

Make a line segment

( Float, Float )
-> ( Float, Float )
-> ( Float, Float )
-> Segment coordinates

Make a quadratic bezier segment

( Float, Float )
-> ( Float, Float )
-> ( Float, Float )
-> ( Float, Float )
-> Segment coordinates

Make a cubic bezier segment

ellipticalArc : ( Float, Float ) -> EllipticalArcArgument -> Segment coordinates

Make an elliptic arc segment

Operations

at : Float -> Segment coordinates -> ( Float, 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 -> 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 : Float -> Segment coordinates -> ( Float, 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 -> ( Float, Float )

The derivative at the starting point of the segment

derivativeAtFinal : Segment coordinates -> ( Float, Float )

The derivative at the ending point of the segment

firstPoint : Segment coordinates -> ( Float, Float )

Extract the first point from a segment

finalPoint : Segment coordinates -> ( Float, 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

Float
-> Segment coordinates
-> Maybe (ArcLengthParameterized coordinates)
arcLength : ArcLengthParameterized coordinates -> Float
pointAlong : ArcLengthParameterized coordinates -> Float -> ( Float, Float )
-> Float
-> Maybe ( Float, Float )
parameterValueToArcLength : ArcLengthParameterized coordinates -> Float -> Float
arcLengthToParameterValue : ArcLengthParameterized coordinates -> Float -> Float

Conversion

toDrawTo : Segment coordinates -> DrawTo

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

toSegment : CursorState -> 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 -> CursorState

Convert a Segment to a CursorState

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