Точки, равномерно распределенные по кривой Безье


10

Я некоторое время осматривался и не могу найти решение этой проблемы. Допустим, у меня есть кубическая кривая Безье (определяемая 4 точками), и я хочу получить набор точек, которые равномерно распределены вдоль кривой. Подумайте о размещении текста вдоль кривой для примера.

Теперь проблема в том, что если я введу t(значение интерполяции от 0 до 1) с постоянным приращением, точки не будут равномерно распределены. Расстояние вдоль кривой меньше, когда кривая делает поворот, и длиннее, когда кривая прямая.

Итак, как мне разместить точки равномерно по кривой Безье?


4
Вы ищете «чисто математическое» (или особенно эффективное) решение? В противном случае простой подход заключается в следующем: преобразовать кривую в ломаную линию, пройдя вдоль кривой, увеличив t, скажем, на 100 шагов, и измерить расстояния между полученными точками. Затем интерполируйте вдоль этой полилинии по желанию.
Marco13

Я думаю, что вы ищете ключевое слово "параметризация длины дуги", на которое был дан ответ, например, в этом вопросе .
Wondra

Что @ Marco13 сказал!
Дэвид ван Бринк

Согласно ответам / комментариям, упомянутый мною подход не только прост, но и довольно распространен. Это для определенного языка? Может быть, тогда кто-нибудь
выложит

1
Возможная
Инженер

Ответы:


3

Это больше математический вопрос. Таким образом, кривая Безье имеет следующую формулу , как в компоненте , так xи в yкомпоненте.

B_x(t) = (1-t)^3 * P0_x + (1-t)^2 * t * P1_x + (1-t) * t^2 * P2_x + t^3 * P3_x
B_y(t) = (1-t)^3 * P0_y + (1-t)^2 * t * P1_y + (1-t) * t^2 * P2_x + t^3 * P3_y

Длина, пройденная tпо кривой gamma, определяется как:

length_gamma(t) = integration( sqrt( derivative(  gamma_x(s)  ) ^2 + derivative(  gamma_y(s)  ) ^2 ) )

Нет понятного человеку решения для интеграла, поэтому вы должны приблизиться.

Замените на gamma(t)выражение, B(t)чтобы получить длину, length_Bпройденную tвдоль сегмента Безье. Допустим, он путешествует из 0в L.

Теперь выберите nзначения между 0и Lкоторые соответствуют равномерно распределенным точкам. Например, длины формы k*L/nдля kот 0до n.

Теперь вам нужно инвертировать функцию length_B, чтобы вы могли вычислить tспину по длине l. Это довольно много математики, и я чертовски ленив, попробуй сделать это сам. Если вы не можете, вы можете перейти к математике стек обмена . Для более полного ответа, вы можете пойти туда в любом случае.

Когда у вас есть эта обратная length_Bфункция (или разумное приближение), вы обрабатываете ее довольно просто.

  • Создать длины l[k]заданного пути пути от начала координат (P0_x,P1_x).
  • Рассчитайте их соответствующее t[k]использование length_B_inverse.
  • Постановка точек с помощью (B_x(t[k]),B_y(t[k])).

1
Спасибо! Оказывается, математика, в которой нужно интегрировать полином Бернштейна, - это кошмар. Я смог использовать это решение
Foaly


0

Просто чтобы расширить сказанное Марко, обычная техника для этого состоит в том, чтобы идти по кривой с гораздо меньшими приращениями, чем шаги фиксированной длины, которые вы хотите предпринять, и сохранять полученную выходную точку (и, возможно, расстояние?) В таблице.

Затем вы просматриваете таблицу и отбрасываете все записи, кроме тех точек, которые ближе всего к целочисленным кратным расстояниям, которые вы хотите пройти.

Затем у вас остается таблица, которую вы можете очень быстро проиндексировать непосредственно во время выполнения. Если вы хотите перейти к месту, в 5 раз превышающему ваше расстояние, посмотрите в таблицу на индекс [5].

