summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Crayne <joe@jerkface.net>2019-06-14 19:22:29 -0400
committerJoe Crayne <joe@jerkface.net>2019-06-14 19:22:29 -0400
commitbc7855492d8bdb21437c6d6b94a9c1872da45f1f (patch)
tree2577c9bbae84957bec410976bab6fa6021653837
parent95f7c67b576f4e1ae897ea010da90d406681f018 (diff)
crayne parser: higher level interface.
-rw-r--r--src/Data/Wavefront.hs167
-rw-r--r--src/Wavefront.hs137
-rw-r--r--src/Wavefront/Lex.hs6
-rw-r--r--wavefront-obj.cabal2
4 files changed, 309 insertions, 3 deletions
diff --git a/src/Data/Wavefront.hs b/src/Data/Wavefront.hs
new file mode 100644
index 0000000..2284d38
--- /dev/null
+++ b/src/Data/Wavefront.hs
@@ -0,0 +1,167 @@
1{-# LANGUAGE ConstraintKinds #-}
2{-# LANGUAGE DeriveFunctor #-}
3{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE KindSignatures #-}
5{-# LANGUAGE MultiParamTypeClasses #-}
6{-# LANGUAGE PatternSynonyms #-}
7{-# LANGUAGE RankNTypes #-}
8{-# LANGUAGE StandaloneDeriving #-}
9{-# LANGUAGE UndecidableInstances #-}
10module Data.Wavefront where
11
12import Data.Kind
13import Data.Text (Text)
14import Numeric.Natural
15import qualified Rank2
16
17data WavefrontOBJ v = WavefrontOBJ {
18 objLocations :: v Location
19 , objTexCoords :: v TexCoord
20 , objNormals :: v Normal
21 , objPoints :: v (Element Point)
22 , objLines :: v (Element Line)
23 , objFaces :: v (Element Face)
24 , objMtlLibs :: v Text
25 }
26
27type ForThisWavefrontOBJ (c :: * -> Constraint) v =
28 ( c (v Location)
29 , c (v TexCoord)
30 , c (v Normal)
31 , c (v (Element Point))
32 , c (v (Element Line))
33 , c (v (Element Face))
34 , c (v Text) )
35
36type ForAllWavefrontOBJ (c :: * -> Constraint) =
37 ( c Location
38 , c TexCoord
39 , c Normal
40 , c (Element Point)
41 , c (Element Line)
42 , c (Element Face)
43 , c Text
44 )
45
46deriving instance ForThisWavefrontOBJ Eq v => Eq (WavefrontOBJ v)
47deriving instance ForThisWavefrontOBJ Show v => Show (WavefrontOBJ v)
48
49instance Rank2.Functor WavefrontOBJ where
50 f <$> obj = obj
51 { objLocations = f (objLocations obj)
52 , objTexCoords = f (objTexCoords obj)
53 , objNormals = f (objNormals obj)
54 , objPoints = f (objPoints obj)
55 , objLines = f (objLines obj)
56 , objFaces = f (objFaces obj)
57 , objMtlLibs = f (objMtlLibs obj)
58 }
59
60class Rank2.Functor g => Payload c g where
61 mapPayload :: proxy c -> (forall a. c a => p a -> q a) -> g p -> g q
62
63instance ForAllWavefrontOBJ c => Payload c WavefrontOBJ where
64 mapPayload _ f obj = obj
65 { objLocations = f (objLocations obj)
66 , objTexCoords = f (objTexCoords obj)
67 , objNormals = f (objNormals obj)
68 , objPoints = f (objPoints obj)
69 , objLines = f (objLines obj)
70 , objFaces = f (objFaces obj)
71 , objMtlLibs = f (objMtlLibs obj)
72 }
73
74
75
76-- | A location is a 4-floating vector. You can access to its components by
77-- pattern matching on them:
78--
79-- > let Location x y z w = Location 1 2 3 4
80--
81-- That type is strict and unboxed.
82data Location = Location {
83 locX :: {-# UNPACK #-} !Float
84 , locY :: {-# UNPACK #-} !Float
85 , locZ :: {-# UNPACK #-} !Float
86 , locW :: {-# UNPACK #-} !Float
87 } deriving (Eq,Show)
88
89-- | A texture coordinate is a 3D-floating vector. You can access to its
90-- components by pattern matching on them:
91--
92-- > let TexCoord r s t = TexCoord 0.1 0.2 0.3
93--
94-- That type is strcit and unboxed.
95data TexCoord = TexCoord {
96 texcoordR :: {-# UNPACK #-} !Float
97 , texcoordS :: {-# UNPACK #-} !Float
98 , texcoordT :: {-# UNPACK #-} !Float
99 } deriving (Eq,Show)
100
101
102-- | A normal is a 3-floating vector. You can access to its components by
103-- pattern matching on them:
104--
105-- > let Normal nx ny nz = Normal 0.1 0.2 0.3
106--
107-- That type is strict and unboxed.
108data Normal = Normal {
109 norX :: {-# UNPACK #-} !Float
110 , norY :: {-# UNPACK #-} !Float
111 , norZ :: {-# UNPACK #-} !Float
112 } deriving (Eq,Show)
113
114-- | A point is a single index that references the locations. It’s a canonical
115-- type that truly represents a polygonal point.
116data Point = Point {
117 pointLocIndex :: {-# UNPACK #-} !Int
118 } deriving (Eq,Show)
119
120-- | A line index is a pair of indices. @'LineIndex' vi vti@. @vi@ references
121-- the locations and @vti@ indexes the texture coordinates. If @vti ==
122-- 'Nothing'@, then that 'LineIndex' doesn’t have texture coordinates
123-- associated with.
124data LineIndex = LineIndex {
125 lineLocIndex :: {-# UNPACK #-} !Int
126 , lineTexCoordIndex :: !(Maybe Int)
127 } deriving (Eq,Show)
128
129-- | A line gathers two line indices accessible by pattern matching or
130-- 'lineIndexA' and 'lineIndexB'.
131data Line = Line {
132 lineIndexA :: LineIndex
133 , lineIndexB :: LineIndex
134 } deriving (Eq,Show)
135
136-- | A face index is a triplet of indices. @'FaceIndex' vi vti vni@ is a face
137-- that indexes the locations with @vi@, the texture coordinates with @vti@ and
138-- the normals with @vni@. An index set to 'Nothing' means /no information/.
139-- That is, if @vni == 'Nothing'@, then that 'FaceIndex' doesn’t have a normal
140-- associated with.
141data FaceIndex = FaceIndex {
142 faceLocIndex :: {-# UNPACK #-} !Int
143 , faceTexCoordIndex :: !(Maybe Int)
144 , faceNorIndex :: !(Maybe Int)
145 } deriving (Eq,Show)
146
147-- | A face gathers several 'FaceIndex' to build up faces. It has a least three
148-- vertices
149data Face = Face FaceIndex FaceIndex FaceIndex [FaceIndex] deriving (Eq,Show)
150
151pattern Triangle :: FaceIndex -> FaceIndex -> FaceIndex -> Face
152pattern Triangle a b c = Face a b c []
153
154pattern Quad :: FaceIndex -> FaceIndex -> FaceIndex -> FaceIndex -> Face
155pattern Quad a b c d = Face a b c [d]
156
157-- | An element holds a value along with the user-defined object’s name (if
158-- any), the associated groups, the used material and the smoothing group the
159-- element belongs to (if any). Those values can be used to sort the data per
160-- object or per group and to lookup materials.
161data Element a = Element {
162 elObject :: Maybe Text
163 , elGroups :: [Text]
164 , elMtl :: Maybe Text
165 , elSmoothingGroup :: Natural
166 , elValue :: a
167 } deriving (Eq,Show,Functor)
diff --git a/src/Wavefront.hs b/src/Wavefront.hs
new file mode 100644
index 0000000..6195eee
--- /dev/null
+++ b/src/Wavefront.hs
@@ -0,0 +1,137 @@
1{-# LANGUAGE FlexibleContexts #-}
2module Wavefront where
3
4import Data.Wavefront
5import Wavefront.Lex
6
7import Control.Arrow
8import Control.Monad.State
9import qualified Data.ByteString.Lazy.Char8 as L
10import qualified Data.DList as DList
11 ;import Data.DList (DList)
12import Data.Functor.Identity
13import qualified Data.IntMap as IntMap
14import Data.Text.Encoding (decodeUtf8)
15import qualified Data.Vector as Vector
16 ;import Data.Vector (Vector)
17import qualified Rank2
18
19newtype Count x = Count Int
20
21updateCount :: MonadState s m => (s -> Count x) -> (Count x -> s -> s) -> m ()
22updateCount field setField = do
23 Count c0 <- gets field
24 let c = succ c0
25 c `seq` modify (setField $ Count c)
26
27fixupRef :: Count x -> Int -> Int
28fixupRef (Count n) x | x > 0 = x - 1 -- Renumber from 0.
29 | otherwise = n + x -- Negative values are relative.
30
31fixupTriple :: WavefrontOBJ Count -> RefTriple -> RefTriple
32fixupTriple o (RefTriple v t n) =
33 RefTriple (fixupRef (objLocations o) v)
34 (fixupRef (objTexCoords o) <$> t)
35 (fixupRef (objNormals o) <$> n)
36
37objBookKeeping :: Monad m => ObjBuilder m -> ObjBuilder (StateT (WavefrontOBJ Count) m)
38objBookKeeping builder = (lift Rank2.<$> builder)
39 { vertex = \xs -> do lift $ vertex builder xs
40 updateCount objLocations $ \x o -> o { objLocations = x }
41 , vertexT = \xs -> do lift $ vertexT builder xs
42 updateCount objTexCoords $ \x o -> o { objTexCoords = x }
43 , vertexN = \xs -> do lift $ vertexN builder xs
44 updateCount objNormals $ \x o -> o { objNormals = x }
45 , points = \xs -> do
46 n <- gets objLocations
47 lift $ points builder $ fixupRef n <$> xs
48 , line = \ts -> do
49 o <- get
50 lift $ line builder $ fixupTriple o <$> ts
51 , face = \ts -> do
52 o <- get
53 lift $ face builder $ fixupTriple o <$> ts
54 }
55
56mkv :: [Double] -> Location
57mkv cs = Location x y z w where (x:y:z:w:_) = map realToFrac cs
58
59mkt :: [Double] -> TexCoord
60mkt cs = TexCoord x y z where (x:y:z:_) = map realToFrac cs
61
62mkn :: [Double] -> Normal
63mkn cs = Normal x y z where (x:y:z:_) = map realToFrac cs
64
65mkl :: RefTriple -> RefTriple -> Line
66mkl (RefTriple a at _) (RefTriple b bt _) = Line (LineIndex a at) (LineIndex b bt)
67
68-- I'd have thought these would be Coercible, but I guess not.
69mkF :: RefTriple -> FaceIndex
70mkF (RefTriple a at an) = FaceIndex a at an
71
72elemental :: Element () -> x -> Element x
73elemental element x = fmap (const x) element
74
75modifyFirst :: MonadState (c, d) m => (c -> c) -> m ()
76modifyFirst = modify' . first
77
78buildOBJ :: ObjBuilder (State (WavefrontOBJ DList,Element ()))
79buildOBJ = (nullBuilder $ pure ())
80 { vertex = \xs -> modifyFirst $ \o -> o { objLocations = objLocations o `DList.snoc` mkv xs }
81 , vertexT = \xs -> modifyFirst $ \o -> o { objTexCoords = objTexCoords o `DList.snoc` mkt xs }
82 , vertexN = \xs -> modifyFirst $ \o -> o { objNormals = objNormals o `DList.snoc` mkn xs }
83 , points = \xs -> do
84 let p = map Point xs :: [Point]
85 (pts,element) <- gets (objPoints *** elemental)
86 modifyFirst $ \o -> o { objPoints = pts `DList.append` fmap element (DList.fromList p) }
87 , line = \xs -> do
88 (lns,element) <- gets (objLines *** elemental)
89 let l = zipWith mkl xs (tail xs)
90 -- Line requires at least two points. We'll ignore it otherwise.
91 when (not $ null l) $
92 modifyFirst $ \o -> o { objLines = lns `DList.append` fmap element (DList.fromList l) }
93 , face = \xs -> do
94 (fcs,element) <- gets (objFaces *** elemental)
95 case map mkF xs of
96 a:b:c:ds -> modifyFirst $ \o -> o { objFaces = fcs `DList.snoc` element (Face a b c ds) }
97 _ -> return () -- Ignore faces with less than 3 indices.
98 , mtllib = \xs -> do
99 let l = map decodeUtf8 xs
100 libs <- gets (objMtlLibs . fst)
101 modifyFirst $ \o -> o { objMtlLibs = libs `DList.append` DList.fromList l }
102 , groups = \xs -> do
103 let g = map decodeUtf8 xs
104 modify' $ second $ \e -> e { elGroups = g }
105 , objectName = \x -> do
106 let o = decodeUtf8 x
107 modify' $ second $ \e -> e { elObject = Just o }
108 , usemtl = \x -> do
109 let mtl = decodeUtf8 x
110 modify' $ second $ \e -> e { elMtl = Just mtl }
111 , smoothingGroup = \x -> when (x > 0) $ do
112 modify $ second $ \e -> e { elSmoothingGroup = fromIntegral x }
113 }
114
115parse :: L.ByteString -> WavefrontOBJ Vector
116parse bs = Rank2.fmap (Vector.fromList . DList.toList) obj
117 where
118 b = objBookKeeping buildOBJ
119 ls = WavefrontOBJ
120 { objLocations = DList.empty
121 , objTexCoords = DList.empty
122 , objNormals = DList.empty
123 , objPoints = DList.empty
124 , objLines = DList.empty
125 , objFaces = DList.empty
126 , objMtlLibs = DList.empty
127 }
128 c = Rank2.fmap (const $ Count 0) ls :: WavefrontOBJ Count
129 el = Element
130 { elObject = Nothing
131 , elGroups = []
132 , elMtl = Nothing
133 , elSmoothingGroup = 0
134 , elValue = ()
135 }
136 substvars = ObjConfig IntMap.empty
137 (obj,_) = execState (runStateT (parseOBJ b substvars bs) c) (ls,el)
diff --git a/src/Wavefront/Lex.hs b/src/Wavefront/Lex.hs
index 7123184..78c6f1d 100644
--- a/src/Wavefront/Lex.hs
+++ b/src/Wavefront/Lex.hs
@@ -377,9 +377,9 @@ data ParamSpec = ParamU | ParamV
377 deriving (Eq,Ord,Show,Enum) 377 deriving (Eq,Ord,Show,Enum)
378 378
379data RefTriple = RefTriple 379data RefTriple = RefTriple
380 { refV :: Int 380 { refV :: {-# UNPACK #-} !Int
381 , refT :: Maybe Int 381 , refT :: !(Maybe Int)
382 , refN :: Maybe Int 382 , refN :: !(Maybe Int)
383 } 383 }
384-- data RefTriple = RefTriple Int (Maybe Int) (Maybe Int) 384-- data RefTriple = RefTriple Int (Maybe Int) (Maybe Int)
385 deriving (Eq,Ord,Show) 385 deriving (Eq,Ord,Show)
diff --git a/wavefront-obj.cabal b/wavefront-obj.cabal
index 1ccbc91..a771fe9 100644
--- a/wavefront-obj.cabal
+++ b/wavefront-obj.cabal
@@ -49,6 +49,8 @@ library
49 , Codec.Wavefront.FreeForm 49 , Codec.Wavefront.FreeForm
50 , Codec.Wavefront.Token 50 , Codec.Wavefront.Token
51 , Wavefront.Lex 51 , Wavefront.Lex
52 , Data.Wavefront
53 , Wavefront
52 -- other-modules: 54 -- other-modules:
53 other-extensions: ForeignFunctionInterface 55 other-extensions: ForeignFunctionInterface
54 , UnicodeSyntax 56 , UnicodeSyntax