c-计算三次贝塞尔长度的廉价方法



三次贝塞尔长度的解析解似乎不存在,但这并不意味着编码一个廉价的解决方案是不存在的。我所说的便宜是指50-100纳秒(或更低)的范围。

有人知道这样的事情吗?可能分为两类:

1) 像1%这样的错误更少,但代码更慢。2) 误差更大,比如20%,但速度更快?

我浏览了一下谷歌,但没有找到任何看起来不错的解决方案。只有类似于N个线段上的除法并且求和N sqrt-对于更高的精度来说太慢,并且对于2个或3个片段可能太不准确。

还有更好的吗?

另一个选项是将弧长估计为弦和控制网之间的平均值。实践中:

Bezier bezier = Bezier (p0, p1, p2, p3);
chord = (p3-p0).Length;
cont_net = (p0 - p1).Length + (p2 - p1).Length + (p3 - p2).Length;
app_arc_length = (cont_net + chord) / 2;

然后,可以递归地将样条曲线分段拆分为两个分段,并计算弧长直至收敛。我测试了自己,它实际上收敛得很快。我是从这个论坛上得到这个想法的。

最简单的算法:压平曲线并计算欧氏距离。只要你想要一个近似的弧长,这个解决方案既快又便宜。给定曲线的坐标LUT——你说的是速度,所以我假设你使用这些,而不是不断地重新计算坐标——这是一个简单的循环,有一个计数。在通用代码中,使用dist函数计算两点之间的欧氏距离:

var arclength = 0,
    last=LUT.length-1,
    i;
for (i=0; i<last; i++) {
  arclength += dist(LUT[i], LUT[i+1]);
}

完成。arclength现在是基于LUT可以在曲线中形成的最大线段数量的近似弧长。需要更快地处理更大的潜在错误吗?控制分段计数。

var arclength = 0,
    segCount = ...,
    last=LUT.length-2,
    step = last/segCount,
    s, i;
for (s=0; s<=segCount; s++) {
  i = (s*step/last)|0;
  arclength += dist(LUT[i], LUT[i+1]);
}

这几乎是最简单的算法,仍然可以生成接近真实弧长的值。为了更好,你将不得不使用更昂贵的数值方法(如勒让德-高斯求积技术)。

如果你想知道原因,请点击"Bézier曲线上的底漆"的弧长部分。

