一半君的总结纸

听话只听一半君

#108 如何找到一条曲线上等分点的位置?

“arcLengthDimension” Node

此处的关键是使用 arcLengthDimension node 来测量沿曲线的距离.

arcLengthDimension node 在这里也有举例 MEL How-To #73:

首先为了试验我们得首先创建一条曲线.

// 创建 curve.
//
string $curve = `curve -d 3 -p -3.311535524 0.01465542598  0.01668581601
                            -p -3.066300435 0.72711707080 -0.29778268700
                            -p -2.575830259 2.15204036000 -0.92671969290
                            -p  1.845868694 2.12236523700 -0.94456269070
                            -p  2.788845691 0.71280207790 -0.33237598680
                            -p  3.258764000 0.00803332170 -0.02627731900
                            -k 0 -k 0 -k 0 -k 1 -k 2 -k 3 -k 3 -k 3`;

现在使用 “arcLengthDimension” node找到整条曲线的长度. 这是通过制定曲线末尾的最后一点的U参数,并查询这一点和曲线起始点之间的距离得到的.

// 得到曲线的 U 范围.
//
float $maxU = `getAttr ( $curve + ".maxValue" )`;
// Result: 3.0 //

// 创建 arcLengthDimension node, 连到曲线的下游.
//
string $arcLD = `arcLengthDimension ( $curve + ".u[" + $maxU + "]" )`;
// Result: arcLengthDimensionShape1  //

// 查询 U parameter 的距离.
//
float $arcLength = `getAttr ( $arcLD + ".arcLength" )`;
// Result: 8.6 //

不幸的是, 没有这么一个命令 “指定一个距离,找到曲线上的相应位置的U数值.” 所以只好采取下面的间接方法.

Walkthrough

为了得到距起点指定距离的曲线上的点的U参数值 (没有直接的查询方法), 我们只好不断的把曲线二等分 (或者曲线的一部分) , 查询中点位置离起点的距离. 如果此长度比指定距离长, 那么就对第一段曲线继续二等分. 如果此距离比指定距离短,那就把第二段曲线继续二等分. 直到找到这么一个点(离起点的距离和指定的距离很接近时)才停止. 这有点像遍历一个无限大的 red-black tree. 不同的是, 我们不是要找到一个匹配的答案,而是在测量值很接近指定的距离时停止遍历.

这个例子里我们将会找到10个沿曲线等间距的点,如果曲线的总长度是 8.6cm ,那么所有的10个点的间距会是 0.9556cm – 此处假设第一个和最后一个点分别位于曲线的头 (U = 0.0) 和尾 (U = 3.0, 在这个例子里).

第一个点很容易: U = 0.0, 并且distance = 0.0.

// 查询 U = 0.0 处的 edit point 的 world-space 位置.
//
float $xyz[3] = `pointOnCurve -pr 0.0 -p $curve`;

// 在这里放一个 locator .
//
spaceLocator -p $xyz[0] $xyz[1] $xyz[2];

下一个点距离曲线的头部 0.9557cm . 第一步把曲线分成两半,并查询曲线中点处的距离 (在这里U = 1.5).

setAttr ( $arcLD + ".uParamValue" ) 1.5;
$arcLength = `getAttr ( $arcLD + ".arcLength" )`;
// Result: 4.099855 //

这个长度比要求的距离大太多了. 所以把第一段曲线(0.0 和 1.5之间的部分)再次等分 , 在 0.75 处再次求距离.

setAttr ( $arcLD + ".uParamValue" ) 0.75;
$arcLength = `getAttr ( $arcLD + ".arcLength" )`;
// Result: 1.802793 //

继续…

setAttr ( $arcLD + ".uParamValue" ) 0.375;
$arcLength = `getAttr ( $arcLD + ".arcLength" )`;
// Result: 0.909892 //

所以, 我们想要的点位于 U = 0.375 和 U = 0.75 之间.

setAttr ( $arcLD + ".uParamValue" ) 0.5625;
$arcLength = `getAttr ( $arcLD + ".arcLength" )`;
// Result: 1.354677 //

现在知道位于 U = 0.375 和 0.5625 之间.

