私は一日中これをやろうとしてきました。基本的に、私は線と点を持っています。線が曲がってその点を通過するようにしたいのですが、滑らかな曲線は望んでいません。次のように、曲線のステップ数を定義したくありません (粗雑な mspaint の描画に注意してください)。
等々。最初の線の中心から角度をとって、その角度の先で線を分割するなど、いろいろ試しましたが、長さに問題があります。最初の長さを歩数で割りましたが、それは正しくありませんでした。
誰でもそれを行う方法を知っていますか?
ありがとう。
私は一日中これをやろうとしてきました。基本的に、私は線と点を持っています。線が曲がってその点を通過するようにしたいのですが、滑らかな曲線は望んでいません。次のように、曲線のステップ数を定義したくありません (粗雑な mspaint の描画に注意してください)。
等々。最初の線の中心から角度をとって、その角度の先で線を分割するなど、いろいろ試しましたが、長さに問題があります。最初の長さを歩数で割りましたが、それは正しくありませんでした。
誰でもそれを行う方法を知っていますか?
ありがとう。
逆の場合もあります。最初に一致する曲線を見つけてから、曲線上の点を使用して線を描画します。例えば:
このプロットは、次の方法で取得されました。
3つの開始点{x0,0}、{x1、y1}、{x2,0}があるとします。
次に、{x1、y1}で交差する2つの放物線が見つかり、その点で最大値を持つという追加の条件があります(スムーズな遷移のため)。それらの曲線は次のとおりです。
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}
2つの曲線があります。
ここで、ポイントを等間隔にする場合は、x1 / x2を有理数にする必要があり、ステップの選択肢が限られていることに注意してください。x0から開始しながら、x1とx2を通過するステップを選択できます。(これらはx1 /(n * x2)の形式です)
そしてそれがすべてです。ここで、x1のどちら側にいるかに応じて、点{x、yLeft [x]}または{x、yRight[x]}に従って線を形成します。
注:3つのポイントを通過する放物線を1つだけ描画することを選択できますが、一般的なケースでは非対称性が高くなります。
ポイントx1が中央にある場合、結果はより良くなります。
おそらく、これを自分でコーディングする必要があります。ここで見つけることができるコードで二次ベジエ曲線関数を実装することでそれを行うことができると思います。いくつかの値を解くだけで、増分をどれだけ細かくするかを決定します。直線が必要な場合は、0 と 1 のみを解いて、それらの点を線で結びます。1 つの角度の例が必要な場合は、0、0.5、および 1 について解き、点を順番に接続します。3 番目の例が必要な場合は、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の Web サイトが方程式の参考になるかもしれません。どちらの場合でも、同じタイプのループを使用して滑らかさの度合いを制御できます。
2回目の編集:これはうまくいくようです。numberOfSteps メンバーを必要なライン セグメントの総数に変更し、ポイント配列を適切に設定するだけです。ちなみに、3点以上使えます。線分の総数をそれらに分散するだけです。しかし、結果が最後の例のようになるように配列を初期化しました。
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();
}
}