summaryrefslogtreecommitdiff
path: root/src/Codec/Wavefront
diff options
context:
space:
mode:
Diffstat (limited to 'src/Codec/Wavefront')
-rw-r--r--src/Codec/Wavefront/Element.hs29
-rw-r--r--src/Codec/Wavefront/Face.hs33
-rw-r--r--src/Codec/Wavefront/IO.hs22
-rw-r--r--src/Codec/Wavefront/Lexer.hs106
-rw-r--r--src/Codec/Wavefront/Line.hs26
-rw-r--r--src/Codec/Wavefront/Location.hs26
-rw-r--r--src/Codec/Wavefront/Normal.hs25
-rw-r--r--src/Codec/Wavefront/Object.hs55
-rw-r--r--src/Codec/Wavefront/Point.hs18
-rw-r--r--src/Codec/Wavefront/TexCoord.hs27
-rw-r--r--src/Codec/Wavefront/Token.hs239
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
12module Codec.Wavefront.Element (
13 -- * Element
14 Element(..)
15 ) where
16
17import Data.Text ( Text )
18import 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.
23data 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
14module 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.
20data 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
27data Face = Face FaceIndex FaceIndex FaceIndex [FaceIndex] deriving (Eq,Show)
28
29pattern Triangle :: FaceIndex -> FaceIndex -> FaceIndex -> Face
30pattern Triangle a b c = Face a b c []
31
32pattern Quad :: FaceIndex -> FaceIndex -> FaceIndex -> FaceIndex -> Face
33pattern 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
12module Codec.Wavefront.IO where
13
14import Codec.Wavefront.Lexer ( lexer )
15import Codec.Wavefront.Object ( WavefrontOBJ, ctxtToWavefrontOBJ )
16import Codec.Wavefront.Token ( tokenize )
17import Control.Monad.IO.Class ( MonadIO(..) )
18import qualified Data.Text.IO as T ( readFile )
19
20-- |Extract a 'WavefrontOBJ' from a Wavefront OBJ formatted file.
21fromFile :: (MonadIO m) => FilePath -> m (Either String WavefrontOBJ)
22fromFile 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
12module Codec.Wavefront.Lexer where
13
14import Codec.Wavefront.Element
15import Codec.Wavefront.Face
16import Codec.Wavefront.Line
17import Codec.Wavefront.Location
18import Codec.Wavefront.Normal
19import Codec.Wavefront.Point
20import Codec.Wavefront.Token
21import Codec.Wavefront.TexCoord
22import Data.DList ( DList, append, empty, fromList, snoc )
23import Data.Text ( Text )
24import Control.Monad.State ( State, execState, gets, modify )
25import Data.Foldable ( traverse_ )
26import Numeric.Natural ( Natural )
27
28-- |The lexer context. The result of lexing a stream of tokens is this exact type.
29data 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.
56emptyCtxt :: Ctxt
57emptyCtxt = 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'.
72lexer :: TokenStream -> Ctxt
73lexer 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.
103prepareElement :: (Ctxt -> DList (Element a)) -> State Ctxt (DList (Element a),a -> Element a)
104prepareElement 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
12module 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.
17data 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'.
23data 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
12module 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.
21data 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
12module 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.
21data 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
12module Codec.Wavefront.Object where
13
14import Codec.Wavefront.Element
15import Codec.Wavefront.Face
16import Codec.Wavefront.Lexer ( Ctxt(..) )
17import Codec.Wavefront.Line
18import Codec.Wavefront.Location
19import Codec.Wavefront.Normal
20import Codec.Wavefront.Point
21import Codec.Wavefront.TexCoord
22import Data.DList ( DList, toList )
23import Data.Text ( Text )
24import Data.Vector ( Vector, fromList )
25
26data 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
43ctxtToWavefrontOBJ :: Ctxt -> WavefrontOBJ
44ctxtToWavefrontOBJ 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
54fromDList :: DList a -> Vector a
55fromDList = 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
12module 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.
16data 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
12module 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.
22data 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
12module Codec.Wavefront.Token where
13
14import Codec.Wavefront.Face
15import Codec.Wavefront.Line
16import Codec.Wavefront.Location
17import Codec.Wavefront.Normal
18import Codec.Wavefront.Point
19import Codec.Wavefront.TexCoord
20import Control.Applicative ( Alternative(..) )
21import Data.Attoparsec.Text as AP
22import Data.Char ( isSpace )
23import Data.Maybe ( catMaybes )
24import Data.Text ( Text, unpack, strip )
25import qualified Data.Text as T ( empty )
26import Numeric.Natural ( Natural )
27import Prelude hiding ( lines )
28
29----------------------------------------------------------------------------------------------------
30-- Token -------------------------------------------------------------------------------------------
31
32data 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'.
47type TokenStream = [Token]
48
49tokenize :: Text -> Either String TokenStream
50tokenize = 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
68analyseResult :: Bool -> Result [Maybe Token] -> Either String [Maybe Token]
69analyseResult 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
74cleanupTokens :: [Maybe Token] -> TokenStream
75cleanupTokens = catMaybes
76
77----------------------------------------------------------------------------------------------------
78-- Location ----------------------------------------------------------------------------------------
79
80location :: Parser Location
81location = 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
95normal :: Parser Normal
96normal = 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
107texCoord :: Parser TexCoord
108texCoord = 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
120points :: Parser [Point]
121points = skipSpace *> string "p " *> skipHSpace *> fmap Point decimal `sepBy1` skipHSpace <* eol
122
123----------------------------------------------------------------------------------------------------
124-- Lines -------------------------------------------------------------------------------------------
125lines :: Parser [Line]
126lines = 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 -------------------------------------------------------------------------------------------
144face :: Parser Face
145face = 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
170groups :: Parser [Text]
171groups = skipSpace *> string "g " *> skipHSpace *> name `sepBy` skipHSpace <* eol
172
173----------------------------------------------------------------------------------------------------
174-- Objects -----------------------------------------------------------------------------------------
175
176object :: Parser Text
177object = skipSpace *> string "o " *> skipHSpace *> spacedName <* eol
178
179----------------------------------------------------------------------------------------------------
180-- Material libraries ------------------------------------------------------------------------------
181
182mtllib :: Parser [Text]
183mtllib = skipSpace *> string "mtllib " *> skipHSpace *> name `sepBy1` skipHSpace <* eol
184
185----------------------------------------------------------------------------------------------------
186-- Using materials ---------------------------------------------------------------------------------
187
188usemtl :: Parser Text
189usemtl = skipSpace *> string "usemtl " *> skipHSpace *> spacedName <* eol
190
191----------------------------------------------------------------------------------------------------
192-- Smoothing groups --------------------------------------------------------------------------------
193smoothingGroup :: Parser Natural
194smoothingGroup = skipSpace *> string "s " *> skipHSpace *> offOrIndex <* skipHSpace <* eol
195 where
196 offOrIndex = string "off" *> pure 0 <|> decimal
197
198----------------------------------------------------------------------------------------------------
199-- Comments ----------------------------------------------------------------------------------------
200comment :: Parser ()
201comment = 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.
207slashThenElse :: Parser a -> Parser a -> Parser a
208slashThenElse thenP elseP = do
209 c <- peekChar
210 case c of
211 Just '/' -> AP.take 1 *> thenP
212 _ -> elseP
213
214-- End of line.
215eol :: Parser ()
216eol = skipMany (satisfy isHorizontalSpace) *> (endOfLine <|> endOfInput)
217
218-- Parse a name (any character but space).
219name :: Parser Text
220name = takeWhile1 $ not . isSpace
221
222spacedName :: Parser Text
223spacedName = strip <$> AP.takeWhile (flip notElem ("\n\r" :: String))
224
225skipHSpace :: Parser ()
226skipHSpace = () <$ AP.takeWhile isHorizontalSpace
227
228float :: Parser Float
229float = 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.
233untilEnd :: Parser a -> Parser [a]
234untilEnd p = go
235 where
236 go = do
237 a <- p
238 end <- atEnd
239 if end then pure [a] else fmap (a:) go