计算三次贝塞尔曲线的最快方法?

时间:2010-07-07 05:17:34

标签: c++ c algorithm graphics vector

现在我按照这样计算:

    double dx1 = a.RightHandle.x - a.UserPoint.x;
    double dy1 = a.RightHandle.y - a.UserPoint.y;
    double dx2 = b.LeftHandle.x - a.RightHandle.x;
    double dy2 = b.LeftHandle.y - a.RightHandle.y;
    double dx3 = b.UserPoint.x - b.LeftHandle.x;
    double dy3 = b.UserPoint.y - b.LeftHandle.y;

    float len = sqrt(dx1 * dx1 + dy1 * dy1) + 
        sqrt(dx2 * dx2 + dy2 * dy2) + 
        sqrt(dx3 * dx3 + dy3 * dy3);




    int NUM_STEPS =  int(len * 0.05);

    if(NUM_STEPS > 55)
    {
        NUM_STEPS = 55;
    }
    double subdiv_step  = 1.0 / (NUM_STEPS + 1);
    double subdiv_step2 = subdiv_step*subdiv_step;
    double subdiv_step3 = subdiv_step*subdiv_step*subdiv_step;

    double pre1 = 3.0 * subdiv_step;
    double pre2 = 3.0 * subdiv_step2;
    double pre4 = 6.0 * subdiv_step2;
    double pre5 = 6.0 * subdiv_step3;



    double tmp1x = a.UserPoint.x - a.RightHandle.x * 2.0 + b.LeftHandle.x;
    double tmp1y = a.UserPoint.y - a.RightHandle.y  * 2.0 + b.LeftHandle.y;

    double tmp2x = (a.RightHandle.x - b.LeftHandle.x)*3.0 - a.UserPoint.x + b.UserPoint.x;
    double tmp2y = (a.RightHandle.y - b.LeftHandle.y)*3.0 - a.UserPoint.y + b.UserPoint.y;

    double fx = a.UserPoint.x;
    double fy = a.UserPoint.y;

    //a user
    //a right
    //b left
    //b user

    double dfx = (a.RightHandle.x - a.UserPoint.x)*pre1 + tmp1x*pre2 + tmp2x*subdiv_step3;
    double dfy = (a.RightHandle.y - a.UserPoint.y)*pre1 + tmp1y*pre2 + tmp2y*subdiv_step3;

    double ddfx = tmp1x*pre4 + tmp2x*pre5;
    double ddfy = tmp1y*pre4 + tmp2y*pre5;

    double dddfx = tmp2x*pre5;
    double dddfy = tmp2y*pre5;

    int step = NUM_STEPS;



    while(step--)
    {


        fx   += dfx;
        fy   += dfy;
        dfx  += ddfx;
        dfy  += ddfy;
        ddfx += dddfx;
        ddfy += dddfy;
        temp[0] = fx;
        temp[1] = fy;
        Contour[currentcontour].DrawingPoints.push_back(temp);
    }


    temp[0] = (GLdouble)b.UserPoint.x;
    temp[1] = (GLdouble)b.UserPoint.y;
    Contour[currentcontour].DrawingPoints.push_back(temp);

我想知道是否有更快的方法来插入立方贝塞尔曲线?

由于

4 个答案:

答案 0 :(得分:3)

查看forward differencing以获得更快的方法。必须小心处理舍入错误。

adaptive subdivision方法,通过一些检查,可以快速准确。

答案 1 :(得分:2)

还有一点非常重要,那就是您使用大量固定长度的直线段逼近曲线。这在你的曲线几乎是直的区域是低效的,并且可能导致曲线非常弯曲的令人讨厌的角度折线。没有一个简单的折衷方案可以用于高曲率和低曲率。

为了解决这个问题,您可以动态细分曲线(例如,在中间点将其分成两部分,然后查看两条线段是否在曲线的合理距离内。如果一段是好的适合曲线,停在那里;如果不适合,则以相同的方式细分并重复)。你必须小心细分它,以便在以这种方式采样曲线时不会错过任何局部(小)特征。

这并不总能让您的曲线“更快”,但它会保证在使用达到该质量所需的最少线段数时始终看起来很好。

一旦你绘制曲线“井”,你就可以看看如何“更快”地进行必要的计算。

答案 2 :(得分:0)

实际上你应该继续分裂,直到连接曲线上的两条线(终点节点)和它们最远的控制点是#34;足够平坦": - 完全对齐或 - 他们的交叉点位于"平方距离"来自两个端节点都低于一半"方形像素") - 请注意,您不需要计算实际距离,因为它需要计算平方根,这很慢)

当你遇到这种情况时,忽略控制点并用直线段连接两个端点。

速度更快,因为使用经典的Bresenham算法,您可以快速获得可以直接绘制的直线段,就好像它们是直线一样。

