cad.net 扫描线算法辅助类

原有连接

1,扫描线算法
https://www.cnblogs.com/JJBox/p/12571436.html
2,扫描线代码
https://www.cnblogs.com/JJBox/p/18652906.html
3,扫描线辅助类
https://www.cnblogs.com/JJBox/p/18677524.html

IFox曲线类,部分是旧的

精度处理

using System;

static class DoubleExtensions {
    public static double RoundToNearest(this double number, double tolerance = 0.0005) {
        double rounded = Math.Round(number);
        if (Math.Abs(rounded - number) <= tolerance) {
            return rounded;
        }
        return number;
    }
}

class Program {
    static void Main() {
        double[] numbers = { 0.9999, 1.0001, 1.9999, 2.0001, 9.9999, 10.0001 };
        foreach (var num in numbers) {
            double result = num.RoundToNearest();
            Console.WriteLine($"原始数: {num}, 处理后: {result}");
        }
    }
}

并行工具类

using System;
using System.Collections.Generic;
using System.Linq;

public static class ListHelper {
    // 按照固定间隔分块
    public static List<(int start, int end)> ToChunks<T>(this List<T> ents, int chunkSize) {
        List<(int start, int end)> result = new List<(int start, int end)>();
        for (int i = 0; i < ents.Count; i += chunkSize) {
            int end = Math.Min(i + chunkSize - 1, ents.Count - 1);
            result.Add((i, end));
        }
        return result;
    }
}

class Program {
    static void Main() {
        int coreCount = Environment.ProcessorCount;
        var rangeList = Enumerable.Range(0, 100).ToList();
        var chunks = rangeList.ToChunks(20);
        chunks.Select((a, b) => { // 居然b是索引
            Console.WriteLine($"Index: {b}, Start: {a.start}, End: {a.end}");
            return a;
        }).ToList();
    }
}

邻接表

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;

public class AdjacencyList<IKey, IValue> {
    Func<IValue, IKey>? StartPointKeySelector;
    Func<IValue, IKey>? EndPointKeySelector;
    int _parallelism = 1;
     // 持有弱引用
    WeakReference<List<IValue>> _values;

    public AdjacencyList(List<IValue> values) {
        if (values == null) 
            throw new ArgumentNullException(nameof(values), "values不能为null");
        _values = new WeakReference<List<IValue>>(values);
    }
    public AdjacencyList<IKey, IValue> SetStartKey(Func<IValue, IKey> func) {
        if (func == null) 
            throw new ArgumentNullException(nameof(func), "选择器不能为null");
        StartPointKeySelector = func;
        return this;
    }
    public AdjacencyList<IKey, IValue> SetEndKey(Func<IValue, IKey> func) {
        if (func == null) 
            throw new ArgumentNullException(nameof(func), "选择器不能为null");
        EndPointKeySelector = func;
        return this;
    }

    public AdjacencyList<IKey, IValue> AsParallel() {
        _parallelism = Environment.ProcessorCount;
        return this;
    }
    public AdjacencyList<IKey, IValue> WithMaxDegreeOfParallelism(int parallelism) {
        if (parallelism < 1 || parallelism >= int.MaxValue)
            throw new ArgumentException("parallelism < 1 || parallelism >= int.MaxValue");
        _parallelism = parallelism;
        return this;
    }

