本博客基于课程"计算机图形学",教材使用为计算机图形学(第4版) [Computer Graphics with OpenGL, Fourth Edition],部分代码模板便来自于此教材,并且有所改动
实验思路
代码思路
- 变量介绍:
dcpt
:点,用坐标表示,x和y成员分别表示x和y坐标;pointStruct
:数据成员包含一个点和一个指向点变量的指针,即后面的栈是以链表的形式存储的,也便于实现递归;pHead
指向链表的头指针(不可移动),p1
相当于临时指针,也指向链表表头,但是可以移动,pTop
为栈顶指针;构造函数:为头指针分配内存空间,并令p1指向和头指针同样内存位置,此时栈为空,pTop
指向也是头指针所指,并且为了防止位置错误发生,此处三个指针的下一结点都需要指向NULL
。析构函数:使用while
循环,自栈顶依次释放每个点内存。pop
:若成功删除栈顶点,返回true
,否则返回false
。push
:传入参数为一个点pt
,先用p1
申请新的内存空间,再将pt
赋值给p1
,再用栈顶指针指向p1
同一内存空间;empty
:用while
循环清空栈,原理同析构函数;getTop
:返回类型为一个点,返回栈顶变量的点成员;isEmpty
:用while
循环判断栈是否为空 Boundaryfill()
函数:传入参数为入栈的第一个点,栈不为空为while
的执行条件。在执行while
之前先把初始点入栈,并用dir
二维数组表示为方向。进入while
循环之后,用p1
保存栈顶元素坐标,用来在后面for
循环中实现点之间的方向搜索(类似于dfs),在for循环中,遍历四个点,并将符合条件的点入栈,退出for
循环,继续while
循环,出栈,然后填充颜色,再继续for
循环。while
循环完成退出后,颜色填充也就完成了
问题及解决方案
MyStack.cpp
中是CMyStack
中各函数的实现。由于本人使用的开发环境是VSCode + GCC,而不是使用的 Visual Studio,所以不能联合编译,vscode不会生成makefile
文件,所以MyStack.cpp
中的代码不会被编译到main.cpp
中,所以若读者也使用和本人相同的开发环境,需要手动把MyStack.cpp
和MyStack.h
中内容复制到main.cpp
中。- 代码中
MyStack.cpp
和MyStack.h
配合main.cpp
的用法,是c/c++工程中常用的使用方法。头文件(.h):写类的声明(包括类里面的成员和方法的声明)、函数原型、#define常数等,但一般来说不写出具体的实现。源文件(.cpp): 源文件主要写实现头文件中已经声明的那些函数的具体代码。需要注意的是,开头必须#include
一下实现的头文件,以及要用到的头文件。例如:.h中的代码是一个宏定义在main.cpp中包含时,直接替换为#if !defined(AFX_MySTACK_H__3657DC2D_E518_4E5E_9A32_023B2A260ED7__INCLUDED_)
和#endif
中的内容,#if !defined
和#pragma once
两个宏的作用都是为了防止重定义。 - 为了保持良好的编程习惯,对应dir数组只访问而不修改,应直接定义为常量。
- 通过查阅,由于glReadPixels()函数性能较差,所以运行程序时会有卡顿,且占显存极高。在while循环中采用的时入栈一个点及其周围点后,便进行填充,而非将所有需要填充点入栈后再填充,是由于基于所给代码模板。若要使用类似于dfs递归的方式,需要额外设定记录数组,较为麻烦,不仅代码量增多,而且由于像素点较多,比较浪费内存,如果填充图形形状大小不固定,那还需要使用动态内存,便没有采用这个方式
实现代码
MyStack.h
// MyStack.h: interface for the MyStack class.
//
//
#ifndef AFX_MySTACK_H__3657DC2D_E518_4E5E_9A32_023B2A260ED7__INCLUDED_
#define AFX_MySTACK_H__3657DC2D_E518_4E5E_9A32_023B2A260ED7__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include <GL/glut.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
struct dcpt {
int x;
int y;
};
struct pointStruct {
dcpt point;
pointStruct* pNext;
};
class CMyStack {
public:
// Write your code here
CMyStack();
virtual ~CMyStack();
bool pop();
void push(dcpt pt);
void empty();
dcpt getTop();
bool isEmpty();
private:
// Write your code here
pointStruct* pHead;
pointStruct* p1;
pointStruct* pTop;
};
#endif // !defined(AFX_MySTACK_H__3657DC2D_E518_4E5E_9A32_023B2A260ED7__INCLUDED_)
MyStack.cpp
// MyStack.cpp: implementation of the MyStack class.
//
//
#include "MyStack.h"
//
// Construction/Destruction
//
// Write your code here
CMyStack::CMyStack() {
pHead = new pointStruct;
p1 = pHead;
pTop = pHead;
pHead->pNext = NULL;
}
CMyStack::~CMyStack() {
while (pTop != NULL) {
p1 = pTop;
pTop = pTop->pNext;
delete p1;
}
}
bool CMyStack::pop() {
if (pTop == pHead)
return false;
p1 = pTop;
pTop = pTop->pNext;
delete p1;
return true;
}
void CMyStack::push(dcpt pt) {
p1 = new pointStruct;
p1->point = pt;
p1->pNext = pTop;
pTop = p1;
}
void CMyStack::empty() {
while (pTop !=pHead) {
p1 = pTop;
pTop = pTop->pNext;
delete p1;
}
}
dcpt CMyStack::getTop() {
return pTop->point;
}
bool CMyStack::isEmpty() {
if (pTop == pHead)
return true;
return false;
}
main.cpp
// ====== Computer Graphics Experiment #3 ======
// | Boundary-Fill Algorithm |
// =============================================
//
// Requirement:
// Implement Boundary-Fill algorithm to fill polygon.
#include "MyStack.h"
#define WINDOW_HEIGHT 400
void Boundaryfill(int seedx, int seedy) {
CMyStack stk1;
long color = RGB(255, 0, 0); //填充颜色
long boundary_color = RGB(255, 255, 255); //边界颜色
unsigned char params[4]; //保存读取的一个像素点的颜色值
dcpt p1;
p1.x = seedx;
p1.y = seedy;
// Write your code here
stk1.push(p1);
const int dir[4][2] = { { 0, -1 }, { 1, 0 }, { 0, 1 }, { -1, 0 } };
while (!stk1.isEmpty()) {
p1 = stk1.getTop();
stk1.pop();
glColor3f(1, 0, 0);
glBegin(GL_POINTS);
glVertex2i(p1.x, p1.y);
glEnd();
for (int i = 0; i < 4; i++) {
int tx = p1.x + dir[i][0];
int ty = p1.y + dir[i][1];
glReadPixels(tx, ty, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, params);
long c1 = RGB(params[0], params[1], params[2]);
if (c1 == color || c1 == boundary_color)
continue;
dcpt tmp;
tmp.x = tx;
tmp.y = ty;
stk1.push(tmp);
}
}
}
void MyPolygonFill(int n, dcpt* vertices)
// n --- Number of vertices
// vertices --- vertex coordinates
{
int max = vertices[0].x;
int mix = vertices[0].x;
int may = vertices[0].y;
int miy = vertices[0].y;
glColor3f(1.0, 1.0, 1.0);
glBegin(GL_LINE_LOOP);
for (int i = 0; i < n; i++) {
glVertex2i(vertices[i].x, vertices[i].y);
if (vertices[i].x > max)
max = vertices[i].x;
if (vertices[i].x < mix)
mix = vertices[i].x;
if (vertices[i].y > may)
may = vertices[i].y;
if (vertices[i].y < miy)
miy = vertices[i].y;
}
glEnd();
int sx = (max + mix) / 2;
int sy = (may + miy) / 2;
Boundaryfill(sx, sy);
}
// Initialization function
void init(void) {
glClearColor(0.0, 0.0, 0.0, 0.0);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
}
// Display callback function
void display(void) {
static dcpt v[4];
v[0].x = 260, v[0].y = 150;
v[1].x = 281, v[1].y = 200;
v[2].x = 340, v[2].y = 230;
v[3].x = 370, v[3].y = 150;
glClear(GL_COLOR_BUFFER_BIT);
MyPolygonFill(4, v);
glFlush();
}
// Reshape callback function
void reshape(int w, int h) {
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, w, 0, h);
}
// Keyboard callback function
void keyboard(unsigned char key, int x, int y) {
switch (key) {
case 27:
exit(0);
}
}
// Main program entrance
int main(int argc, char* argv[]) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB);
glutInitWindowSize(650, 400);
glutInitWindowPosition(50, 100);
glutCreateWindow("Polygon Fill");
init();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutMainLoop();
return 0;
}