@[toc]
需要生成的效果 吊坑或集水坑的基本原理是按照板边界外侧生成一圈墙体进行封堵

折线多边形的原位缩放两种办法 一种是周培德的计算几何-算法设计与分析(第三版)中的办法另外一种是引用自折线平行线的计算方法 的办法,最后第二种可以解决项目问题,但是两种算法都写了一遍就都分析一下,这种情况以后还会遇到方便回查。
6.10 多边形放大、缩小及移动 P309 教材中记载了一种方法,通过封闭多边形内部的一个点向每个点做向量,并基于向量的值向外侧偏移指定距离实现偏移,此处需要计算的值
多边形内部点与边界点的向量 v
偏移值是两个平行线段的垂线距离所以遇到斜向线段时需要计算实际长度
c#实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 public static CurveLoop OffsetPath (CurveLoop array,XYZ insidePoint,double k ){ var curves = new List<Curve>(); var iEnumerator = array.GetEnumerator(); while (iEnumerator.MoveNext()) { var curve = iEnumerator.Current; curves.Add(curve); } if (curves.Count == 0 ) throw new ArgumentNullException(); var points = new List<XYZ>(); foreach (var curve in curves) { if (curve is Line line) { var vec = line.Direction.Normalize(); var targetVec = GetVerticalVector(vec); var referencePoint = line.GetEndPoint(1 ); var dir = new XYZ(insidePoint.X - referencePoint.X, insidePoint.Y - referencePoint.Y, 0 ) .Normalize(); var angle = dir.AngleTo(targetVec); if (angle > Math.PI / 2 ) { var count = Math.Floor(angle / (Math.PI / 2 )); angle -= Math.PI / 2 * count; } var tk = k / Math.Cos(angle); var lVpO = Line.CreateBound(referencePoint, insidePoint).Length; var x = Math.Abs(referencePoint.X - insidePoint.X) / lVpO * (tk + lVpO); var y = Math.Abs(referencePoint.Y - insidePoint.Y) / lVpO * (tk + lVpO); var subtractionVec = (referencePoint - insidePoint).Normalize(); if (subtractionVec.X <= 0 ) { if (subtractionVec.Y >= 0 ) { var targetPoint = new XYZ((insidePoint.X - x).ReduceDouble(4 ), (insidePoint.Y + y).ReduceDouble(4 ), referencePoint.Z); points.Add(targetPoint); } else { var targetPoint = new XYZ((insidePoint.X - x).ReduceDouble(4 ), (insidePoint.Y - y).ReduceDouble(4 ), referencePoint.Z); points.Add(targetPoint); } } else { if (subtractionVec.Y >= 0 ) { var targetPoint = new XYZ((insidePoint.X + x).ReduceDouble(4 ), (insidePoint.Y + y).ReduceDouble(4 ), referencePoint.Z); points.Add(targetPoint); } else { var targetPoint = new XYZ((insidePoint.X + x).ReduceDouble(4 ), (insidePoint.Y - y).ReduceDouble(4 ), referencePoint.Z); points.Add(targetPoint); } } } }
缺点 这种方法无法适用于板边界复杂的情况,但是对于凸包集合可以使用,这种理解起来相对简单,但是需要增加象限的加减,有兴趣的可以根据目录查找一下书籍详细阅读一下
分析 第一张可以看到大部分不准确,原因是对于中心点向边界方向偏移,会出现错误详图,此处下方边界需要向两侧分别偏移但是出现了凸角集体偏移的情况.所以 在实际项目中会出现项目吊坑生成错误的情况。

向量缩放办法 其实第一方法也是使用向量进行修改,但是为了区分随便叫了一个名字,原文链接,此处增加了自己的解释和c#代码

算法剖析
两组偏移线段的可以组成一个菱形
我们可以使用三角函数求出其中一个边的边长
根据单位向量与长度的比值可以求出中心PiQi的长度
输入:偏移的实际距离,边界点集合
输出:偏移后的点集合
算法步骤
根据点集合求出单位向量normal v1 , normal v2
整理公式1 :|a X b| = |a|*|b|*sin(θ)
可以获得关于角度的计算公式,但是由于∠θ与另一个角共线,根据定理可以推出sin(Π - θ) = sin(θ)
根据3与h距离,可以通过三角函数求出v2方向的菱形边长度,Lb为菱形边长度,L代表h,即:L/Lb = sin(Π - θ) 又 sin(Π - θ) = sin(θ)
整理2,3的公式之后可以获得最终公式:Lb = L/|a X b|/|a|/|b|
又因为是单位向量|a|,|b|的模为1
最后通过向量相加公式: Qi = Pi + L/|a x b| x (v1 + v2)
向量的方向代表缩小和放大,主要是原文中对于L/sin(θ)这一步会让人疑惑,所以在这解释一下
c#代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 public static CurveLoop OffsetPath (CurveLoop array, double k ){ var vertices = new List<XYZ>(); var newVertices = new List<XYZ>(); foreach (var curve in array) { vertices.Add(curve.GetEndPoint(0 )); } for (int i = 0 ; i < vertices.Count; i++) { int iPrevious = -1 ; int iEnd = -1 ; if (i == 0 ) { iPrevious = vertices.Count - 1 ; iEnd = i + 1 ; } else if (i == vertices.Count - 1 ) { iPrevious = i - 1 ; iEnd = 0 ; } else { iPrevious = i - 1 ; iEnd = i + 1 ; } XYZ pPrevious = vertices[iPrevious]; XYZ point = vertices[i]; XYZ pEnd = vertices[iEnd]; var v1 = (pPrevious - point).Normalize(); var v2 = (pEnd - point).Normalize(); var cross = (v1.X*v2.Y - v1.Y*v2.X); var lb = 0.00 ; if (cross == 0 ) continue ; lb = k / cross; var tPoint = point + lb * (v1 + v2); newVertices.Add(tPoint); } var loop = new CurveLoop(); for (int i = 0 ; i < newVertices.Count; i++) { if (i == newVertices.Count - 1 ) { var c = Line.CreateBound(newVertices[i], newVertices[0 ]); loop.Append(c); } else { var c = Line.CreateBound(newVertices[i], newVertices[i + 1 ]); loop.Append(c); } } return loop; }
相较于我遇到的问题这种方法的代码量更少,准确性更好,学习之路坎坷需要时刻学习!
Revit API 方法 做Revit开发的话,如果不涉及复杂边界,可以使用里面的CurveLoop.CreateViaOffset方法,但是此种办法会出现错误,在复杂边界中会出现外扩变为缩小的情况,如下图

使用方法二的生成:

方法一生成:
