mirror of
https://github.com/nzp-team/tools.git
synced 2024-11-10 06:31:34 +00:00
415 lines
14 KiB
Python
415 lines
14 KiB
Python
# vim:ts=4:et
|
|
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
# <pep8 compliant>
|
|
|
|
from struct import unpack, pack
|
|
|
|
class MDL:
|
|
ST_SYNC = 0
|
|
ST_RAND = 1
|
|
SYNCTYPE={'ST_SYNC':ST_SYNC, 'ST_RAND':ST_RAND,
|
|
ST_SYNC:'ST_SYNC', ST_RAND:'ST_RAND'}
|
|
EF_ROCKET = 1
|
|
EF_GRENADE = 2
|
|
EF_GIB = 4
|
|
EF_ROTATE = 8
|
|
EF_TRACER = 16
|
|
EF_ZOMGIB = 32
|
|
EF_TRACER2 = 64
|
|
EF_TRACER3 = 128
|
|
EFFECTS={'EF_NONE':0, 'EF_ROCKET':EF_ROCKET, 'EF_GRENADE':EF_GRENADE,
|
|
'EF_GIB':EF_GIB, 'EF_TRACER':EF_TRACER, 'EF_ZOMGIB':EF_ZOMGIB,
|
|
'EF_TRACER2':EF_TRACER2, 'EF_TRACER3':EF_TRACER3}
|
|
|
|
PALETTE = {'PAL_QUAKE': 0, 'PAL_HEXEN2': 1}
|
|
|
|
class Skin:
|
|
def __init__(self):
|
|
self.name = ''
|
|
def info(self):
|
|
info={}
|
|
if self.type:
|
|
if self.times:
|
|
info['intervals'] = list(map(lambda t: str(t), self.times))
|
|
info['skins'] = []
|
|
for s in self.skins:
|
|
info['skins'].append(s.info())
|
|
if self.name:
|
|
info['name'] = self.name
|
|
return info
|
|
def read(self, mdl, sub=0):
|
|
self.width, self.height = mdl.skinwidth, mdl.skinheight
|
|
if sub:
|
|
self.type = 0
|
|
self.read_pixels(mdl)
|
|
return self
|
|
self.type = mdl.read_int()
|
|
if self.type:
|
|
# skin group
|
|
num = mdl.read_int()
|
|
self.times = mdl.read_float(num)
|
|
self.skins = []
|
|
for i in range(num):
|
|
self.skins.append(MDL.Skin().read(mdl, 1))
|
|
num -= 1
|
|
return self
|
|
self.read_pixels(mdl)
|
|
return self
|
|
def write(self, mdl, sub=0):
|
|
if not sub:
|
|
mdl.write_int(self.type)
|
|
if self.type:
|
|
mdl.write_int(len(self.skins))
|
|
mdl.write_float(self.times)
|
|
for subskin in self.skins:
|
|
subskin.write(mdl, 1)
|
|
return
|
|
mdl.write_bytes(self.pixels)
|
|
def read_pixels(self, mdl):
|
|
size = self.width * self.height
|
|
self.pixels = mdl.read_bytes(size)
|
|
|
|
class STVert:
|
|
def __init__(self, st=None, onseam=False):
|
|
if not st:
|
|
st = (0, 0)
|
|
self.onseam = onseam
|
|
self.s, self.t = st
|
|
pass
|
|
def read(self, mdl):
|
|
self.onseam = mdl.read_int()
|
|
self.s, self.t = mdl.read_int(2)
|
|
return self
|
|
def write(self, mdl):
|
|
mdl.write_int(self.onseam)
|
|
mdl.write_int((self.s, self.t))
|
|
|
|
class Tri:
|
|
def __init__(self, verts=None, facesfront=True):
|
|
if not verts:
|
|
verts = (0, 0, 0)
|
|
self.facesfront = facesfront
|
|
self.verts = verts
|
|
def read(self, mdl):
|
|
self.facesfront = mdl.read_int()
|
|
self.verts = mdl.read_int(3)
|
|
return self
|
|
def write(self, mdl):
|
|
mdl.write_int(self.facesfront)
|
|
mdl.write_int(self.verts)
|
|
|
|
class Frame:
|
|
def __init__(self):
|
|
self.type = 0
|
|
self.name = ""
|
|
self.mins = [0, 0, 0]
|
|
self.maxs = [0, 0, 0]
|
|
self.verts = []
|
|
self.frames = []
|
|
self.times = []
|
|
def info(self):
|
|
info={}
|
|
if self.type:
|
|
if self.times:
|
|
info['intervals'] = list(map(lambda t: str(t), self.times))
|
|
info['frames'] = []
|
|
for f in self.frames:
|
|
info['frames'].append(f.info())
|
|
if hasattr(self, 'frameno'):
|
|
info['frameno'] = str(self.frameno)
|
|
if self.name:
|
|
info['name'] = self.name
|
|
return info
|
|
def add_vert(self, vert):
|
|
self.verts.append(vert)
|
|
for i, v in enumerate(vert.r):
|
|
self.mins[i] = min(self.mins[i], v)
|
|
self.maxs[i] = max(self.maxs[i], v)
|
|
def add_frame(self, frame, time):
|
|
self.type = 1
|
|
self.frames.append(frame)
|
|
self.times.append(time)
|
|
for i in range(3):
|
|
self.mins[i] = min(self.mins[i], frame.mins[i])
|
|
self.maxs[i] = max(self.maxs[i], frame.maxs[i])
|
|
def scale(self, mdl):
|
|
self.mins = tuple(map(lambda x, s, t: int((x - t) / s),
|
|
self.mins, mdl.scale, mdl.scale_origin))
|
|
self.maxs = tuple(map(lambda x, s, t: int((x - t) / s),
|
|
self.maxs, mdl.scale, mdl.scale_origin))
|
|
if self.type:
|
|
for subframe in self.frames:
|
|
subframe.scale(mdl)
|
|
else:
|
|
for vert in self.verts:
|
|
vert.scale(mdl)
|
|
def read(self, mdl, numverts, sub=0):
|
|
if sub:
|
|
self.type = 0
|
|
else:
|
|
self.type = mdl.read_int()
|
|
if self.type:
|
|
num = mdl.read_int()
|
|
self.read_bounds(mdl)
|
|
self.times = mdl.read_float(num)
|
|
self.frames = []
|
|
for i in range(num):
|
|
self.frames.append(MDL.Frame().read(mdl, numverts, 1))
|
|
return self
|
|
self.read_bounds(mdl)
|
|
self.read_name(mdl)
|
|
self.read_verts(mdl, numverts)
|
|
return self
|
|
def write(self, mdl, sub=0):
|
|
if not sub:
|
|
mdl.write_int(self.type)
|
|
if self.type:
|
|
mdl.write_int(len(self.frames))
|
|
self.write_bounds(mdl)
|
|
mdl.write_float(self.times)
|
|
for frame in self.frames:
|
|
frame.write(mdl, 1)
|
|
return
|
|
self.write_bounds(mdl)
|
|
self.write_name(mdl)
|
|
self.write_verts(mdl)
|
|
def read_name(self, mdl):
|
|
if mdl.version == 6:
|
|
name = mdl.read_string(16)
|
|
else:
|
|
name = ""
|
|
if "\0" in name:
|
|
name = name[:name.index("\0")]
|
|
self.name = name
|
|
def write_name(self, mdl):
|
|
if mdl.version == 6:
|
|
mdl.write_string(self.name, 16)
|
|
def read_bounds(self, mdl):
|
|
self.mins = mdl.read_byte(4)[:3] #discard normal index
|
|
self.maxs = mdl.read_byte(4)[:3] #discard normal index
|
|
def write_bounds(self, mdl):
|
|
mdl.write_byte(self.mins + (0,))
|
|
mdl.write_byte(self.maxs + (0,))
|
|
def read_verts(self, mdl, num):
|
|
self.verts = []
|
|
for i in range(num):
|
|
self.verts.append(MDL.Vert().read(mdl))
|
|
if mdl.ident == 'MD16':
|
|
for i in range(num):
|
|
v = MDL.Vert().read(mdl)
|
|
r = tuple(map(lambda a, b: a + b / 256.0,
|
|
self.verts[i].r, v.r))
|
|
self.verts[i].r = r
|
|
def write_verts(self, mdl):
|
|
for vert in self.verts:
|
|
vert.write(mdl, True)
|
|
if mdl.ident == 'MD16':
|
|
for vert in self.verts:
|
|
vert.write(mdl, False)
|
|
def clamp_to_bounds(self, mins, maxs):
|
|
"""
|
|
Clamps all vertices and subframes to the bounds specified by mins/maxs
|
|
|
|
NOTE - This should be called _before_ Frame.scale(...)
|
|
|
|
Keyword Arguments:
|
|
mins -- (min x, min y, min z)
|
|
maxs -- (max x, max y, max z)
|
|
"""
|
|
|
|
clamp_to_mins = lambda x : tuple(map(max, zip(x, mins)))
|
|
clamp_to_maxs = lambda x : tuple(map(min, zip(x, maxs)))
|
|
clamp_to_bounds = lambda x : clamp_to_mins(clamp_to_maxs(x))
|
|
|
|
self.mins = clamp_to_bounds(self.mins)
|
|
self.maxs = clamp_to_bounds(self.maxs)
|
|
|
|
if self.type:
|
|
for subframe in self.frames:
|
|
subframe.clamp_to_bounds(mins,maxs)
|
|
else:
|
|
for vert in self.verts:
|
|
vert.r = clamp_to_bounds(vert.r)
|
|
|
|
class Vert:
|
|
def __init__(self, r=None, ni=0):
|
|
if not r:
|
|
r = (0, 0, 0)
|
|
self.r = r
|
|
self.ni = ni
|
|
pass
|
|
def read(self, mdl):
|
|
self.r = mdl.read_byte(3)
|
|
self.ni = mdl.read_byte()
|
|
return self
|
|
def write(self, mdl, high=True):
|
|
if mdl.ident == 'MD16' and not high:
|
|
r = tuple(map(lambda a: int(a * 256) & 255, self.r))
|
|
else:
|
|
r = tuple(map(lambda a: int(a) & 255, self.r))
|
|
mdl.write_byte(r)
|
|
mdl.write_byte(self.ni)
|
|
def scale(self, mdl):
|
|
self.r = tuple(map(lambda x, s, t: (x - t) / s,
|
|
self.r, mdl.scale, mdl.scale_origin))
|
|
|
|
def read_byte(self, count=1):
|
|
size = 1 * count
|
|
data = self.file.read(size)
|
|
data = unpack("<%dB" % count, data)
|
|
if count == 1:
|
|
return data[0]
|
|
return data
|
|
|
|
def read_int(self, count=1):
|
|
size = 4 * count
|
|
data = self.file.read(size)
|
|
data = unpack("<%di" % count, data)
|
|
if count == 1:
|
|
return data[0]
|
|
return data
|
|
|
|
def read_float(self, count=1):
|
|
size = 4 * count
|
|
data = self.file.read(size)
|
|
data = unpack("<%df" % count, data)
|
|
if count == 1:
|
|
return data[0]
|
|
return data
|
|
|
|
def read_bytes(self, size):
|
|
return self.file.read(size)
|
|
|
|
def read_string(self, size):
|
|
data = self.file.read(size)
|
|
s = ""
|
|
for c in data:
|
|
s = s + chr(c)
|
|
return s
|
|
|
|
def write_byte(self, data):
|
|
if not hasattr(data, "__len__"):
|
|
data = (data,)
|
|
self.file.write(pack(("<%dB" % len(data)), *data))
|
|
|
|
def write_int(self, data):
|
|
if not hasattr(data, "__len__"):
|
|
data = (data,)
|
|
self.file.write(pack(("<%di" % len(data)), *data))
|
|
|
|
def write_float(self, data):
|
|
if not hasattr(data, "__len__"):
|
|
data = (data,)
|
|
self.file.write(pack(("<%df" % len(data)), *data))
|
|
|
|
def write_bytes(self, data, size=-1):
|
|
if size == -1:
|
|
size = len(data)
|
|
self.file.write(data[:size])
|
|
if size > len(data):
|
|
self.file.write(bytes(size - len(data)))
|
|
|
|
def write_string(self, data, size=-1):
|
|
data = data.encode()
|
|
self.write_bytes(data, size)
|
|
|
|
def __init__(self, name = "mdl", md16 = False):
|
|
self.name = name
|
|
self.ident = md16 and "MD16" or "IDPO"
|
|
self.version = 6 #write only version 6 (nothing usable uses 3)
|
|
self.scale = (1.0, 1.0, 1.0) #FIXME
|
|
self.scale_origin = (0.0, 0.0, 0.0) #FIXME
|
|
self.boundingradius = 1.0 #FIXME
|
|
self.palette = 0
|
|
self.eyeposition = (0.0, 0.0, 0.0) #FIXME
|
|
self.synctype = MDL.ST_SYNC
|
|
self.flags = 0 #FIXME config
|
|
self.size = 0 #FIXME ???
|
|
self.skins = []
|
|
self.stverts = []
|
|
self.tris = []
|
|
self.frames = []
|
|
pass
|
|
def read(self, filepath):
|
|
self.file = open(filepath, "rb")
|
|
self.name = filepath.split('/')[-1]
|
|
self.name = self.name.split('.')[0]
|
|
self.ident = self.read_string(4)
|
|
self.version = self.read_int()
|
|
if self.ident not in ["IDPO", "MD16"] or self.version not in [3, 6]:
|
|
return None
|
|
self.scale = self.read_float(3)
|
|
self.scale_origin = self.read_float(3)
|
|
self.boundingradius = self.read_float()
|
|
self.eyeposition = self.read_float(3)
|
|
numskins = self.read_int()
|
|
self.skinwidth, self.skinheight = self.read_int(2)
|
|
numverts, numtris, numframes = self.read_int(3)
|
|
self.synctype = self.read_int()
|
|
if self.version == 6:
|
|
self.flags = self.read_int()
|
|
self.size = self.read_float()
|
|
# read in the skin data
|
|
self.skins = []
|
|
for i in range(numskins):
|
|
self.skins.append(MDL.Skin().read(self))
|
|
#read in the st verts (uv map)
|
|
self.stverts = []
|
|
for i in range(numverts):
|
|
self.stverts.append(MDL.STVert().read(self))
|
|
#read in the tris
|
|
self.tris = []
|
|
for i in range(numtris):
|
|
self.tris.append(MDL.Tri().read(self))
|
|
#read in the frames
|
|
self.frames = []
|
|
for i in range(numframes):
|
|
self.frames.append(MDL.Frame().read(self, numverts))
|
|
return self
|
|
|
|
def write(self, filepath):
|
|
self.file = open(filepath, "wb")
|
|
self.write_string(self.ident, 4)
|
|
self.write_int(self.version)
|
|
self.write_float(self.scale)
|
|
self.write_float(self.scale_origin)
|
|
self.write_float(self.boundingradius)
|
|
self.write_float(self.eyeposition)
|
|
self.write_int(len(self.skins))
|
|
self.write_int((self.skinwidth, self.skinheight))
|
|
self.write_int(len(self.stverts))
|
|
self.write_int(len(self.tris))
|
|
self.write_int(len(self.frames))
|
|
self.write_int(self.synctype)
|
|
if self.version == 6:
|
|
self.write_int(self.flags)
|
|
self.write_float(self.size)
|
|
# write out the skin data
|
|
for skin in self.skins:
|
|
skin.write(self)
|
|
#write out the st verts (uv map)
|
|
for stvert in self.stverts:
|
|
stvert.write(self)
|
|
#write out the tris
|
|
for tri in self.tris:
|
|
tri.write(self)
|
|
#write out the frames
|
|
for frame in self.frames:
|
|
frame.write(self)
|