Cohen-Sutherland算法---基于编码的矩形裁剪算法

1 基本思想

对于每条直线段p1(x1, y1)、p2(x2, y2)分三种情况处理

  • 直线段完全可见,“简取”之。即如果直线段完全在矩形内,保留
  • 直线段完全不可见,“简弃”之。即直线段在任意一条矩形边的一侧,不保留
  • 直线段既不满足“简取”的条件,也不满足“简弃”的条件,需要对直线段按交点进行分段,分段后重复上述处理。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

2编码方法

对于任一端点(x,y),根据其所在的区域,赋予一个4位的二进制码D3D2D1D0,顺序依次为上下右左。注意:l为left,r为right,b为bottom,t为top
编码规则如下:

  • 若x < wxl,则D0 = 1, 否则D0 = 0,使用移位操作符,则当D0 = 1时,code = 1 << 0

  • 若x > wxr, 则D1 = 1,否则D1 = 0,使用移位操作符,则当D1 = 1时,code = 1 << 1

  • 若y < wyb,则D2 = 1,否则D2 = 0,使用移位操作符,则当D2 = 1时,code = 1 << 2

  • 若y > wyt,则D3 = 1,否则D3 = 0,使用移位操作符,则当D3 = 1时,code = 1 << 3
    由上述编码规则可绘出如下图:
    在这里插入图片描述
    裁剪一条线段时,先求出端点p1和p2的编码code1和code2,然后

  • 若code1 | code2 = 0 ,得到code1,code2都 为0000,对直线段应简取

  • 若code1 & code2 != 0,说明两个端点肯定在矩形外的同一侧,对直线段简弃

  • 若上述两个条件均不成立。则需要求出直线段与窗口边界的交点。在交点处把线段一分为2,其中必有一段完全在窗口外,可以弃之。再对另一段重复进行上述处理,直到该线段完全被舍弃或者找到位于窗口内的一段线段为止。

3小结

该算法比较适用两种情况:一为大部分线段完全可见;二是大部分线段完全不可见

4代码

#include <iostream>
using namespace std;

class CuhenSutherlandClipper {
private:
	struct Point {
		double x;
		double y;
		Point() = default;
		Point(double x, double y) :x(x), y(y) {
		}
		Point(Point& other) {
			x = other.x;
			y = other.y;
		}		
	};
	struct LineSegment {
		Point* start;	// 端点
		Point* end;		// 端点
		LineSegment() = default;
		// 
		LineSegment(double x0, double y0, double x1, double y1) {
			start = new Point(x0, y0);
			end = new Point(x1, y1);
		}
		LineSegment(LineSegment& other) {			
			start = new Point(*other.start);
			end = new Point(*other.end);
		}
		void setStart(double x, double y) {
			start->x = x;
			start->y = y;
		}
		void setEnd(double x, double y) {
			end->x = x;
			end->y = y;
		}
		double getStartX() const {
			return start->x;
		}
		double getStartY() const {
			return start->y;
		}
		double getEndX() const {
			return end->x;
		}
		double getEndY() const {
			return end->y;
		}
	};
	struct Rectangle {
		double xmin;
		double xmax;
		double ymin;
		double ymax;
		Rectangle() = default;
		// 左下角顶点  矩形宽度、高度
		Rectangle(double xmin, double ymin, double clipw, double cliph) :
			xmin(xmin), ymin(ymin), xmax(xmin + clipw), ymax(ymin + cliph) {}
	};
private:
	LineSegment* lineSegment;
	LineSegment* resultLine;
	Rectangle* rect;
	bool accept;
public:
	CuhenSutherlandClipper() = default;
	// 前面4个参数直线段两端点的坐标,参数5、6是矩形的左下角顶点坐标,7、8是矩形的宽度和长度
	CuhenSutherlandClipper(double x0, double y0, double x1, double y1,
		double clipx, double clipy, double clipw, double cliph) {
		lineSegment = new LineSegment(x0, y0, x1, y1);
		rect = new Rectangle(clipx, clipy, clipw, cliph);
		encodeEndpoint(*lineSegment->start);
		encodeEndpoint(*lineSegment->end);
		resultLine = nullptr;
		accept = false;
	}
	/*
	 * 进行裁剪
	   true代表;该直线段与矩形有交集或存在于矩形内
	   false:表示该线段不在矩形范围内
	 */
	bool lineClipped() {
		//bool accept = false;
		do {
			// get endpoints code
			int e0code = encodeEndpoint(*lineSegment->start); 
			int e1code = encodeEndpoint(*lineSegment->end);

			if ((e0code | e1code )== 0) {
				accept = true;
				resultLine = new LineSegment(*lineSegment);
				break;
			}
			else if ((e0code & e1code) != 0) {
				accept = false;
				break;
			}
			else {
				// 选择一个在裁剪矩形窗口之外的端点
				int code = e0code != 0 ? e0code : e1code;

				double newx, newy;
				double x0 = lineSegment->getStartX();
				double y0 = lineSegment->getStartY();
				double x1 = lineSegment->getEndX();
				double y1 = lineSegment->getEndY();
				// 端点在矩形窗口的左侧
				if ((code & (1 << 0)) != 0) {
					newx = rect->xmin;
					/*
					**    y1- y0 / x1 - x0 = y - y0 / x - x0
					*
					*/
					newy = ((y1 - y0) / (x1 - x0)) * (newx - x0) + y0;
				}
				// 右侧
				else if ((code & (1 << 1)) != 0) {
					newx = rect->xmax;
					newy = ((y1 - y0) / (x1 - x0)) * (newx - x0) + y0;					
				}
				// 下侧
				else if ((code & (1 << 2)) != 0) {
					newy = rect->ymin;
					newx = ((x1 - x0) / (y1 - y0)) * (newy - y0) + x0;
				}
				// 上侧
				else if ((code & (1 << 3)) != 0) {
					newy = rect->ymax;
					newx = ((x1 - x0) / (y1 - y0)) * (newy - y0) + x0;
				}
				/* Now we replace the old endpoint depending on which we chose */
				if (code == e0code) {
					lineSegment->setStart(newx, newy);
				}
				else {
					lineSegment->setEnd(newx, newy);
				}
			}
		} while (true);

		return accept;
	}
	// 如果直线段落在矩形内则输出裁剪之后的直线段,否则输出裁剪为空
	void print() {
		if (accept) {
			cout << "该直线段落在矩形窗口内的部分的坐标为 :" << endl;
			cout << "x0 = " << resultLine->getStartX() << endl;
			cout << "y0 = " << resultLine->getStartY() << endl;
			cout << "x1 = " << resultLine->getEndX() << endl;
			cout << "y1 = " << resultLine->getEndY() << endl;
		}
		else {
			cout << "直线段并未落在矩形窗口之内" << endl;
		}
	}
	// 更新直线段的坐标
	void setLineSegment(double x0, double y0, double x1, double y1) {
		if (lineSegment != nullptr) {
			lineSegment->setStart(x0, y0);
			lineSegment->setEnd(x1, y1);
		}
		else {
			lineSegment = new LineSegment(x0, y0, x1, y1);
		}
	}
private:
	/*
	 * 对端点值根据矩形进行编码
	 */
	int encodeEndpoint( Point& po) {
		int code = 0;
		if (po.x < rect->xmin) {
			code |= (1 << 0);
		}
		else if (po.x > rect->xmax) {
			code |= (1 << 1);
		}

		if (po.y < rect->ymin) {
			code |= (1 << 2);
		}
		else if (po.y > rect->ymax) {
			code |= (1 << 3);
		}
		return code;
	}

};

int main() {
	CuhenSutherlandClipper clipper(0.5, 1.5, 1, 0, 0, 0, 1, 1);
	if (clipper.lineClipped()) {
		clipper.print();
	}
	else {
		cout << "直线段并未落在矩形窗口之内" << endl;
	}
	clipper.setLineSegment(0.5, 1.5, 2, 3);
	if (clipper.lineClipped()) {
		clipper.print();
	}
	else {
		cout << "直线段并未落在矩形窗口之内" << endl;
	}
	system("pause");
	return 0;
}

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值