お気づきのように、スプラインはさまざまな長さの線分を生成します。曲線がきついほど、セグメントは短くなります。これは表示目的には問題ありませんが、モバイル用のパス生成にはあまり役立ちません。
スプライン パスの一定速度のトラバーサルの合理的な近似値を取得するには、曲線のセグメントに沿って補間を行う必要があります。一連の線分 ( によって返される点のペアの間Vector2.CatmullRom()
) が既にあるため、これらの線分を一定の速度で移動する方法が必要です。
ポイントのセットと、それらのポイント間の線として定義されたパスに沿って移動する合計距離が与えられると、次の (多かれ少なかれ疑似) コードは、パスに沿って特定の距離にあるポイントを見つけます。
Point2D WalkPath(Point2D[] path, double distance)
{
Point curr = path[0];
for (int i = 1; i < path.Length; ++i)
{
double dist = Distance(curr, path[i]);
if (dist < distance)
return Interpolate(curr, path[i], distance / dist;
distance -= dist;
curr = path[i];
}
return curr;
}
これを高速化するために実行できるさまざまな最適化があります。たとえば、パス内の各ポイントでパス距離を保存して、ウォーク操作中のルックアップを容易にするなどです。これは、パスが複雑になるほど重要になりますが、セグメントが数個しかないパスではやり過ぎになる可能性があります。
編集: これは、しばらく前に JavaScript でこのメソッドを使用した例です。これは概念実証なので、コードをあまり批判的に見ないでください :P
編集: スプライン生成の詳細
一連の「結び目」ポイント (曲線が順番に通過する必要があるポイント) が与えられると、曲線アルゴリズムに最も明白に適合するのは Catmull-Rom です。欠点は、CR には 2 つの追加のコントロール ポイントが必要であり、自動的に生成するのが面倒なことです。
しばらく前に、パス内の一連のポイントの位置に基づいて一連のコントロール ポイントを計算する、非常に役立つ記事をオンラインで見つけました (正確な帰属を示すために、もう見つけることができません)。コントロール ポイントを計算するメソッドの C# コードを次に示します。
// Calculate control points for Point 'p1' using neighbour points
public static Point2D[] GetControlsPoints(Point2D p0, Point2D p1, Point2D p2, double tension = 0.5)
{
// get length of lines [p0-p1] and [p1-p2]
double d01 = Distance(p0, p1);
double d12 = Distance(p1, p2);
// calculate scaling factors as fractions of total
double sa = tension * d01 / (d01 + d12);
double sb = tension * d12 / (d01 + d12);
// left control point
double c1x = p1.X - sa * (p2.X - p0.X);
double c1y = p1.Y - sa * (p2.Y - p0.Y);
// right control point
double c2x = p1.X + sb * (p2.X - p0.X);
double c2y = p1.Y + sb * (p2.Y - p0.Y);
// return control points
return new Point2D[] { new Point2D(c1x, c1y), new Point2D(c2x, c2y) };
}
このtension
パラメーターは、コントロール ポイントの生成を調整して、曲線のきつさを変更します。値を高くするとカーブが広くなり、値を低くするとカーブがきつくなります。それを試してみて、どの値があなたに最も適しているかを見てください.
「n」個のノット (曲線上の点) のセットが与えられると、ノットのペア間の曲線を生成するために使用される制御点のセットを生成できます。
// Generate all control points for a set of knots
public static List<Point2D> GenerateControlPoints(List<Point2D> knots)
{
if (knots == null || knots.Count < 3)
return null;
List<Point2D> res = new List<Point2D>();
// First control point is same as first knot
res.Add(knots.First());
// generate control point pairs for each non-end knot
for (int i = 1; i < knots.Count - 1; ++i)
{
Point2D[] cps = GetControlsPoints(knots[i - 1], knots[i], knots[i+1]);
res.AddRange(cps);
}
// Last control points is same as last knot
res.Add(knots.Last());
return res;
}
2*(n-1)
これでコントロール ポイントの配列ができました。これを使用して、ノット ポイント間の実際の曲線セグメントを生成できます。
public static Point2D LinearInterp(Point2D p0, Point2D p1, double fraction)
{
double ix = p0.X + (p1.X - p0.X) * fraction;
double iy = p0.Y + (p1.Y - p0.Y) * fraction;
return new Point2D(ix, iy);
}
public static Point2D BezierInterp(Point2D p0, Point2D p1, Point2D c0, Point2D c1, double fraction)
{
// calculate first-derivative, lines containing end-points for 2nd derivative
var t00 = LinearInterp(p0, c0, fraction);
var t01 = LinearInterp(c0, c1, fraction);
var t02 = LinearInterp(c1, p1, fraction);
// calculate second-derivate, line tangent to curve
var t10 = LinearInterp(t00, t01, fraction);
var t11 = LinearInterp(t01, t02, fraction);
// return third-derivate, point on curve
return LinearInterp(t10, t11, fraction);
}
// generate multiple points per curve segment for entire path
public static List<Point2D> GenerateCurvePoints(List<Point2D> knots, List<Point2D> controls)
{
List<Point2D> res = new List<Point2D>();
// start curve at first knot
res.Add(knots[0]);
// process each curve segment
for (int i = 0; i < knots.Count - 1; ++i)
{
// get knot points for this curve segment
Point2D p0 = knots[i];
Point2D p1 = knots[i + 1];
// get control points for this curve segment
Point2D c0 = controls[i * 2];
Point2D c1 = controls[i * 2 + 1];
// calculate 20 points along curve segment
int steps = 20;
for (int s = 1; s < steps; ++s)
{
double fraction = (double)s / steps;
res.Add(BezierInterp(p0, p1, c0, c1, fraction));
}
}
return res;
}
ノットに対してこれを実行すると、線の曲率に応じて距離が可変の補間ポイントのセットが得られます。これから、元の WalkPath メソッドを繰り返し実行して、一定の距離離れた点のセットを生成します。これにより、一定の速度で曲線に沿ったモバイルの進行が定義されます。
パスの任意のポイントでのモバイルの進行方向は、(おおよそ) 両側のポイント間の角度です。n
パス内の任意の点について、p[n-1]
との間の角度p[n+1]
が進行方向の角度です。
// get angle (in Radians) from p0 to p1
public static double AngleBetween(Point2D p0, Point2D p1)
{
return Math.Atan2(p1.X - p0.X, p1.Y - p0.Y);
}
ポイント演算、補間など、多くの機能が組み込まれている、何年も前に書いた Point2D クラスを使用しているため、上記のコードを変更しました。翻訳中にいくつかのバグを追加した可能性がありますが、うまくいけば、遊んでいるときに簡単に見つけることができます。
それがどうなるか教えてください。何か特定の問題が発生した場合は、私ができることを確認します。