{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE UndecidableInstances #-} module Math.Bezier.Cubic ( Bezier(..) , bezier, bezier' , subdivide ) where -- base import GHC.Generics ( Generic ) -- acts import Data.Act ( Torsor ( (-->) ) ) -- MetaBrush import Math.Module ( Module (..) , lerp ) import qualified Math.Bezier.Quadratic as Quadratic ( Bezier(Bezier), bezier ) -------------------------------------------------------------------------------- -- | Points defining a cubic Bézier curve. -- -- @ p0 @ and @ p3 @ are endpoints, whereas @ p1 @ and @ p2 @ are control points. data Bezier p = Bezier { p0 :: !p , p1 :: !p , p2 :: !p , p3 :: !p } deriving stock ( Show, Generic, Functor, Foldable, Traversable ) -- | Cubic Bézier curve. bezier :: forall v r p. ( Torsor v p, Module r v ) => Bezier p -> r -> p bezier ( Bezier { .. } ) t = lerp @v t ( Quadratic.bezier @v ( Quadratic.Bezier p0 p1 p2 ) t ) ( Quadratic.bezier @v ( Quadratic.Bezier p1 p2 p3 ) t ) -- | Derivative of cubic Bézier curve. bezier' :: forall v r p. ( Torsor v p, Module r v ) => Bezier p -> r -> v bezier' ( Bezier { .. } ) t = ( 3 *^ ) $ lerp @v t ( lerp @v t ( p0 --> p1 ) ( p1 --> p2 ) ) ( lerp @v t ( p1 --> p2 ) ( p2 --> p3 ) ) -- | Subdivide a cubic 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 q2 pt, Bezier pt r1 r2 p3 ) where pt, s, q1, q2, r1, r2 :: p q1 = lerp @v t p0 p1 s = lerp @v t p1 p2 r2 = lerp @v t p2 p3 q2 = lerp @v t q1 s r1 = lerp @v t s r2 pt = lerp @v t q2 r1