diff --git a/addons/io_scene_swbf_msh/__init__.py b/addons/io_scene_swbf_msh/__init__.py index 1425387..2ad9fe7 100644 --- a/addons/io_scene_swbf_msh/__init__.py +++ b/addons/io_scene_swbf_msh/__init__.py @@ -54,17 +54,11 @@ if "bpy" in locals(): import bpy from bpy_extras.io_utils import ExportHelper -from bpy.props import StringProperty, BoolProperty, EnumProperty +from bpy.props import BoolProperty, EnumProperty from bpy.types import Operator from .msh_scene import create_scene from .msh_scene_save import save_scene - -def write_some_data(context, filepath, use_some_setting): - with open(filepath, 'wb') as output_file: - save_scene(output_file, create_scene()) - - return {'FINISHED'} - +from .msh_material_properties import * # ExportHelper is a helper class, defines filename and # invoke() function which calls the file selector. @@ -101,24 +95,30 @@ class ExportSomeData(Operator, ExportHelper): ) def execute(self, context): - return write_some_data(context, self.filepath, self.use_setting) + with open(self.filepath, 'wb') as output_file: + save_scene(output_file, create_scene()) + return {'FINISHED'} # Only needed if you want to add into a dynamic menu def menu_func_export(self, context): self.layout.operator(ExportSomeData.bl_idname, text="SWBF msh (.msh)") - def register(): + bpy.utils.register_class(MaterialProperties) + bpy.utils.register_class(MaterialPropertiesPanel) bpy.utils.register_class(ExportSomeData) + bpy.types.TOPBAR_MT_file_export.append(menu_func_export) + bpy.types.Material.swbf_msh = bpy.props.PointerProperty(type=MaterialProperties) def unregister(): + bpy.utils.unregister_class(MaterialProperties) + bpy.utils.unregister_class(MaterialPropertiesPanel) bpy.utils.unregister_class(ExportSomeData) bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) - if __name__ == "__main__": register() diff --git a/addons/io_scene_swbf_msh/msh_material.py b/addons/io_scene_swbf_msh/msh_material.py new file mode 100644 index 0000000..ed7867b --- /dev/null +++ b/addons/io_scene_swbf_msh/msh_material.py @@ -0,0 +1,46 @@ +""" Contains Material and dependent types for representing materials easilly + saved to a .msh file. """ + +from dataclasses import dataclass +from typing import Tuple +from enum import Enum, Flag +from mathutils import Color + +class Rendertype(Enum): + # TODO: Add SWBF1 rendertypes. + NORMAL = 0 + SCROLLING = 3 + ENVMAPPED = 6 + ANIMATED = 7 + REFRACTION = 22 + BLINK = 25 + NORMALMAPPED_TILED = 24 + NORMALMAPPED_ENVMAPPED = 26 + NORMALMAPPED = 27 + NORMALMAPPED_TILED_ENVMAP = 29 + +class MaterialFlags(Flag): + NONE = 0 + UNLIT = 1 + GLOW = 2 + BLENDED_TRANSPARENCY = 4 + DOUBLESIDED = 8 + HARDEDGED_TRANSPARENCY = 16 + PERPIXEL = 32 + ADDITIVE_TRANSPARENCY = 64 + SPECULAR = 128 + +@dataclass +class Material: + """ Data class representing a .msh material. + Intended to be stored in a dictionary so name is missing. """ + + specular_color: Color = Color((1.0, 1.0, 1.0)) + rendertype: Rendertype = Rendertype.NORMAL + flags: MaterialFlags = MaterialFlags.NONE + data: Tuple[int, int] = (0, 0) + + texture0: str = "white.tga" + texture1: str = "" + texture2: str = "" + texture3: str = "" diff --git a/addons/io_scene_swbf_msh/msh_material_gather.py b/addons/io_scene_swbf_msh/msh_material_gather.py new file mode 100644 index 0000000..c3fdace --- /dev/null +++ b/addons/io_scene_swbf_msh/msh_material_gather.py @@ -0,0 +1,110 @@ +""" Gathers the Blender materials and returns them as a dictionary of + strings and Material objects. """ + +import bpy +from typing import Dict +from .msh_material import * + +def gather_materials() -> Dict[str, Material]: + """ Gathers the Blender materials and returns them as + a dictionary of strings and Material objects. """ + + materials: Dict[str, Material] = {} + + for blender_material in bpy.data.materials: + materials[blender_material.name] = read_material(blender_material) + + return materials + +def read_material(blender_material: bpy.types.Material) -> Material: + """ Reads a the swbf_msh properties from a Blender material and + returns a Material object. """ + + result = Material() + + if blender_material.swbf_msh is None: + return result + + props = blender_material.swbf_msh + + result.specular_color = props.specular_color.copy() + result.rendertype = _read_material_props_rendertype(props) + result.flags = _read_material_props_flags(props) + result.data = _read_material_props_data(props) + result.texture0 = props.diffuse_map + result.texture1 = _read_normal_map_or_distortion_map_texture(props) + result.texture2 = _read_detail_texture(props) + result.texture3 = _read_envmap_texture(props) + + return result + +_RENDERTYPES_MAPPING = { + "NORMAL_BF2": Rendertype.NORMAL, + "SCROLLING_BF2": Rendertype.SCROLLING, + "ENVMAPPED_BF2": Rendertype.ENVMAPPED, + "ANIMATED_BF2": Rendertype.ANIMATED, + "REFRACTION_BF2": Rendertype.REFRACTION, + "BLINK_BF2": Rendertype.BLINK, + "NORMALMAPPED_TILED_BF2": Rendertype.NORMALMAPPED_TILED, + "NORMALMAPPED_ENVMAPPED_BF2": Rendertype.NORMALMAPPED_ENVMAPPED, + "NORMALMAPPED_BF2": Rendertype.NORMALMAPPED, + "NORMALMAPPED_TILED_ENVMAPPED_BF2": Rendertype.NORMALMAPPED_TILED_ENVMAP} + +def _read_material_props_rendertype(props) -> Rendertype: + return _RENDERTYPES_MAPPING[props.rendertype] + +def _read_material_props_flags(props) -> MaterialFlags: + flags = MaterialFlags.NONE + + if props.blended_transparency: + flags |= MaterialFlags.BLENDED_TRANSPARENCY + if props.additive_transparency: + flags |= MaterialFlags.ADDITIVE_TRANSPARENCY + if props.hardedged_transparency: + flags |= MaterialFlags.HARDEDGED_TRANSPARENCY + if props.unlit: + flags |= MaterialFlags.UNLIT + if props.glow: + flags |= MaterialFlags.GLOW + if props.perpixel: + flags |= MaterialFlags.PERPIXEL + if props.specular: + flags |= MaterialFlags.SPECULAR + if props.doublesided: + flags |= MaterialFlags.DOUBLESIDED + + return flags + +def _read_material_props_data(props) -> Tuple[int, int]: + if "SCROLLING" in props.rendertype: + return (props.scroll_speed_u, props.scroll_speed_v) + if "BLINK" in props.rendertype: + return (props.blink_min_brightness, props.blink_speed) + if "NORMALMAPPED_TILED" in props.rendertype: + return (props.normal_map_tiling_u, props.normal_map_tiling_v) + if "REFRACTION" in props.rendertype: + return (0, 0) + if "ANIMATED" in props.rendertype: + return (int(str(props.animation_length).split("_")[1]), props.animation_speed) + + return (props.detail_map_tiling_u, props.detail_map_tiling_v) + +def _read_normal_map_or_distortion_map_texture(props) -> str: + if "REFRACTION" in props.rendertype: + return props.distortion_map + if "NORMALMAPPED" in props.rendertype: + return props.normal_map + + return "" + +def _read_detail_texture(props) -> str: + if "REFRACTION" in props.rendertype: + return "" + + return props.detail_map + +def _read_envmap_texture(props) -> str: + if "ENVMAPPED" not in props.rendertype: + return "" + + return props.environment_map diff --git a/addons/io_scene_swbf_msh/msh_material_properties.py b/addons/io_scene_swbf_msh/msh_material_properties.py new file mode 100644 index 0000000..81c8a3e --- /dev/null +++ b/addons/io_scene_swbf_msh/msh_material_properties.py @@ -0,0 +1,257 @@ +""" Contains Blender properties and UI for .msh materials. """ + +import bpy +from bpy.props import StringProperty, BoolProperty, EnumProperty, FloatVectorProperty, IntProperty +from bpy.types import PropertyGroup + +UI_MATERIAL_RENDERTYPES = ( + ('NORMAL_BF2', "00 Normal (SWBF2)", "Normal Material. Unlike you there is nothing inherently awesome " + "about this material. By default it has per-vertex diffuse " + "lighting and can also have a detail map."), + ('SCROLLING_BF2', "03 Scrolling (SWBF2)", "Scrolling Material"), + ('ENVMAPPED_BF2', "06 Envmapped (SWBF2)", "Envmapped Material"), + ('ANIMATED_BF2', "07 Animated (SWBF2)", "Animated Material"), + ('REFRACTION_BF2', "22 Refractive (SWBF2)", "Refractive Material"), + ('BLINK_BF2', "25 Blink (SWBF2)", "Blinking Material\n\n" + "Note: If you see any statues while using this material you " + "are advised **not** to blink (or take your eyes off the statue under any circumstances) " + "and immediately make your way to a crowded public space."), + ('NORMALMAPPED_TILED_BF2', "24 Normalmapped Tiled (SWBF2)", "Normalmapped Tiled Material"), + ('NORMALMAPPED_ENVMAPPED_BF2', "26 Normalmapped Envmapped (SWBF2)", "Normalmapped Envmapped Material"), + ('NORMALMAPPED_BF2', "27 Normalmapped (SWBF2)", "Normalmapped Material"), + ('NORMALMAPPED_TILED_ENVMAPPED_BF2', "26 Normalmapped Tiled Envmapped (SWBF2)", "Normalmapped Tiled Envmapped Material")) + +def _make_anim_length_entry(length): + from math import sqrt + len_sqrt = int(sqrt(length)) + + return ( + f'FRAMES_{length}', + f"{length} ({len_sqrt}x{len_sqrt})", + f"Input texture should be laid out as {len_sqrt}x{len_sqrt} frames.") + +UI_MATERIAL_ANIMATION_LENGTHS = ( + ('FRAMES_1', "1 (1x1)", "Why do you have an animated texture with one frame?"), + _make_anim_length_entry(4), + _make_anim_length_entry(9), + _make_anim_length_entry(16), + _make_anim_length_entry(25), + _make_anim_length_entry(36), + _make_anim_length_entry(49), + _make_anim_length_entry(64), + _make_anim_length_entry(81), + _make_anim_length_entry(100), + _make_anim_length_entry(121), + _make_anim_length_entry(144), + _make_anim_length_entry(169), + _make_anim_length_entry(196), + _make_anim_length_entry(225)) + +class MaterialProperties(PropertyGroup): + rendertype: EnumProperty(name="Rendertype", + description="Rendertype for the material.", + items=UI_MATERIAL_RENDERTYPES, + default='NORMAL_BF2') + + specular_color: FloatVectorProperty(name="Specular Colour", + description="Specular colour of the material. " + "Can be used to tint specular highlights " + "or reduce their strength.", + default=(1.0, 1.0, 1.0), + min=0.0, max=1.0, + soft_min=0.0, soft_max=1.0, + subtype="COLOR") + + blended_transparency: BoolProperty(name="Blended", + description="Enable blended transparency.", + default=False) + + additive_transparency: BoolProperty(name="Additive", + description="Enable additive transparency.", + default=False) + + hardedged_transparency: BoolProperty(name="Hardedged", + description="Enable hardedged (alpha cutout/clip) transparency " + "with a treshold of 0.5/0x80/128.", + default=False) + + unlit: BoolProperty(name="Unlit", + description="Makes the material unlit/emissive.", + default=False) + + glow: BoolProperty(name="Glow", + description="Same as 'Unlit' but also enables the use of a glowmap " + "in the diffuse texture's alpha channel. The material will be significantly " + "significantly brightened based on how opaque the glowmap is.", + default=False) + + perpixel: BoolProperty(name="Per-Pixel Lighting", + description="Use per-pixel lighting instead of per-vertex for diffuse lighting. " + "Be warned due to the way SWBFII handles per-pixel lighting this " + "adds an extra draw call for each segment using the material.", + default=False) + + specular: BoolProperty(name="Specular Lighting", + description="Use specular lighting as well as diffuse lighting. A gloss map " + "In the diffuse map's and normal map's alpha channel can be used " + "to attenuate the specular lighting's strength. (More transparent = less strong).\n\n" + + "Be warned due to the way SWBFII handles specular lighting this " + "adds an extra draw call for each segment using the material.", + default=False) + + doublesided: BoolProperty(name="Doublesided", + description="Disable backface culling, " + "causing both sides of the material to be rasterized.", + default=False) + + detail_map_tiling_u: IntProperty(name="Detail Map Tiling U", + description="Tiling of the detail map in the U direction. (0 = no tiling).", + default=0, + min=0, max=255, + soft_min=0, soft_max=255) + + detail_map_tiling_v: IntProperty(name="Detail Map Tiling V", + description="Tiling of the detail map in the V direction. (0 = no tiling).", + default=0, + min=0, max=255, + soft_min=0, soft_max=255) + + normal_map_tiling_u: IntProperty(name="Normal Map Tiling U", + description="Tiling of the normal map in the U direction. (0 = no tiling).", + default=0, + min=0, max=255, + soft_min=0, soft_max=255) + + normal_map_tiling_v: IntProperty(name="Normal Map Tiling V", + description="Tiling of the normal map in the V direction. (0 = no tiling).", + default=0, + min=0, max=255, + soft_min=0, soft_max=255) + + scroll_speed_u: IntProperty(name="Scroll Speed U", + description="Texture scroll speed in the U direction.", + default=0, + min=0, max=255, + soft_min=0, soft_max=255) + + scroll_speed_v: IntProperty(name="Scroll Speed V", + description="Texture scroll speed in the V direction.", + default=0, + min=0, max=255, + soft_min=0, soft_max=255) + + animation_length: EnumProperty(name="Animation Length", + description="Number of frames in the texture animation.", + items=UI_MATERIAL_ANIMATION_LENGTHS, + default='FRAMES_4') + + animation_speed: IntProperty(name="Animation Speed", + description="Animation speed in frames per second.", + default=1, + min=0, max=255, + soft_min=0, soft_max=255) + + blink_min_brightness: IntProperty(name="Blink Minimum Brightness", + description="Minimum brightness to blink between.", + default=0, + min=0, max=255, + soft_min=0, soft_max=255) + + blink_speed: IntProperty(name="Blink Speed", + description="Speed of blinking, higher is faster.", + default=0, + min=0, max=255, + soft_min=0, soft_max=255) + + diffuse_map: StringProperty(name="Diffuse Map", + description="The basic diffuse map for the material. The alpha channel " + "is either the transparency map, glow map or gloss map, " + "depending on the selected rendertype and flags.", + default="white.tga") + + detail_map: StringProperty(name="Detail Map", + description="Detail maps allow you to add in 'detail' to the diffuse " + "map at runtime. Or they can be used as fake ambient occlusion " + "maps or even wacky emissive maps. See docs for more details.") + + normal_map: StringProperty(name="Normal Map", + description="Normal maps can provide added detail from lighting. " + "If Specular is enabled the alpha channel will be " + "the gloss map.") + + environment_map: StringProperty(name="Environment Map", + description="Environment map for the material. Provides static " + "reflections around. Must be a cubemap.") + + distortion_map: StringProperty(name="Distortion Map", + description="Distortion maps control how Refractive materials " + "distort the scene behind them. Should be a normal map " + "with '-forceformat v8u8' in it's '.tga.option' file.") + +class MaterialPropertiesPanel(bpy.types.Panel): + """ Creates a Panel in the Object properties window """ + bl_label = "SWBF .msh Properties" + bl_idname = "MATERIAL_PT_swbf_msh" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "material" + + def draw(self, context): + layout = self.layout + + material_props = context.material.swbf_msh + + layout.prop(material_props, "rendertype") + layout.prop(material_props, "specular_color") + + layout.label(text="Transparency Flags: ") + row = layout.row() + row.prop(material_props, "blended_transparency") + row.prop(material_props, "additive_transparency") + row.prop(material_props, "hardedged_transparency") + + layout.label(text="Material Flags: ") + row = layout.row() + row.prop(material_props, "unlit") + row.prop(material_props, "glow") + row = layout.row() + row.prop(material_props, "perpixel") + row.prop(material_props, "specular") + layout.prop(material_props, "doublesided") + + if "REFRACTION" not in material_props.rendertype: + layout.label(text="Material Data: ") + row = layout.row() + + if "SCROLLING" in material_props.rendertype: + row.prop(material_props, "scroll_speed_u") + row.prop(material_props, "scroll_speed_v") + elif "ANIMATED" in material_props.rendertype: + row.prop(material_props, "animation_length") + row = layout.row() + row.prop(material_props, "animation_speed") + elif "BLINK" in material_props.rendertype: + row.prop(material_props, "blink_min_brightness") + row.prop(material_props, "blink_speed") + elif "NORMALMAPPED_TILED" in material_props.rendertype: + row.prop(material_props, "normal_map_tiling_u") + row.prop(material_props, "normal_map_tiling_v") + elif "REFRACTION" not in material_props.rendertype: + row.prop(material_props, "detail_map_tiling_u") + row.prop(material_props, "detail_map_tiling_v") + + layout.label(text="Texture Maps: ") + layout.prop(material_props, "diffuse_map") + + if "REFRACTION" not in material_props.rendertype: + layout.prop(material_props, "detail_map") + + if "NORMALMAPPED" in material_props.rendertype: + layout.prop(material_props, "normal_map") + + if "ENVMAPPED" in material_props.rendertype: + layout.prop(material_props, "environment_map") + + if "REFRACTION" in material_props.rendertype: + layout.prop(material_props, "distortion_map") diff --git a/addons/io_scene_swbf_msh/msh_material_utilities.py b/addons/io_scene_swbf_msh/msh_material_utilities.py new file mode 100644 index 0000000..f390324 --- /dev/null +++ b/addons/io_scene_swbf_msh/msh_material_utilities.py @@ -0,0 +1,24 @@ +""" Utilities for operating on Material objects. """ + +from typing import Dict, List +from .msh_material import * +from .msh_model import * + +def remove_unused_materials(materials: Dict[str, Material], + models: List[Model]) -> Dict[str, Material]: + """ Given a dictionary of materials and a list of models + returns a dictionary containing only the materials that are used. """ + + filtered_materials: Dict[str, Material] = {} + + for model in models: + if model.geometry is None: + continue + + for segment in model.geometry: + if not segment.material_name: + continue + + filtered_materials[segment.material_name] = materials[segment.material_name] + + return filtered_materials diff --git a/addons/io_scene_swbf_msh/msh_scene.py b/addons/io_scene_swbf_msh/msh_scene.py index f6160d3..9e3cfd3 100644 --- a/addons/io_scene_swbf_msh/msh_scene.py +++ b/addons/io_scene_swbf_msh/msh_scene.py @@ -2,7 +2,7 @@ from a Blender scene. """ from dataclasses import dataclass, field -from typing import List +from typing import List, Dict from copy import copy import bpy from mathutils import Vector @@ -10,6 +10,9 @@ from .msh_model import Model from .msh_model_gather import gather_models from .msh_model_utilities import sort_by_parent, has_multiple_root_models, reparent_model_roots, get_model_world_matrix from .msh_model_triangle_strips import create_models_triangle_strips +from .msh_material import * +from .msh_material_gather import gather_materials +from .msh_material_utilities import remove_unused_materials from .msh_utilities import * @dataclass @@ -38,6 +41,7 @@ class SceneAABB: class Scene: """ Class containing the scene data for a .msh """ name: str = "Scene" + materials: Dict[str, Material] = field(default_factory=dict) models: List[Model] = field(default_factory=list) def create_scene() -> Scene: @@ -47,6 +51,8 @@ def create_scene() -> Scene: scene.name = bpy.context.scene.name + scene.materials = gather_materials() + scene.models = gather_models() scene.models = sort_by_parent(scene.models) scene.models = create_models_triangle_strips(scene.models) @@ -54,6 +60,8 @@ def create_scene() -> Scene: if has_multiple_root_models(scene.models): scene.models = reparent_model_roots(scene.models) + scene.materials = remove_unused_materials(scene.materials, scene.models) + return scene def create_scene_aabb(scene: Scene) -> SceneAABB: diff --git a/addons/io_scene_swbf_msh/msh_scene_save.py b/addons/io_scene_swbf_msh/msh_scene_save.py index 09f5758..bb14db0 100644 --- a/addons/io_scene_swbf_msh/msh_scene_save.py +++ b/addons/io_scene_swbf_msh/msh_scene_save.py @@ -1,8 +1,10 @@ """ Contains functions for saving a Scene to a .msh file. """ from itertools import islice +from typing import Dict from .msh_scene import Scene, create_scene_aabb from .msh_model import * +from .msh_material import * from .msh_writer import Writer from .msh_utilities import * @@ -15,12 +17,14 @@ def save_scene(output_file, scene: Scene): with msh2.create_child("SINF") as sinf: _write_sinf(sinf, scene) + material_index: Dict[str, int] = {} + with msh2.create_child("MATL") as matl: - _write_matl(matl, scene) + material_index = _write_matl_and_get_material_index(matl, scene) for index, model in enumerate(scene.models): with msh2.create_child("MODL") as modl: - _write_modl(modl, model, index) + _write_modl(modl, model, index, material_index) with hedr.create_child("CL1L"): pass @@ -44,30 +48,56 @@ def _write_sinf(sinf: Writer, scene: Scene): bbox.write_f32(bbox_position.x, bbox_position.y, bbox_position.z) bbox.write_f32(bbox_size.x, bbox_size.y, bbox_size.z, bbox_length) -def _write_matl(matl: Writer, scene: Scene): - # TODO: Material support. +def _write_matl_and_get_material_index(matl: Writer, scene: Scene): + material_index: Dict[str, int] = {} - matl.write_u32(1) # Material count. + if len(scene.materials) > 0: + matl.write_u32(len(scene.materials)) # Material count. - with matl.create_child("MATD") as matd: - with matd.create_child("NAME") as name: - name.write_string(f"{scene.name}Material") # TODO: Proper name with material support. + for index, name_material in enumerate(scene.materials.items()): + with matl.create_child("MATD") as matd: + material_index[name_material[0]] = index + _write_matd(matd, name_material[0], name_material[1]) + else: + matl.write_u32(1) # Material count. - with matd.create_child("DATA") as data: - data.write_f32(1.0, 1.0, 1.0, 1.0) # Diffuse Color (Seams to get ignored by modelmunge) - data.write_f32(1.0, 1.0, 1.0, 1.0) # Specular Color - data.write_f32(0.0, 0.0, 0.0, 1.0) # Ambient Color (Seams to get ignored by modelmunge and Zero(?)) - data.write_f32(50.0) # Specular Exponent/Decay (Gets ignored by RedEngine in SWBFII for all known materials) + default_material_name = f"{scene.name}Material" + material_index[default_material_name] = 0 - with matd.create_child("ATRB") as atrb: - atrb.write_u8(0) # Material Flags - atrb.write_u8(0) # Rendertype - atrb.write_u8(0, 0) # Rendertype Params (Scroll rate, animation divisors, etc) + with matl.create_child("MATD") as matd: + _write_matd(matd, default_material_name, Material()) - with matd.create_child("TX0D") as tx0d: - tx0d.write_string("null_detailmap.tga") + return material_index -def _write_modl(modl: Writer, model: Model, index: int): +def _write_matd(matd: Writer, material_name: str, material: Material): + with matd.create_child("NAME") as name: + name.write_string(material_name) + with matd.create_child("DATA") as data: + data.write_f32(1.0, 1.0, 1.0, 1.0) # Diffuse Color (Seams to get ignored by modelmunge) + data.write_f32(material.specular_color[0], material.specular_color[1], + material.specular_color[2], 1.0) + data.write_f32(0.0, 0.0, 0.0, 1.0) # Ambient Color (Seams to get ignored by modelmunge and Zero(?)) + data.write_f32(50.0) # Specular Exponent/Decay (Gets ignored by RedEngine in SWBFII for all known materials) + with matd.create_child("ATRB") as atrb: + atrb.write_u8(material.flags.value) + atrb.write_u8(material.rendertype.value) + atrb.write_u8(material.data[0], material.data[1]) + + with matd.create_child("TX0D") as tx0d: + tx0d.write_string(material.texture0) + if material.texture1 or material.texture2 or material.texture3: + with matd.create_child("TX1D") as tx1d: + tx1d.write_string(material.texture1) + + if material.texture2 or material.texture3: + with matd.create_child("TX2D") as tx2d: + tx2d.write_string(material.texture2) + + if material.texture3: + with matd.create_child("TX3D") as tx3d: + tx3d.write_string(material.texture3) + +def _write_modl(modl: Writer, model: Model, index: int, material_index: Dict[str, int]): with modl.create_child("MTYP") as mtyp: mtyp.write_u32(model.model_type.value) @@ -92,7 +122,7 @@ def _write_modl(modl: Writer, model: Model, index: int): with modl.create_child("GEOM") as geom: for segment in model.geometry: with geom.create_child("SEGM") as segm: - _write_segm(segm, segment) + _write_segm(segm, segment, material_index) # TODO: Collision Primitive @@ -101,10 +131,10 @@ def _write_tran(tran: Writer, transform: ModelTransform): tran.write_f32(transform.rotation.x, transform.rotation.y, transform.rotation.z, transform.rotation.w) tran.write_f32(transform.translation.x, transform.translation.y, transform.translation.z) -def _write_segm(segm: Writer, segment: GeometrySegment): +def _write_segm(segm: Writer, segment: GeometrySegment, material_index: Dict[str, int]): with segm.create_child("MATI") as mati: - mati.write_u32(0) + mati.write_u32(material_index.get(segment.material_name, 0)) with segm.create_child("POSL") as posl: posl.write_u32(len(segment.positions))