diff options
Diffstat (limited to 'src/Wavefront/Lex.hs')
-rw-r--r-- | src/Wavefront/Lex.hs | 654 |
1 files changed, 654 insertions, 0 deletions
diff --git a/src/Wavefront/Lex.hs b/src/Wavefront/Lex.hs new file mode 100644 index 0000000..501549f --- /dev/null +++ b/src/Wavefront/Lex.hs | |||
@@ -0,0 +1,654 @@ | |||
1 | {-# LANGUAGE NondecreasingIndentation #-} | ||
2 | {-# LANGUAGE OverloadedStrings #-} | ||
3 | module Wavefront.Lex where | ||
4 | |||
5 | import qualified Data.ByteString.Lazy.Char8 as L | ||
6 | import qualified Data.ByteString.Char8 as S | ||
7 | import Data.ByteString.Internal as BS | ||
8 | import Data.Char | ||
9 | import Data.IntMap (IntMap) | ||
10 | import qualified Data.IntMap as IntMap | ||
11 | import Data.ByteString.Lex.Fractional as F | ||
12 | import Data.ByteString.Lex.Integral as I | ||
13 | |||
14 | data 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_cdp :: [Int] -> m () | ||
40 | , deprecated_bzp :: [Int] -> m () | ||
41 | , deprecated_bsp :: [Int] -> m () | ||
42 | , mtllib :: [S.ByteString] -> m () | ||
43 | , objectName :: S.ByteString -> m () | ||
44 | , bmat :: ParamSpec -> [Double] -> m () | ||
45 | , step :: [Int] -> m () | ||
46 | , points :: [Int] -> m () | ||
47 | , usemap :: Maybe S.ByteString -> m () | ||
48 | , maplib :: [S.ByteString] -> m () | ||
49 | , c_interp :: Bool -> m () | ||
50 | , d_interp :: Bool -> m () | ||
51 | , trace_obj :: S.ByteString -> m () | ||
52 | , shadow_obj :: S.ByteString -> m () | ||
53 | , deprecated_res :: [Int] -> m () | ||
54 | , bevel :: Bool -> m () | ||
55 | , lod :: Int -> m () | ||
56 | , call :: S.ByteString -> [S.ByteString] -> m () | ||
57 | , command :: Bool -> L.ByteString -> m L.ByteString | ||
58 | , badToken :: L.ByteString -> m () | ||
59 | } | ||
60 | |||
61 | nullBuilder :: Applicative m => m () -> ObjBuilder m | ||
62 | nullBuilder def = ObjBuilder | ||
63 | { vertex = \vs -> def | ||
64 | , vertexT = \vs -> def | ||
65 | , vertexN = \vs -> def | ||
66 | , vertexP = \vs -> def | ||
67 | , face = \is -> def | ||
68 | , cstype = \isRat typ -> def | ||
69 | , curv2 = \is -> def | ||
70 | , curv = \u0 v0 is -> def | ||
71 | , parm = \uv is -> def | ||
72 | , specialPoints = \is -> def | ||
73 | , endFreeForm = def | ||
74 | , ctech = \approx -> def | ||
75 | , stech = \approx -> def | ||
76 | , deg = \is -> def | ||
77 | , surf = \u0 u1 v0 v1 ts -> def | ||
78 | , trim = \ss -> def | ||
79 | , hole = \ss -> def | ||
80 | , specialCurves = \ss -> def | ||
81 | , equivalentCurves = \ccs -> def | ||
82 | , groups = \gs -> def | ||
83 | , smoothingGroup = \sg -> def | ||
84 | , mergingGroup = \mg δ -> def | ||
85 | , usemtl = \mtl -> def | ||
86 | , deprecated_cdc = \is -> def | ||
87 | , deprecated_cdp = \is -> def | ||
88 | , deprecated_bzp = \is -> def | ||
89 | , deprecated_bsp = \is -> def | ||
90 | , mtllib = \fns -> def | ||
91 | , objectName = \obn -> def | ||
92 | , bmat = \uv fs -> def | ||
93 | , step = \is -> def | ||
94 | , points = \is -> def | ||
95 | , usemap = \map -> def | ||
96 | , maplib = \fns -> def | ||
97 | , c_interp = \b -> def | ||
98 | , d_interp = \b -> def | ||
99 | , trace_obj = \obj -> def | ||
100 | , shadow_obj = \obj -> def | ||
101 | , deprecated_res = \is -> def | ||
102 | , bevel = \b -> def | ||
103 | , lod = \lvl -> def | ||
104 | , call = \obj args -> def | ||
105 | , command = \b cmd -> def *> pure L.empty | ||
106 | , badToken = \bs -> def | ||
107 | } | ||
108 | |||
109 | |||
110 | data CurveSamplingSpec | ||
111 | -- ctech cparm | ||
112 | = UniformSubdivision | ||
113 | { divisionsPerCurveDegree :: Double -- ^ This really ought to be an integer but | ||
114 | -- but examples show floats. The only way | ||
115 | -- it makes sense as a float is if we are to | ||
116 | -- convert to an integer *after* multiplying | ||
117 | -- by the curve degree. | ||
118 | } | ||
119 | -- ctech cspace | ||
120 | | MaxLengthPolygonal { maxPolygonEdgeLength :: Double } | ||
121 | -- ctech curv | ||
122 | | CurvatureBasedPolygon { maxDistanceToCurve :: Double, maximumDegreesPerSample :: Double } | ||
123 | deriving (Eq,Show) | ||
124 | |||
125 | data SurfaceSamplingSpec | ||
126 | -- stech cparma ures vres | ||
127 | = UniformIsoparametric { uDivisionsPerDegree :: Double, vDivisionsPerDegree :: Double } | ||
128 | -- stech cparmb uvres | ||
129 | | UniformAfterTrimming { uvDivisionsPerDegree :: Double } | ||
130 | -- stech cspace maxlength | ||
131 | | MaxLengthPolytopal { maxPolytopEdgeLength :: Double } | ||
132 | -- stech curv maxdist maxangle | ||
133 | | CurvatureBasedPolytope { maxDistanceToSurface :: Double, maxDegreesPerCorner :: Double } | ||
134 | deriving (Eq,Show) | ||
135 | |||
136 | |||
137 | data ObjState = ObjState | ||
138 | { | ||
139 | } | ||
140 | |||
141 | newtype ObjConfig = ObjConfig | ||
142 | { cfgSubst :: IntMap L.ByteString | ||
143 | } | ||
144 | |||
145 | -- consChunk :: S.ByteString -> L.ByteString -> L.ByteString | ||
146 | -- consChunk c bs = L.fromChunks (c : L.toChunks bs) | ||
147 | |||
148 | reappend :: ByteString -> ByteString -> Maybe ByteString | ||
149 | reappend a b = | ||
150 | let (ap,ao,al) = BS.toForeignPtr a | ||
151 | (bp,bo,bl) = BS.toForeignPtr b | ||
152 | in if ap == bp && ao+al == bo | ||
153 | then Just $ BS.PS ap ao (al+bl) | ||
154 | else Nothing | ||
155 | |||
156 | reconsChunk :: S.ByteString -> L.ByteString -> L.ByteString | ||
157 | reconsChunk b bs = case L.toChunks bs of | ||
158 | (c:cs) -> case reappend b c of | ||
159 | Just x -> L.fromChunks (x:cs) | ||
160 | Nothing -> L.fromChunks (b:c:cs) | ||
161 | _ -> L.fromChunks [b] | ||
162 | |||
163 | findToken :: ObjConfig -> L.ByteString -> L.ByteString | ||
164 | findToken (ObjConfig args) bs = case L.dropWhile (\c -> isSpace c || c=='\\') bs of | ||
165 | cs -> case L.uncons cs of | ||
166 | Just ('#',comment) -> findToken (ObjConfig args) $ L.drop 1 $ L.dropWhile (/='\n') comment | ||
167 | Just ('$',ref) -> case L.splitAt 5 ref of | ||
168 | (refp,ds) -> case I.readDecimal (L.toStrict refp) of | ||
169 | Just (i,es) -> case IntMap.lookup i args of | ||
170 | Just val -> val <> reconsChunk es ds | ||
171 | _ -> reconsChunk es ds | ||
172 | _ -> cs | ||
173 | Just _ -> cs | ||
174 | Nothing -> L.empty | ||
175 | |||
176 | findNewLine :: [L.ByteString] -> ObjConfig -> L.ByteString -> (L.ByteString,L.ByteString) | ||
177 | findNewLine ps o@(ObjConfig args) bs = case L.break (\c -> c=='\n' || c=='\\' || c=='#' || c=='\\') bs of | ||
178 | (ts,cs) -> case L.uncons cs of | ||
179 | Just ('\n',ds) -> (L.concat $ reverse $ ts : ps,ds) | ||
180 | Just ('#',comment) -> findNewLine (ts:ps) o $ L.dropWhile (/='\n') comment | ||
181 | Just ('$',ref) -> case L.splitAt 5 ref of | ||
182 | (refp,ds) -> case I.readDecimal (L.toStrict refp) of | ||
183 | Just (i,es) -> case IntMap.lookup i args of | ||
184 | Just val -> findNewLine (val:ts:ps) o $ reconsChunk es ds | ||
185 | _ -> findNewLine (ts:ps) o $ reconsChunk es ds | ||
186 | _ -> findNewLine ("$":ts:ps) o ref | ||
187 | Just ('\\',ds) -> findNewLine (ts:ps) o $ if L.take 1 ds == "\n" then L.drop 1 ds | ||
188 | else ds | ||
189 | Nothing -> (L.concat $ reverse $ ts : ps,L.empty) | ||
190 | |||
191 | -- The 43 keywords of the OBJ file format: | ||
192 | -- | ||
193 | -- 1 bevel | ||
194 | -- 2 bmat | ||
195 | -- 3 bsp | ||
196 | -- 4 bzp | ||
197 | -- 5 call | ||
198 | -- 6 cdc | ||
199 | -- 7 cdp | ||
200 | -- 8 c_interp | ||
201 | -- 9 con | ||
202 | -- 10 csh | ||
203 | -- 11 cstype | ||
204 | -- 12 ctech | ||
205 | -- 13 curv2 | ||
206 | -- 14 curv | ||
207 | -- 15 deg | ||
208 | -- 16 d_interp | ||
209 | -- 17 end | ||
210 | -- 18 f | ||
211 | -- 19 g | ||
212 | -- 20 hole | ||
213 | -- 21 lod | ||
214 | -- 22 maplib | ||
215 | -- 23 mg | ||
216 | -- 24 mtllib | ||
217 | -- 25 o | ||
218 | -- 26 p | ||
219 | -- 27 parm | ||
220 | -- 28 res | ||
221 | -- 29 s | ||
222 | -- 30 scrv | ||
223 | -- 31 shadow_obj | ||
224 | -- 32 sp | ||
225 | -- 33 stech | ||
226 | -- 34 step | ||
227 | -- 35 surf | ||
228 | -- 36 trace_obj | ||
229 | -- 37 trim | ||
230 | -- 38 usemap | ||
231 | -- 39 usemtl | ||
232 | -- 40 v | ||
233 | -- 41 vn | ||
234 | -- 42 vp | ||
235 | -- 43 vt | ||
236 | |||
237 | nextToken :: (L.ByteString -> L.ByteString) -> L.ByteString -> L.ByteString | ||
238 | nextToken tok bs = tok $ L.dropWhile (not . isSpace) bs | ||
239 | |||
240 | parseFloats tok bs cont = case L.splitAt 10 (tok bs) of | ||
241 | (ds,bs') -> case F.readSigned F.readExponential (L.toStrict ds) of | ||
242 | Just (x,b) -> parseFloats tok (reconsChunk b bs') (cont . (x :)) | ||
243 | Nothing -> cont [] bs | ||
244 | |||
245 | parseFloatsN 0 _ bs cont = cont [] bs | ||
246 | parseFloatsN n tok bs cont = case L.splitAt 10 (tok bs) of | ||
247 | (ds,bs') -> case F.readSigned F.readExponential (L.toStrict ds) of | ||
248 | Just (x,b) -> parseFloatsN (n-1) tok (reconsChunk b bs') (cont . (x :)) | ||
249 | Nothing -> cont [] bs | ||
250 | |||
251 | parseInts tok bs cont = case L.splitAt 5 (tok bs) of | ||
252 | (ds,bs') -> case I.readSigned I.readDecimal (L.toStrict ds) of | ||
253 | Just (x,b) -> parseInts tok (reconsChunk b bs') (cont . (x :)) | ||
254 | Nothing -> cont [] bs | ||
255 | |||
256 | parseIntsN 0 tok bs cont = cont [] bs | ||
257 | parseIntsN n tok bs cont = case L.splitAt 5 (tok bs) of | ||
258 | (ds,bs') -> case I.readSigned I.readDecimal (L.toStrict ds) of | ||
259 | Just (x,b) -> parseIntsN (n-1) tok (reconsChunk b bs') (cont . (x :)) | ||
260 | Nothing -> cont [] bs | ||
261 | |||
262 | -- Optimize me | ||
263 | parseTriples :: (L.ByteString -> L.ByteString) -> L.ByteString -> ([RefTriple] -> L.ByteString -> b) -> b | ||
264 | parseTriples tok bs cont = case L.splitAt 17 (tok bs) of | ||
265 | (ds,bs') -> case I.readSigned I.readDecimal (L.toStrict ds) of | ||
266 | |||
267 | Just (v,b) -> case S.splitAt 1 b of | ||
268 | |||
269 | ("/",ds') -> case I.readSigned I.readDecimal ds' of | ||
270 | |||
271 | Just (vt,c) -> case S.splitAt 1 c of | ||
272 | ("/",ds'') -> case I.readSigned I.readDecimal ds'' of | ||
273 | Just (vn,d) -> parseTriples tok (reconsChunk d bs') $ cont . (RefTriple v (Just vt) (Just vn) :) | ||
274 | Nothing -> parseTriples tok (reconsChunk ds'' bs') $ cont . (RefTriple v (Just vt) Nothing :) | ||
275 | |||
276 | _ -> parseTriples tok (reconsChunk c bs') $ cont . (RefTriple v (Just vt) Nothing :) | ||
277 | |||
278 | Nothing -> case S.splitAt 1 ds' of | ||
279 | ("/",ds'') -> case I.readSigned I.readDecimal ds'' of | ||
280 | Just (vn,d) -> parseTriples tok (reconsChunk d bs') $ cont . (RefTriple v Nothing (Just vn) :) | ||
281 | Nothing -> parseTriples tok (reconsChunk ds'' bs') $ cont . (RefTriple v Nothing Nothing :) | ||
282 | |||
283 | _ -> parseTriples tok (reconsChunk ds' bs') $ cont . (RefTriple v Nothing Nothing :) | ||
284 | |||
285 | |||
286 | _ -> parseTriples tok (reconsChunk b bs') $ cont . (RefTriple v Nothing Nothing :) | ||
287 | |||
288 | Nothing -> cont [] bs | ||
289 | |||
290 | parseCurveSpecs :: (L.ByteString -> L.ByteString) -> L.ByteString -> ([CurveSpec] -> L.ByteString -> b) -> b | ||
291 | parseCurveSpecs tok bs cont = parseFloatsN 2 tok bs $ \fs bs' -> case fs of | ||
292 | (u0:u1:_) -> do | ||
293 | parseIntsN 1 tok bs' $ \is bs'' -> case is of | ||
294 | (i:_) -> parseCurveSpecs tok bs'' $ cont . (CurveSpec u0 u1 i :) | ||
295 | _ -> cont [] bs'' | ||
296 | |||
297 | _ -> cont [] bs' | ||
298 | |||
299 | |||
300 | parseCurveSpecsN :: Int -> (L.ByteString -> L.ByteString) -> L.ByteString -> ([CurveSpec] -> L.ByteString -> b) -> b | ||
301 | parseCurveSpecsN 0 tok bs cont = cont [] bs | ||
302 | parseCurveSpecsN n tok bs cont = parseFloatsN 2 tok bs $ \fs bs' -> case fs of | ||
303 | (u0:u1:_) -> do | ||
304 | parseIntsN 1 tok bs' $ \is bs'' -> case is of | ||
305 | (i:_) -> parseCurveSpecsN (n-1) tok bs'' $ cont . (CurveSpec u0 u1 i :) | ||
306 | _ -> cont [] bs'' | ||
307 | |||
308 | _ -> cont [] bs' | ||
309 | |||
310 | parseEmbeddedCurves :: (L.ByteString -> L.ByteString) -> L.ByteString -> ([EmbeddedCurve] -> L.ByteString -> b) -> b | ||
311 | parseEmbeddedCurves tok bs cont = parseIntsN 1 tok bs $ \is bs' -> case is of | ||
312 | (sref:_) -> do | ||
313 | parseCurveSpecsN 1 tok bs' $ \cs bs'' -> case cs of | ||
314 | (c:_) -> parseEmbeddedCurves tok bs'' $ cont . (EmbeddedCurve sref c :) | ||
315 | _ -> cont [] bs'' | ||
316 | |||
317 | _ -> cont [] bs' | ||
318 | |||
319 | data CSType = Bmatrix | Bezier | Bspline | Cardinal | Taylor | ||
320 | deriving (Eq,Ord,Show,Enum) | ||
321 | |||
322 | data ParamSpec = ParamU | ParamV | ||
323 | deriving (Eq,Ord,Show,Enum) | ||
324 | |||
325 | data RefTriple = RefTriple | ||
326 | { refV :: Int | ||
327 | , refT :: Maybe Int | ||
328 | , refN :: Maybe Int | ||
329 | } | ||
330 | -- data RefTriple = RefTriple Int (Maybe Int) (Maybe Int) | ||
331 | deriving (Eq,Ord,Show) | ||
332 | |||
333 | data CurveSpec = CurveSpec | ||
334 | { curveStart :: Double | ||
335 | , curveEnd :: Double | ||
336 | , curveRef :: Int | ||
337 | } | ||
338 | deriving (Eq,Ord,Show) | ||
339 | |||
340 | data EmbeddedCurve = EmbeddedCurve | ||
341 | { curveSurfaceRef :: Int | ||
342 | , embeddedCurve :: CurveSpec | ||
343 | } | ||
344 | deriving (Eq,Ord,Show) | ||
345 | |||
346 | lengthLessThan :: Int -> L.ByteString -> Bool | ||
347 | lengthLessThan n bs = | ||
348 | foldr (\c ret ac -> let m = S.length c in if ac <= m then False else ret $! ac - m) | ||
349 | (const True) | ||
350 | (L.toChunks bs) | ||
351 | n | ||
352 | |||
353 | substVar :: ObjConfig -> L.ByteString -> L.ByteString | ||
354 | substVar _ mtl | L.take 1 mtl/="$" = mtl | ||
355 | substVar (ObjConfig args) mtl = case I.readDecimal (L.toStrict $ L.drop 1 mtl) of | ||
356 | Just (i,_) -> case IntMap.lookup i args of | ||
357 | Just val -> val | ||
358 | Nothing -> mtl | ||
359 | Nothing -> mtl | ||
360 | |||
361 | |||
362 | parseOBJ :: Monad m => ObjBuilder m -> ObjConfig -> L.ByteString -> m () | ||
363 | parseOBJ builder args bs0 | ||
364 | | lengthLessThan 2 bs = return () | ||
365 | | isSpace (L.index bs 1) = case L.head bs of | ||
366 | 'f' -> parseT face 2 | ||
367 | 'g' -> case findNewLine [] args $ L.drop 1 bs of -- Newline required to terminate group name list. | ||
368 | (gn,bs') -> do | ||
369 | groups builder (map L.toStrict $ L.words gn) | ||
370 | parseOBJ builder args bs' | ||
371 | 's' -> case next 1 bs of | ||
372 | tok -> parseOffOrNumber tok $ \sg bs' -> do | ||
373 | smoothingGroup builder sg | ||
374 | parseOBJ builder args bs' | ||
375 | 'v' -> parseV vertex 2 | ||
376 | 'o' -> -- o object-name | ||
377 | case findNewLine [] args $ L.drop 1 bs of | ||
378 | (objn,bs') -> do | ||
379 | objectName builder (L.toStrict objn) | ||
380 | parseOBJ builder args bs' | ||
381 | 'p' -> parseI points 2 | ||
382 | _ -> bad bs | ||
383 | | otherwise = case L.take 2 bs of | ||
384 | "vt" -> parseV vertexT 3 | ||
385 | "vn" -> parseV vertexN 3 | ||
386 | "vp" -> parseV vertexP 3 | ||
387 | "be" -> parseOF bevel 2 -- bevel | ||
388 | "bm" -> parseUV (next 2 bs) $ \uv bs' -> do -- bmat | ||
389 | parseFloats (findToken args) bs' $ \vs bs'' -> do | ||
390 | bmat builder uv vs | ||
391 | parseOBJ builder args bs'' | ||
392 | |||
393 | "bs" -> parseI deprecated_bsp 4 -- bsp | ||
394 | "bz" -> parseI deprecated_bzp 4 -- bzp | ||
395 | "ca" -> -- call | ||
396 | case findNewLine [] args $ next 2 bs of | ||
397 | (fnn,bs') -> do | ||
398 | let slurp fnn = case L.break (=='.') fnn of | ||
399 | (fn,ext) | L.null (L.drop 1 ext) -> if L.null fn then [] | ||
400 | else [fn] | ||
401 | | ".obj" <- L.take 4 ext -> | ||
402 | if L.all isSpace (L.take 1 $ L.drop 4 ext) | ||
403 | then (fn <> ".obj") : slurpArgs (findToken args $ L.drop 4 ext) | ||
404 | else let f:fs = slurp (L.drop 3 ext) in (fn <> L.take 3 ext <> f) : fs | ||
405 | | otherwise -> let (f:fs) = slurp (L.drop 1 ext) | ||
406 | in (fn <> L.take 1 ext <> f) : fs | ||
407 | slurpArgs fnn | L.null fnn = [] | ||
408 | slurpArgs fnn = case L.break isSpace fnn of | ||
409 | (a,as) -> findToken args a : slurpArgs as | ||
410 | case map L.toStrict $ slurpArgs fnn of | ||
411 | [] -> return () | ||
412 | fn:as -> call builder fn as | ||
413 | parseOBJ builder args bs' | ||
414 | |||
415 | "cd" -> if lengthLessThan 3 bs || L.index bs 2 /= 'p' | ||
416 | then parseI deprecated_cdc 4 -- cdc | ||
417 | else parseI deprecated_cdp 4 -- cdp | ||
418 | "co" -> -- con | ||
419 | parseEmbeddedCurves (findToken args) (next 2 bs) $ \ss bs' -> do | ||
420 | equivalentCurves builder ss | ||
421 | parseOBJ builder args bs' | ||
422 | "cs" -> if lengthLessThan 3 bs | ||
423 | then bad bs | ||
424 | else case L.index bs 2 of | ||
425 | 'h' -> -- csh | ||
426 | let (dash,tok) = L.splitAt 1 $ next 3 bs | ||
427 | wantsErrorCheck = dash /= "-" | ||
428 | in case findNewLine [] args tok of | ||
429 | (cmd,bs') -> do result <- command builder wantsErrorCheck cmd | ||
430 | parseOBJ builder args $ result <> bs' | ||
431 | _ -> -- cstype | ||
432 | let parseRat = parseChar 'r' | ||
433 | parseTyp tok cont | lengthLessThan 3 tok = bad tok | ||
434 | | otherwise = case L.index tok 2 of | ||
435 | 'a' -> cont Bmatrix $ next 3 tok | ||
436 | 'z' -> cont Bezier $ next 3 tok | ||
437 | 'p' -> cont Bspline $ next 3 tok | ||
438 | 'r' -> cont Cardinal $ next 3 tok | ||
439 | 'y' -> cont Taylor $ next 3 tok | ||
440 | _ -> bad tok | ||
441 | in parseRat (next 2 bs) $ \isRat bs' -> do | ||
442 | parseTyp bs' $ \typ bs'' -> do | ||
443 | cstype builder isRat typ | ||
444 | parseOBJ builder args bs'' | ||
445 | "ct" -> -- ctech | ||
446 | let tok = next 2 bs | ||
447 | in if lengthLessThan 2 tok | ||
448 | then bad tok | ||
449 | else case L.index tok 1 of | ||
450 | 'p' -> -- cparm | ||
451 | parseFloats (findToken args) (next 2 tok) $ \is bs' -> do | ||
452 | let x:_ = is ++ [0] | ||
453 | ctech builder (UniformSubdivision x) | ||
454 | parseOBJ builder args bs' | ||
455 | 's' -> -- cspace | ||
456 | parseFloats (findToken args) (next 2 tok) $ \fs bs' -> do | ||
457 | let x:_ = fs ++ [1.0] | ||
458 | ctech builder (MaxLengthPolygonal x) | ||
459 | parseOBJ builder args bs' | ||
460 | 'u' -> -- curv | ||
461 | parseFloats (findToken args) (next 2 tok) $ \fs bs' -> do | ||
462 | let δ:θ:_ = fs ++ repeat 1.0 | ||
463 | ctech builder (CurvatureBasedPolygon δ θ) | ||
464 | parseOBJ builder args bs' | ||
465 | _ -> bad tok | ||
466 | "cu" -> if lengthLessThan 5 bs | ||
467 | then bad bs | ||
468 | else if L.index bs 4 == '2' | ||
469 | then parseI curv2 5 -- curv2 | ||
470 | else do -- curv | ||
471 | parseFloatsN 2 (findToken args) (L.drop 4 bs) $ \vs bs' -> | ||
472 | parseInts (findToken args) bs' $ \is bs'' -> do | ||
473 | let u0:v0:_ = vs ++ repeat 0.0 | ||
474 | curv builder u0 v0 is | ||
475 | parseOBJ builder args bs'' | ||
476 | "c_" -> -- c_interp | ||
477 | parseOF c_interp 2 | ||
478 | "de" -> parseI deg 3 | ||
479 | "d_" -> -- d_interp | ||
480 | parseOF d_interp 2 | ||
481 | "en" -> do endFreeForm builder | ||
482 | parseOBJ builder args (next 2 bs) | ||
483 | "lo" -> -- lod | ||
484 | parseInts (findToken args) (next 2 bs) $ \is bs' -> do | ||
485 | let level:_ = is ++ [0] | ||
486 | lod builder level | ||
487 | parseOBJ builder args bs' | ||
488 | "ho" -> -- hole | ||
489 | parseCurveSpecs (findToken args) (next 2 bs) $ \ss bs' -> do | ||
490 | hole builder ss | ||
491 | parseOBJ builder args bs' | ||
492 | "mg" -> case next 2 bs of | ||
493 | tok -> parseOffOrNumber tok $ \mg bs' -> do | ||
494 | parseFloatsN 1 (findToken args) bs' $ \fs bs'' -> do | ||
495 | mergingGroup builder mg (head $ fs ++ [0]) | ||
496 | parseOBJ builder args bs'' | ||
497 | "pa" -> parseUV (next 2 bs) $ \uv bs' -> do | ||
498 | parseFloats (findToken args) bs' $ \vs bs'' -> do | ||
499 | parm builder uv vs | ||
500 | parseOBJ builder args bs'' | ||
501 | "re" -> -- res (deprecated) | ||
502 | parseI deprecated_res 3 | ||
503 | "sc" -> -- scrv | ||
504 | parseCurveSpecs (findToken args) (next 2 bs) $ \ss bs' -> do | ||
505 | specialCurves builder ss | ||
506 | parseOBJ builder args bs' | ||
507 | "sh" -> -- shadow_obj | ||
508 | parseO shadow_obj 10 | ||
509 | "sp" -> parseI specialPoints 3 | ||
510 | "st" -> -- stech or step | ||
511 | if lengthLessThan 4 bs then bad bs | ||
512 | else case L.index bs 3 of | ||
513 | 'c' -> | ||
514 | -- stech | ||
515 | let tok = next 2 bs | ||
516 | in if lengthLessThan 2 tok | ||
517 | then bad tok | ||
518 | else case L.index tok 1 of | ||
519 | 'p' -> -- cparma/cparmb | ||
520 | if lengthLessThan 6 tok | ||
521 | then bad tok | ||
522 | else if L.index tok 5 == 'b' | ||
523 | then -- cparmb | ||
524 | parseFloats (findToken args) (next 5 tok) $ \is bs' -> do | ||
525 | let x:_ = is ++ [0] | ||
526 | stech builder (UniformAfterTrimming x) | ||
527 | parseOBJ builder args bs' | ||
528 | else -- cparma | ||
529 | parseFloats (findToken args) (next 5 tok) $ \is bs' -> do | ||
530 | let x:y:_ = is ++ [0] | ||
531 | stech builder (UniformIsoparametric x y) | ||
532 | parseOBJ builder args bs' | ||
533 | 's' -> -- cspace | ||
534 | parseFloats (findToken args) (next 2 tok) $ \fs bs' -> do | ||
535 | let x:_ = fs ++ [1.0] | ||
536 | stech builder (MaxLengthPolytopal x) | ||
537 | parseOBJ builder args bs' | ||
538 | 'u' -> -- curv | ||
539 | parseFloats (findToken args) (next 2 tok) $ \fs bs' -> do | ||
540 | let δ:θ:_ = fs ++ repeat 1.0 | ||
541 | stech builder (CurvatureBasedPolytope δ θ) | ||
542 | parseOBJ builder args bs' | ||
543 | _ -> bad tok | ||
544 | |||
545 | _ -> -- step | ||
546 | parseI step 4 | ||
547 | |||
548 | "su" -> -- surf | ||
549 | parseFloatsN 4 (findToken args) (next 2 bs) $ \fs bs' -> do | ||
550 | parseTriples (findToken args) bs' $ \ts bs'' -> do | ||
551 | let u0:u1:v0:v1:_ = fs ++ repeat 0 | ||
552 | surf builder u0 u1 v0 v1 ts | ||
553 | parseOBJ builder args bs'' | ||
554 | "tr" -> -- trip or trace_obj | ||
555 | if lengthLessThan 3 bs | ||
556 | then bad bs | ||
557 | else case L.index bs 2 of | ||
558 | 'a' -> -- trace_obj | ||
559 | parseO trace_obj 9 | ||
560 | _ -> -- trim | ||
561 | parseCurveSpecs (findToken args) (next 2 bs) $ \ss bs' -> do | ||
562 | trim builder ss | ||
563 | parseOBJ builder args bs' | ||
564 | "us" -> -- usemtl or usemap | ||
565 | if lengthLessThan 5 bs | ||
566 | then bad bs | ||
567 | else case L.break isSpace $ next 2 bs of | ||
568 | (mtl0,bs') -> do | ||
569 | let mtl = substVar args mtl0 | ||
570 | case L.index bs 4 of | ||
571 | 'a' -> usemap builder $ if mtl == "off" then Nothing | ||
572 | else Just (L.toStrict mtl) | ||
573 | _ -> usemtl builder (L.toStrict mtl) | ||
574 | parseOBJ builder args bs' | ||
575 | "ma" -> -- maplib | ||
576 | case findNewLine [] args $ next 2 bs of | ||
577 | (fnn,bs') -> do | ||
578 | let slurp fnn = case L.break (=='.') fnn of | ||
579 | (fn,ext) | L.null (L.drop 1 ext) -> if L.null fn then [] | ||
580 | else [fn] | ||
581 | | ext <- L.take 4 ext | ||
582 | -- XXX What is the map library extension? | ||
583 | , ext `elem` [".map",".mtl",".obj"] -> | ||
584 | if L.all isSpace (L.take 1 $ L.drop 4 ext) | ||
585 | then (fn <> ext) : slurp (findToken args $ L.drop 4 ext) | ||
586 | else let f:fs = slurp (L.drop 3 ext) in (fn <> L.take 3 ext <> f) : fs | ||
587 | | otherwise -> let (f:fs) = slurp (L.drop 1 ext) | ||
588 | in (fn <> L.take 1 ext <> f) : fs | ||
589 | maplib builder (map L.toStrict $ slurp fnn) | ||
590 | parseOBJ builder args bs' | ||
591 | "mt" -> -- mtllib | ||
592 | case findNewLine [] args $ next 2 bs of | ||
593 | (fnn,bs') -> do | ||
594 | let slurp fnn = case L.break (=='.') fnn of | ||
595 | (fn,ext) | L.null (L.drop 1 ext) -> if L.null fn then [] | ||
596 | else [fn] | ||
597 | | ".mtl" <- L.take 4 ext -> | ||
598 | if L.all isSpace (L.take 1 $ L.drop 4 ext) | ||
599 | then (fn <> ".mtl") : slurp (findToken args $ L.drop 4 ext) | ||
600 | else let f:fs = slurp (L.drop 3 ext) in (fn <> L.take 3 ext <> f) : fs | ||
601 | | otherwise -> let (f:fs) = slurp (L.drop 1 ext) | ||
602 | in (fn <> L.take 1 ext <> f) : fs | ||
603 | mtllib builder (map L.toStrict $ slurp fnn) | ||
604 | parseOBJ builder args bs' | ||
605 | _ -> bad bs | ||
606 | where | ||
607 | bs = findToken args bs0 | ||
608 | bad bs = case findNewLine [] args bs of | ||
609 | (x,bs') -> do badToken builder x | ||
610 | parseOBJ builder args bs' | ||
611 | next n xs = nextToken (findToken args) $ L.drop n xs | ||
612 | parseChar c tok cont = case L.uncons tok of | ||
613 | Just (x,cs) | x==c -> cont True $ next 0 cs | ||
614 | _ -> cont False tok | ||
615 | parseO build n = case findNewLine [] args $ L.drop n bs of | ||
616 | (fn,bs') -> do | ||
617 | build builder $ sanitizeOBJFilename fn | ||
618 | parseOBJ builder args bs' | ||
619 | parseUV tok cont = parseChar 'u' tok $ \isU bs' -> do | ||
620 | cont (if isU then ParamU else ParamV) | ||
621 | (if isU then bs' else L.drop 1 bs') | ||
622 | parseV build n = do | ||
623 | parseFloats (findToken args) (L.drop n bs) $ \vs bs' -> do | ||
624 | build builder vs | ||
625 | parseOBJ builder args bs' | ||
626 | parseI build n = do | ||
627 | parseInts (findToken args) (L.drop n bs) $ \vs bs' -> do | ||
628 | build builder vs | ||
629 | parseOBJ builder args bs' | ||
630 | parseT build n = do | ||
631 | parseTriples (findToken args) (L.drop n bs) $ \vs bs' -> do | ||
632 | build builder vs | ||
633 | parseOBJ builder args bs' | ||
634 | parseOffOrNumber tok cont = parseIntsN 1 (findToken args) tok $ \is bs' -> do | ||
635 | let (sg,bs'') = case is of | ||
636 | (i:_) -> (i,bs') | ||
637 | _ | lengthLessThan 2 tok -> (0,tok) | ||
638 | _ -> (if L.index tok 1 == 'f' then 0 else 1, next 1 tok) | ||
639 | cont sg bs'' | ||
640 | parseOF build n = | ||
641 | let tok = findToken args (next n bs) | ||
642 | in if lengthLessThan 2 tok | ||
643 | then bad tok | ||
644 | else let flag = case L.index tok 1 of | ||
645 | 'f' -> build builder False -- off | ||
646 | _ -> build builder True -- on | ||
647 | in parseOBJ builder args (next 2 tok) | ||
648 | |||
649 | |||
650 | sanitizeOBJFilename :: L.ByteString -> S.ByteString | ||
651 | sanitizeOBJFilename fn = case S.breakEnd isSpace $ L.toStrict $ L.dropWhile isSpace fn of | ||
652 | (stripped,_) -> case S.breakEnd (=='.') stripped of | ||
653 | (basename,ext) | S.null basename -> ext <> ".obj" | ||
654 | | otherwise -> stripped | ||