Files
test/source/blender/blenkernel/intern/armature_deform.c
Sergey Sharybin f17fbf8065 Refactor: Rename Object->obmat to Object->object_to_world
Motivation is to disambiguate on the naming level what the matrix
actually means. It is very easy to understand the meaning backwards,
especially since in Python the name goes the opposite way (it is
called `world_matrix` in the Python API).

It is important to disambiguate the naming without making developers
to look into the comment in the header file (which is also not super
clear either). Additionally, more clear naming facilitates the unit
verification (or, in this case, space validation) when reading an
expression.

This patch calls the matrix `object_to_world` which makes it clear
from the local code what is it exactly going on. This is only done
on DNA level, and a lot of local variables still follow the old
naming.

A DNA rename is setup in a way that there is no change on the file
level, so there should be no regressions at all.

The possibility is to add `_matrix` or `_mat` suffix to the name
to make it explicit that it is a matrix. Although, not sure if it
really helps the readability, or is it something redundant.

Differential Revision: https://developer.blender.org/D16328
2022-11-01 10:48:18 +01:00

689 lines
21 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2001-2002 NaN Holding BV. All rights reserved. */
/** \file
* \ingroup bke
*
* Deform coordinates by a armature object (used by modifier).
*/
#include <ctype.h>
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_task.h"
#include "BLI_utildefines.h"
#include "DNA_armature_types.h"
#include "DNA_gpencil_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.h"
#include "BKE_armature.h"
#include "BKE_customdata.h"
#include "BKE_deform.h"
#include "BKE_editmesh.h"
#include "BKE_lattice.h"
#include "BKE_mesh.h"
#include "DEG_depsgraph_build.h"
#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],
float weight,
float co_accum[3],
DualQuat *dq_accum,
float mat_accum[3][3])
{
if (weight == 0.0f) {
return;
}
if (dq_accum) {
BLI_assert(!co_accum);
add_weighted_dq_dq(dq_accum, deform_dq, weight);
}
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 (mat_accum) {
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],
float weight,
float vec[3],
DualQuat *dq,
float defmat[3][3])
{
const DualQuat *quats = pchan->runtime.bbone_dual_quats;
const Mat4 *mats = pchan->runtime.bbone_deform_mats;
const float(*mat)[4] = mats[0].mat;
float blend, y;
int index;
/* Transform co to bone space and get its y component. */
y = mat[0][1] * co[0] + mat[1][1] * co[1] + mat[2][1] * co[2] + mat[3][1];
/* Calculate the indices of the 2 affecting b_bone segments. */
BKE_pchan_bbone_deform_segment_index(pchan, y / pchan->bone->length, &index, &blend);
pchan_deform_accumulate(
&quats[index], mats[index + 1].mat, co, weight * (1.0f - blend), vec, dq, defmat);
pchan_deform_accumulate(
&quats[index + 1], mats[index + 2].mat, co, weight * blend, vec, dq, defmat);
}
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 Bone *bone = pchan->bone;
float fac, contrib = 0.0;
if (bone == NULL) {
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);
}
else {
pchan_deform_accumulate(
&pchan->runtime.deform_dual_quat, pchan->chan_mat, co, fac, vec, dq, mat);
}
}
}
return contrib;
}
static void pchan_bone_deform(const bPoseChannel *pchan,
float weight,
float vec[3],
DualQuat *dq,
float mat[3][3],
const float co[3],
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);
}
else {
pchan_deform_accumulate(
&pchan->runtime.deform_dual_quat, pchan->chan_mat, co, weight, vec, dq, mat);
}
(*contrib) += weight;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Armature Deform #BKE_armature_deform_coords API
*
* #BKE_armature_deform_coords and related functions.
* \{ */
typedef 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;
} ArmatureUserdata;
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 = NULL;
const bPoseChannel *pchan;
float *co, dco[3];
float sumvec[3], summat[3][3];
float *vec = NULL, (*smat)[3] = NULL;
float contrib = 0.0f;
float armature_weight = 1.0f; /* default to 1 if no overall def group */
float prevco_weight = 1.0f; /* weight for optional cached vertexcos */
if (use_quaternion) {
memset(&sumdq, 0, sizeof(DualQuat));
dq = &sumdq;
}
else {
zero_v3(sumvec);
vec = sumvec;
if (vert_deform_mats) {
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) {
prevco_weight = 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, &contrib);
}
}
/* If there are vertex-groups but not groups with bones (like for soft-body groups). */
if (deformed == 0 && use_envelope) {
for (pchan = 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);
}
}
}
}
else if (use_envelope) {
for (pchan = 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);
}
}
}
/* 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, (vert_deform_mats) ? summat : NULL, dq);
sub_v3_v3(dco, co);
mul_v3_fl(dco, armature_weight);
add_v3_v3(co, dco);
}
else {
mul_v3m3_dq(co, (vert_deform_mats) ? summat : NULL, dq);
}
smat = summat;
}
else {
mul_v3_fl(vec, armature_weight / contrib);
add_v3_v3v3(co, vec, co);
}
if (vert_deform_mats) {
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 UNUSED(tls))
{
const ArmatureUserdata *data = userdata;
const MDeformVert *dvert;
if (data->use_dverts || data->armature_def_nr != -1) {
if (data->me_target) {
BLI_assert(i < data->me_target->totvert);
if (data->dverts != NULL) {
dvert = data->dverts + i;
}
else {
dvert = NULL;
}
}
else if (data->dverts && i < data->dverts_len) {
dvert = data->dverts + i;
}
else {
dvert = NULL;
}
}
else {
dvert = NULL;
}
armature_vert_task_with_dvert(data, i, dvert);
}
static void armature_vert_task_editmesh(void *__restrict userdata,
MempoolIterData *iter,
const TaskParallelTLS *__restrict UNUSED(tls))
{
const ArmatureUserdata *data = userdata;
BMVert *v = (BMVert *)iter;
const MDeformVert *dvert = 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 UNUSED(tls))
{
const ArmatureUserdata *data = userdata;
BMVert *v = (BMVert *)iter;
armature_vert_task_with_dvert(data, BM_elem_index_get(v), NULL);
}
static void armature_deform_coords_impl(const Object *ob_arm,
const Object *ob_target,
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,
const Mesh *me_target,
BMEditMesh *em_target,
bGPDstroke *gps_target)
{
const bArmature *arm = ob_arm->data;
bPoseChannel **pchan_from_defbase = NULL;
const MDeformVert *dverts = NULL;
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 */
int dverts_len = 0; /* safety for vertexgroup 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 == NULL)) {
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)) {
const ID *target_data_id = NULL;
if (ob_target->type == OB_MESH) {
target_data_id = me_target == NULL ? (const ID *)ob_target->data : &me_target->id;
if (em_target == NULL) {
const Mesh *me = (const Mesh *)target_data_id;
dverts = BKE_mesh_deform_verts(me);
if (dverts) {
dverts_len = me->totvert;
}
}
}
else if (ob_target->type == OB_LATTICE) {
const Lattice *lt = ob_target->data;
target_data_id = (const ID *)ob_target->data;
dverts = lt->dvert;
if (dverts) {
dverts_len = lt->pntsu * lt->pntsv * lt->pntsw;
}
}
else if (ob_target->type == OB_GPENCIL) {
target_data_id = (const ID *)ob_target->data;
dverts = gps_target->dvert;
if (dverts) {
dverts_len = gps_target->totpoints;
}
}
/* Collect the vertex group names from the evaluated data. */
armature_def_nr = BKE_id_defgroup_name_index(target_data_id, defgrp_name);
const ListBase *defbase = BKE_id_defgroup_list_get(target_data_id);
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 = (BKE_mesh_deform_verts(me_target) != NULL);
}
else if (dverts) {
use_dverts = true;
}
if (use_dverts) {
pchan_from_defbase = 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] = NULL;
}
}
}
}
}
}
ArmatureUserdata data = {
.ob_arm = ob_arm,
.me_target = me_target,
.vert_coords = vert_coords,
.vert_deform_mats = vert_deform_mats,
.vert_coords_prev = vert_coords_prev,
.use_envelope = use_envelope,
.use_quaternion = use_quaternion,
.invert_vgroup = invert_vgroup,
.use_dverts = use_dverts,
.armature_def_nr = armature_def_nr,
.dverts = dverts,
.dverts_len = dverts_len,
.pchan_from_defbase = pchan_from_defbase,
.defbase_len = defbase_len,
.bmesh =
{
.cd_dvert_offset = cd_dvert_offset,
},
};
float obinv[4][4];
invert_m4_m4(obinv, ob_target->object_to_world);
mul_m4_m4m4(data.postmat, obinv, ob_arm->object_to_world);
invert_m4_m4(data.premat, data.postmat);
if (em_target != NULL) {
/* 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)
{
armature_deform_coords_impl(ob_arm,
ob_target,
vert_coords,
vert_deform_mats,
vert_coords_len,
deformflag,
vert_coords_prev,
defgrp_name,
NULL,
NULL,
gps_target);
}
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)
{
armature_deform_coords_impl(ob_arm,
ob_target,
vert_coords,
vert_deform_mats,
vert_coords_len,
deformflag,
vert_coords_prev,
defgrp_name,
me_target,
NULL,
NULL);
}
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,
BMEditMesh *em_target)
{
armature_deform_coords_impl(ob_arm,
ob_target,
vert_coords,
vert_deform_mats,
vert_coords_len,
deformflag,
vert_coords_prev,
defgrp_name,
NULL,
em_target,
NULL);
}
/** \} */