Checkpoint commit

This commit is contained in:
Colin Basnett
2026-01-06 23:07:45 -08:00
parent efe845bf4a
commit a5eba2b6d9
3 changed files with 67 additions and 68 deletions

View File

@@ -48,28 +48,16 @@ class PsaBuildOptions:
def _get_pose_bone_location_and_rotation(
pose_bone: PoseBone | None,
armature_object: Object | None,
pose_bone: PoseBone,
armature_object: Object,
export_space: str,
scale: Vector,
coordinate_system_transform: Matrix,
has_false_root_bone: bool,
) -> Tuple[Vector, Quaternion]:
is_false_root_bone = pose_bone is None and armature_object is None
# TODO: this is such a disaster; the false root bone idea needs revising.
if is_false_root_bone:
pose_bone_matrix = coordinate_system_transform
elif pose_bone is not None and pose_bone.parent is not None:
pose_bone_matrix = pose_bone.matrix
pose_bone_parent_matrix = pose_bone.parent.matrix
pose_bone_matrix = pose_bone_parent_matrix.inverted() @ pose_bone_matrix
if pose_bone.parent is not None:
pose_bone_matrix = pose_bone.parent.matrix.inverted() @ pose_bone.matrix
else:
# Root bone
if has_false_root_bone:
pose_bone_matrix = armature_object.matrix_world @ pose_bone.matrix
else:
# Get the bone's pose matrix and transform it into the export space.
# In the case of an 'ARMATURE' export space, this will be the inverse of armature object's world matrix.
# Otherwise, it will be the identity matrix.
@@ -90,16 +78,10 @@ def _get_pose_bone_location_and_rotation(
location = pose_bone_matrix.to_translation()
rotation = pose_bone_matrix.to_quaternion().normalized()
if pose_bone.parent is not None:
# Don't apply scale to the root bone of armatures if we have a false root.
if not has_false_root_bone or (pose_bone is None or pose_bone.parent is not None):
# TODO: probably remove this?
location *= scale
if has_false_root_bone:
is_child_bone = not is_false_root_bone
else:
is_child_bone = pose_bone.parent is not None
if is_child_bone:
rotation.conjugate()
return location, rotation
@@ -218,12 +200,12 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
psa.keys.append(key)
class PsaExportBone:
def __init__(self, pose_bone: Optional[PoseBone], armature_object: Optional[Object], scale: Vector):
def __init__(self, pose_bone: PoseBone | None, armature_object: Object | None, scale: Vector):
self.pose_bone = pose_bone
self.armature_object = armature_object
self.scale = scale
armature_scales: Dict[Object, Vector] = {}
armature_scales: dict[Object, Vector] = {}
# Extract the scale from the world matrix of the evaluated armature object.
for armature_object in options.armature_objects:
@@ -235,7 +217,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
# Create a list of export pose bones, in the same order as the bones as they appear in the armature.
# The object contains the pose bone, the armature object, and a pre-calculated scaling value to apply to the
# locations.
export_bones: List[PsaExportBone] = []
export_bones: list[PsaExportBone] = []
for psx_bone, armature_object in psx_bone_create_result.bones:
if armature_object is None:
@@ -250,11 +232,11 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
match options.sampling_mode:
case 'INTERPOLATED':
# Used as a store for the last frame's pose bone locations and rotations.
last_frame: Optional[int] = None
last_frame_bone_poses: List[Tuple[Vector, Quaternion]] = []
last_frame: int | None = None
last_frame_bone_poses: list[tuple[Vector, Quaternion]] = []
next_frame: Optional[int] = None
next_frame_bone_poses: List[Tuple[Vector, Quaternion]] = []
next_frame: int | None = None
next_frame_bone_poses: list[tuple[Vector, Quaternion]] = []
for _ in range(frame_count):
if last_frame is None or last_frame != int(frame):
@@ -275,8 +257,8 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
export_bone.armature_object,
options.export_space,
export_bone.scale,
coordinate_system_transform=coordinate_system_transform,
has_false_root_bone=psx_bone_create_result.has_false_root_bone,
coordinate_system_transform=coordinate_system_transform
# has_false_root_bone=psx_bone_create_result.has_false_root_bone,
)
last_frame_bone_poses.append((location, rotation))
@@ -299,7 +281,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
export_space=options.export_space,
scale=export_bone.scale,
coordinate_system_transform=coordinate_system_transform,
has_false_root_bone=psx_bone_create_result.has_false_root_bone,
# has_false_root_bone=psx_bone_create_result.has_false_root_bone,
)
next_frame_bone_poses.append((location, rotation))
@@ -326,7 +308,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
export_space=options.export_space,
scale=export_bone.scale,
coordinate_system_transform=coordinate_system_transform,
has_false_root_bone=psx_bone_create_result.has_false_root_bone,
# has_false_root_bone=psx_bone_create_result.has_false_root_bone,
)
add_key(location, rotation)

View File

@@ -238,7 +238,7 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil
case _:
assert False, f'Invalid export space: {options.export_space}'
vertex_transform_matrix = scale_matrix @ coordinate_system_matrix @ mesh_export_space_matrix
vertex_transform_matrix = scale_matrix @ coordinate_system_matrix.inverted() @ mesh_export_space_matrix
point_transform_matrix = vertex_transform_matrix @ mesh_object.matrix_world
# Vertices

