山本ワールド
2次ベジェ曲線の長さを求める

長さ
2次ベジェ曲線の長さを求め表示するサンプルです。
マウスカーソルで図上の円で左ボタンを押しながらカーソルを移動させると始終点及び制御点を移動させることができます。マウスがない環境では図の下のラジオボタンで移動させたい点を選択し図上でタップすると一度移動させることができます。
図の上部にベジェ曲線の始点・制御点・終点の座標、2行目がSVGのパスの長さを取得するgetTotalLength()メソッドによる長さの表示、3行目が以下の式で算出した値を表示しています。
2次ベジェ曲線の長さの算出は以下のページを参考としました。
Quadratic Bezier curve length — —
二次ベジェ曲線は媒介変数tを使うと以下の式で表すことができます。
\(\overline{B}(t)=(1-t)^2\overline{P}_0+2(1-t)t\overline{P}_1+t^2\overline{P}_2\)
P0とP2が始終点、P1が制御点を表しています。
\(\displaystyle \frac{\partial\overline{B}(t)}{\partial t}=2t(\overline{P}_0-2\overline{P}_1+\overline{P}_2)+2\overline{P}_1-2\overline{P}_0\)
曲線の長さはxとyそれぞれの微分値の二乗の和の平方根の和を定積分することで求めることができます。
\(\displaystyle L_B=\int^{t}_{0}\sqrt{B'_x(t)^2+B'y(t)^2}\)
式を簡単にするために置き換えをします。
\(\overline{a}=\overline{P}_0-2\overline{P}_1+\overline{P}_2\)
\(\overline{b}=2\overline{P}_1-2\overline{P}_0\)
\(A=4({a_x}^2+{a_y}^2)\)
\(B=4({a_x}b_{x}+a_y b_y)\)
\(C={b_x}^2+{b_y}^2\)
\(\displaystyle L_B=\int^1_0 \sqrt{At^2+Bt+C} dt\)
\(\displaystyle \int \sqrt{x^2+k} dx=\frac{1}{2}(x\sqrt{x^2+k}+k \cdot \ln | x+\sqrt{x^2+k} |)+C\)
\(\displaystyle u=\frac{B^2}{4A}\)
\(\displaystyle k=\frac{4AC-B^2}{4A}\)
\(x=\sqrt{A}t+\sqrt{u}\)
\(\displaystyle L_B=\frac{1}{8A^\frac{3}{2}} \left( 4 A^\frac{3}{2} \sqrt{A+B+C}+2\sqrt{A}B(\sqrt{A+B+C}-\sqrt{C} ) +(4CA-B^2)\ln| \frac{2\sqrt{A}+\frac{B}{\sqrt{A}}+2\sqrt{A+B+C}}{\frac{B}{\sqrt{A}}+2\sqrt{C}} | \right)\)
ソースコード
以下のソースのダウンロード(quadratic_bezier3.zip)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
</head>
<body>
<span id="text1"><br /><br /></span><br />
<svg onmousemove="move()" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 240" width="320" height="240">
<g id="svg1">
<rect x="0" y="0" width="320" height="240" stroke="blue" fill="none"/>
<path id="b1" d="M50,60Q100,150 250,60" fill="none" stroke="black"/>
<circle id="c1" cx="50" cy="60" r="8" fill="red" stroke="black" onmousedown="down1()" onmouseup="up1()"/>
<circle id="c2" cx="100" cy="150" r="8" fill="red" stroke="black" onmousedown="down2()" onmouseup="up2()"/>
<circle id="c3" cx="250" cy="60" r="8" fill="red" stroke="black" onmousedown="down3()" onmouseup="up3()"/>
</g>
</svg><br />
<form name="test1Form">
<fieldset id="id1">
<legend>選択</legend>
<label><input name="name1" onclick="selB()" type="radio" value="1" />始点</label>
<label><input name="name1" onclick="selB()" type="radio" value="2" />制御点</label>
<label><input name="name1" onclick="selB()" type="radio" value="3" />終点</label>
<label><input name="name1" onclick="selB()" type="radio" checked="true" value="4" />フリー</label>
</fieldset>
</form>
<br />
<script type="text/javascript">
// ベジェ曲線の諸元
var xyn=[ [ 50,60 ],
[ 100,150 ],
[250,60 ]];
var mode=0;
function down1(){
mode=1;
}
function up1(){
mode=0;
}
function down2(){
mode=2;
}
function up2(){
mode=0;
}
function down3(){
mode=3;
}
function up3(){
mode=0;
}
function bezier2len(xyn){
var ax,ay,bx,by;
ax = xyn[0][0] - 2*xyn[1][0] + xyn[2][0];
ay = xyn[0][1] - 2*xyn[1][1] + xyn[2][1];
bx = 2*xyn[1][0] - 2*xyn[0][0];
by = 2*xyn[1][1] - 2*xyn[0][1];
var A = 4*(ax*ax + ay*ay);
var B = 4*(ax*bx + ay*by);
var C = bx*bx + by*by;
var Sabc = 2*Math.sqrt(A+B+C);
var A_2 = Math.sqrt(A);
var A_32 = 2*A*A_2;
var C_2 = 2*Math.sqrt(C);
var BA = B/A_2;
return ( A_32*Sabc +
A_2*B*(Sabc-C_2) +
(4*C*A-B*B)*Math.log( (2*A_2+BA+Sabc)/(BA+C_2) )
)/(4*A_32);
}
function selB(){
var rbs=document.forms["test1Form"]["name1"];
var rb=event.target;
var o=document.getElementById('id2');
mode=eval(rb.value) | 0x80;
}
// マウスカーソルが移動しているときに呼び出される
function move(){
var s = document.getElementById('svg1');
var r = s.getBoundingClientRect();
var x=Math.round(event.clientX-r.left);
var y=Math.round(event.clientY-r.top);
if((mode & 0xf)==1){
xyn[0][0]=x;
xyn[0][1]=y;
var b = document.getElementById('b1');
b.setAttribute("d","M"+xyn[0][0]+","+xyn[0][1]+"Q"+xyn[1][0]+","+xyn[1][1]+" "+xyn[2][0]+","+xyn[2][1]);
var c = document.getElementById('c1');
c.setAttribute("cx",x);
c.setAttribute("cy",y);
var t = document.getElementById('text1');
t.innerHTML="ベジェ曲線 "+xyn[0][0]+","+xyn[0][1]+" "+xyn[1][0]+","+xyn[1][1]+" "+xyn[2][0]+","+xyn[2][1]+"<br />getTotalLength()="+b.getTotalLength()+"<br />bezier2len="+bezier2len(xyn);
}
if((mode & 0xf)==2){
xyn[1][0]=x;
xyn[1][1]=y;
var b = document.getElementById('b1');
b.setAttribute("d","M"+xyn[0][0]+","+xyn[0][1]+"Q"+xyn[1][0]+","+xyn[1][1]+" "+xyn[2][0]+","+xyn[2][1]);
var c = document.getElementById('c2');
c.setAttribute("cx",x);
c.setAttribute("cy",y);
var t = document.getElementById('text1');
t.innerHTML="ベジェ曲線 "+xyn[0][0]+","+xyn[0][1]+" "+xyn[1][0]+","+xyn[1][1]+" "+xyn[2][0]+","+xyn[2][1]+"<br />getTotalLength()="+b.getTotalLength()+"<br />bezier2len="+bezier2len(xyn);
}
if((mode & 0xf)==3){
xyn[2][0]=x;
xyn[2][1]=y;
var b = document.getElementById('b1');
b.setAttribute("d","M"+xyn[0][0]+","+xyn[0][1]+"Q"+xyn[1][0]+","+xyn[1][1]+" "+xyn[2][0]+","+xyn[2][1]);
var c = document.getElementById('c3');
c.setAttribute("cx",x);
c.setAttribute("cy",y);
var t = document.getElementById('text1');
t.innerHTML="ベジェ曲線 "+xyn[0][0]+","+xyn[0][1]+" "+xyn[1][0]+","+xyn[1][1]+" "+xyn[2][0]+","+xyn[2][1]+"<br />getTotalLength()="+b.getTotalLength()+"<br />bezier2len="+bezier2len(xyn);
}
if(mode & 0x80){
mode=0;
var rbs=document.forms["test1Form"]["name1"];
rbs[3].checked=true;
}
}
</script>
</body>
</html>