Shape Key editing: propagate updates through basis chains.

It is possible to organize shape keys into a tree through the
reference key setting. Mesh editing and sculpting a reference
key is supposed to update all its children, but this was only
done for one level of dependencies.

This changes the code to retrieve the complete set of dependent
keys and update them all.
This commit is contained in:
Alexander Gavrilov 2023-07-14 20:11:24 +03:00
parent 548ff9dc8d
commit d32748cdf4
5 changed files with 72 additions and 19 deletions

View File

@ -182,6 +182,12 @@ bool BKE_keyblock_move(struct Object *ob, int org_index, int new_index);
*/
bool BKE_keyblock_is_basis(const struct Key *key, int index);
/**
* Returns a newly allocated array containing true for every key that has this one as basis.
* If none are found, returns null.
*/
bool *BKE_keyblock_get_dependent_keys(const struct Key *key, int index);
/* -------------------------------------------------------------------- */
/** \name Key-Block Data Access
* \{ */

View File

@ -2598,3 +2598,46 @@ bool BKE_keyblock_is_basis(const Key *key, const int index)
return false;
}
bool *BKE_keyblock_get_dependent_keys(const Key *key, const int index)
{
if (key->type != KEY_RELATIVE) {
return nullptr;
}
const int count = BLI_listbase_count(&key->block);
if (index < 0 || index >= count) {
return nullptr;
}
/* Seed the table with the specified key. */
bool *marked = static_cast<bool *>(MEM_callocN(sizeof(bool) * count, __func__));
marked[index] = true;
/* Iterative breadth-first search through the key list. This method minimizes
* the number of scans through the list and is failsafe vs reference cycles. */
bool updated, found = false;
int i;
do {
updated = false;
LISTBASE_FOREACH_INDEX (const KeyBlock *, kb, &key->block, i) {
if (!marked[i] && kb->relative >= 0 && kb->relative < count && marked[kb->relative]) {
marked[i] = true;
updated = found = true;
}
}
} while (updated);
if (!found) {
MEM_freeN(marked);
return nullptr;
}
/* After the search is complete, exclude the original key. */
marked[index] = false;
return marked;
}

View File

