Catmull-Romスプライン

Catmull-Romスプライン

Edwin CatmullとRaphael Romにより開発された曲線補間ですが、
Edwin Catmullはピクサー・アニメーション・スタジオの現社長だそうです。

私がCatmullのスプラインを最初に調べたのは、8年も前になりますが、
そのころは記事や論文がほとんどなくて苦労した記憶があります。

3次のB-Spline(Cardinal)に比べてやや鋭角な印象があります。
その分、Createrの意思が反映できます。
正確なデザインは、Bezierを使った方が良いですが、
ラフなデザインや、流線型に限れば簡単で重宝します。

今回も簡単にjavascriptでプログラムしてみます。
Opener(開曲線)なカーブは、検索すればいくらでもありますので、
Closed(閉曲線)なカーブを描いてみます。

とは言っても、行列式は決まっているので、
媒介変数を使って2価関数を許し、始点・終点の節点を調節するだけです。

Catmull-Splineでは4つの節点から、3次の補間式を作るので、
始点を、P[n-1],P[0],P[1],P[2]から作り、
終点は、P[n-2],P[n-1],P[0],P[1]から作ります。

関数catmullCircuitに、節点のX,Y座標列を与えると、
節点間の20個の補間座標を返します。

実行サンプルはこちら

<!DOCTYPE html>
<html>
<!-- 
// (c)Hundredsoft Corporation. 2013 All right reserved.
//
//	UTF-8で保存して下さい
-->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
<title>CatMull-Spline Circuit</title>

<script type="text/javascript">
new function() {

var m_x = [];
var m_y = [];

var draw = function(w, h){
	m_ctx.clearRect(0, 0, w, h);

	m_ctx.lineJoin = "round";
	m_ctx.lineCap = "round";
	m_ctx.lineWidth = 2;
	m_ctx.strokeStyle = "#000000";

	var n = m_x.length;
	var i;
	// 節点の描画
	for (i=0; i<n; i++){
		m_ctx.beginPath();
		m_ctx.arc(m_x[i], m_y[i], 4, 0, Math.PI*2, false);
		m_ctx.fill();
		m_ctx.closePath();
	}
	if (n > 2){
		var cc = catmullCircuit(m_x, m_y);
		var nc = cc.x.length;

		// スプライン描画
		m_ctx.beginPath();
		m_ctx.moveTo(cc.x[0], cc.y[0]);
		for (var i=1; i<nc; i++){
			m_ctx.lineTo(cc.x[i], cc.y[i]);
		}
		m_ctx.stroke();
		m_ctx.closePath();
	}

};

var catmullCircuit = function (xi, yi){
	var n = xi.length;
	var x=[], y=[];
	var ox=[], oy=[];

	x.push(xi[n-1]);	y.push(yi[n-1]);
	x.push(xi[0]);	y.push(yi[0]);
	for (var i=1; i<n ;i++){
		x.push(xi[i]);	y.push(yi[i]);
	}
	x.push(xi[0]);	y.push(yi[0]);
	x.push(xi[1]);	y.push(yi[1]);

	n += 1;

	for (var i=1; i<n ;i++){
		var ax = -1 * x[i-1] + 3 * x[i] - 3 * x[i+1] + 1 * x[i+2];
		var bx =  2 * x[i-1] - 5 * x[i] + 4 * x[i+1] - 1 * x[i+2];
		var cx = -1 * x[i-1] + 0 * x[i] + 1 * x[i+1] + 0 * x[i+2];
		var dx =  0 * x[i-1] + 2 * x[i] + 0 * x[i+1] + 0 * x[i+2];

		var ay = -1 * y[i-1] + 3 * y[i] - 3 * y[i+1] + 1 * y[i+2];
		var by =  2 * y[i-1] - 5 * y[i] + 4 * y[i+1] - 1 * y[i+2];
		var cy = -1 * y[i-1] + 0 * y[i] + 1 * y[i+1] + 0 * y[i+2];
		var dy =  0 * y[i-1] + 2 * y[i] + 0 * y[i+1] + 0 * y[i+2];

		// 20分割(荒い)
		for (var j=0; j<=20; j++){
			var t = j / 20;
			ox.push(((ax *t*t*t) + (bx *t*t) + (cx *t) + dx) / 2);
			oy.push(((ay *t*t*t) + (by *t*t) + (cy *t) + dy) / 2);
		}
	}
	return {"x": ox, "y":oy};
};


test2 = {
init: function(){
	var canvas = document.getElementById('c');
	m_ctx = canvas.getContext("2d");

	canvas.addEventListener('touchstart',
		function(e) {
			e.preventDefault();
			var n = e.touches.length;
			var rect=e.target.getBoundingClientRect();
			m_x.push(e.touches[n-1].pageX-rect.left);
			m_y.push(e.touches[n-1].pageY-rect.top);
			draw(canvas.width, canvas.height);
		}, false);

	canvas.addEventListener('mousedown',
		function(e) {
			e.preventDefault();
			var rect=e.target.getBoundingClientRect();
			m_x.push(e.clientX-rect.left);
			m_y.push(e.clientY-rect.top);
			draw(canvas.width, canvas.height);
		}, false);
}
}
};

</script>
</head>

<body onload="test2.init()">
<canvas id="c" width="640" height="640" style="background-color:#e0e0e0;">
</canvas>
</body>
</html>



実行サンプルはこちら


IE9以降/Chrome/Safari/FF/Androidで、確認しています。




Tags: プログラムメモ
author : HUNDREDSOFT | - | -