Compare commits

...

20 Commits

Author SHA1 Message Date
hasufell 7fe3aa8458
Port to diagrams >1.3 2015-05-21 01:39:34 +02:00
hasufell e9786df1e2
Update .gitignore 2015-05-21 01:37:42 +02:00
hasufell 9f5938da97
HALFEDGE: add Show instance to indirect data structures 2015-02-10 04:17:31 +01:00
hasufell fbb0d2963c
CABAL: cleanup dependencies 2015-02-10 04:17:04 +01:00
hasufell 6a6870b1d3
HALFEDGE: improve pseudo-code 2015-02-10 04:10:14 +01:00
hasufell c2ffde8712
HALFEDGE: fix module doc 2015-02-09 18:46:39 +01:00
hasufell 38a1e4d7fb
HALFEDGE: improve readability 2015-02-09 18:36:43 +01:00
hasufell 84d2e38d55
HALFEDGE: add pseudo-code for 'indirectToDirect' 2015-02-09 18:29:40 +01:00
hasufell d845cc0691
HALFEDGE: make 'indirectToDirect' a safe function
We had to add NoFace/NoEdge/NoVert constructors to our half-edge
data structures, because using "Maybe HeEdge a" as the result value
of 'indirectToDirect' causes an infinite recursion, since the whole
data structure (which is cyclic and infinite) has to be evaluated
in order to know which constructor to use.

Unfortunately this makes the code quite hard to read.

TODO: add pseudo-code
2015-02-09 17:58:33 +01:00
hasufell 57476d2986
HALFEDGE: use Data.IntMap instead of Array 2015-02-09 16:14:01 +01:00
hasufell d37624f2d1
HALFEDGE: optimize buildHeEdgeFromStr
It's faster this way than using buildHeEdge.
2015-02-09 16:12:19 +01:00
hasufell c04ba4f803
HALFEDGE: fix haddock comment 2015-02-04 02:02:58 +01:00
hasufell 97f72dc58d
Update .gitignore 2015-02-04 00:56:32 +01:00
hasufell 351e47fa48
Add test obj for HalfEdge data structure 2015-02-04 00:55:53 +01:00
hasufell b5ecd16a2e
Revert "Remove almost all 'type' usage to make types more transparent"
This reverts commit 5120a44d0f.

Conflicts:
	Parser/Meshparser.hs
2015-02-04 00:51:03 +01:00
hasufell d6174a975c
CABAL: more lax dependencies 2015-02-04 00:47:46 +01:00
hasufell c94a92739d
HALFEDGE: initial implementation for half-edge data structures
See http://www.flipcode.com/archives/The_Half-Edge_Data_Structure.shtml
2015-02-04 00:47:46 +01:00
hasufell 44fee35926
PARSER: improve function names 2015-02-04 00:47:46 +01:00
hasufell a33b451740
PARSER: improve modularity 2015-02-04 00:46:48 +01:00
hasufell df4a4c2a27
PARSER: export the whole module 2015-02-04 00:46:47 +01:00
18 changed files with 511 additions and 248 deletions

7
.gitignore vendored
View File

@ -11,3 +11,10 @@ dist/
# cabal # cabal
.cabal-sandbox/ .cabal-sandbox/
cabal.sandbox.config cabal.sandbox.config
# profiling
*.prof
_darcs/
.liquid/

View File

@ -4,15 +4,14 @@ module Algebra.Polygon where
import Algebra.Vector import Algebra.Vector
import Data.Maybe import Data.Maybe
import Diagrams.TwoD.Types
import MyPrelude import MyPrelude
-- |Split a polygon by a given segment which must be vertices of the -- |Split a polygon by a given segment which must be vertices of the
-- polygon (returns empty array otherwise). -- polygon (returns empty array otherwise).
splitPoly :: [P2] splitPoly :: [PT]
-> (P2, P2) -> Segment
-> [[P2]] -> [[PT]]
splitPoly pts (a, b) splitPoly pts (a, b)
| elem a pts && elem b pts = | elem a pts && elem b pts =
[b : takeWhile (/= b) shiftedPoly, a : dropWhile (/= b) shiftedPoly] [b : takeWhile (/= b) shiftedPoly, a : dropWhile (/= b) shiftedPoly]
@ -22,7 +21,7 @@ splitPoly pts (a, b)
-- |Get all edges of a polygon. -- |Get all edges of a polygon.
polySegments :: [P2] -> [(P2, P2)] polySegments :: [PT] -> [Segment]
polySegments p@(x':_:_:_) = go p ++ [(last p, x')] polySegments p@(x':_:_:_) = go p ++ [(last p, x')]
where where
go (x:y:xs) = (x, y) : go (y:xs) go (x:y:xs) = (x, y) : go (y:xs)
@ -33,7 +32,7 @@ polySegments _ = []
-- |Check whether the given segment is inside the polygon. -- |Check whether the given segment is inside the polygon.
-- This doesn't check for segments that are completely outside -- This doesn't check for segments that are completely outside
-- of the polygon yet. -- of the polygon yet.
isInsidePoly :: [P2] -> (P2, P2) -> Bool isInsidePoly :: [PT] -> Segment -> Bool
isInsidePoly pts seg = isInsidePoly pts seg =
null null
. catMaybes . catMaybes
@ -42,21 +41,21 @@ isInsidePoly pts seg =
-- |Check whether two points are adjacent vertices of a polygon. -- |Check whether two points are adjacent vertices of a polygon.
adjacent :: P2 -> P2 -> [P2] -> Bool adjacent :: PT -> PT -> [PT] -> Bool
adjacent u v = any (\x -> x == (u, v) || x == (v, u)) . polySegments adjacent u v = any (\x -> x == (u, v) || x == (v, u)) . polySegments
-- |Check whether the polygon is a triangle polygon. -- |Check whether the polygon is a triangle polygon.
isTrianglePoly :: [P2] -> Bool isTrianglePoly :: [PT] -> Bool
isTrianglePoly [_, _, _] = True isTrianglePoly [_, _, _] = True
isTrianglePoly _ = False isTrianglePoly _ = False
-- |Get all triangle polygons. -- |Get all triangle polygons.
triangleOnly :: [[P2]] -> [[P2]] triangleOnly :: [[PT]] -> [[PT]]
triangleOnly = filter isTrianglePoly triangleOnly = filter isTrianglePoly
-- |Get all non-triangle polygons. -- |Get all non-triangle polygons.
nonTriangleOnly :: [[P2]] -> [[P2]] nonTriangleOnly :: [[PT]] -> [[PT]]
nonTriangleOnly = filter (not . isTrianglePoly) nonTriangleOnly = filter (not . isTrianglePoly)

View File

