CAD 的 C# 开发中,对多段线(封闭多边形)内部的点进行 “一笔连线且不交叉、不出界

          本质上是约束条件下的路径规划问题,核心是找到一条连续路径遍历所有点,同时满足: 路径不与自身交叉; 路径全程在多段线(多边形)内部; 路径连续(一笔画)。

        核心思路与算法步骤 这类问题没有 “万能算法”,但可以通过组合几何算法实现,以下是一套可行方案:

1. 基础几何工具(前置条件) 首先需要实现几个基础几何检测工具,用于后续约束验证: 点是否在多边形内:用射线法(从点向任意方向发射射线,统计与多边形边的交点数,奇数则在内部)。 线段是否与多边形边界交叉:检测线段是否超出多边形(除了端点可能在边界上)。 两线段是否交叉:判断两条线段是否存在交点(非端点重合)。

2. 核心算法:分层贪婪路径法 步骤如下:

(1)点集预处理 确保所有点都在多段线(多边形)内部(用步骤 1 的工具验证,剔除或修正外部点)。 计算多边形的 “内核”(若为凹多边形,可能存在不可见区域,需确保点在可见区域内)。 (2)构建非交叉路径的核心逻辑 采用 “凸包分层 + 极角排序” 的策略,从外到内逐层连接点,避免交叉: 提取当前点集的凸包: 凸包是包含所有点的最小凸多边形,凸包上的点按顺时针 / 逆时针排序后连接,天然无交叉。 连接凸包上的点: 按凸包顺序(如逆时针)连接凸包顶点,形成外层路径。 处理凸包内部的点: 剔除凸包上的点后,对剩余内部点重复步骤 1-2,形成内层路径,最后将内层路径与外层路径连接(选择一个连接点,确保连线不交叉且在多边形内)。 (3)边界约束验证 每生成一条线段(两点之间的连线),都需验证: 线段是否完全在多边形内部(无跨边界); 线段是否与已生成的路径交叉。

3. 算法优化(可选) 若点集规模大(>100),可引入三角剖分:将多边形内部分割为三角形网格,在网格内规划路径(三角形内的连线天然不交叉)。 结合最小生成树(MST):先通过 MST 生成无交叉的骨架,再按 MST 的边序连接点(需确保 MST 的边都在多边形内)。 算法逻辑:分层凸包连接法(改进版) 该算法通过 “外层优先、逐层向内” 的策略连接点,利用凸包的几何特性避免交叉,同时通过边界检测确保路径不出界。步骤如下: 初始筛选:确认所有点均在多段线内部(前置条件,避免后续重复检测)。

凸包分层: 对当前点集计算凸包(最外层点构成的凸多边形),凸包上的点按逆时针排序后连接,天然无交叉。 从点集中移除已连接的凸包点,对剩余点重复计算凸包,形成内层点集。 层间连接:每处理完一层凸包后,从当前层的终点到下一层的起点找一条合法连线(不交叉、在多边形内)。

最终路径:按 “外层→内层→更内层” 的顺序拼接所有层的路径,形成完整遍历路径。 关键约束验证 连接过程中需实时验证两条核心规则,确保路径合法性: 不出界:任意两点的连线必须完全在多段线内部(通过 “线段与多边形边界无交叉” 验证)。 不交叉:新生成的线段不得与已存在的路径线段相交(通过 “线段交叉检测” 验证)。

C# 实现代码 以下代码基于 AutoCAD 的Autodesk.AutoCAD库实现,包含路径生成的完整逻辑(依赖前文提到的几何工具函数):

using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.DatabaseServices;

public class PathGenerator
{
    // 多段线边界(封闭多边形)
    private Polyline _boundaryPolyline;
    // 已生成的路径线段(用于交叉检测)
    private List<Tuple<Point3d, Point3d>> _existingSegments = new List<Tuple<Point3d, Point3d>>();

    public PathGenerator(Polyline boundary)
    {
        _boundaryPolyline = boundary;
    }

    /// <summary>
    /// 连接所有点,生成无交叉、不出界的路径
    /// </summary>
    /// <param name="points">待连接的点集(已确认在多段线内部)</param>
    /// <returns>路径点序列(按连接顺序排列)</returns>
    public List<Point3d> ConnectPoints(List<Point3d> points)
    {
        if (points == null || points.Count == 0)
            return new List<Point3d>();

        // 复制点集避免修改原始数据
        List<Point3d> remainingPoints = new List<Point3d>(points);
        List<Point3d> fullPath = new List<Point3d>();

        // 递归处理所有层的点
        while (remainingPoints.Count > 0)
        {
            // 1. 计算当前点集的凸包(外层点)
            List<Point3d> currentHull = GetConvexHull(remainingPoints);
            if (currentHull.Count == 0)
                break;

            // 2. 按逆时针顺序连接凸包点(确保无交叉)
            List<Point3d> hullPath = OrderHullPoints(currentHull);

            // 3. 验证并添加凸包路径到总路径
            if (fullPath.Count == 0)
            {
                // 首次添加:直接加入凸包路径
                fullPath.AddRange(hullPath);
            }
            else
            {
                // 非首次添加:先连接上一层终点与当前层起点
                Point3d lastPoint = fullPath.Last();
                Point3d firstHullPoint = hullPath.First();

                // 找到合法的连接点(可能需要调整起点)
                var (validStart, validLast) = FindValidConnection(lastPoint, hullPath, remainingPoints);
                if (validStart == null || validLast == null)
                    throw new Exception("无法找到合法的层间连接点");

                // 添加连接线段
                fullPath.Add(validStart.Value);
                // 添加当前层路径(从连接点开始)
                int startIndex = hullPath.IndexOf(validStart.Value);
                fullPath.AddRange(hullPath.Skip(startIndex));
            }

            // 4. 从剩余点中移除当前凸包点
            remainingPoints = remainingPoints.Except(currentHull).ToList();
        }

        return fullPath;
    }

