mirror of
https://github.com/ZDoom/Raze.git
synced 2024-12-15 15:11:41 +00:00
ebfb0b1ad6
not prompt you for shader paths, collapsed into a single file and to avoid transforming normals. This might break for rotations, but I don't think such transforms should happen with a straight import from ASE -> export to MD3. git-svn-id: https://svn.eduke32.com/eduke32@1839 1a8010ca-5511-0410-912e-c29ae57300e0
1219 lines
No EOL
31 KiB
Python
1219 lines
No EOL
31 KiB
Python
#!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:<br>
|
|
Surfaces, Materials and Tags.
|
|
|
|
Missing:<br>
|
|
None.
|
|
|
|
Known issues:<br>
|
|
None.
|
|
|
|
Notes:<br>
|
|
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 <phaethon@linux.ucla.edu> 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 <phaethon@linux.ucla.edu> 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") |