diff --git a/io_scene_psk_psa/__init__.py b/io_scene_psk_psa/__init__.py index 7570fe9..4b2589f 100644 --- a/io_scene_psk_psa/__init__.py +++ b/io_scene_psk_psa/__init__.py @@ -34,6 +34,7 @@ if 'bpy' in locals(): importlib.reload(psa_reader) importlib.reload(psa_writer) importlib.reload(psa_builder) + importlib.reload(psa_importer) importlib.reload(psa_export_properties) importlib.reload(psa_export_operators) importlib.reload(psa_export_ui) diff --git a/io_scene_psk_psa/psa/importer.py b/io_scene_psk_psa/psa/importer.py index d2f5a18..aad193f 100644 --- a/io_scene_psk_psa/psa/importer.py +++ b/io_scene_psk_psa/psa/importer.py @@ -1,4 +1,3 @@ -import math import typing from typing import List, Optional @@ -81,7 +80,7 @@ def _get_armature_bone_index_for_psa_bone(psa_bone_name: str, armature_bone_name return None -def _resample_sequence_data_matrix(sequence_data_matrix: np.ndarray, target_frame_count: int): +def _resample_sequence_data_matrix(sequence_data_matrix: np.ndarray, time_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 @@ -89,38 +88,40 @@ def _resample_sequence_data_matrix(sequence_data_matrix: np.ndarray, target_fram @param target_frame_count: The number of frames to resample to. @return: The resampled sequence data matrix, or sequence_data_matrix if no resampling is necessary. ''' - def get_sample_times(source_frame_count: int, target_frame_count: int) -> typing.Iterable[float]: + 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 - time_step = source_frame_count / target_frame_count - while time <= target_frame_count - 1: + while time < source_frame_count - 1: yield time time += time_step - yield 1.0 + yield source_frame_count - 1 - source_frame_count, bone_count = sequence_data_matrix.shape[:1] - - if target_frame_count == source_frame_count: + if time_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) resampled_sequence_data_matrix = np.zeros((target_frame_count, bone_count, 7), dtype=float) - sample_times = get_sample_times(source_frame_count, target_frame_count) - for frame_index, sample_time in enumerate(sample_times): + for sample_index, sample_time in enumerate(sample_times): + frame_index = int(sample_time) if sample_time % 1.0 == 0.0: # Sample time has no fractional part, so just copy the frame. - resampled_sequence_data_matrix[frame_index, :, :] = sequence_data_matrix[int(sample_time), :, :] + resampled_sequence_data_matrix[sample_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): - frame_index = int(sample_time) source_frame_1_data = sequence_data_matrix[frame_index, bone_index, :] - source_frame_2_data = sequence_data_matrix[frame_index + 1, bone_index, :] + source_frame_2_data = sequence_data_matrix[next_frame_index, bone_index, :] factor = sample_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[frame_index, bone_index, :] = q.w, q.x, q.y, q.z, l.x, l.y, l.z + resampled_sequence_data_matrix[sample_index, bone_index, :] = q.w, q.x, q.y, q.z, l.x, l.y, l.z return resampled_sequence_data_matrix @@ -268,18 +269,16 @@ def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object, sequence_data_matrix[frame_index, bone_index] = _calculate_fcurve_data(import_bone, key_data) # Resample the sequence data to the target FPS. - # TODO: target frame count can be fractional. - target_frame_count = math.ceil(sequence.frame_count * (target_fps / sequence.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) # Write the keyframes out. # Note that the f-curve data consists of alternating time and value data. + target_frame_count = resampled_sequence_data_matrix.shape[0] fcurve_data = np.zeros(2 * target_frame_count, dtype=float) fcurve_data[0::2] = range(0, target_frame_count) - # Resample the sequence data matrix to the target frame count. - # 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, target_frame_count) - for bone_index, import_bone in enumerate(import_bones): if import_bone is None: continue