packages feed

3dmodels-0.3.0: Graphics/Model/Obj.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
-- | OBJ is a widely used simple 3D model format made for TAV.
-- The Advanced Visualizer (TAV), a 3D graphics software package,
-- was the flagship product of Wavefront Technologies from the 1980s
-- until the 1990s.
-- <https://en.wikipedia.org/wiki/The_Advanced_Visualizer>

module Graphics.Model.Obj (
  loadOBJ,
  parseOBJ,
  --reverseHandOBJ,
  loadMtl,
  parseMtl,

  ObjMesh(..),
  ObjFace(..),
  ObjMaterial(..)
  ) where

import Control.Applicative
import Data.Attoparsec.ByteString.Char8 as A
import qualified Data.ByteString as B
import Data.Int
import Linear

data ObjMesh = ObjMesh
	{ _objGroup :: String
	, _objMaterial :: Maybe (FilePath, String)
	, _objVertices :: [V3 Float]
	, _objUVs :: [V2 Float]
	, _objNormals :: [V3 Float]
	, _objFaces :: [[ObjFace]]
	} deriving Show

data ObjFace = ObjFace
	{ _objVertexIx :: Int32
	, _objTextureIx :: Int32
	, _objNormalIx :: Int32
	} deriving Show

data ObjMaterial = ObjMaterial
	{ _objName :: String
	, _objAmbient :: V3 Float
	, _objDiffuse :: V3 Float
	, _objSpecular :: V3 Float
	, _objShineness :: Float
	} deriving Show

loadOBJ :: FilePath -> IO [ObjMesh]
loadOBJ path = parseOBJ <$> B.readFile path

parseOBJ :: B.ByteString -> [ObjMesh]
parseOBJ bs =
	case feed (parse parseMeshes bs) "" of
		Done _ result -> result
		result -> error $ "parseOBJ: Invalid format:\n" ++ show result

comments = do
	many (skipSpace >> char '#' >> A.takeWhile (/= '\n'))
	skipSpace

parseMeshes :: Parser [ObjMesh]
parseMeshes = do
	path <- comments *> option "" parseMtllib <* comments
	many1 (parseMesh path <* comments)

parseMesh :: String -> Parser ObjMesh
parseMesh path = do
	group <- option "default" parseG <* comments
	mtl1 <- optional parseUsemtl <* comments
	vertices <- many1 (parseV <* comments)
	uvs <- many (parseVt <* comments)
	normals <- many (parseVn <* comments)
	mtl2 <- optional parseUsemtl <* comments
	faces <- many (parseF <* comments)
	let mtl = fmap (path,) (maybe mtl2 Just mtl1)
	return $ ObjMesh group mtl vertices uvs normals faces

getString, parseMtllib, parseG, parseUsemtl :: Parser String
getString = many1 (satisfy (/= '\n'))
parseMtllib = string "mtllib " >> getString
parseG = char 'g' >> sp >> getString
parseUsemtl = string "usemtl " >> getString

parseV = char 'v' >> float3
parseVt = char 'v' >> char 't' >> float2
parseVn = char 'v' >> char 'n' >> float3
parseF = char 'f' >> many indexes

float = realToFrac <$> signed double
sp = char ' '
float3 = V3
	<$> (sp *> float)
	<*> (sp *> float)
	<*> (sp *> float)
float2 = V2
	<$> (sp *> float)
	<*> (sp *> float)

index = option 0 decimal
indexes = ObjFace
	<$> (sp       *> index)
	<*> (char '/' *> index)
	<*> (char '/' *> index)

loadMtl :: FilePath -> IO ObjMaterial
loadMtl path = parseMtl <$> B.readFile path

parseMtl :: B.ByteString -> ObjMaterial
parseMtl bs =
	case feed (parse parseMtl' bs) "" of
		Done _ result -> result
		result -> error $ "parseMtl: Invalid format:\n" ++ show result

parseMtl' :: Parser ObjMaterial
parseMtl' = do
	comments
	mtl <- string "newmtl " >> getString <* comments
	amb <- string "Ka" >> float3 <* comments
	diff <- string "Kd" >> float3 <* comments
	spec <- string "Ks" >> float3 <* comments
	shine <- string "Ns " >> float
	-- XXX map_Kd
	return $ ObjMaterial mtl amb diff spec shine