Community
Maya Animation and Rigging
Welcome to Autodesk’s Maya Forums. Share your knowledge, ask questions, and explore popular Maya animation topics.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

How to drive a joint's scale with the distance between two adjacent joints?

3 REPLIES 3
Reply
Message 1 of 4
Anonymous
761 Views, 3 Replies

How to drive a joint's scale with the distance between two adjacent joints?

I have a grid of joints. Each joint's orientation is driven by the positions of immediately-adjacent joints, so that they stay oriented in a reasonable direction as the adjacent joints move around. I got the behavior driving their positions and orientations working the way I want, but I'm not sure what the best way to drive the scale would be. I'd like the z scale of the joint to be the distance between the joint to its immediate left and the joint to its immediate right, divided by that same distance when the joints are in their resting positions. I want the x scale of the joint to be the same thing, except with respect to the joints below and above instead of to the left and right. Joints on the left and right edges don't scale in z, and joints on the top and bottom edges don't scale in x. Is there a simple way to do this? 

For example, say i have a 4x4 grid (the real one I'm using is 12x20, but that would make the example harder to follow), the distance between all adjacent joints is 1, and each joint is named jnt_[X POSITION OF JOINT]_[Z POSITION OF JOINT]. The grid would look like this:

[jnt_3_0] [jnt_3_1] [jnt_3_2] [jnt_3_3]
[jnt_2_0] [jnt_2_1] [jnt_2_2] [jnt_2_3] [jnt_1_0] [jnt_1_1] [jnt_1_2] [jnt_1_3] [jnt_0_0] [jnt_0_1] [jnt_0_2] [jnt_0_3]

So, in this example, I'd like the z scale of jnt_1_1 to be (current distance between jnt_1_0 and jnt_1_2) / (resting distance between jnt_1_0 and jnt_1_2, which is 2.0 in this case). Similarly, the x scale for jnt_1_1 should be (current distance between jnt_0_1 and jnt_2_1) / (resting distance between jnt_0_1 and jnt_2_1, which is also 2.0 in this case).

I know I can do it similarly to the way I do stretchy spline IKs. I'd make a bunch of linear curves with two CVs each: one on a joint, and the other on the joint two to its right. Make each CV a cluster, constrain the clusters to the joints they're sitting on, make an arc length node for every curve, plug the arc lengths into a divide node dividing by their resting length, pipe the output of each into the scale z of the joint in the middle of the line. Then do it again from each joint to the joint two up from it, piping the result of that into the scale x of the joint between them. That just seems like a lot of work and overhead for something that can probably be done much more simply.

3 REPLIES 3
Message 2 of 4
osidedan
in reply to: Anonymous

as an overall approach, I would recommend using an expression for something like this. This is an expression that would do what you want based on your  "jnt_1_1" example:

//get distance between left and right neighbors
float $dist_l_to_r = getDistance(`xform -q -ws -t jnt_1_0`, `xform -q -ws -t jnt_1_2`);
//get distance between top and bottom neighbors float $dist_t_to_b = getDistance(`xform -q -ws -t jnt_2_1`, `xform -q -ws -t jnt_0_1`);
//create the actual expressions jnt_1_1.scaleZ = $dist_l_to_r/2.0; jnt_1_1.scaleX = $dist_t_to_b/2.0;
//this procedure is used to get the distance, and called from above proc float getDistance(float $p1[] ,float $p2[]) { return sqrt( (($p2[0] - $p1[0])*($p2[0] - $p1[0])) + (($p2[1] - $p1[1])*($p2[1] - $p1[1])) + (($p2[2] - $p1[2])*($p2[2] - $p1[2]))); }

 

 

However, that's only for 1 joint. If you're planning on having a 12x20 grid, that is A LOT of expressions to create and write. To speed things up,  a script can create the expressions for you. 

 

Soooo, what I did here is wrote up a python script to automate the creation of the expressions for each joint. Each joint is going to get it's own custom expression like the one above. Before anything though, make sure to run this next script stuff on a COPY of your file first. I don't want to accidentally mess something up haha.

 

Of note, this script assumes you have your naming convention like you do in your example, as in "jnt_column#_row#", starting with "jnt_0_0". if not, it will fail.

If that is all set though, then you can run the following script in a python script editor. Nothing will happen when you run it, this is just loading the script into memory:

 

 

# this script creates expressions for joint scale to be driven by distances between neighboring joints
# formatting for joints should be "jnt_" + row_number + "_" column_number
import maya.cmds as cmds import math def drive_scale_from_neighbors(number_of_rows, number_of_columns): current_row = 1 current_column = 1 while current_row < (number_of_rows - 1): while current_column < (number_of_columns - 1): expression_name = 'jnt_' + str(current_row) + '_' + str(current_column) + '_expression' if not cmds.objExists(expression_name): cmds.expression(name=expression_name) l_to_r_rest_dist = distance_between_joints('jnt_' + str(current_row) + '_' + str(current_column - 1),'jnt_' + str(current_row) + '_' + str(current_column + 1)) t_to_b_rest_dist = distance_between_joints('jnt_' + str(current_row + 1) + '_' + str(current_column),'jnt_' + str(current_row -1) + '_' + str(current_column)) write_scale_expression(expression_name, current_row, current_column, l_to_r_rest_dist, t_to_b_rest_dist) current_column += 1 current_row += 1 current_column = 1; def write_scale_expression(expression_name, row, column, l_to_r_rest_dist, t_to_b_rest_dist): cmds.expression(expression_name,edit=True, string = 'float $dist_l_to_r = getDistance(`xform -q -ws -t jnt_' + str(row) +'_' + str(column-1) + '`, `xform -q -ws -t jnt_' + str(row) +'_' + str(column+1) +'`);\n' + 'float $dist_t_to_b = getDistance(`xform -q -ws -t jnt_' + str(row+1) +'_' + str(column) + '`, `xform -q -ws -t jnt_' + str(row-1) +'_' + str(column) +'`);\n' + 'jnt_' + str(row) + '_' + str(column) + '.scaleZ = $dist_l_to_r/' + str(l_to_r_rest_dist) + ';\n' + 'jnt_' + str(row) + '_' + str(column) + '.scaleX = $dist_t_to_b/' + str(t_to_b_rest_dist) + ';\n\n' + 'proc float getDistance(float $p1[] ,float $p2[])\n' + '{\n' + 'return sqrt( (($p2[0] - $p1[0])*($p2[0] - $p1[0])) + (($p2[1] - $p1[1])*($p2[1] - $p1[1])) + (($p2[2] - $p1[2])*($p2[2] - $p1[2])));\n' '}' ) def distance_between_joints(j1,j2): p1 = cmds.xform(j1,query=True,translation=True, worldSpace=True) p2 = cmds.xform(j2,query=True,translation=True, worldSpace=True) return distance_between_two_points(p1,p2) def distance_between_two_points(p1, p2): return math.sqrt( ((p2[0] - p1[0])*(p2[0] - p1[0])) + ((p2[1] - p1[1])*(p2[1] - p1[1])) + ((p2[2] - p1[2])*(p2[2] - p1[2])))

 

Once you have run that, all you need to do is run this next bit to create all the expressions. However, make sure to change the number of rows and columns in to match the number of rows and columns you have. the "(4,4)" matches your example, but if you have 12 rows and 20 columns for the final, then change the "(4,4)" to "(12,20)"

drive_scale_from_neighbors(4,4)

Once you've run that, it should all be set! The script automatically finds the initial distance as well, so if you change the initial spacing you can just re-run the script. 

 

let me know if you need more info on anything. 

 

Quick side note: Maya expressions are finicky on when they decide to update. If you don't see changes updating in your viewport when you move a joint or whatever, just drag the time slider a bit. These expressions will update with any time change. 

 

Message 3 of 4
Anonymous
in reply to: osidedan

First of all, thank you very much for the in-depth answer! This ALMOST does exactly what I want, but with one small caveat that I should have made more explicit in my question. My grid of joints DOES have explicit rows and columns and one joint in each combination thereof, but the rows and columns are curved, the resting shape isn't planar, and the spaces between each of the joints in the resting position are not uniform. Your script divides all of the current distances by 2, because the resting distance was always 2 in my example, but what I actually need to do is somehow save out the distance between the joints in their resting position and keep dividing the current distance by that. Would I have to add some kind of attribute to the joints or something to make that work? I'm not sure how to do that.

Message 4 of 4
osidedan
in reply to: Anonymous

You are very welcome! as far as the dividing by two part, I can elaborate. That is just an example of an expression that will be created when the pyhton script is ran. In that example, I had run the script with the joints on a 1x1 grid. you can have the joints in all kinds of wacky locations, then run the python script. It will generate correct starting rest distances for you! 

For example. Here I moved the neighbor joints for "jnt_1_1" into random locations:

joint_neighbors_rand_pos.png

 

Then I ran the python script (which automatically updates the expressions for all joints). and here is the resulting division part of the expression that was created/updated for jnt_1_1 in this case:

 

jnt_1_1.scaleZ = $dist_l_to_r/4.816;
jnt_1_1.scaleX = $dist_t_to_b/6.342;

Hope that helps clear things up, and let me know if you need more info or if it doesn't seem to be working as described. Thanks!

 

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Technology Administrators


Autodesk Design & Make Report