注意:您应该考虑端点的小数位,以正确设置误差变量的初始值,累积差异并使用增量Bresenham算法,以获得更好的结果(特别是当最后一段绘制时离水平或垂直或两个对角线非常近;否则你会得到可见的文物。

在整数网格上对齐的点之间绘制线条的经典Bresenham算法将此误差变量初始化为零,以获得第一个端节点的位置。但是对Bresenham算法进行微小修改后,将两个距离变量和误差值简单地按2的常数幂放大,然后对x或y变量使用0 / + 1增量,保持未缩放。

错误变量的高位也允许您计算可用于使用正确的Alpha阴影绘制两个堆叠像素的Alpha值。在大多数情况下,您的图像最多使用8位色彩平面,因此您不需要为误差值提供超过8位的额外精度,并且可以将放大倍数限制为256:您可以使用它绘制"平滑"线。

但你可以将自己限制在16(四位)的缩放系数:你必须绘制的典型位图图像不是很宽,它们的分辨率远低于+/- 2十亿(签名32位的限制)整数):当您将​​坐标放大16倍时,它将保持28位可以使用,但您应该已经"剪切"要渲染的位图的视图区域的几何图形,Bresenham算法的错误变量在所有情况下都将保持在56位以下,并且仍然适合64位整数。

如果您的错误变量是32位,则必须将缩放坐标限制在2 ^ 15(不超过15位)以下(对于最坏的情况)(否则Bresenham使用的错误变量符号的测试将不起作用由于最坏情况下的整数溢出),并且升级因子为16(4位),您将被限制为绘制宽度或高度不大于11位的图像,即2048x2048图像。

但是如果您的绘制区域实际上低于2048x2048像素,那么绘制由绘制颜色的16个alpha阴影值平滑的衬里没有问题(要绘制alpha阴影像素,您需要读取orignal像素混合Alpha阴影颜色之前图像中的值,除非计算出的阴影对于您不需要绘制的第一个放样像素为0%,对于第二个堆叠像素为100%,您可以直接用普通的抽奖颜色)

如果您的计算图像还包含alpha通道,则您的绘制颜色也可以具有自己的Alpha值,您需要对其进行着色并与要绘制的像素的Alpha值组合。但是,您不需要任何中间缓冲区来绘制线条,因为您可以直接在目标缓冲区中绘制。

使用Bresenham算法使用的错误变量,由于舍入错误导致完全没有问题,因为这个变量会考虑它们。因此,正确设置其初始值(另一种方法是,在开始细分递归之前,简单地将所有坐标按比例放大16倍,在Bresenham算法本身中,样条曲线慢16倍。)

答案 3 :(得分:0)

注意如何计算“足够平坦”。 “平坦度”是两个成功段之间的最小绝对角度(在0和180°之间)的一个轨迹,但是您不需要计算实际角度,因为这个平坦度也等于将余弦设置为最小值他们的相对角度。

余弦值也不需要直接计算,因为你需要的只是两个向量的向量乘积,并将它与它们最大长度的平方进行比较。

另请注意,“余弦的平方”也是“一减去正弦的平方”。最大平方余弦值也是最小平方正弦值...现在你知道使用哪种“矢量乘积”:最快和最简单的计算是标量积,其平方与两者的平方正弦成正比向量和两个向量的平方长度的乘积。

因此,检查曲线是否“足够平坦”很简单:计算两个标量积之间的比率,并查看此比率是否低于“平坦度”常数值(最小平方正弦值)。没有除零,因为你将确定两个向量中的哪一个是最长的,如果这个向量的方形长度低于1/4,那么你的曲线已经足够平坦以获得渲染分辨率;否则检查最长和最短矢量之间的比率(由包含端点和控制点的凸包的交叉对角线形成):

  • 有二次贝塞尔曲线,凸包是一个三角形,你选择两对

  • 对于立方贝塞尔曲线,凸包是一个四边凸多边形,对角线可以将一个终点与两个控制点中的一个连接起来,或者将两个终点和两个控制点连接在一起你有六种可能性

使用组合给出第一个矢量和第一个矢量之间的最大长度,第一个矢量和其他三个点之一,第二个矢量连接另外两个点):

  • 您需要确定段的“最小平方长度”,从一个端点或控制点开始,到序列中的下一个控制点或终点。 (在二次Bezier中,您只需比较两个段,使用二次Bezier,检查3个段)

  • 如果这个“最小平方长度”低于1/4,你就可以停在那里,曲线“足够平坦”。

  • 然后确定从一个端点开始到任何一个另一个端点或控制点的段的“最大平方长度”(使用二次贝塞尔曲线,您可以安全地使用相同的2个段作为在上面,使用三次贝塞尔曲线,您可以丢弃上面用于连接两个控制点的3个段中的一个,但是添加连接两个端节点的段。)

  • 然后检查“最小平方长度”是否低于常数“平直度”(最小平方正弦)乘以“最大平方长度”的乘积(如果是这样,曲线“足够平坦”。< / p>

  • 在这两种情况下,当您的曲线“足够平坦”时,您只需要绘制连接两个端点的线段。否则,您将递归分割样条曲线。

你可以包括递归的限制,但实际上它永远不会到达,除非曲线的凸包覆盖非常大的绘图区域中的非常大的区域;即使有32级重复,它也不会在对角线长度小于2 ^ 32像素的矩形绘制区域中爆炸(只有在具有浮点的几乎无限空间中分割“虚拟贝塞尔曲线”时才会达到限制坐标,但你不打算直接绘制它,因为你不会在这样的空间中有1/2像素限制,并且只有你为“平面度”设置了极值值你的“最小平方正弦”常数参数是1/2 ^ 32或更低。