@ -13,6 +13,13 @@ import GHC.Float
import MyPrelude import MyPrelude
type Vec = V2 Double
type PT = P2 Double
type Coord = (Double, Double)
type Segment = (PT, PT)
type Square = (Coord, Coord)
data Alignment = CW data Alignment = CW
| CCW | CCW
| CL | CL
@ -24,13 +31,13 @@ data Alignment = CW
-- ((xmin, ymin), (xmax, ymax)) -- ((xmin, ymin), (xmax, ymax))
dimToSquare :: (Double, Double) -- ^ x dimension dimToSquare :: (Double, Double) -- ^ x dimension
-> (Double, Double) -- ^ y dimension -> (Double, Double) -- ^ y dimension
-> ((Double, Double), (Double, Double)) -- ^ square describing those dimensions -> Square -- ^ square describing those dimensions
dimToSquare (x1, x2) (y1, y2) = ((x1, y1), (x2, y2)) dimToSquare (x1, x2) (y1, y2) = ((x1, y1), (x2, y2))
-- |Checks whether the Point is in a given Square. -- |Checks whether the Point is in a given Square.
inRange :: ((Double, Double), (Double, Double)) -- ^ the square: ((xmin, ymin), (xmax, ymax)) inRange :: Square -- ^ the square: ((xmin, ymin), (xmax, ymax))
-> P2 -- ^ Coordinate -> PT -- ^ Coordinate
-> Bool -- ^ result -> Bool -- ^ result
inRange ((xmin, ymin), (xmax, ymax)) (coords -> x :& y) inRange ((xmin, ymin), (xmax, ymax)) (coords -> x :& y)
= x >= min xmin xmax = x >= min xmin xmax
@ -40,7 +47,7 @@ inRange ((xmin, ymin), (xmax, ymax)) (coords -> x :& y)
-- |Get the angle between two vectors. -- |Get the angle between two vectors.
getAngle :: R2 -> R2 -> Double getAngle :: Vec -> Vec -> Double
getAngle a b = getAngle a b =
acos acos
. flip (/) (vecLength a * vecLength b) . flip (/) (vecLength a * vecLength b)
@ -49,50 +56,48 @@ getAngle a b =
-- |Get the length of a vector. -- |Get the length of a vector.
vecLength :: R2 -> Double vecLength :: Vec -> Double
vecLength v = sqrt (x^(2 :: Int) + y^(2 :: Int)) vecLength v = sqrt (x^(2 :: Int) + y^(2 :: Int))
where where
(x, y) = unr2 v (x, y) = unr2 v
-- |Compute the scalar product of two vectors. -- |Compute the scalar product of two vectors.
scalarProd :: R2 -> R2 -> Double scalarProd :: Vec -> Vec -> Double
scalarProd (R2 a1 a2) (R2 b1 b2) = a1 * b1 + a2 * b2 scalarProd (V2 a1 a2) (V2 b1 b2) = a1 * b1 + a2 * b2
-- |Multiply a scalar with a vector. -- |Multiply a scalar with a vector.
scalarMul :: Double -> R2 -> R2 scalarMul :: Double -> Vec -> Vec
scalarMul d (R2 a b) = R2 (a * d) (b * d) scalarMul d (V2 a b) = V2 (a * d) (b * d)
-- |Construct a vector that points to a point from the origin. -- |Construct a vector that points to a point from the origin.
pt2Vec :: P2 -> R2 pt2Vec :: PT -> Vec
pt2Vec = r2 . unp2 pt2Vec = r2 . unp2
-- |Give the point which is at the coordinates the vector -- |Give the point which is at the coordinates the vector
-- points to from the origin. -- points to from the origin.
vec2Pt :: R2 -> P2 vec2Pt :: Vec -> PT
vec2Pt = p2 . unr2 vec2Pt = p2 . unr2
-- |Construct a vector between two points. -- |Construct a vector between two points.
vp2 :: P2 -- ^ vector origin vp2 :: PT -- ^ vector origin
-> P2 -- ^ vector points here -> PT -- ^ vector points here
-> R2 -> Vec
vp2 a b = pt2Vec b - pt2Vec a vp2 a b = pt2Vec b - pt2Vec a
-- |Computes the determinant of 3 points. -- |Computes the determinant of 3 points.
det :: P2 -> P2 -> P2 -> Double det :: PT -> PT -> PT -> Double
det (coords -> ax :& ay) (coords -> bx :& by) (coords -> cx :& cy) = det (coords -> ax :& ay) (coords -> bx :& by) (coords -> cx :& cy) =
(bx - ax) * (cy - ay) - (by - ay) * (cx - ax) (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)
-- |Get the point where two lines intesect, if any. -- |Get the point where two lines intesect, if any.
intersectSeg' :: (P2, P2) -- ^ first segment intersectSeg' :: Segment -> Segment -> Maybe PT
-> (P2, P2) -- ^ second segment
-> Maybe P2
intersectSeg' (a, b) (c, d) = intersectSeg' (a, b) (c, d) =
glossToPt <$> intersectSegSeg (ptToGloss a) glossToPt <$> intersectSegSeg (ptToGloss a)
(ptToGloss b) (ptToGloss b)
@ -105,7 +110,7 @@ intersectSeg' (a, b) (c, d) =
-- |Get the point where two lines intesect, if any. Excludes the -- |Get the point where two lines intesect, if any. Excludes the
-- case of end-points intersecting. -- case of end-points intersecting.
intersectSeg'' :: (P2, P2) -> (P2, P2) -> Maybe P2 intersectSeg'' :: Segment -> Segment -> Maybe PT
intersectSeg'' (a, b) (c, d) = case intersectSeg' (a, b) (c, d) of intersectSeg'' (a, b) (c, d) = case intersectSeg' (a, b) (c, d) of
Just x -> if x `notElem` [a,b,c,d] then Just a else Nothing Just x -> if x `notElem` [a,b,c,d] then Just a else Nothing
Nothing -> Nothing Nothing -> Nothing
@ -115,7 +120,7 @@ intersectSeg'' (a, b) (c, d) = case intersectSeg' (a, b) (c, d) of
-- * clock-wise -- * clock-wise
-- * counter-clock-wise -- * counter-clock-wise
-- * collinear -- * collinear
getOrient :: P2 -> P2 -> P2 -> Alignment getOrient :: PT -> PT -> PT -> Alignment
getOrient a b c = case compare (det a b c) 0 of getOrient a b c = case compare (det a b c) 0 of
LT -> CW LT -> CW
GT -> CCW GT -> CCW
@ -125,7 +130,7 @@ getOrient a b c = case compare (det a b c) 0 of
--- |Checks if 3 points a,b,c do not build a clockwise triangle by --- |Checks if 3 points a,b,c do not build a clockwise triangle by
--- connecting a-b-c. This is done by computing the determinant and --- connecting a-b-c. This is done by computing the determinant and
--- checking the algebraic sign. --- checking the algebraic sign.
notcw :: P2 -> P2 -> P2 -> Bool notcw :: PT -> PT -> PT -> Bool
notcw a b c = case getOrient a b c of notcw a b c = case getOrient a b c of
CW -> False CW -> False
_ -> True _ -> True
@ -134,22 +139,22 @@ notcw a b c = case getOrient a b c of
--- |Checks if 3 points a,b,c do build a clockwise triangle by --- |Checks if 3 points a,b,c do build a clockwise triangle by
--- connecting a-b-c. This is done by computing the determinant and --- connecting a-b-c. This is done by computing the determinant and
--- checking the algebraic sign. --- checking the algebraic sign.
cw :: P2 -> P2 -> P2 -> Bool cw :: PT -> PT -> PT -> Bool
cw a b c = not . notcw a b $ c cw a b c = not . notcw a b $ c
-- |Sort X and Y coordinates lexicographically. -- |Sort X and Y coordinates lexicographically.
sortedXY :: [P2] -> [P2] sortedXY :: [PT] -> [PT]
sortedXY = fmap p2 . sortLex . fmap unp2 sortedXY = fmap p2 . sortLex . fmap unp2
-- |Sort Y and X coordinates lexicographically. -- |Sort Y and X coordinates lexicographically.
sortedYX :: [P2] -> [P2] sortedYX :: [PT] -> [PT]
sortedYX = fmap p2 . sortLexSwapped . fmap unp2 sortedYX = fmap p2 . sortLexSwapped . fmap unp2
-- |Sort all points according to their X-coordinates only. -- |Sort all points according to their X-coordinates only.
sortedX :: [P2] -> [P2] sortedX :: [PT] -> [PT]
sortedX xs = sortedX xs =
fmap p2 fmap p2
. sortBy (\(a1, _) (a2, _) -> compare a1 a2) . sortBy (\(a1, _) (a2, _) -> compare a1 a2)
@ -157,7 +162,7 @@ sortedX xs =
-- |Sort all points according to their Y-coordinates only. -- |Sort all points according to their Y-coordinates only.
sortedY :: [P2] -> [P2] sortedY :: [PT] -> [PT]
sortedY xs = sortedY xs =
fmap p2 fmap p2
. sortBy (\(_, b1) (_, b2) -> compare b1 b2) . sortBy (\(_, b1) (_, b2) -> compare b1 b2)
@ -165,25 +170,25 @@ sortedY xs =
-- |Apply a function on the coordinates of a point. -- |Apply a function on the coordinates of a point.
onPT :: ((Double, Double) -> (Double, Double)) -> P2 -> P2 onPT :: (Coord -> Coord) -> PT -> PT
onPT f = p2 . f . unp2 onPT f = p2 . f . unp2
-- |Compare the y-coordinate of two points. -- |Compare the y-coordinate of two points.
ptCmpY :: P2 -> P2 -> Ordering ptCmpY :: PT -> PT -> Ordering
ptCmpY (coords -> _ :& y1) (coords -> _ :& y2) = ptCmpY (coords -> _ :& y1) (coords -> _ :& y2) =
compare y1 y2 compare y1 y2
-- |Compare the x-coordinate of two points. -- |Compare the x-coordinate of two points.
ptCmpX :: P2 -> P2 -> Ordering ptCmpX :: PT -> PT -> Ordering
ptCmpX (coords -> x1 :& _) (coords -> x2 :& _) = ptCmpX (coords -> x1 :& _) (coords -> x2 :& _) =
compare x1 x2 compare x1 x2
posInfPT :: P2 posInfPT :: PT
posInfPT = p2 (read "Infinity", read "Infinity") posInfPT = p2 (read "Infinity", read "Infinity")
negInfPT :: P2 negInfPT :: PT
negInfPT = p2 (negate . read $ "Infinity", negate . read $ "Infinity") negInfPT = p2 (negate . read $ "Infinity", negate . read $ "Infinity")

View File

@ -3,7 +3,6 @@
module Algorithms.GrahamScan where module Algorithms.GrahamScan where
import Algebra.Vector import Algebra.Vector
import Diagrams.TwoD.Types
import MyPrelude import MyPrelude
@ -75,18 +74,18 @@ ys = []
return [(100, 100), (400, 200)] return [(100, 100), (400, 200)]
========================================================= =========================================================
--} --}
grahamCH :: [P2] -> [P2] grahamCH :: [PT] -> [PT]
grahamCH vs = grahamUCH vs ++ (tailInit . grahamLCH $ vs) grahamCH vs = grahamUCH vs ++ (tailInit . grahamLCH $ vs)
-- |Get the lower part of the convex hull. -- |Get the lower part of the convex hull.
grahamLCH :: [P2] -> [P2] grahamLCH :: [PT] -> [PT]
grahamLCH vs = uncurry (\x y -> last . scanH x $ y) grahamLCH vs = uncurry (\x y -> last . scanH x $ y)
(first reverse . splitAt 3 . sortedXY $ vs) (first reverse . splitAt 3 . sortedXY $ vs)
-- |Get the upper part of the convex hull. -- |Get the upper part of the convex hull.
grahamUCH :: [P2] -> [P2] grahamUCH :: [PT] -> [PT]
grahamUCH vs = uncurry (\x y -> last . scanH x $ y) grahamUCH vs = uncurry (\x y -> last . scanH x $ y)
(first reverse . splitAt 3 . reverse . sortedXY $ vs) (first reverse . splitAt 3 . reverse . sortedXY $ vs)
@ -96,9 +95,9 @@ grahamUCH vs = uncurry (\x y -> last . scanH x $ y)
-- If it's the upper or lower half depends on the input. -- If it's the upper or lower half depends on the input.
-- Also, the first list is expected to be reversed since we only care -- Also, the first list is expected to be reversed since we only care
-- about the last 3 elements and want to stay efficient. -- about the last 3 elements and want to stay efficient.
scanH :: [P2] -- ^ the first 3 starting points in reversed order scanH :: [PT] -- ^ the first 3 starting points in reversed order
-> [P2] -- ^ the rest of the points -> [PT] -- ^ the rest of the points
-> [[P2]] -- ^ all convex hull points iterations for the half -> [[PT]] -- ^ all convex hull points iterations for the half
scanH hs@(x:y:z:xs) (r':rs') scanH hs@(x:y:z:xs) (r':rs')
| notcw z y x = hs : scanH (r':hs) rs' | notcw z y x = hs : scanH (r':hs) rs'
| otherwise = hs : scanH (x:z:xs) (r':rs') | otherwise = hs : scanH (x:z:xs) (r':rs')
@ -112,12 +111,12 @@ scanH hs _ = [hs]
-- |Compute all steps of the graham scan algorithm to allow -- |Compute all steps of the graham scan algorithm to allow
-- visualizing it. -- visualizing it.
-- Whether the upper or lower hull is computed depends on the input. -- Whether the upper or lower hull is computed depends on the input.
grahamCHSteps :: Int -> [P2] -> [P2] -> [[P2]] grahamCHSteps :: Int -> [PT] -> [PT] -> [[PT]]
grahamCHSteps c xs' ys' = take c . scanH xs' $ ys' grahamCHSteps c xs' ys' = take c . scanH xs' $ ys'
-- |Get all iterations of the upper hull of the graham scan algorithm. -- |Get all iterations of the upper hull of the graham scan algorithm.
grahamUHSteps :: [P2] -> [[P2]] grahamUHSteps :: [PT] -> [[PT]]
grahamUHSteps vs = grahamUHSteps vs =
(++) [getLastX 2 . sortedXY $ vs] (++) [getLastX 2 . sortedXY $ vs]
. rmdups . rmdups
@ -128,7 +127,7 @@ grahamUHSteps vs =
-- |Get all iterations of the lower hull of the graham scan algorithm. -- |Get all iterations of the lower hull of the graham scan algorithm.
grahamLHSteps :: [P2] -> [[P2]] grahamLHSteps :: [PT] -> [[PT]]
grahamLHSteps vs = grahamLHSteps vs =
(++) [take 2 . sortedXY $ vs] (++) [take 2 . sortedXY $ vs]
. rmdups . rmdups

View File

@ -42,9 +42,9 @@ instance Not Direction where
-- |Construct a kd-tree from a list of points in O(n log n). -- |Construct a kd-tree from a list of points in O(n log n).
kdTree :: [P2] -- ^ list of points to construct the kd-tree from kdTree :: [PT] -- ^ list of points to construct the kd-tree from
-> Direction -- ^ initial direction of the root-node -> Direction -- ^ initial direction of the root-node
-> KDTree P2 -- ^ resulting kd-tree -> KDTree PT -- ^ resulting kd-tree
kdTree xs' = go (sortedX xs') (sortedY xs') kdTree xs' = go (sortedX xs') (sortedY xs')
where where
go [] _ _ = KTNil go [] _ _ = KTNil
@ -67,10 +67,10 @@ kdTree xs' = go (sortedX xs') (sortedY xs')
-- If you want to partition against the pivot of X, then you pass -- If you want to partition against the pivot of X, then you pass
-- partition' (pivot xs) (ys, xs) -- partition' (pivot xs) (ys, xs)
-- and get ((y1, y2), (x1, x2)). -- and get ((y1, y2), (x1, x2)).
partition' :: P2 -- ^ the pivot to partition against partition' :: PT -- ^ the pivot to partition against
-> (P2 -> P2 -> Ordering) -- ^ ptCmpY or ptCmpX -> (PT -> PT -> Ordering) -- ^ ptCmpY or ptCmpX
-> ([P2], [P2]) -- ^ both lists (X, Y) or (Y, X) -> ([PT], [PT]) -- ^ both lists (X, Y) or (Y, X)
-> (([P2], [P2]), ([P2], [P2])) -- ^ ((x1, x2), (y1, y2)) or -> (([PT], [PT]), ([PT], [PT])) -- ^ ((x1, x2), (y1, y2)) or
-- ((y1, y2), (x1, x2)) -- ((y1, y2), (x1, x2))
partition' piv cmp' (xs, ys) = ((x1, x2), (y1, y2)) partition' piv cmp' (xs, ys) = ((x1, x2), (y1, y2))
where where
@ -83,16 +83,16 @@ partition' piv cmp' (xs, ys) = ((x1, x2), (y1, y2))
-- |Partition two sorted lists of points X and Y against the pivot of -- |Partition two sorted lists of points X and Y against the pivot of
-- Y. This function is unsafe as it does not check if there is a valid -- Y. This function is unsafe as it does not check if there is a valid
-- pivot. -- pivot.
partitionY :: ([P2], [P2]) -- ^ both lists (X, Y) partitionY :: ([PT], [PT]) -- ^ both lists (X, Y)
-> (([P2], [P2]), ([P2], [P2])) -- ^ ((x1, x2), (y1, y2)) -> (([PT], [PT]), ([PT], [PT])) -- ^ ((x1, x2), (y1, y2))
partitionY (xs, ys) = partition' (fromJust . pivot $ ys) ptCmpY (xs, ys) partitionY (xs, ys) = partition' (fromJust . pivot $ ys) ptCmpY (xs, ys)
-- |Partition two sorted lists of points X and Y against the pivot of -- |Partition two sorted lists of points X and Y against the pivot of
-- X. This function is unsafe as it does not check if there is a valid -- X. This function is unsafe as it does not check if there is a valid
-- pivot. -- pivot.
partitionX :: ([P2], [P2]) -- ^ both lists (X, Y) partitionX :: ([PT], [PT]) -- ^ both lists (X, Y)
-> (([P2], [P2]), ([P2], [P2])) -- ^ ((x1, x2), (y1, y2)) -> (([PT], [PT]), ([PT], [PT])) -- ^ ((x1, x2), (y1, y2))
partitionX (xs, ys) = (\(x, y) -> (y, x)) partitionX (xs, ys) = (\(x, y) -> (y, x))
. partition' (fromJust . pivot $ xs) ptCmpX $ (ys, xs) . partition' (fromJust . pivot $ xs) ptCmpX $ (ys, xs)
@ -100,9 +100,7 @@ partitionX (xs, ys) = (\(x, y) -> (y, x))
-- |Execute a range search in O(log n). It returns a tuple -- |Execute a range search in O(log n). It returns a tuple
-- of the points found in the range and also gives back a pretty -- of the points found in the range and also gives back a pretty
-- rose tree suitable for printing. -- rose tree suitable for printing.
rangeSearch :: KDTree P2 -- ^ tree to search in rangeSearch :: KDTree PT -> Square -> ([PT], Tree String)
-> ((Double, Double), (Double, Double)) -- ^ square describing the range
-> ([P2], Tree String)
rangeSearch kd' sq' = (goPt kd' sq', goTree kd' sq' True) rangeSearch kd' sq' = (goPt kd' sq', goTree kd' sq' True)
where where
-- either y1 or x1 depending on the orientation -- either y1 or x1 depending on the orientation
@ -112,7 +110,7 @@ rangeSearch kd' sq' = (goPt kd' sq', goTree kd' sq' True)
-- either the second or first of the tuple, depending on the orientation -- either the second or first of the tuple, depending on the orientation
cur' dir = if' (dir == Vertical) snd fst cur' dir = if' (dir == Vertical) snd fst
-- All points in the range. -- All points in the range.
goPt :: KDTree P2 -> ((Double, Double), (Double, Double)) -> [P2] goPt :: KDTree PT -> Square -> [PT]
goPt KTNil _ = [] goPt KTNil _ = []
goPt (KTNode ln pt dir rn) sq = goPt (KTNode ln pt dir rn) sq =
[pt | inRange sq pt] [pt | inRange sq pt]
@ -124,7 +122,7 @@ rangeSearch kd' sq' = (goPt kd' sq', goTree kd' sq' True)
(goPt rn sq) (goPt rn sq)
[]) [])
-- A pretty rose tree suitable for printing. -- A pretty rose tree suitable for printing.
goTree :: KDTree P2 -> ((Double, Double), (Double, Double)) -> Bool -> Tree String goTree :: KDTree PT -> Square -> Bool -> Tree String
goTree KTNil _ _ = Node "nil" [] goTree KTNil _ _ = Node "nil" []
goTree (KTNode ln pt dir rn) sq vis goTree (KTNode ln pt dir rn) sq vis
| ln == KTNil && rn == KTNil = Node treeText [] | ln == KTNil && rn == KTNil = Node treeText []
@ -181,7 +179,7 @@ getDirection _ = Nothing
-- |Convert a kd-tree to a rose tree, for pretty printing. -- |Convert a kd-tree to a rose tree, for pretty printing.
kdTreeToRoseTree :: KDTree P2 -> Tree String kdTreeToRoseTree :: KDTree PT -> Tree String
kdTreeToRoseTree (KTNil) = Node "nil" [] kdTreeToRoseTree (KTNil) = Node "nil" []
kdTreeToRoseTree (KTNode ln val _ rn) = kdTreeToRoseTree (KTNode ln val _ rn) =
Node (show . unp2 $ val) [kdTreeToRoseTree ln, kdTreeToRoseTree rn] Node (show . unp2 $ val) [kdTreeToRoseTree ln, kdTreeToRoseTree rn]

