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
return pose_bone.matrix @ deform @ pose_bone.bone.matrix_local.inverted()
# Armature modifier deforming vertices:
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,
int *r_index,
float *r_blend_next);
/**
* 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];
/* 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(