计算机图形学——Weilerr_Atherton裁剪算法

计算机图形学——Weilerr_Atherton裁剪算法

前提:假设多边形与裁剪框的顶点均是有序存储,且最终是首位相连形成闭合图形。
适合情景:多变裁剪多边,可裁剪凹、凸等任意多边形。
不适合:线的裁剪、圆的裁剪。

Weilerr_Atherton(双边裁剪)的基本思想:

根据多边形的处理方向(顺时针/逆时针)和当前处理的多边形顶点对是由外到内,还是由内到外来确定裁剪后多边形的顶点连接方式:

  • 沿着多边形边界方向连接
  • 沿着裁剪框边界方向连接

注:此处的多边形边界方向指的是多边形顶点存储的顺序,是按照逆时针还是顺时针存储。
如果是顺时针处理多边形顶点,则采用以下规则:

  • 对由内到外的顶点对(此时多边形与裁剪框的交点为进点),沿着多边形边界的方向连接。
  • 对由外到内的顶点(此时多边形与裁剪框的交点为出点),按顺时针沿着裁剪框的边界连接。

算法的实现步骤:

  1. 预先处理多边形与裁剪框的顶点连接顺序。(只有当二者顶点均是按照同一个方向存储连接的才可以使用该算法)
  2. 计算多边形与裁剪框的交点,并将交点依照顺序插入多边形与裁剪框的顶点序列中。
  3. 建立交点索引,两个多边形的相同顶点各自指向对方顶点的位置。
  4. 选取任意一个交点为起点,将其保存到裁剪结果顶点表中。
  • 如果该交点为进点,跟踪多边形边界,并将其保存到裁剪结果顶点表中。
  • 否则,跟踪裁剪框边界。
  1. 跟踪图形的边界,每遇到顶点,将其保存到裁剪结果的顶点表中,直至遇见新的交点
    注:如果遇见相同的交点,一种是裁剪结果中有多个封闭图形,一种是顶点裁剪结束。
  2. 将该顶点输出到裁剪结果的顶点表中,并通过建立的索引改变跟踪的图形边界。
  • 如果上一步跟踪的为多边形边界,则改为跟踪裁剪框边界。反之跟踪多边形边界。
  1. 重复5、6直到所有的交点遍历过一次。

算法的代码实现:

  1. 多边形与裁剪框的预处理,令二者的顶点连接顺序都是按照同一方向(逆时针或顺时针)。
    • 采用原理:二维多边形的方向面积,即每个顶点看作一个向量,按照顶点顺序,依次进行叉积。如果叉积结果大于零为逆时针,小于零顺时针,等于零共线。
#include<iostream>
#include<vector>
#include <algorithm>

using namespace std;

/// <summary>
/// 判断面积的符号,有向面积法
///area > 0 "逆时针";
///area < 0 "顺时针";
///area=0 "共线";
/// </summary>
/// <param name="vertices">图形顶点</param>
/// <returns>返回有向面积的数值</returns>
double checkPolsecondgonOrientation(const vector<pair<int, int>>& vertices) {
	int n = vertices.size();
	double area = 0;
	// 计算有向面积
	for (int i = 0; i < n; ++i) {
		const pair<int, int>& p1 = vertices[i];
		const pair<int, int>& p2 = vertices[(i + 1) % n]; // 下一个顶点(闭合)
		area += (p1.first * p2.second - p2.first * p1.second);
	}
	return area;
}

