Как преобразовать 2 контрольные точки кубической кривой в единую контрольную точку квадратичной кривой? - PullRequest
4 голосов
/ 06 января 2010

Просматривая в Интернете, я вижу разных людей на разных форумах, ссылающихся на аппроксимацию кубической кривой квадратичной. Но я не могу найти формулу.

То, что я хочу, это:

вход: startX, startY, control1X, control1Y, control2X, control2Y, endX, endY выход: startX, startY, controlX, controlY, endX, endY

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

вход: startX, startY, control1X, control1Y, control2X, control2Y, endX, endY выход: controlX, controlY

Ответы [ 8 ]

7 голосов
/ 08 января 2010

Как уже упоминалось, переход от 4 контрольных точек к 3 обычно является приблизительным. Есть только один случай, когда он будет точным - когда кривая Безье куба на самом деле представляет собой квадратичную кривую Безье с повышенным градусом.

Вы можете использовать уравнения повышения степени, чтобы придумать приближение. Это просто, и результаты обычно довольно хорошие.

Назовем контрольные точки кубики Q0..Q3 и контрольные точки квадратичного P0..P2. Тогда для повышения степени уравнения:

Q0 = P0
Q1 = 1/3 P0 + 2/3 P1
Q2 = 2/3 P1 + 1/3 P2
Q3 = P2

В вашем случае у вас есть Q0..Q3, и вы решаете для P0..P2. Есть два способа вычисления P1 из приведенных выше уравнений:

P1 = 3/2 Q1 - 1/2 Q0
P1 = 3/2 Q2 - 1/2 Q3

Если это кубика с повышенным градусом, то оба уравнения дадут одинаковый ответ для P1. Поскольку, скорее всего, нет, лучше всего их усреднить. Итак,

P1 = -1/4 Q0 + 3/4 Q1 + 3/4 Q2 - 1/4 Q3

Чтобы перевести на ваши условия:

controlX = -0.25*startX + .75*control1X + .75*control2X -0.25*endX

Y вычисляется аналогично - размеры независимы, поэтому это работает для 3d (или n-d).

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

4 голосов
/ 25 января 2013

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

http://fontforge.org/bezier.html#ps2ttf говорит: «Другие источники, которые я читал в сети, предлагают проверить кубический сплайн для точек перегиба (чего не может быть у квадратичных сплайнов) и вызвать разрывы там. На мой взгляд, это фактически ухудшает результат, он использует больше точек, а приближение не выглядит так же близко, как и при игнорировании точек перегиба. Поэтому я игнорирую их. "

Это правда, точек перегиба (второй производной от кубики) недостаточно. Но если принять во внимание также локальные экстремумы (min, max), которые являются первыми производными кубической функции, и силовые разрывы на всех этих подклассах, то все подкривары являются квадратичными и могут быть представлены квадратичными.

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

Так что этот ответ не для строгой справки по проблеме, но эти функции обеспечивают отправную точку для преобразования кубического в квадратичное.

Чтобы найти как локальные крайности, так и точки перегиба, следующий get_t_values_of_critical_points() должен предоставить их.

function compare_num(a,b) {
  if (a < b) return -1;
  if (a > b) return 1;
  return 0;
}

function find_inflection_points(p1x,p1y,p2x,p2y,p3x,p3y,p4x,p4y)
{
  var ax = -p1x + 3*p2x - 3*p3x + p4x;
  var bx = 3*p1x - 6*p2x + 3*p3x;
  var cx = -3*p1x + 3*p2x;

  var ay = -p1y + 3*p2y - 3*p3y + p4y;
  var by = 3*p1y - 6*p2y + 3*p3y;
  var cy = -3*p1y + 3*p2y;
  var a = 3*(ay*bx-ax*by);
  var b = 3*(ay*cx-ax*cy);
  var c = by*cx-bx*cy;
  var r2 = b*b - 4*a*c;
  var firstIfp = 0;
  var secondIfp = 0;
  if (r2>=0 && a!==0)
  {
    var r = Math.sqrt(r2);
    firstIfp = (-b + r) / (2*a);
    secondIfp = (-b - r) / (2*a);
    if ((firstIfp>0 && firstIfp<1) && (secondIfp>0 && secondIfp<1))
    {
      if (firstIfp>secondIfp)
      {
        var tmp = firstIfp;
        firstIfp = secondIfp;
        secondIfp = tmp;
      }
      if (secondIfp-firstIfp >0.00001)
        return [firstIfp, secondIfp];
      else return [firstIfp];
    }
    else if (firstIfp>0 && firstIfp<1)
      return [firstIfp];
    else if (secondIfp>0 && secondIfp<1)
    {
      firstIfp = secondIfp;
      return [firstIfp];
    }
    return [];
  }
  else return [];
}

