주어진 점집합을 감싸는 최소 면적의 bounding box(외접 직사각형)는 먼저 점집합의 Convex Hull을 구한 후 Rotating Calipers 알고리즘을 이용하여 계산할 수 있다. 다음 알고리즘은 반시계 방향으로 정렬된 Convex Hull을 입력으로 받아 최소 면적의 bounding box를 구하는 방법이다.
먼저 좌표축에 평행한 초기 bounding box를 구성한 후, Convex Hull의 네 방향에 접하는 네 개의 caliper를 생각한다. 각 caliper는 현재 접하고 있는 Convex Hull의 변과 함께 회전하며, 다음 변과 이루는 회전각을 계산한다. 네 개의 caliper에 대해 계산된 회전각 가운데 가장 작은 각만큼 네 개의 caliper를 동시에 반시계 방향으로 회전시킨다. 회전이 끝나면 새로운 방향에서의 bounding box를 계산하고 그 면적을 구한다. 이 과정을 반복하면서 면적이 가장 작은 bounding box를 선택하면 된다.
이 알고리즘은 Convex Hull을 바닥 위에서 굴리면서 각 방향에서 물체를 감싸는 bounding box를 구하는 과정으로 생각하면 직관적으로 이해하기 쉽다. Convex Hull을 한 바퀴 굴리는 동안 모든 변은 정확히 한 번씩 bounding box의 한 변과 평행하게 되므로, 최소 면적의 bounding box가 될 수 있는 모든 후보를 빠짐없이 검사하게 된다.
Convex Hull의 꼭짓점 수를 (N)이라 하면, 각 caliper는 Convex Hull의 각 변을 최대 한 번씩만 따라 이동하므로 Rotating Calipers 알고리즘의 시간 복잡도는 \(O(N)\)이다. 따라서 Convex Hull이 이미 계산되어 있다면 최소 면적의 bounding box는 선형 시간에 구할 수 있다. Convex Hull 계산까지 포함한 전체 알고리즘의 시간 복잡도는 \(O(n\log n)+O(N)\)이며, 일반적으로 \(N \le n\)이므로 전체 시간 복잡도는 \(O(n\log n)\)이 된다.

