Fixed PSA import resampling logic
This commit is contained in:
@@ -34,6 +34,7 @@ if 'bpy' in locals():
|
|||||||
importlib.reload(psa_reader)
|
importlib.reload(psa_reader)
|
||||||
importlib.reload(psa_writer)
|
importlib.reload(psa_writer)
|
||||||
importlib.reload(psa_builder)
|
importlib.reload(psa_builder)
|
||||||
|
importlib.reload(psa_importer)
|
||||||
importlib.reload(psa_export_properties)
|
importlib.reload(psa_export_properties)
|
||||||
importlib.reload(psa_export_operators)
|
importlib.reload(psa_export_operators)
|
||||||
importlib.reload(psa_export_ui)
|
importlib.reload(psa_export_ui)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import math
|
|
||||||
import typing
|
import typing
|
||||||
from typing import List, Optional
|
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
|
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.
|
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
|
||||||
@@ -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.
|
@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.
|
@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 = 0.0
|
||||||
time_step = source_frame_count / target_frame_count
|
while time < source_frame_count - 1:
|
||||||
while time <= target_frame_count - 1:
|
|
||||||
yield time
|
yield time
|
||||||
time += time_step
|
time += time_step
|
||||||
yield 1.0
|
yield source_frame_count - 1
|
||||||
|
|
||||||
source_frame_count, bone_count = sequence_data_matrix.shape[:1]
|
if time_step == 1.0:
|
||||||
|
|
||||||
if target_frame_count == source_frame_count:
|
|
||||||
# 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]
|
||||||
|
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)
|
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:
|
if sample_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[frame_index, :, :] = sequence_data_matrix[int(sample_time), :, :]
|
resampled_sequence_data_matrix[sample_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
|
||||||
for bone_index in range(bone_count):
|
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_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
|
factor = sample_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[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
|
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)
|
sequence_data_matrix[frame_index, bone_index] = _calculate_fcurve_data(import_bone, key_data)
|
||||||
|
|
||||||
# Resample the sequence data to the target FPS.
|
# Resample the sequence data to the target FPS.
|
||||||
# TODO: target frame count can be fractional.
|
# If the target frame count is the same as the source frame count, this will be a no-op.
|
||||||
target_frame_count = math.ceil(sequence.frame_count * (target_fps / sequence.fps))
|
resampled_sequence_data_matrix = _resample_sequence_data_matrix(sequence_data_matrix,
|
||||||
|
time_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.
|
||||||
|
target_frame_count = resampled_sequence_data_matrix.shape[0]
|
||||||
fcurve_data = np.zeros(2 * target_frame_count, dtype=float)
|
fcurve_data = np.zeros(2 * target_frame_count, dtype=float)
|
||||||
fcurve_data[0::2] = range(0, target_frame_count)
|
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):
|
for bone_index, import_bone in enumerate(import_bones):
|
||||||
if import_bone is None:
|
if import_bone is None:
|
||||||
continue
|
continue
|
||||||
|
|||||||
Reference in New Issue
Block a user