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