在我的案例中,一种快速有效的方法就是这样。(用c#为Unity3d重写)

public static float BezierSingleLength(Vector3[] points){
    var p0 = points[0] - points[1];
    var p1 = points[2] - points[1];
    var p2 = new Vector3();
    var p3 = points[3]-points[2];
    var l0 = p0.magnitude;
    var l1 = p1.magnitude;
    var l3 = p3.magnitude;
    if(l0 > 0) p0 /= l0;
    if(l1 > 0) p1 /= l1;
    if(l3 > 0) p3 /= l3;
    p2 = -p1;
    var a = Mathf.Abs(Vector3.Dot(p0,p1)) + Mathf.Abs(Vector3.Dot(p2,p3));
    if(a > 1.98f || l0 + l1 + l3 < (4 - a)*8) return l0+l1+l3;
    var bl = new Vector3[4];
    var br = new Vector3[4];
    bl[0] = points[0];
    bl[1] = (points[0]+points[1]) * 0.5f;
    var mid = (points[1]+points[2]) * 0.5f;
    bl[2] = (bl[1]+mid) * 0.5f;
    br[3] = points[3];
    br[2] = (points[2]+points[3]) * 0.5f;
    br[1] = (br[2]+mid) * 0.5f;
    br[0] = (br[1]+bl[2]) * 0.5f;
    bl[3] = br[0];
    return BezierSingleLength(bl) + BezierSingleLength(br);
}

我计算出了3点Bezier的长度的闭合形式表达式(如下)。我还没有试图算出一个4分以上的闭合形式。这很可能很难表示和处理。然而,通过使用弧长公式进行积分,诸如龙格-库塔积分算法(详细信息请参见我的问答)之类的数值近似技术将非常有效。

以下是一些Java代码,用于3点Bezier的弧长,包括点abc

    v.x = 2*(b.x - a.x);
    v.y = 2*(b.y - a.y);
    w.x = c.x - 2*b.x + a.x;
    w.y = c.y - 2*b.y + a.y;
    uu = 4*(w.x*w.x + w.y*w.y);
    if(uu < 0.00001)
    {
        return (float) Math.sqrt((c.x - a.x)*(c.x - a.x) + (c.y - a.y)*(c.y - a.y));
    }
    vv = 4*(v.x*w.x + v.y*w.y);
    ww = v.x*v.x + v.y*v.y;
    t1 = (float) (2*Math.sqrt(uu*(uu + vv + ww)));
    t2 = 2*uu+vv;
    t3 = vv*vv - 4*uu*ww;
    t4 = (float) (2*Math.sqrt(uu*ww));
    return (float) ((t1*t2 - t3*Math.log(t2+t1) -(vv*t4 - t3*Math.log(vv+t4))) / (8*Math.pow(uu, 1.5)));
public float FastArcLength()
{
    float arcLength = 0.0f;
    ArcLengthUtil(cp0.position, cp1.position, cp2.position, cp3.position, 5, ref arcLength);
    return arcLength;
}
private void ArcLengthUtil(Vector3 A, Vector3 B, Vector3 C, Vector3 D, uint subdiv, ref float L)
{
    if (subdiv > 0)
    {
        Vector3 a = A + (B - A) * 0.5f;
        Vector3 b = B + (C - B) * 0.5f;
        Vector3 c = C + (D - C) * 0.5f;
        Vector3 d = a + (b - a) * 0.5f;
        Vector3 e = b + (c - b) * 0.5f;
        Vector3 f = d + (e - d) * 0.5f;
        // left branch
        ArcLengthUtil(A, a, d, f, subdiv - 1, ref L);
        // right branch
        ArcLengthUtil(f, e, c, D, subdiv - 1, ref L);
    }
    else
    {
        float controlNetLength = (B-A).magnitude + (C - B).magnitude + (D - C).magnitude;
        float chordLength = (D - A).magnitude;
        L += (chordLength + controlNetLength) / 2.0f;
    }
}

首先你应该了解Bezier中使用的算法,当我用c#编写一个充满图形材料的程序时,我使用了bezier,很多时候我不得不在bezier中找到一个堇青石点,但乍一看似乎是不可能的。所以我做的事情是在我的服装数学课上写立方贝塞尔函数,这是我的项目。所以我会先和你分享代码。

    //--------------- My Costum Power Method ------------------\
public static float FloatPowerX(float number, int power)
        {
            float temp = number;
            for (int i = 0; i < power - 1; i++)
            {
                temp *= number;
            }
            return temp;
        }
    //--------------- Bezier Drawer Code Bellow ------------------\
public static void CubicBezierDrawer(Graphics graphics, Pen pen, float[] startPointPixel, float[] firstControlPointPixel
                , float[] secondControlPointPixel, float[] endPointPixel)
        {
            float[] px = new float[1111], py = new float[1111];
            float[] x = new float[4] { startPointPixel[0], firstControlPointPixel[0], secondControlPointPixel[0], endPointPixel[0] };
            float[] y = new float[4] { startPointPixel[1], firstControlPointPixel[1], secondControlPointPixel[1], endPointPixel[1] };
        int i = 0;
        for (float t = 0; t <= 1F; t += 0.001F)
        {
            px[i] = FloatPowerX((1F - t), 3) * x[0] + 3 * t * FloatPowerX((1F - t), 2) * x[1] + 3 * FloatPowerX(t, 2) * (1F - t) * x[2] + FloatPowerX(t, 3) * x[3];
            py[i] = FloatPowerX((1F - t), 3) * y[0] + 3 * t * FloatPowerX((1F - t), 2) * y[1] + 3 * FloatPowerX(t, 2) * (1F - t) * y[2] + FloatPowerX(t, 3) * y[3];
            graphics.DrawLine(pen, px[i - 1], py[i - 1], px[i], py[i]);
            i++;
        }
    }

正如你在上面看到的,这是贝塞尔函数的工作方式,它绘制的贝塞尔函数与微软贝塞尔函数相同(我已经测试过了)。您可以通过增加数组大小和计数器大小,或者绘制elipse而不是line&…来使其更加准确。所有这些都取决于你的需求和你需要的准确性水平。

回到主目标,问题是如何计算长度???

答案是我们有很多点,每个点都有一个x坐标和y坐标,这让我们记住了一个三角形;尤其是一个直角三角形。所以如果我们有点p1&p2,我们可以将它们的距离计算为直角三角形弦。正如我们在学校数学课上记得的那样,在直角三角形类型的ABC三角形中,弦长为->Sqrt(Angle的FrontCostalLenght^2+Angle的SideCostalLeghth^2);

在所有点之间都存在这种关系,我们计算当前点和当前点之前的最后一个点之间的长度(exmp p[i-1]&p[i]),并将它们的总和存储在一个变量中。让我们在下面的代码中显示

//--------------- My Costum Power Method ------------------\
public static float FloatPower2(float number)
        {
            return number * number;
        }
//--------------- My Bezier Lenght Calculator Method ------------------\
public static float CubicBezierLenghtCalculator(float[] startPointPixel
            , float[] firstControlPointPixel, float[] secondControlPointPixel, float[] endPointPixel)
        {
            float[] tmp = new float[2];
            float lenght = 0;
            float[] px = new float[1111], py = new float[1111];
            float[] x = new float[4] { startPointPixel[0], firstControlPointPixel[0]
                , secondControlPointPixel[0], endPointPixel[0] };
            float[] y = new float[4] { startPointPixel[1], firstControlPointPixel[1]
                , secondControlPointPixel[1], endPointPixel[1] };
            int i = 0;
            for (float t = 0; t <= 1.0; t += 0.001F)
            {
                px[i] = FloatPowerX((1.0F - t), 3) * x[0] + 3 * t * FloatPowerX((1.0F - t), 2) * x[1] + 3F * FloatPowerX(t, 2) * (1.0F - t) * x[2] + FloatPowerX(t, 3) * x[3];
                py[i] = FloatPowerX((1.0F - t), 3) * y[0] + 3 * t * FloatPowerX((1.0F - t), 2) * y[1] + 3F * FloatPowerX(t, 2) * (1.0F - t) * y[2] + FloatPowerX(t, 3) * y[3];
                if (i > 0)
                {
                    tmp[0] = Math.Abs(px[i - 1] - px[i]);// calculating costal lenght
                    tmp[1] = Math.Abs(py[i - 1] - py[i]);// calculating costal lenght
                    lenght += (float)Math.Sqrt(FloatPower2(tmp[0]) + FloatPower2(tmp[1]));// calculating the lenght of current RightTriangle Chord  & add it each time to variable
                }
                i++;
            }
            return lenght;
        }

如果你想有更快的计算只需要减少像素&py数组长度和loob计数。

我们也可以通过将数组长度的px和py减少到1来减少内存需求,或者制作一个简单的双变量,但由于发生了增加我们的大O的条件情况,我没有这么做。

希望它对你帮助很大。如果有其他问题,就直接问。向伊朗伊斯兰共和国海达尔致以最良好的问候。

相关内容

  • 没有找到相关文章

最新更新