はじめに
今回は、凸性を持つ多角形について考察していきます。多角形は辺として線分を持ちますので、前回までに学習した線分の知識は、そのまま多角形の幾何にも活かすことができます。
凸多角形とは
図形に凹みが存在しないとき、その図形は凸であるといいます。もっと厳密に述べると、図形の内部のどんな2点をとっても、その2点を結ぶ線分が図形に含まれるとき、図形は凸になります。図1の場合、左の図形は凸ですが、右の図形は凸ではありません。
さて、凸性のある多角形を凸多角形(convex polygon)と呼びます。
Blogopolisでは、島の外郭が凸多角形になっています(図2)。そして、島の内部のあらゆる土地の区画もまた、例外なく凸多角形になっています(図3)。これは、凸多角形が計算処理上、便利な性質を多く備えているため、凸多角形を意図的に採用しているのです。
今回は、この凸多角形がテーマになります。
凸多角形クラスの作成
まず、凸多角形を表現するクラスを用意しておきましょう。以下のようにConvexPolygonクラスを作成します。このクラスは、コンストラクタで頂点(vertex)の座標リストを与えるものとします。また、頂点の座標リストをもとに凸多角形の辺(edge)を作成し、LineSegmentオブジェクトのリストとして保持するものとします。
凸性の判定
ConvexPolygonクラスを作成したことで、凸多角形のデータ構造は準備できました。しかし、コンストラクタに渡された頂点座標のリストが、凸多角形の条件をみたしているかどうかは分かりません。ここで、ConvexPolygonオブジェクトが凸になることを保証するために、頂点リスト引数の検査を行い、凸でない場合はエラーを発生させたいとします。
多角形の頂点データをもとに、その多角形が凸であるかどうかを判定するには、どうすれば良いのでしょうか。
図4を見てください。多角形の1頂点から出発して、一筆書きのように辺を巡回し、元の頂点に戻ってくる道のりを考えます。新しい頂点に到達したとき、進行方向に回転が加わるわけですが、凸多角形の場合、その回転方向はすべて左向き(反時計回り, counterclockwise)か、右向き(時計回り, clockwise)で統一されています。
これに対し、凸でない多角形では、凹んでいる頂点のところで回転の向きが逆になります。すなわち、時計回りと反時計回りが混在するのです。このように、多角形の辺の回転方向を調べることで、凸性を判定することができます。
以下、回転方向の計算に求められる知識として、ベクトルの外積と、外積にもとづくCCW関数について見ていくことにします。
ベクトルの外積
高校数学で学んだベクトルの内積を憶えているでしょうか。ベクトルaとbの内積(a・b)はスカラーとなり、aとbのなす角度θを使って以下の式で表されます。
さて、この内積と同じく重要性の高いベクトル演算に、外積というものがあります。3次元ベクトルの世界で、ベクトルaとbの外積(a×b)はベクトルとなり、その大きさは以下の式で表されます。
また、その向きはaとbを含む平面に垂直で、aからbへ右ねじが進む方向になります。この様子を図5に示します。
図5を見ると、xy平面上のベクトル同士の外積をとった場合、その結果のベクトルはz軸とちょうど重なり、xとyの成分は常にゼロになることが分かります。したがって、xy平面しか使わない平面幾何の世界では、単に外積の大きさ、つまりz成分のスカラー量だけを指して「外積」ということがあります。本連載でもこの考え方を採用し、外積をスカラーとします。
外積の計算
平面幾何における外積の式を、ここで改めて導入しましょう。
右辺を見ると、これはベクトルaとbを辺とする平行四辺形の符号付き面積に等しくなることが分かります(図6)。
ベクトルaを(x1, y1)、ベクトルbを(x2, y2)と成分表示し、これらの成分を使って平行四辺形の面積を求め、外積の計算式を導いてみましょう。
図7のように補助線を引くと、平行四辺形の面積は、ベクトルa+bを対角線とする長方形の面積から、(1)(2)(3)の領域の面積を引いたものとして求められます。これは簡単に計算でき、以下の式が得られます。
それでは、この外積の計算をユーティリティメソッドとして実装しておきましょう。GeomUtilsクラスを作成し、cross()メソッドを以下のように追加します。メソッド名のcrossは、3次元の外積がクロス積とも呼ばれることに由来します。
CCW関数
さて、外積を利用して、CCWという関数を作ることができます。CCWの名前は、反時計回りを意味するcounterclockwiseに由来しており、次の入力と出力を持ちます。
入力: 3点a, b, cの座標
出力: a→b→cと進むとき、その道のりが反時計回りになる場合は正の値。時計回りになる場合は負の値。a, b, cが一直線上にある場合はゼロ。
なお、ここで時計回り、反時計回りというときは、y座標が下から上に向かって増加する座標系を前提とすることにします。CCW関数の入力と出力の対応を図8に示します。
外積とCCW関数には、非常にシンプルな関係があります。aからbへ向かうベクトルabと、bからcへ向かうベクトルbcの外積を考えます(ベクトルの向きに注意してください)。abとbcのなす角度をθとすると、ベクトルが反時計回りになる場合はsinθが正の値になるため、外積も正になります。また、ベクトルが時計回りになる場合はsinθが負の値になるため、外積も負になります。つまり実質的に、ベクトルabとbcの外積をそのままCCW関数として使えてしまうわけです。
では、CCW関数を実装してみましょう。a, b, cの座標をそれぞれ(x1, y1), (x2, y2), (x3, y3)として引数にとるccw()メソッドを、GeomUtilsクラスに追加します。また、a, b, cの各座標をPoint2Dオブジェクトで引数にとるccw()メソッドも用意することにします。
最後に、ConvexPolygonクラスのコンストラクタに、このccw()メソッドを使った凸性の判定処理を追加してみましょう。先に述べたように、凸多角形では辺が常に時計回りか反時計回りのどちらかになり、凸でない多角形では時計回りと反時計回りが混在します。したがって、コンストラクタに渡された頂点座標を巡回しながら、CCW値の正負が統一されているかどうかをチェックしていけば良いことになります。
以下、この方針にしたがって多角形の凸性をチェックし、凸でない場合にはエラーを発生させるコードを示します。
まとめと次回予告
今回は、多角形の凸性を取り上げました。ベクトルの外積とCCW関数について学習し、CCW関数を使って多角形の凸性判定を行いました。また、凸多角形を表現するConvexPolygonクラスを実装しました。
今回作成したプログラムのソースコードがダウンロードできます。
次回は、多角形の包含判定や面積について考えていきます。お楽しみに!