当平面上有很多线段时,组成的图形往往不止一个多边形,而是一个平面直线图(PSLG),它代表一个平面区域划分,其中一个区域是一个多边形。
如果只有点和边的信息,如何找出所有区域呢?为方便起见,我们把每条边u-v拆成两条半边 u-v 和 v-u.并且每条半边只与它左边的面相邻。接下来,我们从一条半边出发遍历,每次像卷包裹算法那样找一个逆时针转的尽量多的边作为下一条边,直到回到出发的那条半边。
程序实现上可以把边表扩大一倍,让编号为i的半边的反向边的编号为i^1.
首先看一下边的存储结构定义:
//边
struct Edge
{
int from, to; //起点,终点
double ang; //极角
};
平面直线图:
//平面直线图。
struct PSLG
{
int n, m; //顶点数 边数
int face_cnt; //面数
double x[maxn]; //顶点
double y[maxn];
vector<Edge> edges;
vector<int> G[maxn];
bool vis[maxn*2]; // 每条边是否被访问过
int left[maxn*2]; //左面的编号
int prev[maxn*2]; //相同起点的上一条边,即顺时针旋转碰到的下一条边的编号
vector<Polygon> faces; //面
double areas[maxn]; //每个polygon的面积
void init(int n)
{
this->n = n;
edges.clear();
faces.clear();
for(int i = 0; i < n; i++)
G[i].clear();
}
//有向线段from-to的极角
double getAngle(int from, int to)
{
return atan2(y[to]-y[from], x[to]-x[from]);
}
void AddEdge(int from, int to)
{
edges.push_back((Edge){from, to, getAngle(from, to)});
edges.push_back((Edge){to, from, getAngle(to, from)});
m = edges.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}
//找出faces并计算面积
void Build()
{
int u, i, j, d, from, e;
//给从u开始个各条边按照极角排序
for(u = 0; u < n; u++)
{
d = G[u].size();
for(i = 0; i < d; i++)
for(j = i+1; j < d; j++) //这里假设从u出发的线段不会太多
if(edges[G[u][i]].ang > edges[G[u][j]].ang)
swap(G[u][i], G[u][j]);
for(i = 0; i < d; i++)
prev[G[u][(i+1)%d]] = G[u][i];
}
face_cnt = 0;
memset(vis, false, sizeof(vis));
for(u = 0; u < n; u++)
for(i = 0; i < G[u].size(); i++)
{
e = G[u][i];
if(!vis[e]) //逆时针找圈
{
face_cnt++; //找到一个新的面
Polygon poly;
for(;;)
{
vis[e] = true;
left[e] = face_cnt;
from = edges[e].from;
poly.push_back(Point(x[from], y[from])); //把新的点加入面中
e = prev[e ^ 1]; //e^1为反向边,然后prev就是需要走的下一条边
if(e == G[u][i]) //回到了起始边
break;
}
faces.push_back(poly);
}
}
//对于连通图,惟一一个面积小于零的面是无限面
//对于内部区域来说,无限面多边形的各个顶点是顺时针的
for(i = 0; i < faces.size(); i++) //计算各个面的面积
areas[i] = PolygonArea(faces[i]);
}
};