Итеративно сглаживать кривую - PullRequest
7 голосов
/ 08 ноября 2010

Я пытался сделать это целый день. В принципе, у меня есть линия и точка. Я хочу, чтобы линия изогнулась и прошла через эту точку, но я не хочу плавной кривой. Я не хочу определять количество шагов в моей кривой, вот так (остерегайтесь грубого рисования mspaint): curve

И так далее. Я пробовал разные вещи, например, взять угол от центра исходной линии, а затем разделить линию в точке, где угол ведет, но у меня проблема с длиной. Я бы просто взял начальную длину и разделил ее на количество шагов, на которых я был, но это было не совсем правильно.

Кто-нибудь знает способ сделать это?

Спасибо.

Ответы [ 2 ]

7 голосов
/ 08 ноября 2010

Вы можете пойти другим путем: сначала найдите соответствующую кривую, а затем используйте точки на кривой, чтобы нарисовать линии. Например:

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 должно быть рациональным числом, и ваш выбор шагов ограничен. Вы можете выбрать шаги, проходящие через x1 и x2, начиная с x0. (они имеют вид x1 / (n * x2))

И это все. Теперь вы формируете свои линии в соответствии с точками {x, yLeft [x]} или {x, yRight [x]} в зависимости от того, на какой стороне x1 вы находитесь.

Примечание. Вы можете нарисовать только одну параболическую кривую, проходящую мимо ваших трех точек, но в общем случае она будет очень асимметричной.

Если точка x1 находится посередине, результат будет лучше:

alt text

4 голосов
/ 08 ноября 2010

Вам, вероятно, придется написать это самостоятельно. Я думаю, что вы могли бы сделать это, внедрив в код функцию квадратичной кривой Безье, которую можно найти здесь . Вы решаете, насколько хорошо вы хотите приращения, решая только для нескольких значений. Если вы хотите прямую линию, решите только для 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. Но в любом случае вы можете использовать один и тот же тип цикла для контроля степени плавности.

2nd Edit: похоже, работает. Просто измените элемент 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();
    }
}
...