迭代平滑一条曲线

7
我一整天都在尝试做这件事。基本上,我有一条直线和一个点。我想让这条直线弯曲并通过那个点,但我不想要平滑的曲线。我想能够定义我的曲线中步骤的数量,就像这样(请注意粗略的 mspaint 图片): curve 以此类推。我尝试了各种方法,例如从初始线的中心取角度,然后在角度导致的点处分割线条,但是我在长度方面有问题。我只会将初始长度除以我所在的步骤数,但那并不完全正确。
有人知道如何做到这一点吗?
谢谢。

2
我无法充分论述这个主题,但如果你查找贝塞尔曲线及其形成方式,你会得到很好的信息。虽然你可以像你提出的那样迭代,但是计算曲线的更好方法还有其他选择。 - Stefan Kendall
我希望能够控制曲线中的段数。 - Alex Turpin
曲线有无限数量的“线段”。您选择评估并在其之间呈现线条的点数仍由您决定。 - user164771
你用线条近似哪种曲线有关系吗?例如,你可以精确地定义一个穿过三个点的圆,但你总是希望你的曲线是圆的一部分吗?如果不是,还有无数其他曲线可供选择,每个曲线都会给出不同的结果,我认为迄今为止没有任何答案正确地解决了这个问题。 - Bob Sammers
2个回答

8
你可以选择另一种方式: 首先找到匹配的曲线,然后使用曲线上的点来绘制线条。例如: 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在中间,则结果更好。

5
您可能需要自己编写代码。我认为您可以通过在代码中实现二次贝塞尔曲线函数来实现,并且可以在这里找到该函数。只需求解少数值即可确定所需的细分程度。如果想要一条直线,只需求解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成员更改为你想要的所有线段的总数,并适当设置points数组即可。顺便说一句,你可以使用超过三个点。它只是将总线段数分配给它们。但我初始化了这个数组,以便结果看起来像你的最后一个例子。

第三次编辑:我稍微更新了代码,这样你就可以在窗体上左键点击添加点,右键点击删除最后一个点。此外,我还添加了一个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();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接