point-octree (empty) → 0.5.5.3
raw patch · 17 files changed
+1276/−0 lines, 17 filesdep +AC-Vectordep +QuickCheckdep +basesetup-changed
Dependencies added: AC-Vector, QuickCheck, base, hspec, markdown-unlit, random, random-shuffle
Files
- Data/Octree.hs +15/−0
- Data/Octree/BoundingBox/BoundingBox.hs +67/−0
- Data/Octree/BoundingBox/Internal.hs +122/−0
- Data/Octree/BoundingBox/README.lhs +53/−0
- Data/Octree/Internal.hs +356/−0
- LICENSE +31/−0
- README.lhs +26/−0
- Setup.hs +4/−0
- changelog +3/−0
- point-octree.cabal +85/−0
- tests/Main.hs +21/−0
- tests/PropTests/BoundingBoxTests/BoundingBoxTests.hs +72/−0
- tests/PropTests/BoundingBoxTests/Utilities.hs +88/−0
- tests/PropTests/Common.hs +34/−0
- tests/PropTests/Octree.hs +62/−0
- tests/PropTests/OctreeTests/Exposed.hs +94/−0
- tests/PropTests/OctreeTests/Internal.hs +143/−0
+ Data/Octree.hs view
@@ -0,0 +1,15 @@+module Data.Octree(Octree,+ Vector3(..),+ dist, + fromList, toList,+ lookup,+ insert,+ nearest,+ depth,+ size,+ withinRange)+where++import Prelude hiding(lookup)+import Data.Octree.Internal+
+ Data/Octree/BoundingBox/BoundingBox.hs view
@@ -0,0 +1,67 @@+{- |+ Module : Data.Octree.BoundingBox.BoundingBox+ Copyright : Copyright (c) 2016 Michael Litchard+ License : BSD3++ Maintainer : Michael Litchard+ Stability : experimental+ Portability: not portable+ + This module provides a way to use bounding boxes with Octrees.+ +-}++module Data.Octree.BoundingBox.BoundingBox+ ( BBoxConfig (..)+ , traverseOctreeBB+ , defBBoxConfig+ ) where++import Data.Maybe (mapMaybe)+import Data.BoundingBox.B3 (BBox3)++import Data.Octree.Internal (Octree (..), allOctants)+import Data.Octree.BoundingBox.Internal++-- | BBoxConfig - The functions traverseOctreeBB needs+data BBoxConfig x y a = BBoxConfig {+-- | A function to recurse down the Octree+ select :: BBox3 -> x -> Maybe x,+-- | A function to pre-condition the leaves+ procLeaf :: BBox3 -> x -> [LeafValue a] -> y,+-- | A function to recurse back up the tree+ combine :: x -> [y] -> y+}++-- | defBBoxConfig - default BBoxConfig+defBBoxConfig :: BBoxConfig DefInput DefOutput DefNodeValue+defBBoxConfig = BBoxConfig {+ select = filterNodes ,+ procLeaf = points,+ combine = result+}++-- | traverseOctreeBB - Generalized Octree traversal function based on BBox3+-- Base case processes leaves +traverseOctreeBB :: BBoxConfig x y a -> BBox3 -> Octree a -> x -> y+traverseOctreeBB bbc bbx (Leaf leaf_vals) input = procLeaf' bbx input leaf_vals+ where+ procLeaf' = procLeaf bbc ++-- | General framework to traverse an Octree in terms of a BBox3+traverseOctreeBB + bbc bbx (Node split' nwu' nwd' neu' ned' swu' swd' seu' sed') x = + combine' x res + where+ combine' = combine bbc+ res = mapMaybe traverseOctreeBB' octList + select' = select bbc++ traverseOctreeBB' (subbox, subtree) =+ case select' subbox x of+ Just x' -> Just (traverseOctreeBB bbc subbox subtree x)+ Nothing -> Nothing ++ octList = zip boxList children+ boxList = map (newBBox3 bbx split') allOctants + children = [swd',sed',nwd',ned',swu',seu',nwu',neu']
+ Data/Octree/BoundingBox/Internal.hs view
@@ -0,0 +1,122 @@+{- |+ Module : Data.Octree.BoundingBox.Internal+ Copyright : Copyright (c) 2016 Michael Litchard+ License : BSD3++ Maintainer : Michael Litchard+ Stability : experimental+ Portability: not portable+ + This module provides functions etc, for Data.Octree.BoundingBox.BoundingBox++ -- | DefInput, LeafValue a, DefOutput a, DefNodeValue a + Default types for BBoxConfig++ -- | filterNodes, points, result+ Default functions for BBoxConfig++ -- | inclusive+ boolean check for one BBox3 being inclusive of another+-}++module Data.Octree.BoundingBox.Internal+ ( filterNodes+ , points+ , result+ , DefInput+ , LeafValue + , DefOutput + , DefNodeValue+ , newBBox3+ , inclusive + ) where++import Data.BoundingBox.B3 (BBox3 (..), bound_corners, within_bounds, isect)++import Data.Octree.Internal (Vector3 (..), ODir (..)) +import Data.List (foldl')++type DefInput = Vector3+type Split = Vector3+type LeafValue a = (Vector3, a)+type DefOutput = (BBox3, [LeafValue DefNodeValue ])+type DefNodeValue = Int++-- | newBBox3 creates a smaller BBox3+-- Given Node name, previous BBox3, and the split+newBBox3 :: BBox3 -> Split -> ODir -> BBox3+newBBox3 bbx split' SWD =+ bound_corners swdCorner neuCorner+ where+ swdCorner = Vector3 (minX bbx) (minY bbx) (minZ bbx)+ neuCorner = Vector3 (v3x split') (v3y split') (v3z split')++newBBox3 bbx split' SED =+ bound_corners swdCorner neuCorner+ where+ swdCorner = Vector3 (v3x split') (minY bbx) (minZ bbx)+ neuCorner = Vector3 (maxX bbx) (v3y split') (v3z split')++newBBox3 bbx split' NWD =+ bound_corners swdCorner neuCorner+ where+ swdCorner = Vector3 (minX bbx) (v3y split') (minZ bbx)+ neuCorner = Vector3 (v3x split') (maxY bbx) (v3z split')++newBBox3 bbx split' NED =+ bound_corners swdCorner neuCorner+ where+ swdCorner = Vector3 (v3x split') (v3y split') (minZ bbx)+ neuCorner = Vector3 (maxX bbx) (maxY bbx) (v3z split')++newBBox3 bbx split' SWU =+ bound_corners swdCorner neuCorner+ where+ swdCorner = Vector3 (minX bbx) (minY bbx) (v3z split')+ neuCorner = Vector3 (v3x split') (v3y split') (maxZ bbx)++newBBox3 bbx split' SEU =+ bound_corners swdCorner neuCorner+ where+ swdCorner = Vector3 (v3x split') (minY bbx) (v3z split')+ neuCorner = Vector3 (maxX bbx) (v3y split') (maxZ bbx)++newBBox3 bbx split' NWU =+ bound_corners swdCorner neuCorner+ where+ swdCorner = Vector3 (minX bbx) (v3y split') (v3z split')+ neuCorner = Vector3 (v3x split') (maxY bbx) (maxZ bbx)++newBBox3 bbx split' NEU =+ bound_corners swdCorner neuCorner+ where+ swdCorner = Vector3 (v3x split') (v3y split') (v3z split')+ neuCorner = Vector3 (maxX bbx) (maxY bbx) (maxZ bbx)++-- | filterNodes is default function for BBoxConfig +-- used to recurse down octree identifying which BBox3s contain DefInput+filterNodes :: BBox3 -> DefInput -> Maybe DefInput+filterNodes bbox x = if within_bounds x bbox then Just x else Nothing++-- | points is default function for BBoxConfig+-- pre-processes Leaf+points :: BBox3 -> DefInput -> [LeafValue DefNodeValue ] -> DefOutput+points box _ leaf = (box, leaf)++-- | result reduces the list of all BBoxes containing the point to+-- the terminal BBox+result :: DefInput -> [DefOutput ] -> DefOutput +result _ (x:xs) = foldl' findTerminal x xs+ where+ findTerminal :: DefOutput -> DefOutput -> DefOutput+ findTerminal bbox1@(bbox1',_) bbox2@(bbox2',_)+ | inclusive bbox1' bbox2' = bbox1+ | otherwise = bbox2++-- | Supplied boolean test for result function+-- Returns True if Box b1 is contained within Box b2+inclusive :: BBox3 -> BBox3 -> Bool+inclusive b1 b2 =+ case isect b1 b2 of+ Just b3 -> b1 == b3+ Nothing -> False
+ Data/Octree/BoundingBox/README.lhs view
@@ -0,0 +1,53 @@+point-octree/Data/Octree/BoundingBox+==============================+BoundingBox.hs provides a generalized traversal function, that traverses+an `Octree a` based on a BBox3 value.++Here is an example that produces an `Octree Int` with 2 subdivisions,+and finds the bounding box of a particular `(Vector3,Int)`.++~~~ {.haskell}+module Main where++import Data.List+import Data.Maybe+import Data.Vector.Class+import System.Random+import System.Random.Shuffle+import Data.BoundingBox.B3++import Data.Octree.Internal hiding (lookup)+import Data.Octree.BoundingBox.BoundingBox+import Data.Octree.BoundingBox.Internal++main = do+ octree' <- octree 513+ let (testInput,_) = (!!) (toList octree') 256+ (bbx,_) = traverseOctreeBB defBBoxConfig bbis octree' testInput+ putStrLn ("Bounding box of " ++ (show testInput) ++ " is " ++ (show bbx))++bbis :: BBox3+bbis =+ let infinity = (read "Infinity") :: Double+ swdCorner = Vector3 (-infinity) (-infinity) (-infinity)+ neuCorner = Vector3 (infinity) (infinity) (infinity)+ in bound_corners swdCorner neuCorner++octree :: Integer -> IO (Octree DefNodeValue)+octree bound = do+ xGen <- newStdGen+ yGen <- newStdGen+ zGen <- newStdGen+ let xPoints :: [Double]+ yPoints :: [Double]+ zPoints :: [Double]+ xPoints = map fromInteger $ shuffle' pointList (length pointList) xGen+ yPoints = map fromInteger $ shuffle' pointList (length pointList) yGen+ zPoints = map fromInteger $ shuffle' pointList (length pointList) zGen+ ub = bound `div` 2+ lb = - (bound `div` 2)+ pointList = [lb .. ub]+ sizePL = length pointList+ vectors = map (\(x,y,z) -> Vector3 x y z) $ zip3 xPoints yPoints zPoints+ return $ fromList $ zip vectors $ [1 .. sizePL]+~~~
+ Data/Octree/Internal.hs view
@@ -0,0 +1,356 @@+{- |+ Module : Data.Octree.Internal+ Copyright : Copyright (c) 2012 Michal J. Gajda+ License : BSD3+ + Maintainer : Michael Litchard+ Stability : experimental+ Portability : not portable+ + This module provides the description of a point octree+ and it's operations.++-}+{-# LANGUAGE ScopedTypeVariables, DisambiguateRecordFields #-}+{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}+module Data.Octree.Internal+ ( Vector3(..), dist+ , Octree(..), lookup, nearest, withinRange, fromList, toList, insert+ -- internal+ , ODir(..)+ , octreeStep, octantDistance, splitBy', joinStep, splitStep, allOctants, octantDistance'+ , cmp+ , pickClosest+ , depth, size+ , subnodes+ ) where++import Data.Functor (Functor (..))+import Data.Foldable (Foldable (foldr))+import Data.Traversable(Traversable(..))++import Data.Vector.V3+import Data.Vector.Class++import Prelude hiding(lookup, foldr)+import Data.List(sort, sortBy)+import Data.Maybe(maybeToList, listToMaybe)+import Data.Bits((.&.))+import Control.Arrow(second)+import Test.QuickCheck.All(quickCheckAll)+import Test.QuickCheck.Arbitrary++-- | norm of a vector+norm :: Vector3 -> Double+norm a = sqrt (a `vdot` a)++-- | distance between two vectors+dist :: Vector3 -> Vector3 -> Double+dist u v = norm (u - v) ++-- | Datatype for nodes within Octree.+data Octree a + = Node { split :: Vector3+ , nwu :: Octree a+ , nwd :: Octree a+ , neu :: Octree a+ , ned :: Octree a+ , swu :: Octree a+ , swd :: Octree a+ , seu :: Octree a+ , sed :: Octree a + } + | Leaf { unLeaf :: [(Vector3, a)] }+ deriving (Show, Functor, Foldable, Traversable)++-- | Enumerated type to indicate octants in 3D-space relative to given center.+data ODir + = SWD + | SED + | NWD + | NED + | SWU + | SEU + | NWU + | NEU + deriving (Eq, Ord, Enum, Show, Bounded)++-- | Convinience synonym to make function signatures more readable+type OctPoints a = ( [(Vector3, a)]+ , [(Vector3, a)]+ , [(Vector3, a)]+ , [(Vector3, a)]+ , [(Vector3, a)]+ , [(Vector3, a)]+ , [(Vector3, a)]+ , [(Vector3, a)])++-- | Internal method that gives octant of a first vector,+-- relative to the second vector as a center.+cmp :: Vector3 -> Vector3 -> ODir+cmp ca cb = joinStep (cx, cy, cz)+ where + cx = v3x ca >= v3x cb+ cy = v3y ca >= v3y cb+ cz = v3z ca >= v3z cb++-- | Internal method that joins result of three coordinate comparisons,+-- and makes an octant name `ODir`+joinStep :: (Enum a1, Enum a3, Enum a2, Enum a) => (a1, a2, a3) -> a+joinStep (cx, cy, cz) = toEnum (fromEnum cx + 2 * fromEnum cy + 4 * fromEnum cz)++-- | This function converts octant name to a function+-- that steps down in an Octree towards this octant+octreeStep :: Octree a -> ODir -> Octree a+octreeStep ot NWU = nwu ot+octreeStep ot NWD = nwd ot+octreeStep ot NEU = neu ot+octreeStep ot NED = ned ot +octreeStep ot SWU = swu ot +octreeStep ot SWD = swd ot +octreeStep ot SEU = seu ot +octreeStep ot SED = sed ot++-- | Function that splits octant name into three boolean values,+-- depending of sign of a relative coordinate in that octant.+-- (Coordinate is relative to a split point within Octree.)+splitStep :: ODir -> (Bool, Bool, Bool)+splitStep step = ((val .&. 1) == 1, (val .&. 2) == 2, (val .&. 4) == 4)+ where val = fromEnum step++-- | Internal function that finds a lower bound for a distance+-- between a point of relative coordinates, and octant of given name.+-- It works only when coordinates of a given point are all positive.+-- It is used only by `octantDistance`, which respectively changes+-- octant name depending of signs of relative coordinates.+-- Here we assume that a, b, c > 0 +-- (otherwise we will take abs, and correspondingly invert results)+-- same octant+-- dp = difference between given point and the center of Octree node+octantDistance' :: Vector3 -> ODir -> Scalar+octantDistance' dp NEU = 0.0+-- | adjacent by plane+octantDistance' dp NWU = v3x dp+octantDistance' dp SEU = v3y dp+octantDistance' dp NED = v3z dp+-- | adjacent by edge+octantDistance' dp SWU = sqrt ( v3x dp * v3x dp + v3y dp * v3y dp)+octantDistance' dp SED = sqrt ( v3y dp * v3y dp + v3z dp * v3z dp)+octantDistance' dp NWD = sqrt ( v3x dp * v3x dp + v3z dp * v3z dp)+-- adjacent by point+octantDistance' dp SWD = norm dp++-- | List of all octant names.+allOctants :: [ODir]+allOctants = [minBound..maxBound]++-- | Internal function that makes code clearer.+xor :: Bool -> Bool -> Bool+xor = (/=)++-- | Finds a minimum bounds for a distance between a given point+-- | in relative coordinates and a given octant.+octantDistance :: Vector3 -> ODir -> Double+octantDistance dp odir = octantDistance' (abs dp) (toggle dp odir)++-- | Toggles octant names depending on a signs of vector coordinates+-- | for use in octantDistance.+toggle :: Vector3 -> ODir -> ODir+toggle dp odir = + joinStep ((v3x dp >= 0) `xor` not u,+ (v3y dp >= 0) `xor` not v,+ (v3z dp >= 0) `xor` not w)+ where (u, v, w) = splitStep odir++-- | Given a point in relative coordinates, gives list of all octants+-- and minimum distances from this point.+octantDistances :: Vector3 -> [(ODir, Double)]+octantDistances dp = [(o, octantDistance dp o) | o <- allOctants]++-- | splits a list of vectors and "payload" tuples+-- | into a tuple with elements destined for different octants.+-- FIXME: VERY IMPORTANT - add prop_splitBy vs cmp+splitBy :: Vector3 -> [(Vector3, a)] -> OctPoints a+splitBy _splitPoint [] = ([], [], [], [], [], [], [], [])+splitBy splitPoint ((pt@(coord, a)):aList) = case i of+ SWD -> (pt:swd, sed, nwd, ned, swu, seu, nwu, neu)+ SED -> ( swd, pt:sed, nwd, ned, swu, seu, nwu, neu)+ NWD -> ( swd, sed, pt:nwd, ned, swu, seu, nwu, neu)+ NED -> ( swd, sed, nwd, pt:ned, swu, seu, nwu, neu)+ SWU -> ( swd, sed, nwd, ned, pt:swu, seu, nwu, neu)+ SEU -> ( swd, sed, nwd, ned, swu, pt:seu, nwu, neu)+ NWU -> ( swd, sed, nwd, ned, swu, seu, pt:nwu, neu)+ NEU -> ( swd, sed, nwd, ned, swu, seu, nwu, pt:neu)+ where + i = cmp coord splitPoint+ (swd, sed, nwd, ned, swu, seu, nwu, neu) = splitBy splitPoint aList++-- | Computes a center of mass for a given list of vectors.+-- Used to find a splitPoint.+massCenter :: Fractional a => [(a, b)] -> a+massCenter aList = sum (map fst aList) / count+ where count = fromInteger . toInteger . length $ aList++-- | Helper function to map over an 8-element tuple+tmap :: (t -> t1) -> + (t, t, t, t, t, t, t, t) -> + (t1, t1, t1, t1, t1, t1, t1, t1)+tmap t (a, b, c, d, e, f, g, h) = (t a, t b, t c, t d, t e, t f, t g, t h)++-- | Maximum number of elements before Octree leaf is split.+leafLimit :: Int+leafLimit = 16++-- | Creates an Octree from a list of (index, payload) tuples.+fromList :: [(Vector3, a)] -> Octree a+fromList aList = + if length aList <= leafLimit+ then Leaf aList+ else let splitPoint :: Vector3 = massCenter aList+ in splitBy' fromList splitPoint aList++-- | Internal method, that splits a list into octants depending on coordinates,+-- and then applies a specified function to each of these sublists,+-- in order to create subnodes of the Octree+splitBy' :: ([(Vector3, a)] -> Octree a1)-> Vector3-> [(Vector3, a)]-> Octree a1+splitBy' f splitPoint aList = + Node { split = splitPoint+ , nwu = tnwu+ , nwd = tnwd+ , neu = tneu+ , ned = tned+ , swu = tswu+ , swd = tswd+ , seu = tseu+ , sed = tsed }+ where+ (tswd, tsed, tnwd, tned, tswu, tseu, tnwu, tneu) = + tmap f $ splitBy splitPoint aList+-- TODO: use arrays for memory savings++-- | Internal method that prepends contents of the given subtree to a list+-- | given as argument.+toList' :: Octree t -> [(Vector3, t)] -> [(Vector3, t)]+toList' (Leaf l ) tmp = l ++ tmp+toList' (Node { nwu = a,+ nwd = b,+ neu = c,+ ned = d,+ swu = e,+ swd = f,+ seu = g,+ sed = h }) tmp = foldr toList' tmp [a, b, c, d, e, f, g, h]+-- | Creates an Octree from list, trying to keep split points near centers+-- of mass for each subtree.+toList :: Octree t -> [(Vector3, t)]+toList t = toList' t []++-- | Finds a path to a Leaf where a given point should be,+-- and returns a list of octant names.+pathTo :: Vector3 -> Octree a -> [ODir]+pathTo pt (Leaf _) = []+pathTo pt node = aStep : pathTo pt (octreeStep node aStep)+ where aStep = cmp pt (split node)++-- | Applies a given function to a node specified by a path+-- (list of octant names), and then returns a modified Octree.+applyByPath :: (Octree a -> Octree a) -> [ODir] -> Octree a -> Octree a+applyByPath f [] ot = f ot+applyByPath f (step:path) node = case step of+ NWU -> node{ nwu = applyByPath f path (nwu node) }+ NWD -> node{ nwd = applyByPath f path (nwd node) }+ NEU -> node{ neu = applyByPath f path (neu node) }+ NED -> node{ ned = applyByPath f path (ned node) }+ SWU -> node{ swu = applyByPath f path (swu node) }+ SWD -> node{ swd = applyByPath f path (swd node) }+ SEU -> node{ seu = applyByPath f path (seu node) }+ SED -> node{ sed = applyByPath f path (sed node) }++-- | Inserts a point into an Octree.+-- NOTE: insert accepts duplicate points, +-- but lookup would not find them. Use withinRange in such case.+insert :: (Vector3, a) -> Octree a -> Octree a+insert (pt, dat) ot = applyByPath insert' path ot+ where + path = pathTo pt ot+ insert' (Leaf l) = fromList ((pt, dat) : l)+ insert' _ = error "Impossible in insert'"+++-- | Internal: finds candidates for nearest neighbour lazily for each octant;+-- they are returned in a list of (octant, min. bound for distance,+-- Maybe candidate) tuples.+candidates' :: Vector3 -> Octree a -> [(ODir, Double, [(Vector3, a)])]+candidates' pt (Leaf l) = []+candidates' pt node = + map findCandidates . sortBy compareDistance . octantDistances $ + pt - split node+ where+ findCandidates (octant, d) =+ (octant, d, maybeToList . pickClosest pt . toList . octreeStep node $ octant)+ compareDistance a b = compare (snd a) (snd b)++-- | Finds a given point, if it is in the tree.+lookup :: Octree a -> Vector3 -> Maybe (Vector3, a)+lookup (Leaf l) pt = listToMaybe . filter ((==pt) . fst) $ l+lookup node pt = flip lookup pt . octreeStep node . cmp pt . split $ node++-- | Finds nearest neighbour for a given point.+nearest :: Octree a -> Vector3 -> Maybe (Vector3, a)+nearest (Leaf l) pt = pickClosest pt l+nearest node pt = selectFrom candidates+ where + candidates = + map findCandidate . sortBy compareDistance . octantDistances $ + pt - split node+ compareDistance a b = compare (snd a) (snd b)+ findCandidate (octant, d) = + (maybeToList . nearest' . octreeStep node $ octant, d)++ nearest' n = nearest n pt++ selectFrom (([], _d) : cs) = selectFrom cs+ selectFrom (([best], _d) : cs) = selectFrom' best cs+ selectFrom [] = Nothing+ selectFrom' best (([], d) : cs) = selectFrom' best cs+ -- TODO: FAILS: shortcut guard to avoid recursion over + -- whole structure (since d is bound for distance within octant):+ selectFrom' best ((c, d) : cs) | d > dist pt (fst best) = Just best+ selectFrom' best (([next], d) : cs) = + selectFrom' nextBest cs+ where nextBest = if dist pt (fst best) <= dist pt (fst next)+ then best+ else next+ selectFrom' best [] = Just best++-- | Internal method that picks from a given list a point closest to argument, +pickClosest :: Vector3 -> [(Vector3, t)] -> Maybe (Vector3, t)+pickClosest pt [] = Nothing+pickClosest pt (a:as) = Just $ foldr (pickCloser pt) a as+pickCloser pt va@(a, _a) vb@(b, _b) = if dist pt a <= dist pt b+ then va+ else vb++-- | Returns all points within Octree that+-- are within a given distance from argument.+withinRange :: Octree a -> Scalar -> Vector3 -> [(Vector3, a)]+withinRange (Leaf l) r pt = filter (\(lpt, _) -> dist pt lpt <= r) l+withinRange node r pt = + concatMap recurseOctant . -- recurse over remaining octants,+ -- and merge results+ filter ((<=r) . snd) . -- discard octants that are out of range+ octantDistances $ pt - split node -- find octant distances+ where+ recurseOctant (octant, _d) =+ (\o -> withinRange o r pt) . octreeStep node $ octant++subnodes :: Octree a -> [Octree a]+subnodes (Leaf _) = []+subnodes node = map (octreeStep node) allOctants++depth :: Octree a -> Int+depth (Leaf _) = 0+depth node = foldr max 0 . map ((+1) . depth) . subnodes $ node++size :: Octree a -> Int+size = length . toList
+ LICENSE view
@@ -0,0 +1,31 @@+Haskell code in this package is subject to:++Copyright (c) Michal J. Gajda '2012++Copyright (c) Michael A. Litchard '2016++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:+1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.+2. Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.+3. Neither the name of the author nor the names of his contributors+ may be used to endorse or promote products derived from this software+ without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF+SUCH DAMAGE.
+ README.lhs view
@@ -0,0 +1,26 @@+point-octree+======+This is a fork of Michał J Gajda's [Octree](https://github.com/BioHaskell/octree) library, with bounding boxes.++[](https://www.travis-ci.org/mlitchard/point-octree)++(From Michal's README.md) +To use simply:++~~~ {.haskell}+module Main where++import Data.Octree as O++import Data.Vector.V3++main = do let oct = fromList [(Vector3 1 2 3, "a"),+ (Vector3 3 4 5, "b"),+ (Vector3 8 8 8, "c")]+ report msg elt = putStrLn $ msg ++ show elt+ report "Nearest :" $ O.nearest oct $ Vector3 2 2 3+ report "Within range:" $ O.withinRange oct 5.0 $ Vector3 2 2 3+ return ()+~~~++
+ Setup.hs view
@@ -0,0 +1,4 @@+#! /usr/bin/env runhaskell++import Distribution.Simple+main = defaultMain
+ changelog view
@@ -0,0 +1,3 @@+-*-Changelog-*-+0.5.5.3 2016/07/03+ Hard fork from octree library
+ point-octree.cabal view
@@ -0,0 +1,85 @@+name: point-octree+version: 0.5.5.3+stability: beta+homepage: https://github.com/mlitchard/point-octree+package-url: http://hackage.haskell.org/package/point-octree+synopsis: Point octree, with bounding boxes+description: Based on Michal J. Gajda's octree package, but with bounding boxes.+category: Data+license: BSD3+license-file: LICENSE+extra-source-files: changelog++author: Michal J. Gajda, Michael Litchard+copyright: Copyright by Michal J. Gajda '2012, Copyright by Michael Litchard '2016+maintainer: michael@schmong.org+bug-reports: mailto:michael@schmong.org+++build-type: Simple+cabal-version: >=1.8+tested-with: GHC==7.6.3,GHC==7.8.3++source-repository head+ type: git+ location: git@github.com:mgajda/octree.git++Library+ build-depends: base >=4.0 && < 4.10,+ AC-Vector >= 2.3.0,+ QuickCheck >= 2.4.0+ + exposed-modules: Data.Octree+ other-modules: Data.Octree.Internal+ Data.Octree.BoundingBox.BoundingBox+ Data.Octree.BoundingBox.Internal+ exposed: True+ extensions: ScopedTypeVariables++Test-suite test_Octree+ Type: exitcode-stdio-1.0+ build-depends: base >=4.0 && < 4.10,+ hspec == 2.2.3,+ AC-Vector >= 2.3.0,+ QuickCheck >= 2.4.0++ hs-source-dirs: ., Data, tests + Main-is: Main.hs+ other-modules: PropTests.Octree+ Data.Octree+ Data.Octree.Internal+ Data.Octree.BoundingBox.BoundingBox+ Data.Octree.BoundingBox.Internal+ PropTests.Common+ PropTests.OctreeTests.Exposed+ PropTests.OctreeTests.Internal+ PropTests.BoundingBoxTests.BoundingBoxTests+ PropTests.BoundingBoxTests.Utilities+Test-suite readme+ type: exitcode-stdio-1.0+ -- We have a symlink: README.lhs -> README.md+ main-is: README.lhs+ Build-depends: base >=4.0 && < 4.10,+ AC-Vector >= 2.3.0,+ QuickCheck >= 2.4.0,+ markdown-unlit+ ghc-options: -pgmL markdown-unlit+ other-modules: Data.Octree+ Data.Octree.Internal++Test-suite readme-BB+ type: exitcode-stdio-1.0+ hs-source-dirs: .+ main-is: Data/Octree/BoundingBox/README.lhs+ Build-depends: base >=4.0 && < 4.10,+ AC-Vector >= 2.3.0,+ QuickCheck >= 2.4.0,+ random == 1.1,+ random-shuffle,+ markdown-unlit++ ghc-options: -pgmL markdown-unlit+ other-modules: Data.Octree+ Data.Octree.Internal+ Data.Octree.BoundingBox.BoundingBox+ Data.Octree.BoundingBox.Internal
+ tests/Main.hs view
@@ -0,0 +1,21 @@+{- |+ Module: tests/Main.hs+ Copyright: 2016 Michael Litchard+ License: BSD3+ Maintainer: <Michael Litchard> <michael@schmong.org>++ Top-level test driver++-}++import Test.Hspec (hspec)++import PropTests.Octree+import PropTests.BoundingBoxTests.BoundingBoxTests++main :: IO ()+main = do+ hspec propOctExposed+ hspec propValidateBoxes + hspec propMatchValuesToBoxes+ hspec propOctInternal
+ tests/PropTests/BoundingBoxTests/BoundingBoxTests.hs view
@@ -0,0 +1,72 @@+{- |+ Module : PropTests.BoundingBoxTests.BoundingBoxTests+ Copyright : Copyright (c) 2016 Michael Litchard+ License : BSD3++ Maintainer : Michael Litchard+ Stability : experimental+ Portability: not portable+ + This module provides property tests for Data.Octree.BoundingBox.BoundingBox++-}++module PropTests.BoundingBoxTests.BoundingBoxTests+ ( propValidateBoxes+ , propMatchValuesToBoxes+ ) where++++import Test.Hspec (Spec, describe)+import Test.Hspec.QuickCheck (prop)++import Data.Octree.Internal (Vector3 (..), fromList)+import Data.Octree.BoundingBox.BoundingBox (BBoxConfig, traverseOctreeBB)++import PropTests.Common+import PropTests.BoundingBoxTests.Utilities+import Data.Octree.BoundingBox.Internal (DefInput, DefOutput, DefNodeValue)++propValidateBoxes :: Spec+propValidateBoxes = + describe "prop_validateBoxes" $+ prop propMsg prop_validateBoxes + where+ propMsg =+ "Testing that arbitarily generated bounding boxes are well-formed."++propMatchValuesToBoxes :: Spec+propMatchValuesToBoxes =+ describe "prop_matchValuesToBox" $+ prop propMsg $+ prop_matchValuesToBox testConfig1 testConfig2+ where+ propMsg = + "Testing that each generated BBox3 maps to correct [(Vector3, Int)]"+ + +-- | validateBoxes takes the list of all bounding boxes of an Octree+-- and determines if they are all well-formed+prop_validateBoxes :: [(Vector3, Int)] -> Bool+prop_validateBoxes l = + let octree' = fromList l+ bbxs = map fst $ traverseOctreeBB testConfig1 bbis octree' (Vector3 0 0 0)+ in all isBBox bbxs++type TestConfigOne = BBoxConfig DefInput [DefOutput] DefNodeValue+type TestConfigTwo = BBoxConfig AltInput DefOutput DefNodeValue+-- | matchValuesToBox verifies that each BBox3 generated by +-- TestConfigOne maps to the correct key list+prop_matchValuesToBox :: TestConfigOne -> + TestConfigTwo -> + [(Vector3, Int)] -> + Bool+prop_matchValuesToBox config1 config2 l =+ let octree' = fromList l+ boxCol1 = traverseOctreeBB config1 bbis octree' x+ x = Vector3 0 0 0 -- this is never used but needs to be present+ xs' = map fst boxCol1+ boxCol2 = map (traverseOctreeBB config2 bbis octree') xs'+ validate = map (`elem` boxCol1) boxCol2+ in and validate
+ tests/PropTests/BoundingBoxTests/Utilities.hs view
@@ -0,0 +1,88 @@+{- |+ Module : PropTests.BoundingBoxTests.Utilities+ Copyright : Copyright (c) 2016 Michael Litchard+ License : BSD3++ Maintainer : Michael Litchard+ Stability : experimental+ Portability: not portable+ + This module provides utility functions for+ PropTests.BoundingBoxTests.External.hs++-}++module PropTests.BoundingBoxTests.Utilities+ ( bbis+ , isBBox+ , testConfig1+ , testConfig2+ , AltInput+ ) where+++import Data.BoundingBox.B3 (BBox3 (..),bound_corners)++import Data.Octree.Internal hiding (lookup)+import Data.Octree.BoundingBox.BoundingBox (BBoxConfig (..))+import Data.Octree.BoundingBox.Internal ( DefInput+ , DefOutput+ , LeafValue+ , DefNodeValue+ , inclusive)+-- | Bounding Box of Infinite Space+-- The root Bounding Box+bbis :: BBox3+bbis =+ bound_corners swdCorner neuCorner+ where+ infinity = read "Infinity" :: Double+ swdCorner = Vector3 (-infinity) (-infinity) (-infinity)+ neuCorner = Vector3 infinity infinity infinity++isBBox :: BBox3 -> Bool+isBBox (BBox3 minX minY minZ maxX maxY maxZ) =+ (minX < maxX) && (minY < maxY) && (minZ < maxZ)+++testConfig1 :: BBoxConfig DefInput [DefOutput] DefNodeValue+testConfig1 = BBoxConfig {+ select = allPoints,+ procLeaf = allBoxesAndKeys,+ combine = assemble+}+ +allBoxesAndKeys :: BBox3 -> + DefInput -> + [LeafValue DefNodeValue ] -> + [DefOutput]+allBoxesAndKeys bbx x leaf = [(bbx, leaf)]++allPoints :: BBox3 -> DefInput -> Maybe DefInput+allPoints bbx = Just++assemble :: DefInput -> [[DefOutput]] -> [DefOutput]+assemble _ = concat++type AltInput = BBox3++testConfig2 :: BBoxConfig AltInput DefOutput DefNodeValue+testConfig2 = BBoxConfig {+ select = filterBoxes,+ procLeaf = checkbox,+ combine = boxResult+}++filterBoxes :: BBox3 -> AltInput -> Maybe AltInput+filterBoxes genBox origBox = + if inclusive origBox genBox then Just origBox else Nothing++checkbox :: BBox3 -> AltInput -> [LeafValue DefNodeValue ] -> DefOutput+checkbox genBox _ leaf = (genBox, leaf)++boxResult :: AltInput -> [DefOutput] -> DefOutput+boxResult origBox allOuts =+ maybe bogusDummy ((,) origBox) $ lookup origBox allOuts+ where+ bogusDummy :: DefOutput+ bogusDummy = (bbis,[])
+ tests/PropTests/Common.hs view
@@ -0,0 +1,34 @@+{- |+ Module : PropTests.Common+ Copyright : Copyright (c) 2012 Michal J. Gajda+ License : BSD3+ + Maintainer : Michael Litchard+ Stability : experimental+ Portability : not portable+ + This module provides some helpers for the tests.++-}++module PropTests.Common where++import Data.Vector.Class (vunpack)+import Data.Vector.V3 (Vector3 (Vector3))+import Test.QuickCheck.Arbitrary++-- | For testing purposes+instance Ord Vector3 where+ a `compare` b = pointwiseOrd $ zipWith compare (vunpack a) (vunpack b)++pointwiseOrd [] = EQ+pointwiseOrd (LT:cs) = LT+pointwiseOrd (GT:cs) = GT+pointwiseOrd (EQ:cs)= pointwiseOrd cs++instance Arbitrary Vector3 where+ arbitrary = do x <- arbitrary+ y <- arbitrary+ z <- arbitrary+ return $ Vector3 x y z+
+ tests/PropTests/Octree.hs view
@@ -0,0 +1,62 @@+{- |+ Module : PropTests.Octree+ Copyright : Copyright (c) 2016 Michael Litchard+ License : BSD3+ + Maintainer : Michael Litchard+ Stability : experimental+ Portability: not portable+ + This module organizes the property tests for Octree proper. +-} ++{-# LANGUAGE ScopedTypeVariables #-}++module PropTests.Octree + ( propOctInternal+ , propOctExposed)+ where++import Data.Octree.Internal+import Data.Octree() -- test that interface module is not broken+import Prelude hiding(lookup)+import Data.List(sort, sortBy)++import Test.Hspec (Spec, describe)+import Test.Hspec.QuickCheck (prop)++import PropTests.OctreeTests.Internal+import PropTests.OctreeTests.Exposed++propOctInternal :: Spec+propOctInternal = + describe "Tests for internal helper functions" $ do+ prop "depth" prop_depth + prop "prop_cmp1" prop_cmp1+ prop "prop_cmp2" prop_cmp2+ prop "prop_stepDescription" prop_stepDescription+ prop "prop_octantDistanceNoGreaterThanInterpointDistance0"+ prop_octantDistanceNoGreaterThanInterpointDistance0+ prop "prop_octantDistanceNoGreaterThanInterpointDistance"+ prop_octantDistanceNoGreaterThanInterpointDistance+ prop "prop_octantDistanceNoGreaterThanInterpointDistanceZero"+ prop_octantDistanceNoGreaterThanInterpointDistanceZero+ prop "prop_octantDistanceNoGreaterThanCentroidDistance"+ prop_octantDistanceNoGreaterThanCentroidDistance+ prop "prop_pickClosest"+ (prop_pickClosest :: [(Vector3, Int)] -> Vector3 -> Bool)++propOctExposed :: Spec+propOctExposed = + describe "Tests for exposed functions" $ do+ prop "prop_lookup" prop_lookup + prop "prop_fromToList" prop_fromToList + prop "prop_insertionPreserved" prop_insertionPreserved + prop "prop_nearest" prop_nearest + prop "prop_naiveWithinRange" prop_naiveWithinRange + prop "prop_fmap1" prop_fmap1+ prop "prop_fmap2" prop_fmap2+ prop "prop_fmap3" prop_fmap3+ prop "prop_depth_empty" prop_depth_empty+ prop "prop_depth_upper_bound" prop_depth_upper_bound+ prop "prop_size" prop_size
+ tests/PropTests/OctreeTests/Exposed.hs view
@@ -0,0 +1,94 @@+{- |+ Module : PropTests.OctreeTests.Exposed+ Copyright : Copyright (c) 2016 Michal J. Gajda+ License : BSD3+ + Maintainer : Michael Litchard+ Stability : experimental+ Portability: not portable+ + This module provides the property tests for exposed octree functions.++-}+++{-# LANGUAGE ScopedTypeVariables #-}+module PropTests.OctreeTests.Exposed+ ( prop_lookup+ , prop_fromToList+ , prop_insertionPreserved+ , prop_nearest+ , prop_naiveWithinRange+ , prop_fmap1+ , prop_fmap2+ , prop_fmap3+ , prop_depth_empty+ , prop_depth_upper_bound+ , prop_size + ) where++import Prelude hiding(lookup)+import Data.List(sort, sortBy)++import Data.Vector.Class+import Control.Arrow(second)++import Data.Octree.Internal+import Data.Octree() -- test that interface module is not broken+import PropTests.Common++prop_lookup :: [(Vector3, Int)] -> Bool+prop_lookup l = all isIn l+ where + ot = fromList l+ isIn x = lookup ot (fst x) == Just x++prop_fromToList :: [(Vector3, Int)] -> Bool+prop_fromToList l = sort l == (sort . toList . fromList $ l)++prop_insertionPreserved :: [(Vector3, Int)] -> Bool+prop_insertionPreserved l = + sort l == (sort . toList . foldr insert (Leaf []) $ l)++prop_nearest :: [(Vector3, Int)] -> Vector3 -> Bool+prop_nearest l pt = nearest (fromList l) pt == naiveNearest pt l++prop_naiveWithinRange r l pt = + naiveWithinRange r pt l == testPoints+ where+ testPoints = + sort . map fst . (\o -> withinRange o r pt) . fromList . tuplify pt $ l++tuplify pt = map (\a -> (a, dist pt a))++compareDistance pt (a,_) (b,_) = compare (dist pt a) (dist pt b)++naiveNearest pt [] = Nothing+naiveNearest pt l = Just $ head byDist+ where byDist = sortBy (compareDistance pt) l++naiveWithinRange r pt = sort . filter withinRange+ where withinRange p = dist pt p <= r++-- unfortunately there is no Arbitrary for (a -> b)+-- since generic properties are quite common, I wonder how to force Quickcheck to default something reasonable?+prop_fmap1,prop_fmap2 :: [(Vector3, Int)] -> Bool+prop_fmap1 = genericPropertyFmap (+1)+prop_fmap2 = genericPropertyFmap (*2)+prop_fmap3 = genericPropertyFmap (show :: Int -> String)++genericPropertyFmap f l = + (sort . map (Control.Arrow.second f) $ l) == fmapTest+ where fmapTest = sort . toList . fmap f . fromList $ l++prop_depth_empty = depth (Leaf []) == 0++prop_depth_upper_bound :: [(Vector3, Int)] -> Bool+prop_depth_upper_bound l = + depth ot <= max 0 (ceiling . logBase 2 . realToFrac $ size) -- worst splitting ratio possible when we take midpoint (and inputs are colinear)+ where + ot = fromList l+ size = length l++prop_size :: [(Vector3, Int)] -> Bool+prop_size l = size (fromList l) == length l
+ tests/PropTests/OctreeTests/Internal.hs view
@@ -0,0 +1,143 @@+{- |+ Module : PropTests.OctreeTests.Internal+ Copyright : Copyright (c) 2016 Michal J. Gajda+ License : BSD3+ + Maintainer : Michael Litchard+ Stability : experimental+ Portability: not portable+ + This module provides tests for internal helper functions:+ +-}++{-# LANGUAGE ScopedTypeVariables #-}+module PropTests.OctreeTests.Internal+ ( prop_depth+ , prop_cmp1+ , prop_cmp2+ , prop_stepDescription+ , prop_octantDistanceNoGreaterThanInterpointDistance0+ , prop_octantDistanceNoGreaterThanInterpointDistance+ , prop_octantDistanceNoGreaterThanInterpointDistanceZero+ , prop_octantDistanceNoGreaterThanCentroidDistance+ , prop_pickClosest+ ) where++import Prelude hiding(lookup)+import Data.List(sort, sortBy)++import Control.Arrow(second)++import Data.Octree.Internal+import Data.Octree() -- test that interface module is not broken++import PropTests.Common++-- for easier testing+origin :: Vector3+origin = 0++prop_depth [] = True -- fudging , what is true fix?+prop_depth a = + (depth oct <= ((+1) . ceiling $ expectedDepth)) &&+ (depth oct >= ((\a -> a-1) . floor $ expectedDepth))+ where+ expectedDepth =+ (logBase 8 :: Double -> Double) . fromIntegral . length $ a+ oct :: Octree Int = fromList a++prop_cmp1 a b = cmp a b == joinStep (dx >= 0, dy >= 0, dz >= 0)+ where Vector3 dx dy dz = a - b++prop_cmp2 a = cmp a origin == joinStep (dx >= 0, dy >= 0, dz >= 0)+ where Vector3 dx dy dz = a++prop_stepDescription a b = + splitStep (cmp a b) == (v3x a >= v3x b, v3y a >= v3y b, v3z a >= v3z b)++prop_octantDistanceNoGreaterThanInterpointDistance0 ptA ptB = triangleInequality+ where + triangleInequality = octantDistance' aptA (cmp ptB origin) <= dist aptA ptB+ aptA = abs ptA++prop_octantDistanceNoGreaterThanInterpointDistance ptA ptB vp = + triangleInequality+ where + triangleInequality = octantDistance (ptA - vp) (cmp ptB vp) <= dist ptA ptB+ sameOctant = cmp ptA vp == cmp ptB vp++prop_octantDistanceNoGreaterThanInterpointDistanceZero ptA ptB = + triangleInequality+ where + triangleInequality = octantDistance ptA (cmp ptB origin) <= dist ptA ptB+ sameOctant = cmp ptA origin == cmp ptB origin++prop_octantDistanceNoGreaterThanInterpointDistanceZero0 ptA ptB = + triangleInequality+ where + triangleInequality = octantDistance aptA (cmp ptB origin) <= dist aptA ptB+ sameOctant = cmp aptA origin == cmp ptB origin+ aptA = abs ptA++prop_octantDistanceNoGreaterThanCentroidDistance pt vp = all testFun allOctants+ where testFun odir = octantDistance (pt - vp) odir <= dist pt vp++prop_splitByPrime splitPt pt = + (unLeaf . octreeStep ot . cmp pt $ splitPt) == [arg]+ where + ot = splitBy' Leaf splitPt [arg] + arg = (pt, dist pt splitPt)+++prop_pickClosest :: (Eq a) => [(Vector3, a)] -> Vector3 -> Bool+prop_pickClosest l pt = pickClosest pt l == naiveNearest pt l++-- | These are tests for exposed functions:++prop_lookup l = all isIn l+ where + ot = fromList l+ isIn x = lookup ot (fst x) == Just x++prop_fromToList l = sort l == (sort . toList . fromList $ l)+prop_insertionPreserved l = + sort l == (sort . toList . foldr insert (Leaf []) $ l)+prop_nearest l pt = nearest (fromList l) pt == naiveNearest pt l+prop_naiveWithinRange r l pt = + naiveWithinRange r pt l == testPoints+ where + testPoints = + sort . map fst . (\o -> withinRange o r pt) . fromList . tuplify pt $ l++tuplify pt = map (\a -> (a, dist pt a))++compareDistance pt (a,_) (b,_) = compare (dist pt a) (dist pt b)++naiveNearest pt [] = Nothing+naiveNearest pt l = Just $ head byDist+ where byDist = sortBy (compareDistance pt) l++naiveWithinRange r pt = sort . filter withinRange+ where withinRange p = dist pt p <= r++-- unfortunately there is no Arbitrary for (a -> b)+-- since generic properties are quite common, I wonder how to force Quickcheck to default something reasonable?+prop_fmap1,prop_fmap2 :: [(Vector3, Int)] -> Bool+prop_fmap1 = genericPropertyFmap (+1)+prop_fmap2 = genericPropertyFmap (*2)+prop_fmap3 = genericPropertyFmap (show :: Int -> String)++genericPropertyFmap f l = + (sort . map (Control.Arrow.second f) $ l) == testFmap + where testFmap = sort . toList . fmap f . fromList $ l++prop_depth_empty = depth (Leaf []) == 0++prop_depth_upper_bound l = + depth ot <= max 0 (ceiling . logBase 2 . realToFrac $ size) -- worst splitting ratio possible when we take midpoint (and inputs are colinear)+ where + ot = fromList l+ size = length l++prop_size l = size (fromList l) == length l