function get_t_values_of_critical_points(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
    var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
    b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
    c = p1x - c1x,
    t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
    t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
    tvalues=[];
    Math.abs(t1) > "1e12" && (t1 = 0.5);
    Math.abs(t2) > "1e12" && (t2 = 0.5);
    if (t1 >= 0 && t1 <= 1 && tvalues.indexOf(t1)==-1) tvalues.push(t1)
    if (t2 >= 0 && t2 <= 1 && tvalues.indexOf(t2)==-1) tvalues.push(t2);

    a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
    b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
    c = p1y - c1y;
    t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
    t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
    Math.abs(t1) > "1e12" && (t1 = 0.5);
    Math.abs(t2) > "1e12" && (t2 = 0.5);
    if (t1 >= 0 && t1 <= 1 && tvalues.indexOf(t1)==-1) tvalues.push(t1);
    if (t2 >= 0 && t2 <= 1 && tvalues.indexOf(t2)==-1) tvalues.push(t2);

    var inflectionpoints = find_inflection_points(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
    if (inflectionpoints[0]) tvalues.push(inflectionpoints[0]);
    if (inflectionpoints[1]) tvalues.push(inflectionpoints[1]);

    tvalues.sort(compare_num);
    return tvalues;
};

И когда у вас есть эти критические значения t (которые находятся в диапазоне 0-1), вы можете разделить кубическое на части:

function CPoint()
{
  var arg = arguments;
  if (arg.length==1)
  {
    this.X = arg[0].X;
    this.Y = arg[0].Y;
  }
  else if (arg.length==2)
  {
    this.X = arg[0];
    this.Y = arg[1];
  }
}

function subdivide_cubic_to_cubics()
{
    var arg = arguments;
    if (arg.length!=9) return [];
    var m_p1 = {X:arg[0], Y:arg[1]};
  var m_p2 = {X:arg[2], Y:arg[3]};
  var m_p3 = {X:arg[4], Y:arg[5]};
  var m_p4 = {X:arg[6], Y:arg[7]};
  var t = arg[8];
  var p1p = new CPoint(m_p1.X + (m_p2.X - m_p1.X) * t,
                       m_p1.Y + (m_p2.Y - m_p1.Y) * t);
  var p2p = new CPoint(m_p2.X + (m_p3.X - m_p2.X) * t,
                       m_p2.Y + (m_p3.Y - m_p2.Y) * t);
  var p3p = new CPoint(m_p3.X + (m_p4.X - m_p3.X) * t,
                       m_p3.Y + (m_p4.Y - m_p3.Y) * t);
  var p1d = new CPoint(p1p.X + (p2p.X - p1p.X) * t,
                       p1p.Y + (p2p.Y - p1p.Y) * t);
  var p2d = new CPoint(p2p.X + (p3p.X - p2p.X) * t,
                       p2p.Y + (p3p.Y - p2p.Y) * t);
  var p1t = new CPoint(p1d.X + (p2d.X - p1d.X) * t,
                       p1d.Y + (p2d.Y - p1d.Y) * t);
  return [[m_p1.X, m_p1.Y, p1p.X, p1p.Y, p1d.X, p1d.Y, p1t.X, p1t.Y],
          [p1t.X, p1t.Y, p2d.X, p2d.Y, p3p.X, p3p.Y, m_p4.X, m_p4.Y]];
}

subdivide_cubic_to_cubics() в вышеприведенном коде делит исходную кубическую кривую на две части на значение t. Поскольку get_t_values_of_critical_points() возвращает значения t в виде массива, отсортированного по значению t, вы можете легко пройти все значения t и получить соответствующую подкривую. Когда у вас есть эти разделенные кривые, вы должны разделить 2-ю подчиненную кривую на следующее значение t.

Когда все расщепление продолжается, у вас есть контрольные точки всех подкривых. Теперь остались только кубические контрольные точки преобразования в квадратичные. Поскольку все подкривые в настоящее время являются кубами с пониженным возвышением, соответствующие квадратичные контрольные точки легко вычислить Первая и последняя из квадратичных контрольных точек совпадают с первой и последней контрольной точкой кубической (подкривой), а средняя находится в точке, где пересекаются линии P1-P2 и P4-P3.

3 голосов
/ 26 июля 2010

Условные обозначения / терминология

  • Куб определяется: P1 / 2 - опорные точки, контрольные точки C1 / C2
  • | х | является евклидовой нормой х
  • средняя точка прибл. Куб.: Квад, который имеет те же якоря, что и куб, и имеет контрольную точку при C = (3 · C2 - P2 + 3 · C1 - P1) / 4

Алгоритм

  1. выберите абсолютную точность ( prec )
  2. Вычислить Tdiv как корень (кубического) уравнения sqrt (3) / 18 · | P2 - 3 · C2 + 3 · C1 - P1 | / 2 · Tdiv ^ 3 = prec
  3. если Tdiv <0.5, разделите куб на Tdiv. Первый отрезок [0..Tdiv] может быть аппроксимирован квадратичным с дефектом меньше <em>prec в приближении средней точки. Повторите, начиная с шага 2, со вторым полученным сегментом (соответствует 1-Tdiv)
  4. 0.5 <= Tdiv <1 - просто разделите куб на две части. Две половины могут быть аппроксимированы приближением средней точки </li>
  5. Tdiv> = 1 - вся кубика может быть аппроксимирована приближением средней точки

"Волшебная формула" на шаге 2 демонстрируется (с интерактивными примерами) на этой странице .

2 голосов
/ 11 января 2010

Еще один вывод ответа Тфиннига:
Сначала посмотрите Википедию Кривая Безье для формул для квадратичных и кубических кривых Безье (также хорошие анимации):

Q(t) = (1-t)^2 P0 + 2 (1-t) t Q + t^2 P3
P(t) + (1-t)^3 P0 + 3 (1-t)^2 t P1 + 3 (1-t) t^2 P2 + t^3 P3

Требуется, чтобы они соответствовали в середине, t = 1/2:

(P0 + 2 Q + P3) / 4 = (P0 + 3 P1 + 3 P2 + P3) / 8  
=> Q = P1 + P2 - (P0 + P1 + P2 + P3) / 4  

(Q, написанный так, имеет геометрическую интерпретацию:

Pmid = middle of P0 P1 P2 P3  
P12mid = midway between P1 and P2  
draw a line from Pmid to P12mid, and that far again: you're at Q.  

Надеюсь, это имеет смысл - нарисуйте пару примеров.)

1 голос
/ 07 января 2013

Я должен отметить, что решение Адриана отлично подходит для отдельных кубов, но когда кубики являются сегментами гладкого кубического сплайна, то использование его метода приближения средней точки приводит к потере непрерывности наклона в узлах сегментов. Таким образом, метод, описанный в http://fontforge.org/bezier.html#ps2ttf, намного лучше, если вы работаете с глифами шрифтов или по любой другой причине вы хотите сохранить плавность кривой (что, скорее всего, имеет место).

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

1 голос
/ 06 января 2010

В общем случае вам придется использовать несколько квадратичных кривых - многие случаи кубических кривых нельзя даже смутно аппроксимировать одной квадратичной кривой.

Есть хорошая статья, обсуждающая проблему, иЕсть несколько способов ее решения: http://www.timotheegroleau.com/Flash/articles/cubic_bezier_in_flash.htm (включая интерактивные демонстрации).

0 голосов
/ 20 июня 2010

Попробуйте найти конвертер шрифта PostScript с открытым исходным кодом для шрифтов Truetype. Я уверен, что у них это есть. Постскриптум использует кубические кривые Безье, тогда как Truetype использует квадратичные кривые Безье. Удачи.

0 голосов
/ 06 января 2010

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

...