SESEBLO

Unity, Clip Studio, etc.

Unityのベジェ曲線について(2) - 媒介変数を使わない値の取得

umeru.hatenablog.com

こちら(と元記事)で紹介しているベジェ曲線だが、tに応じて(x, y)を取得する形式になっている。

点Aから点Bまでの曲線を描く際にはこれで十分なのだが、例えばCSS3のtransitionのeasingの指定に使うときのような、xに対するyが必要な時もある。

そこで、こちらの136行あたりでやっていることをBezieクラスに追加してみた。

仕組みは単純で、おもむろにtを決めたのちそのtにおける(x, y)に対してニュートン法、二分法と試して方程式を解いている。

一応、コードを続きに。

  /** あるtについてxの微分値を求める
   */
private float sampleCurveDerivativeX(float t) {
    if (n == 2) { // 2次ベジェ
      return 2.0f * this.Ax * t + this.Bx;
    } else { // //3次ベジェ
      return (3.0f * this.Ax * t + 2.0f * this.Bx) * t + this.Cx;
    }
  }

  /** xを与えるとtについての方程式を求めてyを返す
   * 変なbezieじゃなければだいたい軽いはず
   */
  public float CalcYfromX(float x) {
    float epsilon = 1e-5f; // 閾値
    float x2, t0, t1, t2, d2, i;
    for(t2 = x, i = 0; i < 8; i++) {
      x2 = GetPointAtTime (t2).x - x;
      if (Mathf.Abs (x2) < epsilon) {
        return GetPointAtTime (t2).y;
      }
      d2 = sampleCurveDerivativeX (t2);
      if (Mathf.Abs (d2) < 1e-6f) {
        break;
      }
      t2 = t2 - x2 / d2;
    }
    t0 = 0f;
    t1 = 1f;
    t2 = x;
    if (t2 < t0) {
      return GetPointAtTime (t0).y;
    }
    if (t2 > t1) {
      return GetPointAtTime (t1).y;
    }
    while (t0 < t1) {
      x2 = GetPointAtTime (t2).x;
      if (Mathf.Abs (x2 - x) < epsilon) {
        return GetPointAtTime (t2).y;
      }
      if (x > x2) {
        t0 = t2;
      } else {
        t1 = t2;
      }
      t2 = (t1 - t0) * 0.5f + t0;
    }

    return GetPointAtTime (t2).y; // 失敗
  }

とりあえず作ったのでGetPointAtTime(t)してxもしくはyを使うような雑な感じ。