Appearance
塔吊一吊一层:吊装标高搞混语义之后,落钩落在二十米外
初始化中…
这篇里有个错足以让整个场景失去意义:楼建到 30 层、作业面在 105 m,而落钩把构件放在绝对 20 多米的位置。画面不会报错,只会安安静静地演给你看——构件越落越"低",从楼体中间穿过去。这类 bug 比几何穿模危险得多,因为它不影响渲染,只影响这台孪生还能不能说明工序。
场景里有什么
- 楼:30 层装配高层,层高 3.6 m,标准层平面 22 × 16 m,结构到 108 m;核心筒随层生长,每层 1 块楼板加 4 片单元式幕墙。
- 塔吊:坐落于 (-17, 6),起重臂 45 m、平衡臂 13 m、配重块,塔身随作业面顶升,最高约 130 m。
- 工序:起钩离堆 → 回转 → 变幅就位 → 落钩就位 → 脱钩回位,一吊一层,单吊 7 秒,一轮 210 秒。
- 堆场:预制墙板 42 件,7 列 6 层码放在 (-46, 34)。
- 场地:基础底板、四面围挡、3 间临时板房。
交给 AI 的提示词(我固定分四组写)
总体与尺度
- 纯程序化生成,不引入外部模型与贴图;楼板与幕墙用两次实例化,塔吊用标准节重复件。
- 尺寸按真实量纲给死:层高 3.6 m、标准层 22 × 16 m、预制墙板 3.6 × 2.9 × 0.1 m、起重臂 45 m、塔身与楼体的水平净距约 6 m。
- 工序里只允许一种高度量:要么全程用「绝对标高」,要么全程用「自臂下垂绳长」。如果要混用,必须在唯一一处做换算,其余地方不许自己猜。
结构与顶升
- 楼层实例矩阵由
built(已施工层数,可为小数)驱动:正在成型的那一层从 0 长到 1,已完成层保持满尺寸。 - 塔身标准节按
mastH = 作业面 + 22顶升,附着道支撑按 3 m 一道随塔身增高显隐,回转总成整体坐在塔身顶部。 - 核心筒与楼板同源生长,不许出现"楼已经 20 层、筒还在 5 层"的错位。
吊装工序
- 一吊五相,每相给明确的时间区间;先算构件底面目标标高,再换算绳长:堆场取件 0.6 m → 平移安全高度 = 作业面 + 6 m → 落钩 = 当前作业面 + 0.35 m。
- 平移段的高度必须高于作业面一个完整安全值,因为回转和变幅的轨迹会从楼体正上方横穿过去。
- 就位半径不许拍脑袋写一个数,必须由目标点反解:
r = 距离(塔吊中心 → 就位点)、ang = atan2(dz, dx),就位点选在西侧幕墙外侧,不是楼板正中。 - 吊物姿态要抵消回转角,让墙板始终与所要安装的立面平行;钢丝绳起点在小车底面,末端落在吊钩中心。
- 各相之间首尾必须连续:前一相结束时的标高、半径、角度要等于后一相起始值,否则每轮回位都跳一下。
镜头、阴影与读数
- 阴影相机要框得住 130 m 塔身加堆场,别只用盖裙房的 ±90。
- 机位在"全景"与"仰拍塔吊"之间按固定节拍切换,用 smoothstep 渐变,不许 0/1 阶跃;切换周期由本组件已有的参数推出来,不许引用一个传不进来的 prop。
- 拖拽随时接管,5 秒缓动回自动机位;卸载时几何、材质、控制器全部释放。
- 零控件,读数写在左下角:工序名、已施工层数、作业面标高、回转角、幅度、吊钩标高、起升绳长、塔身高度、标准层尺寸、三角面、帧耗时。
实现里几个值得记的点
标高和绳长是两个东西,我却当一个用了。 塔吊的吊钩写在局部坐标 hookBlock.position.set(r, 3.4 - hy, 0),挂在回转总成的起重臂下方,3.4 是臂轴中心线——这里的 hy 明明白白是自臂下垂的绳长,吊钩的世界标高是 mastH + 3.4 − hy。但我五相工序里写的是 hy = 12 − (12 − (top + 1.2)) × seg(...),把 top + 1.2 当落钩高度塞进了 hy。结果落钩时 hy ≈ 106,吊钩世界标高 = 126.8 + 3.4 − 106 ≈ 24 m,而作业面在 104.8 m。越往后差得越离谱,最后构件直接落到楼下二十几层的位置。修法是工序里全程只写构件底面标高(GRAB_Y、travelY、placeY),最后统一一处 hy = mastH + 0.05 − liftY 换算成绳长。读数值也从"吊钩高 hy"改成同时报「吊钩标高」和「起升绳长」,两个量各自独立,骗不了人。
平移段太矮,吊物会横穿楼体。 原来的回转与变幅都在 hy = 12 走,也就是比塔身顶低 8.6 m。而塔身顶 = 作业面 + 22,所以吊物大致只比作业面高 13 m,勉强还行;但一旦把 hy 当标高用(上一条的错),平移段实际高度就跟着瞎了。现在明确 travelY = 作业面 + 6:高出当前作业面 6 m,构件(2.9 m 高)顶再到 8.9 m,横穿楼体正上方时不会刮到已完成的外檐。这里能这么定的原因是堆场在以塔吊为心、半径 40 m 的圆上,整栋楼都在半径 40 m 以内,所以按大半径回转时吊物根本不在楼的正上方;真正把半径收到 8 m 时是沿固定方位角直线进来,这段才需要靠 6 m 的安全高度。
就位半径不是随手一个 11。 原来写 r = 11,配合 angSet = atan2(−6, 17)(指向楼中心的方向),反算世界坐标是 (−6.6, 2.4)——这是楼板正中。墙板往楼面中间落,然后转身假装它贴在幕墙线上,工序就说不通了。正确顺序是先定就位点:西侧幕墙在 x = −11,板厚加操作余量取 (−11.8, 0),再由它反解 rSet = 8.5、angSet = −49°。顺手验算了一遍堆场那侧:rPile = 40.4、angPile = 136°,用 world = 塔吊中心 + r·(cos ang, sin ang) 代回去精确落在 (−46, 34),说明 rotation.y = −ang 这个符号约定是对的——atan2(dz, dx) 配 −ang 才是标准逆时针方位角。
墙板会跟着臂一起转。 吊物是回转总成的子节点,回转角变了,墙板自身的朝向也跟着变,落位时它的板面方向跟幕墙线斜交。加一行 load.rotation.y = π/2 + angSet 把父级旋转抵消掉,板面就始终平行于西侧立面。这类"挂在动态父节点下"的朝向问题,用世界四元数反推很容易,用肉眼调则永远调不准。
单位盒的 fallback 写成 1,玻璃就变成了 1 米厚的墙。 幕墙实例化用的基准几何是 BoxGeometry(1, 1, 1),靠缩放得到四种板。原来的缩放是 sc.set(w === 0.1 ? 1 : k*w, k*p[1], w === 0.1 ? k*p[2] : 1)——薄的那一侧给了 0.1,另一根水平边却给了 1,于是每片幕墙在厚度方向都是实打实 1 m。透明材质让它看起来像一堵很厚的雾墙,而不是单元板块。修完是 0.1 : k*p[0] 和 k*p[2] : 0.1,两个方向各归各位。顺带删掉上一行那个 const w = p[0] === 0.1 ? p[0] : p[0]——一个恒等于自己的三元,看着像在做判断,其实什么都没做,正是它掩盖了后面用错轴的事实。
引用了一个根本不存在的 prop,程序不会告诉你。 机位切换周期我写的 props.cycle,但本组件只声明了 height / floors / liftSeconds。于是 (time % (props.cycle || 24)) || 24 < 0.5 ? 0 : 1 变成拿兜底值硬算,再加上外层 clamp01(布尔),k 永远只在 0 和 1 之间跳——每 12 秒甩一次镜,而不是我想要的渐变。现在改成用手上有的量推周期:liftSeconds × 6(六吊一切),k = seg(cp, 0.08, 0.3) − seg(cp, 0.55, 0.78)。这类"prop 名字写错但 TS 也救不了"的静默退化,只能靠读一次画面察觉。
五相工序的接缝必须首尾相接。 最后一相"脱钩回位"原来收在 hy = 14,而第一相"起钩离堆"从 hy = 2 起——每一轮过渡到下一轮,空钩都在半空跳一下。现在把第五相写成三段叠加:脱钩提起(0.84→0.9)→ 转回堆场上方(0.86→0.98)→ 落到构件顶(0.96→1),末尾正好等于第一相的起始标高。多段状态机里,每段末尾值等于下段起始值是免费的正确性检查,写的时候顺手验一下比事后调 bug 便宜。
阴影框盖不住塔身。 ±90 / top 130 / bottom −40 是按低角度仰拍时的构图给的,但塔身会奔着 130 m 去,30 层时塔尖早就出了阴影盒,越高的部分越没影子——楼顶上那片墙板永远是纯亮面。放宽到 ±150 并把 target 显式 scene.add 进来。另外小车挂在起重臂下弦(板底 2.55~3.05)才符合"小车跑在下弦、绳从车底垂下",原来给在 2.9 时它有一半嵌在臂里;绳的起点也跟着从臂中心 3.4 挪到小车底 2.55,末端落在吊钩中心而不是穿透到构件里面。
未施工的楼层不该留在天上。 楼板与幕墙都靠 k = clamp01(built - f) 驱动,正在成型的那一层从 0 长到 1。我给缩放留了个 Math.max(0.02, k) 的下限,本意是防零缩放算不出法线。结果 30 层里所有还没施工到的层,都以 5.6 mm 厚的薄片悬在各自高度上;这些薄片既 castShadow 又 receiveShadow,上下表面自阴影一染就是一摞黑色锯齿板。从全景机位看过去,楼像已经封顶了,而 HUD 写着已施工 3 层。真正的修法是把没施工到的层整段从渲染里剔掉:已施工层在实例索引上是连续前缀,所以 slabs.count = Math.min(floors, floor(built) + 1)、panels.count = slabs.count * 4 就够了,一行矩阵都不用碰。三角面读数从 4,238 掉到 1,300 上下,正好印证了之前有三分之二的面数在演一出不该存在的封顶。
按交付要求核对
| 要求 | 实际做到 | 判定 |
|---|---|---|
| 零外部素材 | 全几何 + 材质参数,两次实例化成楼 | 达成 |
| 真实量纲 | 层高 3.6、平面 22×16、墙板 3.6×2.9、臂长 45 m | 达成 |
| 标高与绳长分离 | 工序只写标高,唯一一处换算成绳长 | 达成 |
| 落钩命中作业面 | placeY = 作业面 + 0.35,随楼层同步抬升 | 达成 |
| 平移不刮楼体 | 安全高度 = 作业面 + 6 m,并核对回转轨迹在楼外 | 达成 |
| 就位点反解 | rSet / angSet 由西侧幕墙外目标点推出 | 达成 |
| 吊物朝向 | 抵消回转角,板面平行于安装立面 | 达成 |
| 幕墙板厚 | 两个水平轴各归各位,厚 0.1 m | 达成 |
| 工序接缝连续 | 第五相末尾标高 = 第一相起始标高 | 达成 |
| 机位无跳变 | 用已有参数推周期,smoothstep 渐变切换 | 达成 |
| 阴影覆盖 | ±150 框住 130 m 塔身与堆场 | 达成 |
| 未施工层不穿帮 | 用实例 count 剔除,不靠缩放下限 | 达成 |
| 文字读数无控件 | 五项读数含标高与绳长两项独立量 | 达成 |
| 卸载清理 | 几何、材质、控制器全部释放 | 达成 |
已知不足
- 一吊一层是简化。 真实装配楼层的墙板、楼板、楼梯段、阳台板要十几吊,这里把整层的活压成一吊,节奏好看但工序不真实。
- 塔吊没有爬模与附着。 塔身只是按比例放大加显隐支撑道,没有顶升套架、没有锚固、没有自由端高度限制,超高后真机早就不合规了。
- 构件不落位成墙。 脱钩时吊物直接消失,靠实例化的幕墙"接管"。两者位置对得上的时间窗口很短,仔细看会发现构件是凭空隐身的。
- 堆场是静态的。 42 件构件从第一轮到最后一轮一件没少,也没有按吊次递减,所以"材料够不够"这件事无法表达。
- 没有信号与风速。 真实塔吊有力矩限制器、防碰撞、风速超标停工,这台孪生不会拒绝任何超限动作。
- 核心筒只有一根方柱。 没有电梯井、没有剪力墙分片,跟装配式外檐的配合关系看不出来。
- 后台标签页里一轮 210 秒会走得非常慢。 帧间时间截在 0.05 秒是为了防卡顿后镜头和工序跳变,代价就是浏览器降频后整轮被拉长。
本站所有 3D 孪生场景、可视化系统均为个人技术实验 Demo,仅用于技术学习与效果展示,不属于对外运营平台,不提供任何商业服务。