+import itertools
+
class MeshExporter:
- def export_mesh(self, ctx, mesh_or_obj):
+ def export_mesh(self, ctx, mesh_or_obj, *, submesh_index = -1):
from .mesh import Mesh, create_mesh_from_object
if type(mesh_or_obj)==Mesh:
mesh = create_mesh_from_object(task, mesh_or_obj)
from .datafile import Resource, Statement, Token
- resource = Resource(mesh.name+".mesh", "mesh")
+ name = mesh.name
+ if submesh_index>=0:
+ name += "_{}".format(submesh_index)
+ name += ".mesh"
+ resource = Resource(name, "mesh")
statements = resource.statements
task = ctx.task("Creating statements", 1.0)
st.append(Token("PADDING{}_UBYTE".format(pad)))
stride += pad
+ if submesh_index>=0:
+ vertex_indices = [v.index for v in itertools.chain.from_iterable(b.vertices for b in mesh.batches if b.material_index==submesh_index)]
+ first_index = min(vertex_indices)
+ last_index = max(vertex_indices)
+ else:
+ first_index = 0
+ last_index = len(mesh.vertices)-1
+
normal = None
color = None
uvs = [None]*len(mesh.uv_layers)
tan = None
group = None
weight = None
- for v in mesh.vertices:
+ for v in mesh.vertices[first_index:last_index+1]:
if v.normal!=normal:
st.sub.append(Statement("normal", *v.normal))
normal = v.normal
statements.append(st)
for b in mesh.batches:
+ if submesh_index>=0 and b.material_index!=submesh_index:
+ continue
+
st = Statement("batch", Token(b.primitive_type))
if b.primitive_type=="PATCHES":
st.sub.append(Statement("patch_size", b.patch_size))
for i in range(0, len(b.vertices), 32):
- st.sub.append(Statement("indices", *(v.index for v in b.vertices[i:i+32])))
+ st.sub.append(Statement("indices", *(v.index-first_index for v in b.vertices[i:i+32])))
statements.append(st)
task.set_progress(1.0)
return lods
+ def collect_submeshes(self, obj):
+ if not obj.material_slots:
+ return [(None, -1)]
+ elif obj.use_submeshes and len(obj.material_slots)>1:
+ return [(s.material, i) for i, s in enumerate(obj.material_slots)]
+ else:
+ return [(obj.material_slots[0].material, -1)]
+
def export_object_resources(self, ctx, obj, resources):
lods = self.collect_object_lods(obj)
mesh_export = MeshExporter()
material_export = MaterialExporter()
- ctx.set_slices(len(lods))
- for l in lods:
- lod_index = l.lod_index if l.lod_for_parent else 0
- task = ctx.next_slice("LOD {}".format(lod_index))
-
- if l.material_slots and l.material_slots[0].material:
- material = l.material_slots[0].material
- subtask = task.task(material, 0.1)
- if material.render_mode!='EXTERNAL':
- tech_name = material.name+".tech"
- if tech_name not in resources:
- material = Material(material)
- material_export.export_technique_resources(subtask, material, resources)
- resources[tech_name] = material_export.export_technique(material, resources)
- elif "stub.tech" not in resources:
- resources["stub.tech"] = self.export_stub_technique()
-
- mesh_name = l.data.name+".mesh"
- if mesh_name not in resources:
- subtask = task.task(l.data, 1.0)
- mesh = create_mesh_from_object(subtask, l)
- mesh_res = mesh_export.export_mesh(subtask, mesh)
- resources[mesh_name] = mesh_res
+ submeshes = []
+ for i, l in enumerate(lods):
+ submeshes.append((i, l, self.collect_submeshes(l)))
+
+ ctx.set_slices(sum(len(s[2]) for s in submeshes))
+ for i, l, s in submeshes:
+ for m, j in s:
+ label = "LOD {}".format(i)
+ if j>=0:
+ label += " submesh {}".format(j)
+ task = ctx.next_slice(label)
+
+ if m:
+ subtask = task.task(m, 0.1)
+ if m.render_mode!='EXTERNAL':
+ tech_name = m.name+".tech"
+ if tech_name not in resources:
+ material = Material(m)
+ material_export.export_technique_resources(subtask, material, resources)
+ resources[tech_name] = material_export.export_technique(material, resources)
+ elif "stub.tech" not in resources:
+ resources["stub.tech"] = self.export_stub_technique()
+
+ mesh_name = l.data.name
+ if j>=0:
+ mesh_name += "_{}".format(j)
+ mesh_name += ".mesh"
+
+ if mesh_name not in resources:
+ subtask = task.task(l.data, 1.0)
+ mesh = create_mesh_from_object(subtask, l)
+ mesh_res = mesh_export.export_mesh(subtask, mesh, submesh_index=j)
+ resources[mesh_name] = mesh_res
def export_object(self, obj, resources):
if obj.type!='MESH':
center, radius = compute_bounding_sphere([v.co for v in obj.data.vertices])
statements.append(Statement("bounding_sphere_hint", *center, radius))
- prev_mesh = None
- prev_tech = None
for i, l in enumerate(lods):
lod_st = []
- if l.data.name!=prev_mesh:
- mesh_res = resources[l.data.name+".mesh"]
- lod_st.append(obj_res.create_reference_statement("mesh", mesh_res))
+ for m, j in self.collect_submeshes(l):
+ sub_st = []
+
+ mesh_name = l.data.name
+ if j>=0:
+ mesh_name += "_{}".format(j)
+ mesh_name += ".mesh"
- prev_mesh = l.data.name
+ sub_st.append(obj_res.create_reference_statement("mesh", resources[mesh_name]))
- material = None
- if l.material_slots:
- material = l.material_slots[0].material
- if material:
- if material.render_mode=='EXTERNAL':
- tech_name = material.technique
+ if not m:
+ tech_name = "stub.tech"
+ elif m.render_mode=='EXTERNAL':
+ tech_name = m.technique
else:
- tech_name = material.name+".tech"
- else:
- tech_name = "stub.tech"
+ tech_name = m.name+".tech"
+
+ if m and m.render_mode=='EXTERNAL':
+ sub_st.append(Statement("technique", m.technique))
+ else:
+ sub_st.append(obj_res.create_reference_statement("technique", resources[tech_name]))
- if tech_name!=prev_tech:
- if material and material.render_mode=='EXTERNAL':
- lod_st.append(Statement("technique", material.technique))
+ if j>0:
+ st = Statement("submesh")
+ st.sub = sub_st
+ lod_st.append(st)
else:
- lod_st.append(obj_res.create_reference_statement("technique", resources[tech_name]))
- prev_tech = tech_name
+ lod_st += sub_st
if i>0:
- st = Statement("level_of_detail", i)
+ st = Statement("level_of_detail")
st.sub = lod_st
statements.append(st)
else:
class Batch:
def __init__(self, pt):
self.primitive_type = pt
+ self.material_index = -1
self.patch_size = 0
self.vertices = []
while len(v.groups)<self.max_groups_per_vertex:
v.groups.append(VertexGroup(None))
+ def prepare_submeshes(self, task):
+ if len(self.materials)<=1:
+ return
+
+ self.split_vertices(self.find_material_group, task)
+
def split_vertices(self, find_group_func, task, *args):
vertex_count = len(self.vertices)
for i in range(vertex_count):
return group
+ def find_material_group(self, vertex, face):
+ face.flag = True
+
+ group = [face]
+ for f in vertex.faces:
+ if not f.flag and f.material_index==face.material_index:
+ f.flag = True
+ group.append(f)
+
+ return group
+
def compute_normals(self, task):
for i, v in enumerate(self.vertices):
v.normal = mathutils.Vector()
def build_tristrip_sequence(self, task):
sequence = None
+ mat_index = 0
for i, f in enumerate(self.faces):
- if sequence:
+ if f.material_index!=mat_index:
+ sequence = None
+ elif sequence:
if len(sequence)==3:
# Rotate the first three vertices so that the new face can be added
if sequence[0] in f.vertices and sequence[1] not in f.vertices:
self.batches.append(Batch("TRIANGLE_STRIP"))
sequence = self.batches[-1].vertices
sequence += f.vertices
+ mat_index = f.material_index
+ self.batches[-1].material_index = mat_index
task.set_progress(i/len(self.faces))
def build_triangle_sequence(self, task):
- batch = Batch("TRIANGLES")
+ batch = None
+ mat_index = 0
for f in self.faces:
+ if not batch or f.material_index!=mat_index:
+ batch = Batch("TRIANGLES")
+ self.batches.append(batch)
+ mat_index = f.material_index
+ batch.material_index = mat_index
batch.vertices += f.vertices
- self.batches.append(batch)
def build_line_sequence(self):
batch = Batch("LINES")
face = None
reordered_faces = []
+ mat_index = 0
n_processed = 0
while 1:
if not face:
# the first iteration). Scan all faces for the highest score.
best_score = 0
for f in self.faces:
- if f.flag:
+ if f.flag or f.material_index!=mat_index:
continue
score = sum(vertex_info[v.index][0] for v in f.vertices)
face = f
if not face:
+ mat_index += 1
+ if mat_index<len(self.materials):
+ continue
break
reordered_faces.append(face)
best_score = 0
for v in cached_vertices:
for f in v.faces:
- if not f.flag:
+ if not f.flag and f.material_index==mat_index:
score = sum(vertex_info[fv.index][0] for fv in f.vertices)
if score>best_score:
best_score = score
task = ctx.task("Triangulating", 0.3)
mesh.prepare_triangles(task)
- task = ctx.task("Smoothing", 0.5)
+ task = ctx.task("Smoothing", 0.45)
mesh.prepare_smoothing(task)
if mesh.splat_sources:
- task = ctx.task("Splat weights", 0.6)
+ task = ctx.task("Splat weights", 0.55)
mesh.prepare_splat_weights(task)
else:
- task = ctx.task("Vertex groups", 0.6)
+ task = ctx.task("Vertex groups", 0.55)
mesh.prepare_vertex_groups(obj)
- task = ctx.task("Preparing UVs", 0.75)
+ task = ctx.task("Preparing UVs", 0.7)
mesh.prepare_uv(task)
- task = ctx.task("Preparing vertex colors", 0.85)
+ task = ctx.task("Preparing vertex colors", 0.8)
mesh.prepare_colors(task)
+ task = ctx.task("Preparing submeshes", 0.9)
+ mesh.prepare_submeshes(task)
task = ctx.task("Render sequence", 1.0)
mesh.prepare_sequence(task)
obj = context.active_object
self.layout.prop(obj, "compound")
+ self.layout.prop(obj, "use_submeshes")
self.layout.prop(obj, "lod_for_parent")
if obj.lod_for_parent:
self.layout.prop(obj, "lod_index")
bpy.types.Mesh.tangent_uvtex = bpy.props.StringProperty(name="Tangent UV layer", description="UV layer to use as basis for tangent vectors", default="")
bpy.types.Object.compound = bpy.props.BoolProperty(name="Compound with parent", description="Join this object to its parent when exporting")
+ bpy.types.Object.use_submeshes = bpy.props.BoolProperty(name="Use submeshes", description="Split the object's mesh into submeshes by material", default=True)
bpy.types.Object.lod_for_parent = bpy.props.BoolProperty(name="LoD for parent", description="This object is a level of detail for its parent")
bpy.types.Object.lod_index = bpy.props.IntProperty(name="LoD index", description="Index of the level of detail", min=1, max=16, default=1)