【计算机图形学】实验7《复杂图形绘制1》

前言

本实验有参考,如果想直接看原理或者源代码,以下材料更易理解:

(34条消息) 基于C++、OpenGL绘制贝塞尔曲线(Bézier curve)_potato-mine的博客-CSDN博客_c++画曲线图

(34条消息) opengl绘制三次hermite曲线,三次cardinal曲线_Marco&GalaxyDragon的博客-CSDN博客_opengl 画的贝塞尔曲线不连续

因为跟着参考做的时候,在实验时遇到一些异常的结果(指下图这种情况),所以记录了下来。

实验7复杂图形绘制1

一、实验目的

学习样条曲线的绘制。

二、实验内容

1. 绘制Bezier曲线;

2. 绘制Hermite曲线。


三、实验方法

(一)Bezier曲线

首先了解什么是Bezier曲线。如下图所示,作AB,BC,在AB,BC分别取点D,E,使得AD/DB=BE/EC,然后再在DE上去点F,使得AD/DB=BE/EC=DF/FE。取满足条件的所有点F,即为Bezier曲线。

给定点P0, P1, P2, ..., Pn, 则n次贝塞尔曲线由下式给出:

设计的程序希望可以满足以下功能:

(1)点击窗口内任意区域作点,并根据点生成Bezier曲线;

(2)清空窗口内所有的点,重新绘制;

(3)移动已经生成的点,以此改变已经生成的Bezier曲线。

  • Hermite曲线

Hermite曲线通过初始点,初始速度(切向量),最终点,最终速度(切向量)来计算曲线。假设三次多项式函数P(t) = c0 + c1*x + c2*x² + c3*x³,初始坐标点为p0,初始速度v0,终点坐标p1,终点速度v1,H0(t) = 1-3t²+2t³; H1(t) = t - 2t²+ t³; H2(t) = -t²+t³; H3(t) = 3t²-2t³,则有:

设计的程序希望可以满足以下功能:

  1. 绘制Hermite曲线;
  2. 用户可以手动调整切向量和端点坐标。

四、实验步骤

Bezier曲线

1. 准备算法

1结构体point:保存点的坐标(x, y);

(2)mouse_click:判断用户操作是作点还是移动点,并根据用户操作执行结果;

(3)mouse_motion:用户移动点;

(4)keyboard:根据键盘的操作执行结果。

(5)DrawBeziercurve:计算与绘制Bezier曲线;

(6)display:进行画点、画辅助直线、画Bezier曲线等操作;

(7)其他函数、变量、结构体、声明等。

2. 编写代码

1结构体point

新建结构体point数组,元素个数为MAXPOINTS,意味着用户最多可以绘制MAXPOINTS个点。

(2)mouse_click

当用户点击鼠标左键,且当前还可新建点时,判断鼠标点击的坐标是否在已绘制点的附近,如果不在则新建一个点,并发送重绘消息;否则程序将认为用户希望移动某个已存在的点。

if (state == GLUT_DOWN && click_state == NEWPOINT) { //新增点

if (control_points_n < MAXPOINTS) {

P[control_points_n].x = x;

P[control_points_n].y = screen_height - y;

specific_p = control_points_n;

control_points_n++;

glutPostRedisplay();

} else click_state = POINTREACHMAX; //无法新增控制点,只能移动点

} else if (state == GLUT_DOWN && click_state == MOVEPOINT) { //移动点

P[specific_p].x = x;

P[specific_p].y = screen_height - y;

glutPostRedisplay();

} else if (state == GLUT_UP) {

//判断控制点是否已达上限

click_state = control_points_n < MAXPOINTS ? NEWPOINT : POINTREACHMAX;

}

}

(3)mouse_motion

若用户的点击状态表示希望移动点,则修改该点的坐标为用户释放鼠标的坐标,并发送重绘消息。

  1. keyboard

根据键盘的操作执行结果,当用户点击r时表示希望重新绘制,则屏幕清空;若用户点击esc键则表示退出程序。

(5)DrawBeziercurve

计算Bezier曲线。

float t = (float)i / (float)m;  //i从0到m

for (j = 0; j < control_points_n; j++) {

Beziercurve_pointx += C[j] * pow(1 - t, control_points_n - j - 1) * pow(t, j) * p[j].x;

Beziercurve_pointy += C[j] * pow(1 - t, control_points_n - j - 1) * pow(t, j) * p[j].y;

}

(6)display:进行画点、画辅助直线、画Bezier曲线等操作。

void display(void) {

int i;

glClear(GL_COLOR_BUFFER_BIT);

for (i = 0; i < control_points_n; i++) { //画点

Draw_Square(P[i].x, P[i].y);

}

//显示点坐标

gotoxy(0, specific_p + 3);

printf("P%d:(%d, %d)  ", specific_p, P[specific_p].x, P[specific_p].y);

glLineStipple(1, 0x0f0f);

glBegin(GL_LINE_STRIP);

glColor3f(1, 1, 1);

for (i = 0; i < control_points_n; i++) { //画辅助虚线

glVertex2f(P[i].x, P[i].y);

}

glEnd();

if (control_points_n > 1) { //画贝塞尔曲线

DrawBeziercurve(control_points_n - 1, P, 200);

}

glutSwapBuffers();

}

(7)其他函数、变量、结构体、声明等。

3. 修改错误与完善程序

起初因为不太理解Bezier曲线是什么,所以在计算的函数里频频出错;在绘制时也一时不知道应该如何画出曲线来。后来发现可以用多组短小线段来近似画出曲线,于是解决了问题。最后在对于click_state的设置时,定义了NEWPOINT, MOVEPOIN, POINTREACHMAX表示新增点、移动点和不能再新增点这三个状态。

Hermite曲线

1. 准备算法

1结构体Point_H:保存点的坐标(x, y);

(2)DrawHermiteCurve:计算并绘制Hermite曲线;

(3)display:绘制曲线、点与切向量等;

(4)motion:鼠标拖动点以调整曲线;

(5)其他函数、变量、结构体、声明等。

2. 编写代码

1结构体Point_H

保存点的坐标(x, y),初始端点P0, P1, 切向量derP0, derP1。

(2)DrawHermiteCurve

首先计算H0(t), H1(t), H2(t), H3(t),然后进行矩阵叉乘,然后作图,代码如下:

f1 = 2.0 * pow(t, 3)  - 3.0 * pow(t, 2) + 1.0;

f2 = -2.0 * pow(t, 3)  + 3.0 * pow(t, 2);

f3 =  pow(t, 3)   - 2.0 * pow(t, 2) + t;

f4 =  pow(t, 3)  - pow(t, 2);

x = f1 * P0.x + f2 * P1.x + f3 * derP0.x + f4 * derP1.x;

y = f1 * P0.y + f2 * P1.y + f3 * derP0.y + f4 * derP1.y ;

glVertex2f(x, y);

  1. display

绘制曲线、点与切向量等;由于切向量的模长较大,如果按原比例画图的话不够美观,用户改变切向量以此改变曲线形状的效果也可能不够明显,因此将切向量缩小了1/4,控制切向量的点的坐标也缩小1/4。

glLineWidth(2);

glColor3f (1.0, 0.0, 0.0);

glBegin(GL_LINES);

glVertex2f(P0.x, P0.y);

glVertex2f(P0.x + derP0.x / 4, P0.y + derP0.y / 4);

glVertex2f(P1.x, P1.y);

glVertex2f(P1.x - derP1.x / 4, P1.y - derP1.y / 4);

(4)motion

判断用户按下的是鼠标左键还是右键,如果是左键则可以拖动点改变切向量,如果是右键则可以拖动点改变端点位置,并发送重绘的消息。

void motion(int x, int y) {

if (mouseLeftDown) {

if (distance(P0.x + derP0.x / 4, P0.y + derP0.y / 4, x, y) < 20) {

derP0.x = (x - P0.x) * 4;

derP0.y = (y - P0.y) * 4;

}

if (distance(P1.x - derP1.x / 4, P1.y - derP1.y / 4, x, y) < 20) {

derP1.x = (P1.x - x) * 4;

derP1.y = (P1.y - y) * 4;

}

}

if(mouseRightDown){

if (distance(P0.x, P0.y, x, y) < 20) {

P0.x = x;

P0.y = y;

}

if (distance(P1.x, P1.y, x, y) < 20) {

P1.x = x;

P1.y = y;

}

}

glutPostRedisplay();

}

(5)其他函数、变量、结构体、声明等。

mouse函数用于判断鼠标的状态,distance函数用于计算两点之间的距离。

3. 修改错误与完善程序

起初Hermite曲线总是不能连接到两端点,最后突发奇想地加了反走样处理后居然就可以成功作图了,同时如果注释掉反走样处理就又不能成功作图。后来修改了曲线、点和切向量的颜色方便用户分辨,并且在控制台补充打印了使用说明。

