ベジエ曲線に沿って等間隔に配置された点


10

しばらく探し回っていましたが、この問題の解決策が見つかりません。3次ベジェ曲線(4点で定義)があり、曲線に沿って等間隔に配置された一連の点を取得したいとします。例として、曲線に沿ってテキストを配置することを考えてください。

ここで問題はt、一定の増分で入力(0-1からの補間値)した場合、ポイントが等間隔に配置されないことです。カーブに沿った距離は、カーブが曲がる場合は短くなり、カーブが直線の場合は長くなります。

では、ベジェ曲線に沿って点を均等に配置するにはどうすればよいですか?


4
「純粋に数学的な」(または特に効率的な)ソリューションをお探しですか?それ以外の場合、直接的なアプローチは次のとおりです。曲線に沿って歩きt、たとえば100ステップずつ増やして、曲線をポリラインに変換し、結果のポイント間の距離を測定します。次に、必要に応じてこのポリラインに沿って補間します。
Marco13

この質問で例として回答されたキーワード「弧長パラメータ化」を探していると思います。
ワンドラ

@ Marco13が言ったこと!
david van brink 2015

回答/コメントによると、私が述べたアプローチは単純であるだけでなく、かなり一般的です。これは特定の言語用ですか?たぶん誰かが数行のコードを投稿するかもしれません...
Marco13

回答:


3

それは数学の問題です。したがって、ベジエ曲線xyコンポーネントの両方で次の式を持ちます。

B_x(t) = (1-t)^3 * P0_x + (1-t)^2 * t * P1_x + (1-t) * t^2 * P2_x + t^3 * P3_x
B_y(t) = (1-t)^3 * P0_y + (1-t)^2 * t * P1_y + (1-t) * t^2 * P2_x + t^3 * P3_y

t曲線に沿って移動する長さは、次の式gammaで与えられます。

length_gamma(t) = integration( sqrt( derivative(  gamma_x(s)  ) ^2 + derivative(  gamma_y(s)  ) ^2 ) )

積分に対する人間が書き込み可能なソリューションはないため、概算する必要があります。

取り替えgamma(t)式でB(t)長さを取得するlength_Bことにより、旅tベジェセグメントに沿っています。それがから0に移動するとしLます。

次に、等間隔のポイントに対応するとのn間の値を選択します。たとえば、フォームの長さをfromからに。0Lk*L/nk0n

次に、関数を逆にする必要があるlength_Bのでt、長さから背中を計算できますl。それはかなり多くの数学であり、私は地獄のように怠惰です、自分でやってみてください。できない場合は、数学のstackexchangeにアクセスできます。より完全な答えは、とにかくそこに行くことができます。

その逆length_B関数(または妥当な近似)を取得すると、処理は非常に簡単になります。

  • l[k]原点から離れた、指定されたパスの長さを作成します(P0_x,P1_x)
  • t[k]を使用して対応するものを計算しlength_B_inverseます。
  • を使用してポイントを配置し(B_x(t[k]),B_y(t[k]))ます。

1
ありがとうございました!バーンスタイン多項式を統合するために必要な数学は悪夢であることが判明しました。このソリューション
Foaly


0

マルコが言ったことを拡張すると、これを行うための一般的な手法は、必要な固定長のステップよりもはるかに小さい増分でカーブを下って行き、結果の出力ポイント(およびおそらく距離?)をテーブルに格納することです。

次に、テーブルを移動して、歩きたい距離の整数倍に最も近いポイントを除くすべてのエントリを破棄します。

次に、実行時に非常に迅速に直接インデックス付けできるテーブルが残ります。距離の5倍の大きさのスポットに行きたい場合は、インデックス[5]でテーブルを調べます。

2つのステップを1つで実行し、最初に追加のアイテムをテーブルに実際に格納することはできませんが、2つのステップで視覚化して理解する方が簡単です。

テーブルを事前に計算せずに実際にこれをその場で実際に計算する手法を見たことがあります(反復/ルート検索も使用していませんでした!)。残念ながら、詳細をまったく覚えていません)

覚えている、または見つけたら、情報を投稿します!


1
これはまた、あなたが興味を持つかもしれない:math.stackexchange.com/questions/15896/...
アラン・ウルフ

0

ステップ1-1 / Nの増分で曲線を補間してN + 1ポイントを生成します。Nは、視覚的に良好な結果を得るには十分な大きさであるが、簡単に計算するには十分に小さい必要があります。ほとんどの状況では50の値で問題ありませんが、特定のケースに合わせて調整する必要があります。これを「補間ポイント」と呼びます。

または、セグメントの短いリストを生成し、目的の最大セグメント長よりも長い各セグメントを再帰的に分割することもできます(最初に、開始が終了に非常に近いSカーブを考慮して、少なくとも4つのセグメントを生成する必要があります)。

ステップ2-補間されたポイントと各ポイント間の間隔を使用して、「ラインを歩く」。

Unityコードはここに残しておきます。

public static Vector2[] InterpolateBezier(Vector2 start, Vector2 startControlPoint,
    Vector2 end, Vector2 endControlPoint, int segments)
{
    Vector2[] interpolated = new Vector2[segments + 1];
    interpolated[0] = start;
    interpolated[segments] = end;

    float step = 1f / segments;
    for (int i = 1; i < segments; i++)
    {
        interpolated[i] = GetBezierPosition(start, startControlPoint, end,
            endControlPoint, i * step);
    }

    return interpolated;
}

public static Vector2 GetBezierPosition(Vector2 start, Vector2 startControlPoint,
    Vector2 end, Vector2 endControlPoint, float t)
{
    float omt = 1f - t;
    float omt2 = omt * omt;
    float t2 = t * t;

    return
        start * (omt2 * omt) +
        startControlPoint * (3f * omt2 * t) +
        endControlPoint * (3f * omt * t2) +
        end * (t2 * t);
}

public static List<Vector2> WalkLine(Vector2[] points, float spacing, float offset = 0)
{
    List<Vector2> result = new List<Vector2>();

    spacing = spacing > 0.00001f ? spacing : 0.00001f;

    float distanceNeeded = offset;
    while (distanceNeeded < 0)
    {
        distanceNeeded += spacing;
    }

    Vector2 current = points[0];
    Vector2 next = points[1];
    int i = 1;
    int last = points.Length - 1;
    while (true)
    {
        Vector2 diff = next - current;
        float dist = diff.magnitude;

        if (dist >= distanceNeeded)
        {
            current += diff * (distanceNeeded / dist);
            result.Add(current);
            distanceNeeded = spacing;
        }
        else if (i != last)
        {
            distanceNeeded -= dist;
            current = next;
            next = points[++i];
        }
        else
        {
            break;
        }
    }

    return result;
}

-1

これはかなり良い結果を与えるいくつかのアルゴリズムです:

  Point Index(float pos) const
  {
    int count = p.NumPoints();
    Vector val(0.0,0.0,0.0);
    for(int i=0;i<count;i++)
      { 
        val += bin(pos,i,count-1)*Vector(p.Points(i));
      }
    return Point(val);
  }
  float bin(float pos, int i, int n) const
  { 
    return float(ni(n,i)) * pow(double(pos), double(i))*pow(double(1.0-pos), double(n-i));
  }
  int ni(int n, int i) const
  {
    if (i==0) { return 1; }
    if (n==i) { return 1; }
    return ni(n-1,i-1)+ni(n-1,i);
  }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.