diff --git a/addons/io_scene_swbf_msh/__init__.py b/addons/io_scene_swbf_msh/__init__.py index 2d25e73..8b97f68 100644 --- a/addons/io_scene_swbf_msh/__init__.py +++ b/addons/io_scene_swbf_msh/__init__.py @@ -97,9 +97,15 @@ class ExportMSH(Operator, ExportHelper): default=True ) - export_anims: BoolProperty( - name="Export Animations", - description="Action export test", + export_animated: BoolProperty( + name="Export Animated Object", + description="Always check if the object will be animated.", + default=False + ) + + export_skeleton_only: BoolProperty( + name="Export Skeleton", + description="Check if you intend to export skeleton data only.", default=False ) @@ -112,8 +118,10 @@ class ExportMSH(Operator, ExportHelper): scene=create_scene( generate_triangle_strips=self.generate_triangle_strips, apply_modifiers=self.apply_modifiers, - export_target=self.export_target), - separate_anims=self.export_anims + export_target=self.export_target, + skel_only=self.export_skeleton_only + ), + is_animated=self.export_animated ) return {'FINISHED'} diff --git a/addons/io_scene_swbf_msh/msh_anim_gather.py b/addons/io_scene_swbf_msh/msh_anim_gather.py index bf7ef8c..794d094 100644 --- a/addons/io_scene_swbf_msh/msh_anim_gather.py +++ b/addons/io_scene_swbf_msh/msh_anim_gather.py @@ -12,65 +12,42 @@ from .msh_utilities import * from .msh_model_gather import * -def gather_animdata(armature: bpy.types.Armature) -> List[Animation]: - - anim_data = Animation(); +def extract_anim(armature: bpy.types.Armature) -> Animation: action = armature.animation_data.action + anim = Animation(); + + if not action: + framerange = Vector((0.0,1.0)) + else: + framerange = action.frame_range - framerange = action.frame_range - increment = (framerange.y - framerange.x) / 20.0 - offset = framerange.x; + num_frames = math.floor(framerange.y - framerange.x) + 1 + increment = (framerange.y - framerange.x) / (num_frames - 1) - anim_data.bone_transforms[armature.parent.name] = [] + anim.end_index = num_frames - 1 + for bone in armature.data.bones: - anim_data.bone_transforms[bone.name] = [] + anim.bone_transforms[bone.name] = [] - for frame in range(21): - frame_time = offset + frame * increment + for frame in range(num_frames): + + frame_time = framerange.x + frame * increment bpy.context.scene.frame_set(frame_time) - anim_data.bone_transforms[armature.parent.name].append(ModelTransform()) #for testing - for bone in armature.pose.bones: - xform = ModelTransform() - xform.translation = to_skeleton_vector_space(bone.location) - xform.rotation = to_skeleton_rotation_space(bone.rotation_quaternion) - - anim_data.bone_transforms[bone.name].append(xform) - - - return [anim_data] + transform = bone.matrix - -def get_basepose(armature: bpy.types.Armature) -> Animation: - - anim_data = Animation(); - anim_data.name = "basepose" - - bpy.context.scene.frame_set(0.0) - - anim_data.bone_transforms[armature.parent.name] = [] - for bone in armature.data.bones: - anim_data.bone_transforms[bone.name] = [] - - for frame in range(2): - - anim_data.bone_transforms[armature.parent.name].append(ModelTransform()) #for testing - - for bone in armature.pose.bones: + if bone.parent: + transform = bone.parent.matrix.inverted() @ transform + + loc, rot, _ = transform.decompose() xform = ModelTransform() - xform.translation = to_skeleton_vector_space(bone.location) - xform.rotation = to_skeleton_rotation_space(bone.rotation_quaternion) - - anim_data.bone_transforms[bone.name].append(xform) - - - return anim_data - - - + xform.rotation = convert_rotation_space(rot) + xform.translation = convert_vector_space(loc) + anim.bone_transforms[bone.name].append(xform) + return anim diff --git a/addons/io_scene_swbf_msh/msh_model.py b/addons/io_scene_swbf_msh/msh_model.py index 0af2932..10673de 100644 --- a/addons/io_scene_swbf_msh/msh_model.py +++ b/addons/io_scene_swbf_msh/msh_model.py @@ -80,6 +80,10 @@ class Model: class Animation: """ Class representing 'CYCL' + 'KFR3' sections in a .msh file """ - name: str = "wiggle" + name: str = "fullanimation" bone_transforms: Dict[str, List[ModelTransform]] = field(default_factory=dict) + + framerate: float = 10.0 + start_index : int = 0 + end_index : int = 0 diff --git a/addons/io_scene_swbf_msh/msh_model_gather.py b/addons/io_scene_swbf_msh/msh_model_gather.py index c189e5d..5550c44 100644 --- a/addons/io_scene_swbf_msh/msh_model_gather.py +++ b/addons/io_scene_swbf_msh/msh_model_gather.py @@ -14,7 +14,7 @@ SKIPPED_OBJECT_TYPES = {"LATTICE", "CAMERA", "LIGHT", "SPEAKER", "LIGHT_PROBE"} MESH_OBJECT_TYPES = {"MESH", "CURVE", "SURFACE", "META", "FONT", "GPENCIL"} MAX_MSH_VERTEX_COUNT = 32767 -def gather_models(apply_modifiers: bool, export_target: str) -> List[Model]: +def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool) -> List[Model]: """ Gathers the Blender objects from the current scene and returns them as a list of Model objects. """ @@ -36,15 +36,26 @@ def gather_models(apply_modifiers: bool, export_target: str) -> List[Model]: if obj.type == "ARMATURE": models_list += expand_armature(obj) - continue - local_translation, local_rotation, _ = obj.matrix_local.decompose() + if skeleton_only: + continue model = Model() model.name = obj.name model.model_type = get_model_type(obj) model.hidden = get_is_model_hidden(obj) - model.transform.rotation = convert_rotation_space(local_rotation) + + transform = obj.matrix_local + + if obj.parent_bone: + model.parent = obj.parent_bone + transform = obj.parent.data.bones[obj.parent_bone].matrix_local.inverted() @ transform + else: + if obj.parent is not None: + model.parent = obj.parent.name + + local_translation, local_rotation, _ = transform.decompose() + model.transform.rotation = convert_rotation_space(local_rotation) model.transform.translation = convert_vector_space(local_translation) if obj.type in MESH_OBJECT_TYPES: @@ -126,11 +137,11 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, has_weights: bool) -> List[Geomet if use_smooth_normal or mesh.use_auto_smooth: if mesh.has_custom_normals: - vertex_normal = mesh.loops[loop_index].normal + vertex_normal = Vector( mesh.loops[loop_index].normal ) else: - vertex_normal = mesh.vertices[vertex_index].normal + vertex_normal = Vector( mesh.vertices[vertex_index].normal ) else: - vertex_normal = face_normal + vertex_normal = Vector(face_normal) def get_cache_vertex(): yield mesh.vertices[vertex_index].co.x @@ -343,6 +354,8 @@ 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] = [] @@ -355,7 +368,7 @@ def expand_armature(obj: bpy.types.Object) -> List[Model]: transform = bone.parent.matrix_local.inverted() @ transform model.parent = bone.parent.name else: - model.parent = "DummyRoot" # obj.name + model.parent = 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 e33e9f4..b0f7c49 100644 --- a/addons/io_scene_swbf_msh/msh_model_utilities.py +++ b/addons/io_scene_swbf_msh/msh_model_utilities.py @@ -3,6 +3,8 @@ from typing import List from .msh_model import * from .msh_utilities import * +import mathutils +import math from mathutils import Vector, Matrix def scale_segments(scale: Vector, segments: List[GeometrySegment]): @@ -125,15 +127,3 @@ def convert_scale_space(vec: Vector) -> Vector: def convert_rotation_space(quat: Quaternion) -> Quaternion: return Quaternion((-quat.w, quat.x, -quat.z, -quat.y)) - -def to_skeleton_vector_space(vec : Vector): - vnew = convert_vector_space(vec) - vnew.x *= -1.0 - return vnew - -def to_skeleton_rotation_space(quat : Quaternion): - qnew = convert_rotation_space(quat) - qnew.x *= -1.0 - qnew.y *= -1.0 - qnew.z *= -1.0 - return qnew diff --git a/addons/io_scene_swbf_msh/msh_scene.py b/addons/io_scene_swbf_msh/msh_scene.py index 459004e..8a813ed 100644 --- a/addons/io_scene_swbf_msh/msh_scene.py +++ b/addons/io_scene_swbf_msh/msh_scene.py @@ -47,7 +47,7 @@ class Scene: anims: List[Animation] = field(default_factory=list) -def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_target: str) -> Scene: +def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_target: str, skel_only: bool) -> Scene: """ Create a msh Scene from the active Blender scene. """ scene = Scene() @@ -56,7 +56,7 @@ def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_t scene.materials = gather_materials() - scene.models = gather_models(apply_modifiers=apply_modifiers, export_target=export_target) + scene.models = gather_models(apply_modifiers=apply_modifiers, export_target=export_target, skeleton_only=skel_only) scene.models = sort_by_parent(scene.models) if generate_triangle_strips: @@ -70,13 +70,15 @@ def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_t if has_multiple_root_models(scene.models): scene.models = reparent_model_roots(scene.models) - scene.materials = remove_unused_materials(scene.materials, scene.models) - - scene.anims = gather_animdata(bpy.context.scene.objects["Armature"]) + + #creates a dummy basepose if no Action is selected + if "Armature" in bpy.context.scene.objects.keys(): + scene.anims = [extract_anim(bpy.context.scene.objects["Armature"])] return scene + def create_scene_aabb(scene: Scene) -> SceneAABB: """ Create a SceneAABB for a Scene. """ diff --git a/addons/io_scene_swbf_msh/msh_scene_save.py b/addons/io_scene_swbf_msh/msh_scene_save.py index dc3a2ae..685df7f 100644 --- a/addons/io_scene_swbf_msh/msh_scene_save.py +++ b/addons/io_scene_swbf_msh/msh_scene_save.py @@ -10,7 +10,7 @@ from .msh_utilities import * from .crc import * -def save_scene(output_file, scene: Scene, separate_anims: bool): +def save_scene(output_file, scene: Scene, is_animated: bool): """ Saves scene to the supplied file. """ with Writer(file=output_file, chunk_id="HEDR") as hedr: @@ -26,12 +26,19 @@ def save_scene(output_file, scene: Scene, separate_anims: bool): material_index = _write_matl_and_get_material_index(matl, scene) for index, model in enumerate(scene.models): - if separate_anims and (model.model_type not in {ModelType.NULL, ModelType.BONE}): - continue + + print(model.name) + with msh2.create_child("MODL") as modl: _write_modl(modl, model, index, material_index, model_index) - if separate_anims: + if is_animated: + with hedr.create_child("SKL2") as skl2: + _write_skl2(skl2, scene) + + with hedr.create_child("BLN2") as bln2: + _write_bln2(bln2, scene) + with hedr.create_child("ANM2") as anm2: #simple for now for anim in scene.anims: _write_anm2(anm2, anim) @@ -44,8 +51,15 @@ def _write_sinf(sinf: Writer, scene: Scene): name.write_string(scene.name) with sinf.create_child("FRAM") as fram: - fram.write_i32(0, 20) #test values - fram.write_f32(10.0) #test values + 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(min_index, max_index) + fram.write_f32(10.0) with sinf.create_child("BBOX") as bbox: aabb = create_scene_aabb(scene) @@ -130,6 +144,12 @@ def _write_modl(modl: Writer, model: Model, index: int, material_index: Dict[str if model.geometry is not None: with modl.create_child("GEOM") as geom: + + with geom.create_child("BBOX") as bbox: + bbox.write_f32(0.0, 0.0, 0.0, 1.0) + bbox.write_f32(0, 0, 0) + bbox.write_f32(1.0,1.0,1.0,2.0) + for segment in model.geometry: with geom.create_child("SEGM") as segm: _write_segm(segm, segment, material_index) @@ -168,7 +188,7 @@ def _write_segm(segm: Writer, segment: GeometrySegment, material_index: Dict[str with segm.create_child("NRML") as nrml: nrml.write_u32(len(segment.normals)) - for normal in segment.normals: + for i,normal in enumerate(segment.normals): nrml.write_f32(normal.x, normal.y, normal.z) if segment.colors is not None: @@ -214,6 +234,8 @@ SKINNING CHUNKS def _write_wght(wght: Writer, weights: List[List[VertexWeight]]): wght.write_u32(len(weights)) + print("Writing WGHT: ") + for weight_list in weights: weight_list += [VertexWeight(0.0, 0)] * 4 weight_list = sorted(weight_list, key=lambda w: w.weight, reverse=True) @@ -221,18 +243,46 @@ def _write_wght(wght: Writer, weights: List[List[VertexWeight]]): total_weight = max(sum(map(lambda w: w.weight, weight_list)), 1e-5) + print_str = "" + for weight in weight_list: + + print_str += "({}, {}) ".format(weight.bone, weight.weight / total_weight) + wght.write_i32(weight.bone) wght.write_f32(weight.weight / total_weight) + print(" {}".format(print_str)) + def _write_envl(envl: Writer, model: Model, model_index: Dict[str, int]): envl.write_u32(len(model.bone_map)) + print("Writing ENVL: ") + for bone_name in model.bone_map: + print(" {:10} Index: {}".format(bone_name, model_index[bone_name])) envl.write_u32(model_index[bone_name]) ''' -ANIMATION CHUNKS +SKELETON CHUNKS +''' +def _write_bln2(bln2: Writer, scene: Scene): + bones = scene.anims[0].bone_transforms.keys() + bln2.write_u32(len(bones)) + + for boneName in bones: + bln2.write_u32(crc(boneName), 0) + +def _write_skl2(skl2: Writer, scene: Scene): + bones = scene.anims[0].bone_transforms.keys() + skl2.write_u32(len(bones)) + + for boneName in bones: + skl2.write_u32(crc(boneName), 0) #default values + skl2.write_f32(1.0, 0.0, 0.0) #from docs + +''' +ANIMATION CHUNK ''' def _write_anm2(anm2: Writer, anim: Animation): @@ -241,12 +291,12 @@ def _write_anm2(anm2: Writer, anim: Animation): cycl.write_u32(1) cycl.write_string(anim.name) - for _ in range(63 - len(anim.name) ): + for _ in range(63 - len(anim.name)): cycl.write_u8(0) - cycl.write_f32(10.0) #test framerate + cycl.write_f32(anim.framerate) cycl.write_u32(0) #what does play style refer to? - cycl.write_u32(0, 20) #first frame indices + cycl.write_u32(anim.start_index, anim.end_index) #first frame indices with anm2.create_child("KFR3") as kfr3: @@ -257,7 +307,8 @@ def _write_anm2(anm2: Writer, anim: Animation): kfr3.write_u32(crc(boneName)) kfr3.write_u32(0) #what is keyframe type? - kfr3.write_u32(21, 21) #basic testing + num_frames = 1 + anim.end_index - anim.start_index + kfr3.write_u32(num_frames, num_frames) #basic testing for i, xform in enumerate(anim.bone_transforms[boneName]): kfr3.write_u32(i)