圆的对称性
下图表示一个圆,图中点
(
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画圆法的思想是:引入 NE 和 E 的中点 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+y2−r2=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,y0−0.5)=(0+1)2+(r−0.5)2−r2=1.25−r
对于任意正在绘制的一点
(
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,y0−0.5)=(x0+1)2+(y0−0.5)2−r2
当
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,y0−0.5)=(x0+2)2+(y0−0.5)2−r2=(x0+1)2+(y0−0.5)2−r2+2x0+3=w+2x0+3=x0+1
当
w
≥
0
w \geq 0
w≥0时,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,y0−1.5)=(x0+2)2+(y0−1.5)2−r2=(x0+1)2+(y0−0.5)2−r2+2x0−2y0+5=w+2x0−2y0+5=x0+1=y0−1
点
(
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=x0−0y0−0=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.25−r
当
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
w≥0 时:
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+2x0−2y0+5=x0+1=y0−1
循环以上过程直到
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+a2y2−a2b2=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,y0−0.5)=b2(x0+1)2+a2(y0−0.5)2−a2b2
为了避免浮点运算,将判别式
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(2y0−1)2−4a2b2
当
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,y0−0.5)=4b2(x0+2)2+a2(2y0−1)2−4a2b2=w+8b2x0+12b2=w+4b2(2x0+3)
当
w
≥
0
w \geq 0
w≥0时,此时:
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=y0−1=F(x0+2,y0−1.5)=4b2(x0+2)2+a2(2y0−3)2−4a2b2=w+4b2(2x0+3)+4a2(2−2y0)
椭圆的方程为:
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+a2y2−a2b2=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
b2x−a2y<0 时,以
x
x
x 为步长绘制椭圆。当
b
2
x
−
a
2
y
>
0
b^2x-a^2y>0
b2x−a2y>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