{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -fno-warn-name-shadowing #-} ----------------------------------------------------------------------------- -- | -- Module : Diagrams.TwoD.Path.IntersectionExtras -- Copyright : (c) 2018 Mike Zuser -- License : BSD-style (see LICENSE) -- Maintainer : Mike Zuser -- -- Extra functions for working with the intersections of `Path`s. This -- module was motivated by `explodeIntersections`. The rest of the module is -- either functions that where needed to build it or functions to help -- consume it. ----------------------------------------------------------------------------- module Diagrams.TwoD.Path.IntersectionExtras ( -- * Intersection Parameters intersectParams, intersectParams' , intersectParamsP, intersectParamsP' , intersectParamsT, intersectParamsT' , intersectParamsTS, intersectParamsTS' -- * Cutting Paths and Trails , cutBy, cutBy' , cutPBy, cutPBy' , cutTBy, cutTBy' -- * Rad Explosions , explodeSegments , explodeIntersections, explodeIntersections' , explodeBoth, explodeBoth' -- * Consuming Cut Paths , OnSections(..) ) where import Data.List import Diagrams.Prelude import Diagrams.TwoD.Segment -- defEps uses the value from Diagrams.TwoD.Path defEps :: Fractional n => n defEps = 1e-8 ----------------------------------------------------------------------------- -- Intersection Parameters -------------------------------------------------- ----------------------------------------------------------------------------- -- | Find the intersect parameters for each component trail of two pathlike -- objects when the objects are intersected, returning a seperate list for -- each trail. intersectParams :: (InSpace V2 n t, SameSpace t s, ToPath t, ToPath s, OrderedField n) => t -> s -> ([[n]], [[n]]) intersectParams = intersectParams' defEps -- | `intersectParams` using the given tolerance. intersectParams' :: (InSpace V2 n t, SameSpace t s, ToPath t, ToPath s, OrderedField n) => n -> t -> s -> ([[n]], [[n]]) intersectParams' eps as bs = intersectParamsP' eps (toPath as) (toPath bs) -- | Find the intersect parameters for each component trail of two -- paths when the paths are intersected, returning a seperate list for -- each trail. intersectParamsP :: OrderedField n => Path V2 n -> Path V2 n -> ([[n]], [[n]]) intersectParamsP = intersectParamsP' defEps -- | `intersectParamsP` using the given tolerance. intersectParamsP' :: OrderedField n => n -> Path V2 n -> Path V2 n -> ([[n]], [[n]]) intersectParamsP' eps as bs = (ps, qs) where is = map (flip map (pathTrails bs) . intersectParamsT' eps) (pathTrails as) ps = map (concat . map fst) is qs = map (concat . map snd) (transpose is) -- | Find the intersect parameters between two located trails. intersectParamsT :: OrderedField n => Located (Trail V2 n) -> Located (Trail V2 n) -> ([n], [n]) intersectParamsT = intersectParamsT' defEps -- | `intersectParamsT` using the given tolerance. intersectParamsT' :: OrderedField n => n -> Located (Trail V2 n) -> Located (Trail V2 n) -> ([n], [n]) intersectParamsT' eps as bs = (reparam ps, reparam qs) where (ps, qs) = intersectParamsTS' eps as bs reparam segs = concat $ zipWith f [(0::Int)..] segs where f segNo = map $ \p -> (fromIntegral segNo + p) / genericLength segs -- | Find the intersect parameters for each component segment of two -- located trails when the trails are intersected, returning a -- list for each trail containing a list of intersections for -- each segemnt of that trail. intersectParamsTS :: OrderedField n => Located (Trail V2 n) -> Located (Trail V2 n) -> ([[n]], [[n]]) intersectParamsTS = intersectParamsTS' defEps -- | `intersectParamsTS` using the given tolerance. intersectParamsTS' :: OrderedField n => n -> Located (Trail V2 n) -> Located (Trail V2 n) -> ([[n]], [[n]]) intersectParamsTS' eps as bs = (ps, qs) where (as', bs') = (as, bs) & both %~ (zip [0..] . fixTrail) is = map (flip map bs' . isect) as' isect (i, a) (j, b) | a == b = [] | otherwise = filter (not . ends) . map (\(p, q, _) -> (p, q)) $ segmentSegment eps a b where ends (p, q) = adjacent && min p q `near` 0 && max p q `near` 1 adjacent = as == bs && (abs (i - j) == 1 || min i j == 0 && max i j == length as' - 1) near x n = abs (x - n) < eps ps = map (map fst . concat) is qs = map (map snd . concat) (transpose is) ----------------------------------------------------------------------------- -- Cutting Paths and Trails ------------------------------------------------- ----------------------------------------------------------------------------- -- | Seperate a pathlike object into sections at every point it intersects -- a second pathlike object, returning a list of sections for each component -- trail. -- -- <> -- -- > cutByEx = onSections (squares `cutBy` line) colorLines -- > <> stroke line -- > where -- > squares, line :: Path V2 Double -- > squares = square 1 -- > <> square 1 # rotate (1/8 @@ turn) -- > line = hrule 2 -- > colorLines = map (map lc) -- > [ [ red, orange] -- > , [blue, purple] ] cutBy :: (OrderedField n, Real n, InSpace V2 n t, SameSpace t s, ToPath t, ToPath s) => t -> s -> [[Located (Trail V2 n)]] cutBy = cutBy' defEps -- | `cutBy` using the given tolerance for calculating intersections. cutBy' :: (OrderedField n, Real n, InSpace V2 n t, SameSpace t s, ToPath t, ToPath s) => n -> t -> s -> [[Located (Trail V2 n)]] cutBy' eps a b = cutPBy' eps (toPath a) (toPath b) -- | Seperate a path into sections at every point it intersects a second path, -- returning a list of sections for each component trail. cutPBy :: (OrderedField n, Real n) => Path V2 n -> Path V2 n -> [[Located (Trail V2 n)]] cutPBy = cutPBy' defEps -- | `cutPBy` using the given tolerance for calculating intersections. cutPBy' :: (OrderedField n, Real n) => n -> Path V2 n -> Path V2 n -> [[Located (Trail V2 n)]] cutPBy' eps p1 p2 = map (flip (cutTBy' eps) p2) (pathTrails p1) -- | Seperate a located trail into sections at every point it intersects a path. cutTBy :: (OrderedField n, Real n) => Located (Trail V2 n) -> Path V2 n -> [Located (Trail V2 n)] cutTBy = cutTBy' defEps -- | `cutTBy` using the given tolerance for calculating intersections. cutTBy' :: (OrderedField n, Real n) => n -> Located (Trail V2 n) -> Path V2 n -> [Located (Trail V2 n)] cutTBy' eps t p | null isects = [t] | null nearEnds && norm (start .-. end) < eps = gluedEnds | otherwise = subsections where subsections = zipWith (section t) (0:isects) (isects++[1]) isects = sortAndAvoidEmpty notNearEnds sortAndAvoidEmpty = map head . groupBy (\a b -> abs (a - b) < eps) . sort (notNearEnds, nearEnds) = partition (\p -> (eps < p) && (p < 1-eps)) rawIsects rawIsects = concatMap (fst . intersectParamsT' eps t) (pathTrails p) start = head subsections `atParam` 0 end = last subsections `atParam` 1 gluedEnds = unfixTrail (fixTrail (last subsections) ++ fixTrail (head subsections)) : init (tail subsections) ----------------------------------------------------------------------------- -- Rad Explosions ----------------------------------------------------------- ----------------------------------------------------------------------------- -- | explodePath specialized to return located trails. This provides the compiler -- the necessary type information to use it with `onSections` without providing -- a type annotation. -- -- <> -- -- > explodeSegmentsEx = onSections (explodeSegments squares) colorLines -- > where -- > squares = square 1 -- > <> square 1 # rotate (1/8 @@ turn) -- > colorLines = map (map lc) -- > [ [ red, yellow, gold, orange] -- > , [blue, violet, purple, indigo] ] explodeSegments :: (Metric v, OrderedField n) => Path v n -> [[Located (Trail v n)]] explodeSegments = explodePath -- | Turn a path a list of component trails, then cut those segments at all -- their intersections. -- -- <> -- -- > explodeIntersectionsEx = onSections (explodeIntersections squares) colorLines -- > where -- > squares = square 1 -- > <> square 1 # rotate (1/8 @@ turn) -- > colorLines = map (map lc) -- > [ [ gray, red, orange, yellow, green, blue, indigo, violet] -- > , [black, crimson, darkorange, gold, darkgreen, darkblue, midnightblue, darkviolet] ] explodeIntersections :: (OrderedField n, Real n) => Path V2 n -> [[Located (Trail V2 n)]] explodeIntersections = explodeIntersections' defEps -- | `explodeIntersections` using the given tolerance for calculating intersections. explodeIntersections' :: (OrderedField n, Real n) => n -> Path V2 n -> [[Located (Trail V2 n)]] explodeIntersections' eps path = cutBy' eps path path -- | Turn a path into a list of component segments for each component trail, -- then cut those segments at all their intersections. -- -- <> -- -- > explodeBothEx = onSections (explodeBoth squares) colorLines -- > where -- > squares = square 1 -- > <> square 1 # rotate (1/8 @@ turn) -- > colorLines = map (map (map lc)) -- > [ cycle [ [ gray, red, orange], [yellow, green, blue] ] -- > , cycle [ [black, crimson, darkorange], [ gold, darkgreen, darkblue] ] ] explodeBoth :: (OrderedField n, Real n) => Path V2 n -> [[[Located (Trail V2 n)]]] explodeBoth = explodeBoth' defEps -- | `explodeBoth` using the given tolerance for calculating intersections. explodeBoth' :: (OrderedField n, Real n) => n -> Path V2 n -> [[[Located (Trail V2 n)]]] explodeBoth' eps path = map (map (flip (cutTBy' eps) path)) $ explodePath path ----------------------------------------------------------------------------- -- Consuming Cut Paths ----------------------------------------------------- ----------------------------------------------------------------------------- class OnSections ps fs b n | ps b -> fs n, fs -> b n where -- | Zipply apply an arbitrarily nested list of attributes to the same shape -- of lists of pathlike objects, monoidally combining the results. -- -- See examples for `cutBy`, `explodeSegments`, `explodeIntersections`, and `explodeBoth`. onSections :: ps -> fs -> QDiagram b V2 n Any -- Need to list out the instances rather than using overlaping instances -- with ToPath in order to use the fundep (ps b -> fs). instance (TypeableFloat n, OnSections ps fs b n) => OnSections [ps] [fs] b n where onSections ps fs = mconcat $ zipWith onSections ps fs instance (TypeableFloat n, Renderable (Path V2 n) b) => OnSections (Path V2 n) (QDiagram b V2 n Any -> QDiagram b V2 n Any) b n where onSections ps fs = fs $ stroke ps instance (TypeableFloat n, Renderable (Path V2 n) b) => OnSections (Located (Trail V2 n)) (QDiagram b V2 n Any -> QDiagram b V2 n Any) b n where onSections ps fs = fs $ stroke ps instance (TypeableFloat n, Renderable (Path V2 n) b) => OnSections (Located (Trail' l V2 n)) (QDiagram b V2 n Any -> QDiagram b V2 n Any) b n where onSections ps fs = fs $ stroke ps instance (TypeableFloat n, Renderable (Path V2 n) b) => OnSections (Located [Segment Closed V2 n]) (QDiagram b V2 n Any -> QDiagram b V2 n Any) b n where onSections ps fs = fs $ stroke ps instance (TypeableFloat n, Renderable (Path V2 n) b) => OnSections (Located (Segment Closed V2 n)) (QDiagram b V2 n Any -> QDiagram b V2 n Any) b n where onSections ps fs = fs $ stroke ps instance (TypeableFloat n, Renderable (Path V2 n) b) => OnSections (Trail V2 n) (QDiagram b V2 n Any -> QDiagram b V2 n Any) b n where onSections ps fs = fs $ stroke ps instance (TypeableFloat n, Renderable (Path V2 n) b) => OnSections (Trail' l V2 n) (QDiagram b V2 n Any -> QDiagram b V2 n Any) b n where onSections ps fs = fs $ stroke ps instance (TypeableFloat n, Renderable (Path V2 n) b) => OnSections (FixedSegment V2 n) (QDiagram b V2 n Any -> QDiagram b V2 n Any) b n where onSections ps fs = fs $ stroke ps instance (TypeableFloat n, Renderable (Path V2 n) b) => OnSections (QDiagram b V2 n Any) (QDiagram b V2 n Any -> QDiagram b V2 n Any) b n where onSections ps fs = fs ps