本文是"3D 消防演练落地"连载第一篇。文章和动图使用我新搭的 WebGL 演示场景;楼层、设备编号和事件是虚构案例,不是实际现场录屏。下一篇接着做人员疏散与动态改道。
一、项目背景
前面写 3D 消防,更多是在讲园区、楼层和设备怎么展示。有朋友说,消防演练不就是在楼里放一团火、再拉一条逃生线吗?真要这么省事,我的头发应该还能多留几年。
火焰粒子调大一点不难。难的是点击"起火"以后,系统知道是哪栋楼、哪层、哪个房间;烟感、消火栓和出口也得在同一套坐标里。要是模型里着火的是 203,右侧告警却写着 302,画面越炫,值班员越犯嘀咕。
我把这次的演练场景缩到一栋三层办公楼:二楼东侧模拟起火,烟感触发,值班员确认后进入演练,随后引导二楼人员撤离。范围小一点,问题反而容易看清楚。闲话少叙,先把第一步做起来。
上图是这次自己搭的 three.js 演示场景,不是项目现场录屏。三层楼体用了半透明外墙,二楼放了办公桌、烟感、消火栓、出口和人员点位。触发模拟火情后,右侧面板跟着变成"R203 火情定位"。效果看着只是亮了一个点,背后真正要先做好的,是设备与房间的绑定。
二、本节要解决什么
- 园区、建筑、楼层、房间能逐级进入,镜头切换时不丢位置。
- 火点、烟感、消火栓、疏散出口都绑定稳定的业务 ID。
- 演练火情与真实报警明确区分,界面上始终标明"模拟"。
- 场景能复位,同一个脚本可以反复演练。
技术选型仍然是浏览器中的 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();
}
这段代码只实现了最简单的火点标记。烟雾、光效、镜头飞行可以在此基础上叠加,但我建议先把"触发、定位、清除"跑通。演练结束必须能清理对象和计时器,否则第二次演练会出现两团烟、两条报警记录。
五、现场落地还要补的三件事
第一,楼层坐标要校准。模型中的房间边界与实际门洞、楼梯口、疏散指示牌如果错位,路线展示会误导人。第二,演练账号与真实消防联动要隔离,模拟事件只能进入演练通道,不能控制真实设备。第三,给值班员留一个人工确认步骤:探测器报警之后,演练流程可以自动推进,真实火警处置则必须遵循现场预案。
这节课做到这里,我们已有一张能被业务数据准确点亮的三维楼层。下一篇继续做最关键的一步:东侧走廊被烟封住时,原来的逃生路线怎么停用,人员又该往哪里走。
**下篇:**第二课:人员疏散与动态改道