基础图形绘制-直线裁剪

Cohen-Sutherland

Cohen-Sutherland 裁剪算法 是最常用的直线的裁剪算法,用于直线在矩形框的裁剪。

首先,给空间编码:

image-20211019203421901

根据点所在的位置我们可以获得其编码。

接下来考虑几种情况:

  1. 完全在矩形框内:将端点编码按位或,若为 0,则完全在矩形框内,直接全部保留;
  2. 完全在矩形框外:将端点编码按位与,若不为 0,则完全在矩形框外,直接全部舍弃;
  3. 其它情况:计算线段与矩形框的交点,获得新的线段,继续判断。

对于 情况3,我们获得新的线段后接着判断。

这样可以有效减少计算次数,同时,遇到下图这种情况时:

image-20211019204228310

我们会保留得到 ACBD (取决于计算交点的顺序),然后在下一轮计算中舍弃该线段,不会出现问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const int IN_SIDE      =  0x0000;
const int LEFT_SIDE = 0x0001;
const int RIGHT_SIDE = 0x0010;
const int BOTTOM_SIDE = 0x0100;
const int TOP_SIDE = 0x1000;

static int EncodePoint(int x, int y)
{
int code = IN_SIDE;
if (x < LEFT) code |= LEFT_SIDE;
if (RIGHT < x) code |= RIGHT_SIDE;
if (y < BOTTOM) code |= BOTTOM_SIDE;
if (TOP < y) code |= TOP_SIDE;
return code;
}

static void CohenSutherland(int x0, int y0, int x1, int y1)
{
int code0 = EncodePoint(x0, y0), code1 = EncodePoint(x1, y1);

for (;;)
{
if ( (code0 | code1) == 0 ) break;
if ( (code0 & code1) != 0 ) return;

int outCode = code0 ? code0 : code1;
double x, y, z;

if (outCode & LEFT_SIDE) { x = LEFT; float t = (x - x0) / (x1 - x0); y = (1 - t) * y0 + t * y1; }
else if(outCode & RIGHT_SIDE) { x = RIGHT; float t = (x - x0) / (x1 - x0); y = (1 - t) * y0 + t * y1; }
else if(outCode & BOTTOM_SIDE) { y = BOTTOM; float t = (y - y0) / (y1 - y0); x = (1 - t) * x0 + t * x1; }
else if(outCode & TOP_SIDE) { y = TOP; float t = (y - y0) / (y1 - y0); x = (1 - t) * x0 + t * x1; }

if (outCode == code0) x0 = x, y0 = y, code0 = EncodePoint(x0, y0);
else x1 = x, y1 = y, code1 = EncodePoint(x1, y1);
}

Bresenham(x0, y0, x1, y1);
}

OpenGL 完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#define GLEW_STATIC
#include <GL/glew.h>
#include <GL/GL.h>
#include <GLFW/glfw3.h>

#include <iostream>
#include <queue>
#include <vector>
#include <algorithm>

#pragma region Setting

static GLFWwindow* window;
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
const unsigned int MAX_COUNT = 800 * 600;

static void InitializeWindow()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Test", NULL, NULL);
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, [](GLFWwindow* window, int width, int height) { glViewport(0, 0, width, height); });


glewExperimental = GL_TRUE;
glewInit();

}

static void ProcessInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
#pragma endregion


#pragma region InitializeVertex

static struct point
{
int x, y;
point() : x(0), y(0) {}
point(int _x, int _y) : x(_x), y(_y) {}
};

static float vertices[MAX_COUNT * 3 * 2];
static unsigned int VAO, VBO;
static unsigned int count = 0;

static void Normalize(float& x, float& y)
{
x = (x - (SCR_WIDTH / 2)) / (SCR_WIDTH / 2);
y = (y - (SCR_HEIGHT / 2)) / (SCR_HEIGHT / 2);
}

template <typename T>
static void Swap(T& a, T& b) { T temp(a); a = b; b = temp; }


///////////////////////////////////////////////////
/// Boundary

const int LEFT = SCR_WIDTH / 4;
const int RIGHT = SCR_WIDTH / 4 * 3;
const int BOTTOM = SCR_HEIGHT / 4;
const int TOP = SCR_HEIGHT / 4 * 3;

