欢迎关注更多精彩
关注我,学习常用算法与数据结构,一题多解,降维打击。
线段剪裁作用
所谓线段剪裁,就是在二维平面上有一堆线段,和一个矩形窗口。求出现在窗口里线段部分是哪些。
上图中绿色为线段,红色为窗口。
剪裁后效果如下。
在这里我们规定矩形的边是平行于x轴和y轴的。
朴素思想及优化
朴素做法就是对于某一条段,用4个边界分别与线段求交。再判断交点的关系,决定哪段在剪裁窗口内。这种方法计算量大,情况也非常复杂。
优化
之前有基于解方程CohenSutherland算法
本文是基于参数化线段方程求交的Liang-barsky算法。
窗口左右下上分别为 xWmin, xWmax, yWmin, yWmax。
设 一 条 线 段 的 两 个 端 点 为 P 0 ( x 0 , y 0 ) , P e n d ( x e n d , y e n d ) 设一条线段的两个端点为P_0(x_0, y_0), P_{end}(x_{end}, y_{end}) 设一条线段的两个端点为P0(x0,y0),Pend(xend,yend)
∆ x = x e n d − x 0 , ∆ y = y e n d − y 0 ∆x = x_{end}-x_0, ∆y = y_{end}-y_0 ∆x=xend−x0,∆y=yend−y0
x W m i n < = x 0 + u ∗ ∆ x < = x W m a x ( 0 < = u < = 1 ) xWmin<= x_0 + u*∆x <= xWmax (0<=u<=1) xWmin<=x0+u∗∆x<=xWmax(0<=u<=1)
y W m i n < = y 0 + u ∗ ∆ y < = y W m a x ( 0 < = u < = 1 ) yWmin<= y_0 + u*∆y <= yWmax (0<=u<=1) yWmin<=y0+u∗∆y<=yWmax(0<=u<=1)
上述不等式拆成4个不等式。
u ∗ − ∆ x < = x 0 − x W m i n u*-∆x <= x_0-xWmin u∗−∆x<=x0−xWmin
u ∗ ∆ x < = x W m a x − x 0 u*∆x <= xWmax-x_0 u∗∆x<=xWmax−x0
u ∗ − ∆ y < = y 0 − y W m i n u*-∆y<= y_0-yWmin u∗−∆y<=y0−yWmin
u ∗ ∆ y < = y W m a x − y 0 u*∆y <= yWmax-y_0 u∗∆y<=yWmax−y0
从上式中可以看出,当∆x 或 ∆y=0 且 不等式后边的结果是负数时,不等式是无解的,也就是u没有效范围,反映到线段上就是完全被丢弃。如下图,绿色的线将被丢弃。
通过上述4个式子可以解出u的范围,再与[0,1]取交集,就是有效的区间。
一开始,有效区间为[u1, u2], u1=0, u2=1
将上述4个式子整理一下
u ∗ p k < = q k , k = 1 , 2 , 3 , 4 u*p_k<=q_k,\ k=1,2,3,4 u∗pk<=qk, k=1,2,3,4
对于一个p, q 如果 p>0 就会解出u<=q/p, 则更新u2为min(u, u2)。
如果p<0 , 就会解出u>=q/p, 则更新u1为max(u, u1)。
如果p==0, 判断是否在区域外。
上图展示了左右两边对直线进行剪裁,会确定一个最终区间。
x0>xend时,也有类似的结论。
对于下上边的剪裁也是同理。
实现
#include "glew/2.2.0_1/include/GL/glew.h"
#include "glfw/3.3.4/include/GLFW/glfw3.h"
#include <iostream>
using namespace std;
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
//如果按下ESC,把windowShouldClose设置为True,外面的循环会关闭应用
if(key==GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
std::cout<<"ESC"<<mode;
}
const double esp = 1e-6;
int doubleCmp(double a, double b) {
if (fabs(a-b)< esp)return 0;
if (a<b)return -1;
return 1;
}
class Point {
public:
double x, y;
Point(int xx, int yy):x(xx), y(yy){}
};
void setPixel(Point p) {
// cout<<p.x<<","<<p.y<<endl;
glPointSize(4);
glBegin(GL_POINTS);
glVertex2d(p.x, p.y);
glEnd();
glFlush();
}
/*
* 简单Bresenham 算法
* 只处理|m|>1
*/
void LineBres_2(Point p1, Point p2) {
if(p2.y<p1.y) {
Point t = p2;
p2=p1;
p1=t;
}
int deltaX = abs(p2.x-p1.x), deltaY = abs(p2.y-p1.y);
int p0 = 2*deltaX-deltaY;
int twDeltaX = 2*deltaX, twDeltaXmTwoDeltaY = 2*deltaX-2*deltaY;
int step = 0;
if(deltaX>0) step = (p2.x-p1.x) / deltaX;
setPixel(p1);
for (; p1.y < p2.y;) {
p1.y++;
if(p0<0) {
p0+=twDeltaX;
} else {
p0+=twDeltaXmTwoDeltaY;
p1.x+=step;
}
setPixel(p1);
// cout<<p1.x<<","<<p1.y<<endl;
}
}
/*
* 简单Bresenham 算法
* 只处理|m|<=1
*/
void LineBres_1(Point p1, Point p2) {
if(p2.x<p1.x) {
Point t = p2;
p2=p1;
p1=t;
}
int deltaX = abs(p2.x-p1.x), deltaY = abs(p2.y-p1.y);
if(deltaX<deltaY) {
LineBres_2(p1, p2);
return;
}
int p0 = 2*deltaY-deltaX;
int twDeltaY = 2*deltaY, twDeltaYmTwoDeltaX = 2*deltaY-2*deltaX;
int step = 0;
if(deltaY>0) step = (p2.y-p1.y) / deltaY;
setPixel(p1);
for (; p1.x < p2.x;) {
p1.x++;
if(p0<0) {
p0+=twDeltaY;
} else {
p0+=twDeltaYmTwoDeltaX;
p1.y+=step;
}
setPixel(p1);
// cout<<p1.x<<","<<p1.y<<endl;
}
}
/*
P0(X0, Y0), Pend(Xend, Yend)
∆x = Xend-X0, ∆y = Yend-Y0.
xWmin<= X0 + u*∆x <= xWmax (0<=u<=1),
yWmin<= Y0 + u*∆y <= yWmax (0<=u<=1),
拆解上述不等可以得到
*/
const int xWmin = -100, xWmax = 100, yWmin = -100, yWmax = 100;
bool clipTest(double p, double q, double &u1, double &u2){
int d = doubleCmp(p, 0);
if(0==d) {
return doubleCmp(q,0)>=0;
}
double r = q/p;
if (d<0) {
if(doubleCmp(r, u2)>0)return false;
u1 = max(r, u1);
} else {
if(doubleCmp(r, u1)<0)return false;
u2 = min(r, u2);
}
return true;
}
void LiangBarsky(Point p1, Point p2) {
double u1 = 0.0, u2 = 1.0;
int dx = p2.x-p1.x;
int dy = p2.y-p1.y;
if(!clipTest(-dx, p1.x-xWmin, u1, u2))return;
if(!clipTest(dx, xWmax-p1.x, u1, u2))return;
if(!clipTest(-dy, p1.y-yWmin, u1, u2))return;
if(!clipTest(dy, yWmax-p1.y, u1, u2))return;
if (u2<1) {
p2.x = p1.x + u2*dx;
p2.y = p1.y + u2*dy;
}
if (u1>0) {
p1.x += u1*dx;
p1.y += u1*dy;
}
LineBres_1(p1, p2);
}
int main(void) {
//初始化GLFW库
if (!glfwInit())
return -1;
//创建窗口以及上下文
GLFWwindow *window = glfwCreateWindow(400, 400, "hello world", NULL, NULL);
if (!window) {
//创建失败会返回NULL
glfwTerminate();
}
//建立当前窗口的上下文
glfwMakeContextCurrent(window);
glfwSetKeyCallback(window, key_callback); //注册回调函数
//glViewport(0, 0, 400, 400);
gluOrtho2D(-200, 200.0, -200, 200.0);
//循环,直到用户关闭窗口
cout<<123<<endl;
while (!glfwWindowShouldClose(window)) {
/*******轮询事件*******/
glfwPollEvents();
// cout<<456<<endl;
//选择清空的颜色RGBA
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
// glColor3f(0,0, 0);
glMatrixMode(GL_PROJECTION);
/*
//开始画一个三角形
glBegin(GL_TRIANGLES);
glColor3f(1, 0, 0); //Red
glVertex3f(0.5, 0, 0);
glVertex3f(-1, -1, 0);
glVertex3f(1, -1, 0);
//结束一个画图步骤
glEnd();
*/
/*
int point1[] = {50, 100};
glLineWidth(4.0f);
glBegin(GL_LINE_STRIP);
glColor3f(100,100, 100);
glVertex2i(10, 10);
glColor3f(100,0, 0);
glVertex2i(20, 20);
glEnd();
glBegin(GL_POINTS);
glVertex2i(100,100);
glEnd();
*/
//LineDDA(Point(0,0),Point(60,100));
//LineDDA(Point(0,0),Point(100,60));
// 画边框
glColor3f(1, 0, 0); //Red
LineBres_1(Point(xWmin,yWmin),Point(xWmin,yWmax));
LineBres_1(Point(xWmin,yWmin),Point(xWmax,yWmin));
LineBres_1(Point(xWmax,yWmax),Point(xWmax,yWmin));
LineBres_1(Point(xWmin,yWmax),Point(xWmax,yWmax));
glColor3f(0, 1, 0); //green
// 剪裁前线段
/*
LineBres_1(Point(150,-100),Point(150,0));
LineBres_1(Point(145,-100),Point(-150,100));
LineBres_1(Point(0,-150),Point(0,150));
LineBres_1(Point(-150,0),Point(130,0));
LineBres_1(Point(145,50),Point(-150,200));
LineBres_1(Point(145,150),Point(-150,200));
LineBres_1(Point(145,-50),Point(-150,-150));
LineBres_1(Point(145,-120),Point(-150,-170));
LineBres_1(Point(145,-150),Point(-150,-100));
LineBres_1(Point(145,-150),Point(-150,-30));
LineBres_1(Point(-150,-30), Point(145,150));
LineBres_1(Point(-150,-30), Point(-145,150));
LineBres_1(Point(-0,-130), Point(-145,-150));
*/
// glColor3f(0, 0, 1); //green
// 剪裁后线段
LiangBarsky(Point(150,-100),Point(150,0));
LiangBarsky(Point(145,-100),Point(-150,100));
LiangBarsky(Point(0,-150),Point(0,150));
LiangBarsky(Point(-150,0),Point(130,0));
LiangBarsky(Point(145,50),Point(-150,200));
LiangBarsky(Point(145,150),Point(-150,200));
LiangBarsky(Point(145,-50),Point(-150,-150));
LiangBarsky(Point(145,-120),Point(-150,-170));
LiangBarsky(Point(145,-150),Point(-150,-100));
LiangBarsky(Point(145,-150),Point(-150,-30));
LiangBarsky(Point(-150,-30), Point(145,150));
LiangBarsky(Point(-150,-30), Point(-145,150));
LiangBarsky(Point(-0,-130), Point(-145,-150));
/******交换缓冲区,更新window上的内容******/
glfwSwapBuffers(window);
//break;
}
glfwTerminate();
return 0;
}
算法效果
剪裁前效果
剪裁后效果
本人码农,希望通过自己的分享,让大家更容易学懂计算机知识。