扫描线填充算法(DDA应用)

欢迎关注更多精彩
关注我,学习常用算法与数据结构,一题多解,降维打击。

本期话题:给定多边形的边如何填充多边形内部区域

朴素做法

  • 枚举法
    枚举屏幕上的每一点,判断点是否在多边形内。点在多边形内判断方法
    运算量太大。
  • 扫描线求交法
    从小到大枚举平等于x轴的直线, 直线与多边形的边求交,所有交点x轴从小到大排序,每两点之间画线 (根据射线法可知)。
    请添加图片描述
    用一条线(平行于x轴)与多边形求交,会得到偶数个交点,每两点之间作线段。
    复杂度 (ymax-ymin)*n, n为边数量。

基于有序活动边表优化

扫描线求交法的缺点是每次都要与所有边求交。但实际上,从y 到 y+1求交时,可能求交的还是原来的线段,或者改变的边就几条。

从上面图可以看出,y+1时相交的线有可能不变化(a), 当y与一条线交点是线段的最高点时,就会有变化(b)。

基于这个思想,我们只要维护一个动态边表,当前与哪些线段,每次y增加,对交点进行变化,并检查是否有边到达最高点进行替换。y+1, x的变化可以参考DDA画线算法。

数据结构设计

// 直线定义
class Line {
public:
    Point low; // 低点 y较小的点
    Point high; // 高点 y较大的点
};

// 动态边表数据结构
class ActiveEdge {
public:
    double x; // 与该边交点的x,每次+k
    int maxY; // 边最高点y轴坐标
    int deltaX, deltaY; // 最左最右距离,最高最低距离。
    double k ; // 线段斜率, ∆x/∆y
};

// 活动边表有序,先比较x, 相同情况下比较斜率
vector<ActiveEdge> activeEdgeList;

算法过程

step 1 对所有线段按照low.y 从低到高排序
step 2

	for y 从ymin开始遍历到ymax {
		1. 对activeEdgeList所有边的x+=k
		2. 对activeEdgeList每两个交点作线段
		3. 查看activeEdgeList边中有没有ymax<=y的边,如果有就删除
		4. 查看线段集合中有没有ymin=y的边,如果有就加入到activeEdgeList(平行于x轴的边不考虑),并排序
	}

请添加图片描述
上图中蓝色边是中活动边表中的边。绿色线为已经填充区域。每次到达一条线的最高点,时会有新的边更新。

算法实现

代码仓库

#include "glew/2.2.0_1/include/GL/glew.h"
#include "glfw/3.3.4/include/GLFW/glfw3.h"
#include <iostream>
#include <vector>
using namespace std;

double esp = 1e-6;

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    //如果按下ESC,把windowShouldClose设置为True,外面的循环会关闭应用
    if(key==GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
    std::cout<<"ESC"<<mode;
}

int cmpDouble(double a, double b) {
    if (fabs(a-b)<esp)return 0;
    if (a<b)return -1;
    return 1;
}

class ActiveEdge {
public:
    double x; // 每次+k
    int maxY;
    int deltaX, deltaY;
    double k ; // ∆x/∆y
};

int curY;

bool cmpActiveEdge(ActiveEdge a, ActiveEdge b) {
    if(curY>=a.maxY)return false;
    if(curY>=b.maxY)return true;
    int eq = cmpDouble(a.x, b.x);
    if(eq) return eq<0; // x小的排前面
    eq = cmpDouble(a.k, b.k); // 斜率小说明x在减小得快
    return eq<0;
}

class Point {
public:
    int x, y;
    Point(int xx, int yy):x(xx), y(yy){}
    Point(){}
};

class Line {
public:
    Point low, high;
    Line(){}
    Line(Point tp1, Point tp2):low(tp1),high(tp2){
        if (tp2.y<tp1.y){
            low=tp2;
            high=tp1;
        }
    }

    void Out(){
        printf("%d,%d-%d,%d\n", low.x, low.y, high.x, high.y);
    }
};

bool cmpLine(Line a, Line b) {
    return a.low.y<b.low.y;
}

void setPixel(Point p) {
    // cout<<p.x<<","<<p.y<<endl;
    glPointSize(1);
    glBegin(GL_POINTS);
    glVertex2i(p.x, p.y);
    glEnd();
    glFlush();
}

void drawHorizon(int start, int end, int y) {
    for(;start<=end;start++){
        setPixel(Point(start, y));
    }
}

/*
 * 扫描线从低到高扫描
 * 先更新活动边表x值,每一对点画一条线段。
 * 查看当前y值是否有新线段加入,有则入加,并排序。
 */
