2

私が試しているテクニックについて、いくつかの説明が必要です。エンティティをポイントAからポイントBに移動しようとしていますが、エンティティを直線で移動させたくありません。

たとえば、エンティティがx:0、y:0に配置されていて、ポイントx:50、y:0に到達したい場合、エンティティをターゲットまで曲線で移動させたい場合、最大距離を想像します。離れているのはx:25 y:25なので、X上をターゲットに向かって移動しますが、y上のターゲットから離れています。

スプライン、曲線を含むいくつかのオプションを調査しましたが、私がその仕事をするだろうと思ったのはCatmullRom曲線です。使い方が少しわかりませんか?関数が返す補間であるものではなく、フレームごとにエンティティを移動する場所を知りたいです。使い方を教えていただければ幸いです。

私が見逃したより簡単な代替方法があれば、それらも聞いていただければ幸いです。

編集:

私がどのように曲線を取得しているかを示すために:

Vector2 blah = Vector2.CatmullRom(
    StartPosition, 
    new Vector2(StartPosition.X + 5, StartPosition.Y + 5), 
    new Vector2(StartPosition.X + 10, StartPosition.Y + 5), 
    /*This is the end position*/ 
    new Vector2(StartPosition.X + 15, StartPosition.Y), 0.25f);

最終的には、これらのポイントをその場で生成するというアイデアですが、現時点ではそれを解決しようとしています。

4

1 に答える 1

5

お気づきのように、スプラインはさまざまな長さの線分を生成します。曲線がきついほど、セグメントは短くなります。これは表示目的には問題ありませんが、モバイル用のパス生成にはあまり役立ちません。

スプライン パスの一定速度のトラバーサルの合理的な近似値を取得するには、曲線のセグメントに沿って補間を行う必要があります。一連の線分 ( によって返される点のペアの間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 クラスを使用しているため、上記のコードを変更しました。翻訳中にいくつかのバグを追加した可能性がありますが、うまくいけば、遊んでいるときに簡単に見つけることができます。

それがどうなるか教えてください。何か特定の問題が発生した場合は、私ができることを確認します。

于 2013-02-16T23:12:40.510 に答える