基于Y向连贯性算法的多边形扫描线生成(适用于凸多边形和凹多边形)【原理+java实现】

简介: 基于Y向连贯性算法的多边形扫描线生成(适用于凸多边形和凹多边形)【原理+java实现】

问题介绍

给定一个多边形,可能是凸多边形,也可能是凹多边形,现需要生成一系列线条将多边形描述出来,示例如下图

原始方法

遇到这个问题,大家首先想到的方法可能是:使用一系列的竖线来和多边形进行相交,得到几个交点,然后将交点按照z轴坐标值进行升序排序,最后再以两个点为一组来形成扫描线。这样确实很容易理解,但是性能不好,因为需要多次求交点和多次对交点进行排序

Y向连贯性算法

该算法主要就是用来解决上面提到的两个性能问题:多次求交点及多次排序。

避免多次求交点

如何避免多次求交点呢?其实非常简单,就是利用直线函数 y=kx+b 的信息即可,例如x没增加1,y就增加 k 。如下面的例子,假如一开始就知道P点的坐标,那么线段与扫描线1、扫描线2的交点并不需要再去用直线相交公式计算,直接使用 y=kx+b 即可得到

如何避免多次排序

如下图所示,当扫描线在x=[0,10]之间移动时,永远只有上下两个交点,且P2永远在P1上面,那只要x在[0,10]之间移动时,只需要根据直线的表达式来对两个点的坐标进行更新即可,不需要排序两个点。当x>10之后,有新的边和扫描线相交,这时候会出现更多的交点,此时才需要对交点进行排序,大大减少了排序的次数

如何实现

首先需要维护一个边表,遍历多边形的每一条边,将边放到对应的桶中;第二步就是维护一个有效边集合,将y开始向右扫描移动,y的初始值是多边形所有点中最小的那个y,在移动的过程中,主要做一下三件事:

  • 是否有边失效?当扫描线扫描不到时,边就失效,将其从有效边集合中移除
  • 是否有新的有效边加入?随着扫描线的移动,当扫描线会接触到新的线时,需要将其添加到有效边集合中,这时候会产生新的交点,注意此时需要重新排序了
  • 扫描线每沿着y轴移动距离deltaY,z就变化k*deltaY

代码实现

【实体类:Edge,用于在边表和有效边集合中存储数据】

package com.dam.entity.sanLine;
/**
 * @Author dam
 * @create 2023/9/15 14:38
 */
public class Edge {
    public double z;
    public double yMax;
    /**
     * y加一时,z的增量
     */
    public double k;
    public Edge(double z, int yMax, double k) {
        this.z = z;
        this.yMax = yMax;
        this.k = k;
    }
}

【针对零件点集的纵向扫描线生成方法】

/**
 * 扫描线生成,使用连贯性算法
 *
 * @param part
 */
private void vScanLineConstruct1(Part part) {
    List<Integer> vSLineList = new ArrayList<>();
    // 边表
    HashMap<Integer, List<Edge>> edgeTable = new HashMap<>();
    /*
    边表构造
    遍历每一条边,将边的信息放入到相应的桶中,即放入边的两点中y值较小的那个桶中
     */
    for (int i = 0; i < part.offSetOuterContour.size(); i++) {
        double[] pointI = part.offSetOuterContour.get((i) % part.offSetOuterContour.size());
        double[] pointJ = part.offSetOuterContour.get((i + 1) % part.offSetOuterContour.size());
        // 两个点中较小的y
        int yMin = Math.min((int) Math.round(pointI[0]), (int) Math.round(pointJ[0]));
        int yMax = Math.max((int) Math.round(pointI[0]), (int) Math.round(pointJ[0]));
        if (yMin == yMax) {
            // 对于垂直线,不需要添加到边表中
            continue;
        }
        double z = (int) Math.round(pointI[0]) < (int) Math.round(pointJ[0]) ? pointI[1] : pointJ[1];
        Edge edge = new Edge((int) Math.round(z), yMax, MathUtil.getKOfLine(pointI[0], pointI[1], pointJ[0], pointJ[1]));
        if (!edgeTable.containsKey(yMin)) {
            List<Edge> edgeList = new ArrayList<>();
            edgeList.add(edge);
            edgeTable.put(yMin, edgeList);
        } else {
            edgeTable.get(yMin).add(edge);
        }
    }
    /*
    扫描线构造
     */
    List<Edge> activeEdgeList = new ArrayList<>();
    for (int y = 0; y < part.pixelNumInWidDirection; y++) {
        /// 判断是否有无效边需要移除
        int i = 0;
        while (i < activeEdgeList.size()) {
            Edge edge = activeEdgeList.get(i);
            if (edge.yMax == y) {
                // 当边的yMax==y,该边开始无效,移除边
                activeEdgeList.remove(i);
            } else {
                i++;
            }
        }
        /// 判断是否有新的有效边加入,如果有的话,需要重新排序
        List<Edge> edgeList = edgeTable.get(y);
        if (edgeList != null && edgeList.size() > 0) {
            // 需要将新的边添加到有效边集合中
            activeEdgeList.addAll(edgeList);
            // 因为有新边加入,需要重新排序,首先优先按照z的值来升序排序,对于z相同的,按照k升序排序
            Collections.sort(activeEdgeList, ((o1, o2) -> {
                if (o1.z > o2.z) {
                    return 1;
                } else if (o1.z < o2.z) {
                    return -1;
                } else {
                    if (o1.k > o2.k) {
                        return 1;
                    } else if (o1.k < o2.k) {
                        return -1;
                    } else {
                        return 0;
                    }
                }
            }));
        }
        /// 构造扫描线
        for (int j = 0; j < activeEdgeList.size(); j += 2) {
            vSLineList.add(y);
            vSLineList.add((int)activeEdgeList.get(j).z);
            vSLineList.add((int)Math.ceil(activeEdgeList.get(j + 1).z));
            // 进行增量计算,将z的值增加
            activeEdgeList.get(j).z += activeEdgeList.get(j).k;
            activeEdgeList.get(j + 1).z += activeEdgeList.get(j + 1).k;
        }
    }
    vLineListSort(vSLineList);
    part.vSLineList = vSLineList;
}

