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

SCM Repository

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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 404 - (download) (annotate)
Wed Jan 14 19:14:36 2009 UTC (10 years, 6 months ago) by jhr
File size: 11157 byte(s)
  Add pathname to representation
(* load-md3.sml
 *
 * COPYRIGHT (c) 2008 John Reppy (http://cs.uchicago.edu/~jhr)
 * All rights reserved.
 *
 * A loader for "Quake 3: Arena" model files (MD3 files).  This code is based on the
 * description at
 *
 *	http://icculus.org/homepages/phaethon/q3a/formats/md3format.html
 *)

structure LoadMD3 : sig

    val loadFile : string -> MD3.model

  end = struct

    structure W8V = Word8Vector
    structure DB = DataBuffer

  (* seek to the given position in a binary input stream *)
    fun seek (inS, pos) = let
	  val (rd, _) = BinIO.StreamIO.getReader (BinIO.getInstream inS)
	  val BinPrimIO.RD{setPos = SOME setPos, ...} = rd
	  in
	    setPos pos;
	    BinIO.setInstream (inS, BinIO.StreamIO.mkInstream(rd, W8V.fromList[]))
	  end

  (* return the current file position in a binary input stream *)
    fun filePos inS = let
	  val arg as (rd, _) = BinIO.StreamIO.getReader (BinIO.getInstream inS)
	  val BinPrimIO.RD{getPos = SOME getPos, ...} = rd
	  val pos = getPos ()
	  in
	    BinIO.setInstream (inS, BinIO.StreamIO.mkInstream arg);
	    pos
	  end

  (* read data from a binary input stream *)
    fun readData (inS, nbytes) = let
	  val v = BinIO.inputN (inS, nbytes)
	  in
	    if (W8V.length v <> nbytes) then raise Fail "missing data" else ();
	    v
	  end

    fun readN readSomething (inS, n) = let
	  fun read (0, l) = List.rev l
	    | read (n, l) = read (n-1, readSomething inS :: l)
	  in
	    read (n, [])
	  end

    fun repeati f n = let
	  fun iter i = if (i < n) then (f i; iter(i+1)) else ()
	  in
	    iter 0
	  end

  (* extract values from a byte vector *)
    fun getString (data, pos, sz) = let
	  fun strlen n = if (n < sz) andalso (W8V.sub(data, pos+n) <> 0w0)
		then strlen(n+1)
		else n
	  val len = strlen 0
	  in
	    (Byte.unpackStringVec (Word8VectorSlice.slice(data, pos, SOME len)), pos+sz)
	  end
    fun getS16 (data, pos) = let
	  val n = LargeWord.toIntX (PackWord16Little.subVecX(data, pos div 2))
	  in
	    (n, pos+2)
	  end
    fun getU16 (data, pos) = let
	  val n = Word.fromLarge (PackWord16Little.subVec(data, pos div 2))
	  in
	    (n, pos+2)
	  end
    fun getS32 (data, pos) = let
	  val n = LargeWord.toIntX (PackWord32Little.subVecX(data, pos div 4))
	  in
	    (n, pos+4)
	  end
    fun getU32 (data, pos) = let
	  val n = Word.fromLarge (PackWord32Little.subVecX(data, pos div 4))
	  in
	    (n, pos+4)
	  end
    fun getF32 (data, pos) = (PackReal32Little.subVec(data, pos div 4), pos+4)
    fun getVec3 (data, pos) = let
	  val (x, pos) = getF32(data, pos)
	  val (y, pos) = getF32(data, pos)
	  val (z, pos) = getF32(data, pos)
	  in
	    ({x=x, y=y, z=z}, pos)
	  end

(*+DEBUG
val t2s = SML3dTypeUtil.fmt3 Float.toString
val v2s = SML3dTypeUtil.fmtv3 Float.toString
-DEBUG*)

  (* read the MD3 header *)
    fun readHdr inS = let
	  val data = readData(inS, 108)
	  val (ident, pos) = getString (data, 0, 4)	(* 0 *)
	  val (version, pos) = getS32 (data, pos)	(* 4 *)
	  val (name, pos) = getString (data, pos, 64)	(* 8 *)
	  val (flags, pos) = getS32 (data, pos)		(* 72 *)
	  val (numFrames, pos) = getS32 (data, pos)	(* 76 *)
	  val (numTags, pos) = getS32 (data, pos)	(* 80 *)
	  val (numSurfaces, pos) = getS32 (data, pos)	(* 84 *)
	  val (numSkins, pos) = getS32 (data, pos)	(* 88 *)
	  val (framesOffset, pos) = getS32 (data, pos)	(* 92 *)
	  val (tagsOffset, pos) = getS32 (data, pos)	(* 96 *)
	  val (surfsOffset, pos) = getS32 (data, pos)	(* 100 *)
	  val (eofOffset, pos) = getS32 (data, pos)	(* 104 *)
	  in
	    if (ident <> "IDP3")
	      then raise Fail "bogus magic number"
	      else ();
	    { name = name,
	      numFrames = numFrames,
	      numTags = numTags,
	      numSurfaces = numSurfaces,
	      framesOffset = Position.fromInt framesOffset,
	      tagsOffset = Position.fromInt tagsOffset,
	      surfsOffset = Position.fromInt surfsOffset
	    }
	  end

    fun readFrame inS = let
	  val data = readData(inS, 56)
	  val (minBounds, pos) = getVec3 (data, 0)
	  val (maxBounds, pos) = getVec3 (data, pos)
	  val (localOrigin, pos) = getVec3 (data, pos)
	  val (radius, pos) = getF32 (data, pos)
	  val (name, pos) = getString (data, pos, 16)
(*+DEBUG
val _ = print(concat[
	"frame: name = \"", name, "\"; bbox = (", v2s minBounds, ", ", v2s maxBounds, ")\n",
	"       origin = ", v2s localOrigin, "\n"
      ])
-DEBUG*)
	  in MD3.FRAME{
	    name = name,
	    minBounds = minBounds,
	    maxBounds = maxBounds,
	    localOrigin = localOrigin,
	    radius = radius
	  } end

    fun readTag inS = let
	  val data = readData(inS, 112)
	  val (name, pos) = getString (data, 0, 64)
	  val (origin, pos) = getVec3 (data, pos)
	  val (xAxis, pos) = getVec3 (data, pos)
	  val (yAxis, pos) = getVec3 (data, pos)
	  val (zAxis, pos) = getVec3 (data, pos)
(*+DEBUG
val _ = print(concat["tag: name = \"", name, "\" at ", v2s origin, "\n"])
-DEBUG*)
	  in MD3.TAG{
	    name = name,
	    frame = {
		origin = origin,
		onb = {u = xAxis, v = yAxis, w = zAxis}
	      }
	  } end

  (* read the header for a surface *)
    fun readSurfaceHdr inS = let
	  val data = readData (inS, 108)
	  val (ident, pos) = getString (data, 0, 4)	(* 0 *)
	  val (name, pos) = getString (data, pos, 64)	(* 4 *)
	  val (flags, pos) = getS32 (data, pos)		(* 68 *)
	  val (numFrames, pos) = getS32 (data, pos)	(* 72 *)
	  val (numShaders, pos) = getS32 (data, pos)	(* 76 *)
	  val (numVerts, pos) = getS32 (data, pos)	(* 80 *)
	  val (numTris, pos) = getS32 (data, pos)	(* 84 *)
	  val (trisOffset, pos) = getS32 (data, pos)	(* 88 *)
	  val (shadersOffset, pos) = getS32 (data, pos)	(* 92 *)
	  val (stsOffset, pos) = getS32 (data, pos)	(* 96 *)
	  val (vertsOffset, pos) = getS32 (data, pos)	(* 100 *)
	  val (endOffset, pos) = getS32 (data, pos)	(* 104 *)
	  in
	    if (ident <> "IDP3")
	      then raise Fail "bogus magic number in surface"
	      else ();
	    { name = name,
	      numFrames = numFrames,
	      numShaders = numShaders,
	      numVerts = numVerts,
	      numTris = numTris,
	      trisOffset = Position.fromInt trisOffset,
	      shadersOffset = Position.fromInt shadersOffset,
	      stsOffset = Position.fromInt stsOffset,
	      vertsOffset = Position.fromInt vertsOffset,
	      endOffset = Position.fromInt endOffset
	    }  
	  end

    fun readShader inS = let
	  val data = readData (inS, 68)
	  val (name, pos) = getString (data, 0, 64)
	  val (shader, pos) = getS32 (data, pos)
(*+DEBUG
val _ = print(concat["shader: name = \"", name, "\"\n"])
-DEBUG*)
	  in
	    MD3.SHADER{name = name, index = shader}
	  end

  (* read the vertex indices of a triangle *)
    fun readTriangle inS = let
	  val data = readData (inS, 12)
	  val (v1, pos) = getU32 (data, 0)
	  val (v2, pos) = getU32 (data, pos)
	  val (v3, pos) = getU32 (data, pos)
	  in
	    (v1, v2, v3)
	  end

    fun readST inS = let
	  val data = readData (inS, 8)
	  val (s, pos) = getF32 (data, 0)
	  val (t, pos) = getF32 (data, pos)
	  in
	    (s, t)
	  end

  (* read the scaled coordinates and compressed normal of a vertex.  The scaling
   * factor is 1/64.  The normal vector is represented as latitude:longitude (8-bits
   * each).  To convert a compressed normal "n" to a normal vector "(nx, ny, nz)",
   * we compute the following:
   *
   *	lat = (PI * ((n >> 8) & 0xFF)) / 128.0
   *	lng = (PI * (n & 0xFF) / 128.0
   *	nx = cos(lat) * sin(lng)
   *	ny = sin(lat) * sin(lng)
   *	nz = cos(lng)
   *)
    local
      val pi_128 = Float.M_PI / 128.0
      val scale : Float.float = 1.0 / 64.0
      fun wordToFloat w = Float.fromInt(Word.toIntX w)
    in
    fun readVertex inS = let
	  val data = readData (inS, 8)
	  fun cvtCoord i = scale * Float.fromInt i
	  val (x, pos) = getS16 (data, 0)
	  val (y, pos) = getS16 (data, pos)
	  val (z, pos) = getS16 (data, pos)
	  val v = (cvtCoord x, cvtCoord y, cvtCoord z)
	  val (norm, pos) = getU16 (data, pos)
	  val lat = pi_128 * wordToFloat(Word.andb(Word.>>(norm, 0w8), 0wxff))
	  val lng = pi_128 * wordToFloat(Word.andb(norm, 0wxff))
	  val sinLng = Float.sin lng
	  val n = (Float.cos lat * sinLng, Float.sin lat * sinLng, Float.cos lng)
	  in
	    (v, n)
	  end
    end (* local *)

    fun readSurface inS = let
	  val start = filePos inS
	  val {name = name, numFrames, numShaders, numVerts, numTris,
		  trisOffset, shadersOffset, stsOffset, vertsOffset, endOffset
		} = readSurfaceHdr inS
(*+DEBUG
val _ = print(concat["surface: name = \"", name, "\", #frames = ",
Int.toString numFrames, ", #shaders = ", Int.toString numShaders,
", numVerts = ", Int.toString numVerts, ", #numTris = ", Int.toString numTris, "\n"
])
-DEBUG*)
	  val () = seek (inS, start + shadersOffset)
	  val shaders = readN readShader (inS, numShaders)
	  val () = seek (inS, start + trisOffset)
	  val tris = let
		val buf = DB.new(DB.sizeui, 3*numTris)
		fun readTri i = let
		      val (v1, v2, v3) = readTriangle inS
		      val i = 3*i
		      in
(*+DEBUG
print(concat["T[", StringCvt.padLeft #" " 3 (Int.toString i), "] = (",
Word.fmt StringCvt.DEC v1, ", ",
Word.fmt StringCvt.DEC v2, ", ",
Word.fmt StringCvt.DEC v3, ")\n"]);
-DEBUG*)
			DB.setui(buf, i, v1);
			DB.setui(buf, i+1, v2);
			DB.setui(buf, i+2, v3)
		      end
		in
		  repeati readTri numTris;
		  buf
		end
	  val () = seek (inS, start + stsOffset)
	  val texCoords = let
		val buf = DB.new(DB.size2f, numVerts)
		fun read i = DB.set2f (buf, i, readST inS)
		in
		  repeati read numVerts;
		  buf
		end
	  val () = seek (inS, start + vertsOffset)
	  val frames = let
		fun readFrame _ = let
		    (* there are numVerts vertices per frame of animation *)
		      val vBuf = DB.new(DB.size3f, numVerts)
		      val nBuf = DB.new(DB.size3f, numVerts)
		      fun read i = let
			    val (v, n) = readVertex inS
			    in
(*+DEBUG
print(concat["V[", StringCvt.padLeft #" " 3 (Int.toString i), "] v = ",
t2s v, ", n = ", t2s n, "\n"]);
*)
			      DB.set3f (vBuf, i, v);
			      DB.set3f (nBuf, i, n)
			    end
		      in
			repeati read numVerts;
			MD3.MESH{verts = vBuf, norms = nBuf}
		      end
		in
		  List.tabulate (numFrames, readFrame)
		end
	  val () = seek (inS, start + endOffset)
	  in
	    MD3.SURF{
		name = name,
		numVerts  = numVerts,
		shaders = shaders,
		tris = tris,
		texCoords = texCoords,
		frames = frames
	      }
	  end

    fun load (path, inS) = let
	  val {name, numFrames, numTags, numSurfaces,
		  framesOffset, tagsOffset, surfsOffset
		} = readHdr inS
(*+DEBUG*)
val () = print(concat["HDR: name = \"", name, "\", #frames = ",
Int.toString numFrames, ", #tags = ", Int.toString numTags,
", #surfaces = ", Int.toString numSurfaces, "\n"
])
(*-DEBUG*)
	  val () = seek (inS, framesOffset)
	  val frames = readN readFrame (inS, numFrames)
	  val () = seek (inS, tagsOffset)
	  val tags = readN (fn inS => readN readTag (inS, numTags)) (inS, numFrames)
	  val () = seek (inS, surfsOffset)
	  val surfs = readN readSurface (inS, numSurfaces)
	  in
	    MD3.MODEL{
		path = path,
		name = name,
		tags = tags,
		surfs = surfs,
		frames = frames
	      }
	  end

    fun loadFile file = let
	  val inS = BinIO.openIn file
	  val model = (load (file, inS)) handle ex => (BinIO.closeIn inS; raise ex)
	  in
	    BinIO.closeIn inS;
	    model
	  end

  end

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