View File

@ -18,14 +18,14 @@ import QueueEx
-- successor are saved for convenience. -- successor are saved for convenience.
data PolyPT = data PolyPT =
PolyA { PolyA {
id' :: P2 id' :: PT
, pre :: P2 , pre :: PT
, suc :: P2 , suc :: PT
} }
| PolyB { | PolyB {
id' :: P2 id' :: PT
, pre :: P2 , pre :: PT
, suc :: P2 , suc :: PT
} }
deriving (Show, Eq) deriving (Show, Eq)
@ -42,7 +42,7 @@ isPolyB = not . isPolyA
-- |Shift a list of sorted convex hull points of a polygon so that -- |Shift a list of sorted convex hull points of a polygon so that
-- the first element in the list is the one with the highest y-coordinate. -- the first element in the list is the one with the highest y-coordinate.
-- This is done in O(n). -- This is done in O(n).
sortLexPoly :: [P2] -> [P2] sortLexPoly :: [PT] -> [PT]
sortLexPoly ps = maybe [] (`shiftM` ps) (elemIndex (yMax ps) ps) sortLexPoly ps = maybe [] (`shiftM` ps) (elemIndex (yMax ps) ps)
where where
yMax = foldl1 (\x y -> if ptCmpY x y == GT then x else y) yMax = foldl1 (\x y -> if ptCmpY x y == GT then x else y)
@ -50,8 +50,8 @@ sortLexPoly ps = maybe [] (`shiftM` ps) (elemIndex (yMax ps) ps)
-- |Make a PolyPT list out of a regular list of points, so -- |Make a PolyPT list out of a regular list of points, so
-- the predecessor and successors are all saved. -- the predecessor and successors are all saved.
mkPolyPTList :: (P2 -> P2 -> P2 -> PolyPT) -- ^ PolyA or PolyB function mkPolyPTList :: (PT -> PT -> PT -> PolyPT) -- ^ PolyA or PolyB function
-> [P2] -- ^ polygon points -> [PT] -- ^ polygon points
-> [PolyPT] -> [PolyPT]
mkPolyPTList f' pts@(x':y':_:_) = mkPolyPTList f' pts@(x':y':_:_) =
f' x' (last pts) y' : go f' pts f' x' (last pts) y' : go f' pts
@ -64,7 +64,7 @@ mkPolyPTList _ _ = []
-- |Sort the points of two polygons according to their y-coordinates, -- |Sort the points of two polygons according to their y-coordinates,
-- while saving the origin of that point. This is done in O(n). -- while saving the origin of that point. This is done in O(n).
sortLexPolys :: ([P2], [P2]) -> [PolyPT] sortLexPolys :: ([PT], [PT]) -> [PolyPT]
sortLexPolys (pA'@(_:_), pB'@(_:_)) = sortLexPolys (pA'@(_:_), pB'@(_:_)) =
queueToList $ go (Q.fromList . mkPolyPTList PolyA . sortLexPoly $ pA') queueToList $ go (Q.fromList . mkPolyPTList PolyA . sortLexPoly $ pA')
(Q.fromList . mkPolyPTList PolyB . sortLexPoly $ pB') (Q.fromList . mkPolyPTList PolyB . sortLexPoly $ pB')
@ -104,7 +104,7 @@ sortLexPolys _ = []
-- |Get all points that intersect between both polygons. This is done -- |Get all points that intersect between both polygons. This is done
-- in O(n). -- in O(n).
intersectionPoints :: [PolyPT] -> [P2] intersectionPoints :: [PolyPT] -> [PT]
intersectionPoints xs' = rmdups . go $ xs' intersectionPoints xs' = rmdups . go $ xs'
where where
go [] = [] go [] = []
@ -113,7 +113,7 @@ intersectionPoints xs' = rmdups . go $ xs'
-- Get the scan line or in other words the -- Get the scan line or in other words the
-- Segment pairs we are going to check for intersection. -- Segment pairs we are going to check for intersection.
scanLine :: [PolyPT] -> ([(P2, P2)], [(P2, P2)]) scanLine :: [PolyPT] -> ([Segment], [Segment])
scanLine sp@(_:_) = (,) (getSegment isPolyA) (getSegment isPolyB) scanLine sp@(_:_) = (,) (getSegment isPolyA) (getSegment isPolyB)
where where
getSegment f = fromMaybe [] getSegment f = fromMaybe []
@ -124,7 +124,7 @@ intersectionPoints xs' = rmdups . go $ xs'
-- Gets the actual intersections between the segments of -- Gets the actual intersections between the segments of
-- both polygons we currently examine. This is done in O(1) -- both polygons we currently examine. This is done in O(1)
-- since we have max 4 segments. -- since we have max 4 segments.
segIntersections :: ([(P2, P2)], [(P2, P2)]) -> [P2] segIntersections :: ([Segment], [Segment]) -> [PT]
segIntersections (a@(_:_), b@(_:_)) = segIntersections (a@(_:_), b@(_:_)) =
catMaybes catMaybes
. fmap (\[x, y] -> intersectSeg' x y) . fmap (\[x, y] -> intersectSeg' x y)

View File

