diff options
author | Joshua Leung <aligorith@gmail.com> | 2009-11-01 14:29:40 +0300 |
---|---|---|
committer | Joshua Leung <aligorith@gmail.com> | 2009-11-01 14:29:40 +0300 |
commit | 2068eaf1b7f7729198ad865f69fa3bd11d7a9edc (patch) | |
tree | cc15c3d1e3d68ff8f8ca47a353b3b1e51dee52ea /source/blender/blenkernel | |
parent | cb45db0336aa7a090c5edc0f8b6f62d334c72baa (diff) |
Rigging Goodies: Spline IK Constraint
At last, this commit introduces the Spline IK Constraint to Blender. Spline IK is a constraint that makes n bones follow the shape of a specified curve.
Simply add a chain of bones, add a curve, add a Spline IK Constraint to the tip bone and set the number of bones in the chain to make it work. Or, try the following test file:
http://download.blender.org/ftp/incoming/250_splineik_spine01.blend
Screenshots of this in action (as proof):
http://download.blender.org/ftp/incoming/b250_splineik_001_before.png
http://download.blender.org/ftp/incoming/b250_splineik_001_after.png
I've implemented this in a similar way to how standard IK solvers are done. However, this code is currently not an IK plugin, since I imagine that it would be useful to be able to combine the 2 types of IK. This can be easily changed though :)
Finally, a few notes on what to expect still:
* Constraint blending currently doesn't affect this. Getting that to work correctly will take a bit more work still.
* Options for not affecting the root joint (to make it easier to attach the chain to a stump or whatever), and non-uniform scaling options have yet to be added. I've marked the places where they can be added though
* Control over the twisting of the chain still needs investigation.
Have fun!
Diffstat (limited to 'source/blender/blenkernel')
-rw-r--r-- | source/blender/blenkernel/BKE_armature.h | 7 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/action.c | 4 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/armature.c | 277 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/constraint.c | 93 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/depsgraph.c | 4 |
5 files changed, 369 insertions, 16 deletions
diff --git a/source/blender/blenkernel/BKE_armature.h b/source/blender/blenkernel/BKE_armature.h index 19c2662054b..03f23f07c8c 100644 --- a/source/blender/blenkernel/BKE_armature.h +++ b/source/blender/blenkernel/BKE_armature.h @@ -55,11 +55,14 @@ typedef struct PoseTarget typedef struct PoseTree { struct PoseTree *next, *prev; - + + int type; /* type of IK that this serves (CONSTRAINT_TYPE_KINEMATIC or ..._SPLINEIK) */ + int totchannel; /* number of pose channels */ + struct ListBase targets; /* list of targets of the tree */ struct bPoseChannel **pchan; /* array of pose channels */ int *parent; /* and their parents */ - int totchannel; /* number of pose channels */ + float (*basis_change)[3][3]; /* basis change result from solver */ int iterations; /* iterations from the constraint */ int stretch; /* disable stretching */ diff --git a/source/blender/blenkernel/intern/action.c b/source/blender/blenkernel/intern/action.c index 25f539a1992..2dec76d1b6b 100644 --- a/source/blender/blenkernel/intern/action.c +++ b/source/blender/blenkernel/intern/action.c @@ -622,7 +622,7 @@ static void copy_pose_channel_data(bPoseChannel *pchan, const bPoseChannel *chan } } -/* checks for IK constraint, and also for Follow-Path constraint. +/* checks for IK constraint, Spline IK, and also for Follow-Path constraint. * can do more constraints flags later */ /* pose should be entirely OK */ @@ -675,6 +675,8 @@ void update_pose_constraint_flags(bPose *pose) if ((data->tar) && (data->tar->type==OB_CURVE)) pose->flag |= POSE_CONSTRAINTS_TIMEDEPEND; } + else if (con->type == CONSTRAINT_TYPE_SPLINEIK) + pchan->constflag |= PCHAN_HAS_SPLINEIK; else pchan->constflag |= PCHAN_HAS_CONST; } diff --git a/source/blender/blenkernel/intern/armature.c b/source/blender/blenkernel/intern/armature.c index 139ff9d6b19..0bb0041cece 100644 --- a/source/blender/blenkernel/intern/armature.c +++ b/source/blender/blenkernel/intern/armature.c @@ -52,6 +52,7 @@ #include "BKE_armature.h" #include "BKE_action.h" +#include "BKE_anim.h" #include "BKE_blender.h" #include "BKE_constraint.h" #include "BKE_curve.h" @@ -1604,6 +1605,260 @@ void armature_rebuild_pose(Object *ob, bArmature *arm) } +/* ********************** SPLINE IK SOLVER ******************* */ + +/* Temporary evaluation tree data used for Spline IK */ +typedef struct tSplineIK_Tree { + struct tSplineIK_Tree *next, *prev; + + int type; /* type of IK that this serves (CONSTRAINT_TYPE_KINEMATIC or ..._SPLINEIK) */ + + int chainlen; /* number of bones in the chain */ + 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 */ + + bConstraint *con; /* constraint for this chain */ + bSplineIKConstraint *ikData; /* constraint settings for this chain */ +} tSplineIK_Tree; + +/* ----------- */ + +/* Tag the bones in the chain formed by the given bone for IK */ +static void splineik_init_tree_from_pchan(Object *ob, bPoseChannel *pchan_tip) +{ + bPoseChannel *pchan, *pchanRoot=NULL; + bPoseChannel *pchanChain[255]; + bConstraint *con = NULL; + bSplineIKConstraint *ikData = NULL; + float boneLengths[255]; + float totLength = 0.0f; + int segcount = 0; + + /* find the SplineIK constraint */ + for (con= pchan_tip->constraints.first; con; con= con->next) { + if (con->type == CONSTRAINT_TYPE_SPLINEIK) { + ikData= con->data; + + /* target can only be curve */ + if ((ikData->tar == NULL) || (ikData->tar->type != OB_CURVE)) + continue; + /* skip if disabled */ + if ( (con->enforce == 0.0f) || (con->flag & (CONSTRAINT_DISABLE|CONSTRAINT_OFF)) ) + continue; + + /* otherwise, constraint is ok... */ + break; + } + } + if (con == NULL) + return; + + /* find the root bone and the chain of bones from the root to the tip + * NOTE: this assumes that the bones are connected, but that may not be true... + */ + for (pchan= pchan_tip; pchan; pchan= pchan->parent) { + /* store this segment in the chain */ + 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]; + } + + /* 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! + */ + segcount++; + if ((segcount == ikData->chainlen) || (segcount > 255)) + break; + } + + if (segcount == 0) + return; + else + pchanRoot= pchanChain[segcount-1]; + + /* perform binding step if required */ + if ((ikData->flag & CONSTRAINT_SPLINEIK_BOUND) == 0) { + int i; + + /* setup new empty array for the points list */ + if (ikData->points) + MEM_freeN(ikData->points); + ikData->numpoints= (ikData->flag & CONSTRAINT_SPLINEIK_NO_ROOT)? ikData->chainlen : ikData->chainlen+1; + ikData->points= MEM_callocN(sizeof(float)*ikData->numpoints, "Spline IK Binding"); + + /* perform binding of the joints to parametric positions along the curve based + * proportion of the total length that each bone occupies + */ + for (i = 0; i < segcount; i++) { + if (i != 0) { + /* 'head' joints + * - 2 methods; the one chosen depends on whether we've got usable lengths + */ + if (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]= (1.0f / (float)segcount); // TODO: optimize by puttig this outside the loop! + } + else { + /* 2) to find this point on the curve, we take a step from the previous joint + * a distance given by the proportion that this bone takes + */ + ikData->points[i]= ikData->points[i-1] - (boneLengths[i] / totLength); + } + } + else { + /* 'tip' of chain, special exception for the first joint */ + ikData->points[0]= 1.0f; + } + } + + /* spline has now been bound */ + ikData->flag |= CONSTRAINT_SPLINEIK_BOUND; + } + + /* 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... + { + /* make new tree */ + tSplineIK_Tree *tree= MEM_callocN(sizeof(tSplineIK_Tree), "SplineIK Tree"); + tree->type= CONSTRAINT_TYPE_SPLINEIK; + + tree->chainlen= segcount; + + /* copy over the array of links to bones in the chain (from tip to root) */ + tree->chain= MEM_callocN(sizeof(bPoseChannel*)*segcount, "SplineIK Chain"); + memcpy(tree->chain, pchanChain, sizeof(bPoseChannel*)*segcount); + + tree->root= pchanRoot; + tree->con= con; + tree->ikData= ikData; + + /* AND! link the tree to the root */ + BLI_addtail(&pchanRoot->iktree, tree); + } + + /* mark root channel having an IK tree */ + pchanRoot->flag |= POSE_IKSPLINE; +} + +/* Tag which bones are members of Spline IK chains */ +static void splineik_init_tree(Scene *scene, Object *ob, float ctime) +{ + bPoseChannel *pchan; + + /* find the tips of Spline IK chains, which are simply the bones which have been tagged as such */ + for (pchan= ob->pose->chanbase.first; pchan; pchan= pchan->next) { + if (pchan->constflag & PCHAN_HAS_SPLINEIK) + splineik_init_tree_from_pchan(ob, pchan); + } +} + +/* ----------- */ + +/* 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) +{ + 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 vec[4], dir[3]; + + /* step 1: get xyz positions for the endpoints of the bone */ + /* tail */ + if ( where_on_path(ikData->tar, ikData->points[index], vec, dir, NULL, NULL) ) { + /* convert the position to pose-space, then store it */ + Mat4MulVecfl(ob->imat, vec); + VECCOPY(pchan->pose_tail, vec); + } + /* 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); + } + + + /* step 2a: determine the implied transform from these endpoints + * - splineVec: the vector direction that the spline applies on the bone + * - scaleFac: the factor that the bone length is scaled by to get the desired amount + */ + VecSubf(splineVec, pchan->pose_tail, pchan->pose_head); + scaleFac= VecLength(splineVec) / pchan->bone->length; // TODO: this will need to be modified by blending factor + + /* step 2b: the spline vector now becomes the y-axis of the bone + * - we need to normalise the splineVec first, so that it's just a unit direction vector + */ + Mat4One(pchan->pose_mat); + + Normalize(splineVec); + VECCOPY(pchan->pose_mat[1], splineVec); + + + /* step 3: determine two vectors which will both be at right angles to the bone vector + * based on the method described at + * http://ltcconline.net/greenl/courses/203/Vectors/orthonormalBases.htm + * and normalise them to make sure they they don't act strangely + */ + /* x-axis = dirX - projection(dirX onto splineVec) */ + Projf(axis1, dirX, splineVec); /* project dirX onto splineVec */ + VecSubf(pchan->pose_mat[0], dirX, axis1); + + Normalize(pchan->pose_mat[0]); + + /* z-axis = dirZ - projection(dirZ onto splineVec) - projection(dirZ onto dirX) */ + Projf(axis1, dirZ, splineVec); /* project dirZ onto Y-Axis */ + Projf(axis2, dirZ, pchan->pose_mat[0]); /* project dirZ onto X-Axis */ + + VecSubf(tmpVec, dirZ, axis1); /* dirZ - proj(dirZ->YAxis) */ + VecSubf(pchan->pose_mat[2], tmpVec, axis2); /* (dirZ - proj(dirZ->YAxis)) - proj(dirZ->XAxis) */ + + Normalize(pchan->pose_mat[2]); + + + /* step 4a: multiply all the axes of the bone by the scaling factor to get uniform scaling */ + // TODO: maybe this can be extended to give non-uniform scaling? + //VecMulf(pchan->pose_mat[0], scaleFac); + VecMulf(pchan->pose_mat[1], scaleFac); + //VecMulf(pchan->pose_mat[2], scaleFac); + + /* step 5: set the location of the bone in the matrix */ + VECCOPY(pchan->pose_mat[3], pchan->pose_head); + + /* done! */ + pchan->flag |= POSE_DONE; +} + +/* Evaluate the chain starting from the nominated bone */ +static void splineik_execute_tree(Scene *scene, Object *ob, bPoseChannel *pchan_root, float ctime) +{ + tSplineIK_Tree *tree; + + /* for each pose-tree, execute it if it is spline, otherwise just free it */ + for (tree= pchan_root->iktree.first; tree; tree= pchan_root->iktree.first) { + /* only evaluate if tagged for Spline IK */ + 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++) { + bPoseChannel *pchan= tree->chain[i]; + splineik_evaluate_bone(tree, ob, pchan, i); + } + } + + /* free the tree info now */ + if (tree->chain) MEM_freeN(tree->chain); + BLI_freelinkN(&pchan_root->iktree, tree); + } +} + /* ********************** THE POSE SOLVER ******************* */ @@ -1629,7 +1884,7 @@ void chan_calc_mat(bPoseChannel *chan) } else { /* quats are normalised before use to eliminate scaling issues */ - NormalQuat(chan->quat); + NormalQuat(chan->quat); // TODO: do this with local vars only! QuatToMat3(chan->quat, rmat); } @@ -1908,21 +2163,31 @@ void where_is_pose (Scene *scene, Object *ob) } else { Mat4Invert(ob->imat, ob->obmat); // imat is needed - + /* 1. clear flags */ for(pchan= ob->pose->chanbase.first; pchan; pchan= pchan->next) { pchan->flag &= ~(POSE_DONE|POSE_CHAIN|POSE_IKTREE); } - /* 2. construct the IK tree */ + + /* 2a. construct the IK tree (standard IK) */ BIK_initialize_tree(scene, ob, ctime); - + + /* 2b. construct the Spline IK trees + * - this is not integrated as an IK plugin, since it should be able + * to function in conjunction with standard IK + */ + splineik_init_tree(scene, ob, ctime); + /* 3. the main loop, channels are already hierarchical sorted from root to children */ for(pchan= ob->pose->chanbase.first; pchan; pchan= pchan->next) { - - /* 4. if we find an IK root, we handle it separated */ + /* 4a. if we find an IK root, we handle it separated */ if(pchan->flag & POSE_IKTREE) { BIK_execute_tree(scene, ob, pchan, ctime); } + /* 4b. if we find a Spline IK root, we handle it separated too */ + else if(pchan->flag & POSE_IKSPLINE) { + splineik_execute_tree(scene, ob, pchan, ctime); + } /* 5. otherwise just call the normal solver */ else if(!(pchan->flag & POSE_DONE)) { where_is_pose_bone(scene, ob, pchan, ctime); diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c index 93a4ba80d7c..a319838a56f 100644 --- a/source/blender/blenkernel/intern/constraint.c +++ b/source/blender/blenkernel/intern/constraint.c @@ -1224,14 +1224,14 @@ static void followpath_get_tarmat (bConstraint *con, bConstraintOb *cob, bConstr QuatToMat4(quat, totmat); } - + if (data->followflag & FOLLOWPATH_RADIUS) { float tmat[4][4], rmat[4][4]; Mat4Scale(tmat, radius); Mat4MulMat4(rmat, totmat, tmat); Mat4CpyMat4(totmat, rmat); } - + VECCOPY(totmat[3], vec); Mat4MulSerie(ct->matrix, ct->tar->obmat, totmat, NULL, NULL, NULL, NULL, NULL, NULL); @@ -1265,7 +1265,7 @@ static void followpath_evaluate (bConstraint *con, bConstraintOb *cob, ListBase /* un-apply scaling caused by path */ if ((data->followflag & FOLLOWPATH_RADIUS)==0) { /* XXX - assume that scale correction means that radius will have some scale error in it - Campbell */ float obsize[3]; - + Mat4ToSize(cob->matrix, obsize); if (obsize[0]) VecMulf(cob->matrix[0], size[0] / obsize[0]); @@ -3446,13 +3446,95 @@ static bConstraintTypeInfo CTI_DAMPTRACK = { damptrack_evaluate /* evaluate */ }; +/* ----------- Spline IK ------------ */ + +static void splineik_free (bConstraint *con) +{ + bSplineIKConstraint *data= con->data; + + /* binding array */ + if (data->points) + MEM_freeN(data->points); +} + +static void splineik_copy (bConstraint *con, bConstraint *srccon) +{ + bSplineIKConstraint *src= srccon->data; + bSplineIKConstraint *dst= con->data; + + /* copy the binding array */ + dst->points= MEM_dupallocN(src->points); +} + +static int splineik_get_tars (bConstraint *con, ListBase *list) +{ + if (con && list) { + bSplineIKConstraint *data= con->data; + bConstraintTarget *ct; + + /* standard target-getting macro for single-target constraints without subtargets */ + SINGLETARGETNS_GET_TARS(con, data->tar, ct, list) + + return 1; + } + + return 0; +} + +static void splineik_flush_tars (bConstraint *con, ListBase *list, short nocopy) +{ + if (con && list) { + bSplineIKConstraint *data= con->data; + bConstraintTarget *ct= list->first; + + /* the following macro is used for all standard single-target constraints */ + SINGLETARGETNS_FLUSH_TARS(con, data->tar, ct, list, nocopy) + } +} + +static void splineik_get_tarmat (bConstraint *con, bConstraintOb *cob, bConstraintTarget *ct, float ctime) +{ + if (VALID_CONS_TARGET(ct)) { + Curve *cu= ct->tar->data; + + /* note: when creating constraints that follow path, the curve gets the CU_PATH set now, + * currently for paths to work it needs to go through the bevlist/displist system (ton) + */ + + /* only happens on reload file, but violates depsgraph still... fix! */ + if (cu->path==NULL || cu->path->data==NULL) + makeDispListCurveTypes(cob->scene, ct->tar, 0); + } + + /* technically, this isn't really needed for evaluation, but we don't know what else + * might end up calling this... + */ + if (ct) + Mat4One(ct->matrix); +} + +static bConstraintTypeInfo CTI_SPLINEIK = { + CONSTRAINT_TYPE_SPLINEIK, /* type */ + sizeof(bSplineIKConstraint), /* size */ + "Spline IK", /* name */ + "bSplineIKConstraint", /* struct name */ + splineik_free, /* free data */ + NULL, /* relink data */ + splineik_copy, /* copy data */ + NULL, /* new data */ + splineik_get_tars, /* get constraint targets */ + splineik_flush_tars, /* flush constraint targets */ + splineik_get_tarmat, /* get target matrix */ + NULL /* evaluate - solved as separate loop */ +}; + /* ************************* Constraints Type-Info *************************** */ /* All of the constraints api functions use bConstraintTypeInfo structs to carry out * and operations that involve constraint specific code. */ /* These globals only ever get directly accessed in this file */ -static bConstraintTypeInfo *constraintsTypeInfo[NUM_CONSTRAINT_TYPES+1]; +static bConstraintTypeInfo *constraintsTypeInfo[NUM_CONSTRAINT_TYPES]; static short CTI_INIT= 1; /* when non-zero, the list needs to be updated */ /* This function only gets called when CTI_INIT is non-zero */ @@ -3479,6 +3561,7 @@ static void constraints_init_typeinfo () { constraintsTypeInfo[19]= &CTI_TRANSFORM; /* Transformation Constraint */ constraintsTypeInfo[20]= &CTI_SHRINKWRAP; /* Shrinkwrap Constraint */ constraintsTypeInfo[21]= &CTI_DAMPTRACK; /* Damped TrackTo Constraint */ + constraintsTypeInfo[22]= &CTI_SPLINEIK; /* Spline IK Constraint */ } /* This function should be used for getting the appropriate type-info when only @@ -3494,7 +3577,7 @@ bConstraintTypeInfo *get_constraint_typeinfo (int type) /* only return for valid types */ if ( (type >= CONSTRAINT_TYPE_NULL) && - (type <= NUM_CONSTRAINT_TYPES ) ) + (type < NUM_CONSTRAINT_TYPES ) ) { /* there shouldn't be any segfaults here... */ return constraintsTypeInfo[type]; diff --git a/source/blender/blenkernel/intern/depsgraph.c b/source/blender/blenkernel/intern/depsgraph.c index d60a26e8feb..36568ee5667 100644 --- a/source/blender/blenkernel/intern/depsgraph.c +++ b/source/blender/blenkernel/intern/depsgraph.c @@ -399,7 +399,7 @@ static void build_dag_object(DagForest *dag, DagNode *scenenode, Scene *scene, O if (ct->subtarget[0]) dag_add_relation(dag,node3,node, DAG_RL_OB_DATA|DAG_RL_DATA_DATA, cti->name); - else if(ELEM(con->type, CONSTRAINT_TYPE_FOLLOWPATH, CONSTRAINT_TYPE_CLAMPTO)) + else if(ELEM3(con->type, CONSTRAINT_TYPE_FOLLOWPATH, CONSTRAINT_TYPE_CLAMPTO, CONSTRAINT_TYPE_SPLINEIK)) dag_add_relation(dag,node3,node, DAG_RL_DATA_DATA|DAG_RL_OB_DATA, cti->name); else dag_add_relation(dag,node3,node, DAG_RL_OB_DATA, cti->name); @@ -2407,7 +2407,7 @@ void DAG_pose_sort(Object *ob) bPoseChannel *target= get_pose_channel(ob->pose, ct->subtarget); if (target) { node2= dag_get_node(dag, target); - dag_add_relation(dag, node2, node, 0, "IK Constraint"); + dag_add_relation(dag, node2, node, 0, "Pose Constraint"); if (con->type==CONSTRAINT_TYPE_KINEMATIC) { bKinematicConstraint *data = (bKinematicConstraint *)con->data; |