summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Crayne <joe@jerkface.net>2019-06-12 16:48:33 -0400
committerJoe Crayne <joe@jerkface.net>2019-06-12 16:48:33 -0400
commit550c43c8491e2b6a2873caf8e9c032b69e56e03f (patch)
tree3cd4cc5b57e205d6d798194fd6991c5f5fee47c7
parent74fdcbd78256f9f89f32a47dfd1e060fde5ea8ba (diff)
Started bytestring-lexing based parser.
-rw-r--r--src/Wavefront.hs450
-rw-r--r--wavefront-obj.cabal5
2 files changed, 455 insertions, 0 deletions
diff --git a/src/Wavefront.hs b/src/Wavefront.hs
new file mode 100644
index 0000000..66d6cdb
--- /dev/null
+++ b/src/Wavefront.hs
@@ -0,0 +1,450 @@
1{-# LANGUAGE NondecreasingIndentation #-}
2{-# LANGUAGE OverloadedStrings #-}
3module Wavefront where
4
5import qualified Data.ByteString.Lazy.Char8 as L
6import qualified Data.ByteString.Char8 as S
7import Data.ByteString.Internal as BS
8import Data.Char
9import Data.IntMap (IntMap)
10import qualified Data.IntMap as IntMap
11import Data.ByteString.Lex.Fractional as F
12import Data.ByteString.Lex.Integral as I
13
14data ObjBuilder m = ObjBuilder
15 { vertex :: [Double] -> m ()
16 , vertexT :: [Double] -> m ()
17 , vertexN :: [Double] -> m ()
18 , vertexP :: [Double] -> m ()
19 , face :: [RefTriple] -> m ()
20 , cstype :: Bool -> CSType -> m ()
21 , curv2 :: [Int] -> m ()
22 , curv :: Double -> Double -> [Int] -> m ()
23 , parm :: ParamSpec -> [Double] -> m ()
24 , specialPoints :: [Int] -> m ()
25 , endFreeForm :: m ()
26 , ctech :: CurveSamplingSpec -> m ()
27 , stech :: SurfaceSamplingSpec -> m ()
28 , deg :: [Int] -> m ()
29 , surf :: Double -> Double -> Double -> Double -> [RefTriple] -> m ()
30 , trim :: [CurveSpec] -> m ()
31 , hole :: [CurveSpec] -> m ()
32 , specialCurves :: [CurveSpec] -> m ()
33 , equivalentCurves :: [EmbeddedCurve] -> m ()
34 , groups :: [S.ByteString] -> m ()
35 , smoothingGroup :: Int -> m ()
36 , mergingGroup :: Int -> Double -> m ()
37 , usemtl :: S.ByteString -> m ()
38 , deprecated_cdc :: [Int] -> m ()
39 , deprecated_bzp :: [Int] -> m ()
40 , mtllib :: [S.ByteString] -> m ()
41 , objectName :: S.ByteString -> m ()
42 , badToken :: L.ByteString -> m ()
43 }
44
45data CurveSamplingSpec
46 -- ctech cparm
47 = UniformSubdivision
48 { divisionsPerCurveDegree :: Double -- ^ This really ought to be an integer but
49 -- but examples show floats. The only way
50 -- it makes sense as a float is if we are to
51 -- convert to an integer *after* multiplying
52 -- by the curve degree.
53 }
54 -- ctech cspace
55 | MaxLengthPolygonal { maxPolygonEdgeLength :: Double }
56 -- ctech curv
57 | CurvatureBasedPolygon { maxDistanceToCurve :: Double, maximumDegreesPerSample :: Double }
58 deriving (Eq,Show)
59
60data SurfaceSamplingSpec
61 -- stech cparma ures vres
62 = UniformIsoparametric { uDivisionsPerDegree :: Double, vDivisionsPerDegree :: Double }
63 -- stech cparmb uvres
64 | UniformAfterTrimming { uvDivisionsPerDegree :: Double }
65 -- stech cspace maxlength
66 | MaxLengthPolytopal { maxPolytopEdgeLength :: Double }
67 -- stech curv maxdist maxangle
68 | CurvatureBasedPolytope { maxDistanceToSurface :: Double, maxDegreesPerCorner :: Double }
69 deriving (Eq,Show)
70
71
72data ObjState = ObjState
73 {
74 }
75
76newtype ObjConfig = ObjConfig
77 { cfgSubst :: IntMap L.ByteString
78 }
79
80-- consChunk :: S.ByteString -> L.ByteString -> L.ByteString
81-- consChunk c bs = L.fromChunks (c : L.toChunks bs)
82
83reappend :: ByteString -> ByteString -> Maybe ByteString
84reappend a b =
85 let (ap,ao,al) = BS.toForeignPtr a
86 (bp,bo,bl) = BS.toForeignPtr b
87 in if ap == bp && ao+al == bo
88 then Just $ BS.PS ap ao (al+bl)
89 else Nothing
90
91reconsChunk :: S.ByteString -> L.ByteString -> L.ByteString
92reconsChunk b bs = case L.toChunks bs of
93 (c:cs) -> case reappend b c of
94 Just x -> L.fromChunks (x:cs)
95 Nothing -> L.fromChunks (b:c:cs)
96 _ -> L.fromChunks [b]
97
98findToken :: ObjConfig -> L.ByteString -> L.ByteString
99findToken (ObjConfig args) bs = case L.dropWhile (\c -> isSpace c || c=='\\') bs of
100 cs -> case L.uncons cs of
101 Just ('#',comment) -> findToken (ObjConfig args) $ L.drop 1 $ L.dropWhile (/='\n') comment
102 Just ('$',ref) -> case L.splitAt 5 ref of
103 (refp,ds) -> case I.readDecimal (L.toStrict refp) of
104 Just (i,es) -> case IntMap.lookup i args of
105 Just val -> val <> reconsChunk es ds
106 _ -> reconsChunk es ds
107 _ -> cs
108 Just _ -> cs
109 Nothing -> L.empty
110
111{-
112
113 1 x bevel
114 2 x bmat
115 3 bzp
116 4 x call
117 5 cdc
118 6 x c_interp
119 7 con
120 8 x csh -- for all except these,
121 9 cstype -- Two chars suffice to distinguish
122 10 ctech
123 11 curv2 -- for all except these,
124 12 curv -- Two chars suffice to distinguish
125 13 deg
126 14 x d_interp
127 15 end
128 16 f
129 17 g
130 18 hole
131 19 x lod
132 20 x maplib
133 21 mg
134 22 mtllib
135 23 o
136 24 parm
137 25 s
138 26 scrv
139 27 x shadow_obj
140 28 sp
141 29 stech -- for all except these,
142 30 x step -- Two chars suffice to distinguish
143 31 surf
144 32 x trace_obj -- for all except these,
145 33 trim -- Two chars suffice to distinguish
146 34 x usemap -- for all except these,
147 35 usemtl -- Two chars suffice to distinguish
148 36 v
149 37 vn
150 38 vp
151 39 vt
152
153-}
154
155nextToken :: (L.ByteString -> L.ByteString) -> L.ByteString -> L.ByteString
156nextToken tok bs = tok $ L.dropWhile (not . isSpace) bs
157
158parseFloats tok bs cont = case L.splitAt 10 (tok bs) of
159 (ds,bs') -> case F.readSigned F.readExponential (L.toStrict ds) of
160 Just (x,b) -> parseFloats tok (reconsChunk b bs') (cont . (x :))
161 Nothing -> cont [] (ds <> bs')
162
163parseFloatsN 0 _ bs cont = cont [] bs
164parseFloatsN n tok bs cont = case L.splitAt 10 (tok bs) of
165 (ds,bs') -> case F.readSigned F.readExponential (L.toStrict ds) of
166 Just (x,b) -> parseFloatsN (n-1) tok (reconsChunk b bs') (cont . (x :))
167 Nothing -> cont [] (ds <> bs')
168
169parseInts tok bs cont = case L.splitAt 5 (tok bs) of
170 (ds,bs') -> case I.readSigned I.readDecimal (L.toStrict ds) of
171 Just (x,b) -> parseInts tok (reconsChunk b bs') (cont . (x :))
172 Nothing -> cont [] (ds <> bs')
173
174parseIntsN 0 tok bs cont = cont [] bs
175parseIntsN n tok bs cont = case L.splitAt 5 (tok bs) of
176 (ds,bs') -> case I.readSigned I.readDecimal (L.toStrict ds) of
177 Just (x,b) -> parseIntsN (n-1) tok (reconsChunk b bs') (cont . (x :))
178 Nothing -> cont [] (ds <> bs')
179
180parseTriples :: (L.ByteString -> L.ByteString) -> L.ByteString -> ([RefTriple] -> L.ByteString -> b) -> b
181parseTriples tok bs cont = case L.splitAt 17 (tok bs) of
182 (ds,bs') -> case I.readSigned I.readDecimal (L.toStrict ds) of
183
184 Just (v,b) -> case S.splitAt 1 b of
185
186 ("/",ds') -> case I.readSigned I.readDecimal ds' of
187
188 Just (vt,c) -> case S.splitAt 1 c of
189 ("/",ds'') -> case I.readSigned I.readDecimal ds'' of
190 Just (vn,d) -> parseTriples tok (reconsChunk d bs') $ cont . (RefTriple v (Just vt) (Just vn) :)
191 Nothing -> parseTriples tok (reconsChunk ds'' bs') $ cont . (RefTriple v (Just vt) Nothing :)
192
193 _ -> parseTriples tok (reconsChunk c bs') $ cont . (RefTriple v (Just vt) Nothing :)
194
195 Nothing -> case S.splitAt 1 ds' of
196 ("/",ds'') -> case I.readSigned I.readDecimal ds'' of
197 Just (vn,d) -> parseTriples tok (reconsChunk d bs') $ cont . (RefTriple v Nothing (Just vn) :)
198 Nothing -> parseTriples tok (reconsChunk ds'' bs') $ cont . (RefTriple v Nothing Nothing :)
199
200 _ -> parseTriples tok (reconsChunk ds' bs') $ cont . (RefTriple v Nothing Nothing :)
201
202
203 _ -> parseTriples tok (reconsChunk b bs') $ cont . (RefTriple v Nothing Nothing :)
204
205 Nothing -> cont [] (ds <> bs')
206
207parseCurveSpecs :: (L.ByteString -> L.ByteString) -> L.ByteString -> ([CurveSpec] -> L.ByteString -> b) -> b
208parseCurveSpecs tok bs cont = parseFloatsN 2 tok bs $ \fs bs' -> case fs of
209 (u0:u1:_) -> do
210 parseIntsN 1 tok bs' $ \is bs'' -> case is of
211 (i:_) -> parseCurveSpecs tok bs'' $ cont . (CurveSpec u0 u1 i :)
212 _ -> cont [] bs''
213
214 _ -> cont [] bs'
215
216
217parseCurveSpecsN :: Int -> (L.ByteString -> L.ByteString) -> L.ByteString -> ([CurveSpec] -> L.ByteString -> b) -> b
218parseCurveSpecsN 0 tok bs cont = cont [] bs
219parseCurveSpecsN n tok bs cont = parseFloatsN 2 tok bs $ \fs bs' -> case fs of
220 (u0:u1:_) -> do
221 parseIntsN 1 tok bs' $ \is bs'' -> case is of
222 (i:_) -> parseCurveSpecsN (n-1) tok bs'' $ cont . (CurveSpec u0 u1 i :)
223 _ -> cont [] bs''
224
225 _ -> cont [] bs'
226
227parseEmbeddedCurves :: (L.ByteString -> L.ByteString) -> L.ByteString -> ([EmbeddedCurve] -> L.ByteString -> b) -> b
228parseEmbeddedCurves tok bs cont = parseIntsN 1 tok bs $ \is bs' -> case is of
229 (sref:_) -> do
230 parseCurveSpecsN 1 tok bs' $ \cs bs'' -> case cs of
231 (c:_) -> parseEmbeddedCurves tok bs'' $ cont . (EmbeddedCurve sref c :)
232 _ -> cont [] bs''
233
234 _ -> cont [] bs'
235
236data CSType = Bmatrix | Bezier | Bspline | Cardinal | Taylor
237 deriving (Eq,Ord,Show,Enum)
238
239data ParamSpec = ParamU | ParamV
240 deriving (Eq,Ord,Show,Enum)
241
242data RefTriple = RefTriple
243 { refV :: Int
244 , refT :: Maybe Int
245 , refN :: Maybe Int
246 }
247-- data RefTriple = RefTriple Int (Maybe Int) (Maybe Int)
248 deriving (Eq,Ord,Show)
249
250data CurveSpec = CurveSpec
251 { curveStart :: Double
252 , curveEnd :: Double
253 , curveRef :: Int
254 }
255 deriving (Eq,Ord,Show)
256
257data EmbeddedCurve = EmbeddedCurve
258 { curveSurfaceRef :: Int
259 , embeddedCurve :: CurveSpec
260 }
261 deriving (Eq,Ord,Show)
262
263lengthLessThan :: Int -> L.ByteString -> Bool
264lengthLessThan n bs =
265 foldr (\c ret ac -> let m = S.length c in if ac <= m then False else ret $! ac - m)
266 (const True)
267 (L.toChunks bs)
268 n
269
270parseOBJ :: Monad m => ObjBuilder m -> ObjConfig -> L.ByteString -> m ()
271parseOBJ builder args bs0
272 | lengthLessThan 2 bs = return ()
273 | isSpace (L.index bs 1) = case L.head bs of
274 'f' -> parseT face 2
275 'g' -> case L.break (=='\n') $ L.drop 1 bs of -- Newline required to terminate group name list.
276 (gn,bs') -> do
277 groups builder (map L.toStrict $ L.words gn)
278 parseOBJ builder args bs'
279 's' -> case next 1 bs of
280 tok -> parseOffOrNumber tok $ \sg bs' -> do
281 smoothingGroup builder sg
282 parseOBJ builder args bs'
283 'v' -> parseV vertex 2
284 'o' -> -- o object-name
285 case L.break (=='\n') $ findToken args $ L.drop 1 bs of
286 (objn,bs') -> do
287 objectName builder (L.toStrict objn)
288 parseOBJ builder args bs'
289 _ -> badToken builder bs
290 | otherwise = case L.take 2 bs of
291 "vt" -> parseV vertexT 3
292 "vn" -> parseV vertexN 3
293 "vp" -> parseV vertexP 3
294 "bz" -> parseI deprecated_bzp 4 -- bzp
295 "cd" -> parseI deprecated_cdc 4 -- cdc
296 "co" -> -- con
297 parseEmbeddedCurves (findToken args) (next 2 bs) $ \ss bs' -> do
298 equivalentCurves builder ss
299 parseOBJ builder args bs'
300 "cs" -> -- cstype
301 let parseRat = parseChar 'r'
302 parseTyp tok cont | lengthLessThan 3 tok = badToken builder tok
303 | otherwise = case L.index tok 2 of
304 'a' -> cont Bmatrix $ next 3 tok
305 'z' -> cont Bezier $ next 3 tok
306 'p' -> cont Bspline $ next 3 tok
307 'r' -> cont Cardinal $ next 3 tok
308 'y' -> cont Taylor $ next 3 tok
309 _ -> badToken builder tok
310 in parseRat (next 2 bs) $ \isRat bs' -> do
311 parseTyp bs' $ \typ bs'' -> do
312 cstype builder isRat typ
313 parseOBJ builder args bs''
314 "ct" -> -- ctech
315 let tok = next 2 bs
316 in if lengthLessThan 2 tok
317 then badToken builder tok
318 else case L.index tok 1 of
319 'p' -> -- cparm
320 parseFloats (findToken args) (next 2 tok) $ \is bs' -> do
321 let x:_ = is ++ [0]
322 ctech builder (UniformSubdivision x)
323 parseOBJ builder args bs'
324 's' -> -- cspace
325 parseFloats (findToken args) (next 2 tok) $ \fs bs' -> do
326 let x:_ = fs ++ [1.0]
327 ctech builder (MaxLengthPolygonal x)
328 parseOBJ builder args bs'
329 'u' -> -- curv
330 parseFloats (findToken args) (next 2 tok) $ \fs bs' -> do
331 let δ:θ:_ = fs ++ repeat 1.0
332 ctech builder (CurvatureBasedPolygon δ θ)
333 parseOBJ builder args bs'
334 _ -> badToken builder tok
335 "cu" -> if lengthLessThan 5 bs
336 then badToken builder bs
337 else if L.index bs 4 == '2'
338 then parseI curv2 5 -- curv2
339 else do -- curv
340 parseFloatsN 2 (findToken args) (L.drop 4 bs) $ \vs bs' ->
341 parseInts (findToken args) bs' $ \is bs'' -> do
342 let u0:v0:_ = vs ++ repeat 0.0
343 curv builder u0 v0 is
344 parseOBJ builder args bs''
345 "de" -> parseI deg 3
346 "en" -> do endFreeForm builder
347 parseOBJ builder args (next 2 bs)
348 "ho" -> -- hole
349 parseCurveSpecs (findToken args) (next 2 bs) $ \ss bs' -> do
350 hole builder ss
351 parseOBJ builder args bs'
352 "mg" -> case next 2 bs of
353 tok -> parseOffOrNumber tok $ \mg bs' -> do
354 parseFloatsN 1 (findToken args) bs' $ \fs bs'' -> do
355 mergingGroup builder mg (head $ fs ++ [0])
356 parseOBJ builder args bs''
357 "pa" -> parseChar 'u' (next 2 bs) $ \isU bs' -> do
358 parseFloats (findToken args) (if isU then bs' else L.drop 1 bs') $ \vs bs'' -> do
359 parm builder (if isU then ParamU else ParamV) vs
360 parseOBJ builder args bs''
361 "sc" -> -- scrv
362 parseCurveSpecs (findToken args) (next 2 bs) $ \ss bs' -> do
363 specialCurves builder ss
364 parseOBJ builder args bs'
365 "sp" -> parseI specialPoints 3
366 "st" -> -- stech
367 let tok = next 2 bs
368 in if lengthLessThan 2 tok
369 then badToken builder tok
370 else case L.index tok 1 of
371 'p' -> -- cparma/cparmb
372 if lengthLessThan 6 tok
373 then badToken builder tok
374 else if L.index tok 5 == 'b'
375 then -- cparmb
376 parseFloats (findToken args) (next 5 tok) $ \is bs' -> do
377 let x:_ = is ++ [0]
378 stech builder (UniformAfterTrimming x)
379 parseOBJ builder args bs'
380 else -- cparma
381 parseFloats (findToken args) (next 5 tok) $ \is bs' -> do
382 let x:y:_ = is ++ [0]
383 stech builder (UniformIsoparametric x y)
384 parseOBJ builder args bs'
385 's' -> -- cspace
386 parseFloats (findToken args) (next 2 tok) $ \fs bs' -> do
387 let x:_ = fs ++ [1.0]
388 stech builder (MaxLengthPolytopal x)
389 parseOBJ builder args bs'
390 'u' -> -- curv
391 parseFloats (findToken args) (next 2 tok) $ \fs bs' -> do
392 let δ:θ:_ = fs ++ repeat 1.0
393 stech builder (CurvatureBasedPolytope δ θ)
394 parseOBJ builder args bs'
395 _ -> badToken builder tok
396 "su" -> -- surf
397 parseFloatsN 4 (findToken args) (next 2 bs) $ \fs bs' -> do
398 parseTriples (findToken args) bs' $ \ts bs'' -> do
399 let u0:u1:v0:v1:_ = fs ++ repeat 0
400 surf builder u0 u1 v0 v1 ts
401 parseOBJ builder args bs''
402 "tr" -> -- trim
403 parseCurveSpecs (findToken args) (next 2 bs) $ \ss bs' -> do
404 trim builder ss
405 parseOBJ builder args bs'
406 "us" -> -- usemtl
407 case L.break isSpace $ next 2 bs of
408 (mtl,bs') -> do
409 usemtl builder (L.toStrict mtl)
410 parseOBJ builder args bs'
411 "mt" -> -- mtllib
412 case L.break (=='\n') $ next 2 bs of
413 (fnn,bs') -> do
414 let slurp fnn = case L.break (=='.') fnn of
415 (fn,ext) | L.null (L.drop 1 ext) -> if L.null fn then []
416 else [fn]
417 | ".mtl" <- L.take 4 ext ->
418 if L.all isSpace (L.take 1 $ L.drop 4 ext)
419 then (fn <> ".mtl") : slurp (findToken args $ L.drop 4 ext)
420 else let f:fs = slurp (L.drop 3 ext) in (fn <> L.take 3 ext <> f) : fs
421 | otherwise -> let (f:fs) = slurp (L.drop 1 ext)
422 in (fn <> L.take 1 ext <> f) : fs
423 mtllib builder (map L.toStrict $ slurp fnn)
424 parseOBJ builder args bs'
425 -- TODO: call,csh,step,bmat,c_interp,d_interp,lod,shadow_obj,trace_obj,bevel
426 _ -> badToken builder bs
427 where
428 bs = findToken args bs0
429 next n xs = nextToken (findToken args) $ L.drop n xs
430 parseChar c tok cont = case L.uncons tok of
431 Just (x,cs) | x==c -> cont True $ next 0 cs
432 _ -> cont False tok
433 parseV build n = do
434 parseFloats (findToken args) (L.drop n bs) $ \vs bs' -> do
435 build builder vs
436 parseOBJ builder args bs'
437 parseI build n = do
438 parseInts (findToken args) (L.drop n bs) $ \vs bs' -> do
439 build builder vs
440 parseOBJ builder args bs'
441 parseT build n = do
442 parseTriples (findToken args) (L.drop n bs) $ \vs bs' -> do
443 build builder vs
444 parseOBJ builder args bs'
445 parseOffOrNumber tok cont = parseIntsN 1 (findToken args) tok $ \is bs' -> do
446 let (sg,bs'') = case is of
447 (i:_) -> (i,bs')
448 _ | lengthLessThan 2 tok -> (0,tok)
449 _ -> (if L.index tok 1 == 'f' then 0 else 1, next 1 tok)
450 cont sg bs''
diff --git a/wavefront-obj.cabal b/wavefront-obj.cabal
index 717ab5f..612d6f5 100644
--- a/wavefront-obj.cabal
+++ b/wavefront-obj.cabal
@@ -48,6 +48,7 @@ library
48 , Codec.Wavefront.Face 48 , Codec.Wavefront.Face
49 , Codec.Wavefront.FreeForm 49 , Codec.Wavefront.FreeForm
50 , Codec.Wavefront.Token 50 , Codec.Wavefront.Token
51 , Wavefront
51 -- other-modules: 52 -- other-modules:
52 other-extensions: ForeignFunctionInterface 53 other-extensions: ForeignFunctionInterface
53 , UnicodeSyntax 54 , UnicodeSyntax
@@ -85,5 +86,9 @@ library
85 , lens >=4.16 && <4.17 86 , lens >=4.16 && <4.17
86 , transformers >=0.5 && <0.6 87 , transformers >=0.5 && <0.6
87 , mtl >=2.2 && <2.3 88 , mtl >=2.2 && <2.3
89 , bytestring
90 , bytestring-lexing
91 , pretty-show
92
88 hs-source-dirs: src 93 hs-source-dirs: src
89 default-language: Haskell2010 94 default-language: Haskell2010