@ -6,7 +6,6 @@ import Algebra.Polygon
import Algebra.Vector import Algebra.Vector
import qualified Control.Arrow as A import qualified Control.Arrow as A
import Data.Maybe import Data.Maybe
import Diagrams.TwoD.Types
import Safe import Safe
@ -19,12 +18,12 @@ data VCategory = VStart
-- |Classify all vertices on a polygon into five categories (see VCategory). -- |Classify all vertices on a polygon into five categories (see VCategory).
classifyList :: [P2] -> [(P2, VCategory)] classifyList :: [PT] -> [(PT, VCategory)]
classifyList p@(x:y:_:_) = classifyList p@(x:y:_:_) =
-- need to handle the first and last element separately -- need to handle the first and last element separately
[classify (last p) x y] ++ go p ++ [classify (last . init $ p) (last p) x] [classify (last p) x y] ++ go p ++ [classify (last . init $ p) (last p) x]
where where
go :: [P2] -> [(P2, VCategory)] go :: [PT] -> [(PT, VCategory)]
go (x':y':z':xs) = classify x' y' z' : go (y':z':xs) go (x':y':z':xs) = classify x' y' z' : go (y':z':xs)
go _ = [] go _ = []
classifyList _ = [] classifyList _ = []
@ -32,10 +31,10 @@ classifyList _ = []
-- |Classify a vertex on a polygon given it's next and previous vertex -- |Classify a vertex on a polygon given it's next and previous vertex
-- into five categories (see VCategory). -- into five categories (see VCategory).
classify :: P2 -- ^ prev vertex classify :: PT -- ^ prev vertex
-> P2 -- ^ classify this one -> PT -- ^ classify this one
-> P2 -- ^ next vertex -> PT -- ^ next vertex
-> (P2, VCategory) -> (PT, VCategory)
classify prev v next classify prev v next
| isVStart prev v next = (v, VStart) | isVStart prev v next = (v, VStart)
| isVSplit prev v next = (v, VSplit) | isVSplit prev v next = (v, VSplit)
@ -46,9 +45,9 @@ classify prev v next
-- |Whether the vertex, given it's next and previous vertex, -- |Whether the vertex, given it's next and previous vertex,
-- is a start vertex. -- is a start vertex.
isVStart :: P2 -- ^ previous vertex isVStart :: PT -- ^ previous vertex
-> P2 -- ^ vertice to check -> PT -- ^ vertice to check
-> P2 -- ^ next vertex -> PT -- ^ next vertex
-> Bool -> Bool
isVStart prev v next = isVStart prev v next =
ptCmpY next v == LT && ptCmpY prev v == LT && cw next v prev ptCmpY next v == LT && ptCmpY prev v == LT && cw next v prev
@ -56,9 +55,9 @@ isVStart prev v next =
-- |Whether the vertex, given it's next and previous vertex, -- |Whether the vertex, given it's next and previous vertex,
-- is a split vertex. -- is a split vertex.
isVSplit :: P2 -- ^ previous vertex isVSplit :: PT -- ^ previous vertex
-> P2 -- ^ vertice to check -> PT -- ^ vertice to check
-> P2 -- ^ next vertex -> PT -- ^ next vertex
-> Bool -> Bool
isVSplit prev v next = isVSplit prev v next =
ptCmpY prev v == LT && ptCmpY next v == LT && cw prev v next ptCmpY prev v == LT && ptCmpY next v == LT && cw prev v next
@ -66,9 +65,9 @@ isVSplit prev v next =
-- |Whether the vertex, given it's next and previous vertex, -- |Whether the vertex, given it's next and previous vertex,
-- is an end vertex. -- is an end vertex.
isVEnd :: P2 -- ^ previous vertex isVEnd :: PT -- ^ previous vertex
-> P2 -- ^ vertice to check -> PT -- ^ vertice to check
-> P2 -- ^ next vertex -> PT -- ^ next vertex
-> Bool -> Bool
isVEnd prev v next = isVEnd prev v next =
ptCmpY prev v == GT && ptCmpY next v == GT && cw next v prev ptCmpY prev v == GT && ptCmpY next v == GT && cw next v prev
@ -76,9 +75,9 @@ isVEnd prev v next =
-- |Whether the vertex, given it's next and previous vertex, -- |Whether the vertex, given it's next and previous vertex,
-- is a merge vertex. -- is a merge vertex.
isVMerge :: P2 -- ^ previous vertex isVMerge :: PT -- ^ previous vertex
-> P2 -- ^ vertice to check -> PT -- ^ vertice to check
-> P2 -- ^ next vertex -> PT -- ^ next vertex
-> Bool -> Bool
isVMerge prev v next = isVMerge prev v next =
ptCmpY next v == GT && ptCmpY prev v == GT && cw prev v next ptCmpY next v == GT && ptCmpY prev v == GT && cw prev v next
@ -86,9 +85,9 @@ isVMerge prev v next =
-- |Whether the vertex, given it's next and previous vertex, -- |Whether the vertex, given it's next and previous vertex,
-- is a regular vertex. -- is a regular vertex.
isVRegular :: P2 -- ^ previous vertex isVRegular :: PT -- ^ previous vertex
-> P2 -- ^ vertice to check -> PT -- ^ vertice to check
-> P2 -- ^ next vertex -> PT -- ^ next vertex
-> Bool -> Bool
isVRegular prev v next = isVRegular prev v next =
(not . isVStart prev v $ next) (not . isVStart prev v $ next)
@ -99,7 +98,7 @@ isVRegular prev v next =
-- |A polygon P is y-monotone, if it has no split and merge vertices. -- |A polygon P is y-monotone, if it has no split and merge vertices.
isYmonotone :: [P2] -> Bool isYmonotone :: [PT] -> Bool
isYmonotone poly = isYmonotone poly =
not not
. any (\x -> x == VSplit || x == VMerge) . any (\x -> x == VSplit || x == VMerge)
@ -108,12 +107,12 @@ isYmonotone poly =
-- |Partition P into y-monotone pieces. -- |Partition P into y-monotone pieces.
monotonePartitioning :: [P2] -> [[P2]] monotonePartitioning :: [PT] -> [[PT]]
monotonePartitioning pts monotonePartitioning pts
| isYmonotone pts = [pts] | isYmonotone pts = [pts]
| otherwise = go (monotoneDiagonals pts) pts | otherwise = go (monotoneDiagonals pts) pts
where where
go :: [(P2, P2)] -> [P2] -> [[P2]] go :: [Segment] -> [PT] -> [[PT]]
go (x:xs) pts'@(_:_) go (x:xs) pts'@(_:_)
| isYmonotone a && isYmonotone b = [a, b] | isYmonotone a && isYmonotone b = [a, b]
| isYmonotone b = b : go xs a | isYmonotone b = b : go xs a
@ -125,37 +124,37 @@ monotonePartitioning pts
-- |Try to eliminate the merge and split vertices by computing the -- |Try to eliminate the merge and split vertices by computing the
-- diagonals we have to use for splitting the polygon. -- diagonals we have to use for splitting the polygon.
monotoneDiagonals :: [P2] -> [(P2, P2)] monotoneDiagonals :: [PT] -> [Segment]
monotoneDiagonals pts = catMaybes . go $ classifyList pts monotoneDiagonals pts = catMaybes . go $ classifyList pts
where where
go :: [(P2, VCategory)] -> [Maybe (P2, P2)] go :: [(PT, VCategory)] -> [Maybe Segment]
go (x:xs) = case snd x of go (x:xs) = case snd x of
VMerge -> getSeg (belowS . fst $ x) (fst x) : go xs VMerge -> getSeg (belowS . fst $ x) (fst x) : go xs
VSplit -> getSeg (aboveS . fst $ x) (fst x) : go xs VSplit -> getSeg (aboveS . fst $ x) (fst x) : go xs
_ -> [] ++ go xs _ -> [] ++ go xs
go [] = [] go [] = []
getSeg :: [P2] -- all points above/below the current point getSeg :: [PT] -- all points above/below the current point
-> P2 -- current point -> PT -- current point
-> Maybe (P2, P2) -> Maybe Segment
getSeg [] _ = Nothing getSeg [] _ = Nothing
getSeg (z:zs) pt getSeg (z:zs) pt
| isInsidePoly pts (z, pt) = Just (z, pt) | isInsidePoly pts (z, pt) = Just (z, pt)
| otherwise = getSeg zs pt | otherwise = getSeg zs pt
aboveS :: P2 -> [P2] aboveS :: PT -> [PT]
aboveS pt = tail . dropWhile (/= pt) $ sortedYX pts aboveS pt = tail . dropWhile (/= pt) $ sortedYX pts
belowS :: P2 -> [P2] belowS :: PT -> [PT]
belowS pt = reverse . takeWhile (/= pt) $ sortedYX pts belowS pt = reverse . takeWhile (/= pt) $ sortedYX pts
-- |Triangulate a y-monotone polygon. -- |Triangulate a y-monotone polygon.
triangulate :: [P2] -> [[P2]] triangulate :: [PT] -> [[PT]]
triangulate pts = triangulate pts =
go pts . A.first reverse . splitAt 3 . reverse . sortedYX $ pts go pts . A.first reverse . splitAt 3 . reverse . sortedYX $ pts
where where
go :: [P2] -- current polygon go :: [PT] -- current polygon
-> ([P2], [P2]) -- (stack of visited vertices, rest) -> ([PT], [PT]) -- (stack of visited vertices, rest)
-- sorted by Y-coordinate -- sorted by Y-coordinate
-> [[P2]] -> [[PT]]
go xs (p@[_, _], r:rs) = go xs (r:p, rs) go xs (p@[_, _], r:rs) = go xs (r:p, rs)
go xs (p@(u:vi:vi1:ys), rs) go xs (p@(u:vi:vi1:ys), rs)
-- case 1 and 3 -- case 1 and 3

View File

@ -56,8 +56,7 @@ data Orient = North | South | East | West
-- |Get a sub-square of the current square, e.g. nw, ne, sw or se. -- |Get a sub-square of the current square, e.g. nw, ne, sw or se.
nwSq, neSq, swSq, seSq :: ((Double, Double), (Double, Double)) -- ^ current square nwSq, neSq, swSq, seSq :: Square -> Square
-> ((Double, Double), (Double, Double)) -- ^ sub-square
nwSq ((xl, yl), (xu, yu)) = (,) (xl, (yl + yu) / 2) ((xl + xu) / 2, yu) nwSq ((xl, yl), (xu, yu)) = (,) (xl, (yl + yu) / 2) ((xl + xu) / 2, yu)
neSq ((xl, yl), (xu, yu)) = (,) ((xl + xu) / 2, (yl + yu) / 2) (xu, yu) neSq ((xl, yl), (xu, yu)) = (,) ((xl + xu) / 2, (yl + yu) / 2) (xu, yu)
swSq ((xl, yl), (xu, yu)) = (,) (xl, yl) ((xl + xu) / 2, (yl + yu) / 2) swSq ((xl, yl), (xu, yu)) = (,) (xl, yl) ((xl + xu) / 2, (yl + yu) / 2)
@ -80,9 +79,9 @@ isSEchild _ = False
-- |Builds a quadtree of a list of points which recursively divides up 2D -- |Builds a quadtree of a list of points which recursively divides up 2D
-- space into quadrants, so that every leaf-quadrant stores either zero or one -- space into quadrants, so that every leaf-quadrant stores either zero or one
-- point. -- point.
quadTree :: [P2] -- ^ the points to divide quadTree :: [PT] -- ^ the points to divide
-> ((Double, Double), (Double, Double)) -- ^ the initial square around the points -> Square -- ^ the initial square around the points
-> QuadTree P2 -- ^ the quad tree -> QuadTree PT -- ^ the quad tree
quadTree [] _ = TNil quadTree [] _ = TNil
quadTree [pt] _ = TLeaf pt quadTree [pt] _ = TLeaf pt
quadTree pts sq = TNode (quadTree nWPT . nwSq $ sq) (quadTree nEPT . neSq $ sq) quadTree pts sq = TNode (quadTree nWPT . nwSq $ sq) (quadTree nEPT . neSq $ sq)
@ -96,9 +95,9 @@ quadTree pts sq = TNode (quadTree nWPT . nwSq $ sq) (quadTree nEPT . neSq $ sq)
-- |Get all squares of a quad tree. -- |Get all squares of a quad tree.
quadTreeSquares :: ((Double, Double), (Double, Double)) -- ^ the initial square around the points quadTreeSquares :: Square -- ^ the initial square around the points
-> QuadTree P2 -- ^ the quad tree -> QuadTree PT -- ^ the quad tree
-> [((Double, Double), (Double, Double))] -- ^ all squares of the quad tree -> [Square] -- ^ all squares of the quad tree
quadTreeSquares sq (TNil) = [sq] quadTreeSquares sq (TNil) = [sq]
quadTreeSquares sq (TLeaf _) = [sq] quadTreeSquares sq (TLeaf _) = [sq]
quadTreeSquares sq (TNode nw ne sw se) = quadTreeSquares sq (TNode nw ne sw se) =
@ -108,9 +107,7 @@ quadTreeSquares sq (TNode nw ne sw se) =
-- |Get the current square of the zipper, relative to the given top -- |Get the current square of the zipper, relative to the given top
-- square. -- square.
getSquareByZipper :: ((Double, Double), (Double, Double)) -- ^ top square getSquareByZipper :: Square -> QTZipper a -> Square
-> QTZipper a
-> ((Double, Double), (Double, Double)) -- ^ current square
getSquareByZipper sq z = go sq (reverse . snd $ z) getSquareByZipper sq z = go sq (reverse . snd $ z)
where where
go sq' [] = sq' go sq' [] = sq'
@ -203,7 +200,7 @@ lookupByNeighbors :: [Orient] -> QTZipper a -> Maybe (QTZipper a)
lookupByNeighbors = flip (foldlM (flip findNeighbor)) lookupByNeighbors = flip (foldlM (flip findNeighbor))
quadTreeToRoseTree :: QTZipper P2 -> Tree String quadTreeToRoseTree :: QTZipper PT -> Tree String
quadTreeToRoseTree z' = go (rootNode z') quadTreeToRoseTree z' = go (rootNode z')
where where
go z = case z of go z = case z of

