序
手机里的群越来越多,真正需要看的消息倒不一定多。一个通知转发三遍,再跟上几十个"收到",原消息就找不到了。大家都很积极,手机也很忙,只有事情本身还在原地。
有时候觉得,静音功能实在是个好发明。当然,该看的通知还是得看,不然第二天就要解释为什么没收到。
上篇把烟感和房间对应起来了。这篇继续往下做:二楼东侧走廊在演练中封闭,原来画好的路线要撤掉,再看能不能从另一侧楼梯出去。
一、先把能走的地方连起来
路线沿门口、转角和楼梯口连接,别从办公桌中间穿过去。配图是 AI 生成的三维场景设计图,坐标属于本次教学案例;实际通行关系应来自核对过的平面资料和预案。
jsconst nodes = new Map([ ['room', [2, 6, -3]], ['hall', [2, 6, 0]], ['east', [7, 6, 0]], ['west', [-5, 6, 0]], ['exitE', [7, 6, 4]], ['exitW', [-5, 6, 4]] ]); const links = [ ['door', 'room', 'hall'], ['eastHall', 'hall', 'east'], ['westHall', 'hall', 'west'], ['eastStair', 'east', 'exitE'], ['westStair', 'west', 'exitW'] ].map(([id, a, b]) => ({ id, a, b, cost: new THREE.Vector3(...nodes.get(a)) .distanceTo(new THREE.Vector3(...nodes.get(b))) })); const exits = new Set(['exitE', 'exitW']);节点少的时候,这张表就能把位置关系交代清楚。路径 ID 单独保存,后面封闭的是哪一段,也就不用从一串坐标里猜。
二、东侧封闭以后,重新计算
这次用 Dijkstra 查找可到达的预案出口。封闭边不参与计算;两个出口都不可达,就返回空值,不能继续亮着上一条路线。
jsfunction findExit(start, closed) { if (!nodes.has(start)) return null; const dist = new Map([[start, 0]]); const previous = new Map(); const pending = new Set([start]); while (pending.size) { const current = [...pending].reduce((a, b) => dist.get(a) < dist.get(b) ? a : b); pending.delete(current); if (exits.has(current)) { const path = [current]; while (previous.has(path[0])) path.unshift(previous.get(path[0])); return path; } for (const edge of links) { if (closed.has(edge.id)) continue; const next = edge.a === current ? edge.b : edge.b === current ? edge.a : null; if (!next) continue; const candidate = dist.get(current) + edge.cost; if (candidate >= (dist.get(next) ?? Infinity)) continue; dist.set(next, candidate); previous.set(next, current); pending.add(next); } } return null; } const closed = new Set(['eastHall']); const path = findExit('room', closed);小场景这样写够用了。节点上万时再换优先队列,并把路径计算移到 Worker,别让计算和鼠标拖动抢主线程。
三、把计算结果放回三维
画线时直接使用返回的节点序列。转角保持折线,不做穿墙的圆滑插值;整条路线稍微抬高一点,避免和地面闪烁。
jslet routeLine = null; function drawRoute(path) { if (routeLine) { routeLine.removeFromParent(); routeLine.geometry.dispose(); routeLine.material.dispose(); routeLine = null; } if (!path) return; const points = path.map(id => { const point = new THREE.Vector3(...nodes.get(id)); point.y += 0.08; return point; }); routeLine = new THREE.Line( new THREE.BufferGeometry().setFromPoints(points), new THREE.LineBasicMaterial({ color: 0x64b08c }) ); scene.add(routeLine); } drawRoute(path);图里的楼梯和房门是参照物。界面除了显示路线,还应保留当前所在楼层、目的出口和本次通道状态的版本。
四、通道状态变了,再走一遍
服务端推来的状态按版本更新。下面每次先撤旧线,再计算新线,避免出现新告警到了、旧路线还在的情况。
notice对应页面中用于显示提示的一块文字区域。
jslet routeVersion = -1; function onPassageState(message, notice) { if (message.mode !== 'drill') return; if (!Number.isInteger(message.version)) return; if (message.version <= routeVersion) return; if (!Array.isArray(message.closedIds)) return; routeVersion = message.version; drawRoute(null); const blocked = new Set(message.closedIds); const nextPath = findExit(message.personNode, blocked); drawRoute(nextPath); notice.textContent = nextPath ? `演练路线 V${routeVersion} → ${nextPath.at(-1)}` : '当前无可用演练路线,等待人工确认'; return nextPath; }最后试三种情况就很直观:只封东侧、只封西侧、两侧都封。路线应当分别改道、改道、清空。到这里,三维里画的线才跟演练状态连上了。
下一篇把报警、封闭和改道按时间记下来,做一个能暂停、能回退的演练复盘。
技术交流 1203193731@qq.com
交流微信:yeyunfeigc
上篇: 第一课:场景与火情定位
**下篇:**第三课:事件联动与演练复盘
如何使用 three.js 实现 3D 消防疏散,走廊封了怎么改道——第二课|WebGL
魂断蓝桥6662026-11-14 10:11