迭代地平滑曲线

时间:2010-11-08 02:21:01

标签: c# algorithm geometry curve-fitting

我一整天都在尝试这样做。基本上,我有一条线和一条点。我希望线条曲线并通过该点,但我不想要平滑的曲线。我不能像我这样定义曲线中的步数(谨防粗略的mspaint绘图): curve

等等。我尝试了各种各样的东西,比如从初始线的中心取角度然后在角度引导的点处分割线,但是我的长度有问题。我会把初始长度除以我所处的步数,但这不太对。

任何人都知道这样做的方法吗?

感谢。

2 个答案:

答案 0 :(得分:7)

你可以反过来:首先找到匹配的曲线,然后使用曲线上的点绘制线条。例如:

alt text

该图以下列方式获得:

假设您有三个起点{x0,0},{x1,y1},{x2,0}

然后你会发现在{x1,y1}处相交的两条抛物线曲线,其附加条件是在该点处具有最大值(用于平滑过渡)。这些曲线是:

 yLeft[x_] := a x^2 + b x + c; 
 yRight[x_] := d x^2 + e x + f;

我们发现(在一些微积分之后):

   {c -> -((-x0^2 y1 + 2 x0 x1 y1)/(x0 - x1)^2), 
    a -> -(y1/(x0 - x1)^2), 
    b -> (2 x1 y1)/(-x0 + x1)^2}  

   {f -> -((2 x1 x2 y1 - x2^2 y1)/(x1 - x2)^2), 
    d -> -(y1/(x1 - x2)^2), 
    e -> (2 x1 y1)/(x1 - x2)^2}

所以我们有两条曲线。

现在您应该注意,如果您希望您的点间距相等,则x1 / x2应该是一个有理数。并且您对步骤的选择是有限的。您可以选择从x0开始经过x1和x2的步骤。 (形式为x1 /(n * x2))

就是这样。现在你根据点{x,yLeft [x]}或{x,yRight [x]}形成你的线,这取决于你是x1的哪一边。

注意:您可以选择仅绘制一条通过三点的抛物线,但在一般情况下会产生高度不对称。

如果点x1在中间,结果更好:

alt text

答案 1 :(得分:4)

您可能需要自己编写代码。我认为你可以通过在代码中实现二次贝塞尔曲线函数来实现,可以找到here。只需求解一些值,即可决定增量的精确程度。如果你想要一条直线,只求解0和1并用线连接这些点。如果您想要一个角度示例,请求解0,0.5和1并按顺序连接点。如果你想要你的第三个例子,求解0,0.25,0.5,0.75和1.最好把它放在这样的for循环中:

float stepValue = (float)0.25;
float lastCalculatedValue;
for (float t = 0; t <= 1; t += stepValue)
{
    // Solve the quadratic bezier function to get the point at t.
    // If this is not the first point, connect it to the previous point with a line.
    // Store the new value in lastCalculatedValue.
}

编辑:实际上,看起来您希望线路通过您的控制点。如果是这种情况,则不希望使用二次贝塞尔曲线。相反,您可能需要拉格朗日曲线。该网站可能对等式有所帮助:http://www.math.ucla.edu/~baker/java/hoefer/Lagrange.htm。但在任何一种情况下,您都可以使用相同类型的循环来控制平滑度。

第二编辑:这似乎有效。只需将numberOfSteps成员更改为所需的线段总数,并相应地设置点数组。顺便说一句,你可以使用三个以上的点。它只会在它们之间分配线段的总数。但是我初始化了数组,结果看起来就像你的最后一个例子。

第3次编辑:我稍微更新了代码,因此您可以左键单击表单以添加点,然后右键单击以删除最后一个点。另外,我在底部添加了一个NumericUpDown,因此您可以在运行时更改段数。

public class Form1 : Form
{
    private int numberOfSegments = 4;

    private double[,] multipliers;
    private List<Point> points;

    private NumericUpDown numberOfSegmentsUpDown;

    public Form1()
    {
        this.numberOfSegmentsUpDown = new NumericUpDown();
        this.numberOfSegmentsUpDown.Value = this.numberOfSegments;
        this.numberOfSegmentsUpDown.ValueChanged += new System.EventHandler(this.numberOfSegmentsUpDown_ValueChanged);
        this.numberOfSegmentsUpDown.Dock = DockStyle.Bottom;
        this.Controls.Add(this.numberOfSegmentsUpDown);

        this.points = new List<Point> { 
            new Point(100, 110), 
            new Point(50, 60), 
            new Point(100, 10)};

        this.PrecomputeMultipliers();
    }

    public void PrecomputeMultipliers()
    {
        this.multipliers = new double[this.points.Count, this.numberOfSegments + 1];

        double pointCountMinusOne = (double)(this.points.Count - 1);

        for (int currentStep = 0; currentStep <= this.numberOfSegments; currentStep++)
        {
            double t = currentStep / (double)this.numberOfSegments;

            for (int pointIndex1 = 0; pointIndex1 < this.points.Count; pointIndex1++)
            {
                double point1Weight = pointIndex1 / pointCountMinusOne;

                double currentMultiplier = 1;
                for (int pointIndex2 = 0; pointIndex2 < this.points.Count; pointIndex2++)
                {
                    if (pointIndex2 == pointIndex1)
                        continue;

                    double point2Weight = pointIndex2 / pointCountMinusOne;
                    currentMultiplier *= (t - point2Weight) / (point1Weight - point2Weight);
                }

                this.multipliers[pointIndex1, currentStep] = currentMultiplier;
            }
        }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        Point? previousPoint = null;
        for (int currentStep = 0; currentStep <= numberOfSegments; currentStep++)
        {
            double sumX = 0;
            double sumY = 0;
            for (int pointIndex = 0; pointIndex < points.Count; pointIndex++)
            {
                sumX += points[pointIndex].X * multipliers[pointIndex, currentStep];
                sumY += points[pointIndex].Y * multipliers[pointIndex, currentStep];
            }

            Point newPoint = new Point((int)Math.Round(sumX), (int)Math.Round(sumY));

            if (previousPoint.HasValue)
                e.Graphics.DrawLine(Pens.Black, previousPoint.Value, newPoint);

            previousPoint = newPoint;
        }

        for (int pointIndex = 0; pointIndex < this.points.Count; pointIndex++)
        {
            Point point = this.points[pointIndex];
            e.Graphics.FillRectangle(Brushes.Black, new Rectangle(point.X - 1, point.Y - 1, 2, 2));
        }
    }

    protected override void OnMouseClick(MouseEventArgs e)
    {
        base.OnMouseClick(e);

        if (e.Button == MouseButtons.Left)
        {
            this.points.Add(e.Location);
        }
        else
        {
            this.points.RemoveAt(this.points.Count - 1);
        }

        this.PrecomputeMultipliers();
        this.Invalidate();
    }

    private void numberOfSegmentsUpDown_ValueChanged(object sender, EventArgs e)
    {
        this.numberOfSegments = (int)this.numberOfSegmentsUpDown.Value;
        this.PrecomputeMultipliers();
        this.Invalidate();
    }
}