My Magical Mathematical Journey Part 3
Excellent. Smooth stretch was solved. Another day, another math problem solved. I felt like a crime fighter who just beat this giant math monster back. Or was the math monster really solved? Wait, wait wait. Hold on a minute here! The whole reason of redoing this thing was that smooth stretch 2.0 didn’t work well in an IK/FK switch! If this new formula can’t help me solve that, then it’s no better than the old one. Oh crap!
So I continued to stare at my math chalkboard.
Okay. I needed to lay out the basics. Going from IK to FK would be simple. Just grab the current scale of the joints, and apply that to my FK chain. Going from FK to IK is all that I needed to concentrate on. So, what did I need to solve for? The IK Scale variable is what I put into the smooth stretch equation to find my final scale. So presumably all I needed to do was put in the final scale I had from the FK chain and get out the IK Scale.
Wait. That won’t work. The IK Scale is the current scale of the IK chain. It’s based on the distance to the IK handle, and that’s not going to change in either FK or IK. I needed another variable.
Luckily, I knew what I wanted that variable to be. The length variable. The variable that the animators would use to change the total length of the arm. After a little trial and error I came up with this equation to add length to the smooth stretch equation:
This is where I started to really hit the limits of my math knowledge. Length is being multiplied inside the smooth stretch function as well as outside. Even if I did know the inverse of my smooth stretch function, how would I use that to solve for length?
Well, there was nothing else to do but just try and see if something worked. I busted out wolphram alpha. Then I learned how to use wolphram alpha to solve the inverse of an equation. I solved for the inverse of the regular smooth stretch curve. I still didn’t know how to use it. I tried solving for the inverse of the entire smooth stretch function including the pythagorean theorem. I still didn’t know how to use it.
I went back to staring at my math chalkboard.
I like to think that staring at my math chalkboard makes it sound like I’m some sort of math genius. Unfortunately I’m not. And staring at my math chalkboard usually means putting my head into my hands and letting the feeling of despair sink in. Then it’s a matter of waiting to see which will win. Inspiration or despair.
I tried adding the IK Scale variable to my original smooth stretch curve and graphing it. Maybe seeing what was going on visually would help. That was much better. I immediately saw that the length variable just projected my unit curve out from the origin. It merely scaled the smooth stretch curve.
Inspiration struck. The smooth stretch curve could pass through any point in the first quadrant of the graph by changing the length variable. I wanted the smooth stretch curve to pass through the point that was the representation of the “elbow” in our IK chain. A vector starting at the origin with an infinite length pointing at the elbow on the graph will always pass through the unit smooth stretch curve. Therefore the length variable is literally the ratio of the distance to the elbow along that vector divided by the distance to the unit circle along that vector. All I needed to do was solve those two lengths.
Skipping past some wolphram alpha wizardry and a bit of trial and error I ended up with the following graph which solves smooth stretch for the length variable given the IK Scale variable and an arbitrary point in the first quadrant (a,b). Keep in mind that the graph only works in the falloff section of the smooth stretch equation due to the limitations of the graphing software. However, solving for any point which is not in the falloff section is much easier since the distance to the unit circle will always be 1, so the length variable will just be the distance to the elbow point.
Into the python code!
import maya.OpenMaya as om
from math import sqrt, sin, cos
def smooth_stretch_length(joint_positions, original_lengthA, falloff_scale = 1.73):
# Assumes that there are at least 3 ordered joints
# To solve length:
# Find the length of the vector from the origin to the
# joint_positions[1] x, y value (solved on the plane formed by joint_positions). (scalePoint)
# Find the length of that vector where it intersects with smooth stretch circle curve at a length of 1. (unit_curve)
# The difference is the length!
# Find preliminary triangle stuff:
# Assume jointPosition is a lists of lists. Convert to MVectors
vectors = [om.MVector(*pos) for pos in joint_positions]
# Find vectors AB, AC
unit_vectors = [vectors[1] - vectors[0]]
unit_vectors.append(vectors[-1] - vectors[0])
# Find the angle formed where joint_positions[0] is the vertex (in radians).
angle = unit_vectors[0].angle(unit_vectors[-1])
# Solve triangle (scalePoint)
scaleH = unit_vectors[0].length() / original_lengthA
scaleX = scaleH * cos(angle)
scaleY = scaleH * sin(angle)
# Now start to solve for length
# Find radius
radius = 0.5 * (falloff_scale ** 2.0 - 1.0)
if scaleY >= ( radius / falloff_scale ) * scaleX:
# The vector to scale point (the elbow) is above the tangentPoint.
# It is therefore going through a unit circle and would be divided by 1
return scaleH
if round(angle, 2) == 0.0 and round(scaleH, 4.0) >= round(scaleX, 4):
return 1.0
# Solve the intersection of the scaleVector with the unit_curve
# That is, the solution to (scaleY / scaleX) * currentScale == falloffCircle
# Hold on to your panty-hoes kids, we're in for some chop.
unit_curveX = (scaleX ** 2.0 * falloff_scale - sqrt(2.0 * scaleX ** 3.0 * scaleY * falloff_scale * radius -
scaleX ** 2.0 * scaleY ** 2.0 * falloff_scale ** 2.0 + scaleX ** 2.0 * scaleY ** 2.0 * radius ** 2.0) +
scaleX * scaleY * radius) / (scaleX ** 2.0 + scaleY ** 2.0)
unit_curveY = radius - radius * sqrt(1.0 - ((falloff_scale - unit_curveX) / radius) ** 2.0)
unit_curveH = sqrt(unit_curveX ** 2.0 + unit_curveY ** 2.0)
# Length is the distance to the scale point divided by the distance to the unit curve
length = scaleH / unit_curveH
return length
And that’s a wrap. The math monster was defeated for real.