如何用 three.js 把 3D 消防演练落到一栋楼里——第一课:场景搭建与火情定位

本文是"3D 消防演练落地"连载第一篇。文章和动图使用我新搭的 WebGL 演示场景;楼层、设备编号和事件是虚构案例,不是实际现场录屏。下一篇接着做人员疏散与动态改道。

一、项目背景

前面写 3D 消防,更多是在讲园区、楼层和设备怎么展示。有朋友说,消防演练不就是在楼里放一团火、再拉一条逃生线吗?真要这么省事,我的头发应该还能多留几年。

火焰粒子调大一点不难。难的是点击"起火"以后,系统知道是哪栋楼、哪层、哪个房间;烟感、消火栓和出口也得在同一套坐标里。要是模型里着火的是 203,右侧告警却写着 302,画面越炫,值班员越犯嘀咕。

我把这次的演练场景缩到一栋三层办公楼:二楼东侧模拟起火,烟感触发,值班员确认后进入演练,随后引导二楼人员撤离。范围小一点,问题反而容易看清楚。闲话少叙,先把第一步做起来。

上图是这次自己搭的 three.js 演示场景,不是项目现场录屏。三层楼体用了半透明外墙,二楼放了办公桌、烟感、消火栓、出口和人员点位。触发模拟火情后,右侧面板跟着变成"R203 火情定位"。效果看着只是亮了一个点,背后真正要先做好的,是设备与房间的绑定。

二、本节要解决什么

  1. 园区、建筑、楼层、房间能逐级进入,镜头切换时不丢位置。
  2. 火点、烟感、消火栓、疏散出口都绑定稳定的业务 ID。
  3. 演练火情与真实报警明确区分,界面上始终标明"模拟"。
  4. 场景能复位,同一个脚本可以反复演练。

技术选型仍然是浏览器中的 three.js。建筑外壳和常见房间可以用简单几何体搭出来,复杂设备再按需加载模型。建模方式不是越统一越好,先看访问频率和加载成本。

三、先统一坐标与设备台账

这里有个很容易忽略的坑:模型里的 x/y/z 是渲染坐标,设备台账里通常写"2F 东侧走廊烟感"。两份数据如果靠名称模糊匹配,后面修改模型时很容易串位。我的做法是在导入时生成一张点位表,每个可交互对象都有唯一 ID,模型只保存这个 ID。

js 复制代码
const pointTable = {
  "F2-R203": { type: "room", floor: 2, position: [18, 6, -4] },
  "F2-SD03": { type: "smokeDetector", floor: 2, position: [20, 8, -3] },
  "F2-H02": { type: "hydrant", floor: 2, position: [13, 6, -6] },
  "F2-EXIT-E": { type: "exit", floor: 2, position: [28, 6, -2] }
};

function getPoint(id) {
  const point = pointTable[id];
  if (!point) throw new Error(`未知点位:${id}`);
  return point;
}

一旦确定了坐标单位和楼层高度,后面的火点、人员、路径全部引用这张表。建筑模型替换时,只需要重新校准点位,不用把每个演练脚本都改一遍。这里的编号是示例,落到真实项目中应当对接建筑平面图和设备台账,由现场人员核对。

四、把一次火情当作事件,而不是一团粒子

有了点位,下一步才是触发模拟火情。事件里保存演练 ID、起火点、关联探测器和触发时间;画面层只负责根据事件呈现红色高亮与烟雾。这样切换视角或重新打开页面时,事件本身不会消失。

js 复制代码
const drillFire = {
  drillId: "demo-001",
  mode: "simulation",
  eventId: "fire-001",
  ignitionPointId: "F2-R203",
  detectorIds: ["F2-SD03"],
  startedAt: new Date().toISOString()
};

function showDrillFire(scene, event) {
  const point = getPoint(event.ignitionPointId);
  const marker = new THREE.Mesh(
    new THREE.SphereGeometry(0.45, 16, 12),
    new THREE.MeshBasicMaterial({ color: 0xff5c36 })
  );
  marker.name = `drill:${event.drillId}:${event.eventId}`;
  marker.position.set(...point.position);
  scene.add(marker);
  return marker;
}

function clearDrillFire(scene, event) {
  const marker = scene.getObjectByName(`drill:${event.drillId}:${event.eventId}`);
  if (!marker) return;
  scene.remove(marker);
  marker.geometry.dispose();
  marker.material.dispose();
}

这段代码只实现了最简单的火点标记。烟雾、光效、镜头飞行可以在此基础上叠加,但我建议先把"触发、定位、清除"跑通。演练结束必须能清理对象和计时器,否则第二次演练会出现两团烟、两条报警记录。

五、现场落地还要补的三件事

第一,楼层坐标要校准。模型中的房间边界与实际门洞、楼梯口、疏散指示牌如果错位,路线展示会误导人。第二,演练账号与真实消防联动要隔离,模拟事件只能进入演练通道,不能控制真实设备。第三,给值班员留一个人工确认步骤:探测器报警之后,演练流程可以自动推进,真实火警处置则必须遵循现场预案。

这节课做到这里,我们已有一张能被业务数据准确点亮的三维楼层。下一篇继续做最关键的一步:东侧走廊被烟封住时,原来的逃生路线怎么停用,人员又该往哪里走。

**下篇:**第二课:人员疏散与动态改道

相关推荐
莪_幻尘16 小时前
4 个 3D 游戏,总体积 3.82MB:拆解 Claude 的 one-shot 工程链路
前端·ai编程·three.js
总要学点什么2 天前
第96篇:三维数据加载-城市建筑白模样式+着色器——让色带按体量上色,最白的那档却涂不上去
前端·gis·webgl·cesium·着色器·空间分析·白模
誰在花里胡哨2 天前
Qoder + Blender MCP:对话建模到网页渲染
ai编程·three.js·mcp
总要学点什么3 天前
第97篇:三维数据加载-城市建筑白模自定义着色器——让一条光带沿高度扫上去,快慢却由帧率说了算
前端·gis·webgl·cesium·着色器·空间分析·白模
总要学点什么3 天前
第98篇:三维特效-材质线效果——给一条线换十种材质,虚线的图案只填了一半
开发语言·前端·javascript·ecmascript·gis·webgl·材质
Chen7327 天前
WebGPU 性能优化:Three.js 千万级点云调度与渲染实践
性能优化·three.js
柳杉7 天前
没有写一行代码:我用 Codex 制作 了一个 3D 智慧展厅可视化大屏
前端·ai编程·three.js
只睡四小时7 天前
Three.js Sub-tick:漏判 60.7%→0
开发语言·javascript·性能优化·ecmascript·游戏开发·three.js·碰撞检测
小和尚同志10 天前
54.6k star 的 Multica: 集多 Agent 和看板于一体的 AI 平台
人工智能·aigc·three.js