From 755dcee6504d6e2378a917fa76eec6c5b12b2607 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 3 Mar 2023 20:44:35 +0200 Subject: [PATCH] Python API: expose the math mapping vertex positions to B-Bone segments. Recently a user expressed interest in exporting baked animation with B-Bone segments. Currently the python API already exposes segment matrices via a PoseBone method, but there is no access to the mapping of vertices to the segments. Although currently the math is simple and easy to re-implement, forcing Python add-ons to do that would cause a maintenance issue if the mapping is ever changed later (it's quite dumb, ignoring the rest pose curve, and there definitely is room for improvement). This patch extracts the relevant math into a BKE function, and exposes it in the python API as a new PoseBone method. --- ...bpy.types.PoseBone.bbone_segment_matrix.py | 34 ++++++++++++++ source/blender/blenkernel/BKE_armature.h | 23 ++++++++- source/blender/blenkernel/intern/armature.cc | 28 ++++++++--- .../blenkernel/intern/armature_deform.cc | 8 +--- .../blender/blenkernel/intern/constraint.cc | 10 +--- .../blender/makesrna/intern/rna_pose_api.cc | 47 ++++++++++++++++++- 6 files changed, 127 insertions(+), 23 deletions(-) create mode 100644 doc/python_api/examples/bpy.types.PoseBone.bbone_segment_matrix.py diff --git a/doc/python_api/examples/bpy.types.PoseBone.bbone_segment_matrix.py b/doc/python_api/examples/bpy.types.PoseBone.bbone_segment_matrix.py new file mode 100644 index 00000000000..6a94ea2cd1d --- /dev/null +++ b/doc/python_api/examples/bpy.types.PoseBone.bbone_segment_matrix.py @@ -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 diff --git a/source/blender/blenkernel/BKE_armature.h b/source/blender/blenkernel/BKE_armature.h index 367eb9e16ea..78d0815e5a9 100644 --- a/source/blender/blenkernel/BKE_armature.h +++ b/source/blender/blenkernel/BKE_armature.h @@ -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); diff --git a/source/blender/blenkernel/intern/armature.cc b/source/blender/blenkernel/intern/armature.cc index 98392fb3882..bbc33b25bee 100644 --- a/source/blender/blenkernel/intern/armature.cc +++ b/source/blender/blenkernel/intern/armature.cc @@ -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); +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/intern/armature_deform.cc b/source/blender/blenkernel/intern/armature_deform.cc index e280b11bc26..dd8713dae60 100644 --- a/source/blender/blenkernel/intern/armature_deform.cc +++ b/source/blender/blenkernel/intern/armature_deform.cc @@ -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); diff --git a/source/blender/blenkernel/intern/constraint.cc b/source/blender/blenkernel/intern/constraint.cc index da70ca9cb6c..c3605289d78 100644 --- a/source/blender/blenkernel/intern/constraint.cc +++ b/source/blender/blenkernel/intern/constraint.cc @@ -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. */ diff --git a/source/blender/makesrna/intern/rna_pose_api.cc b/source/blender/makesrna/intern/rna_pose_api.cc index 1435978b891..7f7a443a14c 100644 --- a/source/blender/makesrna/intern/rna_pose_api.cc +++ b/source/blender/makesrna/intern/rna_pose_api.cc @@ -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( -- 2.30.2