五、实验结果

(一)Bezier曲线

运行结果如下。

点击窗口内任意位置进行绘制点的操作,窗口绘制出虚线与Bezier曲线,且控制台打印出点的坐标。

点击r键重新绘制。

 

点击已存在的点并拖动,改变位置。

(二)Hermite曲线

运行结果如下。

运行程序后,窗口显示白色的端点、红色的切向量、控制切向量的点、Hermite曲线,且控制台打印用户提示。

左键按住绿点修改切向量。

右键按住白点修改端点坐标。

六、实验结论

(一)Berzier曲线

1. 实验结论

本次实验完成了Bezier曲线的绘制,且实现了一些基础的功能增强用户的交互感。但是本程序也存在一些问题。如下图,Bezier曲线在绘制的时候,往往在点数到达13左右时,绘制的结果与预期大相庭径。

我认为这是由于在计算第一个点P0时,由于control_points_n较大,导致pow(1 - t, control_points_n - j - 1)过小,使得坐标(x,y)取值为(0,0),于是出现了“脱缰”的画面:

  1. 源代码
#include <windows.h>

#include <GL/glut.h>

#include <math.h>

#include <stdio.h>



#define MAXPOINTS 20

#define NEWPOINT 0 //新增点

#define MOVEPOINT 1 //移动点

#define POINTREACHMAX 2 //控制点数目已达最大值



int* C;//二项式系数

int control_points_n = 0;//控制点数量

int specific_p;//特定点标志

int click_state = NEWPOINT;//鼠标点击的状态

int screen_width, screen_height;//屏幕宽度,长度



struct point {

int x;

int y;

};

point* P;





void reshape(int w, int h);

void keyboard(unsigned char button, int x, int y);

void mouse_click(int button, int state, int x, int y);

void mouse_motion(int x, int y);//鼠标在窗口中按下并移动

void display(void);





//显示/打印坐标函数

void gotoxy(int x, int y) {

COORD coord;

coord.X = x;

coord.Y = y;

SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);  //设置指定控制台屏幕缓冲区中的光标位置

}



int main(int argc, char** argv) {

P = new point[MAXPOINTS];

printf("\"ESC\"键退出.                  \"R\"键重新绘制\r\n");

printf("最多添加%d个点.", MAXPOINTS);



glutInit(&argc, argv);

glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);

glutInitWindowPosition(300, 300);

glutInitWindowSize(500, 500);

glutCreateWindow("7复杂图形绘制-贝塞尔曲线");

glClearColor(0.0, 0.0, 0.0, 0.0);

glEnable(GL_LINE_STIPPLE);

glutReshapeFunc(reshape);

glutKeyboardFunc(keyboard);

glutMouseFunc(mouse_click);

glutMotionFunc(mouse_motion);

glutDisplayFunc(display);

glutMainLoop();



delete[] P;

return 0;

}



//标记点

void Draw_Square(int x, int y) {

glColor3f(1, 0, 0);

glBegin(GL_POLYGON);

glVertex2f(x + 2, y + 2);

glVertex2f(x - 2, y + 2);

glVertex2f(x - 2, y - 2);

glVertex2f(x + 2, y - 2);

glEnd();

}



/*计算二项式系数,C[k] = C(k)(n)*/

void CulculateBinomialCoefficient(int n, int* C) {

int k, j;

for (k = 0; k <= n; k++) {

C[k] = 1;

for (j = n; j >= k + 1; j--) {

C[k] *= j;

}

for (j = n - k; j >= 2; j--) {

C[k] /= j;

}

}

}



//绘制贝塞尔曲线

void DrawBeziercurve(int n, point* p, int m) {

int i, j;

float t;

int C[MAXPOINTS - 1];

float Beziercurve_pointx;

float Beziercurve_pointy;

CulculateBinomialCoefficient(n, C);



//画线

glLineStipple(1, 0xffff);

glBegin(GL_LINE_STRIP);

for (i = 0; i <= m; i++) {

t = (float)i / (float)m;

Beziercurve_pointx = 0;

Beziercurve_pointy = 0;

for (j = 0; j < control_points_n; j++) {

//B(t) = sum( C(i,n) * P(i) * (1-t)^(n-i) * t^i )

Beziercurve_pointx += C[j] * pow(1 - t, control_points_n - j - 1) * pow(t, j) * p[j].x;

Beziercurve_pointy += C[j] * pow(1 - t, control_points_n - j - 1) * pow(t, j) * p[j].y;

}

glVertex2f(Beziercurve_pointx, Beziercurve_pointy);

}

glEnd();

}



