计算机图形学学习python实现2 (绘制圆和椭圆) (中点Bresenham画圆法)(完整代码为鼠标绘制)

圆的对称性

下图表示一个圆,图中点 ( x , y ) (x,y) (x,y) 位于第1/8象限,利用圆具有的对称性,可以将该点映射到其他的7/8象限上,得到圆周上的其他七个点,即 ( y , x ) (y,x) (y,x) ( y , − x ) (y,-x) (y,x) ( x , − y ) (x,-y) (x,y) ( − x . − y ) (-x.-y) (x.y) ( − y , − x ) (-y,-x) (y,x) ( − y , x ) (-y,x) (y,x) ( − x , y ) (-x,y) (x,y)。这样我们只需要扫描计算从 x = 0 x=0 x=0 x = y x=y x=y这段圆弧就可以得到整个圆的所有像素点的位置了。
在这里插入图片描述

需要注意的是,在画圆心在窗口任意位置的圆时,需要将每个要画的点的坐标进行相应的移动,移动的方向和距离是待画圆圆心和坐标系原点的相对位置和距离。

glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((y0 + x1) * 0.005, (x0 + y1) * 0.005)
glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glVertex2f((y0 + x1) * 0.005, (-x0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
glVertex2f((-y0 + x1) * 0.005, (x0 + y1) * 0.005)
glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
glVertex2f((-y0 + x1) * 0.005, (-x0 + y1) * 0.005)

Bresenham画圆法

考虑到圆的对称性,我们只画1/8圆弧。假设当前 P ( x i , y i ) P(x_i,y_i) P(xi,yi) 为当前最佳逼近圆的像素点,
Bresenham画圆法的思想是:引入 NEE 的中点 M,如果 M 位于圆内,则 NE 点比较接近圆,所以下一理想的像素点应取 NE 点;反之,如果 M 点在圆外,则 E 点比较接近圆,所以下一理想的像素点应取 E 点。
在这里插入图片描述
注:图片来源于:黄静.计算机图形学及其实践教程[M].北京:机械工业出版社,2015.5:43

初始值:待画圆的圆心坐标 ( x 1 , y 1 ) (x_1,y_1) (x1,y1),圆的半径 r r r。用 ( x 0 , y 0 ) (x_0,y_0) (x0,y0) 来表示即将要画的点。

将圆的圆心移动到原点上,则圆的直角坐标方程可表示为:
x 2 + y 2 = r 2 x^2+y^2=r^2 x2+y2=r2
化为一般式:
F ( x , y ) = x 2 + y 2 − r 2 = 0 F(x,y)=x^2+y^2-r^2=0 F(x,y)=x2+y2r2=0
由数学知识可知:

( 1 ) F ( x , y ) = 0 (1) F(x,y)=0 (1)F(x,y)=0,点在圆上;
( 2 ) F ( x , y ) > 0 (2) F(x,y)>0 (2)F(x,y)>0,点在圆外;
( 1 ) F ( x , y ) < 0 (1) F(x,y)<0 (1)F(x,y)<0,点在圆内。

从圆的上顶点 ( 0 , r ) (0,r) (0,r) 开始从左往右绘制,则初始值为:
x 0 = 0 y 0 = r w = F ( M ) = F ( x 0 + 1 , y 0 − 0.5 ) = ( 0 + 1 ) 2 + ( r − 0.5 ) 2 − r 2 = 1.25 − r \begin{align*} x_0&=0\\ y_0&=r\\ w&=F(M)=F(x_0+1,y_0-0.5)\\ &=(0+1)^2+(r-0.5)^2-r^2\\ &=1.25-r \end{align*} x0y0w=0=r=F(M)=F(x0+1,y00.5)=(0+1)2+(r0.5)2r2=1.25r
对于任意正在绘制的一点 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),其判别式为:
w = F ( M ) = F ( x 0 + 1 , y 0 − 0.5 ) = ( x 0 + 1 ) 2 + ( y 0 − 0.5 ) 2 − r 2 \begin{align*} w&=F(M)=F(x_0+1,y_0-0.5)\\ &=(x_0+1)^2+(y_0-0.5)^2-r^2 \end{align*} w=F(M)=F(x0+1,y00.5)=(x0+1)2+(y00.5)2r2
w < 0 w<0 w<0时,M 在圆内,此时 NE 点更逼近圆,故下一个点取 NE 点,此时:
w = F ( x 0 + 2 , y 0 − 0.5 ) = ( x 0 + 2 ) 2 + ( y 0 − 0.5 ) 2 − r 2 = ( x 0 + 1 ) 2 + ( y 0 − 0.5 ) 2 − r 2 + 2 x 0 + 3 = w + 2 x 0 + 3 x 0 = x 0 + 1 \begin{align*} w&=F(x_0+2,y_0-0.5)\\ &=(x_0+2)^2+(y_0-0.5)^2-r^2\\ &=(x_0+1)^2+(y_0-0.5)^2-r^2+2x_0+3\\ &=w+2x_0+3\\ x_0&=x_0+1 \end{align*} wx0=F(x0+2,y00.5)=(x0+2)2+(y00.5)2r2=(x0+1)2+(y00.5)2r2+2x0+3=w+2x0+3=x0+1
w ≥ 0 w \geq 0 w0时,M 在圆上或圆外,此时视作 E 点更逼近圆,故下一个点取 E 点,此时:
w = F ( x 0 + 2 , y 0 − 1.5 ) = ( x 0 + 2 ) 2 + ( y 0 − 1.5 ) 2 − r 2 = ( x 0 + 1 ) 2 + ( y 0 − 0.5 ) 2 − r 2 + 2 x 0 − 2 y 0 + 5 = w + 2 x 0 − 2 y 0 + 5 x 0 = x 0 + 1 y 0 = y 0 − 1 \begin{align*} w&=F(x_0+2,y_0-1.5)\\ &=(x_0+2)^2+(y_0-1.5)^2-r^2\\ &=(x_0+1)^2+(y_0-0.5)^2-r^2+2x_0-2y_0+5\\ &=w+2x_0-2y_0+5\\ x_0&=x_0+1\\ y_0&=y_0-1 \end{align*} wx0y0=F(x0+2,y01.5)=(x0+2)2+(y01.5)2r2=(x0+1)2+(y00.5)2r2+2x02y0+5=w+2x02y0+5=x0+1=y01
( x 0 , y 0 ) (x_0,y_0) (x0,y0)和原点构成的直线的斜率为:
k = d y 0 d x 0 = y 0 − 0 x 0 − 0 = y 0 x 0 k=\frac{dy_0}{dx_0}=\frac{y_0-0}{x_0-0}=\frac{y_0}{x_0} k=dx0dy0=x00y00=x0y0
k = = 1 k==1 k==1 时,即 y 0 = = x 0 y_0==x_0 y0==x0 时,结束绘制。

除去推理过程可得Bresenham画圆法的完整算法流程:

初始值为:
x 0 = 0 y 0 = r w = 1.25 − r \begin{align*} x_0&=0\\ y_0&=r\\ w&=1.25-r \end{align*} x0y0w=0=r=1.25r
w < 0 w<0 w<0时:
w = w + 2 x 0 + 3 x 0 = x 0 + 1 \begin{align*} w&=w+2x_0+3\\ x_0&=x_0+1 \end{align*} wx0=w+2x0+3=x0+1
w ≥ 0 w \geq 0 w0 时:
w = w + 2 x 0 − 2 y 0 + 5 x 0 = x 0 + 1 y 0 = y 0 − 1 \begin{align*} w&=w+2x_0-2y_0+5\\ x_0&=x_0+1\\ y_0&=y_0-1 \end{align*} wx0y0=w+2x02y0+5=x0+1=y01
循环以上过程直到 x 0 = = y 0 x_0==y_0 x0==y0

以下是Bresenham画圆法的代码实现:

# 中点Bresenham画圆法
def gl_draw_circle(x1, y1, r):
    """
    这个函数用于画给定终点和半径的圆
    :param x1: 圆心横坐标
    :param y1: 圆心纵坐标
    :param r: 圆半径
    :return: None
    """
    x0 = 0
    y0 = r
    w = 1 - y0  # 是1.25 - y0 取整后的结果
    glPointSize(1)
    while x0 < y0:
        glBegin(GL_POINTS)
        glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((y0 + x1) * 0.005, (x0 + y1) * 0.005)
        glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glVertex2f((y0 + x1) * 0.005, (-x0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((-y0 + x1) * 0.005, (x0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glVertex2f((-y0 + x1) * 0.005, (-x0 + y1) * 0.005)
        glEnd()
        x0 += 1
        if w < 0:
            w = w + 2 * x0 + 3
        else:
            w = w + 2 * x0 - 2 * y0 + 5
            y0 -= 1
        x0 += 1
    if x0 == y0:
        glBegin(GL_POINTS)
        glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((y0 + x1) * 0.005, (x0 + y1) * 0.005)
        glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glVertex2f((y0 + x1) * 0.005, (-x0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((-y0 + x1) * 0.005, (x0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glVertex2f((-y0 + x1) * 0.005, (-x0 + y1) * 0.005)
        glEnd()

绘制圆的完整代码

这里鼠标绘制时第一个点为圆的中点,第二个点为圆上任意一点,通过这两个点来确定圆的圆心和半径,通过拖动鼠标可以改变观察将要绘制的圆的大小。

#!/usr/bin/python
# -*- coding: utf-8 -*-
from OpenGL.GL import *
# from OpenGL.GLU import *
from OpenGL.GLUT import *
import math
mx = 200
my = 200
ex = 200
ey = 200
cur_ex = 200
cur_ey = 200
windowsizex = 400
windowsizey = 400
is_cur_begin_draw = 0  # 判断实时绘画线段是否开始绘制
is_begin_draw = 0  # 判断是否开始绘画
is_end_draw = -1  # 判断是否停止绘画
first_left_button_down = 0
lines_list = []   # 保存已经画好了的线


# 中点Bresenham画圆法
def gl_draw_circle(x1, y1, r):
    """
    这个函数用于画给定终点和半径的圆
    :param x1: 圆心横坐标
    :param y1: 圆心纵坐标
    :param r: 圆半径
    :return: None
    """
    x0 = 0
    y0 = r
    w = 1 - y0  # 是1.25 - y0 取整后的结果
    glPointSize(1)
    while x0 < y0:
        glBegin(GL_POINTS)
        glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((y0 + x1) * 0.005, (x0 + y1) * 0.005)
        glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glVertex2f((y0 + x1) * 0.005, (-x0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((-y0 + x1) * 0.005, (x0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glVertex2f((-y0 + x1) * 0.005, (-x0 + y1) * 0.005)
        glEnd()
        if w < 0:
            w = w + 2 * x0 + 3
        else:
            w = w + 2 * x0 - 2 * y0 + 5
            y0 -= 1
        x0 += 1
    if x0 == y0:
        glBegin(GL_POINTS)
        glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((y0 + x1) * 0.005, (x0 + y1) * 0.005)
        glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glVertex2f((y0 + x1) * 0.005, (-x0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((-y0 + x1) * 0.005, (x0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glVertex2f((-y0 + x1) * 0.005, (-x0 + y1) * 0.005)
        glEnd()


def mydisplay():
    # print("-----")
    glClear(GL_COLOR_BUFFER_BIT)
    global is_end_draw
    global is_cur_begin_draw
    global lines_list
    if len(lines_list) != 0:
        for item in lines_list:
            # 绘制图形
            r = int(math.sqrt((item[0] - item[2]) * (item[0] - item[2]) + (item[1] - item[3]) * (item[1] - item[3])))
            gl_draw_circle(item[0], item[1], r)
            pass
    if is_end_draw == 1:
        # 将起始点和结束点添加进去
        lines_list.append([(mx - int(windowsizex / 2)), -(my - int(windowsizey / 2)),
                           (ex - int(windowsizex / 2)), -(ey - int(windowsizey / 2))])
        glutPostRedisplay()
    else:
        if is_cur_begin_draw == 1:
            # 绘制图形
            r = int(math.sqrt((mx - cur_ex) * (mx - cur_ex) + (my - cur_ey) * (my - cur_ey)))
            gl_draw_circle((mx - int(windowsizex / 2)), -(my - int(windowsizey / 2)), r)
            pass
    glutSwapBuffers()  # 双缓存的刷新模式


def mymouse(button, state, mousex, mousey):
    global mx
    global my
    if button == GLUT_RIGHT_BUTTON and state == GLUT_DOWN:
        print("right_button down")
        print("x: ", mousex, " y: ", mousey)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glClearColor(0.0, 0.0, 0.0, 0.0)  # 将清空颜色为黑色
    if button == GLUT_LEFT_BUTTON:
        if state == GLUT_DOWN:
            global is_end_draw
            global first_left_button_down
            global ex
            global ey
            if first_left_button_down == 0:
                mx = mousex
                my = mousey
                print("left_button down")
                print("mx: ", mx, " my: ", my)
                is_end_draw = 0
                first_left_button_down = 1
                glutPostRedisplay()  # 重画,相当于重新调用display
            elif first_left_button_down == 1:
                is_end_draw = 1
                first_left_button_down = 0
                ex = mousex
                ey = mousey
                glutPostRedisplay()  # 重画,相当于重新调用display


def mymousemotion(x1, y1):
    global is_end_draw
    global cur_ex, cur_ey
    global is_cur_begin_draw
    if is_end_draw == 0:
        cur_ex = x1
        cur_ey = y1
        is_cur_begin_draw = 1
        glutPostRedisplay()  # 重画,相当于重新调用display
    else:
        is_cur_begin_draw = 0


if __name__ == "__main__":
    glutInit()  # 对GLUT初始化
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA)  # 设置显示方式
    glutInitWindowSize(windowsizex, windowsizey)
    text = glutCreateWindow("MYTEXT")  # 创建窗口,窗口被创建后,需要调用glutMainLoop()才能看到
    glutDisplayFunc(mydisplay)
    glutMouseFunc(mymouse)
    glutPassiveMotionFunc(mymousemotion)  # 鼠标按钮松开时移动相应函数
    glutMainLoop()
    glClearColor(0.0, 0.0, 0.0, 0.0)  # 将清空颜色为黑色
    glClear(GL_COLOR_BUFFER_BIT)  # 将窗口的背景设置为当前颜色

绘制圆的效果展示

在这里插入图片描述

绘制椭圆

绘制椭圆的思路与绘制圆的思路大致相同,唯一不同的地方在于绘制椭圆时是1/4对称,我们首先绘制第一象限的椭圆,然后将其对称到其他象限。在绘制第一象限的椭圆弧时,当所绘点处的椭圆切线斜率小于-1时,需要将绘制从以x为步长的绘制改为以y为步长的绘制,以求得到更多的椭圆上的点。

初始值:椭圆的圆心 ( x 1 , y 1 ) (x_1,y_1) (x1,y1),椭圆的横半轴长 a a a,纵半轴长 b b b,用 ( x 0 , y 0 ) (x_0,y_0) (x0,y0) 表示正要绘制的顶点坐标。

将椭圆的圆心移动到原点上,则椭圆的直角坐标方程可表示为:
x 2 a 2 + y 2 b 2 = 1 \frac{x^2}{a^2}+\frac{y^2}{b^2}=1 a2x2+b2y2=1
化为一般式:
F ( x , y ) = b 2 x 2 + a 2 y 2 − a 2 b 2 = 0 F(x,y)=b^2x^2+a^2y^2-a^2b^2=0 F(x,y)=b2x2+a2y2a2b2=0
从椭圆的上顶点 ( 0 , b ) (0,b) (0,b) 开始从左往右绘制,则初始值为:
x 0 = 0 y 0 = a \begin{align*} x_0&=0\\ y_0&=a\\ \end{align*} x0y0=0=a
对于任意正要绘制的一点 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),其判别式为:
w = F ( M ) = F ( x 0 + 1 , y 0 − 0.5 ) = b 2 ( x 0 + 1 ) 2 + a 2 ( y 0 − 0.5 ) 2 − a 2 b 2 \begin{align*} w&=F(M)=F(x_0+1,y_0-0.5)\\ &=b^2(x_0+1)^2+a^2(y_0-0.5)^2-a^2b^2 \end{align*} w=F(M)=F(x0+1,y00.5)=b2(x0+1)2+a2(y00.5)2a2b2
为了避免浮点运算,将判别式 w w w 的值乘以4:
w = 4 b 2 ( x 0 + 1 ) 2 + a 2 ( 2 y 0 − 1 ) 2 − 4 a 2 b 2 w=4b^2(x_0+1)^2+a^2(2y_0-1)^2-4a^2b^2 w=4b2(x0+1)2+a2(2y01)24a2b2
w < 0 w<0 w<0时,此时:
x 0 = x 0 + 1 w = F ( x 0 + 2 , y 0 − 0.5 ) = 4 b 2 ( x 0 + 2 ) 2 + a 2 ( 2 y 0 − 1 ) 2 − 4 a 2 b 2 = w + 8 b 2 x 0 + 12 b 2 = w + 4 b 2 ( 2 x 0 + 3 ) \begin{align*} x_0&=x_0+1\\ w&=F(x_0+2,y_0-0.5)\\ &=4b^2(x_0+2)^2+a^2(2y_0-1)^2-4a^2b^2\\ &=w+8b^2x_0+12b^2\\ &=w+4b^2(2x_0+3) \end{align*} x0w=x0+1=F(x0+2,y00.5)=4b2(x0+2)2+a2(2y01)24a2b2=w+8b2x0+12b2=w+4b2(2x0+3)
w ≥ 0 w \geq 0 w0时,此时:
x 0 = x 0 + 1 y 0 = y 0 − 1 w = F ( x 0 + 2 , y 0 − 1.5 ) = 4 b 2 ( x 0 + 2 ) 2 + a 2 ( 2 y 0 − 3 ) 2 − 4 a 2 b 2 = w + 4 b 2 ( 2 x 0 + 3 ) + 4 a 2 ( 2 − 2 y 0 ) \begin{align*} x_0&=x_0+1\\ y_0&=y_0-1\\ w&=F(x_0+2,y_0-1.5)\\ &=4b^2(x_0+2)^2+a^2(2y_0-3)^2-4a^2b^2\\ &=w+4b^2(2x_0+3)+4a^2(2-2y_0) \end{align*} x0y0w=x0+1=y01=F(x0+2,y01.5)=4b2(x0+2)2+a2(2y03)24a2b2=w+4b2(2x0+3)+4a2(22y0)
椭圆的方程为:
F ( x , y ) = b 2 x 2 + a 2 y 2 − a 2 b 2 = 0 F(x,y)=b^2x^2+a^2y^2-a^2b^2=0 F(x,y)=b2x2+a2y2a2b2=0
两边同时对x求导:
2 b 2 x + 2 a 2 y d y d x = 0 2b^2x+2a^2y\frac{dy}{dx}=0 2b2x+2a2ydxdy=0
故:
d y d x = − b 2 x a 2 y \frac{dy}{dx}=-\frac{b^2x}{a^2y} dxdy=a2yb2x
d y d x > − 1 \frac{dy}{dx}>-1 dxdy>1 时,即 b 2 x − a 2 y < 0 b^2x-a^2y<0 b2xa2y<0 时,以 x x x 为步长绘制椭圆。当 b 2 x − a 2 y > 0 b^2x-a^2y>0 b2xa2y>0 时,开始转化为以 y y y 为步长绘制椭圆,当 y 0 = = 0 y_0==0 y0==0 时结束绘制。以 y y y 为步长的椭圆绘制算法的推演与以 x x x 为步长绘制椭圆的算法推演类似。

下面时绘制椭圆的代码实现:

# 椭圆绘制算法
def gl_draw_ellipse(x1, y1, m_a, m_b):
    """
    这是用来绘制椭圆的函数
    :param x1: 椭圆中心横坐标
    :param y1: 椭圆中心纵坐标
    :param m_a: 椭圆横轴长
    :param m_b: 椭圆纵轴长
    :return: None
    """
    x0 = 0
    y0 = m_b
    m_a2 = m_a * m_a
    m_b2 = m_b * m_b
    w = 4 * m_b2 * (x0 + 1) * (x0 + 1) + \
        m_a2 * (2 * y0 - 1) * (2 * y0 - 1) - \
        4 * m_a2 * m_b2  # 判别式,用来判断下一个点的选取
    glPointSize(1)
    # 判断当前点所在椭圆的切线的斜率,如果斜率小于-1,则不交换
    while m_b2 * x0 - m_a2 * y0 < 0:
        glBegin(GL_POINTS)
        glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glEnd()
        x0 += 1
        if w < 0:
            w = w + 4 * m_b2 * (2 * x0 + 3)
        else:
            y0 -= 1
            w = w + 4 * m_b2 * (2 * x0 + 3) + 4 * m_a2 * (2 - 2 * y0)
    if m_b2 * x0 - m_a2 * y0 == 0:
        glBegin(GL_POINTS)
        glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glEnd()
        w = m_b2 * (2 * x0 + 1) * (2 * x0 + 1) + \
            4 * m_a2 * (y0 - 1) * (y0 - 1) - \
            4 * m_a2 * m_b2
    while x0 < m_a and y0 >= 0:
        glBegin(GL_POINTS)
        glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glEnd()
        y0 -= 1
        if w < 0:
            x0 += 1
            w = w + 4 * m_b2 * (2 * x0 + 2) + 4 * m_a2 * (-2 * y0 + 3)
        else:
            w = w + 4 * m_a2 * (-2 * y0 + 3)

绘制椭圆的完整代码

这里鼠标点击的第一个点是椭圆外切矩形的其中一个顶点,第二个点为和第一个点互为对角的椭圆外切矩形的另一个顶点,从这两个顶点推出椭圆圆心、长半轴长和短半轴长,通过移动鼠标可以观察将要绘制的椭圆的实时变化,以下是绘制椭圆的完整代码实现。

#!/usr/bin/python
# -*- coding: utf-8 -*-
from OpenGL.GL import *
# from OpenGL.GLU import *
from OpenGL.GLUT import *
# import math
mx = 200
my = 200
ex = 200
ey = 200
cur_ex = 200
cur_ey = 200
windowsizex = 400
windowsizey = 400
is_cur_begin_draw = 0  # 判断实时绘画线段是否开始绘制
is_begin_draw = 0  # 判断是否开始绘画
is_end_draw = -1  # 判断是否停止绘画
first_left_button_down = 0
lines_list = []   # 保存已经画好了的线


# 椭圆绘制算法
def gl_draw_ellipse(x1, y1, m_a, m_b):
    """
    这是用来绘制椭圆的函数
    :param x1: 椭圆中心横坐标
    :param y1: 椭圆中心纵坐标
    :param m_a: 椭圆横轴长
    :param m_b: 椭圆纵轴长
    :return: None
    """
    x0 = 0
    y0 = m_b
    m_a2 = m_a * m_a
    m_b2 = m_b * m_b
    w = 4 * m_b2 * (x0 + 1) * (x0 + 1) + \
        m_a2 * (2 * y0 - 1) * (2 * y0 - 1) - \
        4 * m_a2 * m_b2  # 判别式,用来判断下一个点的选取
    glPointSize(1)
    # 判断当前点所在椭圆的切线的斜率,如果斜率小于-1,则不交换
    while m_b2 * x0 - m_a2 * y0 < 0:
        glBegin(GL_POINTS)
        glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glEnd()
        x0 += 1
        if w < 0:
            w = w + 4 * m_b2 * (2 * x0 + 3)
        else:
            y0 -= 1
            w = w + 4 * m_b2 * (2 * x0 + 3) + 4 * m_a2 * (2 - 2 * y0)
    if m_b2 * x0 - m_a2 * y0 == 0:
        glBegin(GL_POINTS)
        glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glEnd()
        w = m_b2 * (2 * x0 + 1) * (2 * x0 + 1) + \
            4 * m_a2 * (y0 - 1) * (y0 - 1) - \
            4 * m_a2 * m_b2
    while x0 < m_a and y0 >= 0:
        glBegin(GL_POINTS)
        glVertex2f((x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (y0 + y1) * 0.005)
        glVertex2f((-x0 + x1) * 0.005, (-y0 + y1) * 0.005)
        glEnd()
        y0 -= 1
        if w < 0:
            x0 += 1
            w = w + 4 * m_b2 * (2 * x0 + 2) + 4 * m_a2 * (-2 * y0 + 3)
        else:
            w = w + 4 * m_a2 * (-2 * y0 + 3)


def mydisplay():
    # print("-----")
    # global windowsizex, windowsizey
    # windowsizex = glutGet(GLUT_WINDOW_X)
    # windowsizey = glutGet(GLUT_WINDOW_Y)
    glClear(GL_COLOR_BUFFER_BIT)
    global is_end_draw
    global is_cur_begin_draw
    global lines_list
    if len(lines_list) != 0:
        for item in lines_list:
            # 绘制图形
            m_a = int(abs(item[0] - item[2]) / 2)
            m_b = int(abs(item[1] - item[3]) / 2)
            mid_x = (item[0] + item[2]) / 2
            mid_y = (item[1] + item[3]) / 2
            gl_draw_ellipse(mid_x, mid_y, m_a, m_b)
            pass
    if is_end_draw == 1:
        # 将起始点和结束点添加进去
        lines_list.append([(mx - int(windowsizex / 2)), -(my - int(windowsizey / 2)),
                           (ex - int(windowsizex / 2)), -(ey - int(windowsizey / 2))])
        glutPostRedisplay()
    else:
        if is_cur_begin_draw == 1:
            # 绘制图形
            m_a = int(abs(mx - cur_ex)/2)
            m_b = int(abs(my - cur_ey)/2)
            mid_x = (mx + cur_ex)/2
            mid_y = (my + cur_ey)/2
            gl_draw_ellipse((mid_x - int(windowsizex / 2)), -(mid_y - int(windowsizey / 2)), m_a, m_b)
            pass
    glutSwapBuffers()  # 双缓存的刷新模式


def mymouse(button, state, mousex, mousey):
    global mx
    global my
    if button == GLUT_RIGHT_BUTTON and state == GLUT_DOWN:
        print("right_button down")
        print("x: ", mousex, " y: ", mousey)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glClearColor(0.0, 0.0, 0.0, 0.0)  # 将清空颜色为黑色
    if button == GLUT_LEFT_BUTTON:
        if state == GLUT_DOWN:
            global is_end_draw
            global first_left_button_down
            global ex
            global ey
            if first_left_button_down == 0:
                mx = mousex
                my = mousey
                print("left_button down")
                print("mx: ", mx, " my: ", my)
                is_end_draw = 0
                first_left_button_down = 1
                glutPostRedisplay()  # 重画,相当于重新调用display
            elif first_left_button_down == 1:
                is_end_draw = 1
                first_left_button_down = 0
                ex = mousex
                ey = mousey
                glutPostRedisplay()  # 重画,相当于重新调用display


def mymousemotion(x1, y1):
    global is_end_draw
    global cur_ex, cur_ey
    global is_cur_begin_draw
    if is_end_draw == 0:
        cur_ex = x1
        cur_ey = y1
        is_cur_begin_draw = 1
        glutPostRedisplay()  # 重画,相当于重新调用display
    else:
        is_cur_begin_draw = 0


if __name__ == "__main__":
    glutInit()  # 对GLUT初始化
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA)  # 设置显示方式
    glutInitWindowSize(windowsizex, windowsizey)
    text = glutCreateWindow("MYTEXT")  # 创建窗口,窗口被创建后,需要调用glutMainLoop()才能看到
    glutDisplayFunc(mydisplay)
    glutMouseFunc(mymouse)
    glutPassiveMotionFunc(mymousemotion)  # 鼠标按钮松开时移动相应函数
    glutMainLoop()
    glClearColor(0.0, 0.0, 0.0, 0.0)  # 将清空颜色为黑色
    glClear(GL_COLOR_BUFFER_BIT)  # 将窗口的背景设置为当前颜色

绘制椭圆的效果展示

在这里插入图片描述

结语

本文算法推演部分部分来自于参考文献的摘要,实现的代码未经优化,仅供学习与参考使用,如有错误,敬请谅解。

参考文献

[1] 黄静.计算机图形学及其实践教程[M].北京:机械工业出版社,2015.5:41-44
[2] 徐文鹏.计算机图形学基础(OpenGL版)[M].北京:清华大学出版社,2016.6:50-55

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值