Animation: NLA Vertical Reorder #107990

Merged
Nate Rupsis merged 35 commits from nrupsis/blender:NLA-vertical-shuffle-core into main 2023-06-20 17:10:15 +02:00
2 changed files with 254 additions and 42 deletions

View File

@ -55,6 +55,11 @@ typedef struct TransDataNla {
/** index of track that strip is currently in. */
int trackIndex;
/** NOTE: This index is relative to the initial first track at the start of transforming and
nrupsis marked this conversation as resolved

* thus can be negative when the tracks list grows downward. */
int signed_track_index;
/** handle-index: 0 for dummy entry, -1 for start, 1 for end, 2 for both ends. */
int handle;
} TransDataNla;
@ -143,6 +148,146 @@ static float transdata_get_time_shuffle_offset(ListBase *trans_datas)
return -offset_left < offset_right ? offset_left : offset_right;
}
/** Assumes all of given trans_datas are part of the same ID.
*
*
* \param shuffle_direction: the direction the strip is traveling. 1 is towards the bottom
* of the stack, -1 is away from it.
*
* \param r_total_offset: The minimal total signed offset that results in valid strip track-moves
* for all strips from \a trans_datas.
*
* \returns true if \a r_total_offset results in a valid offset, false if no solution exists in the
* desired direction.
*/
static bool transdata_get_track_shuffle_offset_side(ListBase *trans_datas,
const int shuffle_direction,
int *r_total_offset)
nrupsis marked this conversation as resolved Outdated

This you may want to split out a little bit. If you get a NULL dereference it's going to be very hard to figure out which -> caused it. Something like this should work (but do test before committing ;-) )

  LinkData *first_link = trans_datas->first;
  TransDataNla *first_transdata = first_link->data;
  AnimData *adt = BKE_animdata_from_id(first_transdata->id);
  ListBase *tracks = &adt->nla_tracks;
This you may want to split out a little bit. If you get a NULL dereference it's going to be very hard to figure out which `->` caused it. Something like this should work (but do test before committing ;-) ) ```c LinkData *first_link = trans_datas->first; TransDataNla *first_transdata = first_link->data; AnimData *adt = BKE_animdata_from_id(first_transdata->id); ListBase *tracks = &adt->nla_tracks; ```
{
*r_total_offset = 0;
if (BLI_listbase_is_empty(trans_datas)) {
return false;
}
LinkData *first_link = trans_datas->first;
TransDataNla *first_transdata = first_link->data;
AnimData *adt = BKE_animdata_from_id(first_transdata->id);
ListBase *tracks = &adt->nla_tracks;
int offset;
do {
offset = 0;
LISTBASE_FOREACH (LinkData *, link, trans_datas) {
TransDataNla *trans_data = (TransDataNla *)link->data;
NlaTrack *dst_track = BLI_findlink(tracks, trans_data->trackIndex + *r_total_offset);
/* Cannot keep moving strip in given track direction. No solution. */
if (dst_track == NULL) {
return false;
nrupsis marked this conversation as resolved Outdated

Instead of having a boolean shuffle_down, use an int shuffle_direction which can be either -1 or 1. That way you don't have to do this, and the direction is more clear from the call site of the function.

One thing that's weird, though, is that the shuffle_down == true case selects a positive offset here, but in the code down below you have fabs(offset_down) < offset_up, indicating that the "up" direction is actually positive and the "down" direction is negative.

During testing I haven't been able to get any offset that was non-zero, so I found it tricky to either prove or disprove my own comment.

Instead of having a boolean `shuffle_down`, use an `int shuffle_direction` which can be either `-1` or `1`. That way you don't have to do this, and the direction is more clear from the call site of the function. One thing that's weird, though, is that the `shuffle_down == true` case selects a **positive** offset here, but in the code down below you have `fabs(offset_down) < offset_up`, indicating that the "up" direction is actually positive and the "down" direction is negative. During testing I haven't been able to get any offset that was non-zero, so I found it tricky to either prove or disprove my own comment.

During testing I haven't been able to get any offset that was non-zero, so I found it tricky to either prove or disprove my own comment.

Got it, it required me to lock some tracks. So yes, the offset_down is positive, and offset_up is negative, and thus the fabs() is on the wrong one.

> During testing I haven't been able to get any offset that was non-zero, so I found it tricky to either prove or disprove my own comment. Got it, it required me to lock some tracks. So yes, the `offset_down` is positive, and `offset_up` is negative, and thus the `fabs()` is on the wrong one.
}
/* Shuffle only if track is locked or library override. */
if (((dst_track->flag & NLATRACK_PROTECTED) == 0) &&
!BKE_nlatrack_is_nonlocal_in_liboverride(trans_data->id, dst_track))
{
continue;
}
offset = shuffle_direction;
break;
}
*r_total_offset += offset;
} while (offset != 0);
return true;
}
/** Assumes all of given trans_datas are part of the same ID.
*
* \param r_track_offset: The minimal total signed offset that results in valid strip track-moves
* for all strips from \a trans_datas.
*
* \returns true if \a r_track_offset results in a valid offset, false if no solution exists in
* either direction.
*/
nrupsis marked this conversation as resolved