当然,这个扫描线生成方法你们并不能直接调用,因为我没有将实体类Part的代码放出来,读者只需要参照上面的思路稍微做一些修改即可,非常简单。除此之外,上面是生成纵线扫描线的方法,生成横线扫描线的方法也类似,举一反三即可

效果测试

目录
相关文章
|
1天前
|
缓存 算法 搜索推荐
Java中的算法优化与复杂度分析
在Java开发中,理解和优化算法的时间复杂度和空间复杂度是提升程序性能的关键。通过合理选择数据结构、避免重复计算、应用分治法等策略,可以显著提高算法效率。在实际开发中,应该根据具体需求和场景,选择合适的优化方法,从而编写出高效、可靠的代码。
15 6
|
2天前
|
监控 Java API
探索Java NIO:究竟在哪些领域能大显身手?揭秘原理、应用场景与官方示例代码
Java NIO(New IO)自Java SE 1.4引入,提供比传统IO更高效、灵活的操作,支持非阻塞IO和选择器特性,适用于高并发、高吞吐量场景。NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器(Selector),能实现多路复用和异步操作。其应用场景涵盖网络通信、文件操作、进程间通信及数据库操作等。NIO的优势在于提高并发性和性能,简化编程;但学习成本较高,且与传统IO存在不兼容性。尽管如此,NIO在构建高性能框架如Netty、Mina和Jetty中仍广泛应用。
14 3
|
2天前
|
安全 算法 Java
Java CAS原理和应用场景大揭秘:你掌握了吗?
CAS(Compare and Swap)是一种乐观锁机制,通过硬件指令实现原子操作,确保多线程环境下对共享变量的安全访问。它避免了传统互斥锁的性能开销和线程阻塞问题。CAS操作包含三个步骤:获取期望值、比较当前值与期望值是否相等、若相等则更新为新值。CAS广泛应用于高并发场景,如数据库事务、分布式锁、无锁数据结构等,但需注意ABA问题。Java中常用`java.util.concurrent.atomic`包下的类支持CAS操作。
18 2
|
1月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
1月前
|
算法 容器
令牌桶算法原理及实现,图文详解
本文介绍令牌桶算法,一种常用的限流策略,通过恒定速率放入令牌,控制高并发场景下的流量,确保系统稳定运行。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
令牌桶算法原理及实现,图文详解
|
1月前
|
Java
Java之CountDownLatch原理浅析
本文介绍了Java并发工具类`CountDownLatch`的使用方法、原理及其与`Thread.join()`的区别。`CountDownLatch`通过构造函数接收一个整数参数作为计数器,调用`countDown`方法减少计数,`await`方法会阻塞当前线程,直到计数为零。文章还详细解析了其内部机制,包括初始化、`countDown`和`await`方法的工作原理,并给出了一个游戏加载场景的示例代码。
Java之CountDownLatch原理浅析
|
21天前
|
存储 人工智能 缓存
【AI系统】布局转换原理与算法
数据布局转换技术通过优化内存中数据的排布,提升程序执行效率,特别是对于缓存性能的影响显著。本文介绍了数据在内存中的排布方式,包括内存对齐、大小端存储等概念,并详细探讨了张量数据在内存中的排布,如行优先与列优先排布,以及在深度学习中常见的NCHW与NHWC两种数据布局方式。这些布局方式的选择直接影响到程序的性能,尤其是在GPU和CPU上的表现。此外,还讨论了连续与非连续张量的概念及其对性能的影响。
43 3
|
1月前
|
Java 索引 容器
Java ArrayList扩容的原理
Java 的 `ArrayList` 是基于数组实现的动态集合。初始时,`ArrayList` 底层创建一个空数组 `elementData`,并设置 `size` 为 0。当首次添加元素时,会调用 `grow` 方法将数组扩容至默认容量 10。之后每次添加元素时,如果当前数组已满,则会再次调用 `grow` 方法进行扩容。扩容规则为:首次扩容至 10,后续扩容至原数组长度的 1.5 倍或根据实际需求扩容。例如,当需要一次性添加 100 个元素时,会直接扩容至 110 而不是 15。
Java ArrayList扩容的原理
|
26天前
|
机器学习/深度学习 人工智能 算法
探索人工智能中的强化学习:原理、算法与应用
探索人工智能中的强化学习:原理、算法与应用
|
1月前
|
缓存 算法 网络协议
OSPF的路由计算算法:原理与应用
OSPF的路由计算算法:原理与应用
47 4