FBX IO: Speed up transformation animation import #104870
@ -529,22 +529,25 @@ def blen_read_object_transform_preprocess(fbx_props, fbx_obj, rot_alt_mat, use_p
|
||||
def _blen_read_object_transform_do_anim(transform_data, lcl_translation_mat, lcl_rot_euler, lcl_scale_mat,
|
||||
extra_pre_matrix, extra_post_matrix):
|
||||
"""Specialized version of blen_read_object_transform_do for animation that pre-calculates the non-animated matrices
|
||||
and returns a function that returns base_mat @ geom_mat.
|
||||
and returns a function that calculates (base_mat @ geom_mat). See the comments in blen_read_object_transform_do for
|
||||
a full description of what this function is doing.
|
||||
|
||||
The lcl_translation_mat, lcl_rot_euler and lcl_scale_mat arguments should have their values updated each frame and
|
||||
then the returned function will calculate the updated matrix."""
|
||||
# translation
|
||||
then calling the returned function will calculate the matrix for the current frame.
|
||||
|
||||
extra_pre_matrix and extra_post_matrix are any extra matrices to multiply first/last."""
|
||||
# Translation
|
||||
geom_loc = Matrix.Translation(transform_data.geom_loc)
|
||||
|
||||
# rotation
|
||||
def to_rot(rot, rot_ord):
|
||||
return Euler(convert_deg_to_rad_iter(rot), rot_ord).to_matrix().to_4x4()
|
||||
|
||||
pre_rot = to_rot(transform_data.pre_rot, 'XYZ')
|
||||
pst_rot = to_rot(transform_data.pst_rot, 'XYZ')
|
||||
pst_rot_inv = pst_rot.inverted_safe()
|
||||
geom_rot = to_rot(transform_data.geom_rot, 'XYZ')
|
||||
# Rotation
|
||||
def to_rot_xyz(rot):
|
||||
# All the rotations that can be precalculated have a fixed XYZ order.
|
||||
return Euler(convert_deg_to_rad_iter(rot), 'XYZ').to_matrix().to_4x4()
|
||||
pre_rot = to_rot_xyz(transform_data.pre_rot)
|
||||
pst_rot_inv = to_rot_xyz(transform_data.pst_rot).inverted_safe()
|
||||
geom_rot = to_rot_xyz(transform_data.geom_rot)
|
||||
|
||||
# Offsets and pivots
|
||||
rot_ofs = Matrix.Translation(transform_data.rot_ofs)
|
||||
rot_piv = Matrix.Translation(transform_data.rot_piv)
|
||||
rot_piv_inv = rot_piv.inverted_safe()
|
||||
@ -552,37 +555,36 @@ def _blen_read_object_transform_do_anim(transform_data, lcl_translation_mat, lcl
|
||||
sca_piv = Matrix.Translation(transform_data.sca_piv)
|
||||
sca_piv_inv = sca_piv.inverted_safe()
|
||||
|
||||
# scale
|
||||
# Scale
|
||||
geom_scale = Matrix()
|
||||
geom_scale[0][0], geom_scale[1][1], geom_scale[2][2] = transform_data.geom_sca
|
||||
|
||||
# Some matrices can be combined in advance, using the associative property of matrix multiplication, so that less
|
||||
# matrix multiplication is required each frame.
|
||||
geom_mat = geom_loc @ geom_rot @ geom_scale
|
||||
|
||||
post_lcl_translation = rot_ofs @ rot_piv @ pre_rot
|
||||
post_lcl_rotation = transform_data.rot_alt_mat @ pst_rot_inv @ rot_piv_inv @ sca_ofs @ sca_piv
|
||||
post_lcl_scaling = sca_piv_inv @ geom_mat @ extra_post_matrix
|
||||
|
||||
# Get the bound to_matrix method to avoid re-binding it on each call.
|
||||
lcl_rot_to_matrix = lcl_rot_euler.to_matrix
|
||||
lcl_rot_euler_to_matrix_3x3 = lcl_rot_euler.to_matrix
|
||||
# Get the unbound Matrix.to_4x4 method to avoid having to look it up again on each call.
|
||||
matrix_to_4x4 = Matrix.to_4x4
|
||||
|
||||
if extra_pre_matrix != Matrix():
|
||||
if extra_pre_matrix == Matrix():
|
||||
# There aren't any other matrices that must be multiplied before lcl_translation_mat that extra_pre_matrix can
|
||||
# be combined with, so a separate lambda is required.
|
||||
return lambda: (extra_pre_matrix @
|
||||
lcl_translation_mat @
|
||||
# be combined with, so skip extra_pre_matrix when it's the identity matrix.
|
||||
return lambda: (lcl_translation_mat @
|
||||
post_lcl_translation @
|
||||
matrix_to_4x4(lcl_rot_to_matrix()) @
|
||||
matrix_to_4x4(lcl_rot_euler_to_matrix_3x3()) @
|
||||
post_lcl_rotation @
|
||||
lcl_scale_mat @
|
||||
post_lcl_scaling)
|
||||
else:
|
||||
return lambda: (lcl_translation_mat @
|
||||
return lambda: (extra_pre_matrix @
|
||||
lcl_translation_mat @
|
||||
post_lcl_translation @
|
||||
matrix_to_4x4(lcl_rot_to_matrix()) @
|
||||
matrix_to_4x4(lcl_rot_euler_to_matrix_3x3()) @
|
||||
post_lcl_rotation @
|
||||
lcl_scale_mat @
|
||||
post_lcl_scaling)
|
||||
@ -609,48 +611,46 @@ def _transformation_curves_gen(item, values_arrays, channel_keys):
|
||||
rot_eul_prev = bl_obj.rotation_euler.copy()
|
||||
rot_quat_prev = bl_obj.rotation_quaternion.copy()
|
||||
|
||||
# Pre-compute combined pre matrix
|
||||
# Pre-compute combined pre-matrix
|
||||
# Remove that rest pose matrix from current matrix (also in parent space) by computing the inverted local rest
|
||||
# matrix of the bone, if relevant.
|
||||
combined_pre_matrix = item.get_bind_matrix().inverted_safe() if item.is_bone else Matrix()
|
||||
|
||||
# pre-matrix will contain any correction for a parent's correction matrix or the global matrix
|
||||
# item.pre_matrix will contain any correction for a parent's correction matrix or the global matrix
|
||||
if item.pre_matrix:
|
||||
combined_pre_matrix @= item.pre_matrix
|
||||
|
||||
# Pre-compute combined post matrix
|
||||
# compensate for changes in the local matrix during processing
|
||||
# Pre-compute combined post-matrix
|
||||
# Compensate for changes in the local matrix during processing
|
||||
combined_post_matrix = item.anim_compensation_matrix if item.anim_compensation_matrix else Matrix()
|
||||
|
||||
# post-matrix will contain any correction for lights, camera and bone orientation
|
||||
# item.post_matrix will contain any correction for lights, camera and bone orientation
|
||||
if item.post_matrix:
|
||||
combined_post_matrix @= item.post_matrix
|
||||
|
||||
# Create matrices/euler from the initial transformation values of this item.
|
||||
# These variables will be updated in-place as we iterate through each frame.
|
||||
translation_matrix = Matrix.Translation(transform_data.loc)
|
||||
rotation_euler = Euler(transform_data.rot, transform_data.rot_ord)
|
||||
scaling_matrix = Matrix()
|
||||
scaling_matrix[0][0], scaling_matrix[1][1], scaling_matrix[2][2] = transform_data.sca
|
||||
lcl_translation_mat = Matrix.Translation(transform_data.loc)
|
||||
lcl_rotation_eul = Euler(transform_data.rot, transform_data.rot_ord)
|
||||
lcl_scaling_mat = Matrix()
|
||||
lcl_scaling_mat[0][0], lcl_scaling_mat[1][1], lcl_scaling_mat[2][2] = transform_data.sca
|
||||
|
||||
# Create setters into translation_matrix, rotation_euler and scaling_matrix for each values_array and convert any
|
||||
# rotation values into radians.
|
||||
setters = []
|
||||
# Create setters into lcl_translation_mat, lcl_rotation_eul and lcl_scaling_mat for each values_array and convert
|
||||
# any rotation values into radians.
|
||||
lcl_setters = []
|
||||
values_arrays_converted = []
|
||||
for values_array, (fbx_prop, channel) in zip(values_arrays, channel_keys):
|
||||
if fbx_prop == b'Lcl Translation':
|
||||
# translation_matrix.translation[channel] = value
|
||||
setter = partial(setitem, translation_matrix.translation, channel)
|
||||
# lcl_translation_mat.translation[channel] = value
|
||||
setter = partial(setitem, lcl_translation_mat.translation, channel)
|
||||
elif fbx_prop == b'Lcl Rotation':
|
||||
# FBX rotations are in degrees, but Blender uses radians, so convert all rotation values in advance.
|
||||
values_array = np.deg2rad(values_array)
|
||||
# rotation_euler[channel] = value
|
||||
setter = partial(setitem, rotation_euler, channel)
|
||||
# lcl_rotation_eul[channel] = value
|
||||
setter = partial(setitem, lcl_rotation_eul, channel)
|
||||
else:
|
||||
assert(fbx_prop == b'Lcl Scaling')
|
||||
# scaling_matrix[channel][channel] = value
|
||||
setter = partial(setitem, scaling_matrix[channel], channel)
|
||||
setters.append(setter)
|
||||
# lcl_scaling_mat[channel][channel] = value
|
||||
setter = partial(setitem, lcl_scaling_mat[channel], channel)
|
||||
lcl_setters.append(setter)
|
||||
values_arrays_converted.append(values_array)
|
||||
|
||||
# Create an iterator that gets one value from each array. Each iterated tuple will be all the imported
|
||||
@ -660,33 +660,36 @@ def _transformation_curves_gen(item, values_arrays, channel_keys):
|
||||
# .data, the memoryview of an np.ndarray, is faster to iterate than the ndarray itself.
|
||||
frame_values_it = zip(*(arr.data for arr in values_arrays_converted))
|
||||
|
||||
decompose = Matrix.decompose
|
||||
to_axis_angle = Quaternion.to_axis_angle
|
||||
to_euler = Quaternion.to_euler
|
||||
# Getting the unbound methods in advance avoids having to look them up again on each call within the loop.
|
||||
mat_decompose = Matrix.decompose
|
||||
quat_to_axis_angle = Quaternion.to_axis_angle
|
||||
quat_to_euler = Quaternion.to_euler
|
||||
quat_dot = Quaternion.dot
|
||||
|
||||
calc_mat = _blen_read_object_transform_do_anim(transform_data, translation_matrix, rotation_euler, scaling_matrix,
|
||||
calc_mat = _blen_read_object_transform_do_anim(transform_data,
|
||||
lcl_translation_mat, lcl_rotation_eul, lcl_scaling_mat,
|
||||
combined_pre_matrix, combined_post_matrix)
|
||||
|
||||
# Iterate through the values for each frame.
|
||||
for frame_values in frame_values_it:
|
||||
# Set each value into its corresponding attribute in transform_data.
|
||||
for setter, value in zip(setters, frame_values):
|
||||
setter(value)
|
||||
# Set each value into its corresponding lcl matrix/euler.
|
||||
for lcl_setter, value in zip(lcl_setters, frame_values):
|
||||
lcl_setter(value)
|
||||
|
||||
# Calculate the updated matrix for this frame.
|
||||
mat = calc_mat()
|
||||
|
||||
# Now we have a virtual matrix of transform from AnimCurves, we can yield keyframe values!
|
||||
loc, rot, sca = decompose(mat)
|
||||
loc, rot, sca = mat_decompose(mat)
|
||||
if rot_mode == 'QUATERNION':
|
||||
if rot_quat_prev.dot(rot) < 0.0:
|
||||
if quat_dot(rot_quat_prev, rot) < 0.0:
|
||||
rot = -rot
|
||||
rot_quat_prev = rot
|
||||
elif rot_mode == 'AXIS_ANGLE':
|
||||
vec, ang = to_axis_angle(rot)
|
||||
vec, ang = quat_to_axis_angle(rot)
|
||||
rot = ang, vec.x, vec.y, vec.z
|
||||
else: # Euler
|
||||
rot = to_euler(rot, rot_mode, rot_eul_prev)
|
||||
rot = quat_to_euler(rot, rot_mode, rot_eul_prev)
|
||||
rot_eul_prev = rot
|
||||
|
||||
# Yield order matches the order that the location/rotation/scale FCurves are created in.
|
||||
|
Loading…
Reference in New Issue
Block a user