Skip to content

Commit 458bec9

Browse files
authored
ch19-smallest-enclosing-circle: 平面上の点群に対し最小包含円を求めるアルゴリズムの実装 (#1272)
1 parent f28b27f commit 458bec9

File tree

3 files changed

+225
-3
lines changed

3 files changed

+225
-3
lines changed

Siv3D/include/Siv3D/Geometry2D.hpp

+51-1
Original file line numberDiff line numberDiff line change
@@ -2762,6 +2762,56 @@ namespace s3d
27622762
[[nodiscard]]
27632763
Polygon ConcaveHull(const Vec2* points, size_t size, double concavity = 2.0, double lengthThreshold = 0.0);
27642764

2765+
//////////////////////////////////////////////////
2766+
//
2767+
// SmallestEnclosingCircle
2768+
//
2769+
//////////////////////////////////////////////////
2770+
2771+
/// @brief 点 p0, p1, p2 の最小包含円を返します。
2772+
/// @param p0 点 0
2773+
/// @param p1 点 1
2774+
/// @param p2 点 2
2775+
/// @return 点 p0, p1, p2 の最小包含円
2776+
[[nodiscard]]
2777+
Circle SmallestEnclosingCircle(const Vec2& p0, const Vec2& p1, const Vec2& p2);
2778+
2779+
/// @brief 点 p0, p1, p2, p3 の最小包含円を返します。
2780+
/// @param p0 点 0
2781+
/// @param p1 点 1
2782+
/// @param p2 点 2
2783+
/// @param p3 点 3
2784+
/// @param tolerance 点が円に含まれているかの判定時の許容誤差。相対誤差または絶対誤差がこの値以下であれば、点が円に含まれているとみなします。
2785+
/// @return 点 p0, p1, p2, p3 の最小包含円
2786+
[[nodiscard]]
2787+
Circle SmallestEnclosingCircle(const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, double tolerance = 1e-8);
2788+
2789+
/// @brief 点群 points の最小包含円を返します。
2790+
/// @param points 点群
2791+
/// @param tolerance 点が円に含まれているかの判定時の許容誤差。相対誤差または絶対誤差がこの値以下であれば、点が円に含まれているとみなします。
2792+
/// @return 点群 points の最小包含円
2793+
[[nodiscard]]
2794+
Circle SmallestEnclosingCircle(Array<Vec2> points, double tolerance = 1e-8);
2795+
2796+
/// @brief 点群 points の最小包含円を返します。
2797+
/// @tparam URBG 乱数生成器の型
2798+
/// @param points 点群
2799+
/// @param tolerance 点が円に含まれているかの判定時の許容誤差。相対誤差または絶対誤差がこの値以下であれば、点が円に含まれているとみなします。
2800+
/// @param urbg 乱数生成器。このアルゴリズムには点群の順序をシャッフルする処理が含まれていて、乱数生成器を使用します。
2801+
/// @return 点群 points の最小包含円
2802+
SIV3D_CONCEPT_URBG
2803+
[[nodiscard]]
2804+
Circle SmallestEnclosingCircle(Array<Vec2> points, double tolerance, URBG&& urbg);
2805+
2806+
/// @brief 点群 points の最小包含円を返します。
2807+
/// @tparam URBG 乱数生成器の型
2808+
/// @param points 点群
2809+
/// @param urbg 乱数生成器。このアルゴリズムには点群の順序をシャッフルする処理が含まれていて、乱数生成器を使用します。
2810+
/// @param tolerance 点が円に含まれているかの判定時の許容誤差。相対誤差または絶対誤差がこの値以下であれば、点が円に含まれているとみなします。
2811+
SIV3D_CONCEPT_URBG
2812+
[[nodiscard]]
2813+
Circle SmallestEnclosingCircle(Array<Vec2> points, URBG&& urbg, double tolerance = 1e-8);
2814+
27652815
//////////////////////////////////////////////////
27662816
//
27672817
// Subtract
@@ -2869,4 +2919,4 @@ namespace s3d
28692919
}
28702920
}
28712921

