Python API: expose the math mapping vertex positions to B-Bone segments #105419
|
@ -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:
|
||||
angavrilov marked this conversation as resolved
Outdated
Sybren A. Stüvel
commented
This gives me a 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
|
|
@ -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,
|
||||
Sybren A. Stüvel
commented
Document what the parameters mean, including what Document what the parameters mean, including what `r_index` actually indexes.
|
||||
int *r_index,
|
||||
float *r_blend_next);
|
||||
Sybren A. Stüvel
commented
What does the 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.
Alexander Gavrilov
commented
changed 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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
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];
|
||||
Sybren A. Stüvel
commented
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.
Alexander Gavrilov
commented
This is the only match for 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);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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(
|
||||
|
|
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.