一、问题来源
前段时间在做某个智能设计插件时,遇到的一个问题:已知房间的形状都比较“类矩形”,在房间内我要取到房间内的面积最大的矩形,从而来实现我的下一步操作。在建筑设计中,大多房间都不是严格的矩形,布置空间缺个角,挖个口都是非常常见的情况,所以我觉得该问题应该在很多情况下都可能需要。
二、解决方法
解决方法思路参考leecode 85题【leetcode】85 最大矩形(动态规划,栈)_最大矩形 动态规划-CSDN博客
1、常规操作在房间内先取到房间墙壁围合的CurveLoop(略)
2、获取到curveloop后获取竖向的边和横向的边,然后通过将线延长然后获得到房间的所有边组合成的Grid(示图如下),提示:在形成Grid前应该有先把延长重合的线给筛掉,不然影响curveloop的形成
public CurveLoop[,] GetGrid(CurveLoop curveloop)
{
List<Curve> VerticalCurve = new List<Curve>();//垂直线(dir.x == 0)
List<Curve> HorizontalCurve = new List<Curve>();//水平线(dir.y == 0)
foreach (Curve curve in curveloop)
{
if ((int)(curve as Line).Direction.X == 0)
{
VerticalCurve.Add(curve);
}
else
{
HorizontalCurve.Add(curve);
}
}
List<Curve> VCurve = VerticalCurve.OrderBy(s => s.GetEndPoint(0).X).ToList();
Curve tempVcurve = null; List<Curve> NewVCurve = new List<Curve>();
foreach (Curve curve in VCurve)
{
if (tempVcurve == null)
{
tempVcurve = curve;
NewVCurve.Add(curve);
}
else
{
if (Math.Round(tempVcurve.GetEndPoint(0).X, 3) == Math.Round(curve.GetEndPoint(0).X, 3))
{
continue;
}
else
{
tempVcurve = curve;
NewVCurve.Add(curve);
}
}
}
List<Curve> HCurve = HorizontalCurve.OrderBy(s => s.GetEndPoint(0).Y).ToList();
Curve tempHcurve = null; List<Curve> NewHCurve = new List<Curve>();
foreach (Curve curve in HCurve)
{
if (tempHcurve == null)
{
tempHcurve = curve;
NewHCurve.Add(curve);
}
else
{
if (Math.Round(tempHcurve.GetEndPoint(0).Y, 3) == Math.Round(curve.GetEndPoint(0).Y, 3))
{
continue;
}
else
{
tempHcurve = curve;
NewHCurve.Add(curve);
}
}
}
CurveLoop[,] curvegrid = new CurveLoop[NewVCurve.Count - 1, NewHCurve.Count - 1];
for (int i = 0; i < NewVCurve.Count - 1; i++)
{
for (int j = 0; j < NewHCurve.Count - 1; j++)
{
CurveLoop tempCurveLoop = CreateLoop(new List<Curve>() { NewVCurve[i], NewHCurve[j], NewVCurve[i + 1], NewHCurve[j + 1] });
curvegrid[i, j] = tempCurveLoop;//
}
}
return curvegrid;
}
3、获取到curveloopGrid后,就要用到上面的那个leecode 85题目的内容了。因为我们的实际情况下“类矩形”房间的GetGrid后也不会有太多的矩形,所以用最好理解的暴力广度优先算法解决问题就好,当然就算法来说是有优化的空间的。
操作思路是先确认CurveLoopGrid中的每个位置的loop是否在房间内,如果是在房间内就计算loop的面积,如果在房间外就将面积设置为-100000(当然只要负的足够大,数字你随意),如上图就是左下和右上的两个loop的面积是-10000,其他的loop都是真实面积。
private double CaculateArea(CurveLoop curves)
{
Solid solid = GeometryCreationUtilities.CreateExtrusionGeometry(new List<CurveLoop>() { curves }, new XYZ(0,0,1), 1);
SolidCurveIntersectionOptions opt= new SolidCurveIntersectionOptions();
opt.ResultType = SolidCurveIntersectionMode.CurveSegmentsInside;
bool IsAreaNull = false;
foreach (Curve curve in curves)
{
var intersect = RoomSolid.IntersectWithCurve(curve,opt);
if(intersect.SegmentCount == 0)
{
IsAreaNull = true;
}
}
if (IsAreaNull)
{
return -100000;
}
else
{
return curves.GetRectangularHeight(Plane.CreateByNormalAndOrigin(XYZ.BasisZ, XYZ.Zero)) * curves.GetRectangularWidth(Plane.CreateByNormalAndOrigin(XYZ.BasisZ, XYZ.Zero));
}
}
然后就是模仿leecode 85的操作,遍历所有的矩形组合来获得面积最大的组合方式
/// <summary>
/// 计算某个选定区域内线圈的面积之和
/// </summary>
/// <param name="loopgrid">线圈集</param>
/// <param name="right">向右偏移值</param>
/// <param name="up">向上偏移值</param>
/// <param name="i">线圈横向编号</param>
/// <param name="j">线圈竖向编号</param>
/// <returns></returns>
private double TotalArea(CurveLoop[,] loopgrid,int right,int up,int i,int j)
{
var loop1 = loopgrid[i,j];
double TotalArea = 0;
for(int n = 0; n <= right;n++)
{
for(int m = 0;m <= up;m++)
{
TotalArea = TotalArea + CaculateArea(loopgrid[i+n,j+m]);
}
}
return TotalArea;
}
int Maxi = 0; int Maxj = 0; int Maxright = 0; int Maxup = 0;
for (int i = 0;i<CurveLoopGrid.GetLength(0);i++)//列
{
for(int j = 0;j<CurveLoopGrid.GetLength(1);j++)//行
{
for (int right = 0; right < CurveLoopGrid.GetLength(0)-i; right++)
{
for(int up = 0; up < CurveLoopGrid.GetLength(1)-j; up++)
{
double temparea = TotalArea(CurveLoopGrid, right, up, i, j);
if(temparea > MaxTotalArea)
{
MaxTotalArea = temparea;
Maxi = i;Maxj = j;Maxright = right;Maxup = up;
}
}
}
}
}
最后就是将获得的loop组合方式来merge一下就ok
/// <summary>
/// 合并最大的线圈
/// </summary>
/// <param name="loopgrid">二维线圈数组</param>
/// <param name="right">向右偏移范围</param>
/// <param name="up">向上偏移范围</param>
/// <param name="i">线圈起始x位置</param>
/// <param name="j">线圈起始y位置</param>
/// <param name="minXYZ">形成最大线圈范围的最小点</param>
/// <param name="maxXYZ">形成最大线圈范围的最大点</param>
/// <returns></returns>
private CurveLoop MergeMaxLoop(CurveLoop[,] loopgrid, int right, int up, int i, int j,out XYZ minXYZ,out XYZ maxXYZ)
{
CurveLoop startloop = loopgrid[i,j];
CurveLoop maxloop = loopgrid[i+right,j+up];
minXYZ = null;maxXYZ = null;
foreach (Curve curve in startloop)
{
XYZ endpoint = curve.GetEndPoint(0);
if (minXYZ == null || (int)endpoint.X < (int)minXYZ.X || (int)endpoint.Y < (int)minXYZ.Y)
{
minXYZ = endpoint;
}
}
foreach (Curve curve in maxloop)
{
XYZ endpoint = curve.GetEndPoint(0);
if (maxXYZ == null || (int)endpoint.X > (int)maxXYZ.X || (int)endpoint.Y > (int)maxXYZ.Y)
{
maxXYZ = endpoint;
}
}
XYZ midXYZ1 = new XYZ(minXYZ.X,maxXYZ.Y,minXYZ.Z);XYZ midXYZ2 = new XYZ(maxXYZ.X,minXYZ.Y,minXYZ.Z);
Line line1 = Line.CreateBound(minXYZ, midXYZ1);
Line line2 = Line.CreateBound(midXYZ1, maxXYZ);
Line line3 = Line.CreateBound(maxXYZ, midXYZ2);
Line line4 = Line.CreateBound(midXYZ2, minXYZ);
return CurveLoop.Create(new List<Curve>() { line1,line2,line3,line4});
}
上面的那个房间的最后的结果就是下图,当然要看到这个merge后的loop你得自己创建detailline,我略过了。
三、最后总结
该方法运用算法来获得最大的矩形面积应该在智能插件开发中会有一定的使用,特别是想要将插件运用在实际项目中,不得不直面项目的各种可能性,目前我考虑思路是先简化房间的空间然后完成插件操作最后根据房间特性进行调整。该算法方法应该就是在简化房间的这个步骤中。
最后,欢迎各位大佬指正。