158 层楼板是怎么生成的:没有楼层平面的分层重建
先说一个不那么体面的前提:这个项目的图纸里没有一张楼层平面。整个 DWG 只有总平面图,纸张空间是空的,986 个块里没有一个墙、柱、门窗。
所以"每层楼的平面"不是读出来的,是推出来的。这篇文章讲怎么把一张总平面图放大成 158 个可选中、可聚焦、可放设备的楼层。
一、目标不是好看,是可操作
如果只要一个体量,把轮廓 ExtrudeGeometry 拉起来就够了。但需求是四条:
- 能选中任意一栋楼的任意一层;
- 选中后镜头聚焦到那一层;
- 能在这一层上放设备定位点、拉链路;
- 楼层平面要有基本结构感:核心筒、走廊、卫生间、办公大开间。
第 3 条决定了楼板必须是真实的平面多边形,不能是贴图或纯装饰——设备点落在楼板外面要判非法。
二、数据结构:从 11 到 158
buildings[11] { id, height, aboveFloors, belowFloors, podiumFloors, core, levels[] }
levels[] { level, name, elev, floorH, kind, ring, ringKind, corridorRing }
core { poly, wallInner, angle, depth, parts { stair | lift | toilet } }
两个设计决定值得说:
core挂在楼栋上而不是楼层上。每层核心筒位置相同,只存一份,省掉 158 份重复几何;ring是每层楼板的平面轮廓,局部米制,原点在园区中心(不是图纸的 UTM 坐标,见后文)。
最终生成:
| 楼栋 | 地上 | 地下 | 层数 | 标注高度 |
|---|---|---|---|---|
| 1# / 2# | 1 层裙房 + 18 层塔楼 = 19 | 2 | 21 | 64.65 m |
| 3# / 4# | 3 层裙房 + 13 层塔楼 = 16 | 1 | 17 | 65.4 / 66.6 m |
| 5# / 6# | 21 | 2 | 23 | 73.95 m |
| 7# | 1 层裙房 + 21 层塔楼 = 22 | 2 | 24 | 73.95 m |
| 8# ~ 11# | 3 | 0 | 3 | 13.0 m |
| 合计 | 158 |
层高不是一律 3.6 m。地下统一 3.6,裙房 4.5,首层多为 4.5(5# / 6# 是 3.521,8#~11# 是 4.333),标准层按"标注高度 − 首层 − 裙房"反解均分,实测落在 3.307 / 3.342 / 3.521 三档上,分别有 21 / 36 / 42 层。
这里有个很好用的自校验:算完之后,每栋楼最后一个楼层的标高必须刚好等于图纸标注的 H。 对不上就是层数或层高算错了。我在这条上抓到过一次 bug——地上层数漏算了裙房,循环写成 range(1, tower + 1),正确应该是 range(1, podium + tower + 1)。
三、核心筒落位:不能用质心
第一版我用多边形质心放核心筒,结果 L 形和 U 形的核心筒直接跑到轮廓外面去了——凹形的质心可能根本不在多边形内部。
改成网格Search"内部离边界最远的点":
def deepest_point(poly, step=0.4):
minx, miny, maxx, maxy = poly.bounds
best, best_d = None, -1
y = miny
while y < maxy:
x = minx
while x < maxx:
p = Point(x, y)
if poly.contains(p):
d = poly.boundary.distance(p) # 到边界的距离
if d > best_d:
best_d, best = d, p
x += step
y += step
return best
0.4 m 网格,160 × 200 的楼栋也就几万次采样,毫秒级。这个点是"最不容易贴墙"的位置,天然适合放核心筒。
核心筒尺寸按楼栋短边推:
w = min(max(short_side * 0.52, 5), 15)
h = min(max(short_side * 0.34, 4), 11)
然后按 0.92 递减收缩,直到整个矩形完全落在轮廓内(poly.contains(box));斜置楼栋先按长边方向旋转再判。
实测核心筒深度:1#/2#/5#/6#/7# 约 3.9 m,3#/4# 8.4 m,8#~11# 5.0 m。
四、旋转对齐:PCA 定不了正反
楼板轮廓从图纸提取,天生斜置(地块本身不正南正北)。要把它变成模型的轴对齐,需要两个量:朝向和正反。
朝向用最小外接矩形(minimum_rotated_rectangle)的长边方向,比 PCA 稳——PCA 在 L 形上会被质量分布带偏,最小外接矩形是几何最优解。
但朝向定不了正反:长边方向是 180 度二义的量。同一轮廓,长轴朝北和朝南在数值上完全一样,视觉上一个对一个反。
这在总平面体量上看不出来(楼层是空白板),一旦把带核心筒的平面叠上去就立刻暴露:核心筒跑到北端了,而图纸上在南端。
最后只能加一个手工 FLIP 集合,逐栋指定是否再转 180 度:
rotation.y = angle - Math.PI / 2 + (flip ? Math.PI : 0)
顺带一个符号坑:第一版写成 -(angle - Math.PI / 2),整栋楼多转了 66 度,看起来"方向完全不对"。原因是平面长轴沿局部 +y,映射到 three 的 −z 要减去 π/2,前面的负号是多余的。
还有一条:位置要用最小外接矩形的中心,不是 ring 的质心。 L 形轮廓的质心会偏出去,楼板跟体量就错开了。
五、线框:为什么不用 EdgesGeometry
楼层模式默认开线框。第一版用 new THREE.EdgesGeometry(geo, 30),结果满屏乱线。
原因:体量是 ExtrudeGeometry 生成的,它的三角化会在立面上产生大量竖直碎边,EdgesGeometry 把这些边全当特征边画出来。
改成自己按轮廓算边:
function outlineOf(ring, y0, y1, mat, levels) {
// 1. 顶 / 底各画一条闭合环
// 2. 只在"转角顶点"画竖线:相邻边夹角 > 32°、且两条边都长于 0.4 m,最多取 8 根
// 3. levels 非空时再画逐层水平环(更淡)
}
三个参数决定观感:转角阈值 32 度(低于它的算共线,不画)、短边过滤 0.4 m(幕墙分格那种短齿会刷出一片毛刺)、竖线上限 8 根(再多就是噪点)。
楼层模式下还有一条:底环压在当前层楼板上,竖线只画到这一层顶部——竖线表示"上面还剩多少",是进度感而不是笼子感。
六、口径
必须说清楚:这 158 层是推演示意,不是施工图。 核心筒位置是算法找的"最深处",不是结构专业给的;走廊和卫生间是按长边三等分切的;房间隔墙一间都没有。
界面上也标了这句话。这样做的好处是不用假装精确;坏处是它真的只能做示意——把设备点位、链路走向、楼层选择这些交互跑通,它够用;真要做 BIM,得回到有楼层平面的图纸。
代码:gitee.com/aowind/company-builds(MIT)