Weiler-Atherton多边形裁剪算法(C++实现)计算机图形学作业

#include<iostream>
#include<graphics.h>
#include<conio.h>
#include<windows.h>
#include<vector>
using namespace std;

int xl, xr, yb, yt;

//优化后的Bresenham算法,能够处理斜率不存在,斜率为负数,斜率绝对值大于一等情况,并且可以根据需要自动调整两点顺序,具有良好的通用性
//使用只需要输入两点的横纵坐标值即可
void Bresenham(const unsigned& x1, const unsigned& y1, const unsigned& x2, const unsigned& y2)
{
	int dx = x2 - x1, dy = y2 - y1;
	enum MainChangeDirection { x, y };//用一个枚举变量记录直线的主要变化方向(x方向或者y方向)(用枚举变量更加符合语义)
	MainChangeDirection Direction;
	if (abs(dx) >= abs(dy))
		Direction = x;
	else Direction = y;
	if ((Direction == x && x1 > x2) || (Direction == y && y1 > y2))//如果在主要变化方向上的次序相反了,则递归调用自身即可(相当于交换两个点)
	{
		Bresenham(x2, y2, x1, y1);
		return;
	}

	if (dx == 0)//处理斜率不存在的特殊情况
	{
		for (unsigned y = y1; y <= y2; ++y)
		{
			putpixel(x1, y, RED);
		}
		return;
	}

	//对于不同的变化方向以及斜率的正负不同,需要使用不同的数学表达式(自行推导出),也就是需要分情况
	double k = double(dy) / dx;
	if (Direction == x)//处理主要变化方向为x轴的情况
	{
		unsigned x = x1, y = y1;
		if (k >= 0)//主要变化方向为x轴并且斜率大于等于零
		{
			int e = -dx;
			for (; x <= x2; ++x)
			{
				putpixel(x, y, RED);
				e += 2 * dy;
				if (e > 0)
				{
					y++;
					e -= 2 * dx;
				}
			}
		}
		else if (k < 0)//主要变化方向为x轴且斜率小于零
		{
			int e = dx;
			for (; x <= x2; ++x)
			{
				putpixel(x, y, RED);
				e += 2 * dy;
				if (e <= 0)
				{
					y--;
					e += 2 * dx;
				}
			}
		}
	}
	else if (Direction == y)
	{
		unsigned y = y1, x = x1;
		if (k >= 0)//主要变化方向为y轴方向并且斜率大于等于零
		{
			int e = -dy;
			for (; y <= y2; ++y)
			{
				putpixel(x, y, RED);
				e += 2 * dx;
				if (e > 0)
				{
					x++;
					e -= 2 * dy;
				}
			}
		}
		else if (k < 0)//主要变化方向为y轴方向并且斜率小于零
		{
			int e = dy;
			for (; y <= y2; ++y)
			{
				putpixel(x, y, RED);
				e += 2 * dx;
				if (e <= 0)
				{
					x--;
					e += 2 * dy;
				}
			}
		}
	}
	return;
}

enum Intersection_Type{TopPoint,RightPoint,BottomPoint,LeftPoint};

//用来求给定线段与裁剪窗口顶部交点横坐标的函数,用于辅助运算
int topIntersection(const int& x1, const int& y1, const int& x2, const int& y2)
{
	if ((y1 - yt) * (y2 - yt) >= 0)
		return -1;
	if (x1 == x2)
	{
		if (x1<xl || x1>xr)
			return -1;
		else return x1;
	}
	double k = double(y2 - y1) / (x2 - x1);
	int result = int(x1 + (yt - y1) / k + 0.5);
	if ((result - xl) * (result - xr) <= 0)
		return result;
	else return -1;
}

//用来求给定线段与裁剪窗口底部交点横坐标的函数,用于辅助运算
int bottomIntersection(const int& x1, const int& y1, const int& x2, const int& y2)
{
	if ((y1 - yb) * (y2 - yb) >= 0)
		return -1;
	if (x1 == x2)
	{
		if (x1<xl || x1>xr)
			return -1;
		else return x1;
	}
	double k = double(y2 - y1) / (x2 - x1);
	int result = int(x1 + (yb - y1) / k + 0.5);
	if ((result - xl) * (result - xr) <= 0)
		return result;
	else return -1;
}

