diff --git a/io_scene_psk_psa/psa/import_/properties.py b/io_scene_psk_psa/psa/import_/properties.py index 43dd375..0664eb8 100644 --- a/io_scene_psk_psa/psa/import_/properties.py +++ b/io_scene_psk_psa/psa/import_/properties.py @@ -75,13 +75,13 @@ class PSA_PG_import(PropertyGroup): ) fps_source: EnumProperty(name='FPS Source', items=( ('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), - ('CUSTOM', 'Custom', 'The sequence frame rate dilates to match a custom frame rate', 2), + ('SCENE', 'Scene', 'The sequence is resampled to the frame rate of the scene', 'SCENE_DATA', 1), + ('CUSTOM', 'Custom', 'The sequence is resampled to a custom frame rate', 2), )) fps_custom: FloatProperty( default=30.0, 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, min=1.0, soft_min=1.0, diff --git a/io_scene_psk_psa/psa/importer.py b/io_scene_psk_psa/psa/importer.py index cedef80..c307413 100644 --- a/io_scene_psk_psa/psa/importer.py +++ b/io_scene_psk_psa/psa/importer.py @@ -46,16 +46,16 @@ def _calculate_fcurve_data(import_bone: ImportBone, key_data: typing.Iterable[fl key_location = Vector(key_data[4:]) q = import_bone.post_rotation.copy() q.rotate(import_bone.original_rotation) - quat = q + rotation = q q = import_bone.post_rotation.copy() if import_bone.parent is None: q.rotate(key_rotation.conjugated()) else: q.rotate(key_rotation) - quat.rotate(q.conjugated()) - loc = key_location - import_bone.original_location - loc.rotate(import_bone.post_rotation.conjugated()) - return quat.w, quat.x, quat.y, quat.z, loc.x, loc.y, loc.z + rotation.rotate(q.conjugated()) + location = key_location - import_bone.original_location + location.rotate(import_bone.post_rotation.conjugated()) + return rotation.w, rotation.x, rotation.y, rotation.z, location.x, location.y, location.z 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 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. @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. - @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. - ''' - def get_sample_times(source_frame_count: int, time_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 += time_step - yield source_frame_count - 1 - - if time_step == 1.0: + """ + if frame_step == 1.0: # No resampling is necessary. return sequence_data_matrix source_frame_count, bone_count = sequence_data_matrix.shape[:2] - sample_times = list(get_sample_times(source_frame_count, time_step)) - target_frame_count = len(sample_times) + sample_frame_times = list(_get_sample_frame_times(source_frame_count, frame_step)) + target_frame_count = len(sample_frame_times) resampled_sequence_data_matrix = np.zeros((target_frame_count, bone_count, 7), dtype=float) - for sample_index, sample_time in enumerate(sample_times): - frame_index = int(sample_time) - if sample_time % 1.0 == 0.0: + for sample_frame_index, sample_frame_time in enumerate(sample_frame_times): + frame_index = int(sample_frame_time) + if sample_frame_time % 1.0 == 0.0: # 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: # Sample time has a fractional part, so interpolate between two frames. next_frame_index = frame_index + 1 for bone_index in range(bone_count): source_frame_1_data = sequence_data_matrix[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.normalize() 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 @@ -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): armature_bone = import_bone.armature_bone + 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] + # Calculate the original location & rotation of each bone (in world-space maybe?) if import_bone.parent is not None: 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. # 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, - time_step=sequence.fps / target_fps) + frame_step=sequence.fps / target_fps) # Write the keyframes out. # Note that the f-curve data consists of alternating time and value data.