cubicbezier 0.3.0 → 0.4.0.1
raw patch · 13 files changed
+2343/−537 lines, 13 filesdep +matricesdep +microlensdep +microlens-mtldep ~containersdep ~integrationPVP ok
version bump matches the API change (PVP)
Dependencies added: matrices, microlens, microlens-mtl, microlens-th, mtl, vector
Dependency ranges changed: containers, integration
API changes (from Hackage documentation)
- Geom2D: instance AffineTransform Point
- Geom2D: instance AffineTransform Polygon
- Geom2D: instance AffineTransform Transform
- Geom2D: instance Eq Point
- Geom2D: instance Show Point
- Geom2D: instance Show Transform
- Geom2D.CubicBezier.Approximate: approximateCurve :: CubicBezier -> [Point] -> Double -> (CubicBezier, Double, Double)
- Geom2D.CubicBezier.Approximate: approximateCurveWithParams :: CubicBezier -> [Point] -> [Double] -> Double -> (CubicBezier, Double, Double)
- Geom2D.CubicBezier.Basic: bezierC0 :: CubicBezier -> Point
- Geom2D.CubicBezier.Basic: bezierC1 :: CubicBezier -> Point
- Geom2D.CubicBezier.Basic: bezierC2 :: CubicBezier -> Point
- Geom2D.CubicBezier.Basic: bezierC3 :: CubicBezier -> Point
- Geom2D.CubicBezier.Basic: data Path
- Geom2D.CubicBezier.Basic: instance AffineTransform CubicBezier
- Geom2D.CubicBezier.Basic: instance Show CubicBezier
- Geom2D.CubicBezier.Basic: instance Show Path
- Geom2D.CubicBezier.Basic: instance Show PathJoin
- Geom2D.CubicBezier.MetaPath: CyclicMetaPath :: [(Point, MetaJoin)] -> MetaPath
- Geom2D.CubicBezier.MetaPath: data MetaPath
- Geom2D.CubicBezier.MetaPath: instance Eq MetaNodeType
- Geom2D.CubicBezier.MetaPath: instance Show MetaJoin
- Geom2D.CubicBezier.MetaPath: instance Show MetaNodeType
- Geom2D.CubicBezier.MetaPath: instance Show MetaPath
- Geom2D.CubicBezier.MetaPath: unmeta :: MetaPath -> Path
- Geom2D.CubicBezier.Outline: bezierOffsetMax :: Int -> CubicBezier -> Double -> Double -> [CubicBezier]
- Math.BernsteinPoly: bernsteinDegree :: BernsteinPoly -> !Int
- Math.BernsteinPoly: instance Show BernsteinPoly
+ Geom2D: closestPoint :: Fractional a => Line a -> Point a -> Point a
+ Geom2D: flipVector :: Num a => Point a -> Point a
+ Geom2D: instance Eq a => Eq (Line a)
+ Geom2D: instance Eq a => Eq (Point a)
+ Geom2D: instance Eq a => Eq (Polygon a)
+ Geom2D: instance Eq a => Eq (Transform a)
+ Geom2D: instance Functor Line
+ Geom2D: instance Functor Point
+ Geom2D: instance Functor Polygon
+ Geom2D: instance Functor Transform
+ Geom2D: instance Num a => AffineTransform (Point a) a
+ Geom2D: instance Num a => AffineTransform (Polygon a) a
+ Geom2D: instance Num a => AffineTransform (Transform a) a
+ Geom2D: instance Show a => Show (Line a)
+ Geom2D: instance Show a => Show (Point a)
+ Geom2D: instance Show a => Show (Polygon a)
+ Geom2D: instance Show a => Show (Transform a)
+ Geom2D: instance Unbox a => MVector MVector (Point a)
+ Geom2D: instance Unbox a => Unbox (Point a)
+ Geom2D: instance Unbox a => Vector Vector (Point a)
+ Geom2D: rotateScaleVec :: Num a => Point a -> Transform a
+ Geom2D: type DPoint = Point Double
+ Geom2D.CubicBezier.Approximate: FunctionSegment :: !a -> !a -> CubicBezier a -> FunctionSegment a
+ Geom2D.CubicBezier.Approximate: _fsTmax :: FunctionSegment a -> !a
+ Geom2D.CubicBezier.Approximate: approx1cubic :: (Unbox a, Ord a, Floating a) => Int -> (a -> (Point a, Point a)) -> a -> a -> Int -> (CubicBezier a, a)
+ Geom2D.CubicBezier.Approximate: approx1quad :: (Ord a, Floating a) => (a -> (Point a, Point a)) -> a -> a -> QuadBezier a
+ Geom2D.CubicBezier.Approximate: approxMax :: (Unbox a, Ord a, Floating a) => (a -> (Point a, Point a)) -> a -> Int -> Vector a -> Bool -> Map a (FunctionSegment a) -> [CubicBezier a]
+ Geom2D.CubicBezier.Approximate: approximateCubic :: (Unbox a, Ord a, Floating a) => CubicBezier a -> Vector (Point a) -> Maybe (Vector a) -> Int -> (CubicBezier a, a)
+ Geom2D.CubicBezier.Approximate: approximateCubic' :: (Unbox a, Ord a, Floating a) => CubicBezier a -> Vector (Point a) -> Vector a -> Int -> a -> Vector (Point a) -> Vector (Point a) -> Maybe (CubicBezier a, Vector a, Vector a, a, Vector (Point a))
+ Geom2D.CubicBezier.Approximate: approximateParams :: (Unbox a, Floating a) => Point a -> Point a -> Vector (Point a) -> Vector a
+ Geom2D.CubicBezier.Approximate: approximatePath' :: (Unbox a, Ord a, Floating a) => (a -> (Point a, Point a)) -> Int -> a -> a -> a -> Bool -> [CubicBezier a]
+ Geom2D.CubicBezier.Approximate: approximateQuad' :: (Show a, Unbox a, Ord a, Floating a) => (a -> (Point a, Point a)) -> a -> a -> a -> Bool -> [QuadBezier a]
+ Geom2D.CubicBezier.Approximate: approximateQuadPath :: (Show a, Unbox a, Ord a, Floating a) => (a -> (Point a, Point a)) -> a -> a -> a -> Bool -> [QuadBezier a]
+ Geom2D.CubicBezier.Approximate: approxquad :: (Ord a, Floating a) => Point a -> Point a -> Point a -> Point a -> QuadBezier a
+ Geom2D.CubicBezier.Approximate: data FunctionSegment a
+ Geom2D.CubicBezier.Approximate: fsCurve :: FunctionSegment a -> CubicBezier a
+ Geom2D.CubicBezier.Approximate: fsTmin :: FunctionSegment a -> !a
+ Geom2D.CubicBezier.Approximate: goldSearch :: (Ord a, Floating a) => (a -> a) -> a
+ Geom2D.CubicBezier.Approximate: goldSearch' :: (Ord a, Floating a) => (a -> a) -> a -> a -> a -> a -> a -> a -> a -> a -> Int -> a
+ Geom2D.CubicBezier.Approximate: interpolate :: Num a => a -> a -> a -> a
+ Geom2D.CubicBezier.Approximate: leastSquares :: (Unbox a, Fractional a, Eq a) => Vector a -> Vector a -> Vector a -> Maybe (a, a)
+ Geom2D.CubicBezier.Approximate: lsqDist :: (Unbox a, Fractional a, Eq a) => CubicBezier a -> Vector (Point a) -> Vector a -> Maybe (CubicBezier a)
+ Geom2D.CubicBezier.Approximate: maxDist :: (Unbox a, Ord a, Floating a) => (a -> (Point a, Point a)) -> QuadBezier a -> a -> a -> a
+ Geom2D.CubicBezier.Approximate: phi :: Floating a => a
+ Geom2D.CubicBezier.Approximate: quadDist :: (Unbox a, Floating a) => (a -> (Point a, Point a)) -> QuadBezier a -> a -> a -> a -> a
+ Geom2D.CubicBezier.Approximate: splitCubic :: (Unbox a, Ord a, Floating a) => a -> a -> Int -> (a -> (Point a, Point a)) -> a -> a -> Int -> (a, a, CubicBezier a, a, CubicBezier a)
+ Geom2D.CubicBezier.Approximate: splitQuad :: (Show a, Unbox a, Ord a, Floating a) => a -> a -> (a -> (Point a, Point a)) -> a -> a -> Int -> (a, a, QuadBezier a, a, QuadBezier a)
+ Geom2D.CubicBezier.Basic: AnyBezier :: (Vector (a, a)) -> AnyBezier a
+ Geom2D.CubicBezier.Basic: QuadBezier :: !(Point a) -> !(Point a) -> !(Point a) -> QuadBezier a
+ Geom2D.CubicBezier.Basic: anyToCubic :: Unbox a => AnyBezier a -> Maybe (CubicBezier a)
+ Geom2D.CubicBezier.Basic: anyToQuad :: Unbox a => AnyBezier a -> Maybe (QuadBezier a)
+ Geom2D.CubicBezier.Basic: class GenericBezier b
+ Geom2D.CubicBezier.Basic: closedPathCurves :: Fractional a => ClosedPath a -> [CubicBezier a]
+ Geom2D.CubicBezier.Basic: cubicC0 :: CubicBezier a -> !(Point a)
+ Geom2D.CubicBezier.Basic: cubicC1 :: CubicBezier a -> !(Point a)
+ Geom2D.CubicBezier.Basic: cubicC2 :: CubicBezier a -> !(Point a)
+ Geom2D.CubicBezier.Basic: cubicC3 :: CubicBezier a -> !(Point a)
+ Geom2D.CubicBezier.Basic: curvesToClosed :: [CubicBezier a] -> ClosedPath a
+ Geom2D.CubicBezier.Basic: curvesToOpen :: [CubicBezier a] -> OpenPath a
+ Geom2D.CubicBezier.Basic: data AnyBezier a
+ Geom2D.CubicBezier.Basic: data ClosedPath a
+ Geom2D.CubicBezier.Basic: data OpenPath a
+ Geom2D.CubicBezier.Basic: data QuadBezier a
+ Geom2D.CubicBezier.Basic: degree :: (GenericBezier b, Unbox a) => b a -> Int
+ Geom2D.CubicBezier.Basic: instance Eq a => Eq (CubicBezier a)
+ Geom2D.CubicBezier.Basic: instance Eq a => Eq (QuadBezier a)
+ Geom2D.CubicBezier.Basic: instance Functor ClosedPath
+ Geom2D.CubicBezier.Basic: instance Functor CubicBezier
+ Geom2D.CubicBezier.Basic: instance Functor OpenPath
+ Geom2D.CubicBezier.Basic: instance Functor PathJoin
+ Geom2D.CubicBezier.Basic: instance Functor QuadBezier
+ Geom2D.CubicBezier.Basic: instance GenericBezier AnyBezier
+ Geom2D.CubicBezier.Basic: instance GenericBezier CubicBezier
+ Geom2D.CubicBezier.Basic: instance GenericBezier QuadBezier
+ Geom2D.CubicBezier.Basic: instance Num a => AffineTransform (CubicBezier a) a
+ Geom2D.CubicBezier.Basic: instance Show a => Show (ClosedPath a)
+ Geom2D.CubicBezier.Basic: instance Show a => Show (CubicBezier a)
+ Geom2D.CubicBezier.Basic: instance Show a => Show (OpenPath a)
+ Geom2D.CubicBezier.Basic: instance Show a => Show (PathJoin a)
+ Geom2D.CubicBezier.Basic: instance Show a => Show (QuadBezier a)
+ Geom2D.CubicBezier.Basic: openPathCurves :: Fractional a => OpenPath a -> [CubicBezier a]
+ Geom2D.CubicBezier.Basic: quadC0 :: QuadBezier a -> !(Point a)
+ Geom2D.CubicBezier.Basic: quadC1 :: QuadBezier a -> !(Point a)
+ Geom2D.CubicBezier.Basic: quadC2 :: QuadBezier a -> !(Point a)
+ Geom2D.CubicBezier.Basic: quadToCubic :: Fractional a => QuadBezier a -> CubicBezier a
+ Geom2D.CubicBezier.Basic: toVector :: (GenericBezier b, Unbox a) => b a -> Vector (a, a)
+ Geom2D.CubicBezier.Basic: unsafeFromVector :: (GenericBezier b, Unbox a) => Vector (a, a) -> b a
+ Geom2D.CubicBezier.MetaPath: ClosedMetaPath :: [(Point a, MetaJoin a)] -> ClosedMetaPath a
+ Geom2D.CubicBezier.MetaPath: data ClosedMetaPath a
+ Geom2D.CubicBezier.MetaPath: data OpenMetaPath a
+ Geom2D.CubicBezier.MetaPath: instance Eq a => Eq (ClosedMetaPath a)
+ Geom2D.CubicBezier.MetaPath: instance Eq a => Eq (MetaJoin a)
+ Geom2D.CubicBezier.MetaPath: instance Eq a => Eq (MetaNodeType a)
+ Geom2D.CubicBezier.MetaPath: instance Functor ClosedMetaPath
+ Geom2D.CubicBezier.MetaPath: instance Functor MetaJoin
+ Geom2D.CubicBezier.MetaPath: instance Functor MetaNodeType
+ Geom2D.CubicBezier.MetaPath: instance Show a => Show (ClosedMetaPath a)
+ Geom2D.CubicBezier.MetaPath: instance Show a => Show (MetaJoin a)
+ Geom2D.CubicBezier.MetaPath: instance Show a => Show (MetaNodeType a)
+ Geom2D.CubicBezier.MetaPath: instance Show a => Show (OpenMetaPath a)
+ Geom2D.CubicBezier.MetaPath: unmetaClosed :: ClosedMetaPath Double -> ClosedPath Double
+ Geom2D.CubicBezier.MetaPath: unmetaOpen :: OpenMetaPath Double -> OpenPath Double
+ Geom2D.CubicBezier.Numeric: SparseMatrix :: (Vector Int) -> (Vector (Int, Int)) -> (Matrix a) -> SparseMatrix a
+ Geom2D.CubicBezier.Numeric: addMatrix :: (Num a, Unbox a) => Matrix a -> Matrix a -> Matrix a
+ Geom2D.CubicBezier.Numeric: addVec :: (Num a, Unbox a) => Vector a -> Vector a -> Vector a
+ Geom2D.CubicBezier.Numeric: data SparseMatrix a
+ Geom2D.CubicBezier.Numeric: decompLDL :: (Fractional a, Unbox a) => Matrix a -> Matrix a
+ Geom2D.CubicBezier.Numeric: lsqMatrix :: (Num a, Unbox a) => SparseMatrix a -> Matrix a
+ Geom2D.CubicBezier.Numeric: lsqSolve :: (Fractional a, Unbox a) => SparseMatrix a -> Vector a -> Vector a
+ Geom2D.CubicBezier.Numeric: lsqSolveDist :: (Fractional a, Unbox a) => SparseMatrix (a, a) -> Vector (a, a) -> Vector a
+ Geom2D.CubicBezier.Numeric: makeSparse :: Unbox a => Vector Int -> Matrix a -> SparseMatrix a
+ Geom2D.CubicBezier.Numeric: quadraticRoot :: Double -> Double -> Double -> [Double]
+ Geom2D.CubicBezier.Numeric: sign :: (Ord a, Num a1, Num a) => a -> a1
+ Geom2D.CubicBezier.Numeric: solveLDL :: (Fractional a, Unbox a) => Matrix a -> Vector a -> Vector a
+ Geom2D.CubicBezier.Numeric: solveLinear2x2 :: (Eq a, Fractional a) => a -> a -> a -> a -> a -> a -> Maybe (a, a)
+ Geom2D.CubicBezier.Numeric: sparseMul :: (Num a, Unbox a) => SparseMatrix a -> Vector a -> Vector a
+ Geom2D.CubicBezier.Numeric: sparseMulT :: (Num a, Unbox a) => Vector a -> SparseMatrix a -> Vector a
+ Geom2D.CubicBezier.Numeric: sparseRanges :: Vector Int -> Int -> Int -> Vector (Int, Int)
+ Geom2D.CubicBezier.Overlap: EvenOdd :: FillRule
+ Geom2D.CubicBezier.Overlap: NonZero :: FillRule
+ Geom2D.CubicBezier.Overlap: boolPathOp :: (Bool -> Bool -> Bool) -> [ClosedPath Double] -> [ClosedPath Double] -> FillRule -> Double -> [ClosedPath Double]
+ Geom2D.CubicBezier.Overlap: data FillRule
+ Geom2D.CubicBezier.Overlap: difference :: [ClosedPath Double] -> [ClosedPath Double] -> FillRule -> Double -> [ClosedPath Double]
+ Geom2D.CubicBezier.Overlap: exclusion :: [ClosedPath Double] -> [ClosedPath Double] -> FillRule -> Double -> [ClosedPath Double]
+ Geom2D.CubicBezier.Overlap: instance Eq Curve
+ Geom2D.CubicBezier.Overlap: instance Eq PointEvent
+ Geom2D.CubicBezier.Overlap: instance Ord Curve
+ Geom2D.CubicBezier.Overlap: instance Ord PointEvent
+ Geom2D.CubicBezier.Overlap: instance Show Curve
+ Geom2D.CubicBezier.Overlap: instance Show PointEvent
+ Geom2D.CubicBezier.Overlap: instance Show SweepState
+ Geom2D.CubicBezier.Overlap: intersection :: [ClosedPath Double] -> [ClosedPath Double] -> FillRule -> Double -> [ClosedPath Double]
+ Geom2D.CubicBezier.Overlap: union :: [ClosedPath Double] -> FillRule -> Double -> [ClosedPath Double]
+ Math.BernsteinPoly: bernsteinEvalDeriv :: (Unbox t, Fractional t) => BernsteinPoly t -> t -> (t, t)
+ Math.BernsteinPoly: binCoeff :: (Num a, Unbox a) => Int -> Vector a
+ Math.BernsteinPoly: convolve :: (Unbox a, Num a) => Vector a -> Vector a -> Vector a
+ Math.BernsteinPoly: instance (Show a, Unbox a) => Show (BernsteinPoly a)
+ Math.BernsteinPoly: instance (Show a, Unbox a) => Show (ScaledPoly a)
- Geom2D: ($*) :: AffineTransform a => Transform -> a -> a
+ Geom2D: ($*) :: AffineTransform a b => Transform b -> a -> a
- Geom2D: (*^) :: Double -> Point -> Point
+ Geom2D: (*^) :: Num a => a -> Point a -> Point a
- Geom2D: (^*) :: Point -> Double -> Point
+ Geom2D: (^*) :: Num a => Point a -> a -> Point a
- Geom2D: (^+^) :: Point -> Point -> Point
+ Geom2D: (^+^) :: Num a => Point a -> Point a -> Point a
- Geom2D: (^-^) :: Point -> Point -> Point
+ Geom2D: (^-^) :: Num a => Point a -> Point a -> Point a
- Geom2D: (^.^) :: Point -> Point -> Double
+ Geom2D: (^.^) :: Num a => Point a -> Point a -> a
- Geom2D: (^/) :: Point -> Double -> Point
+ Geom2D: (^/) :: Fractional a => Point a -> a -> Point a
- Geom2D: Line :: Point -> Point -> Line
+ Geom2D: Line :: (Point a) -> (Point a) -> Line a
- Geom2D: Point :: {-# UNPACK #-} !Double -> {-# UNPACK #-} !Double -> Point
+ Geom2D: Point :: !a -> !a -> Point a
- Geom2D: Polygon :: [Point] -> Polygon
+ Geom2D: Polygon :: [Point a] -> Polygon a
- Geom2D: Transform :: {-# UNPACK #-} !Double -> {-# UNPACK #-} !Double -> {-# UNPACK #-} !Double -> {-# UNPACK #-} !Double -> {-# UNPACK #-} !Double -> {-# UNPACK #-} !Double -> Transform
+ Geom2D: Transform :: !a -> !a -> !a -> !a -> !a -> !a -> Transform a
- Geom2D: class AffineTransform a
+ Geom2D: class AffineTransform a b | a -> b
- Geom2D: data Line
+ Geom2D: data Line a
- Geom2D: data Point
+ Geom2D: data Point a
- Geom2D: data Polygon
+ Geom2D: data Polygon a
- Geom2D: data Transform
+ Geom2D: data Transform a
- Geom2D: dirVector :: Double -> Point
+ Geom2D: dirVector :: Floating a => a -> Point a
- Geom2D: interpolateVector :: Point -> Point -> Double -> Point
+ Geom2D: interpolateVector :: Num a => Point a -> Point a -> a -> Point a
- Geom2D: inverse :: Transform -> Maybe Transform
+ Geom2D: inverse :: (Eq a, Num a, Fractional a) => Transform a -> Maybe (Transform a)
- Geom2D: lineDistance :: Line -> Point -> Double
+ Geom2D: lineDistance :: Floating a => Line a -> Point a -> a
- Geom2D: lineEquation :: Line -> (Double, Double, Double)
+ Geom2D: lineEquation :: Floating t => Line t -> (t, t, t)
- Geom2D: normVector :: Point -> Point
+ Geom2D: normVector :: Floating a => Point a -> Point a
- Geom2D: pointX :: Point -> {-# UNPACK #-} !Double
+ Geom2D: pointX :: Point a -> !a
- Geom2D: pointY :: Point -> {-# UNPACK #-} !Double
+ Geom2D: pointY :: Point a -> !a
- Geom2D: rotate :: Double -> Transform
+ Geom2D: rotate :: Floating s => s -> Transform s
- Geom2D: rotate90L :: Transform
+ Geom2D: rotate90L :: Floating s => Transform s
- Geom2D: rotate90R :: Transform
+ Geom2D: rotate90R :: Floating s => Transform s
- Geom2D: rotateVec :: Point -> Transform
+ Geom2D: rotateVec :: Floating a => Point a -> Transform a
- Geom2D: transform :: AffineTransform a => Transform -> a -> a
+ Geom2D: transform :: AffineTransform a b => Transform b -> a -> a
- Geom2D: translate :: Point -> Transform
+ Geom2D: translate :: Num a => Point a -> Transform a
- Geom2D: vectorAngle :: Point -> Double
+ Geom2D: vectorAngle :: RealFloat a => Point a -> a
- Geom2D: vectorCross :: Point -> Point -> Double
+ Geom2D: vectorCross :: Num a => Point a -> Point a -> a
- Geom2D: vectorDistance :: Point -> Point -> Double
+ Geom2D: vectorDistance :: Floating a => Point a -> Point a -> a
- Geom2D: vectorMag :: Point -> Double
+ Geom2D: vectorMag :: Floating a => Point a -> a
- Geom2D: xformA :: Transform -> {-# UNPACK #-} !Double
+ Geom2D: xformA :: Transform a -> !a
- Geom2D: xformB :: Transform -> {-# UNPACK #-} !Double
+ Geom2D: xformB :: Transform a -> !a
- Geom2D: xformC :: Transform -> {-# UNPACK #-} !Double
+ Geom2D: xformC :: Transform a -> !a
- Geom2D: xformD :: Transform -> {-# UNPACK #-} !Double
+ Geom2D: xformD :: Transform a -> !a
- Geom2D: xformE :: Transform -> {-# UNPACK #-} !Double
+ Geom2D: xformE :: Transform a -> !a
- Geom2D: xformF :: Transform -> {-# UNPACK #-} !Double
+ Geom2D: xformF :: Transform a -> !a
- Geom2D.CubicBezier.Approximate: approximatePath :: (Double -> (Point, Point)) -> Double -> Double -> Double -> Double -> [CubicBezier]
+ Geom2D.CubicBezier.Approximate: approximatePath :: (Unbox a, Ord a, Floating a) => (a -> (Point a, Point a)) -> Int -> a -> a -> a -> Bool -> [CubicBezier a]
- Geom2D.CubicBezier.Approximate: approximatePathMax :: Int -> (Double -> (Point, Point)) -> Double -> Double -> Double -> Double -> [CubicBezier]
+ Geom2D.CubicBezier.Approximate: approximatePathMax :: (Unbox a, Floating a, Ord a) => Int -> (a -> (Point a, Point a)) -> Int -> a -> a -> a -> Bool -> [CubicBezier a]
- Geom2D.CubicBezier.Basic: ClosedPath :: [(Point, PathJoin)] -> Path
+ Geom2D.CubicBezier.Basic: ClosedPath :: [(Point a, PathJoin a)] -> ClosedPath a
- Geom2D.CubicBezier.Basic: CubicBezier :: Point -> Point -> Point -> Point -> CubicBezier
+ Geom2D.CubicBezier.Basic: CubicBezier :: !(Point a) -> !(Point a) -> !(Point a) -> !(Point a) -> CubicBezier a
- Geom2D.CubicBezier.Basic: JoinCurve :: Point -> Point -> PathJoin
+ Geom2D.CubicBezier.Basic: JoinCurve :: (Point a) -> (Point a) -> PathJoin a
- Geom2D.CubicBezier.Basic: JoinLine :: PathJoin
+ Geom2D.CubicBezier.Basic: JoinLine :: PathJoin a
- Geom2D.CubicBezier.Basic: OpenPath :: [(Point, PathJoin)] -> Point -> Path
+ Geom2D.CubicBezier.Basic: OpenPath :: [(Point a, PathJoin a)] -> (Point a) -> OpenPath a
- Geom2D.CubicBezier.Basic: arcLength :: CubicBezier -> Double -> Double -> Double
+ Geom2D.CubicBezier.Basic: arcLength :: CubicBezier Double -> Double -> Double -> Double
- Geom2D.CubicBezier.Basic: arcLengthParam :: CubicBezier -> Double -> Double -> Double
+ Geom2D.CubicBezier.Basic: arcLengthParam :: CubicBezier Double -> Double -> Double -> Double
- Geom2D.CubicBezier.Basic: bezierHoriz :: CubicBezier -> [Double]
+ Geom2D.CubicBezier.Basic: bezierHoriz :: CubicBezier Double -> [Double]
- Geom2D.CubicBezier.Basic: bezierParam :: Double -> Bool
+ Geom2D.CubicBezier.Basic: bezierParam :: (Ord a, Num a) => a -> Bool
- Geom2D.CubicBezier.Basic: bezierParamTolerance :: CubicBezier -> Double -> Double
+ Geom2D.CubicBezier.Basic: bezierParamTolerance :: GenericBezier b => b Double -> Double -> Double
- Geom2D.CubicBezier.Basic: bezierSubsegment :: CubicBezier -> Double -> Double -> CubicBezier
+ Geom2D.CubicBezier.Basic: bezierSubsegment :: (Ord a, Unbox a, Fractional a) => GenericBezier b => b a -> a -> a -> b a
- Geom2D.CubicBezier.Basic: bezierToBernstein :: CubicBezier -> (BernsteinPoly, BernsteinPoly)
+ Geom2D.CubicBezier.Basic: bezierToBernstein :: (GenericBezier b, Unbox a) => b a -> (BernsteinPoly a, BernsteinPoly a)
- Geom2D.CubicBezier.Basic: bezierVert :: CubicBezier -> [Double]
+ Geom2D.CubicBezier.Basic: bezierVert :: CubicBezier Double -> [Double]
- Geom2D.CubicBezier.Basic: class AffineTransform a
+ Geom2D.CubicBezier.Basic: class AffineTransform a b | a -> b
- Geom2D.CubicBezier.Basic: colinear :: CubicBezier -> Double -> Bool
+ Geom2D.CubicBezier.Basic: colinear :: CubicBezier Double -> Double -> Bool
- Geom2D.CubicBezier.Basic: data CubicBezier
+ Geom2D.CubicBezier.Basic: data CubicBezier a
- Geom2D.CubicBezier.Basic: data PathJoin
+ Geom2D.CubicBezier.Basic: data PathJoin a
- Geom2D.CubicBezier.Basic: evalBezier :: CubicBezier -> Double -> Point
+ Geom2D.CubicBezier.Basic: evalBezier :: (GenericBezier b, Unbox a, Fractional a) => b a -> a -> Point a
- Geom2D.CubicBezier.Basic: evalBezierDeriv :: CubicBezier -> Double -> (Point, Point)
+ Geom2D.CubicBezier.Basic: evalBezierDeriv :: (Unbox a, Fractional a) => GenericBezier b => b a -> a -> (Point a, Point a)
- Geom2D.CubicBezier.Basic: evalBezierDerivs :: CubicBezier -> Double -> [Point]
+ Geom2D.CubicBezier.Basic: evalBezierDerivs :: (GenericBezier b, Unbox a, Fractional a) => b a -> a -> [Point a]
- Geom2D.CubicBezier.Basic: findBezierCusp :: CubicBezier -> [Double]
+ Geom2D.CubicBezier.Basic: findBezierCusp :: CubicBezier Double -> [Double]
- Geom2D.CubicBezier.Basic: findBezierInflection :: CubicBezier -> [Double]
+ Geom2D.CubicBezier.Basic: findBezierInflection :: CubicBezier Double -> [Double]
- Geom2D.CubicBezier.Basic: findBezierTangent :: Point -> CubicBezier -> [Double]
+ Geom2D.CubicBezier.Basic: findBezierTangent :: DPoint -> CubicBezier Double -> [Double]
- Geom2D.CubicBezier.Basic: reorient :: CubicBezier -> CubicBezier
+ Geom2D.CubicBezier.Basic: reorient :: (GenericBezier b, Unbox a) => b a -> b a
- Geom2D.CubicBezier.Basic: splitBezier :: CubicBezier -> Double -> (CubicBezier, CubicBezier)
+ Geom2D.CubicBezier.Basic: splitBezier :: (Unbox a, Fractional a) => GenericBezier b => b a -> a -> (b a, b a)
- Geom2D.CubicBezier.Basic: splitBezierN :: CubicBezier -> [Double] -> [CubicBezier]
+ Geom2D.CubicBezier.Basic: splitBezierN :: (Ord a, Unbox a, Fractional a) => GenericBezier b => b a -> [a] -> [b a]
- Geom2D.CubicBezier.Basic: transform :: AffineTransform a => Transform -> a -> a
+ Geom2D.CubicBezier.Basic: transform :: AffineTransform a b => Transform b -> a -> a
- Geom2D.CubicBezier.Curvature: curvature :: CubicBezier -> Double -> Double
+ Geom2D.CubicBezier.Curvature: curvature :: CubicBezier Double -> Double -> Double
- Geom2D.CubicBezier.Curvature: curvatureExtrema :: CubicBezier -> Double -> [Double]
+ Geom2D.CubicBezier.Curvature: curvatureExtrema :: CubicBezier Double -> Double -> [Double]
- Geom2D.CubicBezier.Curvature: findRadius :: CubicBezier -> Double -> Double -> [Double]
+ Geom2D.CubicBezier.Curvature: findRadius :: CubicBezier Double -> Double -> Double -> [Double]
- Geom2D.CubicBezier.Curvature: radiusOfCurvature :: CubicBezier -> Double -> Double
+ Geom2D.CubicBezier.Curvature: radiusOfCurvature :: CubicBezier Double -> Double -> Double
- Geom2D.CubicBezier.Intersection: bezierFindRoot :: BernsteinPoly -> Double -> Double -> Double -> [Double]
+ Geom2D.CubicBezier.Intersection: bezierFindRoot :: BernsteinPoly Double -> Double -> Double -> Double -> [Double]
- Geom2D.CubicBezier.Intersection: bezierIntersection :: CubicBezier -> CubicBezier -> Double -> [(Double, Double)]
+ Geom2D.CubicBezier.Intersection: bezierIntersection :: CubicBezier Double -> CubicBezier Double -> Double -> [(Double, Double)]
- Geom2D.CubicBezier.Intersection: bezierLineIntersections :: CubicBezier -> Line -> Double -> [Double]
+ Geom2D.CubicBezier.Intersection: bezierLineIntersections :: CubicBezier Double -> Line Double -> Double -> [Double]
- Geom2D.CubicBezier.Intersection: closest :: CubicBezier -> Point -> Double -> [Double]
+ Geom2D.CubicBezier.Intersection: closest :: CubicBezier Double -> DPoint -> Double -> [Double]
- Geom2D.CubicBezier.MetaPath: Controls :: Point -> Point -> MetaJoin
+ Geom2D.CubicBezier.MetaPath: Controls :: (Point a) -> (Point a) -> MetaJoin a
- Geom2D.CubicBezier.MetaPath: Curl :: Double -> MetaNodeType
+ Geom2D.CubicBezier.MetaPath: Curl :: Double -> MetaNodeType a
- Geom2D.CubicBezier.MetaPath: Direction :: Point -> MetaNodeType
+ Geom2D.CubicBezier.MetaPath: Direction :: Point a -> MetaNodeType a
- Geom2D.CubicBezier.MetaPath: MetaJoin :: MetaNodeType -> Tension -> Tension -> MetaNodeType -> MetaJoin
+ Geom2D.CubicBezier.MetaPath: MetaJoin :: MetaNodeType a -> Tension -> Tension -> MetaNodeType a -> MetaJoin a
- Geom2D.CubicBezier.MetaPath: Open :: MetaNodeType
+ Geom2D.CubicBezier.MetaPath: Open :: MetaNodeType a
- Geom2D.CubicBezier.MetaPath: OpenMetaPath :: [(Point, MetaJoin)] -> Point -> MetaPath
+ Geom2D.CubicBezier.MetaPath: OpenMetaPath :: [(Point a, MetaJoin a)] -> (Point a) -> OpenMetaPath a
- Geom2D.CubicBezier.MetaPath: curlgamma :: MetaNodeType -> Double
+ Geom2D.CubicBezier.MetaPath: curlgamma :: MetaNodeType a -> Double
- Geom2D.CubicBezier.MetaPath: data MetaJoin
+ Geom2D.CubicBezier.MetaPath: data MetaJoin a
- Geom2D.CubicBezier.MetaPath: data MetaNodeType
+ Geom2D.CubicBezier.MetaPath: data MetaNodeType a
- Geom2D.CubicBezier.MetaPath: metaTypeL :: MetaJoin -> MetaNodeType
+ Geom2D.CubicBezier.MetaPath: metaTypeL :: MetaJoin a -> MetaNodeType a
- Geom2D.CubicBezier.MetaPath: metaTypeR :: MetaJoin -> MetaNodeType
+ Geom2D.CubicBezier.MetaPath: metaTypeR :: MetaJoin a -> MetaNodeType a
- Geom2D.CubicBezier.MetaPath: nodedir :: MetaNodeType -> Point
+ Geom2D.CubicBezier.MetaPath: nodedir :: MetaNodeType a -> Point a
- Geom2D.CubicBezier.MetaPath: tensionL :: MetaJoin -> Tension
+ Geom2D.CubicBezier.MetaPath: tensionL :: MetaJoin a -> Tension
- Geom2D.CubicBezier.MetaPath: tensionR :: MetaJoin -> Tension
+ Geom2D.CubicBezier.MetaPath: tensionR :: MetaJoin a -> Tension
- Geom2D.CubicBezier.Outline: bezierOffset :: CubicBezier -> Double -> Double -> [CubicBezier]
+ Geom2D.CubicBezier.Outline: bezierOffset :: CubicBezier Double -> Double -> Maybe Int -> Double -> [CubicBezier Double]
- Math.BernsteinPoly: (*~) :: Double -> BernsteinPoly -> BernsteinPoly
+ Math.BernsteinPoly: (*~) :: (Unbox a, Num a) => a -> BernsteinPoly a -> BernsteinPoly a
- Math.BernsteinPoly: (~*) :: BernsteinPoly -> BernsteinPoly -> BernsteinPoly
+ Math.BernsteinPoly: (~*) :: (Unbox a, Fractional a) => BernsteinPoly a -> BernsteinPoly a -> BernsteinPoly a
- Math.BernsteinPoly: (~+) :: BernsteinPoly -> BernsteinPoly -> BernsteinPoly
+ Math.BernsteinPoly: (~+) :: (Unbox a, Fractional a) => BernsteinPoly a -> BernsteinPoly a -> BernsteinPoly a
- Math.BernsteinPoly: (~-) :: BernsteinPoly -> BernsteinPoly -> BernsteinPoly
+ Math.BernsteinPoly: (~-) :: (Unbox a, Fractional a) => BernsteinPoly a -> BernsteinPoly a -> BernsteinPoly a
- Math.BernsteinPoly: BernsteinPoly :: !Int -> ![Double] -> BernsteinPoly
+ Math.BernsteinPoly: BernsteinPoly :: Vector a -> BernsteinPoly a
- Math.BernsteinPoly: bernsteinCoeffs :: BernsteinPoly -> ![Double]
+ Math.BernsteinPoly: bernsteinCoeffs :: BernsteinPoly a -> Vector a
- Math.BernsteinPoly: bernsteinDeriv :: BernsteinPoly -> BernsteinPoly
+ Math.BernsteinPoly: bernsteinDeriv :: (Unbox a, Num a) => BernsteinPoly a -> BernsteinPoly a
- Math.BernsteinPoly: bernsteinEval :: BernsteinPoly -> Double -> Double
+ Math.BernsteinPoly: bernsteinEval :: (Unbox a, Fractional a) => BernsteinPoly a -> a -> a
- Math.BernsteinPoly: bernsteinEvalDerivs :: BernsteinPoly -> Double -> [Double]
+ Math.BernsteinPoly: bernsteinEvalDerivs :: (Unbox t, Fractional t) => BernsteinPoly t -> t -> [t]
- Math.BernsteinPoly: bernsteinSplit :: BernsteinPoly -> Double -> (BernsteinPoly, BernsteinPoly)
+ Math.BernsteinPoly: bernsteinSplit :: (Unbox a, Num a) => BernsteinPoly a -> a -> (BernsteinPoly a, BernsteinPoly a)
- Math.BernsteinPoly: bernsteinSubsegment :: BernsteinPoly -> Double -> Double -> BernsteinPoly
+ Math.BernsteinPoly: bernsteinSubsegment :: (Unbox a, Ord a, Fractional a) => BernsteinPoly a -> a -> a -> BernsteinPoly a
- Math.BernsteinPoly: data BernsteinPoly
+ Math.BernsteinPoly: data BernsteinPoly a
- Math.BernsteinPoly: degreeElevate :: BernsteinPoly -> Int -> BernsteinPoly
+ Math.BernsteinPoly: degreeElevate :: (Unbox a, Fractional a) => BernsteinPoly a -> Int -> BernsteinPoly a
- Math.BernsteinPoly: listToBernstein :: [Double] -> BernsteinPoly
+ Math.BernsteinPoly: listToBernstein :: (Unbox a, Num a) => [a] -> BernsteinPoly a
- Math.BernsteinPoly: zeroPoly :: BernsteinPoly
+ Math.BernsteinPoly: zeroPoly :: (Num a, Unbox a) => BernsteinPoly a
Files
- Geom2D.hs +134/−45
- Geom2D/CubicBezier.hs +2/−0
- Geom2D/CubicBezier/Approximate.hs +347/−168
- Geom2D/CubicBezier/Basic.hs +279/−76
- Geom2D/CubicBezier/Curvature.hs +7/−7
- Geom2D/CubicBezier/Intersection.hs +60/−37
- Geom2D/CubicBezier/MetaPath.hs +97/−64
- Geom2D/CubicBezier/Numeric.hs +237/−2
- Geom2D/CubicBezier/Outline.hs +10/−29
- Geom2D/CubicBezier/Overlap.lhs +980/−0
- Math/BernsteinPoly.hs +180/−99
- cubicbezier.cabal +5/−5
- tests/test.hs +5/−5
Geom2D.hs view
@@ -1,63 +1,123 @@-{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, FlexibleInstances, DeriveFunctor, FunctionalDependencies #-} -- | Basic 2 dimensional geometry functions. module Geom2D where+import qualified Data.Vector.Generic.Mutable as M+import qualified Data.Vector.Generic as G+import qualified Data.Vector.Unboxed as V+import Control.Monad + infixl 6 ^+^, ^-^ infixl 7 *^, ^*, ^/ infixr 5 $* -data Point = Point {- pointX :: {-# UNPACK #-} !Double,- pointY :: {-# UNPACK #-} !Double}- deriving Eq+data Point a = Point {+ pointX :: !a,+ pointY :: !a}+ deriving (Eq, Functor) -instance Show Point where+type DPoint = Point Double++instance Show a => Show (Point a) where show (Point x y) = "Point " ++ show x ++ " " ++ show y -- | A transformation (x, y) -> (ax + by + c, dx + ey + d)-data Transform = Transform {- xformA :: {-# UNPACK #-} !Double,- xformB :: {-# UNPACK #-} !Double,- xformC :: {-# UNPACK #-} !Double,- xformD :: {-# UNPACK #-} !Double,- xformE :: {-# UNPACK #-} !Double,- xformF :: {-# UNPACK #-} !Double }- deriving Show+data Transform a = Transform {+ xformA :: !a,+ xformB :: !a,+ xformC :: !a,+ xformD :: !a,+ xformE :: !a,+ xformF :: !a }+ deriving (Eq, Show, Functor) -data Line = Line Point Point-data Polygon = Polygon [Point]+data Line a = Line (Point a) (Point a)+ deriving (Show, Eq, Functor)+data Polygon a = Polygon [Point a]+ deriving (Show, Eq, Functor) -class AffineTransform a where- transform :: Transform -> a -> a+class AffineTransform a b | a -> b where+ transform :: Transform b -> a -> a -instance AffineTransform Transform where+instance Num a => AffineTransform (Transform a) a where+ {-# INLINE transform #-} transform (Transform a' b' c' d' e' f') (Transform a b c d e f) = Transform (a*a'+b'*d) (a'*b + b'*e) (a'*c+b'*f +c') (d'*a+e'*d) (d'*b+e'*e) (d'*c+e'*f+f') -instance AffineTransform Point where+instance Num a => AffineTransform (Point a) a where+ {-# INLINE transform #-} transform (Transform a b c d e f) (Point x y) = Point (a*x + b*y + c) (d*x + e*y + f) -instance AffineTransform Polygon where+instance Num a => AffineTransform (Polygon a) a where+ {-# INLINE transform #-} transform t (Polygon p) = Polygon $ map (transform t) p +newtype instance V.MVector s (Point a) = MV_Point (V.MVector s (a, a))+newtype instance V.Vector (Point a) = V_Point (V.Vector (a, a))++instance V.Unbox a => V.Unbox (Point a)+instance V.Unbox a => M.MVector V.MVector (Point a) where+ {-# INLINE basicLength #-}+ {-# INLINE basicUnsafeSlice #-}+ {-# INLINE basicOverlaps #-}+ {-# INLINE basicUnsafeNew #-}+ {-# INLINE basicUnsafeReplicate #-}+ {-# INLINE basicUnsafeRead #-}+ {-# INLINE basicUnsafeWrite #-}+ {-# INLINE basicClear #-}+ {-# INLINE basicSet #-}+ {-# INLINE basicUnsafeCopy #-}+ {-# INLINE basicUnsafeGrow #-}+ basicLength (MV_Point v) = M.basicLength v+ basicUnsafeSlice i n (MV_Point v) = MV_Point $ M.basicUnsafeSlice i n v+ basicOverlaps (MV_Point v1) (MV_Point v2) = M.basicOverlaps v1 v2+ basicUnsafeNew n = MV_Point `liftM` M.basicUnsafeNew n+ basicUnsafeReplicate n (Point x y) = MV_Point `liftM` M.basicUnsafeReplicate n (x,y)+ basicUnsafeRead (MV_Point v) i = uncurry Point `liftM` M.basicUnsafeRead v i+ basicUnsafeWrite (MV_Point v) i (Point x y) = M.basicUnsafeWrite v i (x,y)+ basicClear (MV_Point v) = M.basicClear v+ basicSet (MV_Point v) (Point x y) = M.basicSet v (x,y)+ basicUnsafeCopy (MV_Point v1) (MV_Point v2) = M.basicUnsafeCopy v1 v2+ basicUnsafeGrow (MV_Point v) n = MV_Point `liftM` M.basicUnsafeGrow v n++instance V.Unbox a => G.Vector V.Vector (Point a) where+ {-# INLINE basicUnsafeFreeze #-}+ {-# INLINE basicUnsafeThaw #-}+ {-# INLINE basicLength #-}+ {-# INLINE basicUnsafeSlice #-}+ {-# INLINE basicUnsafeIndexM #-}+ {-# INLINE elemseq #-}+ basicUnsafeFreeze (MV_Point v) = V_Point `liftM` G.basicUnsafeFreeze v+ basicUnsafeThaw (V_Point v) = MV_Point `liftM` G.basicUnsafeThaw v+ basicLength (V_Point v) = G.basicLength v+ basicUnsafeSlice i n (V_Point v) = V_Point $ G.basicUnsafeSlice i n v+ basicUnsafeIndexM (V_Point v) i+ = uncurry Point `liftM` G.basicUnsafeIndexM v i+ basicUnsafeCopy (MV_Point mv) (V_Point v)+ = G.basicUnsafeCopy mv v+ elemseq _ (Point x y) z = G.elemseq (undefined :: V.Vector a) x+ $ G.elemseq (undefined :: V.Vector a) y z+ -- | Operator for applying a transformation.-($*) :: AffineTransform a => Transform -> a -> a+($*) :: AffineTransform a b => Transform b -> a -> a t $* p = transform t p+{-# INLINE ($*) #-} -- | Calculate the inverse of a transformation.-inverse :: Transform -> Maybe Transform+inverse :: (Eq a, Num a, Fractional a) => Transform a -> Maybe (Transform a) inverse (Transform a b c d e f) = case a*e - b*d of 0 -> Nothing det -> Just $! Transform (a/det) (d/det) (-(a*c + d*f)/det) (b/det) (e/det) (-(b*c + e*f)/det)+{-# SPECIALIZE inverse :: Transform Double -> Maybe (Transform Double) #-} -- | Return the parameters (a, b, c) for the normalised equation -- of the line: @a*x + b*y + c = 0@.-lineEquation :: Line -> (Double, Double, Double)+lineEquation :: Floating t => Line t -> (t, t, t) lineEquation (Line (Point x1 y1) (Point x2 y2)) = (a, b, c) where a = a' / d b = b' / d@@ -65,100 +125,129 @@ a' = y1 - y2 b' = x2 - x1 d = sqrt(a'*a' + b'*b')+{-# SPECIALIZE lineEquation :: Line Double -> (Double, Double, Double) #-} -- | Return the signed distance from a point to the line. If the -- distance is negative, the point lies to the right of the line- -lineDistance :: Line -> Point -> Double+lineDistance :: Floating a => Line a -> Point a -> a lineDistance l = \(Point x y) -> a*x + b*y + c where (a, b, c) = lineEquation l+{-# SPECIALIZE lineDistance :: Line Double -> DPoint -> Double #-} +-- | Return the point on the line closest to the given point.+closestPoint :: Fractional a => Line a -> Point a -> Point a+closestPoint (Line p1 p2) p3 = Point px py+ where+ (Point dx dy) = p2 ^-^ p1+ u = dy*pointY p3 + dx*pointX p3+ v = pointX p1*pointY p2 - pointX p2*pointY p1+ m = dx*dx + dy*dy+ px = (dx*u + dy*v) / m+ py = (dy*u - dx*v) / m+{-# specialize closestPoint :: Line Double -> Point Double -> Point Double #-} + -- | The lenght of the vector.-vectorMag :: Point -> Double+vectorMag :: Floating a => Point a -> a vectorMag (Point x y) = sqrt(x*x + y*y) {-# INLINE vectorMag #-} -- | The angle of the vector, in the range @(-'pi', 'pi']@.-vectorAngle :: Point -> Double+vectorAngle :: RealFloat a => Point a -> a vectorAngle (Point 0.0 0.0) = 0.0 vectorAngle (Point x y) = atan2 y x {-# INLINE vectorAngle #-} -- | The unitvector with the given angle.-dirVector :: Double -> Point+dirVector :: Floating a => a -> Point a dirVector angle = Point (cos angle) (sin angle) {-# INLINE dirVector #-} -- | The unit vector with the same direction.-normVector :: Point -> Point+normVector :: Floating a => Point a -> Point a normVector p@(Point x y) = Point (x/l) (y/l) where l = vectorMag p+{-# INLINE normVector #-} -- | Scale vector by constant.-(*^) :: Double -> Point -> Point+(*^) :: Num a => a -> Point a -> Point a s *^ (Point x y) = Point (s*x) (s*y) {-# INLINE (*^) #-} -- | Scale vector by reciprocal of constant.-(^/) :: Point -> Double -> Point+(^/) :: Fractional a => Point a -> a -> Point a (Point x y) ^/ s = Point (x/s) (y/s) {-# INLINE (^/) #-} -- | Scale vector by constant, with the arguments swapped.-(^*) :: Point -> Double -> Point+(^*) :: Num a => Point a -> a -> Point a p ^* s = s *^ p {-# INLINE (^*) #-} -- | Add two vectors.-(^+^) :: Point -> Point -> Point+(^+^) :: Num a => Point a -> Point a -> Point a (Point x1 y1) ^+^ (Point x2 y2) = Point (x1+x2) (y1+y2) {-# INLINE (^+^) #-} -- | Subtract two vectors.-(^-^) :: Point -> Point -> Point+(^-^) :: Num a => Point a -> Point a -> Point a (Point x1 y1) ^-^ (Point x2 y2) = Point (x1-x2) (y1-y2) {-# INLINE (^-^) #-} -- | Dot product of two vectors.-(^.^) :: Point -> Point -> Double+(^.^) :: Num a => Point a -> Point a -> a (Point x1 y1) ^.^ (Point x2 y2) = x1*x2 + y1*y2 {-# INLINE (^.^) #-} -- | Cross product of two vectors.-vectorCross :: Point -> Point -> Double+vectorCross :: Num a => Point a -> Point a -> a vectorCross (Point x1 y1) (Point x2 y2) = x1*y2 - y1*x2 {-# INLINE vectorCross #-} -- | Distance between two vectors.-vectorDistance :: Point -> Point -> Double+vectorDistance :: Floating a => Point a -> Point a -> a vectorDistance p q = vectorMag (p^-^q) {-# INLINE vectorDistance #-} -- | Interpolate between two vectors.-interpolateVector :: Point -> Point -> Double -> Point+interpolateVector :: (Num a) => Point a -> Point a -> a -> Point a interpolateVector a b t = t*^b ^+^ (1-t)*^a {-# INLINE interpolateVector #-} -- | Create a transform that rotates by the angle of the given vector+-- and multiplies with the magnitude of the vector.+rotateScaleVec :: Num a => Point a -> Transform a+rotateScaleVec (Point x y) = Transform x (-y) 0 y x 0+{-# INLINE rotateScaleVec #-}++-- | reflect the vector over the X-axis.+flipVector :: (Num a) => Point a -> Point a+flipVector (Point x y) = Point x (-y)+{-# INLINE flipVector #-}++-- | Create a transform that rotates by the angle of the given vector -- with the x-axis-rotateVec :: Point -> Transform+rotateVec :: Floating a => Point a -> Transform a rotateVec v = Transform x (-y) 0 y x 0 where Point x y = normVector v+{-# INLINE rotateVec #-} -- | Create a transform that rotates by the given angle (radians).-rotate :: Double -> Transform+rotate :: Floating s => s -> Transform s rotate a = Transform (cos a) (negate $ sin a) 0 (sin a) (cos a) 0+{-# INLINE rotate #-} -- | Rotate vector 90 degrees left.-rotate90L :: Transform+rotate90L :: Floating s => Transform s rotate90L = rotateVec (Point 0 1)+{-# INLINE rotate90L #-} -- | Rotate vector 90 degrees right.-rotate90R :: Transform+rotate90R :: Floating s => Transform s rotate90R = rotateVec (Point 0 (-1))+{-# INLINE rotate90R #-} -- | Create a transform that translates by the given vector.-translate :: Point -> Transform+translate :: Num a => Point a -> Transform a translate (Point x y) = Transform 1 0 x 0 1 y-+{-# INLINE translate #-}
Geom2D/CubicBezier.hs view
@@ -3,6 +3,7 @@ module Geom2D.CubicBezier (module Geom2D.CubicBezier.Basic, module Geom2D.CubicBezier.Approximate,+ module Geom2D.CubicBezier.Overlap, module Geom2D.CubicBezier.Outline, module Geom2D.CubicBezier.Curvature, module Geom2D.CubicBezier.Intersection,@@ -15,6 +16,7 @@ import Geom2D.CubicBezier.Approximate import Geom2D.CubicBezier.Outline import Geom2D.CubicBezier.Curvature+import Geom2D.CubicBezier.Overlap import Geom2D.CubicBezier.Intersection import Geom2D.CubicBezier.MetaPath
Geom2D/CubicBezier/Approximate.hs view
@@ -1,16 +1,17 @@-{-# LANGUAGE BangPatterns #-}-module Geom2D.CubicBezier.Approximate (- approximatePath, approximatePathMax, approximateCurve, approximateCurveWithParams)+{-# LANGUAGE BangPatterns, MultiWayIf #-}+module Geom2D.CubicBezier.Approximate+-- (approximatePath, approximateQuadPath, approximatePathMax, approximateCubic) where import Geom2D-import Geom2D.CubicBezier.Numeric import Geom2D.CubicBezier.Basic-import Data.Function-import Data.List+import Geom2D.CubicBezier.Numeric import Data.Maybe+import Data.List+import qualified Data.Vector.Unboxed as V import qualified Data.Map as M+import Data.Function -interpolate :: Double -> Double -> Double -> Double+interpolate :: (Num a) => a -> a -> a -> a interpolate a b x = (1-x)*a + x*b -- | Approximate a function with piecewise cubic bezier splines using@@ -18,128 +19,294 @@ -- approximated by using a finite number of samples. It is recommended -- to avoid changes in direction by subdividing the original function -- at points of inflection.+approximatePath :: (V.Unbox a, Ord a, Floating a) =>+ (a -> (Point a, Point a)) -- ^ The function to approximate and it's derivative+ -> Int+ -- ^ The number of discrete samples taken to+ -- approximate each subcurve. More samples are+ -- more precise but take more time to calculate.+ -- For good precision 16 is a good candidate.+ -> a -- ^ The tolerance+ -> a -- ^ The lower parameter of the function + -> a -- ^ The upper parameter of the function+ -> Bool+ -- ^ Calculate the result faster, but with more+ -- subcurves. Runs typically 10 times faster, but+ -- generates 50% more subcurves. Useful for interactive use.+ -> [CubicBezier a]+approximatePath f n tol tmin tmax fast+ | err < tol = [curve]+ | otherwise = approximatePath' f n tol tmin tmax fast+ where+ (curve, err) = approx1cubic n f tmin tmax (if fast then 0 else 5)+{-# SPECIALIZE approximatePath :: (Double -> (DPoint, DPoint)) -> Int -> Double+ -> Double -> Double -> Bool -> [CubicBezier Double] #-} -approximatePath :: (Double -> (Point, Point)) -- ^ The function to approximate and it's derivative- -> Double -- ^ The number of discrete samples taken to approximate each subcurve- -> Double -- ^ The tolerance- -> Double -- ^ The lower parameter of the function - -> Double -- ^ The upper parameter of the function- -> [CubicBezier]-approximatePath f n tol tmin tmax- | err <= tol = [cb_out]- | otherwise = approximatePath f n tol tmin terr ++- approximatePath f n tol terr tmax+-- | Approximate a function with piecewise quadratic bezier splines+-- using a least-squares fit, within the given tolerance. It is+-- recommended to avoid changes in direction by subdividing the+-- original function at points of inflection.+approximateQuadPath :: (Show a, V.Unbox a, Ord a, Floating a) =>+ (a -> (Point a, Point a)) -- ^ The function to approximate and it's derivative+ -> a -- ^ The tolerance+ -> a -- ^ The lower parameter of the function + -> a -- ^ The upper parameter of the function+ -> Bool + -- ^ Calculate the result faster, but with more+ -- subcurves.+ -> [QuadBezier a]+approximateQuadPath f tol tmin tmax fast+ | err < tol = [curve]+ | otherwise = approximateQuad' f tol tmin tmax fast where- (cb_out, terr', err) = approximateCurveWithParams curveCb- points ts tol- terr = interpolate tmin tmax terr'- ts = [i/(n+1) | i <- [1..n]]- points = map (fst . f . interpolate tmin tmax) ts- (t0, t0') = f tmin- (t1, t1') = f tmax- curveCb = CubicBezier t0 (t0^+^t0') (t1^-^t1') t1+ curve = approx1quad f tmin tmax+ err = maxDist f curve tmin tmax+{-# SPECIALIZE approximateQuadPath :: (Double -> (DPoint, DPoint)) -> Double ->+ Double -> Double -> Bool -> [QuadBezier Double] #-}+ +-- find the distance between the function at t and the quadratic bezier.+-- calculate the value and derivative at t, and improve the closeness of t.+quadDist :: (V.Unbox a, Floating a) =>+ (a -> (Point a, Point a)) -> QuadBezier a -> a -> a -> a -> a+quadDist f qb tmin tmax t =+ let p = fst (f $ interpolate tmin tmax t)+ (b, b') = evalBezierDeriv qb t+ -- distance from p to the normal at b(t) / velocity+ nd = ((p ^-^ b) ^.^ b') / (b'^.^b')+ in vectorDistance p $ evalBezier qb (t + nd) +phi :: (Floating a) => a+phi = (-1 + sqrt 5) / 2 +goldSearch :: (Ord a, Floating a) => (a -> a) -> a+goldSearch f =+ goldSearch' f 0 x1 x2 1 (f 0)+ (f x1) (f x2) (f 1) 4+ where x1 = 1 - phi+ x2 = phi++goldSearch' :: (Ord a, Floating a) =>+ (a -> a) -> a -> a -> a ->+ a -> a -> a -> a -> a -> Int -> a+goldSearch' f x0 x1 x2 x3 y0 y1 y2 y3 maxiter+ | maxiter < 1 = maximum [y0, y1, y2, y3]+ | y1 < y2 =+ let x25 = x1 + phi*(x3-x1)+ y25 = f x25+ in goldSearch' f x1 x2 x25 x3 y1 y2 y25 y3 (maxiter-1)+ | otherwise =+ let x05 = x2 + phi*(x0-x2)+ y05 = f x05+ in goldSearch' f x0 x05 x1 x2 y0 y05 y1 y2 (maxiter-1)++-- find maximum distance using golden section search+maxDist :: (V.Unbox a, Ord a, Floating a) =>+ (a -> (Point a, Point a)) ->+ QuadBezier a -> a -> a -> a+maxDist f qb tmin tmax = goldSearch (quadDist f qb tmin tmax)++approxquad :: (Ord a, Floating a) =>+ Point a -> Point a -> Point a -> Point a -> QuadBezier a+approxquad p0 p0' p1' p1+ | abs (pointY q') < abs (pointX q'*1e-3) = + QuadBezier p0 (interpolateVector p0 p1 0.5) p1+ | otherwise = QuadBezier p0 (p1^+^p1'^*t) p1+ where+ q = rotateVec (flipVector p0') $* p1^-^p0+ q' = rotateVec (flipVector p0') $* p1'+ t = - pointY q / pointY q'++approx1quad :: (Ord a, Floating a) =>+ (a -> (Point a, Point a)) -> a -> a -> QuadBezier a+approx1quad f tmin tmax =+ approxquad p0 p0' p1' p1+ where (p0, p0') = f tmin+ (p1, p1') = f tmax++splitQuad :: (Show a, V.Unbox a, Ord a, Floating a) =>+ a -> a -> (a -> (Point a, Point a))+ -> a -> a -> Int -> (a, a, QuadBezier a, a, QuadBezier a)+splitQuad node offset f tmin tmax maxiter+ | maxiter < 1 || (err0 < 2*err1 && err0 > err1/2) =+ (tmid, err0, curve0, err1, curve1)+ | otherwise =+ splitQuad (if err0 < err1 then node+offset else node-offset)+ (offset/2) f tmin tmax (maxiter-1)+ where+ tmid = interpolate tmin tmax node+ curve0 = approx1quad f tmin tmid + err0 = maxDist f curve0 tmin tmid+ curve1 = approx1quad f tmid tmax + err1 = maxDist f curve1 tmid tmax++approximateQuad' :: (Show a, V.Unbox a, Ord a, Floating a) =>+ (a -> (Point a, Point a)) -> + a -> a -> a -> Bool ->+ [QuadBezier a]+approximateQuad' f tol tmin tmax fast =+ (if err0 <= tol+ then [curve0]+ else approximateQuad' f tol tmin tmid fast) +++ (if err1 <= tol+ then [curve1]+ else approximateQuad' f tol tmid tmax fast)+ where+ (tmid, err0, curve0, err1, curve1) =+ splitQuad 0.5 0.25 f tmin tmax (if fast then 0 else 5)++approximatePath' :: (V.Unbox a, Ord a, Floating a) =>+ (a -> (Point a, Point a)) -> Int ->+ a -> a -> a -> Bool ->+ [CubicBezier a]+approximatePath' f n tol tmin tmax fast =+ (if err0 <= tol+ then [curve0]+ else approximatePath' f n tol tmin tmid fast) +++ (if err1 <= tol+ then [curve1]+ else approximatePath' f n tol tmid tmax fast)+ where+ (tmid, err0, curve0, err1, curve1) =+ splitCubic 0.5 0.25 n f tmin tmax (if fast then 0 else 5)+--{-# SPECIALIZE approximatePath' :: (Double -> (Point Double, Point Double)) -> Int -> Double -> Double -> Double -> [CubicBezier Double] #-} + -- | Like approximatePath, but limit the number of subcurves.-approximatePathMax :: Int -- ^ The maximum number of subcurves- -> (Double -> (Point, Point)) -- ^ The function to approximate and it's derivative- -> Double -- ^ The number of discrete samples taken to approximate each subcurve- -> Double -- ^ The tolerance- -> Double -- ^ The lower parameter of the function - -> Double -- ^ The upper parameter of the function- -> [CubicBezier]-approximatePathMax m f n tol tmin tmax =- approxMax f tol m ts segments- where segments = M.singleton err (FunctionSegment tmin tmax t_err outline)+approximatePathMax :: (V.Unbox a, Floating a, Ord a) =>+ Int -- ^ The maximum number of subcurves+ -> (a -> (Point a, Point a)) -- ^ The function to approximate and it's derivative+ -> Int+ -- ^ The number of discrete samples taken to+ -- approximate each subcurve. More samples are+ -- more precise but take more time to calculate.+ -- For good precision 16 is a good candidate.+ -> a -- ^ The tolerance+ -> a -- ^ The lower parameter of the function + -> a -- ^ The upper parameter of the function+ -> Bool+ -- ^ Calculate the result faster, but with more+ -- subcurves. Runs typically 10 times faster, but+ -- generates 50% more subcurves. Useful for interactive use.+ -> [CubicBezier a]+approximatePathMax m f n tol tmin tmax fast =+ approxMax f tol m ts fast segments+ where segments = M.singleton err (FunctionSegment tmin tmax outline) (p0, p0') = f tmin (p1, p1') = f tmax- ts = [i/(n+1) | i <- [1..n]]- points = map (fst . f . interpolate tmin tmax) ts+ ts = V.map (\i -> fromIntegral i/(fromIntegral n+1) `asTypeOf` tmin) $+ V.enumFromN (1::Int) n+ points = V.map (fst . f . interpolate tmin tmax) ts curveCb = CubicBezier p0 (p0^+^p0') (p1^-^p1') p1- (outline, t_err', err) = approximateCurveWithParams curveCb- points ts tol- t_err = interpolate tmin tmax t_err'--data FunctionSegment = FunctionSegment {- fs_t_min :: {-# UNPACK #-} !Double, -- the least t param of the segment in the original curve- _fs_t_max :: {-# UNPACK #-} !Double, -- the max t param of the segment in the original curve- _fs_t_err :: {-# UNPACK #-} !Double, -- the param where the error is maximal- fs_curve :: CubicBezier -- the curve segment+ (outline, err) =+ approximateCubic curveCb points (Just ts) (if fast then 0 else 5)+{-# SPECIALIZE approximatePathMax ::+ Int -> (Double -> (Point Double, Point Double)) -> Int + -> Double -> Double -> Double -> Bool -> [CubicBezier Double] #-}+data FunctionSegment a = FunctionSegment {+ fsTmin :: !a, -- the least t param of the segment in the original curve+ _fsTmax :: !a, -- the max t param of the segment in the original curve+ fsCurve :: CubicBezier a -- the curve segment } -- Keep a map from maxError to FunctionSegment for each subsegment to keep -- track of the segment with the maximum error. This ensures a n -- log(n) execution time, rather than n^2 when a list is used.-approxMax :: (Double -> (Point, Point)) -> Double -> Int- -> [Double] -> M.Map Double FunctionSegment -> [CubicBezier]-approxMax f tol n ts segments- | n < 1 = error "Minimum number of segments is one."- | (n == 1) || (err < tol) =- map fs_curve $ sortBy (compare `on` fs_t_min) $ map snd $ M.toList segments- | otherwise = approxMax f tol (n-1) ts $- M.insert err_l (FunctionSegment t_min t_err t_err_l curve_l) $- M.insert err_r (FunctionSegment t_err t_max t_err_r curve_r)+approxMax :: (V.Unbox a, Ord a, Floating a) =>+ (a -> (Point a, Point a)) -> a -> Int+ -> V.Vector a -> Bool -> M.Map a (FunctionSegment a) ->+ [CubicBezier a]+approxMax f tol n ts fast segments+ | (n <= 1) || (err < tol) =+ map fsCurve $ sortBy (compare `on` fsTmin) $+ map snd $ M.toList segments+ | otherwise = approxMax f tol (n-1) ts fast $+ M.insert err_l (FunctionSegment t_min t_mid curve_l) $+ M.insert err_r (FunctionSegment t_mid t_max curve_r) newSegments where- ((err, FunctionSegment t_min t_max t_err _), newSegments) = M.deleteFindMax segments- (fmin, fmin') = f t_min- (fmid, fmid') = f t_err- (fmax, fmax') = f t_max- fcurve_l = CubicBezier fmin (fmin^+^fmin') (fmid^-^fmid') fmid- fcurve_r = CubicBezier fmid (fmid^+^fmid') (fmax^-^fmax') fmax- pointsl = map (fst . f . interpolate t_min t_err) ts- pointsr = map (fst . f . interpolate t_err t_max) ts- t_err_l = interpolate t_min t_err t_err_l'- t_err_r = interpolate t_err t_max t_err_r'- (curve_l, t_err_l', err_l) = approximateCurveWithParams fcurve_l pointsl ts tol- (curve_r, t_err_r', err_r) = approximateCurveWithParams fcurve_r pointsr ts tol+ ((err, FunctionSegment t_min t_max _), newSegments) =+ M.deleteFindMax segments+ (t_mid, err_l, curve_l, err_r, curve_r) =+ splitCubic 0.5 0.25 n f t_min t_max (if fast then 0 else 5)+{-# SPECIALIZE approxMax :: (Double -> (Point Double, Point Double)) -> Double -> Int+ -> V.Vector Double -> Bool -> M.Map Double (FunctionSegment Double) -> [CubicBezier Double] #-}+ +splitCubic :: (V.Unbox a, Ord a, Floating a) =>+ a -> a -> Int -> (a -> (Point a, Point a))+ -> a -> a -> Int -> (a, a, CubicBezier a, a, CubicBezier a)+splitCubic node offset n f tmin tmax maxiter+ | maxiter < 1 || (err0 < 2*err1 && err0 > err1/2) =+ (tmid, err0, curve0, err1, curve1)+ | otherwise = + splitCubic (if err0 < err1 then node+offset else node-offset)+ (offset/2) n f tmin tmax (maxiter-1)+ where+ tmid = interpolate tmin tmax node+ (curve0, err0) = approx1cubic n f tmin tmid maxiter+ (curve1, err1) = approx1cubic n f tmid tmax maxiter+{-# SPECIALIZE splitCubic :: Double -> Double -> Int -> (Double -> (Point Double, Point Double))+ -> Double -> Double -> Int -> (Double, Double, CubicBezier Double, Double, CubicBezier Double) #-}+ +approx1cubic :: (V.Unbox a, Ord a, Floating a) =>+ Int -> (a -> (Point a, Point a)) -> a -> a ->+ Int -> (CubicBezier a, a)+approx1cubic n f t0 t1 maxiter =+ approximateCubic curveCb points (Just ts) maxiter+ where (p0, p0') = f t0+ (p1, p1') = f t1+ ts = V.map (\i -> fromIntegral i/(fromIntegral n+1))+ (V.enumFromN 1 n :: V.Vector Int)+ points = V.map (fst . f . interpolate t0 t1) ts+ curveCb = CubicBezier p0 (p0^+^p0') (p1^+^p1') p1+{-# SPECIALIZE approx1cubic :: Int -> (Double -> (Point Double, Point Double)) -> Double -> Double -> Int -> (CubicBezier Double, Double) #-} --- | @approximateCurve b pts eps@ finds the least squares fit of a bezier+-- | @approximateCubic b pts maxiter@ finds the least squares fit of a bezier -- curve to the points @pts@. The resulting bezier has the same first -- and last control point as the curve @b@, and have tangents colinear with @b@.--- return the curve, the parameter with maximum error, and maximum error.--- Calculate to withing eps tolerance.--approximateCurve :: CubicBezier -> [Point] -> Double -> (CubicBezier, Double, Double)-approximateCurve curve@(CubicBezier p1 _ _ p4) pts eps =- approximateCurveWithParams curve pts (approximateParams curve p1 p4 pts) eps---- | Like approximateCurve, but also takes an initial guess of the--- parameters closest to the points. This might be faster if a good--- guess can be made.--approximateCurveWithParams :: CubicBezier -> [Point] -> [Double] -> Double -> (CubicBezier, Double, Double)-approximateCurveWithParams curve pts ts eps =- let (c, newTs) = fromMaybe (curve, ts) $- approximateCurve' curve pts ts 40 (bezierParamTolerance curve eps) 1- curvePts = map (evalBezier c) newTs- distances = zipWith vectorDistance pts curvePts- (t, maxError) = maximumBy (compare `on` snd) (zip ts distances)- in (c, t, maxError)--data LSParams = LSParams {-# UNPACK #-} !Double- {-# UNPACK #-} !Double- {-# UNPACK #-} !Double- {-# UNPACK #-} !Double- {-# UNPACK #-} !Double- {-# UNPACK #-} !Double+approximateCubic :: (V.Unbox a, Ord a, Floating a) =>+ CubicBezier a -- ^ Curve+ -> V.Vector (Point a) -- ^ Points+ -> Maybe (V.Vector a) -- ^ Params. Approximate if Nothing+ -> Int -- ^ Maximum iterations+ -> (CubicBezier a, a) -- ^ result curve and maximum error+approximateCubic curve pts mbTs maxiter =+ let ts = fromMaybe (approximateParams (cubicC0 curve) (cubicC3 curve) pts) mbTs+ curve2 = fromMaybe curve $ lsqDist curve pts ts+ (bt, bt') = V.unzip $ V.map (evalBezierDeriv curve2) ts+ err = V.maximum $ V.zipWith vectorDistance pts bt+ (c, _, _, err2, _) =+ fromMaybe (curve2, ts, undefined, err, undefined) $+ approximateCubic' curve2 pts ts maxiter err bt bt'+ in (c, err2)+{-# SPECIALIZE approximateCubic :: CubicBezier Double -> V.Vector (Point Double)+ -> Maybe (V.Vector Double) -> Int -> (CubicBezier Double, Double) #-} -addParams :: LSParams -> LSParams -> LSParams-addParams (LSParams a b c d e f) (LSParams a' b' c' d' e' f') =- LSParams (a+a') (b+b') (c+c') (d+d') (e+e') (f+f')+-- find (a, b) which minimises ∑ᵢ(a*aᵢ + b*bᵢ + epsᵢ)²+leastSquares :: (V.Unbox a, Fractional a, Eq a) =>+ V.Vector a -> V.Vector a -> V.Vector a -> Maybe (a, a)+leastSquares as bs epses = solveLinear2x2 a b c d e f+ where+ square x = x*x+ a = V.sum $ V.map square as+ b = V.sum $ V.zipWith (*) as bs+ c = V.sum $ V.zipWith (*) as epses+ d = b+ e = V.sum $ V.map square bs+ f = V.sum $ V.zipWith (*) bs epses+{-# SPECIALIZE leastSquares ::V.Vector Double -> V.Vector Double -> V.Vector Double -> Maybe (Double, Double) #-} --- find the least squares between the points p_i and B(t_i) for--- bezier curve B, where pts contains the points p_i and ts--- the values of t_i .+-- find the least squares between the points pᵢ and B(tᵢ) for+-- bezier curve B, where pts contains the points pᵢ and ts+-- the values of tᵢ . -- The tangent at the beginning and end is maintained. -- Since the start and end point remains the same,--- we need to find the new value of p2' = p1 + alpha1 * (p2 - p1)--- and p3' = p4 + alpha2 * (p3 - p4)--- minimizing (sum |B(t_i) - p_i|^2) gives a linear equation--- with two unknown values (alpha1 and alpha2), which can be--- solved easily-leastSquares :: CubicBezier -> [Point] -> [Double] -> Maybe CubicBezier-leastSquares (CubicBezier (Point !p1x !p1y) (Point !p2x !p2y) (Point !p3x !p3y) (Point !p4x !p4y)) pts ts = let+-- we need to find the new value of p2' = p1 + α₁ * (p2 - p1)+-- and p₃' = p4 + α2 * (p3 - p4)+-- minimizing (∑|B(tᵢ) - pᵢ|²) gives a linear equation+-- with two unknown values (α₁ and α₂)+lsqDist :: (V.Unbox a, Fractional a, Eq a) =>+ CubicBezier a+ -> V.Vector (Point a) -> V.Vector a -> Maybe (CubicBezier a)+lsqDist (CubicBezier (Point !p1x !p1y) (Point !p2x !p2y) (Point !p3x !p3y) (Point !p4x !p4y)) pts ts = let calcParams t (Point px py) = let t2 = t * t; t3 = t2 * t ax = 3 * (p2x - p1x) * (t3 - 2 * t2 + t)@@ -148,76 +315,88 @@ by = 3 * (p3y - p4y) * (t2 - t3) cx = (p4x - p1x) * (3 * t2 - 2 * t3) + p1x - px cy = (p4y - p1y) * (3 * t2 - 2 * t3) + p1y - py- in LSParams- (ax * ax + ay * ay)- (ax * bx + ay * by)- (ax * cx + ay * cy)- (bx * ax + by * ay)- (bx * bx + by * by)- (bx * cx + by * cy)- LSParams !a !b !c !d !e !f = foldl1' addParams (zipWith calcParams ts pts)- in do (alpha1, alpha2) <- solveLinear2x2 a b c d e f+ in (ax * ax + ay * ay,+ ax * bx + ay * by,+ ax * cx + ay * cy,+ bx * ax + by * ay,+ bx * bx + by * by,+ bx * cx + by * cy)+ add6 (!a,!b,!c,!d,!e,!f) (!a',!b',!c',!d',!e',!f') =+ (a+a',b+b',c+c',d+d',e+e',f+f')+ ( as, bs, cs, ds, es, fs ) = V.foldl1' add6 $ V.zipWith calcParams ts pts+ in do (alpha1, alpha2) <- solveLinear2x2 as bs cs ds es fs let cp1 = Point (alpha1 * (p2x - p1x) + p1x) (alpha1 * (p2y - p1y) + p1y) cp2 = Point (alpha2 * (p3x - p4x) + p4x) (alpha2 * (p3y - p4y) + p4y) Just $ CubicBezier (Point p1x p1y) cp1 cp2 (Point p4x p4y)+{-# SPECIALIZE lsqDist :: CubicBezier Double+ -> V.Vector (Point Double) -> V.Vector Double -> Maybe (CubicBezier Double) #-} -- calculate the least Squares bezier curve by choosing approximate values -- of t, and iterating again with an improved estimate of t, by taking the -- the values of t for which the points are closest to the curve+approximateCubic' :: (V.Unbox a, Ord a, Floating a) =>+ CubicBezier a+ -> V.Vector (Point a) -> V.Vector a+ -> Int -> a -> V.Vector (Point a)+ -> V.Vector (Point a)+ -> Maybe (CubicBezier a, V.Vector a, V.Vector a, a, V.Vector (Point a))+approximateCubic' (CubicBezier p1 p2 p3 p4) pts ts maxiter err bt bt' = do+ let dir1 = V.map (($* (p2^-^p1)) . rotateVec . flipVector) bt'+ dir2 = V.map (($* (p3^-^p4)) . rotateVec . flipVector) bt'+ ps = V.zipWith3 (\b b' p ->+ rotateVec (flipVector b') $*+ (p^-^b)) bt bt' pts+ errs = V.map (negate.pointY) ps+ as = V.zipWith (\d t -> 3*pointY d*(1-t)*(1-t)*t)+ dir1 ts+ bs = V.zipWith (\d t -> 3*pointY d*(1-t)*t*t)+ dir2 ts+ (a,b) <- leastSquares as bs errs+ let newTs = V.zipWith5 (\t p d1 d2 b' ->+ max 0 $ min 1 $+ t + (pointX p - 3*(1-t)*t*(a*pointX d1*(1-t) ++ b*pointX d2*t)) /+ vectorMag b')+ ts ps dir1 dir2 bt'+ newCurve = CubicBezier p1 (p2 ^+^ a*^(p2^-^p1)) (p3 ^+^ b*^(p3^-^p4)) p4+ (bt2,bt2') = V.unzip $ V.map (evalBezierDeriv newCurve) newTs+ err2 = V.zipWith vectorDistance pts bt2+ maxErr = V.maximum err2+ -- alternative method for finding the t values:+ -- newTs = V.zipWith (-) ts (V.zipWith (calcDeltaT newCurve) pts ts)+ if maxiter < 1 || abs(err - maxErr) <= err/8+ then return (newCurve, newTs, err2, maxErr, bt2)+ else approximateCubic' newCurve pts newTs (maxiter-1) maxErr bt2 bt2'+{-# SPECIALIZE approximateCubic' ::+ CubicBezier Double -> V.Vector (Point Double) -> V.Vector Double+ -> Int -> Double -> V.Vector (Point Double)+ -> V.Vector (Point Double)+ -> Maybe (CubicBezier Double, V.Vector Double, V.Vector Double, Double, V.Vector (Point Double)) #-} -approximateCurve' :: CubicBezier -> [Point] -> [Double] -> Int -> Double -> Double -> Maybe (CubicBezier, [Double])-approximateCurve' curve pts ts maxiter eps prevDeltaT = do- newCurve <- leastSquares curve pts ts- let deltaTs = zipWith (calcDeltaT newCurve) pts ts- ts' = map (max 0 . min 1) $ zipWith (-) ts deltaTs- newerCurve <- leastSquares curve pts ts'- let deltaTs' = zipWith (calcDeltaT newerCurve) pts ts'- newTs = interpolateTs ts ts' deltaTs deltaTs'- thisDeltaT = maximum $ map abs $ zipWith (-) newTs ts- if maxiter < 1 ||- -- Because convergence may be slow initially, make sure it is converging:- (prevDeltaT < eps/2 && thisDeltaT < prevDeltaT / 2)- then do c <- leastSquares curve pts newTs- return (c, newTs)- else approximateCurve' curve pts newTs (maxiter - 1) eps thisDeltaT --- improve convergence by making a better estimate for t--- it is based on the observation that the ratio --- r = dt_2 / dt_1, with dt_2 = t_2 - t_1 and dt_1 = t_1 - t_0--- for successive approximations of t changes little.--- The infinite sum (dt_1 + dt_1 * r + dt_1 * r^2 + dt_1 * r^3 ...)--- can easily be calculated by dt_1 * (1 / (1 - r))--- which becomes dt_1^2 / (dt_1 - dt_2)--- Only do this if it appears to converge for all values of t--- If the value of t changes too much keep the old value.--- This improves the convergence by a factor of about 10-interpolateTs :: [Double] -> [Double] -> [Double] -> [Double] -> [Double]-interpolateTs ts ts' deltaTs deltaTs' =- map (max 0 . min 1) (- if all id $ zipWith (\dT dT' -> dT * dT' > 0 && dT' / dT < 1) deltaTs deltaTs'- then zipWith3 (\t dT dT' -> let- newDt = (dT * dT / (dT - dT'))- in t - (if abs newDt > 0.2 then dT' else newDt)) ts deltaTs deltaTs'- else zipWith (-) ts' deltaTs')- -- approximate t by calculating the distances between all points -- and dividing by the total sum-approximateParams :: CubicBezier -> Point -> Point -> [Point] -> [Double]-approximateParams cb start end pts = let- segments = start : (pts ++ [end])- dists = zipWith vectorDistance segments (tail segments)- total = sum dists- improve p t = t - calcDeltaT cb p t- in zipWith improve pts $ map (/ total) $ scanl1 (+) dists+approximateParams :: (V.Unbox a, Floating a) =>+ Point a -> Point a -> V.Vector (Point a) -> V.Vector a+approximateParams start end pts + | V.null pts = V.empty+ | otherwise =+ let dists = V.generate (V.length pts)+ (\i -> if i == 0+ then vectorDistance start (V.unsafeIndex pts 0)+ else vectorDistance (V.unsafeIndex pts (i-1)) (V.unsafeIndex pts i))+ total = V.sum dists + vectorDistance (V.last pts) end+ in V.map (/ total) $ V.scanl1 (+) dists+{-# SPECIALIZE approximateParams ::+ Point Double -> Point Double -> V.Vector (Point Double) -> V.Vector Double #-} --- find a value of t where B(t) is closer between the bezier curve and--- the point (ptx, pty), by solving f' = 0 where--- f(t) = (X(t) - x)^2 + (Y(t) - y)^2, the square of the distance between the bezier and the point--- the reduction of t is one iteration of Newton Raphson: f'(t)/f''(t)--- using more iterations doesn't appear to give an improvement--- See Curve Fitting with Piecewise Parametric Cubics by Stone & Plass-calcDeltaT :: CubicBezier -> Point -> Double -> Double-calcDeltaT curve (Point !ptx !pty) t = let- [Point bezx bezy, Point dbezx dbezy, Point ddbezx ddbezy, _] = evalBezierDerivs curve t- in ((bezx - ptx) * dbezx + (bezy - pty) * dbezy) /- (dbezx * dbezx + dbezy * dbezy + (bezx - ptx) * ddbezx + (bezy - pty) * ddbezy)+-- Alternative method for finding the next t values, using+-- Newton-Rafphson. There is no noticable difference in speed or+-- efficiency.++-- calcDeltaT :: CubicBezier -> Point -> Double -> Double+-- calcDeltaT curve (Point !ptx !pty) t = let+-- (Point bezx bezy, Point dbezx dbezy, Point ddbezx ddbezy, _) = evalBezierDerivs curve t+-- in ((bezx - ptx) * dbezx + (bezy - pty) * dbezy) /+-- (dbezx * dbezx + dbezy * dbezy + (bezx - ptx) * ddbezx + (bezy - pty) * ddbezy)+
Geom2D/CubicBezier/Basic.hs view
@@ -1,8 +1,10 @@-{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE BangPatterns, FlexibleInstances, MultiParamTypeClasses, DeriveFunctor, ViewPatterns #-} module Geom2D.CubicBezier.Basic- (CubicBezier (..), PathJoin (..), Path (..), AffineTransform (..), + (CubicBezier (..), QuadBezier (..), AnyBezier (..), GenericBezier(..),+ PathJoin (..), ClosedPath(..), OpenPath (..), AffineTransform (..), anyToCubic, anyToQuad,+ openPathCurves, closedPathCurves, curvesToOpen, curvesToClosed, bezierParam, bezierParamTolerance, reorient, bezierToBernstein,- evalBezier, evalBezierDeriv, evalBezierDerivs, findBezierTangent,+ evalBezierDerivs, evalBezier, evalBezierDeriv, findBezierTangent, quadToCubic, bezierHoriz, bezierVert, findBezierInflection, findBezierCusp, arcLength, arcLengthParam, splitBezier, bezierSubsegment, splitBezierN, colinear)@@ -11,84 +13,249 @@ import Geom2D.CubicBezier.Numeric import Math.BernsteinPoly import Numeric.Integration.TanhSinh+import qualified Data.Vector.Unboxed as V+import qualified Data.Vector.Unboxed.Mutable as MV -data CubicBezier = CubicBezier {- bezierC0 :: Point,- bezierC1 :: Point,- bezierC2 :: Point,- bezierC3 :: Point} deriving Show+-- | A cubic bezier curve.+data CubicBezier a = CubicBezier {+ cubicC0 :: !(Point a),+ cubicC1 :: !(Point a),+ cubicC2 :: !(Point a),+ cubicC3 :: !(Point a)}+ deriving (Eq, Show, Functor) -data PathJoin = JoinLine | JoinCurve Point Point- deriving Show-data Path = OpenPath [(Point, PathJoin)] Point- | ClosedPath [(Point, PathJoin)]- deriving Show+-- | A quadratic bezier curve.+data QuadBezier a = QuadBezier {+ quadC0 :: !(Point a),+ quadC1 :: !(Point a),+ quadC2 :: !(Point a)}+ deriving (Eq, Show, Functor) -instance AffineTransform CubicBezier where+-- Use a tuple, because it has 0(1) unzip when using unboxed vectors.+-- | A bezier curve of any degree.+data AnyBezier a = AnyBezier (V.Vector (a, a))+ +class GenericBezier b where+ degree :: (V.Unbox a) => b a -> Int+ toVector :: (V.Unbox a) => b a -> V.Vector (a, a)+ unsafeFromVector :: (V.Unbox a) => V.Vector (a, a) -> b a++instance GenericBezier CubicBezier where+ degree _ = 3+ toVector (CubicBezier (Point ax ay) (Point bx by)+ (Point cx cy) (Point dx dy)) =+ V.create $ do+ v <- MV.new 4+ MV.write v 0 (ax, ay)+ MV.write v 1 (bx, by)+ MV.write v 2 (cx, cy)+ MV.write v 3 (dx, dy)+ return v+ unsafeFromVector v = CubicBezier+ (uncurry Point $ v `V.unsafeIndex` 0)+ (uncurry Point $ v `V.unsafeIndex` 1)+ (uncurry Point $ v `V.unsafeIndex` 2)+ (uncurry Point $ v `V.unsafeIndex` 3)++instance GenericBezier QuadBezier where+ degree _ = 2+ toVector (QuadBezier (Point ax ay) (Point bx by)+ (Point cx cy)) =+ V.create $ do+ v <- MV.new 3+ MV.write v 0 (ax, ay)+ MV.write v 1 (bx, by)+ MV.write v 2 (cx, cy)+ return v+ unsafeFromVector v = QuadBezier+ (uncurry Point $ v `V.unsafeIndex` 0)+ (uncurry Point $ v `V.unsafeIndex` 1)+ (uncurry Point $ v `V.unsafeIndex` 2)++instance GenericBezier AnyBezier where+ degree (AnyBezier b) = V.length b+ toVector (AnyBezier v) = v+ unsafeFromVector = AnyBezier++data PathJoin a = JoinLine |+ JoinCurve (Point a) (Point a)+ deriving (Show, Functor)+data OpenPath a = OpenPath [(Point a, PathJoin a)] (Point a) + deriving (Show, Functor)+data ClosedPath a = ClosedPath [(Point a, PathJoin a)]+ deriving (Show, Functor)++instance (Num a) => AffineTransform (CubicBezier a) a where+ {-# SPECIALIZE transform :: Transform Double -> CubicBezier Double -> CubicBezier Double #-} transform t (CubicBezier c0 c1 c2 c3) = CubicBezier (transform t c0) (transform t c1) (transform t c2) (transform t c3) +-- | Return the open path as a list of curves.+openPathCurves :: Fractional a => OpenPath a -> [CubicBezier a]+openPathCurves (OpenPath curves p) = go curves p+ where+ go [] _ = []+ go [(p0, jn)] p = [makeCB p0 jn p]+ go ((p0, jn):rest@((p1,_):_)) p =+ makeCB p0 jn p1 : go rest p+ makeCB p0 (JoinLine) p1 =+ CubicBezier p0 (interpolateVector p0 p1 (1/3))+ (interpolateVector p0 p1 (2/3)) p1+ makeCB p0 (JoinCurve p1 p2) p3 =+ CubicBezier p0 p1 p2 p3 +-- | Return the closed path as a list of curves+closedPathCurves :: Fractional a => ClosedPath a -> [CubicBezier a]+closedPathCurves (ClosedPath []) = []+closedPathCurves (ClosedPath (cs@((p1, _):_))) =+ openPathCurves (OpenPath cs p1) +-- | Make an open path from a list of curves. The last control point+-- of each curve except the last is ignored.+curvesToOpen :: [CubicBezier a] -> OpenPath a+curvesToOpen [] = OpenPath [] undefined+curvesToOpen [CubicBezier p0 p1 p2 p3] =+ OpenPath [(p0, JoinCurve p1 p2)] p3+curvesToOpen (CubicBezier p0 p1 p2 _:cs) =+ OpenPath ((p0, JoinCurve p1 p2):rest) lastP+ where+ OpenPath rest lastP = curvesToOpen cs++-- | Make an open path from a list of curves. The last control point+-- of each curve is ignored.+curvesToClosed :: [CubicBezier a] -> ClosedPath a+curvesToClosed cs = ClosedPath cs2+ where+ OpenPath cs2 _ = curvesToOpen cs+++-- | safely convert from `AnyBezier' to `CubicBezier`+anyToCubic :: (V.Unbox a) => AnyBezier a -> Maybe (CubicBezier a)+anyToCubic b@(AnyBezier v)+ | degree b == 3 = Just $ unsafeFromVector v+ | otherwise = Nothing++-- | safely convert from `AnyBezier' to `QuadBezier`+anyToQuad :: (V.Unbox a) => AnyBezier a -> Maybe (QuadBezier a)+anyToQuad b@(AnyBezier v)+ | degree b == 2 = Just $ unsafeFromVector v+ | otherwise = Nothing++evalBezierDerivsCubic :: Fractional a =>+ CubicBezier a -> a -> [Point a]+evalBezierDerivsCubic (CubicBezier a b c d) t =+ [p, p', p'', p''', Point 0 0]+ where+ u = 1-t+ t2 = t*t+ t3 = t2*t+ da = 3*^(b^-^a)+ db = 3*^(c^-^b)+ dc = 3*^(d^-^c)+ p = u*^(u*^(u*^a ^+^ 3*t*^b) ^+^ 3*t2*^c) ^+^ t3*^d+ p' = u*^(u*^da ^+^ 2*t*^db) ^+^ t2*^dc+ p'' = 2*u*^(db^-^da) ^+^ 2*t*^(dc^-^db)+ p''' = 2*^(dc^-^2*^db^+^da)+{-# SPECIALIZE evalBezierDerivsCubic :: CubicBezier Double -> Double -> [DPoint] #-} ++evalBezierDerivsQuad :: Fractional a =>+ QuadBezier a -> a -> [Point a]+evalBezierDerivsQuad (QuadBezier a b c) t = [p, p', p'', Point 0 0]+ where+ u = 1-t+ t2 = t*t+ p = u*^(u*^a ^+^ 2*t*^b) ^+^ t2*^c+ p' = 2*^(u*^(b^-^a) ^+^ t*^(c^-^b))+ p'' = 2*^(c^-^ 2*^b ^+^ a)+{-# SPECIALIZE evalBezierDerivsQuad :: QuadBezier Double -> Double -> [DPoint] #-} ++-- | Evaluate the bezier and all its derivatives using the modified horner algorithm.+evalBezierDerivs :: (GenericBezier b, V.Unbox a, Fractional a) =>+ b a -> a -> [Point a]+evalBezierDerivs b t =+ zipWith Point (bernsteinEvalDerivs (BernsteinPoly x) t)+ (bernsteinEvalDerivs (BernsteinPoly y) t)+ where (x, y) = V.unzip $ toVector b+{-# SPECIALIZE evalBezierDerivs :: AnyBezier Double -> Double -> [DPoint] #-}+{-# NOINLINE [2] evalBezierDerivs #-}+{-# RULES "evalBezierDerivs/cubic" evalBezierDerivs = evalBezierDerivsCubic #-}+{-# RULES "evalBezierDerivs/quad" evalBezierDerivs = evalBezierDerivsQuad #-}+ -- | Return True if the param lies on the curve, iff it's in the interval @[0, 1]@.-bezierParam :: Double -> Bool+bezierParam :: (Ord a, Num a) => a -> Bool bezierParam t = t >= 0 && t <= 1 --- | Convert a tolerance from the codomain to the domain of the bezier curve.--- Should be good enough, but may not hold for high very tolerance values.---- The magnification of error from the domain to the codomain of the--- curve approaches the length of the tangent for small errors. We--- can use the maximum of the convex hull of the derivative, and double it to--- have some margin for larger values.-bezierParamTolerance :: CubicBezier -> Double -> Double-bezierParamTolerance (CubicBezier !p1 !p2 !p3 !p4) eps = eps / maxDist+-- | Convert a tolerance from the codomain to the domain of the bezier+-- curve, by dividing by the maximum velocity on the curve. The+-- estimate is conservative, but holds for any value on the curve.+bezierParamTolerance :: (GenericBezier b) => b Double -> Double -> Double+bezierParamTolerance (toVector -> v) eps = eps / maxVel where - maxDist = 6 * (max (vectorDistance p1 p2) $- max (vectorDistance p2 p3)- (vectorDistance p3 p4))+ maxVel = 3 * V.maximum (V.zipWith vectorDistance (V.map (uncurry Point) v)+ (V.map (uncurry Point) $ V.tail v)) -- | Reorient to the curve B(1-t).-reorient :: CubicBezier -> CubicBezier-reorient (CubicBezier p0 p1 p2 p3) = CubicBezier p3 p2 p1 p0 +reorient :: (GenericBezier b, V.Unbox a) => b a -> b a+reorient = unsafeFromVector . V.reverse . toVector+{-# SPECIALIZE reorient :: (V.Unbox a) => AnyBezier a -> AnyBezier a #-}+{-# NOINLINE [2] reorient #-} +reorientCubic :: CubicBezier a -> CubicBezier a+reorientCubic (CubicBezier a b c d) = CubicBezier d c b a++reorientQuad :: QuadBezier a -> QuadBezier a+reorientQuad (QuadBezier a b c) = QuadBezier c b a+{-# RULES "reorient/cubic" reorient = reorientCubic #-}+{-# RULES "reorient/quad" reorient = reorientQuad #-}+ -- | Give the bernstein polynomial for each coordinate.-bezierToBernstein :: CubicBezier -> (BernsteinPoly, BernsteinPoly)-bezierToBernstein (CubicBezier a b c d) = (listToBernstein $ map pointX coeffs,- listToBernstein $ map pointY coeffs)- where coeffs = [a, b, c, d]+bezierToBernstein :: (GenericBezier b, MV.Unbox a) =>+ b a -> (BernsteinPoly a, BernsteinPoly a)+bezierToBernstein b = (BernsteinPoly x, BernsteinPoly y)+ where (x, y) = V.unzip $ toVector b --- | Calculate a value on the curve.-evalBezier :: CubicBezier -> Double -> Point-evalBezier b = fst . evalBezierDeriv b +-- | Calculate a value on the bezier curve.+evalBezier :: (GenericBezier b, MV.Unbox a, Fractional a) =>+ b a -> a -> Point a+evalBezier bc t = head $ evalBezierDerivs bc t+{-# SPECIALIZE evalBezier :: AnyBezier Double -> Double -> DPoint #-}+{-# NOINLINE [2] evalBezier #-} --- | Calculate a value and the first derivative on the curve.-evalBezierDeriv :: CubicBezier -> Double -> (Point, Point)-evalBezierDeriv (CubicBezier !p0 !p1 !p2 !p3) t = (bt, bt')+evalBezierCubic :: Fractional a =>+ CubicBezier a -> a -> Point a+evalBezierCubic (CubicBezier a b c d) t =+ u*^(u*^(u*^a ^+^ 3*t*^b) ^+^ 3*t2*^c) ^+^ t3*^d where- b0' = 3*^(p1^-^p0)- b0'' = 2*^(3*^(p2^-^p1) ^-^ b0')- b0''' = 6*^(p3^-^ 2*^p2 ^+^ p1) ^-^ b0''- bt' = b0'^+^(b0''^+^ t*^b0'''^/2)^*t- bt = p0 ^+^ t*^(b0' ^+^ t*^(b0''^/2 ^+^ t*^(b0'''^/6)))+ u = 1-t+ t2 = t*t+ t3 = t2*t+{-# SPECIALIZE evalBezierCubic :: CubicBezier Double -> Double -> DPoint #-} --- | Calculate a value and all derivatives on the curve.-evalBezierDerivs :: CubicBezier -> Double -> [Point]-evalBezierDerivs (CubicBezier !p0 !p1 !p2 !p3) t = [bt, bt', bt'', b0''']+evalBezierQuad :: Fractional a =>+ QuadBezier a -> a -> Point a+evalBezierQuad (QuadBezier a b c) t = + u*^(u*^a ^+^ 2*t*^b) ^+^ t2*^c where- b0' = 3*^(p1^-^p0)- b0'' = 2*^(3*^(p2^-^p1) ^-^ b0')- b0''' = 6*^(p3^-^ 2*^p2 ^+^ p1) ^-^ b0''- bt'' = b0''^+^ b0'''^*t- bt' = b0'^+^(b0''^+^ t*^b0'''^/2)^*t- bt = p0 ^+^ t*^(b0' ^+^ t*^(b0''^/2 ^+^ t*^(b0'''^/6)))+ u = 1-t+ t2 = t*t+{-# SPECIALIZE evalBezierQuad :: QuadBezier Double -> Double -> DPoint #-} +{-# RULES "evalBezier/cubic" evalBezier = evalBezierCubic #-}+{-# RULES "evalBezier/quad" evalBezier = evalBezierQuad #-}++-- | Calculate a value and the first derivative on the curve.+evalBezierDeriv :: (V.Unbox a, Fractional a) =>+ GenericBezier b => b a -> a -> (Point a, Point a)+evalBezierDeriv bc t = (b,b')+ where+ (b:b':_) = evalBezierDerivs bc t+ -- | @findBezierTangent p b@ finds the parameters where -- the tangent of the bezier curve @b@ has the same direction as vector p. -- Use the formula tx * B'y(t) - ty * B'x(t) = 0 where -- B'x is the x value of the derivative of the Bezier curve.-findBezierTangent :: Point -> CubicBezier -> [Double]+findBezierTangent :: DPoint -> CubicBezier Double -> [Double] findBezierTangent (Point tx ty) (CubicBezier (Point x0 y0) (Point x1 y1) (Point x2 y2) (Point x3 y3)) = filter bezierParam $ quadraticRoot a b c where@@ -97,19 +264,18 @@ c = tx*(y1 - y0) - ty*(x1 - x0) -- | Find the parameter where the bezier curve is horizontal.-bezierHoriz :: CubicBezier -> [Double]+bezierHoriz :: CubicBezier Double -> [Double] bezierHoriz = findBezierTangent (Point 1 0) -- | Find the parameter where the bezier curve is vertical.-bezierVert :: CubicBezier -> [Double]+bezierVert :: CubicBezier Double -> [Double] bezierVert = findBezierTangent (Point 0 1) -- | Find inflection points on the curve.---- Use the formula B''x(t) * B'y(t) - B''y(t) * B'x(t) = 0--- with B'x(t) the x value of the first derivative at t,--- B''y(t) the y value of the second derivative at t-findBezierInflection :: CubicBezier -> [Double]+-- Use the formula B_x''(t) * B_y'(t) - B_y''(t) * B_x'(t) = 0 with+-- B_x'(t) the x value of the first derivative at t, B_y''(t) the y+-- value of the second derivative at t+findBezierInflection :: CubicBezier Double -> [Double] findBezierInflection (CubicBezier (Point x0 y0) (Point x1 y1) (Point x2 y2) (Point x3 y3)) = filter bezierParam $ quadraticRoot a b c where@@ -127,13 +293,13 @@ -- find a cusp. We look for points where the tangent is both horizontal -- and vertical, which is only true for the zero vector.-findBezierCusp :: CubicBezier -> [Double]+findBezierCusp :: CubicBezier Double -> [Double] findBezierCusp b = filter vertical $ bezierHoriz b where vertical = (== 0) . pointY . snd . evalBezierDeriv b -- | @arcLength c t tol finds the arclength of the bezier c at t, within given tolerance tol. -arcLength :: CubicBezier -> Double -> Double -> Double+arcLength :: CubicBezier Double -> Double -> Double -> Double arcLength b@(CubicBezier c0 c1 c2 c3) t eps = if eps / maximum [vectorDistance c0 c1, vectorDistance c1 c2,@@ -142,19 +308,19 @@ arcLengthEstimate (fst $ splitBezier b t) eps else arcLengthQuad b t eps -arcLengthQuad :: CubicBezier -> Double -> Double -> Double+arcLengthQuad :: CubicBezier Double -> Double -> Double -> Double arcLengthQuad b t eps = result $ absolute eps $ trap distDeriv 0 t where distDeriv t' = vectorMag $ snd $ evalD t'- evalD = evalBezierDeriv b + evalD = evalBezierDeriv b -outline :: CubicBezier -> Double+outline :: CubicBezier Double -> Double outline (CubicBezier c0 c1 c2 c3) = vectorDistance c0 c1 + vectorDistance c1 c2 + vectorDistance c2 c3 -arcLengthEstimate :: CubicBezier -> Double -> (Double, (Double, Double))+arcLengthEstimate :: CubicBezier Double -> Double -> (Double, (Double, Double)) arcLengthEstimate b eps = (arclen, (estimate, ol)) where estimate = (4*(olL+olR) - ol) / 3@@ -167,14 +333,14 @@ -- | arcLengthParam c len tol finds the parameter where the curve c has the arclength len, -- within tolerance tol.-arcLengthParam :: CubicBezier -> Double -> Double -> Double+arcLengthParam :: CubicBezier Double -> Double -> Double -> Double arcLengthParam b len eps = arcLengthP b len ol (len/ol) 1 eps where ol = outline b -- Use the Newton rootfinding method. Start with large tolerance -- values, and decrease tolerance as we go closer to the root.-arcLengthP :: CubicBezier -> Double -> Double ->+arcLengthP :: CubicBezier Double -> Double -> Double -> Double -> Double -> Double -> Double arcLengthP !b !len !tot !t !dt !eps | abs diff < eps = t - newDt@@ -182,9 +348,28 @@ where diff = arcLength b t (max (abs (dt*tot/50)) (eps/2)) - len newDt = diff / vectorMag (snd $ evalBezierDeriv b t) +-- | Convert a quadratic bezier to a cubic bezier.+quadToCubic :: (Fractional a) =>+ QuadBezier a -> CubicBezier a+quadToCubic (QuadBezier a b c) =+ CubicBezier a (1/3*^(a ^+^ 2*^b)) (1/3*^(2*^b ^+^ c)) c+ -- | Split a bezier curve into two curves.-splitBezier :: CubicBezier -> Double -> (CubicBezier, CubicBezier)-splitBezier (CubicBezier a b c d) t =+splitBezier :: (V.Unbox a, Fractional a) =>+ GenericBezier b => b a -> a -> (b a, b a)+splitBezier b t =+ (unsafeFromVector $ V.zip (bernsteinCoeffs x1) (bernsteinCoeffs y1),+ unsafeFromVector $ V.zip (bernsteinCoeffs x2) (bernsteinCoeffs y2))+ where+ (x, y) = bezierToBernstein b+ (x1, x2) = bernsteinSplit x t+ (y1, y2) = bernsteinSplit y t+{-# NOINLINE [2] splitBezier #-}+{-# SPECIALIZE splitBezier :: AnyBezier Double -> Double -> (AnyBezier Double, AnyBezier Double) #-}++-- | Split a bezier curve into two curves.+splitBezierCubic :: (Fractional a) => CubicBezier a -> a -> (CubicBezier a, CubicBezier a)+splitBezierCubic (CubicBezier a b c d) t = let ab = interpolateVector a b t bc = interpolateVector b c t cd = interpolateVector c d t@@ -192,18 +377,36 @@ bccd = interpolateVector bc cd t mid = interpolateVector abbc bccd t in (CubicBezier a ab abbc mid, CubicBezier mid bccd cd d)+{-# SPECIALIZE splitBezierCubic :: CubicBezier Double -> Double -> (CubicBezier Double, CubicBezier Double) #-} +-- | Split a bezier curve into two curves.+splitBezierQuad :: (Fractional a) => QuadBezier a -> a -> (QuadBezier a, QuadBezier a)+splitBezierQuad (QuadBezier a b c) t =+ let ab = interpolateVector a b t+ bc = interpolateVector b c t+ mid = interpolateVector ab bc t+ in (QuadBezier a ab mid, QuadBezier mid bc c)+{-# SPECIALIZE splitBezierQuad :: QuadBezier Double -> Double -> (QuadBezier Double, QuadBezier Double) #-}+{-# RULES "splitBezier/cubic" splitBezier = splitBezierCubic #-}+{-# RULES "splitBezier/quad" splitBezier = splitBezierQuad #-}++ -- | Return the subsegment between the two parameters.-bezierSubsegment :: CubicBezier -> Double -> Double -> CubicBezier+bezierSubsegment :: (Ord a, V.Unbox a, Fractional a) => GenericBezier b =>+ b a -> a -> a -> b a bezierSubsegment b t1 t2 | t1 > t2 = bezierSubsegment b t2 t1+ | t2 == 0 = fst $ splitBezier b t1 | otherwise = snd $ flip splitBezier (t1/t2) $ fst $ splitBezier b t2+{-# SPECIALIZE bezierSubsegment :: CubicBezier Double -> Double -> Double -> CubicBezier Double #-}+{-# SPECIALIZE bezierSubsegment :: QuadBezier Double -> Double -> Double -> QuadBezier Double #-} -- | Split a bezier curve into a list of beziers -- The parameters should be in ascending order or -- the result is unpredictable.-splitBezierN :: CubicBezier -> [Double] -> [CubicBezier]+splitBezierN :: (Ord a, V.Unbox a, Fractional a) =>+ GenericBezier b => b a -> [a] -> [b a] splitBezierN c [] = [c] splitBezierN c [t] = [a, b] where (a, b) = splitBezier c t@@ -211,11 +414,13 @@ bezierSubsegment c 0 t : bezierSubsegment c t u : tail (splitBezierN c $ u:rest)+{-# SPECIALIZE splitBezierN :: CubicBezier Double -> [Double] -> [CubicBezier Double] #-}+{-# SPECIALIZE splitBezierN :: QuadBezier Double -> [Double] -> [QuadBezier Double] #-} -- | Return False if some points fall outside a line with a thickness of the given tolerance. -- fat line calculation taken from the bezier-clipping algorithm (Sederberg)-colinear :: CubicBezier -> Double -> Bool+colinear :: CubicBezier Double -> Double -> Bool colinear (CubicBezier !a !b !c !d) eps = dmax - dmin < eps where ld = lineDistance (Line a d) d1 = ld b@@ -224,5 +429,3 @@ 3/4 * maximum [0, d1, d2]) | otherwise = (4/9 * minimum [0, d1, d2], 4/9 * maximum [0, d1, d2])--
Geom2D/CubicBezier/Curvature.hs view
@@ -8,14 +8,14 @@ -- | Curvature of the Bezier curve. A negative curvature means the curve -- curves to the right.-curvature :: CubicBezier -> Double -> Double+curvature :: CubicBezier Double -> Double -> Double curvature b t | t == 0 = curvature' b | t == 1 = negate $ curvature' $ reorient b | t < 0.5 = curvature' $ snd $ splitBezier b t | otherwise = negate $ curvature' $ reorient $ fst $ splitBezier b t -curvature' :: CubicBezier -> Double+curvature' :: CubicBezier Double -> Double curvature' (CubicBezier c0 c1 c2 _c3) = 2/3 * b/a^(3::Int) where a = vectorDistance c1 c0@@ -23,10 +23,10 @@ -- | Radius of curvature of the Bezier curve. This -- is the reciprocal of the curvature.-radiusOfCurvature :: CubicBezier -> Double -> Double+radiusOfCurvature :: CubicBezier Double -> Double -> Double radiusOfCurvature b t = 1 / curvature b t -extrema :: CubicBezier -> BernsteinPoly+extrema :: CubicBezier Double -> BernsteinPoly Double extrema bez = let (x, y) = bezierToBernstein bez x' = bernsteinDeriv x@@ -39,13 +39,13 @@ 3 *~ (x'~*y'' ~- y'~*x'') ~* (y'~*y'' ~+ x'~*x'') -- | Find extrema of the curvature, but not inflection points.-curvatureExtrema :: CubicBezier -> Double -> [Double]+curvatureExtrema :: CubicBezier Double -> Double -> [Double] curvatureExtrema b eps | colinear b eps = [] | otherwise = bezierFindRoot (extrema b) 0 1 $ bezierParamTolerance b eps -radiusSquareEq :: CubicBezier -> Double -> BernsteinPoly+radiusSquareEq :: CubicBezier Double -> Double -> BernsteinPoly Double radiusSquareEq bez d = let (x, y) = bezierToBernstein bez x' = bernsteinDeriv x@@ -58,7 +58,7 @@ -- | Find points on the curve that have a certain radius of curvature. -- Values to the left and to the right of the curve are returned.-findRadius :: CubicBezier -- ^ the curve+findRadius :: CubicBezier Double -- ^ the curve -> Double -- ^ distance -> Double -- ^ tolerance -> [Double] -- ^ result
Geom2D/CubicBezier/Intersection.hs view
@@ -7,25 +7,26 @@ import Geom2D.CubicBezier.Basic import Math.BernsteinPoly import Data.Maybe-+import qualified Data.Vector.Unboxed as V+import Debug.Trace -- find the convex hull by comparing the angles of the vectors with -- the cross product and backtracking if necessary.-findOuter' :: Bool -> Point -> Point -> [Point] -> Either [Point] [Point]+findOuter' :: Bool -> DPoint -> DPoint -> [DPoint] -> Either [DPoint] [DPoint] findOuter' !upper !dir !p1 l@(p2:rest) -- backtrack if the direction is outward | if upper then dir `vectorCross` (p2^-^p1) > 0 -- left turn- else dir `vectorCross` (p2^-^p1) < 0 = Left $! l+ else dir `vectorCross` (p2^-^p1) < 0 = Left l -- succeed | otherwise = case findOuter' upper (p2^-^p1) p2 rest of Left m -> findOuter' upper dir p1 m Right m -> Right (p1:m) -findOuter' _ _ p1 p = Right $! (p1:p)+findOuter' _ _ p1 p = Right (p1:p) -- find the outermost point. It doesn't look at the x values.-findOuter :: Bool -> [Point] -> [Point]+findOuter :: Bool -> [DPoint] -> [DPoint] findOuter upper (p1:p2:rest) = case findOuter' upper (p2^-^p1) p2 rest of Right l -> p1:l@@ -34,7 +35,7 @@ -- take the y values and turn it in into a convex hull with upper en -- lower points separated.-makeHull :: [Double] -> ([Point], [Point])+makeHull :: [Double] -> ([DPoint], [DPoint]) makeHull ds = let n = fromIntegral $ length ds - 1 points = zipWith Point [i/n | i <- [0..n]] ds@@ -43,34 +44,36 @@ -- test if the chords cross the fat line -- return the continuation if above the line-testBelow :: Double -> [Point] -> Maybe Double -> Maybe Double+testBelow :: Double -> [DPoint] -> Maybe Double -> Maybe Double testBelow _ [] _ = Nothing testBelow _ [_] _ = Nothing testBelow !dmin (p:q:rest) cont | pointY p >= dmin = cont | pointY p > pointY q = Nothing | pointY q < dmin = testBelow dmin (q:rest) cont- | otherwise = Just $! intersectPt dmin p q+ | otherwise = Just $ intersectPt dmin p q -testBetween :: Double -> Point -> Maybe Double -> Maybe Double+testBetween :: Double -> DPoint -> Maybe Double -> Maybe Double testBetween !dmax (Point !x !y) cont | y <= dmax = Just x | otherwise = cont -- test if the chords cross the line y=dmax somewhere-testAbove :: Double -> [Point] -> Maybe Double+testAbove :: Double -> [DPoint] -> Maybe Double testAbove _ [] = Nothing testAbove _ [_] = Nothing testAbove dmax (p:q:rest) | pointY p < pointY q = Nothing | pointY q > dmax = testAbove dmax (q:rest)- | otherwise = Just $! intersectPt dmax p q+ | otherwise = Just $ intersectPt dmax p q -- find the x value where the line through the two points -- intersect the line y=d-intersectPt :: Double -> Point -> Point -> Double-intersectPt d (Point x1 y1) (Point x2 y2) =- x1 + (d - y1) * (x2 - x1) / (y2 - y1)+intersectPt :: Double -> DPoint -> DPoint -> Double+intersectPt d (Point x1 y1) (Point x2 y2)+ | y1 == y2 = x1+ | otherwise =+ x1 + (d - y1) * (x2 - x1) / (y2 - y1) -- make a hull and test over which interval the -- curve is garuanteed to lie inside the fat line@@ -85,12 +88,11 @@ testAbove dmax (reverse lower) Just (left_t, right_t) -bezierClip :: CubicBezier -> CubicBezier -> Double -> Double+bezierClip :: CubicBezier Double -> CubicBezier Double -> Double -> Double -> Double -> Double -> Double -> Double -> Bool -> [(Double, Double)] bezierClip p@(CubicBezier !p0 !p1 !p2 !p3) q@(CubicBezier !q0 !q1 !q2 !q3) tmin tmax umin umax prevClip eps revCurves- -- no intersection | isNothing chop_interval = [] @@ -105,23 +107,36 @@ -- not enough reduction, so split the curve in case we have -- multiple intersections | prevClip > 0.8 && newClip > 0.8 =- if new_tmax - new_tmin > umax - umin -- split the longest segment- then let- (pl, pr) = splitBezier newP 0.5- half_t = new_tmin + (new_tmax - new_tmin) / 2- in bezierClip q pl umin umax new_tmin half_t newClip eps (not revCurves) ++- bezierClip q pr umin umax half_t new_tmax newClip eps (not revCurves)- else let- (ql, qr) = splitBezier q 0.5- half_t = umin + (umax - umin) / 2- in bezierClip ql newP umin half_t new_tmin new_tmax newClip eps (not revCurves) ++- bezierClip qr newP half_t umax new_tmin new_tmax newClip eps (not revCurves)-+ if | abs (dmax - dmin) < eps * vectorDistance p0 p3 ->+ -- fat line is smaller than tolerance.+ if revCurves+ then [(umin, tmin), (umax, tmax)]+ else [(tmin, umin), (umin, tmin)]+ | new_tmax - new_tmin > umax - umin ->+ -- split the longest segment+ let (pl, pr) = splitBezier newP 0.5+ half_t = new_tmin + (new_tmax - new_tmin) / 2+ in bezierClip q pl umin umax new_tmin half_t+ newClip eps (not revCurves) +++ bezierClip q pr umin umax half_t new_tmax+ newClip eps (not revCurves)+ | otherwise ->+ let (ql, qr) = splitBezier q 0.5+ half_t = umin + (umax - umin) / 2+ in bezierClip ql newP umin half_t+ new_tmin new_tmax newClip eps (not revCurves) +++ bezierClip qr newP half_t umax new_tmin new_tmax+ newClip eps (not revCurves) -- iterate with the curves reversed.- | otherwise = bezierClip q newP umin umax new_tmin new_tmax newClip eps (not revCurves)+ | otherwise =+ bezierClip q newP umin umax new_tmin+ new_tmax newClip eps (not revCurves) where- d = lineDistance (Line q0 q3)+ q3' | q0 == q3 =+ q0 ^+^ (rotate90L $* p3 ^-^ p0)+ | otherwise = q3+ d = lineDistance (Line q0 q3') d1 = d q1 d2 = d q2 (dmin, dmax) | d1*d2 > 0 = (3/4 * minimum [0, d1, d2],@@ -136,11 +151,19 @@ new_tmin = tmax * chop_tmin + tmin * (1 - chop_tmin) new_tmax = tmax * chop_tmax + tmin * (1 - chop_tmax) +maxEps = 1e-8+ -- | Find the intersections between two Bezier curves, using the -- Bezier Clip algorithm. Returns the parameters for both curves.-bezierIntersection :: CubicBezier -> CubicBezier -> Double -> [(Double, Double)]-bezierIntersection p q eps = bezierClip p q 0 1 0 1 0 eps False+bezierIntersection :: CubicBezier Double -> CubicBezier Double -> Double -> [(Double, Double)]+bezierIntersection p q eps = bezierClip p q 0 1 0 1 0 eps2 False+ where eps2 = max eps maxEps +-- TODO:+-- following curve generate very large list of intersections+-- let b1 = CubicBezier {cubicC0 = Point 365.70000000000005 477.40000000000003, cubicC1 = Point 373.3 476.70000000000005, cubicC2 = Point 381.1 476.3, cubicC3 = Point 389.20000000000005 476.3};+-- b2 = CubicBezier {cubicC0 = Point 365.70000000000005 477.40000000000003, cubicC1 = Point 365.70000000000005 476.6, cubicC2 = Point 365.70000000000005 475.8, cubicC3 = Point 365.70000000000005 475.0}+ ------------------------ Line intersection ------------------------------------- -- Clipping a line uses a simplified version of the Bezier Clip algorithm, -- and uses the (thin) line itself instead of the fat line.@@ -148,14 +171,14 @@ -- | Find the zero of a 1D bezier curve of any degree. Note that this -- can be used as a bernstein polynomial root solver by converting from -- the power basis to the bernstein basis.-bezierFindRoot :: BernsteinPoly -- ^ the bernstein coefficients of the polynomial+bezierFindRoot :: BernsteinPoly Double -- ^ the bernstein coefficients of the polynomial -> Double -- ^ The lower bound of the interval -> Double -- ^ The upper bound of the interval -> Double -- ^ The accuracy -> [Double] -- ^ The roots found bezierFindRoot p tmin tmax eps -- no intersection- | chop_interval == Nothing = []+ | isNothing chop_interval = [] -- not enough reduction, so split the curve in case we have -- multiple intersections@@ -174,7 +197,7 @@ bezierFindRoot newP new_tmin new_tmax eps where- chop_interval = chopHull 0 0 (bernsteinCoeffs p)+ chop_interval = chopHull 0 0 (V.toList $ bernsteinCoeffs p) Just (chop_tmin, chop_tmax) = chop_interval newP = bernsteinSubsegment p chop_tmin chop_tmax clip = chop_tmax - chop_tmin@@ -185,7 +208,7 @@ -- Apply a transformation to the bezier that maps the line onto the -- X-axis. Then we only need to test the Y-values for a zero.-bezierLineIntersections :: CubicBezier -> Line -> Double -> [Double]+bezierLineIntersections :: CubicBezier Double -> Line Double -> Double -> [Double] bezierLineIntersections b (Line p q) eps = bezierFindRoot (listToBernstein $ map pointY [p0, p1, p2, p3]) 0 1 $ bezierParamTolerance b eps@@ -193,7 +216,7 @@ fromJust (inverse $ translate p $* rotateVec (q ^-^ p)) $* b -- | Find the closest value(s) on the bezier to the given point, within tolerance.-closest :: CubicBezier -> Point -> Double -> [Double]+closest :: CubicBezier Double -> DPoint -> Double -> [Double] closest cb p@(Point px py) eps = map fst $ filter (\(_, d) -> abs (d - closestDist) < eps/2) $ zip tVals dists
Geom2D/CubicBezier/MetaPath.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE BangPatterns, DeriveFunctor #-} -- | This module implements an extension to paths as used in -- D.E.Knuth's /Metafont/. Metafont gives an alternate way -- to specify paths using bezier curves. I'll give a brief overview of@@ -54,40 +54,76 @@ -- as the first point. module Geom2D.CubicBezier.MetaPath- (unmeta, MetaPath (..), MetaJoin (..), MetaNodeType (..), Tension (..))+ (unmetaOpen, unmetaClosed, ClosedMetaPath(..), OpenMetaPath (..),+ MetaJoin (..), MetaNodeType (..), Tension (..)) where import Geom2D import Geom2D.CubicBezier.Basic import Data.List import Text.Printf--data MetaPath = OpenMetaPath [(Point, MetaJoin)] Point- | CyclicMetaPath [(Point, MetaJoin)]+import qualified Data.Vector as V -data MetaJoin = MetaJoin { metaTypeL :: MetaNodeType- , tensionL :: Tension- , tensionR :: Tension- , metaTypeR :: MetaNodeType- }- | Controls Point Point- deriving Show+data OpenMetaPath a = OpenMetaPath [(Point a, MetaJoin a)] (Point a)+ -- ^ A metapath with endpoints+data ClosedMetaPath a = ClosedMetaPath [(Point a, MetaJoin a)]+ -- ^ A metapath with cycles. The last join+ -- joins the last point with the first.+ deriving (Eq, Functor) -data MetaNodeType = Open+data MetaJoin a = MetaJoin { metaTypeL :: MetaNodeType a+ -- ^ The nodetype going out of the+ -- previous point. The metafont default is+ -- @Open@.+ , tensionL :: Tension+ -- ^ The tension going out of the previous point.+ -- The metafont default is 1.+ , tensionR :: Tension+ -- ^ The tension going into the next point.+ -- The metafont default is 1.+ , metaTypeR :: MetaNodeType a+ -- ^ The nodetype going into the next point.+ -- The metafont default is @Open@.+ }+ | Controls (Point a) (Point a)+ -- ^ Specify the control points explicitly.+ deriving (Show, Eq, Functor)+ +data MetaNodeType a = Open+ -- ^ An open node has no direction specified. If+ -- it is an internal node, the curve will keep the+ -- same direction going into and going out from+ -- the node. If it is an endpoint or corner+ -- point, it will have curl of 1. | Curl {curlgamma :: Double}- | Direction {nodedir :: Point}- deriving (Eq, Show)+ -- ^ The node becomes and endpoint or a corner+ -- point. The curl specifies how much the segment+ -- `curves`. A curl of `gamma` means that the+ -- curvature is `gamma` times that of the+ -- following node.+ | Direction {nodedir :: Point a}+ -- ^ The node has a given direction.+ deriving (Eq, Show, Functor) data Tension = Tension {tensionValue :: Double}+ -- ^ The tension value specifies how /tense/ the curve is.+ -- A higher value means the curve approaches a line+ -- segment, while a lower value means the curve is more+ -- round. Metafont doesn't allow values below 3/4. | TensionAtLeast {tensionValue :: Double}+ -- ^ Like @Tension@, but keep the segment inside the+ -- bounding triangle defined by the control points, if+ -- there is one. deriving (Eq, Show) -instance Show MetaPath where- show (CyclicMetaPath nodes) =+instance Show a => Show (ClosedMetaPath a) where+ show (ClosedMetaPath nodes) = showPath nodes ++ "cycle"++instance Show a => Show (OpenMetaPath a) where show (OpenMetaPath nodes lastpoint) = showPath nodes ++ showPoint lastpoint -showPath :: [(Point, MetaJoin)] -> String+showPath :: Show a => [(Point a, MetaJoin a)] -> String showPath = concatMap showNodes where showNodes (p, Controls u v) =@@ -106,15 +142,26 @@ typename (Curl g) = printf "{curl %.3f}" g :: String typename (Direction dir) = printf "{%s}" (showPoint dir) :: String -showPoint :: Point -> String-showPoint (Point x y) = printf "(%.3f, %.3f)" x y+showPoint :: Show a => Point a -> String+showPoint (Point x y) = "(" ++ show x ++ ", " ++ show y ++ ")" -- | Create a normal path from a metapath.-unmeta :: MetaPath -> Path-unmeta (OpenMetaPath nodes endpoint) =- unmetaOpen (flip sanitize endpoint $ removeEmptyDirs nodes) endpoint+unmetaOpen :: OpenMetaPath Double -> OpenPath Double+unmetaOpen (OpenMetaPath nodes endpoint) =+ unmetaOpen' (flip sanitize endpoint $+ removeEmptyDirs nodes)+ endpoint -unmeta (CyclicMetaPath nodes) =+unmetaOpen' :: [(DPoint, MetaJoin Double)] -> DPoint -> OpenPath Double+unmetaOpen' nodes endpoint =+ let subsegs = openSubSegments nodes endpoint+ path = joinSegments $ map unmetaSubSegment subsegs+ in OpenPath path endpoint++++unmetaClosed :: ClosedMetaPath Double -> ClosedPath Double+unmetaClosed (ClosedMetaPath nodes) = case spanList bothOpen (removeEmptyDirs nodes) of ([], []) -> error "empty metapath" (l, []) -> if fst (last l) == fst (head l)@@ -128,21 +175,15 @@ -- solve a cyclic metapath as an open path if possible. -- rotate to the defined node, and rotate back after -- solving the path.-unmetaAsOpen :: [(Point, MetaJoin)] -> [(Point, MetaJoin)] -> Path+unmetaAsOpen :: [(DPoint, MetaJoin Double)] -> [(DPoint, MetaJoin Double)] -> ClosedPath Double unmetaAsOpen l m = ClosedPath (l'++m') where n = length m OpenPath o _ =- unmetaOpen (sanitizeCycle (m++l)) (fst $ head (m ++l))+ unmetaOpen' (sanitizeCycle (m++l)) (fst $ head (m ++l)) (m',l') = splitAt n o -unmetaOpen :: [(Point, MetaJoin)] -> Point -> Path-unmetaOpen nodes endpoint =- let subsegs = openSubSegments nodes endpoint- path = joinSegments $ map unmetaSubSegment subsegs- in OpenPath path endpoint- -- decompose into a list of subsegments that need to be solved.-openSubSegments :: [(Point, MetaJoin)] -> Point -> [MetaPath]+openSubSegments :: [(DPoint, MetaJoin Double)] -> DPoint -> [OpenMetaPath Double] openSubSegments [] _ = [] openSubSegments l lastPoint = case spanList (not . breakPoint) l of@@ -155,13 +196,13 @@ _ -> error "openSubSegments': unexpected end of segments" -- join subsegments into one segment-joinSegments :: [Path] -> [(Point, PathJoin)]+joinSegments :: [OpenPath Double] -> [(DPoint, PathJoin Double)] joinSegments = concatMap nodes where nodes (OpenPath n _) = n- nodes (ClosedPath n) = n+ --nodes (ClosedPath n) = n -- solve a cyclic metapath where all angles depend on the each other.-unmetaCyclic :: [(Point, MetaJoin)] -> Path+unmetaCyclic :: [(DPoint, MetaJoin Double)] -> ClosedPath Double unmetaCyclic nodes = let points = map fst nodes chords = zipWith (^-^) (tail $ cycle points) points@@ -179,7 +220,7 @@ thetas phis tensionsA tensionsB -- solve a subsegment-unmetaSubSegment :: MetaPath -> Path+unmetaSubSegment :: OpenMetaPath Double -> OpenPath Double -- the simple case where the control points are already given. unmetaSubSegment (OpenMetaPath [(p, Controls u v)] q) =@@ -202,32 +243,30 @@ zipWith6 unmetaJoin points (tail points) thetas phis tensionsA tensionsB in OpenPath (zip points pathjoins) lastpoint -unmetaSubSegment _ = error "unmetaSubSegment: subsegment should not be cyclic"--removeEmptyDirs :: [(Point, MetaJoin)] -> [(Point, MetaJoin)]+removeEmptyDirs :: [(DPoint, MetaJoin Double)] -> [(DPoint, MetaJoin Double)] removeEmptyDirs = map remove where remove (p, MetaJoin (Direction (Point 0 0)) tl tr jr) = remove (p, MetaJoin Open tl tr jr) remove (p, MetaJoin jl tl tr (Direction (Point 0 0))) = (p, MetaJoin jl tl tr Open) remove j = j -- if p == q, it will become a control point-bothOpen :: [(Point, MetaJoin)] -> Bool+bothOpen :: [(DPoint, MetaJoin Double)] -> Bool bothOpen ((p, MetaJoin Open _ _ Open):(q, _):_) = p /= q bothOpen [(_, MetaJoin Open _ _ Open)] = True bothOpen _ = False -leftOpen :: [(Point, MetaJoin)] -> Bool+leftOpen :: [(DPoint, MetaJoin Double)] -> Bool leftOpen ((p, MetaJoin Open _ _ _):(q, _):_) = p /= q leftOpen [(_, MetaJoin Open _ _ _)] = True leftOpen _ = False -sanitizeCycle :: [(Point, MetaJoin)] -> [(Point, MetaJoin)]+sanitizeCycle :: [(DPoint, MetaJoin Double)] -> [(DPoint, MetaJoin Double)] sanitizeCycle [] = [] sanitizeCycle l = take n $ tail $ sanitize (drop (n-1) $ cycle l) (fst $ head l) where n = length l -sanitize :: [(Point, MetaJoin)] -> Point -> [(Point, MetaJoin)]+sanitize :: [(DPoint, MetaJoin Double)] -> DPoint -> [(DPoint, MetaJoin Double)] sanitize [] _ = [] -- ending open => curl@@ -284,7 +323,7 @@ | otherwise = ([],xs) -- break the subsegment if the angle to the left or the right is defined or a curl.-breakPoint :: [(Point, MetaJoin)] -> Bool+breakPoint :: [(DPoint, MetaJoin Double)] -> Bool breakPoint ((_, MetaJoin _ _ _ Open):(_, MetaJoin Open _ _ _):_) = False breakPoint _ = True @@ -298,28 +337,22 @@ -- see metafont the program: ¶ 283 solveTriDiagonal :: [(Double, Double, Double, Double)] -> [Double] solveTriDiagonal [] = error "solveTriDiagonal: not enough equations"-solveTriDiagonal ((_, b0, c0, d0): rows) = solutions+solveTriDiagonal ((_, b0, c0, d0): rows) =+ V.toList $ solveTriDiagonal2 (b0, c0, d0) (V.fromList rows)++solveTriDiagonal2 :: (Double, Double, Double) -> V.Vector (Double, Double, Double, Double) -> V.Vector Double+solveTriDiagonal2 (!b0, !c0, !d0) rows = solutions where- ((_, vn): twovars) =- reverse $ scanl nextrow (c0/b0, d0/b0) rows+ twovars = V.scanl nextrow (c0/b0, d0/b0) rows+ solutions = V.scanr nextsol vn (V.unsafeInit twovars)+ vn = snd $ V.unsafeLast twovars+ nextsol (u, v) ti = v - u*ti nextrow (u, v) (ai, bi, ci, di) = (ci/(bi - u*ai), (di - v*ai)/(bi - u*ai))- solutions = reverse $ scanl nextsol vn twovars- nextsol ti (u, v) = v - u*ti --- solveTriDiagonal2 :: (Double, Double, Double) -> V.Vector (Double, Double, Double, Double) -> V.Vector Double--- solveTriDiagonal2 (!b0, !c0, !d0) rows = solutions--- where--- solutions = undefined--- twovars = V.scanl nextrow (c0/b0, d0/b0) rows--- solutions = scanr V.unsafeInit--- nextrow (u, v) (ai, bi, ci, di) =--- (ci/(bi - u*ai), (di - v*ai)/(bi - u*ai))- -- test = ((80.0,58.0,51.0),[(-432.0,78.0,102.0,503.0),(71.0,-82.0,20.0,2130.0),(52.39,-10.43,4.0,56.0),(34.0,38.0,0.0,257.0)]) -- [-15.726940528143576,22.571642107784243,-78.93751365259996,-297.27313545829384,272.74438435742667] - -- solve the cyclic tridiagonal system. -- see metafont the program: ¶ 286 solveCyclicTriD :: [(Double, Double, Double, Double)] -> [Double]@@ -337,7 +370,7 @@ ((un, vn, wn) : reverse (tail threevars)) nextsol t (!u, !v, !w) = (v + w*t0 - t)/u -turnAngle :: Point -> Point -> Double+turnAngle :: DPoint -> DPoint -> Double turnAngle (Point 0 0) _ = 0 turnAngle (Point x y) q = vectorAngle $ rotateVec p $* q where p = Point x (-y)@@ -347,7 +380,7 @@ zipNext l = zip l (tail $ cycle l) -- find the equations for a cycle containing only open points-eqsCycle :: [Tension] -> [Point] -> [Tension]+eqsCycle :: [Tension] -> [DPoint] -> [Tension] -> [Double] -> [(Double, Double, Double, Double)] eqsCycle tensionsA points tensionsB turnAngles = zipWith4 eqTension@@ -361,7 +394,7 @@ -- find the equations for an path with open points. -- The first and last node should be a curl or a given angle -eqsOpen :: [Point] -> [MetaJoin] -> [Point] -> [Double]+eqsOpen :: [DPoint] -> [MetaJoin Double] -> [DPoint] -> [Double] -> [Double] -> [Double] -> [(Double, Double, Double, Double)] eqsOpen _ [MetaJoin mt1 t1 t2 mt2] [delta] _ _ _ = let replaceType Open = Curl 1@@ -433,7 +466,7 @@ chi = gamma*tensionA*tensionA / (tensionB*tensionB) -- getting the control points-unmetaJoin :: Point -> Point -> Double -> Double -> Tension -> Tension -> PathJoin+unmetaJoin :: DPoint -> DPoint -> Double -> Double -> Tension -> Tension -> PathJoin Double unmetaJoin !z0 !z1 !theta !phi !alpha !beta | abs phi < 1e-4 && abs theta < 1e-4 = JoinLine | otherwise = JoinCurve u v
Geom2D/CubicBezier/Numeric.hs view
@@ -1,6 +1,17 @@ -- | Some numerical computations used by the cubic bezier functions module Geom2D.CubicBezier.Numeric where+import Data.Vector.Unboxed as V+import Data.Vector.Unboxed.Mutable as MV+import Data.Matrix.Unboxed as M+import qualified Data.Matrix.Generic as G+import qualified Data.Matrix.Unboxed.Mutable as MM+import Control.Monad.ST+import Control.Monad ++sign x | x < 0 = -1+ | otherwise = 1+ -- | @quadraticRoot a b c@ find the real roots of the quadratic equation -- @a x^2 + b x + c = 0@. It will return one, two or zero roots. @@ -11,7 +22,7 @@ | otherwise = result where d = b*b - 4*a*c- q = - (b + signum b * sqrt d) / 2+ q = - (b + sign b * sqrt d) / 2 x1 = q/a x2 = c/q result | d < 0 = []@@ -24,9 +35,233 @@ -- >d x + e y + f = 0 -- -- Returns @Nothing@ if no solution is found.-solveLinear2x2 :: Double -> Double -> Double -> Double -> Double -> Double -> Maybe (Double, Double)+solveLinear2x2 :: (Eq a, Fractional a) => a -> a -> a -> a -> a -> a -> Maybe (a, a) solveLinear2x2 a b c d e f = case det of 0 -> Nothing _ -> Just ((c * e - b * f) / det, (a * f - c * d) / det) where det = d * b - a * e+{-# SPECIALIZE solveLinear2x2 :: Double -> Double -> Double -> Double -> Double -> Double -> Maybe (Double, Double) #-} +data SparseMatrix a =+ SparseMatrix (V.Vector Int)+ (V.Vector (Int, Int)) (M.Matrix a)+ +makeSparse :: Unbox a => Vector Int+ -- ^ The column index of the first element of each row.+ -- Should be ascending in order.+ -> M.Matrix a+ -- ^ The adjacent coefficients in each row+ -> SparseMatrix a+ -- ^ A sparse matrix.+makeSparse v m = SparseMatrix v (sparseRanges v vars width) m+ where+ width = cols m+ vars = V.last v + width++-- give the range of (possibly) nonzero coefficients for each column.+-- The column indices are those of the dense matrix of which the+-- sparse is a representation.+sparseRanges :: V.Vector Int -> Int -> Int -> V.Vector (Int, Int)+sparseRanges v vars width = ranges + where+ height = V.length v+ ranges = V.scanl' nextRange (nextRange (0,0) 0) $+ V.enumFromN 1 (vars-1)+ nextRange (s,e) i = (nextStart s i, nextEnd e i)+ nextStart s i+ | s >= height = height+ | v `V.unsafeIndex` s + width <= i =+ nextStart (s+1) i+ | otherwise = s+ nextEnd e i+ | e >= height = height+ | v `V.unsafeIndex` e > i = e+ | otherwise = nextEnd (e+1) i++-- | Given a rectangular matrix M, calculate the symmetric square+-- matrix MᵀM which can be used to find a least squares solution to+-- the overconstrained system.+lsqMatrix :: (Num a, Unbox a) =>+ SparseMatrix a+ -- ^ The input system.+ -> Matrix a+ -- ^ The resulting symmetric matrix as a sparse matrix.+ -- The first element of each row is the element on the+ -- diagonal.++lsqMatrix (SparseMatrix rowStart ranges m)+ | V.length rowStart /= height =+ error "lsqMatrix: lengths don't match."+ | otherwise = M.generate (vars, width) coeff+ where+ (height, width) = dim m+ vars = V.last rowStart + width+ overlap (s1,e1) (s2, e2) =+ (max s1 s2, min e1 e2)+ realIndex (r, c) =+ (r, c - rowStart `V.unsafeIndex` r)+ coeff (r,c) = let+ (s, e) | r+c >= vars = (0, 0)+ | otherwise =+ overlap (ranges `V.unsafeIndex` r) (ranges `V.unsafeIndex` (r+c))+ in V.foldl' (\acc i -> acc + m `M.unsafeIndex` realIndex (i, r) *+ m `M.unsafeIndex` realIndex (i, r+c)) 0 $+ V.enumFromN s (e-s)+{-# SPECIALIZE lsqMatrix :: SparseMatrix Double -> M.Matrix Double #-}++addMatrix :: (Num a, Unbox a) => M.Matrix a -> M.Matrix a -> M.Matrix a+addMatrix = M.zipWith (+)+{-# SPECIALIZE addMatrix :: M.Matrix Double -> M.Matrix Double -> M.Matrix Double #-}++addVec :: (Num a, Unbox a) => V.Vector a -> V.Vector a -> V.Vector a+addVec = V.zipWith (+)+{-# SPECIALIZE addVec :: V.Vector Double -> V.Vector Double -> V.Vector Double #-}++-- | Multiply the vector by the transpose of the sparse matrix.+sparseMulT :: (Num a, Unbox a) =>+ V.Vector a+ -> SparseMatrix a+ -> V.Vector a+sparseMulT v (SparseMatrix rowStart ranges m)+ | V.length v /= height =+ error "sparseMulT: lengths don't match."+ | otherwise = V.generate vars coeff+ where (height, width) = dim m+ vars | V.null rowStart = 0+ | otherwise = V.unsafeLast rowStart + width+ realIndex (r, c) =+ (r, c - rowStart `V.unsafeIndex` r)+ coeff i =+ let (s, e) = ranges `V.unsafeIndex` i+ in V.foldl' (\acc j ->+ acc + m `M.unsafeIndex` realIndex (j, i) *+ v `V.unsafeIndex` j) 0 $+ V.enumFromN s (e-s)+{-# SPECIALIZE sparseMulT :: V.Vector Double -> SparseMatrix Double -> V.Vector Double #-}++-- | Sparse matrix * vector multiplication.+sparseMul :: (Num a, Unbox a) =>+ SparseMatrix a+ -> V.Vector a+ -> V.Vector a+sparseMul (SparseMatrix rowStart _ranges m) v+ | V.length v /= vars =+ error "sparseMulT: lengths don't match."+ | otherwise = V.generate height coeff+ where (height, width) = dim m+ vars | V.null rowStart = 0+ | otherwise = V.unsafeLast rowStart + width+ coeff i = V.sum $ V.zipWith (*)+ (V.unsafeSlice (rowStart V.! i) width v)+ (G.unsafeTakeRow m i)+{-# SPECIALIZE sparseMul :: SparseMatrix Double -> V.Vector Double -> V.Vector Double #-}++-- | LDL* decomposition of the sparse hermitian matrix. The+-- first element of each row is the diagonal component of the D+-- matrix. The following elements are the elements next to the+-- diagonal in the L* matrix (the diagonal components in L* are 1).+-- For efficiency it mutates the matrix inplace.+decompLDL :: (Fractional a, Unbox a) => M.Matrix a -> M.Matrix a+decompLDL m = runST $ do+ m2 <- M.thaw m+ let (vars, width) = dim m+ V.forM_ (V.enumFromN 0 $ vars-1) $+ \startr -> do+ pivot <- MM.unsafeRead m2 (startr, 0)+ V.forM_ (V.enumFromN 1 $ width-1) $+ \c -> do+ el <- MM.unsafeRead m2 (startr, c)+ MM.unsafeWrite m2 (startr, c) (el/pivot)+ V.forM_ (V.enumFromN 0 $ min (width-1) $ vars-startr-1) $+ \r -> do+ r0 <- MM.unsafeRead m2 (startr, r+1)+ V.forM_ (V.enumFromN 0 (width-r-1)) $+ \c -> do r1 <- MM.unsafeRead m2 (startr, r+c+1)+ el <- MM.unsafeRead m2 (r+startr+1, c)+ MM.unsafeWrite m2 (r+startr+1, c)+ (el - r0*r1*pivot)+ M.unsafeFreeze m2+{-# SPECIALIZE decompLDL :: Matrix Double -> Matrix Double #-}++solveLDL :: (Fractional a, Unbox a) =>+ M.Matrix a -> V.Vector a -> V.Vector a+solveLDL m v+ | rows m /= V.length v = error "solveLDL: lengths don't match"+ | otherwise = runST $ do+ let (vars, width) = M.dim m+ sol1 <- MV.new vars+ -- forward substitution on the first (width) rows+ V.forM_ (V.enumFromN 0 $ min vars width) $+ \i -> do+ let vi = v `V.unsafeIndex` i+ s <- liftM (V.foldl' (-) vi) $+ V.forM (enumFromN 0 i) $+ \j -> liftM ((m `M.unsafeIndex` (j, i-j)) *)+ (MV.unsafeRead sol1 j)+ MV.unsafeWrite sol1 i s+ + -- forward substitution on the next (height-width) rows+ V.forM_ (V.enumFromN width $ vars - width) $+ \i -> do+ let vi = v `V.unsafeIndex` i+ s <- liftM (V.foldl' (-) vi) $+ V.forM (enumFromN 1 (width-1)) $+ \j -> liftM ((m `M.unsafeIndex` (i-j, j)) *)+ (MV.unsafeRead sol1 $ i-j)+ MV.unsafeWrite sol1 i s+ + -- backward substitution on the last (width) rows+ V.forM_ (V.enumFromN 0 $ min vars width) $+ \i -> do+ solI <- MV.unsafeRead sol1 (vars-i-1)+ let d = m `M.unsafeIndex` (vars-i-1, 0)+ s <- liftM (V.foldl' (-) (solI/d)) $+ V.forM (enumFromN 0 i) $+ \j -> liftM ((m `M.unsafeIndex` (vars-i-1, j+1)) *)+ (MV.unsafeRead sol1 $ vars-i+j)+ MV.unsafeWrite sol1 (vars-i-1) s+ + -- backward substitution on the prevous (vars-width) rows+ V.forM_ (V.enumFromN width $ vars - width) $+ \i -> do+ solI <- MV.unsafeRead sol1 (vars-i-1)+ let d = m `M.unsafeIndex` (vars-i-1, 0)+ s <- liftM (V.foldl' (-) (solI/d)) $+ V.forM (enumFromN 0 (width-1)) $+ \j -> liftM ((m `M.unsafeIndex` (vars-i-1, j+1)) *)+ (MV.unsafeRead sol1 $ vars-i+j)+ MV.unsafeWrite sol1 (vars-i-1) s+ + V.unsafeFreeze sol1+{-# SPECIALIZE solveLDL :: M.Matrix Double -> V.Vector Double -> V.Vector Double #-}+ +-- | @lsqSolve rowStart M y@ Find a least squares solution x to the+-- system xM = y.+lsqSolve :: (Fractional a, Unbox a) =>+ SparseMatrix a -- ^ sparse matrix+ -> V.Vector a -- ^ Right hand side vector.+ -> V.Vector a -- ^ Solution vector+lsqSolve m@(SparseMatrix _ _ m') v+ | rows m' /= V.length v = error "lsqSolve: lengths don't match"+ | otherwise = solveLDL m2 v2+ where+ v2 = sparseMulT v m+ m2 = decompLDL $ lsqMatrix m+{-# SPECIALIZE lsqSolve :: SparseMatrix Double -> V.Vector Double -> V.Vector Double #-}++-- | @lsqSolveDist rowStart M y@ Find a least squares solution of the distance between the points.+lsqSolveDist :: (Fractional a, Unbox a) =>+ SparseMatrix (a, a) -- ^ sparse matrix+ -> V.Vector (a, a) -- ^ Right hand side vector.+ -> V.Vector a -- ^ Solution vector+lsqSolveDist (SparseMatrix r s m') v+ | rows m' /= V.length v = error "lsqSolve: lengths don't match"+ | otherwise = solveLDL m3 v3+ where+ v3 = sparseMulT v1 m1 `addVec` sparseMulT v2 m2+ m3 = decompLDL $ lsqMatrix m1 `addMatrix` lsqMatrix m2+ (v1, v2) = V.unzip v+ (m1', m2') = M.unzip m'+ m1 = SparseMatrix r s m1'+ m2 = SparseMatrix r s m2'+{-# SPECIALIZE lsqSolveDist :: SparseMatrix (Double, Double) -> V.Vector (Double, Double) -> V.Vector Double #-}
Geom2D/CubicBezier/Outline.hs view
@@ -1,50 +1,31 @@ -- | Offsetting bezier curves and stroking curves. module Geom2D.CubicBezier.Outline- (bezierOffset, bezierOffsetMax)+ (bezierOffset) where import Geom2D import Geom2D.CubicBezier.Basic import Geom2D.CubicBezier.Approximate-import Geom2D.CubicBezier.Curvature -offsetPoint :: Double -> Point -> Point -> Point+offsetPoint :: (Floating a) => a -> Point a -> Point a -> Point a offsetPoint dist start tangent = start ^+^ (rotate90L $* dist *^ normVector tangent) -bezierOffsetPoint :: CubicBezier -> Double -> Double -> (Point, Point)+bezierOffsetPoint :: CubicBezier Double -> Double -> Double -> (DPoint, DPoint) bezierOffsetPoint cb dist t = (offsetPoint dist p p', p') where (p, p') = evalBezierDeriv cb t --- Approximate the bezier curve offset by dist. A positive value--- means to the left, a negative to the right.-offsetSegment :: Double -> Double -> CubicBezier -> [CubicBezier]-offsetSegment dist tol cb =- approximatePath (bezierOffsetPoint cb dist) 15 tol 0 1--offsetSegmentMax :: Int -> Double -> Double -> CubicBezier -> [CubicBezier]-offsetSegmentMax m dist tol cb =- approximatePathMax m (bezierOffsetPoint cb dist) 15 tol 0 1- -- | Calculate an offset path from the bezier curve to within -- tolerance. If the distance is positive offset to the left, -- otherwise to the right. A smaller tolerance may require more bezier -- curves in the path to approximate the offset curve-bezierOffset :: CubicBezier -- ^ The curve+bezierOffset :: CubicBezier Double -- ^ The curve -> Double -- ^ Offset distance.+ -> Maybe Int -- ^ maximum subcurves -> Double -- ^ Tolerance.- -> [CubicBezier] -- ^ The offset curve-bezierOffset cb dist tol =- --Path $ map BezierSegment $- concatMap (offsetSegment dist tol) $- splitBezierN cb $- findRadius cb dist tol+ -> [CubicBezier Double] -- ^ The offset curve+bezierOffset cb dist (Just m) tol =+ approximatePathMax m (bezierOffsetPoint cb dist) 15 tol 0 1 False --- | Like bezierOffset, but limit the number of subpaths for each--- smooth subsegment. The number should not be smaller than one.-bezierOffsetMax :: Int -> CubicBezier -> Double -> Double -> [CubicBezier]-bezierOffsetMax n cb dist tol =- -- Path $ map BezierSegment $- concatMap (offsetSegmentMax n dist tol) $- splitBezierN cb $- findRadius cb dist tol+bezierOffset cb dist Nothing tol =+ approximatePath (bezierOffsetPoint cb dist) 15 tol 0 1 False
+ Geom2D/CubicBezier/Overlap.lhs view
@@ -0,0 +1,980 @@+> {-# LANGUAGE MultiWayIf, PatternGuards, TemplateHaskell, BangPatterns #-}++Removing overlap from bezier paths in haskell+=============================================++This document describes an algorithm for removing overlap and+performing set operations on bezier paths, but at the same time it is+a working module for the haskell `cubicbezier` package. This way it+can serve two purposes at once: someone who wants to implement this+algorithm can use this as an explanation of the algorithm, while at+the same time it is a working version of the described algorithm.++**Note on porting**: Porting this code to another language should+present no difficulties. However some care must be taken with regards+to lazyness. Often many variables inside the `where` statement aren't+evaluated in all guards, so it's important to evaluate only those+which appear in the guards. The main state can be modified using+mutation instead of copying without any troubles. For modifying the+state, the `lens` library is used. The lens functions can be+interpreted in a mutable language as follows:++ * reading state:+ - `view field struct`: `struct.field`+ - `get`: `state` (implicit state, usually sweepstate)+ - `use field`: `state.field`++ * writing state:+ - `set field value struct`: `struct.field = value`+ - `field .= value`: `state.field = value` (typically sweepstate)++ * modifying state:+ - `over field fun struct`: `struct.field = fun (struct.field)`+ - `modify fun`: `state = fun state`+ - `field %= fun`: `state.field = fun (state.field)`++Let's begin with declaring the module and library imports:++> module Geom2D.CubicBezier.Overlap+> (boolPathOp, union, intersection, difference,+> exclusion, FillRule (..))+> where+> import Prelude hiding (mapM)+> import Geom2D+> import Geom2D.CubicBezier.Basic+> import Geom2D.CubicBezier.Intersection+> import Math.BernsteinPoly+> import Data.Traversable (mapM)+> import Data.Functor ((<$>))+> import Data.List (sortBy, sort)+> import Control.Monad.State hiding (mapM)+> import Lens.Micro+> import Lens.Micro.TH+> import Lens.Micro.Mtl+> import qualified Data.Map.Strict as M+> import qualified Data.Set as S++So what does it mean to remove overlap? Basicly we want to keep+curves where one side is inside the filled region, and the other side+is outside, and discard the rest.Since that could be true only of a+part of the curve, we also need to split each curve when it intersects+another curve. How do you know which side is the inside, and which+side the outside? There are two methods which are use the most: the+[*even-odd rule*](https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule)+and the [*nonzero rule*](https://en.wikipedia.org/wiki/Nonzero-rule).+Instead of hardwiring it, I use higher-order functions to determine+when a turnratio is inside the region to be filled, and how the+turnratio changes with each curve.++Checking each pair of curves for intersections would work, but is+rather inefficient. We only need to check for overlap when two curves+are adjacent. Fortunately there exist a good method from+*computational geometry*, called a *sweep line algorithm*. The basic+idea is to sweep a vertical line over the input, starting from+leftmost point to the right (of course the opposite direction is also+possible), and to update the input dynamically. We keep track of each+curve that intersects the sweepline by using a balanced tree of+curves. When adding a new curve, it's only necessary to check for+intersections with the curve above and below. Since searching on the+tree takes only `O(log n)` operations, this will save a lot of+computation.++The input is processed in horizontal order, and after splitting curves+the order must be preserved, so an ordered structure is needed. The+standard map library from `Data.Map` is ideal, and has all the+operations needed. This structure is called the *X-structure*,+since the data is ordered by X coordinate.:++> type XStruct = M.Map PointEvent [Curve]++Why `PointEvent`, and not just `Point`? We need to have a `Ord`+instance for the map, which much match our horizontal ordering. A+newtype is ideal, since it has no extra cost, and allows us to define+a Ord instance for defining the relative order. The value from the+map is a list, since there can be many curves starting from the same+point.++> newtype PointEvent = PointEvent DPoint+> deriving Show++When the x-coordinates are equal, I use the y-coordinate to determine+the order.++> instance Eq PointEvent where+> (PointEvent (Point x1 y1)) == (PointEvent (Point x2 y2)) =+> (x1, y1) == (x2, y2)+>+> instance Ord PointEvent where+> compare (PointEvent (Point x1 y1)) (PointEvent (Point x2 y2)) =+> compare (x1, y2) (x2, y1)++All curves are kept left to right, so we need to remember the+direction for the output:++The curves intersecting the sweepline are kept in another balanced+Tree, called the *Y-structure*. *These curves are not allowed to+overlap*, except in the endpoints, and will be ordered vertically.+I'll use the `Curve` datatype to define the ordering of the curves,+and to add additional information. The `turnRatio` field is the+turnRatio of the area to the left for a left to right curve, and to+the right for a right to left curve. The `changeTurn` function+determines how the turnRatio will change from up to down. This+together with a test for the *insideness* of a certain turnratio,+allows for more flexibility. Using this, it is possible to generalize+this algorithm to boolean operations!++The FillRule datatype is used for the exported API:++> data FillRule = EvenOdd | NonZero++> data Curve = Curve {+> _bezier :: !(CubicBezier Double),+> _turnRatio :: !(Int, Int),+> _changeTurn :: !((Int, Int) -> (Int, Int))}+>+> trOne :: (Int, Int)+> trOne = (0,0)+> +> makeLenses ''Curve+>+> instance Show Curve where+> show (Curve b a _) =+> "Curve " ++ show b ++ " " ++ show a+> +> type YStruct = S.Set Curve++The total state for the algorithm consists of the X-structure, the+Y-structure, and the output found so far. I use a trick to make+access to curves above and below the current pointevent more+convenient. I use two sets to represent a focus point into the+Y-structure, where the left set are the elements less than the+pointEvent (above), and the right set the elements greater (below):+++> data SweepState = SweepState {+> _output :: !(M.Map PointEvent [CubicBezier Double]),+> _yStructLeft :: !YStruct,+> _yStructRight :: !YStruct,+> _xStruct :: !XStruct}+> deriving Show+> +> makeLenses ''SweepState++Changing the focus point can be done efficiently in `O(log n)` by+mering and splitting again:++> changeFocus :: DPoint -> SweepState -> SweepState+> changeFocus p sweep =+> let (lStr, rStr) =+> S.split (Curve (CubicBezier p p p p) trOne id) $+> S.union (view yStructLeft sweep) (view yStructRight sweep)+> in set yStructLeft lStr $+> set yStructRight rStr+> sweep++This handy helper function will pass the first curve above to the+given function, and if it doesn't return `Nothing`, remove it from the+state. It does nothing when there is no curve above.++> withAbove :: (Curve -> Maybe a) -> State SweepState (Maybe a)+> withAbove f = do+> lStr <- use yStructLeft+> if S.null lStr+> then return Nothing+> else let (c, lStr') = S.deleteFindMax lStr+> in case f c of+> Nothing ->+> return Nothing+> Just x -> do+> yStructLeft .= lStr'+> return $ Just x++The same with the curve below.++> withBelow :: (Curve -> Maybe a) -> State SweepState (Maybe a)+> withBelow f = do+> rStr <- use yStructRight+> if S.null rStr+> then return Nothing+> else let (c, rStr') = S.deleteFindMin rStr+> in case f c of+> Nothing ->+> return Nothing+> Just x -> do+> yStructRight .= rStr'+> return $ Just x++`splitYStruct` changes the focus and returns and removes any curves which end in+the current pointEvent:++> splitYStruct :: DPoint -> State SweepState [Curve]+> splitYStruct p = do+> modify $ changeFocus p+> let go = do+> mbC <- withAbove $ \c ->+> -- remove and return c if it ends in point p+> +> guard (cubicC3 (_bezier c) == p) >> Just c+> case mbC of+> Just c ->+> (c:) <$> go+> Nothing -> return []+> go+++=== Some functions on the Sweep state:++Adding and removing curves from the X structure.++> insertX :: PointEvent -> [Curve] -> SweepState -> SweepState+> insertX p c =+> over xStruct $ M.insertWith (++) p c+>+> xStructAdd :: Curve -> SweepState -> SweepState+> xStructAdd c =+> insertX (PointEvent $ cubicC0 $+> view bezier c) [c]+>+> xStructRemove :: State SweepState (PointEvent, [Curve])+> xStructRemove = zoom xStruct $ state M.deleteFindMin++To compare curves vertically, take the the curve which starts the+rightmost, and see if it falls below or above the curve. If the first control points are coincident, test the+last control points instead. The curves in the Y-structure shouldn't+intersect (except in the endpoints), so these cases don't have to be+handled. To lookup a single point, I use a singular bezier curve.++> instance Eq Curve where+> Curve c1 t1 ct1 == Curve c2 t2 ct2 =+> c1 == c2 && t1 == t2 && ct1 (ct2 t1) == t1+> +> instance Ord Curve where+> compare (Curve c1@(CubicBezier p0 p1 p2 p3) tr1 _)+> (Curve c2@(CubicBezier q0 q1 q2 q3) tr2 _)+> | p0 == q0 = if+> | p3 == q3 ->+> -- compare the midpoint+> case (compVert (evalBezier c1 0.5) c2) of+> LT -> LT+> GT -> GT+> EQ ->+> -- otherwise arbitrary+> compare (tr1, PointEvent p1, PointEvent p2)+> (tr2, PointEvent q1, PointEvent q2)+> | pointX p3 < pointX q3 ->+> case (compVert p3 c2) of+> LT -> LT+> EQ -> LT+> GT -> GT+> | otherwise ->+> case compVert q3 c1 of+> LT -> GT+> EQ -> GT+> GT -> LT+> | pointX p0 < pointX q0 =+> case compVert q0 c1 of+> LT -> GT+> EQ -> LT+> GT -> LT+> | otherwise =+> case (compVert p0 c2) of+> LT -> LT+> EQ -> GT+> GT -> GT++Compare a point with a curve. See if it falls below or above the hull+first. Otherwise find the point on the curve with the same+X-coordinate by solving a cubic equation.++> compVert :: DPoint -> CubicBezier Double -> Ordering+> compVert p c+> | p == cubicC0 c ||+> p == cubicC3 c = EQ+> | compH /= EQ = compH+> | otherwise = comparePointCurve p c+> where+> compH = compareHull p c++=== Test if the point is above or below the curve {#comparePC}++> comparePointCurve :: Point Double -> CubicBezier Double -> Ordering+> comparePointCurve (Point x1 y1) c1@(CubicBezier p0 p1 p2 p3)+> | pointX p0 == x1 &&+> pointX p0 == pointX p1 &&+> pointX p0 == pointX p2 &&+> pointX p0 == pointX p3 =+> compare (pointY p0) y1+> | otherwise = compare y2 y1+> where+> t = findX x1 c1 $+> maximum (map maxp [p0, p1, p2, p3])*1e-12+> maxp (Point x y) = max (abs x) (abs y)+> y2 = pointY $ evalBezier c1 t++=== Comparing against the hull {#hull}++Compare a point against the convex hull of the bezier. `EQ` means the+point is inside the hull, `LT` below and `GT` above. I am currently+only testing against the control points, some testing needs to be done+to see what is faster.++> belowLine :: DPoint -> DPoint -> DPoint -> Bool+> belowLine (Point px py) (Point lx ly) (Point rx ry)+> | lx == rx = True+> | (px >= lx && px <= rx) ||+> (px <= lx && px >= rx) = py < midY+> | otherwise = True+> where midY = ly + (ry-ly) * (rx-lx) / (px-lx)+> +> aboveLine :: DPoint -> DPoint -> DPoint -> Bool+> aboveLine (Point px py) (Point lx ly) (Point rx ry)+> | lx == rx = True+> | (px >= lx && px <= rx) ||+> (px <= lx && px >= rx) = py > midY+> | otherwise = True+> where midY = ly + (ry-ly) * (rx-lx) / (px-lx)+> +> compareHull :: DPoint -> CubicBezier Double -> Ordering+> compareHull p (CubicBezier c0 c1 c2 c3)+> | pointY p > pointY c0 &&+> pointY p > pointY c1 &&+> pointY p > pointY c2 &&+> pointY p > pointY c3 = LT+> | pointY p < pointY c0 &&+> pointY p < pointY c1 &&+> pointY p < pointY c2 &&+> pointY p < pointY c3 = GT+> | otherwise = EQ++Preprocessing+-------------++Since the algorithm assumes curves are increasing in the horizontal+direction they have to be preprocessed first. I split each curve+where the tangent is vertical. If the resulting subsegment is too+small however, I just adjust the control point to make the curve+vertical at the endpoint.++I also do snaprounding to prevent points closer than the tolerance.++> makeXStruct :: ((Int, Int) -> (Int, Int)) -> ((Int, Int) -> (Int, Int)) -> Double -> [CubicBezier Double] -> XStruct+> makeXStruct chTr chTrBack tol =+> M.fromListWith (++) .+> concatMap (toCurve . snapRoundBezier tol) .+> concatMap (splitVert tol)+> where toCurve c@(CubicBezier p0 _ _ p3) =+> case compare (pointX p0) (pointX p3) of+> LT -> [(PointEvent p0, [Curve c trOne chTr])]+> GT -> [(PointEvent p3, [Curve (reorient c) trOne chTrBack]),+> (PointEvent p0, [])]+> -- vertical curve+> EQ | pointY p0 > pointY p3 ->+> [(PointEvent p0, [Curve c trOne chTr])]+> | otherwise ->+> [(PointEvent p3, [Curve (reorient c) trOne chTrBack]),+> (PointEvent p0, [])]+>+> splitVert :: Double -> CubicBezier Double -> [CubicBezier Double]+> splitVert tol curve@(CubicBezier c0 c1 c2 c3) =+> uncurry splitBezierN $+> adjustLast $+> adjustFirst (curve, vert)+> where vert+> | pointX c0 == pointX c1 &&+> pointX c0 == pointX c2 &&+> pointX c0 == pointX c3 = []+> | otherwise = +> sort $ bezierVert curve+> -- adjust control points to avoid small curve fragments+> -- near the endpoints+> adjustFirst (c@(CubicBezier p0 p1 p2 p3), t:ts)+> | vectorDistance p0 (evalBezier c t) < tol =+> (CubicBezier p0 (Point (pointX p0) (pointY p1)) p2 p3,+> ts)+> adjustFirst x = x+> adjustLast (c@(CubicBezier p0 p1 p2 p3), ts@(_:_))+> | vectorDistance p3 (evalBezier c $ last ts) < tol =+> (CubicBezier p0 p1 (Point (pointX p3) (pointY p2)) p3,+> init ts)+> adjustLast x = x++main loop+---------++For the main loop, we remove the leftmost point from the+X-structure, and do the following steps:++ 1. Split any curves which come near the current pointEvent.++ 2. Send all curves to the left of the sweepline to the output, after+ filtering them based on the turning number.++ 3. For each curve starting at the point, split if it intersects with+the curve above or the curve below. Sort resulting curves vertically.+If there are no curves starting from point, test the curves above and+below instead. Adjust the turnRatios for each curve.++ 4. Insert the points in the Y structure.++ 5. Loop until the X-structure is empty++> loopEvents :: ((Int, Int) -> Bool) -> Double -> SweepState -> SweepState+> loopEvents isInside tol sweep +> | M.null $ view xStruct sweep = sweep+> | otherwise =+> loopEvents isInside tol $!+> flip execState sweep $ do+> -- remove leftmost point from X structure+> (PointEvent p, curves) <- xStructRemove+> -- change focus, and remove curves ending at current+> -- pointevent from Y structure+> ending <- splitYStruct p+> -- split near curves+> (ending2, rightSubCurves) <- splitNearPoints p tol+> -- output curves to the left of the sweepline.+> modify $ filterOutput (ending ++ ending2) isInside +> let allCurves = rightSubCurves ++ curves+> if null allCurves+> -- split surrounding curves+> then splitSurround tol+> else do+> -- sort curves+> sorted <- splitAndOrder tol allCurves+> -- split curve above+> curves2 <- splitAbove sorted tol+> -- add curves to Y structure+> addMidCurves curves2 tol++Send curves to output+---------------------++> outputPaths :: (M.Map PointEvent [CubicBezier Double]) -> [ClosedPath Double]+> outputPaths m+> | M.null m = []+> | otherwise = outputNext m+> where+> lookupDelete p m =+> case M.lookup (PointEvent p) m of+> Nothing -> Nothing+> Just (x:xs) -> Just (x, m')+> where m' | null xs = M.delete (PointEvent p) m+> | otherwise = M.insert (PointEvent p) xs m+> _ -> error "outputPaths: empty list inside map."+> outputNext !m+> | M.null m = []+> | otherwise = +> let ((PointEvent p0, (c0:cs)), m0) =+> M.deleteFindMin m+> m0' | null cs = m0+> | otherwise = M.insert (PointEvent p0) cs m0+> in go m0' c0 [] p0+> go !m !next !prev !start+> | p == start =+> curvesToPath (reverse $ next:prev):+> outputNext m+> | otherwise =+> case lookupDelete p m of+> Nothing -> outputNext m+> Just (x, m') -> go m' x (next:prev) start+> where p = cubicC3 next+>+> curvesToPath :: [CubicBezier Double] -> ClosedPath Double+> curvesToPath =+> ClosedPath .+> map (\(CubicBezier p0 p1 p2 _) ->+> (p0, JoinCurve p1 p2))++Filter and output the given curves. The `isInside` function+determines the *insideness* of a give turnratio. For example for the+nonzero-rule, this would be `(> 0)`. This inserts the curve into the+output map.++> filterOutput :: [Curve] -> ((Int, Int) -> Bool) -> SweepState -> SweepState+> filterOutput curves isInside sweep =+> foldl (flip $ outputCurve isInside) sweep curves+>+> outputCurve :: ((Int, Int) -> Bool) -> Curve -> SweepState -> SweepState+> outputCurve isInside (Curve c tr op)+> | isInside (op tr) /= isInside tr =+> let c' | isInside tr = reorient c+> | otherwise = c+> in over output (M.insertWith (++) (PointEvent $ cubicC0 c') [c'])+> | otherwise = id++Test for intersections and split:+---------------------------------++Since the curves going out of the current pointEvent in the X-structure are+unordered, they need to be ordered first. First they are ordered by+first derivative. Since it's easier to compare two curves when they+don't overlap, remove overlap, and then sort again by comparing the+whole curve.++To do this, I implemented a monadic insertion sort. First the curves are split+in the statemonad, then they are compared.++> splitAndOrder :: Double -> [Curve] -> State SweepState [Curve]+> splitAndOrder tol curves =+> sortSplit tol $+> sortBy compDeriv curves+>+> compDeriv :: Curve -> Curve -> Ordering+> compDeriv (Curve (CubicBezier p0 p1 _ _) _ _)+> (Curve (CubicBezier q0 q1 _ _) _ _) =+> compare (vectorCross (p1^-^p0) (q1^-^ q0)) 0++Insertion sort, by splitting and comparing. This should be efficient+enough, since ordering by derivative should mostly order the curves.++> sortSplit :: Double -> [Curve] -> State SweepState [Curve]+> sortSplit _ [] = return []+> sortSplit tol (x:xs) =+> insertM x tol =<<+> sortSplit tol xs+>+> insertM :: Curve -> Double -> [Curve] -> State SweepState [Curve]+> insertM x _ [] = return [x]+> insertM x tol (y:ys) =+> case curveOverlap x y tol of+> Just (c1, c2) -> do+> mapM (modify . xStructAdd) c2+> insertM c1 tol ys+> Nothing -> do+> (x', y') <- splitM x y tol+> if x' < y'+> then return (x':y':ys)+> else (y':) <$> insertM x' tol ys+>+> splitM :: Curve -> Curve -> Double -> State SweepState (Curve, Curve)+> splitM x y tol =+> case splitMaybe x y tol of+> (Just (a, b), Just (c, d)) -> do+> modify $ insertX (PointEvent $ cubicC0 $ view bezier b) [b, d]+> return (a, c)+> (Nothing, Just (c, d)) -> do+> modify $ insertX (PointEvent $ cubicC0 $ view bezier d) [d]+> return (x, c)+> (Just (a, b), Nothing) -> do+> modify $ insertX (PointEvent $ cubicC0 $ view bezier b) [b]+> return (a, y)+> (Nothing, Nothing) ->+> return (x, y)++Handle intersections of the first curve at point and the curve+above. Return the curves with updated turnratios. Some care is needed+when one of the curves is intersected at the endpoints, in order not+to create singular curves.++> updateTurnRatio :: Curve -> Curve -> Curve+> updateTurnRatio (Curve _ tr chTr) =+> set turnRatio (chTr tr)+>+> propagateTurnRatio :: Curve -> [Curve] -> [Curve]+> propagateTurnRatio cAbove l =+> tail $ scanl updateTurnRatio cAbove l+>+> splitAbove :: [Curve] -> Double -> State SweepState [Curve]+> splitAbove [] _ = return []+> splitAbove (c:cs) tol = do+> lStr <- use yStructLeft+> if S.null lStr+> then let c' = set turnRatio trOne c+> in return $ c':propagateTurnRatio c' cs+> else do+> let (cAbove, lStr') = S.deleteFindMax lStr+> case splitMaybe c cAbove tol of+> (Nothing, Nothing) ->+> return $ propagateTurnRatio cAbove $ c:cs+> (Just (c1, c2), Nothing) ->+> if cubicC3 (_bezier c1) == cubicC0 (_bezier cAbove)+> then do+> modify $ xStructAdd cAbove . xStructAdd c2+> yStructLeft .= lStr'+> return $ propagateTurnRatio cAbove $ c1:cs+> else do+> modify $ xStructAdd c2+> return $ propagateTurnRatio cAbove $ c1:cs+> (Nothing, Just (c3, c4)) ->+> if cubicC3 (_bezier c3) == cubicC0 (_bezier c)+> then error "curve intersecting pointevent"+> else do+> modify $ xStructAdd c4+> yStructLeft .= S.insert c3 lStr'+> return $ propagateTurnRatio cAbove $ c:cs+> (Just (c1, c2), Just (c3, c4)) -> do+> modify $ xStructAdd c2 . xStructAdd c4+> yStructLeft .= S.insert c3 lStr'+> return $ propagateTurnRatio cAbove $ c1:cs++Split curves near the point. Return the curves starting from this point, and the index of the last split point++> splitNearPoints :: DPoint -> Double -> State SweepState ([Curve], [Curve])+> splitNearPoints p tol = do+> curves1 <- splitNearDir withAbove p tol+> curves2 <- splitNearDir withBelow p tol+> return (map fst curves1 ++ map fst curves2,+> map snd curves1 ++ map snd curves2)+>+> splitNearDir :: ((Curve -> Maybe (Curve, Double))+> -> State SweepState (Maybe (Curve, Double)))+> -> DPoint -> Double+> -> State SweepState [(Curve, Curve)]+> splitNearDir dir p tol = do+> mbSplit <- dir $ \curve ->+> (,) curve <$>+> pointOnCurve tol p+> (view bezier curve)+> case mbSplit of+> Nothing -> return []+> Just (curve, t) -> do+> let (c1, c2) = splitBezier (view bezier curve) t+> c1' = adjust curve $ adjustC3 p $+> snapRound tol <$> c1+> c2' = adjust curve $ adjustC0 p $+> snapRound tol <$> c2+> ((c1', c2'):) <$> splitNearDir dir p tol++Add the sorted curves starting at point to the Y-structure, and test+last curve with curve below.++> addMidCurves :: [Curve] -> Double -> State SweepState ()+> addMidCurves [] _ = return ()+> addMidCurves [c] tol =+> splitBelow c tol+> addMidCurves (c:cs) tol = do+> yStructLeft %= S.insert c +> addMidCurves cs tol+> +> splitBelow :: Curve -> Double -> State SweepState ()+> splitBelow c tol = do+> rStr <- use yStructRight+> let (cBelow, rStr') = S.deleteFindMin rStr+> if S.null rStr+> then yStructLeft %= S.insert c+> else+> case splitMaybe c cBelow tol of+> (Nothing, Nothing) ->+> yStructLeft %= S.insert c+> (Nothing, Just (c3, c4)) ->+> if cubicC3 (_bezier c3) == cubicC0 (_bezier c)+> then error "internal error: splitBelow: curve starting in future"+> else do+> modify $ xStructAdd c4+> yStructLeft %= S.insert c . S.insert c3+> yStructRight .= rStr'+> (Just (c1, c2), Nothing) ->+> if cubicC3 (_bezier c1) == cubicC0 (_bezier cBelow)+> then error "internal error: splitBelow: curve intersecting pointevent."+> else do+> modify $ xStructAdd c2+> yStructLeft %= S.insert c1+> (Just (c1, c2), Just (c3, c4)) -> do+> modify $ xStructAdd c2 . xStructAdd c4+> yStructLeft %= S.insert c1 . S.insert c3+> yStructRight .= rStr'++If no curves start from the point, we have to check if the surrounding+curves overlap.++> splitSurround :: Double -> State SweepState ()+> splitSurround tol = do+> lStr <- use yStructLeft+> rStr <- use yStructRight+> if S.null lStr || S.null rStr+> then return ()+> else do+> let (cAbove, lStr') = S.deleteFindMax lStr+> (cBelow, rStr') = S.deleteFindMin rStr+> case splitMaybe cAbove cBelow tol of+> (Just (c1, c2), Just (c3, c4)) -> do+> modify $ xStructAdd c2 .+> xStructAdd c4+> yStructLeft .= S.insert c1 lStr'+> yStructRight .= S.insert c3 rStr'+> (Just (c1, c2), Nothing) -> do+> modify $ xStructAdd c2+> yStructLeft .= S.insert c1 lStr'+> (Nothing, Just (c1, c2)) -> do+> modify $ xStructAdd c2+> yStructRight .= S.insert c1 rStr'+> (Nothing, Nothing) ->+> return ()+ ++=== Find curve intersections++Test if both curves intersect. Split one or both of the curves when+they intersect. Also snapround each point, and make sure the point+of overlap is the same in both curves.++> splitMaybe :: Curve -> Curve -> Double ->+> (Maybe (Curve, Curve),+> Maybe (Curve, Curve))+> splitMaybe c1 c2 tol =+> (adjustSplit c1 <$> fst n,+> adjustSplit c2 <$> snd n)+> where+> n = nextIntersection b1 b2 tol $+> bezierIntersection b1 b2 pTol+> pTol = min (bezierParamTolerance b1 tol)+> (bezierParamTolerance b2 tol)+> b1 = view bezier c1+> b2 = view bezier c2+>+> adjustSplit :: Curve -> (CubicBezier Double, CubicBezier Double) -> (Curve, Curve)+> adjustSplit curve (b1, b2) =+> (set bezier b1 curve,+> set bezier b2 curve)+>+> adjust :: Curve -> CubicBezier Double -> Curve+> adjust curve curve2 = set bezier curve2 curve+>+> snapRoundBezier :: Double -> CubicBezier Double -> CubicBezier Double+> snapRoundBezier tol = fmap (snapRound tol)+>++Given a list of intersection parameters, split at the next+intersection, but don't split at the first or last control point, or+when the two curves are (nearly) coincident. Note that list of+intersections is read lazily, in order not to evaluate more+intersections that necessary.++> nextIntersection :: CubicBezier Double -> CubicBezier Double -> Double -> [(Double, Double)]+> -> (Maybe (CubicBezier Double, CubicBezier Double),+> Maybe (CubicBezier Double, CubicBezier Double))+> nextIntersection _ _ _ [] = (Nothing, Nothing)+> nextIntersection b1@(CubicBezier p0 _ _ p3) b2@(CubicBezier q0 _ _ q3) tol ((t1, t2): ts)+> | atStart1 && atStart2 =+> nextIntersection b1 b2 tol ts+> | atStart1 =+> (Nothing,+> Just (adjustC3 p0 $ snapRoundBezier tol b2l,+> adjustC0 p0 $ snapRoundBezier tol b2r))+> | atStart2 =+> (Just (adjustC3 q0 $ snapRoundBezier tol b1l,+> adjustC0 q0 $ snapRoundBezier tol b1r),+> Nothing)+> | atEnd1 && atEnd2 = (Nothing, Nothing)+> | atEnd1 =+> (Nothing,+> Just (adjustC3 p3 $ snapRoundBezier tol b2l,+> adjustC0 p3 $ snapRoundBezier tol b2r))+> | atEnd2 =+> (Just (adjustC3 q3 $ snapRoundBezier tol b1l,+> adjustC0 q3 $ snapRoundBezier tol b1r),+> Nothing)+> | bezierEqual b1l b2l tol =+> nextIntersection b1 b2 tol ts+> | otherwise =+> let pMid = snapRound tol <$> cubicC3 b1l+> in (Just (snapRoundBezier tol b1l,+> snapRoundBezier tol b1r),+> Just (adjustC3 pMid $ snapRoundBezier tol b2l,+> adjustC0 pMid $ snapRoundBezier tol b2r))+> where+> x1 = evalBezier b1 t1+> x2 = evalBezier b2 t2+> atStart1 = vectorDistance (cubicC0 b1) x1 < tol+> atStart2 = vectorDistance (cubicC0 b2) x2 < tol+> atEnd1 = vectorDistance (cubicC3 b1) x1 < tol+> atEnd2 = vectorDistance (cubicC3 b2) x2 < tol+> (b1l, b1r) = splitBezier b1 t1+> (b2l, b2r) = splitBezier b2 t2+> +> adjustC0 :: Point a -> CubicBezier a -> CubicBezier a+> adjustC0 p (CubicBezier _ p1 p2 p3) = CubicBezier p p1 p2 p3+>+> adjustC3 :: Point a -> CubicBezier a -> CubicBezier a+> adjustC3 p (CubicBezier p0 p1 p2 _) = CubicBezier p0 p1 p2 p++=== Check if curves overlap.++If the curves overlap, combine the overlapping part into one curve.+To compare the curves, I first split the longest curve so that the+velocities in the first control point match, then compare those curves+for equality.++> curveOverlap :: Curve -> Curve -> Double+> -> Maybe (Curve, Maybe Curve)+> curveOverlap c1 c2 tol+> -- starting in the same point+> | p0 /= q0 = Nothing+> | colinear (view bezier c1) tol = if+> | not $ colinear (view bezier c2) tol ->+> Nothing+> | vectorDistance (p3^-^p0)+> ((q3^-^q0) ^* (d1/d2)) > tol ->+> Nothing+> | p3 == q3 -> +> Just (combineCurves c2 c1,+> Nothing)+> | d1 > d2 ->+> Just (combineCurves c2 c1,+> Just $ adjust c1 $+> CubicBezier q3+> (snapRound tol <$> interpolateVector q3 p3 (1/3))+> (snapRound tol <$> interpolateVector q3 p3 (2/3))+> p3)+> | otherwise ->+> Just (combineCurves c1 c2,+> Just $ adjust c2 $+> CubicBezier p3+> (snapRound tol <$> interpolateVector p3 q3 (1/3))+> (snapRound tol <$> interpolateVector p3 q3 (2/3))+> q3)+> -- equalize velocities, and compare +> | v1 == 0 ||+> v2 == 0 = Nothing+> | v1 > v2 = if bezierEqual b2 b1l tol+> then Just (combineCurves c2 c1,+> if checkEmpty b1r tol+> then Nothing+> else Just $ adjust c1 $+> adjustC0 (cubicC3 b2) $+> snapRoundBezier tol b1r)+> else Nothing+> +> | otherwise =+> if bezierEqual b1 b2l tol+> then Just (combineCurves c1 c2,+> if checkEmpty b2r tol+> then Nothing+> else Just $ adjust c2 $+> adjustC0 (cubicC3 b1) $+> snapRoundBezier tol b2r)+> else Nothing+> where+> (b1l, b1r) = splitBezier b1 (v2/v1)+> (b2l, b2r) = splitBezier b2 (v1/v2)+> b1@(CubicBezier p0 p1 _ p3) = view bezier c1+> b2@(CubicBezier q0 q1 _ q3) = view bezier c2+> d1 = vectorDistance p0 p3+> d2 = vectorDistance q0 q3+> v1 = vectorDistance p0 p1+> v2 = vectorDistance q0 q1+>+> checkEmpty :: CubicBezier Double -> Double -> Bool+> checkEmpty (CubicBezier p0 p1 p2 p3) tol = +> p0 == p3 &&+> vectorDistance p0 p1 < tol &&+> vectorDistance p0 p2 < tol++Curves can be combined if they are equal, just by composing their+changeTurn functions.++> combineCurves :: Curve -> Curve -> Curve+> combineCurves c1 c2 =+> over changeTurn (view changeTurn c2 .) c1++=== Snaprounding++> snapRound :: Double -> Double -> Double+> snapRound tol v =+> fromInteger (round (v/tol)) * tol++=== Test if the point is on the curve (within tolerance) {#oncurve}++> pointOnCurve :: Double -> DPoint -> CubicBezier Double -> Maybe Double+> pointOnCurve tol p c1+> | (t:_) <-+> closest c1 p tol,+> p2 <- evalBezier c1 t,+> vectorDistance p p2 < tol = Just t+> | otherwise = Nothing++=== Testing beziers for approximate equality {#eq}++If the control points of two bezier curves are within a distance `eps`+from each other, then both curves will all so be at least within+distance `eps` from each other. This can be proven easily:+subtracting both curves gives the distance curve. Since each control+point of this curve lies within a circle of radius `eps`, by the+convex hull property, the curve will also be inside the circle, so the+distances between each point will never exceed `eps`.++> bezierEqual :: CubicBezier Double -> CubicBezier Double -> Double -> Bool+> bezierEqual cb1@(CubicBezier a0 a1 a2 a3) cb2@(CubicBezier b0 b1 b2 b3) tol+> -- controlpoints equal within tol+> | vectorDistance a1 b1 < tol &&+> vectorDistance a2 b2 < tol &&+> vectorDistance a3 b3 < tol &&+> vectorDistance a0 b0 < tol = True+> -- compare if both are colinear and close together+> | dist < tol &&+> colinear cb1 ((tol-dist)/2) &&+> colinear cb2 ((tol-dist)/2) = True+> | otherwise = False+> where dist = max (abs $ ld b0) (abs $ ld b3)+> ld = lineDistance (Line a0 a3)++=== Finding the on curve point at the X coordinate {#findx}++Solve a cubic equation to find the X coordinate. This should be+converted to a closed form solver for efficiency.++> findX :: Double -> CubicBezier Double -> Double -> Double+> findX x c@(CubicBezier p0 p1 p2 p3) eps =+> head $ bezierFindRoot bez 0 1 $+> bezierParamTolerance c (eps/10)+> where bez = listToBernstein +> (map pointX [p0, p1, p2, p3]) ~-+> listToBernstein [x, x, x, x]++Higher level functions+----------------------++> fillFunction :: FillRule -> Int -> Bool+> fillFunction NonZero = (>0)+> fillFunction EvenOdd = odd++> -- | Union of paths, removing overlap and rounding to the given+> -- tolerance.+> union :: [ClosedPath Double] -- ^ Paths+> -> FillRule -- ^ input fillrule+> -> Double -- ^ Tolerance+> -> [ClosedPath Double]+> union p fill tol =+> outputPaths out+> where+> out = view output $+> loopEvents (fillFunction fill . fst) tol sweep+> sweep = SweepState M.empty S.empty S.empty xStr+> xStr = makeXStruct (over _1 $ subtract 1) (over _1 (+1)) tol $+> concatMap closedPathCurves p+>+> -- | combine paths using the given boolean operation +> boolPathOp :: (Bool -> Bool -> Bool) -- ^ operation+> -> [ClosedPath Double] -- ^ first path (merged with union)+> -> [ClosedPath Double] -- ^ second path (merged with union)+> -> FillRule -- ^ input fillrule+> -> Double -- ^ tolerance +> -> [ClosedPath Double]+> boolPathOp op p1 p2 fill tol =+> outputPaths $ view output $+> loopEvents isInside tol sweep+> where+> isInside (a, b) = fillFunction fill a `op`+> fillFunction fill b+> sweep = SweepState M.empty S.empty S.empty xStr+> xStr = M.unionWith (++)+> (makeXStruct +> (over _1 (subtract 1))+> (over _1 (+1)) tol $+> concatMap closedPathCurves p1)+> (makeXStruct+> (over _2 (subtract 1))+> (over _2 (+1)) tol $+> concatMap closedPathCurves p2)+>+> intersection, difference, exclusion ::+> [ClosedPath Double] -> [ClosedPath Double] ->+> FillRule -> Double -> [ClosedPath Double]+>+> -- | path intersection +> intersection = boolPathOp (&&)+>+> -- | path difference+> difference = boolPathOp (\a b -> a && not b)+>+> -- | path exclusion+> exclusion = boolPathOp (\a b -> if a then not b else b)
Math/BernsteinPoly.hs view
@@ -1,140 +1,221 @@-{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE BangPatterns, ViewPatterns #-}+-- | Algebra on polynomials in the Bernstein form. It is based on the+-- paper /Algebraic manipulation in the Bernstein form made simple via+-- convolutions/ by J. Sanchez-Reyes. It's an efficient+-- implementation using the scaled basis, and using ghc rewrite rules+-- to eliminate intermediate polynomials.+ module Math.BernsteinPoly- (BernsteinPoly(..), bernsteinSubsegment, listToBernstein, zeroPoly, (~*), (*~), (~+),- (~-), degreeElevate, bernsteinSplit, bernsteinEval,- bernsteinEvalDerivs, bernsteinDeriv)+ (BernsteinPoly(..), bernsteinSubsegment, listToBernstein, zeroPoly,+ (~*), (*~), (~+), (~-), degreeElevate, bernsteinSplit, bernsteinEval,+ bernsteinEvalDeriv, binCoeff, convolve, bernsteinEvalDerivs, bernsteinDeriv) where--import Data.List+import Data.Vector.Unboxed as V+import qualified Data.Vector as B -data BernsteinPoly = BernsteinPoly {- bernsteinDegree :: !Int,- bernsteinCoeffs :: ![Double] }+data BernsteinPoly a = BernsteinPoly {+ bernsteinCoeffs :: V.Vector a} deriving Show +data ScaledPoly a = ScaledPoly {+ scaledCoeffs :: V.Vector a }+ deriving Show infixl 7 ~*, *~ infixl 6 ~+, ~- +{-# RULES "toScaled/fromScaled" forall a. toScaled (fromScaled a) = a;+ "fromScaled/toScaled" forall a. fromScaled (toScaled a) = a; #-}++toScaled :: (Unbox a, Num a) => BernsteinPoly a -> ScaledPoly a+toScaled (BernsteinPoly v) =+ ScaledPoly $+ V.zipWith (*) v $ binCoeff $ V.length v - 1+{-# NOINLINE[2] toScaled #-}++fromScaled :: (Unbox a, Fractional a) => ScaledPoly a -> BernsteinPoly a+fromScaled (ScaledPoly v) =+ BernsteinPoly $+ V.zipWith (/) v $ binCoeff $ V.length v - 1+{-# NOINLINE[2] fromScaled #-}+ -- | Create a bernstein polynomail from a list of coëfficients.-listToBernstein :: [Double] -> BernsteinPoly+listToBernstein :: (Unbox a, Num a) => [a] -> BernsteinPoly a listToBernstein [] = zeroPoly-listToBernstein l = BernsteinPoly (length l - 1) l+listToBernstein l = BernsteinPoly $ V.fromList l+{-# INLINE listToBernstein #-} -- | The constant zero.-zeroPoly :: BernsteinPoly-zeroPoly = BernsteinPoly 0 [0]+zeroPoly :: (Num a, Unbox a) => BernsteinPoly a+zeroPoly = BernsteinPoly $ V.fromList [0]+{-# SPECIALIZE zeroPoly :: BernsteinPoly Double #-} -- | Return the subsegment between the two parameters.-bernsteinSubsegment :: BernsteinPoly -> Double -> Double -> BernsteinPoly+bernsteinSubsegment :: (Unbox a, Ord a, Fractional a) =>+ BernsteinPoly a -> a -> a -> BernsteinPoly a bernsteinSubsegment b t1 t2 | t1 > t2 = bernsteinSubsegment b t2 t1 | otherwise = snd $ flip bernsteinSplit (t1/t2) $ fst $ bernsteinSplit b t2+{-# INLINE bernsteinSubsegment #-} --- multiply two bezier curves--- control point i from the product of beziers P * Q--- is sum (P_j * Q_k) where j + k = i+1+-- | Calculate the convolution of two vectors.+convolve :: (Unbox a, Num a) => Vector a -> Vector a -> Vector a+convolve a b =+ V.map (\i -> V.sum $+ V.zipWith (*) a $+ V.reverse $+ V.unsafeTake i b)+ (V.enumFromN 1 $ V.length b)+ V.++ V.map (\i -> V.sum $+ V.zipWith (*)+ (V.unsafeDrop i a)+ (V.reverse b))+ (V.enumFromN 1 $ V.length a-1)+{-# SPECIALIZE convolve :: Vector Double -> Vector Double -> Vector Double #-} --- | Multiply two bernstein polynomials. The final degree--- will be the sum of either degrees. This operation takes O((n+m)^2)--- with n and m the degree of the beziers.+-- | Multiply two bernstein polynomials using convolution. The final+-- degree will be the sum of either degrees. This operation takes+-- O((n+m)^2) with n and m the degree of the beziers. Note that+-- convolution can be done in O(n log n) using the FFT, which may be+-- faster for large polynomials. -(~*) :: BernsteinPoly -> BernsteinPoly -> BernsteinPoly-(BernsteinPoly la a) ~* (BernsteinPoly lb b) =- BernsteinPoly (la+lb) $- zipWith (flip (/)) (binCoeff (la + lb)) $- init $ map sum $- zipWith (zipWith (*)) (repeat a') (down b') ++- zipWith (zipWith (*)) (tail $ tails a') (repeat $ reverse b')- where down l = tail $ scanl (flip (:)) [] l -- [[1], [2, 1], [3, 2, 1], ...- a' = zipWith (*) a (binCoeff la)- b' = zipWith (*) b (binCoeff lb)+(~*) :: (Unbox a, Fractional a) =>+ BernsteinPoly a -> BernsteinPoly a -> BernsteinPoly a+(toScaled -> a) ~* (toScaled -> b) =+ fromScaled $ mulScaled a b+{-# INLINE (~*) #-} +mulScaled :: (Unbox a, Num a) => ScaledPoly a -> ScaledPoly a -> ScaledPoly a+mulScaled (ScaledPoly a) (ScaledPoly b) =+ ScaledPoly $ convolve a b+{-# INLINE mulScaled #-} --- find the binomial coefficients of degree n.-binCoeff :: Int -> [Double]-binCoeff n = map fromIntegral $- scanl (\x m -> x * (n-m+1) `quot` m) 1 [1..n]+-- | Give the binomial coefficients of degree n.+binCoeff :: (Num a, Unbox a) => Int -> V.Vector a+binCoeff n = V.map fromIntegral $+ V.scanl (\x m -> x * (n-m+1) `quot` m)+ 1 (V.enumFromN 1 n)+{-# INLINE binCoeff #-} --- | Degree elevate a bernstein polynomail a number of times.-degreeElevate :: BernsteinPoly -> Int -> BernsteinPoly-degreeElevate b 0 = b-degreeElevate (BernsteinPoly lp p) times =- degreeElevate (BernsteinPoly (lp+1) (head p:inner p 1)) (times-1)- where- n = fromIntegral lp- inner [] _ = error "empty bernstein coefficients"- inner [a] _ = [a]- inner (a:b:rest) i =- (i*a/(n+1) + b*(1 - i/(n+1)))- : inner (b:rest) (i+1)+-- | Degree elevate a bernstein polynomial a number of times.+degreeElevateScaled :: (Unbox a, Num a)+ => ScaledPoly a -> Int -> ScaledPoly a+degreeElevateScaled b@(ScaledPoly p) times+ | times <= 0 = b+ | otherwise = ScaledPoly $ convolve (binCoeff times) p+{-# SPECIALIZE degreeElevateScaled :: ScaledPoly Double ->+ Int -> ScaledPoly Double #-} +degreeElevate :: (Unbox a, Fractional a)+ => BernsteinPoly a -> Int -> BernsteinPoly a+degreeElevate (toScaled -> b) times =+ fromScaled (degreeElevateScaled b times)+{-# INLINE degreeElevate #-} --- | Evaluate the bernstein polynomial.-bernsteinEval :: BernsteinPoly -> Double -> Double-bernsteinEval (BernsteinPoly _ []) _ = error "illegal bernstein polynomial"-bernsteinEval (BernsteinPoly _ [b]) _ = b-bernsteinEval (BernsteinPoly lp (b':bs)) t = go t n (b'*u) 2 bs+-- | Evaluate the bernstein polynomial using the horner rule adapted+-- for bernstein polynomials.++bernsteinEval :: (Unbox a, Fractional a)+ => BernsteinPoly a -> a -> a+bernsteinEval (BernsteinPoly v) _+ | V.length v == 0 = 0+bernsteinEval (BernsteinPoly v) _+ | V.length v == 1 = V.unsafeHead v+bernsteinEval (BernsteinPoly v) t =+ go t (fromIntegral n) (V.unsafeIndex v 0 * u) 1 where u = 1-t- n = fromIntegral lp- go !tn !bc !tmp _ [b] = tmp + tn*bc*b- go !tn !bc !tmp !i (b:rest) =- go (tn*t) -- tn- (bc*(n-i+1)/i) -- bc- ((tmp + tn*bc*b)*u) -- tmp- (i+1) -- i- rest- go _ _ _ _ [] = error "impossible"- + n = fromIntegral $ V.length v - 1+ go !tn !bc !tmp !i+ | i == n = tmp + tn*V.unsafeIndex v n+ | otherwise =+ go (tn*t) -- tn+ (bc*fromIntegral (n-i)/(fromIntegral i + 1)) -- bc+ ((tmp + tn*bc*V.unsafeIndex v i)*u) -- tmp+ (i+1) -- i+{-# SPECIALIZE bernsteinEval :: BernsteinPoly Double -> Double -> Double #-} + +-- | Evaluate the bernstein polynomial and first derivative+bernsteinEvalDeriv :: (Unbox t, Fractional t) => BernsteinPoly t -> t -> (t,t)+bernsteinEvalDeriv b@(BernsteinPoly v) t+ | V.length v <= 1 = (V.unsafeHead v, 0)+ | otherwise = (bernsteinEval b t, bernsteinEval (bernsteinDeriv b) t)+{-# INLINE bernsteinEvalDeriv #-} + -- | Evaluate the bernstein polynomial and its derivatives.-bernsteinEvalDerivs :: BernsteinPoly -> Double -> [Double]-bernsteinEvalDerivs b t- | bernsteinDegree b == 0 = [bernsteinEval b t]+bernsteinEvalDerivs :: (Unbox t, Fractional t) => BernsteinPoly t -> t -> [t]+bernsteinEvalDerivs b@(BernsteinPoly v) t+ | V.length v <= 1 = [V.unsafeHead v, 0] | otherwise = bernsteinEval b t : bernsteinEvalDerivs (bernsteinDeriv b) t+{-# INLINE bernsteinEvalDerivs #-} -- | Find the derivative of a bernstein polynomial.-bernsteinDeriv :: BernsteinPoly -> BernsteinPoly-bernsteinDeriv (BernsteinPoly 0 _) = zeroPoly-bernsteinDeriv (BernsteinPoly lp p) =- BernsteinPoly (lp-1) $- map (* fromIntegral lp) $ zipWith (-) (tail p) p+bernsteinDeriv :: (Unbox a, Num a) => BernsteinPoly a -> BernsteinPoly a+bernsteinDeriv (BernsteinPoly v)+ | V.length v == 0 = zeroPoly+bernsteinDeriv (BernsteinPoly v) =+ BernsteinPoly $+ V.map (* fromIntegral (V.length v - 1)) $+ V.zipWith (-) (V.tail v) v+{-# SPECIALIZE bernsteinDeriv :: BernsteinPoly Double ->+ BernsteinPoly Double #-} -- | Split a bernstein polynomial-bernsteinSplit :: BernsteinPoly -> Double -> (BernsteinPoly, BernsteinPoly)-bernsteinSplit (BernsteinPoly lp p) t =- (BernsteinPoly lp $ map head controls,- BernsteinPoly lp $ reverse $ map last controls)+bernsteinSplit :: (Unbox a, Num a) =>+ BernsteinPoly a -> a -> (BernsteinPoly a, BernsteinPoly a)+bernsteinSplit (BernsteinPoly v) t =+ (BernsteinPoly $ convert $+ B.map V.head interpVecs,+ BernsteinPoly $ V.reverse $ convert $+ B.map V.last $ convert interpVecs) where interp a b = (1-t)*a + t*b- terp [_] = []- terp l = let ctrs = zipWith interp l (tail l)- in ctrs : terp ctrs- controls = p:terp p+ interpVecs = B.iterateN (V.length v) interpVec v+ interpVec v2 = V.zipWith interp v2 (V.tail v2)+{-# SPECIALIZE bernsteinSplit :: BernsteinPoly Double -> Double ->+ (BernsteinPoly Double, BernsteinPoly Double) #-} --- | Sum two bernstein polynomials. The final degree will be the maximum of either--- degrees.-(~+) :: BernsteinPoly -> BernsteinPoly -> BernsteinPoly-ba@(BernsteinPoly la a) ~+ bb@(BernsteinPoly lb b)- | la < lb = BernsteinPoly lb $- zipWith (+) (bernsteinCoeffs $ degreeElevate ba $ lb-la) b- | la > lb = BernsteinPoly la $- zipWith (+) a (bernsteinCoeffs $ degreeElevate bb $ la-lb)- | otherwise = BernsteinPoly la $- zipWith (+) a b+addScaled :: (Unbox a, Num a) => ScaledPoly a -> ScaledPoly a -> ScaledPoly a+addScaled ba@(ScaledPoly a) bb@(ScaledPoly b)+ | la < lb = ScaledPoly $+ V.zipWith (+) (scaledCoeffs $ degreeElevateScaled ba $ lb-la) b+ | la > lb = ScaledPoly $+ V.zipWith (+) a (scaledCoeffs $ degreeElevateScaled bb $ la-lb)+ | otherwise = ScaledPoly $ V.zipWith (+) a b+ where la = V.length a+ lb = V.length b+{-# SPECIALIZE addScaled :: ScaledPoly Double -> ScaledPoly Double ->+ ScaledPoly Double #-} --- | Subtract two bernstein polynomials. The final degree will be the maximum of either--- degrees.-(~-) :: BernsteinPoly -> BernsteinPoly -> BernsteinPoly-ba@(BernsteinPoly la a) ~- bb@(BernsteinPoly lb b)- | la < lb = BernsteinPoly lb $- zipWith (-) (bernsteinCoeffs $ degreeElevate ba (lb-la)) b- | la > lb = BernsteinPoly la $- zipWith (-) a (bernsteinCoeffs $ degreeElevate bb (la-lb))- | otherwise = BernsteinPoly la $- zipWith (-) a b+-- | Sum two bernstein polynomials. The final degree will be the+-- maximum of either degrees.+(~+) :: (Unbox a, Fractional a) =>+ BernsteinPoly a -> BernsteinPoly a -> BernsteinPoly a+(toScaled -> a) ~+ (toScaled -> b) = fromScaled $ addScaled a b+{-# INLINE (~+) #-} +subScaled :: (Unbox a, Num a) => ScaledPoly a -> ScaledPoly a -> ScaledPoly a+subScaled ba@(ScaledPoly a) bb@(ScaledPoly b)+ | la < lb = ScaledPoly $+ V.zipWith (-) (scaledCoeffs $ degreeElevateScaled ba $ lb-la) b+ | la > lb = ScaledPoly $+ V.zipWith (-) a (scaledCoeffs $ degreeElevateScaled bb $ la-lb)+ | otherwise = ScaledPoly $ V.zipWith (-) a b+ where la = V.length a+ lb = V.length b+{-# SPECIALIZE subScaled :: ScaledPoly Double -> ScaledPoly Double ->+ ScaledPoly Double #-} ++-- | Subtract two bernstein polynomials. The final degree will be the+-- maximum of either degrees.+(~-) :: (Unbox a, Fractional a) =>+ BernsteinPoly a -> BernsteinPoly a -> BernsteinPoly a++(toScaled -> a) ~- (toScaled -> b) = fromScaled $ subScaled a b+{-# INLINE (~-) #-}+ -- | Scale a bernstein polynomial by a constant.-(*~) :: Double -> BernsteinPoly -> BernsteinPoly-a *~ (BernsteinPoly lb b) = BernsteinPoly lb (map (*a) b)+(*~) :: (Unbox a, Num a) => a -> BernsteinPoly a -> BernsteinPoly a+a *~ (BernsteinPoly v) = BernsteinPoly (V.map (*a) v)+{-# INLINE (*~) #-}
cubicbezier.cabal view
@@ -1,5 +1,5 @@ Name: cubicbezier-Version: 0.3.0+Version: 0.4.0.1 Synopsis: Efficient manipulating of 2D cubic bezier curves. Category: Graphics, Geometry, Typography Copyright: Kristof Bastiaensen (2014)@@ -11,8 +11,7 @@ Bug-Reports: https://github.com/kuribas/cubicbezier/issues Build-type: Simple Cabal-version: >=1.8-Description: This library supports efficient manipulating of 2D cubic bezier curves. The original goal- is to support typography, but it is useful for general graphics. Supported features are:+Description: This library supports efficient manipulating of 2D cubic bezier curves, for use in graphics or typography. Supported features are: . Evaluating bezier curves and derivatives, affine transformations on bezier curves, arclength and inverse arclength, intersections between two curves, intersection between a curve and a line, curvature and radius of curvature, finding tangents parallel to a vector, finding inflection points and cusps. .@@ -27,7 +26,8 @@ Library Ghc-options: -Wall- Build-depends: base >= 3 && < 5, containers > 0.4, integration >= 0.1.1+ Build-depends: base >= 3 && < 5, containers >= 0.5.3, integration >= 0.1.1, vector >= 0.10,+ matrices >= 0.4.1, microlens >= 0.1.2, microlens-th >= 0.1.2, microlens-mtl >= 0.1.2, mtl >= 2.1.1 Exposed-Modules: Geom2D Geom2D.CubicBezier@@ -35,10 +35,10 @@ Geom2D.CubicBezier.Approximate Geom2D.CubicBezier.Outline Geom2D.CubicBezier.Curvature+ Geom2D.CubicBezier.Overlap Geom2D.CubicBezier.Intersection Geom2D.CubicBezier.MetaPath Math.BernsteinPoly- Other-Modules: Geom2D.CubicBezier.Numeric test-suite test
tests/test.hs view
@@ -1,5 +1,3 @@-{-# Language ViewPatterns #-}- import Test.Tasty import Test.Tasty.HUnit import Geom2D.CubicBezier@@ -7,9 +5,11 @@ import Text.Parsec import Text.Parsec.String import Text.Parsec.Error+import MPTest+import NumTest tests :: TestTree-tests = testGroup "Tests" [unitTests]+tests = testGroup "Tests" [mfTests, numTests] num :: Parser Double num = @@ -186,8 +186,8 @@ -- These tests were created by running mf, typing expr after the -- prompt, and entering the metapaths.-unitTests :: TestTree-unitTests = testGroup "Metafont" [+mfTests :: TestTree+mfTests = testGroup "Metafont" [ testPath "(0,0)..(4,3)" "(0,0)..controls (1.33333,1) and (2.66667,2) ..(4,3)",