    public Dictionary<IKey, HashSet<IValue>> ToMap() {
        if (!_values.TryGetTarget(out var actualValues)) {
            return new Dictionary<IKey, HashSet<IValue>>();
        }
        if (_parallelism == 1) {
            return ProcessMap(0, actualValues.Count);
        }

        int chunkSize = (actualValues.Count + _parallelism - 1) / _parallelism;
        var tasks = Enumerable.Range(0, _parallelism)
            .Select(i => {
                int startIndex = i * chunkSize;
                int endIndex = Math.Min((i + 1) * chunkSize, actualValues.Count);
                return Task.Run(() => ProcessMap(startIndex, endIndex));
            });
        Task.WaitAll(tasks);

        // 合并所有局部字典
        var adjMap = tasks.Select(task => task.Result)
            .Aggregate((aggMap, localMap) => {
                if (aggMap.Count < localMap.Count) {
                    (aggMap, localMap) = (localMap, aggMap);
                }
#if NET6_0_OR_GREATER
                foreach (var pair in localMap) {
                    aggMap.GetValueRefOrAddDefault(pair.Key).UnionWith(pair.Value);
                }
#else
                foreach (var pair in localMap) {
                    if (!aggMap.TryGetValue(pair.Key, out var set)) {
                        set = new HashSet<IValue>();
                        aggMap.Add(pair.Key, set);
                    }
                    set.UnionWith(pair.Value);
                }
#endif
                return aggMap;
            });
        return adjMap;
    }
    private Dictionary<IKey, HashSet<IValue>> ProcessMap(int start, int end) {
        if (StartPointKeySelector == null) 
            throw new ArgumentNullException(nameof(StartPointKeySelector), "起点选择器不能为null");
        if (EndPointKeySelector == null) 
            throw new ArgumentNullException(nameof(EndPointKeySelector), "终点选择器不能为null");
        var localMap = new Dictionary<IKey, HashSet<IValue>>();
        if (!_values.TryGetTarget(out var actualValues)) {
            return localMap;
        }
   
        for (int j = start; j < end; j++) {
            var value = actualValues[j];
            var sp = StartPointKeySelector(value);
            var ep = EndPointKeySelector(value);
            AddMap(localMap, value, sp);
            AddMap(localMap, value, ep);
        }
        return localMap;
    }
    private void AddMap(Dictionary<IKey, HashSet<IValue>> localMap, IValue value, IKey key) {
        if (key == null) throw new ArgumentNullException(nameof(key), "键不能为null");
        HashSet<IValue> set;
        if (!localMap.TryGetValue(key, out set)) {
            set = new HashSet<IValue>();
            localMap.Add(key, set);
        }
        set.Add(value);
    }

    public AdjacencyList<IKey, IValue> PrunBranch(Dictionary<IKey, HashSet<IValue>> adjMap) {
        if (adjMap == null) throw new ArgumentNullException(nameof(adjMap), "不能为null");
        if (!_values.TryGetTarget(out var actualValues)) {
            return this;
        }
        PrunBranch(adjMap, sub => actualValues.Remove(sub));
        return this;
    }

    // 剪枝:修剪邻接表单一分支
    // 邻接表的共点只使用了一次,代表此图元是尾巴.
    void PrunBranch(Dictionary<IKey, HashSet<IValue>> adjMap,
        Action<IValue> removeAction) {
        if (StartPointKeySelector == null)
            throw new ArgumentNullException(nameof(StartPointKeySelector), "起点选择器不能为null");
        if (EndPointKeySelector == null)
            throw new ArgumentNullException(nameof(EndPointKeySelector), "终点选择器不能为null");

        var set = adjMap.AsParallel()
           .WithMaxDegreeOfParallelism(_parallelism)
           .Where(kv => kv.Value.Count == 1)
           .Select(kv => kv.Key)
           .ToHashSet();
        if (set.Count == 0) return;

        while (true) {
            // 尾巴只会在关联键上面,记录下来循环.
            HashSet<IKey> set2 = new();
            foreach(var kn in set) {
                // 尾巴只有一个曲线,所以只需要取出一个.
                var info = adjMap[kn].First();
                removeAction(info);
                // 获取键,必然含有此曲线,移除后value容器1就是新尾巴.
                var sp = StartPointKeySelector(info);
                var ep = EndPointKeySelector(info);
                adjMap[sp].Remove(info);
                adjMap[ep].Remove(info);
                if (adjMap[sp].Count == 1) set2.Add(sp);
                if (adjMap[ep].Count == 1) set2.Add(ep);
                if (adjMap[sp].Count == 0) adjMap.Remove(sp);
                if (adjMap[ep].Count == 0) adjMap.Remove(ep);
            }
            if (set2.Count == 0) break;
            // 循环剪枝,链式剪尾.
            set = set2;
        }
    }
}

单元曲线:曲线信息类

主要是缓存一层包围盒
1,存档曲线Curve
2,复合曲线CompositeCurve3d
3,单元曲线Curve3d
验证包围盒在这里:
https://www.cnblogs.com/JJBox/p/18677417

