3dmodels-0.3.0: Graphics/Model/DirectX.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
-- | DirectX Object File (.x) Parser
module Graphics.Model.DirectX (
loadXOF,
parseXOF,
reverseHand,
XOF(..),
DxMesh(..),
DxMaterial(..)
) where
import Control.Applicative
import Data.Attoparsec.ByteString.Char8 as A
import qualified Data.ByteString as B
import Data.Traversable
import Data.Word
import Linear
-- | DirectX Object File
data XOF = XOF
{ _xofFormat :: B.ByteString
, _xofTemplates :: [(B.ByteString, [B.ByteString])]
, _xofHeader :: (Word16, Word16, Word32) -- ^ major minor flags
, _xofMesh :: DxMesh
-- , _xofFrames :: [(frame_name, [(mesh_name, DxMesh)])]
-- , _xofAnimationSets :: [DxAnimationSet :: (frame_name, [DxKeyframe])]
} deriving Show
data DxMesh = DxMesh
{ _dxVertices :: [V3 Float]
, _dxFaces :: [Either (V4 Word32) (V3 Word32)]
, _dxIndexes :: [Word32]
, _dxMaterials :: [DxMaterial]
, _dxNormals :: [V3 Float]
, _dxNormalIndexes :: [Either (V4 Word32) (V3 Word32)]
, _dxTexCoords :: [V2 Float]
, _dxVertexColors :: [(Word32, V4 Float)]
} deriving Show
data DxMaterial = DxMaterial
{ _dxFaceColor :: V4 Float
, _dxPower :: Float
, _dxSpecular :: V3 Float
, _dxEmissive :: V3 Float
, _dxTexturePath :: Maybe B.ByteString
} deriving Show
--data DxKeyframe =
-- DxRotate Int Quaternion
-- | DxScale Int (V3 Float)
-- | DxTranslate Int (V3 Float)
-- | DxApplyMatrix Int M44 Float
loadXOF :: FilePath -> IO XOF
loadXOF path = parseXOF <$> B.readFile path
parseXOF :: B.ByteString -> XOF
parseXOF bs =
case feed (parse parseXMesh bs) "" of
Done _ result -> result
result -> error $ "parseXOF: Invalid format:\n" ++ show result
-- | Convert to right handed system.
reverseHand :: XOF -> XOF
reverseHand xof@XOF{_xofMesh=DxMesh{..}, ..} =
xof { _xofMesh = (_xofMesh xof) {
_dxVertices = fmap rhVtx _dxVertices,
_dxFaces = fmap rhFace _dxFaces,
_dxNormals = fmap rhVtx _dxNormals,
_dxNormalIndexes = fmap rhFace _dxNormalIndexes
}}
where rhVtx (V3 x y z) = V3 x y (-z)
rhFace (Right (V3 a b c)) = Right $ V3 b a c
rhFace (Left (V4 a b c d)) = Left $ V4 a d c b
parseXMesh :: Parser XOF
parseXMesh = XOF
<$> string "xof 0302txt 0064" <* skipSpace -- \r\n
<*> many (templateDecl <* skipSpace)
<*> headerDecl <* skipSpace
<*> meshDecl
takeIdent :: Parser B.ByteString
takeIdent = A.takeWhile (inClass "a-zA-Z0-9_")
-- | e.g. template Header { field1; field2; ... }
templateDecl = try $
(,) <$> (string "template" >> skipSpace >> takeIdent <* skipSpace) -- name
<*> braces '{' '}' (skipSpace >> many (fieldDecl <* skipSpace)) -- fields
fieldDecl =
braces '<' '>' (A.takeWhile (/= '>'))
<|> braces '[' ']' (string "..." <|> takeIdent)
<|> A.takeWhile (inClass "a-zA-Z0-9_ []") <* char ';'
float = realToFrac <$> signed double
getVector :: (Applicative a, Traversable a) => Parser (a Float)
getVector = sequenceA $ pure getFloatField
-- | e.g. <aaa> => braces '<' '>' (string "aaa")
braces l r p = char l *> p <* char r
semicolon = char ';' <* skipSpace
comma = char ','
maybeComma = (comma <|> return ' ') >> skipSpace
getIntField = decimal <* semicolon
getFloatField = float <* semicolon
getStringField = braces '"' '"' (A.takeWhile (/= '"')) <* semicolon
getVectorField :: (Applicative a, Traversable a) => Parser (a Float)
getVectorField = getVector <* semicolon
getArray :: Parser a -> Int -> Parser [a]
getArray parser len = count len (parser <* maybeComma) <* semicolon
section name p = do
string name
skipSpace
braces '{' '}' (skipSpace >> p)
headerDecl :: Parser (Word16, Word16, Word32)
headerDecl = section "Header" $ do
major <- fromIntegral <$> getIntField
minor <- fromIntegral <$> getIntField
flags <- fromIntegral <$> getIntField
return (major, minor, flags)
meshDecl :: Parser DxMesh
meshDecl = section "Mesh" $ do
vertices <- getIntField >>= getArray getVector
faces <- getIntField >>= getArray meshFace
(indexes, materials) <- option ([], []) meshMaterialList <* skipSpace
(normals, faceNormals) <- option ([], []) meshNormals <* skipSpace
DxMesh vertices faces indexes materials normals faceNormals
<$> option [] meshTextureCoords <* skipSpace
<*> option [] meshVertexColors <* skipSpace
meshFace = fmap Left meshFace4 <|> fmap Right meshFace3
meshFace4 = do
char '4' >> semicolon
sequenceA $ V4 (decimal <* comma) (decimal <* comma)
(decimal <* comma) (decimal <* char ';')
meshFace3 = do
char '3' >> semicolon
sequenceA $ V3 (decimal <* comma)
(decimal <* comma) (decimal <* char ';')
meshMaterialList = section "MeshMaterialList" $ do
nMaterials <- getIntField
indexes <- getIntField >>= getArray faceIndex
semicolon
materials <- count nMaterials (materialDecl <* skipSpace)
return (indexes, materials)
faceIndex :: Parser Word32
faceIndex = decimal
materialDecl = section "Material" $ do
faceColor <- getVectorField
power <- getFloatField
specularColor <- getVectorField
emissiveColor <- getVectorField
texFile <- optional textureFilename
skipSpace
return $ DxMaterial faceColor power specularColor emissiveColor texFile
textureFilename = section "TextureFilename" getStringField
meshNormals = section "MeshNormals" $ do
normals <- getIntField >>= getArray getVector
faceNormals <- getIntField >>= getArray meshFace
return (normals, faceNormals)
meshTextureCoords = section "MeshTextureCoords" $
getIntField >>= getArray getVector
meshVertexColors = section "MeshVertexColors" $
getIntField >>= getArray indexedColor
indexedColor = (,) <$> fmap fromIntegral getIntField <*> getVector