View File

@@ -172,18 +172,12 @@ def convert_string_to_cp1252_bytes(string: str) -> bytes:
def create_psx_bones_from_blender_bones(
bones: List[bpy.types.Bone],
armature_object_matrix_world: Matrix,
forward_axis: str = 'X',
up_axis: str = 'Z',
) -> List[PsxBone]:
"""
Creates PSX bones from the given Blender bones.
The bones are in world space based on the armature object's world matrix.
"""
coordinate_system_transform = get_coordinate_system_transform(forward_axis, up_axis)
coordinate_system_default_rotation = coordinate_system_transform.to_quaternion()
# Apply the scale of the armature object to the bone location.
_, _, armature_object_scale = armature_object_matrix_world.decompose()
@@ -209,11 +203,9 @@ def create_psx_bones_from_blender_bones(
location = (parent_tail - parent_head) + bone.head
else:
location = armature_object_matrix_world @ bone.head
location = coordinate_system_transform @ location
bone_rotation = bone.matrix.to_quaternion().conjugated()
rotation = bone_rotation @ armature_object_matrix_world.to_3x3().to_quaternion()
rotation.conjugate()
rotation = coordinate_system_default_rotation @ rotation
location.x *= armature_object_scale.x
location.y *= armature_object_scale.y
@@ -352,9 +344,6 @@ def create_psx_bones(
raise RuntimeError(f'When exporting multiple armatures, the Export Space must be World.\n' \
f'The following armatures are attempting to be exported: {root_armature_names}')
coordinate_system_matrix = get_coordinate_system_transform(forward_axis, up_axis)
coordinate_system_default_rotation = coordinate_system_matrix.to_quaternion()
total_bone_count = 0
for armature_object in filter(lambda x: x.data is not None, armature_objects):
armature_data = typing_cast(Armature, armature_object.data)
@@ -384,7 +373,7 @@ def create_psx_bones(
# requirement that a PSK file must have at least one bone.
psx_bone = PsxBone()
psx_bone.name = convert_string_to_cp1252_bytes(root_bone_name)
psx_bone.rotation = convert_bpy_quaternion_to_psx_quaternion(coordinate_system_default_rotation)
psx_bone.rotation = Quaternion(0.0, 0.0, 0.0, 1.0)
bones.append((psx_bone, None))
armature_object_root_bone_indices[None] = 0
@@ -394,7 +383,7 @@ def create_psx_bones(
psx_bone = PsxBone()
psx_bone.name = convert_string_to_cp1252_bytes(root_bone_name)
psx_bone.children_count = total_bone_count
psx_bone.rotation = convert_bpy_quaternion_to_psx_quaternion(coordinate_system_default_rotation)
psx_bone.rotation = Quaternion(0.0, 0.0, 0.0, 1.0)
bones.append((psx_bone, None))
armature_object_root_bone_indices[None] = 0
@@ -408,8 +397,6 @@ def create_psx_bones(
armature_psx_bones = create_psx_bones_from_blender_bones(
bones=armature_bones,
armature_object_matrix_world=armature_object.matrix_world,
forward_axis=forward_axis,
up_axis=up_axis,
)
# We have the bones in world space. If we are attaching this armature to a parent bone, we need to convert
@@ -541,6 +528,36 @@ def create_psx_bones(
f''
raise RuntimeError(error_message)
# Apply the scale to the bone locations.
for psx_bone, _ in bones:
psx_bone.location.x *= scale
psx_bone.location.y *= scale
psx_bone.location.z *= scale
coordinate_system_matrix = get_coordinate_system_transform(forward_axis, up_axis)
coordinate_system_default_rotation = coordinate_system_matrix.to_quaternion()
# Apply the coordinate system transform to the root bone.
root_psx_bone = bones[0][0]
# Get transform matrix from root bone location and rotation.
root_bone_location = Vector((root_psx_bone.location.x, root_psx_bone.location.y, root_psx_bone.location.z))
root_bone_rotation = BpyQuaternion((root_psx_bone.rotation.w, root_psx_bone.rotation.x, root_psx_bone.rotation.y, root_psx_bone.rotation.z))
root_bone_matrix = (
Matrix.Translation(root_bone_location) @
root_bone_rotation.to_matrix().to_4x4()
)
root_bone_matrix = coordinate_system_default_rotation.inverted().to_matrix().to_4x4() @ root_bone_matrix
location, rotation, _ = root_bone_matrix.decompose()
root_psx_bone.location.x = location.x
root_psx_bone.location.y = location.y
root_psx_bone.location.z = location.z
root_psx_bone.rotation.w = rotation.w
root_psx_bone.rotation.x = rotation.x
root_psx_bone.rotation.y = rotation.y
root_psx_bone.rotation.z = rotation.z
convert_bpy_quaternion_to_psx_quaternion(coordinate_system_default_rotation)
return PsxBoneCreateResult(
bones=bones,
armature_object_root_bone_indices=armature_object_root_bone_indices,