ボロノむ図ずその応甚

䞀日䞭良い、サむトHabrahabrの芪愛なる蚪問者。 この蚘事では、ボロノむ図䞋の図を参照、それを構築するためのさたざたなアルゎリズム 、 -半平面の亀差、 -Fortuneのアルゎリズムおよび実装のいく぀かの埮劙な点C ++で。







チャヌトの倚くの興味深い応甚ず、それに関するいく぀かの興味深い事実も考慮されたす。 面癜いでしょう



蚘事の抂芁



- 必芁な抂念ず定矩

- 構築アルゎリズム

- アプリケヌション

- 興味深い事実

- 参照



この蚘事では、平面䞊にボロノむ図を構築するためのアルゎリズムのみが考慮されるこずに泚意しおください。 途䞭で、ダむアグラムの䜜成に必芁な他のアルゎリズムを怜蚎したす。2぀のセグメントの亀点を決定するアルゎリズム、2぀の凞倚角圢を亀差させるO'Rourkeアルゎリズム、および「海岞線」を䜜成するアルゎリズムです。



必芁な抂念ず定矩



次に起こるこずはすべお 飛行機䞊にあるずすぐに蚀わなければなりたせん。



さお、これが䜕であるかを理解する前に-ボロノむ図、私たちが必芁ずする幟䜕孊オブゞェクトのいく぀かの抂念を思い出させたすただし、点、線、光線、線分、倚角圢、頂点、倚角圢の゚ッゞの定矩に既に粟通しおいるず仮定したす、平面を分割するベクトルおよび盎感的な抂念



単玔なポリゎンずは、自己亀差のないポリゎンです。 単玔なポリゎンのみを怜蚎したす。



非凞倚角圢ずは、2぀の頂点が存圚する倚角圢のこずで、これらの頂点を結ぶ゚ッゞを陀き、指定された倚角圢ず亀差する線が描かれたす図を参照。



凞倚角圢ずは、蟺の延長線が他の蟺ず亀差しない倚角圢です図を参照。 他の定矩はりィキペディアで利甚可胜です。









ダむアグラムを構成するのは凞倚角圢です。 なぜ凞から正確に それらは凞面図圢である半平面の亀点埌で芋るにすぎないため、凞面図圢の亀点が凞面図圢である理由はここにあるので、自分で確かめるこずをお勧めしたす蚌拠は、たずえば本[2]にありたす 。



半平面に぀いお話し始めたので、ダむアグラム自䜓いわゆる軌跡からなるにスムヌズに移動できたす。この領域では、他のすべおのポむントよりも特定のポむントに近いポむントがすべおありたす。 ボロノむ図では、軌跡は凞倚角圢です。



軌跡を䜜成する方法は 定矩により、次のように構築されたす。n個のポむントのセットを䞎えお、ダむアグラムを構築したす。 軌跡を構成する特定のポむントpず 、䞎えられたセットの別のポむント-q  pず等しくないを取りたす。 これらの2぀のポむントを結ぶ線を描画し、このセグメントの䞭倮の垂線ずなる線を描画したす。 この線は平面を2぀の半平面に分割したす。1぀は点pで、もう1぀は点qです。 この堎合、これらの2点の軌跡は、結果の半平面です。 ぀たり、点pの軌跡を構築するには、そのようなすべおの半平面の亀点を取埗する必芁がありたす぀たり、 qを陀く特定のセットのすべおの点がqの代わりになりたす。









軌跡が構築されるポむントは、 サむトず呌ばれたす 。 次の図では、軌跡は異なる色でマヌクされおいたす。









ダむアグラムを構築するためのアルゎリズムは、特定のセットのすべおのポむントに察しおこれらの同じ軌跡を構築するためのアルゎリズムに他なりたせん。 この問題の軌跡は、 ボロノむポリゎンたたはボロノむセルずも呌ばれたす 。



最埌に、平面䞊のn点のボロノむ図の定矩を定匏化したすnは正の敎数-これは、 n軌跡 軌跡ごずの各点で構成される平面のパヌティションです 。 繰り返しになりたすが、別の定矩はWikipediaにありたす。



ちなみに、ここはボロノむ図のむンタラクティブなカラヌビゞュアラむザヌを備えたサむトです。 自分で領域をクリックしお、図の䜜成方法を確認するこずができたす。



ダむアグラムがどのように衚瀺され、なぜボロノむずいう名前が付けられおいるのかに興味がある堎合は、以䞋のネタバレをご芧ください。



歎史的事実

ちょっずした歎史



 このサむトから取られた資料

䞀般に、この図の最初の䜿甚法は、Rene Descartes1596-1650、The Beginning of Philosophy1644の䜜品にありたす。 デカルトは、宇宙を星の重力の圱響のゟヌンに分割するこずを提案したした。







わずか2䞖玀埌、有名なドむツの数孊者ペハンピヌタヌグスタフレゞュヌヌディリクレ1805幎-1859幎が2次元および3次元の堎合の図を導入したした。 したがっお、それらはディリクレ図ず呌ばれるこずもありたす。







たあ、すでに1908幎に、ロシアの数孊者Georgy Feodosievich Voronoi1868幎4月16日28、1868幎-1908幎11月7日20、1908幎は、この図がより倧きな次元の空間に぀いお説明したした。 こちらが圌の短い䌝蚘です りィキペディアから匕甚



ゲオルギヌ・フェオドシ゚ビッチ・ボロノむは、ポルタバ州のズラノカ村珟圚のチェルニヌヒり地域で生たれたした。 1889幎以来、圌はサンクトペテルブルク倧孊でアンドレむ・マルコフず孊びたした。 1894幎、圌は修士論文「3次方皋匏の根に䟝存する敎数に぀いお」を擁護した。 同幎、圌はワルシャワ倧孊の教授に遞出され、そこで鎖の分数を研究したした。 ボロノむは、 ノァヌツラフ・シェルピンスキヌによっお蚓緎されたした。 1897幎、ボロノむは「継続分数のアルゎリズムの䞀般化に぀いお」博士論文を擁護し、ブニャコフスキヌ賞を受賞したした。







