diff --git a/addons/io_scene_swbf_msh/msh_anim_gather.py b/addons/io_scene_swbf_msh/msh_anim_gather.py index 43e2363..1da0003 100644 --- a/addons/io_scene_swbf_msh/msh_anim_gather.py +++ b/addons/io_scene_swbf_msh/msh_anim_gather.py @@ -32,7 +32,10 @@ def extract_anim(armature: bpy.types.Armature) -> Animation: anim.bone_transforms[bone.name] = [] for frame in range(num_frames): - + + #if frame % 10 == 0: + # print("Sample frame {}:".format(frame)) + frame_time = framerange.x + frame * increment bpy.context.scene.frame_set(frame_time) @@ -50,6 +53,9 @@ def extract_anim(armature: bpy.types.Armature) -> Animation: xform.rotation = convert_rotation_space(rot) xform.translation = convert_vector_space(loc) + #if frame % 10 == 0: + # print("\t{:10}: loc {:15} rot {:15}".format(bone.name, vec_to_str(xform.translation), quat_to_str(xform.rotation))) + anim.bone_transforms[bone.name].append(xform) return anim diff --git a/addons/io_scene_swbf_msh/msh_model_gather.py b/addons/io_scene_swbf_msh/msh_model_gather.py index dd1b8ba..ea1fe03 100644 --- a/addons/io_scene_swbf_msh/msh_model_gather.py +++ b/addons/io_scene_swbf_msh/msh_model_gather.py @@ -38,23 +38,37 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool models_list += expand_armature(obj) continue - if skeleton_only: - continue - model = Model() model.name = obj.name - model.model_type = get_model_type(obj) + model.model_type = get_model_type(obj, skeleton_only) model.hidden = get_is_model_hidden(obj) transform = obj.matrix_local + transform_reset = Matrix.Identity(4) if obj.parent_bone: model.parent = obj.parent_bone - transform = obj.parent.data.bones[obj.parent_bone].matrix_local.inverted() @ transform + + # matrix_local, when called on an armature child also parented to a bone, appears to be broken. + # At the very least, the results contradict the docs... + armature_relative_transform = obj.parent.matrix_world.inverted() @ obj.matrix_world + bone_relative_transform = obj.parent.data.bones[obj.parent_bone].matrix_local.inverted() @ armature_relative_transform + + transform = bone_relative_transform + + ''' + # Since the transforms of direct bone children are discarded by ZEngine (but not ZEditor), we apply the transform + # before geometry extraction, then apply the inversion after. + if obj.type in MESH_OBJECT_TYPES: + obj.data.transform(bone_relative_transform) + transform_reset = bone_relative_transform.inverted() + transform = Matrix.Identity(4) + ''' else: if obj.parent is not None: if obj.parent.type == "ARMATURE": model.parent = obj.parent.parent.name + transform = obj.parent.matrix_local @ transform else: model.parent = obj.parent.name @@ -63,6 +77,7 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool model.transform.translation = convert_vector_space(local_translation) if obj.type in MESH_OBJECT_TYPES: + mesh = obj.to_mesh() model.geometry = create_mesh_geometry(mesh, obj.vertex_groups) obj.to_mesh_clear() @@ -70,21 +85,25 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool _, _, world_scale = obj.matrix_world.decompose() world_scale = convert_scale_space(world_scale) scale_segments(world_scale, model.geometry) - + for segment in model.geometry: if len(segment.positions) > MAX_MSH_VERTEX_COUNT: raise RuntimeError(f"Object '{obj.name}' has resulted in a .msh geometry segment that has " f"more than {MAX_MSH_VERTEX_COUNT} vertices! Split the object's mesh up " f"and try again!") + if obj.vertex_groups: + model.bone_map = [group.name for group in obj.vertex_groups] + + obj.data.transform(transform_reset) + if get_is_collision_primitive(obj): model.collisionprimitive = get_collision_primitive(obj) - if obj.vertex_groups: - model.bone_map = [group.name for group in obj.vertex_groups] models_list.append(model) + return models_list @@ -215,10 +234,10 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, has_weights: bool) -> List[Geomet return segments -def get_model_type(obj: bpy.types.Object) -> ModelType: +def get_model_type(obj: bpy.types.Object, skel_only: bool) -> ModelType: """ Get the ModelType for a Blender object. """ - if obj.type in MESH_OBJECT_TYPES: + if obj.type in MESH_OBJECT_TYPES and not skel_only: if obj.vertex_groups: return ModelType.SKIN else: @@ -372,7 +391,11 @@ def expand_armature(obj: bpy.types.Object) -> List[Model]: transform = bone.parent.matrix_local.inverted() @ transform model.parent = bone.parent.name else: - model.parent = "tst_prop" + model.parent = obj.parent.name + for child_obj in obj.children: + if child_obj.vertex_groups and not get_is_model_hidden(obj) and not obj.parent_bone: + model.parent = child_obj.name + local_translation, local_rotation, _ = transform.decompose() diff --git a/addons/io_scene_swbf_msh/msh_model_utilities.py b/addons/io_scene_swbf_msh/msh_model_utilities.py index b0f7c49..eec1450 100644 --- a/addons/io_scene_swbf_msh/msh_model_utilities.py +++ b/addons/io_scene_swbf_msh/msh_model_utilities.py @@ -7,6 +7,26 @@ import mathutils import math from mathutils import Vector, Matrix + +def inject_dummy_data(model : Model): + """ Adds a triangle and material to the scene root when exporting skeletons to satisfy ZenAsset. """ + model.hidden = True + + dummy_seg = GeometrySegment() + dummy_seg.material_name = "" + + dummy_seg.positions = [Vector((0.0,0.1,0.0)), Vector((0.1,0.0,0.0)), Vector((0.0,0.0,0.1))] + dummy_seg.normals = [Vector((0.0,1.0,0.0)), Vector((1.0,0.0,0.0)), Vector((0.0,0.0,1.0))] + dummy_seg.texcoords = [Vector((0.1,0.1)), Vector((0.2,0.2)), Vector((0.3,0.3))] + tri = [[0,1,2]] + dummy_seg.triangles = tri + dummy_seg.polygons = tri + dummy_seg.triangle_strips = tri + + model.geometry = [dummy_seg] + model.model_type = ModelType.STATIC + + def scale_segments(scale: Vector, segments: List[GeometrySegment]): """ Scales are positions in the GeometrySegment list. """ diff --git a/addons/io_scene_swbf_msh/msh_scene.py b/addons/io_scene_swbf_msh/msh_scene.py index 8a813ed..2f37f9a 100644 --- a/addons/io_scene_swbf_msh/msh_scene.py +++ b/addons/io_scene_swbf_msh/msh_scene.py @@ -76,6 +76,10 @@ def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_t if "Armature" in bpy.context.scene.objects.keys(): scene.anims = [extract_anim(bpy.context.scene.objects["Armature"])] + root = scene.models[0] + if skel_only and root.model_type == ModelType.NULL: + inject_dummy_data(root) + return scene diff --git a/addons/io_scene_swbf_msh/msh_scene_save.py b/addons/io_scene_swbf_msh/msh_scene_save.py index 7dfb017..6507b17 100644 --- a/addons/io_scene_swbf_msh/msh_scene_save.py +++ b/addons/io_scene_swbf_msh/msh_scene_save.py @@ -19,7 +19,7 @@ def save_scene(output_file, scene: Scene, is_animated: bool): 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)} + model_index: Dict[str, int] = {model.name:(i+1) for i, model in enumerate(scene.models)} material_index: Dict[str, int] = {} with msh2.create_child("MATL") as matl: @@ -27,7 +27,7 @@ def save_scene(output_file, scene: Scene, is_animated: bool): for index, model in enumerate(scene.models): - print(model.name) + #print("Name: {:.10}, Pos: {:15}, Rot: {:15}, Parent: {}".format(model.name, vec_to_str(model.transform.translation), quat_to_str(model.transform.rotation), model.parent)) with msh2.create_child("MODL") as modl: _write_modl(modl, model, index, material_index, model_index) @@ -48,16 +48,9 @@ def save_scene(output_file, scene: Scene, is_animated: bool): def _write_sinf(sinf: Writer, scene: Scene): with sinf.create_child("NAME") as name: - name.write_string("spa1_prop_impdoor_rig") + name.write_string(scene.name) with sinf.create_child("FRAM") as fram: - min_index = 0 - max_index = 1 - - if scene.anims and len(scene.anims) > 0: - min_index = min([anim.start_index for anim in scene.anims]) - max_index = min([anim.end_index for anim in scene.anims]) - fram.write_i32(0, 1) fram.write_f32(29.97) @@ -126,7 +119,7 @@ def _write_modl(modl: Writer, model: Model, index: int, material_index: Dict[str mtyp.write_u32(model.model_type.value) with modl.create_child("MNDX") as mndx: - mndx.write_u32(index) + mndx.write_u32(index + 1) with modl.create_child("NAME") as name: name.write_string(model.name) @@ -135,13 +128,9 @@ def _write_modl(modl: Writer, model: Model, index: int, material_index: Dict[str with modl.create_child("PRNT") as prnt: prnt.write_string(model.parent) - if model.model_type != ModelType.NULL and model.model_type != ModelType.BONE: - if model.hidden or "Dummy" in model.name or "hp_" in model.name: - with modl.create_child("FLGS") as flgs: - flgs.write_u32(1) - for seg in model.geometry: - seg.texcoords = None - + if model.hidden: + with modl.create_child("FLGS") as flgs: + flgs.write_u32(1) with modl.create_child("TRAN") as tran: _write_tran(tran, model.transform) diff --git a/addons/io_scene_swbf_msh/msh_utilities.py b/addons/io_scene_swbf_msh/msh_utilities.py index 7715383..5a3fa6e 100644 --- a/addons/io_scene_swbf_msh/msh_utilities.py +++ b/addons/io_scene_swbf_msh/msh_utilities.py @@ -2,6 +2,13 @@ from mathutils import Vector + +def vec_to_str(vec): + return "({:.4},{:.4},{:.4})".format(vec.x,vec.y,vec.z) + +def quat_to_str(quat): + return "({:.4},{:.4},{:.4},{:.4})".format(quat.x, quat.y, quat.z, quat.w) + def add_vec(l: Vector, r: Vector) -> Vector: return Vector(v0 + v1 for v0, v1 in zip(l, r))