pioneers/src/Importer/IQM/Parser.hs
2014-04-26 00:15:36 +02:00

249 lines
8.7 KiB
Haskell

{-# LANGUAGE RankNTypes #-}
-- | Parser for IQM-Files
--
-- Assumes that the file is stored with 32-Bit-BigEndian-Ints
module Importer.IQM.Parser (parseIQM) where
import Importer.IQM.Types
import Data.Attoparsec.ByteString.Char8
import Data.Attoparsec.ByteString
import Data.Attoparsec.Binary
import Data.ByteString.Char8 (pack)
import Data.ByteString (split, null, ByteString)
import Data.ByteString.Unsafe (unsafeUseAsCString)
import qualified Data.ByteString as B
import Data.Word
import Data.Int
import Unsafe.Coerce
import Control.Monad.Trans.State
import Control.Monad.Trans.Class
import Control.Monad
import Foreign.Ptr
import Foreign.Marshal.Alloc
import Foreign.Marshal.Utils
import Prelude as P hiding (take, null)
-- | helper-function for creating an integral out of [8-Bit Ints]
_w8ToInt :: Integral a => a -> a -> a
_w8ToInt i add = 256*i + add
-- | shorthand-function for parsing Word8 into Integrals
_parseNum :: (Integral a, Integral b) => [a] -> b
_parseNum = foldl1 _w8ToInt . map fromIntegral
-- | read a 16-Bit Int from Parsing-Input and log 2 bytes in our Parsing-Monad
--
-- begins with _ to defeat ghc-warnings. Rename if used!
_int16 :: CParser Word16
_int16 = do
ret <- lift $ do
a <- anyWord8 :: Parser Word8
b <- anyWord8 :: Parser Word8
return $ _parseNum [b,a]
modify (+2)
return ret
-- | read a 32-Bit Int from Parsing-Input and log 4 bytes in our Parsing-Monad
_int32 :: CParser Int32
_int32 = do
ret <- lift $ do
a <- anyWord8 :: Parser Word8
b <- anyWord8 :: Parser Word8
c <- anyWord8 :: Parser Word8
d <- anyWord8 :: Parser Word8
return $ _parseNum [d,c,b,a]
modify (+4)
return ret
w32leCParser :: CParser Word32
w32leCParser = do
ret <- lift anyWord32le
modify (+4)
return ret
-- | Parser for the header
readHeader :: CParser IQMHeader
readHeader = do
_ <- lift $ string (pack "INTERQUAKEMODEL\0")
modify (+16)
v <- w32leCParser
lift $ when (v /= 2) $ fail "Version /= 2.\nThis Parser only supports Version 2 of the InterQuake-Model IQM"
-- when v /= 2 then fail parsing.
size' <- w32leCParser
flags' <- w32leCParser
num_text' <- w32leCParser
ofs_text' <- w32leCParser
num_meshes' <- w32leCParser
ofs_meshes' <- w32leCParser
num_vertexarrays' <- w32leCParser
num_vertexes' <- w32leCParser
ofs_vertexarrays' <- w32leCParser
num_triangles' <- w32leCParser
ofs_triangles' <- w32leCParser
ofs_adjacency' <- w32leCParser
num_joints' <- w32leCParser
ofs_joints' <- w32leCParser
num_poses' <- w32leCParser
ofs_poses' <- w32leCParser
num_anims' <- w32leCParser
ofs_anims' <- w32leCParser
num_frames' <- w32leCParser
num_framechannels' <- w32leCParser
ofs_frames' <- w32leCParser
ofs_bounds' <- w32leCParser
num_comment' <- w32leCParser
ofs_comment' <- w32leCParser
num_extensions' <- w32leCParser
ofs_extensions' <- w32leCParser
return IQMHeader { version = v
, filesize = size'
, flags = fromIntegral flags'
, num_text = num_text'
, ofs_text = ofs_text'
, num_meshes = num_meshes'
, ofs_meshes = ofs_meshes'
, num_vertexarrays = num_vertexarrays'
, num_vertexes = num_vertexes'
, ofs_vertexarrays = ofs_vertexarrays'
, num_triangles = num_triangles'
, ofs_triangles = ofs_triangles'
, ofs_adjacency = ofs_adjacency'
, num_joints = num_joints'
, ofs_joints = ofs_joints'
, num_poses = num_poses'
, ofs_poses = ofs_poses'
, num_anims = num_anims'
, ofs_anims = ofs_anims'
, num_frames = num_frames'
, num_framechannels = num_framechannels'
, ofs_frames = ofs_frames'
, ofs_bounds = ofs_bounds'
, num_comment = num_comment'
, ofs_comment = ofs_comment'
, num_extensions = num_extensions'
, ofs_extensions = ofs_extensions'
}
-- | Parser for Mesh-Structure
readMesh :: CParser IQMMesh
readMesh = do
name <- w32leCParser
mat <- w32leCParser
fv <- w32leCParser
nv <- w32leCParser
ft <- w32leCParser
nt <- w32leCParser
return IQMMesh
{ meshName = if name == 0 then Nothing else Just (Mesh name)
, meshMaterial = mat
, meshFirstVertex = fv
, meshNumVertexes = nv
, meshFirstTriangle = ft
, meshNumTriangles = nt
}
-- | helper to read n consecutive Meshes tail-recursive
readMeshes :: Int -> CParser [IQMMesh]
readMeshes 1 = do
m <- readMesh
return [m]
readMeshes n = do
m <- readMesh
ms <- readMeshes (n-1)
return $ m:ms
-- | Parser for Mesh-Structure
readVAF :: CParser IQMVertexArray
readVAF = do
vat <- rawEnumToVAT =<< w32leCParser
flags' <- w32leCParser
format <- rawEnumToVAF =<< w32leCParser
size <- w32leCParser
offset <- w32leCParser
return $ IQMVertexArray vat (fromIntegral flags') format (fromIntegral size) offset nullPtr
-- | helper to read n consecutive Meshes tail-recursive
readVAFs :: Int -> CParser [IQMVertexArray]
readVAFs 1 = do
f <- readVAF
return [f]
readVAFs n = do
f <- readVAF
fs <- readVAFs (n-1)
return $ f:fs
-- | helper-Notation for subtracting 2 integral values of different kind in the precision
-- of the target-kind
(.-) :: forall a a1 a2.
(Num a, Integral a2, Integral a1) =>
a1 -> a2 -> a
(.-) a b = fromIntegral a - fromIntegral b
infix 5 .-
-- | skips (=drops) all input until the internal counter is at a given bytecount
--
-- Fails the parser if given bytecount is lower than the internal counter as we
-- read sequentially and do not do backtracking
skipToCounter :: Integral a => a -> CParser ()
skipToCounter a = do
let d = fromIntegral a
c <- get
when (d < c) $ fail "wanting to skip to counter already passed"
_ <- lift $ take $ d .- c
put d
-- | Parses an IQM-File and handles back the Haskell-Structure
parseIQM :: String -> IO IQM
parseIQM a =
do
f <- B.readFile a
putStrLn "Before Parse:"
putStrLn $ show f
putStrLn "Real Parse:"
r <- return $ parse doIQMparse f
raw <- case r of
Done _ x -> return x
y -> error $ show y
let ret = raw
return ret
readInVAO :: IQMVertexArray -> ByteString -> IO IQMVertexArray
readInVAO (IQMVertexArray type' a format num offset ptr) d =
do
let
byteLen = (fromIntegral num)*(vaSize format)
data' = skipDrop (fromIntegral offset) byteLen d
when (not (ptr == nullPtr)) $ error $ "Error reading Vertex-Array: Double Read of " ++ show type'
p <- mallocBytes byteLen
unsafeUseAsCString data' (\s -> copyBytes p s byteLen)
p' <- unsafeCoerce p
return (IQMVertexArray type' a format num offset p')
doIQMparse :: Parser IQM
doIQMparse =
flip evalStateT 0 $ --evaluate parser with state starting at 0
do
h <- readHeader --read header
skipToCounter $ ofs_text h --skip 0-n bytes to get to text
text <- lift . take . fromIntegral $ num_text h --read texts
modify . (+) . fromIntegral $ num_text h --put offset forward
skipToCounter $ ofs_meshes h --skip 0-n bytes to get to meshes
meshes' <- readMeshes $ fromIntegral $ num_meshes h --read meshes
skipToCounter $ ofs_vertexarrays h --skip 0-n bytes to get to Vertex-Arrays
vaf <- readVAFs $ fromIntegral $ num_vertexarrays h --read Vertex-Arrays
_ <- lift takeByteString
return IQM
{ header = h
, texts = filter (not.null) (split (unsafeCoerce '\0') text)
, meshes = meshes'
, vertexArrays = vaf
}
skipDrop :: Int -> Int -> ByteString -> ByteString
skipDrop a b= B.drop b . B.take a