Files
test2/source/blender/blenkernel/intern/armature_deform.cc
Lukas Tönne 49965841bb Fix: GPv3: Armature deform using the wrong vertex group list
Grease Pencil armature deformation has to use the correct
`vertex_group_names` list from the `Curves` (i.e. drawings) rather than
the names from its parent `GreasePencil` data block.

The ancient armature deformation code used a `defbase` list (`ListBase`
of type `bDeformGroup`) from the object via `BKE_id_defgroup_list_get`.
This function does not have enough information to determine the correct
vertex groups for GPv3, and it's use inside supposedly generic code is
highly questionable.

This patch removes the `defbase` lookup inside
`armature_deform_coords_impl` and instead passes an explicit list of
vertex group names to be used for resolving indices. We already have
context in every place this is called to determine the correct vertex
groups, without having to make assumptions down the line based on just
the object type.

Pull Request: https://projects.blender.org/blender/blender/pulls/127509
2024-09-12 17:35:35 +02:00

728 lines
23 KiB
C++

/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*
* Deform coordinates by a armature object (used by modifier).
*/
#include <cctype>
#include <cfloat>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_task.h"
#include "BLI_utildefines.h"
#include "DNA_armature_types.h"
#include "DNA_gpencil_legacy_types.h"
#include "DNA_lattice_types.h"
#include "DNA_listBase.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "BKE_action.hh"
#include "BKE_armature.hh"
#include "BKE_curves.hh"
#include "BKE_customdata.hh"
#include "BKE_deform.hh"
#include "BKE_editmesh.hh"
#include "BKE_lattice.hh"
#include "BKE_mesh.hh"
#include "DEG_depsgraph_build.hh"
#include "CLG_log.h"
static CLG_LogRef LOG = {"bke.armature_deform"};
/* -------------------------------------------------------------------- */
/** \name Armature Deform Internal Utilities
* \{ */
/* Add the effect of one bone or B-Bone segment to the accumulated result. */
static void pchan_deform_accumulate(const DualQuat *deform_dq,
const float deform_mat[4][4],
const float co_in[3],
const float weight,
float co_accum[3],
DualQuat *dq_accum,
float mat_accum[3][3],
const bool full_deform)
{
if (weight == 0.0f) {
return;
}
if (dq_accum) {
BLI_assert(!co_accum);
add_weighted_dq_dq_pivot(dq_accum, deform_dq, co_in, weight, full_deform);
}
else {
float tmp[3];
mul_v3_m4v3(tmp, deform_mat, co_in);
sub_v3_v3(tmp, co_in);
madd_v3_v3fl(co_accum, tmp, weight);
if (full_deform) {
float tmpmat[3][3];
copy_m3_m4(tmpmat, deform_mat);
madd_m3_m3m3fl(mat_accum, mat_accum, tmpmat, weight);
}
}
}
static void b_bone_deform(const bPoseChannel *pchan,
const float co[3],
const float weight,
float vec[3],
DualQuat *dq,
float defmat[3][3],
const bool full_deform)
{
const DualQuat *quats = pchan->runtime.bbone_dual_quats;
const Mat4 *mats = pchan->runtime.bbone_deform_mats;
float blend;
int index;
/* Calculate the indices of the 2 affecting b_bone segments. */
BKE_pchan_bbone_deform_segment_index(pchan, co, &index, &blend);
pchan_deform_accumulate(&quats[index],
mats[index + 1].mat,
co,
weight * (1.0f - blend),
vec,
dq,
defmat,
full_deform);
pchan_deform_accumulate(
&quats[index + 1], mats[index + 2].mat, co, weight * blend, vec, dq, defmat, full_deform);
}
float distfactor_to_bone(
const float vec[3], const float b1[3], const float b2[3], float rad1, float rad2, float rdist)
{
float dist_sq;
float bdelta[3];
float pdelta[3];
float hsqr, a, l, rad;
sub_v3_v3v3(bdelta, b2, b1);
l = normalize_v3(bdelta);
sub_v3_v3v3(pdelta, vec, b1);
a = dot_v3v3(bdelta, pdelta);
hsqr = len_squared_v3(pdelta);
if (a < 0.0f) {
/* If we're past the end of the bone, do a spherical field attenuation thing */
dist_sq = len_squared_v3v3(b1, vec);
rad = rad1;
}
else if (a > l) {
/* If we're past the end of the bone, do a spherical field attenuation thing */
dist_sq = len_squared_v3v3(b2, vec);
rad = rad2;
}
else {
dist_sq = (hsqr - (a * a));
if (l != 0.0f) {
rad = a / l;
rad = rad * rad2 + (1.0f - rad) * rad1;
}
else {
rad = rad1;
}
}
a = rad * rad;
if (dist_sq < a) {
return 1.0f;
}
l = rad + rdist;
l *= l;
if (rdist == 0.0f || dist_sq >= l) {
return 0.0f;
}
a = sqrtf(dist_sq) - rad;
return 1.0f - (a * a) / (rdist * rdist);
}
static float dist_bone_deform(const bPoseChannel *pchan,
float vec[3],
DualQuat *dq,
float mat[3][3],
const float co[3],
const bool full_deform)
{
const Bone *bone = pchan->bone;
float fac, contrib = 0.0;
if (bone == nullptr) {
return 0.0f;
}
fac = distfactor_to_bone(
co, bone->arm_head, bone->arm_tail, bone->rad_head, bone->rad_tail, bone->dist);
if (fac > 0.0f) {
fac *= bone->weight;
contrib = fac;
if (contrib > 0.0f) {
if (bone->segments > 1 && pchan->runtime.bbone_segments == bone->segments) {
b_bone_deform(pchan, co, fac, vec, dq, mat, full_deform);
}
else {
pchan_deform_accumulate(
&pchan->runtime.deform_dual_quat, pchan->chan_mat, co, fac, vec, dq, mat, full_deform);
}
}
}
return contrib;
}
static void pchan_bone_deform(const bPoseChannel *pchan,
const float weight,
float vec[3],
DualQuat *dq,
float mat[3][3],
const float co[3],
const bool full_deform,
float *contrib)
{
const Bone *bone = pchan->bone;
if (!weight) {
return;
}
if (bone->segments > 1 && pchan->runtime.bbone_segments == bone->segments) {
b_bone_deform(pchan, co, weight, vec, dq, mat, full_deform);
}
else {
pchan_deform_accumulate(
&pchan->runtime.deform_dual_quat, pchan->chan_mat, co, weight, vec, dq, mat, full_deform);
}
(*contrib) += weight;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Armature Deform #BKE_armature_deform_coords API
*
* #BKE_armature_deform_coords and related functions.
* \{ */
struct ArmatureUserdata {
const Object *ob_arm;
const Mesh *me_target;
float (*vert_coords)[3];
float (*vert_deform_mats)[3][3];
float (*vert_coords_prev)[3];
bool use_envelope;
bool use_quaternion;
bool invert_vgroup;
bool use_dverts;
int armature_def_nr;
const MDeformVert *dverts;
int dverts_len;
bPoseChannel **pchan_from_defbase;
int defbase_len;
float premat[4][4];
float postmat[4][4];
/** Specific data types. */
struct {
int cd_dvert_offset;
} bmesh;
};
static void armature_vert_task_with_dvert(const ArmatureUserdata *data,
const int i,
const MDeformVert *dvert)
{
float(*const vert_coords)[3] = data->vert_coords;
float(*const vert_deform_mats)[3][3] = data->vert_deform_mats;
float(*const vert_coords_prev)[3] = data->vert_coords_prev;
const bool use_envelope = data->use_envelope;
const bool use_quaternion = data->use_quaternion;
const bool use_dverts = data->use_dverts;
const int armature_def_nr = data->armature_def_nr;
DualQuat sumdq, *dq = nullptr;
const bPoseChannel *pchan;
float *co, dco[3];
float sumvec[3], summat[3][3];
float *vec = nullptr, (*smat)[3] = nullptr;
float contrib = 0.0f;
float armature_weight = 1.0f; /* default to 1 if no overall def group */
float prevco_weight = 0.0f; /* weight for optional cached vertexcos */
const bool full_deform = vert_deform_mats != nullptr;
if (use_quaternion) {
memset(&sumdq, 0, sizeof(DualQuat));
dq = &sumdq;
}
else {
zero_v3(sumvec);
vec = sumvec;
if (full_deform) {
zero_m3(summat);
smat = summat;
}
}
if (armature_def_nr != -1 && dvert) {
armature_weight = BKE_defvert_find_weight(dvert, armature_def_nr);
if (data->invert_vgroup) {
armature_weight = 1.0f - armature_weight;
}
/* hackish: the blending factor can be used for blending with vert_coords_prev too */
if (vert_coords_prev) {
/* This weight specifies the contribution from the coordinates at the start of this
* modifier evaluation, while armature_weight is normally the opposite of that. */
prevco_weight = 1.0f - armature_weight;
armature_weight = 1.0f;
}
}
/* check if there's any point in calculating for this vert */
if (vert_coords_prev) {
if (prevco_weight == 1.0f) {
return;
}
/* get the coord we work on */
co = vert_coords_prev[i];
}
else {
if (armature_weight == 0.0f) {
return;
}
/* get the coord we work on */
co = vert_coords[i];
}
/* Apply the object's matrix */
mul_m4_v3(data->premat, co);
if (use_dverts && dvert && dvert->totweight) { /* use weight groups ? */
const MDeformWeight *dw = dvert->dw;
int deformed = 0;
uint j;
for (j = dvert->totweight; j != 0; j--, dw++) {
const uint index = dw->def_nr;
if (index < data->defbase_len && (pchan = data->pchan_from_defbase[index])) {
float weight = dw->weight;
const Bone *bone = pchan->bone;
deformed = 1;
if (bone && bone->flag & BONE_MULT_VG_ENV) {
weight *= distfactor_to_bone(
co, bone->arm_head, bone->arm_tail, bone->rad_head, bone->rad_tail, bone->dist);
}
pchan_bone_deform(pchan, weight, vec, dq, smat, co, full_deform, &contrib);
}
}
/* If there are vertex-groups but not groups with bones (like for soft-body groups). */
if (deformed == 0 && use_envelope) {
for (pchan = static_cast<const bPoseChannel *>(data->ob_arm->pose->chanbase.first); pchan;
pchan = pchan->next)
{
if (!(pchan->bone->flag & BONE_NO_DEFORM)) {
contrib += dist_bone_deform(pchan, vec, dq, smat, co, full_deform);
}
}
}
}
else if (use_envelope) {
for (pchan = static_cast<const bPoseChannel *>(data->ob_arm->pose->chanbase.first); pchan;
pchan = pchan->next)
{
if (!(pchan->bone->flag & BONE_NO_DEFORM)) {
contrib += dist_bone_deform(pchan, vec, dq, smat, co, full_deform);
}
}
}
/* actually should be EPSILON? weight values and contrib can be like 10e-39 small */
if (contrib > 0.0001f) {
if (use_quaternion) {
normalize_dq(dq, contrib);
if (armature_weight != 1.0f) {
copy_v3_v3(dco, co);
mul_v3m3_dq(dco, full_deform ? summat : nullptr, dq);
sub_v3_v3(dco, co);
mul_v3_fl(dco, armature_weight);
add_v3_v3(co, dco);
}
else {
mul_v3m3_dq(co, full_deform ? summat : nullptr, dq);
}
smat = summat;
}
else {
mul_v3_fl(vec, armature_weight / contrib);
add_v3_v3v3(co, vec, co);
}
if (full_deform) {
float pre[3][3], post[3][3], tmpmat[3][3];
copy_m3_m4(pre, data->premat);
copy_m3_m4(post, data->postmat);
copy_m3_m3(tmpmat, vert_deform_mats[i]);
if (!use_quaternion) { /* quaternion already is scale corrected */
mul_m3_fl(smat, armature_weight / contrib);
}
mul_m3_series(vert_deform_mats[i], post, smat, pre, tmpmat);
}
}
/* always, check above code */
mul_m4_v3(data->postmat, co);
/* interpolate with previous modifier position using weight group */
if (vert_coords_prev) {
float mw = 1.0f - prevco_weight;
vert_coords[i][0] = prevco_weight * vert_coords[i][0] + mw * co[0];
vert_coords[i][1] = prevco_weight * vert_coords[i][1] + mw * co[1];
vert_coords[i][2] = prevco_weight * vert_coords[i][2] + mw * co[2];
}
}
static void armature_vert_task(void *__restrict userdata,
const int i,
const TaskParallelTLS *__restrict /*tls*/)
{
const ArmatureUserdata *data = static_cast<const ArmatureUserdata *>(userdata);
const MDeformVert *dvert;
if (data->use_dverts || data->armature_def_nr != -1) {
if (data->me_target) {
BLI_assert(i < data->me_target->verts_num);
if (data->dverts != nullptr) {
dvert = data->dverts + i;
}
else {
dvert = nullptr;
}
}
else if (data->dverts && i < data->dverts_len) {
dvert = data->dverts + i;
}
else {
dvert = nullptr;
}
}
else {
dvert = nullptr;
}
armature_vert_task_with_dvert(data, i, dvert);
}
static void armature_vert_task_editmesh(void *__restrict userdata,
MempoolIterData *iter,
const TaskParallelTLS *__restrict /*tls*/)
{
const ArmatureUserdata *data = static_cast<const ArmatureUserdata *>(userdata);
BMVert *v = (BMVert *)iter;
const MDeformVert *dvert = static_cast<const MDeformVert *>(
BM_ELEM_CD_GET_VOID_P(v, data->bmesh.cd_dvert_offset));
armature_vert_task_with_dvert(data, BM_elem_index_get(v), dvert);
}
static void armature_vert_task_editmesh_no_dvert(void *__restrict userdata,
MempoolIterData *iter,
const TaskParallelTLS *__restrict /*tls*/)
{
const ArmatureUserdata *data = static_cast<const ArmatureUserdata *>(userdata);
BMVert *v = (BMVert *)iter;
armature_vert_task_with_dvert(data, BM_elem_index_get(v), nullptr);
}
static void armature_deform_coords_impl(const Object *ob_arm,
const Object *ob_target,
const ListBase *defbase,
float (*vert_coords)[3],
float (*vert_deform_mats)[3][3],
const int vert_coords_len,
const int deformflag,
float (*vert_coords_prev)[3],
const char *defgrp_name,
blender::Span<MDeformVert> dverts,
const Mesh *me_target,
const BMEditMesh *em_target)
{
const bArmature *arm = static_cast<const bArmature *>(ob_arm->data);
bPoseChannel **pchan_from_defbase = nullptr;
const bool use_envelope = (deformflag & ARM_DEF_ENVELOPE) != 0;
const bool use_quaternion = (deformflag & ARM_DEF_QUATERNION) != 0;
const bool invert_vgroup = (deformflag & ARM_DEF_INVERT_VGROUP) != 0;
int defbase_len = 0; /* safety for vertexgroup index overflow */
bool use_dverts = false;
int armature_def_nr = -1;
int cd_dvert_offset = -1;
/* in editmode, or not an armature */
if (arm->edbo || (ob_arm->pose == nullptr)) {
return;
}
if ((ob_arm->pose->flag & POSE_RECALC) != 0) {
CLOG_ERROR(&LOG,
"Trying to evaluate influence of armature '%s' which needs Pose recalc!",
ob_arm->id.name);
BLI_assert(0);
}
if (BKE_object_supports_vertex_groups(ob_target)) {
/* Collect the vertex group names from the evaluated data. */
armature_def_nr = BLI_findstringindex(defbase, defgrp_name, offsetof(bDeformGroup, name));
defbase_len = BLI_listbase_count(defbase);
/* get a vertex-deform-index to posechannel array */
if (deformflag & ARM_DEF_VGROUP) {
/* if we have a Mesh, only use dverts if it has them */
if (em_target) {
cd_dvert_offset = CustomData_get_offset(&em_target->bm->vdata, CD_MDEFORMVERT);
use_dverts = (cd_dvert_offset != -1);
}
else if (me_target) {
use_dverts = !me_target->deform_verts().is_empty();
}
else if (dverts.size() == vert_coords_len) {
use_dverts = true;
}
if (use_dverts) {
pchan_from_defbase = static_cast<bPoseChannel **>(
MEM_callocN(sizeof(*pchan_from_defbase) * defbase_len, "defnrToBone"));
/* TODO(sergey): Some considerations here:
*
* - Check whether keeping this consistent across frames gives speedup.
*/
int i;
LISTBASE_FOREACH_INDEX (bDeformGroup *, dg, defbase, i) {
pchan_from_defbase[i] = BKE_pose_channel_find_name(ob_arm->pose, dg->name);
/* exclude non-deforming bones */
if (pchan_from_defbase[i]) {
if (pchan_from_defbase[i]->bone->flag & BONE_NO_DEFORM) {
pchan_from_defbase[i] = nullptr;
}
}
}
}
}
}
ArmatureUserdata data{};
data.ob_arm = ob_arm;
data.me_target = me_target;
data.vert_coords = vert_coords;
data.vert_deform_mats = vert_deform_mats;
data.vert_coords_prev = vert_coords_prev;
data.use_envelope = use_envelope;
data.use_quaternion = use_quaternion;
data.invert_vgroup = invert_vgroup;
data.use_dverts = use_dverts;
data.armature_def_nr = armature_def_nr;
data.dverts = dverts.data();
data.dverts_len = dverts.size();
data.pchan_from_defbase = pchan_from_defbase;
data.defbase_len = defbase_len;
data.bmesh.cd_dvert_offset = cd_dvert_offset;
float obinv[4][4];
invert_m4_m4(obinv, ob_target->object_to_world().ptr());
mul_m4_m4m4(data.postmat, obinv, ob_arm->object_to_world().ptr());
invert_m4_m4(data.premat, data.postmat);
if (em_target != nullptr) {
/* While this could cause an extra loop over mesh data, in most cases this will
* have already been properly set. */
BM_mesh_elem_index_ensure(em_target->bm, BM_VERT);
TaskParallelSettings settings;
BLI_parallel_mempool_settings_defaults(&settings);
if (use_dverts) {
BLI_task_parallel_mempool(
em_target->bm->vpool, &data, armature_vert_task_editmesh, &settings);
}
else {
BLI_task_parallel_mempool(
em_target->bm->vpool, &data, armature_vert_task_editmesh_no_dvert, &settings);
}
}
else {
TaskParallelSettings settings;
BLI_parallel_range_settings_defaults(&settings);
settings.min_iter_per_thread = 32;
BLI_task_parallel_range(0, vert_coords_len, &data, armature_vert_task, &settings);
}
if (pchan_from_defbase) {
MEM_freeN(pchan_from_defbase);
}
}
void BKE_armature_deform_coords_with_gpencil_stroke(const Object *ob_arm,
const Object *ob_target,
float (*vert_coords)[3],
float (*vert_deform_mats)[3][3],
int vert_coords_len,
int deformflag,
float (*vert_coords_prev)[3],
const char *defgrp_name,
bGPDstroke *gps_target)
{
const ListBase *defbase = BKE_id_defgroup_list_get(static_cast<const ID *>(ob_target->data));
const blender::Span<MDeformVert> dverts = {gps_target->dvert, gps_target->totpoints};
armature_deform_coords_impl(ob_arm,
ob_target,
defbase,
vert_coords,
vert_deform_mats,
vert_coords_len,
deformflag,
vert_coords_prev,
defgrp_name,
dverts,
nullptr,
nullptr);
}
void BKE_armature_deform_coords_with_curves(
const Object &ob_arm,
const Object &ob_target,
const ListBase *defbase,
blender::MutableSpan<blender::float3> vert_coords,
std::optional<blender::MutableSpan<blender::float3>> vert_coords_prev,
std::optional<blender::MutableSpan<blender::float3x3>> vert_deform_mats,
blender::Span<MDeformVert> dverts,
int deformflag,
blender::StringRefNull defgrp_name)
{
/* Vertex groups must be provided explicitly, cannot rely on object vertex groups since this is
* used for Grease Pencil layers as well. */
BLI_assert(dverts.size() == vert_coords.size());
armature_deform_coords_impl(
&ob_arm,
&ob_target,
defbase,
reinterpret_cast<float(*)[3]>(vert_coords.data()),
vert_deform_mats ? reinterpret_cast<float(*)[3][3]>(vert_deform_mats->data()) : nullptr,
vert_coords.size(),
deformflag,
vert_coords_prev ? reinterpret_cast<float(*)[3]>(vert_coords_prev->data()) : nullptr,
defgrp_name.c_str(),
dverts,
nullptr,
nullptr);
}
void BKE_armature_deform_coords_with_mesh(const Object *ob_arm,
const Object *ob_target,
float (*vert_coords)[3],
float (*vert_deform_mats)[3][3],
int vert_coords_len,
int deformflag,
float (*vert_coords_prev)[3],
const char *defgrp_name,
const Mesh *me_target)
{
const ListBase *defbase = BKE_id_defgroup_list_get(static_cast<const ID *>(ob_target->data));
blender::Span<MDeformVert> dverts;
if (ob_target->type == OB_MESH) {
if (me_target == nullptr) {
me_target = static_cast<const Mesh *>(ob_target->data);
}
dverts = me_target->deform_verts();
}
else if (ob_target->type == OB_LATTICE) {
const Lattice *lt = static_cast<const Lattice *>(ob_target->data);
dverts = blender::Span<MDeformVert>(lt->dvert, lt->pntsu * lt->pntsv * lt->pntsw);
}
armature_deform_coords_impl(ob_arm,
ob_target,
defbase,
vert_coords,
vert_deform_mats,
vert_coords_len,
deformflag,
vert_coords_prev,
defgrp_name,
dverts,
me_target,
nullptr);
}
void BKE_armature_deform_coords_with_editmesh(const Object *ob_arm,
const Object *ob_target,
float (*vert_coords)[3],
float (*vert_deform_mats)[3][3],
int vert_coords_len,
int deformflag,
float (*vert_coords_prev)[3],
const char *defgrp_name,
const BMEditMesh *em_target)
{
const ListBase *defbase = BKE_id_defgroup_list_get(static_cast<const ID *>(ob_target->data));
armature_deform_coords_impl(ob_arm,
ob_target,
defbase,
vert_coords,
vert_deform_mats,
vert_coords_len,
deformflag,
vert_coords_prev,
defgrp_name,
{},
nullptr,
em_target);
}
/** \} */