基于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的代码放出来,读者只需要参照上面的思路稍微做一些修改即可,非常简单。除此之外,上面是生成纵线扫描线的方法,生成横线扫描线的方法也类似,举一反三即可

效果测试

目录
相关文章
|
2月前
|
负载均衡 算法 关系型数据库
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
本文聚焦 MySQL 集群架构中的负载均衡算法,阐述其重要性。详细介绍轮询、加权轮询、最少连接、加权最少连接、随机、源地址哈希等常用算法,分析各自优缺点及适用场景。并提供 Java 语言代码实现示例,助力直观理解。文章结构清晰,语言通俗易懂,对理解和应用负载均衡算法具有实用价值和参考价值。
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
|
18天前
|
消息中间件 存储 缓存
zk基础—1.一致性原理和算法
本文详细介绍了分布式系统的特点、理论及一致性算法。首先分析了分布式系统的五大特点:分布性、对等性、并发性、缺乏全局时钟和故障随时发生。接着探讨了分布式系统理论,包括CAP理论(一致性、可用性、分区容错性)和BASE理论(基本可用、软状态、最终一致性)。文中还深入讲解了两阶段提交(2PC)与三阶段提交(3PC)协议,以及Paxos算法的推导过程和核心思想,强调了其在ZooKeeper中的应用。最后简述了ZAB算法,指出其通过改编的两阶段提交协议确保节点间数据一致性,并在Leader故障时快速恢复服务。这些内容为理解分布式系统的设计与实现提供了全面的基础。
|
2月前
|
存储 缓存 监控
上网行为监控系统剖析:基于 Java LinkedHashMap 算法的时间序列追踪机制探究
数字化办公蓬勃发展的背景下,上网行为监控系统已成为企业维护信息安全、提升工作效能的关键手段。该系统需实时记录并深入分析员工的网络访问行为,如何高效存储和管理这些处于动态变化中的数据,便成为亟待解决的核心问题。Java 语言中的LinkedHashMap数据结构,凭借其独有的有序性特征以及可灵活配置的淘汰策略,为上网行为监控系统提供了一种兼顾性能与功能需求的数据管理方案。本文将对LinkedHashMap在上网行为监控系统中的应用原理、实现路径及其应用价值展开深入探究。
61 3
|
2月前
|
人工智能 算法 NoSQL
LRU算法的Java实现
LRU(Least Recently Used)算法用于淘汰最近最少使用的数据,常应用于内存管理策略中。在Redis中,通过`maxmemory-policy`配置实现不同淘汰策略,如`allkeys-lru`和`volatile-lru`等,采用采样方式近似LRU以优化性能。Java中可通过`LinkedHashMap`轻松实现LRUCache,利用其`accessOrder`特性和`removeEldestEntry`方法完成缓存淘汰逻辑,代码简洁高效。
|
2月前
|
存储 缓存 Java
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。
88 0
|
2月前
|
存储 安全 Java
深入探究Java中ThreadLocal的工作原理和用途
总结起来,ThreadLocal是Java多线程编程中一个非常有用的工具,通过为每个线程分配独立的变量副本,实现线程隔离,避免资
67 9
|
2月前
|
NoSQL 算法 安全
分布式锁—1.原理算法和使用建议
本文主要探讨了Redis分布式锁的八大问题,包括非原子操作、忘记释放锁、释放其他线程的锁、加锁失败处理、锁重入问题、锁竞争问题、锁超时失效及主从复制问题,并提供了相应的优化措施。接着分析了Redis的RedLock算法,讨论其优缺点以及分布式专家Martin对其的质疑。此外,文章对比了基于Redis和Zookeeper(zk)的分布式锁实现原理,包括获取与释放锁的具体流程。最后总结了两种分布式锁的适用场景及使用建议,指出Redis分布式锁虽有性能优势但模型不够健壮,而zk分布式锁更稳定但部署成本较高。实际应用中需根据业务需求权衡选择。
|
2月前
|
人工智能 JavaScript Java
Java反射机制及原理
本文介绍了Java反射机制的基本概念、使用方法及其原理。反射在实际项目中比代理更常用,掌握它可以提升编程能力并理解框架设计原理。文章详细讲解了获取Class对象的四种方式:对象.getClass()、类.class、Class.forName()和类加载器.loadClass(),并分析了Class.forName()与ClassLoader的区别。此外,还探讨了通过Class对象进行实例化、获取方法和字段等操作的具体实现。最后从JVM类加载机制角度解析了Class对象的本质及其与类和实例的关系,帮助读者深入理解Java反射的工作原理。
|
3月前
|
存储 机器学习/深度学习 监控
如何监控员工的电脑——基于滑动时间窗口的Java事件聚合算法实现探析​
在企业管理场景中,如何监控员工的电脑操作行为是一个涉及效率与合规性的重要课题。传统方法依赖日志采集或屏幕截图,但数据量庞大且实时性不足。本文提出一种基于滑动时间窗口的事件聚合算法,通过Java语言实现高效、低资源占用的监控逻辑,为如何监控员工的电脑提供一种轻量化解决方案。
72 3
|
2月前
|
存储 安全 Java
【高薪程序员必看】万字长文拆解Java并发编程!(4-1):悲观锁底层原理与性能优化实战
目录4. JVM字节码文件4.1. 字节码文件-组成4.1.1. 组成-基础信息4.1.1.1. 基础信息-魔数4.1.1.2. 基础信息-主副版本号4.1.2. 组成-常量池4.1.3. 组成-方法4.1.3.1. 方法-工作流程4.1.4. 组成-字段4.1.5. 组成-属性4.2. 字节码文件-查看工具4.2.1. javap4.2.2. jclasslib4.2.3. 阿里Arthas
45 0

热门文章

最新文章