packages feed

marching-cubes-0.1.0.0: src/MarchingCubes/Mesh.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TupleSections    #-}
module MarchingCubes.Mesh
  ( makeMesh
  , Mesh (..)
  ) where
import           Data.Array.Unboxed             ( IArray
                                                , UArray
                                                )
import           Data.Bifunctor                 ( bimap )
import           Data.Foldable                  ( foldl' )
import           Data.IntMap.Strict             ( IntMap
                                                , elems
                                                , unionWith
                                                )
import qualified Data.IntMap.Strict            as IM
import           Data.List.Split                ( chunksOf )
import           Data.Map.Strict                ( insertLookupWithKey )
import qualified Data.Map.Strict               as M
import           Data.Matrix                    ( Matrix
                                                , nrows
                                                , toLists
                                                )
import           Data.Tuple.Extra               ( (***)
                                                , curry3
                                                , second
                                                , thd3
                                                )
import           Data.Vector.Unboxed            ( (!)
                                                , Unbox
                                                , Vector
                                                , fromList
                                                )
import           Linear                         ( V3(..)
                                                , (^+^)
                                                , (^-^)
                                                , cross
                                                , signorm
                                                )
import           MarchingCubes.MarchingCubes    ( Voxel
                                                , marchingCubes
                                                )

-- type Mesh a = ((Vector (a, a, a), [[Int]]), Vector (a, a, a))

data Mesh a = Mesh 
  { 
    _vertices :: Vector (a, a, a)
  , _faces :: [(Int, Int, Int)] 
  , _normals :: Vector (a, a, a)
  } 
  deriving Show

matrix2listOfTriples :: Matrix a -> [(a, a, a)]
matrix2listOfTriples mtrx =
  map (\row -> (row !! 0, row !! 1, row !! 2)) (toLists mtrx)

uniqueWithIndices :: (Unbox a, Ord a) => [a] -> (Vector a, Vector Int)
uniqueWithIndices list = (fromList *** fromList) $ go 0 M.empty list
 where
  go _ _ []       = ([], [])
  go i m (x : xs) = case insertLookupWithKey (curry3 thd3) x i m of
    (Nothing, m') -> bimap (x :) (i :) (go (i + 1) m' xs)
    (Just j , m') -> second (j :) (go i m' xs)

undupMesh
  :: (Unbox a, Ord a) => ([(a, a, a)], [[Int]]) -> (Vector (a, a, a), [[Int]])
undupMesh (vertices, faces) = (vertices', faces')
 where
  (vertices', idx) = uniqueWithIndices vertices
  faces'           = map (map (idx !)) faces

normal :: Floating a => (a, a, a) -> (a, a, a) -> (a, a, a) -> V3 a
normal v1 v2 v3 = signorm $ cross (v3' ^-^ v1') (v2' ^-^ v1') -- (NaN,NaN,NaN) if degenerate face
 where
  toV3 (x, y, z) = V3 x y z
  v1' = toV3 v1
  v2' = toV3 v2
  v3' = toV3 v3

faceNormals
  :: (Floating a, Unbox a) => Vector (a, a, a) -> [Int] -> IntMap (V3 a)
faceNormals vs face = IM.fromList nrmls
 where
  nrml  = normal (vs ! (face !! 0)) (vs ! (face !! 1)) (vs ! (face !! 2))
  nrmls = map (, nrml) face

normalsV3
  :: (Floating a, Unbox a) => (Vector (a, a, a), [[Int]]) -> IntMap (V3 a)
normalsV3 (vs, faces) = IM.map
  signorm
  (foldl' (unionWith (^+^)) IM.empty (map (faceNormals vs) faces))

normals
  :: (Floating a, Unbox a) => (Vector (a, a, a), [[Int]]) -> Vector (a, a, a)
normals mesh = fromList $ map fromV3 (elems $ normalsV3 mesh)
  where fromV3 (V3 x y z) = (x, y, z)

degenerateFace :: (Unbox a, Eq a) => Vector (a, a, a) -> [Int] -> Bool
degenerateFace vertices face = v1 == v2 || v1 == v3 || v2 == v3
 where
  v1 = vertices ! (face !! 0)
  v2 = vertices ! (face !! 1)
  v3 = vertices ! (face !! 2)

-- | Make mesh of isosurface.
makeMesh :: 
  (Unbox a, RealFloat a, IArray UArray a) 
  => Voxel a -- ^ voxel obtained with `makeVoxel`
  -> a       -- ^ isovalue
  -> Mesh a
makeMesh voxel level = Mesh 
  {
    _vertices = vertices', _faces = faces''', _normals = normals mesh
  }
 where
  mtrx                = marchingCubes voxel level
  vertices            = matrix2listOfTriples mtrx
  faces               = chunksOf 3 [0 .. nrows mtrx - 1]
  (vertices', faces') = undupMesh (vertices, faces)
  faces''             = filter (not . degenerateFace vertices') faces'
  faces'''            = map toTriplet faces''
  mesh                = (vertices', faces'')
  toTriplet ikj       = (ikj !! 0, ikj !! 2, ikj !! 1)