一半君的总结纸

听话只听一半君

#105 如何让物体对齐曲线(不使用约束)?

Orthogonal Basis Vectors

(等等! 别被微积分的术语吓跑了 a la Eric W. Weisstein’s MathWorld. 我保证没那么难.)(lz表示lz不会数学)

物体对齐到曲线的关键是求得一个轴向,物体将沿这个轴向被对齐. 这其实没有听起来那么难(lz完全不懂). 3×3 rotation矩阵的一个特性是,他的行恰好是他描述的空间的 Orthogonal basis vectors. 我们在这里反响利用这一点 – 首先计算得到 basis vectors ,然后使用他们得到 rotation matrix.

(这里正好可以介绍下 matrixPlayground MEL script.)

Tangent

第一个需要知道的轴向是曲线的 tangent. Webster的 “tangent” 定义是:

“A line that is tangent; specifically : a straight line that is the limiting position of a secant of a curve through a fixed point and a variable point on the curve as the variable point approaches the fixed point.”

Hmm.. 也许一个插图会更直观.

Tangent
Tangent

曲线的 tangent 值可由”pointOnCurve” command 求得:

float $tangent[3] = `pointOnCurve -ch off -parameter 0.0 -tangent curve1`;
// Result: 3.359381 1.5 -11.471752 //

Normal

另外两个 vectors 是 normalbinormal. 这两个vectors位于 tangent的right-angles方向, 他们自身之间也是位于对方的right-angles方向, 形成了我们对齐所需要的 orthogonal basis vectors .

normal 可以用 “pointOnCurve” command得到:

float $normal[3] = `pointOnCurve -ch off -parameter 0.0 -normal curve1`;
// Result: -1.706138 48.378846 5.826198 //

Maya 返回的是 “normalized” 的 vector 值( 意思是说返回的是 unit vector). 但有时候并不是这样.

float $normal[3] = `pointOnCurve -ch off -parameter 0.0 -normalizedNormal curve1`;
// Result: -0.000349918 0.00992218 0.00119492 //

不要依靠 Maya的 normalized 的返回值 – 一定要自己去 normalize .

Binormal

binormal 用 “pointOnCurve”查询不到; 但是, 你在拿到 tangentnormal 之后,只要使用 cross product 就可以得到 – 使用 “cross” command – 可以得到 binormal. 注意你必须以vector的形式来表示 tangentnormal 才能使用 “cross” command.

Maya 6 开始新加入了自动转换 (float[3]) array 和 (vector)变量的功能, 所以你只需要把 “pointOnCurve” 的返回值赋予一个 (vector) variable即可.

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 "pointOnCurve"的返回值.
//
$tan = `unit $tan`;
// Result: <<0.27885, 0.12451, -0.952229>>  //
$norm = `unit $norm`;
// Result: <<-0.0349918, 0.992218, 0.119492>>  //

// 计算 binormal.
//
vector $bi = `cross $tan $norm`;
// Result: <<0.959697, 0.0, 0.281037>> //

Five Easy Steps

有了上面的数学知识, 我们把他付诸应用. 只需要5步, 下面是我们如何对齐:

  1. 查询curve上给定点的位置.
  2. 查询该点的tangent 和 normal.
  3. 计算tangent 和 normal 对应的 orthogonal binormal .
  4. 把上述结果拼成一个 transformation matrix.
  5. 把这个 matrix 赋予 object.

首先, 我们来创建一条曲线:

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();