void reshape(int w, int h) {

screen_width = w;

screen_height = h;



glViewport(0, 0, w, h);

glMatrixMode(GL_PROJECTION);

glLoadIdentity();

gluOrtho2D(0.0, w, 0.0, h);

}





void keyboard(unsigned char button, int x, int y) {

switch (button) {

case 82://"R"

case 114://"r"

//重新绘图

//清空之前显示的坐标

gotoxy(0, 3);

for (int i = 0; i < control_points_n; i++) {

printf("                                   \r\n");

}



specific_p = control_points_n = 0;

click_state = 0;



glutPostRedisplay();

break;

case 27:

exit(0);

}

}



//处理鼠标click事件函数

void mouse_click(int button, int state, int x, int y) {

int i;

int distance;//鼠标点击处于任意一点距离

if (button == GLUT_LEFT_BUTTON) {//鼠标左键按下

if (state == GLUT_DOWN && click_state != MOVEPOINT) {//若还可以移动点(即不止只能移动点),则计算距离

for (i = 0; i < control_points_n; i++) {

//按控制点依次计算距离

distance = (x - P[i].x) * (x - P[i].x) +

           (screen_height - y - P[i].y) * (screen_height - y - P[i].y);

if (distance < 20) { //如果距离小于特定值。则开始移动点

click_state = MOVEPOINT;

specific_p = i;

break;//跳出循环

}

}

}



if (state == GLUT_DOWN && click_state == NEWPOINT) { //新增点

if (control_points_n < MAXPOINTS) {

P[control_points_n].x = x;

P[control_points_n].y = screen_height - y;

specific_p = control_points_n;

control_points_n++;

glutPostRedisplay();

} else click_state = POINTREACHMAX; //无法新增控制点,只能移动点

} else if (state == GLUT_DOWN && click_state == MOVEPOINT) { //移动点

P[specific_p].x = x;

P[specific_p].y = screen_height - y;

glutPostRedisplay();

} else if (state == GLUT_UP) {

//判断控制点是否已达上限

click_state = control_points_n < MAXPOINTS ? NEWPOINT : POINTREACHMAX;

}

}

}



//鼠标在窗口中按下并移动

void mouse_motion(int x, int y) {

if (click_state == MOVEPOINT) {

P[specific_p].x = x;

P[specific_p].y = screen_height - y;

glutPostRedisplay();

}

}



//显示函数,需要显示时调用此函数

void display(void) {

int i;



glClear(GL_COLOR_BUFFER_BIT);



for (i = 0; i < control_points_n; i++) { //画点

Draw_Square(P[i].x, P[i].y);

}

//显示点坐标

gotoxy(0, specific_p + 3);

printf("P%d:(%d, %d)  ", specific_p, P[specific_p].x, P[specific_p].y);

glLineStipple(1, 0x0f0f);

glBegin(GL_LINE_STRIP);

glColor3f(1, 1, 1);

for (i = 0; i < control_points_n; i++) { //画虚线

glVertex2f(P[i].x, P[i].y);

}

glEnd();



if (control_points_n > 1) { //画贝塞尔曲线

DrawBeziercurve(control_points_n - 1, P, 200);

}



glutSwapBuffers();

}

(二)Hermite曲线

1. 实验结论

本次实验完成了Hermite曲线的绘制,且实现了一些基础的功能增强用户的交互感。但是本程序还有可以改进的地方。我在网上查到也有实现“给定一定数量的点坐标,和端点的切向量,绘制出经过这些点的Hermite曲线”的方法,且可以修改这些点的位置,因此还有很多改进的地方。

2. 源代码

#include <math.h>

#include <GL/glut.h>

#include <iostream>

using namespace std;



struct Point_H {

double x;

double y;

Point_H(int px, int py) {

x = px;

y = py;

}

};



Point_H P0(100, 200);

Point_H P1(700, 450);

Point_H derP0(100, 200);

Point_H derP1(-500, 300);



bool mouseLeftDown = false;

bool mouseRightDown = false;



//计算Hermite曲线

void DrawHermiteCurve(int n) {

float f1, f2, f3, f4;

float x, y;

double t;

x = 0;

y = 0;

glColor3f (1.0, 1.0, 0.0);

glEnable(GL_LINE_SMOOTH);

glEnable(GL_BLEND);

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);

glBegin(GL_LINE_STRIP);



