扫面线算法 javascript版

4 篇文章 3 订阅
2 篇文章 0 订阅

前言

关于随机多边形填色,无意看到了扫描线算法,虽然目前还不能完全解决我遇到的问题。但是已经有了方向,顺便用js实现了一个demo。

效果图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
效果还算满意,基本达到了预期的效果。

一、需求

所谓代码未写,需求先行。有了需求边界,才好下手。
需求描述:对任意边数的多边形进行填充。(不限制凸面体与凹面体)

二、分析

  • 任意数量不重复的矢量无序队列(矢量 = 点)。
  • 临近序列的两点组合成线段,首尾序列点闭合成线段。
  • 所有线段组成我们需要填充的多边形。
  • 维护一条平行于x轴的扫面线。(扫描线可以是任意直线)
  • 扫面线扫过多边形,与多边形相交点,从左向右进行填充。
  • 当扫描完整个多变形后,填充完毕。

三、渲染

我采用html + canvas对数据进行渲染。这里我的思路是,不在逻辑代码中填充多边形。而等整体的渲染数据完成后。一并返回,交由渲染层统一渲染。

因为canvas本来就有点->线->多边形的填充api。所以我这里显得有点像脱了裤子放屁。

四、逻辑代码

1、矢量无序队列

var pointers = [[50, 50], [170, 100], [290, 100], [250, 10], [10, 290], [150, 250], [33, 88]];

2、临近点生成线段对象
生成线段对象并确定线段的方向,将包含4个线段信息,斜率、起点x坐标、起点y坐标、线段结束点y坐标。(这里的值并不固定,可以根据自己的喜好增删)
简单说下我这里4个值的用处。

  • 斜率:用来求与扫描线焦点。
  • 起点y:激活线段。
  • 起点x:排序、与扫描线求焦点。
  • 结束点y:判断扫描线是否扫描完该线段。

这里值得注意,把斜率为0的线段过滤掉。(不要问我为什么,也不要问我怎么做)

  var getLineInfo = function (v1, v2) {
    var [x1, y1] = v1;
    var [x2, y2] = v2;
    var slope = (y2 - y1) / (x2 - x1);

    if (y1 > y2) return {
      slope,
      sX: x2,
      sY: y2,
      eY: y1,
    };
    else if(y2 > y1) return {
      slope,
      sX: x1,
      sY: y1,
      eY: y2,
    };
  };

3、构建多边形
将我们得到的线段添加到一个表中就得到了我们逻辑上需要的多边形。(好吧,你也可以叫它别的什么,就将就着当它是多边形吧 -_-||||)

  var lineMap = new Map();
  var createLineMap = function () {
    var len = pointers.length;
    
    for (var i = 0; i < len; i ++) {
      var v1 = pointers[i];
      var v2 = pointers[i + 1 >= len ? 0 : i + 1];
      var tempLine = getLineInfo(v1, v2);

      if (tempLine) {
        var tempData = lineMap.get(tempLine.sY) || [];

        lineMap.set(tempLine.sY, [
          ...tempData,
          tempLine,
        ]);
      }
    }
  }

4、维护一条扫描线
我这里采用的是平行于x轴的扫描线,从上往下扫瞄。(顺带一句,扫描线是可以任意直线的,三维中应该就是一个平面了)

  // 扫描线
  var sweepLineNum = 0;
  
  // 递归扫描,扫描整个场景
  var sweepHandle = function () {
    if (sweepLineNum > screenHeight) return;
	// 其它逻辑
  }

5、扫描至多边形,产生焦点,配对焦点形成渲染数据
这里算是核心逻辑,算是难点把。OK来看一张图,自己不想画随便找的一张。
在这里插入图片描述
这张图是从下往上扫描,和我的实现相反。(将就着用用)

当扫描线到y = 1的时候。会激活p2p1、p2p3两条线段。那魔我就将我多边形中的两条线段取出,加入一个新的升序队列中。这个升序队列,由下往上、由左到右进行排序。

  // 升序队列
  var lineQueue = [];
  // 取出被激活的线段
  var lineDatas = lineMap.get(sweepLineNum);
  
  // 将新取出的线段插入到升序队列中
  insertLineQueue(lineDatas);

当扫面线到y=2的时候与p1与E相交,这时候根据斜率、线段开始坐标、扫描线位置求出相交点。

  var getFocus = function (line) {
    const { slope, sX, sY } = line;

	// 与扫描线垂直的线段
    if (slope === Infinity) return [sX, sweepLineNum];
    // 普通线段
    return [sX + (sweepLineNum - sY) / slope, sweepLineNum];
  }

当焦点成对的时候(两个焦点决定一条直线),就可以确认一条渲染数据了。

  var len = lineQueue.length;
  var line = [];
  // 渲染数据
  var sweeplineRenderDatas = [];

  for (var i = 0; i < len; i ++) {
      var v = getFocus(lineQueue[i])
      // console.log(v);
      line.push(v);

      if ((i & 1) === 1 && line.length > 0) {
        sweeplineRenderDatas.push(line);
        line = [];
      }
    }

同样y=2的时候,激活p1p6同时p2p1线段已经扫描完成,从升序队列中删除。(oh,good,到了这一步基本就是递归之前的步骤了)

  var deleteLineQueueMember = function () {
    lineQueue = lineQueue.filter(v => {
      return v.eY >= sweepLineNum;
    })
  }

6、完成
当扫面线超出场景就完成扫面抛出渲染数据了。(当然,也可以判断,线段升序队列为空)

  // 扫描递归
  var sweepHandle = function () {
    if (sweepLineNum > screenHeight) return;
	// 其它逻辑
  }

  // 最终sweeplineRenderDatas 就是我们得到的渲染数据,返回给渲染层渲染

以上完整代码包https://download.csdn.net/download/lijia111927/12838484

后记

前言中我提到在关注扫描线算法遇到的相关没有解决的难题。
关于任意图像填充的问题。https://blog.csdn.net/lijia111927/article/details/103555838

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值