现在沿曲线创建一系列locator. 这些locators 的 Z axes 将会和曲线的 tangent对齐, 他们的 X axes 将会和曲线的 normal对齐, normal是通过 “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 )
  {
    // 查询 position, tangent 和 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`;

    // Maya matrix里的Translation 坐标总是用Maya的 internal units表示的
    // 把 position 转换成 (cm) units.
    // See <a href="http://ewertb.soundlinker.com/mel/mel.102.php">MEL How-To #102</a>.
    //
    $p[0] = <a href="http://ewertb.soundlinker.com/mel/mel.102.php">linearToInternal</a>( $p[0] );
    $p[1] = <a href="http://ewertb.soundlinker.com/mel/mel.102.php">linearToInternal</a>( $p[1] );
    $p[2] = <a href="http://ewertb.soundlinker.com/mel/mel.102.php">linearToInternal</a>( $p[2] );

    // Maya 本应返回 normalized 的 tangent 和 normal,
    // 但经常并非如此.
    //
    $tan  = `unit << $t[0], $t[1], $t[2] >>`;
    $norm = `unit << $n[0], $n[1], $n[2] >>`;

    // 计算 binormal.
    //
    $bi = `cross << ($tan.x),  ($tan.y),  ($tan.z)  >>
                 << ($norm.x), ($norm.y), ($norm.z) >>`;

    // Normalize 得到的 vector.
    //
    $bi = `unit $bi`;

    // 创建一个 matrix, 使用 normal 作为 X axis
    // tangent 作为 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

    // 创建一个 locator 并赋予他 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];
  }
}

// 生成更多的 locators.
//
be_plot_locators( $curve );

Preventing “Roll”

当使用从 “pointOnCurve” command 返回的 tangents 和 normals的时候, 你可能会发现normals 沿着曲线”翻转” 了. 下图显示了用上面的方法创建的某些”翻转”了得locator.

Rolling normals
Rolling normals

通常情况下这不是我们想要的. 我们想要的是计算出一个能更好的匹配朝向的normal, 而不是简单的从曲线查询得到normal. Maya的motion path功能的创建选项里有好几个 “up vector” 的选项 – 这些选项约束着最终旋转的朝向,使结果更加和预想一致. 我们在这里使用同样的方法.

一个常用的方法是使用 world up-axis 作为最终对齐的basis. 为了计算得到需要的 normal 这里需要使用一些三角函数的数学 – 使用 cross product . 这样可以算出曲线上任意一点的 orthogonal basis – tangent, normal 和 binormal. Maya 会提供 tangent, 我们需要算出另外两个值.

下面是另外一种生成 locators 的方法. 这种方法可以让你指定一个 up vector. 像之前一样, locators的 Z axes 和曲线的tangent对齐. 不同之处是这次 Y axis (binormal) 会优先选择 $upVector, X axis (normal) 使用同样的方式计算.

global proc be_plot_no_roll_locators( string $curve, vector $upVector )
{
  // 确保有个有效的 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`;

    // Maya matrix 里的 Translational coordinates 始终是使用
    // Maya的 internal units表示的. 下面把 position 转换成 (cm) units.
    // See <a href="http://ewertb.soundlinker.com/mel/mel.102.php">MEL How-To #102</a>.
    //
    $p[0] = <a href="http://ewertb.soundlinker.com/mel/mel.102.php">linearToInternal</a>( $p[0] );
    $p[1] = <a href="http://ewertb.soundlinker.com/mel/mel.102.php">linearToInternal</a>( $p[1] );
    $p[2] = <a href="http://ewertb.soundlinker.com/mel/mel.102.php">linearToInternal</a>( $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.

No roll
No roll

Limitation

Try it using the X axis as the up vector:

vector $upVector = << 1.0, 0.0, 0.0 >>;
be_plot_no_roll_locators( $curve, $upVector );

这里你又会遇到 roll 现象. 此时roll发生在一下两个点,曲线的 tangent 和 X axis 重合的时候 – 也就是和up-vector方向相同的时候. 此时我们没法计算 binormal, 因为你没法从两个相同方向的vectors计算得到 orthogonal basis . 这时候你只能想办法绕过,比如选择另外一个up-vector, 或者稍微调整当前 up-vector 的方向.

Acknowledgements

Related How-To’s

19 Feb 2005

Advertisements

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: