目次

    トリム曲線と平面の交点を求める
    距離関数とベジエクリッピング

    ベジエ曲面スライサー(トリム対応版)では、スライス面とトリム曲線の交点に球を置いています。この交点は曲面を細かく刻んで探しているのではなく、1変数の多項式の根として求めています。その仕組みである距離関数とベジエクリッピングを、動かしながら解説します。

    S(u,v) = Σi=03 Σj=03 Pij Bi3(u) Bj3(v)双3次ベジエ曲面
    C(t) = S(u(t), v(t)) = Σk=018 Qk Bk18(t)曲面上のトリム曲線:厳密に18次、制御点19個
    g(t) = n·C(t) − d = Σk=018 (n·Qkd) Bk18(t)距離関数:この根が交点
    曲線の次数
    双3次曲面の上に3次のトリム曲線 (u(t), v(t)) をのせると、3次元曲線 C(t) は t の18次のベジエ曲線になります(制御点は19個)。近似はせず、この18次曲線を厳密に組み立てています。
    距離関数
    平面 n·p = d までの符号付き距離 g(t) も18次のベジエ曲線で、その制御係数は「各制御点 Qk から平面までの距離」そのものです。
    根の探索
    g(t) = 0 の解を、1次元のベジエクリッピングで求めます。仕上げにニュートン法を数回だけ使います。
    得られるもの
    t* から (u,v) を求め、曲面上の3次元点 p = S(u,v) を計算します。この点が球の中心です。

    求めたいもの

    曲面は4×4の制御点 Pij を持つ双3次ベジエ曲面 S(u,v) です。トリム曲線は、パッチのUV空間に描いた3次ベジエ曲線 (u(t), v(t))(t ∈ [0,1])です。円は4本の3次ベジエ弧の閉ループで、円弧の制御点距離には κ = 0.5522847498307936 を使います。

    トリム曲線を曲面に写した3次元曲線が C(t) = S(u(t), v(t)) です。スライス面は単位法線 n と定数 d を使って n·p = d と書けます。求めるのは、

    n·C(t) = d を満たす t ∈ [0,1]

    のすべてです。トリム曲線は弧ごとに別のベジエ曲線なので、この問題を弧ごとに解きます(1つの円で4弧)。

    全体の流れ

    1. 形状を作るとき(1回だけ)
      各弧について、18次曲線の制御点 Q0Q18 を厳密に組み立てます(次数の章)。円の大きさを変えたときは作り直します。
    2. スライス面の向きが変わるたび
      19個の内積 n·Qk を計算し、最小値と最大値を控えます。
    3. 平面ごとの範囲判定
      d が [最小, 最大] の外なら、その弧は平面と交わりません。ここで終了です。
    4. 距離関数の根探索
      gk = n·Qkd を係数とする18次ベジエ曲線に、1次元ベジエクリッピングを適用して根 t* を求めます(クリッピングの章)。
    5. 3次元点の復元
      (u,v) = (u(t*), v(t*)) を求め、p = S(u,v) を球の中心にします。弧の継ぎ目で同じ点が二重に見つかった場合は、距離 10−6 未満のものを1つにまとめます。

    距離関数:3次元の交点問題を1変数の根探索にする

    単位法線 n の平面 n·p = d に対して、点 p の符号付き距離は n·pd です(平面の表側なら正、裏側なら負)。曲線上の点の距離を t の関数として書いたものが距離関数です。

    g(t) = n·C(t) − d

    曲線が平面を貫く点では距離が0になるので、交点は g(t) = 0 の根です。ここまでは当然ですが、ベジエ曲線の場合はもう一段よいことが起きます。

    制御係数は制御点の距離そのもの

    ベジエ曲線 C(t) = Σ Qk Bk(t) に、線形写像 pn·pd を施すと、写像後の曲線は「制御点を同じ写像で移した曲線」になります。ベジエ基底の総和が常に1なので、定数項 −d も各係数に配ることができるからです。

    g(t) = Σk=0n gk Bk(t), gk = n·Qkd

    つまり g(t) は、制御点が (k/n, gk) のスカラー値ベジエ曲線です。3次元曲線と平面の交差判定は、「グラフ (t, g) が t 軸を横切る位置を探す」問題に置き換わります。ベジエ曲線の性質がそのまま使えます。

    体験:制御点の距離が、そのまま g(t) の係数になる2次元の3次ベジエ曲線で示します(実装では3次元・18次)
    Q₀ Q₁ Q₂ Q₃ 交点(g の根)

    左の青い曲線の制御点(色付きの丸)をドラッグしてみてください。左の破線が「各制御点から直線への符号付き距離」で、右のグラフで同じ色の点の高さになっています。右のグラフの曲線が横軸(赤)と交わる t が、左の金色の交点に対応します。すべての制御点が直線の同じ側に寄ると、右の制御多角形も横軸の片側に寄り、交点が消えます。

    18次の曲線を厳密に組み立てる

    ここが、次数を正しく数えるうえでいちばん大事な部分です。C(t) = S(u(t), v(t)) を、t のベジエ曲線として書き直します。

    手順1:基底関数に3次曲線を代入する

    3次のベジエ基底は Bi3(u) = C(3,i) ui (1−u)3−i で、u について3次です。ここに3次のトリム曲線 u(t) を代入すると、u(t)i が 3i 次、(1−u(t))3−i が 3(3−i) 次になり、掛け合わせると次数は足し算で 3i + 3(3−i) = 9 次になります。i がどれでも9次です。v 側の Bj3(v(t)) も同じく9次です。

    手順2:u 側と v 側を掛ける

    曲面の各項は Bi(u(t)) · Bj(v(t)) という積です。9次と9次の積なので、次数は 9 + 9 = 18 になります。

    手順3:制御点で線形結合する

    16個の積 Wij(t) = Bi(u(t)) Bj(v(t)) に制御点 Pij を掛けて足すだけなので、次数は増えません。

    C(t) = Σi,j Pij Wij(t) = Σk=018 Qk Bk18(t)

    18次のベジエ曲線の制御点は 18 + 1 = 19 個です。Qk は、Wij を18次のベジエ基底で展開したときの k 番目の係数を重みにして、Pij を足し合わせたものです(x, y, z 座標ごとに同じ計算)。

    「9次」と「18次」の違い「9次」になるのは、u だけ(または v だけ)に注目した基底関数1つ分です。曲面上の点は uv の両方に依存し、u³v³ のような混合項を持つので、積をとった座標そのものは 9 + 9 = 18 次になります。曲面ではなく3次曲線を3次曲線に代入するだけなら 3 × 3 = 9 次ですが、双3次曲面は2変数なので、その2倍の18次になります。
    計算次数
    u(t), v(t)UV空間の3次ベジエ曲線3
    Bi(u(t)), Bj(v(t))3次の基底に3次曲線を代入(3 × 3)9
    Wij(t)積は次数の和(9 + 9)18
    C(t)制御点による線形結合(次数は増えない)18(制御点19個)
    g(t) = n·C(t) − d内積と定数の減算(線形なので次数は変わらない)18
    体験:次数の数え方曲面の次数とトリム曲線の次数を変えて確かめます

    一般に、u 方向 m 次・v 方向 n 次の曲面に d 次のトリム曲線を代入すると、合成曲線の次数は (m + n)d になります。実装では m = n = d = 3 なので 6 × 3 = 18 です。

    ベジエ基底の積の公式

    手順1〜2の掛け算は、ベジエ基底のまま計算できます。次の公式を使うと、係数配列同士の畳み込みで済みます。

    Bam(t) · Bbn(t) = C(m,a) C(n,b)C(m+n, a+b) Ba+bm+n(t)

    次数 mn の2つの多項式の積は次数 m + n になる、という当たり前の事実が、そのまま添字の足し算 a + b に現れています。実装の bernMul はこの公式そのものです。

    
    
    
    

    「厳密」であるということ

    ここまでの計算は恒等式の変形だけで、標本点から曲線をあてはめたり、低い次数で近似したりする処理を含みません。組み立てた18次曲線が曲面の直接評価 S(u(t), v(t)) と一致することは、ランダムな点で確かめています(最大誤差 7.0 × 10−16、丸め誤差の範囲。詳しくは検証の章)。したがって、交点の位置に近似による誤差は入りません。

    ベジエクリッピング:18次の根をすべて見つける

    距離関数 g(t) は係数 g0g18 の18次ベジエ曲線です。次数が高いので、公式で根を求めることはできません。また、ニュートン法だけでは初期値しだいで根を取りこぼします。そこでベジエ曲線の凸包性を使い、「根の入っている可能性のある区間」を確実に絞り込んでいきます。

    1. 制御多角形を描く
      点 (k/n, gk) を結びます。曲線は、これらの点の凸包の内側にあります。
    2. 凸包と t 軸の交わりを求める
      凸包が t 軸(g = 0)と交わらなければ、この区間に根はありません。交わるなら、その交わりの区間 [tmin, tmax] の外に根はありません。
    3. 区間を切り出す
      de Casteljau 法で [tmin, tmax] の部分曲線を取り出し、[0,1] に再パラメータ化します(形は変わらず、拡大して見るのと同じです)。
    4. 縮まらなければ二分する
      絞り込みで区間が25%以下にしか縮まなかったときは(残りが75%を超える場合)、区間を半分に分けて左右を別々に処理します。複数の根が近くにあるときに、それぞれを分離するためです。
    5. 収束したら根とする
      区間の幅が 10−9 未満になったら、その中央を根とします。最後にニュートン法を最大4回かけて、機械精度まで仕上げます。
    体験:ベジエクリッピングを1ステップずつ実行する実装(bezRoots)と同じ手順です
    曲線 g(t) と制御多角形 凸包 凸包と t 軸の交わり 除外された範囲

    実行前(0ステップ目)は、制御点を上下にドラッグして g の形を変えられます。ステップを進めるたびに、下の帯グラフで「除外された範囲(赤)」が広がり、「処理待ちの範囲(青)」が根の周りに絞られていきます。切り出した部分曲線はほぼ直線に近づき、凸包が細くなるので、根のまわりの区間は急速に縮みます。単根では、誤差が二乗のペースで減る2次収束になります。

    実装の細部

    凸包と t 軸の交わりの求め方

    凸包そのものは作りません。符号の異なる2つの制御点を結ぶ線分がどれも凸包の内側にあることを使い、そのすべての組について線分が t 軸を横切る t 座標を計算して、最小値と最大値を取ります。これが凸包と t 軸の交わりの両端になります。18次では制御点19個の組は最大171通りで、計算は軽いです。係数がちょうど0の制御点は、その位置自体を候補に入れます。

    
    
    
    

    精度を保つための工夫

    スライス線とトリム境界をつなぐ

    球の位置は上の方法で厳密に決まりますが、画面のスライス線(赤い線)は、ベジエクリッピングで絞り込んだ小さなパッチの中をマーチングスクエア法で線分にしたものです。線分の端をトリム境界にきちんとつなぐため、次の処理をしています。

    つまり球は距離関数の根から、線は格子から作られますが、根のほうが正確なので、線の端を根に合わせる形で整合させています。

    検証

    確認内容結果
    合成した18次曲線を曲面の直接評価と比較制御点は19個。最大誤差 7.0 × 10−16
    平面の向き400通り × ティーポットの4パッチ × 4弧 = 6400曲線について、根の数を2万分割の符号変化と比較6400本すべて一致。見つかった根はすべて平面上(|n·pd| < 10−9
    合成した多項式での根探索:5根(0.1, 0.3, 0.55, 0.8, 0.95)、近接根(0.4 ± 10−4、0.4 ± 10−7)、恒等的に0、常に正5根は正確に検出。近接根も別々の根として分離。恒等的に0と常に正は根なし
    全5形状(ティーポット・ドーナツ・楕円体・円柱・1枚のパッチ)でのパイプライン全体NaN・無限大なし。交点は平面上(残差 約 10−16)。トリム有効時は線分数が減少(例:円柱 576 → 448)
    円柱で、断面線の点がトリムの穴の内側に入り込んでいないか(896点)UV距離 0.01 以上の侵入は0件

    制限と注意点

    接する場合(重根)スライス面がトリム曲線にちょうど接すると、距離関数は曲線が横軸に触れるだけで交差しない重根になります。このとき凸包は根の近くで軸に触れ続けるので、絞り込みが進みにくくなります。丸め誤差しだいで、根が見つかる場合と見逃される場合があり、二重に出ることもあります。上のデモの「接する(重根)」で挙動を見られます。

    式のまとめ

    S(u,v) = ΣiΣj Pij Bi3(u) Bj3(v)
    C(t) = S(u(t), v(t)) = Σk=018 Qk Bk18(t)
    Bi3(u(t)) : 9次 Wij = Bi(u(t)) Bj(v(t)) : 18次
    g(t) = n·C(t) − d = Σk gk Bk18(t), gk = n·Qkd
    g(t*) = 0 → (u,v) = (u(t*), v(t*)) → p = S(u,v) → 球