diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..ba2a6c01 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python-envs.defaultEnvManager": "ms-python.python:system", + "python-envs.pythonProjects": [] +} \ No newline at end of file diff --git a/blender_bindings/models/mdl44/__init__.py b/blender_bindings/models/mdl44/__init__.py index fa6312cc..a80ca0f0 100644 --- a/blender_bindings/models/mdl44/__init__.py +++ b/blender_bindings/models/mdl44/__init__.py @@ -46,7 +46,7 @@ def import_mdl44(model_path: TinyPath, buffer: Buffer, logger.error(f'Failed to import materials, caused by {t_ex}') import traceback traceback.print_exc() - container = import_model(content_manager, mdl, vtx, vvd, options.scale, options.create_flex_drivers) + container = import_model(content_manager, mdl, vtx, vvd, options.scale, options.create_flex_drivers, debug_stereo_balance=options.debug_stereo_balance) if options.import_physics: phy_buffer = content_manager.find_file(model_path.with_suffix(".phy")) if phy_buffer is None: diff --git a/blender_bindings/models/mdl44/import_mdl.py b/blender_bindings/models/mdl44/import_mdl.py index d9d54b81..e1809785 100644 --- a/blender_bindings/models/mdl44/import_mdl.py +++ b/blender_bindings/models/mdl44/import_mdl.py @@ -79,7 +79,7 @@ def create_armature(mdl: MdlV44, scale=1.0, load_refpose=False): def import_model(content_manager: ContentManager, mdl: MdlV44, vtx: Vtx, vvd: Vvd, - scale=1.0, create_drivers=False, load_refpose=False): + scale=1.0, create_drivers=False, load_refpose=False, *, debug_stereo_balance=False): full_material_names = collect_full_material_names([mat.name for mat in mdl.materials], mdl.materials_paths, content_manager) [setattr(mat, 'bpy_material', get_or_create_material(mat.name, full_material_names[mat.name])) for mat in mdl.materials if mat.bpy_material is None] @@ -95,7 +95,7 @@ def import_model(content_manager: ContentManager, mdl: MdlV44, vtx: Vtx, vvd: Vv static_prop = mdl.header.flags & StudioHDRFlags.STATIC_PROP != 0 armature = None vertex_anim_cache = preprocess_vertex_animation(mdl, vvd) - + vert_anim_fixed_point_scale = mdl.header.vert_anim_fixed_point_scale if (mdl.header.flags & StudioHDRFlags.VERT_ANIM_FIXED_POINT_SCALE !=0 ) else 1/4096 if not static_prop: armature = create_armature(mdl, scale) @@ -167,23 +167,68 @@ def import_model(content_manager: ContentManager, mdl: MdlV44, vtx: Vtx, vvd: Vv bone_name = mdl.bones[bone_index].name weight_groups[bone_name].add([n], weight, 'REPLACE') - flex_names = [] + flexes = [] for mesh in model.meshes: if mesh.flexes: - flex_names.extend([mdl.flex_names[flex.flex_desc_index] for flex in mesh.flexes]) + flexes.extend([(mdl.flex_names[flex.flex_desc_index], flex) for flex in mesh.flexes]) - if flex_names: + if flexes: mesh_obj.shape_key_add(name='base') - for flex_name in flex_names: - shape_key = mesh_data.shape_keys.key_blocks.get(flex_name, None) or mesh_obj.shape_key_add( - name=flex_name) - shape_key.value = 0.0 - vertex_animation = vertex_anim_cache[flex_name] - model_vertices = get_slice(vertex_animation["pos"], model.vertex_offset, model.vertex_count) - flex_vertices = model_vertices[vtx_vertices] * scale + if debug_stereo_balance: + # debug tool to get stereo flex balances. i'll leave it here just in case + side_right = mesh_obj.vertex_groups.new(name='blendright') + side_left = mesh_obj.vertex_groups.new(name='blendleft') + side_all = np.zeros(model.vertex_count, dtype=np.float32) + + for flex_name, flex_desc in flexes: + vertex_animation = vertex_anim_cache[flex_name] + side = get_slice(vertex_animation['side'], model.vertex_offset, model.vertex_count).ravel() + side_all = np.maximum(side_all, side) + side_all = side_all[vtx_vertices] + 0.0 - shape_key.data.foreach_set("co", flex_vertices.reshape(-1)) + for n, vert in enumerate(vtx_vertices): + side_right.add([n], side_all[n], 'REPLACE') + side_left.add([n], 1-side_all[n], 'REPLACE') + + for flex_name, flex_desc in flexes: + vertex_animation = vertex_anim_cache[flex_name] + flex_delta = get_slice(vertex_animation["pos"], model.vertex_offset, model.vertex_count) + flex_delta = flex_delta[vtx_vertices] * scale + + side = get_slice(vertex_animation["side"], model.vertex_offset, model.vertex_count) + side = side[vtx_vertices] + 0.0 + wrinkle = get_slice(vertex_animation["wrinkle"], model.vertex_offset, model.vertex_count) + wrinkle = wrinkle[vtx_vertices] + 0.0 # this will have to be explained to me :P + # model.vertex_count and vtx_vertices can differ in size, so doing something like this just makes it work? + # apparently vtx_vertices has duplicate indicies, which can be observed by turning it into a set. + # i'm just following here + # -hisanimations + + model_vertices = get_slice(all_vertices['vertex'], model.vertex_offset, model.vertex_count) + model_vertices = model_vertices[vtx_vertices] * scale + + if flex_desc.partner_index: + partner_name = mdl.flex_names[flex_desc.partner_index] + flexes, sides = [flex_name, partner_name], [1-side, side] if not debug_stereo_balance else [1.0, 1.0] + else: + flexes, sides = [flex_name], [1.0] + + for flex_name, side in zip(flexes, sides): + shape_key = mesh_data.shape_keys.key_blocks.get(flex_name, None) or mesh_obj.shape_key_add( + name=flex_name) + shape_key.data.foreach_set("co", (flex_delta*side + model_vertices).ravel()) + shape_key.value = 0.0 + + if flex_desc.vertex_anim_type == 1: + mesh_data: bpy.types.Mesh + if wrinkle.max() > 0: + wrinkle_name = f'WR.{flex_name}.S' + if wrinkle.min() < 0: + wrinkle_name = f'WR.{flex_name}.C' + attr: bpy.types.Attribute = mesh_data.attributes.get(wrinkle_name, None) or mesh_data.attributes.new(wrinkle_name, 'FLOAT', 'POINT') + wrinkle_data = (abs(wrinkle) * vert_anim_fixed_point_scale) * side + attr.data.foreach_set('value', wrinkle_data.ravel()) if create_drivers: create_flex_drivers(mesh_obj, mdl) diff --git a/blender_bindings/models/mdl49/__init__.py b/blender_bindings/models/mdl49/__init__.py index 074eab68..30325b41 100644 --- a/blender_bindings/models/mdl49/__init__.py +++ b/blender_bindings/models/mdl49/__init__.py @@ -45,7 +45,7 @@ def import_mdl49(model_path: TinyPath, buffer: Buffer, import traceback traceback.print_exc() - container = import_model(content_manager, mdl, vtx, vvd, options.scale, options.create_flex_drivers) + container = import_model(content_manager, mdl, vtx, vvd, options.scale, options.create_flex_drivers, debug_stereo_balance=options.debug_stereo_balance) if options.import_physics: phy_buffer = content_manager.find_file(model_path.with_suffix(".phy")) if phy_buffer is None: diff --git a/blender_bindings/models/mdl49/import_mdl.py b/blender_bindings/models/mdl49/import_mdl.py index 019656c4..a663f57f 100644 --- a/blender_bindings/models/mdl49/import_mdl.py +++ b/blender_bindings/models/mdl49/import_mdl.py @@ -30,7 +30,7 @@ def import_model(content_manager: ContentManager, mdl: MdlV49, vtx: Vtx, vvd: Vvd, - scale=1.0, create_drivers=False, load_refpose=False): + scale=1.0, create_drivers=False, load_refpose=False, *, debug_stereo_balance=False): full_material_names = collect_full_material_names([mat.name for mat in mdl.materials], mdl.materials_paths, content_manager) [setattr(mat, 'bpy_material', get_or_create_material(mat.name, full_material_names[mat.name])) for mat in mdl.materials if mat.bpy_material is None] # ensure all MaterialV49 has its bpy_material counterpart @@ -42,6 +42,7 @@ def import_model(content_manager: ContentManager, mdl: MdlV49, vtx: Vtx, vvd: Vv all_vertices = vvd.lod_data[desired_lod] extra_stuff = [] static_prop = mdl.header.flags & StudioHDRFlags.STATIC_PROP != 0 + vert_anim_fixed_point_scale = mdl.header.vert_anim_fixed_point_scale if (mdl.header.flags & StudioHDRFlags.VERT_ANIM_FIXED_POINT_SCALE !=0 ) else 1/4096 armature = None vertex_anim_cache = preprocess_vertex_animation(mdl, vvd) if not static_prop: @@ -133,37 +134,63 @@ def import_model(content_manager: ContentManager, mdl: MdlV49, vtx: Vtx, vvd: Vv if flexes: mesh_obj.shape_key_add(name='base') + + if debug_stereo_balance: + # debug tool to get stereo flex balances. i'll leave it here just in case + side_right = mesh_obj.vertex_groups.new(name='blendright') + side_left = mesh_obj.vertex_groups.new(name='blendleft') + side_all = np.zeros(model.vertex_count, dtype=np.float32) + + for flex_name, flex_desc in flexes: + vertex_animation = vertex_anim_cache[flex_name] + side = get_slice(vertex_animation['side'], model.vertex_offset, model.vertex_count).ravel() + side_all = np.maximum(side_all, side) + side_all = side_all[vtx_vertices] + 0.0 + + for n, vert in enumerate(vtx_vertices): + side_right.add([n], side_all[n], 'REPLACE') + side_left.add([n], 1-side_all[n], 'REPLACE') + for flex_name, flex_desc in flexes: vertex_animation = vertex_anim_cache[flex_name] flex_delta = get_slice(vertex_animation["pos"], model.vertex_offset, model.vertex_count) flex_delta = flex_delta[vtx_vertices] * scale + + side = get_slice(vertex_animation["side"], model.vertex_offset, model.vertex_count) + side = side[vtx_vertices] + 0.0 + wrinkle = get_slice(vertex_animation["wrinkle"], model.vertex_offset, model.vertex_count) + wrinkle = wrinkle[vtx_vertices] + 0.0 # this will have to be explained to me :P + # model.vertex_count and vtx_vertices can differ in size, so doing something like this just makes it work? + # apparently vtx_vertices has duplicate indicies, which can be observed by turning it into a set. + # i'm just following here + # -hisanimations + model_vertices = get_slice(all_vertices['vertex'], model.vertex_offset, model.vertex_count) model_vertices = model_vertices[vtx_vertices] * scale - if create_drivers and flex_desc.partner_index: + if flex_desc.partner_index: partner_name = mdl.flex_names[flex_desc.partner_index] - partner_shape_key = (mesh_data.shape_keys.key_blocks.get(partner_name, None) or - mesh_obj.shape_key_add(name=partner_name)) - partner_shape_key.value = 0.0 - shape_key = (mesh_data.shape_keys.key_blocks.get(flex_name, None) or - mesh_obj.shape_key_add(name=flex_name)) - shape_key.value = 0.0 - - balance = model_vertices[:, 0] - balance_width = (model_vertices.max() - model_vertices.min()) * (1 - (99.3 / 100)) - balance = np.clip((-balance / balance_width / 2) + 0.5, 0, 1) - - flex_vertices = (flex_delta * balance[:, None]) + model_vertices - shape_key.data.foreach_set("co", flex_vertices.reshape(-1)) - - p_balance = 1 - balance - p_flex_vertices = (flex_delta * p_balance[:, None]) + model_vertices - partner_shape_key.data.foreach_set("co", p_flex_vertices.reshape(-1)) + flexes, sides = [flex_name, partner_name], [1-side, side] if not debug_stereo_balance else [1.0, 1.0] else: + flexes, sides = [flex_name], [1.0] + + for flex_name, side in zip(flexes, sides): shape_key = mesh_data.shape_keys.key_blocks.get(flex_name, None) or mesh_obj.shape_key_add( name=flex_name) + shape_key.data.foreach_set("co", (flex_delta*side + model_vertices).ravel()) shape_key.value = 0.0 - shape_key.data.foreach_set("co", (flex_delta + model_vertices).ravel()) + + if flex_desc.vertex_anim_type == 1: + mesh_data: bpy.types.Mesh + if wrinkle.max() > 0: + wrinkle_name = f'WR.{flex_name}.S' + if wrinkle.min() < 0: + wrinkle_name = f'WR.{flex_name}.C' + attr: bpy.types.Attribute = mesh_data.attributes.get(wrinkle_name, None) or mesh_data.attributes.new(wrinkle_name, 'FLOAT', 'POINT') + wrinkle_data = (abs(wrinkle) * vert_anim_fixed_point_scale) * side + attr.data.foreach_set('value', wrinkle_data.ravel()) + + if create_drivers: create_flex_drivers(mesh_obj, mdl) mesh_data.validate() diff --git a/blender_bindings/models/mdl52/__init__.py b/blender_bindings/models/mdl52/__init__.py index 4c59a06d..bc1508ff 100644 --- a/blender_bindings/models/mdl52/__init__.py +++ b/blender_bindings/models/mdl52/__init__.py @@ -47,7 +47,7 @@ def import_mdl52(model_path: TinyPath, buffer: Buffer, import traceback traceback.print_exc() - container = import_model(content_manager, mdl, vtx, vvd, vvc, options.scale) + container = import_model(content_manager, mdl, vtx, vvd, vvc, options.scale, debug_stereo_balance=options.debug_stereo_balance) if options.import_physics: phy_buffer = content_manager.find_file(model_path.with_suffix(".phy")) if phy_buffer is None: diff --git a/blender_bindings/models/mdl52/import_mdl.py b/blender_bindings/models/mdl52/import_mdl.py index 0ea62b06..fa8576a8 100644 --- a/blender_bindings/models/mdl52/import_mdl.py +++ b/blender_bindings/models/mdl52/import_mdl.py @@ -24,7 +24,7 @@ def import_model(content_provider: ContentProvider, mdl: MdlV52, vtx: Vtx, vvd: Vvd, vvc: Vvc, - scale=1.0, create_drivers=False, load_refpose=False): + scale=1.0, create_drivers=False, load_refpose=False, *, debug_stereo_balance=False): full_material_names = collect_full_material_names([mat.name for mat in mdl.materials], mdl.materials_paths, content_provider) [setattr(mat, 'bpy_material', get_or_create_material(mat.name, full_material_names[mat.name])) for mat in mdl.materials if mat.bpy_material is None] @@ -39,6 +39,7 @@ def import_model(content_provider: ContentProvider, mdl: MdlV52, vtx: Vtx, vvd: vertex_anim_cache = preprocess_vertex_animation(mdl, vvd) static_prop = mdl.header.flags & StudioHDRFlags.STATIC_PROP != 0 + vert_anim_fixed_point_scale = mdl.header.vert_anim_fixed_point_scale if (mdl.header.flags & StudioHDRFlags.VERT_ANIM_FIXED_POINT_SCALE !=0 ) else 1/4096 armature = None if not static_prop: @@ -131,38 +132,63 @@ def import_model(content_provider: ContentProvider, mdl: MdlV52, vtx: Vtx, vvd: if flexes: mesh_obj.shape_key_add(name='base') - for flex_name, flex_desc in flexes: - vertex_animation = vertex_anim_cache[flex_name] - flex_delta = get_slice(vertex_animation["pos"], model.vertex_offset, model.vertex_count) - flex_delta = flex_delta[vtx_vertices] * scale - model_vertices = get_slice(all_vertices['vertex'], model.vertex_offset, model.vertex_count) - model_vertices = model_vertices[vtx_vertices] * scale - - if create_drivers and flex_desc.partner_index: - partner_name = mdl.flex_names[flex_desc.partner_index] - partner_shape_key = (mesh_data.shape_keys.key_blocks.get(partner_name, None) or - mesh_obj.shape_key_add(name=partner_name)) - partner_shape_key.value = 0.0 - shape_key = (mesh_data.shape_keys.key_blocks.get(flex_name, None) or - mesh_obj.shape_key_add(name=flex_name)) - shape_key.value = 0.0 - - balance = model_vertices[:, 0] - balance_width = (model_vertices.max() - model_vertices.min()) * (1 - (99.3 / 100)) - balance = np.clip((-balance / balance_width / 2) + 0.5, 0, 1) - - flex_vertices = (flex_delta * balance[:, None]) + model_vertices - shape_key.data.foreach_set("co", flex_vertices.reshape(-1)) - - p_balance = 1 - balance - p_flex_vertices = (flex_delta * p_balance[:, None]) + model_vertices - partner_shape_key.data.foreach_set("co", p_flex_vertices.reshape(-1)) - else: - shape_key = mesh_data.shape_keys.key_blocks.get(flex_name, None) or mesh_obj.shape_key_add( - name=flex_name) - shape_key.value = 0.0 - - shape_key.data.foreach_set("co", (flex_delta + model_vertices).reshape(-1)) + + if debug_stereo_balance: + # debug tool to get stereo flex balances. i'll leave it here just in case + side_right = mesh_obj.vertex_groups.new(name='blendright') + side_left = mesh_obj.vertex_groups.new(name='blendleft') + side_all = np.zeros(model.vertex_count, dtype=np.float32) + + for flex_name, flex_desc in flexes: + vertex_animation = vertex_anim_cache[flex_name] + side = get_slice(vertex_animation['side'], model.vertex_offset, model.vertex_count).ravel() + side_all = np.maximum(side_all, side) + side_all = side_all[vtx_vertices] + 0.0 + + for n, vert in enumerate(vtx_vertices): + side_right.add([n], side_all[n], 'REPLACE') + side_left.add([n], 1-side_all[n], 'REPLACE') + + for flex_name, flex_desc in flexes: + vertex_animation = vertex_anim_cache[flex_name] + flex_delta = get_slice(vertex_animation["pos"], model.vertex_offset, model.vertex_count) + flex_delta = flex_delta[vtx_vertices] * scale + + side = get_slice(vertex_animation["side"], model.vertex_offset, model.vertex_count) + side = side[vtx_vertices] + 0.0 + wrinkle = get_slice(vertex_animation["wrinkle"], model.vertex_offset, model.vertex_count) + wrinkle = wrinkle[vtx_vertices] + 0.0 # this will have to be explained to me :P + # model.vertex_count and vtx_vertices can differ in size, so doing something like this just makes it work? + # apparently vtx_vertices has duplicate indicies, which can be observed by turning it into a set. + # i'm just following here + # -hisanimations + + model_vertices = get_slice(all_vertices['vertex'], model.vertex_offset, model.vertex_count) + model_vertices = model_vertices[vtx_vertices] * scale + + if flex_desc.partner_index: + partner_name = mdl.flex_names[flex_desc.partner_index] + flexes, sides = [flex_name, partner_name], [1-side, side] if not debug_stereo_balance else [1.0, 1.0] + else: + flexes, sides = [flex_name], [1.0] + + for flex_name, side in zip(flexes, sides): + shape_key = mesh_data.shape_keys.key_blocks.get(flex_name, None) or mesh_obj.shape_key_add( + name=flex_name) + shape_key.data.foreach_set("co", (flex_delta*side + model_vertices).ravel()) + shape_key.value = 0.0 + + if flex_desc.vertex_anim_type == 1: + mesh_data: bpy.types.Mesh + if wrinkle.max() > 0: + wrinkle_name = f'WR.{flex_name}.S' + if wrinkle.min() < 0: + wrinkle_name = f'WR.{flex_name}.C' + + attr: bpy.types.Attribute = mesh_data.attributes.get(wrinkle_name, None) or mesh_data.attributes.new(wrinkle_name, 'FLOAT', 'POINT') + wrinkle_data = (abs(wrinkle) * vert_anim_fixed_point_scale) * side + attr.data.foreach_set('value', wrinkle_data.ravel()) + if create_drivers: create_flex_drivers(mesh_obj, mdl) mesh_data.validate() diff --git a/blender_bindings/operators/import_settings_base.py b/blender_bindings/operators/import_settings_base.py index 78f541ab..fe342dc9 100644 --- a/blender_bindings/operators/import_settings_base.py +++ b/blender_bindings/operators/import_settings_base.py @@ -41,6 +41,8 @@ class ModelOptions(SharedOptions, Source1SharedSettings): bodygroup_grouping: BoolProperty(name="Group meshes by bodygroup", default=True, subtype='UNSIGNED') import_textures: BoolProperty(name="Import materials", default=True, subtype='UNSIGNED') + debug_stereo_balance: BoolProperty(name='Debug Stereo Flex Balance', description='Add vertex groups to show how stereo flexes blend. If enabled, the balance will not be mixed into shape keys.', default=False, subtype='UNSIGNED') + @classmethod def default(cls): diff --git a/library/models/mdl/structs/flex.py b/library/models/mdl/structs/flex.py index 1766f3c6..38d7bdb1 100644 --- a/library/models/mdl/structs/flex.py +++ b/library/models/mdl/structs/flex.py @@ -251,6 +251,6 @@ class VertAnimWrinkleV49(VertAnimV49): ('side', np.uint8, (1,)), ('vertex_delta', np.float16, (3,)), ('normal_delta', np.float16, (3,)), - ('wrinkle_delta', np.float16, (1,)), + ('wrinkle_delta', np.int16, (1,)), ] ) diff --git a/library/models/mdl/v44/vertex_animation_cache.py b/library/models/mdl/v44/vertex_animation_cache.py index 66114ffc..bc55c604 100644 --- a/library/models/mdl/v44/vertex_animation_cache.py +++ b/library/models/mdl/v44/vertex_animation_cache.py @@ -12,7 +12,8 @@ # ("index", np.int32, (1,)), ("pos", np.float32, (3,)), ("normal", np.float32, (3,)), - ("wrinkle", np.float32, (1,)), + ("wrinkle", np.int16, (1,)), + ("side", np.float32, (1,)), ]) @@ -33,8 +34,12 @@ def process_mesh(mesh: Mesh, desired_lod=0): index_ = flex.vertex_animations['index'].astype(np.uint32).reshape(-1) vertex_indices = index_ + mesh.vertex_index_start + vertex_offset vertex_data = vertex_cache[flex_name] + #vertex_data["side"][:] = 1.0 vertex_data["pos"][vertex_indices] = flex.vertex_animations['vertex_delta'] vertex_data["normal"][vertex_indices] = flex.vertex_animations['normal_delta'] + vertex_data["side"][vertex_indices] = flex.vertex_animations["side"] / 255 + if flex.vertex_anim_type == 1: + vertex_data['wrinkle'][vertex_indices] = flex.vertex_animations['wrinkle_delta'] for body_part in mdl.body_parts: for model in body_part.models: