Centripetal Catmull-Romスプライン

コンピュータグラフィックスにおいて、**centripetal Catmull–Romスプライン**または**求心性Catmull–Romスプライン**（きゅうしんせいキャットマルロムスプライン）は、Edwin Catmullとによって定式化されたCatmull–Romスプライン

の発展形であり、BarryとGoldmanが提案した再帰的アルゴリズムを用いて求めることができる
。
このスプラインは補間スプライン（制御点を通過する曲線）の一種で、4つの制御点 \mathbf{P}_0, \mathbf{P}_1, \mathbf{P}_2, \mathbf{P}_3 によって定義され、曲線は \mathbf{P}_1 から \mathbf{P}_2 の間のみ描画される。

## 定義

\mathbf{P}_i = [x_i \quad y_i]^T とする。
点 \mathbf{P}_0, \mathbf{P}_1, \mathbf{P}_2, \mathbf{P}_3 と、ノットシーケンス t_0, t_1, t_2, t_3 によって定義される曲線セグメント \mathbf{C} について、centripetal Catmull–Romスプラインは以下の式で生成される：

: \mathbf{C} = \frac{t_2-t}{t_2-t_1}\mathbf{B}_1+\frac{t-t_1}{t_2-t_1}\mathbf{B}_2

ただし、

: \mathbf{B}_1 = \frac{t_{2}-t}{t_{2}-t_0}\mathbf{A}_1+\frac{t-t_0}{t_{2}-t_0}\mathbf{A}_2
: \mathbf{B}_2 = \frac{t_{3}-t}{t_{3}-t_1}\mathbf{A}_2+\frac{t-t_1}{t_{3}-t_1}\mathbf{A}_3
: \mathbf{A}_1 = \frac{t_{1}-t}{t_{1}-t_0}\mathbf{P}_0+\frac{t-t_0}{t_{1}-t_0}\mathbf{P}_1
: \mathbf{A}_2 = \frac{t_{2}-t}{t_{2}-t_1}\mathbf{P}_1+\frac{t-t_1}{t_{2}-t_1}\mathbf{P}_2
: \mathbf{A}_3 = \frac{t_{3}-t}{t_{3}-t_2}\mathbf{P}_2+\frac{t-t_2}{t_{3}-t_2}\mathbf{P}_3

また、

:t_{i+1} = \left[\sqrt{(x_{i+1}-x_i)^2+(y_{i+1}-y_i)^2}\right]^{\alpha} + t_i

ここで、\alpha は0から1の範囲のノットパラメータ、i = 0,1,2,3、t_0 = 0  である。
centripetal Catmull–Romスプラインでは、\alpha は 0.5である。
\alpha = 0 の場合に得られる曲線は、標準のuniform Catmull–Romスプラインであり、
\alpha = 1 の場合に得られるのは、chordal Catmull–Romスプラインである。

スプラインの方程式  \mathbf{A}_1, \mathbf{A}_2, \mathbf{A}_3, \mathbf{B}_1, \mathbf{B}_2, と  \mathbf{C} に t = t_1 を代入すると、t_1 におけるスプライン曲線の値は \mathbf{C} = \mathbf{P}_1 になる。
同様に、スプラインの方程式に t = t_2 を代入すると、t_2において  \mathbf{C} = \mathbf{P}_2  となる。
これは \alpha の値に依存せず常に成り立つ。なぜなら、点 t_1 および t_2 における \mathbf{C} の値の計算には t_{i+1} が必要ないためである。

この手法は、一般的な3Dポイント \mathbf{P}_i を \mathbf{P}_i = [x_i \quad y_i \quad z_i]^T とし、
:t_{i+1} = \left[\sqrt{(x_{i+1}-x_i)^2+(y_{i+1}-y_i)^2+(z_{i+1}-z_i)^2}\right]^{\alpha} + t_i
とすることで、3次元空間にも容易に拡張できる。

## 特長
centripetal Catmull–Romスプラインには、元のCatmull–Romスプラインや他のバリエーションと比べて、いくつかの望ましい数学的特性がある
。
第一に、曲線セグメント内でループや自己交差が発生しないこと、第二に、曲線セグメント内でカスプ（尖点）が発生しないこと、第三に、制御点に、より忠実に従うことである。

## その他の用途
コンピュータビジョンでは、centripetal Catmull–Romスプラインは、セグメンテーション用**アクティブスプラインモデル**の基盤として使用されている。このモデルはをベースとしており、連続する点どうしを直線ではなくcentripetal Catmull–Romスプラインで結ぶことで、必要な制御点の数を削減している。その結果、centripetal Catmull-Romスプラインの制御点を出力するよう訓練されたセグメンテーションモデルは、より迅速に収束し、生成される曲線は、線形ポリゴンや他の3次スプラインよりも編集が容易になる。

## Pythonコード例
以下は、下に示されたプロットを生成する、Catmull–RomスプラインのPythonでの実装である。


```numpy
import numpy
import matplotlib.pyplot as plt


QUADRUPLE_SIZE: int = 4


def num_segments(point_chain: tuple) -> int:
    # There is 1 segment per 4 points, so we must subtract 3 from the number of points  
    return len(point_chain) - (QUADRUPLE_SIZE - 1)


def flatten(list_of_lists) -> list:
    # E.g. mapping [[1, 2], [3], [4, 5]] to  [1, 2, 3, 4, 5] 
    return [elem for lst in list_of_lists for elem in lst]


def catmull_rom_spline(
    P0: tuple,
    P1: tuple,
    P2: tuple,
    P3: tuple,
    num_points: int,
    alpha: float = 0.5,
):
    """
    Compute the points in the spline segment
    :param P0, P1, P2, and P3: The (x,y) point pairs that define the Catmull-Rom spline
    :param num_points: The number of points to include in the resulting curve segment
    :param alpha: 0.5 for the centripetal spline, 0.0 for the uniform spline, 1.0 for the chordal spline.
    :return: The points
    """

    # Calculate t0 to t4. Then only calculate points between P1 and P2.
    # Reshape linspace so that we can multiply by the points P0 to P3
    # and get a point for each value of t.
    def tj(ti: float, pi: tuple, pj: tuple) -> float:
        xi, yi = pi
        xj, yj = pj
        dx, dy = xj - xi, yj - yi
        l = (dx ** 2 + dy ** 2) ** 0.5
        return ti + l ** alpha

    t0: float = 0.0
    t1: float = tj(t0, P0, P1)
    t2: float = tj(t1, P1, P2)
    t3: float = tj(t2, P2, P3)
    t = numpy.linspace(t1, t2, num_points).reshape(num_points, 1)

    A1 = (t1 - t) / (t1 - t0) * P0 + (t - t0) / (t1 - t0) * P1
    A2 = (t2 - t) / (t2 - t1) * P1 + (t - t1) / (t2 - t1) * P2
    A3 = (t3 - t) / (t3 - t2) * P2 + (t - t2) / (t3 - t2) * P3
    B1 = (t2 - t) / (t2 - t0) * A1 + (t - t0) / (t2 - t0) * A2
    B2 = (t3 - t) / (t3 - t1) * A2 + (t - t1) / (t3 - t1) * A3
    points = (t2 - t) / (t2 - t1) * B1 + (t - t1) / (t2 - t1) * B2
    return points


def catmull_rom_chain(points: tuple, num_points: int) -> list:
    """
    Calculate Catmull-Rom for a sequence of initial points and return the combined curve.
    :param points: Base points from which the quadruples for the algorithm are taken
    :param num_points: The number of points to include in each curve segment
    :return: The chain of all points (points of all segments)
    """
    point_quadruples = (  # Prepare function inputs
        (points[idx_segment_start + d] for d in range(QUADRUPLE_SIZE))
        for idx_segment_start in range(num_segments(points))
    )
    all_splines = (catmull_rom_spline(*pq, num_points) for pq in point_quadruples)
    return flatten(all_splines)


if __name__ == "__main__":
    POINTS: tuple = ((0, 1.5), (2, 2), (3, 1), (4, 0.5), (5, 1), (6, 2), (7, 3))  # Red points
    NUM_POINTS: int = 100  # Density of blue chain points between two red points

    chain_points: list = catmull_rom_chain(POINTS, NUM_POINTS)
    assert len(chain_points) == num_segments(POINTS) * NUM_POINTS  # 400 blue points for this example

    plt.plot(*zip(*chain_points), c="blue")
    plt.plot(*zip(*POINTS), c="red", linestyle="none", marker="o")
    plt.show()
```


## Unity C# コード例
Unityゲームエンジン用のC# コード例。

