在网上搜索过后,我看到各种论坛中的各种人都暗示用二次曲线近似一个三次曲线.但我找不到配方.
我想要的是这个:
输入:startX,startY,control1X,control1Y,control2X,control2Y,endX,endY输出:startX,startY,controlX,controlY,endX,endY
实际上,由于起点和终点都是一样的,我真正需要的只是......
输入:startX,startY,control1X,control1Y,control2X,control2Y,endX,endY输出:controlX,controlY
如上所述,从4个控制点到3通常是一个近似值.只有一种情况是精确的 - 当三次贝塞尔曲线实际上是一个度数提升的二次贝塞尔曲线时.
您可以使用度数高程方程来得出近似值.这很简单,结果通常都很好.
让我们调用立方Q0..Q3的控制点和二次P0..P2的控制点.然后对于度数提升,方程是:
Q0 = P0
Q1 = 1/3 P0 + 2/3 P1
Q2 = 2/3 P1 + 1/3 P2
Q3 = P2
Run Code Online (Sandbox Code Playgroud)
在你的情况下,你有Q0..Q3,你正在解决P0..P2.有两种方法可以从上面的等式计算P1:
P1 = 3/2 Q1 - 1/2 Q0
P1 = 3/2 Q2 - 1/2 Q3
Run Code Online (Sandbox Code Playgroud)
如果这是一个升高程度的立方,那么这两个方程式将给出P1的相同答案.由于它可能不是,你最好的选择是平均它们.所以,
P1 = -1/4 Q0 + 3/4 Q1 + 3/4 Q2 - 1/4 Q3
Run Code Online (Sandbox Code Playgroud)
要翻译成您的条款:
controlX = -0.25*startX + .75*control1X + .75*control2X -0.25*endX
Run Code Online (Sandbox Code Playgroud)
Y的计算方法类似 - 维度是独立的,因此适用于3d(或nd).
这将是一个近似值.如果您需要更好的近似,获得它的一种方法是使用deCastlejau算法细分初始立方体,然后降低每个段的度数.如果您需要更好的连续性,还有其他近似方法不那么快速和脏.
三次方可以有环和尖点,而二次方不能有。这意味着几乎从来没有简单的解决方案。如果三次方已经是二次方,则存在简单解。通常你必须将三次方除以二次方。你必须决定细分的关键点是什么。
http://fontforge.org/bezier.html#ps2ttf说:“我在网上读到的其他资料建议检查三次样条曲线的拐点(二次样条曲线不能有)并在此处强制断裂。在我看来,这实际上是使结果变得更糟,它使用更多的点,并且近似值看起来不像忽略拐点时那么接近。所以我忽略它们。”
确实如此,拐点(三次方的二阶导数)还不够。但是,如果您还考虑到三次函数的一阶导数的局部极值(最小值、最大值),并且对所有这些进行力破坏,则子曲线都是二次曲线,并且可以用二次曲线表示。
我测试了以下函数,它们按预期工作(找到立方体的所有临界点并将立方体划分为下高的立方体)。当绘制这些子曲线时,曲线与原始三次曲线完全相同,但由于某种原因,当将子曲线绘制为二次曲线时,结果几乎正确,但不完全正确。
所以这个答案并不是对问题的严格帮助,但这些函数提供了三次到二次转换的起点。
为了找到局部极值和拐点,下面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;
};
Run Code Online (Sandbox Code Playgroud)
当您拥有这些临界 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]];
}
Run Code Online (Sandbox Code Playgroud)
subdivide_cubic_to_cubics()在上面的代码中,将原始三次曲线除以值 t 两部分。因为get_t_values_of_critical_points()将t值作为按t值排序的数组返回,所以可以轻松遍历所有t值并得到相应的子曲线。当您拥有这些划分的曲线时,您必须将第二条子曲线除以下一个 t 值。
当所有分割完成后,您就拥有了所有子曲线的控制点。现在只剩下三次控制点到二次控制点的转换了。由于所有子曲线现在都是下高的三次曲线,因此相应的二次控制点很容易计算。二次方的第一个和最后一个控制点与三次方(子曲线)的第一个和最后一个控制点相同,中间的控制点位于直线 P1-P2 和 P4-P3 交叉的点处。
| 归档时间: |
|
| 查看次数: |
4203 次 |
| 最近记录: |