folkertdev/one-true-path-experiment/4.0.1

NOTE: the latest version is 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
= LineSegment LineSegment2d
| Quadratic QuadraticSpline2d
| Cubic CubicSpline2d
| Arc EllipticalArc2d

The four types of segments.

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

Make a line segment

quadratic : ( Float, Float ) -> ( Float, Float ) -> ( Float, Float ) -> Segment

Make a quadratic bezier segment

( Float, Float )
-> ( Float, Float )
-> ( Float, Float )
-> ( Float, Float )

Make a cubic bezier segment

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

Make an elliptic arc segment

Operations

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

The derivative at the starting point of the segment

derivativeAtFinal : Segment -> ( Float, Float )

The derivative at the ending point of the segment

firstPoint : Segment -> ( Float, Float )

Extract the first point from a segment

finalPoint : Segment -> ( Float, Float )

Extract the final point from a segment

Reverse a line segment

Arc Length Parameterization

Opaque type for the arc length parameterization of a segment

pointAlong : ArcLengthParameterized -> Float -> Maybe ( Float, Float )
tangentAlong : ArcLengthParameterized -> Float -> Maybe ( Float, Float )

Conversion

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

toSegment : CursorState -> DrawTo -> List Segment

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

Convert a Segment to a CursorState

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