構築アルゎリズム



ボロノむ図の䜜成方法を孊びたす。 4぀のアルゎリズムを怜蚎したす。そのうち2぀は詳现です1぀は実装あり、別の蚘事ではFortuneのアルゎリズムの完党な実装に専念したす。



  1. 「額に」ボロノむ図をプロットするためのアルゎリズム。 難易床 ;
  2. 半平面を亀差させおボロノむ図を䜜成するアルゎリズム。 難易床 ;
  3. 平面䞊にボロノむ図を䜜成するためのフォヌチュンのアルゎリズム。 難易床 ;
  4. 再垰的ボロノむ線図プロットアルゎリズム。 難易床 。


いく぀かのアルゎリズムの説明の埌、C ++での実装が提䟛されたす。 実装には、私たちが䜜成したラむブラリSplashGeom©-github ぞのリンクを䜿甚したした。これには、平面䞊および空間内のいく぀かの蚈算ゞオメトリのアルゎリズムを実装するために必芁なすべおが含たれおいたす。 このラむブラリを厳密に刀断しないようお願いしたす。ただ掻発な開発ず改善の段階にありたすが、すべおのコメントが聞かれたす。



他の実装に興味がある堎合は、次のずおりです。





それでは、アルゎリズムの盎接調査に移りたしょう。



「額に」ボロノむ図を構築するためのアルゎリズム



ここでのアむデアは、半平面を暪切るのではなく、セグメントの䞭倮の垂線をこれは簡単で、同意するため亀差させ、この点を他のすべおの点ず接続するこずです。 ぀たり、ボロノむセルの定矩に埓っお、次のように点pの軌跡を䜜成したす。



  1. このポむントpを他のセグメントず接続するすべおのセグメントの䞭倮の垂線を描いたので、n-1の盎線䞭倮の垂線を取埗したす。
  2. すべおの線をペアで亀差させお、 亀差点「最悪の堎合」では、各盎線が他のすべおの盎線ず亀差できるため;
  3. これらすべおを確認しおください n-1個の半平面のそれぞれのメンバヌシップ䞊のポむント、぀たり、挞近線をすでに取埗しおいる 。 したがっお、すべおの半平面に属するポむントは、ポむントpのボロノむセルの頂点になりたす。
  4. すべおのnポむントに察しお最初の3぀のステップを実行し、結果の挞近を取埗したす 。


アルゎリズムはe-maxx.ruにもありたす。



必芁に応じお、SplashGeom©を䜿甚しおこのアルゎリズムを個別に実装できたす。 この蚘事では、このアルゎリズムは実際には少なくずも以䞋のものよりはるかに劣るため、その実装は瀺されおいたせん...



半平面を超えお亀差するこずによりボロノむ図を構築するためのアルゎリズム



このアルゎリズムは、それほど耇雑な蚈算を必芁ずしないため、すでに実際に䜿甚できたす。 そのためには、セグメントずラむンを亀差させ、凞倚角圢を亀差させ、半平面を亀差させ、埗られた軌跡をダむアグラムに結合できる必芁がありたす。



アルゎリズム



  1. 珟圚のサむトのn-1盎線を取埗したす前のアルゎリズムのように-䞭倮の垂線。 これらは、半平面の「ゞェネレヌタ」になりたす。
  2. これで、n-1半平面ができたした。 これらの各半平面は、前からの盎線で䞎えられたす。 ポむントず方向、぀たり、線のどちら偎にあるか。 方向は、軌跡を構築する珟圚のサむトによっお決定されたす。これは、目的の半平面にあるため、軌跡はその䞭になければなりたせん。
  3. 私たちはすべおの半平面を暪断したす-私たちはそれをするこずができたす -珟圚のサむトの軌跡を取埗したす。
  4. すべおのnポむントに察しお最初の3぀のステップを実行し、結果の挞近を取埗したす * = 。


実装



ここでの䞻なキャッチは、凞倚角圢の通垞の亀差を実装するこずです、なぜなら、䞍快な瞮退ケヌスがあるためです倚角圢の頂点および/たたは偎面が䞀臎したす。倚角圢がそれ自䜓ず亀差する堎合、この堎合。



アクションの範囲党䜓を倖接する長方圢に制限しおいるこずをすぐに蚀わなければなりたせん-ハヌフプレヌンは互いに亀差する郚分を切り取りたす。 これにより、ダむアグラム内の無限セルの問題が解決されたす。



線ず線分の亀点


そしお、その存圚の刀断だけでなく、正確に亀点が必芁です。 SplashGeom©では、これは-たずえば、ラむンずセグメントの亀差の実装です。



コヌドを衚瀺
//   // kInfPoint -  , kNegInfPoint -   Point2D Line2D::GetIntersection(const Line2D& second_line) const { double cross_prod_norms = Vector2D(this->A, this->B).OrientedCCW(Vector2D(second_line.A, second_line.B)); Point2D intersect_point; if (fabs(cross_prod_norms) <= EPS) /* A1 / A2 == B1 / B2 */ { if (fabs(this->B * second_line.C - second_line.B * this->C) <= EPS) /* .. == C1 / C2 */ { intersect_point = kNegInfPoint2D; } else { intersect_point = kInfPoint2D; } } else { double res_x = (second_line.C * this->B - this->C * second_line.B) / cross_prod_norms; double res_y = (second_line.A * this->C - this->A * second_line.C) / cross_prod_norms; intersect_point = Point2D(res_x, res_y); } return intersect_point; } //   Point2D Segment2D::GetIntersection(const Segment2D& second_seg) const { Line2D first_line(*this); Line2D second_line(second_seg); Point2D intersect_point = first_line.GetIntersection(second_line); if (intersect_point == kNegInfPoint2D) { if (this->Contains(second_seg.b)) { intersect_point = second_seg.b; } else if (this->Contains(second_seg.a)) { intersect_point = second_seg.a; } else if (second_seg.Contains(this->b)) { intersect_point = this->b; } else if (second_seg.Contains(this->a)) { intersect_point = this->a; } else { intersect_point = kInfPoint2D; } } else if (!(this->Contains(intersect_point) && second_seg.Contains(intersect_point))) { intersect_point = kInfPoint2D; } return intersect_point; }
      
      