View File

@ -65,6 +65,7 @@ executable Gtk
Graphics.Diagram.Core Graphics.Diagram.Core
Graphics.Diagram.Gtk Graphics.Diagram.Gtk
Graphics.Diagram.Plotter Graphics.Diagram.Plotter
Graphics.HalfEdge
GUI.Gtk GUI.Gtk
MyPrelude MyPrelude
Parser.Meshparser Parser.Meshparser
@ -76,21 +77,20 @@ executable Gtk
-- Other library packages from which modules are imported. -- Other library packages from which modules are imported.
build-depends: attoparsec >= 0.12.1.1, build-depends: attoparsec >= 0.12.1.1,
base >=4.6 && <4.8, base >=4.6,
bytestring >= 0.10.4.0, bytestring >= 0.10.4.0,
containers >= 0.5.0.0, containers >= 0.5.0.0,
dequeue >= 0.1.5, dequeue >= 0.1.5,
diagrams-lib >=1.2 && <1.3, diagrams-lib >=1.3,
diagrams-cairo >=1.2 && <1.3, diagrams-cairo >=1.3,
diagrams-contrib >= 1.1.2.1, diagrams-contrib >= 1.3.0.0,
directory >=1.2 && <1.3, directory >=1.2,
filepath >= 1.3.0.2, filepath >= 1.3.0.2,
glade >=0.12 && <0.13, glade >=0.12,
gloss >= 1.2.0.1, gloss >= 1.2.0.1,
gtk >=0.12 && <0.13, gtk >=0.12,
multiset-comb >= 0.2.1,
safe >= 0.3.8, safe >= 0.3.8,
transformers >=0.4 && <0.5 transformers >=0.4
-- Directories containing source files. -- Directories containing source files.
-- hs-source-dirs: -- hs-source-dirs:
@ -115,6 +115,7 @@ executable Gif
Graphics.Diagram.Core Graphics.Diagram.Core
Graphics.Diagram.Gif Graphics.Diagram.Gif
Graphics.Diagram.Plotter Graphics.Diagram.Plotter
Graphics.HalfEdge
MyPrelude MyPrelude
Parser.Meshparser Parser.Meshparser
Parser.PathParser Parser.PathParser
@ -126,18 +127,17 @@ executable Gif
-- Other library packages from which modules are imported. -- Other library packages from which modules are imported.
build-depends: attoparsec >= 0.12.1.1, build-depends: attoparsec >= 0.12.1.1,
base >=4.6 && <4.8, base >=4.6,
bytestring >= 0.10.4.0, bytestring >= 0.10.4.0,
containers >= 0.5.0.0, containers >= 0.5.0.0,
dequeue >= 0.1.5, dequeue >= 0.1.5,
diagrams-lib >=1.2 && <1.3, diagrams-lib >=1.3,
diagrams-cairo >=1.2 && <1.3, diagrams-cairo >=1.3,
diagrams-contrib >= 1.1.2.1, diagrams-contrib >= 1.3.0.0,
gloss >= 1.2.0.1, gloss >= 1.2.0.1,
JuicyPixels >= 3.1.7.1, JuicyPixels >= 3.1.7.1,
multiset-comb >= 0.2.1, safe >= 0.3.8,
transformers >=0.4 && <0.5, transformers >=0.4
safe >= 0.3.8
-- Directories containing source files. -- Directories containing source files.
-- hs-source-dirs: -- hs-source-dirs:
@ -162,6 +162,7 @@ executable Test
Graphics.Diagram.Core Graphics.Diagram.Core
Graphics.Diagram.Gif Graphics.Diagram.Gif
Graphics.Diagram.Plotter Graphics.Diagram.Plotter
Graphics.HalfEdge
MyPrelude MyPrelude
Parser.Meshparser Parser.Meshparser
Parser.PathParser Parser.PathParser
@ -175,18 +176,14 @@ executable Test
-- Other library packages from which modules are imported. -- Other library packages from which modules are imported.
build-depends: attoparsec >= 0.12.1.1, build-depends: attoparsec >= 0.12.1.1,
base >=4.6 && <4.8, base >=4.6,
bytestring >= 0.10.4.0, bytestring >= 0.10.4.0,
containers >= 0.5.0.0, containers >= 0.5.0.0,
dequeue >= 0.1.5, diagrams-lib >=1.3,
diagrams-lib >=1.2 && <1.3, diagrams-cairo >=1.3,
diagrams-cairo >=1.2 && <1.3, diagrams-contrib >= 1.3.0.0,
diagrams-contrib >= 1.1.2.1,
gloss >= 1.2.0.1, gloss >= 1.2.0.1,
JuicyPixels >= 3.1.7.1,
multiset-comb >= 0.2.1,
QuickCheck >= 2.4.2, QuickCheck >= 2.4.2,
transformers >=0.4 && <0.5,
safe >= 0.3.8 safe >= 0.3.8
-- Directories containing source files. -- Directories containing source files.

View File