//用来求给定线段与裁剪窗口左边界交点横坐标的函数,用于辅助运算
int leftIntersection(const int& x1, const int& y1, const int& x2, const int& y2)
{
	if ((x1 - xl) * (x2 - xl) >= 0)
		return -1;
	double k = double(y2 - y1) / (x2 - x1);
	int result = int(k * (xl - x1) + y1 + 0.5);
	if ((result - yb) * (result - yt) <= 0)
		return result;
	else return -1;
}

//用来求给定线段与裁剪窗口右边界交点横坐标的函数,用于辅助运算
int rightIntersection(const int& x1, const int& y1, const int& x2, const int& y2)
{
	if ((x1 - xr) * (x2 - xr) >= 0)
		return -1;
	double k = double(y2 - y1) / (x2 - x1);
	int result = int(k * (xr - x1) + y1 + 0.5);
	if ((result - yb) * (result - yt) <= 0)
		return result;
	else return -1;
}

//用一个结构体来记录交点,结构体中存储交点的横纵坐标以及始发顶点
struct Intersection
{
	int x;
	int y;
	int start;

	explicit Intersection(const int& x, const int& y, const int& start) :x(x), y(y), start(start) {}
};

//梁式直线裁剪算法的程序,由于该方法用编程实现非常简单,在介绍时稍微简略一些
void Liang_Barsky(const int& x1, const int& y1, const int& x2, const int& y2)
{
	//首先分别计算出四个P值和四个Q值,如果满足两种算法停止条件的一种则可以直接终止算法而无序计算U值,从而提高效率节约空间
	int p1, p2, p3, p4;
	int q1, q2, q3, q4;
	p1 = x1 - x2;
	p2 = x2 - x1;
	p3 = y1 - y2;
	p4 = y2 - y1;
	q1 = x1 - xl;
	q2 = xr - x1;
	q3 = y1 - yb;
	q4 = yt - y1;
	if (p1 == 0)
	{
		if (q1 < 0 && q2 < 0)
			return;
	}
	if (p3 == 0)
	{
		if (q3 < 0 && q4 < 0)
			return;
	}
	//计算U值,并分别计算出Umax和Umin
	float u1, u2, u3, u4;
	float umax = 0, umin = 1;
	u1 = float(q1) / p1;
	u2 = float(q2) / p2;
	u3 = float(q3) / p3;
	u4 = float(q4) / p4;
	if (p1 < 0)
	{
		if (umax < u1)
			umax = u1;
	}
	else
	{
		if (umin > u1)
			umin = u1;
	}
	if (p2 < 0)
	{
		if (umax < u2)
			umax = u2;
	}
	else
	{
		if (umin > u2)
			umin = u2;
	}
	if (p3 < 0)
	{
		if (umax < u3)
			umax = u3;
	}
	else
	{
		if (umin > u3)
			umin = u3;
	}
	if (p4 < 0)
	{
		if (umax < u4)
			umax = u4;
	}
	else
	{
		if (umin > u4)
			umin = u4;
	}

	//进行比较:如果Umax>Umin则直接终止算法,否则代入两个U值来求出直线与窗口的实交点坐标,并代入坐标作出直线
	if (umax > umin)
		return;
	else
	{
		unsigned New_x1, New_y1, New_x2, New_y2;
		New_x1 = x1 + unsigned((x2 - x1) * umax + 0.5);
		New_y1 = y1 + unsigned((y2 - y1) * umax + 0.5);
		New_x2 = x1 + unsigned((x2 - x1) * umin + 0.5);
		New_y2 = y1 + unsigned((y2 - y1) * umin + 0.5);

		Bresenham(New_x1, New_y1, New_x2, New_y2);
	}
	return;
}

void Weiler_Atherton(const int* x, const int* y, const unsigned& num)
{
	//使用一个向量来存放直线与窗口边框的交点,交点中记录直线的一个端点以及交点类型和横纵坐标
	vector<Intersection>InterVec;
	//使用另一个向量来记录每一个点是否可见(true为可见,false为不可见)
	vector<bool>visible;
	//用四个变量记录不同类型交点的个数
	unsigned topPoints(0), bottomPoints(0), leftPoints(0), rightPoints(0);

	//对visible向量进行初始化,完成后可以得出任意一个点是否在窗口中可见
	for (unsigned i = 0; i < num; i++)
	{
		if (x[i] >= xl && x[i] <= xr && y[i] >= yb && y[i] <= yt)
			visible.push_back(true);
		else visible.push_back(false);
	}

	//对于每一条直线,判定其与窗口的顶部边框是否有交点,如果有交点则将交点信息存入向量中保存
	for (unsigned i = 0; i < num - 1; i++)
	{
		int New_x = topIntersection(x[i], y[i], x[i + 1], y[i + 1]);
		if (New_x != -1)
		{
			Intersection temp(New_x, yt, i);
			InterVec.push_back(temp);
			topPoints++;
		}
	}
	//对于最后一个顶点和第一个顶点之间的线段也要进行考虑
	int New_x1 = topIntersection(x[num - 1], y[num - 1], x[0], y[0]);
	if (New_x1 != -1)
	{
		Intersection temp(New_x1, yt, num - 1);
		InterVec.push_back(temp);
		topPoints++;
	}

	//接下来考虑多边形的边与右边框的交点,方法与之前求上边框的交点类似
	for (unsigned i = 0; i < num - 1; i++)
	{
		int New_y = rightIntersection(x[i], y[i], x[i + 1], y[i + 1]);
		if (New_y != -1)
		{
			Intersection temp(xr, New_y, i);
			InterVec.push_back(temp);
			rightPoints++;
		}
	}
	//同样需要考虑最后一个顶点和第一个顶点之间的线段
	int New_y2 = rightIntersection(x[num - 1], y[num - 1], x[0], y[0]);
	if (New_y2 != -1)
	{
		Intersection temp(xr, New_y2, num - 1);
		InterVec.push_back(temp);
		rightPoints++;
	}

	//接下来考虑多边形的每一条边与窗口的底边框是否有交点,其过程与上边框的判断类似
	for (unsigned i = 0; i < num - 1; i++)
	{
		int New_x = bottomIntersection(x[i], y[i], x[i + 1], y[i + 1]);
		if (New_x != -1)
		{
			Intersection temp(New_x, yb, i);
			InterVec.push_back(temp);
			bottomPoints++;
		}
	}
	//同样需要考虑最后一个顶点和第一个顶点之间的线段是否与底边框相交
	int New_x2 = bottomIntersection(x[num - 1], y[num - 1], x[0], y[0]);
	if (New_x2 != -1)
	{
		Intersection temp(New_x2, yb, num - 1);
		InterVec.push_back(temp);
		bottomPoints++;
	}

	//接下来考虑多边形的每一条边是否与左边框相交
	for (unsigned i = 0; i < num - 1; i++)
	{
		int New_y = leftIntersection(x[i], y[i], x[i + 1], y[i + 1]);
		if (New_y != -1)
		{
			Intersection temp(xl, New_y, i);
			InterVec.push_back(temp);
			leftPoints++;
		}
	}
	//需要考虑最后一个顶点和第一个顶点之间的线段
	int New_y1 = leftIntersection(x[num - 1], y[num - 1], x[0], y[0]);
	if (New_y1 != -1)
	{
		Intersection temp(xl, New_y1, num-1);
		InterVec.push_back(temp);
		leftPoints++;
	}

	//目前已经得到了交点的向量,并且向量中的交点已经按照“上交点部分-右交点部分-下交点部分-左交点部分”的顺序排列完成,因此对每一部分分别排序即可得到有序的交点向量
	//首先对上交点部分进行排序,将横坐标小的交点排在前面
	for (unsigned i = 0; i <= topPoints - 2; i++)
	{
		for (unsigned j = 0; j <= topPoints - 2 - i; j++)
		{
			if (InterVec[j].x > InterVec[j + 1].x)
			{
				int temp = InterVec[j].x;
				InterVec[j].x = InterVec[j + 1].x;
				InterVec[j + 1].x = temp;

				temp = InterVec[j].start;
				InterVec[j].start = InterVec[j + 1].start;
				InterVec[j + 1].start = temp;
			}
		}
	}

	//接着对右边界交点进行排序,将纵坐标小的交点排在前面
	for (unsigned i = topPoints; i <= topPoints+rightPoints - 2; i++)
	{
		for (unsigned j = topPoints; j <= topPoints + rightPoints - 2 - i; j++)
		{
			if (InterVec[j].y < InterVec[j + 1].y)
			{
				int temp = InterVec[j].y;
				InterVec[j].y = InterVec[j + 1].y;
				InterVec[j + 1].y = temp;

				temp = InterVec[j].start;
				InterVec[j].start = InterVec[j + 1].start;
				InterVec[j + 1].start = temp;
			}
		}
	}

	//接着对底边界交点进行排序,将横坐标大的交点排在前面
	for (unsigned i = topPoints+rightPoints; i <= topPoints+rightPoints+bottomPoints - 2; i++)
	{
		for (unsigned j = topPoints + rightPoints; j <= topPoints + rightPoints + bottomPoints - 2 - i; j++)
		{
			if (InterVec[j].x < InterVec[j + 1].x)
			{
				int temp = InterVec[j].x;
				InterVec[j].x = InterVec[j + 1].x;
				InterVec[j + 1].x = temp;

				temp = InterVec[j].start;
				InterVec[j].start = InterVec[j + 1].start;
				InterVec[j + 1].start = temp;
			}
		}
	}

	//最后对左边界交点进行排序,将纵坐标小的交点排在前面
	for (unsigned i = topPoints+rightPoints+bottomPoints; i <= topPoints+rightPoints+bottomPoints+leftPoints - 2; i++)
	{
		for (unsigned j = topPoints + rightPoints + bottomPoints; j <= topPoints + rightPoints + bottomPoints + leftPoints - 2 - i; j++)
		{
			if (InterVec[j].y > InterVec[j + 1].y)
			{
				int temp = InterVec[j].y;
				InterVec[j].y = InterVec[j + 1].y;
				InterVec[j + 1].y = temp;

				temp = InterVec[j].start;
				InterVec[j].start = InterVec[j + 1].start;
				InterVec[j + 1].start = temp;
			}
		}
	}

	//经过上述排序处理,各个交点已经完全按照顺时针相邻的顺序进行排列,由此可以进行作图部分

	rectangle(xl, yt, xr, yb);//作出一个矩形边框,表示裁剪窗口
	for (unsigned i = 0; i < num - 1; i++)
	{
		//对于任意两个相邻的多边形顶点,首先可以直接调用直线裁剪算法绘制出两点之间的裁剪线段
		Liang_Barsky(x[i], y[i], x[i + 1], y[i + 1]);
		Sleep(200);
		//如果直线的前一个端点在窗口内而另一个端点在窗口外,则此时需要考虑相邻交点之间的线段绘制
		if (visible[i] == true && visible[i + 1] == false)
		{
			//首先在交点向量组中找出始端点为该端点的交点
			unsigned j;
			for (j = 0; j < InterVec.size(); j++)
			{
				if (InterVec[j].start == i)
					break;
			}
			//接着对交点进行判断:如果是位于相邻两边界上的交点,则需要连接裁剪窗口的顶点
			if (j == topPoints - 1)
			{
				Bresenham(InterVec[j].x, InterVec[j].y, xr, yt);
				Sleep(200);
				Bresenham(xr, yt, InterVec[j + 1].x, InterVec[j + 1].y);
				Sleep(200);
			}
			else if (j == topPoints + rightPoints - 1)
			{
				Bresenham(InterVec[j].x, InterVec[j].y, xr, yb);
				Sleep(200);
				Bresenham(xr, yb, InterVec[j + 1].x, InterVec[j + 1].y);
				Sleep(200);
			}
			else if (j == topPoints + rightPoints + bottomPoints - 1)
			{
				Bresenham(InterVec[j].x, InterVec[j].y, xl, yb);
				Sleep(200);
				Bresenham(xl, yb, InterVec[j + 1].x, InterVec[j + 1].y);
				Sleep(200);
			}
			else if (j == topPoints + rightPoints + bottomPoints + leftPoints - 1)
			{
				Bresenham(InterVec[j].x, InterVec[j].y, xl, yt);
				Sleep(200);
				Bresenham(xl, yt, InterVec[0].x, InterVec[0].y);
				Sleep(200);
			}
			//如果不满足上面的特殊情况,则直接将相邻交点进行连线即可(此处使用了之前所学习的Bresenham线段绘制方法)
			else
			{
				Bresenham(InterVec[j].x, InterVec[j].y, InterVec[j + 1].x, InterVec[j + 1].y);
				Sleep(200);
			}
		}
	}
	//同样不能忽略最后一个顶点和第一个顶点之间的连线,处理方法与上面类似
	Liang_Barsky(x[num - 1], y[num - 1], x[0], y[0]);
	Sleep(200);
	if (visible[num - 1] == true && visible[0] == false)
	{
		unsigned j;
		for (j = 0; j < InterVec.size(); j++)
		{
			if (InterVec[j].start == num-1)
				break;
		}
		if (j == topPoints - 1)
		{
			Bresenham(InterVec[j].x, InterVec[j].y, xr, yt);
			Sleep(200);
			Bresenham(xr, yt, InterVec[j + 1].x, InterVec[j + 1].y);
			Sleep(200);
		}
		else if (j == topPoints + rightPoints - 1)
		{
			Bresenham(InterVec[j].x, InterVec[j].y, xr, yb);
			Sleep(200);
			Bresenham(xr, yb, InterVec[j + 1].x, InterVec[j + 1].y);
			Sleep(200);
		}
		else if (j == topPoints + rightPoints + bottomPoints - 1)
		{
			Bresenham(InterVec[j].x, InterVec[j].y, xl, yb);
			Sleep(200);
			Bresenham(xl, yb, InterVec[j + 1].x, InterVec[j + 1].y);
			Sleep(200);
		}
		else if (j == topPoints + rightPoints + bottomPoints + leftPoints - 1)
		{
			Bresenham(InterVec[j].x, InterVec[j].y, xl, yt);
			Sleep(200);
			Bresenham(xl, yt, InterVec[0].x, InterVec[0].y);
			Sleep(200);
		}
		else
		{
			Bresenham(InterVec[j].x, InterVec[j].y, InterVec[j + 1].x, InterVec[j + 1].y);
			Sleep(200);
		}
	}
}

