hgeometry 0.10.0.0 → 0.14
raw patch · 169 files changed
Files
- README.md +46/−39
- benchmark/Algorithms/Geometry/ClosestPair/Bench.hs +36/−0
- benchmark/Algorithms/Geometry/ConvexHull/Bench.hs +65/−0
- benchmark/Algorithms/Geometry/ConvexHull/GrahamFam.hs +103/−0
- benchmark/Algorithms/Geometry/ConvexHull/GrahamFam6.hs +103/−0
- benchmark/Algorithms/Geometry/ConvexHull/GrahamFixed.hs +104/−0
- benchmark/Algorithms/Geometry/ConvexHull/GrahamV2.hs +95/−0
- benchmark/Algorithms/Geometry/LineSegmentIntersection/Bench.hs +52/−0
- benchmark/Algorithms/Geometry/LineSegmentIntersection/BentleyOttmannNoExt.hs +440/−0
- benchmark/Algorithms/Geometry/LineSegmentIntersection/BentleyOttmannOld.hs +225/−0
- benchmark/Algorithms/Geometry/LineSegmentIntersection/TypesNoExt.hs +200/−0
- benchmark/Algorithms/Geometry/PolygonTriangulation/Bench.hs +64/−0
- benchmark/Algorithms/Geometry/PolygonTriangulation/MakeMonotoneOld.hs +311/−0
- benchmark/Benchmark/Util.hs +7/−0
- benchmark/Benchmarks.hs +10/−0
- benchmark/Data/Geometry/IntervalTreeBench.hs +75/−0
- benchmark/Data/Geometry/Vector/VectorFamily6.hs +257/−0
- changelog +199/−0
- changelog.org +97/−0
- docs/Data/Geometry/PlanarSubdivision/mySubdiv.jpg binary
- docs/Data/PlaneGraph/planegraph.png binary
- doctests.hs +8/−1
- hgeometry.cabal +195/−42
- src/Algorithms/Geometry/ClosestPair.hs +14/−0
- src/Algorithms/Geometry/ClosestPair/DivideAndConquer.hs +3/−3
- src/Algorithms/Geometry/ConvexHull.hs +10/−0
- src/Algorithms/Geometry/ConvexHull/DivideAndConquer.hs +4/−5
- src/Algorithms/Geometry/ConvexHull/GrahamScan.hs +85/−6
- src/Algorithms/Geometry/ConvexHull/JarvisMarch.hs +151/−0
- src/Algorithms/Geometry/ConvexHull/Naive.hs +98/−0
- src/Algorithms/Geometry/ConvexHull/QuickHull.hs +12/−5
- src/Algorithms/Geometry/DelaunayTriangulation/DivideAndConquer.hs +45/−34
- src/Algorithms/Geometry/DelaunayTriangulation/Naive.hs +13/−7
- src/Algorithms/Geometry/DelaunayTriangulation/Types.hs +50/−25
- src/Algorithms/Geometry/Diameter.hs +13/−0
- src/Algorithms/Geometry/Diameter/ConvexHull.hs +35/−0
- src/Algorithms/Geometry/Diameter/Naive.hs +10/−0
- src/Algorithms/Geometry/EuclideanMST.hs +46/−0
- src/Algorithms/Geometry/EuclideanMST/EuclideanMST.hs +5/−35
- src/Algorithms/Geometry/FrechetDistance/Discrete.hs +7/−0
- src/Algorithms/Geometry/InPolygon.hs +155/−0
- src/Algorithms/Geometry/LineSegmentIntersection.hs +28/−9
- src/Algorithms/Geometry/LineSegmentIntersection/BentleyOttmann.hs +214/−79
- src/Algorithms/Geometry/LineSegmentIntersection/BooleanSweep.hs +190/−0
- src/Algorithms/Geometry/LineSegmentIntersection/Naive.hs +35/−29
- src/Algorithms/Geometry/LineSegmentIntersection/Types.hs +170/−45
- src/Algorithms/Geometry/LinearProgramming/LP2DRIC.hs +25/−22
- src/Algorithms/Geometry/LinearProgramming/Types.hs +5/−5
- src/Algorithms/Geometry/LowerEnvelope/DualCH.hs +8/−1
- src/Algorithms/Geometry/PolyLineSimplification/DouglasPeucker.hs +15/−8
- src/Algorithms/Geometry/PolyLineSimplification/ImaiIri.hs +138/−0
- src/Algorithms/Geometry/PolygonTriangulation.hs +14/−0
- src/Algorithms/Geometry/PolygonTriangulation/EarClip.hs +525/−0
- src/Algorithms/Geometry/PolygonTriangulation/MakeMonotone.hs +31/−25
- src/Algorithms/Geometry/PolygonTriangulation/Triangulate.hs +17/−15
- src/Algorithms/Geometry/PolygonTriangulation/TriangulateMonotone.hs +52/−33
- src/Algorithms/Geometry/PolygonTriangulation/Types.hs +21/−15
- src/Algorithms/Geometry/RayShooting/Naive.hs +88/−0
- src/Algorithms/Geometry/RedBlueSeparator/RIC.hs +2/−2
- src/Algorithms/Geometry/SSSP.hs +454/−0
- src/Algorithms/Geometry/SSSP/Naive.hs +90/−0
- src/Algorithms/Geometry/SmallestEnclosingBall.hs +20/−0
- src/Algorithms/Geometry/SmallestEnclosingBall/Naive.hs +11/−9
- src/Algorithms/Geometry/SmallestEnclosingBall/RIC.hs +30/−15
- src/Algorithms/Geometry/SmallestEnclosingBall/Types.hs +5/−5
- src/Algorithms/Geometry/SoS.hs +235/−0
- src/Algorithms/Geometry/SoS/AsPoint.hs +27/−0
- src/Algorithms/Geometry/SoS/Determinant.hs +13/−0
- src/Algorithms/Geometry/SoS/Expr.hs +78/−0
- src/Algorithms/Geometry/SoS/Internal.hs +28/−0
- src/Algorithms/Geometry/SoS/Orientation.hs +83/−0
- src/Algorithms/Geometry/SoS/Sign.hs +31/−0
- src/Algorithms/Geometry/SoS/Symbolic.hs +359/−0
- src/Algorithms/Geometry/VisibilityPolygon/Lee.hs +531/−0
- src/Algorithms/Geometry/WSPD.hs +474/−0
- src/Algorithms/Geometry/WSPD/Types.hs +85/−0
- src/Algorithms/Geometry/WellSeparatedPairDecomposition/Types.hs +7/−70
- src/Algorithms/Geometry/WellSeparatedPairDecomposition/WSPD.hs +5/−448
- src/Data/Geometry/Arrangement/Internal.hs +39/−44
- src/Data/Geometry/Ball.hs +59/−23
- src/Data/Geometry/BezierSpline.hs +641/−0
- src/Data/Geometry/Boundary.hs +14/−2
- src/Data/Geometry/Box.hs +16/−46
- src/Data/Geometry/Box/Corners.hs +80/−0
- src/Data/Geometry/Box/Internal.hs +76/−37
- src/Data/Geometry/Box/Sides.hs +98/−0
- src/Data/Geometry/Directions.hs +56/−0
- src/Data/Geometry/Duality.hs +9/−2
- src/Data/Geometry/Ellipse.hs +63/−0
- src/Data/Geometry/HalfLine.hs +133/−65
- src/Data/Geometry/HalfSpace.hs +19/−12
- src/Data/Geometry/HyperPlane.hs +59/−6
- src/Data/Geometry/Interval.hs +94/−30
- src/Data/Geometry/Interval/Util.hs +8/−0
- src/Data/Geometry/IntervalTree.hs +15/−10
- src/Data/Geometry/KDTree.hs +19/−13
- src/Data/Geometry/Line.hs +23/−19
- src/Data/Geometry/Line/Internal.hs +86/−41
- src/Data/Geometry/LineSegment.hs +101/−257
- src/Data/Geometry/LineSegment/Internal.hs +551/−0
- src/Data/Geometry/Matrix.hs +95/−0
- src/Data/Geometry/Matrix/Internal.hs +14/−0
- src/Data/Geometry/PlanarSubdivision.hs +47/−30
- src/Data/Geometry/PlanarSubdivision/Basic.hs +289/−78
- src/Data/Geometry/PlanarSubdivision/Dynamic.hs +526/−0
- src/Data/Geometry/PlanarSubdivision/Merge.hs +15/−15
- src/Data/Geometry/PlanarSubdivision/Raw.hs +9/−1
- src/Data/Geometry/PlanarSubdivision/TreeRep.hs +110/−0
- src/Data/Geometry/Point.hs +37/−395
- src/Data/Geometry/Point/Class.hs +85/−0
- src/Data/Geometry/Point/Internal.hs +303/−0
- src/Data/Geometry/Point/Orientation.hs +31/−0
- src/Data/Geometry/Point/Orientation/Degenerate.hs +226/−0
- src/Data/Geometry/Point/Quadrants.hs +62/−0
- src/Data/Geometry/PointLocation.hs +12/−0
- src/Data/Geometry/PointLocation/PersistentSweep.hs +176/−0
- src/Data/Geometry/PolyLine.hs +73/−10
- src/Data/Geometry/Polygon.hs +102/−26
- src/Data/Geometry/Polygon/Bezier.hs +57/−0
- src/Data/Geometry/Polygon/Convex.hs +376/−70
- src/Data/Geometry/Polygon/Core.hs +520/−278
- src/Data/Geometry/Polygon/Extremes.hs +5/−6
- src/Data/Geometry/Polygon/Inflate.hs +142/−0
- src/Data/Geometry/Polygon/Monotone.hs +118/−0
- src/Data/Geometry/PrioritySearchTree.hs +4/−2
- src/Data/Geometry/Properties.hs +0/−1
- src/Data/Geometry/QuadTree.hs +211/−0
- src/Data/Geometry/QuadTree/Cell.hs +151/−0
- src/Data/Geometry/QuadTree/Quadrants.hs +23/−0
- src/Data/Geometry/QuadTree/Split.hs +40/−0
- src/Data/Geometry/QuadTree/Tree.hs +123/−0
- src/Data/Geometry/RangeTree.hs +11/−7
- src/Data/Geometry/RangeTree/Generic.hs +9/−2
- src/Data/Geometry/RangeTree/Measure.hs +12/−5
- src/Data/Geometry/SegmentTree.hs +7/−0
- src/Data/Geometry/SegmentTree/Generic.hs +8/−6
- src/Data/Geometry/Slab.hs +54/−32
- src/Data/Geometry/SubLine.hs +176/−70
- src/Data/Geometry/Transformation.hs +47/−161
- src/Data/Geometry/Transformation/Internal.hs +224/−0
- src/Data/Geometry/Triangle.hs +90/−42
- src/Data/Geometry/Vector.hs +71/−18
- src/Data/Geometry/Vector/VectorFamily.hs +98/−34
- src/Data/Geometry/Vector/VectorFamilyPeano.hs +52/−14
- src/Data/Geometry/Vector/VectorFixed.hs +19/−5
- src/Data/Geometry/VerticalRayShooting.hs +12/−0
- src/Data/Geometry/VerticalRayShooting/PersistentSweep.hs +213/−0
- src/Data/PlaneGraph.hs +84/−7
- src/Data/PlaneGraph/AdjRep.hs +1/−1
- src/Data/PlaneGraph/Core.hs +318/−81
- src/Data/PlaneGraph/IO.hs +85/−14
- src/Graphics/Camera.hs +35/−6
- test/Algorithms/Geometry/LineSegmentIntersection/manual.ipe +0/−324
- test/Algorithms/Geometry/LineSegmentIntersection/selfIntersections.ipe +0/−313
- test/Algorithms/Geometry/LinearProgramming/manual.ipe +0/−370
- test/Algorithms/Geometry/LowerEnvelope/manual.ipe +0/−299
- test/Algorithms/Geometry/PolygonTriangulation/monotone.ipe +0/−364
- test/Algorithms/Geometry/PolygonTriangulation/simplepolygon6.ipe +0/−297
- test/Algorithms/Geometry/RedBlueSeparator/manual.ipe +0/−355
- test/Algorithms/Geometry/SmallestEnclosingDisk/manual.ipe +0/−369
- test/Data/Geometry/Polygon/Convex/convexTests.ipe +0/−308
- test/Data/Geometry/Polygon/star_shaped.ipe +0/−339
- test/Data/Geometry/arrangement.ipe +0/−296
- test/Data/Geometry/arrangement.ipe.out.ipe +0/−247
- test/Data/Geometry/pointInPolygon.ipe +0/−311
- test/Data/Geometry/pointInTriangle.ipe +0/−310
- test/Data/PlaneGraph/myPlaneGraph.yaml +0/−90
- test/Data/PlaneGraph/small.yaml +0/−58
- test/Data/PlaneGraph/testsegs.png binary
README.md view
@@ -1,8 +1,9 @@ HGeometry ========= -[](https://travis-ci.org/noinia/hgeometry)-[](https://hackage.haskell.org/package/hgeometry)++[](https://hackage.haskell.org/package/hgeometry)+[](https://noinia.github.io/hgeometry/doc/) HGeometry is a library for computing with geometric objects in Haskell. It defines basic geometric types and primitives, and it@@ -34,19 +35,23 @@ HGeometry is split into a few smaller packages. In particular: -- hgeometry-combinatorial : defines some non-geometric+- hgeometry : defines the actual geometric data types, data+ structures, and algorithms,+- hgeometry-combinatorial : defines the non-geometric (i.e. combinatorial) data types, data structures, and algorithms.+ - hgeometry-ipe : defines functions for working with [ipe](http://ipe.otfried.org) files. - hgeometry-svg : defines functions for working with svg files.-- hgeometry-interactive : defines functions for building an+- hgeometry-web : defines functions for building an interactive viewer using [miso](https://haskell-miso.org).-- hgeometry : defines the actual geometric data types, data- structures, and algorithms.+- hgeometry-interactive : defines functions for building an+ interactive viewer using+ [reflex-sdl2](https://hackage.haskell.org/package/reflex-sdl2). -In addition there is a [hgeometry-examples](hgeometry-examples)-package that defines some example applications, and a hgometry-test-package that contains all testcases. The latter is to work around a-bug in cabal.+In addition there are [hgeometry-examples](hgeometry-examples) and+[hgeometry-showcase](hgeometry-showcase) packages that define some+example applications, and a hgometry-test package that contains all+testcases. The latter is to work around a bug in cabal. Available Geometric Algorithms ------------------------------@@ -56,25 +61,28 @@ implements some more advanced geometric algorithms. In particuar, the following algorithms are currently available: -* two \(O(n \log n)\) time algorithms for convex hull in- $\mathbb{R}^2$: the typical Graham scan, and a divide and conquer algorithm,-* an \(O(n)\) expected time algorithm for smallest enclosing disk in $\mathbb{R}^$2,+* two *O(n log n)* time algorithms for convex hull in+ ℝ²: the typical Graham scan, and a divide and conquer algorithm,+* an *O(n)* expected time algorithm for smallest enclosing disk in ℝ², * the well-known Douglas Peucker polyline line simplification algorithm,-* an \(O(n \log n)\) time algorithm for computing the Delaunay triangulation-(using divide and conquer).-* an \(O(n \log n)\) time algorithm for computing the Euclidean Minimum Spanning-Tree (EMST), based on computing the Delaunay Triangulation.-* an \(O(\log^2 n)\) time algorithm to find extremal points and tangents on/to a- convex polygon.-* An optimal \(O(n+m)\) time algorithm to compute the Minkowski sum of two convex-polygons.-* An \(O(1/\varepsilon^dn\log n)\) time algorithm for constructing a Well-Separated pair- decomposition.-* The classic (optimal) \(O(n\log n)\) time divide and conquer algorithm to- compute the closest pair among a set of \(n\) points in \(\mathbb{R}^2\).-* An \(O(nm)\) time algorithm to compute the discrete Fr\'echet- distance of two sequences of points (curves) of length \(n\) and- \(m\), respectively.+* an *O(n log n)* time algorithm for computing the Delaunay triangulation+(using divide and conquer),+* an *O(n log n)* time algorithm for computing the Euclidean Minimum Spanning+Tree (EMST), based on computing the Delaunay Triangulation,+* an *O(log n)* time algorithm to find extremal points and tangents on/to a+ convex polygon,+* an optimal *O(n+m)* time algorithm to compute the Minkowski sum of two convex+polygons,+* an *O(1/εᵈn log n)* time algorithm for constructing a Well-Separated pair+ decomposition,+* the classic (optimal) *O(n log n)* time divide and conquer algorithm to+ compute the closest pair among a set of *n* points in ℝ²,+* an *O(nm)* time algorithm to compute the discrete Fréchet+ distance of two sequences of points (curves) of length *n* and+ *m*, respectively.+* an *O(n)* time single-source shortest path algorithm on triangulated polygons.+* an *O(n log n)* time algorithm for generating random convex polygons.+* an *O(n)* time algorithm for finding the convex hull of a simple polygon. Available Geometric Data Structures -----------------------------------@@ -85,11 +93,13 @@ * A one dimensional Segment Tree. The base tree is static. * A one dimensional Interval Tree. The base tree is static. * A KD-Tree. The base tree is static.+* An *O(n log n)* size planar point location data structure supporting+ *O(log n)* queries. There is also support for working with planar subdivisions. As a result, [hgeometry-combinatorial] also includes a data structure for working with planar graphs. In particular, it has an `EdgeOracle` data-structure, that can be built in \(O(n)\) time that can test if the+structure, that can be built in *O(n)* time that can test if the planar graph contains an edge in constant time. @@ -101,8 +111,8 @@ i.e. because of floating point errors one may get completely wrong results. Hence, I *strongly* advise against using `Double` or `Float` for these types. In several algorithms it is sufficient if the type `r` is-`Fractional`. Hence, you can use an exact number type such as `Rational`.-+`Fractional`. Hence, you can use an exact number type such as+`Data.RealNumber.Rational` or `Data.Ratio.Rational`. Working with additional data ----------------------------@@ -119,14 +129,11 @@ To still allow for some extensibility our types will use the Ext (:+) type, as defined in the hgeometry-combinatorial package. For example,-our `Polygon` data type, has an extra type parameter `p` that allows-the vertices of the polygon to cary some extra information of type `p`-(for example a color, a size, or whatever).+our `LineSegment` data type, has an extra type parameter `p` that+allows the vertices of the line segment to carry some extra+information of type `p` (for example a color, a size, or+whatever). Polylines, Polylygons, Boxes, etc have similar such+parameters. -```haskell-data Polygon (t :: PolygonType) p r where- SimplePolygon :: C.CSeq (Point 2 r :+ p) -> Polygon Simple p r- MultiPolygon :: C.CSeq (Point 2 r :+ p) -> [Polygon Simple p r] -> Polygon Multi p r-``` In all places this extra data is accessable by the (:+) type in Data.Ext, which is essentially just a pair.
+ benchmark/Algorithms/Geometry/ClosestPair/Bench.hs view
@@ -0,0 +1,36 @@+module Algorithms.Geometry.ClosestPair.Bench where++import qualified Algorithms.Geometry.ClosestPair.DivideAndConquer as DivideAndConquer+import qualified Algorithms.Geometry.ClosestPair.Naive as Naive++import Control.Monad.Random+import Data.Ext+import Data.Geometry.Point+import Data.Hashable+import Data.LSeq (LSeq)+import qualified Data.LSeq as LSeq+import Test.Tasty.Bench++--------------------------------------------------------------------------------++genPts :: (Ord r, Random r, RandomGen g)+ => Int -> Rand g (LSeq 2 (Point 2 r :+ ()))+genPts n | n >= 2 = LSeq.promise . LSeq.fromList <$> replicateM n (fmap ext getRandom)+ | otherwise = error "genPts: Need at least 2 points"++gen :: StdGen+gen = mkStdGen (hash "closest pair")++-- | Benchmark computing the closest pair+benchmark :: Benchmark+benchmark = bgroup "ClosestPair"+ [ bgroup (show n) (build $ evalRand (genPts @Int n) gen)+ | n <- sizes'+ ]+ where+ sizes' = [500]++ build pts = [ bench "sort" $ nf LSeq.unstableSort pts+ , bench "Div&Conq" $ nf DivideAndConquer.closestPair pts+ , bench "Naive" $ nf Naive.closestPair pts+ ]
+ benchmark/Algorithms/Geometry/ConvexHull/Bench.hs view
@@ -0,0 +1,65 @@+module Algorithms.Geometry.ConvexHull.Bench (benchmark) where++import qualified Algorithms.Geometry.ConvexHull.DivideAndConquer as DivideAndConquer+import qualified Algorithms.Geometry.ConvexHull.GrahamScan as GrahamScan+import qualified Algorithms.Geometry.ConvexHull.JarvisMarch as JarvisMarch+import qualified Algorithms.Geometry.ConvexHull.QuickHull as QuickHull++import Control.Monad.Random+import Data.Double.Approximate+import Data.Ext+import Data.Geometry.Point+import Data.Hashable+import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.List.NonEmpty as NonEmpty+import Data.RealNumber.Rational+import Test.Tasty.Bench++type R = RealNumber 5++--------------------------------------------------------------------------------++genPts :: (Ord r, Random r, RandomGen g)+ => Int -> Rand g (NonEmpty (Point 2 r :+ ()))+genPts n = NonEmpty.fromList <$> replicateM n (fmap ext getRandom)++-- genPts' :: (Ord r, Random r, RandomGen g) => Int+-- -> Rand g ( NonEmpty (Point 2 r :+ ())+-- , NonEmpty (Point 2 r Multi.:+ '[])+-- )+-- genPts' n = (\pts -> (pts, fmap (\ ~(c :+ _) -> Multi.ext c) pts)+-- ) <$> genPts n++gen :: StdGen+gen = mkStdGen (hash "convex hull")++-- | Benchmark building the convexHull+benchmark :: Benchmark+benchmark = bgroup "ConvexHull" $+ [ bgroup ("1e"++show i ++ "/RealNumber") (convexHullFractional $ evalRand (genPts @R n) gen)+ | i <- [3, 4::Int]+ , let n = 10^i+ ] +++ [ bgroup ("1e"++show i ++ "/Int") (convexHullNum $ evalRand (genPts @Int n) gen)+ | i <- [4, 5::Int]+ , let n = 10^i+ ] +++ [ bgroup ("1e"++show i ++ "/SafeDouble") (convexHullFractional $ evalRand (genPts @SafeDouble n) gen)+ | i <- [4, 5::Int]+ , let n = 10^i+ ] +++ [ bgroup ("1e"++show i ++ "/Double") (convexHullFractional $ evalRand (genPts @Double n) gen)+ | i <- [4, 5::Int]+ , let n = 10^i ]+ where+ convexHullFractional pts =+ [ bench "GrahamScan" $ nf GrahamScan.convexHull pts+ , bench "DivideAndConquer" $ nf DivideAndConquer.convexHull pts+ , bench "QuickHull" $ nf QuickHull.convexHull pts+ , bench "JarvisMarch" $ nf JarvisMarch.convexHull pts+ ]+ convexHullNum pts =+ [ bench "GrahamScan" $ nf GrahamScan.convexHull pts+ , bench "DivideAndConquer" $ nf DivideAndConquer.convexHull pts+ , bench "JarvisMarch" $ nf JarvisMarch.convexHull pts+ ]
+ benchmark/Algorithms/Geometry/ConvexHull/GrahamFam.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE UndecidableInstances #-}+module Algorithms.Geometry.ConvexHull.GrahamFam( convexHull+ , upperHull+ , lowerHull, fromP+ ) where++import Control.DeepSeq+import Control.Lens ((^.))+import Data.Ext+import Data.Geometry.Point+import qualified Data.Geometry.Vector.VectorFamily as VF+import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.List.NonEmpty as NonEmpty+import Data.Monoid+import GHC.TypeLits+++newtype MyPoint d r = MyPoint (VF.Vector d r)++deriving instance (VF.Arity d, Eq r) => Eq (MyPoint d r)+deriving instance (VF.Arity d, Ord r) => Ord (MyPoint d r)+deriving instance (VF.Arity d, Show r) => Show (MyPoint d r)+deriving instance (NFData (VF.Vector d r)) => NFData (MyPoint d r)++pattern MyPoint2 x y = MyPoint (VF.Vector2 x y)+++-- instance (NFData r, Arity d) => NFData (MyPoint d r) where+-- rnf (MyPoint x y) = rnf (x,y)+-- rnf (MyP p) = rnf p++toP :: MyPoint 2 r :+ e -> Point 2 r :+ e+toP (MyPoint2 x y :+ e) = Point2 x y :+ e++fromP :: Point 2 r :+ e -> MyPoint 2 r :+ e+fromP (Point2 x y :+ e) = MyPoint2 x y :+ e+++subt :: Num r => MyPoint 2 r -> MyPoint 2 r -> MyPoint 2 r+(MyPoint2 x y) `subt` (MyPoint2 a b) = MyPoint2 (x-a) (y-b)++newtype ConvexPolygon p r = ConvexPolygon [Point 2 r :+ p] deriving (Show,Eq,NFData)++-- | \(O(n \log n)\) time ConvexHull using Graham-Scan. The resulting polygon is+-- given in clockwise order.+convexHull :: (Ord r, Num r)+ => NonEmpty (MyPoint 2 r :+ p) -> ConvexPolygon p r+convexHull (p :| []) = ConvexPolygon $ [toP p]+convexHull ps = let ps' = NonEmpty.toList . NonEmpty.sortBy incXdecY $ ps+ uh = NonEmpty.tail . hull' $ ps'+ lh = NonEmpty.tail . hull' $ reverse ps'+ in ConvexPolygon . map toP . reverse $ lh ++ uh++upperHull :: (Ord r, Num r) => NonEmpty (MyPoint 2 r :+ p) -> NonEmpty (MyPoint 2 r :+ p)+upperHull = hull id+++lowerHull :: (Ord r, Num r) => NonEmpty (MyPoint 2 r :+ p) -> NonEmpty (MyPoint 2 r :+ p)+lowerHull = hull reverse+++-- | Helper function so that that can compute both the upper or the lower hull, depending+-- on the function f+hull :: (Ord r, Num r)+ => ([MyPoint 2 r :+ p] -> [MyPoint 2 r :+ p])+ -> NonEmpty (MyPoint 2 r :+ p) -> NonEmpty (MyPoint 2 r :+ p)+hull _ h@(_ :| []) = h+hull f pts = hull' . f+ . NonEmpty.toList . NonEmpty.sortBy incXdecY $ pts++incXdecY :: Ord r => (MyPoint 2 r) :+ p -> (MyPoint 2 r) :+ q -> Ordering+incXdecY (MyPoint2 px py :+ _) (MyPoint2 qx qy :+ _) =+ compare px qx <> compare qy py+++-- | Precondition: The list of input points is sorted+hull' :: (Ord r, Num r) => [MyPoint 2 r :+ p] -> NonEmpty (MyPoint 2 r :+ p)+hull' (a:b:ps) = NonEmpty.fromList $ hull'' [b,a] ps+ where+ hull'' h [] = h+ hull'' h (p:ps') = hull'' (cleanMiddle (p:h)) ps'++ cleanMiddle h@[_,_] = h+ cleanMiddle h@(z:y:x:rest)+ | rightTurn (x^.core) (y^.core) (z^.core) = h+ | otherwise = cleanMiddle (z:x:rest)+ cleanMiddle _ = error "cleanMiddle: too few points"++rightTurn :: (Ord r, Num r) => MyPoint 2 r -> MyPoint 2 r -> MyPoint 2 r -> Bool+rightTurn a b c = ccwP a b c == CW++++ccwP :: (Ord r, Num r) => MyPoint 2 r -> MyPoint 2 r -> MyPoint 2 r -> CCW+ccwP p q r = case z `compare` 0 of+ LT -> CW+ GT -> CCW+ EQ -> CoLinear+ where++ MyPoint2 ux uy = q `subt` p+ MyPoint2 vx vy = r `subt` p+ z = ux * vy - uy * vx
+ benchmark/Algorithms/Geometry/ConvexHull/GrahamFam6.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE UndecidableInstances #-}+module Algorithms.Geometry.ConvexHull.GrahamFam6( convexHull+ , upperHull+ , lowerHull, fromP+ ) where++import Control.DeepSeq+import Control.Lens ((^.))+import Data.Ext+import Data.Geometry.Point+import qualified Data.Geometry.Vector.VectorFamily6 as VF+import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.List.NonEmpty as NonEmpty+import Data.Monoid+import GHC.TypeLits+++newtype MyPoint d r = MyPoint (VF.Vector d r)++deriving instance (VF.Arity d, Eq r) => Eq (MyPoint d r)+deriving instance (VF.Arity d, Ord r) => Ord (MyPoint d r)+deriving instance (VF.Arity d, Show r) => Show (MyPoint d r)+deriving instance (NFData (VF.Vector d r)) => NFData (MyPoint d r)++pattern MyPoint2 x y = MyPoint (VF.Vector2 x y)+++-- instance (NFData r, Arity d) => NFData (MyPoint d r) where+-- rnf (MyPoint x y) = rnf (x,y)+-- rnf (MyP p) = rnf p++toP :: MyPoint 2 r :+ e -> Point 2 r :+ e+toP (MyPoint2 x y :+ e) = Point2 x y :+ e++fromP :: Point 2 r :+ e -> MyPoint 2 r :+ e+fromP (Point2 x y :+ e) = MyPoint2 x y :+ e+++subt :: Num r => MyPoint 2 r -> MyPoint 2 r -> MyPoint 2 r+(MyPoint2 x y) `subt` (MyPoint2 a b) = MyPoint2 (x-a) (y-b)++newtype ConvexPolygon p r = ConvexPolygon [Point 2 r :+ p] deriving (Show,Eq,NFData)++-- | \(O(n \log n)\) time ConvexHull using Graham-Scan. The resulting polygon is+-- given in clockwise order.+convexHull :: (Ord r, Num r)+ => NonEmpty (MyPoint 2 r :+ p) -> ConvexPolygon p r+convexHull (p :| []) = ConvexPolygon $ [toP p]+convexHull ps = let ps' = NonEmpty.toList . NonEmpty.sortBy incXdecY $ ps+ uh = NonEmpty.tail . hull' $ ps'+ lh = NonEmpty.tail . hull' $ reverse ps'+ in ConvexPolygon . map toP . reverse $ lh ++ uh++upperHull :: (Ord r, Num r) => NonEmpty (MyPoint 2 r :+ p) -> NonEmpty (MyPoint 2 r :+ p)+upperHull = hull id+++lowerHull :: (Ord r, Num r) => NonEmpty (MyPoint 2 r :+ p) -> NonEmpty (MyPoint 2 r :+ p)+lowerHull = hull reverse+++-- | Helper function so that that can compute both the upper or the lower hull, depending+-- on the function f+hull :: (Ord r, Num r)+ => ([MyPoint 2 r :+ p] -> [MyPoint 2 r :+ p])+ -> NonEmpty (MyPoint 2 r :+ p) -> NonEmpty (MyPoint 2 r :+ p)+hull _ h@(_ :| []) = h+hull f pts = hull' . f+ . NonEmpty.toList . NonEmpty.sortBy incXdecY $ pts++incXdecY :: Ord r => (MyPoint 2 r) :+ p -> (MyPoint 2 r) :+ q -> Ordering+incXdecY (MyPoint2 px py :+ _) (MyPoint2 qx qy :+ _) =+ compare px qx <> compare qy py+++-- | Precondition: The list of input points is sorted+hull' :: (Ord r, Num r) => [MyPoint 2 r :+ p] -> NonEmpty (MyPoint 2 r :+ p)+hull' (a:b:ps) = NonEmpty.fromList $ hull'' [b,a] ps+ where+ hull'' h [] = h+ hull'' h (p:ps') = hull'' (cleanMiddle (p:h)) ps'++ cleanMiddle h@[_,_] = h+ cleanMiddle h@(z:y:x:rest)+ | rightTurn (x^.core) (y^.core) (z^.core) = h+ | otherwise = cleanMiddle (z:x:rest)+ cleanMiddle _ = error "cleanMiddle: too few points"++rightTurn :: (Ord r, Num r) => MyPoint 2 r -> MyPoint 2 r -> MyPoint 2 r -> Bool+rightTurn a b c = ccwP a b c == CW++++ccwP :: (Ord r, Num r) => MyPoint 2 r -> MyPoint 2 r -> MyPoint 2 r -> CCW+ccwP p q r = case z `compare` 0 of+ LT -> CW+ GT -> CCW+ EQ -> CoLinear+ where++ MyPoint2 ux uy = q `subt` p+ MyPoint2 vx vy = r `subt` p+ z = ux * vy - uy * vx
+ benchmark/Algorithms/Geometry/ConvexHull/GrahamFixed.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE UndecidableInstances #-}+module Algorithms.Geometry.ConvexHull.GrahamFixed( convexHull+ , upperHull+ , lowerHull, fromP+ ) where++import Control.DeepSeq+import Control.Lens ((^.))+import Data.Ext+import Data.Geometry.Point+import Data.Vector.Fixed (Arity)+import qualified Data.Geometry.Vector.VectorFixed as VF+import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.List.NonEmpty as NonEmpty+import Data.Monoid+import GHC.TypeLits+++newtype MyPoint d r = MyPoint (VF.Vector d r)++deriving instance (Arity d, Eq r) => Eq (MyPoint d r)+deriving instance (Arity d, Ord r) => Ord (MyPoint d r)+deriving instance (Arity d, Show r) => Show (MyPoint d r)+deriving instance (NFData (VF.Vector d r)) => NFData (MyPoint d r)++pattern MyPoint2 x y = MyPoint (VF.Vector2 x y)+++-- instance (NFData r, Arity d) => NFData (MyPoint d r) where+-- rnf (MyPoint x y) = rnf (x,y)+-- rnf (MyP p) = rnf p++toP :: MyPoint 2 r :+ e -> Point 2 r :+ e+toP (MyPoint2 x y :+ e) = Point2 x y :+ e++fromP :: Point 2 r :+ e -> MyPoint 2 r :+ e+fromP (Point2 x y :+ e) = MyPoint2 x y :+ e+++subt :: Num r => MyPoint 2 r -> MyPoint 2 r -> MyPoint 2 r+(MyPoint2 x y) `subt` (MyPoint2 a b) = MyPoint2 (x-a) (y-b)++newtype ConvexPolygon p r = ConvexPolygon [Point 2 r :+ p] deriving (Show,Eq,NFData)++-- | \(O(n \log n)\) time ConvexHull using Graham-Scan. The resulting polygon is+-- given in clockwise order.+convexHull :: (Ord r, Num r)+ => NonEmpty (MyPoint 2 r :+ p) -> ConvexPolygon p r+convexHull (p :| []) = ConvexPolygon $ [toP p]+convexHull ps = let ps' = NonEmpty.toList . NonEmpty.sortBy incXdecY $ ps+ uh = NonEmpty.tail . hull' $ ps'+ lh = NonEmpty.tail . hull' $ reverse ps'+ in ConvexPolygon . map toP . reverse $ lh ++ uh++upperHull :: (Ord r, Num r) => NonEmpty (MyPoint 2 r :+ p) -> NonEmpty (MyPoint 2 r :+ p)+upperHull = hull id+++lowerHull :: (Ord r, Num r) => NonEmpty (MyPoint 2 r :+ p) -> NonEmpty (MyPoint 2 r :+ p)+lowerHull = hull reverse+++-- | Helper function so that that can compute both the upper or the lower hull, depending+-- on the function f+hull :: (Ord r, Num r)+ => ([MyPoint 2 r :+ p] -> [MyPoint 2 r :+ p])+ -> NonEmpty (MyPoint 2 r :+ p) -> NonEmpty (MyPoint 2 r :+ p)+hull _ h@(_ :| []) = h+hull f pts = hull' . f+ . NonEmpty.toList . NonEmpty.sortBy incXdecY $ pts++incXdecY :: Ord r => (MyPoint 2 r) :+ p -> (MyPoint 2 r) :+ q -> Ordering+incXdecY (MyPoint2 px py :+ _) (MyPoint2 qx qy :+ _) =+ compare px qx <> compare qy py+++-- | Precondition: The list of input points is sorted+hull' :: (Ord r, Num r) => [MyPoint 2 r :+ p] -> NonEmpty (MyPoint 2 r :+ p)+hull' (a:b:ps) = NonEmpty.fromList $ hull'' [b,a] ps+ where+ hull'' h [] = h+ hull'' h (p:ps') = hull'' (cleanMiddle (p:h)) ps'++ cleanMiddle h@[_,_] = h+ cleanMiddle h@(z:y:x:rest)+ | rightTurn (x^.core) (y^.core) (z^.core) = h+ | otherwise = cleanMiddle (z:x:rest)+ cleanMiddle _ = error "cleanMiddle: too few points"++rightTurn :: (Ord r, Num r) => MyPoint 2 r -> MyPoint 2 r -> MyPoint 2 r -> Bool+rightTurn a b c = ccwP a b c == CW++++ccwP :: (Ord r, Num r) => MyPoint 2 r -> MyPoint 2 r -> MyPoint 2 r -> CCW+ccwP p q r = case z `compare` 0 of+ LT -> CW+ GT -> CCW+ EQ -> CoLinear+ where++ MyPoint2 ux uy = q `subt` p+ MyPoint2 vx vy = r `subt` p+ z = ux * vy - uy * vx
+ benchmark/Algorithms/Geometry/ConvexHull/GrahamV2.hs view
@@ -0,0 +1,95 @@+{-# Language DeriveGeneric #-}+module Algorithms.Geometry.ConvexHull.GrahamV2( convexHull+ , upperHull+ , lowerHull, fromP+ ) where+++import Control.DeepSeq+import Control.Lens ((^.))+import Data.Ext+import Data.Geometry.Point+import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.List.NonEmpty as NonEmpty+import Data.Monoid+import GHC.Generics+import qualified Linear.V2 as V2++++newtype MyPoint r = MKPoint (V2.V2 r) deriving (Show,Eq,Ord,Generic)+-- data MyPoint r = MyPoint !r !r deriving (Show,Eq,Ord,Generic)++pattern MyPoint x y = MKPoint (V2.V2 x y)++instance NFData r => NFData (MyPoint r)+++toP (MyPoint x y :+ e) = Point2 x y :+ e+fromP (Point2 x y :+ e) = MyPoint x y :+ e++(MyPoint x y) `subt` (MyPoint a b) = MyPoint (x-a) (y-b)+++newtype ConvexPolygon p r = ConvexPolygon [Point 2 r :+ p] deriving (Show,Eq,NFData)++-- | \(O(n \log n)\) time ConvexHull using Graham-Scan. The resulting polygon is+-- given in clockwise order.+convexHull :: (Ord r, Num r)+ => NonEmpty (MyPoint r :+ p) -> ConvexPolygon p r+convexHull (p :| []) = ConvexPolygon $ [toP p]+convexHull ps = let ps' = NonEmpty.toList . NonEmpty.sortBy incXdecY $ ps+ uh = NonEmpty.tail . hull' $ ps'+ lh = NonEmpty.tail . hull' $ reverse ps'+ in ConvexPolygon . map toP . reverse $ lh ++ uh++upperHull :: (Ord r, Num r) => NonEmpty (MyPoint r :+ p) -> NonEmpty (MyPoint r :+ p)+upperHull = hull id+++lowerHull :: (Ord r, Num r) => NonEmpty (MyPoint r :+ p) -> NonEmpty (MyPoint r :+ p)+lowerHull = hull reverse+++-- | Helper function so that that can compute both the upper or the lower hull, depending+-- on the function f+hull :: (Ord r, Num r)+ => ([MyPoint r :+ p] -> [MyPoint r :+ p])+ -> NonEmpty (MyPoint r :+ p) -> NonEmpty (MyPoint r :+ p)+hull _ h@(_ :| []) = h+hull f pts = hull' . f+ . NonEmpty.toList . NonEmpty.sortBy incXdecY $ pts++incXdecY :: Ord r => (MyPoint r) :+ p -> (MyPoint r) :+ q -> Ordering+incXdecY (MyPoint px py :+ _) (MyPoint qx qy :+ _) =+ compare px qx <> compare qy py+++-- | Precondition: The list of input points is sorted+hull' :: (Ord r, Num r) => [MyPoint r :+ p] -> NonEmpty (MyPoint r :+ p)+hull' (a:b:ps) = NonEmpty.fromList $ hull'' [b,a] ps+ where+ hull'' h [] = h+ hull'' h (p:ps') = hull'' (cleanMiddle (p:h)) ps'++ cleanMiddle h@[_,_] = h+ cleanMiddle h@(z:y:x:rest)+ | rightTurn (x^.core) (y^.core) (z^.core) = h+ | otherwise = cleanMiddle (z:x:rest)+ cleanMiddle _ = error "cleanMiddle: too few points"++rightTurn :: (Ord r, Num r) => MyPoint r -> MyPoint r -> MyPoint r -> Bool+rightTurn a b c = ccwP a b c == CW++++ccwP :: (Ord r, Num r) => MyPoint r -> MyPoint r -> MyPoint r -> CCW+ccwP p q r = case z `compare` 0 of+ LT -> CW+ GT -> CCW+ EQ -> CoLinear+ where++ MyPoint ux uy = q `subt` p+ MyPoint vx vy = r `subt` p+ z = ux * vy - uy * vx
+ benchmark/Algorithms/Geometry/LineSegmentIntersection/Bench.hs view
@@ -0,0 +1,52 @@+module Algorithms.Geometry.LineSegmentIntersection.Bench (benchmark) where++import qualified Algorithms.Geometry.LineSegmentIntersection.BentleyOttmann as BONew+import qualified Algorithms.Geometry.LineSegmentIntersection.BentleyOttmannNoExt as BONoExt+import qualified Algorithms.Geometry.LineSegmentIntersection.BentleyOttmannOld as BOOld++import Control.DeepSeq+import Control.Lens+import Control.Monad.Random+import Data.Ext+import Data.Geometry.LineSegment+import Data.Geometry.Point+import Data.Hashable+import qualified Data.List as List+import Data.RealNumber.Rational+import Test.Tasty.Bench++--------------------------------------------------------------------------------++type R = RealNumber 5++benchmark :: Benchmark+benchmark = bgroup "LineSegmentIntersection"+ [ benchBuild (evalRand (genPts @R 100) gen)+ ]++gen :: StdGen+gen = mkStdGen (hash "line segment intersection")++--------------------------------------------------------------------------------++genPts :: (Ord r, Random r, RandomGen g)+ => Int -> Rand g [LineSegment 2 () r :+ ()]+genPts n = map ext <$> replicateM n sampleLineSegment++-- | Benchmark computing the closest pair+benchBuild :: (Ord r, Fractional r, NFData r) => [LineSegment 2 () r :+ ()] -> Benchmark+benchBuild ss = bgroup "LineSegs" [ bgroup (show n) (build $ take n ss)+ | n <- sizes' ss+ ]+ where+ sizes' xs = [length xs]+ -- let n = length pts in [ n*i `div` 100 | i <- [10,20,25,50,75,100]]++ build segs = [ bench "sort" $ nf sort' segs+ , bench "Old" $ nf BOOld.intersections (map (^.core) segs)+ , bench "NoExt" $ nf BONoExt.intersections (map (^.core) segs)+ , bench "New" $ nf BONew.intersections segs+ ]++sort' :: Ord r => [LineSegment 2 () r :+ ()] -> [Point 2 r]+sort' = List.sort . concatMap (\s -> s^..core.endPoints.core)
+ benchmark/Algorithms/Geometry/LineSegmentIntersection/BentleyOttmannNoExt.hs view
@@ -0,0 +1,440 @@+{-# LANGUAGE ScopedTypeVariables #-}+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.LineSegmentIntersection.BentleyOttmann+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--+-- The \(O((n+k)\log n)\) time line segment intersection algorithm by Bentley+-- and Ottmann.+--+--------------------------------------------------------------------------------+module Algorithms.Geometry.LineSegmentIntersection.BentleyOttmannNoExt+ ( intersections+ , interiorIntersections+ ) where++import Algorithms.Geometry.LineSegmentIntersection.TypesNoExt+import Control.Lens hiding (contains)+import Data.Ext+import qualified Data.Foldable as F+import Data.Function (on)+import Data.Geometry.Interval+import Data.Geometry.LineSegment+import Data.Geometry.Point+import Data.Geometry.Properties+import qualified Data.List as L+import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Map as M+import Data.Maybe+import Data.Ord (Down(..), comparing)+import qualified Data.Set as EQ -- event queue+import qualified Data.Set as SS -- status struct+import qualified Data.Set.Util as SS -- status struct+import Data.Vinyl+import Data.Vinyl.CoRec++--------------------------------------------------------------------------------++-- | Compute all intersections+--+-- \(O((n+k)\log n)\), where \(k\) is the number of intersections.+intersections :: (Ord r, Fractional r)+ => [LineSegment 2 p r] -> Intersections p r+intersections ss = merge $ sweep pts SS.empty+ where+ pts = EQ.fromAscList . groupStarts . L.sort . concatMap asEventPts $ ss++-- | Computes all intersection points p s.t. p lies in the interior of at least+-- one of the segments.+--+-- \(O((n+k)\log n)\), where \(k\) is the number of intersections.+interiorIntersections :: (Ord r, Fractional r)+ => [LineSegment 2 p r] -> Intersections p r+interiorIntersections = M.filter isInteriorIntersection . intersections++-- | Computes the event points for a given line segment+asEventPts :: Ord r => LineSegment 2 p r -> [Event p r]+asEventPts s = let [p,q] = L.sortBy ordPoints [s^.start.core,s^.end.core]+ in [Event p (Start $ s :| []), Event q (End s)]++-- | Group the segments with the intersection points+merge :: (Ord r, Fractional r) => [IntersectionPoint p r] -> Intersections p r+merge = foldr (\(IntersectionPoint p a) -> M.insertWith (<>) p a) M.empty++-- | Group the startpoints such that segments with the same start point+-- correspond to one event.+groupStarts :: Eq r => [Event p r] -> [Event p r]+groupStarts [] = []+groupStarts (Event p (Start s) : es) = Event p (Start ss) : groupStarts rest+ where+ (ss',rest) = L.span sameStart es+ -- FIXME: this seems to keep the segments on decreasing y, increasing x. shouldn't we+ -- sort them cyclically around p instead?+ ss = let (x:|xs) = s+ in x :| (xs ++ concatMap startSegs ss')++ sameStart (Event q (Start _)) = p == q+ sameStart _ = False+groupStarts (e : es) = e : groupStarts es++--------------------------------------------------------------------------------+-- * Data type for Events++-- | Type of segment+data EventType s = Start !(NonEmpty s)| Intersection | End !s deriving (Show)++instance Eq (EventType s) where+ a == b = a `compare` b == EQ++instance Ord (EventType s) where+ (Start _) `compare` (Start _) = EQ+ (Start _) `compare` _ = LT+ Intersection `compare` (Start _) = GT+ Intersection `compare` Intersection = EQ+ Intersection `compare` (End _) = LT+ (End _) `compare` (End _) = EQ+ (End _) `compare` _ = GT++-- | The actual event consists of a point and its type+data Event p r = Event { eventPoint :: !(Point 2 r)+ , eventType :: !(EventType (LineSegment 2 p r))+ } deriving (Show,Eq)++instance Ord r => Ord (Event p r) where+ -- decreasing on the y-coord, then increasing on x-coord, and increasing on event-type+ (Event p s) `compare` (Event q t) = case ordPoints p q of+ EQ -> s `compare` t+ x -> x++-- | Get the segments that start at the given event point+startSegs :: Event p r -> [LineSegment 2 p r]+startSegs e = case eventType e of+ Start ss -> NonEmpty.toList ss+ _ -> []++--------------------------------------------------------------------------------+++--------------------------------------------------------------------------------+-- * The Main Sweep++type EventQueue p r = EQ.Set (Event p r)+type StatusStructure p r = SS.Set (LineSegment 2 p r)++-- | Run the sweep handling all events+sweep :: (Ord r, Fractional r)+ => EventQueue p r -> StatusStructure p r -> [IntersectionPoint p r]+sweep eq ss = case EQ.minView eq of+ Nothing -> []+ Just (e,eq') -> handle e eq' ss++isClosedStart :: Eq r => Point 2 r -> LineSegment 2 p r -> Bool+isClosedStart p (LineSegment s e)+ | p == s^.unEndPoint.core = isClosed s+ | otherwise = isClosed e+++-- data AssocKind b a = Start b a | End b a | Neighter a++-- -- | test if the given segment has p as its endpoint, an construct the+-- -- appropriate associated representing that.+-- mkAssociated :: Point 2 r -> LineSegment 2 p r -> AssocKind (LineSegment 2 p r)+-- mkAssociated p s@(LineSegment a b)+-- | p == a^.unEndPoint.core = Start a s+-- | p == b^.unEndPoint.core = End b s+-- | otherwise = Neighter s++-- -- -- | We need to report a segment as an segment for starting point p if+-- -- -- it is a closed segment starting at p, or an open segment starting+-- -- -- at p that intersects with some other segment. since the segments+-- -- -- are given in sorted order around s, we can just look at the next+-- -- -- segment to see if we should report such an open-ended segment.+-- -- shouldReportStart :: Point 2 r -> [LineSegment 2 p r] -> Associated p r+-- -- shouldReportStart p = go . map (categorize p)+-- -- where+-- -- go [] = mempty+-- -- go (s:ss) = let (xs,ys) = List.span overlapsWith s ss+-- -- in case s of+-- -- Start (Closed _) s' -> Asso+++++++-- -- (s@(LineSegment a b):ss)+-- -- | p == a^.unEndPoint.core =+++-- -- if isClosed a || overlapsWithNext ss+-- -- then Associated [s] [] [] <> go ss+-- -- -- | p == b^.unEndPoint.core = Associated [] [s] []++++++++-- _ [] = mempty+-- go certainlyReport (s:ss) = let x = mkAssociated p s+-- x' = then x else mempty+-- in++++-- case shouldReport mp s of++++++-- mkAssociated mp s <> go (Just s) ss+++-- mkAsscoiated _ s@(LineSegment a b)+-- | p == a^.unEndPoint.core = if isClosed a ||++++-- = Associated [s] [] []+-- | p == b^.unEndPoint.core = Associated [] [s] []+-- | otherwise = mempty++-- _ [] = []++++-- shouldReportStart _ [] = []+-- shouldReportStart p (s:ss) = case hasStartingPoint p s of+-- Nothing -> shouldReportStart ss -- don't report the seg+-- Just (Closed _, s) -> s : shouldReportStart ss+-- Just (Open _, )+++-- -- [s] | isClosedStart p s = [s]+-- -- | otherwise = []+-- -- shouldReportStart p (s:s':ss) | isStart p s =++++-- (s:ss) = isClosedStart p s ||+++-- shouldReport :: Eq r => Point 2 r -> [LineSegment 2 p r] -> Associated p r+-- shouldReport p = foldMap (\(s,c) -> case c of+-- Start' -> Associated [s] [] []+-- End' -> Associated [] [s] []+-- Neighter -> Associated [] [] [s]+-- )+-- . overlapsOr (\(LineSegment a b,c) -> case c of+-- Start' -> isClosed a+-- End' -> isClosed b+-- Neighter -> False+-- ) (overlap p)+-- . map (\s -> (s, categorize p s))++overlap :: Point 2 r -> (LineSegment 2 q r, Cat) -> (LineSegment 2 q r, Cat) -> Bool+overlap p s1 s2 = go (toStart s1) (toStart s2)+ where+ toStart (s@(LineSegment a b),c) = case c of+ Start' -> (s,False)+ End' -> (LineSegment b a,False) -- flip to start+ Neighter -> (s, True)+ go = undefined+++++data Cat = Start' | End' | Neighter++categorize p (LineSegment a b)+ | p == a^.unEndPoint.core = Start'+ | p == b^.unEndPoint.core = End'+ | otherwise = Neighter++++overlapsOr :: (a -> Bool)+ -> (a -> a -> Bool)+ -> [a]+ -> [a]+overlapsOr p q = map fst . filter snd . map (\((a,b),b') -> (a, b || b'))+ . overlapsWithNeighbour (q `on` fst)+ . map (\x -> (x, p x))++overlapsWithNeighbour :: (a -> a -> Bool) -> [a] -> [(a,Bool)]+overlapsWithNeighbour p = go0+ where+ go0 = \case+ [] -> []+ (x:xs) -> go x False xs++ go x b = \case+ [] -> []+ (y:ys) -> let b' = p x y+ in (x,b || b') : go y b' ys++++++++++annotateReport :: (a -> Bool) -> [a] -> [(a,Bool)]+annotateReport p = map (\x -> (x, p x))+++overlapsWithNext' :: (a -> a -> Bool) -> [a] -> [(a,Bool)]+overlapsWithNext' p = go+ where+ go = \case+ [] -> []+ [x] -> [(x,False)]+ (x:xs@(y:_)) -> (x,p x y) : go xs++overlapsWithPrev' :: (a -> a -> Bool) -> [a] -> [(a,Bool)]+overlapsWithPrev' p = go0+ where+ go0 = \case+ [] -> []+ (x:xs) -> (x,False) : go x xs++ go x = \case+ [] -> []+ (y:ys) -> (y,p x y) : go y ys+++++++overlapsWithNeighbour2 p = map (\((a,b),b') -> (a, b || b'))+ . overlapsWithNext' (p `on` fst)+ . overlapsWithPrev' p++shouldBe :: Eq a => a -> a -> Bool+shouldBe = (==)++propSameAsSeparate p xs = overlapsWithNeighbour p xs `shouldBe` overlapsWithNeighbour2 p xs++test' = overlapsWithNeighbour (==) testOverlapNext+testOverlapNext = [1,2,3,3,3,5,6,6,8,10,11,34,2,2,3]++-- reportOverlappingBy :: Eq a => (a -> Bool) -> [a] -> [a]+-- reportOverlappingBy p = \case+-- [] -> []+-- (x:xs) -> L.span+++-- | Handle an event point+handle :: forall r p. (Ord r, Fractional r)+ => Event p r -> EventQueue p r -> StatusStructure p r+ -> [IntersectionPoint p r]+handle e@(eventPoint -> p) eq ss = toReport <> sweep eq' ss'+ where+ starts = startSegs e+ (before,contains',after) = extractContains p ss+ (ends,contains) = L.partition (endsAt p) contains'+ -- starting segments, exluding those that have an open starting point+ starts' = filter (isClosedStart p) starts+++ -- starts'' = shouldReport p . SS.toAscList $ newSegs+ -- FIXME: we should look at the starts in-order (around p).+ -- closed endpoints we should report anyway. For an open endpoint+ -- we should check if it overlaps with a sucessor or predecessor+ -- to see if we have to report it.++ -- I think we could get those from the 'toStatusStruct' structure below++ -- any (closed) ending segments at this event point.+ closedEnds = filter (isClosedStart p) ends++ toReport = case starts' <> contains' of+ (_:_:_) -> [mkIntersectionPoint p (starts' <> closedEnds) contains]+ _ -> []++ -- new status structure+ ss' = before `SS.join` newSegs `SS.join` after+ newSegs = toStatusStruct p $ starts ++ contains+++ -- the new eeventqueue+ eq' = foldr EQ.insert eq es+ -- the new events:+ es | F.null newSegs = maybeToList $ app (findNewEvent p) sl sr+ | otherwise = let s' = SS.lookupMin newSegs+ s'' = SS.lookupMax newSegs+ in catMaybes [ app (findNewEvent p) sl s'+ , app (findNewEvent p) s'' sr+ ]+ sl = SS.lookupMax before+ sr = SS.lookupMin after++ app f x y = do { x' <- x ; y' <- y ; f x' y'}++-- | split the status structure, extracting the segments that contain p.+-- the result is (before,contains,after)+extractContains :: (Fractional r, Ord r)+ => Point 2 r -> StatusStructure p r+ -> (StatusStructure p r, [LineSegment 2 p r], StatusStructure p r)+extractContains p ss = (before, F.toList mid1 <> F.toList mid2, after)+ where+ (before, mid1, after') = SS.splitOn (xCoordAt $ p^.yCoord) (p^.xCoord) ss+ -- Make sure to also select the horizontal segments containing p+ (mid2, after) = SS.spanAntitone (intersects p) after'+++-- | Given a point and the linesegements that contain it. Create a piece of+-- status structure for it.+toStatusStruct :: (Fractional r, Ord r)+ => Point 2 r -> [LineSegment 2 p r] -> StatusStructure p r+toStatusStruct p xs = ss `SS.join` hors+ -- ss { SS.nav = ordAtNav $ p^.yCoord } `SS.join` hors+ where+ (hors',rest) = L.partition isHorizontal xs+ ss = SS.fromListBy (ordAtY $ maxY xs) rest+ hors = SS.fromListBy (comparing rightEndpoint) hors'++ isHorizontal s = s^.start.core.yCoord == s^.end.core.yCoord++ -- find the y coord of the first interesting thing below the sweep at y+ maxY = maximum . filter (< p^.yCoord)+ . concatMap (\s -> [s^.start.core.yCoord,s^.end.core.yCoord])++-- | Get the right endpoint of a segment+rightEndpoint :: Ord r => LineSegment 2 p r -> r+rightEndpoint s = (s^.start.core.xCoord) `max` (s^.end.core.xCoord)++-- | Test if a segment ends at p+endsAt :: Ord r => Point 2 r -> LineSegment 2 p r -> Bool+endsAt p (LineSegment' a b) = all (\q -> ordPoints (q^.core) p /= GT) [a,b]++--------------------------------------------------------------------------------+-- * Finding New events++-- | Find all events+findNewEvent :: (Ord r, Fractional r)+ => Point 2 r -> LineSegment 2 p r -> LineSegment 2 p r+ -> Maybe (Event p r)+findNewEvent p l r = match (l `intersect` r) $+ H (const Nothing) -- NoIntersection+ :& H (\q -> if ordPoints q p == GT then Just (Event q Intersection)+ else Nothing)+ :& H (const Nothing) -- full segment intersectsions are handled+ -- at insertion time+ :& RNil++++type R = Rational++seg1, seg2 :: LineSegment 2 () R+seg1 = ClosedLineSegment (ext $ Point2 0 0) (ext $ Point2 0 10)+seg2 = ClosedLineSegment (ext $ Point2 0 1) (ext $ Point2 0 5)
+ benchmark/Algorithms/Geometry/LineSegmentIntersection/BentleyOttmannOld.hs view
@@ -0,0 +1,225 @@+{-# LANGUAGE ScopedTypeVariables #-}+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.LineSegmentIntersection.BentleyOttmann+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--+-- The \(O((n+k)\log n)\) time line segment intersection algorithm by Bentley+-- and Ottmann.+--+--------------------------------------------------------------------------------+module Algorithms.Geometry.LineSegmentIntersection.BentleyOttmannOld where++import Algorithms.Geometry.LineSegmentIntersection.TypesNoExt( Intersections+ , IntersectionPoint(..)+ , Associated(..)+ , mkIntersectionPoint+ )+import Control.Lens hiding (contains)+import Data.Ext+import qualified Data.Foldable as F+import Data.Geometry.Interval+import Data.Geometry.LineSegment+import Data.Geometry.Point+import Data.Geometry.Properties+import qualified Data.List as L+import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Map as M+import Data.Maybe+import Data.Ord (Down(..), comparing)+import qualified Data.OrdSeq as SS -- status struct+import qualified Data.Set as EQ -- event queue+import Data.Vinyl+import Data.Vinyl.CoRec++--------------------------------------------------------------------------------++-- todo; use an old copy of the imports as well.++-- | Compute all intersections+--+-- \(O((n+k)\log n)\), where \(k\) is the number of intersections.+intersections :: (Ord r, Fractional r)+ => [LineSegment 2 p r] -> Intersections p r+intersections ss = merge $ sweep pts mempty+ where+ pts = EQ.fromAscList . groupStarts . L.sort . concatMap asEventPts $ ss++-- | Computes all intersection points p s.t. p lies in the interior of at least+-- one of the segments.+--+-- \(O((n+k)\log n)\), where \(k\) is the number of intersections.+interiorIntersections :: (Ord r, Fractional r)+ => [LineSegment 2 p r] -> Intersections p r+interiorIntersections = M.filter isInteriorIntersection . intersections++isInteriorIntersection :: Associated p r -> Bool+isInteriorIntersection = not . null . _interiorTo+++-- | Computes the event points for a given line segment+asEventPts :: Ord r => LineSegment 2 p r -> [Event p r]+asEventPts s = let [p,q] = L.sortBy ordPoints [s^.start.core,s^.end.core]+ in [Event p (Start $ s :| []), Event q (End s)]++-- | Group the segments with the intersection points+merge :: (Ord r, Fractional r) => [IntersectionPoint p r] -> Intersections p r+merge = foldr (\(IntersectionPoint p a) -> M.insertWith (<>) p a) M.empty++-- | Group the startpoints such that segments with the same start point+-- correspond to one event.+groupStarts :: Eq r => [Event p r] -> [Event p r]+groupStarts [] = []+groupStarts (Event p (Start s) : es) = Event p (Start ss) : groupStarts rest+ where+ (ss',rest) = L.span sameStart es+ -- sort the segs on lower endpoint+ ss = let (x:|xs) = s in x :| (xs ++ concatMap startSegs ss')++ sameStart (Event q (Start _)) = p == q+ sameStart _ = False+groupStarts (e : es) = e : groupStarts es++--------------------------------------------------------------------------------+-- * Data type for Events++-- | Type of segment+data EventType s = Start !(NonEmpty s)| Intersection | End !s deriving (Show)++instance Eq (EventType s) where+ a == b = a `compare` b == EQ++instance Ord (EventType s) where+ (Start _) `compare` (Start _) = EQ+ (Start _) `compare` _ = LT+ Intersection `compare` (Start _) = GT+ Intersection `compare` Intersection = EQ+ Intersection `compare` (End _) = LT+ (End _) `compare` (End _) = EQ+ (End _) `compare` _ = GT++-- | The actual event consists of a point and its type+data Event p r = Event { eventPoint :: !(Point 2 r)+ , eventType :: !(EventType (LineSegment 2 p r))+ } deriving (Show,Eq)++instance Ord r => Ord (Event p r) where+ -- decreasing on the y-coord, then increasing on x-coord, and increasing on event-type+ (Event p s) `compare` (Event q t) = case ordPoints p q of+ EQ -> s `compare` t+ x -> x++-- | An ordering that is decreasing on y, increasing on x+ordPoints :: Ord r => Point 2 r -> Point 2 r -> Ordering+ordPoints a b = let f p = (Down $ p^.yCoord, p^.xCoord) in comparing f a b++-- | Get the segments that start at the given event point+startSegs :: Event p r -> [LineSegment 2 p r]+startSegs e = case eventType e of+ Start ss -> NonEmpty.toList ss+ _ -> []++--------------------------------------------------------------------------------+-- * The Main Sweep++type EventQueue p r = EQ.Set (Event p r)+type StatusStructure p r = SS.OrdSeq (LineSegment 2 p r)++-- | Run the sweep handling all events+sweep :: (Ord r, Fractional r)+ => EventQueue p r -> StatusStructure p r -> [IntersectionPoint p r]+sweep eq ss = case EQ.minView eq of+ Nothing -> []+ Just (e,eq') -> handle e eq' ss++isClosedStart :: Eq r => Point 2 r -> LineSegment 2 p r -> Bool+isClosedStart p (LineSegment s e)+ | p == s^.unEndPoint.core = isClosed s+ | otherwise = isClosed e++-- | Handle an event point+handle :: forall r p. (Ord r, Fractional r)+ => Event p r -> EventQueue p r -> StatusStructure p r+ -> [IntersectionPoint p r]+handle e@(eventPoint -> p) eq ss = toReport <> sweep eq' ss'+ where+ starts = startSegs e+ (before,contains',after) = extractContains p ss+ (ends,contains) = L.partition (endsAt p) contains'+ -- starting segments, exluding those that have an open starting point+ starts' = filter (isClosedStart p) starts+ toReport = case starts' ++ contains' of+ (_:_:_) -> [mkIntersectionPoint p (starts' <> ends) contains]+ _ -> []++ -- new status structure+ ss' = before <> newSegs <> after+ newSegs = toStatusStruct p $ starts ++ contains++ -- the new eeventqueue+ eq' = foldr EQ.insert eq es+ -- the new events:+ es | F.null newSegs = maybeToList $ app (findNewEvent p) sl sr+ | otherwise = let s' = fst <$> SS.minView newSegs+ s'' = fst <$> SS.maxView newSegs+ in catMaybes [ app (findNewEvent p) sl s'+ , app (findNewEvent p) s'' sr+ ]+ sl = fst <$> SS.maxView before+ sr = fst <$> SS.minView after++ app f x y = do { x' <- x ; y' <- y ; f x' y'}++-- | split the status structure, extracting the segments that contain p.+-- the result is (before,contains,after)+extractContains :: (Fractional r, Ord r)+ => Point 2 r -> StatusStructure p r+ -> (StatusStructure p r, [LineSegment 2 p r], StatusStructure p r)+extractContains p ss = (before, F.toList $ mid1 <> mid2, after)+ where+ (before, mid1, after') = SS.splitOn (xCoordAt $ p^.yCoord) (p^.xCoord) ss+ -- Make sure to also select the horizontal segments containing p+ (mid2, after) = SS.splitMonotonic (not . intersects p) after'++-- | Given a point and the linesegements that contain it. Create a piece of+-- status structure for it.+toStatusStruct :: (Fractional r, Ord r)+ => Point 2 r -> [LineSegment 2 p r] -> StatusStructure p r+toStatusStruct p xs = ss <> hors+ -- ss { SS.nav = ordAtNav $ p^.yCoord } `SS.join` hors+ where+ (hors',rest) = L.partition isHorizontal xs+ ss = SS.fromListBy (ordAtY $ maxY xs) rest+ hors = SS.fromListBy (comparing rightEndpoint) hors'++ isHorizontal s = s^.start.core.yCoord == s^.end.core.yCoord++ -- find the y coord of the first interesting thing below the sweep at y+ maxY = maximum . filter (< p^.yCoord)+ . concatMap (\s -> [s^.start.core.yCoord,s^.end.core.yCoord])++-- | Get the right endpoint of a segment+rightEndpoint :: Ord r => LineSegment 2 p r -> r+rightEndpoint s = (s^.start.core.xCoord) `max` (s^.end.core.xCoord)++-- | Test if a segment ends at p+endsAt :: Ord r => Point 2 r -> LineSegment 2 p r -> Bool+endsAt p (LineSegment' a b) = all (\q -> ordPoints (q^.core) p /= GT) [a,b]++--------------------------------------------------------------------------------+-- * Finding New events++-- | Find all events+findNewEvent :: (Ord r, Fractional r)+ => Point 2 r -> LineSegment 2 p r -> LineSegment 2 p r+ -> Maybe (Event p r)+findNewEvent p l r = match (l `intersect` r) $+ (H $ \NoIntersection -> Nothing)+ :& (H $ \q -> if ordPoints q p == GT then Just (Event q Intersection)+ else Nothing)+ :& (H $ \_ -> Nothing) -- full segment intersectsions are handled+ -- at insertion time+ :& RNil
+ benchmark/Algorithms/Geometry/LineSegmentIntersection/TypesNoExt.hs view
@@ -0,0 +1,200 @@+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE TemplateHaskell #-}+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.LineSegmentIntersection.Types+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Algorithms.Geometry.LineSegmentIntersection.TypesNoExt where++-- import Algorithms.DivideAndConquer+import Control.DeepSeq+import Control.Lens+import Data.Ext+import Data.Bifunctor+import Data.Geometry.Interval+import Data.Geometry.LineSegment+import Data.Geometry.Point+import qualified Data.Map as Map+import qualified Data.Set as Set+import Data.Ord (comparing, Down(..))+import GHC.Generics+import Data.Vinyl.CoRec+import Data.Vinyl+import Data.Intersection+++----------------------------------------------------------------------------------+++-- FIXME: What do we do when one segmnet lies *on* the other one. For+-- the short segment it should be an "around start", but then the+-- startpoints do not match.+--+-- for the long one it's an "on" segment, but they do not intersect+++-- | Assumes that two segments have the same start point+newtype AroundStart a = AroundStart a deriving (Show,Read,NFData)++instance Eq r => Eq (AroundStart (LineSegment 2 p r)) where+ -- | equality on endpoint+ (AroundStart s) == (AroundStart s') = s^.end.core == s'^.end.core++instance (Ord r, Num r) => Ord (AroundStart (LineSegment 2 p r)) where+ -- | ccw ordered around their suposed common startpoint+ (AroundStart s) `compare` (AroundStart s') =+ ccwCmpAround (s^.start.core) (s^.end.core) (s'^.end.core)++----------------------------------------++-- | Assumes that two segments have the same end point+newtype AroundEnd a = AroundEnd a deriving (Show,Read,NFData)++instance Eq r => Eq (AroundEnd (LineSegment 2 p r)) where+ -- | equality on endpoint+ (AroundEnd s) == (AroundEnd s') = s^.start.core == s'^.start.core++instance (Ord r, Num r) => Ord (AroundEnd (LineSegment 2 p r)) where+ -- | ccw ordered around their suposed common end point+ (AroundEnd s) `compare` (AroundEnd s') =+ ccwCmpAround (s^.end.core) (s^.start.core) (s'^.start.core)++--------------------------------------------------------------------------------++-- | Assumes that two segments intersect in a single point.+newtype AroundIntersection a = AroundIntersection a deriving (Show,Read,NFData)++instance Eq r => Eq (AroundIntersection (LineSegment 2 p r)) where+ -- | equality ignores the p type+ (AroundIntersection s) == (AroundIntersection s') = first (const ()) s == first (const ()) s'++instance (Ord r, Fractional r) => Ord (AroundIntersection (LineSegment 2 p r)) where+ -- | ccw ordered around their common intersection point.+ l@(AroundIntersection s) `compare` r@(AroundIntersection s') = match (s `intersect` s') $+ H (\NoIntersection -> error "AroundIntersection: segments do not intersect!")+ :& H (\p -> cmpAroundP p s s')+ :& H (\_ -> (squaredLength s) `compare` (squaredLength s'))+ -- if s and s' just happen to be the same length but+ -- intersect in different behaviour from using (==).+ -- but that situation doese not satisfy the precondition+ -- of aroundIntersection anyway.+ :& RNil+ where+ squaredLength (LineSegment' a b) = squaredEuclideanDist (a^.core) (b^.core)++-- | compare around p+cmpAroundP :: (Ord r, Num r) => Point 2 r -> LineSegment 2 p r -> LineSegment 2 p r -> Ordering+cmpAroundP p s s' = ccwCmpAround p (s^.start.core) (s'^.start.core)+++-- seg1 = ClosedLineSegment (ext $ Point2 0 0) (ext $ Point2 0 10)+-- seg2 = ClosedLineSegment (ext $ Point2 0 0) (ext $ Point2 0 10)+++--------------------------------------------------------------------------------++++-- | The line segments that contain a given point p may either have p+-- as the endpoint or have p in their interior.+--+-- if somehow the segment is degenerate, and p is both the start and+-- end it is reported only as the start point.+data Associated p r =+ Associated { _startPointOf :: Set.Set (AroundEnd (LineSegment 2 p r))+ -- ^ segments for which the intersection point is the+ -- start point (i.e. s^.start.core == p)+ , _endPointOf :: Set.Set (AroundStart (LineSegment 2 p r))+ -- ^ segments for which the intersection point is the end+ -- point (i.e. s^.end.core == p)+ , _interiorTo :: Set.Set (AroundIntersection (LineSegment 2 p r))+ } deriving stock (Show, Read, Generic, Eq)++makeLenses ''Associated++++-- | Reports whether this associated has any interior intersections+--+-- \(O(1)\)+isInteriorIntersection :: Associated p r -> Bool+isInteriorIntersection = not . null . _interiorTo+++-- | test if the given segment has p as its endpoint, an construct the+-- appropriate associated representing that.+--+-- pre: p intersects the segment+mkAssociated :: (Ord r, Fractional r)+ => Point 2 r -> LineSegment 2 p r -> Associated p r+mkAssociated p s@(LineSegment a b)+ | p == a^.unEndPoint.core = mempty&startPointOf .~ Set.singleton (AroundEnd s)+ | p == b^.unEndPoint.core = mempty&endPointOf .~ Set.singleton (AroundStart s)+ | otherwise = mempty&interiorTo .~ Set.singleton (AroundIntersection s)+++-- | test if the given segment has p as its endpoint, an construct the+-- appropriate associated representing that.+--+-- If p is not one of the endpoints we concstruct an empty Associated!+--+mkAssociated' :: (Ord r, Fractional r) => Point 2 r -> LineSegment 2 p r -> Associated p r+mkAssociated' p s = (mkAssociated p s)&interiorTo .~ mempty++instance (Ord r, Fractional r) => Semigroup (Associated p r) where+ (Associated ss es is) <> (Associated ss' es' is') =+ Associated (ss <> ss') (es <> es') (is <> is')++instance (Ord r, Fractional r) => Monoid (Associated p r) where+ mempty = Associated mempty mempty mempty++instance (NFData p, NFData r) => NFData (Associated p r)++-- | For each intersection point the segments intersecting there.+type Intersections p r = Map.Map (Point 2 r) (Associated p r)++-- | An intersection point together with all segments intersecting at+-- this point.+data IntersectionPoint p r =+ IntersectionPoint { _intersectionPoint :: !(Point 2 r)+ , _associatedSegs :: !(Associated p r)+ } deriving (Show,Read,Eq,Generic)+makeLenses ''IntersectionPoint++instance (NFData p, NFData r) => NFData (IntersectionPoint p r)+++-- sameOrder :: (Ord r, Num r, Eq p) => Point 2 r+-- -> [LineSegment 2 p r] -> [LineSegment 2 p r] -> Bool+-- sameOrder c ss ss' = f ss == f ss'+-- where+-- f = map (^.extra) . sortAround' (ext c) . map (\s -> s^.end.core :+ s)+++++-- | Given a point p, and a bunch of segments that suposedly intersect+-- at p, correctly categorize them.+mkIntersectionPoint :: (Ord r, Fractional r)+ => Point 2 r+ -> [LineSegment 2 p r] -- ^ uncategorized+ -> [LineSegment 2 p r] -- ^ segments we know contain p,+ -> IntersectionPoint p r+mkIntersectionPoint p as cs = IntersectionPoint p $ foldMap (mkAssociated p) $ as <> cs++ -- IntersectionPoint p+ -- $ Associated mempty mempty (Set.fromAscList cs')+ -- <> foldMap (mkAssociated p) as+ -- where+ -- cs' = map AroundIntersection . List.sortBy (cmpAroundP p) $ cs+ -- -- TODO: In the bentley ottman algo we already know the sorted order of the segments+ -- -- so we can likely save the additional sort++++-- | An ordering that is decreasing on y, increasing on x+ordPoints :: Ord r => Point 2 r -> Point 2 r -> Ordering+ordPoints a b = let f p = (Down $ p^.yCoord, p^.xCoord) in comparing f a b
+ benchmark/Algorithms/Geometry/PolygonTriangulation/Bench.hs view
@@ -0,0 +1,64 @@+module Algorithms.Geometry.PolygonTriangulation.Bench where+{-+import Algorithms.Geometry.LineSegmentIntersection (hasSelfIntersections)+import qualified Algorithms.Geometry.PolygonTriangulation.MakeMonotone as New+import qualified Algorithms.Geometry.PolygonTriangulation.MakeMonotoneOld as Old+import Benchmark.Util+import Control.DeepSeq+import Control.Lens+import Data.Ext+import Test.Tasty.Bench+import qualified Data.Foldable as F+import Ipe+import Data.Geometry.LineSegment+import Data.Geometry.Polygon+import Data.Geometry.PlanarSubdivision+import Data.Geometry.Point+import qualified Data.LSeq as LSeq+import qualified Data.List as List+import Data.Proxy+import Test.QuickCheck++--------------------------------------------------------------------------------++data PX = PX++main :: IO ()+main = do+ polies <- getPolies "/home/frank/tmp/antarctica.ipe"+ defaultMain [ benchBuild polies ]++getPolies inFile = do+ ePage <- readSinglePageFile inFile+ case ePage of+ Left err -> error $ show err+ Right (page :: IpePage Rational) -> pure $ runPage page+ where+ runPage page =+ let polies = page^..content.to flattenGroups.traverse._withAttrs _IpePath _asSimplePolygon+ in filter (not . hasSelfIntersections . (^.core)) polies+++process f polies = let subdivs = map (\(pg :+ _) -> f (Identity PX) pg) polies+ in concatMap (\ps -> map (^._2.core) . F.toList . edgeSegments $ ps) subdivs++-- benchmark :: Benchmark+-- benchmark = bgroup "MakeMonotoneBench"+-- [ env (genPts (Proxy :: Proxy Rational) 100) benchBuild+-- ]++--------------------------------------------------------------------------------++-- | Benchmark computing the closest pair+benchBuild :: (Ord r, Fractional r, NFData r) => [Polygon t () r :+ p] -> Benchmark+benchBuild ss = bgroup "MakeMonotone" [ bgroup (show n) (build $ take n ss)+ | n <- sizes' ss+ ]+ where+ sizes' xs = [length xs]+ -- let n = length pts in [ n*i `div` 100 | i <- [10,20,25,50,75,100]]++ build ps = [ bench "Old" $ nf (process Old.makeMonotone) ps+ , bench "New" $ nf (process New.makeMonotone) ps+ ]+-}
+ benchmark/Algorithms/Geometry/PolygonTriangulation/MakeMonotoneOld.hs view
@@ -0,0 +1,311 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+module Algorithms.Geometry.PolygonTriangulation.MakeMonotoneOld where++import Algorithms.Geometry.PolygonTriangulation.Types++import Control.Lens+import Control.Monad (forM_, when)+import Control.Monad.Reader+import Control.Monad.State.Strict+import Control.Monad.Writer (WriterT, execWriterT, tell)+import Data.Bifunctor+import Data.CircularSeq (rotateL, rotateR, zip3LWith)+import qualified Data.DList as DList+import Data.Ext+import qualified Data.Foldable as F+import Data.Geometry.LineSegment+import Data.Geometry.PlanarSubdivision+import Data.Geometry.Point+import Data.Geometry.Polygon+import qualified Data.IntMap as IntMap+import qualified Data.List.NonEmpty as NonEmpty+import Data.Ord (Down (..), comparing)+import Data.OrdSeq (OrdSeq)+import qualified Data.OrdSeq as SS+import Data.Util+import qualified Data.Vector as V+import qualified Data.Vector.Circular as CV+import qualified Data.Vector.Mutable as MV+++----------------------------------------------------------------------------------++data VertexType = Start | Merge | Split | End | Regular deriving (Show,Read,Eq)+++-- How about the hole vertices?++-- | assigns a vertex type to each vertex+--+-- pre: the polygon is given in CCW order+--+-- running time: \(O(n)\).+classifyVertices :: (Num r, Ord r)+ => Polygon t p r+ -> Polygon t (p :+ VertexType) r+classifyVertices p@SimplePolygon{} = classifyVertices' p+classifyVertices (MultiPolygon vs h) = MultiPolygon vs' h'+ where+ vs' = classifyVertices' vs+ h' = map (first (&extra %~ onHole) . classifyVertices') h++ -- the roles on hole vertices are slightly different+ onHole Start = Split+ onHole Merge = End+ onHole Split = Start+ onHole End = Merge+ onHole Regular = Regular++-- | assigns a vertex type to each vertex+--+-- pre: the polygon is given in CCW order+--+-- running time: \(O(n)\).+classifyVertices' :: (Num r, Ord r)+ => SimplePolygon p r+ -> SimplePolygon (p :+ VertexType) r+classifyVertices' poly =+ -- SimplePolygon $ zip3LWith f (rotateL vs) vs (rotateR vs)+ unsafeFromCircularVector $ CV.zipWith3 f (CV.rotateLeft 1 vs) vs (CV.rotateRight 1 vs)+ where+ vs = poly ^. outerBoundaryVector+ -- is the angle larger than > 180 degrees+ largeInteriorAngle p c n = case ccw (p^.core) (c^.core) (n^.core) of+ CCW -> False+ CW -> True+ _ -> error "classifyVertices -> largeInteriorAngle: colinear points"++ f p c n = c&extra %~ (:+ vt)+ where+ vt = case (p `cmpSweep` c, n `cmpSweep` c, largeInteriorAngle p c n) of+ (LT, LT, False) -> Start+ (LT, LT, True) -> Split+ (GT, GT, False) -> End+ (GT, GT, True) -> Merge+ _ -> Regular++++-- | p < q = p.y < q.y || p.y == q.y && p.x > q.y+cmpSweep :: Ord r => Point 2 r :+ e -> Point 2 r :+ e -> Ordering+p `cmpSweep` q =+ comparing (^.core.yCoord) p q <> comparing (Down . (^.core.xCoord)) p q+++--------------------------------------------------------------------------------++type Event r = Point 2 r :+ (Two (LineSegment 2 Int r))++data StatusStruct r = SS { _statusStruct :: !(SS.OrdSeq (LineSegment 2 Int r))+ , _helper :: !(IntMap.IntMap Int)+ -- ^ for every e_i, the id of the helper vertex+ } deriving (Show)+makeLenses ''StatusStruct++ix' :: Int -> Lens' (V.Vector a) a+ix' i = singular (ix i)++-- | Given a polygon, find a set of non-intersecting diagonals that partition+-- the polygon into y-monotone pieces.+--+-- running time: \(O(n\log n)\)+computeDiagonals :: forall t r p. (Fractional r, Ord r)+ => Polygon t p r -> [LineSegment 2 p r]+computeDiagonals p' = map f . sweep+ . NonEmpty.sortBy (flip cmpSweep)+ . polygonVertices . withIncidentEdges+ . first (^._1) $ pg+ where+ -- remaps to get the p value rather than the vertexId+ f = first (\i -> vertexInfo^.ix' i._2)++ pg :: Polygon t (SP Int (p :+ VertexType)) r+ pg = numberVertices . classifyVertices . toCounterClockWiseOrder $ p'+ vertexInfo :: V.Vector (STR (Point 2 r) p VertexType)+ vertexInfo = let vs = polygonVertices pg+ n = F.length vs+ in V.create $ do+ v <- MV.new n+ forM_ vs $ \(pt :+ SP i (p :+ vt)) ->+ MV.write v i (STR pt p vt)+ return v++ initialSS = SS mempty mempty++ sweep es = flip runReader vertexInfo $ evalStateT (sweep' es) initialSS+ sweep' es = DList.toList <$> execWriterT (sweep'' es)++ sweep'' :: NonEmpty.NonEmpty (Event r) -> Sweep p r ()+ sweep'' = mapM_ handle++-- | Computes a set of diagionals that decompose the polygon into y-monotone+-- pieces.+--+-- running time: \(O(n\log n)\)+makeMonotone :: forall proxy s t p r. (Fractional r, Ord r)+ => proxy s -> Polygon t p r+ -> PlanarSubdivision s p PolygonEdgeType PolygonFaceData r+makeMonotone _ pg = let (e:es) = listEdges pg+ in constructSubdivision @s e es (computeDiagonals pg)++type Sweep p r = WriterT (DList.DList (LineSegment 2 Int r))+ (StateT (StatusStruct r)+ (Reader (V.Vector (VertexInfo p r))))++type VertexInfo p r = STR (Point 2 r) p VertexType+++tell' :: LineSegment 2 Int r -> Sweep p r ()+tell' = tell . DList.singleton++getIdx :: Event r -> Int+getIdx = view (extra._1.end.extra)++getVertexType :: Int -> Sweep p r VertexType+getVertexType v = asks (^.ix' v._3)++getEventType :: Event r -> Sweep p r VertexType+getEventType = getVertexType . getIdx++handle :: (Fractional r, Ord r) => Event r -> Sweep p r ()+handle e = let i = getIdx e in getEventType e >>= \case+ Start -> handleStart i e+ End -> handleEnd i e+ Split -> handleSplit i e+ Merge -> handleMerge i e+ Regular | isLeftVertex i e -> handleRegularL i e+ | otherwise -> handleRegularR i e+++insertAt :: (Ord r, Fractional r) => Point 2 r -> LineSegment 2 q r+ -> OrdSeq (LineSegment 2 q r) -> OrdSeq (LineSegment 2 q r)+insertAt v = SS.insertBy (ordAtY $ v^.yCoord)++deleteAt :: (Fractional r, Ord r) => Point 2 r -> LineSegment 2 p r+ -> OrdSeq (LineSegment 2 p r) -> OrdSeq (LineSegment 2 p r)+deleteAt v = SS.deleteAllBy (ordAtY $ v^.yCoord)+++handleStart :: (Fractional r, Ord r)+ => Int -> Event r -> Sweep p r ()+handleStart i (v :+ adj) = modify $ \(SS t h) ->+ SS (insertAt v (adj^._2) t)+ (IntMap.insert i i h)++handleEnd :: (Fractional r, Ord r)+ => Int -> Event r -> Sweep p r ()+handleEnd i (v :+ adj) = do let iPred = adj^._1.start.extra -- i-1+ -- lookup p's helper; if it is a merge vertex+ -- we insert a new segment+ tellIfMerge i v iPred+ -- delete e_{i-1} from the status struct+ modify $ \ss ->+ ss&statusStruct %~ deleteAt v (adj^._1)++-- | Adds edge (i,j) if e_j's helper is a merge vertex+tellIfMerge :: Int -> Point 2 r -> Int -> Sweep p r ()+tellIfMerge i v j = do SP u ut <- getHelper j+ when (ut == Merge) (tell' $ ClosedLineSegment (v :+ i) u)++-- | Get the helper of edge i, and its vertex type+getHelper :: Int -> Sweep p r (SP (Point 2 r :+ Int) VertexType)+getHelper i = do ui <- gets (^?!helper.ix i)+ STR u _ ut <- asks (^.ix' ui)+ pure $ SP (u :+ ui) ut+++lookupLE :: (Ord r, Fractional r)+ => Point 2 r -> OrdSeq (LineSegment 2 Int r)+ -> Maybe (LineSegment 2 Int r)+lookupLE v s = let (l,m,_) = SS.splitOn (xCoordAt $ v^.yCoord) (v^.xCoord) s+ in SS.lookupMax (l <> m)+++handleSplit :: (Fractional r, Ord r) => Int -> Event r -> Sweep p r ()+handleSplit i (v :+ adj) = do ej <- gets $ \ss -> ss^?!statusStruct.to (lookupLE v)._Just+ let j = ej^.start.extra+ SP u _ <- getHelper j+ -- update the status struct:+ -- insert the new edge into the status Struct and+ -- set the helper of e_j to be v_i+ modify $ \(SS t h) ->+ SS (insertAt v (adj^._2) t)+ (IntMap.insert i i . IntMap.insert j i $ h)+ -- return the diagonal+ tell' $ ClosedLineSegment (v :+ i) u++handleMerge :: (Fractional r, Ord r) => Int -> Event r -> Sweep p r ()+handleMerge i (v :+ adj) = do let ePred = adj^._1.start.extra -- i-1+ tellIfMerge i v ePred+ -- delete e_{i-1} from the status struct+ modify $ \ss -> ss&statusStruct %~ deleteAt v (adj^._1)+ connectToLeft i v++-- | finds the edge j to the left of v_i, and connect v_i to it if the helper+-- of j is a merge vertex+connectToLeft :: (Fractional r, Ord r) => Int -> Point 2 r -> Sweep p r ()+connectToLeft i v = do ej <- gets $ \ss -> ss^?!statusStruct.to (lookupLE v)._Just+ let j = ej^.start.extra+ tellIfMerge i v j+ modify $ \ss -> ss&helper %~ IntMap.insert j i++-- | returns True if v the interior of the polygon is to the right of v+isLeftVertex :: Ord r => Int -> Event r -> Bool+isLeftVertex i (v :+ adj) = case (adj^._1.start) `cmpSweep` (v :+ i) of+ GT -> True+ _ -> False+ -- if the predecessor occurs before the sweep, this must be a left vertex++handleRegularL :: (Fractional r, Ord r) => Int -> Event r -> Sweep p r ()+handleRegularL i (v :+ adj) = do let ePred = adj^._1.start.extra -- i-1+ tellIfMerge i v ePred+ -- delete e_{i-1} from the status struct+ modify $ \ss ->+ ss&statusStruct %~ deleteAt v (adj^._1)+ -- insert a e_i in the status struct, and set its helper+ -- to be v_i+ modify $ \(SS t h) ->+ SS (insertAt v (adj^._2) t)+ (IntMap.insert i i h)++handleRegularR :: (Fractional r, Ord r) => Int -> Event r -> Sweep p r ()+handleRegularR i (v :+ _) = connectToLeft i v+++++--------------------------------------------------------------------------------+++-- testPolygon :: SimplePolygon Int Rational+-- testPolygon = fromPoints [ Point2 20 20 :+ 1+-- , Point2 18 19 :+ 2+-- , Point2 16 25 :+ 3+-- , Point2 13 23 :+ 4+-- , Point2 10 24 :+ 5+-- , Point2 6 22 :+ 6+-- , Point2 8 21 :+ 7+-- , Point2 7 18 :+ 8+-- , Point2 2 19 :+ 9+-- , Point2 1 10 :+ 10+-- , Point2 3 5 :+ 11+-- , Point2 11 7 :+ 12+-- , Point2 15 1 :+ 13+-- , Point2 12 15 :+ 14+-- , Point2 15 12 :+ 15+-- ]++-- vertexTypes = [Start,Merge,Start,Merge,Start,Regular,Regular,Merge,Start,Regular,End,Split,End,Split,End]+++-- loadT = do pgs <- readAllFrom "/Users/frank/tmp/testPoly.ipe"+-- :: IO [SimplePolygon () Rational :+ IpeAttributes Path Rational]+-- mapM_ print pgs+-- let diags = map (computeDiagonals . (^.core)) pgs+-- f = asIpeGroup . map (asIpeObject' mempty)+-- out = [ asIpeGroup $ map (\(pg :+ a) -> asIpeObject pg a) pgs+-- , asIpeGroup $ map f diags+-- ]+-- outFile = "/Users/frank/tmp/out.ipe"+-- writeIpeFile outFile . singlePageFromContent $ out
+ benchmark/Benchmark/Util.hs view
@@ -0,0 +1,7 @@+module Benchmark.Util where++++-- | Generates different size benchmarks+sizes :: Foldable f => f a -> [Int]+sizes xs = let n = length xs in (\i -> n*i `div` 100) <$> [5,10..100]
+ benchmark/Benchmarks.hs view
@@ -0,0 +1,10 @@+module Main where++import qualified Algorithms.Geometry.ClosestPair.Bench as CP+import qualified Algorithms.Geometry.LineSegmentIntersection.Bench as Line+-- import qualified Algorithms.Geometry.PolygonTriangulation.Bench as M+import qualified Algorithms.Geometry.ConvexHull.Bench as M+import Test.Tasty.Bench++main :: IO ()+main = defaultMain [ CP.benchmark, M.benchmark, Line.benchmark ]
+ benchmark/Data/Geometry/IntervalTreeBench.hs view
@@ -0,0 +1,75 @@+module Data.Geometry.IntervalTreeBench where++import Benchmark.Util+import Control.DeepSeq+import Control.Lens+import Test.Tasty.Bench+import Data.Ext+import Data.Geometry.Interval+import qualified Data.Geometry.IntervalTree as IT+import Data.Geometry.SegmentTree (I(..))+import qualified Data.Geometry.SegmentTree as SegTree+import qualified Data.List.NonEmpty as NonEmpty+import Debug.Trace+import Test.QuickCheck++--------------------------------------------------------------------------------++main :: IO ()+main = defaultMain [ intervalBench ]++intervalBench :: Benchmark+intervalBench = bgroup "IntervalTree"+ [ -- env (genIntervals (I (5 :: Int)) 1000) benchBuild+ -- env (genIntervals (I (5 :: Int)) 100) benchQueryIT+ ]++--------------------------------------------------------------------------------++-- | generates n random intervals+genIntervals :: (Ord r, Arbitrary r)+ => proxy r -> Int -> IO [Interval () r]+genIntervals _ n | n <= 0 = error "genIntervals: need n > 0"+ | otherwise = generate (vectorOf n arbitrary)++genQueries :: (Ord r, Arbitrary r)+ => proxy r -> Int -> IO [r]+genQueries _ n | n <= 0 = error "genQueries: need n > 0"+ | otherwise = generate (vectorOf n arbitrary)+++-- genQuerySetup :: (Ord r, Arbitrary r)+-- => proxy r -> Int -> IO (Int,IT.IntervalTree (I (Interval () r)) r, [r])+-- genQuerySetup p n = (\is qs -> (n, IT.fromIntervals . fmap I $ is, qs))+-- <$> genIntervals p n+-- <*> genQueries p n+++-- | Benchmark building the interval tree+benchBuild :: (Ord r, NFData r) => [Interval () r] -> Benchmark+benchBuild is = bgroup "build" [ bench (show n) $ nf IT.fromIntervals (take n is')+ | n <- sizes is+ ]+ where+ is' = I <$> is++-- benchQueryIT :: (Ord r, Arbitrary r, NFData r) => [Interval () r] -> Benchmark+-- benchQueryIT is = bgroup "queries"+-- [ env (setup' n) (\(t,qs) ->+-- bench ("queries on size" ++ show n) $ whnf (queryAll t) qs)+-- | n <- sizes is+-- ]+-- where+-- is' = I <$> is+-- r = is^.to head.start.core+-- setup' n = traceShow "setup" $ setup n++-- setup n = (IT.fromIntervals (take n is'),) <$> genQueries (I r) 100000+-- queryAll t = map (flip IT.search t)+++-- benchQueryIT :: Ord r+-- => (Int, IT.IntervalTree (I (Interval () r)) r, [r]) -> Benchmark+-- benchQueryIT (n,t,qs) = bgroup "queries" [ bench "query" $ whnf (flip IT.search t) q+-- | q <- qs+-- ]
+ benchmark/Data/Geometry/Vector/VectorFamily6.hs view
@@ -0,0 +1,257 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE UndecidableInstances #-}+module Data.Geometry.Vector.VectorFamily6 where++import Control.Applicative (liftA2)+import Control.DeepSeq+import Control.Lens hiding (element)+-- import Data.Aeson (ToJSON(..),FromJSON(..))+import qualified Data.Foldable as F+import qualified Data.Geometry.Vector.VectorFixed as FV+import Data.Maybe (fromMaybe)+import Data.Proxy+import Data.Traversable (foldMapDefault,fmapDefault)+import qualified Data.Vector.Fixed as V+import Data.Vector.Fixed.Cont (Peano(..), PeanoNum(..), Fun(..))+import GHC.TypeLits+import Linear.Affine (Affine(..))+import Linear.Metric+import qualified Linear.V2 as L2+import qualified Linear.V3 as L3+import qualified Linear.V4 as L4+import Linear.Vector++--------------------------------------------------------------------------------+-- * d dimensional Vectors+++type One = S Z+type Two = S One+type Three = S Two+type Four = S Three+type Many d = S (S (S (S (S d))))+++type family FromPeano (d :: PeanoNum) :: Nat where+ FromPeano Z = 0+ FromPeano (S d) = 1 + FromPeano d+++data SingPeano (d :: PeanoNum) where+ SZ :: SingPeano Z+ SS :: !(SingPeano d) -> SingPeano (S d)++class ImplicitPeano (d :: PeanoNum) where+ implicitPeano :: SingPeano d+instance ImplicitPeano Z where+ implicitPeano = SZ+instance ImplicitPeano d => ImplicitPeano (S d) where+ implicitPeano = SS implicitPeano++-- | Mapping between the implementation type, and the actual implementation.+type family VectorFamilyF (d :: PeanoNum) :: * -> * where+ VectorFamilyF Z = Const ()+ VectorFamilyF One = Identity+ VectorFamilyF Two = L2.V2+ VectorFamilyF Three = L3.V3+ VectorFamilyF Four = L4.V4+ VectorFamilyF (Many d) = FV.Vector (FromPeano (Many d))+++-- | Datatype representing d dimensional vectors. The default implementation is+-- based n VectorFixed. However, for small vectors we automatically select a+-- more efficient representation.+newtype VectorFamily (d :: PeanoNum) (r :: *) =+ VectorFamily { _unVF :: VectorFamilyF d r }++type ImplicitArity d = (ImplicitPeano d, V.Arity (FromPeano d))++++instance (Eq r, ImplicitArity d) => Eq (VectorFamily d r) where+ (VectorFamily u) == (VectorFamily v) = case (implicitPeano :: SingPeano d) of+ SZ -> u == v+ (SS SZ) -> u == v+ (SS (SS SZ)) -> u == v+ (SS (SS (SS SZ))) -> u == v+ (SS (SS (SS (SS SZ)))) -> u == v+ (SS (SS (SS (SS (SS _))))) -> u == v+ {-# INLINE (==) #-}++instance (Ord r, ImplicitArity d) => Ord (VectorFamily d r) where+ (VectorFamily u) `compare` (VectorFamily v) = case (implicitPeano :: SingPeano d) of+ SZ -> u `compare` v+ (SS SZ) -> u `compare` v+ (SS (SS SZ)) -> u `compare` v+ (SS (SS (SS SZ))) -> u `compare` v+ (SS (SS (SS (SS SZ)))) -> u `compare` v+ (SS (SS (SS (SS (SS _))))) -> u `compare` v+ {-# INLINE compare #-}+++instance ImplicitArity d => Functor (VectorFamily d) where+ fmap f = VectorFamily . g f . _unVF+ where g = case (implicitPeano :: SingPeano d) of+ SZ -> fmap+ (SS SZ) -> fmap+ (SS (SS SZ)) -> fmap+ (SS (SS (SS SZ))) -> fmap+ (SS (SS (SS (SS SZ)))) -> fmap+ (SS (SS (SS (SS (SS _))))) -> fmap+ {-# INLINE fmap #-}+++instance ImplicitArity d => Foldable (VectorFamily d) where+ foldMap f = g f . _unVF+ where g = case (implicitPeano :: SingPeano d) of+ SZ -> foldMap+ (SS SZ) -> foldMap+ (SS (SS SZ)) -> foldMap+ (SS (SS (SS SZ))) -> foldMap+ (SS (SS (SS (SS SZ)))) -> foldMap+ (SS (SS (SS (SS (SS _))))) -> foldMap+ {-# INLINE foldMap #-}++instance ImplicitArity d => Traversable (VectorFamily d) where+ traverse f = fmap VectorFamily . g f . _unVF+ where g = case (implicitPeano :: SingPeano d) of+ SZ -> traverse+ (SS SZ) -> traverse+ (SS (SS SZ)) -> traverse+ (SS (SS (SS SZ))) -> traverse+ (SS (SS (SS (SS SZ)))) -> traverse+ (SS (SS (SS (SS (SS _))))) -> traverse+ {-# INLINE traverse #-}++instance ImplicitArity d => Applicative (VectorFamily d) where+ pure = VectorFamily . case (implicitPeano :: SingPeano d) of+ SZ -> pure+ (SS SZ) -> pure+ (SS (SS SZ)) -> pure+ (SS (SS (SS SZ))) -> pure+ (SS (SS (SS (SS SZ)))) -> pure+ (SS (SS (SS (SS (SS _))))) -> pure+ {-# INLINE pure #-}+ liftA2 f (VectorFamily u) (VectorFamily v) = VectorFamily $+ case (implicitPeano :: SingPeano d) of+ SZ -> liftA2 f u v+ (SS SZ) -> liftA2 f u v+ (SS (SS SZ)) -> liftA2 f u v+ (SS (SS (SS SZ))) -> liftA2 f u v+ (SS (SS (SS (SS SZ)))) -> liftA2 f u v+ (SS (SS (SS (SS (SS _))))) -> liftA2 f u v+ {-# INLINE liftA2 #-}+++++type instance V.Dim (VectorFamily d) = FromPeano d+++++instance ImplicitArity d => V.Vector (VectorFamily d) r where+ construct = fmap VectorFamily $ case (implicitPeano :: SingPeano d) of+ SZ -> Fun $ Const ()+ (SS SZ) -> V.construct+ (SS (SS SZ)) -> Fun L2.V2+ (SS (SS (SS SZ))) -> Fun L3.V3+ (SS (SS (SS (SS SZ)))) -> Fun L4.V4+ (SS (SS (SS (SS (SS _))))) -> V.construct+ {-# INLINE construct #-}+ inspect (VectorFamily v) ff@(Fun f) = case (implicitPeano :: SingPeano d) of+ SZ -> f+ (SS SZ) -> V.inspect v ff+ (SS (SS SZ)) -> let (L2.V2 x y) = v in f x y+ (SS (SS (SS SZ))) -> let (L3.V3 x y z) = v in f x y z+ (SS (SS (SS (SS SZ)))) -> let (L4.V4 x y z w) = v in f x y z w+ (SS (SS (SS (SS (SS _))))) -> V.inspect v ff+ {-# INLINE inspect #-}+ -- basicIndex (VectorFamily v) i = case (implicitPeano :: SingPeano d) of+ -- SZ -> err+ -- (SS SZ) -> if i == 0 then runIdentity v else err+ -- (SS (SS SZ)) -> let (L2.V2 x y) = v in f x y+ -- (SS (SS (SS SZ))) -> let (L3.V3 x y z) = v in f x y z+ -- (SS (SS (SS (SS SZ)))) -> let (L4.V4 x y z w) = v in f x y z w+ -- (SS (SS (SS (SS (SS _))))) -> V.basicIndex v i+ -- where+ -- err = error "VectorFamily: basicIndex out of range"+ -- {-# INLINE basicIndex #-}+++instance (ImplicitArity d, Show r) => Show (VectorFamily d r) where+ show v = mconcat [ "Vector", show $ F.length v , " "+ , show $ F.toList v ]++deriving instance (NFData (VectorFamilyF d r)) => NFData (VectorFamily d r)+++type instance Index (VectorFamily d r) = Int+type instance IxValue (VectorFamily d r) = r++--------------------------------------------------------------------------------+++newtype Vector (d :: Nat) (r :: *) = MKVector { _unV :: VectorFamily (Peano d) r }++type instance V.Dim (Vector d) = d+++type instance Index (Vector d r) = Int+type instance IxValue (Vector d r) = r++type Arity d = ImplicitArity (Peano d)++deriving instance (Eq r, Arity d) => Eq (Vector d r)+deriving instance (Ord r, Arity d) => Ord (Vector d r)++deriving instance Arity d => Functor (Vector d)+deriving instance Arity d => Foldable (Vector d)+deriving instance Arity d => Traversable (Vector d)++instance (Arity d, Show r) => Show (Vector d r) where+ show v = mconcat [ "Vector", show $ F.length v , " "+ , show $ F.toList v ]+++deriving instance (NFData (VectorFamily (Peano d) r)) => NFData (Vector d r)+++++--------------------------------------------------------------------------------+-- * Convenience "constructors"++pattern Vector :: VectorFamilyF (Peano d) r -> Vector d r+pattern Vector v = MKVector (VectorFamily v)++pattern Vector1 :: r -> Vector 1 r+pattern Vector1 x = (Vector (Identity x))++pattern Vector2 :: r -> r -> Vector 2 r+pattern Vector2 x y = (Vector (L2.V2 x y))++pattern Vector3 :: r -> r -> r -> Vector 3 r+pattern Vector3 x y z = (Vector (L3.V3 x y z))++pattern Vector4 :: r -> r -> r -> r -> Vector 4 r+pattern Vector4 x y z w = (Vector (L4.V4 x y z w))++--------------------------------------------------------------------------------++-- -- destruct :: (Vec d r, Vec (d + 1) r, 1 <= (d + 1))+-- -- => Vector (d + 1) r -> (r, Vector d r)+-- -- destruct (Vector v) = (V.head v, Vector $ V.tail v)+++-- -- -- vectorFromList :: Arity d => [a] -> Maybe (Vector d a)+-- -- vectorFromList = fmap Vector . V.fromListM++-- -- vectorFromListUnsafe :: V.Arity d => [a] -> Vector d a+-- -- vectorFromListUnsafe = Vector . V.fromList++ --------------------------------------------------------------------------------++-- | Cross product of two three-dimensional vectors+cross :: Num r => Vector 3 r -> Vector 3 r -> Vector 3 r+(Vector u) `cross` (Vector v) = Vector $ u `L3.cross` v
+ changelog view
@@ -0,0 +1,199 @@+#+STARTUP: showeverything++* Changelog++** 0.14++- Allow the associated/extra data of linesegments and intervals to+ differ when testing for intersections.+- Intersection testing between line segments and rectangles+- Testing if lines and/or line segments intersect no longer requires a+ Fractional constraint; Num is sufficient. However, in turn we now do+ need Ord rather than just Eq. That seemed a worthwile tradeoff though.+- Cleaning up the public API by hiding several internal modules.+- Introduced the 'HasSquaredEuclideanDistance' class describing+ geometry types for which we can compute the squared distance from a+ point to a geometry, and added instances for some of the basic+ geometries.+- Fixed a bug in computing lengths to open line segments.+- Removed some proxy arguments, in e.g. Data.Geometry.Point.coord,+ rather than take a Proxy to specify which coordinate we want, use+ type applications.+- Support for GHC 9.0 and 9.2+- Better support for open-ended line segments in the Bentley Ottmann+ line segment intersection algorithm.++** 0.13++- Moved 'intersects' from the HasIntersectionWith class into a new+ class IsIntersectableWith. This allows separate (weaker) constraints+ for checking *if* geometries intersect rather than computing exact+ intersections.+- New BezierSpline features.+- "Zoom to fit" transformation.+- Many fixes related to PlaneGraph/PlanarSubdivison; i.e. bugs in+ which order the vertices/darts where reported when traversing a+ face. The polygon representing the outer boundary now is some area+ inside a bounding polygon.+- Fixed a bug in the DelaunayTriangulation.+- Preliminary implementations for updating planar subdivisions+ (e.g. subdividing edges).++** 0.12++- New website: https://hgeometry.org/+- Switch polygon implementation from a circular seq to a circular vector.+- Hide polygon implementation details.+- Enforce CCW polygon order by default.+- Fix bug in Data.Geometry.Polygon.Convex.extremes/maxInDirection.+- Fix bug in pointInPolygon in case of degenerate situations.+- Fix Read/Show instances for Point and Polygon such that 'read.show = id'.+- Improved numerical robustness.+- Random generation of monotone polygons. Thanks to @1ndy.+- Random and uniform generation of convex polygons.+- More IsIntersectableWith instances+- Updated Show/Read instances for LineSegments+- New algorithm: Visibility polygon in O(n log n) time.+- New algorithm: Earclip triangulation in O(n^2) time worst case, O(n)+ time expected case.+- New algorithm: Single-source shortest path in O(n) time.+- New algorithm: Planar point locator in O(log n) time.+- New algorithm: Point set diameter in O(n log n) time.+- New algorithm: Convex hull of a polygon in O(n) time.+- New algorithm: Diameter of a convex polygon in O(n) time.+- New algorithm: Check if a point lies inside a convex polygon in O(n)+ time.+- New algorithm: Discrete Frechet distance in O(n^2) time.++** 0.11++- Removed Functor instance from Triangle and replaced it with Bifunctor/Bifoldable/Bitraversable+- Testing if a point lies above/below a line is now in a typeclass,+ moreover there now is also an instance of this typeclass for+ planes. Hence, we can test if a point in R^3 lies above or below a+ plane.+- Bugfixes in the incomingEdges and outgoingEdges functions in+ Planar/Plane graphs and Planar subdivisions+- Added separate data types for Sides and Corners of Rectangles.+- More functionality for working with Halfspaces+- Fixed a bug in computing the intersection of overlapping+ linesegments+- PolyLine.fromPoints now returns a Maybe PolyLine rather than a+ Polyine. Use fromPointsUnsafe for the old behavior.+- Interval now no longer exports its constructor. Use the provided+ patterns instead.+- Added an OpenLineSegment pattern/constructor+- The corners and sides functions in Box now return specific types+ representing those rather than four tuples.+- Added a BezierSpline module and data type (Thanks to Maarten).+- Added a QuadTree implementation. It can be built from a set of+ points, and to represent the zeroset of some function.+- Added a Naive implementation of Convex hull in R^3. Note however+ that it works only for points in general position. In particular, no+ four points should be coplanar.+- Added a Data.Geometry.Directions module that defines cardinal and+ InterCardinal directions.+- Added an Ellipse type (mostly so that hgeometry-ipe can read+ ellipses)+- Added FunctorWithIndex, FoldableWithIndex, and TraversableWithIndex+ instances for Vector, and removed specifically exporting imap; we+ can now just use those functions from the Lens package.++** 0.10++- renamed the smallest enclosing ball to RIC+- improved tangency finding on convex hulls/chains+- changes to how we order points in ccwCmpAround and cwCmpAround;+ these will report EQ if points appear at the same angle from the+ center point.+- new functions ccwCmpAroundWith and cwCmpAroundWith that allow you to+ specify the direction corresponding to "zero".+- bugfixes, in particular triangulating a polygon with holes now works properly.+- removed some unused dependencies+- we are no longer depending on ghc-plugins; as a result hgeometry+ now also compiles with ghcjs+- more ToJSON/FromJSON instances.+- removed the 'point2' and 'point3' functions in favor of the pattern+ synonyms Point2 and Point3.++** 0.9++- Implemented 2D Linear Programming using randomized incremental+ construction (in \(O(n)\) expected time). This allows us to solve+ the following problems+ - testing starshapedness of simple polygons in expected linear time+ - testing if we can separate a set of red and a set of blue points+ in expected linear time.+- Data types for halfspaces++** 0.8++- Compatibility with GHC 8.6+- Added \(O(n\log n)\) time closest pair algorithm.+- Added arrangement data type+- Various Bugfixes+- Added Camera data type with some world to screen transformations.+- Additional read/show instances+- Updated some of the show instances for Ipe related types.++** 0.7+++- Compatibility with GHC 8.0-8.4+- Implemented more Algorithms and Data Structures. This includes+ * Polygon triangulation+- A new implementation of PlanarSubdivision that now also supports disconnected+ subdivsions.+- Performance improvements by changing to a different Vector+ implementation. For low dimensional vectors (of dimension at most four) we+ now essentially use the types from+ [linear](https://hackage.haskell.org/package/linear), this gives significant+ speedups on several small benchmarks.+- bugfixes.++** 0.6++- Implemented more Algorithms and Data Structures. This includes+ * Bentley-Ottmannn line-segment intersection,+ * Well-Separated Pair decompositions,+ * extremal point/tangents for Convex hulls,+ * Minkowski sum for convex polygons,+ * one dimensional segment trees,+ * one dimensional interval trees, and a+ * KD-tree.+- Several bug fixes, including a very stupid bug in Box+- Separate ConvexPolygon type.+- More thorough testing for some of the algorithms.+- Started work on a proper representation for planar subdivsions. This includes+ a representation of planar graphs that support querying if two vertices are+ connected by an edge in $O(1)$ time.+- Dropped support for GHC 7.8++** 0.5++- Implemented several algorithms, including Delaunay Triangulation, EMST, and+Douglas Peucker.+- Revamped the data types for Intersections++** 0.++- Major rewrite from scratch, providing much stronger type-level+ guarantees. Incompatible with older versions.+- Convex Hull and Smallest enclosing disk algorithms.+- HGeometry now includes some very experimental and preliminary support for+ reading and writing Ipe7 files.++** 0.2 & 0.3++- Internal releases.++** 0.1.1++- Fixed a bug in point on n the line segment test+- Generalized the types of inCircle, inDisc, onCircle, onDisc etc. We now need+ only that the type representing precision model implements the typeclass+ `Num` instead of `Floating'.++** 0.1++- Initial release.
changelog.org view
@@ -2,6 +2,103 @@ * Changelog +** 0.14++- Allow the associated/extra data of linesegments and intervals to+ differ when testing for intersections.+- Intersection testing between line segments and rectangles+- Testing if lines and/or line segments intersect no longer requires a+ Fractional constraint; Num is sufficient. However, in turn we now do+ need Ord rather than just Eq. That seemed a worthwile tradeoff though.+- Cleaning up the public API by hiding several internal modules.+- Introduced the 'HasSquaredEuclideanDistance' class describing+ geometry types for which we can compute the squared distance from a+ point to a geometry, and added instances for some of the basic+ geometries.+- Fixed a bug in computing lengths to open line segments.+- Removed some proxy arguments, in e.g. Data.Geometry.Point.coord,+ rather than take a Proxy to specify which coordinate we want, use+ type applications.+- Support for GHC 9.0 and 9.2+- Better support for open-ended line segments in the Bentley Ottmann+ line segment intersection algorithm.++** 0.13++- Moved 'intersects' from the HasIntersectionWith class into a new+ class IsIntersectableWith. This allows separate (weaker) constraints+ for checking *if* geometries intersect rather than computing exact+ intersections.+- New BezierSpline features.+- "Zoom to fit" transformation.+- Many fixes related to PlaneGraph/PlanarSubdivison; i.e. bugs in+ which order the vertices/darts where reported when traversing a+ face. The polygon representing the outer boundary now is some area+ inside a bounding polygon.+- Fixed a bug in the DelaunayTriangulation.+- Preliminary implementations for updating planar subdivisions+ (e.g. subdividing edges).++** 0.12++- New website: https://hgeometry.org/+- Switch polygon implementation from a circular seq to a circular vector.+- Hide polygon implementation details.+- Enforce CCW polygon order by default.+- Fix bug in Data.Geometry.Polygon.Convex.extremes/maxInDirection.+- Fix bug in pointInPolygon in case of degenerate situations.+- Fix Read/Show instances for Point and Polygon such that 'read.show = id'.+- Improved numerical robustness.+- Random generation of monotone polygons. Thanks to @1ndy.+- Random and uniform generation of convex polygons.+- More IsIntersectableWith instances+- Updated Show/Read instances for LineSegments+- New algorithm: Visibility polygon in O(n log n) time.+- New algorithm: Earclip triangulation in O(n^2) time worst case, O(n)+ time expected case.+- New algorithm: Single-source shortest path in O(n) time.+- New algorithm: Planar point locator in O(log n) time.+- New algorithm: Point set diameter in O(n log n) time.+- New algorithm: Convex hull of a polygon in O(n) time.+- New algorithm: Diameter of a convex polygon in O(n) time.+- New algorithm: Check if a point lies inside a convex polygon in O(n)+ time.+- New algorithm: Discrete Frechet distance in O(n^2) time.++** 0.11++- Removed Functor instance from Triangle and replaced it with Bifunctor/Bifoldable/Bitraversable+- Testing if a point lies above/below a line is now in a typeclass,+ moreover there now is also an instance of this typeclass for+ planes. Hence, we can test if a point in R^3 lies above or below a+ plane.+- Bugfixes in the incomingEdges and outgoingEdges functions in+ Planar/Plane graphs and Planar subdivisions+- Added separate data types for Sides and Corners of Rectangles.+- More functionality for working with Halfspaces+- Fixed a bug in computing the intersection of overlapping+ linesegments+- PolyLine.fromPoints now returns a Maybe PolyLine rather than a+ Polyine. Use fromPointsUnsafe for the old behavior.+- Interval now no longer exports its constructor. Use the provided+ patterns instead.+- Added an OpenLineSegment pattern/constructor+- The corners and sides functions in Box now return specific types+ representing those rather than four tuples.+- Added a BezierSpline module and data type (Thanks to Maarten).+- Added a QuadTree implementation. It can be built from a set of+ points, and to represent the zeroset of some function.+- Added a Naive implementation of Convex hull in R^3. Note however+ that it works only for points in general position. In particular, no+ four points should be coplanar.+- Added a Data.Geometry.Directions module that defines cardinal and+ InterCardinal directions.+- Added an Ellipse type (mostly so that hgeometry-ipe can read+ ellipses)+- Added FunctorWithIndex, FoldableWithIndex, and TraversableWithIndex+ instances for Vector, and removed specifically exporting imap; we+ can now just use those functions from the Lens package.+ ** 0.10 - renamed the smallest enclosing ball to RIC
+ docs/Data/Geometry/PlanarSubdivision/mySubdiv.jpg view
binary file changed (absent → 378223 bytes)
+ docs/Data/PlaneGraph/planegraph.png view
binary file changed (absent → 323390 bytes)
doctests.hs view
@@ -29,10 +29,11 @@ , "DeriveFunctor" , "DeriveFoldable" , "DeriveTraversable"- , "AutoDeriveTypeable" , "DeriveGeneric" , "FlexibleInstances" , "FlexibleContexts"+ , "DerivingStrategies"+ , "DerivingVia" ] files :: [String]@@ -63,6 +64,12 @@ , "Data.Geometry.Polygon" , "Data.Geometry.Ball" , "Data.Geometry.Box"+ , "Data.Geometry.HyperPlane" -- , "Algorithms.Geometry.HiddenSurfaceRemoval.HiddenSurfaceRemoval"+ , "Algorithms.Geometry.ConvexHull.Naive"+ , "Algorithms.Geometry.ConvexHull.JarvisMarch"++ , "Algorithms.Geometry.SoS.Orientation"+ , "Algorithms.Geometry.InPolygon" ]
hgeometry.cabal view
@@ -1,5 +1,6 @@+cabal-version: 2.4 name: hgeometry-version: 0.10.0.0+version: 0.14 synopsis: Geometric Algorithms, Data structures, and Data types. description: HGeometry provides some basic geometry types, and geometric algorithms and@@ -8,46 +9,23 @@ asymptotic running time guarantees. Note that HGeometry is still highly experimental, don't be surprised to find bugs. homepage: https://fstaals.net/software/hgeometry-license: BSD3+license: BSD-3-Clause license-file: LICENSE author: Frank Staals maintainer: frank@fstaals.net -- copyright: -tested-with: GHC >= 8.2+tested-with: GHC >= 8.8 category: Geometry build-type: Simple -data-files: test/Algorithms/Geometry/LineSegmentIntersection/manual.ipe- test/Algorithms/Geometry/LineSegmentIntersection/selfIntersections.ipe- test/Algorithms/Geometry/LowerEnvelope/manual.ipe- test/Algorithms/Geometry/PolygonTriangulation/monotone.ipe- test/Algorithms/Geometry/PolygonTriangulation/simplepolygon6.ipe- test/Algorithms/Geometry/SmallestEnclosingDisk/manual.ipe- test/Algorithms/Geometry/LinearProgramming/manual.ipe- test/Algorithms/Geometry/RedBlueSeparator/manual.ipe- test/Data/Geometry/pointInPolygon.ipe- test/Data/Geometry/pointInTriangle.ipe- test/Data/Geometry/Polygon/star_shaped.ipe- test/Data/Geometry/Polygon/Convex/convexTests.ipe- test/Data/Geometry/arrangement.ipe- test/Data/Geometry/arrangement.ipe.out.ipe- test/Data/PlaneGraph/myPlaneGraph.yaml- test/Data/PlaneGraph/small.yaml- test/Data/PlaneGraph/testsegs.png-- -- in the future (cabal >=2.4) we can use- -- examples/**/*.in- -- examples/**/*.out- extra-source-files: README.md+ changelog changelog.org -Extra-doc-files: docs/Data/PlaneGraph/small.png- -- docs/**/*.png--cabal-version: 2.0+Extra-doc-files: docs/**/*.png+ docs/**/*.jpg source-repository head type: git location: https://github.com/noinia/hgeometry@@ -56,26 +34,32 @@ ghc-options: -O2 -Wall -fno-warn-unticked-promoted-constructors -fno-warn-type-defaults exposed-modules:+ -- * Primitives; Simulating General Position+ Algorithms.Geometry.SoS+ Algorithms.Geometry.SoS.Symbolic+ -- * Generic Geometry Data.Geometry Data.Geometry.Properties Data.Geometry.Transformation Data.Geometry.Boundary Data.Geometry.Duality+ Data.Geometry.Directions -- * Basic Geometry Types Data.Geometry.Vector Data.Geometry.Vector.VectorFixed Data.Geometry.Vector.VectorFamily- Data.Geometry.Vector.VectorFamilyPeano + Data.Geometry.Matrix+ -- Data.Geometry.Vector.Vinyl Data.Geometry.Interval- Data.Geometry.Interval.Util Data.Geometry.Point+ Data.Geometry.Line- Data.Geometry.Line.Internal Data.Geometry.LineSegment+ Data.Geometry.LineSegment.Internal Data.Geometry.SubLine Data.Geometry.HalfLine Data.Geometry.PolyLine@@ -86,10 +70,20 @@ Data.Geometry.Slab Data.Geometry.Box Data.Geometry.Box.Internal+ Data.Geometry.Box.Sides+ Data.Geometry.Box.Corners+ Data.Geometry.Ball+ Data.Geometry.Ellipse+ Data.Geometry.Polygon+ Data.Geometry.Polygon.Bezier+ Data.Geometry.Polygon.Inflate Data.Geometry.Polygon.Convex+ Data.Geometry.Polygon.Monotone + Data.Geometry.BezierSpline+ -- * Geometric Data Structures Data.Geometry.IntervalTree Data.Geometry.SegmentTree@@ -99,11 +93,10 @@ Data.Geometry.PlanarSubdivision Data.Geometry.PlanarSubdivision.Raw- Data.Geometry.PlanarSubdivision.Basic- Data.Geometry.PlanarSubdivision.Merge+ Data.Geometry.PlanarSubdivision.Dynamic+ Data.Geometry.PlanarSubdivision.TreeRep Data.Geometry.Arrangement- Data.Geometry.Arrangement.Internal Data.Geometry.RangeTree Data.Geometry.RangeTree.Measure@@ -111,17 +104,31 @@ Data.Geometry.PrioritySearchTree + Data.Geometry.QuadTree+ Data.Geometry.QuadTree.Cell+ Data.Geometry.QuadTree.Quadrants+ Data.Geometry.QuadTree.Split+ Data.Geometry.QuadTree.Tree++ Data.Geometry.PointLocation+ Data.Geometry.PointLocation.PersistentSweep++ Data.Geometry.VerticalRayShooting+ Data.Geometry.VerticalRayShooting.PersistentSweep+ -- * Algorithms -- * Geometric Algorithms+ Algorithms.Geometry.ConvexHull Algorithms.Geometry.ConvexHull.GrahamScan Algorithms.Geometry.ConvexHull.DivideAndConquer Algorithms.Geometry.ConvexHull.QuickHull- -- Algorithms.Geometry.ConvexHull.JarvisMarch+ Algorithms.Geometry.ConvexHull.JarvisMarch+ Algorithms.Geometry.ConvexHull.Naive Algorithms.Geometry.LowerEnvelope.DualCH - Algorithms.Geometry.SmallestEnclosingBall.Types+ Algorithms.Geometry.SmallestEnclosingBall Algorithms.Geometry.SmallestEnclosingBall.RIC Algorithms.Geometry.SmallestEnclosingBall.Naive @@ -129,29 +136,36 @@ Algorithms.Geometry.DelaunayTriangulation.DivideAndConquer Algorithms.Geometry.DelaunayTriangulation.Naive + Algorithms.Geometry.PolyLineSimplification.ImaiIri Algorithms.Geometry.PolyLineSimplification.DouglasPeucker + Algorithms.Geometry.EuclideanMST Algorithms.Geometry.EuclideanMST.EuclideanMST + Algorithms.Geometry.WSPD Algorithms.Geometry.WellSeparatedPairDecomposition.WSPD Algorithms.Geometry.WellSeparatedPairDecomposition.Types + Algorithms.Geometry.Diameter Algorithms.Geometry.Diameter.Naive+ Algorithms.Geometry.Diameter.ConvexHull -- Algorithms.Geometry.Sweep-+ Algorithms.Geometry.PolygonTriangulation Algorithms.Geometry.PolygonTriangulation.Types Algorithms.Geometry.PolygonTriangulation.Triangulate Algorithms.Geometry.PolygonTriangulation.MakeMonotone Algorithms.Geometry.PolygonTriangulation.TriangulateMonotone+ Algorithms.Geometry.PolygonTriangulation.EarClip Algorithms.Geometry.LineSegmentIntersection Algorithms.Geometry.LineSegmentIntersection.Naive Algorithms.Geometry.LineSegmentIntersection.BentleyOttmann- Algorithms.Geometry.LineSegmentIntersection.Types+ Algorithms.Geometry.LineSegmentIntersection.BooleanSweep -- Algorithms.Geometry.HiddenSurfaceRemoval.HiddenSurfaceRemoval + Algorithms.Geometry.ClosestPair Algorithms.Geometry.ClosestPair.Naive Algorithms.Geometry.ClosestPair.DivideAndConquer @@ -162,10 +176,14 @@ Algorithms.Geometry.FrechetDistance.Discrete + Algorithms.Geometry.VisibilityPolygon.Lee+ Algorithms.Geometry.SSSP+ Algorithms.Geometry.SSSP.Naive + Algorithms.Geometry.RayShooting.Naive+ -- * Embedded Planar Graphs Data.PlaneGraph- Data.PlaneGraph.Core Data.PlaneGraph.AdjRep Data.PlaneGraph.IO @@ -174,18 +192,55 @@ Graphics.Render other-modules:+ Data.Geometry.Matrix.Internal+ Data.Geometry.Transformation.Internal+ -- * Implementation Internals of Polygons Data.Geometry.Polygon.Core Data.Geometry.Polygon.Extremes+ Algorithms.Geometry.InPolygon + Algorithms.Geometry.LineSegmentIntersection.Types+ Algorithms.Geometry.SmallestEnclosingBall.Types++ Algorithms.Geometry.WSPD.Types++ Data.Geometry.Vector.VectorFamilyPeano++ Data.Geometry.Point.Internal+ Data.Geometry.Point.Orientation+ Data.Geometry.Point.Quadrants+ Data.Geometry.Point.Orientation.Degenerate+ Data.Geometry.Point.Class++ Data.Geometry.Line.Internal+++ Data.Geometry.Interval.Util++ Algorithms.Geometry.SoS.Expr+ Algorithms.Geometry.SoS.AsPoint+ Algorithms.Geometry.SoS.Internal+ Algorithms.Geometry.SoS.Orientation+ Algorithms.Geometry.SoS.Determinant+ Algorithms.Geometry.SoS.Sign++ Data.PlaneGraph.Core++ Data.Geometry.Arrangement.Internal++ Data.Geometry.PlanarSubdivision.Basic+ Data.Geometry.PlanarSubdivision.Merge+ -- other-extensions: build-depends: base >= 4.11 && < 5- , hgeometry-combinatorial >= 0.10.0.0+ , hgeometry-combinatorial >= 0.13 , bifunctors >= 4.1 , bytestring >= 0.10 , containers >= 0.5.9+ -- , multi-containers >= 0.2 , dlist >= 0.7 , lens >= 4.2 , semigroupoids >= 5@@ -198,10 +253,13 @@ , deepseq >= 1.1 , fingertree >= 0.1 , MonadRandom >= 0.5+ , random >= 1.1 , QuickCheck >= 2.5 , quickcheck-instances >= 0.3 , reflection >= 2.1 , primitive >= 0.6.3.0+ , hashable >= 1.2+ -- , singleton-typelits >= 0.1.0.0 -- , ghc-typelits-natnormalise >= 0.6@@ -209,7 +267,11 @@ , vector >= 0.11 , data-clist >= 0.1.2.3+ , vector-circular >= 0.1.4+ , nonempty-vector >= 0.2.0.0 , text >= 1.1.1.0+ , vector-algorithms+ , witherable >= 0.4 , aeson >= 1.0 , yaml >= 0.8@@ -220,7 +282,7 @@ , hspec, QuickCheck, quickcheck-instances - hs-source-dirs: src test+ hs-source-dirs: src default-language: Haskell2010 @@ -246,6 +308,8 @@ , DeriveFoldable , DeriveTraversable , DeriveGeneric+ , DerivingStrategies+ , DerivingVia , FlexibleInstances@@ -260,5 +324,94 @@ , doctest >= 0.8 , doctest-discover , QuickCheck+ , quickcheck-instances default-language: Haskell2010++benchmark benchmarks++ hs-source-dirs: benchmark++ main-is: Benchmarks.hs+ type: exitcode-stdio-1.0++ other-modules: Benchmark.Util+ Algorithms.Geometry.ConvexHull.Bench+ Algorithms.Geometry.ConvexHull.GrahamV2+ Algorithms.Geometry.ConvexHull.GrahamFam+ -- Algorithms.Geometry.ConvexHull.GrahamFamPeano+ Algorithms.Geometry.ConvexHull.GrahamFixed+ Data.Geometry.Vector.VectorFamily6+ Algorithms.Geometry.ConvexHull.GrahamFam6+ Data.Geometry.IntervalTreeBench+ -- Demo.ExpectedPairwiseDistance+ -- Demo.TriangulateWorld+ -- WSPDBench+ Algorithms.Geometry.ClosestPair.Bench++ Algorithms.Geometry.LineSegmentIntersection.Bench+ Algorithms.Geometry.LineSegmentIntersection.BentleyOttmannOld+ Algorithms.Geometry.LineSegmentIntersection.BentleyOttmannNoExt+ Algorithms.Geometry.LineSegmentIntersection.TypesNoExt++ Algorithms.Geometry.PolygonTriangulation.Bench+ Algorithms.Geometry.PolygonTriangulation.MakeMonotoneOld+++ build-depends:+ base+ , tasty-bench+ , fixed-vector+ , linear+ , semigroups+ , deepseq+ , deepseq-generics+ , hgeometry+ , hgeometry-combinatorial+ , lens+ , semigroupoids+ , QuickCheck+ , bytestring+ , containers+ , optparse-applicative+ , vinyl+ , vector+ , dlist+ , mtl+ , vector-circular+ , MonadRandom+ , hashable+++ ghc-options: -Wall -O2 -rtsopts -fno-warn-unticked-promoted-constructors++ default-language: Haskell2010++ default-extensions: TypeFamilies+ , GADTs+ , KindSignatures+ , DataKinds+ , TypeOperators+ , ConstraintKinds+ , PolyKinds+ , RankNTypes+ , TypeApplications+ , ScopedTypeVariables++ , PatternSynonyms+ , ViewPatterns+ , LambdaCase+ , TupleSections+++ , StandaloneDeriving+ , GeneralizedNewtypeDeriving+ , DeriveFunctor+ , DeriveFoldable+ , DeriveTraversable++ , FlexibleInstances+ , FlexibleContexts+ , MultiParamTypeClasses+ , DerivingStrategies+ , DeriveGeneric
+ src/Algorithms/Geometry/ClosestPair.hs view
@@ -0,0 +1,14 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.ClosestPair+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--+-- \(O(n\log n)\) time algorithm to compute the+-- closest pair among a set of \(n\) points in \(\mathbb{R}^2\).+--+--------------------------------------------------------------------------------+module Algorithms.Geometry.ClosestPair( closestPair ) where++import Algorithms.Geometry.ClosestPair.DivideAndConquer
src/Algorithms/Geometry/ClosestPair/DivideAndConquer.hs view
@@ -36,7 +36,7 @@ -- | Classical divide and conquer algorithm to compute the closest pair among -- \(n\) points. ----- running time: \(O(n)\)+-- running time: \(O(n \log n)\) closestPair :: (Ord r, Num r) => LSeq 2 (Point 2 r :+ p) -> Two (Point 2 r :+ p) closestPair = f . divideAndConquer1 mkCCP . toNonEmpty . LSeq.unstableSortBy (comparing (^.core))@@ -100,8 +100,8 @@ -> CP (Point 2 r :+ p) r run cp'' r ls = runWhile cp'' ls- (\cp l -> (ValT $ sqVertDist r l) < getDist cp) -- r and l inverted- -- by design+ (\cp l -> ValT (sqVertDist r l) < getDist cp) -- r and l inverted+ -- by design (\cp l -> minBy getDist cp (ValT $ SP (Two l r) (dist l r))) where dist (p :+ _) (q :+ _) = squaredEuclideanDist p q
+ src/Algorithms/Geometry/ConvexHull.hs view
@@ -0,0 +1,10 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.ConvexHull+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Algorithms.Geometry.ConvexHull( convexHull ) where++import Algorithms.Geometry.ConvexHull.GrahamScan
src/Algorithms/Geometry/ConvexHull/DivideAndConquer.hs view
@@ -16,7 +16,6 @@ import Algorithms.DivideAndConquer import Control.Arrow ((&&&))-import Control.Lens ((^.), to) import Data.Ext import Data.Geometry.Point import Data.Geometry.Polygon@@ -29,10 +28,10 @@ -- | \(O(n \log n)\) time ConvexHull using divide and conquer. The resulting polygon is -- given in clockwise order. convexHull :: (Ord r, Num r) => NonEmpty (Point 2 r :+ p) -> ConvexPolygon p r-convexHull (p :| []) = ConvexPolygon . fromPoints $ [p]+convexHull (p :| []) = ConvexPolygon . unsafeFromPoints $ [p] convexHull pts = combine . (upperHull' &&& lowerHull') . NonEmpty.sortBy incXdecY $ pts where- combine (l:|uh,_:|lh) = ConvexPolygon . fromPoints $ l : uh <> reverse (init lh)+ combine (l:|uh,_:|lh) = ConvexPolygon . unsafeFromPoints $ l : uh <> reverse (init lh) ---------------------------------------- -- * Computing a lower hull@@ -73,10 +72,10 @@ hull :: (NonEmpty p -> NonEmpty p -> Two (p :+ [p])) -> NonEmpty p -> NonEmpty p -> NonEmpty p hull tangent lh rh = let Two (l :+ lh') (r :+ rh') = tangent (NonEmpty.reverse lh) rh- in NonEmpty.fromList $ (reverse lh') <> [l,r] <> rh'+ in NonEmpty.fromList $ reverse lh' <> [l,r] <> rh' -------------------------------------------------------------------------------- -incXdecY :: Ord r => (Point 2 r) :+ p -> (Point 2 r) :+ q -> Ordering+incXdecY :: Ord r => Point 2 r :+ p -> Point 2 r :+ q -> Ordering incXdecY (Point2 px py :+ _) (Point2 qx qy :+ _) = compare px qx <> compare qy py
src/Algorithms/Geometry/ConvexHull/GrahamScan.hs view
@@ -1,6 +1,15 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.ConvexHull.GrahamScan+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Algorithms.Geometry.ConvexHull.GrahamScan( convexHull- , upperHull- , lowerHull+ , upperHull, upperHull'+ , lowerHull, lowerHull'++ , upperHullFromSorted, upperHullFromSorted' ) where import Control.Lens ((^.))@@ -16,20 +25,61 @@ -- given in clockwise order. convexHull :: (Ord r, Num r) => NonEmpty (Point 2 r :+ p) -> ConvexPolygon p r-convexHull (p :| []) = ConvexPolygon . fromPoints $ [p]+convexHull (p :| []) = ConvexPolygon . unsafeFromPoints $ [p] convexHull ps = let ps' = NonEmpty.toList . NonEmpty.sortBy incXdecY $ ps uh = NonEmpty.tail . hull' $ ps' lh = NonEmpty.tail . hull' $ reverse ps'- in ConvexPolygon . fromPoints . reverse $ lh ++ uh+ in ConvexPolygon . unsafeFromPoints . reverse $ lh ++ uh -- | Computes the upper hull. The upper hull is given from left to right.+--+-- Specifically. A pair of points defines an edge of the upper hull+-- iff all other points are strictly to the right of its supporting+-- line.+--+-- Note that this definition implies that the segment may be+-- vertical. Use 'upperHull'' if such an edge should not be reported.+--+-- running time: \(O(n\log n)\) upperHull :: (Ord r, Num r) => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p) upperHull = NonEmpty.reverse . hull id --- | Computes the upper hull. The upper hull is given from left to right+-- | Computes the upper hull, making sure that there are no vertical segments.+--+-- The upper hull is given from left to right+--+upperHull' :: (Ord r, Num r) => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)+upperHull' = NonEmpty.reverse . dropVertical . hull id++-- | Helper function to remove vertical segments from the hull.+--+-- Tests if the first two points are on a vertical line, if so removes+-- the first point.+dropVertical :: Eq r => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)+dropVertical = \case+ h@(_ :| []) -> h+ h@(p :| (q : rest)) | p^.core.xCoord == q^.core.xCoord -> q :| rest+ | otherwise -> h+++-- | Computes the upper hull. The upper hull is given from left to right.+--+-- Specifically. A pair of points defines an edge of the lower hull+-- iff all other points are strictly to the left of its supporting+-- line.+--+-- Note that this definition implies that the segment may be+-- vertical. Use 'lowerHull'' if such an edge should not be reported.+--+-- running time: \(O(n\log n)\) lowerHull :: (Ord r, Num r) => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p) lowerHull = hull reverse +-- | Computes the lower hull, making sure there are no vertical+-- segments. (Note that the only such segment could be the first+-- segment).+lowerHull' :: (Ord r, Num r) => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)+lowerHull' = dropVertical . hull reverse -- | Helper function so that that can compute both the upper or the lower hull, depending -- on the function f@@ -40,11 +90,37 @@ hull f pts = hull' . f . NonEmpty.toList . NonEmpty.sortBy incXdecY $ pts -incXdecY :: Ord r => (Point 2 r) :+ p -> (Point 2 r) :+ q -> Ordering+incXdecY :: Ord r => Point 2 r :+ p -> Point 2 r :+ q -> Ordering incXdecY (Point2 px py :+ _) (Point2 qx qy :+ _) = compare px qx <> compare qy py +-- | Given a sequence of points that is sorted on increasing+-- x-coordinate and decreasing y-coordinate, computes the upper+-- hull, in *right to left order*.+--+-- Specifically. A pair of points defines an edge of the upper hull+-- iff all other points are strictly to the right of its supporting+-- line.+--+--+-- Note that In constrast to the 'upperHull' function, the result is+-- returned *from right to left* !!!+--+-- running time: \(O(n)\).+upperHullFromSorted :: (Ord r, Num r) => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)+upperHullFromSorted = \case+ h@(_ :| []) -> h+ pts -> hull' $ NonEmpty.toList pts++-- | Computes the upper hull from a sorted input. Removes the last vertical segment.+--+--+-- running time: \(O(n)\).+upperHullFromSorted' :: (Ord r, Num r) => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)+upperHullFromSorted' = dropVertical . upperHullFromSorted++ -- | Precondition: The list of input points is sorted hull' :: (Ord r, Num r) => [Point 2 r :+ p] -> NonEmpty (Point 2 r :+ p) hull' (a:b:ps) = NonEmpty.fromList $ hull'' [b,a] ps@@ -57,6 +133,9 @@ | rightTurn (x^.core) (y^.core) (z^.core) = h | otherwise = cleanMiddle (z:x:rest) cleanMiddle _ = error "cleanMiddle: too few points"+hull' _ = error+ "Algorithms.Geometry.ConvexHull.GrahamScan.hull' requires a list with at least \+ \two elements." rightTurn :: (Ord r, Num r) => Point 2 r -> Point 2 r -> Point 2 r -> Bool rightTurn a b c = ccw a b c == CW
+ src/Algorithms/Geometry/ConvexHull/JarvisMarch.hs view
@@ -0,0 +1,151 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.ConvexHull.JarvisMarch+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Algorithms.Geometry.ConvexHull.JarvisMarch(+ convexHull++ , upperHull, upperHull'+ , lowerHull, lowerHull'+ , steepestCcwFrom, steepestCwFrom+ ) where++import Control.Lens ((^.))+import Data.Bifunctor+import Data.Ext+import Data.Foldable+import Data.Geometry.Point+import Data.Geometry.Polygon+import Data.Geometry.Polygon.Convex (ConvexPolygon(..))+import Data.Geometry.Vector+import qualified Data.List as List+import Data.List.NonEmpty (NonEmpty(..), (<|))+import qualified Data.List.NonEmpty as NonEmpty+import Data.Ord (comparing, Down(..))+import Data.Semigroup.Foldable++--------------------------------------------------------------------------------++-- | Compute the convexhull using JarvisMarch. The resulting polygon+-- is given in clockwise order.+--+-- running time: \(O(nh)\), where \(n\) is the number of input points+-- and \(h\) is the complexity of the hull.+convexHull :: (Ord r, Num r)+ => NonEmpty (Point 2 r :+ p) -> ConvexPolygon p r+convexHull (p :| []) = ConvexPolygon . unsafeFromPoints $ [p]+convexHull pts = ConvexPolygon . unsafeFromPoints $ uh <> reverse lh+ where+ lh = case NonEmpty.nonEmpty (NonEmpty.init $ lowerHull pts) of+ Nothing -> []+ Just (_:|lh') -> lh'+ uh = toList $ upperHull pts++ -- note that fromList is afe since ps contains at least two elements+ -- where+ -- SP p@(c :+ _) pts = minViewBy incXdecY ps+ -- takeWhile' pf (x :| xs) = x : takeWhile pf xs++upperHull :: (Num r, Ord r) => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)+upperHull pts = repeatedly cmp steepestCwFrom s rest+ where+ (s:_ :+ rest) = extractMinimaBy cmp (NonEmpty.toList pts)+ cmp = comparing (\(Point2 x y :+ _) -> (x, Down y))+ -- start from the topmost point that has minimum x-coord+ -- also use cmp as the comparator, so that we also select the last+ -- vertical segment.++-- | Upepr hull from left to right, without any vertical segments.+upperHull' :: (Num r, Ord r) => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)+upperHull' pts = pruneVertical $ repeatedly cmp steepestCwFrom s rest+ where+ (s:_ :+ rest) = extractMinimaBy cmp0 (NonEmpty.toList pts)+ cmp0 = comparing (\(Point2 x y :+ _) -> (x, Down y))+ -- start from the topmost point that has minimum x-coord+ cmp = comparing (^.core)+ -- for the rest select them in normal+ -- lexicographic order, this causes the last+ -- vertical segment to be ignored.++-- | Computes the lower hull, from left to right. Includes vertical+-- segments at the start.+--+-- running time: \(O(nh)\), where \(h\) is the complexity of the hull.+lowerHull :: (Num r, Ord r) => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)+lowerHull pts = pruneVertical $ repeatedly cmp steepestCcwFrom s rest+ where+ (s:_ :+ rest) = extractMinimaBy cmp0 (NonEmpty.toList pts)+ cmp0 = comparing (\(Point2 x y :+ _) -> (x, Down y))+ -- start from the topmost point that has minimum x-coord+ cmp = comparing (^.core)+ -- for the rest of the comparions use the normal+ -- lexicographic comparing order.++-- | Jarvis March to compute the lower hull, without any vertical segments.+--+--+-- running time: \(O(nh)\), where \(h\) is the complexity of the hull.+lowerHull' :: (Num r, Ord r) => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)+lowerHull' pts = pruneVertical $ repeatedly cmp steepestCcwFrom s rest+ where+ (s:_ :+ rest) = extractMinimaBy cmp (NonEmpty.toList pts)+ cmp = comparing (^.core)+++-- | Find the next point in counter clockwise order, i.e. the point+-- with minimum slope w.r.t. the given point.+steepestCcwFrom :: (Ord r, Num r)+ => (Point 2 r :+ a) -> NonEmpty (Point 2 r :+ b) -> Point 2 r :+ b+steepestCcwFrom p = List.minimumBy (ccwCmpAroundWith' (Vector2 0 (-1)) p)++-- | Find the next point in clockwise order, i.e. the point+-- with maximum slope w.r.t. the given point.+steepestCwFrom :: (Ord r, Num r)+ => (Point 2 r :+ a) -> NonEmpty (Point 2 r :+ b) -> Point 2 r :+ b+steepestCwFrom p = List.minimumBy (cwCmpAroundWith' (Vector2 0 1) p)++repeatedly :: (a -> a -> Ordering) -> (a -> NonEmpty a -> a) -> a -> [a] -> NonEmpty a+repeatedly cmp f = go+ where+ go m xs' = case NonEmpty.nonEmpty xs' of+ Nothing -> m :| []+ Just xs -> let p = f m xs+ in m <| go p (NonEmpty.filter (\x -> p `cmp` x == LT) xs)+++-- | Removes the topmost vertical points, if they exist+pruneVertical :: Eq r => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)+pruneVertical = either id id . foldr1With f (\q -> Left $ q:|[])+ where+ f p = \case+ Left (q:|qs) | p^.core.xCoord == q^.core.xCoord -> Left (p :| qs)+ | otherwise -> Right (p :| q:qs)+ Right pts -> Right (p <| pts)++-- | Foldr, but start by applying some function on the rightmost+-- element to get the starting value.+foldr1With :: Foldable1 f => (a -> b -> b) -> (a -> b) -> f a -> b+foldr1With f b = go . toNonEmpty+ where+ go (x :| xs) = case NonEmpty.nonEmpty xs of+ Nothing -> b x+ Just xs' -> x `f` go xs'++-- | extracts all minima from the list. The result consists of the+-- list of minima, and all remaining points. Both lists are returned+-- in the order in which they occur in the input.+--+-- >>> extractMinimaBy compare [1,2,3,0,1,2,3,0,1,2,0,2]+-- [0,0,0] :+ [2,3,1,2,3,1,2,1,2]+extractMinimaBy :: (a -> a -> Ordering) -> [a] -> [a] :+ [a]+extractMinimaBy cmp = \case+ [] -> [] :+ []+ (x:xs) -> first NonEmpty.toList $ foldr (\y (mins@(m:|_) :+ rest) ->+ case m `cmp` y of+ LT -> mins :+ y:rest+ EQ -> (y NonEmpty.<| mins) :+ rest+ GT -> (y:|[]) :+ NonEmpty.toList mins <> rest+ ) ((x:|[]) :+ []) xs
+ src/Algorithms/Geometry/ConvexHull/Naive.hs view
@@ -0,0 +1,98 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.ConvexHull.Naive+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Algorithms.Geometry.ConvexHull.Naive( ConvexHull+ , lowerHull', lowerHullAll++ , isValidTriangle, upperHalfSpaceOf+ ) where++import Control.Lens+import Data.Ext+import Data.Foldable (toList)+import Data.Geometry.HalfSpace+import Data.Geometry.HyperPlane+import Data.Geometry.Line+import Data.Geometry.Point+import Data.Geometry.Triangle+import Data.Geometry.Vector+import Data.Intersection(intersects)+import Data.List.NonEmpty (NonEmpty(..))+import Data.List (find)+import Data.Maybe (isNothing)+import Data.Util+--------------------------------------------------------------------------------++type ConvexHull d p r = [Triangle 3 p r]++-- | Computes the lower hull without its vertical triangles.+--+-- pre: The points are in general position. In particular, no four+-- points should be coplanar.+--+-- running time: \(O(n^4)\)+lowerHull' :: forall r p. (Ord r, Fractional r, Show r)+ => NonEmpty (Point 3 r :+ p) -> ConvexHull 3 p r+lowerHull' = filter (not . isVertical) . lowerHullAll+ where+ isVertical (Triangle p q r) =+ ccw' (p&core %~ projectPoint) (q&core %~ projectPoint) (r&core %~ projectPoint) == CoLinear++-- | Generates a set of triangles to be used to construct a complete+-- convex hull. In particular, it may contain vertical triangles.+--+-- pre: The points are in general position. In particular, no four+-- points should be coplanar.+--+-- running time: \(O(n^4)\)+lowerHullAll :: forall r p. (Ord r, Fractional r, Show r)+ => NonEmpty (Point 3 r :+ p) -> ConvexHull 3 p r+lowerHullAll (toList -> pts) = let mkT (Three p q r) = Triangle p q r in+ [ t | t <- mkT <$> uniqueTriplets pts, isNothing (isValidTriangle t pts) ]++++_killOverlapping :: (Ord r, Fractional r) => [Triangle 3 p r] -> [Triangle 3 p r]+_killOverlapping = foldr keepIfNotOverlaps []+ where+ keepIfNotOverlaps t ts | any (t `overlaps`) ts = ts+ | otherwise = t:ts++overlaps :: (Fractional r, Ord r) => Triangle 3 p1 r -> Triangle 3 p2 r -> Bool+t1 `overlaps` t2 = upperHalfSpaceOf t1 == upperHalfSpaceOf t2 && False++++-- | Tests if this is a valid triangle for the lower envelope. That+-- is, if all point lie above the plane through these points. Returns+-- a Maybe; if the result is a Nothing the triangle is valid, if not+-- it returns a counter example.+--+-- >>> let t = (Triangle (ext origin) (ext $ Point3 1 0 0) (ext $ Point3 0 1 0))+-- >>> isValidTriangle t [ext $ Point3 5 5 0]+-- Nothing+-- >>> let t = (Triangle (ext origin) (ext $ Point3 1 0 0) (ext $ Point3 0 1 0))+-- >>> isValidTriangle t [ext $ Point3 5 5 (-10)]+-- Just (Point3 5 5 (-10) :+ ())+isValidTriangle :: (Num r, Ord r)+ => Triangle 3 p r -> [Point 3 r :+ q] -> Maybe (Point 3 r :+ q)+isValidTriangle t = find (\a -> not $ (a^.core) `intersects` h)+ where+ h = upperHalfSpaceOf t+++-- | Computes the halfspace above the triangle.+--+-- >>> upperHalfSpaceOf (Triangle (ext $ origin) (ext $ Point3 10 0 0) (ext $ Point3 0 10 0))+-- HalfSpace {_boundingPlane = HyperPlane {_inPlane = Point3 0 0 0, _normalVec = Vector3 0 0 100}}+upperHalfSpaceOf :: (Ord r, Num r) => Triangle 3 p r -> HalfSpace 3 r+upperHalfSpaceOf (Triangle p q r) = HalfSpace h+ where+ h' = from3Points (p^.core) (q^.core) (r^.core)+ c = p&core.zCoord -~ 1+ h = if (c^.core) `liesBelow` h' then h' else h'&normalVec %~ ((-1) *^)+ a `liesBelow` plane = (a `onSideUpDown` plane) == Below
src/Algorithms/Geometry/ConvexHull/QuickHull.hs view
@@ -1,6 +1,13 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.ConvexHull.QuickHull+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Algorithms.Geometry.ConvexHull.QuickHull( convexHull ) where -import Control.Lens ((^.),(&),(.~))+import Control.Lens ((^.)) import Data.Ext import qualified Data.Foldable as F import Data.Geometry.Line@@ -26,9 +33,9 @@ -- running time: \(O(n^2)\) convexHull :: (Ord r, Fractional r, Show r, Show p) => NonEmpty (Point 2 r :+ p) -> ConvexPolygon p r-convexHull (p :| []) = ConvexPolygon . fromPoints $ [p]-convexHull ps = ConvexPolygon . fromPoints- $ [l] <> hull l r above <> [r] <> (reverse $ hull l r below)+convexHull (p :| []) = ConvexPolygon . unsafeFromPoints $ [p]+convexHull ps = ConvexPolygon . unsafeFromPoints+ $ [l] <> hull l r above <> [r] <> reverse (hull l r below) where STR l r mids = findExtremes ps m = lineThrough (l^.core) (r^.core)@@ -59,7 +66,7 @@ -- in STR l r [p | p <- F.toList pts, p /=. l, p /=. r] -incXdecY :: Ord r => (Point 2 r) :+ p -> (Point 2 r) :+ q -> Ordering+incXdecY :: Ord r => Point 2 r :+ p -> Point 2 r :+ q -> Ordering incXdecY (Point2 px py :+ _) (Point2 qx qy :+ _) = compare px qx <> compare qy py
src/Algorithms/Geometry/DelaunayTriangulation/DivideAndConquer.hs view
@@ -1,29 +1,40 @@ {-# LANGUAGE ScopedTypeVariables #-}-module Algorithms.Geometry.DelaunayTriangulation.DivideAndConquer where+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.DelaunayTriangulation.DivideAndConquer+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Algorithms.Geometry.DelaunayTriangulation.DivideAndConquer+ (+ -- * Divide & Conqueror Delaunay Triangulation+ delaunayTriangulation+ ) where -import Algorithms.Geometry.ConvexHull.GrahamScan as GS+import Algorithms.Geometry.ConvexHull.GrahamScan as GS import Algorithms.Geometry.DelaunayTriangulation.Types import Control.Lens import Control.Monad.Reader import Control.Monad.State import Data.BinaryTree-import qualified Data.CircularList as CL-import qualified Data.CircularSeq as CS-import qualified Data.CircularList.Util as CU+import qualified Data.CircularList as CL+import qualified Data.CircularList.Util as CU import Data.Ext-import qualified Data.Foldable as F-import Data.Function (on)-import Data.Geometry hiding (rotateTo)-import Data.Geometry.Ball (disk, insideBall)-import Data.Geometry.Polygon-import qualified Data.Geometry.Polygon.Convex as Convex-import Data.Geometry.Polygon.Convex (ConvexPolygon(..), simplePolygon)-import qualified Data.IntMap.Strict as IM-import qualified Data.List as L-import qualified Data.List.NonEmpty as NonEmpty-import qualified Data.Map as M-import Data.Maybe (fromJust, fromMaybe)-import qualified Data.Vector as V+import qualified Data.Foldable as F+import Data.Function (on)+import Data.Geometry hiding (rotateTo)+import Data.Geometry.Ball (disk, insideBall)+import Data.Geometry.Polygon.Convex (ConvexPolygon (..), simplePolygon)+import qualified Data.Geometry.Polygon.Convex as Convex+import qualified Data.IntMap.Strict as IM+import qualified Data.List as L+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Map as M+import Data.Maybe (fromJust, fromMaybe)+import Data.Measured.Size+import qualified Data.Vector as V+import qualified Data.Vector.Circular.Util as CV ------------------------------------------------------------------------------- -- * Divide & Conqueror Delaunay Triangulation@@ -41,7 +52,6 @@ -- -- Rotating Right <-> rotate clockwise - -- | Computes the delaunay triangulation of a set of points. -- -- Running time: \(O(n \log n)\)@@ -72,7 +82,7 @@ delaunayTriangulation' pts mapping'@(vtxMap,_) | size' pts == 1 = let (Leaf p) = pts i = lookup' vtxMap (p^.core)- in (IM.singleton i CL.empty, ConvexPolygon $ fromPoints [withID p i])+ in (IM.singleton i CL.empty, ConvexPolygon $ unsafeFromPoints [withID p i]) | size' pts <= 3 = let pts' = NonEmpty.fromList . map (\p -> withID p (lookup' vtxMap (p^.core))) . F.toList $ pts@@ -98,8 +108,8 @@ -- pre: at least two elements fromHull :: Ord r => Mapping p r -> ConvexPolygon (p :+ q) r -> Adj fromHull (vtxMap,_) p = let vs@(u:v:vs') = map (lookup' vtxMap . (^.core))- . F.toList . CS.rightElements- $ p^.simplePolygon.outerBoundary+ . F.toList . CV.rightElements+ $ p^.simplePolygon.outerBoundaryVector es = zipWith3 f vs (tail vs ++ [u]) (vs' ++ [u,v]) f prv c nxt = (c,CL.fromList . L.nub $ [prv, nxt]) in IM.fromList es@@ -137,8 +147,8 @@ | otherwise = do insert l r -- Get the neighbours of r and l along the convex hull- r1 <- pred' . rotateTo l . lookup'' r <$> get- l1 <- succ' . rotateTo r . lookup'' l <$> get+ r1 <- gets (pred' . rotateTo l . lookup'' r)+ l1 <- gets (succ' . rotateTo r . lookup'' l) (r1',a) <- rotateR l r r1 (l1',b) <- rotateL l r l1@@ -151,7 +161,7 @@ moveUp ut l' r' --- | ''rotates'' around r and removes all neighbours of r that violate the+-- | \'rotates\' around r and removes all neighbours of r that violate the -- delaunay condition. Returns the first vertex (as a Neighbour of r) that -- should remain in the Delaunay Triangulation, as well as a boolean A that -- helps deciding if we merge up by rotating left or rotating right (See@@ -198,12 +208,12 @@ -- by the first three points. qTest :: (Ord r, Fractional r) => VertexID -> VertexID -> Vertex -> Vertex -> Merge p r Bool-qTest h i j k = withPtMap . snd . fst <$> ask+qTest h i j k = asks (withPtMap . snd . fst) where withPtMap ptMap = let h' = ptMap V.! h i' = ptMap V.! i- j' = ptMap V.! (focus' j)- k' = ptMap V.! (focus' k)+ j' = ptMap V.! focus' j+ k' = ptMap V.! focus' k in not . maybe True ((k'^.core) `insideBall`) $ disk' h' i' j' disk' p q r = disk (p^.core) (q^.core) (r^.core) @@ -232,8 +242,8 @@ . IM.adjustWithKey (insert'' u) v where -- inserts b into the adjacency list of a- insert'' bi ai = CU.insertOrdBy (cwCmpAround' (ptMap V.! ai) `on` (ptMap V.!)) bi- cwCmpAround' c p q = cwCmpAround c p q <> cmpByDistanceTo c p q+ insert'' bi ai = CU.insertOrdBy (cmp (ptMap V.! ai) `on` (ptMap V.!)) bi+ cmp c p q = cwCmpAround' c p q <> cmpByDistanceTo' c p q -- | Deletes an edge@@ -246,7 +256,7 @@ -- | Lifted version of Convex.IsLeftOf isLeftOf :: (Ord r, Num r) => VertexID -> (VertexID, VertexID) -> Merge p r Bool-p `isLeftOf` (l,r) = withPtMap . snd . fst <$> ask+p `isLeftOf` (l,r) = asks (withPtMap . snd . fst) where withPtMap ptMap = (ptMap V.! p) `isLeftOf'` (ptMap V.! l, ptMap V.! r) a `isLeftOf'` (b,c) = ccw' b c a == CCW@@ -254,7 +264,7 @@ -- | Lifted version of Convex.IsRightOf isRightOf :: (Ord r, Num r) => VertexID -> (VertexID, VertexID) -> Merge p r Bool-p `isRightOf` (l,r) = withPtMap . snd . fst <$> ask+p `isRightOf` (l,r) = asks (withPtMap . snd . fst) where withPtMap ptMap = (ptMap V.! p) `isRightOf'` (ptMap V.! l, ptMap V.! r) a `isRightOf'` (b,c) = ccw' b c a == CW@@ -270,7 +280,7 @@ size' (Leaf _) = 1 size' (Node _ s _) = s --- | an 'unsafe' version of rotateTo that assumes the element to rotate to+-- | an \'unsafe\' version of rotateTo that assumes the element to rotate to -- occurs in the list. rotateTo :: Eq a => a -> CL.CList a -> CL.CList a rotateTo x = fromJust . CL.rotateTo x@@ -283,6 +293,7 @@ succ' :: CL.CList a -> CL.CList a succ' = CL.rotL +-- | Return the focus of the CList, throwing an exception if the list is empty. focus' :: CL.CList a -> a focus' = fromJust . CL.focus @@ -295,4 +306,4 @@ withID p i = p&extra %~ (:+i) lookup'' :: Int -> IM.IntMap a -> a-lookup'' k m = fromJust . IM.lookup k $ m+lookup'' k m = m IM.! k
src/Algorithms/Geometry/DelaunayTriangulation/Naive.hs view
@@ -1,3 +1,10 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.DelaunayTriangulation.Naive+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Algorithms.Geometry.DelaunayTriangulation.Naive where import Algorithms.Geometry.DelaunayTriangulation.Types@@ -17,7 +24,7 @@ -------------------------------------------------------------------------------- --- | Naive O(n^4) time implementation of the delaunay triangulation. Simply+-- | Naive \( O(n^4) \) time implementation of the delaunay triangulation. Simply -- tries each triple (p,q,r) and tests if it is delaunay, i.e. if there are no -- other points in the circle defined by p, q, and r. --@@ -52,14 +59,14 @@ addAt v i j = updateAt v i (j:) -- convert to a CList, sorted in CCW order around point u- toCList u = C.fromList . sortAround' m u+ toCList u = C.fromList . sortAroundMapping m u -- | Given a particular point u and a list of points vs, sort the points vs in -- CW order around u.--- running time: O(m log m), where m=|vs| is the number of vertices to sort.-sortAround' :: (Num r, Ord r)+-- running time: \( O(m log m) \), where m=|vs| is the number of vertices to sort.+sortAroundMapping :: (Num r, Ord r) => Mapping p r -> VertexID -> [VertexID] -> [VertexID]-sortAround' (_,ptsV) u vs = reverse . map (^.extra) $ sortAround (f u) (map f vs)+sortAroundMapping (_,ptsV) u vs = reverse . map (^.extra) $ sortAround' (f u) (map f vs) where f v = (ptsV V.! v)&extra .~ v @@ -71,8 +78,7 @@ -- we sort, group, and take the head of the lists --- | Test if the given three points form a triangle in the delaunay triangulation.--- running time: O(n)+-- | \( O(n) \) Test if the given three points form a triangle in the delaunay triangulation. isDelaunay :: (Fractional r, Ord r) => Mapping p r -> VertexID -> VertexID -> VertexID -> Bool isDelaunay (_,ptsV) p q r = case disk (pt p) (pt q) (pt r) of
src/Algorithms/Geometry/DelaunayTriangulation/Types.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE ScopedTypeVariables #-} -------------------------------------------------------------------------------- -- |@@ -10,7 +9,20 @@ -- Defines some geometric types used in the delaunay triangulation -- ---------------------------------------------------------------------------------module Algorithms.Geometry.DelaunayTriangulation.Types where+module Algorithms.Geometry.DelaunayTriangulation.Types+ ( VertexID+ , Vertex+ , Adj+ , Triangulation(..)+ , vertexIds+ , positions+ , neighbours+ , Mapping+ , edgesAsPoints+ , edgesAsVertices+ , toPlanarSubdivision+ , toPlaneGraph+ ) where import Control.Lens import qualified Data.CircularList as C@@ -19,7 +31,7 @@ import Data.Geometry.PlanarSubdivision import qualified Data.IntMap.Strict as IM import qualified Data.Map as M-import qualified Data.Map.Strict as SM+-- import qualified Data.Map.Strict as SM import qualified Data.PlaneGraph as PG import qualified Data.PlanarGraph as PPG import qualified Data.Vector as V@@ -32,12 +44,13 @@ -- : If v on the convex hull, then its first entry in the adj. lists is its CCW -- successor (i.e. its predecessor) on the convex hull --- | Rotating Right <-> rotate clockwise-+-- | Vertex identifier. type VertexID = Int +-- | Rotating Right <-> rotate clockwise type Vertex = C.CList VertexID +-- | Neighbours indexed by VertexID. type Adj = IM.IntMap (C.CList VertexID) -- | Neighbours are stored in clockwise order: i.e. rotating right moves to the@@ -47,35 +60,48 @@ , _neighbours :: V.Vector (C.CList VertexID) } deriving (Show,Eq)-makeLenses ''Triangulation +-- | Mapping between triangulated points and their internal VertexID.+vertexIds :: Lens' (Triangulation p r) (M.Map (Point 2 r) VertexID)+vertexIds = lens _vertexIds (\(Triangulation _v p n) v -> Triangulation v p n)++-- | Point positions indexed by VertexID.+positions :: Lens (Triangulation p1 r) (Triangulation p2 r) (V.Vector (Point 2 r :+ p1)) (V.Vector (Point 2 r :+ p2))+positions = lens _positions (\(Triangulation v _p n) p -> Triangulation v p n)++-- | Point neighbours indexed by VertexID.+neighbours :: Lens' (Triangulation p r) (V.Vector (C.CList VertexID))+neighbours = lens _neighbours (\(Triangulation v p _n) n -> Triangulation v p n)++ type instance NumType (Triangulation p r) = r type instance Dimension (Triangulation p r) = 2 -+-- | Bidirectional mapping between points and VertexIDs. type Mapping p r = (M.Map (Point 2 r) VertexID, V.Vector (Point 2 r :+ p)) -showDT :: (Show p, Show r) => Triangulation p r -> IO ()-showDT = mapM_ print . triangulationEdges+-- showDT :: (Show p, Show r) => Triangulation p r -> IO ()+-- showDT = mapM_ print . edgesAsPoints -triangulationEdges :: Triangulation p r -> [(Point 2 r :+ p, Point 2 r :+ p)]-triangulationEdges t = let pts = _positions t- in map (\(u,v) -> (pts V.! u, pts V.! v)) . tEdges $ t-+-- | List add edges as point pairs.+edgesAsPoints :: Triangulation p r -> [(Point 2 r :+ p, Point 2 r :+ p)]+edgesAsPoints t = let pts = _positions t+ in map (bimap (pts V.!) (pts V.!)) . edgesAsVertices $ t -tEdges :: Triangulation p r -> [(VertexID,VertexID)]-tEdges = concatMap (\(i,ns) -> map (i,) . filter (> i) . C.toList $ ns)+-- | List add edges as VertexID pairs.+edgesAsVertices :: Triangulation p r -> [(VertexID,VertexID)]+edgesAsVertices = concatMap (\(i,ns) -> map (i,) . filter (> i) . C.toList $ ns) . zip [0..] . V.toList . _neighbours -------------------------------------------------------------------------------- -data ST a b c = ST { fst' :: !a, snd' :: !b , trd' :: !c}+-- data ST a b c = ST { fst' :: !a, snd' :: !b , trd' :: !c} -type ArcID = Int+-- type ArcID = Int -- | ST' is a strict triple (m,a,x) containing: --@@ -83,23 +109,22 @@ -- u < v, to arcId's. -- - a: the next available unused arcID -- - x: the data value we are interested in computing-type ST' a = ST (SM.Map (VertexID,VertexID) ArcID) ArcID a+-- type ST' a = ST (SM.Map (VertexID,VertexID) ArcID) ArcID a -- | convert the triangulation into a planarsubdivision -- -- running time: \(O(n)\).-toPlanarSubdivision :: (Ord r, Fractional r)- => proxy s -> Triangulation p r -> PlanarSubdivision s p () () r-toPlanarSubdivision px = fromPlaneGraph . toPlaneGraph px+toPlanarSubdivision :: forall s p r. (Ord r, Fractional r)+ => Triangulation p r -> PlanarSubdivision s p () () r+toPlanarSubdivision = fromPlaneGraph . toPlaneGraph -- | convert the triangulation into a plane graph -- -- running time: \(O(n)\).-toPlaneGraph :: forall proxy s p r.- proxy s -> Triangulation p r -> PG.PlaneGraph s p () () r-toPlaneGraph _ tr = PG.PlaneGraph $ g&PPG.vertexData .~ vtxData+toPlaneGraph :: forall s p r. Triangulation p r -> PG.PlaneGraph s p () () r+toPlaneGraph tr = PG.PlaneGraph $ g&PPG.vertexData .~ vtxData where g = PPG.fromAdjacencyLists . V.toList . V.imap f $ tr^.neighbours- f i adj = (VertexId i, VertexId <$> adj)+ f i adj = (VertexId i, C.leftElements $ VertexId <$> adj) -- report in CCW order vtxData = (\(loc :+ p) -> VertexData loc p) <$> tr^.positions
+ src/Algorithms/Geometry/Diameter.hs view
@@ -0,0 +1,13 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.Diameter+-- Copyright : (C) David Himmelstrup+-- License : see the LICENSE file+-- Maintainer : Frank Staals, David Himmelstrup+--------------------------------------------------------------------------------+module Algorithms.Geometry.Diameter+ ( diameter+ , diametralPair+ ) where++import Algorithms.Geometry.Diameter.ConvexHull
+ src/Algorithms/Geometry/Diameter/ConvexHull.hs view
@@ -0,0 +1,35 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.Diameter.ConvexHull+-- Copyright : (C) David Himmelstrup+-- License : see the LICENSE file+-- Maintainer : Frank Staals, David Himmelstrup+--------------------------------------------------------------------------------+module Algorithms.Geometry.Diameter.ConvexHull+ ( diameter+ , diametralPair+ ) where++import Algorithms.Geometry.ConvexHull.GrahamScan (convexHull)+import qualified Algorithms.Geometry.Diameter.Naive as Naive+import Control.Lens ((^.))+import Data.Ext (core, type (:+))+import Data.Geometry (Point, euclideanDist)+import qualified Data.Geometry.Polygon.Convex as Convex+import qualified Data.List.NonEmpty as NonEmpty++--------------------------------------------------------------------------------++-- | Computes the Euclidean diameter by first finding the convex hull.+--+-- running time: \(O(n \log n)\)+diameter :: (Ord r, Floating r) => [Point 2 r :+ p] -> r+diameter = maybe 0 (\(p,q) -> euclideanDist (p^.core) (q^.core)) . diametralPair++-- | Computes the Euclidean diameter by first finding the convex hull.+--+-- running time: \(O(n \log n)\)+diametralPair :: (Ord r, Num r)+ => [Point 2 r :+ p] -> Maybe (Point 2 r :+ p, Point 2 r :+ p)+diametralPair lst@(_:_:_:_) = Just . Convex.diametralPair $ convexHull $ NonEmpty.fromList lst+diametralPair lst = Naive.diametralPair lst
src/Algorithms/Geometry/Diameter/Naive.hs view
@@ -1,3 +1,10 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.Diameter.Naive+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Algorithms.Geometry.Diameter.Naive where import Control.Lens@@ -7,6 +14,9 @@ -------------------------------------------------------------------------------- +-- | Computes the Euclidean diameter by naively trying all pairs.+--+-- running time: \(O(n^2)\) diameter :: (Ord r, Floating r, Arity d) => [Point d r :+ p] -> r diameter = maybe 0 (\(p,q) -> euclideanDist (p^.core) (q^.core)) . diametralPair
+ src/Algorithms/Geometry/EuclideanMST.hs view
@@ -0,0 +1,46 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.EuclideanMST+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--+-- \(O(n\log n)\) time algorithm algorithm to compute the Euclidean minimum+-- spanning tree of a set of \(n\) points in \(\mathbb{R}^2\).+--+--------------------------------------------------------------------------------+module Algorithms.Geometry.EuclideanMST ( euclideanMST ) where++import Algorithms.Geometry.DelaunayTriangulation.DivideAndConquer+import Algorithms.Geometry.DelaunayTriangulation.Types+import Algorithms.Graph.MST+import Control.Lens+import Data.Ext+import Data.Geometry+import qualified Data.List.NonEmpty as NonEmpty+import Data.PlaneGraph+import Data.Tree++--------------------------------------------------------------------------------++-- | Computes the Euclidean Minimum Spanning Tree. We compute the Delaunay+-- Triangulation (DT), and then extract the EMST. Hence, the same restrictions+-- apply as for the DT:+--+-- pre: the input is a *SET*, i.e. contains no duplicate points. (If the input+-- does contain duplicate points, the implementation throws them away)+--+-- running time: \(O(n \log n)\)+euclideanMST :: (Ord r, Fractional r)+ => NonEmpty.NonEmpty (Point 2 r :+ p) -> Tree (Point 2 r :+ p)+euclideanMST pts = (\v -> g^.locationOf v :+ g^.dataOf v) <$> t+ where+ -- since we care only about the relative order of the edges we can use the+ -- squared Euclidean distance rather than the Euclidean distance, thus+ -- avoiding the Floating constraint+ g = withEdgeDistances squaredEuclideanDist . toPlaneGraph @MSTW+ . delaunayTriangulation $ pts+ t = mst $ g^.graph+++data MSTW
src/Algorithms/Geometry/EuclideanMST/EuclideanMST.hs view
@@ -9,39 +9,9 @@ -- spanning tree of a set of \(n\) points in \(\mathbb{R}^2\). -- ---------------------------------------------------------------------------------module Algorithms.Geometry.EuclideanMST.EuclideanMST where--import Algorithms.Geometry.DelaunayTriangulation.DivideAndConquer-import Algorithms.Geometry.DelaunayTriangulation.Types-import Algorithms.Graph.MST-import Control.Lens-import Data.Ext-import Data.Geometry-import qualified Data.List.NonEmpty as NonEmpty-import Data.PlaneGraph-import Data.Proxy-import Data.Tree-------------------------------------------------------------------------------------- | Computes the Euclidean Minimum Spanning Tree. We compute the Delaunay--- Triangulation (DT), and then extract the EMST. Hence, the same restrictions--- apply as for the DT:------ pre: the input is a *SET*, i.e. contains no duplicate points. (If the input--- does contain duplicate points, the implementation throws them away)------ running time: \(O(n \log n)\)-euclideanMST :: (Ord r, Fractional r)- => NonEmpty.NonEmpty (Point 2 r :+ p) -> Tree (Point 2 r :+ p)-euclideanMST pts = (\v -> g^.locationOf v :+ g^.dataOf v) <$> t- where- -- since we care only about the relative order of the edges we can use the- -- squared Euclidean distance rather than the Euclidean distance, thus- -- avoiding the Floating constraint- g = withEdgeDistances squaredEuclideanDist . toPlaneGraph (Proxy :: Proxy MSTW)- . delaunayTriangulation $ pts- t = mst $ g^.graph-+module Algorithms.Geometry.EuclideanMST.EuclideanMST+ {-# DEPRECATED "This module will be deleted after 2021-06-01. \+ \Use Algorithms.Geometry.EuclideanMST instead." #-}+ ( module Algorithms.Geometry.EuclideanMST ) where -data MSTW+import Algorithms.Geometry.EuclideanMST
src/Algorithms/Geometry/FrechetDistance/Discrete.hs view
@@ -1,3 +1,10 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.FrechetDistance.Discrete+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Algorithms.Geometry.FrechetDistance.Discrete( discreteFrechetDistance , discreteFrechetDistanceWith ) where
+ src/Algorithms/Geometry/InPolygon.hs view
@@ -0,0 +1,155 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.InPolygon+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--+-- Testing if a point lies in a polygon+--+--------------------------------------------------------------------------------+module Algorithms.Geometry.InPolygon+ ( inPolygon+ , insidePolygon+ , onBoundary+ ) where++import Control.Lens++import Data.Ext+import qualified Data.Foldable as F+import Data.Geometry.Boundary+import Data.Geometry.Line+import Data.Geometry.LineSegment+import Data.Geometry.Point+import Data.Geometry.Polygon.Core+import Data.Geometry.Properties++import qualified Data.List.Util as List+import Data.Maybe (mapMaybe)+import Data.Vinyl.CoRec (asA)+--------------------------------------------------------------------------------++{- $setup+>>> import Data.RealNumber.Rational+>>> import Data.Foldable+>>> import Control.Lens.Extras+>>> :{+-- import qualified Data.Vector.Circular as CV+let simplePoly :: SimplePolygon () (RealNumber 10)+ simplePoly = fromPoints . map ext $+ [ Point2 0 0+ , Point2 10 0+ , Point2 10 10+ , Point2 5 15+ , Point2 1 11+ ]+ simpleTriangle :: SimplePolygon () (RealNumber 10)+ simpleTriangle = fromPoints . map ext $+ [ Point2 0 0, Point2 2 0, Point2 1 1]+ multiPoly :: MultiPolygon () (RealNumber 10)+ multiPoly = MultiPolygon+ (fromPoints . map ext $ [Point2 (-1) (-1), Point2 3 (-1), Point2 2 2])+ [simpleTriangle]+:} -}+++-- | \( O(n) \) Test if q lies on the boundary of the polygon.+--+-- >>> Point2 1 1 `onBoundary` simplePoly+-- False+-- >>> Point2 0 0 `onBoundary` simplePoly+-- True+-- >>> Point2 10 0 `onBoundary` simplePoly+-- True+-- >>> Point2 5 13 `onBoundary` simplePoly+-- False+-- >>> Point2 5 10 `onBoundary` simplePoly+-- False+-- >>> Point2 10 5 `onBoundary` simplePoly+-- True+-- >>> Point2 20 5 `onBoundary` simplePoly+-- False+--+-- TODO: testcases multipolygon+onBoundary :: (Num r, Ord r) => Point 2 r -> Polygon t p r -> Bool+q `onBoundary` pg = any (q `intersects`) es+ where+ out = pg^.outerBoundary+ es = concatMap (F.toList . outerBoundaryEdges) $ out : holeList pg++-- | Check if a point lies inside a polygon, on the boundary, or outside of the polygon.+-- Running time: O(n).+--+-- >>> Point2 1 1 `inPolygon` simplePoly+-- Inside+-- >>> Point2 0 0 `inPolygon` simplePoly+-- OnBoundary+-- >>> Point2 10 0 `inPolygon` simplePoly+-- OnBoundary+-- >>> Point2 5 13 `inPolygon` simplePoly+-- Inside+-- >>> Point2 5 10 `inPolygon` simplePoly+-- Inside+-- >>> Point2 10 5 `inPolygon` simplePoly+-- OnBoundary+-- >>> Point2 20 5 `inPolygon` simplePoly+-- Outside+--+-- TODO: Add some testcases with multiPolygons+-- TODO: Add some more onBoundary testcases+inPolygon :: forall t p r. (Fractional r, Ord r)+ => Point 2 r -> Polygon t p r -> PointLocationResult+q `inPolygon` pg+ | q `onBoundary` pg = OnBoundary+ | inHole = Outside+ | otherwise = q `inPolygon'` (pg^.outerBoundary)+ where+ inHole = any (q `insidePolygon`) $ holeList pg++-- | Returns true if the point lies in the polygon+-- pre: point lies inside or outside the polygon, not on its boundary.+inPolygon' :: forall p r. (Fractional r, Ord r)+ => Point 2 r -> SimplePolygon p r+ -> PointLocationResult+q `inPolygon'` pg = if odd . length . mapMaybe intersectionPoint $ ups <> downs+ then Inside else Outside+ where+ -- we don't care about horizontal edges+ (ups',_horizontals,downs') = partitionEdges . listEdges $ pg+ partitionEdges = List.partition3 $ \s -> (s^.end.core.yCoord) `compare` (s^.start.core.yCoord)++ -- upward edges include start, exclude end+ ups = map (\(LineSegment' a b) -> LineSegment (Closed a) (Open b)) ups'+ -- downward edges exclude start, include end+ downs = map (\(LineSegment' a b) -> LineSegment (Open a) (Closed b)) downs'++ -- Given an edge, compute the intersection point (if a point) with+ -- the line through the query point, and test if it lies strictly+ -- right of q.+ --+ -- See http://geomalgorithms.com/a03-_inclusion.html for more information.+ intersectionPoint = F.find (\p -> p^.xCoord > q^.xCoord) . asA @(Point 2 r) . (`intersect` l)+ l = horizontalLine $ q^.yCoord+++-- | Test if a point lies strictly inside the polgyon.+insidePolygon :: (Fractional r, Ord r) => Point 2 r -> Polygon t p r -> Bool+q `insidePolygon` pg = q `inPolygon` pg == Inside+++-- testQ = map (`inPolygon` testPoly) [ Point2 1 1 -- Inside+-- , Point2 0 0 -- OnBoundary+-- , Point2 5 14 -- Inside+-- , Point2 5 10 -- Inside+-- , Point2 10 5 -- OnBoundary+-- , Point2 20 5 -- Outside+-- ]++-- testPoly :: SimplePolygon () Rational+-- testPoly = fromPoints . map ext $ [ Point2 0 0+-- , Point2 10 0+-- , Point2 10 10+-- , Point2 5 15+-- , Point2 1 11+-- ]
src/Algorithms/Geometry/LineSegmentIntersection.hs view
@@ -1,16 +1,35 @@-module Algorithms.Geometry.LineSegmentIntersection where+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.LineSegmentIntersection+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Algorithms.Geometry.LineSegmentIntersection+ ( BooleanSweep.hasIntersections+ , BO.intersections+ , BO.interiorIntersections+ , Intersections+ , Associated(..)+ , IntersectionPoint(..), mkIntersectionPoint+ -- , isInteriorIntersection+ , hasSelfIntersections+ ) where import qualified Algorithms.Geometry.LineSegmentIntersection.BentleyOttmann as BO+import qualified Algorithms.Geometry.LineSegmentIntersection.BooleanSweep as BooleanSweep+import Algorithms.Geometry.LineSegmentIntersection.Types+import Data.Ext (ext) import Data.Geometry.LineSegment import Data.Geometry.Polygon --- Tests if there are any interior intersections.------ | \(O(n \log n)\)-hasInteriorIntersections :: (Ord r, Fractional r)- => [LineSegment 2 p r] -> Bool-hasInteriorIntersections = not . null . BO.interiorIntersections+import qualified Data.Map as Map --- | \(O(n \log n)\)+-- | Test if the polygon has self intersections.+--+-- \(O(n \log n)\) hasSelfIntersections :: (Ord r, Fractional r) => Polygon t p r -> Bool-hasSelfIntersections = hasInteriorIntersections . listEdges+hasSelfIntersections = not . Map.null . BO.interiorIntersections . map ext . listEdges+-- hasSelfIntersections :: (Ord r, Num r) => Polygon t p r -> Bool+-- hasSelfIntersections = BooleanSweep.hasIntersections . listEdges+-- FIXME: fix the open/closed bug, then switch to a boolean sweep based version
src/Algorithms/Geometry/LineSegmentIntersection/BentleyOttmann.hs view
@@ -10,12 +10,17 @@ -- and Ottmann. -- ---------------------------------------------------------------------------------module Algorithms.Geometry.LineSegmentIntersection.BentleyOttmann where+module Algorithms.Geometry.LineSegmentIntersection.BentleyOttmann+ ( intersections+ , interiorIntersections+ ) where import Algorithms.Geometry.LineSegmentIntersection.Types import Control.Lens hiding (contains)+import Data.Coerce import Data.Ext import qualified Data.Foldable as F+import Data.Function (on) import Data.Geometry.Interval import Data.Geometry.LineSegment import Data.Geometry.Point@@ -26,50 +31,105 @@ import qualified Data.Map as M import Data.Maybe import Data.Ord (Down(..), comparing)-import Data.OrdSeq (Compare)+import qualified Data.Set as EQ -- event queue import qualified Data.Set as SS -- status struct+import qualified Data.Set as Set import qualified Data.Set.Util as SS -- status struct-import qualified Data.Set as EQ -- event queue import Data.Vinyl import Data.Vinyl.CoRec- -------------------------------------------------------------------------------- -- | Compute all intersections -- -- \(O((n+k)\log n)\), where \(k\) is the number of intersections.-intersections :: (Ord r, Fractional r)- => [LineSegment 2 p r] -> Intersections p r-intersections ss = merge $ sweep pts SS.empty+intersections :: forall p r e. (Ord r, Fractional r)+ => [LineSegment 2 p r :+ e] -> Intersections p r e+intersections ss = fmap unflipSegs . merge $ sweep pts SS.empty where- pts = EQ.fromAscList . groupStarts . L.sort . concatMap asEventPts $ ss+ pts = EQ.fromAscList . groupStarts . L.sort . concatMap (asEventPts . tagFlipped) $ ss -- | Computes all intersection points p s.t. p lies in the interior of at least -- one of the segments. -- -- \(O((n+k)\log n)\), where \(k\) is the number of intersections. interiorIntersections :: (Ord r, Fractional r)- => [LineSegment 2 p r] -> Intersections p r-interiorIntersections = M.filter (not . isEndPointIntersection) . intersections+ => [LineSegment 2 p r :+ e] -> Intersections p r e+interiorIntersections = M.filter isInteriorIntersection . intersections +--------------------------------------------------------------------------------+-- * Flipping and unflipping++data Flipped = NotFlipped | Flipped deriving (Show,Eq)++-- | Make sure the 'start' endpoint occurs before the end-endpoints in+-- terms of the sweep order.+tagFlipped :: Ord r => LineSegment 2 p r :+ e -> LineSegment 2 p r :+ (e :+ Flipped)+tagFlipped s = case (s^.core.start.core) `ordPoints` (s^.core.end.core) of+ GT -> s&core %~ flipSeg+ &extra %~ (:+ Flipped)+ _ -> s&extra %~ (:+ NotFlipped)++-- | Flips the segment+flipSeg :: LineSegment d p r -> LineSegment d p r+flipSeg seg = seg&start .~ (seg^.end)+ &end .~ (seg^.start)++-- | Unflips the segments in an associated.+unflipSegs :: (Fractional r, Ord r)+ => Associated p r (e :+ Flipped) -> Associated p r e+unflipSegs (Associated ss es is) =+ Associated (dropFlipped ss1 <> unflipSegs' es')+ (dropFlipped es1 <> unflipSegs' ss')+ (dropFlipped is1 <> unflipSegs' is')+ where+ (ss',ss1) = Set.partition (\(AroundEnd s) -> isFlipped s) ss+ (es',es1) = Set.partition (\(AroundStart s) -> isFlipped s) es+ (is',is1) = Set.partition (\(AroundIntersection s) -> isFlipped s) is++ isFlipped s = Flipped == s^.extra.extra++ -- | For segments that are not acutally flipped, we can just drop the flipped bit+ dropFlipped :: Functor f+ => Set.Set (f (LineSegment 2 p r :+ (e :+ Flipped)))+ -> Set.Set (f (LineSegment 2 p r :+ e))+ dropFlipped = Set.mapMonotonic (fmap dropFlip)++ -- For flipped segs we unflip them (and appropriately coerce the+ -- so that they remain in the same order. I.e. if they were sorted+ -- around the start point they are now sorted around the endpoint.+ unflipSegs' :: ( Functor f+ , Coercible (f (LineSegment 2 p r :+ e)) (g (LineSegment 2 p r :+ e))+ )+ => Set.Set (f (LineSegment 2 p r :+ (e :+ Flipped)))+ -> Set.Set (g (LineSegment 2 p r :+ e))+ unflipSegs' = Set.mapMonotonic (coerce . fmap unflip)++ unflip (s :+ (e :+ _)) = flipSeg s :+ e+ dropFlip (s :+ (e :+ _)) = s :+ e++--------------------------------------------------------------------------------+ -- | Computes the event points for a given line segment-asEventPts :: Ord r => LineSegment 2 p r -> [Event p r]-asEventPts s = let [p,q] = L.sortBy ordPoints [s^.start.core,s^.end.core]- in [Event p (Start $ s :| []), Event q (End s)]+asEventPts :: LineSegment 2 p r :+ e -> [Event p r e]+asEventPts s = [ Event (s^.core.start.core) (Start $ s :| [])+ , Event (s^.core.end.core) (End s)+ ] -- | Group the segments with the intersection points-merge :: Ord r => [IntersectionPoint p r] -> Intersections p r+merge :: (Ord r, Fractional r) => [IntersectionPoint p r e] -> Intersections p r e merge = foldr (\(IntersectionPoint p a) -> M.insertWith (<>) p a) M.empty -- | Group the startpoints such that segments with the same start point -- correspond to one event.-groupStarts :: Eq r => [Event p r] -> [Event p r]+groupStarts :: Eq r => [Event p r e] -> [Event p r e] groupStarts [] = [] groupStarts (Event p (Start s) : es) = Event p (Start ss) : groupStarts rest where (ss',rest) = L.span sameStart es- -- sort the segs on lower endpoint- ss = let (x:|xs) = s in x :| (xs ++ concatMap startSegs ss')+ -- FIXME: this seems to keep the segments on decreasing y, increasing x. shouldn't we+ -- sort them cyclically around p instead?+ ss = let (x:|xs) = s+ in x :| (xs ++ concatMap startSegs ss') sameStart (Event q (Start _)) = p == q sameStart _ = False@@ -94,84 +154,68 @@ (End _) `compare` _ = GT -- | The actual event consists of a point and its type-data Event p r = Event { eventPoint :: !(Point 2 r)- , eventType :: !(EventType (LineSegment 2 p r))- } deriving (Show,Eq)+data Event p r e = Event { eventPoint :: !(Point 2 r)+ , eventType :: !(EventType (LineSegment 2 p r :+ e))+ } deriving (Show,Eq) -instance Ord r => Ord (Event p r) where+instance Ord r => Ord (Event p r e) where -- decreasing on the y-coord, then increasing on x-coord, and increasing on event-type (Event p s) `compare` (Event q t) = case ordPoints p q of EQ -> s `compare` t x -> x --- | An ordering that is decreasing on y, increasing on x-ordPoints :: Ord r => Point 2 r -> Point 2 r -> Ordering-ordPoints a b = let f p = (Down $ p^.yCoord, p^.xCoord) in comparing f a b- -- | Get the segments that start at the given event point-startSegs :: Event p r -> [LineSegment 2 p r]+startSegs :: Event p r e -> [LineSegment 2 p r :+ e] startSegs e = case eventType e of Start ss -> NonEmpty.toList ss _ -> [] -------------------------------------------------------------------------------- --- | Compare based on the x-coordinate of the intersection with the horizontal--- line through y-ordAt :: (Fractional r, Ord r) => r -> Compare (LineSegment 2 p r)-ordAt y = comparing (xCoordAt y) --- | Given a y coord and a line segment that intersects the horizontal line--- through y, compute the x-coordinate of this intersection point.------ note that we will pretend that the line segment is closed, even if it is not-xCoordAt :: (Fractional r, Ord r) => r -> LineSegment 2 p r -> r-xCoordAt y (LineSegment' (Point2 px py :+ _) (Point2 qx qy :+ _))- | py == qy = px `max` qx -- s is horizontal, and since it by the- -- precondition it intersects the sweep- -- line, we return the x-coord of the- -- rightmost endpoint.- | otherwise = px + alpha * (qx - px)- where- alpha = (y - py) / (qy - py)- -------------------------------------------------------------------------------- -- * The Main Sweep -type EventQueue p r = EQ.Set (Event p r)-type StatusStructure p r = SS.Set (LineSegment 2 p r)+type EventQueue p r e = EQ.Set (Event p r e)+type StatusStructure p r e = SS.Set (LineSegment 2 p r :+ e) -- | Run the sweep handling all events sweep :: (Ord r, Fractional r)- => EventQueue p r -> StatusStructure p r -> [IntersectionPoint p r]+ => EventQueue p r e -> StatusStructure p r e -> [IntersectionPoint p r e] sweep eq ss = case EQ.minView eq of Nothing -> [] Just (e,eq') -> handle e eq' ss -isClosedStart :: Eq r => Point 2 r -> LineSegment 2 p r -> Bool-isClosedStart p (LineSegment s e)- | p == s^.unEndPoint.core = isClosed s- | otherwise = isClosed e- -- | Handle an event point-handle :: forall r p. (Ord r, Fractional r)- => Event p r -> EventQueue p r -> StatusStructure p r- -> [IntersectionPoint p r]+handle :: forall r p e. (Ord r, Fractional r)+ => Event p r e -> EventQueue p r e -> StatusStructure p r e+ -> [IntersectionPoint p r e] handle e@(eventPoint -> p) eq ss = toReport <> sweep eq' ss' where starts = startSegs e (before,contains',after) = extractContains p ss (ends,contains) = L.partition (endsAt p) contains' -- starting segments, exluding those that have an open starting point- starts' = filter (isClosedStart p) starts- toReport = case starts' ++ contains' of- (_:_:_) -> [IntersectionPoint p $ associated (starts' <> ends) contains]+ -- starts' = filter (isClosedStart p) starts+ starts' = shouldReport p $ SS.toAscList newSegs++ -- If we just inserted open-ended segments that start here, then+ -- don't consider them to be "contained" segments.+ pureContains = filter (\(LineSegment s _ :+ _) ->+ not $ isOpen s && p == s^.unEndPoint.core) contains++ -- any (closed) ending segments at this event point.+ closedEnds = filter (\(LineSegment _ e' :+ _) -> isClosed e') ends++ toReport = case starts' <> closedEnds <> pureContains of+ (_:_:_) -> [mkIntersectionPoint p (starts' <> closedEnds) pureContains] _ -> [] -- new status structure ss' = before `SS.join` newSegs `SS.join` after newSegs = toStatusStruct p $ starts ++ contains + -- the new eeventqueue eq' = foldr EQ.insert eq es -- the new events:@@ -186,54 +230,145 @@ app f x y = do { x' <- x ; y' <- y ; f x' y'} +-- | given the starting point p, and the segments that either start in+-- p, or continue in p, in left to right order along a line just+-- epsilon below p, figure out which segments we should report as+-- intersecting at p.+--+-- in partcular; those that:+-- - have a closed endpoint at p+-- - those that have an open endpoint at p and have an intersection+-- with a segment eps below p. Those segments thus overlap wtih+-- their predecessor or successor in the cyclic order.+shouldReport :: (Ord r, Num r)+ => Point 2 r -> [LineSegment 2 p r :+ e] -> [LineSegment 2 p r :+ e]+shouldReport _ = overlapsOr (\(LineSegment s _ :+ _) -> isClosed s)+ (\(s :+ _) (s2 :+ _) -> s `intersects` s2)+ -- | split the status structure, extracting the segments that contain p. -- the result is (before,contains,after) extractContains :: (Fractional r, Ord r)- => Point 2 r -> StatusStructure p r- -> (StatusStructure p r, [LineSegment 2 p r], StatusStructure p r)+ => Point 2 r -> StatusStructure p r e+ -> (StatusStructure p r e, [LineSegment 2 p r :+ e], StatusStructure p r e) extractContains p ss = (before, F.toList mid1 <> F.toList mid2, after) where- (before, mid1, after') = SS.splitOn (xCoordAt $ p^.yCoord) (p^.xCoord) ss+ (before, mid1, after') = SS.splitOn (xCoordAt' $ p^.yCoord) (p^.xCoord) ss -- Make sure to also select the horizontal segments containing p- (mid2, after) = SS.spanAntitone (\s -> p `onSegment` s) after'-+ (mid2, after) = SS.spanAntitone (intersects p . view core) after'+ xCoordAt' y sa = xCoordAt y (sa^.core) -- | Given a point and the linesegements that contain it. Create a piece of -- status structure for it. toStatusStruct :: (Fractional r, Ord r)- => Point 2 r -> [LineSegment 2 p r] -> StatusStructure p r+ => Point 2 r -> [LineSegment 2 p r :+ e] -> StatusStructure p r e toStatusStruct p xs = ss `SS.join` hors -- ss { SS.nav = ordAtNav $ p^.yCoord } `SS.join` hors where (hors',rest) = L.partition isHorizontal xs- ss = SS.fromListBy (ordAt $ maxY xs) rest+ ss = SS.fromListBy (ordAtY' $ maxY xs) rest hors = SS.fromListBy (comparing rightEndpoint) hors' - isHorizontal s = s^.start.core.yCoord == s^.end.core.yCoord+ isHorizontal s = s^.core.start.core.yCoord == s^.core.end.core.yCoord + ordAtY' q sa sb = ordAtY q (sa^.core) (sb^.core)+ -- find the y coord of the first interesting thing below the sweep at y maxY = maximum . filter (< p^.yCoord)- . concatMap (\s -> [s^.start.core.yCoord,s^.end.core.yCoord])+ . concatMap (\s -> [s^.core.start.core.yCoord,s^.core.end.core.yCoord]) -- | Get the right endpoint of a segment-rightEndpoint :: Ord r => LineSegment 2 p r -> r-rightEndpoint s = (s^.start.core.xCoord) `max` (s^.end.core.xCoord)+rightEndpoint :: Ord r => LineSegment 2 p r :+ e -> r+rightEndpoint s = (s^.core.start.core.xCoord) `max` (s^.core.end.core.xCoord) -- | Test if a segment ends at p-endsAt :: Ord r => Point 2 r -> LineSegment 2 p r -> Bool-endsAt p (LineSegment' a b) = all (\q -> ordPoints (q^.core) p /= GT) [a,b]+endsAt :: Eq r => Point 2 r -> LineSegment 2 p r :+ e -> Bool+endsAt p (LineSegment' _ (b :+ _) :+ _) = p == b+ -- all (\q -> ordPoints (q^.core) p /= GT) [a,b] -------------------------------------------------------------------------------- -- * Finding New events -- | Find all events findNewEvent :: (Ord r, Fractional r)- => Point 2 r -> LineSegment 2 p r -> LineSegment 2 p r- -> Maybe (Event p r)-findNewEvent p l r = match (l `intersect` r) $- (H $ \NoIntersection -> Nothing)- :& (H $ \q -> if ordPoints q p == GT then Just (Event q Intersection)- else Nothing)- :& (H $ \_ -> Nothing) -- full segment intersectsions are handled- -- at insertion time+ => Point 2 r -> LineSegment 2 p r :+ e -> LineSegment 2 p r :+ e+ -> Maybe (Event p r e)+findNewEvent p l r = match ((l^.core) `intersect` (r^.core)) $+ H (const Nothing) -- NoIntersection+ :& H (\q -> if ordPoints q p == GT then Just (Event q Intersection)+ else Nothing)+ :& H (const Nothing) -- full segment intersectsions are handled+ -- at insertion time :& RNil++++type R = Rational++seg1, seg2 :: LineSegment 2 () R+seg1 = ClosedLineSegment (ext $ Point2 0 0) (ext $ Point2 0 10)+seg2 = ClosedLineSegment (ext $ Point2 0 1) (ext $ Point2 0 5)++++--------------------------------------------------------------------------------+-- *++-- | Given a predicate p on elements, and a predicate q on+-- (neighbouring) pairs of elements, filter the elements that satisfy+-- p, or together with one of their neighbours satisfy q.+overlapsOr :: (a -> Bool)+ -> (a -> a -> Bool)+ -> [a]+ -> [a]+overlapsOr p q = map fst . filter snd . map (\((a,b),b') -> (a, b || b'))+ . overlapsWithNeighbour (q `on` fst)+ . map (\x -> (x, p x))++-- | Given a predicate, test and a list, annotate each element whether+-- it, together with one of its neighbors satisifies the predicate.+overlapsWithNeighbour :: (a -> a -> Bool) -> [a] -> [(a,Bool)]+overlapsWithNeighbour p = go0+ where+ go0 = \case+ [] -> []+ (x:xs) -> go x False xs++ go x b = \case+ [] -> []+ (y:ys) -> let b' = p x y+ in (x,b || b') : go y b' ys++-- annotateReport :: (a -> Bool) -> [a] -> [(a,Bool)]+-- annotateReport p = map (\x -> (x, p x))++overlapsWithNext' :: (a -> a -> Bool) -> [a] -> [(a,Bool)]+overlapsWithNext' p = go+ where+ go = \case+ [] -> []+ [x] -> [(x,False)]+ (x:xs@(y:_)) -> (x,p x y) : go xs++overlapsWithPrev' :: (a -> a -> Bool) -> [a] -> [(a,Bool)]+overlapsWithPrev' p = go0+ where+ go0 = \case+ [] -> []+ (x:xs) -> (x,False) : go x xs++ go x = \case+ [] -> []+ (y:ys) -> (y,p x y) : go y ys+++overlapsWithNeighbour2 p = map (\((a,b),b') -> (a, b || b'))+ . overlapsWithNext' (p `on` fst)+ . overlapsWithPrev' p++shouldBe :: Eq a => a -> a -> Bool+shouldBe = (==)++propSameAsSeparate p xs = overlapsWithNeighbour p xs `shouldBe` overlapsWithNeighbour2 p xs++test' = overlapsWithNeighbour (==) testOverlapNext+testOverlapNext = [1,2,3,3,3,5,6,6,8,10,11,34,2,2,3]
+ src/Algorithms/Geometry/LineSegmentIntersection/BooleanSweep.hs view
@@ -0,0 +1,190 @@+{-# LANGUAGE ScopedTypeVariables #-}+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.LineSegmentIntersection.BooleanSweep+-- Copyright : (C) Frank Staals, David Himmelstrup+-- License : see the LICENSE file+-- Maintainer : David Himmelstrup+--+-- \( O(n \log n) \) algorithm for determining if any two sets of line segments intersect.+--+-- Shamos and Hoey.+--+--------------------------------------------------------------------------------+module Algorithms.Geometry.LineSegmentIntersection.BooleanSweep+ ( hasIntersections+ ) where++import Control.Lens hiding (contains)+import Data.Ext+import Data.Geometry.Interval+import Data.Geometry.LineSegment+import Data.Geometry.Point++import Data.Intersection+import qualified Data.List as L+import Data.Maybe+import Data.Ord (Down (..), comparing)+import qualified Data.Set as SS+import qualified Data.Set.Util as SS++-- import Data.RealNumber.Rational+import Debug.Trace+import Data.Geometry.Polygon++--------------------------------------------------------------------------------++-- | Tests if there are any intersections.+--+-- \(O(n\log n)\)+hasIntersections :: (Ord r, Num r)+ => [LineSegment 2 p r :+ e] -> Bool+hasIntersections ss = sweep pts SS.empty+ where+ pts = L.sortBy ordEvents . concatMap asEventPts $ ss++-- | Computes the event points for a given line segment+asEventPts :: Ord r => LineSegment 2 p r :+ e -> [Event p r]+asEventPts (s :+ _) =+ case ordPoints (s^.start.core) (s^.end.core) of+ LT -> [Insert s, Delete s]+ _ -> let LineSegment a b = s+ s' = LineSegment b a+ in [Insert s', Delete s']++--------------------------------------------------------------------------------+-- * Data type for Events++-- | The actual event consists of a point and its type+data Event p r = Insert (LineSegment 2 p r) | Delete (LineSegment 2 p r)+ deriving (Show)++eventPoint :: Event p r -> Point 2 r+eventPoint (Insert l) = l^.start.core+eventPoint (Delete l) = l^.end.core++-- Sort order:+-- 1. Y-coord. Larger Ys before smaller.+-- 2. X-coord. Smaller Xs before larger.+-- 3. Type: Inserts before deletions+ordEvents :: (Num r, Ord r) => Event p r -> Event p r -> Ordering+ordEvents e1 e2 = ordPoints (eventPoint e1) (eventPoint e2) <> cmpType e1 e2+ where+ cmpType Insert{} Delete{} = LT+ cmpType Delete{} Insert{} = GT+ cmpType _ _ = EQ++-- | An ordering that is decreasing on y, increasing on x+ordPoints :: Ord r => Point 2 r -> Point 2 r -> Ordering+ordPoints a b = let f p = (Down $ p^.yCoord, p^.xCoord) in comparing f a b++--------------------------------------------------------------------------------+-- * The Main Sweep++type StatusStructure p r = SS.Set (LineSegment 2 p r)++-- | Run the sweep handling all events+sweep :: forall r p. (Ord r, Num r)+ => [Event p r] -> StatusStructure p r+ -> Bool+sweep [] _ = False+sweep (Delete l:eq) ss =+ overlaps || sweep eq ss'+ where+ p = l^.end.core+ (before,_contains,after) = splitBeforeAfter p ss+ overlaps = fromMaybe False (intersects <$> sl <*> sr)+ sl = SS.lookupMax before+ sr = SS.lookupMin after+ ss' = before `SS.join` after+sweep (Insert l@(LineSegment startPoint _endPoint):eq) ss =+ endOverlap || overlaps || sweep eq ss'+ where+ p = l^.start.core+ (before,contains,after) = splitBeforeAfter p ss++ -- Check whether the endpoint is contained in one of the existing+ -- segments. The only segments that could qualify are the ones in+ -- 'contains'. Hence check only those. Note that it is not+ -- sufficient just to check whether 'contains' is empty or not,+ -- since there may be segments whose endpoint is open and coincides with p.+ endOverlap = isClosed startPoint && any (p `intersects`) contains++ overlaps =+ or [ fromMaybe False (intersects l <$> sl)+ , fromMaybe False (intersects l <$> sr) ]+ sl = SS.lookupMax before+ sr = SS.lookupMin after+ ss' = before `SS.join` SS.singleton l `SS.join` after+++-- | split the status structure around p.+-- the result is (before,contains,after)+splitBeforeAfter :: (Num r, Ord r)+ => Point 2 r -> StatusStructure p r+ -> (StatusStructure p r, [LineSegment 2 p r],StatusStructure p r)+splitBeforeAfter p ss = (before, filter (not . endsAt p) $ SS.toList contains, after)+ where+ (before,contains,after) = SS.splitBy cmpLine ss+ cmpLine line+ | isHorizontal line =+ let [_top,bot] = L.sortBy ordPoints [line^.start.core,line^.end.core] in+ (bot^.xCoord) `compare` (p^.xCoord)+ cmpLine line =+ let [top,bot] = L.sortBy ordPoints [line^.start.core,line^.end.core] in+ case ccw bot top p of+ CW -> LT+ CoLinear -> EQ+ CCW -> GT+++isHorizontal :: Eq r => LineSegment 2 p r -> Bool+isHorizontal s = s^.start.core.yCoord == s^.end.core.yCoord++-- | Test if a segment ends at p+endsAt :: Ord r => Point 2 r -> LineSegment 2 p r -> Bool+endsAt p (LineSegment _ b) = fmap (view core) b == Open p++--------------------------------------------------------------------------------+-- * Finding New events++-- -- | Given two segments test if they intersect. Why don't we simply use 'intersect'+-- segmentsOverlap :: (Num r, Ord r) => LineSegment 2 p r -> LineSegment 2 p r -> Bool+-- segmentsOverlap a@(LineSegment aStart aEnd) b =+-- (isClosed aStart && (aStart^.unEndPoint.core) `intersects` b) ||+-- (isClosed aEnd && (aEnd^.unEndPoint.core) `intersects` b) ||+-- (opposite (ccw' (a^.start) (b^.start) (a^.end)) (ccw' (a^.start) (b^.end) (a^.end)) &&+-- not (onTriangleRelaxed (a^.end.core) t1) &&+-- not (onTriangleRelaxed (a^.start.core) t2))+-- where+-- opposite CW CCW = True+-- opposite CCW CW = True+-- opposite _ _ = False+-- t1 = Triangle (a^.start) (b^.start) (b^.end)+-- t2 = Triangle (a^.end) (b^.start) (b^.end)+++bug' = hasIntersections $ map ext $ listEdges bug++bug :: SimplePolygon () Int+bug = fromPoints . map ext $ [+ Point2 144 592+ , Point2 336 624+ , Point2 320 544+ , Point2 240 624+ ]++s1, s2 :: LineSegment 2 () Int+s1 = read "LineSegment (Closed (Point2 240 620 :+ ())) (Open (Point2 320 544 :+ ()))"+s2 = read "LineSegment (Closed (Point2 144 592 :+ ())) (Open (Point2 336 624 :+ ()))"++tr s x = traceShow (s <> " : ", x) x++edges' :: [LineSegment 2 () Int]+edges' = [ LineSegment (Closed (Point2 240 624 :+ ())) (Open (Point2 320 544 :+ ()))+-- , LineSegment (Closed (Point2 320 544 :+ ())) (Open (Point2 336 624 :+ ()))+ , LineSegment (Closed (Point2 336 624 :+ ())) (Open (Point2 144 592 :+ ()))+ , LineSegment (Closed (Point2 144 592 :+ ())) (Open (Point2 240 624 :+ ()))+ ]++-- ah, I guess it selects the wrong predecessor/successor seg, since they overlap at the endpoint.
src/Algorithms/Geometry/LineSegmentIntersection/Naive.hs view
@@ -1,52 +1,58 @@ {-# LANGUAGE ScopedTypeVariables #-}-module Algorithms.Geometry.LineSegmentIntersection.Naive where+-- | Line segment intersections in \(O(n^2)\) by checking+-- all pairs.+module Algorithms.Geometry.LineSegmentIntersection.Naive+ ( intersections+ ) where import Algorithms.Geometry.LineSegmentIntersection.Types-import Control.Lens+import Control.Lens((^.)) import Data.Ext-import Data.Geometry.Interval+-- import Data.Geometry.Interval import Data.Geometry.LineSegment import Data.Geometry.Point import Data.Geometry.Properties import qualified Data.Map as M+import Data.Util import Data.Vinyl import Data.Vinyl.CoRec+import qualified Data.List as List +-------------------------------------------------------------------------------- -- | Compute all intersections (naively) -- -- \(O(n^2)\)-intersections :: forall r p. (Ord r, Fractional r)- => [LineSegment 2 p r] -> Intersections p r-intersections = foldr collect mempty . pairs+intersections :: forall r p e. (Ord r, Fractional r)+ => [LineSegment 2 p r :+ e] -> Intersections p r e+intersections = foldr collect mempty . uniquePairs -- | Test if the two segments intersect, and if so add the segment to the map-collect :: (Ord r, Fractional r)- => (LineSegment 2 p r, LineSegment 2 p r)- -> Intersections p r -> Intersections p r-collect (s,s') m = match (s `intersect` s') $- (H $ \NoIntersection -> m)- :& (H $ \p -> handlePoint s s' p $ m)- :& (H $ \s'' -> foldr (handlePoint s s') m [s''^.start.core, s''^.end.core])+collect :: (Ord r, Fractional r)+ => Two (LineSegment 2 p r :+ e)+ -> Intersections p r e -> Intersections p r e+collect (Two s s') m = match ((s^.core) `intersect` (s'^.core)) $+ H (\NoIntersection -> m)+ :& H (\p -> handlePoint s s' p m)+ :& H (\s'' -> handlePoint s s' (topEndPoint s'') m) :& RNil --- | Add s and s' to the map with key p-handlePoint :: Ord r- => LineSegment 2 p r -> LineSegment 2 p r -> Point 2 r- -> Intersections p r -> Intersections p r-handlePoint s s' p = addTo p s . addTo p s' --- | figure out which map to add the point to-addTo :: Ord r => Point 2 r -> LineSegment 2 p r- -> Intersections p r -> Intersections p r-addTo p s- | p `isEndPointOf` s = M.insertWith (<>) p (associated [s] [])- | otherwise = M.insertWith (<>) p (associated [] [s])+topEndPoint :: Ord r => LineSegment 2 p r -> Point 2 r+topEndPoint (LineSegment' (a :+ _) (b :+ _)) = List.minimumBy ordPoints [a,b] -isEndPointOf :: Eq r => Point 2 r -> LineSegment 2 p r -> Bool-p `isEndPointOf` s = p == s^.start.core || p == s^.end.core +-- | Add s and s' to the map with key p+handlePoint :: (Ord r, Fractional r)+ => LineSegment 2 p r :+ e+ -> LineSegment 2 p r :+ e+ -> Point 2 r+ -> Intersections p r e -> Intersections p r e+handlePoint s s' p = M.insertWith (<>) p (mkAssociated p s <> mkAssociated p s') -pairs :: [a] -> [(a, a)]-pairs [] = []-pairs (x:xs) = map (x,) xs ++ pairs xs++type R = Rational++seg1, seg2 :: LineSegment 2 () R+seg1 = ClosedLineSegment (ext $ Point2 0 0) (ext $ Point2 0 10)+seg2 = ClosedLineSegment (ext $ Point2 0 1) (ext $ Point2 0 5)
src/Algorithms/Geometry/LineSegmentIntersection/Types.hs view
@@ -1,85 +1,210 @@+{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE TemplateHaskell #-}+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.LineSegmentIntersection.Types+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Algorithms.Geometry.LineSegmentIntersection.Types where +-- import Algorithms.DivideAndConquer import Control.DeepSeq import Control.Lens import Data.Ext+import Data.Bifunctor import Data.Geometry.Interval import Data.Geometry.LineSegment import Data.Geometry.Point-import Data.Geometry.Properties-import qualified Data.List as L-import Data.List.NonEmpty (NonEmpty(..))-import qualified Data.List.NonEmpty as NonEmpty import qualified Data.Map as Map+import qualified Data.Set as Set+import Data.Ord (comparing, Down(..)) import GHC.Generics+import Data.Vinyl.CoRec+import Data.Vinyl+import Data.Intersection --------------------------------------------------------------------------------- --- get the endpoints of a line segment-endPoints' :: (HasEnd s, HasStart s) => s -> (StartCore s, EndCore s)-endPoints' s = (s^.start.core,s^.end.core)+---------------------------------------------------------------------------------- -type Set' l =- Map.Map (Point (Dimension l) (NumType l), Point (Dimension l) (NumType l)) (NonEmpty l)+-- FIXME: What do we do when one segmnet lies *on* the other one. For+-- the short segment it should be an "around start", but then the+-- startpoints do not match.+--+-- for the long one it's an "on" segment, but they do not intersect -data Associated p r = Associated { _endPointOf :: Set' (LineSegment 2 p r)- , _interiorTo :: Set' (LineSegment 2 p r)- } deriving (Show, Generic) +-- | Assumes that two segments have the same start point+newtype AroundStart a = AroundStart a deriving (Show,Read,NFData,Functor) -instance (Eq p, Eq r) => Eq (Associated p r) where- (Associated es is) == (Associated es' is') = f es es' && f is is'+instance Eq r => Eq (AroundStart (LineSegment 2 p r :+ e)) where+ -- | equality on endpoint+ (AroundStart s) == (AroundStart s') = s^.core.end.core == s'^.core.end.core++instance (Ord r, Num r) => Ord (AroundStart (LineSegment 2 p r :+ e)) where+ -- | ccw ordered around their suposed common startpoint+ (AroundStart s) `compare` (AroundStart s') =+ ccwCmpAround (s^.core.start.core) (s^.core.end.core) (s'^.core.end.core)++----------------------------------------++-- | Assumes that two segments have the same end point+newtype AroundEnd a = AroundEnd a deriving (Show,Read,NFData,Functor)++instance Eq r => Eq (AroundEnd (LineSegment 2 p r :+ e)) where+ -- | equality on endpoint+ (AroundEnd s) == (AroundEnd s') = s^.core.start.core == s'^.core.start.core++instance (Ord r, Num r) => Ord (AroundEnd (LineSegment 2 p r :+ e)) where+ -- | ccw ordered around their suposed common end point+ (AroundEnd s) `compare` (AroundEnd s') =+ ccwCmpAround (s^.core.end.core) (s^.core.start.core) (s'^.core.start.core)++--------------------------------------------------------------------------------++-- | Assumes that two segments intersect in a single point.+newtype AroundIntersection a = AroundIntersection a deriving (Show,Read,NFData,Functor)++instance Eq r => Eq (AroundIntersection (LineSegment 2 p r :+ e)) where+ -- | equality ignores the p and the e types+ (AroundIntersection (s :+ _)) == (AroundIntersection (s' :+ _))+ = first (const ()) s == first (const ()) s'++instance (Ord r, Fractional r) => Ord (AroundIntersection (LineSegment 2 p r :+ e)) where+ -- | ccw ordered around their common intersection point.+ (AroundIntersection (s :+ _)) `compare` (AroundIntersection (s' :+ _)) =+ match (s `intersect` s') $+ H (\NoIntersection -> error "AroundIntersection: segments do not intersect!")+ :& H (\p -> cmpAroundP p s s')+ :& H (\_ -> (squaredLength s) `compare` (squaredLength s'))+ -- if s and s' just happen to be the same length but+ -- intersect in different behaviour from using (==).+ -- but that situation doese not satisfy the precondition+ -- of aroundIntersection anyway.+ :& RNil where- f xs ys = and $ zipWith (\(p,pa) (q,qa) -> p == q && pa `sameElements` qa)- (Map.toAscList xs) (Map.toAscList ys)+ squaredLength (LineSegment' a b) = squaredEuclideanDist (a^.core) (b^.core) - g = L.nub . NonEmpty.toList- sameElements (g -> xs) (g -> ys) = L.null $ (xs L.\\ ys) ++ (ys L.\\ xs)+-- | compare around p+cmpAroundP :: (Ord r, Num r) => Point 2 r -> LineSegment 2 p r -> LineSegment 2 p r -> Ordering+cmpAroundP p s s' = ccwCmpAround p (s^.start.core) (s'^.start.core) -instance (NFData p, NFData r) => NFData (Associated p r)+-- seg1 = ClosedLineSegment (ext $ Point2 0 0) (ext $ Point2 0 10)+-- seg2 = ClosedLineSegment (ext $ Point2 0 0) (ext $ Point2 0 10) +-------------------------------------------------------------------------------- -associated :: Ord r- => [LineSegment 2 p r] -> [LineSegment 2 p r] -> Associated p r-associated es is = Associated (f es) (f is)- where- f = foldr (\s -> Map.insertWith (<>) (endPoints' s) (s :| [])) mempty +-- | The line segments that contain a given point p may either have p+-- as the endpoint or have p in their interior.+--+-- if somehow the segment is degenerate, and p is both the start and+-- end it is reported only as the start point.+data Associated p r e =+ Associated { _startPointOf :: Set.Set (AroundEnd (LineSegment 2 p r :+ e))+ -- ^ segments for which the intersection point is the+ -- start point (i.e. s^.start.core == p)+ , _endPointOf :: Set.Set (AroundStart (LineSegment 2 p r :+ e))+ -- ^ segments for which the intersection point is the end+ -- point (i.e. s^.end.core == p)+ , _interiorTo :: Set.Set (AroundIntersection (LineSegment 2 p r :+ e))+ } deriving stock (Show, Read, Generic, Eq) -endPointOf :: Associated p r -> [LineSegment 2 p r]-endPointOf = concatMap NonEmpty.toList . Map.elems . _endPointOf+makeLenses ''Associated -interiorTo :: Associated p r -> [LineSegment 2 p r]-interiorTo = concatMap NonEmpty.toList . Map.elems . _interiorTo+instance Functor (Associated p r) where+ fmap f (Associated ss es is) = Associated (Set.mapMonotonic (g f) ss)+ (Set.mapMonotonic (g f) es)+ (Set.mapMonotonic (g f) is)+ where+ g :: forall f c e b. Functor f => (e -> b) -> f (c :+ e) -> f (c :+ b)+ g f' = fmap (&extra %~ f') -instance Ord r => Semigroup (Associated p r) where- (Associated es is) <> (Associated es' is') = Associated (es <> es') (is <> is')+-- | Reports whether this associated has any interior intersections+--+-- \(O(1)\)+isInteriorIntersection :: Associated p r e -> Bool+isInteriorIntersection = not . null . _interiorTo -instance Ord r => Monoid (Associated p r) where- mempty = Associated mempty mempty- mappend = (<>) -type Intersections p r = Map.Map (Point 2 r) (Associated p r)+-- | test if the given segment has p as its endpoint, an construct the+-- appropriate associated representing that.+--+-- pre: p intersects the segment+mkAssociated :: (Ord r, Fractional r)+ => Point 2 r -> LineSegment 2 p r :+ e-> Associated p r e+mkAssociated p s@(LineSegment a b :+ _)+ | p == a^.unEndPoint.core = mempty&startPointOf .~ Set.singleton (AroundEnd s)+ | p == b^.unEndPoint.core = mempty&endPointOf .~ Set.singleton (AroundStart s)+ | otherwise = mempty&interiorTo .~ Set.singleton (AroundIntersection s) -data IntersectionPoint p r =++-- | test if the given segment has p as its endpoint, an construct the+-- appropriate associated representing that.+--+-- If p is not one of the endpoints we concstruct an empty Associated!+--+mkAssociated' :: (Ord r, Fractional r)+ => Point 2 r -> LineSegment 2 p r :+ e -> Associated p r e+mkAssociated' p s = (mkAssociated p s)&interiorTo .~ mempty++instance (Ord r, Fractional r) => Semigroup (Associated p r e) where+ (Associated ss es is) <> (Associated ss' es' is') =+ Associated (ss <> ss') (es <> es') (is <> is')++instance (Ord r, Fractional r) => Monoid (Associated p r e) where+ mempty = Associated mempty mempty mempty++instance (NFData p, NFData r, NFData e) => NFData (Associated p r e)++-- | For each intersection point the segments intersecting there.+type Intersections p r e = Map.Map (Point 2 r) (Associated p r e)++-- | An intersection point together with all segments intersecting at+-- this point.+data IntersectionPoint p r e = IntersectionPoint { _intersectionPoint :: !(Point 2 r)- , _associatedSegs :: !(Associated p r)- } deriving (Show,Eq)+ , _associatedSegs :: !(Associated p r e)+ } deriving (Show,Read,Eq,Generic,Functor) makeLenses ''IntersectionPoint +instance (NFData p, NFData r, NFData e) => NFData (IntersectionPoint p r e) --- | reports true if there is at least one segment for which this intersection--- point is interior.------ \(O(1)\)-isEndPointIntersection :: Associated p r -> Bool-isEndPointIntersection = Map.null . _interiorTo +-- sameOrder :: (Ord r, Num r, Eq p) => Point 2 r+-- -> [LineSegment 2 p r] -> [LineSegment 2 p r] -> Bool+-- sameOrder c ss ss' = f ss == f ss'+-- where+-- f = map (^.extra) . sortAround' (ext c) . map (\s -> s^.end.core :+ s) --- newtype E a b = E (a -> b)++++-- | Given a point p, and a bunch of segments that suposedly intersect+-- at p, correctly categorize them.+mkIntersectionPoint :: (Ord r, Fractional r)+ => Point 2 r+ -> [LineSegment 2 p r :+ e] -- ^ uncategorized+ -> [LineSegment 2 p r :+ e] -- ^ segments we know contain p,+ -> IntersectionPoint p r e+mkIntersectionPoint p as cs = IntersectionPoint p $ foldMap (mkAssociated p) $ as <> cs++ -- IntersectionPoint p+ -- $ Associated mempty mempty (Set.fromAscList cs')+ -- <> foldMap (mkAssociated p) as+ -- where+ -- cs' = map AroundIntersection . List.sortBy (cmpAroundP p) $ cs+ -- -- TODO: In the bentley ottman algo we already know the sorted order of the segments+ -- -- so we can likely save the additional sort++++-- | An ordering that is decreasing on y, increasing on x+ordPoints :: Ord r => Point 2 r -> Point 2 r -> Ordering+ordPoints a b = let f p = (Down $ p^.yCoord, p^.xCoord) in comparing f a b
src/Algorithms/Geometry/LinearProgramming/LP2DRIC.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE PackageImports #-} -------------------------------------------------------------------------------- -- | -- Module : Algorithms.Geometry.LinearProgramming.LP2DRIC@@ -37,13 +38,13 @@ import Data.Util import Data.Vinyl import Data.Vinyl.CoRec-import System.Random.Shuffle+import "hgeometry-combinatorial" System.Random.Shuffle -------------------------------------------------------------------------------- -- | Solve a linear program-solveLinearProgram :: MonadRandom m => LinearProgram 2 r -> m (LPSolution 2 r)-solveLinearProgram = undefined+_solveLinearProgram :: MonadRandom m => LinearProgram 2 r -> m (LPSolution 2 r)+_solveLinearProgram = undefined -- | Solves a bounded linear program in 2d. Returns Nothing if there is no@@ -86,8 +87,8 @@ , _current :: !(Point d r) } -deriving instance (Arity d, Show r) => Show (LPState d r)-deriving instance (Arity d, Eq r) => Eq (LPState d r)+deriving instance (Arity d, Show r) => Show (LPState d r)+deriving instance (Arity d, Eq r, Fractional r) => Eq (LPState d r) obj :: Lens' (LPState d r) (Vector d r) obj = lens _obj (\(LPState _ s p) o -> LPState o s p)@@ -115,20 +116,20 @@ => Line 2 r -> [HalfSpace 2 r] -> Maybe [HalfLine 2 r]-collectOn l = sequence . mapMaybe collect . map (l `intersect`)+collectOn l = sequence . mapMaybe (collect . (l `intersect`)) where collect :: Intersection (Line 2 r) (HalfSpace 2 r) -> Maybe (Maybe (HalfLine 2 r)) collect r = match r $- (H $ \NoIntersection -> Just Nothing)- :& (H $ \hl -> Just $ Just hl)- :& (H $ \_ -> Nothing)+ H (const $ Just Nothing) -- NoIntersection+ :& H (Just . Just) -- HalfLine+ :& H (const Nothing) -- Line :& RNil -- | Given a vector v and two points a and b, determine which is smaller in direction v. cmpHalfPlane :: (Ord r, Num r, Arity d) => Vector d r -> Point d r -> Point d r -> Ordering-cmpHalfPlane v a b = case a `inHalfSpace` (HalfSpace $ HyperPlane b $ v) of+cmpHalfPlane v a b = case a `inHalfSpace` HalfSpace (HyperPlane b v) of Inside -> GT OnBoundary -> EQ Outside -> LT@@ -147,7 +148,7 @@ commonIntersection :: (Ord r, Num r, Arity d) => Line d r -> NonEmpty.NonEmpty (HalfLine d r :+ a)- -> Either (Two ((HalfLine d r :+ a)))+ -> Either (Two (HalfLine d r :+ a)) (OneOrTwo (Point d r :+ a)) commonIntersection (Line _ v) hls = case (nh,ph) of (Nothing,Nothing) -> error "absurd; this case cannot occur"@@ -159,7 +160,7 @@ GT -> Right . Right $ Two (extract p) (extract n) where extract = over core (^.startPoint)- (pos,neg) = NonEmpty.partition (\hl -> hl^.core.halfLineDirection == v) $ hls+ (pos,neg) = NonEmpty.partition (\hl -> hl^.core.halfLineDirection == v) hls ph = maximumBy' (cmpHalfPlane' v) pos nh = maximumBy' (flip $ cmpHalfPlane' v) neg @@ -219,7 +220,9 @@ where Just p = asA @(Point 2 r) $ (m1^.boundingPlane._asLine) `intersect` (m2^.boundingPlane._asLine)-+initialize _ = error+ "Algorithms.Geometry.LinearProgramming.LP2DRIC.initialize requires \+ \at least two constraints." --------------------------------------------------------------------------------@@ -234,13 +237,13 @@ -- - \(c \cdot d > 0\), and -- - \(d \cdot n(h) \geq 0\), wherefor every half space \(h\). ---findD :: (Ord r, Fractional r)+_findD :: (Ord r, Fractional r) => LinearProgram 2 r -> Maybe (Vector 2 r)-findD (LinearProgram c hs) = do hls <- collectOn nl hs'- d <- toVec <$> oneDLinearProgramming v nl hls- -- the direction v here does not really matter- if c `dot` d > 0 then pure d- else Nothing+_findD (LinearProgram c hs) = do hls <- collectOn nl hs'+ d <- toVec <$> oneDLinearProgramming v nl hls+ -- the direction v here does not really matter+ if c `dot` d > 0 then pure d+ else Nothing where -- we interpret the points on nl as directions w.r.t the origin nl@(Line _ v) = perpendicularTo (Line (origin .+^ c) c)@@ -248,14 +251,14 @@ -- every halfspace creates an allowed set of directions, modelled by a -- half-line on nl- toHL h = let n = h^.boundingPlane.normalVec+ toHL h = let _n = h^.boundingPlane.normalVec in undefined -- | Either finds an unbounded Haflline, or evidence the two halfspaces that provide -- evidence that no solution exists-findUnBoundedHalfLine :: LinearProgram 2 r -> Either (Two (HalfSpace 2 r)) (HalfLine 2 r)-findUnBoundedHalfLine = undefined -- use findD then find the starting point+_findUnBoundedHalfLine :: LinearProgram 2 r -> Either (Two (HalfSpace 2 r)) (HalfLine 2 r)+_findUnBoundedHalfLine = undefined -- use findD then find the starting point
src/Algorithms/Geometry/LinearProgramming/Types.hs view
@@ -26,14 +26,14 @@ | UnBounded (HalfLine d r) makePrisms ''LPSolution -deriving instance (Arity d, Show r) => Show (LPSolution d r)-deriving instance (Arity d, Eq r) => Eq (LPSolution d r)+deriving instance (Arity d, Show r) => Show (LPSolution d r)+deriving instance (Arity d, Eq r, Fractional r) => Eq (LPSolution d r) data LinearProgram d r = LinearProgram { _objective :: !(Vector d r) , _constraints :: [HalfSpace d r] } makeLenses ''LinearProgram -deriving instance Arity d => Functor (LinearProgram d)-deriving instance (Arity d, Show r) => Show (LinearProgram d r)-deriving instance (Arity d, Eq r) => Eq (LinearProgram d r)+deriving instance Arity d => Functor (LinearProgram d)+deriving instance (Arity d, Show r) => Show (LinearProgram d r)+deriving instance (Arity d, Fractional r, Eq r) => Eq (LinearProgram d r)
src/Algorithms/Geometry/LowerEnvelope/DualCH.hs view
@@ -1,4 +1,11 @@ {-# LANGUAGE ScopedTypeVariables #-}+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.LowerEnvelope.DualCH+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Algorithms.Geometry.LowerEnvelope.DualCH where import Data.Maybe(fromJust)@@ -29,7 +36,7 @@ -- the upper convex hull. It uses the given algorithm to do so -- -- running time: O(time required by the given upper hull algorithm)-lowerEnvelopeWith :: (Fractional r, Eq r)+lowerEnvelopeWith :: (Fractional r, Ord r) => UpperHullAlgorithm (Line 2 r :+ a) r -> NonEmpty (Line 2 r :+ a) -> Envelope a r lowerEnvelopeWith chAlgo = fromPts . chAlgo . toPts
src/Algorithms/Geometry/PolyLineSimplification/DouglasPeucker.hs view
@@ -1,3 +1,10 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.PolyLineSimplification.DouglasPeucker+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Algorithms.Geometry.PolyLineSimplification.DouglasPeucker where import Control.Lens hiding (only)@@ -14,23 +21,23 @@ -------------------------------------------------------------------------------- -- | Line simplification with the well-known Douglas Peucker alogrithm. Given a distance--- value eps adn a polyline pl, constructs a simplification of pl (i.e. with+-- value eps and a polyline pl, constructs a simplification of pl (i.e. with -- vertices from pl) s.t. all other vertices are within dist eps to the -- original polyline. ----- Running time: O(n^2) worst case, O(n log n) expected.+-- Running time: \( O(n^2) \) worst case, \( O(n log n) \) on average. douglasPeucker :: (Ord r, Fractional r, Arity d) => r -> PolyLine d p r -> PolyLine d p r douglasPeucker eps pl- | dst <= (eps*eps) = fromPoints [a,b]+ | dst <= (eps*eps) = fromPointsUnsafe [a,b] -- at least two points, so we are fine. | otherwise = douglasPeucker eps pref `merge` douglasPeucker eps subf where- pts = pl^.points- a = LSeq.head pts- b = LSeq.last pts- (i,dst) = maxDist pts (ClosedLineSegment a b)+ pts = pl^.points+ a = LSeq.head pts+ b = LSeq.last pts+ (i,dst) = maxDist pts (ClosedLineSegment a b) - (pref,subf) = split i pl+ (pref,subf) = split i pl -------------------------------------------------------------------------------- -- * Internal functions
+ src/Algorithms/Geometry/PolyLineSimplification/ImaiIri.hs view
@@ -0,0 +1,138 @@+-- |+-- Module : Algorithms.Geometry.PolyLineSimplification.ImaiIri+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Algorithms.Geometry.PolyLineSimplification.ImaiIri+ ( simplify+ , simplifyWith+ ) where++import Algorithms.Graph.BFS (bfs')+import Control.Lens+import Data.Ext+import qualified Data.Foldable as F+import Data.Geometry.LineSegment+import Data.Geometry.Point+import Data.Geometry.PolyLine+import Data.Geometry.Vector+import qualified Data.LSeq as LSeq+import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Sequence as Seq+import Data.Tree+import qualified Data.Vector as V+import Witherable++-- import Data.RealNumber.Rational+-- type R = RealNumber 5++--------------------------------------------------------------------------------++-- | Line simplification with the Imai-Iri alogrithm. Given a distance+-- value eps and a polyline pl, constructs a simplification of pl+-- (i.e. with vertices from pl) s.t. all other vertices are within+-- dist eps to the original polyline.+--+-- Running time: \( O(n^2) \) time.+simplify :: (Ord r, Fractional r, Arity d)+ => r -> PolyLine d p r -> PolyLine d p r+simplify eps = simplifyWith $ \shortcut subPoly -> all (closeTo shortcut) (subPoly^.points)+ where+ closeTo seg (p :+ _) = sqDistanceToSeg p seg <= epsSq+ epsSq = eps*eps++-- | Given a function that tests if the shortcut is valid, compute a+-- simplification using the Imai-Iri algorithm.+--+-- Running time: \( O(Tn^2 \) time, where \(T\) is the time to+-- evaluate the predicate.+simplifyWith :: (LineSegment d p r -> PolyLine d p r -> Bool)+ -> PolyLine d p r -> PolyLine d p r+simplifyWith isValid pl = pl&points %~ (LSeq.promise @2 . extract path)+ where+ g = mkGraph isValid pl+ spt = bfs' 0 g+ path = case pathsTo (pl^.points.to F.length - 1) spt of+ [] -> error "no path found?"+ (pth:_) -> pth++----------------------------------------++type Graph = V.Vector [Int]++-- | Constructs the shortcut graph+mkGraph :: (LineSegment d p r -> PolyLine d p r -> Bool) -> PolyLine d p r -> Graph+mkGraph isValid = flip V.snoc [] . V.imap f . V.fromList . F.toList . allPrefixes+ where+ f i subPl = catMaybes+ $ zipWith isValid' [i+1..] . F.toList . allSuffixes $ subPl++ isValid' j subPoly = let shortcut = ClosedLineSegment (subPoly^.start) (subPoly^.end)+ in if isValid shortcut subPoly then Just j else Nothing++-- | Generates all prefixes of the polyline; i.e. all contiguous+-- polylines all starting at the original starting point.+allPrefixes :: PolyLine d p r -> Seq.Seq (PolyLine d p r)+allPrefixes pl = mapMaybe mkPolyLine . Seq.tails . LSeq.toSeq $ pl^.points++mkPolyLine :: Seq.Seq (Point d r :+ p) -> Maybe (PolyLine d p r)+mkPolyLine = fmap PolyLine . LSeq.eval @2 . LSeq.fromSeq++-- | Generates all suffixes of the polyline.+allSuffixes :: PolyLine d p r -> Seq.Seq (PolyLine d p r)+allSuffixes pl = mapMaybe mkPolyLine . Seq.drop 2 . Seq.inits . LSeq.toSeq $ pl^.points+++++++-- | Get all paths to the particular element in the tree.+pathsTo :: Eq a => a -> Tree a -> [NonEmpty a]+pathsTo x = findPaths (== x)++-- | All paths to the nodes satisfying the predicate.+findPaths :: (a -> Bool) -> Tree a -> [NonEmpty a]+findPaths p = go+ where+ go (Node x chs) = case foldMap go chs of+ [] | p x -> [x:|[]]+ | otherwise -> []+ paths | p x -> (x:|[]) : map (x NonEmpty.<|) paths+ | otherwise -> map (x NonEmpty.<|) paths+++++-- | Given a non-empty list of indices, and some LSeq, extract the elemnets+-- on those indices.+--+-- running time: \(O(n)\)+extract :: NonEmpty Int -> LSeq.LSeq n a -> LSeq.LSeq 0 a+extract is = LSeq.fromList . extract' (F.toList is) 0 . F.toList++extract' :: [Int] -> Int -> [a] -> [a]+extract' [] _ _ = []+extract' (_:_) _ [] = []+extract' is'@(i:is) j (x:xs) | i == j = x : extract' is (j+1) xs+ | otherwise = extract' is' (j+1) xs++--------------------------------------------------------------------------------+++-- tr :: Tree Int+-- tr = Node 0 [Node 1 [], Node 2 [Node 3 [], Node 2 [], Node 4 [Node 5 []]]]++-- poly :: PolyLine 2 Int R+-- poly = case fromPoints [origin :+ 0, Point2 1 1 :+ 1, Point2 2 2 :+ 2, Point2 3 3 :+ 3] of+-- Just p -> p++-- test = Seq.fromList [0..5]++-- myTree :: Tree Int+-- myTree = Node {rootLabel = 0, subForest = [Node {rootLabel = 1, subForest = []}+-- ,Node {rootLabel = 2, subForest = []}+-- ,Node {rootLabel = 3, subForest = []}]+-- }
+ src/Algorithms/Geometry/PolygonTriangulation.hs view
@@ -0,0 +1,14 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.PolygonTriangulation+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Algorithms.Geometry.PolygonTriangulation+ ( triangulate+ , triangulate'+ , computeDiagonals+ ) where++import Algorithms.Geometry.PolygonTriangulation.Triangulate
+ src/Algorithms/Geometry/PolygonTriangulation/EarClip.hs view
@@ -0,0 +1,525 @@+{-# LANGUAGE RecordWildCards #-}+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.PolygonTriangulation.EarClip+-- Copyright : (C) David Himmelstrup+-- License : see the LICENSE file+-- Maintainer : David Himmelstrup+--+-- Ear clipping triangulation algorithms. The baseline algorithm runs in \( O(n^2) \)+-- but has a low constant factor overhead. The z-order hashed variant runs in+-- \( O(n \log n) \) time.+--+-- References:+--+-- 1. https://en.wikipedia.org/wiki/Polygon_triangulation#Ear_clipping_method+-- 2. https://en.wikipedia.org/wiki/Z-order_curve+--+--------------------------------------------------------------------------------+module Algorithms.Geometry.PolygonTriangulation.EarClip+ ( earClip+ , earClipRandom+ , earClipHashed+ , earClipRandomHashed+ , zHash+ , zUnHash+ ) where++import Control.Lens ((^.))+import Control.Monad.Identity+import Control.Monad.ST (ST, runST)+import Control.Monad.ST.Unsafe (unsafeInterleaveST)+import Data.Bits+import Data.Ext+import Data.Geometry.Boundary (PointLocationResult (Outside))+import Data.Geometry.Point (Point (Point2), ccw', pattern CCW)+import Data.Geometry.Polygon+import Data.Geometry.Box+import Data.Geometry.Triangle (Triangle (Triangle), inTriangleRelaxed)+import Data.STRef+import Data.Vector (Vector)+import qualified Data.Vector as V+import qualified Data.Vector.Algorithms.Intro as Algo+import qualified Data.Vector.Circular as CV+import qualified Data.Vector.NonEmpty as NE+import qualified Data.Vector.Unboxed as U+import qualified Data.Vector.Unboxed.Mutable as MU+import GHC.Exts (build)+import Linear.V2+import System.Random (mkStdGen, randomR)++{-+ We can check if a vertex is an ear in O(n) time. Checking all vertices will definitely+ yield at least one ear in O(n^2) time. So, finding N ears will take O(n^3) if done naively.++ Keeping a separate list of possible ears will improve matters. For each possible ear,+ we check if the vertex really is an ear or not. If it isn't, it is deleted from the+ list of possible ears. If it /is/ an ear, the vertex is cut and the neighbours are+ added back to the list of possible ears (if they aren't in the list already).++ So, start with a list of N possible ears, and we might add two vertices to the list+ ever time we find an ear. Since there are only N ears to be found, only 2*N vertices+ can be added to the list of possible ears in the worst case scenario. The list is+ therefore bounded to 3*N and finding all ears is therefore O(n^2).++ Note: When checking if a vertex is an ear, it is sufficient to check against+ reflex vertices. Some implementations keep a separate list of reflex+ vertices for this reason but it does increase the constant factor+ overhead. I think it's better to keep the constant factor low for small values+ of N and use the hashed algorithm for larger values of N.+-}+-- | \( O(n^2) \)+--+-- Returns triangular faces using absolute polygon point indices.+earClip :: (Num r, Ord r) => SimplePolygon p r -> [(Int,Int,Int)]+earClip poly = build gen+ where+ vs = NE.toVector $ CV.vector $ poly^.outerBoundaryVector+ gen :: ((Int,Int,Int) -> b -> b) -> b -> b+ gen cons nil = runST $ do+ vertices <- mutListFromVector vs+ possibleEars <- mutListClone vertices+ let worker len focus = do+ prev <- mutListPrev vertices focus+ next <- mutListNext vertices focus+ if len == 3+ then+ return $ cons (prev, focus, next) nil+ else do+ prevEar <- mutListPrev possibleEars focus+ nextEar <- mutListNext possibleEars focus+ isEar <- earCheck vertices prev focus next+ if isEar+ then do+ mutListDelete possibleEars prevEar nextEar+ mutListDelete vertices prev next -- remove ear++ case (prevEar /= prev, nextEar /= next) of+ (True, True) -> do+ mutListInsert possibleEars prevEar nextEar prev+ mutListInsert possibleEars prev nextEar next+ (True, False) -> do+ mutListInsert possibleEars prevEar nextEar prev+ (False, True) -> do+ mutListInsert possibleEars prevEar nextEar next+ (False, False) -> return ()++ cons (prev, focus, next)+ <$> unsafeInterleaveST (worker (len-1) nextEar)+ else do -- not an ear+ mutListDelete possibleEars prevEar nextEar -- remove vertex+ worker len nextEar+ worker (V.length vs) 0++-- | \( O(n^2) \)+--+-- Returns triangular faces using absolute polygon point indices.+earClipRandom :: (Num r, Ord r) => SimplePolygon p r -> [(Int,Int,Int)]+earClipRandom poly = build gen+ where+ vs = NE.toVector $ CV.vector $ poly^.outerBoundaryVector+ gen :: ((Int,Int,Int) -> b -> b) -> b -> b+ gen cons nil = runST $ do+ vertices <- mutListFromVector vs+ possibleEars <- mutListClone vertices+ shuffled <- newShuffled (V.length vs)+ let worker len = do+ focus <- popShuffled shuffled+ prev <- mutListPrev vertices focus+ next <- mutListNext vertices focus+ if len == 3+ then+ return $ cons (prev, focus, next) nil+ else do+ prevEar <- mutListPrev possibleEars focus+ nextEar <- mutListNext possibleEars focus+ isEar <- earCheck vertices prev focus next+ if isEar+ then do+ mutListDelete possibleEars prevEar nextEar+ mutListDelete vertices prev next -- remove ear++ case (prevEar /= prev, nextEar /= next) of+ (True, True) -> do+ pushShuffled shuffled prev+ pushShuffled shuffled next+ mutListInsert possibleEars prevEar nextEar prev+ mutListInsert possibleEars prev nextEar next+ (True, False) -> do+ pushShuffled shuffled prev+ mutListInsert possibleEars prevEar nextEar prev+ (False, True) -> do+ pushShuffled shuffled next+ mutListInsert possibleEars prevEar nextEar next+ (False, False) -> return ()++ cons (prev, focus, next)+ <$> unsafeInterleaveST (worker (len-1))+ else do -- not an ear+ mutListDelete possibleEars prevEar nextEar -- remove vertex+ worker len+ worker (V.length vs)++-- | \( O(n \log n) \) expected time.+--+-- Returns triangular faces using absolute polygon point indices.+earClipHashed :: Real r => SimplePolygon p r -> [(Int,Int,Int)]+earClipHashed poly = build gen+ where+ vs = NE.toVector $ CV.vector $ poly^.outerBoundaryVector+ n = V.length vs+ hasher = zHashGen vs+ zHashVec = U.generate n $ \i -> hasher (V.unsafeIndex vs i ^. core)+ gen :: ((Int,Int,Int) -> b -> b) -> b -> b+ gen cons nil = runST $ do+ vertices <- mutListFromVector vs+ zHashes <- mutListSort zHashVec+ possibleEars <- mutListClone vertices+ let worker len focus = do+ prev <- mutListPrev vertices focus+ next <- mutListNext vertices focus+ if len == 3+ then+ return $ cons (prev, focus, next) nil+ else do+ prevEar <- mutListPrev possibleEars focus+ nextEar <- mutListNext possibleEars focus+ isEar <- earCheckHashed hasher vertices zHashes prev focus next+ if isEar+ then do+ mutListDelete possibleEars prevEar nextEar+ mutListDelete vertices prev next -- remove ear+ mutListDeleteFocus zHashes focus++ case (prevEar /= prev, nextEar /= next) of+ (True, True) -> do+ mutListInsert possibleEars prevEar nextEar prev+ mutListInsert possibleEars prev nextEar next+ (True, False) -> do+ mutListInsert possibleEars prevEar nextEar prev+ (False, True) -> do+ mutListInsert possibleEars prevEar nextEar next+ (False, False) -> return ()++ cons (prev, focus, next)+ <$> unsafeInterleaveST (worker (len-1) nextEar)+ else do -- not an ear+ mutListDelete possibleEars prevEar nextEar -- remove vertex+ worker len nextEar+ worker n 0++-- | \( O(n \log n) \) expected time.+--+-- Returns triangular faces using absolute polygon point indices.+earClipRandomHashed :: Real r => SimplePolygon p r -> [(Int,Int,Int)]+earClipRandomHashed poly = build gen+ where+ vs = NE.toVector $ CV.vector $ poly^.outerBoundaryVector+ n = V.length vs+ hasher = zHashGen vs+ zHashVec = U.generate n $ \i -> hasher (V.unsafeIndex vs i ^. core)+ gen :: ((Int,Int,Int) -> b -> b) -> b -> b+ gen cons nil = runST $ do+ vertices <- mutListFromVector vs+ zHashes <- mutListSort zHashVec+ possibleEars <- mutListClone vertices+ shuffled <- newShuffled (V.length vs)+ let worker len = do+ focus <- popShuffled shuffled+ prev <- mutListPrev vertices focus+ next <- mutListNext vertices focus+ if len == 3+ then+ return $ cons (prev, focus, next) nil+ else do+ prevEar <- mutListPrev possibleEars focus+ nextEar <- mutListNext possibleEars focus+ isEar <- earCheckHashed hasher vertices zHashes prev focus next+ if isEar+ then do+ mutListDelete possibleEars prevEar nextEar+ mutListDelete vertices prev next -- remove ear+ mutListDeleteFocus zHashes focus++ case (prevEar /= prev, nextEar /= next) of+ (True, True) -> do+ pushShuffled shuffled prev+ pushShuffled shuffled next+ mutListInsert possibleEars prevEar nextEar prev+ mutListInsert possibleEars prev nextEar next+ (True, False) -> do+ pushShuffled shuffled prev+ mutListInsert possibleEars prevEar nextEar prev+ (False, True) -> do+ pushShuffled shuffled next+ mutListInsert possibleEars prevEar nextEar next+ (False, False) -> return ()++ cons (prev, focus, next)+ <$> unsafeInterleaveST (worker (len-1))+ else do -- not an ear+ mutListDelete possibleEars prevEar nextEar -- remove vertex+ worker len+ worker n++-------------------------------------------------------------------------------+-- Bounding box++-- Returns (minX, widthX, minY, heightY)+zHashGen :: Real r => V.Vector (Point 2 r :+ p) -> (Point 2 r -> Word)+zHashGen v = zHashPoint bounds+ where+ bounds = (minX, realToFrac (maxX-minX), minY, realToFrac (maxY-minY))+ bb = V.foldl1' (<>) $ V.map boundingBox v+ Point2 minX minY = minPoint bb ^. core+ Point2 maxX maxY = minPoint bb ^. core++-------------------------------------------------------------------------------+-- Z-Order+-- https://en.wikipedia.org/wiki/Z-order_curve++zHashPoint :: Real r => (r,Double,r,Double) -> Point 2 r -> Word+zHashPoint (minX, widthX, minY, heightY) (Point2 x y) =+ zHash (V2 x' y')+ where+ x' = round (realToFrac (x-minX) / widthX * zHashMax)+ y' = round (realToFrac (y-minY) / heightY * zHashMax)++zHashMax :: Double+zHashMax = realToFrac zHashMaxW++zHashMaxW :: Word+zHashMaxW = if finiteBitSize zHashMaxW == 32 then 0xFFFF else 0xFFFFFFFF++-- | O(1) Z-Order hash the first half-world of each coordinate.+zHash :: V2 Word -> Word+zHash (V2 a b) = zHashSingle a .|. (unsafeShiftL (zHashSingle b) 1)++-- | O(1) Reverse z-order hash.+zUnHash :: Word -> V2 Word+zUnHash z =+ V2 (zUnHashSingle z) (zUnHashSingle (unsafeShiftR z 1))++zHashSingle :: Word -> Word+zHashSingle w+ | finiteBitSize w == 32 = zHashSingle32 w+ | otherwise = zHashSingle64 w++zUnHashSingle :: Word -> Word+zUnHashSingle w+ | finiteBitSize w == 32 = zUnHashSingle32 w+ | otherwise = zUnHashSingle64 w++zHashSingle32 :: Word -> Word+zHashSingle32 w = runIdentity $ do+ w <- pure $ w .&. 0x0000FFFF+ w <- pure $ (w .|. unsafeShiftL w 8) .&. 0x00FF00FF+ w <- pure $ (w .|. unsafeShiftL w 4) .&. 0x0F0F0F0F+ w <- pure $ (w .|. unsafeShiftL w 2) .&. 0x33333333+ w <- pure $ (w .|. unsafeShiftL w 1) .&. 0x55555555+ pure w++zUnHashSingle32 :: Word -> Word+zUnHashSingle32 w = runIdentity $ do+ w <- pure $ w .&. 0x55555555+ w <- pure $ (w .|. unsafeShiftR w 1) .&. 0x33333333+ w <- pure $ (w .|. unsafeShiftR w 2) .&. 0x0F0F0F0F+ w <- pure $ (w .|. unsafeShiftR w 4) .&. 0x00FF00FF+ w <- pure $ (w .|. unsafeShiftR w 8) .&. 0x0000FFFF+ pure w++zHashSingle64 :: Word -> Word+zHashSingle64 w = runIdentity $ do+ w <- pure $ w .&. 0x00000000FFFFFFFF+ w <- pure $ (w .|. unsafeShiftL w 16) .&. 0x0000FFFF0000FFFF+ w <- pure $ (w .|. unsafeShiftL w 8) .&. 0x00FF00FF00FF00FF+ w <- pure $ (w .|. unsafeShiftL w 4) .&. 0x0F0F0F0F0F0F0F0F+ w <- pure $ (w .|. unsafeShiftL w 2) .&. 0x3333333333333333+ w <- pure $ (w .|. unsafeShiftL w 1) .&. 0x5555555555555555+ pure w++zUnHashSingle64 :: Word -> Word+zUnHashSingle64 w = runIdentity $ do+ w <- pure $ w .&. 0x5555555555555555+ w <- pure $ (w .|. unsafeShiftR w 1) .&. 0x3333333333333333+ w <- pure $ (w .|. unsafeShiftR w 2) .&. 0x0F0F0F0F0F0F0F0F+ w <- pure $ (w .|. unsafeShiftR w 4) .&. 0x00FF00FF00FF00FF+ w <- pure $ (w .|. unsafeShiftR w 8) .&. 0x0000FFFF0000FFFF+ w <- pure $ (w .|. unsafeShiftR w 16) .&. 0x00000000FFFFFFFF+ pure w++-------------------------------------------------------------------------------+-- Shuffled++data Shuffled s = Shuffled+ { shuffleCount :: STRef s Int+ , shuffleVector :: MU.MVector s Int }++newShuffled :: Int -> ST s (Shuffled s)+newShuffled len = Shuffled <$> newSTRef len <*> U.unsafeThaw (U.enumFromN 0 len)++popShuffled :: Shuffled s -> ST s Int+popShuffled Shuffled{..} = do+ count <- readSTRef shuffleCount+ writeSTRef shuffleCount (count-1)+ let idx = fst $ randomR (0, count-1) (mkStdGen count)+ val <- MU.unsafeRead shuffleVector idx+ MU.unsafeWrite shuffleVector idx =<< MU.unsafeRead shuffleVector (count-1)+ pure val++pushShuffled :: Shuffled s -> Int -> ST s ()+pushShuffled (Shuffled ref vector) val = do+ count <- readSTRef ref+ writeSTRef ref (count+1)+ MU.unsafeWrite vector count val++-------------------------------------------------------------------------------+-- MutList++data MutList s a = MutList+ { mutListIndex :: (Int -> a)+ , mutListNextVec :: MU.MVector s Int+ , mutListPrevVec :: MU.MVector s Int+ }++-- O(n)+mutListFromVector :: Vector a -> ST s (MutList s a)+mutListFromVector vec = MutList (V.unsafeIndex vec)+ <$> do+ arr <- U.unsafeThaw (U.enumFromN 1 (V.length vec))+ MU.unsafeWrite arr (V.length vec-1) 0+ pure arr+ <*> do+ arr <- U.unsafeThaw (U.enumFromN (-1) (V.length vec))+ MU.unsafeWrite arr 0 (V.length vec-1)+ pure arr++mutListClone :: MutList s a -> ST s (MutList s a)+mutListClone (MutList vec nextVec prevVec) = MutList vec+ <$> MU.clone nextVec+ <*> MU.clone prevVec++mutListNext :: MutList s a -> Int -> ST s Int+mutListNext m idx = MU.unsafeRead (mutListNextVec m) idx++mutListPrev :: MutList s a -> Int -> ST s Int+mutListPrev m idx = MU.unsafeRead (mutListPrevVec m) idx++mutListDelete :: MutList s a -> Int -> Int -> ST s ()+mutListDelete m prev next = do+ MU.unsafeWrite (mutListNextVec m) prev next+ MU.unsafeWrite (mutListPrevVec m) next prev++mutListDeleteFocus :: MutList s a -> Int -> ST s ()+mutListDeleteFocus m focus = do+ prev <- mutListPrev m focus+ next <- mutListNext m focus+ unless (prev == -1) $+ MU.unsafeWrite (mutListNextVec m) prev next+ unless (next == -1) $+ MU.unsafeWrite (mutListPrevVec m) next prev++mutListInsert :: MutList s a -> Int -> Int -> Int -> ST s ()+mutListInsert m before after elt = do+ MU.unsafeWrite (mutListNextVec m) before elt -- before.next = elt+ MU.unsafeWrite (mutListNextVec m) elt after -- elt.next = after+ MU.unsafeWrite (mutListPrevVec m) after elt -- after.prev = elt+ MU.unsafeWrite (mutListPrevVec m) elt before -- elt.prev = before++mutListSort :: (Ord a, MU.Unbox a) => U.Vector a -> ST s (MutList s a)+mutListSort vec = do+ sorted <- do+ arr <- U.unsafeThaw $ (U.enumFromN 0 n :: U.Vector Int)+ Algo.sortBy (\a b -> compare (U.unsafeIndex vec a) (U.unsafeIndex vec b)) arr+ U.unsafeFreeze arr++ next <- MU.new n+ prev <- MU.new n+ MU.write next+ (U.unsafeIndex sorted (n-1))+ (-1)+ forM_ [0..n-2] $ \i -> do+ MU.write next+ (U.unsafeIndex sorted i)+ (U.unsafeIndex sorted (i+1))+ MU.write prev+ (U.unsafeIndex sorted 0)+ (-1)+ forM_ [1..n-1] $ \i -> do+ MU.write prev+ (U.unsafeIndex sorted i)+ (U.unsafeIndex sorted (i-1))+ pure $ MutList (U.unsafeIndex vec) next prev+ where+ n = U.length vec++-------------------------------------------------------------------------------+-- Ear checking++-- O(n)+earCheck :: (Num r, Ord r) => MutList s (Point 2 r :+ p) -> Int -> Int -> Int -> ST s Bool+earCheck vertices a b c = do+ let pointA = mutListIndex vertices a+ pointB = mutListIndex vertices b+ pointC = mutListIndex vertices c+ trig = Triangle pointA pointB pointC++ let loop elt | elt == a = pure True+ loop elt = do+ let point = mutListIndex vertices elt ^. core+ case inTriangleRelaxed point trig of+ Outside -> loop =<< mutListNext vertices elt+ _ -> pure False+ if ccw' pointA pointB pointC == CCW+ then loop =<< mutListNext vertices c+ else pure False++-- showBinary :: (Integral a, Show a) => a -> String+-- showBinary i = showIntAtBase 2 intToDigit i ""++earCheckHashed :: Real r => (Point 2 r -> Word) -> MutList s (Point 2 r :+ p) -> MutList s Word -> Int -> Int -> Int -> ST s Bool+earCheckHashed hasher vertices zHashes a b c = do+ let pointA = mutListIndex vertices a+ pointB = mutListIndex vertices b+ pointC = mutListIndex vertices c+ trig = Triangle pointA pointB pointC+ trigBB = boundingBox trig+ lowPt = minPoint trigBB ^. core+ highPt = maxPoint trigBB ^. core+ -- (lowPt, highPt) = triangleBoundingBox trig++ minZ = hasher lowPt+ maxZ = hasher highPt++ let upwards up+ | up == -1 || upZ > maxZ = pure True+ | inTriangleRelaxed pointUp trig /= Outside = pure False+ | otherwise = upwards =<< mutListNext zHashes up+ where+ upZ = mutListIndex zHashes up+ pointUp = mutListIndex vertices up ^. core+ downwards down+ | down == -1 || downZ < minZ = pure True+ | inTriangleRelaxed pointDown trig /= Outside = pure False+ | otherwise = downwards =<< mutListPrev zHashes down+ where+ downZ = mutListIndex zHashes down+ pointDown = mutListIndex vertices down ^. core+ bidirectional up down+ | up == -1 || upZ > maxZ = downwards down+ | down == -1 || downZ < minZ = upwards up+ | up /= a && up /= b && inTriangleRelaxed pointUp trig /= Outside = pure False+ | down /= a && down /= b && inTriangleRelaxed pointDown trig /= Outside = pure False+ | otherwise = do+ up' <- mutListNext zHashes up+ down' <- mutListPrev zHashes down+ bidirectional up' down'+ where+ upZ = mutListIndex zHashes up+ downZ = mutListIndex zHashes down+ pointUp = mutListIndex vertices up ^. core+ pointDown = mutListIndex vertices down ^. core+ if ccw' pointA pointB pointC == CCW+ then bidirectional b b+ else pure False
src/Algorithms/Geometry/PolygonTriangulation/MakeMonotone.hs view
@@ -1,23 +1,27 @@-{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE ScopedTypeVariables #-}-module Algorithms.Geometry.PolygonTriangulation.MakeMonotone( makeMonotone- , computeDiagonals+{-# LANGUAGE TemplateHaskell #-}+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.PolygonTriangulation.MakeMonotone+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Algorithms.Geometry.PolygonTriangulation.MakeMonotone+ ( makeMonotone+ , computeDiagonals - , VertexType(..)- , classifyVertices- ) where+ , VertexType(..)+ , classifyVertices+ ) where -import Algorithms.Geometry.LineSegmentIntersection.BentleyOttmann ( xCoordAt- , ordAt) import Algorithms.Geometry.PolygonTriangulation.Types import Control.Lens-import Control.Monad (forM_, when) import Control.Monad.Reader import Control.Monad.State.Strict-import Control.Monad.Writer (WriterT, execWriterT,tell)+import Control.Monad.Writer (WriterT, execWriterT, tell) import Data.Bifunctor-import Data.CircularSeq (rotateL, rotateR, zip3LWith) import qualified Data.DList as DList import Data.Ext import qualified Data.Foldable as F@@ -27,16 +31,16 @@ import Data.Geometry.Polygon import qualified Data.IntMap as IntMap import qualified Data.List.NonEmpty as NonEmpty-import Data.Ord (comparing, Down(..))+import Data.Ord (Down (..), comparing) import qualified Data.Set as SS import qualified Data.Set.Util as SS import Data.Util import qualified Data.Vector as V+import qualified Data.Vector.Circular as CV import qualified Data.Vector.Mutable as MV -- import Debug.Trace--- import qualified Data.CircularSeq as CC ---------------------------------------------------------------------------------- data VertexType = Start | Merge | Split | End | Regular deriving (Show,Read,Eq)@@ -49,10 +53,10 @@ classifyVertices :: (Num r, Ord r) => Polygon t p r -> Polygon t (p :+ VertexType) r-classifyVertices p@(SimplePolygon _) = classifyVertices' p+classifyVertices p@SimplePolygon{} = classifyVertices' p classifyVertices (MultiPolygon vs h) = MultiPolygon vs' h' where- (SimplePolygon vs') = classifyVertices' $ SimplePolygon vs+ vs' = classifyVertices' vs h' = map (first (&extra %~ onHole) . classifyVertices') h -- the roles on hole vertices are slightly different@@ -70,9 +74,10 @@ classifyVertices' :: (Num r, Ord r) => SimplePolygon p r -> SimplePolygon (p :+ VertexType) r-classifyVertices' (SimplePolygon vs) =- SimplePolygon $ zip3LWith f (rotateL vs) vs (rotateR vs)+classifyVertices' poly =+ unsafeFromCircularVector $ CV.zipWith3 f (CV.rotateLeft 1 vs) vs (CV.rotateRight 1 vs) where+ vs = poly ^. outerBoundaryVector -- is the angle larger than > 180 degrees largeInteriorAngle p c n = case ccw (p^.core) (c^.core) (n^.core) of CCW -> False@@ -98,7 +103,7 @@ -------------------------------------------------------------------------------- -type Event r = Point 2 r :+ (Two (LineSegment 2 Int r))+type Event r = Point 2 r :+ Two (LineSegment 2 Int r) data StatusStruct r = SS { _statusStruct :: !(SS.Set (LineSegment 2 Int r)) , _helper :: !(IntMap.IntMap Int)@@ -109,6 +114,7 @@ ix' :: Int -> Lens' (V.Vector a) a ix' i = singular (ix i) +{- HLINT ignore computeDiagonals -} -- | Given a polygon, find a set of non-intersecting diagonals that partition -- the polygon into y-monotone pieces. --@@ -155,11 +161,11 @@ -- pre: the polygon boundary is given in counterClockwise order. -- -- running time: \(O(n\log n)\)-makeMonotone :: (Fractional r, Ord r)- => proxy s -> Polygon t p r- -> PlanarSubdivision s p PolygonEdgeType PolygonFaceData r-makeMonotone px pg = let (e:es) = listEdges pg- in constructSubdivision px e es (computeDiagonals pg)+makeMonotone :: forall s t p r. (Fractional r, Ord r)+ => Polygon t p r+ -> PlanarSubdivision s p PolygonEdgeType PolygonFaceData r+makeMonotone pg = let (e:es) = listEdges pg+ in constructSubdivision e es (computeDiagonals pg) type Sweep p r = WriterT (DList.DList (LineSegment 2 Int r)) (StateT (StatusStruct r)@@ -192,11 +198,11 @@ insertAt :: (Ord r, Fractional r) => Point 2 r -> LineSegment 2 q r -> SS.Set (LineSegment 2 q r) -> SS.Set (LineSegment 2 q r)-insertAt v = SS.insertBy (ordAt $ v^.yCoord)+insertAt v = SS.insertBy (ordAtY $ v^.yCoord) deleteAt :: (Fractional r, Ord r) => Point 2 r -> LineSegment 2 p r -> SS.Set (LineSegment 2 p r) -> SS.Set (LineSegment 2 p r)-deleteAt v = SS.deleteAllBy (ordAt $ v^.yCoord)+deleteAt v = SS.deleteAllBy (ordAtY $ v^.yCoord) handleStart :: (Fractional r, Ord r)
src/Algorithms/Geometry/PolygonTriangulation/Triangulate.hs view
@@ -1,3 +1,10 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.PolygonTriangulation.Triangulate+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Algorithms.Geometry.PolygonTriangulation.Triangulate where @@ -11,17 +18,15 @@ import Data.Geometry.LineSegment import Data.Geometry.PlanarSubdivision.Basic import Data.Geometry.Polygon-import Data.PlaneGraph (PlaneGraph) -------------------------------------------------------------------------------- -- | Triangulates a polygon of \(n\) vertices -- -- running time: \(O(n \log n)\)-triangulate :: (Ord r, Fractional r)- => proxy s -> Polygon t p r- -> PlanarSubdivision s p PolygonEdgeType PolygonFaceData r-triangulate px pg' = constructSubdivision px e es diags+triangulate :: forall s t p r. (Ord r, Fractional r)+ => Polygon t p r -> PlanarSubdivision s p PolygonEdgeType PolygonFaceData r+triangulate pg' = constructSubdivision e es diags where (pg, diags) = computeDiagonals' pg' (e:es) = listEdges pg@@ -30,10 +35,9 @@ -- | Triangulates a polygon of \(n\) vertices -- -- running time: \(O(n \log n)\)-triangulate' :: (Ord r, Fractional r)- => proxy s -> Polygon t p r- -> PlaneGraph s p PolygonEdgeType PolygonFaceData r-triangulate' px pg' = constructGraph px e es diags+triangulate' :: forall s t p r. (Ord r, Fractional r)+ => Polygon t p r -> PlaneGraph s p PolygonEdgeType PolygonFaceData r+triangulate' pg' = constructGraph e es diags where (pg, diags) = computeDiagonals' pg' (e:es) = listEdges pg@@ -56,7 +60,7 @@ computeDiagonals' pg' = (pg, monotoneDiags <> extraDiags) where pg = toCounterClockWiseOrder pg'- monotoneP = MM.makeMonotone (Identity pg) pg -- use some arbitrary proxy type+ monotoneP = MM.makeMonotone @() pg -- use some arbitrary proxy type -- outerFaceId' = outerFaceId monotoneP monotoneDiags = map (^._2.core) . filter (\e' -> e'^._2.extra == Diagonal)@@ -65,8 +69,6 @@ . lefts . map (^._2.core) . filter (\mp -> mp^._2.extra == Inside) -- triangulate only the insides -- . filter (\f -> f^._1 /= outerFaceId')- . F.toList . rawFacePolygons $ monotoneP-- -- -- we alredy know we get the polgyons in *clockwise* order, so skip the- -- -- check if it is counter clockwise- -- toCounterClockWiseOrder'' = reverseOuterBoundary+ . F.toList . internalFacePolygons $ monotoneP+ -- FIXME: we should already get all polygons in CCW order, so no+ -- need for the toClockwiseOrder' call
src/Algorithms/Geometry/PolygonTriangulation/TriangulateMonotone.hs view
@@ -1,23 +1,45 @@-module Algorithms.Geometry.PolygonTriangulation.TriangulateMonotone where+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.PolygonTriangulation.TriangulateMonotone+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Algorithms.Geometry.PolygonTriangulation.TriangulateMonotone+ ( MonotonePolygon+ , triangulate+ , triangulate'+ , computeDiagonals+ -- , LR(..)+ -- , P+ -- , Stack+ -- , chainOf+ -- , toVtx+ -- , seg+ -- , process+ -- , isInside+ -- , mergeBy+ -- , splitPolygon+ ) where +import Algorithms.Geometry.PolygonTriangulation.Types import Control.Lens-import Data.Bifunctor-import qualified Data.CircularSeq as C import Data.Ext-import qualified Data.Foldable as F+import qualified Data.Foldable as F import Data.Geometry.LineSegment+import Data.Geometry.PlanarSubdivision.Basic (PlanarSubdivision, PolygonFaceData) import Data.Geometry.Point import Data.Geometry.Polygon-import qualified Data.List as L-import Data.Ord (comparing, Down(..))+import qualified Data.List as L+import Data.Ord (Down (..), comparing)+import Data.PlaneGraph (PlaneGraph) import Data.Util-import Algorithms.Geometry.PolygonTriangulation.Types-import Data.PlaneGraph (PlaneGraph)-import Data.Geometry.PlanarSubdivision.Basic(PolygonFaceData, PlanarSubdivision)+import qualified Data.Vector.Circular.Util as CV -------------------------------------------------------------------------------- ---+-- | Y-monotone polygon. All straight horizontal lines intersects the polygon+-- no more than twice. type MonotonePolygon p r = SimplePolygon p r data LR = L | R deriving (Show,Eq)@@ -25,10 +47,9 @@ -- | Triangulates a polygon of \(n\) vertices -- -- running time: \(O(n \log n)\)-triangulate :: (Ord r, Fractional r)- => proxy s -> MonotonePolygon p r- -> PlanarSubdivision s p PolygonEdgeType PolygonFaceData r-triangulate px pg' = constructSubdivision px e es (computeDiagonals pg)+triangulate :: forall s p r. (Ord r, Fractional r)+ => MonotonePolygon p r -> PlanarSubdivision s p PolygonEdgeType PolygonFaceData r+triangulate pg' = constructSubdivision e es (computeDiagonals pg) where pg = toCounterClockWiseOrder pg' (e:es) = listEdges pg@@ -37,10 +58,9 @@ -- | Triangulates a polygon of \(n\) vertices -- -- running time: \(O(n \log n)\)-triangulate' :: (Ord r, Fractional r)- => proxy s -> MonotonePolygon p r- -> PlaneGraph s p PolygonEdgeType PolygonFaceData r-triangulate' px pg' = constructGraph px e es (computeDiagonals pg)+triangulate' :: forall s p r. (Ord r, Fractional r)+ => MonotonePolygon p r-> PlaneGraph s p PolygonEdgeType PolygonFaceData r+triangulate' pg' = constructGraph e es (computeDiagonals pg) where pg = toCounterClockWiseOrder pg' (e:es) = listEdges pg@@ -126,19 +146,18 @@ -- running time: \(O(n)\) splitPolygon :: Ord r => MonotonePolygon p r -> ([Point 2 r :+ (LR :+ p)], [Point 2 r :+ (LR :+ p)])-splitPolygon pg = bimap (f L) (f R)- . second reverse+splitPolygon pg = bimap (f L) (f R . reverse) . L.break (\v -> v^.core == vMinY)- . F.toList . C.rightElements $ vs'+ . F.toList . CV.rightElements $ vs' where f x = map (&extra %~ (x :+)) -- rotates the list to the vtx with max ycoord- Just vs' = C.findRotateTo (\v -> v^.core == vMaxY)- $ pg^.outerBoundary+ Just vs' = CV.findRotateTo (\v -> v^.core == vMaxY)+ $ pg^.outerBoundaryVector vMaxY = getY F.maximumBy vMinY = getY F.minimumBy swap' (Point2 x y) = Point2 y x- getY ff = let p = ff (comparing (^.core.to swap')) $ pg^.outerBoundary+ getY ff = let p = ff (comparing (^.core.to swap')) $ pg^.outerBoundaryVector in p^.core @@ -156,15 +175,15 @@ -testPoly5 :: SimplePolygon () Rational-testPoly5 = toCounterClockWiseOrder . fromPoints $ map ext $ [ Point2 176 736- , Point2 240 688- , Point2 240 608- , Point2 128 576- , Point2 64 640- , Point2 80 720- , Point2 128 752- ]+-- testPoly5 :: SimplePolygon () Rational+-- testPoly5 = toCounterClockWiseOrder . fromPoints $ map ext [ Point2 176 736+-- , Point2 240 688+-- , Point2 240 608+-- , Point2 128 576+-- , Point2 64 640+-- , Point2 80 720+-- , Point2 128 752+-- ] -- testPoly5 :: SimplePolygon () Rational
src/Algorithms/Geometry/PolygonTriangulation/Types.hs view
@@ -1,4 +1,11 @@ {-# LANGUAGE ScopedTypeVariables #-}+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.PolygonTriangulation.Types+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Algorithms.Geometry.PolygonTriangulation.Types where import Control.Lens@@ -15,6 +22,7 @@ -------------------------------------------------------------------------------- +-- | After triangulation, edges are either from the original polygon or a new diagonal. data PolygonEdgeType = Original | Diagonal deriving (Show,Read,Eq) @@ -23,16 +31,15 @@ -- -- -- running time: \(O(n\log n)\)-constructSubdivision :: forall proxy r s p. (Fractional r, Ord r)- => proxy s- -> LineSegment 2 p r -- ^ A counter-clockwise- -- edge along the outer- -- boundary- -> [LineSegment 2 p r] -- ^ remaining original edges- -> [LineSegment 2 p r] -- ^ diagonals- -> PlanarSubdivision s- p PolygonEdgeType PolygonFaceData r-constructSubdivision px e origs diags = fromPlaneGraph $ constructGraph px e origs diags+constructSubdivision :: forall s r p. (Fractional r, Ord r)+ => LineSegment 2 p r -- ^ A counter-clockwise+ -- edge along the outer+ -- boundary+ -> [LineSegment 2 p r] -- ^ remaining original edges+ -> [LineSegment 2 p r] -- ^ diagonals+ -> PlanarSubdivision s+ p PolygonEdgeType PolygonFaceData r+constructSubdivision e origs diags = fromPlaneGraph $ constructGraph e origs diags -- constructSubdivision px e origs diags = -- subdiv & rawVertexData.traverse.dataVal %~ NonEmpty.head@@ -72,22 +79,21 @@ -- -- -- running time: \(O(n\log n)\)-constructGraph :: forall proxy r s p. (Fractional r, Ord r)- => proxy s- -> LineSegment 2 p r -- ^ A counter-clockwise+constructGraph :: forall s r p. (Fractional r, Ord r)+ => LineSegment 2 p r -- ^ A counter-clockwise -- edge along the outer -- boundary -> [LineSegment 2 p r] -- ^ remaining original edges -> [LineSegment 2 p r] -- ^ diagonals -> PG.PlaneGraph s p PolygonEdgeType PolygonFaceData r-constructGraph px e origs diags =+constructGraph e origs diags = subdiv & PG.vertexData.traverse %~ NonEmpty.head & PG.faceData .~ faceData' & PG.rawDartData.traverse %~ snd where subdiv :: PG.PlaneGraph s (NonEmpty p) (Bool,PolygonEdgeType) () r- subdiv = PG.fromConnectedSegments px $ e' : origs' <> diags'+ subdiv = PG.fromConnectedSegments $ e' : origs' <> diags' diags' = (:+ (True, Diagonal)) <$> diags origs' = (:+ (False,Original)) <$> origs
+ src/Algorithms/Geometry/RayShooting/Naive.hs view
@@ -0,0 +1,88 @@+module Algorithms.Geometry.RayShooting.Naive+ ( firstHit+ , firstHit'+++ , firstHitSegments+ , intersectionDistance+ , labelWithDistances+ ) where++import Control.Lens+import Data.Bifunctor+import Data.Ext+import Data.Geometry.HalfLine+import Data.Geometry.LineSegment+import Data.Geometry.Point+import Data.Geometry.Polygon+import Data.Intersection+import qualified Data.List as List+import Data.Maybe+import Data.Ord (comparing)+import Data.Vinyl.CoRec+import Data.Vinyl++--------------------------------------------------------------------------------++-- |+--+-- pre: halfline should start in the interior+firstHit :: (Fractional r, Ord r)+ => HalfLine 2 r+ -> Polygon t p r+ -> LineSegment 2 p r+firstHit ray = fromMaybe err . firstHit' ray+ where+ err = error "Algorithms.Geometry.RayShooting.Naive: no intersections; ray must have started outside the polygon"++-- | Compute the first edge hit by the ray, if it exists+firstHit' :: (Fractional r, Ord r)+ => HalfLine 2 r+ -> Polygon t p r+ -> Maybe (LineSegment 2 p r)+firstHit' ray pg = fmap (^.core) . firstHitSegments ray . map ext $ listEdges pg+++-- | Compute the first segment hit by the ray, if it exists+firstHitSegments :: (Ord r, Fractional r)+ => HalfLine 2 r+ -> [LineSegment 2 p r :+ e]+ -> Maybe (LineSegment 2 p r :+ e)+firstHitSegments ray = fmap (^.extra) . minimumOn (^.core)+ . mapMaybe (\(s :+ (md, e)) -> (:+ (s :+ e)) <$> md)+ . labelWithDistances (ray^.startPoint) ray++minimumOn :: Ord b => (a -> b) -> [a] -> Maybe a+minimumOn f = \case+ [] -> Nothing+ xs -> Just . List.minimumBy (comparing f) $ xs+++-- | Given q, a ray, and a segment s, computes if the+-- segment intersects the initial, rightward ray starting in q, and if+-- so returns the (squared) distance from q to that point together+-- with the segment.+intersectionDistance :: forall r p. (Ord r, Fractional r)+ => Point 2 r -> HalfLine 2 r -> LineSegment 2 p r+ -> Maybe r+intersectionDistance q ray s = match (seg `intersect` ray) $+ H (\NoIntersection -> Nothing)+ :& H (\p -> Just $ d p)+ :& H (\(LineSegment' (a :+ _) (b :+ _)) -> Just $ d a `min` d b)+ :& RNil+ -- TODO: there is some slight subtility if the segment is open.+ where+ d = squaredEuclideanDist q+ seg = first (const ()) s+++-- | Labels the segments with the distance from q to their+-- intersection point with the ray.+labelWithDistances :: (Ord r, Fractional r)+ => Point 2 r -> HalfLine 2 r -> [LineSegment 2 p r :+ b]+ -> [LineSegment 2 p r :+ (Maybe r, b)]+labelWithDistances q ray = map (\(s :+ e) -> s :+ (intersectionDistance q ray s, e))++++--------------------------------------------------------------------------------
src/Algorithms/Geometry/RedBlueSeparator/RIC.hs view
@@ -65,11 +65,11 @@ -> g (Point 2 r :+ blueData) -> m (Maybe (Line 2 r)) separatingLine' reds blues = case verticalSeparatingLine reds blues of- SP Nothing ((r:+_),(b :+ _)) -> separatingLine'' r b reds blues+ SP Nothing (r:+_,b :+ _) -> separatingLine'' r b reds blues -- observe that if r and b were vertically above each other then we would -- have found a separating line. So r and b are not vertically -- aligned. Hence we satisfy the precondition.- SP ml@(Just _) _ -> pure ml -- already found a line+ SP ml@(Just _) _ -> pure ml -- already found a line -- | given a red and blue point that are *NOT* vertically alligned, and all red
+ src/Algorithms/Geometry/SSSP.hs view
@@ -0,0 +1,454 @@+{-# LANGUAGE RecordWildCards #-}+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.SSSP+-- Copyright : (C) David Himmelstrup+-- License : see the LICENSE file+-- Maintainer : David Himmelstrup+--------------------------------------------------------------------------------+module Algorithms.Geometry.SSSP+ ( SSSP+ , triangulate+ , sssp+ , visibilityDual+ , visibilityFinger+ , visibilitySensitive+ ) where++import Algorithms.Geometry.PolygonTriangulation.Triangulate (triangulate')+import Algorithms.Geometry.PolygonTriangulation.Types (PolygonEdgeType)++import Algorithms.Graph.DFS (adjacencyLists, dfs', dfsSensitive)+import Control.Lens ((^.))+import Data.Bitraversable+import Data.Either+import Data.Ext (ext, extra, type (:+) (..))+import qualified Data.FingerTree as F+import Data.Geometry.Line (lineThrough)+import Data.Geometry.LineSegment (LineSegment (ClosedLineSegment, LineSegment))+import Data.Geometry.PlanarSubdivision (PolygonFaceData (..))+import Data.Geometry.Point (Point, ccw, pattern CCW, pattern CW)+import Data.Geometry.Polygon+import Data.Intersection+import Data.List (sortOn, (\\))+import Data.Maybe (fromMaybe)+import Data.PlanarGraph (PlanarGraph)+import qualified Data.PlanarGraph as Graph+import Data.PlaneGraph (FaceId (..), PlaneGraph, VertexData (..),+ VertexId, VertexId', dual, graph, incidentEdges,+ leftFace, vertices)+import qualified Data.PlaneGraph as PlaneGraph+import Data.Tree (Tree (Node))+import qualified Data.Vector as V+import qualified Data.Vector.Circular as CV+import qualified Data.Vector.Circular.Util as CV+import Data.Vector.Unboxed (Vector)+import qualified Data.Vector.Unboxed as VU+import Data.Vinyl+import Data.Vinyl.CoRec++{-+type AbsOffset = Int++data TriangulatedPolygon t p r = TriangulatedPolygon+ { triangulatedMap :: Map AbsOffset (VertexId () Primal)+ , triangulatedGraph :: PlaneGraph () AbsOffset PolygonEdgeType PolygonFaceData r+ , triangulatedPolygon :: Polygon t p r+ }+-}++++-- | Single-source shortest paths tree. Both keys and values are vertex offset ints.+--+-- @parentOf(i) = sssp[i]@+type SSSP = Vector Int++-- FIXME: The code for generating the dual cannot deal with offsets so+-- we're running 'unsafeFromPoints . toPoints' to reset the polygon.+-- Super silly. Please fix.+-- | \( O(n \log n) \)+triangulate :: forall s p r. (Ord r, Fractional r)+ => SimplePolygon p r -> PlaneGraph s Int PolygonEdgeType PolygonFaceData r+triangulate p =+ let poly' = snd $ bimapAccumL (\a _ -> (a+1,a)) (,) 0 $ unsafeFromPoints $ toPoints p+ in triangulate' @s poly'++-- | \( O(n) \) Single-Source shortest path.+sssp :: (Ord r, Fractional r)+ => PlaneGraph s Int PolygonEdgeType PolygonFaceData r+ -> SSSP+sssp trig =+ ssspFinger d+ where+ Just v0 = fst <$> V.find (\(_vid, VertexData _ idx) -> idx == 0) (vertices trig)+ v0i = incidentEdges v0 trig+ Just (FaceId firstFace) = V.find (/= FaceId outer) $ V.map (`leftFace` trig) v0i+ FaceId outer = PlaneGraph.outerFaceId trig+ dualGraph = trig^.graph.dual+ dualTree' = dfs' (V.map (filter (/= outer)) $ adjacencyLists dualGraph) firstFace+ dualVS = fmap (\v -> toCCW $ PlaneGraph.boundaryVertices (FaceId v) trig) dualTree'+ trigTree = toTrigTree trig dualVS+ d = mkDual trigTree++ toCCW v =+ let cv = CV.reverse $ CV.unsafeFromVector v+ in CV.toVector $ fromMaybe cv $ CV.findRotateTo (== v0) cv++{-+1. Find the starting face.+-}+visibilitySensitive :: forall s r. (Ord r, Fractional r, Show r)+ => PlaneGraph s Int PolygonEdgeType PolygonFaceData r+ -> SimplePolygon () r+visibilitySensitive = fromPoints . map ext . rights . visibilityFinger . visibilityDual+++visibilityDual :: forall s r. (Ord r, Fractional r)+ => PlaneGraph s Int PolygonEdgeType PolygonFaceData r+ -> Dual r+visibilityDual trig = d+ where+ Just v0 = fst <$> V.find (\(_vid, VertexData _ idx) -> idx == 0) (vertices trig)+ v0i = incidentEdges v0 trig++ outer :: VertexId s Graph.Dual+ FaceId outer = PlaneGraph.outerFaceId trig++ firstFace :: VertexId s Graph.Dual+ Just (FaceId firstFace) = V.find (/= FaceId outer) $ V.map (`leftFace` trig) v0i++ dualGraph :: PlanarGraph s Graph.Dual PolygonFaceData PolygonEdgeType (VertexData r Int)+ dualGraph = trig^.graph.dual++ dualTree' :: Tree (VertexId s Graph.Dual)+ dualTree' = dfsSensitive neigh firstFace++ neigh :: VertexId s Graph.Dual -> [VertexId s Graph.Dual]+ neigh v = V.toList $ V.filter (/=outer) $ Graph.neighboursOf v dualGraph++ dualVS :: Tree (V.Vector (VertexId' s))+ dualVS = fmap (\v -> toCCW $ PlaneGraph.boundaryVertices (FaceId v) trig) dualTree'++ trigTree :: Tree (Index r, Index r, Index r)+ trigTree = toTrigTree trig dualVS++ d :: Dual r+ d = mkDual trigTree++ toCCW v =+ let cv = CV.reverse $ CV.unsafeFromVector v+ in CV.toVector $ fromMaybe cv $ CV.findRotateTo (== v0) cv++++visibilityFinger :: forall r. (Fractional r, Ord r, Show r) => Dual r -> [Either (Int, Int, Int) (Point 2 r)]+visibilityFinger d =+ case d of+ Dual (a,b,c) ab bc ca ->+ Left (indexExtra a, indexExtra b, indexExtra c) :+ worker (Funnel (F.singleton b) a F.empty) ab +++ worker (Funnel (F.singleton c) a (F.singleton b)) bc +++ worker (Funnel F.empty a (F.singleton c)) ca+ where+ -- Final edge is the leftmost of each funnel.+ -- The most visible are the rightmost of each funnel.+ -- Cut line segment.+ worker f EmptyDual =+ let edgeA = ringAccess $ funnelRightTop f+ edgeB = ringAccess $ funnelLeftTop f+ edge = ClosedLineSegment (ext edgeA) (ext edgeB)+ coneA = ringAccess $ funnelRightBottom f+ coneB = ringAccess $ funnelLeftBottom f+ lineA = lineThrough (ringAccess $ funnelCusp f) coneA+ lineB = lineThrough (ringAccess $ funnelCusp f) coneB+ -- findIntersection :: Line 2 r -> Point 2 r+ findIntersection line =+ match (edge `intersect` line) $+ H (\NoIntersection -> error "no intersection")+ :& H (\pt -> Right pt)+ :& H (\LineSegment{} -> error "line intersection")+ :& RNil+ in [if edgeA == coneA then Right coneA else findIntersection lineA] +++ if edgeB == coneB then [] else [findIntersection lineB]+ worker f (NodeDual x l r) =+ Left (indexExtra $ fromMaybe (funnelCusp f) $ chainTop (funnelRight f)+ ,indexExtra x+ ,indexExtra $ fromMaybe (funnelCusp f) $ chainTop (funnelLeft f)) :+ case splitFunnel x f of+ (_v, fL, fR, dir) -> case dir of+ -- 'x' is to the left of the visibility cone. Everything further to the left cannot+ -- be visible to just go right.+ SplitLeft -> worker fR r -- assert cusp of fR == cusp of f+ -- 'x' is visible from our cusp. Add it to the output and go both to the left and right.+ NoSplit -> worker fR r ++ [Right (ringAccess x)] ++ worker fL l+ -- 'x' is to the right of the visibility cone. Everything further to the right cannot+ -- be visible to just go left.+ SplitRight -> worker fL l -- assert cusp of fL == cusp of f+++--------------------------------------------------------------------------------+-- SSSP (with fingertree) implementation++++++data MinMax r = MinMax (Index r) (Index r) | MinMaxEmpty deriving (Show)+instance Semigroup (MinMax r) where+ MinMaxEmpty <> b = b+ a <> MinMaxEmpty = a+ MinMax a _b <> MinMax _c d+ = MinMax a d+instance Monoid (MinMax r) where+ mempty = MinMaxEmpty++-- Including the 'Point 2 r' here means we don't have to look it up.+-- This mattered since lookups used to be O(log n) rather than O(1).+newtype Index r = Index (Point 2 r :+ Int) -- deriving (Show)++instance Show (Index r) where+ show = show . indexExtra++indexExtra :: Index r -> Int+indexExtra (Index p) = p^.extra++instance Eq (Index r) where+ Index (_ :+ a) == Index (_ :+ b) = a == b++type Chain r = F.FingerTree (MinMax r) (Index r)+data Funnel r = Funnel+ { funnelLeft :: Chain r -- Left-most element is furthest away from cusp.+ , funnelCusp :: Index r+ , funnelRight :: Chain r -- Left-most element is furthest away from cusp.+ } deriving (Show)++-- Left side of the funnel, furthest away from the cusp.+funnelLeftTop :: Funnel r -> Index r+funnelLeftTop f = fromMaybe (funnelCusp f) $ chainTop (funnelLeft f)++-- Left side of the funnel, closest to the cusp.+funnelLeftBottom :: Funnel r -> Index r+funnelLeftBottom f = fromMaybe (funnelCusp f) $ chainBottom (funnelLeft f)++-- Right side of the funnel, furthest away from the cusp.+funnelRightTop :: Funnel r -> Index r+funnelRightTop f = fromMaybe (funnelCusp f) $ chainTop (funnelRight f)++-- Right side of the funnel, closest to the cusp.+funnelRightBottom :: Funnel r -> Index r+funnelRightBottom f = fromMaybe (funnelCusp f) $ chainBottom (funnelRight f)++-- Element closest to the cusp.+chainBottom :: Chain r -> Maybe (Index r)+chainBottom chain = case F.viewl chain of+ F.EmptyL -> Nothing+ elt F.:< _ -> Just elt++-- Element furthest away from the cusp.+chainTop :: Chain r -> Maybe (Index r)+chainTop chain = case F.viewr chain of+ F.EmptyR -> Nothing+ _ F.:> elt -> Just elt++instance F.Measured (MinMax r) (Index r) where+ measure i = MinMax i i++data SplitDirection = SplitLeft | NoSplit | SplitRight+ deriving (Show)++-- Split a funnel w.r.t. a point 'x'. There are three cases:+-- 1. 'x' is visible from the cusp.+-- 2. the path to 'x' hits the left side of the funnel.+-- 3. the path to 'x' hits the right side of the funnel.+--+-- ********************************************************+-- Drawing guide:+-- \ /+-- left side of funnel -> \ / <- right side of funnel+-- \ /+-- * <- cusp+-- ********************************************************+--+-- Case 1:+-- x+-- \ /+-- \ /+-- \ /+-- *+--+-- Case 2:+--+-- x+-- \ /+-- \ /+-- \ /+-- *+--+-- Case 3:+--+-- x+-- \ /+-- \ /+-- \ /+-- *+--+-- If 'x' is visible from the cusp, then the shortest path is a straight line and we're done.+-- If 'x' is not visible from the cusp, then we find the first point up the funnel where+-- 'x' becomes visible. We'll use a fingertree to find the point in O(log(min(n,m))). Because+-- of math, this adds up to O(n) for the entire SSSP tree.+--+-- Once we've found the first point that can see 'x', we split the funnel in two: One funnel+-- that will be used for points to the left of 'x' and one funnel for points to the right of+-- 'x'. Oh, "left" and "right" here are used to indicate branches in the dual tree.+splitFunnel :: (Fractional r, Ord r) => Index r -> Funnel r -> (Index r, Funnel r, Funnel r, SplitDirection)+splitFunnel x Funnel{..}+ | isOnLeftChain =+ case doSearch isRightTurn funnelLeft of+ (lower, t, upper) ->+ ( t+ , Funnel upper t (F.singleton x)+ , Funnel (lower F.|> t F.|> x) funnelCusp funnelRight+ , SplitLeft)+ | isOnRightChain =+ case doSearch isLeftTurn funnelRight of+ (lower, t, upper) ->+ ( t+ , Funnel funnelLeft funnelCusp (lower F.|> t F.|> x)+ , Funnel (F.singleton x) t upper+ , SplitRight)+ | otherwise =+ ( funnelCusp+ , Funnel funnelLeft funnelCusp (F.singleton x)+ , Funnel (F.singleton x) funnelCusp funnelRight+ , NoSplit)+ where+ isOnLeftChain = fromMaybe False $+ isLeftTurnOrLinear cuspElt <$> leftElt <*> pure targetElt+ isOnRightChain = fromMaybe False $+ isRightTurnOrLinear cuspElt <$> rightElt <*> pure targetElt+ doSearch fn chain =+ case F.search (searchChain fn) chain of+ F.Position lower t upper -> (lower, t, upper)+ F.OnLeft -> error "cannot happen"+ F.OnRight -> error "cannot happen"+ F.Nowhere -> error "cannot happen"+ searchChain _ MinMaxEmpty _ = False+ searchChain _ _ MinMaxEmpty = True+ searchChain check (MinMax _ l) (MinMax r _) =+ check (ringAccess l) (ringAccess r) targetElt+ cuspElt = ringAccess funnelCusp+ targetElt = ringAccess x+ leftElt = ringAccess <$> chainBottom funnelLeft+ rightElt = ringAccess <$> chainBottom funnelRight++-- FIXME: Turning a list of pairs into a vector is incredibly inefficient.+-- Would be much faster to write directly into a mutable vector and+-- then freeze it at the end.+-- \( O(n) \)+ssspFinger :: (Fractional r, Ord r) => Dual r -> SSSP+ssspFinger d = toSSSP $+ case d of+ Dual (a,b,c) ab bc ca ->+ (a, a) :+ (b, a) :+ (c, a) :+ loopLeft a c ca +++ worker (Funnel (F.singleton c) a (F.singleton b)) bc +++ loopRight a b ab+ where+ toSSSP :: [(Index r,Index r)] -> SSSP+ toSSSP lst =+ VU.fromList . map snd . sortOn fst $+ [ (a,b) | (Index (_ :+ a), Index (_ :+ b)) <- lst ]+ loopLeft a outer l =+ case l of+ EmptyDual -> []+ NodeDual x l' r' ->+ (x,a) :+ worker (Funnel (F.singleton x) a (F.singleton outer)) r' +++ loopLeft a x l'+ loopRight a outer r =+ case r of+ EmptyDual -> []+ NodeDual x l' r' ->+ (x, a) :+ worker (Funnel (F.singleton outer) a (F.singleton x)) l' +++ loopRight a x r'+ worker _ EmptyDual = []+ worker f (NodeDual x l r) =+ case splitFunnel x f of+ (v, fL, fR, _) ->+ (x, v) :+ worker fL l +++ worker fR r+++--------------------------------------------------------------------------------+-- Duals++++data Dual r = Dual (Index r, Index r, Index r) -- (a,b,c)+ (DualTree r) -- borders ab+ (DualTree r) -- borders bc+ (DualTree r) -- borders ca+ deriving (Show)++data DualTree r+ = EmptyDual+ | NodeDual (Index r) -- axb triangle, a and b are from parent.+ (DualTree r) -- borders xb+ (DualTree r) -- borders ax+ deriving (Show)++toTrigTree :: PlaneGraph s Int PolygonEdgeType PolygonFaceData r+ -> Tree (V.Vector (VertexId' s))+ -> Tree (Index r,Index r,Index r)+toTrigTree trig = fmap toTrig . fmap (fmap toDat)+ where+ toTrig v = case V.toList v of+ [a,b,c] -> (a,b,c)+ _ -> error "Algorithms.Geometry.SSSP: Invalid triangulation."+ toDat v = Index $ PlaneGraph.vtxDataToExt (trig ^. PlaneGraph.vertexDataOf v)++-- pp :: Show a => Tree a -> IO ()+-- pp = putStrLn . drawTree . fmap show++mkDual :: Tree (Index r,Index r,Index r) -> Dual r+mkDual (Node (a,b,c) forest) =+ Dual (a, b, c)+ (dualTree a b forest)+ (dualTree b c forest)+ (dualTree c a forest)++dualTree :: Index r -> Index r -> [Tree (Index r,Index r,Index r)] -> DualTree r+dualTree p1 p2 (Node (a,b,c) sub:xs) =+ case [a,b,c] \\ [p1,p2] of+ [x] -> NodeDual x (dualTree x p2 sub) (dualTree p1 x sub)+ _ -> dualTree p1 p2 xs+dualTree _p1 _p2 [] = EmptyDual++++++--------------------------------------------------------------------------------+-- Helpers++ringAccess :: Index r -> Point 2 r+ringAccess (Index (pt :+ _idx)) = pt++isRightTurnOrLinear :: (Ord r, Num r) => Point 2 r -> Point 2 r -> Point 2 r -> Bool+isRightTurnOrLinear p1 p2 p3 = not $ isLeftTurn p1 p2 p3++isLeftTurnOrLinear :: (Ord r, Num r) => Point 2 r -> Point 2 r -> Point 2 r -> Bool+isLeftTurnOrLinear p1 p2 p3 = not $ isRightTurn p1 p2 p3++isLeftTurn :: (Ord r, Num r) => Point 2 r -> Point 2 r -> Point 2 r -> Bool+isLeftTurn p1 p2 p3 =+ ccw p1 p2 p3 == CCW++isRightTurn :: (Ord r, Num r) => Point 2 r -> Point 2 r -> Point 2 r -> Bool+isRightTurn p1 p2 p3 =+ ccw p1 p2 p3 == CW
+ src/Algorithms/Geometry/SSSP/Naive.hs view
@@ -0,0 +1,90 @@+{-# LANGUAGE ParallelListComp #-}+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.SSSP.Naive+-- Copyright : (C) David Himmelstrup+-- License : see the LICENSE file+-- Maintainer : David Himmelstrup+--------------------------------------------------------------------------------+module Algorithms.Geometry.SSSP.Naive+ ( sssp+ , sssp'+ ) where++import Algorithms.FloydWarshall (floydWarshall, mkGraph, mkIndex)+import Control.Lens+import Control.Monad.ST (runST)+import Data.Ext (_core, core)+import qualified Data.Foldable as F+import Data.Geometry.Interval (EndPoint (Closed, Open), end, start)+import Data.Geometry.LineSegment (LineSegment (..), sqSegmentLength)+import Data.Geometry.Point (ccwCmpAroundWith')+import Data.Geometry.Polygon (SimplePolygon, listEdges, outerBoundaryVector)+import Data.Intersection (IsIntersectableWith (intersect),+ NoIntersection (NoIntersection))+import Data.Vector (Vector)+import qualified Data.Vector as V+import qualified Data.Vector.Circular as CV+import qualified Data.Vector.Unboxed as VU+import Data.Vinyl (Rec (RNil, (:&)))+import Data.Vinyl.CoRec (Handler (H), match)+import Linear.Affine ((.-.))++type SSSP = VU.Vector Int++-- | \( O(n^3) \) Single-Source Shortest Path.+sssp :: (Real r, Fractional r) => SimplePolygon p r -> SSSP+sssp p = V.head . sssp' $ p++-- | \( O(n^3) \) Single-Source Shortest Path from all vertices.+sssp' :: (Real r, Fractional r) => SimplePolygon p r -> Vector SSSP+sssp' p = runST $ do+ -- Create an n*n matrix containing paths and distances between vertices.+ graph <- mkGraph n infinity (visibleEdges p)+ -- Use FloydWarshall O(n^3) to complete the matrix.+ floydWarshall n graph+ -- Create a tree describing the shortest path from any node to the 0th node.+ g <- VU.unsafeFreeze graph+ pure $ V.generate n $ \origin ->+ VU.generate n $ \i ->+ let (_dist, next) = g VU.! mkIndex n (i, origin)+ in next+ where+ infinity = read "Infinity" :: Double+ n = F.length (p ^. outerBoundaryVector)++-- \( O(n^3) \)+visibleEdges :: (Real r, Fractional r) => SimplePolygon p r -> [(Int, Int, Double)]+visibleEdges p = concat+ [+ [ (i, j, sqrt (realToFrac (sqSegmentLength line)))+ | j <- [i+2 .. n-1]+ , let endPt = CV.index vs j+ , let line = LineSegment (Closed pt) (Open endPt)+ -- Check if the line goes through the inside of the polygon.+ , ccwCmpAroundWith' ((_core prev) .-. (_core pt)) pt endPt next == GT+ -- Check if there are any intersections not the line end points.+ , not (interiorIntersection line edges)+ ]+ | i <- [0 .. n-1]+ , let pt = CV.index vs i+ prev = CV.index vs (i-1)+ next = CV.index vs (i+1)+ ] +++ [ (i,(i+1)`mod`n,sqrt (realToFrac (sqSegmentLength edge)))+ | (i, edge) <- zip [0..] edges+ ]+ where+ vs = p^.outerBoundaryVector+ n = F.length vs+ edges = listEdges p++interiorIntersection :: (Ord r, Fractional r) => LineSegment 2 p r -> [LineSegment 2 p r] -> Bool+interiorIntersection _ [] = False+interiorIntersection l (x:xs) =+ match (l `intersect` x) (+ H (\NoIntersection -> False)+ :& H (\pt -> pt /= l^.start.core && pt /= l^.end.core)+ :& H (\line -> sqSegmentLength line /= 0)+ :& RNil)+ || interiorIntersection l xs
+ src/Algorithms/Geometry/SmallestEnclosingBall.hs view
@@ -0,0 +1,20 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.SmallestEnclosingBall+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--+-- Types to represent the smallest enclosing disk of a set of points in+-- \(\mathbb{R}^2\)+--+--------------------------------------------------------------------------------+module Algorithms.Geometry.SmallestEnclosingBall+ ( DiskResult(..)+ , enclosingDisk+ , definingPoints+ , TwoOrThree(..)+ , twoOrThreeFromList+ ) where++import Algorithms.Geometry.SmallestEnclosingBall.Types
src/Algorithms/Geometry/SmallestEnclosingBall/Naive.hs view
@@ -9,9 +9,10 @@ -- points in \(\mathbb{R}^2\) -- ---------------------------------------------------------------------------------module Algorithms.Geometry.SmallestEnclosingBall.Naive( smallestEnclosingDisk- , enclosesAll- ) where+module Algorithms.Geometry.SmallestEnclosingBall.Naive+ ( smallestEnclosingDisk+ , enclosesAll+ ) where -- just for the types import Control.Lens@@ -22,11 +23,11 @@ import Data.List (minimumBy) import Data.Function (on) import Data.Maybe (fromMaybe)-import Data.Util(STR(..),SP(..), uniquePairs, uniqueTriplets)-+import Data.Util(uniquePairs, uniqueTriplets)+import qualified Data.Util as Util -------------------------------------------------------------------------------- --- | Horrible O(n^4) implementation that simply tries all disks, checks if they+-- | Horrible \( O(n^4) \) implementation that simply tries all disks, checks if they -- enclose all points, and takes the largest one. Basically, this is only useful -- to check correctness of the other algorithm(s) smallestEnclosingDisk :: (Ord r, Fractional r)@@ -38,12 +39,13 @@ pairs :: Fractional r => [Point 2 r :+ p] -> [DiskResult p r] pairs pts = [ DiskResult (fromDiameter (a^.core) (b^.core)) (Two a b)- | SP a b <- uniquePairs pts]+ | Util.Two a b <- uniquePairs pts] triplets :: (Ord r, Fractional r) => [Point 2 r :+ p] -> [DiskResult p r] triplets pts = [DiskResult (disk' a b c) (Three a b c)- | STR a b c <- uniqueTriplets pts]+ | Util.Three a b c <- uniqueTriplets pts] +{- HLINT ignore disk' -} disk' :: (Ord r, Fractional r) => Point 2 r :+ p -> Point 2 r :+ p -> Point 2 r :+ p -> Disk () r disk' a b c = fromMaybe degen $ disk (a^.core) (b^.core) (c^.core)@@ -56,7 +58,7 @@ smallestEnclosingDisk' :: (Ord r, Num r) => [Point 2 r :+ p] -> [DiskResult p r] -> DiskResult p r smallestEnclosingDisk' pts = minimumBy (compare `on` (^.enclosingDisk.squaredRadius))- . filter (flip enclosesAll pts)+ . filter (`enclosesAll` pts) -- | check if a disk encloses all points enclosesAll :: (Num r, Ord r) => DiskResult p r -> [Point 2 r :+ q] -> Bool
src/Algorithms/Geometry/SmallestEnclosingBall/RIC.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE TemplateHaskell #-} -------------------------------------------------------------------------------- -- | -- Module : Algorithms.Geometry.SmallestEnclosingBall.RIC@@ -31,7 +29,8 @@ import Data.Ord (comparing) import System.Random.Shuffle (shuffle) -import Debug.Trace+-- import Data.RealNumber.Rational+-- import Debug.Trace -------------------------------------------------------------------------------- @@ -47,8 +46,8 @@ => [Point 2 r :+ p] -> m (DiskResult p r) -smallestEnclosingDisk pts@(_:_:_) = ((\(p:q:pts') -> smallestEnclosingDisk' p q pts')- . F.toList) <$> shuffle pts+smallestEnclosingDisk pts@(_:_:_) = (\(p:q:pts') -> smallestEnclosingDisk' p q pts')+ . F.toList <$> shuffle pts smallestEnclosingDisk _ = error "smallestEnclosingDisk: Too few points" -- | Smallest enclosing disk.@@ -149,16 +148,32 @@ -------------------------------------------------------------------------------- -test :: Maybe (DiskResult () Rational)-test = smallestEnclosingDiskWithPoints p q myPts- where- p = ext $ Point2 0 (-6)- q = ext $ Point2 0 6+-- test :: Maybe (DiskResult () Rational)+-- test = smallestEnclosingDiskWithPoints p q myPts+-- where+-- p = ext $ Point2 0 (-6)+-- q = ext $ Point2 0 6 -myPts = map ext [Point2 5 1, Point2 3 3, Point2 (-2) 2, Point2 (-4) 5]+-- myPts = map ext [Point2 5 1, Point2 3 3, Point2 (-2) 2, Point2 (-4) 5] -disk'' r = (r:+) <$> disk (p^.core) (q^.core) (r^.core)- where- p = ext $ Point2 0 (-6)- q = ext $ Point2 0 6+-- disk'' r = (r:+) <$> disk (p^.core) (q^.core) (r^.core)+-- where+-- p = ext $ Point2 0 (-6)+-- q = ext $ Point2 0 6+++-- maartenBug :: DiskResult () Double+-- maartenBug = let (p:q:rest) = maartenBug'+-- in smallestEnclosingDisk' p q rest++-- maartenBug' :: [Point 2 Double :+ ()]+-- maartenBug' = [ Point2 (7.2784424e-3) (249.23) :+ ()+-- , Point2 (-5.188493 ) (249.23) :+ ()+-- , Point2 (-10.382694 ) (249.23) :+ ()+-- , Point2 (-15.575621 ) (249.23) :+ ()+-- , Point2 (0.0 ) (249.23) :+ ()+-- , Point2 (0.0 ) (239.9031) :+ ()+-- , Point2 (0.0 ) (230.37791) :+ ()+-- , Point2 (0.0 ) (220.67882) :+ ()+-- ]
src/Algorithms/Geometry/SmallestEnclosingBall/Types.hs view
@@ -27,11 +27,11 @@ foldMap f (Two a b) = f a <> f b foldMap f (Three a b c) = f a <> f b <> f c --fromList :: [a] -> Either String (TwoOrThree a)-fromList [a,b] = Right $ Two a b-fromList [a,b,c] = Right $ Three a b c-fromList _ = Left "Wrong number of elements"+-- | Construct datatype from list with exactly two or three elements.+twoOrThreeFromList :: [a] -> Either String (TwoOrThree a)+twoOrThreeFromList [a,b] = Right $ Two a b+twoOrThreeFromList [a,b,c] = Right $ Three a b c+twoOrThreeFromList _ = Left "Wrong number of elements"
+ src/Algorithms/Geometry/SoS.hs view
@@ -0,0 +1,235 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.SoS+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--+-- Implementation of+-- Simulation of Simplicity: A Technique to Cope with Degenerate Cases in Geometric Algorithms+--+-- By+-- Herbert Edelsbrunner and Ernst Peter Mucke+--+--------------------------------------------------------------------------------+module Algorithms.Geometry.SoS+ ( module Algorithms.Geometry.SoS.Sign+ , module Algorithms.Geometry.SoS.Orientation+ , module Algorithms.Geometry.SoS.Determinant+ ) where++-- import Algorithms.Geometry.SoS.Internal+import Algorithms.Geometry.SoS.Orientation+import Algorithms.Geometry.SoS.Determinant+import Algorithms.Geometry.SoS.Sign++--------------------------------------------------------------------------------++--------------------------------------------------------------------------------++++-- sideTest' :: ( SoS p, Dimension p ~ 2, r ~ NumType p+-- , Eq r, Num r+-- ) => [p] -> Sign+-- sideTest' (q:p1:p2:_) = sideTest q (Vector2 p1 p2)+++++++--------------------------------------------------------------------------------+++----------------------------------------+--------------------------------------------------------------------------------++--------------------------------------------------------------------------------++++++-- instance (i `CanAquire` Point d r, Arity d) => P i d r `CanAquire` Point d (R i) where+-- aquire (P i) = Point $ pure ()+++++--------------------------------------------------------------------------------++++--------------------------------------------------------------------------------+++--------------------------------------------------------------------------------+++-- -- TODO: Remove this one+-- instance HasIndex (Point d r :+ Int) where+-- indexOf = view extra+++-- test1 :: Sign+-- test1 = sideTest (Point1 1 :+ 0 :: Point 1 Int :+ Int) (Vector1 $ Point1 5 :+ 1)++-- test2 :: Sign+-- test2 = sideTest (Point1 5 :+ 0 :: Point 1 Int :+ Int) (Vector1 $ Point1 5 :+ 1)+++-- test3 :: Sign+-- test3 = sideTest (Point2 (-1) 5 :+ 0 :: Point 2 Int :+ Int) (Vector2 (Point2 0 0 :+ 1)+-- (Point2 0 10 :+ 2)+-- )+++-- pattern Point1 x = Point (Vector1 x)+++-- testV :: Sign+-- testV = simulateSimplicity sideTest' [ Point2 (-1) 5+-- , Point2 0 0+-- , Point2 0 10+-- ]++++++--------------------------------------------------------------------------------+++++++++-- cmpSignificance :: Ord k => Bag k -> Bag k -> Ordering+-- cmpSignificance (Bag e1) (Bag e2) = e1e2 `compare` e2e1+-- where+-- e1e2 = fmap fst . Map.lookupMax $ e1 `Map.difference` e2+-- e2e1 = fmap fst . Map.lookupMax $ e2 `Map.difference` e1++++-- -- | Represents a Sum of terms, i.e. a value that has the form:+-- --+-- -- \[+-- -- \sum c \Pi_{(i,j)} \varepsilon(i,j)+-- -- \]+-- newtype Symbolic i j r = Symbolic [Term i j r] deriving (Show,Eq,Functor)++-- instance (Ord i, Ord j, Num r) => Num (Symbolic i j r) where+-- (Symbolic ts) + (Symbolic ts') = Symbolic (ts `addTerms` ts')+-- negate = fmap negate+-- (Symbolic ts) * (Symbolic ts') = Symbolic $ multiplyTerms ts ts'+-- fromInteger x = constant (fromInteger x)+-- -- abs x | signum x == -1 = (-1)*x+-- -- | oterwise = x++-- -- signum = undefined+++++++++++-- -- | Adds two lists of terms+-- addTerms :: forall i j r. (Ord i, Ord j, Num r)+-- => [Term i j r] -> [Term i j r] -> [Term i j r]+-- addTerms ts ts' = (\(eps,c) -> Term c eps) <$> Map.toList m+-- where+-- m :: Map.Map (EpsFold i j) r+-- m = Map.fromListWith (+) [ (eps,c) | (Term c eps) <- ts <> ts' ]++-- multiplyTerms :: forall i j r. (Ord i, Ord j, Num r)+-- => [Term i j r] -> [Term i j r] -> [Term i j r]+-- multiplyTerms ts ts' = (\(eps,c) -> Term c eps) <$> Map.toList m+-- where+-- m :: Map.Map (EpsFold i j) r+-- m = Map.fromListWith (+) [ (es <> es',c*d) | (Term c es) <- ts, (Term d es') <- ts' ]+++++-- orderedTerms :: (Ord i, Ord j) => Symbolic i j r -> [Term i j r]+-- orderedTerms (Symbolic ts) = List.sortBy (\(Term _ e1) (Term _ e2) -> cmpSignificance e1 e2) ts++++++++++++++++++ -- zipWith (\j x -> Term x $ singleton (i,j)) [0..] . toList+++++++-- orderTerms :: (Ord i, Ord j) => Symbolic i j r -> Symbolic i j r+-- orderTerms (Symbolic ts) = Symbolic $ List.sortBy cmpSignificance ts++++-- fromPoint' :: Foldable f => i -> f r -> Symbolic i Int r+-- fromPoint' i = Symbolic . zipWith (\j x -> Term x [(i,j)]) [0..] . toList++++-- testZ :: Symbolic Int Int Int+-- testZ = (5 + 6) *++++++ -- case sign i of+ -- (-1) -> Negative $ fromInteger i+ -- 0 -> Zero+ -- _ -> Positive $ fromInteger i+ -- negate = \case+ -- Negative c -> Positive c+ -- Positive c -> Negative c+++-- newtype N = N String deriving (Show,Eq)+++-- instance Num N where+-- (N x) + (N y) = N $ x <> "+" <> y+-- (N x) * (N y) = N $ x <> y+-- negate (N x) = N ("negate(" <> x <> ")")+-- fromInteger = N . show+++-- n :: (Ord i, Ord j) => String -> i -> j -> Symbolic i j N+-- n x i j = Symbolic [Term (N x) mempty, Term 1 (singleton (i,j))]++++++-- testM3 = det33 $ V3 (fromPoint' [N "px", N "py"] <> 1)+-- (fromPoint' [N "px", N "py"] <> 1)+-- (fromPoint' [N "px", N "py"] <> 1)+-- -- (V3 (N "qx") (N "qy") 1)+-- -- (V3 (N "rx") (N "ry") 1)
+ src/Algorithms/Geometry/SoS/AsPoint.hs view
@@ -0,0 +1,27 @@+module Algorithms.Geometry.SoS.AsPoint where++import Control.CanAquire+import Data.Ext+import Data.Geometry.Point.Internal+import Data.Geometry.Properties+import Data.Geometry.Vector++--------------------------------------------------------------------------------+-- | a P is a 'read only' point in d dimensions+newtype P i d r = P i deriving (Eq, Show)++-- | Indxec type that can disambiguate points+newtype SoSIndex i = SoSIndex i deriving (Show,Eq,Ord)++instance HasIndex (P i d r) i where+ indexOf (P i) = i++instance Int `CanAquire` Point d r => P Int d r `CanAquire` Point d r where+ aquire (P i) = aquire i++type instance NumType (P i d r) = r+type instance Dimension (P i d r) = d++asPointWithIndex :: (Arity d, i `CanAquire` Point d r)+ => P i d r -> Point d r :+ SoSIndex i+asPointWithIndex (P i) = aquire i :+ SoSIndex i
+ src/Algorithms/Geometry/SoS/Determinant.hs view
@@ -0,0 +1,13 @@+module Algorithms.Geometry.SoS.Determinant where++import Algorithms.Geometry.SoS.Sign+import Algorithms.Geometry.SoS.Symbolic+import Data.Geometry.Matrix+++-- | pre: computes the sign of the determinant+signDet :: (HasDeterminant d, Ord i, Num r, Ord r) => Matrix d d (Symbolic i r) -> Sign+signDet m = case det m `compare` 0 of+ LT -> Negative+ GT -> Positive+ EQ -> error "signDet: determinant is zero! this should not happen!"
+ src/Algorithms/Geometry/SoS/Expr.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE TemplateHaskell #-}+module Algorithms.Geometry.SoS.Expr where++import Control.Lens+import qualified Data.List as List++--------------------------------------------------------------------------------++data Expr v r = Constant r+ | Negate (Expr v r)+ | Sum [Expr v r]+ | Prod [Expr v r]+ | Var v+ deriving (Show,Eq)+makePrisms ''Expr+++foldExpr :: (r -> b) -> (b -> b) -> ([b] -> b) -> ([b] -> b) -> (v -> b) -> Expr v r -> b+foldExpr con' neg' sum' prod' var' = go+ where+ go = \case+ Constant c -> con' c+ Negate e -> neg' $ go e+ Sum es -> sum' $ map go es+ Prod es -> prod' $ map go es+ Var v -> var' v++-- | Test if the expression has any variables.+hasVariables :: Expr v r -> Bool+hasVariables = foldExpr (const False)+ id+ or+ or+ (const True)++instance (Num r) => Num (Expr i r) where+ fromInteger = Constant . fromInteger+ abs _ = error "'abs' not defined for Algorithms.Geometry.SoS.Expr.Expr"+ signum _ = error "'signum' not defined for Algorithms.Geometry.SoS.Expr.Expr"+ negate = \case+ Negate e -> e+ e -> Negate e++ (Sum es) + (Sum es') = Sum $ es <> es'+ (Sum es) + e = Sum (e:es)+ e + (Sum es) = Sum (e:es)+ e + e' = Sum [e,e']++ (Prod es) * (Prod es') = Prod $ es <> es'+ (Prod es) * e = Prod (e:es)+ e * (Prod es) = Prod (e:es)+ e * e' = Prod [e,e']+++simplify :: (Num r, Eq r) => Expr v r -> Expr v r+simplify = \case+ Prod es -> case filter (isn't $ _Constant.only 1) es of+ [] -> Constant 1+ es' -> Prod $ map simplify es'+ Sum es -> case filter (isn't $ _Constant.only 0) es of+ [] -> Constant 0+ es' -> Sum $ map simplify es'+ Negate e -> Negate $ simplify e+ e -> e++prettyP :: (Show r, Show v) => Expr v r -> String+prettyP = \case+ Constant c -> show c+ Negate e -> "(-1)*(" <> prettyP e <> ")"+ Prod es -> mconcat [ "("+ , List.intercalate ")*(" (prettyP <$> es)+ , ")"+ ]+ Sum es -> mconcat [ "("+ , List.intercalate ") + (" (prettyP <$> es)+ , ")"+ ]+ Var v -> show v
+ src/Algorithms/Geometry/SoS/Internal.hs view
@@ -0,0 +1,28 @@+module Algorithms.Geometry.SoS.Internal where++import Algorithms.Geometry.SoS.AsPoint+import Algorithms.Geometry.SoS.Orientation+import Control.CanAquire+import Data.Geometry.Point.Internal++--------------------------------------------------------------------------------++-- simulateSimplicity :: forall t d r b. (Traversable t, SoSD d)+-- => (forall p. ( AsPoint p, HasIndex p+-- , d ~ Dimension p, r ~ NumType p+-- ) => t p -> b)+-- -> t (Point d r) -> b+-- simulateSimplicity = simulateSimplicity'+++-- | The actual implementation of SoS+simulateSimplicity' :: forall t d r b. (Traversable t, SoS d)+ => (forall i. ( CanAquire i (Point d r)+ , SoS d+ ) => t (P i d r) -> b)+ -> t (Point d r) -> b+simulateSimplicity' alg = runAcquire alg'+ where+ alg' :: forall i. CanAquire i (Point d r) => t i -> b+ alg' = alg . fmap (P @i @d @r)+ -- ideally the fmap would just be a coerce, but GHC does not want to do that.
+ src/Algorithms/Geometry/SoS/Orientation.hs view
@@ -0,0 +1,83 @@+module Algorithms.Geometry.SoS.Orientation( SoS++ , sideTest+ , sideTest'++ , toSymbolic+ ) where++import Algorithms.Geometry.SoS.Determinant+import Algorithms.Geometry.SoS.Sign+import Algorithms.Geometry.SoS.Symbolic+import Control.Lens hiding (snoc,cons)+import Data.Ext+import Data.Geometry.Matrix+import Data.Geometry.Point+import Data.Geometry.Vector+import GHC.TypeNats++--------------------------------------------------------------------------------++++-- | A dimension d has support for SoS when we can: compute a+-- dterminant of a d+1 by d+1 dimensional matrix.+type SoS d = (Arity d, HasDeterminant (d+1))++-- | Given a query point q, and a vector of d points defining a+-- hyperplane test if q lies above or below the hyperplane. Each point+-- is assumed to have an unique index of type i that can be used to+-- disambiguate it in case of degeneracies.+--+-- some 1D examples:+--+-- >>> sideTest (Point1 0 :+ 0) (Vector1 $ Point1 2 :+ 1)+-- Negative+-- >>> sideTest (Point1 10 :+ 0) (Vector1 $ Point1 2 :+ 1)+-- Positive+-- >>> sideTest (Point1 2 :+ 0) (Vector1 $ Point1 2 :+ 1)+-- Positive+-- >>> sideTest (Point1 2 :+ 3) (Vector1 $ Point1 2 :+ 1)+-- Negative+--+-- some 2D examples:+--+-- >>> sideTest (Point2 1 2 :+ 0) $ Vector2 (Point2 0 0 :+ 1) (Point2 2 2 :+ 3)+-- Positive+-- >>> sideTest (Point2 1 (-2) :+ 0) $ Vector2 (Point2 0 0 :+ 1) (Point2 2 2 :+ 3)+-- Negative+-- >>> sideTest (Point2 1 1 :+ 0) $ Vector2 (Point2 0 0 :+ 1) (Point2 2 2 :+ 3)+-- Positive+-- >>> sideTest (Point2 1 1 :+ 10) $ Vector2 (Point2 0 0 :+ 1) (Point2 2 2 :+ 3)+-- Negative+-- >>> sideTest (Point2 1 1 :+ 10) $ Vector2 (Point2 0 0 :+ 3) (Point2 2 2 :+ 1)+-- Negative+sideTest :: (SoS d, Num r, Ord r, Ord i)+ => Point d r :+ i -> Vector d (Point d r :+ i) -> Sign+sideTest q ps = sideTest'' . fmap toSymbolic $ cons q ps++-- | Given an input point, transform its number type to include+-- symbolic $\varepsilon$ expressions so that we can use SoS.+toSymbolic :: (Ord i, Arity d) => Point d r :+ i -> Point d (Symbolic (i,Int) r)+toSymbolic (p :+ i) = p&vector %~ imap (\j x -> symbolic x (i,j))++-- | Given a point q and a vector of d points defining a hyperplane,+-- test on which side of the hyperplane q lies.+--+-- TODO: Specify what the sign means+sideTest' :: (Num r, Ord r, Ord i, HasDeterminant (d+1), Arity d, Arity (d+1))+ => Point d (Symbolic i r) -> Vector d (Point d (Symbolic i r)) -> Sign+sideTest' q ps = sideTest'' $ cons q ps++-- | Given a vector of points, tests if the point encoded in the first+-- row is above/below the hyperplane defined by the remaining points+-- (rows).+sideTest'' :: (Num r, Ord r, Ord i, HasDeterminant (d+1), Arity d, Arity (d+1))+ => Vector (d+1) (Point d (Symbolic i r)) -> Sign+sideTest'' = signDet . Matrix . fmap mkLambdaRow++-- | Given a point produces the vector/row corresponding to this point+-- in a homogeneous matrix represetnation. I.e. we add a 1 as an+-- additonal column at the end.+mkLambdaRow :: (Num r, Arity d, Arity (d+1)) => Point d r -> Vector (d+1) r+mkLambdaRow = flip snoc 1 . view vector
+ src/Algorithms/Geometry/SoS/Sign.hs view
@@ -0,0 +1,31 @@+module Algorithms.Geometry.SoS.Sign where++import qualified Data.List as List+import Data.Maybe++--------------------------------------------------------------------------------++-- | The sign of an expression+data Sign = Negative | Positive deriving (Show,Eq,Ord,Enum,Bounded)++-- | Flip Positive <=> Negative.+flipSign :: Sign -> Sign+flipSign = \case+ Negative -> Positive+ Positive -> Negative++--------------------------------------------------------------------------------++-- | Given the terms, in decreasing order of significance, computes the sign+--+-- i.e. expects a list of terms, we base the sign on the sign of the first non-zero term.+--+-- pre: the list contains at least one such a term.+signFromTerms :: (Num r, Eq r) => [r] -> Sign+signFromTerms = List.head . mapMaybe signum'+ where+ signum' x = case signum x of+ -1 -> Just Negative+ 0 -> Nothing+ 1 -> Just Positive+ _ -> error "signum': absurd"
+ src/Algorithms/Geometry/SoS/Symbolic.hs view
@@ -0,0 +1,359 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.SoS.Symbolic+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--+--------------------------------------------------------------------------------+module Algorithms.Geometry.SoS.Symbolic(+ EpsFold+ , eps, mkEpsFold+ , hasNoPertubation+ , factors+ , suitableBase++ , Term(..), term, constantFactor++ , Symbolic+ , constant, symbolic, perturb++ , toTerms+ , signOf+ ) where++import Algorithms.Geometry.SoS.Sign (Sign(..))+import Control.Lens+import Data.Foldable (toList)+import qualified Data.List as List+import qualified Data.Map as Map+import qualified Data.Map.Merge.Strict as Map+import Data.Maybe (isNothing)+import Test.QuickCheck (Arbitrary(..), listOf)+import Test.QuickCheck.Instances ()++--------------------------------------------------------------------------------+-- * EpsFolds++{-+Let \(\mathcal{I}\) be a bag with indices, let \(c\) be an upper+bound on the number of times a single item may occur in+\(\mathcal{I}\), and let \(\varepsilon\) be a function mapping indices+to real numbers that satisfies:++1. \(0 < \varepsilon(j) < 1\), for all \(1 \leq j\),+2. \(\prod_{0 \leq i \leq j} \varepsilon(i)^c > \varepsilon(k)\), for all \(1 \leq j < k\)++Note that such a function exists:++\begin{lemma}+ \label{lem:condition_2}+ Let \(\delta \in (0,1)\) and \(d \geq c+1\). The function+ \(\varepsilon(i) = \delta^{d^i}\) satisfies condition 2.+\end{lemma}++\begin{proof}+ By transitivity it suffices to argue this for \(k=j+1\):++ \begin{align*}+ &\qquad \prod_{0 \leq i \leq j} \varepsilon(i)^c > \varepsilon(j+1) \\+ \equiv &\qquad \prod_{0 \leq i \leq j} (\delta^{d^i})^c > \delta^{d^{j+1}}\\+ \equiv &\qquad \prod_{0 \leq i \leq j} \delta^{cd^i} > \delta^{d^{j+1}} \\+ \equiv &\qquad \delta^{\sum_{0 \leq i \leq j} cd^i} > \delta^{d^{j+1}} &+ \text{using+ }+ \delta \in (0,1)\\+ \equiv &\qquad \sum_{0 \leq i \leq j} cd^i < d^{j+1} \\+ \equiv &\qquad c\sum_{0 \leq i \leq j} d^i < d^{j+1} \\+ \end{align*}++ We prove this by induction.++ For the base case \(j=0\): we have \(0 < 1\), which is trivially true.++ For the step case we have the induction hypothesis+ \(c\sum_{0 \leq i \leq j} d^i < d^{j+1}\), and we have to prove that+ \(c\sum_{0 \leq i \leq j+1} d^i < d^{j+2}\):++ \begin{align*}+ c\sum_{0 \leq i \leq j+1} d^i+ &= cd^{j+1} + c\sum_{0 \leq i \leq j} d^i \\+ &< cd^{j+1} + d^{j+1} & \text{using IH} \\+ &= (c+1)d^{j+1} & \text{using that } c+1 \leq d \\+ &\leq dd^{j+1} \\+ &=d^{j+2}+ \end{align*}+ This completes the proof.+\end{proof}+++++++An EpsFold now represents the term++\[ \prod_{i \in \mathcal{I}} \varepsilon(i) \]++for some bag \(\mathcal{I}\).+++Let \(\mathcal{J}\) be some sub-bag of \(\mathcal{I}\). Note that+condition 2 implies that:++\(\prod_{i \in \mathcal{J}} \varepsilon(i) > \varepsilon(k)\), for all \(1 \leq j < k\)++This means that when comparing two EpsFolds, say \(e_1\) and \(e_2\),+representing bags \(\mathcal{I}_1\) and \(\mathcal{I}_2\),+respectively. It suffices to compare the largest index+\(j \in \mathcal{I}_1\setminus\mathcal{I}_2\) with the largest index+\(k \in \mathcal{I}_2\setminus\mathcal{I}_1\). We have that++\(e_1 > e_2\) if and only if \(j < k\).+-}+newtype EpsFold i = Pi (Bag i) deriving (Semigroup,Monoid)++-- | Gets the factors+factors :: EpsFold i -> Bag i+factors (Pi is) = is++-- | Creates the term \(\varepsilon(i)\)+eps :: i -> EpsFold i+eps = Pi . singleton++mkEpsFold :: Ord i => [i] -> EpsFold i+mkEpsFold = Pi . foldMap singleton++++-- | computes a base 'd' that can be used as:+--+-- \( \varepsilon(i) = \varepsilon^{d^i} \)+suitableBase :: EpsFold i -> Int+suitableBase = max 2 . (1+) . maxMultiplicity . factors++instance Show i => Show (EpsFold i) where+ showsPrec d (Pi b) = showParen (d > app_prec) $+ showString "Pi " . showsPrec d (toList b)+ where+ app_prec = 10+++instance Ord i => Eq (EpsFold i) where+ e1 == e2 = (e1 `compare` e2) == EQ++instance Ord i => Ord (EpsFold i) where+ (Pi e1) `compare` (Pi e2) = k `compare` j -- note that k and j are flipped here+ where+ j = maximum' $ e1 `difference` e2+ k = maximum' $ e2 `difference` e1+ -- note: If the terms are all the same, the difference of the bags is empty+ -- and thus both e1e2 and e2e1 are Nothing and thus equal.++ -- otherwise, let j be the largest term that is in e1 but not in e2.+ -- If e2 does not have any terms at all (Nothing) it will be bigger than e1+ --+ -- if e2 does have a term, let k be the largest one, then the+ -- biggest of those terms is the pair whose indices comes first.++instance (Arbitrary i, Ord i) => Arbitrary (EpsFold i) where+ arbitrary = mkEpsFold . take 4 <$> listOf arbitrary+++-- | Test if the epsfold has no pertubation at all (i.e. if it is \(\Pi_{\emptyset}\)+hasNoPertubation :: EpsFold i -> Bool+hasNoPertubation (Pi b) = null b+++--------------------------------------------------------------------------------+-- * Terms++-- | A term 'Term c es' represents a term:+--+-- \[ c \Pi_{i \in es} \varepsilon(i)+-- \]+--+-- for a constant c and an arbitrarily small value \(\varepsilon\),+-- parameterized by i.+data Term i r = Term r (EpsFold i) deriving (Eq,Functor)++-- | Lens to access the constant 'c' in the term.+constantFactor :: Lens' (Term i r) r+constantFactor = lens (\(Term c _) -> c) (\(Term _ es) c -> Term c es)+++instance (Show i, Show r) => Show (Term i r) where+ showsPrec d (Term c es) = showParen (d > up_prec) $+ showsPrec (up_prec + 1) c+ . showString " * "+ . showsPrec (up_prec + 1) es+ where+ up_prec = 5+++-- | Creates a singleton term+term :: r -> i -> Term i r+term r i = Term r $ eps i++instance (Ord i, Ord r, Num r) => Ord (Term i r) where+ (Term c e1) `compare` (Term d e2) = case (hasNoPertubation e1, hasNoPertubation e2) of+ (True,True) -> c `compare` d+ _ -> case (signum c, signum d) of+ (-1,-1) -> e2 `compare` e1+ (0,0) -> e1 `compare` e2+ (1,1) -> e1 `compare` e2+ (-1,_) -> LT+ (_,-1) -> GT+ _ -> error "SoS: Term.ord absurd"+ -- If both the eps folds are zero, and thus we just have constants+ -- then we should compare the individual terms.++ -- if *one* of the two has an eps term, then we can choose eps to be+ -- arbitrarily small, i.e. small enough so that that terms is+ -- actually smaller than the other term. this is reflected since+ -- findMax will then return a Noting, which is smaller than anything+ -- else++ -- if both terms have epsilon terms, we first look at the sign. If+ -- they have non-negative signs we compare the eps-folds as in the+ -- paper. (Lemma 3.3). If both are negative, that reverses the+ -- ordering. If the signs are different then we can base the+ -- ordering on that.++instance (Arbitrary r, Arbitrary (EpsFold i), Ord i) => Arbitrary (Term i r) where+ arbitrary = Term <$> arbitrary <*> arbitrary++--------------------------------------------------------------------------------+-- * Symbolic++-- | Represents a Sum of terms, i.e. a value that has the form:+--+-- \[+-- \sum c \Pi_i \varepsilon(i)+-- \]+--+-- The terms are represented in order of decreasing significance.+--+-- The main idea in this type is that, if symbolic values contains+-- \(\varepsilon(i)\) terms we can always order them. That is, two+-- Symbolic terms will be equal only if:+--+-- - they contain *only* a constant term (that is equal)+-- - they contain the exact same \(\varepsilon\)-fold.+--+newtype Symbolic i r = Sum (Map.Map (EpsFold i) r) deriving (Functor)++-- | Produces a list of terms, in decreasing order of significance+toTerms :: Symbolic i r -> [Term i r]+toTerms (Sum m) = map (\(i,c) -> Term c i) . Map.toDescList $ m++-- | Computing the Sign of an expression. (Nothing represents zero)+signOf :: (Num r, Eq r) => Symbolic i r -> Maybe Sign+signOf e = case List.dropWhile (== 0) . map (\(Term c _) -> signum c) $ toTerms e of+ [] -> Nothing+ (-1:_) -> Just Negative+ _ -> Just Positive++instance (Ord i, Eq r, Num r) => Eq (Symbolic i r) where+ e1 == e2 = isNothing $ signOf (e1 - e2)++instance (Ord i, Ord r, Num r) => Ord (Symbolic i r) where+ e1 `compare` e2 = case signOf (e1 - e2) of+ Nothing -> EQ+ Just Negative -> LT+ Just Positive -> GT++instance (Ord i, Num r, Eq r) => Num (Symbolic i r) where+ (Sum e1) + (Sum e2) = Sum $ Map.merge Map.preserveMissing -- insert things only in e1+ Map.preserveMissing -- insert things only in e2+ combine+ e1 e2+ where+ -- if things are in both e1 and e2, we add the constant terms. If they are non-zero+ -- we use this value in the map. Otherwise we drop it.+ combine = Map.zipWithMaybeMatched+ (\_ c d -> let x = c + d in if x /= 0 then Just x else Nothing)+ -- Symbolic $ Map.unionWith (+) ts ts'++ negate = fmap negate++ (Sum ts) * (Sum ts') = Sum $ Map.fromListWith (+) [ (es <> es',c*d)+ | (es, c) <- Map.toList ts+ , (es',d) <- Map.toList ts'+ , c*d /= 0+ ]++ fromInteger x = constant (fromInteger x)++ signum s = case signOf s of+ Nothing -> 0+ Just Negative -> (-1)+ Just Positive -> 1++ abs x | signum x == -1 = (-1)*x+ | otherwise = x+++instance (Show i, Show r) => Show (Symbolic i r) where+ showsPrec d s = showParen (d > app_prec) $+ showString "Sum " . showsPrec d (toTerms s)+ where+ app_prec = 10++instance (Arbitrary r, Ord i, Arbitrary (EpsFold i)) => Arbitrary (Symbolic i r) where+ arbitrary = Sum <$> arbitrary++----------------------------------------++-- | Creates a constant symbolic value+constant :: Ord i => r -> Symbolic i r+constant c = Sum $ Map.singleton mempty c++-- | Creates a symbolic vlaue with a single indexed term. If you just need a constant (i.e. non-indexed), use 'constant'+symbolic :: Ord i => r -> i -> Symbolic i r+symbolic r i = Sum $ Map.singleton (eps i) r++-- | given the value c and the index i, creates the perturbed value+-- \(c + \varepsilon(i)\)+perturb :: (Num r, Ord i) => r -> i -> Symbolic i r+perturb c i = Sum $ Map.fromAscList [ (mempty,c) , (eps i,1) ]+++--------------------------------------------------------------------------------++-- | The word specifiies how many *duplicates* there are. I.e. If the+-- Bag maps k to i, then k has multiplicity i+1.+newtype Bag a = Bag (Map.Map a Int) deriving (Show,Eq,Ord,Arbitrary)++singleton :: k -> Bag k+singleton x = Bag $ Map.singleton x 0+++instance Foldable Bag where+ -- ^ Takes multiplicity into account.+ foldMap f (Bag m) =+ Map.foldMapWithKey (\k d -> foldMap f (List.replicate (fromIntegral d+1) k)) m+ null (Bag m) = Map.null m++instance Ord k => Semigroup (Bag k) where+ (Bag m) <> (Bag m') = Bag $ Map.unionWith (\d d' -> d + d' + 1) m m'++instance Ord k => Monoid (Bag k) where+ mempty = Bag Map.empty++-- | Computes the difference of the two maps+difference :: Ord a => Bag a -> Bag a -> Bag a+difference (Bag m1) (Bag m2) = Bag $ Map.differenceWith updateCount m1 m2+ where+ updateCount i j = let d = i - j -- note that we should actually compare (i+1) and (j+1)+ in if d <= 0 then Nothing -- we have no copies left+ else Just $ d - 1+++maximum' :: Bag b -> Maybe b+maximum' (Bag m) = fmap fst . Map.lookupMax $ m+++-- | maximum multiplicity of an element in the bag+maxMultiplicity :: Bag a -> Int+maxMultiplicity (Bag m) = maximum . (0:) . map (1+) . Map.elems $ m
+ src/Algorithms/Geometry/VisibilityPolygon/Lee.hs view
@@ -0,0 +1,531 @@+{-# LANGUAGE TemplateHaskell #-}+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.VisibilityPolygon.Lee+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--+-- \(O(n\log n)\) time algorithm to compute the visibility polygon of+-- a point inside a polygon (possibly containing holes) with \(n\)+-- vertices, or among a set of \(n\) disjoint segments. The alogirhtm+-- used is the the rotational sweepline algorithm by Lee, described+-- in:+--+-- D. T. Lee. Proximity and reachability in the plane. Report R-831, Dept. Elect.+-- Engrg., Univ. Illinois, Urbana, IL, 1978.+--+--------------------------------------------------------------------------------+module Algorithms.Geometry.VisibilityPolygon.Lee+ ( visibilityPolygon+ , visibilitySweep+ , VisibilityPolygon+ , Definer, StarShapedPolygon+ , compareAroundEndPoint+ ) where++import Algorithms.Geometry.RayShooting.Naive+import Control.Lens+import Control.Monad ((<=<))+import Data.Bifunctor (first)+import Data.Ext+import qualified Data.Foldable as F+import Data.Function (on)+import Data.Geometry.HalfLine+import Data.Geometry.Line+import Data.Geometry.LineSegment+import Data.Geometry.Point+import Data.Geometry.Polygon+import Data.Geometry.Vector+import Data.Intersection+import qualified Data.List as List+import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.List.Util as List+import Data.Maybe (mapMaybe, isJust)+import Data.Ord (comparing)+import Data.RealNumber.Rational+import Data.Semigroup.Foldable+import qualified Data.Set as Set+import qualified Data.Set.Util as Set+import Data.Util+import Data.Vinyl.CoRec+import Debug.Trace++type R = RealNumber 5++--------------------------------------------------------------------------------++type StarShapedPolygon p r = SimplePolygon p r++-- | Vertices of the visibility polgyon are either original vertices+-- or defined by some vertex and an edge+type Definer p e r = Either p (Point 2 r :+ p,LineSegment 2 p r :+ e)++type VisibilityPolygon p e r = StarShapedPolygon (Definer p e r) r++-- | We either insert or delete segments+data Action a = Insert a | Delete a deriving (Show,Eq,Ord)++isInsert :: Action a -> Bool+isInsert = \case+ Insert _ -> True+ Delete _ -> False++extract :: Action a -> a+extract = \case+ Insert x -> x+ Delete x -> x++-- | An event corresponds to some orientation at which the set of segments+-- intersected by the ray changes (this orientation is defined by a point)+data Event p e r = Event { _eventVtx :: Point 2 r :+ p+ , _actions :: NonEmpty (Action (LineSegment 2 p r :+ e))+ } deriving Show+makeLenses ''Event++-- | The status structure maintains the subset of segments currently+-- intersected by the ray that starts in the query point q, in order+-- of increasing distance along the ray.+type Status p e r = Set.Set (LineSegment 2 p r :+ e)++++--------------------------------------------------------------------------------+++++-- | Computes the visibility polygon of a point q in a polygon with+-- \(n\) vertices.+--+-- pre: q lies strictly inside the polygon+--+-- running time: \(O(n\log n)\)+visibilityPolygon :: forall p t r. (Ord r, Fractional r)+ => Point 2 r+ -> Polygon t p r+ -> StarShapedPolygon (Definer p () r) r+visibilityPolygon q pg =+ fromPoints . visibilitySweep v Nothing q . map ext . closedEdges $ pg+ where+ v = uncurry (startingDirection q) . consecutive q . polygonVertices $ pg++++++++++++++++++++++++-- | Computes the visibility polgyon from a vertex+visibilityPolygonFromVertex :: forall p t r. (Ord r, Fractional r, Show r, Show p)+ => Polygon t p r+ -> Int -- ^ from the i^th vertex on the outer boundary+ -> VisibilityPolygon p () r+visibilityPolygonFromVertex pg i =+ fromPoints . visibilitySweep sv (Just w) v . map ext $ segs+ where+ (v :+ _) = pg^.outerVertex i+ (w :+ _) = pg^.outerVertex (i-1)+ (u :+ _) = pg^.outerVertex (i+1)++ -- rotates the polygon so that u becomes the focus, and gets all+ -- other vertices. Takes the next CCW vertex around v, starting+ -- form the direction indicated by v.+ z = let u' :| rest = traceShowIdWith "vertices"+ $ polygonVertices $ pg&outerBoundary %~ rotateRight (i+1)+ in traceShowIdWith "z" $ consecutiveFrom (u .-. v) v (List.init rest)+ -- the last vertex in rest is v; so kill that++ sv = startingDirection v u z++ segs = map (first (^._2))+ . filter (not . incidentTo i)+ . closedEdges $ numberVertices pg++visibilityPolygonFromVertex' q sv mt segs = sweep q statusStruct (traceShowIdWith "events" events)+ where+ v = undefined++ -- lazily test if the segment intersects the initial ray+ segs' = labelWithDistances q initialRay segs++ events = computeEvents q sv (untilEnd q sv mt) segs'+ -- take only until the end of the range (if defined)++ initialRay = traceShowIdWith "ray" $ HalfLine q sv+ statusStruct = traceShowIdWith "initialSS" $ mkInitialSS segs'+++-- | Test if the line segment is incident to a point with the given+-- index.+incidentTo :: Int -> LineSegment 2 (SP Int a) r -> Bool+incidentTo i s = s^.start.extra._1 == i || s^.end.extra._1 == i++++++++++-- | computes a (partial) visibility polygon of a set of \(n\)+-- disjoint segments. The input segments are allowed to share+-- endpoints, but no intersections or no endpoints in the interior of+-- other segments. The input vector indicates the starting direction,+-- the Maybe point indicates up to which point/dicrection (CCW) of the+-- starting vector we should compute the visibility polygon.+--+-- pre : - all line segments are considered closed.+-- - no singleton linesegments exactly pointing away from q.+-- - for every orientattion the visibility is blocked somewhere, i.e.+-- no rays starting in the query point q that are disjoint from all segments.+-- - no vertices at staring direction sv+--+-- running time: \(O(n\log n)\)+visibilitySweep :: forall p r e. (Ord r, Fractional r)+ => Vector 2 r -- ^ starting direction of the sweep+ -> Maybe (Point 2 r)+ -- ^ -- point indicating the last point to sweep to+ -> Point 2 r -- ^ the point form which we compute the visibility polgyon+ -> [LineSegment 2 p r :+ e]+ -> [Point 2 r :+ Definer p e r]+visibilitySweep sv mt q segs = sweep q statusStruct events+ where+ -- lazily test if the segment intersects the initial ray+ segs' = labelWithDistances q initialRay segs+ events = computeEvents q sv (untilEnd q sv mt) segs'++ initialRay = HalfLine q sv+ statusStruct = mkInitialSS segs'++-- | Take until the ending point if defined. We can use that the list+-- of events appears in sorted order in the cyclic orientation around+-- the query point q+untilEnd :: (Ord r, Num r)+ => Point 2 r -- ^ query point+ -> Vector 2 r -- ^ starting direction+ -> Maybe (Point 2 r) -- ^ possible ending point+ -> [Event a e r] -> [Event a e r]+untilEnd q sv = \case+ Nothing -> id+ Just t -> List.takeWhile (\e -> ccwCmpAroundWith' sv (ext q) (e^.eventVtx) (ext t) == LT)++-- | Runs the actual sweep+sweep :: (Foldable t, Ord r, Fractional r)+ => Point 2 r -- ^ query point+ -> Status p e r -- ^ initial status structure+ -> t (Event p e r) -- ^ events to handle+ -> [Point 2 r :+ Definer p e r]+sweep q statusStruct = snd . List.foldl' (handleEvent q) (statusStruct,[])+++-- | Computes the events in the sweep+computeEvents :: (Ord r, Num r, Foldable t)+ => Point 2 r -- ^ query point+ -> Vector 2 r -- ^ starting direction+ -> ([Event p1 e1 r] -> [Event p2 e2 r]) -- ^ until where to take the vents+ -> t (LineSegment 2 p1 r :+ (Maybe r, e1))+ -> [Event p2 e2 r]+computeEvents q sv takeUntil =+ map (combine q)+ . List.groupBy' (\a b -> ccwCmpAroundWith' sv (ext q) (a^.eventVtx) (b^.eventVtx))+ . takeUntil+ . List.sortBy (cmp `on` (^.eventVtx))+ . concatMap (mkEvent sv q)+ where+ cmp = ccwCmpAroundWith' sv (ext q) <> cmpByDistanceTo' (ext q)++-- | Given multiple events happening at the same orientation, combine+-- them into a single event.+combine :: (Ord r, Num r) => Point 2 r -> NonEmpty (Event p e r) -> Event p e r+combine q es = Event p acts+ where+ acts = foldMap1 (^.actions) es+ p = F.minimumBy (cmpByDistanceTo' (ext q)) . fmap (^.eventVtx) $ es++-- | Constructs the at most two events resulting from this segement.+mkEvent :: (Ord r, Num r)+ => Vector 2 r -- ^ starting direction+ -> Point 2 r -- ^ query point+ -> LineSegment 2 p r :+ (Maybe r, e)+ -> [Event p e r]+mkEvent sv q (s@(LineSegment' u v) :+ (d,e)) = case cmp u v of+ LT -> [ Event u insert+ , Event v delete+ ]+ GT -> [ Event v insert+ , Event u delete+ ]+ EQ -> [] -- zero length segment, just skip+ where+ cmp = ccwCmpAroundWith' sv (ext q) <> cmpByDistanceTo' (ext q)+ s' = s :+ e++ insert = (if isJust d then Delete s' else Insert s') :| []+ delete = (if isJust d then Insert s' else Delete s') :| []+++-- | Handles an event, computes the new status structure and output polygon.+handleEvent :: (Ord r, Fractional r)+ => Point 2 r+ -> (Status p e r, [Point 2 r :+ Definer p e r])+ -> Event p e r+ -> (Status p e r, [Point 2 r :+ Definer p e r])+handleEvent q (ss,out) (Event (p :+ z) acts) = (ss', newVtx <> out)+ where+ (ins,dels) = bimap (map extract) (map extract) . NonEmpty.partition isInsert $ acts++ ss' = flip (foldr (insertAt q p)) ins+ . flip (foldr (deleteAt q p)) dels+ $ ss++ newVtx = let (a :+ sa) = firstHitAt' q p ss+ (b :+ sb) = firstHitAt' q p ss'+ ae = valOf a sa+ be = valOf b sb+ in case (a /= b, a == p) of+ (True, _) -> -- new window of the output polygon discovered+ -- figure out who is the closest vertex, (the reflex vtx)+ -- and add the appropriate two vertices+ case squaredEuclideanDist q a < squaredEuclideanDist q b of+ True -> [ b :+ Right (a :+ ae, sb)+ , a :+ Left ae -- a must be a vertex!+ ]+ False -> [ b :+ Left be+ , a :+ Right (b :+ be, sa)+ ]+ (False,True) -> [ p :+ Left z]+ -- sweeping over a regular vertex of the visibility polygon+ (False,False) -> [] -- sweeping over a vertex not in output++ valOf a (LineSegment' (b :+ be) (_ :+ ce) :+ _ ) | a == b = be+ | otherwise = ce++++--------------------------------------------------------------------------------++-- | Given two points q and p, and a status structure retrieve the+-- first segment in the status structure intersected by the ray from q+-- through p.+--+-- pre: all segments in the status structure should intersect the ray+-- from q through p (in a point), in that order.+--+-- running time: \(O(\log n)\)+firstHitAt :: forall p r e. (Ord r, Fractional r)+ => Point 2 r -> Point 2 r+ -> Status p e r+ -> Maybe (Point 2 r :+ LineSegment 2 p r :+ e)+firstHitAt q p = computeIntersectionPoint <=< Set.lookupMin+ where+ computeIntersectionPoint s = fmap (:+ s) . asA @(Point 2 r)+ $ supportingLine (s^.core) `intersect` lineThrough p q++-- | Given two points q and p, and a status structure retrieve the+-- first segment in the status structure intersected by the ray from q+-- through p.+--+-- pre: - all segments in the status structure should intersect the ray+-- from q through p (in a point), in that order.+-- - the status structure is non-empty+--+-- running time: \(O(\log n)\)+firstHitAt' :: forall p r e. (Ord r, Fractional r)+ => Point 2 r -> Point 2 r+ -> Status p e r+ -> Point 2 r :+ LineSegment 2 p r :+ e+firstHitAt' q p s = case firstHitAt q p s of+ Just x -> x+ Nothing -> error "firstHitAt: precondition failed!"++--------------------------------------------------------------------------------+-- * Status Structure Operations++-- | Insert a new segment into the status structure, depending on the+-- (distance from q to to the) intersection point with the ray from q+-- through p+--+-- pre: all segments in the status structure should intersect the ray+-- from q through p, in that order.+--+-- \(O(\log n)\)+insertAt :: (Ord r, Fractional r)+ => Point 2 r -> Point 2 r -> LineSegment 2 p r :+ e+ -> Status p e r -> Status p e r+insertAt q p = Set.insertBy (compareByDistanceToAt q p <> flip (compareAroundEndPoint q))+ -- if two segments have the same distance, they must share and endpoint+ -- so we use the CCW ordering around this common endpoint to determine+ -- the order.++-- | Delete a segment from the status structure, depending on the+-- (distance from q to to the) intersection point with the ray from q+-- through p+--+-- pre: all segments in the status structure should intersect the ray+-- from q through p, in that order.+--+-- \(O(\log n)\)+deleteAt :: (Ord r, Fractional r)+ => Point 2 r -> Point 2 r -> LineSegment 2 p r :+ e+ -> Status p e r -> Status p e r+deleteAt q p = Set.deleteAllBy (compareByDistanceToAt q p <> compareAroundEndPoint q)+ -- if two segments have the same distance, we use the ccw order around their common+ -- (end) point.++-- FIXME: If there are somehow segmetns that would continue at p as+-- well, they are also deleted.+++-- | Given a list of line segments, each labeled with the distance+-- from their intersection point with the initial ray to the query+-- point, build the initial status structure.+mkInitialSS :: forall r p e. (Ord r, Fractional r)+ => [ LineSegment 2 p r :+ (Maybe r, e)] -> Status p e r+mkInitialSS = Set.mapMonotonic (^.extra)+ . foldr (Set.insertBy $ comparing (^.core)) Set.empty+ . mapMaybe (\(s :+ (md,e)) -> (:+ (s :+ e)) <$> md)++-- | Given q, the initial ray, and a segment s, computes if the+-- segment intersects the initial, rightward ray starting in q, and if+-- so returns the (squared) distance from q to that point together+-- with the segment.+initialIntersection :: forall r p. (Ord r, Fractional r)+ => Point 2 r -> HalfLine 2 r -> LineSegment 2 p r+ -> Maybe r+initialIntersection q ray s =+ case asA @(Point 2 r) $ seg `intersect` ray of+ Nothing -> Nothing+ Just z -> Just $ squaredEuclideanDist q z+ where+ seg = first (const ()) s++--------------------------------------------------------------------------------+-- * Comparators for the rotating ray++-- | Given two points q and p, and two segments a and b that are guaranteed to+-- intersect the ray from q through p once, order the segments by their+-- intersection point+compareByDistanceToAt :: forall p r e. (Ord r, Fractional r)+ => Point 2 r -> Point 2 r+ -> LineSegment 2 p r :+ e+ -> LineSegment 2 p r :+ e+ -> Ordering+compareByDistanceToAt q p = comparing f+ where+ f (s :+ _) = fmap (squaredEuclideanDist q)+ . asA @(Point 2 r)+ $ supportingLine s `intersect` lineThrough p q++-- | Given two segments that share an endpoint, order them by their+-- order around this common endpoint. I.e. if uv and uw share endpoint+-- u we uv is considered smaller iff v is smaller than w in the+-- counterclockwise order around u (treating the direction from q to+-- the common endpoint as zero).+compareAroundEndPoint :: forall p r e. (Ord r, Fractional r)+ => Point 2 r+ -> LineSegment 2 p r :+ e+ -> LineSegment 2 p r :+ e+ -> Ordering+compareAroundEndPoint q+ (LineSegment' a b :+ _)+ (LineSegment' s t :+ _)+ -- traceshow ("comapreAroundEndPoint ", sa, sb) False = undefined+ | a^.core == s^.core = ccwCmpAroundWith' (a^.core .-. q) a b t+ | a^.core == t^.core = ccwCmpAroundWith' (a^.core .-. q) a b s+ | b^.core == s^.core = ccwCmpAroundWith' (b^.core .-. q) b a t+ | b^.core == t^.core = ccwCmpAroundWith' (b^.core .-. q) b a s+ | otherwise = error "compareAroundEndPoint: precondition failed!"++--------------------------------------------------------------------------------+-- * Helper functions for polygon operations++-- | Given q, and two consecutive points u and v, Computes a direction+-- for the initial ray, i.e. a "generic" ray that does not go through+-- any vertices.+startingDirection :: Fractional r => Point 2 r -> Point 2 r -> Point 2 r -> Vector 2 r+startingDirection q u w = v .-. q+ where+ v = u .+^ ((w .-. u) ^/ 2) -- point in the middle between u and w+ -- note: the segment between u and w could pass on the wrong side of q+ -- (i.e. so that does not "cover" the CCW but the CW range between u and w)+ -- however, in that case there is apparently nothing on the CCW side opposite+ -- to v, as u and w are supposed to be the first two events. This means the+ -- precondition does not hold.++-- | finds two consecutive vertices in the clockwise order around the+-- given point q. I.e. there are no other points in between the two+-- returned points.+consecutive :: (Ord r, Num r) => Point 2 r -> NonEmpty (Point 2 r :+ p)+ -> (Point 2 r, Point 2 r)+consecutive q ((p :+ _):|pts) = (p,consecutiveFrom (p .-. q) q pts)++-- | pre: input list is non-empty+consecutiveFrom :: (Ord r, Num r)+ => Vector 2 r -- ^ starting vector+ -> Point 2 r -- ^ query point+ -> [Point 2 r :+ p] -> Point 2 r+consecutiveFrom v q = view core . List.minimumBy (ccwCmpAroundWith' v (ext q))++-- | Gets the edges of the polygon as closed line segments.+closedEdges :: Polygon t p r -> [LineSegment 2 p r]+closedEdges = map asClosed . listEdges+ where+ asClosed (LineSegment' u v) = ClosedLineSegment u v+++--------------------------------------------------------------------------------+-- * Generic Helper functions++++--------------------------------------------------------------------------------++test :: StarShapedPolygon (Definer Int () R) R+test = visibilityPolygon origin testPg++testVtx = visibilityPolygonFromVertex testPg 0++testPg :: SimplePolygon Int R+testPg = fromPoints $ zipWith (:+) [ Point2 3 1+ , Point2 3 2+ , Point2 4 2+ , Point2 2 4+ , Point2 (-1) 4+ , Point2 1 2+ , Point2 (-3) (-1)+ , Point2 4 (-1)+ ] [1..]++testPg2 :: SimplePolygon Int R+testPg2 = fromPoints $ zipWith (:+) [ Point2 3 1+ , Point2 3 2+ , Point2 4 2+ , Point2 2 4+ , Point2 (-1) 4+ , Point2 1 2.1+ , Point2 (-3) (-1)+ , Point2 4 (-1)+ ] [1..]++++traceShowIdWith x y = traceShow (show x,y) y
+ src/Algorithms/Geometry/WSPD.hs view
@@ -0,0 +1,474 @@+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.WSPD+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--+-- Algorithm to construct a well separated pair decomposition (wspd).+--+--------------------------------------------------------------------------------+module Algorithms.Geometry.WSPD+ ( fairSplitTree+ , wellSeparatedPairs+ , NodeData(NodeData)+ , WSP+ , SplitTree+ , nodeData+ , Level(..)+ , reIndexPoints+ , distributePoints+ , distributePoints'+ ) where++import Algorithms.Geometry.WSPD.Types+import Control.Lens hiding (Level, levels)+import Control.Monad.Reader+import Control.Monad.ST (ST,runST)+import Data.BinaryTree+import Data.Ext+import qualified Data.Foldable as F+import Data.Geometry.Box+import Data.Geometry.Point+-- import Data.Geometry.Properties+-- import Data.Geometry.Transformation+import Data.Geometry.Vector+import qualified Data.Geometry.Vector as GV+import qualified Data.IntMap.Strict as IntMap+import qualified Data.LSeq as LSeq+import Data.LSeq (LSeq, toSeq,pattern (:<|))+import qualified Data.List as L+import qualified Data.List.NonEmpty as NonEmpty+import Data.Maybe+import Data.Ord (comparing)+import Data.Range+import qualified Data.Range as Range+import qualified Data.Sequence as S+import qualified Data.Vector as V+import qualified Data.Vector.Mutable as MV+import GHC.TypeLits++-- import Debug.Trace++--------------------------------------------------------------------------------++-- | Construct a split tree+--+-- running time: \(O(n \log n)\)+fairSplitTree :: (Fractional r, Ord r, Arity d, 1 <= d+ , Show r, Show p+ )+ => NonEmpty.NonEmpty (Point d r :+ p) -> SplitTree d p r ()+fairSplitTree pts = foldUp node' Leaf $ fairSplitTree' n pts'+ where+ pts' = imap sortOn . pure . g $ pts+ n = length $ pts'^.GV.element @0++ sortOn' i = NonEmpty.sortWith (^.core.unsafeCoord i)+ sortOn i = LSeq.fromNonEmpty . sortOn' (i + 1)+ -- sorts the points on the first coordinate, and then associates each point+ -- with an index,; its rank in terms of this first coordinate.+ g = NonEmpty.zipWith (\i (p :+ e) -> p :+ (i :+ e)) (NonEmpty.fromList [0..])+ . sortOn' 1++ -- node' :: b -> a -> b -> b+ -- node' :: SplitTree d p r () -> Int -> SplitTree d p r () -> SplitTree d p r ()+ node' l j r = Node l (NodeData j (bbOf l <> bbOf r) ()) r+++-- | Given a split tree, generate the Well separated pairs+--+-- running time: \(O(s^d n)\)+wellSeparatedPairs :: (Floating r, Ord r, Arity d, Arity (d + 1))+ => r -> SplitTree d p r a -> [WSP d p r a]+wellSeparatedPairs s = f+ where+ f (Leaf _) = []+ f (Node l _ r) = findPairs s l r ++ f l ++ f r++++-- -- | Given a split tree, generate the well separated pairs such that one set is+-- -- a singleton.+-- -- running time: \(O(s^d n\log n)\)+-- wellSeparatedPairSingletons :: (Fractional r, Ord r, AlwaysTrueWSPD d)+-- => r -> SplitTree d p r a -> [(Point d r :+ p, PointSet d p r (Sized a))]+-- wellSeparatedPairSingletons s t = concatMap split $ wellSeparatedPairs s t'+-- where+-- split (l,r) = undefined+-- -- | measure l <= measure r = map (,r) $ F.toList l+-- -- | otherwise = map (,l) $ F.toList r+-- t' = foldUpData (\l nd r -> )++-- t+++--------------------------------------------------------------------------------+-- * Building the split tree++-- | Given the points, sorted in every dimension, recursively build a split tree+--+-- The algorithm works in rounds. Each round takes \( O(n) \) time, and halves the+-- number of points. Thus, the total running time is \( O(n log n) \).+--+-- The algorithm essentially builds a path in the split tree; at every node on+-- the path that we construct, we split the point set into two sets (L,R)+-- according to the longest side of the bounding box.+--+-- The smaller set is "assigned" to the current node and set asside. We+-- continue to build the path with the larger set until the total number of+-- items remaining is less than n/2.+--+-- To start the next round, each node on the path needs to have the points+-- assigned to that node, sorted in each dimension (i.e. the Vector+-- (PointSeq))'s. Since we have the level assignment, we can compute these+-- lists by traversing each original input list (i.e. one for every dimension)+-- once, and partition the points based on their level assignment.+fairSplitTree' :: (Fractional r, Ord r, Arity d, 1 <= d+ , Show r, Show p+ )+ => Int -> GV.Vector d (PointSeq d (Idx :+ p) r)+ -> BinLeafTree Int (Point d r :+ p)+fairSplitTree' n pts+ | n <= 1 = let p = LSeq.head $ pts^.GV.element @0 in Leaf (dropIdx p)+ | otherwise = foldr node' (V.last path) $ V.zip nodeLevels (V.init path)+ where+ -- note that points may also be assigned level 'Nothing'.+ (levels, nodeLevels'@(maxLvl NonEmpty.:| _)) = runST $ do+ lvls <- MV.replicate n Nothing+ ls <- runReaderT (assignLevels (n `div` 2) 0 pts (Level 0 Nothing) []) lvls+ lvls' <- V.unsafeFreeze lvls+ pure (lvls',ls)++ -- TODO: We also need to report the levels in the order in which they are+ -- assigned to nodes++ nodeLevels = V.fromList . L.reverse . NonEmpty.toList $ nodeLevels'++ -- levels = traceShow ("Levels",levels',maxLvl) levels'++ -- path = traceShow ("path", path',nodeLevels) path'+ distrPts = distributePoints (1 + maxLvl^.unLevel) levels pts++ path = recurse <$> distrPts -- (traceShow ("distributed pts",distrPts) distrPts)++ -- node' (lvl,lc) rc | traceShow ("node' ",lvl,lc,rc) False = undefined+ node' (lvl,lc) rc = case lvl^?widestDim._Just of+ Nothing -> error "Unknown widest dimension"+ Just j -> Node lc j rc+ recurse pts' = fairSplitTree' (length $ pts'^.GV.element @0)+ (reIndexPoints pts')++-- | Assign the points to their the correct class. The 'Nothing' class is+-- considered the last class+distributePoints :: (Arity d , Show r, Show p)+ => Int -> V.Vector (Maybe Level)+ -> GV.Vector d (PointSeq d (Idx :+ p) r)+ -> V.Vector (GV.Vector d (PointSeq d (Idx :+ p) r))+distributePoints k levels = transpose . fmap (distributePoints' k levels)++transpose :: Arity d => GV.Vector d (V.Vector a) -> V.Vector (GV.Vector d a)+transpose = V.fromList . map GV.vectorFromListUnsafe . L.transpose+ . map V.toList . F.toList++-- | Assign the points to their the correct class. The 'Nothing' class is+-- considered the last class+distributePoints' :: Int -- ^ number of classes+ -> V.Vector (Maybe Level) -- ^ level assignment+ -> PointSeq d (Idx :+ p) r -- ^ input points+ -> V.Vector (PointSeq d (Idx :+ p) r)+distributePoints' k levels pts+ = fmap fromSeqUnsafe $ V.create $ do+ v <- MV.replicate k mempty+ forM_ pts $ \p ->+ append v (level p) p+ pure v+ where+ level p = maybe (k-1) _unLevel $ levels V.! (p^.extra.core)+ append v i p = MV.read v i >>= MV.write v i . (S.|> p)++fromSeqUnsafe :: S.Seq a -> LSeq n a+fromSeqUnsafe = LSeq.promise . LSeq.fromSeq++-- | Given a sequence of points, whose index is increasing in the first+-- dimension, i.e. if idx p < idx q, then p[0] < q[0].+-- Reindex the points so that they again have an index+-- in the range [0,..,n'], where n' is the new number of points.+--+-- running time: O(n' * d) (more or less; we are actually using an intmap for+-- the lookups)+--+-- alternatively: I can unsafe freeze and thaw an existing vector to pass it+-- along to use as mapping. Except then I would have to force the evaluation+-- order, i.e. we cannot be in 'reIndexPoints' for two of the nodes at the same+-- time.+--+-- so, basically, run reIndex points in ST as well.+reIndexPoints :: (Arity d, 1 <= d)+ => GV.Vector d (PointSeq d (Idx :+ p) r)+ -> GV.Vector d (PointSeq d (Idx :+ p) r)+reIndexPoints ptsV = fmap reIndex ptsV+ where+ pts = ptsV^.GV.element @0++ reIndex = fmap (\p -> p&extra.core %~ fromJust . flip IntMap.lookup mapping')+ mapping' = IntMap.fromAscList $ zip (map (^.extra.core) . F.toList $ pts) [0..]++-- | ST monad with access to the vector storign the level of the points.+type RST s = ReaderT (MV.MVector s (Maybe Level)) (ST s)++{- HLINT ignore assignLevels -}+-- | Assigns the points to a level. Returns the list of levels used. The first+-- level in the list is the level assigned to the rest of the nodes. Their+-- level is actually still set to Nothing in the underlying array.+assignLevels :: (Fractional r, Ord r, Arity d+ , Show r, Show p+ )+ => Int -- ^ Number of items we need to collect+ -> Int -- ^ Number of items we collected so far+ -> GV.Vector d (PointSeq d (Idx :+ p) r)+ -> Level -- ^ next level to use+ -> [Level] -- ^ Levels used so far+ -> RST s (NonEmpty.NonEmpty Level)+assignLevels h m pts l prevLvls+ | m >= h = pure (l NonEmpty.:| prevLvls)+ | otherwise = do+ pts' <- compactEnds pts+ -- find the widest dimension j = i+1+ let j = widestDimension pts'+ i = j - 1 -- traceShow ("i",j,pts') j - 1+ extJ = (extends pts')^.ix' i+ mid = midPoint extJ++ -- find the set of points that we have to delete, by looking at the sorted+ -- list L_j. As a side effect, this will remove previously assigned points+ -- from L_j.+ (lvlJPts,deletePts) <- findAndCompact j (pts'^.ix' i) mid+ let pts'' = pts'&ix' i .~ lvlJPts+ l' = l&widestDim ?~ j+ forM_ deletePts $ \p ->+ assignLevel p l'+ assignLevels h (m + length deletePts) pts'' (nextLevel l) (l' : prevLvls)++-- | Remove already assigned pts from the ends of all vectors.+compactEnds :: Arity d+ => GV.Vector d (PointSeq d (Idx :+ p) r)+ -> RST s (GV.Vector d (PointSeq d (Idx :+ p) r))+compactEnds = traverse compactEnds'++-- | Assign level l to point p+assignLevel :: (c :+ (Idx :+ p)) -> Level -> RST s ()+assignLevel p l = ask >>= \levels -> lift $ MV.write levels (p^.extra.core) (Just l)++-- | Get the level of a point+levelOf :: (c :+ (Idx :+ p)) -> RST s (Maybe Level)+levelOf p = ask >>= \levels -> lift $ MV.read levels (p^.extra.core)++-- | Test if the point already has a level assigned to it.+hasLevel :: c :+ (Idx :+ p) -> RST s Bool+hasLevel = fmap isJust . levelOf++-- | Remove allready assigned points from the sequence+--+-- pre: there are points remaining+compactEnds' :: PointSeq d (Idx :+ p) r+ -> RST s (PointSeq d (Idx :+ p) r)+compactEnds' (l0 :<| s0) = fmap fromSeqUnsafe . goL $ l0 S.<| toSeq s0+ where+ goL s@(S.viewl -> l S.:< s') = hasLevel l >>= \case+ False -> goR s+ True -> goL s'+ goL _ = error "Unreachable, but cannot prove it in Haskell"+ goR s@(S.viewr -> s' S.:> r) = hasLevel r >>= \case+ False -> pure s+ True -> goR s'+ goR _ = error "Unreachable, but cannot prove it in Haskell"+++-- | Given the points, ordered by their j^th coordinate, split the point set+-- into a "left" and a "right" half, i.e. the points whose j^th coordinate is+-- at most the given mid point m, and the points whose j^th coordinate is+-- larger than m.+--+-- We return a pair (Largest set, Smallest set)+--+--+--fi ndAndCompact works by simultaneously traversing the points from left to+-- right, and from right to left. As soon as we find a point crossing the mid+-- point we stop and return. Thus, in principle this takes only O(|Smallest+-- set|) time.+--+-- running time: O(|Smallest set|) + R, where R is the number of *old* points+-- (i.e. points that should have been removed) in the list.+findAndCompact :: (Ord r, Arity d+ , Show r, Show p+ )+ => Int+ -- ^ the dimension we are in, i.e. so that we know+ -- which coordinate of the point to compare+ -> PointSeq d (Idx :+ p) r+ -> r -- ^ the mid point+ -> RST s ( PointSeq d (Idx :+ p) r+ , PointSeq d (Idx :+ p) r+ )+findAndCompact j (l0 :<| s0) m = fmap select . stepL $ l0 S.<| toSeq s0+ where+ -- stepL and stepR together build a data structure (FAC l r S) that+ -- contains the left part of the list, i.e. the points before midpoint, and+ -- the right part of the list., and a value S that indicates which part is+ -- the short side.++ -- stepL takes a step on the left side of the list; if the left point l+ -- already has been assigned, we continue waling along (and "ignore" the+ -- point). If it has not been assigned, and is before the mid point, we+ -- take a step from the right, and add l onto the left part. If it is+ -- larger than the mid point, we have found our split.+ -- stepL :: S.Seq (Point d r :+ (Idx :+ p)) -> ST s (FindAndCompact d r (Idx :+ p))+ stepL s = case S.viewl s of+ S.EmptyL -> pure $ FAC mempty mempty L+ l S.:< s' -> hasLevel l >>= \case+ False -> if l^.core.unsafeCoord j <= m+ then addL l <$> stepR s'+ else pure $ FAC mempty s L+ True -> stepL s' -- delete, continue left++ -- stepR :: S.Seq (Point d r :+ (Idx :+ p)) -> ST s (FindAndCompact d r (Idx :+ p))+ stepR s = case S.viewr s of+ S.EmptyR -> pure $ FAC mempty mempty R+ s' S.:> r -> hasLevel r >>= \case+ False -> if r^.core.unsafeCoord j >= m+ then addR r <$> stepL s'+ else pure $ FAC s mempty R+ True -> stepR s'+++ addL l x = x&leftPart %~ (l S.<|)+ addR r x = x&rightPart %~ (S.|> r)++ select = over both fromSeqUnsafe . select'++ -- select' f | traceShow ("select'", f) False = undefined+ select' (FAC l r L) = (r, l)+ select' (FAC l r R) = (l, r)+++-- | Find the widest dimension of the point set+--+-- pre: points are sorted according to their dimension+widestDimension :: (Num r, Ord r, Arity d) => GV.Vector d (PointSeq d p r) -> Int+widestDimension = fst . L.maximumBy (comparing snd) . zip [1..] . F.toList . widths++widths :: (Num r, Arity d) => GV.Vector d (PointSeq d p r) -> GV.Vector d r+widths = fmap Range.width . extends+++{- HLINT ignore extends -}+-- | get the extends of the set of points in every dimension, i.e. the left and+-- right boundaries.+--+-- pre: points are sorted according to their dimension+extends :: Arity d => GV.Vector d (PointSeq d p r) -> GV.Vector d (Range r)+extends = imap (\i pts ->+ ClosedRange ((LSeq.head pts)^.core.unsafeCoord (i + 1))+ ((LSeq.last pts)^.core.unsafeCoord (i + 1)))+++--------------------------------------------------------------------------------+-- * Finding Well Separated Pairs++findPairs :: (Floating r, Ord r, Arity d, Arity (d + 1))+ => r -> SplitTree d p r a -> SplitTree d p r a+ -> [WSP d p r a]+findPairs s l r+ | areWellSeparated' s l r = [(l,r)]+ | maxWidth l <= maxWidth r = concatMap (findPairs s l) $ children' r+ | otherwise = concatMap (findPairs s r) $ children' l+++-- -- | Test if the two sets are well separated with param s+-- areWellSeparated :: (Arity d, Arity (d + 1), Fractional r, Ord r)+-- => r -- ^ separation factor+-- -> SplitTree d p r a+-- -> SplitTree d p r a -> Bool+-- areWellSeparated _ (Leaf _) (Leaf _) = True+-- areWellSeparated s l r = boxBox s (bbOf l) (bbOf r)+++-- areWellSeparated s (Leaf p) (Node _ nd _) = pointBox s (p^.core) (nd^.bBox)+-- areWellSeparated s (Node _ nd _) (Leaf p) = pointBox s (p^.core) (nd^.bBox)+-- areWellSeparated s (Node _ ld _) (Node _ rd _) = boxBox s (ld^.bBox) (rd^.bBox)++{- HLINT ignore boxBox -}+-- -- | Test if the point and the box are far enough appart+-- pointBox :: (Fractional r, Ord r, AlwaysTruePFT d, AlwaysTrueTransformation d)+-- => r -> Point d r -> Box d p r -> Bool+-- pointBox s p b = not $ p `inBox` b'+-- where+-- v = (centerPoint b)^.vector+-- b' = translateBy v . scaleUniformlyBy s . translateBy ((-1) *^ v) $ b++-- -- | Test if the two boxes are sufficiently far appart+-- boxBox :: (Fractional r, Ord r, Arity d, Arity (d + 1))+-- => r -> Box d p r -> Box d p r -> Bool+-- boxBox s lb rb = boxBox' lb rb && boxBox' rb lb+-- where+-- boxBox' b' b = not $ b' `intersects` bOut+-- where+-- v = (centerPoint b)^.vector+-- bOut = translateBy v . scaleUniformlyBy s . translateBy ((-1) *^ v) $ b++--------------------------------------------------------------------------------+-- * Alternative def if wellSeparated that uses fractional+++areWellSeparated' :: (Floating r, Ord r, Arity d)+ => r+ -> SplitTree d p r a+ -> SplitTree d p r a+ -> Bool+areWellSeparated' _ (Leaf _) (Leaf _) = True+areWellSeparated' s l r = boxBox1 s (bbOf l) (bbOf r)++-- (Leaf p) (Node _ nd _) = pointBox' s (p^.core) (nd^.bBox)+-- areWellSeparated' s (Node _ nd _) (Leaf p) = pointBox' s (p^.core) (nd^.bBox)+-- areWellSeparated' s (Node _ ld _) (Node _ rd _) = boxBox' s (ld^.bBox) (rd^.bBox)++boxBox1 :: (Floating r, Ord r, Arity d) => r -> Box d p r -> Box d p r -> Bool+boxBox1 s lb rb = euclideanDist (centerPoint lb) (centerPoint rb) >= (s+1)*d+ where+ diam b = euclideanDist (b^.minP.core.cwMin) (b^.maxP.core.cwMax)+ d = max (diam lb) (diam rb)+++++--------------------------------------------------------------------------------+-- * Helper stuff+++-- | Computes the maximum width of a splitTree+maxWidth :: (Arity d, Num r)+ => SplitTree d p r a -> r+maxWidth (Leaf _) = 0+maxWidth (Node _ (NodeData i b _) _) = fromJust $ widthIn' i b++-- | 'Computes' the bounding box of a split tree+bbOf :: Ord r => SplitTree d p r a -> Box d () r+bbOf (Leaf p) = boundingBox $ p^.core+bbOf (Node _ (NodeData _ b _) _) = b+++children' :: BinLeafTree v a -> [BinLeafTree v a]+children' (Leaf _) = []+children' (Node l _ r) = [l,r]+++-- | Turn a traversal into lens+ix' :: (Arity d, KnownNat d) => Int -> Lens' (GV.Vector d a) a+ix' i = singular (GV.element' i)+++dropIdx :: core :+ (t :+ extra) -> core :+ extra+dropIdx (p :+ (_ :+ e)) = p :+ e++--------------------------------------------------------------------------------
+ src/Algorithms/Geometry/WSPD/Types.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE UndecidableInstances #-}+--------------------------------------------------------------------------------+-- |+-- Module : Algorithms.Geometry.WSPD.Types+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--+-- Data types that can represent a well separated pair decomposition (wspd).+--+--------------------------------------------------------------------------------+module Algorithms.Geometry.WSPD.Types+ where++import Control.Lens hiding (Level)+import Data.BinaryTree+import Data.Ext+import Data.Geometry.Box+import Data.Geometry.Point+import Data.Geometry.Vector+import qualified Data.LSeq as LSeq+import Data.Measured.Class+import qualified Data.Sequence as S+import qualified Data.Traversable as Tr++--------------------------------------------------------------------------------++type SplitTree d p r a = BinLeafTree (NodeData d r a) (Point d r :+ p)++type PointSet d p r a = SplitTree d p r a++type WSP d p r a = (PointSet d p r a, PointSet d p r a)++-- | Data that we store in the split tree+data NodeData d r a = NodeData { _splitDim :: !Int+ , _bBox :: !(Box d () r)+ , _nodeData :: !a+ }+deriving instance (Arity d, Show r, Show a) => Show (NodeData d r a)+deriving instance (Arity d, Eq r, Eq a) => Eq (NodeData d r a)++makeLenses ''NodeData++instance Semigroup v => Measured v (NodeData d r v) where+ measure = _nodeData++instance Functor (NodeData d r) where+ fmap = Tr.fmapDefault++instance Foldable (NodeData d r) where+ foldMap = Tr.foldMapDefault++instance Traversable (NodeData d r) where+ traverse f (NodeData d b x) = NodeData d b <$> f x++--------------------------------------------------------------------------------+-- * Implementation types++-- | Non-empty sequence of points.+type PointSeq d p r = LSeq.LSeq 1 (Point d r :+ p)+++data Level = Level { _unLevel :: Int+ , _widestDim :: Maybe Int+ } deriving (Show,Eq,Ord)+makeLenses ''Level++nextLevel :: Level -> Level+nextLevel (Level i _) = Level (i+1) Nothing+++type Idx = Int+++data ShortSide = L | R deriving (Show,Eq)++data FindAndCompact d r p = FAC { _leftPart :: !(S.Seq (Point d r :+ p))+ , _rightPart :: !(S.Seq (Point d r :+ p))+ , _shortSide :: !ShortSide+ }+deriving instance (Arity d, Show r, Show p) => Show (FindAndCompact d r p)+deriving instance (Arity d, Eq r, Eq p) => Eq (FindAndCompact d r p)++makeLenses ''FindAndCompact
src/Algorithms/Geometry/WellSeparatedPairDecomposition/Types.hs view
@@ -10,74 +10,11 @@ -- Data types that can represent a well separated pair decomposition (wspd). -- ---------------------------------------------------------------------------------module Algorithms.Geometry.WellSeparatedPairDecomposition.Types where--import Control.Lens hiding (Level)-import Data.BinaryTree-import Data.Ext-import Data.Geometry.Box-import Data.Geometry.Point-import Data.Geometry.Vector-import qualified Data.LSeq as LSeq-import qualified Data.Sequence as S-import qualified Data.Traversable as Tr------------------------------------------------------------------------------------type SplitTree d p r a = BinLeafTree (NodeData d r a) (Point d r :+ p)--type PointSet d p r a = SplitTree d p r a--type WSP d p r a = (PointSet d p r a, PointSet d p r a)---- | Data that we store in the split tree-data NodeData d r a = NodeData { _splitDim :: !Int- , _bBox :: !(Box d () r)- , _nodeData :: !a- }-deriving instance (Arity d, Show r, Show a) => Show (NodeData d r a)-deriving instance (Arity d, Eq r, Eq a) => Eq (NodeData d r a)--makeLenses ''NodeData--instance Semigroup v => Measured v (NodeData d r v) where- measure = _nodeData--instance Functor (NodeData d r) where- fmap = Tr.fmapDefault--instance Foldable (NodeData d r) where- foldMap = Tr.foldMapDefault--instance Traversable (NodeData d r) where- traverse f (NodeData d b x) = NodeData d b <$> f x------------------------------------------------------------------------------------- * Implementation types--type PointSeq d p r = LSeq.LSeq 1 (Point d r :+ p)---data Level = Level { _unLevel :: Int- , _widestDim :: Maybe Int- } deriving (Show,Eq,Ord)-makeLenses ''Level--nextLevel :: Level -> Level-nextLevel (Level i _) = Level (i+1) Nothing----type Idx = Int---data ShortSide = L | R deriving (Show,Eq)--data FindAndCompact d r p = FAC { _leftPart :: !(S.Seq (Point d r :+ p))- , _rightPart :: !(S.Seq (Point d r :+ p))- , _shortSide :: !ShortSide- }-deriving instance (Arity d, Show r, Show p) => Show (FindAndCompact d r p)-deriving instance (Arity d, Eq r, Eq p) => Eq (FindAndCompact d r p)+-- FIXME: This module should be internal and not exposed. Fix after 2021-06-01.+module Algorithms.Geometry.WellSeparatedPairDecomposition.Types+ {-# DEPRECATED "This module will be deleted after 2021-06-01. \+ \Use Algorithms.Geometry.WSPD instead." #-}+ ( module Algorithms.Geometry.WSPD.Types )+ where -makeLenses ''FindAndCompact+import Algorithms.Geometry.WSPD.Types
src/Algorithms/Geometry/WellSeparatedPairDecomposition/WSPD.hs view
@@ -8,453 +8,10 @@ -- Algorithm to construct a well separated pair decomposition (wspd). -- ---------------------------------------------------------------------------------module Algorithms.Geometry.WellSeparatedPairDecomposition.WSPD where--import Algorithms.Geometry.WellSeparatedPairDecomposition.Types-import Control.Lens hiding (Level, levels)-import Control.Monad.Reader-import Control.Monad.ST (ST,runST)-import Data.BinaryTree-import Data.Ext-import qualified Data.Foldable as F-import Data.Geometry.Box-import Data.Geometry.Point-import Data.Geometry.Properties-import Data.Geometry.Transformation-import Data.Geometry.Vector-import qualified Data.Geometry.Vector as GV-import qualified Data.IntMap.Strict as IntMap-import qualified Data.LSeq as LSeq-import Data.LSeq (LSeq,toSeq,ViewL(..),ViewR(..),pattern (:<|))-import qualified Data.List as L-import qualified Data.List.NonEmpty as NonEmpty-import Data.Maybe-import Data.Ord (comparing)-import Data.Range-import qualified Data.Range as Range-import qualified Data.Sequence as S-import qualified Data.Vector as V-import qualified Data.Vector.Mutable as MV-import GHC.TypeLits--import Debug.Trace-------------------------------------------------------------------------------------- | Construct a split tree------ running time: \(O(n \log n)\)-fairSplitTree :: (Fractional r, Ord r, Arity d, 1 <= d- , Show r, Show p- )- => NonEmpty.NonEmpty (Point d r :+ p) -> SplitTree d p r ()-fairSplitTree pts = foldUp node' Leaf $ fairSplitTree' n pts'+module Algorithms.Geometry.WellSeparatedPairDecomposition.WSPD+ {-# DEPRECATED "This module will be deleted after 2021-06-01. \+ \Use Algorithms.Geometry.WSPD instead." #-}+ ( module Algorithms.Geometry.WSPD ) where- pts' = GV.imap sortOn . pure . g $ pts- n = length $ pts'^.GV.element (C :: C 0) - sortOn' i = NonEmpty.sortWith (^.core.unsafeCoord i)- sortOn i = LSeq.fromNonEmpty . sortOn' (i + 1)- -- sorts the points on the first coordinate, and then associates each point- -- with an index,; its rank in terms of this first coordinate.- g = NonEmpty.zipWith (\i (p :+ e) -> p :+ (i :+ e)) (NonEmpty.fromList [0..])- . sortOn' 1-- -- node' :: b -> a -> b -> b- -- node' :: SplitTree d p r () -> Int -> SplitTree d p r () -> SplitTree d p r ()- node' l j r = Node l (NodeData j (bbOf l <> bbOf r) ()) r----- | Given a split tree, generate the Well separated pairs------ running time: \(O(s^d n)\)-wellSeparatedPairs :: (Floating r, Ord r, Arity d, Arity (d + 1))- => r -> SplitTree d p r a -> [WSP d p r a]-wellSeparatedPairs s = f- where- f (Leaf _) = []- f (Node l _ r) = findPairs s l r ++ f l ++ f r------ -- | Given a split tree, generate the well separated pairs such that one set is--- -- a singleton.--- -- running time: \(O(s^d n\log n)\)--- wellSeparatedPairSingletons :: (Fractional r, Ord r, AlwaysTrueWSPD d)--- => r -> SplitTree d p r a -> [(Point d r :+ p, PointSet d p r (Sized a))]--- wellSeparatedPairSingletons s t = concatMap split $ wellSeparatedPairs s t'--- where--- split (l,r) = undefined--- -- | measure l <= measure r = map (,r) $ F.toList l--- -- | otherwise = map (,l) $ F.toList r--- t' = foldUpData (\l nd r -> )---- t-------------------------------------------------------------------------------------- * Building the split tree---- | Given the points, sorted in every dimension, recursively build a split tree------ The algorithm works in rounds. Each round takes O(n) time, and halves the--- number of points. Thus, the total running time is O(n log n).------ The algorithm essentially builds a path in the split tree; at every node on--- the path that we construct, we split the point set into two sets (L,R)--- according to the longest side of the bounding box.------ The smaller set is "assigned" to the current node and set asside. We--- continue to build the path with the larger set until the total number of--- items remaining is less than n/2.------ To start the next round, each node on the path needs to have the points--- assigned to that node, sorted in each dimension (i.e. the Vector--- (PointSeq))'s. Since we have the level assignment, we can compute these--- lists by traversing each original input list (i.e. one for every dimension)--- once, and partition the points based on their level assignment.-fairSplitTree' :: (Fractional r, Ord r, Arity d, 1 <= d- , Show r, Show p- )- => Int -> GV.Vector d (PointSeq d (Idx :+ p) r)- -> BinLeafTree Int (Point d r :+ p)-fairSplitTree' n pts- | n <= 1 = let p = LSeq.head $ pts^.GV.element (C :: C 0) in Leaf (dropIdx p)- | otherwise = foldr node' (V.last path) $ V.zip nodeLevels (V.init path)- where- -- note that points may also be assigned level 'Nothing'.- (levels, nodeLevels'@(maxLvl NonEmpty.:| _)) = runST $ do- lvls <- MV.replicate n Nothing- ls <- runReaderT (assignLevels (n `div` 2) 0 pts (Level 0 Nothing) []) lvls- lvls' <- V.unsafeFreeze lvls- pure (lvls',ls)-- -- TODO: We also need to report the levels in the order in which they are- -- assigned to nodes-- nodeLevels = V.fromList . L.reverse . NonEmpty.toList $ nodeLevels'-- -- levels = traceShow ("Levels",levels',maxLvl) levels'-- -- path = traceShow ("path", path',nodeLevels) path'- distrPts = distributePoints (1 + maxLvl^.unLevel) levels pts-- path = recurse <$> distrPts -- (traceShow ("distributed pts",distrPts) distrPts)-- -- node' (lvl,lc) rc | traceShow ("node' ",lvl,lc,rc) False = undefined- node' (lvl,lc) rc = case lvl^?widestDim._Just of- Nothing -> error "Unknown widest dimension"- Just j -> Node lc j rc- recurse pts' = fairSplitTree' (length $ pts'^.GV.element (C :: C 0))- (reIndexPoints pts')---- | Assign the points to their the correct class. The 'Nothing' class is--- considered the last class-distributePoints :: (Arity d , Show r, Show p)- => Int -> V.Vector (Maybe Level)- -> GV.Vector d (PointSeq d (Idx :+ p) r)- -> V.Vector (GV.Vector d (PointSeq d (Idx :+ p) r))-distributePoints k levels = transpose . fmap (distributePoints' k levels)--transpose :: Arity d => GV.Vector d (V.Vector a) -> V.Vector (GV.Vector d a)-transpose = V.fromList . map GV.vectorFromListUnsafe . L.transpose- . map V.toList . F.toList---- | Assign the points to their the correct class. The 'Nothing' class is--- considered the last class-distributePoints' :: Int -- ^ number of classes- -> V.Vector (Maybe Level) -- ^ level assignment- -> PointSeq d (Idx :+ p) r -- ^ input points- -> V.Vector (PointSeq d (Idx :+ p) r)-distributePoints' k levels pts- | otherwise- = fmap fromSeqUnsafe $ V.create $ do- v <- MV.replicate k mempty- forM_ pts $ \p ->- append v (level p) p- pure v- where- level p = maybe (k-1) _unLevel $ levels V.! (p^.extra.core)- append v i p = MV.read v i >>= MV.write v i . (S.|> p)--fromSeqUnsafe = LSeq.promise . LSeq.fromSeq---- | Given a sequence of points, whose index is increasing in the first--- dimension, i.e. if idx p < idx q, then p[0] < q[0].--- Reindex the points so that they again have an index--- in the range [0,..,n'], where n' is the new number of points.------ running time: O(n' * d) (more or less; we are actually using an intmap for--- the lookups)------ alternatively: I can unsafe freeze and thaw an existing vector to pass it--- along to use as mapping. Except then I would have to force the evaluation--- order, i.e. we cannot be in 'reIndexPoints' for two of the nodes at the same--- time.------ so, basically, run reIndex points in ST as well.-reIndexPoints :: (Arity d, 1 <= d)- => GV.Vector d (PointSeq d (Idx :+ p) r)- -> GV.Vector d (PointSeq d (Idx :+ p) r)-reIndexPoints ptsV = fmap reIndex ptsV- where- pts = ptsV^.GV.element (C :: C 0)-- reIndex = fmap (\p -> p&extra.core %~ fromJust . flip IntMap.lookup mapping')- mapping' = IntMap.fromAscList $ zip (map (^.extra.core) . F.toList $ pts) [0..]---- | ST monad with access to the vector storign the level of the points.-type RST s = ReaderT (MV.MVector s (Maybe Level)) (ST s)---- | Assigns the points to a level. Returns the list of levels used. The first--- level in the list is the level assigned to the rest of the nodes. Their--- level is actually still set to Nothing in the underlying array.-assignLevels :: (Fractional r, Ord r, Arity d- , Show r, Show p- )- => Int -- ^ Number of items we need to collect- -> Int -- ^ Number of items we collected so far- -> GV.Vector d (PointSeq d (Idx :+ p) r)- -> Level -- ^ next level to use- -> [Level] -- ^ Levels used so far- -> RST s (NonEmpty.NonEmpty Level)-assignLevels h m pts l prevLvls- | m >= h = pure (l NonEmpty.:| prevLvls)- | otherwise = do- pts' <- compactEnds pts- -- find the widest dimension j = i+1- let j = widestDimension pts'- i = j - 1 -- traceShow ("i",j,pts') j - 1- extJ = (extends pts')^.ix' i- mid = midPoint extJ-- -- find the set of points that we have to delete, by looking at the sorted- -- list L_j. As a side effect, this will remove previously assigned points- -- from L_j.- (lvlJPts,deletePts) <- findAndCompact j (pts'^.ix' i) mid- let pts'' = pts'&ix' i .~ lvlJPts- l' = l&widestDim .~ Just j- forM_ deletePts $ \p ->- assignLevel p l'- assignLevels h (m + length deletePts) pts'' (nextLevel l) (l' : prevLvls)---- | Remove already assigned pts from the ends of all vectors.-compactEnds :: Arity d- => GV.Vector d (PointSeq d (Idx :+ p) r)- -> RST s (GV.Vector d (PointSeq d (Idx :+ p) r))-compactEnds = traverse compactEnds'---- | Assign level l to point p-assignLevel :: (c :+ (Idx :+ p)) -> Level -> RST s ()-assignLevel p l = ask >>= \levels -> lift $ MV.write levels (p^.extra.core) (Just l)---- | Get the level of a point-levelOf :: (c :+ (Idx :+ p)) -> RST s (Maybe Level)-levelOf p = ask >>= \levels -> lift $ MV.read levels (p^.extra.core)---- | Test if the point already has a level assigned to it.-hasLevel :: c :+ (Idx :+ p) -> RST s Bool-hasLevel = fmap isJust . levelOf---- | Remove allready assigned points from the sequence------ pre: there are points remaining-compactEnds' :: PointSeq d (Idx :+ p) r- -> RST s (PointSeq d (Idx :+ p) r)-compactEnds' (l0 :<| s0) = fmap fromSeqUnsafe . goL $ l0 S.<| toSeq s0- where- goL s@(S.viewl -> l S.:< s') = hasLevel l >>= \case- False -> goR s- True -> goL s'- goR s@(S.viewr -> s' S.:> r) = hasLevel r >>= \case- False -> pure s- True -> goR s'----- | Given the points, ordered by their j^th coordinate, split the point set--- into a "left" and a "right" half, i.e. the points whose j^th coordinate is--- at most the given mid point m, and the points whose j^th coordinate is--- larger than m.------ We return a pair (Largest set, Smallest set)---------fi ndAndCompact works by simultaneously traversing the points from left to--- right, and from right to left. As soon as we find a point crossing the mid--- point we stop and return. Thus, in principle this takes only O(|Smallest--- set|) time.------ running time: O(|Smallest set|) + R, where R is the number of *old* points--- (i.e. points that should have been removed) in the list.-findAndCompact :: (Ord r, Arity d- , Show r, Show p- )- => Int- -- ^ the dimension we are in, i.e. so that we know- -- which coordinate of the point to compare- -> PointSeq d (Idx :+ p) r- -> r -- ^ the mid point- -> RST s ( PointSeq d (Idx :+ p) r- , PointSeq d (Idx :+ p) r- )-findAndCompact j (l0 :<| s0) m = fmap select . stepL $ l0 S.<| toSeq s0- where- -- stepL and stepR together build a data structure (FAC l r S) that- -- contains the left part of the list, i.e. the points before midpoint, and- -- the right part of the list., and a value S that indicates which part is- -- the short side.-- -- stepL takes a step on the left side of the list; if the left point l- -- already has been assigned, we continue waling along (and "ignore" the- -- point). If it has not been assigned, and is before the mid point, we- -- take a step from the right, and add l onto the left part. If it is- -- larger than the mid point, we have found our split.- -- stepL :: S.Seq (Point d r :+ (Idx :+ p)) -> ST s (FindAndCompact d r (Idx :+ p))- stepL s = case S.viewl s of- S.EmptyL -> pure $ FAC mempty mempty L- l S.:< s' -> hasLevel l >>= \case- False -> if l^.core.unsafeCoord j <= m- then addL l <$> stepR s'- else pure $ FAC mempty s L- True -> stepL s' -- delete, continue left-- -- stepR :: S.Seq (Point d r :+ (Idx :+ p)) -> ST s (FindAndCompact d r (Idx :+ p))- stepR s = case S.viewr s of- S.EmptyR -> pure $ FAC mempty mempty R- s' S.:> r -> hasLevel r >>= \case- False -> if r^.core.unsafeCoord j >= m- then addR r <$> stepL s'- else pure $ FAC s mempty R- True -> stepR s'--- addL l x = x&leftPart %~ (l S.<|)- addR r x = x&rightPart %~ (S.|> r)-- select = over both fromSeqUnsafe . select'-- -- select' f | traceShow ("select'", f) False = undefined- select' (FAC l r L) = (r, l)- select' (FAC l r R) = (l, r)----- | Find the widest dimension of the point set------ pre: points are sorted according to their dimension-widestDimension :: (Num r, Ord r, Arity d) => GV.Vector d (PointSeq d p r) -> Int-widestDimension = fst . L.maximumBy (comparing snd) . zip [1..] . F.toList . widths--widths :: (Num r, Arity d) => GV.Vector d (PointSeq d p r) -> GV.Vector d r-widths = fmap Range.width . extends------ | get the extends of the set of points in every dimension, i.e. the left and--- right boundaries.------ pre: points are sorted according to their dimension-extends :: Arity d => GV.Vector d (PointSeq d p r) -> GV.Vector d (Range r)-extends = GV.imap (\i pts ->- ClosedRange ((LSeq.head pts)^.core.unsafeCoord (i + 1))- ((LSeq.last pts)^.core.unsafeCoord (i + 1)))-------------------------------------------------------------------------------------- * Finding Well Separated Pairs--findPairs :: (Floating r, Ord r, Arity d, Arity (d + 1))- => r -> SplitTree d p r a -> SplitTree d p r a- -> [WSP d p r a]-findPairs s l r- | areWellSeparated' s l r = [(l,r)]- | maxWidth l <= maxWidth r = concatMap (findPairs s l) $ children' r- | otherwise = concatMap (findPairs s r) $ children' l----- | Test if the two sets are well separated with param s-areWellSeparated :: (Arity d, Arity (d + 1), Fractional r, Ord r)- => r -- ^ separation factor- -> SplitTree d p r a- -> SplitTree d p r a -> Bool-areWellSeparated _ (Leaf _) (Leaf _) = True-areWellSeparated s l r = boxBox s (bbOf l) (bbOf r)----- areWellSeparated s (Leaf p) (Node _ nd _) = pointBox s (p^.core) (nd^.bBox)--- areWellSeparated s (Node _ nd _) (Leaf p) = pointBox s (p^.core) (nd^.bBox)--- areWellSeparated s (Node _ ld _) (Node _ rd _) = boxBox s (ld^.bBox) (rd^.bBox)----- -- | Test if the point and the box are far enough appart--- pointBox :: (Fractional r, Ord r, AlwaysTruePFT d, AlwaysTrueTransformation d)--- => r -> Point d r -> Box d p r -> Bool--- pointBox s p b = not $ p `inBox` b'--- where--- v = (centerPoint b)^.vector--- b' = translateBy v . scaleUniformlyBy s . translateBy ((-1) *^ v) $ b---- | Test if the two boxes are sufficiently far appart-boxBox :: (Fractional r, Ord r, Arity d, Arity (d + 1))- => r -> Box d p r -> Box d p r -> Bool-boxBox s lb rb = boxBox' lb rb && boxBox' rb lb- where- boxBox' b' b = not $ b' `intersects` bOut- where- v = (centerPoint b)^.vector- bOut = translateBy v . scaleUniformlyBy s . translateBy ((-1) *^ v) $ b------------------------------------------------------------------------------------- * Alternative def if wellSeparated that uses fractional---areWellSeparated' :: (Floating r, Ord r, Arity d)- => r- -> SplitTree d p r a- -> SplitTree d p r a- -> Bool-areWellSeparated' _ (Leaf _) (Leaf _) = True-areWellSeparated' s l r = boxBox1 s (bbOf l) (bbOf r)---- (Leaf p) (Node _ nd _) = pointBox' s (p^.core) (nd^.bBox)--- areWellSeparated' s (Node _ nd _) (Leaf p) = pointBox' s (p^.core) (nd^.bBox)--- areWellSeparated' s (Node _ ld _) (Node _ rd _) = boxBox' s (ld^.bBox) (rd^.bBox)--boxBox1 :: (Floating r, Ord r, Arity d) => r -> Box d p r -> Box d p r -> Bool-boxBox1 s lb rb = euclideanDist (centerPoint lb) (centerPoint rb) >= (s+1)*d- where- diam b = euclideanDist (b^.minP.core.cwMin) (b^.maxP.core.cwMax)- d = max (diam lb) (diam rb)---------------------------------------------------------------------------------------- * Helper stuff----- | Computes the maximum width of a splitTree-maxWidth :: (Arity d, Num r)- => SplitTree d p r a -> r-maxWidth (Leaf _) = 0-maxWidth (Node _ (NodeData i b _) _) = fromJust $ widthIn' i b---- | 'Computes' the bounding box of a split tree-bbOf :: Ord r => SplitTree d p r a -> Box d () r-bbOf (Leaf p) = boundingBox $ p^.core-bbOf (Node _ (NodeData _ b _) _) = b---children' :: BinLeafTree v a -> [BinLeafTree v a]-children' (Leaf _) = []-children' (Node l _ r) = [l,r]----- | Turn a traversal into lens-ix' :: (Arity d, KnownNat d) => Int -> Lens' (GV.Vector d a) a-ix' i = singular (GV.element' i)---dropIdx :: core :+ (t :+ extra) -> core :+ extra-dropIdx (p :+ (_ :+ e)) = p :+ e----------------------------------------------------------------------------------+import Algorithms.Geometry.WSPD
src/Data/Geometry/Arrangement/Internal.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TemplateHaskell #-} -------------------------------------------------------------------------------- -- | -- Module : Data.Geometry.Arrangement.Internal@@ -11,10 +11,12 @@ -------------------------------------------------------------------------------- module Data.Geometry.Arrangement.Internal where +import Algorithms.BinarySearch import Control.Lens-import qualified Data.CircularSeq as CSeq+import Data.Bifunctor+import qualified Data.CircularSeq as CSeq import Data.Ext-import qualified Data.Foldable as F+import qualified Data.Foldable as F import Data.Geometry.Boundary import Data.Geometry.Box import Data.Geometry.Line@@ -22,11 +24,10 @@ import Data.Geometry.PlanarSubdivision import Data.Geometry.Point import Data.Geometry.Properties-import qualified Data.List as List+import qualified Data.List as List import Data.Maybe-import Data.Ord (Down(..))-import Data.Sequence.Util-import qualified Data.Vector as V+import Data.Ord (Down (..))+import qualified Data.Vector as V import Data.Vinyl.CoRec --------------------------------------------------------------------------------@@ -52,39 +53,36 @@ -- | Builds an arrangement of \(n\) lines -- -- running time: \(O(n^2\log n\)-constructArrangement :: (Ord r, Fractional r)- => proxy s- -> [Line 2 r :+ l]- -> Arrangement s l () (Maybe l) () r-constructArrangement px ls = let b = makeBoundingBox ls- in constructArrangementInBox' px b ls+constructArrangement :: forall s l r. (Ord r, Fractional r)+ => [Line 2 r :+ l]+ -> Arrangement s l () (Maybe l) () r+constructArrangement ls = let b = makeBoundingBox ls+ in constructArrangementInBox' b ls -- | Constructs the arrangemnet inside the box. note that the resulting box -- may be larger than the given box to make sure that all vertices of the -- arrangement actually fit. -- -- running time: \(O(n^2\log n\)-constructArrangementInBox :: (Ord r, Fractional r)- => proxy s- -> Rectangle () r- -> [Line 2 r :+ l]- -> Arrangement s l () (Maybe l) () r-constructArrangementInBox px rect ls = let b = makeBoundingBox ls- in constructArrangementInBox' px (b <> rect) ls+constructArrangementInBox :: forall s l r. (Ord r, Fractional r)+ => Rectangle () r+ -> [Line 2 r :+ l]+ -> Arrangement s l () (Maybe l) () r+constructArrangementInBox rect ls = let b = makeBoundingBox ls+ in constructArrangementInBox' (b <> rect) ls -- | Constructs the arrangemnet inside the box. (for parts to be useful, it is -- assumed this boxfits at least the boundingbox of the intersections in the -- Arrangement)-constructArrangementInBox' :: (Ord r, Fractional r)- => proxy s- -> Rectangle () r- -> [Line 2 r :+ l]- -> Arrangement s l () (Maybe l) () r-constructArrangementInBox' px rect ls =+constructArrangementInBox' :: forall s l r. (Ord r, Fractional r)+ => Rectangle () r+ -> [Line 2 r :+ l]+ -> Arrangement s l () (Maybe l) () r+constructArrangementInBox' rect ls = Arrangement (V.fromList ls) subdiv rect (link parts' subdiv) where- subdiv = fromConnectedSegments px segs+ subdiv = fromConnectedSegments segs & rawVertexData.traverse.dataVal .~ () (segs,parts') = computeSegsAndParts rect ls @@ -97,7 +95,7 @@ computeSegsAndParts rect ls = ( segs <> boundarySegs, parts') where segs = map (&extra %~ Just)- . concatMap (\(l,ls') -> perLine rect l ls') $ makePairs ls+ . concatMap (uncurry (perLine rect)) $ makePairs ls boundarySegs = map (:+ Nothing) . toSegments . dupFirst $ map fst parts' dupFirst = \case [] -> [] xs@(x:_) -> xs ++ [x]@@ -112,7 +110,7 @@ rmDuplicates = map head . List.group vs = mapMaybe (m `intersectionPoint`) ls vs' = maybe [] (\(p,q) -> [p,q]) . asA @(Point 2 r, Point 2 r)- $ (m^.core) `intersect` (Boundary b)+ $ (m^.core) `intersect` Boundary b intersectionPoint :: forall r l. (Ord r, Fractional r)@@ -121,7 +119,7 @@ toSegments :: Ord r => [Point 2 r] -> [LineSegment 2 () r]-toSegments ps = let pts = map ext $ ps in+toSegments ps = let pts = map ext ps in zipWith ClosedLineSegment pts (tail pts) @@ -181,7 +179,7 @@ => [Line 2 r :+ l] -> LineSegment 2 q r -> [(Point 2 r, Line 2 r :+ l)] sideIntersections ls s = let l = supportingLine s :+ undefined- in List.sortOn fst . filter (flip onSegment s . fst)+ in List.sortOn fst . filter ((`intersects` s) . fst) . mapMaybe (\m -> (,m) <$> l `intersectionPoint` m) $ ls -- | Constructs the unbounded intersections. Reported in clockwise direction.@@ -192,13 +190,10 @@ unBoundedParts rect ls = [tl] <> t <> [tr] <> reverse r <> [br] <> reverse b <> [bl] <> l where sideIntersections' = over (traverse._2) Just . sideIntersections ls- (t,r,b,l) = map4 sideIntersections' $ sides rect- (tl,tr,br,bl) = map4 ((,Nothing) . (^.core)) $ corners rect+ Sides t r b l = sideIntersections' <$> sides rect+ Corners tl tr br bl = (,Nothing) . (^.core) <$> corners rect -map4 :: (a -> b) -> (a,a,a,a) -> (b,b,b,b)-map4 f (a,b',c,d) = (f a, f b', f c, f d)- -- | Links the vertices of the outer boundary with those in the subdivision link :: Eq r => [(Point 2 r, a)] -> PlanarSubdivision s v (Maybe e) f r -> V.Vector (Point 2 r, VertexId' s, a)@@ -215,10 +210,10 @@ makePairs = go where go [] = []- go (x:xs) = (x,xs) : map (\(y,ys) -> (y,x:ys)) (go xs)+ go (x:xs) = (x,xs) : map (second (x:)) (go xs) -allPairs :: [a] -> [(a,a)]-allPairs ys = go ys+allPairs :: [a] -> [(a,a)]+allPairs = go where go [] = [] go (x:xs) = map (x,) xs ++ go xs@@ -247,7 +242,7 @@ => Line 2 r -> Arrangement s l v (Maybe e) f r -> Maybe (Dart s) findStart l arr = do (p,_) <- asA @(Point 2 r, Point 2 r) $- l `intersect` (Boundary $ arr^.boundedArea)+ l `intersect` Boundary (arr^.boundedArea) (_,v,_) <- findStartVertex p arr findStartDart (arr^.subdivision) v @@ -267,13 +262,13 @@ -> Maybe (Point 2 r, VertexId' s, Maybe (Line 2 r :+ l)) findStartVertex p arr = do ss <- findSide p- i <- binarySearchVec (pred' ss) (arr^.unboundedIntersections)+ i <- binarySearchIdxIn (pred' ss) (arr^.unboundedIntersections) pure $ arr^.unboundedIntersections.singular (ix i) where- (t,r,b,l) = sides'' $ arr^.boundedArea- sides'' = map4 (\(ClosedLineSegment a c) -> LineSegment (Closed a) (Open c)) . sides+ Sides t r b l = sides'' $ arr^.boundedArea+ sides'' = fmap (\(ClosedLineSegment a c) -> LineSegment (Closed a) (Open c)) . sides - findSide q = fmap fst . List.find (onSegment q . snd) $ zip [1..] [t,r,b,l]+ findSide q = fmap fst . List.find (intersects q. snd) $ zip [1..] [t,r,b,l] pred' ss (q,_,_) = let Just j = findSide q x = before (ss,p) (j,q)
src/Data/Geometry/Ball.hs view
@@ -31,6 +31,7 @@ import Linear.Matrix import Linear.V3 (V3(..)) + -------------------------------------------------------------------------------- -- * A d-dimensional ball @@ -75,6 +76,7 @@ -- * Querying if a point lies in a ball +-- | Query location of a point relative to a d-dimensional ball. inBall :: (Arity d, Ord r, Num r) => Point d r -> Ball d p r -> PointLocationResult p `inBall` (Ball c sr) = case qdA p (c^.core) `compare` sr of@@ -123,36 +125,43 @@ pattern Sphere c r = Boundary (Ball c r) {-# COMPLETE Sphere #-} --+-- |+_BallSphere :: Iso (Disk p r) (Disk p s) (Circle p r) (Circle p s)+_BallSphere = _Boundary -------------------------------------------------------------------------------- -- * Disks and Circles, aka 2-dimensional Balls and Spheres type Disk p r = Ball 2 p r +-- | Given the center and the squared radius, constructs a disk pattern Disk :: Point 2 r :+ p -> r -> Disk p r pattern Disk c r = Ball c r {-# COMPLETE Disk #-} - type Circle p r = Sphere 2 p r +-- | Iso for converting between Disks and Circles, i.e. forgetting the boundary+_DiskCircle :: Iso (Disk p r) (Disk p s) (Circle p r) (Circle p s)+_DiskCircle = _BallSphere++-- | Given the center and the squared radius, constructs a circle pattern Circle :: Point 2 r :+ p -> r -> Circle p r pattern Circle c r = Sphere c r {-# COMPLETE Circle #-} +{- HLINT ignore disk -} -- | Given three points, get the disk through the three points. If the three -- input points are colinear we return Nothing -- -- >>> disk (Point2 0 10) (Point2 10 0) (Point2 (-10) 0)--- Just (Ball {_center = Point2 [0.0,0.0] :+ (), _squaredRadius = 100.0})-disk :: (Eq r, Fractional r)+-- Just (Ball {_center = Point2 0.0 0.0 :+ (), _squaredRadius = 100.0})+disk :: (Ord r, Fractional r) => Point 2 r -> Point 2 r -> Point 2 r -> Maybe (Disk () r) disk p q r = match (f p `intersect` f q) $- (H $ \NoIntersection -> Nothing)- :& (H $ \c@(Point _) -> Just $ Ball (ext c) (qdA c p))- :& (H $ \_ -> Nothing)+ H (\NoIntersection -> Nothing)+ :& H (\c@Point{} -> Just $ Ball (ext c) (qdA c p))+ :& H (\_ -> Nothing) :& RNil -- If the intersection is not a point, The two lines f p and f q are -- parallel, that means the three input points where colinear.@@ -184,16 +193,36 @@ newtype Touching p = Touching p deriving (Show,Eq,Ord,Functor,F.Foldable,T.Traversable) -- | No intersection, one touching point, or two points-type instance IntersectionOf (Line 2 r) (Circle p r) = [ NoIntersection- , Touching (Point 2 r)- , (Point 2 r, Point 2 r)- ]+type instance IntersectionOf (Line d r) (Sphere d p r) = [ NoIntersection+ , Touching (Point d r)+ , (Point d r, Point d r)+ ] +instance {-# OVERLAPPABLE #-} (Ord r, Fractional r, Arity d)+ => Line d r `HasIntersectionWith` Sphere d q r where+ l `intersects` (Sphere (c :+ _) r) = let closest = pointClosestTo c l+ in squaredEuclideanDist c closest <= r -instance (Ord r, Floating r) => (Line 2 r) `IsIntersectableWith` (Circle p r) where+instance {-# OVERLAPPING #-} (Ord r, Num r) => Line 2 r `HasIntersectionWith` Circle p r where+ (Line p' v) `intersects` (Circle (c :+ _) r) = discr >= 0+ where+ (Vector2 vx vy) = v+ -- (px, py) is the vector/point after translating the circle s.t. it is centered at the+ -- origin+ (Vector2 px py) = p' .-. c - nonEmptyIntersection = defaultNonEmptyIntersection+ -- let q lambda be the intersection point. We solve the following equation+ -- solving the equation (q_x)^2 + (q_y)^2 = r^2 then yields the equation+ -- L^2(vx^2 + vy^2) + L2(px*vx + py*vy) + px^2 + py^2 = 0+ -- where L = \lambda+ aa = vx^2 + vy^2+ bb = 2 * (px * vx + py * vy)+ cc = px^2 + py^2 - r^2+ discr = bb^2 - 4*aa*cc +instance (Ord r, Floating r) => Line 2 r `IsIntersectableWith` Circle p r where++ nonEmptyIntersection = defaultNonEmptyIntersection (Line p' v) `intersect` (Circle (c :+ _) r) = case discr `compare` 0 of LT -> coRec NoIntersection EQ -> coRec . Touching $ q' (lambda (+))@@ -225,23 +254,30 @@ -- | A line segment may not intersect a circle, touch it, or intersect it -- properly in one or two points.-type instance IntersectionOf (LineSegment 2 p r) (Circle q r) = [ NoIntersection- , Touching (Point 2 r)- , Point 2 r- , (Point 2 r, Point 2 r)- ]+type instance IntersectionOf (LineSegment d p r) (Sphere d q r) = [ NoIntersection+ , Touching (Point d r)+ , Point d r+ , (Point d r, Point d r)+ ] +instance (Ord r, Fractional r, Arity d)+ => LineSegment d p r `HasIntersectionWith` Sphere d q r where+ seg `intersects` (Sphere (c :+ _) r) = let closest = pointClosestTo c (supportingLine seg)+ in case squaredEuclideanDist c closest `compare` r of+ LT -> True+ EQ -> closest `intersects` seg+ GT -> False -instance (Ord r, Floating r) => (LineSegment 2 p r) `IsIntersectableWith` (Circle q r) where+instance (Ord r, Floating r) => LineSegment 2 p r `IsIntersectableWith` Circle q r where nonEmptyIntersection = defaultNonEmptyIntersection s `intersect` c = match (supportingLine s `intersect` c) $- (H $ \NoIntersection -> coRec NoIntersection)- :& (H $ \(Touching p) -> if p `onSegment` s then coRec $ Touching p+ H (\NoIntersection -> coRec NoIntersection)+ :& H (\(Touching p) -> if p `intersects` s then coRec $ Touching p else coRec NoIntersection )- :& (H $ \(p,q) -> case (p `onSegment` s, q `onSegment` s) of+ :& H (\(p,q) -> case (p `intersects` s, q `intersects` s) of (False,False) -> coRec NoIntersection (False,True) -> coRec q (True, False) -> coRec p
+ src/Data/Geometry/BezierSpline.hs view
@@ -0,0 +1,641 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE UndecidableInstances #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.BezierSpline+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Data.Geometry.BezierSpline(+ BezierSpline (BezierSpline, Bezier2, Bezier3)+ , controlPoints+ , fromPointSeq+ , endPoints+ , Data.Geometry.BezierSpline.reverse++ , evaluate+ , split+ , splitMany+ , splitMonotone+ , splitByPoints+ , extension+ , extend+ , growTo+ , merge+ , subBezier+ , tangent+ , approximate+ , parameterOf+ , snap+ , intersectB+ , colinear+ , quadToCubic+ ) where++import Algorithms.Geometry.ConvexHull.GrahamScan+import Algorithms.Geometry.SmallestEnclosingBall.RIC+import Algorithms.Geometry.SmallestEnclosingBall.Types+import Control.Lens hiding (Empty)+import Data.Ext+import qualified Data.Foldable as F+import Data.Geometry.Ball+import Data.Geometry.Box.Internal+import Data.Geometry.Line+import Data.Geometry.LineSegment hiding (endPoints)+import Data.Geometry.Point+import Data.Geometry.PolyLine (PolyLine(..))+import Data.Geometry.Polygon+import Data.Geometry.Polygon.Convex hiding (merge)+import Data.Geometry.Properties+import Data.Geometry.Transformation+import Data.Geometry.Vector hiding (init)+import Data.LSeq (LSeq)+import qualified Data.LSeq as LSeq+import Data.List (sort)+import qualified Data.List.NonEmpty as NonEmpty+import Data.Sequence (Seq(..))+import qualified Data.Sequence as Seq+import Data.Traversable (fmapDefault,foldMapDefault)+import GHC.TypeNats+import qualified Test.QuickCheck as QC++-- import Debug.Trace++--------------------------------------------------------------------------------++-- | Datatype representing a Bezier curve of degree \(n\) in \(d\)-dimensional space.+newtype BezierSpline n d r = BezierSpline { _controlPoints :: LSeq (1+n) (Point d r) }+-- makeLenses ''BezierSpline++-- | Bezier control points. With n degrees, there are n+1 control points.+controlPoints :: Iso (BezierSpline n1 d1 r1) (BezierSpline n2 d2 r2)+ (LSeq (1+n1) (Point d1 r1)) (LSeq (1+n2) (Point d2 r2))+controlPoints = iso _controlPoints BezierSpline++-- | Quadratic Bezier Spline+pattern Bezier2 :: Point d r -> Point d r -> Point d r -> BezierSpline 2 d r+pattern Bezier2 p q r <- (F.toList . LSeq.take 3 . _controlPoints -> [p,q,r])+ where+ Bezier2 p q r = fromPointSeq . Seq.fromList $ [p,q,r]+{-# COMPLETE Bezier2 #-}++-- | Cubic Bezier Spline+pattern Bezier3 :: Point d r -> Point d r -> Point d r -> Point d r -> BezierSpline 3 d r+pattern Bezier3 p q r s <- (F.toList . LSeq.take 4 . _controlPoints -> [p,q,r,s])+ where+ Bezier3 p q r s = fromPointSeq . Seq.fromList $ [p,q,r,s]+{-# COMPLETE Bezier3 #-}++-- | Constructs the Bezier Spline from a given sequence of points.+fromPointSeq :: Seq (Point d r) -> BezierSpline n d r+fromPointSeq = BezierSpline . LSeq.promise . LSeq.fromSeq+++deriving instance (Arity d, Eq r) => Eq (BezierSpline n d r)++type instance Dimension (BezierSpline n d r) = d+type instance NumType (BezierSpline n d r) = r++instance (Arity n, Arity d, QC.Arbitrary r) => QC.Arbitrary (BezierSpline n d r) where+ arbitrary = fromPointSeq . Seq.fromList <$> QC.vector (fromIntegral . (1+) . natVal $ C @n)++{-+instance (Arity n, Arity d, QC.Arbitrary r, Ord r) => QC.Arbitrary (BezierSpline n d r) where+ arbitrary = fromPointSeq . Seq.fromList <$> allDifferent (fromIntegral . (1+) . natVal $ C @n)++-- | Generates a set of unique items.+allDifferent :: (Ord a, QC.Arbitrary a) => Int -> QC.Gen [a]+allDifferent n = take n . Set.toList . go maxattempts mempty <$> QC.infiniteList+ where+ maxattempts = 100+ go 0 s _ = s -- too many attempts+ go t s (x:xs) | Set.size s == n = s+ | otherwise = go (t-1) (Set.insert x s) xs+-}++instance (Arity d, Show r) => Show (BezierSpline n d r) where+ show (BezierSpline ps) =+ mconcat [ "BezierSpline", show $ length ps - 1, " ", show (F.toList ps) ]++instance Arity d => Functor (BezierSpline n d) where+ fmap = fmapDefault++instance Arity d => Foldable (BezierSpline n d) where+ foldMap = foldMapDefault++instance Arity d => Traversable (BezierSpline n d) where+ traverse f (BezierSpline ps) = BezierSpline <$> traverse (traverse f) ps++instance (Fractional r, Arity d, Arity (d + 1), Arity n)+ => IsTransformable (BezierSpline n d r) where+ transformBy = transformPointFunctor++instance PointFunctor (BezierSpline n d) where+ pmap f = over controlPoints (fmap f)++--------------------------------------------------------------------------------++-- | Convert a quadratic bezier to a cubic bezier.+quadToCubic :: Fractional r => BezierSpline 2 2 r -> BezierSpline 3 2 r+quadToCubic (Bezier2 a (Point b) c) =+ Bezier3 a (Point $ (1/3)*^ (toVec a ^+^ 2*^b)) (Point $ (1/3)*^ (2*^ b ^+^ toVec c)) c++--------------------------------------------------------------------------------++-- | Reverse a BezierSpline+reverse :: (Arity d, Ord r, Num r) => BezierSpline n d r -> BezierSpline n d r+reverse = controlPoints %~ LSeq.reverse+++-- | Evaluate a BezierSpline curve at time t in [0, 1]+--+-- pre: \(t \in [0,1]\)+evaluate :: (Arity d, Eq r, Num r) => BezierSpline n d r -> r -> Point d r+evaluate b 0 = fst $ endPoints b+evaluate b 1 = snd $ endPoints b+evaluate b t = evaluate' (b^.controlPoints.to LSeq.toSeq)+ where+ evaluate' = \case+ (p :<| Empty) -> p+ pts@(_ :<| tl) -> let (ini :|> _) = pts in evaluate' $ Seq.zipWith blend ini tl+ _ -> error "evaluate: absurd"+ blend p q = p .+^ t *^ (q .-. p)++-- | Extract a tangent vector from the first to the second control point.+tangent :: (Arity d, Num r, 1 <= n) => BezierSpline n d r -> Vector d r+tangent b = b^?!controlPoints.ix 1 .-. b^?!controlPoints.ix 0++-- | Return the endpoints of the Bezier spline.+endPoints :: BezierSpline n d r -> (Point d r, Point d r)+endPoints b = let (p LSeq.:<| _) = b^.controlPoints+ (_ LSeq.:|> q) = b^.controlPoints+ in (p,q)+++++-- | Restrict a Bezier curve to the piece between parameters t < u in [0, 1].+subBezier :: (KnownNat n, Arity d, Ord r, Num r)+ => r -> r -> BezierSpline n d r -> BezierSpline n d r+subBezier t u = fst . split u . snd . split t+++-- | Compute the convex hull of the control polygon of a 2-dimensional Bezier curve.+-- Should also work in any dimension, but convex hull is not yet implemented.+convexHullB :: (Ord r, Fractional r) => BezierSpline n 2 r -> ConvexPolygon () r+convexHullB = convexHull . NonEmpty.fromList . fmap ext . F.toList . _controlPoints++--------------------------------------------------------------------------------++-- | Split a Bezier curve at time t in [0, 1] into two pieces.+split :: forall n d r. (KnownNat n, Arity d, Ord r, Num r)+ => r -> BezierSpline n d r -> (BezierSpline n d r, BezierSpline n d r)+split t b | t < 0 = error "split: t < 0" -- ++ show t ++ " < 0"+ | t > 1 = error "split: t > 1" -- ++ show t ++ " > 1"+ | otherwise = splitRaw t b+++-- | Split without parameter check. If t outside [0,1], will actually extend the curve+-- rather than split it.+splitRaw :: forall n d r. (KnownNat n, Arity d, Ord r, Num r)+ => r -> BezierSpline n d r -> (BezierSpline n d r, BezierSpline n d r)+splitRaw t b = let n = fromIntegral $ natVal (C @n)+ ps = collect t $ b^.controlPoints+ in ( fromPointSeq . Seq.take (n + 1) $ ps+ , fromPointSeq . Seq.drop (n + 0) $ ps+ )++-- | implementation of splitRaw+collect :: (Arity d, Ord r, Num r) => r -> LSeq n (Point d r) -> Seq (Point d r)+collect t = go . LSeq.toSeq+ where+ go = \case+ ps@(_ :<| Empty) -> ps+ ps@(p :<| tl) -> let (ini :|> q) = ps in (p :<| go (Seq.zipWith blend ini tl)) :|> q+ _ -> error "collect: absurd"++ blend p q = p .+^ t *^ (q .-. p)++-- | Split a Bezier curve into many pieces.+-- Todo: filter out duplicate parameter values!+splitMany :: forall n d r. (KnownNat n, Arity d, Ord r, Fractional r)+ => [r] -> BezierSpline n d r -> [BezierSpline n d r]+splitMany = splitManySorted . sort . map (restrict "splitMany" 0 1)++ where splitManySorted [] b' = [b']+ splitManySorted (t : ts) b' = let (a,c) = split t b'+ in a : splitManySorted (map (rescale t) ts) c+ rescale :: r -> r -> r+ rescale 1 _ = 1+ rescale t u = (u - t) / (1 - t)+++-- | Cut a Bezier curve into $x_i$-monotone pieces.+-- Can only be solved exactly for degree 4 or smaller.+-- Only gives rational result for degree 2 or smaller.+-- Currentlly implemented for degree 3.+splitMonotone :: (Arity d, Ord r, Enum r, Floating r) => Int -> BezierSpline 3 d r -> [BezierSpline 3 d r]+splitMonotone i b = splitMany (locallyExtremalParameters i b) b++{-+type family RealTypeConstraint (n :: Nat) (r :: *) :: Constraint where+ RealTypeConstraint 1 r = (Fractional r)+ RealTypeConstraint 2 r = (Fractional r)+ RealTypeConstraint 3 r = (Floating r)+ RealTypeConstraint 4 r = (Floating r)+ RealTypeConstraint 5 r = (Floating r)+ RealTypeConstraint n r = TypeError ""+-}++-- | Report all parameter values at which the derivative of the $i$th coordinate is 0.+locallyExtremalParameters :: (Arity d, Ord r, Enum r, Floating r)+ => Int -> BezierSpline 3 d r -> [r]+locallyExtremalParameters i curve =+ let [x1, x2, x3, x4] = map (view $ unsafeCoord i) $ F.toList $ _controlPoints curve+ a = 3 * x4 - 9 * x3 + 9 * x2 - 3 * x1+ b = 6 * x1 - 12 * x2 + 6 * x3+ c = 3 * x2 - 3 * x1+ in filter (\j -> 0 <= j && j <= 1) $ solveQuadraticEquation a b c+++-- | Subdivide a curve based on a sequence of points.+-- Assumes these points are all supposed to lie on the curve, and+-- snaps endpoints of pieces to these points.+-- (higher dimensions would work, but depends on convex hull)+splitByPoints :: (KnownNat n, Ord r, RealFrac r)+ => r -> [Point 2 r] -> BezierSpline n 2 r -> [BezierSpline n 2 r]+splitByPoints treshold points curve =+ let a = fst $ endPoints curve+ b = snd $ endPoints curve+ intern = filter (\p -> p /= a && p /= b) points+ times = map (parameterOf treshold curve) intern+ tipos = sort $ zip times intern+ pieces = splitMany (map fst tipos) curve+ stapts = a : map snd tipos+ endpts = map snd tipos ++ [b]+ in zipWith3 snapEndpoints stapts endpts pieces++--------------------------------------------------------------------------------++-- | Extend a Bezier curve to a parameter value t outside the interval [0,1].+-- For t < 0, returns a Bezier representation of the section of the underlying curve+-- from parameter value t until paramater value 0. For t > 1, the same from 1 to t.+--+-- pre: t outside [0,1]+extension :: forall n d r. (KnownNat n, Arity d, Ord r, Num r)+ => r -> BezierSpline n d r -> BezierSpline n d r+extension t b | t > 0 && t < 1 = error "extension: 0 < t < 1" -- ++ show t ++ " < 1"+ | t <= 0 = fst $ splitRaw t b+ | otherwise {- t >= 1-} = snd $ splitRaw t b++-- | Extend a Bezier curve to a parameter value t outside the interval [0,1].+-- For t < 0, returns a Bezier representation of the section of the underlying curve+-- from parameter value t until paramater value 1. For t > 1, the same from 0 to t.+--+-- pre: t outside [0,1]+extend :: forall n d r. (KnownNat n, Arity d, Ord r, Num r)+ => r -> BezierSpline n d r -> BezierSpline n d r+extend t b | t > 0 && t < 1 = error "extend: 0 < t < 1" -- ++ show t ++ " < 1"+ | t <= 0 = snd $ splitRaw t b+ | otherwise {- t >= 1 -} = fst $ splitRaw t b+++-- | Extend a Bezier curve to a point not on the curve, but on / close+-- to the extended underlying curve.+growTo :: (KnownNat n, Arity d, Ord r, Fractional r)+ => r -> Point d r -> BezierSpline n d r -> BezierSpline n d r+growTo treshold p b =+ let t = extendedParameterOf treshold b p+ r | t < 0 = extend t b+ | t > 1 = extend t b+ | otherwise = b+ in r++{-++-- | Tries to fit a degree n Bezier curve through a list of points, with error parameter eps.+-- Either returns an appropriate curve, or fails.+fit :: r -> [Point 2 r] -> Maybe (Bezier n d r)+fit eps pts++-}+++--------------------------------------------------------------------------------++-- | Merge two Bezier pieces. Assumes they can be merged into a single piece of the same degree+-- (as would e.g. be the case for the result of a 'split' operation).+-- Does not test whether this is the case!+merge :: (KnownNat n, Arity d, Ord r, Fractional r)+ => r -> BezierSpline n d r -> BezierSpline n d r -> BezierSpline n d r+merge treshold b1 b2 = let (p1, q1) = endPoints b1+ (p2, q2) = endPoints b2+ result | q1 /= p2 = error "merge: something is wrong, maybe need to flip one of the curves?"+ | otherwise = snapEndpoints p1 q2 $ growTo treshold p1 b2+ in result++-- need distance function between polyBeziers...+++--------------------------------------------------------------------------------+++-- | Approximate Bezier curve by Polyline with given resolution. That+-- is, every point on the approximation will have distance at most res+-- to the Bezier curve.+approximate :: (KnownNat n, Arity d, Ord r, Fractional r)+ => r -> BezierSpline n d r -> PolyLine d () r+approximate res = PolyLine . fmap ext . approximate' res++-- | implementation of approximate; returns the polyline as an LSeq+approximate' :: (KnownNat n, Arity d, Ord r, Fractional r)+ => r -> BezierSpline n d r -> LSeq 2 (Point d r)+approximate' res = LSeq.promise . LSeq.fromSeq . go+ where+ go b | flat res b = let (p,q) = endPoints b in Seq.fromList [p,q]+ | otherwise = let (b1, b2) = split 0.5 b in go b1 <> Seq.drop 1 (go b2)++-- | Test whether a Bezier curve can be approximated by a single line segment,+-- given the resolution parameter.+flat :: (KnownNat n, Arity d, Ord r, Fractional r) => r -> BezierSpline n d r -> Bool+flat r b = let p = fst $ endPoints b+ q = snd $ endPoints b+ s = ClosedLineSegment (p :+ ()) (q :+ ())+ e t = squaredEuclideanDistTo (evaluate b t) s < r ^ 2+ in qdA p q < r ^ 2 || all e [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]++-- seems this is now covered by approximate+--+--+-- -- | Approximate curve as line segments where no point on the curve is further away+-- -- from the nearest line segment than the given tolerance.+-- lineApproximate :: (Ord r, Fractional r) => r -> BezierSpline 3 2 r -> [Point 2 r]+-- lineApproximate eps bezier+-- | colinear eps bezier =+-- [ bezier^.controlPoints.to LSeq.head+-- , bezier^.controlPoints.to LSeq.last ]+-- | otherwise =+-- let (b1, b2) = split 0.5 bezier+-- in lineApproximate eps b1 ++ tail (lineApproximate eps b2)++-- If both control points are on the same side of the straight line from the start and end+-- points then the curve is guaranteed to be within 3/4 of the distance from the straight line+-- to the furthest control point.+-- Otherwise, if the control points are on either side of the straight line, the curve is+-- guaranteed to be within 4/9 of the maximum distance from the straight line to a control+-- point.+-- Also: 3/4 * sqrt(v) = sqrt (9/16 * v)+-- 4/9 * sqrt(v) = sqrt (16/81 * v)+-- So: 3/4 * sqrt(v) < eps =>+-- sqrt(9/16 * v) < eps =>+-- 9/16*v < eps*eps+-- | Return True if the curve is definitely completely covered by a line of thickness+-- twice the given tolerance. May return false negatives but not false positives.+colinear :: (Ord r, Fractional r) => r -> BezierSpline 3 2 r -> Bool+colinear eps (Bezier3 !a !b !c !d) = sqBound < eps*eps+ where ld = flip squaredEuclideanDistTo (lineThrough a d)+ sameSide = ccw a d b == ccw a d c+ maxDist = max (ld b) (ld c)+ sqBound+ | sameSide = 9/16 * maxDist+ | otherwise = 16/81 * maxDist++--------------------------------------------------------------------------------++-- general d depends on convex hull+-- parameterOf :: (Arity d, Ord r, Fractional r) => BezierSpline n d r -> Point d r -> r+--+-- | Given a point on (or within distance treshold to) a Bezier curve, return the parameter value+-- of some point on the curve within distance treshold from p.+-- For points farther than treshold from the curve, the function will attempt to return the+-- parameter value of an approximate locally closest point to the input point, but no guarantees.+parameterOf :: (KnownNat n, Ord r, RealFrac r) => r -> BezierSpline n 2 r -> Point 2 r -> r+parameterOf treshold b p | closeEnough treshold p $ fst $ endPoints b = 0+ | closeEnough treshold p $ snd $ endPoints b = 1+ | otherwise = parameterInterior treshold b p++-- parameterInterior is slow, look into algebraic solution?++-- general d depends on convex hull+parameterInterior :: (KnownNat n, Ord r, RealFrac r) => r -> BezierSpline n 2 r -> Point 2 r -> r+parameterInterior treshold b p | sqrad (F.toList $ view controlPoints b) < (0.5 * treshold)^2 = 0.5+ | otherwise =+ let (b1, b2) = split 0.5 b+ recurse1 = 0.5 * parameterInterior treshold b1 p+ recurse2 = 0.5 + 0.5 * parameterInterior treshold b2 p+ chb1 = _simplePolygon $ convexHullB b1+ chb2 = _simplePolygon $ convexHullB b2+ in1 = squaredEuclideanDistTo p chb1 < treshold^2+ in2 = squaredEuclideanDistTo p chb2 < treshold^2+ result | in1 && in2 = betterFit b p recurse1 recurse2+ | in2 && not in2 = recurse1+ | not in2 && in2 = recurse2+ | squaredEuclideanDistTo p chb1 < squaredEuclideanDistTo p chb2 = recurse1+ | otherwise = recurse2+ in result++-- | Given a point on (or close to) the extension of a Bezier curve, return the corresponding+-- parameter value, which might also be smaller than 0 or larger than 1.+-- (For points far away from the curve, the function will return the parameter value of+-- an approximate locally closest point to the input point.)+--+-- This implementation is not robust: might return a locally closest point on the curve+-- even though the point lies on another part of the curve. For points on the actual+-- curve, use parameterOf instead.+extendedParameterOf :: (Arity d, KnownNat n, Ord r, Fractional r)+ => r -> BezierSpline n d r -> Point d r -> r+extendedParameterOf treshold b p | p == fst (endPoints b) = 0+ | p == snd (endPoints b) = 1+ | otherwise = binarySearch treshold (qdA p . evaluate b) (-100) 100++----------------------------------------+-- * Stuff to implement parameterOf and extendedParameterOf++betterFit :: (KnownNat n, Arity d, Ord r, Fractional r)+ => BezierSpline n d r -> Point d r -> r -> r -> r+betterFit b p t u =+ let q = evaluate b t+ r = evaluate b u+ in if qdA q p < qdA r p then t else u++--------------------------------------------------------------------------------++-- | Given two Bezier curves, list all intersection points.+-- Not exact, since for degree >= 3 there is no closed form.+-- (In principle, this algorithm works in any dimension+-- but this requires convexHull, area/volume, and intersect.)+intersectB :: (KnownNat n, Ord r, RealFrac r) => r -> BezierSpline n 2 r -> BezierSpline n 2 r -> [Point 2 r]+intersectB treshold a b+ | a == b = [fst $ endPoints b, snd $ endPoints b] -- should really return the whole curve+ | otherwise = let [a1, _a2, _a3, a4] = F.toList $ _controlPoints a+ [b1, _b2, _b3, b4] = F.toList $ _controlPoints b+ in intersectPointsPoints treshold [a1, a4] [b1, b4]+ ++ intersectPointsInterior treshold [a1, a4] b+ ++ intersectPointsInterior treshold [b1, b4] a+ ++ intersectInteriorInterior treshold [a1, a4, b1, b4] a b+++closeEnough :: (Arity d, Ord r, Fractional r) => r -> Point d r -> Point d r -> Bool+closeEnough treshold p q = qdA p q < treshold ^ 2++intersectPointsPoints :: (Ord r, Fractional r) => r -> [Point 2 r] -> [Point 2 r] -> [Point 2 r]+intersectPointsPoints treshold ps = filter (\q -> any (closeEnough treshold q) ps)++intersectPointsInterior :: (KnownNat n, Ord r, RealFrac r) => r -> [Point 2 r] -> BezierSpline n 2 r -> [Point 2 r]+intersectPointsInterior treshold ps b =+ let [b1, _b2, _b3, b4] = F.toList $ _controlPoints b+ nearc p = closeEnough treshold (snap treshold b p) p+ near1 = closeEnough treshold b1+ near4 = closeEnough treshold b4+ in filter (\p -> nearc p && not (near1 p) && not (near4 p)) ps+++intersectInteriorInterior :: (KnownNat n, Ord r, RealFrac r) => r -> [Point 2 r] -> BezierSpline n 2 r -> BezierSpline n 2 r -> [Point 2 r]+intersectInteriorInterior treshold forbidden a b =+ let cha = _simplePolygon $ convexHullB a+ chb = _simplePolygon $ convexHullB b+ (a1, a2) = split 0.5 a+ (b1, b2) = split 0.5 b+ points = F.toList (view controlPoints a)+ ++ F.toList (view controlPoints b)+ approx = average points+ done | not (cha `intersectsP` chb) = True+ | sqrad points < treshold^2 = True+ | otherwise = False+ result | not (cha `intersectsP` chb) = []+ | any (closeEnough treshold approx) forbidden = []+ | otherwise = [approx]+ recurse = intersectInteriorInterior treshold forbidden a1 b1+ ++ intersectInteriorInterior treshold forbidden a1 b2+ ++ intersectInteriorInterior treshold forbidden a2 b1+ ++ intersectInteriorInterior treshold forbidden a2 b2+ in if done then result else recurse++sqrad :: (Ord r, RealFrac r) => [Point 2 r] -> r+sqrad points | length points < 2 = error "sqrad: not enough points"+sqrad points | otherwise =+ let rationalPoints :: [Point 2 Rational] -- smallestEnclosingDisk fails on Floats+ rationalPoints = map (traverse %~ realToFrac) points+ (a : b : cs) = map (:+ ()) rationalPoints+ diskResult = smallestEnclosingDisk' a b cs+ in realToFrac $ view squaredRadius $ view enclosingDisk $ diskResult++average :: (Functor t, Foldable t, Arity d, Fractional r) => t (Point d r) -> Point d r+average ps = origin .+^ foldr1 (^+^) (fmap toVec ps) ^/ realToFrac (length ps)++{-+type instance IntersectionOf (BezierSpline n 2 r) (BezierSpline n 2 r) = [ NoIntersection+ , [Point 2 r]+ , BezierSpline n 2 r+ ]+++instance (KnownNat n, Ord r, Fractional r) => (BezierSpline n 2 r) `IsIntersectableWith` (BezierSpline n 2 r) where+ nonEmptyIntersection = defaultNonEmptyIntersection+ a `intersect` b = a `intersectB` b+-}+++-- function to test whether two convex polygons intersect+-- for speed, first test bounding boxes+-- maybe would be faster to directly compare bounding boxes of points, rather than+-- call convex hull first?+intersectsP :: (Ord r, Fractional r) => SimplePolygon p r -> SimplePolygon p r -> Bool+intersectsP p q | not $ boundingBox p `intersects` boundingBox q = False+ | otherwise = or [a `intersects` b | a <- p & listEdges, b <- q & listEdges]+ || (any (flip insidePolygon p) $ map _core $ F.toList $ polygonVertices q)+ || (any (flip insidePolygon q) $ map _core $ F.toList $ polygonVertices p)+ -- first test bounding box?+++{-++instance (Arity d, Floating r) => IsBoxable (BezierSpline 3 d r) where+ boundingBox b = foldr1 (<>) $ map (\i -> boundingBox (extremal True i b) <> boundingBox (extremal False i b)) [1 .. d]++-- | Find extremal points on curve in the $i$th dimension.+extremal :: Floating r => Bool -> Int -> BezierSpline 3 d r -> Point d r+extremal pos i b =+ let [p1, _, _, p4] = F.toList $ view controlPoints b+ ps = map evaluate $ locallyExtremalParameters i b+ candidates = [p1, p4] ++ ps+ result | pos = maximumBy (unsafeCoord i . snd) candidates+ | not pos = minimumBy (unsafeCoord i . snd) candidates+ in result++-}+++--------------------------------------------------------------------------------++snapEndpoints :: (KnownNat n, Arity d, Ord r, Fractional r)+ => Point d r -> Point d r -> BezierSpline n d r -> BezierSpline n d r+snapEndpoints p q curve =+ let points = F.toList $ _controlPoints curve+ middle = tail . init $ points+ new = [p] ++ middle ++ [q]+ in fromPointSeq $ Seq.fromList new+++-- | Snap a point close to a Bezier curve to the curve.+snap :: (KnownNat n, Ord r, RealFrac r) => r -> BezierSpline n 2 r -> Point 2 r -> Point 2 r+snap treshold b = evaluate b . parameterOf treshold b++--------------------------------------------------------------------------------+-- * Helper functions++-- | Solve equation of the form ax^2 + bx + c = 0.+-- If there are multiple solutions, report in ascending order.+-- Attempt at a somewhat robust implementation.+solveQuadraticEquation :: (Ord r, Enum r, Floating r) => r -> r -> r -> [r]+solveQuadraticEquation 0 0 0 = [0..] -- error "infinite solutions"+solveQuadraticEquation _ 0 0 = [0]+solveQuadraticEquation 0 _ 0 = [0]+solveQuadraticEquation 0 0 _ = []+solveQuadraticEquation a b 0 = sort [0, -b / a]+solveQuadraticEquation a 0 c | (-c / a) < 0 = []+ | (-c / a) == 0 = [0]+ | (-c / a) > 0 = [sqrt (-c / a)]+solveQuadraticEquation 0 b c = [-c / b]+solveQuadraticEquation a b c | almostzero a || almostzero (a / b) || almostzero (a / c) = solveQuadraticEquation 0 b c+solveQuadraticEquation a b c =+ let d = b^2 - 4 * a * c+ result | d == 0 = [-b / (2 * a)]+ | d > 0 = [(-b - sqrt d) / (2 * a), (-b + sqrt d) / (2 * a)]+ | otherwise = []+ in result+ -- trace ("soving equation " ++ show a ++ "x^2 + " ++ show b ++ "x + " ++ show c ++ " = 0") $ result++-- | Test whether a floating point number is close enough to zero, taking rounding errors into account.+almostzero :: (Floating r, Ord r) => r -> Bool+almostzero x = abs x < epsilon++-- | Treshold for rounding errors in almostzero test.+-- TODO: Should be different depending on the type.+epsilon :: Floating r => r+epsilon = 0.0001++++-- | This function tests whether a value lies within bounds of a given interval.+-- If not, graciously continues with value snapped to interval.+-- This should never happen, but apparently it sometimes does?+restrict :: (Ord r) => String -> r -> r -> r -> r+restrict f l r x | l > r = error $ f <> ": restrict [l,r] is not an interval" --error $ f ++ ": restrict: [" ++ show l ++ ", " ++ show r ++ "] is not an interval"+ -- | x < l = trace (f ++ ": restricting " ++ show x ++ " to [" ++ show l ++ ", " ++ show r ++ "]") l+ -- | x > r = trace (f ++ ": restricting " ++ show x ++ " to [" ++ show l ++ ", " ++ show r ++ "]") r+ | otherwise = x+++binarySearch :: (Ord r, Fractional r)+ => r -> (r -> r) -> r -> r -> r+binarySearch treshold f l r+ | abs (f l - f r) < treshold = restrict "binarySearch" l r m+ | derivative f m > 0 = restrict "binarySearch" l r $ binarySearch treshold f l m+ | otherwise = restrict "binarySearch" l r $ binarySearch treshold f m r+ where m = (l + r) / 2++derivative :: Fractional r => (r -> r) -> r -> r+derivative f x = (f (x + delta) - f x) / delta+ where delta = 0.0000001
src/Data/Geometry/Boundary.hs view
@@ -1,7 +1,15 @@+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.Boundary+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Data.Geometry.Boundary where -import Data.Geometry.Properties-import Data.Geometry.Transformation+import Control.Lens (iso,Iso)+import Data.Geometry.Properties+import Data.Geometry.Transformation -------------------------------------------------------------------------------- @@ -12,6 +20,10 @@ type instance NumType (Boundary g) = NumType g type instance Dimension (Boundary g) = Dimension g++-- | Iso for converting between things with a boundary and without its boundary+_Boundary :: Iso g h (Boundary g) (Boundary h)+_Boundary = iso Boundary (\(Boundary b) -> b) -- | Result of a query that asks if something is Inside a g, *on* the boundary
src/Data/Geometry/Box.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE DeriveAnyClass #-} {-# OPTIONS_GHC -fno-warn-orphans #-}@@ -13,57 +11,29 @@ -- Orthogonal \(d\)-dimensiontal boxes (e.g. rectangles) -- ---------------------------------------------------------------------------------module Data.Geometry.Box( module Data.Geometry.Box.Internal- , topSide, leftSide, bottomSide, rightSide- , sides, sides'- ) where+module Data.Geometry.Box+ ( module Data.Geometry.Box.Internal+ , module Data.Geometry.Box.Corners+ , module Data.Geometry.Box.Sides+ , inBox'+ ) where import Control.DeepSeq+import Data.Geometry.Box.Corners import Data.Geometry.Box.Internal-import Data.Geometry.LineSegment+import Data.Geometry.Box.Sides import Data.Geometry.Vector+import Data.Geometry.Point+import Data.Geometry.Boundary -------------------------------------------------------------------------------- deriving instance (NFData p, NFData r, Arity d) => NFData (Box d p r) -topSide :: Num r => Rectangle p r -> LineSegment 2 p r-topSide = (\(l,r,_,_) -> ClosedLineSegment l r) . corners---- | Oriented from *left to right*-bottomSide :: Num r => Rectangle p r -> LineSegment 2 p r-bottomSide = (\(_,_,r,l) -> ClosedLineSegment l r) . corners-----leftSide :: Num r => Rectangle p r -> LineSegment 2 p r-leftSide = (\(t,_,_,b) -> ClosedLineSegment b t) . corners---- | The right side, oriented from *bottom* to top-rightSide :: Num r => Rectangle p r -> LineSegment 2 p r-rightSide = (\(_,t,b,_) -> ClosedLineSegment b t) . corners----- | The sides of the rectangle, in order (Top, Right, Bottom, Left). The sides--- themselves are also oriented in clockwise order. If, you want them in the--- same order as the functions `topSide`, `bottomSide`, `leftSide`, and--- `rightSide`, use `sides'` instead.-sides :: Num r => Rectangle p r -> ( LineSegment 2 p r- , LineSegment 2 p r- , LineSegment 2 p r- , LineSegment 2 p r- )-sides = (\(t,r,b,l) -> (t,flipSegment r,flipSegment b,l)) . sides'----- | The sides of the rectangle. The order of the segments is (Top, Right,--- Bottom, Left). Note that the segments themselves, are oriented as described--- by the functions topSide, bottomSide, leftSide, rightSide (basically: from--- left to right, and from bottom to top). If you want the segments oriented--- along the boundary of the rectangle, use the `sides` function instead.-sides' :: Num r => Rectangle p r -> ( LineSegment 2 p r- , LineSegment 2 p r- , LineSegment 2 p r- , LineSegment 2 p r- )-sides' r = (topSide r, rightSide r, bottomSide r, leftSide r)+-- | Compute whether the point lies inside, on the boundary of, or+-- outside the box.+inBox' :: (Arity d, Ord r) => Point d r -> Box d p r -> PointLocationResult+q `inBox'` b | q `insideBox` b = Inside+ | q `inBox` b = OnBoundary+ | otherwise = Outside
+ src/Data/Geometry/Box/Corners.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE TemplateHaskell #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.Box.Corners+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Data.Geometry.Box.Corners( Corners(Corners), northWest, northEast, southEast, southWest+ , corners, cornersInDirection+ ) where++import Control.Lens (makeLenses,Ixed(..),Index, IxValue,(%~),(&),(^?!))+import Data.Ext+import Data.Functor.Apply+import Data.Geometry.Box.Internal+import Data.Geometry.Directions+import Data.Geometry.Point+import Data.Semigroup.Foldable.Class+import Data.Semigroup.Traversable.Class+import Data.Util+import GHC.Generics (Generic)++--------------------------------------------------------------------------------++-- | A data type rperesenting the corners of a box. the order of the+-- Corners is 'northWest, northEast, southEast, southWest', i.e. in+-- clockwise order starting from the topleft.+data Corners a = Corners { _northWest :: !a+ , _northEast :: !a+ , _southEast :: !a+ , _southWest :: !a+ } deriving (Show,Eq,Ord,Generic,Functor,Foldable,Traversable)+makeLenses ''Corners+++type instance Index (Corners a) = InterCardinalDirection+type instance IxValue (Corners a) = a++instance Ixed (Corners a) where+ ix = \case+ NorthWest -> northWest+ NorthEast -> northEast+ SouthEast -> southEast+ SouthWest -> southWest++instance Foldable1 Corners+instance Traversable1 Corners where+ traverse1 f (Corners a b c d) = Corners <$> f a <.> f b <.> f c <.> f d++instance Applicative Corners where+ pure x = Corners x x x x+ (Corners f g h i) <*> (Corners a b c d) = Corners (f a) (g b) (h c) (i d)++instance Semigroup a => Semigroup (Corners a) where+ s <> s' = (<>) <$> s <*> s'+instance Monoid a => Monoid (Corners a) where+ mempty = pure mempty+++--------------------------------------------------------------------------------++{- HLINT ignore corners -}+-- | Get the corners of a rectangle, the order is:+-- (TopLeft, TopRight, BottomRight, BottomLeft).+-- The extra values in the Top points are taken from the Top point,+-- the extra values in the Bottom points are taken from the Bottom point+corners :: Num r => Rectangle p r -> Corners (Point 2 r :+ p)+corners r = let w = width r+ p = (_maxP r)&core %~ _cwMax+ q = (_minP r)&core %~ _cwMin+ in Corners (p&core.xCoord %~ subtract w) p+ (q&core.xCoord %~ (+ w)) q+++--------------------------------------------------------------------------------++-- | Gets the corners in a particular direction+cornersInDirection :: CardinalDirection -> Corners p -> Two p+cornersInDirection d c = (\icd -> c^?!ix icd) <$> interCardinalsOf d
src/Data/Geometry/Box/Internal.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE AllowAmbiguousTypes #-} -------------------------------------------------------------------------------- -- | -- Module : Data.Geometry.Box.Internal@@ -15,22 +16,24 @@ import Control.DeepSeq import Control.Lens+import Data.Bifoldable import Data.Bifunctor+import Data.Bitraversable import Data.Ext+import qualified Data.Foldable as F import Data.Geometry.Point import Data.Geometry.Properties-import Data.Geometry.Transformation+import Data.Geometry.Transformation.Internal import Data.Geometry.Vector import qualified Data.Geometry.Vector as V import qualified Data.List.NonEmpty as NE import qualified Data.Range as R import qualified Data.Semigroup.Foldable as F-import qualified Data.Foldable as F import qualified Data.Vector.Fixed as FV import Data.Vinyl.CoRec (asA) import GHC.Generics (Generic) import GHC.TypeLits-import Test.QuickCheck(Arbitrary(..))+import Test.QuickCheck (Arbitrary(..)) -------------------------------------------------------------------------------- @@ -60,6 +63,10 @@ } deriving Generic makeLenses ''Box ++++ -- | Given the point with the lowest coordinates and the point with highest -- coordinates, create a box. box :: Point d r :+ p -> Point d r :+ p -> Box d p r@@ -83,6 +90,7 @@ in fromExtent $ FV.zipWith f (toVec c) ((/2) <$> ws) +{- HLINT ignore centerPoint -} -- | Center of the box centerPoint :: (Arity d, Fractional r) => Box d p r -> Point d r centerPoint b = Point $ w V.^/ 2@@ -99,7 +107,9 @@ type instance IntersectionOf (Box d p r) (Box d q r) = '[ NoIntersection, Box d () r] -instance (Ord r, Arity d) => (Box d p r) `IsIntersectableWith` (Box d q r) where+instance (Ord r, Arity d) => Box d p r `HasIntersectionWith` Box d q r++instance (Ord r, Arity d) => Box d p r `IsIntersectableWith` Box d q r where nonEmptyIntersection = defaultNonEmptyIntersection bx `intersect` bx' = f . sequence $ FV.zipWith intersect' (extent bx) (extent bx')@@ -108,12 +118,14 @@ r `intersect'` s = asA @(R.Range r) $ r `intersect` s instance Arity d => Bifunctor (Box d) where- bimap :: forall p q r s. (p -> q) -> (r -> s) -> Box d p r -> Box d q s- bimap f g (Box mi ma) = Box (bimap g' f mi) (bimap g' f ma)+ bimap = bimapDefault+instance Arity d => Bifoldable (Box d) where+ bifoldMap = bifoldMapDefault+instance Arity d => Bitraversable (Box d) where+ bitraverse f g (Box mi ma) = Box <$> bitraverse (tr g) f mi <*> bitraverse (tr g) f ma where- g' :: Functor g => g (Point d r) -> g (Point d s)- g' = fmap (fmap g)-+ tr :: (Traversable t, Applicative f) => (r -> f s) -> t (Point d r) -> f (t (Point d s))+ tr g' = traverse $ traverse g' -- -- In principle this should also just work for Boxes in higher dimensions. It is just -- -- that we need a better way to compute their corners@@ -136,7 +148,10 @@ type instance IntersectionOf (Point d r) (Box d p r) = '[ NoIntersection, Point d r] -instance (Arity d, Ord r) => (Point d r) `IsIntersectableWith` (Box d p r) where+instance (Arity d, Ord r) => Point d r `HasIntersectionWith` Box d p r where+ intersects = inBox++instance (Arity d, Ord r) => Point d r `IsIntersectableWith` Box d p r where nonEmptyIntersection = defaultNonEmptyIntersection p `intersect` b | not $ p `inBox` b = coRec NoIntersection@@ -179,12 +194,24 @@ inBox :: (Arity d, Ord r) => Point d r -> Box d p r -> Bool p `inBox` b = FV.and . FV.zipWith R.inRange (toVec p) . extent $ b ++-- | Check if a point lies strictly inside a box (i.e. not on its boundary)+--+-- >>> origin `inBox` (boundingBoxList' [Point3 1 2 3, Point3 10 20 30] :: Box 3 () Int)+-- False+-- >>> origin `inBox` (boundingBoxList' [Point3 (-1) (-2) (-3), Point3 10 20 30] :: Box 3 () Int)+-- True+insideBox :: (Arity d, Ord r) => Point d r -> Box d p r -> Bool+p `insideBox` b = FV.and . FV.zipWith R.inRange (toVec p) . fmap toOpenRange . extent $ b+ where+ toOpenRange (R.Range' l r) = R.OpenRange l r+ -- | Get a vector with the extent of the box in each dimension. Note that the -- resulting vector is 0 indexed whereas one would normally count dimensions -- starting at zero. -- -- >>> extent (boundingBoxList' [Point3 1 2 3, Point3 10 20 30] :: Box 3 () Int)--- Vector3 [Range (Closed 1) (Closed 10),Range (Closed 2) (Closed 20),Range (Closed 3) (Closed 30)]+-- Vector3 (Range (Closed 1) (Closed 10)) (Range (Closed 2) (Closed 20)) (Range (Closed 3) (Closed 30)) extent :: Arity d => Box d p r -> Vector d (R.Range r) extent (Box (CWMin a :+ _) (CWMax b :+ _)) = FV.zipWith R.ClosedRange (toVec a) (toVec b)@@ -193,19 +220,19 @@ -- whereas one would normally count dimensions starting at zero. -- -- >>> size (boundingBoxList' [origin, Point3 1 2 3] :: Box 3 () Int)--- Vector3 [1,2,3]+-- Vector3 1 2 3 size :: (Arity d, Num r) => Box d p r -> Vector d r size = fmap R.width . extent -- | Given a dimension, get the width of the box in that dimension. Dimensions are 1 indexed. ----- >>> widthIn (C :: C 1) (boundingBoxList' [origin, Point3 1 2 3] :: Box 3 () Int)+-- >>> widthIn @1 (boundingBoxList' [origin, Point3 1 2 3] :: Box 3 () Int) -- 1--- >>> widthIn (C :: C 3) (boundingBoxList' [origin, Point3 1 2 3] :: Box 3 () Int)+-- >>> widthIn @3 (boundingBoxList' [origin, Point3 1 2 3] :: Box 3 () Int) -- 3-widthIn :: forall proxy p i d r. (Arity d, Arity (i - 1), Num r, ((i-1)+1) <= d)- => proxy i -> Box d p r -> r-widthIn _ = view (V.element (C :: C (i - 1))) . size+widthIn :: forall i p d r. (Arity d, Arity (i - 1), Num r, ((i-1)+1) <= d)+ => Box d p r -> r+widthIn = view (V.element @(i-1)) . size -- | Same as 'widthIn' but with a runtime int instead of a static dimension.@@ -225,38 +252,24 @@ type Rectangle = Box 2 +-- | -- >>> width (boundingBoxList' [origin, Point2 1 2] :: Rectangle () Int) -- 1 -- >>> width (boundingBoxList' [origin] :: Rectangle () Int) -- 0 width :: Num r => Rectangle p r -> r-width = widthIn (C :: C 1)+width = widthIn @1 +-- | -- >>> height (boundingBoxList' [origin, Point2 1 2] :: Rectangle () Int) -- 2 -- >>> height (boundingBoxList' [origin] :: Rectangle () Int) -- 0 height :: Num r => Rectangle p r -> r-height = widthIn (C :: C 2)+height = widthIn @2 --- | Get the corners of a rectangle, the order is:--- (TopLeft, TopRight, BottomRight, BottomLeft).--- The extra values in the Top points are taken from the Top point,--- the extra values in the Bottom points are taken from the Bottom point-corners :: Num r => Rectangle p r -> ( Point 2 r :+ p- , Point 2 r :+ p- , Point 2 r :+ p- , Point 2 r :+ p- )-corners r = let w = width r- p = (_maxP r)&core %~ _cwMax- q = (_minP r)&core %~ _cwMin- in ( p&core.xCoord %~ (subtract w)- , p- , q&core.xCoord %~ (+ w)- , q- )+-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- * Constructing bounding boxes@@ -264,7 +277,7 @@ class IsBoxable g where boundingBox :: Ord (NumType g) => g -> Box (Dimension g) () (NumType g) -+-- | Create a bounding box that encapsulates a list of objects. boundingBoxList :: (IsBoxable g, F.Foldable1 c, Ord (NumType g), Arity (Dimension g)) => c g -> Box (Dimension g) () (NumType g) boundingBoxList = F.foldMap1 boundingBox@@ -282,3 +295,29 @@ instance IsBoxable (Box d p r) where boundingBox (Box m m') = Box (m&extra .~ ()) (m'&extra .~ ())++instance IsBoxable c => IsBoxable (c :+ e) where+ boundingBox = boundingBox . view core++--------------------------------------------------------------------------------+-- * Distances++instance (Num r, Ord r) => HasSquaredEuclideanDistance (Box 2 p r) where+ pointClosestToWithDistance q bx =+ case ((q^.xCoord) `R.inRange` hor, (q^.yCoord) `R.inRange` ver) of+ (False,False) -> if q^.yCoord < b+ then closest (Point2 l b) (Point2 r b)+ else closest (Point2 l t) (Point2 r t)+ (True, False) -> if q^.yCoord < b+ then (q&yCoord .~ b, sq $ q^.yCoord - b)+ else (q&yCoord .~ t, sq $ q^.yCoord - t)+ (False, True) -> if q^.xCoord < l+ then (q&yCoord .~ l, sq $ q^.xCoord - l)+ else (q&yCoord .~ r, sq $ q^.xCoord - r)+ (True, True) -> (q, 0) -- point lies inside the box+ where+ Vector2 hor@(R.Range' l r) ver@(R.Range' b t) = extent bx+ sq x = x*x+ closest p1 p2 = let d1 = squaredEuclideanDist q p1+ d2 = squaredEuclideanDist q p2+ in if d1 < d2 then (p1, d1) else (p2, d2)
+ src/Data/Geometry/Box/Sides.hs view
@@ -0,0 +1,98 @@+{-# LANGUAGE TemplateHaskell #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.Box.Sides+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Data.Geometry.Box.Sides( Sides(Sides), north, east, south, west+ , topSide, bottomSide, leftSide, rightSide+ , sides, sides'++ , sideDirections+ ) where++import Data.Geometry.Directions+import Data.Geometry.Box.Internal+import Data.Geometry.Box.Corners+import Data.Geometry.LineSegment.Internal+import Data.Functor.Apply+import Data.Semigroup.Foldable.Class+import Data.Semigroup.Traversable.Class+import GHC.Generics (Generic)+import Control.Lens(makeLenses, Ixed(..), Index, IxValue)++--------------------------------------------------------------------------------++-- | The four sides of a rectangle+data Sides a = Sides { _north :: !a+ , _east :: !a+ , _south :: !a+ , _west :: !a+ } deriving (Show,Read,Eq,Generic,Ord,Foldable,Functor,Traversable)+makeLenses ''Sides++instance Applicative Sides where+ pure x = Sides x x x x+ (Sides f g h i) <*> (Sides a b c d) = Sides (f a) (g b) (h c) (i d)++instance Foldable1 Sides+instance Traversable1 Sides where+ traverse1 f (Sides a b c d) = Sides <$> f a <.> f b <.> f c <.> f d++instance Semigroup a => Semigroup (Sides a) where+ s <> s' = (<>) <$> s <*> s'+instance Monoid a => Monoid (Sides a) where+ mempty = pure mempty+++type instance Index (Sides a) = CardinalDirection+type instance IxValue (Sides a) = a++instance Ixed (Sides a) where+ ix = \case+ North -> north+ East -> east+ South -> south+ West -> west++-- | Constructs a Sides value that indicates the appropriate+-- direction.+sideDirections :: Sides CardinalDirection+sideDirections = Sides North East South West++--------------------------------------------------------------------------------++topSide :: Num r => Rectangle p r -> LineSegment 2 p r+topSide = (\(Corners l r _ _) -> ClosedLineSegment l r) . corners++-- | Oriented from *left to right*+bottomSide :: Num r => Rectangle p r -> LineSegment 2 p r+bottomSide = (\(Corners _ _ r l) -> ClosedLineSegment l r) . corners++--+leftSide :: Num r => Rectangle p r -> LineSegment 2 p r+leftSide = (\(Corners t _ _ b) -> ClosedLineSegment b t) . corners++-- | The right side, oriented from *bottom* to top+rightSide :: Num r => Rectangle p r -> LineSegment 2 p r+rightSide = (\(Corners _ t b _) -> ClosedLineSegment b t) . corners+++-- | The sides of the rectangle, in order (Top, Right, Bottom, Left). The sides+-- themselves are also oriented in clockwise order. If, you want them in the+-- same order as the functions `topSide`, `bottomSide`, `leftSide`, and+-- `rightSide`, use `sides'` instead.+sides :: Num r => Rectangle p r -> Sides (LineSegment 2 p r)+sides r = let Corners nw ne se sw = corners r+ in Sides (ClosedLineSegment nw ne) (ClosedLineSegment ne se)+ (ClosedLineSegment se sw) (ClosedLineSegment sw nw)++-- | The sides of the rectangle. The order of the segments is (Top, Right,+-- Bottom, Left). Note that the segments themselves, are oriented as described+-- by the functions topSide, bottomSide, leftSide, rightSide (basically: from+-- left to right, and from bottom to top). If you want the segments oriented+-- along the boundary of the rectangle, use the `sides` function instead.+sides' :: Num r => Rectangle p r -> Sides (LineSegment 2 p r)+sides' r = Sides (topSide r) (rightSide r) (bottomSide r) (leftSide r)
+ src/Data/Geometry/Directions.hs view
@@ -0,0 +1,56 @@+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.Directions+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--+--------------------------------------------------------------------------------+module Data.Geometry.Directions( CardinalDirection(..)+ -- , _North, _East, _South, _West+ , oppositeDirection++ , InterCardinalDirection(..)+ -- , _NorthWest, _NorthEast, _SouthEast, _SouthWest++ , interCardinalsOf+ ) where++import Data.Util+import GHC.Generics (Generic)++--------------------------------------------------------------------------------++-- | The four cardinal directions.+data CardinalDirection = North | East | South | West deriving (Show,Read,Eq,Ord,Enum,Bounded)+-- makePrisms ''CardinalDirection++--------------------------------------------------------------------------------+-- * Functions on Cardinal Directions++-- | Computes the direction opposite to the given one.+oppositeDirection :: CardinalDirection -> CardinalDirection+oppositeDirection = \case+ North -> South+ East -> West+ South -> North+ West -> East++--------------------------------------------------------------------------------++-- | Intercardinal directions+data InterCardinalDirection = NorthWest | NorthEast | SouthEast | SouthWest+ deriving (Show,Read,Eq,Ord,Enum,Generic)+-- makePrisms ''InterCardinalDirection++--------------------------------------------------------------------------------+-- * Functions on InterCardinal Directions++-- | Get the two intercardinal directions, in increasing order,+-- corresponding to the cardinal direction.+interCardinalsOf :: CardinalDirection -> Two InterCardinalDirection+interCardinalsOf = \case+ North -> Two NorthWest NorthEast+ East -> Two NorthEast SouthEast+ South -> Two SouthEast SouthWest+ West -> Two SouthWest NorthWest
src/Data/Geometry/Duality.hs view
@@ -1,3 +1,10 @@+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.Duality+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Data.Geometry.Duality where import Data.Geometry.Line@@ -12,9 +19,9 @@ -- | Returns Nothing if the input line is vertical -- Maps a line l: y = ax + b to a point (a,-b)-dualPoint :: (Fractional r, Eq r) => Line 2 r -> Maybe (Point 2 r)+dualPoint :: (Fractional r, Ord r) => Line 2 r -> Maybe (Point 2 r) dualPoint l = (\(a,b) -> Point2 a (-b)) <$> toLinearFunction l -- | Pre: the input line is not vertical-dualPoint' :: (Fractional r, Eq r) => Line 2 r -> Point 2 r+dualPoint' :: (Fractional r, Ord r) => Line 2 r -> Point 2 r dualPoint' = fromJust . dualPoint
+ src/Data/Geometry/Ellipse.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE TemplateHaskell #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.Ellipse+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Data.Geometry.Ellipse(+ Ellipse(Ellipse)+ , affineTransformation+ , ellipseMatrix+ , unitEllipse+ , circleToEllipse, ellipseToCircle, _EllipseCircle+ ) where++import Control.Lens+import Data.Ext+import Data.Geometry.Ball+import Data.Geometry.Matrix+import Data.Geometry.Transformation+import Data.Geometry.Point+import Data.Geometry.Properties+import Data.Geometry.Vector++--------------------------------------------------------------------------------++-- | A type representing planar ellipses+newtype Ellipse r = Ellipse { _affineTransformation :: Transformation 2 r }+ deriving (Show,Eq,Functor,Foldable,Traversable)+makeLenses ''Ellipse++type instance Dimension (Ellipse r) = 2+type instance NumType (Ellipse r) = r++instance Num r => IsTransformable (Ellipse r) where+ transformBy t (Ellipse t') = Ellipse $ t |.| t'+++ellipseMatrix :: Iso (Ellipse r) (Ellipse s) (Matrix 3 3 r) (Matrix 3 3 s)+ellipseMatrix = affineTransformation.transformationMatrix++-- | Ellipse representing the unit circle+unitEllipse :: Num r => Ellipse r+unitEllipse = Ellipse $ Transformation identityMatrix++--------------------------------------------------------------------------------+-- | Converting between ellipses and circles++_EllipseCircle :: (Floating r, Eq r) => Prism' (Ellipse r) (Circle () r)+_EllipseCircle = prism' circleToEllipse ellipseToCircle++ellipseToCircle :: (Num r, Eq r) => Ellipse r -> Maybe (Circle () r)+ellipseToCircle e = case e^.ellipseMatrix of+ Matrix (Vector3 (Vector3 sx 0 x)+ (Vector3 0 sy y)+ (Vector3 0 0 1)+ )+ | sx == sy -> Just $ Circle (ext $ Point2 x y) (sx*sx)+ _ -> Nothing++circleToEllipse :: Floating r => Circle p r -> Ellipse r+circleToEllipse (Circle (Point v :+ _) rr) = Ellipse $ translation v |.| uniformScaling (sqrt rr)
src/Data/Geometry/HalfLine.hs view
@@ -1,13 +1,25 @@-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE DeriveAnyClass #-}-module Data.Geometry.HalfLine where-+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.HalfLine+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Data.Geometry.HalfLine( HalfLine(HalfLine)+ , startPoint, halfLineDirection+ , toHalfLine+ , halfLineToSubLine, fromSubLine+ ) where import Control.DeepSeq import Control.Lens import Data.Ext import qualified Data.Foldable as F+import Data.Geometry.Boundary+import Data.Geometry.Box import Data.Geometry.Interval import Data.Geometry.Line import Data.Geometry.LineSegment@@ -18,6 +30,9 @@ import Data.Geometry.Vector import qualified Data.Traversable as T import Data.UnBounded+import qualified Data.Vector.Fixed as FV+import Data.Vinyl+import Data.Vinyl.CoRec import GHC.Generics (Generic) import GHC.TypeLits @@ -31,7 +46,6 @@ makeLenses ''HalfLine deriving instance (Show r, Arity d) => Show (HalfLine d r)-deriving instance (Eq r, Arity d) => Eq (HalfLine d r) deriving instance (NFData r, Arity d) => NFData (HalfLine d r) deriving instance Arity d => Functor (HalfLine d)@@ -41,6 +55,19 @@ type instance Dimension (HalfLine d r) = d type instance NumType (HalfLine d r) = r ++instance {-# OVERLAPPING #-} (Eq r, Fractional r) => Eq (HalfLine 2 r) where+ (HalfLine p u) == (HalfLine q v) =+ p == q && -- Same starting point.+ isCoLinear p (Point u) (Point v) && -- Directions are on the same line.+ sameSigns -- Directions point in the same quadrant.+ where+ sameSigns = F.and $ FV.zipWith (\a b -> signum a==signum b) u v++instance (Eq r, Fractional r, Arity d) => Eq (HalfLine d r) where+ (HalfLine p u) == (HalfLine q v) = let lam = scalarMultiple u v+ in p == q && (signum <$> lam) == Just 1+ instance HasStart (HalfLine d r) where type StartCore (HalfLine d r) = Point d r type StartExtra (HalfLine d r) = ()@@ -73,9 +100,9 @@ (MinInfinity, Val x) -> Just $ HalfLine (pointAt x l) ((-1) *^ l^.direction) _ -> Nothing -type instance IntersectionOf (HalfLine 2 r) (Line 2 r) = [ NoIntersection- , Point 2 r- , HalfLine 2 r+type instance IntersectionOf (HalfLine d r) (Line d r) = [ NoIntersection+ , Point d r+ , HalfLine d r ] type instance IntersectionOf (HalfLine 2 r) (HalfLine 2 r) = [ NoIntersection@@ -84,88 +111,129 @@ , HalfLine 2 r ] -type instance IntersectionOf (HalfLine 2 r) (LineSegment 2 p r) = [ NoIntersection+type instance IntersectionOf (LineSegment 2 p r) (HalfLine 2 r) = [ NoIntersection , Point 2 r , LineSegment 2 () r ] +type instance IntersectionOf (Point d r) (HalfLine d r) = [ NoIntersection+ , Point d r+ ] --- instance (Ord r, Fractional r) => (HalfLine 2 r) `IsIntersectableWith` (Line 2 r) where- -- hl `intersect` l = match (halfLineToSubLine hl, l)+instance (Ord r, Fractional r) => HalfLine 2 r `HasIntersectionWith` Line 2 r +instance (Ord r, Fractional r) => HalfLine 2 r `IsIntersectableWith` Line 2 r where+ nonEmptyIntersection = defaultNonEmptyIntersection+ hl `intersect` l = match (supportingLine hl `intersect` l) $+ H (\NoIntersection -> coRec NoIntersection)+ :& H (\p -> if onHalfLine p hl then coRec p else coRec NoIntersection)+ :& H (\_l' -> coRec hl)+ :& RNil --- instance (Ord r, Fractional r) => (HalfLine 2 r) `IsIntersectableWith` (Line 2 r) where--- data Intersection (HalfLine 2 r) (Line 2 r) = NoHalfLineLineIntersection--- | HalfLineLineIntersection !(Point 2 r)--- | HalfLineLineOverlap !(HalfLine 2 r)--- deriving (Show,Eq) --- nonEmptyIntersection NoHalfLineLineIntersection = False--- nonEmptyIntersection _ = True+instance (Ord r, Fractional r) => HalfLine 2 r `HasIntersectionWith` HalfLine 2 r --- hl `intersect` l = case supportingLine hl `intersect` l of--- SameLine _ -> HalfLineLineOverlap hl--- LineLineIntersection p -> if p `onHalfLine` hl then HalfLineLineIntersection p--- else NoHalfLineLineIntersection--- ParallelLines -> NoHalfLineLineIntersection+instance (Ord r, Fractional r) => HalfLine 2 r `IsIntersectableWith` HalfLine 2 r where+ nonEmptyIntersection = defaultNonEmptyIntersection+ la@(HalfLine a va) `intersect` lb@(HalfLine b vb) =+ match (supportingLine la `intersect` supportingLine lb) $+ H (\NoIntersection -> coRec NoIntersection)+ :& H (\p -> if onHalfLine p la && onHalfLine p lb+ then coRec p else coRec NoIntersection)+ :& H (\_line -> case ( a `onHalfLine ` lb+ , b `onHalfLine ` la+ , va `sameDirection` vb+ ) of+ (False,False,_) -> coRec NoIntersection+ (True,True,True) -> coRec la -- exact same halfline!+ (True,True,False) -> coRec $ ClosedLineSegment (ext a) (ext b)+ (True,_,True) -> coRec la+ (_,True,True) -> coRec lb+ (_,_,False) -> error "HalfLine x Halfline intersection: impossible"+ -- it is impossible for a to be on+ -- lb, while b does not lie on la, while having different+ -- orientations + )+ :& RNil --- instance (Ord r, Fractional r) => (HalfLine 2 r) `IsIntersectableWith` (HalfLine 2 r) where--- data Intersection (HalfLine 2 r) (HalfLine 2 r) = NoHalfLineHalfLineIntersection--- | HLHLIntersectInPoint !(Point 2 r)--- | HLHLIntersectInSegment !(LineSegment 2 () r)--- | HLHLIntersectInHalfLine !(HalfLine 2 r)--- deriving (Show,Eq)+instance (Ord r, Fractional r) => LineSegment 2 () r `HasIntersectionWith` HalfLine 2 r --- nonEmptyIntersection NoHalfLineHalfLineIntersection = False--- nonEmptyIntersection _ = True+instance (Ord r, Fractional r) => LineSegment 2 () r `IsIntersectableWith` HalfLine 2 r where+ nonEmptyIntersection = defaultNonEmptyIntersection --- hl' `intersect` hl = case supportingLine hl' `intersect` supportingLine hl of--- ParallelLines -> NoHalfLineHalfLineIntersection--- LineLineIntersection p -> if p `onHalfLine` hl' && p `onHalfLine` hl then HLHLIntersectInPoint p--- else NoHalfLineHalfLineIntersection--- SameLine _ -> let p = _startPoint hl'--- q = _startPoint hl--- seg = LineSegment (p :+ ()) (q :+ ())--- in case (p `onHalfLine` hl, q `onHalfLine` hl') of--- (False,False) -> NoHalfLineHalfLineIntersection--- (False,True) -> HLHLIntersectInHalfLine hl--- (True, False) -> HLHLIntersectInHalfLine hl'--- (True, True) -> if hl == hl' then HLHLIntersectInHalfLine hl--- else HLHLIntersectInSegment seg+ seg@(LineSegment s t) `intersect` hl@(HalfLine o _) =+ match (supportingLine seg `intersect` supportingLine hl) $+ H (\NoIntersection -> coRec NoIntersection)+ :& H (\p -> if onHalfLine p hl && p `intersects` seg then coRec p+ else coRec NoIntersection+ )+ :& H (\_line -> case (o `intersects` seg, onHalfLine (t^.unEndPoint.core) hl) of+ (False,False) -> coRec NoIntersection+ (False,True) -> coRec seg+ (True,True) -> coRec $ LineSegment (Closed $ ext o) t+ (True,False) -> coRec $ LineSegment s (Closed $ ext o)+ )+ :& RNil +instance (Ord r, Fractional r, Arity d) => Point d r `HasIntersectionWith` HalfLine d r where+ intersects = onHalfLine --- instance (Ord r, Fractional r) => (LineSegment 2 p r) `IsIntersectableWith` (HalfLine 2 r) where--- data Intersection (LineSegment 2 p r) (HalfLine 2 r) = NoSegmentHalfLineIntersection--- | SegmentHalfLineIntersection !(Point 2 r)--- | SegmentOnHalfLine !(LineSegment 2 () r)+instance (Ord r, Fractional r, Arity d) => Point d r `IsIntersectableWith` HalfLine d r where+ nonEmptyIntersection = defaultNonEmptyIntersection+ p `intersect` hl | p `intersects` hl = coRec p+ | otherwise = coRec NoIntersection --- nonEmptyIntersection NoSegmentHalfLineIntersection = False--- nonEmptyIntersection _ = True --- s `intersect` hl = case supportingLine s `intersect` supportingLine hl of--- ParallelLines -> NoSegmentHalfLineIntersection--- LineLineIntersection p -> if p `onSegment` s && p `onHalfLine` hl then SegmentHalfLineIntersection p--- else NoSegmentHalfLineIntersection--- SameLine _ -> let p = s ^.start.core--- q = s ^.end.core--- r = hl ^.start.core--- seg a b = LineSegment (a :+ ()) (b :+ ())--- in case (p `onHalfLine` hl, q `onHalfLine` hl) of--- (False, False) -> NoSegmentHalfLineIntersection--- (False, True) -> SegmentOnHalfLine $ seg r q--- (True, False) -> SegmentOnHalfLine $ seg p r--- (True, True) -> SegmentOnHalfLine $ seg p q +type instance IntersectionOf (HalfLine 2 r) (Boundary (Rectangle p r)) =+ [ NoIntersection, Point 2 r, (Point 2 r, Point 2 r) , LineSegment 2 () r] +type instance IntersectionOf (HalfLine 2 r) (Rectangle p r) = [ NoIntersection+ , Point 2 r+ , LineSegment 2 () r+ ]+instance (Ord r, Fractional r)+ => HalfLine 2 r `HasIntersectionWith` Boundary (Rectangle p r) +instance (Ord r, Fractional r)+ => HalfLine 2 r `IsIntersectableWith` Boundary (Rectangle p r) where+ nonEmptyIntersection = defaultNonEmptyIntersection + hl@(HalfLine o v) `intersect` br = match (Line o v `intersect` br) $+ H coRec -- NoIntersection+ :& H (\p -> if p `intersects` hl then coRec p else coRec NoIntersection)+ :& H (\(p,q) -> case (p `intersects` hl, q `intersects` hl) of+ (False,False) -> coRec NoIntersection+ (False,True) -> coRec q+ (True,False) -> coRec p+ (True,True) -> coRec (p,q))+ :& H (\s@(LineSegment' p q) -> case ((p^.core) `intersects` hl, (q^.core) `intersects` hl) of+ (False,False) -> coRec NoIntersection+ (False,True) -> coRec $ ClosedLineSegment (ext o) q+ (True,False) -> coRec $ ClosedLineSegment (ext o) p+ (True,True) -> coRec s)+ :& RNil+instance (Ord r, Fractional r)+ => HalfLine 2 r `HasIntersectionWith` Rectangle p r++instance (Ord r, Fractional r)+ => HalfLine 2 r `IsIntersectableWith` Rectangle p r where+ nonEmptyIntersection = defaultNonEmptyIntersection++ hl@(HalfLine o _) `intersect` rect = match (hl `intersect` Boundary rect) $+ H coRec -- NoIntersection+ :& H (\p -> if o `insideBox` rect then coRec (ClosedLineSegment (ext o) (ext p))+ else coRec p -- p is on the boundary+ )+ :& H (\(p,q) -> coRec $ ClosedLineSegment (ext p) (ext q))+ :& H coRec -- LineSegment+ :& RNil+ -- | Test if a point lies on a half-line onHalfLine :: (Ord r, Fractional r, Arity d) => Point d r -> HalfLine d r -> Bool p `onHalfLine` (HalfLine q v) = maybe False (>= 0) $ scalarMultiple (p .-. q) v--
src/Data/Geometry/HalfSpace.hs view
@@ -38,13 +38,14 @@ -------------------------------------------------------------------------------- --- | A Halfspace in \(d\) dimensions.+-- | A Halfspace in \(d\) dimensions. Note that the intended side of+-- the halfspace is already indicated by the normal vector of the+-- bounding plane. newtype HalfSpace d r = HalfSpace { _boundingPlane :: HyperPlane d r } deriving Generic makeLenses ''HalfSpace deriving instance (Arity d, Show r) => Show (HalfSpace d r)-deriving instance (Arity d, Eq r) => Eq (HalfSpace d r) -- deriving instance (NFData r, Arity d) => NFData (HalfSpace d r) deriving instance Arity d => Functor (HalfSpace d) deriving instance Arity d => Foldable (HalfSpace d)@@ -55,23 +56,29 @@ deriving instance (Arity d, Arity (d + 1), Fractional r) => IsTransformable (HalfSpace d r) +instance (Arity d, Eq r, Fractional r) => Eq (HalfSpace d r) where+ (HalfSpace h) == (HalfSpace h') = let u = h^.normalVec+ v = h'^.normalVec+ d = quadrance (u ^+^ v) - quadrance u+ in h == h' && signum d == 1+ -------------------------------------------------------------------------------- type HalfPlane = HalfSpace 2 -+{- HLINT ignore leftOf -} -- | Get the halfplane left of a line (i.e. "above") a line -- -- >>> leftOf $ horizontalLine 4--- HalfSpace {_boundingPlane = HyperPlane {_inPlane = Point2 [0,4], _normalVec = Vector2 [0,1]}}+-- HalfSpace {_boundingPlane = HyperPlane {_inPlane = Point2 0 4, _normalVec = Vector2 0 1}} leftOf :: Num r => Line 2 r -> HalfPlane r leftOf l = (rightOf l)&boundingPlane.normalVec %~ ((-1) *^) -- | Get the halfplane right of a line (i.e. "below") a line -- -- >>> rightOf $ horizontalLine 4--- HalfSpace {_boundingPlane = HyperPlane {_inPlane = Point2 [0,4], _normalVec = Vector2 [0,-1]}}+-- HalfSpace {_boundingPlane = HyperPlane {_inPlane = Point2 0 4, _normalVec = Vector2 0 (-1)}} rightOf :: Num r => Line 2 r -> HalfPlane r rightOf l = HalfSpace $ l^.re _asLine @@ -91,31 +98,31 @@ type instance IntersectionOf (Point d r) (HalfSpace d r) = [NoIntersection, Point d r] -instance (Num r, Ord r, Arity d) => Point d r `IsIntersectableWith` HalfSpace d r where- nonEmptyIntersection = defaultNonEmptyIntersection-+instance (Num r, Ord r, Arity d) => Point d r `HasIntersectionWith` HalfSpace d r where q `intersects` h = q `inHalfSpace` h /= Outside +instance (Num r, Ord r, Arity d) => Point d r `IsIntersectableWith` HalfSpace d r where+ nonEmptyIntersection = defaultNonEmptyIntersection q `intersect` h | q `intersects` h = coRec q | otherwise = coRec NoIntersection - type instance IntersectionOf (Line d r) (HalfSpace d r) = [NoIntersection, HalfLine d r, Line d r] +instance (Fractional r, Ord r) => Line 2 r `HasIntersectionWith` HalfSpace 2 r instance (Fractional r, Ord r) => Line 2 r `IsIntersectableWith` HalfSpace 2 r where nonEmptyIntersection = defaultNonEmptyIntersection l@(Line o v) `intersect` h = match (l `intersect` m) $- (H $ \NoIntersection -> if o `intersects` h+ H (\NoIntersection -> if o `intersects` h then coRec l else coRec NoIntersection)- :& (H $ \p -> if (p .+^ v) `intersects` h+ :& H (\p -> if (p .+^ v) `intersects` h then coRec $ HalfLine p v else coRec $ HalfLine p ((-1) *^ v))- :& (H $ \_l -> coRec l)+ :& H (\_l -> coRec l) :& RNil where m = h^.boundingPlane._asLine
src/Data/Geometry/HyperPlane.hs view
@@ -1,6 +1,13 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE TemplateHaskell #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.HyperPlane+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Data.Geometry.HyperPlane where import Control.DeepSeq@@ -11,26 +18,31 @@ import Data.Geometry.Transformation import Data.Geometry.Vector import GHC.Generics (Generic)+import Data.Kind import GHC.TypeLits -------------------------------------------------------------------------------- -- | Hyperplanes embedded in a \(d\) dimensional space.-data HyperPlane (d :: Nat) (r :: *) = HyperPlane { _inPlane :: !(Point d r)- , _normalVec :: !(Vector d r)- } deriving Generic+data HyperPlane (d :: Nat) (r :: Type) =+ HyperPlane { _inPlane :: !(Point d r)+ , _normalVec :: !(Vector d r)+ } deriving Generic makeLenses ''HyperPlane type instance Dimension (HyperPlane d r) = d type instance NumType (HyperPlane d r) = r deriving instance (Arity d, Show r) => Show (HyperPlane d r)-deriving instance (Arity d, Eq r) => Eq (HyperPlane d r) deriving instance (NFData r, Arity d) => NFData (HyperPlane d r) deriving instance Arity d => Functor (HyperPlane d) deriving instance Arity d => Foldable (HyperPlane d) deriving instance Arity d => Traversable (HyperPlane d) +instance (Arity d, Eq r, Fractional r) => Eq (HyperPlane d r) where+ (HyperPlane p u) == h@(HyperPlane _ v) = p `intersects` h && u `isScalarMultipleOf` v++ instance (Arity d, Arity (d + 1), Fractional r) => IsTransformable (HyperPlane d r) where transformBy t (HyperPlane p v) = HyperPlane (transformBy t p) (transformBy t v) @@ -38,9 +50,11 @@ type instance IntersectionOf (Point d r) (HyperPlane d r) = [NoIntersection, Point d r] +instance (Num r, Eq r, Arity d) => Point d r `HasIntersectionWith` HyperPlane d r where+ q `intersects` (HyperPlane p n) = n `dot` (q .-. p) == 0+ instance (Num r, Eq r, Arity d) => Point d r `IsIntersectableWith` HyperPlane d r where nonEmptyIntersection = defaultNonEmptyIntersection- q `intersects` (HyperPlane p n) = n `dot` (q .-. p) == 0 q `intersect` h | q `intersects` h = coRec q | otherwise = coRec NoIntersection@@ -68,16 +82,35 @@ pattern Plane :: Point 3 r -> Vector 3 r -> Plane r pattern Plane p n = HyperPlane p n+{-# COMPLETE Plane #-} +-- | Produces a plane. If r lies counter clockwise of q w.r.t. p then+-- the normal vector of the resulting plane is pointing "upwards".+--+-- >>> from3Points origin (Point3 1 0 0) (Point3 0 1 0)+-- HyperPlane {_inPlane = Point3 0 0 0, _normalVec = Vector3 0 0 1} from3Points :: Num r => Point 3 r -> Point 3 r -> Point 3 r -> HyperPlane 3 r from3Points p q r = let u = q .-. p v = r .-. p in HyperPlane p (u `cross` v) +instance OnSideUpDownTest (Plane r) where+ -- >>> (Point3 5 5 5) `onSideUpDown` from3Points origin (Point3 1 0 0) (Point3 0 1 0)+ -- Above+ -- >>> (Point3 5 5 (-5)) `onSideUpDown` from3Points origin (Point3 1 0 0) (Point3 0 1 0)+ -- Below+ -- >>> (Point3 5 5 0) `onSideUpDown` from3Points origin (Point3 1 0 0) (Point3 0 1 0)+ -- On+ q `onSideUpDown` (Plane p n) = let v = q .-. p in case (n `dot` v) `compare` 0 of+ LT -> Below+ EQ -> On+ GT -> Above type instance IntersectionOf (Line 3 r) (Plane r) = [NoIntersection, Point 3 r, Line 3 r] -instance (Eq r, Fractional r) => (Line 3 r) `IsIntersectableWith` (Plane r) where+instance (Eq r, Fractional r) => Line 3 r `HasIntersectionWith` Plane r++instance (Eq r, Fractional r) => Line 3 r `IsIntersectableWith` Plane r where nonEmptyIntersection = defaultNonEmptyIntersection l@(Line p v) `intersect` (HyperPlane q n) | denum == 0 = if num == 0 then coRec l else coRec NoIntersection@@ -107,3 +140,23 @@ instance HasSupportingPlane (HyperPlane d r) where supportingPlane = id+++-- | Given+-- * a plane,+-- * a unit vector in the plane that will represent the y-axis (i.e. the "view up" vector), and+-- * a point in the plane,+--+-- computes the plane coordinates of the given point, using the+-- inPlane point as the origin, the normal vector of the plane as the+-- unit vector in the "z-direction" and the view up vector as the+-- y-axis.+--+-- >>> planeCoordinatesWith (Plane origin (Vector3 0 0 1)) (Vector3 0 1 0) (Point3 10 10 0)+-- Point2 10.0 10.0+planeCoordinatesWith :: Fractional r => Plane r -> Vector 3 r -> Point 3 r -> Point 2 r+planeCoordinatesWith h vup = projectPoint . transformBy (planeCoordinatesTransform h vup)++planeCoordinatesTransform :: Num r => Plane r -> Vector 3 r -> Transformation 3 r+planeCoordinatesTransform (HyperPlane o n) v = rotateTo (Vector3 (v `cross` n) v n)+ |.| translation ((-1) *^ toVec o)
src/Data/Geometry/Interval.hs view
@@ -1,30 +1,37 @@-{-# LANGUAGE TemplateHaskell #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.Interval+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Data.Geometry.Interval(- -- * 1 dimensional Intervals- Interval(..)- , pattern OpenInterval- , pattern ClosedInterval- , pattern Interval+ -- * 1 dimensional Intervals+ Interval (Interval, OpenInterval,ClosedInterval)+ , fromRange, toRange+ , _Range - -- * querying the start and end of intervals+ -- * querying the start and end of intervals , HasStart(..), HasEnd(..) -- * Working with intervals- , inInterval+ , intersectsInterval, inInterval , shiftLeft' + , asProperInterval, flipInterval+ , module Data.Range- )- where+ ) where import Control.DeepSeq-import Control.Lens (makeLenses, (^.),(%~),(&), Lens')+import Control.Lens (Iso', Lens', iso, (%~), (&), (^.)) import Data.Bifunctor import Data.Bitraversable import Data.Ext import qualified Data.Foldable as F+import Data.Geometry.Boundary import Data.Geometry.Properties import Data.Range-import Data.Semigroup(Arg(..))+import Data.Semigroup (Arg (..)) import qualified Data.Traversable as T import Data.Vinyl import Data.Vinyl.CoRec@@ -34,20 +41,36 @@ -------------------------------------------------------------------------------- -- | An Interval is essentially a 'Data.Range' but with possible payload-newtype Interval a r = GInterval { _unInterval :: Range (r :+ a) }+--+-- We can think of an interval being defined as:+--+-- >>> data Interval a r = Interval (EndPoint (r :+ a)) (EndPoint (r :+ a))+newtype Interval a r = GInterval (Range (r :+ a)) deriving (Eq,Generic,Arbitrary)-makeLenses ''Interval +-- | Cast an interval to a range.+toRange :: Interval a r -> Range (r :+ a)+toRange (GInterval r) = r++-- | Intervals and ranges are isomorphic.+_Range :: Iso' (Interval a r) (Range (r :+ a))+_Range = iso toRange fromRange+{-# INLINE _Range #-}++-- | Constrct an interval from a Range+fromRange :: Range (r :+ a) -> Interval a r+fromRange = GInterval+ deriving instance (NFData a, NFData r) => NFData (Interval a r) instance (Show a, Show r) => Show (Interval a r) where show ~(Interval l u) = concat [ "Interval (", show l, ") (", show u,")"] instance Functor (Interval a) where- fmap = T.fmapDefault+ fmap f (GInterval r) = GInterval $ fmap (first f) r instance F.Foldable (Interval a) where- foldMap = T.foldMapDefault+ foldMap f (GInterval r) = foldMap (f . (^.core)) r instance T.Traversable (Interval a) where traverse f (GInterval r) = GInterval <$> T.traverse f' r@@ -58,14 +81,39 @@ bimap f g (GInterval r) = GInterval $ fmap (bimap g f) r +-- type instance IntersectionOf r (Interval b r) = [NoIntersection, r]+-- -- somehow: GHC does not understand the r here cannot be 'Interval a r' itself :( +-- instance Ord r => r `HasIntersectionWith` Interval b r where+-- x `intersects` r = x `inRange` fmap (^.core) (r^._Range )+++-- instance Ord r => r `IsIntersectableWith` Interval b r where+-- x `intersect` r | x `intersects` r = coRec x+-- | otherwise = coRec NoIntersection+ -- | Test if a value lies in an interval. Note that the difference between -- inInterval and inRange is that the extra value is *not* used in the -- comparison with inInterval, whereas it is in inRange.-inInterval :: Ord r => r -> Interval a r -> Bool-x `inInterval` r = x `inRange` (fmap (^.core) $ r^.unInterval )+intersectsInterval :: Ord r => r -> Interval a r -> Bool+x `intersectsInterval` r = x `inRange` fmap (^.core) (r^._Range ) +-- | Compute where the given query value is with respect to the interval.+--+-- Note that even if the boundary of the interval is open we may+-- return "OnBoundary".+inInterval :: Ord r => r -> Interval a r -> PointLocationResult+x `inInterval` (Interval l r) =+ case x `compare` (l^.unEndPoint.core) of+ LT -> Outside+ EQ -> OnBoundary+ GT -> case x `compare` (r^.unEndPoint.core) of+ LT -> Inside+ EQ -> OnBoundary+ GT -> Outside++ pattern OpenInterval :: (r :+ a) -> (r :+ a) -> Interval a r pattern OpenInterval l u = GInterval (OpenRange l u) @@ -87,7 +135,8 @@ instance HasStart (Interval a r) where type StartCore (Interval a r) = r type StartExtra (Interval a r) = a- start = unInterval.lower.unEndPoint+ start = _Range.lower.unEndPoint+ {-# INLINE start #-} class HasEnd t where type EndCore t@@ -97,30 +146,45 @@ instance HasEnd (Interval a r) where type EndCore (Interval a r) = r type EndExtra (Interval a r) = a- end = unInterval.upper.unEndPoint+ end = _Range.upper.unEndPoint+ {-# INLINE end #-} type instance Dimension (Interval a r) = 1 type instance NumType (Interval a r) = r -type instance IntersectionOf (Interval a r) (Interval a r) = [NoIntersection, Interval a r]+type instance IntersectionOf (Interval a r) (Interval b r)+ = [NoIntersection, Interval (Either a b) r] -instance Ord r => (Interval a r) `IsIntersectableWith` (Interval a r) where+instance Ord r => Interval a r `HasIntersectionWith` Interval b r+instance Ord r => Interval a r `IsIntersectableWith` Interval b r where nonEmptyIntersection = defaultNonEmptyIntersection (GInterval r) `intersect` (GInterval s) = match (r' `intersect` s') $- (H $ \NoIntersection -> coRec NoIntersection)- :& (H $ \(Range l u) -> coRec . GInterval $ Range (l&unEndPoint %~ g)- (u&unEndPoint %~ g) )+ H (\NoIntersection -> coRec NoIntersection)+ :& H (\(Range l u) -> coRec . GInterval $ Range (l&unEndPoint %~ g)+ (u&unEndPoint %~ g) ) :& RNil where- f x = Arg (x^.core) x- r' = fmap f r- s' = fmap f s+ r' :: Range (Arg r (r :+ Either a b))+ r' = fmap (\(x :+ a) -> Arg x (x :+ Left a)) r+ s' :: Range (Arg r (r :+ Either a b))+ s' = fmap (\(x :+ b) -> Arg x (x :+ Right b)) s g (Arg _ x) = x +-- | Shifts the interval to the left by delta+shiftLeft' :: Num r => r -> Interval a r -> Interval a r+shiftLeft' delta = fmap (subtract delta) -shiftLeft' :: Num r => r -> Interval a r -> Interval a r-shiftLeft' x = fmap (subtract x)++-- | Makes sure the start and endpoint are oriented such that the+-- starting value is smaller than the ending value.+asProperInterval :: Ord r => Interval p r -> Interval p r+asProperInterval i | (i^.start.core) > (i^.end.core) = flipInterval i+ | otherwise = i++-- | Flips the start and endpoint of the interval.+flipInterval :: Interval a r -> Interval a r+flipInterval = _Range %~ \(Range s t) -> Range t s
src/Data/Geometry/Interval/Util.hs view
@@ -1,4 +1,12 @@ {-# LANGUAGE TemplateHaskell #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.Interval.Util+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--+-------------------------------------------------------------------------------- module Data.Geometry.Interval.Util where import Control.DeepSeq
src/Data/Geometry/IntervalTree.hs view
@@ -1,4 +1,11 @@ {-# LANGUAGE TemplateHaskell #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.IntervalTree+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Data.Geometry.IntervalTree( NodeData(..) , splitPoint, intervalsLeft, intervalsRight , IntervalTree(..), unIntervalTree@@ -44,8 +51,8 @@ -- -- \(O(n)\) createTree :: Ord r => [r] -> IntervalTree i r-createTree pts = IntervalTree . asBalancedBinTree- . map (\m -> NodeData m mempty mempty) $ pts+createTree = IntervalTree . asBalancedBinTree+ . map (\m -> NodeData m mempty mempty) -- | Build an interval tree@@ -55,7 +62,7 @@ => [i] -> IntervalTree i r fromIntervals is = foldr insert (createTree pts) is where- endPoints (toRange -> Range' a b) = [a,b]+ endPoints (asRange -> Range' a b) = [a,b] pts = List.sort . concatMap endPoints $ is -- | Lists the intervals. We don't guarantee anything about the order@@ -100,7 +107,7 @@ => i -> IntervalTree i r -> IntervalTree i r insert i (IntervalTree t) = IntervalTree $ insert' t where- ri@(Range a b) = toRange i+ ri@(Range a b) = asRange i insert' Nil = Nil insert' (Internal l nd@(_splitPoint -> m) r)@@ -119,7 +126,7 @@ => i -> IntervalTree i r -> IntervalTree i r delete i (IntervalTree t) = IntervalTree $ delete' t where- ri@(Range a b) = toRange i+ ri@(Range a b) = asRange i delete' Nil = Nil delete' (Internal l nd@(_splitPoint -> m) r)@@ -137,15 +144,13 @@ -- | Anything that looks like an interval class IntervalLike i where- toRange :: i -> Range (NumType i)+ asRange :: i -> Range (NumType i) instance IntervalLike (Range r) where- toRange = id+ asRange = id instance IntervalLike (Interval p r) where- toRange = fmap (^.core) . _unInterval--+ asRange = fmap (^.core) . toRange --------------------------------------------------------------------------------
src/Data/Geometry/KDTree.hs view
@@ -1,25 +1,31 @@-{-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE UndecidableInstances #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.KDTree+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Data.Geometry.KDTree where -import Control.Lens hiding (imap, element, Empty, (:<))+import Control.Lens hiding (Empty, element, imap, (:<)) import Data.BinaryTree-import Unsafe.Coerce(unsafeCoerce) import Data.Ext-import qualified Data.Foldable as F+import qualified Data.Foldable as F import Data.Geometry.Box import Data.Geometry.Point import Data.Geometry.Properties import Data.Geometry.Vector-import qualified Data.List.NonEmpty as NonEmpty-import Data.Maybe (fromJust)+import Data.LSeq (LSeq, pattern (:<|))+import qualified Data.LSeq as LSeq+import qualified Data.List.NonEmpty as NonEmpty import Data.Proxy-import Data.LSeq (LSeq, pattern (:<|))-import qualified Data.LSeq as LSeq import Data.Util-import qualified Data.Vector.Fixed as FV+import qualified Data.Vector.Fixed as FV import GHC.TypeLits-import Prelude hiding (replicate)+import Prelude hiding (replicate)+import Unsafe.Coerce (unsafeCoerce) -------------------------------------------------------------------------------- @@ -165,7 +171,7 @@ where -- i = traceShow (c,j) j - m = let xs = fromJust $ pts^?element' (i-1)+ m = let xs = pts^?!element' (i-1) in xs `LSeq.index` (F.length xs `div` 2) -- Since the input seq has >= 2 elems, F.length xs / 2 >= 1. It follows@@ -183,6 +189,6 @@ asSingleton :: (1 <= d, Arity d) => PointSet (LSeq 1) d p r -> Either (Point d r :+ p) (PointSet (LSeq 2) d p r)-asSingleton v = case v^.element (C :: C 0) of+asSingleton v = case v^.element @0 of (p :<| s) | null s -> Left p -- only one lement _ -> Right $ unsafeCoerce v
src/Data/Geometry/Line.hs view
@@ -16,12 +16,13 @@ ) where import Control.Lens+import Data.Bifunctor import Data.Ext import Data.Geometry.Boundary import Data.Geometry.Box import Data.Geometry.Line.Internal import Data.Geometry.LineSegment-import Data.Geometry.Point+import Data.Geometry.Point.Internal import Data.Geometry.Properties import Data.Geometry.SubLine import Data.Geometry.Transformation@@ -48,39 +49,41 @@ type instance IntersectionOf (Point d r) (Line d r) = [NoIntersection, Point d r] -instance (Eq r, Fractional r, Arity d) => (Point d r) `IsIntersectableWith` (Line d r) where- nonEmptyIntersection = defaultNonEmptyIntersection+instance (Eq r, Fractional r, Arity d) => Point d r `HasIntersectionWith` Line d r where intersects = onLine+instance {-# OVERLAPPING #-} (Ord r, Num r) => Point 2 r `HasIntersectionWith` Line 2 r where+ intersects = onLine2++instance (Eq r, Fractional r, Arity d) => Point d r `IsIntersectableWith` Line d r where+ nonEmptyIntersection = defaultNonEmptyIntersection p `intersect` l | p `intersects` l = coRec p | otherwise = coRec NoIntersection -instance {-# OVERLAPPING #-} (Ord r, Num r)- => (Point 2 r) `IsIntersectableWith` (Line 2 r) where+instance {-# OVERLAPPING #-} (Ord r, Num r) => Point 2 r `IsIntersectableWith` Line 2 r where nonEmptyIntersection = defaultNonEmptyIntersection- intersects = onLine2 p `intersect` l | p `intersects` l = coRec p | otherwise = coRec NoIntersection - type instance IntersectionOf (Line 2 r) (Boundary (Rectangle p r)) = [ NoIntersection, Point 2 r, (Point 2 r, Point 2 r) , LineSegment 2 () r] - instance (Ord r, Fractional r)- => (Line 2 r) `IsIntersectableWith` (Boundary (Rectangle p r)) where+ => Line 2 r `HasIntersectionWith` Boundary (Rectangle p r)+instance (Ord r, Fractional r)+ => Line 2 r `IsIntersectableWith` Boundary (Rectangle p r) where nonEmptyIntersection = defaultNonEmptyIntersection line' `intersect` (Boundary rect) = case asAP segP of [sl'] -> case fromUnbounded sl' of Nothing -> error "intersect: line x boundary rect; unbounded line? absurd"- Just sl'' -> coRec $ sl''^.re _SubLine+ Just sl'' -> coRec $ first (either id id) $ sl''^.re _SubLine [] -> case nub' $ asAP pointP of [p] -> coRec p [p,q] -> coRec (p,q) _ -> coRec NoIntersection _ -> error "intersect; line x boundary rect; absurd" where- (t,r,b,l) = sides' rect+ Sides t r b l = sides' rect ints = map (\s -> sl `intersect` toSL s) [t,r,b,l] nub' = map L.head . L.group . L.sort@@ -95,21 +98,22 @@ => proxy t -> [t] asAP _ = mapMaybe (asA @t) ints - segP = Proxy :: Proxy (SubLine 2 () (UnBounded r) r)+ segP = Proxy :: Proxy (SubLine 2 (Either () ()) (UnBounded r) r) pointP = Proxy :: Proxy (Point 2 r) type instance IntersectionOf (Line 2 r) (Rectangle p r) = [ NoIntersection, Point 2 r, LineSegment 2 () r] - instance (Ord r, Fractional r)- => (Line 2 r) `IsIntersectableWith` (Rectangle p r) where+ => Line 2 r `HasIntersectionWith` Rectangle p r+instance (Ord r, Fractional r)+ => Line 2 r `IsIntersectableWith` Rectangle p r where nonEmptyIntersection = defaultNonEmptyIntersection - line' `intersect` rect = match (line' `intersect` (Boundary rect)) $- (H $ \NoIntersection -> coRec NoIntersection)- :& (H $ \p@(Point2 _ _) -> coRec p)- :& (H $ \(p,q) -> coRec $ ClosedLineSegment (ext p) (ext q))- :& (H $ \s -> coRec s)+ line' `intersect` rect = match (line' `intersect` Boundary rect) $+ H coRec -- NoIntersection+ :& H coRec -- Point2+ :& H (\(p,q) -> coRec $ ClosedLineSegment (ext p) (ext q))+ :& H coRec -- LineSegment :& RNil
src/Data/Geometry/Line/Internal.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE UndecidableInstances #-} --------------------------------------------------------------------------------@@ -16,7 +15,9 @@ import Control.DeepSeq import Control.Lens import qualified Data.Foldable as F-import Data.Geometry.Point+import Data.Geometry.Point.Internal+import Data.Geometry.Point.Orientation.Degenerate+import Data.Geometry.Point.Class import Data.Geometry.Properties import Data.Geometry.Vector import Data.Ord (comparing)@@ -34,8 +35,15 @@ data Line d r = Line { _anchorPoint :: !(Point d r) , _direction :: !(Vector d r) } deriving Generic-makeLenses ''Line +-- | Line anchor point.+anchorPoint :: Lens' (Line d r) (Point d r)+anchorPoint = lens _anchorPoint (\line pt -> line{_anchorPoint=pt})++-- | Line direction.+direction :: Lens' (Line d r) (Vector d r)+direction = lens _direction (\line dir -> line{_direction=dir})+ instance (Show r, Arity d) => Show (Line d r) where show (Line p v) = concat [ "Line (", show p, ") (", show v, ")" ] @@ -53,6 +61,8 @@ instance (Arity d, Eq r, Fractional r) => Eq (Line d r) where l@(Line p _) == m = l `isParallelTo` m && p `onLine` m ++ instance (Arbitrary r, Arity d, Num r, Eq r) => Arbitrary (Line d r) where arbitrary = do p <- arbitrary q <- suchThat arbitrary (/= p)@@ -67,9 +77,11 @@ lineThrough :: (Num r, Arity d) => Point d r -> Point d r -> Line d r lineThrough p q = Line p (q .-. p) +-- | Vertical line with a given X-coordinate. verticalLine :: Num r => r -> Line 2 r verticalLine x = Line (Point2 x 0) (Vector2 0 1) +-- | Horizontal line with a given Y-coordinate. horizontalLine :: Num r => r -> Line 2 r horizontalLine y = Line (Point2 0 y) (Vector2 1 0) @@ -78,7 +90,7 @@ -- oriented such that v points into the left halfplane of m. -- -- >>> perpendicularTo $ Line (Point2 3 4) (Vector2 (-1) 2)--- Line (Point2 [3,4]) (Vector2 [-2,-1])+-- Line (Point2 3 4) (Vector2 (-2) (-1)) perpendicularTo :: Num r => Line 2 r -> Line 2 r perpendicularTo (Line p ~(Vector2 vx vy)) = Line p (Vector2 (-vy) vx) @@ -101,8 +113,17 @@ isParallelTo :: (Eq r, Fractional r, Arity d) => Line d r -> Line d r -> Bool (Line _ u) `isParallelTo` (Line _ v) = u `isScalarMultipleOf` v- -- TODO: Maybe use a specialize pragma for 2D (see intersect instance for two lines.)+{-# RULES+"isParallelTo/isParallelTo2" [3]+ forall (l1 :: forall r. Line 2 r) l2. isParallelTo l1 l2 = isParallelTo2 l1 l2+#-}+{-# INLINE[2] isParallelTo #-} +-- | Check whether two lines are parallel+isParallelTo2 :: (Eq r, Num r) => Line 2 r -> Line 2 r -> Bool+isParallelTo2 (Line _ (Vector2 ux uy)) (Line _ (Vector2 vx vy)) = denom == 0+ where+ denom = vy * ux - vx * uy -- | Test if point p lies on line l --@@ -120,9 +141,6 @@ p `onLine2` (Line q v) = ccw p q (q .+^ v) == CoLinear --- -- | Get the point at the given position along line, where 0 corresponds to the -- anchorPoint of the line, and 1 to the point anchorPoint .+^ directionVector pointAt :: (Num r, Arity d) => r -> Line d r -> Point d r@@ -135,15 +153,24 @@ toOffset p (Line q v) = scalarMultiple (p .-. q) v --- | Given point p *on* a line (Line q v), Get the scalar lambda s.t.--- p = q + lambda v. (So this is an unsafe version of 'toOffset')+-- | Given point p near a line (Line q v), get the scalar lambda s.t.+-- the distance between 'p' and 'q + lambda v' is minimized. ----- pre: the input point p lies on the line l.+-- >>> toOffset' (Point2 1 1) (lineThrough origin $ Point2 10 10)+-- 0.1+--+-- >>> toOffset' (Point2 5 5) (lineThrough origin $ Point2 10 10)+-- 0.5+--+-- The point (6,4) is not on the line but we can still point closest to it.+-- >>> toOffset' (Point2 6 4) (lineThrough origin $ Point2 10 10)+-- 0.5 toOffset' :: (Eq r, Fractional r, Arity d) => Point d r -> Line d r -> r-toOffset' p = fromJust' . toOffset p- where- fromJust' (Just x) = x- fromJust' _ = error "toOffset: Nothing"+toOffset' p (Line q v) = dot (p .-. q) v / quadrance v+-- toOffset' p = fromJust' . toOffset p+-- where+-- fromJust' (Just x) = x+-- fromJust' _ = error "toOffset: Nothing" -- | The intersection of two lines is either: NoIntersection, a point or a line.@@ -152,14 +179,14 @@ , Line 2 r ] -instance (Eq r, Fractional r) => (Line 2 r) `IsIntersectableWith` (Line 2 r) where-+instance (Ord r, Num r) => Line 2 r `HasIntersectionWith` Line 2 r where+ l1 `intersects` l2@(Line q _) = not (l1 `isParallelTo2` l2) || q `onLine2` l1 +instance (Ord r, Fractional r) => Line 2 r `IsIntersectableWith` Line 2 r where nonEmptyIntersection = defaultNonEmptyIntersection- l@(Line p ~(Vector2 ux uy)) `intersect` (Line q ~v@(Vector2 vx vy))- | areParallel = if q `onLine` l then coRec l- else coRec NoIntersection+ | areParallel = if q `onLine2` l then coRec l+ else coRec NoIntersection | otherwise = coRec r where r = q .+^ alpha *^ v@@ -205,9 +232,10 @@ fromLinearFunction :: Num r => r -> r -> Line 2 r fromLinearFunction a b = Line (Point2 0 b) (Vector2 1 a) +{- HLINT ignore toLinearFunction -} -- | get values a,b s.t. the input line is described by y = ax + b. -- returns Nothing if the line is vertical-toLinearFunction :: forall r. (Fractional r, Eq r)+toLinearFunction :: forall r. (Fractional r, Ord r) => Line 2 r -> Maybe (r,r) toLinearFunction l@(Line _ ~(Vector2 vx vy)) = match (l `intersect` verticalLine (0 :: r)) $ (H $ \NoIntersection -> Nothing) -- l is a vertical line@@ -215,30 +243,44 @@ :& (H $ \_ -> Nothing) -- l is a vertical line (through x=0) :& RNil ++instance (Fractional r, Arity d) => HasSquaredEuclideanDistance (Line d r) where+ pointClosestTo p (Line a m) = a .+^ (t0 *^ m)+ where+ -- see https://monkeyproofsolutions.nl/wordpress/how-to-calculate-the-shortest-distance-between-a-point-and-a-line/+ t0 = numerator / divisor+ numerator = (p .-. a) `dot` m+ divisor = m `dot` m++ -- | Result of a side test data SideTestUpDown = Below | On | Above deriving (Show,Read,Eq,Ord) --- | Given a point q and a line l, compute to which side of l q lies. For--- vertical lines the left side of the line is interpeted as below.------ >>> Point2 10 10 `onSideUpDown` (lineThrough origin $ Point2 10 5)--- Above--- >>> Point2 10 10 `onSideUpDown` (lineThrough origin $ Point2 (-10) 5)--- Above--- >>> Point2 5 5 `onSideUpDown` (verticalLine 10)--- Below--- >>> Point2 5 5 `onSideUpDown` (lineThrough origin $ Point2 (-3) (-3))--- On-onSideUpDown :: (Ord r, Num r) => Point 2 r -> Line 2 r -> SideTestUpDown-q `onSideUpDown` (Line p v) = let r = p .+^ v- f z = (z^.xCoord, -z^.yCoord)- minBy g a b = F.minimumBy (comparing g) [a,b]- maxBy g a b = F.maximumBy (comparing g) [a,b]- in case ccw (minBy f p r) (maxBy f p r) q of- CCW -> Above- CW -> Below- CoLinear -> On+class OnSideUpDownTest t where+ onSideUpDown :: (d ~ Dimension t, r ~ NumType t, Ord r, Num r)+ => Point d r -> t -> SideTestUpDown +instance OnSideUpDownTest (Line 2 r) where+ -- | Given a point q and a line l, compute to which side of l q lies. For+ -- vertical lines the left side of the line is interpeted as below.+ --+ -- >>> Point2 10 10 `onSideUpDown` (lineThrough origin $ Point2 10 5)+ -- Above+ -- >>> Point2 10 10 `onSideUpDown` (lineThrough origin $ Point2 (-10) 5)+ -- Above+ -- >>> Point2 5 5 `onSideUpDown` (verticalLine 10)+ -- Below+ -- >>> Point2 5 5 `onSideUpDown` (lineThrough origin $ Point2 (-3) (-3))+ -- On+ q `onSideUpDown` (Line p v) = let r = p .+^ v+ f z = (z^.xCoord, -z^.yCoord)+ minBy g a b = F.minimumBy (comparing g) [a,b]+ maxBy g a b = F.maximumBy (comparing g) [a,b]+ in case ccw (minBy f p r) (maxBy f p r) q of+ CCW -> Above+ CW -> Below+ CoLinear -> On+ -- | Result of a side test data SideTest = LeftSide | OnLine | RightSide deriving (Show,Read,Eq,Ord) @@ -267,6 +309,9 @@ liesAbove :: (Ord r, Num r) => Point 2 r -> Line 2 r -> Bool q `liesAbove` l = q `onSideUpDown` l == Above +-- | Test if the query point q lies (strictly) above line l+liesBelow :: (Ord r, Num r) => Point 2 r -> Line 2 r -> Bool+q `liesBelow` l = q `onSideUpDown` l == Below -- | Get the bisector between two points bisector :: Fractional r => Point 2 r -> Point 2 r -> Line 2 r
src/Data/Geometry/LineSegment.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} -------------------------------------------------------------------------------- -- | -- Module : Data.Geometry.LineSegment@@ -10,285 +11,128 @@ -- Line segment data type and some basic functions on line segments -- ---------------------------------------------------------------------------------module Data.Geometry.LineSegment( LineSegment- , pattern LineSegment- , pattern LineSegment'- , pattern ClosedLineSegment- , endPoints+module Data.Geometry.LineSegment+ ( LineSegment(LineSegment, LineSegment', ClosedLineSegment, OpenLineSegment)+ , endPoints - , _SubLine- , module Data.Geometry.Interval+ , _SubLine+ , module Data.Geometry.Interval + , toLineSegment+ , orderedEndPoints+ , segmentLength+ , sqSegmentLength+ , sqDistanceToSeg, sqDistanceToSegArg+ , flipSegment - , toLineSegment- , onSegment- , orderedEndPoints- , segmentLength- , sqDistanceToSeg, sqDistanceToSegArg- , flipSegment- ) where+ , interpolate, sampleLineSegment+ , ordAtX, ordAtY, xCoordAt, yCoordAt+ ) where -import Control.Arrow ((&&&))-import Control.DeepSeq-import Control.Lens+-- import Control.Lens import Data.Ext-import qualified Data.Foldable as F+-- import qualified Data.Foldable as F+import Data.Geometry.Boundary import Data.Geometry.Box.Internal+import Data.Geometry.Box.Sides import Data.Geometry.Interval hiding (width, midPoint)-import Data.Geometry.Line.Internal+import Data.Geometry.LineSegment.Internal import Data.Geometry.Point import Data.Geometry.Properties-import Data.Geometry.SubLine-import Data.Geometry.Transformation-import Data.Geometry.Vector-import Data.Ord (comparing)-import Data.Vinyl-import Data.Vinyl.CoRec-import GHC.TypeLits-import Test.QuickCheck+-- import Data.Geometry.SubLine+import Data.Util+-- import Data.Vinyl.CoRec+-- import Data.Bifunctor+-- import Data.Either+-- import Data.Maybe (mapMaybe) ------------------------------------------------------------------------------------ * d-dimensional LineSegments --- | Line segments. LineSegments have a start and end point, both of which may--- contain additional data of type p. We can think of a Line-Segment being defined as--------- >>> data LineSegment d p r = LineSegment (EndPoint (Point d r :+ p)) (EndPoint (Point d r :+ p))-newtype LineSegment d p r = GLineSegment { _unLineSeg :: Interval p (Point d r)}--makeLenses ''LineSegment----- | Pattern that essentially models the line segment as a:------ >>> data LineSegment d p r = LineSegment (EndPoint (Point d r :+ p)) (EndPoint (Point d r :+ p))-pattern LineSegment :: EndPoint (Point d r :+ p)- -> EndPoint (Point d r :+ p)- -> LineSegment d p r-pattern LineSegment s t = GLineSegment (Interval s t)-{-# COMPLETE LineSegment #-}---- | Gets the start and end point, but forgetting if they are open or closed.-pattern LineSegment' :: Point d r :+ p- -> Point d r :+ p- -> LineSegment d p r-pattern LineSegment' s t <- ((^.start) &&& (^.end) -> (s,t))-{-# COMPLETE LineSegment' #-}--pattern ClosedLineSegment :: Point d r :+ p- -> Point d r :+ p- -> LineSegment d p r-pattern ClosedLineSegment s t = GLineSegment (ClosedInterval s t)-{-# COMPLETE ClosedLineSegment #-}--type instance Dimension (LineSegment d p r) = d-type instance NumType (LineSegment d p r) = r--instance HasStart (LineSegment d p r) where- type StartCore (LineSegment d p r) = Point d r- type StartExtra (LineSegment d p r) = p- start = unLineSeg.start--instance HasEnd (LineSegment d p r) where- type EndCore (LineSegment d p r) = Point d r- type EndExtra (LineSegment d p r) = p- end = unLineSeg.end--instance (Arbitrary r, Arbitrary p, Arity d) => Arbitrary (LineSegment d p r) where- arbitrary = LineSegment <$> arbitrary <*> arbitrary--deriving instance (Arity d, NFData r, NFData p) => NFData (LineSegment d p r)+-------------------------------------------------------------------------------- --- | Traversal to access the endpoints. Note that this traversal--- allows you to change more or less everything, even the dimension--- and the numeric type used, but it preservers if the segment is open--- or closed.-endPoints :: Traversal (LineSegment d p r) (LineSegment d' q s)- (Point d r :+ p) (Point d' s :+ q)-endPoints = \f (LineSegment p q) -> LineSegment <$> traverse f p- <*> traverse f q--_SubLine :: (Num r, Arity d) => Iso' (LineSegment d p r) (SubLine d p r r)-_SubLine = iso segment2SubLine subLineToSegment-{-# INLINE _SubLine #-}--segment2SubLine :: (Num r, Arity d)- => LineSegment d p r -> SubLine d p r r-segment2SubLine ss = SubLine (Line p (q .-. p)) (Interval s e)- where- p = ss^.start.core- q = ss^.end.core- (Interval a b) = ss^.unLineSeg- s = a&unEndPoint.core .~ 0- e = b&unEndPoint.core .~ 1--subLineToSegment :: (Num r, Arity d) => SubLine d p r r -> LineSegment d p r-subLineToSegment sl = let (Interval s' e') = (fixEndPoints sl)^.subRange- s = s'&unEndPoint %~ (^.extra)- e = e'&unEndPoint %~ (^.extra)- in LineSegment s e--instance (Num r, Arity d) => HasSupportingLine (LineSegment d p r) where- supportingLine s = lineThrough (s^.start.core) (s^.end.core)+type instance IntersectionOf (LineSegment 2 p r) (Boundary (Rectangle q r)) =+ [ NoIntersection, Point 2 r, Two (Point 2 r) , LineSegment 2 () r ] -instance (Show r, Show p, Arity d) => Show (LineSegment d p r) where- show ~(LineSegment p q) = concat ["LineSegment (", show p, ") (", show q, ")"]--deriving instance (Eq r, Eq p, Arity d) => Eq (LineSegment d p r)--- deriving instance (Ord r, Ord p, Arity d) => Ord (LineSegment d p r)-deriving instance Arity d => Functor (LineSegment d p)+type instance IntersectionOf (LineSegment 2 p r) (Rectangle q r) =+ [ NoIntersection, Point 2 r, LineSegment 2 (Maybe p) r ] -instance PointFunctor (LineSegment d p) where- pmap f ~(LineSegment s e) = LineSegment (s&unEndPoint.core %~ f)- (e&unEndPoint.core %~ f)+instance (Fractional r, Ord r)+ => LineSegment 2 p r `HasIntersectionWith` Boundary (Rectangle q r) where+ seg `intersects` (Boundary rect) = any (seg `intersects`) $ sides rect -instance Arity d => IsBoxable (LineSegment d p r) where- boundingBox l = boundingBox (l^.start.core) <> boundingBox (l^.end.core)+instance (Fractional r, Ord r) => LineSegment 2 p r `HasIntersectionWith` Rectangle q r where+ seg@(LineSegment p q) `intersects` rect =+ inRect p || inRect q || any (seg `intersects`) (sides rect) || bothOpenAndOnBoundary seg+ where+ inRect = \case+ Open (a :+ _) -> a `insideBox` rect -- if strictly inside the seg intersects.+ Closed (a :+ _) -> a `inBox` rect -- in or on the boundary is fine -instance (Fractional r, Arity d, Arity (d + 1)) => IsTransformable (LineSegment d p r) where- transformBy = transformPointFunctor+ -- if somehow the segment is open, and both endpoints lie on+ -- different sides of the boundary, (so the segment crosses the+ -- interior) it also intersects. Handle that case.+ bothOpenAndOnBoundary (LineSegment (Open _) (Open _)) =+ interpolate (1/2) seg `intersects` rect+ bothOpenAndOnBoundary _ = False -instance Arity d => Bifunctor (LineSegment d) where- bimap f g (GLineSegment i) = GLineSegment $ bimap f (fmap g) i+-- instance (Num r, Ord r)+-- => (LineSegment 2 p r) `IsIntersectableWith` (Boundary (Rectangle q r)) where+-- seg `intersect` (Boundary rect) = case partitionEithers res of+-- (s : _, _) -> coRec s -- if we find a segment that should be the+-- -- answer; we shouldn't fine more than one+-- -- by the way.+-- ([], []) -> coRec NoIntersection+-- ([], [p]) -> coRec p+-- ([], (p:q:_)) -> coRec $ Two p q+-- -- more than two points is impossible anwyay+-- where+-- res = mapMaybe (\side -> match (seg `intersect` side) $+-- (H $ \NoIntersection -> Nothing)+-- :& (H $ \(p :: Point 2 r) -> Just $ Right p)+-- :& (H $ \(s :: LineSegment 2 () r) -> Just $ Left s)+-- :& RNil+-- ) . F.toList $ sides rect --- ** Converting between Lines and LineSegments---- | Directly convert a line into a line segment.-toLineSegment :: (Monoid p, Num r, Arity d) => Line d r -> LineSegment d p r-toLineSegment (Line p v) = ClosedLineSegment (p :+ mempty)- (p .+^ v :+ mempty)---- *** Intersecting LineSegments--type instance IntersectionOf (LineSegment 2 p r) (LineSegment 2 p r) = [ NoIntersection- , Point 2 r- , LineSegment 2 p r- ]--type instance IntersectionOf (LineSegment 2 p r) (Line 2 r) = [ NoIntersection- , Point 2 r- , LineSegment 2 p r- ]---instance (Ord r, Fractional r) =>- (LineSegment 2 p r) `IsIntersectableWith` (LineSegment 2 p r) where- nonEmptyIntersection = defaultNonEmptyIntersection-- a `intersect` b = match ((a^._SubLine) `intersect` (b^._SubLine)) $- (H coRec)- :& (H coRec)- :& (H $ coRec . subLineToSegment)- :& RNil---instance (Ord r, Fractional r) =>- (LineSegment 2 p r) `IsIntersectableWith` (Line 2 r) where- nonEmptyIntersection = defaultNonEmptyIntersection-- s `intersect` l = let ubSL = s^._SubLine.re _unBounded.to dropExtra- in match (ubSL `intersect` (fromLine l)) $- (H coRec)- :& (H $ coRec)- :& (H $ const (coRec s))- :& RNil---- * Functions on LineSegments---- | Test if a point lies on a line segment.------ >>> (Point2 1 0) `onSegment` (ClosedLineSegment (origin :+ ()) (Point2 2 0 :+ ()))--- True--- >>> (Point2 1 1) `onSegment` (ClosedLineSegment (origin :+ ()) (Point2 2 0 :+ ()))--- False--- >>> (Point2 5 0) `onSegment` (ClosedLineSegment (origin :+ ()) (Point2 2 0 :+ ()))--- False--- >>> (Point2 (-1) 0) `onSegment` (ClosedLineSegment (origin :+ ()) (Point2 2 0 :+ ()))--- False--- >>> (Point2 1 1) `onSegment` (ClosedLineSegment (origin :+ ()) (Point2 3 3 :+ ()))--- True------ Note that the segments are assumed to be closed. So the end points lie on the segment.------ >>> (Point2 2 0) `onSegment` (ClosedLineSegment (origin :+ ()) (Point2 2 0 :+ ()))--- True--- >>> origin `onSegment` (ClosedLineSegment (origin :+ ()) (Point2 2 0 :+ ()))--- True--------- This function works for arbitrary dimensons.------ >>> (Point3 1 1 1) `onSegment` (ClosedLineSegment (origin :+ ()) (Point3 3 3 3 :+ ()))--- True--- >>> (Point3 1 2 1) `onSegment` (ClosedLineSegment (origin :+ ()) (Point3 3 3 3 :+ ()))--- False-onSegment :: (Ord r, Fractional r, Arity d)- => Point d r -> LineSegment d p r -> Bool-p `onSegment` l = let s = l^.start.core- t = l^.end.core- inRange' x = 0 <= x && x <= 1- in- if s == t -- zero length segment- then p == s- else maybe False inRange' $ scalarMultiple (p .-. s) (t .-. s)----- | The left and right end point (or left below right if they have equal x-coords)-orderedEndPoints :: Ord r => LineSegment 2 p r -> (Point 2 r :+ p, Point 2 r :+ p)-orderedEndPoints s = if pc <= qc then (p, q) else (q,p)- where- p@(pc :+ _) = s^.start- q@(qc :+ _) = s^.end----- | Length of the line segment-segmentLength :: (Arity d, Floating r) => LineSegment d p r -> r-segmentLength ~(LineSegment' p q) = distanceA (p^.core) (q^.core)----- | Squared distance from the point to the Segment s. The same remark as for--- the 'sqDistanceToSegArg' applies here.-sqDistanceToSeg :: (Arity d, Fractional r, Ord r) => Point d r -> LineSegment d p r -> r-sqDistanceToSeg p = fst . sqDistanceToSegArg p----- | Squared distance from the point to the Segment s, and the point on s--- realizing it. Note that if the segment is *open*, the closest point--- returned may be one of the (open) end points, even though technically the--- end point does not lie on the segment. (The true closest point then lies--- arbitrarily close to the end point).-sqDistanceToSegArg :: (Arity d, Fractional r, Ord r)- => Point d r -> LineSegment d p r -> (r, Point d r)-sqDistanceToSegArg p s = let m = sqDistanceToArg p (supportingLine s)- xs = m : map (\(q :+ _) -> (qdA p q, q)) [s^.start, s^.end]- in F.minimumBy (comparing fst)- . filter (flip onSegment s . snd) $ xs---- | flips the start and end point of the segment-flipSegment :: LineSegment d p r -> LineSegment d p r-flipSegment s = let p = s^.start- q = s^.end- in (s&start .~ q)&end .~ p---- testSeg :: LineSegment 2 () Rational--- testSeg = LineSegment (Open $ ext origin) (Closed $ ext (Point2 10 0))---- horL' :: Line 2 Rational--- horL' = horizontalLine 0---- testI = testSeg `intersect` horL'----- ff = bimap (fmap Val) (const ())+-- -- instance (Num r, Ord r) => (LineSegment 2 p r) `IsIntersectableWith` (Rectangle q r) where+-- -- seg@(LineSegment' (p :+ _) (q :+ _)) `intersect` rect =+-- -- case (p `intersects` rect, q `intersects` rect) of+-- -- (True,True) -> coRec seg'+-- -- (False,False) -> match boundaryIntersection $ -- both endpoints outside+-- -- (H $ \NoIntersection -> coRec NoIntersection)+-- -- :& (H $ \(a :: Point 2 r) -> coRec a)+-- -- :& (H $ \(Two a b) -> coRec $ ClosedLineSegment (ext a) (ext b))+-- -- :& (H $ \s -> coRec s)+-- -- :& RNil+-- -- (True,False) -> withInside p (\other -> LineSegment p' (closed other))+-- -- (False,True) -> withInside q (\other -> LineSegment (closed other) q')+-- -- where+-- -- seg'@(LineSegment p' q') = first (const ()) seg --- ss' = let (LineSegment p q) = testSeg in--- LineSegment (p&unEndPoint %~ ff)--- (q&unEndPoint %~ ff)+-- -- boundaryIntersection = seg `intersect` (Boundary rect)+-- -- closed :: Point 2 r -> EndPoint (Point 2 r :+ ())+-- -- closed = Closed . ext --- ss'' = ss'^._SubLine+-- -- -- the given endpoint endPt is inside the box [*], while the+-- -- -- other endpoint is not. The second arg is a function that+-- -- -- rebuilds the segment given the replacement endpoint, compute+-- -- -- the right segment that is inside the rectangle.+-- -- --+-- -- -- [*] We require that the *point* lies in or on the box. If the+-- -- -- endpoint was open, it may still be the case that we do not+-- -- -- actually intersect the rectangle (i.e. if the open endPoint+-- -- -- was on a corner of the rect).+-- -- -- withInside :: Point 2 r+-- -- -- -> (Point 2 r -> LineSegment 2 () r)+-- -- -- -> IntersectionOf ....+-- -- withInside endPt mkSeg = match boundaryIntersection $+-- -- (H $ \NoIntersection -> coRec NoIntersection)+-- -- -- seems this should happen only if the endpoint that was+-- -- -- suposedly in/on the rect was open.+-- -- :& (H $ \(a :: Point 2 r) -> coRec . mkSeg $ a)+-- -- :& (H $ \(Two a b) -> coRec . mkSeg $ if a == endPt then b else a)+-- -- :& (H $ \s -> coRec s)+-- -- :& RNil
+ src/Data/Geometry/LineSegment/Internal.hs view
@@ -0,0 +1,551 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE UndecidableInstances #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.LineSegment.Internal+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--+-- Line segment data type and some basic functions on line segments+--+--------------------------------------------------------------------------------+module Data.Geometry.LineSegment.Internal+ ( LineSegment(LineSegment, LineSegment', ClosedLineSegment, OpenLineSegment)+ , endPoints++ , _SubLine+ , module Data.Geometry.Interval+++ , toLineSegment+ , onSegment, onSegment2+ , orderedEndPoints+ , segmentLength+ , sqSegmentLength+ , sqDistanceToSeg, sqDistanceToSegArg -- todo, at some point remove these. They are superfluous+ , flipSegment++ , interpolate+ , validSegment+ , sampleLineSegment++ , ordAtX, ordAtY, xCoordAt, yCoordAt+ ) where++import Control.Arrow ((&&&))+import Control.DeepSeq+import Control.Lens+import Control.Monad.Random+import Data.Ext+import qualified Data.Foldable as F+import Data.Geometry.Box.Internal+import Data.Geometry.Interval hiding (width, midPoint)+import Data.Geometry.Line.Internal+import Data.Geometry.Point+import Data.Geometry.Properties+import Data.Geometry.SubLine+import Data.Geometry.Transformation.Internal+import Data.Geometry.Vector+import Data.Ord (comparing)+import Data.Tuple (swap)+import Data.Vinyl+import Data.Vinyl.CoRec+import GHC.TypeLits+import Test.QuickCheck (Arbitrary(..), suchThatMap)+import Text.Read+++--------------------------------------------------------------------------------+-- * d-dimensional LineSegments+++-- | Line segments. LineSegments have a start and end point, both of which may+-- contain additional data of type p. We can think of a Line-Segment being defined as+--+--+-- >>> data LineSegment d p r = LineSegment (EndPoint (Point d r :+ p)) (EndPoint (Point d r :+ p))+--+-- it is assumed that the two endpoints of the line segment are disjoint. This is not checked.+newtype LineSegment d p r = GLineSegment { _unLineSeg :: Interval p (Point d r) }++makeLenses ''LineSegment+++pattern LineSegment :: EndPoint (Point d r :+ p)+ -> EndPoint (Point d r :+ p)+ -> LineSegment d p r+pattern LineSegment s t = GLineSegment (Interval s t)+{-# COMPLETE LineSegment #-}++-- | Gets the start and end point, but forgetting if they are open or closed.+pattern LineSegment' :: Point d r :+ p+ -> Point d r :+ p+ -> LineSegment d p r+pattern LineSegment' s t <- ((^.start) &&& (^.end) -> (s,t))+{-# COMPLETE LineSegment' #-}++pattern ClosedLineSegment :: Point d r :+ p -> Point d r :+ p -> LineSegment d p r+pattern ClosedLineSegment s t = GLineSegment (ClosedInterval s t)+{-# COMPLETE ClosedLineSegment #-}++pattern OpenLineSegment :: Point d r :+ p -> Point d r :+ p -> LineSegment d p r+pattern OpenLineSegment s t = GLineSegment (OpenInterval s t)+{-# COMPLETE OpenLineSegment #-}++++type instance Dimension (LineSegment d p r) = d+type instance NumType (LineSegment d p r) = r++instance HasStart (LineSegment d p r) where+ type StartCore (LineSegment d p r) = Point d r+ type StartExtra (LineSegment d p r) = p+ start = unLineSeg.start++instance HasEnd (LineSegment d p r) where+ type EndCore (LineSegment d p r) = Point d r+ type EndExtra (LineSegment d p r) = p+ end = unLineSeg.end++instance (Arbitrary r, Arbitrary p, Eq r, Arity d) => Arbitrary (LineSegment d p r) where+ arbitrary = suchThatMap ((,) <$> arbitrary <*> arbitrary)+ (uncurry validSegment)+++deriving instance (Arity d, NFData r, NFData p) => NFData (LineSegment d p r)++-- | Compute a random line segmeent+sampleLineSegment :: (Arity d, RandomGen g, Random r) => Rand g (LineSegment d () r)+sampleLineSegment = do+ a <- ext <$> getRandom+ a' <- getRandom+ b <- ext <$> getRandom+ b' <- getRandom+ pure $ LineSegment (if a' then Open a else Closed a) (if b' then Open b else Closed b)+++{- HLINT ignore endPoints -}+-- | Traversal to access the endpoints. Note that this traversal+-- allows you to change more or less everything, even the dimension+-- and the numeric type used, but it preservers if the segment is open+-- or closed.+endPoints :: Traversal (LineSegment d p r) (LineSegment d' q s)+ (Point d r :+ p) (Point d' s :+ q)+endPoints = \f (LineSegment p q) -> LineSegment <$> traverse f p+ <*> traverse f q++_SubLine :: (Num r, Arity d) => Iso' (LineSegment d p r) (SubLine d p r r)+_SubLine = iso segment2SubLine subLineToSegment+{-# INLINE _SubLine #-}++segment2SubLine :: (Num r, Arity d)+ => LineSegment d p r -> SubLine d p r r+segment2SubLine ss = SubLine (Line p (q .-. p)) (Interval s e)+ where+ p = ss^.start.core+ q = ss^.end.core+ (Interval a b) = ss^.unLineSeg+ s = a&unEndPoint.core .~ 0+ e = b&unEndPoint.core .~ 1++{- HLINT ignore subLineToSegment -}+subLineToSegment :: (Num r, Arity d) => SubLine d p r r -> LineSegment d p r+subLineToSegment sl = let Interval s' e' = (fixEndPoints sl)^.subRange+ s = s'&unEndPoint %~ (^.extra)+ e = e'&unEndPoint %~ (^.extra)+ in LineSegment s e++instance (Num r, Arity d) => HasSupportingLine (LineSegment d p r) where+ supportingLine s = lineThrough (s^.start.core) (s^.end.core)+++instance (Show r, Show p, Arity d) => Show (LineSegment d p r) where+ showsPrec d (LineSegment p' q') = case (p',q') of+ (Closed p, Closed q) -> f "ClosedLineSegment" p q+ (Open p, Open q) -> f "OpenLineSegment" p q+ (p,q) -> f "LineSegment" p q+ where+ app_prec = 10+ f :: (Show a, Show b) => String -> a -> b -> String -> String+ f cn p q = showParen (d > app_prec) $+ showString cn . showString " "+ . showsPrec (app_prec+1) p+ . showString " "+ . showsPrec (app_prec+1) q++instance (Read r, Read p, Arity d) => Read (LineSegment d p r) where+ readPrec = parens $ (prec app_prec $ do+ Ident "ClosedLineSegment" <- lexP+ p <- step readPrec+ q <- step readPrec+ return (ClosedLineSegment p q))+ ++++ (prec app_prec $ do+ Ident "OpenLineSegment" <- lexP+ p <- step readPrec+ q <- step readPrec+ return (OpenLineSegment p q))+ ++++ (prec app_prec $ do+ Ident "LineSegment" <- lexP+ p <- step readPrec+ q <- step readPrec+ return (LineSegment p q))+ where app_prec = 10+++deriving instance (Eq r, Eq p, Arity d) => Eq (LineSegment d p r)+-- deriving instance (Ord r, Ord p, Arity d) => Ord (LineSegment d p r)+deriving instance Arity d => Functor (LineSegment d p)++instance PointFunctor (LineSegment d p) where+ pmap f ~(LineSegment s e) = LineSegment (s&unEndPoint.core %~ f)+ (e&unEndPoint.core %~ f)++instance Arity d => IsBoxable (LineSegment d p r) where+ boundingBox l = boundingBox (l^.start.core) <> boundingBox (l^.end.core)++instance (Fractional r, Arity d, Arity (d + 1)) => IsTransformable (LineSegment d p r) where+ transformBy = transformPointFunctor++instance Arity d => Bifunctor (LineSegment d) where+ bimap f g (GLineSegment i) = GLineSegment $ bimap f (fmap g) i++-- | Transform a segment into a closed line segment+toClosedSegment :: LineSegment d p r -> LineSegment d p r+toClosedSegment (LineSegment' s t) = ClosedLineSegment s t+++-- ** Converting between Lines and LineSegments++-- | Directly convert a line into a Closed line segment.+toLineSegment :: (Monoid p, Num r, Arity d) => Line d r -> LineSegment d p r+toLineSegment (Line p v) = ClosedLineSegment (p :+ mempty)+ (p .+^ v :+ mempty)++-- *** Intersecting LineSegments++type instance IntersectionOf (Point d r) (LineSegment d p r) = [ NoIntersection+ , Point d r+ ]++-- type instance IntersectionOf (LineSegment 2 p r) (LineSegment 2 p r) = [ NoIntersection+-- , Point 2 r+-- , LineSegment 2 p r+-- ]++type instance IntersectionOf (LineSegment 2 p r) (LineSegment 2 q r) =+ [ NoIntersection, Point 2 r, LineSegment 2 (Either p q) r]++type instance IntersectionOf (LineSegment 2 p r) (Line 2 r) = [ NoIntersection+ , Point 2 r+ , LineSegment 2 p r+ ]+++instance {-# OVERLAPPING #-} (Ord r, Num r)+ => Point 2 r `HasIntersectionWith` LineSegment 2 p r where+ intersects = onSegment2++instance {-# OVERLAPPING #-} (Ord r, Num r)+ => Point 2 r `IsIntersectableWith` LineSegment 2 p r where+ nonEmptyIntersection = defaultNonEmptyIntersection+ p `intersect` seg | p `intersects` seg = coRec p+ | otherwise = coRec NoIntersection+++instance {-# OVERLAPPABLE #-} (Ord r, Fractional r, Arity d)+ => Point d r `HasIntersectionWith` LineSegment d p r where+ intersects = onSegment++instance {-# OVERLAPPABLE #-} (Ord r, Fractional r, Arity d)+ => Point d r `IsIntersectableWith` LineSegment d p r where+ nonEmptyIntersection = defaultNonEmptyIntersection+ p `intersect` seg | p `intersects` seg = coRec p+ | otherwise = coRec NoIntersection++-- | Test if a point lies on a line segment.+--+-- As a user, you should typically just use 'intersects' instead.+onSegment :: (Ord r, Fractional r, Arity d) => Point d r -> LineSegment d p r -> Bool+p `onSegment` (LineSegment up vp) =+ maybe False inRange' (scalarMultiple (p .-. u) (v .-. u))+ where+ u = up^.unEndPoint.core+ v = vp^.unEndPoint.core++ atMostUpperBound = if isClosed vp then (<= 1) else (< 1)+ atLeastLowerBound = if isClosed up then (0 <=) else (0 <)++ inRange' x = atLeastLowerBound x && atMostUpperBound x+ -- the type of test we use for the 2D version might actually also+ -- work in higher dimensions that might allow us to drop the+ -- Fractional constraint+++-- | Orders the endpoints of the segments in the given direction.+withRank :: forall p q r. (Ord r, Num r)+ => Vector 2 r+ -> LineSegment 2 p r -> LineSegment 2 q r+ -> (Interval p Int, Interval q Int)+withRank v (LineSegment p q) (LineSegment a b) = (i1,i2)+ where+ -- let rank p = 3, rank q = 6+ i1 = Interval (p&unEndPoint.core .~ 3) (q&unEndPoint.core .~ 6)++ i2 = Interval (a&unEndPoint.core .~ assign' 1 a') (a&unEndPoint.core .~ assign' 2 b')++ -- make sure the intervals are in the same order, otherwise flip them.+ (a',b') = case cmp a b of+ LT -> (a,b)+ EQ -> (a,b)+ GT -> (b,a)++ assign' x c = case cmp c p of+ LT -> x+ EQ -> 3+ GT -> case cmp c q of+ LT -> 4 + x+ EQ -> 6+ GT -> 7 + x++ cmp :: EndPoint (Point 2 r :+ a) -> EndPoint (Point 2 r :+ b) -> Ordering+ cmp c d = cmpInDirection v (c^.unEndPoint.core) (d^.unEndPoint.core)++instance (Ord r, Num r) =>+ LineSegment 2 p r `HasIntersectionWith` LineSegment 2 q r where+ s1@(LineSegment p _) `intersects` s2+ | l1 `isParallelTo2` l2 = parallelCase+ | otherwise = s1 `intersects` l2 && s2 `intersects` l1+ where+ l1@(Line _ v) = supportingLine s1+ l2 = supportingLine s2++ parallelCase = (p^.unEndPoint.core) `onLine2` l2 && i1 `intersects` i2+ (i1,i2) = withRank v s1 s2++ -- correctness argument:+ -- if the segments share a supportingLine (l1 and l2 parallel, and point of l1 on l2)+ -- the segments intersect iff their intervals along the line intersect.++ -- if the supporting lines intersect in a point, say x the+ -- segments intersect iff s1 intersects the supporting line and+ -- vice versa:+ ---+ -- => direction: is trivial+ -- <= direction: s1 intersects l2 means x+ -- lies on s1. Symmetrically s2 intersects l1 means x lies on+ -- s2. Hence, x lies on both s1 and s2, and thus the segments+ -- intersect.+++++++instance (Ord r, Fractional r) =>+ LineSegment 2 p r `IsIntersectableWith` LineSegment 2 q r where+ nonEmptyIntersection = defaultNonEmptyIntersection++ a `intersect` b = match ((a^._SubLine) `intersect` (b^._SubLine)) $+ H coRec+ :& H coRec+ :& H (coRec . subLineToSegment)+ :& RNil++instance (Ord r, Num r) =>+ LineSegment 2 p r `HasIntersectionWith` Line 2 r where+ (LineSegment p q) `intersects` l = case onSide (p^.unEndPoint.core) l of+ OnLine -> isClosed p || case onSide (q^.unEndPoint.core) l of+ OnLine -> isClosed q || (p^.unEndPoint.core) /= (q^.unEndPoint.core)+ _ -> False+ sp -> case onSide (q^.unEndPoint.core) l of+ OnLine -> isClosed q+ sq -> sp /= sq+++instance (Ord r, Fractional r) =>+ LineSegment 2 p r `IsIntersectableWith` Line 2 r where+ nonEmptyIntersection = defaultNonEmptyIntersection++ s `intersect` l = let ubSL = s^._SubLine.re _unBounded.to dropExtra+ in match (ubSL `intersect` fromLine l) $+ H coRec+ :& H coRec+ :& H (const (coRec s))+ :& RNil++++-- * Functions on LineSegments++-- | Test if a point lies on a line segment.+--+-- >>> (Point2 1 0) `onSegment2` (ClosedLineSegment (origin :+ ()) (Point2 2 0 :+ ()))+-- True+-- >>> (Point2 1 1) `onSegment2` (ClosedLineSegment (origin :+ ()) (Point2 2 0 :+ ()))+-- False+-- >>> (Point2 5 0) `onSegment2` (ClosedLineSegment (origin :+ ()) (Point2 2 0 :+ ()))+-- False+-- >>> (Point2 (-1) 0) `onSegment2` (ClosedLineSegment (origin :+ ()) (Point2 2 0 :+ ()))+-- False+-- >>> (Point2 1 1) `onSegment2` (ClosedLineSegment (origin :+ ()) (Point2 3 3 :+ ()))+-- True+-- >>> (Point2 2 0) `onSegment2` (ClosedLineSegment (origin :+ ()) (Point2 2 0 :+ ()))+-- True+-- >>> origin `onSegment2` (ClosedLineSegment (origin :+ ()) (Point2 2 0 :+ ()))+-- True+onSegment2 :: (Ord r, Num r)+ => Point 2 r -> LineSegment 2 p r -> Bool+p `onSegment2` s@(LineSegment u v) = case ccw' (ext p) (u^.unEndPoint) (v^.unEndPoint) of+ CoLinear -> let su = p `onSide` lu+ sv = p `onSide` lv+ in su /= sv+ && ((su == OnLine) `implies` isClosed u)+ && ((sv == OnLine) `implies` isClosed v)+ _ -> False+ where+ (Line _ w) = perpendicularTo $ supportingLine s+ lu = Line (u^.unEndPoint.core) w+ lv = Line (v^.unEndPoint.core) w++ a `implies` b = b || not a+++-- | The left and right end point (or left below right if they have equal x-coords)+orderedEndPoints :: Ord r => LineSegment 2 p r -> (Point 2 r :+ p, Point 2 r :+ p)+orderedEndPoints s = if pc <= qc then (p, q) else (q,p)+ where+ p@(pc :+ _) = s^.start+ q@(qc :+ _) = s^.end+++-- | Length of the line segment+segmentLength :: (Arity d, Floating r) => LineSegment d p r -> r+segmentLength ~(LineSegment' p q) = distanceA (p^.core) (q^.core)++-- | Squared length of a line segment.+sqSegmentLength :: (Arity d, Num r) => LineSegment d p r -> r+sqSegmentLength ~(LineSegment' p q) = qdA (p^.core) (q^.core)++-- | Squared distance from the point to the Segment s. The same remark as for+-- the 'sqDistanceToSegArg' applies here.+{-# DEPRECATED sqDistanceToSeg "use squaredEuclideanDistTo instead" #-}+sqDistanceToSeg :: (Arity d, Fractional r, Ord r) => Point d r -> LineSegment d p r -> r+sqDistanceToSeg p = fst . sqDistanceToSegArg p++-- | Squared distance from the point to the Segment s, and the point on s+-- realizing it.+--+-- Note that if the segment is *open*, the closest point returned may+-- be one of the (open) end points, even though technically the end+-- point does not lie on the segment. (The true closest point then+-- lies arbitrarily close to the end point).+--+-- >>> :{+-- let ls = OpenLineSegment (Point2 0 0 :+ ()) (Point2 1 0 :+ ())+-- p = Point2 2 0+-- in snd (sqDistanceToSegArg p ls) == Point2 1 0+-- :}+-- True+sqDistanceToSegArg :: (Arity d, Fractional r, Ord r)+ => Point d r -> LineSegment d p r -> (r, Point d r)+sqDistanceToSegArg p (toClosedSegment -> s) =+ let m = sqDistanceToArg p (supportingLine s)+ xs = m : map (\(q :+ _) -> (qdA p q, q)) [s^.start, s^.end]+ in F.minimumBy (comparing fst)+ . filter (flip onSegment s . snd) $ xs++instance (Fractional r, Arity d, Ord r) => HasSquaredEuclideanDistance (LineSegment d p r) where+ pointClosestToWithDistance q = swap . sqDistanceToSegArg q+++-- | flips the start and end point of the segment+flipSegment :: LineSegment d p r -> LineSegment d p r+flipSegment s = let p = s^.start+ q = s^.end+ in (s&start .~ q)&end .~ p++-- testSeg :: LineSegment 2 () Rational+-- testSeg = LineSegment (Open $ ext origin) (Closed $ ext (Point2 10 0))++-- horL' :: Line 2 Rational+-- horL' = horizontalLine 0++-- testI = testSeg `intersect` horL'+++-- ff = bimap (fmap Val) (const ())++-- ss' = let (LineSegment p q) = testSeg in+-- LineSegment (p&unEndPoint %~ ff)+-- (q&unEndPoint %~ ff)++-- ss'' = ss'^._SubLine++-- | Linearly interpolate the two endpoints with a value in the range [0,1]+--+-- >>> interpolate 0.5 $ ClosedLineSegment (ext $ origin) (ext $ Point2 10.0 10.0)+-- Point2 5.0 5.0+-- >>> interpolate 0.1 $ ClosedLineSegment (ext $ origin) (ext $ Point2 10.0 10.0)+-- Point2 1.0 1.0+-- >>> interpolate 0 $ ClosedLineSegment (ext $ origin) (ext $ Point2 10.0 10.0)+-- Point2 0.0 0.0+-- >>> interpolate 1 $ ClosedLineSegment (ext $ origin) (ext $ Point2 10.0 10.0)+-- Point2 10.0 10.0+interpolate :: (Fractional r, Arity d) => r -> LineSegment d p r -> Point d r+interpolate t (LineSegment' p q) = Point $ (asV p ^* (1-t)) ^+^ (asV q ^* t)+ where+ asV = (^.core.vector)+++-- | smart constructor that creates a valid segment, i.e. it validates+-- that the endpoints are disjoint.+validSegment :: (Eq r, Arity d)+ => EndPoint (Point d r :+ p) -> EndPoint (Point d r :+ p)+ -> Maybe (LineSegment d p r)+validSegment u v = let s = LineSegment u v+ in if s^.start.core /= s^.end.core then Just s else Nothing++++-- | Given a y-coordinate, compare the segments based on the+-- x-coordinate of the intersection with the horizontal line through y+ordAtY :: (Fractional r, Ord r) => r+ -> LineSegment 2 p r -> LineSegment 2 p r -> Ordering+ordAtY y = comparing (xCoordAt y)++-- | Given an x-coordinate, compare the segments based on the+-- y-coordinate of the intersection with the horizontal line through y+ordAtX :: (Fractional r, Ord r) => r+ -> LineSegment 2 p r -> LineSegment 2 p r -> Ordering+ordAtX x = comparing (yCoordAt x)++-- | Given a y coord and a line segment that intersects the horizontal line+-- through y, compute the x-coordinate of this intersection point.+--+-- note that we will pretend that the line segment is closed, even if it is not+xCoordAt :: (Fractional r, Ord r) => r -> LineSegment 2 p r -> r+xCoordAt y (LineSegment' (Point2 px py :+ _) (Point2 qx qy :+ _))+ | py == qy = px `max` qx -- s is horizontal, and since it by the+ -- precondition it intersects the sweep+ -- line, we return the x-coord of the+ -- rightmost endpoint.+ | otherwise = px + alpha * (qx - px)+ where+ alpha = (y - py) / (qy - py)+++-- | Given an x-coordinate and a line segment that intersects the vertical line+-- through x, compute the y-coordinate of this intersection point.+--+-- note that we will pretend that the line segment is closed, even if it is not+yCoordAt :: (Fractional r, Ord r) => r -> LineSegment 2 p r -> r+yCoordAt x (LineSegment' (Point2 px py :+ _) (Point2 qx qy :+ _))+ | px == qx = py `max` qy -- s is vertical, since by the precondition it+ -- intersects we return the y-coord of the topmost+ -- endpoint.+ | otherwise = py + alpha * (qy - py)+ where+ alpha = (x - px) / (qx - px)
+ src/Data/Geometry/Matrix.hs view
@@ -0,0 +1,95 @@+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.Matrix+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--+-- type-indexed matrices.+--+--------------------------------------------------------------------------------+module Data.Geometry.Matrix(+ Matrix(Matrix)+ , identityMatrix++ , multM+ , mult++ , Invertible(..)+ , HasDeterminant(..)+ ) where++import Control.Lens (imap)+import Data.Coerce+import Data.Geometry.Matrix.Internal (mkRow)+import Data.Geometry.Vector+import Data.Geometry.Vector.VectorFamilyPeano+import Linear.Matrix (M22, M33, M44, (!*!), (!*))+import qualified Linear.Matrix as Lin++--------------------------------------------------------------------------------+-- * Matrices++-- | A matrix of n rows, each of m columns, storing values of type r.+newtype Matrix n m r = Matrix (Vector n (Vector m r))++deriving instance (Show r, Arity n, Arity m) => Show (Matrix n m r)+deriving instance (Eq r, Arity n, Arity m) => Eq (Matrix n m r)+deriving instance (Ord r, Arity n, Arity m) => Ord (Matrix n m r)+deriving instance (Arity n, Arity m) => Functor (Matrix n m)+deriving instance (Arity n, Arity m) => Foldable (Matrix n m)+deriving instance (Arity n, Arity m) => Traversable (Matrix n m)++-- | Matrix product.+multM :: (Arity r, Arity c, Arity c', Num a) => Matrix r c a -> Matrix c c' a -> Matrix r c' a+(Matrix a) `multM` (Matrix b) = Matrix $ a !*! b++-- | Matrix * column vector.+mult :: (Arity m, Arity n, Num r) => Matrix n m r -> Vector m r -> Vector n r+(Matrix m) `mult` v = m !* v++-- | Produces the Identity Matrix.+identityMatrix :: (Arity d, Num r) => Matrix d d r+identityMatrix = Matrix $ imap mkRow (pure 1)++-- | Class of matrices that are invertible.+class Invertible n r where+ inverse' :: Matrix n n r -> Matrix n n r++instance Fractional r => Invertible 2 r where+ -- >>> inverse' $ Matrix $ Vector2 (Vector2 1 2) (Vector2 3 4.0)+ -- Matrix Vector2 [Vector2 [-2.0,1.0],Vector2 [1.5,-0.5]]+ inverse' = withM22 Lin.inv22++instance Fractional r => Invertible 3 r where+ -- >>> inverse' $ Matrix $ Vector3 (Vector3 1 2 4) (Vector3 4 2 2) (Vector3 1 1 1.0)+ -- Matrix Vector3 [Vector3 [0.0,0.5,-1.0],Vector3 [-0.5,-0.75,3.5],Vector3 [0.5,0.25,-1.5]]+ inverse' = withM33 Lin.inv33++instance Fractional r => Invertible 4 r where+ inverse' = withM44 Lin.inv44++-- | Class of matrices that have a determinant.+class Arity d => HasDeterminant d where+ det :: Num r => Matrix d d r -> r++instance HasDeterminant 1 where+ det (Matrix (Vector1 (Vector1 x))) = x+instance HasDeterminant 2 where+ det = Lin.det22 . coerce+instance HasDeterminant 3 where+ det = Lin.det33 . coerce+instance HasDeterminant 4 where+ det = Lin.det44 . coerce++--------------------------------------------------------------------------------+-- Boilerplate code for converting between Matrix and M22/M33/M44.++withM22 :: (M22 a -> M22 b) -> Matrix 2 2 a -> Matrix 2 2 b+withM22 f = coerce . f . coerce++withM33 :: (M33 a -> M33 b) -> Matrix 3 3 a -> Matrix 3 3 b+withM33 f = coerce . f . coerce++withM44 :: (M44 a -> M44 b) -> Matrix 4 4 a -> Matrix 4 4 b+withM44 f = coerce . f . coerce
+ src/Data/Geometry/Matrix/Internal.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE Unsafe #-}+module Data.Geometry.Matrix.Internal where++import Control.Lens (set)+import Data.Geometry.Vector+import qualified Data.Vector.Fixed as FV++--------------------------------------------------------------------------------+-- * Helper functions to easily create matrices++-- | Creates a row with zeroes everywhere, except at position i, where the+-- value is the supplied value.+mkRow :: forall d r. (Arity d, Num r) => Int -> r -> Vector d r+mkRow i x = set (FV.element i) x zero
src/Data/Geometry/PlanarSubdivision.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE ScopedTypeVariables #-} --------------------------------------------------------------------------------@@ -23,9 +22,8 @@ import qualified Data.List.NonEmpty as NonEmpty import Data.Geometry.PlanarSubdivision.Basic import Data.Geometry.PlanarSubdivision.Merge+import Data.Geometry.PlanarSubdivision.TreeRep import Data.Geometry.Polygon-import qualified Data.PlaneGraph as PG-import Data.Proxy -- import Data.Geometry.Point@@ -43,25 +41,23 @@ -- -- runningtime: \(O(n\log n\log k)\) in case of polygons with holes, -- and \(O(n\log k)\) in case of simple polygons.-fromPolygons :: (Foldable1 c, Ord r, Fractional r)- => proxy s- -> f -- ^ outer face data- -> c (Polygon t p r :+ f) -- ^ the disjoint polygons- -> PlanarSubdivision s p () f r-fromPolygons px oD = mergeAllWith const- . fmap (\(pg :+ iD) -> fromPolygon px pg iD oD) . toNonEmpty+fromPolygons :: forall s c t p r f. (Foldable1 c, Ord r, Num r)+ => f -- ^ outer face data+ -> c (Polygon t p r :+ f) -- ^ the disjoint polygons+ -> PlanarSubdivision s p () f r+fromPolygons oD = mergeAllWith const+ . fmap (\(pg :+ iD) -> fromPolygon pg iD oD) . toNonEmpty -- | Version of 'fromPolygons' that accepts 'SomePolygon's as input.-fromPolygons' :: forall proxy c s p r f. (Foldable1 c, Ord r, Fractional r)- => proxy s- -> f -- ^ outer face data+fromPolygons' :: forall s c p r f. (Foldable1 c, Ord r, Num r)+ => f -- ^ outer face data -> c (SomePolygon p r :+ f) -- ^ the disjoint polygons -> PlanarSubdivision s p () f r-fromPolygons' px oD =+fromPolygons' oD = mergeAllWith const . fmap (\(pg :+ iD) -> either (build iD) (build iD) pg) . toNonEmpty where build :: f -> Polygon t p r -> PlanarSubdivision s p () f r- build iD pg = fromPolygon px pg iD oD+ build iD pg = fromPolygon pg iD oD -- | Construct a planar subdivision from a polygon. Since our PlanarSubdivision -- models only connected planar subdivisions, this may add dummy/invisible@@ -71,21 +67,19 @@ -- -- running time: \(O(n)\) for a simple polygon, \(O(n\log n)\) for a -- polygon with holes.-fromPolygon :: forall proxy t p f r s. (Ord r, Fractional r)- => proxy s- -> Polygon t p r+fromPolygon :: forall s t p f r. (Ord r, Num r)+ => Polygon t p r -> f -- ^ data inside -> f -- ^ data outside the polygon -> PlanarSubdivision s p () f r-fromPolygon p pg@(SimplePolygon _) iD oD = fromSimplePolygon p pg iD oD-fromPolygon p (MultiPolygon vs hs) iD oD = case NonEmpty.nonEmpty hs of+fromPolygon pg@SimplePolygon{} iD oD = fromSimplePolygon @s pg iD oD+fromPolygon (MultiPolygon vs hs) iD oD = case NonEmpty.nonEmpty hs of Nothing -> outerPG- Just hs' -> let hs'' = (\pg -> fromSimplePolygon wp (toCounterClockWiseOrder pg) oD iD) <$> hs'+ Just hs' -> let hs'' = (\pg -> fromSimplePolygon @(Wrap s)+ (toCounterClockWiseOrder pg) oD iD) <$> hs' in embedAsHolesIn hs'' (\_ x -> x) i outerPG where- wp = Proxy :: Proxy (Wrap s)-- outerPG = fromSimplePolygon p (SimplePolygon vs) iD oD+ outerPG = fromSimplePolygon @s vs iD oD i = V.last $ faces' outerPG @@ -123,13 +117,13 @@ data HoleData f p = Outer !f | Hole !f !p deriving (Show,Eq) -holeData :: HoleData f p -> f-holeData (Outer f) = f-holeData (Hole f _) = f+_holeData :: HoleData f p -> f+_holeData (Outer f) = f+_holeData (Hole f _) = f -getP :: HoleData f p -> Maybe p-getP (Outer _) = Nothing-getP (Hole _ p) = Just p+_getP :: HoleData f p -> Maybe p+_getP (Outer _) = Nothing+_getP (Hole _ p) = Just p -------------------------------------------------------------------------------- @@ -158,3 +152,26 @@ -- mySubDiv = fromSimplePolygons (Id Test) -- 0 -- (NonEmpty.fromList [simplePg' :+ 1, trianglePG :+ 2])+++++-- type R = Int+-- data MyWorld++-- mySubDiv :: PlanarSubdivision MyWorld Int (Int,Int) String R+-- mySubDiv = undefined++-- faceData xs = FaceData (Seq.fromList xs)++++-- fromTreeRep :: TreeRep v e f r -> PlanarSubdivision s v e f r+-- fromTreeRep (PlanarSD of' (InnerSD ajs fs)) = undefined+++-- fromInnerRep :: forall s v e f r. (Ord r, Fractional r)+-- => InnerRep v e f r -> PlanarSubdivision s v e () r+-- fromInnerRep f (InnerSD ajs fs) = fromConnectedSegments (Proxy @s) segs+-- where+-- segs = adjs
src/Data/Geometry/PlanarSubdivision/Basic.hs view
@@ -1,6 +1,4 @@ {-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE ScopedTypeVariables #-} -------------------------------------------------------------------------------- -- |@@ -35,14 +33,16 @@ , components, component , vertices', vertices , edges', edges- , faces', faces, internalFaces+ , faces', internalFaces', faces, internalFaces , darts'- -- , traverseVertices, traverseDarts, traverseFaces+ , traverseVertices, traverseDarts, traverseFaces+ , mapVertices, mapDarts, mapFaces , headOf, tailOf, twin, endPoints , incidentEdges, incomingEdges, outgoingEdges- , nextIncidentEdge+ , nextIncidentEdge, prevIncidentEdge+ , nextIncidentEdgeFrom, prevIncidentEdgeFrom , neighboursOf , leftFace, rightFace@@ -58,8 +58,10 @@ , faceDataOf , edgeSegment, edgeSegments- , rawFacePolygon, rawFaceBoundary- , rawFacePolygons+ , faceBoundary+ , internalFacePolygon, internalFacePolygons+ , outerFacePolygon, outerFacePolygon'+ , facePolygons , VertexId(..), FaceId(..), Dart, World(..) @@ -68,9 +70,14 @@ , dataVal , dartMapping, Raw(..)++ , asLocalD, asLocalV, asLocalF+ , Incident (incidences)+ , common, commonVertices, commonDarts, commonFaces ) where import Control.Lens hiding (holes, holesOf, (.=))+import Data.Bifunctor (first, second) import Data.Coerce import Data.Ext import qualified Data.Foldable as F@@ -92,6 +99,7 @@ , HasDataOf(..) ) import qualified Data.Sequence as Seq+import qualified Data.Set as Set import qualified Data.Vector as V import qualified Data.Vector.Mutable as MV import GHC.Generics (Generic)@@ -136,6 +144,7 @@ boundingBox = boundingBoxList' . V.toList . _components +-- | Lens to access a particular component of the planar subdivision. component :: ComponentId s -> Lens' (PlanarSubdivision s v e f r) (Component s r) component ci = components.singular (ix $ unCI ci)@@ -151,10 +160,11 @@ -- | Constructs a planarsubdivision from a PlaneGraph -- -- runningTime: \(O(n)\)-fromPlaneGraph :: forall s v e f r. (Ord r, Fractional r)+fromPlaneGraph :: forall s v e f r. (Ord r, Num r) => PlaneGraph s v e f r -> PlanarSubdivision s v e f r fromPlaneGraph g = fromPlaneGraph' g (PG.outerFaceDart g) +{- HLINT ignore fromPlaneGraph' -} -- | Given a (connected) PlaneGraph and a dart that has the outerface on its left -- | Constructs a planarsubdivision --@@ -202,24 +212,22 @@ -- -- pre: the input polygon is given in counterclockwise order -- running time: \(O(n)\).-fromSimplePolygon :: (Ord r, Fractional r)- => proxy s- -> SimplePolygon p r+fromSimplePolygon :: forall s p f r. (Ord r, Num r)+ => SimplePolygon p r -> f -- ^ data inside -> f -- ^ data outside the polygon -> PlanarSubdivision s p () f r-fromSimplePolygon p pg iD oD =- fromPlaneGraph (PG.fromSimplePolygon p pg iD oD)+fromSimplePolygon pg iD oD =+ fromPlaneGraph (PG.fromSimplePolygon pg iD oD) -- | Constructs a connected planar subdivision. -- -- pre: the segments form a single connected component -- running time: \(O(n\log n)\)-fromConnectedSegments :: (Foldable f, Ord r, Fractional r)- => proxy s- -> f (LineSegment 2 p r :+ e)- -> PlanarSubdivision s (NonEmpty p) e () r-fromConnectedSegments px = fromPlaneGraph . PG.fromConnectedSegments px+fromConnectedSegments :: forall s p e r f. (Foldable f, Ord r, Num r)+ => f (LineSegment 2 p r :+ e)+ -> PlanarSubdivision s (NonEmpty p) e () r+fromConnectedSegments = fromPlaneGraph . PG.fromConnectedSegments -- g1 = PG.fromConnectedSegments (Identity Test1) testSegs -- ps1 = fromConnectedSegments (Identity Test1) testSegs@@ -280,7 +288,7 @@ numEdges :: PlanarSubdivision s v e f r -> Int numEdges = (`div` 2) . V.length . _rawDartData --- | Get the number of faces+-- | \( O(1) \). Get the number of faces -- -- >>> numFaces myGraph -- 4@@ -327,11 +335,16 @@ edges :: PlanarSubdivision s v e f r -> V.Vector (Dart s, e) edges ps = (\e -> (e,ps^.dataOf e)) <$> edges' ps -+-- | \( O(n) \). Vector of all primal faces. faces' :: PlanarSubdivision s v e f r -> V.Vector (FaceId' s) faces' ps = let n = numFaces ps in V.fromList $ map (FaceId . VertexId) [0..n-1] +-- | \( O(n) \). Vector of all primal faces.+internalFaces' :: PlanarSubdivision s v e f r -> V.Vector (FaceId' s)+internalFaces' = V.tail . faces'++-- | \( O(n) \). Vector of all primal faces with associated data. faces :: PlanarSubdivision s v e f r -> V.Vector (FaceId' s, FaceData (Dart s) f) faces ps = (\fi -> (fi,ps^.faceDataOf fi)) <$> faces' ps @@ -414,8 +427,8 @@ in (\d -> g^.dataOf d) <$> ds --- | Given a dart d that points into some vertex v, report the next--- dart e in the cyclic order around v.+-- | Given a dart d that points into some vertex v, report the next dart in the+-- cyclic (counterclockwise) order around v. -- -- running time: \(O(1)\) nextIncidentEdge :: Dart s -> PlanarSubdivision s v e f r -> Dart s@@ -423,13 +436,57 @@ d'' = PG.nextIncidentEdge d' g in g^.dataOf d'' --- | All incoming edges incident to vertex v, in counterclockwise order around v.+-- | Given a dart d that points into some vertex v, report the+-- previous dart in the cyclic (counterclockwise) order around v.+--+-- running time: \(O(1)\)+--+-- >>> prevIncidentEdge (dart 1 "+1") smallG+-- Dart (Arc 3) +1+prevIncidentEdge :: Dart s -> PlanarSubdivision s v e f r -> Dart s+prevIncidentEdge d ps = let (_,d',g) = asLocalD d ps+ d'' = PG.prevIncidentEdge d' g+ in g^.dataOf d''++-- | Given a dart d that points away from some vertex v, report the+-- next dart in the cyclic (counterclockwise) order around v.+--+--+-- running time: \(O(1)\)+--+nextIncidentEdgeFrom :: Dart s -> PlanarSubdivision s v e f r -> Dart s+nextIncidentEdgeFrom d ps = let (_,d',g) = asLocalD d ps+ d'' = PG.nextIncidentEdgeFrom d' g+ in g^.dataOf d''++-- | Given a dart d that points into away from vertex v, report the previous dart in the+-- cyclic (counterclockwise) order around v.+--+-- running time: \(O(1)\)+--+prevIncidentEdgeFrom :: Dart s -> PlanarSubdivision s v e f r -> Dart s+prevIncidentEdgeFrom d ps = let (_,d',g) = asLocalD d ps+ d'' = PG.prevIncidentEdgeFrom d' g+ in g^.dataOf d''+++-- | All edges incident to vertex v in incoming direction+-- (i.e. pointing into v) in counterclockwise order around v.+--+-- running time: \(O(k)\), where \(k) is the total number of incident edges of v incomingEdges :: VertexId' s -> PlanarSubdivision s v e f r -> V.Vector (Dart s)-incomingEdges v ps = V.filter (not . isPositive) $ incidentEdges v ps+incomingEdges v ps = orient <$> incidentEdges v ps+ where+ orient d = if headOf d ps == v then d else twin d --- | All outgoing edges incident to vertex v, in counterclockwise order around v.+-- | All edges incident to vertex v in outgoing direction+-- (i.e. pointing away from v) in counterclockwise order around v.+--+-- running time: \(O(k)\), where \(k) is the total number of incident edges of v outgoingEdges :: VertexId' s -> PlanarSubdivision s v e f r -> V.Vector (Dart s)-outgoingEdges v ps = V.filter isPositive $ incidentEdges v ps+outgoingEdges v ps = orient <$> incidentEdges v ps+ where+ orient d = if tailOf d ps == v then d else twin d -- | Gets the neighbours of a particular vertex, in counterclockwise order@@ -437,10 +494,7 @@ -- -- running time: \(O(k)\), where \(k\) is the output size neighboursOf :: VertexId' s -> PlanarSubdivision s v e f r -> V.Vector (VertexId' s)-neighboursOf v ps = otherVtx <$> incidentEdges v ps- where- otherVtx d = let u = tailOf d ps in if u == v then headOf d ps else u-+neighboursOf v ps = flip tailOf ps <$> incomingEdges v ps -- | The face to the left of the dart --@@ -458,10 +512,11 @@ fi = PG.rightFace d' g in g^.dataOf fi --- | The darts on the outer boundary of the face, for internal faces--- the darts are in clockwise order. For the outer face the darts are--- in counterclockwise order, and the darts from various components are in no particular order.---+-- | The darts on the outer boundary of this face. The darts are+-- reported in order along the face. This means that for internal+-- faces the darts are reported in *clockwise* order along the+-- boundary, whereas for the outer face the darts are reported in+-- counter clockwise order. -- -- running time: \(O(k)\), where \(k\) is the output size. outerBoundaryDarts :: FaceId' s -> PlanarSubdivision s v e f r -> V.Vector (Dart s)@@ -469,6 +524,7 @@ where single (_,f',g) = (\d -> g^.dataOf d) <$> PG.boundary f' g + -- | Get the local face and component from a given face. asLocalF :: FaceId' s -> PlanarSubdivision s v e f r -> NonEmpty (ComponentId s, FaceId' (Wrap s), Component s r)@@ -478,14 +534,15 @@ where toLocalF d = let (ci,d',c) = asLocalD d ps in (ci,PG.leftFace d' c,c) --- | The vertices of the outer boundary of the face, for internal faces in--- clockwise order, for the outer face in counter clockwise order.+-- | The vertices of the outer boundary of the face, for internal+-- faces in clockwise order, for the outer face in counter clockwise+-- order. -- -- -- running time: \(O(k)\), where \(k\) is the output size. boundaryVertices :: FaceId' s -> PlanarSubdivision s v e f r -> V.Vector (VertexId' s)-boundaryVertices f ps = (\d -> headOf d ps) <$> outerBoundaryDarts f ps+boundaryVertices f ps = (`headOf` ps) <$> outerBoundaryDarts f ps -- | Lists the holes in this face, given as a list of darts to arbitrary darts@@ -517,7 +574,10 @@ asLocalV (VertexId v) ps = let (Raw ci v' _) = ps^?!rawVertexData.ix v in (ci,v',ps^.component ci) --- | Note that using the setting part of this lens may be very expensive!!+-- | Lens to access the vertex data+--+-- Note that using the setting part of this lens may be very+-- expensive!! (O(n)) vertexDataOf :: VertexId' s -> Lens' (PlanarSubdivision s v e f r ) (VertexData r v) vertexDataOf (VertexId vi) = lens get' set''@@ -529,10 +589,17 @@ in ps&rawVertexData.ix vi.dataVal .~ (x^.vData) &component ci.PG.vertexDataOf wvdi.location .~ (x^.location) ++-- | Get the location of a vertex in the planar subdivision.+--+-- Note that the setting part of this lens may be very expensive!+-- Moreover, use with care (as this may destroy planarity etc.) locationOf :: VertexId' s -> Lens' (PlanarSubdivision s v e f r ) (Point 2 r) locationOf v = vertexDataOf v.location +-- | Lens to get the face data of a particular face. Note that the+-- setting part of this lens may be very expensive! (O(n)) faceDataOf :: FaceId' s -> Lens' (PlanarSubdivision s v e f r) (FaceData (Dart s) f) faceDataOf fi = lens getF setF@@ -553,33 +620,54 @@ type DataOf (PlanarSubdivision s v e f r) (FaceId' s) = f dataOf f = faceDataOf f.fData +-- | Traverse the vertices of the planar subdivision+traverseVertices :: Applicative g+ => (VertexId' s -> v -> g v')+ -> PlanarSubdivision s v e f r -> g (PlanarSubdivision s v' e f r)+traverseVertices h = traverseOf rawVertexData (traverseWith VertexId h) --- -- | Traverse the vertices--- ----- traverseVertices :: Applicative m--- => (VertexId' s -> v -> m v')--- -> PlanarSubdivision s v e f r--- -> m (PlanarSubdivision s v' e f r)--- traverseVertices f = itraverseOf (vertexData.itraversed) (\i -> f (VertexId i))+-- | Traverse the darts of the Planar subdivision+traverseDarts :: Applicative g+ => (Dart s -> e -> g e')+ -> PlanarSubdivision s v e f r -> g (PlanarSubdivision s v e' f r)+traverseDarts h = traverseOf rawDartData (traverseWith toEnum h) --- -- | Traverses the darts--- ----- traverseDarts :: Applicative m--- => (Dart s -> e -> m e')--- -> PlanarSubdivision s v e f r--- -> m (PlaneGraph s v e' f r)--- traverseDarts f = traverseOf (dart) (PG.traverseDarts f) +-- | Traverse the faces of the planar subdivision.+traverseFaces :: Applicative g+ => (FaceId' s -> f -> g f')+ -> PlanarSubdivision s v e f r -> g (PlanarSubdivision s v e f' r)+traverseFaces h = traverseOf rawFaceData (traverseFaces' h)+ where+ traverseFaces' h' = itraverse (\i -> traverse (h' (FaceId . VertexId $ i))) --- -- | Traverses the faces--- ----- traverseFaces :: Applicative m--- => (FaceId' s -> f -> m f')--- -> PlaneGraph s v e f r--- -> m (PlaneGraph s v e f' r)--- traverseFaces f = traverseOf graph (PG.traverseFaces f)+-- | Helper function to implement traver(vertertices|darts|faces)+traverseWith :: Applicative g+ => (Int -> w s)+ -> (w s -> v -> g v')+ -> V.Vector (Raw ci i v)+ -> g (V.Vector (Raw ci i v'))+traverseWith mkIdx h = itraverse (\i -> traverse (h $ mkIdx i)) +-------------------------------------------------------------------------------- +-- | Map with index over all faces+mapFaces :: (FaceId' s -> t -> f')+ -> PlanarSubdivision s v e t r -> PlanarSubdivision s v e f' r+mapFaces h = runIdentity . traverseFaces (\i x -> Identity $ h i x)++-- | Map with index over all vertices+mapVertices :: (VertexId' s -> t -> v')+ -> PlanarSubdivision s t e f r -> PlanarSubdivision s v' e f r+mapVertices h = runIdentity . traverseVertices (\i x -> Identity $ h i x)++-- | Map with index over all darts+mapDarts :: (Dart s -> t -> e')+ -> PlanarSubdivision s v t f r -> PlanarSubdivision s v e' f r+mapDarts h = runIdentity . traverseDarts (\i x -> Identity $ h i x)++--------------------------------------------------------------------------------+ -- | Getter for the data at the endpoints of a dart -- -- running time: \(O(1)\)@@ -612,7 +700,9 @@ edgeSegments ps = (\d -> (d,edgeSegment d ps)) <$> edges' ps --- | Given a dart and the subdivision constructs the line segment representing it+-- | Given a dart and the subdivision constructs the line segment+-- representing it. The segment \(\overline{uv})\) is has \(u\) as its+-- tail and \(v\) as its head. -- -- \(O(1)\) edgeSegment :: Dart s -> PlanarSubdivision s v e f r -> LineSegment 2 v r :+ e@@ -620,45 +710,106 @@ in ClosedLineSegment p q :+ ps^.dataOf d --- | Generates the darts incident to a face, starting with the given dart.+-- | Given a dart d, generates the darts on (the current component of)+-- the boundary of the the face that is to the right of the given+-- dart. The darts are reported in order along the face. This means+-- that for --+-- - (the outer boundary of an) internal faces the darts are reported+-- in *clockwise* order along the boundary,+-- - the "inner" boundary of a face, i.e. the boundary of ahole, the+-- darts are reported in *counter clockwise* order. --+-- Note that this latter case means that in the darts of a a component+-- of the outer face are reported in counter clockwise order.+-- -- \(O(k)\), where \(k\) is the number of darts reported boundary' :: Dart s -> PlanarSubdivision s v e f r -> V.Vector (Dart s) boundary' d ps = let (_,d',g) = asLocalD d ps in (\d'' -> g^.dataOf d'') <$> PG.boundary' d' g ---- | Constructs the outer boundary of the face+-- | The outerboundary of the face as a simple polygon. For internal+-- faces the polygon that is reported has its vertices stored in CCW+-- order (as expected). ----- \(O(k)\), where \(k\) is the complexity of the outer boundary of the face-rawFaceBoundary :: FaceId' s -> PlanarSubdivision s v e f r -> SimplePolygon v r :+ f-rawFaceBoundary i ps = fromPoints pts :+ (ps^.dataOf i)+-- pre: FaceId refers to an internal face.+--+-- \(O(k)\), where \(k\) is the complexity of the outer boundary of+-- the face+faceBoundary :: FaceId' s -> PlanarSubdivision s v e f r -> SimplePolygon v r :+ f+faceBoundary i ps = unsafeFromPoints (reverse pts) :+ (ps^.dataOf i) where d = V.head $ outerBoundaryDarts i ps pts = (\d' -> PG.vtxDataToExt $ ps^.vertexDataOf (headOf d' ps)) <$> V.toList (boundary' d ps)-+ -- for internal faces boundary' produces the boundary darts in+ -- clockwise order. Hence, we reverse the sequence of points we+ -- obtain to get the points/vertices in CCW order, so that we can+ -- construct a simplepolygon out of them. --- | Constructs the boundary of the given face+-- | Constructs the boundary of the given face. -- -- \(O(k)\), where \(k\) is the complexity of the face-rawFacePolygon :: FaceId' s -> PlanarSubdivision s v e f r- -> SomePolygon v r :+ f-rawFacePolygon i ps = case F.toList $ holesOf i ps of+internalFacePolygon :: FaceId' s -> PlanarSubdivision s v e f r+ -> SomePolygon v r :+ f+internalFacePolygon i ps = case F.toList $ holesOf i ps of [] -> Left res :+ x- hs -> Right (MultiPolygon vs $ map toHole hs) :+ x+ hs -> Right (MultiPolygon res $ map toHole hs) :+ x where- res@(SimplePolygon vs) :+ x = rawFaceBoundary i ps- toHole d = (rawFaceBoundary (leftFace d ps) ps)^.core+ res :+ x = faceBoundary i ps+ toHole d = faceBoundary (leftFace d ps) ps ^. core+-- TODO: Verify that holes are in the right orientation. --- | Lists all *internal* faces of the planar subdivision.-rawFacePolygons :: PlanarSubdivision s v e f r- -> V.Vector (FaceId' s, SomePolygon v r :+ f)-rawFacePolygons ps = fmap (\(i,_) -> (i,rawFacePolygon i ps)) . internalFaces $ ps +-- | Returns a sufficiently large, rectangular, polygon that contains+-- the entire planar subdivision. Each component corresponds to a hole+-- in this polygon.+outerFacePolygon :: (Num r, Ord r)+ => PlanarSubdivision s v e f r -> MultiPolygon (Maybe v) r :+ f+outerFacePolygon ps = outerFacePolygon' outer ps & core %~ first (either (const Nothing) Just)+ where+ outer = rectToPolygon . grow 1 . boundingBox $ ps+ rectToPolygon = unsafeFromPoints . reverse . F.toList . corners +-- | Given a sufficiently large outer boundary, draw the outerface as+-- a polygon with a hole.+outerFacePolygon' :: SimplePolygon v' r+ -> PlanarSubdivision s v e f r -> MultiPolygon (Either v' v) r :+ f+outerFacePolygon' outer ps = MultiPolygon (first Left outer) holePgs :+ ps^.dataOf i+ where+ i = outerFaceId ps+ holePgs = map getBoundary . F.toList $ holesOf i ps+ -- get the bondary of a hole. Note that for holes, the function+ -- 'boundary' promisses to report the darts, and therefore the+ -- vertices in CCW order. Hence, we can directly construct a SimplePolygon out of it.+ getBoundary d = unsafeFromPoints . fmap (second Right) $ faceBoundary' (twin d)+ faceBoundary' d = (\d' -> PG.vtxDataToExt $ ps^.vertexDataOf (headOf d' ps))+ <$> V.toList (boundary' d ps) +-- | Procuces a polygon for each *internal* face of the planar+-- subdivision.+internalFacePolygons :: PlanarSubdivision s v e f r+ -> V.Vector (FaceId' s, SomePolygon v r :+ f)+internalFacePolygons ps = fmap (\(i,_) -> (i,internalFacePolygon i ps)) . internalFaces $ ps+++-- | Procuces a polygon for each face of the planar subdivision.+facePolygons :: (Num r, Ord r)+ => PlanarSubdivision s v e f r+ -> V.Vector (FaceId' s, SomePolygon (Maybe v) r :+ f)+facePolygons ps = V.cons (outerFaceId ps, first Right $ outerFacePolygon ps) ifs+ where+ ifs = wrapJust <$> internalFacePolygons ps+ g :: Bifunctor g => g a b -> g (Maybe a) b+ g = first Just++ wrapJust :: (FaceId' s, SomePolygon v r :+ f)+ -> (FaceId' s, SomePolygon (Maybe v) r :+ f)+ wrapJust (i,(spg :+ f)) = (i,bimap g g spg :+ f)++++-- | Mapping between the internal and extenral darts dartMapping :: PlanarSubdivision s v e f r -> V.Vector (Dart (Wrap s), Dart s) dartMapping ps = ps^.component (ComponentId 0).PG.dartData @@ -674,3 +825,63 @@ -- $ trianglePG -- trianglePG = fromPoints . map ext $ [origin, Point2 10 0, Point2 10 10]+++++++++++++++--------------------------------------------------------------------------------+++-- | A class for describing which features (vertex, edge, face) of a planar subdivision+-- can be incident to each other.+class Incident s a b where+ incidences :: PlanarSubdivision s v e f r -> a -> [b]++instance Incident s (VertexId' s) (Dart s) where+ incidences psd i = V.toList (incidentEdges i psd) ++ map twin (V.toList $ incidentEdges i psd)++instance Incident s (VertexId' s) (FaceId' s) where+ incidences psd i = map ((flip leftFace) psd) $ V.toList $ incidentEdges i psd++instance Incident s (Dart s) (VertexId' s) where+ incidences psd i = [headOf i psd, tailOf i psd]++instance Incident s (Dart s) (FaceId' s) where+ incidences psd i = [leftFace i psd, rightFace i psd]++instance Incident s (FaceId' s) (VertexId' s) where+ incidences psd i = V.toList $ boundaryVertices i psd++instance Incident s (FaceId' s) (Dart s) where+ incidences psd i = V.toList (outerBoundaryDarts i psd) ++ map twin (V.toList $ outerBoundaryDarts i psd)++-- | Given two features (vertex, edge, or face) of a subdivision,+-- report all features of a given type that are incident to both.+common :: (Incident s a c, Incident s b c, Ord c) => PlanarSubdivision s v e f r -> a -> b -> [c]+common psd a b = Set.toList $ Set.intersection (Set.fromList $ incidences psd a) (Set.fromList $ incidences psd b)++-- | Given two features (edge or face) of a subdivision, report all+-- vertices that are incident to both.+commonVertices :: (Incident s a (VertexId' s), Incident s b (VertexId' s)) => PlanarSubdivision s v e f r -> a -> b -> [VertexId' s]+commonVertices = common++-- | Given two features (vertex or face) of a subdivision, report all+-- edges that are incident to both. Returns both darts of each+-- qualifying edge.+commonDarts :: (Incident s a (Dart s), Incident s b (Dart s)) => PlanarSubdivision s v e f r -> a -> b -> [Dart s]+commonDarts = common++-- | Given two features (vertex or edge) of a subdivision, report all+-- faces that are incident to both.+commonFaces :: (Incident s a (FaceId' s), Incident s b (FaceId' s)) => PlanarSubdivision s v e f r -> a -> b -> [FaceId' s]+commonFaces = common
+ src/Data/Geometry/PlanarSubdivision/Dynamic.hs view
@@ -0,0 +1,526 @@+module Data.Geometry.PlanarSubdivision.Dynamic+ ( splitEdge, unSplitEdge+ , sproutIntoFace+ , splitFace+ ) where++import Control.Lens+import Data.Ext+import Data.Functor.Identity+import Data.Geometry hiding (Vector, head, imap)+import Data.Geometry.PlanarSubdivision+import Data.Geometry.PlanarSubdivision.Basic+import Data.Geometry.PlanarSubdivision.Raw+import Data.List (sort, sortOn, findIndex)+import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.List.NonEmpty as NonEmpty+import Data.PlanarGraph (Dart (Dart), Arc (Arc), VertexId (VertexId), FaceId (FaceId), Direction (Positive, Negative))+import Data.PlaneGraph (PlaneGraph)+import qualified Data.PlaneGraph as PG+import qualified Data.PlaneGraph.AdjRep as AR (id, vData, fData, faces, Face (..))+import Data.PlaneGraph.AdjRep hiding (id, vData, faces)+import Data.Vector (Vector, toList, (//), empty)+import qualified Data.Vector as V++import Debug.Trace+++tracingOn = False++tr :: Show a => String -> a -> a+tr s a | tracingOn = trace ("\9608 " ++ s ++ ": " ++ show a) a+ | otherwise = a+++-- TO DO:+-- ADD EDGE JOINING TWO COMPONENTS+-- CREATE NEW COMPONENT (SINGLE VERTEX)+-- DELETIONS+++-- | Splits a given edge of a planar subdivision by inserting a new vertex on the edges.+-- Increases #vertices and #edges by 1.+splitEdge+ :: (Show v, Show e, Show f, Show r)+ => VertexId' s+ -> VertexId' s+ -> Point 2 r+ -> v+ -> (e -> (e, e))+ -> PlanarSubdivision s v e f r+ -> PlanarSubdivision s v e f r++splitEdge a b p v f d =+ let (_, la, _) = asLocalV a d+ (_, lb, _) = asLocalV b d+ v' = (freeVertexId d, v)+ fd = freeDart d+ f' (Dart i Positive, e) = ((Dart i Positive, fst $ f e), (fd, snd $ f e))+ f' (Dart i Negative, e) = ((twin fd, fst $ f e), (Dart i Negative, snd $ f e))+ in tr "splitEdge" $ d & components' %~ fmap (splitEdgeInPlaneGraph la lb p v' f')++-- | Sprouts a new edge from a given vertex into the interior of a given (incident) face.+-- Increases #vertices and #edges by 1.+sproutIntoFace+ :: (Show v, Show e, Show f, Show r)+ => VertexId' s+ -> FaceId' s+ -> Point 2 r+ -> v+ -> (e, e)+ -> PlanarSubdivision s v e f r+ -> PlanarSubdivision s v e f r++sproutIntoFace a f p v (e1, e2) d =+ let [ea] = tr "[ea]" $ filter (\e -> headOf e d == a && leftFace e d == f) $ commonDarts d a f+ (_, la, _) = asLocalV a d+ (_, lc, _) = asLocalV (tailOf ea d) d+ v' = (freeVertexId d, v)+ fd = freeDart d+ e1' = (fd, e1)+ e2' = (twin fd, e2)+ in tr "sproutIntoFace" $ d & components' %~ fmap (sproutIntoFaceInPlaneGraph la lc p v' (e1', e2'))++-- | Inserts a new edge between two given vertices, adjacent to a common face.+-- Increases #edges and #faces by 1.+splitFace+ :: (Show v, Show e, Show f, Show r)+ => VertexId' s+ -> VertexId' s+ -> (e, e)+ -> (f -> (f, f))+ -> PlanarSubdivision s v e f r+ -> PlanarSubdivision s v e f r++splitFace a b e g d =+ let (ca, _, _) = asLocalV a d+ (cb, _, _) = asLocalV b d+ in if ca == cb then splitFaceSameComponent a b e g d+ else splitFaceDifferentComponents a b e g d++splitFaceSameComponent a b e g d =+ let fs = commonFaces d a b+ f | length fs == 1 = tr "f(a)" $ headTrace "splitFaceSameComponent f" fs+ | otherwise = tr "f(b)" $ headTrace "splitFaceSameComponent f" $ filter (not . isOuterFace) fs+ [ea] = tr "[ea]" $ filter (\e -> headOf e d == a && leftFace e d == f) $ commonDarts d a f+ [eb] = tr "[eb]" $ filter (\e -> headOf e d == b && leftFace e d == f) $ commonDarts d b f+ (_, la, _) = asLocalV a d+ (_, lb, _) = asLocalV b d+ (_, lc, _) = asLocalV (tailOf ea d) d+ (_, ld, _) = asLocalV (tailOf eb d) d+ (_, lf, _) :| [] = asLocalF f d+ fd = freeDart d+ e' = ((fd, fst e), (twin fd, snd e))+ tf = freeFaceId d+ g' (ef, x) = ((ef, fst $ g x), (tf, snd $ g x))+ in tr "splitFaceSameComponent" $ d & components' %~ fmap (splitFaceInPlaneGraph (tr "la" la) (tr "lb" lb) (tr "lc" lc) (tr "ld" ld) (tr "lf" lf) e' g')++splitFaceDifferentComponents = undefined+++-- | Splits a given edge of a planar subdivision by inserting a new vertex on the edges.+-- Increases #vertices and #edges by 1.+unSplitEdge+ :: (Show v, Show e, Show f, Show r)+ => VertexId' s+ -> ((e, e) -> e)+ -> PlanarSubdivision s v e f r+ -> PlanarSubdivision s v e f r++unSplitEdge b f d =+ let [a, c] = tr "[a, c]" $ toList $ neighboursOf b d+ (_, la, _) = asLocalV a d+ (_, lb, _) = asLocalV b d+ (_, lc, _) = asLocalV c d+ [dab] = filter (\e -> tailOf e d == a) $ commonDarts d a b+ [dcb] = filter (\e -> tailOf e d == c) $ commonDarts d b c+ f' ((di, ei), (dj, ej)) | di == dab = ( dab, f (ei, ej))+ | di == dcb = (twin dab, f (ei, ej))+ | otherwise = error "you shouldn't call f' on any other dart"+ -- no longer used: vertex id b and dart id dcb+ in tr "unSplitEdge" $ d & components' %~ fmap (unSplitEdgeInPlaneGraph la lb lc f')+-- globally, need to restore VertexId and DartIds ???++++++-- nodig:++freeVertexId :: PlanarSubdivision s v e f r -> VertexId' s+freeDart :: PlanarSubdivision s v e f r -> Dart s+freeFaceId :: PlanarSubdivision s v e f r -> FaceId' s++freeVertexId = VertexId . numVertices+freeDart = flip Dart Positive . Arc . numEdges+freeFaceId = FaceId . VertexId . numFaces++components' :: (Show v, Show e, Show f, Show r) => Lens' (PlanarSubdivision s v e f r) (Vector (Component' s v e f r))+type Component' s v e f r = PlaneGraph (Wrap s) (VertexId' s, v) (Dart s, e) (FaceId' s, f) r+components' = lens getComponents' setComponents'++getComponents' :: PlanarSubdivision s v e f r -> Vector (Component' s v e f r)+getComponents' p = fmap (addExtraData p) $ p ^. components++addExtraData :: PlanarSubdivision s v e f r -> Component s r -> Component' s v e f r+addExtraData p c = c & PG.vertexData . traverse %~ (\i -> (i, p ^. dataOf i))+ & PG.rawDartData . traverse %~ (\i -> (i, p ^. dataOf i))+ & PG.faceData . traverse %~ (\i -> (i, p ^. dataOf i))++setComponents' :: (Show v, Show e, Show f, Show r) => PlanarSubdivision s v e f r -> Vector (Component' s v e f r) -> PlanarSubdivision s v e f r+setComponents' p cs = p & components .~ fmap remExtraData cs+ & rawVertexData .~ (tr "rawVertexData" . vectorise $ getRawVertexData cs)+ & rawDartData .~ (tr "rawDartData" . vectorise $ getRawEdgeData cs)+ & rawFaceData .~ (tr "rawFaceData" . vectorise $ getRawFaceData cs)++getRawVertexData :: Vector (Component' s v e f r)+ -> [(VertexId' s, Raw s (VertexId' (Wrap s)) v)]+getRawVertexData = concat . imap (\ci g -> map (\(li, VertexData _ (gi, v)) -> (gi, Raw (toEnum ci) li v)) $ toList $ PG.vertices g) . toList++--getEdgeData :: Vector (Component' s v e f r) -> [(Dart s, (Dart s, e))]+--getEdgeData = map (\(a, b) -> (a, (a, b))) . concatMap (toList . (^. PG.rawDartData)) . toList++getRawEdgeData :: Vector (Component' s v e f r)+ -> [(Dart s, Raw s (Dart (Wrap s)) e)]+getRawEdgeData = concat . imap (\ci g -> map (\(li, (gi, e)) -> (gi, Raw (toEnum ci) li e)) $ toList $ PG.darts g) . toList+++--getFaceData :: Vector (Component' s v e f r) -> [(FaceId' s, f)]+--getFaceData = concatMap (toList . (^. PG.faceData)) . toList+++-- data RawFace s f+-- _faceIdx :: !(Maybe (ComponentId s, FaceId' (Wrap s)))+-- _faceDataVal :: !(FaceData (Dart s) f)++-- | Something in this implementation is not right. It makes asLocalF produce an error.+getRawFaceData :: Vector (Component' s v e f r)+ -> [(FaceId' s, RawFace s f)]+getRawFaceData = concat . imap (\ci g -> map (bla ci) $ toList $ PG.faces g) . toList+ where+ bla ci (li, (gi, f)) | isOuterFace gi = (gi, RawFace Nothing (FaceData Empty f))+ | otherwise = (gi, RawFace (Just (toEnum ci, li)) (FaceData Empty f))+-- holes are always empty! (where to get them from?)++isOuterFace :: FaceId' s -> Bool+isOuterFace i = fromEnum i == 0++remExtraData :: Component' s v e f r -> Component s r+remExtraData c = c & PG.vertexData . traverse %~ fst+ & PG.rawDartData . traverse %~ fst+ & PG.faceData . traverse %~ fst+++vectorise :: (Enum i, Show i) => [(i, a)] -> Vector a+vectorise vs = V.replicate (length vs) undefined // map (\(i, a) -> (fromEnum i, a)) vs+++++------------------+-- PLANE GRAPHS --+------------------+++-- INSERTIONS --+++splitEdgeInPlaneGraph+ :: (Show v, Show e, Show f, Show r)+ => VertexId' s+ -> VertexId' s+ -> Point 2 r+ -> v+ -> (e -> (e, e))+ -> PlaneGraph s v e f r+ -> PlaneGraph s v e f r+-- LET OP! TEST OF a EN b WEL VOORKOMEN!+splitEdgeInPlaneGraph a b p v f+ = tr "splitEdgeInPlaneGraph"+ . PG.fromAdjRep+ . splitEdgeInAdjRep (fromEnum a) (fromEnum b) p v f+ . PG.toAdjRep++sproutIntoFaceInPlaneGraph+ :: (Show v, Show e, Show f, Show r)+ => VertexId' s+ -> VertexId' s+ -> Point 2 r+ -> v+ -> (e, e)+ -> PlaneGraph s v e f r+ -> PlaneGraph s v e f r+sproutIntoFaceInPlaneGraph a c p v e g =+ let ai = fromEnum a+ ci = fromEnum c+ in tr "splitEdgeInPlaneGraph"+ $ PG.fromAdjRep+ $ sproutInAdjRep ai ci p v e+ $ PG.toAdjRep g+++-- PG.toAdjRep :: PlaneGraph s v e f r -> Gr (Vtx v e r) (Face f)+-- PG.fromAdjRep :: proxy s -> Gr (Vtx v e r) (Face f) -> PlaneGraph s v e f r+++splitFaceInPlaneGraph+ :: (Show v, Show e, Show f, Show r)+ => VertexId' s -- index van vertex a+ -> VertexId' s -- index van vertex b+ -> VertexId' s -- index van vertex c+ -> VertexId' s -- index van vertex d+ -> FaceId' s -- index van te splitsen face+ -> (e, e) -- extra data voor nieuwe edge ab+ -> (f -> (f, f)) -- functie om face data in twee stukken te knippen+ -> PlaneGraph s v e f r -- input graaf+ -> PlaneGraph s v e f r -- output graaf++splitFaceInPlaneGraph a b c d f e h g =+ let ai = fromEnum a+ bi = fromEnum b+ ci = fromEnum c+ di = fromEnum d+ fi = fromEnum $ tr "fi" $ traceShow (g ^. dataOf f) $ PG.tailOf (PG.boundaryDart f g) g+ fj = fromEnum $ tr "fj" $ PG.headOf (PG.boundaryDart f g) g+ -- ^ boundaryDart seems not working either+ in tr "splitFaceInPlaneGraph"+ $ PG.fromAdjRep+ $ splitFaceInAdjRep ai bi ci di fi fj e h+ $ PG.toAdjRep g+++-- DELETIONS --+++unSplitEdgeInPlaneGraph+ :: (Show v, Show e, Show f, Show r)+ => VertexId' s+ -> VertexId' s+ -> VertexId' s+ -> ((e, e) -> e)+ -> PlaneGraph s v e f r+ -> PlaneGraph s v e f r++unSplitEdgeInPlaneGraph a b c f+ = tr "unSplitEdgeInPlaneGraph"+ . PG.fromAdjRep+ . unSplitEdgeInAdjRep (fromEnum a) (fromEnum b) (fromEnum c) f+ . PG.toAdjRep+++-------------+-- ADJREPS --+-------------++-- Gr+-- adjacencies :: [v]+-- faces :: [f]++-- Vtx+-- id :: Int+-- loc :: Point 2 r+-- adj :: [(Int, e)]+-- vData :: v++-- Face+-- incidentEdge :: (Int, Int)+-- fData :: f++--deriving instance (Show v, Show f) => Show (Gr v f)+--deriving instance (Show v, Show e, Show r) => Show (Vtx v e r)+--deriving instance Show f => Show (Face f)+++-- instance {-# OVERLAPS #-} Show (VertexId s Primal) where show i = 'v' : show (fromEnum i)+-- instance {-# OVERLAPS #-} Show (FaceId s Primal) where show i = 'f' : show (fromEnum i)+-- instance {-# OVERLAPS #-} Show (Dart s, v) where+-- show (Dart (Arc s) Positive, _) = 'd' : show (fromEnum s) ++ "+"+-- show (Dart (Arc s) Negative, _) = 'd' : show (fromEnum s) ++ "-"++-- instance Show f => Show (Face f) where show f = (show $ AR.fData f) ++ "~>" ++ (show $ incidentEdge f)+-- instance (Show e, Show r) => Show (Vtx v e r) where show v = (show $ AR.id v) ++ "~>" ++ (show $ adj v)+-- instance (Show v, Show f) => Show (Gr v f) where show g = "Gr " ++ (show $ adjacencies g) ++ " " ++ (show $ AR.faces g)++-- ik heb:+splitEdgeInAdjRep+ :: (Show v, Show e, Show f, Show r)+ => Int -- index van vertex a+ -> Int -- index van vertex b+ -> Point 2 r -- locatie voor nieuwe vertex c+ -> v -- extra data voor vertex c+ -> (e -> (e, e)) -- functie om edge data in twee stukken te knippen+ -> Gr (Vtx v e r) (Face f) -- input graaf+ -> Gr (Vtx v e r) (Face f) -- output graaf++splitEdgeInAdjRep a b p v f g =+ let n = length $ adjacencies g+ -- first find vertices a and b+ oa = headTrace "splitEdgeInAdjRep oa" $ filter ((== a) . AR.id) $ adjacencies g+ ob = headTrace "splitEdgeInAdjRep ob" $ filter ((== b) . AR.id) $ adjacencies g+ os = filter ((lift (&&) (/= a) (/= b)) . AR.id) $ adjacencies g+ -- find edge data+ e1 = snd $ headTrace "splitEdgeInAdjRep e1" $ filter ((== b) . fst) $ adj oa+ e2 = snd $ headTrace "splitEdgeInAdjRep e2" $ filter ((== a) . fst) $ adj ob+ -- create new adjacencies to c in a and b+ na = oa {adj = replace ((== b) . fst) (const (n, fst $ f e1)) $ adj oa}+ nb = ob {adj = replace ((== a) . fst) (const (n, fst $ f e2)) $ adj ob}+ -- create new vertex c+ nc = Vtx {AR.id = n, loc = p, adj = [(a, snd $ f e2), (b, snd $ f e1)], AR.vData = v}+ -- update faces (only if incidentEdge happens to point to ab)+ nf = replace ((== (a, b)) . incidentEdge) (\f -> f {incidentEdge = (a, n)})+ $ replace ((== (b, a)) . incidentEdge) (\f -> f {incidentEdge = (b, n)})+ $ AR.faces g+ in tr "splitEdgeInAdjRep" $ (tr "original" g) {adjacencies = sortOn AR.id $ na : nb : nc : os, AR.faces = nf}+++sproutInAdjRep+ :: (Show v, Show e, Show f, Show r)+ => Int -- index van vertex a+ -> Int -- index van vertex c (andere kant van edge a)+ -> Point 2 r -- locatie voor nieuwe vertex c+ -> v -- extra data voor vertex c+ -> (e, e) -- extra data voor nieuwe edge+ -> Gr (Vtx v e r) (Face f) -- input graaf+ -> Gr (Vtx v e r) (Face f) -- output graaf++sproutInAdjRep a c p v e g =+ let n = length $ adjacencies g+ -- first find vertex a+ oa = tr "oa" $ headTrace "sproutInAdjRep oa" $ filter ((== a) . AR.id) $ adjacencies g+ os = tr "os" $ filter ((/= a) . AR.id) $ adjacencies g+ -- need to find index of c+ fj (Just x) = x+ fj Nothing = error "splitFaceInAdjRep got Nothing"+ ci = tr "ci" $ fj $ findIndex ((== c) . fst) $ adj oa+ -- create new adjacency to new vertex z in a+ na = tr "na" $ oa {adj = take ci (adj oa) ++ (n, fst e) : drop ci (adj oa)}+ -- create new vertex z+ nz = Vtx {AR.id = n, loc = p, adj = [(a, snd e)], AR.vData = v}+ in tr "splitFaceInAdjRep" $ (tr "original" g) {adjacencies = sortOn AR.id $ na : nz : os}++splitFaceInAdjRep+ :: (Show v, Show e, Show f, Show r)+ => Int -- index van vertex a+ -> Int -- index van vertex b+ -> Int -- index van vertex c (andere kant van edge a)+ -> Int -- index van vertex d (andere kant van edge b)+ -> Int -- index van face edge start+ -> Int -- index van face edge eind+ -> (e, e) -- extra data voor nieuwe edge ab+ -> (f -> (f, f)) -- functie om face data in twee stukken te knippen+ -> Gr (Vtx v e r) (Face f) -- input graaf+ -> Gr (Vtx v e r) (Face f) -- output graaf++-- is it easier to split a vertex than a face?++splitFaceInAdjRep a b c d u v e f g =+ let+ -- first find vertices a and b+ oa = tr "oa" $ headTrace "splitFaceInAdjRep oa" $ filter ((== a) . AR.id) $ adjacencies g+ ob = tr "ob" $ headTrace "splitFaceInAdjRep ob" $ filter ((== b) . AR.id) $ adjacencies g+ os = tr "os" $ filter ((lift (&&) (/= a) (/= b)) . AR.id) $ adjacencies g+ -- insert new adjacency between a and b+ fj (Just x) = x+ fj Nothing = error "splitFaceInAdjRep got Nothing"+ -- need to find indices c and d!+ ci = tr "ci" $ fj $ findIndex ((== c) . fst) $ adj oa+ di = tr "di" $ fj $ findIndex ((== d) . fst) $ adj ob+ -- insert new adjacencies to each other in a and b+ na = tr "na" $ oa {adj = take ci (adj oa) ++ (b, fst e) : drop ci (adj oa)}+ nb = tr "nb" $ ob {adj = take di (adj ob) ++ (a, snd e) : drop di (adj ob)}+ -- find the face that is incident to both a and b+ i = tr "i" $ fj $ findIndex ((== (u, v)) . incidentEdge) $ AR.faces g+ fd = tr "fd" $ AR.fData $ AR.faces g !! i+ ef = tr "ef" $ take i (AR.faces g) ++ drop (i + 1) (AR.faces g)+ f1 = tr "f1" $ AR.Face {incidentEdge = (a, b), AR.fData = fst $ f fd}+ f2 = tr "f2" $ AR.Face {incidentEdge = (b, a), AR.fData = snd $ f fd}+ in tr "splitFaceInAdjRep" $ (tr "original" g) {adjacencies = sortOn AR.id $ na : nb : os, AR.faces = ef ++ [f1, f2]}++++++unSplitEdgeInAdjRep+ :: (Show v, Show e, Show f, Show r)+ => Int -- index van vertex a+ -> Int -- index van vertex b (te verwijderen)+ -> Int -- index van vertex c+ -> ((e, e) -> e) -- functie om edge data te mergen+ -> Gr (Vtx v e r) (Face f) -- input graaf+ -> Gr (Vtx v e r) (Face f) -- output graaf++unSplitEdgeInAdjRep a b c f g =+ let n = length $ adjacencies g+ -- first find vertices a, b and c+ oa = head $ filter ((== a) . AR.id) $ adjacencies g+ ob = head $ filter ((== b) . AR.id) $ adjacencies g+ oc = head $ filter ((== c) . AR.id) $ adjacencies g+ os = filter ((\i -> i /= a && i /= b && i /= c) . AR.id) $ adjacencies g+ -- find edge data+ eab = snd $ head $ filter ((== b) . fst) $ adj oa+ eba = snd $ head $ filter ((== a) . fst) $ adj ob+ ebc = snd $ head $ filter ((== c) . fst) $ adj ob+ ecb = snd $ head $ filter ((== b) . fst) $ adj oc+ -- create new adjacencies between a and c+ na = oa {adj = replace ((== b) . fst) (const (c, f (eab, ebc))) $ adj oa}+ nc = oc {adj = replace ((== b) . fst) (const (a, f (ecb, eba))) $ adj oc}+ nv = sortOn AR.id $ na : nc : os+ -- update faces (only if incidentEdge happens to point to ab or bc)+ nf = replace ((== (a, b)) . incidentEdge) (\f -> f {incidentEdge = (a, c)})+ $ replace ((== (b, a)) . incidentEdge) (\f -> f {incidentEdge = (c, a)})+ $ replace ((== (b, c)) . incidentEdge) (\f -> f {incidentEdge = (a, c)})+ $ replace ((== (c, b)) . incidentEdge) (\f -> f {incidentEdge = (c, a)})+ $ AR.faces g+ -- restore consecutive numbering: replace vertex n-1 by b+ ng = replaceIndex (n - 1) b $ (tr "original" g) {adjacencies = nv, AR.faces = nf}+ in tr "unSplitEdgeInAdjRep" $ ng++-- Gr+-- adjacencies :: [v]+-- faces :: [f]++-- Vtx+-- id :: Int+-- loc :: Point 2 r+-- adj :: [(Int, e)]+-- vData :: v++-- Face+-- incidentEdge :: (Int, Int)+-- fData :: f++replaceIndex :: Int -> Int -> Gr (Vtx v e r) (Face f) -> Gr (Vtx v e r) (Face f)+replaceIndex i j g = g { adjacencies = map (replaceIndexAdjacency i j) $ adjacencies g+ , AR.faces = map (replaceIndexFace i j) $ AR.faces g+ }++replaceIndexAdjacency :: Int -> Int -> Vtx v e r -> Vtx v e r+replaceIndexAdjacency i j v = v { AR.id = if AR.id v == i then j else AR.id v+ , adj = replace ((== i) . fst) (set _1 j) $ adj v+ }++replaceIndexFace :: Int -> Int -> Face f -> Face f+replaceIndexFace i j f | fst (incidentEdge f) == i = f {incidentEdge = incidentEdge f & set _1 j}+ | snd (incidentEdge f) == i = f {incidentEdge = incidentEdge f & set _2 j}+ | otherwise = f+++-------------+-- HELPERS --+-------------++replace :: (a -> Bool) -> (a -> a) -> [a] -> [a]+replace f g = map $ replace' f g++replace' :: (a -> Bool) -> (a -> a) -> a -> a+replace' f g x | f x = g x+ | otherwise = x++lift :: (a -> b -> c) -> (d -> a) -> (d -> b) -> d -> c+lift f g h x = f (g x) (h x)++++headTrace :: String -> [a] -> a+headTrace s xs | null xs = error $ s ++ ": head of empty list"+ | otherwise = head xs
src/Data/Geometry/PlanarSubdivision/Merge.hs view
@@ -23,9 +23,6 @@ import Data.Geometry.Point import Data.Geometry.Polygon import Data.PlanarGraph.Dart-import Data.PlaneGraph ( Dart, VertexId(..), FaceId(..)- , VertexId', FaceId'- ) import qualified Data.PlaneGraph as PG import Data.Semigroup.Foldable import qualified Data.Vector as V@@ -214,35 +211,38 @@ -------------------------------------------------------------------------------- -data Test = Test-data Id a = Id a-+data Test triangle1 :: PlanarSubdivision Test () () Int Rational-triangle1 = (\pg -> fromSimplePolygon (Id Test) pg 1 0)- $ trianglePG1+triangle1 = (\pg -> fromSimplePolygon @Test pg 1 0)+ trianglePG1+trianglePG1 :: SimplePolygon () Rational trianglePG1 = fromPoints . map ext $ [origin, Point2 200 0, Point2 200 200] triangle2 :: PlanarSubdivision Test () () Int Rational-triangle2 = (\pg -> fromSimplePolygon (Id Test) pg 2 0)- $ trianglePG2+triangle2 = (\pg -> fromSimplePolygon @Test pg 2 0)+ trianglePG2+trianglePG2 :: SimplePolygon () Rational trianglePG2 = fromPoints . map ext $ [Point2 0 30, Point2 10 30, Point2 10 40] triangle4 :: PlanarSubdivision Test () () Int Rational-triangle4 = (\pg -> fromSimplePolygon (Id Test) pg 1 0)- $ trianglePG4+triangle4 = (\pg -> fromSimplePolygon @Test pg 1 0)+ trianglePG4+trianglePG4 :: SimplePolygon () Rational trianglePG4 = fromPoints . map ext $ [Point2 400 400, Point2 600 400, Point2 600 600] triangle3 :: PlanarSubdivision Test () () Int Rational-triangle3 = (\pg -> fromSimplePolygon (Id Test) pg 3 0)- $ trianglePG3+triangle3 = (\pg -> fromSimplePolygon @Test pg 3 0)+ trianglePG3+trianglePG3 :: SimplePolygon () Rational trianglePG3 = fromPoints . map ext $ [Point2 401 530, Point2 410 530, Point2 410 540] -myPS = embedAsHoleIn triangle2 const (mkFI 1) triangle1+_myPS :: PlanarSubdivision Test () () Int Rational+_myPS = embedAsHoleIn triangle2 const (mkFI 1) triangle1 `merge` embedAsHoleIn triangle3 const (mkFI 1) triangle4
src/Data/Geometry/PlanarSubdivision/Raw.hs view
@@ -39,6 +39,14 @@ instance (ToJSON ia, ToJSON a) => ToJSON (Raw s ia a) where toEncoding = genericToEncoding defaultOptions +instance FunctorWithIndex i (Raw ci i) where+ imap f (Raw ci i x) = Raw ci i (f i x)+instance FoldableWithIndex i (Raw ci i) where+ ifoldMap f (Raw _ i x) = f i x+instance TraversableWithIndex i (Raw ci i) where+ itraverse f (Raw ci i x) = Raw ci i <$> f i x++ -- | get the dataVal of a Raw dataVal :: Lens (Raw s ia a) (Raw s ia b) a b dataVal = lens (\(Raw _ _ x) -> x) (\(Raw c i _) y -> Raw c i y)@@ -46,7 +54,7 @@ -------------------------------------------------------------------------------- -- | The Face data consists of the data itself and a list of holes-data FaceData h f = FaceData { _holes :: (Seq.Seq h)+data FaceData h f = FaceData { _holes :: Seq.Seq h , _fData :: !f } deriving (Show,Eq,Ord,Functor,Foldable,Traversable,Generic) makeLenses ''FaceData
+ src/Data/Geometry/PlanarSubdivision/TreeRep.hs view
@@ -0,0 +1,110 @@+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.PlanarSubdivision.TreeRep+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--+-- Data types that help encode/decode a planegraph as a JSON/YAML file.+--+--------------------------------------------------------------------------------+module Data.Geometry.PlanarSubdivision.TreeRep( PlanarSD(..)+ , Vtx(..)+ , myTreeRep+ ) where++-- FIXME; uncomment myTreeRep++import Data.Aeson+import Data.PlaneGraph.AdjRep (Vtx(..))+import GHC.Generics (Generic)++import Data.Geometry.Point+import Data.RealNumber.Rational++--------------------------------------------------------------------------------++++-- | Specify the planar subdivison as a tree of components+data PlanarSD v e f r = PlanarSD+ { outerFace :: f -- ^ outer face+ , inner :: InnerSD v e f r+ } deriving (Show,Eq,Functor,Generic)++instance (ToJSON r, ToJSON v, ToJSON e, ToJSON f) => ToJSON (PlanarSD v e f r) where+ toEncoding = genericToEncoding defaultOptions+instance (FromJSON r, FromJSON v, FromJSON e, FromJSON f) => FromJSON (PlanarSD v e f r)+++data InnerSD v e f r = InnerSD+ { adjs :: [Vtx v e r] -- ^ list of vertices and edges in the+ -- components incident to the outer+ -- face+ , faces :: [(f, [InnerSD v e f r])] -- ^ for each internal+ -- face in the component described by adjs its data,+ -- and possible holes+ } deriving (Show,Eq,Functor,Generic)++instance (ToJSON r, ToJSON v, ToJSON e, ToJSON f) => ToJSON (InnerSD v e r f) where+ toEncoding = genericToEncoding defaultOptions+instance (FromJSON r, FromJSON v, FromJSON e, FromJSON f) => FromJSON (InnerSD v e r f)++++--------------------------------------------------------------------------------++-- | This represents the following Planar subdivision. Note that the+-- graph is undirected, the arrows are just to indicate what the+-- Positive direction of the darts is.+--+-- +myTreeRep :: PlanarSD Int () String (RealNumber 3)+myTreeRep = PlanarSD "f_infty" (InnerSD ads fs)+ where+ fs = [ ("f_1", [])+ , ("f_2", [f5, f6])+ , ("f_3", [])+ , ("f_4", [f7])+ ]++ f5 = InnerSD [ vtx 16 (Point2 3 8) [e 17, e 18]+ , vtx 17 (Point2 0 7) [e 16, e 18]+ , vtx 18 (Point2 (-1) 4) [e 16, e 17]+ ] [("f_5",[])]++ f6 = InnerSD [ vtx 15 (Point2 3 3) [e 14, e 13]+ , vtx 13 (Point2 6 4) [e 14, e 15]+ , vtx 14 (Point2 3 6) [e 13, e 15]+ ] [("f_6",[])]++ f7 = InnerSD [ vtx 19 (Point2 0 9) [e 20, e 23]+ , vtx 20 (Point2 0 4) [e 19, e 21]+ , vtx 21 (Point2 15 2) [e 20, e 22]+ , vtx 22 (Point2 17 5) [e 21, e 23]+ , vtx 23 (Point2 15 8) [e 19, e 22]+ ] [("f_7",[f8])]++ f8 = InnerSD [ vtx 24 (Point2 14 6) [e 25, e 26]+ , vtx 25 (Point2 13 8) [e 24, e 26]+ , vtx 26 (Point2 12 5) [e 24, e 25]+ ] [("f_8",[])]++ ads = [ vtx 0 (Point2 0 0) [e 1, e 4]+ , vtx 1 (Point2 10 2) [e 0, e 5]+ , vtx 2 (Point2 9 9) [e 1, e 7, e 3]+ , vtx 3 (Point2 0 10) [e 2, e 4]+ , vtx 4 (Point2 (-4) 5) [e 0, e 3]+ , vtx 5 (Point2 15 3) [e 1, e 6]+ , vtx 6 (Point2 20 6) [e 5, e 7]+ , vtx 7 (Point2 10 14) [e 2, e 6, e 8]+ , vtx 8 (Point2 4 13) [e 7, e 3]+ , vtx 9 (Point2 4 (-4)) [e 10, e 11]+ , vtx 10 (Point2 8 (-4)) [e 11, e 9]+ , vtx 11 (Point2 11 (-2)) [e 10, e 12]+ , vtx 12 (Point2 7 (-1)) [e 9, e 11]+ ]++ e i = (i,())++ vtx i p as = Vtx i p as i
src/Data/Geometry/Point.hs view
@@ -10,418 +10,60 @@ -- \(d\)-dimensional points. -- ---------------------------------------------------------------------------------module Data.Geometry.Point( Point(..)+module Data.Geometry.Point( Point(.., Point1, Point2, Point3) , origin, vector , pointFromList-- , coord , unsafeCoord- , projectPoint - , pattern Point2- , pattern Point3 , xCoord, yCoord, zCoord , PointFunctor(..) - , CCW(..), ccw, ccw'+ , CCW, ccw, ccw', isCoLinear+ , pattern CCW, pattern CW, pattern CoLinear - , ccwCmpAround, cwCmpAround, ccwCmpAroundWith, cwCmpAroundWith- , sortAround, insertIntoCyclicOrder+ , ccwCmpAround, ccwCmpAround'+ , cwCmpAround, cwCmpAround'+ , ccwCmpAroundWith, ccwCmpAroundWith'+ , cwCmpAroundWith, cwCmpAroundWith'+ , sortAround, sortAround'+ , insertIntoCyclicOrder , Quadrant(..), quadrantWith, quadrant, partitionIntoQuadrants - , cmpByDistanceTo+ , cmpByDistanceTo, cmpByDistanceTo', cmpInDirection , squaredEuclideanDist, euclideanDist- ) where+ , HasSquaredEuclideanDistance(..) -import Control.DeepSeq-import Control.Lens-import Data.Aeson-import qualified Data.CircularList as C-import qualified Data.CircularList.Util as CU-import Data.Ext-import qualified Data.Foldable as F-import Data.Geometry.Properties-import Data.Geometry.Vector-import qualified Data.Geometry.Vector as Vec-import qualified Data.List as L-import Data.Ord (comparing)-import Data.Proxy-import GHC.Generics (Generic)-import GHC.TypeLits-import Test.QuickCheck (Arbitrary)-import Text.ParserCombinators.ReadP (ReadP, string,pfail)-import Text.ParserCombinators.ReadPrec (lift)-import Text.Read (Read(..),readListPrecDefault, readPrec_to_P,minPrec)+ , coord, unsafeCoord+ ) where +import Data.Geometry.Point.Class+import Data.Geometry.Point.Internal hiding (coord, unsafeCoord)+import Data.Geometry.Point.Orientation.Degenerate+import Data.Geometry.Point.Quadrants+import Data.Geometry.Line.Internal+import Data.Geometry.Vector ----------------------------------------------------------------------------------- $setup--- >>> :{--- let myVector :: Vector 3 Int--- myVector = Vector3 1 2 3--- myPoint = Point myVector--- :} ------------------------------------------------------------------------------------- * A d-dimensional Point---- | A d-dimensional point.-newtype Point d r = Point { toVec :: Vector d r } deriving (Generic)--instance (Show r, Arity d) => Show (Point d r) where- show (Point v) = mconcat [ "Point", show $ F.length v , " "- , show $ F.toList v- ]-instance (Read r, Arity d) => Read (Point d r) where- readPrec = lift readPt- readListPrec = readListPrecDefault--readPt :: forall d r. (Arity d, Read r) => ReadP (Point d r)-readPt = do let d = natVal (Proxy :: Proxy d)- _ <- string $ "Point" <> show d <> " "- rs <- readPrec_to_P readPrec minPrec- case pointFromList rs of- Just p -> pure p- _ -> pfail--deriving instance (Eq r, Arity d) => Eq (Point d r)-deriving instance (Ord r, Arity d) => Ord (Point d r)-deriving instance Arity d => Functor (Point d)-deriving instance Arity d => Foldable (Point d)-deriving instance Arity d => Traversable (Point d)-deriving instance (Arity d, NFData r) => NFData (Point d r)-deriving instance (Arity d, Arbitrary r) => Arbitrary (Point d r)--type instance NumType (Point d r) = r-type instance Dimension (Point d r) = d--instance Arity d => Affine (Point d) where- type Diff (Point d) = Vector d-- p .-. q = toVec p ^-^ toVec q- p .+^ v = Point $ toVec p ^+^ v--instance (FromJSON r, Arity d, KnownNat d) => FromJSON (Point d r) where- parseJSON = fmap Point . parseJSON--instance (ToJSON r, Arity d) => ToJSON (Point d r) where- toJSON = toJSON . toVec- toEncoding = toEncoding . toVec---- | Point representing the origin in d dimensions------ >>> origin :: Point 4 Int--- Point4 [0,0,0,0]-origin :: (Arity d, Num r) => Point d r-origin = Point $ pure 0----- ** Accessing points---- | Lens to access the vector corresponding to this point.------ >>> (Point3 1 2 3) ^. vector--- Vector3 [1,2,3]--- >>> origin & vector .~ Vector3 1 2 3--- Point3 [1,2,3]-vector :: Lens' (Point d r) (Vector d r)-vector = lens toVec (const Point)----- | Get the coordinate in a given dimension. This operation is unsafe in the--- sense that no bounds are checked. Consider using `coord` instead.--------- >>> Point3 1 2 3 ^. unsafeCoord 2--- 2-unsafeCoord :: Arity d => Int -> Lens' (Point d r) r-unsafeCoord i = vector . singular (ix (i-1))- -- Points are 1 indexed, vectors are 0 indexed---- | Get the coordinate in a given dimension------ >>> Point3 1 2 3 ^. coord (C :: C 2)--- 2--- >>> Point3 1 2 3 & coord (C :: C 1) .~ 10--- Point3 [10,2,3]--- >>> Point3 1 2 3 & coord (C :: C 3) %~ (+1)--- Point3 [1,2,4]-coord :: forall proxy i d r. (1 <= i, i <= d, ((i - 1) + 1) ~ i- , Arity (i - 1), Arity d- ) => proxy i -> Lens' (Point d r) r-coord _ = vector . Vec.element (Proxy :: Proxy (i-1))-{-# INLINABLE coord #-}----- somehow these rules don't fire--- {-# SPECIALIZE coord :: C 1 -> Lens' (Point 2 r) r#-}--- {-# SPECIALIZE coord :: C 2 -> Lens' (Point 2 r) r#-}----- | Constructs a point from a list of coordinates------ >>> pointFromList [1,2,3] :: Maybe (Point 3 Int)--- Just Point3 [1,2,3]-pointFromList :: Arity d => [r] -> Maybe (Point d r)-pointFromList = fmap Point . Vec.vectorFromList----- | Project a point down into a lower dimension.-projectPoint :: (Arity i, Arity d, i <= d) => Point d r -> Point i r-projectPoint = Point . prefix . toVec------------------------------------------------------------------------------------- * Convenience functions to construct 2 and 3 dimensional points----- | We provide pattern synonyms Point2 and Point3 for 2 and 3 dimensional points. i.e.--- we can write:------ >>> :{--- let--- f :: Point 2 r -> r--- f (Point2 x y) = x--- in f (Point2 1 2)--- :}--- 1------ if we want.-pattern Point2 :: r -> r -> Point 2 r-pattern Point2 x y = Point (Vector2 x y)-{-# COMPLETE Point2 #-}---- | Similarly, we can write:------ >>> :{--- let--- g :: Point 3 r -> r--- g (Point3 x y z) = z--- in g myPoint--- :}--- 3-pattern Point3 :: r -> r -> r -> Point 3 r-pattern Point3 x y z = (Point (Vector3 x y z))-{-# COMPLETE Point3 #-}---- | Shorthand to access the first coordinate C 1------ >>> Point3 1 2 3 ^. xCoord--- 1--- >>> Point2 1 2 & xCoord .~ 10--- Point2 [10,2]-xCoord :: (1 <= d, Arity d) => Lens' (Point d r) r-xCoord = coord (C :: C 1)-{-# INLINABLE xCoord #-}---- | Shorthand to access the second coordinate C 2------ >>> Point2 1 2 ^. yCoord--- 2--- >>> Point3 1 2 3 & yCoord %~ (+1)--- Point3 [1,3,3]-yCoord :: (2 <= d, Arity d) => Lens' (Point d r) r-yCoord = coord (C :: C 2)-{-# INLINABLE yCoord #-}---- | Shorthand to access the third coordinate C 3------ >>> Point3 1 2 3 ^. zCoord--- 3--- >>> Point3 1 2 3 & zCoord %~ (+1)--- Point3 [1,2,4]-zCoord :: (3 <= d, Arity d) => Lens' (Point d r) r-zCoord = coord (C :: C 3)-{-# INLINABLE zCoord #-}-------------------------------------------------------------------------------------- * Point Functors---- | Types that we can transform by mapping a function on each point in the structure-class PointFunctor g where- pmap :: (Point (Dimension (g r)) r -> Point (Dimension (g s)) s) -> g r -> g s-- -- pemap :: (d ~ Dimension (g r)) => (Point d r :+ p -> Point d s :+ p) -> g r -> g s- -- pemap =--instance PointFunctor (Point d) where- pmap f = f-------------------------------------------------------------------------------------- * Functions specific to Two Dimensional points--data CCW = CCW | CoLinear | CW- deriving (Show,Eq)---- | Given three points p q and r determine the orientation when going from p to r via q.-ccw :: (Ord r, Num r) => Point 2 r -> Point 2 r -> Point 2 r -> CCW-ccw p q r = case z `compare` 0 of- LT -> CW- GT -> CCW- EQ -> CoLinear- where- Vector2 ux uy = q .-. p- Vector2 vx vy = r .-. p- z = ux * vy - uy * vx---- | Given three points p q and r determine the orientation when going from p to r via q.-ccw' :: (Ord r, Num r) => Point 2 r :+ a -> Point 2 r :+ b -> Point 2 r :+ c -> CCW-ccw' p q r = ccw (p^.core) (q^.core) (r^.core)---- | Sort the points arround the given point p in counter clockwise order with--- respect to the rightward horizontal ray starting from p. If two points q--- and r are colinear with p, the closest one to p is reported first.--- running time: O(n log n)-sortAround :: (Ord r, Num r)- => Point 2 r :+ q -> [Point 2 r :+ p] -> [Point 2 r :+ p]-sortAround c = L.sortBy (ccwCmpAround c <> cmpByDistanceTo c)----- | Quadrants of two dimensional points. in CCW order-data Quadrant = TopRight | TopLeft | BottomLeft | BottomRight- deriving (Show,Read,Eq,Ord,Enum,Bounded)---- | Quadrants around point c; quadrants are closed on their "previous"--- boundary (i..e the boundary with the previous quadrant in the CCW order),--- open on next boundary. The origin itself is assigned the topRight quadrant-quadrantWith :: (Ord r, 1 <= d, 2 <= d, Arity d)- => Point d r :+ q -> Point d r :+ p -> Quadrant-quadrantWith (c :+ _) (p :+ _) = case ( (c^.xCoord) `compare` (p^.xCoord)- , (c^.yCoord) `compare` (p^.yCoord) ) of- (EQ, EQ) -> TopRight- (LT, EQ) -> TopRight- (LT, LT) -> TopRight- (EQ, LT) -> TopLeft- (GT, LT) -> TopLeft- (GT, EQ) -> BottomLeft- (GT, GT) -> BottomLeft- (EQ, GT) -> BottomRight- (LT, GT) -> BottomRight---- | Quadrants with respect to the origin-quadrant :: (Ord r, Num r, 1 <= d, 2 <= d, Arity d) => Point d r :+ p -> Quadrant-quadrant = quadrantWith (ext origin)---- | Given a center point c, and a set of points, partition the points into--- quadrants around c (based on their x and y coordinates). The quadrants are--- reported in the order topLeft, topRight, bottomLeft, bottomRight. The points--- are in the same order as they were in the original input lists.--- Points with the same x-or y coordinate as p, are "rounded" to above.-partitionIntoQuadrants :: (Ord r, 1 <= d, 2 <= d, Arity d)- => Point d r :+ q- -> [Point d r :+ p]- -> ( [Point d r :+ p], [Point d r :+ p]- , [Point d r :+ p], [Point d r :+ p]- )-partitionIntoQuadrants c pts = (topL, topR, bottomL, bottomR)- where- (below',above') = L.partition (on yCoord) pts- (bottomL,bottomR) = L.partition (on xCoord) below'- (topL,topR) = L.partition (on xCoord) above'-- on l q = q^.core.l < c^.core.l------ | Given a zero vector z, a center c, and two points p and q,--- compute the ccw ordering of p and q around c with this vector as zero--- direction.------ pre: the points p,q /= c-ccwCmpAroundWith :: (Ord r, Num r)- => Vector 2 r- -> Point 2 r :+ c- -> Point 2 r :+ a -> Point 2 r :+ b- -> Ordering-ccwCmpAroundWith z@(Vector2 zx zy) (c :+ _) (q :+ _) (r :+ _) =- case (ccw c a q, ccw c a r) of- (CCW,CCW) -> cmp- (CCW,CW) -> LT- (CCW,CoLinear) | onZero r -> GT- | otherwise -> LT-- (CW, CCW) -> GT- (CW, CW) -> cmp- (CW, CoLinear) -> GT-- (CoLinear, CCW) | onZero q -> LT- | otherwise -> GT-- (CoLinear, CW) -> LT- (CoLinear,CoLinear) -> case (onZero q, onZero r) of- (True, True) -> EQ- (False, False) -> EQ- (True, False) -> LT- (False, True) -> GT- where- a = c .+^ z- b = c .+^ Vector2 (-zy) zx- -- b is on a perpendicular vector to z-- -- test if the point lies on the ray defined by z, starting in c- onZero d = case ccw c b d of- CCW -> False- CW -> True- CoLinear -> True -- this shouldh appen only when you ask for c itself-- cmp = case ccw c q r of- CCW -> LT- CW -> GT- CoLinear -> EQ---- | Given a zero vector z, a center c, and two points p and q,--- compute the cw ordering of p and q around c with this vector as zero--- direction.------ pre: the points p,q /= c-cwCmpAroundWith :: (Ord r, Num r)- => Vector 2 r- -> Point 2 r :+ a- -> Point 2 r :+ b -> Point 2 r :+ c- -> Ordering-cwCmpAroundWith z c = flip (ccwCmpAroundWith z c)------ | Compare by distance to the first argument-cmpByDistanceTo :: (Ord r, Num r, Arity d)- => Point d r :+ c -> Point d r :+ p -> Point d r :+ q -> Ordering-cmpByDistanceTo (c :+ _) p q = comparing (squaredEuclideanDist c) (p^.core) (q^.core)----- | Counter clockwise ordering of the points around c. Points are ordered with--- respect to the positive x-axis.-ccwCmpAround :: (Num r, Ord r)- => Point 2 r :+ qc -> Point 2 r :+ p -> Point 2 r :+ q -> Ordering-ccwCmpAround = ccwCmpAroundWith (Vector2 1 0)---- | Clockwise ordering of the points around c. Points are ordered with--- respect to the positive x-axis.-cwCmpAround :: (Num r, Ord r)- => Point 2 r :+ qc -> Point 2 r :+ p -> Point 2 r :+ q -> Ordering-cwCmpAround = cwCmpAroundWith (Vector2 1 0)----- | Given a center c, a new point p, and a list of points ps, sorted in--- counter clockwise order around c. Insert p into the cyclic order. The focus--- of the returned cyclic list is the new point p.+-- | Compare the points with respect to the direction given by the+-- vector, i.e. by taking planes whose normal is the given vector. ----- running time: O(n)-insertIntoCyclicOrder :: (Ord r, Num r)- => Point 2 r :+ q -> Point 2 r :+ p- -> C.CList (Point 2 r :+ p) -> C.CList (Point 2 r :+ p)-insertIntoCyclicOrder c = CU.insertOrdBy (ccwCmpAround c <> cmpByDistanceTo c)----- | Squared Euclidean distance between two points-squaredEuclideanDist :: (Num r, Arity d) => Point d r -> Point d r -> r-squaredEuclideanDist = qdA---- | Euclidean distance between two points-euclideanDist :: (Floating r, Arity d) => Point d r -> Point d r -> r-euclideanDist = distanceA+-- >>> cmpInDirection (Vector2 1 0) (Point2 5 0) (Point2 10 0)+-- LT+-- >>> cmpInDirection (Vector2 1 1) (Point2 5 0) (Point2 10 0)+-- LT+-- >>> cmpInDirection (Vector2 1 1) (Point2 5 0) (Point2 10 10)+-- LT+-- >>> cmpInDirection (Vector2 1 1) (Point2 15 15) (Point2 10 10)+-- GT+-- >>> cmpInDirection (Vector2 1 0) (Point2 15 15) (Point2 15 10)+-- EQ+cmpInDirection :: (Ord r, Num r) => Vector 2 r -> Point 2 r -> Point 2 r -> Ordering+cmpInDirection n p q = case p `onSide` perpendicularTo (Line q n) of+ LeftSide -> LT+ OnLine -> EQ+ RightSide -> GT+ -- TODO: Generalize to arbitrary dimension
+ src/Data/Geometry/Point/Class.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+module Data.Geometry.Point.Class where++import Control.Lens+import Data.Geometry.Point.Internal (Point)+import qualified Data.Geometry.Point.Internal as Internal+import Data.Geometry.Vector+import GHC.TypeNats++--------------------------------------------------------------------------------++-- $setup+-- >>> import Data.Geometry.Point.Internal (pattern Point2, pattern Point3, origin)++class ToAPoint point d r where+ toPoint :: Prism' (point d r) (Point d r)++class AsAPoint p where+ asAPoint :: Lens (p d r) (p d' r') (Point d r) (Point d' r')++-- | Lens to access the vector corresponding to this point.+--+-- >>> (Point3 1 2 3) ^. vector'+-- Vector3 1 2 3+-- >>> origin & vector' .~ Vector3 1 2 3+-- Point3 1 2 3+vector' :: AsAPoint p => Lens (p d r) (p d r') (Vector d r) (Vector d r')+vector' = asAPoint . lens Internal.toVec (const Internal.Point)++-- | Get the coordinate in a given dimension+--+-- >>> Point3 1 2 3 ^. coord @2+-- 2+-- >>> Point3 1 2 3 & coord @1 .~ 10+-- Point3 10 2 3+-- >>> Point3 1 2 3 & coord @3 %~ (+1)+-- Point3 1 2 4+coord :: forall i p d r. (1 <= i, i <= d, KnownNat i, Arity d, AsAPoint p) => Lens' (p d r) r+coord = asAPoint.Internal.coord @i++-- | Get the coordinate in a given dimension. This operation is unsafe in the+-- sense that no bounds are checked. Consider using `coord` instead.+--+--+-- >>> Point3 1 2 3 ^. unsafeCoord 2+-- 2+unsafeCoord :: (Arity d, AsAPoint p) => Int -> Lens' (p d r) r+unsafeCoord i = asAPoint.Internal.unsafeCoord i++instance ToAPoint Point d r where+ toPoint = prism' id Just++instance AsAPoint Point where+ asAPoint = id+++-- | Shorthand to access the first coordinate C 1+--+-- >>> Point3 1 2 3 ^. xCoord+-- 1+-- >>> Point2 1 2 & xCoord .~ 10+-- Point2 10 2+xCoord :: (1 <= d, Arity d, AsAPoint point) => Lens' (point d r) r+xCoord = coord @1+{-# INLINABLE xCoord #-}++-- | Shorthand to access the second coordinate C 2+--+-- >>> Point2 1 2 ^. yCoord+-- 2+-- >>> Point3 1 2 3 & yCoord %~ (+1)+-- Point3 1 3 3+yCoord :: (2 <= d, Arity d, AsAPoint point) => Lens' (point d r) r+yCoord = coord @2+{-# INLINABLE yCoord #-}++-- | Shorthand to access the third coordinate C 3+--+-- >>> Point3 1 2 3 ^. zCoord+-- 3+-- >>> Point3 1 2 3 & zCoord %~ (+1)+-- Point3 1 2 4+zCoord :: (3 <= d, Arity d, AsAPoint point) => Lens' (point d r) r+zCoord = coord @3+{-# INLINABLE zCoord #-}
+ src/Data/Geometry/Point/Internal.hs view
@@ -0,0 +1,303 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE AllowAmbiguousTypes #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.Point+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--+-- \(d\)-dimensional points.+--+--------------------------------------------------------------------------------+module Data.Geometry.Point.Internal+ ( Point(..)+ , origin, vector+ , pointFromList++ , coord , unsafeCoord++ , projectPoint++ , pattern Point1+ , pattern Point2+ , pattern Point3+ , PointFunctor(..)++ , cmpByDistanceTo+ , cmpByDistanceTo'+ , squaredEuclideanDist, euclideanDist+ , HasSquaredEuclideanDistance(..)+ ) where++import Control.DeepSeq+import Control.Lens+import Control.Monad+import Data.Aeson+import Data.Ext+import qualified Data.Foldable as F+import Data.Functor.Classes+import Data.Geometry.Properties+import Data.Geometry.Vector+import qualified Data.Geometry.Vector as Vec+import Data.Hashable+import Data.List (intersperse)+import Data.Ord (comparing)+import Data.Proxy+import GHC.Generics (Generic)+import GHC.TypeLits+import System.Random (Random (..))+import Test.QuickCheck (Arbitrary, Arbitrary1)+import Text.Read (Read (..), readListPrecDefault)+++--------------------------------------------------------------------------------+-- $setup+-- >>> :{+-- let myVector :: Vector 3 Int+-- myVector = Vector3 1 2 3+-- myPoint = Point myVector+-- :}+++--------------------------------------------------------------------------------+-- * A d-dimensional Point++-- | A d-dimensional point.+--+-- There are convenience pattern synonyms for 1, 2 and 3 dimensional points.+--+-- >>> let f (Point1 x) = x in f (Point1 1)+-- 1+-- >>> let f (Point2 x y) = x in f (Point2 1 2)+-- 1+-- >>> let f (Point3 x y z) = z in f (Point3 1 2 3)+-- 3+-- >>> let f (Point3 x y z) = z in f (Point $ Vector3 1 2 3)+-- 3+newtype Point d r = Point { toVec :: Vector d r } deriving (Generic)++instance (Show r, Arity d) => Show (Point d r) where+ showsPrec = liftShowsPrec showsPrec showList++instance (Arity d) => Show1 (Point d) where+ liftShowsPrec sp _ d (Point v) = showParen (d > 10) $+ showString constr . showChar ' ' .+ unwordsS (map (sp 11) (F.toList v))+ where+ constr = "Point" <> show (fromIntegral (natVal @d Proxy))+ unwordsS = foldr (.) id . intersperse (showChar ' ')++instance (Read r, Arity d) => Read (Point d r) where+ readPrec = liftReadPrec readPrec readListPrec+ readListPrec = readListPrecDefault++instance (Arity d) => Read1 (Point d) where+ liftReadPrec rp _rl = readData $+ readUnaryWith (replicateM d rp) constr $ \rs ->+ case pointFromList rs of+ Just p -> p+ _ -> error "internal error in Data.Geometry.Point read instance."+ where+ d = fromIntegral (natVal (Proxy :: Proxy d))+ constr = "Point" <> show d+ liftReadListPrec = liftReadListPrecDefault++-- readPt :: forall d r. (Arity d, Read r) => ReadP (Point d r)+-- readPt = do let d = natVal (Proxy :: Proxy d)+-- _ <- string $ "Point" <> show d+-- rs <- if d > 3+-- then readPrec_to_P readPrec minPrec+-- else replicateM (fromIntegral d) (readPrec_to_P readPrec minPrec)+-- case pointFromList rs of+-- Just p -> pure p+-- _ -> pfail++deriving instance (Eq r, Arity d) => Eq (Point d r)+deriving instance Arity d => Eq1 (Point d)+deriving instance (Ord r, Arity d) => Ord (Point d r)+deriving instance Arity d => Functor (Point d)+deriving instance Arity d => Applicative (Point d)+deriving instance Arity d => Foldable (Point d)+deriving instance Arity d => Traversable (Point d)+deriving instance (Arity d, NFData r) => NFData (Point d r)+deriving instance (Arity d, Arbitrary r) => Arbitrary (Point d r)+deriving instance Arity d => Arbitrary1 (Point d)+deriving instance (Arity d, Hashable r) => Hashable (Point d r)+deriving instance (Arity d, Random r) => Random (Point d r)+++type instance NumType (Point d r) = r+type instance Dimension (Point d r) = d++instance Arity d => Affine (Point d) where+ type Diff (Point d) = Vector d++ p .-. q = toVec p ^-^ toVec q+ p .+^ v = Point $ toVec p ^+^ v++instance (FromJSON r, Arity d, KnownNat d) => FromJSON (Point d r) where+ parseJSON = fmap Point . parseJSON++instance (ToJSON r, Arity d) => ToJSON (Point d r) where+ toJSON = toJSON . toVec+ toEncoding = toEncoding . toVec++-- | Point representing the origin in d dimensions+--+-- >>> origin :: Point 4 Int+-- Point4 0 0 0 0+origin :: (Arity d, Num r) => Point d r+origin = Point $ pure 0+++-- ** Accessing points++-- | Lens to access the vector corresponding to this point.+--+-- >>> (Point3 1 2 3) ^. vector+-- Vector3 1 2 3+-- >>> origin & vector .~ Vector3 1 2 3+-- Point3 1 2 3+vector :: Lens (Point d r) (Point d r') (Vector d r) (Vector d r')+vector = lens toVec (const Point)+{-# INLINABLE vector #-}++-- | Get the coordinate in a given dimension. This operation is unsafe in the+-- sense that no bounds are checked. Consider using `coord` instead.+--+--+-- >>> Point3 1 2 3 ^. unsafeCoord 2+-- 2+unsafeCoord :: Arity d => Int -> Lens' (Point d r) r+unsafeCoord i = vector . singular (ix (i-1))+ -- Points are 1 indexed, vectors are 0 indexed+{-# INLINABLE unsafeCoord #-}++-- | Get the coordinate in a given dimension+--+-- >>> Point3 1 2 3 ^. coord @2+-- 2+-- >>> Point3 1 2 3 & coord @1 .~ 10+-- Point3 10 2 3+-- >>> Point3 1 2 3 & coord @3 %~ (+1)+-- Point3 1 2 4+coord :: forall i d r. (1 <= i, i <= d, Arity d, KnownNat i)+ => Lens' (Point d r) r+coord = unsafeCoord $ fromIntegral (natVal $ C @i)+{-# INLINABLE coord #-}++ -- somehow these rules don't fire+-- {-# SPECIALIZE coord :: C 1 -> Lens' (Point 2 r) r#-}+-- {-# SPECIALIZE coord :: C 2 -> Lens' (Point 2 r) r#-}+-- {-# SPECIALIZE coord :: C 3 -> Lens' (Point 3 r) r#-}+++-- | Constructs a point from a list of coordinates. The length of the+-- list has to match the dimension exactly.+--+-- >>> pointFromList [1,2,3] :: Maybe (Point 3 Int)+-- Just (Point3 1 2 3)+-- >>> pointFromList [1] :: Maybe (Point 3 Int)+-- Nothing+-- >>> pointFromList [1,2,3,4] :: Maybe (Point 3 Int)+-- Nothing+pointFromList :: Arity d => [r] -> Maybe (Point d r)+pointFromList = fmap Point . Vec.vectorFromList+++-- | Project a point down into a lower dimension.+projectPoint :: (Arity i, Arity d, i <= d) => Point d r -> Point i r+projectPoint = Point . prefix . toVec++--------------------------------------------------------------------------------+-- * Convenience functions to construct 1, 2 and 3 dimensional points++-- | A bidirectional pattern synonym for 1 dimensional points.+pattern Point1 :: r -> Point 1 r+pattern Point1 x = Point (Vector1 x)+{-# COMPLETE Point1 #-}+++-- | A bidirectional pattern synonym for 2 dimensional points.+pattern Point2 :: r -> r -> Point 2 r+pattern Point2 x y = Point (Vector2 x y)+{-# COMPLETE Point2 #-}++-- | A bidirectional pattern synonym for 3 dimensional points.+pattern Point3 :: r -> r -> r -> Point 3 r+pattern Point3 x y z = (Point (Vector3 x y z))+{-# COMPLETE Point3 #-}++--------------------------------------------------------------------------------+-- * Point Functors++-- | Types that we can transform by mapping a function on each point in the structure+class PointFunctor g where+ pmap :: (Point (Dimension (g r)) r -> Point (Dimension (g s)) s) -> g r -> g s++ -- pemap :: (d ~ Dimension (g r)) => (Point d r :+ p -> Point d s :+ p) -> g r -> g s+ -- pemap =++instance PointFunctor (Point d) where+ pmap f = f++++--------------------------------------------------------------------------------+++++--------------------------------------------------------------------------------+-- * Functions specific to Two Dimensional points++-- | Compare by distance to the first argument+cmpByDistanceTo :: (Ord r, Num r, Arity d)+ => Point d r -> Point d r -> Point d r -> Ordering+cmpByDistanceTo c p q = comparing (squaredEuclideanDist c) p q++-- | Compare by distance to the first argument+cmpByDistanceTo' :: (Ord r, Num r, Arity d)+ => Point d r :+ c -> Point d r :+ p -> Point d r :+ q -> Ordering+cmpByDistanceTo' c p q = cmpByDistanceTo (c^.core) (p^.core) (q^.core)+++-- | Squared Euclidean distance between two points+squaredEuclideanDist :: (Num r, Arity d) => Point d r -> Point d r -> r+squaredEuclideanDist = qdA++-- | Euclidean distance between two points+euclideanDist :: (Floating r, Arity d) => Point d r -> Point d r -> r+euclideanDist = distanceA+++--------------------------------------------------------------------------------+-- * Distances++class HasSquaredEuclideanDistance g where+ -- | Given a point q and a geometry g, the squared Euclidean distance between q and g.+ squaredEuclideanDistTo :: (Num (NumType g), Arity (Dimension g))+ => Point (Dimension g) (NumType g) -> g -> NumType g+ squaredEuclideanDistTo q = snd . pointClosestToWithDistance q++ -- | Given q and g, computes the point p in g closest to q according+ -- to the Squared Euclidean distance.+ pointClosestTo :: (Num (NumType g), Arity (Dimension g))+ => Point (Dimension g) (NumType g) -> g+ -> Point (Dimension g) (NumType g)+ pointClosestTo q = fst . pointClosestToWithDistance q++ -- | Given q and g, computes the point p in g closest to q according+ -- to the Squared Euclidean distance. Returns both the point and the+ -- distance realized by this point.+ pointClosestToWithDistance :: (Num (NumType g), Arity (Dimension g))+ => Point (Dimension g) (NumType g) -> g+ -> (Point (Dimension g) (NumType g), NumType g)+ pointClosestToWithDistance q g = let p = pointClosestTo q g+ in (p, squaredEuclideanDist p q)+ {-# MINIMAL pointClosestToWithDistance | pointClosestTo #-}++instance (Num r, Arity d) => HasSquaredEuclideanDistance (Point d r) where+ pointClosestTo _ p = p
+ src/Data/Geometry/Point/Orientation.hs view
@@ -0,0 +1,31 @@+module Data.Geometry.Point.Orientation where++import Algorithms.Geometry.SoS.Orientation+import Algorithms.Geometry.SoS.Sign+import Data.Ext+import Data.Geometry.Point.Internal+import Data.Geometry.Vector++--------------------------------------------------------------------------------++--------------------------------------------------------------------------------++newtype StrictCCW = SCCW Sign deriving Eq++pattern CCW :: StrictCCW+pattern CCW = SCCW Negative++pattern CW :: StrictCCW+pattern CW = SCCW Positive+{-# COMPLETE CCW, CW #-}++instance Show StrictCCW where+ show = \case+ CCW -> "CCW"+ CW -> "CW"+++-- | Given three points p q and r determine the orientation when going from p to r via q.+ccw :: (Ord r, Num r, Ord i)+ => Point 2 r :+ i -> Point 2 r :+ i -> Point 2 r :+ i -> StrictCCW+ccw p q r = SCCW $ sideTest r (Vector2 p q)
+ src/Data/Geometry/Point/Orientation/Degenerate.hs view
@@ -0,0 +1,226 @@+module Data.Geometry.Point.Orientation.Degenerate(+ CCW(..)+ , pattern CCW, pattern CW, pattern CoLinear++ , ccw, ccw'++ , isCoLinear++ , sortAround, sortAround'++ , ccwCmpAroundWith, ccwCmpAroundWith'+ , cwCmpAroundWith, cwCmpAroundWith'+ , ccwCmpAround, ccwCmpAround'+ , cwCmpAround, cwCmpAround'++ , insertIntoCyclicOrder+ ) where++import Control.Lens+import qualified Data.CircularList as C+import qualified Data.CircularList.Util as CU+import Data.Ext+import Data.Geometry.Point.Internal+import Data.Geometry.Vector+import qualified Data.List as L++--------------------------------------------------------------------------------++-- $setup+-- >>> import Data.Double.Approximate++-- | Data type for expressing the orientation of three points, with+-- the option of allowing Colinearities.+newtype CCW = CCWWrap Ordering deriving Eq++-- | CounterClockwise orientation. Also called a left-turn.+pattern CCW :: CCW+pattern CCW = CCWWrap GT++-- | Clockwise orientation. Also called a right-turn.+pattern CW :: CCW+pattern CW = CCWWrap LT++-- | CoLinear orientation. Also called a straight line.+pattern CoLinear :: CCW+pattern CoLinear = CCWWrap EQ+{-# COMPLETE CCW, CW, CoLinear #-}++instance Show CCW where+ show = \case+ CCW -> "CCW"+ CW -> "CW"+ CoLinear -> "CoLinear"+++-- | Given three points p q and r determine the orientation when going from p to r via q.+--+-- Be vary of numerical instability:+-- >>> ccw (Point2 0 0.3) (Point2 1 0.6) (Point2 2 (0.9::Double))+-- CCW+--+-- >>> ccw (Point2 0 0.3) (Point2 1 0.6) (Point2 2 (0.9::Rational))+-- CoLinear+--+-- If you can't use 'Rational', try 'SafeDouble' instead of 'Double':+-- >>> ccw (Point2 0 0.3) (Point2 1 0.6) (Point2 2 (0.9::SafeDouble))+-- CoLinear+--+ccw :: (Ord r, Num r) => Point 2 r -> Point 2 r -> Point 2 r -> CCW+ccw p q r = CCWWrap $ (ux*vy) `compare` (uy*vx)+-- ccw p q r = CCWWrap $ z `compare` 0 -- Comparing against 0 is bad for numerical robustness.+ -- I've added a testcase that fails if comparing against 0.+ -- case z `compare` 0 of+ -- LT -> CW+ -- GT -> CCW+ -- EQ -> CoLinear+ where+ Vector2 ux uy = q .-. p+ Vector2 vx vy = r .-. p+ -- _z = ux * vy - uy * vx++-- | Given three points p q and r determine if the line from p to r via q is straight/colinear.+--+-- This is identical to `ccw p q r == CoLinear` but doesn't have the `Ord` constraint.+isCoLinear :: (Eq r, Num r) => Point 2 r -> Point 2 r -> Point 2 r -> Bool+isCoLinear p q r = (ux * vy) == (uy * vx)+ where+ Vector2 ux uy = q .-. p+ Vector2 vx vy = r .-. p++-- | Given three points p q and r determine the orientation when going from p to r via q.+ccw' :: (Ord r, Num r) => Point 2 r :+ a -> Point 2 r :+ b -> Point 2 r :+ c -> CCW+ccw' p q r = ccw (p^.core) (q^.core) (r^.core)++-- | \( O(n log n) \)+-- Sort the points arround the given point p in counter clockwise order with+-- respect to the rightward horizontal ray starting from p. If two points q+-- and r are colinear with p, the closest one to p is reported first.+sortAround :: (Ord r, Num r)+ => Point 2 r -> [Point 2 r] -> [Point 2 r]+sortAround c = L.sortBy (ccwCmpAround c <> cmpByDistanceTo c)++-- | \( O(n log n) \)+-- Sort the points arround the given point p in counter clockwise order with+-- respect to the rightward horizontal ray starting from p. If two points q+-- and r are colinear with p, the closest one to p is reported first.+sortAround' :: (Ord r, Num r)+ => Point 2 r :+ q -> [Point 2 r :+ p] -> [Point 2 r :+ p]+sortAround' c = L.sortBy (ccwCmpAround' c <> cmpByDistanceTo' c)+++-- | Given a zero vector z, a center c, and two points p and q,+-- compute the ccw ordering of p and q around c with this vector as zero+-- direction.+--+-- pre: the points p,q /= c+ccwCmpAroundWith :: (Ord r, Num r)+ => Vector 2 r+ -> Point 2 r+ -> Point 2 r -> Point 2 r+ -> Ordering+ccwCmpAroundWith z@(Vector2 zx zy) c q r =+ case (ccw c a q, ccw c a r) of+ (CCW,CCW) -> cmp+ (CCW,CW) -> LT+ (CCW,CoLinear) | onZero r -> GT+ | otherwise -> LT++ (CW, CCW) -> GT+ (CW, CW) -> cmp+ (CW, CoLinear) -> GT++ (CoLinear, CCW) | onZero q -> LT+ | otherwise -> GT++ (CoLinear, CW) -> LT+ (CoLinear,CoLinear) -> case (onZero q, onZero r) of+ (True, True) -> EQ+ (False, False) -> EQ+ (True, False) -> LT+ (False, True) -> GT+ where+ a = c .+^ z+ b = c .+^ Vector2 (-zy) zx+ -- b is on a perpendicular vector to z++ -- test if the point lies on the ray defined by z, starting in c+ onZero d = case ccw c b d of+ CCW -> False+ CW -> True+ CoLinear -> True -- this shouldh appen only when you ask for c itself++ cmp = case ccw c q r of+ CCW -> LT+ CW -> GT+ CoLinear -> EQ++-- | Given a zero vector z, a center c, and two points p and q,+-- compute the ccw ordering of p and q around c with this vector as zero+-- direction.+--+-- pre: the points p,q /= c+ccwCmpAroundWith' :: (Ord r, Num r)+ => Vector 2 r+ -> Point 2 r :+ c+ -> Point 2 r :+ a -> Point 2 r :+ b+ -> Ordering+ccwCmpAroundWith' z (c :+ _) (q :+ _) (r :+ _) = ccwCmpAroundWith z c q r++-- | Given a zero vector z, a center c, and two points p and q,+-- compute the cw ordering of p and q around c with this vector as zero+-- direction.+--+-- pre: the points p,q /= c+cwCmpAroundWith :: (Ord r, Num r)+ => Vector 2 r+ -> Point 2 r+ -> Point 2 r -> Point 2 r+ -> Ordering+cwCmpAroundWith z c = flip (ccwCmpAroundWith z c)+++-- | Given a zero vector z, a center c, and two points p and q,+-- compute the cw ordering of p and q around c with this vector as zero+-- direction.+--+-- pre: the points p,q /= c+cwCmpAroundWith' :: (Ord r, Num r)+ => Vector 2 r+ -> Point 2 r :+ a+ -> Point 2 r :+ b -> Point 2 r :+ c+ -> Ordering+cwCmpAroundWith' z c = flip (ccwCmpAroundWith' z c)++-- | Counter clockwise ordering of the points around c. Points are ordered with+-- respect to the positive x-axis.+ccwCmpAround :: (Num r, Ord r)+ => Point 2 r -> Point 2 r -> Point 2 r -> Ordering+ccwCmpAround = ccwCmpAroundWith (Vector2 1 0)++-- | Counter clockwise ordering of the points around c. Points are ordered with+-- respect to the positive x-axis.+ccwCmpAround' :: (Num r, Ord r)+ => Point 2 r :+ qc -> Point 2 r :+ p -> Point 2 r :+ q -> Ordering+ccwCmpAround' = ccwCmpAroundWith' (Vector2 1 0)++-- | Clockwise ordering of the points around c. Points are ordered with+-- respect to the positive x-axis.+cwCmpAround :: (Num r, Ord r)+ => Point 2 r -> Point 2 r -> Point 2 r -> Ordering+cwCmpAround = cwCmpAroundWith (Vector2 1 0)++-- | Clockwise ordering of the points around c. Points are ordered with+-- respect to the positive x-axis.+cwCmpAround' :: (Num r, Ord r)+ => Point 2 r :+ qc -> Point 2 r :+ p -> Point 2 r :+ q -> Ordering+cwCmpAround' a b c = cwCmpAround (a^.core) (b^.core) (c^.core)++-- | \( O(n) \)+-- Given a center c, a new point p, and a list of points ps, sorted in+-- counter clockwise order around c. Insert p into the cyclic order. The focus+-- of the returned cyclic list is the new point p.+insertIntoCyclicOrder :: (Ord r, Num r)+ => Point 2 r :+ q -> Point 2 r :+ p+ -> C.CList (Point 2 r :+ p) -> C.CList (Point 2 r :+ p)+insertIntoCyclicOrder c = CU.insertOrdBy (ccwCmpAround' c <> cmpByDistanceTo' c)
+ src/Data/Geometry/Point/Quadrants.hs view
@@ -0,0 +1,62 @@+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.Point.Quadrants+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Data.Geometry.Point.Quadrants where++import Control.Lens+import Data.Ext+import Data.Geometry.Point.Class+import Data.Geometry.Point.Internal+import Data.Geometry.Vector+import qualified Data.List as L+import GHC.TypeLits++--------------------------------------------------------------------------------++-- | Quadrants of two dimensional points. in CCW order+data Quadrant = TopRight | TopLeft | BottomLeft | BottomRight+ deriving (Show,Read,Eq,Ord,Enum,Bounded)++-- | Quadrants around point c; quadrants are closed on their "previous"+-- boundary (i..e the boundary with the previous quadrant in the CCW order),+-- open on next boundary. The origin itself is assigned the topRight quadrant+quadrantWith :: (Ord r, 1 <= d, 2 <= d, Arity d)+ => Point d r :+ q -> Point d r :+ p -> Quadrant+quadrantWith (c :+ _) (p :+ _) = case ( (c^.xCoord) `compare` (p^.xCoord)+ , (c^.yCoord) `compare` (p^.yCoord) ) of+ (EQ, EQ) -> TopRight+ (LT, EQ) -> TopRight+ (LT, LT) -> TopRight+ (EQ, LT) -> TopLeft+ (GT, LT) -> TopLeft+ (GT, EQ) -> BottomLeft+ (GT, GT) -> BottomLeft+ (EQ, GT) -> BottomRight+ (LT, GT) -> BottomRight++-- | Quadrants with respect to the origin+quadrant :: (Ord r, Num r, 1 <= d, 2 <= d, Arity d) => Point d r :+ p -> Quadrant+quadrant = quadrantWith (ext origin)++-- | Given a center point c, and a set of points, partition the points into+-- quadrants around c (based on their x and y coordinates). The quadrants are+-- reported in the order topLeft, topRight, bottomLeft, bottomRight. The points+-- are in the same order as they were in the original input lists.+-- Points with the same x-or y coordinate as p, are "rounded" to above.+partitionIntoQuadrants :: (Ord r, 1 <= d, 2 <= d, Arity d)+ => Point d r :+ q+ -> [Point d r :+ p]+ -> ( [Point d r :+ p], [Point d r :+ p]+ , [Point d r :+ p], [Point d r :+ p]+ )+partitionIntoQuadrants c pts = (topL, topR, bottomL, bottomR)+ where+ (below',above') = L.partition (on yCoord) pts+ (bottomL,bottomR) = L.partition (on xCoord) below'+ (topL,topR) = L.partition (on xCoord) above'++ on l q = q^.core.l < c^.core.l
+ src/Data/Geometry/PointLocation.hs view
@@ -0,0 +1,12 @@+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.PointLocation+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Data.Geometry.PointLocation+ ( module Data.Geometry.PointLocation.PersistentSweep+ ) where++import Data.Geometry.PointLocation.PersistentSweep
+ src/Data/Geometry/PointLocation/PersistentSweep.hs view
@@ -0,0 +1,176 @@+{-# Language TemplateHaskell #-}+{-# Language TypeApplications #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.PointLocation.PersistentSweep+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Data.Geometry.PointLocation.PersistentSweep+ ( PointLocationDS(PointLocationDS)+ , verticalRayShootingStructure, subdivision, outerFace++ -- * Building the Data Structure+ , pointLocationDS+ -- * Querying the Data Structure+ , dartAbove, dartAboveOrOn+ , faceContaining, faceIdContaining++ , InPolygonDS, inPolygonDS+ , InOut(..)++ , pointInPolygon+ , edgeOnOrAbove+ ) where++import qualified Data.Geometry.VerticalRayShooting.PersistentSweep as VRS+import Control.Lens hiding (contains, below)+import Data.Ext+import Data.Geometry.LineSegment+import Data.Geometry.PlanarSubdivision+import Data.Geometry.Point+import Data.Geometry.Polygon+import qualified Data.List.NonEmpty as NonEmpty+import Data.Util (SP(..))+import qualified Data.Vector as V++--------------------------------------------------------------------------------++-- | Planar Point Location Data structure+data PointLocationDS s v e f r = PointLocationDS {+ _verticalRayShootingStructure :: VRS.VerticalRayShootingStructure v (Dart s) r+ , _subdivision :: PlanarSubdivision s v e f r+ , _outerFace :: FaceId' s+ } deriving (Show,Eq)++makeLensesWith (lensRules&generateUpdateableOptics .~ False) ''PointLocationDS++--------------------------------------------------------------------------------+-- * Buidlding the Point location Data structure++-- | Builds a pointlocation data structure on the planar subdivision with \(n\)+-- vertices.+--+-- running time: \(O(n\log n)\).+-- space: \(O(n\log n)\).+pointLocationDS :: (Ord r, Fractional r)+ => PlanarSubdivision s v e f r -> PointLocationDS s v e f r+pointLocationDS ps = PointLocationDS (VRS.verticalRayShootingStructure es) ps (outerFaceId ps)+ where+ es = NonEmpty.fromList . V.toList . fmap (\(d,s) -> s&extra .~ d) . edgeSegments $ ps+ -- the VRS structure will throw away vertical edges. So there is no need to+ -- explicitly filter them yet at this point++--------------------------------------------------------------------------------+-- * Querying the Structure++-- | Locates the first edge (dart) strictly above the query point.+-- returns Nothing if the query point lies in the outer face and there is no dart+-- above it.+--+-- running time: \(O(\log n)\)+dartAbove :: (Ord r, Fractional r)+ => Point 2 r -> PointLocationDS s v e f r -> Maybe (Dart s)+dartAbove = queryWith VRS.segmentAbove++dartAboveOrOn :: (Ord r, Fractional r)+ => Point 2 r -> PointLocationDS s v e f r -> Maybe (Dart s)+dartAboveOrOn = queryWith VRS.segmentAboveOrOn++type QueryAlgorithm v e r =+ Point 2 r -> VRS.VerticalRayShootingStructure v e r -> Maybe (LineSegment 2 v r :+ e)++queryWith :: (Ord r, Fractional r)+ => QueryAlgorithm v (Dart s) r+ -> Point 2 r -> PointLocationDS s v e f r -> Maybe (Dart s)+queryWith query q = fmap (view extra) . query q . view verticalRayShootingStructure++-- | Locates the face containing the query point.+--+-- running time: \(O(\log n)\)+faceContaining :: (Ord r, Fractional r)+ => Point 2 r -> PointLocationDS s v e f r -> f+faceContaining q ds = ds^.subdivision.dataOf (faceIdContaining q ds)++-- | Locates the faceId of the face containing the query point.+--+-- If the query point lies *on* an edge, an arbitrary face incident to+-- the edge is returned.+--+-- running time: \(O(\log n)\)+faceIdContaining :: (Ord r, Fractional r)+ => Point 2 r -> PointLocationDS s v e f r -> FaceId' s+faceIdContaining q ds = dartToFace ds $ dartAbove q ds++-- | Given the dart determine the faceId correspondig to it (depending+-- on the orientation of the dart that is returned.)+dartToFace :: Ord r => PointLocationDS s v e f r -> Maybe (Dart s) -> FaceId' s+dartToFace ds = maybe (ds^.outerFace) getFace+ where+ ps = ds^.subdivision+ getFace d = let (u,v) = bimap (^.location) (^.location) $ endPointData d ps+ in if u <= v then rightFace d ps+ else leftFace d ps+++data OneOrTwo a = One !a | Two !a !a deriving (Show,Read,Eq,Ord,Functor,Foldable,Traversable)++-- | Locates the faceId of the face containing the query point. If the+-- query point lies on an edge, it returns both faces incident to the+-- edge; first the one below the edge then the one above the edge.+--+-- running time: \(O(\log n)\)+faceIdContaining' :: (Ord r, Fractional r)+ => Point 2 r -> PointLocationDS s v e f r -> OneOrTwo (FaceId' s)+faceIdContaining' q ds = maybe (One $ ds^.outerFace) getFace $ dartAboveOrOn q ds+ where+ ps = ds^.subdivision++ getFace = getFace' . orient++ orient d = let (u,v) = bimap (^.location) (^.location) $ endPointData d ps+ in if u <= v then (d,u,v) else (twin d, v, u)+++ getFace' (d,u,v) = case ccw u q v of+ CoLinear -> Two (rightFace d ps) (leftFace d ps)+ _ -> One (rightFace d ps)++--------------------------------------------------------------------------------++-- | Data structure for fast InPolygon Queries+-- newtype InPolygonDS v r = InPolygonDS (VRS.VerticalRayShootingStructure (Vertex v r) () r)+-- deriving (Show,Eq)++data InOut = In | Out deriving (Show,Eq)++data Dummy+type InPolygonDS v r = PointLocationDS Dummy (SP Int v) () InOut r+++-- type Vertex v r = Int :+ (Point 2 r :+ v)++inPolygonDS :: (Fractional r, Ord r) => SimplePolygon v r -> InPolygonDS v r+inPolygonDS pg = pointLocationDS $ fromSimplePolygon @Dummy (numberVertices pg) In Out++-- | Finds the edge on or above the query point, if it exists+--+--+edgeOnOrAbove :: (Ord r, Fractional r)+ => Point 2 r -> InPolygonDS v r -> Maybe (LineSegment 2 (SP Int v) r)+edgeOnOrAbove q ds = view core . flip edgeSegment (ds^.subdivision) <$> dartAboveOrOn q ds+++-- | Returns if a query point lies in (or on the boundary of) the polygon.+--+-- \(O(\log n)\)+pointInPolygon :: (Ord r, Fractional r) => Point 2 r -> InPolygonDS v r -> InOut+pointInPolygon q ds = case faceIdContaining' q ds of+ One i -> ds^.subdivision.dataOf i+ Two _ _ -> In -- on an edge, so inside.++ -- FIXME: Make sure to also test the edge "below" q, i.e. if q is on+ -- some edge we should return that edge.++ -- FIXME: Figure out if this works ok for vertical edges as well
src/Data/Geometry/PolyLine.hs view
@@ -1,11 +1,18 @@-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE UndecidableInstances #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.PolyLine+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Data.Geometry.PolyLine where import Control.Lens import Data.Aeson+import Data.Bifoldable import Data.Bifunctor+import Data.Bitraversable import Data.Ext import qualified Data.Foldable as F import Data.Geometry.Box@@ -17,16 +24,28 @@ import Data.LSeq (LSeq, pattern (:<|)) import qualified Data.LSeq as LSeq import qualified Data.List.NonEmpty as NE-import GHC.Generics(Generic)+import Data.Ord (comparing)+import GHC.Generics (Generic) import GHC.TypeLits --------------------------------------------------------------------------------++--------------------------------------------------------------------------------+-- $setup+-- >>> :{+-- let myPolyLine = fromPointsUnsafe $ map ext [origin, Point2 10.0 10.0, Point2 10.0 20.0]+-- :}++-------------------------------------------------------------------------------- -- * d-dimensional Polygonal Lines (PolyLines) -- | A Poly line in R^d has at least 2 vertices newtype PolyLine d p r = PolyLine { _points :: LSeq 2 (Point d r :+ p) } deriving (Generic)-makeLenses ''PolyLine +-- | PolyLines are isomorphic to a sequence of points with at least 2 members.+points :: Iso (PolyLine d1 p1 r1) (PolyLine d2 p2 r2) (LSeq 2 (Point d1 r1 :+ p1)) (LSeq 2 (Point d2 r2 :+ p2))+points = iso (\(PolyLine s) -> s) PolyLine+ deriving instance (Show r, Show p, Arity d) => Show (PolyLine d p r) deriving instance (Eq r, Eq p, Arity d) => Eq (PolyLine d p r) deriving instance (Ord r, Ord p, Arity d) => Ord (PolyLine d p r)@@ -50,25 +69,49 @@ pmap f = over points (fmap (first f)) instance Arity d => Bifunctor (PolyLine d) where- bimap f g (PolyLine pts) = PolyLine $ fmap (bimap (fmap g) f) pts+ bimap = bimapDefault+instance Arity d => Bifoldable (PolyLine d) where+ bifoldMap = bifoldMapDefault+instance Arity d => Bitraversable (PolyLine d) where+ bitraverse f g (PolyLine pts) = PolyLine <$> traverse (bitraverse (traverse g) f) pts instance (ToJSON p, ToJSON r, Arity d) => ToJSON (PolyLine d p r) where toEncoding = genericToEncoding defaultOptions instance (FromJSON p, FromJSON r, Arity d, KnownNat d) => FromJSON (PolyLine d p r) +instance HasStart (PolyLine d p r) where+ type StartCore (PolyLine d p r) = Point d r+ type StartExtra (PolyLine d p r) = p+ start = points.head1++instance HasEnd (PolyLine d p r) where+ type EndCore (PolyLine d p r) = Point d r+ type EndExtra (PolyLine d p r) = p+ end = points.last1++instance (Fractional r, Arity d, Ord r) => HasSquaredEuclideanDistance (PolyLine d p r) where+ pointClosestToWithDistance q = F.minimumBy (comparing snd)+ . fmap (pointClosestToWithDistance q)+ . edgeSegments+++-- | Builds a Polyline from a list of points, if there are sufficiently many points+fromPoints :: [Point d r :+ p] -> Maybe (PolyLine d p r)+fromPoints = fmap PolyLine . LSeq.eval @2 . LSeq.fromList+ -- | pre: The input list contains at least two points-fromPoints :: [Point d r :+ p] -> PolyLine d p r-fromPoints = PolyLine . LSeq.forceLSeq (C :: C 2) . LSeq.fromList+fromPointsUnsafe :: [Point d r :+ p] -> PolyLine d p r+fromPointsUnsafe = PolyLine . LSeq.forceLSeq (C @2) . LSeq.fromList -- | pre: The input list contains at least two points. All extra vields are -- initialized with mempty.-fromPoints' :: (Monoid p) => [Point d r] -> PolyLine d p r-fromPoints' = fromPoints . map (\p -> p :+ mempty)+fromPointsUnsafe' :: (Monoid p) => [Point d r] -> PolyLine d p r+fromPointsUnsafe' = fromPointsUnsafe . map (:+ mempty) -- | We consider the line-segment as closed. fromLineSegment :: LineSegment d p r -> PolyLine d p r-fromLineSegment ~(LineSegment' p q) = fromPoints [p,q]+fromLineSegment ~(LineSegment' p q) = fromPointsUnsafe [p,q] -- | Convert to a closed line segment by taking the first two points. asLineSegment :: PolyLine d p r -> LineSegment d p r@@ -80,3 +123,23 @@ asLineSegment' (PolyLine pts) = case F.toList pts of [p,q] -> Just $ ClosedLineSegment p q _ -> Nothing++-- | Computes the edges, as linesegments, of an LSeq+edgeSegments :: Arity d => PolyLine d p r -> LSeq 1 (LineSegment d p r)+edgeSegments pl = let vs = pl^.points+ in LSeq.zipWith ClosedLineSegment (LSeq.init vs) (LSeq.tail vs)+++-- | Linearly interpolate the polyline with a value in the range+-- \([0,n-1]\), where \(n\) is the number of vertices of the polyline.+--+-- running time: \(O(\log n)\)+--+-- >>> interpolatePoly 0.5 myPolyLine+-- Point2 5.0 5.0+-- >>> interpolatePoly 1.5 myPolyLine+-- Point2 10.0 15.0+interpolatePoly :: (RealFrac r, Arity d) => r -> PolyLine d p r -> Point d r+interpolatePoly t pl = let i = floor t in case edgeSegments pl^?ix i of+ Nothing -> pl^.points.to LSeq.last.core+ Just e -> interpolate (t-fromIntegral i) e
src/Data/Geometry/Polygon.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} -------------------------------------------------------------------------------- -- | -- Module : Data.Geometry.Polygon@@ -9,55 +9,92 @@ -- A Polygon data type and some basic functions to interact with them. -- ---------------------------------------------------------------------------------module Data.Geometry.Polygon( PolygonType(..)- , Polygon(..)- , _SimplePolygon, _MultiPolygon- , SimplePolygon, MultiPolygon, SomePolygon+module Data.Geometry.Polygon+ ( -- * Types+ PolygonType(..)+ , Polygon(..)+ , _SimplePolygon, _MultiPolygon+ , SimplePolygon, MultiPolygon, SomePolygon - , fromPoints+ -- * Conversion+ , fromPoints+ , fromCircularVector - , polygonVertices, listEdges+ , simpleFromPoints+ , simpleFromCircularVector - , outerBoundary, outerBoundaryEdges- , outerVertex, outerBoundaryEdge+ , unsafeFromPoints+ , unsafeFromCircularVector+ , unsafeFromVector+ , toVector+ , toPoints - , polygonHoles, polygonHoles'- , holeList+ , isSimple - , inPolygon, insidePolygon, onBoundary+ -- * Accessors - , area, signedArea+ , size+ , polygonVertices, listEdges - , centroid- , pickPoint+ , outerBoundary, outerBoundaryVector+ , unsafeOuterBoundaryVector+ , outerBoundaryEdges+ , outerVertex, outerBoundaryEdge - , isTriangle, isStarShaped+ , polygonHoles, polygonHoles'+ , holeList - , isCounterClockwise- , toCounterClockWiseOrder, toCounterClockWiseOrder'- , toClockwiseOrder, toClockwiseOrder'- , reverseOuterBoundary+ -- * Properties - , findDiagonal+ , area, signedArea+ , centroid - , withIncidentEdges, numberVertices+ -- * Queries+ , inPolygon, insidePolygon, onBoundary - , asSimplePolygon- , extremesLinear, cmpExtreme- ) where + , isTriangle, isStarShaped++ , isCounterClockwise+ , toCounterClockWiseOrder, toCounterClockWiseOrder'+ , toClockwiseOrder, toClockwiseOrder'+ , reverseOuterBoundary++ , rotateLeft+ , rotateRight+ , maximumVertexBy+ , minimumVertexBy+++ -- * Misc+ , pickPoint+ , findDiagonal++ , withIncidentEdges, numberVertices++ , extremesLinear, cmpExtreme++ , findRotateTo++ ) where++import Algorithms.Geometry.InPolygon import Algorithms.Geometry.LinearProgramming.LP2DRIC import Algorithms.Geometry.LinearProgramming.Types import Control.Lens hiding (Simple) import Control.Monad.Random.Class import Data.Ext import qualified Data.Foldable as F+import Data.Geometry.Boundary import Data.Geometry.HalfSpace (rightOf) import Data.Geometry.Line+import Data.Geometry.LineSegment import Data.Geometry.Point import Data.Geometry.Polygon.Core import Data.Geometry.Polygon.Extremes-+import Data.Geometry.Properties+import Data.Ord (comparing)+import qualified Data.Sequence as Seq -------------------------------------------------------------------------------- -- * Polygons@@ -76,3 +113,42 @@ -- the first vertex is the intersection point of the two supporting lines -- bounding it, so the first two edges bound the shape in this sirection hs = fmap (rightOf . supportingLine) . outerBoundaryEdges $ pg+++--------------------------------------------------------------------------------+-- * Instances++type instance IntersectionOf (Line 2 r) (Boundary (Polygon t p r)) =+ '[Seq.Seq (Either (Point 2 r) (LineSegment 2 () r))]++type instance IntersectionOf (Point 2 r) (Polygon t p r) = [NoIntersection, Point 2 r]++instance (Fractional r, Ord r) => Point 2 r `HasIntersectionWith` Polygon t p r where+ q `intersects` pg = q `inPolygon` pg /= Outside++instance (Fractional r, Ord r) => Point 2 r `IsIntersectableWith` Polygon t p r where+ nonEmptyIntersection = defaultNonEmptyIntersection+ q `intersect` pg | q `intersects` pg = coRec q+ | otherwise = coRec NoIntersection++-- instance IsIntersectableWith (Line 2 r) (Boundary (Polygon t p r)) where+-- nonEmptyIntersection _ _ (CoRec xs) = null xs+-- l `intersect` (Boundary (SimplePolygon vs)) =+-- undefined+ -- l `intersect` (Boundary (MultiPolygon vs hs)) = coRec .+ -- Seq.sortBy f . Seq.fromList+ -- . concatMap (unpack . (l `intersect`) . Boundary)+ -- $ SimplePolygon vs : hs+ -- where+ -- unpack (CoRec x) = x+ -- f = undefined++instance (Fractional r, Ord r) => HasSquaredEuclideanDistance (Boundary (Polygon t p r)) where+ pointClosestToWithDistance q = F.minimumBy (comparing snd)+ . fmap (pointClosestToWithDistance q)+ . listEdges . review _Boundary++instance (Fractional r, Ord r) => HasSquaredEuclideanDistance (Polygon t p r) where+ pointClosestToWithDistance q pg+ | q `intersects` pg = (q, 0)+ | otherwise = pointClosestToWithDistance q (Boundary pg)
+ src/Data/Geometry/Polygon/Bezier.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Data.Geometry.Polygon.Bezier+ ( PathJoin(..)+ , fromBeziers+ , approximate+ , approximateSome+ ) where++import Control.Lens+import Data.Ext+import Data.Geometry.BezierSpline (BezierSpline, pattern Bezier3)+import qualified Data.Geometry.BezierSpline as Bezier+import Data.Geometry.Point+import Data.Geometry.PolyLine(points)+import Data.Geometry.Polygon+import qualified Data.Vector.Circular as CV+import qualified Data.Foldable as F++data PathJoin r+ = JoinLine+ | JoinCurve (Point 2 r) (Point 2 r)+ deriving (Show, Eq, Ord)++-- | Construct a polygon from a closed set of bezier curves. Each curve must be connected to+-- its neighbours.+fromBeziers :: (Eq r, Num r) => [BezierSpline 3 2 r] -> SimplePolygon (PathJoin r) r+fromBeziers curves+ | isCounterClockwise expanded = p+ | otherwise = p'+ where+ p = unsafeFromPoints+ [ a :+ JoinCurve b c+ | Bezier3 a b c _d <- curves ]+ p' = unsafeFromPoints+ [ d :+ JoinCurve c b+ | Bezier3 _a b c d <- reverse curves ]+ expanded = unsafeFromPoints $ concat+ [ map ext [a, b, c]+ | Bezier3 a b c _d <- curves ]++approximate :: forall t r. (Ord r, Fractional r) => r -> Polygon t (PathJoin r) r -> Polygon t () r+approximate eps p =+ case p of+ SimplePolygon{} ->+ let vs = p^.outerBoundaryVector+ in unsafeFromCircularVector $ CV.concatMap f $ CV.zip vs (CV.rotateRight 1 vs)+ MultiPolygon v hs -> MultiPolygon (approximate eps v) (map (approximate eps) hs)+ where+ f :: (Point 2 r :+ PathJoin r, Point 2 r :+ PathJoin r) -> CV.CircularVector (Point 2 r :+ ())+ f (a :+ JoinLine, _) = CV.singleton (ext a)+ f (a :+ JoinCurve b c, d :+ _) = let poly = Bezier.approximate eps (Bezier3 a b c d)+ in CV.unsafeFromList . init . F.toList $ poly^.points++approximateSome :: (Ord r, Fractional r) => r -> SomePolygon (PathJoin r) r -> SomePolygon () r+approximateSome eps (Left p) = Left $ approximate eps p+approximateSome eps (Right p) = Right $ approximate eps p
src/Data/Geometry/Polygon/Convex.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TemplateHaskell #-} -------------------------------------------------------------------------------- -- | -- Module : Data.Geometry.Polygon.Convex@@ -10,54 +9,82 @@ -- Convex Polygons -- ---------------------------------------------------------------------------------module Data.Geometry.Polygon.Convex( ConvexPolygon(..), simplePolygon- , merge- , lowerTangent, lowerTangent'- , upperTangent, upperTangent'+module Data.Geometry.Polygon.Convex+ ( ConvexPolygon(..), simplePolygon+ , convexPolygon+ , isConvex, verifyConvex+ , merge+ , lowerTangent, lowerTangent'+ , upperTangent, upperTangent' - , extremes- , maxInDirection+ , extremes+ , maxInDirection - , leftTangent, rightTangent+ , leftTangent, rightTangent - , minkowskiSum- , bottomMost- ) where+ , minkowskiSum+ , bottomMost+ , inConvex+ , randomConvex -import Control.DeepSeq-import Control.Lens hiding ((:<), (:>))-import Data.CircularSeq (CSeq)-import qualified Data.CircularSeq as C+ , diameter+ , diametralPair+ , diametralIndexPair+ ) where+++import Control.DeepSeq (NFData)+import Control.Lens (Iso, iso, over, view, (%~), (&), (^.))+import Control.Monad.Random+import Control.Monad.ST+import Control.Monad.State+import Data.Coerce import Data.Ext-import qualified Data.Foldable as F-import Data.Function (on)-import Data.Geometry.Box (IsBoxable(..))+import qualified Data.Foldable as F+import Data.Function (on)+import Data.Geometry.Boundary+import Data.Geometry.Box (IsBoxable (..)) import Data.Geometry.LineSegment import Data.Geometry.Point-import Data.Geometry.Polygon.Core (fromPoints, SimplePolygon, outerBoundary)-import Data.Geometry.Polygon.Extremes(cmpExtreme)+import Data.Geometry.Polygon.Core (Polygon (..), SimplePolygon, centroid,+ outerBoundaryVector, outerVertex, size,+ unsafeFromPoints, unsafeFromVector,+ unsafeOuterBoundaryVector)+import Data.Geometry.Polygon.Extremes (cmpExtreme) import Data.Geometry.Properties import Data.Geometry.Transformation+import Data.Geometry.Triangle import Data.Geometry.Vector-import Data.List.NonEmpty (NonEmpty(..))-import qualified Data.List.NonEmpty as NonEmpty-import Data.Maybe (fromJust)-import Data.Ord (comparing)-import Data.Semigroup.Foldable (Foldable1(..))-import Data.Sequence (viewl,viewr, ViewL(..), ViewR(..))-import qualified Data.Sequence as S+import qualified Data.IntSet as IS+import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.List.NonEmpty as NonEmpty+import Data.Maybe (fromJust)+import Data.Ord (comparing)+import Data.Semigroup.Foldable (Foldable1 (..)) import Data.Util-+import qualified Data.Vector as V+import Data.Vector.Circular (CircularVector)+import qualified Data.Vector.Circular as CV+import qualified Data.Vector.Circular.Util as CV+import qualified Data.Vector.Mutable as Mut+import qualified Data.Vector.NonEmpty as NE+import qualified Data.Vector.Unboxed as VU -- import Data.Geometry.Ipe--- import Debug.Trace+-- import Data.Ratio+-- import Data.RealNumber.Rational+-- import Debug.Trace -------------------------------------------------------------------------------- -- | Data Type representing a convex polygon newtype ConvexPolygon p r = ConvexPolygon {_simplePolygon :: SimplePolygon p r } deriving (Show,Eq,NFData)-makeLenses ''ConvexPolygon +-- | ConvexPolygons are isomorphic to SimplePolygons with the added constraint that they have no+-- reflex vertices.+simplePolygon :: Iso (ConvexPolygon p1 r1) (ConvexPolygon p2 r2) (SimplePolygon p1 r1) (SimplePolygon p2 r2)+simplePolygon = iso _simplePolygon ConvexPolygon+ instance PointFunctor (ConvexPolygon p) where pmap f (ConvexPolygon p) = ConvexPolygon $ pmap f p @@ -73,14 +100,110 @@ boundingBox = boundingBox . _simplePolygon --- convexPolygon :: SimplePolygon p r -> Maybe (ConvexPolygon p r)--- convexPolygon p = if isConvex p then Just p else Nothing --- isConvex :: SimplePolygon p r -> Bool--- isConvex p = let ch = convexHull $ p^.vertices--- in p^.vertices.size == ch^.simplePolygon.vertices.size+--------------------------------------------------------------------------------+-- Convex hull of simple polygon. +type M s v a = StateT (Mut.MVector s v, Int) (ST s) a +runM :: Int -> M s v () -> ST s (Mut.MVector s v)+runM s action = do+ v <- Mut.new (2*s)+ (v', f) <- execStateT action (Mut.drop s v, 0)+ return $ Mut.tail $ Mut.take f v'++dequeRemove :: M s a ()+dequeRemove = do+ modify $ \(Mut.MVector offset len arr, f) -> (Mut.MVector (offset+1) (len-1) arr, f-1)++dequeInsert :: a -> M s a ()+dequeInsert a = do+ modify $ \(Mut.MVector offset len arr, f) -> (Mut.MVector (offset-1) (len+1) arr, f+1)+ (v,_) <- get+ Mut.write v 0 a++dequePush :: a -> M s a ()+dequePush a = do+ (v, f) <- get+ Mut.write v f a+ put (v,f+1)++dequePop :: M s a ()+dequePop = do+ modify $ \(v,f) -> (v,f-1)++dequeBottom :: Int -> M s a a+dequeBottom idx = do+ (v,_) <- get+ Mut.read v idx++dequeTop :: Int -> M s a a+dequeTop idx = do+ (v,f) <- get+ Mut.read v (f-idx-1)++-- Melkman's algorithm: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.512.9681&rep=rep1&type=pdf++-- | \( O(n) \) Convex hull of a simple polygon.+--+-- For algorithmic details see: <https://en.wikipedia.org/wiki/Convex_hull_of_a_simple_polygon>+convexPolygon :: forall t p r. (Ord r, Num r, Show r, Show p) => Polygon t p r -> ConvexPolygon p r+convexPolygon p = ConvexPolygon $ unsafeFromVector $ V.create $ runM (size p) $+ findStartingPoint 2+ where+ -- Find the first spot where 0,n-1,n is not colinear.+ findStartingPoint :: Int -> M s (Point 2 r :+ p) ()+ findStartingPoint nth = do+ let vPrev = NE.unsafeIndex vs (nth-1)+ vNth = NE.unsafeIndex vs nth+ case ccw' v1 vPrev vNth of+ CoLinear -> findStartingPoint (nth+1)+ CCW -> do+ dequePush v1 >> dequePush vPrev+ dequePush vNth; dequeInsert vNth+ V.mapM_ build (NE.drop (nth+1) vs)+ CW -> do+ dequePush vPrev >> dequePush v1+ dequePush vNth; dequeInsert vNth+ V.mapM_ build (NE.drop (nth+1) vs)++ v1 = NE.unsafeIndex vs 0+ vs = CV.vector (p^.outerBoundaryVector)+ build v = do+ botTurn <- ccw' <$> pure v <*> dequeBottom 0 <*> dequeBottom 1+ topTurn <- ccw' <$> dequeTop 1 <*> dequeTop 0 <*> pure v+ when (botTurn == CW || topTurn == CW) $ do+ backtrackTop v; dequePush v+ backtrackBot v; dequeInsert v+ backtrackTop v = do+ turn <- ccw' <$> dequeTop 1 <*> dequeTop 0 <*> pure v+ unless (turn == CCW) $ do+ dequePop+ backtrackTop v+ backtrackBot v = do+ turn <- ccw' <$> pure v <*> dequeBottom 0 <*> dequeBottom 1+ unless (turn == CCW) $ do+ dequeRemove+ backtrackBot v++++++++-- | \( O(n) \) Check if a polygon is strictly convex.+isConvex :: (Ord r, Num r) => SimplePolygon p r -> Bool+isConvex s =+ CV.and (CV.zipWith3 f (CV.rotateLeft 1 vs) vs (CV.rotateRight 1 vs))+ where+ f a b c = ccw' a b c == CCW+ vs = s ^. outerBoundaryVector++-- | \( O(n) \) Verify that a convex polygon is strictly convex.+verifyConvex :: (Ord r, Num r) => ConvexPolygon p r -> Bool+verifyConvex = isConvex . _simplePolygon+ -- mainWith inFile outFile = do -- ePage <- readSinglePageFile inFile -- case ePage of@@ -115,13 +238,40 @@ -- -- pre: The input polygon is strictly convex. ----- running time: \(O(\log^2 n)\)+-- running time: \(O(\log n)\) maxInDirection :: (Num r, Ord r) => Vector 2 r -> ConvexPolygon p r -> Point 2 r :+ p maxInDirection u = findMaxWith (cmpExtreme u) +-- FIXME: c+1 is always less than n so we don't need to use `mod` or do bounds checking.+-- Use unsafe indexing.+-- \( O(\log n) \)+findMaxWith :: (Point 2 r :+ p -> Point 2 r :+ p -> Ordering)+ -> ConvexPolygon p r -> Point 2 r :+ p+findMaxWith cmp p = CV.index v (worker 0 (F.length v))+ where+ v = p ^. simplePolygon.outerBoundaryVector+ a `icmp` b = CV.index v a `cmp` CV.index v b+ worker a b+ | localMaximum c = c+ | a+1==b = b+ | otherwise =+ case (isUpwards a, isUpwards c, c `icmp` a /= LT) of+ (True, False, _) -> worker a c -- A is up, C is down, pick [a,c]+ (True, True, True) -> worker c b -- A is up, C is up, C is GTE A, pick [c,b]+ (True, True, False) -> worker a c -- A is up, C is LT A, pick [a,c]+ (False, True, _) -> worker c b -- A is down, C is up, pick [c,b]+ (False, False, False) -> worker c b -- A is down, C is down, C is LT A, pick [c,b]+ (False, _, True) -> worker a c -- A is down, C is GTE A, pick [a,c]+ where+ c = (a+b) `div` 2+ localMaximum idx = idx `icmp` (c-1) == GT && idx `icmp` (c+1) == GT+ isUpwards idx = idx `icmp` (idx+1) /= GT++{- Convex binary search using sequences in \( O(log^2 n) \)+ findMaxWith :: (Point 2 r :+ p -> Point 2 r :+ p -> Ordering) -> ConvexPolygon p r -> Point 2 r :+ p-findMaxWith cmp p = findMaxStart . C.rightElements . getVertices $ p+findMaxWith cmp = findMaxStart . S.fromList . F.toList . getVertices where p' >=. q = (p' `cmp` q) /= LT @@ -140,7 +290,7 @@ | otherwise = binSearch ac c cb -- | Given the vertices [a..] c [..b] find the exteral vtx- binSearch ac@(viewl -> a:<r) c cb = case (isUpwards a r, isUpwards c cb, a >=. c) of+ binSearch ac@(viewl -> a:<r) c cb = case (isUpwards a (r |> c), isUpwards c cb, a >=. c) of (True,False,_) -> findMax (ac |> c) (True,True,True) -> findMax (ac |> c) (True,True,False) -> findMax (c <| cb)@@ -157,7 +307,7 @@ -- the Edge from a to b is upwards w.r.t b if a is not larger than b isUpwards a (viewl -> b :< _) = (a `cmp` b) /= GT isUpwards _ _ = error "isUpwards: no edge endpoint"-+-} tangentCmp :: (Num r, Ord r) => Point 2 r -> Point 2 r :+ p -> Point 2 r :+ q -> Ordering@@ -167,19 +317,19 @@ CW -> GT -- q is right of the line from o to p --- | Given a convex polygon poly, and a point outside the polygon, find the+-- | Given a convex polygon poly, and a point outside the polygon, find the -- left tangent of q and the polygon, i.e. the vertex v of the convex polygon -- s.t. the polygon lies completely to the right of the line from q to v. ----- running time: \(O(\log^2 n)\).+-- running time: \(O(\log n)\). leftTangent :: (Ord r, Num r) => ConvexPolygon p r -> Point 2 r -> Point 2 r :+ p leftTangent poly q = findMaxWith (tangentCmp q) poly --- | Given a convex polygon poly, and a point outside the polygon, find the+-- | Given a convex polygon poly, and a point outside the polygon, find the -- right tangent of q and the polygon, i.e. the vertex v of the convex polygon -- s.t. the polygon lies completely to the left of the line from q to v. ----- running time: \(O(\log^2 n)\).+-- running time: \(O(\log n)\). rightTangent :: (Ord r, Num r) => ConvexPolygon p r -> Point 2 r -> Point 2 r :+ p rightTangent poly q = findMaxWith (flip $ tangentCmp q) poly @@ -209,21 +359,21 @@ -- Running time: O(n+m), where n and m are the sizes of the two polygons respectively merge :: (Num r, Ord r) => ConvexPolygon p r -> ConvexPolygon p r -> (ConvexPolygon p r, LineSegment 2 p r, LineSegment 2 p r)-merge lp rp = (ConvexPolygon . fromPoints $ r' ++ l', lt, ut)+merge lp rp = (ConvexPolygon . unsafeFromPoints $ r' ++ l', lt, ut) where lt@(ClosedLineSegment a b) = lowerTangent lp rp ut@(ClosedLineSegment c d) = upperTangent lp rp takeUntil p xs = let (xs',x:_) = break p xs in xs' ++ [x]- rightElems = F.toList . C.rightElements+ rightElems = F.toList . CV.rightElements takeAndRotate x y = takeUntil (coreEq x) . rightElems . rotateTo' y . getVertices r' = takeAndRotate b d rp l' = takeAndRotate c a lp -rotateTo' :: Eq a => (a :+ b) -> CSeq (a :+ b) -> CSeq (a :+ b)-rotateTo' x = fromJust . C.findRotateTo (coreEq x)+rotateTo' :: Eq a => (a :+ b) -> CircularVector (a :+ b) -> CircularVector (a :+ b)+rotateTo' x = fromJust . CV.findRotateTo (coreEq x) coreEq :: Eq a => (a :+ b) -> (a :+ b) -> Bool coreEq = (==) `on` (^.core)@@ -246,9 +396,8 @@ -> LineSegment 2 p r lowerTangent lp rp = ClosedLineSegment l r where- mkH f = NonEmpty.fromList . F.toList . f . getVertices- lh = mkH (C.rightElements . rightMost) lp- rh = mkH (C.leftElements . leftMost) rp+ lh = CV.rightElements . rightMost . getVertices $ lp+ rh = CV.leftElements . leftMost . getVertices $ rp (Two (l :+ _) (r :+ _)) = lowerTangent' lh rh -- | Compute the lower tangent of the two convex chains lp and rp@@ -286,16 +435,15 @@ -- the two polygons. -- - The vertices of the polygons are given in clockwise order ----- Running time: O(n+m), where n and m are the sizes of the two polygons respectively+-- Running time: \( O(n+m) \), where n and m are the sizes of the two polygons respectively upperTangent :: (Num r, Ord r) => ConvexPolygon p r -> ConvexPolygon p r -> LineSegment 2 p r upperTangent lp rp = ClosedLineSegment l r where- mkH f = NonEmpty.fromList . F.toList . f . getVertices- lh = mkH (C.leftElements . rightMost) lp- rh = mkH (C.rightElements . leftMost) rp+ lh = CV.leftElements . rightMost . getVertices $ lp+ rh = CV.rightElements . leftMost . getVertices $ rp (Two (l :+ _) (r :+ _)) = upperTangent' lh rh -- | Compute the upper tangent of the two convex chains lp and rp@@ -335,14 +483,14 @@ -- running time: \(O(n+m)\). minkowskiSum :: (Ord r, Num r) => ConvexPolygon p r -> ConvexPolygon q r -> ConvexPolygon (p,q) r-minkowskiSum p q = ConvexPolygon . fromPoints $ merge' (f p) (f q)+minkowskiSum p q = ConvexPolygon . unsafeFromPoints $ merge' (f p) (f q) where- f p' = let xs@(S.viewl -> (v :< _)) = C.asSeq . bottomMost . getVertices $ p'- in F.toList $ xs |> v- (v :+ ve) .+. (w :+ we) = v .+^ (toVec w) :+ (ve,we)+ f p' = let (v:xs) = F.toList . bottomMost . getVertices $ p'+ in v:xs++[v]+ (v :+ ve) .+. (w :+ we) = v .+^ toVec w :+ (ve,we) cmpAngle v v' w w' =- ccwCmpAround (ext $ origin) (ext . Point $ v' .-. v) (ext . Point $ w' .-. w)+ ccwCmpAround origin (Point $ v' .-. v) (Point $ w' .-. w) merge' [_] [_] = [] merge' vs@[v] (w:ws) = v .+. w : merge' vs ws@@ -352,32 +500,117 @@ LT -> merge' (v':vs) (w:w':ws) GT -> merge' (v:v':vs) (w':ws) EQ -> merge' (v':vs) (w':ws)- merge' _ _ = error $ "minkowskiSum: Should not happen"+ merge' _ _ = error "minkowskiSum: Should not happen" +--------------------------------------------------------------------------------+-- inConvex +-- 1. Check if p is on left edge or right edge.+-- 2. Do binary search:+-- Find the largest n where p is on the right of 0 to n.+-- 3. Check if p is on segment n,n+1+-- 4. Check if p is in triangle 0,n,n+1 +-- | \( O(\log n) \)+-- Check if a point lies inside a convex polygon, on the boundary, or outside of the+-- convex polygon.+inConvex :: forall p r. (Fractional r, Ord r)+ => Point 2 r -> ConvexPolygon p r+ -> PointLocationResult+inConvex p (ConvexPolygon poly)+ | p `intersects` leftEdge = OnBoundary+ | p `intersects` rightEdge = OnBoundary+ | otherwise = worker 1 n+ where+ p' = p :+ undefined+ n = size poly - 1+ point0 = point 0+ leftEdge = ClosedLineSegment point0 (point n)+ rightEdge = ClosedLineSegment point0 (point 1)+ worker a b+ | a+1 == b =+ if p `intersects` (ClosedLineSegment (point a) (point b))+ then OnBoundary+ else+ if inTriangle p (Triangle point0 (point a) (point b)) == Outside+ then Outside+ else Inside+ | ccw' point0 (point c) p' == CCW = worker c b+ | otherwise = worker a c+ where c = (a+b) `div` 2+ point x = poly ^. outerVertex x+ --------------------------------------------------------------------------------+-- Diameter +-- | \( O(n) \) Computes the Euclidean diameter by scanning antipodal pairs.+diameter :: (Ord r, Floating r) => ConvexPolygon p r -> r+diameter p = euclideanDist (a^.core) (b^.core)+ where+ (a,b) = diametralPair p++-- | \( O(n) \)+-- Computes the Euclidean diametral pair by scanning antipodal pairs.+diametralPair :: (Ord r, Num r) => ConvexPolygon p r -> (Point 2 r :+ p, Point 2 r :+ p)+diametralPair p = (p^.simplePolygon.outerVertex a, p^.simplePolygon.outerVertex b)+ where+ (a,b) = diametralIndexPair p++-- | \( O(n) \)+-- Computes the Euclidean diametral pair by scanning antipodal pairs.+diametralIndexPair :: (Ord r, Num r) => ConvexPolygon p r -> (Int, Int)+diametralIndexPair p = F.maximumBy fn $ antipodalPairs p+ where+ fn (a1,b1) (a2,b2) =+ squaredEuclideanDist (p^.simplePolygon.outerVertex a1.core) (p^.simplePolygon.outerVertex b1.core)+ `compare`+ squaredEuclideanDist (p^.simplePolygon.outerVertex a2.core) (p^.simplePolygon.outerVertex b2.core)++antipodalPairs :: forall p r. (Ord r, Num r) => ConvexPolygon p r -> [(Int, Int)]+antipodalPairs p = worker 0 (CV.index vectors 0) 1+ where+ n = size (p^.simplePolygon)+ vs = p^.simplePolygon.outerBoundaryVector++ worker a aElt b+ | a == n = []+ | otherwise =+ case ccw aElt (Point2 0 0) (CV.index vectors b) of+ CW -> worker a aElt (b+1)+ _ ->+ (a, b `mod` n) :+ worker (a+1) (CV.index vectors (a+1)) b++ vectors :: CircularVector (Point 2 r)+ vectors = CV.unsafeFromVector $ V.generate n $ \i ->+ let Point p1 = point i+ p2 = point (i+1)+ in p2 .-^ p1++ point x = CV.index vs x ^. core++--------------------------------------------------------------------------------+ -- | Rotate to the rightmost point (rightmost and topmost in case of ties)-rightMost :: Ord r => CSeq (Point 2 r :+ p) -> CSeq (Point 2 r :+ p)-rightMost xs = let m = F.maximumBy (comparing (^.core)) xs in rotateTo' m xs+rightMost :: Ord r => CircularVector (Point 2 r :+ p) -> CircularVector (Point 2 r :+ p)+rightMost = CV.rotateToMaximumBy (comparing (^.core)) -- | Rotate to the leftmost point (and bottommost in case of ties)-leftMost :: Ord r => CSeq (Point 2 r :+ p) -> CSeq (Point 2 r :+ p)-leftMost xs = let m = F.minimumBy (comparing (^.core)) xs in rotateTo' m xs+leftMost :: Ord r => CircularVector (Point 2 r :+ p) -> CircularVector (Point 2 r :+ p)+leftMost = CV.rotateToMinimumBy (comparing (^.core)) -- | Rotate to the bottommost point (and leftmost in case of ties)-bottomMost :: Ord r => CSeq (Point 2 r :+ p) -> CSeq (Point 2 r :+ p)-bottomMost xs = let f p = (p^.core.yCoord,p^.core.xCoord)- m = F.minimumBy (comparing f) xs- in rotateTo' m xs+bottomMost :: Ord r => CircularVector (Point 2 r :+ p) -> CircularVector (Point 2 r :+ p)+bottomMost = CV.rotateToMinimumBy (comparing f)+ where+ f p = (p^.core.yCoord,p^.core.xCoord) -- | Helper to get the vertices of a convex polygon-getVertices :: ConvexPolygon p r -> CSeq (Point 2 r :+ p)-getVertices = view (simplePolygon.outerBoundary)+getVertices :: ConvexPolygon p r -> CircularVector (Point 2 r :+ p)+getVertices = view (simplePolygon.outerBoundaryVector) -- -- | rotate right while p 'current' 'rightNeibhour' is true -- rotateRWhile :: (a -> a -> Bool) -> C.CList a -> C.CList a@@ -401,3 +634,76 @@ -- testB :: Num r => ConvexPolygon () r -- testB = ConvexPolygon . fromPoints . map ext $ [origin, Point2 5 3, Point2 (-2) 2, Point2 (-2) 1]+++++--------------------------------------------------------------------------------+-- Random convex polygons++-- This is true for all convex polygons:+-- 1. the sum of all edge vectors is (0,0). This is even true for all polygons.+-- 2. edges are sorted by angle. Ie. all vertices are convex, not reflex.+--+-- So, if we can generate a set of vectors that sum to zero then we can sort them+-- and place them end-to-end and the result will be a convex polygon.+--+-- So, we need to generate N points that sum to 0. This can be done by generating+-- two sets of N points that sum to M, and the subtracting them from each other.+--+-- Generating N points that sum to M is done like this: Generate (N-1) unique points+-- between (but not including) 0 and M. Write down the distance between the points.+-- Imagine a scale from 0 to M:+-- 0 M+-- | |+-- Then we add two randomly selected points:+-- 0 M+-- | * * |+-- Then we look at the distance between 0 and point1, point1 and point2, and point2 to M:+-- 0 M+-- |--*------*--|+-- 2 6 2+-- 2+6+2 = 10 = M+--+-- Doing this again might yield [5,2,3]. Subtract them:+-- [2, 6, 2 ]+-- - [5, 2, 3 ]+-- = [2-5, 6-2, 2-3]+-- = [-3, 4, -1 ]+-- And the sum of [-3, 4, -1] = -3+4-1 = 0.++-- O(n log n)+randomBetween :: RandomGen g => Int -> Int -> Rand g (VU.Vector Int)+randomBetween n vMax | vMax < n+1 = pure $ VU.replicate vMax 1+randomBetween n vMax = worker (n-1) IS.empty+ where+ gen from [] = [vMax-from]+ gen from (x:xs) = (x-from) : gen x xs+ worker 0 seen = pure (VU.fromList (gen 0 $ IS.elems seen))+ worker i seen = do+ v <- getRandomR (1, vMax-1)+ if IS.member v seen+ then worker i seen+ else worker (i-1) (IS.insert v seen)++randomBetweenZero :: RandomGen g => Int -> Int -> Rand g (VU.Vector Int)+randomBetweenZero n vMax = VU.zipWith (-) <$> randomBetween n vMax <*> randomBetween n vMax++randomEdges :: RandomGen g => Int -> Int -> Rand g [Vector 2 Int]+randomEdges n vMax = do+ zipWith Vector2+ <$> fmap VU.toList (randomBetweenZero n vMax)+ <*> fmap VU.toList (randomBetweenZero n vMax)++-- | \( O(n \log n) \)+-- Generate a uniformly random ConvexPolygon with @N@ vertices and a granularity of @vMax@.+randomConvex :: RandomGen g => Int -> Int -> Rand g (ConvexPolygon () Rational)+randomConvex n _vMax | n < 3 =+ error "Data.Geometry.Polygon.Convex.randomConvex: At least 3 edges are required."+randomConvex n vMax = do+ ~(v:vs) <- coerce . sortAround origin . coerce <$> randomEdges n vMax+ let vertices = fmap ((/ realToFrac vMax) . realToFrac) <$> scanl (.+^) (Point v) vs+ pRational = unsafeFromPoints $ map ext vertices+ Point c = centroid pRational+ pFinal = pRational & unsafeOuterBoundaryVector %~ CV.map (over core (.-^ c))+ pure $ ConvexPolygon pFinal
src/Data/Geometry/Polygon/Core.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE OverloadedStrings #-} -------------------------------------------------------------------------------- -- | -- Module : Data.Geometry.Polygon.Core@@ -8,101 +9,167 @@ -- A Polygon data type and some basic functions to interact with them. -- ---------------------------------------------------------------------------------module Data.Geometry.Polygon.Core( PolygonType(..)- , Polygon(..)- , _SimplePolygon, _MultiPolygon- , SimplePolygon, MultiPolygon, SomePolygon+module Data.Geometry.Polygon.Core+ ( PolygonType(..)+ , Polygon(..)+ , Vertices+ , _SimplePolygon, _MultiPolygon+ , SimplePolygon, MultiPolygon, SomePolygon + -- * Construction+ , fromPoints+ , fromCircularVector - , fromPoints+ , simpleFromPoints+ , simpleFromCircularVector - , polygonVertices, listEdges+ , unsafeFromPoints+ , unsafeFromCircularVector+ , unsafeFromVector+ , toVector+ , toPoints - , outerBoundary, outerBoundaryEdges- , outerVertex, outerBoundaryEdge+ , isSimple - , polygonHoles, polygonHoles'- , holeList+ , size+ , polygonVertices, listEdges - , inPolygon, insidePolygon, onBoundary+ , outerBoundary, outerBoundaryVector+ , unsafeOuterBoundaryVector+ , outerBoundaryEdges+ , outerVertex, unsafeOuterVertex+ , outerBoundaryEdge - , area, signedArea+ , polygonHoles, polygonHoles'+ , holeList - , centroid- , pickPoint+ , area, signedArea - , isTriangle+ , centroid+ , pickPoint - , isCounterClockwise- , toCounterClockWiseOrder, toCounterClockWiseOrder'- , toClockwiseOrder, toClockwiseOrder'- , reverseOuterBoundary+ , isTriangle - , findDiagonal+ , isCounterClockwise+ , toCounterClockWiseOrder, toCounterClockWiseOrder'+ , toClockwiseOrder, toClockwiseOrder'+ , reverseOuterBoundary - , withIncidentEdges, numberVertices+ , findDiagonal - , asSimplePolygon- ) where+ , withIncidentEdges, numberVertices + -- * Testing for Reflex or Convex++ , isReflexVertex, isConvexVertex, isStrictlyConvexVertex+ , reflexVertices, convexVertices, strictlyConvexVertices++ -- * Specialized folds+ , maximumVertexBy+ , minimumVertexBy+ , findRotateTo+ , rotateLeft+ , rotateRight+ ) where++import qualified Algorithms.Geometry.LineSegmentIntersection.BentleyOttmann as BO import Control.DeepSeq-import Control.Lens hiding (Simple)+import Control.Lens (Getter, Lens', Prism',+ Traversal', lens, over,+ prism', to, toListOf,+ view, (%~), (&), (.~),+ (^.))+import Data.Aeson import Data.Bifoldable import Data.Bifunctor import Data.Bitraversable-import qualified Data.CircularSeq as C import Data.Ext import qualified Data.Foldable as F import Data.Geometry.Boundary-import Data.Geometry.Box+import Data.Geometry.Box (IsBoxable (..),+ boundingBoxList') import Data.Geometry.Line import Data.Geometry.LineSegment import Data.Geometry.Point import Data.Geometry.Properties import Data.Geometry.Transformation-import Data.Geometry.Triangle (Triangle(..), inTriangle)-import Data.Geometry.Vector+import Data.Geometry.Triangle (Triangle (..),+ inTriangle)+import Data.Geometry.Vector (Additive (zero, (^+^)),+ Affine ((.+^), (.-.)),+ (*^), (^*), (^/)) import qualified Data.List as List-import Data.List.NonEmpty (NonEmpty(..)) import qualified Data.List.NonEmpty as NonEmpty-import Data.Maybe (mapMaybe, catMaybes)+import Data.Maybe (catMaybes) import Data.Ord (comparing) import Data.Semigroup (sconcat) import Data.Semigroup.Foldable-import qualified Data.Sequence as Seq import Data.Util-import Data.Vinyl.CoRec (asA)+import Data.Vector (Vector)+import qualified Data.Vector as V+import Data.Vector.Circular (CircularVector)+import qualified Data.Vector.Circular as CV+import qualified Data.Vector.Circular.Util as CV ++-- import Data.RealNumber.Rational+ -------------------------------------------------------------------------------- {- $setup+>>> import Data.RealNumber.Rational+>>> import Data.Foldable+>>> import Control.Lens.Extras >>> :{--- import qualified Data.CircularSeq as C-let simplePoly :: SimplePolygon () Rational- simplePoly = SimplePolygon . C.fromList . map ext $ [ Point2 0 0- , Point2 10 0- , Point2 10 10- , Point2 5 15- , Point2 1 11- ]+-- import qualified Data.Vector.Circular as CV+let simplePoly :: SimplePolygon () (RealNumber 10)+ simplePoly = fromPoints . map ext $+ [ Point2 0 0+ , Point2 10 0+ , Point2 10 10+ , Point2 5 15+ , Point2 1 11+ ]+ simpleTriangle :: SimplePolygon () (RealNumber 10)+ simpleTriangle = fromPoints . map ext $+ [ Point2 0 0, Point2 2 0, Point2 1 1]+ multiPoly :: MultiPolygon () (RealNumber 10)+ multiPoly = MultiPolygon+ (fromPoints . map ext $ [Point2 (-1) (-1), Point2 3 (-1), Point2 2 2])+ [simpleTriangle] :} -} --- | We distinguish between simple polygons (without holes) and Polygons with holes.+-- | We distinguish between simple polygons (without holes) and polygons with holes. data PolygonType = Simple | Multi -+-- | Polygons are sequences of points and may or may not contain holes.+--+-- Degenerate polygons (polygons with self-intersections or fewer than 3 points)+-- are only possible if you use functions marked as unsafe. data Polygon (t :: PolygonType) p r where- SimplePolygon :: C.CSeq (Point 2 r :+ p) -> Polygon Simple p r- MultiPolygon :: C.CSeq (Point 2 r :+ p) -> [Polygon Simple p r] -> Polygon Multi p r+ SimplePolygon :: Vertices (Point 2 r :+ p) -> SimplePolygon p r+ MultiPolygon :: SimplePolygon p r -> [SimplePolygon p r] -> MultiPolygon p r +newtype Vertices a = Vertices (CircularVector a)+ deriving (Functor, Foldable, Foldable1, Traversable, NFData, Eq, Ord)+ -- | Prism to 'test' if we are a simple polygon-_SimplePolygon :: Prism' (Polygon Simple p r) (C.CSeq (Point 2 r :+ p))+--+-- >>> is _SimplePolygon simplePoly+-- True+_SimplePolygon :: Prism' (Polygon Simple p r) (Vertices (Point 2 r :+ p)) _SimplePolygon = prism' SimplePolygon (\(SimplePolygon vs) -> Just vs) -- | Prism to 'test' if we are a Multi polygon-_MultiPolygon :: Prism' (Polygon Multi p r) (C.CSeq (Point 2 r :+ p), [Polygon Simple p r])+--+-- >>> is _MultiPolygon multiPoly+-- True+_MultiPolygon :: Prism' (Polygon Multi p r) (Polygon Simple p r, [Polygon Simple p r]) _MultiPolygon = prism' (uncurry MultiPolygon) (\(MultiPolygon vs hs) -> Just (vs,hs)) +instance Functor (Polygon t p) where+ fmap = bimap id+ instance Bifunctor (Polygon t) where bimap = bimapDefault @@ -112,7 +179,7 @@ instance Bitraversable (Polygon t) where bitraverse f g p = case p of SimplePolygon vs -> SimplePolygon <$> bitraverseVertices f g vs- MultiPolygon vs hs -> MultiPolygon <$> bitraverseVertices f g vs+ MultiPolygon vs hs -> MultiPolygon <$> bitraverse f g vs <*> traverse (bitraverse f g) hs instance (NFData p, NFData r) => NFData (Polygon t p r) where@@ -123,8 +190,10 @@ -> t (Point 2 r :+ p) -> f (t (Point 2 s :+ q)) bitraverseVertices f g = traverse (bitraverse (traverse g) f) +-- | Polygon without holes. type SimplePolygon = Polygon Simple +-- | Polygon with zero or more holes. type MultiPolygon = Polygon Multi -- | Either a simple or multipolygon@@ -138,9 +207,23 @@ type instance NumType (Polygon t p r) = r instance (Show p, Show r) => Show (Polygon t p r) where- show (SimplePolygon vs) = "SimplePolygon (" <> show vs <> ")"+ show (SimplePolygon vs) = "SimplePolygon " <> show (F.toList vs) show (MultiPolygon vs hs) = "MultiPolygon (" <> show vs <> ") (" <> show hs <> ")" +instance (Read p, Read r) => Read (SimplePolygon p r) where+ readsPrec d = readParen (d > app_prec) $ \r ->+ [ (unsafeFromPoints vs, t)+ | ("SimplePolygon", s) <- lex r, (vs, t) <- reads s ]+ where app_prec = 10++instance (Read p, Read r) => Read (MultiPolygon p r) where+ readsPrec d = readParen (d > app_prec) $ \r ->+ [ (MultiPolygon vs hs, t')+ | ("MultiPolygon", s) <- lex r+ , (vs, t) <- reads s+ , (hs, t') <- reads t ]+ where app_prec = 10+ -- instance (Read p, Read r) => Show (Polygon t p r) where -- show (SimplePolygon vs) = "SimplePolygon (" <> show vs <> ")" -- show (MultiPolygon vs hs) = "MultiPolygon (" <> show vs <> ") (" <> show hs <> ")"@@ -153,54 +236,92 @@ instance PointFunctor (Polygon t p) where pmap f (SimplePolygon vs) = SimplePolygon (fmap (first f) vs)- pmap f (MultiPolygon vs hs) = MultiPolygon (fmap (first f) vs) (map (pmap f) hs)+ pmap f (MultiPolygon vs hs) = MultiPolygon (pmap f vs) (map (pmap f) hs) instance Fractional r => IsTransformable (Polygon t p r) where transformBy = transformPointFunctor instance IsBoxable (Polygon t p r) where- boundingBox = boundingBoxList' . toListOf (outerBoundary.traverse.core)+ boundingBox = boundingBoxList' . toListOf (outerBoundaryVector.traverse.core) -type instance IntersectionOf (Line 2 r) (Boundary (Polygon t p r)) =- '[Seq.Seq (Either (Point 2 r) (LineSegment 2 () r))] -type instance IntersectionOf (Point 2 r) (Polygon t p r) = [NoIntersection, Point 2 r]+instance (ToJSON r, ToJSON p) => ToJSON (Polygon t p r) where+ toJSON = \case+ (SimplePolygon vs) -> object [ "tag" .= ("SimplePolygon" :: String)+ , "vertices" .= F.toList vs+ ]+ (MultiPolygon vs hs) -> object [ "tag" .= ("MultiPolygon" :: String)+ , "outerBoundary" .= getVertices vs+ , "holes" .= map getVertices hs+ ]+ where+ getVertices = view (outerBoundaryVector.to F.toList) -instance (Fractional r, Ord r) => (Point 2 r) `IsIntersectableWith` (Polygon t p r) where- nonEmptyIntersection = defaultNonEmptyIntersection- q `intersects` pg = q `inPolygon` pg /= Outside- q `intersect` pg | q `intersects` pg = coRec q- | otherwise = coRec NoIntersection+instance (FromJSON r, Eq r, Num r, FromJSON p) => FromJSON (Polygon Simple p r) where+ parseJSON = withObject "Polygon" $ \o -> o .: "tag" >>= \case+ "SimplePolygon" -> pSimple o+ (_ :: String) -> fail "Not a SimplePolygon"+ where+ pSimple o = fromPoints <$> o .: "vertices" --- instance IsIntersectableWith (Line 2 r) (Boundary (Polygon t p r)) where--- nonEmptyIntersection _ _ (CoRec xs) = null xs--- l `intersect` (Boundary (SimplePolygon vs)) =--- undefined- -- l `intersect` (Boundary (MultiPolygon vs hs)) = coRec .- -- Seq.sortBy f . Seq.fromList- -- . concatMap (unpack . (l `intersect`) . Boundary)- -- $ SimplePolygon vs : hs- -- where- -- unpack (CoRec x) = x- -- f = undefined+instance (FromJSON r, Eq r, Num r, FromJSON p) => FromJSON (Polygon Multi p r) where+ parseJSON = withObject "Polygon" $ \o -> o .: "tag" >>= \case+ "MultiPolygon" -> pMulti o+ (_ :: String) -> fail "Not a MultiPolygon"+ where+ pMulti o = (\vs hs -> MultiPolygon (fromPoints vs) (map fromPoints hs))+ <$> o .: "outerBoundary" <*> o .: "holes" +-- * Functions on Polygons +-- | Getter access to the outer boundary vector of a polygon.+--+-- >>> toList (simpleTriangle ^. outerBoundaryVector)+-- [Point2 0 0 :+ (),Point2 2 0 :+ (),Point2 1 1 :+ ()]+outerBoundaryVector :: forall t p r. Getter (Polygon t p r) (CircularVector (Point 2 r :+ p))+outerBoundaryVector = to g+ where+ g :: Polygon t p r -> CircularVector (Point 2 r :+ p)+ g (SimplePolygon (Vertices vs)) = vs+ g (MultiPolygon (SimplePolygon (Vertices vs)) _) = vs +-- | Unsafe lens access to the outer boundary vector of a polygon.+--+-- >>> toList (simpleTriangle ^. unsafeOuterBoundaryVector)+-- [Point2 0 0 :+ (),Point2 2 0 :+ (),Point2 1 1 :+ ()]+--+-- >>> simpleTriangle & unsafeOuterBoundaryVector .~ CV.singleton (Point2 0 0 :+ ())+-- SimplePolygon [Point2 0 0 :+ ()]+unsafeOuterBoundaryVector :: forall t p r. Lens' (Polygon t p r) (CircularVector (Point 2 r :+ p))+unsafeOuterBoundaryVector = lens g s+ where+ g :: Polygon t p r -> CircularVector (Point 2 r :+ p)+ g (SimplePolygon (Vertices vs)) = vs+ g (MultiPolygon (SimplePolygon (Vertices vs)) _) = vs --- * Functions on Polygons+ s :: Polygon t p r -> CircularVector (Point 2 r :+ p)+ -> Polygon t p r+ s SimplePolygon{} vs = SimplePolygon (Vertices vs)+ s (MultiPolygon _ hs) vs = MultiPolygon (SimplePolygon (Vertices vs)) hs -outerBoundary :: forall t p r. Lens' (Polygon t p r) (C.CSeq (Point 2 r :+ p))++-- | \( O(1) \) Lens access to the outer boundary of a polygon.+outerBoundary :: forall t p r. Lens' (Polygon t p r) (SimplePolygon p r) outerBoundary = lens g s where- g :: Polygon t p r -> C.CSeq (Point 2 r :+ p)- g (SimplePolygon vs) = vs- g (MultiPolygon vs _) = vs+ g :: Polygon t p r -> SimplePolygon p r+ g poly@SimplePolygon{} = poly+ g (MultiPolygon simple _) = simple - s :: Polygon t p r -> C.CSeq (Point 2 r :+ p)+ s :: Polygon t p r -> SimplePolygon p r -> Polygon t p r- s (SimplePolygon _) vs = SimplePolygon vs- s (MultiPolygon _ hs) vs = MultiPolygon vs hs+ s SimplePolygon{} simple = simple+ s (MultiPolygon _ hs) simple = MultiPolygon simple hs +-- | Lens access for polygon holes.+--+-- >>> multiPoly ^. polygonHoles+-- [SimplePolygon [Point2 0 0 :+ (),Point2 2 0 :+ (),Point2 1 1 :+ ()]] polygonHoles :: forall p r. Lens' (Polygon Multi p r) [Polygon Simple p r] polygonHoles = lens g s where@@ -210,16 +331,30 @@ -> Polygon Multi p r s (MultiPolygon vs _) = MultiPolygon vs +{- HLINT ignore polygonHoles' -}+-- | \( O(1) \). Traversal lens for polygon holes. Does nothing for simple polygons. polygonHoles' :: Traversal' (Polygon t p r) [Polygon Simple p r] polygonHoles' = \f -> \case- p@(SimplePolygon _) -> pure p- (MultiPolygon vs hs) -> MultiPolygon vs <$> f hs+ p@SimplePolygon{} -> pure p+ MultiPolygon vs hs -> MultiPolygon vs <$> f hs --- | Access the i^th vertex on the outer boundary-outerVertex :: Int -> Lens' (Polygon t p r) (Point 2 r :+ p)-outerVertex i = outerBoundary.C.item i+-- | /O(1)/ Access the i^th vertex on the outer boundary. Indices are modulo \(n\).+--+-- >>> simplePoly ^. outerVertex 0+-- Point2 0 0 :+ ()+outerVertex :: Int -> Getter (Polygon t p r) (Point 2 r :+ p)+outerVertex i = outerBoundaryVector . CV.item i --- running time: \(O(\log i)\)+-- | \( O(1) \) read and \( O(n) \) write. Access the i^th vertex on the outer boundary+--+-- >>> simplePoly ^. unsafeOuterVertex 0+-- Point2 0 0 :+ ()+-- >>> simplePoly & unsafeOuterVertex 0 .~ (Point2 10 10 :+ ())+-- SimplePolygon [Point2 10 10 :+ (),Point2 10 0 :+ (),Point2 10 10 :+ (),Point2 5 15 :+ (),Point2 1 11 :+ ()]+unsafeOuterVertex :: Int -> Lens' (Polygon t p r) (Point 2 r :+ p)+unsafeOuterVertex i = unsafeOuterBoundaryVector . CV.item i++-- | \( O(1) \) Get the n^th edge along the outer boundary of the polygon. The edge is half open. outerBoundaryEdge :: Int -> Polygon t p r -> LineSegment 2 p r outerBoundaryEdge i p = let u = p^.outerVertex i v = p^.outerVertex (i+1)@@ -228,36 +363,116 @@ -- | Get all holes in a polygon holeList :: Polygon t p r -> [Polygon Simple p r]-holeList (SimplePolygon _) = []+holeList SimplePolygon{} = [] holeList (MultiPolygon _ hs) = hs --- | The vertices in the polygon. No guarantees are given on the order in which+-- | \( O(1) \) Vertex count. Includes the vertices of holes.+size :: Polygon t p r -> Int+size (SimplePolygon (Vertices cv)) = F.length cv+size (MultiPolygon b hs) = sum (map size (b:hs))++-- | \( O(n) \) The vertices in the polygon. No guarantees are given on the order in which -- they appear! polygonVertices :: Polygon t p r -> NonEmpty.NonEmpty (Point 2 r :+ p)-polygonVertices (SimplePolygon vs) = toNonEmpty vs+polygonVertices p@SimplePolygon{} = toNonEmpty $ p^.outerBoundaryVector polygonVertices (MultiPolygon vs hs) =- sconcat $ toNonEmpty vs NonEmpty.:| map polygonVertices hs+ sconcat $ toNonEmpty (polygonVertices vs) NonEmpty.:| map polygonVertices hs +-- FIXME: Get rid of 'Fractional r' constraint.+-- | \( O(n \log n) \) Check if a polygon has any holes, duplicate points, or+-- self-intersections.+isSimple :: (Ord r, Fractional r) => Polygon p t r -> Bool+isSimple p@SimplePolygon{} = null . BO.interiorIntersections . map ext $ listEdges p+isSimple (MultiPolygon b []) = isSimple b+isSimple MultiPolygon{} = False --- | Creates a simple polygon from the given list of vertices.+requireThree :: String -> [a] -> [a]+requireThree _ lst@(_:_:_:_) = lst+requireThree label _ = error $+ "Data.Geometry.Polygon." ++ label ++ ": Polygons must have at least three points."++-- | \( O(n) \) Creates a polygon from the given list of vertices. --+-- The points are placed in CCW order if they are not already. Overlapping+-- edges and repeated vertices are allowed.+--+fromPoints :: forall p r. (Eq r, Num r) => [Point 2 r :+ p] -> SimplePolygon p r+fromPoints = fromCircularVector . CV.unsafeFromList . requireThree "fromPoints"++-- | \( O(n) \) Creates a polygon from the given vector of vertices.+--+-- The points are placed in CCW order if they are not already. Overlapping+-- edges and repeated vertices are allowed.+--+fromCircularVector :: forall p r. (Eq r, Num r) => CircularVector (Point 2 r :+ p) -> SimplePolygon p r+fromCircularVector = toCounterClockWiseOrder . unsafeFromCircularVector++-- | \( O(n \log n) \) Creates a simple polygon from the given list of vertices.+--+-- The points are placed in CCW order if they are not already. Overlapping+-- edges and repeated vertices are /not/ allowed and will trigger an exception.+--+simpleFromPoints :: forall p r. (Ord r, Fractional r) => [Point 2 r :+ p] -> SimplePolygon p r+simpleFromPoints =+ simpleFromCircularVector . CV.unsafeFromList . requireThree "simpleFromPoints"++-- | \( O(n \log n) \) Creates a simple polygon from the given vector of vertices.+--+-- The points are placed in CCW order if they are not already. Overlapping+-- edges and repeated vertices are /not/ allowed and will trigger an exception.+--+simpleFromCircularVector :: forall p r. (Ord r, Fractional r)+ => CircularVector (Point 2 r :+ p) -> SimplePolygon p r+simpleFromCircularVector v =+ let p = fromCircularVector v+ hasInteriorIntersections = not . null . BO.interiorIntersections . map ext+ in if hasInteriorIntersections (listEdges p)+ then error "Data.Geometry.Polygon.simpleFromCircularVector: \+ \Found self-intersections or repeated vertices."+ else p++-- | \( O(n) \) Creates a simple polygon from the given list of vertices.+-- -- pre: the input list constains no repeated vertices.-fromPoints :: [Point 2 r :+ p] -> SimplePolygon p r-fromPoints = SimplePolygon . C.fromList+unsafeFromPoints :: [Point 2 r :+ p] -> SimplePolygon p r+unsafeFromPoints = unsafeFromCircularVector . CV.unsafeFromList +-- | \( O(1) \) Creates a simple polygon from the given vector of vertices.+--+-- pre: the input list constains no repeated vertices.+unsafeFromCircularVector :: CircularVector (Point 2 r :+ p) -> SimplePolygon p r+unsafeFromCircularVector = SimplePolygon . Vertices --- | The edges along the outer boundary of the polygon. The edges are half open.+-- | \( O(1) \) Creates a simple polygon from the given vector of vertices. ----- running time: \(O(n)\)-outerBoundaryEdges :: Polygon t p r -> C.CSeq (LineSegment 2 p r)-outerBoundaryEdges = toEdges . (^.outerBoundary)+-- pre: the input list constains no repeated vertices.+unsafeFromVector :: Vector (Point 2 r :+ p) -> SimplePolygon p r+unsafeFromVector = unsafeFromCircularVector . CV.unsafeFromVector --- | Lists all edges. The edges on the outer boundary are given before the ones+-- -- | Polygon points, from left to right.+-- toList :: Polygon t p r -> [Point 2 r :+ p]+-- toList (SimplePolygon c) = F.toList c+-- toList (MultiPolygon s hs) = toList s ++ concatMap toList hs++-- | \( O(n) \)+-- Polygon points, from left to right.+toVector :: Polygon t p r -> Vector (Point 2 r :+ p)+toVector p@SimplePolygon{} = CV.toVector $ p^.outerBoundaryVector+toVector (MultiPolygon s hs) = foldr (<>) (toVector s) (map toVector hs)++-- | \( O(n) \)+-- Polygon points, from left to right.+toPoints :: Polygon t p r -> [Point 2 r :+ p]+toPoints = V.toList . toVector++-- | \( O(n) \) The edges along the outer boundary of the polygon. The edges are half open.+outerBoundaryEdges :: Polygon t p r -> CircularVector (LineSegment 2 p r)+outerBoundaryEdges = toEdges . (^.outerBoundaryVector)++-- | \( O(n) \) Lists all edges. The edges on the outer boundary are given before the ones -- on the holes. However, no other guarantees are given on the order.------ running time: \(O(n)\) listEdges :: Polygon t p r -> [LineSegment 2 p r] listEdges pg = let f = F.toList . outerBoundaryEdges in f pg <> concatMap f (holeList pg)@@ -268,20 +483,21 @@ -- -- -- >>> mapM_ print . polygonVertices $ withIncidentEdges simplePoly--- Point2 [0 % 1,0 % 1] :+ SP LineSegment (Closed (Point2 [1 % 1,11 % 1] :+ ())) (Closed (Point2 [0 % 1,0 % 1] :+ ())) LineSegment (Closed (Point2 [0 % 1,0 % 1] :+ ())) (Closed (Point2 [10 % 1,0 % 1] :+ ()))--- Point2 [10 % 1,0 % 1] :+ SP LineSegment (Closed (Point2 [0 % 1,0 % 1] :+ ())) (Closed (Point2 [10 % 1,0 % 1] :+ ())) LineSegment (Closed (Point2 [10 % 1,0 % 1] :+ ())) (Closed (Point2 [10 % 1,10 % 1] :+ ()))--- Point2 [10 % 1,10 % 1] :+ SP LineSegment (Closed (Point2 [10 % 1,0 % 1] :+ ())) (Closed (Point2 [10 % 1,10 % 1] :+ ())) LineSegment (Closed (Point2 [10 % 1,10 % 1] :+ ())) (Closed (Point2 [5 % 1,15 % 1] :+ ()))--- Point2 [5 % 1,15 % 1] :+ SP LineSegment (Closed (Point2 [10 % 1,10 % 1] :+ ())) (Closed (Point2 [5 % 1,15 % 1] :+ ())) LineSegment (Closed (Point2 [5 % 1,15 % 1] :+ ())) (Closed (Point2 [1 % 1,11 % 1] :+ ()))--- Point2 [1 % 1,11 % 1] :+ SP LineSegment (Closed (Point2 [5 % 1,15 % 1] :+ ())) (Closed (Point2 [1 % 1,11 % 1] :+ ())) LineSegment (Closed (Point2 [1 % 1,11 % 1] :+ ())) (Closed (Point2 [0 % 1,0 % 1] :+ ()))+-- Point2 0 0 :+ V2 (ClosedLineSegment (Point2 1 11 :+ ()) (Point2 0 0 :+ ())) (ClosedLineSegment (Point2 0 0 :+ ()) (Point2 10 0 :+ ()))+-- Point2 10 0 :+ V2 (ClosedLineSegment (Point2 0 0 :+ ()) (Point2 10 0 :+ ())) (ClosedLineSegment (Point2 10 0 :+ ()) (Point2 10 10 :+ ()))+-- Point2 10 10 :+ V2 (ClosedLineSegment (Point2 10 0 :+ ()) (Point2 10 10 :+ ())) (ClosedLineSegment (Point2 10 10 :+ ()) (Point2 5 15 :+ ()))+-- Point2 5 15 :+ V2 (ClosedLineSegment (Point2 10 10 :+ ()) (Point2 5 15 :+ ())) (ClosedLineSegment (Point2 5 15 :+ ()) (Point2 1 11 :+ ()))+-- Point2 1 11 :+ V2 (ClosedLineSegment (Point2 5 15 :+ ()) (Point2 1 11 :+ ())) (ClosedLineSegment (Point2 1 11 :+ ()) (Point2 0 0 :+ ())) withIncidentEdges :: Polygon t p r -> Polygon t (Two (LineSegment 2 p r)) r-withIncidentEdges (SimplePolygon vs) =- SimplePolygon $ C.zip3LWith f (C.rotateL vs) vs (C.rotateR vs)+withIncidentEdges poly@SimplePolygon{} =+ unsafeFromCircularVector $ CV.zipWith3 f (CV.rotateLeft 1 vs) vs (CV.rotateRight 1 vs) where- f p c n = c&extra .~ SP (ClosedLineSegment p c) (ClosedLineSegment c n)+ vs = poly ^. outerBoundaryVector+ f p c n = c&extra .~ Two (ClosedLineSegment p c) (ClosedLineSegment c n) withIncidentEdges (MultiPolygon vs hs) = MultiPolygon vs' hs' where- (SimplePolygon vs') = withIncidentEdges $ SimplePolygon vs+ vs' = withIncidentEdges vs hs' = map withIncidentEdges hs -- -- | Gets the i^th edge on the outer boundary of the polygon, that is the edge@@ -289,143 +505,33 @@ -- -- modulo n. -- -- +-- FIXME: Test that \poly -> fromEdges (toEdges poly) == poly -- | Given the vertices of the polygon. Produce a list of edges. The edges are -- half-open.-toEdges :: C.CSeq (Point 2 r :+ p) -> C.CSeq (LineSegment 2 p r)-toEdges vs = C.zipLWith (\p q -> LineSegment (Closed p) (Open q)) vs (C.rotateR vs)- -- let vs' = F.toList vs in- -- C.fromList $ zipWith (\p q -> LineSegment (Closed p) (Open q)) vs' (tail vs' ++ vs')----- | Test if q lies on the boundary of the polygon. Running time: O(n)------ >>> Point2 1 1 `onBoundary` simplePoly--- False--- >>> Point2 0 0 `onBoundary` simplePoly--- True--- >>> Point2 10 0 `onBoundary` simplePoly--- True--- >>> Point2 5 13 `onBoundary` simplePoly--- False--- >>> Point2 5 10 `onBoundary` simplePoly--- False--- >>> Point2 10 5 `onBoundary` simplePoly--- True--- >>> Point2 20 5 `onBoundary` simplePoly--- False------ TODO: testcases multipolygon-onBoundary :: (Fractional r, Ord r) => Point 2 r -> Polygon t p r -> Bool-q `onBoundary` pg = any (q `onSegment`) es- where- out = SimplePolygon $ pg^.outerBoundary- es = concatMap (F.toList . outerBoundaryEdges) $ out : holeList pg---- | Check if a point lies inside a polygon, on the boundary, or outside of the polygon.--- Running time: O(n).------ >>> Point2 1 1 `inPolygon` simplePoly--- Inside--- >>> Point2 0 0 `inPolygon` simplePoly--- OnBoundary--- >>> Point2 10 0 `inPolygon` simplePoly--- OnBoundary--- >>> Point2 5 13 `inPolygon` simplePoly--- Inside--- >>> Point2 5 10 `inPolygon` simplePoly--- Inside--- >>> Point2 10 5 `inPolygon` simplePoly--- OnBoundary--- >>> Point2 20 5 `inPolygon` simplePoly--- Outside------ TODO: Add some testcases with multiPolygons--- TODO: Add some more onBoundary testcases-inPolygon :: forall t p r. (Fractional r, Ord r)- => Point 2 r -> Polygon t p r- -> PointLocationResult-q `inPolygon` pg- | q `onBoundary` pg = OnBoundary- | odd kl && odd kr && not (any (q `inHole`) hs) = Inside- | otherwise = Outside- where- l = horizontalLine $ q^.yCoord-- -- Given a line segment, compute the intersection point (if a point) with the- -- line l- intersectionPoint = asA @(Point 2 r) . (`intersect` l)-- -- Count the number of intersections that the horizontal line through q- -- maxes with the polygon, that are strictly to the left and strictly to- -- the right of q. If these numbers are both odd the point lies within the polygon.- --- --- -- note that: - by the asA (Point 2 r) we ignore horizontal segments (as desired)- -- - by the filtering, we effectively limit l to an open-half line, starting- -- at the (open) point q.- -- - by using half-open segments as edges we avoid double counting- -- intersections that coincide with vertices.- -- - If the point is outside, and on the same height as the- -- minimum or maximum coordinate of the polygon. The number of- -- intersections to the left or right may be one. Thus- -- incorrectly classifying the point as inside. To avoid this,- -- we count both the points to the left *and* to the right of- -- p. Only if both are odd the point is inside. so that if- -- the point is outside, and on the same y-coordinate as one- -- of the extermal vertices (one ofth)- --- -- See http://geomalgorithms.com/a03-_inclusion.html for more information.- SP kl kr = count (\p -> (p^.xCoord) `compare` (q^.xCoord))- . mapMaybe intersectionPoint . F.toList . outerBoundaryEdges $ pg-- -- For multi polygons we have to test if we do not lie in a hole .- inHole = insidePolygon- hs = holeList pg-- count :: (a -> Ordering) -> [a] -> SP Int Int- count f = foldr (\x (SP lts gts) -> case f x of- LT -> SP (lts + 1) gts- EQ -> SP lts gts- GT -> SP lts (gts + 1)) (SP 0 0)----- | Test if a point lies strictly inside the polgyon.-insidePolygon :: (Fractional r, Ord r) => Point 2 r -> Polygon t p r -> Bool-q `insidePolygon` pg = q `inPolygon` pg == Inside----- testQ = map (`inPolygon` testPoly) [ Point2 1 1 -- Inside--- , Point2 0 0 -- OnBoundary--- , Point2 5 14 -- Inside--- , Point2 5 10 -- Inside--- , Point2 10 5 -- OnBoundary--- , Point2 20 5 -- Outside--- ]---- testPoly :: SimplePolygon () Rational--- testPoly = SimplePolygon . C.fromList . map ext $ [ Point2 0 0--- , Point2 10 0--- , Point2 10 10--- , Point2 5 15--- , Point2 1 11--- ]+toEdges :: CircularVector (Point 2 r :+ p) -> CircularVector (LineSegment 2 p r)+toEdges vs = CV.zipWith (\p q -> LineSegment (Closed p) (Open q)) vs (CV.rotateRight 1 vs) -- | Compute the area of a polygon area :: Fractional r => Polygon t p r -> r-area poly@(SimplePolygon _) = abs $ signedArea poly-area (MultiPolygon vs hs) = area (SimplePolygon vs) - sum [area h | h <- hs]+area poly@SimplePolygon{} = abs $ signedArea poly+area (MultiPolygon vs hs) = area vs - sum [area h | h <- hs] -- | Compute the signed area of a simple polygon. The the vertices are in -- clockwise order, the signed area will be negative, if the verices are given -- in counter clockwise order, the area will be positive. signedArea :: Fractional r => SimplePolygon p r -> r-signedArea poly = x / 2+signedArea poly = signedArea2X poly / 2++-- | Compute the signed area times 2 of a simple polygon. The the vertices are in+-- clockwise order, the signed area will be negative, if the verices are given+-- in counter clockwise order, the area will be positive.+signedArea2X :: Num r => SimplePolygon p r -> r+signedArea2X poly = x where x = sum [ p^.core.xCoord * q^.core.yCoord - q^.core.xCoord * p^.core.yCoord | LineSegment' p q <- F.toList $ outerBoundaryEdges poly ] - -- | Compute the centroid of a simple polygon. centroid :: Fractional r => SimplePolygon p r -> Point 2 r centroid poly = Point $ sum' xs ^/ (6 * signedArea poly)@@ -436,43 +542,33 @@ sum' = F.foldl' (^+^) zero --- | Pick a point that is inside the polygon.+-- | \( O(n) \) Pick a point that is inside the polygon. -- -- (note: if the polygon is degenerate; i.e. has <3 vertices, we report a -- vertex of the polygon instead.) -- -- pre: the polygon is given in CCW order------ running time: \(O(n)\) pickPoint :: (Ord r, Fractional r) => Polygon p t r -> Point 2 r-pickPoint pg | isTriangle pg = centroid . SimplePolygon $ pg^.outerBoundary+pickPoint pg | isTriangle pg = centroid $ pg^.outerBoundary | otherwise = let LineSegment' (p :+ _) (q :+ _) = findDiagonal pg in p .+^ (0.5 *^ (q .-. p)) --- | Test if the polygon is a triangle------ running time: \(O(1)\)+-- | \( O(1) \) Test if the polygon is a triangle isTriangle :: Polygon p t r -> Bool isTriangle = \case- SimplePolygon vs -> go vs- MultiPolygon vs [] -> go vs+ p@SimplePolygon{} -> F.length (p^.outerBoundaryVector) == 3+ MultiPolygon vs [] -> isTriangle vs MultiPolygon _ _ -> False- where- go vs = case toNonEmpty vs of- (_ :| [_,_]) -> True- _ -> False --- | Find a diagonal of the polygon.+-- | \( O(n) \) Find a diagonal of the polygon. -- -- pre: the polygon is given in CCW order------ running time: \(O(n)\) findDiagonal :: (Ord r, Fractional r) => Polygon t p r -> LineSegment 2 p r findDiagonal pg = List.head . catMaybes . F.toList $ diags -- note that a diagonal is guaranteed to exist, so the usage of head is safe. where- vs = pg^.outerBoundary- diags = C.zip3LWith f (C.rotateL vs) vs (C.rotateR vs)+ vs = pg^.outerBoundaryVector+ diags = CV.zipWith3 f (CV.rotateLeft 1 vs) vs (CV.rotateRight 1 vs) f u v w = case ccw (u^.core) (v^.core) (w^.core) of CCW -> Just $ findDiag u v w -- v is a convex vertex, so find a diagonal@@ -505,59 +601,48 @@ xs -> Just $ List.maximumBy (comparing f) xs --- | Test if the outer boundary of the polygon is in clockwise or counter+-- | \( O(n) \) Test if the outer boundary of the polygon is in clockwise or counter -- clockwise order.------ running time: \(O(n)\)----isCounterClockwise :: (Eq r, Fractional r) => Polygon t p r -> Bool-isCounterClockwise = (\x -> x == abs x) . signedArea- . fromPoints . F.toList . (^.outerBoundary)+isCounterClockwise :: (Eq r, Num r) => Polygon t p r -> Bool+isCounterClockwise = (\x -> x == abs x) . signedArea2X . view outerBoundary --- | Make sure that every edge has the polygon's interior on its+-- | \( O(n) \) Make sure that every edge has the polygon's interior on its -- right, by orienting the outer boundary into clockwise order, and -- the inner borders (i.e. any holes, if they exist) into -- counter-clockwise order.------ running time: \(O(n)\)--- | Orient the outer boundary of the polygon to clockwise order-toClockwiseOrder :: (Eq r, Fractional r) => Polygon t p r -> Polygon t p r-toClockwiseOrder p = (toClockwiseOrder' p)&polygonHoles'.traverse %~ toCounterClockWiseOrder'+toClockwiseOrder :: (Eq r, Num r) => Polygon t p r -> Polygon t p r+toClockwiseOrder p = toClockwiseOrder' p & polygonHoles'.traverse %~ toCounterClockWiseOrder' --- | Orient the outer boundary into clockwise order. Leaves any holes+-- | \( O(n) \) Orient the outer boundary into clockwise order. Leaves any holes -- as they are. ---toClockwiseOrder' :: (Eq r, Fractional r) => Polygon t p r -> Polygon t p r+toClockwiseOrder' :: (Eq r, Num r) => Polygon t p r -> Polygon t p r toClockwiseOrder' pg | isCounterClockwise pg = reverseOuterBoundary pg | otherwise = pg --- | Make sure that every edge has the polygon's interior on its left,+-- | \( O(n) \) Make sure that every edge has the polygon's interior on its left, -- by orienting the outer boundary into counter-clockwise order, and -- the inner borders (i.e. any holes, if they exist) into clockwise order.------ running time: \(O(n)\)-toCounterClockWiseOrder :: (Eq r, Fractional r) => Polygon t p r -> Polygon t p r+toCounterClockWiseOrder :: (Eq r, Num r) => Polygon t p r -> Polygon t p r toCounterClockWiseOrder p =- (toCounterClockWiseOrder' p)&polygonHoles'.traverse %~ toClockwiseOrder'+ toCounterClockWiseOrder' p & polygonHoles'.traverse %~ toClockwiseOrder' --- | Orient the outer boundary into counter-clockwise order. Leaves+-- | \( O(n) \) Orient the outer boundary into counter-clockwise order. Leaves -- any holes as they are.----toCounterClockWiseOrder' :: (Eq r, Fractional r) => Polygon t p r -> Polygon t p r+toCounterClockWiseOrder' :: (Eq r, Num r) => Polygon t p r -> Polygon t p r toCounterClockWiseOrder' p | not $ isCounterClockwise p = reverseOuterBoundary p | otherwise = p +-- FIXME: Delete this function.+-- | Reorient the outer boundary from clockwise order to counter-clockwise order or+-- from counter-clockwise order to clockwise order. Leaves+-- any holes as they are.+-- reverseOuterBoundary :: Polygon t p r -> Polygon t p r-reverseOuterBoundary p = p&outerBoundary %~ C.reverseDirection----- | Convert a Polygon to a simple polygon by forgetting about any holes.-asSimplePolygon :: Polygon t p r -> SimplePolygon p r-asSimplePolygon poly@(SimplePolygon _) = poly-asSimplePolygon (MultiPolygon vs _) = SimplePolygon vs+reverseOuterBoundary p = p&unsafeOuterBoundaryVector %~ CV.reverse -- | assigns unique integer numbers to all vertices. Numbers start from 0, and@@ -565,7 +650,164 @@ -- will be numbered last, in the same order. -- -- >>> numberVertices simplePoly--- SimplePolygon (CSeq [Point2 [0 % 1,0 % 1] :+ SP 0 (),Point2 [10 % 1,0 % 1] :+ SP 1 (),Point2 [10 % 1,10 % 1] :+ SP 2 (),Point2 [5 % 1,15 % 1] :+ SP 3 (),Point2 [1 % 1,11 % 1] :+ SP 4 ()])+-- SimplePolygon [Point2 0 0 :+ SP 0 (),Point2 10 0 :+ SP 1 (),Point2 10 10 :+ SP 2 (),Point2 5 15 :+ SP 3 (),Point2 1 11 :+ SP 4 ()] numberVertices :: Polygon t p r -> Polygon t (SP Int p) r-numberVertices = snd . bimapAccumL (\a p -> (a+1,SP a p)) (\a r -> (a,r)) 0+numberVertices = snd . bimapAccumL (\a p -> (a+1,SP a p)) (,) 0 -- TODO: Make sure that this does not have the same issues as foldl vs foldl'++--------------------------------------------------------------------------------+-- Specialized folds++-- maximum and minimum probably aren't useful. Disabled for now. Lemmih, 2020-12-26.++-- | \( O(n) \) Yield the maximum point of the polygon. Points are compared first by x-coordinate+-- and then by y-coordinate. The maximum point will therefore be the right-most point in+-- the polygon (and top-most if multiple points share the largest x-coordinate).+--+-- Hole vertices are ignored since they cannot be the maximum.+_maximum :: Ord r => Polygon t p r -> Point 2 r :+ p+_maximum = F.maximumBy (comparing _core) . view outerBoundaryVector++-- | \( O(n) \) Yield the maximum point of a polygon according to the given comparison function.+maximumVertexBy :: (Point 2 r :+ p -> Point 2 r :+ p -> Ordering) -> Polygon t p r -> Point 2 r :+ p+maximumVertexBy fn (SimplePolygon vs) = F.maximumBy fn vs+maximumVertexBy fn (MultiPolygon b hs) = F.maximumBy fn $ map (maximumVertexBy fn) (b:hs)++-- | \( O(n) \) Yield the maximum point of the polygon. Points are compared first by x-coordinate+-- and then by y-coordinate. The minimum point will therefore be the left-most point in+-- the polygon (and bottom-most if multiple points share the smallest x-coordinate).+--+-- Hole vertices are ignored since they cannot be the minimum.+_minimum :: Ord r => Polygon t p r -> Point 2 r :+ p+_minimum = F.minimumBy (comparing _core) . view outerBoundaryVector++-- | \( O(n) \) Yield the maximum point of a polygon according to the given comparison function.+minimumVertexBy :: (Point 2 r :+ p -> Point 2 r :+ p -> Ordering) -> Polygon t p r -> Point 2 r :+ p+minimumVertexBy fn (SimplePolygon vs) = F.minimumBy fn vs+minimumVertexBy fn (MultiPolygon b hs) = F.minimumBy fn $ map (minimumVertexBy fn) (b:hs)++-- | Rotate to the first point that matches the given condition.+--+-- >>> toVector <$> findRotateTo (== (Point2 1 0 :+ ())) (unsafeFromPoints [Point2 0 0 :+ (), Point2 1 0 :+ (), Point2 1 1 :+ ()])+-- Just [Point2 1 0 :+ (),Point2 1 1 :+ (),Point2 0 0 :+ ()]+-- >>> findRotateTo (== (Point2 7 0 :+ ())) $ unsafeFromPoints [Point2 0 0 :+ (), Point2 1 0 :+ (), Point2 1 1 :+ ()]+-- Nothing+findRotateTo :: (Point 2 r :+ p -> Bool) -> SimplePolygon p r -> Maybe (SimplePolygon p r)+findRotateTo fn = fmap unsafeFromCircularVector . CV.findRotateTo fn . view outerBoundaryVector++--------------------------------------------------------------------------------+-- Rotation++-- | \( O(1) \) Rotate the polygon to the left by n number of points.+rotateLeft :: Int -> SimplePolygon p r -> SimplePolygon p r+rotateLeft n = over unsafeOuterBoundaryVector (CV.rotateLeft n)++-- | \( O(1) \) Rotate the polygon to the right by n number of points.+rotateRight :: Int -> SimplePolygon p r -> SimplePolygon p r+rotateRight n = over unsafeOuterBoundaryVector (CV.rotateRight n)++--------------------------------------------------------------------------------+-- Testing for reflex or convex++-- | Test if a given vertex is a reflex vertex.+--+-- \(O(1)\)+isReflexVertex :: (Ord r, Num r) => Int -> Polygon Simple p r -> Bool+isReflexVertex i pg = ccw' u v w == CW+ where+ u = pg^.outerVertex (i-1)+ v = pg^.outerVertex i+ w = pg^.outerVertex (i+1)++-- | Test if a given vertex is a convex vertex (i.e. not a reflex vertex).+--+-- \(O(1)\)+isConvexVertex :: (Ord r, Num r) => Int -> Polygon Simple p r -> Bool+isConvexVertex i = not . isReflexVertex i++-- | Test if a given vertex is a strictly convex vertex.+--+-- \(O(1)\)+isStrictlyConvexVertex :: (Ord r, Num r) => Int -> Polygon t p r -> Bool+isStrictlyConvexVertex i pg = ccw' u v w == CCW+ where+ u = pg^.outerVertex (i-1)+ v = pg^.outerVertex i+ w = pg^.outerVertex (i+1)+++-- | Computes all reflex vertices of the polygon.+--+-- \(O(n)\)+reflexVertices :: (Ord r, Num r) => Polygon t p r -> [Int :+ (Point 2 r :+ p)]+reflexVertices p@(SimplePolygon _) = reflexVertices' p+reflexVertices (numberVertices -> MultiPolygon vs hs) =+ map (\(_ :+ (p :+ SP i e)) -> i :+ (p :+ e)) $+ reflexVertices' vs <> concatMap strictlyConvexVertices' hs++-- | Computes all convex (i.e. non-reflex) vertices of the polygon.+--+-- \(O(n)\)+convexVertices :: (Ord r, Num r) => Polygon t p r -> [Int :+ (Point 2 r :+ p)]+convexVertices = \case+ p@(SimplePolygon _) -> convexVertices' p+ (numberVertices -> MultiPolygon vs hs) ->+ map (\(_ :+ (p :+ SP i e)) -> i :+ (p :+ e)) $+ convexVertices' vs <> concatMap reflexVertices' hs++-- | Computes all strictly convex vertices of the polygon.+--+-- \(O(n)\)+strictlyConvexVertices :: (Ord r, Num r) => Polygon t p r -> [Int :+ (Point 2 r :+ p)]+strictlyConvexVertices = \case+ p@(SimplePolygon _) -> convexVertices' p+ (numberVertices -> MultiPolygon vs hs) ->+ map (\(_ :+ (p :+ SP i e)) -> i :+ (p :+ e)) $+ strictlyConvexVertices' vs <> concatMap reflexVertices' hs++----------------------------------------++-- | Return (the indices of) all reflex vertices, in increasing order+-- along the boundary.+--+-- \(O(n)\)+reflexVertices' :: (Ord r, Num r) => SimplePolygon p r -> [Int :+ (Point 2 r :+ p)]+reflexVertices' = filterReflexConvexWorker asReflex+ where+ asReflex u v w | ccw' (u^.extra) (v^.extra) (w^.extra) == CW = Just v+ | otherwise = Nothing++-- | Return (the indices of) all strictly convex vertices, in+-- increasing order along the boundary.+--+-- \(O(n)\)+strictlyConvexVertices' :: (Ord r, Num r) => SimplePolygon p r -> [Int :+ (Point 2 r :+ p)]+strictlyConvexVertices' = filterReflexConvexWorker asStrictlyConvex+ where+ asStrictlyConvex u v w | ccw' (u^.extra) (v^.extra) (w^.extra) == CCW = Just v+ | otherwise = Nothing++-- | Return (the indices of) all convex (= non-reflex) vertices, in increasing order+-- along the boundary.+--+-- \(O(n)\)+convexVertices' :: (Ord r, Num r) => SimplePolygon p r -> [Int :+ (Point 2 r :+ p)]+convexVertices' = filterReflexConvexWorker asConvex+ where+ asConvex u v w | ccw' (u^.extra) (v^.extra) (w^.extra) /= CW = Just v+ | otherwise = Nothing++-- | Helper function to implement convexVertices, reflexVertices, and+-- strictlyConvexVertices+filterReflexConvexWorker :: (Ord r, Num r)+ => ( Int :+ (Point 2 r :+ p)+ -> Int :+ (Point 2 r :+ p)+ -> Int :+ (Point 2 r :+ p)+ -> Maybe (Int :+ (Point 2 r :+ p))+ )+ -> SimplePolygon p r -> [Int :+ (Point 2 r :+ p)]+filterReflexConvexWorker g pg =+ catMaybes $ zip3RWith g (CV.rotateLeft 1 vs) vs (CV.rotateRight 1 vs)+ where+ vs = CV.withIndicesRight $ pg^.outerBoundaryVector+ zip3RWith f us' vs' ws' = zipWith3 f (F.toList us') (F.toList vs') (F.toList ws')
src/Data/Geometry/Polygon/Extremes.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE TemplateHaskell #-} -------------------------------------------------------------------------------- -- | -- Module : Data.Geometry.Polygon.Extremes@@ -13,15 +12,15 @@ , extremesLinear ) where -import Control.Lens hiding (Simple)+import Control.Lens hiding (Simple,simple) import Data.Ext-import qualified Data.Foldable as F import Data.Geometry.Point-import Data.Geometry.Polygon.Core+import Data.Geometry.Polygon.Core as P import Data.Geometry.Vector -------------------------------------------------------------------------------- +{- HLINT ignore cmpExtreme -} -- | Comparison that compares which point is 'larger' in the direction given by -- the vector u. cmpExtreme :: (Num r, Ord r)@@ -34,6 +33,6 @@ -- running time: \(O(n)\) extremesLinear :: (Ord r, Num r) => Vector 2 r -> Polygon t p r -> (Point 2 r :+ p, Point 2 r :+ p)-extremesLinear u p = let vs = p^.outerBoundary+extremesLinear u p = let simple = p^.outerBoundary f = cmpExtreme u- in (F.minimumBy f vs, F.maximumBy f vs)+ in (P.minimumVertexBy f simple, P.maximumVertexBy f simple)
+ src/Data/Geometry/Polygon/Inflate.hs view
@@ -0,0 +1,142 @@+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.Polygon.Inflate+-- Copyright : (C) David Himmelstrup+-- License : see the LICENSE file+-- Maintainer : David Himmelstrup+--------------------------------------------------------------------------------+module Data.Geometry.Polygon.Inflate+ ( Arc(..)+ , inflate+ ) where++import Algorithms.Geometry.SSSP (SSSP, sssp, triangulate)+import Control.Lens+import Data.Ext+import Data.Geometry.Line (lineThrough)+import Data.Geometry.LineSegment (LineSegment (LineSegment, OpenLineSegment),+ interpolate, sqSegmentLength)+import Data.Geometry.Point+import Data.Geometry.Polygon.Core+import Data.Intersection (IsIntersectableWith (intersect),+ NoIntersection (NoIntersection))+import Data.Maybe (catMaybes)+import qualified Data.Vector as V+import qualified Data.Vector.Circular as CV+import qualified Data.Vector.Unboxed as VU+import Data.Vinyl (Rec (RNil, (:&)))+import Data.Vinyl.CoRec (Handler (H), match)++----------------------------------------------------+-- Implementation++-- | Points annotated with an 'Arc' indicate that the edge from this point to+-- the next should not be a straight line but instead an arc with a given center+-- and a given border edge.+data Arc r = Arc+ { arcCenter :: Point 2 r+ , arcEdge :: (Point 2 r, Point 2 r)+ } deriving (Show)++type Parent = Int++markParents :: SSSP -> SimplePolygon p r -> SimplePolygon Parent r+markParents t p = unsafeFromCircularVector $+ CV.imap (\i (pt :+ _) -> pt :+ t VU.! i) (p^.outerBoundaryVector)++addSteinerPoints :: (Ord r, Fractional r) => SimplePolygon Parent r -> SimplePolygon Parent r+addSteinerPoints p = fromPoints $ concatMap worker [0 .. size p - 1]+ where+ worker nth = do+ pointA : catMaybes [ (:+ parent nth) <$> getIntersection edge lineA+ , (:+ parent (nth+1)) <$> getIntersection edge lineB ]+ where+ fetch idx = p ^. outerVertex idx+ pointA = fetch nth+ pointB = fetch (nth+1)+ parent idx = p^.outerVertex idx.extra+ lineA = lineThrough+ (fetch (parent nth) ^. core)+ (fetch (parent (parent nth)) ^. core)+ lineB = lineThrough+ (fetch (parent (nth+1)) ^. core)+ (fetch (parent (parent (nth+1))) ^. core)+ edge = OpenLineSegment pointA pointB+ getIntersection segment line =+ match (segment `intersect` line) (+ H (\NoIntersection -> Nothing)+ :& H (\pt -> Just pt)+ :& H (\LineSegment{} -> Nothing)+ :& RNil+ )++annotate :: (Real r, Fractional r) =>+ Double -> SimplePolygon Parent r -> SimplePolygon Parent r -> SimplePolygon (Arc r) r+annotate t original p = unsafeFromCircularVector $+ CV.imap ann (p^.outerBoundaryVector)+ -- CV.generate (size p) ann -- Use this when circular-vector-0.1.2 is out.+ where+ nO = size original+ visibleDist = V.maximum distanceTreeSum * t+ parent idx = p^.outerVertex idx.extra+ parentO idx = original^.outerVertex idx.extra+ getLineO idx = OpenLineSegment (original ^. outerVertex (parentO idx)) (original ^. outerVertex idx)+ getLineP idx = OpenLineSegment (original ^. outerVertex (parent idx)) (p ^. outerVertex idx)++ ann i _ =+ ptLocation i :+ arc+ where+ start = p ^. outerVertex i . core+ end = p ^. outerVertex (i+1) . core+ arc = Arc+ { arcCenter =+ original ^. outerVertex (commonParent original (parent i) (parent (i+1))) . core+ , arcEdge = (start, end) }++ -- Array of locations for points in the original polygon.+ ptLocationsO = V.generate nO ptLocationO+ ptLocationO 0 = (original ^. outerVertex 0 . core)+ ptLocationO i+ | frac <= 0 = ptLocationsO V.! (parentO i)+ | frac >= 1 = (original ^. outerVertex i . core)+ | otherwise = (interpolate frac (getLineO i))+ where+ dParent = distanceTreeSum V.! parentO i+ dSelf = oDistance VU.! i+ frac = realToFrac ((visibleDist - dParent) / dSelf)++ -- Locations for original points and steiner points.+ ptLocation 0 = (p ^. outerVertex 0 . core)+ ptLocation i+ | frac <= 0 = ptLocationsO V.! (parent i)+ | frac >= 1 = (p ^. outerVertex i . core)+ | otherwise = (interpolate frac (getLineP i))+ where+ dParent = distanceTreeSum V.! parent i+ dSelf = sqrt $ realToFrac $ sqSegmentLength $ getLineP i+ frac = realToFrac ((visibleDist - dParent) / dSelf)++ oDistance = VU.generate nO $ \i ->+ case i of+ 0 -> 0+ _ -> sqrt $ realToFrac $ sqSegmentLength $ getLineO i+ distanceTreeSum = V.generate nO $ \i ->+ case i of+ 0 -> 0+ _ -> distanceTreeSum V.! parentO i + oDistance VU.! i++commonParent :: SimplePolygon Parent r -> Int -> Int -> Int+commonParent p a b = worker 0 (parents a) (parents b)+ where+ worker _shared (x:xs) (y:ys)+ | x == y = worker x xs ys+ worker shared _ _ = shared+ parents 0 = [0]+ parents i = parents (p ^. outerVertex i . extra) ++ [i]++-- | \( O(n \log n) \)+inflate :: (Real r, Fractional r) => Double -> SimplePolygon () r -> SimplePolygon (Arc r) r+inflate t p = annotate t marked steiner+ where+ marked = markParents (sssp (triangulate p)) p+ steiner = addSteinerPoints marked
+ src/Data/Geometry/Polygon/Monotone.hs view
@@ -0,0 +1,118 @@+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.Polygon.Monotone+-- Copyright : (C) 1ndy+-- License : see the LICENSE file+-- Maintainer : David Himmelstrup+--+-- A polygon is monotone in a certain direction if rays orthogonal to that+-- direction intersects the polygon at most twice. See+-- <https://en.wikipedia.org/wiki/Monotone_polygon>+--+--------------------------------------------------------------------------------+module Data.Geometry.Polygon.Monotone+ ( isMonotone+ , randomMonotone+ , randomMonotoneDirected+ , monotoneFrom+ , randomNonZeroVector+ ) where++import Control.Monad.Random+import Data.Ext+import qualified Data.Foldable as F+import Data.Geometry.Line (Line (..))+import Data.Geometry.LineSegment+import Data.Geometry.Point+import Data.Geometry.Polygon.Core+import Data.Geometry.Polygon.Extremes+import Data.Geometry.Vector+import Data.Intersection+import Data.List+import Data.Vinyl+import Data.Vinyl.CoRec+import Prelude hiding (max, min)++-- | \( O(n \log n) \)+-- A polygon is monotone if a straight line in a given direction+-- cannot have more than two intersections.+isMonotone :: (Fractional r, Ord r) => Vector 2 r -> SimplePolygon p r -> Bool+-- Check for each vertex that the number of intersections with the+-- line starting at the vertex and going out in the given direction+-- intersects with the edges of the polygon no more than 2 times.+isMonotone direction p = all isMonotoneAt (map _core $ toPoints p)+ where+ isMonotoneAt pt =+ sum (map (intersectionsThrough pt) (F.toList $ outerBoundaryEdges p)) <= 2+ intersectionsThrough pt edge =+ match (Data.Intersection.intersect edge line) $+ H (\NoIntersection -> 0)+ :& H (\Point{} -> 1)+ -- This happens when an edge is parallel with the given direction.+ -- I think it's correct to count it as a single intersection.+ :& H (\LineSegment{} -> 1)+ :& RNil+ where+ line = Line pt (rot90 direction)+ rot90 (Vector2 x y) = Vector2 (-y) x++{- Algorithm overview:++ 1. Create N `Point 2 Rational` (N >= 3)+ 2. Create a random `Vector 2 Rational`+ 3. Find the extremes (min and max) of the points when sorted in the direction of the vector.+ We already have code for this. See `maximumBy (cmpExtreme vector)` and+ `minimumBy (cmpExtreme vector)`.+ 4. Take out the two extremal points from the set.+ 5. Partition the remaining points according to whether they're on the left side or right side+ of the imaginary line between the two extremal points.+ 6. Sort the two partitioned sets, one in the direction of the vector and one in the opposite+ direction.+ 7. Connect the points, starting from the minimal extreme point, going through the set of points+ that are increasing in the direction of the vector, then to the maximal point, and finally+ down through the points that are decreasing in the direction of the vector.+-}+-- | \( O(n \log n) \)+-- Generate a random N-sided polygon that is monotone in a random direction.+randomMonotone :: (RandomGen g, Random r, Ord r, Num r) => Int -> Rand g (SimplePolygon () r)+randomMonotone nVertices = randomMonotoneDirected nVertices =<< randomNonZeroVector++-- Pick a random vector and then call 'randomMonotone'.+-- | \( O(n \log n) \)+-- Generate a random N-sided polygon that is monotone in the given direction.+randomMonotoneDirected :: (RandomGen g, Random r, Ord r, Num r)+ => Int -> Vector 2 r -> Rand g (SimplePolygon () r)+randomMonotoneDirected nVertices direction = do+ points <- replicateM nVertices getRandom+ return (monotoneFrom direction points)++-- | \( O(n \log n) \)+-- Assemble a given set of points in a polygon that is monotone in the given direction.+monotoneFrom :: (Ord r, Num r) => Vector 2 r -> [Point 2 r] -> SimplePolygon () r+monotoneFrom direction vertices = fromPoints ([min] ++ rightHalf ++ [max] ++ leftHalf)+ where+ specialPoints = map (\x -> x :+ ()) vertices+ min = Data.List.minimumBy (cmpExtreme direction) specialPoints+ max = Data.List.maximumBy (cmpExtreme direction) specialPoints+ -- 4+ pointsWithoutExtremes = filter (\x -> x /= min && x /= max) specialPoints+ -- 5, 6+ (leftHalfUnsorted,rightHalfUnsorted) = Data.List.partition (toTheLeft min max) pointsWithoutExtremes+ leftHalf = sortBy (flip $ cmpExtreme direction) leftHalfUnsorted+ rightHalf = sortBy (cmpExtreme direction) rightHalfUnsorted++-------------------------------------------------------------------------------------------------+-- helper functions++-- for partitioning points+toTheLeft :: (Ord r, Num r) => Point 2 r :+ () -> Point 2 r :+ () -> Point 2 r :+ () -> Bool+toTheLeft min max x = ccw' min max x == CCW++-- | \( O(1) \)+-- Create a random 2D vector which has a non-zero magnitude.+randomNonZeroVector :: (RandomGen g, Random r, Eq r, Num r) => Rand g (Vector 2 r)+randomNonZeroVector = do+ v <- getRandom+ if (quadrance v==0)+ then randomNonZeroVector+ else pure v
src/Data/Geometry/PrioritySearchTree.hs view
@@ -26,6 +26,8 @@ import Data.Geometry.Point import Data.List.NonEmpty (NonEmpty(..)) import qualified Data.List.NonEmpty as NonEmpty+import Data.Measured.Class ()+import Data.Measured.Size import Data.Ord (comparing, Down(..)) import Data.Range import qualified Data.Set as Set@@ -39,7 +41,7 @@ } deriving (Show,Eq) instance Bifunctor NodeData where- bimap f g (NodeData x m) = NodeData (g x) ((bimap (fmap g) f) <$> m)+ bimap f g (NodeData x m) = NodeData (g x) (bimap (fmap g) f <$> m) maxVal :: Lens' (NodeData p r) (Maybe (Point 2 r :+ p)) maxVal = lens _maxVal (\(NodeData x _) m -> NodeData x m)@@ -95,7 +97,7 @@ -- TODO: In case we have multiple points with the same x-coord, these points -- are not really in decreasing y-order. Node l d r | py > d^?maxVal._Just.core.yCoord ->- node' l (d&maxVal .~ Just p) r (d^.maxVal)+ node' l (d&maxVal ?~ p) r (d^.maxVal) -- push the existing point down | otherwise -> node' l d r (Just p)
src/Data/Geometry/Properties.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE ImpredicativeTypes #-} {-# LANGUAGE UnicodeSyntax #-}-{-# LANGUAGE DefaultSignatures #-} -------------------------------------------------------------------------------- -- | -- Module : Data.Geometry.Properties
+ src/Data/Geometry/QuadTree.hs view
@@ -0,0 +1,211 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.QuadTree+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Data.Geometry.QuadTree-- ( module Data.Geometry.QuadTree.Cell+ -- , module Data.Geometry.QuadTree.Quadrants+ -- , module Data.Geometry.QuadTree.Split+ -- , QuadTree(..)+ -- , leaves+ -- , withCells+ -- )+ where+++import Control.Lens (makeLenses, (^.), (.~), (&), (^?!), ix, view)+import Data.Ext+import qualified Data.Foldable as F+import Data.Geometry.Box+import Data.Geometry.Point+import Data.Geometry.QuadTree.Cell+import Data.Geometry.QuadTree.Quadrants+import Data.Geometry.QuadTree.Split+import Data.Geometry.QuadTree.Tree (Tree(..))+import qualified Data.Geometry.QuadTree.Tree as Tree+import Data.Geometry.Vector+import Data.Intersection+import Data.List.NonEmpty (NonEmpty(..))+import Data.Tree.Util (TreeNode(..), levels)+import GHC.Generics (Generic)+--------------------------------------------------------------------------------++-- | QuadTree on the starting cell+data QuadTree v p r = QuadTree { _startingCell :: !(Cell r)+ , _tree :: !(Tree v p)+ }+ deriving (Show,Eq,Generic,Functor,Foldable,Traversable)+makeLenses ''QuadTree++--------------------------------------------------------------------------------+-- * Functions operating on the QuadTree (in terms of the 'Tree' type)++withCells :: (Fractional r, Ord r) => QuadTree v p r -> QuadTree (v :+ Cell r) (p :+ Cell r) r+withCells qt = qt&tree .~ withCellsTree qt++withCellsTree :: (Fractional r, Ord r)+ => QuadTree v p r -> Tree (v :+ Cell r) (p :+ Cell r)+withCellsTree (QuadTree c t) = Tree.withCells c t++leaves :: QuadTree v p r -> NonEmpty p+leaves = Tree.leaves . view tree++perLevel :: QuadTree v p r -> NonEmpty (NonEmpty (TreeNode v p))+perLevel = levels . Tree.toRoseTree . view tree+++--------------------------------------------------------------------------------++-- | Given a starting cell, a Tree builder, and some input required by+-- the builder, constructs a quadTree.+buildOn :: Cell r -> (Cell r -> i -> Tree v p) -> i -> QuadTree v p r+buildOn c0 builder = QuadTree c0 . builder c0++-- | The Equivalent of Tree.build for constructing a QuadTree+build :: (Fractional r, Ord r) => (Cell r -> i -> Split i v p) -> Cell r -> i -> QuadTree v p r+build f c = buildOn c (Tree.build f)++-- | Build a QuadtTree from a set of points.+--+-- pre: the points lie inside the initial given cell.+--+-- running time: \(O(nh)\), where \(n\) is the number of points and+-- \(h\) is the height of the resulting quadTree.+fromPointsBox :: (Fractional r, Ord r)+ => Cell r -> [Point 2 r :+ p] -> QuadTree () (Maybe (Point 2 r :+ p)) r+fromPointsBox c = buildOn c Tree.fromPoints++fromPoints :: (RealFrac r, Ord r)+ => NonEmpty (Point 2 r :+ p) -> QuadTree () (Maybe (Point 2 r :+ p)) r+fromPoints pts = buildOn c Tree.fromPoints (F.toList pts)+ where+ c = fitsRectangle $ boundingBoxList (view core <$> pts)++{- HLINT ignore findLeaf -}+-- | Locates the cell containing the given point, if it exists.+--+-- running time: \(O(h)\), where \(h\) is the height of the quadTree+findLeaf :: (Fractional r, Ord r)+ => Point 2 r -> QuadTree v p r -> Maybe (p :+ Cell r)+findLeaf q (QuadTree c0 t) | q `intersects` c0 = Just $ findLeaf' c0 t+ | otherwise = Nothing+ where+ -- |+ -- pre: p intersects c+ findLeaf' c = \case+ Leaf p -> p :+ c+ Node _ qs -> let quad = quadrantOf q c+ in findLeaf' ((splitCell c)^?!ix quad) (qs^?!ix quad)++--------------------------------------------------------------------------------+++fromZeros :: (Fractional r, Ord r, Num a, Eq a, v ~ Quadrants Sign)+ => Cell r -> (Point 2 r -> a) -> QuadTree v (Either v Sign) r+fromZeros = fromZerosWith (limitWidthTo (-1))+++fromZerosWith :: (Fractional r, Ord r, Eq a, Num a)+ => Limiter r (Corners Sign) (Corners Sign) Sign+ -> Cell r+ -> (Point 2 r -> a)+ -> QuadTree (Quadrants Sign) (Signs Sign) r+fromZerosWith limit c0 f = fromZerosWith' limit c0 (fromSignum f)+++type Signs sign = Either (Corners sign) sign+++fromZerosWith' :: (Eq sign, Fractional r, Ord r)+ => Limiter r (Corners sign) (Corners sign) sign+ -> Cell r+ -> (Point 2 r -> sign)+ -> QuadTree (Quadrants sign) (Signs sign) r+fromZerosWith' limit c0 f = build (limit $ shouldSplitZeros f) c0 (f <$> cellCorners c0)++++-- type Sign = Ordering++-- pattern Negative :: Sign+-- pattern Negative = LT+-- pattern Zero :: Sign+-- pattern Zero = EQ+-- pattern Positive :: Sign+-- pattern Positive = GT+-- {-# COMPLETE Negative, Zero, Positive #-}++-- fromOrdering :: Ordering -> Sign+-- fromOrdering = id+++data Sign = Negative | Zero | Positive deriving (Show,Eq,Ord)++++-- | Interpret an ordering result as a Sign+fromOrdering :: Ordering -> Sign+fromOrdering = \case+ LT -> Negative+ EQ -> Zero+ GT -> Positive++fromSignum :: (Num a, Eq a) => (b -> a) -> b -> Sign+fromSignum f x = case signum (f x) of+ -1 -> Negative+ 0 -> Zero+ 1 -> Positive+ _ -> error "absurd: fromSignum"++-- | Splitter that determines if we should split a cell based on the+-- sign of the corners.+shouldSplitZeros :: forall r sign. (Fractional r, Eq sign)+ => (Point 2 r -> sign) -- ^ The function we are evaluating+ -> Splitter r+ (Quadrants sign) -- the input are the signs of the corners+ (Quadrants sign) -- at internal nodes we store signs of corners+ sign+shouldSplitZeros f (Cell w' p) qs@(Quadrants nw ne se sw) | all sameSign qs = No ne+ | otherwise = Yes qs qs'+ where+ m = fAt rr rr+ n = fAt rr ww+ e = fAt ww rr+ s = fAt rr 0+ w = fAt 0 rr++ sameSign = (== ne)++ -- signs at the new corners+ qs' = Quadrants (Quadrants nw n m w)+ (Quadrants n ne e m)+ (Quadrants m e se s)+ (Quadrants w m s sw)++ r = w' - 1+ rr = pow r+ ww = pow w'++ fAt x y = f $ p .+^ Vector2 x y+++isZeroCell :: (Eq sign) => sign -- ^ the zero value+ -> Either v sign -> Bool+isZeroCell z = \case+ Left _ -> True -- if we kept splitting then we must have a sign transition+ Right s -> s == z++--------------------------------------------------------------------------------++++-- | Constructs an empty/complete tree from the starting width+completeTree :: (Fractional r, Ord r) => Cell r -> QuadTree () () r+completeTree c0 =+ build (\_ w -> if w == 0 then No () else Yes () (pure $ w - 1)) c0 (c0^.cellWidthIndex)++--------------------------------------------------------------------------------
+ src/Data/Geometry/QuadTree/Cell.hs view
@@ -0,0 +1,151 @@+{-# LANGUAGE TemplateHaskell #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.QuadTree.Cell+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Data.Geometry.QuadTree.Cell where++import Control.Lens (makeLenses, (^.),(&),(%~),ix, to)+import Data.Ext+import Data.Geometry.Box+import Data.Geometry.Directions+import Data.Geometry.LineSegment+import Data.Geometry.Point+import Data.Geometry.Properties+import Data.Geometry.QuadTree.Quadrants+import Data.Geometry.Vector++--------------------------------------------------------------------------------++-- | side lengths will be 2^i for some integer i+type WidthIndex = Int++-- | A Cell corresponding to a node in the QuadTree+data Cell r = Cell { _cellWidthIndex :: {-# UNPACK #-} !WidthIndex+ , _lowerLeft :: !(Point 2 r)+ } deriving (Show,Eq,Functor,Foldable,Traversable)+makeLenses ''Cell++-- | Computes a cell that contains the given rectangle+fitsRectangle :: (RealFrac r, Ord r) => Rectangle p r -> Cell r+fitsRectangle b = Cell w ((b^.to minPoint.core) .-^ Vector2 1 1)+ where+ w = lg' . ceiling . (1+) . maximum . size $ b++ -- "approximate log" that over approximates by a factor of at most two.+ lg' :: Integer -> WidthIndex+ lg' n = go 1+ where+ go i | floor (pow i) <= n = go (i+1) -- note that the floor does not really do anything+ -- since i is integral and >= 1.+ | otherwise = i++type instance Dimension (Cell r) = 2+type instance NumType (Cell r) = r++type instance IntersectionOf (Point 2 r) (Cell r) = '[ NoIntersection, Point 2 r]++instance (Ord r, Fractional r) => Point 2 r `HasIntersectionWith` Cell r where+ p `intersects` c = p `intersects` toBox c++instance (Ord r, Fractional r) => Point 2 r `IsIntersectableWith` Cell r where+ nonEmptyIntersection = defaultNonEmptyIntersection+ p `intersect` c = p `intersect` toBox c++pow :: Fractional r => WidthIndex -> r+pow i = case i `compare` 0 of+ LT -> 1 / (2 ^ (-1*i))+ EQ -> 1+ GT -> 2 ^ i++cellWidth :: Fractional r => Cell r -> r+cellWidth (Cell w _) = pow w++toBox :: Fractional r => Cell r -> Box 2 () r+toBox (Cell w p) = box (ext p) (ext $ p .+^ Vector2 (pow w) (pow w))++inCell :: (Fractional r, Ord r) => Point 2 r :+ p -> Cell r -> Bool+inCell (p :+ _) c = p `inBox` toBox c++cellCorners :: Fractional r => Cell r -> Quadrants (Point 2 r)+cellCorners = fmap (^.core) . corners . toBox++-- | Sides are open+cellSides :: Fractional r => Cell r -> Sides (LineSegment 2 () r)+cellSides = fmap (\(ClosedLineSegment p q) -> OpenLineSegment p q) . sides . toBox++splitCell :: (Num r, Fractional r) => Cell r -> Quadrants (Cell r)+splitCell (Cell w p) = Quadrants (Cell r $ f 0 rr)+ (Cell r $ f rr rr)+ (Cell r $ f rr 0)+ (Cell r p)+ where+ r = w - 1+ rr = pow r+ f x y = p .+^ Vector2 x y+++midPoint :: Fractional r => Cell r -> Point 2 r+midPoint (Cell w p) = let rr = pow (w - 1) in p .+^ Vector2 rr rr+++--------------------------------------------------------------------------------++-- | Partitions the points into quadrants. See 'quadrantOf' for the+-- precise rules.+partitionPoints :: (Fractional r, Ord r)+ => Cell r -> [Point 2 r :+ p] -> Quadrants [Point 2 r :+ p]+partitionPoints c = foldMap (\p -> let q = quadrantOf (p^.core) c in mempty&ix q %~ (p:))++-- | Computes the quadrant of the cell corresponding to the current+-- point. Note that we decide the quadrant solely based on the+-- midpoint. If the query point lies outside the cell, it is still+-- assigned a quadrant.+--+-- - The northEast quadrants includes its bottom and left side+-- - The southEast quadrant includes its left side+-- - The northWest quadrant includes its bottom side+-- - The southWest quadrants does not include any of its sides.+--+--+-- >>> quadrantOf (Point2 9 9) (Cell 4 origin)+-- NorthEast+-- >>> quadrantOf (Point2 8 9) (Cell 4 origin)+-- NorthEast+-- >>> quadrantOf (Point2 8 8) (Cell 4 origin)+-- NorthEast+-- >>> quadrantOf (Point2 8 7) (Cell 4 origin)+-- SouthEast+-- >>> quadrantOf (Point2 4 7) (Cell 4 origin)+-- SouthWest+-- >>> quadrantOf (Point2 4 10) (Cell 4 origin)+-- NorthWest+-- >>> quadrantOf (Point2 4 40) (Cell 4 origin)+-- NorthEast+-- >>> quadrantOf (Point2 4 40) (Cell 4 origin)+-- NorthWest+quadrantOf :: forall r. (Fractional r, Ord r)+ => Point 2 r -> Cell r -> InterCardinalDirection+quadrantOf q c = let m = midPoint c+ in case (q^.xCoord < m^.xCoord, q^.yCoord < m^.yCoord) of+ (False,False) -> NorthEast+ (False,True) -> SouthEast+ (True,False) -> NorthWest+ (True,True) -> SouthWest++++-- | Given two cells c and me, compute on which side of `me` the cell+-- `c` is.+--+-- pre: c and me are non-overlapping+relationTo :: (Fractional r, Ord r)+ => (p :+ Cell r) -> Cell r -> Sides (Maybe (p :+ Cell r))+c `relationTo` me = f <$> Sides b l t r <*> cellSides me+ where+ Sides t r b l = cellSides (c^.extra)+ f e e' | e `intersects` e' = Just c+ | otherwise = Nothing
+ src/Data/Geometry/QuadTree/Quadrants.hs view
@@ -0,0 +1,23 @@+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.QuadTree.Quadrants+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Data.Geometry.QuadTree.Quadrants( pattern Quadrants+ , Quadrants+ , module Data.Geometry.Box.Corners+ ) where++import Data.Geometry.Box.Corners++--------------------------------------------------------------------------------++type Quadrants = Corners++pattern Quadrants :: a -> a -> a -> a -> Corners a+pattern Quadrants a b c d = Corners a b c d+{-# COMPLETE Quadrants #-}++--------------------------------------------------------------------------------
+ src/Data/Geometry/QuadTree/Split.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE TemplateHaskell #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.QuadTree.Split+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Data.Geometry.QuadTree.Split where++import Control.Lens (makePrisms,(^.))+import Data.Geometry.QuadTree.Cell+import Data.Geometry.QuadTree.Quadrants++--------------------------------------------------------------------------------++-- | Data Type to Decide if we should continue splitting the current cell+data Split i v p = No !p | Yes !v (Quadrants i) deriving (Show,Eq,Ord)+makePrisms ''Split++-- | A splitter is a function that determines weather or not we should the given cell+-- corresponding to the given input (i).+type Splitter r i v p = Cell r -> i -> Split i v p++-- | Transformer that limits the depth of a splitter+type Limiter r i v p = Splitter r i v p+ -> Splitter r i v (Either i p)++-- | Split only when the Cell-width is at least wMin+limitWidthTo :: WidthIndex -- ^ smallest allowed width of a cell (i.e. width of a leaf)+ -> Limiter r i v p+limitWidthTo wMin f c pts =+ case f c pts of+ No p -> No (Right p)+ Yes v qs | wMin < c^.cellWidthIndex -> Yes v qs+ | otherwise -> No (Left pts)+ -- note that it is important that we still evaluate the function so+ -- that we can distinguish at the last level i.e. between a regular+ -- " we are done splitting (No (Right p))" and a "we are no longer+ -- allowed to split further (No (Left p))"
+ src/Data/Geometry/QuadTree/Tree.hs view
@@ -0,0 +1,123 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.QuadTree.Tree+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Data.Geometry.QuadTree.Tree where+++import Control.Lens (makePrisms)+import Data.Bifoldable+import Data.Bifunctor+import Data.Bitraversable+import Data.Ext+import qualified Data.Foldable as F+import Data.Functor.Apply+import Data.Geometry.Point+import Data.Geometry.QuadTree.Cell+import Data.Geometry.QuadTree.Quadrants+import Data.Geometry.QuadTree.Split+import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.List.NonEmpty as NonEmpty+import Data.Semigroup.Foldable.Class+import Data.Semigroup.Traversable.Class+import qualified Data.Tree as RoseTree+import Data.Tree.Util (TreeNode(..))++--------------------------------------------------------------------------------++-- | Our cells use Rational numbers as their numeric type+-- type CellR = Cell (RealNumber 10)++-- | The Actual Tree type representing a quadTree+data Tree v p = Leaf !p+ | Node !v (Quadrants (Tree v p)) -- quadrants are stored lazily on purpose+ deriving (Show,Eq)+makePrisms ''Tree++instance Bifunctor Tree where+ bimap = bimapDefault++instance Bifoldable Tree where+ bifoldMap = bifoldMapDefault++instance Bitraversable Tree where+ bitraverse f g = \case+ Leaf p -> Leaf <$> g p+ Node v qs -> Node <$> f v <*> traverse (bitraverse f g) qs++instance Bifoldable1 Tree+instance Bitraversable1 Tree where+ bitraverse1 f g = \case+ Leaf p -> Leaf <$> g p+ Node v qs -> Node <$> f v <.> traverse1 (bitraverse1 f g) qs++-- | Fold on the Tree type.+foldTree :: (p -> b) -> (v -> Quadrants b -> b) -> Tree v p -> b+foldTree f g = go+ where+ go = \case+ Leaf p -> f p+ Node v qs -> g v (go <$> qs)++-- | Produce a list of all leaves of a quad tree+leaves :: Tree v p -> NonEmpty p+leaves = NonEmpty.fromList . bifoldMap (const []) (:[])++-- | Converts into a RoseTree+toRoseTree :: Tree v p -> RoseTree.Tree (TreeNode v p)+toRoseTree = foldTree (\p -> RoseTree.Node (LeafNode p) [])+ (\v qs -> RoseTree.Node (InternalNode v) (F.toList qs))++-- | Computes the height of the quadtree+height :: Tree v p -> Integer+height = foldTree (const 1) (\_ -> (1 +) . maximum)+++--------------------------------------------------------------------------------++--------------------------------------------------------------------------------+-- * Functions operating on the QuadTree (in temrs of the 'Tree' type)++-- | Builds a QuadTree+build :: Fractional r+ => Splitter r pts v p -> Cell r -> pts -> Tree v p+build shouldSplit = build'+ where+ build' cc pts = case shouldSplit cc pts of+ No p -> Leaf p+ Yes v qs -> Node v $ build' <$> splitCell cc <*> qs++-- | Annotate the tree with its corresponing cells+withCells :: Fractional r => Cell r -> Tree v p -> Tree (v :+ Cell r) (p :+ Cell r)+withCells c0 = \case+ Leaf p -> Leaf (p :+ c0)+ Node v qs -> Node (v :+ c0) (withCells <$> splitCell c0 <*> qs)+++--------------------------------------------------------------------------------+++-- | Build a QuadtTree from a set of points.+--+-- pre: the points lie inside the initial given cell.+--+-- running time: \(O(nh)\), where \(n\) is the number of points and+-- \(h\) is the height of the resulting quadTree.+fromPoints :: (Fractional r, Ord r)+ => Cell r -> [Point 2 r :+ p]+ -> Tree () (Maybe (Point 2 r :+ p))+fromPoints = build fromPointsF++-- | The function that can be used to build a quadTree 'fromPoints'+fromPointsF :: (Fractional r, Ord r)+ => Splitter r [Point 2 r :+ p] () (Maybe (Point 2 r :+ p))+fromPointsF c = \case+ [] -> No Nothing+ [p] -> No (Just p)+ pts -> Yes () $ partitionPoints c pts+ -- (\cell -> filter (`inCell` cell) pts) <$> splitCell c
src/Data/Geometry/RangeTree.hs view
@@ -1,8 +1,14 @@ {-# LANGUAGE UndecidableInstances #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.RangeTree+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Data.Geometry.RangeTree where import Control.Lens hiding (element)-import Data.BinaryTree (Measured(..)) import Data.Ext import qualified Data.Foldable as F import Data.Geometry.Point@@ -11,10 +17,8 @@ import Data.Geometry.Vector import Data.List.NonEmpty (NonEmpty(..)) import qualified Data.List.NonEmpty as NonEmpty-import Data.Proxy+import Data.Measured.Class import Data.Range-import Data.Semigroup.Foldable-import Data.Vector.Fixed.Cont (Peano, PeanoNum(..)) import GHC.TypeLits import Prelude hiding (last,init,head) @@ -110,7 +114,7 @@ , 1 <= d -- this one is kind of silly ) => NonEmpty (Point d r :+ p) -> RT 2 d v p r createRangeTree2 = RangeTree . GRT.createTree- . fmap (\p -> p^.core.coord (Proxy :: Proxy 2) :+ Leaf [p])+ . fmap (\p -> p^.core.coord @2 :+ Leaf [p]) -------------------------------------------------------------------------------- -- * Querying@@ -127,11 +131,11 @@ instance (1 <= d, Arity d) => Query 1 d where search' qr = map unAssoc . GRT.search' r . _unRangeTree where- r = qr^.element (Proxy :: Proxy 0)+ r = qr^.element @0 instance ( 1 <= d, i <= d, Query (i-1) d, Arity d , i ~ 2 ) => Query 2 d where search' qr = concatMap (maybe [] (search' qr) . unAssoc) . GRT.search' r . _unRangeTree where- r = qr^.element (Proxy :: Proxy (i-1))+ r = qr^.element @(i-1)
src/Data/Geometry/RangeTree/Generic.hs view
@@ -1,17 +1,24 @@+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.RangeTree.Generic+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Data.Geometry.RangeTree.Generic where import Control.Lens import Data.BinaryTree import Data.Ext-import Data.Geometry.Point import Data.Geometry.Properties import Data.Geometry.RangeTree.Measure import Data.List.NonEmpty (NonEmpty(..)) import qualified Data.List.NonEmpty as NonEmpty import Data.Range+import Data.Measured.Class+import Data.Measured.Size import Data.Semigroup import Data.Semigroup.Foldable-import qualified Data.Set as Set import Data.Util --------------------------------------------------------------------------------
src/Data/Geometry/RangeTree/Measure.hs view
@@ -1,6 +1,13 @@+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.RangeTree.Measure+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Data.Geometry.RangeTree.Measure where -import Data.BinaryTree(Measured(..))+import Data.Measured.Class import Data.Functor.Product(Product(..)) import Data.Functor.Classes @@ -45,11 +52,11 @@ instance (LabeledMeasure l, LabeledMeasure r) => LabeledMeasure (l :*: r) where labeledMeasure xs = Pair (labeledMeasure xs) (labeledMeasure xs) -instance (Semigroup (l a), Semigroup (r a)) => Semigroup ((l :*: r) a) where- (Pair l r) <> (Pair l' r') = Pair (l <> l') (r <> r')+-- instance (Semigroup (l a), Semigroup (r a)) => Semigroup ((l :*: r) a) where+-- (Pair l r) <> (Pair l' r') = Pair (l <> l') (r <> r') -instance (Monoid (l a), Monoid (r a)) => Monoid ((l :*: r) a) where- mempty = Pair mempty mempty+-- instance (Monoid (l a), Monoid (r a)) => Monoid ((l :*: r) a) where+-- mempty = Pair mempty mempty
src/Data/Geometry/SegmentTree.hs view
@@ -1,3 +1,10 @@+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.SegmentTree+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Data.Geometry.SegmentTree( module Data.Geometry.SegmentTree.Generic ) where
src/Data/Geometry/SegmentTree/Generic.hs view
@@ -34,6 +34,8 @@ import qualified Data.List as List import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as NonEmpty+import Data.Measured.Class+import Data.Measured.Size import GHC.Generics (Generic) --------------------------------------------------------------------------------@@ -73,7 +75,7 @@ -- AtomicRange -> OpenRange MinInfinity MaxInfinity -data BuildLeaf a = LeafSingleton a | LeafRange a a deriving (Show,Eq)+data BuildLeaf a = LeafSingleton !a | LeafRange !a !a deriving (Show,Eq) -- | Given a sorted list of endpoints, without duplicates, construct a segment tree --@@ -118,7 +120,7 @@ -> NonEmpty (Interval p r) -> SegmentTree v r fromIntervals f is = foldr (insert . f) (createTree pts mempty) is where- endPoints (toRange -> Range' a b) = [a,b]+ endPoints (asRange -> Range' a b) = [a,b] pts = nub' . NonEmpty.sort . NonEmpty.fromList . concatMap endPoints $ is nub' = fmap NonEmpty.head . NonEmpty.group1 @@ -186,8 +188,8 @@ => i -> SegmentTree v r -> SegmentTree v r insert i (SegmentTree t) = SegmentTree $ insertRoot t where- ri@(Range a b) = toRange i- insertRoot t' = maybe t' (flip insert' t') $ getRange t'+ ri@(Range a b) = asRange i+ insertRoot t' = maybe t' (`insert'` t') $ getRange t' insert' inR lf@(Leaf nd@(LeafData rr _)) | coversAtomic ri inR rr = Leaf $ nd&leafAssoc %~ insertAssoc i@@ -209,7 +211,7 @@ => i -> SegmentTree v r -> SegmentTree v r delete i (SegmentTree t) = SegmentTree $ delete' t where- (Range _ b) = toRange i+ (Range _ b) = asRange i delete' (Leaf ld) = Leaf $ ld&leafAssoc %~ deleteAssoc i delete' (Node l nd@(_splitPoint -> m) r)@@ -254,7 +256,7 @@ instance IntervalLike a => IntervalLike (I a) where- toRange = toRange . _unI+ asRange = asRange . _unI fromIntervals' :: (Eq p, Ord r)
src/Data/Geometry/Slab.hs view
@@ -1,5 +1,12 @@ {-# Language ScopedTypeVariables #-} {-# Language TemplateHaskell #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.Slab+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Data.Geometry.Slab where import Control.Lens (makeLenses, (^.),(%~),(.~),(&), both, from)@@ -19,11 +26,13 @@ -------------------------------------------------------------------------------- +-- | Orthogonal directions data Orthogonal = Horizontal | Vertical deriving (Show,Eq,Read) -+-- | An strip between two parallel lines. The lines can be either+-- horizontal or vertical. newtype Slab (o :: Orthogonal) a r = Slab { _unSlab :: Interval a r } deriving (Show,Eq) makeLenses ''Slab@@ -50,66 +59,73 @@ bimap f g (Slab i) = Slab $ bimap f g i -type instance IntersectionOf (Slab o a r) (Slab o a r) =- [NoIntersection, Slab o a r]-type instance IntersectionOf (Slab Horizontal a r) (Slab Vertical a r) =- '[Rectangle (a,a) r]+type instance IntersectionOf (Slab o a r) (Slab o b r) =+ [NoIntersection, Slab o (Either a b) r]+type instance IntersectionOf (Slab Horizontal a r) (Slab Vertical b r) =+ '[Rectangle (a,b) r] -instance Ord r => (Slab o a r) `IsIntersectableWith` (Slab o a r) where+instance Ord r => Slab o a r `HasIntersectionWith` Slab o b r++instance Ord r => Slab o a r `IsIntersectableWith` Slab o b r where nonEmptyIntersection = defaultNonEmptyIntersection (Slab i) `intersect` (Slab i') = match (i `intersect` i') $- (H $ \NoIntersection -> coRec NoIntersection)- :& (H $ \i'' -> coRec (Slab i'' :: Slab o a r))+ H (\NoIntersection -> coRec NoIntersection)+ :& H (\i'' -> coRec $ (Slab i'' :: Slab o (Either a b) r)) :& RNil -instance (Slab Horizontal a r) `IsIntersectableWith` (Slab Vertical a r) where+instance Slab Horizontal a r `HasIntersectionWith` Slab Vertical b r where+ _ `intersects` _ = True++instance Slab Horizontal a r `IsIntersectableWith` Slab Vertical b r where nonEmptyIntersection _ _ _ = True (Slab h) `intersect` (Slab v) = coRec $ box low high where- low = Point2 (v^.start.core) (h^.start.core) :+ (v^.start.extra, h^.start.extra)- high = Point2 (v^.end.core) (h^.end.core) :+ (v^.end.extra, h^.end.extra)+ low = Point2 (v^.start.core) (h^.start.core) :+ (h^.start.extra, v^.start.extra)+ high = Point2 (v^.end.core) (h^.end.core) :+ (h^.end.extra, v^.end.extra) class HasBoundingLines (o :: Orthogonal) where -- | The two bounding lines of the slab, first the lower one, then the higher one:- -- boundingLines :: Num r => Slab o a r -> (Line 2 r :+ a, Line 2 r :+ a)-+ -- | Test if a point lies inside a slab. inSlab :: Ord r => Point 2 r -> Slab o a r -> Bool instance HasBoundingLines Horizontal where boundingLines (Slab i) = (i^.start, i^.end)&both.core %~ horizontalLine - p `inSlab` (Slab i) = (p^.yCoord) `inInterval` i+ p `inSlab` (Slab i) = (p^.yCoord) `intersectsInterval` i instance HasBoundingLines Vertical where boundingLines (Slab i) = (i^.start, i^.end)&both.core %~ verticalLine - p `inSlab` (Slab i) = (p^.xCoord) `inInterval` i+ p `inSlab` (Slab i) = (p^.xCoord) `intersectsInterval` i type instance IntersectionOf (Line 2 r) (Slab o a r) = [NoIntersection, Line 2 r, LineSegment 2 a r] instance (Fractional r, Ord r, HasBoundingLines o) =>- Line 2 r `IsIntersectableWith` (Slab o a r) where+ Line 2 r `HasIntersectionWith` Slab o a r++instance (Fractional r, Ord r, HasBoundingLines o) =>+ Line 2 r `IsIntersectableWith` Slab o a r where nonEmptyIntersection = defaultNonEmptyIntersection l@(Line p _) `intersect` s = match (l `intersect` a) $- (H $ \NoIntersection -> if p `inSlab` s then coRec l else coRec NoIntersection)- :& (H $ \pa -> match (l `intersect` b) $- (H $ \NoIntersection -> coRec NoIntersection)- :& (H $ \pb -> coRec $ lineSegment' pa pb)- :& (H $ \_ -> coRec l)+ H (\NoIntersection -> if p `inSlab` s then coRec l else coRec NoIntersection)+ :& H (\pa -> match (l `intersect` b) $+ H coRec -- NoIntersection+ :& H (coRec . lineSegment' pa)+ :& H (\_ -> coRec l) :& RNil )- :& (H $ \_ -> coRec l)+ :& H (\_ -> coRec l) :& RNil where (a :+ _,b :+ _) = boundingLines s@@ -125,17 +141,20 @@ [NoIntersection, SubLine 2 () s r] instance (Fractional r, Ord r, HasBoundingLines o) =>- SubLine 2 a r r `IsIntersectableWith` (Slab o a r) where+ SubLine 2 a r r `HasIntersectionWith` Slab o a r +instance (Fractional r, Ord r, HasBoundingLines o) =>+ SubLine 2 a r r `IsIntersectableWith` Slab o a r where+ nonEmptyIntersection = defaultNonEmptyIntersection sl@(SubLine l _) `intersect` s = match (l `intersect` s) $- (H $ \NoIntersection -> coRec NoIntersection)- :& (H $ \(Line _ _) -> coRec $ dropExtra sl)- :& (H $ \seg -> match (sl `intersect` (seg^._SubLine)) $- (H $ \NoIntersection -> coRec NoIntersection)- :& (H $ \p@(Point2 _ _) -> coRec $ singleton p)- :& (H $ \ss -> coRec $ dropExtra ss)+ H (\NoIntersection -> coRec NoIntersection)+ :& H (\(Line _ _) -> coRec $ dropExtra sl)+ :& H (\seg -> match (sl `intersect` (seg^._SubLine)) $+ H (\NoIntersection -> coRec NoIntersection)+ :& H (\p@Point2{} -> coRec $ singleton p)+ :& H ( coRec . dropExtra) :& RNil) :& RNil where@@ -146,12 +165,15 @@ [NoIntersection, LineSegment 2 () r] instance (Fractional r, Ord r, HasBoundingLines o) =>- LineSegment 2 a r `IsIntersectableWith` (Slab o a r) where+ LineSegment 2 a r `HasIntersectionWith` Slab o a r++instance (Fractional r, Ord r, HasBoundingLines o) =>+ LineSegment 2 a r `IsIntersectableWith` Slab o a r where nonEmptyIntersection = defaultNonEmptyIntersection seg `intersect` slab = match ((seg^._SubLine) `intersect` slab) $- (H $ \NoIntersection -> coRec NoIntersection)- :& (H $ \sl -> coRec $ sl^. from _SubLine)+ H (\NoIntersection -> coRec NoIntersection)+ :& H (\sl -> coRec $ sl^. from _SubLine) :& RNil
src/Data/Geometry/SubLine.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE UndecidableInstances #-} -------------------------------------------------------------------------------- -- |@@ -10,7 +9,23 @@ -- SubLine; a part of a line -- ---------------------------------------------------------------------------------module Data.Geometry.SubLine where+module Data.Geometry.SubLine+ ( SubLine(..)+ , line+ , subRange+ , fixEndPoints+ , dropExtra+ , onSubLine+ , onSubLineUB+ , onSubLine2+ , onSubLine2UB+ , reorient+ , getEndPointsUnBounded+ , fromLine+ , _unBounded+ , toUnbounded+ , fromUnbounded+ ) where import Control.Lens import Data.Bifunctor@@ -27,8 +42,6 @@ import Data.Vinyl.CoRec import Test.QuickCheck(Arbitrary(..)) -import Data.Ratio- -------------------------------------------------------------------------------- -- | Part of a line. The interval is ranged based on the vector of the@@ -36,8 +49,16 @@ data SubLine d p s r = SubLine { _line :: Line d r , _subRange :: Interval p s }-makeLenses ''SubLine +-- | Line part of SubLine.+line :: Lens (SubLine d1 p s r1) (SubLine d2 p s r2) (Line d1 r1) (Line d2 r2)+line = lens _line (\sub l -> SubLine l (_subRange sub))++-- | Interval part of SubLine.+subRange :: Lens (SubLine d p1 s1 r) (SubLine d p2 s2 r) (Interval p1 s1) (Interval p2 s2)+subRange = lens _subRange (SubLine . _line)++ type instance Dimension (SubLine d p s r) = d @@ -52,12 +73,13 @@ => Arbitrary (SubLine d p s r) where arbitrary = SubLine <$> arbitrary <*> arbitrary + -- | Annotate the subRange with the actual ending points fixEndPoints :: (Num r, Arity d) => SubLine d p r r -> SubLine d (Point d r :+ p) r r fixEndPoints sl = sl&subRange %~ f where ptAt = flip pointAt (sl^.line)- label (c :+ e) = (c :+ (ptAt c :+ e))+ label (c :+ e) = c :+ (ptAt c :+ e) f ~(Interval l u) = Interval (l&unEndPoint %~ label) (u&unEndPoint %~ label) @@ -65,16 +87,8 @@ dropExtra :: SubLine d p s r -> SubLine d () s r dropExtra = over subRange (first (const ())) -_unBounded :: Prism' (SubLine d p (UnBounded r) r) (SubLine d p r r)-_unBounded = prism' toUnbounded fromUnbounded --- | Transform into an subline with a potentially unbounded interval-toUnbounded :: SubLine d p r r -> SubLine d p (UnBounded r) r-toUnbounded = over subRange (fmap Val) --- | Try to make a potentially unbounded subline into a bounded one.-fromUnbounded :: SubLine d p (UnBounded r) r -> Maybe (SubLine d p r r)-fromUnbounded (SubLine l i) = SubLine l <$> mapM unBoundedToMaybe i -- | given point p, and a Subline l r such that p lies on line l, test if it -- lies on the subline, i.e. in the interval r@@ -82,20 +96,13 @@ => Point d r -> SubLine d p r r -> Bool onSubLine p (SubLine l r) = case toOffset p l of Nothing -> False- Just x -> x `inInterval` r+ Just x -> x `intersectsInterval` r --- | given point p, and a Subline l r such that p lies on line l, test if it--- lies on the subline, i.e. in the interval r-onSubLineUB :: (Ord r, Fractional r)- => Point 2 r -> SubLine 2 p (UnBounded r) r -> Bool-p `onSubLineUB` (SubLine l r) = case toOffset p l of- Nothing -> False- Just x -> Val x `inInterval` r -- | given point p, and a Subline l r such that p lies on line l, test if it -- lies on the subline, i.e. in the interval r onSubLine2 :: (Ord r, Num r) => Point 2 r -> SubLine 2 p r r -> Bool-p `onSubLine2` sl = d `inInterval` r+p `onSubLine2` sl = d `intersectsInterval` r where -- get the endpoints (a,b) of the subline SubLine _ (Interval s e) = fixEndPoints sl@@ -106,72 +113,84 @@ r = Interval (s&unEndPoint.core .~ 0) (e&unEndPoint.core .~ squaredEuclideanDist b a) --- | given point p, and a Subline l r such that p lies on line l, test if it--- lies on the subline, i.e. in the interval r-onSubLine2UB :: (Ord r, Fractional r)- => Point 2 r -> SubLine 2 p (UnBounded r) r -> Bool-p `onSubLine2UB` sl = p `onSubLineUB` sl+type instance IntersectionOf (SubLine 2 p s r) (SubLine 2 q s r) =+ [ NoIntersection, Point 2 r, SubLine 2 (Either p q) s r] -type instance IntersectionOf (SubLine 2 p s r) (SubLine 2 q s r) = [ NoIntersection- , Point 2 r- , SubLine 2 p s r- ] + instance (Ord r, Fractional r) =>- (SubLine 2 p r r) `IsIntersectableWith` (SubLine 2 p r r) where+ SubLine 2 p r r `HasIntersectionWith` SubLine 2 q r r ++-- -- | Given two sublines that supposedly have the same line (but+-- -- possibly represented differently), test if they intersect.+-- intersectsSLRange :: SubLine 2 p r r -> SubLine 2 q r r -> Bool+-- intersectsSLRange = undefined+++-- -- | Given two sublines of the s ame line (but possibly represented differently)+-- -- align the first one to the second one.+-- --+-- -- pre: the+-- alignTo :: (Eq r, Num r, Arity d) => SubLine d p r r -> SubLine d q r r -> SubLine d p r r+-- sl `alignTo` (SubLine l@(Line p v) i2) = SubLine l i'+-- where+-- SubLine (Line q u) i = reorient sl v+++-- i' = undefined++++++++++-- | Given a subline with vector u, and a vector v that is parallel to+-- u (but possibly pointing in the exact opposite direction). Make the+-- subline point in direction v as well (but keep the magnitude of the+-- original vector.)+--+-- pre: the lines are parallel.+reorient :: (Eq r,Num r, Arity d) => SubLine d p r r -> Vector d r -> SubLine d p r r+reorient sl@(SubLine (Line p u) i) v+ | sameDirection u v = sl+ | otherwise = SubLine (Line p ((-1) *^ u)) (flipInterval i)++++++{- HLINT ignore "Redundant bracket" -}+instance (Ord r, Fractional r) =>+ SubLine 2 p r r `IsIntersectableWith` SubLine 2 q r r where+ nonEmptyIntersection = defaultNonEmptyIntersection sl@(SubLine l r) `intersect` sm@(SubLine m _) = match (l `intersect` m) $- (H $ \NoIntersection -> coRec NoIntersection)- :& (H $ \p@(Point _) -> if onSubLine2 p sl && onSubLine2 p sm+ H (\NoIntersection -> coRec NoIntersection)+ :& H (\p@(Point _) -> if onSubLine2 p sl && onSubLine2 p sm then coRec p else coRec NoIntersection)- :& (H $ \_ -> match (r `intersect` s'') $- (H $ \NoIntersection -> coRec NoIntersection)- :& (H $ \i -> coRec $ SubLine l i)+ :& H (\_ -> match (r `intersect` s'') $+ H coRec -- NoIntersection+ :& H (coRec . SubLine l) :& RNil ) :& RNil where s' = (fixEndPoints sm)^.subRange- s'' = bimap (^.extra) id+ s'' = asProperInterval . first (^.extra) $ s'&start.core .~ toOffset' (s'^.start.extra.core) l &end.core .~ toOffset' (s'^.end.extra.core) l -instance (Ord r, Fractional r) =>- (SubLine 2 p (UnBounded r) r) `IsIntersectableWith` (SubLine 2 p (UnBounded r) r) where- nonEmptyIntersection = defaultNonEmptyIntersection - sl@(SubLine l r) `intersect` sm@(SubLine m _) = match (l `intersect` m) $- (H $ \NoIntersection -> coRec NoIntersection)- :& (H $ \p@(Point _) -> if onSubLine2UB p sl && onSubLine2UB p sm- then coRec p- else coRec NoIntersection)- :& (H $ \_ -> match (r `intersect` s'') $- (H $ \NoIntersection -> coRec NoIntersection)- :& (H $ \i -> coRec $ SubLine l i)- :& RNil- )- :& RNil- where- -- convert to points, then convert back to 'r' values (but now w.r.t. l)- s' = getEndPointsUnBounded sm- s'' = second (fmap f) s'- f = flip toOffset' l --- | Get the endpoints of an unbounded interval-getEndPointsUnBounded :: (Num r, Arity d) => SubLine d p (UnBounded r) r- -> Interval p (UnBounded (Point d r))-getEndPointsUnBounded sl = second (fmap f) $ sl^.subRange- where- f = flip pointAt (sl^.line) -fromLine :: Arity d => Line d r -> SubLine d () (UnBounded r) r-fromLine l = SubLine l (ClosedInterval (ext MinInfinity) (ext MaxInfinity)) - -- testL :: SubLine 2 () (UnBounded Rational) -- testL = SubLine (horizontalLine 0) (Interval (Closed (only 0)) (Open $ only 10)) @@ -185,6 +204,93 @@ -- testzz = let f = bimap (fmap Val) (const ()) -- in -testz :: SubLine 2 () Rational Rational-testz = SubLine (Line (Point2 0 0) (Vector2 10 0))- (Interval (Closed (0 % 1 :+ ())) (Closed (1 % 1 :+ ())))+-- testz :: SubLine 2 () Rational Rational+-- testz = SubLine (Line (Point2 0 0) (Vector2 10 0))+-- (Interval (Closed (0 % 1 :+ ())) (Closed (1 % 1 :+ ())))+++++--------------------------------------------------------------------------------+-- * Anything that deals with Unbounded intervals++-- | Create a SubLine that covers the original line from -infinity to +infinity.+fromLine :: Arity d => Line d r -> SubLine d () (UnBounded r) r+fromLine l = SubLine l (ClosedInterval (ext MinInfinity) (ext MaxInfinity))+++-- | Prism for downcasting an unbounded subline to a subline.+_unBounded :: Prism' (SubLine d p (UnBounded r) r) (SubLine d p r r)+_unBounded = prism' toUnbounded fromUnbounded++-- | Transform into an subline with a potentially unbounded interval+toUnbounded :: SubLine d p r r -> SubLine d p (UnBounded r) r+toUnbounded = over subRange (fmap Val)++-- | Try to make a potentially unbounded subline into a bounded one.+fromUnbounded :: SubLine d p (UnBounded r) r -> Maybe (SubLine d p r r)+fromUnbounded (SubLine l i) = SubLine l <$> mapM unBoundedToMaybe i+++-- | Get the endpoints of an unbounded interval+getEndPointsUnBounded :: (Num r, Arity d) => SubLine d p (UnBounded r) r+ -> Interval p (UnBounded (Point d r))+getEndPointsUnBounded sl = second (fmap f) $ sl^.subRange+ where+ f = flip pointAt (sl^.line)++++++-- | given point p, and a Subline l r such that p lies on line l, test if it+-- lies on the subline, i.e. in the interval r+onSubLineUB :: (Ord r, Fractional r)+ => Point 2 r -> SubLine 2 p (UnBounded r) r -> Bool+p `onSubLineUB` (SubLine l r) =+ p `onLine2` l &&+ Val (toOffset' p l) `intersectsInterval` r++inSubLineIntervalUB :: (Ord r, Fractional r)+ => Point 2 r -> SubLine 2 p (UnBounded r) r -> Bool+p `inSubLineIntervalUB` (SubLine l r) = Val (toOffset' p l) `intersectsInterval` r++++-- | given point p, and a Subline l r such that p lies on line l, test if it+-- lies on the subline, i.e. in the interval r+onSubLine2UB :: (Ord r, Fractional r)+ => Point 2 r -> SubLine 2 p (UnBounded r) r -> Bool+p `onSubLine2UB` sl = p `onSubLineUB` sl++++++++--------++instance (Ord r, Fractional r) =>+ SubLine 2 p (UnBounded r) r `HasIntersectionWith` SubLine 2 q (UnBounded r) r++instance (Ord r, Fractional r) =>+ SubLine 2 p (UnBounded r) r `IsIntersectableWith` SubLine 2 q (UnBounded r) r where+ nonEmptyIntersection = defaultNonEmptyIntersection++ sl@(SubLine l r) `intersect` sm@(SubLine m _) = match (l `intersect` m) $+ H (\NoIntersection -> coRec NoIntersection)+ :& H (\p@(Point _) -> if inSubLineIntervalUB p sl && inSubLineIntervalUB p sm+ then coRec p+ else coRec NoIntersection)+ :& H (\_ -> match (r `intersect` s'') $+ H coRec -- NoIntersection+ :& H (coRec . SubLine l)+ :& RNil+ )+ :& RNil+ where+ -- convert to points, then convert back to 'r' values (but now w.r.t. l)+ s' = getEndPointsUnBounded sm+ s'' = second (fmap f) s'+ f = flip toOffset' l
src/Data/Geometry/Transformation.hs view
@@ -1,173 +1,59 @@-{-# LANGUAGE UndecidableInstances #-}-module Data.Geometry.Transformation where--import Control.Lens (lens,Lens',set)-import Unsafe.Coerce(unsafeCoerce)-import Data.Geometry.Point-import Data.Geometry.Properties-import Data.Geometry.Vector-import qualified Data.Geometry.Vector as V-import Data.Proxy-import qualified Data.Vector.Fixed as FV-import GHC.TypeLits-import Linear.Matrix ((!*),(!*!))-import qualified Linear.Matrix as Lin- ----------------------------------------------------------------------------------- * Matrices---- | a matrix of n rows, each of m columns, storing values of type r-newtype Matrix n m r = Matrix (Vector n (Vector m r))--deriving instance (Show r, Arity n, Arity m) => Show (Matrix n m r)-deriving instance (Eq r, Arity n, Arity m) => Eq (Matrix n m r)-deriving instance (Ord r, Arity n, Arity m) => Ord (Matrix n m r)-deriving instance (Arity n, Arity m) => Functor (Matrix n m)--multM :: (Arity r, Arity c, Arity c', Num a) => Matrix r c a -> Matrix c c' a -> Matrix r c' a-(Matrix a) `multM` (Matrix b) = Matrix $ a !*! b--mult :: (Arity m, Arity n, Num r) => Matrix n m r -> Vector m r -> Vector n r-(Matrix m) `mult` v = m !* v---class Invertible n r where- inverse' :: Matrix n n r -> Matrix n n r--instance Fractional r => Invertible 2 r where- -- >>> inverse' $ Matrix $ Vector2 (Vector2 1 2) (Vector2 3 4.0)- -- Matrix Vector2 [Vector2 [-2.0,1.0],Vector2 [1.5,-0.5]]- inverse' (Matrix m) = Matrix . unsafeCoerce . Lin.inv22 . unsafeCoerce $ m--instance Fractional r => Invertible 3 r where- -- >>> inverse' $ Matrix $ Vector3 (Vector3 1 2 4) (Vector3 4 2 2) (Vector3 1 1 1.0)- -- Matrix Vector3 [Vector3 [0.0,0.5,-1.0],Vector3 [-0.5,-0.75,3.5],Vector3 [0.5,0.25,-1.5]]- inverse' (Matrix m) = Matrix . unsafeCoerce . Lin.inv33 . unsafeCoerce $ m--instance Fractional r => Invertible 4 r where- inverse' (Matrix m) = Matrix . unsafeCoerce . Lin.inv44 . unsafeCoerce $ m-+-- |+-- Module : Data.Geometry.Transformation+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals ----------------------------------------------------------------------------------- * Transformations---- | A type representing a Transformation for d dimensional objects-newtype Transformation d r = Transformation { _transformationMatrix :: Matrix (d + 1) (d + 1) r }--transformationMatrix :: Lens' (Transformation d r) (Matrix (d + 1) (d + 1) r)-transformationMatrix = lens _transformationMatrix (const Transformation)--deriving instance (Show r, Arity (d + 1)) => Show (Transformation d r)-deriving instance (Eq r, Arity (d + 1)) => Eq (Transformation d r)-deriving instance (Ord r, Arity (d + 1)) => Ord (Transformation d r)-deriving instance Arity (d + 1) => Functor (Transformation d)+module Data.Geometry.Transformation+ ( Transformation(Transformation)+ , transformationMatrix+ , (|.|), identity, inverseOf -type instance NumType (Transformation d r) = r+ , IsTransformable(..)+ , transformAllBy+ , transformPointFunctor + , translation, scaling, uniformScaling --- | Compose transformations (right to left)-(|.|) :: (Num r, Arity (d + 1)) => Transformation d r -> Transformation d r -> Transformation d r-(Transformation f) |.| (Transformation g) = Transformation $ f `multM` g+ , translateBy, scaleBy, scaleUniformlyBy + , rotateTo --- if it exists?+ , skewX, rotation, reflection, reflectionV, reflectionH --- | Compute the inverse transformation------ >>> inverseOf $ translation (Vector2 (10.0) (5.0))--- Transformation {_transformationMatrix = Matrix Vector3 [Vector3 [1.0,0.0,-10.0],Vector3 [0.0,1.0,-5.0],Vector3 [0.0,0.0,1.0]]}-inverseOf :: (Fractional r, Invertible (d + 1) r)- => Transformation d r -> Transformation d r-inverseOf = Transformation . inverse' . _transformationMatrix+ , fitToBox+ , fitToBoxTransform+ ) where +import Control.Lens+import Data.Ext+import Data.Geometry.Box.Internal (Rectangle, IsBoxable)+import qualified Data.Geometry.Box.Internal as Box+import Data.Geometry.Properties+import Data.Geometry.Point+import Data.Geometry.Transformation.Internal+import Data.Geometry.Vector ----------------------------------------------------------------------------------- * Transformable geometry objects --- | A class representing types that can be transformed using a transformation-class IsTransformable g where- transformBy :: Transformation (Dimension g) (NumType g) -> g -> g--transformAllBy :: (Functor c, IsTransformable g)- => Transformation (Dimension g) (NumType g) -> c g -> c g-transformAllBy t = fmap (transformBy t)---transformPointFunctor :: ( PointFunctor g, Fractional r, d ~ Dimension (g r)- , Arity d, Arity (d + 1)- ) => Transformation d r -> g r -> g r-transformPointFunctor t = pmap (transformBy t)--instance (Fractional r, Arity d, Arity (d + 1))- => IsTransformable (Point d r) where- transformBy t = Point . transformBy t . toVec--instance (Fractional r, Arity d, Arity (d + 1))- => IsTransformable (Vector d r) where- transformBy (Transformation m) v = f $ m `mult` snoc v 1- where- f u = (/ V.last u) <$> V.init u-------------------------------------------------------------------------------------- * Common transformations--translation :: (Num r, Arity d, Arity (d + 1))- => Vector d r -> Transformation d r-translation v = Transformation . Matrix $ V.imap transRow (snoc v 1)---scaling :: (Num r, Arity d, Arity (d + 1))- => Vector d r -> Transformation d r-scaling v = Transformation . Matrix $ V.imap mkRow (snoc v 1)--uniformScaling :: (Num r, Arity d, Arity (d + 1)) => r -> Transformation d r-uniformScaling = scaling . pure------------------------------------------------------------------------------------- * Functions that execute transformations--translateBy :: ( IsTransformable g, Num (NumType g)- , Arity (Dimension g), Arity (Dimension g + 1)- ) => Vector (Dimension g) (NumType g) -> g -> g-translateBy = transformBy . translation--scaleBy :: ( IsTransformable g, Num (NumType g)- , Arity (Dimension g), Arity (Dimension g + 1)- ) => Vector (Dimension g) (NumType g) -> g -> g-scaleBy = transformBy . scaling---scaleUniformlyBy :: ( IsTransformable g, Num (NumType g)- , Arity (Dimension g), Arity (Dimension g + 1)- ) => NumType g -> g -> g-scaleUniformlyBy = transformBy . uniformScaling-------------------------------------------------------------------------------------- * Helper functions to easily create matrices---- | Creates a row with zeroes everywhere, except at position i, where the--- value is the supplied value.-mkRow :: forall d r. (Arity d, Num r) => Int -> r -> Vector d r-mkRow i x = set (FV.element i) x zero---- | Row in a translation matrix--- transRow :: forall n r. ( Arity n, Arity (n- 1), ((n - 1) + 1) ~ n--- , Num r) => Int -> r -> Vector n r--- transRow i x = set (V.element (Proxy :: Proxy (n-1))) x $ mkRow i 1--transRow :: forall n r. (Arity n, Arity (n + 1), Num r)- => Int -> r -> Vector (n + 1) r-transRow i x = set (V.element (Proxy :: Proxy n)) x $ mkRow i 1------------------------------------------------------------------------------------- * 3D Rotations+-- | Given a rectangle r and a geometry g with its boundingbox,+-- transform the g to fit r.+fitToBox :: forall g r q.+ ( IsTransformable g, IsBoxable g, NumType g ~ r, Dimension g ~ 2+ , Ord r, Fractional r+ ) => Rectangle q r -> g -> g+fitToBox r g = transformBy (fitToBoxTransform r g) g --- | Given three new unit-length basis vectors (u,v,w) that map to (x,y,z),--- construct the appropriate rotation that does this.-------rotateTo :: Num r => Vector 3 (Vector 3 r) -> Transformation 3 r-rotateTo (Vector3 u v w) = Transformation . Matrix $ Vector4 (snoc u 0)- (snoc v 0)- (snoc w 0)- (Vector4 0 0 0 1)+-- | Given a rectangle r and a geometry g with its boundingbox,+-- compute a transformation can fit g to r.+fitToBoxTransform :: forall g r q. ( IsTransformable g, IsBoxable g+ , NumType g ~ r, Dimension g ~ 2+ , Ord r, Fractional r+ ) => Rectangle q r -> g -> Transformation 2 r+fitToBoxTransform r g = translation v2 |.| uniformScaling lam |.| translation v1+ where+ b = Box.boundingBox g+ v1 :: Vector 2 r+ v1 = negate <$> b^.to Box.minPoint.core.vector+ v2 = r^.to Box.minPoint.core.vector+ lam = minimum $ (/) <$> Box.size r <*> Box.size b
+ src/Data/Geometry/Transformation/Internal.hs view
@@ -0,0 +1,224 @@+{-# LANGUAGE UndecidableInstances #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.Transformation.Internal+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Data.Geometry.Transformation.Internal where++import Control.Lens (iso,set,Iso,imap)+import Data.Geometry.Matrix+import Data.Geometry.Matrix.Internal (mkRow)+import Data.Geometry.Point+import Data.Geometry.Properties+import Data.Geometry.Vector+import qualified Data.Geometry.Vector as V+import GHC.TypeLits++{- $setup+>>> import Data.Geometry.LineSegment+>>> import Data.Ext+-}++--------------------------------------------------------------------------------+-- * Transformations++-- | A type representing a Transformation for d dimensional objects+newtype Transformation d r = Transformation { _transformationMatrix :: Matrix (d + 1) (d + 1) r }++-- | Transformations and Matrices are isomorphic.+transformationMatrix :: Iso (Transformation d r) (Transformation d s)+ (Matrix (d + 1) (d + 1) r) (Matrix (d + 1) (d + 1) s)+transformationMatrix = iso _transformationMatrix Transformation++deriving instance (Show r, Arity (d + 1)) => Show (Transformation d r)+deriving instance (Eq r, Arity (d + 1)) => Eq (Transformation d r)+deriving instance (Ord r, Arity (d + 1)) => Ord (Transformation d r)+deriving instance Arity (d + 1) => Functor (Transformation d)+deriving instance Arity (d + 1) => Foldable (Transformation d)+deriving instance Arity (d + 1) => Traversable (Transformation d)++type instance NumType (Transformation d r) = r++-- | Compose transformations (right to left)+(|.|) :: (Num r, Arity (d + 1)) => Transformation d r -> Transformation d r -> Transformation d r+(Transformation f) |.| (Transformation g) = Transformation $ f `multM` g++-- | Identity transformation; i.e. the transformation which does not change anything.+identity :: (Num r, Arity (d + 1)) => Transformation d r+identity = Transformation identityMatrix++instance (Num r, Arity (d+1)) => Semigroup (Transformation d r) where+ (<>) = (|.|)+instance (Num r, Arity (d+1)) => Monoid (Transformation d r) where+ mempty = identity+++-- if it exists?++-- | Compute the inverse transformation+--+-- >>> inverseOf $ translation (Vector2 (10.0) (5.0))+-- Transformation {_transformationMatrix = Matrix (Vector3 (Vector3 1.0 0.0 (-10.0)) (Vector3 0.0 1.0 (-5.0)) (Vector3 0.0 0.0 1.0))}+inverseOf :: (Fractional r, Invertible (d + 1) r)+ => Transformation d r -> Transformation d r+inverseOf = Transformation . inverse' . _transformationMatrix++--------------------------------------------------------------------------------+-- * Transformable geometry objects++-- | A class representing types that can be transformed using a transformation+class IsTransformable g where+ transformBy :: Transformation (Dimension g) (NumType g) -> g -> g++-- | Apply a transformation to a collection of objects.+--+-- >>> transformAllBy (uniformScaling 2) [Point1 1, Point1 2, Point1 3]+-- [Point1 2.0,Point1 4.0,Point1 6.0]+transformAllBy :: (Functor c, IsTransformable g)+ => Transformation (Dimension g) (NumType g) -> c g -> c g+transformAllBy t = fmap (transformBy t)++-- | Apply transformation to a PointFunctor, ie something that contains+-- points. Polygons, triangles, line segments, etc, are all PointFunctors.+--+-- >>> transformPointFunctor (uniformScaling 2) $ OpenLineSegment (Point1 1 :+ ()) (Point1 2 :+ ())+-- OpenLineSegment (Point1 2.0 :+ ()) (Point1 4.0 :+ ())+transformPointFunctor :: ( PointFunctor g, Fractional r, d ~ Dimension (g r)+ , Arity d, Arity (d + 1)+ ) => Transformation d r -> g r -> g r+transformPointFunctor t = pmap (transformBy t)++instance (Fractional r, Arity d, Arity (d + 1))+ => IsTransformable (Point d r) where+ transformBy t = Point . transformBy t . toVec++instance (Fractional r, Arity d, Arity (d + 1))+ => IsTransformable (Vector d r) where+ transformBy (Transformation m) v = f $ m `mult` snoc v 1+ where+ f u = (/ V.last u) <$> V.init u+++--------------------------------------------------------------------------------+-- * Common transformations++-- | Create translation transformation from a vector.+--+-- >>> transformBy (translation $ Vector2 1 2) $ Point2 2 3+-- Point2 3.0 5.0+translation :: (Num r, Arity d, Arity (d + 1))+ => Vector d r -> Transformation d r+translation v = Transformation . Matrix $ imap transRow (snoc v 1)++-- | Create scaling transformation from a vector.+--+-- >>> transformBy (scaling $ Vector2 2 (-1)) $ Point2 2 3+-- Point2 4.0 (-3.0)+scaling :: (Num r, Arity d, Arity (d + 1))+ => Vector d r -> Transformation d r+scaling v = Transformation . Matrix $ imap mkRow (snoc v 1)++-- | Create scaling transformation from a scalar that is applied+-- to all dimensions.+--+-- >>> transformBy (uniformScaling 5) $ Point2 2 3+-- Point2 10.0 15.0+-- >>> uniformScaling 5 == scaling (Vector2 5 5)+-- True+-- >>> uniformScaling 5 == scaling (Vector3 5 5 5)+-- True+uniformScaling :: (Num r, Arity d, Arity (d + 1)) => r -> Transformation d r+uniformScaling = scaling . pure+++--------------------------------------------------------------------------------+-- * Functions that execute transformations++-- | Translate a given point.+--+-- >>> translateBy (Vector2 1 2) $ Point2 2 3+-- Point2 3.0 5.0+translateBy :: ( IsTransformable g, Num (NumType g)+ , Arity (Dimension g), Arity (Dimension g + 1)+ ) => Vector (Dimension g) (NumType g) -> g -> g+translateBy = transformBy . translation++-- | Scale a given point.+--+-- >>> scaleBy (Vector2 2 (-1)) $ Point2 2 3+-- Point2 4.0 (-3.0)+scaleBy :: ( IsTransformable g, Num (NumType g)+ , Arity (Dimension g), Arity (Dimension g + 1)+ ) => Vector (Dimension g) (NumType g) -> g -> g+scaleBy = transformBy . scaling+++-- | Scale a given point uniformly in all dimensions.+--+-- >>> scaleUniformlyBy 5 $ Point2 2 3+-- Point2 10.0 15.0+scaleUniformlyBy :: ( IsTransformable g, Num (NumType g)+ , Arity (Dimension g), Arity (Dimension g + 1)+ ) => NumType g -> g -> g+scaleUniformlyBy = transformBy . uniformScaling+++-- | Row in a translation matrix+-- transRow :: forall n r. ( Arity n, Arity (n- 1), ((n - 1) + 1) ~ n+-- , Num r) => Int -> r -> Vector n r+-- transRow i x = set (V.element (Proxy :: Proxy (n-1))) x $ mkRow i 1++transRow :: forall n r. (Arity n, Arity (n + 1), Num r)+ => Int -> r -> Vector (n + 1) r+transRow i x = set (V.element @n) x $ mkRow i 1++--------------------------------------------------------------------------------+-- * 3D Rotations++-- | Given three new unit-length basis vectors (u,v,w) that map to (x,y,z),+-- construct the appropriate rotation that does this.+--+--+rotateTo :: Num r => Vector 3 (Vector 3 r) -> Transformation 3 r+rotateTo (Vector3 u v w) = Transformation . Matrix $ Vector4 (snoc u 0)+ (snoc v 0)+ (snoc w 0)+ (Vector4 0 0 0 1)++--------------------------------------------------------------------------------+-- * 2D Transformations++-- | Skew transformation that keeps the y-coordinates fixed and shifts+-- the x coordinates.+skewX :: Num r => r -> Transformation 2 r+skewX lambda = Transformation . Matrix $ Vector3 (Vector3 1 lambda 0)+ (Vector3 0 1 0)+ (Vector3 0 0 1)++-- | Create a matrix that corresponds to a rotation by 'a' radians counter-clockwise+-- around the origin.+rotation :: Floating r => r -> Transformation 2 r+rotation a = Transformation . Matrix $ Vector3 (Vector3 (cos a) (- sin a) 0)+ (Vector3 (sin a) ( cos a) 0)+ (Vector3 0 0 1)++-- | Create a matrix that corresponds to a reflection in a line through the origin+-- which makes an angle of 'a' radians with the positive 'x'-asis, in counter-clockwise+-- orientation.+reflection :: Floating r => r -> Transformation 2 r+reflection a = rotation a |.| reflectionV |.| rotation (-a)++-- | Vertical reflection+reflectionV :: Num r => Transformation 2 r+reflectionV = Transformation . Matrix $ Vector3 (Vector3 1 0 0)+ (Vector3 0 (-1) 0)+ (Vector3 0 0 1)++-- | Horizontal reflection+reflectionH :: Num r => Transformation 2 r+reflectionH = Transformation . Matrix $ Vector3 (Vector3 (-1) 0 0)+ (Vector3 0 1 0)+ (Vector3 0 0 1)
src/Data/Geometry/Triangle.hs view
@@ -1,47 +1,72 @@ {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE UndecidableInstances #-}+-- | Triangles in \(d\)-dimensional space. module Data.Geometry.Triangle where +import Control.DeepSeq (NFData) import Control.Lens-import Data.Bifunctor-import Data.Either (partitionEithers)+import Data.Bifoldable (Bifoldable (bifoldMap))+import Data.Bifunctor (Bifunctor (first))+import Data.Bitraversable+import Data.Either (partitionEithers) import Data.Ext-import Data.Geometry.Ball (Disk, disk)-import Data.Geometry.Boundary+import Data.Geometry.Ball (Disk, disk)+import Data.Geometry.Boundary (PointLocationResult (..))+import Data.Geometry.Box (IsBoxable (..)) import Data.Geometry.HyperPlane-import Data.Geometry.Line+import Data.Geometry.Line (Line (Line)) import Data.Geometry.LineSegment import Data.Geometry.Point import Data.Geometry.Properties import Data.Geometry.Transformation import Data.Geometry.Vector-import qualified Data.Geometry.Vector as V-import qualified Data.List as List-import Data.Maybe (mapMaybe)-import Data.Vinyl-import Data.Vinyl.CoRec-import GHC.TypeLits-+import qualified Data.Geometry.Vector as V+import qualified Data.List as List+import Data.Maybe (mapMaybe)+import Data.Util (Three, pattern Three)+import Data.Vinyl (Rec (RNil, (:&)))+import Data.Vinyl.CoRec (Handler (H), match)+import GHC.Generics (Generic)+import GHC.TypeLits (type (+)) -------------------------------------------------------------------------------- --- | Triangles in \(d\)-dimensional space.-data Triangle d p r = Triangle (Point d r :+ p)- (Point d r :+ p)- (Point d r :+ p)+-- | A triangle in \(d\)-dimensional space.+data Triangle d p r = Triangle !(Point d r :+ p)+ !(Point d r :+ p)+ !(Point d r :+ p)+ deriving (Generic) -deriving instance (Arity d, Show r, Show p) => Show (Triangle d p r)-deriving instance (Arity d, Read r, Read p) => Read (Triangle d p r)-deriving instance (Arity d, Eq r, Eq p) => Eq (Triangle d p r)+deriving instance (Arity d, Show r, Show p) => Show (Triangle d p r)+deriving instance (Arity d, Read r, Read p) => Read (Triangle d p r)+deriving instance (Arity d, Eq r, Eq p) => Eq (Triangle d p r) -instance Arity d => Functor (Triangle d p) where- fmap f (Triangle p q r) = let f' = first (fmap f) in Triangle (f' p) (f' q) (f' r)+instance (Arity d, NFData r, NFData p) => NFData (Triangle d p r) +instance Arity d => Bifunctor (Triangle d) where bimap = bimapDefault+instance Arity d => Bifoldable (Triangle d) where bifoldMap = bifoldMapDefault +instance Arity d => Bitraversable (Triangle d) where+ bitraverse f g (Triangle p q r) = let tr = bitraverse (traverse g) f in+ Triangle <$> tr p <*> tr q <*> tr r++-- instance Arity d => Functor (Triangle d p) where+-- fmap f (Triangle p q r) = let f' = first (fmap f) in Triangle (f' p) (f' q) (f' r)++instance Field1 (Triangle d p r) (Triangle d p r) (Point d r :+ p) (Point d r :+ p) where+ _1 = lens (\(Triangle p _ _) -> p) (\(Triangle _ q r) p -> Triangle p q r)+instance Field2 (Triangle d p r) (Triangle d p r) (Point d r :+ p) (Point d r :+ p) where+ _2 = lens (\(Triangle _ q _) -> q) (\(Triangle p _ r) q -> Triangle p q r)+instance Field3 (Triangle d p r) (Triangle d p r) (Point d r :+ p) (Point d r :+ p) where+ _3 = lens (\(Triangle _ _ r) -> r) (\(Triangle p q _) r -> Triangle p q r)+ type instance NumType (Triangle d p r) = r type instance Dimension (Triangle d p r) = d +-- | A \(d\)-dimensional triangle is isomorphic to a triple of \(d\)-dimensional points.+_TriangleThreePoints :: Iso' (Triangle d p r) (Three (Point d r :+ p))+_TriangleThreePoints = iso (\(Triangle p q r) -> Three p q r) (\(Three p q r) -> Triangle p q r)+ instance PointFunctor (Triangle d p) where pmap f (Triangle p q r) = Triangle (p&core %~ f) (q&core %~ f) (r&core %~ f) @@ -54,7 +79,7 @@ where Triangle' p q r = Triangle (ext p) (ext q) (ext r) -+-- | Get the three line-segments that make up the sides of a triangle. sideSegments :: Triangle d p r -> [LineSegment d p r] sideSegments (Triangle p q r) = [ClosedLineSegment p q, ClosedLineSegment q r, ClosedLineSegment r p]@@ -78,9 +103,9 @@ isDegenerateTriangle :: (Num r, Eq r) => Triangle 2 p r -> Bool isDegenerateTriangle = (== 0) . doubleArea --- | get the inscribed disk. Returns Nothing if the triangle is degenerate,+-- | Get the inscribed disk. Returns Nothing if the triangle is degenerate, -- i.e. if the points are colinear.-inscribedDisk :: (Eq r, Fractional r)+inscribedDisk :: (Ord r, Fractional r) => Triangle 2 p r -> Maybe (Disk () r) inscribedDisk (Triangle p q r) = disk (p^.core) (q^.core) (r^.core) @@ -121,18 +146,33 @@ inTriangle :: (Ord r, Fractional r) => Point 2 r -> Triangle 2 p r -> PointLocationResult inTriangle q t- | all (`inRange` (OpenRange 0 1)) [a,b,c] = Inside- | all (`inRange` (ClosedRange 0 1)) [a,b,c] = OnBoundary+ | all (`inRange` OpenRange 0 1) [a,b,c] = Inside+ | all (`inRange` ClosedRange 0 1) [a,b,c] = OnBoundary | otherwise = Outside where Vector3 a b c = toBarricentric q t +inTriangleRelaxed :: (Ord r, Num r)+ => Point 2 r -> Triangle 2 p r -> PointLocationResult+inTriangleRelaxed q (Triangle a b c)+ | ab == CoLinear && bc == ca = OnBoundary+ | bc == CoLinear && ca == ab = OnBoundary+ | ca == CoLinear && bc == ab = OnBoundary+ | ab == bc && bc == ca = Inside+ | otherwise = Outside+ where+ ab = ccw (a^.core) (b^.core) q+ bc = ccw (b^.core) (c^.core) q+ ca = ccw (c^.core) (a^.core) q+ -- | Test if a point lies inside or on the boundary of a triangle onTriangle :: (Ord r, Fractional r) => Point 2 r -> Triangle 2 p r -> Bool q `onTriangle` t = let Vector3 a b c = toBarricentric q t- in all (`inRange` (ClosedRange 0 1)) [a,b,c]+ in all (`inRange` ClosedRange 0 1) [a,b,c] +onTriangleRelaxed :: (Ord r, Num r) => Point 2 r -> Triangle 2 p r -> Bool+q `onTriangleRelaxed` t = inTriangleRelaxed q t /= Outside -- myQ :: Point 2 Rational -- myQ = read "Point2 [(-5985) % 16,(-14625) % 1]"@@ -142,7 +182,9 @@ type instance IntersectionOf (Line 2 r) (Triangle 2 p r) = [ NoIntersection, Point 2 r, LineSegment 2 () r ] -instance (Fractional r, Ord r) => (Line 2 r) `IsIntersectableWith` (Triangle 2 p r) where+instance (Fractional r, Ord r) => Line 2 r `HasIntersectionWith` Triangle 2 p r++instance (Fractional r, Ord r) => Line 2 r `IsIntersectableWith` Triangle 2 p r where nonEmptyIntersection = defaultNonEmptyIntersection l `intersect` (Triangle p q r) =@@ -157,9 +199,9 @@ collect :: LineSegment 2 p r -> Maybe (Either (Point 2 r) (LineSegment 2 p r)) collect s = match (s `intersect` l) $- (H $ \NoIntersection -> Nothing)- :& (H $ \(a :: Point 2 r) -> Just $ Left a)- :& (H $ \(e :: LineSegment 2 p r) -> Just $ Right e)+ H (\NoIntersection -> Nothing)+ :& H (\(a :: Point 2 r) -> Just $ Left a)+ :& H (\(e :: LineSegment 2 p r) -> Just $ Right e) :& RNil @@ -167,14 +209,17 @@ type instance IntersectionOf (Line 3 r) (Triangle 3 p r) = [ NoIntersection, Point 3 r, LineSegment 3 () r ] -instance (Fractional r, Ord r) => (Line 3 r) `IsIntersectableWith` (Triangle 3 p r) where+instance (Fractional r, Ord r) => Line 3 r `HasIntersectionWith` Triangle 3 p r++{- HLINT ignore "Use const" -}+instance (Fractional r, Ord r) => Line 3 r `IsIntersectableWith` Triangle 3 p r where nonEmptyIntersection = defaultNonEmptyIntersection l@(Line a v) `intersect` t@(Triangle (p :+ _) (q :+ _) (r :+ _)) = match (l `intersect` h) $- (H $ \NoIntersection -> coRec NoIntersection)- :& (H $ \i@(Point3 _ _ _) -> if onTriangle' i then coRec i else coRec NoIntersection)- :& (H $ \_ -> intersect2d)+ H (\NoIntersection -> coRec NoIntersection)+ :& H (\i@Point3{} -> if onTriangle' i then coRec i else coRec NoIntersection)+ :& H (\_ -> intersect2d) :& RNil where h@(Plane _ n) = supportingPlane t@@ -187,13 +232,13 @@ -- test if the point in terms of its 2d coords lies in side the projected triangle onTriangle' :: Point 3 r -> Bool- onTriangle' i = (project i) `onTriangle` t'+ onTriangle' i = project i `onTriangle` t' -- FIXME! these vectors may not be unit vectors. How do we deal with -- that? (and does that really matter here?) transf :: Transformation 3 r transf = let u = p .-. q- in rotateTo (Vector3 u (n `cross` u) n) |.| translation ((-1) *^ (toVec q))+ in rotateTo (Vector3 u (n `cross` u) n) |.| translation ((-1) *^ toVec q) -- inverse of the transformation above. invTrans :: Transformation 3 r invTrans = inverseOf transf@@ -210,8 +255,11 @@ intersect2d :: Intersection (Line 3 r) (Triangle 3 p r) intersect2d = match (l' `intersect` t') $- (H $ \NoIntersection -> coRec NoIntersection)- :& (H $ \i@(Point2 _ _) -> coRec $ lift i)- :& (H $ \(LineSegment s e) -> coRec $ LineSegment (s&unEndPoint.core %~ lift)- (e&unEndPoint.core %~ lift))+ H (\NoIntersection -> coRec NoIntersection)+ :& H (\i@(Point2 _ _) -> coRec $ lift i)+ :& H (\(LineSegment s e) -> coRec $ LineSegment (s&unEndPoint.core %~ lift)+ (e&unEndPoint.core %~ lift)) :& RNil++instance (Arity d, Ord r) => IsBoxable (Triangle d p r) where+ boundingBox (Triangle a b c) = boundingBox a <> boundingBox b <> boundingBox c
src/Data/Geometry/Vector.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -------------------------------------------------------------------------------- -- |@@ -14,45 +14,60 @@ , module LV , C(..) , Affine(..)- , qdA, distanceA+ , quadrance, qdA, distanceA , dot, norm, signorm , isScalarMultipleOf- , scalarMultiple+ , scalarMultiple, sameDirection -- reexports , FV.replicate- , FV.imap , xComponent, yComponent, zComponent ) where -import Control.Applicative (liftA2)-import Control.Lens(Lens')-import qualified Data.Foldable as F+import Control.Applicative (liftA2)+import Control.Lens (Lens')+import Control.Monad.State+import qualified Data.Foldable as F import Data.Geometry.Properties import Data.Geometry.Vector.VectorFamily-import Data.Geometry.Vector.VectorFixed (C(..))-import Data.Maybe-import qualified Data.Vector.Fixed as FV+import Data.Geometry.Vector.VectorFixed (C (..))+import qualified Data.Vector.Fixed as FV import GHC.TypeLits-import Linear.Affine (Affine(..), qdA, distanceA)-import Linear.Metric (dot,norm,signorm)-import Linear.Vector as LV-import Test.QuickCheck+import Linear.Affine (Affine (..), distanceA, qdA)+import Linear.Metric (dot, norm, quadrance, signorm)+import Linear.Vector as LV hiding (E (..))+import System.Random (Random (..))+import Test.QuickCheck (Arbitrary (..), Arbitrary1 (..), infiniteList,+ infiniteListOf) -------------------------------------------------------------------------------- +-- $setup+-- >>> import Control.Lens+ type instance Dimension (Vector d r) = d type instance NumType (Vector d r) = r instance (Arbitrary r, Arity d) => Arbitrary (Vector d r) where arbitrary = vectorFromListUnsafe <$> infiniteList +instance (Arity d) => Arbitrary1 (Vector d) where+ liftArbitrary gen = vectorFromListUnsafe <$> infiniteListOf gen +instance (Random r, Arity d) => Random (Vector d r) where+ randomR (lows,highs) g0 = flip runState g0 $+ FV.zipWithM (\l h -> state $ randomR (l,h)) lows highs+ random g0 = flip runState g0 $ FV.replicateM (state random)+ -- | 'isScalarmultipleof u v' test if v is a scalar multiple of u. -- -- >>> Vector2 1 1 `isScalarMultipleOf` Vector2 10 10 -- True+-- >>> Vector3 1 1 2 `isScalarMultipleOf` Vector3 10 10 20+-- True -- >>> Vector2 1 1 `isScalarMultipleOf` Vector2 10 1 -- False+-- >>> Vector2 1 1 `isScalarMultipleOf` Vector2 (-1) (-1)+-- True -- >>> Vector2 1 1 `isScalarMultipleOf` Vector2 11.1 11.1 -- True -- >>> Vector2 1 1 `isScalarMultipleOf` Vector2 11.1 11.2@@ -63,11 +78,20 @@ -- True -- >>> Vector2 2 1 `isScalarMultipleOf` Vector2 4 0 -- False+-- >>> Vector3 2 1 0 `isScalarMultipleOf` Vector3 4 0 5+-- False+-- >>> Vector3 0 0 0 `isScalarMultipleOf` Vector3 4 0 5+-- True isScalarMultipleOf :: (Eq r, Fractional r, Arity d) => Vector d r -> Vector d r -> Bool-u `isScalarMultipleOf` v = isJust $ scalarMultiple u v+u `isScalarMultipleOf` v = let d = u `dot` v+ num = quadrance u * quadrance v+ in num == 0 || num == d*d+-- u `isScalarMultipleOf` v = isJust $ scalarMultiple u v {-# SPECIALIZE isScalarMultipleOf :: (Eq r, Fractional r) => Vector 2 r -> Vector 2 r -> Bool #-}+{-# SPECIALIZE+ isScalarMultipleOf :: (Eq r, Fractional r) => Vector 3 r -> Vector 3 r -> Bool #-} -- | scalarMultiple u v computes the scalar labmda s.t. v = lambda * u (if it exists) scalarMultiple :: (Eq r, Fractional r, Arity d)@@ -129,17 +153,46 @@ scalarMultiple' :: (Eq r, Fractional r) => Vector 2 r -> Vector 2 r -> Maybe r #-} +-- | Given two colinar vectors, u and v, test if they point in the same direction, i.e.+-- iff scalarMultiple' u v == Just lambda, with lambda > 0+--+-- pre: u and v are colinear, u and v are non-zero+sameDirection :: (Eq r, Num r, Arity d) => Vector d r -> Vector d r -> Bool+sameDirection u v = and $ FV.zipWith (\ux vx -> signum ux == signum vx) u v++-- sameDirectionProp :: (Eq r, Fractional r, Arity d)+-- => Vector d r -> Vector d r -> Bool+-- sameDirectionProp u v = sameDirection u v == maybe False ((/= (-1)) . signum) (scalarMultiple' u v)+ -------------------------------------------------------------------------------- -- * Helper functions specific to two and three dimensional vectors +-- | Shorthand to access the first component+--+-- >>> Vector3 1 2 3 ^. xComponent+-- 1+-- >>> Vector2 1 2 & xComponent .~ 10+-- Vector2 10 2 xComponent :: (1 <= d, Arity d) => Lens' (Vector d r) r-xComponent = element (C :: C 0)+xComponent = element @0 {-# INLINABLE xComponent #-} +-- | Shorthand to access the second component+--+-- >>> Vector3 1 2 3 ^. yComponent+-- 2+-- >>> Vector2 1 2 & yComponent .~ 10+-- Vector2 1 10 yComponent :: (2 <= d, Arity d) => Lens' (Vector d r) r-yComponent = element (C :: C 1)+yComponent = element @1 {-# INLINABLE yComponent #-} +-- | Shorthand to access the third component+--+-- >>> Vector3 1 2 3 ^. zComponent+-- 3+-- >>> Vector3 1 2 3 & zComponent .~ 10+-- Vector3 1 2 10 zComponent :: (3 <= d, Arity d) => Lens' (Vector d r) r-zComponent = element (C :: C 2)+zComponent = element @2 {-# INLINABLE zComponent #-}
src/Data/Geometry/Vector/VectorFamily.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE AllowAmbiguousTypes #-} -------------------------------------------------------------------------------- -- | -- Module : Data.Geometry.Vector.VectorFamily@@ -16,29 +17,29 @@ import Control.DeepSeq import Control.Lens hiding (element)+import Control.Monad import Data.Aeson--- import Data.Aeson (ToJSON(..),FromJSON(..)) import qualified Data.Foldable as F-import qualified Data.List as L-import Data.Geometry.Vector.VectorFixed (C(..))+import Data.Functor.Classes+import Data.Geometry.Vector.VectorFamilyPeano (ImplicitArity, VectorFamily (..),+ VectorFamilyF) import qualified Data.Geometry.Vector.VectorFamilyPeano as Fam-import Data.Geometry.Vector.VectorFamilyPeano ( VectorFamily(..)- , VectorFamilyF- , ImplicitArity- )+import Data.Geometry.Vector.VectorFixed (C (..))+import Data.Hashable+import Data.Kind+import Data.List+import qualified Data.List as L+import Data.Proxy import qualified Data.Vector.Fixed as V import Data.Vector.Fixed.Cont (Peano) import GHC.TypeLits-import Linear.Affine (Affine(..))+import Linear.Affine (Affine (..)) import Linear.Metric import qualified Linear.V2 as L2 import qualified Linear.V3 as L3 import qualified Linear.V4 as L4 import Linear.Vector-import Text.ParserCombinators.ReadP (ReadP, string,pfail)-import Text.ParserCombinators.ReadPrec (lift)-import Text.Read (Read(..),readListPrecDefault, readPrec_to_P,minPrec)-import Data.Proxy+import Text.Read (Read (..), readListPrecDefault) -------------------------------------------------------------------------------- -- * d dimensional Vectors@@ -47,7 +48,7 @@ -- | Datatype representing d dimensional vectors. The default implementation is -- based n VectorFixed. However, for small vectors we automatically select a -- more efficient representation.-newtype Vector (d :: Nat) (r :: *) = MKVector { _unV :: VectorFamily (Peano d) r }+newtype Vector (d :: Nat) (r :: Type) = MKVector { _unV :: VectorFamily (Peano d) r } type instance V.Dim (Vector d) = Fam.FromPeano (Peano d) -- the above definition is a bit convoluted, but it allows us to make Vector an instance of@@ -55,13 +56,18 @@ type instance Index (Vector d r) = Int type instance IxValue (Vector d r) = r -unV :: Lens (Vector d r) (Vector d s) (VectorFamily (Peano d) r) (VectorFamily (Peano d) s)-unV = lens _unV (const MKVector)+-- | Vectors are isomorphic to a definition determined by 'VectorFamily'.+unV :: Iso (Vector d r) (Vector d s) (VectorFamily (Peano d) r) (VectorFamily (Peano d) s)+unV = iso _unV MKVector {-# INLINE unV #-} -type Arity d = (ImplicitArity (Peano d), KnownNat d)+-- type Arity d = (ImplicitArity (Peano d), KnownNat d)+class (ImplicitArity (Peano d), KnownNat d) => Arity d+instance (ImplicitArity (Peano d), KnownNat d) => Arity d + deriving instance (Eq r, Arity d) => Eq (Vector d r)+deriving instance Arity d => Eq1 (Vector d) deriving instance (Ord r, Arity d) => Ord (Vector d r) deriving instance Arity d => Functor (Vector d)@@ -69,6 +75,15 @@ deriving instance Arity d => Traversable (Vector d) deriving instance Arity d => Applicative (Vector d) +++instance Arity d => FunctorWithIndex Int (Vector d) where+ imap = V.imap+instance Arity d => FoldableWithIndex Int (Vector d)+instance Arity d => TraversableWithIndex Int (Vector d) where+ itraverse = V.imapM++ deriving instance Arity d => Additive (Vector d) deriving instance Arity d => Metric (Vector d) instance Arity d => Affine (Vector d) where@@ -76,6 +91,8 @@ u .-. v = u ^-^ v p .+^ v = p ^+^ v +deriving instance (Arity d, Hashable r) => Hashable (Vector d r)+ instance Arity d => Ixed (Vector d r) where ix = element' @@ -84,22 +101,50 @@ inspect = V.inspect . _unV basicIndex = V.basicIndex . _unV -instance (Arity d, Show r) => Show (Vector d r) where- show v = mconcat [ "Vector", show $ F.length v , " "- , show $ F.toList v ]+-- instance (Arity d, Show r) => Show (Vector d r) where+-- show v = mconcat [ "Vector", show $ F.length v , " "+-- , show $ F.toList v ] +-- instance (Read r, Arity d) => Read (Vector d r) where+-- readPrec = lift readVec+-- where+-- readVec :: (Arity d, Read r) => ReadP (Vector d r)+-- readVec = do let d = natVal (Proxy :: Proxy d)+-- _ <- string $ "Vector" <> show d <> " "+-- rs <- readPrec_to_P readPrec minPrec+-- case vectorFromList rs of+-- Just v -> pure v+-- _ -> pfail+-- readListPrec = readListPrecDefault++instance (Show r, Arity d) => Show (Vector d r) where+ showsPrec = liftShowsPrec showsPrec showList++instance (Arity d) => Show1 (Vector d) where+ liftShowsPrec sp _ d v = showParen (d > 10) $+ showString constr . showChar ' ' .+ unwordsS (map (sp 11) (F.toList v))+ where+ constr = "Vector" <> show (fromIntegral (natVal @d Proxy))+ unwordsS = foldr (.) id . intersperse (showChar ' ')+ instance (Read r, Arity d) => Read (Vector d r) where- readPrec = lift readVec+ readPrec = liftReadPrec readPrec readListPrec readListPrec = readListPrecDefault -readVec :: forall d r. (Arity d, Read r) => ReadP (Vector d r)-readVec = do let d = natVal (Proxy :: Proxy d)- _ <- string $ "Vector" <> show d <> " "- rs <- readPrec_to_P readPrec minPrec- case vectorFromList rs of- Just v -> pure v- _ -> pfail+instance (Arity d) => Read1 (Vector d) where+ liftReadPrec rp _rl = readData $+ readUnaryWith (replicateM d rp) constr $ \rs ->+ case vectorFromList rs of+ Just p -> p+ _ -> error "internal error in Data.Geometry.Vector read instance."+ where+ d = fromIntegral (natVal (Proxy :: Proxy d))+ constr = "Vector" <> show d+ liftReadListPrec = liftReadListPrecDefault ++ deriving instance (FromJSON r, Arity d) => FromJSON (Vector d r) instance (ToJSON r, Arity d) => ToJSON (Vector d r) where toJSON = toJSON . _unV@@ -110,64 +155,82 @@ -------------------------------------------------------------------------------- -- * Convenience "constructors" +-- | Constant sized vector with d elements. pattern Vector :: VectorFamilyF (Peano d) r -> Vector d r pattern Vector v = MKVector (VectorFamily v) {-# COMPLETE Vector #-} +-- | Constant sized vector with 1 element. pattern Vector1 :: r -> Vector 1 r pattern Vector1 x = (Vector (Identity x)) {-# COMPLETE Vector1 #-} +-- | Constant sized vector with 2 elements. pattern Vector2 :: r -> r -> Vector 2 r pattern Vector2 x y = (Vector (L2.V2 x y)) {-# COMPLETE Vector2 #-} +-- | Constant sized vector with 3 elements. pattern Vector3 :: r -> r -> r -> Vector 3 r pattern Vector3 x y z = (Vector (L3.V3 x y z)) {-# COMPLETE Vector3 #-} +-- | Constant sized vector with 4 elements. pattern Vector4 :: r -> r -> r -> r -> Vector 4 r pattern Vector4 x y z w = (Vector (L4.V4 x y z w)) {-# COMPLETE Vector4 #-} -------------------------------------------------------------------------------- +-- | \( O(n) \) Convert from a list to a non-empty vector. vectorFromList :: Arity d => [r] -> Maybe (Vector d r) vectorFromList = V.fromListM +-- | \( O(n) \) Convert from a list to a non-empty vector. vectorFromListUnsafe :: Arity d => [r] -> Vector d r vectorFromListUnsafe = V.fromList +-- | \( O(n) \) Pop the first element off a vector. destruct :: (Arity d, Arity (d + 1)) => Vector (d + 1) r -> (r, Vector d r) destruct v = (L.head $ F.toList v, vectorFromListUnsafe . tail $ F.toList v) -- FIXME: this implementaion of tail is not particularly nice +-- | \( O(1) \) First element. Since arity is at least 1, this function is total. head :: (Arity d, 1 <= d) => Vector d r -> r-head = view $ element (C :: C 0)+head = view $ element @0 -------------------------------------------------------------------------------- -- * Indexing vectors -- | Lens into the i th element-element :: forall proxy i d r. (Arity d, KnownNat i, (i + 1) <= d)- => proxy i -> Lens' (Vector d r) r-element _ = singular . element' . fromInteger $ natVal (C :: C i)+element :: forall i d r. (Arity d, KnownNat i, (i + 1) <= d)+ => Lens' (Vector d r) r+element = elementProxy (C @i) {-# INLINE element #-} +-- | Lens into the i th element+elementProxy :: forall proxy i d r. (Arity d, KnownNat i, (i + 1) <= d)+ => proxy i -> Lens' (Vector d r) r+elementProxy _ = singular $ element' $ fromInteger . natVal $ C @i+{-# INLINE elementProxy #-} -- | Similar to 'element' above. Except that we don't have a static guarantee -- that the index is in bounds. Hence, we can only return a Traversal element' :: forall d r. Arity d => Int -> Traversal' (Vector d r) r-element' i = unV.(e (C :: C d) i)+element' i = unV.e (C :: C d) i where e :: Arity d => proxy d -> Int -> Traversal' (VectorFamily (Peano d) r) r- e _ = Fam.element'+ e _ = ix {-# INLINE element' #-} -------------------------------------------------------------------------------- -- * Snoccing and consindg +-- | \( O(n) \) Prepend an element.+cons :: (Arity d, Arity (d+1)) => r -> Vector d r -> Vector (d + 1) r+cons x = vectorFromListUnsafe . (x:) . F.toList+ -- | Add an element at the back of the vector snoc :: (Arity (d + 1), Arity d) => Vector d r -> r -> Vector (d + 1) r snoc v x = vectorFromListUnsafe . (++ [x]) $ F.toList v@@ -177,8 +240,9 @@ init :: (Arity d, Arity (d + 1)) => Vector (d + 1) r -> Vector d r init = vectorFromListUnsafe . L.init . F.toList +-- | \( O(1) \) Last element. Since the vector is non-empty, runtime bounds checks are bypassed. last :: forall d r. (KnownNat d, Arity (d + 1)) => Vector (d + 1) r -> r-last = view $ element (C :: C d)+last = view $ element @d -- | Get a prefix of i elements of a vector prefix :: forall i d r. (Arity d, Arity i, i <= d)
src/Data/Geometry/Vector/VectorFamilyPeano.hs view
@@ -1,15 +1,30 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE UndecidableInstances #-}-module Data.Geometry.Vector.VectorFamilyPeano where+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.Vector.VectorFamilyPeano+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Data.Geometry.Vector.VectorFamilyPeano+ ( ImplicitArity+ , VectorFamily(VectorFamily)+ , VectorFamilyF+ , FromPeano+ , Two+ ) where import Control.Applicative (liftA2) import Control.DeepSeq import Control.Lens hiding (element)-import Data.Aeson(FromJSON(..),ToJSON(..))+import Data.Aeson (FromJSON(..),ToJSON(..))+import Data.Kind -- import Data.Aeson (ToJSON(..),FromJSON(..)) import qualified Data.Foldable as F import qualified Data.Geometry.Vector.VectorFixed as FV import Data.Proxy+import Data.Functor.Classes import qualified Data.Vector.Fixed as V import Data.Vector.Fixed.Cont (PeanoNum(..), Fun(..)) import GHC.TypeLits@@ -19,6 +34,7 @@ import qualified Linear.V3 as L3 import qualified Linear.V4 as L4 import Linear.Vector+import Data.Hashable -------------------------------------------------------------------------------- -- * Natural number stuff@@ -52,11 +68,11 @@ -- | Datatype representing d dimensional vectors. The default implementation is -- based n VectorFixed. However, for small vectors we automatically select a -- more efficient representation.-newtype VectorFamily (d :: PeanoNum) (r :: *) =+newtype VectorFamily (d :: PeanoNum) (r :: Type) = VectorFamily { _unVF :: VectorFamilyF d r } -- | Mapping between the implementation type, and the actual implementation.-type family VectorFamilyF (d :: PeanoNum) :: * -> * where+type family VectorFamilyF (d :: PeanoNum) :: Type -> Type where VectorFamilyF Z = Const () VectorFamilyF One = Identity VectorFamilyF Two = L2.V2@@ -77,7 +93,9 @@ unVF = lens _unVF (const VectorFamily) {-# INLINE unVF #-} -type ImplicitArity d = (ImplicitPeano d, V.Arity (FromPeano d))+-- type ImplicitArity d = (ImplicitPeano d, V.Arity (FromPeano d))+class (ImplicitPeano d, V.Arity (FromPeano d)) => ImplicitArity d+instance (ImplicitPeano d, V.Arity (FromPeano d)) => ImplicitArity d instance (Eq r, ImplicitArity d) => Eq (VectorFamily d r) where (VectorFamily u) == (VectorFamily v) = case (implicitPeano :: SingPeano d) of@@ -89,6 +107,15 @@ (SS (SS (SS (SS (SS _))))) -> u == v {-# INLINE (==) #-} +instance (ImplicitArity d) => Eq1 (VectorFamily d) where+ liftEq eq (VectorFamily u) (VectorFamily v) = case (implicitPeano :: SingPeano d) of+ SZ -> liftEq eq u v+ (SS SZ) -> liftEq eq u v+ (SS (SS SZ)) -> liftEq eq u v+ (SS (SS (SS SZ))) -> liftEq eq u v+ (SS (SS (SS (SS SZ)))) -> liftEq eq u v+ (SS (SS (SS (SS (SS _))))) -> liftEq eq u v+ instance (Ord r, ImplicitArity d) => Ord (VectorFamily d r) where (VectorFamily u) `compare` (VectorFamily v) = case (implicitPeano :: SingPeano d) of SZ -> u `compare` v@@ -188,6 +215,17 @@ (SS (SS (SS (SS (SS _))))) -> rnf v {-# INLINE rnf #-} ++instance (ImplicitArity d, Hashable r) => Hashable (VectorFamily d r) where+ hashWithSalt = case (implicitPeano :: SingPeano d) of+ SZ -> hashWithSalt+ (SS SZ) -> hashWithSalt+ (SS (SS SZ)) -> hashWithSalt+ (SS (SS (SS SZ))) -> hashWithSalt+ (SS (SS (SS (SS SZ)))) -> hashWithSalt+ (SS (SS (SS (SS (SS _))))) -> hashWithSalt++ instance ImplicitArity d => Ixed (VectorFamily d r) where ix = element' @@ -202,13 +240,13 @@ {-# INLINE element' #-} elem0 :: Int -> Traversal' (VectorFamily Z r) r-elem0 _ = \_ v -> pure v+elem0 _ _ = pure {-# INLINE elem0 #-} -- zero length vectors don't store any elements elem1 :: Int -> Traversal' (VectorFamily One r) r elem1 = \case- 0 -> unVF.(lens runIdentity (\_ -> Identity))+ 0 -> unVF.lens runIdentity (const Identity) _ -> \_ v -> pure v {-# INLINE elem1 #-} @@ -273,14 +311,14 @@ vectorFromList :: ImplicitArity d => [r] -> Maybe (VectorFamily d r) vectorFromList = V.fromListM -vectorFromListUnsafe :: ImplicitArity d => [r] -> VectorFamily d r-vectorFromListUnsafe = V.fromList+-- vectorFromListUnsafe :: ImplicitArity d => [r] -> VectorFamily d r+-- vectorFromListUnsafe = V.fromList --- | Get the head and tail of a vector-destruct :: (ImplicitArity d, ImplicitArity (S d))- => VectorFamily (S d) r -> (r, VectorFamily d r)-destruct v = (head $ F.toList v, vectorFromListUnsafe . tail $ F.toList v)- -- FIXME: this implementaion of tail is not particularly nice+-- -- | Get the head and tail of a vector+-- destruct :: (ImplicitArity d, ImplicitArity (S d))+-- => VectorFamily (S d) r -> (r, VectorFamily d r)+-- destruct v = (head $ F.toList v, vectorFromListUnsafe . tail $ F.toList v)+-- -- FIXME: this implementaion of tail is not particularly nice -- snoc :: (ImplicitArity d, ImplicitArity (S d)) -- => VectorFamily d r -> r -> VectorFamily (S d) r
src/Data/Geometry/Vector/VectorFixed.hs view
@@ -1,14 +1,23 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE UndecidableInstances #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.Vector.VectorFixed+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+-------------------------------------------------------------------------------- module Data.Geometry.Vector.VectorFixed where import Control.DeepSeq import Control.Lens hiding (element) import Data.Aeson import qualified Data.Foldable as F+import Data.Functor.Classes+import Data.Kind import Data.Proxy-import qualified Data.Vector.Fixed as V import Data.Vector.Fixed (Arity)+import qualified Data.Vector.Fixed as V import Data.Vector.Fixed.Boxed import GHC.Generics (Generic) import GHC.TypeLits@@ -28,8 +37,8 @@ -- | Datatype representing d dimensional vectors. Our implementation wraps the -- implementation provided by fixed-vector.-newtype Vector (d :: Nat) (r :: *) = Vector { _unV :: Vec d r }- deriving (Generic)+newtype Vector (d :: Nat) (r :: Type) = Vector { _unV :: Vec d r }+ deriving (Generic) unV :: Lens' (Vector d r) (Vec d r) unV = lens _unV (const Vector)@@ -46,7 +55,7 @@ element' :: forall d r. Arity d => Int -> Traversal' (Vector d r) r element' i f v | 0 <= i && i < fromInteger (natVal (C :: C d)) = f (v V.! i)- <&> \a -> (v&V.element i .~ a)+ <&> \a -> v&V.element i .~ a -- Implementation based on that of Ixed Vector in Control.Lens.At | otherwise = pure v @@ -64,6 +73,11 @@ ] deriving instance (Eq r, Arity d) => Eq (Vector d r)++-- FIXME: Upstream Eq1 instance to 'fixed-vector' package.+instance Arity d => Eq1 (Vector d) where+ liftEq eq (Vector lhs) (Vector rhs) = V.and $ V.zipWith eq lhs rhs+ deriving instance (Ord r, Arity d) => Ord (Vector d r) -- deriving instance Arity d => Functor (Vector d) @@ -125,7 +139,7 @@ -- | Cross product of two three-dimensional vectors cross :: Num r => Vector 3 r -> Vector 3 r -> Vector 3 r-u `cross` v = fromV3 $ (toV3 u) `L3.cross` (toV3 v)+u `cross` v = fromV3 $ toV3 u `L3.cross` toV3 v --------------------------------------------------------------------------------
+ src/Data/Geometry/VerticalRayShooting.hs view
@@ -0,0 +1,12 @@+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.VerticalRayShooting+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Data.Geometry.VerticalRayShooting+ ( module Data.Geometry.VerticalRayShooting.PersistentSweep+ ) where++import Data.Geometry.VerticalRayShooting.PersistentSweep
+ src/Data/Geometry/VerticalRayShooting/PersistentSweep.hs view
@@ -0,0 +1,213 @@+{-# Language TemplateHaskell #-}+--------------------------------------------------------------------------------+-- |+-- Module : Data.Geometry.VerticalRayShooting.PersistentSweep+-- Copyright : (C) Frank Staals+-- License : see the LICENSE file+-- Maintainer : Frank Staals+--------------------------------------------------------------------------------+module Data.Geometry.VerticalRayShooting.PersistentSweep+ ( VerticalRayShootingStructure(VerticalRayShootingStructure), StatusStructure+ , leftMost, sweepStruct++ -- * Building the Data Structure+ , verticalRayShootingStructure+ -- * Querying the Data Structure+ , segmentAbove, segmentAboveOrOn+ , findSlab+ , lookupAbove, lookupAboveOrOn, searchInSlab+ ) where++import Algorithms.BinarySearch (binarySearchIn)+import Control.Lens hiding (contains, below)+import Data.Ext+import Data.Foldable (toList)+import Data.Function (on)+import Data.Geometry.Line+import Data.Geometry.LineSegment+import Data.Geometry.Point+import qualified Data.List as List+import Data.List.NonEmpty (NonEmpty(..))+import qualified Data.List.NonEmpty as NonEmpty+import Data.Maybe (mapMaybe)+import Data.Semigroup.Foldable+import qualified Data.Set as SS -- status struct+import qualified Data.Set.Util as SS+import qualified Data.Vector as V+++-- import Data.RealNumber.Rational++-- type R = RealNumber 5+--------------------------------------------------------------------------------++-- | The vertical ray shooting data structure+data VerticalRayShootingStructure p e r =+ VerticalRayShootingStructure { _leftMost :: r+ , _sweepStruct :: V.Vector (r :+ StatusStructure p e r)+ -- ^ entry (r :+ s) means that "just" left of "r" the+ -- status structure is 's', i.e up to 'r'+ } deriving (Show,Eq)++type StatusStructure p e r = SS.Set (LineSegment 2 p r :+ e)++makeLensesWith (lensRules&generateUpdateableOptics .~ False) ''VerticalRayShootingStructure++--------------------------------------------------------------------------------+-- * Building the DS++-- | Given a set of \(n\) interiorly pairwise disjoint *closed* segments,+-- compute a vertical ray shooting data structure. (i.e. the+-- endpoints of the segments may coincide).+--+-- pre: no vertical segments+--+-- running time: \(O(n\log n)\).+-- space: \(O(n\log n)\).+verticalRayShootingStructure :: (Ord r, Fractional r, Foldable1 t)+ => t (LineSegment 2 p r :+ e)+ -> VerticalRayShootingStructure p e r+verticalRayShootingStructure ss = VerticalRayShootingStructure (eventX e) (sweep' events)+ where+ events@(e :| _) = fmap combine+ . NonEmpty.groupAllWith1 eventX+ . foldMap1 toEvents+ . NonEmpty.fromList -- precondition guarantees that this is safe+ . mapMaybe reOrient . toList+ $ ss+ sweep' = V.fromList . toList . sweep++ reOrient s'@(s :+ z) = case (s^.start.core.xCoord) `compare` (s^.end.core.xCoord) of+ LT -> Just s'+ GT -> let s'' = s&start .~ (s^.end) -- flip the segment+ &end .~ (s^.start)+ in Just $ s'' :+ z+ EQ -> Nothing -- precondition says this won't happen, but kill+ -- them anyway++-- | Given a bunch of events happening at the same time, merge them into a single event+-- where we apply all actions.+combine :: NonEmpty (Event p e r) -> Event p e r+combine es@((x :+ _) :| _) = x :+ foldMap1 eventActions es++-- | Given a line segment construct the two events; i.e. when we+-- insert it and when we delete it.+toEvents :: Ord r => LineSegment 2 p r :+ e -> NonEmpty (Event p e r)+toEvents s@(LineSegment' p q :+ _) = NonEmpty.fromList [ (p^.core.xCoord) :+ Insert s :| []+ , (q^.core.xCoord) :+ Delete s :| []+ ]++----------------------------------------++data Action a = Insert a | Delete a deriving (Show,Eq)++{- HLINT ignore "Avoid lambda using `infix`" -}+interpret :: Action a -> (a -> a -> Ordering) -> SS.Set a -> SS.Set a+interpret = \case+ Insert s -> \cmp -> SS.insertBy cmp s+ Delete s -> \cmp -> SS.deleteAllBy cmp s+++type Event p e r = r :+ NonEmpty (Action (LineSegment 2 p r :+ e))++eventX :: Event p e r -> r+eventX = view core++eventActions :: Event p e r -> NonEmpty (Action (LineSegment 2 p r :+ e))+eventActions = view extra++----------------------------------------++-- | Runs the sweep building the data structure from left to right.+sweep :: (Ord r, Fractional r)+ => NonEmpty (Event p e r) -> NonEmpty (r :+ StatusStructure p e r)+sweep es = NonEmpty.fromList+ . snd . List.mapAccumL h SS.empty+ $ zip (toList es) (NonEmpty.tail es)+ where+ h ss evts = let x :+ ss' = handle ss evts in (ss',x :+ ss')++-- | Given the current status structure (for left of the next event+-- 'l'), and the next two events (l,r); essentially defining the slab+-- between l and r, we construct the status structure for in the slab (l,r).+-- returns the right boundary and this status structure.+handle :: (Ord r, Fractional r)+ => StatusStructure p e r+ -> (Event p e r, Event p e r)+ -> r :+ StatusStructure p e r+handle ss ( l :+ acts+ , r :+ _) = let mid = (l+r)/2+ runActionAt x act = interpret act (ordAtX' x)+ in r :+ foldr (runActionAt mid) ss (orderActs acts)+ -- run deletions first++-- | order by x coord+ordAtX' :: (Ord r, Fractional r)+ => r -> LineSegment 2 p r :+ a -> LineSegment 2 p r :+ a -> Ordering+ordAtX' x = ordAtX x `on` view core++-- | orders the actions to put insertions first and then all deletions+orderActs :: NonEmpty (Action a) -> NonEmpty (Action a)+orderActs acts = let (dels,ins) = NonEmpty.partition (\case+ Delete _ -> True+ Insert _ -> False+ ) acts+ in NonEmpty.fromList $ ins <> dels+++--------------------------------------------------------------------------------+-- * Querying the DS++-- | Find the segment vertically strictly above query point q, if it+-- exists.+--+-- \(O(\log n)\)+segmentAbove :: (Ord r, Num r) => Point 2 r -> VerticalRayShootingStructure p e r+ -> Maybe (LineSegment 2 p r :+ e)+segmentAbove q ds = findSlab q ds >>= lookupAbove q++-- | Find the segment vertically query point q, if it exists.+--+-- \(O(\log n)\)+segmentAboveOrOn :: (Ord r, Num r)+ => Point 2 r -> VerticalRayShootingStructure p e r+ -> Maybe (LineSegment 2 p r :+ e)+segmentAboveOrOn q ds = findSlab q ds >>= lookupAboveOrOn q++++-- | Given a query point, find the (data structure of the) slab containing the query point+--+-- \(O(\log n)\)+findSlab :: Ord r+ => Point 2 r -> VerticalRayShootingStructure p e r -> Maybe (StatusStructure p e r)+findSlab q ds | q^.xCoord < ds^.leftMost = Nothing+ | otherwise = view extra+ <$> binarySearchIn (q `leftOf `) (ds^.sweepStruct)+ where+ q' `leftOf` (r :+ _) = q'^.xCoord <= r++--------------------------------------------------------------------------------+-- * Querying in a single slab++-- | Finds the segment containing or above the query point 'q'+--+-- \(O(\log n)\)+lookupAboveOrOn :: (Ord r, Num r)+ => Point 2 r -> StatusStructure p e r -> Maybe (LineSegment 2 p r :+ e)+lookupAboveOrOn q = searchInSlab (not . (q `liesAbove`))++-- | Finds the first segment strictly above q+--+-- \(O(\log n)\)+lookupAbove :: (Ord r, Num r)+ => Point 2 r -> StatusStructure p e r -> Maybe (LineSegment 2 p r :+ e)+lookupAbove q = searchInSlab (q `liesBelow`)++-- | generic searching function+searchInSlab :: Num r => (Line 2 r -> Bool)+ -> StatusStructure p e r -> Maybe (LineSegment 2 p r :+ e)+searchInSlab p = binarySearchIn (p . supportingLine . view core)+++----------------------------------------------------------------------------------
src/Data/PlaneGraph.hs view
@@ -1,6 +1,4 @@-{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE OverloadedStrings #-} -------------------------------------------------------------------------------- -- | -- Module : Data.PlaneGraph@@ -13,7 +11,8 @@ -- embedding. -- ---------------------------------------------------------------------------------module Data.PlaneGraph( PlaneGraph(PlaneGraph), graph+module Data.PlaneGraph( -- $setup+ PlaneGraph(PlaneGraph), graph , PlanarGraph , VertexData(VertexData), vData, location, vtxDataToExt , fromSimplePolygon, fromConnectedSegments@@ -33,7 +32,7 @@ , incidentEdges, incomingEdges, outgoingEdges , neighboursOf , nextIncidentEdge, prevIncidentEdge-+ , nextIncidentEdgeFrom, prevIncidentEdgeFrom , leftFace, rightFace , nextEdge, prevEdge@@ -46,15 +45,93 @@ , vertexData, faceData, dartData, rawDartData , edgeSegment, edgeSegments- , rawFacePolygon, rawFaceBoundary- , rawFacePolygons+ , faceBoundary, internalFacePolygon+ , outerFacePolygon, outerFacePolygon'+ , facePolygons, facePolygons' , VertexId(..), FaceId(..), Dart, World(..), VertexId', FaceId' - , withEdgeDistances , writePlaneGraph, readPlaneGraph ) where import Data.PlaneGraph.IO import Data.PlaneGraph.Core+++--------------------------------------------------------------------------------++-- $setup+-- >>> import Data.Proxy+-- >>> import Data.PlaneGraph.AdjRep(Gr(Gr),Face(Face),Vtx(Vtx))+-- >>> import Data.PlaneGraph.IO(fromAdjRep)+-- >>> import Data.PlanarGraph.Dart(Dart(..),Arc(..))+-- >>> :{+-- let dart i s = Dart (Arc i) (read s)+-- small :: Gr (Vtx Int String Int) (Face String)+-- small = Gr [ Vtx 0 (Point2 0 0) [ (2,"0->2")+-- , (1,"0->1")+-- , (3,"0->3")+-- ] 0+-- , Vtx 1 (Point2 2 2) [ (0,"1->0")+-- , (2,"1->2")+-- , (3,"1->3")+-- ] 1+-- , Vtx 2 (Point2 2 0) [ (0,"2->0")+-- , (1,"2->1")+-- ] 2+-- , Vtx 3 (Point2 (-1) 4) [ (0,"3->0")+-- , (1,"3->1")+-- ] 3+-- ]+-- [ Face (2,1) "OuterFace"+-- , Face (0,1) "A"+-- , Face (1,0) "B"+-- ]+-- smallG = fromAdjRep (Proxy :: Proxy ()) small+-- :}+--+--+-- This represents the following graph. Note that the graph is undirected, the+-- arrows are just to indicate what the Positive direction of the darts is.+--+-- +--+--+-- Here is also a slightly larger example graph:+-- +--+-- >>> import Data.RealNumber.Rational+-- >>> data MyWorld+-- >>> :{+-- let myPlaneGraph :: PlaneGraph MyWorld Int () String (RealNumber 5)+-- myPlaneGraph = fromAdjRep (Proxy @MyWorld) myPlaneGraphAdjrep+-- myPlaneGraphAdjrep :: Gr (Vtx Int () (RealNumber 5)) (Face String)+-- myPlaneGraphAdjrep = Gr [ vtx 0 (Point2 0 0 ) [e 9, e 5, e 1, e 2]+-- , vtx 1 (Point2 4 4 ) [e 0, e 5, e 12]+-- , vtx 2 (Point2 3 7 ) [e 0, e 3]+-- , vtx 3 (Point2 0 5 ) [e 4, e 2]+-- , vtx 4 (Point2 3 8 ) [e 3, e 13]+-- , vtx 5 (Point2 8 1 ) [e 0, e 6, e 8, e 1]+-- , vtx 6 (Point2 6 (-1)) [e 5, e 9]+-- , vtx 7 (Point2 9 (-1)) [e 8, e 11]+-- , vtx 8 (Point2 12 1 ) [e 7, e 12, e 5]+-- , vtx 9 (Point2 8 (-5)) [e 0, e 10, e 6]+-- , vtx 10 (Point2 12 (-3)) [e 9, e 11]+-- , vtx 11 (Point2 14 (-1)) [e 10, e 7]+-- , vtx 12 (Point2 10 4 ) [e 1, e 8, e 13, e 14]+-- , vtx 13 (Point2 9 6 ) [e 4, e 14, e 12]+-- , vtx 14 (Point2 8 5 ) [e 13, e 12]+-- ]+-- [ Face (0,9) "OuterFace"+-- , Face (0,5) "A"+-- , Face (0,1) "B"+-- , Face (0,2) "C"+-- , Face (14,13) "D"+-- , Face (1,12) "E"+-- , Face (5,8) "F"+-- ]+-- where+-- e i = (i,())+-- vtx i p es = Vtx i p es i+-- :}
src/Data/PlaneGraph/AdjRep.hs view
@@ -27,7 +27,7 @@ -- edge. Adjacencies are given in -- arbitrary order , vData :: v- } deriving (Generic, Functor)+ } deriving (Generic, Show, Eq, Functor) instance (ToJSON r, ToJSON v, ToJSON e) => ToJSON (Vtx v e r) where toEncoding = genericToEncoding defaultOptions
src/Data/PlaneGraph/Core.hs view
@@ -1,6 +1,6 @@-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-} -------------------------------------------------------------------------------- -- | -- Module : Data.PlaneGraph.Core@@ -13,7 +13,8 @@ -- embedding. -- ---------------------------------------------------------------------------------module Data.PlaneGraph.Core( PlaneGraph(PlaneGraph), graph+module Data.PlaneGraph.Core( -- $setup+ PlaneGraph(PlaneGraph), graph , PlanarGraph , VertexData(VertexData), vData, location, vtxDataToExt , fromSimplePolygon, fromConnectedSegments@@ -24,7 +25,7 @@ , vertices', vertices , edges', edges- , faces', faces, internalFaces, faces''+ , faces', internalFaces', faces, internalFaces, faces'' , darts', darts , traverseVertices, traverseDarts, traverseFaces @@ -33,6 +34,7 @@ , incidentEdges, incomingEdges, outgoingEdges , neighboursOf , nextIncidentEdge, prevIncidentEdge+ , nextIncidentEdgeFrom, prevIncidentEdgeFrom , leftFace, rightFace@@ -46,12 +48,12 @@ , vertexData, faceData, dartData, rawDartData , edgeSegment, edgeSegments- , rawFacePolygon, rawFaceBoundary- , rawFacePolygons+ , faceBoundary, internalFacePolygon+ , outerFacePolygon, outerFacePolygon'+ , facePolygons, facePolygons', internalFacePolygons , VertexId(..), FaceId(..), Dart, World(..), VertexId', FaceId' - , withEdgeDistances -- , writePlaneGraph, readPlaneGraph ) where@@ -59,7 +61,7 @@ import Control.Lens hiding (holes, holesOf, (.=)) import Data.Aeson-import qualified Data.CircularSeq as C+import Data.Bifunctor (first) import Data.Ext import qualified Data.Foldable as F import Data.Function (on)@@ -68,28 +70,25 @@ import Data.Geometry.Line (cmpSlope, supportingLine) import Data.Geometry.LineSegment hiding (endPoints) import Data.Geometry.Point+import Data.Geometry.Vector import Data.Geometry.Polygon import Data.Geometry.Properties import qualified Data.List.NonEmpty as NonEmpty import qualified Data.Map as M import Data.Ord (comparing)+import Data.PlanarGraph (Arc (..), Dart (..), Direction (..), FaceId (..),+ FaceId', HasDataOf (..), PlanarGraph, VertexId (..),+ VertexId', World (..), dual, planarGraph, twin) import qualified Data.PlanarGraph as PG-import Data.PlanarGraph( PlanarGraph, planarGraph, dual- , Dart(..), VertexId(..), FaceId(..), Arc(..)- , Direction(..), twin- , World(..)- , FaceId', VertexId'- , HasDataOf(..)- ) import Data.Util import qualified Data.Vector as V+import Data.Vector.Circular (CircularVector) import GHC.Generics (Generic) --------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- -- $setup--- >>> import Data.Proxy -- >>> import Data.PlaneGraph.AdjRep(Gr(Gr),Face(Face),Vtx(Vtx)) -- >>> import Data.PlaneGraph.IO(fromAdjRep) -- >>> import Data.PlanarGraph.Dart(Dart(..),Arc(..))@@ -115,7 +114,7 @@ -- , Face (0,1) "A" -- , Face (1,0) "B" -- ]--- smallG = fromAdjRep (Proxy :: Proxy ()) small+-- smallG = fromAdjRep @() small -- :} -- --@@ -123,7 +122,45 @@ -- arrows are just to indicate what the Positive direction of the darts is. -- -- -+--+--+-- Here is also a slightly larger example graph:+-- +--+-- >>> import Data.RealNumber.Rational+-- >>> data MyWorld+-- >>> :{+-- let myPlaneGraph :: PlaneGraph MyWorld Int () String (RealNumber 5)+-- myPlaneGraph = fromAdjRep @MyWorld myPlaneGraphAdjrep+-- myPlaneGraphAdjrep :: Gr (Vtx Int () (RealNumber 5)) (Face String)+-- myPlaneGraphAdjrep = Gr [ vtx 0 (Point2 0 0 ) [e 9, e 5, e 1, e 2]+-- , vtx 1 (Point2 4 4 ) [e 0, e 5, e 12]+-- , vtx 2 (Point2 3 7 ) [e 0, e 3]+-- , vtx 3 (Point2 0 5 ) [e 4, e 2]+-- , vtx 4 (Point2 3 8 ) [e 3, e 13]+-- , vtx 5 (Point2 8 1 ) [e 0, e 6, e 8, e 1]+-- , vtx 6 (Point2 6 (-1)) [e 5, e 9]+-- , vtx 7 (Point2 9 (-1)) [e 8, e 11]+-- , vtx 8 (Point2 12 1 ) [e 7, e 12, e 5]+-- , vtx 9 (Point2 8 (-5)) [e 0, e 10, e 6]+-- , vtx 10 (Point2 12 (-3)) [e 9, e 11]+-- , vtx 11 (Point2 14 (-1)) [e 10, e 7]+-- , vtx 12 (Point2 10 4 ) [e 1, e 8, e 13, e 14]+-- , vtx 13 (Point2 9 6 ) [e 4, e 14, e 12]+-- , vtx 14 (Point2 8 5 ) [e 13, e 12]+-- ]+-- [ Face (0,9) "OuterFace"+-- , Face (0,5) "A"+-- , Face (0,1) "B"+-- , Face (0,2) "C"+-- , Face (14,13) "D"+-- , Face (1,12) "E"+-- , Face (5,8) "F"+-- ]+-- where+-- e i = (i,())+-- vtx i p es = Vtx i p es i+-- :} -------------------------------------------------------------------------------- -- * Vertex Data@@ -135,6 +172,7 @@ ,Functor,Foldable,Traversable) makeLenses ''VertexData +-- | Convert to an Ext vtxDataToExt :: VertexData r v -> Point 2 r :+ v vtxDataToExt (VertexData p v) = p :+ v @@ -177,22 +215,23 @@ -- -- pre: the input polygon is given in counterclockwise order -- running time: \(O(n)\).-fromSimplePolygon :: proxy s- -> SimplePolygon p r+fromSimplePolygon :: forall s p r f.+ SimplePolygon p r -> f -- ^ data inside -> f -- ^ data outside the polygon -> PlaneGraph s p () f r-fromSimplePolygon p (SimplePolygon vs) iD oD = PlaneGraph g'+fromSimplePolygon poly iD oD = PlaneGraph g' where- g = fromVertices p vs+ vs = poly ^. outerBoundaryVector+ g = fromVertices vs fData' = V.fromList [iD, oD] g' = g & PG.faceData .~ fData' -- | Constructs a planar from the given vertices-fromVertices :: proxy s- -> C.CSeq (Point 2 r :+ p)- -> PlanarGraph s Primal (VertexData r p) () ()-fromVertices _ vs = g&PG.vertexData .~ vData'+fromVertices :: forall s r p.+ CircularVector (Point 2 r :+ p)+ -> PlanarGraph s Primal (VertexData r p) () ()+fromVertices vs = g&PG.vertexData .~ vData' where n = length vs g = planarGraph [ [ (Dart (Arc i) Positive, ())@@ -206,11 +245,10 @@ -- pre: The segments form a single connected component -- -- running time: \(O(n\log n)\)-fromConnectedSegments :: (Foldable f, Ord r, Num r)- => proxy s- -> f (LineSegment 2 p r :+ e)- -> PlaneGraph s (NonEmpty.NonEmpty p) e () r-fromConnectedSegments _ ss = PlaneGraph $ planarGraph dts & PG.vertexData .~ vxData+fromConnectedSegments :: forall s p r e f. (Foldable f, Ord r, Num r)+ => f (LineSegment 2 p r :+ e)+ -> PlaneGraph s (NonEmpty.NonEmpty p) e () r+fromConnectedSegments ss = PlaneGraph $ planarGraph dts & PG.vertexData .~ vxData where pts = M.fromListWith (<>) . concatMap f . zipWith g [0..] . F.toList $ ss f (s :+ e) = [ ( s^.start.core@@ -223,7 +261,7 @@ sing x = x NonEmpty.:| [] - vts = map (\(p,sp) -> (p,map (^.extra) . sortAround (ext p) <$> sp))+ vts = map (\(p,sp) -> (p,map (^.extra) . sortAround' (ext p) <$> sp)) . M.assocs $ pts -- vertex Data vxData = V.fromList . map (\(p,sp) -> VertexData p (sp^._1)) $ vts@@ -238,6 +276,8 @@ -- -- >>> numVertices smallG -- 4+-- >>> numVertices myPlaneGraph+-- 15 numVertices :: PlaneGraph s v e f r -> Int numVertices = PG.numVertices . _graph @@ -245,6 +285,7 @@ -- -- >>> numDarts smallG -- 10+-- numDarts :: PlaneGraph s v e f r -> Int numDarts = PG.numDarts . _graph @@ -259,6 +300,8 @@ -- -- >>> numFaces smallG -- 3+-- >>> numFaces myPlaneGraph+-- 7 numFaces :: PlaneGraph s v e f r -> Int numFaces = PG.numFaces . _graph @@ -272,10 +315,10 @@ -- | Enumerate all vertices, together with their vertex data -- -- >>> mapM_ print $ vertices smallG--- (VertexId 0,VertexData {_location = Point2 [0,0], _vData = 0})--- (VertexId 1,VertexData {_location = Point2 [2,2], _vData = 1})--- (VertexId 2,VertexData {_location = Point2 [2,0], _vData = 2})--- (VertexId 3,VertexData {_location = Point2 [-1,4], _vData = 3})+-- (VertexId 0,VertexData {_location = Point2 0 0, _vData = 0})+-- (VertexId 1,VertexData {_location = Point2 2 2, _vData = 1})+-- (VertexId 2,VertexData {_location = Point2 2 0, _vData = 2})+-- (VertexId 3,VertexData {_location = Point2 (-1) 4, _vData = 3}) vertices :: PlaneGraph s v e f r -> V.Vector (VertexId' s, VertexData r v) vertices = PG.vertices . _graph @@ -284,9 +327,12 @@ darts' = PG.darts' . _graph -- | Get all darts together with their data+--+-- darts :: PlaneGraph s v e f r -> V.Vector (Dart s, e) darts = PG.darts . _graph + -- | Enumerate all edges. We report only the Positive darts edges' :: PlaneGraph s v e f r -> V.Vector (Dart s) edges' = PG.edges' . _graph@@ -330,26 +376,40 @@ faces' :: PlaneGraph s v e f r -> V.Vector (FaceId' s) faces' = PG.faces' . _graph ++-- | face Ids of all internal faces in the plane graph+--+-- running time: \(O(n)\)+internalFaces' :: (Ord r, Num r) => PlaneGraph s v e f r -> V.Vector (FaceId' s)+internalFaces' g = let i = outerFaceId g in V.filter (/= i) $ faces' g+ -- | All faces with their face data. -- -- >>> mapM_ print $ faces smallG -- (FaceId 0,"OuterFace") -- (FaceId 1,"A") -- (FaceId 2,"B")+-- >>> mapM_ print $ faces myPlaneGraph+-- (FaceId 0,"OuterFace")+-- (FaceId 1,"A")+-- (FaceId 2,"B")+-- (FaceId 3,"C")+-- (FaceId 4,"E")+-- (FaceId 5,"F")+-- (FaceId 6,"D") faces :: PlaneGraph s v e f r -> V.Vector (FaceId' s, f) faces = PG.faces . _graph - -- | Reports the outerface and all internal faces separately. -- running time: \(O(n)\)-faces'' :: (Ord r, Fractional r)+faces'' :: (Ord r, Num r) => PlaneGraph s v e f r -> ((FaceId' s, f), V.Vector (FaceId' s, f)) faces'' g = let i = outerFaceId g in ((i,g^.dataOf i), V.filter (\(j,_) -> i /= j) $ faces g) -- | Reports all internal faces. -- running time: \(O(n)\)-internalFaces :: (Ord r, Fractional r)+internalFaces :: (Ord r, Num r) => PlaneGraph s v e f r -> V.Vector (FaceId' s, f) internalFaces = snd . faces'' @@ -387,20 +447,34 @@ -- -- >>> incidentEdges (VertexId 1) smallG -- [Dart (Arc 1) -1,Dart (Arc 4) +1,Dart (Arc 3) +1]+-- >>> mapM_ print $ incidentEdges (VertexId 5) myPlaneGraph+-- Dart (Arc 1) -1+-- Dart (Arc 7) +1+-- Dart (Arc 10) +1+-- Dart (Arc 4) -1 incidentEdges :: VertexId' s -> PlaneGraph s v e f r -> V.Vector (Dart s) incidentEdges v = PG.incidentEdges v . _graph --- | All incoming edges incident to vertex v, in counterclockwise order around v.++-- | All edges incident to vertex v in incoming direction+-- (i.e. pointing into v) in counterclockwise order around v. --+-- running time: \(O(k)\), where \(k) is the total number of incident edges of v+-- -- >>> incomingEdges (VertexId 1) smallG--- [Dart (Arc 1) -1]+-- [Dart (Arc 1) +1,Dart (Arc 4) -1,Dart (Arc 3) -1] incomingEdges :: VertexId' s -> PlaneGraph s v e f r -> V.Vector (Dart s) incomingEdges v = PG.incomingEdges v . _graph --- | All outgoing edges incident to vertex v, in counterclockwise order around v.+++-- | All edges incident to vertex v in outgoing direction+-- (i.e. pointing out of v) in counterclockwise order around v. --+-- running time: \(O(k)\), where \(k) is the total number of incident edges of v+-- -- >>> outgoingEdges (VertexId 1) smallG--- [Dart (Arc 4) +1,Dart (Arc 3) +1]+-- [Dart (Arc 1) -1,Dart (Arc 4) +1,Dart (Arc 3) +1] outgoingEdges :: VertexId' s -> PlaneGraph s v e f r -> V.Vector (Dart s) outgoingEdges v = PG.outgoingEdges v . _graph @@ -411,31 +485,69 @@ -- -- >>> neighboursOf (VertexId 1) smallG -- [VertexId 0,VertexId 2,VertexId 3]+-- >>> neighboursOf (VertexId 5) myPlaneGraph+-- [VertexId 0,VertexId 6,VertexId 8,VertexId 1] neighboursOf :: VertexId' s -> PlaneGraph s v e f r -> V.Vector (VertexId' s) neighboursOf v = PG.neighboursOf v . _graph -- | Given a dart d that points into some vertex v, report the next dart in the--- cyclic order around v in clockwise direction.+-- cyclic (counterclockwise) order around v. -- -- running time: \(O(1)\) -- -- >>> nextIncidentEdge (dart 1 "+1") smallG--- Dart (Arc 2) +1+-- Dart (Arc 4) +1+-- >>> nextIncidentEdge (dart 1 "+1") myPlaneGraph+-- Dart (Arc 7) +1+-- >>> nextIncidentEdge (dart 17 "-1") myPlaneGraph+-- Dart (Arc 15) -1 nextIncidentEdge :: Dart s -> PlaneGraph s v e f r -> Dart s nextIncidentEdge d = PG.nextIncidentEdge d . _graph --- | Given a dart d that points into some vertex v, report the next dart in the--- cyclic order around v (in clockwise order)+-- | Given a dart d that points into some vertex v, report the previous dart in the+-- cyclic (counterclockwise) order around v. -- -- running time: \(O(1)\) -- -- >>> prevIncidentEdge (dart 1 "+1") smallG--- Dart (Arc 0) +1+-- Dart (Arc 3) +1+-- >>> prevIncidentEdge (dart 1 "+1") myPlaneGraph+-- Dart (Arc 4) -1+-- >>> prevIncidentEdge (dart 7 "-1") myPlaneGraph+-- Dart (Arc 1) -1 prevIncidentEdge :: Dart s -> PlaneGraph s v e f r -> Dart s prevIncidentEdge d = PG.prevIncidentEdge d . _graph +-- | Given a dart d that points away from some vertex v, report the+-- next dart in the cyclic (counterclockwise) order around v.+--+--+-- running time: \(O(1)\)+--+-- >>> nextIncidentEdgeFrom (dart 1 "+1") smallG+-- Dart (Arc 2) +1+-- >>> nextIncidentEdgeFrom (dart 1 "+1") myPlaneGraph+-- Dart (Arc 2) +1+-- >>> nextIncidentEdgeFrom (dart 4 "+1") myPlaneGraph+-- Dart (Arc 15) +1+nextIncidentEdgeFrom :: Dart s -> PlaneGraph s v e f r -> Dart s+nextIncidentEdgeFrom d = PG.nextIncidentEdgeFrom d . _graph++-- | Given a dart d that points into away from vertex v, report the previous dart in the+-- cyclic (counterclockwise) order around v.+--+-- running time: \(O(1)\)+--+-- >>> prevIncidentEdgeFrom (dart 1 "+1") smallG+-- Dart (Arc 0) +1+-- >>> prevIncidentEdgeFrom (dart 4 "+1") myPlaneGraph+-- Dart (Arc 2) -1+prevIncidentEdgeFrom :: Dart s -> PlaneGraph s v e f r -> Dart s+prevIncidentEdgeFrom d = PG.prevIncidentEdgeFrom d . _graph++ -- | The face to the left of the dart -- -- running time: \(O(1)\).@@ -488,20 +600,34 @@ prevEdge d = PG.prevEdge d . _graph --- | The darts bounding this face, for internal faces in clockwise order, for--- the outer face in counter clockwise order.---+-- | The darts bounding this face. The darts are reported in order+-- along the face. This means that for internal faces the darts are+-- reported in *clockwise* order along the boundary, whereas for the+-- outer face the darts are reported in counter clockwise order. -- -- running time: \(O(k)\), where \(k\) is the output size. --+-- >>> boundary (FaceId $ VertexId 2) smallG -- around face B+-- [Dart (Arc 2) +1,Dart (Arc 3) -1,Dart (Arc 1) -1]+-- >>> boundary (FaceId $ VertexId 0) smallG -- around outer face+-- [Dart (Arc 0) +1,Dart (Arc 4) -1,Dart (Arc 3) +1,Dart (Arc 2) -1] -- boundary :: FaceId' s -> PlaneGraph s v e f r -> V.Vector (Dart s) boundary f = PG.boundary f . _graph --- | Generates the darts incident to a face, starting with the given dart.+-- | Given a dart d, generates the darts bounding the face that is to+-- the right of the given dart. The darts are reported in order along+-- the face. This means that for internal faces the darts are reported+-- in *clockwise* order along the boundary, whereas for the outer face+-- the darts are reported in counter clockwise order. --+-- running time: \(O(k)\), where \(k\) is the number of darts reported ----- \(O(k)\), where \(k\) is the number of darts reported+-- >>> boundary' (dart 2 "+1") smallG -- around face B+-- [Dart (Arc 2) +1,Dart (Arc 3) -1,Dart (Arc 1) -1]+-- >>> boundary' (dart 0 "+1") smallG -- around outer face+-- [Dart (Arc 0) +1,Dart (Arc 4) -1,Dart (Arc 3) +1,Dart (Arc 2) -1]+-- boundary' :: Dart s -> PlaneGraph s v e f r -> V.Vector (Dart s) boundary' d = PG.boundary' d . _graph @@ -513,8 +639,24 @@ -- | The vertices bounding this face, for internal faces in clockwise order, for -- the outer face in counter clockwise order. ----- -- running time: \(O(k)\), where \(k\) is the output size.+--+-- >>> boundaryVertices (FaceId $ VertexId 2) smallG -- around B+-- [VertexId 0,VertexId 3,VertexId 1]+-- >>> boundaryVertices (FaceId $ VertexId 0) smallG -- around outerface+-- [VertexId 0,VertexId 2,VertexId 1,VertexId 3]+-- >>> mapM_ print $ boundaryVertices (FaceId $ VertexId 0) myPlaneGraph+-- VertexId 0+-- VertexId 9+-- VertexId 10+-- VertexId 11+-- VertexId 7+-- VertexId 8+-- VertexId 12+-- VertexId 13+-- VertexId 4+-- VertexId 3+-- VertexId 2 boundaryVertices :: FaceId' s -> PlaneGraph s v e f r -> V.Vector (VertexId' s) boundaryVertices f = PG.boundaryVertices f . _graph@@ -523,9 +665,18 @@ -------------------------------------------------------------------------------- -- * Access data ++-- | Lens to access the vertex data+--+-- Note that using the setting part of this lens may be very+-- expensive!! (O(n)) vertexDataOf :: VertexId' s -> Lens' (PlaneGraph s v e f r ) (VertexData r v) vertexDataOf v = graph.PG.dataOf v +-- | Get the location of a vertex in the plane graph+--+-- Note that the setting part of this lens may be very expensive!+-- Moreover, use with care (as this may destroy planarity etc.) locationOf :: VertexId' s -> Lens' (PlaneGraph s v e f r ) (Point 2 r) locationOf v = vertexDataOf v.location @@ -556,7 +707,7 @@ => (VertexId' s -> v -> m v') -> PlaneGraph s v e f r -> m (PlaneGraph s v' e f r)-traverseVertices f = itraverseOf (vertexData.itraversed) (\i -> f (VertexId i))+traverseVertices f = itraverseOf (vertexData.itraversed) (f . VertexId) -- | Traverses the darts --@@ -611,7 +762,7 @@ -- -- running time: \(O(n)\) ---outerFaceId :: (Ord r, Fractional r) => PlaneGraph s v e f r -> FaceId' s+outerFaceId :: (Ord r, Num r) => PlaneGraph s v e f r -> FaceId' s outerFaceId ps = leftFace (outerFaceDart ps) ps @@ -620,19 +771,24 @@ -- -- running time: \(O(n)\) ---outerFaceDart :: (Ord r, Fractional r) => PlaneGraph s v e f r -> Dart s-outerFaceDart ps = d+outerFaceDart :: (Ord r, Num r) => PlaneGraph s v e f r -> Dart s+outerFaceDart pg = d where- (v,_) = V.minimumBy (comparing (^._2.location)) . vertices $ ps+ (v,_) = V.minimumBy (comparing (^._2.location)) . vertices $ pg -- compare lexicographically; i.e. if same x-coord prefer the one with the -- smallest y-coord- d :+ _ = V.maximumBy (cmpSlope `on` (^.extra))- . fmap (\d' -> d' :+ (edgeSegment d' ps)^.core.to supportingLine)- $ incidentEdges v ps++ (_ :+ d) = V.minimumBy (cwCmpAroundWith' (Vector2 (-1) 0) (pg^.locationOf v :+ ()))+ . fmap (\d' -> let u = headOf d' pg in (pg^.locationOf u) :+ d')+ $ outgoingEdges v pg -- based on the approach sketched at https://cstheory.stackexchange.com/questions/27586/finding-outer-face-in-plane-graph-embedded-planar-graph -- basically: find the leftmost vertex, find the incident edge with the largest slope -- and take the face left of that edge. This is the outerface. -- note that this requires that the edges are straight line segments+ --+ -- note that rather computing slopes we just ask for the first+ -- vertec cw vertex around v. First with respect to some direction+ -- pointing towards the left. --------------------------------------------------------------------------------@@ -641,11 +797,32 @@ -- | Reports all edges as line segments -- -- >>> mapM_ print $ edgeSegments smallG--- (Dart (Arc 0) +1,LineSegment (Closed (Point2 [0,0] :+ 0)) (Closed (Point2 [2,0] :+ 2)) :+ "0->2")--- (Dart (Arc 1) +1,LineSegment (Closed (Point2 [0,0] :+ 0)) (Closed (Point2 [2,2] :+ 1)) :+ "0->1")--- (Dart (Arc 2) +1,LineSegment (Closed (Point2 [0,0] :+ 0)) (Closed (Point2 [-1,4] :+ 3)) :+ "0->3")--- (Dart (Arc 4) +1,LineSegment (Closed (Point2 [2,2] :+ 1)) (Closed (Point2 [2,0] :+ 2)) :+ "1->2")--- (Dart (Arc 3) +1,LineSegment (Closed (Point2 [2,2] :+ 1)) (Closed (Point2 [-1,4] :+ 3)) :+ "1->3")+-- (Dart (Arc 0) +1,ClosedLineSegment (Point2 0 0 :+ 0) (Point2 2 0 :+ 2) :+ "0->2")+-- (Dart (Arc 1) +1,ClosedLineSegment (Point2 0 0 :+ 0) (Point2 2 2 :+ 1) :+ "0->1")+-- (Dart (Arc 2) +1,ClosedLineSegment (Point2 0 0 :+ 0) (Point2 (-1) 4 :+ 3) :+ "0->3")+-- (Dart (Arc 4) +1,ClosedLineSegment (Point2 2 2 :+ 1) (Point2 2 0 :+ 2) :+ "1->2")+-- (Dart (Arc 3) +1,ClosedLineSegment (Point2 2 2 :+ 1) (Point2 (-1) 4 :+ 3) :+ "1->3")+-- >>> mapM_ print $ edgeSegments myPlaneGraph+-- (Dart (Arc 0) +1,ClosedLineSegment (Point2 0 0 :+ 0) (Point2 8 (-5) :+ 9) :+ ())+-- (Dart (Arc 1) +1,ClosedLineSegment (Point2 0 0 :+ 0) (Point2 8 1 :+ 5) :+ ())+-- (Dart (Arc 2) +1,ClosedLineSegment (Point2 0 0 :+ 0) (Point2 4 4 :+ 1) :+ ())+-- (Dart (Arc 3) +1,ClosedLineSegment (Point2 0 0 :+ 0) (Point2 3 7 :+ 2) :+ ())+-- (Dart (Arc 4) +1,ClosedLineSegment (Point2 4 4 :+ 1) (Point2 8 1 :+ 5) :+ ())+-- (Dart (Arc 15) +1,ClosedLineSegment (Point2 4 4 :+ 1) (Point2 10 4 :+ 12) :+ ())+-- (Dart (Arc 5) +1,ClosedLineSegment (Point2 3 7 :+ 2) (Point2 0 5 :+ 3) :+ ())+-- (Dart (Arc 6) +1,ClosedLineSegment (Point2 0 5 :+ 3) (Point2 3 8 :+ 4) :+ ())+-- (Dart (Arc 18) +1,ClosedLineSegment (Point2 3 8 :+ 4) (Point2 9 6 :+ 13) :+ ())+-- (Dart (Arc 7) +1,ClosedLineSegment (Point2 8 1 :+ 5) (Point2 6 (-1) :+ 6) :+ ())+-- (Dart (Arc 10) +1,ClosedLineSegment (Point2 8 1 :+ 5) (Point2 12 1 :+ 8) :+ ())+-- (Dart (Arc 12) +1,ClosedLineSegment (Point2 6 (-1) :+ 6) (Point2 8 (-5) :+ 9) :+ ())+-- (Dart (Arc 8) +1,ClosedLineSegment (Point2 9 (-1) :+ 7) (Point2 12 1 :+ 8) :+ ())+-- (Dart (Arc 14) +1,ClosedLineSegment (Point2 9 (-1) :+ 7) (Point2 14 (-1) :+ 11) :+ ())+-- (Dart (Arc 9) +1,ClosedLineSegment (Point2 12 1 :+ 8) (Point2 10 4 :+ 12) :+ ())+-- (Dart (Arc 11) +1,ClosedLineSegment (Point2 8 (-5) :+ 9) (Point2 12 (-3) :+ 10) :+ ())+-- (Dart (Arc 13) +1,ClosedLineSegment (Point2 12 (-3) :+ 10) (Point2 14 (-1) :+ 11) :+ ())+-- (Dart (Arc 16) +1,ClosedLineSegment (Point2 10 4 :+ 12) (Point2 9 6 :+ 13) :+ ())+-- (Dart (Arc 17) +1,ClosedLineSegment (Point2 10 4 :+ 12) (Point2 8 5 :+ 14) :+ ())+-- (Dart (Arc 19) +1,ClosedLineSegment (Point2 9 6 :+ 13) (Point2 8 5 :+ 14) :+ ()) edgeSegments :: PlaneGraph s v e f r -> V.Vector (Dart s, LineSegment 2 v r :+ e) edgeSegments ps = fmap withSegment . edges $ ps where@@ -665,31 +842,87 @@ seg = let (p,q) = bimap vtxDataToExt vtxDataToExt $ ps^.endPointsOf d in ClosedLineSegment p q --- | The polygon describing the face++-- | The boundary of the face as a simple polygon. For internal faces+-- the polygon that is reported has its vertices stored in CCW order+-- (as expected). ----- runningtime: \(O(k)\), where \(k\) is the size of the face.+-- pre: FaceId refers to an internal face. --+-- For the other face this prodcuces a polygon in CW order (this may+-- lead to unexpected results.) ---rawFaceBoundary :: FaceId' s -> PlaneGraph s v e f r- -> SimplePolygon v r :+ f-rawFaceBoundary i ps = pg :+ (ps^.dataOf i)+-- runningtime: \(O(k)\), where \(k\) is the size of the face.+faceBoundary :: FaceId' s -> PlaneGraph s v e f r -> SimplePolygon v r :+ f+faceBoundary i ps = pg :+ (ps^.dataOf i) where- pg = fromPoints . F.toList . fmap (\j -> ps^.graph.dataOf j.to vtxDataToExt)+ pg = unsafeFromVector . V.reverse . fmap (\j -> ps^.graph.dataOf j.to vtxDataToExt) . boundaryVertices i $ ps+ -- polygons are stored in CCW order, the boundaryVertices of+ -- internal faces are reported in CW order we reverse them. --- | Alias for rawFace Boundary+--------------------------------------------------------------------------------++-- | The boundary of the face as a simple polygon. For internal faces+-- the polygon that is reported has its vertices stored in CCW order+-- (as expected). --+-- pre: FaceId refers to an internal face.+--+-- For the other face this prodcuces a polygon in CW order (this may+-- lead to unexpected results.)+-- -- runningtime: \(O(k)\), where \(k\) is the size of the face.-rawFacePolygon :: FaceId' s -> PlaneGraph s v e f r -> SimplePolygon v r :+ f-rawFacePolygon = rawFaceBoundary+internalFacePolygon :: FaceId' s -> PlaneGraph s v e f r -> SimplePolygon v r :+ f+internalFacePolygon = faceBoundary --- | Lists all faces of the plane graph.-rawFacePolygons :: PlaneGraph s v e f r- -> V.Vector (FaceId' s, SimplePolygon v r :+ f)-rawFacePolygons ps = fmap (\i -> (i,rawFacePolygon i ps)) . faces' $ ps+-- | Given the outerFaceId and the graph, construct a sufficiently+-- large rectangular multipolygon ith a hole containing the boundary+-- of the outer face.+outerFacePolygon :: (Num r, Ord r)+ => FaceId' s -> PlaneGraph s v e f r -> MultiPolygon (Maybe v) r :+ f+outerFacePolygon i pg =+ outerFacePolygon' i outer pg & core %~ first (either (const Nothing) Just)+ where+ outer = rectToPolygon . grow 1 . boundingBox $ pg+ rectToPolygon = unsafeFromPoints . reverse . F.toList . corners +-- | Given the outerface id, and a sufficiently large outer boundary,+-- draw the outerface as a polygon with a hole.+outerFacePolygon' :: FaceId' s -> SimplePolygon v' r+ -> PlaneGraph s v e f r -> MultiPolygon (Either v' v) r :+ f+outerFacePolygon' i outer pg = MultiPolygon (first Left outer) [hole] :+ pg^.dataOf i+ where+ hole = reverseOuterBoundary . first Right . view core $ faceBoundary i pg+ -- if we call faceBoundary on the outerface we get a polygon in+ -- the wrong orientation. So reverse it.+ -------------------------------------------------------------------------------- +-- | Given the outerFace Id, construct polygons for all faces. We+-- construct a polygon with a hole for the outer face.+--+facePolygons :: (Num r, Ord r) => FaceId' s -> PlaneGraph s v e f r+ -> ( (FaceId' s, MultiPolygon (Maybe v) r :+ f)+ , V.Vector (FaceId' s, SimplePolygon v r :+ f)+ )+facePolygons i ps = ((i, outerFacePolygon i ps), facePolygons' i ps)++-- | Given the outerFace Id, lists all internal faces of the plane+-- graph with their boundaries.+facePolygons' :: FaceId' s -> PlaneGraph s v e f r+ -> V.Vector (FaceId' s, SimplePolygon v r :+ f)+facePolygons' i ps = fmap (\j -> (j,internalFacePolygon j ps)) . V.filter (/= i) . faces' $ ps+++-- | lists all internal faces of the plane graph with their+-- boundaries.+internalFacePolygons :: (Ord r, Num r)+ => PlaneGraph s v e f r -> V.Vector (FaceId' s, SimplePolygon v r :+ f)+internalFacePolygons pg = facePolygons' (outerFaceId pg) pg++--------------------------------------------------------------------------------+ -- | Labels the edges of a plane graph with their distances, as specified by -- the distance function. withEdgeDistances :: (Point 2 r -> Point 2 r -> a)@@ -697,3 +930,7 @@ withEdgeDistances f g = g&graph.PG.dartData %~ fmap (\(d,x) -> (d,len d :+ x)) where len d = uncurry f . over both (^.location) $ endPointData d g++++--------------------------------------------------------------------------------
src/Data/PlaneGraph/IO.hs view
@@ -16,19 +16,22 @@ import Data.Aeson import Data.Bifunctor import qualified Data.ByteString as B-import Data.Ext import Data.Geometry.Point import qualified Data.List as List import qualified Data.PlanarGraph.AdjRep as PGA import qualified Data.PlanarGraph.IO as PGIO import Data.PlaneGraph.Core-import Data.PlaneGraph.AdjRep (Face,Vtx(Vtx),Gr(Gr))-import Data.Proxy+import Data.PlaneGraph.AdjRep import qualified Data.Vector as V import qualified Data.Vector.Mutable as MV import Data.Yaml (ParseException) import Data.Yaml.Util ++import Data.RealNumber.Rational+-- import Data.PlanarGraph.Dart+-- import Data.PlaneGraph.AdjRep+ -------------------------------------------------------------------------------- -- $setup@@ -56,7 +59,7 @@ -- , Face (0,1) "A" -- , Face (1,0) "B" -- ]--- smallG = fromAdjRep (Proxy :: Proxy ()) small+-- smallG = fromAdjRep @() small -- :} -- --@@ -69,10 +72,10 @@ -- * Reading and Writing the Plane Graph -- | Reads a plane graph from a bytestring-readPlaneGraph :: (FromJSON v, FromJSON e, FromJSON f, FromJSON r)- => proxy s -> B.ByteString- -> Either ParseException (PlaneGraph s v e f r)-readPlaneGraph _ = decodeYaml+readPlaneGraph :: forall s v e f r. (FromJSON v, FromJSON e, FromJSON f, FromJSON r)+ => B.ByteString+ -> Either ParseException (PlaneGraph s v e f r)+readPlaneGraph = decodeYaml -- | Writes a plane graph to a bytestring writePlaneGraph :: (ToJSON v, ToJSON e, ToJSON f, ToJSON r)@@ -87,7 +90,7 @@ instance (FromJSON v, FromJSON e, FromJSON f, FromJSON r) => FromJSON (PlaneGraph s v e f r) where- parseJSON v = fromAdjRep (Proxy :: Proxy s) <$> parseJSON v+ parseJSON v = fromAdjRep @s <$> parseJSON v -------------------------------------------------------------------------------- @@ -106,9 +109,9 @@ -- should be in counter clockwise order. -- -- running time: \(O(n)\)-fromAdjRep :: proxy s -> Gr (Vtx v e r) (Face f) -> PlaneGraph s v e f r-fromAdjRep px = PlaneGraph . PGIO.fromAdjRep px- . first (\(Vtx v p aj x) -> PGA.Vtx v aj $ VertexData p x)+fromAdjRep :: forall s v e f r. Gr (Vtx v e r) (Face f) -> PlaneGraph s v e f r+fromAdjRep = PlaneGraph . PGIO.fromAdjRep+ . first (\(Vtx v p aj x) -> PGA.Vtx v aj $ VertexData p x) -------------------------------------------------------------------------------- @@ -123,13 +126,81 @@ location' = V.create $ do a <- MV.new (length vs) forM_ vs $ \(Vtx i p _ _) ->- MV.write a i $ ext p+ MV.write a i p pure a -- sort the adjacencies around every vertex v sort' (Vtx v p ajs x) = Vtx v p (List.sortBy (around p) ajs) x- around p (a,_) (b,_) = ccwCmpAround (ext p) (location' V.! a) (location' V.! b)+ around p (a,_) (b,_) = ccwCmpAround p (location' V.! a) (location' V.! b) -- note: since the graph is planar, there should not be -- any pairs of points for which ccwCmpAround returns EQ -- hence, no need to pick a secondary comparison --------------------------------------------------------------------------------++-- smallG = fromAdjRep (Proxy :: Proxy ()) small+-- where+-- small :: Gr (Vtx Int String Int) (Face String)+-- small = Gr [ Vtx 0 (Point2 0 0) [ (2,"0->2")+-- , (1,"0->1")+-- , (3,"0->3")+-- ] 0+-- , Vtx 1 (Point2 2 2) [ (0,"1->0")+-- , (2,"1->2")+-- , (3,"1->3")+-- ] 1+-- , Vtx 2 (Point2 2 0) [ (0,"2->0")+-- , (1,"2->1")+-- ] 2+-- , Vtx 3 (Point2 (-1) 4) [ (0,"3->0")+-- , (1,"3->1")+-- ] 3+-- ]+-- [ Face (2,1) "OuterFace"+-- , Face (0,1) "A"+-- , Face (1,0) "B"+-- ]++-- dart i s = Dart (Arc i) (read s)++data MyWorld++-- +myPlaneGraph :: PlaneGraph MyWorld Int () String (RealNumber 5)+myPlaneGraph = fromAdjRep @MyWorld myPlaneGraphAdjrep++myPlaneGraphAdjrep :: Gr (Vtx Int () (RealNumber 5)) (Face String)+myPlaneGraphAdjrep = Gr [ vtx 0 (Point2 0 0 ) [e 9, e 5, e 1, e 2]+ , vtx 1 (Point2 4 4 ) [e 0, e 5, e 12]+ , vtx 2 (Point2 3 7 ) [e 0, e 3]+ , vtx 3 (Point2 0 5 ) [e 4, e 2]+ , vtx 4 (Point2 3 8 ) [e 3, e 13]+ , vtx 5 (Point2 8 1 ) [e 0, e 6, e 8, e 1]+ , vtx 6 (Point2 6 (-1)) [e 5, e 9]+ , vtx 7 (Point2 9 (-1)) [e 8, e 11]+ , vtx 8 (Point2 12 1 ) [e 7, e 12, e 5]+ , vtx 9 (Point2 8 (-5)) [e 0, e 10, e 6]+ , vtx 10 (Point2 12 (-3)) [e 9, e 11]+ , vtx 11 (Point2 14 (-1)) [e 10, e 7]+ , vtx 12 (Point2 10 4 ) [e 1, e 8, e 13, e 14]+ , vtx 13 (Point2 9 6 ) [e 4, e 14, e 12]+ , vtx 14 (Point2 8 5 ) [e 13, e 12]+ ]+ [ Face (0,9) "OuterFace"+ , Face (0,5) "A"+ , Face (0,1) "B"+ , Face (0,2) "C"+ , Face (14,13) "D"+ , Face (1,12) "E"+ , Face (5,8) "F"+ ]+ where+ e i = (i,())+ vtx i p es = Vtx i p es i+++++-- myPlaneGraph' :: IO (PlaneGraph MyWorld () () () (RealNumber 5))+-- myPlaneGraph' = let err x = error $ show x+-- in either err id . readPlaneGraph+-- <$> B.readFile "docs/Data/PlaneGraph/myPlaneGraph.yaml"
src/Graphics/Camera.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE TemplateHaskell #-} -------------------------------------------------------------------------------- -- | -- Module : Graphics.Camera@@ -22,16 +21,17 @@ ) where import Control.Lens+import Data.Geometry.Matrix import Data.Geometry.Point-import Data.Geometry.Vector import Data.Geometry.Transformation+import Data.Geometry.Vector -------------------------------------------------------------------------------- -- | A basic camera data type. The fields stored are: -- -- * the camera position,--- * the raw camera normal, i.e. a unit vecotr into the center of the screen,+-- * the raw camera normal, i.e. a unit vector into the center of the screen, -- * the raw view up vector indicating which side points "upwards" in the scene, -- * the viewplane depth (i.e. the distance from the camera position to the plane on which we project), -- * the near distance (everything closer than this is clipped),@@ -52,8 +52,38 @@ ---------------------------------------- -- * Field Accessor Lenses -makeLenses ''Camera+-- Lemmih: Writing out the lenses by hand so they can be documented.+-- makeLenses ''Camera +-- | Camera position.+cameraPosition :: Lens' (Camera r) (Point 3 r)+cameraPosition = lens _cameraPosition (\cam p -> cam{_cameraPosition=p})++-- | Raw camera normal, i.e. a unit vector into the center of the screen.+rawCameraNormal :: Lens' (Camera r) (Vector 3 r)+rawCameraNormal = lens _rawCameraNormal (\cam r -> cam{_rawCameraNormal=r})++-- | Raw view up vector indicating which side points "upwards" in the scene.+rawViewUp :: Lens' (Camera r) (Vector 3 r)+rawViewUp = lens _rawViewUp (\cam r -> cam{_rawViewUp=r})++-- | Viewplane depth (i.e. the distance from the camera position to the plane on which we project).+viewPlaneDepth :: Lens' (Camera r) r+viewPlaneDepth = lens _viewPlaneDepth (\cam v -> cam{_viewPlaneDepth=v})++-- | Near distance (everything closer than this is clipped).+nearDist :: Lens' (Camera r) r+nearDist = lens _nearDist (\cam n -> cam{_nearDist=n})++-- | Far distance (everything further away than this is clipped).+farDist :: Lens' (Camera r) r+farDist = lens _farDist (\cam f -> cam{_farDist=f})++-- | Screen dimensions.+screenDimensions :: Lens' (Camera r) (Vector 2 r)+screenDimensions = lens _screenDimensions (\cam d -> cam{_screenDimensions=d})++ -------------------------------------------------------------------------------- -- * Accessor Lenses @@ -81,8 +111,7 @@ -- | Translates world coordinates into view coordinates worldToView :: Fractional r => Camera r -> Transformation 3 r-worldToView c = rotateCoordSystem c- |.| (translation $ (-1) *^ c^.cameraPosition.vector)+worldToView c = rotateCoordSystem c |.| translation ((-1) *^ c^.cameraPosition.vector) -- | Transformation into viewport coordinates toViewPort :: Fractional r => Camera r -> Transformation 3 r
− test/Algorithms/Geometry/LineSegmentIntersection/manual.ipe
@@ -1,324 +0,0 @@-<?xml version="1.0"?>-<!DOCTYPE ipe SYSTEM "ipe.dtd">-<ipe version="70107" creator="Ipe 7.2.2">-<info created="D:20160903131616" modified="D:20161022125153"/>-<ipestyle name="basic">-<symbol name="arrow/arc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/farc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/ptarc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fptarc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="mark/circle(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</symbol>-<symbol name="mark/disk(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-</path>-</symbol>-<symbol name="mark/fdisk(sfx)" transformations="translations">-<group>-<path fill="sym-fill">-0.5 0 0 0.5 0 0 e-</path>-<path fill="sym-stroke" fillrule="eofill">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</group>-</symbol>-<symbol name="mark/box(sx)" transformations="translations">-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</symbol>-<symbol name="mark/square(sx)" transformations="translations">-<path fill="sym-stroke">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h-</path>-</symbol>-<symbol name="mark/fsquare(sfx)" transformations="translations">-<group>-<path fill="sym-fill">--0.5 -0.5 m-0.5 -0.5 l-0.5 0.5 l--0.5 0.5 l-h-</path>-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</group>-</symbol>-<symbol name="mark/cross(sx)" transformations="translations">-<group>-<path fill="sym-stroke">--0.43 -0.57 m-0.57 0.43 l-0.43 0.57 l--0.57 -0.43 l-h-</path>-<path fill="sym-stroke">--0.43 0.57 m-0.57 -0.43 l-0.43 -0.57 l--0.57 0.43 l-h-</path>-</group>-</symbol>-<symbol name="arrow/fnormal(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/pointed(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fpointed(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/linear(spx)">-<path stroke="sym-stroke" pen="sym-pen">--1 0.333 m-0 0 l--1 -0.333 l-</path>-</symbol>-<symbol name="arrow/fdouble(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/double(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<pen name="heavier" value="0.8"/>-<pen name="fat" value="1.2"/>-<pen name="ultrafat" value="2"/>-<symbolsize name="large" value="5"/>-<symbolsize name="small" value="2"/>-<symbolsize name="tiny" value="1.1"/>-<arrowsize name="large" value="10"/>-<arrowsize name="small" value="5"/>-<arrowsize name="tiny" value="3"/>-<color name="red" value="1 0 0"/>-<color name="green" value="0 1 0"/>-<color name="blue" value="0 0 1"/>-<color name="yellow" value="1 1 0"/>-<color name="orange" value="1 0.647 0"/>-<color name="gold" value="1 0.843 0"/>-<color name="purple" value="0.627 0.125 0.941"/>-<color name="gray" value="0.745"/>-<color name="brown" value="0.647 0.165 0.165"/>-<color name="navy" value="0 0 0.502"/>-<color name="pink" value="1 0.753 0.796"/>-<color name="seagreen" value="0.18 0.545 0.341"/>-<color name="turquoise" value="0.251 0.878 0.816"/>-<color name="violet" value="0.933 0.51 0.933"/>-<color name="darkblue" value="0 0 0.545"/>-<color name="darkcyan" value="0 0.545 0.545"/>-<color name="darkgray" value="0.663"/>-<color name="darkgreen" value="0 0.392 0"/>-<color name="darkmagenta" value="0.545 0 0.545"/>-<color name="darkorange" value="1 0.549 0"/>-<color name="darkred" value="0.545 0 0"/>-<color name="lightblue" value="0.678 0.847 0.902"/>-<color name="lightcyan" value="0.878 1 1"/>-<color name="lightgray" value="0.827"/>-<color name="lightgreen" value="0.565 0.933 0.565"/>-<color name="lightyellow" value="1 1 0.878"/>-<dashstyle name="dashed" value="[4] 0"/>-<dashstyle name="dotted" value="[1 3] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<textsize name="large" value="\large"/>-<textsize name="Large" value="\Large"/>-<textsize name="LARGE" value="\LARGE"/>-<textsize name="huge" value="\huge"/>-<textsize name="Huge" value="\Huge"/>-<textsize name="small" value="\small"/>-<textsize name="footnote" value="\footnotesize"/>-<textsize name="tiny" value="\tiny"/>-<textstyle name="center" begin="\begin{center}" end="\end{center}"/>-<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/>-<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/>-<gridsize name="4 pts" value="4"/>-<gridsize name="8 pts (~3 mm)" value="8"/>-<gridsize name="16 pts (~6 mm)" value="16"/>-<gridsize name="32 pts (~12 mm)" value="32"/>-<gridsize name="10 pts (~3.5 mm)" value="10"/>-<gridsize name="20 pts (~7 mm)" value="20"/>-<gridsize name="14 pts (~5 mm)" value="14"/>-<gridsize name="28 pts (~10 mm)" value="28"/>-<gridsize name="56 pts (~20 mm)" value="56"/>-<anglesize name="90 deg" value="90"/>-<anglesize name="60 deg" value="60"/>-<anglesize name="45 deg" value="45"/>-<anglesize name="30 deg" value="30"/>-<anglesize name="22.5 deg" value="22.5"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="75%" value="0.75"/>-<tiling name="falling" angle="-60" step="4" width="1"/>-<tiling name="rising" angle="30" step="4" width="1"/>-</ipestyle>-<ipestyle name="frank">-<arrowsize name="normal" value="5"/>-<arrowsize name="large" value="8"/>-<arrowsize name="huge" value="10"/>-<arrowsize name="small" value="3"/>-<arrowsize name="tiny" value="1"/>-<dashstyle name="dashed" value="[2 2] 0"/>-<dashstyle name="dotted" value="[0.5 1] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<gridsize name="1 pts" value="1"/>-<gridsize name="2 pts" value="2"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="20%" value="0.2"/>-<opacity name="40%" value="0.4"/>-<opacity name="60%" value="0.6"/>-<opacity name="70%" value="0.7"/>-<opacity name="80%" value="0.8"/>-<opacity name="90%" value="0.9"/>-</ipestyle>-<page>-<layer name="alpha"/>-<view layers="alpha" active="alpha"/>-<path layer="alpha" stroke="black">-16 16 m-144 80 l-</path>-<path stroke="black">-128 32 m-256 112 l-</path>-<path stroke="black">-144 80 m-225.708 52.1085 l-</path>-<path matrix="2.39165 0 0 2.39165 -206.711 -120.784" stroke="black">-144 80 m-148.537 86.7918 l-</path>-<path stroke="black">-144 80 m-140.284 85.5945 l-</path>-<path stroke="black">-137.686 70.5482 m-173.031 38.3458 l-</path>-<path stroke="black">-89.5694 78.2992 m-186.333 44.2802 l-</path>-<path stroke="black">-75.255 80.7127 m-75.255 24.0132 l-</path>-<path stroke="black">-35.9103 40.8867 m-180.812 40.8867 l-</path>-<path stroke="black">-82.6725 35.3346 m-57.1425 25.6907 l-</path>-<path stroke="black">-40.6648 34.541 m-144.411 12.4306 l-</path>-</page>-</ipe>
− test/Algorithms/Geometry/LineSegmentIntersection/selfIntersections.ipe
@@ -1,313 +0,0 @@-<?xml version="1.0"?>-<!DOCTYPE ipe SYSTEM "ipe.dtd">-<ipe version="70107" creator="Ipe 7.2.2">-<info created="D:20170428203409" modified="D:20170428203521"/>-<ipestyle name="basic">-<symbol name="arrow/arc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/farc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/ptarc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fptarc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="mark/circle(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</symbol>-<symbol name="mark/disk(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-</path>-</symbol>-<symbol name="mark/fdisk(sfx)" transformations="translations">-<group>-<path fill="sym-fill">-0.5 0 0 0.5 0 0 e-</path>-<path fill="sym-stroke" fillrule="eofill">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</group>-</symbol>-<symbol name="mark/box(sx)" transformations="translations">-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</symbol>-<symbol name="mark/square(sx)" transformations="translations">-<path fill="sym-stroke">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h-</path>-</symbol>-<symbol name="mark/fsquare(sfx)" transformations="translations">-<group>-<path fill="sym-fill">--0.5 -0.5 m-0.5 -0.5 l-0.5 0.5 l--0.5 0.5 l-h-</path>-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</group>-</symbol>-<symbol name="mark/cross(sx)" transformations="translations">-<group>-<path fill="sym-stroke">--0.43 -0.57 m-0.57 0.43 l-0.43 0.57 l--0.57 -0.43 l-h-</path>-<path fill="sym-stroke">--0.43 0.57 m-0.57 -0.43 l-0.43 -0.57 l--0.57 0.43 l-h-</path>-</group>-</symbol>-<symbol name="arrow/fnormal(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/pointed(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fpointed(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/linear(spx)">-<path stroke="sym-stroke" pen="sym-pen">--1 0.333 m-0 0 l--1 -0.333 l-</path>-</symbol>-<symbol name="arrow/fdouble(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/double(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<pen name="heavier" value="0.8"/>-<pen name="fat" value="1.2"/>-<pen name="ultrafat" value="2"/>-<symbolsize name="large" value="5"/>-<symbolsize name="small" value="2"/>-<symbolsize name="tiny" value="1.1"/>-<arrowsize name="large" value="10"/>-<arrowsize name="small" value="5"/>-<arrowsize name="tiny" value="3"/>-<color name="red" value="1 0 0"/>-<color name="green" value="0 1 0"/>-<color name="blue" value="0 0 1"/>-<color name="yellow" value="1 1 0"/>-<color name="orange" value="1 0.647 0"/>-<color name="gold" value="1 0.843 0"/>-<color name="purple" value="0.627 0.125 0.941"/>-<color name="gray" value="0.745"/>-<color name="brown" value="0.647 0.165 0.165"/>-<color name="navy" value="0 0 0.502"/>-<color name="pink" value="1 0.753 0.796"/>-<color name="seagreen" value="0.18 0.545 0.341"/>-<color name="turquoise" value="0.251 0.878 0.816"/>-<color name="violet" value="0.933 0.51 0.933"/>-<color name="darkblue" value="0 0 0.545"/>-<color name="darkcyan" value="0 0.545 0.545"/>-<color name="darkgray" value="0.663"/>-<color name="darkgreen" value="0 0.392 0"/>-<color name="darkmagenta" value="0.545 0 0.545"/>-<color name="darkorange" value="1 0.549 0"/>-<color name="darkred" value="0.545 0 0"/>-<color name="lightblue" value="0.678 0.847 0.902"/>-<color name="lightcyan" value="0.878 1 1"/>-<color name="lightgray" value="0.827"/>-<color name="lightgreen" value="0.565 0.933 0.565"/>-<color name="lightyellow" value="1 1 0.878"/>-<dashstyle name="dashed" value="[4] 0"/>-<dashstyle name="dotted" value="[1 3] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<textsize name="large" value="\large"/>-<textsize name="Large" value="\Large"/>-<textsize name="LARGE" value="\LARGE"/>-<textsize name="huge" value="\huge"/>-<textsize name="Huge" value="\Huge"/>-<textsize name="small" value="\small"/>-<textsize name="footnote" value="\footnotesize"/>-<textsize name="tiny" value="\tiny"/>-<textstyle name="center" begin="\begin{center}" end="\end{center}"/>-<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/>-<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/>-<gridsize name="4 pts" value="4"/>-<gridsize name="8 pts (~3 mm)" value="8"/>-<gridsize name="16 pts (~6 mm)" value="16"/>-<gridsize name="32 pts (~12 mm)" value="32"/>-<gridsize name="10 pts (~3.5 mm)" value="10"/>-<gridsize name="20 pts (~7 mm)" value="20"/>-<gridsize name="14 pts (~5 mm)" value="14"/>-<gridsize name="28 pts (~10 mm)" value="28"/>-<gridsize name="56 pts (~20 mm)" value="56"/>-<anglesize name="90 deg" value="90"/>-<anglesize name="60 deg" value="60"/>-<anglesize name="45 deg" value="45"/>-<anglesize name="30 deg" value="30"/>-<anglesize name="22.5 deg" value="22.5"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="75%" value="0.75"/>-<tiling name="falling" angle="-60" step="4" width="1"/>-<tiling name="rising" angle="30" step="4" width="1"/>-</ipestyle>-<ipestyle name="frank">-<arrowsize name="normal" value="5"/>-<arrowsize name="large" value="8"/>-<arrowsize name="huge" value="10"/>-<arrowsize name="small" value="3"/>-<arrowsize name="tiny" value="1"/>-<dashstyle name="dashed" value="[2 2] 0"/>-<dashstyle name="dotted" value="[0.5 1] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<gridsize name="1 pts" value="1"/>-<gridsize name="2 pts" value="2"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="20%" value="0.2"/>-<opacity name="40%" value="0.4"/>-<opacity name="60%" value="0.6"/>-<opacity name="70%" value="0.7"/>-<opacity name="80%" value="0.8"/>-<opacity name="90%" value="0.9"/>-</ipestyle>-<page>-<layer name="alpha"/>-<view layers="alpha" active="alpha"/>-<path layer="alpha" stroke="red">-96 704 m-192 816 l-352 656 l-208 640 l-336 784 l-h-</path>-<path stroke="red">-144 592 m-336 624 l-320 544 l-240 624 l-h-</path>-<path stroke="blue">-80 624 m-32 576 l-128 576 l-h-</path>-<path stroke="blue">-64 784 m-32 752 l-32 688 l-64 672 l-144 672 l-96 688 l-80 768 l-128 768 l-112 800 l-h-</path>-</page>-</ipe>
− test/Algorithms/Geometry/LinearProgramming/manual.ipe
@@ -1,370 +0,0 @@-<?xml version="1.0"?>-<!DOCTYPE ipe SYSTEM "ipe.dtd">-<ipe version="70206" creator="Ipe 7.2.7">-<info created="D:20190310094400" modified="D:20190310110316"/>-<ipestyle name="basic">-<symbol name="arrow/arc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/farc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/ptarc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fptarc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="mark/circle(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</symbol>-<symbol name="mark/disk(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-</path>-</symbol>-<symbol name="mark/fdisk(sfx)" transformations="translations">-<group>-<path fill="sym-fill">-0.5 0 0 0.5 0 0 e-</path>-<path fill="sym-stroke" fillrule="eofill">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</group>-</symbol>-<symbol name="mark/box(sx)" transformations="translations">-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</symbol>-<symbol name="mark/square(sx)" transformations="translations">-<path fill="sym-stroke">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h-</path>-</symbol>-<symbol name="mark/fsquare(sfx)" transformations="translations">-<group>-<path fill="sym-fill">--0.5 -0.5 m-0.5 -0.5 l-0.5 0.5 l--0.5 0.5 l-h-</path>-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</group>-</symbol>-<symbol name="mark/cross(sx)" transformations="translations">-<group>-<path fill="sym-stroke">--0.43 -0.57 m-0.57 0.43 l-0.43 0.57 l--0.57 -0.43 l-h-</path>-<path fill="sym-stroke">--0.43 0.57 m-0.57 -0.43 l-0.43 -0.57 l--0.57 0.43 l-h-</path>-</group>-</symbol>-<symbol name="arrow/fnormal(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/pointed(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fpointed(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/linear(spx)">-<path stroke="sym-stroke" pen="sym-pen">--1 0.333 m-0 0 l--1 -0.333 l-</path>-</symbol>-<symbol name="arrow/fdouble(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/double(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<pen name="heavier" value="0.8"/>-<pen name="fat" value="1.2"/>-<pen name="ultrafat" value="2"/>-<symbolsize name="large" value="5"/>-<symbolsize name="small" value="2"/>-<symbolsize name="tiny" value="1.1"/>-<arrowsize name="large" value="10"/>-<arrowsize name="small" value="5"/>-<arrowsize name="tiny" value="3"/>-<color name="red" value="1 0 0"/>-<color name="green" value="0 1 0"/>-<color name="blue" value="0 0 1"/>-<color name="yellow" value="1 1 0"/>-<color name="orange" value="1 0.647 0"/>-<color name="gold" value="1 0.843 0"/>-<color name="purple" value="0.627 0.125 0.941"/>-<color name="gray" value="0.745"/>-<color name="brown" value="0.647 0.165 0.165"/>-<color name="navy" value="0 0 0.502"/>-<color name="pink" value="1 0.753 0.796"/>-<color name="seagreen" value="0.18 0.545 0.341"/>-<color name="turquoise" value="0.251 0.878 0.816"/>-<color name="violet" value="0.933 0.51 0.933"/>-<color name="darkblue" value="0 0 0.545"/>-<color name="darkcyan" value="0 0.545 0.545"/>-<color name="darkgray" value="0.663"/>-<color name="darkgreen" value="0 0.392 0"/>-<color name="darkmagenta" value="0.545 0 0.545"/>-<color name="darkorange" value="1 0.549 0"/>-<color name="darkred" value="0.545 0 0"/>-<color name="lightblue" value="0.678 0.847 0.902"/>-<color name="lightcyan" value="0.878 1 1"/>-<color name="lightgray" value="0.827"/>-<color name="lightgreen" value="0.565 0.933 0.565"/>-<color name="lightyellow" value="1 1 0.878"/>-<dashstyle name="dashed" value="[4] 0"/>-<dashstyle name="dotted" value="[1 3] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<textsize name="large" value="\large"/>-<textsize name="Large" value="\Large"/>-<textsize name="LARGE" value="\LARGE"/>-<textsize name="huge" value="\huge"/>-<textsize name="Huge" value="\Huge"/>-<textsize name="small" value="\small"/>-<textsize name="footnote" value="\footnotesize"/>-<textsize name="tiny" value="\tiny"/>-<textstyle name="center" begin="\begin{center}" end="\end{center}"/>-<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/>-<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/>-<gridsize name="4 pts" value="4"/>-<gridsize name="8 pts (~3 mm)" value="8"/>-<gridsize name="16 pts (~6 mm)" value="16"/>-<gridsize name="32 pts (~12 mm)" value="32"/>-<gridsize name="10 pts (~3.5 mm)" value="10"/>-<gridsize name="20 pts (~7 mm)" value="20"/>-<gridsize name="14 pts (~5 mm)" value="14"/>-<gridsize name="28 pts (~10 mm)" value="28"/>-<gridsize name="56 pts (~20 mm)" value="56"/>-<anglesize name="90 deg" value="90"/>-<anglesize name="60 deg" value="60"/>-<anglesize name="45 deg" value="45"/>-<anglesize name="30 deg" value="30"/>-<anglesize name="22.5 deg" value="22.5"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="75%" value="0.75"/>-<tiling name="falling" angle="-60" step="4" width="1"/>-<tiling name="rising" angle="30" step="4" width="1"/>-</ipestyle>-<ipestyle name="frank">-<arrowsize name="normal" value="5"/>-<arrowsize name="large" value="8"/>-<arrowsize name="huge" value="10"/>-<arrowsize name="small" value="3"/>-<arrowsize name="tiny" value="1"/>-<dashstyle name="dashed" value="[2 2] 0"/>-<dashstyle name="dotted" value="[0.5 1] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<gridsize name="1 pts" value="1"/>-<gridsize name="2 pts" value="2"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="20%" value="0.2"/>-<opacity name="40%" value="0.4"/>-<opacity name="60%" value="0.6"/>-<opacity name="70%" value="0.7"/>-<opacity name="80%" value="0.8"/>-<opacity name="90%" value="0.9"/>-</ipestyle>-<page>-<layer name="alpha"/>-<view layers="alpha" active="alpha"/>-<path layer="alpha" stroke="blue">-80 512 m-496 688 l-</path>-<path stroke="blue">-32 656 m-400 496 l-</path>-<path stroke="blue">-192 512 m-512 592 l-</path>-<path stroke="blue">-80 544 m-448 512 l-</path>-<path stroke="red">-48 608 m-400 768 l-</path>-<path stroke="red">-64 784 m-512 672 l-</path>-<path stroke="red">-64 720 m-560 688 l-</path>-<use name="mark/disk(sx)" pos="223.532 572.725" size="normal" stroke="black"/>-</page>-<page>-<layer name="alpha"/>-<view layers="alpha" active="alpha"/>-<path layer="alpha" matrix="2.40555 0 0 2.40555 -64.8028 -863.829" stroke="red">-46.1048 614.582 m-429.2 688.899 l-</path>-<path matrix="2.32631 0 0 2.32631 -54.964 -984.057" stroke="blue">-41.4414 741.953 m-423.596 711.062 l-</path>-<path stroke="red">-121.985 842 m-859.993 369.865 l-</path>-</page>-<page>-<layer name="alpha"/>-<view layers="alpha" active="alpha"/>-<path layer="alpha" stroke="red">-22.6642 533.532 m-595 760.836 l-</path>-<path stroke="red">-0 803.856 m-595 555.084 l-</path>-<path stroke="red">--29.9681 598.468 m-794.006 675.181 l-</path>-<path stroke="red">-411.476 809.515 m-595 311.453 l-</path>-<path stroke="red">-0 382.909 m-437.853 1024.64 l-</path>-<path stroke="blue">--16.5606 862.606 m-411.101 433.915 l-</path>-<path stroke="blue">-95.0642 522.237 m-857.65 676.395 l-</path>-<path stroke="blue">-277.375 460.704 m-642.518 950.636 l-</path>-<path stroke="blue">-450.168 450.864 m-868.112 754.196 l-</path>-<path stroke="blue">-28.6395 500.773 m-594.217 555.411 l-</path>-<use name="mark/disk(sx)" pos="284.74 560.58" size="normal" stroke="blue"/>-</page>-</ipe>
− test/Algorithms/Geometry/LowerEnvelope/manual.ipe
@@ -1,299 +0,0 @@-<?xml version="1.0"?>-<!DOCTYPE ipe SYSTEM "ipe.dtd">-<ipe version="70107" creator="Ipe 7.2.2">-<info created="D:20170715125328" modified="D:20170715125434"/>-<ipestyle name="basic">-<symbol name="arrow/arc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/farc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/ptarc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fptarc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="mark/circle(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</symbol>-<symbol name="mark/disk(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-</path>-</symbol>-<symbol name="mark/fdisk(sfx)" transformations="translations">-<group>-<path fill="sym-fill">-0.5 0 0 0.5 0 0 e-</path>-<path fill="sym-stroke" fillrule="eofill">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</group>-</symbol>-<symbol name="mark/box(sx)" transformations="translations">-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</symbol>-<symbol name="mark/square(sx)" transformations="translations">-<path fill="sym-stroke">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h-</path>-</symbol>-<symbol name="mark/fsquare(sfx)" transformations="translations">-<group>-<path fill="sym-fill">--0.5 -0.5 m-0.5 -0.5 l-0.5 0.5 l--0.5 0.5 l-h-</path>-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</group>-</symbol>-<symbol name="mark/cross(sx)" transformations="translations">-<group>-<path fill="sym-stroke">--0.43 -0.57 m-0.57 0.43 l-0.43 0.57 l--0.57 -0.43 l-h-</path>-<path fill="sym-stroke">--0.43 0.57 m-0.57 -0.43 l-0.43 -0.57 l--0.57 0.43 l-h-</path>-</group>-</symbol>-<symbol name="arrow/fnormal(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/pointed(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fpointed(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/linear(spx)">-<path stroke="sym-stroke" pen="sym-pen">--1 0.333 m-0 0 l--1 -0.333 l-</path>-</symbol>-<symbol name="arrow/fdouble(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/double(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<pen name="heavier" value="0.8"/>-<pen name="fat" value="1.2"/>-<pen name="ultrafat" value="2"/>-<symbolsize name="large" value="5"/>-<symbolsize name="small" value="2"/>-<symbolsize name="tiny" value="1.1"/>-<arrowsize name="large" value="10"/>-<arrowsize name="small" value="5"/>-<arrowsize name="tiny" value="3"/>-<color name="red" value="1 0 0"/>-<color name="green" value="0 1 0"/>-<color name="blue" value="0 0 1"/>-<color name="yellow" value="1 1 0"/>-<color name="orange" value="1 0.647 0"/>-<color name="gold" value="1 0.843 0"/>-<color name="purple" value="0.627 0.125 0.941"/>-<color name="gray" value="0.745"/>-<color name="brown" value="0.647 0.165 0.165"/>-<color name="navy" value="0 0 0.502"/>-<color name="pink" value="1 0.753 0.796"/>-<color name="seagreen" value="0.18 0.545 0.341"/>-<color name="turquoise" value="0.251 0.878 0.816"/>-<color name="violet" value="0.933 0.51 0.933"/>-<color name="darkblue" value="0 0 0.545"/>-<color name="darkcyan" value="0 0.545 0.545"/>-<color name="darkgray" value="0.663"/>-<color name="darkgreen" value="0 0.392 0"/>-<color name="darkmagenta" value="0.545 0 0.545"/>-<color name="darkorange" value="1 0.549 0"/>-<color name="darkred" value="0.545 0 0"/>-<color name="lightblue" value="0.678 0.847 0.902"/>-<color name="lightcyan" value="0.878 1 1"/>-<color name="lightgray" value="0.827"/>-<color name="lightgreen" value="0.565 0.933 0.565"/>-<color name="lightyellow" value="1 1 0.878"/>-<dashstyle name="dashed" value="[4] 0"/>-<dashstyle name="dotted" value="[1 3] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<textsize name="large" value="\large"/>-<textsize name="Large" value="\Large"/>-<textsize name="LARGE" value="\LARGE"/>-<textsize name="huge" value="\huge"/>-<textsize name="Huge" value="\Huge"/>-<textsize name="small" value="\small"/>-<textsize name="footnote" value="\footnotesize"/>-<textsize name="tiny" value="\tiny"/>-<textstyle name="center" begin="\begin{center}" end="\end{center}"/>-<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/>-<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/>-<gridsize name="4 pts" value="4"/>-<gridsize name="8 pts (~3 mm)" value="8"/>-<gridsize name="16 pts (~6 mm)" value="16"/>-<gridsize name="32 pts (~12 mm)" value="32"/>-<gridsize name="10 pts (~3.5 mm)" value="10"/>-<gridsize name="20 pts (~7 mm)" value="20"/>-<gridsize name="14 pts (~5 mm)" value="14"/>-<gridsize name="28 pts (~10 mm)" value="28"/>-<gridsize name="56 pts (~20 mm)" value="56"/>-<anglesize name="90 deg" value="90"/>-<anglesize name="60 deg" value="60"/>-<anglesize name="45 deg" value="45"/>-<anglesize name="30 deg" value="30"/>-<anglesize name="22.5 deg" value="22.5"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="75%" value="0.75"/>-<tiling name="falling" angle="-60" step="4" width="1"/>-<tiling name="rising" angle="30" step="4" width="1"/>-</ipestyle>-<ipestyle name="frank">-<arrowsize name="normal" value="5"/>-<arrowsize name="large" value="8"/>-<arrowsize name="huge" value="10"/>-<arrowsize name="small" value="3"/>-<arrowsize name="tiny" value="1"/>-<dashstyle name="dashed" value="[2 2] 0"/>-<dashstyle name="dotted" value="[0.5 1] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<gridsize name="1 pts" value="1"/>-<gridsize name="2 pts" value="2"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="20%" value="0.2"/>-<opacity name="40%" value="0.4"/>-<opacity name="60%" value="0.6"/>-<opacity name="70%" value="0.7"/>-<opacity name="80%" value="0.8"/>-<opacity name="90%" value="0.9"/>-</ipestyle>-<page>-<layer name="alpha"/>-<view layers="alpha" active="alpha"/>-<path layer="alpha" stroke="black">-80 496 m-304 624 l-</path>-<path stroke="black">-320 512 m-176 624 l-</path>-<path stroke="black">-80 560 m-368 560 l-</path>-<path stroke="black">-80 512 m-320 608 l-</path>-<use name="mark/disk(sx)" pos="173.333 549.333" size="normal" stroke="black"/>-<use name="mark/disk(sx)" pos="200 560" size="normal" stroke="black"/>-<use name="mark/disk(sx)" pos="258.286 560" size="normal" stroke="black"/>-</page>-</ipe>
− test/Algorithms/Geometry/PolygonTriangulation/monotone.ipe
@@ -1,364 +0,0 @@-<?xml version="1.0"?>-<!DOCTYPE ipe SYSTEM "ipe.dtd">-<ipe version="70107" creator="Ipe 7.2.2">-<info created="D:20170207224641" modified="D:20180526104709"/>-<ipestyle name="basic">-<symbol name="arrow/arc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/farc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/ptarc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fptarc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="mark/circle(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</symbol>-<symbol name="mark/disk(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-</path>-</symbol>-<symbol name="mark/fdisk(sfx)" transformations="translations">-<group>-<path fill="sym-fill">-0.5 0 0 0.5 0 0 e-</path>-<path fill="sym-stroke" fillrule="eofill">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</group>-</symbol>-<symbol name="mark/box(sx)" transformations="translations">-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</symbol>-<symbol name="mark/square(sx)" transformations="translations">-<path fill="sym-stroke">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h-</path>-</symbol>-<symbol name="mark/fsquare(sfx)" transformations="translations">-<group>-<path fill="sym-fill">--0.5 -0.5 m-0.5 -0.5 l-0.5 0.5 l--0.5 0.5 l-h-</path>-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</group>-</symbol>-<symbol name="mark/cross(sx)" transformations="translations">-<group>-<path fill="sym-stroke">--0.43 -0.57 m-0.57 0.43 l-0.43 0.57 l--0.57 -0.43 l-h-</path>-<path fill="sym-stroke">--0.43 0.57 m-0.57 -0.43 l-0.43 -0.57 l--0.57 0.43 l-h-</path>-</group>-</symbol>-<symbol name="arrow/fnormal(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/pointed(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fpointed(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/linear(spx)">-<path stroke="sym-stroke" pen="sym-pen">--1 0.333 m-0 0 l--1 -0.333 l-</path>-</symbol>-<symbol name="arrow/fdouble(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/double(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<pen name="heavier" value="0.8"/>-<pen name="fat" value="1.2"/>-<pen name="ultrafat" value="2"/>-<symbolsize name="large" value="5"/>-<symbolsize name="small" value="2"/>-<symbolsize name="tiny" value="1.1"/>-<arrowsize name="large" value="10"/>-<arrowsize name="small" value="5"/>-<arrowsize name="tiny" value="3"/>-<color name="red" value="1 0 0"/>-<color name="green" value="0 1 0"/>-<color name="blue" value="0 0 1"/>-<color name="yellow" value="1 1 0"/>-<color name="orange" value="1 0.647 0"/>-<color name="gold" value="1 0.843 0"/>-<color name="purple" value="0.627 0.125 0.941"/>-<color name="gray" value="0.745"/>-<color name="brown" value="0.647 0.165 0.165"/>-<color name="navy" value="0 0 0.502"/>-<color name="pink" value="1 0.753 0.796"/>-<color name="seagreen" value="0.18 0.545 0.341"/>-<color name="turquoise" value="0.251 0.878 0.816"/>-<color name="violet" value="0.933 0.51 0.933"/>-<color name="darkblue" value="0 0 0.545"/>-<color name="darkcyan" value="0 0.545 0.545"/>-<color name="darkgray" value="0.663"/>-<color name="darkgreen" value="0 0.392 0"/>-<color name="darkmagenta" value="0.545 0 0.545"/>-<color name="darkorange" value="1 0.549 0"/>-<color name="darkred" value="0.545 0 0"/>-<color name="lightblue" value="0.678 0.847 0.902"/>-<color name="lightcyan" value="0.878 1 1"/>-<color name="lightgray" value="0.827"/>-<color name="lightgreen" value="0.565 0.933 0.565"/>-<color name="lightyellow" value="1 1 0.878"/>-<dashstyle name="dashed" value="[4] 0"/>-<dashstyle name="dotted" value="[1 3] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<textsize name="large" value="\large"/>-<textsize name="Large" value="\Large"/>-<textsize name="LARGE" value="\LARGE"/>-<textsize name="huge" value="\huge"/>-<textsize name="Huge" value="\Huge"/>-<textsize name="small" value="\small"/>-<textsize name="footnote" value="\footnotesize"/>-<textsize name="tiny" value="\tiny"/>-<textstyle name="center" begin="\begin{center}" end="\end{center}"/>-<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/>-<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/>-<gridsize name="4 pts" value="4"/>-<gridsize name="8 pts (~3 mm)" value="8"/>-<gridsize name="16 pts (~6 mm)" value="16"/>-<gridsize name="32 pts (~12 mm)" value="32"/>-<gridsize name="10 pts (~3.5 mm)" value="10"/>-<gridsize name="20 pts (~7 mm)" value="20"/>-<gridsize name="14 pts (~5 mm)" value="14"/>-<gridsize name="28 pts (~10 mm)" value="28"/>-<gridsize name="56 pts (~20 mm)" value="56"/>-<anglesize name="90 deg" value="90"/>-<anglesize name="60 deg" value="60"/>-<anglesize name="45 deg" value="45"/>-<anglesize name="30 deg" value="30"/>-<anglesize name="22.5 deg" value="22.5"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="75%" value="0.75"/>-<tiling name="falling" angle="-60" step="4" width="1"/>-<tiling name="rising" angle="30" step="4" width="1"/>-</ipestyle>-<ipestyle name="frank">-<arrowsize name="normal" value="5"/>-<arrowsize name="large" value="8"/>-<arrowsize name="huge" value="10"/>-<arrowsize name="small" value="3"/>-<arrowsize name="tiny" value="1"/>-<dashstyle name="dashed" value="[2 2] 0"/>-<dashstyle name="dotted" value="[0.5 1] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<gridsize name="1 pts" value="1"/>-<gridsize name="2 pts" value="2"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="20%" value="0.2"/>-<opacity name="40%" value="0.4"/>-<opacity name="60%" value="0.6"/>-<opacity name="70%" value="0.7"/>-<opacity name="80%" value="0.8"/>-<opacity name="90%" value="0.9"/>-</ipestyle>-<page>-<layer name="alpha"/>-<view layers="alpha" active="alpha"/>-<path layer="alpha" stroke="black">-176 736 m-240 688 l-240 608 l-128 576 l-64 640 l-80 720 l-128 752 l-h-</path>-<path stroke="black">-240 608 m-64 640 l-</path>-<path stroke="black">-64 640 m-240 688 l-</path>-<path stroke="black">-240 688 m-80 720 l-</path>-<path stroke="black">-80 720 m-176 736 l-</path>-<path matrix="1 0 0 1 32 96" stroke="red">-160 384 m-352 384 l-128 176 l-224 320 l-48 400 l-h-</path>-<path stroke="red">-384 480 m-256 416 l-</path>-<path stroke="red">-256 416 m-192 480 l-</path>-<path matrix="1 0 0 1 208 320" stroke="green">-320 320 m-256 320 l-224 320 l-128 240 l-64 224 l-256 192 l-h-</path>-<path matrix="1 0 0 1 208 320" stroke="green">-256 320 m-128 240 l-</path>-<path matrix="1 0 0 1 208 320" stroke="green">-128 240 m-320 320 l-</path>-<path matrix="1 0 0 1 208 320" stroke="green">-256 192 m-128 240 l-</path>-<path matrix="-1 0 -0 -1 592 400" stroke="violet">-320 320 m-256 320 l-224 320 l-128 240 l-64 224 l-256 192 l-h-</path>-<path stroke="violet">-464 160 m-336 208 l-</path>-<path stroke="violet">-336 80 m-336 208 l-</path>-<path stroke="violet">-368 80 m-336 208 l-</path>-</page>-</ipe>
− test/Algorithms/Geometry/PolygonTriangulation/simplepolygon6.ipe
@@ -1,297 +0,0 @@-<?xml version="1.0"?>-<!DOCTYPE ipe SYSTEM "ipe.dtd">-<ipe version="70107" creator="Ipe 7.2.2">-<info created="D:20180524200410" modified="D:20180524221410"/>-<ipestyle name="basic">-<symbol name="arrow/arc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/farc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/ptarc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fptarc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="mark/circle(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</symbol>-<symbol name="mark/disk(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-</path>-</symbol>-<symbol name="mark/fdisk(sfx)" transformations="translations">-<group>-<path fill="sym-fill">-0.5 0 0 0.5 0 0 e-</path>-<path fill="sym-stroke" fillrule="eofill">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</group>-</symbol>-<symbol name="mark/box(sx)" transformations="translations">-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</symbol>-<symbol name="mark/square(sx)" transformations="translations">-<path fill="sym-stroke">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h-</path>-</symbol>-<symbol name="mark/fsquare(sfx)" transformations="translations">-<group>-<path fill="sym-fill">--0.5 -0.5 m-0.5 -0.5 l-0.5 0.5 l--0.5 0.5 l-h-</path>-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</group>-</symbol>-<symbol name="mark/cross(sx)" transformations="translations">-<group>-<path fill="sym-stroke">--0.43 -0.57 m-0.57 0.43 l-0.43 0.57 l--0.57 -0.43 l-h-</path>-<path fill="sym-stroke">--0.43 0.57 m-0.57 -0.43 l-0.43 -0.57 l--0.57 0.43 l-h-</path>-</group>-</symbol>-<symbol name="arrow/fnormal(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/pointed(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fpointed(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/linear(spx)">-<path stroke="sym-stroke" pen="sym-pen">--1 0.333 m-0 0 l--1 -0.333 l-</path>-</symbol>-<symbol name="arrow/fdouble(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/double(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<pen name="heavier" value="0.8"/>-<pen name="fat" value="1.2"/>-<pen name="ultrafat" value="2"/>-<symbolsize name="large" value="5"/>-<symbolsize name="small" value="2"/>-<symbolsize name="tiny" value="1.1"/>-<arrowsize name="large" value="10"/>-<arrowsize name="small" value="5"/>-<arrowsize name="tiny" value="3"/>-<color name="red" value="1 0 0"/>-<color name="green" value="0 1 0"/>-<color name="blue" value="0 0 1"/>-<color name="yellow" value="1 1 0"/>-<color name="orange" value="1 0.647 0"/>-<color name="gold" value="1 0.843 0"/>-<color name="purple" value="0.627 0.125 0.941"/>-<color name="gray" value="0.745"/>-<color name="brown" value="0.647 0.165 0.165"/>-<color name="navy" value="0 0 0.502"/>-<color name="pink" value="1 0.753 0.796"/>-<color name="seagreen" value="0.18 0.545 0.341"/>-<color name="turquoise" value="0.251 0.878 0.816"/>-<color name="violet" value="0.933 0.51 0.933"/>-<color name="darkblue" value="0 0 0.545"/>-<color name="darkcyan" value="0 0.545 0.545"/>-<color name="darkgray" value="0.663"/>-<color name="darkgreen" value="0 0.392 0"/>-<color name="darkmagenta" value="0.545 0 0.545"/>-<color name="darkorange" value="1 0.549 0"/>-<color name="darkred" value="0.545 0 0"/>-<color name="lightblue" value="0.678 0.847 0.902"/>-<color name="lightcyan" value="0.878 1 1"/>-<color name="lightgray" value="0.827"/>-<color name="lightgreen" value="0.565 0.933 0.565"/>-<color name="lightyellow" value="1 1 0.878"/>-<dashstyle name="dashed" value="[4] 0"/>-<dashstyle name="dotted" value="[1 3] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<textsize name="large" value="\large"/>-<textsize name="Large" value="\Large"/>-<textsize name="LARGE" value="\LARGE"/>-<textsize name="huge" value="\huge"/>-<textsize name="Huge" value="\Huge"/>-<textsize name="small" value="\small"/>-<textsize name="footnote" value="\footnotesize"/>-<textsize name="tiny" value="\tiny"/>-<textstyle name="center" begin="\begin{center}" end="\end{center}"/>-<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/>-<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/>-<gridsize name="4 pts" value="4"/>-<gridsize name="8 pts (~3 mm)" value="8"/>-<gridsize name="16 pts (~6 mm)" value="16"/>-<gridsize name="32 pts (~12 mm)" value="32"/>-<gridsize name="10 pts (~3.5 mm)" value="10"/>-<gridsize name="20 pts (~7 mm)" value="20"/>-<gridsize name="14 pts (~5 mm)" value="14"/>-<gridsize name="28 pts (~10 mm)" value="28"/>-<gridsize name="56 pts (~20 mm)" value="56"/>-<anglesize name="90 deg" value="90"/>-<anglesize name="60 deg" value="60"/>-<anglesize name="45 deg" value="45"/>-<anglesize name="30 deg" value="30"/>-<anglesize name="22.5 deg" value="22.5"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="75%" value="0.75"/>-<tiling name="falling" angle="-60" step="4" width="1"/>-<tiling name="rising" angle="30" step="4" width="1"/>-</ipestyle>-<ipestyle name="frank">-<arrowsize name="normal" value="5"/>-<arrowsize name="large" value="8"/>-<arrowsize name="huge" value="10"/>-<arrowsize name="small" value="3"/>-<arrowsize name="tiny" value="1"/>-<dashstyle name="dashed" value="[2 2] 0"/>-<dashstyle name="dotted" value="[0.5 1] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<gridsize name="1 pts" value="1"/>-<gridsize name="2 pts" value="2"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="20%" value="0.2"/>-<opacity name="40%" value="0.4"/>-<opacity name="60%" value="0.6"/>-<opacity name="70%" value="0.7"/>-<opacity name="80%" value="0.8"/>-<opacity name="90%" value="0.9"/>-</ipestyle>-<page>-<layer name="alpha"/>-<layer name="beta"/>-<view layers="alpha beta" active="beta"/>-<path layer="alpha" stroke="black">-80 544 m-320 527 l-208 496 l-48 432 l-16 560 l-h-</path>-<path stroke="black">-80 544 m-208 496 l-</path>-<path layer="beta" stroke="black">-208 496 m-16 560 l-</path>-</page>-</ipe>
− test/Algorithms/Geometry/RedBlueSeparator/manual.ipe
@@ -1,355 +0,0 @@-<?xml version="1.0"?>-<!DOCTYPE ipe SYSTEM "ipe.dtd">-<ipe version="70206" creator="Ipe 7.2.7">-<info created="D:20190316210045" modified="D:20190316234250"/>-<ipestyle name="basic">-<symbol name="arrow/arc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/farc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/ptarc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fptarc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="mark/circle(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</symbol>-<symbol name="mark/disk(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-</path>-</symbol>-<symbol name="mark/fdisk(sfx)" transformations="translations">-<group>-<path fill="sym-fill">-0.5 0 0 0.5 0 0 e-</path>-<path fill="sym-stroke" fillrule="eofill">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</group>-</symbol>-<symbol name="mark/box(sx)" transformations="translations">-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</symbol>-<symbol name="mark/square(sx)" transformations="translations">-<path fill="sym-stroke">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h-</path>-</symbol>-<symbol name="mark/fsquare(sfx)" transformations="translations">-<group>-<path fill="sym-fill">--0.5 -0.5 m-0.5 -0.5 l-0.5 0.5 l--0.5 0.5 l-h-</path>-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</group>-</symbol>-<symbol name="mark/cross(sx)" transformations="translations">-<group>-<path fill="sym-stroke">--0.43 -0.57 m-0.57 0.43 l-0.43 0.57 l--0.57 -0.43 l-h-</path>-<path fill="sym-stroke">--0.43 0.57 m-0.57 -0.43 l-0.43 -0.57 l--0.57 0.43 l-h-</path>-</group>-</symbol>-<symbol name="arrow/fnormal(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/pointed(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fpointed(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/linear(spx)">-<path stroke="sym-stroke" pen="sym-pen">--1 0.333 m-0 0 l--1 -0.333 l-</path>-</symbol>-<symbol name="arrow/fdouble(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/double(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<pen name="heavier" value="0.8"/>-<pen name="fat" value="1.2"/>-<pen name="ultrafat" value="2"/>-<symbolsize name="large" value="5"/>-<symbolsize name="small" value="2"/>-<symbolsize name="tiny" value="1.1"/>-<arrowsize name="large" value="10"/>-<arrowsize name="small" value="5"/>-<arrowsize name="tiny" value="3"/>-<color name="red" value="1 0 0"/>-<color name="green" value="0 1 0"/>-<color name="blue" value="0 0 1"/>-<color name="yellow" value="1 1 0"/>-<color name="orange" value="1 0.647 0"/>-<color name="gold" value="1 0.843 0"/>-<color name="purple" value="0.627 0.125 0.941"/>-<color name="gray" value="0.745"/>-<color name="brown" value="0.647 0.165 0.165"/>-<color name="navy" value="0 0 0.502"/>-<color name="pink" value="1 0.753 0.796"/>-<color name="seagreen" value="0.18 0.545 0.341"/>-<color name="turquoise" value="0.251 0.878 0.816"/>-<color name="violet" value="0.933 0.51 0.933"/>-<color name="darkblue" value="0 0 0.545"/>-<color name="darkcyan" value="0 0.545 0.545"/>-<color name="darkgray" value="0.663"/>-<color name="darkgreen" value="0 0.392 0"/>-<color name="darkmagenta" value="0.545 0 0.545"/>-<color name="darkorange" value="1 0.549 0"/>-<color name="darkred" value="0.545 0 0"/>-<color name="lightblue" value="0.678 0.847 0.902"/>-<color name="lightcyan" value="0.878 1 1"/>-<color name="lightgray" value="0.827"/>-<color name="lightgreen" value="0.565 0.933 0.565"/>-<color name="lightyellow" value="1 1 0.878"/>-<dashstyle name="dashed" value="[4] 0"/>-<dashstyle name="dotted" value="[1 3] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<textsize name="large" value="\large"/>-<textsize name="Large" value="\Large"/>-<textsize name="LARGE" value="\LARGE"/>-<textsize name="huge" value="\huge"/>-<textsize name="Huge" value="\Huge"/>-<textsize name="small" value="\small"/>-<textsize name="footnote" value="\footnotesize"/>-<textsize name="tiny" value="\tiny"/>-<textstyle name="center" begin="\begin{center}" end="\end{center}"/>-<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/>-<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/>-<gridsize name="4 pts" value="4"/>-<gridsize name="8 pts (~3 mm)" value="8"/>-<gridsize name="16 pts (~6 mm)" value="16"/>-<gridsize name="32 pts (~12 mm)" value="32"/>-<gridsize name="10 pts (~3.5 mm)" value="10"/>-<gridsize name="20 pts (~7 mm)" value="20"/>-<gridsize name="14 pts (~5 mm)" value="14"/>-<gridsize name="28 pts (~10 mm)" value="28"/>-<gridsize name="56 pts (~20 mm)" value="56"/>-<anglesize name="90 deg" value="90"/>-<anglesize name="60 deg" value="60"/>-<anglesize name="45 deg" value="45"/>-<anglesize name="30 deg" value="30"/>-<anglesize name="22.5 deg" value="22.5"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="75%" value="0.75"/>-<tiling name="falling" angle="-60" step="4" width="1"/>-<tiling name="rising" angle="30" step="4" width="1"/>-</ipestyle>-<ipestyle name="frank">-<arrowsize name="normal" value="5"/>-<arrowsize name="large" value="8"/>-<arrowsize name="huge" value="10"/>-<arrowsize name="small" value="3"/>-<arrowsize name="tiny" value="1"/>-<dashstyle name="dashed" value="[2 2] 0"/>-<dashstyle name="dotted" value="[0.5 1] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<gridsize name="1 pts" value="1"/>-<gridsize name="2 pts" value="2"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="20%" value="0.2"/>-<opacity name="40%" value="0.4"/>-<opacity name="60%" value="0.6"/>-<opacity name="70%" value="0.7"/>-<opacity name="80%" value="0.8"/>-<opacity name="90%" value="0.9"/>-</ipestyle>-<page>-<layer name="alpha"/>-<view layers="alpha" active="alpha"/>-<use layer="alpha" name="mark/disk(sx)" pos="176 688" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="224 672" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="160 656" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="208 624" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="400 688" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="448 640" size="normal" stroke="red"/>-<use matrix="1 0 0 1 -43.6079 16.3284" name="mark/disk(sx)" pos="400 640" size="normal" stroke="red"/>-</page>-<page>-<layer name="alpha"/>-<view layers="alpha" active="alpha"/>-<use layer="alpha" name="mark/disk(sx)" pos="208 608" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="224 640" size="normal" stroke="red"/>-<use matrix="1 0 0 1 0 1.54293" name="mark/disk(sx)" pos="288 608" size="normal" stroke="red"/>-<use matrix="1 0 0 1 21.7014 -12.8726" name="mark/disk(sx)" pos="224 608" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="336 640" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="320 688" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="64 704" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="160 656" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="160 752" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="112 672" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="112 592" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="288 736" size="normal" stroke="blue"/>-</page>-<page>-<layer name="alpha"/>-<view layers="alpha" active="alpha"/>-<use layer="alpha" name="mark/cross(sx)" pos="137.831 733.813" size="normal" stroke="blue"/>-<use name="mark/cross(sx)" pos="230.904 721.563" size="normal" stroke="blue"/>-<use name="mark/cross(sx)" pos="150.255 624.633" size="normal" stroke="blue"/>-<use name="mark/cross(sx)" pos="85.1135 709.576" size="normal" stroke="blue"/>-<use name="mark/cross(sx)" pos="160.209 702.601" size="normal" stroke="blue"/>-<use name="mark/cross(sx)" pos="160.209 702.601" size="normal" stroke="blue"/>-<use name="mark/cross(sx)" pos="170.678 688.287" size="normal" stroke="blue"/>-<use name="mark/cross(sx)" pos="251.915 645.615" size="normal" stroke="blue"/>-<use name="mark/cross(sx)" pos="55.337 684.816" size="normal" stroke="blue"/>-<use name="mark/cross(sx)" pos="129.955 678.571" size="normal" stroke="blue"/>-<use name="mark/cross(sx)" pos="130.15 631.793" size="normal" stroke="blue"/>-<use name="mark/cross(sx)" pos="249.435 558.192" size="normal" stroke="blue"/>-<use name="mark/cross(sx)" pos="324.114 668.358" size="normal" stroke="blue"/>-<use name="mark/cross(sx)" pos="324.114 668.358" size="normal" stroke="blue"/>-<use name="mark/cross(sx)" pos="361.354 661.106" size="normal" stroke="blue"/>-<use name="mark/cross(sx)" pos="189.131 599.845" size="normal" stroke="blue"/>-<use name="mark/cross(sx)" pos="143.375 537.289" size="normal" stroke="blue"/>-<use name="mark/cross(sx)" pos="359.679 584.286" size="normal" stroke="red"/>-<use name="mark/cross(sx)" pos="396.828 627.4" size="normal" stroke="red"/>-<use name="mark/cross(sx)" pos="417.481 668.711" size="normal" stroke="red"/>-<use name="mark/cross(sx)" pos="467.45 598.096" size="normal" stroke="red"/>-<use matrix="1 0 0 1 -93.524 0.18083" name="mark/cross(sx)" pos="326.916 608.601" size="normal" stroke="red"/>-<use name="mark/cross(sx)" pos="338.842 545.777" size="normal" stroke="red"/>-<use name="mark/cross(sx)" pos="432.668 573.584" size="normal" stroke="red"/>-<use name="mark/cross(sx)" pos="357.521 629.624" size="normal" stroke="red"/>-<use name="mark/cross(sx)" pos="309.927 526.735" size="normal" stroke="red"/>-<use name="mark/cross(sx)" pos="417.855 527.98" size="normal" stroke="red"/>-<use name="mark/cross(sx)" pos="425.293 604.935" size="normal" stroke="red"/>-<use name="mark/cross(sx)" pos="482.54 641.657" size="normal" stroke="red"/>-</page>-<page>-<layer name="alpha"/>-<view layers="alpha" active="alpha"/>-<use layer="alpha" name="mark/disk(sx)" pos="71.295 646.994" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="124.003 669.524" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="158.117 593.965" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="106.985 602.699" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="130.801 646.957" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="183.976 638.614" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="268.842 528.238" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="303.35 581.718" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="353.58 636.762" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="288.989 633.613" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="325.76 613.53" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="337.81 597.8" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="245.135 580.319" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="168.779 515.317" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="116.706 526.039" size="normal" stroke="blue"/>-</page>-</ipe>
− test/Algorithms/Geometry/SmallestEnclosingDisk/manual.ipe
@@ -1,369 +0,0 @@-<?xml version="1.0"?>-<!DOCTYPE ipe SYSTEM "ipe.dtd">-<ipe version="70107" creator="Ipe 7.1.7">-<info created="D:20151023214323" modified="D:20160123195914"/>-<ipestyle name="basic">-<symbol name="arrow/arc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/farc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/ptarc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fptarc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="mark/circle(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</symbol>-<symbol name="mark/disk(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-</path>-</symbol>-<symbol name="mark/fdisk(sfx)" transformations="translations">-<group>-<path fill="sym-fill">-0.5 0 0 0.5 0 0 e-</path>-<path fill="sym-stroke" fillrule="eofill">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</group>-</symbol>-<symbol name="mark/box(sx)" transformations="translations">-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</symbol>-<symbol name="mark/square(sx)" transformations="translations">-<path fill="sym-stroke">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h-</path>-</symbol>-<symbol name="mark/fsquare(sfx)" transformations="translations">-<group>-<path fill="sym-fill">--0.5 -0.5 m-0.5 -0.5 l-0.5 0.5 l--0.5 0.5 l-h-</path>-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</group>-</symbol>-<symbol name="mark/cross(sx)" transformations="translations">-<group>-<path fill="sym-stroke">--0.43 -0.57 m-0.57 0.43 l-0.43 0.57 l--0.57 -0.43 l-h-</path>-<path fill="sym-stroke">--0.43 0.57 m-0.57 -0.43 l-0.43 -0.57 l--0.57 0.43 l-h-</path>-</group>-</symbol>-<symbol name="arrow/fnormal(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/pointed(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fpointed(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/linear(spx)">-<path stroke="sym-stroke" pen="sym-pen">--1 0.333 m-0 0 l--1 -0.333 l-</path>-</symbol>-<symbol name="arrow/fdouble(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/double(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<pen name="heavier" value="0.8"/>-<pen name="fat" value="1.2"/>-<pen name="ultrafat" value="2"/>-<symbolsize name="large" value="5"/>-<symbolsize name="small" value="2"/>-<symbolsize name="tiny" value="1.1"/>-<arrowsize name="large" value="10"/>-<arrowsize name="small" value="5"/>-<arrowsize name="tiny" value="3"/>-<color name="red" value="1 0 0"/>-<color name="green" value="0 1 0"/>-<color name="blue" value="0 0 1"/>-<color name="yellow" value="1 1 0"/>-<color name="orange" value="1 0.647 0"/>-<color name="gold" value="1 0.843 0"/>-<color name="purple" value="0.627 0.125 0.941"/>-<color name="gray" value="0.745"/>-<color name="brown" value="0.647 0.165 0.165"/>-<color name="navy" value="0 0 0.502"/>-<color name="pink" value="1 0.753 0.796"/>-<color name="seagreen" value="0.18 0.545 0.341"/>-<color name="turquoise" value="0.251 0.878 0.816"/>-<color name="violet" value="0.933 0.51 0.933"/>-<color name="darkblue" value="0 0 0.545"/>-<color name="darkcyan" value="0 0.545 0.545"/>-<color name="darkgray" value="0.663"/>-<color name="darkgreen" value="0 0.392 0"/>-<color name="darkmagenta" value="0.545 0 0.545"/>-<color name="darkorange" value="1 0.549 0"/>-<color name="darkred" value="0.545 0 0"/>-<color name="lightblue" value="0.678 0.847 0.902"/>-<color name="lightcyan" value="0.878 1 1"/>-<color name="lightgray" value="0.827"/>-<color name="lightgreen" value="0.565 0.933 0.565"/>-<color name="lightyellow" value="1 1 0.878"/>-<dashstyle name="dashed" value="[4] 0"/>-<dashstyle name="dotted" value="[1 3] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<textsize name="large" value="\large"/>-<textsize name="small" value="\small"/>-<textsize name="tiny" value="\tiny"/>-<textsize name="Large" value="\Large"/>-<textsize name="LARGE" value="\LARGE"/>-<textsize name="huge" value="\huge"/>-<textsize name="Huge" value="\Huge"/>-<textsize name="footnote" value="\footnotesize"/>-<textstyle name="center" begin="\begin{center}" end="\end{center}"/>-<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/>-<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/>-<gridsize name="4 pts" value="4"/>-<gridsize name="8 pts (~3 mm)" value="8"/>-<gridsize name="16 pts (~6 mm)" value="16"/>-<gridsize name="32 pts (~12 mm)" value="32"/>-<gridsize name="10 pts (~3.5 mm)" value="10"/>-<gridsize name="20 pts (~7 mm)" value="20"/>-<gridsize name="14 pts (~5 mm)" value="14"/>-<gridsize name="28 pts (~10 mm)" value="28"/>-<gridsize name="56 pts (~20 mm)" value="56"/>-<anglesize name="90 deg" value="90"/>-<anglesize name="60 deg" value="60"/>-<anglesize name="45 deg" value="45"/>-<anglesize name="30 deg" value="30"/>-<anglesize name="22.5 deg" value="22.5"/>-<tiling name="falling" angle="-60" step="4" width="1"/>-<tiling name="rising" angle="30" step="4" width="1"/>-</ipestyle>-<ipestyle name="frank">-<arrowsize name="normal" value="5"/>-<arrowsize name="large" value="8"/>-<arrowsize name="small" value="3"/>-<arrowsize name="tiny" value="1"/>-<arrowsize name="huge" value="10"/>-<dashstyle name="dashed" value="[2 2] 0"/>-<dashstyle name="dotted" value="[0.5 1] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<gridsize name="1 pts" value="1"/>-<gridsize name="2 pts" value="2"/>-<opacity name="10%" value="0.1"/>-<opacity name="20%" value="0.2"/>-<opacity name="30%" value="0.3"/>-<opacity name="40%" value="0.4"/>-<opacity name="50%" value="0.5"/>-<opacity name="60%" value="0.6"/>-<opacity name="70%" value="0.7"/>-<opacity name="80%" value="0.8"/>-<opacity name="90%" value="0.9"/>-</ipestyle>-<page>-<layer name="alpha"/>-<layer name="beta"/>-<layer name="gamma"/>-<layer name="delta"/>-<view layers="alpha beta gamma delta" active="delta"/>-<use layer="alpha" name="mark/cross(sx)" pos="112 736" size="normal" stroke="black"/>-<use name="mark/cross(sx)" pos="336 640" size="normal" stroke="black"/>-<use name="mark/cross(sx)" pos="336 816" size="normal" stroke="black"/>-<use matrix="1 0 0 1 6.13784 -3.9059" name="mark/disk(sx)" pos="304 784" size="normal" stroke="black"/>-<use matrix="1 0 0 1 4.44908 -4.21815" name="mark/disk(sx)" pos="320 736" size="normal" stroke="black"/>-<use matrix="1 0 0 1 0.586099 6.64103" name="mark/disk(sx)" pos="352 736" size="normal" stroke="black"/>-<path stroke="black">-129.39 0 0 129.39 241.143 728 e-</path>-<use name="mark/disk(sx)" pos="264.671 689.288" size="normal" stroke="black"/>-<use name="mark/disk(sx)" pos="222.264 731.137" size="normal" stroke="black"/>-<use name="mark/disk(sx)" pos="289.78 722.767" size="normal" stroke="black"/>-<use name="mark/disk(sx)" pos="215.01 683.15" size="normal" stroke="black"/>-<use name="mark/disk(sx)" pos="197.154 741.181" size="normal" stroke="black"/>-<use name="mark/disk(sx)" pos="194.365 766.848" size="normal" stroke="black"/>-<use name="mark/disk(sx)" pos="230.076 767.406" size="normal" stroke="black"/>-<use name="mark/disk(sx)" pos="155.864 708.26" size="normal" stroke="black"/>-<use name="mark/disk(sx)" pos="238.445 743.413" size="normal" stroke="black"/>-<use name="mark/disk(sx)" pos="280.294 767.964" size="normal" stroke="black"/>-<use name="mark/disk(sx)" pos="274.715 659.157" size="normal" stroke="black"/>-<use name="mark/disk(sx)" pos="221.148 609.496" size="normal" stroke="black"/>-<use name="mark/disk(sx)" pos="310.984 638.512" size="normal" stroke="black"/>-<use name="mark/disk(sx)" pos="223.38 649.671" size="normal" stroke="black"/>-<use name="mark/disk(sx)" pos="169.813 689.846" size="normal" stroke="black"/>-<use name="mark/disk(sx)" pos="201.618 709.934" size="normal" stroke="black"/>-<use layer="beta" name="mark/disk(sx)" pos="368 608" size="normal" stroke="red"/>-<use matrix="1 0 0 1 2.78993 -9.48576" name="mark/disk(sx)" pos="336 608" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="352 592" size="normal" stroke="red"/>-<use matrix="1 0 0 1 10.0437 -7.8118" name="mark/disk(sx)" pos="368 576" size="normal" stroke="red"/>-<use matrix="1 0 0 1 2.78993 1.11597" name="mark/disk(sx)" pos="320 576" size="normal" stroke="red"/>-<use matrix="1 0 0 1 -8.36979 -11.1597" name="mark/disk(sx)" pos="352 576" size="normal" stroke="red"/>-<use matrix="1 0 0 1 30.6892 -15.0656" name="mark/disk(sx)" pos="400 608" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="400 576" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="304 560" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="368 544" size="normal" stroke="red"/>-<use name="mark/cross(sx)" pos="288.737 510.098" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="379.689 503.96" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="409.82 541.903" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="363.507 541.345" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="437.719 527.954" size="normal" stroke="red"/>-<use name="mark/cross(sx)" pos="476.778 591.564" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="403.682 564.223" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="409.82 562.549" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="414.284 563.107" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="462.271 545.809" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="450.553 567.013" size="normal" stroke="red"/>-<use name="mark/disk(sx)" pos="343.978 524.606" size="normal" stroke="red"/>-<path stroke="red">-102.465 0 0 102.465 382.758 550.831 e-</path>-<use layer="gamma" matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="368 608" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 9.39047 -5.35868" name="mark/disk(sx)" pos="336 608" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="352 592" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 16.6443 -3.68472" name="mark/disk(sx)" pos="368 576" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 9.39047 5.24305" name="mark/disk(sx)" pos="320 576" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 -1.76925 -7.03264" name="mark/disk(sx)" pos="352 576" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 37.2898 -10.9385" name="mark/disk(sx)" pos="400 608" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="400 576" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="304 560" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="368 544" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="379.689 503.96" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="409.82 541.903" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="363.507 541.345" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="437.719 527.954" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="403.682 564.223" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="409.82 562.549" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="414.284 563.107" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="462.271 545.809" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="450.553 567.013" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="343.978 524.606" size="normal" stroke="blue"/>-<use layer="delta" name="mark/disk(sx)" pos="71.4684 472.509" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="90.3283 458.001" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="75.0953 453.649" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="101.209 426.084" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="45.3547 421.732" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="76.5461 421.007" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="56.9608 430.437" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="85.2506 433.338" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="113.541 435.514" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="75.0953 453.649" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="46.8054 454.374" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 39.896 -3.62691" name="mark/disk(sx)" pos="75.0953 453.649" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="105.561 473.96" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="107.012 458.727" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="135.302 418.105" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 4.52803 -3.42357" name="mark/disk(sx)" pos="101.209 426.084" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="90.2821 422.711" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="118.434 461.101" size="normal" stroke="blue"/>-<use matrix="1 0 0 1 5.59154 0.698943" name="mark/disk(sx)" pos="57.5231 446.257" size="normal" stroke="blue"/>-<use name="mark/disk(sx)" pos="68.9116 432.574" size="normal" stroke="blue"/>-</page>-</ipe>
− test/Data/Geometry/Polygon/Convex/convexTests.ipe
@@ -1,308 +0,0 @@-<?xml version="1.0"?>-<!DOCTYPE ipe SYSTEM "ipe.dtd">-<ipe version="70107" creator="Ipe 7.2.2">-<info created="D:20160608222645" modified="D:20160611094216"/>-<ipestyle name="basic">-<symbol name="arrow/arc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/farc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/ptarc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fptarc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="mark/circle(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</symbol>-<symbol name="mark/disk(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-</path>-</symbol>-<symbol name="mark/fdisk(sfx)" transformations="translations">-<group>-<path fill="sym-fill">-0.5 0 0 0.5 0 0 e-</path>-<path fill="sym-stroke" fillrule="eofill">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</group>-</symbol>-<symbol name="mark/box(sx)" transformations="translations">-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</symbol>-<symbol name="mark/square(sx)" transformations="translations">-<path fill="sym-stroke">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h-</path>-</symbol>-<symbol name="mark/fsquare(sfx)" transformations="translations">-<group>-<path fill="sym-fill">--0.5 -0.5 m-0.5 -0.5 l-0.5 0.5 l--0.5 0.5 l-h-</path>-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</group>-</symbol>-<symbol name="mark/cross(sx)" transformations="translations">-<group>-<path fill="sym-stroke">--0.43 -0.57 m-0.57 0.43 l-0.43 0.57 l--0.57 -0.43 l-h-</path>-<path fill="sym-stroke">--0.43 0.57 m-0.57 -0.43 l-0.43 -0.57 l--0.57 0.43 l-h-</path>-</group>-</symbol>-<symbol name="arrow/fnormal(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/pointed(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fpointed(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/linear(spx)">-<path stroke="sym-stroke" pen="sym-pen">--1 0.333 m-0 0 l--1 -0.333 l-</path>-</symbol>-<symbol name="arrow/fdouble(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/double(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<pen name="heavier" value="0.8"/>-<pen name="fat" value="1.2"/>-<pen name="ultrafat" value="2"/>-<symbolsize name="large" value="5"/>-<symbolsize name="small" value="2"/>-<symbolsize name="tiny" value="1.1"/>-<arrowsize name="large" value="10"/>-<arrowsize name="small" value="5"/>-<arrowsize name="tiny" value="3"/>-<color name="red" value="1 0 0"/>-<color name="green" value="0 1 0"/>-<color name="blue" value="0 0 1"/>-<color name="yellow" value="1 1 0"/>-<color name="orange" value="1 0.647 0"/>-<color name="gold" value="1 0.843 0"/>-<color name="purple" value="0.627 0.125 0.941"/>-<color name="gray" value="0.745"/>-<color name="brown" value="0.647 0.165 0.165"/>-<color name="navy" value="0 0 0.502"/>-<color name="pink" value="1 0.753 0.796"/>-<color name="seagreen" value="0.18 0.545 0.341"/>-<color name="turquoise" value="0.251 0.878 0.816"/>-<color name="violet" value="0.933 0.51 0.933"/>-<color name="darkblue" value="0 0 0.545"/>-<color name="darkcyan" value="0 0.545 0.545"/>-<color name="darkgray" value="0.663"/>-<color name="darkgreen" value="0 0.392 0"/>-<color name="darkmagenta" value="0.545 0 0.545"/>-<color name="darkorange" value="1 0.549 0"/>-<color name="darkred" value="0.545 0 0"/>-<color name="lightblue" value="0.678 0.847 0.902"/>-<color name="lightcyan" value="0.878 1 1"/>-<color name="lightgray" value="0.827"/>-<color name="lightgreen" value="0.565 0.933 0.565"/>-<color name="lightyellow" value="1 1 0.878"/>-<dashstyle name="dashed" value="[4] 0"/>-<dashstyle name="dotted" value="[1 3] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<textsize name="large" value="\large"/>-<textsize name="Large" value="\Large"/>-<textsize name="LARGE" value="\LARGE"/>-<textsize name="huge" value="\huge"/>-<textsize name="Huge" value="\Huge"/>-<textsize name="small" value="\small"/>-<textsize name="footnote" value="\footnotesize"/>-<textsize name="tiny" value="\tiny"/>-<textstyle name="center" begin="\begin{center}" end="\end{center}"/>-<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/>-<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/>-<gridsize name="4 pts" value="4"/>-<gridsize name="8 pts (~3 mm)" value="8"/>-<gridsize name="16 pts (~6 mm)" value="16"/>-<gridsize name="32 pts (~12 mm)" value="32"/>-<gridsize name="10 pts (~3.5 mm)" value="10"/>-<gridsize name="20 pts (~7 mm)" value="20"/>-<gridsize name="14 pts (~5 mm)" value="14"/>-<gridsize name="28 pts (~10 mm)" value="28"/>-<gridsize name="56 pts (~20 mm)" value="56"/>-<anglesize name="90 deg" value="90"/>-<anglesize name="60 deg" value="60"/>-<anglesize name="45 deg" value="45"/>-<anglesize name="30 deg" value="30"/>-<anglesize name="22.5 deg" value="22.5"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="75%" value="0.75"/>-<tiling name="falling" angle="-60" step="4" width="1"/>-<tiling name="rising" angle="30" step="4" width="1"/>-</ipestyle>-<ipestyle name="frank">-<arrowsize name="normal" value="5"/>-<arrowsize name="large" value="8"/>-<arrowsize name="huge" value="10"/>-<arrowsize name="small" value="3"/>-<arrowsize name="tiny" value="1"/>-<dashstyle name="dashed" value="[2 2] 0"/>-<dashstyle name="dotted" value="[0.5 1] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<gridsize name="1 pts" value="1"/>-<gridsize name="2 pts" value="2"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="20%" value="0.2"/>-<opacity name="40%" value="0.4"/>-<opacity name="60%" value="0.6"/>-<opacity name="70%" value="0.7"/>-<opacity name="80%" value="0.8"/>-<opacity name="90%" value="0.9"/>-</ipestyle>-<page>-<layer name="alpha"/>-<layer name="beta"/>-<view layers="alpha beta" active="beta"/>-<path layer="alpha" stroke="black">-144 720 m-224 768 l-368 672 l-320 592 l-240 544 l-112 528 l-96 640 l-h-</path>-<path stroke="black">-224 656 m-352 784 l-416 816 l-544 800 l-576 752 l-592 720 l-592 672 l-576 640 l-544 608 l-480 560 l-400 544 l-320 544 l-272 560 l-240 608 l-h-</path>-</page>-</ipe>
− test/Data/Geometry/Polygon/star_shaped.ipe
@@ -1,339 +0,0 @@-<?xml version="1.0"?>-<!DOCTYPE ipe SYSTEM "ipe.dtd">-<ipe version="70206" creator="Ipe 7.2.7">-<info created="D:20190310121058" modified="D:20190310122053"/>-<ipestyle name="basic">-<symbol name="arrow/arc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/farc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/ptarc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fptarc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="mark/circle(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</symbol>-<symbol name="mark/disk(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-</path>-</symbol>-<symbol name="mark/fdisk(sfx)" transformations="translations">-<group>-<path fill="sym-fill">-0.5 0 0 0.5 0 0 e-</path>-<path fill="sym-stroke" fillrule="eofill">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</group>-</symbol>-<symbol name="mark/box(sx)" transformations="translations">-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</symbol>-<symbol name="mark/square(sx)" transformations="translations">-<path fill="sym-stroke">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h-</path>-</symbol>-<symbol name="mark/fsquare(sfx)" transformations="translations">-<group>-<path fill="sym-fill">--0.5 -0.5 m-0.5 -0.5 l-0.5 0.5 l--0.5 0.5 l-h-</path>-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</group>-</symbol>-<symbol name="mark/cross(sx)" transformations="translations">-<group>-<path fill="sym-stroke">--0.43 -0.57 m-0.57 0.43 l-0.43 0.57 l--0.57 -0.43 l-h-</path>-<path fill="sym-stroke">--0.43 0.57 m-0.57 -0.43 l-0.43 -0.57 l--0.57 0.43 l-h-</path>-</group>-</symbol>-<symbol name="arrow/fnormal(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/pointed(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fpointed(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/linear(spx)">-<path stroke="sym-stroke" pen="sym-pen">--1 0.333 m-0 0 l--1 -0.333 l-</path>-</symbol>-<symbol name="arrow/fdouble(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/double(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<pen name="heavier" value="0.8"/>-<pen name="fat" value="1.2"/>-<pen name="ultrafat" value="2"/>-<symbolsize name="large" value="5"/>-<symbolsize name="small" value="2"/>-<symbolsize name="tiny" value="1.1"/>-<arrowsize name="large" value="10"/>-<arrowsize name="small" value="5"/>-<arrowsize name="tiny" value="3"/>-<color name="red" value="1 0 0"/>-<color name="green" value="0 1 0"/>-<color name="blue" value="0 0 1"/>-<color name="yellow" value="1 1 0"/>-<color name="orange" value="1 0.647 0"/>-<color name="gold" value="1 0.843 0"/>-<color name="purple" value="0.627 0.125 0.941"/>-<color name="gray" value="0.745"/>-<color name="brown" value="0.647 0.165 0.165"/>-<color name="navy" value="0 0 0.502"/>-<color name="pink" value="1 0.753 0.796"/>-<color name="seagreen" value="0.18 0.545 0.341"/>-<color name="turquoise" value="0.251 0.878 0.816"/>-<color name="violet" value="0.933 0.51 0.933"/>-<color name="darkblue" value="0 0 0.545"/>-<color name="darkcyan" value="0 0.545 0.545"/>-<color name="darkgray" value="0.663"/>-<color name="darkgreen" value="0 0.392 0"/>-<color name="darkmagenta" value="0.545 0 0.545"/>-<color name="darkorange" value="1 0.549 0"/>-<color name="darkred" value="0.545 0 0"/>-<color name="lightblue" value="0.678 0.847 0.902"/>-<color name="lightcyan" value="0.878 1 1"/>-<color name="lightgray" value="0.827"/>-<color name="lightgreen" value="0.565 0.933 0.565"/>-<color name="lightyellow" value="1 1 0.878"/>-<dashstyle name="dashed" value="[4] 0"/>-<dashstyle name="dotted" value="[1 3] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<textsize name="large" value="\large"/>-<textsize name="Large" value="\Large"/>-<textsize name="LARGE" value="\LARGE"/>-<textsize name="huge" value="\huge"/>-<textsize name="Huge" value="\Huge"/>-<textsize name="small" value="\small"/>-<textsize name="footnote" value="\footnotesize"/>-<textsize name="tiny" value="\tiny"/>-<textstyle name="center" begin="\begin{center}" end="\end{center}"/>-<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/>-<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/>-<gridsize name="4 pts" value="4"/>-<gridsize name="8 pts (~3 mm)" value="8"/>-<gridsize name="16 pts (~6 mm)" value="16"/>-<gridsize name="32 pts (~12 mm)" value="32"/>-<gridsize name="10 pts (~3.5 mm)" value="10"/>-<gridsize name="20 pts (~7 mm)" value="20"/>-<gridsize name="14 pts (~5 mm)" value="14"/>-<gridsize name="28 pts (~10 mm)" value="28"/>-<gridsize name="56 pts (~20 mm)" value="56"/>-<anglesize name="90 deg" value="90"/>-<anglesize name="60 deg" value="60"/>-<anglesize name="45 deg" value="45"/>-<anglesize name="30 deg" value="30"/>-<anglesize name="22.5 deg" value="22.5"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="75%" value="0.75"/>-<tiling name="falling" angle="-60" step="4" width="1"/>-<tiling name="rising" angle="30" step="4" width="1"/>-</ipestyle>-<ipestyle name="frank">-<arrowsize name="normal" value="5"/>-<arrowsize name="large" value="8"/>-<arrowsize name="huge" value="10"/>-<arrowsize name="small" value="3"/>-<arrowsize name="tiny" value="1"/>-<dashstyle name="dashed" value="[2 2] 0"/>-<dashstyle name="dotted" value="[0.5 1] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<gridsize name="1 pts" value="1"/>-<gridsize name="2 pts" value="2"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="20%" value="0.2"/>-<opacity name="40%" value="0.4"/>-<opacity name="60%" value="0.6"/>-<opacity name="70%" value="0.7"/>-<opacity name="80%" value="0.8"/>-<opacity name="90%" value="0.9"/>-</ipestyle>-<page>-<layer name="alpha"/>-<view layers="alpha" active="alpha"/>-<path layer="alpha" stroke="blue">-176 752 m-208 688 l-160 624 l-288 672 l-304 752 l-256 736 l-h-</path>-<path stroke="red">-384 784 m-448 624 l-368 576 l-512 624 l-448 704 l-560 672 l-528 576 l-592 688 l-h-</path>-<path stroke="red">-240 544 m-80 416 l-224 336 l-320 336 l-208 416 l-352 416 l-320 320 l-480 400 l-448 496 l-320 496 l-256 464 l-h-</path>-<path stroke="blue">-176 272 m-144 80 l-400 176 l-h-</path>-<path stroke="blue">-432 272 m-480 144 l-528 192 l-496 192 l-512 240 l-480 224 l-h-</path>-<path stroke="blue">-64 656 m-32 592 l-80 640 l-144 640 l-80 656 l-96 704 l-80 672 l-h-</path>-</page>-</ipe>
− test/Data/Geometry/arrangement.ipe
@@ -1,296 +0,0 @@-<?xml version="1.0"?>-<!DOCTYPE ipe SYSTEM "ipe.dtd">-<ipe version="70107" creator="Ipe 7.2.2">-<info created="D:20180731094955" modified="D:20180731094955"/>-<ipestyle name="basic">-<symbol name="arrow/arc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/farc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/ptarc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fptarc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="mark/circle(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</symbol>-<symbol name="mark/disk(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-</path>-</symbol>-<symbol name="mark/fdisk(sfx)" transformations="translations">-<group>-<path fill="sym-fill">-0.5 0 0 0.5 0 0 e-</path>-<path fill="sym-stroke" fillrule="eofill">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</group>-</symbol>-<symbol name="mark/box(sx)" transformations="translations">-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</symbol>-<symbol name="mark/square(sx)" transformations="translations">-<path fill="sym-stroke">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h-</path>-</symbol>-<symbol name="mark/fsquare(sfx)" transformations="translations">-<group>-<path fill="sym-fill">--0.5 -0.5 m-0.5 -0.5 l-0.5 0.5 l--0.5 0.5 l-h-</path>-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</group>-</symbol>-<symbol name="mark/cross(sx)" transformations="translations">-<group>-<path fill="sym-stroke">--0.43 -0.57 m-0.57 0.43 l-0.43 0.57 l--0.57 -0.43 l-h-</path>-<path fill="sym-stroke">--0.43 0.57 m-0.57 -0.43 l-0.43 -0.57 l--0.57 0.43 l-h-</path>-</group>-</symbol>-<symbol name="arrow/fnormal(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/pointed(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fpointed(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/linear(spx)">-<path stroke="sym-stroke" pen="sym-pen">--1 0.333 m-0 0 l--1 -0.333 l-</path>-</symbol>-<symbol name="arrow/fdouble(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/double(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<pen name="heavier" value="0.8"/>-<pen name="fat" value="1.2"/>-<pen name="ultrafat" value="2"/>-<symbolsize name="large" value="5"/>-<symbolsize name="small" value="2"/>-<symbolsize name="tiny" value="1.1"/>-<arrowsize name="large" value="10"/>-<arrowsize name="small" value="5"/>-<arrowsize name="tiny" value="3"/>-<color name="red" value="1 0 0"/>-<color name="green" value="0 1 0"/>-<color name="blue" value="0 0 1"/>-<color name="yellow" value="1 1 0"/>-<color name="orange" value="1 0.647 0"/>-<color name="gold" value="1 0.843 0"/>-<color name="purple" value="0.627 0.125 0.941"/>-<color name="gray" value="0.745"/>-<color name="brown" value="0.647 0.165 0.165"/>-<color name="navy" value="0 0 0.502"/>-<color name="pink" value="1 0.753 0.796"/>-<color name="seagreen" value="0.18 0.545 0.341"/>-<color name="turquoise" value="0.251 0.878 0.816"/>-<color name="violet" value="0.933 0.51 0.933"/>-<color name="darkblue" value="0 0 0.545"/>-<color name="darkcyan" value="0 0.545 0.545"/>-<color name="darkgray" value="0.663"/>-<color name="darkgreen" value="0 0.392 0"/>-<color name="darkmagenta" value="0.545 0 0.545"/>-<color name="darkorange" value="1 0.549 0"/>-<color name="darkred" value="0.545 0 0"/>-<color name="lightblue" value="0.678 0.847 0.902"/>-<color name="lightcyan" value="0.878 1 1"/>-<color name="lightgray" value="0.827"/>-<color name="lightgreen" value="0.565 0.933 0.565"/>-<color name="lightyellow" value="1 1 0.878"/>-<dashstyle name="dashed" value="[4] 0"/>-<dashstyle name="dotted" value="[1 3] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<textsize name="large" value="\large"/>-<textsize name="Large" value="\Large"/>-<textsize name="LARGE" value="\LARGE"/>-<textsize name="huge" value="\huge"/>-<textsize name="Huge" value="\Huge"/>-<textsize name="small" value="\small"/>-<textsize name="footnote" value="\footnotesize"/>-<textsize name="tiny" value="\tiny"/>-<textstyle name="center" begin="\begin{center}" end="\end{center}"/>-<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/>-<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/>-<gridsize name="4 pts" value="4"/>-<gridsize name="8 pts (~3 mm)" value="8"/>-<gridsize name="16 pts (~6 mm)" value="16"/>-<gridsize name="32 pts (~12 mm)" value="32"/>-<gridsize name="10 pts (~3.5 mm)" value="10"/>-<gridsize name="20 pts (~7 mm)" value="20"/>-<gridsize name="14 pts (~5 mm)" value="14"/>-<gridsize name="28 pts (~10 mm)" value="28"/>-<gridsize name="56 pts (~20 mm)" value="56"/>-<anglesize name="90 deg" value="90"/>-<anglesize name="60 deg" value="60"/>-<anglesize name="45 deg" value="45"/>-<anglesize name="30 deg" value="30"/>-<anglesize name="22.5 deg" value="22.5"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="75%" value="0.75"/>-<tiling name="falling" angle="-60" step="4" width="1"/>-<tiling name="rising" angle="30" step="4" width="1"/>-</ipestyle>-<ipestyle name="frank">-<arrowsize name="normal" value="5"/>-<arrowsize name="large" value="8"/>-<arrowsize name="huge" value="10"/>-<arrowsize name="small" value="3"/>-<arrowsize name="tiny" value="1"/>-<dashstyle name="dashed" value="[2 2] 0"/>-<dashstyle name="dotted" value="[0.5 1] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<gridsize name="1 pts" value="1"/>-<gridsize name="2 pts" value="2"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="20%" value="0.2"/>-<opacity name="40%" value="0.4"/>-<opacity name="60%" value="0.6"/>-<opacity name="70%" value="0.7"/>-<opacity name="80%" value="0.8"/>-<opacity name="90%" value="0.9"/>-</ipestyle>-<page>-<layer name="alpha"/>-<view layers="alpha" active="alpha"/>-<path layer="alpha" stroke="black">-80 560 m-368 800 l-</path>-<path stroke="black">-160 768 m-416 608 l-</path>-<path stroke="black">-80 624 m-400 672 l-</path>-<path matrix="0.857143 0 0 1.3 32 -172.8" stroke="black">-224 576 m-336 736 l-</path>-</page>-</ipe>
− test/Data/Geometry/arrangement.ipe.out.ipe
@@ -1,247 +0,0 @@-<?xml version="1.0" encoding="UTF-8"?>-<ipe version="70005" creator="HGeometry"><ipestyle name="basic">-<color name="red" value="1 0 0"/>-<color name="green" value="0 1 0"/>-<color name="blue" value="0 0 1"/>-<color name="yellow" value="1 1 0"/>-<color name="orange" value="1 0.647 0"/>-<color name="gold" value="1 0.843 0"/>-<color name="purple" value="0.627 0.125 0.941"/>-<color name="gray" value="0.745 0.745 0.745"/>-<color name="brown" value="0.647 0.165 0.165"/>-<color name="navy" value="0 0 0.502"/>-<color name="pink" value="1 0.753 0.796"/>-<color name="seagreen" value="0.18 0.545 0.341"/>-<color name="turquoise" value="0.251 0.878 0.816"/>-<color name="violet" value="0.933 0.51 0.933"/>-<color name="darkblue" value="0 0 0.545"/>-<color name="darkcyan" value="0 0.545 0.545"/>-<color name="darkgray" value="0.663 0.663 0.663"/>-<color name="darkgreen" value="0 0.392 0"/>-<color name="darkmagenta" value="0.545 0 0.545"/>-<color name="darkorange" value="1 0.549 0"/>-<color name="darkred" value="0.545 0 0"/>-<color name="lightblue" value="0.678 0.847 0.902"/>-<color name="lightcyan" value="0.878 1 1"/>-<color name="lightgray" value="0.827 0.827 0.827"/>-<color name="lightgreen" value="0.565 0.933 0.565"/>-<color name="lightyellow" value="1 1 0.878"/>-<dashstyle name="dashed" value="[4] 0"/>-<dashstyle name="dotted" value="[1 3] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<pen name="heavier" value="0.8"/>-<pen name="fat" value="1.2"/>-<pen name="ultrafat" value="2"/>-<textsize name="large" value="\large"/>-<textsize name="Large" value="\Large"/>-<textsize name="LARGE" value="\LARGE"/>-<textsize name="huge" value="\huge"/>-<textsize name="Huge" value="\Huge"/>-<textsize name="small" value="\small"/>-<textsize name="footnote" value="\footnotesize"/>-<textsize name="tiny" value="\tiny"/>-<symbolsize name="small" value="2"/>-<symbolsize name="tiny" value="1.1"/>-<symbolsize name="large" value="5"/>-<arrowsize name="small" value="5"/>-<arrowsize name="tiny" value="3"/>-<arrowsize name="large" value="10"/>-<gridsize name="4 pts" value="4"/>-<gridsize name="8 pts (~3 mm)" value="8"/>-<gridsize name="16 pts (~6 mm)" value="16"/>-<gridsize name="32 pts (~12 mm)" value="32"/>-<gridsize name="10 pts (~3.5 mm)" value="10"/>-<gridsize name="20 pts (~7 mm)" value="20"/>-<gridsize name="14 pts (~5 mm)" value="14"/>-<gridsize name="28 pts (~10 mm)" value="28"/>-<gridsize name="56 pts (~20 mm)" value="56"/>-<anglesize name="90 deg" value="90"/>-<anglesize name="60 deg" value="60"/>-<anglesize name="45 deg" value="45"/>-<anglesize name="30 deg" value="30"/>-<anglesize name="22.5 deg" value="22.5"/>-<symbol name="mark/circle(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e 0.4 0 0 0.4 0 0 e-</path></symbol>-<symbol name="mark/disk(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-</path></symbol>-<symbol name="mark/fdisk(sfx)" transformations="translations">-<group><path fill="sym-fill">-0.5 0 0 0.5 0 0 e-</path><path fill="sym-stroke" fillrule="eofill">-0.6 0 0 0.6 0 0 e 0.4 0 0 0.4 0 0 e-</path></group></symbol>-<symbol name="mark/box(sx)" transformations="translations">-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m 0.6 -0.6 l 0.6 0.6 l -0.6 0.6 l h--0.4 -0.4 m 0.4 -0.4 l 0.4 0.4 l -0.4 0.4 l h</path></symbol>-<symbol name="mark/square(sx)" transformations="translations">-<path fill="sym-stroke">--0.6 -0.6 m 0.6 -0.6 l 0.6 0.6 l -0.6 0.6 l h</path></symbol>-<symbol name="mark/fsquare(sfx)" transformations="translations">-<group><path fill="sym-fill">--0.5 -0.5 m 0.5 -0.5 l 0.5 0.5 l -0.5 0.5 l h</path>-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m 0.6 -0.6 l 0.6 0.6 l -0.6 0.6 l h--0.4 -0.4 m 0.4 -0.4 l 0.4 0.4 l -0.4 0.4 l h</path></group></symbol>-<symbol name="mark/cross(sx)" transformations="translations">-<group><path fill="sym-stroke">--0.43 -0.57 m 0.57 0.43 l 0.43 0.57 l -0.57 -0.43 l h</path>-<path fill="sym-stroke">--0.43 0.57 m 0.57 -0.43 l 0.43 -0.57 l -0.57 0.43 l h</path>-</group></symbol>-<symbol name="arrow/arc(spx)">-<path pen="sym-pen" stroke="sym-stroke" fill="sym-stroke">-0 0 m -1.0 0.333 l -1.0 -0.333 l h</path></symbol>-<symbol name="arrow/farc(spx)">-<path pen="sym-pen" stroke="sym-stroke" fill="white">-0 0 m -1.0 0.333 l -1.0 -0.333 l h</path></symbol>-<symbol name="arrow/ptarc(spx)">-<path pen="sym-pen" stroke="sym-stroke" fill="sym-stroke">-0 0 m -1.0 0.333 l -0.8 0 l -1.0 -0.333 l h</path></symbol>-<symbol name="arrow/fptarc(spx)">-<path pen="sym-pen" stroke="sym-stroke" fill="white">-0 0 m -1.0 0.333 l -0.8 0 l -1.0 -0.333 l h</path></symbol>-<symbol name="arrow/fnormal(spx)">-<path pen="sym-pen" stroke="sym-stroke" fill="white">-0 0 m -1.0 0.333 l -1.0 -0.333 l h</path></symbol>-<symbol name="arrow/pointed(spx)">-<path pen="sym-pen" stroke="sym-stroke" fill="sym-stroke">-0 0 m -1.0 0.333 l -0.8 0 l -1.0 -0.333 l h</path></symbol>-<symbol name="arrow/fpointed(spx)">-<path pen="sym-pen" stroke="sym-stroke" fill="white">-0 0 m -1.0 0.333 l -0.8 0 l -1.0 -0.333 l h</path></symbol>-<symbol name="arrow/linear(spx)">-<path pen="sym-pen" stroke="sym-stroke">--1.0 0.333 m 0 0 l -1.0 -0.333 l</path></symbol>-<symbol name="arrow/fdouble(spx)">-<path pen="sym-pen" stroke="sym-stroke" fill="white">-0 0 m -1.0 0.333 l -1.0 -0.333 l h--1 0 m -2.0 0.333 l -2.0 -0.333 l h-</path></symbol>-<symbol name="arrow/double(spx)">-<path pen="sym-pen" stroke="sym-stroke" fill="sym-stroke">-0 0 m -1.0 0.333 l -1.0 -0.333 l h--1 0 m -2.0 0.333 l -2.0 -0.333 l h-</path></symbol>-<tiling name="falling" angle="-60" width="1" step="4"/>-<tiling name="rising" angle="30" width="1" step="4"/>-<textstyle name="center" begin="\begin{center}" end="\end{center}"/>-<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/>-<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/>-</ipestyle><page><group><use pos="172.658536585365 637.048780487804" name="mark/disk(sx)"/><use pos="172.658536585365 637.215447154471" name="mark/disk(sx)"/><use pos="172.658536585365 637.898780487804" name="mark/disk(sx)"/><use pos="172.658536585365 746.000060937506" name="mark/disk(sx)"/><use pos="173.658536585365 638.048780487804" name="mark/disk(sx)"/><use pos="195.199902499989 746.000060937506" name="mark/disk(sx)"/><use pos="252.176396921200 637.048780487804" name="mark/disk(sx)"/><use pos="256.914285714285 707.428571428571" name="mark/disk(sx)"/><use pos="258.512437254286 650.776865588142" name="mark/disk(sx)"/><use pos="278.447793072843 693.970129329472" name="mark/disk(sx)"/><use pos="302.000073125007 745.000060937506" name="mark/disk(sx)"/><use pos="302.461611663469 746.000060937506" name="mark/disk(sx)"/><use pos="303.200073125007 746.000060937506" name="mark/disk(sx)"/><use pos="330.322580645161 661.548387096774" name="mark/disk(sx)"/><use pos="331.322580645161 637.048780487804" name="mark/disk(sx)"/><use pos="331.322580645161 660.923387096774" name="mark/disk(sx)"/><use pos="331.322580645161 661.698387096774" name="mark/disk(sx)"/><use pos="331.322580645161 746.000060937506" name="mark/disk(sx)"/><path>172.658536585365 637.215447154471 m-173.658536585365 638.048780487804 l-</path><path>173.658536585365 638.048780487804 m-256.914285714285 707.428571428571 l-</path><path>256.914285714285 707.428571428571 m-302.000073125007 745.000060937506 l-</path><path>302.000073125007 745.000060937506 m-303.200073125007 746.000060937506 l-</path><path>195.199902499989 746.000060937506 m-256.914285714285 707.428571428571 l-</path><path>256.914285714285 707.428571428571 m-278.447793072843 693.970129329472 l-</path><path>278.447793072843 693.970129329472 m-330.322580645161 661.548387096774 l-</path><path>330.322580645161 661.548387096774 m-331.322580645161 660.923387096774 l-</path><path>172.658536585365 637.898780487804 m-173.658536585365 638.048780487804 l-</path><path>173.658536585365 638.048780487804 m-258.512437254286 650.776865588142 l-</path><path>258.512437254286 650.776865588142 m-330.322580645161 661.548387096774 l-</path><path>330.322580645161 661.548387096774 m-331.322580645161 661.698387096774 l-</path><path>252.176396921200 637.048780487804 m-258.512437254286 650.776865588142 l-</path><path>258.512437254286 650.776865588142 m-278.447793072843 693.970129329472 l-</path><path>278.447793072843 693.970129329472 m-302.000073125007 745.000060937506 l-</path><path>302.000073125007 745.000060937506 m-302.461611663469 746.000060937506 l-</path><path>172.658536585365 746.000060937506 m-195.199902499989 746.000060937506 l-</path><path>195.199902499989 746.000060937506 m-302.461611663469 746.000060937506 l-</path><path>302.461611663469 746.000060937506 m-303.200073125007 746.000060937506 l-</path><path>303.200073125007 746.000060937506 m-331.322580645161 746.000060937506 l-</path><path>331.322580645161 746.000060937506 m-331.322580645161 661.698387096774 l-</path><path>331.322580645161 661.698387096774 m-331.322580645161 660.923387096774 l-</path><path>331.322580645161 660.923387096774 m-331.322580645161 637.048780487804 l-</path><path>331.322580645161 637.048780487804 m-252.176396921200 637.048780487804 l-</path><path>252.176396921200 637.048780487804 m-172.658536585365 637.048780487804 l-</path><path>172.658536585365 637.048780487804 m-172.658536585365 637.215447154471 l-</path><path>172.658536585365 637.215447154471 m-172.658536585365 637.898780487804 l-</path><path>172.658536585365 637.898780487804 m-172.658536585365 746.000060937506 l-</path><path>172.658536585365 637.215447154471 m-173.658536585365 638.048780487804 l-258.512437254286 650.776865588142 l-252.176396921200 637.048780487804 l-172.658536585365 637.048780487804 l-h-</path><path>172.658536585365 637.898780487804 m-173.658536585365 638.048780487804 l-172.658536585365 637.215447154471 l-h-</path><path>172.658536585365 746.000060937506 m-195.199902499989 746.000060937506 l-256.914285714285 707.428571428571 l-173.658536585365 638.048780487804 l-172.658536585365 637.898780487804 l-h-</path><path>256.914285714285 707.428571428571 m-278.447793072843 693.970129329472 l-258.512437254286 650.776865588142 l-173.658536585365 638.048780487804 l-h-</path><path>302.461611663469 746.000060937506 m-302.000073125007 745.000060937506 l-256.914285714285 707.428571428571 l-195.199902499989 746.000060937506 l-h-</path><path>258.512437254286 650.776865588142 m-330.322580645161 661.548387096774 l-331.322580645161 660.923387096774 l-331.322580645161 637.048780487804 l-252.176396921200 637.048780487804 l-h-</path><path>302.000073125007 745.000060937506 m-278.447793072843 693.970129329472 l-256.914285714285 707.428571428571 l-h-</path><path>278.447793072843 693.970129329472 m-330.322580645161 661.548387096774 l-258.512437254286 650.776865588142 l-h-</path><path>302.000073125007 745.000060937506 m-303.200073125007 746.000060937506 l-331.322580645161 746.000060937506 l-331.322580645161 661.698387096774 l-330.322580645161 661.548387096774 l-278.447793072843 693.970129329472 l-h-</path><path>302.461611663469 746.000060937506 m-303.200073125007 746.000060937506 l-302.000073125007 745.000060937506 l-h-</path><path>331.322580645161 661.698387096774 m-331.322580645161 660.923387096774 l-330.322580645161 661.548387096774 l-h-</path></group></page></ipe>
− test/Data/Geometry/pointInPolygon.ipe
@@ -1,311 +0,0 @@-<?xml version="1.0"?>-<!DOCTYPE ipe SYSTEM "ipe.dtd">-<ipe version="70107" creator="Ipe 7.1.7">-<info created="D:20150923215046" modified="D:20150924223742"/>-<ipestyle name="basic">-<symbol name="arrow/arc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/farc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/ptarc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fptarc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="mark/circle(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</symbol>-<symbol name="mark/disk(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-</path>-</symbol>-<symbol name="mark/fdisk(sfx)" transformations="translations">-<group>-<path fill="sym-fill">-0.5 0 0 0.5 0 0 e-</path>-<path fill="sym-stroke" fillrule="eofill">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</group>-</symbol>-<symbol name="mark/box(sx)" transformations="translations">-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</symbol>-<symbol name="mark/square(sx)" transformations="translations">-<path fill="sym-stroke">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h-</path>-</symbol>-<symbol name="mark/fsquare(sfx)" transformations="translations">-<group>-<path fill="sym-fill">--0.5 -0.5 m-0.5 -0.5 l-0.5 0.5 l--0.5 0.5 l-h-</path>-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</group>-</symbol>-<symbol name="mark/cross(sx)" transformations="translations">-<group>-<path fill="sym-stroke">--0.43 -0.57 m-0.57 0.43 l-0.43 0.57 l--0.57 -0.43 l-h-</path>-<path fill="sym-stroke">--0.43 0.57 m-0.57 -0.43 l-0.43 -0.57 l--0.57 0.43 l-h-</path>-</group>-</symbol>-<symbol name="arrow/fnormal(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/pointed(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fpointed(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/linear(spx)">-<path stroke="sym-stroke" pen="sym-pen">--1 0.333 m-0 0 l--1 -0.333 l-</path>-</symbol>-<symbol name="arrow/fdouble(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/double(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<pen name="heavier" value="0.8"/>-<pen name="fat" value="1.2"/>-<pen name="ultrafat" value="2"/>-<symbolsize name="large" value="5"/>-<symbolsize name="small" value="2"/>-<symbolsize name="tiny" value="1.1"/>-<arrowsize name="large" value="10"/>-<arrowsize name="small" value="5"/>-<arrowsize name="tiny" value="3"/>-<color name="red" value="1 0 0"/>-<color name="green" value="0 1 0"/>-<color name="blue" value="0 0 1"/>-<color name="yellow" value="1 1 0"/>-<color name="orange" value="1 0.647 0"/>-<color name="gold" value="1 0.843 0"/>-<color name="purple" value="0.627 0.125 0.941"/>-<color name="gray" value="0.745"/>-<color name="brown" value="0.647 0.165 0.165"/>-<color name="navy" value="0 0 0.502"/>-<color name="pink" value="1 0.753 0.796"/>-<color name="seagreen" value="0.18 0.545 0.341"/>-<color name="turquoise" value="0.251 0.878 0.816"/>-<color name="violet" value="0.933 0.51 0.933"/>-<color name="darkblue" value="0 0 0.545"/>-<color name="darkcyan" value="0 0.545 0.545"/>-<color name="darkgray" value="0.663"/>-<color name="darkgreen" value="0 0.392 0"/>-<color name="darkmagenta" value="0.545 0 0.545"/>-<color name="darkorange" value="1 0.549 0"/>-<color name="darkred" value="0.545 0 0"/>-<color name="lightblue" value="0.678 0.847 0.902"/>-<color name="lightcyan" value="0.878 1 1"/>-<color name="lightgray" value="0.827"/>-<color name="lightgreen" value="0.565 0.933 0.565"/>-<color name="lightyellow" value="1 1 0.878"/>-<dashstyle name="dashed" value="[4] 0"/>-<dashstyle name="dotted" value="[1 3] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<textsize name="large" value="\large"/>-<textsize name="Large" value="\Large"/>-<textsize name="LARGE" value="\LARGE"/>-<textsize name="huge" value="\huge"/>-<textsize name="Huge" value="\Huge"/>-<textsize name="small" value="\small"/>-<textsize name="footnote" value="\footnotesize"/>-<textsize name="tiny" value="\tiny"/>-<textstyle name="center" begin="\begin{center}" end="\end{center}"/>-<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/>-<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/>-<gridsize name="4 pts" value="4"/>-<gridsize name="8 pts (~3 mm)" value="8"/>-<gridsize name="16 pts (~6 mm)" value="16"/>-<gridsize name="32 pts (~12 mm)" value="32"/>-<gridsize name="10 pts (~3.5 mm)" value="10"/>-<gridsize name="20 pts (~7 mm)" value="20"/>-<gridsize name="14 pts (~5 mm)" value="14"/>-<gridsize name="28 pts (~10 mm)" value="28"/>-<gridsize name="56 pts (~20 mm)" value="56"/>-<anglesize name="90 deg" value="90"/>-<anglesize name="60 deg" value="60"/>-<anglesize name="45 deg" value="45"/>-<anglesize name="30 deg" value="30"/>-<anglesize name="22.5 deg" value="22.5"/>-<tiling name="falling" angle="-60" step="4" width="1"/>-<tiling name="rising" angle="30" step="4" width="1"/>-</ipestyle>-<ipestyle name="frank">-<arrowsize name="normal" value="5"/>-<arrowsize name="large" value="8"/>-<arrowsize name="huge" value="10"/>-<arrowsize name="small" value="3"/>-<arrowsize name="tiny" value="1"/>-<dashstyle name="dashed" value="[2 2] 0"/>-<dashstyle name="dotted" value="[0.5 1] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<gridsize name="1 pts" value="1"/>-<gridsize name="2 pts" value="2"/>-<opacity name="10%" value="0.1"/>-<opacity name="20%" value="0.2"/>-<opacity name="30%" value="0.3"/>-<opacity name="40%" value="0.4"/>-<opacity name="50%" value="0.5"/>-<opacity name="60%" value="0.6"/>-<opacity name="70%" value="0.7"/>-<opacity name="80%" value="0.8"/>-<opacity name="90%" value="0.9"/>-</ipestyle>-<page>-<layer name="alpha"/>-<view layers="alpha" active="alpha"/>-<path layer="alpha" stroke="darkblue">-208 752 m-304 688 l-224 592 l-48 736 l-h-</path>-<use name="mark/disk(sx)" pos="128 704" size="normal" stroke="darkblue"/>-<use name="mark/disk(sx)" pos="192 672" size="normal" stroke="darkblue"/>-<use name="mark/disk(sx)" pos="192 720" size="normal" stroke="darkblue"/>-<use name="mark/box(sx)" pos="304 688" size="normal" stroke="black"/>-<use name="mark/cross(sx)" pos="352 736" size="normal" stroke="darkblue"/>-<use name="mark/cross(sx)" pos="352 704" size="normal" stroke="darkblue"/>-<path stroke="orange">-496 736 m-432 624 l-512 608 l-h-</path>-<use name="mark/box(sx)" pos="432 624" size="normal" stroke="orange"/>-<use name="mark/box(sx)" pos="512 608" size="normal" stroke="orange"/>-<use name="mark/box(sx)" pos="496 736" size="normal" stroke="orange"/>-<use name="mark/disk(sx)" pos="496 656" size="normal" stroke="orange"/>-<use name="mark/disk(sx)" pos="480 640" size="normal" stroke="orange"/>-<use name="mark/disk(sx)" pos="464 656" size="normal" stroke="orange"/>-<use name="mark/cross(sx)" pos="368 608" size="normal" stroke="orange"/>-<use name="mark/cross(sx)" pos="384 592" size="normal" stroke="orange"/>-<use name="mark/cross(sx)" pos="336 576" size="normal" stroke="orange"/>-<use name="mark/disk(sx)" pos="496 624" size="normal" stroke="orange"/>-<use name="mark/cross(sx)" pos="528 624" size="normal" stroke="orange"/>-<use name="mark/cross(sx)" pos="384 624" size="normal" stroke="orange"/>-<use name="mark/cross(sx)" pos="368 640" size="normal" stroke="orange"/>-<use name="mark/cross(sx)" pos="336 688" size="normal" stroke="darkblue"/>-<use name="mark/disk(sx)" pos="256 688" size="normal" stroke="darkblue"/>-<use name="mark/disk(sx)" pos="224 688" size="normal" stroke="darkblue"/>-</page>-</ipe>
− test/Data/Geometry/pointInTriangle.ipe
@@ -1,310 +0,0 @@-<?xml version="1.0"?>-<!DOCTYPE ipe SYSTEM "ipe.dtd">-<ipe version="70107" creator="Ipe 7.2.2">-<info created="D:20150923215046" modified="D:20180808120020"/>-<ipestyle name="basic">-<symbol name="arrow/arc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/farc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/ptarc(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fptarc(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="mark/circle(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</symbol>-<symbol name="mark/disk(sx)" transformations="translations">-<path fill="sym-stroke">-0.6 0 0 0.6 0 0 e-</path>-</symbol>-<symbol name="mark/fdisk(sfx)" transformations="translations">-<group>-<path fill="sym-fill">-0.5 0 0 0.5 0 0 e-</path>-<path fill="sym-stroke" fillrule="eofill">-0.6 0 0 0.6 0 0 e-0.4 0 0 0.4 0 0 e-</path>-</group>-</symbol>-<symbol name="mark/box(sx)" transformations="translations">-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</symbol>-<symbol name="mark/square(sx)" transformations="translations">-<path fill="sym-stroke">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h-</path>-</symbol>-<symbol name="mark/fsquare(sfx)" transformations="translations">-<group>-<path fill="sym-fill">--0.5 -0.5 m-0.5 -0.5 l-0.5 0.5 l--0.5 0.5 l-h-</path>-<path fill="sym-stroke" fillrule="eofill">--0.6 -0.6 m-0.6 -0.6 l-0.6 0.6 l--0.6 0.6 l-h--0.4 -0.4 m-0.4 -0.4 l-0.4 0.4 l--0.4 0.4 l-h-</path>-</group>-</symbol>-<symbol name="mark/cross(sx)" transformations="translations">-<group>-<path fill="sym-stroke">--0.43 -0.57 m-0.57 0.43 l-0.43 0.57 l--0.57 -0.43 l-h-</path>-<path fill="sym-stroke">--0.43 0.57 m-0.57 -0.43 l-0.43 -0.57 l--0.57 0.43 l-h-</path>-</group>-</symbol>-<symbol name="arrow/fnormal(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/pointed(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/fpointed(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--0.8 0 l--1 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/linear(spx)">-<path stroke="sym-stroke" pen="sym-pen">--1 0.333 m-0 0 l--1 -0.333 l-</path>-</symbol>-<symbol name="arrow/fdouble(spx)">-<path stroke="sym-stroke" fill="white" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<symbol name="arrow/double(spx)">-<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">-0 0 m--1 0.333 l--1 -0.333 l-h--1 0 m--2 0.333 l--2 -0.333 l-h-</path>-</symbol>-<pen name="heavier" value="0.8"/>-<pen name="fat" value="1.2"/>-<pen name="ultrafat" value="2"/>-<symbolsize name="large" value="5"/>-<symbolsize name="small" value="2"/>-<symbolsize name="tiny" value="1.1"/>-<arrowsize name="large" value="10"/>-<arrowsize name="small" value="5"/>-<arrowsize name="tiny" value="3"/>-<color name="red" value="1 0 0"/>-<color name="green" value="0 1 0"/>-<color name="blue" value="0 0 1"/>-<color name="yellow" value="1 1 0"/>-<color name="orange" value="1 0.647 0"/>-<color name="gold" value="1 0.843 0"/>-<color name="purple" value="0.627 0.125 0.941"/>-<color name="gray" value="0.745"/>-<color name="brown" value="0.647 0.165 0.165"/>-<color name="navy" value="0 0 0.502"/>-<color name="pink" value="1 0.753 0.796"/>-<color name="seagreen" value="0.18 0.545 0.341"/>-<color name="turquoise" value="0.251 0.878 0.816"/>-<color name="violet" value="0.933 0.51 0.933"/>-<color name="darkblue" value="0 0 0.545"/>-<color name="darkcyan" value="0 0.545 0.545"/>-<color name="darkgray" value="0.663"/>-<color name="darkgreen" value="0 0.392 0"/>-<color name="darkmagenta" value="0.545 0 0.545"/>-<color name="darkorange" value="1 0.549 0"/>-<color name="darkred" value="0.545 0 0"/>-<color name="lightblue" value="0.678 0.847 0.902"/>-<color name="lightcyan" value="0.878 1 1"/>-<color name="lightgray" value="0.827"/>-<color name="lightgreen" value="0.565 0.933 0.565"/>-<color name="lightyellow" value="1 1 0.878"/>-<dashstyle name="dashed" value="[4] 0"/>-<dashstyle name="dotted" value="[1 3] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<textsize name="large" value="\large"/>-<textsize name="Large" value="\Large"/>-<textsize name="LARGE" value="\LARGE"/>-<textsize name="huge" value="\huge"/>-<textsize name="Huge" value="\Huge"/>-<textsize name="small" value="\small"/>-<textsize name="footnote" value="\footnotesize"/>-<textsize name="tiny" value="\tiny"/>-<textstyle name="center" begin="\begin{center}" end="\end{center}"/>-<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/>-<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/>-<gridsize name="4 pts" value="4"/>-<gridsize name="8 pts (~3 mm)" value="8"/>-<gridsize name="16 pts (~6 mm)" value="16"/>-<gridsize name="32 pts (~12 mm)" value="32"/>-<gridsize name="10 pts (~3.5 mm)" value="10"/>-<gridsize name="20 pts (~7 mm)" value="20"/>-<gridsize name="14 pts (~5 mm)" value="14"/>-<gridsize name="28 pts (~10 mm)" value="28"/>-<gridsize name="56 pts (~20 mm)" value="56"/>-<anglesize name="90 deg" value="90"/>-<anglesize name="60 deg" value="60"/>-<anglesize name="45 deg" value="45"/>-<anglesize name="30 deg" value="30"/>-<anglesize name="22.5 deg" value="22.5"/>-<tiling name="falling" angle="-60" step="4" width="1"/>-<tiling name="rising" angle="30" step="4" width="1"/>-</ipestyle>-<ipestyle name="frank">-<arrowsize name="normal" value="5"/>-<arrowsize name="large" value="8"/>-<arrowsize name="huge" value="10"/>-<arrowsize name="small" value="3"/>-<arrowsize name="tiny" value="1"/>-<dashstyle name="dashed" value="[2 2] 0"/>-<dashstyle name="dotted" value="[0.5 1] 0"/>-<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>-<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>-<gridsize name="1 pts" value="1"/>-<gridsize name="2 pts" value="2"/>-<opacity name="10%" value="0.1"/>-<opacity name="30%" value="0.3"/>-<opacity name="50%" value="0.5"/>-<opacity name="20%" value="0.2"/>-<opacity name="40%" value="0.4"/>-<opacity name="60%" value="0.6"/>-<opacity name="70%" value="0.7"/>-<opacity name="80%" value="0.8"/>-<opacity name="90%" value="0.9"/>-</ipestyle>-<page>-<layer name="alpha"/>-<view layers="alpha" active="alpha"/>-<path layer="alpha" stroke="darkblue">-304 720 m-224 592 l-48 736 l-h-</path>-<use name="mark/disk(sx)" pos="128 704" size="normal" stroke="darkblue"/>-<use name="mark/disk(sx)" pos="192 672" size="normal" stroke="darkblue"/>-<use name="mark/disk(sx)" pos="192 720" size="normal" stroke="darkblue"/>-<use name="mark/box(sx)" pos="304 688" size="normal" stroke="black"/>-<use name="mark/cross(sx)" pos="352 736" size="normal" stroke="darkblue"/>-<use name="mark/cross(sx)" pos="352 704" size="normal" stroke="darkblue"/>-<path stroke="orange">-496 736 m-432 624 l-512 608 l-h-</path>-<use name="mark/box(sx)" pos="432 624" size="normal" stroke="orange"/>-<use name="mark/box(sx)" pos="512 608" size="normal" stroke="orange"/>-<use name="mark/box(sx)" pos="496 736" size="normal" stroke="orange"/>-<use name="mark/disk(sx)" pos="496 656" size="normal" stroke="orange"/>-<use name="mark/disk(sx)" pos="480 640" size="normal" stroke="orange"/>-<use name="mark/disk(sx)" pos="464 656" size="normal" stroke="orange"/>-<use name="mark/cross(sx)" pos="368 608" size="normal" stroke="orange"/>-<use name="mark/cross(sx)" pos="384 592" size="normal" stroke="orange"/>-<use name="mark/cross(sx)" pos="336 576" size="normal" stroke="orange"/>-<use name="mark/disk(sx)" pos="496 624" size="normal" stroke="orange"/>-<use name="mark/cross(sx)" pos="528 624" size="normal" stroke="orange"/>-<use name="mark/cross(sx)" pos="384 624" size="normal" stroke="orange"/>-<use name="mark/cross(sx)" pos="368 640" size="normal" stroke="orange"/>-<use name="mark/cross(sx)" pos="336 688" size="normal" stroke="darkblue"/>-<use name="mark/disk(sx)" pos="256 688" size="normal" stroke="darkblue"/>-<use name="mark/disk(sx)" pos="224 688" size="normal" stroke="darkblue"/>-</page>-</ipe>
− test/Data/PlaneGraph/myPlaneGraph.yaml
@@ -1,90 +0,0 @@-adjacencies:-- adj:- - - 4- - []- - - 2- - []- - - 1- - []- - - 3- - []- id: 0- loc:- - 0- - 0- vData:- - []- - []- - []- - []-- adj:- - - 2- - []- - - 3- - []- - - 0- - []- id: 1- loc:- - 10- - 10- vData:- - []- - []- - []-- adj:- - - 1- - []- - - 0- - []- - - 4- - []- id: 2- loc:- - 12- - 10- vData:- - []- - []- - []-- adj:- - - 0- - []- - - 1- - []- id: 3- loc:- - 13- - 20- vData:- - []- - []-- adj:- - - 2- - []- - - 0- - []- id: 4- loc:- - 20- - 5- vData:- - []- - []-faces:-- fData: []- incidentEdge:- - 0- - 4-- fData: []- incidentEdge:- - 0- - 2-- fData: []- incidentEdge:- - 0- - 1-- fData: []- incidentEdge:- - 0- - 3
− test/Data/PlaneGraph/small.yaml
@@ -1,58 +0,0 @@-ajacencies:-- adj:- - - 2- - 0->2- - - 1- - 0->1- - - 3- - 0->3- id: 0- loc:- - 0- - 0- vData: 0-- adj:- - - 0- - 1->0- - - 2- - 1->2- - - 3- - 1->3- id: 1- loc:- - 2- - 2- vData: 1-- adj:- - - 0- - 2->0- - - 1- - 2->1- id: 2- loc:- - 2- - 0- vData: 2-- adj:- - - 0- - 3->0- - - 1- - 3->1- id: 3- loc:- - -1- - 4- vData: 3-faces:-- fData: OuterFace- incidentEdge:- - 0- - 2-- fData: A- incidentEdge:- - 0- - 1-- fData: B- incidentEdge:- - 0- - 3
− test/Data/PlaneGraph/testsegs.png
binary file changed (56081 → absent bytes)