/// <summary>
/// 初始化调整裁剪框与图形的顶点顺序为相同
/// </summary>
/// <param name="frame">裁剪框顶点</param>
/// <param name="picture">图形顶点</param>
void Initializing_Graphics(vector<pair<int, int>>& frame, vector<pair<int, int>>& picture) {
	// 判断多边形顶点顺序
	double pictureflage = checkPolsecondgonOrientation(picture);
	double frameflage = checkPolsecondgonOrientation(frame);
	if (pictureflage * frameflage < 0) {
		reverse(frame.begin(), frame.end());//反转
	}
}
  1. 计算多边形与裁剪框的交点
    • 特殊点处理:
      • 二者顶点重合,不计算
      • 裁剪框的顶点在多边形的边上,将裁剪框的顶点插入多边形。
      • 多边形的顶点在裁剪框的边上,将多边形的顶点插入多边形。
    • 交点的计算:
      • 使用直线的参数方程 ( x , y ) = ( x 0 , y 0 ) + t ( x 1 − x 0 , y 1 − y 0 ) (x,y)=(x_{0},y_{0})+t(x_{1}-x_{0},y_{1}-y_{0}) (x,y)=(x0,y0)+t(x1x0,y1y0)
      • 分别建立两个边所在直线的参数方程,联立求解参数,参数分别为 t 1 、 t 2 t_{1}、t_{2} t1t2
    • 判断交点是否为两个边(线段)的交点
      • 0 < = t < = 1 0<=t<=1 0<=t<=1时,表示该点在这个线段上(包括两个端点)
/// <summary>
/// 计算两个直线的交点参数,使用直线的参数方程,A为定点
/// </summary>
/// <param name="A0">直线1的点A</param>
/// <param name="B0">直线1的点B</param>
/// <param name="C0">直线2的点A</param>
/// <param name="D0">直线2的点B</param>
/// <returns>依次返回直线1、2的参数</returns>
vector<double> Cal_intersec_straight_lines(const pair<int, int>& A0, const pair<int, int>& B0, const pair<int, int>& C0, const pair<int, int>& D0) {
	pair<double, double> A = { static_cast<double>(A0.first),static_cast<double>(A0.second) };
	pair<double, double> B = { static_cast<double>(B0.first),static_cast<double>(B0.second) };
	pair<double, double> C = { static_cast<double>(C0.first),static_cast<double>(C0.second) };
	pair<double, double> D = { static_cast<double>(D0.first),static_cast<double>(D0.second) };

	//判断是否平行
	vector<double> result;
	pair<double, double> AB = { B.first - A.first,B.second - A.second };
	pair<double, double> CD = { D.first - C.first,D.second - C.second };
	//计算t,u
	double denom = AB.first * CD.second - AB.second * CD.first;

	if (denom == 0) {
		return result;
	}
	double t_up = (C.first - A.first) * (D.second - C.second) - (D.first - C.first) * (C.second - A.second);

	double t_num = t_up / denom;

	double u_up = (C.first - A.first) * (B.second - A.second) - (B.first - A.first) * (C.second - A.second);

	double u_num = u_up / denom;

	result.push_back(t_num);
	result.push_back(u_num);
	return result;
}

/// <summary>
/// 两个图形的交点,并按照顺序插入
/// </summary>
/// <param name="Frame">裁剪框的顶点</param>
/// <param name="Picture">图形的顶点</param>
void Graphical_Intersection(vector<pair<int, int>>& Frame, vector<pair<int, int>>& Picture) {
	int fnum = Frame.size(), pnum = Picture.size();
	for (int i = 0; i < fnum; i++) {
		for (int j = 0; j < pnum; j++) {
			vector<double> node = Cal_intersec_straight_lines(Frame[i], Frame[(i + 1) % fnum], Picture[j], Picture[(j + 1) % pnum]);
			if (!node.empty()) {
				pair<int, int> exp = { static_cast<int>(round(Frame[i].first + node[0] * (Frame[(i + 1) % fnum].first - Frame[i].first))),static_cast<int>(round(Frame[i].second + node[0] * (Frame[(i + 1) % fnum].second - Frame[i].second))) };
				if (0 < node[0] && node[0] < 1&& 0 <= node[1] && node[1] <= 1) {
					//将交点插入裁剪框
					Frame.insert(Frame.begin() + i + 1, exp);
					fnum++;
				}
				if (0 <= node[0] && node[0] <= 1 && 0 < node[1] && node[1] < 1) {
					//将交点插入图形
					Picture.insert(Picture.begin() + j + 1, exp);
					pnum++;
				}
			}
		}
	}
}
  1. 最终Weilerr_Atherton算法的书写