static void DrawBoundary()
{
for (int i = 0; i < SCR_HEIGHT; i++)
{
vertices[count * 3] = LEFT, vertices[count * 3 + 1] = i;
Normalize(vertices[count * 3], vertices[count * 3 + 1]), count++;

vertices[count * 3] = RIGHT, vertices[count * 3 + 1] = i;
Normalize(vertices[count * 3], vertices[count * 3 + 1]), count++;
}

for (int i = 0; i < SCR_WIDTH; i++)
{
vertices[count * 3] = i, vertices[count * 3 + 1] = BOTTOM;
Normalize(vertices[count * 3], vertices[count * 3 + 1]), count++;

vertices[count * 3] = i, vertices[count * 3 + 1] = TOP;
Normalize(vertices[count * 3], vertices[count * 3 + 1]), count++;
}
}


static void Bresenham(int x1, int y1, int x2, int y2)
{
bool steep = abs(y2 - y1) > abs(x2 - x1);
if (steep) Swap(x1, y1), Swap(x2, y2);
if (x1 > x2) Swap(x1, x2), Swap(y1, y2);
int dx = x2 - x1, dy = y2 - y1;

int d = -dx;
for (int i = 0, x = x1, y = y1; i <= dx; i++, count++)
{
vertices[count * 3] = !steep ? x : y;
vertices[count * 3 + 1] = !steep ? y : x;

Normalize(vertices[count * 3], vertices[count * 3 + 1]);

x++;
d += 2 * abs(dy);
if (d > 0) y += (dy > 0 ? 1 : -1), d -= 2 * dx;
}
}

///////////////////////////////////////////////////
/// CohenSutherland

const int IN_SIDE = 0x0000;
const int LEFT_SIDE = 0x0001;
const int RIGHT_SIDE = 0x0010;
const int BOTTOM_SIDE = 0x0100;
const int TOP_SIDE = 0x1000;

static int EncodePoint(int x, int y)
{
int code = IN_SIDE;
if (x < LEFT) code |= LEFT_SIDE;
if (RIGHT < x) code |= RIGHT_SIDE;
if (y < BOTTOM) code |= BOTTOM_SIDE;
if (TOP < y) code |= TOP_SIDE;
return code;
}

static void CohenSutherland(int x0, int y0, int x1, int y1)
{
int code0 = EncodePoint(x0, y0), code1 = EncodePoint(x1, y1);

for (;;)
{
if ( (code0 | code1) == 0 ) break;
if ( (code0 & code1) != 0 ) return;

int outCode = code0 ? code0 : code1;
double x, y, z;

if (outCode & LEFT_SIDE) { x = LEFT; float t = (x - x0) / (x1 - x0); y = (1 - t) * y0 + t * y1; }
else if(outCode & RIGHT_SIDE) { x = RIGHT; float t = (x - x0) / (x1 - x0); y = (1 - t) * y0 + t * y1; }
else if(outCode & BOTTOM_SIDE) { y = BOTTOM; float t = (y - y0) / (y1 - y0); x = (1 - t) * x0 + t * x1; }
else if(outCode & TOP_SIDE) { y = TOP; float t = (y - y0) / (y1 - y0); x = (1 - t) * x0 + t * x1; }

if (outCode == code0) x0 = x, y0 = y, code0 = EncodePoint(x0, y0);
else x1 = x, y1 = y, code1 = EncodePoint(x1, y1);
}

Bresenham(x0, y0, x1, y1);
}


static void InitializeVertex()
{
// Generate
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);

// Bind
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
}

#pragma endregion


void Render()
{
glClearColor(0.5, 0.5, 0.5, 1);
glClear(GL_COLOR_BUFFER_BIT);

glBindVertexArray(VAO);
glDrawArrays(GL_POINTS, 0, count);
}

int main()
{

int x0, y0, x1, y1;
std::cin >> x0 >> y0 >> x1 >> y1;

InitializeWindow();

DrawBoundary();
CohenSutherland(x0, y0, x1, y1);

InitializeVertex();

while (!glfwWindowShouldClose(window))
{
ProcessInput(window);

Render();

glfwSwapBuffers(window);
glfwPollEvents();
}

glfwTerminate();
return 0;
}