brush-strokes-0.1.0.0: src/lib/Math/Bezier/Spline.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UndecidableInstances #-}
module Math.Bezier.Spline where
-- base
import Control.Monad
( guard )
import Data.Bifoldable
( Bifoldable(..) )
import Data.Bifunctor
( Bifunctor(..) )
import Data.Bitraversable
( Bitraversable(..) )
import Data.Coerce
( coerce )
import Data.Functor.Const
( Const(..) )
import Data.Functor.Identity
( Identity(..) )
import Data.Kind
( Type, Constraint )
import Data.Monoid
( Ap(..) )
import Data.Semigroup
( First(..) )
import GHC.Generics
( Generic, Generic1, Generically1(..) )
-- bifunctors
import qualified Data.Bifunctor.Tannen as Biff
( Tannen(..) )
-- containers
import Data.Sequence
( Seq(..) )
import qualified Data.Sequence as Seq
( drop, length, singleton, splitAt )
-- deepseq
import Control.DeepSeq
( NFData(..), NFData1, deepseq )
-- generic-lens
import Data.Generics.Product.Fields
( field )
import Data.Generics.Internal.VL
( set )
-- transformers
import Control.Monad.Trans.Class
( lift )
import Control.Monad.Trans.State.Strict
( StateT(runStateT), modify' )
-- acts
import Data.Act
( Torsor (..) )
-- brush-strokes
import qualified Math.Bezier.Cubic as Cubic
( Bezier(..), subdivide )
import qualified Math.Bezier.Quadratic as Quadratic
( Bezier(..), subdivide )
import Math.Linear
( ℝ(..) )
import Math.Module
( Module, lerp )
--------------------------------------------------------------------------------
data PointType
= PathPoint
| ControlPoint ControlPoint
deriving stock ( Eq, Ord, Show, Generic )
deriving anyclass NFData
data ControlPoint
= Bez2Cp
| Bez3Cp1
| Bez3Cp2
deriving stock ( Eq, Ord, Show, Generic )
deriving anyclass NFData
--------------------------------------------------------------------------------
data SplineType = Open | Closed
data SSplineType ( clo :: SplineType ) where
SOpen :: SSplineType Open
SClosed :: SSplineType Closed
class ( Traversable ( NextPoint clo )
, forall crvData. Traversable ( Curves clo crvData )
, Bitraversable ( Curves clo )
, forall ptData. Show ptData => Show ( NextPoint clo ptData )
, forall ptData crvData. ( Show ptData, Show crvData ) => Show ( Curves clo crvData ptData )
, forall ptData. NFData ptData => NFData ( NextPoint clo ptData )
, forall ptData crvData. ( NFData ptData, NFData crvData ) => NFData ( Curves clo crvData ptData )
)
=> SplineTypeI ( clo :: SplineType ) where
-- | Singleton for the spline type
ssplineType :: SSplineType clo
instance SplineTypeI Open where
ssplineType = SOpen
instance SplineTypeI Closed where
ssplineType = SClosed
type NextPoint :: SplineType -> Type -> Type
data family NextPoint clo
newtype instance NextPoint Open ptData = NextPoint { nextPoint :: ptData }
deriving stock ( Show, Generic, Generic1, Functor, Foldable, Traversable )
deriving anyclass ( NFData, NFData1 )
deriving Applicative
via ( Generically1 ( NextPoint Open ) )
data instance NextPoint Closed ptData = BackToStart
deriving stock ( Show, Generic, Generic1, Functor, Foldable, Traversable )
deriving anyclass ( NFData, NFData1 )
deriving Applicative
via ( Generically1 ( NextPoint Closed ) )
fromNextPoint :: forall clo ptData. SplineTypeI clo => ptData -> NextPoint clo ptData -> ptData
fromNextPoint pt nxt
| SOpen <- ssplineType @clo
= case nxt of { NextPoint q -> q }
| otherwise
= pt
toNextPoint :: forall clo ptData. SplineTypeI clo => ptData -> NextPoint clo ptData
toNextPoint pt = case ssplineType @clo of
SOpen -> NextPoint pt
SClosed -> BackToStart
type Curve :: SplineType -> Type -> Type -> Type
data Curve clo crvData ptData
= LineTo
{ curveEnd :: !( NextPoint clo ptData )
, curveData :: !crvData
}
| Bezier2To
{ controlPoint :: !ptData
, curveEnd :: !( NextPoint clo ptData )
, curveData :: !crvData
}
| Bezier3To
{ controlPoint1 :: !ptData
, controlPoint2 :: !ptData
, curveEnd :: !( NextPoint clo ptData )
, curveData :: !crvData
}
deriving stock ( Generic, Generic1 )
deriving stock instance ( Show ptData, Show crvData, Show ( NextPoint clo ptData ) ) => Show ( Curve clo crvData ptData )
deriving anyclass instance ( NFData ptData, NFData crvData, NFData ( NextPoint clo ptData ) ) => NFData ( Curve clo crvData ptData )
deriving stock instance Functor ( NextPoint clo ) => Functor ( Curve clo crvData )
deriving stock instance Foldable ( NextPoint clo ) => Foldable ( Curve clo crvData )
deriving stock instance Traversable ( NextPoint clo ) => Traversable ( Curve clo crvData )
instance Functor ( NextPoint clo ) => Bifunctor ( Curve clo ) where
bimap f g ( LineTo np d ) = LineTo ( fmap g np ) ( f d )
bimap f g ( Bezier2To cp np d ) = Bezier2To ( g cp ) ( fmap g np ) ( f d )
bimap f g ( Bezier3To cp1 cp2 np d ) = Bezier3To ( g cp1 ) ( g cp2 ) ( fmap g np ) ( f d )
instance Foldable ( NextPoint clo ) => Bifoldable ( Curve clo ) where
bifoldMap f g ( LineTo np d ) = foldMap g np <> f d
bifoldMap f g ( Bezier2To cp np d ) = g cp <> foldMap g np <> f d
bifoldMap f g ( Bezier3To cp1 cp2 np d ) = g cp1 <> g cp2 <> foldMap g np <> f d
instance Traversable ( NextPoint clo ) => Bitraversable ( Curve clo ) where
bitraverse f g ( LineTo np d ) = LineTo <$> traverse g np <*> f d
bitraverse f g ( Bezier2To cp np d ) = Bezier2To <$> g cp <*> traverse g np <*> f d
bitraverse f g ( Bezier3To cp1 cp2 np d ) = Bezier3To <$> g cp1 <*> g cp2 <*> traverse g np <*> f d
openCurveEnd :: Curve Open crvData ptData -> ptData
openCurveEnd = nextPoint . curveEnd
openCurveStart :: Curve Open crvData ptData -> ptData
openCurveStart ( LineTo ( NextPoint p ) _ ) = p
openCurveStart ( Bezier2To p _ _ ) = p
openCurveStart ( Bezier3To p _ _ _ ) = p
type Curves :: SplineType -> Type -> Type -> Type
data family Curves clo
newtype instance Curves Open crvData ptData
= OpenCurves { openCurves :: Seq ( Curve Open crvData ptData ) }
deriving stock ( Show, Generic, Generic1, Functor, Foldable, Traversable )
deriving newtype ( Semigroup, Monoid, NFData )
deriving via Biff.Tannen Seq ( Curve Open )
instance Bifunctor ( Curves Open )
deriving via Biff.Tannen Seq ( Curve Open )
instance Bifoldable ( Curves Open )
instance Bitraversable ( Curves Open ) where
bitraverse f g ( OpenCurves { openCurves = curves } )
= OpenCurves <$> traverse ( bitraverse f g ) curves
data instance Curves Closed crvData ptData
= NoCurves
| ClosedCurves
{ prevOpenCurves :: !( Seq ( Curve Open crvData ptData ) )
, lastClosedCurve :: !( Curve Closed crvData ptData )
}
deriving stock ( Show, Generic, Generic1, Functor, Foldable, Traversable )
deriving anyclass NFData
nbCurves :: Curves Closed crvData ptData -> Int
nbCurves NoCurves = 0
nbCurves ( ClosedCurves { prevOpenCurves } ) = 1 + Seq.length prevOpenCurves
instance Bifunctor ( Curves Closed ) where
bimap _ _ NoCurves = NoCurves
bimap f g ( ClosedCurves p l ) = ClosedCurves ( fmap ( bimap f g ) p ) ( bimap f g l )
instance Bifoldable ( Curves Closed ) where
bifoldMap _ _ NoCurves = mempty
bifoldMap f g ( ClosedCurves p l ) = foldMap ( bifoldMap f g ) p <> bifoldMap f g l
instance Bitraversable ( Curves Closed ) where
bitraverse _ _ NoCurves = pure NoCurves
bitraverse f g ( ClosedCurves p l )
= ClosedCurves <$> ( traverse ( bitraverse f g ) p ) <*> bitraverse f g l
data Spline ( clo :: SplineType ) crvData ptData
= Spline
{ splineStart :: !ptData
, splineCurves :: !( Curves clo crvData ptData )
}
deriving stock ( Generic, Generic1 )
deriving stock instance ( Show ptData, Show ( Curves clo crvData ptData ) )
=> Show ( Spline clo crvData ptData )
deriving anyclass instance ( NFData ptData, NFData ( Curves clo crvData ptData ) )
=> NFData ( Spline clo crvData ptData )
deriving stock instance Functor ( Curves clo crvData ) => Functor ( Spline clo crvData )
deriving stock instance Foldable ( Curves clo crvData ) => Foldable ( Spline clo crvData )
deriving stock instance Traversable ( Curves clo crvData ) => Traversable ( Spline clo crvData )
instance KnownSplineType clo => Bifunctor ( Spline clo ) where
bimap fc fp = bimapSpline ( const $ bimap fc fp ) fp
instance KnownSplineType clo => Bifoldable ( Spline clo ) where
bifoldMap fc fp = runIdentity . bifoldSpline @_ @Identity ( const $ bifoldMap ( coerce fc ) ( coerce fp ) ) ( coerce fp )
instance KnownSplineType clo => Bitraversable ( Spline clo ) where
bitraverse fc fp = bitraverseSpline ( const $ bitraverse fc fp ) fp
type SplinePts clo = Spline clo () ( ℝ 2 )
bimapCurve
:: Functor ( NextPoint clo )
=> ( crvData -> crvData' ) -> ( PointType -> ptData -> ptData' )
-> Curve clo crvData ptData -> Curve clo crvData' ptData'
bimapCurve f g ( LineTo p1 d ) = LineTo ( g PathPoint <$> p1 ) ( f d )
bimapCurve f g ( Bezier2To p1 p2 d ) = Bezier2To ( g ( ControlPoint Bez2Cp ) p1 ) ( g PathPoint <$> p2 ) ( f d )
bimapCurve f g ( Bezier3To p1 p2 p3 d ) = Bezier3To ( g ( ControlPoint Bez3Cp1 ) p1 ) ( g ( ControlPoint Bez3Cp2 ) p2 ) ( g PathPoint <$> p3 ) ( f d )
bifoldMapCurve
:: forall m clo crvData ptData
. ( Monoid m, Foldable ( NextPoint clo ) )
=> ( crvData -> m ) -> ( PointType -> ptData -> m )
-> Curve clo crvData ptData -> m
bifoldMapCurve f g ( LineTo p1 d ) = ( foldMap ( g PathPoint ) p1 ) <> f d
bifoldMapCurve f g ( Bezier2To p1 p2 d ) = g ( ControlPoint Bez2Cp ) p1 <> ( foldMap ( g PathPoint ) p2 ) <> f d
bifoldMapCurve f g ( Bezier3To p1 p2 p3 d ) = g ( ControlPoint Bez3Cp1 ) p1 <> g ( ControlPoint Bez3Cp2 ) p2 <> ( foldMap ( g PathPoint ) p3 ) <> f d
bitraverseCurve
:: forall f clo crvData crvData' ptData ptData'
. ( Applicative f, Traversable ( NextPoint clo ) )
=> ( crvData -> f crvData' ) -> ( PointType -> ptData -> f ptData' )
-> Curve clo crvData ptData -> f ( Curve clo crvData' ptData' )
bitraverseCurve f g ( LineTo p1 d ) = LineTo <$> traverse ( g PathPoint ) p1 <*> f d
bitraverseCurve f g ( Bezier2To p1 p2 d ) = Bezier2To <$> g ( ControlPoint Bez2Cp ) p1 <*> traverse ( g PathPoint ) p2 <*> f d
bitraverseCurve f g ( Bezier3To p1 p2 p3 d ) = Bezier3To <$> g ( ControlPoint Bez3Cp1 ) p1 <*> g ( ControlPoint Bez3Cp2 ) p2 <*> traverse ( g PathPoint ) p3 <*> f d
dropCurves :: Int -> Spline Open crvData ptData -> Maybe ( Spline Open crvData ptData )
dropCurves i spline@( Spline { splineCurves = OpenCurves curves } )
| i < 1
= Just spline
| otherwise
= case Seq.drop ( i - 1 ) curves of
prev :<| next -> Just $ Spline { splineStart = openCurveEnd prev, splineCurves = OpenCurves next }
Empty -> Nothing
splitSplineAt :: Int -> Spline Open crvData ptData -> ( Spline Open crvData ptData, Spline Open crvData ptData )
splitSplineAt i ( Spline { splineStart, splineCurves = OpenCurves curves } ) = case Seq.splitAt i curves of
( Empty, next ) ->
( Spline { splineStart, splineCurves = OpenCurves Empty }, Spline { splineStart, splineCurves = OpenCurves next } )
( prev@( _ :|> lastPrev ), next ) ->
( Spline { splineStart, splineCurves = OpenCurves prev }, Spline { splineStart = openCurveEnd lastPrev, splineCurves = OpenCurves next } )
-- | Subdivide a single curve at the given Bézier parameter.
subdivideCurveAt
:: forall diff ptData crvData
. ( Torsor diff ptData, Module Double diff )
=> ptData
-> Curve Open crvData ptData
-> Double -- ^ Bézier parameter of split
-> ( Curve Open crvData ptData, ptData, Curve Open crvData ptData )
subdivideCurveAt p0 curve t = case curve of
LineTo { curveEnd = NextPoint p1, curveData } ->
let splitPt = lerp @diff t p0 p1
in ( LineTo { curveEnd = NextPoint splitPt, curveData }
, splitPt
, LineTo { curveEnd = NextPoint p1, curveData }
)
Bezier2To { controlPoint = p1, curveEnd = NextPoint p2, curveData } ->
let ( Quadratic.Bezier _ q1 splitPt, Quadratic.Bezier _ r1 _ ) =
Quadratic.subdivide @diff ( Quadratic.Bezier { p0, p1, p2 } ) t
in ( Bezier2To { controlPoint = q1, curveEnd = NextPoint splitPt, curveData }
, splitPt
, Bezier2To { controlPoint = r1, curveEnd = NextPoint p2, curveData }
)
Bezier3To { controlPoint1 = p1, controlPoint2 = p2, curveEnd = NextPoint p3, curveData } ->
let ( Cubic.Bezier _ q1 q2 splitPt, Cubic.Bezier _ r1 r2 _ ) =
Cubic.subdivide @diff ( Cubic.Bezier { p0, p1, p2, p3 } ) t
in ( Bezier3To { controlPoint1 = q1, controlPoint2 = q2, curveEnd = NextPoint splitPt, curveData }
, splitPt
, Bezier3To { controlPoint1 = r1, controlPoint2 = r2, curveEnd = NextPoint p3, curveData }
)
-- | Subdivide a spline at multiple positions, each given as a
-- @(curveIndex, t)@ pair (assumed sorted and without duplicates).
subdivideSplineAt
:: forall diff ptData crvData
. ( Torsor diff ptData, Module Double diff )
=> Spline Open crvData ptData
-> [ ( Int, Double ) ] -- ^ @(curveIndex, t)@, where @t@ is the Bézier parameter for that curve
-> [ Spline Open crvData ptData ]
subdivideSplineAt ( Spline { splineStart, splineCurves = OpenCurves curves } ) splits =
go splineStart splineStart Empty 0 0 curves splits
where
go :: ptData
-> ptData
-> Seq ( Curve Open crvData ptData )
-> Int -> Double
-> Seq ( Curve Open crvData ptData )
-> [ ( Int, Double ) ]
-> [ Spline Open crvData ptData ]
go segStart _curPt acc _idx _tPrev cs [] =
-- No more splits: absorb all remaining curves into the current segment.
[ Spline { splineStart = segStart, splineCurves = OpenCurves ( acc <> cs ) } ]
go segStart curPt acc idx tPrev cs ( ( i, t ) : rest_splits ) =
case cs of
Empty ->
-- No more curves (shouldn't happen for valid split positions).
[ Spline { splineStart = segStart, splineCurves = OpenCurves acc } ]
c :<| rest
| i > idx
-> -- No split in this curve; add it to the accumulator and
-- go to the next curve, starting with Bézier parameter t=0.
go segStart ( openCurveEnd c ) ( acc :|> c ) ( idx + 1 ) 0 rest
( ( i, t ) : rest_splits )
| otherwise
-> -- Split falls in this curve: reparametrise the Bézier parameter
-- for further splits.
let t' = ( t - tPrev ) / ( 1 - tPrev )
( leftC, splitPt, rightC ) = subdivideCurveAt @diff curPt c t'
in Spline { splineStart = segStart, splineCurves = OpenCurves ( acc :|> leftC ) }
: go splitPt splitPt Empty idx t ( rightC :<| rest ) rest_splits
-- NB: pass t, not t', here.
reverseSpline :: forall crvData ptData. Spline Open crvData ptData -> Spline Open crvData ptData
reverseSpline spline@( Spline { splineStart = p0, splineCurves = OpenCurves curves } ) = case curves of
Empty -> spline
prev :|> lst -> Spline { splineStart = openCurveEnd lst, splineCurves = OpenCurves ( go prev lst ) }
where
go :: Seq ( Curve Open crvData ptData ) -> Curve Open crvData ptData -> Seq ( Curve Open crvData ptData )
go Empty ( LineTo _ dat ) = LineTo ( NextPoint p0 ) dat :<| Empty
go Empty ( Bezier2To p1 _ dat ) = Bezier2To p1 ( NextPoint p0 ) dat :<| Empty
go Empty ( Bezier3To p1 p2 _ dat ) = Bezier3To p2 p1 ( NextPoint p0 ) dat :<| Empty
go ( crvs :|> crv ) ( LineTo _ dat ) = LineTo ( curveEnd crv ) dat :<| go crvs crv
go ( crvs :|> crv ) ( Bezier2To p1 _ dat ) = Bezier2To p1 ( NextPoint $ openCurveEnd crv ) dat :<| go crvs crv
go ( crvs :|> crv ) ( Bezier3To p1 p2 _ dat ) = Bezier3To p2 p1 ( NextPoint $ openCurveEnd crv ) dat :<| go crvs crv
splineEnd :: Spline Open crvData ptData -> ptData
splineEnd ( Spline { splineStart, splineCurves = OpenCurves curves } ) = case curves of
Empty -> splineStart
_ :|> lastCurve -> openCurveEnd lastCurve
catMaybesSpline :: ( ptData -> ptData -> Bool ) -> crvData -> ptData -> Maybe ptData -> Maybe ptData -> ptData -> Spline Open crvData ptData
catMaybesSpline eq dat p0 mbCp1 mbCp2 p3 =
let
mbCp1' = do { cp1 <- mbCp1; guard ( not $ eq cp1 p0 ); return cp1 }
mbCp2' = do { cp2 <- mbCp2; guard ( not $ eq cp2 p3 ); return cp2 }
mbCp1'' = do { cp1 <- mbCp1'; guard ( case mbCp2' of { Just {} -> True ; Nothing -> not ( eq cp1 p3 ) } ); return cp1 }
mbCp2'' = do { cp2 <- mbCp2'; guard ( case mbCp1' of { Just {} -> True ; Nothing -> not ( eq cp2 p0 ) } ); return cp2 }
in go mbCp1'' mbCp2''
where
go Nothing Nothing = Spline { splineStart = p0, splineCurves = OpenCurves $ Seq.singleton ( LineTo ( NextPoint p3 ) dat ) }
go ( Just p1 ) Nothing = Spline { splineStart = p0, splineCurves = OpenCurves $ Seq.singleton ( Bezier2To p1 ( NextPoint p3 ) dat ) }
go Nothing ( Just p2 ) = Spline { splineStart = p0, splineCurves = OpenCurves $ Seq.singleton ( Bezier2To p2 ( NextPoint p3 ) dat ) }
go ( Just p1 ) ( Just p2 ) = Spline { splineStart = p0, splineCurves = OpenCurves $ Seq.singleton ( Bezier3To p1 p2 ( NextPoint p3 ) dat ) }
-- | Connect two open curves.
--
-- It is assumed (not checked) that the end of the first curve is the start of the second curve.
instance Semigroup ( Spline Open crvData ptData ) where
spline1@( Spline { splineStart, splineCurves = segs1 } ) <> spline2@( Spline { splineCurves = segs2 } )
| null segs1 = spline2
| null segs2 = spline1
| otherwise = Spline { splineStart, splineCurves = segs1 <> segs2 }
-- | Create a curve containing a single (open) cubic Bézier segment.
openCubicBezierCurveSpline :: crvData -> Cubic.Bezier ptData -> Spline Open crvData ptData
openCubicBezierCurveSpline crvData ( Cubic.Bezier {..} )
= Spline
{ splineStart = p0
, splineCurves = OpenCurves . Seq.singleton $
Bezier3To
{ controlPoint1 = p1
, controlPoint2 = p2
, curveEnd = NextPoint p3
, curveData = crvData
}
}
-- | Drop the end of an open curve segment to create a "closed" curve (i.e. one that returns to the start).
dropCurveEnd :: Curve Open crvData ptData -> Curve Closed crvData ptData
dropCurveEnd ( LineTo _ dat ) = LineTo BackToStart dat
dropCurveEnd ( Bezier2To cp _ dat ) = Bezier2To cp BackToStart dat
dropCurveEnd ( Bezier3To cp1 cp2 _ dat ) = Bezier3To cp1 cp2 BackToStart dat
-- | A 'Maybe' value which keeps track of whether it is 'Nothing' or 'Just' with a type-level boolean.
--
-- This is used to enable writing 'biwitherSpline' with a dependent type signature,
-- as the result type depends on whether a starting point has been found yet or not.
data CurrentStart ( hasStart :: Bool ) ptData where
NoStartFound :: CurrentStart False ptData
CurrentStart ::
{ wasOriginalStart :: Bool
, startPoint :: !ptData
} -> CurrentStart True ptData
deriving stock instance Show ptData => Show ( CurrentStart hasStart ptData )
deriving stock instance Functor ( CurrentStart hasStart )
deriving stock instance Foldable ( CurrentStart hasStart )
deriving stock instance Traversable ( CurrentStart hasStart )
instance NFData ptData => NFData ( CurrentStart hasStart ptData ) where
rnf NoStartFound = ()
rnf ( CurrentStart orig ptData ) = rnf orig `seq` rnf ptData
-- | The result of a wither operation on a spline.
--
-- - When a starting point has been found, we can choose whether to keep the current segment or not.
-- - When a starting point has not yet been found, we first need to determine a new start point,
-- before we can consider keeping or dismissing the curve.
data WitherResult ( hasStart :: Bool ) ( clo' :: SplineType ) crvData' ptData' where
Dismiss :: WitherResult hasStart clo' crvData' ptData'
UseStartPoint :: ptData' -> Maybe ( Curve clo' crvData' ptData' ) -> WitherResult False clo' crvData' ptData'
UseCurve :: Curve clo' crvData' ptData' -> WitherResult True clo' crvData' ptData'
deriving stock instance ( Show ptData', Show crvData', SplineTypeI clo' ) => Show ( WitherResult hasStart clo' crvData' ptData' )
instance ( NFData ptData', NFData crvData', SplineTypeI clo' ) => NFData ( WitherResult hasStart clo' crvData' ptData' ) where
rnf Dismiss = ()
rnf ( UseStartPoint ptData mbCurve ) = ptData `deepseq` mbCurve `deepseq` ()
rnf ( UseCurve curve ) = rnf curve
class SplineTypeI clo => KnownSplineType clo where
type TraversalCt clo ( clo' :: SplineType ) :: Constraint
-- | Last point of a spline.
lastPoint :: Spline clo crvData ptData -> ptData
-- | Close a spline if necessary.
adjustSplineType :: forall clo' crvData ptData. SplineTypeI clo' => Spline clo' crvData ptData -> Spline clo crvData ptData
-- | Indexed traversal of a spline.
ibitraverseSpline
:: forall f crvData ptData crvData' ptData'
. Applicative f
=> ( forall clo'. ( TraversalCt clo clo', SplineTypeI clo' )
=> Int -> ptData -> Curve clo' crvData ptData -> f ( Curve clo' crvData' ptData' )
)
-> ( ptData -> f ptData' )
-> Spline clo crvData ptData
-> f ( Spline clo crvData' ptData' )
-- | Bi-witherable traversal of a spline.
biwitherSpline
:: forall f crvData ptData crvData' ptData'
. Monad f
=> ( forall clo' hasStart. ( TraversalCt clo clo', SplineTypeI clo' )
=> CurrentStart hasStart ptData' -> Curve clo' crvData ptData -> f ( WitherResult hasStart clo' crvData' ptData' )
)
-> ( ptData -> f ( Maybe ptData' ) )
-> Spline clo crvData ptData
-> f ( Maybe ( Spline clo crvData' ptData' ) )
-- | Traversal of a spline.
bitraverseSpline
:: forall f crvData ptData crvData' ptData'
. Applicative f
=> ( forall clo'. ( TraversalCt clo clo', SplineTypeI clo' )
=> ptData -> Curve clo' crvData ptData -> f ( Curve clo' crvData' ptData' )
)
-> ( ptData -> f ptData' )
-> Spline clo crvData ptData
-> f ( Spline clo crvData' ptData' )
bitraverseSpline fc fp = ibitraverseSpline ( const fc ) fp
-- | Indexed fold of a spline.
ibifoldSpline
:: forall f m crvData ptData
. ( Applicative f, Monoid m )
=> ( forall clo'. ( TraversalCt clo clo', SplineTypeI clo' )
=> Int -> ptData -> Curve clo' crvData ptData -> f m
)
-> ( ptData -> f m )
-> Spline clo crvData ptData
-> f m
ibifoldSpline fc fp
= coerce
. ibitraverseSpline @clo @( Const ( Ap f m ) ) ( coerce fc ) ( coerce fp )
-- | Fold of a spline.
bifoldSpline
:: forall f m crvData ptData
. ( Applicative f, Monoid m )
=> ( forall clo'. ( TraversalCt clo clo', SplineTypeI clo' )
=> ptData -> Curve clo' crvData ptData -> f m )
-> ( ptData -> f m )
-> Spline clo crvData ptData
-> f m
bifoldSpline fc fp = ibifoldSpline ( const fc ) fp
-- | Bifunctor fmap of a spline.
bimapSpline
:: forall crvData ptData crvData' ptData'
. ( forall clo'. ( TraversalCt clo clo', SplineTypeI clo' )
=> ptData -> Curve clo' crvData ptData -> Curve clo' crvData' ptData'
)
-> ( ptData -> ptData' )
-> Spline clo crvData ptData
-> Spline clo crvData' ptData'
bimapSpline fc fp
= runIdentity
. bitraverseSpline @clo @Identity ( coerce fc ) ( coerce fp )
instance KnownSplineType Open where
type TraversalCt Open clo' = clo' ~ Open
lastPoint ( Spline { splineStart, splineCurves = OpenCurves curves } ) =
case curves of
Empty -> splineStart
_ :|> lastCurve -> openCurveEnd lastCurve
adjustSplineType :: forall clo' crvData ptData. SplineTypeI clo' => Spline clo' crvData ptData -> Spline Open crvData ptData
adjustSplineType spline@( Spline { splineStart, splineCurves } ) = case ssplineType @clo' of
SOpen -> spline
SClosed -> case splineCurves of
NoCurves -> Spline { splineStart, splineCurves = OpenCurves Empty }
ClosedCurves prev lst -> Spline { splineStart, splineCurves = OpenCurves $ prev :|> set ( field @"curveEnd" ) ( NextPoint splineStart ) lst }
ibitraverseSpline fc fp ( Spline { splineStart, splineCurves = OpenCurves curves } ) =
( \ p cs -> Spline p ( OpenCurves cs ) ) <$> fp splineStart <*> go 0 splineStart curves
where
go _ _ Empty = pure Empty
go i p ( seg :<| segs ) = (:<|) <$> fc i p seg <*> go ( i + 1 ) ( openCurveEnd seg ) segs
biwitherSpline
:: forall f crvData ptData crvData' ptData'
. Monad f
=> ( forall clo' hasStart. ( clo' ~ Open, SplineTypeI clo' )
=> CurrentStart hasStart ptData' -> Curve clo' crvData ptData -> f ( WitherResult hasStart clo' crvData' ptData' )
)
-> ( ptData -> f ( Maybe ptData' ) )
-> Spline Open crvData ptData
-> f ( Maybe ( Spline Open crvData' ptData' ) )
biwitherSpline fc fp ( Spline { splineStart, splineCurves = OpenCurves curves } ) = do
mbStart' <- fp splineStart
( curves', mbStart'' ) <- ( `runStateT` ( fmap First mbStart' ) ) $ go ( (, True ) <$> mbStart' ) curves
case mbStart'' of
Nothing -> pure Nothing
Just ( First start' ) ->
pure ( Just $ Spline { splineStart = start', splineCurves = OpenCurves curves' } )
where
go :: Maybe ( ptData', Bool ) -> Seq ( Curve Open crvData ptData ) -> StateT ( Maybe ( First ptData' ) ) f ( Seq ( Curve Open crvData' ptData' ) )
go _ Empty = pure Empty
go Nothing ( crv :<| crvs ) = do
mbCrv' <- lift $ fc NoStartFound crv
case mbCrv' of
Dismiss -> go Nothing crvs
UseStartPoint ptData'' mbCrv'' -> do
modify' ( <> Just ( First ptData'' ) )
case mbCrv'' of
Nothing -> go ( Just ( ptData'', False ) ) crvs
Just crv'' -> ( crv'' :<| ) <$> go ( Just ( ptData'', False ) ) crvs
go ( Just ( ptData', orig ) ) ( crv :<| crvs ) = do
mbCrv' <- lift $ fc ( CurrentStart orig ptData' ) crv
case mbCrv' of
Dismiss -> go ( Just ( ptData', False ) ) crvs
UseCurve crv'' ->
( crv'' :<| ) <$> go ( Just ( openCurveEnd crv'', True ) ) crvs
instance KnownSplineType Closed where
type TraversalCt Closed clo' = ()
lastPoint ( Spline { splineStart } ) = splineStart
adjustSplineType :: forall clo' crvData ptData. SplineTypeI clo' => Spline clo' crvData ptData -> Spline Closed crvData ptData
adjustSplineType spline@( Spline { splineStart, splineCurves } ) = case ssplineType @clo' of
SClosed -> spline
SOpen -> case splineCurves of
OpenCurves ( Empty ) -> Spline { splineStart, splineCurves = NoCurves }
OpenCurves ( prev :|> lst ) -> Spline { splineStart, splineCurves = ClosedCurves prev ( set ( field @"curveEnd" ) BackToStart lst ) }
ibitraverseSpline
:: forall f crvData ptData crvData' ptData'
. Applicative f
=> ( forall clo'. ( (), SplineTypeI clo' )
=> Int -> ptData -> Curve clo' crvData ptData -> f ( Curve clo' crvData' ptData' )
)
-> ( ptData -> f ptData' )
-> Spline Closed crvData ptData
-> f ( Spline Closed crvData' ptData' )
ibitraverseSpline _ fp ( Spline { splineStart = p0, splineCurves = NoCurves } ) = ( \ p -> Spline p NoCurves ) <$> fp p0
ibitraverseSpline fc fp ( Spline { splineStart = p0, splineCurves = ClosedCurves prevCurves lastCurve } ) =
( \ p cs lst -> Spline p ( ClosedCurves cs lst ) ) <$> fp p0 <*> go 0 p0 prevCurves <*> fc n pn lastCurve
where
n :: Int
n = length prevCurves
pn :: ptData
pn = case prevCurves of
Empty -> p0
_ :|> lastPrev -> openCurveEnd lastPrev
go _ _ Empty = pure Empty
go i p ( seg :<| segs ) = (:<|) <$> fc i p seg <*> go ( i + 1 ) ( openCurveEnd seg ) segs
biwitherSpline fc fp closedSpline = do
spline' <- biwitherSpline fc fp ( adjustSplineType @Open closedSpline )
return $ adjustSplineType @Closed <$> spline'
showSplinePoints :: forall clo ptData crvData
. (KnownSplineType clo, Show ptData)
=> Spline clo crvData ptData -> String
showSplinePoints
= runIdentity
. bifoldSpline
( \ _pt crv -> Identity $ f crv )
( \ pt -> Identity $ "[ " <> show pt )
where
f :: SplineTypeI clo' => Curve clo' crvData ptData -> String
f (LineTo end _) = " -> " ++ showEnd end
f (Bezier2To cp end _) = " -- " ++ show cp ++ " -> " ++ showEnd end
f (Bezier3To cp1 cp2 end _) = " -- " ++ show cp1 ++ " -- " ++ show cp2 ++ " -> " ++ showEnd end
showEnd :: forall clo'. SplineTypeI clo' => NextPoint clo' ptData -> String
showEnd = case ssplineType @clo' of
SOpen -> \ ( NextPoint pt ) -> show pt <> "\n, "
SClosed -> \ BackToStart -> ". ]"