diff --git a/release/scripts/ui/properties_object_constraint.py b/release/scripts/ui/properties_object_constraint.py index 1c0f58ae796..485fd0d0973 100644 --- a/release/scripts/ui/properties_object_constraint.py +++ b/release/scripts/ui/properties_object_constraint.py @@ -36,7 +36,7 @@ class ConstraintButtonsPanel(bpy.types.Panel): # show/key buttons here are most likely obsolete now, with # keyframing functionality being part of every button - if con.type not in ('RIGID_BODY_JOINT', 'NULL'): + if con.type not in ('RIGID_BODY_JOINT', 'SPLINE_IK', 'NULL'): box.itemR(con, "influence") def space_template(self, layout, con, target=True, owner=True): @@ -597,10 +597,17 @@ class ConstraintButtonsPanel(bpy.types.Panel): def SPLINE_IK(self, context, layout, con): self.target_template(layout, con) - - row = layout.row() - row.itemR(con, "chain_length") - # TODO: add the various options this constraint has... + + col = layout.column() + col.itemL(text="Spline Fitting:") + col.itemR(con, "chain_length") + col.itemR(con, "even_divisions") + #col.itemR(con, "affect_root") # XXX: this is not that useful yet + + col = layout.column() + col.itemL(text="Chain Scaling:") + col.itemR(con, "keep_max_length") + col.itemR(con, "radius_to_thickness") class OBJECT_PT_constraints(ConstraintButtonsPanel): diff --git a/source/blender/blenkernel/intern/armature.c b/source/blender/blenkernel/intern/armature.c index e5bf0fb35a9..62c4143632c 100644 --- a/source/blender/blenkernel/intern/armature.c +++ b/source/blender/blenkernel/intern/armature.c @@ -34,13 +34,12 @@ #include "MEM_guardedalloc.h" -//XXX #include "nla.h" - #include "BLI_arithb.h" #include "BLI_blenlib.h" #include "DNA_armature_types.h" #include "DNA_action_types.h" +#include "DNA_curve_types.h" #include "DNA_constraint_types.h" #include "DNA_mesh_types.h" #include "DNA_lattice_types.h" @@ -1613,7 +1612,10 @@ typedef struct tSplineIK_Tree { int type; /* type of IK that this serves (CONSTRAINT_TYPE_KINEMATIC or ..._SPLINEIK) */ - int chainlen; /* number of bones in the chain */ + short free_points; /* free the point positions array */ + short chainlen; /* number of bones in the chain */ + + float *points; /* parametric positions for the joints along the curve */ bPoseChannel **chain; /* chain of bones to affect using Spline IK (ordered from the tip) */ bPoseChannel *root; /* bone that is the root node of the chain */ @@ -1631,8 +1633,9 @@ static void splineik_init_tree_from_pchan(Object *ob, bPoseChannel *pchan_tip) bPoseChannel *pchanChain[255]; bConstraint *con = NULL; bSplineIKConstraint *ikData = NULL; - float boneLengths[255]; + float boneLengths[255], *jointPoints; float totLength = 0.0f; + short free_joints = 0; int segcount = 0; /* find the SplineIK constraint */ @@ -1662,10 +1665,8 @@ static void splineik_init_tree_from_pchan(Object *ob, bPoseChannel *pchan_tip) pchanChain[segcount]= pchan; /* if performing rebinding, calculate the length of the bone */ - if ((ikData->flag & CONSTRAINT_SPLINEIK_BOUND) == 0) { - boneLengths[segcount]= pchan->bone->length; - totLength += boneLengths[segcount]; - } + boneLengths[segcount]= pchan->bone->length; + totLength += boneLengths[segcount]; /* check if we've gotten the number of bones required yet (after incrementing the count first) * NOTE: the 255 limit here is rather ugly, but the standard IK does this too! @@ -1699,9 +1700,8 @@ static void splineik_init_tree_from_pchan(Object *ob, bPoseChannel *pchan_tip) /* 'head' joints * - 2 methods; the one chosen depends on whether we've got usable lengths */ - if (totLength == 0.0f) { + if ((ikData->flag & CONSTRAINT_SPLINEIK_EVENSPLITS) || (totLength == 0.0f)) { /* 1) equi-spaced joints */ - // TODO: maybe this should become an option too, in case we want this option by default ikData->points[i]= segmentLen; } else { @@ -1724,8 +1724,37 @@ static void splineik_init_tree_from_pchan(Object *ob, bPoseChannel *pchan_tip) /* apply corrections for sensitivity to scaling on a copy of the bind points, * since it's easier to determine the positions of all the joints beforehand this way */ - // TODO: code me! - + if ((ikData->flag & CONSTRAINT_SPLINEIK_SCALE_LIMITED) && (totLength != 0.0f)) { + Curve *cu= (Curve *)ikData->tar->data; + float splineLen, maxScale; + int i; + + /* make a copy of the points array, that we'll store in the tree + * - although we could just multiply the points on the fly, this approach means that + * we can introduce per-segment stretchiness later if it is necessary + */ + jointPoints= MEM_dupallocN(ikData->points); + free_joints= 1; + + /* get the current length of the curve */ + // NOTE: this is assumed to be correct even after the curve was resized + splineLen= cu->path->totdist; + + /* calculate the scale factor to multiply all the path values by so that the + * bone chain retains its current length, such that + * maxScale * splineLen = totLength + */ + maxScale = totLength / splineLen; + + /* apply scaling correction to all of the temporary points */ + for (i = 0; i < segcount; i++) + jointPoints[i] *= maxScale; + } + else { + /* just use the existing points array */ + jointPoints= ikData->points; + free_joints= 0; + } /* make a new Spline-IK chain, and store it in the IK chains */ // TODO: we should check if there is already an IK chain on this, since that would take presidence... @@ -1740,6 +1769,11 @@ static void splineik_init_tree_from_pchan(Object *ob, bPoseChannel *pchan_tip) tree->chain= MEM_callocN(sizeof(bPoseChannel*)*segcount, "SplineIK Chain"); memcpy(tree->chain, pchanChain, sizeof(bPoseChannel*)*segcount); + /* store reference to joint position array */ + tree->points= jointPoints; + tree->free_points= free_joints; + + /* store references to different parts of the chain */ tree->root= pchanRoot; tree->con= con; tree->ikData= ikData; @@ -1767,28 +1801,53 @@ static void splineik_init_tree(Scene *scene, Object *ob, float ctime) /* ----------- */ /* Evaluate spline IK for a given bone */ -// TODO: this method doesn't allow for non-strechiness... -// TODO: include code for dealing with constraint blending -static void splineik_evaluate_bone(tSplineIK_Tree *tree, Object *ob, bPoseChannel *pchan, int index) +static void splineik_evaluate_bone(tSplineIK_Tree *tree, Scene *scene, Object *ob, bPoseChannel *pchan, int index, float ctime) { bSplineIKConstraint *ikData= tree->ikData; float dirX[3]={1,0,0}, dirZ[3]={0,0,1}; float axis1[3], axis2[3], tmpVec[3]; float splineVec[3], scaleFac; + float rad, radius=1.0f; float vec[4], dir[3]; - /* step 1: get xyz positions for the endpoints of the bone */ + /* step 1: get xyz positions for the endpoints of the bone + * assume that they can be calculated on the path so that these calls will never fail + */ /* tail */ - if ( where_on_path(ikData->tar, ikData->points[index], vec, dir, NULL, NULL) ) { + if ( where_on_path(ikData->tar, tree->points[index], vec, dir, NULL, &rad) ) { /* convert the position to pose-space, then store it */ Mat4MulVecfl(ob->imat, vec); VECCOPY(pchan->pose_tail, vec); + + /* set the new radius */ + radius= rad; } - /* head */ // TODO: only calculate here when we're - if ( where_on_path(ikData->tar, ikData->points[index+1], vec, dir, NULL, NULL) ) { - /* store the position, and convert it to pose space */ - Mat4MulVecfl(ob->imat, vec); - VECCOPY(pchan->pose_head, vec); + /* head + * - check that this isn't the last bone that is subject to restrictions + * i.e. if no-root option is enabled, the root should be calculated in the standard way + */ + if ((ikData->flag & CONSTRAINT_SPLINEIK_NO_ROOT)==0 || (index+1 < ikData->chainlen)) + { + /* the head location of this bone is driven by the spline */ + if ( where_on_path(ikData->tar, tree->points[index+1], vec, dir, NULL, &rad) ) { + /* store the position, and convert it to pose space */ + Mat4MulVecfl(ob->imat, vec); + VECCOPY(pchan->pose_head, vec); + + /* set the new radius (it should be the average value) */ + radius = (radius+rad) / 2; + } + } + else { + // FIXME: this option isn't really useful yet... + // maybe we are more interested in the head deltas that arise from this instead? + /* use the standard calculations for this */ + where_is_pose_bone(scene, ob, pchan, ctime); + + /* hack: assume for now that the pose_tail vector is still valid from the previous step, + * and set that again now so that the chain doesn't get broken + */ + VECCOPY(pchan->pose_tail, vec); } @@ -1833,9 +1892,15 @@ static void splineik_evaluate_bone(tSplineIK_Tree *tree, Object *ob, bPoseChanne // TODO: code me! - /* step 4: only multiply the y-axis by the scaling factor to get nice volume-preservation */ - // NOTE: the x+z could get multiplied too, but that may be best left as an option + /* step 4: set the scaling factors for the axes */ + /* only multiply the y-axis by the scaling factor to get nice volume-preservation */ VecMulf(pchan->pose_mat[1], scaleFac); + + /* set the scaling factors of the x and z axes from the average radius of the curve? */ + if (ikData->flag & CONSTRAINT_SPLINEIK_RAD2FAT) { + VecMulf(pchan->pose_mat[0], radius); + VecMulf(pchan->pose_mat[2], radius); + } /* step 5: set the location of the bone in the matrix */ VECCOPY(pchan->pose_mat[3], pchan->pose_head); @@ -1855,17 +1920,23 @@ static void splineik_execute_tree(Scene *scene, Object *ob, bPoseChannel *pchan_ if (tree->type == CONSTRAINT_TYPE_SPLINEIK) { int i; - /* walk over each bone in the chain, calculating the effects of spline IK */ - for (i= 0; i < tree->chainlen; i++) { + /* walk over each bone in the chain, calculating the effects of spline IK + * - the chain is traversed in the opposite order to storage order (i.e. parent to children) + * so that dependencies are correct + */ + for (i= tree->chainlen-1; i >= 0; i--) { bPoseChannel *pchan= tree->chain[i]; - splineik_evaluate_bone(tree, ob, pchan, i); + splineik_evaluate_bone(tree, scene, ob, pchan, i, ctime); } // TODO: if another pass is needed to ensure the validity of the chain after blending, it should go here + + /* free the tree info specific to SplineIK trees now */ + if (tree->chain) MEM_freeN(tree->chain); + if (tree->free_points) MEM_freeN(tree->points); } - /* free the tree info now */ - if (tree->chain) MEM_freeN(tree->chain); + /* free this tree */ BLI_freelinkN(&pchan_root->iktree, tree); } } diff --git a/source/blender/makesdna/DNA_constraint_types.h b/source/blender/makesdna/DNA_constraint_types.h index bf8139ce6a0..f5a08764c42 100644 --- a/source/blender/makesdna/DNA_constraint_types.h +++ b/source/blender/makesdna/DNA_constraint_types.h @@ -546,11 +546,15 @@ typedef enum B_CONSTRAINTCHANNEL_FLAG { /* bSplineIKConstraint->flag */ /* chain has been attached to spline */ -#define CONSTRAINT_SPLINEIK_BOUND (1<<0) - /* roll on chains is not determined by the constraint */ -#define CONSTRAINT_SPLINEIK_NO_TWIST (1<<1) - /* root of chain is not influence by the constraint */ -#define CONSTRAINT_SPLINEIK_NO_ROOT (1<<2) +#define CONSTRAINT_SPLINEIK_BOUND (1<<0) + /* root of chain is not influenced by the constraint */ +#define CONSTRAINT_SPLINEIK_NO_ROOT (1<<1) + /* bones in the chain should not scale to fit the curve */ +#define CONSTRAINT_SPLINEIK_SCALE_LIMITED (1<<2) + /* bones in the chain should take their x/z scales from the curve radius */ +#define CONSTRAINT_SPLINEIK_RAD2FAT (1<<3) + /* evenly distribute the bones along the path regardless of length */ +#define CONSTRAINT_SPLINEIK_EVENSPLITS (1<<4) /* MinMax (floor) flags */ #define MINMAX_STICKY 0x01 diff --git a/source/blender/makesrna/intern/rna_constraint.c b/source/blender/makesrna/intern/rna_constraint.c index 63b1538354f..02cf44dcc7a 100644 --- a/source/blender/makesrna/intern/rna_constraint.c +++ b/source/blender/makesrna/intern/rna_constraint.c @@ -1683,10 +1683,11 @@ static void rna_def_constraint_spline_ik(BlenderRNA *brna) srna= RNA_def_struct(brna, "SplineIKConstraint", "Constraint"); RNA_def_struct_ui_text(srna, "Spline IK Constraint", "Align 'n' bones along a curve."); RNA_def_struct_sdna_from(srna, "bSplineIKConstraint", "data"); - + + /* target chain */ prop= RNA_def_property(srna, "target", PROP_POINTER, PROP_NONE); - RNA_def_property_pointer_sdna(prop, NULL, "tar"); // TODO: curve only - RNA_def_property_ui_text(prop, "Target", "Target Object"); + RNA_def_property_pointer_sdna(prop, NULL, "tar"); + RNA_def_property_ui_text(prop, "Target", "Curve that controls this relationship"); RNA_def_property_flag(prop, PROP_EDITABLE); RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_dependency_update"); @@ -1697,6 +1698,27 @@ static void rna_def_constraint_spline_ik(BlenderRNA *brna) RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_dependency_update"); // TODO: add access to the positions array to allow more flexible aligning? + + /* settings */ + prop= RNA_def_property(srna, "affect_root", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", CONSTRAINT_SPLINEIK_NO_ROOT); + RNA_def_property_ui_text(prop, "Affect Root", "Include the root joint in the calculations."); + RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_update"); + + prop= RNA_def_property(srna, "even_divisions", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", CONSTRAINT_SPLINEIK_EVENSPLITS); + RNA_def_property_ui_text(prop, "Even Divisions", "Ignore the relative lengths of the bones when fitting to the curve."); + RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_update"); + + prop= RNA_def_property(srna, "keep_max_length", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", CONSTRAINT_SPLINEIK_SCALE_LIMITED); + RNA_def_property_ui_text(prop, "Keep Max Length", "Maintain the maximum length of the chain when spline is stretched."); + RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_update"); + + prop= RNA_def_property(srna, "radius_to_thickness", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", CONSTRAINT_SPLINEIK_RAD2FAT); + RNA_def_property_ui_text(prop, "Radius to Thickness", "Radius of the spline affects the x/z scaling of the bones."); + RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_update"); } /* base struct for constraints */