Home My Page Projects Code Snippets Project Openings 3D graphics for Standard ML
Summary Activity SCM

SCM Repository

[sml3d] Annotation of /trunk/sml3d/examples/md3-viewer/load-md3.sml
ViewVC logotype

Annotation of /trunk/sml3d/examples/md3-viewer/load-md3.sml

Parent Directory Parent Directory | Revision Log Revision Log


Revision 402 - (view) (download)

1 : jhr 388 (* load-md3.sml
2 :     *
3 :     * COPYRIGHT (c) 2008 John Reppy (http://cs.uchicago.edu/~jhr)
4 :     * All rights reserved.
5 :     *
6 :     * A loader for "Quake 3: Arena" model files (MD3 files). This code is based on the
7 :     * description at
8 :     *
9 : jhr 402 * http://icculus.org/homepages/phaethon/q3a/formats/md3format.html
10 : jhr 388 *)
11 :    
12 : jhr 392 structure LoadMD3 : sig
13 : jhr 388
14 : jhr 392 val loadFile : string -> MD3.model
15 : jhr 388
16 : jhr 392 end = struct
17 : jhr 388
18 :     structure W8V = Word8Vector
19 :     structure DB = DataBuffer
20 :    
21 :     (* seek to the given position in a binary input stream *)
22 :     fun seek (inS, pos) = let
23 :     val (rd, _) = BinIO.StreamIO.getReader (BinIO.getInstream inS)
24 :     val BinPrimIO.RD{setPos = SOME setPos, ...} = rd
25 :     in
26 :     setPos pos;
27 :     BinIO.setInstream (inS, BinIO.StreamIO.mkInstream(rd, W8V.fromList[]))
28 :     end
29 :    
30 :     (* return the current file position in a binary input stream *)
31 :     fun filePos inS = let
32 :     val arg as (rd, _) = BinIO.StreamIO.getReader (BinIO.getInstream inS)
33 :     val BinPrimIO.RD{getPos = SOME getPos, ...} = rd
34 :     val pos = getPos ()
35 :     in
36 :     BinIO.setInstream (inS, BinIO.StreamIO.mkInstream arg);
37 :     pos
38 :     end
39 :    
40 :     (* read data from a binary input stream *)
41 :     fun readData (inS, nbytes) = let
42 :     val v = BinIO.inputN (inS, nbytes)
43 :     in
44 :     if (W8V.length v <> nbytes) then raise Fail "missing data" else ();
45 :     v
46 :     end
47 :    
48 :     fun readN readSomething (inS, n) = let
49 :     fun read (0, l) = List.rev l
50 :     | read (n, l) = read (n-1, readSomething inS :: l)
51 :     in
52 :     read (n, [])
53 :     end
54 :    
55 :     fun repeati f n = let
56 :     fun iter i = if (i < n) then (f i; iter(i+1)) else ()
57 :     in
58 :     iter 0
59 :     end
60 :    
61 :     (* extract values from a byte vector *)
62 :     fun getString (data, pos, sz) = let
63 :     fun strlen n = if (n < sz) andalso (W8V.sub(data, pos+n) <> 0w0)
64 :     then strlen(n+1)
65 :     else n
66 :     val len = strlen 0
67 :     in
68 :     (Byte.unpackStringVec (Word8VectorSlice.slice(data, pos, SOME len)), pos+sz)
69 :     end
70 :     fun getS16 (data, pos) = let
71 :     val n = LargeWord.toIntX (PackWord16Little.subVecX(data, pos div 2))
72 :     in
73 :     (n, pos+2)
74 :     end
75 :     fun getU16 (data, pos) = let
76 :     val n = Word.fromLarge (PackWord16Little.subVec(data, pos div 2))
77 :     in
78 :     (n, pos+2)
79 :     end
80 :     fun getS32 (data, pos) = let
81 :     val n = LargeWord.toIntX (PackWord32Little.subVecX(data, pos div 4))
82 :     in
83 :     (n, pos+4)
84 :     end
85 :     fun getU32 (data, pos) = let
86 :     val n = Word.fromLarge (PackWord32Little.subVecX(data, pos div 4))
87 :     in
88 :     (n, pos+4)
89 :     end
90 :     fun getF32 (data, pos) = (PackReal32Little.subVec(data, pos div 4), pos+4)
91 :     fun getVec3 (data, pos) = let
92 :     val (x, pos) = getF32(data, pos)
93 :     val (y, pos) = getF32(data, pos)
94 :     val (z, pos) = getF32(data, pos)
95 :     in
96 :     ({x=x, y=y, z=z}, pos)
97 :     end
98 :    
99 : jhr 394 (*+DEBUG
100 : jhr 391 val t2s = SML3dTypeUtil.fmt3 Float.toString
101 :     val v2s = SML3dTypeUtil.fmtv3 Float.toString
102 : jhr 394 -DEBUG*)
103 : jhr 388
104 :     (* read the MD3 header *)
105 :     fun readHdr inS = let
106 :     val data = readData(inS, 108)
107 :     val (ident, pos) = getString (data, 0, 4) (* 0 *)
108 :     val (version, pos) = getS32 (data, pos) (* 4 *)
109 :     val (name, pos) = getString (data, pos, 64) (* 8 *)
110 :     val (flags, pos) = getS32 (data, pos) (* 72 *)
111 :     val (numFrames, pos) = getS32 (data, pos) (* 76 *)
112 :     val (numTags, pos) = getS32 (data, pos) (* 80 *)
113 :     val (numSurfaces, pos) = getS32 (data, pos) (* 84 *)
114 :     val (numSkins, pos) = getS32 (data, pos) (* 88 *)
115 :     val (framesOffset, pos) = getS32 (data, pos) (* 92 *)
116 :     val (tagsOffset, pos) = getS32 (data, pos) (* 96 *)
117 :     val (surfsOffset, pos) = getS32 (data, pos) (* 100 *)
118 :     val (eofOffset, pos) = getS32 (data, pos) (* 104 *)
119 :     in
120 :     if (ident <> "IDP3")
121 :     then raise Fail "bogus magic number"
122 :     else ();
123 :     { name = name,
124 :     numFrames = numFrames,
125 :     numTags = numTags,
126 :     numSurfaces = numSurfaces,
127 :     framesOffset = Position.fromInt framesOffset,
128 :     tagsOffset = Position.fromInt tagsOffset,
129 :     surfsOffset = Position.fromInt surfsOffset
130 :     }
131 :     end
132 :    
133 :     fun readFrame inS = let
134 :     val data = readData(inS, 56)
135 :     val (minBounds, pos) = getVec3 (data, 0)
136 :     val (maxBounds, pos) = getVec3 (data, pos)
137 :     val (localOrigin, pos) = getVec3 (data, pos)
138 :     val (radius, pos) = getF32 (data, pos)
139 :     val (name, pos) = getString (data, pos, 16)
140 : jhr 394 (*+DEBUG
141 : jhr 388 val _ = print(concat[
142 : jhr 389 "frame: name = \"", name, "\"; bbox = (", v2s minBounds, ", ", v2s maxBounds, ")\n",
143 :     " origin = ", v2s localOrigin, "\n"
144 : jhr 388 ])
145 : jhr 394 -DEBUG*)
146 : jhr 391 in MD3.FRAME{
147 : jhr 388 name = name,
148 :     minBounds = minBounds,
149 :     maxBounds = maxBounds,
150 :     localOrigin = localOrigin,
151 :     radius = radius
152 :     } end
153 :    
154 :     fun readTag inS = let
155 :     val data = readData(inS, 112)
156 :     val (name, pos) = getString (data, 0, 64)
157 :     val (origin, pos) = getVec3 (data, pos)
158 :     val (xAxis, pos) = getVec3 (data, pos)
159 :     val (yAxis, pos) = getVec3 (data, pos)
160 :     val (zAxis, pos) = getVec3 (data, pos)
161 : jhr 394 (*+DEBUG
162 :     val _ = print(concat["tag: name = \"", name, "\" at ", v2s origin, "\n"])
163 :     -DEBUG*)
164 : jhr 388 in MD3.TAG{
165 :     name = name,
166 :     frame = {
167 :     origin = origin,
168 :     onb = {u = xAxis, v = yAxis, w = zAxis}
169 :     }
170 :     } end
171 :    
172 :     (* read the header for a surface *)
173 :     fun readSurfaceHdr inS = let
174 :     val data = readData (inS, 108)
175 :     val (ident, pos) = getString (data, 0, 4) (* 0 *)
176 :     val (name, pos) = getString (data, pos, 64) (* 4 *)
177 :     val (flags, pos) = getS32 (data, pos) (* 68 *)
178 :     val (numFrames, pos) = getS32 (data, pos) (* 72 *)
179 :     val (numShaders, pos) = getS32 (data, pos) (* 76 *)
180 :     val (numVerts, pos) = getS32 (data, pos) (* 80 *)
181 :     val (numTris, pos) = getS32 (data, pos) (* 84 *)
182 :     val (trisOffset, pos) = getS32 (data, pos) (* 88 *)
183 :     val (shadersOffset, pos) = getS32 (data, pos) (* 92 *)
184 :     val (stsOffset, pos) = getS32 (data, pos) (* 96 *)
185 :     val (vertsOffset, pos) = getS32 (data, pos) (* 100 *)
186 :     val (endOffset, pos) = getS32 (data, pos) (* 104 *)
187 :     in
188 :     if (ident <> "IDP3")
189 :     then raise Fail "bogus magic number in surface"
190 :     else ();
191 :     { name = name,
192 :     numFrames = numFrames,
193 :     numShaders = numShaders,
194 :     numVerts = numVerts,
195 :     numTris = numTris,
196 :     trisOffset = Position.fromInt trisOffset,
197 :     shadersOffset = Position.fromInt shadersOffset,
198 :     stsOffset = Position.fromInt stsOffset,
199 :     vertsOffset = Position.fromInt vertsOffset,
200 :     endOffset = Position.fromInt endOffset
201 :     }
202 :     end
203 :    
204 :     fun readShader inS = let
205 :     val data = readData (inS, 68)
206 :     val (name, pos) = getString (data, 0, 64)
207 :     val (shader, pos) = getS32 (data, pos)
208 : jhr 394 (*+DEBUG
209 : jhr 388 val _ = print(concat["shader: name = \"", name, "\"\n"])
210 : jhr 402 -DEBUG*)
211 : jhr 388 in
212 :     MD3.SHADER{name = name, index = shader}
213 :     end
214 :    
215 :     (* read the vertex indices of a triangle *)
216 :     fun readTriangle inS = let
217 :     val data = readData (inS, 12)
218 :     val (v1, pos) = getU32 (data, 0)
219 :     val (v2, pos) = getU32 (data, pos)
220 :     val (v3, pos) = getU32 (data, pos)
221 :     in
222 :     (v1, v2, v3)
223 :     end
224 :    
225 :     fun readST inS = let
226 :     val data = readData (inS, 8)
227 :     val (s, pos) = getF32 (data, 0)
228 :     val (t, pos) = getF32 (data, pos)
229 :     in
230 :     (s, t)
231 :     end
232 :    
233 :     (* read the scaled coordinates and compressed normal of a vertex. The scaling
234 :     * factor is 1/64. The normal vector is represented as latitude:longitude (8-bits
235 :     * each). To convert a compressed normal "n" to a normal vector "(nx, ny, nz)",
236 :     * we compute the following:
237 :     *
238 :     * lat = (PI * ((n >> 8) & 0xFF)) / 128.0
239 :     * lng = (PI * (n & 0xFF) / 128.0
240 :     * nx = cos(lat) * sin(lng)
241 :     * ny = sin(lat) * sin(lng)
242 :     * nz = cos(lng)
243 :     *)
244 :     local
245 :     val pi_128 = Float.M_PI / 128.0
246 :     val scale : Float.float = 1.0 / 64.0
247 :     fun wordToFloat w = Float.fromInt(Word.toIntX w)
248 :     in
249 :     fun readVertex inS = let
250 :     val data = readData (inS, 8)
251 :     fun cvtCoord i = scale * Float.fromInt i
252 :     val (x, pos) = getS16 (data, 0)
253 :     val (y, pos) = getS16 (data, pos)
254 :     val (z, pos) = getS16 (data, pos)
255 :     val v = (cvtCoord x, cvtCoord y, cvtCoord z)
256 :     val (norm, pos) = getU16 (data, pos)
257 :     val lat = pi_128 * wordToFloat(Word.andb(Word.>>(norm, 0w8), 0wxff))
258 :     val lng = pi_128 * wordToFloat(Word.andb(norm, 0wxff))
259 :     val sinLng = Float.sin lng
260 :     val n = (Float.cos lat * sinLng, Float.sin lat * sinLng, Float.cos lng)
261 :     in
262 :     (v, n)
263 :     end
264 : jhr 402 end (* local *)
265 : jhr 388
266 : jhr 389 fun readSurface inS = let
267 : jhr 388 val start = filePos inS
268 :     val {name = name, numFrames, numShaders, numVerts, numTris,
269 :     trisOffset, shadersOffset, stsOffset, vertsOffset, endOffset
270 :     } = readSurfaceHdr inS
271 : jhr 394 (*+DEBUG
272 : jhr 388 val _ = print(concat["surface: name = \"", name, "\", #frames = ",
273 :     Int.toString numFrames, ", #shaders = ", Int.toString numShaders,
274 :     ", numVerts = ", Int.toString numVerts, ", #numTris = ", Int.toString numTris, "\n"
275 :     ])
276 : jhr 394 -DEBUG*)
277 : jhr 388 val () = seek (inS, start + shadersOffset)
278 :     val shaders = readN readShader (inS, numShaders)
279 :     val () = seek (inS, start + trisOffset)
280 :     val tris = let
281 :     val buf = DB.new(DB.sizeui, 3*numTris)
282 :     fun readTri i = let
283 :     val (v1, v2, v3) = readTriangle inS
284 :     val i = 3*i
285 :     in
286 : jhr 394 (*+DEBUG
287 : jhr 388 print(concat["T[", StringCvt.padLeft #" " 3 (Int.toString i), "] = (",
288 :     Word.fmt StringCvt.DEC v1, ", ",
289 :     Word.fmt StringCvt.DEC v2, ", ",
290 :     Word.fmt StringCvt.DEC v3, ")\n"]);
291 : jhr 394 -DEBUG*)
292 : jhr 388 DB.setui(buf, i, v1);
293 :     DB.setui(buf, i+1, v2);
294 :     DB.setui(buf, i+2, v3)
295 :     end
296 :     in
297 :     repeati readTri numTris;
298 :     buf
299 :     end
300 :     val () = seek (inS, start + stsOffset)
301 :     val texCoords = let
302 :     val buf = DB.new(DB.size2f, numVerts)
303 :     fun read i = DB.set2f (buf, i, readST inS)
304 :     in
305 :     repeati read numVerts;
306 :     buf
307 :     end
308 :     val () = seek (inS, start + vertsOffset)
309 :     val frames = let
310 : jhr 389 fun readFrame _ = let
311 : jhr 388 (* there are numVerts vertices per frame of animation *)
312 :     val vBuf = DB.new(DB.size3f, numVerts)
313 :     val nBuf = DB.new(DB.size3f, numVerts)
314 :     fun read i = let
315 :     val (v, n) = readVertex inS
316 :     in
317 : jhr 394 (*+DEBUG
318 : jhr 388 print(concat["V[", StringCvt.padLeft #" " 3 (Int.toString i), "] v = ",
319 : jhr 391 t2s v, ", n = ", t2s n, "\n"]);
320 : jhr 392 *)
321 : jhr 388 DB.set3f (vBuf, i, v);
322 : jhr 392 DB.set3f (nBuf, i, n)
323 : jhr 388 end
324 :     in
325 :     repeati read numVerts;
326 : jhr 389 MD3.MESH{verts = vBuf, norms = nBuf}
327 : jhr 388 end
328 :     in
329 : jhr 391 List.tabulate (numFrames, readFrame)
330 : jhr 388 end
331 :     val () = seek (inS, start + endOffset)
332 :     in
333 :     MD3.SURF{
334 :     name = name,
335 :     numVerts = numVerts,
336 :     shaders = shaders,
337 :     tris = tris,
338 :     texCoords = texCoords,
339 :     frames = frames
340 :     }
341 :     end
342 :    
343 :     fun load inS = let
344 :     val {name, numFrames, numTags, numSurfaces,
345 :     framesOffset, tagsOffset, surfsOffset
346 :     } = readHdr inS
347 : jhr 402 (*+DEBUG*)
348 : jhr 388 val () = print(concat["HDR: name = \"", name, "\", #frames = ",
349 :     Int.toString numFrames, ", #tags = ", Int.toString numTags,
350 :     ", #surfaces = ", Int.toString numSurfaces, "\n"
351 :     ])
352 : jhr 402 (*-DEBUG*)
353 : jhr 388 val () = seek (inS, framesOffset)
354 :     val frames = readN readFrame (inS, numFrames)
355 :     val () = seek (inS, tagsOffset)
356 : jhr 394 val tags = readN (fn inS => readN readTag (inS, numTags)) (inS, numFrames)
357 : jhr 388 val () = seek (inS, surfsOffset)
358 : jhr 391 val surfs = readN readSurface (inS, numSurfaces)
359 : jhr 388 in
360 :     MD3.MODEL{
361 :     tags = tags,
362 : jhr 389 surfs = surfs,
363 :     frames = frames
364 : jhr 388 }
365 :     end
366 :    
367 :     fun loadFile file = let
368 :     val inS = BinIO.openIn file
369 :     val model = (load inS) handle ex => (BinIO.closeIn inS; raise ex)
370 :     in
371 :     BinIO.closeIn inS;
372 :     model
373 :     end
374 :    
375 :     end

root@smlnj-gforge.cs.uchicago.edu
ViewVC Help
Powered by ViewVC 1.0.0