Оптимизируйте трассировку квадратичных кривых с помощью численных методов

Я пытаюсь проследить квадратичные кривые Безье, расставляя «маркеры» на заданной длине шага distance. Пытался сделать это наивным способом:

    const p = toPoint(map, points[section + 1]);
    const p2 = toPoint(map, points[section]);
    const {x: cx, y: cy} = toPoint(map, cp);
    const ll1 = toLatLng(map, p),
    ll2 = toLatLng(map, p2),
    llc = toLatLng(map, { x: cx, y: cy });
    const lineLength = quadraticBezierLength(
      ll1.lat,
      ll1.lng,
      llc.lat,
      llc.lng,
      ll2.lat,
      ll2.lng
    );
    for (let index = 0; index < Math.floor(lineLength / distance); index++) {
      const t = distance / lineLength;
      const markerPoint = getQuadraticPoint(
        t * index,
        p.x,
        p.y,
        cx,
        cy,
        p2.x,
        p2.y
      );
      const markerLatLng = toLatLng(map, markerPoint);

      markers.push(markerLatLng);
    }

Этот подход не работает, поскольку корреляция квадратичной кривой между t и L не является линейной. Я не мог найти формулу, которая давала бы мне хорошее приближение, поэтому рассматриваю решение этой задачи с помощью численных методов [Ньютон]. Я рассматриваю один простой вариант — разбить кривую на x [например, в 10] раз больше частей, чем нужно. После этого с помощью той же функции quadraticBezierLength() рассчитайте расстояние до каждой из этих точек. После этого выберите точку так, чтобы длина была ближе всего к distance * index.

Однако это было бы огромным перебором с точки зрения сложности алгоритма. Я, вероятно, мог бы начать сравнивать точки для index + 1 с подмножества после/без точки, которую я уже выбрал, тем самым пропустив начало набора. Это немного снизит сложность, но все равно будет очень неэффективно.

Есть идеи и/или предложения?

В идеале мне нужна функция, которая принимала бы d — расстояние вдоль кривой, p0, cp, p1 — три точки, определяющие квадратичную кривую Безье, и возвращала бы массив координат, реализованный с наименьшей возможной сложностью.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
64
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

ОК, я нашел аналитическую формулу для двумерной квадратичной кривой Безье здесь:

Итак, идея заключается в простом бинарном поиске параметра t до тех пор, пока аналитически полученная длина дуги не совпадет с желаемой длиной...

Код С++:

//---------------------------------------------------------------------------
float x0,x1,x2,y0,y1,y2;    // control points
float ax[3],ay[3];          // coefficients
//---------------------------------------------------------------------------
void get_xy(float &x,float &y,float t)  // get point on curve from parameter t=<0,1>
    {
    float tt=t*t;
    x=ax[0]+(ax[1]*t)+(ax[2]*tt);
    y=ay[0]+(ay[1]*t)+(ay[2]*tt);
    }
//---------------------------------------------------------------------------
float get_l_naive(float t)      // get arclength from parameter t=<0,1>
    {
    // naive iteration
    float x0,x1,y0,y1,dx,dy,l=0.0,dt=0.001;
    get_xy(x1,y1,t);
    for (int e=1;e;)
        {
        t-=dt; if (t<0.0){ e=0; t=0.0; }
        x0=x1; y0=y1; get_xy(x1,y1,t);
        dx=x1-x0; dy=y1-y0;
        l+=sqrt((dx*dx)+(dy*dy));
        }
    return l;
    }
//---------------------------------------------------------------------------
float get_l(float t)        // get arclength from parameter t=<0,1>
    {
    // analytic fomula from: https://stackoverflow.com/a/11857788/2521214
    float ax,ay,bx,by,A,B,C,b,c,u,k,cu,cb;
    ax=x0-x1-x1+x2;
    ay=y0-y1-y1+y2;
    bx=x1+x1-x0-x0;
    by=y1+y1-y0-y0;
    A=4.0*((ax*ax)+(ay*ay));
    B=4.0*((ax*bx)+(ay*by));
    C=     (bx*bx)+(by*by);
    b=B/(2.0*A);
    c=C/A;
    u=t+b;
    k=c-(b*b);
    cu=sqrt((u*u)+k);
    cb=sqrt((b*b)+k);
    return 0.5*sqrt(A)*((u*cu)-(b*cb)+(k*log(fabs((u+cu))/(b+cb))));
    }
//---------------------------------------------------------------------------
float get_t(float l0)       // get parameter t=<0,1> from arclength
    {
    float t0,t,dt,l;
    for (t=0.0,dt=0.5;dt>1e-10;dt*=0.5)
        {
        t0=t; t+=dt;
        l=get_l(t);
        if (l>l0) t=t0;
        }
    return t;
    }
//---------------------------------------------------------------------------
void set_coef()             // compute coefficients from control points
    {
    ax[0]=                  (    x0);
    ax[1]=        +(2.0*x1)-(2.0*x0);
    ax[2]=(    x2)-(2.0*x1)+(    x0);
    ay[0]=                  (    y0);
    ay[1]=        +(2.0*y1)-(2.0*y0);
    ay[2]=(    y2)-(2.0*y1)+(    y0);
    }
