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
精度处理
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 {
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)