{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Diagrams.TwoD.Path
(
stroke, stroke'
, strokePath, strokeP, strokePath', strokeP'
, strokeTrail, strokeT, strokeTrail', strokeT'
, strokeLine, strokeLoop
, strokeLocTrail, strokeLocT, strokeLocLine, strokeLocLoop
, FillRule(..)
, getFillRule, fillRule, _fillRule
, StrokeOpts(..), vertexNames, queryFillRule
, Crossings (..)
, isInsideWinding
, isInsideEvenOdd
, Clip(..), _Clip, _clip
, clipBy, clipTo, clipped
, intersectPoints, intersectPoints'
, intersectPointsP, intersectPointsP'
, intersectPointsT, intersectPointsT'
) where
import Control.Applicative (liftA2)
import Control.Lens hiding (at, transform)
import qualified Data.Foldable as F
import Data.Semigroup
import Data.Typeable
import Data.Default.Class
import Diagrams.Angle
import Diagrams.Combinators (withEnvelope, withTrace)
import Diagrams.Core
import Diagrams.Core.Trace
import Diagrams.Located (Located, mapLoc, unLoc)
import Diagrams.Parametric
import Diagrams.Path
import Diagrams.Query
import Diagrams.Segment
import Diagrams.Solve.Polynomial
import Diagrams.Trail
import Diagrams.TrailLike
import Diagrams.TwoD.Segment
import Diagrams.TwoD.Types
import Diagrams.TwoD.Vector
import Diagrams.Util (tau)
import Linear.Affine
import Linear.Vector
instance RealFloat n => Traced (Trail V2 n) where
getTrace = withLine $
foldr
(\seg bds -> moveOriginBy (negated . atEnd $ seg) bds <> getTrace seg)
mempty
. lineSegments
instance RealFloat n => Traced (Path V2 n) where
getTrace = F.foldMap getTrace . op Path
data FillRule
= Winding
| EvenOdd
deriving (Show, Typeable, Eq, Ord)
instance AttributeClass FillRule
instance Semigroup FillRule where
_ <> b = b
instance Default FillRule where
def = Winding
data StrokeOpts a
= StrokeOpts
{ _vertexNames :: [[a]]
, _queryFillRule :: FillRule
}
makeLensesWith (generateSignatures .~ False $ lensRules) ''StrokeOpts
vertexNames :: Lens (StrokeOpts a) (StrokeOpts a') [[a]] [[a']]
queryFillRule :: Lens' (StrokeOpts a) FillRule
instance Default (StrokeOpts a) where
def = StrokeOpts
{ _vertexNames = []
, _queryFillRule = def
}
stroke :: (InSpace V2 n t, ToPath t, TypeableFloat n, Renderable (Path V2 n) b)
=> t -> QDiagram b V2 n Any
stroke = strokeP . toPath
stroke' :: (InSpace V2 n t, ToPath t, TypeableFloat n, Renderable (Path V2 n) b, IsName a)
=> StrokeOpts a -> t -> QDiagram b V2 n Any
stroke' opts = strokeP' opts . toPath
strokeP :: (TypeableFloat n, Renderable (Path V2 n) b)
=> Path V2 n -> QDiagram b V2 n Any
strokeP = strokeP' (def :: StrokeOpts ())
strokePath :: (TypeableFloat n, Renderable (Path V2 n) b)
=> Path V2 n -> QDiagram b V2 n Any
strokePath = strokeP
instance (TypeableFloat n, Renderable (Path V2 n) b)
=> TrailLike (QDiagram b V2 n Any) where
trailLike = strokeP . trailLike
strokeP' :: (TypeableFloat n, Renderable (Path V2 n) b, IsName a)
=> StrokeOpts a -> Path V2 n -> QDiagram b V2 n Any
strokeP' opts path
| null (pLines ^. _Wrapped') = mkP pLoops
| null (pLoops ^. _Wrapped') = mkP pLines
| otherwise = mkP pLines <> mkP pLoops
where
(pLines,pLoops) = partitionPath (isLine . unLoc) path
mkP p
= mkQD (Prim p)
(getEnvelope p)
(getTrace p)
(fromNames . concat $
zipWith zip (opts^.vertexNames) ((map . map) subPoint (pathVertices p))
)
(Query $ Any . (runFillRule (opts^.queryFillRule)) p)
strokePath' :: (TypeableFloat n, Renderable (Path V2 n) b, IsName a)
=> StrokeOpts a -> Path V2 n -> QDiagram b V2 n Any
strokePath' = strokeP'
strokeTrail :: (TypeableFloat n, Renderable (Path V2 n) b)
=> Trail V2 n -> QDiagram b V2 n Any
strokeTrail = stroke . pathFromTrail
strokeT :: (TypeableFloat n, Renderable (Path V2 n) b)
=> Trail V2 n -> QDiagram b V2 n Any
strokeT = strokeTrail
strokeTrail' :: (TypeableFloat n, Renderable (Path V2 n) b, IsName a)
=> StrokeOpts a -> Trail V2 n -> QDiagram b V2 n Any
strokeTrail' opts = stroke' opts . pathFromTrail
strokeT' :: (TypeableFloat n, Renderable (Path V2 n) b, IsName a)
=> StrokeOpts a -> Trail V2 n -> QDiagram b V2 n Any
strokeT' = strokeTrail'
strokeLine :: (TypeableFloat n, Renderable (Path V2 n) b)
=> Trail' Line V2 n -> QDiagram b V2 n Any
strokeLine = strokeT . wrapLine
strokeLoop :: (TypeableFloat n, Renderable (Path V2 n) b)
=> Trail' Loop V2 n -> QDiagram b V2 n Any
strokeLoop = strokeT . wrapLoop
strokeLocTrail :: (TypeableFloat n, Renderable (Path V2 n) b)
=> Located (Trail V2 n) -> QDiagram b V2 n Any
strokeLocTrail = strokeP . trailLike
strokeLocT :: (TypeableFloat n, Renderable (Path V2 n) b)
=> Located (Trail V2 n) -> QDiagram b V2 n Any
strokeLocT = strokeLocTrail
strokeLocLine :: (TypeableFloat n, Renderable (Path V2 n) b)
=> Located (Trail' Line V2 n) -> QDiagram b V2 n Any
strokeLocLine = strokeP . trailLike . mapLoc wrapLine
strokeLocLoop :: (TypeableFloat n, Renderable (Path V2 n) b)
=> Located (Trail' Loop V2 n) -> QDiagram b V2 n Any
strokeLocLoop = strokeP . trailLike . mapLoc wrapLoop
runFillRule :: RealFloat n => FillRule -> Path V2 n -> Point V2 n -> Bool
runFillRule Winding = isInsideWinding
runFillRule EvenOdd = isInsideEvenOdd
getFillRule :: FillRule -> FillRule
getFillRule = id
fillRule :: HasStyle a => FillRule -> a -> a
fillRule = applyAttr
_fillRule :: Lens' (Style V2 n) FillRule
_fillRule = atAttr . non def
newtype Crossings = Crossings Int
deriving (Show, Eq, Ord, Num, Enum, Real, Integral)
instance Semigroup Crossings where
Crossings a <> Crossings b = Crossings (a + b)
instance Monoid Crossings where
mempty = Crossings 0
mappend = (<>)
instance RealFloat n => HasQuery (Located (Trail V2 n)) Crossings where
getQuery trail = Query $ \p -> trailCrossings p trail
instance RealFloat n => HasQuery (Located (Trail' l V2 n)) Crossings where
getQuery trail' = getQuery (mapLoc Trail trail')
instance RealFloat n => HasQuery (Path V2 n) Crossings where
getQuery = foldMapOf each getQuery
isInsideWinding :: HasQuery t Crossings => t -> Point (V t) (N t) -> Bool
isInsideWinding t = (/= 0) . sample t
isInsideEvenOdd :: HasQuery t Crossings => t -> Point (V t) (N t) -> Bool
isInsideEvenOdd t = odd . sample t
trailCrossings :: RealFloat n => Point V2 n -> Located (Trail V2 n) -> Crossings
trailCrossings _ t | not (isLoop (unLoc t)) = 0
trailCrossings p@(unp2 -> (x,y)) tr
= F.foldMap test $ fixTrail tr
where
test (FLinear a@(unp2 -> (_,ay)) b@(unp2 -> (_,by)))
| ay <= y && by > y && isLeft a b > 0 = 1
| by <= y && ay > y && isLeft a b < 0 = -1
| otherwise = 0
test c@(FCubic (P x1@(V2 _ x1y))
(P c1@(V2 _ c1y))
(P c2@(V2 _ c2y))
(P x2@(V2 _ x2y))
) =
sum . map testT $ ts
where ts = filter (liftA2 (&&) (>=0) (<=1))
$ cubForm (- x1y + 3*c1y - 3*c2y + x2y)
( 3*x1y - 6*c1y + 3*c2y)
(-3*x1y + 3*c1y)
(x1y - y)
testT t = let (unp2 -> (px,_)) = c `atParam` t
in if px > x then signFromDerivAt t else 0
signFromDerivAt t =
let v = (3*t*t) *^ ((-1)*^x1 ^+^ 3*^c1 ^-^ 3*^c2 ^+^ x2)
^+^ (2*t) *^ (3*^x1 ^-^ 6*^c1 ^+^ 3*^c2)
^+^ ((-3)*^x1 ^+^ 3*^c1)
ang = v ^. _theta . rad
in case () of _ | 0 < ang && ang < tau/2 && t < 1 -> 1
| -tau/2 < ang && ang < 0 && t > 0 -> -1
| otherwise -> 0
isLeft a b = cross2 (b .-. a) (p .-. a)
newtype Clip n = Clip [Path V2 n]
deriving (Typeable, Semigroup)
makeWrapped ''Clip
instance Typeable n => AttributeClass (Clip n)
instance AsEmpty (Clip n) where
_Empty = _Clip . _Empty
type instance V (Clip n) = V2
type instance N (Clip n) = n
instance (OrderedField n) => Transformable (Clip n) where
transform t (Clip ps) = Clip (transform t ps)
instance RealFloat n => HasQuery (Clip n) All where
getQuery (Clip paths) = Query $ \p ->
F.foldMap (All . flip isInsideWinding p) paths
_Clip :: Iso (Clip n) (Clip n') [Path V2 n] [Path V2 n']
_Clip = _Wrapped
_clip :: (Typeable n, OrderedField n) => Lens' (Style V2 n) [Path V2 n]
_clip = atTAttr . non' _Empty . _Clip
clipBy :: (HasStyle a, V a ~ V2, N a ~ n, TypeableFloat n) => Path V2 n -> a -> a
clipBy = applyTAttr . Clip . (:[])
clipTo :: TypeableFloat n
=> Path V2 n -> QDiagram b V2 n Any -> QDiagram b V2 n Any
clipTo p d = setTrace intersectionTrace . toEnvelope $ clipBy p d
where
envP = appEnvelope . getEnvelope $ p
envD = appEnvelope . getEnvelope $ d
toEnvelope = case (envP, envD) of
(Just eP, Just eD) -> setEnvelope . mkEnvelope $ \v -> min (eP v) (eD v)
(_, _) -> id
intersectionTrace = Trace traceIntersections
traceIntersections pt v =
onSortedList (filter pInside) (appTrace (getTrace d) pt v) <>
onSortedList (filter dInside) (appTrace (getTrace p) pt v) where
newPt dist = pt .+^ v ^* dist
pInside dDist = runFillRule Winding p (newPt dDist)
dInside pDist = getAny . sample d $ newPt pDist
clipped :: TypeableFloat n
=> Path V2 n -> QDiagram b V2 n Any -> QDiagram b V2 n Any
clipped p = withTrace p . withEnvelope p . clipBy p
intersectPoints :: (InSpace V2 n t, SameSpace t s, ToPath t, ToPath s, OrderedField n)
=> t -> s -> [P2 n]
intersectPoints = intersectPoints' 1e-8
intersectPoints' :: (InSpace V2 n t, SameSpace t s, ToPath t, ToPath s, OrderedField n)
=> n -> t -> s -> [P2 n]
intersectPoints' eps t s = intersectPointsP' eps (toPath t) (toPath s)
intersectPointsP :: OrderedField n => Path V2 n -> Path V2 n -> [P2 n]
intersectPointsP = intersectPointsP' 1e-8
intersectPointsP' :: OrderedField n => n -> Path V2 n -> Path V2 n -> [P2 n]
intersectPointsP' eps as bs = do
a <- pathTrails as
b <- pathTrails bs
intersectPointsT' eps a b
intersectPointsT :: OrderedField n => Located (Trail V2 n) -> Located (Trail V2 n) -> [P2 n]
intersectPointsT = intersectPointsT' 1e-8
intersectPointsT' :: OrderedField n => n -> Located (Trail V2 n) -> Located (Trail V2 n) -> [P2 n]
intersectPointsT' eps as bs = do
a <- fixTrail as
b <- fixTrail bs
intersectPointsS' eps a b