std::vector<CfPt> MinVolumeBox(const std::vector<CfPt>& chull) {
if (chull.size() < 3) return std::vector<CfPt> ();
std::vector<CfPt> edges(chull.size());
std::vector<int> visited(chull.size(), 0); // initialized = 0;
enum {BB_NONE, BB_LEFT, BB_RIGHT, BB_BOTTOM, BB_TOP};
for (int i = 0; i < chull.size(); i++) {
const CfPt e = chull[(i+1) % chull.size()] - chull[i];
edges[i] = e / e.Norm();// make a unit vector;
}
// 먼저 좌표의 최소-최대 값을 구한다;
// index을 순환할 때, 항상 최대-최소가 (index+chull.size)%chull.size가 되게
double xmin = chull[0].x, xmax = xmin;
double ymin = chull[0].y, ymax = curr;
int left = 0, bot = 0, right = 0, top = 0 ;
for (int i = 1; i < chull.size(); i++) {
if (chull[i].x <= xmin) { xmin = chull[i].x; left = i;}
else if (chull[i].x >= xmax) { xmax = chull[i].x; right = i;}
if (chull[i].y <= ymin) { ymin = chull[i].y; bot = i;}
else if (chull[i].y >= ymax) { ymax = chull[i].y; top = i;}
}
if (chull[0].x <= xmin) { xmin = chull[0].x; left = 0;}
else if ( chull[0].x >= xmax) { xmax = chull[0].x; right = 0;}
if (chull[0].y <= ymin) { ymin = chull[0].y; bot = 0;}
else if (chull[0].y >= ymax) { ymax = chull[0].y; top = 0;}
/*____Initial mvbox Setting____*/
CfPt center = CfPt(xmin + xmax, ymin + ymax)/2;
CfPt ax0 = CfPt(1,0), ax1 = CfPt(0,1); // mvbox의 unit vectors;
CfPt eh = ax0, ev = ax1; // working unit vectors;
double hscale = (xmax - xmin) / 2;
double vscale = (ymax - ymin) / 2;
double minarea = hscale * vscale; // area / 4;
int done = 0;
while (!done) {
// edges[bot]는 chull[bot+1]-chull[bot];
// edges[bot],edges[right],edges[top],edges[left] 중에서
// 현재의 mvbox의 변(eh, ev,-eh,-ev)과 가장 작은 각(max_cosine)을 이루는
// edge를 찾음;
int iflag = BB_NONE;
double max_cosine = 0;
double cosine = eh * edges[bot];
if (cosine > max_cosine) { max_cosine = cosine; iflag = BB_BOTTOM;}
cosine = ev * edges[right]; ;
if (cosine > max_cosine) { max_cosine = cosine; iflag = BB_RIGHT;}
cosine = -eh * edges[top];
if (cosine > max_cosine) { max_cosine = cosine; iflag = BB_TOP;}
cosine = -ev * edges[left];
if (cosine > max_cosine) { max_cosine = cosine; iflag = BB_LEFT;}
switch (iflag) {
case BB_BOTTOM:
if (!visited[bot]) {// edges[bot]가 mvbox의 한변에 포함됨;
eh = edges[bot]; ev = eh.Rot90();
// 다음 변으로 회전;
visited[bot] = 1;
if (++bot == chull.size()) bot = 0;
} else done = 1;
break;
case BB_RIGHT:
if (!visited[right]) {// edges[right]가 mvbox의 한변에 포함됨;
ev = edges[right]; eh = -ev.Rot90();
// 다음 변으로 회전;
visited[right] = 1;
if (++right == chull.size()) right = 0;
} else done = 1;
break;
case BB_TOP:
if (!visited[top]) { // edges[top]이 mvbox의 한변에 포함됨;
eh = -edges[top]; ev = eh.Rot90();
// 다음 변으로 회전;
visited[top] = 1;
if (++top == chull.size()) top = 0;
} else done = 1;
break;
case BB_LEFT:
if (!visited[left]) {// edges[left]가 mvbox의 한변에 포함됨;
ev = -edges[left]; eh = -ev.Rot90();
// 다음 변으로 회전;
visited[left] = 1;
if (++left == chull.size()) left = 0;
} else done = 1;
break;
case BB_NONE: // polygon이 직사각형임;
done = 1;
break;
}
// check area of mvbox;
double len0 = eh * (chull[right] - chull[left]) / 2;
double len1 = ev * (chull[top] - chull[bot]) / 2;
double area = len0 * len1; //면적의 1/4 임;
if (area < minarea) {
minarea = area;
ax0 = eh; ax1 = ev;
hscale = len0; vscale = len1;
// box center: tt = left 기준 top-bot을 연결하는 vector/2;
CfPt tt = (chull[top] + chull[bot]) / 2 - chull[left];
double zz = ev * tt;
center = eh * len0 + ev * zz + chull[left];
}
}
eh = ax0 * hscale; // recover original scale;
ev = ax1 * vscale; // recover original scale;
std::vector<CfPt> mvbox(4);
mvbox[0] = center + eh + ev;
mvbox[1] = center - eh + ev;
mvbox[2] = center - eh - ev;
mvbox[3] = center + eh - ev;
return mvbox;
}'Computational Geometry' 카테고리의 다른 글
| Approximate Convex Hull (0) | 2024.06.29 |
|---|---|
| Catmull-Rom Spline (2) (0) | 2024.06.21 |
| Natural Cubic Spline: revisited (0) | 2024.06.14 |
| Polygon Decimation (0) | 2024.06.08 |
| Centripetal Catmull-Rom Spline (1) | 2024.06.06 |