for (int i = 0; i <= n; i++) {

t = (double)i / n;

f1 = 2.0 * pow(t, 3)  - 3.0 * pow(t, 2) + 1.0;

f2 = -2.0 * pow(t, 3)  + 3.0 * pow(t, 2);

f3 =  pow(t, 3)   - 2.0 * pow(t, 2) + t;

f4 =  pow(t, 3)  - pow(t, 2);



x = f1 * P0.x + f2 * P1.x + f3 * derP0.x + f4 * derP1.x;

y = f1 * P0.y + f2 * P1.y + f3 * derP0.y + f4 * derP1.y ;

glVertex2f(x, y);



}

glEnd();

}



void display() {

glClear(GL_COLOR_BUFFER_BIT);



glLineWidth(2);

glColor3f (1.0, 0.0, 0.0);

glBegin(GL_LINES);

glVertex2f(P0.x, P0.y);

glVertex2f(P0.x + derP0.x / 4, P0.y + derP0.y / 4);

glVertex2f(P1.x, P1.y);

glVertex2f(P1.x - derP1.x / 4, P1.y - derP1.y / 4);

glEnd();



glColor3f (1.0, 1.0, 1.0);

glPointSize(10.0f);

//反走样

glEnable(GL_POINT_SMOOTH);

glEnable(GL_BLEND);

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);



glEnable(GL_LINE_SMOOTH);

glEnable(GL_BLEND);

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);



glBegin(GL_POINTS);

glColor3f (1.0, 1.0, 1.0);

glVertex2f(P0.x, P0.y);

glVertex2f(P1.x, P1.y);

glColor3f(0.0, 1.0, 0.0);

glVertex2f(P0.x + derP0.x / 4, P0.y + derP0.y / 4);

glVertex2f(P1.x - derP1.x / 4, P1.y - derP1.y / 4);

glEnd();



DrawHermiteCurve(200);



glFlush();

glutSwapBuffers();

}







void reshape(int w, int h) {

glViewport(0, 0, (GLsizei)w, (GLsizei)h);

glMatrixMode(GL_PROJECTION);

glLoadIdentity();

gluOrtho2D(0.0, (GLsizei)w, (GLsizei)h, 0.0);

glMatrixMode(GL_MODELVIEW);

glLoadIdentity();

}



void mouse(int button, int state, int x, int y) {

if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {

mouseLeftDown = true;

}



if (button == GLUT_LEFT_BUTTON && state == GLUT_UP) {

mouseLeftDown = false;

}



if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN) {

mouseRightDown = true;

}



if (button == GLUT_RIGHT_BUTTON && state == GLUT_UP) {

mouseRightDown = false;

}

}



double distance(int x1, int y1, int x2, int y2) {

return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));

}



void motion(int x, int y) {

if (mouseLeftDown) {

if (distance(P0.x + derP0.x / 4, P0.y + derP0.y / 4, x, y) < 20) {

derP0.x = (x - P0.x) * 4;

derP0.y = (y - P0.y) * 4;

}



if (distance(P1.x - derP1.x / 4, P1.y - derP1.y / 4, x, y) < 20) {

derP1.x = (P1.x - x) * 4;

derP1.y = (P1.y - y) * 4;

}

}



if(mouseRightDown){

if (distance(P0.x, P0.y, x, y) < 20) {

P0.x = x;

P0.y = y;

}



if (distance(P1.x, P1.y, x, y) < 20) {

P1.x = x;

P1.y = y;

}

}



glutPostRedisplay();

}



int  main(int argc, char** argv) {

cout<<"按住鼠标左键拖动改变切向量.           按住右键拖动改变端点坐标."<<endl;

glutInit(&argc, argv);

glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);

glutInitWindowSize (900, 900);

glutInitWindowPosition (200, 200);

glutCreateWindow ("7复杂图形绘制-埃尔米特曲线");

glClearColor(0.0, 0.0, 0.0, 0.0);

glShadeModel(GL_FLAT);

glutDisplayFunc(display);

glutReshapeFunc(reshape);

glutMouseFunc(mouse);

glutMotionFunc(motion);



glutMainLoop();

return 0;

}

七、实验小结

本次实验自主学习了Berzier曲线、Hermite曲线为何物,学习了样条曲线的绘制,并且根据调试发现了程序一些问题,依据问题找到了产生问题的原因。不过在控制台打印等方面还是有一些小毛病没能修改;由于程序只考虑了用户正确输入的情况,所以若用户非法输入,程序没有办法处理或者提示用户,而只能“将错就错”。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值