2010-11-08 21 views
7

He estado tratando de hacer esto todo el día. Básicamente, tengo una línea y un punto. Quiero que la línea se curve y pase por ese punto, pero no quiero una curva suave. Quiero poder definir el número de pasos en mi curva, así que (cuidado con el dibujo crudo de mspaint): curveIterativamente suavizar una curva

Y así sucesivamente. Probé varias cosas, como tomar el ángulo desde el centro de la línea inicial y luego dividir la línea en el punto donde el ángulo conduce, pero tengo un problema con la longitud. Simplemente tomaría la longitud inicial y la dividiría por el número de pasos en que estaba, pero no estaba del todo bien.

¿Alguien sabe una manera de hacer eso?

Gracias.

+1

que no podía hacer el tema de justicia, sino que obtendrá una buena información si miras hacia arriba las curvas de Bezier y cómo para formarlos. Podría repetir como sugiera, pero existen mejores métodos para calcular curvas. –

+0

Quiero ser capaz de controlar el número de segmentos en la curva. –

+0

La curva tiene un número infinito de 'segmentos'. La cantidad de puntos que elijas evaluar y luego renderizar líneas depende de ti. –

Respuesta

4

Probablemente necesite codificar esto usted mismo. Creo que podría hacerlo implementando una función de curva de bezier cuadrática en el código, que se puede encontrar here. Usted decide qué tan bien desea los incrementos resolviendo solo unos pocos valores. Si quieres una línea recta, solo resuelve para 0 y 1 y conecta esos puntos con líneas. Si desea el ejemplo de un ángulo, resuelva para 0, 0.5 y 1 y conecte los puntos en orden. Si desea que su tercer ejemplo, resolver durante 0, 0,25, 0,5, 0,75 y 1. Probablemente sería mejor ponerlo en un bucle como este:

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. 
} 

Editar: En realidad, parece que quiere que la línea pase a través de su punto de control. Si ese es el caso, no desea usar una curva de bezier cuadrática. En cambio, es probable que desee una curva de Lagrange. Este sitio web podría ayudar con la ecuación: http://www.math.ucla.edu/~baker/java/hoefer/Lagrange.htm. Pero en cualquier caso, puede usar el mismo tipo de bucle para controlar el grado de suavidad.

2nd Edit: Parece que funciona. Simplemente cambie el miembro numberOfSteps para que sea el número total de segmentos de línea que desea y configure la matriz de puntos de manera apropiada. Por cierto, puedes usar más de tres puntos. Simplemente distribuirá el número total de segmentos de línea a través de ellos. Pero inicialicé la matriz para que el resultado se vea como tu último ejemplo.

3ra edición: Actualicé el código un poco para que pueda hacer clic izquierdo en el formulario para agregar puntos y haga clic con el botón derecho para eliminar el último punto. Además, agregué un NumericUpDown en la parte inferior para que pueda cambiar la cantidad de segmentos en tiempo de ejecución.

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(); 
    } 
} 
+0

¡Muchas gracias, funciona a la perfección! –

7

usted podría ir al revés: En primer lugar encontramos una curva de juego y luego utilizar los puntos de la curva para dibujar las líneas. Por ejemplo:

alt text

se obtuvo Esta parcela de la siguiente manera:

Suponga que tiene los tres puntos de partida x0,0 {}, {x1, y1}, {} x2,0

Luego encontrará dos curvas parabólicas que se cruzan en {x1, y1}, con la condición adicional de tener un máximo en ese punto (para una transición suave).Esas curvas son:

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

donde nos encontramos (después de algún cálculo):

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

y

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

por lo que tienen nuestros dos curvas.

Ahora debe tener en cuenta que si desea que sus puntos estén igualmente espaciados, x1/x2 debe ser un número racional. Y sus opciones de pasos son limitadas. Puede elegir los pasos que pasan por x1 Yx2 mientras comienza desde x0. (esos son de la forma x1/(n * x2))

Y eso es todo. Ahora forma sus líneas de acuerdo con los puntos {x, yLeft [x]} o {x, yRight [x]}, dependiendo de en qué lado de x1 se encuentre.

Nota: Puede optar por dibujar una sola curva parabólica que pase por sus tres puntos, pero resultará altamente asimétrica en el caso general.

Si el punto x1 está en el medio, los resultados son más agradables:

alt text

+0

¡Gracias por la respuesta! –

Cuestiones relacionadas