凞倚角圢の亀差


これで、ポリゎンの亀差が実珟したした。 もちろん、あなたはそれをするこずができたす ここで、nずmはそれぞれ1番目ず2番目の倚角圢の頂点の数です-最初の倚角圢の各蟺ず2番目の倚角圢の各蟺を亀差させ、亀差点を曞き蟌み、さらに別の倚角圢に属する点を確認したすが、より良い速床を達成するために、アルゎリズムに埓っお亀差したすO`Rourkeアルゎリズムの元の説明- [3] 。



たた、その説明のオプションの1぀はalgolist.ruにありたす。 私たちの堎合、実装は、いく぀かの補足的なアむデアずずもに、本[1] p。334のアルゎリズムの説明に基づいおいたす。 この実装では 、ポリゎンに共通の蟺が ある堎合本[1]に蚘茉されおいるように、この堎合は別途考慮する必芁がありたすを考慮しおいたせんが 、共通の頂点を持぀堎合は正しく機胜したす。



以䞋のネタバレの䞋に、アルゎリズムの䞀般的な説明がありたす。



O'Rourkeアルゎリズムを知りたい
アルゎリズム远加の説明に぀いおは、䞊蚘の゜ヌスを参照しおください

  1. 最倧反埩回数が経過するたで2 *n + m以䞋であるこずが蚌明されたす、次の手順を実行したす。

    a。 1番目ず2番目のポリゎンの珟圚の゚ッゞを取埗したす。

    b それらが亀差する堎合-亀差点からの点を考慮したす亀差点ポリゎンに远加しただけで、远加を無芖する堎合がありたす。そうでない堎合-最初の亀差点でない堎合぀たり、すでに円を䜜っおいる、亀差点、それ以倖の堎合はアルゎリズムを終了したす-亀差点に到達したした。

    c。 次に珟圚の゚ッゞが亀差するかどうかに関係なく、 モヌション関数を呌び出したす。この関数は、最初のたたは2番目のポリゎンのりィンドりを1゚ッゞ前方に移動し、亀差ポリゎンに頂点を远加できるようにしたす。 䞻な行動は、 運動で正確に行われたす。
  2. 亀差点がなかった堎合、あるポリゎンが別のポリゎンの内偎にあるかどうかを刀断したすポむントがOの埌ろの凞ポリゎンに属しおいるかどうかを確認したすlogポリゎンの頂点の数- [1] 、59-60ペヌゞ。 そうであれば、それを返し、そうでなければ空の亀差点を返したす。


モヌション関数はさたざたな方法で実装でき、゚ッゞを移動しおいるポリゎンの番号ラベルを返したす。 抂念的に、圌女は自分の䞭で3぀のこずを行いたす。

- ケヌス番号を決定したす -最初のポリゎンの゚ッゞず2番目のポリゎンの゚ッゞの珟圚の盞察䜍眮。 これがアルゎリズムの䞻芁なアむデアです -互いに察する゚ッゞの䜍眮のケヌスを衚瀺したす。 これらの4぀の䜍眮はすべお[1]で詳しく説明されおいたすが、実装では、平面䞊のベクトルのスキュヌ積を䜿甚しお䜍眮が決定されたす。

-珟圚どのポリゎンのどの゚ッゞが「内偎」にあるか、぀たり、他の゚ッゞの「巊偎」にあるかを決定したす。これは斜めの補品でもチェックされたす終点が「巊偎」にある堎合は「巊偎」にありたす。

-゚ッゞの1぀の珟圚の端を亀差ポリゎンに曞き蟌むかどうかを決定したす。 これが目的のケヌスず内偎のリブに察応する堎合、远加する必芁がありたす。



さお、SplashGeom©では次のようになりたす。



ポリゎン亀差コヌド
 //   ,       Convex2D Rectangle::GetIntersectionalConvex2D(const Point2D& cur_point, const Line2D& halfplane) const { vector<Point2D> convex_points; Segment2D cur_side; Point2D intersection_point; for (int i = 0, sz = vertices_.size(); i < sz; ++i) { int j = (i + 1) % sz; cur_side = Segment2D(vertices_[i], vertices_[j]); intersection_point = halfplane.GetIntersection(cur_side); if (intersection_point != kInfPoint2D) convex_points.push_back(intersection_point); if (halfplane.Sign(cur_point) == halfplane.Sign(vertices_[i])) convex_points.push_back(vertices_[i]); } Convex2D result_polygon(MakeConvexHullJarvis(convex_points)); return result_polygon; } //       NumOfCase EdgesCaseNum(const Segment2D& first_edge, const Segment2D& second_edge) { bool first_looks_at_second = first_edge.LooksAt(second_edge); bool second_looks_at_first = second_edge.LooksAt(first_edge); if (first_looks_at_second && second_looks_at_first) { return NumOfCase::kBothLooks; } else if (first_looks_at_second) { return NumOfCase::kFirstLooksAtSecond; } else if (second_looks_at_first) { return NumOfCase::kSecondLooksAtFirst; } else { return NumOfCase::kBothNotLooks; } } // ,    "" WhichEdge WhichEdgeIsInside(const Segment2D& first_edge, const Segment2D& second_edge) { double first_second_side = Vector2D(second_edge).OrientedCCW(Vector2D(second_edge.a, first_edge.b)); double second_first_side = Vector2D(first_edge).OrientedCCW(Vector2D(first_edge.a, second_edge.b)); if (first_second_side < 0) { return WhichEdge::kSecondEdge; } else if (second_first_side < 0) { return WhichEdge::kFirstEdge; } else { return WhichEdge::Unknown; } } //   WhichEdge MoveOneOfEdges(const Segment2D& first_edge, const Segment2D& second_edge, Convex2D& result_polygon) { WhichEdge now_inside = WhichEdgeIsInside(first_edge, second_edge); NumOfCase case_num = EdgesCaseNum(first_edge, second_edge); WhichEdge which_edge_is_moving; switch (case_num) { case NumOfCase::kBothLooks: { if (now_inside == WhichEdge::kFirstEdge) { which_edge_is_moving = WhichEdge::kSecondEdge; } else { which_edge_is_moving = WhichEdge::kFirstEdge; } break; } case NumOfCase::kFirstLooksAtSecond: { which_edge_is_moving = WhichEdge::kFirstEdge; break; } case NumOfCase::kSecondLooksAtFirst: { which_edge_is_moving = WhichEdge::kSecondEdge; break; } case NumOfCase::kBothNotLooks: { if (now_inside == WhichEdge::kFirstEdge) { which_edge_is_moving = WhichEdge::kSecondEdge; } else { which_edge_is_moving = WhichEdge::kFirstEdge; } break; } } if (result_polygon.Size() != 0 && (case_num == NumOfCase::kFirstLooksAtSecond || case_num == NumOfCase::kSecondLooksAtFirst)) { Point2D vertex_to_add; if (now_inside == WhichEdge::kFirstEdge) { vertex_to_add = first_edge.b; } else if (now_inside == WhichEdge::kSecondEdge) { vertex_to_add = second_edge.b; } else { if (case_num == NumOfCase::kFirstLooksAtSecond) vertex_to_add = first_edge.b; // ?! else vertex_to_add = second_edge.b; } if (vertex_to_add != result_polygon.GetCurVertex()) result_polygon.AddVertex(vertex_to_add); } return which_edge_is_moving; } //    (    ,      ) Convex2D Convex2D::GetIntersectionalConvex(Convex2D& second_polygon) { Convex2D result_polygon; size_t max_iter = 2 * (this->Size() + second_polygon.Size()); Segment2D cur_fp_edge; // current first polygon edge Segment2D cur_sp_edge; // current second polygon edge Point2D intersection_point; bool no_intersection = true; WhichEdge moving_edge = WhichEdge::Unknown; for (size_t i = 0; i < max_iter; ++i) { cur_fp_edge = this->GetCurEdge(); cur_sp_edge = second_polygon.GetCurEdge(); intersection_point = cur_fp_edge.GetIntersection(cur_sp_edge); if (intersection_point != kInfPoint2D) { if (result_polygon.Size() == 0) { no_intersection = false; result_polygon.AddVertex(intersection_point); } else if (intersection_point != result_polygon.GetCurVertex()) { if (intersection_point == result_polygon.vertices_[0]) { break; // we already found the intersection polygon } else { result_polygon.AddVertex(intersection_point); } } } moving_edge = MoveOneOfEdges(cur_fp_edge, cur_sp_edge, result_polygon); if (moving_edge == WhichEdge::kFirstEdge) { this->MoveCurVertex(); } else { second_polygon.MoveCurVertex(); } } if (no_intersection == true) { if (second_polygon.Contains(this->GetCurVertex())) { result_polygon = *this; } else if (this->Contains(second_polygon.GetCurVertex())) { result_polygon = second_polygon; } } return result_polygon; }
      
      







この説明ずコヌドが圹に立぀こずを願っおいたす。



半平面の亀差点


したがっお、半平面の亀差点を構築するために必芁なものはすべお揃っおいたす。 さお、それをやっおみたしょう、そしおそれだけでなく、賢い方法で-亀差する半平面の操䜜の結合性のために、任意の順序でそれらを亀差させるこずができたす。぀たり、2぀の平面を亀差させ、さらに2぀の平面を亀差させ、次に亀差点を亀差させたす、これはすべおを個別に暪断するよりも早くなっおいたす。



そのため、 再垰を䜿甚するこずをお勧めしたす気分はすぐに悪くなりたした -ここでの䜜業は理解できたす。



コヌドを衚瀺
 Convex2D GetHalfPlanesIntersection(const Point2D& cur_point, const vector<Line2D>& halfplanes, const Rectangle& border_box) { if (halfplanes.size() == 1) { Convex2D cur_convex(border_box.GetIntersectionalConvex2D(cur_point, halfplanes[0])); return cur_convex; } else { int middle = halfplanes.size() >> 1; vector<Line2D> first_half(halfplanes.begin(), halfplanes.begin() + middle); vector<Line2D> second_half(halfplanes.begin() + middle, halfplanes.end()); Convex2D first_convex(GetHalfPlanesIntersection(cur_point, first_half, border_box)); Convex2D second_convex(GetHalfPlanesIntersection(cur_point, second_half, border_box)); return first_convex.GetIntersectionalConvex(second_convex); } }
      
      







軌跡をチャヌトに远加する


これで、指定したポむントのボロノむポリゎンができたした。それをダむアグラムに远加したす。 領域を任意の順序で取埗し、それらは互いに関連しおいないため、これには特に問題はありたせんFortuneアルゎリズムの実装ずは異なり、゚ッゞぞのポむンタヌで隣接する軌跡に移動できたす。



コヌドを衚瀺
 //      Voronoi2DLocus VoronoiDiagram2D::MakeVoronoi2DLocus(const Point2D& site, const vector<Point2D>& points, const Rectangle& border_box) { Voronoi2DLocus cur_locus; vector<Line2D> halfplanes; for (auto cur_point : points) { if (cur_point != site) { Segment2D cur_seg(site, cur_point); Line2D cur_halfplane(cur_seg.GetCenter(), cur_seg.NormalVec()); halfplanes.push_back(cur_halfplane); } } *cur_locus.region_ = GetHalfPlanesIntersection(site, halfplanes, border_box); cur_locus.site_ = site; return cur_locus; } //    VoronoiDiagram2D VoronoiDiagram2D::MakeVoronoiDiagram2DHalfPlanes(const vector<Point2D>& points, const Rectangle& border_box) { Voronoi2DLocus cur_locus; for (auto cur_point : points) { cur_locus = MakeVoronoi2DLocus(cur_point, points, border_box); this->diagram_.push_back(cur_locus); } return *this; }
      
      







そこで、ボロノむ図を䜜成する方法を孊びたした 、それは軌跡のベクトルリストの圢をしおいたす。 この゜リュヌションの欠点は、隣人に関する情報を取埗できないこずですおそらく、この欠点は実装を改善するこずで解消できたす。



さらに倚くの情報をRSEL  DCEL から取埗できたす。 この構造は、Fortuneのアルゎリズムで䜿甚されたす。



次に、Fortuneのアルゎリズムを、盎線ず「海岞線」を䜿甚しお説明したす氎着ず氎泳パンツを準備したす-ビヌチに行きたす 。 私の意芋では、これはボロノむ図の構築を実装し、 光の 「速床」で構築したい堎合に最も受け入れられるオプションです 。



のボロノむ図を構築するためのフォヌチュンのアルゎリズム



1987幎、スティヌブフォヌチュンは、 。 もちろん、それはそのような挞近線を持぀唯䞀の構築アルゎリズムではありたせんが、非垞に理解可胜であり、実装するのはそれほど難しくありたせんさらに、非垞に矎しく数孊的です 、私はそれを遞びたした。



フォヌチュンアルゎリズムの資料は、 こちら 、 こちら 、 こちら 、 こちらでご芧いただけたす 。



ずころで、 Habrahabrの蚘事はすでにこのアルゎリズムの怜蚎に専念しおいたす。



したがっお、アルゎリズムの䞻なアむデアは、いわゆるスむヌプラむンです。 特定のオブゞェクトセットに沿った盎線の動きを簡単にシミュレヌトできるため、蚈算ゞオメトリの倚くのアルゎリズムで䜿甚されたすたずえば、n個のセグメントを亀差させるアルゎリズムでもスむヌプラむンが䜿甚されたす。



どのように、䜕をするのかに぀いお話を始める前に、スむヌプラむンがどのように動くかを芋おみたしょう ここから 









いいですね。 実装では、すべおがほが同じで、RFPのみが通垞、巊から右ではなく䞊から䞋に移動したす。実際、すべおはそれほど滑らかではなく、むベントごずに発生したす以䞋を参照。



アルゎリズムの本質



n個のサむト平面䞊のポむントがありたす。 「䞊から䞋ぞ」、぀たり最倧の瞊座暙を持぀サむトから小さい座暙を持぀サむトぞ正確にはむベントからむベントぞに移動するスむヌプラむンがありたす。 すぐに泚意する必芁があるのは、図の構築に圱響を䞎えるのは、 より高いサむトたたは抜本的なラむン䞊にあるサむトのみ です 。



ZPが次のサむト ポむントむベントに到達するず、新しい攟物線アヌチが䜜成されたす。このサむトの焊点は 、 ディレクタヌです りィキペディアの攟物線に぀いお 。 この攟物線は平面を2぀の郚分に分割したす。攟物線の「内偎」領域はサむトに近いポむントに察応し、「倖偎」領域は掃匕線に近いポむントに察応し、攟物線䞊にあるポむントはサむトおよびGPから等距離にありたす。 攟物線は、サむトぞのRFPの䜍眮に応じお倉化したす。RFPがサむトからさらに䞋がれば䞋がるほど、攟物線は拡倧したすが、最初は䞀般にセグメントです「䞊向き」。



攟物線が拡倧するず、2぀のブレヌクポむントがありたす。他の攟物線ずの亀点「海岞線」です。 「海岞線」には、亀差点の1぀のポむントから他のポむントたでの攟物線状の円匧が栌玍されおいるため、ビヌチラむンが刀明したす。 実際、このアルゎリズムでは、この「海岞線」の動きをシミュレヌトしたす。 これらの同じブレヌクポむントはボロノむセルの゚ッゞに沿っお正確に移動するためです結局、コントロヌルポむントはこれらの攟物線が察応する䞡方のサむトから、さらにはRFPからも等距離であるこずがわかりたす。



そしおちょうどその瞬間、異なる攟物線の1぀にある2぀の制埡点が「䌚う」ずき、぀たり1぀に倉わるかのように、この点がボロノむセルの頂点になりたす円むベントが発生したす。この時点で、これらの2぀のポむントの間にあった円匧-「厩壊」し、「海岞線」から削陀されたす。 次に、このポむントを前の察応するポむントに接続し、ボロノむセルの゚ッゞを取埗したす。



アルゎリズム



そのため、スむヌプラむンを䞋に移動するず、2 皮類のむベントが発生したす 。



ポむントむベント


ポむントのむベントは、サむトの1぀でのPOのヒットです。そのため、このサむトに察応する新しい攟物線を䜜成し、2぀のブレヌクポむントを远加したす実際、最初は1぀ですが、アヌチを拡匵するず、すでに2぀ありたす-この亀差点海岞線のある攟物線぀たり、既存の攟物線の前面。 このアルゎリズムでは、攟物線たたはむしろ「海岞線」に属する郚分- アヌチ は、ポむントむベントの堎合にのみ 「海岞線に挿入」されたす。぀たり、新しいアヌチはポむントむベントの凊理時にのみ衚瀺されたす。



ずころで、次の写真は、なぜこのような「攟物線のかけら」の関連が「海岞線」ず呌ばれるのかを瀺しおいたす。







サヌクルむベント


サヌクルむベントは、1぀のアヌチの削陀に䌎うボロノむセルの新しい頂点の出珟です。ここでのダむアグラムの新しい頂点の出珟は、アヌチの巊右の点ず新しい頂点のアプロヌチにより、巊、䞭倮、右、䞭倮の「厩壊」の3぀のアヌチがあったこずを意味するためですボロノむ図。 このアルゎリズムでは、円むベントが発生した堎合にのみ攟物線アヌチが「海岞線」から削陀されたす。぀たり、アヌチは円むベントを凊理するずきにのみ削陀できたす。









ボロノむ図の頂点は垞に図の正確に3぀の゚ッゞの亀点にあるずいう定理があり、図の頂点は3぀のサむトを通る円の䞭心であり、この点からスむヌプ線たでの距離も同じであるず述べおいたすこの円の半埄に等しいこれは「海岞線」䞊にあるポむントのプロパティです。 これは、3぀のサむトを通過する円の最䞋点がスむヌプラむンの䞋たたはスむヌプラむン䞊にあるずきに、この最䞋点の円むベントをむベントキュヌにプッシュするためです。ボロノむ図。



むベントポむントたたはサヌクルでは、1぀の特定のアヌチが接続されおいるこずが重芁です。 これは、むベントを凊理するずきに圹立ちたす。 たた、時間内にRSDS  DCEL に゚ッゞを远加する必芁があるこずを忘れおはなりたせん構造内のポむント1、以䞋を参照。したがっお、アヌチず゚ッゞの関係を理解する必芁がありたす。



したがっお、線の動きは離散的です。 サむト䞊の任意の瞬間、 たたは 3぀のサむトを通る円の䞋郚の線で 、その䞭心はボロノむ図の新しい頂点です。 玠晎らしい。



䞀般的なアルゎリズム 



  1. 最初にポむントむベントで初期化するむベントのキュヌ優先順䜍付きを䜜成したす-このサむトのセット結局、ポむントむベントは各サむトに察応したす;



  2. キュヌが空ではない間



    a。 それからむベントを取りたす。

    b これがポむントむベントの堎合、ポむントむベントを凊理したす。

    c。 これがサヌクルむベントの堎合、サヌクルむベントを凊理したす。



  3. 残りのすべおの゚ッゞを仕䞊げたすborder_boxを䜿甚。


実装



Fortuneアルゎリズムの実装に぀いおは、別の蚘事で詳现に怜蚎したすが、ここでは、その理解に圹立぀可胜性のあるいく぀かの開発に぀いお説明したす。



必芁な構造


このアルゎリズムを実装するには、いく぀かの構造クラスが必芁です。





このようなデヌタ構造を䜿甚しお、䞀般的なアルゎリズムの実装を䜜成できたす。



コヌドを衚瀺
 VoronoiDiagram2D VoronoiDiagram2D::MakeVoronoiDiagram2DFortune(const vector<Point2D>& points, const Rectangle& border_box) { priority_queue<Event> events_queue(points.begin(), points.end()); shared_ptr<Event> cur_event; BeachSearchTree beach_line; DCEL edges; while (!events_queue.empty()) { cur_event = make_shared<Event>(events_queue.top()); shared_ptr<const PointEvent> is_point_event(dynamic_cast<const PointEvent *>(cur_event.get())); if (is_point_event) { events_queue.pop(); beach_line.HandlePointEvent(*is_point_event, border_box, events_queue, edges); } else { shared_ptr<const CircleEvent> is_circle_event(dynamic_cast<const CircleEvent *>(cur_event.get())); events_queue.pop(); beach_line.HandleCircleEvent(*is_circle_event, border_box, events_queue, edges); } } edges.Finish(border_box); this->dcel_ = edges; return *this; }
      
      







HandlePointEventずHandleCircleEventのすべおのロゞックず耇雑さは、別の蚘事の䞻題になりたす。以降、実装に圹立぀いく぀かの補助関数を瀺したす。



ヘルパヌ関数


攟物線の亀差点アヌチ


RFPの䜍眮に応じお、2぀の攟物線アヌチの亀差点を取埗できる必芁がありたす。 点x 'およびy'に焊点を圓おた攟物線の方皋匏ず、y軞に沿った䜍眮がlである盎接行列は、次の方皋匏で䞎えられたす導出可胜。







ずころで、ブラケットの前の郚分はこの攟物線の焊点パラメヌタです 。 ここから、攟物線方皋匏の察応する係数を「匕き出し」、簡単な方法で2぀の非線圢方皋匏を解くこずができたす。䞀方から他方を枛算し、最初に芋぀かった根を代入しお、2぀のポむントを取埗したす。 最も高いポむントは「海岞線」の背埌にあるため、䜎いポむント぀たり、瞊座暙が小さいポむントに興味がありたす。 説明されたアクションは、次のコヌドに反映されたす。



パラボヌル亀差点コヌド
 pair<Point2D, Point2D> Arch::GetIntersection(const Arch& second_arch, double line_pos) const { pair<Point2D, Point2D> intersect_points; double p1 = 2 * (line_pos - this->focus_->y); double p2 = 2 * (line_pos - second_arch.focus_->y); // is not 0.0, because line moved down if (fabs(p1) <= EPS) { intersect_points.first = this->GetIntersection(Ray2D(*this->focus_, Point2D(this->focus_->x, this->focus_->y + 1))); intersect_points.second = intersect_points.first; } else { // solving the equation double a1 = 1 / p1; double a2 = 1 / p2; double a = a2 - a1; double b1 = -this->focus_->x / p1; double b2 = -second_arch.focus_->x / p2; double b = b2 - b1; double c1 = pow(this->focus_->x, 2) + pow(this->focus_->y, 2) - pow(line_pos, 2) / p1; double c2 = pow(second_arch.focus_->x, 2) + pow(second_arch.focus_->y, 2) - pow(line_pos, 2) / p1; double c = c2 - c1; double D = pow(b, 2) - 4 * a * c; if (D < 0) { intersect_points = make_pair(kInfPoint2D, kInfPoint2D); } else if (fabs(D) <= EPS) { double x = -b / (2 * a); double y = a1 * pow(x, 2) + b1 * x + c1; intersect_points = make_pair(Point2D(x, y), Point2D(x, y)); } else { double x1 = (-b - sqrt(D)) / (2 * a); double x2 = (-b + sqrt(D)) / (2 * a); double y1 = a1 * pow(x1, 2) + b1 * x1 + c1; double y2 = a1 * pow(x2, 2) + b1 * x2 + c1; intersect_points = make_pair(Point2D(x1, y1), Point2D(x2, y2)); } } return intersect_points; }
      
      







3点で円を描く


円むベントを凊理するずき、3぀のアヌチサむトの焊点から構成される円の䞭心ず最䞋点を決定する必芁がありたす。3点を䜿甚しお円を構築するためのいく぀かの分析アルゎリズムがありたす構築により、その䞭心ず半埄を取埗するこずを意味したす、私たちのプログラムではこれが行われたすアルゎリズムのおかげで-最初の2点ず2番目の2点をセグメントで接続したす。䞭心は䞭倮の垂線の亀点にあり、半埄は䞭心から3点のいずれかたでの距離です。速くお矎しい



3぀のポむントで円建蚭コヌドを衚瀺する
 Circle::Circle(const Point2D& p1, const Point2D& p2, const Point2D& p3) { Segment2D first_segment(p1, p2); Segment2D second_segment(p2, p3); Line2D first_perpendicular(first_segment.GetCenter(), first_segment.NormalVec()); Line2D second_perpendicular(second_segment.GetCenter(), second_segment.NormalVec()); center_ = first_perpendicular.GetIntersection(second_perpendicular); little_haxis_ = big_haxis_ = center_.l2_distance(p1); }
      
      







ポむントむベント凊理


ポむントむベントは、PointEventをキュヌから取り出したずきです。䜕が入っおいるのこのむベントが関連付けられおいるサむトのみがありたす。



それを凊理するずきに䜕をしたすか 「海岞線」に新しいアヌチを远加し、ツリヌの「必芁に応じお」すべおの接続を蚭定し、3぀の可胜なケヌスのいずれかでサヌクルむベントが発生したかどうかを確認したす。新しいサむトが参加できるすべおのケヌスを確認する必芁がありたす。



アヌチを远加するずきはどうしたすかバむナリの「海岞線」ツリヌx座暙でその堎所を探し、それを挿入したす。









貌り付けたら䜕をしたすか新しいアヌチが2぀のポむントで亀差するアヌチぞのポむンタヌを芋぀けたした亀差ポむントがコントロヌルポむントの1぀に正確に圓たる堎合は個別に考慮されたす-亀差は2぀の攟物線-巊右にありたす。



぀たり、「壊れる」このアヌチを取り、代わりに5぀のノヌド1、5、はいを挿入したす-arch1、bp1、arch2、bp2、arch3。 Arch1は、新しいアヌチが亀差するアヌチの巊偎郚分です。぀たり、巊のブレヌクポむントの巊にあるピヌス、bp1は巊のブレヌクポむント新しいアヌチの巊の亀差、arch2は新しいアヌチ、bp2は右のブレヌクポむントです右新しいアヌチの亀差点、arch3は、新しいアヌチが亀差するアヌチの正しい郚分です。



ポむントむベントがボロノむ図の新しい゚ッゞたたは、゚ッゞがある堎合がありたすを生成するこずは泚目に倀したす。



新しい海岞線を「海岞線」に远加するための可胜なオプションの1぀は、Habréのこの蚘事で説明されおいたす。



サヌクルむベントハンドリング


サヌクルむベントは、キュヌからCircleEventを匕き出したずきです。



䜕が入っおいるのその䞭にポむントがありたす-これは、3぀のサむトを通過する特定の円の最䜎ポむントであり、削陀すべきアヌチです。ツリヌには2぀のコントロヌルポむントがあり、圌女自身、コントロヌルポむントは最終的に1぀になりたす。そしお、アヌチをツリヌから慎重に削陀し、すべおの「芪子関係」を再構築する必芁がありたす。実際、このむベントを凊理するず、ツリヌ内の3぀のノヌドが1぀に眮き換えられたす2぀のブレヌクポむントず1぀のブレヌクポむントを持぀アヌチ。



サヌクルむベントがボロノむ図の2぀の゚ッゞを完了するこず、぀たり、このむベントが凊理されるず゚ッゞが終了するこずに泚意しおください。



たた、むベント凊理の重芁な郚分は、゚ッゞの成長を監芖し、それらをリストに远加しお゚ッゞを完成させる方法です。぀たり、最終的にはすべお「終了」し、有限であるか、境界に眮かれたす長方圢。



結果の小さな分析


出力ですでにRSDSDCELを受け取るので、十分な情報を取埗できたす-察応するサむトを知っおいる各゚ッゞに぀いお、この゚ッゞの「ツむン」を取埗し、そのサむトを芋぀けお、出来䞊がり-隣人を認識したしたリストに、すべおのサむトの隣人のリストを䜜成できたす。これはすでに達成されおいたす。 + = 



さらに、゚ッゞに「れロ」サむトがある堎合、「境界サむト」、぀たり「無限」の軌跡を持぀サむトにいたす。うヌん、しかしこれにより、ポむントの初期セットの凞包を構築するこずができたす; 最埌に、玠晎らしい。



たあ、䞀般に、RSDSは本質的にグラフなので、倚くのグラフアルゎリズムでこのリストを匕き続き䜿甚できたす。



のボロノむ図を䜜成するための再垰アルゎリズム



このアルゎリズムは本[1]p。260で提䟛されおいたす。ここでは、Fortuneアルゎリズムずよく䌌おいたすが、このオプションを実装しなかったため、構築アルゎリズムのみを提䟛したす。



アルゎリズム



  1. Sサむトのセット党䜓を2぀のほが等しい郚分奇数個のポむントがある堎合がありたすS 1ずS 2に分割したす。
  2. S 1およびS 2のボロノむ図を再垰的に䜜成したす。
  3. 結果の図を組み合わせお、Sの図を取埗したす。


アルゎリズムの䞀般的な説明は耇雑ではありたせんが、アルゎリズムには独自の埮劙な点がありたす。詳现に぀いおは、指定された本を参照しおください。



甹途



ボロノむ図のすべおのアプリケヌションの完党なリストはここにありたす。私にずっお最も興味深いず思われたものをいく぀か玹介したす倚くの情報は[1]から取埗したした。



プログラミング、ゲヌム開発、地図䜜成



蚈算幟䜕孊ボロノむ図での問題解決がすべおの必芁な最初のある近接ポむントを、むしろ、それはのタスクで特別賞チャヌト䞎え、すべおの最近傍 ..かかわらず、ない倧音量の音楜が含たれたものを、同様の方法、それはそう簡単ではありたせんので、 [1]。ボロノむ図を䜿甚しお、背埌に凞包を構築できたす「レむ」゚ッゞを芋お、それらが属するサむトを芋぀け、それらを船䜓に含めたす。



ボロノむ図ずドロネヌ䞉角圢分割の間にも重芁な関係があり、次々に構築するこずができたす圌らはお互いにデュアルなので -゚ッゞが隣接サむトは、最終的にドロネヌ䞉角圢分割取埗接続vikikonspektyを。ゲヌム開発で



ボロノむ図を䜿甚する䟋は、たずえばこの蚘事にありたす。ここでは、ゲヌム゚ンゞンのナビゲヌションシステムは図に基づいおいたす。



さたざたなゞオロケヌション゜フトりェアがボロノむ図を䜿甚する可胜性がありたす。䜍眮情報参照システムは、Voronoiダむアグラムを䜿甚しお、たずえば、堎所のさたざたな怜玢ず分析のために、最寄りの食料品店を決定できたす。







ここでは、地図䜜成でのチャヌトの䜿甚に蚀及するこずもできたす-地域の境界の抂芁ず、それらに基づいたさらなる分析。ずにかく、色付きのボロノむ図の助けを借りお、䜕かの分垃を瀺す地理図を明確に瀺すこずができたす。そこには、必芁なむンゞケヌタヌ枩床などの掚移が衚瀺されたす。









もちろん、Voronoiダむアグラムを䜿甚しおさたざたなフィルタヌ写真ハンドラヌを䜜成し、ある皮の「モザむク」を䜜成できたす。









しかし、これはアプリケヌションの始たりにすぎたせん。



建築ず蚭蚈においお



ボロノむ図は矎しい図圢であり、䞀皮の「幟䜕孊的なりェブ」であるため、人々が建築ずデザむンでボロノむ図を䜿甚するずいうアむデアを思い぀いたのは非垞に論理的です。すべおの䜜成。 䟋











考叀孊で



[1]から
考叀孊では、ボロノむポリゎンを䜿甚しお、叀代文化のツヌルの䜿甚範囲をマッピングし、競合する貿易の䞭心の圱響を研究しおいたす。

生態孊では、䜓が生き残る胜力は、食物ず光のために戊わなければならない隣人の数に䟝存したす。
-これは非垞に論理的です。なぜなら、通垞、近隣の地域は「生存」のために戊うからです。



モデリングず認識においお



この蚘事では、Voronoiの3Dダむアグラムに぀いおは説明したせんが、物理孊およびオブゞェクトの3Dモデリングに倚くの甚途がありたす。空間内のオブゞェクトのさたざたな皮類のグリッドおよびスケルトンは、ボロノむ図を䜿甚しお構築できたすただし、より倚くの堎合、ドロネヌ䞉角圢分割を䜿甚したす。さたざたなオブゞェクトの







3Dスキャンおよびコンピュヌタヌビゞョンでは、ボロノむ図ずドロネヌ䞉角圢分割も䜿甚できたす。たた、ロボット工孊障害物を考慮したロボットの動きず密接に関連しおいたす。









生物孊ず化孊



[1]から
耇雑なボロノむ図が構築される研究のために、電気力ず短距離力を組み合わせた圱響は、分子の構造を決定するのに圹立ちたす。


ボロノむ図の䜿甚に関する別の興味深い蚘事がありたす。



興味深い事実



自然は驚くべきものです。キリンの色が実際にボロノむ図のように芋えるこずがわかったからです。これは肉県で芋るこずができたす









次の芳察も泚目に倀したす。これは、暹朚の葉の䞊でも図を芋るこずができるこずを瀺しおいたす。









ちなみに、1幎ほど前に、1぀の事件がボロノむ図にも関連付けられたした。ここでは、新しいモスクワプロゞェクトのロゎに䜿甚されたした。











そしお-最埌に-サむト内に䞭心を持぀円の成長ずずもに図がどのように衚瀺されるかを芋るこずができるビデオ







ここでは、いく぀かの火源を持぀火の広がりずの類䌌性を描くこずができたす。



さお、誰が自分に最も近いかを知っおいる䞖界ぞの私たちの小さな旅行は終わりたした。この蚘事を楜しんで、新しい、䟿利で面癜い䜕かを本圓に孊んだこずを願っおいたす。



ご枅聎ありがずうございたした



参照資料



[1]準備F.、Shaymos M.蚈算幟䜕孊。はじめに1989

[2] Alexandrov A. D.、Werner A. L.、Ryzhik V. I. Stereometry。空間の幟䜕孊

[3]ゞョセフ・オルヌク。Cの蚈算幟䜕孊



」この蚘事は、Institute of Physics and Technologyの1幎生であるIlya Zakharkinによっお䜜成されたした。

»FIVT MIPT Kirilenko ElenaずKasimova Nadezhdaの1幎生も図曞通の執筆を手䌝いたした。

»ラむブラリをテストするこずで、ダロスラフ・スピリンを助けたした。

»指導者のGadelshin IlnurずGafarov Rustamに特に感謝したす。



All Articles