// 本质是个代理类
public class CurveInfo : DRect {
    public int RegionColor = 0; // 染色:默认0,横区1,竖区2
    public MyGroup Group { get; set; }
    public ConcurrentBag<double> Paramss { get; }
    public Curve3d OriginalC3d { get; }
    public bool IsCurve => OriginalC3d is Spline;
    public CurveInfo(Curve3d cc) {
        OriginalC3d = cc;
        _left = cc.OrthoBoundBlock.GetMinimumPoint().Y;
        _right = cc.OrthoBoundBlock.GetMaximumPoint().Y;
        _bottom = cc.OrthoBoundBlock.GetMinimumPoint().X;
        _top = cc.OrthoBoundBlock.GetMaximumPoint().X;
        Group = new(0);
        Paramss = new();
    }

    public CurveInfo(Curve curve) : this(curve.GetGeCurve()) {
    }

    // 根据曲线参数打断
    public IEnumerable<CurveInfo> BreakCurves() {
        var array = Paramss.ToArray();
        Array.Sort(array);
        return GetSplitCurvesToInfo(array);
    }

    public IEnumerable<CurveInfo> GetSplitCurvesToInfo(double[] paramss) {
        Curve3d[] resultArray = null;
        GetSplitCurves(paramss, ref resultArray);
        return resultArray.Select(curve3d => new CurveInfo(curve3d));
    }

    // 断分,扫描线碰撞需要
    // 从后面往前打断
    // 打断之后只有两个成员,头不输出,用来继续打断.
    public void GetSplitCurves(double[] paramss, ref Curve3d[] resultArray) {
        int size = paramss.Length + 1;
#if NET7_1_OR_GREATER
        resultArray = stackalloc Curve3d[size];
#else
        resultArray = new Curve3d[size];
#endif
        Curve3d cur = OriginalC3d;
        for (int i = paramss.Length - 1; i >= 0; i--) {
            Curve3d[] cs = cur.GetSplitCurves(paramss[i]);
            resultArray[i + 1] = cs.Last();
            cur = cs.First();
        }
        resultArray[0] = cur;
    }

    // TODO 炸开失败要返回本体,这里炸开有这个功能吗?
    // 单元曲线:复合曲线炸开而来.
    static Interval _interval = new Interval(Tolerance.Global.EqualPoint);
    public Curve3d[] Explode() {
        return OriginalC3d.Explode(_interval);
    }
    public IEnumerable<CurveInfo> ExplodeToInfo() {
        return Explode().Select(curve3d => new CurveInfo(curve3d));
    }

    // 隐式转换
    public static implicit operator Curve3d(CurveInfo m) {
        return m.OriginalC3d;
    }
    public static implicit operator CompositeCurve3d(CurveInfo m) {
        return Curve.CreateFormGeCurve(m.OriginalC3d))
                .ToCompositeCurve3d();
    }
    public static implicit operator Curve(CurveInfo m) {
        return Curve.CreateFormGeCurve(m.OriginalC3d));
    }

    public bool HasPoint(Point3d pt) {
        return OriginalC3d.StartPoint.IsEqualTo(pt, Tolerance.Global)
            || OriginalC3d.EndPoint.IsEqualTo(pt, Tolerance.Global);
    }
    public bool HasTwoPoint(CurveInfo other) {
        return (OriginalC3d.StartPoint.IsEqualTo(other.OriginalC3d.StartPoint, Tolerance.Global)
            && OriginalC3d.EndPoint.IsEqualTo(other.OriginalC3d.EndPoint, Tolerance.Global))
            || (OriginalC3d.StartPoint.IsEqualTo(other.OriginalC3d.EndPoint, Tolerance.Global)
            && OriginalC3d.EndPoint.IsEqualTo(other.OriginalC3d.StartPoint, Tolerance.Global));
    }
}

复合曲线:超级多段线

必然连续
CompositeCurve3d其实这个类就是它,只是我们做了一层代理

// 超级多段线,多断曲线
public class SuperPoly : DRect {
    Point3d StartPoint = Point3d.Origin;
    Point3d EndPoint = Point3d.Origin;
    // 边界是有序
    List<CurveInfo> _line = new();
    public List<CurveInfo> Sub => _line;
    // 点集计数
    Dictionary<Point3d, int> _pointMap = new();
    public bool _isClosed = false;
    public bool IsClosed => _isClosed;

    // 空构造是美好的,尤其是序列化需要,
    // 那么第一段也通过tryadd加入了
    // 并且移除时候也可能移除全部子元素.
    public SuperPoly() {}

