metabrush/src/lib/Math/Bezier/Quadratic.hs

105 lines
2.8 KiB
Haskell
Raw Normal View History

2020-08-05 20:23:16 +00:00
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE UndecidableInstances #-}
2020-08-04 06:15:06 +00:00
module Math.Bezier.Quadratic
( Bezier(..)
, bezier, bezier'
2020-08-10 14:38:27 +00:00
, subdivide
, closestPoint
2020-08-04 06:15:06 +00:00
)
where
-- base
import Data.List.NonEmpty
( NonEmpty(..) )
2020-08-04 06:15:06 +00:00
import GHC.Generics
( Generic )
-- acts
import Data.Act
2020-08-05 20:23:16 +00:00
( Torsor
2020-08-04 06:15:06 +00:00
( (-->) )
)
-- MetaBrush
import Math.Module
( Module (..)
, lerp
, Inner(..), squaredNorm
2020-08-04 06:15:06 +00:00
)
import Math.RealRoots
( realRoots )
2020-08-04 06:15:06 +00:00
--------------------------------------------------------------------------------
-- | Points defining a quadratic Bézier curve.
--
-- @ p0 @ and @ p2 @ are endpoints, whereas @ p1 @ is a control point.
data Bezier p
= Bezier
{ p0 :: !p
, p1 :: !p
, p2 :: !p
}
deriving stock ( Show, Generic, Functor, Foldable, Traversable )
-- | Quadratic Bézier curve.
bezier :: forall v r p. ( Torsor v p, Module r v ) => Bezier p -> r -> p
bezier ( Bezier { .. } ) t = lerp @v t ( lerp @v t p0 p1 ) ( lerp @v t p1 p2 )
-- | Derivative of quadratic Bézier curve.
bezier' :: forall v r p. ( Torsor v p, Module r v ) => Bezier p -> r -> v
2020-08-10 14:38:27 +00:00
bezier' ( Bezier { .. } ) t = 2 *^ lerp @v t ( p0 --> p1 ) ( p1 --> p2 )
-- | Subdivide a quadratic Bézier curve into two parts.
subdivide :: forall v r p. ( Torsor v p, Module r v ) => Bezier p -> r -> ( Bezier p, Bezier p )
subdivide ( Bezier { .. } ) t = ( Bezier p0 q1 pt, Bezier pt r1 p2 )
where
pt, q1, r1 :: p
q1 = lerp @v t p0 p1
r1 = lerp @v t p1 p2
pt = lerp @v t q1 r1
-- | Finds the closest point to a given point on a quadratic Bézier curve.
closestPoint :: forall v r p. ( Torsor v p, Inner r v, RealFloat r ) => Bezier p -> p -> ( r, p )
closestPoint pts@( Bezier { .. } ) c = pickClosest ( 0 :| 1 : roots )
where
roots :: [ r ]
roots = filter ( \ r -> r > 0 && r < 1 ) ( realRoots [ a0, a1, a2, a3 ] )
v, v', v'' :: v
v = c --> p0
v' = p0 --> p1
v'' = p1 --> p0 ^+^ p1 --> p2
a0, a1, a2, a3 :: r
a0 = v ^.^ v'
a1 = v ^.^ v'' + 2 * squaredNorm v'
a2 = 3 * ( v' ^.^ v'' )
a3 = squaredNorm v''
pickClosest :: NonEmpty r -> ( r, p )
pickClosest ( s :| ss ) = go s q nm0 ss
where
q :: p
q = bezier @v pts s
nm0 :: r
nm0 = squaredNorm ( c --> q :: v )
go t p _ [] = ( t, p )
go t p nm ( t' : ts )
| nm' < nm = go t' p' nm' ts
| otherwise = go t p nm ts
where
p' :: p
p' = bezier @v pts t'
nm' :: r
nm' = squaredNorm ( c --> p' :: v )