//---------------------------------------------------------------------------

Применение:

  1. установить контрольные точки x0,y0,...
  2. тогда вы можете свободно использовать t=get_t(wanted_arclength)

Если вы хотите использовать get_t_naive и или get_xy, вы должны сначала вызвать set_coef

Если вы хотите настроить скорость / точность, вы можете играть с целевой точностью binsearch, установленной в настоящее время на1e-10

Здесь оптимизированная (объединенная get_l,get_t функция) версия:

//---------------------------------------------------------------------------
float get_t(float l0)       // get parameter t=<0,1> from arclength
    {
    float t0,t,dt,l;
    float ax,ay,bx,by,A,B,C,b,c,u,k,cu,cb,cA;
    // precompute get_l(t) constants
    ax=x0-x1-x1+x2;
    ay=y0-y1-y1+y2;
    bx=x1+x1-x0-x0;
    by=y1+y1-y0-y0;
    A=4.0*((ax*ax)+(ay*ay));
    B=4.0*((ax*bx)+(ay*by));
    C=     (bx*bx)+(by*by);
    b=B/(2.0*A);
    c=C/A;
    k=c-(b*b);
    cb=sqrt((b*b)+k);
    cA=0.5*sqrt(A);
    // bin search t so get_l == l0
    for (t=0.0,dt=0.5;dt>1e-10;dt*=0.5)
        {
        t0=t; t+=dt;
        // l=get_l(t);
        u=t+b; cu=sqrt((u*u)+k);
        l=cA*((u*cu)-(b*cb)+(k*log(fabs((u+cu))/(b+cb))));
        if (l>l0) t=t0;
        }
    return t;
    }
//---------------------------------------------------------------------------

Меня не волнует ни куб, ни расстояние по прямым линиям. Меня интересуют квадратичные кривые.

Igor Shmukler 01.12.2022 12:34

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

Igor Shmukler 01.12.2022 12:59

@IgorShmukler Я нашел правильную формулу с помощью t ... Я полностью переработал свой ответ, снова удалил все устаревшие вещи и оставил только решение, основанное на бинарном поиске ... оно работает точно на кривых, которые я пробовал ...

Spektre 02.12.2022 09:13

На данный момент я придумал следующее:

    for (let index = 0; index < Math.floor(numFloat * times); index++) {
      const t = distance / lineLength / times;
      const l1 = toLatLng(map, p), lcp = toLatLng(map, new L.Point(cx, cy));
      const lutPoint = getQuadraticPoint(
        t * index,
        p.x,
        p.y,
        cx,
        cy,
        p2.x,
        p2.y
      );
      const lutLatLng = toLatLng(map, lutPoint);
      const length = quadraticBezierLength(l1.lat, l1.lng, lcp.lat, lcp.lng, lutLatLng.lat, lutLatLng.lng);
      lut.push({t: t * index, length});
    }
    const lut1 = lut.filter(({length}) => !isNaN(length));
    console.info('lookup table:', lut1);
    for (let index = 0; index < Math.floor(numFloat); index++) {
      const t = distance / lineLength;
      // find t closest to distance * index
      const markerT = lut1.reduce((a, b) => {
        return a.t && Math.abs(b.length - distance * index) < Math.abs(a.length - distance * index) ? b.t : a.t || 0;
      });
      const markerPoint = getQuadraticPoint(
        markerT,
        p.x,
        p.y,
        cx,
        cy,
        p2.x,
        p2.y
      );
      const markerLatLng = toLatLng(map, markerPoint);
    }

Я думаю только о том, что моя длина кривой Безье работает не так, как я ожидал.

function quadraticBezierLength(x1, y1, x2, y2, x3, y3) {
  let a, b, c, d, e, u, a1, e1, c1, d1, u1, v1x, v1y;

  v1x = x2 * 2;
  v1y = y2 * 2;
  d = x1 - v1x + x3;
  d1 = y1 - v1y + y3;
  e = v1x - 2 * x1;
  e1 = v1y - 2 * y1;
  c1 = a = 4 * (d * d + d1 * d1);
  c1 += b = 4 * (d * e + d1 * e1);
  c1 += c = e * e + e1 * e1;
  c1 = 2 * Math.sqrt(c1);
  a1 = 2 * a * (u = Math.sqrt(a));
  u1 = b / u;
  a = 4 * c * a - b * b;
  c = 2 * Math.sqrt(c);
  return (
    (a1 * c1 + u * b * (c1 - c) + a * Math.log((2 * u + u1 + c1) / (u1 + c))) /
    (4 * a1)
  );
}

Я считаю, что полная длина кривой верна, но частичная длина, вычисляемая для таблицы поиска, неверна.

Расчет длины всей кривой работает нормально. Проблема в том, что я использую его для расчета не той вещи. Я нахожу точку на кривой, а затем предполагаю, что новая кривая, состоящая из начала, контроля и этой вновь найденной точки, будет составлять более короткую часть той же кривой. Это неправильно, я понимаю.

