@tmesoft/lab-scene
v2.1.0
Published
a lab scene
Keywords
Readme
说明
基于 Three.js 的 3D 实验室场景编辑器。核心是 LabScene 类:加载实验室与设备 GLB 模型,提供拖拽/变换、后处理描边、模型动画控制,以及场景状态的 JSON 序列化。
扩展模块:ZoneManager(区域人数可视化) —— 在实验室地面创建监测区域、对接传感器实时显示人数分布。
一、基本使用
import LabScene from '@/js/LabScene'
import request from '@/utils/request'
const app = document.querySelector('#app')
// 构造函数:第一参为容器节点,第二参为可选配置
const labScene = new LabScene(app, {
// 禁用后处理(描边、抗锯齿等),不需要时可禁用以提升性能。默认 false
disablePostProcessing: false,
// 自动更新(requestAnimationFrame 循环)。无模型动画时可关闭,自行控制更新时机。默认 true
autoUpdate: true,
// 禁用模型点击交互。不需要选中模型时可禁用。默认 false
disableClickModel: false
})
// 请求数据
const v = Date.now()
Promise.all([
request.get(`glb/config.json?v=${v}`), // 模型列表数据
request.get(`data.json?v=${v}`) // 上一次保存的场景数据
]).then(([modelList, jsonData]) => {
// 初始化列表(必须先调用,后续 initLab / setJSON / addModel 都依赖它)
labScene.initList(modelList)
// 有保存的数据则还原,否则加载第一个实验室
if (jsonData) {
labScene.setJSON(jsonData)
} else {
labScene.initLab(modelList.labs[0])
}
})
// 监听焦点(选中模型)变化
labScene.addEventListener('focusChange', e => {
console.log('焦点变化', e.model)
})构造参数 options
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| disablePostProcessing | boolean | false | 禁用后处理(OutlinePass 描边、SMAA 抗锯齿) |
| autoUpdate | boolean | true | 自动渲染循环。关闭后需手动调 update() 触发重绘 |
| disableClickModel | boolean | false | 禁用模型点击/拖拽交互(仍可缩放相机) |
二、事件
基于 Three.js 的 EventDispatcher,用 addEventListener 监听:
| 事件 | 触发时机 | 回调参数 |
|---|---|---|
| focusChange | 焦点(选中模型)变化 | { model },取消选中时 model 为 undefined |
| hoveron | 鼠标移入模型 | { model } |
| hoveroff | 鼠标移出模型 | { model } |
| pointerdown | 在模型上按下 | { model } |
| pointerup | 在模型上抬起 | { model } |
| click | 在模型上点击(按下到抬起 ≤ 200ms) | { model } |
// 监听选中模型变化(点击切换焦点,再次点击同一模型取消选中)
labScene.addEventListener('focusChange', e => {
console.log('焦点变化', e.model)
})三、属性
// 相机
labScene.camera
// 渲染器
labScene.renderer
// 轨道控制器(默认仅开启缩放)
labScene.orbitControl
// 变换控制器(平移、旋转)
labScene.transformControl
// 后处理合成器(未禁用后处理时存在)
labScene.composer
// 描边通道(未禁用后处理时存在)
labScene.outlinePass
// 实验室列表 object(按 id 索引),initList 后填充
labScene.labList
// 设备列表 object(按 id 索引),initList 后填充
labScene.equipmentList
// 当前实验室模型对象,未初始化时为 undefined
labScene.lab
// 当前可交互的设备模型数组(含实验室自带的门窗等)
labScene.equipments
// Boolean,默认 true,是否允许对设备进行平移、旋转
labScene.enabled
// 当前焦点设备模型,没有则为 undefined
labScene.focus
// 焦点模型绑定的业务数据(仅独立设备有;实验室设备的数据在 lab.equipments[key].data)
labScene.focus.data
// 绘制延迟时间,控制帧率(低性能设备用),单位 ms,默认 16(约 60fps)
// 性能差时可设为 33(约 30fps)或 66(约 15fps)
labScene.delayTime = 16
// 同时下载设备模型的最大并发数,默认 2
labScene.limitDownloadTasks关于模型绑定数据:统一通过
model.data读写。注意data是一个对象,不要直接替换data引用——实验室自带设备的data是引用数据,getJSON时从labEquipmentsData读取,修改引用对象不会反映到导出结果。
// 控制场景灯光:0(全黑)~ 1(正常),开灯 1,关灯 0.4
labScene.renderer.toneMappingExposure = 1四、方法
初始化与数据
// 初始化实验室/设备列表,必须在 initLab / setJSON / addModel 之前调用
// modelList 为 config.json 的内容
labScene.initList(modelList)
// 用 JSON 数据还原整个场景(实验室 + 设备)
// jsonData 为 getJSON 的返回值(字符串或对象均可)
// onLoad / onProgress(0~100) / onError 为可选回调
labScene.setJSON(jsonData, onLoad, onProgress, onError)
// 返回当前场景的 JSON 数据(字符串类型,可直接保存到后端)
const jsonStr = labScene.getJSON()实验室与设备
// 单独初始化(切换)实验室模型,可传 id 或列表中的实验室数据
// 会自动清除当前实验室
labScene.initLab(typeId) // 传 id
labScene.initLab(labScene.labList[typeId]) // 传对象
// 完整签名:labScene.initLab(labConfig, onLoad, onProgress, onError)
// 单独导入设备模型到场景,可传 id 或列表中的设备数据
// 同一设备可多次添加(通过 config.list 指定多份)
labScene.addModel(typeId)
labScene.addModel(labScene.equipmentList[typeId])
// 完整签名:labScene.addModel(config, onLoad, onProgress, onError)
// config 为对象时支持 { id, list: [{ name, position, rotation, scale, data }] }
// 删除场景内的模型,可传单个模型或数组
// 删除实验室传 labScene.lab(其自带设备会一并删除)
// 删除独立设备传 labScene.equipments 中的某项(不影响实验室自带设备)
labScene.removeModels(model)
labScene.removeModels([model1, model2])渲染与生命周期
// 手动触发一次绘制更新
// autoUpdate 关闭时,模型增删/动画/尺寸变化后需要手动调用
labScene.update()
// 销毁实例,释放资源(控制器、渲染器、事件、动画帧等)
labScene.destroy()外部扩展点
// 注册逐帧更新钩子,fn 会收到 update 内部计算的 mixerDelta(单位秒)
// 供 ZoneManager 等伴随模块接入逐帧动画,不要在回调里做重活(处于渲染关键路径)
labScene.addUpdateHook(fn)
// 注销逐帧更新钩子
labScene.removeUpdateHook(fn)五、模型动画控制
模型动画来自 GLB 文件内部,config.json 中通过 controls 声明每个动画的播放参数(name 对应 GLB 中 AnimationClip 的名称)。LabScene 会为每个声明的动画挂载 play 方法。
// play(immediate?, progress?)
// immediate Boolean,可选,立即跳到动画结果,无过渡动画
// progress Number 0~1,可选,设置动画播放到指定进度
// 生效条件:action.loop = LoopOnce 且 clampWhenFinished = true实验室模型动画(门窗、窗帘)
实验室自带设备(窗户、窗帘、门等)挂在 lab.equipments 上,key 对应 config.json 中实验室 equipments 的字段名。动画控制名:on_window / off_window / on_curtain / off_curtain。
// 实际使用需遍历 lab.equipments,根据 data 值匹配对应的设备再控制
labScene.lab.equipments['window1'].controls.on_window.play() // 开窗
labScene.lab.equipments['window1'].controls.off_window.play() // 关窗
labScene.lab.equipments['window1'].controls.on_curtain.play() // 拉开窗帘
labScene.lab.equipments['window1'].controls.off_curtain.play() // 关上窗帘
// 立即关窗(无过渡)
labScene.lab.equipments['window1'].controls.off_window.play(true)
// 窗户打开到 50% 进度
labScene.lab.equipments['window1'].controls.on_window.play(false, 0.5)设备模型动画
独立设备动画挂在 equipmentList[typeId].models 数组的每个模型上。动画控制名:on / off。
// models 是数组,需按 name 遍历查找目标模型
const models = labScene.equipmentList['KongTiao'].models
const target = models.find(m => m.name === 'KongTiao')
target.controls.on.play() // 开启
target.controls.off.play() // 关闭六、数据格式
config.json(模型清单)
由后端返回,initList 时加载。结构示例:
{
"labs": [
{
"id": "Lab1",
"name": "实验室三窗两门",
"url": "glb/lab/Lab1.glb",
"config": {
"wall": { "xMin": 0.25, "xMax": 0.25, "yMin": 0.1, "yMax": 0, "zMin": 0.35, "zMax": 0.3 },
"rotation": { "x": 0, "y": 0, "z": 0 },
"scale": { "x": 0.01, "y": 0.01, "z": 0.01 }
},
"equipments": { // 实验室自带的设备(窗户/窗帘/门等)
"window1": {
"type": "window",
"name": "窗户窗帘",
"enable": true, // 是否启用该设备
"group": ["Lab1_ChuangLian01", "Lab1_Window01"], // GLB 中对应的节点名
"controls": {
"on_window": { "name": "ChuangHu1_DaKai", "only": false, "loop": false, "timeScale": 1, "clampWhenFinished": true },
"off_window": { "name": "ChuangHu1_DaKai", "only": false, "loop": false, "timeScale": -1, "clampWhenFinished": true }
}
}
}
}
],
"equipments": [
{
"id": "KongTiao",
"name": "空调",
"url": "glb/equipment/KongTiao.glb",
"config": { "position": { "x": 0, "y": 0, "z": 0 }, "scale": { "x": 0.01, "y": 0.01, "z": 0.01 } },
"controls": {
"on": { "name": "KaiQi", "loop": false, "timeScale": 1, "clampWhenFinished": true },
"off": { "name": "KaiQi", "loop": false, "timeScale": -1, "clampWhenFinished": true }
}
}
]
}controls 中每项的字段含义:
| 字段 | 类型 | 说明 |
|---|---|---|
| name | string | GLB 中 AnimationClip 的名称 |
| only | boolean | 播放时是否停止该模型其它动画 |
| loop | boolean | 是否循环(LoopRepeat / LoopOnce) |
| timeScale | number | 时间比例,默认 1;-1 倒放;0 暂停 |
| clampWhenFinished | boolean | 单次播放后是否停在最后一帧(immediate / progress 依赖此选项生效) |
场景数据(getJSON / setJSON)
getJSON() 返回字符串,setJSON() 接受字符串或对象。结构示例:
{
"lab": {
"id": "Lab2",
"name": "实验室三窗两门",
"equipments": { /* 实验室自带设备的绑定数据,key 与 config.json 一致 */ }
},
"equipments": [
{
"id": "KongTiao",
"list": [
{
"name": "空调-1",
"position": { "x": 0, "z": -3 }, // 只保存平移 x、z
"rotation": { "x": 0, "y": 0, "z": 0 },
"data": { /* 设备绑定的业务数据 */ }
}
]
}
]
}