2872-
# include "detail/Geometry2D.ipp"
2922+
# include "detail/Geometry2D.ipp"

Siv3D/include/Siv3D/detail/Geometry2D.ipp

+109-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,26 @@ namespace s3d
4141
&& (point.y < bottom);
4242
}
4343

44+
/// @brief 点 p が円 c に含まれているかを判定します。
45+
/// @param c 円
46+
/// @param p 点
47+
/// @param tolerance 許容誤差(相対誤差または絶対誤差のいずれかが許容誤差以下であれば許容)
48+
/// @return 点 p が円 c に含まれている場合 true, それ以外の場合は false
49+
[[nodiscard]]
50+
inline bool Contains(const Circle& c, const Vec2& p, const double tolerance = 1e-8)
51+
{
52+
const double dSquared = (c.center - p).lengthSq();
53+
const double rSquared = (c.r * c.r);
54+
const double err = Max(0.0, (dSquared - rSquared));
55+
56+
if (rSquared == 0)
57+
{
58+
return (err <= tolerance);
59+
}
60+
61+
return (((err / rSquared) <= tolerance) || (err <= tolerance));
62+
}
63+
4464
//
4565
// http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
4666
//
@@ -1752,5 +1772,93 @@ namespace s3d
17521772
sum += ((p0.x - p3.x) * (p0.y + p3.y));
17531773
return (sum < 0);
17541774
}
1775+
1776+
//////////////////////////////////////////////////
1777+
//
1778+
// SmallestEnclosingCircle
1779+
//
1780+
//////////////////////////////////////////////////
1781+
//-----------------------------------------------
1782+
// Authors (OpenSiv3D challenge #19)
1783+
// - あぷりばーど
1784+
// - Nachia
1785+
// - Luke256
1786+
// - ラクラムシ
1787+
// - polyester
1788+
// - sasa
1789+
//-----------------------------------------------
1790+
1791+
SIV3D_CONCEPT_URBG_
1792+
Circle SmallestEnclosingCircle(Array<Vec2> points, const double tolerance, URBG&& urbg)
1793+
{
1794+
if (points.size() == 0)
1795+
{
1796+
return Circle{};
1797+
}
1798+
1799+
if (points.size() == 1)
1800+
{
1801+
return Circle{ points[0], 0.0 };
1802+
}
1803+
1804+
if (points.size() == 2)
1805+
{
1806+
return Circle{ points[0], points[1] };
1807+
}
1808+
1809+
if (points.size() == 3)
1810+
{
1811+
return SmallestEnclosingCircle(points[0], points[1], points[2]);
1812+
}
1813+
1814+
if (points.size() == 4)
1815+
{
1816+
return SmallestEnclosingCircle(points[0], points[1], points[2], points[3], tolerance);
1817+
}
1818+
1819+
points.shuffle(std::forward<URBG>(urbg));
1820+
1821+
// 適当な 1 点を含む最小包含円から始めて、少しずつ広げていく戦略を取る。
1822+
// 含まれない点があったら、それが境界上になるように新たに取り直す。
1823+
Circle circle{ points[0], 0.0 };
1824+
1825+
for (size_t i = 1; i < points.size(); ++i)
1826+
{
1827+
const Vec2& p0 = points[i];
1828+
1829+
if (not detail::Contains(circle, p0, tolerance))
1830+
{
1831+
circle = Circle{ p0, 0.0 };
1832+
1833+
for (size_t j = 0; j < i; ++j)
1834+
{
1835+
const Vec2& p1 = points[j];
1836+
1837+
if (not detail::Contains(circle, p1, tolerance))
1838+
{
1839+
circle = Circle{ p0, p1 };
1840+
1841+
for (size_t k = 0; k < j; ++k)
1842+
{
1843+
const Vec2& p2 = points[k];
1844+
1845+
if (not detail::Contains(circle, p2, tolerance))
1846+
{
1847+
circle = Triangle(p0, p1, p2).getCircumscribedCircle();
1848+
}
1849+
}
1850+
}
1851+
}
1852+
}
1853+
}
1854+
1855+
return circle;
1856+
}
1857+
1858+
SIV3D_CONCEPT_URBG_
1859+
Circle SmallestEnclosingCircle(Array<Vec2> points, URBG&& urbg, double tolerance)
1860+
{
1861+
return SmallestEnclosingCircle(std::move(points), tolerance, std::forward<URBG>(urbg));
1862+
}
17551863
}
1756-
}
1864+
}

