沿着NSBezier路径获得任何一点

时间:2012-01-11 11:53:47

标签: objective-c cocoa drawing nsbezierpath

对于我正在编写的程序,我需要能够跟踪对象必须经过的虚拟线(不直)。我当时想用NSBezierPath绘制线条,但是找不到沿线的任何一点的方法,我必须这样做,所以我可以沿着它移动对象。

有人可以建议找到NSBezierPath点的方法吗?如果那不可能,那么有人可以建议一种方法来做上述事情吗?

1 个答案:

答案 0 :(得分:4)

编辑:下面的代码仍然准确,但有更快的方法来计算它。请参阅Introduction to Fast BezierEven Faster Bezier


有两种方法可以解决这个问题。如果您只需要移动某些内容,请使用CAKeyframeAnimation。这非常简单,你永远不需要计算积分。

另一方面,如果出于某种原因你真的需要知道这一点,你必须自己计算Bézier。例如,您可以从iOS 5 Programming Pushing the Limits中提取第18章的示例代码。 (它是为iOS编写的,但它同样适用于Mac。)查看CurvyTextView.m

鉴于控制点P0_P3_以及0到1之间的偏移量(见下文),pointForOffset:会给出路径上的点:

static double Bezier(double t, double P0, double P1, double P2,
                     double P3) {
  return 
                   pow(1-t, 3) *     P0
     + 3 *         pow(1-t, 2) * t * P1
     + 3 * (1-t) * pow(t,   2) *     P2
     +             pow(t,   3) *     P3;
}

- (CGPoint)pointForOffset:(double)t {
  double x = Bezier(t, P0_.x, P1_.x, P2_.x, P3_.x);
  double y = Bezier(t, P0_.y, P1_.y, P2_.y, P3_.y);
  return CGPointMake(x, y);
}

注意:此代码违反了我总是使用访问器而不是直接访问ivars的基本规则之一。这是因为它被称为数千次,并且消除方法调用会对性能产生重大影响。

“抵消”并不是一件容易的事情。它不会沿着曲线线性地进行。如果沿曲线需要均匀间隔的点,则需要计算每个点的正确偏移量。这是通过以下程序完成的:

// Simplistic routine to find the offset along Bezier that is
// aDistance away from aPoint. anOffset is the offset used to
// generate aPoint, and saves us the trouble of recalculating it
// This routine just walks forward until it finds a point at least
// aDistance away. Good optimizations here would reduce the number
// of guesses, but this is tricky since if we go too far out, the
// curve might loop back on leading to incorrect results. Tuning
// kStep is good start.
- (double)offsetAtDistance:(double)aDistance 
                 fromPoint:(CGPoint)aPoint
                    offset:(double)anOffset {
  const double kStep = 0.001; // 0.0001 - 0.001 work well
  double newDistance = 0;
  double newOffset = anOffset + kStep;
  while (newDistance <= aDistance && newOffset < 1.0) {
    newOffset += kStep;
    newDistance = Distance(aPoint, 
                           [self pointForOffset:newOffset]);
  }
  return newOffset;
}

我将Distance()作为练习留给读者,但当然是在示例代码中。

如果您需要,引用的代码还会提供BezierPrime()angleForOffset:。 iOS第18章:作为讨论如何沿任意路径绘制文本的一部分,PTL更详细地介绍了这一点。