diff options
Diffstat (limited to 'src/Codec/Wavefront')
-rw-r--r-- | src/Codec/Wavefront/Element.hs | 29 | ||||
-rw-r--r-- | src/Codec/Wavefront/Face.hs | 33 | ||||
-rw-r--r-- | src/Codec/Wavefront/IO.hs | 22 | ||||
-rw-r--r-- | src/Codec/Wavefront/Lexer.hs | 106 | ||||
-rw-r--r-- | src/Codec/Wavefront/Line.hs | 26 | ||||
-rw-r--r-- | src/Codec/Wavefront/Location.hs | 26 | ||||
-rw-r--r-- | src/Codec/Wavefront/Normal.hs | 25 | ||||
-rw-r--r-- | src/Codec/Wavefront/Object.hs | 55 | ||||
-rw-r--r-- | src/Codec/Wavefront/Point.hs | 18 | ||||
-rw-r--r-- | src/Codec/Wavefront/TexCoord.hs | 27 | ||||
-rw-r--r-- | src/Codec/Wavefront/Token.hs | 239 |
11 files changed, 606 insertions, 0 deletions
diff --git a/src/Codec/Wavefront/Element.hs b/src/Codec/Wavefront/Element.hs new file mode 100644 index 0000000..846b603 --- /dev/null +++ b/src/Codec/Wavefront/Element.hs | |||
@@ -0,0 +1,29 @@ | |||
1 | ----------------------------------------------------------------------------- | ||
2 | -- | | ||
3 | -- Copyright : (C) 2015 Dimitri Sabadie | ||
4 | -- License : BSD3 | ||
5 | -- | ||
6 | -- Maintainer : Dimitri Sabadie <dimitri.sabadie@gmail.com> | ||
7 | -- Stability : experimental | ||
8 | -- Portability : portable | ||
9 | -- | ||
10 | ----------------------------------------------------------------------------- | ||
11 | |||
12 | module Codec.Wavefront.Element ( | ||
13 | -- * Element | ||
14 | Element(..) | ||
15 | ) where | ||
16 | |||
17 | import Data.Text ( Text ) | ||
18 | import Numeric.Natural ( Natural ) | ||
19 | |||
20 | -- |An element holds a value along with the user-defined object’s name (if any), the associated | ||
21 | -- groups, the used material and the smoothing group the element belongs to (if any). Those values | ||
22 | -- can be used to sort the data per object or per group and to lookup materials. | ||
23 | data Element a = Element { | ||
24 | elObject :: Maybe Text | ||
25 | , elGroups :: [Text] | ||
26 | , elMtl :: Maybe Text | ||
27 | , elSmoothingGroup :: Natural | ||
28 | , elValue :: a | ||
29 | } deriving (Eq,Show) | ||
diff --git a/src/Codec/Wavefront/Face.hs b/src/Codec/Wavefront/Face.hs new file mode 100644 index 0000000..f055dd0 --- /dev/null +++ b/src/Codec/Wavefront/Face.hs | |||
@@ -0,0 +1,33 @@ | |||
1 | {-# LANGUAGE PatternSynonyms #-} | ||
2 | |||
3 | ----------------------------------------------------------------------------- | ||
4 | -- | | ||
5 | -- Copyright : (C) 2015 Dimitri Sabadie | ||
6 | -- License : BSD3 | ||
7 | -- | ||
8 | -- Maintainer : Dimitri Sabadie <dimitri.sabadie@gmail.com> | ||
9 | -- Stability : experimental | ||
10 | -- Portability : portable | ||
11 | -- | ||
12 | ----------------------------------------------------------------------------- | ||
13 | |||
14 | module Codec.Wavefront.Face where | ||
15 | |||
16 | -- |A face index is a triplet of indices. @'FaceIndex' vi vti vni@ is a face that indexes the | ||
17 | -- locations with @vi@, the texture coordinates with @vti@ and the normals with @vni@. An index set | ||
18 | -- to 'Nothing' means /no information/. That is, if @vni == 'Nothing'@, then that 'FaceIndex' | ||
19 | -- doesn’t have a normal associated with. | ||
20 | data FaceIndex = FaceIndex { | ||
21 | faceLocIndex :: {-# UNPACK #-} !Int | ||
22 | , faceTexCoordIndex :: !(Maybe Int) | ||
23 | , faceNorIndex :: !(Maybe Int) | ||
24 | } deriving (Eq,Show) | ||
25 | |||
26 | -- |A face gathers several 'FaceIndex' to build up faces. It has a least three vertices | ||
27 | data Face = Face FaceIndex FaceIndex FaceIndex [FaceIndex] deriving (Eq,Show) | ||
28 | |||
29 | pattern Triangle :: FaceIndex -> FaceIndex -> FaceIndex -> Face | ||
30 | pattern Triangle a b c = Face a b c [] | ||
31 | |||
32 | pattern Quad :: FaceIndex -> FaceIndex -> FaceIndex -> FaceIndex -> Face | ||
33 | pattern Quad a b c d = Face a b c [d] | ||
diff --git a/src/Codec/Wavefront/IO.hs b/src/Codec/Wavefront/IO.hs new file mode 100644 index 0000000..2bcf2b3 --- /dev/null +++ b/src/Codec/Wavefront/IO.hs | |||
@@ -0,0 +1,22 @@ | |||
1 | ----------------------------------------------------------------------------- | ||
2 | -- | | ||
3 | -- Copyright : (C) 2015 Dimitri Sabadie | ||
4 | -- License : BSD3 | ||
5 | -- | ||
6 | -- Maintainer : Dimitri Sabadie <dimitri.sabadie@gmail.com> | ||
7 | -- Stability : experimental | ||
8 | -- Portability : portable | ||
9 | -- | ||
10 | ----------------------------------------------------------------------------- | ||
11 | |||
12 | module Codec.Wavefront.IO where | ||
13 | |||
14 | import Codec.Wavefront.Lexer ( lexer ) | ||
15 | import Codec.Wavefront.Object ( WavefrontOBJ, ctxtToWavefrontOBJ ) | ||
16 | import Codec.Wavefront.Token ( tokenize ) | ||
17 | import Control.Monad.IO.Class ( MonadIO(..) ) | ||
18 | import qualified Data.Text.IO as T ( readFile ) | ||
19 | |||
20 | -- |Extract a 'WavefrontOBJ' from a Wavefront OBJ formatted file. | ||
21 | fromFile :: (MonadIO m) => FilePath -> m (Either String WavefrontOBJ) | ||
22 | fromFile fd = liftIO $ fmap (fmap (ctxtToWavefrontOBJ . lexer) . tokenize) (T.readFile fd) | ||
diff --git a/src/Codec/Wavefront/Lexer.hs b/src/Codec/Wavefront/Lexer.hs new file mode 100644 index 0000000..79b167a --- /dev/null +++ b/src/Codec/Wavefront/Lexer.hs | |||
@@ -0,0 +1,106 @@ | |||
1 | ----------------------------------------------------------------------------- | ||
2 | -- | | ||
3 | -- Copyright : (C) 2015 Dimitri Sabadie | ||
4 | -- License : BSD3 | ||
5 | -- | ||
6 | -- Maintainer : Dimitri Sabadie <dimitri.sabadie@gmail.com> | ||
7 | -- Stability : experimental | ||
8 | -- Portability : portable | ||
9 | -- | ||
10 | ----------------------------------------------------------------------------- | ||
11 | |||
12 | module Codec.Wavefront.Lexer where | ||
13 | |||
14 | import Codec.Wavefront.Element | ||
15 | import Codec.Wavefront.Face | ||
16 | import Codec.Wavefront.Line | ||
17 | import Codec.Wavefront.Location | ||
18 | import Codec.Wavefront.Normal | ||
19 | import Codec.Wavefront.Point | ||
20 | import Codec.Wavefront.Token | ||
21 | import Codec.Wavefront.TexCoord | ||
22 | import Data.DList ( DList, append, empty, fromList, snoc ) | ||
23 | import Data.Text ( Text ) | ||
24 | import Control.Monad.State ( State, execState, gets, modify ) | ||
25 | import Data.Foldable ( traverse_ ) | ||
26 | import Numeric.Natural ( Natural ) | ||
27 | |||
28 | -- |The lexer context. The result of lexing a stream of tokens is this exact type. | ||
29 | data Ctxt = Ctxt { | ||
30 | -- |Locations. | ||
31 | ctxtLocations :: DList Location | ||
32 | -- |Texture coordinates. | ||
33 | , ctxtTexCoords :: DList TexCoord | ||
34 | -- |Normals. | ||
35 | , ctxtNormals :: DList Normal | ||
36 | -- |Points. | ||
37 | , ctxtPoints :: DList (Element Point) | ||
38 | -- |Lines. | ||
39 | , ctxtLines :: DList (Element Line) | ||
40 | -- |Faces. | ||
41 | , ctxtFaces :: DList (Element Face) | ||
42 | -- |Current object. | ||
43 | , ctxtCurrentObject :: Maybe Text | ||
44 | -- |Current groups. | ||
45 | , ctxtCurrentGroups :: [Text] | ||
46 | -- |Current material. | ||
47 | , ctxtCurrentMtl :: Maybe Text | ||
48 | -- |Material libraries. | ||
49 | , ctxtMtlLibs :: DList Text | ||
50 | -- |Current smoothing group. | ||
51 | , ctxtCurrentSmoothingGroup :: Natural | ||
52 | } deriving (Eq,Show) | ||
53 | |||
54 | -- |The empty 'Ctxt'. Such a context exists at the beginning of the token stream and gets altered | ||
55 | -- as we consume tokens. | ||
56 | emptyCtxt :: Ctxt | ||
57 | emptyCtxt = Ctxt { | ||
58 | ctxtLocations = empty | ||
59 | , ctxtTexCoords = empty | ||
60 | , ctxtNormals = empty | ||
61 | , ctxtPoints = empty | ||
62 | , ctxtLines = empty | ||
63 | , ctxtFaces = empty | ||
64 | , ctxtCurrentObject = Nothing | ||
65 | , ctxtCurrentGroups = ["default"] | ||
66 | , ctxtCurrentMtl = Nothing | ||
67 | , ctxtMtlLibs = empty | ||
68 | , ctxtCurrentSmoothingGroup = 0 | ||
69 | } | ||
70 | |||
71 | -- |The lexer function, consuming tokens and yielding a 'Ctxt'. | ||
72 | lexer :: TokenStream -> Ctxt | ||
73 | lexer stream = execState (traverse_ consume stream) emptyCtxt | ||
74 | where | ||
75 | consume tk = case tk of | ||
76 | TknV v -> do | ||
77 | locations <- gets ctxtLocations | ||
78 | modify $ \ctxt -> ctxt { ctxtLocations = locations `snoc` v } | ||
79 | TknVN vn -> do | ||
80 | normals <- gets ctxtNormals | ||
81 | modify $ \ctxt -> ctxt { ctxtNormals = normals `snoc` vn } | ||
82 | TknVT vt -> do | ||
83 | texCoords <- gets ctxtTexCoords | ||
84 | modify $ \ctxt -> ctxt { ctxtTexCoords = texCoords `snoc` vt } | ||
85 | TknP p -> do | ||
86 | (pts,element) <- prepareElement ctxtPoints | ||
87 | modify $ \ctxt -> ctxt { ctxtPoints = pts `append` fmap element (fromList p) } | ||
88 | TknL l -> do | ||
89 | (lns,element) <- prepareElement ctxtLines | ||
90 | modify $ \ctxt -> ctxt { ctxtLines = lns `append` fmap element (fromList l) } | ||
91 | TknF f -> do | ||
92 | (fcs,element) <- prepareElement ctxtFaces | ||
93 | modify $ \ctxt -> ctxt { ctxtFaces = fcs `snoc` element f } | ||
94 | TknG g -> modify $ \ctxt -> ctxt { ctxtCurrentGroups = g } | ||
95 | TknO o -> modify $ \ctxt -> ctxt { ctxtCurrentObject = Just o } | ||
96 | TknMtlLib l -> do | ||
97 | libs <- gets ctxtMtlLibs | ||
98 | modify $ \ctxt -> ctxt { ctxtMtlLibs = libs `append` fromList l } | ||
99 | TknUseMtl mtl -> modify $ \ctxt -> ctxt { ctxtCurrentMtl = Just mtl } | ||
100 | TknS sg -> modify $ \ctxt -> ctxt { ctxtCurrentSmoothingGroup = sg } | ||
101 | |||
102 | -- Prepare to create a new 'Element' by retrieving its associated list. | ||
103 | prepareElement :: (Ctxt -> DList (Element a)) -> State Ctxt (DList (Element a),a -> Element a) | ||
104 | prepareElement field = do | ||
105 | (aList,obj,grp,mtl,sg) <- gets $ (\ctxt -> (field ctxt,ctxtCurrentObject ctxt,ctxtCurrentGroups ctxt,ctxtCurrentMtl ctxt,ctxtCurrentSmoothingGroup ctxt)) | ||
106 | pure (aList,Element obj grp mtl sg) | ||
diff --git a/src/Codec/Wavefront/Line.hs b/src/Codec/Wavefront/Line.hs new file mode 100644 index 0000000..d44e9e2 --- /dev/null +++ b/src/Codec/Wavefront/Line.hs | |||
@@ -0,0 +1,26 @@ | |||
1 | ----------------------------------------------------------------------------- | ||
2 | -- | | ||
3 | -- Copyright : (C) 2015 Dimitri Sabadie | ||
4 | -- License : BSD3 | ||
5 | -- | ||
6 | -- Maintainer : Dimitri Sabadie <dimitri.sabadie@gmail.com> | ||
7 | -- Stability : experimental | ||
8 | -- Portability : portable | ||
9 | -- | ||
10 | ----------------------------------------------------------------------------- | ||
11 | |||
12 | module Codec.Wavefront.Line where | ||
13 | |||
14 | -- |A line index is a pair of indices. @'LineIndex' vi vti@. @vi@ references the locations and @vti@ | ||
15 | -- indexes the texture coordinates. If @vti == 'Nothing'@, then that 'LineIndex' doesn’t have | ||
16 | -- texture coordinates associated with. | ||
17 | data LineIndex = LineIndex { | ||
18 | lineLocIndex :: {-# UNPACK #-} !Int | ||
19 | , lineTexCoordIndex :: !(Maybe Int) | ||
20 | } deriving (Eq,Show) | ||
21 | |||
22 | -- A line gathers two line indices accessible by pattern matching or 'lineIndexA' and 'lineIndexB'. | ||
23 | data Line = Line { | ||
24 | lineIndexA :: LineIndex | ||
25 | , lineIndexB :: LineIndex | ||
26 | } deriving (Eq,Show) | ||
diff --git a/src/Codec/Wavefront/Location.hs b/src/Codec/Wavefront/Location.hs new file mode 100644 index 0000000..abdbc6a --- /dev/null +++ b/src/Codec/Wavefront/Location.hs | |||
@@ -0,0 +1,26 @@ | |||
1 | ----------------------------------------------------------------------------- | ||
2 | -- | | ||
3 | -- Copyright : (C) 2015 Dimitri Sabadie | ||
4 | -- License : BSD3 | ||
5 | -- | ||
6 | -- Maintainer : Dimitri Sabadie <dimitri.sabadie@gmail.com> | ||
7 | -- Stability : experimental | ||
8 | -- Portability : portable | ||
9 | -- | ||
10 | ----------------------------------------------------------------------------- | ||
11 | |||
12 | module Codec.Wavefront.Location where | ||
13 | |||
14 | -- |A location is a 4-floating vector. You can access to its components by pattern matching on them: | ||
15 | -- | ||
16 | -- @ | ||
17 | -- let Location x y z w = Location 1 2 3 4 | ||
18 | -- @ | ||
19 | -- | ||
20 | -- That type is strict and unboxed. | ||
21 | data Location = Location { | ||
22 | locX :: {-# UNPACK #-} !Float | ||
23 | , locY :: {-# UNPACK #-} !Float | ||
24 | , locZ :: {-# UNPACK #-} !Float | ||
25 | , locW :: {-# UNPACK #-} !Float | ||
26 | } deriving (Eq,Show) | ||
diff --git a/src/Codec/Wavefront/Normal.hs b/src/Codec/Wavefront/Normal.hs new file mode 100644 index 0000000..adf6221 --- /dev/null +++ b/src/Codec/Wavefront/Normal.hs | |||
@@ -0,0 +1,25 @@ | |||
1 | ----------------------------------------------------------------------------- | ||
2 | -- | | ||
3 | -- Copyright : (C) 2015 Dimitri Sabadie | ||
4 | -- License : BSD3 | ||
5 | -- | ||
6 | -- Maintainer : Dimitri Sabadie <dimitri.sabadie@gmail.com> | ||
7 | -- Stability : experimental | ||
8 | -- Portability : portable | ||
9 | -- | ||
10 | ----------------------------------------------------------------------------- | ||
11 | |||
12 | module Codec.Wavefront.Normal where | ||
13 | |||
14 | -- |A normal is a 3-floating vector. You can access to its components by pattern matching on them: | ||
15 | -- | ||
16 | -- @ | ||
17 | -- let Normal nx ny nz = Normal 0.1 0.2 0.3 | ||
18 | -- @ | ||
19 | -- | ||
20 | -- That type is strict and unboxed. | ||
21 | data Normal = Normal { | ||
22 | norX :: {-# UNPACK #-} !Float | ||
23 | , norY :: {-# UNPACK #-} !Float | ||
24 | , norZ :: {-# UNPACK #-} !Float | ||
25 | } deriving (Eq,Show) | ||
diff --git a/src/Codec/Wavefront/Object.hs b/src/Codec/Wavefront/Object.hs new file mode 100644 index 0000000..15557fe --- /dev/null +++ b/src/Codec/Wavefront/Object.hs | |||
@@ -0,0 +1,55 @@ | |||
1 | ----------------------------------------------------------------------------- | ||
2 | -- | | ||
3 | -- Copyright : (C) 2015 Dimitri Sabadie | ||
4 | -- License : BSD3 | ||
5 | -- | ||
6 | -- Maintainer : Dimitri Sabadie <dimitri.sabadie@gmail.com> | ||
7 | -- Stability : experimental | ||
8 | -- Portability : portable | ||
9 | -- | ||
10 | ----------------------------------------------------------------------------- | ||
11 | |||
12 | module Codec.Wavefront.Object where | ||
13 | |||
14 | import Codec.Wavefront.Element | ||
15 | import Codec.Wavefront.Face | ||
16 | import Codec.Wavefront.Lexer ( Ctxt(..) ) | ||
17 | import Codec.Wavefront.Line | ||
18 | import Codec.Wavefront.Location | ||
19 | import Codec.Wavefront.Normal | ||
20 | import Codec.Wavefront.Point | ||
21 | import Codec.Wavefront.TexCoord | ||
22 | import Data.DList ( DList, toList ) | ||
23 | import Data.Text ( Text ) | ||
24 | import Data.Vector ( Vector, fromList ) | ||
25 | |||
26 | data WavefrontOBJ = WavefrontOBJ { | ||
27 | -- |Locations. | ||
28 | objLocations :: Vector Location | ||
29 | -- |Texture coordinates. | ||
30 | , objTexCoords :: Vector TexCoord | ||
31 | -- |Normals. | ||
32 | , objNormals :: Vector Normal | ||
33 | -- |Points. | ||
34 | , objPoints :: Vector (Element Point) | ||
35 | -- |Lines. | ||
36 | , objLines :: Vector (Element Line) | ||
37 | -- |Faces. | ||
38 | , objFaces :: Vector (Element Face) | ||
39 | -- |Material libraries. | ||
40 | , objMtlLibs :: Vector Text | ||
41 | } deriving (Eq,Show) | ||
42 | |||
43 | ctxtToWavefrontOBJ :: Ctxt -> WavefrontOBJ | ||
44 | ctxtToWavefrontOBJ ctxt = WavefrontOBJ { | ||
45 | objLocations = fromDList (ctxtLocations ctxt) | ||
46 | , objTexCoords = fromDList (ctxtTexCoords ctxt) | ||
47 | , objNormals = fromDList (ctxtNormals ctxt) | ||
48 | , objPoints = fromDList (ctxtPoints ctxt) | ||
49 | , objLines = fromDList (ctxtLines ctxt) | ||
50 | , objFaces = fromDList (ctxtFaces ctxt) | ||
51 | , objMtlLibs = fromDList (ctxtMtlLibs ctxt) | ||
52 | } | ||
53 | |||
54 | fromDList :: DList a -> Vector a | ||
55 | fromDList = fromList . toList | ||
diff --git a/src/Codec/Wavefront/Point.hs b/src/Codec/Wavefront/Point.hs new file mode 100644 index 0000000..698aeca --- /dev/null +++ b/src/Codec/Wavefront/Point.hs | |||
@@ -0,0 +1,18 @@ | |||
1 | ----------------------------------------------------------------------------- | ||
2 | -- | | ||
3 | -- Copyright : (C) 2015 Dimitri Sabadie | ||
4 | -- License : BSD3 | ||
5 | -- | ||
6 | -- Maintainer : Dimitri Sabadie <dimitri.sabadie@gmail.com> | ||
7 | -- Stability : experimental | ||
8 | -- Portability : portable | ||
9 | -- | ||
10 | ----------------------------------------------------------------------------- | ||
11 | |||
12 | module Codec.Wavefront.Point where | ||
13 | |||
14 | -- |A point is a single index that references the locations. It’s a canonical type that truly | ||
15 | -- represents a polygonal point. | ||
16 | data Point = Point { | ||
17 | pointLocIndex :: {-# UNPACK #-} !Int | ||
18 | } deriving (Eq,Show) | ||
diff --git a/src/Codec/Wavefront/TexCoord.hs b/src/Codec/Wavefront/TexCoord.hs new file mode 100644 index 0000000..02e5c5a --- /dev/null +++ b/src/Codec/Wavefront/TexCoord.hs | |||
@@ -0,0 +1,27 @@ | |||
1 | ----------------------------------------------------------------------------- | ||
2 | -- | | ||
3 | -- Copyright : (C) 2015 Dimitri Sabadie | ||
4 | -- License : BSD3 | ||
5 | -- | ||
6 | -- Maintainer : Dimitri Sabadie <dimitri.sabadie@gmail.com> | ||
7 | -- Stability : experimental | ||
8 | -- Portability : portable | ||
9 | -- | ||
10 | ----------------------------------------------------------------------------- | ||
11 | |||
12 | module Codec.Wavefront.TexCoord where | ||
13 | |||
14 | -- |A texture coordinate is a 3D-floating vector. You can access to its components by pattern | ||
15 | -- matching on them: | ||
16 | -- | ||
17 | -- @ | ||
18 | -- let TexCoord r s t = TexCoord 0.1 0.2 0.3 | ||
19 | -- @ | ||
20 | -- | ||
21 | -- That type is strcit and unboxed. | ||
22 | data TexCoord = TexCoord { | ||
23 | texcoordR :: {-# UNPACK #-} !Float | ||
24 | , texcoordS :: {-# UNPACK #-} !Float | ||
25 | , texcoordT :: {-# UNPACK #-} !Float | ||
26 | } deriving (Eq,Show) | ||
27 | |||
diff --git a/src/Codec/Wavefront/Token.hs b/src/Codec/Wavefront/Token.hs new file mode 100644 index 0000000..76540c9 --- /dev/null +++ b/src/Codec/Wavefront/Token.hs | |||
@@ -0,0 +1,239 @@ | |||
1 | ----------------------------------------------------------------------------- | ||
2 | -- | | ||
3 | -- Copyright : (C) 2015 Dimitri Sabadie | ||
4 | -- License : BSD3 | ||
5 | -- | ||
6 | -- Maintainer : Dimitri Sabadie <dimitri.sabadie@gmail.com> | ||
7 | -- Stability : experimental | ||
8 | -- Portability : portable | ||
9 | -- | ||
10 | ----------------------------------------------------------------------------- | ||
11 | |||
12 | module Codec.Wavefront.Token where | ||
13 | |||
14 | import Codec.Wavefront.Face | ||
15 | import Codec.Wavefront.Line | ||
16 | import Codec.Wavefront.Location | ||
17 | import Codec.Wavefront.Normal | ||
18 | import Codec.Wavefront.Point | ||
19 | import Codec.Wavefront.TexCoord | ||
20 | import Control.Applicative ( Alternative(..) ) | ||
21 | import Data.Attoparsec.Text as AP | ||
22 | import Data.Char ( isSpace ) | ||
23 | import Data.Maybe ( catMaybes ) | ||
24 | import Data.Text ( Text, unpack, strip ) | ||
25 | import qualified Data.Text as T ( empty ) | ||
26 | import Numeric.Natural ( Natural ) | ||
27 | import Prelude hiding ( lines ) | ||
28 | |||
29 | ---------------------------------------------------------------------------------------------------- | ||
30 | -- Token ------------------------------------------------------------------------------------------- | ||
31 | |||
32 | data Token | ||
33 | = TknV Location | ||
34 | | TknVN Normal | ||
35 | | TknVT TexCoord | ||
36 | | TknP [Point] | ||
37 | | TknL [Line] | ||
38 | | TknF Face | ||
39 | | TknG [Text] | ||
40 | | TknO Text | ||
41 | | TknMtlLib [Text] | ||
42 | | TknUseMtl Text | ||
43 | | TknS Natural | ||
44 | deriving (Eq,Show) | ||
45 | |||
46 | -- |A stream of 'Token'. | ||
47 | type TokenStream = [Token] | ||
48 | |||
49 | tokenize :: Text -> Either String TokenStream | ||
50 | tokenize = fmap cleanupTokens . analyseResult False . parse (untilEnd tokenizer) | ||
51 | where | ||
52 | tokenizer = choice | ||
53 | [ | ||
54 | fmap (Just . TknV) location | ||
55 | , fmap (Just . TknVN) normal | ||
56 | , fmap (Just . TknVT) texCoord | ||
57 | , fmap (Just . TknP) points | ||
58 | , fmap (Just . TknL) lines | ||
59 | , fmap (Just . TknF) face | ||
60 | , fmap (Just . TknG) groups | ||
61 | , fmap (Just . TknO) object | ||
62 | , fmap (Just . TknMtlLib) mtllib | ||
63 | , fmap (Just . TknUseMtl) usemtl | ||
64 | , fmap (Just . TknS) smoothingGroup | ||
65 | , Nothing <$ comment | ||
66 | ] | ||
67 | |||
68 | analyseResult :: Bool -> Result [Maybe Token] -> Either String [Maybe Token] | ||
69 | analyseResult partial r = case r of | ||
70 | Done _ tkns -> Right tkns | ||
71 | Fail i _ e -> Left $ "`" ++ Prelude.take 10 (unpack i) ++ "` [...]: " ++ e | ||
72 | Partial p -> if partial then Left "not completely tokenized" else analyseResult True (p T.empty) | ||
73 | |||
74 | cleanupTokens :: [Maybe Token] -> TokenStream | ||
75 | cleanupTokens = catMaybes | ||
76 | |||
77 | ---------------------------------------------------------------------------------------------------- | ||
78 | -- Location ---------------------------------------------------------------------------------------- | ||
79 | |||
80 | location :: Parser Location | ||
81 | location = skipSpace *> string "v " *> skipHSpace *> parseXYZW <* eol | ||
82 | where | ||
83 | parseXYZW = do | ||
84 | xyz <- float `sepBy1` skipHSpace | ||
85 | case xyz of | ||
86 | [x,y,z] -> pure (Location x y z 1) | ||
87 | [x,y,z,w] -> pure (Location x y z w) | ||
88 | [x,y,z,w,r,g] -> pure (Location x y z w) -- TODO: RG-colorspace | ||
89 | [x,y,z,w,r,g,b] -> pure (Location x y z w) -- TODO: RGB-colorspace | ||
90 | _ -> fail "wrong number of x, y and z arguments for location" | ||
91 | |||
92 | ---------------------------------------------------------------------------------------------------- | ||
93 | -- Normal ------------------------------------------------------------------------------------------ | ||
94 | |||
95 | normal :: Parser Normal | ||
96 | normal = skipSpace *> string "vn " *> skipHSpace *> parseIJK <* eol | ||
97 | where | ||
98 | parseIJK = do | ||
99 | ijk <- float `sepBy1` skipHSpace | ||
100 | case ijk of | ||
101 | [i,j,k] -> pure (Normal i j k) | ||
102 | _ -> fail "wrong number of i, j and k arguments for normal" | ||
103 | |||
104 | ---------------------------------------------------------------------------------------------------- | ||
105 | -- Texture coordinates ----------------------------------------------------------------------------- | ||
106 | |||
107 | texCoord :: Parser TexCoord | ||
108 | texCoord = skipSpace *> string "vt " *> skipHSpace *> parseUVW <* eol | ||
109 | where | ||
110 | parseUVW = do | ||
111 | uvw <- float `sepBy1` skipHSpace | ||
112 | case uvw of | ||
113 | [u,v] -> pure (TexCoord u v 0) | ||
114 | [u,v,w] -> pure (TexCoord u v w) | ||
115 | _ -> fail "wrong number of u, v and w arguments for texture coordinates" | ||
116 | |||
117 | ---------------------------------------------------------------------------------------------------- | ||
118 | -- Points ------------------------------------------------------------------------------------------ | ||
119 | |||
120 | points :: Parser [Point] | ||
121 | points = skipSpace *> string "p " *> skipHSpace *> fmap Point decimal `sepBy1` skipHSpace <* eol | ||
122 | |||
123 | ---------------------------------------------------------------------------------------------------- | ||
124 | -- Lines ------------------------------------------------------------------------------------------- | ||
125 | lines :: Parser [Line] | ||
126 | lines = do | ||
127 | skipSpace | ||
128 | _ <- string "l " | ||
129 | skipHSpace | ||
130 | pointIndices <- parsePointIndices | ||
131 | pts <- case pointIndices of | ||
132 | _:_:_ -> pure $ zipWith Line pointIndices (tail pointIndices) | ||
133 | _ -> fail "line doesn't have at least two points" | ||
134 | eol | ||
135 | pure pts | ||
136 | where | ||
137 | parsePointIndices = fmap (\(i,j) -> LineIndex i j) parseLinePair `sepBy1` skipHSpace | ||
138 | parseLinePair = do | ||
139 | v <- decimal | ||
140 | slashThenElse (fmap (\vt -> (v, Just vt)) decimal) (pure (v,Nothing)) | ||
141 | |||
142 | ---------------------------------------------------------------------------------------------------- | ||
143 | -- Faces ------------------------------------------------------------------------------------------- | ||
144 | face :: Parser Face | ||
145 | face = do | ||
146 | skipSpace | ||
147 | _ <- string "f " | ||
148 | skipHSpace | ||
149 | faceIndices <- parseFaceIndices | ||
150 | f <- case faceIndices of | ||
151 | a:b:c:s -> pure (Face a b c s) | ||
152 | _ -> fail "face doesn't have at least three points" | ||
153 | eol | ||
154 | pure f | ||
155 | where | ||
156 | parseFaceIndices = fmap (\(i,k,j) -> FaceIndex i k j) parseFaceTriple `sepBy1` skipHSpace | ||
157 | parseFaceTriple = do | ||
158 | v <- decimal | ||
159 | slashThenElse (parseVT v) (pure (v,Nothing,Nothing)) | ||
160 | parseVT v = slashThenElse (parseVN v Nothing) $ do | ||
161 | vt <- decimal | ||
162 | slashThenElse (parseVN v $ Just vt) (pure (v,Just vt,Nothing)) | ||
163 | parseVN v vt = do | ||
164 | vn <- decimal | ||
165 | pure (v,vt,Just vn) | ||
166 | |||
167 | ---------------------------------------------------------------------------------------------------- | ||
168 | -- Groups ------------------------------------------------------------------------------------------ | ||
169 | |||
170 | groups :: Parser [Text] | ||
171 | groups = skipSpace *> string "g " *> skipHSpace *> name `sepBy` skipHSpace <* eol | ||
172 | |||
173 | ---------------------------------------------------------------------------------------------------- | ||
174 | -- Objects ----------------------------------------------------------------------------------------- | ||
175 | |||
176 | object :: Parser Text | ||
177 | object = skipSpace *> string "o " *> skipHSpace *> spacedName <* eol | ||
178 | |||
179 | ---------------------------------------------------------------------------------------------------- | ||
180 | -- Material libraries ------------------------------------------------------------------------------ | ||
181 | |||
182 | mtllib :: Parser [Text] | ||
183 | mtllib = skipSpace *> string "mtllib " *> skipHSpace *> name `sepBy1` skipHSpace <* eol | ||
184 | |||
185 | ---------------------------------------------------------------------------------------------------- | ||
186 | -- Using materials --------------------------------------------------------------------------------- | ||
187 | |||
188 | usemtl :: Parser Text | ||
189 | usemtl = skipSpace *> string "usemtl " *> skipHSpace *> spacedName <* eol | ||
190 | |||
191 | ---------------------------------------------------------------------------------------------------- | ||
192 | -- Smoothing groups -------------------------------------------------------------------------------- | ||
193 | smoothingGroup :: Parser Natural | ||
194 | smoothingGroup = skipSpace *> string "s " *> skipHSpace *> offOrIndex <* skipHSpace <* eol | ||
195 | where | ||
196 | offOrIndex = string "off" *> pure 0 <|> decimal | ||
197 | |||
198 | ---------------------------------------------------------------------------------------------------- | ||
199 | -- Comments ---------------------------------------------------------------------------------------- | ||
200 | comment :: Parser () | ||
201 | comment = skipSpace *> string "#" *> (() <$ manyTill anyChar eol) | ||
202 | |||
203 | ---------------------------------------------------------------------------------------------------- | ||
204 | -- Special parsers --------------------------------------------------------------------------------- | ||
205 | |||
206 | -- Read a slash ('/') and run the @thenP@ parser on success. Otherwise, call the @elseP@ parser. | ||
207 | slashThenElse :: Parser a -> Parser a -> Parser a | ||
208 | slashThenElse thenP elseP = do | ||
209 | c <- peekChar | ||
210 | case c of | ||
211 | Just '/' -> AP.take 1 *> thenP | ||
212 | _ -> elseP | ||
213 | |||
214 | -- End of line. | ||
215 | eol :: Parser () | ||
216 | eol = skipMany (satisfy isHorizontalSpace) *> (endOfLine <|> endOfInput) | ||
217 | |||
218 | -- Parse a name (any character but space). | ||
219 | name :: Parser Text | ||
220 | name = takeWhile1 $ not . isSpace | ||
221 | |||
222 | spacedName :: Parser Text | ||
223 | spacedName = strip <$> AP.takeWhile (flip notElem ("\n\r" :: String)) | ||
224 | |||
225 | skipHSpace :: Parser () | ||
226 | skipHSpace = () <$ AP.takeWhile isHorizontalSpace | ||
227 | |||
228 | float :: Parser Float | ||
229 | float = fmap realToFrac double | ||
230 | |||
231 | -- Loop a parser and collect its values until we hit the end of the stream. Fails on the first | ||
232 | -- failure. | ||
233 | untilEnd :: Parser a -> Parser [a] | ||
234 | untilEnd p = go | ||
235 | where | ||
236 | go = do | ||
237 | a <- p | ||
238 | end <- atEnd | ||
239 | if end then pure [a] else fmap (a:) go | ||