diff --git a/addons/io_scene_swbf_msh/msh_model.py b/addons/io_scene_swbf_msh/msh_model.py index 31f04a0..cdcc2e4 100644 --- a/addons/io_scene_swbf_msh/msh_model.py +++ b/addons/io_scene_swbf_msh/msh_model.py @@ -27,18 +27,25 @@ class ModelTransform: translation: Vector = field(default_factory=Vector) rotation: Quaternion = field(default_factory=Quaternion) +@dataclass +class VertexWeight: + """ Class representing a vertex weight in a .msh file. """ + + weight: float = 1.0 + bone: int = 0 + @dataclass class GeometrySegment: """ Class representing a 'SEGM' section in a .msh file. """ - material_name: str = "" + material_name: str = field(default_factory=str) positions: List[Vector] = field(default_factory=list) normals: List[Vector] = field(default_factory=list) colors: List[List[float]] = None texcoords: List[Vector] = field(default_factory=list) - weights: List[Tuple[int, float]] = None + weights: List[List[VertexWeight]] = None polygons: List[List[int]] = field(default_factory=list) triangles: List[List[int]] = field(default_factory=list) @@ -64,11 +71,11 @@ class Model: transform: ModelTransform = field(default_factory=ModelTransform) + bone_map: List[str] = None + geometry: List[GeometrySegment] = None collisionprimitive: CollisionPrimitive = None - vgroups_to_modelnames_map : Dict[int, str] = None - @dataclass class Animation: """ Class representing 'CYCL' + 'KFR3' sections in a .msh file """ diff --git a/addons/io_scene_swbf_msh/msh_model_gather.py b/addons/io_scene_swbf_msh/msh_model_gather.py index dd952f4..2119dbc 100644 --- a/addons/io_scene_swbf_msh/msh_model_gather.py +++ b/addons/io_scene_swbf_msh/msh_model_gather.py @@ -10,7 +10,7 @@ from .msh_model import * from .msh_model_utilities import * from .msh_utilities import * -SKIPPED_OBJECT_TYPES = {"LATTICE", "CAMERA", "LIGHT", "SPEAKER", "LIGHT_PROBE", "ARMATURE"} +SKIPPED_OBJECT_TYPES = {"LATTICE", "CAMERA", "LIGHT", "SPEAKER", "LIGHT_PROBE"} MESH_OBJECT_TYPES = {"MESH", "CURVE", "SURFACE", "META", "FONT", "GPENCIL"} MAX_MSH_VERTEX_COUNT = 32767 @@ -22,7 +22,6 @@ def gather_models(apply_modifiers: bool, export_target: str) -> List[Model]: parents = create_parents_set() models_list: List[Model] = [] - skeleton: bpy.types.Armature = None for uneval_obj in select_objects(export_target): if uneval_obj.type in SKIPPED_OBJECT_TYPES and uneval_obj.name not in parents: @@ -31,13 +30,13 @@ def gather_models(apply_modifiers: bool, export_target: str) -> List[Model]: if apply_modifiers: obj = uneval_obj.evaluated_get(depsgraph) else: - obj = uneval_obj - - if uneval_obj.type == "ARMATURE": - continue + obj = uneval_obj check_for_bad_lod_suffix(obj) + if obj.type == "ARMATURE": + models_list += expand_armature(obj) + local_translation, local_rotation, _ = obj.matrix_local.decompose() model = Model() @@ -47,30 +46,8 @@ def gather_models(apply_modifiers: bool, export_target: str) -> List[Model]: model.transform.rotation = convert_rotation_space(local_rotation) model.transform.translation = convert_vector_space(local_translation) - print("Adding model: " + model.name) - - if obj.parent is not None: - if obj.parent.type == "ARMATURE": - - model.parent = "DummyRoot" - - skeleton = obj.parent - - parent_bone_name = obj.parent_bone - if parent_bone_name == "": - model.parent = obj.parent.parent - else: - model.parent = parent_bone_name - - if model.model_type == ModelType.SKIN: - model.vgroups_to_modelnames_map = {} - for i, vgroup in enumerate(obj.vertex_groups): - vgroups_to_modelnames_map[i] = vgroup.name - - if obj.type in MESH_OBJECT_TYPES: - mesh = obj.to_mesh() - model.geometry = create_mesh_geometry(mesh, model.model_type == ModelType.SKIN) + model.geometry = create_mesh_geometry(mesh, obj.vertex_groups) obj.to_mesh_clear() _, _, world_scale = obj.matrix_world.decompose() @@ -86,38 +63,14 @@ def gather_models(apply_modifiers: bool, export_target: str) -> List[Model]: if get_is_collision_primitive(obj): model.collisionprimitive = get_collision_primitive(obj) - models_list.append(model) - - - for bone in skeleton.data.bones: - - model = Model() - model.name = bone.name - model.model_type = ModelType.BONE - model.hidden = False - - local_translation, local_rotation, _ = bone.matrix_local.decompose() - model.transform.rotation = convert_rotation_space(local_rotation) - model.transform.translation = convert_vector_space(local_translation) - - print("Adding bone: " + model.name) - - parent_name = bone.parent - if parent_name is not None: - model.parent = parent_name - else: - if skeleton.parent is not None: - model.parent = skeleton.parent.name - else: - model.parent = None + if obj.vertex_groups: + model.bone_map = [group.name for group in obj.vertex_groups] models_list.append(model) - - - return models_list + def create_parents_set() -> Set[str]: """ Creates a set with the names of the Blender objects from the current scene that have at least one child. """ @@ -130,7 +83,8 @@ def create_parents_set() -> Set[str]: return parents -def create_mesh_geometry(mesh: bpy.types.Mesh, is_skinned : bool) -> List[GeometrySegment]: + +def create_mesh_geometry(mesh: bpy.types.Mesh, has_weights: bool) -> List[GeometrySegment]: """ Creates a list of GeometrySegment objects from a Blender mesh. Does NOT create triangle strips in the GeometrySegment however. """ @@ -143,7 +97,7 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, is_skinned : bool) -> List[Geomet material_count = max(len(mesh.materials), 1) segments: List[GeometrySegment] = [GeometrySegment() for i in range(material_count)] - vertex_cache: List[Dict[Tuple[float], int]] = [dict() for i in range(material_count)] + vertex_cache = [dict() for i in range(material_count)] vertex_remap: List[Dict[Tuple[int, int], int]] = [dict() for i in range(material_count)] polygons: List[Set[int]] = [set() for i in range(material_count)] @@ -151,6 +105,10 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, is_skinned : bool) -> List[Geomet for segment in segments: segment.colors = [] + if has_weights: + for segment in segments: + segment.weights = [] + for segment, material in zip(segments, mesh.materials): segment.material_name = material.name @@ -189,6 +147,11 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, is_skinned : bool) -> List[Geomet for v in mesh.vertex_colors.active.data[loop_index].color: yield v + if segment.weights is not None: + for v in mesh.vertices[vertex_index].groups: + yield v.group + yield v.weight + vertex_cache_entry = tuple(get_cache_vertex()) cached_vertex_index = cache.get(vertex_cache_entry, vertex_cache_miss_index) @@ -204,16 +167,6 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, is_skinned : bool) -> List[Geomet segment.positions.append(convert_vector_space(mesh.vertices[vertex_index].co)) segment.normals.append(convert_vector_space(vertex_normal)) - if is_skinned: - for i,grp_el in mesh.vertices[vertex_index].groups: - segment.weights.append(tuple(grp_el.group, grp_el.weight)) - print("Adding weight to group {grp_el.group} of value {grp_el.weight}") - if i > 3: #will have to look into aramture/skin settings for limiting envolopes to 4 weights... - break - while i < 3: - segment.weights.append(tuple(0,0.0)) - i+=1 - if mesh.uv_layers.active is None: segment.texcoords.append(Vector((0.0, 0.0))) else: @@ -222,6 +175,11 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, is_skinned : bool) -> List[Geomet if segment.colors is not None: segment.colors.append(list(mesh.vertex_colors.active.data[loop_index].color)) + if segment.weights is not None: + groups = mesh.vertices[vertex_index].groups + + segment.weights.append([VertexWeight(v.weight, v.group) for v in groups]) + return new_index @@ -242,11 +200,12 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, is_skinned : bool) -> List[Geomet def get_model_type(obj: bpy.types.Object) -> ModelType: """ Get the ModelType for a Blender object. """ - if obj.parent_type == "ARMATURE": - return ModelType.SKIN if obj.type in MESH_OBJECT_TYPES: - return ModelType.STATIC + if obj.vertex_groups: + return ModelType.SKIN + else: + return ModelType.STATIC return ModelType.NULL @@ -382,6 +341,31 @@ def select_objects(export_target: str) -> List[bpy.types.Object]: return objects + parents +def expand_armature(obj: bpy.types.Object) -> List[Model]: + bones: List[Model] = [] + + for bone in obj.data.bones: + model = Model() + + transform = bone.matrix_local + + if bone.parent: + transform = bone.parent.matrix_local.inverted() @ transform + model.parent = bone.parent.name + else: + model.parent = obj.name + + local_translation, local_rotation, _ = transform.decompose() + + model.model_type = ModelType.BONE + model.name = bone.name + model.transform.rotation = convert_rotation_space(local_rotation) + model.transform.translation = convert_vector_space(local_translation) + + bones.append(model) + + return bones + def convert_vector_space(vec: Vector) -> Vector: return Vector((-vec.x, vec.z, vec.y)) diff --git a/addons/io_scene_swbf_msh/msh_scene.py b/addons/io_scene_swbf_msh/msh_scene.py index f19afa2..459004e 100644 --- a/addons/io_scene_swbf_msh/msh_scene.py +++ b/addons/io_scene_swbf_msh/msh_scene.py @@ -71,21 +71,6 @@ def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_t scene.models = reparent_model_roots(scene.models) - #now that we've collected all models, we should remap WGHT indices... - ''' - names_to_indices = {} - for i,model in enumerate(scene.models): - names_to_indices[model.name] = i; - - for model in scene.models: - if model.model_type == ModelType.SKIN: - for segment in model.geometry: - for i in range(len(segment.weights)): - vgroup_index = segment.weights[i][0] - segment.weights[i][0] = names_to_indices[model.vgroups_to_modelnames_map[vgroup_index]] - ''' - - scene.materials = remove_unused_materials(scene.materials, scene.models) scene.anims = gather_animdata(bpy.context.scene.objects["Armature"]) diff --git a/addons/io_scene_swbf_msh/msh_scene_save.py b/addons/io_scene_swbf_msh/msh_scene_save.py index 78c786c..a049bf1 100644 --- a/addons/io_scene_swbf_msh/msh_scene_save.py +++ b/addons/io_scene_swbf_msh/msh_scene_save.py @@ -19,6 +19,7 @@ def save_scene(output_file, scene: Scene): with msh2.create_child("SINF") as sinf: _write_sinf(sinf, scene) + model_index: Dict[str, int] = {model.name:i for i, model in enumerate(scene.models)} material_index: Dict[str, int] = {} with msh2.create_child("MATL") as matl: @@ -26,7 +27,7 @@ def save_scene(output_file, scene: Scene): for index, model in enumerate(scene.models): with msh2.create_child("MODL") as modl: - _write_modl(modl, model, index, material_index, scene) + _write_modl(modl, model, index, material_index, model_index) with hedr.create_child("ANM2") as anm2: #simple for now for anim in scene.anims: @@ -103,7 +104,7 @@ def _write_matd(matd: Writer, material_name: str, material: Material): 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], scene: Scene): +def _write_modl(modl: Writer, model: Model, index: int, material_index: Dict[str, int], model_index: Dict[str, int]): with modl.create_child("MTYP") as mtyp: mtyp.write_u32(model.model_type.value) @@ -129,13 +130,10 @@ def _write_modl(modl: Writer, model: Model, index: int, material_index: Dict[str for segment in model.geometry: with geom.create_child("SEGM") as segm: _write_segm(segm, segment, material_index) - ''' - if model.model_type == ModelType.SKIN: - with modl.create_child("ENVL") as envl: - envl.write_u32(len(scene.models)) - for i in range(len(scene.models)): - envl.write_u32(i) - ''' + + if model.bone_map: + with geom.create_child("ENVL") as envl: + _write_envl(envl, model, model_index) if model.collisionprimitive is not None: with modl.create_child("SWCI") as swci: @@ -160,21 +158,16 @@ def _write_segm(segm: Writer, segment: GeometrySegment, material_index: Dict[str for position in segment.positions: posl.write_f32(position.x, position.y, position.z) + if segment.weights: + with segm.create_child("WGHT") as wght: + _write_wght(wght, segment.weights) + with segm.create_child("NRML") as nrml: nrml.write_u32(len(segment.normals)) for normal in segment.normals: nrml.write_f32(normal.x, normal.y, normal.z) - ''' - if segment.weights is not None: - with segm.create_child("WGHT") as wght: - wght.write_u32(len(segment.weights) / 4) - for weight in segment.weights: - wght.write_u32(weight[0]) - wght.write_f32(weight[1]) - ''' - if segment.colors is not None: with segm.create_child("CLRL") as clrl: clrl.write_u32(len(segment.colors)) @@ -220,7 +213,7 @@ def _write_anm2(anm2: Writer, anim: Animation): cycl.write_u32(1) cycl.write_string(anim.name) - for _ in range(64 - (len(anim.name) + 1)): + for _ in range(63 - len(anim.name) ): cycl.write_u8(0) cycl.write_f32(10.0) #test framerate @@ -247,10 +240,22 @@ def _write_anm2(anm2: Writer, anim: Animation): kfr3.write_f32(xform.rotation.x, xform.rotation.y, xform.rotation.z, xform.rotation.w) +def _write_wght(wght: Writer, weights: List[List[VertexWeight]]): + wght.write_u32(len(weights)) + for weight_list in weights: + weight_list += [VertexWeight(0.0, 0)] * 4 + weight_list = sorted(weight_list, key=lambda w: w.weight, reverse=True) + weight_list = weight_list[:4] + total_weight = max(sum(map(lambda w: w.weight, weight_list)), 1e-5) + for weight in weight_list: + wght.write_i32(weight.bone) + wght.write_f32(weight.weight / total_weight) +def _write_envl(envl: Writer, model: Model, model_index: Dict[str, int]): + envl.write_u32(len(model.bone_map)) - - + for bone_name in model.bone_map: + envl.write_u32(model_index[bone_name])