summaryrefslogtreecommitdiff
path: root/src/Graphics/WaveFront/Parse
diff options
context:
space:
mode:
authorJoe Crayne <joe@jerkface.net>2019-06-10 23:03:04 -0400
committerJoe Crayne <joe@jerkface.net>2019-06-10 23:03:04 -0400
commit38b7bcf654e5e804a13518b060ebdba59bf232bb (patch)
tree2fa3c4ccf3496750f0ce388a9ea0998fdd93bf69 /src/Graphics/WaveFront/Parse
Initial commit.
Diffstat (limited to 'src/Graphics/WaveFront/Parse')
-rw-r--r--src/Graphics/WaveFront/Parse/Common.hs166
-rw-r--r--src/Graphics/WaveFront/Parse/MTL.hs142
-rw-r--r--src/Graphics/WaveFront/Parse/OBJ.hs173
3 files changed, 481 insertions, 0 deletions
diff --git a/src/Graphics/WaveFront/Parse/Common.hs b/src/Graphics/WaveFront/Parse/Common.hs
new file mode 100644
index 0000000..bfeb2d8
--- /dev/null
+++ b/src/Graphics/WaveFront/Parse/Common.hs
@@ -0,0 +1,166 @@
1-- |
2-- Module : Graphics.WaveFront.Parse.Common
3-- Description :
4-- Copyright : (c) Jonatan H Sundqvist, October 2 2016
5-- License : MIT
6-- Maintainer : Jonatan H Sundqvist
7-- Stability : experimental|stable
8-- Portability : POSIX (not sure)
9
10-- TODO | - Fully polymorphic (even in the string and list types) (?)
11-- -
12
13-- SPEC | -
14-- -
15
16
17
18--------------------------------------------------------------------------------------------------------------------------------------------
19-- GHC Extensions
20--------------------------------------------------------------------------------------------------------------------------------------------
21{-# LANGUAGE OverloadedStrings #-}
22
23
24
25--------------------------------------------------------------------------------------------------------------------------------------------
26-- Section
27--------------------------------------------------------------------------------------------------------------------------------------------
28module Graphics.WaveFront.Parse.Common where
29
30
31
32--------------------------------------------------------------------------------------------------------------------------------------------
33-- We'll need these
34--------------------------------------------------------------------------------------------------------------------------------------------
35import Data.Text (Text, pack)
36import qualified Data.Attoparsec.Text as Atto
37
38import Control.Applicative (pure, liftA2, (<$>), (<*>), (<*), (*>), (<|>))
39import Linear (V2(..), V3(..))
40
41import Graphics.WaveFront.Types
42
43
44
45--------------------------------------------------------------------------------------------------------------------------------------------
46-- Functions (pure)
47--------------------------------------------------------------------------------------------------------------------------------------------
48
49-- Jon's little helpers --------------------------------------------------------------------------------------------------------------------
50
51-- | Consumes all input, including any leading or trailing comments and whitespace
52-- TODO | - Rename (?)
53wholeFile :: Atto.Parser a -> Atto.Parser a
54wholeFile p = cutToTheChase *> p <* cutToTheChase <* Atto.endOfInput
55
56
57-- | Skips any leading comments, line breaks and empty lines
58-- TODO | - Rename (?)
59-- - Skip whitespace
60cutToTheChase :: Atto.Parser ()
61cutToTheChase = Atto.skipMany ((comment *> pure ()) <|> (Atto.satisfy isLinearSpace *> pure ()) <|> Atto.endOfLine)
62
63
64-- | OBJ rows may be separated by one or more lines of comments and whitespace, or empty lines.
65-- TODO | - Make sure this is right
66lineSeparator :: Atto.Parser ()
67lineSeparator = Atto.skipMany1 $ ignore space *> ignore comment *> Atto.endOfLine
68
69
70-- | Parses a comment (from the '#' to end of the line), possibly preceded by whitespace
71-- TODO | - Break out the whitespace part (?)
72comment :: Atto.Parser Text
73comment = Atto.skipSpace *> Atto.char '#' *> Atto.takeTill (\c -> (c == '\r') || (c == '\n')) -- TODO: Is the newline consumed (?)
74
75
76-- | Tries the given parser, falls back to 'Nothing' if it fails
77-- TODO | - Use 'try' to enforce backtracking (?)
78optional :: Atto.Parser a -> Atto.Parser (Maybe a)
79optional p = Atto.option Nothing (Just <$> p)
80
81
82-- | Like Atto.skipMany, except it skips one match at the most
83ignore :: Atto.Parser a -> Atto.Parser ()
84ignore p = optional p *> pure ()
85
86
87-- |
88atleast :: Int -> Atto.Parser a -> Atto.Parser [a]
89atleast n p = liftA2 (++) (Atto.count n p) (Atto.many' p)
90
91
92-- | Skips atleast one white space character (not including newlines and carriage returns)
93space :: Atto.Parser ()
94space = Atto.skipMany1 (Atto.satisfy isLinearSpace)
95
96
97-- | Predicate for linear space (eg. whitespace besides newlines)
98-- TODO | - Unicode awareness (cf. Data.Char.isSpace)
99-- - Come up with a better name (?)
100isLinearSpace :: Char -> Bool
101isLinearSpace c = (c == ' ') || (c == '\t')
102
103
104-- | One or more letters (cf. 'Atto.letter' for details)
105word :: Atto.Parser Text
106word = pack <$> Atto.many1 Atto.letter
107
108
109-- | Used for texture, material, object and group names (and maybe others that I have yet to think of)
110-- TODO | - Use Unicode groups, make more robust (?)
111name :: Atto.Parser Text
112name = pack <$> Atto.many1 (Atto.satisfy $ \c -> (c /= ' ') && (c /= '\t') && (c /= '\r') && (c /= '\n'))
113
114
115-- | Parses the strings "off" (False) and "on" (True)
116toggle :: Atto.Parser Bool
117toggle = (Atto.string "off" *> pure False) <|> (Atto.string "on" *> pure True)
118
119
120-- | Wraps a parser in a '(' and a ')', with no whitespace in between
121parenthesised :: Atto.Parser a -> Atto.Parser a
122parenthesised p = Atto.char '(' *> p <* Atto.char ')'
123
124
125-- TODO | - Allow scientific notation (?)
126
127-- |
128coord :: Fractional f => Atto.Parser f
129coord = space *> (parenthesised Atto.rational <|> Atto.rational)
130
131
132-- | A single colour channel
133-- TODO | - Clamp to [0,1] (cf. partial from monadplus) (?)
134-- - Can channels be parenthesised (?)
135channel :: Fractional f => Atto.Parser f
136channel = space *> (parenthesised Atto.rational <|> Atto.rational)
137
138
139-- | A colour with three or four channels (RGB[A])
140colour :: Fractional f => Atto.Parser (Colour f)
141colour = Colour <$> channel <*> channel <*> channel <*> Atto.option 1 channel
142
143
144-- | A point in 3D space
145point3D :: Fractional f => Atto.Parser (V3 f)
146point3D = V3 <$> coord <*> coord <*> coord
147
148
149-- | A point in 2D space
150point2D :: Fractional f => Atto.Parser (V2 f)
151point2D = V2 <$> coord <*> coord
152
153
154-- |
155clamp :: Ord n => n -> n -> n -> Atto.Parser n
156clamp lower upper n
157 | between lower upper n = pure n
158 | otherwise = fail "Number not in range"
159 where
160 between lw up n = (lower <= n) && (n <= upper)
161 -- between 0 <. n <. 5
162
163-- |
164-- TODO | - Clean up and generalise
165clamped :: Integral i => i -> i -> Atto.Parser i
166clamped lower upper = Atto.decimal >>= clamp lower upper \ No newline at end of file
diff --git a/src/Graphics/WaveFront/Parse/MTL.hs b/src/Graphics/WaveFront/Parse/MTL.hs
new file mode 100644
index 0000000..060952f
--- /dev/null
+++ b/src/Graphics/WaveFront/Parse/MTL.hs
@@ -0,0 +1,142 @@
1-- |
2-- Module : Graphics.WaveFront.Parse.MTL
3-- Description :
4-- Copyright : (c) Jonatan H Sundqvist, October 2 2016
5-- License : MIT
6-- Maintainer : Jonatan H Sundqvist
7-- Stability : experimental|stable
8-- Portability : POSIX (not sure)
9
10-- TODO | -
11-- -
12
13-- SPEC | -
14-- -
15
16
17
18--------------------------------------------------------------------------------------------------------------------------------------------
19-- GHC Extensions
20--------------------------------------------------------------------------------------------------------------------------------------------
21{-# LANGUAGE UnicodeSyntax #-}
22{-# LANGUAGE TupleSections #-}
23{-# LANGUAGE OverloadedStrings #-}
24{-# LANGUAGE NamedFieldPuns #-}
25
26
27
28--------------------------------------------------------------------------------------------------------------------------------------------
29-- API
30--------------------------------------------------------------------------------------------------------------------------------------------
31module Graphics.WaveFront.Parse.MTL (
32 mtl, row, token,
33 ambient, diffuse, specular,
34 mapDiffuse, newMaterial
35) where
36
37
38
39--------------------------------------------------------------------------------------------------------------------------------------------
40-- We'll need these
41--------------------------------------------------------------------------------------------------------------------------------------------
42-- import qualified Data.Map as M
43-- import qualified Data.Set as S
44-- import qualified Data.Vector as V
45import Data.Text (Text)
46
47import qualified Data.Attoparsec.Text as Atto
48
49import Control.Applicative ((<$>), (<*), (*>), (<|>))
50
51import Graphics.WaveFront.Parse.Common
52
53import Graphics.WaveFront.Types hiding (ambient, diffuse, specular)
54
55
56
57--------------------------------------------------------------------------------------------------------------------------------------------
58-- Functions
59--------------------------------------------------------------------------------------------------------------------------------------------
60
61-- MTL parsing -----------------------------------------------------------------------------------------------------------------------------
62
63-- | Produces a list of MTL tokens
64mtl :: (Fractional f) => Atto.Parser (MTL f Text [])
65mtl = Atto.sepBy row lineSeparator
66
67
68-- | Parses a single MTL row.
69row :: (Fractional f) => Atto.Parser (MTLToken f Text)
70row = token <* ignore comment
71
72--------------------------------------------------------------------------------------------------------------------------------------------
73
74-- | Parse an MTL token
75-- TODO: How to deal with common prefix (Ka, Kd, Ks) (backtrack?)
76token :: (Fractional f) => Atto.Parser (MTLToken f Text)
77token = (Atto.string "Ka" *> ambient) <|>
78 (Atto.string "Kd" *> diffuse) <|>
79 (Atto.string "Ks" *> specular) <|>
80 (Atto.string "Ns" *> specExp) <|>
81 (Atto.string "illum" *> illum) <|>
82 (Atto.string "Ni" *> refraction) <|>
83 (Atto.string "d" *> dissolve) <|> -- TODO: Handle inverse as well (cf. 'Tr' attribute)
84 (Atto.string "map_Kd" *> mapDiffuse) <|>
85 (Atto.string "map_Ka" *> mapAmbient) <|>
86 (Atto.string "newmtl" *> newMaterial)
87
88--------------------------------------------------------------------------------------------------------------------------------------------
89
90-- TODO: Expose these parsers for testing purposes (?)
91
92-- TODO | - Change definition of 'colour' and 'Colour' to only allow three channels (alpha is handled by the 'dissolve' attribute)
93-- - Change the definition of 'Colour' or use the one defined in the colour package
94
95-- | Three or four channel values (RGB[A])
96ambient :: (Fractional f) => Atto.Parser (MTLToken f s)
97ambient = Ambient <$> colour
98
99
100-- | Three or four channel values (RGB[A])
101diffuse :: (Fractional f) => Atto.Parser (MTLToken f s)
102diffuse = Diffuse <$> colour
103
104
105-- | Three or four channel values (RGB[A])
106specular :: (Fractional f) => Atto.Parser (MTLToken f s)
107specular = Specular <$> colour
108
109
110-- | A rational number, preceded by whitespace (specular exponent)
111specExp :: (Fractional f) => Atto.Parser (MTLToken f s)
112specExp = space *> (SpecularExponent <$> Atto.rational)
113
114
115-- | A number between 0 and 10 (inclusive) (illumination model)
116illum :: Atto.Parser (MTLToken f s)
117illum = space *> (Illum <$> clamped 0 10)
118
119
120-- | A rational number, preceded by whitespace (refraction index)
121refraction :: (Fractional f) => Atto.Parser (MTLToken f s)
122refraction = space *> (Refraction <$> Atto.rational)
123
124
125-- | A rational number, preceded by whitespace (doss)
126dissolve :: (Fractional f) => Atto.Parser (MTLToken f s)
127dissolve = space *> (Dissolve <$> Atto.rational)
128
129
130-- | A texture name, preceded by whitespace
131mapDiffuse :: Atto.Parser (MTLToken f Text)
132mapDiffuse = space *> (MapDiffuse <$> name)
133
134
135-- | A texture name, preceded by whitespace
136mapAmbient :: Atto.Parser (MTLToken f Text)
137mapAmbient = space *> (MapAmbient <$> name)
138
139
140-- | A material name, preceded by whitespace
141newMaterial :: Atto.Parser (MTLToken f Text)
142newMaterial = space *> (NewMaterial <$> name) \ No newline at end of file
diff --git a/src/Graphics/WaveFront/Parse/OBJ.hs b/src/Graphics/WaveFront/Parse/OBJ.hs
new file mode 100644
index 0000000..37aa5a0
--- /dev/null
+++ b/src/Graphics/WaveFront/Parse/OBJ.hs
@@ -0,0 +1,173 @@
1-- |
2-- Module : Graphics.WaveFront.Parse.OBJ
3-- Description :
4-- Copyright : (c) Jonatan H Sundqvist, October 2 2016
5-- License : MIT
6-- Maintainer : Jonatan H Sundqvist
7-- Stability : experimental|stable
8-- Portability : POSIX (not sure)
9
10-- TODO | - Fully polymorphic (even in the string and list types) (?)
11-- -
12
13-- SPEC | -
14-- -
15
16
17
18--------------------------------------------------------------------------------------------------------------------------------------------
19-- GHC Extensions
20--------------------------------------------------------------------------------------------------------------------------------------------
21{-# LANGUAGE UnicodeSyntax #-}
22{-# LANGUAGE TupleSections #-}
23{-# LANGUAGE OverloadedStrings #-}
24{-# LANGUAGE NamedFieldPuns #-}
25
26
27
28--------------------------------------------------------------------------------------------------------------------------------------------
29-- API
30--------------------------------------------------------------------------------------------------------------------------------------------
31module Graphics.WaveFront.Parse.OBJ (
32 obj, row, face,
33 normal, texcoord, vertex, object, group,
34 lib, use,
35 vertexIndices,
36) where
37
38
39
40--------------------------------------------------------------------------------------------------------------------------------------------
41-- We'll need these
42--------------------------------------------------------------------------------------------------------------------------------------------
43import Data.Text (Text)
44-- import qualified Data.Vector as V
45import qualified Data.Set as S
46
47import qualified Data.Attoparsec.Text as Atto
48
49import Control.Applicative ((<$>), (<*>), (<*), (*>), (<|>))
50
51-- import Linear (V2(..), V3(..))
52
53import Graphics.WaveFront.Parse.Common
54import Graphics.WaveFront.Types hiding (texture)
55
56
57
58--------------------------------------------------------------------------------------------------------------------------------------------
59-- Functions
60--------------------------------------------------------------------------------------------------------------------------------------------
61
62-- OBJ parsing -----------------------------------------------------------------------------------------------------------------------------
63
64-- | This function creates an OBJToken or error for each line in the input data
65obj :: (Fractional f, Integral i) => Atto.Parser (OBJ f Text i [])
66obj = Atto.sepBy row lineSeparator -- <* Atto.endOfInput
67
68
69-- | Parses a token given a single valid OBJ row
70--
71-- TODO | - Correctness (total function, no runtime exceptions)
72-- - Handle invalid rows (how to deal with mangled definitions w.r.t indices?)
73-- - Use ListLike or Monoid (or maybe Indexable, since that's the real requirement) (?)
74row :: (Fractional f, Integral i) => Atto.Parser (OBJToken f Text i [])
75row = token <* ignore comment -- TODO: Let the separator handle comments (?)
76
77
78-- |
79-- Parses an OBJ token
80token :: (Fractional f, Integral i) => Atto.Parser (OBJToken f Text i [])
81token = (Atto.string "f" *> face) <|>
82 (Atto.string "l" *> line) <|>
83 -- TODO: How to deal with common prefix (v, vn, vt) (backtrack?) (doesn't seem to be a problem)
84 (Atto.string "vn" *> normal) <|>
85 (Atto.string "vt" *> texcoord) <|>
86 (Atto.string "v" *> vertex) <|>
87 (Atto.string "o" *> object) <|>
88 (Atto.string "g" *> group) <|>
89 (Atto.string "s" *> smooth) <|>
90 (Atto.string "mtllib" *> lib) <|>
91 (Atto.string "usemtl" *> use)
92
93
94-- TODO: Expose these parsers for testing purposes (?)
95
96--------------------------------------------------------------------------------------------------------------------------------------------
97
98-- | Three or more vertex definitions (cf. 'vertexIndices' for details)
99face :: Integral i => Atto.Parser (OBJToken f Text i [])
100face = OBJFace <$> vertexIndices
101
102
103-- | A single vertex definition with indices for vertex position, normal, and texture coordinates
104--
105-- TODO: | - Should the slashes be optional?
106-- - Allowed trailing slashes (I'll have to check the spec again) (?)
107--
108-- f Int[/((Int[/Int])|(/Int))]
109vertexIndices :: Integral i => Atto.Parser [VertexIndices i]
110vertexIndices = atleast 3 (space *> (ivertex <*> index <*> index)) <|> -- vi/ti/ni
111 atleast 3 (space *> (ivertex <*> nothing <*> skipIndex)) <|> -- vi//ni
112 atleast 3 (space *> (ivertex <*> index <*> nothing)) <|> -- vi/ti
113 atleast 3 (space *> (ivertex <*> nothing <*> nothing)) -- vi
114 where
115 ivertex :: Integral i => Atto.Parser (Maybe i -> Maybe i -> VertexIndices i)
116 ivertex = VertexIndices <$> Atto.decimal
117
118 index :: Integral i => Atto.Parser (Maybe i)
119 index = Just <$> (Atto.char '/' *> Atto.decimal)
120
121 skipIndex :: Integral i => Atto.Parser (Maybe i)
122 skipIndex = Atto.char '/' *> index
123
124 nothing :: Atto.Parser (Maybe i)
125 nothing = pure Nothing
126
127-- Geometry primitives ---------------------------------------------------------------------------------------------------------------------
128
129-- | Two integers, separated by whitespace
130line :: Integral i => Atto.Parser (OBJToken f Text i m)
131line = Line <$> (space *> Atto.decimal) <*> (space *> Atto.decimal)
132
133--------------------------------------------------------------------------------------------------------------------------------------------
134
135-- | Three cordinates, separated by whitespace
136normal :: (Fractional f) => Atto.Parser (OBJToken f Text i m)
137normal = OBJNormal <$> point3D
138
139
140-- | Two coordinates, separated by whitespace
141texcoord :: (Fractional f) => Atto.Parser (OBJToken f Text i m)
142texcoord = OBJTexCoord <$> point2D
143
144
145-- | Three coordinates, separated by whitespace
146vertex :: (Fractional f) => Atto.Parser (OBJToken f s i m)
147vertex = OBJVertex <$> point3D
148
149
150-- | Object names, separated by whitespace
151object :: Atto.Parser (OBJToken f Text i m)
152object = Object . S.fromList <$> atleast 1 (space *> name)
153
154
155-- | Group names, separated by whitespace
156group :: Atto.Parser (OBJToken f Text i m)
157group = Group . S.fromList <$> atleast 1 (space *> name)
158
159
160-- | Smoothing group
161-- TODO: Refactor
162smooth :: Atto.Parser (OBJToken f Text i m)
163smooth = SmoothGroup <$> (((Atto.string "off" <|> Atto.string "0") *> pure Nothing) <|> (space *> (Just <$> name)))
164
165
166-- | An MTL library name
167lib :: Atto.Parser (OBJToken f Text i m)
168lib = LibMTL <$> (space *> name)
169
170
171-- | An MTL material name
172use :: Atto.Parser (OBJToken f Text i m)
173use = UseMTL <$> (space *> name) \ No newline at end of file