3次ベジェ長さの分析解は存在しないようですが、安価な解のコーディングが存在しないことを意味するものではありません。安いとは、50-100 ns(またはそれ以下)の範囲のようなものを意味します。
誰かがそのようなことを知っていますか?多分2つのカテゴリーに:
1)1%のようなエラーは少なくなりますが、コードは遅くなります。 2)20%のようなより多くのエラーがより速く?
Googleで少しスキャンしましたが、Niceソリューションのように見えるものは何も見つかりません。 N個のラインセグメントを分割してN個の平方根を合計するようなものだけです。精度を上げるには遅すぎ、おそらく2個または3個のセグメントでは不正確です。
もっと良いものはありますか?
別のオプションは、弦とコントロールネットの間の平均としてアーク長を推定することです。実際には:
Bezier bezier = Bezier (p0, p1, p2, p3);
chord = (p3-p0).Length;
cont_net = (p0 - p1).Length + (p2 - p1).Length + (p3 - p2).Length;
app_arc_length = (cont_net + chord) / 2;
その後、スプラインセグメントを2つのセグメントに再帰的に分割し、収束までの弧の長さを計算できます。私は自分でテストしましたが、実際にはかなり速く収束しました。私はこれからアイデアを得ました フォーラム 。
最も単純なアルゴリズム:曲線を平坦化し、ユークリッド距離を計算します。おおよその弧の長さが必要である限り、このソリューションは高速で安価です。カーブの座標LUTを考えると、あなたは速度について話しているので、それらを使用し、常に座標を再計算しないことを想定しています。これは、タリーを使用した単純なforループです。一般的なコードでは、2つのポイント間のユークリッド距離を計算するdist
関数を使用します。
var arclength = 0,
last=LUT.length-1,
i;
for (i=0; i<last; i++) {
arclength += dist(LUT[i], LUT[i+1]);
}
できました。 arclength
は、LUTに基づいて曲線に形成できるセグメントの最大数に基づくおおよその円弧長になりました。より大きな潜在的エラーでより速く物事が必要ですか?セグメント数を制御します。
var arclength = 0,
segCount = ...,
last=LUT.length-2,
step = last/segCount,
s, i;
for (s=0; s<=segCount; s++) {
i = (s*step/last)|0;
arclength += dist(LUT[i], LUT[i+1]);
}
これは、真の弧の長さにさえ近づく値を生成する、最も単純な可能なアルゴリズムです。より良いもののためには、より高価な数値アプローチを使用する必要があります(ルジャンドルガウス求積法など)。
理由を知りたい場合は、「ベジエ曲線の入門書」の 弧の長さのセクション を押してください。
3ポイントのベジェ(下)の閉じた長さの式を計算しました。私は4ポイント以上の閉じたフォームを作成しようとしませんでした。これは、おそらく表現および処理が困難または複雑になります。ただし、Runge-Kutta積分アルゴリズム( 詳細はこちらのQ&Aを参照 )などの数値近似手法は、 弧長の数式 を使用して積分することで非常にうまく機能します。
ここにいくつかのJava 3ポイントのベジェの弧の長さのコード、ポイントa
、b
、およびc
があります。
v.x = 2*(b.x - a.x);
v.y = 2*(b.y - a.y);
w.x = c.x - 2*b.x + a.x;
w.y = c.y - 2*b.y + a.y;
uu = 4*(w.x*w.x + w.y*w.y);
if(uu < 0.00001)
{
return (float) Math.sqrt((c.x - a.x)*(c.x - a.x) + (c.y - a.y)*(c.y - a.y));
}
vv = 4*(v.x*w.x + v.y*w.y);
ww = v.x*v.x + v.y*v.y;
t1 = (float) (2*Math.sqrt(uu*(uu + vv + ww)));
t2 = 2*uu+vv;
t3 = vv*vv - 4*uu*ww;
t4 = (float) (2*Math.sqrt(uu*ww));
return (float) ((t1*t2 - t3*Math.log(t2+t1) -(vv*t4 - t3*Math.log(vv+t4))) / (8*Math.pow(uu, 1.5)));
public float FastArcLength()
{
float arcLength = 0.0f;
ArcLengthUtil(cp0.position, cp1.position, cp2.position, cp3.position, 5, ref arcLength);
return arcLength;
}
private void ArcLengthUtil(Vector3 A, Vector3 B, Vector3 C, Vector3 D, uint subdiv, ref float L)
{
if (subdiv > 0)
{
Vector3 a = A + (B - A) * 0.5f;
Vector3 b = B + (C - B) * 0.5f;
Vector3 c = C + (D - C) * 0.5f;
Vector3 d = a + (b - a) * 0.5f;
Vector3 e = b + (c - b) * 0.5f;
Vector3 f = d + (e - d) * 0.5f;
// left branch
ArcLengthUtil(A, a, d, f, subdiv - 1, ref L);
// right branch
ArcLengthUtil(f, e, c, D, subdiv - 1, ref L);
}
else
{
float controlNetLength = (B-A).magnitude + (C - B).magnitude + (D - C).magnitude;
float chordLength = (D - A).magnitude;
L += (chordLength + controlNetLength) / 2.0f;
}
}
私の場合、高速で有効なアプローチはこれです。 (Unity3d用にc#で書き直されました)
public static float BezierSingleLength(Vector3[] points){
var p0 = points[0] - points[1];
var p1 = points[2] - points[1];
var p2 = new Vector3();
var p3 = points[3]-points[2];
var l0 = p0.magnitude;
var l1 = p1.magnitude;
var l3 = p3.magnitude;
if(l0 > 0) p0 /= l0;
if(l1 > 0) p1 /= l1;
if(l3 > 0) p3 /= l3;
p2 = -p1;
var a = Mathf.Abs(Vector3.Dot(p0,p1)) + Mathf.Abs(Vector3.Dot(p2,p3));
if(a > 1.98f || l0 + l1 + l3 < (4 - a)*8) return l0+l1+l3;
var bl = new Vector3[4];
var br = new Vector3[4];
bl[0] = points[0];
bl[1] = (points[0]+points[1]) * 0.5f;
var mid = (points[1]+points[2]) * 0.5f;
bl[2] = (bl[1]+mid) * 0.5f;
br[3] = points[3];
br[2] = (points[2]+points[3]) * 0.5f;
br[1] = (br[2]+mid) * 0.5f;
br[0] = (br[1]+bl[2]) * 0.5f;
bl[3] = br[0];
return BezierSingleLength(bl) + BezierSingleLength(br);
}