Получение любой точки по пути NSBezier - PullRequest
1 голос
/ 11 января 2012

Для программы, которую я пишу, мне нужно иметь возможность отслеживать виртуальную линию (которая не является прямой), по которой должен проходить объект. Я думал использовать NSBezierPath, чтобы нарисовать линию, но я не могу найти способ провести какую-либо точку вдоль линии, что я должен сделать, чтобы я мог перемещать объект вдоль нее.

Может кто-нибудь предложить способ найти точку вдоль NSBezierPath? Если это невозможно, может кто-нибудь предложить метод, чтобы сделать выше?

1 Ответ

4 голосов
/ 11 января 2012

РЕДАКТИРОВАТЬ: приведенный ниже код все еще точен, но есть гораздо более быстрые способы его расчета. См. Введение в Fast Bezier и Even Faster Bezier .


Есть два способа приблизиться к этому. Если вам просто нужно переместить что-то по линии, используйте CAKeyframeAnimation. Это довольно просто, и вам никогда не нужно вычислять очки.

Если, с другой стороны, вам по какой-то причине вам действительно необходимо знать точку, вы должны сами рассчитать Безье. Например, вы можете взять пример кода для главы 18 из Программирование на iOS 5 «Раздвигая пределы» . (Он написан для 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);
}

ПРИМЕЧАНИЕ. Этот код нарушает одно из моих основных правил - всегда использовать аксессоры, а не прямой доступ к иварам. Это потому, что он вызывается много тысяч раз, а устранение вызова метода оказывает значительное влияние на производительность.

"Смещение" - это не тривиальная вещь, которую нужно выработать. Это не происходит линейно вдоль кривой. Если вам нужны равномерно расположенные точки вдоль кривой, вам необходимо рассчитать правильное смещение для каждой точки. Это делается с помощью этой процедуры:

// 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:, если они вам нужны. Глава 18 iOS: PTL описывает это более подробно в рамках дискуссии о том, как рисовать текст по произвольному пути.

...