## MEL How-To #105 | ||||||

| ||||||

## How do I align an object to a curve (without using a constraint)?## Orthogonal Basis Vectors(Wait! Don't run away frightened at the prospect of calculus derivatives The key to aligning an object to a curve is generating the orthogonal axes to which the object will be aligned. That's not as nearly as difficult as it sounds. One of the wonderful properties of matrix mathematics is that the rows of a 3x3 rotation matrix are in fact the orthogonal basis vectors for the space described by the matrix. In this How-To we apply this in reverse - calculating the basis vectors and then using them to populate a rotation matrix. (This would be a good opportunity to the plug the matrixPlayground MEL script.) ## TangentThe first axis of interest is the
Hmm.. perhaps an illustration would be better. The tangent for a curve is easily queried with the "pointOnCurve" command: float $tangent[3] = `pointOnCurve -ch off -parameter 0.0 -tangent curve1`; // Result: 3.359381 1.5 -11.471752 // ## NormalThe other two vectors are the The normal can also be queried with the "pointOnCurve" command: float $normal[3] = `pointOnCurve -ch off -parameter 0.0 -normal curve1`; // Result: -1.706138 48.378846 5.826198 // Maya also promises a "normalized" query for this vector (that is, returned as a unit vector). However, this is unfortunately not always the case. float $normal[3] = `pointOnCurve -ch off -parameter 0.0 -normalizedNormal curve1`; // Result: -0.000349918 0.00992218 0.00119492 // Do not rely on Maya's normalized result - always normalize them yourself. ## BinormalThe vector $tan = `pointOnCurve -ch off -parameter 0.0 -tangent curve1`; // Result: <<3.359381, 1.5, -11.471752>> // vector $norm = `pointOnCurve -ch off -parameter 0.0 -normal curve1`; // Result: <<-1.706138, 48.378846, 5.826198>> // // Normalize the vectors queried from "pointOnCurve". // $tan = `unit $tan`; // Result: <<0.27885, 0.12451, -0.952229>> // $norm = `unit $norm`; // Result: <<-0.0349918, 0.992218, 0.119492>> // // Calculate the binormal. // vector $bi = `cross $tan $norm`; // Result: <<0.959697, 0.0, 0.281037>> // ## Five Easy StepsWith the math in hand, let's put it to use. In five easy steps, this is how we'll perform the alignment: - Query the position of the parameter point along the curve.
- Query the tangent and normal for the parameter point.
- Calculate the orthogonal binormal to the tangent and normal.
- Assemble these into a transformation matrix.
- Assign this matrix to the object.
First, let's make a curve to demonstrate: global proc string be_make_curve() { string $curve = `curve -d 3 -p -72.883868 0.0 -12.81907 -p -50.487995 10.0 -89.297418 -p -5.696248 50.0 -242.254114 -p 15.842993 25.0 26.335076 -p 33.010327 50.0 136.913809 -p 112.657308 10.0 71.004563 -p 140.283868 -25.0 -34.099569 -p 154.097148 50.0 -86.651634 -k 0 -k 0 -k 0 -k 1 -k 2 -k 3 -k 4 -k 5 -k 5 -k 5`; return $curve; } // Make the curve. // string $curve = be_make_curve(); Now plot a bunch of locators along the curve. The Z axes for these locators will be aligned to the curve's tangent, and their X axes will be aligned to the curve's normal, as returned by the "pointOnCurve" command. global proc be_plot_locators( string $curve ) { float $p[3]; float $t[3]; float $n[3]; vector $tan; vector $norm; vector $bi; string $locator[]; matrix $m[4][4] = << 1.0, 0.0, 0.0, 0.0; 0.0, 1.0, 0.0, 0.0; 0.0, 0.0, 1.0, 0.0; 0.0, 0.0, 0.0, 1.0 >>; float $u; float $span = 0.1; float $maxU = `getAttr ( $curve + ".maxValue" )`; for ( $u = 0.0; $u <= $maxU; $u += $span ) { // Query the position, tangent and normal. // $p = `pointOnCurve -ch off -pr $u -p $curve`; $t = `pointOnCurve -ch off -pr $u -nt $curve`; $n = `pointOnCurve -ch off -pr $u -nn $curve`; // Translational coordinates in a Maya matrix are always represented // in Maya's internal units. Convert position to (cm) units. // See MEL How-To #102. // $p[0] = linearToInternal( $p[0] ); $p[1] = linearToInternal( $p[1] ); $p[2] = linearToInternal( $p[2] ); // Maya promises normalized tangent and normal, // but they really aren't. // $tan = `unit << $t[0], $t[1], $t[2] >>`; $norm = `unit << $n[0], $n[1], $n[2] >>`; // Calculate the binormal. // $bi = `cross << ($tan.x), ($tan.y), ($tan.z) >> << ($norm.x), ($norm.y), ($norm.z) >>`; // Normalize our vector. // $bi = `unit $bi`; // Create a matrix, using normal for the X axis and // tangent for the Z axis. // $m = << ($norm.x), ($norm.y), ($norm.z), 0.0; // X axis ($bi.x), ($bi.y), ($bi.z), 0.0; // Y axis ($tan.x), ($tan.y), ($tan.z), 0.0; // Z axis $p[0], $p[1], $p[2], 1.0 >>; // Position // Create a locator and assign its world-space matrix. // $locator = `spaceLocator`; xform -ws -m ($m[0][0]) ($m[0][1]) ($m[0][2]) ($m[0][3]) ($m[1][0]) ($m[1][1]) ($m[1][2]) ($m[1][3]) ($m[2][0]) ($m[2][1]) ($m[2][2]) ($m[2][3]) ($m[3][0]) ($m[3][1]) ($m[3][2]) ($m[3][3]) $locator[0]; } } // Plot some locators. // be_plot_locators( $curve ); ## Preventing "Roll"When using the tangents and normals returned from the "pointOnCurve" command, you may find that the normals "roll" around the curve. The illustration below shows the orientation for some of the locators plotted from the example above. It is common that this is not the desired behavior. What is necessary, then, is to calculate a normal which better satisfies the desired orientation, rather than simply querying it from the curve. Consider how Maya offers several "up vector" options when animating an object along a motion path - these options constrain the solution in order to produce a more consistent behavior. We'll employ the same technique here. A popular method is to use the world up-axis as a basis for the desired alignment. To calculate the desired normal you'll need to resort to some trigonometry - specifically, the use of the cross product operator. This allows us to calculate the orthogonal basis - the tangent, normal and binormal - for any point along the curve. Maya will provide the tangent, and we'll figure out the other two. Below is a variation for plotting the locators. This one allows you to specify an up vector. As before, the locators' Z axes will be aligned to the tangent of the curve. This time, however, the Y axis (the binormal) will favour the $upVector, and the X axis (the normal) will be calculated accordingly. global proc be_plot_no_roll_locators( string $curve, vector $upVector ) { // Ensure a valid up-vector. Use Y-up as default if input is bogus. // if ( `mag $upVector` < 0.001 ) $upVector = << 0.0, 1.0, 0.0 >>; // Ensure up-vector is normalized. // $upVector = `unit $upVector`; float $p[3]; float $t[3]; vector $norm; vector $tan; vector $bi; string $locator[]; matrix $m[4][4] = << 1.0, 0.0, 0.0, 0.0; 0.0, 1.0, 0.0, 0.0; 0.0, 0.0, 1.0, 0.0; 0.0, 0.0, 0.0, 1.0 >>; float $u; float $span = 0.1; float $maxU = `getAttr ( $curve + ".maxValue" )`; for ( $u = 0.0; $u <= $maxU; $u += $span ) { // Query the position and tangent. // $p = `pointOnCurve -ch off -pr $u -p $curve`; $t = `pointOnCurve -ch off -pr $u -nt $curve`; // Translational coordinates in a Maya matrix are always represented // in Maya's internal units. Convert position to (cm) units. // See MEL How-To #102. // $p[0] = linearToInternal( $p[0] ); $p[1] = linearToInternal( $p[1] ); $p[2] = linearToInternal( $p[2] ); // Maya promises normalized tangent, // but it really isn't. // $tan = `unit << $t[0], $t[1], $t[2] >>`; // Calculate a normal using the Y-up vector as the binormal. // $norm = `cross $tan $upVector`; // Calculate the orthogonal binormal. // $bi = `cross $tan $norm`; // If the binormal is pointing the wrong way, // negate it and the normal. // if ( `dot $upVector $bi` < 0.0 ) { $bi = -$bi; $norm = -$norm; } // Normalize our vectors. // $norm = `unit $norm`; $bi = `unit $bi`; // Create a matrix, using normal for the X axis and // tangent for the Z axis. $m = << ($norm.x), ($norm.y), ($norm.z), 0.0; // X axis ($bi.x), ($bi.y), ($bi.z), 0.0; // Y axis ($tan.x), ($tan.y), ($tan.z), 0.0; // Z axis $p[0], $p[1], $p[2], 1.0 >>; // Position // Create a locator and assign its world-space matrix. // $locator = `spaceLocator`; xform -ws -m ($m[0][0]) ($m[0][1]) ($m[0][2]) ($m[0][3]) ($m[1][0]) ($m[1][1]) ($m[1][2]) ($m[1][3]) ($m[2][0]) ($m[2][1]) ($m[2][2]) ($m[2][3]) ($m[3][0]) ($m[3][1]) ($m[3][2]) ($m[3][3]) $locator[0]; } } // You may want to delete all of the previous locators here. // Specify an up vector. // vector $upVector = << 0.0, 1.0, 0.0 >>; // Plot new locators, using the "no roll" method. // be_plot_no_roll_locators( $curve, $upVector ); And, voila. No roll. ## LimitationTry it using the X axis as the up vector: vector $upVector = << 1.0, 0.0, 0.0 >>; be_plot_no_roll_locators( $curve, $upVector ); Here you're going to see a roll again. You'll note that the roll occurs at the two points where the curve's tangent is aligned along the X axis - the same as the up-vector. This introduces a problem when calculating the binormal, as you cannot generate an orthogonal basis from two equal vectors. You would need to work around this by either carefully selecting your up-vector, or by adjusting the up-vector slightly when approaching this situation. ## Acknowledgements**Terathon Software**. Their article on Computing tangent space basis vectors for an arbitrary mesh enlightened me as to the correct usage of "binormal" vs. "bitangent"
## Related How-To's19 Feb 2005 | ||||||

Copyright ©2006 by Bryan Ewert, maya@ewertb.comMaya is a Registered Trademark of Autodesk (formerly Alias) |