From bde3ec824d300e9aff11914406fb76dd9016e387 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Mon, 20 May 2019 02:03:41 +0300 Subject: [PATCH] Implement material maps for exporting objects with multiple materials This replaces the material texture feature which was removed earlier. The principle is similar (store material color values in a texture) but the maps are global, making it possible for multiple objects to use the same map. --- blender/io_mspgl/__init__.py | 2 +- blender/io_mspgl/export_material.py | 72 +++++++++++++++++++++++------ blender/io_mspgl/export_object.py | 50 ++++++++++++++++---- blender/io_mspgl/export_scene.py | 4 +- blender/io_mspgl/mesh.py | 29 +++++++++++- blender/io_mspgl/properties.py | 2 + blender/io_mspgl/util.py | 12 +++++ 7 files changed, 146 insertions(+), 25 deletions(-) diff --git a/blender/io_mspgl/__init__.py b/blender/io_mspgl/__init__.py index 133b844c..905bfe73 100644 --- a/blender/io_mspgl/__init__.py +++ b/blender/io_mspgl/__init__.py @@ -7,7 +7,7 @@ bl_info = { if "bpy" in locals(): import imp - for sub in "armature", "datafile", "export_armature", "export_material", "export_mesh", "export_object", "export_scene", "export_texture", "mesh", "properties", "util": + for sub in "armature", "datafile", "export_armature", "export_material", "export_mesh", "export_object", "export_scene", "export_texture", "material", "mesh", "properties", "util": if sub in locals(): imp.reload(locals()[sub]) diff --git a/blender/io_mspgl/export_material.py b/blender/io_mspgl/export_material.py index 46b571a9..0860c28b 100644 --- a/blender/io_mspgl/export_material.py +++ b/blender/io_mspgl/export_material.py @@ -1,18 +1,5 @@ import os -def linear_to_srgb(l): - if l<0.0031308: - return 12.92*l - else: - return 1.055*(l**(1/2.4))-0.055 - -def get_colormap(srgb): - if srgb: - return linear_to_srgb - else: - return lambda x: x - - class MaterialExporter: def __init__(self): self.single_file = True @@ -96,7 +83,9 @@ class MaterialExporter: mat_res = Resource(material.name+".mat") statements = mat_res.statements + from .util import get_colormap cm = get_colormap(material.srgb_colors) + if any(s.use_map_color_diffuse for s in material.texture_slots if s): statements.append(Statement("diffuse", 1.0, 1.0, 1.0, 1.0)) amb = cm(material.ambient) @@ -111,3 +100,60 @@ class MaterialExporter: statements.append(Statement("shininess", material.specular_hardness)) return mat_res + + +class MaterialMapExporter: + def __init__(self): + self.single_file = True + + def export_technique_resources(self, material_map, resources): + from .datafile import Resource, Statement, Token + diffuse_name = material_map.name+"_diffuse.tex2d" + if diffuse_name not in resources: + diffuse_res = Resource(diffuse_name) + + fmt = 'SRGB_ALPHA' if material_map.srgb_colors else 'RGBA' + + diffuse_res.statements.append(Statement("min_filter", Token('NEAREST'))) + diffuse_res.statements.append(Statement("mag_filter", Token('NEAREST'))) + diffuse_res.statements.append(Statement("storage", Token(fmt), *material_map.size)) + diffuse_res.statements.append(Statement("raw_data", material_map.diffuse_data)) + + resources[diffuse_name] = diffuse_res + + if "basic_white.mat" not in resources: + mat_res = Resource("basic_white.mat") + mat_res.statements.append(Statement("diffuse", 1.0, 1.0, 1.0, 1.0)) + + resources["basic_white.mat"] = mat_res + + def export_technique(self, material_map, *, resources=None): + from .datafile import Resource, Statement + tech_res = Resource(material_map.name+".tech") + + mat_res = resources["basic_white.mat"] + diffuse_res = resources[material_map.name+"_diffuse.tex2d"] + + if material_map.technique: + if self.single_file: + raise Exception("Can't export inherited technique to a single file") + + st = Statement("inherit", material_map.technique) + st.sub.append(tech_res.create_reference_statement("texture", "diffuse_map", diffuse_res)) + st.sub.append(tech_res.create_reference_statement("material", "surface", mat_res)) + tech_res.statements.append(st) + else: + st = Statement("pass", "") + if self.single_file: + st.sub.append(tech_res.create_embed_statement("material", mat_res)) + else: + st.sub.append(tech_res.create_reference_statement("material", mat_res)) + ss = Statement("texunit", 0) + if self.single_file: + ss.sub.append(tech_res.create_embed_statement("texture2d", diffuse_res)) + else: + ss.sub.append(tech_res.create_reference_statement("texture", diffuse_res)) + st.sub.append(ss) + tech_res.statements.append(st) + + return tech_res diff --git a/blender/io_mspgl/export_object.py b/blender/io_mspgl/export_object.py index ad76336b..cbd009a1 100644 --- a/blender/io_mspgl/export_object.py +++ b/blender/io_mspgl/export_object.py @@ -48,6 +48,12 @@ class ObjectExporter: material_export.use_textures = self.use_textures return material_export + def create_material_map_exporter(self): + from .export_material import MaterialMapExporter + material_map_export = MaterialMapExporter() + material_map_export.single_file = self.single_file + return material_map_export + def export_to_file(self, context, out_fn): obj = context.active_object @@ -82,24 +88,41 @@ class ObjectExporter: for s in obj_res.statements: s.write_to_file(out_file) - def export_object_resources(self, context, obj, resources, progress): + def export_object_resources(self, context, obj, resources, progress, material_maps=None): + if material_maps is None: + material_maps = {} + lods = self.collect_object_lods(obj) from .mesh import create_mesh_from_object + from .material import create_material_map mesh_export = self.create_mesh_exporter() material_export = self.create_material_exporter() + material_map_export = self.create_material_map_exporter() for i, l in enumerate(lods): lod_index = l.lod_index if l.lod_for_parent else 0 progress.push_task_slice("LOD {}".format(lod_index), i, len(lods)) - mesh_name = l.data.name+".mesh" - if mesh_name not in resources: - mesh = create_mesh_from_object(context, l, progress) - mesh_res = mesh_export.export_mesh(context, mesh, progress) - resources[mesh_name] = mesh_res + material_map = None + mapped_count = sum(m.material_map for m in l.data.materials) + if mapped_count: + material_map_tech = l.data.materials[0].technique + tech_mismatch = any(m.technique!=material_map_tech for m in l.data.materials) + if mapped_count!=len(l.data.materials) or tech_mismatch: + raise Exception("Conflicting settings in object materials") + + if material_map_tech in material_maps: + material_map = material_maps[material_map_tech] + else: + material_map = create_material_map(context, l.data.materials[0]) + material_maps[material_map_tech] = material_map - if l.material_slots and l.material_slots[0].material: + tech_name = "material_map_{}.tech".format(os.path.splitext(material_map_tech)[0]) + if tech_name not in resources: + material_map_export.export_technique_resources(material_map, resources) + resources[tech_name] = material_map_export.export_technique(material_map, resources=resources) + elif l.material_slots and l.material_slots[0].material: material = l.material_slots[0].material tech_name = material.name+".tech" if tech_name not in resources: @@ -108,6 +131,12 @@ class ObjectExporter: 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: + mesh = create_mesh_from_object(context, l, progress, material_map=material_map) + mesh_res = mesh_export.export_mesh(context, mesh, progress) + resources[mesh_name] = mesh_res + progress.pop_task() def export_object(self, context, obj, progress, *, resources=None): @@ -142,12 +171,15 @@ class ObjectExporter: if l.material_slots: material = l.material_slots[0].material if material: - tech_res = resources[material.name+".tech"] + if material.material_map: + tech_res = resources["material_map_{}.tech".format(material.technique)] + else: + tech_res = resources[material.name+".tech"] else: tech_res = resources["stub.tech"] if tech_res.name!=prev_tech: - if material and material.technique and not material.inherit_tech: + if material and not material.material_map and material.technique and not material.inherit_tech: lod_st.append(Statement("technique", material.technique)) elif not self.single_file: lod_st.append(obj_res.create_reference_statement("technique", tech_res)) diff --git a/blender/io_mspgl/export_scene.py b/blender/io_mspgl/export_scene.py index b32683cc..a0e41181 100644 --- a/blender/io_mspgl/export_scene.py +++ b/blender/io_mspgl/export_scene.py @@ -79,9 +79,11 @@ class SceneExporter: object_export = ObjectExporter() object_export.single_file = False + material_maps = {} + for i, o in enumerate(objs): progress.push_task_slice(o.name, i, len(objs)) - object_export.export_object_resources(context, o, resources, progress) + object_export.export_object_resources(context, o, resources, progress, material_maps=material_maps) obj_name = o.name+".object" resources[obj_name] = object_export.export_object(context, o, progress, resources=resources) progress.pop_task() diff --git a/blender/io_mspgl/mesh.py b/blender/io_mspgl/mesh.py index 95749f46..125bc5bb 100644 --- a/blender/io_mspgl/mesh.py +++ b/blender/io_mspgl/mesh.py @@ -393,6 +393,30 @@ class Mesh: for g in v.groups: g.group = group_index_map[g.group] + def apply_material_map(self, material_map): + for m in self.materials: + if m not in material_map.materials: + raise Exception("Material map is not compatible with Mesh") + + if self.use_uv=='NONE': + return + + layer = UvLayer("material_map") + if self.use_uv=='UNIT0': + self.uv_layers = [layer] + layer.unit = 0 + else: + self.uv_layers.append(layer) + used_units = [u.unit for u in self.uv_layers] + layer.unit = next(i for i in itertools.count() if i not in used_units) + self.uv_layers.sort(key=lambda u: u.unit) + + layer.uvs = [(0.0, 0.0)]*len(self.loops) + for f in self.faces: + uv = material_map.get_material_uv(self.materials[f.material_index]) + for i in f.loop_indices: + layer.uvs[i] = uv + def prepare_uv(self, progress): # Form a list of UV layers referenced by materials with the array atlas # property set @@ -740,7 +764,7 @@ class Mesh: self._mesh = None -def create_mesh_from_object(context, obj, progress): +def create_mesh_from_object(context, obj, progress, *, material_map=None): if obj.type!="MESH": raise Exception("Object is not a mesh") @@ -781,6 +805,9 @@ def create_mesh_from_object(context, obj, progress): mesh.name = obj.data.name + if material_map: + mesh.apply_material_map(material_map) + progress.set_task("Triangulating", 0.2, 0.3) mesh.prepare_triangles(progress) progress.set_task("Smoothing", 0.3, 0.5) diff --git a/blender/io_mspgl/properties.py b/blender/io_mspgl/properties.py index 91f3c56b..032bf8ed 100644 --- a/blender/io_mspgl/properties.py +++ b/blender/io_mspgl/properties.py @@ -72,6 +72,7 @@ class MspGLMaterialProperties(bpy.types.Panel): self.layout.prop(mat, "array_atlas") if mat.array_atlas: self.layout.prop(mat, "array_layer") + self.layout.prop(mat, "material_map") class MspGLTextureProperties(bpy.types.Panel): bl_idname = "TEXTURE_PT_mspgl_properties" @@ -118,5 +119,6 @@ def register_properties(): bpy.types.Material.srgb_colors = bpy.props.BoolProperty(name="sRGB colors", description="Export material colors as sRGB instead of linear", default=True) bpy.types.Material.array_atlas = bpy.props.BoolProperty(name="Texture array atlas", description="The material is stored in a texture array") bpy.types.Material.array_layer = bpy.props.IntProperty("Texture array layer", description="Layer of the texture array atlas to use") + bpy.types.Material.material_map = bpy.props.BoolProperty(name="Material map", description="Make this material part of a material map") bpy.types.Texture.default_filter = bpy.props.BoolProperty(name="Default filter", description="Let the loading program determine filtering options") diff --git a/blender/io_mspgl/util.py b/blender/io_mspgl/util.py index 7e932df3..70b005f6 100644 --- a/blender/io_mspgl/util.py +++ b/blender/io_mspgl/util.py @@ -52,6 +52,18 @@ class Progress: self.last = value +def linear_to_srgb(l): + if l<0.0031308: + return 12.92*l + else: + return 1.055*(l**(1/2.4))-0.055 + +def get_colormap(srgb): + if srgb: + return linear_to_srgb + else: + return lambda x: x + def image_name(img): fp = img.filepath if fp: -- 2.43.0