/// <summary>
///判断该点是否为进点
/// </summary>
/// <param name="Frame">裁剪框的顶点</param>
/// <param name="A">交点</param>
/// <param name="B">交点对应的图形的下一顶点</param>
/// <returns>true表示是进点</returns>
bool isInNode(const vector<pair<int, int>>& Frame, pair<int, int>A, pair<int, int>B) {
	vector<double> node;
	int fnum = Frame.size();
	for (int i = 0; i < fnum; i++) {
		node = Cal_intersec_straight_lines(Frame[i], Frame[(i + 1) % fnum], A, B);
		if (!node.empty()) {
			if (0 <= node[0] && node[0] <= 1 && node[1] > 0) {//边上有交点,并且在射线上
				return true;
			}
		}
	}
	return false;
}


// <summary>
/// 裁剪算法
/// </summary>
/// <param name="Frame">裁剪框顶点</param>
/// <param name="Picture">图形顶点</param>
/// <returns>返回裁剪后的顶点,每一个封闭图形是一个单独的vector<pair<int,int>>容器</returns>
vector<vector<pair<int, int>>> Weiler_Atherton(vector<pair<int, int>>& Frame, vector<pair<int, int>>& Picture) {
	vector<vector<pair<int, int>>> result;
	if (result.size() == 0) {
		result.push_back({});
	}
	//处理图形与裁剪图形,他们的顶点连接方向应该相反
	Initializing_Graphics(Frame, Picture);
	//计算两个图形的交点,并按照顺序插入两个图形
	Graphical_Intersection(Frame, Picture);
	//建立索引表
	//index.first为Frame的索引
	//index.second为Picture的索引
	vector<pair<int, int>> index;
	for (int i = 0; i < Frame.size(); i++) {//沿着裁剪框的边界方向建立索引
		for (int j = 0; j < Picture.size(); j++) {
			if (Frame[i] == Picture[j]) {
				index.emplace_back(i, j);
				break;//交点只有一个,所以找到合适的立马退出就行
			}
		}
	}
	//索引建立后开始创建图案表
	//什么时候结束?
	//任意取一个交点,判断交点类型
	// 进点沿着图形的顺序保存,出点沿着裁剪框的顺序保存,直到遇见的新的交点
	//什么时候图形结束?再次遇见刚开始的交点。
	//什么时候所有图形结束?
	//交点其实是按照顺序存储的,只是每次旋转着,就会回到起点
	//因此,在遇见一个闭环时,按照顺序查找下一交点
	int index_num = index.size();
	int picture_num = Picture.size();
	int count = 0;
	//裁剪后图形顶点的存储
	int flage = isInNode(Frame, Frame[index[0].first], Picture[(index[0].second + 1) % picture_num]);
	for (int i = 0; i < index_num; i++) {
		if (flage) {//进点,跟着图形走
			int j = index[i].second;
			while (true){
				result[count].push_back(Picture[j % picture_num]);//存储图形顶点
				j++;
				auto it = find_if(index.begin(), index.end(), [j,picture_num](const pair<int, int>& a) {return a.second == j%picture_num; });
				if (it != index.end()) {//是交点
					if (result[count][0] == Picture[j%picture_num]) {//并且相等
						count++;
						result.push_back({});
					}
					break;
				}
			} 
			//结束
			flage = false;
		}
		else {
			for (int j = index[i].first; j < index[(i + 1) % index_num].first; j++) {
				result[count].push_back(Frame[j]);
			}
			//结束
			if (result[count][0] == Frame[index[(i + 1) % index_num].first]) {//并且相等
				count++;
				result.push_back({});
			}
			flage = true;
		}
	}
	
	return result;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值