setAttr ( $arcLD + ".uParamValue" ) 0.46875;
$arcLength = `getAttr ( $arcLD + ".arcLength" )`;
// Result: 1.132847 //

不断继续下去.. 同样操作. 直到 $arcLength 和所需要的距离足够接近时才停下.

// 小于 0.001cm 的时候认为是已经足够近了.
//
float $epsilon = 0.001;

if ( abs( $arcLength - $distance ) < $epsilon )
{
  // 找到了想要的点. 对他进行某些操作.
}

在上面的例子里, 找到的第一个点位于 U = 0.3940429688.

setAttr ( $arcLD + ".uParamValue" ) 0.3940429688;
$arcLength = `getAttr ( $arcLD + ".arcLength" )`;
// Result: 0.955322 //

// 误差是 1cm +/- 1/1000 .
//
print ( abs( 0.955322 - 0.9557 ) );
// Result: 0.000378 //

你可能会觉得这个方法肯定很慢, 其实他不是那么糟. 上面的距离只需要执行11次就可以得到.

最终版

下面是我们的最终结果: 完成上述所有步骤的一个 MEL script :

proc float findParamAtDistance( string $curve,
                                string $arcLD,
                                float $distance,
                                float $epsilon )
{
  float $u = 0.0;

  float $min = `getAttr ( $curve + ".minValue" )`;
  float $max = `getAttr ( $curve + ".maxValue" )`;

  setAttr ( $arcLD + ".uParamValue" ) $max;
  float $arcLength = `getAttr ( $arcLD + ".arcLength" )`;

  // 起点终点不用求.
  //
  if ( $distance <= 0.0 ) return 0.0;
  if ( $distance >= $arcLength ) return $max;

  // 这只是想记录一下得到结果用了几次循环
  // 你可能会发现其实没有用到很多循环
  //
  int $pass = 1;

  while ( true )
  {
    $u = ( $min + $max ) / 2.0;
    setAttr ( $arcLD + ".uParamValue" ) $u;
    $arcLength = `getAttr ( $arcLD + ".arcLength" )`;
    if ( abs( $arcLength - $distance ) < $epsilon ) break;
    if ( $arcLength > $distance ) $max = $u;
    else $min = $u;
    $pass++;
  }

  return $u;
}

proc string plotLocator( string $curve, float $uParam )
{
  float $p[3] = `pointOnCurve -pr $uParam -p $curve`;
  string $locator[] = `spaceLocator -p $p[0] $p[1] $p[2]`;

  return $locator[0];
}

global proc plotEquidistantLocatorsOnCurve( string $curve, int $count )
{
  // 显然多于两个点的时候, 求解才有意义.
  //
  if ( $count < 2 ) error( "Must plot at least two equidistant locators." );

  // 得到曲线的U参数范围.
  //
  float $maxU = `getAttr ( $curve + ".maxValue" )`;

  // 创建一个 arcLengthDimension node .
  //
  string $arcLD = `arcLengthDimension ( $curve + ".u[" + $maxU + "]" )`;

  // 得到曲线的总长度.
  //
  float $arcLength = `getAttr ( $arcLD + ".arcLength" )`;

  // 所指定的点之间的间距是 ( $arcLength / ( $count - 1 ) )
  //
  float $span = $arcLength / ( $count - 1 );

  // 第一个点位于 0.0.
  //
  plotLocator( $curve, 0.0 );

  // Fill in the middle.
  //
  float $epsilon = 0.0001;
  int $i;
  for ( $i = 1; $i < ( $count - 1 ); $i++ )
  {
    float $distance = $span * $i;
    float $uParam = findParamAtDistance( $curve, $arcLD, $distance, $epsilon );
    plotLocator( $curve, $uParam );
  }

  // 最后一个点位于 $maxU.
  //
  plotLocator( $curve, $maxU );

  // 删除 arcLengthDimensionNode (的 transform).
  //
  delete `listRelatives -fullPath -parent $arcLD`;
}

使用举例:

plotEquidistantLocatorsOnCurve( "curve1", 10 );

Related How-To’s

20 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 博主赞过: