FBX IO: Speed up animation simplification using NumPy #104904

Merged
Thomas Barlow merged 17 commits from Mysteryem/blender-addons:fbx_anim_export_numpy_simplify into blender-v4.0-release 2023-10-06 17:53:02 +02:00
Showing only changes of commit e796771454 - Show all commits

View File

@ -1314,42 +1314,88 @@ class AnimationCurveNodeWrapper:
min_reldiff_fac = fac * 1.0e-3 # min relative value evolution: 0.1% of current 'order of magnitude'.
min_absdiff_fac = 0.1 # A tenth of reldiff...
are_keyed = []
for values, frame_write_mask in zip(self._frame_values_array, self._frame_write_mask_array):
# Initialise to no frames written.
frame_write_mask[:] = False
# Create views of the 'previous' and 'current' mask and values. The memoryview, .data, of each array is used
# for its iteration and indexing performance compared to the array.
key = values[1:].data
p_key = values[:-1].data
key_write = frame_write_mask[1:].data
p_key_write = frame_write_mask[:-1].data
abs_values = np.abs(values)
# Create views of the 'previous' and 'current' mask and values.
p_val = values[:-1]
abs_p_val = abs_values[:-1]
p_write_mask = frame_write_mask[:-1]
val = values[1:]
abs_val = abs_values[1:]
write_mask = frame_write_mask[1:]
p_keyedval = values[0]
is_keyed = False
for idx, (val, p_val) in enumerate(zip(key, p_key)):
if val == p_val:
# Never write keyframe when value is exactly the same as prev one!
continue
# This is contracted form of relative + absolute-near-zero difference:
# absdiff = abs(a - b)
# if absdiff < min_reldiff_fac * min_absdiff_fac:
# return False
# return (absdiff / ((abs(a) + abs(b)) / 2)) > min_reldiff_fac
# Note that we ignore the '/ 2' part here, since it's not much significant for us.
if abs(val - p_val) > (min_reldiff_fac * max(abs(val) + abs(p_val), min_absdiff_fac)):
enough_diff_p_val_mask = (
np.abs(val - p_val) > (min_reldiff_fac * np.maximum(abs_val + abs_p_val, min_absdiff_fac))
)
# If enough difference from previous sampled value, key this value *and* the previous one!
key_write[idx] = True
p_key_write[idx] = True
p_keyedval = val
is_keyed = True
elif abs(val - p_keyedval) > (min_reldiff_fac * max((abs(val) + abs(p_keyedval)), min_absdiff_fac)):
# Else, if enough difference from previous keyed value, key this value only!
key_write[idx] = True
p_keyedval = val
is_keyed = True
are_keyed.append(is_keyed)
# Unless it is forcefully keyed later, this is the only way that the first value can be keyed.
p_write_mask[enough_diff_p_val_mask] = True
write_mask[enough_diff_p_val_mask] = True
# The other case where we key a value is if there is enough difference between it and the previous keyed
# value.
# Values that equal their previous value can be skipped, so the remaining values to check are those which
# are currently not keyed.
not_keyed_mask = ~write_mask
check_diff_mask = np.logical_and(not_keyed_mask, p_val != val)
val_check_idx = np.flatnonzero(check_diff_mask)
val_check = val[val_check_idx]
abs_val_check = abs_val[val_check_idx]
# For each current value, get the index of the previous keyed value in `values`.
p_keyed_idx = np.arange(1, len(values))
# The first 'previous keyed value' defaults to values[0], even if it's not actually keyed.
p_keyed_idx[not_keyed_mask] = 0
# Accumulative maximum fills in the zeroed indices with the closest previous non-zero index because the
# indices must be increasing.
p_keyed_idx = np.maximum.accumulate(p_keyed_idx)
# Extract only the indices that need checking.
p_keyed_idx_check = p_keyed_idx[val_check_idx]
p_keyed_val_check = values[p_keyed_idx_check]
abs_p_keyed_val_check = np.abs(p_keyed_val_check)
# We check the relative + absolute-near-zero difference again, but against the previous keyed value this
# time.
enough_diff_p_keyed_val_mask = (
np.abs(val_check - p_keyed_val_check)
> (min_reldiff_fac * np.maximum(abs_val_check + abs_p_keyed_val_check, min_absdiff_fac))
)
if np.any(enough_diff_p_keyed_val_mask):
# If there are any that are different enough from the previous keyed value, then we have to check them
# all iteratively because keying a new value can change the previous keyed value of some elements, which
# changes their relative + absolute-near-zero difference.
new_p_keyed_idx = -1
new_p_keyed_val = -1
new_p_keyed_val_abs = -1
# Accessing .data, the memoryview of the array, iteratively or by individual index is faster than doing
# the same with the array itself.
key_write_mv = write_mask.data
zipped = zip(val_check_idx.data, val_check.data, abs_val_check.data, p_keyed_idx_check.data,
enough_diff_p_keyed_val_mask.data)
for cur_idx, cur_val, abs_cur_val, old_p_keyed_idx, enough_diff in zipped:
if new_p_keyed_idx > old_p_keyed_idx:
# The previous keyed value is new and was not included when enough_diff_p_keyed_val_mask was
# calculated, so whether the current value is different enough needs to be calculated.
# Check if the relative + absolute-near-zero difference is enough to key this value.
enough_diff = (abs(cur_val - new_p_keyed_val)
> (min_reldiff_fac * max(abs_cur_val + new_p_keyed_val_abs, min_absdiff_fac)))
if enough_diff:
# The current value needs to be keyed.
key_write_mv[cur_idx] = True
# Update the index and values for this newly keyed value.
new_p_keyed_idx = cur_idx
new_p_keyed_val = cur_val
new_p_keyed_val_abs = abs_cur_val
# If we write nothing (action doing nothing) and are in 'force_keep' mode, we key everything! :P
# See T41766.
@ -1358,7 +1404,9 @@ class AnimationCurveNodeWrapper:
# one key in this case.
# See T41719, T41605, T41254...
if self.force_keying or (force_keep and not self):
are_keyed[:] = [True] * len(are_keyed)
are_keyed = [True] * len(self._frame_write_mask_array)
else:
are_keyed = np.any(self._frame_write_mask_array, axis=1)
# If we did key something, ensure first and last sampled values are keyed as well.
if self.force_startend_keying: