Python API: expose the math mapping vertex positions to B-Bone segments #105419

Merged
Alexander Gavrilov merged 1 commits from angavrilov/blender:pr-pyapi-bbone-weight into main 2023-08-03 14:44:46 +02:00
6 changed files with 127 additions and 23 deletions

View File

@ -0,0 +1,34 @@
"""
This example shows how to use B-Bone segment matrices to emulate deformation
produced by the Armature modifier or constraint when assigned to the given bone
(without Preserve Volume). The coordinates are processed in armature Pose space:
"""
def bbone_deform_matrix(pose_bone, point):
index, blend_next = pose_bone.bbone_segment_index(point)
rest1 = pose_bone.bbone_segment_matrix(index, rest=True)
pose1 = pose_bone.bbone_segment_matrix(index, rest=False)
deform1 = pose1 @ rest1.inverted()
# bbone_segment_index ensures that index + 1 is always valid
rest2 = pose_bone.bbone_segment_matrix(index + 1, rest=True)
pose2 = pose_bone.bbone_segment_matrix(index + 1, rest=False)
deform2 = pose2 @ rest2.inverted()
deform = deform1 * (1 - blend_next) + deform2 * blend_next

Could you expand this example so that it actually does something? Could be as simple as moving an empty along with the bbone.

Could you expand this example so that it actually does something? Could be as simple as moving an empty along with the bbone.

I added a couple of lines showing how to emulate an Armature modifier or constraint with a single bone by updating vertex coordinates or matrices. However they are still abstract, because the purpose of this example is to document how exactly the math works with the output of the API functions, not to provide some code that moves empties and such.

I added a couple of lines showing how to emulate an Armature modifier or constraint with a single bone by updating vertex coordinates or matrices. However they are still abstract, because the purpose of this example is to document how exactly the math works with the output of the API functions, not to provide some code that moves empties and such.
return pose_bone.matrix @ deform @ pose_bone.bone.matrix_local.inverted()
# Armature modifier deforming vertices:
angavrilov marked this conversation as resolved Outdated

This gives me a NameError: name 'vertices' is not defined. Can you give an example that can actually be used to see the code in action?

This gives me a `NameError: name 'vertices' is not defined`. Can you give an example that can actually be used to see the code in action?
mesh = bpy.data.objects["Mesh"]
pose_bone = bpy.data.objects["Armature"].pose.bones["Bone"]
for vertex in mesh.data.vertices:
vertex.co = bbone_deform_matrix(pose_bone, vertex.co) @ vertex.co
# Armature constraint modifying an object transform:
empty = bpy.data.objects["Empty"]
matrix = empty.matrix_world
empty.matrix_world = bbone_deform_matrix(pose_bone, matrix.translation) @ matrix

View File

@ -539,10 +539,29 @@ void BKE_pchan_bbone_segments_cache_copy(struct bPoseChannel *pchan,
/**
* Calculate index and blend factor for the two B-Bone segment nodes
* affecting the point at 0 <= pos <= 1.
* affecting the specified point along the bone.
*
* \param pchan: Pose channel.
* \param head_tail: head-tail position along the bone (auto-clamped between 0 and 1).
* \param r_index: OUTPUT index of the first segment joint affecting the point.
* \param r_blend_next: OUTPUT blend factor between the first and the second segment in [0..1]
*/
void BKE_pchan_bbone_deform_clamp_segment_index(const struct bPoseChannel *pchan,
float head_tail,

Document what the parameters mean, including what r_index actually indexes.

Document what the parameters mean, including what `r_index` actually indexes.
int *r_index,
float *r_blend_next);

What does the _pt suffix mean? I think the name is not different enough from BKE_pchan_bbone_deform_segment_index to make it clear when to use which one.

What does the `_pt` suffix mean? I think the name is not different enough from `BKE_pchan_bbone_deform_segment_index` to make it clear when to use which one.

changed _pt to _from_point

changed `_pt` to `_from_point`
/**
* Calculate index and blend factor for the two B-Bone segment nodes
* affecting the specified point in object (pose) space.
*
* \param pchan: Pose channel.
* \param co: Pose space coordinates of the point being deformed.
* \param r_index: OUTPUT index of the first segment joint affecting the point.
* \param r_blend_next: OUTPUT blend factor between the first and the second segment in [0..1]
*/
void BKE_pchan_bbone_deform_segment_index(const struct bPoseChannel *pchan,
float pos,
const float *co,
int *r_index,
float *r_blend_next);

View File

@ -1565,20 +1565,20 @@ void BKE_pchan_bbone_segments_cache_copy(bPoseChannel *pchan, bPoseChannel *pcha
}
}
void BKE_pchan_bbone_deform_segment_index(const bPoseChannel *pchan,
float pos,
int *r_index,
float *r_blend_next)
void BKE_pchan_bbone_deform_clamp_segment_index(const bPoseChannel *pchan,
float head_tail,
int *r_index,
float *r_blend_next)
{
int segments = pchan->bone->segments;
CLAMP(pos, 0.0f, 1.0f);
CLAMP(head_tail, 0.0f, 1.0f);
/* Calculate the indices of the 2 affecting b_bone segments.
* Integer part is the first segment's index.
* Integer part plus 1 is the second segment's index.
* Fractional part is the blend factor. */
float pre_blend = pos * float(segments);
float pre_blend = head_tail * float(segments);
int index = int(floorf(pre_blend));
CLAMP(index, 0, segments - 1);
@ -1590,6 +1590,22 @@ void BKE_pchan_bbone_deform_segment_index(const bPoseChannel *pchan,
*r_blend_next = blend;
}
void BKE_pchan_bbone_deform_segment_index(const bPoseChannel *pchan,
const float *co,
int *r_index,
float *r_blend_next)
{
const Mat4 *mats = pchan->runtime.bbone_deform_mats;
const float(*mat)[4] = mats[0].mat;
/* Transform co to bone space and get its y component. */
const float y = mat[0][1] * co[0] + mat[1][1] * co[1] + mat[2][1] * co[2] + mat[3][1];

The line should IMO be a function in the math library, and not something that's inlined here. Getting a single element of a matrix-vector multiplication could be useful in other situations as well. It would also make the abstraction level of this function more uniform.

The line should IMO be a function in the math library, and not something that's inlined here. Getting a single element of a matrix-vector multiplication could be useful in other situations as well. It would also make the abstraction level of this function more uniform.

This is the only match for \[0\]\[1\].*\[1\]\[1\].*\[2\]\[1\].*\[3\]\[1\] that is not in the context of a complete matrix*vector multiplication, so I don't think introducing a single use function would be warranted.

This is the only match for `\[0\]\[1\].*\[1\]\[1\].*\[2\]\[1\].*\[3\]\[1\]` that is not in the context of a complete matrix*vector multiplication, so I don't think introducing a single use function would be warranted.
/* Calculate the indices of the 2 affecting b_bone segments. */
BKE_pchan_bbone_deform_clamp_segment_index(
pchan, y / pchan->bone->length, r_index, r_blend_next);
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -106,15 +106,11 @@ static void b_bone_deform(const bPoseChannel *pchan,
{
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;
float blend;
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);
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);

View File

@ -729,7 +729,7 @@ static void constraint_target_to_mat4(Object *ob,
int index;
/* figure out which segment(s) the headtail value falls in */
BKE_pchan_bbone_deform_segment_index(pchan, headtail, &index, &fac);
BKE_pchan_bbone_deform_clamp_segment_index(pchan, headtail, &index, &fac);
/* apply full transformation of the segment if requested */
if (full_bbone) {
@ -2631,18 +2631,12 @@ static void armdef_accumulate_bone(bConstraintTarget *ct,
if (bone->segments > 1 && bone->segments == pchan->runtime.bbone_segments) {
Mat4 *b_bone_mats = pchan->runtime.bbone_deform_mats;
Mat4 *b_bone_rest_mats = pchan->runtime.bbone_rest_mats;
float(*iamat)[4] = b_bone_mats[0].mat;
float basemat[4][4];
/* The target is a B-Bone:
* FIRST: find the segment (see b_bone_deform in `armature.cc`)
* Need to transform co back to bone-space, only need y. */
float y = iamat[0][1] * co[0] + iamat[1][1] * co[1] + iamat[2][1] * co[2] + iamat[3][1];
/* Blend the matrix. */
int index;
float blend;
BKE_pchan_bbone_deform_segment_index(pchan, y / bone->length, &index, &blend);
BKE_pchan_bbone_deform_segment_index(pchan, co, &index, &blend);
if (r_sum_dq != nullptr) {
/* Compute the object space rest matrix of the segment. */

View File

@ -47,6 +47,24 @@ static float rna_PoseBone_do_envelope(bPoseChannel *chan, const float vec[3])
bone->dist * scale);
}
static void rna_PoseBone_bbone_segment_index(
bPoseChannel *pchan, ReportList *reports, const float pt[3], int *r_index, float *r_blend_next)
{
if (!pchan->bone || pchan->bone->segments <= 1) {
BKE_reportf(reports, RPT_ERROR, "Bone '%s' is not a B-Bone!", pchan->name);
return;
}
if (pchan->runtime.bbone_segments != pchan->bone->segments) {
BKE_reportf(reports,
RPT_ERROR,
"Bone '%s' has out of date B-Bone segment data - depsgraph update required!",
pchan->name);
return;
}
BKE_pchan_bbone_deform_segment_index(pchan, pt, r_index, r_blend_next);
}
static void rna_PoseBone_bbone_segment_matrix(
bPoseChannel *pchan, ReportList *reports, float mat_ret[16], int index, bool rest)
{
@ -55,7 +73,10 @@ static void rna_PoseBone_bbone_segment_matrix(
return;
}
if (pchan->runtime.bbone_segments != pchan->bone->segments) {
BKE_reportf(reports, RPT_ERROR, "Bone '%s' has out of date B-Bone segment data!", pchan->name);
BKE_reportf(reports,
RPT_ERROR,
"Bone '%s' has out of date B-Bone segment data - depsgraph update required!",
pchan->name);
return;
}
if (index < 0 || index > pchan->runtime.bbone_segments) {
@ -271,6 +292,30 @@ void RNA_api_pose_channel(StructRNA *srna)
func, "factor", 0, -FLT_MAX, FLT_MAX, "Factor", "Envelope factor", -FLT_MAX, FLT_MAX);
RNA_def_function_return(func, parm);
/* B-Bone segment index from point */
func = RNA_def_function(srna, "bbone_segment_index", "rna_PoseBone_bbone_segment_index");
RNA_def_function_flag(func, FUNC_USE_REPORTS);
RNA_def_function_ui_description(
func, "Retrieve the index and blend factor of the B-Bone segments based on vertex position");
parm = RNA_def_float_vector_xyz(func,
"point",
3,
nullptr,
-FLT_MAX,
FLT_MAX,
"Point",
"Vertex position in armature pose space",
-FLT_MAX,
FLT_MAX);
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
/* outputs */
parm = RNA_def_property(func, "index", PROP_INT, PROP_NONE);
RNA_def_property_ui_text(parm, "", "The index of the first segment joint affecting the point");
RNA_def_function_output(func, parm);
parm = RNA_def_property(func, "blend_next", PROP_FLOAT, PROP_NONE);
RNA_def_property_ui_text(parm, "", "The blend factor between the given and the following joint");
RNA_def_function_output(func, parm);
/* B-Bone segment matrices */
func = RNA_def_function(srna, "bbone_segment_matrix", "rna_PoseBone_bbone_segment_matrix");
RNA_def_function_ui_description(