module Graphics.Formats.Obj.Parse (parseTests,mtllibs) where
import Graphics.Formats.Obj.Contents
import Graphics.Formats.Obj.ParserBits
import Test.QuickCheck
import Data.Maybe hiding (fromJust)
import Data.Binary
import Data.Binary.Get
import Data.Binary.Put
import qualified Data.ByteString.Lazy as LBS
import qualified Data.ByteString.Char8 as CBS
import Control.Monad
import Control.Applicative
instance Binary ObjFile where
put (OF sts) =
forM_ sts put
get =
return . OF
. catMaybes
. map decodeStmt
. CBS.lines
. CBS.concat
. LBS.toChunks =<< getRemainingLazyByteString
instance Binary Statement where
put (V x y z w) =
do putString "v "
putShow x >> put ' '
putShow y >> put ' '
putShow z >> put ' '
putShow w >> put ' '
put '\n'
put (VN x y z) =
do putString "vn "
putShow x >> put ' '
putShow y >> put ' '
putShow z >> put ' '
put '\n'
put (VT x y z) =
do putString "vt "
putShow x >> put ' '
putShow y >> put ' '
putShow z >> put ' '
put '\n'
put (P is) = put 'p' >> putList putShow is >> put '\n'
put (L is) = put 'l' >> putList putDouble is >> put '\n'
put (F is) = put 'f' >> putList putTriple is >> put '\n'
put (G gs) = put 'g' >> putList putByteString gs >> put '\n'
put (SG g) = putString "s " >> putShow g >> put '\n'
put (MtlLib m) = put "mtllib" >> putList putByteString m >> put '\n'
put (UseMtl m) = put "usemtl " >> putByteString m >> put '\n'
get = undefined
putString :: String -> Put
putString = putByteString . CBS.pack
putShow :: Show a => a -> Put
putShow = putString . show
putList :: (a -> Put) -> [a] -> Put
putList f x = forM_ x (\i -> put ' ' >> f i)
putDouble :: VDouble -> Put
putDouble (VD x (Just y)) = putShow x >> put '/' >> putShow y
putDouble (VD x Nothing ) = putShow x
putTriple :: VTriple -> Put
putTriple (VTr v t n) =
putShow v >>
case (t,n) of
(Nothing,Just n') -> putString "//" >> putShow n'
_ -> put' t >> put' n
where
put' x = case x of
Nothing -> return ()
Just x' -> put '/' >> putShow x'
decodeStmt :: CBS.ByteString -> Maybe Statement
decodeStmt = decodeStmt' . consumeWS . removeComments
decodeStmt' :: CBS.ByteString -> Maybe Statement
decodeStmt' s =
if CBS.length s > 0 then
case CBS.head s of
'p' -> Just . P . runParse parsePoints $ s
'l' -> Just . L . runParse parseLines $ s
'f' -> Just . F . runParse parseFace $ s
'g' -> Just . G . runParse parseGroups $ s
's' -> Just . SG . runParse parseSmoothGroup $ s
_ | (CBS.pack "vn") `CBS.isPrefixOf` s
-> Just . runParse parseNormal . CBS.tail $ s
_ | (CBS.pack "vt") `CBS.isPrefixOf` s
-> Just . runParse parseTexCoord . CBS.tail $ s
'v' -> Just . runParse parseVertex $ s
_ | (CBS.pack "mtllib") `CBS.isPrefixOf` s
-> Just . MtlLib . runParse parseMtlLib . CBS.drop 5 $ s
_ | (CBS.pack "usemtl") `CBS.isPrefixOf` s
-> Just . UseMtl . runParse parseUseMtl . CBS.drop 5 $ s
_ -> Nothing
else Nothing
runParse :: (CBS.ByteString -> a) -> CBS.ByteString -> a
runParse x = x . consumeWS . CBS.tail
if' :: Bool -> a -> a -> a
if' c t e = if c then t else e
parsePoints :: CBS.ByteString -> [Int]
parseLines :: CBS.ByteString -> [VDouble]
parseFace :: CBS.ByteString -> [VTriple]
parsePoints = map unsafeReadInt . bsWords
parseLines = map readDouble . bsWords
parseFace = map readTriple . bsWords
parseGroups :: CBS.ByteString -> [CBS.ByteString]
parseSmoothGroup :: CBS.ByteString -> Int
parseGroups = map parseName . bsWords
parseSmoothGroup = if' <$> (== CBS.pack "off") <*> (const 0) <*> unsafeReadInt
parseMtlLib :: CBS.ByteString -> [CBS.ByteString]
parseUseMtl :: CBS.ByteString -> CBS.ByteString
parseMtlLib = map parseName . bsWords
parseUseMtl = parseName . head . bsWords
parseNormal :: CBS.ByteString -> Statement
parseTexCoord :: CBS.ByteString -> Statement
parseVertex :: CBS.ByteString -> Statement
parseNormal s = let Just (x,s' ) = unsafeRFloat s
Just (y,s'') = unsafeRFloat s'
Just (z,_ ) = unsafeRFloat s''
in VN x y z
parseTexCoord s = let Just (x,s') = unsafeRFloat s
y = unsafeRFloat s'
in case y of
Just (y',r) -> case unsafeRFloat r of
Just (z,_) -> VT x y' z
Nothing -> VT x y' 0
Nothing -> VT x 0 0
parseVertex s = let Just (x,s' ) = unsafeRFloat s
Just (y,s'' ) = unsafeRFloat s'
Just (z,s''') = unsafeRFloat s''
w = unsafeRFloat s'''
in case w of
Just (w',_) -> V x y z w'
Nothing -> V x y z 1
unsafeReadInt :: CBS.ByteString -> Int
unsafeReadInt x = case CBS.readInt x of
Just (i,_) -> i
Nothing -> error "unsafeReadInt: No integer to read."
readDouble :: CBS.ByteString -> VDouble
readDouble x =
if CBS.length b > 1
then VD (unsafeReadInt a) (Just . unsafeReadInt $ CBS.tail b)
else VD (unsafeReadInt a) Nothing
where
(a,b) = CBS.break (=='/') x
readTriple :: CBS.ByteString -> VTriple
readTriple vtns =
VTr v t n
where
(vs,tnr) = CBS.break (=='/') vtns
(ts,nr ) = if CBS.length tnr > 0
then CBS.break (=='/') . CBS.tail $ tnr
else (CBS.empty, CBS.empty)
ns = if CBS.length nr > 0
then CBS.tail nr
else CBS.empty
v = unsafeReadInt vs
t = getMaybeInt ts
n = getMaybeInt ns
getMaybeInt x = if CBS.length x > 0
then Just $ unsafeReadInt x
else Nothing
mtllibs :: ObjFile -> [CBS.ByteString]
mtllibs (OF f) = concatMap stmtMtlLibs f
stmtMtlLibs :: Statement -> [CBS.ByteString]
stmtMtlLibs (MtlLib xs) = xs
stmtMtlLibs _ = []
prop_parseUnParse :: ObjFile -> Bool
prop_parseUnParse x =
(decode . encode $ x) == x
parseTests :: IO ()
parseTests = do putStr "prop_parseUnParse: "
quickCheck prop_parseUnParse