抛物线的中点Bresenham算法

1 抛物线的特征

通常定义抛物线为到一条直线(准线)和直线外一点(焦点)距离相等的点的集合。这里只讨论顶点为原点,沿纵坐标轴对称且开口向上的情况。而对于其他情况可以通过图形的平移和旋转等线性变换得到。其描述方程如下:
F ( x , y ) = y − a x 2 ( a > 0 ) F(x,y)=y-ax^2(a>0) F(x,y)=yax2(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)=xF(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 2a1x<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.5a(xi+1)2

d l i ≥ 0 {{d}_{li}}\ge 0 dli0,中点在抛物线上方,应取正右方候选点 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 dli0时,应计算:
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.5a(xi+2)2=dli+a[(xi+1)2(xi+2)2]=dli2axi3a
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.5a(xi+2)2=dli2axi3a+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.5a(x0+1)2=0.5a

2.2 推导后半部分的抛物线绘制公式

对于 1 2 a ≤ x < x _ b o r d e r \frac{1}{2a}\le x<x\_border 2a1x<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+1a(xi+0.5)2
d r i ≤ 0 {{d}_{ri}}\le 0 dri0,中点在抛物线(左)上方,应取正上方候选点 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 dri0时,应计算:
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+2a(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+2a(xi+1.5)2=dri+1+a[(xi+0.5)2(xi+1.5)2]=dri2axi2a+1
计算判别式的初始值。弧起点的横坐标为 ⌈ 1 2 a ⌉ \left\lceil \frac{1}{2a} \right\rceil 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-a\left\lceil \frac{1}{2a} \right\rceil -0.25a \end{aligned} dr0=y0+1a(x0+0.5)2=y0+1ax02ax00.25a=1ax00.25a=1a2a10.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 一些注意事项

  1. 为了使运行结果更利于观察,可以将画布网格化,产生离散的可选点集。设grid_size为网格的间距,将可选点的横纵坐标限制为grid_size的整数倍。
  2. 注意在算法中计算增量tmp的步骤一定要在x++y++之前。因为tmp的计算是基于上一步骤中的点的。
  3. 此算法涉及到了很多浮点数运算与取整运算,可能导致算法效率不高。在实际应用时,可以用 替换 ,以消除初始化时的复杂运算。

5 运行结果示例

以下是基于C++OpenGL编写的,使用参数值为grid_size=0.01a=0.01x_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();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值