int main(void)
{
	unsigned num;
	cout << "请输入多边形的顶点个数:";
	cin >> num;
	int* xArray = new int[num];
	int* yArray = new int[num];
	cout << "请按顺时针顺序输入多边形顶点的横坐标:";
	for (unsigned i = 0; i < num; i++)
	{
		cin >> xArray[i];
	}
	cout << "请按顺时针顺序输入多边形顶点的纵坐标:";
	for (unsigned i = 0; i < num; i++)
	{
		cin >> yArray[i];
	}
	cout << "请输入裁剪窗口的左下角顶点坐标和右上角顶点坐标:";
	cin >> xl >> yb >> xr >> yt;
	cout << "参数确认完毕,按任意键开始裁剪...(裁剪完成后按任意键返回)";
	_getch();
	initgraph(1000, 640);
	for (unsigned i = 0; i < num - 1; i++)
	{
		Bresenham(xArray[i], yArray[i], xArray[i + 1], yArray[i + 1]);
	}
	Bresenham(xArray[num - 1], yArray[num - 1], xArray[0], yArray[0]);
	rectangle(xl, yt, xr, yb);
	Sleep(2000);
	_getch();
	closegraph();
	initgraph(1000, 640);
	Weiler_Atherton(xArray, yArray, num);
	_getch();
	closegraph();
	return 0;
}
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值