void fillPolygen(vector<Line> polygen) {
    //1. 对线段按照底端从低到高排序
    sort(polygen.begin(), polygen.end(), cmpLine);

    // 2. 查找最低点与最高点
    int minY = polygen[0].low.y;
    int maxY = polygen[0].high.y;
    for (Line l : polygen){
        maxY = max(maxY, l.high.y);
    }

    // 定义活动边表
    vector<ActiveEdge> ae;
    for(int h=minY, j=0;h<=maxY;h++) {
        bool needSort=false;
        // 更新提有活动边x值。
        for(int i=0;i<ae.size();i++) {
            if (h>ae[i].maxY)continue;
            if (ae[i].deltaX) ae[i].x += ae[i].k;
            // 每两点画一条线
            if (i&1 && h<=ae[i].maxY) {
                drawHorizon(ae[i-1].x, ae[i].x, h);
            }
            if (h>=ae[i].maxY && !needSort) needSort=true; // 有边需要删除,删除采用假删除,将无用边排到最后边去
        }

        // 查看当前是否有边加入
        for(;j<polygen.size() && polygen[j].low.y==h;j++){
            // 平行边不需要
            if (polygen[j].low.y== polygen[j].high.y)continue;
            if (!needSort) needSort=true; // 有新边加入,需要排序
            ActiveEdge newEdge;
            newEdge.maxY = polygen[j].high.y;
            newEdge.x = polygen[j].low.x;
            newEdge.deltaX = polygen[j].high.x-polygen[j].low.x;
            newEdge.deltaY = polygen[j].high.y-polygen[j].low.y;
            newEdge.k = double(newEdge.deltaX) / double(newEdge.deltaY); // ∆x/∆y
            ae.push_back(newEdge);
        }

        if (needSort) {
            curY=h;
            sort(ae.begin(), ae.end(), cmpActiveEdge);
        }
    }
}


vector<Line> getTriangle() {
    vector<int> pList = {
            -100,-200,200,50,
            -100,-200,0,100,
            0,100,200,50,
    };

    vector<Line> sq;
    for(int i=0;i+3<pList.size();i+=4){
        sq.push_back(Line(Point(pList[i]-350,pList[i+1]-250),Point(pList[i+2]-350,pList[i+3]-250)));
    }

    return sq;
}

vector<Line> get5Triangle() {
    vector<int> pList = {
            -100,-200,150,0,
            -100,-200,0,100,
            -150,0,150,0,
            100,-200,-150,0,
            100,-200,0,100,
    };

    vector<Line> sq;
    //glBegin(GL_LINES);
    for(int i=0;i+3<pList.size();i+=4){
        sq.push_back(Line(Point(pList[i]-350,pList[i+1]+50),Point(pList[i+2]-350,pList[i+3]+50)));
        //glVertex2i(pList[i],pList[i+1]);
        //glVertex2i(pList[i+2],pList[i+3]);
    }
    //glEnd();

    return sq;
}


vector<Line> getUnNor() {
    vector<int> pList = {
            -10,10,100,-50,
            120,51,150,-58,
            170,100,200,-50,
            300,150,250,120,300,170,270,200,
            210,180,200,250,
            150,210,
            110,230,
    };

    vector<Line> sq;
    // glBegin(GL_LINES);
    for(int i=0;i+3<pList.size();i+=2){
        sq.push_back(Line(Point(pList[i],pList[i+1]),Point(pList[i+2],pList[i+3])));
        //glVertex2i(pList[i],pList[i+1]);
        //glVertex2i(pList[i+2],pList[i+3]);
    }
    int n = pList.size();
    sq.push_back(Line(Point(pList[n-2],pList[n-1]),Point(pList[0],pList[1])));
    //glVertex2i(pList[n-2],pList[n-1]);
    //glVertex2i(pList[0],pList[1]);
    //glEnd();

    return sq;
}

vector<Line> getSquare() {
    vector<int> pList = {
            0,0,100,0,
            0,0,0,100,
            100,0,100,100,
            0,100,100,100,
    };

    vector<Line> sq;
    for(int i=0;i+3<pList.size();i+=4){
        sq.push_back(Line(Point(pList[i]-350,pList[i+1]+250),Point(pList[i+2]-350,pList[i+3]+250)));
    }

    return sq;
}

int main(void) {

    //初始化GLFW库
    if (!glfwInit())
        return -1;
    //创建窗口以及上下文
    GLFWwindow *window = glfwCreateWindow(1024, 900, "hello world", NULL, NULL);
    if (!window) {
        //创建失败会返回NULL
        glfwTerminate();
    }

    //建立当前窗口的上下文
    glfwMakeContextCurrent(window);

    glfwSetKeyCallback(window, key_callback); //注册回调函数
    //glViewport(0, 0, 400, 400);
    gluOrtho2D(-512, 512, -450, 450);
    //循环,直到用户关闭窗口
    cout<<123<<endl;
    while (!glfwWindowShouldClose(window)) {
        /*******轮询事件*******/
        glfwPollEvents();
        //选择清空的颜色RGBA
        glClearColor(0, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT);
        glMatrixMode(GL_PROJECTION);


        vector<Line> polygen = getSquare();
        glColor3f(1,0, 0);
       fillPolygen(polygen);

        polygen = getTriangle();
        glColor3f(1,1, 0);
       fillPolygen(polygen);


        glColor3f(1,0, 1);
        polygen = get5Triangle();
       fillPolygen(polygen);

        polygen = getUnNor();
        fillPolygen(polygen);


        /******交换缓冲区,更新window上的内容******/
        glfwSwapBuffers(window);
        //break;
    }
    glfwTerminate();
    return 0;
}


算法效果


该算法适用于规则图形,也适用于复杂的凸多边形与凹多边形。


本人码农,希望通过自己的分享,让大家更容易学懂计算机知识。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值