diff --git a/polymer/eduke32/build/src/util/md3_export.py b/polymer/eduke32/build/src/util/md3_export.py
new file mode 100644
index 000000000..6a21e4513
--- /dev/null
+++ b/polymer/eduke32/build/src/util/md3_export.py
@@ -0,0 +1,1219 @@
+#!BPY
+
+"""
+Name: 'Quake3 (.md3)...'
+Blender: 242
+Group: 'Export'
+Tooltip: 'Export to Quake3 file format. (.md3)'
+"""
+__author__ = "PhaethonH, Bob Holcomb, Damien McGinnes, Robert (Tr3B) Beckebans"
+__url__ = ("http://xreal.sourceforge.net")
+__version__ = "0.7 2006-11-12"
+
+__bpydoc__ = """\
+This script exports a Quake3 file (MD3).
+
+Supported:
+ Surfaces, Materials and Tags.
+
+Missing:
+ None.
+
+Known issues:
+ None.
+
+Notes:
+ TODO
+"""
+
+import sys, os, os.path, struct, string, math
+
+import Blender
+from Blender import *
+from Blender.Draw import *
+from Blender.BGL import *
+from Blender.Window import *
+
+import types
+
+import textwrap
+
+import logging
+reload(logging)
+
+import sys, struct, string, math
+from types import *
+
+import os
+from os import path
+
+GAMEDIR = 'D:/Games/XreaL_testing/base/'
+#GAMEDIR = '/opt/XreaL/base/'
+MAX_QPATH = 64
+
+import sys, struct, string, math
+from types import *
+
+import os
+from os import path
+
+import q_shared
+from q_shared import *
+
+MD3_IDENT = "IDP3"
+MD3_VERSION = 15
+MD3_MAX_TAGS = 16
+MD3_MAX_SURFACES = 32
+MD3_MAX_FRAMES = 1024
+MD3_MAX_SHADERS = 256
+MD3_MAX_VERTICES = 4096
+MD3_MAX_TRIANGLES = 8192
+MD3_XYZ_SCALE = (1.0 / 64.0)
+MD3_BLENDER_SCALE = (1.0 / 1.0)
+
+
+class md3Vert:
+ xyz = []
+ normal = 0
+ binaryFormat = "<3hh"
+
+ def __init__(self):
+ self.xyz = [0, 0, 0]
+ self.normal = 0
+
+ def GetSize(self):
+ return struct.calcsize(self.binaryFormat)
+
+ # copied from PhaethonH md3.py
+ def Decode(self, latlng):
+ lat = (latlng >> 8) & 0xFF;
+ lng = (latlng) & 0xFF;
+ lat *= math.pi / 128;
+ lng *= math.pi / 128;
+ x = math.cos(lat) * math.sin(lng)
+ y = math.sin(lat) * math.sin(lng)
+ z = math.cos(lng)
+ retval = [ x, y, z ]
+ return retval
+
+ # copied from PhaethonH md3.py
+ def Encode(self, normal):
+ x, y, z = normal
+
+ # normalise
+ l = math.sqrt((x*x) + (y*y) + (z*z))
+ if l == 0:
+ return 0
+ x = x/l
+ y = y/l
+ z = z/l
+
+ if (x == 0.0) & (y == 0.0) :
+ if z > 0.0:
+ return 0
+ else:
+ return (128 << 8)
+
+ # Encode a normal vector into a 16-bit latitude-longitude value
+ #lng = math.acos(z)
+ #lat = math.acos(x / math.sin(lng))
+ #retval = ((lat & 0xFF) << 8) | (lng & 0xFF)
+ lng = math.acos(z) * 255 / (2 * math.pi)
+ lat = math.atan2(y, x) * 255 / (2 * math.pi)
+ retval = ((int(lat) & 0xFF) << 8) | (int(lng) & 0xFF)
+ return retval
+
+ def Load(self, file):
+ tmpData = file.read(struct.calcsize(self.binaryFormat))
+ data = struct.unpack(self.binaryFormat, tmpData)
+ self.xyz[0] = data[0] * MD3_XYZ_SCALE
+ self.xyz[1] = data[1] * MD3_XYZ_SCALE
+ self.xyz[2] = data[2] * MD3_XYZ_SCALE
+ self.normal = data[3]
+ return self
+
+ def Save(self, file):
+ tmpData = [0] * 4
+ tmpData[0] = self.xyz[0] / MD3_XYZ_SCALE
+ tmpData[1] = self.xyz[1] / MD3_XYZ_SCALE
+ tmpData[2] = self.xyz[2] / MD3_XYZ_SCALE
+ tmpData[3] = self.normal
+ data = struct.pack(self.binaryFormat, tmpData[0], tmpData[1], tmpData[2], tmpData[3])
+ file.write(data)
+ #print "Wrote MD3 Vertex: ", data
+
+ def Dump(self):
+ log.info("MD3 Vertex")
+ log.info("X: %s", self.xyz[0])
+ log.info("Y: %s", self.xyz[1])
+ log.info("Z: %s", self.xyz[2])
+ log.info("Normal: %s", self.normal)
+ log.info("")
+
+class md3TexCoord:
+ u = 0.0
+ v = 0.0
+
+ binaryFormat = "<2f"
+
+ def __init__(self):
+ self.u = 0.0
+ self.v = 0.0
+
+ def GetSize(self):
+ return struct.calcsize(self.binaryFormat)
+
+ def Load(self, file):
+ tmpData = file.read(struct.calcsize(self.binaryFormat))
+ data = struct.unpack(self.binaryFormat, tmpData)
+ # for some reason quake3 texture maps are upside down, flip that
+ self.u = data[0]
+ self.v = 1.0 - data[1]
+ return self
+
+ def Save(self, file):
+ tmpData = [0] * 2
+ tmpData[0] = self.u
+ tmpData[1] = 1.0 - self.v
+ data = struct.pack(self.binaryFormat, tmpData[0], tmpData[1])
+ file.write(data)
+ #print "wrote MD3 texture coordinate structure: ", data
+
+ def Dump(self):
+ log.info("MD3 Texture Coordinates")
+ log.info("U: %s", self.u)
+ log.info("V: %s", self.v)
+ log.info("")
+
+
+class md3Triangle:
+ indexes = []
+
+ binaryFormat = "<3i"
+
+ def __init__(self):
+ self.indexes = [ 0, 0, 0 ]
+
+ def GetSize(self):
+ return struct.calcsize(self.binaryFormat)
+
+ def Load(self, file):
+ tmpData = file.read(struct.calcsize(self.binaryFormat))
+ data = struct.unpack(self.binaryFormat, tmpData)
+ self.indexes[0] = data[0]
+ self.indexes[1] = data[2] # reverse
+ self.indexes[2] = data[1] # reverse
+ return self
+
+ def Save(self, file):
+ tmpData = [0] * 3
+ tmpData[0] = self.indexes[0]
+ tmpData[1] = self.indexes[2] # reverse
+ tmpData[2] = self.indexes[1] # reverse
+ data = struct.pack(self.binaryFormat,tmpData[0], tmpData[1], tmpData[2])
+ file.write(data)
+ #print "wrote MD3 face structure: ",data
+
+ def Dump(self, log):
+ log.info("MD3 Triangle")
+ log.info("Indices: %s", self.indexes)
+ log.info("")
+
+
+class md3Shader:
+ name = ""
+ index = 0
+
+ binaryFormat = "<%dsi" % MAX_QPATH
+
+ def __init__(self):
+ self.name = ""
+ self.index = 0
+
+ def GetSize(self):
+ return struct.calcsize(self.binaryFormat)
+
+ def Load(self, file):
+ tmpData = file.read(struct.calcsize(self.binaryFormat))
+ data = struct.unpack(self.binaryFormat, tmpData)
+ self.name = asciiz(data[0])
+ self.index = data[1]
+ return self
+
+ def Save(self, file):
+ tmpData = [0] * 2
+ tmpData[0] = self.name
+ tmpData[1] = self.index
+ data = struct.pack(self.binaryFormat, tmpData[0], tmpData[1])
+ file.write(data)
+ #print "wrote MD3 shader structure: ",data
+
+ def Dump(self, log):
+ log.info("MD3 Shader")
+ log.info("Name: %s", self.name)
+ log.info("Index: %s", self.index)
+ log.info("")
+
+
+class md3Surface:
+ ident = ""
+ name = ""
+ flags = 0
+ numFrames = 0
+ numShaders = 0
+ numVerts = 0
+ numTriangles = 0
+ ofsTriangles = 0
+ ofsShaders = 0
+ ofsUV = 0
+ ofsVerts = 0
+ ofsEnd = 0
+ shaders = []
+ triangles = []
+ uv = []
+ verts = []
+
+ binaryFormat = "<4s%ds10i" % MAX_QPATH # 1 int, name, then 10 ints
+
+ def __init__(self):
+ self.ident = ""
+ self.name = ""
+ self.flags = 0
+ self.numFrames = 0
+ self.numShaders = 0
+ self.numVerts = 0
+ self.numTriangles = 0
+ self.ofsTriangles = 0
+ self.ofsShaders = 0
+ self.ofsUV = 0
+ self.ofsVerts = 0
+ self.ofsEnd
+ self.shaders = []
+ self.triangles = []
+ self.uv = []
+ self.verts = []
+
+ def GetSize(self):
+ sz = struct.calcsize(self.binaryFormat)
+ self.ofsTriangles = sz
+ for t in self.triangles:
+ sz += t.GetSize()
+ self.ofsShaders = sz
+ for s in self.shaders:
+ sz += s.GetSize()
+ self.ofsUV = sz
+ for u in self.uv:
+ sz += u.GetSize()
+ self.ofsVerts = sz
+ for v in self.verts:
+ sz += v.GetSize()
+ self.ofsEnd = sz
+ return self.ofsEnd
+
+ def Load(self, file, log):
+ # where are we in the file (for calculating real offsets)
+ ofsBegin = file.tell()
+ tmpData = file.read(struct.calcsize(self.binaryFormat))
+ data = struct.unpack(self.binaryFormat, tmpData)
+ self.ident = data[0]
+ self.name = asciiz(data[1])
+ self.flags = data[2]
+ self.numFrames = data[3]
+ self.numShaders = data[4]
+ self.numVerts = data[5]
+ self.numTriangles = data[6]
+ self.ofsTriangles = data[7]
+ self.ofsShaders = data[8]
+ self.ofsUV = data[9]
+ self.ofsVerts = data[10]
+ self.ofsEnd = data[11]
+
+ # load the tri info
+ file.seek(ofsBegin + self.ofsTriangles, 0)
+ for i in range(0, self.numTriangles):
+ self.triangles.append(md3Triangle())
+ self.triangles[i].Load(file)
+ #self.triangles[i].Dump(log)
+
+ # load the shader info
+ file.seek(ofsBegin + self.ofsShaders, 0)
+ for i in range(0, self.numShaders):
+ self.shaders.append(md3Shader())
+ self.shaders[i].Load(file)
+ #self.shaders[i].Dump(log)
+
+ # load the uv info
+ file.seek(ofsBegin + self.ofsUV, 0)
+ for i in range(0, self.numVerts):
+ self.uv.append(md3TexCoord())
+ self.uv[i].Load(file)
+ #self.uv[i].Dump(log)
+
+ # load the verts info
+ file.seek(ofsBegin + self.ofsVerts, 0)
+ for i in range(0, self.numFrames):
+ for j in range(0, self.numVerts):
+ self.verts.append(md3Vert())
+ #i*self.numVerts+j=where in the surface vertex list the vert position for this frame is
+ self.verts[(i * self.numVerts) + j].Load(file)
+ #self.verts[j].Dump(log)
+
+ # go to the end of this structure
+ file.seek(ofsBegin+self.ofsEnd, 0)
+
+ return self
+
+ def Save(self, file):
+ self.GetSize()
+ tmpData = [0] * 12
+ tmpData[0] = self.ident
+ tmpData[1] = self.name
+ tmpData[2] = self.flags
+ tmpData[3] = self.numFrames
+ tmpData[4] = self.numShaders
+ tmpData[5] = self.numVerts
+ tmpData[6] = self.numTriangles
+ tmpData[7] = self.ofsTriangles
+ tmpData[8] = self.ofsShaders
+ tmpData[9] = self.ofsUV
+ tmpData[10] = self.ofsVerts
+ tmpData[11] = self.ofsEnd
+ data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6],tmpData[7],tmpData[8],tmpData[9],tmpData[10],tmpData[11])
+ file.write(data)
+
+ # write the tri data
+ for t in self.triangles:
+ t.Save(file)
+
+ # save the shader coordinates
+ for s in self.shaders:
+ s.Save(file)
+
+ # save the uv info
+ for u in self.uv:
+ u.Save(file)
+
+ # save the verts
+ for v in self.verts:
+ v.Save(file)
+
+ def Dump(self, log):
+ log.info("MD3 Surface")
+ log.info("Ident: %s", self.ident)
+ log.info("Name: %s", self.name)
+ log.info("Flags: %s", self.flags)
+ log.info("Number of Frames: %s", self.numFrames)
+ log.info("Number of Shaders: %s", self.numShaders)
+ log.info("Number of Verts: %s", self.numVerts)
+ log.info("Number of Triangles: %s", self.numTriangles)
+ log.info("Offset to Triangles: %s", self.ofsTriangles)
+ log.info("Offset to Shaders: %s", self.ofsShaders)
+ log.info("Offset to UV: %s", self.ofsUV)
+ log.info("Offset to Verts: %s", self.ofsVerts)
+ log.info("Offset to end: %s", self.ofsEnd)
+ log.info("")
+
+
+class md3Tag:
+ name = ""
+ origin = []
+ axis = []
+
+ binaryFormat="<%ds3f9f" % MAX_QPATH
+
+ def __init__(self):
+ self.name = ""
+ self.origin = [0, 0, 0]
+ self.axis = [0, 0, 0, 0, 0, 0, 0, 0, 0]
+
+ def GetSize(self):
+ return struct.calcsize(self.binaryFormat)
+
+ def Load(self, file):
+ tmpData = file.read(struct.calcsize(self.binaryFormat))
+ data = struct.unpack(self.binaryFormat, tmpData)
+ self.name = asciiz(data[0])
+ self.origin[0] = data[1]
+ self.origin[1] = data[2]
+ self.origin[2] = data[3]
+ self.axis[0] = data[4]
+ self.axis[1] = data[5]
+ self.axis[2] = data[6]
+ self.axis[3] = data[7]
+ self.axis[4] = data[8]
+ self.axis[5] = data[9]
+ self.axis[6] = data[10]
+ self.axis[7] = data[11]
+ self.axis[8] = data[12]
+ return self
+
+ def Save(self, file):
+ tmpData = [0] * 13
+ tmpData[0] = self.name
+ tmpData[1] = float(self.origin[0])
+ tmpData[2] = float(self.origin[1])
+ tmpData[3] = float(self.origin[2])
+ tmpData[4] = float(self.axis[0])
+ tmpData[5] = float(self.axis[1])
+ tmpData[6] = float(self.axis[2])
+ tmpData[7] = float(self.axis[3])
+ tmpData[8] = float(self.axis[4])
+ tmpData[9] = float(self.axis[5])
+ tmpData[10] = float(self.axis[6])
+ tmpData[11] = float(self.axis[7])
+ tmpData[12] = float(self.axis[8])
+ data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6], tmpData[7], tmpData[8], tmpData[9], tmpData[10], tmpData[11], tmpData[12])
+ file.write(data)
+ #print "wrote MD3 Tag structure: ",data
+
+ def Dump(self, log):
+ log.info("MD3 Tag")
+ log.info("Name: %s", self.name)
+ log.info("Origin: %s", self.origin)
+ log.info("Axis: %s", self.axis)
+ log.info("")
+
+class md3Frame:
+ mins = 0
+ maxs = 0
+ localOrigin = 0
+ radius = 0.0
+ name = ""
+
+ binaryFormat="<3f3f3ff16s"
+
+ def __init__(self):
+ self.mins = [0, 0, 0]
+ self.maxs = [0, 0, 0]
+ self.localOrigin = [0, 0, 0]
+ self.radius = 0.0
+ self.name = ""
+
+ def GetSize(self):
+ return struct.calcsize(self.binaryFormat)
+
+ def Load(self, file):
+ tmpData = file.read(struct.calcsize(self.binaryFormat))
+ data = struct.unpack(self.binaryFormat, tmpData)
+ self.mins[0] = data[0]
+ self.mins[1] = data[1]
+ self.mins[2] = data[2]
+ self.maxs[0] = data[3]
+ self.maxs[1] = data[4]
+ self.maxs[2] = data[5]
+ self.localOrigin[0] = data[6]
+ self.localOrigin[1] = data[7]
+ self.localOrigin[2] = data[8]
+ self.radius = data[9]
+ self.name = asciiz(data[10])
+ return self
+
+ def Save(self, file):
+ tmpData = [0] * 11
+ tmpData[0] = self.mins[0]
+ tmpData[1] = self.mins[1]
+ tmpData[2] = self.mins[2]
+ tmpData[3] = self.maxs[0]
+ tmpData[4] = self.maxs[1]
+ tmpData[5] = self.maxs[2]
+ tmpData[6] = self.localOrigin[0]
+ tmpData[7] = self.localOrigin[1]
+ tmpData[8] = self.localOrigin[2]
+ tmpData[9] = self.radius
+ tmpData[10] = self.name
+ data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6],tmpData[7], tmpData[8], tmpData[9], tmpData[10])
+ file.write(data)
+ #print "wrote MD3 frame structure: ",data
+
+ def Dump(self, log):
+ log.info("MD3 Frame")
+ log.info("Min Bounds: %s", self.mins)
+ log.info("Max Bounds: %s", self.maxs)
+ log.info("Local Origin: %s", self.localOrigin)
+ log.info("Radius: %s", self.radius)
+ log.info("Name: %s", self.name)
+ log.info("")
+
+class md3Object:
+ # header structure
+ ident = "" # this is used to identify the file (must be IDP3)
+ version = 0 # the version number of the file (Must be 15)
+ name = ""
+ flags = 0
+ numFrames = 0
+ numTags = 0
+ numSurfaces = 0
+ numSkins = 0
+ ofsFrames = 0
+ ofsTags = 0
+ ofsSurfaces = 0
+ ofsEnd = 0
+ frames = []
+ tags = []
+ surfaces = []
+
+ binaryFormat="<4si%ds9i" % MAX_QPATH # little-endian (<), 17 integers (17i)
+
+ def __init__(self):
+ self.ident = 0
+ self.version = 0
+ self.name = ""
+ self.flags = 0
+ self.numFrames = 0
+ self.numTags = 0
+ self.numSurfaces = 0
+ self.numSkins = 0
+ self.ofsFrames = 0
+ self.ofsTags = 0
+ self.ofsSurfaces = 0
+ self.ofsEnd = 0
+ self.frames = []
+ self.tags = []
+ self.surfaces = []
+
+ def GetSize(self):
+ self.ofsFrames = struct.calcsize(self.binaryFormat)
+ self.ofsTags = self.ofsFrames
+ for f in self.frames:
+ self.ofsTags += f.GetSize()
+ self.ofsSurfaces += self.ofsTags
+ for t in self.tags:
+ self.ofsSurfaces += t.GetSize()
+ self.ofsEnd = self.ofsSurfaces
+ for s in self.surfaces:
+ self.ofsEnd += s.GetSize()
+ return self.ofsEnd
+
+ def Load(self, file, log):
+ tmpData = file.read(struct.calcsize(self.binaryFormat))
+ data = struct.unpack(self.binaryFormat, tmpData)
+
+ self.ident = data[0]
+ self.version = data[1]
+
+ if(self.ident != "IDP3" or self.version != 15):
+ log.error("Not a valid MD3 file")
+ log.error("Ident: %s", self.ident)
+ log.error("Version: %s", self.version)
+ Exit()
+
+ self.name = asciiz(data[2])
+ self.flags = data[3]
+ self.numFrames = data[4]
+ self.numTags = data[5]
+ self.numSurfaces = data[6]
+ self.numSkins = data[7]
+ self.ofsFrames = data[8]
+ self.ofsTags = data[9]
+ self.ofsSurfaces = data[10]
+ self.ofsEnd = data[11]
+
+ # load the frame info
+ file.seek(self.ofsFrames, 0)
+ for i in range(0, self.numFrames):
+ self.frames.append(md3Frame())
+ self.frames[i].Load(file)
+ #self.frames[i].Dump(log)
+
+ # load the tags info
+ file.seek(self.ofsTags, 0)
+ for i in range(0, self.numFrames):
+ for j in range(0, self.numTags):
+ tag = md3Tag()
+ tag.Load(file)
+ #tag.Dump(log)
+ self.tags.append(tag)
+
+ # load the surface info
+ file.seek(self.ofsSurfaces, 0)
+ for i in range(0, self.numSurfaces):
+ self.surfaces.append(md3Surface())
+ self.surfaces[i].Load(file, log)
+ self.surfaces[i].Dump(log)
+ return self
+
+ def Save(self, file):
+ self.GetSize()
+ tmpData = [0] * 12
+ tmpData[0] = self.ident
+ tmpData[1] = self.version
+ tmpData[2] = self.name
+ tmpData[3] = self.flags
+ tmpData[4] = self.numFrames
+ tmpData[5] = self.numTags
+ tmpData[6] = self.numSurfaces
+ tmpData[7] = self.numSkins
+ tmpData[8] = self.ofsFrames
+ tmpData[9] = self.ofsTags
+ tmpData[10] = self.ofsSurfaces
+ tmpData[11] = self.ofsEnd
+
+ data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6],tmpData[7], tmpData[8], tmpData[9], tmpData[10], tmpData[11])
+ file.write(data)
+
+ for f in self.frames:
+ f.Save(file)
+
+ for t in self.tags:
+ t.Save(file)
+
+ for s in self.surfaces:
+ s.Save(file)
+
+ def Dump(self, log):
+ log.info("Header Information")
+ log.info("Ident: %s", self.ident)
+ log.info("Version: %s", self.version)
+ log.info("Name: %s", self.name)
+ log.info("Flags: %s", self.flags)
+ log.info("Number of Frames: %s",self.numFrames)
+ log.info("Number of Tags: %s", self.numTags)
+ log.info("Number of Surfaces: %s", self.numSurfaces)
+ log.info("Number of Skins: %s", self.numSkins)
+ log.info("Offset Frames: %s", self.ofsFrames)
+ log.info("Offset Tags: %s", self.ofsTags)
+ log.info("Offset Surfaces: %s", self.ofsSurfaces)
+ log.info("Offset end: %s", self.ofsEnd)
+ log.info("")
+
+def asciiz(s):
+ n = 0
+ while(ord(s[n]) != 0):
+ n = n + 1
+ return s[0:n]
+
+# strips the slashes from the back of a string
+def StripPath(path):
+ for c in range(len(path), 0, -1):
+ if path[c-1] == "/" or path[c-1] == "\\":
+ path = path[c:]
+ break
+ return path
+
+# strips the model from path
+def StripModel(path):
+ for c in range(len(path), 0, -1):
+ if path[c-1] == "/" or path[c-1] == "\\":
+ path = path[:c]
+ break
+ return path
+
+# strips file type extension
+def StripExtension(name):
+ n = 0
+ best = len(name)
+ while(n != -1):
+ n = name.find('.',n+1)
+ if(n != -1):
+ best = n
+ name = name[0:best]
+ return name
+
+# strips gamedir
+def StripGamePath(name):
+ gamepath = GAMEDIR.replace( '\\', '/' )
+ namepath = name.replace( '\\', '/' )
+ if namepath[0:len(gamepath)] == gamepath:
+ namepath= namepath[len(gamepath):len(namepath)]
+ return namepath
+
+import sys, struct, string, math
+
+def ANGLE2SHORT(x):
+ return int((x * 65536 / 360) & 65535)
+
+def SHORT2ANGLE(x):
+ return x * (360.0 / 65536.0)
+
+def DEG2RAD(a):
+ return (a * math.pi) / 180.0
+
+def RAD2DEG(a):
+ return (a * 180.0) / math.pi
+
+def DotProduct(x, y):
+ return x[0] * y[0] + x[1] * y[1] + x[2] * y[2]
+
+def CrossProduct(a,b):
+ return [a[1]*b[2] - a[2]*b[1], a[2]*b[0]-a[0]*b[2], a[0]*b[1]-a[1]*b[0]]
+
+def VectorLength(v):
+ return math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])
+
+def VectorSubtract(a, b):
+ return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
+
+def VectorAdd(a, b):
+ return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
+
+def VectorCopy(v):
+ return [v[0], v[1], v[2]]
+
+def VectorInverse(v):
+ return [-v[0], -v[1], -v[2]]
+
+#define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2])
+#define VectorScale(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s))
+#define VectorMA(v, s, b, o) ((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s))
+
+def RadiusFromBounds(mins, maxs):
+ corner = [0, 0, 0]
+ a = 0
+ b = 0
+
+ for i in range(0, 3):
+ a = abs(mins[i])
+ b = abs(maxs[i])
+ if a > b:
+ corner[i] = a
+ else:
+ corner[i] = b
+
+ return VectorLength(corner)
+
+
+# NOTE: Tr3B - matrix is in column-major order
+def MatrixIdentity():
+ return [[1.0, 0.0, 0.0, 0.0],
+ [0.0, 1.0, 0.0, 0.0],
+ [0.0, 0.0, 1.0, 0.0],
+ [0.0, 0.0, 0.0, 1.0]]
+
+def MatrixFromAngles(pitch, yaw, roll):
+ sp = math.sin(DEG2RAD(pitch))
+ cp = math.cos(DEG2RAD(pitch))
+
+ sy = math.sin(DEG2RAD(yaw))
+ cy = math.cos(DEG2RAD(yaw))
+
+ sr = math.sin(DEG2RAD(roll))
+ cr = math.cos(DEG2RAD(roll))
+
+# return [[cp * cy, (sr * sp * cy + cr * -sy), (cr * sp * cy + -sr * -sy), 0.0],
+# [cp * sy, (sr * sp * sy + cr * cy), (cr * sp * sy + -sr * cy), 0.0],
+# [-sp, sr * cp, cr * cp, 0.0],
+# [0.0, 0.0, 0.0, 1.0]]
+
+ return [[cp * cy, cp * sy, -sp, 0.0],
+ [(sr * sp * cy + cr * -sy), (sr * sp * sy + cr * cy), sr * cp, 0.0],
+ [(cr * sp * cy + -sr * -sy), (cr * sp * sy + -sr * cy), cr * cp, 0.0],
+ [0.0, 0.0, 0.0, 1.0]]
+
+def MatrixTransformPoint(m, p):
+ return [m[0][0] * p[0] + m[1][0] * p[1] + m[2][0] * p[2] + m[3][0],
+ m[0][1] * p[0] + m[1][1] * p[1] + m[2][1] * p[2] + m[3][1],
+ m[0][2] * p[0] + m[1][2] * p[1] + m[2][2] * p[2] + m[3][2]]
+
+
+def MatrixTransformNormal(m, p):
+ return [m[0][0] * p[0] + m[1][0] * p[1] + m[2][0] * p[2],
+ m[0][1] * p[0] + m[1][1] * p[1] + m[2][1] * p[2],
+ m[0][2] * p[0] + m[1][2] * p[1] + m[2][2] * p[2]]
+
+def MatrixMultiply(b, a):
+ return [[
+ a[0][0] * b[0][0] + a[0][1] * b[1][0] + a[0][2] * b[2][0],
+ a[0][0] * b[0][1] + a[0][1] * b[1][1] + a[0][2] * b[2][1],
+ a[0][0] * b[0][2] + a[0][1] * b[1][2] + a[0][2] * b[2][2],
+ 0.0,
+ ],[
+ a[1][0] * b[0][0] + a[1][1] * b[1][0] + a[1][2] * b[2][0],
+ a[1][0] * b[0][1] + a[1][1] * b[1][1] + a[1][2] * b[2][1],
+ a[1][0] * b[0][2] + a[1][1] * b[1][2] + a[1][2] * b[2][2],
+ 0.0,
+ ],[
+ a[2][0] * b[0][0] + a[2][1] * b[1][0] + a[2][2] * b[2][0],
+ a[2][0] * b[0][1] + a[2][1] * b[1][1] + a[2][2] * b[2][1],
+ a[2][0] * b[0][2] + a[2][1] * b[1][2] + a[2][2] * b[2][2],
+ 0.0,
+ ],[
+ a[3][0] * b[0][0] + a[3][1] * b[1][0] + a[3][2] * b[2][0] + b[3][0],
+ a[3][0] * b[0][1] + a[3][1] * b[1][1] + a[3][2] * b[2][1] + b[3][1],
+ a[3][0] * b[0][2] + a[3][1] * b[1][2] + a[3][2] * b[2][2] + b[3][2],
+ 1.0,
+ ]]
+
+def MatrixSetupTransform(forward, left, up, origin):
+ return [[forward[0], forward[1], forward[2], origin[0]],
+ [left[0], left[1], left[2], origin[1]],
+ [up[0], up[1], up[2], origin[2]],
+ [0.0, 0.0, 0.0, 1.0]]
+
+# our own logger class. it works just the same as a normal logger except
+# all info messages get show.
+class Logger(logging.Logger):
+ def __init__(self, name,level = logging.NOTSET):
+ logging.Logger.__init__(self, name, level)
+
+ self.has_warnings = False
+ self.has_errors = False
+ self.has_critical = False
+
+ def info(self, msg, *args, **kwargs):
+ apply(self._log,(logging.INFO, msg, args), kwargs)
+
+ def warning(self, msg, *args, **kwargs):
+ logging.Logger.warning(self, msg, *args, **kwargs)
+ self.has_warnings = True
+
+ def error(self, msg, *args, **kwargs):
+ logging.Logger.error(self, msg, *args, **kwargs)
+ self.has_errors = True
+
+ def critical(self, msg, *args, **kwargs):
+ logging.Logger.critical(self, msg, *args, **kwargs)
+ self.has_errors = True
+
+# should be able to make this print to stdout in realtime and save MESSAGES
+# as well. perhaps also have a log to file option
+class LogHandler(logging.StreamHandler):
+ def __init__(self):
+ logging.StreamHandler.__init__(self, sys.stdout)
+
+ if "md3_export_log" not in Blender.Text.Get():
+ self.outtext = Blender.Text.New("md3_export_log")
+ else:
+ self.outtext = Blender.Text.Get('md3_export_log')
+ self.outtext.clear()
+
+ self.lastmsg = ''
+
+ def emit(self, record):
+ # print to stdout and to a new blender text object
+ msg = self.format(record)
+
+ if msg == self.lastmsg:
+ return
+
+ self.lastmsg = msg
+ self.outtext.write("%s\n" %msg)
+
+ logging.StreamHandler.emit(self, record)
+
+logging.setLoggerClass(Logger)
+log = logging.getLogger('md3_export')
+
+handler = LogHandler()
+formatter = logging.Formatter('%(levelname)s %(message)s')
+handler.setFormatter(formatter)
+
+log.addHandler(handler)
+# set this to minimum output level. eg. logging.DEBUG, logging.WARNING, logging.ERROR
+# logging.CRITICAL. logging.INFO will make little difference as these always get
+# output'd
+log.setLevel(logging.WARNING)
+
+
+class BlenderGui:
+ def __init__(self):
+ text = """A log has been written to a blender text window. Change this window type to
+a text window and you will be able to select the file md3_export_log."""
+
+ text = textwrap.wrap(text,40)
+ text += ['']
+
+ if log.has_critical:
+ text += ['There were critical errors!!!!']
+
+ elif log.has_errors:
+ text += ['There were errors!']
+
+ elif log.has_warnings:
+ text += ['There were warnings']
+
+ # add any more text before here
+ text.reverse()
+
+ self.msg = text
+
+ Blender.Draw.Register(self.gui, self.event, self.button_event)
+
+ def gui(self,):
+ quitbutton = Blender.Draw.Button("Exit", 1, 0, 0, 100, 20, "Close Window")
+
+ y = 35
+
+ for line in self.msg:
+ BGL.glRasterPos2i(10,y)
+ Blender.Draw.Text(line)
+ y+=15
+
+ def event(self,evt, val):
+ if evt == Blender.Draw.ESCKEY:
+ Blender.Draw.Exit()
+ return
+
+ def button_event(self,evt):
+ if evt == 1:
+ Blender.Draw.Exit()
+ return
+
+
+def ApplyTransform(vert, matrix):
+ return vert * matrix
+
+
+def UpdateFrameBounds(v, f):
+ for i in range(0, 3):
+ f.mins[i] = min(v[i], f.mins[i])
+ for i in range(0, 3):
+ f.maxs[i] = max(v[i], f.maxs[i])
+
+
+def UpdateFrameRadius(f):
+ f.radius = RadiusFromBounds(f.mins, f.maxs)
+
+
+def ProcessSurface(scene, blenderObject, md3, pathName, modelName):
+ # because md3 doesnt suppoort faceUVs like blender, we need to duplicate
+ # any vertex that has multiple uv coords
+
+ vertDict = {}
+ indexDict = {} # maps a vertex index to the revised index after duplicating to account for uv
+ vertList = [] # list of vertices ordered by revised index
+ numVerts = 0
+ uvList = [] # list of tex coords ordered by revised index
+ faceList = [] # list of faces (they index into vertList)
+ numFaces = 0
+
+ scene.makeCurrent()
+ Blender.Set("curframe", 1)
+ Blender.Window.Redraw()
+
+ # get the object (not just name) and the Mesh, not NMesh
+ mesh = blenderObject.getData(False, True)
+ matrix = blenderObject.getMatrix('worldspace')
+
+ surf = md3Surface()
+ surf.numFrames = md3.numFrames
+ surf.name = blenderObject.getName()
+ surf.ident = MD3_IDENT
+
+ # create shader for surface
+ surf.shaders.append(md3Shader())
+ surf.numShaders += 1
+ surf.shaders[0].index = 0
+
+ log.info("Materials: %s", mesh.materials)
+ # :P
+ #shaderpath=Blender.Draw.PupStrInput("shader path for "+blenderObject.name+":", "", MAX_QPATH )
+ shaderpath=""
+ if shaderpath == "" :
+ if not mesh.materials:
+ surf.shaders[0].name = pathName + blenderObject.name
+ else:
+ surf.shaders[0].name = pathName + mesh.materials[0].name
+ else:
+ if not mesh.materials:
+ surf.shaders[0].name = shaderpath + blenderObject.name
+ else:
+ surf.shaders[0].name = shaderpath + mesh.materials[0].name
+
+ # process each face in the mesh
+ for face in mesh.faces:
+
+ tris_in_this_face = [] #to handle quads and up...
+
+ # this makes a list of indices for each tri in this face. a quad will be [[0,1,1],[0,2,3]]
+ for vi in range(1, len(face.v)-1):
+ tris_in_this_face.append([0, vi, vi + 1])
+
+ # loop across each tri in the face, then each vertex in the tri
+ for this_tri in tris_in_this_face:
+ numFaces += 1
+ tri = md3Triangle()
+ tri_ind = 0
+ for i in this_tri:
+ # get the vertex index, coords and uv coords
+ index = face.v[i].index
+ v = face.v[i].co
+ if mesh.faceUV == True:
+ uv = (face.uv[i][0], face.uv[i][1])
+ elif mesh.vertexUV:
+ uv = (face.v[i].uvco[0], face.v[i].uvco[1])
+ else:
+ uv = (0.0, 0.0) # handle case with no tex coords
+
+
+ if vertDict.has_key((index, uv)):
+ # if we've seen this exact vertex before, simply add it
+ # to the tris list of vertex indices
+ tri.indexes[tri_ind] = vertDict[(index, uv)]
+ else:
+ # havent seen this tri before
+ # (or its uv coord is different, so we need to duplicate it)
+
+ vertDict[(index, uv)] = numVerts
+
+ # put the uv coord into the list
+ # (uv coord are directly related to each vertex)
+ tex = md3TexCoord()
+ tex.u = uv[0]
+ tex.v = uv[1]
+ uvList.append(tex)
+
+ tri.indexes[tri_ind] = numVerts
+
+ # now because we have created a new index,
+ # we need a way to link it to the index that
+ # blender returns for NMVert.index
+ if indexDict.has_key(index):
+ # already there - each of the entries against
+ # this key represents the same vertex with a
+ # different uv value
+ ilist = indexDict[index]
+ ilist.append(numVerts)
+ indexDict[index] = ilist
+ else:
+ # this is a new one
+ indexDict[index] = [numVerts]
+
+ numVerts += 1
+ tri_ind +=1
+ faceList.append(tri)
+
+ # we're done with faces and uv coords
+ for t in uvList:
+ surf.uv.append(t)
+
+ for f in faceList:
+ surf.triangles.append(f)
+
+ surf.numTriangles = len(faceList)
+ surf.numVerts = numVerts
+
+ # now vertices are stored as frames -
+ # all vertices for frame 1, all vertices for frame 2...., all vertices for frame n
+ # so we need to iterate across blender's frames, and copy out each vertex
+ for frameNum in range(1, md3.numFrames + 1):
+ Blender.Set("curframe", frameNum)
+ Blender.Window.Redraw()
+
+ m = NMesh.GetRawFromObject(blenderObject.name)
+
+ vlist = [0] * numVerts
+ for vertex in m.verts:
+ try:
+ vindices = indexDict[vertex.index]
+ except:
+ log.warning("Found a vertex in %s that is not part of a face", blenderObject.name)
+ continue
+
+ vTx = ApplyTransform(vertex.co, matrix)
+ nTx = ApplyTransform(vertex.no, matrix)
+ UpdateFrameBounds(vTx, md3.frames[frameNum - 1])
+ vert = md3Vert()
+ #vert.xyz = vertex.co[0:3]
+ #vert.normal = vert.Encode(vertex.no[0:3])
+ vert.xyz = vTx[0:3]
+ vert.normal = vert.Encode(vertex.no[0:3])
+ #print vertex.no
+ for ind in vindices: # apply the position to all the duplicated vertices
+ vlist[ind] = vert
+
+ UpdateFrameRadius(md3.frames[frameNum - 1])
+
+ for vl in vlist:
+ surf.verts.append(vl)
+
+ surf.Dump(log)
+ md3.surfaces.append(surf)
+ md3.numSurfaces += 1
+
+
+def Export(fileName):
+ if(fileName.find('.md3', -4) <= 0):
+ fileName += '.md3'
+
+ log.info("Starting ...")
+
+ log.info("Exporting MD3 format to: %s", fileName)
+
+ pathName = StripGamePath(StripModel(fileName))
+ log.info("Shader path name: %s", pathName)
+
+ modelName = StripExtension(StripPath(fileName))
+ log.info("Model name: %s", modelName)
+
+ md3 = md3Object()
+ md3.ident = MD3_IDENT
+ md3.version = MD3_VERSION
+
+ tagList = []
+
+ # get the scene
+ scene = Blender.Scene.getCurrent()
+ context = scene.getRenderingContext()
+
+ scene.makeCurrent()
+ md3.numFrames = Blender.Get("curframe")
+ Blender.Set("curframe", 1)
+
+ # create a bunch of blank frames, they'll be filled in by 'ProcessSurface'
+ for i in range(1, md3.numFrames + 1):
+ frame = md3Frame()
+ frame.name = "frame_" + str(i)
+ md3.frames.append(frame)
+
+ # export all selected objects
+ objlist = Blender.Object.GetSelected()
+
+ # process each object for the export
+ for obj in objlist:
+ # check if it's a mesh object
+ if obj.getType() == "Mesh":
+ log.info("Processing surface: %s", obj.name)
+ if len(md3.surfaces) == MD3_MAX_SURFACES:
+ log.warning("Hit md3 limit (%i) for number of surfaces, skipping ...", MD3_MAX_SURFACES, obj.getName())
+ else:
+ ProcessSurface(scene, obj, md3, pathName, modelName)
+ elif obj.getType() == "Empty": # for tags, we just put em in a list so we can process them all together
+ if obj.name[0:4] == "tag_":
+ log.info("Processing tag: %s", obj.name)
+ tagList.append(obj)
+ md3.numTags += 1
+ else:
+ log.info("Skipping object: %s", obj.name)
+
+
+ # work out the transforms for the tags for each frame of the export
+ for i in range(1, md3.numFrames + 1):
+
+ # needed to update IPO's value, but probably not the best way for that...
+ scene.makeCurrent()
+ Blender.Set("curframe", i)
+ Blender.Window.Redraw()
+ for tag in tagList:
+ t = md3Tag()
+ matrix = tag.getMatrix('worldspace')
+ t.origin[0] = matrix[3][0]
+ t.origin[1] = matrix[3][1]
+ t.origin[2] = matrix[3][2]
+
+ t.axis[0] = matrix[0][0]
+ t.axis[1] = matrix[0][1]
+ t.axis[2] = matrix[0][2]
+
+ t.axis[3] = matrix[1][0]
+ t.axis[4] = matrix[1][1]
+ t.axis[5] = matrix[1][2]
+
+ t.axis[6] = matrix[2][0]
+ t.axis[7] = matrix[2][1]
+ t.axis[8] = matrix[2][2]
+ t.name = tag.name
+ #t.Dump(log)
+ md3.tags.append(t)
+
+ # export!
+ file = open(fileName, "wb")
+ md3.Save(file)
+ file.close()
+ md3.Dump(log)
+
+def FileSelectorCallback(fileName):
+ Export(fileName)
+
+ BlenderGui()
+
+Blender.Window.FileSelector(FileSelectorCallback, "Export Quake3 MD3")
\ No newline at end of file