@ -795,6 +795,7 @@ static void bm_to_mesh_shape(BMesh *bm,
BMIter iter;
BMVert *eve;
float(*ofs)[3] = nullptr;
bool *dependent = nullptr;
/* Editing the basis key updates others. */
if ((key->type == KEY_RELATIVE) &&
@ -803,7 +804,7 @@ static void bm_to_mesh_shape(BMesh *bm,
/* Original key-indices are only used to check the vertex existed when entering edit-mode. */
(cd_shape_keyindex_offset != -1) &&
/* Offsets are only needed if the current shape is a basis for others. */
BKE_keyblock_is_basis(key, bm->shapenr - 1))
(dependent = BKE_keyblock_get_dependent_keys(key, bm->shapenr - 1)) != nullptr)
{
BLI_assert(actkey != nullptr); /* Assured by `actkey_has_layer` check. */
@ -830,6 +831,8 @@ static void bm_to_mesh_shape(BMesh *bm,
* ones, creating a mess when doing e.g. subdivide + translate. */
MEM_freeN(ofs);
ofs = nullptr;
MEM_freeN(dependent);
dependent = nullptr;
break;
}
}
@ -856,7 +859,8 @@ static void bm_to_mesh_shape(BMesh *bm,
}
}
LISTBASE_FOREACH (KeyBlock *, currkey, &key->block) {
int currkey_i;
LISTBASE_FOREACH_INDEX (KeyBlock *, currkey, &key->block, currkey_i) {
int keyi;
float(*currkey_data)[3];
@ -867,8 +871,7 @@ static void bm_to_mesh_shape(BMesh *bm,
/* Common case, the layer data is available, use it where possible. */
if (cd_shape_offset != -1) {
const bool apply_offset = (ofs != nullptr) && (currkey != actkey) &&
(bm->shapenr - 1 == currkey->relative);
const bool apply_offset = (ofs != nullptr) && (currkey != actkey) && dependent[currkey_i];
if (currkey->data && (currkey->totelem == bm->totvert)) {
/* Use memory in-place. */
@ -956,9 +959,8 @@ static void bm_to_mesh_shape(BMesh *bm,
}
}
if (ofs) {
MEM_freeN(ofs);
}
MEM_SAFE_FREE(ofs);
MEM_SAFE_FREE(dependent);
}
/** \} */

View File

@ -631,7 +631,7 @@ static void calc_shapeKeys(Object *obedit, ListBase *newnurbs)
return;
}
int a, i;
int a, i, currkey_i;
EditNurb *editnurb = cu->editnurb;
KeyBlock *actkey = BLI_findlink(&cu->key->block, editnurb->shapenr - 1);
BezTriple *bezt, *oldbezt;
@ -640,11 +640,14 @@ static void calc_shapeKeys(Object *obedit, ListBase *newnurbs)
int totvert = BKE_keyblock_curve_element_count(&editnurb->nurbs);
float(*ofs)[3] = NULL;
bool *dependent = NULL;
float *oldkey, *newkey, *ofp;
/* editing the base key should update others */
if (cu->key->type == KEY_RELATIVE) {
if (BKE_keyblock_is_basis(cu->key, editnurb->shapenr - 1)) { /* active key is a base */
dependent = BKE_keyblock_get_dependent_keys(cu->key, editnurb->shapenr - 1);
if (dependent) { /* active key is a base */
int totvec = 0;
/* Calculate needed memory to store offset */
@ -702,9 +705,8 @@ static void calc_shapeKeys(Object *obedit, ListBase *newnurbs)
}
}
LISTBASE_FOREACH (KeyBlock *, currkey, &cu->key->block) {
const bool apply_offset = (ofs && (currkey != actkey) &&
(editnurb->shapenr - 1 == currkey->relative));
LISTBASE_FOREACH_INDEX (KeyBlock *, currkey, &cu->key->block, currkey_i) {
const bool apply_offset = (ofs && (currkey != actkey) && dependent[currkey_i]);
float *fp = newkey = MEM_callocN(cu->key->elemsize * totvert, "currkey->data");
ofp = oldkey = currkey->data;
@ -866,9 +868,8 @@ static void calc_shapeKeys(Object *obedit, ListBase *newnurbs)
currkey->data = newkey;
}
if (ofs) {
MEM_freeN(ofs);
}
MEM_SAFE_FREE(ofs);
MEM_SAFE_FREE(dependent);
}
/** \} */

View File

@ -3406,11 +3406,11 @@ void SCULPT_vertcos_to_key(Object *ob, KeyBlock *kb, const float (*vertCos)[3])
{
Mesh *me = (Mesh *)ob->data;
float(*ofs)[3] = nullptr;
int a;
int a, currkey_i;
const int kb_act_idx = ob->shapenr - 1;
/* For relative keys editing of base should update other keys. */
if (BKE_keyblock_is_basis(me->key, kb_act_idx)) {
if (bool *dependent = BKE_keyblock_get_dependent_keys(me->key, kb_act_idx)) {
ofs = BKE_keyblock_convert_to_vertcos(ob, kb);
/* Calculate key coord offsets (from previous location). */
@ -3419,13 +3419,14 @@ void SCULPT_vertcos_to_key(Object *ob, KeyBlock *kb, const float (*vertCos)[3])
}
/* Apply offsets on other keys. */
LISTBASE_FOREACH (KeyBlock *, currkey, &me->key->block) {
if ((currkey != kb) && (currkey->relative == kb_act_idx)) {
LISTBASE_FOREACH_INDEX (KeyBlock *, currkey, &me->key->block, currkey_i) {
if ((currkey != kb) && dependent[currkey_i]) {
BKE_keyblock_update_from_offset(ob, currkey, ofs);
}
}
MEM_freeN(ofs);
MEM_freeN(dependent);
}
/* Modifying of basis key should update mesh. */