麻烦计算贝塞尔剪裁中的距离函数

时间:2012-04-16 23:47:22

标签: java graphics geometry processing bezier

我试图实现一种称为贝塞尔裁剪的曲线截面算法,这在this article末尾的一节中有所描述(虽然文章称之为"胖线裁剪&#34 )。我一直在浏览该示例的文章和源代码(可用here)。

注意:其他来源包括this paper。如果我能找到它们,会发布更多内容。

该算法的核心部分是计算距离函数"在curve1和a" baseline"之间curve2(从曲线2的一个端点到另一个端点的直线)。所以我有一些东西要比较我的结果,我使用了第一个例子的源代码中的曲线。我设法从示例中复制了距离函数的 shape ,但函数的距离位置已关闭。在尝试另一条曲线时,距离函数远不及其他两条曲线,尽管两条曲线明显相交。我可能对这个算法的运作很天真,但我认为这会导致没有检测到交集。

根据我的理解(这很可能是错误的),定义距离函数的过程涉及以 x a + y b + c的形式表示曲线2的基线= 0 ,其中 a 2 + b 2 = 1 。通过以 y = u x + v 的形式重新排列行的术语来获得系数,其中 u 等于斜率,x和y是基线上的任何点。公式可以重新排列,以提供 v v = y - u x 。再次重新排列公式,我们得到 -u * x + 1 * y - v = 0 ,其中 a = -u b = 1 ,和 c = -v 。为了确保条件 a 2 + b 2 = 1 ,系数除以标点 Math.sqrt(u < em> u + 1) 。然后将该线的表示代入另一条曲线的函数(基线不与之相关联的函数)以获得距离函数。该距离函数表示为贝塞尔曲线, y i = a P ix + b * P < sub> iy + c x i =(1 - t) x1 + t x2 ,其中< strong> t 等于 0,1 / 3,2 / 3, 3 m x1 x2 是基线的终点, P i 是curve1的控制点。

以下是计算距离函数所涉及的示例程序源代码(用语言处理编写)的一些切割,奇怪的是,使用稍微不同的方法来计算上段的替代表示。基线。

/**
 * Set up four points, to form a cubic curve, and a static curve that is used for intersection checks
 */
void setupPoints()
{
points = new Point[4];
points[0] = new Point(85,30);
points[1] = new Point(180,50);
points[2] = new Point(30,155);
points[3] = new Point(130,160);

curve = new Bezier3(175,25,  55,40,  140,140,  85,210);
curve.setShowControlPoints(false);
}

...

flcurve = new Bezier3(points[0].getX(), points[0].getY(),
                                        points[1].getX(), points[1].getY(),
                                        points[2].getX(), points[2].getY(),
                                        points[3].getX(), points[3].getY());

...

void drawClipping()
{
    double[] bounds = flcurve.getBoundingBox();

    // get the distances from C1's baseline to the two other lines
    Point p0 = flcurve.points[0];
    // offset distances from baseline
    double dx = p0.x - bounds[0];
    double dy = p0.y - bounds[1];
    double d1 = sqrt(dx*dx+dy*dy);
    dx = p0.x - bounds[2];
    dy = p0.y - bounds[3];
    double d2 = sqrt(dx*dx+dy*dy);

    ...

    double a, b, c;
a = dy / dx;
b = -1;
c = -(a * flcurve.points[0].x - flcurve.points[0].y);
// normalize so that a² + b² = 1
double scale = sqrt(a*a+b*b);
a /= scale; b /= scale; c /= scale;     

// set up the coefficients for the Bernstein polynomial that
// describes the distance from curve 2 to curve 1's baseline
double[] coeff = new double[4];
for(int i=0; i<4; i++) { coeff[i] = a*curve.points[i].x + b*curve.points[i].y + c; }
double[] vals = new double[4];
for(int i=0; i<4; i++) { vals[i] = computeCubicBaseValue(i*(1/3), coeff[0], coeff[1], coeff[2], coeff[3]); }

translate(0,100);

...

// draw the distance Bezier function
double range = 200;
for(float t = 0; t<1.0; t+=1.0/range) {
    double y = computeCubicBaseValue(t, coeff[0], coeff[1], coeff[2], coeff[3]);
    params.drawPoint(t*range, y, 0,0,0,255); }

...

translate(0,-100);
}

...

/**
 * compute the value for the cubic bezier function at time=t
 */
double computeCubicBaseValue(double t, double a, double b, double c, double d) {
double mt = 1-t;
return mt*mt*mt*a + 3*mt*mt*t*b + 3*mt*t*t*c + t*t*t*d; }

这是我写的用于重新创建上述代码的类(javax.swing.JPanel的扩展):

package bezierclippingdemo2;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

public class ReplicateBezierClippingPanel extends JPanel {

CubicCurveExtended curve1, curve2;

public ReplicateBezierClippingPanel(CubicCurveExtended curve1, CubicCurveExtended curve2) {

    this.curve1 = curve1;
    this.curve2 = curve2;

}

public void paint(Graphics g) {

    super.paint(g);
    Graphics2D g2d = (Graphics2D) g;
    g2d.setStroke(new BasicStroke(1));
    g2d.setColor(Color.black);
    drawCurve1(g2d);
    drawCurve2(g2d);
    drawDistanceFunction(g2d);

}

public void drawCurve1(Graphics2D g2d) {

    double range = 200;

    double t = 0;

    double prevx = curve1.x1*(1 - t)*(1 - t)*(1 - t) + 3*curve1.ctrlx1*(1 - t)*(1 - t)*t + 3*curve1.ctrlx2*(1 - t)*t*t + curve1.x2*t*t*t;
    double prevy = curve1.y1*(1 - t)*(1 - t)*(1 - t) + 3*curve1.ctrly1*(1 - t)*(1 - t)*t + 3*curve1.ctrly2*(1 - t)*t*t + curve1.y2*t*t*t;

    for(t += 1.0/range; t < 1.0; t += 1.0/range) {

        double x = curve1.x1*(1 - t)*(1 - t)*(1 - t) + 3*curve1.ctrlx1*(1 - t)*(1 - t)*t + 3*curve1.ctrlx2*(1 - t)*t*t + curve1.x2*t*t*t;
        double y = curve1.y1*(1 - t)*(1 - t)*(1 - t) + 3*curve1.ctrly1*(1 - t)*(1 - t)*t + 3*curve1.ctrly2*(1 - t)*t*t + curve1.y2*t*t*t;

        g2d.draw(new LineExtended(prevx, prevy, x, y));

        prevx = x;
        prevy = y;

    }

}

public void drawCurve2(Graphics2D g2d) {

    double range = 200;

    double t = 0;

    double prevx = curve2.x1*(1 - t)*(1 - t)*(1 - t) + 3*curve2.ctrlx1*(1 - t)*(1 - t)*t + 3*curve2.ctrlx2*(1 - t)*t*t + curve2.x2*t*t*t;
    double prevy = curve2.y1*(1 - t)*(1 - t)*(1 - t) + 3*curve2.ctrly1*(1 - t)*(1 - t)*t + 3*curve2.ctrly2*(1 - t)*t*t + curve2.y2*t*t*t;

    for(t += 1.0/range; t < 1.0; t += 1.0/range) {

        double x = curve2.x1*(1 - t)*(1 - t)*(1 - t) + 3*curve2.ctrlx1*(1 - t)*(1 - t)*t + 3*curve2.ctrlx2*(1 - t)*t*t + curve2.x2*t*t*t;
        double y = curve2.y1*(1 - t)*(1 - t)*(1 - t) + 3*curve2.ctrly1*(1 - t)*(1 - t)*t + 3*curve2.ctrly2*(1 - t)*t*t + curve2.y2*t*t*t;

        g2d.draw(new LineExtended(prevx, prevy, x, y));

        prevx = x;
        prevy = y;

    }

}

public void drawDistanceFunction(Graphics2D g2d) {

    double a = (curve1.y2 - curve1.y1)/(curve1.x2 - curve1.x1);
    double b = -1;
    double c = -(a*curve1.x1 - curve1.y1);

    double scale = Math.sqrt(a*a + b*b);

    a /= scale;
    b /= scale;
    c /= scale;

    double y1 = a*curve2.x1 + b*curve2.y1 + c;
    double y2 = a*curve2.ctrlx1 + b*curve2.ctrly1 + c;
    double y3 = a*curve2.ctrlx1 + b*curve2.ctrly2 + c;
    double y4 = a*curve2.x2 + b*curve2.y2 + c;

    double range = 200;
    double t = 0;
    double prevx = t*range;
    double prevy = (1 - t)*(1 - t)*(1 - t)*y1 + 3*(1 - t)*(1 - t)*t*y2 + 3*(1 - t)*t*t*y3 + t*t*t*y4;

    for(t += 1.0/range; t < 1.0; t += 1.0/range) {

        double x = t*range;
        double y = (1 - t)*(1 - t)*(1 - t)*y1 + 3*(1 - t)*(1 - t)*t*y2 + 3*(1 - t)*t*t*y3 + t*t*t*y4;

        g2d.draw(new LineExtended(prevx, prevy, x, y));

        prevx = x;
        prevy = y;

    }
}



}

其中CubicCurveExtended和LineExtended是java.awt.geom.CubicCurve2D.Double和java.awt.geom.Line2D.Double的次要扩展。在将曲线传递到构造函数之前,曲线是均匀旋转的,因此curve1的端点是水平的,导致基线的斜率为零。

对于曲线1的输入(485,430,580,60,430,115,530,160)和曲线2的(575,25,455,60,541,140,​​486,210)(保持在请注意,这些值会被曲线1的端点之间的负角旋转,结果如下所示(距离函数是距离相对平滑的曲线):

enter image description here

我真的不确定自己错了什么。 y值似乎以正确的模式排列,但远离它所基于的两条曲线。我意识到我可能有x值可能沿曲线而不是基线排列,但y值是我真正感到困惑的。如果有人可以看一看并解释我的错误,我真的很感激。感谢您抽出宝贵时间阅读这个相当冗长的问题。如果需要更多细节,请随时在评论中告诉我。

更新:我已经测试了我计算过的线的表示。 a x + b y + c = 0表示显然仍然代表相同的行,因为我仍然可以插入x1并获得y1。另外,对于插入函数的任何两个坐标对,f(x,y)= 0成立。此外,我发现文章中描述的表示和源代码中实际使用的表示可互换地表示同一行。从这一切,我可以假设问题并不在于计算线。额外的兴趣点

1 个答案:

答案 0 :(得分:1)

您的距离函数不一定要靠近两条原始曲线:它使用完全不同的坐标系,即t与D,而不是使用x和y的原始曲线。 [编辑]即t仅上升到1.0,并测量沿着曲线的总长度比例,以及D测量曲线2距曲线1基线的距离。

另外,当你说曲线1和曲线2的“基线”之间的“距离函数”时,我认为你已经将曲线1和曲线2混合在一起,就像你的代码一样,你明显使用了曲线1的基线。

此算法假设“每个Bézier曲线完全由连接所有起点/控制/终点的多边形所包含,称为”凸壳“,在你的情况下,[curve]为curve1是一个三角形,其中第二个起始值的控制点不是顶点。我不确定这会如何影响算法。

否则,你的距离计算看起来很好(尽管你可以真正优化一些事情:))。