うにてぃブログ

主にUnityとC#に関する記事を書いていきます

【C#】ベジェ曲線を等間隔で分割したある点を取得する

以前作成したベジェ曲線では
例えば曲線を計算する t を10分割して各点の座標を表示すると下図のようになり等間隔で表示されない

f:id:hacchi_man:20201010144501p:plain:w300

using UnityEngine;
using System.Linq;

public class SampleMonoBehaviour : MonoBehaviour
{
    [SerializeField]
    private Transform[] _transforms;
 
    private void OnDrawGizmos()
    {
        if (_transforms == null || _transforms.Length <= 1)
            return;

        var a = _transforms.Select(t => t.position).ToArray();
        var c = new BezierCurve(a);
        var prev = _transforms[0].position;
        for (var i = 0; i <= 100; i++)
        {
            var pos = c.Eval(i / 100f);
            Gizmos.DrawLine(prev, pos);

            if (i % 10 == 0)
                Gizmos.DrawSphere(pos, 0.1f);
            prev = pos;
        }
    }
}

等間隔にしてみる

等間隔にするにはベジェ曲線の全体の距離をn分割して、距離の t地点 を取得し
t 地点となる Eval(i) の i 値を取得することで等間隔になる

    /// <summary>
    /// 等速で評価する
    /// </summary>
    public Vector3 ConstantEval(float t)
    {
        // t の地点の距離を取得
        var tl = Mathf.Lerp(0, Length, t);
        return Eval(GetT(tl));
    }
 
    /// <summary>
    /// 指定した地点の t を逆算
    /// </summary>
    /// <param name="tl"></param>
    /// <returns></returns>
    private float GetT(float tl)
    {
        var total = 0f;
        var prev = Eval(_positions, 0f);
        var c = Vector3.zero;
        var diff = Vector3.zero;
        // 長さに応じて分割数を変える
        var a = 0.01f / Length;
        for (var t = a; t < 1f; t += a)
        {
            c = Eval(_positions, t);
            diff = prev - c;
            total += (float)Math.Sqrt(diff.x * diff.x + diff.y * diff.y + diff.z * diff.z);
 
            if (total >= tl)
                return t;
 
            prev = c;
        }
        return 1f;
    }

これにより等間隔である地点を取得できる

f:id:hacchi_man:20201010145138p:plain:w300