packages feed

marching-cubes2-0.1.0.0: src/MarchingCubes/MarchingCubes.hs

{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE FlexibleContexts         #-}
module MarchingCubes.MarchingCubes
  ( Voxel
  , XYZ
  , marchingCubes
  , makeVoxel
  ) where
import           Control.Monad                 (when)
import           Data.List                     (transpose)
import           Foreign.C.Types
import           Foreign.Marshal.Alloc         (free, mallocBytes)
import           Foreign.Marshal.Array         (peekArray, pokeArray)
import           Foreign.Ptr                   (Ptr)
import           Foreign.Storable              (peek, sizeOf)
import           Data.Matrix                   ( Matrix
                                               , mapCol
                                               , fromLists )

type Bounds a = ((a, a), (a, a), (a, a))
type Dims = (Int, Int, Int)
type Voxel a = ([a], (Bounds a, Dims))
type XYZ a = (a, a, a)

-- | Make the voxel. 
makeVoxel
  :: RealFloat a
  => (XYZ a -> a) -- ^ the function defining the isosurface
  -> Bounds a     -- ^ bounds of the grid
  -> Dims         -- ^ numbers of subdivisions of the grid
  -> Voxel a
makeVoxel fun bds@((xm, xM), (ym, yM), (zm, zM)) dims@(nx, ny, nz) =
  (values, (bds, dims))
  where
    x_ = [ xm + (xM - xm) * fracx i | i <- [0 .. nx - 1] ]
    fracx p = realToFrac p / (realToFrac nx - 1)
    y_ = [ ym + (yM - ym) * fracy i | i <- [0 .. ny - 1] ]
    fracy p = realToFrac p / (realToFrac ny - 1)
    z_ = [ zm + (zM - zm) * fracz i | i <- [0 .. nz - 1] ]
    fracz p = realToFrac p / (realToFrac nz - 1)
    values = [ fun (x, y, z) | x <- x_, y <- y_, z <- z_ ]

voxelMax :: RealFloat a => Voxel a -> a
voxelMax (values, _) = maximum (filter (not . isNaN) values)

rescale :: Fractional a => (a, a) -> Int -> a -> a
rescale (minmm, maxmm) n w = minmm + (maxmm - minmm) * w / fromIntegral (n - 1)

rescaleMatrix :: Fractional a => Matrix a -> Bounds a -> Dims -> Matrix a
rescaleMatrix mtrx (xbds, ybds, zbds) (nx, ny, nz) = mtrx'''
 where
  mtrx'   = mapCol (\_ w -> rescale xbds nx (w - 1)) 1 mtrx
  mtrx''  = mapCol (\_ w -> rescale ybds ny (w - 1)) 2 mtrx'
  mtrx''' = mapCol (\_ w -> rescale zbds nz (w - 1)) 3 mtrx''

foreign import ccall unsafe "computeContour3d" c_computeContour3d
    :: Ptr CDouble
    -> CUInt
    -> CUInt
    -> CUInt
    -> CDouble
    -> CDouble
    -> Ptr CSize
    -> IO (Ptr (Ptr CDouble))

computeContour3d :: 
  Voxel Double -> Double -> IO (Ptr (Ptr CDouble), Int)
computeContour3d voxel level = do 
  let voxmax = voxelMax voxel
      (nx, ny, nz) = snd $ snd voxel 
      voxel' = map realToFrac (fst voxel)
  nrowsPtr <- mallocBytes (sizeOf (undefined :: CSize))
  putStrLn "Allocating voxel"
  voxelPtr <- mallocBytes (nx*ny*nz * sizeOf (undefined :: CDouble))
  putStrLn "Poking voxel..."
  pokeArray voxelPtr voxel'
  putStrLn "Run C code..."
  result <- c_computeContour3d voxelPtr
            (fromIntegral nx) (fromIntegral ny) (fromIntegral nz)
            (realToFrac voxmax) (realToFrac level) nrowsPtr
  nrows <- peek nrowsPtr
  free nrowsPtr
  free voxelPtr
  return (result, fromIntegral nrows)

marchingCubes :: Voxel Double -> Double -> Bool -> IO (Matrix Double)
marchingCubes voxel level summary = do
  let (bds@(xbds, ybds, zbds), dims@(nx, ny, nz)) = snd voxel
  (ppCDouble, nrows) <- computeContour3d voxel level
  cpoints <- mapM (peekArray 3) =<< peekArray nrows ppCDouble
  let toDouble :: CDouble -> Double
      toDouble = realToFrac
      points = map (map toDouble) cpoints
  when summary $ do
    let tpoints = transpose points
        x = tpoints !! 0
        y = tpoints !! 1
        z = tpoints !! 2
        xm = minimum x - 1
        xM = maximum x - 1
        ym = minimum y - 1
        yM = maximum y - 1
        zm = minimum z - 1
        zM = maximum z - 1
        xmin = rescale xbds nx xm
        xmax = rescale xbds nx xM
        ymin = rescale ybds ny ym
        ymax = rescale ybds ny yM
        zmin = rescale zbds nz zm
        zmax = rescale zbds nz zM
    putStrLn "Prebounds:"
    print ((xm, ym, zm), (xM, yM, zM))
    putStrLn "Bounds:"
    print ((xmin, ymin, zmin), (xmax, ymax, zmax))
  return $ rescaleMatrix (fromLists points) bds dims