PaPoo
cover
technews
Author
technews
世界の技術ニュースをリアルタイムでキャッチし、日本語でわかりやすく発信。AI・半導体・スタートアップから規制動向まで、グローバルテックシーンの「今」をお届けします。

耳を切ると何が速くなるのか

この記事は後半です。前半は Qiita にあります: 370万ポリゴンを489msで割る地図エンジンの選択

速さは「切る回数」を減らして作る

画像: github.com より
画像: github.com より

前半では、地図エンジンが 3,740,425 個のポリゴンを 489ms で三角形化し、従来の拘束 Delaunay の 2.93 秒を大きく下回った話まで見た。ここで重要なのは、単に ear clipping が軽いから勝ったのではなく、耳を探すコストそのものを削っていった点にある。

最初の素朴な実装は、耳かどうかを調べるたびに境界上の他の頂点を広く見にいく。これでは大きいポリゴンほど重い。記事でも、3.7 百万ポリゴン全体では 1.12 秒、特に 257 頂点以上の巨大ポリゴン 1,123 個だけで 199ms を食っていた。つまり、速さの敵は「耳を切ること」ではなく「耳を探すこと」だった。

そこで earstrip は、まず反転可能な頂点だけに絞る。多角形の角のうち、実際に耳を邪魔できるのは凹頂点だけだからだ。凸の角はそもそも候補に入れない。この一段で、判定対象がかなり減る。次に、耳の三角形の中に入りうる凹頂点だけを数え、さらにその候補を帯状に分けて読む。細かい工夫の積み重ねで、全件スキャンを避ける設計になっている。

穴と壊れた輪郭をどう受け止めるか

地図データでは、理論通りの単純多角形のほうが少ない。穴のある建物、中庭、森林の抜け、そして格子座標の丸めで生じる自己交差や接触が普通に出てくる。earstrip はここで「耳が切れないなら失敗」とはせず、輪郭を組み替えて処理を続ける。

穴は、右端の頂点から外枠へ橋を張って一つの輪にまとめる。橋は輪郭を横切らない必要があるが、地図の整数座標では、ちょうど頂点を通るレイや、外壁に一点で接する穴のような例外が出る。記事では、そうしたケースも拾うようにしている。単純な教科書実装では見落としやすいところだ。

それでも輪郭が壊れていて ear clipping だけでは処理しきれない場合、earstrip は拘束 Delaunay に切り替える。README には、輪郭が交差したり接したりする場合は constrained Delaunay で覆い直すとある。さらに、128 を超えるリングを持つポリゴンは最初からその経路に回す。穴を外枠につなぐだけでも二乗時間が効いてくるからだ。速さを優先しても、全部を耳で押し切るわけではない。

489ms の中身はかなり現実的だ

このライブラリの面白さは、数式よりも実運用の数字が先に立つところにある。GitHub の README では、GLMap の triangulator として使われていること、C++ 版は earcut.hpp より 13〜14%速く、ブラウザでは earcut.js より 1.4〜2.1倍速いとされている。さらに、穴が非常に多いデータでは 6〜7倍速いという記述もある。

一方で、万能ではない。earcut 側の README が明言している通り、耳切りは実用データには強いが、入力が壊れていれば正しさを保証しない。earstrip はそこを埋めるためにフォールバックを持つが、それでも何でも完璧に直す魔法ではない。run() が Exact、Constrained、HolesLeftOut のように結果を返すのは、その境界を隠さないためだ。うまくいかなかったケースを無理に成功扱いしない姿勢は、地図エンジンらしい。

精度面でも割り切りがある。earcut は任意入力に対して正しい三角形分割を保証しないし、出力は T ジャンクションを含みうる。レンダリングには十分でも、ナビメッシュや FEM のように、面同士の接続性まで厳密に欲しい用途では向かない。earstrip も目的は地図描画であって、一般の幾何ライブラリを置き換えることではない。

自分ならどこで使うか

この手の実装をそのまま学ぶ価値があるのは、地図描画、タイルベースの 2D レンダリング、あるいはブラウザやモバイルでの即時表示が重要な場面だ。入力がほぼ地図タイルで、三角形の品質よりも「毎回とにかく速く出る」ことが大事なら、かなり筋がいい。

逆に、入力を完全に正規化できるなら話は少し変わる。壊れた輪郭の救済まで含めた複雑さを抱え込むより、前処理で形を整えたほうが単純になることもある。だが実際の地図データは、そこまで素直ではない。だからこそ、速い実装が「理想の幾何」ではなく「汚れた現場」を前提に設計されているのは意味がある。

僕が注目するのは、triangle strip を出す設計だ。三角形の個数だけでなく、書き出すインデックスまで減らしている。描画パイプライン全体で見ると、この差は地味でも効く。三角形を作ることと、GPU に渡すことは別問題で、その両方を少しずつ軽くしている。

つまりこの話は、Delaunay をやめたから速くなった、という単純な置き換えではない。地図データの癖を観察し、耳を切る順番を変え、候補を絞り、壊れた輪郭には別ルートを用意した。その結果として 489ms が出た。実装の勝ち筋は、たいていこういう細部にある。

同じ著者の記事