Siv3D/src/Siv3D/Geometry2D/SivGeometry2D.cpp

+65-1
Original file line numberDiff line numberDiff line change
@@ -5278,6 +5278,70 @@ namespace s3d
52785278
return detail::ConcaveHull(points, size, concavity, lengthThreshold);
52795279
}
52805280

5281+
//////////////////////////////////////////////////
5282+
//
5283+
// SmallestEnclosingCircle
5284+
//
5285+
//////////////////////////////////////////////////
5286+
//-----------------------------------------------
5287+
// Authors (OpenSiv3D challenge #19)
5288+
// - あぷりばーど
5289+
// - Nachia
5290+
// - Luke256
5291+
// - ラクラムシ
5292+
// - polyester
5293+
// - sasa
5294+
//-----------------------------------------------
5295+
5296+
Circle SmallestEnclosingCircle(const Vec2& p0, const Vec2& p1, const Vec2& p2)
5297+
{
5298+
// 三角形 (p0, p1, p2) に対して鈍角の存在を判定し、もしあればその対辺(最長辺)を弦とする円が最小の円となる。
5299+
if ((p1 - p0).dot(p2 - p0) <= 0.0)
5300+
{
5301+
return Circle{ p1, p2 };
5302+
}
5303+
5304+
if ((p0 - p1).dot(p2 - p1) <= 0.0)
5305+
{
5306+
return Circle{ p0, p2 };
5307+
}
5308+
5309+
if ((p0 - p2).dot(p1 - p2) <= 0.0)
5310+
{
5311+
return Circle{ p0, p1 };
5312+
}
5313+
5314+
// 鋭角三角形の場合は (p0, p1, p2) の外接円が最小となる。
5315+
return Triangle{ p0, p1, p2 }.getCircumscribedCircle();
5316+
}
5317+
5318+
Circle SmallestEnclosingCircle(const Vec2& p0, const Vec2& p1, const Vec2& p2, const Vec2& p3, const double tolerance)
5319+
{
5320+
Circle circle = SmallestEnclosingCircle(p0, p1, p2);
5321+
5322+
if (not detail::Contains(circle, p3, tolerance))
5323+
{
5324+
circle = SmallestEnclosingCircle(p0, p1, p3);
5325+
5326+
if (not detail::Contains(circle, p2, tolerance))
5327+
{
5328+
circle = SmallestEnclosingCircle(p0, p2, p3);
5329+
5330+
if (not detail::Contains(circle, p1, tolerance))
5331+
{
5332+
circle = SmallestEnclosingCircle(p1, p2, p3);
5333+
}
5334+
}
5335+
}
5336+
5337+
return circle;
5338+
}
5339+
5340+
Circle SmallestEnclosingCircle(Array<Vec2> points, const double tolerance)
5341+
{
5342+
return SmallestEnclosingCircle(std::move(points), tolerance, GetDefaultRNG());
5343+
}
5344+
52815345
//////////////////////////////////////////////////
52825346
//
52835347
// Subtract
@@ -5581,4 +5645,4 @@ namespace s3d
55815645

55825646
return { minX, minY, (maxX - minX), (maxY - minY) };
55835647
}
5584-
}
5648+
}

0 commit comments

Comments
 (0)