Обратите внимание, что вы могли бы выполнить два шага в одном, и на самом деле не сохранять дополнительные элементы в таблице для начала, но это проще визуализировать и понять в два этапа.

Однажды я видел метод, позволяющий фактически рассчитать это на лету без предварительного вычисления таблицы (он также не использовал итерацию / поиск корня!), Но, к сожалению, я не могу вспомнить детали вообще:

Если я запомню или найду, я опубликую информацию!


1
это также может вас заинтересовать: math.stackexchange.com/questions/15896/…
Алан Вульф

0

Шаг 1 - Генерация N + 1 точек путем интерполяции кривой с шагом 1 / N. N должно быть достаточно большим для хороших визуальных результатов, но достаточно маленьким, чтобы его можно было легко вычислить. Значение 50 должно быть в порядке для большинства ситуаций, но оно должно быть настроено для вашего конкретного случая. Я назову это «интерполированными точками».

В качестве альтернативы вы можете создать короткий список сегментов и рекурсивно разбить каждый сегмент, который длиннее, чем желаемая максимальная длина сегмента (первоначально вы должны сгенерировать как минимум четыре сегмента, чтобы учесть S кривых, где начало очень близко к концу).

Шаг 2 - «Пройдите по линии», используя интерполированные точки и желаемый интервал между каждой точкой.

Я оставлю здесь свой код Unity:

public static Vector2[] InterpolateBezier(Vector2 start, Vector2 startControlPoint,
    Vector2 end, Vector2 endControlPoint, int segments)
{
    Vector2[] interpolated = new Vector2[segments + 1];
    interpolated[0] = start;
    interpolated[segments] = end;

    float step = 1f / segments;
    for (int i = 1; i < segments; i++)
    {
        interpolated[i] = GetBezierPosition(start, startControlPoint, end,
            endControlPoint, i * step);
    }

    return interpolated;
}

public static Vector2 GetBezierPosition(Vector2 start, Vector2 startControlPoint,
    Vector2 end, Vector2 endControlPoint, float t)
{
    float omt = 1f - t;
    float omt2 = omt * omt;
    float t2 = t * t;

    return
        start * (omt2 * omt) +
        startControlPoint * (3f * omt2 * t) +
        endControlPoint * (3f * omt * t2) +
        end * (t2 * t);
}

public static List<Vector2> WalkLine(Vector2[] points, float spacing, float offset = 0)
{
    List<Vector2> result = new List<Vector2>();

    spacing = spacing > 0.00001f ? spacing : 0.00001f;

    float distanceNeeded = offset;
    while (distanceNeeded < 0)
    {
        distanceNeeded += spacing;
    }

    Vector2 current = points[0];
    Vector2 next = points[1];
    int i = 1;
    int last = points.Length - 1;
    while (true)
    {
        Vector2 diff = next - current;
        float dist = diff.magnitude;

        if (dist >= distanceNeeded)
        {
            current += diff * (distanceNeeded / dist);
            result.Add(current);
            distanceNeeded = spacing;
        }
        else if (i != last)
        {
            distanceNeeded -= dist;
            current = next;
            next = points[++i];
        }
        else
        {
            break;
        }
    }

    return result;
}

-1

Вот некоторый алгоритм, который дает довольно хорошие результаты:

  Point Index(float pos) const
  {
    int count = p.NumPoints();
    Vector val(0.0,0.0,0.0);
    for(int i=0;i<count;i++)
      { 
        val += bin(pos,i,count-1)*Vector(p.Points(i));
      }
    return Point(val);
  }
  float bin(float pos, int i, int n) const
  { 
    return float(ni(n,i)) * pow(double(pos), double(i))*pow(double(1.0-pos), double(n-i));
  }
  int ni(int n, int i) const
  {
    if (i==0) { return 1; }
    if (n==i) { return 1; }
    return ni(n-1,i-1)+ni(n-1,i);
  }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.