mirror of
https://gitlab.com/sheaf/metabrush.git
synced 2024-11-05 23:03:38 +00:00
fitting a cubic Bézier to a sequence of points
* WIP: current code exhibits convergence issues / instability
This commit is contained in:
parent
86a1426136
commit
f16ac3fa93
|
@ -36,6 +36,8 @@ common common
|
||||||
>= 0.8.0.0 && < 0.8.4.0
|
>= 0.8.0.0 && < 0.8.4.0
|
||||||
, groups
|
, groups
|
||||||
^>= 0.4.1.0
|
^>= 0.4.1.0
|
||||||
|
, transformers
|
||||||
|
^>= 0.5.6.2
|
||||||
|
|
||||||
default-language:
|
default-language:
|
||||||
Haskell2010
|
Haskell2010
|
||||||
|
@ -62,17 +64,20 @@ library
|
||||||
|
|
||||||
exposed-modules:
|
exposed-modules:
|
||||||
Math.Bezier.Cubic
|
Math.Bezier.Cubic
|
||||||
|
, Math.Bezier.Cubic.Fit
|
||||||
, Math.Bezier.Quadratic
|
, Math.Bezier.Quadratic
|
||||||
, Math.Bezier.Stroke
|
, Math.Bezier.Stroke
|
||||||
, Math.Bezier.Subdivision
|
|
||||||
, Math.Epsilon
|
, Math.Epsilon
|
||||||
|
, Math.Linear.SVD
|
||||||
, Math.Module
|
, Math.Module
|
||||||
, Math.RealRoots
|
, Math.Roots
|
||||||
, Math.Vector2D
|
, Math.Vector2D
|
||||||
|
|
||||||
build-depends:
|
build-depends:
|
||||||
groups-generic
|
groups-generic
|
||||||
^>= 0.1.0.0
|
^>= 0.1.0.0
|
||||||
|
, vector
|
||||||
|
^>= 0.12.1.2
|
||||||
|
|
||||||
executable MetaBrush
|
executable MetaBrush
|
||||||
|
|
||||||
|
@ -148,5 +153,3 @@ executable MetaBrush
|
||||||
^>= 0.4.1.0
|
^>= 0.4.1.0
|
||||||
, text
|
, text
|
||||||
^>= 1.2.3.1 && < 1.2.5
|
^>= 1.2.3.1 && < 1.2.5
|
||||||
, transformers
|
|
||||||
^>= 0.5.6.2
|
|
||||||
|
|
|
@ -30,31 +30,14 @@ import Data.Text
|
||||||
|
|
||||||
data ColourRecord a
|
data ColourRecord a
|
||||||
= Colours
|
= Colours
|
||||||
{ bg :: !a
|
{ bg, active, highlight, cursor, cursorOutline
|
||||||
, active :: !a
|
, plain, base, splash
|
||||||
, highlight :: !a
|
, pathPoint, pathPointOutline, controlPoint, controlPointOutline
|
||||||
, cursor :: !a
|
, path, brush, brushStroke, brushCenter
|
||||||
, cursorOutline :: !a
|
, pointHover, pointSelected
|
||||||
, plain :: !a
|
, viewport, viewportScrollbar, tabScrollbar
|
||||||
, base :: !a
|
, magnifier, glass
|
||||||
, splash :: !a
|
, selected, selectedOutline :: !a
|
||||||
, pathPoint :: !a
|
|
||||||
, pathPointOutline :: !a
|
|
||||||
, controlPoint :: !a
|
|
||||||
, controlPointOutline :: !a
|
|
||||||
, path :: !a
|
|
||||||
, brush :: !a
|
|
||||||
, brushStroke :: !a
|
|
||||||
, brushCenter :: !a
|
|
||||||
, pointHover :: !a
|
|
||||||
, pointSelected :: !a
|
|
||||||
, viewport :: !a
|
|
||||||
, viewportScrollbar :: !a
|
|
||||||
, tabScrollbar :: !a
|
|
||||||
, magnifier :: !a
|
|
||||||
, glass :: !a
|
|
||||||
, selected :: !a
|
|
||||||
, selectedOutline :: !a
|
|
||||||
}
|
}
|
||||||
deriving stock ( Show, Functor, Foldable, Traversable )
|
deriving stock ( Show, Functor, Foldable, Traversable )
|
||||||
|
|
||||||
|
|
|
@ -58,9 +58,7 @@ import MetaBrush.Unique
|
||||||
|
|
||||||
data AABB
|
data AABB
|
||||||
= AABB
|
= AABB
|
||||||
{ topLeft :: !( Point2D Double )
|
{ topLeft, botRight :: !( Point2D Double ) }
|
||||||
, botRight :: !( Point2D Double )
|
|
||||||
}
|
|
||||||
deriving stock Show
|
deriving stock Show
|
||||||
|
|
||||||
data Document
|
data Document
|
||||||
|
|
|
@ -257,13 +257,13 @@ handleScrollEvent
|
||||||
| dx == 0 && any ( \ key -> key == Shift_L || key == Shift_R ) pressedKeys
|
| dx == 0 && any ( \ key -> key == Shift_L || key == Shift_R ) pressedKeys
|
||||||
= let
|
= let
|
||||||
newCenter :: Point2D Double
|
newCenter :: Point2D Double
|
||||||
newCenter = ( ( 25 / oldZoomFactor ) *^ Vector2D ( Point2D dy 0 ) ) • oldCenter
|
newCenter = ( ( 25 / oldZoomFactor ) *^ Vector2D dy 0 ) • oldCenter
|
||||||
in doc { viewportCenter = newCenter }
|
in doc { viewportCenter = newCenter }
|
||||||
-- Vertical scrolling.
|
-- Vertical scrolling.
|
||||||
| otherwise
|
| otherwise
|
||||||
= let
|
= let
|
||||||
newCenter :: Point2D Double
|
newCenter :: Point2D Double
|
||||||
newCenter = ( ( 25 / oldZoomFactor ) *^ Vector2D ( Point2D dx dy ) ) • oldCenter
|
newCenter = ( ( 25 / oldZoomFactor ) *^ Vector2D dx dy ) • oldCenter
|
||||||
in doc { viewportCenter = newCenter }
|
in doc { viewportCenter = newCenter }
|
||||||
docs' :: IntMap Document
|
docs' :: IntMap Document
|
||||||
docs' = IntMap.insert i newDoc docs
|
docs' = IntMap.insert i newDoc docs
|
||||||
|
|
|
@ -89,12 +89,8 @@ import MetaBrush.UI.ToolBar
|
||||||
|
|
||||||
data Renders a
|
data Renders a
|
||||||
= Renders
|
= Renders
|
||||||
{ renderPath :: a
|
{ renderPath, renderStrokes, renderBrushes
|
||||||
, renderStrokes :: a
|
, renderCPts, renderCLines, renderPPts :: a
|
||||||
, renderBrushes :: a
|
|
||||||
, renderCPts :: a
|
|
||||||
, renderCLines :: a
|
|
||||||
, renderPPts :: a
|
|
||||||
}
|
}
|
||||||
deriving stock ( Show, Functor, Foldable, Traversable, Generic, Generic1 )
|
deriving stock ( Show, Functor, Foldable, Traversable, Generic, Generic1 )
|
||||||
deriving Applicative
|
deriving Applicative
|
||||||
|
|
|
@ -51,18 +51,14 @@ import MetaBrush.Render.Util
|
||||||
|
|
||||||
data InfoBar
|
data InfoBar
|
||||||
= InfoBar
|
= InfoBar
|
||||||
{ zoomText :: !GTK.Label -- make this editable
|
{ zoomText :: !GTK.Label -- make this editable
|
||||||
, cursorPosText :: !GTK.Label
|
, cursorPosText, topLeftPosText, botRightPosText :: !GTK.Label
|
||||||
, topLeftPosText :: !GTK.Label
|
|
||||||
, botRightPosText :: !GTK.Label
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data InfoData
|
data InfoData
|
||||||
= InfoData
|
= InfoData
|
||||||
{ zoom :: !Double
|
{ zoom :: !Double
|
||||||
, mousePos :: !( Point2D Double )
|
, mousePos, topLeftPos, botRightPos :: !( Point2D Double )
|
||||||
, topLeftPos :: !( Point2D Double )
|
|
||||||
, botRightPos :: !( Point2D Double )
|
|
||||||
}
|
}
|
||||||
deriving stock Show
|
deriving stock Show
|
||||||
|
|
||||||
|
|
|
@ -110,40 +110,26 @@ data Menu ( rt :: ResourceType )
|
||||||
|
|
||||||
data FileMenu ( rt :: ResourceType )
|
data FileMenu ( rt :: ResourceType )
|
||||||
= FileMenu
|
= FileMenu
|
||||||
{ new :: !( MenuItem NoSubresource rt )
|
{ new, open, save, saveAs, close, quit :: !( MenuItem NoSubresource rt ) }
|
||||||
, open :: !( MenuItem NoSubresource rt )
|
|
||||||
, save :: !( MenuItem NoSubresource rt )
|
|
||||||
, saveAs :: !( MenuItem NoSubresource rt )
|
|
||||||
, close :: !( MenuItem NoSubresource rt )
|
|
||||||
, quit :: !( MenuItem NoSubresource rt )
|
|
||||||
}
|
|
||||||
deriving stock Generic
|
deriving stock Generic
|
||||||
|
|
||||||
data EditMenu ( rt :: ResourceType )
|
data EditMenu ( rt :: ResourceType )
|
||||||
= EditMenu
|
= EditMenu
|
||||||
{ undo :: !( MenuItem NoSubresource rt )
|
{ undo, redo, history :: !( MenuItem NoSubresource rt )
|
||||||
, redo :: !( MenuItem NoSubresource rt )
|
, editSep1 :: !( Separator rt )
|
||||||
, history :: !( MenuItem NoSubresource rt )
|
, cut, copy, paste, duplicate, delete :: !( MenuItem NoSubresource rt )
|
||||||
, editSep1 :: !( Separator rt )
|
, editSep2 :: !( Separator rt )
|
||||||
, cut :: !( MenuItem NoSubresource rt )
|
|
||||||
, copy :: !( MenuItem NoSubresource rt )
|
|
||||||
, paste :: !( MenuItem NoSubresource rt )
|
|
||||||
, duplicate :: !( MenuItem NoSubresource rt )
|
|
||||||
, delete :: !( MenuItem NoSubresource rt )
|
|
||||||
, editSep2 :: !( Separator rt )
|
|
||||||
, preferences :: !( MenuItem NoSubresource rt )
|
, preferences :: !( MenuItem NoSubresource rt )
|
||||||
}
|
}
|
||||||
deriving stock Generic
|
deriving stock Generic
|
||||||
|
|
||||||
data ViewMenu ( rt :: ResourceType )
|
data ViewMenu ( rt :: ResourceType )
|
||||||
= ViewMenu
|
= ViewMenu
|
||||||
{ navigator :: !( MenuItem NoSubresource rt )
|
{ navigator :: !( MenuItem NoSubresource rt )
|
||||||
, viewSep1 :: !( Separator rt )
|
, viewSep1 :: !( Separator rt )
|
||||||
, strokes :: !( MenuItem NoSubresource rt )
|
, strokes, brushes, metaparameters :: !( MenuItem NoSubresource rt )
|
||||||
, brushes :: !( MenuItem NoSubresource rt )
|
, viewSep2 :: !( Separator rt )
|
||||||
, metaparameters :: !( MenuItem NoSubresource rt )
|
, transform :: !( MenuItem NoSubresource rt )
|
||||||
, viewSep2 :: !( Separator rt )
|
|
||||||
, transform :: !( MenuItem NoSubresource rt )
|
|
||||||
}
|
}
|
||||||
deriving stock Generic
|
deriving stock Generic
|
||||||
|
|
||||||
|
|
|
@ -54,12 +54,7 @@ data Mode
|
||||||
|
|
||||||
data ToolBar
|
data ToolBar
|
||||||
= ToolBar
|
= ToolBar
|
||||||
{ selectionTool :: !GTK.RadioButton
|
{ selectionTool, penTool, pathTool, brushTool, metaTool :: !GTK.RadioButton }
|
||||||
, penTool :: !GTK.RadioButton
|
|
||||||
, pathTool :: !GTK.RadioButton
|
|
||||||
, brushTool :: !GTK.RadioButton
|
|
||||||
, metaTool :: !GTK.RadioButton
|
|
||||||
}
|
|
||||||
|
|
||||||
createToolBar :: STM.TVar Tool -> STM.TVar Mode -> Colours -> GTK.DrawingArea -> GTK.Box -> IO ToolBar
|
createToolBar :: STM.TVar Tool -> STM.TVar Mode -> Colours -> GTK.DrawingArea -> GTK.Box -> IO ToolBar
|
||||||
createToolBar toolTVar modeTVar colours drawingArea toolBar = do
|
createToolBar toolTVar modeTVar colours drawingArea toolBar = do
|
||||||
|
|
|
@ -13,7 +13,7 @@ module Math.Bezier.Cubic
|
||||||
( Bezier(..)
|
( Bezier(..)
|
||||||
, bezier, bezier'
|
, bezier, bezier'
|
||||||
, subdivide
|
, subdivide
|
||||||
, closestPoint
|
, ddist, closestPoint
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
@ -39,21 +39,17 @@ import Math.Module
|
||||||
, lerp
|
, lerp
|
||||||
, Inner(..), squaredNorm
|
, Inner(..), squaredNorm
|
||||||
)
|
)
|
||||||
import Math.RealRoots
|
import Math.Roots
|
||||||
( realRoots )
|
( realRoots )
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
-- | Points defining a cubic Bézier curve.
|
-- | Points defining a cubic Bézier curve (Bernstein form).
|
||||||
--
|
--
|
||||||
-- @ p0 @ and @ p3 @ are endpoints, whereas @ p1 @ and @ p2 @ are control points.
|
-- @ p0 @ and @ p3 @ are endpoints, whereas @ p1 @ and @ p2 @ are control points.
|
||||||
data Bezier p
|
data Bezier p
|
||||||
= Bezier
|
= Bezier
|
||||||
{ p0 :: !p
|
{ p0, p1, p2, p3 :: !p }
|
||||||
, p1 :: !p
|
|
||||||
, p2 :: !p
|
|
||||||
, p3 :: !p
|
|
||||||
}
|
|
||||||
deriving stock ( Show, Generic, Functor, Foldable, Traversable )
|
deriving stock ( Show, Generic, Functor, Foldable, Traversable )
|
||||||
|
|
||||||
-- | Cubic Bézier curve.
|
-- | Cubic Bézier curve.
|
||||||
|
@ -83,13 +79,10 @@ subdivide ( Bezier { .. } ) t = ( Bezier p0 q1 q2 pt, Bezier pt r1 r2 p3 )
|
||||||
r1 = lerp @v t s r2
|
r1 = lerp @v t s r2
|
||||||
pt = lerp @v t q2 r1
|
pt = lerp @v t q2 r1
|
||||||
|
|
||||||
-- | Finds the closest point to a given point on a cubic Bézier curve.
|
-- | Polynomial coefficients of the derivative of the distance to a cubic Bézier curve.
|
||||||
closestPoint :: forall v r p. ( Torsor v p, Inner r v, RealFloat r ) => Bezier p -> p -> ArgMin r ( r, p )
|
ddist :: forall v r p. ( Torsor v p, Inner r v, RealFloat r ) => Bezier p -> p -> [ r ]
|
||||||
closestPoint pts@( Bezier { .. } ) c = pickClosest ( 0 :| 1 : roots )
|
ddist ( Bezier { .. } ) c = [ a0, a1, a2, a3, a4, a5 ]
|
||||||
where
|
where
|
||||||
roots :: [ r ]
|
|
||||||
roots = filter ( \ r -> r > 0 && r < 1 ) ( realRoots [ a0, a1, a2, a3, a4, a5 ] )
|
|
||||||
|
|
||||||
v, v', v'', v''' :: v
|
v, v', v'', v''' :: v
|
||||||
v = c --> p0
|
v = c --> p0
|
||||||
v' = p0 --> p1
|
v' = p0 --> p1
|
||||||
|
@ -99,11 +92,18 @@ closestPoint pts@( Bezier { .. } ) c = pickClosest ( 0 :| 1 : roots )
|
||||||
a0, a1, a2, a3, a4, a5 :: r
|
a0, a1, a2, a3, a4, a5 :: r
|
||||||
a0 = v ^.^ v'
|
a0 = v ^.^ v'
|
||||||
a1 = 3 * squaredNorm v' + 2 * v ^.^ v''
|
a1 = 3 * squaredNorm v' + 2 * v ^.^ v''
|
||||||
a2 = 9 * v' ^.^ v'' + 3 * v ^.^ v'''
|
a2 = 9 * v' ^.^ v'' + v ^.^ v'''
|
||||||
a3 = 6 * squaredNorm v'' + 4 * v' ^.^ v'''
|
a3 = 6 * squaredNorm v'' + 4 * v' ^.^ v'''
|
||||||
a4 = 5 * v'' ^.^ v'''
|
a4 = 5 * v'' ^.^ v'''
|
||||||
a5 = squaredNorm v'''
|
a5 = squaredNorm v'''
|
||||||
|
|
||||||
|
-- | Finds the closest point to a given point on a cubic Bézier curve.
|
||||||
|
closestPoint :: forall v r p. ( Torsor v p, Inner r v, RealFloat r ) => Bezier p -> p -> ArgMin r ( r, p )
|
||||||
|
closestPoint pts@( Bezier { .. } ) c = pickClosest ( 0 :| 1 : roots )
|
||||||
|
where
|
||||||
|
roots :: [ r ]
|
||||||
|
roots = filter ( \ r -> r > 0 && r < 1 ) ( realRoots $ ddist @v pts c )
|
||||||
|
|
||||||
pickClosest :: NonEmpty r -> ArgMin r ( r, p )
|
pickClosest :: NonEmpty r -> ArgMin r ( r, p )
|
||||||
pickClosest ( s :| ss ) = go s q nm0 ss
|
pickClosest ( s :| ss ) = go s q nm0 ss
|
||||||
where
|
where
|
||||||
|
|
181
src/lib/Math/Bezier/Cubic/Fit.hs
Normal file
181
src/lib/Math/Bezier/Cubic/Fit.hs
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
{-# LANGUAGE BlockArguments #-}
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
{-# LANGUAGE TypeApplications #-}
|
||||||
|
|
||||||
|
module Math.Bezier.Cubic.Fit where
|
||||||
|
|
||||||
|
-- base
|
||||||
|
import Control.Arrow
|
||||||
|
( first, second )
|
||||||
|
import Control.Monad
|
||||||
|
( when )
|
||||||
|
import Control.Monad.ST
|
||||||
|
( ST, runST )
|
||||||
|
import Data.Complex
|
||||||
|
( Complex(..) )
|
||||||
|
import Data.Foldable
|
||||||
|
( for_ )
|
||||||
|
|
||||||
|
-- acts
|
||||||
|
import Data.Act
|
||||||
|
( Act
|
||||||
|
( (•) )
|
||||||
|
)
|
||||||
|
|
||||||
|
-- transformers
|
||||||
|
import Control.Monad.Trans.State.Strict
|
||||||
|
( execStateT, modify' )
|
||||||
|
import Control.Monad.Trans.Class
|
||||||
|
( lift )
|
||||||
|
|
||||||
|
-- vector
|
||||||
|
import qualified Data.Vector.Unboxed.Mutable as Unboxed
|
||||||
|
( MVector )
|
||||||
|
import qualified Data.Vector.Unboxed.Mutable as Unboxed.MVector
|
||||||
|
( unsafeRead, unsafeWrite )
|
||||||
|
import qualified Data.Vector.Unboxed as Unboxed.Vector
|
||||||
|
( unsafeThaw, generate )
|
||||||
|
|
||||||
|
-- MetaBrush
|
||||||
|
import Math.Bezier.Cubic
|
||||||
|
( Bezier(..), bezier, ddist )
|
||||||
|
import Math.Epsilon
|
||||||
|
( epsilon )
|
||||||
|
import Math.Linear.SVD
|
||||||
|
( lsolve )
|
||||||
|
import Math.Module
|
||||||
|
( Module((*^), (^-^))
|
||||||
|
, Inner((^.^)), quadrance
|
||||||
|
)
|
||||||
|
import Math.Roots
|
||||||
|
( laguerre ) --, eval, derivative )
|
||||||
|
import Math.Vector2D
|
||||||
|
( Mat22(..), Point2D(..), Vector2D(..) )
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- | Fits a single cubic Bézier curve to the given data.
|
||||||
|
--
|
||||||
|
-- This will consist of a cubic Bézier curve which:
|
||||||
|
--
|
||||||
|
-- * starts at \( p \) with tangent \( \textrm{t}_p \),
|
||||||
|
-- * ends at \( r \) with tangent \( \textrm{t}_r \),
|
||||||
|
-- * best fits the intermediate sequence of points \( \left ( q_i \right )_{i=1}^n \).
|
||||||
|
--
|
||||||
|
-- /Note/: the order of the intermediate points is important.
|
||||||
|
--
|
||||||
|
-- Proceeds by fitting a cubic Bézier curve \( B(t) \), \( 0 \leqslant t \leqslant 1 \),
|
||||||
|
-- with given endpoints and tangents, which minimises the sum of squares functional
|
||||||
|
--
|
||||||
|
-- \[ \sum_{i=1}^n \left \| C(t_i) - q_i \right \|^2. \]
|
||||||
|
--
|
||||||
|
-- The values of the parameters \( \left ( t_i \right )_{i=1}^n \) are recursively estimated,
|
||||||
|
-- starting from uniform parametrisation.
|
||||||
|
--
|
||||||
|
-- The iteration ends when any of the following conditions are satisfied:
|
||||||
|
--
|
||||||
|
-- * each new estimated parameter values \( t_i' \) differs from
|
||||||
|
-- its previous value \( t_i \) by less than \( \texttt{t_tol} \),
|
||||||
|
-- * each point \( C(t_i) \) is within squared distance \( \texttt{sq_dist_tol} \)
|
||||||
|
-- of the point \( q_i \) it is associated with,
|
||||||
|
-- * the maximum iteration limit \( \texttt{maxCount} \) has been reached.
|
||||||
|
fitPiece
|
||||||
|
:: Double -- ^ \( \texttt{t_tol} \), the tolerance for the Bézier parameter
|
||||||
|
-> Double -- ^ \( \texttt{sq_dist_tol} \), tolerance for the squared distance
|
||||||
|
-> Int -- ^ \( \texttt{maxCount} \), maximum number of iterations
|
||||||
|
-> Point2D Double -- ^ \( p \), start point
|
||||||
|
-> Vector2D Double -- ^ \( \textrm{t}_p \), start tangent vector (length is ignored)
|
||||||
|
-> [ Point2D Double ] -- ^ \( \left ( q_i \right )_{i=1}^n \), points to fit
|
||||||
|
-> Point2D Double -- ^ \( r \), end point
|
||||||
|
-> Vector2D Double -- ^ \( \textrm{t}_r \), end tangent vector (length is ignored)
|
||||||
|
-> Bezier ( Point2D Double )
|
||||||
|
fitPiece t_tol sq_dist_tol maxCount p tp qs r tr = piece
|
||||||
|
where
|
||||||
|
n :: Int
|
||||||
|
n = length qs
|
||||||
|
uniform :: Int -> Double
|
||||||
|
uniform i = fromIntegral ( i + 1 ) / fromIntegral ( n + 1 )
|
||||||
|
|
||||||
|
f0, f1, f2, f3 :: Double -> Vector2D Double
|
||||||
|
f0 t = h1 t *^ tp
|
||||||
|
f1 t = h2 t *^ tr
|
||||||
|
f2 t = h0 t *^ ( MkVector2D p )
|
||||||
|
f3 t = h3 t *^ ( MkVector2D r )
|
||||||
|
|
||||||
|
piece :: Bezier ( Point2D Double )
|
||||||
|
piece = runST do
|
||||||
|
ts <- Unboxed.Vector.unsafeThaw ( Unboxed.Vector.generate n uniform )
|
||||||
|
loop ts 0
|
||||||
|
|
||||||
|
loop :: forall s. Unboxed.MVector s Double -> Int -> ST s ( Bezier ( Point2D Double ) )
|
||||||
|
loop ts count = do
|
||||||
|
let
|
||||||
|
hermiteParameters :: Mat22 Double -> Vector2D Double -> Int -> [ Point2D Double ] -> ST s ( Vector2D Double )
|
||||||
|
hermiteParameters ( Mat22 a11 a12 _ a22 ) ( Vector2D b1 b2 ) i ( q : rest ) = do
|
||||||
|
ti <- Unboxed.MVector.unsafeRead ts i
|
||||||
|
let
|
||||||
|
f0i, f1i, f2i, f3i :: Vector2D Double
|
||||||
|
f0i = f0 ti
|
||||||
|
f1i = f1 ti
|
||||||
|
f2i = f2 ti
|
||||||
|
f3i = f3 ti
|
||||||
|
q' = MkVector2D q ^-^ f2i ^-^ f3i
|
||||||
|
a11', a12', a21', a22', b1', b2' :: Double
|
||||||
|
a11' = a11 + ( f0i ^.^ f0i )
|
||||||
|
a12' = a12 + ( f1i ^.^ f0i )
|
||||||
|
a21' = a12'
|
||||||
|
a22' = a22 + ( f1i ^.^ f1i )
|
||||||
|
b1' = b1 + ( q' ^.^ f0i )
|
||||||
|
b2' = b2 + ( q' ^.^ f1i )
|
||||||
|
hermiteParameters ( Mat22 a11' a12' a21' a22' ) ( Vector2D b1' b2' ) ( i + 1 ) rest
|
||||||
|
hermiteParameters a b _ [] = pure ( lsolve a b )
|
||||||
|
|
||||||
|
Vector2D s1 s2 <- hermiteParameters ( Mat22 0 0 0 0 ) ( Vector2D 0 0 ) 0 qs
|
||||||
|
|
||||||
|
let
|
||||||
|
cp1, cp2 :: Point2D Double
|
||||||
|
cp1 = ( ( s1 / 3 ) *^ tp ) • p
|
||||||
|
cp2 = ( ( (-s2) / 3 ) *^ tr ) • r
|
||||||
|
|
||||||
|
bez :: Bezier ( Point2D Double )
|
||||||
|
bez = Bezier p cp1 cp2 r
|
||||||
|
|
||||||
|
if count >= maxCount
|
||||||
|
then pure bez
|
||||||
|
else do
|
||||||
|
|
||||||
|
-- Run one iteration of Laguerre's method to improve the parameter values t_i,
|
||||||
|
-- so that t_i' is a better approximation of the parameter
|
||||||
|
-- at which the curve is closest to q_i.
|
||||||
|
( ts_ok, pts_ok ) <- ( `execStateT` ( True, True ) ) $ for_ ( zip qs [ 0 .. ] ) \( q, i ) -> do
|
||||||
|
ti <- lift ( Unboxed.MVector.unsafeRead ts i )
|
||||||
|
let
|
||||||
|
poly :: [ Complex Double ]
|
||||||
|
poly = map (:+ 0) $ ddist @( Vector2D Double ) bez q
|
||||||
|
ti' :: Double
|
||||||
|
ti' = case laguerre epsilon 1 poly ( ti :+ 0 ) of
|
||||||
|
x :+ y
|
||||||
|
| abs y > epsilon
|
||||||
|
|| isNaN x
|
||||||
|
|| isNaN y
|
||||||
|
-> ti
|
||||||
|
| otherwise
|
||||||
|
-> x
|
||||||
|
|
||||||
|
when ( abs ( ti' - ti ) > t_tol )
|
||||||
|
( modify' ( first ( const False ) ) )
|
||||||
|
when ( quadrance @( Vector2D Double ) q ( bezier @( Vector2D Double ) bez ti' ) > sq_dist_tol )
|
||||||
|
( modify' ( second ( const False ) ) )
|
||||||
|
lift ( Unboxed.MVector.unsafeWrite ts i ti' )
|
||||||
|
|
||||||
|
if ts_ok || pts_ok
|
||||||
|
then pure bez
|
||||||
|
else loop ts ( count + 1 )
|
||||||
|
|
||||||
|
-- | Cubic Hermite polynomial.
|
||||||
|
h0, h1, h2, h3 :: Num t => t -> t
|
||||||
|
h0 t = ( 1 + 2 * t ) * ( t - 1 ) ^ ( 2 :: Int )
|
||||||
|
h1 t = t * ( t - 1 ) ^ ( 2 :: Int )
|
||||||
|
h2 t = t ^ ( 2 :: Int ) * ( t - 1 )
|
||||||
|
h3 t = t ^ ( 2 :: Int ) * ( 3 - 2 * t )
|
|
@ -13,7 +13,7 @@ module Math.Bezier.Quadratic
|
||||||
( Bezier(..)
|
( Bezier(..)
|
||||||
, bezier, bezier'
|
, bezier, bezier'
|
||||||
, subdivide
|
, subdivide
|
||||||
, closestPoint
|
, ddist, closestPoint
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
@ -37,20 +37,17 @@ import Math.Module
|
||||||
, lerp
|
, lerp
|
||||||
, Inner(..), squaredNorm
|
, Inner(..), squaredNorm
|
||||||
)
|
)
|
||||||
import Math.RealRoots
|
import Math.Roots
|
||||||
( realRoots )
|
( realRoots )
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
-- | Points defining a quadratic Bézier curve.
|
-- | Points defining a quadratic Bézier curve (Bernstein form).
|
||||||
--
|
--
|
||||||
-- @ p0 @ and @ p2 @ are endpoints, whereas @ p1 @ is a control point.
|
-- @ p0 @ and @ p2 @ are endpoints, whereas @ p1 @ is a control point.
|
||||||
data Bezier p
|
data Bezier p
|
||||||
= Bezier
|
= Bezier
|
||||||
{ p0 :: !p
|
{ p0, p1, p2 :: !p }
|
||||||
, p1 :: !p
|
|
||||||
, p2 :: !p
|
|
||||||
}
|
|
||||||
deriving stock ( Show, Generic, Functor, Foldable, Traversable )
|
deriving stock ( Show, Generic, Functor, Foldable, Traversable )
|
||||||
|
|
||||||
-- | Quadratic Bézier curve.
|
-- | Quadratic Bézier curve.
|
||||||
|
@ -70,13 +67,10 @@ subdivide ( Bezier { .. } ) t = ( Bezier p0 q1 pt, Bezier pt r1 p2 )
|
||||||
r1 = lerp @v t p1 p2
|
r1 = lerp @v t p1 p2
|
||||||
pt = lerp @v t q1 r1
|
pt = lerp @v t q1 r1
|
||||||
|
|
||||||
-- | Finds the closest point to a given point on a quadratic Bézier curve.
|
-- | Polynomial coefficients of the derivative of the distance to a quadratic Bézier curve.
|
||||||
closestPoint :: forall v r p. ( Torsor v p, Inner r v, RealFloat r ) => Bezier p -> p -> ArgMin r ( r, p )
|
ddist :: forall v r p. ( Torsor v p, Inner r v, RealFloat r ) => Bezier p -> p -> [ r ]
|
||||||
closestPoint pts@( Bezier { .. } ) c = pickClosest ( 0 :| 1 : roots )
|
ddist ( Bezier { .. } ) c = [ a0, a1, a2, a3 ]
|
||||||
where
|
where
|
||||||
roots :: [ r ]
|
|
||||||
roots = filter ( \ r -> r > 0 && r < 1 ) ( realRoots [ a0, a1, a2, a3 ] )
|
|
||||||
|
|
||||||
v, v', v'' :: v
|
v, v', v'' :: v
|
||||||
v = c --> p0
|
v = c --> p0
|
||||||
v' = p0 --> p1
|
v' = p0 --> p1
|
||||||
|
@ -88,6 +82,13 @@ closestPoint pts@( Bezier { .. } ) c = pickClosest ( 0 :| 1 : roots )
|
||||||
a2 = 3 * v' ^.^ v''
|
a2 = 3 * v' ^.^ v''
|
||||||
a3 = squaredNorm v''
|
a3 = squaredNorm v''
|
||||||
|
|
||||||
|
-- | 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 -> ArgMin r ( r, p )
|
||||||
|
closestPoint pts@( Bezier { .. } ) c = pickClosest ( 0 :| 1 : roots )
|
||||||
|
where
|
||||||
|
roots :: [ r ]
|
||||||
|
roots = filter ( \ r -> r > 0 && r < 1 ) ( realRoots $ ddist @v pts c )
|
||||||
|
|
||||||
pickClosest :: NonEmpty r -> ArgMin r ( r, p )
|
pickClosest :: NonEmpty r -> ArgMin r ( r, p )
|
||||||
pickClosest ( s :| ss ) = go s q nm0 ss
|
pickClosest ( s :| ss ) = go s q nm0 ss
|
||||||
where
|
where
|
||||||
|
|
|
@ -39,7 +39,7 @@ import Math.Epsilon
|
||||||
( epsilon )
|
( epsilon )
|
||||||
import Math.Module
|
import Math.Module
|
||||||
( Module((^-^)), Inner((^.^)), lerp )
|
( Module((^-^)), Inner((^.^)), lerp )
|
||||||
import Math.RealRoots
|
import Math.Roots
|
||||||
( realRoots )
|
( realRoots )
|
||||||
import Math.Vector2D
|
import Math.Vector2D
|
||||||
( Point2D(..), Vector2D(..), cross )
|
( Point2D(..), Vector2D(..), cross )
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
module Math.Bezier.Subdivision where
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
107
src/lib/Math/Linear/SVD.hs
Normal file
107
src/lib/Math/Linear/SVD.hs
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
{-# LANGUAGE DerivingStrategies #-}
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
|
||||||
|
module Math.Linear.SVD
|
||||||
|
( SVD(..), svd
|
||||||
|
, pinv
|
||||||
|
, lsolve
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
-- base
|
||||||
|
import Data.Complex
|
||||||
|
( Complex(..), magnitude )
|
||||||
|
|
||||||
|
-- MetaBrush
|
||||||
|
import Math.Epsilon
|
||||||
|
( epsilon )
|
||||||
|
import Math.Vector2D
|
||||||
|
( Vector2D(..), Mat22(..) )
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
data SVD a
|
||||||
|
= SVD
|
||||||
|
{ u :: !( Complex a )
|
||||||
|
, sv1 :: !a
|
||||||
|
, sv2 :: !a
|
||||||
|
, v :: !( Complex a )
|
||||||
|
}
|
||||||
|
deriving stock Show
|
||||||
|
|
||||||
|
-- | Singular value decomposition of a real 2x2 matrix.
|
||||||
|
svd :: forall a. ( RealFloat a, Show a ) => Mat22 a -> SVD a
|
||||||
|
svd ( Mat22 a b c d )
|
||||||
|
| abs f < tol
|
||||||
|
|| any isNaN [ c1, c2, s1, s2 ]
|
||||||
|
|| magnitude u < 1 - tol
|
||||||
|
|| magnitude v < 1 - tol
|
||||||
|
= SVD { u = 1, v = 1, .. }
|
||||||
|
| otherwise
|
||||||
|
= SVD { .. }
|
||||||
|
where
|
||||||
|
tol = sqrt epsilon
|
||||||
|
|
||||||
|
det = a * d - b * c
|
||||||
|
q = a * a + b * b + c * c + d * d
|
||||||
|
|
||||||
|
n1 = q + 2 * det
|
||||||
|
n2 = q - 2 * det
|
||||||
|
|
||||||
|
r1 = sqrt n1
|
||||||
|
r2 = sqrt n2
|
||||||
|
|
||||||
|
sv1 = 0.5 * ( r1 + r2 )
|
||||||
|
sv2 = 0.5 * ( r1 - r2 )
|
||||||
|
|
||||||
|
k = a * a - d * d
|
||||||
|
l = b * b - c * c
|
||||||
|
f = n1 * n2
|
||||||
|
|
||||||
|
|
||||||
|
i = 0.5 / sqrt f
|
||||||
|
|
||||||
|
ip = ( k + l ) * i
|
||||||
|
im = ( k - l ) * i
|
||||||
|
|
||||||
|
c1 = sqrt ( 0.5 + ip )
|
||||||
|
c2 = sqrt ( 0.5 + im )
|
||||||
|
s1 = signum ( a * c + b * d ) * sqrt ( 0.5 - ip )
|
||||||
|
s2 = signum ( a * b + c * d ) * sqrt ( 0.5 - im )
|
||||||
|
|
||||||
|
u = c1 :+ s1
|
||||||
|
v = c2 :+ s2
|
||||||
|
|
||||||
|
-- | Pseudo-inverse of a real 2x2 matrix.
|
||||||
|
pinv :: forall a. ( RealFloat a, Show a ) => Mat22 a -> Mat22 a
|
||||||
|
pinv mat = case svd mat of
|
||||||
|
SVD { u = c1 :+ s1, sv1, sv2, v = c2 :+ s2 } ->
|
||||||
|
Mat22
|
||||||
|
( c1 * c2 * rsv1 + s1 * s2 * rsv2 ) ( s1 * c2 * rsv1 - c1 * s2 * rsv2 )
|
||||||
|
( c1 * s2 * rsv1 - s1 * c2 * rsv2 ) ( s1 * s2 * rsv1 + c1 * c2 * rsv2 )
|
||||||
|
where
|
||||||
|
rsv1, rsv2 :: a
|
||||||
|
rsv1
|
||||||
|
| sv1 < epsilon
|
||||||
|
= sv1
|
||||||
|
| otherwise
|
||||||
|
= recip sv1
|
||||||
|
rsv2
|
||||||
|
| abs sv2 < epsilon
|
||||||
|
= sv2
|
||||||
|
| otherwise
|
||||||
|
= recip sv2
|
||||||
|
|
||||||
|
-- | Solve a 2x2 system of linear equations.
|
||||||
|
lsolve :: forall a. ( RealFloat a, Show a ) => Mat22 a -> Vector2D a -> Vector2D a
|
||||||
|
lsolve mat ( Vector2D x y ) = Vector2D x' y'
|
||||||
|
where
|
||||||
|
x', y', a11, a12, a21, a22 :: a
|
||||||
|
Mat22
|
||||||
|
a11 a12
|
||||||
|
a21 a22
|
||||||
|
= pinv mat
|
||||||
|
x' = a11 * x + a12 * y
|
||||||
|
y' = a21 * x + a22 * y
|
|
@ -2,10 +2,12 @@
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
{-# LANGUAGE FunctionalDependencies #-}
|
{-# LANGUAGE FunctionalDependencies #-}
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
{-# LANGUAGE TypeApplications #-}
|
||||||
|
|
||||||
module Math.Module
|
module Math.Module
|
||||||
( Module(..), lerp
|
( Module(..), lerp
|
||||||
, Inner(..), squaredNorm
|
, Inner(..)
|
||||||
|
, squaredNorm, quadrance, distance
|
||||||
, proj, projC, closestPointToLine
|
, proj, projC, closestPointToLine
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
@ -48,6 +50,14 @@ class Module r m => Inner r m where
|
||||||
squaredNorm :: forall m r. Inner r m => m -> r
|
squaredNorm :: forall m r. Inner r m => m -> r
|
||||||
squaredNorm v = v ^.^ v
|
squaredNorm v = v ^.^ v
|
||||||
|
|
||||||
|
-- | Squared distance between two points.
|
||||||
|
quadrance :: forall v r p. ( Inner r v, Torsor v p ) => p -> p -> r
|
||||||
|
quadrance p1 p2 = squaredNorm ( p1 --> p2 :: v )
|
||||||
|
|
||||||
|
-- | Distance between two points.
|
||||||
|
distance :: forall v r p. ( Floating r, Inner r v, Torsor v p ) => p -> p -> r
|
||||||
|
distance p1 p2 = sqrt ( quadrance @v p1 p2 )
|
||||||
|
|
||||||
-- | Projects the first argument onto the second.
|
-- | Projects the first argument onto the second.
|
||||||
proj :: forall m r. ( Inner r m, Fractional r ) => m -> m -> m
|
proj :: forall m r. ( Inner r m, Fractional r ) => m -> m -> m
|
||||||
proj x y = projC x y *^ y
|
proj x y = projC x y *^ y
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
|
||||||
module Math.RealRoots
|
module Math.Roots
|
||||||
( realRoots )
|
|
||||||
where
|
where
|
||||||
|
|
||||||
-- base
|
-- base
|
||||||
|
@ -75,9 +75,9 @@ laguerre eps maxIters p = go maxIters
|
||||||
p'' = derivative p'
|
p'' = derivative p'
|
||||||
go :: Int -> Complex a -> Complex a
|
go :: Int -> Complex a -> Complex a
|
||||||
go iterationsLeft x
|
go iterationsLeft x
|
||||||
| iterationsLeft <= 0 = x
|
| iterationsLeft <= 1
|
||||||
| magnitude ( x' - x ) < eps = x'
|
|| magnitude ( x' - x ) < eps = x'
|
||||||
| otherwise = go ( iterationsLeft - 1 ) x'
|
| otherwise = go ( iterationsLeft - 1 ) x'
|
||||||
where
|
where
|
||||||
x' :: Complex a
|
x' :: Complex a
|
||||||
x' = laguerreStep eps p p' p'' x
|
x' = laguerreStep eps p p' p'' x
|
|
@ -3,9 +3,10 @@
|
||||||
{-# LANGUAGE DerivingVia #-}
|
{-# LANGUAGE DerivingVia #-}
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE PatternSynonyms #-}
|
||||||
|
|
||||||
module Math.Vector2D
|
module Math.Vector2D
|
||||||
( Point2D(..), Vector2D(..)
|
( Point2D(..), Vector2D(.., Vector2D), Mat22(..)
|
||||||
, cross
|
, cross
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
@ -43,11 +44,15 @@ data Point2D a = Point2D !a !a
|
||||||
deriving ( Act ( Vector2D a ), Torsor ( Vector2D a ) )
|
deriving ( Act ( Vector2D a ), Torsor ( Vector2D a ) )
|
||||||
via Vector2D a
|
via Vector2D a
|
||||||
|
|
||||||
newtype Vector2D a = Vector2D { tip :: Point2D a }
|
newtype Vector2D a = MkVector2D { tip :: Point2D a }
|
||||||
deriving stock ( Show, Eq, Functor, Foldable, Traversable )
|
deriving stock ( Show, Eq, Functor, Foldable, Traversable )
|
||||||
deriving ( Semigroup, Monoid, Group )
|
deriving ( Semigroup, Monoid, Group )
|
||||||
via GenericProduct ( Point2D ( Sum a ) )
|
via GenericProduct ( Point2D ( Sum a ) )
|
||||||
|
|
||||||
|
{-# COMPLETE Vector2D #-}
|
||||||
|
pattern Vector2D :: a -> a -> Vector2D a
|
||||||
|
pattern Vector2D x y = MkVector2D ( Point2D x y )
|
||||||
|
|
||||||
instance Num a => Module a ( Vector2D a ) where
|
instance Num a => Module a ( Vector2D a ) where
|
||||||
(^+^) = (<>)
|
(^+^) = (<>)
|
||||||
p ^-^ q = p <> invert q
|
p ^-^ q = p <> invert q
|
||||||
|
@ -56,9 +61,13 @@ instance Num a => Module a ( Vector2D a ) where
|
||||||
p ^* c = fmap ( * c ) p
|
p ^* c = fmap ( * c ) p
|
||||||
|
|
||||||
instance Num a => Inner a ( Vector2D a ) where
|
instance Num a => Inner a ( Vector2D a ) where
|
||||||
( Vector2D ( Point2D x1 y1 ) ) ^.^ ( Vector2D ( Point2D x2 y2 ) )
|
( Vector2D x1 y1 ) ^.^ ( Vector2D x2 y2 )
|
||||||
= x1 * x2 + y1 * y2
|
= x1 * x2 + y1 * y2
|
||||||
|
|
||||||
cross :: Num a => Vector2D a -> Vector2D a -> a
|
cross :: Num a => Vector2D a -> Vector2D a -> a
|
||||||
cross ( Vector2D ( Point2D x1 y1 ) ) ( Vector2D ( Point2D x2 y2 ) )
|
cross ( Vector2D x1 y1 ) ( Vector2D x2 y2 )
|
||||||
= x1 * y2 - x2 * y1
|
= x1 * y2 - x2 * y1
|
||||||
|
|
||||||
|
data Mat22 a
|
||||||
|
= Mat22 !a !a !a !a
|
||||||
|
deriving stock ( Show, Eq, Generic, Functor, Foldable, Traversable )
|
||||||
|
|
Loading…
Reference in a new issue