@[toc]

需要生成的效果

吊坑或集水坑的基本原理是按照板边界外侧生成一圈墙体进行封堵

![在这里插入图片描述](https://cdn.bimath.com/blog/pg/Snipaste_2026-01-04_17-12-09.png)

折线多边形的原位缩放两种办法

一种是周培德的计算几何-算法设计与分析(第三版)中的办法另外一种是引用自折线平行线的计算方法的办法,最后第二种可以解决项目问题,但是两种算法都写了一遍就都分析一下,这种情况以后还会遇到方便回查。

6.10 多边形放大、缩小及移动 P309

教材中记载了一种方法,通过封闭多边形内部的一个点向每个点做向量,并基于向量的值向外侧偏移指定距离实现偏移,此处需要计算的值

  1. 多边形内部点与边界点的向量 v
  2. 偏移值是两个平行线段的垂线距离所以遇到斜向线段时需要计算实际长度

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
/// <summary>
/// 偏移指定路径值
/// </summary>
/// <param name="array">楼板轮廓线集合</param>
/// <param name="insidePoint">内部点,使用时将face origin处理后传入</param>
/// <param name="k">缩放系数</param>
/// <returns></returns>
public static CurveLoop OffsetPath(CurveLoop array,XYZ insidePoint,double k)
{
//get all vertexes
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>();
//遍历每个线段取线段的1点作为偏移点,求线段线段向量的垂直向量并求出角度cos theta
//求出实际偏移距离kt,从而求出延长点,加入到集合points中
foreach (var curve in curves)
{
if (curve is Line line)
{
var vec = line.Direction.Normalize();
var targetVec = GetVerticalVector(vec);
//取线段的1点作为参照点
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;
}
//在多边形内部角度都为锐角。因为他们与向量的垂直向量组成锐角三角形,角度最大为90°
//实际偏移值,根据一直向量求延长线上此点位置
var tk = k / Math.Cos(angle);

//求已知向量上的延长线,此处仅讨论向外侧延申,Z都一致,为二维向量
//输入:原点o , 边界点 vp , 延长距离 tk
//输出:延长点 tp
// tp.x = o.x + abs(vp.x - o.x) / L(vp , o) * (tk + L(vp,o))
// tp.y = o.y + abs(vp.y - o.y) / L(vp , o) * (tk + L(vp,o))
//垂直三角形三个边 a,b,c c最长 ,每个延长线均可视为一个三角形,通过比较c的比值,从而获得a,b的长度,并从原点增加相应长度

//L(vp,o)
//可求出具体的x,y偏移值,但是生成的复杂吊坑面会出现整体偏移的情况,原因是向外部做射线,并
//从referencePoint 开始偏移会导致最终生成生成错误

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);
}
}


}

}

缺点

这种方法无法适用于板边界复杂的情况,但是对于凸包集合可以使用,这种理解起来相对简单,但是需要增加象限的加减,有兴趣的可以根据目录查找一下书籍详细阅读一下

分析

第一张可以看到大部分不准确,原因是对于中心点向边界方向偏移,会出现错误详图,此处下方边界需要向两侧分别偏移但是出现了凸角集体偏移的情况.所以 在实际项目中会出现项目吊坑生成错误的情况。

![在这里插入图片描述](https://cdn.bimath.com/blog/pg/Snipaste_2026-01-04_17-12-15.png)

向量缩放办法

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

![在这里插入图片描述](https://cdn.bimath.com/blog/pg/Snipaste_2026-01-04_17-12-19.png)

算法剖析

  1. 两组偏移线段的可以组成一个菱形
  2. 我们可以使用三角函数求出其中一个边的边长
  3. 根据单位向量与长度的比值可以求出中心PiQi的长度
  • 输入:偏移的实际距离,边界点集合
  • 输出:偏移后的点集合

算法步骤

  1. 根据点集合求出单位向量normal v1 , normal v2
  2. 整理公式1 :|a X b| = |a|*|b|*sin(θ)
  3. 可以获得关于角度的计算公式,但是由于∠θ与另一个角共线,根据定理可以推出sin(Π - θ) = sin(θ)
  4. 根据3与h距离,可以通过三角函数求出v2方向的菱形边长度,Lb为菱形边长度,L代表h,即:L/Lb = sin(Π - θ) 又 sin(Π - θ) = sin(θ)
  5. 整理2,3的公式之后可以获得最终公式:Lb = L/|a X b|/|a|/|b|
  6. 又因为是单位向量|a|,|b|的模为1
  7. 最后通过向量相加公式: Qi = Pi + L/|a x b| x (v1 + v2)
  8. 向量的方向代表缩小和放大,主要是原文中对于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)
{

/*
* 第二种方法:来源:https://blog.csdn.net/happy__888/article/details/315762
* 此方法适用于平行直线结合形成的折线多边形的原位缩小与放大
* 通过缩放前点与缩放后的点及两点形成的向量(a,b)可组成四边相同的菱形形状
* 平行四边形面积:|a X b| = |a|*|b|*sin(θ) ,
* 已知偏移的长度,及两条平行线段的垂线距离L,可以在菱形边做垂线获得一个直角三角形
* 通过三角形定理,L/Lb = sin(Π - θ) 又 sin(Π - θ) = sin(θ)
* 所以可以得到最终公式: Lb = L/|a X b|/|a|/|b|
* 又最终点是起点(Ps) + 向量方向(a + b) * 向量长度(Lb)[因为之前求取向量将向量简化为单位向量,所以起点到中点的距离应该是菱形边会到未
* 单位化之前的值 及 normal(Lb) / Lb = normal(LTargetPoint) / LTargetPoint]
* 所以通过上述公式可以将最终点求出
*
*/

var vertices = new List<XYZ>();
var newVertices = new List<XYZ>();
//因为Revit中顶点都是逆时针排序,只需要取出点即可
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];

//normalize
var v1 = (pPrevious - point).Normalize();
var v2 = (pEnd - point).Normalize();
var cross = (v1.X*v2.Y - v1.Y*v2.X);//叉积 , v1 , v2单位向量模为1
var lb = 0.00;
if (cross == 0)
continue;
lb = k / cross;
var tPoint = point + lb * (v1 + v2);
newVertices.Add(tPoint);

}


//output
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方法,但是此种办法会出现错误,在复杂边界中会出现外扩变为缩小的情况,如下图

![在这里插入图片描述](https://cdn.bimath.com/blog/pg/Snipaste_2026-01-04_17-12-25.png)
使用方法二的生成:
![在这里插入图片描述](https://cdn.bimath.com/blog/pg/Snipaste_2026-01-04_17-12-29.png)
方法一生成:
![在这里插入图片描述](https://cdn.bimath.com/blog/pg/Snipaste_2026-01-04_17-12-34.png)