1 抛物线的特征
通常定义抛物线为到一条直线(准线)和直线外一点(焦点)距离相等的点的集合。这里只讨论顶点为原点,沿纵坐标轴对称且开口向上的情况。而对于其他情况可以通过图形的平移和旋转等线性变换得到。其描述方程如下:
F
(
x
,
y
)
=
y
−
a
x
2
(
a
>
0
)
F(x,y)=y-ax^2(a>0)
F(x,y)=y−ax2(a>0)
与椭圆不同,抛物线是无边界的非封闭图形,若要在屏幕上绘制,必须给定坐标范围,以绘制指定抛物线的一个片段。可以在函数中设置参数
x
_
b
o
r
d
e
r
x_border
x_border ,则横坐标约束其范围为
[
−
x
_
b
o
r
d
e
r
,
x
_
b
o
r
d
e
r
]
[-x_border,x_border]
[−x_border,x_border]。
抛物线关于纵坐标轴对称,故只需绘制其第一象限内的点,第二象限中的点可以通过对称得到。
为确定最大位移方向,考虑抛物线的斜率范围。在第一象限,其上一点
(
x
,
y
)
(x, y)
(x,y)处的斜率为:
k
(
x
)
=
∂
F
(
x
,
y
)
∂
x
=
2
a
x
∈
[
0
,
+
∞
)
k(x) = frac{partial F(x,y)}{partial x}=2ax in [0,+infin)
k(x)=∂x∂F(x,y)=2ax∈[0,+∞)
又由于斜率的变化率为:
d
k
(
x
)
d
x
=
d
(
2
a
x
)
d
x
=
2
a
>
0
frac{dk(x)}{dx}=frac{d(2ax)}{dx}=2a>0
dxdk(x)=dxd(2ax)=2a>0
所以在第一象限内,抛物线斜率从0开始随
x
x
x递增至正无穷。用斜率为1的点对图形进行划分。容易解出,当斜率为1时,
x
=
1
2
a
x=frac{1}{2a}
x=2a1。只需沿
x
x
x轴绘制图形,其中
0
<
x
<
1
2
a
0<x<frac{1}{2a}
0<x<2a1时,最大位移方向为
x
x
x方向;
1
2
a
≤
x
<
x
_
b
o
r
d
e
r
frac{1}{2a}le x < x_border
2a1≤x<x_border时,最大位移方向为
y
y
y方向。
2 算法推导过程
假定当前与抛物线距离最近者已确定为 P ( x i , y i ) P(x_i,y_i) P(xi,yi),那么在抛物线前部分时,下一候选点是 P d ( x i + 1 , y i ) P_d(x_i+1,y_i) Pd(xi+1,yi)和 P u ( x i + 1 , y i + 1 ) P_u(x_i+1,y_i+1) Pu(xi+1,yi+1);而在抛物线的后半部分时,下一候选点是 P l ( x i , y i + 1 ) P_l(x_i, y_i+1) Pl(xi,yi+1)和 P r ( x i + 1 , y i + 1 ) P_r(x_i+1,y_i+1) Pr(xi+1,yi+1) 。如何选用候选点仍然使用中点进行判别。
2.1 推导前半部分的抛物线绘制公式
对于
0
<
x
<
1
2
a
0<x<frac{1}{2a}
0<x<2a1,构造判别式
d
l
i
=
F
(
x
i
+
1
,
y
i
+
0.5
)
=
y
i
+
0.5
−
a
(
x
i
+
1
)
2
{{d}_{li}}=F({{x}_{i}}+1,{{y}_{i}}+0.5)={{y}_{i}}+0.5-a{{({{x}_{i}}+1)}^{2}}
dli=F(xi+1,yi+0.5)=yi+0.5−a(xi+1)2
若
d
l
i
≥
0
{{d}_{li}}ge 0
dli≥0,中点在抛物线上方,应取正右方候选点
P
d
(
x
i
+
1
,
y
i
)
{{P}_{d}}({{x}_{i}}+1,{{y}_{i}})
Pd(xi+1,yi);反之,中点在抛物线下方,应取右上方候选点
P
u
(
x
i
+
1
,
y
i
+
1
)
{{P}_{u}}({{x}_{i}}+1,{{y}_{i}}+1)
Pu(xi+1,yi+1)。
前半部分误差项的递推。
在
d
l
i
≥
0
{{d}_{li}}ge 0
dli≥0时,应计算:
d
l
(
i
+
1
)
=
F
(
x
i
+
2
,
y
i
+
0.5
)
=
y
i
+
0.5
−
a
(
x
i
+
2
)
2
=
d
l
i
+
a
[
(
x
i
+
1
)
2
−
(
x
i
+
2
)
2
]
=
d
l
i
−
2
a
x
i
−
3
a
begin{aligned} {{d}_{l(i+1)}}& = F({{x}_{i}}+2,{{y}_{i}}+0.5) \ & = {{y}_{i}}+0.5-a{{({{x}_{i}}+2)}^{2}} & \ & = {{d}_{li}}+a[{{({{x}_{i}}+1)}^{2}}-{{({{x}_{i}}+2)}^{2}}] \ & = {{d}_{li}}-2a{{x}_{i}}-3a end{aligned}
dl(i+1)=F(xi+2,yi+0.5)=yi+0.5−a(xi+2)2=dli+a[(xi+1)2−(xi+2)2]=dli−2axi−3a
在
d
l
i
<
0
{{d}_{li}}<0
dli<0时,应计算:
d
l
(
i
+
1
)
=
F
(
x
i
+
2
,
y
i
+
1.5
)
=
y
i
+
1.5
−
a
(
x
i
+
2
)
2
=
d
l
i
−
2
a
x
i
−
3
a
+
1
begin{aligned} {{d}_{l(i+1)}}&= F({{x}_{i}}+2,{{y}_{i}}+1.5) \ & = {{y}_{i}}+1.5-a{{({{x}_{i}}+2)}^{2}} \ & = {{d}_{li}}-2a{{x}_{i}}-3a+1 end{aligned}
dl(i+1)=F(xi+2,yi+1.5)=yi+1.5−a(xi+2)2=dli−2axi−3a+1
计算判别式的初始值。弧起点为
(
0
,
0
)
(0,0)
(0,0),因此第一个中点为
(
1
,
0.5
)
(1,0.5)
(1,0.5),对应的判别式为:
d
l
0
=
y
0
+
0.5
−
a
(
x
0
+
1
)
2
=
0.5
−
a
{{d}_{l0}}={{y}_{0}}+0.5-a{{({{x}_{0}}+1)}^{2}}=0.5-a
dl0=y0+0.5−a(x0+1)2=0.5−a
2.2 推导后半部分的抛物线绘制公式
对于
1
2
a
≤
x
<
x
_
b
o
r
d
e
r
frac{1}{2a}le x<x_border
2a1≤x<x_border,构造判别式
d
r
i
=
F
(
x
i
+
0.5
,
y
i
+
1
)
=
y
i
+
1
−
a
(
x
i
+
0.5
)
2
{{d}_{ri}}=F({{x}_{i}}+0.5,{{y}_{i}}+1)={{y}_{i}}+1-a{{({{x}_{i}}+0.5)}^{2}}
dri=F(xi+0.5,yi+1)=yi+1−a(xi+0.5)2
若
d
r
i
≤
0
{{d}_{ri}}le 0
dri≤0,中点在抛物线(左)上方,应取正上方候选点
P
l
(
x
i
,
y
i
+
1
)
{{P}_{l}}({{x}_{i}},{{y}_{i}}+1)
Pl(xi,yi+1);反之,中点在抛物线(右)下方,应取右上方候选点
P
r
(
x
i
+
1
,
y
i
+
1
)
{{P}_{r}}({{x}_{i}}+1,{{y}_{i}}+1)
Pr(xi+1,yi+1)。
后半部分误差项的递推。
在
d
r
i
≤
0
{{d}_{ri}}le 0
dri≤0时,应计算:
d
r
(
i
+
1
)
=
F
(
x
i
+
0.5
,
y
i
+
2
)
=
y
i
+
2
−
a
(
x
i
+
0.5
)
2
=
d
r
i
+
1
begin{aligned} {{d}_{r(i+1)}}&= F({{x}_{i}}+0.5,{{y}_{i}}+2) \ & = {{y}_{i}}+2-a{{({{x}_{i}}+0.5)}^{2}} \ & = {{d}_{ri}}+1 end{aligned}
dr(i+1)=F(xi+0.5,yi+2)=yi+2−a(xi+0.5)2=dri+1
在
d
r
i
>
0
{{d}_{ri}}>0
dri>0时,应计算:
d
r
(
i
+
1
)
=
F
(
x
i
+
1.5
,
y
i
+
2
)
=
y
i
+
2
−
a
(
x
i
+
1.5
)
2
=
d
r
i
+
1
+
a
[
(
x
i
+
0.5
)
2
−
(
x
i
+
1.5
)
2
]
=
d
r
i
−
2
a
x
i
−
2
a
+
1
begin{aligned} {{d}_{r(i+1)}}&= F({{x}_{i}}+1.5,{{y}_{i}}+2) \ & = {{y}_{i}}+2-a{{({{x}_{i}}+1.5)}^{2}} \ & = {{d}_{ri}}+1+a[{{({{x}_{i}}+0.5)}^{2}}-{{({{x}_{i}}+1.5)}^{2}}] \ & = {{d}_{ri}}-2a{{x}_{i}}-2a+1 end{aligned}
dr(i+1)=F(xi+1.5,yi+2)=yi+2−a(xi+1.5)2=dri+1+a[(xi+0.5)2−(xi+1.5)2]=dri−2axi−2a+1
计算判别式的初始值。弧起点的横坐标为
⌈
1
2
a
⌉
leftlceil frac{1}{2a} rightrceil
⌈2a1⌉,对应的判别式为:
d
r
0
=
y
0
+
1
−
a
(
x
0
+
0.5
)
2
=
y
0
+
1
−
a
x
0
2
−
a
x
0
−
0.25
a
=
1
−
a
x
0
−
0.25
a
=
1
−
a
⌈
1
2
a
⌉
−
0.25
a
begin{aligned} {{d}_{r0}}&= {{y}_{0}}+1-a{{({{x}_{0}}+0.5)}^{2}} \ & = {{y}_{0}}+1-ax_{0}^{2}-a{{x}_{0}}-0.25a \ & = 1-a{{x}_{0}}-0.25a \ & = 1-aleftlceil frac{1}{2a} rightrceil -0.25a end{aligned}
dr0=y0+1−a(x0+0.5)2=y0+1−ax02−ax0−0.25a=1−ax0−0.25a=1−a⌈2a1⌉−0.25a
3 算法的伪代码
中点Bresenham算法
绘制抛物线函数(
a
a
a值,边界值
x
_
b
o
r
d
e
r
x_border
x_border):
计算分界点div=0.5/a;
初始化:
前半部分判别式初始值: d_pre = 0.5 - a;
后半部分判别式初始值: d_post = 1 - a * ceil(div) - 0.25 * a
x=x0, y=y0;
当x<x_border循环执行:
绘制点(x, y)和(-x, y)
若x<div:
计算增量tmp = -2 * a * x - 3 * a;
x ++;
如果d_pre < 0:
y ++;
d_pre += tmp + 1;
否则:
d_pre += tmp;
否则:
计算增量tmp = -2 * a * x - 2 * a + 1;
y ++;
如果d_post >= 0:
x ++;
d_post += tmp;
否则:
d_post += 1;
4 一些注意事项
- 为了使运行结果更利于观察,可以将画布网格化,产生离散的可选点集。设
grid_size
为网格的间距,将可选点的横纵坐标限制为grid_size
的整数倍。 - 注意在算法中计算增量
tmp
的步骤一定要在x++
与y++
之前。因为tmp
的计算是基于上一步骤中的点的。 - 此算法涉及到了很多浮点数运算与取整运算,可能导致算法效率不高。在实际应用时,可以用 替换 ,以消除初始化时的复杂运算。
5 运行结果示例
以下是基于C++
和OpenGL
编写的,使用参数值为grid_size=0.01
,a=0.01
,x_border=100
的实例的运行结果。
附录:源代码
#define GLUT_DISABLE_ATEXIT_HACK
#include <windows.h>
#include <GL/glut.h>
#include <cmath>
using namespace std;
const float grid_size = 0.01f;
void paracurve_midpoint_bresenham(float a, int x_border) {
float div = 0.5 / a;
int x = 0, y = 0;
float d_pre = 0.5 - a;
float d_post = 1 - a * ceil(div) - 0.25 * a;
glPointSize(3.0f);
glBegin(GL_POINTS);
while (x <= x_border) {
glVertex2f(x * grid_size, y * grid_size);
glVertex2f(-x * grid_size, y * grid_size);
if (x < div) {
float tmp = -2 * a * x - 3 * a;
x ++;
if (d_pre < 0) {
y ++;
d_pre += tmp + 1;
} else {
d_pre += tmp;
}
} else {
float tmp = -2 * a * x - 2 * a + 1;
y ++;
if (d_post >= 0) {
x ++;
d_post += tmp;
} else {
d_post += 1;
}
}
}
glEnd();
}
void reDisplay() {
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(0.0f, 1.0f, 0.0f);
paracurve_midpoint_bresenham(0.01, 100);
glFlush();
}
int main(int argc, char**argv) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(500, 500);
glutInitWindowPosition(100, 100);
glutCreateWindow("Basic Graphic Generation Algorithm");
glutDisplayFunc(&reDisplay);
glutMainLoop();
}
最后
以上就是拼搏魔镜最近收集整理的关于抛物线的中点Bresenham算法的全部内容,更多相关抛物线内容请搜索靠谱客的其他文章。
发表评论 取消回复