Igor Shmukler 02.12.2022 10:10

Вы действительно тестировали свой код? Я создал аналогичную функцию, и я не получаю правильных результатов.

Igor Shmukler 02.12.2022 10:16

Я получаю значения, которые даже не близки. Длина всей кривой для моего случая составляет около 218, а результат для t = 1 — более 50 000.

Igor Shmukler 02.12.2022 10:26

В JavaScript все является числом с плавающей запятой, поэтому нет необходимости преобразовывать целые числа в числа с плавающей запятой. Это проблема для большинства вычислений, но здесь работает нормально. Кстати, зачем вам fabs в вашем коде?

Igor Shmukler 02.12.2022 10:27

это то, что я тестирую i.stack.imgur.com/ObbQi.png точки масштабируются с размером окна, поэтому я могу быстро протестировать, изменяя размер окна. Как вы можете видеть длины для совпадений t=0.75 ... также get_l(get_t(200))=200.000005, что довольно хорошо, учитывая, что я использую только поплавки

Spektre 02.12.2022 10:28

fabs здесь, потому что это в формуле, которую я связал ... вы знаете, что || рядом с k*log|...| означает абсолютное значение в математическом синтаксисе

Spektre 02.12.2022 10:33

Я сделал два расчета, полная длина дуги с: x1: 286, y1: 389, x2: 324.98205596852745, y2: 246.72650759509187, x3: 393.5, y3: 265.5 и результат - длина линии: 181.71253724569695; затем quadraticBezierArcLength(1, 286, 389, 324,98205596852745, 246,72650759509187, 393,5, 265,5) и результат 41919.46098139183. Интересно, как это возможно, что вы получаете правильные результаты.

Igor Shmukler 02.12.2022 10:46

Я жестко закодировал ваши точки, использовал t=1.0 и l=100 ... i.stack.imgur.com/eHO0k.png ... хех, печать get_l(200) должна быть 100 ее жестко запрограммированной строки :) ... попробуйте скопировать и вставить мой get_t и функции get_l ... просто добавьте x0,y0,x1,y1,x2,y2 к параметрам вызова ... также у вашего quadraticBezierArcLength есть 6 параметров, как вы можете вызывать его с 7? это синтаксическая ошибка ... или у вас другой код, чем здесь

Spektre 02.12.2022 10:56

@Spektre Журнал был в порядке, но где-то была ошибка. Попробовал ваш вариант, очень похож на мой. Тем не менее, ваша версия работает правильно, а моя нет. Большое спасибо. Постараюсь выяснить, что не так.

Igor Shmukler 02.12.2022 12:01

Кстати, вы можете оптимизировать дальнейшую оптимизацию, объединив get_l с get_t, поскольку get_l имеют много констант (не зависящих от t), которые можно вычислить только один раз ... добавят код в мой ответ через минуту

Spektre 02.12.2022 12:15

да, хороший момент.

Igor Shmukler 02.12.2022 12:18

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

Spektre 02.12.2022 12:24

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

Вычисление криволинейной абсциссы s как функции параметра кривой t действительно вариант, но это приводит к решению уравнения s(t) = Sk/n для целого числа k, где S — общая длина (или s(t) = kd, если наложен шаг). Это неудобно, потому что s(t) недоступна как простая функция и является трансцендентной.

Лучшим методом является решение дифференциального уравнения

dt/ds = 1/(ds/dt) = 1/√(dx/dt)²+(dy/dt)²

используя предпочитаемый вами решатель ODE (RK4). Это позволяет вам наложить фиксированный шаг на s и эффективно с точки зрения вычислений.

Не до конца понял ваш пост. Следовательно, я собираюсь предположить, что вы, возможно, имели в виду. s(t) легко вычислить. Я искал решение в закрытой форме для C(s) [в ваших терминах]. Хотя я считаю, что эта проблема теоретически решаема, считаю, что решения, кроме как с использованием численных методов, пока нет.

Igor Shmukler 08.12.2022 16:27

t(s) зависит от обратной эллиптической функции, которую вы все равно должны вычислить численно. Этого легко достичь с помощью ОДУ.

Yves Daoust 08.12.2022 17:15

Я не знаю, что такое ODE. Не думайте, что вы ДОЛЖНЫ решить это численно. На самом деле я использовал числовое решение. Тем не менее, я считаю, что решение в закрытой форме возможно. Мы получили бы уравнения 4-й степени, и они разрешимы. Я лично не понял решения. Я верю, что однажды кто-нибудь придет с умной заменой и опубликует решение. Время от времени я буду просматривать темы SIGGRAPH.

Igor Shmukler 08.12.2022 20:03

@IgorShmukler: извините, ошибся. Для квадратичного Безье длина дуги имеет аналитическое выражение, включающее гиперболическую функцию. Это выражение не может быть инвертировано аналитически и никогда не будет. Точно так же, как многочлены пятой степени никогда не будут решаться радикалами.

Yves Daoust 08.12.2022 21:44

Другие вопросы по теме