Not for this patch, but for future reference: we may want to have a function series minabs_ff(float a, float b) (with more _fff and also _dd and _ii just like the min_... and max_... functions) that return the value for which the absolute value is the smallest.

Not for this patch, but for future reference: we may want to have a function series `minabs_ff(float a, float b)` (with more `_fff` and also `_dd` and `_ii` just like the `min_...` and `max_...` functions) that return the value for which the absolute value is the smallest.
static bool transdata_get_track_shuffle_offset(ListBase *trans_datas, int *r_track_offset)
{
int offset_down = 0;
const bool down_valid = transdata_get_track_shuffle_offset_side(trans_datas, 1, &offset_down);
int offset_up = 0;
const bool up_valid = transdata_get_track_shuffle_offset_side(trans_datas, -1, &offset_up);
if (down_valid && up_valid) {
if (offset_down < abs(offset_up)) {
*r_track_offset = offset_down;
}
else {
*r_track_offset = offset_up;
}
}
else if (down_valid) {
*r_track_offset = offset_down;
}
else if (up_valid) {
*r_track_offset = offset_up;
}
return down_valid || up_valid;
}
/* -------------------------------------------------------------------- */
/** \name Transform application to NLA strips
* \{ */
nrupsis marked this conversation as resolved Outdated

use LISTBASE_FOREACH