```csharp
using UnityEngine;

// a single Catmull–Rom curve
public struct CatmullRomCurve
{
	public Vector2 p0, p1, p2, p3;
	public float alpha;

	public CatmullRomCurve(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float alpha)
    {
		(this.p0, this.p1, this.p2, this.p3) = (p0, p1, p2, p3);
		this.alpha = alpha;
	}

	// Evaluates a point at the given t-value from 0 to 1
	public Vector2 GetPoint(float t)
    {
		// calculate knots
		const float k0 = 0;
		float k1 = GetKnotInterval(p0, p1);
		float k2 = GetKnotInterval(p1, p2) + k1;
		float k3 = GetKnotInterval(p2, p3) + k2;

		// evaluate the point
		float u = Mathf.LerpUnclamped(k1, k2, t);
		Vector2 A1 = Remap(k0, k1, p0, p1, u);
		Vector2 A2 = Remap(k1, k2, p1, p2, u);
		Vector2 A3 = Remap(k2, k3, p2, p3, u);
		Vector2 B1 = Remap(k0, k2, A1, A2, u);
		Vector2 B2 = Remap(k1, k3, A2, A3, u);
		return Remap(k1, k2, B1, B2, u);
	}

	static Vector2 Remap(float a, float b, Vector2 c, Vector2 d, float u)
    {
		return Vector2.LerpUnclamped(c, d, (u - a) / (b - a));
	}

	float GetKnotInterval(Vector2 a, Vector2 b)
    {
		return Mathf.Pow(Vector2.SqrMagnitude(a - b), 0.5f * alpha);
	}
}
```



```csharp
using UnityEngine;

// Draws a Catmull–Rom spline in the scene view,
// along the child objects of the transform of this component
public class CatmullRomSpline : MonoBehaviour
{
	[Range(0, 1)]
    public float alpha = 0.5f;
	int PointCount => transform.childCount;
	int SegmentCount => PointCount - 3;
	Vector2 GetPoint(int i) => transform.GetChild(i).position;

	CatmullRomCurve GetCurve(int i)
    {
		return new CatmullRomCurve(GetPoint(i), GetPoint(i+1), GetPoint(i+2), GetPoint(i+3), alpha);
	}

	void OnDrawGizmos()
    {
		for (int i = 0; i < SegmentCount; i++)
			DrawCurveSegment(GetCurve(i));
	}

	void DrawCurveSegment(CatmullRomCurve curve)
    {
		const int detail = 32;
		Vector2 prev = curve.p1;

		for (int i = 1; i < detail; i++)
        {
			float t = i / (detail - 1f);
			Vector2 pt = curve.GetPoint(t);
			Gizmos.DrawLine(prev, pt);
			prev = pt;
		}
	}
}
```


## Unreal C++ コード例

```c++
float GetT( float t, float alpha, const FVector& p0, const FVector& p1 )
{
    auto d  = p1 - p0;
    float a = d | d; // Dot product
    float b = FMath::Pow( a, alpha*.5f );
    return (b + t);
}

FVector CatmullRom( const FVector& p0, const FVector& p1, const FVector& p2, const FVector& p3, float t /* between 0 and 1 */, float alpha=.5f /* between 0 and 1 */ )
{
    float t0 = 0.0f;
    float t1 = GetT( t0, alpha, p0, p1 );
    float t2 = GetT( t1, alpha, p1, p2 );
    float t3 = GetT( t2, alpha, p2, p3 );
    t = FMath::Lerp( t1, t2, t );
    FVector A1 = ( t1-t )/( t1-t0 )*p0 + ( t-t0 )/( t1-t0 )*p1;
    FVector A2 = ( t2-t )/( t2-t1 )*p1 + ( t-t1 )/( t2-t1 )*p2;
    FVector A3 = ( t3-t )/( t3-t2 )*p2 + ( t-t2 )/( t3-t2 )*p3;
    FVector B1 = ( t2-t )/( t2-t0 )*A1 + ( t-t0 )/( t2-t0 )*A2;
    FVector B2 = ( t3-t )/( t3-t1 )*A2 + ( t-t1 )/( t3-t1 )*A3;
    FVector C  = ( t2-t )/( t2-t1 )*B1 + ( t-t1 )/( t2-t1 )*B2;
    return C;
}
```


## 関連項目
* Catmull–Romスプライン曲線
* 3次エルミートスプライン

## 脚注

## 外部リンク
*  Catmull-Rom curve with no cusps and no self-intersections implementation in Java
* Catmull-Rom curve with no cusps and no self-intersections simplified implementation in C++
* Catmull-Rom splines interactive generation via Python, in a Jupyter notebook
* Smooth Paths Using Catmull-Rom Splines another versatile implementation in C++ including centripetal CR splines