@ -63,9 +63,9 @@ data MyGUI = MkMyGUI {
-- |Path entry widget for the quad tree. -- |Path entry widget for the quad tree.
quadPathEntry :: Entry, quadPathEntry :: Entry,
-- |Horizontal box containing the path entry widget. -- |Horizontal box containing the path entry widget.
vbox7 :: Box, vbox7 :: Graphics.UI.Gtk.Box,
-- |Horizontal box containing the Rang search entry widgets. -- |Horizontal box containing the Rang search entry widgets.
vbox10 :: Box, vbox10 :: Graphics.UI.Gtk.Box,
-- |Range entry widget for lower x bound -- |Range entry widget for lower x bound
rangeXminEntry :: Entry, rangeXminEntry :: Entry,
-- |Range entry widget for upper x bound -- |Range entry widget for upper x bound
@ -299,9 +299,9 @@ saveAndDrawDiag fp fps mygui =
renderDiag winWidth winHeight buildDiag = renderDiag winWidth winHeight buildDiag =
renderDia Cairo renderDia Cairo
(CairoOptions fps (CairoOptions fps
(Dims (fromIntegral winWidth) (fromIntegral winHeight)) (mkSizeSpec2D (Just $ fromIntegral winWidth) (Just $ fromIntegral winHeight))
SVG False) SVG False)
(buildDiag (def{ (buildDiag (MyPrelude.def{
dotSize = scaleVal, dotSize = scaleVal,
xDimension = fromMaybe (0, 500) xDim, xDimension = fromMaybe (0, 500) xDim,
yDimension = fromMaybe (0, 500) yDim, yDimension = fromMaybe (0, 500) yDim,

View File

@ -2,6 +2,7 @@
module Graphics.Diagram.AlgoDiags where module Graphics.Diagram.AlgoDiags where
import Algebra.Vector(PT,Square)
import Algorithms.GrahamScan import Algorithms.GrahamScan
import Algorithms.QuadTree import Algorithms.QuadTree
import Algorithms.KDTree import Algorithms.KDTree
@ -123,9 +124,7 @@ kdSquares = Diag f
where where
-- Gets all lines that make up the kdSquares. Every line is -- Gets all lines that make up the kdSquares. Every line is
-- described by two points, start and end respectively. -- described by two points, start and end respectively.
kdLines :: KDTree P2 kdLines :: KDTree PT -> Square -> [(PT, PT)]
-> ((Double, Double), (Double, Double)) -- ^ square
-> [(P2, P2)]
kdLines (KTNode ln pt Horizontal rn) ((xmin, ymin), (xmax, ymax)) = kdLines (KTNode ln pt Horizontal rn) ((xmin, ymin), (xmax, ymax)) =
(\(x, _) -> [(p2 (x, ymin), p2 (x, ymax))]) (\(x, _) -> [(p2 (x, ymin), p2 (x, ymax))])
(unp2 pt) (unp2 pt)
@ -180,7 +179,7 @@ kdTreeDiag = Diag f
-- |Get the quad tree corresponding to the given points and diagram properties. -- |Get the quad tree corresponding to the given points and diagram properties.
qt :: [P2] -> DiagProp -> QuadTree P2 qt :: [PT] -> DiagProp -> QuadTree PT
qt vt p = quadTree vt (diagDimSquare p) qt vt p = quadTree vt (diagDimSquare p)
@ -193,9 +192,7 @@ quadPathSquare = Diag f
(uncurry rectByDiagonal # lw thin # lc red) (uncurry rectByDiagonal # lw thin # lc red)
(getSquare (stringToQuads (quadPath p)) (qt (mconcat vts) p, [])) (getSquare (stringToQuads (quadPath p)) (qt (mconcat vts) p, []))
where where
getSquare :: [Either Quad Orient] getSquare :: [Either Quad Orient] -> QTZipper PT -> Square
-> QTZipper P2
-> ((Double, Double), (Double, Double))
getSquare [] z = getSquareByZipper (diagDimSquare p) z getSquare [] z = getSquareByZipper (diagDimSquare p) z
getSquare (q:qs) z = case q of getSquare (q:qs) z = case q of
Right x -> getSquare qs (fromMaybe z (findNeighbor x z)) Right x -> getSquare qs (fromMaybe z (findNeighbor x z))
@ -211,9 +208,7 @@ gifQuadPath = GifDiag f
(uncurry rectByDiagonal # lw thick # lc col) (uncurry rectByDiagonal # lw thick # lc col)
<$> getSquares (stringToQuads (quadPath p)) (qt vt p, []) <$> getSquares (stringToQuads (quadPath p)) (qt vt p, [])
where where
getSquares :: [Either Quad Orient] getSquares :: [Either Quad Orient] -> QTZipper PT -> [Square]
-> QTZipper P2
-> [((Double, Double), (Double, Double))]
getSquares [] z = [getSquareByZipper (diagDimSquare p) z] getSquares [] z = [getSquareByZipper (diagDimSquare p) z]
getSquares (q:qs) z = case q of getSquares (q:qs) z = case q of
Right x -> getSquareByZipper (diagDimSquare p) z : Right x -> getSquareByZipper (diagDimSquare p) z :
@ -233,12 +228,12 @@ treePretty = Diag f
. quadPath . quadPath
$ p) $ p)
where where
getCurQT :: [Either Quad Orient] -> QTZipper P2 -> QTZipper P2 getCurQT :: [Either Quad Orient] -> QTZipper PT -> QTZipper PT
getCurQT [] z = z getCurQT [] z = z
getCurQT (q:qs) z = case q of getCurQT (q:qs) z = case q of
Right x -> getCurQT qs (fromMaybe z (findNeighbor x z)) Right x -> getCurQT qs (fromMaybe z (findNeighbor x z))
Left x -> getCurQT qs (fromMaybe z (goQuad x z)) Left x -> getCurQT qs (fromMaybe z (goQuad x z))
prettyRoseTree :: Tree String -> Diagram Cairo R2 prettyRoseTree :: Tree String -> Diagram Cairo
prettyRoseTree tree = prettyRoseTree tree =
-- HACK: in order to give specific nodes a specific color -- HACK: in order to give specific nodes a specific color
renderTree (\n -> case head n of renderTree (\n -> case head n of

View File

@ -15,18 +15,18 @@ data Diag =
Diag Diag
{ {
mkDiag :: DiagProp mkDiag :: DiagProp
-> [[P2]] -> [[PT]]
-> Diagram Cairo R2 -> Diagram Cairo
} }
| GifDiag | GifDiag
{ {
mkGifDiag :: DiagProp mkGifDiag :: DiagProp
-> Colour Double -> Colour Double
-> ([P2] -> [[P2]]) -> ([PT] -> [[PT]])
-> [P2] -> [PT]
-> [Diagram Cairo R2] -> [Diagram Cairo]
} }
| EmptyDiag (Diagram Cairo R2) | EmptyDiag (Diagram Cairo)
-- |Holds the properties for a Diagram, like thickness of 2d points etc. -- |Holds the properties for a Diagram, like thickness of 2d points etc.
@ -49,7 +49,7 @@ data DiagProp = MkProp {
-- |The path to a quad in the quad tree. -- |The path to a quad in the quad tree.
quadPath :: String, quadPath :: String,
-- |The square for the kd-tree range search. -- |The square for the kd-tree range search.
rangeSquare :: ((Double, Double), (Double, Double)) rangeSquare :: Square
} }
@ -134,33 +134,33 @@ maybeDiag b d
| otherwise = mempty | otherwise = mempty
filterValidPT :: DiagProp -> [P2] -> [P2] filterValidPT :: DiagProp -> [PT] -> [PT]
filterValidPT = filterValidPT =
filter filter
. inRange . inRange
. diagDimSquare . diagDimSquare
diagDimSquare :: DiagProp -> ((Double, Double), (Double, Double)) diagDimSquare :: DiagProp -> Square
diagDimSquare p = dimToSquare (xDimension p) $ yDimension p diagDimSquare p = dimToSquare (xDimension p) $ yDimension p
-- |Draw a list of points. -- |Draw a list of points.
drawP :: [P2] -- ^ the points to draw drawP :: [PT] -- ^ the points to draw
-> Double -- ^ dot size -> Double -- ^ dot size
-> Diagram Cairo R2 -- ^ the resulting diagram -> Diagram Cairo -- ^ the resulting diagram
drawP [] _ = mempty drawP [] _ = mempty
drawP vt ds = drawP vt ds =
position (zip vt (repeat dot)) position (zip vt (repeat dot))
where where
dot = circle ds :: Diagram Cairo R2 dot = circle ds :: Diagram Cairo
-- |Create a rectangle around a diagonal line, which has sw -- |Create a rectangle around a diagonal line, which has sw
-- as startpoint and nw as endpoint. -- as startpoint and nw as endpoint.
rectByDiagonal :: (Double, Double) -- ^ sw point rectByDiagonal :: (Double, Double) -- ^ sw point
-> (Double, Double) -- ^ nw point -> (Double, Double) -- ^ nw point
-> Diagram Cairo R2 -> Diagram Cairo
rectByDiagonal (xmin, ymin) (xmax, ymax) = rectByDiagonal (xmin, ymin) (xmax, ymax) =
fromVertices [p2 (xmin, ymin) fromVertices [p2 (xmin, ymin)
, p2 (xmax, ymin) , p2 (xmax, ymin)
@ -172,7 +172,7 @@ rectByDiagonal (xmin, ymin) (xmax, ymax) =
-- |Creates a Diagram from a point that shows the coordinates -- |Creates a Diagram from a point that shows the coordinates
-- in text format, such as "(1.0, 2.0)". -- in text format, such as "(1.0, 2.0)".
pointToTextCoord :: P2 -> Diagram Cairo R2 pointToTextCoord :: PT -> Diagram Cairo
pointToTextCoord pt = pointToTextCoord pt =
text ("(" ++ (show . trim') x ++ ", " ++ (show . trim') y ++ ")") # scale 10 text ("(" ++ (show . trim') x ++ ", " ++ (show . trim') y ++ ")") # scale 10
where where

View File

@ -16,7 +16,7 @@ import Parser.Meshparser
-- |Return a list of tuples used by 'gifMain' to generate an animated gif. -- |Return a list of tuples used by 'gifMain' to generate an animated gif.
gifDiag :: DiagProp -> [PT] -> [(Diagram Cairo R2, GifDelay)] gifDiag :: DiagProp -> [PT] -> [(Diagram Cairo, GifDelay)]
gifDiag p xs = gifDiag p xs =
fmap ((\x -> (x, 50)) . (<> nonChDiag)) fmap ((\x -> (x, 50)) . (<> nonChDiag))
(upperHullList (upperHullList
@ -35,5 +35,5 @@ gifDiag p xs =
-- |Same as gifDiag, except that it takes a string containing the -- |Same as gifDiag, except that it takes a string containing the
-- mesh file content instead of the the points. -- mesh file content instead of the the points.
gifDiagS :: DiagProp -> B.ByteString -> [(Diagram Cairo R2, GifDelay)] gifDiagS :: DiagProp -> B.ByteString -> [(Diagram Cairo, GifDelay)]
gifDiagS p = gifDiag p . filterValidPT p . meshToArr gifDiagS p = gifDiag p . filterValidPT p . meshVertices

View File

@ -2,6 +2,7 @@
module Graphics.Diagram.Gtk where module Graphics.Diagram.Gtk where
import Algebra.Vector(PT)
import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Char8 as B
import Data.List(find) import Data.List(find)
import Diagrams.Backend.Cairo import Diagrams.Backend.Cairo
@ -45,7 +46,7 @@ diagTreAlgos =
-- |Create the Diagram from the points. -- |Create the Diagram from the points.
diag :: DiagProp -> [DiagAlgo] -> [[P2]] -> Diagram Cairo R2 diag :: DiagProp -> [DiagAlgo] -> [[PT]] -> Diagram Cairo
diag p das vts = maybe mempty (\x -> mkDiag x p vts) diag p das vts = maybe mempty (\x -> mkDiag x p vts)
$ mconcat $ mconcat
-- get the actual [Diag] array -- get the actual [Diag] array
@ -57,22 +58,22 @@ diag p das vts = maybe mempty (\x -> mkDiag x p vts)
-- |Create the Diagram from a String which is supposed to be the contents -- |Create the Diagram from a String which is supposed to be the contents
-- of an obj file. -- of an obj file.
diagS :: DiagProp -> B.ByteString -> Diagram Cairo R2 diagS :: DiagProp -> B.ByteString -> Diagram Cairo
diagS p mesh = diagS p mesh =
diag p diagAlgos diag p diagAlgos
. fmap (filterValidPT p) . fmap (filterValidPT p)
. (\x -> if null x then [meshToArr mesh] else x) . (\x -> if null x then [meshVertices mesh] else x)
. facesToArr . meshFaceVertices
$ mesh $ mesh
-- |Create the tree diagram from a String which is supposed to be the contents -- |Create the tree diagram from a String which is supposed to be the contents
-- of an obj file. -- of an obj file.
diagTreeS :: DiagProp -> B.ByteString -> Diagram Cairo R2 diagTreeS :: DiagProp -> B.ByteString -> Diagram Cairo
diagTreeS p mesh = diagTreeS p mesh =
diag p diagTreAlgos diag p diagTreAlgos
. fmap (filterValidPT p) . fmap (filterValidPT p)
. (\x -> if null x then [meshToArr mesh] else x) . (\x -> if null x then [meshVertices mesh] else x)
. facesToArr . meshFaceVertices
$ mesh $ mesh

241
Graphics/HalfEdge.hs Normal file
View File

@ -0,0 +1,241 @@
{-# OPTIONS_HADDOCK ignore-exports #-}
-- |This module provides methods to build a cyclic half-edge data structure
-- from an already parsed obj mesh file. As such, it depends on details
-- of the parsed data.
--
-- In particular, 'indirectHeFaces', 'indirectHeVerts' and 'indirectToDirect'
-- assume specific structure of some input lists. Check their respective
-- documentation.
--
-- As the data structure has a lot of cross-references and the knots are
-- not really known at compile-time, we have to use helper data structures
-- such as lists and maps under the hood and tie the knots through
-- index lookups.
--
-- For an explanation of the abstract concept of the half-edge data structure,
-- check <http://www.flipcode.com/archives/The_Half-Edge_Data_Structure.shtml>
module Graphics.HalfEdge (
HeVert(..)
, HeFace(..)
, HeEdge(..)
, buildHeEdge
, buildHeEdgeFromStr
) where
import Algebra.Vector
import Control.Applicative
import Control.Monad
import qualified Data.ByteString.Char8 as B
import qualified Data.IntMap.Lazy as Map
import Data.Maybe
import Parser.Meshparser
import Safe
-- |The vertex data structure for the half-edge.
data HeVert a = HeVert {
vcoord :: a -- the coordinates of the vertex
, emedge :: HeEdge a -- one of the half-edges emanating from the vertex
} | NoVert
-- |The face data structure for the half-edge.
data HeFace a = HeFace {
bordedge :: HeEdge a -- one of the half-edges bordering the face
} | NoFace
-- |The actual half-edge data structure.
data HeEdge a = HeEdge {
startvert :: HeVert a -- start-vertex of the half-edge
, oppedge :: HeEdge a -- oppositely oriented adjacent half-edge
, edgeface :: HeFace a -- face the half-edge borders
, nextedge :: HeEdge a -- next half-edge around the face
} | NoEdge
-- This is a helper data structure of half-edge edges
-- for tying the knots in 'indirectToDirect'.
data IndirectHeEdge = IndirectHeEdge {
edgeindex :: Int -- edge index
, svindex :: Int -- index of start-vertice
, nvindex :: Int -- index of next-vertice
, indexf :: Int -- index of face
, offsetedge :: Int -- offset to get the next edge
} deriving (Show)
-- This is a helper data structure of half-edge vertices
-- for tying the knots in 'indirectToDirect'.
data IndirectHeVert = IndirectHeVert {
emedgeindex :: Int -- emanating edge index (starts at 1)
, edgelist :: [Int] -- index of edge that points to this vertice
} deriving (Show)
-- This is a helper data structure of half-edge faces
-- for tying the knots in 'indirectToDirect'.
data IndirectHeFace =
IndirectHeFace (Int, [Int]) -- (faceIndex, [verticeindex])
deriving (Show)
-- |Construct the indirect data structure for half-edge faces.
-- This function assumes that the input faces are parsed exactly like so:
--
-- @
-- f 1 3 4 5
-- f 4 6 1 3
-- @
--
-- becomes
--
-- > [[1,3,4,5],[4,6,1,3]]
indirectHeFaces :: [[Int]] -- ^ list of faces with their respective
-- list of vertice-indices
-> [IndirectHeFace]
indirectHeFaces = fmap IndirectHeFace . zip [0..]
-- |Construct the indirect data structure for half-edge edges.
indirectHeEdges :: [IndirectHeFace] -> [IndirectHeEdge]
indirectHeEdges = concat . fmap indirectHeEdge
where
indirectHeEdge :: IndirectHeFace -> [IndirectHeEdge]
indirectHeEdge (IndirectHeFace (_, [])) = []
indirectHeEdge p@(IndirectHeFace (_, pv@(v:_))) = go p 0
where
go (IndirectHeFace (_, [])) _
= []
-- connect last to first element
go (IndirectHeFace (fi, [vlast])) ei
= [IndirectHeEdge ei vlast v fi (negate $ length pv - 1)]
-- regular non-last element
go (IndirectHeFace (fi, vfirst:vnext:vrest)) ei
= (:) (IndirectHeEdge ei vfirst vnext fi 1)
(go (IndirectHeFace (fi, vnext:vrest)) (ei + 1))
-- |Construct the indirect data structure for half-edge vertices.
-- It is assumed that the list of points is indexed in order of their
-- appearance in the obj mesh file.
indirectHeVerts :: [IndirectHeEdge] -- ^ list of indirect edges
-> Map.IntMap IndirectHeVert -- ^ output map, starts at index 1
indirectHeVerts hes' = go hes' Map.empty 0
where
go [] map' _ = map'
go (IndirectHeEdge _ _ nv _ offset:hes) map' i
= go hes
(Map.alter updateMap nv map')
(i + 1)
where
updateMap (Just (IndirectHeVert _ xs))
= Just (IndirectHeVert (i + offset) (i:xs))
updateMap Nothing
= Just (IndirectHeVert (i + offset) [i])
-- |Tie the knots!
-- It is assumed that the list of points is indexed in order of their
-- appearance in the obj mesh file.
--
-- pseudo-code:
--
-- @
-- indirectToDirect :: [a] -- parsed vertices, e.g. 2d points (Double, Double)
-- -> [IndirectHeEdge]
-- -> [IndirectHeFace]
-- -> [IndirectHeVert]
-- -> HeEdge a
-- indirectToDirect points edges faces vertices
-- = thisEdge (head edges)
-- where
-- thisEdge edge
-- = HeEdge (thisVert (vertices !! svindex edge) $ svindex edge)
-- (thisOppEdge (svindex edge) $ indexf edge)
-- (thisFace $ faces !! indexf edge)
-- (thisEdge $ edges !! (edgeindex edge + offsetedge edge))
-- thisFace face = HeFace $ thisEdge (edges !! (head . snd $ face))
-- thisVert vertice coordindex
-- = HeVert (points !! (coordindex - 1))
-- (thisEdge $ points !! (emedgeindex vertice - 1))
-- thisOppEdge startverticeindex faceindex
-- = case headMay
-- . filter ((/=) faceindex . indexf)
-- . fmap (edges !!)
-- . edgelist -- getter
-- $ vertices !! startverticeindex
-- of Just x -> thisEdge x
-- Nothing -> NoEdge
-- @
indirectToDirect :: [a] -- ^ list of points
-> [IndirectHeEdge]
-> [IndirectHeFace]
-> Map.IntMap IndirectHeVert -- ^ assumed to start at index 1
-> HeEdge a
indirectToDirect pts pe@(e:_) fs vertmap
= thisEdge e
where
thisEdge (IndirectHeEdge ei sv _ fi off)
= case (fs `atMay` fi, pe `atMay` (ei + off), Map.lookup sv vertmap) of
(Just face,
Just edge,
Just vert) -> HeEdge (thisVert vert sv)
(getOppEdge sv fi)
(thisFace face)
(thisEdge edge)
_ -> NoEdge
thisFace (IndirectHeFace (_, vi:_))
= case pe `atMay` vi of
Just edge -> HeFace (thisEdge edge)
Nothing -> NoFace
thisFace (IndirectHeFace _) = NoFace
thisVert (IndirectHeVert eedg _) coordi
= case (pts `atMay` (coordi - 1), pe `atMay` (eedg - 1)) of
(Just vert, Just edge) -> HeVert vert $ thisEdge edge
_ -> NoVert
getOppEdge sv fi
= case join
$ headMay
. filter ((/=) fi . indexf)
. catMaybes
. fmap (pe `atMay`)
. edgelist
<$> Map.lookup sv vertmap
of Just x -> thisEdge x
Nothing -> NoEdge
indirectToDirect _ _ _ _ = NoEdge
-- |Build the half-edge data structure from a list of points
-- and from a list of faces.
-- The points are assumed to have been parsed in order of their appearance
-- in the .obj mesh file, so that the indices match.
-- The faces are assumed to have been parsed in order of their appearance
-- in the .obj mesh file as follows:
--
-- @
-- f 1 3 4 5
-- f 4 6 1 3
-- @
--
-- becomes
--
-- > [[1,3,4,5],[4,6,1,3]]
buildHeEdge :: [a] -> [[Int]] -> Maybe (HeEdge a)
buildHeEdge [] _ = Nothing
buildHeEdge _ [] = Nothing
buildHeEdge pts fs
= let faces' = indirectHeFaces fs
edges' = indirectHeEdges faces'
verts' = indirectHeVerts edges'
in Just $ indirectToDirect pts edges' faces' verts'
-- |Build the HeEdge data structure from the .obj mesh file contents.
buildHeEdgeFromStr :: B.ByteString -- ^ contents of an .obj mesh file
-> HeEdge PT
buildHeEdgeFromStr bmesh =
let pts = meshVertices bmesh
faces' = indirectHeFaces . meshFaces $ bmesh
edges = indirectHeEdges faces'
verts = indirectHeVerts edges
in indirectToDirect pts edges faces' verts

View File

@ -1,7 +1,8 @@
{-# OPTIONS_HADDOCK ignore-exports #-} {-# OPTIONS_HADDOCK ignore-exports #-}
module Parser.Meshparser (meshToArr, facesToArr) where module Parser.Meshparser where
import Algebra.Vector(PT)
import Control.Applicative import Control.Applicative
import Data.Attoparsec.ByteString.Char8 import Data.Attoparsec.ByteString.Char8
import Data.Either import Data.Either
@ -11,19 +12,17 @@ import Diagrams.TwoD.Types
-- |Convert a text String with multiple vertices and faces into -- |Convert a text String with multiple vertices and faces into
-- a list of vertices, ordered by the faces specification. -- a list of vertices, ordered by the faces specification.
facesToArr :: B.ByteString -> [[P2]] meshFaceVertices :: B.ByteString -> [[PT]]
facesToArr str = fmap (fmap (\y -> meshToArr str !! (fromIntegral y - 1))) meshFaceVertices str = fmap (fmap (\y -> meshVertices str !! (y - 1)))
(faces str) (meshFaces str)
where
faces = rights . fmap (parseOnly parseFace) . B.lines
-- |Convert a text String with multiple vertices into -- |Convert a text String with multiple vertices into
-- an array of float tuples. -- an array of float tuples.
meshToArr :: B.ByteString -- ^ the string to convert meshVertices :: B.ByteString -- ^ the string to convert
-> [P2] -- ^ the resulting vertice table -> [PT] -- ^ the resulting vertice table
meshToArr = meshVertices
fmap p2 = fmap p2
. rights . rights
. fmap (parseOnly parseVertice) . fmap (parseOnly parseVertice)
. B.lines . B.lines
@ -37,5 +36,12 @@ parseVertice =
<*> (many' space *> double) <*> (many' space *> double)
parseFace :: Parser [Integer] parseFace :: (Integral a) => Parser [a]
parseFace = char 'f' *> many1' (many' space *> decimal) parseFace = char 'f' *> many1' (many' space *> decimal)
meshFaces :: B.ByteString -> [[Int]]
meshFaces
= rights
. fmap (parseOnly parseFace)
. B.lines

View File

@ -21,19 +21,19 @@ newtype PosRoundDouble = PosRoundDouble { getPRD :: Double }
deriving (Eq, Ord, Show, Read) deriving (Eq, Ord, Show, Read)
newtype RoundR2 = RoundR2 { getRR2 :: R2 } newtype RoundR2 = RoundR2 { getRR2 :: V2 Double }
deriving (Eq, Ord, Show, Read) deriving (Eq, Ord, Show, Read)
newtype PosRoundR2 = PosRoundR2 { getPRR2 :: R2 } newtype PosRoundR2 = PosRoundR2 { getPRR2 :: V2 Double }
deriving (Eq, Ord, Show, Read) deriving (Eq, Ord, Show, Read)
newtype RoundP2 = RoundP2 { getRP2 :: P2 } newtype RoundP2 = RoundP2 { getRP2 :: P2 Double }
deriving (Eq, Ord, Show, Read) deriving (Eq, Ord, Show, Read)
newtype PosRoundP2 = PosRoundP2 { getPRP2 :: P2 } newtype PosRoundP2 = PosRoundP2 { getPRP2 :: P2 Double }
deriving (Eq, Ord, Show, Read) deriving (Eq, Ord, Show, Read)
@ -72,50 +72,50 @@ instance Arbitrary PosRoundP2 where
<*> (arbitrary :: Gen PosRoundDouble) <*> (arbitrary :: Gen PosRoundDouble)
instance Arbitrary R2 where instance Arbitrary (V2 Double) where
arbitrary = curry r2 <$> arbitrary <*> arbitrary arbitrary = curry r2 <$> arbitrary <*> arbitrary
instance Arbitrary P2 where instance Arbitrary (P2 Double) where
arbitrary = curry p2 <$> arbitrary <*> arbitrary arbitrary = curry p2 <$> arbitrary <*> arbitrary
-- the point describing the lower left corner of the square -- the point describing the lower left corner of the square
-- must be part of the square -- must be part of the square
inRangeProp1 :: ((Double, Double), (Double, Double)) -> Bool inRangeProp1 :: Square -> Bool
inRangeProp1 sq@((x1, y1), _) = inRangeProp1 sq@((x1, y1), _) =
inRange sq (p2 (x1, y1)) inRange sq (p2 (x1, y1))
-- the point describing the upper right corner of the square -- the point describing the upper right corner of the square
-- must be part of the square -- must be part of the square
inRangeProp2 :: ((Double, Double), (Double, Double)) -> Bool inRangeProp2 :: Square -> Bool
inRangeProp2 sq@(_, (x2, y2)) = inRangeProp2 sq@(_, (x2, y2)) =
inRange sq (p2 (x2, y2)) inRange sq (p2 (x2, y2))
-- the point describing the upper left corner of the square -- the point describing the upper left corner of the square
-- must be part of the square -- must be part of the square
inRangeProp3 :: ((Double, Double), (Double, Double)) -> Bool inRangeProp3 :: Square -> Bool
inRangeProp3 sq@((x1, _), (_, y2)) = inRangeProp3 sq@((x1, _), (_, y2)) =
inRange sq (p2 (x1, y2)) inRange sq (p2 (x1, y2))
-- the point describing the lower right corner of the square -- the point describing the lower right corner of the square
-- must be part of the square -- must be part of the square
inRangeProp4 :: ((Double, Double), (Double, Double)) -> Bool inRangeProp4 :: Square -> Bool
inRangeProp4 sq@((_, y1), (x2, _)) = inRangeProp4 sq@((_, y1), (x2, _)) =
inRange sq (p2 (x2, y1)) inRange sq (p2 (x2, y1))
-- generating random points within the square -- generating random points within the square
inRangeProp5 :: ((Double, Double), (Double, Double)) -> Positive Double -> Positive Double -> Bool inRangeProp5 :: Square -> Positive Double -> Positive Double -> Bool
inRangeProp5 sq@((x1, y1), (x2, y2)) (Positive a) (Positive b) = inRangeProp5 sq@((x1, y1), (x2, y2)) (Positive a) (Positive b) =
inRange sq (p2 (x1 + ((x2 - x1) / (a + 1)), y1 + ((y2 - y1) / (b + 1)))) inRange sq (p2 (x1 + ((x2 - x1) / (a + 1)), y1 + ((y2 - y1) / (b + 1))))
-- generating random points outside of the square -- generating random points outside of the square
inRangeProp6 :: ((Double, Double), (Double, Double)) -> Positive Double -> Positive Double -> Bool inRangeProp6 :: Square -> Positive Double -> Positive Double -> Bool
inRangeProp6 sq@((x1, y1), (x2, y2)) (Positive a) (Positive b) = inRangeProp6 sq@((x1, y1), (x2, y2)) (Positive a) (Positive b) =
(not . inRange sq $ p2 (max x1 x2 + (a + 1), max y1 y2 + (b + 1))) (not . inRange sq $ p2 (max x1 x2 + (a + 1), max y1 y2 + (b + 1)))
&& (not . inRange sq $ p2 (max x1 x2 + (a + 1), max y1 y2 - (b + 1))) && (not . inRange sq $ p2 (max x1 x2 + (a + 1), max y1 y2 - (b + 1)))
@ -126,51 +126,51 @@ inRangeProp6 sq@((x1, y1), (x2, y2)) (Positive a) (Positive b) =
-- apply id function on the point -- apply id function on the point
onPTProp1 :: P2 -> Bool onPTProp1 :: PT -> Bool
onPTProp1 pt = onPT id pt == pt onPTProp1 pt = onPT id pt == pt
-- add a random value to the point coordinates -- add a random value to the point coordinates
onPTProp2 :: P2 -> Positive R2 -> Bool onPTProp2 :: PT -> Positive (V2 Double) -> Bool
onPTProp2 pt (Positive (R2 rx ry)) onPTProp2 pt (Positive (V2 rx ry))
= onPT (\(x, y) -> (x + rx, y + ry)) pt /= pt = onPT (\(x, y) -> (x + rx, y + ry)) pt /= pt
-- angle between two vectors both on the x-axis must be 0 -- angle between two vectors both on the x-axis must be 0
getAngleProp1 :: Positive R2 -> Positive R2 -> Bool getAngleProp1 :: Positive Vec -> Positive Vec -> Bool
getAngleProp1 (Positive (R2 x1 _)) (Positive (R2 x2 _)) getAngleProp1 (Positive (V2 x1 _)) (Positive (V2 x2 _))
= getAngle (R2 x1 0) (R2 x2 0) == 0 = getAngle (V2 x1 0) (V2 x2 0) == 0
-- angle between two vectors both on the y-axis must be 0 -- angle between two vectors both on the y-axis must be 0
getAngleProp2 :: Positive R2 -> Positive R2 -> Bool getAngleProp2 :: Positive Vec -> Positive Vec -> Bool
getAngleProp2 (Positive (R2 _ y1)) (Positive (R2 _ y2)) getAngleProp2 (Positive (V2 _ y1)) (Positive (V2 _ y2))
= getAngle (R2 0 y1) (R2 0 y2) == 0 = getAngle (V2 0 y1) (V2 0 y2) == 0
-- angle between two vectors both on the x-axis but with opposite direction -- angle between two vectors both on the x-axis but with opposite direction
-- must be pi -- must be pi
getAngleProp3 :: Positive R2 -> Positive R2 -> Bool getAngleProp3 :: Positive Vec -> Positive Vec -> Bool
getAngleProp3 (Positive (R2 x1 _)) (Positive (R2 x2 _)) getAngleProp3 (Positive (V2 x1 _)) (Positive (V2 x2 _))
= getAngle (R2 (negate x1) 0) (R2 x2 0) == pi = getAngle (V2 (negate x1) 0) (V2 x2 0) == pi
-- angle between two vectors both on the y-axis but with opposite direction -- angle between two vectors both on the y-axis but with opposite direction
-- must be pi -- must be pi
getAngleProp4 :: Positive R2 -> Positive R2 -> Bool getAngleProp4 :: Positive Vec -> Positive Vec -> Bool
getAngleProp4 (Positive (R2 _ y1)) (Positive (R2 _ y2)) getAngleProp4 (Positive (V2 _ y1)) (Positive (V2 _ y2))
= getAngle (R2 0 (negate y1)) (R2 0 y2) == pi = getAngle (V2 0 (negate y1)) (V2 0 y2) == pi
-- angle between vector in x-axis direction and y-axis direction must be -- angle between vector in x-axis direction and y-axis direction must be
-- p/2 -- p/2
getAngleProp5 :: Positive R2 -> Positive R2 -> Bool getAngleProp5 :: Positive Vec -> Positive Vec -> Bool
getAngleProp5 (Positive (R2 x1 _)) (Positive (R2 _ y2)) getAngleProp5 (Positive (V2 x1 _)) (Positive (V2 _ y2))
= getAngle (R2 x1 0) (R2 0 y2) == pi / 2 = getAngle (V2 x1 0) (V2 0 y2) == pi / 2
-- commutative -- commutative
getAngleProp6 :: Positive R2 -> Positive R2 -> Bool getAngleProp6 :: Positive Vec -> Positive Vec -> Bool
getAngleProp6 (Positive v1) (Positive v2) getAngleProp6 (Positive v1) (Positive v2)
= getAngle v1 v2 == getAngle v2 v1 = getAngle v1 v2 == getAngle v2 v1
@ -183,7 +183,7 @@ getAngleProp7 (PosRoundR2 v)
-- commutative -- commutative
scalarProdProp1 :: R2 -> R2 -> Bool scalarProdProp1 :: Vec -> Vec -> Bool
scalarProdProp1 v1 v2 = v1 `scalarProd` v2 == v2 `scalarProd` v1 scalarProdProp1 v1 v2 = v1 `scalarProd` v2 == v2 `scalarProd` v1
@ -212,9 +212,9 @@ scalarProdProp4 (RoundDouble s1) (RoundDouble s2) (RoundR2 v1) (RoundR2 v2)
-- orthogonal -- orthogonal
scalarProdProp5 :: Positive R2 -> Positive R2 -> Bool scalarProdProp5 :: Positive Vec -> Positive Vec -> Bool
scalarProdProp5 (Positive (R2 x1 _)) (Positive (R2 _ y2)) scalarProdProp5 (Positive (V2 x1 _)) (Positive (V2 _ y2))
= scalarProd (R2 x1 0) (R2 0 y2) == 0 = scalarProd (V2 x1 0) (V2 0 y2) == 0
-- this is almost the same as the function definition -- this is almost the same as the function definition
@ -226,49 +226,49 @@ dimToSquareProp1 (x1, x2) (y1, y2) =
-- multiply scalar with result of vecLength or with the vector itself... -- multiply scalar with result of vecLength or with the vector itself...
-- both results must be the same. We can't check against 0 -- both results must be the same. We can't check against 0
-- because of sqrt in vecLength. -- because of sqrt in vecLength.
vecLengthProp1 :: PosRoundDouble -> R2 -> Bool vecLengthProp1 :: PosRoundDouble -> Vec -> Bool
vecLengthProp1 (PosRoundDouble r) v vecLengthProp1 (PosRoundDouble r) v
= abs (vecLength v * r - vecLength (scalarMul r v)) < 0.0001 = abs (vecLength v * r - vecLength (scalarMul r v)) < 0.0001
-- convert to vector and back again -- convert to vector and back again
pt2VecProp1 :: P2 -> Bool pt2VecProp1 :: PT -> Bool
pt2VecProp1 pt = (vec2Pt . pt2Vec $ pt) == pt pt2VecProp1 pt = (vec2Pt . pt2Vec $ pt) == pt
-- unbox coordinates and check if equal -- unbox coordinates and check if equal
pt2VecProp2 :: P2 -> Bool pt2VecProp2 :: PT -> Bool
pt2VecProp2 pt = (unr2 . pt2Vec $ pt) == unp2 pt pt2VecProp2 pt = (unr2 . pt2Vec $ pt) == unp2 pt
-- convert to point and back again -- convert to point and back again
vec2PtProp1 :: R2 -> Bool vec2PtProp1 :: Vec -> Bool
vec2PtProp1 v = (pt2Vec . vec2Pt $ v) == v vec2PtProp1 v = (pt2Vec . vec2Pt $ v) == v
-- unbox coordinates and check if equal -- unbox coordinates and check if equal
vec2PtProp2 :: R2 -> Bool vec2PtProp2 :: Vec -> Bool
vec2PtProp2 v = (unp2 . vec2Pt $ v) == unr2 v vec2PtProp2 v = (unp2 . vec2Pt $ v) == unr2 v
-- vector from a to b must not be the same as b to a -- vector from a to b must not be the same as b to a
vp2Prop1 :: P2 -> P2 -> Bool vp2Prop1 :: PT -> PT -> Bool
vp2Prop1 p1' p2' vp2Prop1 p1' p2'
| p1' == origin && p2' == origin = True | p1' == origin && p2' == origin = True
| otherwise = vp2 p1' p2' /= vp2 p2' p1' | otherwise = vp2 p1' p2' /= vp2 p2' p1'
-- negating vector from a to be must be the same as vector b to a -- negating vector from a to be must be the same as vector b to a
vp2Prop2 :: P2 -> P2 -> Bool vp2Prop2 :: PT -> PT -> Bool
vp2Prop2 p1' p2' vp2Prop2 p1' p2'
| p1' == origin && p2' == origin = True | p1' == origin && p2' == origin = True
| otherwise = vp2 p1' p2' == (\(R2 x y) -> negate x ^& negate y) | otherwise = vp2 p1' p2' == (\(V2 x y) -> negate x ^& negate y)
(vp2 p2' p1') (vp2 p2' p1')
&& &&
vp2 p2' p1' == (\(R2 x y) -> negate x ^& negate y) vp2 p2' p1' == (\(V2 x y) -> negate x ^& negate y)
(vp2 p1' p2') (vp2 p1' p2')
-- determinant of the 3 same points is always 0 -- determinant of the 3 same points is always 0
detProp1 :: P2 -> Bool detProp1 :: PT -> Bool
detProp1 pt' = det pt' pt' pt' == 0 detProp1 pt' = det pt' pt' pt' == 0

View File

@ -0,0 +1,19 @@
v 9.0 10.0
v 11.0 10.0
v 9.0 11.0
v 11.0 11.0
v 9.0 11.0
v 11.0 11.0
v 9.0 10.0
v 11.0 10.0
f 1 2 4 3
f 3 4 6 5
f 5 6 8 7
f 7 8 2 1
f 2 8 6 4
f 7 1 3 5
cstype bezier
deg 3
curv 1 2 3 4
end