    public SuperPoly(SuperPoly sp) {
        StartPoint = sp.StartPoint;
        EndPoint = sp.EndPoint;
        _line = new(sp._line);
        _pointMap = new(sp._pointMap);
        _isClosed = sp._isClosed;
        _left = sp._left;
        _right = sp._right;
        _bottom = sp._bottom;
        _top = sp._top;
    }

/*
    // 存在共点,计数判断撞腰效率比较低
    public bool HasRepeatedPoint() {
        return _pointMap.Any(kv => kv.Value > 2); // 结构体从不为空
    }
*/

    public bool TryAdd(CurveInfo cc) {
        // 按道理已经在数据清洗时候剔除了,因为零长根本没有碰撞.
        if (cc.StartPoint.IsEqualTo(cc.EndPoint, Tolerance.Global))
            throw new ("零长度拒绝");

        var flag = false;
        // 空构造需要加入第一个,并且不断移除会导致为0
        if (_line.Count == 0) {
            _line.Add(cc);
            StartPoint = cc.StartPoint;
            EndPoint = cc.EndPoint;
            flag = true;
        }
        else if (StartPoint.IsEqualTo(cc.StartPoint, Tolerance.Global)) {
            _line.Insert(0, cc);
            StartPoint = cc.EndPoint;
            flag = true;
        }
        else if (StartPoint.IsEqualTo(cc.EndPoint, Tolerance.Global)) {
            _line.Insert(0, cc);
            StartPoint = cc.StartPoint;
            flag = true;
        }
        else if (EndPoint.IsEqualTo(cc.StartPoint, Tolerance.Global)) {
            _line.Add(cc);
            EndPoint = cc.EndPoint;
            flag = true;
        }
        else if (EndPoint.IsEqualTo(cc.EndPoint, Tolerance.Global)) {
            _line.Add(cc);
            EndPoint = cc.StartPoint;
            flag = true;
        }
        if (flag) Update(cc);
        return flag;
    }

    void Update(CurveInfo cc) {
        _isClosed = StartPoint.IsEqualTo(EndPoint, Tolerance.Global);

        _left = Math.Min(_left, cc.X);
        _right = Math.Max(_right, cc.Right);
        _bottom = Math.Min(_bottom, cc.Y);
        _top = Math.Max(_top, cc.Top);

        if (_pointMap.ContainsKey(cc.StartPoint))
            _pointMap[cc.StartPoint]++;
        else _pointMap.Add(cc.StartPoint, 1);
        if (_pointMap.ContainsKey(cc.EndPoint))
            _pointMap[cc.EndPoint]++;
        else _pointMap.Add(cc.EndPoint, 1);
    }    

    // 传入对象是连续的,那么移除也是连续的.
    // 必须是从尾巴一个个移除,不然就不是链条了
    public void RemoveAll(SuperPoly sp) {
        var hashset = sp._line.ToHashSet();
        for(int i = _line.Count-1; i >=0; i--) {
            if(hashset.Contains(_line[i]))
                _line.RemoveAt(i);
        }
        _pointMap.Clear();
        if (_line.Count == 0) {
            StartPoint = Point3d.Origin;
            EndPoint = Point3d.Origin;
            _isClosed = false;
            _left = double.MaxValue;
            _right = double.MinValue;
            _bottom = double.MaxValue;
            _top = double.MinValue;
            return;
        }
        foreach(var info in _line) 
            Update(info);
   }

    public bool HasSub(CurveInfo cc) {
        // 包围盒矩形相交
        // curve1.BoundBlock.IsDisjoint(curve2.BoundBlock))
        if (base.IntersectsWith(cc)) {
            return _line.Contains(cc);
        }
        return false;
    }

    // 撞腰,腰间闭环,加入前判断点集是否含有.
    public bool HasTwoPoint(CurveInfo cc) {
        return _pointMap.ContainsKey(cc.StartPoint)
            && _pointMap.ContainsKey(cc.EndPoint);
    }

   // 加入的线段首尾连接,能够立即成环
   public bool HasPointFirstAndLast(CurveInfo cc) {
        if (_line.Count == 0) return false;
        return (StartPoint.IsEqualTo(cc.StartPoint, Tolerance.Global)
            && EndPoint.IsEqualTo(cc.EndPoint, Tolerance.Global))
            || (EndPoint.IsEqualTo(cc.StartPoint, Tolerance.Global)
            && StartPoint.IsEqualTo(cc.EndPoint, Tolerance.Global));
   }

