Changeset View
Changeset View
Standalone View
Standalone View
source/blender/blenkernel/intern/armature_update.c
| Show First 20 Lines • Show All 57 Lines • ▼ Show 20 Lines | typedef struct tSplineIK_Tree { | ||||
| short chainlen; /* number of bones in the chain */ | short chainlen; /* number of bones in the chain */ | ||||
| float totlength; /* total length of bones in the chain */ | float totlength; /* total length of bones in the chain */ | ||||
| const float *points; /* parametric positions for the joints along the curve */ | const 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 **chain; /* chain of bones to affect using Spline IK (ordered from the tip) */ | ||||
| bPoseChannel *root; /* bone that is the root node of the chain */ | bPoseChannel *root; /* bone that is the root node of the chain */ | ||||
| bConstraint *con; /* constraint for this chain */ | bConstraint *con; /* constraint for this chain */ | ||||
| bSplineIKConstraint *ikData; /* constraint settings for this chain */ | bSplineIKConstraint *ik_data; /* constraint settings for this chain */ | ||||
| } tSplineIK_Tree; | } tSplineIK_Tree; | ||||
| /* ----------- */ | /* ----------- */ | ||||
| /* Tag the bones in the chain formed by the given bone for IK */ | /* Tag the bones in the chain formed by the given bone for IK. */ | ||||
| static void splineik_init_tree_from_pchan(Scene *UNUSED(scene), | static void splineik_init_tree_from_pchan(Scene *UNUSED(scene), | ||||
| Object *UNUSED(ob), | Object *UNUSED(ob), | ||||
| bPoseChannel *pchan_tip) | bPoseChannel *pchan_tip) | ||||
| { | { | ||||
| bPoseChannel *pchan, *pchanRoot = NULL; | bPoseChannel *pchan, *pchan_root = NULL; | ||||
| bPoseChannel *pchanChain[255]; | bPoseChannel *pchan_chain[255]; | ||||
| bConstraint *con = NULL; | bConstraint *con = NULL; | ||||
| bSplineIKConstraint *ikData = NULL; | bSplineIKConstraint *ik_data = NULL; | ||||
| float boneLengths[255]; | float bone_lengths[255]; | ||||
| float totLength = 0.0f; | float totlength = 0.0f; | ||||
| int segcount = 0; | int segcount = 0; | ||||
| /* find the SplineIK constraint */ | /* Find the SplineIK constraint. */ | ||||
| for (con = pchan_tip->constraints.first; con; con = con->next) { | for (con = pchan_tip->constraints.first; con; con = con->next) { | ||||
| if (con->type == CONSTRAINT_TYPE_SPLINEIK) { | if (con->type == CONSTRAINT_TYPE_SPLINEIK) { | ||||
| ikData = con->data; | ik_data = con->data; | ||||
| /* target can only be curve */ | /* Target can only be a curve. */ | ||||
| if ((ikData->tar == NULL) || (ikData->tar->type != OB_CURVE)) { | if ((ik_data->tar == NULL) || (ik_data->tar->type != OB_CURVE)) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| /* skip if disabled */ | /* Skip if disabled. */ | ||||
| if ((con->enforce == 0.0f) || (con->flag & (CONSTRAINT_DISABLE | CONSTRAINT_OFF))) { | if ((con->enforce == 0.0f) || (con->flag & (CONSTRAINT_DISABLE | CONSTRAINT_OFF))) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| /* otherwise, constraint is ok... */ | /* Otherwise, constraint is ok... */ | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| if (con == NULL) { | if (con == NULL) { | ||||
| return; | return; | ||||
| } | } | ||||
| /* find the root bone and the chain of bones from the root to the tip | /* 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... */ | * NOTE: this assumes that the bones are connected, but that may not be true... */ | ||||
| for (pchan = pchan_tip; pchan && (segcount < ikData->chainlen); | for (pchan = pchan_tip; pchan && (segcount < ik_data->chainlen); | ||||
| pchan = pchan->parent, segcount++) { | pchan = pchan->parent, segcount++) { | ||||
| /* store this segment in the chain */ | /* Store this segment in the chain. */ | ||||
| pchanChain[segcount] = pchan; | pchan_chain[segcount] = pchan; | ||||
| /* if performing rebinding, calculate the length of the bone */ | /* If performing rebinding, calculate the length of the bone. */ | ||||
| boneLengths[segcount] = pchan->bone->length; | bone_lengths[segcount] = pchan->bone->length; | ||||
| totLength += boneLengths[segcount]; | totlength += bone_lengths[segcount]; | ||||
| } | } | ||||
| if (segcount == 0) { | if (segcount == 0) { | ||||
| return; | return; | ||||
| } | } | ||||
| pchanRoot = pchanChain[segcount - 1]; | pchan_root = pchan_chain[segcount - 1]; | ||||
| /* perform binding step if required */ | /* Perform binding step if required. */ | ||||
| if ((ikData->flag & CONSTRAINT_SPLINEIK_BOUND) == 0) { | if ((ik_data->flag & CONSTRAINT_SPLINEIK_BOUND) == 0) { | ||||
| float segmentLen = (1.0f / (float)segcount); | float segmentLen = (1.0f / (float)segcount); | ||||
| /* setup new empty array for the points list */ | /* Setup new empty array for the points list. */ | ||||
| if (ikData->points) { | if (ik_data->points) { | ||||
| MEM_freeN(ikData->points); | MEM_freeN(ik_data->points); | ||||
| } | } | ||||
| ikData->numpoints = ikData->chainlen + 1; | ik_data->numpoints = ik_data->chainlen + 1; | ||||
| ikData->points = MEM_mallocN(sizeof(float) * ikData->numpoints, "Spline IK Binding"); | ik_data->points = MEM_mallocN(sizeof(float) * ik_data->numpoints, "Spline IK Binding"); | ||||
| /* bind 'tip' of chain (i.e. first joint = tip of bone with the Spline IK Constraint) */ | /* Bind 'tip' of chain (i.e. first joint = tip of bone with the Spline IK Constraint). */ | ||||
| ikData->points[0] = 1.0f; | ik_data->points[0] = 1.0f; | ||||
| /* perform binding of the joints to parametric positions along the curve based | /* Perform binding of the joints to parametric positions along the curve based | ||||
| * proportion of the total length that each bone occupies | * proportion of the total length that each bone occupies. | ||||
| */ | */ | ||||
| for (int i = 0; i < segcount; i++) { | for (int i = 0; i < segcount; i++) { | ||||
| /* 'head' joints, traveling towards the root of the chain | /* 'head' joints, traveling towards the root of the chain. | ||||
| * - 2 methods; the one chosen depends on whether we've got usable lengths | * - 2 methods; the one chosen depends on whether we've got usable lengths. | ||||
| */ | */ | ||||
| if ((ikData->flag & CONSTRAINT_SPLINEIK_EVENSPLITS) || (totLength == 0.0f)) { | if ((ik_data->flag & CONSTRAINT_SPLINEIK_EVENSPLITS) || (totlength == 0.0f)) { | ||||
| /* 1) equi-spaced joints */ | /* 1) Equi-spaced joints. */ | ||||
| ikData->points[i + 1] = ikData->points[i] - segmentLen; | ik_data->points[i + 1] = ik_data->points[i] - segmentLen; | ||||
| } | } | ||||
| else { | else { | ||||
| /* 2) to find this point on the curve, we take a step from the previous joint | /* 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 | * a distance given by the proportion that this bone takes. | ||||
| */ | */ | ||||
| ikData->points[i + 1] = ikData->points[i] - (boneLengths[i] / totLength); | ik_data->points[i + 1] = ik_data->points[i] - (bone_lengths[i] / totlength); | ||||
| } | } | ||||
| } | } | ||||
| /* spline has now been bound */ | /* Spline has now been bound. */ | ||||
| ikData->flag |= CONSTRAINT_SPLINEIK_BOUND; | ik_data->flag |= CONSTRAINT_SPLINEIK_BOUND; | ||||
| } | } | ||||
| /* disallow negative values (happens with float precision) */ | /* Disallow negative values (happens with float precision). */ | ||||
| CLAMP_MIN(ikData->points[segcount], 0.0f); | CLAMP_MIN(ik_data->points[segcount], 0.0f); | ||||
| /* make a new Spline-IK chain, and store it in the IK chains */ | /* 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, | /* TODO: we should check if there is already an IK chain on this, | ||||
| * since that would take precedence... */ | * since that would take precedence... */ | ||||
| { | { | ||||
| /* make new tree */ | /* Make a new tree. */ | ||||
| tSplineIK_Tree *tree = MEM_callocN(sizeof(tSplineIK_Tree), "SplineIK Tree"); | tSplineIK_Tree *tree = MEM_callocN(sizeof(tSplineIK_Tree), "SplineIK Tree"); | ||||
| tree->type = CONSTRAINT_TYPE_SPLINEIK; | tree->type = CONSTRAINT_TYPE_SPLINEIK; | ||||
| tree->chainlen = segcount; | tree->chainlen = segcount; | ||||
| tree->totlength = totLength; | tree->totlength = totlength; | ||||
| /* copy over the array of links to bones in the chain (from tip to root) */ | /* Copy over the array of links to bones in the chain (from tip to root). */ | ||||
| tree->chain = MEM_mallocN(sizeof(bPoseChannel *) * segcount, "SplineIK Chain"); | tree->chain = MEM_mallocN(sizeof(bPoseChannel *) * segcount, "SplineIK Chain"); | ||||
| memcpy(tree->chain, pchanChain, sizeof(bPoseChannel *) * segcount); | memcpy(tree->chain, pchan_chain, sizeof(bPoseChannel *) * segcount); | ||||
| /* store reference to joint position array */ | /* Store reference to joint position array. */ | ||||
| tree->points = ikData->points; | tree->points = ik_data->points; | ||||
| /* store references to different parts of the chain */ | /* Store references to different parts of the chain. */ | ||||
| tree->root = pchanRoot; | tree->root = pchan_root; | ||||
| tree->con = con; | tree->con = con; | ||||
| tree->ikData = ikData; | tree->ik_data = ik_data; | ||||
| /* AND! link the tree to the root */ | /* AND! Link the tree to the root. */ | ||||
| BLI_addtail(&pchanRoot->siktree, tree); | BLI_addtail(&pchan_root->siktree, tree); | ||||
| } | } | ||||
| /* mark root channel having an IK tree */ | /* Mark root channel having an IK tree. */ | ||||
| pchanRoot->flag |= POSE_IKSPLINE; | pchan_root->flag |= POSE_IKSPLINE; | ||||
| } | } | ||||
| /* Tag which bones are members of Spline IK chains */ | /* Tag which bones are members of Spline IK chains. */ | ||||
| static void splineik_init_tree(Scene *scene, Object *ob, float UNUSED(ctime)) | static void splineik_init_tree(Scene *scene, Object *ob, float UNUSED(ctime)) | ||||
| { | { | ||||
| bPoseChannel *pchan; | bPoseChannel *pchan; | ||||
| /* find the tips of Spline IK chains, | /* Find the tips of Spline IK chains, | ||||
| * which are simply the bones which have been tagged as such */ | * which are simply the bones which have been tagged as such. */ | ||||
| for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) { | for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) { | ||||
| if (pchan->constflag & PCHAN_HAS_SPLINEIK) { | if (pchan->constflag & PCHAN_HAS_SPLINEIK) { | ||||
| splineik_init_tree_from_pchan(scene, ob, pchan); | splineik_init_tree_from_pchan(scene, ob, pchan); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /* ----------- */ | /* ----------- */ | ||||
| typedef struct tSplineIk_EvalState { | typedef struct tSplineIk_EvalState { | ||||
| float curve_position; /* Current position along the curve. */ | float curve_position; /* Current position along the curve. */ | ||||
| float curve_scale; /* Global scale to apply to curve positions. */ | float curve_scale; /* Global scale to apply to curve positions. */ | ||||
| float locrot_offset[4][4]; /* Bone rotation and location offset inherited from parent. */ | float locrot_offset[4][4]; /* Bone rotation and location offset inherited from parent. */ | ||||
| float prev_tail_loc[3]; /* Tail location of the previous bone. */ | |||||
| float prev_tail_radius; /* Tail curve radius of the previous bone. */ | |||||
| int prev_tail_seg_idx; /* Curve segment the previous tail bone belongs to. */ | |||||
| } tSplineIk_EvalState; | } tSplineIk_EvalState; | ||||
| /* Prepare data to evaluate spline IK. */ | /* Prepare data to evaluate spline IK. */ | ||||
| static bool splineik_evaluate_init(tSplineIK_Tree *tree, tSplineIk_EvalState *state) | static bool splineik_evaluate_init(tSplineIK_Tree *tree, tSplineIk_EvalState *state) | ||||
| { | { | ||||
| bSplineIKConstraint *ikData = tree->ikData; | bSplineIKConstraint *ik_data = tree->ik_data; | ||||
| /* Make sure that the constraint targets are ok, to avoid crashes | /* Make sure that the constraint targets are ok, to avoid crashes | ||||
| * in case of a depsgraph bug or dependency cycle. | * in case of a depsgraph bug or dependency cycle. | ||||
| */ | */ | ||||
| if (ikData->tar == NULL) { | if (ik_data->tar == NULL) { | ||||
| return false; | return false; | ||||
| } | } | ||||
| CurveCache *cache = ikData->tar->runtime.curve_cache; | CurveCache *cache = ik_data->tar->runtime.curve_cache; | ||||
| if (ELEM(NULL, cache, cache->path, cache->path->data)) { | if (ELEM(NULL, cache, cache->anim_path_accum_length)) { | ||||
| return false; | return false; | ||||
| } | } | ||||
| /* Initialize the evaluation state. */ | /* Initialize the evaluation state. */ | ||||
| state->curve_position = 0.0f; | state->curve_position = 0.0f; | ||||
| state->curve_scale = 1.0f; | state->curve_scale = 1.0f; | ||||
| unit_m4(state->locrot_offset); | unit_m4(state->locrot_offset); | ||||
| zero_v3(state->prev_tail_loc); | |||||
| state->prev_tail_radius = 1.0f; | |||||
| state->prev_tail_seg_idx = 0; | |||||
| /* Apply corrections for sensitivity to scaling. */ | /* Apply corrections for sensitivity to scaling. */ | ||||
| if ((ikData->yScaleMode != CONSTRAINT_SPLINEIK_YS_FIT_CURVE) && (tree->totlength != 0.0f)) { | if ((ik_data->yScaleMode != CONSTRAINT_SPLINEIK_YS_FIT_CURVE) && (tree->totlength != 0.0f)) { | ||||
| /* get the current length of the curve */ | /* Get the current length of the curve. */ | ||||
| /* NOTE: this is assumed to be correct even after the curve was resized */ | /* NOTE: This is assumed to be correct even after the curve was resized. */ | ||||
| float splineLen = cache->path->totdist; | const float spline_len = BKE_anim_path_get_length(cache); | ||||
| /* calculate the scale factor to multiply all the path values by so that the | /* Calculate the scale factor to multiply all the path values by so that the | ||||
| * bone chain retains its current length, such that | * bone chain retains its current length, such that: | ||||
| * maxScale * splineLen = totLength | * maxScale * splineLen = totLength | ||||
| */ | */ | ||||
| state->curve_scale = tree->totlength / splineLen; | state->curve_scale = tree->totlength / spline_len; | ||||
| } | } | ||||
| return true; | return true; | ||||
| } | } | ||||
| static void apply_curve_transform( | |||||
| bSplineIKConstraint *ik_data, Object *ob, float radius, float r_vec[3], float *r_radius) | |||||
| { | |||||
| /* Apply the curve's object-mode transforms to the position | |||||
| * unless the option to allow curve to be positioned elsewhere is activated (i.e. no root). | |||||
| */ | |||||
| if ((ik_data->flag & CONSTRAINT_SPLINEIK_NO_ROOT) == 0) { | |||||
| mul_m4_v3(ik_data->tar->obmat, r_vec); | |||||
| } | |||||
| /* Convert the position to pose-space. */ | |||||
| mul_m4_v3(ob->imat, r_vec); | |||||
| /* Set the new radius (it should be the average value). */ | |||||
| *r_radius = (radius + *r_radius) / 2; | |||||
| } | |||||
| /* This function positions the tail of the bone so that it preserves the length of it. | |||||
| * The length of the bone can be seen as a sphere radius. | |||||
| */ | |||||
| static int position_tail_on_spline(bSplineIKConstraint *ik_data, | |||||
| const float head_pos[3], | |||||
| const float sphere_radius, | |||||
| const int prev_seg_idx, | |||||
| float r_tail_pos[3], | |||||
| float *r_new_curve_pos, | |||||
| float *r_radius) | |||||
| { | |||||
| /* This is using the tessellated curve data. | |||||
| * So we are working with piece-wise linear curve segments. | |||||
| * The same method is use in #BKE_where_on_path to get curve location data. */ | |||||
| const CurveCache *cache = ik_data->tar->runtime.curve_cache; | |||||
| const BevList *bl = cache->bev.first; | |||||
| BevPoint *bp = bl->bevpoints; | |||||
| const float spline_len = BKE_anim_path_get_length(cache); | |||||
| const float *seg_accum_len = cache->anim_path_accum_length; | |||||
| int max_seg_idx = BKE_anim_path_get_array_size(cache) - 1; | |||||
| /* Convert our initial intersection point guess to a point index. | |||||
| * If the curve was a straight line, then pointEnd would be the correct location. | |||||
| * So make it our first initial guess. | |||||
| */ | |||||
| const float guessed_len = *r_new_curve_pos * spline_len; | |||||
| BLI_assert(prev_seg_idx >= 0); | |||||
| int cur_seg_idx = prev_seg_idx; | |||||
| while (cur_seg_idx < max_seg_idx && guessed_len > seg_accum_len[cur_seg_idx]) { | |||||
| cur_seg_idx++; | |||||
| } | |||||
| int bp_idx = cur_seg_idx + 1; | |||||
| bp = bp + bp_idx; | |||||
| bool is_cyclic = bl->poly >= 0; | |||||
| BevPoint *prev_bp = bp - 1; | |||||
| /* Go to the next tessellated curve point until we cross to outside of the sphere. */ | |||||
| while (len_v3v3(head_pos, bp->vec) < sphere_radius) { | |||||
| if (bp_idx > max_seg_idx) { | |||||
| /* We are outside the defined curve. We will now extrapolate the intersection point. */ | |||||
| break; | |||||
| } | |||||
| prev_bp = bp; | |||||
| if (is_cyclic && bp_idx == max_seg_idx) { | |||||
| /* Wrap around to the start point. | |||||
| * Don't set the bp_idx to zero here as we use it to get the segment index later. | |||||
| */ | |||||
| bp = bl->bevpoints; | |||||
| } | |||||
| else { | |||||
| bp++; | |||||
| } | |||||
| bp_idx++; | |||||
| } | |||||
| float isect_1[3], isect_2[3]; | |||||
| /* Calculate the intersection point. */ | |||||
| isect_line_sphere_v3(prev_bp->vec, bp->vec, head_pos, sphere_radius, isect_1, isect_2); | |||||
| /* Because of how `isect_line_sphere_v3` works, we know that `isect_1` contains the | |||||
| * intersection point we want. And it will always intersect as we go from inside to outside | |||||
| * of the sphere. | |||||
| */ | |||||
| copy_v3_v3(r_tail_pos, isect_1); | |||||
| cur_seg_idx = bp_idx - 2; | |||||
| float prev_seg_len = 0; | |||||
| if (cur_seg_idx < 0) { | |||||
| cur_seg_idx = 0; | |||||
| prev_seg_len = 0; | |||||
| } | |||||
| else { | |||||
| prev_seg_len = seg_accum_len[cur_seg_idx]; | |||||
| } | |||||
| /* Convert the point back into the 0-1 interpolation range. */ | |||||
| const float isect_seg_len = len_v3v3(prev_bp->vec, isect_1); | |||||
| const float frac = isect_seg_len / len_v3v3(prev_bp->vec, bp->vec); | |||||
| *r_new_curve_pos = (prev_seg_len + isect_seg_len) / spline_len; | |||||
| if (*r_new_curve_pos > 1.0f) { | |||||
| *r_radius = bp->radius; | |||||
| } | |||||
| else { | |||||
| *r_radius = (1.0f - frac) * prev_bp->radius + frac * bp->radius; | |||||
| } | |||||
| return cur_seg_idx; | |||||
| } | |||||
| /* Evaluate spline IK for a given bone. */ | /* Evaluate spline IK for a given bone. */ | ||||
| static void splineik_evaluate_bone( | static void splineik_evaluate_bone( | ||||
| tSplineIK_Tree *tree, Object *ob, bPoseChannel *pchan, int index, tSplineIk_EvalState *state) | tSplineIK_Tree *tree, Object *ob, bPoseChannel *pchan, int index, tSplineIk_EvalState *state) | ||||
| { | { | ||||
| bSplineIKConstraint *ikData = tree->ikData; | bSplineIKConstraint *ik_data = tree->ik_data; | ||||
| float origHead[3], origTail[3], poseHead[3], poseTail[3], basePoseMat[3][3], poseMat[3][3]; | |||||
| float splineVec[3], scaleFac, radius = 1.0f; | if (pchan->bone->length == 0.0f) { | ||||
| float tailBlendFac = 0.0f; | /* Only move the bone position with zero length bones. */ | ||||
| float bone_pos[4], dir[3], rad; | |||||
| BKE_where_on_path(ik_data->tar, state->curve_position, bone_pos, dir, NULL, &rad, NULL); | |||||
| apply_curve_transform(ik_data, ob, rad, bone_pos, &rad); | |||||
| copy_v3_v3(pchan->pose_mat[3], bone_pos); | |||||
| copy_v3_v3(pchan->pose_head, bone_pos); | |||||
| copy_v3_v3(pchan->pose_tail, bone_pos); | |||||
| pchan->flag |= POSE_DONE; | |||||
| return; | |||||
| } | |||||
| mul_v3_m4v3(poseHead, state->locrot_offset, pchan->pose_head); | float orig_head[3], orig_tail[3], pose_head[3], pose_tail[3]; | ||||
| mul_v3_m4v3(poseTail, state->locrot_offset, pchan->pose_tail); | float base_pose_mat[3][3], pose_mat[3][3]; | ||||
| float spline_vec[3], scale_fac, radius = 1.0f; | |||||
| float tail_blend_fac = 0.0f; | |||||
| copy_v3_v3(origHead, poseHead); | mul_v3_m4v3(pose_head, state->locrot_offset, pchan->pose_head); | ||||
| mul_v3_m4v3(pose_tail, state->locrot_offset, pchan->pose_tail); | |||||
| /* first, adjust the point positions on the curve */ | copy_v3_v3(orig_head, pose_head); | ||||
| /* First, adjust the point positions on the curve. */ | |||||
| float curveLen = tree->points[index] - tree->points[index + 1]; | float curveLen = tree->points[index] - tree->points[index + 1]; | ||||
| float pointStart = state->curve_position; | float bone_len = len_v3v3(pose_head, pose_tail); | ||||
| float poseScale = len_v3v3(poseHead, poseTail) / pchan->bone->length; | float point_start = state->curve_position; | ||||
| float baseScale = 1.0f; | float pose_scale = bone_len / pchan->bone->length; | ||||
| float base_scale = 1.0f; | |||||
| if (ikData->yScaleMode == CONSTRAINT_SPLINEIK_YS_ORIGINAL) { | if (ik_data->yScaleMode == CONSTRAINT_SPLINEIK_YS_ORIGINAL) { | ||||
| /* Carry over the bone Y scale to the curve range. */ | /* Carry over the bone Y scale to the curve range. */ | ||||
| baseScale = poseScale; | base_scale = pose_scale; | ||||
| } | } | ||||
| float pointEnd = pointStart + curveLen * baseScale * state->curve_scale; | float point_end = point_start + curveLen * base_scale * state->curve_scale; | ||||
| state->curve_position = pointEnd; | state->curve_position = point_end; | ||||
| /* step 1: determine the positions for the endpoints of the bone */ | /* Step 1: determine the positions for the endpoints of the bone. */ | ||||
| if (pointStart < 1.0f) { | if (point_start < 1.0f) { | ||||
| float vec[4], dir[3], rad; | float vec[4], dir[3], rad; | ||||
| radius = 0.0f; | |||||
| /* determine if the bone should still be affected by SplineIK */ | /* Calculate head position. */ | ||||
| if (pointEnd >= 1.0f) { | if (point_start == 0.0f) { | ||||
| /* blending factor depends on the amount of the bone still left on the chain */ | /* Start of the path. We have no previous tail position to copy. */ | ||||
| tailBlendFac = (1.0f - pointStart) / (pointEnd - pointStart); | BKE_where_on_path(ik_data->tar, point_start, vec, dir, NULL, &rad, NULL); | ||||
| } | } | ||||
| else { | else { | ||||
| tailBlendFac = 1.0f; | copy_v3_v3(vec, state->prev_tail_loc); | ||||
| rad = state->prev_tail_radius; | |||||
| } | } | ||||
| /* tail endpoint */ | radius = rad; | ||||
| if (where_on_path(ikData->tar, pointEnd, vec, dir, NULL, &rad, NULL)) { | copy_v3_v3(pose_head, vec); | ||||
| /* apply curve's object-mode transforms to the position | apply_curve_transform(ik_data, ob, rad, pose_head, &radius); | ||||
| * unless the option to allow curve to be positioned elsewhere is activated (i.e. no root) | |||||
| */ | |||||
| if ((ikData->flag & CONSTRAINT_SPLINEIK_NO_ROOT) == 0) { | |||||
| mul_m4_v3(ikData->tar->obmat, vec); | |||||
| } | |||||
| /* convert the position to pose-space, then store it */ | /* Calculate tail position. */ | ||||
| mul_m4_v3(ob->imat, vec); | if (ik_data->yScaleMode != CONSTRAINT_SPLINEIK_YS_FIT_CURVE) { | ||||
| copy_v3_v3(poseTail, vec); | float sphere_radius; | ||||
| /* set the new radius */ | if (ik_data->yScaleMode == CONSTRAINT_SPLINEIK_YS_ORIGINAL) { | ||||
| radius = rad; | sphere_radius = bone_len; | ||||
| } | } | ||||
| else { | |||||
| /* head endpoint */ | /* Don't take bone scale into account. */ | ||||
| if (where_on_path(ikData->tar, pointStart, vec, dir, NULL, &rad, NULL)) { | sphere_radius = pchan->bone->length; | ||||
| /* apply curve's object-mode transforms to the position | |||||
| * unless the option to allow curve to be positioned elsewhere is activated (i.e. no root) | |||||
| */ | |||||
| if ((ikData->flag & CONSTRAINT_SPLINEIK_NO_ROOT) == 0) { | |||||
| mul_m4_v3(ikData->tar->obmat, vec); | |||||
| } | } | ||||
| /* store the position, and convert it to pose space */ | /* Calculate the tail position with sphere curve intersection. */ | ||||
| mul_m4_v3(ob->imat, vec); | state->prev_tail_seg_idx = position_tail_on_spline( | ||||
| copy_v3_v3(poseHead, vec); | ik_data, vec, sphere_radius, state->prev_tail_seg_idx, pose_tail, &point_end, &rad); | ||||
| /* set the new radius (it should be the average value) */ | state->prev_tail_radius = rad; | ||||
| radius = (radius + rad) / 2; | copy_v3_v3(state->prev_tail_loc, pose_tail); | ||||
| apply_curve_transform(ik_data, ob, rad, pose_tail, &radius); | |||||
| state->curve_position = point_end; | |||||
| } | |||||
| else { | |||||
| /* Scale to fit curve end position. */ | |||||
| if (BKE_where_on_path(ik_data->tar, point_end, vec, dir, NULL, &rad, NULL)) { | |||||
| state->prev_tail_radius = rad; | |||||
| copy_v3_v3(state->prev_tail_loc, vec); | |||||
| copy_v3_v3(pose_tail, vec); | |||||
| apply_curve_transform(ik_data, ob, rad, pose_tail, &radius); | |||||
| } | |||||
| } | |||||
| /* Determine if the bone should still be affected by SplineIK. | |||||
| * This makes it so that the bone slowly becomes poseable again the further it rolls off the | |||||
| * curve. When the whole bone has rolled off the curve, the IK constraint will not influence it | |||||
| * anymore. | |||||
| */ | |||||
| if (point_end >= 1.0f) { | |||||
| /* Blending factor depends on the amount of the bone still left on the chain. */ | |||||
| tail_blend_fac = (1.0f - point_start) / (point_end - point_start); | |||||
| } | |||||
| else { | |||||
| tail_blend_fac = 1.0f; | |||||
| } | } | ||||
| } | } | ||||
| /* Step 2: determine the implied transform from these endpoints. | /* Step 2: determine the implied transform from these endpoints. | ||||
| * - splineVec: the vector direction that the spline applies on the bone. | * - 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. | * - scaleFac: the factor that the bone length is scaled by to get the desired amount. | ||||
| */ | */ | ||||
| sub_v3_v3v3(splineVec, poseTail, poseHead); | sub_v3_v3v3(spline_vec, pose_tail, pose_head); | ||||
| scaleFac = len_v3(splineVec) / pchan->bone->length; | scale_fac = len_v3(spline_vec) / pchan->bone->length; | ||||
| /* Extrapolate the full length of the bone as it rolls off the end of the curve. */ | |||||
| scaleFac = (tailBlendFac < 1e-5f) ? baseScale : scaleFac / tailBlendFac; | |||||
| /* Step 3: compute the shortest rotation needed | /* Step 3: compute the shortest rotation needed | ||||
| * to map from the bone rotation to the current axis. | * to map from the bone rotation to the current axis. | ||||
| * - this uses the same method as is used for the Damped Track Constraint | * - this uses the same method as is used for the Damped Track Constraint | ||||
| * (see the code there for details). | * (see the code there for details). | ||||
| */ | */ | ||||
| { | { | ||||
| float dmat[3][3], rmat[3][3]; | float dmat[3][3], rmat[3][3]; | ||||
| float raxis[3], rangle; | float raxis[3], rangle; | ||||
| /* compute the raw rotation matrix from the bone's current matrix by extracting only the | /* Compute the raw rotation matrix from the bone's current matrix by extracting only the | ||||
| * orientation-relevant axes, and normalizing them | * orientation-relevant axes, and normalizing them. | ||||
| */ | */ | ||||
| mul_m3_m4m4(basePoseMat, state->locrot_offset, pchan->pose_mat); | mul_m3_m4m4(base_pose_mat, state->locrot_offset, pchan->pose_mat); | ||||
| normalize_m3_m3(rmat, basePoseMat); | normalize_m3_m3(rmat, base_pose_mat); | ||||
| /* Also, normalize the orientation imposed by the bone, | /* Also, normalize the orientation imposed by the bone, | ||||
| * now that we've extracted the scale factor. */ | * now that we've extracted the scale factor. */ | ||||
| normalize_v3(splineVec); | normalize_v3(spline_vec); | ||||
| /* calculate smallest axis-angle rotation necessary for getting from the | /* Calculate smallest axis-angle rotation necessary for getting from the | ||||
| * current orientation of the bone, to the spline-imposed direction | * current orientation of the bone, to the spline-imposed direction. | ||||
| */ | */ | ||||
| cross_v3_v3v3(raxis, rmat[1], splineVec); | cross_v3_v3v3(raxis, rmat[1], spline_vec); | ||||
| rangle = dot_v3v3(rmat[1], splineVec); | rangle = dot_v3v3(rmat[1], spline_vec); | ||||
| CLAMP(rangle, -1.0f, 1.0f); | CLAMP(rangle, -1.0f, 1.0f); | ||||
| rangle = acosf(rangle); | rangle = acosf(rangle); | ||||
| /* multiply the magnitude of the angle by the influence of the constraint to | /* Multiply the magnitude of the angle by the influence of the constraint to | ||||
| * control the influence of the SplineIK effect | * control the influence of the SplineIK effect. | ||||
| */ | */ | ||||
| rangle *= tree->con->enforce * tailBlendFac; | rangle *= tree->con->enforce * tail_blend_fac; | ||||
| /* construct rotation matrix from the axis-angle rotation found above | /* Construct rotation matrix from the axis-angle rotation found above. | ||||
| * - this call takes care to make sure that the axis provided is a unit vector first | * - This call takes care to make sure that the axis provided is a unit vector first. | ||||
| */ | */ | ||||
| axis_angle_to_mat3(dmat, raxis, rangle); | axis_angle_to_mat3(dmat, raxis, rangle); | ||||
| /* Combine these rotations so that the y-axis of the bone is now aligned as the | /* Combine these rotations so that the y-axis of the bone is now aligned as the | ||||
| * spline dictates, while still maintaining roll control from the existing bone animation. */ | * spline dictates, while still maintaining roll control from the existing bone animation. */ | ||||
| mul_m3_m3m3(poseMat, dmat, rmat); | mul_m3_m3m3(pose_mat, dmat, rmat); | ||||
| /* attempt to reduce shearing, though I doubt this'll really help too much now... */ | /* Attempt to reduce shearing, though I doubt this'll really help too much now... */ | ||||
| normalize_m3(poseMat); | normalize_m3(pose_mat); | ||||
| mul_m3_m3m3(basePoseMat, dmat, basePoseMat); | mul_m3_m3m3(base_pose_mat, dmat, base_pose_mat); | ||||
| /* apply rotation to the accumulated parent transform */ | /* Apply rotation to the accumulated parent transform. */ | ||||
| mul_m4_m3m4(state->locrot_offset, dmat, state->locrot_offset); | mul_m4_m3m4(state->locrot_offset, dmat, state->locrot_offset); | ||||
| } | } | ||||
| /* step 4: set the scaling factors for the axes */ | /* Step 4: Set the scaling factors for the axes. */ | ||||
| /* Always multiply the y-axis by the scaling factor to get the correct length. */ | /* Always multiply the y-axis by the scaling factor to get the correct length. */ | ||||
| mul_v3_fl(poseMat[1], scaleFac); | mul_v3_fl(pose_mat[1], scale_fac); | ||||
| /* After that, apply x/z scaling modes. */ | /* After that, apply x/z scaling modes. */ | ||||
| if (ikData->xzScaleMode != CONSTRAINT_SPLINEIK_XZS_NONE) { | if (ik_data->xzScaleMode != CONSTRAINT_SPLINEIK_XZS_NONE) { | ||||
| /* First, apply the original scale if enabled. */ | /* First, apply the original scale if enabled. */ | ||||
| if (ikData->xzScaleMode == CONSTRAINT_SPLINEIK_XZS_ORIGINAL || | if (ik_data->xzScaleMode == CONSTRAINT_SPLINEIK_XZS_ORIGINAL || | ||||
| (ikData->flag & CONSTRAINT_SPLINEIK_USE_ORIGINAL_SCALE) != 0) { | (ik_data->flag & CONSTRAINT_SPLINEIK_USE_ORIGINAL_SCALE) != 0) { | ||||
| float scale; | float scale; | ||||
| /* x-axis scale */ | /* X-axis scale. */ | ||||
| scale = len_v3(pchan->pose_mat[0]); | scale = len_v3(pchan->pose_mat[0]); | ||||
| mul_v3_fl(poseMat[0], scale); | mul_v3_fl(pose_mat[0], scale); | ||||
| /* z-axis scale */ | /* Z-axis scale. */ | ||||
| scale = len_v3(pchan->pose_mat[2]); | scale = len_v3(pchan->pose_mat[2]); | ||||
| mul_v3_fl(poseMat[2], scale); | mul_v3_fl(pose_mat[2], scale); | ||||
| /* Adjust the scale factor used for volume preservation | /* Adjust the scale factor used for volume preservation | ||||
| * to consider the pre-IK scaling as the initial volume. */ | * to consider the pre-IK scaling as the initial volume. */ | ||||
| scaleFac /= poseScale; | scale_fac /= pose_scale; | ||||
| } | } | ||||
| /* Apply volume preservation. */ | /* Apply volume preservation. */ | ||||
| switch (ikData->xzScaleMode) { | switch (ik_data->xzScaleMode) { | ||||
| case CONSTRAINT_SPLINEIK_XZS_INVERSE: { | case CONSTRAINT_SPLINEIK_XZS_INVERSE: { | ||||
| /* old 'volume preservation' method using the inverse scale */ | /* Old 'volume preservation' method using the inverse scale. */ | ||||
| float scale; | float scale; | ||||
| /* calculate volume preservation factor which is | /* Calculate volume preservation factor which is | ||||
| * basically the inverse of the y-scaling factor | * basically the inverse of the y-scaling factor. | ||||
| */ | */ | ||||
| if (fabsf(scaleFac) != 0.0f) { | if (fabsf(scale_fac) != 0.0f) { | ||||
| scale = 1.0f / fabsf(scaleFac); | scale = 1.0f / fabsf(scale_fac); | ||||
| /* We need to clamp this within sensible values. */ | /* We need to clamp this within sensible values. */ | ||||
| /* NOTE: these should be fine for now, but should get sanitized in future. */ | /* NOTE: these should be fine for now, but should get sanitized in future. */ | ||||
| CLAMP(scale, 0.0001f, 100000.0f); | CLAMP(scale, 0.0001f, 100000.0f); | ||||
| } | } | ||||
| else { | else { | ||||
| scale = 1.0f; | scale = 1.0f; | ||||
| } | } | ||||
| /* apply the scaling */ | /* Apply the scaling. */ | ||||
| mul_v3_fl(poseMat[0], scale); | mul_v3_fl(pose_mat[0], scale); | ||||
| mul_v3_fl(poseMat[2], scale); | mul_v3_fl(pose_mat[2], scale); | ||||
| break; | break; | ||||
| } | } | ||||
| case CONSTRAINT_SPLINEIK_XZS_VOLUMETRIC: { | case CONSTRAINT_SPLINEIK_XZS_VOLUMETRIC: { | ||||
| /* improved volume preservation based on the Stretch To constraint */ | /* Improved volume preservation based on the Stretch To constraint. */ | ||||
| float final_scale; | float final_scale; | ||||
| /* as the basis for volume preservation, we use the inverse scale factor... */ | /* As the basis for volume preservation, we use the inverse scale factor... */ | ||||
| if (fabsf(scaleFac) != 0.0f) { | if (fabsf(scale_fac) != 0.0f) { | ||||
| /* NOTE: The method here is taken wholesale from the Stretch To constraint */ | /* NOTE: The method here is taken wholesale from the Stretch To constraint. */ | ||||
| float bulge = powf(1.0f / fabsf(scaleFac), ikData->bulge); | float bulge = powf(1.0f / fabsf(scale_fac), ik_data->bulge); | ||||
| if (bulge > 1.0f) { | if (bulge > 1.0f) { | ||||
| if (ikData->flag & CONSTRAINT_SPLINEIK_USE_BULGE_MAX) { | if (ik_data->flag & CONSTRAINT_SPLINEIK_USE_BULGE_MAX) { | ||||
| float bulge_max = max_ff(ikData->bulge_max, 1.0f); | float bulge_max = max_ff(ik_data->bulge_max, 1.0f); | ||||
| float hard = min_ff(bulge, bulge_max); | float hard = min_ff(bulge, bulge_max); | ||||
| float range = bulge_max - 1.0f; | float range = bulge_max - 1.0f; | ||||
| float scale = (range > 0.0f) ? 1.0f / range : 0.0f; | float scale = (range > 0.0f) ? 1.0f / range : 0.0f; | ||||
| float soft = 1.0f + range * atanf((bulge - 1.0f) * scale) / (float)M_PI_2; | float soft = 1.0f + range * atanf((bulge - 1.0f) * scale) / (float)M_PI_2; | ||||
| bulge = interpf(soft, hard, ikData->bulge_smooth); | bulge = interpf(soft, hard, ik_data->bulge_smooth); | ||||
| } | } | ||||
| } | } | ||||
| if (bulge < 1.0f) { | if (bulge < 1.0f) { | ||||
| if (ikData->flag & CONSTRAINT_SPLINEIK_USE_BULGE_MIN) { | if (ik_data->flag & CONSTRAINT_SPLINEIK_USE_BULGE_MIN) { | ||||
| float bulge_min = CLAMPIS(ikData->bulge_min, 0.0f, 1.0f); | float bulge_min = CLAMPIS(ik_data->bulge_min, 0.0f, 1.0f); | ||||
| float hard = max_ff(bulge, bulge_min); | float hard = max_ff(bulge, bulge_min); | ||||
| float range = 1.0f - bulge_min; | float range = 1.0f - bulge_min; | ||||
| float scale = (range > 0.0f) ? 1.0f / range : 0.0f; | float scale = (range > 0.0f) ? 1.0f / range : 0.0f; | ||||
| float soft = 1.0f - range * atanf((1.0f - bulge) * scale) / (float)M_PI_2; | float soft = 1.0f - range * atanf((1.0f - bulge) * scale) / (float)M_PI_2; | ||||
| bulge = interpf(soft, hard, ikData->bulge_smooth); | bulge = interpf(soft, hard, ik_data->bulge_smooth); | ||||
| } | } | ||||
| } | } | ||||
| /* compute scale factor for xz axes from this value */ | /* Compute scale factor for xz axes from this value. */ | ||||
| final_scale = sqrtf(bulge); | final_scale = sqrtf(bulge); | ||||
| } | } | ||||
| else { | else { | ||||
| /* no scaling, so scale factor is simple */ | /* No scaling, so scale factor is simple. */ | ||||
| final_scale = 1.0f; | final_scale = 1.0f; | ||||
| } | } | ||||
| /* Apply the scaling (assuming normalized scale). */ | /* Apply the scaling (assuming normalized scale). */ | ||||
| mul_v3_fl(poseMat[0], final_scale); | mul_v3_fl(pose_mat[0], final_scale); | ||||
| mul_v3_fl(poseMat[2], final_scale); | mul_v3_fl(pose_mat[2], final_scale); | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /* Finally, multiply the x and z scaling by the radius of the curve too, | /* Finally, multiply the x and z scaling by the radius of the curve too, | ||||
| * to allow automatic scales to get tweaked still. | * to allow automatic scales to get tweaked still. | ||||
| */ | */ | ||||
| if ((ikData->flag & CONSTRAINT_SPLINEIK_NO_CURVERAD) == 0) { | if ((ik_data->flag & CONSTRAINT_SPLINEIK_NO_CURVERAD) == 0) { | ||||
| mul_v3_fl(poseMat[0], radius); | mul_v3_fl(pose_mat[0], radius); | ||||
| mul_v3_fl(poseMat[2], radius); | mul_v3_fl(pose_mat[2], radius); | ||||
| } | } | ||||
| /* Blend the scaling of the matrix according to the influence. */ | /* Blend the scaling of the matrix according to the influence. */ | ||||
| sub_m3_m3m3(poseMat, poseMat, basePoseMat); | sub_m3_m3m3(pose_mat, pose_mat, base_pose_mat); | ||||
| madd_m3_m3m3fl(poseMat, basePoseMat, poseMat, tree->con->enforce * tailBlendFac); | madd_m3_m3m3fl(pose_mat, base_pose_mat, pose_mat, tree->con->enforce * tail_blend_fac); | ||||
| /* step 5: set the location of the bone in the matrix */ | /* Step 5: Set the location of the bone in the matrix. */ | ||||
| if (ikData->flag & CONSTRAINT_SPLINEIK_NO_ROOT) { | if (ik_data->flag & CONSTRAINT_SPLINEIK_NO_ROOT) { | ||||
| /* when the 'no-root' option is affected, the chain can retain | /* When the 'no-root' option is affected, the chain can retain | ||||
| * the shape but be moved elsewhere | * the shape but be moved elsewhere. | ||||
| */ | */ | ||||
| copy_v3_v3(poseHead, origHead); | copy_v3_v3(pose_head, orig_head); | ||||
| } | } | ||||
| else if (tree->con->enforce < 1.0f) { | else if (tree->con->enforce < 1.0f) { | ||||
| /* when the influence is too low | /* When the influence is too low: | ||||
| * - blend the positions for the 'root' bone | * - Blend the positions for the 'root' bone. | ||||
| * - stick to the parent for any other | * - Stick to the parent for any other. | ||||
| */ | */ | ||||
| if (index < tree->chainlen - 1) { | if (index < tree->chainlen - 1) { | ||||
| copy_v3_v3(poseHead, origHead); | copy_v3_v3(pose_head, orig_head); | ||||
| } | } | ||||
| else { | else { | ||||
| interp_v3_v3v3(poseHead, origHead, poseHead, tree->con->enforce); | interp_v3_v3v3(pose_head, orig_head, pose_head, tree->con->enforce); | ||||
| } | } | ||||
| } | } | ||||
| /* finally, store the new transform */ | /* Finally, store the new transform. */ | ||||
| copy_m4_m3(pchan->pose_mat, poseMat); | copy_m4_m3(pchan->pose_mat, pose_mat); | ||||
| copy_v3_v3(pchan->pose_mat[3], poseHead); | copy_v3_v3(pchan->pose_mat[3], pose_head); | ||||
| copy_v3_v3(pchan->pose_head, poseHead); | copy_v3_v3(pchan->pose_head, pose_head); | ||||
| mul_v3_mat3_m4v3(origTail, state->locrot_offset, pchan->pose_tail); | mul_v3_mat3_m4v3(orig_tail, state->locrot_offset, pchan->pose_tail); | ||||
| /* recalculate tail, as it's now outdated after the head gets adjusted above! */ | /* Recalculate tail, as it's now outdated after the head gets adjusted above! */ | ||||
| BKE_pose_where_is_bone_tail(pchan); | BKE_pose_where_is_bone_tail(pchan); | ||||
| /* update the offset in the accumulated parent transform */ | /* Update the offset in the accumulated parent transform. */ | ||||
| sub_v3_v3v3(state->locrot_offset[3], pchan->pose_tail, origTail); | sub_v3_v3v3(state->locrot_offset[3], pchan->pose_tail, orig_tail); | ||||
| /* done! */ | /* Done! */ | ||||
| pchan->flag |= POSE_DONE; | pchan->flag |= POSE_DONE; | ||||
| } | } | ||||
| /* Evaluate the chain starting from the nominated bone */ | /* Evaluate the chain starting from the nominated bone */ | ||||
| static void splineik_execute_tree( | static void splineik_execute_tree( | ||||
| struct Depsgraph *depsgraph, Scene *scene, Object *ob, bPoseChannel *pchan_root, float ctime) | struct Depsgraph *depsgraph, Scene *scene, Object *ob, bPoseChannel *pchan_root, float ctime) | ||||
| { | { | ||||
| tSplineIK_Tree *tree; | tSplineIK_Tree *tree; | ||||
| /* for each pose-tree, execute it if it is spline, otherwise just free it */ | /* for each pose-tree, execute it if it is spline, otherwise just free it */ | ||||
| while ((tree = pchan_root->siktree.first) != NULL) { | while ((tree = pchan_root->siktree.first) != NULL) { | ||||
| /* Firstly, calculate the bone matrix the standard way, | /* Firstly, calculate the bone matrix the standard way, | ||||
| * since this is needed for roll control. */ | * since this is needed for roll control. */ | ||||
| for (int i = tree->chainlen - 1; i >= 0; i--) { | for (int i = tree->chainlen - 1; i >= 0; i--) { | ||||
| BKE_pose_where_is_bone(depsgraph, scene, ob, tree->chain[i], ctime, 1); | BKE_pose_where_is_bone(depsgraph, scene, ob, tree->chain[i], ctime, 1); | ||||
| } | } | ||||
| /* After that, evaluate the actual Spline IK, unless there are missing dependencies. */ | /* After that, evaluate the actual Spline IK, unless there are missing dependencies. */ | ||||
| tSplineIk_EvalState state; | tSplineIk_EvalState state; | ||||
| if (splineik_evaluate_init(tree, &state)) { | if (splineik_evaluate_init(tree, &state)) { | ||||
| /* Walk over each bone in the chain, calculating the effects of spline IK | /* 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) | * - the chain is traversed in the opposite order to storage order | ||||
| * so that dependencies are correct | * (i.e. parent to children) so that dependencies are correct | ||||
| */ | */ | ||||
| for (int i = tree->chainlen - 1; i >= 0; i--) { | for (int i = tree->chainlen - 1; i >= 0; i--) { | ||||
| bPoseChannel *pchan = tree->chain[i]; | bPoseChannel *pchan = tree->chain[i]; | ||||
| splineik_evaluate_bone(tree, ob, pchan, i, &state); | splineik_evaluate_bone(tree, ob, pchan, i, &state); | ||||
| } | } | ||||
| } | } | ||||
| /* free the tree info specific to SplineIK trees now */ | /* free the tree info specific to SplineIK trees now */ | ||||
| ▲ Show 20 Lines • Show All 344 Lines • Show Last 20 Lines | |||||