2011-09-21 08:37:32 +00:00
|
|
|
# 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>
|
|
|
|
|
|
|
|
import bpy
|
|
|
|
from bpy_extras.object_utils import object_data_add
|
|
|
|
from mathutils import Vector,Matrix
|
|
|
|
|
2011-09-21 10:13:01 +00:00
|
|
|
from .quakepal import palette
|
|
|
|
from .mdl import MDL
|
2011-09-21 08:37:32 +00:00
|
|
|
|
2011-09-23 04:00:46 +00:00
|
|
|
def check_faces(mesh):
|
|
|
|
#Check that all faces are tris because mdl does not support anything else.
|
|
|
|
#Because the diagonal on which a quad is split can make a big difference,
|
|
|
|
#quad to tri conversion will not be done automatically.
|
2011-09-21 13:34:23 +00:00
|
|
|
faces_ok = True
|
|
|
|
save_select = []
|
|
|
|
for f in mesh.faces:
|
|
|
|
save_select.append(f.select)
|
|
|
|
f.select = False
|
|
|
|
if len(f.vertices) > 3:
|
|
|
|
f.select = True
|
|
|
|
faces_ok = False
|
|
|
|
if not faces_ok:
|
|
|
|
mesh.update()
|
2011-09-23 04:00:46 +00:00
|
|
|
return False
|
2011-09-21 13:34:23 +00:00
|
|
|
#reset selection to what it was before the check.
|
2011-09-21 15:58:57 +00:00
|
|
|
for f, s in map(lambda x, y: (x, y), mesh.faces, save_select):
|
2011-09-21 13:34:23 +00:00
|
|
|
f.select = s
|
2011-09-23 04:00:46 +00:00
|
|
|
mesh.update()
|
|
|
|
return True
|
|
|
|
|
|
|
|
def make_skin(mdl, mesh):
|
2011-09-21 15:58:57 +00:00
|
|
|
if (not mesh.uv_textures or not mesh.uv_textures[0].data
|
|
|
|
or not mesh.uv_textures[0].data[0].image):
|
|
|
|
mdl.skinwidth = mdl.skinheight = 4
|
|
|
|
skin = MDL.Skin()
|
|
|
|
skin.type = 0
|
|
|
|
skin.pixels = bytes(mdl.skinwidth * mdl.skinheight) # black skin
|
|
|
|
else:
|
|
|
|
image = mesh.uv_textures[0].data[0].image
|
|
|
|
mdl.skinwidth, mdl.skinheight = image.size
|
|
|
|
skin = MDL.Skin()
|
|
|
|
skin.type = 0
|
|
|
|
skin.pixels = bytearray(mdl.skinwidth * mdl.skinheight) # preallocate
|
2011-09-25 11:50:32 +00:00
|
|
|
cache = {}
|
|
|
|
pixels = image.pixels[:]
|
2011-09-21 15:58:57 +00:00
|
|
|
for y in range(mdl.skinheight):
|
|
|
|
for x in range(mdl.skinwidth):
|
2011-09-22 05:08:10 +00:00
|
|
|
outind = y * mdl.skinwidth + x
|
2011-09-21 15:58:57 +00:00
|
|
|
# quake textures are top to bottom, but blender images
|
|
|
|
# are bottom to top
|
2011-09-22 05:08:10 +00:00
|
|
|
inind = ((mdl.skinheight - 1 - y) * mdl.skinwidth + x) * 4
|
2011-09-25 11:50:32 +00:00
|
|
|
rgb = pixels[inind : inind + 3] # ignore alpha
|
2011-09-22 05:08:10 +00:00
|
|
|
rgb = tuple(map(lambda x: int(x * 255 + 0.5), rgb))
|
2011-09-25 11:50:32 +00:00
|
|
|
if rgb not in cache:
|
|
|
|
best = (3*256*256, -1)
|
|
|
|
for i, p in enumerate(palette):
|
|
|
|
if i > 255: # should never happen
|
|
|
|
break
|
|
|
|
r = 0
|
|
|
|
for x in map (lambda a, b: (a - b) ** 2, rgb, p):
|
|
|
|
r += x
|
|
|
|
if r < best[0]:
|
|
|
|
best = (r, i)
|
|
|
|
cache[rgb] = best[1]
|
|
|
|
skin.pixels[outind] = cache[rgb]
|
2011-09-21 15:58:57 +00:00
|
|
|
mdl.skins.append(skin)
|
2011-09-23 04:00:46 +00:00
|
|
|
|
2011-09-23 10:01:31 +00:00
|
|
|
def build_tris(mesh):
|
|
|
|
# mdl files have a 1:1 relationship between stverts and 3d verts.
|
|
|
|
# a bit sucky, but it does allow faces to take less memory
|
|
|
|
#
|
|
|
|
# modelgen's algorithm for generating UVs is very efficient in that no
|
|
|
|
# vertices are duplicated (thanks to the onseam flag), but it can result
|
|
|
|
# in fairly nasty UV layouts, and worse: the artist has no control over
|
|
|
|
# the layout. However, there seems to be nothing in the mdl format
|
|
|
|
# preventing the use of duplicate 3d vertices to allow complete freedom
|
|
|
|
# of the UV layout.
|
|
|
|
uvfaces = mesh.uv_textures[0].data
|
|
|
|
stverts = []
|
|
|
|
tris = []
|
|
|
|
vertmap = [] # map mdl vert num to blender vert num (for 3d verts)
|
|
|
|
uvdict = {}
|
|
|
|
for face, uvface in map(lambda a,b: (a,b), mesh.faces, uvfaces):
|
2011-09-24 00:00:14 +00:00
|
|
|
fv = list(face.vertices)
|
|
|
|
uv = list(uvface.uv)
|
|
|
|
# blender's and quake's vertex order seem to be opposed
|
|
|
|
fv.reverse()
|
|
|
|
uv.reverse()
|
2011-09-23 10:01:31 +00:00
|
|
|
tv = []
|
|
|
|
for v, u in map(lambda a,b: (a,b), fv, uv):
|
|
|
|
k = tuple(u)
|
|
|
|
if k not in uvdict:
|
|
|
|
uvdict[k] = len(stverts)
|
|
|
|
vertmap.append(v)
|
|
|
|
stverts.append(u)
|
|
|
|
tv.append(uvdict[k])
|
|
|
|
tris.append(MDL.Tri(tv))
|
|
|
|
return tris, stverts, vertmap
|
|
|
|
|
|
|
|
def convert_stverts(mdl, stverts):
|
|
|
|
for i, st in enumerate (stverts):
|
|
|
|
s, t = st
|
2011-09-23 10:58:26 +00:00
|
|
|
# quake textures are top to bottom, but blender images
|
|
|
|
# are bottom to top
|
2011-09-25 11:52:28 +00:00
|
|
|
s = int (s * (mdl.skinwidth - 1) + 0.5)
|
|
|
|
t = int ((1 - t) * (mdl.skinheight - 1) + 0.5)
|
2011-09-23 10:01:31 +00:00
|
|
|
# ensure st is within the skin
|
|
|
|
s = ((s % mdl.skinwidth) + mdl.skinwidth) % mdl.skinwidth
|
|
|
|
t = ((t % mdl.skinheight) + mdl.skinheight) % mdl.skinheight
|
|
|
|
stverts[i] = MDL.STVert ((s, t))
|
|
|
|
|
|
|
|
def make_frame(mesh, vertmap):
|
|
|
|
frame = MDL.Frame()
|
|
|
|
for v in vertmap:
|
|
|
|
vert = MDL.Vert(tuple(mesh.vertices[v].co))
|
|
|
|
frame.add_vert(vert)
|
|
|
|
return frame
|
|
|
|
|
|
|
|
def scale_verts(mdl):
|
|
|
|
tf = MDL.Frame()
|
|
|
|
for f in mdl.frames:
|
|
|
|
tf.add_frame(f, 0.0) # let the frame class do the dirty work for us
|
|
|
|
size = Vector(tf.maxs) - Vector(tf.mins)
|
2011-09-24 04:03:18 +00:00
|
|
|
rsqr = tuple(map(lambda a, b: max(abs(a), abs(b)) ** 2, tf.mins, tf.maxs))
|
|
|
|
mdl.boundingradius = (rsqr[0] + rsqr[1] + rsqr[2]) ** 0.5
|
2011-09-23 10:01:31 +00:00
|
|
|
mdl.scale_origin = tf.mins
|
|
|
|
mdl.scale = tuple(map(lambda x: x / 255.0, size))
|
|
|
|
for f in mdl.frames:
|
|
|
|
f.scale(mdl)
|
|
|
|
|
2011-09-24 04:18:23 +00:00
|
|
|
def calc_average_area(mdl):
|
|
|
|
frame = mdl.frames[0]
|
|
|
|
if frame.type:
|
|
|
|
frame = frame.frames[0]
|
|
|
|
totalarea = 0.0
|
|
|
|
for tri in mdl.tris:
|
|
|
|
verts = tuple(map(lambda i: frame.verts[i], tri.verts))
|
|
|
|
a = Vector(verts[0].r) - Vector(verts[1].r)
|
|
|
|
b = Vector(verts[2].r) - Vector(verts[1].r)
|
|
|
|
c = a.cross(b)
|
|
|
|
totalarea += (c * c) ** 0.5 / 2.0
|
|
|
|
return totalarea / len(mdl.tris)
|
|
|
|
|
2011-09-23 04:00:46 +00:00
|
|
|
def export_mdl(operator, context, filepath):
|
|
|
|
obj = context.active_object
|
2011-09-23 10:01:31 +00:00
|
|
|
mesh = obj.to_mesh (context.scene, True, 'PREVIEW') #wysiwyg?
|
2011-09-23 04:00:46 +00:00
|
|
|
if not check_faces (mesh):
|
|
|
|
operator.report({'ERROR'},
|
|
|
|
"Mesh has faces with more than 3 vertices.")
|
|
|
|
return {'CANCELLED'}
|
|
|
|
mdl = MDL(obj.name)
|
2011-09-27 22:41:20 +00:00
|
|
|
#FIXME this should be configurable
|
|
|
|
#FIXME use it
|
|
|
|
if mdl.name in bpy.data.texts:
|
|
|
|
mdl.script = bpy.data.texts[mdl.name].as_string()
|
2011-09-23 04:00:46 +00:00
|
|
|
make_skin(mdl, mesh)
|
2011-09-23 10:01:31 +00:00
|
|
|
mdl.tris, mdl.stverts, vertmap = build_tris(mesh)
|
|
|
|
convert_stverts (mdl, mdl.stverts)
|
|
|
|
mdl.frames.append(make_frame(mesh, vertmap))
|
2011-09-24 04:18:23 +00:00
|
|
|
mdl.size = calc_average_area(mdl)
|
2011-09-23 10:01:31 +00:00
|
|
|
scale_verts(mdl)
|
2011-09-23 04:00:46 +00:00
|
|
|
mdl.write(filepath)
|
2011-09-21 08:37:32 +00:00
|
|
|
return {'FINISHED'}
|