    // 加入的线段只有一个拐点连接.
    public bool HasPointFirstOrLast(CurveInfo cc) {
        if (_line.Count == 0) return false;
        return StartPoint.IsEqualTo(cc.StartPoint, Tolerance.Global)
            || StartPoint.IsEqualTo(cc.EndPoint, Tolerance.Global)
            || EndPoint.IsEqualTo(cc.StartPoint, Tolerance.Global)
            || EndPoint.IsEqualTo(cc.EndPoint, Tolerance.Global);
    }

}

扫描线

/// <summary>
/// 扫描线
/// </summary>
public class Scanline<T> : IComparable<Scanline<T>> {
    // 依照行排序或者列排序
    public int CompareTo(Scanline<T> other) {
        return Num.CompareTo(other.Num);
    }

    // num是Y,num2就是X,反之亦然.
    public double Num;

    // key是num2一条扫描线的各个断分点,
    // value是0x51描述.
    SortedList<double, T> _line = new();
    public int Count => _line.Count;

    public Scanline(double num) {
        Num = num;
    }

    // 0x51,交点重叠不能是同一个组√
    // 虽然存在同交点重叠图元,但是它们不是同组,否则破坏对边成组关系.
    // 但是压根不存在扫描线碰撞到端点:
    // a1,如果曲线中点有另一条曲线端点压在图元上面,
    // 会在数据清洗时候,判断为碰撞,进行打断成更碎,成为三条曲线.
    // a2,如果不是碰撞,就会+=微量,避开端点.
    // 所以只会击中中点位置的一个图元.
    // 0x52,对边也是会被加入同一个组.
    public void Add(double num2, T curveInfo) {
        if(_line.TryGetValue(num2, out T a)) {
            string msg = "扫描线只能击中一个图元的腰部,此处为什么出错?你是不是没有消重";
            msg += $"\n已经加入的{a}";
            msg += $"\n错误加入的{curveInfo}";
            throw new(msg);
        }
        _line.Add(num2, curveInfo);
    }

    /// <summary>
    /// 左右两个碰撞点间隔输出
    /// </summary>
    /// <param name="action">左右两个(点,组)输出</param>
    // 间隔输出画线0-1,2-3,4-5... 
    public void APairTask(Action<KeyValuePair<double, T>,
        KeyValuePair<double, T>> action) {
        for (int i = 1; i < _line.Count; i += 2)
            action(_line[i - 1], _line[i]);
    }

    // 如果需要找到一笔画五芒星中心,只需要跳过_line第一个交点
    // 间隔输出画线1-2,3-4,5-6...
    public void BPairTask(Action<KeyValuePair<double, T>,
        KeyValuePair<double, T>> action) {
        for (int i = 2; i < _line.Count; i += 2)
            action(_line[i - 1], _line[i]);
    }
}

矩形

public class DRect {
    protected double _left;
    protected double _right;
    protected double _bottom;
    protected double _top;
    public double X => _left;
    public double Y => _bottom;
    public double Right => _right;
    public double Top => _top;

    public double Height => _top - _bottom;
    public double Width => _right - _left;

    public double MidX => (_top - _bottom) / 2 + _bottom;
    public double MidY => (_right - _left) / 2 + _left;

    // 碰撞
    public bool IntersectsWith(DRect rect) {
        return _right > rect._left &&
                   _left < rect._right &&
                   _top > rect._bottom &&
                   _bottom < rect._top;
    }

    // 完全包含
    public bool Contains(DRect rect) {
        return _left <= rect._left &&
                   _right >= rect._right &&
                   _bottom <= rect._bottom &&
                   _top >= rect._top;
    }
}

填充边界层次树

它是一颗多叉树,
内环可能还有内环,是一棵多叉树,相同层次就同级.
内环段数可能比外环多,外环先后完成都存在.
层次环,有序,通过点集射线法?

什么定理可以确定环套环呢?如果是样条曲线,岂不是要采样吗?
让AI写:
我想制作一棵多叉树,每次加入一个曲线
1,如果不包含就是同层,要增加分支.
2,如果反向包含,需要根节点交换..
3,如果和根节点是同层,则报错.
C#语言...

public class SuperTree : DRect {

}
posted @   惊惊  阅读(90)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示