处理多折线

处理多折线

最近实现了一个比较有趣的小功能,其中有两个需要注意的小细节。

功能概述

多折线每一段都与相邻段正交,可以通过拖拽任一端点调整其所在段折线的长度/位置到目标位置。拖拽过程中始终保持每一段都与相邻段正交。

为了叙述方便,这里定义折线的数据部分形式为由每个端点的坐标组成的列表,如一条由六个点组成的多折线 L 的点列表示例:

scss 复制代码
P0 (0, 0)
P1 (100, 0)
P2 (100, 100)
P3 (150, 100)
P4 (150, 0)
P5 (200, 0)

上述折线用代码表示如下:

C++ 复制代码
QList<QPoint> vertexList = {
    QPoint(0, 0),
    QPoint(100, 0),
    QPoint(100, 100),
    QPoint(150, 100),
    QPoint(150, 0),
    QPoint(200, 0)
    }

细节一

功能需求

在调整折线时,若将点 P1 (100, 0) 拖拽到 (150, -50) 处, P1' , P2' , P3 以及 P4 将位于一条直线 (x = 150) 上,若此时完成拖拽操作,新的折线 L' 应变为:

scss 复制代码
P0' (50, -50)
P1' (150, -50)
P4' (150, 0)
P5 (200, 0)

即当同一段线段上若存在 ≥ 3 个端点,则应仅保留这一段中端点索引最小和最大的两个端点

代码实现

用最简单粗暴的逻辑来实现这个方法,很容易想到的就是遍历:

其中 adjustedIndex 为当前拖拽的端点索引

C++ 复制代码
void removeRedundantVertex() {
    // 记录 x 方向,即水平方向,共线点中的最小/最大索引
    int minXIndex = adjustedIndex;
    int maxXIndex = adjustedIndex;
    // 记录 y 方向,即竖直方向,共线点中的最小/最大索引
    int minYIndex = adjustedIndex;
    int maxYIndex = adjustedIndex;

    // 共线基准点,即当前调整点的最新位置
    QPoint pivot = newAdjustedPos.toPoint();
    // X/Y 方向上的共线点计数,基准点与自身共线,因此初始值为 1
    int XCollinearCount = 1;
    int YCollinearCount = 1;

    for(int i = 0; i < vertexList.size(); i++)
    {
        if(i == adjustedIndex)
        {
            continue;
        }
        QPoint curr = vertexList.at(i);
        if(curr.x() == pivot.x())
        {
            minYIndex = i < minYIndex ? i : minYIndex;
            maxYIndex = i > maxYIndex ? i : maxYIndex;
            XCollinearCount += 1;
        }
        if(curr.y() == pivot.y())
        {
            minXIndex = i < minXIndex ? i : minXIndex;
            maxXIndex = i > maxXIndex ? i : maxXIndex;
            YCollinearCount += 1;
        }
    }
    // 若存在多点共线,则移除最小 index 和最大 index 之间的共线点
    if(XCollinearCount > 2)
    {
        // pseudo code
        // 移除索引范围为 [minYIndex + 1, maxYIndex) 的端点
        vertexData->removeVertexRange(minYIndex + 1, maxYIndex);
    }
    if(YCollinearCount > 2)
    {
        // pseudo code
        // 移除索引范围为 [minXIndex + 1, maxXIndex) 的端点
        vertexData->removeVertexRange(minXIndex + 1, maxXIndex);
    }
}
bug

但在实际拖拽时会发现,这个逻辑存在一定问题。若将示例的折线 L 中的端点 P1 (100, 0) 先拖拽到 (100, 1) ,此时未完成拖拽,继续拖拽回 (100, 0) 后结束拖拽,正确的结果应为折线 L 不变。但由函数 removeRedundantVertex() 逻辑得到的结果为折线 M

scss 复制代码
P0' (0, 0)
P5 (200, 0)
bug 修复

问题在于不应遍历整个 vertexList ,这样可能会 "误伤" 原本就位于同一直线上,但与调整点并不相邻的其他 "无辜" 端点。因此,只应遍历被调整端点的相邻端点。修改后的 removeRedundantVertex() 如下:

C++ 复制代码
void SchemaGraphicsWire::removeRedundantPoint() {
    int vertexCount = vertexList.size();
    // 记录 x 方向,即水平方向,共线点中的最小/最大索引
    int minXIndex = adjustedIndex;
    int maxXIndex = adjustedIndex;
    // 记录 y 方向,即竖直方向,共线点中的最小/最大索引
    int minYIndex = adjustedIndex;
    int maxYIndex = adjustedIndex;

    // 共线基准点,即当前调整点的最新位置
    QPoint pivot = newAdjustedPos.toPoint();
    // X/Y 方向上的共线点计数,基准点与自身共线,因此初始值为 1
    int XCollinearCount = 1;
    int YCollinearCount = 1;

    // 从 mAdjustedIndex 向前搜索
    int i = adjustedIndex - 1;
    // 从 mAdjustedIndex 向后搜索
    int j = adjustedIndex + 1;

    while(i >= 0 || j < vertexCount)
    {
        if(i >= 0)
        {
            QPoint curr = vertexList.at(i);
            if(curr.x() == pivot.x())
            {
                minYIndex = i < minYIndex ? i : minYIndex;
                maxYIndex = i > maxYIndex ? i : maxYIndex;
                XCollinearCount += 1;
                i--;
            }
            else if(curr.y() == pivot.y())
            {
                minXIndex = i < minXIndex ? i : minXIndex;
                maxXIndex = i > maxXIndex ? i : maxXIndex;
                YCollinearCount += 1;
                i--;
            }
            else
            {
                break;
            }
        }
        if(j < vertexCount)
        {
            QPoint curr = vertexList.at(j);
            if(curr.x() == pivot.x())
            {
                minYIndex = j < minYIndex ? j : minYIndex;
                maxYIndex = j > maxYIndex ? j : maxYIndex;
                XCollinearCount += 1;
                j++;
            }
            else if(curr.y() == pivot.y())
            {
                minXIndex = j < minXIndex ? j : minXIndex;
                maxXIndex = j > maxXIndex ? j : maxXIndex;
                YCollinearCount += 1;
                j++;
            }
            else
            {
                break;
            }
        }
    }
}

细节二

功能需求

我们仍以折线 L 为例,若将点 P1 (100, 0) 拖拽至 (-100, 0) ,拖拽过程中经过点 P0 (0, 0)

bug

此时我们会发现,当 P1' 经过 P0 (0, 0) 的一瞬间, P0 就会消失, P1 似乎永远无法越过 P0

bug 修复

这个现象非常好解决,是因为在调整过程中,修改的一直是原数组,即 vertexList 中的数据。(至于为什么一定要在操作过程中修改,是因为在 GUI 绘制中,拖拽过程中的折线的变化应实时绘制。)

我们只需要在操作前将 vertexList copy 一份即可,调整过程中修改的始终是 vertexListCopy 。 并在调整结束时,如 mouseRelease 时,再用 vertexListCopy 替换原有的 vertexList

相关推荐
mit6.8241 小时前
[Sum] C++STL oj常用API
c++·算法·leetcode
Dongliner~1 小时前
【QT:控件】
开发语言·qt
槐月初叁1 小时前
C++洛谷基础练习题及解答
开发语言·c++·算法
誓约酱1 小时前
linux 下消息队列
linux·运维·服务器·c语言·c++
Archer1941 小时前
C++基础——从C语言快速入门
数据结构·c++·算法
Hetertopia2 小时前
C++ QT零基础教学(二)
开发语言·c++·qt
一匹电信狗2 小时前
【Linux我做主】基础命令完全指南下篇
linux·运维·服务器·c++·开源·centos·unix
Maple_land2 小时前
C++初阶——类和对象(三) 构造函数、析构函数
c++
张胤尘2 小时前
C/C++ | 每日一练 (6)
c语言·c++·面试
Bruce Jue3 小时前
C++特性——智能指针
c++