module Diagrams.TwoD.Path.Boolean
(
union, difference, intersection, exclusion,
union', difference', intersection', exclusion',
loopUnion, loopDifference,
loopIntersection, loopExclusion,)
where
import Control.Lens hiding (at)
import Data.Maybe
import Diagrams.Located
import Diagrams.Path
import Diagrams.Points
import Diagrams.Segment
import Diagrams.Trail
import Diagrams.TrailLike
import Diagrams.TwoD.Path
import qualified Geom2D.CubicBezier as C
import Linear
fillrule :: FillRule -> C.FillRule
fillrule Winding = C.NonZero
fillrule EvenOdd = C.EvenOdd
loop2path :: Located (Trail' Loop V2 Double) -> C.ClosedPath Double
loop2path t =
C.ClosedPath $ go x0 y0 (lineSegments $ cutLoop $ unLoc t)
where
(P (V2 x0 y0)) = loc t
go :: Double -> Double -> [Segment Closed V2 Double] -> [(C.DPoint, C.PathJoin Double)]
go _ _ [] = []
go x y (Linear (OffsetClosed (V2 x3 y3)):r) =
(C.Point x y, C.JoinLine) :
go (x+x3) (y+y3) r
go x y (Cubic (V2 x1 y1) (V2 x2 y2) (OffsetClosed (V2 x3 y3)):r) =
(C.Point x y, C.JoinCurve (C.Point (x+x1) (y+y1)) (C.Point (x+x2) (y+y2))) :
go (x+x3) (y+y3) r
path2loop :: C.ClosedPath Double -> Located (Trail' Loop V2 Double)
path2loop (C.ClosedPath []) = fromSegments [] `at` origin
path2loop (C.ClosedPath ((C.Point x0 y0, join):r)) =
fromSegments (go x0 y0 join r) `at` P (V2 x0 y0)
where go x y C.JoinLine [] =
[straight (V2 (x0x) (y0y))]
go x y C.JoinLine ((C.Point x2 y2, join'):r') =
straight (V2 (x2x) (y2y)):
go x2 y2 join' r'
go x y (C.JoinCurve (C.Point x1 y1) (C.Point x2 y2)) r' =
case r' of
[] -> [bezier3 (V2 (x1x) (y1y))
(V2 (x2x) (y2y)) (V2 (x0x) (y0y))]
((C.Point x3 y3, join'):r'') ->
bezier3 (V2 (x1x) (y1y)) (V2 (x2x) (y2y))
(V2 (x3x) (y3y)) :
go x3 y3 join' r''
trail2loop :: Located (Trail V2 Double) -> Maybe (Located (Trail' Loop V2 Double))
trail2loop = located (withTrail (const Nothing) Just)
offsetMax :: Offset c V2 Double -> Double
offsetMax (OffsetClosed (V2 m n)) = max (abs m) (abs n)
offsetMax OffsetOpen = 0
segmentMax :: Segment c V2 Double -> Double
segmentMax (Linear o) =
offsetMax o
segmentMax (Cubic (V2 a b) (V2 c d) o) =
maximum [offsetMax o, abs a, abs b,
abs c, abs d]
loopMax :: Trail' Loop V2 Double -> Double
loopMax l = maximum (segmentMax lastSeg: map segmentMax segs)
where (segs, lastSeg) = loopSegments l
defaultTol :: Double
defaultTol = 1e-7
loop2trail :: Located (Trail' Loop V2 Double) -> Located (Trail V2 Double)
loop2trail = over located wrapLoop
union :: FillRule -> Path V2 Double -> Path V2 Double
union fill p =
Path $ map loop2trail $
loopUnion tol fill loops
where loops = mapMaybe trail2loop $
pathTrails p
tol = maximum (map (loopMax.unLoc) loops) *
defaultTol
intersection :: FillRule -> Path V2 Double -> Path V2 Double -> Path V2 Double
intersection fill path1 path2 =
Path $ map loop2trail $
loopIntersection tol fill loops1 loops2
where loops1 = mapMaybe trail2loop $
pathTrails path1
loops2 = mapMaybe trail2loop $
pathTrails path2
tol = max (maximum (map (loopMax.unLoc) loops1))
(maximum (map (loopMax.unLoc) loops2))
* defaultTol
difference :: FillRule -> Path V2 Double -> Path V2 Double -> Path V2 Double
difference fill path1 path2 =
Path $ map loop2trail $
loopDifference tol fill loops1 loops2
where loops1 = mapMaybe trail2loop $
pathTrails path1
loops2 = mapMaybe trail2loop $
pathTrails path2
tol = max (maximum (map (loopMax.unLoc) loops1))
(maximum (map (loopMax.unLoc) loops2))
* defaultTol
exclusion :: FillRule -> Path V2 Double -> Path V2 Double -> Path V2 Double
exclusion fill path1 path2 =
Path $ map loop2trail $
loopExclusion tol fill loops1 loops2
where loops1 = mapMaybe trail2loop $
pathTrails path1
loops2 = mapMaybe trail2loop $
pathTrails path2
tol = max (maximum (map (loopMax.unLoc) loops1))
(maximum (map (loopMax.unLoc) loops2))
* defaultTol
union' :: Double -> FillRule -> Path V2 Double -> Path V2 Double
union' tol fill p =
Path $ map loop2trail $
loopUnion tol fill $
mapMaybe trail2loop $
pathTrails p
intersection' :: Double -> FillRule -> Path V2 Double -> Path V2 Double -> Path V2 Double
intersection' tol fill path1 path2 =
Path $ map loop2trail $
loopIntersection tol fill
(mapMaybe trail2loop $ pathTrails path1)
(mapMaybe trail2loop $ pathTrails path2)
difference' :: Double -> FillRule -> Path V2 Double -> Path V2 Double -> Path V2 Double
difference' tol fill path1 path2 =
Path $ map loop2trail $
loopDifference tol fill
(mapMaybe trail2loop $ pathTrails path1)
(mapMaybe trail2loop $ pathTrails path2)
exclusion' :: Double -> FillRule -> Path V2 Double -> Path V2 Double -> Path V2 Double
exclusion' tol fill path1 path2 =
Path $ map loop2trail $
loopExclusion tol fill
(mapMaybe trail2loop $ pathTrails path1)
(mapMaybe trail2loop $ pathTrails path2)
loopUnion :: Double -> FillRule
-> [Located (Trail' Loop V2 Double)]
-> [Located (Trail' Loop V2 Double)]
loopUnion tol fill p =
map path2loop $ C.union (map loop2path p) (fillrule fill) tol
loopDifference :: Double -> FillRule
-> [Located (Trail' Loop V2 Double)]
-> [Located (Trail' Loop V2 Double)]
-> [Located (Trail' Loop V2 Double)]
loopDifference tol fill path1 path2 =
map path2loop $ C.difference (map loop2path path1)
(map loop2path path2) (fillrule fill) tol
loopIntersection :: Double -> FillRule
-> [Located (Trail' Loop V2 Double)]
-> [Located (Trail' Loop V2 Double)]
-> [Located (Trail' Loop V2 Double)]
loopIntersection tol fill path1 path2 =
map path2loop $ C.intersection (map loop2path path1)
(map loop2path path2) (fillrule fill) tol
loopExclusion :: Double -> FillRule
-> [Located (Trail' Loop V2 Double)]
-> [Located (Trail' Loop V2 Double)]
-> [Located (Trail' Loop V2 Double)]
loopExclusion tol fill path1 path2 =
map path2loop $ C.exclusion (map loop2path path1)
(map loop2path path2) (fillrule fill) tol