use `LISTBASE_FOREACH`
/** \} */
static void nlatrack_truncate_temporary_tracks(bAnimContext *ac)
{
short filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_ANIMDATA);
ListBase anim_data = {NULL, NULL};
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
ListBase *nla_tracks = &ale->adt->nla_tracks;
/** Remove top tracks that weren't necessary. */
LISTBASE_FOREACH_BACKWARD_MUTABLE (NlaTrack *, track, nla_tracks) {
if (!(track->flag & NLATRACK_TEMPORARILY_ADDED)) {
break;
}
if (track->strips.first != NULL) {
break;
}
BKE_nlatrack_remove_and_free(nla_tracks, track, true);
}
/** Remove bottom tracks that weren't necessary. */
LISTBASE_FOREACH_MUTABLE (NlaTrack *, track, nla_tracks) {
/** Library override tracks are the first N tracks. They're never temporary and determine
* where we start removing temporaries.*/
if ((track->flag & NLATRACK_OVERRIDELIBRARY_LOCAL) == 0) {
continue;
}
if (!(track->flag & NLATRACK_TEMPORARILY_ADDED)) {
break;
}
if (track->strips.first != NULL) {
break;
}
BKE_nlatrack_remove_and_free(nla_tracks, track, true);
}
/** Clear temporary flag. */
LISTBASE_FOREACH_MUTABLE (NlaTrack *, track, nla_tracks) {
track->flag &= ~NLATRACK_TEMPORARILY_ADDED;
}
}
ANIM_animdata_freelist(&anim_data);
}
/* -------------------------------------------------------------------- */
/** \name Transform application to NLA strips
* \{ */
@ -400,6 +545,7 @@ static void createTransNlaData(bContext *C, TransInfo *t)
tdn->oldTrack = tdn->nlt = nlt;
tdn->strip = strip;
tdn->trackIndex = BLI_findindex(&adt->nla_tracks, nlt);
tdn->signed_track_index = tdn->trackIndex;
yval = (float)(tdn->trackIndex * NLACHANNEL_STEP(snla));
@ -581,56 +727,83 @@ static void recalcData_nla(TransInfo *t)
continue;
}
delta_y1 = ((int)tdn->h1[1] / NLACHANNEL_STEP(snla) - tdn->trackIndex);
delta_y2 = ((int)tdn->h2[1] / NLACHANNEL_STEP(snla) - tdn->trackIndex);
delta_y1 = ((int)tdn->h1[1] / NLACHANNEL_STEP(snla) - tdn->signed_track_index);
delta_y2 = ((int)tdn->h2[1] / NLACHANNEL_STEP(snla) - tdn->signed_track_index);
/* Move strip into track in the requested direction. */
if (delta_y1 || delta_y2) {
NlaTrack *track;
int delta = (delta_y2) ? delta_y2 : delta_y1;
int n;
/* Move in the requested direction,
* checking at each layer if there's space for strip to pass through,
* stopping on the last track available or that we're able to fit in.
AnimData *anim_data = BKE_animdata_from_id(tdn->id);
ListBase *nla_tracks = &anim_data->nla_tracks;
NlaTrack *old_track = tdn->nlt;
NlaTrack *dst_track = NULL;
/* Calculate the total new tracks needed
*
* Determine dst_track, which will end up being NULL, the last library override
* track, or a normal local track. The first two cases lead to delta_new_tracks!=0.
* The last case leads to delta_new_tracks==0.
*/
if (delta > 0) {
for (track = tdn->nlt->next, n = 0; (track) && (n < delta); track = track->next, n++) {
/* check if space in this track for the strip */
if (BKE_nlatrack_has_space(track, strip->start, strip->end) &&
!BKE_nlatrack_is_nonlocal_in_liboverride(tdn->id, track))
{
/* move strip to this track */
BKE_nlatrack_remove_strip(tdn->nlt, strip);
BKE_nlatrack_add_strip(track, strip, is_liboverride);
int delta_new_tracks = delta;
tdn->nlt = track;
tdn->trackIndex++;
}
else { /* can't move any further */
break;
}
}
/* it's possible to drag a strip fast enough to make delta > |1|. We only want to process
* 1 track shift at a time.
*/
CLAMP(delta_new_tracks, -1, 1);
dst_track = old_track;
while (delta_new_tracks < 0) {
dst_track = dst_track->prev;
delta_new_tracks++;
}
else {
/* make delta 'positive' before using it, since we now know to go backwards */
delta = -delta;
for (track = tdn->nlt->prev, n = 0; (track) && (n < delta); track = track->prev, n++) {
/* check if space in this track for the strip */
if (BKE_nlatrack_has_space(track, strip->start, strip->end) &&
!BKE_nlatrack_is_nonlocal_in_liboverride(tdn->id, track))
{
/* move strip to this track */
BKE_nlatrack_remove_strip(tdn->nlt, strip);
BKE_nlatrack_add_strip(track, strip, is_liboverride);
/* We assume all library tracks are grouped at the bottom of the nla stack.
* Thus, no need to check for them when moving tracks upward.
*/
while (delta_new_tracks > 0) {
dst_track = dst_track->next;
delta_new_tracks--;
}
tdn->nlt = track;
tdn->trackIndex--;
}
else { /* can't move any further */
break;
}
}
for (int i = 0; i < -delta_new_tracks; i++) {
NlaTrack *new_track = BKE_nlatrack_new();
new_track->flag |= NLATRACK_TEMPORARILY_ADDED;
BKE_nlatrack_insert_before(
nla_tracks, (NlaTrack *)nla_tracks->first, new_track, is_liboverride);
dst_track = new_track;
}
for (int i = 0; i < delta_new_tracks; i++) {
NlaTrack *new_track = BKE_nlatrack_new();
new_track->flag |= NLATRACK_TEMPORARILY_ADDED;
BKE_nlatrack_insert_after(
nla_tracks, (NlaTrack *)nla_tracks->last, new_track, is_liboverride);
dst_track = new_track;
}
/* If the destination track is null, then we need to go to the last track. */
if (dst_track == NULL) {
dst_track = old_track;
}
/* Move strip from old_track to dst_track. */
if (dst_track != old_track) {
BKE_nlatrack_remove_strip(old_track, strip);
BKE_nlastrips_add_strip_unsafe(&dst_track->strips, strip);
tdn->nlt = dst_track;
tdn->signed_track_index += delta;
tdn->trackIndex = BLI_findindex(nla_tracks, dst_track);
}
/* Ensure we set the target track as active. */
BKE_nlatrack_set_active(nla_tracks, dst_track);
if (tdn->nlt->flag & NLATRACK_PROTECTED) {
strip->flag |= NLASTRIP_FLAG_INVALID_LOCATION;
}
}
@ -699,6 +872,38 @@ static void nlastrip_shuffle_transformed(TransDataContainer *tc, TransDataNla *f
LISTBASE_FOREACH (IDGroupedTransData *, group, &grouped_trans_datas) {
ListBase *trans_datas = &group->trans_datas;
/* Apply vertical shuffle. */
int minimum_track_offset = 0;
transdata_get_track_shuffle_offset(trans_datas, &minimum_track_offset);
if (minimum_track_offset != 0) {
ListBase *tracks = &BKE_animdata_from_id(group->id)->nla_tracks;
LISTBASE_FOREACH (LinkData *, link, trans_datas) {
TransDataNla *trans_data = (TransDataNla *)link->data;
NlaTrack *dst_track = BLI_findlink(tracks, trans_data->trackIndex + minimum_track_offset);
NlaStrip *strip = trans_data->strip;
if ((dst_track->flag & NLATRACK_PROTECTED) != 0) {
BKE_nlatrack_remove_strip(trans_data->nlt, strip);
BKE_nlatrack_add_strip(dst_track, strip, false);
trans_data->nlt = dst_track;
}
else {
/* if destination track is locked, we need revert strip to source track. */
printf("Cannot moved. Target track '%s' is locked. \n", trans_data->nlt->name);
int old_track_index = BLI_findindex(tracks, trans_data->oldTrack);
NlaTrack *old_track = BLI_findlink(tracks, old_track_index);
BKE_nlatrack_remove_strip(trans_data->nlt, strip);
BKE_nlastrips_add_strip_unsafe(&old_track->strips, strip);
trans_data->nlt = old_track;
}
}
}
/* Apply horizontal shuffle. */
const float minimum_time_offset = transdata_get_time_shuffle_offset(trans_datas);
LISTBASE_FOREACH (LinkData *, link, trans_datas) {
@ -735,7 +940,7 @@ static void special_aftertrans_update__nla(bContext *C, TransInfo *t)
TransDataNla *first_trans_data = tc->custom.type.data;
/* Shuffle transformed strips. */
if (ELEM(t->mode, TFM_TRANSLATION)) {
if (ELEM(t->mode, TFM_TRANSLATION) && t->state != TRANS_CANCEL) {
nlastrip_shuffle_transformed(tc, first_trans_data);
}
@ -774,6 +979,9 @@ static void special_aftertrans_update__nla(bContext *C, TransInfo *t)
/* free temp memory */
ANIM_animdata_freelist(&anim_data);
/* Truncate temporarily added tracks. */
nlatrack_truncate_temporary_tracks(&ac);
/* Perform after-transform validation. */
ED_nla_postop_refresh(&ac);
}

View File

@ -906,6 +906,10 @@ typedef enum eNlaTrack_Flag {
* usually as result of tweaking being enabled (internal flag) */
NLATRACK_DISABLED = (1 << 10),
/** Marks tracks automatically added for space while dragging strips vertically.
* Internal flag that's only set during transform operator. */
NLATRACK_TEMPORARILY_ADDED = (1 << 11),
/** This NLA track is added to an override ID, which means it is fully editable.
* Irrelevant in case the owner ID is not an override. */
NLATRACK_OVERRIDELIBRARY_LOCAL = 1 << 16,