convexHullNd-0.1.0.0: src/Geometry/ConvexHull/ConvexHull.hs
{- HLINT ignore "Use head" -}
module Geometry.ConvexHull.ConvexHull
where
import Control.Monad ( unless, when )
import Geometry.ConvexHull.CConvexHull ( c_convexhull, cConvexHullToConvexHull )
import Geometry.ConvexHull.Types ( Ridge
, Facet(
_fvertices
, _fridges
, _fedges
, _normal'
, _centroid
, _area
)
, ConvexHull(
_hfacets
, _hridges
, _dimension
, _simplicial
)
)
import Data.Function ( on )
import Data.Graph ( flattenSCCs, stronglyConnComp )
import qualified Data.HashMap.Strict.InsOrd as H
import Data.IntMap.Strict ( IntMap )
import qualified Data.IntMap.Strict as IM
import qualified Data.IntSet as IS
import Data.List ( nubBy, findIndices, groupBy, partition, intercalate )
import Data.List.Index ( imap )
import Data.List.Unique ( allUnique, count_ )
import Data.Tuple.Extra ( both )
import Foreign.C.String ( newCString )
import Foreign.C.Types ( CDouble, CUInt )
import Foreign.Marshal.Alloc ( free, mallocBytes )
import Foreign.Marshal.Array ( pokeArray )
import Foreign.Storable ( peek, sizeOf )
import Geometry.Qhull.Shared ( verticesIds, sameFamily, nVertices, nEdges, edgesIds )
import Geometry.Qhull.Types ( IndexPair(Pair)
, IndexMap
, Index
, HasVertices(_vertices)
, HasNormal(_normal)
, HasFamily(_family)
, HasEdges(_edges)
, Family(None)
, EdgeMap )
import Text.Printf ( printf )
import Text.Regex ( mkRegex, subRegex )
convexHull :: [[Double]] -- ^ vertices
-> Bool -- ^ whether to triangulate
-> Bool -- ^ whether to print output to stdout
-> Maybe FilePath -- ^ write summary to a file
-> IO ConvexHull
convexHull points triangulate stdout file = do
let n = length points
dim = length (head points)
when (dim < 2) $
error "dimension must be at least 2"
unless (all ((== dim) . length) (tail points)) $
error "the points must have the same dimension"
when (n <= dim) $
error "insufficient number of points"
unless (allUnique points) $
error "some points are duplicated"
pointsPtr <- mallocBytes (n * dim * sizeOf (undefined :: CDouble))
pokeArray pointsPtr (concatMap (map realToFrac) points)
exitcodePtr <- mallocBytes (sizeOf (undefined :: CUInt))
summaryFile <- maybe (newCString []) newCString file
resultPtr <- c_convexhull pointsPtr (fromIntegral dim) (fromIntegral n)
(fromIntegral $ fromEnum triangulate)
(fromIntegral $ fromEnum stdout) summaryFile exitcodePtr
exitcode <- peek exitcodePtr
free exitcodePtr
free pointsPtr
if exitcode /= 0
then do
free resultPtr
error $ "qhull returned an error (code " ++ show exitcode ++ ")"
else do
result <- (>>=) (peek resultPtr) cConvexHullToConvexHull
free resultPtr
return result
-- | convex hull summary
hullSummary :: ConvexHull -> String
hullSummary hull =
"Convex hull:\n" ++
printf "%d vertices\n" (IM.size vertices) ++
printf "%d facets (%s)\n" nfacets families ++
printf "%d ridges\n" nridges ++
printf "%d edges\n" nedges ++
(if dim > 2
then printf "number of vertices per facet: %s\n" (show counts_vertices) ++
(if dim > 3
then printf "number of edges per facet: %s\n" (show counts_edges) ++
printf "number of ridges per facet: %s\n" (show counts_ridges)
else "")
else "")
where
vertices = _vertices hull
nedges = nEdges hull
nridges = IM.size (_hridges hull)
facets = _hfacets hull
facets' = IM.elems facets
nfacets = IM.size facets
(nf1,nf2) = both length $
partition (None ==) (nubBy sameFamily (map _family facets'))
families = show nf1 ++ " single, " ++
show nf2 ++ if nf2 > 1 then " families" else " family"
dim = length $ head (IM.elems vertices)
counts_vertices = count_ (map nVertices facets')
counts_edges = count_ (map nEdges facets')
counts_ridges = count_ (map (IS.size . _fridges) facets')
-- | volume of the convex hull (area in dimension 2, volume in dimension 3,
-- hypervolume in higher dimension)
hullVolume :: ConvexHull -> Double
hullVolume hull = sum (map fvolume facets) / fromIntegral (_dimension hull)
where
fvolume :: Facet -> Double
fvolume fct =
sum (zipWith (*) (_centroid fct) (_normal' fct)) * _area fct
facets = IM.elems (_hfacets hull)
-- | facets ids an edge belongs to
edgeOf :: ConvexHull -> (Index, Index) -> [Int]
edgeOf hull (v1,v2) = IM.keys $ IM.filter (elem (Pair v1 v2)) facetsEdges
where
facetsEdges = IM.map edgesIds (_hfacets hull)
-- | ridges of a facet
facetRidges :: ConvexHull -> Facet -> IntMap Ridge
facetRidges hull facet = IM.restrictKeys (_hridges hull) (_fridges facet)
-- | vertices ids of all facets
facetsVerticesIds :: ConvexHull -> [[Index]]
facetsVerticesIds hull = map verticesIds (IM.elems $ _hfacets hull)
-- | vertices ids of all ridges
ridgesVerticesIds :: ConvexHull -> [[Index]]
ridgesVerticesIds hull = map verticesIds (IM.elems (_hridges hull))
-- | group facets of the same family
groupedFacets :: ConvexHull -> [(Family, [IndexMap [Double]], [EdgeMap])]
groupedFacets hull =
zip3 (map head families) verticesGroups edgesGroups
where
facets = IM.elems (_hfacets hull)
facetsGroups = groupBy (sameFamily `on` _family) facets
edgesGroups = map (map _fedges) facetsGroups
verticesGroups = map (map _fvertices) facetsGroups
families = map (map _family) facetsGroups
-- | group facets of the same family and merge vertices and edges
groupedFacets' :: ConvexHull -> [(Family, IndexMap [Double], EdgeMap)]
groupedFacets' hull =
map (\(f,v,e) -> (f, foldr IM.union IM.empty v, foldr delta H.empty e))
(groupedFacets hull)
-- zip3 (map head families) (map (foldr IM.union IM.empty) verticesGroups)
-- (map (foldr delta H.empty) edgesGroups)
where
-- facets = IM.elems (_hfacets hull)
-- facetsGroups = groupBy (sameFamily `on` _family) facets
-- edgesGroups = map (map _fedges) facetsGroups
-- verticesGroups = map (map _fvertices) facetsGroups
-- families = map (map _family) facetsGroups
delta :: EdgeMap -> EdgeMap -> EdgeMap
delta e1 e2 = H.difference (H.union e1 e2) (H.intersection e1 e2)
-- data Vertex3 = Vertex3 Double Double Double
-- deriving Show
--
-- toVertex3 :: [Double] -> Vertex3
-- toVertex3 xs = Vertex3 (xs!!0) (xs!!1) (xs!!2)
-- | for 3D only, orders the vertices of the facet (i.e. provides a polygon);
-- also returns a Boolean indicating the orientation of the vertices
facetToPolygon :: Facet -> ([(Index, [Double])], Bool)
facetToPolygon facet = (polygon, dotProduct > 0)
where
vs = IM.toList $ _vertices facet
x = imap (\i v -> (v, i, findIndices (connectedVertices v) vs)) vs
where
connectedVertices :: (Index, [Double]) -> (Index, [Double]) -> Bool
connectedVertices (i,_) (j,_) = Pair i j `H.member` _edges facet
polygon = flattenSCCs (stronglyConnComp x)
vertices = map snd polygon
v1 = vertices !! 0
v2 = vertices !! 1
v3 = vertices !! 2
normal = crossProd (zipWith subtract v1 v2) (zipWith subtract v1 v3)
where
crossProd u v = [ u!!1 * v!!2 - u!!2 * v!!1
, u!!2 * v!!0 - u!!0 * v!!2
, u!!0 * v!!1 - u!!1 * v!!0 ]
dotProduct = sum $ zipWith (*) normal (_normal facet)
-- | for 3D only, orders the vertices of the facet (i.e. provides a polygon)
-- in anticlockwise orientation
facetToPolygon' :: Facet -> [(Index, [Double])]
facetToPolygon' facet = if test then polygon else reverse polygon
where
(polygon, test) = facetToPolygon facet
-- -- | like `facetToPolygon`, but returns the vertices indices
-- facetToPolygon' :: Facet -> [Index]
-- facetToPolygon' facet = map fst $ flattenSCCs (stronglyConnComp x)
-- where
-- vs = IM.toList $ _vertices facet
-- x = imap (\i v -> (v, i, findIndices (connectedVertices v) vs)) vs
-- connectedVertices :: (Index, [Double]) -> (Index, [Double]) -> Bool
-- connectedVertices (i,_) (j,_) = Pair i j `H.member` _edges facet
-- | for 4D only, orders the vertices of a ridge (i.e. provides a polygon)
ridgeToPolygon :: Ridge -> [(Index, [Double])]
ridgeToPolygon ridge = flattenSCCs (stronglyConnComp x)
where
vs = IM.toList $ _vertices ridge
x = imap (\i v -> (v, i, findIndices (connectedVertices v) vs)) vs
where
connectedVertices :: (Index, [Double]) -> (Index, [Double]) -> Bool
connectedVertices (i,_) (j,_) = Pair i j `H.member` _edges ridge
-- | for 3D only, convert the convex hull to a STL file;
-- the STL format is valid for triangle meshes only
hullToSTL :: ConvexHull -> FilePath -> IO ()
hullToSTL chull filename = do
when (_dimension chull /= 3) $
error "dimension is not 3"
unless (_simplicial chull) $
print "* are you sure this convex hull is triangulated?\n"
let facets = IM.elems (_hfacets chull)
normals = map _normal facets
facetNormals =
map (\n -> "facet normal " ++ stringify " " n ++ "\nouter loop\n")
normals
polygons =
map (\f -> "vertex " ++
(stringify "\nvertex " . map snd . facetToPolygon') f) facets
vertices = map (++ "\nendloop\nendfacet\n") polygons
vertices' =
map (\v -> subRegex (mkRegex "\\]") (subRegex (mkRegex "\\[") v "") "")
vertices
out = subRegex
(mkRegex ",") (concat [x ++ y | x <- facetNormals, y <- vertices']) " "
writeFile filename ("solid " ++ filename ++ " produced by Haskell\n" ++ out)
where
stringify :: Show a => String -> [a] -> String
stringify sep = intercalate sep . map show