Reorganizing & renaming some things for clarity and correctness

This commit is contained in:
Colin Basnett
2024-03-14 19:08:32 -07:00
parent d4d46bea66
commit fb02742381
2 changed files with 32 additions and 31 deletions

View File

@@ -75,13 +75,13 @@ class PSA_PG_import(PropertyGroup):
) )
fps_source: EnumProperty(name='FPS Source', items=( fps_source: EnumProperty(name='FPS Source', items=(
('SEQUENCE', 'Sequence', 'The sequence frame rate matches the original frame rate', 'ACTION', 0), ('SEQUENCE', 'Sequence', 'The sequence frame rate matches the original frame rate', 'ACTION', 0),
('SCENE', 'Scene', 'The sequence frame rate dilates to match that of the scene', 'SCENE_DATA', 1), ('SCENE', 'Scene', 'The sequence is resampled to the frame rate of the scene', 'SCENE_DATA', 1),
('CUSTOM', 'Custom', 'The sequence frame rate dilates to match a custom frame rate', 2), ('CUSTOM', 'Custom', 'The sequence is resampled to a custom frame rate', 2),
)) ))
fps_custom: FloatProperty( fps_custom: FloatProperty(
default=30.0, default=30.0,
name='Custom FPS', name='Custom FPS',
description='The frame rate to which the imported actions will be converted', description='The frame rate to which the imported sequences will be resampled to',
options=empty_set, options=empty_set,
min=1.0, min=1.0,
soft_min=1.0, soft_min=1.0,

View File

@@ -46,16 +46,16 @@ def _calculate_fcurve_data(import_bone: ImportBone, key_data: typing.Iterable[fl
key_location = Vector(key_data[4:]) key_location = Vector(key_data[4:])
q = import_bone.post_rotation.copy() q = import_bone.post_rotation.copy()
q.rotate(import_bone.original_rotation) q.rotate(import_bone.original_rotation)
quat = q rotation = q
q = import_bone.post_rotation.copy() q = import_bone.post_rotation.copy()
if import_bone.parent is None: if import_bone.parent is None:
q.rotate(key_rotation.conjugated()) q.rotate(key_rotation.conjugated())
else: else:
q.rotate(key_rotation) q.rotate(key_rotation)
quat.rotate(q.conjugated()) rotation.rotate(q.conjugated())
loc = key_location - import_bone.original_location location = key_location - import_bone.original_location
loc.rotate(import_bone.post_rotation.conjugated()) location.rotate(import_bone.post_rotation.conjugated())
return quat.w, quat.x, quat.y, quat.z, loc.x, loc.y, loc.z return rotation.w, rotation.x, rotation.y, rotation.z, location.x, location.y, location.z
class PsaImportResult: class PsaImportResult:
@@ -79,49 +79,48 @@ def _get_armature_bone_index_for_psa_bone(psa_bone_name: str, armature_bone_name
return armature_bone_index return armature_bone_index
return None return None
def _get_sample_frame_times(source_frame_count: int, frame_step: float) -> typing.Iterable[float]:
# TODO: for correctness, we should also emit the target frame time as well (because the last frame can be a
# fractional frame).
time = 0.0
while time < source_frame_count - 1:
yield time
time += frame_step
yield source_frame_count - 1
def _resample_sequence_data_matrix(sequence_data_matrix: np.ndarray, time_step: float = 1.0) -> np.ndarray: def _resample_sequence_data_matrix(sequence_data_matrix: np.ndarray, frame_step: float = 1.0) -> np.ndarray:
''' """
Resamples the sequence data matrix to the target frame count. Resamples the sequence data matrix to the target frame count.
@param sequence_data_matrix: FxBx7 matrix where F is the number of frames, B is the number of bones, and X is the @param sequence_data_matrix: FxBx7 matrix where F is the number of frames, B is the number of bones, and X is the
number of data elements per bone. number of data elements per bone.
@param target_frame_count: The number of frames to resample to. @param frame_step: The step between frames in the resampled sequence.
@return: The resampled sequence data matrix, or sequence_data_matrix if no resampling is necessary. @return: The resampled sequence data matrix, or sequence_data_matrix if no resampling is necessary.
''' """
def get_sample_times(source_frame_count: int, time_step: float) -> typing.Iterable[float]: if frame_step == 1.0:
# TODO: for correctness, we should also emit the target frame time as well (because the last frame can be a
# fractional frame).
time = 0.0
while time < source_frame_count - 1:
yield time
time += time_step
yield source_frame_count - 1
if time_step == 1.0:
# No resampling is necessary. # No resampling is necessary.
return sequence_data_matrix return sequence_data_matrix
source_frame_count, bone_count = sequence_data_matrix.shape[:2] source_frame_count, bone_count = sequence_data_matrix.shape[:2]
sample_times = list(get_sample_times(source_frame_count, time_step)) sample_frame_times = list(_get_sample_frame_times(source_frame_count, frame_step))
target_frame_count = len(sample_times) target_frame_count = len(sample_frame_times)
resampled_sequence_data_matrix = np.zeros((target_frame_count, bone_count, 7), dtype=float) resampled_sequence_data_matrix = np.zeros((target_frame_count, bone_count, 7), dtype=float)
for sample_index, sample_time in enumerate(sample_times): for sample_frame_index, sample_frame_time in enumerate(sample_frame_times):
frame_index = int(sample_time) frame_index = int(sample_frame_time)
if sample_time % 1.0 == 0.0: if sample_frame_time % 1.0 == 0.0:
# Sample time has no fractional part, so just copy the frame. # Sample time has no fractional part, so just copy the frame.
resampled_sequence_data_matrix[sample_index, :, :] = sequence_data_matrix[frame_index, :, :] resampled_sequence_data_matrix[sample_frame_index, :, :] = sequence_data_matrix[frame_index, :, :]
else: else:
# Sample time has a fractional part, so interpolate between two frames. # Sample time has a fractional part, so interpolate between two frames.
next_frame_index = frame_index + 1 next_frame_index = frame_index + 1
for bone_index in range(bone_count): for bone_index in range(bone_count):
source_frame_1_data = sequence_data_matrix[frame_index, bone_index, :] source_frame_1_data = sequence_data_matrix[frame_index, bone_index, :]
source_frame_2_data = sequence_data_matrix[next_frame_index, bone_index, :] source_frame_2_data = sequence_data_matrix[next_frame_index, bone_index, :]
factor = sample_time - frame_index factor = sample_frame_time - frame_index
q = Quaternion((source_frame_1_data[:4])).slerp(Quaternion((source_frame_2_data[:4])), factor) q = Quaternion((source_frame_1_data[:4])).slerp(Quaternion((source_frame_2_data[:4])), factor)
q.normalize() q.normalize()
l = Vector(source_frame_1_data[4:]).lerp(Vector(source_frame_2_data[4:]), factor) l = Vector(source_frame_1_data[4:]).lerp(Vector(source_frame_2_data[4:]), factor)
resampled_sequence_data_matrix[sample_index, bone_index, :] = q.w, q.x, q.y, q.z, l.x, l.y, l.z resampled_sequence_data_matrix[sample_frame_index, bone_index, :] = q.w, q.x, q.y, q.z, l.x, l.y, l.z
return resampled_sequence_data_matrix return resampled_sequence_data_matrix
@@ -188,8 +187,10 @@ def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object,
for import_bone in filter(lambda x: x is not None, import_bones): for import_bone in filter(lambda x: x is not None, import_bones):
armature_bone = import_bone.armature_bone armature_bone = import_bone.armature_bone
if armature_bone.parent is not None and armature_bone.parent.name in psa_bone_names: if armature_bone.parent is not None and armature_bone.parent.name in psa_bone_names:
import_bone.parent = import_bones_dict[armature_bone.parent.name] import_bone.parent = import_bones_dict[armature_bone.parent.name]
# Calculate the original location & rotation of each bone (in world-space maybe?) # Calculate the original location & rotation of each bone (in world-space maybe?)
if import_bone.parent is not None: if import_bone.parent is not None:
import_bone.original_location = armature_bone.matrix_local.translation - armature_bone.parent.matrix_local.translation import_bone.original_location = armature_bone.matrix_local.translation - armature_bone.parent.matrix_local.translation
@@ -272,7 +273,7 @@ def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object,
# Resample the sequence data to the target FPS. # Resample the sequence data to the target FPS.
# If the target frame count is the same as the source frame count, this will be a no-op. # If the target frame count is the same as the source frame count, this will be a no-op.
resampled_sequence_data_matrix = _resample_sequence_data_matrix(sequence_data_matrix, resampled_sequence_data_matrix = _resample_sequence_data_matrix(sequence_data_matrix,
time_step=sequence.fps / target_fps) frame_step=sequence.fps / target_fps)
# Write the keyframes out. # Write the keyframes out.
# Note that the f-curve data consists of alternating time and value data. # Note that the f-curve data consists of alternating time and value data.