    /// <summary>
    /// 对凸包点按逆时针排序(确保连接无交叉)
    /// </summary>
    private List<Point3d> OrderHullPoints(List<Point3d> hull)
    {
        if (hull.Count <= 1)
            return hull;

        // 以凸包重心为基准,按极角排序
        Point3d centroid = CalculateCentroid(hull);
        return hull.OrderBy(p => GetPolarAngle(centroid, p)).ToList();
    }

    /// <summary>
    /// 计算点集的重心(用于极角排序)
    /// </summary>
    private Point3d CalculateCentroid(List<Point3d> points)
    {
        double x = points.Average(p => p.X);
        double y = points.Average(p => p.Y);
        return new Point3d(x, y, 0); // 忽略Z轴
    }

    /// <summary>
    /// 寻找上一层终点到当前凸包的合法连接点
    /// </summary>
    private (Point3d? start, Point3d? last) FindValidConnection(
        Point3d lastPoint, List<Point3d> hullPath, List<Point3d> remainingPoints)
    {
        // 尝试当前凸包的每个点作为连接起点
        foreach (var candidate in hullPath)
        {
            // 验证连线是否合法(不出界、不交叉)
            if (IsSegmentValid(lastPoint, candidate))
            {
                return (candidate, lastPoint);
            }
        }

        // 若直接连接失败,尝试通过最近点中转(简化处理)
        var nearest = remainingPoints
            .OrderBy(p => Distance(lastPoint, p))
            .FirstOrDefault();
        if (nearest != null && IsSegmentValid(lastPoint, nearest))
        {
            return (nearest, lastPoint);
        }

        return (null, null);
    }

    /// <summary>
    /// 验证线段是否合法(不出界、不与已有路径交叉)
    /// </summary>
    private bool IsSegmentValid(Point3d a, Point3d b)
    {
        // 1. 验证线段是否完全在多段线内部
        if (!IsSegmentInsidePolygon(a, b))
            return false;

        // 2. 验证线段是否与已有路径交叉
        foreach (var seg in _existingSegments)
        {
            if (DoSegmentsIntersect(a, b, seg.Item1, seg.Item2))
                return false;
        }

        // 3. 记录合法线段(用于后续检测)
        _existingSegments.Add(Tuple.Create(a, b));
        return true;
    }

    // ------------------------------
    // 以下为依赖的几何工具函数(简化版)
    // ------------------------------

    /// <summary>
    /// 计算凸包(Graham扫描法,参考前文)
    /// </summary>
    private List<Point3d> GetConvexHull(List<Point3d> points)
    {
        // 实现同前文,此处简化
        if (points.Count <= 3)
            return points;

        // (实际实现需补充Graham扫描逻辑)
        return points;
    }

    /// <summary>
    /// 验证线段是否完全在多边形内部
    /// </summary>
    private bool IsSegmentInsidePolygon(Point3d a, Point3d b)
    {
        // 简化逻辑:线段两端点在内部,且线段不与多边形边界交叉
        if (!IsPointInPolygon(a) || !IsPointInPolygon(b))
            return false;

        // (实际需补充线段与多边形边界的交叉检测)
        return true;
    }

    /// <summary>
    /// 判断点是否在多边形内(射线法,参考前文)
    /// </summary>
    private bool IsPointInPolygon(Point3d p)
    {
        // (实现同前文)
        return true;
    }

    /// <summary>
    /// 判断两线段是否交叉
    /// </summary>
    private bool DoSegmentsIntersect(Point3d a1, Point3d a2, Point3d b1, Point3d b2)
    {
        // (实现线段交叉检测,基于叉积判断方向)
        return false;
    }

    /// <summary>
    /// 计算极角(用于排序)
    /// </summary>
    private double GetPolarAngle(Point3d pivot, Point3d p)
    {
        return Math.Atan2(p.Y - pivot.Y, p.X - pivot.X);
    }

    /// <summary>
    /// 计算两点距离
    /// </summary>
    private double Distance(Point3d a, Point3d b)
    {
        return Math.Sqrt(Math.Pow(a.X - b.X, 2) + Math.Pow(a.Y - b.Y, 2));
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山水CAD插件定制

你的鼓励是我创作最大的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值