]> git.tdb.fi Git - libs/gl.git/commitdiff
Add capability to export submeshes for different materials
authorMikko Rasa <tdb@tdb.fi>
Sun, 7 Jul 2024 20:47:21 +0000 (23:47 +0300)
committerMikko Rasa <tdb@tdb.fi>
Sun, 7 Jul 2024 20:47:21 +0000 (23:47 +0300)
blender/io_mspgl/export_mesh.py
blender/io_mspgl/export_object.py
blender/io_mspgl/mesh.py
blender/io_mspgl/properties.py

index b0f186dbc04f6d65b850871169fe1db2e13e2e38..34b0fc50649013806e25c213c3d159ef61c12454 100644 (file)
@@ -1,5 +1,7 @@
+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:
@@ -9,7 +11,11 @@ class MeshExporter:
                        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)
@@ -53,13 +59,21 @@ class MeshExporter:
                        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
@@ -91,11 +105,14 @@ class MeshExporter:
                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)
index 989d675837485f121459d9bd130f3f4e6035e320..1f2a7cece3349185f7385b436751d55d2ea91fe6 100644 (file)
@@ -10,6 +10,14 @@ class ObjectExporter:
 
                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)
 
@@ -20,29 +28,39 @@ class ObjectExporter:
                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':
@@ -58,37 +76,40 @@ class ObjectExporter:
                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:
index c738b8f3ddaa51193fdbfdd0714fb87cfe14a4d0..25167a3a8a445d015da929cafbf07d2cbf53abf9 100644 (file)
@@ -81,6 +81,7 @@ class VertexGroup:
 class Batch:
        def __init__(self, pt):
                self.primitive_type = pt
+               self.material_index = -1
                self.patch_size = 0
                self.vertices = []
 
@@ -561,6 +562,12 @@ class Mesh:
                        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):
@@ -670,6 +677,17 @@ class Mesh:
 
                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()
@@ -740,8 +758,11 @@ class Mesh:
 
        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:
@@ -765,14 +786,21 @@ class Mesh:
                                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")
@@ -816,6 +844,7 @@ class Mesh:
                face = None
                reordered_faces = []
 
+               mat_index = 0
                n_processed = 0
                while 1:
                        if not face:
@@ -823,7 +852,7 @@ class Mesh:
                                # 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)
@@ -832,6 +861,9 @@ class Mesh:
                                                face = f
 
                        if not face:
+                               mat_index += 1
+                               if mat_index<len(self.materials):
+                                       continue
                                break
 
                        reordered_faces.append(face)
@@ -860,7 +892,7 @@ class Mesh:
                        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
@@ -972,18 +1004,20 @@ def create_mesh_from_object(ctx, obj):
 
        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)
 
index 274aba52bddcf6ce06bd6f04535ceaa4fbac1168..e7b036f532324218f14c3bc120660656bd5f6e8d 100644 (file)
@@ -81,6 +81,7 @@ class MspGLObjectProperties(bpy.types.Panel):
                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")
@@ -264,6 +265,7 @@ def register_properties():
        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)