From 2068eaf1b7f7729198ad865f69fa3bd11d7a9edc Mon Sep 17 00:00:00 2001 From: Joshua Leung Date: Sun, 1 Nov 2009 11:29:40 +0000 Subject: [PATCH] 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! --- .../ui/properties_object_constraint.py | 7 + source/blender/blenkernel/BKE_armature.h | 7 +- source/blender/blenkernel/intern/action.c | 4 +- source/blender/blenkernel/intern/armature.c | 277 +++++++++++++++++- source/blender/blenkernel/intern/constraint.c | 93 +++++- source/blender/blenkernel/intern/depsgraph.c | 4 +- source/blender/blenloader/intern/readfile.c | 5 + source/blender/blenloader/intern/writefile.c | 10 +- .../editors/object/object_constraint.c | 32 +- .../editors/space_view3d/drawarmature.c | 100 +++++-- .../blender/ikplugin/intern/iksolver_plugin.c | 8 +- source/blender/makesdna/DNA_action_types.h | 43 ++- source/blender/makesdna/DNA_armature_types.h | 1 + .../blender/makesdna/DNA_constraint_types.h | 40 ++- source/blender/makesrna/RNA_access.h | 1 + .../blender/makesrna/intern/rna_constraint.c | 30 +- 16 files changed, 590 insertions(+), 72 deletions(-) diff --git a/release/scripts/ui/properties_object_constraint.py b/release/scripts/ui/properties_object_constraint.py index 29df85abf6a..d33b18b1b09 100644 --- a/release/scripts/ui/properties_object_constraint.py +++ b/release/scripts/ui/properties_object_constraint.py @@ -581,6 +581,13 @@ class ConstraintButtonsPanel(bpy.types.Panel): row = layout.row() row.itemL(text="To:") row.itemR(con, "track", expand=True) + + 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... class OBJECT_PT_constraints(ConstraintButtonsPanel): 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; diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 5d6338af409..5fc5fd8fb7d 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -2288,6 +2288,11 @@ static void direct_link_constraints(FileData *fd, ListBase *lb) if (data->prop) IDP_DirectLinkProperty(data->prop, (fd->flags & FD_FLAGS_SWITCH_ENDIAN), fd); } + else if (cons->type == CONSTRAINT_TYPE_SPLINEIK) { + bSplineIKConstraint *data= cons->data; + + data->points= newdataadr(fd, data->points); + } } } diff --git a/source/blender/blenloader/intern/writefile.c b/source/blender/blenloader/intern/writefile.c index ee84ae59974..8ec12496e59 100644 --- a/source/blender/blenloader/intern/writefile.c +++ b/source/blender/blenloader/intern/writefile.c @@ -1077,7 +1077,15 @@ static void write_constraints(WriteData *wd, ListBase *conlist) of library blocks that implement this.*/ IDP_WriteProperty(data->prop, wd); } - break; + break; + case CONSTRAINT_TYPE_SPLINEIK: + { + bSplineIKConstraint *data= (bSplineIKConstraint*)con->data; + + /* write points array */ + writedata(wd, DATA, sizeof(float)*(data->numpoints), data->points); + } + break; } } diff --git a/source/blender/editors/object/object_constraint.c b/source/blender/editors/object/object_constraint.c index 8c0da354938..15ec8d6116d 100644 --- a/source/blender/editors/object/object_constraint.c +++ b/source/blender/editors/object/object_constraint.c @@ -327,7 +327,7 @@ static void test_constraints (Object *owner, const char substring[]) /* clear disabled-flag first */ curcon->flag &= ~CONSTRAINT_DISABLE; - + if (curcon->type == CONSTRAINT_TYPE_KINEMATIC) { bKinematicConstraint *data = curcon->data; @@ -395,6 +395,26 @@ static void test_constraints (Object *owner, const char substring[]) if (data->lockflag+3==data->trackflag) curcon->flag |= CONSTRAINT_DISABLE; } + else if (curcon->type == CONSTRAINT_TYPE_SPLINEIK) { + bSplineIKConstraint *data = curcon->data; + + /* if the number of points does not match the amount required by the chain length, + * free the points array and request a rebind... + */ + if ( (data->points == NULL) || + (!(data->flag & CONSTRAINT_SPLINEIK_NO_ROOT) && (data->numpoints != data->chainlen+1)) || + ( (data->flag & CONSTRAINT_SPLINEIK_NO_ROOT) && (data->numpoints != data->chainlen)) ) + { + /* free the points array */ + if (data->points) { + MEM_freeN(data->points); + data->points = NULL; + } + + /* clear the bound flag, forcing a rebind next time this is evaluated */ + data->flag &= ~CONSTRAINT_SPLINEIK_BOUND; + } + } /* Check targets for constraints */ if (cti && cti->get_constraint_targets) { @@ -414,7 +434,7 @@ static void test_constraints (Object *owner, const char substring[]) } /* target checks for specific constraints */ - if (ELEM(curcon->type, CONSTRAINT_TYPE_FOLLOWPATH, CONSTRAINT_TYPE_CLAMPTO)) { + if (ELEM3(curcon->type, CONSTRAINT_TYPE_FOLLOWPATH, CONSTRAINT_TYPE_CLAMPTO, CONSTRAINT_TYPE_SPLINEIK)) { if (ct->tar) { if (ct->tar->type != OB_CURVE) { ct->tar= NULL; @@ -855,7 +875,7 @@ static int pose_constraints_clear_exec(bContext *C, wmOperator *op) CTX_DATA_BEGIN(C, bPoseChannel*, pchan, selected_pchans) { free_constraints(&pchan->constraints); - pchan->constflag &= ~(PCHAN_HAS_IK|PCHAN_HAS_CONST); + pchan->constflag &= ~(PCHAN_HAS_IK|PCHAN_HAS_SPLINEIK|PCHAN_HAS_CONST); } CTX_DATA_END; @@ -947,6 +967,7 @@ static short get_new_constraint_target(bContext *C, int con_type, Object **tar_o /* curve-based constraints - set the only_curve and only_ob flags */ case CONSTRAINT_TYPE_CLAMPTO: case CONSTRAINT_TYPE_FOLLOWPATH: + case CONSTRAINT_TYPE_SPLINEIK: only_curve= 1; only_ob= 1; add= 0; @@ -1070,6 +1091,10 @@ static int constraint_add_exec(bContext *C, wmOperator *op, Object *ob, ListBase BKE_report(op->reports, RPT_ERROR, "IK Constraint can only be added to Bones."); return OPERATOR_CANCELLED; } + if ( (type == CONSTRAINT_TYPE_SPLINEIK) && ((!pchan) || (list != &pchan->constraints)) ) { + BKE_report(op->reports, RPT_ERROR, "Spline IK Constraint can only be added to Bones."); + return OPERATOR_CANCELLED; + } /* create a new constraint of the type requried, and add it to the active/given constraints list */ con = add_new_constraint(type); @@ -1119,6 +1144,7 @@ static int constraint_add_exec(bContext *C, wmOperator *op, Object *ob, ListBase } /* do type-specific tweaking to the constraint settings */ + // TODO: does action constraint need anything here - i.e. spaceonce? switch (type) { case CONSTRAINT_TYPE_CHILDOF: { diff --git a/source/blender/editors/space_view3d/drawarmature.c b/source/blender/editors/space_view3d/drawarmature.c index 26135cd8d31..656d7061d48 100644 --- a/source/blender/editors/space_view3d/drawarmature.c +++ b/source/blender/editors/space_view3d/drawarmature.c @@ -209,6 +209,7 @@ static short set_pchan_glColor (short colCode, int armflag, int boneflag, int co if (constflag & PCHAN_HAS_STRIDE) glColor4ub(0, 0, 200, 80); else if (constflag & PCHAN_HAS_TARGET) glColor4ub(255, 150, 0, 80); else if (constflag & PCHAN_HAS_IK) glColor4ub(255, 255, 0, 80); + else if (constflag & PCHAN_HAS_SPLINEIK) glColor4ub(200, 255, 0, 80); else if (constflag & PCHAN_HAS_CONST) glColor4ub(0, 255, 120, 80); else if (constflag) UI_ThemeColor4(TH_BONE_POSE); // PCHAN_HAS_ACTION @@ -280,6 +281,7 @@ static short set_pchan_glColor (short colCode, int armflag, int boneflag, int co if (constflag & PCHAN_HAS_STRIDE) glColor3ub(0, 0, 200); else if (constflag & PCHAN_HAS_TARGET) glColor3ub(255, 150, 0); else if (constflag & PCHAN_HAS_IK) glColor3ub(255, 255, 0); + else if (constflag & PCHAN_HAS_SPLINEIK) glColor3ub(200, 255, 0); else if (constflag & PCHAN_HAS_CONST) glColor3ub(0, 255, 120); else if (constflag) UI_ThemeColor(TH_BONE_POSE); /* PCHAN_HAS_ACTION */ } @@ -1296,36 +1298,68 @@ static void pchan_draw_IK_root_lines(bPoseChannel *pchan, short only_temp) bPoseChannel *parchan; for (con= pchan->constraints.first; con; con= con->next) { - if (con->type == CONSTRAINT_TYPE_KINEMATIC && (con->enforce!=0.0)) { - bKinematicConstraint *data = (bKinematicConstraint*)con->data; - int segcount= 0; - - /* if only_temp, only draw if it is a temporary ik-chain */ - if ((only_temp) && !(data->flag & CONSTRAINT_IK_TEMP)) - continue; - - setlinestyle(3); - glBegin(GL_LINES); - - /* exclude tip from chain? */ - if ((data->flag & CONSTRAINT_IK_TIP)==0) - parchan= pchan->parent; - else - parchan= pchan; - - glVertex3fv(parchan->pose_tail); - - /* Find the chain's root */ - while (parchan->parent) { - segcount++; - if(segcount==data->rootbone || segcount>255) break; // 255 is weak - parchan= parchan->parent; + if (con->enforce == 0.0f) + continue; + + switch (con->type) { + case CONSTRAINT_TYPE_KINEMATIC: + { + bKinematicConstraint *data = (bKinematicConstraint*)con->data; + int segcount= 0; + + /* if only_temp, only draw if it is a temporary ik-chain */ + if ((only_temp) && !(data->flag & CONSTRAINT_IK_TEMP)) + continue; + + setlinestyle(3); + glBegin(GL_LINES); + + /* exclude tip from chain? */ + if ((data->flag & CONSTRAINT_IK_TIP)==0) + parchan= pchan->parent; + else + parchan= pchan; + + glVertex3fv(parchan->pose_tail); + + /* Find the chain's root */ + while (parchan->parent) { + segcount++; + if(segcount==data->rootbone || segcount>255) break; // 255 is weak + parchan= parchan->parent; + } + if (parchan) + glVertex3fv(parchan->pose_head); + + glEnd(); + setlinestyle(0); } - if (parchan) - glVertex3fv(parchan->pose_head); - - glEnd(); - setlinestyle(0); + break; + case CONSTRAINT_TYPE_SPLINEIK: + { + bSplineIKConstraint *data = (bSplineIKConstraint*)con->data; + int segcount= 0; + + setlinestyle(3); + glBegin(GL_LINES); + + parchan= pchan; + glVertex3fv(parchan->pose_tail); + + /* Find the chain's root */ + while (parchan->parent) { + segcount++; + // FIXME: revise the breaking conditions + if(segcount==data->chainlen || segcount>255) break; // 255 is weak + parchan= parchan->parent; + } + if (parchan) // XXX revise the breaking conditions to only stop at the tail? + glVertex3fv(parchan->pose_head); + + glEnd(); + setlinestyle(0); + } + break; } } } @@ -1743,6 +1777,14 @@ static void draw_pose_channels(Scene *scene, View3D *v3d, ARegion *ar, Base *bas pchan_draw_IK_root_lines(pchan, !(do_dashed & 2)); } } + else if (pchan->constflag & PCHAN_HAS_SPLINEIK) { + if (bone->flag & BONE_SELECTED) { + glColor3ub(150, 200, 50); // add theme! + + glLoadName(index & 0xFFFF); + pchan_draw_IK_root_lines(pchan, !(do_dashed & 2)); + } + } } } diff --git a/source/blender/ikplugin/intern/iksolver_plugin.c b/source/blender/ikplugin/intern/iksolver_plugin.c index b160e7346bd..6eb1ef56094 100644 --- a/source/blender/ikplugin/intern/iksolver_plugin.c +++ b/source/blender/ikplugin/intern/iksolver_plugin.c @@ -109,7 +109,9 @@ static void initialize_posetree(struct Object *ob, bPoseChannel *pchan_tip) if(tree==NULL) { /* make new tree */ tree= MEM_callocN(sizeof(PoseTree), "posetree"); - + + tree->type= CONSTRAINT_TYPE_KINEMATIC; + tree->iterations= data->iterations; tree->totchannel= segcount; tree->stretch = (data->flag & CONSTRAINT_IK_STRETCH); @@ -503,6 +505,10 @@ void iksolver_execute_tree(struct Scene *scene, struct Object *ob, struct bPose PoseTree *tree= pchan->iktree.first; int a; + /* stop on the first tree that isn't a standard IK chain */ + if (tree->type != CONSTRAINT_TYPE_KINEMATIC) + return; + /* 4. walk over the tree for regular solving */ for(a=0; atotchannel; a++) { if(!(tree->pchan[a]->flag & POSE_DONE)) // successive trees can set the flag diff --git a/source/blender/makesdna/DNA_action_types.h b/source/blender/makesdna/DNA_action_types.h index 8d590692794..7067c967da3 100644 --- a/source/blender/makesdna/DNA_action_types.h +++ b/source/blender/makesdna/DNA_action_types.h @@ -159,20 +159,30 @@ typedef struct bPoseChannel { /* PoseChannel (transform) flags */ typedef enum ePchan_Flag { - POSE_LOC = 0x0001, - POSE_ROT = 0x0002, - POSE_SIZE = 0x0004, - POSE_IK_MAT = 0x0008, - POSE_UNUSED2 = 0x0010, - POSE_UNUSED3 = 0x0020, - POSE_UNUSED4 = 0x0040, - POSE_UNUSED5 = 0x0080, - POSE_HAS_IK = 0x0100, - POSE_CHAIN = 0x0200, - POSE_DONE = 0x0400, - POSE_KEY = 0x1000, - POSE_STRIDE = 0x2000, - POSE_IKTREE = 0x4000, + /* has transforms */ + POSE_LOC = (1<<0), + POSE_ROT = (1<<1), + POSE_SIZE = (1<<2), + /* old IK/cache stuff... */ + POSE_IK_MAT = (1<<3), + POSE_UNUSED2 = (1<<4), + POSE_UNUSED3 = (1<<5), + POSE_UNUSED4 = (1<<6), + POSE_UNUSED5 = (1<<7), + /* has Standard IK */ + POSE_HAS_IK = (1<<8), + /* IK/Pose solving*/ + POSE_CHAIN = (1<<9), + POSE_DONE = (1<<10), + /* visualisation */ + POSE_KEY = (1<<11), + POSE_STRIDE = (1<<12), + /* standard IK solving */ + POSE_IKTREE = (1<<13), + /* has Spline IK */ + POSE_HAS_IKS = (1<<14), + /* spline IK solving */ + POSE_IKSPLINE = (1<<15), } ePchan_Flag; /* PoseChannel constflag (constraint detection) */ @@ -183,7 +193,9 @@ typedef enum ePchan_ConstFlag { PCHAN_HAS_ACTION = (1<<2), PCHAN_HAS_TARGET = (1<<3), /* only for drawing Posemode too */ - PCHAN_HAS_STRIDE = (1<<4) + PCHAN_HAS_STRIDE = (1<<4), + /* spline IK */ + PCHAN_HAS_SPLINEIK = (1<<5), } ePchan_ConstFlag; /* PoseChannel->ikflag */ @@ -202,7 +214,6 @@ typedef enum ePchan_IkFlag { BONE_IK_NO_XDOF_TEMP = (1<<10), BONE_IK_NO_YDOF_TEMP = (1<<11), BONE_IK_NO_ZDOF_TEMP = (1<<12), - } ePchan_IkFlag; /* PoseChannel->rotmode and Object->rotmode */ diff --git a/source/blender/makesdna/DNA_armature_types.h b/source/blender/makesdna/DNA_armature_types.h index 0f80fb4821f..d3e611178fe 100644 --- a/source/blender/makesdna/DNA_armature_types.h +++ b/source/blender/makesdna/DNA_armature_types.h @@ -73,6 +73,7 @@ typedef struct Bone { typedef struct bArmature { ID id; struct AnimData *adt; + ListBase bonebase; ListBase chainbase; ListBase *edbo; /* editbone listbase, we use pointer so we can check state */ diff --git a/source/blender/makesdna/DNA_constraint_types.h b/source/blender/makesdna/DNA_constraint_types.h index 7b0b1c3d814..bf8139ce6a0 100644 --- a/source/blender/makesdna/DNA_constraint_types.h +++ b/source/blender/makesdna/DNA_constraint_types.h @@ -32,7 +32,6 @@ #define DNA_CONSTRAINT_TYPES_H #include "DNA_ID.h" -#include "DNA_ipo_types.h" #include "DNA_listBase.h" #include "DNA_object_types.h" @@ -41,9 +40,10 @@ struct Text; struct Ipo; /* channels reside in Object or Action (ListBase) constraintChannels */ +// XXX depreceated... old AnimSys typedef struct bConstraintChannel { struct bConstraintChannel *next, *prev; - Ipo *ipo; + struct Ipo *ipo; short flag; char name[30]; } bConstraintChannel; @@ -66,6 +66,7 @@ typedef struct bConstraint { int pad; struct Ipo *ipo; /* local influence ipo or driver */ // XXX depreceated for 2.5... old animation system hack + /* below are readonly fields that are set at runtime by the solver for use in the GE (only IK atm) */ float lin_error; /* residual error on constraint expressed in blender unit*/ float rot_error; /* residual error on constraint expressed in radiant */ @@ -122,7 +123,7 @@ typedef struct bPythonConstraint { } bPythonConstraint; -/* inverse-Kinematics (IK) constraint +/* Inverse-Kinematics (IK) constraint This constraint supports a variety of mode determine by the type field according to B_CONSTRAINT_IK_TYPE. Some fields are used by all types, some are specific to some types @@ -151,6 +152,27 @@ typedef enum B_CONSTRAINT_IK_TYPE { CONSTRAINT_IK_DISTANCE /* maintain distance with target */ } B_CONSTRAINT_IK_TYPE; + +/* Spline IK Constraint + * Aligns 'n' bones to the curvature defined by the curve, + * with the chain ending on the bone that owns this constraint, + * and starting on the nth parent. + */ +typedef struct bSplineIKConstraint { + /* target(s) */ + Object *tar; /* curve object (with follow path enabled) which drives the bone chain */ + + /* binding details */ + float *points; /* array of numpoints items, denoting parametric positions along curve that joints should follow */ + short numpoints; /* number of points to bound in points array */ + short chainlen; /* number of bones ('n') that are in the chain */ + + /* settings */ + short flag; /* general settings for constraint */ + short upflag; /* axis of bone that points up */ +} bSplineIKConstraint; + + /* Single-target subobject constraints --------------------- */ /* Track To Constraint */ typedef struct bTrackToConstraint { @@ -378,9 +400,10 @@ typedef enum B_CONSTAINT_TYPES { CONSTRAINT_TYPE_TRANSFORM, /* transformation (loc/rot/size -> loc/rot/size) constraint */ CONSTRAINT_TYPE_SHRINKWRAP, /* shrinkwrap (loc/rot) constraint */ CONSTRAINT_TYPE_DAMPTRACK, /* New Tracking constraint that minimises twisting */ + CONSTRAINT_TYPE_SPLINEIK, /* Spline-IK - Align 'n' bones to a curve */ - /* NOTE: everytime a new constraint is added, update this */ - NUM_CONSTRAINT_TYPES= CONSTRAINT_TYPE_DAMPTRACK + /* NOTE: no constraints are allowed to be added after this */ + NUM_CONSTRAINT_TYPES } B_CONSTRAINT_TYPES; /* bConstraint->flag */ @@ -521,6 +544,13 @@ typedef enum B_CONSTRAINTCHANNEL_FLAG { /* axis relative to target */ #define CONSTRAINT_IK_TARGETAXIS 16384 +/* 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) /* MinMax (floor) flags */ #define MINMAX_STICKY 0x01 diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index 1d4aa100fce..44b36e65aa3 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -413,6 +413,7 @@ extern StructRNA RNA_SoftBodyModifier; extern StructRNA RNA_SoftBodySettings; extern StructRNA RNA_Sound; extern StructRNA RNA_SoundSequence; +extern StructRNA RNA_SplineIKConstraint; extern StructRNA RNA_Space; extern StructRNA RNA_Space3DView; extern StructRNA RNA_SpaceConsole; diff --git a/source/blender/makesrna/intern/rna_constraint.c b/source/blender/makesrna/intern/rna_constraint.c index 7a9a07939b1..63b1538354f 100644 --- a/source/blender/makesrna/intern/rna_constraint.c +++ b/source/blender/makesrna/intern/rna_constraint.c @@ -55,6 +55,7 @@ EnumPropertyItem constraint_type_items[] ={ {CONSTRAINT_TYPE_CLAMPTO, "CLAMP_TO", ICON_CONSTRAINT_DATA, "Clamp To", ""}, {CONSTRAINT_TYPE_STRETCHTO, "STRETCH_TO",ICON_CONSTRAINT_DATA, "Stretch To", ""}, {CONSTRAINT_TYPE_KINEMATIC, "IK", ICON_CONSTRAINT_DATA, "Inverse Kinematics", ""}, + {CONSTRAINT_TYPE_SPLINEIK, "SPLINE_IK", ICON_CONSTRAINT_DATA, "Spline IK", ""}, {0, "", 0, "Relationship", ""}, {CONSTRAINT_TYPE_CHILDOF, "CHILD_OF", ICON_CONSTRAINT_DATA, "Child Of", ""}, {CONSTRAINT_TYPE_MINMAX, "FLOOR", ICON_CONSTRAINT_DATA, "Floor", ""}, @@ -147,6 +148,8 @@ static StructRNA *rna_ConstraintType_refine(struct PointerRNA *ptr) return &RNA_ShrinkwrapConstraint; case CONSTRAINT_TYPE_DAMPTRACK: return &RNA_DampedTrackConstraint; + case CONSTRAINT_TYPE_SPLINEIK: + return &RNA_SplineIKConstraint; default: return &RNA_UnknownType; } @@ -1169,7 +1172,7 @@ static void rna_def_constraint_clamp_to(BlenderRNA *brna) RNA_def_struct_sdna_from(srna, "bClampToConstraint", "data"); prop= RNA_def_property(srna, "target", PROP_POINTER, PROP_NONE); - RNA_def_property_pointer_sdna(prop, NULL, "tar"); + RNA_def_property_pointer_sdna(prop, NULL, "tar"); // TODO: curve only! RNA_def_property_ui_text(prop, "Target", "Target Object"); RNA_def_property_flag(prop, PROP_EDITABLE); RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_dependency_update"); @@ -1672,6 +1675,30 @@ static void rna_def_constraint_damped_track(BlenderRNA *brna) RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_update"); } +static void rna_def_constraint_spline_ik(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + 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"); + + 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_flag(prop, PROP_EDITABLE); + RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_dependency_update"); + + prop= RNA_def_property(srna, "chain_length", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "chainlen"); + RNA_def_property_range(prop, 1, 255); // TODO: this should really check the max length of the chain the constraint is attached to + RNA_def_property_ui_text(prop, "Chain Length", "How many bones are included in the chain"); + 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? +} + /* base struct for constraints */ void RNA_def_constraint(BlenderRNA *brna) { @@ -1773,6 +1800,7 @@ void RNA_def_constraint(BlenderRNA *brna) rna_def_constraint_transform(brna); rna_def_constraint_shrinkwrap(brna); rna_def_constraint_damped_track(brna); + rna_def_constraint_spline_ik(brna); } #endif