agentworkshop 0.7.41 → 0.7.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.AgentWorkShop/prompts/host-tools.json +265 -6
- package/.AgentWorkShop/prompts/lead-supervise.md +54 -29
- package/.AgentWorkShop/prompts/peer-reply-optional.md +6 -2
- package/.AgentWorkShop/prompts/peer-reply-required.md +8 -7
- package/.output/.AgentWorkShop/aml-python/amlkit.py +185 -120
- package/.output/.AgentWorkShop/aml-python/requirements.txt +9 -7
- package/.output/.AgentWorkShop/aml-python/train-hybrid-example.py +87 -0
- package/.output/.AgentWorkShop/prompts/host-tools.json +265 -6
- package/.output/.AgentWorkShop/prompts/lead-supervise.md +54 -29
- package/.output/.AgentWorkShop/prompts/peer-reply-optional.md +6 -2
- package/.output/.AgentWorkShop/prompts/peer-reply-required.md +8 -7
- package/.output/.AgentWorkShop/schema.json +12 -0
- package/.output/nitro.json +1 -1
- package/.output/public/_nuxt/-hB4kEmt.js +1 -0
- package/.output/public/_nuxt/1g9IT9Qh2.js +1 -0
- package/.output/public/_nuxt/74x7BOWv2.js +1 -0
- package/.output/public/_nuxt/9c5GOgbu.js +1 -0
- package/.output/public/_nuxt/9zrLAN2o2.js +1 -0
- package/.output/public/_nuxt/B1RT9NTO2.js +1 -0
- package/.output/public/_nuxt/B2x8SKRO.js +1 -0
- package/.output/public/_nuxt/B6Olw0Op.js +1 -0
- package/.output/public/_nuxt/B7jlMWsz.js +1 -0
- package/.output/public/_nuxt/B8cnjDtt.js +1 -0
- package/.output/public/_nuxt/B9rIcCX12.js +1 -0
- package/.output/public/_nuxt/BACBBVdw.js +1 -0
- package/.output/public/_nuxt/BBhLVYdM.js +1 -0
- package/.output/public/_nuxt/BDmNbZ252.js +1 -0
- package/.output/public/_nuxt/BFVf8HLR.js +1 -0
- package/.output/public/_nuxt/BGKUW7ra.js +1 -0
- package/.output/public/_nuxt/BH3obZJ42.js +1 -0
- package/.output/public/_nuxt/BHwG4yNs.js +1 -0
- package/.output/public/_nuxt/BIOq3xqe2.js +1 -0
- package/.output/public/_nuxt/BITZvQBK.js +1 -0
- package/.output/public/_nuxt/BL_dbd2v2.js +1 -0
- package/.output/public/_nuxt/BMMoqi94.js +1 -0
- package/.output/public/_nuxt/BNnnYEYh2.js +1 -0
- package/.output/public/_nuxt/BQXCMsXk.js +1 -0
- package/.output/public/_nuxt/BS9P_Ies2.js +1 -0
- package/.output/public/_nuxt/BWU1pvLL.js +1 -0
- package/.output/public/_nuxt/B_9_BM9G.js +3 -0
- package/.output/public/_nuxt/B_LcD7qE2.js +1 -0
- package/.output/public/_nuxt/BcQXL8pb.js +1 -0
- package/.output/public/_nuxt/BdM_Qr752.js +1 -0
- package/.output/public/_nuxt/BiOSWsjD.js +2 -0
- package/.output/public/_nuxt/BkSViqQd.js +1 -0
- package/.output/public/_nuxt/Bl5dYv4m.js +1 -0
- package/.output/public/_nuxt/BmiMC_BC.js +2 -0
- package/.output/public/_nuxt/BnpY_zNx.js +1 -0
- package/.output/public/_nuxt/BqYVLAcm.js +1 -0
- package/.output/public/_nuxt/Br3AySNQ.js +1 -0
- package/.output/public/_nuxt/Br3MggOL.js +1 -0
- package/.output/public/_nuxt/BrRX-l9B2.js +4108 -0
- package/.output/public/_nuxt/Bt2ZL5332.js +1 -0
- package/.output/public/_nuxt/Bub2P5me.js +28 -0
- package/.output/public/_nuxt/BvRk9kiK.js +0 -0
- package/.output/public/_nuxt/BvRk9kiK2.js +0 -0
- package/.output/public/_nuxt/Bwc6lt2X.js +3 -0
- package/.output/public/_nuxt/BxHBIVAg.js +1 -0
- package/.output/public/_nuxt/BxYTM0hb.js +1 -0
- package/.output/public/_nuxt/ByMj6e7s.js +3 -0
- package/.output/public/_nuxt/BzvHUfRR2.js +1 -0
- package/.output/public/_nuxt/C2YYOgMe.js +42 -0
- package/.output/public/_nuxt/C3twEwTh2.js +1 -0
- package/.output/public/_nuxt/C4WhK2wq.js +1 -0
- package/.output/public/_nuxt/C50LmoI1.js +0 -0
- package/.output/public/_nuxt/CAaSPBqk.js +1 -0
- package/.output/public/_nuxt/CBDX7p73.js +1 -0
- package/.output/public/_nuxt/CD9lpEz22.js +13 -0
- package/.output/public/_nuxt/CGZVhYJ4.js +1 -0
- package/.output/public/_nuxt/CJRDL4EG.js +1 -0
- package/.output/public/_nuxt/CKQPwwZa2.js +1 -0
- package/.output/public/_nuxt/CLySL7mW.js +75 -0
- package/.output/public/_nuxt/CMXcOb6W.js +1 -0
- package/.output/public/_nuxt/CN1CDAwe.js +1 -0
- package/.output/public/_nuxt/CP0I2l-6.js +1 -0
- package/.output/public/_nuxt/CP0Nep9S.js +1 -0
- package/.output/public/_nuxt/CPeZNE4S2.js +1 -0
- package/.output/public/_nuxt/CPmNRTBk2.js +21 -0
- package/.output/public/_nuxt/CQabB0oy.js +1 -0
- package/.output/public/_nuxt/CT-vTjcG.js +1 -0
- package/.output/public/_nuxt/CTVfcnjU.js +3 -0
- package/.output/public/_nuxt/CWzp2R6G.js +1 -0
- package/.output/public/_nuxt/C_VAXV7I2.js +7 -0
- package/.output/public/_nuxt/CavpkQb0.js +1 -0
- package/.output/public/_nuxt/CeWnOF3O.js +1 -0
- package/.output/public/_nuxt/CfLUvgr32.js +1 -0
- package/.output/public/_nuxt/Ch1P2mIf.js +1 -0
- package/.output/public/_nuxt/ChoLwR-s.js +1 -0
- package/.output/public/_nuxt/Chphw5IA2.js +1 -0
- package/.output/public/_nuxt/ChtjzEpK2.js +19 -0
- package/.output/public/_nuxt/CiTQY_n32.js +1 -0
- package/.output/public/_nuxt/Ck5oMvUk.js +1 -0
- package/.output/public/_nuxt/Ckafrjnw2.js +1 -0
- package/.output/public/_nuxt/Cm_PTn9t2.js +1 -0
- package/.output/public/_nuxt/Cmp8PMrA.js +1 -0
- package/.output/public/_nuxt/Cnrmlixo.js +3 -0
- package/.output/public/_nuxt/Cr518Gh02.js +1 -0
- package/.output/public/_nuxt/Crbir9cT.js +1 -0
- package/.output/public/_nuxt/CtPNnATm2.js +31 -0
- package/.output/public/_nuxt/Cu8c3oMN2.js +1 -0
- package/.output/public/_nuxt/CwLQAKay2.js +1 -0
- package/.output/public/_nuxt/CzlUIbK32.js +17 -0
- package/.output/public/_nuxt/D6lZftfy.js +1 -0
- package/.output/public/_nuxt/D83nVQPl.js +1 -0
- package/.output/public/_nuxt/D89BUtx6.js +1 -0
- package/.output/public/_nuxt/DCtk3qqr.js +1 -0
- package/.output/public/_nuxt/DD6Z0yPN2.js +1 -0
- package/.output/public/_nuxt/DGw8juu_.js +2 -0
- package/.output/public/_nuxt/DHSDk8Bq2.js +4 -0
- package/.output/public/_nuxt/DJdDFnjG2.js +1 -0
- package/.output/public/_nuxt/DJyNDnag.js +1 -0
- package/.output/public/_nuxt/DK3Fl9T5.js +1 -0
- package/.output/public/_nuxt/DLPuRMeG.js +19 -0
- package/.output/public/_nuxt/DMYYS5sK.js +1 -0
- package/.output/public/_nuxt/DNno23Jf2.js +1 -0
- package/.output/public/_nuxt/DNoEoo8t.js +1 -0
- package/.output/public/_nuxt/DPUXbTg92.js +1 -0
- package/.output/public/_nuxt/DRpFsrno.js +1 -0
- package/.output/public/_nuxt/DXIaadqv.js +1 -0
- package/.output/public/_nuxt/D_7AzAWe.js +1 -0
- package/.output/public/_nuxt/Dbe9TFc9.js +1 -0
- package/.output/public/_nuxt/DdNujZN5.js +1 -0
- package/.output/public/_nuxt/DdvQ7LRI.js +14 -0
- package/.output/public/_nuxt/DhFv8S2Z.js +1 -0
- package/.output/public/_nuxt/DkJVtqfc.js +1 -0
- package/.output/public/_nuxt/DkLCjrCk2.js +47 -0
- package/.output/public/_nuxt/DkpmSnHX.js +1 -0
- package/.output/public/_nuxt/DlUQa0wn2.js +1 -0
- package/.output/public/_nuxt/DoKpLEqc.js +1 -0
- package/.output/public/_nuxt/DpqA8D8_.js +1 -0
- package/.output/public/_nuxt/Dq0emNZi2.js +1 -0
- package/.output/public/_nuxt/DqtgGFEc.js +1 -0
- package/.output/public/_nuxt/DqtnqNgL2.js +1 -0
- package/.output/public/_nuxt/Dqz5HiXa.js +2 -0
- package/.output/public/_nuxt/Du9v6qGB.js +1 -0
- package/.output/public/_nuxt/DxagrjB8.js +1 -0
- package/.output/public/_nuxt/Dy3oXKdn2.js +1 -0
- package/.output/public/_nuxt/DzBbYWrw2.js +1 -0
- package/.output/public/_nuxt/EhhQmlBV2.js +1 -0
- package/.output/public/_nuxt/H2rNs1O82.js +1 -0
- package/.output/public/_nuxt/H6pPjo0l.js +1 -0
- package/.output/public/_nuxt/JAM4aNC_.js +1 -0
- package/.output/public/_nuxt/JgheoP3e.js +1 -0
- package/.output/public/_nuxt/LwIFGLgF.js +1 -0
- package/.output/public/_nuxt/Nw_6OpQZ2.js +1 -0
- package/.output/public/_nuxt/OXtV_ukR.js +1 -0
- package/.output/public/_nuxt/OmpTerminalPanel.ComMDrA8.css +1 -0
- package/.output/public/_nuxt/PLGqdko0.js +1 -0
- package/.output/public/_nuxt/PixDfCSm.js +1 -0
- package/.output/public/_nuxt/RAqsw632.js +1 -0
- package/.output/public/_nuxt/Sjjmp5OD2.js +1 -0
- package/.output/public/_nuxt/TOWR4WpO.js +1 -0
- package/.output/public/_nuxt/TownView.BvlfuLtP.css +1 -0
- package/.output/public/_nuxt/U8oQxruK.js +1 -0
- package/.output/public/_nuxt/WleoCtUX.js +1 -0
- package/.output/public/_nuxt/YaroNkJZ2.js +1 -0
- package/.output/public/_nuxt/Z-Z6EhXs.js +1 -0
- package/.output/public/_nuxt/ZuQtOnfI.js +1 -0
- package/.output/public/_nuxt/ZvXovq7N2.js +1 -0
- package/.output/public/_nuxt/_id_.BTrFUIHX.css +1 -0
- package/.output/public/_nuxt/_id_.Bossmhv1.css +1 -0
- package/.output/public/_nuxt/_wsId_.D-u-dbP9.css +1 -0
- package/.output/public/_nuxt/agents.Cgm_JG9L.css +1 -0
- package/.output/public/_nuxt/aml.Bi5X6_oi.css +1 -0
- package/.output/public/_nuxt/bg2adfck.js +1 -0
- package/.output/public/_nuxt/bt__BP-I.js +13 -0
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/7827fe9f-b4c2-461f-b1fb-c0c7da7d66b8.json +1 -0
- package/.output/public/_nuxt/cBI0JqsV2.js +209 -0
- package/.output/public/_nuxt/cDcDztgb2.js +1 -0
- package/.output/public/_nuxt/cGSl2GZp.js +1 -0
- package/.output/public/_nuxt/channel-templates.DplM1pJ3.css +1 -0
- package/.output/public/_nuxt/csitHKnE2.js +1 -0
- package/.output/public/_nuxt/d-w5jR8R.js +1 -0
- package/.output/public/_nuxt/daq.0uieKcZk.css +1 -0
- package/.output/public/_nuxt/dcw.pQRVkAi_.css +1 -0
- package/.output/public/_nuxt/default.uao9U50c.css +1 -0
- package/.output/public/_nuxt/e7BBCLxr.js +1 -0
- package/.output/public/_nuxt/entry.6DEjPWvR.css +1 -0
- package/.output/public/_nuxt/eutnXI4F.js +24 -0
- package/.output/public/_nuxt/fxWyOzyp2.js +1 -0
- package/.output/public/_nuxt/hkMGMTqt.js +1 -0
- package/.output/public/_nuxt/ikAUnxx3.js +1 -0
- package/.output/public/_nuxt/lWYQbIR3.js +1 -0
- package/.output/public/_nuxt/logs.CD0Olr3c.css +1 -0
- package/.output/public/_nuxt/monitor.CI2v5Nvl.css +1 -0
- package/.output/public/_nuxt/pages.CMhSEhEb.css +1 -0
- package/.output/public/_nuxt/permissions.DxuOOIqQ.css +1 -0
- package/.output/public/_nuxt/plugins.C50x1qZp.css +1 -0
- package/.output/public/_nuxt/qOlYoGLf.js +1 -0
- package/.output/public/_nuxt/schedules.BXNJ9q_I.css +1 -0
- package/.output/public/_nuxt/settings.Bo5EYkLI.css +1 -0
- package/.output/public/_nuxt/teams.sGmx1AdX.css +1 -0
- package/.output/public/_nuxt/tokens.DFMFaCYU.css +1 -0
- package/.output/public/_nuxt/uI9Ki_XZ.js +9 -0
- package/.output/public/_nuxt/users.DBxcKnfg.css +1 -0
- package/.output/public/_nuxt/v55S8LnT.js +1 -0
- package/.output/public/_nuxt/wDPc4SSR.js +106 -0
- package/.output/public/_nuxt/workshop.b4YRXEe0.css +1 -0
- package/.output/public/_nuxt/y9kob70X.js +1 -0
- package/.output/public/_nuxt/zh-S_7rN.js +1 -0
- package/.output/server/chunks/_/LeftOutlined.mjs +1 -1
- package/.output/server/chunks/_/SearchOutlined.mjs +1243 -279
- package/.output/server/chunks/_/SearchOutlined.mjs.map +1 -1
- package/.output/server/chunks/_/binding-authz.mjs +1 -1
- package/.output/server/chunks/_/character-asset.repo.mjs +1 -1
- package/.output/server/chunks/_/entity.mjs +1 -1
- package/.output/server/chunks/_/env-manager.mjs +1 -1
- package/.output/server/chunks/_/host-bindings.mjs +1 -1
- package/.output/server/chunks/_/nitro.mjs +26659 -18787
- package/.output/server/chunks/_/nitro.mjs.map +1 -1
- package/.output/server/chunks/_/scene-layout.repo.mjs +1 -1
- package/.output/server/chunks/build/Checkbox-4EdGAAl_.mjs +206 -0
- package/.output/server/chunks/build/Checkbox-4EdGAAl_.mjs.map +1 -0
- package/.output/server/chunks/build/FormItemContext-4ORSzhWv.mjs +72 -0
- package/.output/server/chunks/build/FormItemContext-4ORSzhWv.mjs.map +1 -0
- package/.output/server/chunks/build/OmpTerminalPanel-EQHrOJbw.mjs +942 -0
- package/.output/server/chunks/build/OmpTerminalPanel-EQHrOJbw.mjs.map +1 -0
- package/.output/server/chunks/build/PluginSlot-BP_qedpQ.mjs +399 -0
- package/.output/server/chunks/build/PluginSlot-BP_qedpQ.mjs.map +1 -0
- package/.output/server/chunks/build/TownView-BAR8CPIZ.mjs +6373 -0
- package/.output/server/chunks/build/TownView-BAR8CPIZ.mjs.map +1 -0
- package/.output/server/chunks/build/_id_-DgMpjM3_.mjs +1354 -0
- package/.output/server/chunks/build/_id_-DgMpjM3_.mjs.map +1 -0
- package/.output/server/chunks/build/_id_-Dv3BQbYV.mjs +388 -0
- package/.output/server/chunks/build/_id_-Dv3BQbYV.mjs.map +1 -0
- package/.output/server/chunks/build/_wsId_-BKJvFmyf.mjs +13109 -0
- package/.output/server/chunks/build/_wsId_-BKJvFmyf.mjs.map +1 -0
- package/.output/server/chunks/build/agents-B2GLPmn2.mjs +1085 -0
- package/.output/server/chunks/build/agents-B2GLPmn2.mjs.map +1 -0
- package/.output/server/chunks/build/aml-DdWicH3t.mjs +806 -0
- package/.output/server/chunks/build/aml-DdWicH3t.mjs.map +1 -0
- package/.output/server/chunks/build/api-BY6ji7RC.mjs +50 -0
- package/.output/server/chunks/build/api-BY6ji7RC.mjs.map +1 -0
- package/.output/server/chunks/build/api-error-BwAQLw44.mjs +47 -0
- package/.output/server/chunks/build/api-error-BwAQLw44.mjs.map +1 -0
- package/.output/server/chunks/build/apiClient-D281qrK2.mjs +81 -0
- package/.output/server/chunks/build/apiClient-D281qrK2.mjs.map +1 -0
- package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs +117 -0
- package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs.map +1 -0
- package/.output/server/chunks/build/card-CUSocE9p.mjs +953 -0
- package/.output/server/chunks/build/card-CUSocE9p.mjs.map +1 -0
- package/.output/server/chunks/build/channel-templates-RPOtkgRf.mjs +846 -0
- package/.output/server/chunks/build/channel-templates-RPOtkgRf.mjs.map +1 -0
- package/.output/server/chunks/build/checkbox-C4AKj81e.mjs +439 -0
- package/.output/server/chunks/build/checkbox-C4AKj81e.mjs.map +1 -0
- package/.output/server/chunks/build/client-only-qNA2OVV_.mjs +43 -0
- package/.output/server/chunks/build/client-only-qNA2OVV_.mjs.map +1 -0
- package/.output/server/chunks/build/constants-HPHJvjne.mjs +139 -0
- package/.output/server/chunks/build/constants-HPHJvjne.mjs.map +1 -0
- package/.output/server/chunks/build/core-CgTOPS4t.mjs +294 -0
- package/.output/server/chunks/build/core-CgTOPS4t.mjs.map +1 -0
- package/.output/server/chunks/build/daq-CPtsh0e7.mjs +1253 -0
- package/.output/server/chunks/build/daq-CPtsh0e7.mjs.map +1 -0
- package/.output/server/chunks/build/dcw-ry5Gyw6Y.mjs +478 -0
- package/.output/server/chunks/build/dcw-ry5Gyw6Y.mjs.map +1 -0
- package/.output/server/chunks/build/default-DxOE1eB0.mjs +2061 -0
- package/.output/server/chunks/build/default-DxOE1eB0.mjs.map +1 -0
- package/.output/server/chunks/build/descriptions-CFaUq4y4.mjs +406 -0
- package/.output/server/chunks/build/descriptions-CFaUq4y4.mjs.map +1 -0
- package/.output/server/chunks/build/drawer-Cfh9_zft.mjs +834 -0
- package/.output/server/chunks/build/drawer-Cfh9_zft.mjs.map +1 -0
- package/.output/server/chunks/build/dropdown-KdKWCS5K.mjs +556 -0
- package/.output/server/chunks/build/dropdown-KdKWCS5K.mjs.map +1 -0
- package/.output/server/chunks/build/dropdown-Yev6knaj.mjs +14 -0
- package/.output/server/chunks/build/dropdown-Yev6knaj.mjs.map +1 -0
- package/.output/server/chunks/build/form-uSlNqIAV.mjs +4711 -0
- package/.output/server/chunks/build/form-uSlNqIAV.mjs.map +1 -0
- package/.output/server/chunks/build/input-Cr_KFj4T.mjs +1381 -0
- package/.output/server/chunks/build/input-Cr_KFj4T.mjs.map +1 -0
- package/.output/server/chunks/build/input-number-BRVnxeiA.mjs +1119 -0
- package/.output/server/chunks/build/input-number-BRVnxeiA.mjs.map +1 -0
- package/.output/server/chunks/build/interval-DvHBIIZh.mjs +9 -0
- package/.output/server/chunks/build/interval-DvHBIIZh.mjs.map +1 -0
- package/.output/server/chunks/build/logs-CeaiO3y9.mjs +367 -0
- package/.output/server/chunks/build/logs-CeaiO3y9.mjs.map +1 -0
- package/.output/server/chunks/build/menu-BgkRSSx7.mjs +1949 -0
- package/.output/server/chunks/build/menu-BgkRSSx7.mjs.map +1 -0
- package/.output/server/chunks/build/monitor-DnuXEIi5.mjs +2245 -0
- package/.output/server/chunks/build/monitor-DnuXEIi5.mjs.map +1 -0
- package/.output/server/chunks/build/narrow-fetch-CcG-BPxc.mjs +6 -0
- package/.output/server/chunks/build/narrow-fetch-CcG-BPxc.mjs.map +1 -0
- package/.output/server/chunks/build/pages-Bn90ijKD.mjs +539 -0
- package/.output/server/chunks/build/pages-Bn90ijKD.mjs.map +1 -0
- package/.output/server/chunks/build/permissions-DL70W1YD.mjs +629 -0
- package/.output/server/chunks/build/permissions-DL70W1YD.mjs.map +1 -0
- package/.output/server/chunks/build/plugins-DrsZo--K.mjs +246 -0
- package/.output/server/chunks/build/plugins-DrsZo--K.mjs.map +1 -0
- package/.output/server/chunks/build/popconfirm-Ox-_2V8A.mjs +339 -0
- package/.output/server/chunks/build/popconfirm-Ox-_2V8A.mjs.map +1 -0
- package/.output/server/chunks/build/schedules-VLzNcjnW.mjs +7164 -0
- package/.output/server/chunks/build/schedules-VLzNcjnW.mjs.map +1 -0
- package/.output/server/chunks/build/segmented-C61yLHvo.mjs +341 -0
- package/.output/server/chunks/build/segmented-C61yLHvo.mjs.map +1 -0
- package/.output/server/chunks/build/select-rdBWytVK.mjs +3922 -0
- package/.output/server/chunks/build/select-rdBWytVK.mjs.map +1 -0
- package/.output/server/chunks/build/settings-DVAUVMsX.mjs +1089 -0
- package/.output/server/chunks/build/settings-DVAUVMsX.mjs.map +1 -0
- package/.output/server/chunks/build/space-B2uhHJG8.mjs +110 -0
- package/.output/server/chunks/build/space-B2uhHJG8.mjs.map +1 -0
- package/.output/server/chunks/build/spin-C2cuvGth.mjs +357 -0
- package/.output/server/chunks/build/spin-C2cuvGth.mjs.map +1 -0
- package/.output/server/chunks/build/switch-DZAUIbIN.mjs +377 -0
- package/.output/server/chunks/build/switch-DZAUIbIN.mjs.map +1 -0
- package/.output/server/chunks/build/table-DOFAvY-v.mjs +8881 -0
- package/.output/server/chunks/build/table-DOFAvY-v.mjs.map +1 -0
- package/.output/server/chunks/build/tabs-DTeIuZeP.mjs +1923 -0
- package/.output/server/chunks/build/tabs-DTeIuZeP.mjs.map +1 -0
- package/.output/server/chunks/build/tag-CHLGvNM6.mjs +1029 -0
- package/.output/server/chunks/build/tag-CHLGvNM6.mjs.map +1 -0
- package/.output/server/chunks/build/teams-Mc07PYO8.mjs +1195 -0
- package/.output/server/chunks/build/teams-Mc07PYO8.mjs.map +1 -0
- package/.output/server/chunks/build/tokens-DiwD5qni.mjs +1036 -0
- package/.output/server/chunks/build/tokens-DiwD5qni.mjs.map +1 -0
- package/.output/server/chunks/build/town-BIn3be5v.mjs +39 -0
- package/.output/server/chunks/build/town-BIn3be5v.mjs.map +1 -0
- package/.output/server/chunks/build/town-D8VmTCmN.mjs +128 -0
- package/.output/server/chunks/build/town-D8VmTCmN.mjs.map +1 -0
- package/.output/server/chunks/build/types-D6J7slEa.mjs +5561 -0
- package/.output/server/chunks/build/types-D6J7slEa.mjs.map +1 -0
- package/.output/server/chunks/build/useAgentHarnessOptions-CPn5dzKe.mjs +121 -0
- package/.output/server/chunks/build/useAgentHarnessOptions-CPn5dzKe.mjs.map +1 -0
- package/.output/server/chunks/build/useAgentTemplateActions-DQWzVnZ0.mjs +74 -0
- package/.output/server/chunks/build/useAgentTemplateActions-DQWzVnZ0.mjs.map +1 -0
- package/.output/server/chunks/build/useAgentTemplateDialogState-CZzpu5og.mjs +64 -0
- package/.output/server/chunks/build/useAgentTemplateDialogState-CZzpu5og.mjs.map +1 -0
- package/.output/server/chunks/build/useAgentTemplateForm-vqyiRPtt.mjs +114 -0
- package/.output/server/chunks/build/useAgentTemplateForm-vqyiRPtt.mjs.map +1 -0
- package/.output/server/chunks/build/useAgentTemplatesCatalog-DcQbsOik.mjs +130 -0
- package/.output/server/chunks/build/useAgentTemplatesCatalog-DcQbsOik.mjs.map +1 -0
- package/.output/server/chunks/build/useAmlDatasets-BM0lZu40.mjs +89 -0
- package/.output/server/chunks/build/useAmlDatasets-BM0lZu40.mjs.map +1 -0
- package/.output/server/chunks/build/useAmlEntities-BjXWvFd_.mjs +83 -0
- package/.output/server/chunks/build/useAmlEntities-BjXWvFd_.mjs.map +1 -0
- package/.output/server/chunks/build/useAmlEnv-BB7p6x_w.mjs +191 -0
- package/.output/server/chunks/build/useAmlEnv-BB7p6x_w.mjs.map +1 -0
- package/.output/server/chunks/build/useAmlExperiments-BtyW32PO.mjs +86 -0
- package/.output/server/chunks/build/useAmlExperiments-BtyW32PO.mjs.map +1 -0
- package/.output/server/chunks/build/useAmlFormat-WrKuRfw2.mjs +79 -0
- package/.output/server/chunks/build/useAmlFormat-WrKuRfw2.mjs.map +1 -0
- package/.output/server/chunks/build/useAmlJobs-DBuLvWMF.mjs +263 -0
- package/.output/server/chunks/build/useAmlJobs-DBuLvWMF.mjs.map +1 -0
- package/.output/server/chunks/build/useAmlModels-VurmkBYe.mjs +90 -0
- package/.output/server/chunks/build/useAmlModels-VurmkBYe.mjs.map +1 -0
- package/.output/server/chunks/build/useAmlNotes-BUdj3X6Y.mjs +83 -0
- package/.output/server/chunks/build/useAmlNotes-BUdj3X6Y.mjs.map +1 -0
- package/.output/server/chunks/build/useAmlPredict-DFy-Do10.mjs +127 -0
- package/.output/server/chunks/build/useAmlPredict-DFy-Do10.mjs.map +1 -0
- package/.output/server/chunks/build/useDaqAddNode-kW7uTSMQ.mjs +139 -0
- package/.output/server/chunks/build/useDaqAddNode-kW7uTSMQ.mjs.map +1 -0
- package/.output/server/chunks/build/useDaqDetailBinding-cpBz4hSL.mjs +67 -0
- package/.output/server/chunks/build/useDaqDetailBinding-cpBz4hSL.mjs.map +1 -0
- package/.output/server/chunks/build/useDaqDetailFrames-BZGtKKXW.mjs +76 -0
- package/.output/server/chunks/build/useDaqDetailFrames-BZGtKKXW.mjs.map +1 -0
- package/.output/server/chunks/build/useDaqDetailHistory-C4_hVeqZ.mjs +173 -0
- package/.output/server/chunks/build/useDaqDetailHistory-C4_hVeqZ.mjs.map +1 -0
- package/.output/server/chunks/build/useDaqDetailNode-Bwid_iA8.mjs +96 -0
- package/.output/server/chunks/build/useDaqDetailNode-Bwid_iA8.mjs.map +1 -0
- package/.output/server/chunks/build/useDaqDetailParams-JfqHvTxr.mjs +156 -0
- package/.output/server/chunks/build/useDaqDetailParams-JfqHvTxr.mjs.map +1 -0
- package/.output/server/chunks/build/useDaqFilters-C13836p-.mjs +219 -0
- package/.output/server/chunks/build/useDaqFilters-C13836p-.mjs.map +1 -0
- package/.output/server/chunks/build/useDaqNodeWindow-1DWj2H5G.mjs +87 -0
- package/.output/server/chunks/build/useDaqNodeWindow-1DWj2H5G.mjs.map +1 -0
- package/.output/server/chunks/build/useDaqOptimizations-Ct-TSXxy.mjs +133 -0
- package/.output/server/chunks/build/useDaqOptimizations-Ct-TSXxy.mjs.map +1 -0
- package/.output/server/chunks/build/useDaqStream-wOxZcMDA.mjs +395 -0
- package/.output/server/chunks/build/useDaqStream-wOxZcMDA.mjs.map +1 -0
- package/.output/server/chunks/build/useDaqTemplates-BqQRdrIm.mjs +184 -0
- package/.output/server/chunks/build/useDaqTemplates-BqQRdrIm.mjs.map +1 -0
- package/.output/server/chunks/build/useDashboardCharts-DZysNDUX.mjs +351 -0
- package/.output/server/chunks/build/useDashboardCharts-DZysNDUX.mjs.map +1 -0
- package/.output/server/chunks/build/useDashboardData-Ny_wtbo6.mjs +150 -0
- package/.output/server/chunks/build/useDashboardData-Ny_wtbo6.mjs.map +1 -0
- package/.output/server/chunks/build/useDcwAddNode-C8nE7geh.mjs +148 -0
- package/.output/server/chunks/build/useDcwAddNode-C8nE7geh.mjs.map +1 -0
- package/.output/server/chunks/build/useDcwCards-ETEsHvVy.mjs +64 -0
- package/.output/server/chunks/build/useDcwCards-ETEsHvVy.mjs.map +1 -0
- package/.output/server/chunks/build/useDcwDetailScope-BNrpDzbO.mjs +181 -0
- package/.output/server/chunks/build/useDcwDetailScope-BNrpDzbO.mjs.map +1 -0
- package/.output/server/chunks/build/useDcwDetailTemplates-IcKAJ37Y.mjs +145 -0
- package/.output/server/chunks/build/useDcwDetailTemplates-IcKAJ37Y.mjs.map +1 -0
- package/.output/server/chunks/build/useDcwFilter-FfVlDdyS.mjs +68 -0
- package/.output/server/chunks/build/useDcwFilter-FfVlDdyS.mjs.map +1 -0
- package/.output/server/chunks/build/useDcwLedger-BLoMmcu1.mjs +76 -0
- package/.output/server/chunks/build/useDcwLedger-BLoMmcu1.mjs.map +1 -0
- package/.output/server/chunks/build/useDcwLineMutations-BMC5BTWU.mjs +134 -0
- package/.output/server/chunks/build/useDcwLineMutations-BMC5BTWU.mjs.map +1 -0
- package/.output/server/chunks/build/useDcwLineRun-Bpxk4mse.mjs +101 -0
- package/.output/server/chunks/build/useDcwLineRun-Bpxk4mse.mjs.map +1 -0
- package/.output/server/chunks/build/useDcwProducts-D-zrMaeV.mjs +90 -0
- package/.output/server/chunks/build/useDcwProducts-D-zrMaeV.mjs.map +1 -0
- package/.output/server/chunks/build/useDcwQuery-1lYHOaU6.mjs +95 -0
- package/.output/server/chunks/build/useDcwQuery-1lYHOaU6.mjs.map +1 -0
- package/.output/server/chunks/build/useDcwQuickActions-C-tgcXIr.mjs +85 -0
- package/.output/server/chunks/build/useDcwQuickActions-C-tgcXIr.mjs.map +1 -0
- package/.output/server/chunks/build/useDcwRecipes-BzGOymqz.mjs +237 -0
- package/.output/server/chunks/build/useDcwRecipes-BzGOymqz.mjs.map +1 -0
- package/.output/server/chunks/build/useDcwStream-D04kCV3J.mjs +435 -0
- package/.output/server/chunks/build/useDcwStream-D04kCV3J.mjs.map +1 -0
- package/.output/server/chunks/build/useDcwTemplates-Dsjw9u6U.mjs +88 -0
- package/.output/server/chunks/build/useDcwTemplates-Dsjw9u6U.mjs.map +1 -0
- package/.output/server/chunks/build/useDcwWrites-3RxwN36I.mjs +120 -0
- package/.output/server/chunks/build/useDcwWrites-3RxwN36I.mjs.map +1 -0
- package/.output/server/chunks/build/useDeviceTwins-CbVad1M3.mjs +128 -0
- package/.output/server/chunks/build/useDeviceTwins-CbVad1M3.mjs.map +1 -0
- package/.output/server/chunks/build/useLocalTime-9a4B-aHv.mjs +1 -8
- package/.output/server/chunks/build/useLocalTime-9a4B-aHv.mjs.map +1 -1
- package/.output/server/chunks/build/useLogsFormat-BlwLpyzy.mjs +74 -0
- package/.output/server/chunks/build/useLogsFormat-BlwLpyzy.mjs.map +1 -0
- package/.output/server/chunks/build/useLogsManual-Deev2Cjk.mjs +81 -0
- package/.output/server/chunks/build/useLogsManual-Deev2Cjk.mjs.map +1 -0
- package/.output/server/chunks/build/useLogsQuery-CRAm2hmS.mjs +112 -0
- package/.output/server/chunks/build/useLogsQuery-CRAm2hmS.mjs.map +1 -0
- package/.output/server/chunks/build/useLogsScope-ClNijVj4.mjs +80 -0
- package/.output/server/chunks/build/useLogsScope-ClNijVj4.mjs.map +1 -0
- package/.output/server/chunks/build/useMonitorColumns-ylxrUNTK.mjs +212 -0
- package/.output/server/chunks/build/useMonitorColumns-ylxrUNTK.mjs.map +1 -0
- package/.output/server/chunks/build/useMonitorData-CEpKXoD4.mjs +131 -0
- package/.output/server/chunks/build/useMonitorData-CEpKXoD4.mjs.map +1 -0
- package/.output/server/chunks/build/useMonitorFormat-_7zLapf3.mjs +87 -0
- package/.output/server/chunks/build/useMonitorFormat-_7zLapf3.mjs.map +1 -0
- package/.output/server/chunks/build/useMonitorTerminal-Dj4QwRNW.mjs +68 -0
- package/.output/server/chunks/build/useMonitorTerminal-Dj4QwRNW.mjs.map +1 -0
- package/.output/server/chunks/build/useOpsLog-CgnNG7_n.mjs +137 -0
- package/.output/server/chunks/build/useOpsLog-CgnNG7_n.mjs.map +1 -0
- package/.output/server/chunks/build/useTeamActions-D6sxdpkP.mjs +76 -0
- package/.output/server/chunks/build/useTeamActions-D6sxdpkP.mjs.map +1 -0
- package/.output/server/chunks/build/useTeamAddMemberDialog-Q-ntQ6Vo.mjs +81 -0
- package/.output/server/chunks/build/useTeamAddMemberDialog-Q-ntQ6Vo.mjs.map +1 -0
- package/.output/server/chunks/build/useTeamCreateDialog-D7OMfDTM.mjs +94 -0
- package/.output/server/chunks/build/useTeamCreateDialog-D7OMfDTM.mjs.map +1 -0
- package/.output/server/chunks/build/useTeamDeployDialog-vBRqjiut.mjs +77 -0
- package/.output/server/chunks/build/useTeamDeployDialog-vBRqjiut.mjs.map +1 -0
- package/.output/server/chunks/build/useTeamDialogState-CFRUQxAf.mjs +79 -0
- package/.output/server/chunks/build/useTeamDialogState-CFRUQxAf.mjs.map +1 -0
- package/.output/server/chunks/build/useTeamPluginDialog-B8kxewpZ.mjs +100 -0
- package/.output/server/chunks/build/useTeamPluginDialog-B8kxewpZ.mjs.map +1 -0
- package/.output/server/chunks/build/useTeamsCatalog-xRhk7ezA.mjs +120 -0
- package/.output/server/chunks/build/useTeamsCatalog-xRhk7ezA.mjs.map +1 -0
- package/.output/server/chunks/build/useTokenClipboard-CwQpoFeP.mjs +69 -0
- package/.output/server/chunks/build/useTokenClipboard-CwQpoFeP.mjs.map +1 -0
- package/.output/server/chunks/build/useTokenCreate-DpDcF1Vx.mjs +112 -0
- package/.output/server/chunks/build/useTokenCreate-DpDcF1Vx.mjs.map +1 -0
- package/.output/server/chunks/build/useTokenRename-BRejFrtc.mjs +84 -0
- package/.output/server/chunks/build/useTokenRename-BRejFrtc.mjs.map +1 -0
- package/.output/server/chunks/build/useTokenReveal-DER2afAS.mjs +115 -0
- package/.output/server/chunks/build/useTokenReveal-DER2afAS.mjs.map +1 -0
- package/.output/server/chunks/build/useTokensList-DHt65IOY.mjs +89 -0
- package/.output/server/chunks/build/useTokensList-DHt65IOY.mjs.map +1 -0
- package/.output/server/chunks/build/useWorkbenchAuth-B2j-zILO.mjs +135 -0
- package/.output/server/chunks/build/useWorkbenchAuth-B2j-zILO.mjs.map +1 -0
- package/.output/server/chunks/build/useWorkbenchWorkspaces-B_2mOxN6.mjs +133 -0
- package/.output/server/chunks/build/useWorkbenchWorkspaces-B_2mOxN6.mjs.map +1 -0
- package/.output/server/chunks/build/useWorkshopApi-B1B9vd4m.mjs +102 -0
- package/.output/server/chunks/build/useWorkshopApi-B1B9vd4m.mjs.map +1 -0
- package/.output/server/chunks/build/useWorkshopWs-B-Es2Xg3.mjs +1782 -0
- package/.output/server/chunks/build/useWorkshopWs-B-Es2Xg3.mjs.map +1 -0
- package/.output/server/chunks/build/useWorkspaceAuth-tQ1ORWXo.mjs +57 -0
- package/.output/server/chunks/build/useWorkspaceAuth-tQ1ORWXo.mjs.map +1 -0
- package/.output/server/chunks/build/useWorkspaceChannels-BwB6TAWY.mjs +87 -0
- package/.output/server/chunks/build/useWorkspaceChannels-BwB6TAWY.mjs.map +1 -0
- package/.output/server/chunks/build/useWorkspacePanels-CuAmDpT5.mjs +75 -0
- package/.output/server/chunks/build/useWorkspacePanels-CuAmDpT5.mjs.map +1 -0
- package/.output/server/chunks/build/useWorkspaceShell-CcQ4JlqO.mjs +165 -0
- package/.output/server/chunks/build/useWorkspaceShell-CcQ4JlqO.mjs.map +1 -0
- package/.output/server/chunks/build/users-De2HygNt.mjs +561 -0
- package/.output/server/chunks/build/users-De2HygNt.mjs.map +1 -0
- package/.output/server/chunks/build/workshop-CmgpTwyf.mjs +1018 -0
- package/.output/server/chunks/build/workshop-CmgpTwyf.mjs.map +1 -0
- package/.output/server/chunks/build/workspaces-DVIEk1Bl.mjs +139 -0
- package/.output/server/chunks/build/workspaces-DVIEk1Bl.mjs.map +1 -0
- package/.output/server/chunks/routes/api/health.get.mjs +2 -2
- package/.output/server/chunks/routes/api/index.get.mjs +2 -2
- package/.output/server/chunks/routes/api/index.post.mjs +2 -2
- package/.output/server/chunks/routes/api/mcp/workshop.mjs +2 -2
- package/.output/server/chunks/routes/api/metrics.mjs +2 -2
- package/.output/server/chunks/routes/api/plugins/_name/_...path_.mjs +2 -2
- package/.output/server/chunks/routes/api/plugins/client/_name_.get.mjs +2 -2
- package/.output/server/chunks/routes/api/plugins/i18n.get.mjs +2 -2
- package/.output/server/chunks/routes/api/plugins/manifest.get.mjs +2 -2
- package/.output/server/chunks/routes/api/system/config-groups/_id_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/system/config-groups/_id_.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/system/config.get.mjs +2 -2
- package/.output/server/chunks/routes/api/system/index.get.mjs +2 -2
- package/.output/server/chunks/routes/api/system/index.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/system/index.post.mjs +2 -2
- package/.output/server/chunks/routes/api/system/index2.get.mjs +2 -2
- package/.output/server/chunks/routes/api/system/monitor/terminal/ws.mjs +35 -4
- package/.output/server/chunks/routes/api/system/monitor/terminal/ws.mjs.map +1 -1
- package/.output/server/chunks/routes/api/system/monitor/terminate.post.mjs +2 -2
- package/.output/server/chunks/routes/api/system/monitor.get.mjs +2 -2
- package/.output/server/chunks/routes/api/system/settings/events.get.mjs +2 -2
- package/.output/server/chunks/routes/api/system/settings/reset.post.mjs +2 -2
- package/.output/server/chunks/routes/api/users/_id_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/users/_id_.get.mjs +2 -2
- package/.output/server/chunks/routes/api/users/_id_.put.mjs +2 -2
- package/.output/server/chunks/routes/api/users/index.get.mjs +2 -2
- package/.output/server/chunks/routes/api/users/index.post.mjs +2 -2
- package/.output/server/chunks/routes/api/users/login.post.mjs +2 -2
- package/.output/server/chunks/routes/api/users/logout.post.mjs +2 -2
- package/.output/server/chunks/routes/api/users/me.get.mjs +2 -2
- package/.output/server/chunks/routes/api/users/register.post.mjs +2 -2
- package/.output/server/chunks/routes/api/users/setup-status.get.mjs +2 -2
- package/.output/server/chunks/routes/api/users/tokens/_id/token.get.mjs +2 -2
- package/.output/server/chunks/routes/api/users/tokens/_id_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/users/tokens/_id_.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/_...path_.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/a2a/_agentId/index.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/a2a/_agentId/index.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/a2a/send.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/agent-tools/approvals/_id/decide.post.mjs +38 -6
- package/.output/server/chunks/routes/api/workshop/agent-tools/approvals/_id/decide.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/_id_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/_id_.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/agent-tools/index.get.mjs +14 -5
- package/.output/server/chunks/routes/api/workshop/agent-tools/index.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/agent-tools/index.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/agent-tools/index2.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/agent-tools/invoke.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/agent-tools/list.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/agents/_id_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/agents/_id_.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/agents/_id_.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/agents/subscribe.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/datasets/_id/preview.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/entities/prune.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/env/recheck.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/env/uv.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/env/venv.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/index.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/index.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/index2.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/index2.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/index3.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/index4.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/index5.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/index6.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/cancel.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/logs.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/retry.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/jobs/_id_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/jobs/_id_.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/models/_id/predict.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/models/_id/promote.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/models/_id_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/aml/models/_id_.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/approvals/_id/decide.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/assets/character/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/assets/devices/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/assets/index.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/assets/index.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/assets/index2.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/assets/index2.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/audit.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/caller.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +10 -5
- package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/channel-templates/_id_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channel-templates/_id_.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channel-templates/_id_.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channel-templates/from-channel.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/activate.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/index.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/index.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/memories/_memoryId_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/memories/search.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/model.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/position.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/range.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/stop.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId_.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/chat/index.get.mjs +41 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/chat/index.get.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/chat/index.post.mjs +52 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/chat/index.post.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/chat/messages/_messageId/read.post.mjs +33 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/chat/messages/_messageId/read.post.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/chat/permissions.get.mjs +48 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/chat/permissions.get.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/events.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/index.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/index.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/index.put.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/index2.get.mjs +10 -14
- package/.output/server/chunks/routes/api/workshop/channels/_id/index2.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index2.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/index3.get.mjs +15 -28
- package/.output/server/chunks/routes/api/workshop/channels/_id/index3.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index3.post.mjs +3 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/index3.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index4.get.mjs +29 -23
- package/.output/server/chunks/routes/api/workshop/channels/_id/index4.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index4.post.mjs +9 -4
- package/.output/server/chunks/routes/api/workshop/channels/_id/index4.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index5.get.mjs +22 -10
- package/.output/server/chunks/routes/api/workshop/channels/_id/index5.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/channels/_id/index6.get.mjs +34 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/index6.get.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/members/_userId/approve.post.mjs +33 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/members/_userId/approve.post.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/members/_userId_.delete.mjs +34 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/members/_userId_.delete.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/members/join.post.mjs +35 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/members/join.post.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/members/leave.post.mjs +33 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/members/leave.post.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/channels/_id/memories/_memoryId_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/queue.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id/terminals.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/_id_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/index.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/channels/index.patch.mjs +22 -6
- package/.output/server/chunks/routes/api/workshop/channels/index.patch.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/channels.get.mjs +21 -3
- package/.output/server/chunks/routes/api/workshop/channels.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/channels.post.mjs +5 -5
- package/.output/server/chunks/routes/api/workshop/daq/_id/bind.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/daq/_id/bindings.put.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/daq/_id/frames/content.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/daq/_id/frames.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/daq/_id/samples.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/daq/_id/test.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/daq/_id_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/daq/_id_.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/daq/alarms/_id/ack.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/daq/alarms.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/daq/controller.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/daq/index.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/daq/infra/reconnect.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/daq/infra.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/daq/test-driver.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/_id/bind.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/_id/bindings.put.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/_id/param-ledger.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/_id/read.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/_id/test.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/_id/write.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/_id_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/_id_.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/controller.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/index.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/index.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/index2.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/index2.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/index3.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/index3.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/index4.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/index4.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/index5.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/index6.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/journal/node/_nodeId/rollback.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/line/query.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/judge.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/rollback.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/series.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id_.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id/read.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id/write.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/apply.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/mark-good.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/revert.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/rollback-good.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/versions.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/recipes/index.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/close.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/data.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/rollback.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/dcw/test-driver.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/device-twins/_id/control.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/device-twins/_id/telemetry.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/device-twins/_id_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/device-twins/_id_.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/fs/dirs.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +11 -4
- package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +28 -36
- package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/index.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/index.put.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/index10.get.mjs +26 -12
- package/.output/server/chunks/routes/api/workshop/index10.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index11.get.mjs +12 -7
- package/.output/server/chunks/routes/api/workshop/index11.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index12.get.mjs +3 -3
- package/.output/server/chunks/routes/api/workshop/index12.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index13.get.mjs +29 -0
- package/.output/server/chunks/routes/api/workshop/index13.get.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/index14.get.mjs +28 -0
- package/.output/server/chunks/routes/api/workshop/index14.get.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/index2.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/index2.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/index3.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/index3.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/index4.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/index4.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/index5.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/index5.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/index6.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/index6.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/index7.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/index7.post.mjs +14 -16
- package/.output/server/chunks/routes/api/workshop/index7.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index8.get.mjs +40 -19
- package/.output/server/chunks/routes/api/workshop/index8.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index8.post.mjs +19 -5
- package/.output/server/chunks/routes/api/workshop/index8.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index9.get.mjs +20 -24
- package/.output/server/chunks/routes/api/workshop/index9.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index9.post.mjs +30 -0
- package/.output/server/chunks/routes/api/workshop/index9.post.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/mailbox/all.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/mailbox.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/memories/maintenance.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/notifications/read.post.mjs +41 -0
- package/.output/server/chunks/routes/api/workshop/notifications/read.post.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/plugins/index.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/runtime.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/scene/layouts.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/schedules/_id/run.post.mjs +29 -0
- package/.output/server/chunks/routes/api/workshop/schedules/_id/run.post.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/schedules/_id/runs.get.mjs +31 -0
- package/.output/server/chunks/routes/api/workshop/schedules/_id/runs.get.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/schedules/_id_.delete.mjs +30 -0
- package/.output/server/chunks/routes/api/workshop/schedules/_id_.delete.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/schedules/_id_.get.mjs +29 -0
- package/.output/server/chunks/routes/api/workshop/schedules/_id_.get.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/schedules/_id_.patch.mjs +40 -0
- package/.output/server/chunks/routes/api/workshop/schedules/_id_.patch.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/tasks/_id/complete.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/tasks/_id/dispatch.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/tasks/_id/index.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/tasks/_id/reassign.post.mjs +39 -0
- package/.output/server/chunks/routes/api/workshop/tasks/_id/reassign.post.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/tasks/_id/report.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/tasks/_id/retry.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/tasks/_id/update.post.mjs +44 -0
- package/.output/server/chunks/routes/api/workshop/tasks/_id/update.post.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/tasks/_id_.get.mjs +9 -5
- package/.output/server/chunks/routes/api/workshop/tasks/_id_.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/tasks.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/teams/_id/deploy.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/teams/_id/index.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/teams/_id/index.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/teams/_id/index.put.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/teams/_id/index2.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/teams/_id/members/_agentId_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/teams/_id_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/teams/_id_.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/teams/_id_.patch.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/users/me.get.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/users/register.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +3 -3
- package/.output/server/chunks/routes/api/workshop/workspaces/_id/channels/_channelId_.delete.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/workspaces/_id/channels/_channelId_.post.mjs +2 -2
- package/.output/server/chunks/routes/api/workshop/workspaces/index.delete.mjs +2 -2
- package/.output/server/chunks/routes/renderer.mjs +3 -3
- package/.output/server/chunks/virtual/entry.mjs +5704 -722
- package/.output/server/chunks/virtual/entry.mjs.map +1 -1
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/index.mjs +15 -5549
- package/.output/server/index.mjs.map +1 -1
- package/.output/server/package.json +1 -1
- package/README-zh.md +109 -45
- package/README.md +115 -38
- package/app/components/AppHeader.vue +284 -959
- package/app/components/aml/AmlDatasetFormModal.vue +279 -0
- package/app/components/aml/AmlDatasetTable.vue +319 -0
- package/app/components/aml/AmlEnvPanel.vue +230 -0
- package/app/components/aml/AmlExperimentsTable.vue +142 -0
- package/app/components/aml/AmlInfraBanner.vue +23 -0
- package/app/components/aml/AmlJobsTable.vue +248 -0
- package/app/components/aml/AmlModelsTable.vue +173 -0
- package/app/components/aml/AmlOverviewBar.vue +62 -0
- package/app/components/aml/AmlPageHeader.vue +47 -0
- package/app/components/aml/AmlPredictConsole.vue +201 -0
- package/app/components/app-header/HeaderTools.vue +208 -0
- package/app/components/app-header/HitlBell.vue +423 -0
- package/app/components/app-header/TrailNav.vue +261 -0
- package/app/components/app-header/UserChip.vue +150 -0
- package/app/components/daq/DaqAddNodeModal.vue +209 -0
- package/app/components/daq/DaqAlarmBar.vue +46 -0
- package/app/components/daq/DaqControlCard.vue +159 -0
- package/app/components/daq/DaqDetailFramesTile.vue +139 -0
- package/app/components/daq/DaqDetailHistoryCard.vue +128 -0
- package/app/components/daq/DaqDetailLiveCard.vue +92 -0
- package/app/components/daq/DaqDetailParamForm.vue +350 -0
- package/app/components/daq/DaqInfraBanner.vue +31 -0
- package/app/components/daq/DaqLineBanner.vue +47 -0
- package/app/components/daq/DaqNodePanel.vue +396 -0
- package/app/components/daq/DaqOptPanel.vue +197 -0
- package/app/components/daq/DaqTemplateModal.vue +246 -0
- package/app/components/dashboard/DashboardFleetPanel.vue +228 -0
- package/app/components/dashboard/DashboardGaugeBand.vue +78 -0
- package/app/components/dashboard/DashboardHarnessPanel.vue +149 -0
- package/app/components/dashboard/DashboardHero.vue +209 -0
- package/app/components/dashboard/DashboardLineStatePanel.vue +78 -0
- package/app/components/dashboard/DashboardNodeStatePanel.vue +46 -0
- package/app/components/dashboard/DashboardPipelinePanel.vue +46 -0
- package/app/components/dashboard/DashboardTrendPanel.vue +83 -0
- package/app/components/dashboard/DashboardWritePanel.vue +52 -0
- package/app/components/dcw/DcwAddNodeModal.vue +328 -0
- package/app/components/dcw/DcwAdoptCard.vue +89 -0
- package/app/components/dcw/DcwCreateLineModal.vue +105 -0
- package/app/components/dcw/DcwDeleteLineModal.vue +73 -0
- package/app/components/dcw/DcwDetailHead.vue +65 -0
- package/app/components/dcw/DcwDetailTemplateModal.vue +238 -0
- package/app/components/dcw/DcwEditLineModal.vue +108 -0
- package/app/components/dcw/DcwGatewayBar.vue +94 -0
- package/app/components/dcw/DcwLedgerCard.vue +160 -0
- package/app/components/dcw/DcwLineCard.vue +117 -0
- package/app/components/dcw/DcwLineRunCard.vue +158 -0
- package/app/components/dcw/DcwNodesTable.vue +253 -0
- package/app/components/dcw/DcwProductCreateModal.vue +113 -0
- package/app/components/dcw/DcwQueryCard.vue +251 -0
- package/app/components/dcw/DcwRecipeEditModal.vue +285 -0
- package/app/components/dcw/DcwRecipeHistoryModal.vue +165 -0
- package/app/components/dcw/DcwRecipePanel.vue +367 -0
- package/app/components/dcw/DcwRunDataModal.vue +191 -0
- package/app/components/dcw/DcwTemplateModal.vue +227 -0
- package/app/components/dcw/DcwWriteHistoryCard.vue +96 -0
- package/app/components/logs/LogsEventTable.vue +309 -0
- package/app/components/logs/LogsFilterCard.vue +234 -0
- package/app/components/logs/LogsManualModal.vue +231 -0
- package/app/components/logs/LogsPageHead.vue +71 -0
- package/app/components/monitor/MonitorAgentTable.vue +143 -0
- package/app/components/monitor/MonitorChannelTable.vue +80 -0
- package/app/components/monitor/MonitorLoginGate.vue +24 -0
- package/app/components/monitor/MonitorPageHead.vue +92 -0
- package/app/components/monitor/MonitorProcessTable.vue +159 -0
- package/app/components/monitor/MonitorStatGrid.vue +165 -0
- package/app/components/settings/AppearancePane.vue +282 -0
- package/app/components/settings/PluginsSection.vue +280 -0
- package/app/components/settings/RuntimeFieldRow.vue +189 -0
- package/app/components/settings/RuntimeGroupSection.vue +290 -0
- package/app/components/settings/RuntimePane.vue +235 -0
- package/app/components/settings/SystemPane.vue +83 -0
- package/app/components/tokens/TokenCreateModal.vue +42 -0
- package/app/components/tokens/TokenOnceModal.vue +164 -0
- package/app/components/tokens/TokenRenameModal.vue +40 -0
- package/app/components/tokens/TokensAuthGate.vue +47 -0
- package/app/components/tokens/TokensPageHead.vue +76 -0
- package/app/components/tokens/TokensTable.vue +227 -0
- package/app/components/workshop/A2aRpcDebugger.vue +237 -237
- package/app/components/workshop/AgentInspectorDrawer.vue +424 -424
- package/app/components/workshop/AgentLanesView.vue +278 -998
- package/app/components/workshop/ArtifactCard.vue +120 -120
- package/app/components/workshop/AssetLibrary.vue +452 -452
- package/app/components/workshop/ChannelSessionList.vue +377 -804
- package/app/components/workshop/ChatMemberPanel.vue +555 -0
- package/app/components/workshop/ChatTimeline.vue +569 -0
- package/app/components/workshop/CommandPalette.vue +270 -269
- package/app/components/workshop/Composer.vue +540 -827
- package/app/components/workshop/DeviceTwinPanel.vue +167 -643
- package/app/components/workshop/EventBlock.vue +214 -602
- package/app/components/workshop/FileSelectorModal.vue +188 -188
- package/app/components/workshop/InspectorPanel.vue +301 -301
- package/app/components/workshop/MemoryPanel.vue +48 -7
- package/app/components/workshop/MultiChannelView.vue +4 -4
- package/app/components/workshop/NotificationCenter.vue +325 -0
- package/app/components/workshop/TaskBoardView.vue +121 -547
- package/app/components/workshop/TaskInspectorDrawer.vue +101 -7
- package/app/components/workshop/TranscriptTimeline.vue +223 -601
- package/app/components/workshop/agents/AgentTemplateEditModal.vue +139 -0
- package/app/components/workshop/agents/AgentTemplateTable.vue +269 -0
- package/app/components/workshop/agents/AgentTemplateToolbar.vue +67 -0
- package/app/components/workshop/blocks/ClusterArtifact.vue +61 -61
- package/app/components/workshop/blocks/ClusterError.vue +85 -85
- package/app/components/workshop/blocks/ClusterLife.vue +87 -87
- package/app/components/workshop/blocks/ClusterMember.vue +95 -93
- package/app/components/workshop/blocks/ClusterMemory.vue +84 -84
- package/app/components/workshop/blocks/ClusterOther.vue +67 -67
- package/app/components/workshop/blocks/ClusterRoute.vue +1 -1
- package/app/components/workshop/blocks/ClusterStatus.vue +149 -147
- package/app/components/workshop/blocks/ClusterTool.vue +158 -158
- package/app/components/workshop/blocks/EventBlockAvatar.vue +114 -0
- package/app/components/workshop/blocks/EventBlockHead.vue +232 -0
- package/app/components/workshop/composer/ComposerToolbar.vue +278 -0
- package/app/components/workshop/composer/MentionMenu.vue +135 -0
- package/app/components/workshop/composer/ReplyChip.vue +65 -0
- package/app/components/workshop/composer/StatusChip.vue +171 -0
- package/app/components/workshop/console/CenterPane.vue +94 -0
- package/app/components/workshop/console/Dock.vue +71 -0
- package/app/components/workshop/console/LeftPane.vue +84 -0
- package/app/components/workshop/console/RightPane.vue +95 -0
- package/app/components/workshop/console/Topbar.vue +289 -0
- package/app/components/workshop/device-twin/DeviceTwinCard.vue +265 -0
- package/app/components/workshop/device-twin/DeviceTwinDcwSection.vue +203 -0
- package/app/components/workshop/lanes/AddMemberModal.vue +270 -0
- package/app/components/workshop/lanes/EditMemberModal.vue +106 -0
- package/app/components/workshop/lanes/LaneHeader.vue +432 -0
- package/app/components/workshop/lanes/LanesToolbar.vue +97 -0
- package/app/components/workshop/session-list/ChannelSettingsModal.vue +329 -0
- package/app/components/workshop/session-list/MountChannelModal.vue +124 -0
- package/app/components/workshop/session-list/SaveTemplateModal.vue +93 -0
- package/app/components/workshop/task-board/Card.vue +172 -0
- package/app/components/workshop/task-board/Column.vue +151 -0
- package/app/components/workshop/task-board/List.vue +157 -0
- package/app/components/workshop/task-board/StatusMenu.vue +88 -0
- package/app/components/workshop/teams/TeamAddMemberModal.vue +53 -0
- package/app/components/workshop/teams/TeamCard.vue +183 -0
- package/app/components/workshop/teams/TeamCreateModal.vue +94 -0
- package/app/components/workshop/teams/TeamDeployModal.vue +40 -0
- package/app/components/workshop/teams/TeamPluginModal.vue +119 -0
- package/app/components/workshop/terminal/OmpTerminalPanel.vue +270 -1011
- package/app/components/workshop/terminal/TerminalHitlCard.vue +170 -0
- package/app/components/workshop/town/TownScene.ts +72 -961
- package/app/components/workshop/town/TownScene3D.ts +357 -4045
- package/app/components/workshop/town/TownView.vue +461 -7405
- package/app/components/workshop/town/components/TownAgentBindings.vue +380 -0
- package/app/components/workshop/town/components/TownAgentInspector.vue +546 -0
- package/app/components/workshop/town/components/TownAlarmList.vue +147 -0
- package/app/components/workshop/town/components/TownBoundaryPanel.vue +350 -0
- package/app/components/workshop/town/components/TownCalloutLayer.vue +147 -0
- package/app/components/workshop/town/components/TownChannelDock.vue +116 -0
- package/app/components/workshop/town/components/TownControlDock.vue +400 -0
- package/app/components/workshop/town/components/TownDaqNodeInspector.vue +345 -0
- package/app/components/workshop/town/components/TownDaqTree.vue +366 -0
- package/app/components/workshop/town/components/TownDcwBindSection.vue +473 -0
- package/app/components/workshop/town/components/TownDcwNodeInspector.vue +185 -0
- package/app/components/workshop/town/components/TownDcwTree.vue +280 -0
- package/app/components/workshop/town/components/TownDeviceBindings.vue +394 -0
- package/app/components/workshop/town/components/TownDeviceTransform.vue +248 -0
- package/app/components/workshop/town/components/TownEventList.vue +120 -0
- package/app/components/workshop/town/components/TownHealthBody.vue +100 -0
- package/app/components/workshop/town/components/TownKpiStrip.vue +207 -0
- package/app/components/workshop/town/components/TownLeftRail.vue +308 -0
- package/app/components/workshop/town/components/TownLoadingMask.vue +61 -0
- package/app/components/workshop/town/components/TownNavMap.vue +154 -0
- package/app/components/workshop/town/components/TownRightRail.vue +540 -0
- package/app/components/workshop/town/components/TownStage.vue +281 -0
- package/app/components/workshop/town/components/TownStageTop.vue +238 -0
- package/app/components/workshop/town/components/TownStatusBar.vue +108 -0
- package/app/components/workshop/town/components/TownTopNav.vue +367 -0
- package/app/components/workshop/town/components/TownTrendDock.vue +178 -0
- package/app/components/workshop/town/scene/town-scene-agents.ts +254 -0
- package/app/components/workshop/town/scene/town-scene-behavior.ts +194 -0
- package/app/components/workshop/town/scene/town-scene-bubbles.ts +96 -0
- package/app/components/workshop/town/scene/town-scene-core.ts +158 -0
- package/app/components/workshop/town/scene/town-scene-models.ts +109 -0
- package/app/components/workshop/town/scene/town-scene-visuals.ts +279 -0
- package/app/components/workshop/town/scene/town-scene3d-agents.ts +308 -0
- package/app/components/workshop/town/scene/town-scene3d-blocks.ts +297 -0
- package/app/components/workshop/town/scene/town-scene3d-bubbles.ts +281 -0
- package/app/components/workshop/town/scene/town-scene3d-camera.ts +282 -0
- package/app/components/workshop/town/scene/town-scene3d-devices.ts +433 -0
- package/app/components/workshop/town/scene/town-scene3d-editing.ts +490 -0
- package/app/components/workshop/town/scene/town-scene3d-events.ts +173 -0
- package/app/components/workshop/town/scene/town-scene3d-factory.ts +412 -0
- package/app/components/workshop/town/scene/town-scene3d-hud.ts +110 -0
- package/app/components/workshop/town/scene/town-scene3d-lifecycle.ts +144 -0
- package/app/components/workshop/town/scene/town-scene3d-links.ts +141 -0
- package/app/components/workshop/town/scene/town-scene3d-loop.ts +286 -0
- package/app/components/workshop/town/scene/town-scene3d-models.ts +154 -0
- package/app/components/workshop/town/scene/town-scene3d-nodes.ts +562 -0
- package/app/components/workshop/town/scene/town-scene3d-ranges.ts +273 -0
- package/app/components/workshop/town/scene/town-scene3d-renderer.ts +320 -0
- package/app/components/workshop/town/scene/town-scene3d-selection.ts +257 -0
- package/app/components/workshop/town/scene/town-scene3d-types.ts +264 -0
- package/app/components/workshop/transcript/TranscriptBlockList.vue +68 -0
- package/app/components/workshop/transcript/TranscriptEarlierPager.vue +81 -0
- package/app/components/workshop/transcript/TranscriptEmptyState.vue +153 -0
- package/app/components/workshop/transcript/TranscriptFilterBar.vue +126 -0
- package/app/components/workshop/workbench/AuthGate.vue +179 -0
- package/app/components/workshop/workbench/CreateWorkspaceModal.vue +36 -0
- package/app/components/workshop/workbench/WorkbenchHead.vue +129 -0
- package/app/components/workshop/workbench/WorkspaceCard.vue +198 -0
- package/app/components/workshop/workbench/WorkspaceEmpty.vue +56 -0
- package/app/composables/workshop/device-twin-types.ts +29 -0
- package/app/composables/workshop/event-blocks/attention.ts +58 -0
- package/app/composables/workshop/event-blocks/classify.ts +44 -0
- package/app/composables/workshop/event-blocks/clusterer.ts +231 -0
- package/app/composables/workshop/event-blocks/constants.ts +48 -0
- package/app/composables/workshop/event-blocks/fold-stream.ts +45 -0
- package/app/composables/workshop/event-blocks/markdown.ts +81 -0
- package/app/composables/workshop/event-blocks/mentions.ts +49 -0
- package/app/composables/workshop/event-blocks/text-utils.ts +100 -0
- package/app/composables/workshop/event-blocks/types.ts +40 -0
- package/app/composables/workshop/town/town-relay-props.ts +226 -0
- package/app/composables/workshop/town/town-view-types.ts +145 -0
- package/app/composables/workshop/town/useTownAgentBindings.ts +115 -0
- package/app/composables/workshop/town/useTownAgentChat.ts +203 -0
- package/app/composables/workshop/town/useTownAlarms.ts +36 -0
- package/app/composables/workshop/town/useTownCallouts.ts +174 -0
- package/app/composables/workshop/town/useTownChannels.ts +220 -0
- package/app/composables/workshop/town/useTownHealth.ts +66 -0
- package/app/composables/workshop/town/useTownHudTickers.ts +77 -0
- package/app/composables/workshop/town/useTownLayout.ts +277 -0
- package/app/composables/workshop/town/useTownNavMap.ts +301 -0
- package/app/composables/workshop/town/useTownNodeBindings.ts +345 -0
- package/app/composables/workshop/town/useTownPanelDrag.ts +91 -0
- package/app/composables/workshop/town/useTownRailData.ts +421 -0
- package/app/composables/workshop/town/useTownSceneControl.ts +92 -0
- package/app/composables/workshop/town/useTownSceneInput.ts +275 -0
- package/app/composables/workshop/town/useTownSceneRuntime.ts +390 -0
- package/app/composables/workshop/town/useTownSheets.ts +41 -0
- package/app/composables/workshop/town/useTownTrends.ts +93 -0
- package/app/composables/workshop/useChannelList.ts +88 -0
- package/app/composables/workshop/useChannelTerminals.ts +65 -0
- package/app/composables/workshop/useComposerMentions.ts +130 -0
- package/app/composables/workshop/useDeviceTwinDcwWrite.ts +77 -0
- package/app/composables/workshop/useDeviceTwinPanel.ts +56 -0
- package/app/composables/workshop/useEventBlockView.ts +159 -0
- package/app/composables/workshop/useEventBlocks.ts +33 -662
- package/app/composables/workshop/useHitlAnswer.ts +184 -0
- package/app/composables/workshop/useLaneWidths.ts +27 -0
- package/app/composables/workshop/useOmpTerminalSession.ts +424 -0
- package/app/composables/workshop/usePluginAdmin.ts +152 -0
- package/app/composables/workshop/useRuntimeConfigDraft.ts +118 -0
- package/app/composables/workshop/useSettingsGroups.ts +243 -0
- package/app/composables/workshop/useTaskBoardColumns.ts +47 -0
- package/app/composables/workshop/useTaskBoardDrag.ts +78 -0
- package/app/composables/workshop/useTaskBoardMenu.ts +60 -0
- package/app/composables/workshop/useTaskBoardTasks.ts +74 -0
- package/app/composables/workshop/useTerminalRenderer.ts +308 -0
- package/app/composables/workshop/useTranscriptEnterStage.ts +48 -0
- package/app/composables/workshop/useTranscriptHistory.ts +52 -0
- package/app/composables/workshop/useTranscriptScroll.ts +113 -0
- package/app/composables/workshop/useWorkshopApi.ts +85 -2
- package/app/composables/workshop/useWorkshopWs.ts +265 -126
- package/app/config/schema.ts +13 -0
- package/app/layouts/town.vue +21 -16
- package/app/middleware/locale-cookie.global.ts +22 -0
- package/app/pages/aml/api.ts +6 -0
- package/app/pages/aml/composables/useAmlDatasets.ts +38 -0
- package/app/pages/aml/composables/useAmlEntities.ts +57 -0
- package/app/pages/aml/composables/useAmlEnv.ts +191 -0
- package/app/pages/aml/composables/useAmlExperiments.ts +45 -0
- package/app/pages/aml/composables/useAmlFormat.ts +44 -0
- package/app/pages/aml/composables/useAmlJobs.ts +131 -0
- package/app/pages/aml/composables/useAmlModels.ts +56 -0
- package/app/pages/aml/composables/useAmlNotes.ts +47 -0
- package/app/pages/aml/composables/useAmlPredict.ts +93 -0
- package/app/pages/aml/constants.ts +63 -0
- package/app/pages/aml/index.vue +143 -2574
- package/app/pages/aml/styles.css +420 -0
- package/app/pages/aml/types.ts +189 -0
- package/app/pages/composables/useDashboardCharts.ts +192 -0
- package/app/pages/composables/useDashboardData.ts +144 -0
- package/app/pages/daq/[id].vue +59 -929
- package/app/pages/daq/composables/useDaqAddNode.ts +126 -0
- package/app/pages/daq/composables/useDaqDetailBinding.ts +29 -0
- package/app/pages/daq/composables/useDaqDetailFrames.ts +42 -0
- package/app/pages/daq/composables/useDaqDetailHistory.ts +199 -0
- package/app/pages/daq/composables/useDaqDetailNode.ts +53 -0
- package/app/pages/daq/composables/useDaqDetailParams.ts +138 -0
- package/app/pages/daq/composables/useDaqFilters.ts +187 -0
- package/app/pages/daq/composables/useDaqNodeWindow.ts +86 -0
- package/app/pages/daq/composables/useDaqOptimizations.ts +99 -0
- package/app/pages/daq/composables/useDaqTemplates.ts +152 -0
- package/app/pages/daq/daq-page.css +681 -0
- package/app/pages/daq/index.vue +260 -2316
- package/app/pages/dcw/[id].vue +297 -2616
- package/app/pages/dcw/composables/useDcwAddNode.ts +121 -0
- package/app/pages/dcw/composables/useDcwCards.ts +27 -0
- package/app/pages/dcw/composables/useDcwDetailScope.ts +145 -0
- package/app/pages/dcw/composables/useDcwDetailTemplates.ts +94 -0
- package/app/pages/dcw/composables/useDcwFilter.ts +29 -0
- package/app/pages/dcw/composables/useDcwLedger.ts +34 -0
- package/app/pages/dcw/composables/useDcwLineMutations.ts +115 -0
- package/app/pages/dcw/composables/useDcwLineRun.ts +60 -0
- package/app/pages/dcw/composables/useDcwProducts.ts +54 -0
- package/app/pages/dcw/composables/useDcwQuery.ts +68 -0
- package/app/pages/dcw/composables/useDcwQuickActions.ts +47 -0
- package/app/pages/dcw/composables/useDcwRecipes.ts +239 -0
- package/app/pages/dcw/composables/useDcwTemplates.ts +50 -0
- package/app/pages/dcw/composables/useDcwWrites.ts +81 -0
- package/app/pages/dcw/dcw-page.css +344 -0
- package/app/pages/dcw/index.vue +64 -986
- package/app/pages/dcw/types.ts +9 -0
- package/app/pages/index.vue +126 -981
- package/app/pages/logs/composables/useLogsFormat.ts +43 -0
- package/app/pages/logs/composables/useLogsManual.ts +63 -0
- package/app/pages/logs/composables/useLogsQuery.ts +52 -0
- package/app/pages/logs/composables/useLogsScope.ts +39 -0
- package/app/pages/logs/index.vue +57 -728
- package/app/pages/monitor/composables/useMonitorColumns.ts +69 -0
- package/app/pages/monitor/composables/useMonitorData.ts +124 -0
- package/app/pages/monitor/composables/useMonitorFormat.ts +39 -0
- package/app/pages/monitor/composables/useMonitorTerminal.ts +28 -0
- package/app/pages/monitor/types.ts +61 -0
- package/app/pages/monitor.vue +88 -658
- package/app/pages/permissions/index.vue +3 -2
- package/app/pages/plugins/index.vue +318 -311
- package/app/pages/settings.vue +168 -1562
- package/app/pages/tokens/composables/useTokenClipboard.ts +33 -0
- package/app/pages/tokens/composables/useTokenCreate.ts +93 -0
- package/app/pages/tokens/composables/useTokenRename.ts +54 -0
- package/app/pages/tokens/composables/useTokenReveal.ts +84 -0
- package/app/pages/tokens/composables/useTokensList.ts +60 -0
- package/app/pages/tokens.vue +79 -597
- package/app/pages/users.vue +5 -4
- package/app/pages/workshop/agents.vue +130 -609
- package/app/pages/workshop/channel-templates.vue +18 -11
- package/app/pages/workshop/composables/useAgentHarnessOptions.ts +59 -0
- package/app/pages/workshop/composables/useAgentTemplateActions.ts +42 -0
- package/app/pages/workshop/composables/useAgentTemplateDialogState.ts +24 -0
- package/app/pages/workshop/composables/useAgentTemplateForm.ts +88 -0
- package/app/pages/workshop/composables/useAgentTemplatesCatalog.ts +92 -0
- package/app/pages/workshop/composables/useTeamActions.ts +44 -0
- package/app/pages/workshop/composables/useTeamAddMemberDialog.ts +49 -0
- package/app/pages/workshop/composables/useTeamCreateDialog.ts +67 -0
- package/app/pages/workshop/composables/useTeamDeployDialog.ts +49 -0
- package/app/pages/workshop/composables/useTeamDialogState.ts +46 -0
- package/app/pages/workshop/composables/useTeamPluginDialog.ts +72 -0
- package/app/pages/workshop/composables/useTeamsCatalog.ts +100 -0
- package/app/pages/workshop/composables/useWorkbenchAuth.ts +125 -0
- package/app/pages/workshop/composables/useWorkbenchWorkspaces.ts +123 -0
- package/app/pages/workshop/composables/useWorkspaceAuth.ts +31 -0
- package/app/pages/workshop/composables/useWorkspaceChannels.ts +62 -0
- package/app/pages/workshop/composables/useWorkspacePanels.ts +56 -0
- package/app/pages/workshop/composables/useWorkspaceShell.ts +136 -0
- package/app/pages/workshop/index.vue +116 -668
- package/app/pages/workshop/schedules.vue +553 -0
- package/app/pages/workshop/teams.vue +233 -740
- package/app/pages/workshop/w/[wsId].vue +221 -704
- package/app/plugins/locale-restore.client.ts +16 -1
- package/app/stores/workshop/chat.ts +434 -0
- package/app/stores/workshop/entities.ts +385 -283
- package/app/stores/workshop/events.ts +5 -4
- package/app/stores/workshop/hitl.ts +110 -7
- package/app/stores/workshop/narrow-fetch.ts +23 -0
- package/app/stores/workshop/notifications.ts +288 -0
- package/app/stores/workshop/user.ts +23 -10
- package/app/stores/workshop/workspaces.ts +12 -2
- package/app/utils/builtin-catalog.ts +104 -0
- package/config.yml +176 -170
- package/docs/aml.md +10 -0
- package/docs/cli.en.md +5 -5
- package/docs/cli.md +5 -5
- package/docs/full-test-plan.md +17 -4
- package/docs/plugins.en.md +1 -1
- package/docs/plugins.md +1 -1
- package/docs/sdk.en.md +1 -1
- package/docs/sdk.md +1 -1
- package/i18n/dicts/dcwDetail.json +2 -0
- package/i18n/locales/en.ts +364 -35
- package/i18n/locales/zh-CN.ts +336 -7
- package/package.json +3 -4
- package/scripts/_audit/detached-instance.mjs +112 -0
- package/scripts/_audit/schedules-ui/schedules-page.png +0 -0
- package/scripts/_audit/schedules-ui/workspace-channel-tag.png +0 -0
- package/scripts/_audit/stubs/nuxt-imports.mjs +67 -0
- package/scripts/_audit/ts-register-hook.mjs +12 -0
- package/scripts/_audit/ts-resolve-hook.mjs +25 -5
- package/scripts/_audit/watchdog-recovery-test.mjs +24 -2
- package/scripts/_dbg-comm-verify.mjs +188 -0
- package/scripts/_dbg-full-feature-e2e.mjs +19 -11
- package/scripts/_dbg-im-e2e.mjs +169 -0
- package/scripts/_dbg-opt2-run.mjs +114 -0
- package/scripts/_dbg-opt2-setup.mjs +95 -0
- package/scripts/_dbg-param-map-e2e.ts +1 -1
- package/scripts/_dbg-param-map-rest-smoke.mjs +1 -1
- package/scripts/_dbg-perms-e2e.mjs +132 -109
- package/scripts/_dbg-real-scenario-e2e.mjs +246 -0
- package/scripts/_dbg-schedule-e2e.mjs +262 -0
- package/scripts/_dbg-schedule-ui-verify.mjs +115 -0
- package/scripts/_dbg-team-matrix.mjs +148 -0
- package/scripts/_dbg-ui-fullcrawl.mjs +91 -0
- package/scripts/_dbg-ui-static-audit.mjs +65 -0
- package/scripts/_dbg-write-lock-e2e.mjs +131 -0
- package/scripts/_demo-agentteam-capture.mjs +278 -0
- package/scripts/_e2e-ui-functional.mjs +371 -0
- package/scripts/_i18n-en-cjk-scan.mjs +28 -0
- package/scripts/_i18n-en-verify.mjs +107 -0
- package/scripts/_i18n-extra-checks.mjs +53 -0
- package/scripts/_i18n-scan-hardcoded.mjs +180 -0
- package/scripts/_i18n-verify-keys.mjs +50 -0
- package/scripts/_loop-biax-campaign.mjs +15 -9
- package/scripts/_probe-daq-omp-task.mjs +100 -0
- package/scripts/_probe-daq-scope-default.mjs +123 -0
- package/scripts/_probe-recipe-switch.mjs +92 -0
- package/scripts/_provision-injection-line.mjs +39 -0
- package/scripts/_run-matrix.ps1 +53 -0
- package/scripts/_run-real-injection-fifo-goals.mjs +367 -0
- package/scripts/_sweep-button-contrast.mjs +87 -0
- package/scripts/acceptance-aml-db.ts +23 -0
- package/scripts/acceptance-aml-hybrid-live.mjs +94 -0
- package/scripts/acceptance-aml-hybrid-twin.ts +93 -0
- package/scripts/acceptance-hybrid-python.py +51 -0
- package/scripts/aml-ui-smoke.mjs +2 -0
- package/scripts/api-live-e2e.mjs +32 -7
- package/scripts/demo-group-chat-all-real.ts +279 -0
- package/scripts/demo-group-chat-omp-cases.ts +210 -0
- package/scripts/demo-group-chat-omp.ts +184 -0
- package/scripts/demo-group-chat-order.ts +197 -0
- package/scripts/e2e-agent-team.mjs +6 -4
- package/scripts/e2e-agentteam-chat.mjs +473 -0
- package/scripts/e2e-agentteam-real-triage.mjs +421 -0
- package/scripts/e2e-agentteam-task-flow-real.ts +443 -0
- package/scripts/e2e-aw-tui-global.mjs +8 -8
- package/scripts/e2e-channel-isolation.ts +5 -1
- package/scripts/e2e-closedloop/cleanup.mjs +27 -0
- package/scripts/e2e-closedloop/lib.mjs +109 -0
- package/scripts/e2e-closedloop/main.mjs +59 -0
- package/scripts/e2e-closedloop/scenarios-accounts.mjs +208 -0
- package/scripts/e2e-closedloop/scenarios-authz.mjs +190 -0
- package/scripts/e2e-closedloop/scenarios-daq-dcw.mjs +219 -0
- package/scripts/e2e-closedloop/scenarios-plugins.mjs +169 -0
- package/scripts/e2e-closedloop/state.mjs +10 -0
- package/scripts/e2e-crash-cycle.mjs +108 -0
- package/scripts/e2e-full-closedloop.mjs +6 -919
- package/scripts/e2e-hil-multi-user.mjs +307 -0
- package/scripts/e2e-hil-restart.mjs +339 -0
- package/scripts/e2e-hitl-live.mjs +336 -323
- package/scripts/e2e-lead-team-mgmt.ts +1 -1
- package/scripts/e2e-memory-system.ts +70 -89
- package/scripts/e2e-multiharness-team.mjs +32 -15
- package/scripts/e2e-omp-workspace.ts +21 -6
- package/scripts/e2e-parallel-execution.mjs +20 -6
- package/scripts/e2e-plc-plugin-closedloop.mjs +96 -65
- package/scripts/e2e-rest-memory.mjs +26 -11
- package/scripts/e2e-resume-crash.mjs +22 -7
- package/scripts/e2e-task-queue.ts +8 -8
- package/scripts/e2e-template-isolation.mjs +4 -2
- package/scripts/proof-omp-real.ts +189 -0
- package/scripts/real-injection-closed-loop.mjs +383 -0
- package/scripts/test-agent-runtime.ts +5 -1
- package/scripts/test-agentteam-guardrails.ts +113 -0
- package/scripts/test-agentteam-task-chat-flow.ts +420 -0
- package/scripts/test-agentteam-workflow.ts +472 -0
- package/scripts/test-collab-e2e.mjs +17 -3
- package/scripts/test-dcw-read.ts +9 -2
- package/scripts/test-event-persistence.mjs +10 -2
- package/scripts/test-full-system.ts +16 -3
- package/scripts/test-group-chat-hardening.ts +265 -0
- package/scripts/test-group-chat-unit/lib.ts +186 -0
- package/scripts/test-group-chat-unit/main.ts +45 -0
- package/scripts/test-group-chat-unit/section-a-migration.ts +75 -0
- package/scripts/test-group-chat-unit/section-b-permission-matrix.ts +88 -0
- package/scripts/test-group-chat-unit/section-c-member-lifecycle.ts +64 -0
- package/scripts/test-group-chat-unit/section-d-mention-resolution.ts +36 -0
- package/scripts/test-group-chat-unit/section-e-chat-facts.ts +97 -0
- package/scripts/test-group-chat-unit/section-f-user-notifications.ts +52 -0
- package/scripts/test-group-chat-unit/section-g-member-projection.ts +44 -0
- package/scripts/test-group-chat-unit/section-h-outbox-consistency.ts +30 -0
- package/scripts/test-group-chat-unit/section-i-read-idempotency.ts +24 -0
- package/scripts/test-group-chat-unit/section-j-permission-scope.ts +150 -0
- package/scripts/test-group-chat-unit.ts +35 -0
- package/scripts/test-hitl-decision.ts +526 -0
- package/scripts/test-hitl-legacy-channel-auth.ts +143 -0
- package/scripts/test-lane-history-live.mjs +30 -7
- package/scripts/test-memory-vector.ts +10 -0
- package/scripts/test-orchestration.ts +7 -2
- package/scripts/test-persistence-lazy.ts +7 -1
- package/scripts/test-prompt-system.mjs +46 -20
- package/scripts/test-route-reason.mjs +9 -2
- package/scripts/test-schedule-runtime.ts +309 -0
- package/scripts/test-scheduler-loop/lib.ts +321 -0
- package/scripts/test-scheduler-loop/main.ts +40 -0
- package/scripts/test-scheduler-loop/scenarios-modes.ts +95 -0
- package/scripts/test-scheduler-loop/scenarios-runtime.ts +58 -0
- package/scripts/test-scheduler-loop/scenarios-supervise.ts +183 -0
- package/scripts/test-scheduler-loop/scenarios-triage.ts +102 -0
- package/scripts/test-scheduler-loop.ts +5 -460
- package/scripts/test-supervision-watchdog.ts +105 -0
- package/scripts/test-task-engine.ts +27 -5
- package/scripts/test-task-lease-fencing.ts +154 -0
- package/scripts/test-terminal-e2e.mjs +9 -3
- package/scripts/three-system-e2e/lib.mjs +79 -0
- package/scripts/three-system-e2e/main.mjs +40 -0
- package/scripts/three-system-e2e/phases/phase-0-user-token.mjs +42 -0
- package/scripts/three-system-e2e/phases/phase-0a2-line-autoselect.mjs +35 -0
- package/scripts/three-system-e2e/phases/phase-0b-fixture-grants.mjs +39 -0
- package/scripts/three-system-e2e/phases/phase-0c-plugin-outbound-auth.mjs +95 -0
- package/scripts/three-system-e2e/phases/stage-1-health.mjs +47 -0
- package/scripts/three-system-e2e/phases/stage-2-5-team-plugins.mjs +84 -0
- package/scripts/three-system-e2e/phases/stage-2-teams-tools-hitl.mjs +184 -0
- package/scripts/three-system-e2e/phases/stage-3-diag.mjs +73 -0
- package/scripts/three-system-e2e/phases/stage-4-plugin-toggle.mjs +53 -0
- package/scripts/three-system-e2e/state.mjs +87 -0
- package/scripts/three-system-e2e.mjs +24 -646
- package/scripts/typecheck.mjs +75 -0
- package/scripts/ui/lib.mjs +14 -2
- package/scripts/ui/record-demo.mjs +557 -0
- package/scripts/ui/smoke-refactor.mjs +258 -0
- package/scripts/verify-a2a-live.mjs +15 -1
- package/scripts/verify-ws-events.mjs +19 -1
- package/server/api/system/monitor/terminal/ws.ts +51 -1
- package/server/api/workshop/agent-tools/approvals/[id]/decide.post.ts +59 -5
- package/server/api/workshop/agent-tools/approvals/index.get.ts +28 -3
- package/server/api/workshop/channel-templates/[id]/instantiate.post.ts +6 -1
- package/server/api/workshop/channels/[id]/chat/messages/[messageId]/read.post.ts +22 -0
- package/server/api/workshop/channels/[id]/chat/messages/index.get.ts +29 -0
- package/server/api/workshop/channels/[id]/chat/messages/index.post.ts +51 -0
- package/server/api/workshop/channels/[id]/chat/permissions.get.ts +35 -0
- package/server/api/workshop/channels/[id]/index.patch.ts +34 -8
- package/server/api/workshop/channels/[id]/members/[userId]/approve.post.ts +20 -0
- package/server/api/workshop/channels/[id]/members/[userId].delete.ts +21 -0
- package/server/api/workshop/channels/[id]/members/index.get.ts +23 -0
- package/server/api/workshop/channels/[id]/members/join.post.ts +30 -0
- package/server/api/workshop/channels/[id]/members/leave.post.ts +20 -0
- package/server/api/workshop/channels/[id]/messages/index.post.ts +3 -0
- package/server/api/workshop/channels/[id]/tasks/index.post.ts +8 -0
- package/server/api/workshop/channels.get.ts +21 -1
- package/server/api/workshop/hitl/pending.get.ts +27 -7
- package/server/api/workshop/hitl/respond.post.ts +86 -89
- package/server/api/workshop/notifications/index.get.ts +60 -0
- package/server/api/workshop/notifications/read.post.ts +31 -0
- package/server/api/workshop/schedules/[id]/run.post.ts +15 -0
- package/server/api/workshop/schedules/[id]/runs.get.ts +15 -0
- package/server/api/workshop/schedules/[id].delete.ts +14 -0
- package/server/api/workshop/schedules/[id].get.ts +13 -0
- package/server/api/workshop/schedules/[id].patch.ts +29 -0
- package/server/api/workshop/schedules/index.get.ts +12 -0
- package/server/api/workshop/schedules/index.post.ts +32 -0
- package/server/api/workshop/tasks/[id]/reassign.post.ts +32 -0
- package/server/api/workshop/tasks/[id]/update.post.ts +35 -0
- package/server/api/workshop/tasks/[id].get.ts +15 -6
- package/server/api/workshop/ws/audience.ts +56 -0
- package/server/api/workshop/ws/db-buffer.ts +68 -0
- package/server/api/workshop/ws/event-mapping.ts +47 -0
- package/server/api/workshop/ws/handlers.ts +132 -0
- package/server/api/workshop/ws/hub.ts +133 -0
- package/server/api/workshop/ws/index.ts +23 -0
- package/server/api/workshop/ws/notification-uplink.ts +77 -0
- package/server/api/workshop/ws/peer-sub.ts +128 -0
- package/server/api/workshop/ws/publish.ts +122 -0
- package/server/api/workshop/ws/shared.ts +94 -0
- package/server/api/workshop/ws/streams.ts +146 -0
- package/server/api/workshop/ws/subscriptions.ts +137 -0
- package/server/plugins/workshop.ts +30 -0
- package/server/plugins-builtin/diag-bridge/host/constants.mjs +25 -0
- package/server/plugins-builtin/diag-bridge/host/diagnosis.mjs +56 -0
- package/server/plugins-builtin/diag-bridge/host/helpers.mjs +126 -0
- package/server/plugins-builtin/diag-bridge/host/index.mjs +15 -0
- package/server/plugins-builtin/diag-bridge/host/plugin.mjs +296 -0
- package/server/plugins-builtin/diag-bridge/host/snapshot.mjs +93 -0
- package/server/plugins-builtin/diag-bridge/host/sweep.mjs +51 -0
- package/server/plugins-builtin/diag-bridge/index.mjs +9 -661
- package/server/plugins-builtin/rag-bridge/index.mjs +57 -133
- package/server/repositories/user.repository.ts +13 -0
- package/server/services/system-config/contracts.ts +29 -0
- package/server/services/system-config/dispose.ts +19 -0
- package/server/services/system-config/facade.ts +26 -0
- package/server/services/system-config/groups.ts +108 -0
- package/server/services/system-config/helpers.ts +41 -0
- package/server/services/system-config/index.ts +21 -0
- package/server/services/system-config/init.ts +26 -0
- package/server/services/system-config/mutate.ts +103 -0
- package/server/services/system-config/plugins.ts +104 -0
- package/server/services/system-config/recompute.ts +66 -0
- package/server/services/system-config/snapshot.ts +59 -0
- package/server/services/system-config/state.ts +79 -0
- package/server/services/system-config/types.ts +58 -0
- package/server/services/workshop/agents/adapters/line-spawn.ts +2 -2
- package/server/services/workshop/agents/adapters/omp-rpc-client/api.ts +111 -0
- package/server/services/workshop/agents/adapters/omp-rpc-client/contracts.ts +22 -0
- package/server/services/workshop/agents/adapters/omp-rpc-client/facade.ts +7 -0
- package/server/services/workshop/agents/adapters/omp-rpc-client/frames.ts +111 -0
- package/server/services/workshop/agents/adapters/omp-rpc-client/helpers.ts +38 -0
- package/server/services/workshop/agents/adapters/omp-rpc-client/index.ts +25 -0
- package/server/services/workshop/agents/adapters/omp-rpc-client/start.ts +70 -0
- package/server/services/workshop/agents/adapters/omp-rpc-client/state.ts +65 -0
- package/server/services/workshop/agents/adapters/omp-rpc-client/stdout.ts +92 -0
- package/server/services/workshop/agents/adapters/omp-rpc-client/types.ts +115 -0
- package/server/services/workshop/agents/adapters/omp-rpc-client/usage.ts +47 -0
- package/server/services/workshop/agents/agent-interface.ts +52 -4
- package/server/services/workshop/agents/base-agent.ts +36 -8
- package/server/services/workshop/agents/claude-agent.ts +5 -0
- package/server/services/workshop/agents/codex-agent/client.ts +170 -0
- package/server/services/workshop/agents/codex-agent/core.ts +119 -0
- package/server/services/workshop/agents/codex-agent/facade.ts +8 -0
- package/server/services/workshop/agents/codex-agent/helpers.ts +13 -0
- package/server/services/workshop/agents/codex-agent/hitl.ts +138 -0
- package/server/services/workshop/agents/codex-agent/index.ts +14 -0
- package/server/services/workshop/agents/codex-agent/turn.ts +367 -0
- package/server/services/workshop/agents/codex-agent/types.ts +42 -0
- package/server/services/workshop/agents/dsh-agent.ts +5 -0
- package/server/services/workshop/agents/harness-availability.ts +127 -127
- package/server/services/workshop/agents/harness-terminal/api.ts +197 -0
- package/server/services/workshop/agents/harness-terminal/index.ts +17 -0
- package/server/services/workshop/agents/harness-terminal/io.ts +120 -0
- package/server/services/workshop/agents/harness-terminal/park.ts +55 -0
- package/server/services/workshop/agents/harness-terminal/sanitize.ts +98 -0
- package/server/services/workshop/agents/harness-terminal/session.ts +124 -0
- package/server/services/workshop/agents/harness-terminal/shared.ts +74 -0
- package/server/services/workshop/agents/hermes-agent.ts +5 -0
- package/server/services/workshop/agents/hitl-capabilities.ts +312 -0
- package/server/services/workshop/agents/hitl-decision/answers.ts +27 -0
- package/server/services/workshop/agents/hitl-decision/decide.ts +268 -0
- package/server/services/workshop/agents/hitl-decision/dispatcher.ts +81 -0
- package/server/services/workshop/agents/hitl-decision/index.ts +24 -0
- package/server/services/workshop/agents/hitl-decision/payload.ts +45 -0
- package/server/services/workshop/agents/hitl-decision/reconcile.ts +70 -0
- package/server/services/workshop/agents/hitl-decision/shared.ts +106 -0
- package/server/services/workshop/agents/hitl-decision/visibility.ts +94 -0
- package/server/services/workshop/agents/hitl-registry.ts +403 -7
- package/server/services/workshop/agents/host-tool-bridge/catalog.ts +78 -0
- package/server/services/workshop/agents/host-tool-bridge/dispatch.ts +167 -0
- package/server/services/workshop/agents/host-tool-bridge/index.ts +15 -0
- package/server/services/workshop/agents/host-tool-bridge/session.ts +22 -0
- package/server/services/workshop/agents/host-tool-bridge/tools/industrial.ts +64 -0
- package/server/services/workshop/agents/host-tool-bridge/tools/memory.ts +44 -0
- package/server/services/workshop/agents/host-tool-bridge/tools/messaging.ts +182 -0
- package/server/services/workshop/agents/host-tool-bridge/tools/progress.ts +84 -0
- package/server/services/workshop/agents/host-tool-bridge/tools/tasks.ts +120 -0
- package/server/services/workshop/agents/host-tool-bridge/tools/team.ts +70 -0
- package/server/services/workshop/agents/host-tool-bridge/types.ts +31 -0
- package/server/services/workshop/agents/industrial/aml-dataset-tools.ts +276 -0
- package/server/services/workshop/agents/industrial/aml-job-tools.ts +363 -0
- package/server/services/workshop/agents/industrial/daq-tools.ts +251 -0
- package/server/services/workshop/agents/industrial/dcw-judge-tools.ts +153 -0
- package/server/services/workshop/agents/industrial/dcw-tools.ts +213 -0
- package/server/services/workshop/agents/industrial/index.ts +24 -0
- package/server/services/workshop/agents/industrial/ops-tools.ts +382 -0
- package/server/services/workshop/agents/industrial/param-tools.ts +172 -0
- package/server/services/workshop/agents/industrial/twin-tools.ts +175 -0
- package/server/services/workshop/agents/mock-agent.ts +136 -12
- package/server/services/workshop/agents/omp-agent/client.ts +116 -0
- package/server/services/workshop/agents/omp-agent/context-governance.ts +204 -0
- package/server/services/workshop/agents/omp-agent/core.ts +146 -0
- package/server/services/workshop/agents/omp-agent/event-mapping.ts +142 -0
- package/server/services/workshop/agents/omp-agent/facade.ts +8 -0
- package/server/services/workshop/agents/omp-agent/helpers.ts +37 -0
- package/server/services/workshop/agents/omp-agent/index.ts +16 -0
- package/server/services/workshop/agents/omp-agent/lifecycle.ts +80 -0
- package/server/services/workshop/agents/omp-agent/tools-steer.ts +487 -0
- package/server/services/workshop/agents/omp-agent/types.ts +45 -0
- package/server/services/workshop/agents/opencode-agent/core.ts +77 -0
- package/server/services/workshop/agents/opencode-agent/engine-agnostic.ts +274 -0
- package/server/services/workshop/agents/opencode-agent/facade.ts +8 -0
- package/server/services/workshop/agents/opencode-agent/helpers.ts +29 -0
- package/server/services/workshop/agents/opencode-agent/hitl.ts +113 -0
- package/server/services/workshop/agents/opencode-agent/index.ts +16 -0
- package/server/services/workshop/agents/opencode-agent/lifecycle.ts +44 -0
- package/server/services/workshop/agents/opencode-agent/server.ts +244 -0
- package/server/services/workshop/agents/opencode-agent/turn.ts +278 -0
- package/server/services/workshop/agents/opencode-agent/types.ts +54 -0
- package/server/services/workshop/agents/prompt-builder.ts +16 -6
- package/server/services/workshop/agents/qwen-agent.ts +6 -2
- package/server/services/workshop/agents/registry.ts +32 -2
- package/server/services/workshop/aml/job-orchestrator/broadcast.ts +37 -0
- package/server/services/workshop/aml/job-orchestrator/control.ts +94 -0
- package/server/services/workshop/aml/job-orchestrator/failure.ts +77 -0
- package/server/services/workshop/aml/job-orchestrator/index.ts +22 -0
- package/server/services/workshop/aml/job-orchestrator/process.ts +100 -0
- package/server/services/workshop/aml/job-orchestrator/shared.ts +51 -0
- package/server/services/workshop/aml/job-orchestrator/stages.ts +96 -0
- package/server/services/workshop/aml/job-orchestrator/start-job.ts +124 -0
- package/server/services/workshop/aml/job-orchestrator/status.ts +20 -0
- package/server/services/workshop/aml/job-orchestrator/submit.ts +120 -0
- package/server/services/workshop/aml/job-orchestrator/tick.ts +37 -0
- package/server/services/workshop/aml/python/amlkit.py +185 -120
- package/server/services/workshop/aml/python/requirements.txt +9 -7
- package/server/services/workshop/aml/python/train-hybrid-example.py +87 -0
- package/server/services/workshop/aml/twin/acceptance.ts +68 -0
- package/server/services/workshop/aml/twin/calibration-scheduler.ts +48 -0
- package/server/services/workshop/aml/twin/channel-profile.ts +43 -0
- package/server/services/workshop/aml/twin/contracts.ts +215 -0
- package/server/services/workshop/aml/twin/physics-runtime.ts +165 -0
- package/server/services/workshop/aml/twin/repo.ts +26 -0
- package/server/services/workshop/aml/twin/snapshot-service.ts +81 -0
- package/server/services/workshop/aml/twin/trial-service.ts +89 -0
- package/server/services/workshop/aml/twin/uq-ood.ts +66 -0
- package/server/services/workshop/daq/daq-controller/contracts.ts +39 -0
- package/server/services/workshop/daq/daq-controller/crud.ts +202 -0
- package/server/services/workshop/daq/daq-controller/driver-probe.ts +64 -0
- package/server/services/workshop/daq/daq-controller/facade.ts +28 -0
- package/server/services/workshop/daq/daq-controller/frames.ts +137 -0
- package/server/services/workshop/daq/daq-controller/helpers.ts +52 -0
- package/server/services/workshop/daq/daq-controller/index.ts +22 -0
- package/server/services/workshop/daq/daq-controller/ingest.ts +189 -0
- package/server/services/workshop/daq/daq-controller/runtime-control.ts +72 -0
- package/server/services/workshop/daq/daq-controller/state.ts +194 -0
- package/server/services/workshop/daq/daq-controller/sweep-alarms.ts +164 -0
- package/server/services/workshop/daq/daq-controller/tsdb-twin.ts +138 -0
- package/server/services/workshop/daq/daq-controller/types.ts +68 -0
- package/server/services/workshop/daq/daq-controller/views.ts +84 -0
- package/server/services/workshop/daq/drivers/http.ts +95 -0
- package/server/services/workshop/daq/drivers/index.ts +29 -0
- package/server/services/workshop/daq/drivers/mock.ts +130 -0
- package/server/services/workshop/daq/drivers/modbus-rtu.ts +67 -0
- package/server/services/workshop/daq/drivers/modbus-tcp.ts +307 -0
- package/server/services/workshop/daq/drivers/mqtt.ts +182 -0
- package/server/services/workshop/daq/drivers/opcua.ts +227 -0
- package/server/services/workshop/daq/drivers/registry.ts +144 -0
- package/server/services/workshop/daq/drivers/s7-stub.ts +29 -0
- package/server/services/workshop/daq/drivers/shared.ts +60 -0
- package/server/services/workshop/db/channel-member.repo.ts +122 -0
- package/server/services/workshop/db/channel.repo.ts +93 -8
- package/server/services/workshop/db/chat-message.repo.ts +233 -0
- package/server/services/workshop/db/database/index.ts +18 -0
- package/server/services/workshop/db/database/json.ts +14 -0
- package/server/services/workshop/db/database/migrations.ts +367 -0
- package/server/services/workshop/db/database/open.ts +76 -0
- package/server/services/workshop/db/database/rows.ts +374 -0
- package/server/services/workshop/db/database/schema.ts +651 -0
- package/server/services/workshop/db/database/seed-data.ts +60 -0
- package/server/services/workshop/db/database/seed.ts +145 -0
- package/server/services/workshop/db/hitl-request.repo.ts +221 -0
- package/server/services/workshop/db/notification.repo.ts +199 -0
- package/server/services/workshop/db/outbox.repo.ts +127 -0
- package/server/services/workshop/db/scheduled-task.repo.ts +235 -0
- package/server/services/workshop/db/schema.sql +185 -0
- package/server/services/workshop/db/task.repo.ts +185 -69
- package/server/services/workshop/db/transaction.ts +72 -0
- package/server/services/workshop/dcw/dcw-controller/actuation.ts +127 -0
- package/server/services/workshop/dcw/dcw-controller/bindings.ts +83 -0
- package/server/services/workshop/dcw/dcw-controller/contracts.ts +16 -0
- package/server/services/workshop/dcw/dcw-controller/control.ts +78 -0
- package/server/services/workshop/dcw/dcw-controller/crud.ts +152 -0
- package/server/services/workshop/dcw/dcw-controller/facade.ts +19 -0
- package/server/services/workshop/dcw/dcw-controller/helpers.ts +57 -0
- package/server/services/workshop/dcw/dcw-controller/index.ts +22 -0
- package/server/services/workshop/dcw/dcw-controller/lines.ts +213 -0
- package/server/services/workshop/dcw/dcw-controller/params.ts +97 -0
- package/server/services/workshop/dcw/dcw-controller/products.ts +103 -0
- package/server/services/workshop/dcw/dcw-controller/recipes.ts +137 -0
- package/server/services/workshop/dcw/dcw-controller/state.ts +66 -0
- package/server/services/workshop/dcw/dcw-controller/types.ts +57 -0
- package/server/services/workshop/dcw/dcw-controller/write.ts +147 -0
- package/server/services/workshop/dcw/dcw-node.ts +8 -0
- package/server/services/workshop/dcw/drivers/http.ts +66 -0
- package/server/services/workshop/dcw/drivers/index.ts +26 -0
- package/server/services/workshop/dcw/drivers/mock.ts +44 -0
- package/server/services/workshop/dcw/drivers/modbus-rtu.ts +93 -0
- package/server/services/workshop/dcw/drivers/modbus.ts +139 -0
- package/server/services/workshop/dcw/drivers/mqtt.ts +108 -0
- package/server/services/workshop/dcw/drivers/opcua.ts +110 -0
- package/server/services/workshop/dcw/drivers/registry.ts +118 -0
- package/server/services/workshop/dcw/drivers/shared.ts +113 -0
- package/server/services/workshop/dcw/recipe-rollback-manager/contracts.ts +26 -0
- package/server/services/workshop/dcw/recipe-rollback-manager/facade.ts +13 -0
- package/server/services/workshop/dcw/recipe-rollback-manager/helpers.ts +30 -0
- package/server/services/workshop/dcw/recipe-rollback-manager/index.ts +17 -0
- package/server/services/workshop/dcw/recipe-rollback-manager/internal.ts +33 -0
- package/server/services/workshop/dcw/recipe-rollback-manager/judge.ts +52 -0
- package/server/services/workshop/dcw/recipe-rollback-manager/lock.ts +137 -0
- package/server/services/workshop/dcw/recipe-rollback-manager/rollback.ts +190 -0
- package/server/services/workshop/dcw/recipe-rollback-manager/state.ts +21 -0
- package/server/services/workshop/dcw/recipe-rollback-manager/sweep.ts +77 -0
- package/server/services/workshop/dcw/recipe-rollback-manager/types.ts +3 -0
- package/server/services/workshop/dcw/recipe-rollback-manager/views.ts +214 -0
- package/server/services/workshop/plugins/host/api.mjs +90 -0
- package/server/services/workshop/plugins/host/config.mjs +135 -0
- package/server/services/workshop/plugins/host/index.mjs +23 -0
- package/server/services/workshop/plugins/host/init.mjs +101 -0
- package/server/services/workshop/plugins/host/load.mjs +321 -0
- package/server/services/workshop/plugins/host/reload.mjs +94 -0
- package/server/services/workshop/plugins/host/rollback.mjs +72 -0
- package/server/services/workshop/plugins/host/services.mjs +58 -0
- package/server/services/workshop/plugins/host/state.mjs +54 -0
- package/server/services/workshop/plugins/host/toggle.mjs +64 -0
- package/server/services/workshop/plugins/host.mjs +6 -890
- package/server/services/workshop/runtime/agent-runtime/contracts.ts +25 -0
- package/server/services/workshop/runtime/agent-runtime/facade.ts +7 -0
- package/server/services/workshop/runtime/agent-runtime/helpers.ts +18 -0
- package/server/services/workshop/runtime/agent-runtime/index.ts +19 -0
- package/server/services/workshop/runtime/agent-runtime/lifecycle.ts +119 -0
- package/server/services/workshop/runtime/agent-runtime/message.ts +409 -0
- package/server/services/workshop/runtime/agent-runtime/state.ts +371 -0
- package/server/services/workshop/runtime/agent-runtime/supervise.ts +188 -0
- package/server/services/workshop/runtime/agent-runtime/types.ts +186 -0
- package/server/services/workshop/runtime/channel-runtime.ts +7 -0
- package/server/services/workshop/runtime/chat-mention.ts +164 -0
- package/server/services/workshop/runtime/chat-projection.ts +246 -0
- package/server/services/workshop/runtime/execution-mode.ts +11 -2
- package/server/services/workshop/runtime/manager/a2a.ts +283 -0
- package/server/services/workshop/runtime/manager/access.ts +483 -0
- package/server/services/workshop/runtime/manager/admin-agents.ts +268 -0
- package/server/services/workshop/runtime/manager/admin-channel.ts +276 -0
- package/server/services/workshop/runtime/manager/bus.ts +182 -0
- package/server/services/workshop/runtime/manager/channel-templates.ts +171 -0
- package/server/services/workshop/runtime/manager/chat-reply.ts +181 -0
- package/server/services/workshop/runtime/manager/chat.ts +384 -0
- package/server/services/workshop/runtime/manager/contracts.ts +210 -0
- package/server/services/workshop/runtime/manager/facade.ts +24 -0
- package/server/services/workshop/runtime/manager/helpers.ts +119 -0
- package/server/services/workshop/runtime/manager/host-tools.ts +249 -0
- package/server/services/workshop/runtime/manager/index.ts +42 -0
- package/server/services/workshop/runtime/manager/internal.ts +73 -0
- package/server/services/workshop/runtime/manager/lead-team.ts +161 -0
- package/server/services/workshop/runtime/manager/notifications.ts +108 -0
- package/server/services/workshop/runtime/manager/runtime-observe.ts +300 -0
- package/server/services/workshop/runtime/manager/runtime-wiring.ts +307 -0
- package/server/services/workshop/runtime/manager/schedules.ts +302 -0
- package/server/services/workshop/runtime/manager/state.ts +644 -0
- package/server/services/workshop/runtime/manager/tasks.ts +518 -0
- package/server/services/workshop/runtime/manager/teams.ts +148 -0
- package/server/services/workshop/runtime/manager/types.ts +245 -0
- package/server/services/workshop/runtime/manager/workspace.ts +136 -0
- package/server/services/workshop/runtime/memory/contracts.ts +18 -0
- package/server/services/workshop/runtime/memory/facade.ts +10 -0
- package/server/services/workshop/runtime/memory/governance.ts +184 -0
- package/server/services/workshop/runtime/memory/helpers.ts +196 -0
- package/server/services/workshop/runtime/memory/index.ts +27 -0
- package/server/services/workshop/runtime/memory/recall.ts +216 -0
- package/server/services/workshop/runtime/memory/score.ts +33 -0
- package/server/services/workshop/runtime/memory/state.ts +40 -0
- package/server/services/workshop/runtime/memory/types.ts +44 -0
- package/server/services/workshop/runtime/memory/write.ts +93 -0
- package/server/services/workshop/runtime/permission-scope.ts +81 -0
- package/server/services/workshop/runtime/schedule-runtime.ts +320 -0
- package/server/services/workshop/runtime/scheduler-loop/contracts.ts +29 -0
- package/server/services/workshop/runtime/scheduler-loop/execute.ts +295 -0
- package/server/services/workshop/runtime/scheduler-loop/facade.ts +7 -0
- package/server/services/workshop/runtime/scheduler-loop/helpers.ts +14 -0
- package/server/services/workshop/runtime/scheduler-loop/index.ts +16 -0
- package/server/services/workshop/runtime/scheduler-loop/rules.ts +131 -0
- package/server/services/workshop/runtime/scheduler-loop/snapshot.ts +99 -0
- package/server/services/workshop/runtime/scheduler-loop/state.ts +150 -0
- package/server/services/workshop/runtime/scheduler-loop/tick.ts +157 -0
- package/server/services/workshop/runtime/scheduler-loop/types.ts +50 -0
- package/server/services/workshop/runtime/task-engine/contracts.ts +16 -0
- package/server/services/workshop/runtime/task-engine/create.ts +145 -0
- package/server/services/workshop/runtime/task-engine/facade.ts +7 -0
- package/server/services/workshop/runtime/task-engine/helpers.ts +87 -0
- package/server/services/workshop/runtime/task-engine/index.ts +15 -0
- package/server/services/workshop/runtime/task-engine/lease.ts +79 -0
- package/server/services/workshop/runtime/task-engine/lifecycle.ts +308 -0
- package/server/services/workshop/runtime/task-engine/policy.ts +113 -0
- package/server/services/workshop/runtime/task-engine/transition.ts +146 -0
- package/server/services/workshop/runtime/task-engine/types.ts +3 -0
- package/server/services/workshop/runtime/task-engine/update.ts +116 -0
- package/server/services/workshop/runtime/task-engine/views.ts +169 -0
- package/server/services/workshop/runtime/user-notification-hub.ts +192 -0
- package/server/services/workshop/settings.ts +32 -0
- package/server/services/workshop/types/task.ts +85 -0
- package/server/utils/errors.ts +2 -0
- package/server/utils/mojibake-guard.ts +29 -0
- package/shared/config/schema.json +12 -0
- package/shared/dcw-protocol/catalog.ts +203 -0
- package/shared/dcw-protocol/closed-loop.ts +137 -0
- package/shared/dcw-protocol/index.ts +19 -0
- package/shared/dcw-protocol/line.ts +131 -0
- package/shared/dcw-protocol/recipe.ts +301 -0
- package/shared/workshop-protocol.ts +327 -5
- package/tui/aw-tui.mjs +27 -6
- package/tui/lib/pickers.mjs +21 -2
- package/.output/public/_nuxt/4sIEbgcl.js +0 -42
- package/.output/public/_nuxt/88GOowgO2.js +0 -1
- package/.output/public/_nuxt/B7gZokg82.js +0 -209
- package/.output/public/_nuxt/BC9wjTJK2.js +0 -4
- package/.output/public/_nuxt/BCGafdXT.js +0 -2
- package/.output/public/_nuxt/BDmh31hl.js +0 -1
- package/.output/public/_nuxt/BFjGV-RV2.js +0 -4108
- package/.output/public/_nuxt/BHA40aHa.js +0 -1
- package/.output/public/_nuxt/BJHKLJJU.js +0 -1
- package/.output/public/_nuxt/BN16aRdQ.js +0 -1
- package/.output/public/_nuxt/BN6iuJEg2.js +0 -1
- package/.output/public/_nuxt/BU83-NnX.js +0 -106
- package/.output/public/_nuxt/BWVhZntO.js +0 -1
- package/.output/public/_nuxt/BXxH0UQE.js +0 -1
- package/.output/public/_nuxt/BYhEUx8t.js +0 -17
- package/.output/public/_nuxt/BneN7fbu.js +0 -75
- package/.output/public/_nuxt/BsROmY89.js +0 -1
- package/.output/public/_nuxt/C0ZrS1KL.js +0 -19
- package/.output/public/_nuxt/C3J6FYKa.js +0 -1
- package/.output/public/_nuxt/C3gwHTz0.js +0 -1
- package/.output/public/_nuxt/C4DWUZ6d2.js +0 -1
- package/.output/public/_nuxt/C4oW1HsE.js +0 -1
- package/.output/public/_nuxt/C4s_niy-.js +0 -14
- package/.output/public/_nuxt/CClOy-Hz2.js +0 -1
- package/.output/public/_nuxt/CFxtLwjp.js +0 -1
- package/.output/public/_nuxt/CS50LB_l.js +0 -1
- package/.output/public/_nuxt/CSkxz2NX.js +0 -3
- package/.output/public/_nuxt/CWoJjWmZ.js +0 -1
- package/.output/public/_nuxt/CYWVXZTS2.js +0 -7
- package/.output/public/_nuxt/C__m4EhR.js +0 -1
- package/.output/public/_nuxt/CoNpEq5X.js +0 -1
- package/.output/public/_nuxt/Crod5z2f.js +0 -1
- package/.output/public/_nuxt/CssgYAAL2.js +0 -1
- package/.output/public/_nuxt/CzlMDL5H.js +0 -13
- package/.output/public/_nuxt/D0ZBQrN_2.js +0 -1
- package/.output/public/_nuxt/D6mbFA-a.js +0 -1
- package/.output/public/_nuxt/D6uzXueg.js +0 -2
- package/.output/public/_nuxt/D98NH8kG.js +0 -1
- package/.output/public/_nuxt/D9Oidm5I.js +0 -1
- package/.output/public/_nuxt/DBHNsT1Y.js +0 -23
- package/.output/public/_nuxt/DDgJcSET.js +0 -19
- package/.output/public/_nuxt/DGIuvyRN2.js +0 -1
- package/.output/public/_nuxt/DGbd-UiG.js +0 -1
- package/.output/public/_nuxt/DGj-WEBg.js +0 -1
- package/.output/public/_nuxt/DHFrCUxA2.js +0 -13
- package/.output/public/_nuxt/DIaZ2PTo2.js +0 -1
- package/.output/public/_nuxt/DPnwL14Y.js +0 -1
- package/.output/public/_nuxt/DR12_WCA.js +0 -3
- package/.output/public/_nuxt/DS8qj8EL.js +0 -1
- package/.output/public/_nuxt/DTFCy6h-.js +0 -1
- package/.output/public/_nuxt/DUjW5Yq5.js +0 -1
- package/.output/public/_nuxt/DYr1VMiQ.js +0 -6
- package/.output/public/_nuxt/DZtm4l-P2.js +0 -1
- package/.output/public/_nuxt/D_OTvQza.js +0 -1
- package/.output/public/_nuxt/Dap5PmTL.js +0 -2
- package/.output/public/_nuxt/DdD9mCHM.js +0 -1
- package/.output/public/_nuxt/DeJykBFf.js +0 -9
- package/.output/public/_nuxt/DeyBFhKH2.js +0 -4
- package/.output/public/_nuxt/DkGQ7lnb2.js +0 -1
- package/.output/public/_nuxt/Dqw1lpr-.js +0 -1
- package/.output/public/_nuxt/DtYHUi5Z.js +0 -1
- package/.output/public/_nuxt/DvLxILQP.js +0 -1
- package/.output/public/_nuxt/Dz_Q3Lpw2.js +0 -1
- package/.output/public/_nuxt/DzbFpZRk.js +0 -3
- package/.output/public/_nuxt/IREZ1eC2.js +0 -1
- package/.output/public/_nuxt/JCoYF4IJ.js +0 -31
- package/.output/public/_nuxt/JTLM-kqr2.js +0 -1
- package/.output/public/_nuxt/KZQX7xoM.js +0 -3
- package/.output/public/_nuxt/OmpTerminalPanel.B_Pl5Ynm.css +0 -1
- package/.output/public/_nuxt/RYFtK4qZ2.js +0 -1
- package/.output/public/_nuxt/TownView.f-YOdXRs.css +0 -1
- package/.output/public/_nuxt/YnQxK3eG.js +0 -1
- package/.output/public/_nuxt/_id_.D9ymtHB5.css +0 -1
- package/.output/public/_nuxt/_id_.jf-_8E_c.css +0 -1
- package/.output/public/_nuxt/_wsId_.B4WiqkIS.css +0 -1
- package/.output/public/_nuxt/aLLjF2K-2.js +0 -1
- package/.output/public/_nuxt/aWWIh8YL.js +0 -24
- package/.output/public/_nuxt/agents.ZD0rVdHE.css +0 -1
- package/.output/public/_nuxt/aml.BN8ZEW4h.css +0 -1
- package/.output/public/_nuxt/builds/meta/78a295bb-2072-48a4-8b46-6d545fdae743.json +0 -1
- package/.output/public/_nuxt/ca4TJ3Op.js +0 -1
- package/.output/public/_nuxt/channel-templates.BOtahzPi.css +0 -1
- package/.output/public/_nuxt/daq.DjSgY94l.css +0 -1
- package/.output/public/_nuxt/dcw.8Y7FqCVN.css +0 -1
- package/.output/public/_nuxt/default.J32SgqFY.css +0 -1
- package/.output/public/_nuxt/entry.bdm0tv6M.css +0 -1
- package/.output/public/_nuxt/eqq3wWQF.js +0 -18
- package/.output/public/_nuxt/jZsY8yYz.js +0 -1
- package/.output/public/_nuxt/logs.BpKzmKsh.css +0 -1
- package/.output/public/_nuxt/monitor.D9pe7qN6.css +0 -1
- package/.output/public/_nuxt/pages.D7aSDlUI.css +0 -1
- package/.output/public/_nuxt/pdk8jLGT.js +0 -1
- package/.output/public/_nuxt/permissions.DwTVlFe6.css +0 -1
- package/.output/public/_nuxt/plugins.sb4nDGqr.css +0 -1
- package/.output/public/_nuxt/qr6R6Ha1.js +0 -1
- package/.output/public/_nuxt/settings.BTgR-NiE.css +0 -1
- package/.output/public/_nuxt/teams.CxnxKVVs.css +0 -1
- package/.output/public/_nuxt/tokens.BFY7qv4f.css +0 -1
- package/.output/public/_nuxt/town.BLrD9-WH.css +0 -1
- package/.output/public/_nuxt/users.DLgZ1D0h.css +0 -1
- package/.output/public/_nuxt/vRTjoWqd.js +0 -1
- package/.output/public/_nuxt/workshop.CR_QzeAN.css +0 -1
- package/.output/public/_nuxt/xh3_vYcu.js +0 -1
- package/.output/public/_nuxt/yF0aYMF1.js +0 -4
- package/.output/public/_nuxt/zq5t1vyX.js +0 -1
- package/.output/server/chunks/build/Checkbox-eax2M3o-.mjs +0 -207
- package/.output/server/chunks/build/Checkbox-eax2M3o-.mjs.map +0 -1
- package/.output/server/chunks/build/OmpTerminalPanel-Cu_s4i0A.mjs +0 -915
- package/.output/server/chunks/build/OmpTerminalPanel-Cu_s4i0A.mjs.map +0 -1
- package/.output/server/chunks/build/PluginSlot-CcR4Ys3T.mjs +0 -400
- package/.output/server/chunks/build/PluginSlot-CcR4Ys3T.mjs.map +0 -1
- package/.output/server/chunks/build/TownView--otsnr_7.mjs +0 -2790
- package/.output/server/chunks/build/TownView--otsnr_7.mjs.map +0 -1
- package/.output/server/chunks/build/_id_-Brv-JSvr.mjs +0 -803
- package/.output/server/chunks/build/_id_-Brv-JSvr.mjs.map +0 -1
- package/.output/server/chunks/build/_id_-D9pl1OZy.mjs +0 -381
- package/.output/server/chunks/build/_id_-D9pl1OZy.mjs.map +0 -1
- package/.output/server/chunks/build/_wsId_-BASfDf0s.mjs +0 -10066
- package/.output/server/chunks/build/_wsId_-BASfDf0s.mjs.map +0 -1
- package/.output/server/chunks/build/agents-CLQqJF2y.mjs +0 -1111
- package/.output/server/chunks/build/agents-CLQqJF2y.mjs.map +0 -1
- package/.output/server/chunks/build/aml-_7FdFL2p.mjs +0 -588
- package/.output/server/chunks/build/aml-_7FdFL2p.mjs.map +0 -1
- package/.output/server/chunks/build/api-error-dul30BYQ.mjs +0 -47
- package/.output/server/chunks/build/api-error-dul30BYQ.mjs.map +0 -1
- package/.output/server/chunks/build/apiClient-BwnfOQU8.mjs +0 -81
- package/.output/server/chunks/build/apiClient-BwnfOQU8.mjs.map +0 -1
- package/.output/server/chunks/build/button-DEcZFcqN.mjs +0 -1938
- package/.output/server/chunks/build/button-DEcZFcqN.mjs.map +0 -1
- package/.output/server/chunks/build/card-Dipvaykh.mjs +0 -955
- package/.output/server/chunks/build/card-Dipvaykh.mjs.map +0 -1
- package/.output/server/chunks/build/channel-templates-p6SZNjlM.mjs +0 -835
- package/.output/server/chunks/build/channel-templates-p6SZNjlM.mjs.map +0 -1
- package/.output/server/chunks/build/checkbox-8fB-wI-T.mjs +0 -439
- package/.output/server/chunks/build/checkbox-8fB-wI-T.mjs.map +0 -1
- package/.output/server/chunks/build/client-only-D5BzSQc6.mjs +0 -43
- package/.output/server/chunks/build/client-only-D5BzSQc6.mjs.map +0 -1
- package/.output/server/chunks/build/daq-DkMvD8sg.mjs +0 -1058
- package/.output/server/chunks/build/daq-DkMvD8sg.mjs.map +0 -1
- package/.output/server/chunks/build/dcw-C1jgVJkO.mjs +0 -249
- package/.output/server/chunks/build/dcw-C1jgVJkO.mjs.map +0 -1
- package/.output/server/chunks/build/default-BE4r0FZN.mjs +0 -1486
- package/.output/server/chunks/build/default-BE4r0FZN.mjs.map +0 -1
- package/.output/server/chunks/build/drawer-Cmh90erI.mjs +0 -836
- package/.output/server/chunks/build/drawer-Cmh90erI.mjs.map +0 -1
- package/.output/server/chunks/build/dropdown-CGQ0KAzA.mjs +0 -14
- package/.output/server/chunks/build/dropdown-CGQ0KAzA.mjs.map +0 -1
- package/.output/server/chunks/build/dropdown-vhn1NPFs.mjs +0 -558
- package/.output/server/chunks/build/dropdown-vhn1NPFs.mjs.map +0 -1
- package/.output/server/chunks/build/initDefaultProps-DIbWwP4R.mjs +0 -21
- package/.output/server/chunks/build/initDefaultProps-DIbWwP4R.mjs.map +0 -1
- package/.output/server/chunks/build/input-number-DzpyYB99.mjs +0 -1521
- package/.output/server/chunks/build/input-number-DzpyYB99.mjs.map +0 -1
- package/.output/server/chunks/build/input-sf_BT6o6.mjs +0 -2617
- package/.output/server/chunks/build/input-sf_BT6o6.mjs.map +0 -1
- package/.output/server/chunks/build/logs--dByNyhw.mjs +0 -198
- package/.output/server/chunks/build/logs--dByNyhw.mjs.map +0 -1
- package/.output/server/chunks/build/menu-WCrlpYVh.mjs +0 -1950
- package/.output/server/chunks/build/menu-WCrlpYVh.mjs.map +0 -1
- package/.output/server/chunks/build/modal-yvUquPj5.mjs +0 -1272
- package/.output/server/chunks/build/modal-yvUquPj5.mjs.map +0 -1
- package/.output/server/chunks/build/monitor-C0okQaXz.mjs +0 -2212
- package/.output/server/chunks/build/monitor-C0okQaXz.mjs.map +0 -1
- package/.output/server/chunks/build/pages-B0bUWnC6.mjs +0 -512
- package/.output/server/chunks/build/pages-B0bUWnC6.mjs.map +0 -1
- package/.output/server/chunks/build/permissions-68SsNjex.mjs +0 -629
- package/.output/server/chunks/build/permissions-68SsNjex.mjs.map +0 -1
- package/.output/server/chunks/build/plugins-lGlUr1lj.mjs +0 -240
- package/.output/server/chunks/build/plugins-lGlUr1lj.mjs.map +0 -1
- package/.output/server/chunks/build/popconfirm-CRaHcz5O.mjs +0 -342
- package/.output/server/chunks/build/popconfirm-CRaHcz5O.mjs.map +0 -1
- package/.output/server/chunks/build/segmented-3TrWo0lS.mjs +0 -5047
- package/.output/server/chunks/build/segmented-3TrWo0lS.mjs.map +0 -1
- package/.output/server/chunks/build/select-D-CeE5vY.mjs +0 -3923
- package/.output/server/chunks/build/select-D-CeE5vY.mjs.map +0 -1
- package/.output/server/chunks/build/settings-Ddd1qx2Z.mjs +0 -1174
- package/.output/server/chunks/build/settings-Ddd1qx2Z.mjs.map +0 -1
- package/.output/server/chunks/build/space-CCBYAkXt.mjs +0 -197
- package/.output/server/chunks/build/space-CCBYAkXt.mjs.map +0 -1
- package/.output/server/chunks/build/spin-HVY81X_L.mjs +0 -358
- package/.output/server/chunks/build/spin-HVY81X_L.mjs.map +0 -1
- package/.output/server/chunks/build/switch-CelRXpcy.mjs +0 -377
- package/.output/server/chunks/build/switch-CelRXpcy.mjs.map +0 -1
- package/.output/server/chunks/build/table-BjSWwmic.mjs +0 -8883
- package/.output/server/chunks/build/table-BjSWwmic.mjs.map +0 -1
- package/.output/server/chunks/build/tabs-BM3tAh_J.mjs +0 -1925
- package/.output/server/chunks/build/tabs-BM3tAh_J.mjs.map +0 -1
- package/.output/server/chunks/build/tag-DNE9vXc0.mjs +0 -1029
- package/.output/server/chunks/build/tag-DNE9vXc0.mjs.map +0 -1
- package/.output/server/chunks/build/teams-MEDLZdMn.mjs +0 -1215
- package/.output/server/chunks/build/teams-MEDLZdMn.mjs.map +0 -1
- package/.output/server/chunks/build/tokens-C7fTM5C2.mjs +0 -888
- package/.output/server/chunks/build/tokens-C7fTM5C2.mjs.map +0 -1
- package/.output/server/chunks/build/town-BUqRobze.mjs +0 -52
- package/.output/server/chunks/build/town-BUqRobze.mjs.map +0 -1
- package/.output/server/chunks/build/town-CoQdblbZ.mjs +0 -124
- package/.output/server/chunks/build/town-CoQdblbZ.mjs.map +0 -1
- package/.output/server/chunks/build/useDaqStream-DfCFpb3p.mjs +0 -395
- package/.output/server/chunks/build/useDaqStream-DfCFpb3p.mjs.map +0 -1
- package/.output/server/chunks/build/useDcwStream-UPiX-3iO.mjs +0 -435
- package/.output/server/chunks/build/useDcwStream-UPiX-3iO.mjs.map +0 -1
- package/.output/server/chunks/build/useDeviceTwins-Yko6nNPF.mjs +0 -128
- package/.output/server/chunks/build/useDeviceTwins-Yko6nNPF.mjs.map +0 -1
- package/.output/server/chunks/build/useOpsLog-CO_VWh7t.mjs +0 -137
- package/.output/server/chunks/build/useOpsLog-CO_VWh7t.mjs.map +0 -1
- package/.output/server/chunks/build/useWorkshopApi-NyjL0L3j.mjs +0 -90
- package/.output/server/chunks/build/useWorkshopApi-NyjL0L3j.mjs.map +0 -1
- package/.output/server/chunks/build/useWorkshopWs-C_I7q9As.mjs +0 -1319
- package/.output/server/chunks/build/useWorkshopWs-C_I7q9As.mjs.map +0 -1
- package/.output/server/chunks/build/users-Cfd5-l2-.mjs +0 -561
- package/.output/server/chunks/build/users-Cfd5-l2-.mjs.map +0 -1
- package/.output/server/chunks/build/workshop-BL5bhfs0.mjs +0 -931
- package/.output/server/chunks/build/workshop-BL5bhfs0.mjs.map +0 -1
- package/.output/server/chunks/build/workspaces-Bgz5Ekwp.mjs +0 -139
- package/.output/server/chunks/build/workspaces-Bgz5Ekwp.mjs.map +0 -1
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.continue.json +0 -1
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.json +0 -1
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/054bb4bc4e4217a2ab0b0c6b663747214a9af464f5568ea60e784704e340bb0f.source +0 -153
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/0554197c3d2bac203a1f5c48a5d52495d1336db1bd7aa4f4c19875810363e684.source +0 -153
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/05c203d12a954fffba942e1d626e3f5fec05b18a5aef2bac003b7884a4b77406.source +0 -431
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/145a6e6ba811af35aed31e0cc3211f53706a14980fa069ee9e3c1591139f96f0.source +0 -180
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/1ae0fb281f97899c025e368ca73f41349b01fb5f85b19e08d7ebccff49095ed5.source +0 -112
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/20ee9967aa0541c516db7575bdd4a1a745939bc7016bd10d7635903113edb8ec.source +0 -227
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/233751843aeba9d4c5263f275b8102de00443c06f286918cbd23e0b6729e01aa.source +0 -131
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/263c2e05b425e5c322e60c6475279f963bab3a873eb521b21df7fec935a46597.source +0 -160
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/2ac5eaac28eaaee2178c4b32014df8e732f9dd432a285a872f5b7f31c1395fa4.source +0 -157
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/33a0db0d2d95d2c11d0720090458e5fba1425376f5ceb5b422d1d3471be3226b.source +0 -348
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/359e474ae83db35e4615d3728de6bf694b64d4dce9b536730a3b6518f9c0494a.source +0 -296
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/37a4c3c31c4b70f6b719c53d1e9c4fcba603f31adfdd11bb6ef9f0d094923c56.source +0 -285
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/461feca1c14a695eb8b0f17a6f95fe97eeceabc0510faaea4ba6d25bde147d8d.source +0 -217
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/4cfadbda8591f29d5e1e230dc000e701b756e850441b3900fb596a4d2f6e98c2.source +0 -301
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/50ca2810cf0b248c96948994c0c404531bbad6e553166b6816b3b03bfa0f75d2.source +0 -151
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/52d9e8cb591a9ae86e238c3620a610cd386717bee222c0f37b25b2ac7e523dc4.source +0 -135
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/6c87daafa4c006560a954636a97333f6123153c9d34d96203f7ee3a14e556357.source +0 -156
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/77bc47854b577900621cacf1697f81fa2bc005af129d37614a97a3441c08f99f.source +0 -72
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/7bbb789d3a527817118cf8399ed1861d6a962ade10878fd07223435d333a7089.source +0 -469
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/86b7c1daafc8b60ba9982dde5bf1bcc74265709483c176e71f23ec6fc267451f.source +0 -259
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/8df66d62bd41984d0970e8236882080c5085ac937d07a29779463d6a03b37c76.source +0 -97
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/9962f2d1b3a85137bd40cc835725600b39f89ee02a27c06b2e0402e1eafde115.source +0 -277
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a1090ea066b792cb201739f4994e37bf7c12f52e805239a1eab87f18a537f266.source +0 -171
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a5241091650134d0919897e086a606a3846c8b2b54f8b4c84a8696c145ac3e9e.source +0 -425
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/af19fa4c6e34d5540028be4b17600161283af3a2a2dfc61a3fdc0834436a0c14.source +0 -99
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3713d78e2b2f93905ff6be8d58a8d0a2f42dea322eb67c987fda22ff6a084d5.source +0 -325
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3849943c3aff102d1af815631fc843a2a52c17646a4b04696ece323e483bbec.source +0 -332
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/bac735a3803a6650b7f1eb8f587e6dc5c68ea055000c3c99a11789438b78e837.source +0 -43
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d32c24d61eb8b33c73e5a8fc7e4dc7ef437aed238ee4371c2b00617e6104090a.source +0 -154
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d5170dc9a9879fda04fdf91d8a110d4efb6787ee38dda596d4da899883d36a72.source +0 -531
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/dc59bcd7bb397be63ba2f2c3fec23f3782dcb17d2d75a9981eeced761f33eb32.source +0 -439
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/eb5ff8cf66b92aec7bf6726781d8df74faa2b030c3aaac17d6b00c1fc04c9b32.source +0 -323
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f10615a6fae18436e70d1201b2d44fdae3174a3aa7bb4db97dea1b3ed291d721.source +0 -433
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f36d3e271668bc1260c2823542d22dbf1d0f7041ff16b1ac175bce2256e582ca.source +0 -280
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f94449380af09f31ac352152007146a75cc80bae8d6411ffe57b2c56f933b0ff.source +0 -117
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/fe4dfd81178fd6b648335f6405d9ede71319aebde796008e50db21f8adf44c99.source +0 -160
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/ffb4e3aa548cfdadabaa44ab599f9b6a3ca6288176a2cc7a4b2fc2a12b5fb848.source +0 -101
- package/scripts/.mimosa/hook-status/sess_aeaecb58-8833-4b83-b93c-050467d21e03-d944d71bb3.json +0 -14
- package/scripts/_dbg-audit-all-pages.mjs +0 -43
- package/scripts/_dbg-daq-frames-e2e.mjs +0 -118
- package/scripts/_dbg-daq-rowstyle-verify.mjs +0 -112
- package/scripts/_dbg-hitl-timeout-e2e.mjs +0 -93
- package/scripts/_dbg-live-line-e2e.mjs +0 -279
- package/scripts/_dbg-minimal-omp.mjs +0 -77
- package/scripts/_dbg-mode-nav.mjs +0 -173
- package/scripts/_dbg-real-scenario-timeline.log +0 -55
- package/scripts/_dbg-team-repro.mjs +0 -76
- package/scripts/_dbg-town-agents-probe.mjs +0 -60
- package/scripts/_dbg-visual-acceptance.mjs +0 -63
- package/scripts/_townview_style.css +0 -1185
- package/scripts/_townview_template.html +0 -1215
- package/scripts/test-agent-brain.ts +0 -135
- package/scripts/test-game-session.ts +0 -160
- package/scripts/test-protocol.ts +0 -156
- package/scripts/verify-game.mjs +0 -477
- package/server/api/workshop/ws.ts +0 -763
- package/server/data/agent-node-bindings.json +0 -554
- package/server/data/daq-templates.json +0 -16
- package/server/data/daqs.json +0 -7464
- package/server/data/dcw-lines.json +0 -1787
- package/server/data/dcw-products.json +0 -1626
- package/server/data/dcw-recipes.json +0 -4672
- package/server/data/dcw-rollback.json +0 -20754
- package/server/data/dcw-runs.json +0 -6354
- package/server/data/dcw-templates.json +0 -146
- package/server/data/dcw-writes.json +0 -15650
- package/server/data/dcws.json +0 -9033
- package/server/data/device-twins.json +0 -133
- package/server/data/line-runs.json +0 -322
- package/server/data/scene-layouts.json +0 -277
- package/server/services/system-config.ts +0 -629
- package/server/services/workshop/agents/adapters/omp-rpc-client.ts +0 -604
- package/server/services/workshop/agents/codex-agent.ts +0 -743
- package/server/services/workshop/agents/harness-terminal.ts +0 -625
- package/server/services/workshop/agents/host-tool-bridge.ts +0 -714
- package/server/services/workshop/agents/industrial-tools.ts +0 -1680
- package/server/services/workshop/agents/omp-agent.ts +0 -1199
- package/server/services/workshop/agents/opencode-agent.ts +0 -960
- package/server/services/workshop/aml/job-orchestrator.ts +0 -639
- package/server/services/workshop/daq/daq-controller.ts +0 -1284
- package/server/services/workshop/daq/drivers.ts +0 -1185
- package/server/services/workshop/db/database.ts +0 -1044
- package/server/services/workshop/dcw/dcw-controller.ts +0 -1176
- package/server/services/workshop/dcw/drivers.ts +0 -731
- package/server/services/workshop/dcw/recipe-rollback-manager.ts +0 -713
- package/server/services/workshop/runtime/agent-runtime.ts +0 -821
- package/server/services/workshop/runtime/manager.ts +0 -3050
- package/server/services/workshop/runtime/memory.ts +0 -681
- package/server/services/workshop/runtime/scheduler-loop.ts +0 -799
- package/server/services/workshop/runtime/task-engine.ts +0 -574
- package/shared/dcw-protocol.ts +0 -747
- /package/.output/public/_nuxt/{BrIJiJSN.js → BrIJiJSN2.js} +0 -0
- /package/.output/public/_nuxt/{CNs_Ozdc2.js → CNs_Ozdc.js} +0 -0
- /package/.output/public/_nuxt/{DIOBYJe3.js → DIOBYJe32.js} +0 -0
- /package/.output/public/_nuxt/{lO5tpDZ5.js → lO5tpDZ52.js} +0 -0
|
@@ -15,773 +15,167 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import * as THREE from 'three'
|
|
17
17
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
|
|
21
|
-
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
|
|
22
|
-
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js'
|
|
23
|
-
import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js'
|
|
18
|
+
import type { TransformControls } from 'three/examples/jsm/controls/TransformControls.js'
|
|
19
|
+
import type { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
|
|
24
20
|
import type { AepEnvelope } from '#shared/workshop-protocol'
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
21
|
+
import type { TownBubbleKind } from '#shared/town-protocol'
|
|
22
|
+
import type { ActionKind, ActionContext } from '#shared/town-behavior'
|
|
27
23
|
// 纯几何/色彩/尺度决策层(与测试共用,零渲染依赖)
|
|
28
24
|
import {
|
|
29
|
-
|
|
30
|
-
WORLD_CX, WORLD_CZ, WORLD_H, WORLD_W,
|
|
31
|
-
boundaryPoints, bubbleDisplayMs, channelColorNum, drainDisplayMs,
|
|
32
|
-
clampToAgentRange, clampToBoundary, distToRangeBoundary,
|
|
33
|
-
normLayout, pointInBoundary, toLocal,
|
|
25
|
+
WORLD_CX, WORLD_CZ,
|
|
34
26
|
type AgentRangeLayout, type ChannelLayout,
|
|
35
27
|
} from '#shared/town-scene-math'
|
|
36
28
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
29
|
+
// 轨道相机与相机交互自本文件抽出(见 scene/town-scene3d-camera.ts)
|
|
30
|
+
import {
|
|
31
|
+
autoFrameTo, flyToPreset, focusTo, getAutoOrbit, getCameraPose, orbitBy, panBy, panByScreen,
|
|
32
|
+
panWorldBy, resetView, screenToWorld, setAutoOrbit, setViewPreset, worldToScreen, zoomBy,
|
|
33
|
+
} from './scene/town-scene3d-camera'
|
|
34
|
+
// 渲染核心初始化自本文件抽出(见 scene/town-scene3d-renderer.ts)
|
|
35
|
+
import { initRendererCore } from './scene/town-scene3d-renderer'
|
|
36
|
+
// 类型契约 / 面向对象节点(Channel 领地 / 角色 / 数字孪生设备)自本文件抽出,
|
|
37
|
+
// 只通过宿主契约回调场景 —— 细节见 town-scene3d-types.ts 与 town-scene3d-nodes.ts
|
|
38
|
+
// 信息接收器 + 聊天气泡自本文件抽出(见 scene/town-scene3d-bubbles.ts)
|
|
39
|
+
import { drainReceivers, enqueueBubble } from './scene/town-scene3d-bubbles'
|
|
40
|
+
// 数采绑定链路 + 薄膜 web 自本文件抽出(见 scene/town-scene3d-links.ts)
|
|
41
|
+
import { deviceTopY, rebuildFilmWeb, refreshDaqLinks, syncDaqLinks } from './scene/town-scene3d-links'
|
|
42
|
+
// 事件共鸣 + 行为 FSM 入口 + WS 事件入口自本文件抽出(见 scene/town-scene3d-events.ts)
|
|
43
|
+
import { behaviorActionLabel, emitResonance, handleTownEvent, startBehavior } from './scene/town-scene3d-events'
|
|
44
|
+
// 调试 / HUD 数据面自本文件抽出(见 scene/town-scene3d-hud.ts)
|
|
45
|
+
import { getDebugState, getMinimapState, getRecentActivity } from './scene/town-scene3d-hud'
|
|
46
|
+
// 数字孪生设备节点层自本文件抽出(见 scene/town-scene3d-devices.ts)
|
|
47
|
+
import {
|
|
48
|
+
getDeviceModelRef, getDeviceName, getDeviceNodes, persistAllDevices, persistDeviceTransform,
|
|
49
|
+
recreateDeviceNode, removeDevice, renameDevice, renameDeviceSprite, spawnDeviceNode,
|
|
50
|
+
swapDeviceModel, swapDeviceModelSprite, syncDevices, updateDeviceNode,
|
|
51
|
+
} from './scene/town-scene3d-devices'
|
|
52
|
+
// GLB 加载与 PBR 材质增强自本文件抽出(见 scene/town-scene3d-models.ts)
|
|
53
|
+
import { enhancePbrMaterials } from './scene/town-scene3d-models'
|
|
54
|
+
// 角色节点层 + 模型库挂载自本文件抽出(见 scene/town-scene3d-agents.ts)
|
|
55
|
+
import {
|
|
56
|
+
dropModelOnWorld, ensureAgent, getAgentClips, getAgentModel, getAgentName,
|
|
57
|
+
registerModelsFromList, setAnimPref, swapAgentModel,
|
|
58
|
+
} from './scene/town-scene3d-agents'
|
|
59
|
+
// 频道领地放置层自本文件抽出(见 scene/town-scene3d-blocks.ts)
|
|
60
|
+
import {
|
|
61
|
+
applyLayoutToBlock, applySceneLayouts, blockLayout, buildBlocks, dropChannelOnWorld,
|
|
62
|
+
getAllChannelLayouts, getCameraTarget, getChannelLayout, hasChannel, placedChannels,
|
|
63
|
+
removeChannel, trackLayout, updateChannelLayout,
|
|
64
|
+
} from './scene/town-scene3d-blocks'
|
|
65
|
+
// 渲染循环 + 画质阶梯自本文件抽出(见 scene/town-scene3d-loop.ts)
|
|
66
|
+
import { adaptQuality, applyPixelRatio, applyQuality, setQualityMode, startRenderLoop } from './scene/town-scene3d-loop'
|
|
67
|
+
// 拾取 / 选中 / 缩放旋转自本文件抽出(见 scene/town-scene3d-selection.ts)
|
|
68
|
+
import {
|
|
69
|
+
getModelRotation, getSelectedChannel, getSelectedScale, isGizmoBusy, persistScale, pickAt,
|
|
70
|
+
pickBeacon, pickChannel, pickChannelEdge, registerScalable, selectChannel, setExposure,
|
|
71
|
+
setModelRotation, setModelScale, setSelected, setTerritoryOpacity, setTransformMode,
|
|
72
|
+
} from './scene/town-scene3d-selection'
|
|
73
|
+
// 实例资源释放与整场重置自本文件抽出(见 scene/town-scene3d-lifecycle.ts)
|
|
74
|
+
import {
|
|
75
|
+
dispose as disposeScene, disposeAgentAssets, disposeCanvasTextures, disposeDeviceAssets,
|
|
76
|
+
resetAll as resetScene,
|
|
77
|
+
} from './scene/town-scene3d-lifecycle'
|
|
78
|
+
// 编辑/浏览模式 + 场景对象拖拽(设备/角色/频道/边界手柄)自本文件抽出(见 scene/town-scene3d-editing.ts)
|
|
79
|
+
import {
|
|
80
|
+
applyBlockMove, applyResize, boundaryHandlePoints, clampAgentsToBoundary, endPointerDrag, getMode,
|
|
81
|
+
getSnap, isPointerDragging, movePointerDrag, pickResizeHandle, refreshChannelHandles, setMode,
|
|
82
|
+
setSnap, snapWorld, tryStartPointerDrag,
|
|
83
|
+
} from './scene/town-scene3d-editing'
|
|
84
|
+
// Agent 独立活动范围(框选绘制/整框平移/手柄缩放/面板读写)自本文件抽出(见 scene/town-scene3d-ranges.ts)
|
|
85
|
+
import {
|
|
86
|
+
applyAgentRangeResize, cancelRangeDraw, clearAgentRange, commitAgentRange, emitAgentRangeChanged,
|
|
87
|
+
getAgentRange, hitAgentRangeBoundary, isRangeDrawing, pickAgentRangeHandle, rangeLineVisible,
|
|
88
|
+
rangeLineVisibleFor, refreshAgentRangeHandles, refreshAgentRangeLines, setAgentRangeScene,
|
|
89
|
+
startRangeDraw, updateRangeDrawPreview,
|
|
90
|
+
} from './scene/town-scene3d-ranges'
|
|
91
|
+
import type { Agent3D, Block3D, DeviceNode } from './scene/town-scene3d-nodes'
|
|
92
|
+
import type {
|
|
93
|
+
AgentHost, AgentRangeHandle, BlockHost, ChannelMessageReceiver, ChannelResizeHandle, DaqLink,
|
|
94
|
+
DeviceApi, DeviceHost, DeviceTwinSync, ModelInfo, PointerDragState, ScaledTarget,
|
|
95
|
+
SelectedTarget, TownEntityInput, TownEventMap, TownScene3DMode, TownSceneHost,
|
|
96
|
+
} from './scene/town-scene3d-types'
|
|
100
97
|
|
|
101
|
-
|
|
102
|
-
export
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
harness: string
|
|
110
|
-
state: 'idle' | 'busy' | 'stopped'
|
|
111
|
-
currentTaskId?: string | null
|
|
112
|
-
currentTaskTitle?: string | null
|
|
113
|
-
currentTaskProgress?: number | null
|
|
114
|
-
modelRef?: string | null
|
|
115
|
-
/** 管理员布局落点(来自 config.homeX/homeZ;缺省 = 领地环形排布) */
|
|
116
|
-
homeX?: number | null
|
|
117
|
-
homeZ?: number | null
|
|
118
|
-
/** 管理员布局活动范围(来自 config.range;缺省 = 沿用频道边界) */
|
|
119
|
-
range?: AgentRangeLayout | null
|
|
120
|
-
}>
|
|
121
|
-
}
|
|
98
|
+
export type { AgentRangeLayout, ChannelLayout } from '#shared/town-scene-math'
|
|
99
|
+
export {
|
|
100
|
+
Block3D, Agent3D, DeviceNode, makeBoundary,
|
|
101
|
+
} from './scene/town-scene3d-nodes'
|
|
102
|
+
export type {
|
|
103
|
+
BehaviorState, BubbleMsg, ChannelMessageReceiver, DeviceTransformPatch,
|
|
104
|
+
DeviceTwinSync, ModelInfo, ScaledTarget, TownEntityInput, TownEventMap, TownScene3DMode,
|
|
105
|
+
} from './scene/town-scene3d-types'
|
|
122
106
|
|
|
123
107
|
/** 频道布局(3D 小镇放置):与共享 AepSceneLayout/useSceneLayouts 同构 —— 定义见 #shared/town-scene-math */
|
|
124
108
|
|
|
125
109
|
/** Agent 独立活动范围(编辑模式框选绘制/手柄调整;经 config.range 持久化)。
|
|
126
110
|
* 缺省(null)= 未设置,该 Agent 沿用频道边界活动。 —— 定义见 #shared/town-scene-math */
|
|
127
111
|
|
|
128
|
-
|
|
129
|
-
* 数据来自 scene-layouts 持久化,由场景按数据库元数据实例化,并注入宿主控制器。 */
|
|
130
|
-
class Block3D {
|
|
131
|
-
channelId!: string
|
|
132
|
-
name!: string
|
|
133
|
-
x!: number
|
|
134
|
-
z!: number
|
|
135
|
-
radiusX!: number
|
|
136
|
-
radiusZ!: number
|
|
137
|
-
shape!: 'ellipse' | 'rect'
|
|
138
|
-
rotationY!: number
|
|
139
|
-
color!: number
|
|
140
|
-
platform!: THREE.Mesh
|
|
141
|
-
/** 领地边缘发光环(工业孪生边界告示;随平台移动) */
|
|
142
|
-
padRing!: THREE.Mesh
|
|
143
|
-
/** 中央信标(HMI 定位销:点击 → 定位频道中心 + 唤醒边界编辑) */
|
|
144
|
-
beacon!: THREE.Group
|
|
145
|
-
/** 活动边界(编辑高亮) */
|
|
146
|
-
boundary!: THREE.LineLoop
|
|
147
|
-
label!: THREE.Sprite
|
|
148
|
-
/** 本频道的 Agent3D 成员实例(面向对象聚合) */
|
|
149
|
-
members: Agent3D[] = []
|
|
150
|
-
/** 宿主场景控制器(实例化后注入;提供跨实体服务) */
|
|
151
|
-
host!: TownScene3D
|
|
152
|
-
|
|
153
|
-
constructor(init: {
|
|
154
|
-
channelId: string
|
|
155
|
-
name: string
|
|
156
|
-
x: number
|
|
157
|
-
z: number
|
|
158
|
-
radiusX: number
|
|
159
|
-
radiusZ: number
|
|
160
|
-
shape: 'ellipse' | 'rect'
|
|
161
|
-
rotationY: number
|
|
162
|
-
color: number
|
|
163
|
-
platform: THREE.Mesh
|
|
164
|
-
padRing: THREE.Mesh
|
|
165
|
-
beacon: THREE.Group
|
|
166
|
-
boundary: THREE.LineLoop
|
|
167
|
-
label: THREE.Sprite
|
|
168
|
-
}) {
|
|
169
|
-
Object.assign(this, init)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/** 当前布局(与共享 ChannelLayout 同构;供边界面板/落库/E2E) */
|
|
173
|
-
layout(): ChannelLayout {
|
|
174
|
-
return { channelId: this.channelId, x: this.x, z: this.z, radiusX: this.radiusX, radiusZ: this.radiusZ, shape: this.shape, rotationY: this.rotationY }
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/** 归一化布局(radius 钳制下限) */
|
|
178
|
-
normLayout(): ChannelLayout {
|
|
179
|
-
return normLayout(this.layout())
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/** 边界手柄本地坐标(矩形四角 / 椭圆轴向四点;radius 是半轴或半宽)。 */
|
|
183
|
-
handlePoints(): Array<[number, number]> {
|
|
184
|
-
if (this.shape === 'rect') return [[this.radiusX, this.radiusZ], [-this.radiusX, this.radiusZ], [-this.radiusX, -this.radiusZ], [this.radiusX, -this.radiusZ]]
|
|
185
|
-
return [[this.radiusX, 0], [-this.radiusX, 0], [0, this.radiusZ], [0, -this.radiusZ]]
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/** 世界点是否落在领地边界内(点选/命中判定) */
|
|
189
|
-
pointIn(x: number, z: number): boolean {
|
|
190
|
-
return pointInBoundary(this.layout(), x, z)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/** 整体平移:更新中心、平台/边界/名牌网格,并携带全部成员落点与各自活动范围(行为内聚) */
|
|
194
|
-
moveBy(dx: number, dz: number): void {
|
|
195
|
-
this.x += dx
|
|
196
|
-
this.z += dz
|
|
197
|
-
this.platform.position.set(this.x, 0.16, this.z)
|
|
198
|
-
this.padRing.position.set(this.x, 0.32, this.z)
|
|
199
|
-
this.beacon.position.set(this.x, 0, this.z)
|
|
200
|
-
this.boundary.position.set(this.x, 0.3, this.z)
|
|
201
|
-
this.boundary.rotation.y = this.rotationY * Math.PI / 180
|
|
202
|
-
this.label.position.set(this.x, 30, this.z)
|
|
203
|
-
for (const a of this.members) {
|
|
204
|
-
a.root.position.x += dx
|
|
205
|
-
a.root.position.z += dz
|
|
206
|
-
a.homeX += dx
|
|
207
|
-
a.homeZ += dz
|
|
208
|
-
if (a.range) {
|
|
209
|
-
a.range.x += dx
|
|
210
|
-
a.range.z += dz
|
|
211
|
-
a.renderRangeLine()
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
this.host.trackLayout(this)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/** 按补丁更新布局字段(边界编辑;钳制下限由宿主统一保证) */
|
|
218
|
-
applyPatch(patch: Partial<ChannelLayout>): void {
|
|
219
|
-
if (patch.x !== undefined) this.x = patch.x
|
|
220
|
-
if (patch.z !== undefined) this.z = patch.z
|
|
221
|
-
if (patch.radiusX !== undefined) this.radiusX = patch.radiusX
|
|
222
|
-
if (patch.radiusZ !== undefined) this.radiusZ = patch.radiusZ
|
|
223
|
-
if (patch.shape !== undefined) this.shape = patch.shape
|
|
224
|
-
if (patch.rotationY !== undefined) this.rotationY = patch.rotationY
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/** 把成员落点与各自活动范围收进当前频道边界(边界缩放/整体移动后调用) */
|
|
228
|
-
clampMembersAndRanges(): void {
|
|
229
|
-
const layout = this.layout()
|
|
230
|
-
for (const a of this.members) {
|
|
231
|
-
const c = clampToBoundary(layout, a.root.position.x, a.root.position.z, 16)
|
|
232
|
-
a.root.position.x = c.x
|
|
233
|
-
a.root.position.z = c.z
|
|
234
|
-
a.homeX = c.x
|
|
235
|
-
a.homeZ = c.z
|
|
236
|
-
if (a.range) {
|
|
237
|
-
a.renderRangeLine()
|
|
238
|
-
const cc = clampToAgentRange(a.range, a.homeX, a.homeZ, 0)
|
|
239
|
-
if (cc.x !== a.homeX || cc.z !== a.homeZ) {
|
|
240
|
-
a.homeX = cc.x
|
|
241
|
-
a.homeZ = cc.z
|
|
242
|
-
a.root.position.x = cc.x
|
|
243
|
-
a.root.position.z = cc.z
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
this.host.markDirty()
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/** 边界几何体:椭圆/矩形线框,弯折朝向 rotationY(度)。
|
|
252
|
-
* 轮廓点来自 #shared/town-scene-math.boundaryPoints(纯几何,场景只做渲染)。 */
|
|
253
|
-
function makeBoundary(shape: 'ellipse' | 'rect', rx: number, rz: number, color: number): THREE.LineLoop {
|
|
254
|
-
const pts = boundaryPoints(shape, rx, rz, 48).map(([x, z]) => new THREE.Vector3(x, 0, z))
|
|
255
|
-
const geo = new THREE.BufferGeometry().setFromPoints(pts)
|
|
256
|
-
const line = new THREE.LineLoop(geo, new THREE.LineBasicMaterial({ color, transparent: true, opacity: 0.75, depthTest: false }))
|
|
257
|
-
return line
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/** 归一化边界(radius 钳制下限;rect 用半宽) —— 定义见 #shared/town-scene-math */
|
|
261
|
-
|
|
262
|
-
/** 把点钳制到频道边界内 —— 定义见 #shared/town-scene-math */
|
|
263
|
-
|
|
264
|
-
/** 点到边界内判定 —— 定义见 #shared/town-scene-math */
|
|
265
|
-
|
|
266
|
-
/** 边界/范围的极值点(矩形四角 / 椭圆轴向四点;世界坐标,含 rotationY) —— 定义见 #shared/town-scene-math */
|
|
267
|
-
|
|
268
|
-
/** 把 Agent 活动范围整体收进频道边界 —— 定义见 #shared/town-scene-math */
|
|
269
|
-
|
|
270
|
-
/** 把点钳制到 Agent 自己活动范围内 —— 定义见 #shared/town-scene-math */
|
|
271
|
-
|
|
272
|
-
/** 世界点到活动范围边界线的最近距离 —— 定义见 #shared/town-scene-math */
|
|
273
|
-
|
|
274
|
-
/** 角色 3D 实例(面向对象:持有模型/状态/行为,封装漫游 FSM、移动钳制、动画、活动范围)。
|
|
275
|
-
* 由场景按 entities/数据库元数据(home/range/modelRef)实例化并注入宿主;动画状态驱动 motion 事件。 */
|
|
276
|
-
class Agent3D {
|
|
277
|
-
channelId!: string
|
|
278
|
-
agentId!: string
|
|
279
|
-
name!: string
|
|
280
|
-
role!: 'lead' | 'worker'
|
|
281
|
-
/** 根 Group(位置=落地点,脚底) */
|
|
282
|
-
root!: THREE.Group
|
|
283
|
-
/** 模型子节点(换模型时替换) —— 始终存在(空组占位) */
|
|
284
|
-
model!: THREE.Group
|
|
285
|
-
mixer!: THREE.AnimationMixer | null
|
|
286
|
-
/** 当前模型动画 clip(若有) */
|
|
287
|
-
clips!: THREE.AnimationClip[]
|
|
288
|
-
/** 身份色(频道哈希色;气泡/小地图等取色数据源) */
|
|
289
|
-
colorNum!: number
|
|
290
|
-
/** 头顶名字 Sprite */
|
|
291
|
-
nameSprite!: THREE.Sprite
|
|
292
|
-
/** 当前气泡 */
|
|
293
|
-
bubble!: THREE.Sprite | null
|
|
294
|
-
/** 当前头顶聊天气泡文本(调试/HUD 用;null = 无气泡) */
|
|
295
|
-
bubbleText!: string | null
|
|
296
|
-
bubbleTimer!: ReturnType<typeof setTimeout> | null
|
|
297
|
-
/** 状态/进度/行为 */
|
|
298
|
-
state!: 'idle' | 'busy' | 'stopped'
|
|
299
|
-
progress!: number | null
|
|
300
|
-
/** 行为 FSM */
|
|
301
|
-
behavior!: BehaviorState
|
|
302
|
-
/** 用户拖动中 */
|
|
303
|
-
dragging!: boolean
|
|
304
|
-
/** home(行为结束后回归) */
|
|
305
|
-
homeX!: number
|
|
306
|
-
homeZ!: number
|
|
307
|
-
/** 独立活动范围(编辑模式框选/手柄;null = 沿用频道边界) */
|
|
308
|
-
range!: AgentRangeLayout | null
|
|
309
|
-
/** 活动范围线框(随 range 渲染;清除/重建时移除) */
|
|
310
|
-
rangeLine!: THREE.LineLoop | null
|
|
311
|
-
textureKey!: string
|
|
312
|
-
modelRef!: string
|
|
313
|
-
/** 当前动画状态(数据驱动模型:idle/walk;经宿主 motion 事件监听) */
|
|
314
|
-
animState: 'idle' | 'walk' = 'idle'
|
|
315
|
-
/** 当前 mixer action(crossfade 切换用;null = 未开始) */
|
|
316
|
-
activeAction: THREE.AnimationAction | null = null
|
|
317
|
-
/** 宿主场景控制器(实例化后注入) */
|
|
318
|
-
host!: TownScene3D
|
|
319
|
-
/** 本帧是否在移动(update 结束时驱动动画) */
|
|
320
|
-
private moving = false
|
|
321
|
-
/** 脚下光环组(频道色身份标识;root 子节点,随移动跟随,零每帧定位成本) */
|
|
322
|
-
aura: THREE.Group | null = null
|
|
323
|
-
/** 光环材质(渲染循环呼吸;userData.baseOp 存基准透明度) */
|
|
324
|
-
auraMats: THREE.MeshBasicMaterial[] = []
|
|
325
|
-
/** 呼吸相位(每实例随机,避免全场同频闪烁) */
|
|
326
|
-
auraPhase = 0
|
|
327
|
-
|
|
328
|
-
/** 挂脚下光环(频道色,与领地同源;lead 加外环 —— 角色身份一眼可辨) */
|
|
329
|
-
attachAura(color: number, role: 'lead' | 'worker'): void {
|
|
330
|
-
const g = new THREE.Group()
|
|
331
|
-
const mats: THREE.MeshBasicMaterial[] = []
|
|
332
|
-
const mk = (r0: number, r1: number, y: number, op: number): void => {
|
|
333
|
-
const m = new THREE.MeshBasicMaterial({ color, transparent: true, opacity: op, side: THREE.DoubleSide, depthWrite: false, blending: THREE.AdditiveBlending })
|
|
334
|
-
m.userData.baseOp = op
|
|
335
|
-
mats.push(m)
|
|
336
|
-
const mesh = new THREE.Mesh(new THREE.RingGeometry(r0, r1, 44), m)
|
|
337
|
-
mesh.rotation.x = -Math.PI / 2
|
|
338
|
-
mesh.position.y = y
|
|
339
|
-
g.add(mesh)
|
|
340
|
-
}
|
|
341
|
-
mk(34, 40, 0.36, 0.26)
|
|
342
|
-
if (role === 'lead') mk(46, 50, 0.42, 0.2)
|
|
343
|
-
this.root.add(g)
|
|
344
|
-
this.aura = g
|
|
345
|
-
this.auraMats = mats
|
|
346
|
-
this.auraPhase = Math.random() * Math.PI * 2
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
constructor(init: {
|
|
350
|
-
channelId: string
|
|
351
|
-
agentId: string
|
|
352
|
-
name: string
|
|
353
|
-
role: 'lead' | 'worker'
|
|
354
|
-
root: THREE.Group
|
|
355
|
-
model: THREE.Group
|
|
356
|
-
mixer: THREE.AnimationMixer | null
|
|
357
|
-
clips: THREE.AnimationClip[]
|
|
358
|
-
colorNum: number
|
|
359
|
-
nameSprite: THREE.Sprite
|
|
360
|
-
bubble: THREE.Sprite | null
|
|
361
|
-
bubbleText: string | null
|
|
362
|
-
bubbleTimer: ReturnType<typeof setTimeout> | null
|
|
363
|
-
state: 'idle' | 'busy' | 'stopped'
|
|
364
|
-
progress: number | null
|
|
365
|
-
behavior: BehaviorState
|
|
366
|
-
dragging: boolean
|
|
367
|
-
homeX: number
|
|
368
|
-
homeZ: number
|
|
369
|
-
range: AgentRangeLayout | null
|
|
370
|
-
rangeLine: THREE.LineLoop | null
|
|
371
|
-
textureKey: string
|
|
372
|
-
modelRef: string
|
|
373
|
-
}) {
|
|
374
|
-
Object.assign(this, init)
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/** 到达判定 */
|
|
378
|
-
reached(target: { x: number, z: number }): boolean {
|
|
379
|
-
return Math.hypot(this.root.position.x - target.x, this.root.position.z - target.z) <= ARRIVE
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/** 朝目标匀速走一步(按自身活动范围/频道边界钳制;驱动朝向与动画) */
|
|
383
|
-
driveToward(target: { x: number, z: number }, speed: number, dt: number): void {
|
|
384
|
-
this.moving = true
|
|
385
|
-
const cur = { x: this.root.position.x, z: this.root.position.z }
|
|
386
|
-
const next = stepToward({ x: cur.x, y: cur.z }, { x: target.x, y: target.z }, speed, dt)
|
|
387
|
-
let nx = next.x
|
|
388
|
-
let nz = next.y
|
|
389
|
-
if (this.range) {
|
|
390
|
-
const clamped = clampToAgentRange(this.range, nx, nz, 6)
|
|
391
|
-
nx = clamped.x
|
|
392
|
-
nz = clamped.z
|
|
393
|
-
}
|
|
394
|
-
else {
|
|
395
|
-
const layout = this.host.blockLayoutOf(this.channelId)
|
|
396
|
-
if (layout) {
|
|
397
|
-
const clamped = clampToBoundary(layout, nx, nz, 6)
|
|
398
|
-
nx = clamped.x
|
|
399
|
-
nz = clamped.z
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
this.root.position.x = nx
|
|
403
|
-
this.root.position.z = nz
|
|
404
|
-
// 朝向平滑:朝移动方向插值转动(左/右两态,不用瞬间 snap,2.5D 行走更顺)
|
|
405
|
-
const dir = next.dir
|
|
406
|
-
const targetY = dir === 'left' ? Math.PI : 0
|
|
407
|
-
const curY = this.root.rotation.y
|
|
408
|
-
// 最短角差插值(0 ↔ π 之间取捷径;步长限速防抖)
|
|
409
|
-
let d = targetY - curY
|
|
410
|
-
if (d > Math.PI) d -= Math.PI * 2
|
|
411
|
-
else if (d < -Math.PI) d += Math.PI * 2
|
|
412
|
-
const maxStep = dt * 3.2
|
|
413
|
-
this.root.rotation.y = curY + Math.max(-maxStep, Math.min(maxStep, d))
|
|
414
|
-
void dt
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/** 行为 FSM(idle/roam/approach/wait/returnHome);由渲染循环每帧驱动,动画随移动状态切换 */
|
|
418
|
-
update(dt: number): void {
|
|
419
|
-
this.moving = false
|
|
420
|
-
try {
|
|
421
|
-
this.updateBehavior(dt)
|
|
422
|
-
}
|
|
423
|
-
finally {
|
|
424
|
-
this.playWalkAnim(this.moving)
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
private updateBehavior(dt: number): void {
|
|
429
|
-
const b = this.behavior
|
|
430
|
-
if (b.mode === 'idle' || b.mode === 'roam') {
|
|
431
|
-
if (b.engaged || this.dragging) return
|
|
432
|
-
b.mode = 'roam'
|
|
433
|
-
if (!b.roamTarget) {
|
|
434
|
-
// 到达后就地停顿片刻:保持 idle 动画,营造走走停停的闲逛节奏(并产生 motion 事件)
|
|
435
|
-
if (performance.now() < b.pauseUntil) return
|
|
436
|
-
// 漫游目标:有独立活动范围 → 在自身范围内取点;否则沿用频道边界内取点
|
|
437
|
-
const layout = this.host.blockLayoutOf(this.channelId)
|
|
438
|
-
let tx: number
|
|
439
|
-
let tz: number
|
|
440
|
-
if (this.range) {
|
|
441
|
-
tx = this.homeX + (Math.random() * 2 - 1) * this.range.radiusX * 0.9
|
|
442
|
-
tz = this.homeZ + (Math.random() * 2 - 1) * this.range.radiusZ * 0.9
|
|
443
|
-
const clamped = clampToAgentRange(this.range, tx, tz, 8)
|
|
444
|
-
tx = clamped.x
|
|
445
|
-
tz = clamped.z
|
|
446
|
-
}
|
|
447
|
-
else {
|
|
448
|
-
const range = layout ? Math.min(layout.radiusX, layout.radiusZ) * 0.45 : 80
|
|
449
|
-
tx = this.homeX + (Math.random() * 2 - 1) * range
|
|
450
|
-
tz = this.homeZ + (Math.random() * 2 - 1) * range * 0.6
|
|
451
|
-
if (layout) {
|
|
452
|
-
const clamped = clampToBoundary(layout, tx, tz, 16)
|
|
453
|
-
tx = clamped.x
|
|
454
|
-
tz = clamped.z
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
b.roamTarget = { x: tx, z: tz }
|
|
458
|
-
}
|
|
459
|
-
this.driveToward(b.roamTarget, AGENT_SPEED * 0.5, dt)
|
|
460
|
-
if (this.reached(b.roamTarget)) {
|
|
461
|
-
b.roamTarget = null
|
|
462
|
-
b.pauseUntil = performance.now() + 350 + Math.random() * 900
|
|
463
|
-
}
|
|
464
|
-
return
|
|
465
|
-
}
|
|
466
|
-
if (b.mode === 'approach') {
|
|
467
|
-
const target = b.targetId ? this.host.getAgent(b.targetId) : undefined
|
|
468
|
-
if (!target) {
|
|
469
|
-
b.mode = 'idle'
|
|
470
|
-
return
|
|
471
|
-
}
|
|
472
|
-
let pos = { x: target.root.position.x, z: target.root.position.z }
|
|
473
|
-
// 目标在自身活动范围外 → 逼近到自身范围边界(送达交接在边界处完成)
|
|
474
|
-
if (this.range) {
|
|
475
|
-
const c = clampToAgentRange(this.range, pos.x, pos.z, 8)
|
|
476
|
-
pos = { x: c.x, z: c.z }
|
|
477
|
-
}
|
|
478
|
-
this.driveToward(pos, AGENT_SPEED, dt)
|
|
479
|
-
if (this.reached(pos)) {
|
|
480
|
-
this.host.deliverBehavior(this, target)
|
|
481
|
-
if (b.action?.requireReply) {
|
|
482
|
-
b.mode = 'wait'
|
|
483
|
-
b.waitUntil = performance.now() + WAIT_MS
|
|
484
|
-
}
|
|
485
|
-
else {
|
|
486
|
-
b.mode = 'returnHome'
|
|
487
|
-
this.host.releaseEngaged(this)
|
|
488
|
-
b.targetId = null
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
return
|
|
492
|
-
}
|
|
493
|
-
if (b.mode === 'wait') {
|
|
494
|
-
if (this.dragging) return
|
|
495
|
-
const target = b.targetId ? this.host.getAgent(b.targetId) : undefined
|
|
496
|
-
if (target && !target.dragging) {
|
|
497
|
-
const stand = { x: target.root.position.x + 28, z: target.root.position.z + 8 }
|
|
498
|
-
this.driveToward(stand, AGENT_SPEED * 0.6, dt)
|
|
499
|
-
}
|
|
500
|
-
if (performance.now() >= b.waitUntil) {
|
|
501
|
-
b.mode = 'returnHome'
|
|
502
|
-
this.host.releaseEngaged(this)
|
|
503
|
-
}
|
|
504
|
-
return
|
|
505
|
-
}
|
|
506
|
-
if (b.mode === 'returnHome') {
|
|
507
|
-
if (this.dragging) return
|
|
508
|
-
const home = { x: this.homeX, z: this.homeZ }
|
|
509
|
-
this.driveToward(home, AGENT_SPEED * 0.7, dt)
|
|
510
|
-
if (this.reached(home)) {
|
|
511
|
-
this.root.position.set(this.homeX, GROUND_Y, this.homeZ)
|
|
512
|
-
b.mode = 'idle'
|
|
513
|
-
b.targetId = null
|
|
514
|
-
b.action = undefined
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
/** 动画状态切换(有 clip: idle/walk 动作;无 clip: 上下浮动 bob);切换时经宿主广播 motion 事件 */
|
|
520
|
-
playWalkAnim(moving: boolean): void {
|
|
521
|
-
const next = moving ? 'walk' : 'idle'
|
|
522
|
-
if (this.animState !== next) {
|
|
523
|
-
this.animState = next
|
|
524
|
-
this.host.notifyMotion(this)
|
|
525
|
-
}
|
|
526
|
-
const bob = () => {
|
|
527
|
-
// 程序化动作绑定(model 局部,色环/名牌保持贴地稳定):
|
|
528
|
-
// 待机呼吸浮动 + 行走跳跃颠簸 + 行走左右微摆
|
|
529
|
-
const t = performance.now()
|
|
530
|
-
const breathe = 1.2 + Math.sin(t * 0.0016) * 1.6
|
|
531
|
-
const hop = Math.abs(Math.sin(t * 0.005)) * 6
|
|
532
|
-
this.model.position.y = moving ? hop : breathe
|
|
533
|
-
const swayTarget = moving ? Math.sin(t * 0.006) * 0.07 : 0
|
|
534
|
-
this.model.rotation.z += (swayTarget - this.model.rotation.z) * 0.12
|
|
535
|
-
}
|
|
536
|
-
if (this.clips.length === 0) {
|
|
537
|
-
bob()
|
|
538
|
-
return
|
|
539
|
-
}
|
|
540
|
-
// 有动画 clip:名字感知选择(剪辑命名各异:WALK/Walk、idle/stand/breath...),
|
|
541
|
-
// 先按语义匹配,找不到回退索引(idle→0,walk→1);仅含走路剪辑的模型待机时
|
|
542
|
-
// 回退程序化 bob,避免原地播放走路。crossfade 平滑过渡(避免双 action 叠加)。
|
|
543
|
-
if (!this.mixer) return
|
|
544
|
-
const lower = (c: THREE.AnimationClip) => c.name.toLowerCase()
|
|
545
|
-
const clip = moving
|
|
546
|
-
? (this.clips.find(c => /walk|run|move/.test(lower(c))) ?? this.clips[Math.min(1, this.clips.length - 1)])
|
|
547
|
-
: (this.clips.find(c => /idle|stand|breath/.test(lower(c))) ?? (this.clips.length > 1 ? this.clips[0] : null))
|
|
548
|
-
if (!clip) {
|
|
549
|
-
if (this.activeAction) {
|
|
550
|
-
this.activeAction.fadeOut(0.15)
|
|
551
|
-
this.activeAction = null
|
|
552
|
-
}
|
|
553
|
-
bob()
|
|
554
|
-
return
|
|
555
|
-
}
|
|
556
|
-
const nextAction = this.mixer.clipAction(clip)
|
|
557
|
-
if (this.activeAction === nextAction) {
|
|
558
|
-
nextAction.timeScale = moving ? 1.3 : 0.9
|
|
559
|
-
return
|
|
560
|
-
}
|
|
561
|
-
nextAction.reset().setEffectiveTimeScale(moving ? 1.3 : 0.9).fadeIn(0.15).play()
|
|
562
|
-
if (this.activeAction) this.activeAction.fadeOut(0.15)
|
|
563
|
-
this.activeAction = nextAction
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
/** 更新 home 落点(行为结束后回归点;含拖拽/范围位移后落库路径) */
|
|
567
|
-
setHome(x: number, z: number): void {
|
|
568
|
-
this.homeX = x
|
|
569
|
-
this.homeZ = z
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
/** 渲染/刷新活动范围线框(数据驱动:range 变更 → 线框重建)+ 可见性 */
|
|
573
|
-
renderRangeLine(): void {
|
|
574
|
-
if (this.rangeLine) {
|
|
575
|
-
this.host.threeScene.remove(this.rangeLine)
|
|
576
|
-
this.rangeLine = null
|
|
577
|
-
}
|
|
578
|
-
if (!this.range) return
|
|
579
|
-
const color = 0x41c8f4
|
|
580
|
-
const line = makeBoundary(this.range.shape, this.range.radiusX, this.range.radiusZ, color)
|
|
581
|
-
// 虚线描边(与频道实线边界区分;2.5D 可读性)
|
|
582
|
-
line.material = new THREE.LineDashedMaterial({ color, transparent: true, opacity: 0.75, depthTest: false, dashSize: 14, gapSize: 9 })
|
|
583
|
-
line.computeLineDistances()
|
|
584
|
-
line.position.set(this.range.x, 0.5, this.range.z)
|
|
585
|
-
line.rotation.y = this.range.rotationY * Math.PI / 180
|
|
586
|
-
line.visible = this.host.rangeLineVisibleFor(this)
|
|
587
|
-
this.host.threeScene.add(line)
|
|
588
|
-
this.rangeLine = line
|
|
589
|
-
this.host.markDirty()
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
/** 挂气泡(替换旧气泡;宿主完成纹理渲染) */
|
|
593
|
-
attachBubble(sprite: THREE.Sprite, text: string): void {
|
|
594
|
-
if (this.bubble) {
|
|
595
|
-
this.host.threeScene.remove(this.bubble)
|
|
596
|
-
this.bubbleText = null
|
|
597
|
-
}
|
|
598
|
-
this.bubble = sprite
|
|
599
|
-
this.bubbleText = text
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
/** 移除气泡(宿主负责从场景摘除) */
|
|
603
|
-
clearBubble(): void {
|
|
604
|
-
if (!this.bubble) return
|
|
605
|
-
this.host.threeScene.remove(this.bubble)
|
|
606
|
-
this.bubble = null
|
|
607
|
-
this.bubbleText = null
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
type BehaviorMode
|
|
612
|
-
= 'idle'
|
|
613
|
-
| 'roam'
|
|
614
|
-
| 'approach'
|
|
615
|
-
| 'wait'
|
|
616
|
-
| 'returnHome'
|
|
617
|
-
|
|
618
|
-
/** 数字孪生设备 3D 实例(面向对象:持有节点网格/状态/遥测,封装讑生同步、状态环、模型重挂、transform 记忆)。
|
|
619
|
-
* 由场景按 device-twins 数据库元数据实例化;state/telemetry 数据驱动渲染。 */
|
|
620
|
-
class DeviceNode {
|
|
621
|
-
twinId!: string
|
|
622
|
-
name!: string
|
|
623
|
-
modelRef!: string
|
|
624
|
-
root!: THREE.Group
|
|
625
|
-
/** 模型挂载组(换模型时 clear 重挂;缩放施加于此) */
|
|
626
|
-
holder!: THREE.Group
|
|
627
|
-
ring!: THREE.Mesh
|
|
628
|
-
/** 运行态动效弧(仅 running 可见;渲染循环缓转 —— 语义动效:设备在转 = 在产) */
|
|
629
|
-
arc: THREE.Mesh | null = null
|
|
630
|
-
label!: THREE.Sprite
|
|
631
|
-
state!: 'idle' | 'running' | 'offline' | 'alarm'
|
|
632
|
-
telemetry!: Record<string, number | string | boolean>
|
|
633
|
-
/** 宿主场景控制器(实例化后注入) */
|
|
634
|
-
host!: TownScene3D
|
|
635
|
-
|
|
636
|
-
constructor(init: {
|
|
637
|
-
twinId: string
|
|
638
|
-
name: string
|
|
639
|
-
modelRef: string
|
|
640
|
-
root: THREE.Group
|
|
641
|
-
holder: THREE.Group
|
|
642
|
-
ring: THREE.Mesh
|
|
643
|
-
label: THREE.Sprite
|
|
644
|
-
state: 'idle' | 'running' | 'offline' | 'alarm'
|
|
645
|
-
telemetry: Record<string, number | string | boolean>
|
|
646
|
-
}) {
|
|
647
|
-
Object.assign(this, init)
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
/** 模型顶面世界高度缓存(HUD/callout 每帧读取;缩放变更时失效) */
|
|
651
|
-
topYCache: number | null = null
|
|
652
|
-
/** 当前产线光晕色(空 = 未分配) */
|
|
653
|
-
lineColor = ''
|
|
654
|
-
/** 产线换色回调(recreateDaqNode 装配;applyTwin 检测变化时重 tint) */
|
|
655
|
-
applyLine: ((color: string) => void) | null = null
|
|
656
|
-
|
|
657
|
-
/** 状态环颜色(数据驱动:状态变化/产线换色时调用,渲染循环不再每帧重刷)。
|
|
658
|
-
* 分配了产线的节点(数采)环随产线色 —— 状态生命感已由 LED 呼吸环表达;常规设备环 = 状态色。 */
|
|
659
|
-
updateRing(): void {
|
|
660
|
-
const mat = this.ring.material as THREE.MeshBasicMaterial
|
|
661
|
-
if (this.lineColor) mat.color.set(this.lineColor)
|
|
662
|
-
else mat.color.setHex(this.state === 'alarm' ? 0xff6b6b : this.state === 'offline' ? 0x8496a5 : this.state === 'running' ? 0x35e0a0 : 0xf6c453)
|
|
663
|
-
if (this.arc) this.arc.visible = this.state === 'running'
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
/** 与数据库讑生记录收敛(状态/遥测/名称/模型/产线光晕;宿主完成名牌与模型重挂) */
|
|
667
|
-
applyTwin(t: DeviceTwinSync): void {
|
|
668
|
-
if (t.state) this.state = t.state
|
|
669
|
-
if (t.telemetry) this.telemetry = { ...this.telemetry, ...t.telemetry }
|
|
670
|
-
if (t.name && t.name !== this.name) this.host.renameDeviceSprite(this, t.name)
|
|
671
|
-
if (t.modelRef && t.modelRef !== this.modelRef) this.host.swapDeviceModelSprite(this, t.modelRef)
|
|
672
|
-
if ((t.lineColor ?? '') !== this.lineColor) {
|
|
673
|
-
this.lineColor = t.lineColor ?? ''
|
|
674
|
-
this.applyLine?.(this.lineColor)
|
|
675
|
-
}
|
|
676
|
-
this.updateRing()
|
|
677
|
-
this.host.markDirty()
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
/** 记录当前 transform(供防抖落库快照) */
|
|
681
|
-
rememberTransform(): { posX: number, posZ: number, rotationY: number } {
|
|
682
|
-
return {
|
|
683
|
-
posX: Math.round(this.root.position.x * 10) / 10,
|
|
684
|
-
posZ: Math.round(this.root.position.z * 10) / 10,
|
|
685
|
-
rotationY: Math.round(THREE.MathUtils.radToDeg(this.root.rotation.y) * 10) / 10,
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
interface BehaviorState {
|
|
691
|
-
mode: BehaviorMode
|
|
692
|
-
roamTarget: { x: number, z: number } | null
|
|
693
|
-
targetId: string | null
|
|
694
|
-
waitUntil: number
|
|
695
|
-
/** 漫游到达后就地停顿至该时刻(idle 动画;营造走走停停的闲逛节奏,驱动 motion 事件) */
|
|
696
|
-
pauseUntil: number
|
|
697
|
-
action?: { kind: ActionKind, taskKind?: string, requireReply: boolean, text: string }
|
|
698
|
-
engaged: boolean
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
/** 信息接收器队列中的一条消息(按频道 FIFO 逐条消费) */
|
|
702
|
-
interface BubbleMsg {
|
|
703
|
-
/** 说话 Agent(null = 频道级/系统气泡) */
|
|
704
|
-
agentId: string | null
|
|
705
|
-
kind: TownBubbleKind
|
|
706
|
-
text: string
|
|
707
|
-
ttlMs: number
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
/** 每个实例化 Channel 的信息接收器:WS 实时信息经 handleTownEvent 入队,FIFO 逐条消费渲染到对应 Agent 头顶 */
|
|
711
|
-
interface ChannelMessageReceiver {
|
|
712
|
-
channelId: string
|
|
713
|
-
queue: BubbleMsg[]
|
|
714
|
-
current: BubbleMsg | null
|
|
715
|
-
currentUntil: number
|
|
716
|
-
}
|
|
112
|
+
// 频道领地 / 角色 / 数字孪生设备节点与类型契约见 ./scene/town-scene3d-nodes.ts 与 ./scene/town-scene3d-types.ts
|
|
717
113
|
|
|
718
114
|
/** 世界尺度/速度/身份色等纯常量与函数集中定义于 #shared/town-scene-math(与 2D/测试共用) */
|
|
719
115
|
|
|
720
|
-
|
|
721
|
-
|
|
116
|
+
export class TownScene3D implements TownSceneHost {
|
|
117
|
+
// ================================================================
|
|
118
|
+
// 宿主契约面:下列成员被 scene/*.ts 抽出模块以「窄接口」直接读写
|
|
119
|
+
// (每个模块只声明它真正触达的项,见各模块顶部的 XxxHost 接口)。
|
|
120
|
+
// 为「方法簇 → 模块」的结构性拆分而公开,不改变任何运行时语义。
|
|
121
|
+
// 其中 autoFrameTo / pickChannelEdge / boundaryHandlePoints 由 private 放宽为 public:
|
|
122
|
+
// 原调用点全在本类内部(已随簇迁出),editing / ranges 模块经宿主契约回调,纯属可见性放宽。
|
|
123
|
+
// ================================================================
|
|
722
124
|
|
|
723
|
-
|
|
724
|
-
private renderer!: THREE.WebGLRenderer
|
|
125
|
+
renderer!: THREE.WebGLRenderer
|
|
725
126
|
/** 后处理管线:Render → Bloom(夜航辉光)→ Output(tone mapping + sRGB;曝光经 setExposure 仍实时生效) */
|
|
726
|
-
|
|
727
|
-
|
|
127
|
+
composer!: EffectComposer
|
|
128
|
+
camera!: THREE.PerspectiveCamera
|
|
728
129
|
/** 相机注视目标(拖拽平移它,zoom 微调距离) */
|
|
729
|
-
|
|
130
|
+
camTarget = new THREE.Vector3(WORLD_CX, 20, WORLD_CZ)
|
|
730
131
|
/** 主方向光(阴影相机范围随 dolly 扩,大领地投影不消失) */
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
132
|
+
keyLight!: THREE.DirectionalLight
|
|
133
|
+
scene!: THREE.Scene
|
|
134
|
+
ground!: THREE.Mesh
|
|
734
135
|
/** 穹顶天幕(随镜头平移,保证无限视野观感) */
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
136
|
+
skyDome: THREE.Mesh | null = null
|
|
137
|
+
blocks = new Map<string, Block3D>()
|
|
138
|
+
agents = new Map<string, Agent3D>()
|
|
738
139
|
/** 数字孪生设备节点(twinId → node) */
|
|
739
|
-
|
|
140
|
+
deviceNodes = new Map<string, DeviceNode>()
|
|
740
141
|
/** 数采节点顶端 LED 环(twinId → 环;渲染循环缓转呼吸) */
|
|
741
|
-
|
|
142
|
+
daqLedRings = new Map<string, THREE.Mesh>()
|
|
742
143
|
/** 数采→设备 绑定链路(虚线贝塞尔 + 流动脉冲;syncDaqLinks 维护) */
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
private static readonly _wDir = new THREE.Vector3()
|
|
748
|
-
private static readonly _wRel = new THREE.Vector3()
|
|
749
|
-
private static readonly _wVec = new THREE.Vector3()
|
|
750
|
-
private static readonly _wVec2 = new THREE.Vector2()
|
|
751
|
-
private static readonly _groundPlane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
|
|
752
|
-
private static readonly _raycaster = new THREE.Raycaster()
|
|
753
|
-
private daqLinkSig = ''
|
|
144
|
+
daqLinkGroup = new THREE.Group()
|
|
145
|
+
daqLinks: DaqLink[] = []
|
|
146
|
+
|
|
147
|
+
daqLinkSig = ''
|
|
754
148
|
/** 期望链路(TownView 传入;设备节点晚到时由 syncDevices 末尾重仲裁) */
|
|
755
|
-
|
|
149
|
+
daqLinksWanted: Array<{ daqId: string, deviceId: string }> = []
|
|
756
150
|
/** 薄膜 web(产线设备之间的半透明膜;按 X 序连接挤出→流延→MD→TD→收卷) */
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
151
|
+
filmWebGroup = new THREE.Group()
|
|
152
|
+
filmWebSig = ''
|
|
153
|
+
filmWebMat: THREE.MeshStandardMaterial | null = null
|
|
760
154
|
/** 服务端已有但模型资产尚未注册的设备孪生,资产到达后补建节点。 */
|
|
761
|
-
|
|
155
|
+
pendingDeviceTwins = new Map<string, DeviceTwinSync>()
|
|
762
156
|
/** 尚未取得服务端 ID 的本地设备节点(临时 ID → 创建请求初始状态)。 */
|
|
763
|
-
|
|
157
|
+
pendingDeviceCreates = new Map<string, { name: string, modelRef: string, posX: number, posZ: number }>()
|
|
764
158
|
/** 用户在创建设备完成前删除的临时节点,创建完成后补偿删除服务端记录。 */
|
|
765
|
-
|
|
159
|
+
cancelledDeviceCreates = new Set<string>()
|
|
766
160
|
/** 场景内可缩放目标(Agent 或设备) */
|
|
767
|
-
|
|
161
|
+
scalables = new Map<string, ScaledTarget>()
|
|
768
162
|
/** 当前选中(kind:id) */
|
|
769
|
-
|
|
163
|
+
selected: SelectedTarget | null = null
|
|
770
164
|
/** 已登记模型(id → file) */
|
|
771
|
-
|
|
165
|
+
modelsById = new Map<string, ModelInfo>()
|
|
772
166
|
/** GLTF 缓存(避免重复加载) */
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
167
|
+
gltfCache = new Map<string, { file: string, scene: THREE.Group, height: number }>()
|
|
168
|
+
gltfLoader = new GLTFLoader()
|
|
169
|
+
el: HTMLDivElement
|
|
170
|
+
raf = 0
|
|
171
|
+
clock = new THREE.Clock()
|
|
172
|
+
frameCount = 0
|
|
173
|
+
fpsAccum = 0
|
|
780
174
|
/** 墙钟 FPS 窗口起点与 rAF 回调计数(徽标/自适应质量的真实输入;原 dt 累计会漂移) */
|
|
781
|
-
|
|
782
|
-
|
|
175
|
+
fpsWinT0 = 0
|
|
176
|
+
rafCount = 0
|
|
783
177
|
/** 帧预算(ms):渲染节流上限;0 = 不限制。数据消费不走此门控(帧到达即入实时缓冲) */
|
|
784
|
-
|
|
178
|
+
frameBudgetMs = 1000 / 40
|
|
785
179
|
|
|
786
180
|
/** 用户帧率选择(60/120/0=不限制):只影响渲染节流,与数据消费频率无关 */
|
|
787
181
|
setFpsCap(fps: number): void {
|
|
@@ -789,70 +183,56 @@ export class TownScene3D {
|
|
|
789
183
|
this.frameAcc = 0
|
|
790
184
|
}
|
|
791
185
|
|
|
792
|
-
|
|
186
|
+
frameAcc = 0
|
|
793
187
|
/** 动态分辨率基准(初始 dpr):实测帧率过低时降,富余时回升 */
|
|
794
|
-
|
|
795
|
-
|
|
188
|
+
baseDpr = Math.min(window.devicePixelRatio, 2)
|
|
189
|
+
dirty = true
|
|
796
190
|
/** 频道旗缓存(任务状态) */
|
|
797
|
-
|
|
798
|
-
|
|
191
|
+
flagBy = new Map<string, THREE.Mesh>()
|
|
192
|
+
disposed = false
|
|
799
193
|
/** 当前相机缩放(滚轮;作用于 dolly 距离) */
|
|
800
|
-
|
|
194
|
+
dolly = 1.0
|
|
801
195
|
/** 轨道相机状态机(Blender 规范):yaw 方位角 / pitch 仰角 / radius 半径。
|
|
802
196
|
* viewTarget 供预设平滑趋近;左键环绕直接改双值(即时跟手)。 */
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
197
|
+
autoOrbit = false
|
|
198
|
+
viewCur = { yaw: 0, pitch: 0.7, radius: 1178 }
|
|
199
|
+
viewTarget = { yaw: 0, pitch: 0.7, radius: 1178 }
|
|
806
200
|
/** Blender 式变换手柄(选中设备;G 移动 / R 旋转 / S 缩放) */
|
|
807
|
-
|
|
201
|
+
tControls: TransformControls | null = null
|
|
808
202
|
/** 舞台尺寸观察器 */
|
|
809
|
-
|
|
203
|
+
resizeOb: ResizeObserver | null = null
|
|
810
204
|
/** 编辑 / 浏览模式(浏览只读:设备/角色不可拖,仅点选) */
|
|
811
|
-
|
|
205
|
+
mode: TownScene3DMode = 'browse'
|
|
812
206
|
/** 正在拖曳的场景对象(编辑模式):设备 / 角色落点 / 频道整体 / 边界手柄 / Agent 活动范围 */
|
|
813
|
-
|
|
814
|
-
| { kind: 'device', id: string }
|
|
815
|
-
| { kind: 'agent', id: string }
|
|
816
|
-
| { kind: 'channel', id: string, dx: number, dz: number }
|
|
817
|
-
| { kind: 'resize', id: string, handle: number }
|
|
818
|
-
| { kind: 'channelEdge', id: string, rx0: number, rz0: number, rd0: number }
|
|
819
|
-
| { kind: 'rangeDraw', id: string, x0: number, z0: number }
|
|
820
|
-
| { kind: 'agentRange', id: string, dx: number, dz: number }
|
|
821
|
-
| { kind: 'agentRangeResize', id: string, handle: number }
|
|
822
|
-
| null = null
|
|
207
|
+
pointerDrag: PointerDragState = null
|
|
823
208
|
|
|
824
209
|
/** 网格吸附(编辑拖拽落点对齐 16 单位网格) */
|
|
825
|
-
|
|
210
|
+
snapEnabled = true
|
|
826
211
|
/** 选中高亮环(琥珀色动效环,跟随当前选中设备/角色;渲染循环驱动) */
|
|
827
|
-
|
|
212
|
+
selRing: THREE.Mesh | null = null
|
|
828
213
|
/** 边界缩放手柄(编辑模式选中频道时显示;拖拽手柄调整 radiusX/radiusZ) */
|
|
829
|
-
|
|
214
|
+
readonly resizeHandles: ChannelResizeHandle[] = []
|
|
830
215
|
/** Agent 活动范围缩放手柄(编辑模式选中带范围角色时显示;拖拽调整该 Agent 范围大小) */
|
|
831
|
-
|
|
216
|
+
readonly agentRangeHandles: AgentRangeHandle[] = []
|
|
832
217
|
/** 框选绘制预览线框(rangeDraw 模式;松开后变为正式范围) */
|
|
833
|
-
|
|
218
|
+
rangeDrawLine: THREE.LineLoop | null = null
|
|
834
219
|
/** 框选绘制目标 agent(非空 = 编辑模式正在为该角色拉动矩形框) */
|
|
835
|
-
|
|
220
|
+
rangeDrawAgent: string | null = null
|
|
836
221
|
/** 框选绘制当前对角(供 endPointerDrag 生成范围) */
|
|
837
|
-
|
|
222
|
+
rangeDrawPreviewState: { x0: number, z0: number, x1: number, z1: number } | null = null
|
|
838
223
|
/** setAgentRangeScene 中因范围收缩而位移的 home(面板提交时一并落库) */
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
224
|
+
rangeHomeMoved: { agentId: string, x: number, z: number } | null = null
|
|
225
|
+
lastActivity: { channelId: string, agentName: string, text: string, at?: number } | null = null
|
|
226
|
+
recentActivity: Array<{ channelId: string, agentName: string, text: string, at?: number }> = []
|
|
227
|
+
dbgBubbles: Array<{ text: string, at: number }> = []
|
|
843
228
|
/** 频道信息接收器(channelId → FIFO 队列;实时信息经 handleTownEvent 入队,渲染循环逐条消费) */
|
|
844
|
-
|
|
229
|
+
receivers = new Map<string, ChannelMessageReceiver>()
|
|
845
230
|
readonly container: HTMLDivElement
|
|
846
231
|
|
|
847
232
|
/** 任务 ID → assignee 反查 */
|
|
848
233
|
resolveTaskAssignee: ((taskId: string) => string | null) | null = null
|
|
849
234
|
/** 数字孪生设备 API(由 TownView 注入 useDeviceTwins 适配器;拖 dev 模型进场景时创建设备) */
|
|
850
|
-
devices:
|
|
851
|
-
create(input: { name: string, modelRef?: string, kind?: string, controls?: string[], posX?: number, posZ?: number, scale?: number }): Promise<{ id: string }>
|
|
852
|
-
update(id: string, patch: DeviceTransformPatch): Promise<unknown>
|
|
853
|
-
remove?(id: string): Promise<unknown>
|
|
854
|
-
control(id: string, command: string, args?: Record<string, unknown>): Promise<unknown>
|
|
855
|
-
} | null = null
|
|
235
|
+
devices: DeviceApi | null = null
|
|
856
236
|
|
|
857
237
|
/** 管理员布局:保存 Agent 落点(由 TownView 注入;经既有 channel_agents.config 持久化) */
|
|
858
238
|
agentApi: {
|
|
@@ -875,363 +255,32 @@ export class TownScene3D {
|
|
|
875
255
|
this.buildBlocks(seed)
|
|
876
256
|
}
|
|
877
257
|
|
|
258
|
+
// ================================================================
|
|
259
|
+
// 面向对象节点的宿主契约(Block3D / Agent3D / DeviceNode)
|
|
260
|
+
// 节点只依赖 scene/town-scene3d-types 的最小接口,此处显式暴露宿主能力,
|
|
261
|
+
// 保证「节点所需服务」在编译期可校验(缺一即报错)。
|
|
262
|
+
// ================================================================
|
|
263
|
+
|
|
264
|
+
/** 频道领地节点宿主(Block3D.host) */
|
|
265
|
+
get blockHost(): BlockHost { return this }
|
|
266
|
+
|
|
267
|
+
/** 角色节点宿主(Agent3D.host) */
|
|
268
|
+
get agentHost(): AgentHost { return this }
|
|
269
|
+
|
|
270
|
+
/** 设备节点宿主(DeviceNode.host) */
|
|
271
|
+
get deviceHost(): DeviceHost { return this }
|
|
272
|
+
|
|
878
273
|
// ================================================================
|
|
879
274
|
// 初始化(渲染器/相机/灯光/地面)
|
|
880
275
|
// ================================================================
|
|
881
276
|
|
|
882
277
|
private initRenderer(): void {
|
|
883
|
-
this
|
|
884
|
-
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
|
|
885
|
-
this.renderer.setSize(this.el.clientWidth || 1100, this.el.clientHeight || 700)
|
|
886
|
-
this.renderer.shadowMap.enabled = true
|
|
887
|
-
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
|
|
888
|
-
// 阴影按需重绘:渲染循环里仅场景内容变化(dirty)或角色动画在跑时置 needsUpdate,
|
|
889
|
-
// 静态场景(总览/无动画)不再每帧全量重绘阴影贴图(2048² PCFSoft 是最大单项 GPU 开销)
|
|
890
|
-
this.renderer.shadowMap.autoUpdate = false
|
|
891
|
-
this.renderer.outputColorSpace = THREE.SRGBColorSpace
|
|
892
|
-
// 电影级色调映射:高光滚降 + 中间调层次,GLB 材质不再"平板曝光"
|
|
893
|
-
this.renderer.toneMapping = THREE.ACESFilmicToneMapping
|
|
894
|
-
this.renderer.toneMappingExposure = 1.12
|
|
895
|
-
// 统计跨 pass 累计(composer 多 pass 直渲染时 info 每次自动清零,只会读到末 pass 的 1 次调用):
|
|
896
|
-
// 每帧渲染前手动 reset,render() 累加,__townStats 拿到的才是整帧真实 drawCalls/triangles
|
|
897
|
-
this.renderer.info.autoReset = false
|
|
898
|
-
this.el.appendChild(this.renderer.domElement)
|
|
899
|
-
// 舞台尺寸跟随宿主(网格布局/抽屉/全屏切换都会改变宿主尺寸)
|
|
900
|
-
this.resizeOb = new ResizeObserver(() => {
|
|
901
|
-
const w = this.el.clientWidth
|
|
902
|
-
const h = this.el.clientHeight
|
|
903
|
-
if (w < 2 || h < 2) return
|
|
904
|
-
this.renderer.setSize(w, h)
|
|
905
|
-
this.composer.setSize(w, h)
|
|
906
|
-
this.camera.aspect = w / h
|
|
907
|
-
this.camera.updateProjectionMatrix()
|
|
908
|
-
this.dirty = true
|
|
909
|
-
})
|
|
910
|
-
this.resizeOb.observe(this.el)
|
|
911
|
-
// 选中:点击 Agent/设备 → 弹缩放/旋转滑杆(经 on('select') 通知 Vue);拖拽释放不触发
|
|
912
|
-
// 点击语义守卫:记录按下位置;松手位移大 = 相机平移手势 → 不触发点选(含信标)
|
|
913
|
-
let clickDownAt: { x: number, y: number } | null = null
|
|
914
|
-
this.renderer.domElement.addEventListener('pointerdown', (e: PointerEvent) => {
|
|
915
|
-
clickDownAt = e.button === 0 ? { x: e.clientX, y: e.clientY } : null
|
|
916
|
-
})
|
|
917
|
-
this.renderer.domElement.addEventListener('pointerup', (e: PointerEvent) => {
|
|
918
|
-
if (e.button !== 0 || this.pointerDrag || this.tControls?.dragging) return
|
|
919
|
-
// 平移手势(按下后移动超 8px)→ 不作点选
|
|
920
|
-
if (clickDownAt && Math.hypot(e.clientX - clickDownAt.x, e.clientY - clickDownAt.y) > 8) {
|
|
921
|
-
clickDownAt = null
|
|
922
|
-
return
|
|
923
|
-
}
|
|
924
|
-
clickDownAt = null
|
|
925
|
-
const w = this.screenToWorld(e.clientX, e.clientY)
|
|
926
|
-
// 频道信标优先(领队驻扎中心与信标重叠;中心 ±34 内点=信标,按住拖=Agent:
|
|
927
|
-
// 拖拽手势在 pointerup 时因 pointerDrag 存在提前返回,不会误触信标)
|
|
928
|
-
const beacon = this.pickBeacon(w.x, w.z)
|
|
929
|
-
if (beacon) {
|
|
930
|
-
this.setSelected(null)
|
|
931
|
-
this.selectChannel(beacon.cid)
|
|
932
|
-
this.focusTo(beacon.x, beacon.z)
|
|
933
|
-
return
|
|
934
|
-
}
|
|
935
|
-
const hit = this.pickAt(w.x, w.z)
|
|
936
|
-
if (hit) {
|
|
937
|
-
this.setSelected(hit)
|
|
938
|
-
// 点中角色/设备时清除频道选中(避免与边界面板混用)
|
|
939
|
-
if (this.selectedChannel) this.selectChannel(null)
|
|
940
|
-
return
|
|
941
|
-
}
|
|
942
|
-
// 未命中 agent/设备 → 尝试点选频道领地(打开边界编辑面板)
|
|
943
|
-
const cid = this.pickChannel(w.x, w.z)
|
|
944
|
-
this.setSelected(null)
|
|
945
|
-
if (cid && this.mode === 'edit') this.selectChannel(cid)
|
|
946
|
-
})
|
|
947
|
-
// 双击 Agent/设备:缓动聚焦(指挥官镜头;不打断选择语义)
|
|
948
|
-
this.renderer.domElement.addEventListener('dblclick', (e: MouseEvent) => {
|
|
949
|
-
if (this.pointerDrag) return
|
|
950
|
-
const w = this.screenToWorld(e.clientX, e.clientY)
|
|
951
|
-
const hit = this.pickAt(w.x, w.z)
|
|
952
|
-
if (hit?.kind === 'agent') {
|
|
953
|
-
const a = this.agents.get(hit.id)
|
|
954
|
-
if (a) this.focusTo(a.root.position.x, a.root.position.z)
|
|
955
|
-
}
|
|
956
|
-
else if (hit?.kind === 'device') {
|
|
957
|
-
const d = this.deviceNodes.get(hit.id)
|
|
958
|
-
if (d) this.focusTo(d.root.position.x, d.root.position.z)
|
|
959
|
-
}
|
|
960
|
-
else {
|
|
961
|
-
// 双击领地(非 Agent/设备落点)→ 对焦该 Channel 中心并选中
|
|
962
|
-
// (浏览模式:边界面板即刻可用调节范围;编辑模式:缩放手柄同时显示,可拖拽控制)
|
|
963
|
-
const cid = this.pickChannel(w.x, w.z)
|
|
964
|
-
const b = cid ? this.blocks.get(cid) : undefined
|
|
965
|
-
if (b) {
|
|
966
|
-
this.selectChannel(cid)
|
|
967
|
-
this.focusTo(b.x, b.z)
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
})
|
|
971
|
-
|
|
972
|
-
this.scene = new THREE.Scene()
|
|
973
|
-
this.scene.background = new THREE.Color(0x0a0f18)
|
|
974
|
-
// 大气纵深:雾把远处柔化进夜色,2.5D 场景立刻有工业孪生的空间感
|
|
975
|
-
this.scene.fog = new THREE.Fog(0x1b2836, 1400, 8800)
|
|
976
|
-
// PBR 环境光照:PMREM 室内环境 → 金属/粗糙 GLB 材质获得真实反射与 specular 生命;
|
|
977
|
-
// 低强度保留夜景工业气氛(只在材质细节里"呼吸",不提亮全场)
|
|
978
|
-
try {
|
|
979
|
-
const pmrem = new THREE.PMREMGenerator(this.renderer)
|
|
980
|
-
this.scene.environment = pmrem.fromScene(new RoomEnvironment(), 0.04).texture
|
|
981
|
-
this.scene.environmentIntensity = 0.42
|
|
982
|
-
pmrem.dispose()
|
|
983
|
-
}
|
|
984
|
-
catch { /* 环境贴图失败:退回纯灯光方案 */ }
|
|
985
|
-
|
|
986
|
-
// 穹顶渐变天幕(深空蓝黑 → 地平线工业暖灰;BackSide 大球,随镜头平移)
|
|
987
|
-
this.skyDome = this.makeSkyDome()
|
|
988
|
-
this.scene.add(this.skyDome)
|
|
989
|
-
|
|
990
|
-
// 边界缩放手柄(编辑模式选中频道时显示;4 个:椭圆轴点 / 矩形角点)
|
|
991
|
-
for (let i = 0; i < 4; i++) {
|
|
992
|
-
const h = new THREE.Mesh(
|
|
993
|
-
new THREE.TorusGeometry(16, 6, 8, 20),
|
|
994
|
-
new THREE.MeshBasicMaterial({ color: 0xf6c453, transparent: true, opacity: 0.95, depthTest: false }),
|
|
995
|
-
)
|
|
996
|
-
h.rotation.x = Math.PI / 2
|
|
997
|
-
h.position.y = 1.4
|
|
998
|
-
h.visible = false
|
|
999
|
-
this.scene.add(h)
|
|
1000
|
-
this.resizeHandles.push({ mesh: h, cid: '', handle: i })
|
|
1001
|
-
}
|
|
1002
|
-
// Agent 活动范围缩放手柄(编辑模式选中带范围角色时显示;4 个:椭圆轴点 / 矩形角点)
|
|
1003
|
-
for (let i = 0; i < 4; i++) {
|
|
1004
|
-
const h = new THREE.Mesh(
|
|
1005
|
-
new THREE.TorusGeometry(10, 4, 8, 16),
|
|
1006
|
-
new THREE.MeshBasicMaterial({ color: 0x41c8f4, transparent: true, opacity: 0.95, depthTest: false }),
|
|
1007
|
-
)
|
|
1008
|
-
h.rotation.x = Math.PI / 2
|
|
1009
|
-
h.position.y = 1.2
|
|
1010
|
-
h.visible = false
|
|
1011
|
-
this.scene.add(h)
|
|
1012
|
-
this.agentRangeHandles.push({ mesh: h, agentId: '', handle: i })
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
// 选中高亮环:单位几何 + 每帧按目标缩放(琥珀 #f6c453,加性发光,慢转活性)
|
|
1016
|
-
const selRing = new THREE.Mesh(
|
|
1017
|
-
new THREE.RingGeometry(0.92, 1.0, 56),
|
|
1018
|
-
new THREE.MeshBasicMaterial({ color: 0xf6c453, transparent: true, opacity: 0.85, side: THREE.DoubleSide, depthWrite: false, blending: THREE.AdditiveBlending }),
|
|
1019
|
-
)
|
|
1020
|
-
selRing.rotation.x = -Math.PI / 2
|
|
1021
|
-
selRing.position.y = 0.5
|
|
1022
|
-
selRing.visible = false
|
|
1023
|
-
selRing.renderOrder = 5
|
|
1024
|
-
this.scene.add(selRing)
|
|
1025
|
-
this.selRing = selRing
|
|
1026
|
-
|
|
1027
|
-
// 相机:斜俯视 2.5D(FOV 55 + 远距框景,整个园区一块入画)
|
|
1028
|
-
this.camera = new THREE.PerspectiveCamera(55, (this.el.clientWidth || 1100) / (this.el.clientHeight || 700), 1, 12000)
|
|
1029
|
-
this.camera.position.set(WORLD_CX, 760, WORLD_CZ + 900)
|
|
1030
|
-
this.camera.lookAt(WORLD_CX, 20, WORLD_CZ)
|
|
1031
|
-
|
|
1032
|
-
// Blender 式变换手柄:选中设备时出现(移动 G / 旋转 R / 缩放 S;UI 分段钮 + 键盘同源)
|
|
1033
|
-
const tc = new TransformControls(this.camera, this.renderer.domElement)
|
|
1034
|
-
tc.size = 0.85
|
|
1035
|
-
tc.setMode('translate')
|
|
1036
|
-
tc.showY = false
|
|
1037
|
-
tc.addEventListener('dragging-changed', (e) => {
|
|
1038
|
-
// 手柄拖拽期间压制场景点选/相机平移;松手后设备 transform 落库
|
|
1039
|
-
if (!e.value) {
|
|
1040
|
-
const sel = this.selected
|
|
1041
|
-
if (sel?.kind === 'device' && this.deviceNodes.has(sel.id)) this.persistDeviceTransform(sel.id)
|
|
1042
|
-
}
|
|
1043
|
-
})
|
|
1044
|
-
tc.addEventListener('objectChange', () => {
|
|
1045
|
-
const sel = this.selected
|
|
1046
|
-
if (!sel || sel.kind !== 'device') return
|
|
1047
|
-
const dev = this.deviceNodes.get(sel.id)
|
|
1048
|
-
if (!dev) return
|
|
1049
|
-
// 缩放转移:gizmo 对 root 的缩放倍率转移到模型 holder(免得环/铭牌跟着变大),root 复位
|
|
1050
|
-
if (tc.mode === 'scale' && Math.abs(dev.root.scale.x - 1) > 1e-4) {
|
|
1051
|
-
const reg = this.scalables.get(`device:${sel.id}`)
|
|
1052
|
-
const cur = reg?.userScale ?? 1
|
|
1053
|
-
this.setModelScale(sel.id, Math.min(50, Math.max(0.02, cur * dev.root.scale.x)), 'device')
|
|
1054
|
-
dev.root.scale.set(1, 1, 1)
|
|
1055
|
-
}
|
|
1056
|
-
this.dirty = true
|
|
1057
|
-
})
|
|
1058
|
-
this.tControls = tc
|
|
1059
|
-
this.scene.add(tc.getHelper())
|
|
1060
|
-
|
|
1061
|
-
// 灯光:为 ACES 配平(整体提亮补偿 filmic 滚降;normalBias 消除模型自阴影痤疮)
|
|
1062
|
-
this.scene.add(new THREE.AmbientLight(0xd0e4ee, 1.6))
|
|
1063
|
-
const hemi = new THREE.HemisphereLight(0x9fc7e8, 0x3a2f25, 0.7)
|
|
1064
|
-
this.scene.add(hemi)
|
|
1065
|
-
const key = new THREE.DirectionalLight(0xfff4e0, 3.2)
|
|
1066
|
-
key.position.set(WORLD_CX + 500, 900, WORLD_CZ + 300)
|
|
1067
|
-
key.castShadow = true
|
|
1068
|
-
this.keyLight = key
|
|
1069
|
-
// 高分辨率软影:2048 map + 大 radius(PCSS 观感),贴地阴影是真实感的核心来源
|
|
1070
|
-
key.shadow.mapSize.set(2048, 2048)
|
|
1071
|
-
key.shadow.camera.left = -1400
|
|
1072
|
-
key.shadow.camera.right = 1400
|
|
1073
|
-
key.shadow.camera.top = 2000
|
|
1074
|
-
key.shadow.camera.bottom = -2000
|
|
1075
|
-
key.shadow.normalBias = 2
|
|
1076
|
-
key.shadow.bias = -0.0002
|
|
1077
|
-
key.shadow.radius = 6
|
|
1078
|
-
this.scene.add(key)
|
|
1079
|
-
const fill = new THREE.DirectionalLight(0x88bbff, 0.75)
|
|
1080
|
-
fill.position.set(WORLD_CX - 1200, 900, WORLD_CZ - 800)
|
|
1081
|
-
this.scene.add(fill)
|
|
1082
|
-
// 设计稿光系补光:青色轮廓光(金属切边高光)+ 绿色低位补光(孪生绿氛围)
|
|
1083
|
-
const rim = new THREE.DirectionalLight(0x41c8f4, 1.4)
|
|
1084
|
-
rim.position.set(WORLD_CX - 1400, 620, WORLD_CZ - 1000)
|
|
1085
|
-
this.scene.add(rim)
|
|
1086
|
-
const green = new THREE.DirectionalLight(0x35e0a0, 0.4)
|
|
1087
|
-
green.position.set(WORLD_CX + 700, 380, WORLD_CZ + 900)
|
|
1088
|
-
this.scene.add(green)
|
|
1089
|
-
|
|
1090
|
-
// 地面(大平面,无限观感:7× 世界尺寸 + 高重复贴图)——夜航深蓝地坪;低反射让网格退为纹理
|
|
1091
|
-
const groundMat = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.62, metalness: 0.3, envMapIntensity: 0.42 })
|
|
1092
|
-
this.ground = new THREE.Mesh(new THREE.PlaneGeometry(WORLD_W * 16, WORLD_H * 16), groundMat)
|
|
1093
|
-
this.ground.rotation.x = -Math.PI / 2
|
|
1094
|
-
this.ground.receiveShadow = true
|
|
1095
|
-
this.scene.add(this.ground)
|
|
1096
|
-
|
|
1097
|
-
// 绑定链路层 + 薄膜 web 层(syncDaqLinks / 产线设备增删移动时重建)
|
|
1098
|
-
this.scene.add(this.daqLinkGroup, this.filmWebGroup)
|
|
1099
|
-
|
|
1100
|
-
// 后处理:RenderPass → UnrealBloom(夜航辉光只作用于 HDR 顶端:阈值 3.0 =
|
|
1101
|
-
// 本灯光系下漫反射亮面(2~2.5 线性)不起晕,只留指示灯/金属高光;LED 等指示件
|
|
1102
|
-
// 已按 ×4.5~6 提为 HDR)→ OutputPass(tone mapping + sRGB 收尾;setExposure
|
|
1103
|
-
// 经 OutputPass 透传仍实时生效)。MSAA 目标保住抗锯齿(EffectComposer 默认无 MSAA)。
|
|
1104
|
-
const bloomTarget = new THREE.WebGLRenderTarget(1, 1, { type: THREE.HalfFloatType, samples: 4 })
|
|
1105
|
-
this.composer = new EffectComposer(this.renderer, bloomTarget)
|
|
1106
|
-
this.composer.setPixelRatio(this.renderer.getPixelRatio())
|
|
1107
|
-
this.composer.setSize(this.el.clientWidth || 1100, this.el.clientHeight || 700)
|
|
1108
|
-
this.composer.addPass(new RenderPass(this.scene, this.camera))
|
|
1109
|
-
this.composer.addPass(new UnrealBloomPass(new THREE.Vector2(this.el.clientWidth || 1100, this.el.clientHeight || 700), 0.32, 0.35, 3.0))
|
|
1110
|
-
this.composer.addPass(new OutputPass())
|
|
1111
|
-
|
|
1112
|
-
this.applyGroundTexture()
|
|
1113
|
-
|
|
278
|
+
initRendererCore(this)
|
|
1114
279
|
// E2E/性能探针钩子:真值验证用(渲染帧计数/质量档/pixelRatio),dispose 时清除
|
|
1115
280
|
;(globalThis as { __townScene3d?: TownScene3D }).__townScene3d = this
|
|
1116
281
|
this.loop()
|
|
1117
282
|
}
|
|
1118
283
|
|
|
1119
|
-
/** 工业孪生地面贴图(程序化 Canvas:深色混凝土地基 + 分块拼缝 + 细网格导引线 + 噪点)。
|
|
1120
|
-
* 一次性生成并缓存;SVG 背景贴图仅作历史兼容,优先使用本贴图。 */
|
|
1121
|
-
private makeIndustrialGroundTexture(): THREE.CanvasTexture {
|
|
1122
|
-
const size = 1024
|
|
1123
|
-
const canvas = document.createElement('canvas')
|
|
1124
|
-
canvas.width = size
|
|
1125
|
-
canvas.height = size
|
|
1126
|
-
const ctx = canvas.getContext('2d')!
|
|
1127
|
-
// 深色混凝土地基(微渐变)+ 分块拼缝 + 细网格导引线 —— 夜航深蓝基调(设计稿 #131f36 系)
|
|
1128
|
-
const grad = ctx.createLinearGradient(0, 0, size, size)
|
|
1129
|
-
grad.addColorStop(0, '#151f36')
|
|
1130
|
-
grad.addColorStop(0.5, '#101a2d')
|
|
1131
|
-
grad.addColorStop(1, '#131c31')
|
|
1132
|
-
ctx.fillStyle = grad
|
|
1133
|
-
ctx.fillRect(0, 0, size, size)
|
|
1134
|
-
// 分块拼缝(工业板格 8×8;低调度 —— 地面是舞台,不是主角)
|
|
1135
|
-
ctx.strokeStyle = 'rgba(42,63,102,0.32)'
|
|
1136
|
-
ctx.lineWidth = 2
|
|
1137
|
-
for (let i = 0; i <= 8; i++) {
|
|
1138
|
-
const p = (i / 8) * size
|
|
1139
|
-
ctx.beginPath()
|
|
1140
|
-
ctx.moveTo(p, 0)
|
|
1141
|
-
ctx.lineTo(p, size)
|
|
1142
|
-
ctx.stroke()
|
|
1143
|
-
ctx.beginPath()
|
|
1144
|
-
ctx.moveTo(0, p)
|
|
1145
|
-
ctx.lineTo(size, p)
|
|
1146
|
-
ctx.stroke()
|
|
1147
|
-
}
|
|
1148
|
-
// 细网格导引线(设计稿 GridHelper 主色 0x2a3f66;压暗让设备读第一眼)
|
|
1149
|
-
ctx.strokeStyle = 'rgba(42,63,102,0.2)'
|
|
1150
|
-
ctx.lineWidth = 1
|
|
1151
|
-
for (let i = 0; i <= 32; i++) {
|
|
1152
|
-
const p = (i / 32) * size
|
|
1153
|
-
ctx.beginPath()
|
|
1154
|
-
ctx.moveTo(p, 0)
|
|
1155
|
-
ctx.lineTo(p, size)
|
|
1156
|
-
ctx.stroke()
|
|
1157
|
-
ctx.beginPath()
|
|
1158
|
-
ctx.moveTo(0, p)
|
|
1159
|
-
ctx.lineTo(size, p)
|
|
1160
|
-
ctx.stroke()
|
|
1161
|
-
}
|
|
1162
|
-
// 噪点(亚光粗糙感;伪随机但确定性)
|
|
1163
|
-
ctx.fillStyle = 'rgba(255,255,255,0.02)'
|
|
1164
|
-
for (let i = 0; i < 900; i++) {
|
|
1165
|
-
const x = (i * 733) % size
|
|
1166
|
-
const y = (i * 151) % size
|
|
1167
|
-
ctx.fillRect(x, y, 2, 2)
|
|
1168
|
-
}
|
|
1169
|
-
const tex = new THREE.CanvasTexture(canvas)
|
|
1170
|
-
tex.colorSpace = THREE.SRGBColorSpace
|
|
1171
|
-
tex.wrapS = THREE.RepeatWrapping
|
|
1172
|
-
tex.wrapT = THREE.RepeatWrapping
|
|
1173
|
-
tex.repeat.set(23, 17)
|
|
1174
|
-
tex.anisotropy = Math.min(8, this.renderer.capabilities.getMaxAnisotropy())
|
|
1175
|
-
return tex
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
/** 穹顶天幕:垂直渐变(顶部深空 → 地平线工业暖灰)+ 顶半球星野 + 底部更亮一点,覆盖整球 */
|
|
1179
|
-
private makeSkyDome(): THREE.Mesh {
|
|
1180
|
-
const canvas = document.createElement('canvas')
|
|
1181
|
-
canvas.width = 1024
|
|
1182
|
-
canvas.height = 256
|
|
1183
|
-
const ctx = canvas.getContext('2d')!
|
|
1184
|
-
const grad = ctx.createLinearGradient(0, 0, 0, 256)
|
|
1185
|
-
grad.addColorStop(0, '#080d16') // 天顶:近黑夜空
|
|
1186
|
-
grad.addColorStop(0.40, '#121b27')
|
|
1187
|
-
grad.addColorStop(0.64, '#22303e') // 中段石墨蓝(提前起坡,渐变带更长)
|
|
1188
|
-
grad.addColorStop(0.84, '#3b4b5b') // 地平线:钢色微光(与雾同族)
|
|
1189
|
-
grad.addColorStop(1, '#2a3644') // 地平线下收暗(下半球)
|
|
1190
|
-
ctx.fillStyle = grad
|
|
1191
|
-
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
|
1192
|
-
// 星野(顶部 45%:确定性伪随机,数据青/冷白微点,越靠天顶越密 —— 夜航纵深,低调度不抢戏)
|
|
1193
|
-
let seed = 20260831
|
|
1194
|
-
const rnd = (): number => {
|
|
1195
|
-
seed = (seed * 1103515245 + 12345) % 2147483648
|
|
1196
|
-
return seed / 2147483648
|
|
1197
|
-
}
|
|
1198
|
-
for (let i = 0; i < 220; i++) {
|
|
1199
|
-
const x = rnd() * canvas.width
|
|
1200
|
-
const y = rnd() * rnd() * 118
|
|
1201
|
-
const r = rnd() < 0.88 ? 1 : 2
|
|
1202
|
-
const a = 0.12 + rnd() * 0.5
|
|
1203
|
-
ctx.fillStyle = rnd() < 0.24 ? `rgba(160,220,255,${a.toFixed(2)})` : `rgba(232,240,250,${a.toFixed(2)})`
|
|
1204
|
-
ctx.beginPath()
|
|
1205
|
-
ctx.arc(x, y, r, 0, Math.PI * 2)
|
|
1206
|
-
ctx.fill()
|
|
1207
|
-
}
|
|
1208
|
-
const tex = new THREE.CanvasTexture(canvas)
|
|
1209
|
-
tex.colorSpace = THREE.SRGBColorSpace
|
|
1210
|
-
const geo = new THREE.SphereGeometry(5600, 24, 16)
|
|
1211
|
-
const mat = new THREE.MeshBasicMaterial({ map: tex, side: THREE.BackSide, fog: false, depthWrite: false })
|
|
1212
|
-
const dome = new THREE.Mesh(geo, mat)
|
|
1213
|
-
dome.position.set(WORLD_CX, 0, WORLD_CZ)
|
|
1214
|
-
dome.renderOrder = -10
|
|
1215
|
-
return dome
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
/** 赛博小镇背景贴图 → 地面材质(优先程序化工业贴图;SVG 兼容保留) */
|
|
1219
|
-
private applyGroundTexture(): void {
|
|
1220
|
-
try {
|
|
1221
|
-
const mat = this.ground.material as THREE.MeshStandardMaterial
|
|
1222
|
-
// 工业孪生贴图为主(SVG 历史贴图只在程序化不可用时兜底)
|
|
1223
|
-
mat.map = this.makeIndustrialGroundTexture()
|
|
1224
|
-
mat.color.set(0xffffff)
|
|
1225
|
-
mat.roughness = 0.94
|
|
1226
|
-
mat.metalness = 0.06
|
|
1227
|
-
// 环境反射压低:沥青地面保持亚光,反射生命留给金属设备/角色模型
|
|
1228
|
-
mat.envMapIntensity = 0.28
|
|
1229
|
-
mat.needsUpdate = true
|
|
1230
|
-
this.dirty = true
|
|
1231
|
-
}
|
|
1232
|
-
catch { /* Canvas 不可用:保持纯色地面 */ }
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
284
|
// ================================================================
|
|
1236
285
|
// 事件总线(与 2D 同构)
|
|
1237
286
|
// ================================================================
|
|
@@ -1241,7 +290,7 @@ export class TownScene3D {
|
|
|
1241
290
|
return () => this.busHandlers.delete(event)
|
|
1242
291
|
}
|
|
1243
292
|
|
|
1244
|
-
|
|
293
|
+
emit<K extends keyof TownEventMap>(event: K, e: TownEventMap[K]): void {
|
|
1245
294
|
const fn = this.busHandlers.get(event)
|
|
1246
295
|
if (fn) fn(e)
|
|
1247
296
|
}
|
|
@@ -1253,455 +302,52 @@ export class TownScene3D {
|
|
|
1253
302
|
/**
|
|
1254
303
|
* 用频道实体基线同步领地/角色。仅放置已保存布局的频道(布局来自 useSceneLayouts,
|
|
1255
304
|
* 经 applySceneLayouts 注入);未放置频道只出现在频道坞,不进 3D。
|
|
305
|
+
* 实现见 scene/town-scene3d-blocks.ts。
|
|
1256
306
|
*/
|
|
1257
|
-
|
|
1258
|
-
this.entityIndex.clear()
|
|
1259
|
-
for (const ch of seeds) {
|
|
1260
|
-
this.entityIndex.set(ch.channelId, ch)
|
|
1261
|
-
const layout = this.layouts.get(ch.channelId)
|
|
1262
|
-
if (!layout) continue
|
|
1263
|
-
this.placeChannel(ch, layout)
|
|
1264
|
-
}
|
|
1265
|
-
this.emit('blockCount', this.blocks.size)
|
|
1266
|
-
this.emit('agentCount', this.agents.size)
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
/** 按布局放置一个频道领地(面向对象:Block3D 实例)+ 在其边界内铺放全部 Agent */
|
|
1270
|
-
private placeChannel(ch: TownEntityInput, rawLayout: ChannelLayout): void {
|
|
1271
|
-
const layout = normLayout(rawLayout)
|
|
1272
|
-
const color = channelColorNum(ch.channelId)
|
|
1273
|
-
const pad = this.makeBlock(ch.channelName, color, layout)
|
|
1274
|
-
const block = new Block3D({
|
|
1275
|
-
channelId: ch.channelId, name: ch.channelName,
|
|
1276
|
-
x: layout.x, z: layout.z,
|
|
1277
|
-
radiusX: layout.radiusX, radiusZ: layout.radiusZ,
|
|
1278
|
-
shape: layout.shape, rotationY: layout.rotationY,
|
|
1279
|
-
color,
|
|
1280
|
-
platform: pad.platform,
|
|
1281
|
-
padRing: pad.padRing,
|
|
1282
|
-
beacon: pad.beacon,
|
|
1283
|
-
boundary: makeBoundary(layout.shape, layout.radiusX, layout.radiusZ, color),
|
|
1284
|
-
label: this.makeLabel(ch.channelName, layout.x, 30, layout.z),
|
|
1285
|
-
})
|
|
1286
|
-
block.host = this
|
|
1287
|
-
block.boundary.position.set(layout.x, 0.3, layout.z)
|
|
1288
|
-
block.boundary.rotation.y = layout.rotationY * Math.PI / 180
|
|
1289
|
-
this.scene.add(block.boundary)
|
|
1290
|
-
this.blocks.set(ch.channelId, block)
|
|
1291
|
-
// 在边界内铺放该频道的全部 Agent(lead 居中心,worker 左右展开;钳在边界内)
|
|
1292
|
-
this.layoutAgentsInBlock(ch, block)
|
|
1293
|
-
// 面向对象聚合:把该频道全部成员挂到 Block3D.members(供整体移动/边界钳制批量处理)
|
|
1294
|
-
for (const a of this.agents.values()) {
|
|
1295
|
-
if (a.channelId === ch.channelId && !block.members.includes(a)) block.members.push(a)
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
/** 在频道领地里铺放全部 Agent(按边界内分布;客户自定 home 优先) */
|
|
1300
|
-
private layoutAgentsInBlock(ch: TownEntityInput, block: Block3D): void {
|
|
1301
|
-
const { x, z, radiusX, radiusZ } = block
|
|
1302
|
-
const inner = 0.6 // 内缩比例:agent 铺在边界内侧
|
|
1303
|
-
const n = Math.max(1, ch.agents.length)
|
|
1304
|
-
ch.agents.forEach((a, i) => {
|
|
1305
|
-
// 若该 agent 有 persisted home(且在边界附近)则用之;否则沿长轴均匀铺放
|
|
1306
|
-
let px: number
|
|
1307
|
-
let pz: number
|
|
1308
|
-
if (typeof a.homeX === 'number' && typeof a.homeZ === 'number') {
|
|
1309
|
-
px = a.homeX
|
|
1310
|
-
pz = a.homeZ
|
|
1311
|
-
}
|
|
1312
|
-
else {
|
|
1313
|
-
const t = n <= 1 ? 0 : (i / (n - 1)) * 2 - 1
|
|
1314
|
-
px = x + t * radiusX * inner * 0.8
|
|
1315
|
-
pz = z + (a.role === 'lead' ? 0 : (i % 2 === 0 ? -1 : 1) * radiusZ * inner * 0.4)
|
|
1316
|
-
}
|
|
1317
|
-
this.ensureAgent({ ...a, channelId: ch.channelId }, px, pz, block.color)
|
|
1318
|
-
})
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
/** 领地底盘 = 按真实形状的平台(染色层) + 同形边框环(边界告示);所见即用户设置 */
|
|
1322
|
-
private makeBlock(name: string, color: number, layout: ChannelLayout): { platform: THREE.Mesh, padRing: THREE.Mesh, beacon: THREE.Group } {
|
|
1323
|
-
const rotY = layout.rotationY * Math.PI / 180
|
|
1324
|
-
// 平台:按真实半轴构建(与边框环同一构建路径,无单位几何、无 scale 传递 —— 杜绝缩放残留)
|
|
1325
|
-
const platform = new THREE.Mesh(
|
|
1326
|
-
this.makePadShapeGeometry(layout.shape, layout.radiusX, layout.radiusZ),
|
|
1327
|
-
new THREE.MeshStandardMaterial({
|
|
1328
|
-
color, transparent: true, opacity: 0.24, roughness: 0.85, metalness: 0.12,
|
|
1329
|
-
emissive: color, emissiveIntensity: 0.12, side: THREE.DoubleSide,
|
|
1330
|
-
}),
|
|
1331
|
-
)
|
|
1332
|
-
// 几何已烘焙放平(XY→XZ 无镜像),网格仅做 Y 旋转(与边界线同语义),不再有任何 scale
|
|
1333
|
-
platform.rotation.set(0, rotY, 0)
|
|
1334
|
-
platform.position.set(layout.x, 0.16, layout.z)
|
|
1335
|
-
platform.receiveShadow = false
|
|
1336
|
-
this.scene.add(platform)
|
|
1337
|
-
// 边框环:实际尺寸外形挖去内缩内形(恒定 13 单位线宽,随形状变化重建)
|
|
1338
|
-
const padRing = new THREE.Mesh(
|
|
1339
|
-
this.makePadRingGeometry(layout.shape, layout.radiusX, layout.radiusZ),
|
|
1340
|
-
new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.52, side: THREE.DoubleSide, depthWrite: false }),
|
|
1341
|
-
)
|
|
1342
|
-
padRing.rotation.set(0, rotY, 0)
|
|
1343
|
-
padRing.position.set(layout.x, 0.34, layout.z)
|
|
1344
|
-
this.scene.add(padRing)
|
|
1345
|
-
// 中央信标(HMI 定位销):底座 + 立杆 + 频道色菱形顶标(顶标随渲染循环缓转)
|
|
1346
|
-
const beacon = this.makeBeacon(color)
|
|
1347
|
-
beacon.position.set(layout.x, 0, layout.z)
|
|
1348
|
-
this.scene.add(beacon)
|
|
1349
|
-
void name
|
|
1350
|
-
return { platform, padRing, beacon }
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
/** 频道中心信标(HMI 定位销):点击 → pickBeacon 定位中心 + 唤醒边界编辑 */
|
|
1354
|
-
private makeBeacon(color: number): THREE.Group {
|
|
1355
|
-
const g = new THREE.Group()
|
|
1356
|
-
const metal = new THREE.MeshStandardMaterial({ color: 0x2a3542, roughness: 0.45, metalness: 0.65 })
|
|
1357
|
-
const base = new THREE.Mesh(new THREE.CylinderGeometry(7, 9, 5, 12), metal)
|
|
1358
|
-
base.position.y = 2.5
|
|
1359
|
-
const pole = new THREE.Mesh(new THREE.CylinderGeometry(1.6, 1.6, 30, 8), metal)
|
|
1360
|
-
pole.position.y = 18
|
|
1361
|
-
const tip = new THREE.Mesh(new THREE.OctahedronGeometry(6.5), new THREE.MeshBasicMaterial({ color }))
|
|
1362
|
-
// HDR 亮度:信标顶标是 HMI 定位销的"灯",bloom 起晕(seleRing/边界保持 LDR 不起晕)
|
|
1363
|
-
;(tip.material as THREE.MeshBasicMaterial).color.multiplyScalar(4.5)
|
|
1364
|
-
tip.position.y = 37
|
|
1365
|
-
g.add(base, pole, tip)
|
|
1366
|
-
return g
|
|
1367
|
-
}
|
|
307
|
+
buildBlocks(seeds: TownEntityInput[]): void { buildBlocks(this, seeds) }
|
|
1368
308
|
|
|
1369
309
|
/** 命中频道信标(中心 ±34 单位;点击信标 = 定位中心 + 唤醒边界编辑,任何模式) */
|
|
1370
|
-
|
|
1371
|
-
for (const b of this.blocks.values()) {
|
|
1372
|
-
if (Math.hypot(b.x - x, b.z - z) < 34) return { cid: b.channelId, x: b.x, z: b.z }
|
|
1373
|
-
}
|
|
1374
|
-
return null
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1377
|
-
/** 领地平台面几何:按真实半轴的外形(椭圆/矩形);放平烘焙进顶点(XY→XZ,无镜像),
|
|
1378
|
-
* 网格仅需 rotation.y —— 与边界线同一旋转语义,任意朝向严格贴合 */
|
|
1379
|
-
private makePadShapeGeometry(shape: 'ellipse' | 'rect', radiusX: number, radiusZ: number): THREE.ShapeGeometry {
|
|
1380
|
-
const shp = new THREE.Shape()
|
|
1381
|
-
if (shape === 'rect') {
|
|
1382
|
-
shp.moveTo(-radiusX, -radiusZ)
|
|
1383
|
-
shp.lineTo(radiusX, -radiusZ)
|
|
1384
|
-
shp.lineTo(radiusX, radiusZ)
|
|
1385
|
-
shp.lineTo(-radiusX, radiusZ)
|
|
1386
|
-
shp.closePath()
|
|
1387
|
-
}
|
|
1388
|
-
else {
|
|
1389
|
-
shp.absellipse(0, 0, radiusX, radiusZ, 0, Math.PI * 2)
|
|
1390
|
-
}
|
|
1391
|
-
const geo = new THREE.ShapeGeometry(shp, shape === 'rect' ? 1 : 56)
|
|
1392
|
-
geo.rotateX(Math.PI / 2)
|
|
1393
|
-
return geo
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
/** 领地边框几何:外形挖去内缩 ringW 的同形内形(椭圆/矩形;恒定世界线宽) */
|
|
1397
|
-
private makePadRingGeometry(shape: 'ellipse' | 'rect', radiusX: number, radiusZ: number, ringW = 13): THREE.ShapeGeometry {
|
|
1398
|
-
const irx = Math.max(4, radiusX - ringW)
|
|
1399
|
-
const irz = Math.max(4, radiusZ - ringW)
|
|
1400
|
-
const outer = new THREE.Shape()
|
|
1401
|
-
const inner = new THREE.Path()
|
|
1402
|
-
if (shape === 'rect') {
|
|
1403
|
-
outer.moveTo(-radiusX, -radiusZ)
|
|
1404
|
-
outer.lineTo(radiusX, -radiusZ)
|
|
1405
|
-
outer.lineTo(radiusX, radiusZ)
|
|
1406
|
-
outer.lineTo(-radiusX, radiusZ)
|
|
1407
|
-
outer.closePath()
|
|
1408
|
-
inner.moveTo(-irx, -irz)
|
|
1409
|
-
inner.lineTo(irx, -irz)
|
|
1410
|
-
inner.lineTo(irx, irz)
|
|
1411
|
-
inner.lineTo(-irx, irz)
|
|
1412
|
-
inner.closePath()
|
|
1413
|
-
}
|
|
1414
|
-
else {
|
|
1415
|
-
outer.absellipse(0, 0, radiusX, radiusZ, 0, Math.PI * 2)
|
|
1416
|
-
inner.absellipse(0, 0, irx, irz, 0, Math.PI * 2)
|
|
1417
|
-
}
|
|
1418
|
-
outer.holes.push(inner)
|
|
1419
|
-
const geo = new THREE.ShapeGeometry(outer, shape === 'rect' ? 1 : 56)
|
|
1420
|
-
geo.rotateX(Math.PI / 2)
|
|
1421
|
-
return geo
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
/** 平台/边框贴形(半径或形状变化后):平台与边框几何都按真实尺寸重建;同步朝向 */
|
|
1425
|
-
private applyPadToBlock(b: Block3D): void {
|
|
1426
|
-
const rotY = b.rotationY * Math.PI / 180
|
|
1427
|
-
b.platform.rotation.set(0, rotY, 0)
|
|
1428
|
-
b.padRing.rotation.set(0, rotY, 0)
|
|
1429
|
-
const oldP = b.platform.geometry
|
|
1430
|
-
b.platform.geometry = this.makePadShapeGeometry(b.shape, b.radiusX, b.radiusZ)
|
|
1431
|
-
oldP.dispose()
|
|
1432
|
-
const old = b.padRing.geometry
|
|
1433
|
-
b.padRing.geometry = this.makePadRingGeometry(b.shape, b.radiusX, b.radiusZ)
|
|
1434
|
-
old.dispose()
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
/** 文本 Sprite(名牌/名字)—— HMI 铭牌:深底 + 发丝描边 + 左缘数据条 + 等宽字。
|
|
1438
|
-
* accent: 左缘数据条与描边 tint 的身份色(Agent 铭牌传所属频道哈希色 → 归属一眼可辨)。 */
|
|
1439
|
-
private makeLabel(text: string, x: number, y: number, z: number, accent = '#41c8f4'): THREE.Sprite {
|
|
1440
|
-
const canvas = document.createElement('canvas')
|
|
1441
|
-
const ctx = canvas.getContext('2d')!
|
|
1442
|
-
const font = '600 21px "Geist Mono", Geist, "PingFang SC", monospace'
|
|
1443
|
-
ctx.font = font
|
|
1444
|
-
const padL = 26
|
|
1445
|
-
const padR = 14
|
|
1446
|
-
const w = Math.max(104, Math.ceil(ctx.measureText(text).width) + padL + padR)
|
|
1447
|
-
const h = 48
|
|
1448
|
-
canvas.width = w
|
|
1449
|
-
canvas.height = h
|
|
1450
|
-
ctx.font = font
|
|
1451
|
-
// 深色铭牌底 + 半透明度(不遮场景,只提字)
|
|
1452
|
-
ctx.fillStyle = 'rgba(10,14,20,0.8)'
|
|
1453
|
-
ctx.fillRect(0, 0, w, h)
|
|
1454
|
-
// 发丝描边(身份色轻 tint)+ 左缘数据条(身份色)
|
|
1455
|
-
ctx.strokeStyle = `${accent}66`
|
|
1456
|
-
ctx.lineWidth = 1
|
|
1457
|
-
ctx.strokeRect(0.5, 0.5, w - 1, h - 1)
|
|
1458
|
-
ctx.fillStyle = accent
|
|
1459
|
-
ctx.fillRect(0, 0, 3, h)
|
|
1460
|
-
ctx.fillStyle = '#dce7f0'
|
|
1461
|
-
ctx.textAlign = 'left'
|
|
1462
|
-
ctx.textBaseline = 'middle'
|
|
1463
|
-
ctx.fillText(text, padL, h / 2 + 1)
|
|
1464
|
-
const tex = new THREE.CanvasTexture(canvas)
|
|
1465
|
-
tex.colorSpace = THREE.SRGBColorSpace
|
|
1466
|
-
const sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map: tex, transparent: true, depthTest: false }))
|
|
1467
|
-
sprite.scale.set(w / 6, h / 6, 1)
|
|
1468
|
-
sprite.position.set(x, y, z)
|
|
1469
|
-
this.scene.add(sprite)
|
|
1470
|
-
return sprite
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
/** int 颜色 → CSS hex(铭牌身份色用) */
|
|
1474
|
-
private hexOf(color: number): string {
|
|
1475
|
-
return `#${color.toString(16).padStart(6, '0')}`
|
|
1476
|
-
}
|
|
310
|
+
pickBeacon(x: number, z: number): { cid: string, x: number, z: number } | null { return pickBeacon(this, x, z) }
|
|
1477
311
|
|
|
1478
312
|
// ================================================================
|
|
1479
|
-
// Agent(3D 角色:根 Group + 模型 + 同频道色环 +
|
|
313
|
+
// Agent(3D 角色:根 Group + 模型 + 同频道色环 + 名字;
|
|
314
|
+
// 实现见 scene/town-scene3d-agents.ts)
|
|
1480
315
|
// ================================================================
|
|
1481
316
|
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
// 管理员布局落点(来自 config.homeX/homeZ)优先;缺省按领地内排布;整体钳制在频道边界内
|
|
1486
|
-
const layout = this.blockLayout(a.channelId)
|
|
1487
|
-
let homeX = a.homeX ?? cx
|
|
1488
|
-
let homeZ = a.homeZ ?? cz
|
|
1489
|
-
if (layout) {
|
|
1490
|
-
const clamped = clampToBoundary(layout, homeX, homeZ, 20)
|
|
1491
|
-
homeX = clamped.x
|
|
1492
|
-
homeZ = clamped.z
|
|
1493
|
-
}
|
|
1494
|
-
// 管理员布局活动范围(来自 config.range;缺省 null = 沿用频道边界);收进频道边界
|
|
1495
|
-
const range: AgentRangeLayout | null = a.range ?? null
|
|
1496
|
-
const root = new THREE.Group()
|
|
1497
|
-
root.position.set(homeX, GROUND_Y, homeZ)
|
|
1498
|
-
const nameSprite = this.makeLabel(a.name, homeX, 48, homeZ, this.hexOf(color))
|
|
1499
|
-
// 默认模型(内置二次元角色 hero-anime-1「樱叶少女」;模型库换装可覆盖)
|
|
1500
|
-
const texKey = a.modelRef || 'hero-anime-1'
|
|
1501
|
-
const model = new THREE.Group()
|
|
1502
|
-
root.add(model)
|
|
1503
|
-
this.scene.add(root)
|
|
1504
|
-
const agent = new Agent3D({
|
|
1505
|
-
channelId: a.channelId,
|
|
1506
|
-
agentId: key,
|
|
1507
|
-
name: a.name,
|
|
1508
|
-
role: a.role,
|
|
1509
|
-
root,
|
|
1510
|
-
model,
|
|
1511
|
-
mixer: null,
|
|
1512
|
-
clips: [],
|
|
1513
|
-
colorNum: color,
|
|
1514
|
-
nameSprite,
|
|
1515
|
-
bubble: null,
|
|
1516
|
-
bubbleText: null,
|
|
1517
|
-
bubbleTimer: null,
|
|
1518
|
-
state: a.state,
|
|
1519
|
-
progress: a.currentTaskProgress ?? null,
|
|
1520
|
-
dragging: false,
|
|
1521
|
-
homeX,
|
|
1522
|
-
homeZ,
|
|
1523
|
-
range,
|
|
1524
|
-
rangeLine: null,
|
|
1525
|
-
textureKey: texKey,
|
|
1526
|
-
modelRef: a.modelRef ?? '',
|
|
1527
|
-
behavior: { mode: 'idle', roamTarget: null, targetId: null, waitUntil: 0, pauseUntil: 0, engaged: false },
|
|
1528
|
-
})
|
|
1529
|
-
agent.host = this
|
|
1530
|
-
agent.attachAura(color, a.role)
|
|
1531
|
-
this.agents.set(key, agent)
|
|
1532
|
-
// 载入模型(GLB);模型库注册先于/晚于挂载都能正确解析(内置二次元角色按 id 直取文件)
|
|
1533
|
-
const info = this.modelsById.get(texKey)
|
|
1534
|
-
?? (texKey.startsWith('hero-anime-')
|
|
1535
|
-
? { id: texKey, file: `/assets/game/character/${texKey}.glb`, name: '二次元角色' }
|
|
1536
|
-
: (this.modelsById.get('hero-3d') ?? { id: 'hero-3d', file: '/assets/game/character/hero-3d.glb', name: '标准员工模型' }))
|
|
1537
|
-
void this.mountModel(agent, info.file, info.name)
|
|
1538
|
-
agent.renderRangeLine()
|
|
1539
|
-
this.emit('agentCount', this.agents.size)
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
|
-
/**
|
|
1543
|
-
* 绑定模型到 Agent(GLTFLoader 加载 → 归一化 scale/锚点贴地 → 若有动画则 mixer)。
|
|
1544
|
-
* 缺失/失败 → 回退内置 hero-3d,永不白屏。
|
|
1545
|
-
*/
|
|
1546
|
-
private async mountModel(asp: Agent3D, file: string, name: string): Promise<void> {
|
|
1547
|
-
const cached = this.gltfCache.get(file)
|
|
1548
|
-
let loaded: THREE.Group
|
|
1549
|
-
let height: number
|
|
1550
|
-
if (cached) {
|
|
1551
|
-
loaded = cached.scene.clone(true)
|
|
1552
|
-
height = cached.height
|
|
1553
|
-
}
|
|
1554
|
-
else {
|
|
1555
|
-
try {
|
|
1556
|
-
const gltf = await this.loadGltf(file)
|
|
1557
|
-
loaded = gltf.scene
|
|
1558
|
-
const box = new THREE.Box3().setFromObject(loaded)
|
|
1559
|
-
height = Math.max(0.5, box.max.y - box.min.y)
|
|
1560
|
-
this.gltfCache.set(file, { file, scene: loaded, height })
|
|
1561
|
-
// 缓存动画 clip(供 mixer 状态切换;GLTFLoader 动画必须在解析层保留)
|
|
1562
|
-
this.agentAnimClips.set(file, gltf.animations)
|
|
1563
|
-
}
|
|
1564
|
-
catch {
|
|
1565
|
-
// 加载失败回退程序化「孪生机器人」(胶囊躯干 + 发光核心 + 天线),角色永不隐形
|
|
1566
|
-
loaded = this.makeFallbackBot()
|
|
1567
|
-
height = 1.4
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
// 归一化 scale(使模型高度≈UNITS 世界单位)并贴地(模型大小自适应)
|
|
1571
|
-
loaded.position.y = 0
|
|
1572
|
-
loaded.scale.setScalar(UNITS / height)
|
|
1573
|
-
// 真实投影 + PBR 增强:模型网格 cast/receiveShadow(体积感) + 分级环境反射(角色质感)
|
|
1574
|
-
loaded.traverse((o) => {
|
|
1575
|
-
if ((o as THREE.Mesh).isMesh) o.castShadow = true
|
|
1576
|
-
})
|
|
1577
|
-
this.enhancePbrMaterials(loaded, 'character')
|
|
1578
|
-
// 清掉旧模型子节点与 mixer
|
|
1579
|
-
asp.model.clear()
|
|
1580
|
-
asp.model.add(loaded)
|
|
1581
|
-
// 客制化:注册可缩放目标并恢复用户缩放(套在 asp.model 上,作为自适应之上的倍率层)
|
|
1582
|
-
this.registerScalable('agent', asp.agentId, asp.model)
|
|
1583
|
-
asp.mixer = null
|
|
1584
|
-
asp.activeAction = null
|
|
1585
|
-
const clips = this.agentAnimClips.get(file) ?? []
|
|
1586
|
-
const firstClip = clips[0]
|
|
1587
|
-
if (firstClip) {
|
|
1588
|
-
asp.mixer = new THREE.AnimationMixer(loaded)
|
|
1589
|
-
asp.clips = clips
|
|
1590
|
-
const firstAction = asp.mixer.clipAction(firstClip)
|
|
1591
|
-
firstAction.play()
|
|
1592
|
-
asp.activeAction = firstAction
|
|
1593
|
-
}
|
|
1594
|
-
void name
|
|
1595
|
-
this.dirty = true
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
private agentAnimClips = new Map<string, THREE.AnimationClip[]>()
|
|
1599
|
-
|
|
1600
|
-
private loadGltf(file: string): Promise<{ scene: THREE.Group, animations: THREE.AnimationClip[] }> {
|
|
1601
|
-
return new Promise((resolve, reject) => {
|
|
1602
|
-
this.gltfLoader.load(file, gltf => resolve({ scene: gltf.scene as THREE.Group, animations: gltf.animations ?? [] }), undefined, reject)
|
|
1603
|
-
})
|
|
317
|
+
/** 按实体元数据实例化 Agent3D(落点钳制 / 活动范围 / 身份光环 / 名牌) */
|
|
318
|
+
ensureAgent(a: TownEntityInput['agents'][number] & { channelId: string }, cx: number, cz: number, color: number): void {
|
|
319
|
+
ensureAgent(this, a, cx, cz, color)
|
|
1604
320
|
}
|
|
1605
321
|
|
|
1606
|
-
|
|
1607
|
-
* 程序化「孪生机器人」兜底模型:GLB 缺失/加载失败时使用,保证 Agent 永远可见。
|
|
1608
|
-
* 结构:胶囊躯干 + 头部 + 发光核心胸灯 + 天线信号球 + 悬浮底盘;中性工业灰 + 青蓝核心。
|
|
1609
|
-
*/
|
|
1610
|
-
private makeFallbackBot(): THREE.Group {
|
|
1611
|
-
const bot = new THREE.Group()
|
|
1612
|
-
const bodyMat = new THREE.MeshStandardMaterial({ color: 0x2a3542, roughness: 0.42, metalness: 0.62 })
|
|
1613
|
-
const darkMat = new THREE.MeshStandardMaterial({ color: 0x141b24, roughness: 0.6, metalness: 0.4 })
|
|
1614
|
-
const coreMat = new THREE.MeshBasicMaterial({ color: 0x41c8f4 })
|
|
1615
|
-
// 躯干(胶囊)
|
|
1616
|
-
const body = new THREE.Mesh(new THREE.CapsuleGeometry(0.42, 0.62, 8, 16), bodyMat)
|
|
1617
|
-
body.position.y = 0.86
|
|
1618
|
-
// 头部
|
|
1619
|
-
const head = new THREE.Mesh(new THREE.SphereGeometry(0.32, 16, 12), bodyMat)
|
|
1620
|
-
head.position.y = 1.62
|
|
1621
|
-
// 面窗(发光条带,数字孪生眼)
|
|
1622
|
-
const visor = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.1, 0.12), coreMat)
|
|
1623
|
-
visor.position.set(0, 1.66, 0.27)
|
|
1624
|
-
// 发光核心胸灯
|
|
1625
|
-
const core = new THREE.Mesh(new THREE.SphereGeometry(0.14, 12, 8), coreMat)
|
|
1626
|
-
core.position.set(0, 1.1, 0.36)
|
|
1627
|
-
// 悬浮底盘
|
|
1628
|
-
const base = new THREE.Mesh(new THREE.CylinderGeometry(0.5, 0.62, 0.26, 16), darkMat)
|
|
1629
|
-
base.position.y = 0.16
|
|
1630
|
-
// 天线 + 信号球
|
|
1631
|
-
const antenna = new THREE.Mesh(new THREE.CylinderGeometry(0.03, 0.03, 0.5, 6), darkMat)
|
|
1632
|
-
antenna.position.set(0, 2.04, 0)
|
|
1633
|
-
const signal = new THREE.Mesh(new THREE.SphereGeometry(0.08, 10, 8), coreMat)
|
|
1634
|
-
signal.position.set(0, 2.34, 0)
|
|
1635
|
-
bot.add(body, head, visor, core, base, antenna, signal)
|
|
1636
|
-
return bot
|
|
1637
|
-
}
|
|
322
|
+
agentAnimClips = new Map<string, THREE.AnimationClip[]>()
|
|
1638
323
|
|
|
1639
324
|
/** 注册模型清单,并将晚到资产挂载到已存在的 Agent/设备。 */
|
|
1640
325
|
registerModelsFromList(list: Array<{ id: string, file: string, name: string, kind?: string, hFactor?: number }>): void {
|
|
1641
|
-
|
|
1642
|
-
const changed = new Set<string>()
|
|
1643
|
-
for (const model of list) {
|
|
1644
|
-
const previous = this.modelsById.get(model.id)
|
|
1645
|
-
if (!previous || previous.file !== model.file || previous.name !== model.name || previous.kind !== model.kind || previous.hFactor !== (model.hFactor ?? 1)) {
|
|
1646
|
-
this.modelsById.set(model.id, { ...model, hFactor: model.hFactor ?? 1 })
|
|
1647
|
-
changed.add(model.id)
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
if (changed.size > 0) {
|
|
1651
|
-
for (const agent of this.agents.values()) {
|
|
1652
|
-
if (!agent.modelRef || !changed.has(agent.modelRef)) continue
|
|
1653
|
-
const model = this.modelsById.get(agent.modelRef)
|
|
1654
|
-
if (model) void this.mountModel(agent, model.file, model.name)
|
|
1655
|
-
}
|
|
1656
|
-
for (const device of this.deviceNodes.values()) {
|
|
1657
|
-
if (changed.has(device.modelRef)) this.swapDeviceModelSprite(device, device.modelRef)
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
for (const twin of [...this.pendingDeviceTwins.values()]) {
|
|
1661
|
-
const isDaq = twin.kind === 'daq' || (twin.modelRef ?? '').startsWith('daq-')
|
|
1662
|
-
if (!isDaq && !this.modelsById.get(twin.modelRef)?.file) continue
|
|
1663
|
-
this.pendingDeviceTwins.delete(twin.id)
|
|
1664
|
-
this.recreateDeviceNode(twin)
|
|
1665
|
-
}
|
|
326
|
+
registerModelsFromList(this, list)
|
|
1666
327
|
}
|
|
1667
328
|
|
|
1668
329
|
// ================================================================
|
|
1669
|
-
// 布局注入 / 重建 / 聚焦 / 事件入口(公开面与 2D
|
|
330
|
+
// 布局注入 / 重建 / 聚焦 / 事件入口(公开面与 2D 镜像;
|
|
331
|
+
// 领地层实现见 scene/town-scene3d-blocks.ts)
|
|
1670
332
|
// ================================================================
|
|
1671
333
|
|
|
1672
334
|
/**
|
|
1673
335
|
* 注入频道领地布局(来自 useSceneLayouts)。构建场景前必须调用;
|
|
1674
336
|
* 未放置的频道会在 rebuild 时被跳过(只停留在频道坞)。
|
|
1675
337
|
*/
|
|
1676
|
-
applySceneLayouts(layouts: ChannelLayout[]): void {
|
|
1677
|
-
this.layouts.clear()
|
|
1678
|
-
for (const l of layouts) {
|
|
1679
|
-
const n = normLayout(l)
|
|
1680
|
-
this.layouts.set(n.channelId, n)
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
338
|
+
applySceneLayouts(layouts: ChannelLayout[]): void { applySceneLayouts(this, layouts) }
|
|
1683
339
|
|
|
1684
340
|
/** 频道是否已放入场景(供频道坞标记已放置/未放置) */
|
|
1685
|
-
hasChannel(channelId: string): boolean {
|
|
1686
|
-
return this.blocks.has(channelId)
|
|
1687
|
-
}
|
|
341
|
+
hasChannel(channelId: string): boolean { return hasChannel(this, channelId) }
|
|
1688
342
|
|
|
1689
343
|
/** 频道当前布局(供边界约束;未放置返回空) */
|
|
1690
|
-
|
|
1691
|
-
const b = this.blocks.get(channelId)
|
|
1692
|
-
return b ? b.layout() : null
|
|
1693
|
-
}
|
|
344
|
+
blockLayout(channelId: string): ChannelLayout | null { return blockLayout(this, channelId) }
|
|
1694
345
|
|
|
1695
346
|
/** 已放入场景的频道集(供频道坞/选中面板) */
|
|
1696
|
-
placedChannels(): string[] {
|
|
1697
|
-
return [...this.blocks.keys()]
|
|
1698
|
-
}
|
|
347
|
+
placedChannels(): string[] { return placedChannels(this) }
|
|
1699
348
|
|
|
1700
349
|
/** 频道当前布局(供边界编辑面板初始化) */
|
|
1701
|
-
getChannelLayout(channelId: string): ChannelLayout | null {
|
|
1702
|
-
const b = this.blocks.get(channelId)
|
|
1703
|
-
return b ? b.layout() : null
|
|
1704
|
-
}
|
|
350
|
+
getChannelLayout(channelId: string): ChannelLayout | null { return getChannelLayout(this, channelId) }
|
|
1705
351
|
|
|
1706
352
|
// ================================================================
|
|
1707
353
|
// 控制器公开面(供实体 class 反向调用:场景图/脏标记/布局表/设备名牌与模型)
|
|
@@ -1718,29 +364,13 @@ export class TownScene3D {
|
|
|
1718
364
|
}
|
|
1719
365
|
|
|
1720
366
|
/** 记录频道当前布局到场景布局表(移动/缩放后由 Block3D 调用) */
|
|
1721
|
-
trackLayout(b: Block3D): void {
|
|
1722
|
-
this.layouts.set(b.channelId, b.normLayout())
|
|
1723
|
-
}
|
|
367
|
+
trackLayout(b: Block3D): void { trackLayout(this, b) }
|
|
1724
368
|
|
|
1725
|
-
/** 设备改名:重建名牌 Sprite(由 DeviceNode.applyTwin
|
|
1726
|
-
renameDeviceSprite(dev: DeviceNode, name: string): void {
|
|
1727
|
-
dev.name = name.trim()
|
|
1728
|
-
this.scene.remove(dev.label)
|
|
1729
|
-
dev.label = this.makeLabel(`⚙ ${dev.name}`, dev.root.position.x, 60, dev.root.position.z)
|
|
1730
|
-
this.dirty = true
|
|
1731
|
-
}
|
|
369
|
+
/** 设备改名:重建名牌 Sprite(由 DeviceNode.applyTwin 委托;实现见 scene/town-scene3d-devices.ts) */
|
|
370
|
+
renameDeviceSprite(dev: DeviceNode, name: string): void { renameDeviceSprite(this, dev, name) }
|
|
1732
371
|
|
|
1733
|
-
/** 设备换模型:按 modelRef 重挂 GLB 到 holder(由 DeviceNode.applyTwin 委托)
|
|
1734
|
-
swapDeviceModelSprite(dev: DeviceNode, modelRef: string): void {
|
|
1735
|
-
const info = this.modelsById.get(modelRef)
|
|
1736
|
-
if (!info?.file) return
|
|
1737
|
-
if (dev.modelRef === modelRef && dev.holder.userData.modelFile === info.file) return
|
|
1738
|
-
dev.modelRef = modelRef
|
|
1739
|
-
dev.holder.clear()
|
|
1740
|
-
dev.holder.userData.modelFile = info.file
|
|
1741
|
-
void this.loadGltfToGroup(info.file, dev.holder, UNITS * 1.6)
|
|
1742
|
-
this.dirty = true
|
|
1743
|
-
}
|
|
372
|
+
/** 设备换模型:按 modelRef 重挂 GLB 到 holder(由 DeviceNode.applyTwin 委托) */
|
|
373
|
+
swapDeviceModelSprite(dev: DeviceNode, modelRef: string): void { swapDeviceModelSprite(this, dev, modelRef) }
|
|
1744
374
|
|
|
1745
375
|
/** 统一实例化入口:按数据库元数据(布局/实体/设备孪生)实例化并初始化场景内全部实例。 */
|
|
1746
376
|
hydrate(channels: TownEntityInput[], layouts: ChannelLayout[], devices: DeviceTwinSync[]): void {
|
|
@@ -1765,7 +395,7 @@ export class TownScene3D {
|
|
|
1765
395
|
}
|
|
1766
396
|
|
|
1767
397
|
// ================================================================
|
|
1768
|
-
// 频道放置(drop 入口)+ 活动边界编辑
|
|
398
|
+
// 频道放置(drop 入口)+ 活动边界编辑(实现见 scene/town-scene3d-blocks.ts)
|
|
1769
399
|
// ================================================================
|
|
1770
400
|
|
|
1771
401
|
/**
|
|
@@ -1773,285 +403,61 @@ export class TownScene3D {
|
|
|
1773
403
|
* 频道 id 需已在实体基线中;返回落点世界坐标(供 HUD 提示)。
|
|
1774
404
|
*/
|
|
1775
405
|
dropChannelOnWorld(x: number, z: number, channelId: string, channelName: string, agentCount: number): { x: number, z: number, channelId: string, name: string } {
|
|
1776
|
-
|
|
1777
|
-
if (existing) {
|
|
1778
|
-
this.focusTo(existing.x, existing.z)
|
|
1779
|
-
return { x: Math.round(existing.x), z: Math.round(existing.z), channelId, name: existing.name }
|
|
1780
|
-
}
|
|
1781
|
-
// 缺省布局:以落点为中心、与 Agent 数匹配的边界
|
|
1782
|
-
const rx = Math.max(120, 110 + agentCount * 18)
|
|
1783
|
-
const rz = Math.max(80, 70 + agentCount * 14)
|
|
1784
|
-
const layout: ChannelLayout = { channelId, x, z, radiusX: rx, radiusZ: rz, shape: 'ellipse', rotationY: 0 }
|
|
1785
|
-
this.layouts.set(channelId, normLayout(layout))
|
|
1786
|
-
// 从实体基线取该频道 agents(由 TownView ensureChannelPresent 提前放置)
|
|
1787
|
-
const seed = this.entityIndex.get(channelId)
|
|
1788
|
-
if (seed) {
|
|
1789
|
-
this.placeChannel(seed, layout)
|
|
1790
|
-
}
|
|
1791
|
-
else {
|
|
1792
|
-
// 无实体基线(频道坞拖入,数据尚未到达):只建空领地占位
|
|
1793
|
-
const color = channelColorNum(channelId)
|
|
1794
|
-
const pad = this.makeBlock(channelName, color, layout)
|
|
1795
|
-
const block = new Block3D({
|
|
1796
|
-
channelId, name: channelName, x, z,
|
|
1797
|
-
radiusX: layout.radiusX, radiusZ: layout.radiusZ,
|
|
1798
|
-
shape: layout.shape, rotationY: layout.rotationY, color,
|
|
1799
|
-
platform: pad.platform,
|
|
1800
|
-
padRing: pad.padRing,
|
|
1801
|
-
beacon: pad.beacon,
|
|
1802
|
-
boundary: makeBoundary(layout.shape, layout.radiusX, layout.radiusZ, color),
|
|
1803
|
-
label: this.makeLabel(channelName, x, 30, z),
|
|
1804
|
-
})
|
|
1805
|
-
block.host = this
|
|
1806
|
-
block.boundary.position.set(x, 0.3, z)
|
|
1807
|
-
this.scene.add(block.boundary)
|
|
1808
|
-
this.blocks.set(channelId, block)
|
|
1809
|
-
}
|
|
1810
|
-
this.emit('blockCount', this.blocks.size)
|
|
1811
|
-
this.emit('agentCount', this.agents.size)
|
|
1812
|
-
return { x: Math.round(x), z: Math.round(z), channelId, name: channelName }
|
|
406
|
+
return dropChannelOnWorld(this, x, z, channelId, channelName, agentCount)
|
|
1813
407
|
}
|
|
1814
408
|
|
|
1815
409
|
/** 更新频道布局(编辑边界后本地即时生效;持久化由 TownView 经 useSceneLayouts.save 落库) */
|
|
1816
|
-
updateChannelLayout(channelId: string, patch: Partial<ChannelLayout>): void {
|
|
1817
|
-
const b = this.blocks.get(channelId)
|
|
1818
|
-
if (!b) return
|
|
1819
|
-
if (patch.x !== undefined) b.x = patch.x
|
|
1820
|
-
if (patch.z !== undefined) b.z = patch.z
|
|
1821
|
-
if (patch.radiusX !== undefined) b.radiusX = Math.max(60, patch.radiusX)
|
|
1822
|
-
if (patch.radiusZ !== undefined) b.radiusZ = Math.max(40, patch.radiusZ)
|
|
1823
|
-
if (patch.shape !== undefined) b.shape = patch.shape
|
|
1824
|
-
if (patch.rotationY !== undefined) b.rotationY = patch.rotationY
|
|
1825
|
-
const layout = normLayout({ channelId, x: b.x, z: b.z, radiusX: b.radiusX, radiusZ: b.radiusZ, shape: b.shape, rotationY: b.rotationY })
|
|
1826
|
-
this.layouts.set(channelId, layout)
|
|
1827
|
-
// 重建领地平台/边界/名牌
|
|
1828
|
-
this.scene.remove(b.boundary)
|
|
1829
|
-
this.applyPadToBlock(b)
|
|
1830
|
-
b.boundary = makeBoundary(layout.shape, layout.radiusX, layout.radiusZ, b.color)
|
|
1831
|
-
b.boundary.position.set(layout.x, 0.3, layout.z)
|
|
1832
|
-
b.boundary.rotation.y = layout.rotationY * Math.PI / 180
|
|
1833
|
-
this.scene.add(b.boundary)
|
|
1834
|
-
// 频道边界变化后把成员活动范围一并收进新边界(面板滑杆编辑路径)
|
|
1835
|
-
this.clampAgentsToBoundary(channelId)
|
|
1836
|
-
this.dirty = true
|
|
1837
|
-
}
|
|
410
|
+
updateChannelLayout(channelId: string, patch: Partial<ChannelLayout>): void { updateChannelLayout(this, channelId, patch) }
|
|
1838
411
|
|
|
1839
412
|
/** 移除频道放置(从场景撤走领地及其 Agent) */
|
|
1840
|
-
removeChannel(channelId: string): void {
|
|
1841
|
-
const b = this.blocks.get(channelId)
|
|
1842
|
-
if (!b) return
|
|
1843
|
-
this.scene.remove(b.platform)
|
|
1844
|
-
this.scene.remove(b.padRing)
|
|
1845
|
-
this.scene.remove(b.beacon)
|
|
1846
|
-
if (b.boundary) this.scene.remove(b.boundary)
|
|
1847
|
-
this.scene.remove(b.label)
|
|
1848
|
-
for (const [aid, a] of [...this.agents.entries()]) {
|
|
1849
|
-
if (a.channelId === channelId) {
|
|
1850
|
-
this.scene.remove(a.root)
|
|
1851
|
-
if (a.nameSprite) this.scene.remove(a.nameSprite)
|
|
1852
|
-
if (a.bubble) this.scene.remove(a.bubble)
|
|
1853
|
-
if (a.rangeLine) this.scene.remove(a.rangeLine)
|
|
1854
|
-
this.disposeAgentAssets(a)
|
|
1855
|
-
this.agents.delete(aid)
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
if (b.label) this.disposeCanvasTextures(b.label)
|
|
1859
|
-
if (this.rangeDrawAgent && this.agents.get(this.rangeDrawAgent) === undefined) this.cancelRangeDraw()
|
|
1860
|
-
this.blocks.delete(channelId)
|
|
1861
|
-
this.layouts.delete(channelId)
|
|
1862
|
-
this.receivers.delete(channelId)
|
|
1863
|
-
for (const hl of this.resizeHandles) hl.mesh.visible = false
|
|
1864
|
-
for (const hl of this.agentRangeHandles) hl.mesh.visible = false
|
|
1865
|
-
if (this.selectedChannel === channelId) {
|
|
1866
|
-
this.selectedChannel = null
|
|
1867
|
-
this.emit('selectChannel', null)
|
|
1868
|
-
}
|
|
1869
|
-
this.emit('blockCount', this.blocks.size)
|
|
1870
|
-
this.emit('agentCount', this.agents.size)
|
|
1871
|
-
this.dirty = true
|
|
1872
|
-
}
|
|
413
|
+
removeChannel(channelId: string): void { removeChannel(this, channelId) }
|
|
1873
414
|
|
|
1874
415
|
// ================================================================
|
|
1875
416
|
// 频道整体拖拽 / 边界手柄缩放(编辑模式;用户自定义布局)
|
|
417
|
+
// 实现见 scene/town-scene3d-editing.ts
|
|
1876
418
|
// ================================================================
|
|
1877
419
|
|
|
1878
420
|
/** 把频道地块整体平移(委托 Block3D.moveBy:平台/边界/名牌/成员落点与各自范围一并位移) */
|
|
1879
|
-
|
|
1880
|
-
b.moveBy(nx - b.x, nz - b.z)
|
|
1881
|
-
this.refreshAgentRangeHandles()
|
|
1882
|
-
this.dirty = true
|
|
1883
|
-
}
|
|
421
|
+
applyBlockMove(b: Block3D, nx: number, nz: number): void { applyBlockMove(this, b, nx, nz) }
|
|
1884
422
|
|
|
1885
423
|
/** 按当前 Block 字段重建领地几何(平台刻度/边界线框/朝向),供缩放与移动共用 */
|
|
1886
|
-
|
|
1887
|
-
const layout = { channelId: b.channelId, x: b.x, z: b.z, radiusX: b.radiusX, radiusZ: b.radiusZ, shape: b.shape, rotationY: b.rotationY }
|
|
1888
|
-
this.layouts.set(b.channelId, normLayout(layout))
|
|
1889
|
-
b.platform.position.set(b.x, 0.16, b.z)
|
|
1890
|
-
b.padRing.position.set(b.x, 0.34, b.z)
|
|
1891
|
-
this.applyPadToBlock(b)
|
|
1892
|
-
if (b.boundary) this.scene.remove(b.boundary)
|
|
1893
|
-
b.boundary = makeBoundary(b.shape, b.radiusX, b.radiusZ, b.color)
|
|
1894
|
-
b.boundary.position.set(b.x, 0.3, b.z)
|
|
1895
|
-
b.boundary.rotation.y = b.rotationY * Math.PI / 180
|
|
1896
|
-
this.scene.add(b.boundary)
|
|
1897
|
-
this.dirty = true
|
|
1898
|
-
}
|
|
424
|
+
applyLayoutToBlock(b: Block3D): void { applyLayoutToBlock(this, b) }
|
|
1899
425
|
|
|
1900
|
-
/** 边界手柄本地坐标(椭圆:轴向四点;矩形:四角;radius 是半轴或半宽)
|
|
1901
|
-
|
|
1902
|
-
if (layout.shape === 'rect') return [[layout.radiusX, layout.radiusZ], [-layout.radiusX, layout.radiusZ], [-layout.radiusX, -layout.radiusZ], [layout.radiusX, -layout.radiusZ]]
|
|
1903
|
-
return [[layout.radiusX, 0], [-layout.radiusX, 0], [0, layout.radiusZ], [0, -layout.radiusZ]]
|
|
1904
|
-
}
|
|
426
|
+
/** 边界手柄本地坐标(椭圆:轴向四点;矩形:四角;radius 是半轴或半宽)。宿主契约面:ranges 模块回调。 */
|
|
427
|
+
boundaryHandlePoints(layout: { radiusX: number, radiusZ: number, shape: 'ellipse' | 'rect' }): Array<[number, number]> { return boundaryHandlePoints(layout) }
|
|
1905
428
|
|
|
1906
429
|
/** 刷新边界手柄位置/可见性(编辑模式选中频道时显示;其余隐藏) */
|
|
1907
|
-
|
|
1908
|
-
for (const hl of this.resizeHandles) hl.mesh.visible = false
|
|
1909
|
-
if (this.mode !== 'edit' || !this.selectedChannel) return
|
|
1910
|
-
const b = this.blocks.get(this.selectedChannel)
|
|
1911
|
-
if (!b) return
|
|
1912
|
-
const rot = b.rotationY * Math.PI / 180
|
|
1913
|
-
const pts = this.boundaryHandlePoints(b)
|
|
1914
|
-
for (let i = 0; i < this.resizeHandles.length && i < pts.length; i++) {
|
|
1915
|
-
const [lx, lz] = pts[i]!
|
|
1916
|
-
const wx = b.x + lx * Math.cos(rot) - lz * Math.sin(rot)
|
|
1917
|
-
const wz = b.z + lx * Math.sin(rot) + lz * Math.cos(rot)
|
|
1918
|
-
this.resizeHandles[i]!.cid = b.channelId
|
|
1919
|
-
this.resizeHandles[i]!.mesh.position.set(wx, 1.4, wz)
|
|
1920
|
-
this.resizeHandles[i]!.mesh.visible = true
|
|
1921
|
-
}
|
|
1922
|
-
}
|
|
430
|
+
refreshChannelHandles(): void { refreshChannelHandles(this) }
|
|
1923
431
|
|
|
1924
432
|
/** 命中边界手柄(仅编辑模式;返回频道 id + 手柄号) */
|
|
1925
|
-
|
|
1926
|
-
if (this.mode !== 'edit') return null
|
|
1927
|
-
for (const hl of this.resizeHandles) {
|
|
1928
|
-
if (!hl.mesh.visible) continue
|
|
1929
|
-
if (Math.hypot(hl.mesh.position.x - x, hl.mesh.position.z - z) < 90) return { cid: hl.cid, handle: hl.handle }
|
|
1930
|
-
}
|
|
1931
|
-
return null
|
|
1932
|
-
}
|
|
433
|
+
pickResizeHandle(x: number, z: number): { cid: string, handle: number } | null { return pickResizeHandle(this, x, z) }
|
|
1933
434
|
|
|
1934
435
|
/** 拖拽手柄 → 实时调整 radiusX/radiusZ(矩形:角点双轴;椭圆:对应轴向轴点)。 */
|
|
1935
|
-
|
|
1936
|
-
const rad = -b.rotationY * Math.PI / 180
|
|
1937
|
-
const cos = Math.cos(rad)
|
|
1938
|
-
const sin = Math.sin(rad)
|
|
1939
|
-
const dx = wx - b.x
|
|
1940
|
-
const dz = wz - b.z
|
|
1941
|
-
const lx = dx * cos - dz * sin
|
|
1942
|
-
const lz = dx * sin + dz * cos
|
|
1943
|
-
if (b.shape === 'rect') {
|
|
1944
|
-
// radiusX/radiusZ 表示半宽:角点到中心的局部距离即新半宽(无上限,相机自动跟随取景)
|
|
1945
|
-
b.radiusX = Math.max(60, Math.abs(lx))
|
|
1946
|
-
b.radiusZ = Math.max(40, Math.abs(lz))
|
|
1947
|
-
}
|
|
1948
|
-
else {
|
|
1949
|
-
if (handle === 0 || handle === 1) b.radiusX = Math.max(60, Math.abs(lx))
|
|
1950
|
-
else b.radiusZ = Math.max(40, Math.abs(lz))
|
|
1951
|
-
}
|
|
1952
|
-
this.applyLayoutToBlock(b)
|
|
1953
|
-
this.refreshChannelHandles()
|
|
1954
|
-
this.autoFrameTo(b.radiusX, b.radiusZ)
|
|
1955
|
-
this.dirty = true
|
|
1956
|
-
}
|
|
436
|
+
applyResize(b: Block3D, handle: number, wx: number, wz: number): void { applyResize(this, b, handle, wx, wz) }
|
|
1957
437
|
|
|
1958
438
|
/** 缩放/移动结束后把该频道成员落点与活动范围钳回新边界内(委托 Block3D) */
|
|
1959
|
-
|
|
1960
|
-
const b = this.blocks.get(channelId)
|
|
1961
|
-
if (!b) return
|
|
1962
|
-
b.clampMembersAndRanges()
|
|
1963
|
-
this.dirty = true
|
|
1964
|
-
}
|
|
439
|
+
clampAgentsToBoundary(channelId: string): void { clampAgentsToBoundary(this, channelId) }
|
|
1965
440
|
|
|
1966
441
|
/** 频道是否在场景内且被选中(供边界编辑面板) */
|
|
1967
|
-
getSelectedChannel(): string | null {
|
|
1968
|
-
return this.selectedChannel
|
|
1969
|
-
}
|
|
442
|
+
getSelectedChannel(): string | null { return getSelectedChannel(this) }
|
|
1970
443
|
|
|
1971
444
|
/** 点选频道(供频道坞/边界编辑面板打开) */
|
|
1972
|
-
selectChannel(channelId: string | null): void {
|
|
1973
|
-
this.selectedChannel = channelId
|
|
1974
|
-
this.emit('selectChannel', channelId)
|
|
1975
|
-
this.refreshChannelHandles()
|
|
1976
|
-
this.cancelRangeDraw()
|
|
1977
|
-
this.refreshAgentRangeHandles()
|
|
1978
|
-
// 选中频道边界高亮勾边(取消时恢复频道本色)
|
|
1979
|
-
for (const bb of this.blocks.values()) {
|
|
1980
|
-
const m = bb.boundary.material as THREE.LineBasicMaterial
|
|
1981
|
-
m.color.setHex(bb.channelId === channelId ? 0xf6c453 : bb.color)
|
|
1982
|
-
m.opacity = bb.channelId === channelId ? 1 : 0.75
|
|
1983
|
-
}
|
|
1984
|
-
// 拖拽中不聚焦(避免拖频道/手柄时相机被拽走导致落点错乱)
|
|
1985
|
-
if (channelId && !this.pointerDrag) this.focusTo(this.blocks.get(channelId)?.x ?? WORLD_CX, this.blocks.get(channelId)?.z ?? WORLD_CZ)
|
|
1986
|
-
}
|
|
445
|
+
selectChannel(channelId: string | null): void { selectChannel(this, channelId) }
|
|
1987
446
|
|
|
1988
447
|
/** 当前场景内全部频道布局(供「保存全部布局」/E2E) */
|
|
1989
|
-
getAllChannelLayouts(): ChannelLayout[] {
|
|
1990
|
-
return [...this.blocks.values()].map(b => ({
|
|
1991
|
-
channelId: b.channelId, x: b.x, z: b.z,
|
|
1992
|
-
radiusX: b.radiusX, radiusZ: b.radiusZ,
|
|
1993
|
-
shape: b.shape, rotationY: b.rotationY,
|
|
1994
|
-
}))
|
|
1995
|
-
}
|
|
448
|
+
getAllChannelLayouts(): ChannelLayout[] { return getAllChannelLayouts(this) }
|
|
1996
449
|
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
// 频道布局事件:本地已放同一频道则即时应用(他人编辑边界/移入场景 → 本端同步)
|
|
2000
|
-
if (e.type === 'scene.layout.saved') {
|
|
2001
|
-
const l = e.payload as ChannelLayout
|
|
2002
|
-
if (this.blocks.has(l.channelId)) {
|
|
2003
|
-
this.applySceneLayouts([...(this.layouts.values()), l])
|
|
2004
|
-
// 更新对应领地几何
|
|
2005
|
-
const cur = this.getChannelLayout(l.channelId)
|
|
2006
|
-
if (cur) this.updateChannelLayout(l.channelId, l)
|
|
2007
|
-
}
|
|
2008
|
-
return
|
|
2009
|
-
}
|
|
2010
|
-
if (e.type === 'scene.layout.removed') {
|
|
2011
|
-
const { channelId } = e.payload as { channelId: string }
|
|
2012
|
-
this.removeChannel(channelId)
|
|
2013
|
-
return
|
|
2014
|
-
}
|
|
2015
|
-
const intent = mapEnvelopeToIntent(e)
|
|
2016
|
-
if (!intent) return
|
|
2017
|
-
if (intent.agentId) {
|
|
2018
|
-
const asp = this.agents.get(intent.agentId)
|
|
2019
|
-
if (asp) {
|
|
2020
|
-
if (e.type === 'agent.status') {
|
|
2021
|
-
asp.state = (e.payload as { state: 'idle' | 'busy' | 'stopped' }).state
|
|
2022
|
-
if (asp.state !== 'busy') asp.progress = null
|
|
2023
|
-
}
|
|
2024
|
-
if (e.type === 'task.progress') asp.progress = (e.payload as { progress: number }).progress
|
|
2025
|
-
this.dirty = true
|
|
2026
|
-
}
|
|
2027
|
-
}
|
|
2028
|
-
if (intent.bubble) this.enqueueBubble(intent.bubble.channelId, intent.bubble.agentId, intent.bubble.kind, intent.bubble.text, intent.bubble.ttlMs)
|
|
2029
|
-
this.emitResonance(e)
|
|
2030
|
-
const action = parseActionFromEnvelope(e, { resolveTaskAssignee: this.resolveTaskAssignee ?? undefined })
|
|
2031
|
-
if (action) this.startBehavior(action)
|
|
2032
|
-
}
|
|
450
|
+
/** WS 事件入口(与 2D 同构):布局同步 / 状态收敛 / 气泡入队 / 共鸣 / 行为决策(实现见 scene/town-scene3d-events.ts) */
|
|
451
|
+
handleTownEvent(e: AepEnvelope): void { handleTownEvent(this, e) }
|
|
2033
452
|
|
|
2034
453
|
// ================================================================
|
|
2035
|
-
// 行为 FSM(3D x/z 平面,复刻 2D
|
|
454
|
+
// 行为 FSM(3D x/z 平面,复刻 2D 逻辑;实现见 scene/town-scene3d-events.ts)
|
|
2036
455
|
// ================================================================
|
|
2037
456
|
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
const to = this.agents.get(action.toId)
|
|
2041
|
-
if (!from || !to || from.dragging) return
|
|
2042
|
-
from.behavior.mode = 'approach'
|
|
2043
|
-
from.behavior.targetId = to.agentId
|
|
2044
|
-
from.behavior.action = action
|
|
2045
|
-
if (!to.dragging) {
|
|
2046
|
-
to.behavior.engaged = true
|
|
2047
|
-
to.behavior.roamTarget = null
|
|
2048
|
-
}
|
|
2049
|
-
this.emit('behavior', { agentName: from.name, action: this.behaviorActionLabel(action.kind), targetName: to.name })
|
|
2050
|
-
}
|
|
457
|
+
/** 触发一次「跑去下发」行为:from 跑到 to 身边 */
|
|
458
|
+
startBehavior(action: ActionContext): void { startBehavior(this, action) }
|
|
2051
459
|
|
|
2052
|
-
|
|
2053
|
-
return kind === 'task' ? '下发任务' : '回复'
|
|
2054
|
-
}
|
|
460
|
+
behaviorActionLabel(kind: ActionKind): string { return behaviorActionLabel(kind) }
|
|
2055
461
|
|
|
2056
462
|
/** 动画状态切换广播(数据驱动模型 → HUD/E2E 观察) */
|
|
2057
463
|
notifyMotion(asp: Agent3D): void {
|
|
@@ -2082,348 +488,43 @@ export class TownScene3D {
|
|
|
2082
488
|
return this.blockLayout(channelId)
|
|
2083
489
|
}
|
|
2084
490
|
|
|
2085
|
-
private resolveFile(id: string): string {
|
|
2086
|
-
return this.modelsById.get(id)?.file ?? ''
|
|
2087
|
-
}
|
|
2088
|
-
|
|
2089
491
|
/** 相机注视目标(供频道坞首放落点/聚焦) */
|
|
2090
|
-
getCameraTarget(): { x: number, z: number } {
|
|
2091
|
-
return { x: Math.round(this.camTarget.x), z: Math.round(this.camTarget.z) }
|
|
2092
|
-
}
|
|
492
|
+
getCameraTarget(): { x: number, z: number } { return getCameraTarget(this) }
|
|
2093
493
|
|
|
2094
494
|
/** 为指定角色换装模型(选择器绑定;要求实体已在场景,否则仅记录) */
|
|
2095
|
-
swapAgentModel(agentId: string, modelRef: string): void {
|
|
2096
|
-
const asp = this.agents.get(agentId)
|
|
2097
|
-
if (!asp) return
|
|
2098
|
-
const info = this.modelsById.get(modelRef)
|
|
2099
|
-
const file = info?.file ?? '/assets/game/character/hero-3d.glb'
|
|
2100
|
-
asp.modelRef = modelRef
|
|
2101
|
-
asp.textureKey = modelRef
|
|
2102
|
-
void this.mountModel(asp, file, info?.name ?? modelRef)
|
|
2103
|
-
}
|
|
495
|
+
swapAgentModel(agentId: string, modelRef: string): void { swapAgentModel(this, agentId, modelRef) }
|
|
2104
496
|
|
|
2105
497
|
// ================================================================
|
|
2106
498
|
// 事件共鸣(说话/完成/错误/旗) —— 3D 圆环/光柱
|
|
499
|
+
// 实现见 scene/town-scene3d-events.ts
|
|
2107
500
|
// ================================================================
|
|
2108
501
|
|
|
2109
|
-
|
|
2110
|
-
const b = this.blocks.get(e.channelId)
|
|
2111
|
-
const asp = e.agentId ? this.agents.get(e.agentId) : undefined
|
|
2112
|
-
const x = asp?.root.position.x ?? b?.x
|
|
2113
|
-
const z = asp?.root.position.z ?? b?.z
|
|
2114
|
-
if (x === undefined || z === undefined) return
|
|
2115
|
-
if (e.type === 'agent.message' || e.type === 'agent.status.message' || e.type === 'a2a.message') {
|
|
2116
|
-
this.pulseRing(x, z, asp ? 0x41c8f4 : (b?.color ?? 0x41c8f4))
|
|
2117
|
-
}
|
|
2118
|
-
else if (e.type === 'error' || (e.type === 'task.status' && ((e.payload as { state?: string }).state === 'failed' || (e.payload as { state?: string }).state === 'canceled'))) {
|
|
2119
|
-
this.pulseRing(x, z, 0xff6b6b)
|
|
2120
|
-
}
|
|
2121
|
-
else if (e.type === 'task.status' && (e.payload as { state?: string }).state === 'completed') {
|
|
2122
|
-
this.lightColumn(x, z, 0x35e0a0)
|
|
2123
|
-
}
|
|
2124
|
-
}
|
|
2125
|
-
|
|
2126
|
-
/** 事件共鸣扩散环(时间基:800ms 缓出扩散 + 淡出;帧率无关,结束即释放资源) */
|
|
2127
|
-
private pulseRing(x: number, z: number, color: number): void {
|
|
2128
|
-
const ring = new THREE.Mesh(new THREE.RingGeometry(10, 22, 24), new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.8, side: THREE.DoubleSide, depthWrite: false }))
|
|
2129
|
-
ring.rotation.x = -Math.PI / 2
|
|
2130
|
-
ring.position.set(x, 0.4, z)
|
|
2131
|
-
this.scene.add(ring)
|
|
2132
|
-
const start = performance.now()
|
|
2133
|
-
const dur = 800
|
|
2134
|
-
const step = () => {
|
|
2135
|
-
const k = Math.min(1, (performance.now() - start) / dur)
|
|
2136
|
-
const e = 1 - Math.pow(1 - k, 3)
|
|
2137
|
-
ring.scale.setScalar(1 + e * 1.6)
|
|
2138
|
-
;(ring.material as THREE.MeshBasicMaterial).opacity = 0.8 * (1 - e)
|
|
2139
|
-
if (k >= 1) {
|
|
2140
|
-
this.scene.remove(ring)
|
|
2141
|
-
ring.geometry.dispose()
|
|
2142
|
-
;(ring.material as THREE.MeshBasicMaterial).dispose()
|
|
2143
|
-
return
|
|
2144
|
-
}
|
|
2145
|
-
this.rafAnims.push(step)
|
|
2146
|
-
}
|
|
2147
|
-
this.rafAnims.push(step)
|
|
2148
|
-
}
|
|
2149
|
-
|
|
2150
|
-
/** 交付光柱(时间基:底部锚定向上生长 + 淡出;几何上移锚定,修复旧零高度柱不可见的缺陷) */
|
|
2151
|
-
private lightColumn(x: number, z: number, color: number): void {
|
|
2152
|
-
const geo = new THREE.CylinderGeometry(6, 12, 120, 16)
|
|
2153
|
-
geo.translate(0, 60, 0)
|
|
2154
|
-
const beam = new THREE.Mesh(geo, new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.8, depthWrite: false }))
|
|
2155
|
-
beam.position.set(x, 0, z)
|
|
2156
|
-
beam.scale.y = 0.01
|
|
2157
|
-
this.scene.add(beam)
|
|
2158
|
-
const start = performance.now()
|
|
2159
|
-
const dur = 900
|
|
2160
|
-
const step = () => {
|
|
2161
|
-
const k = Math.min(1, (performance.now() - start) / dur)
|
|
2162
|
-
const e = 1 - Math.pow(1 - k, 2)
|
|
2163
|
-
beam.scale.y = 0.01 + e * 0.99
|
|
2164
|
-
;(beam.material as THREE.MeshBasicMaterial).opacity = 0.8 * (1 - k)
|
|
2165
|
-
if (k >= 1) {
|
|
2166
|
-
this.scene.remove(beam)
|
|
2167
|
-
beam.geometry.dispose()
|
|
2168
|
-
;(beam.material as THREE.MeshBasicMaterial).dispose()
|
|
2169
|
-
return
|
|
2170
|
-
}
|
|
2171
|
-
this.rafAnims.push(step)
|
|
2172
|
-
}
|
|
2173
|
-
this.rafAnims.push(step)
|
|
2174
|
-
}
|
|
502
|
+
emitResonance(e: AepEnvelope): void { emitResonance(this, e) }
|
|
2175
503
|
|
|
2176
|
-
|
|
504
|
+
rafAnims: Array<() => void> = []
|
|
2177
505
|
|
|
2178
506
|
/** 频道领地布局(channelId → 放置);由 applySceneLayouts 注入,驱动 buildBlocks/边界约束 */
|
|
2179
|
-
|
|
507
|
+
layouts = new Map<string, ChannelLayout>()
|
|
2180
508
|
/** 频道实体基线(channelId → seed;供 dropChannelOnWorld 即时铺放) */
|
|
2181
|
-
|
|
509
|
+
entityIndex = new Map<string, TownEntityInput>()
|
|
2182
510
|
/** 当前选中频道(供边界编辑面板) */
|
|
2183
|
-
|
|
511
|
+
selectedChannel: string | null = null
|
|
2184
512
|
|
|
2185
513
|
// ================================================================
|
|
2186
514
|
// 信息接收器 + 聊天气泡(每个实例化 Channel 一个接收器:FIFO 逐条消费,
|
|
2187
515
|
// WS 实时信息经 townBus → handleTownEvent 入队,渲染到对应 Agent 头顶,像真实对话)
|
|
516
|
+
// 实现见 scene/town-scene3d-bubbles.ts
|
|
2188
517
|
// ================================================================
|
|
2189
518
|
|
|
2190
519
|
/** 实时信息(讲话/交付/错误)入队到目标频道的信息接收器;系统指标即时更新,3D 展示按 FIFO 消费 */
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
this.dbgBubbles.push({ text, at: Date.now() })
|
|
2194
|
-
if (this.dbgBubbles.length > 30) this.dbgBubbles.splice(0, this.dbgBubbles.length - 30)
|
|
2195
|
-
const speaker = agentId ? this.agents.get(agentId)?.name : undefined
|
|
2196
|
-
this.lastActivity = { channelId, agentName: speaker ?? this.blocks.get(channelId)?.name ?? '系统', text, at: Date.now() }
|
|
2197
|
-
this.recentActivity.push(this.lastActivity)
|
|
2198
|
-
if (this.recentActivity.length > 30) this.recentActivity.splice(0, this.recentActivity.length - 30)
|
|
2199
|
-
this.emit('lastActivity', this.lastActivity)
|
|
2200
|
-
// 入队(FIFO;超长队列丢最旧,防止异常风暴撑爆内存)
|
|
2201
|
-
let rec = this.receivers.get(channelId)
|
|
2202
|
-
if (!rec) {
|
|
2203
|
-
rec = { channelId, queue: [], current: null, currentUntil: 0 }
|
|
2204
|
-
this.receivers.set(channelId, rec)
|
|
2205
|
-
}
|
|
2206
|
-
if (rec.queue.length >= 24) rec.queue.splice(0, rec.queue.length - 24)
|
|
2207
|
-
rec.queue.push({ agentId: agentId ?? null, kind, text, ttlMs })
|
|
2208
|
-
this.dirty = true
|
|
520
|
+
enqueueBubble(channelId: string, agentId: string | undefined, kind: TownBubbleKind, text: string, ttlMs: number): void {
|
|
521
|
+
enqueueBubble(this, channelId, agentId, kind, text, ttlMs)
|
|
2209
522
|
}
|
|
2210
523
|
|
|
2211
524
|
/** 各频道接收器 FIFO 消费:当前条目展示期满 → 清理其气泡并取下一条实时渲染(渲染循环每帧调用)。
|
|
2212
525
|
* 队列积压时按 drainDisplayMs 压缩单条时长,让爆发期消息尽快追平。 */
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
if (rec.current) {
|
|
2216
|
-
if (now < rec.currentUntil) continue
|
|
2217
|
-
this.clearReceiverBubble(rec.channelId, rec.current)
|
|
2218
|
-
rec.current = null
|
|
2219
|
-
}
|
|
2220
|
-
const msg = rec.queue.shift()
|
|
2221
|
-
if (!msg) continue
|
|
2222
|
-
rec.current = msg
|
|
2223
|
-
rec.currentUntil = now + drainDisplayMs(msg.text, rec.queue.length)
|
|
2224
|
-
this.renderReceiverBubble(rec.channelId, msg)
|
|
2225
|
-
}
|
|
2226
|
-
}
|
|
2227
|
-
|
|
2228
|
-
/** 清除接收器条目对应的场景气泡(不同说话人衔接时避免叠泡) */
|
|
2229
|
-
private clearReceiverBubble(channelId: string, msg: BubbleMsg): void {
|
|
2230
|
-
if (!msg.agentId) return
|
|
2231
|
-
const asp = this.agents.get(msg.agentId)
|
|
2232
|
-
if (asp?.bubble) {
|
|
2233
|
-
this.scene.remove(asp.bubble)
|
|
2234
|
-
asp.bubble = null
|
|
2235
|
-
asp.bubbleText = null
|
|
2236
|
-
if (asp.bubbleTimer) {
|
|
2237
|
-
clearTimeout(asp.bubbleTimer)
|
|
2238
|
-
asp.bubbleTimer = null
|
|
2239
|
-
}
|
|
2240
|
-
}
|
|
2241
|
-
}
|
|
2242
|
-
|
|
2243
|
-
/** 将接收器消息渲染为 2.5D 聊天气泡(说话 Agent 头顶;无对应用户则挂频道名牌上方) */
|
|
2244
|
-
private renderReceiverBubble(channelId: string, msg: BubbleMsg): void {
|
|
2245
|
-
const asp = msg.agentId ? this.agents.get(msg.agentId) : undefined
|
|
2246
|
-
const b = this.blocks.get(channelId)
|
|
2247
|
-
// 频道未放置且无对应用户 → 暂不渲染(队列保留,放置/用户出现后补显)
|
|
2248
|
-
if (!asp && !b) return
|
|
2249
|
-
const name = asp?.name ?? b?.name ?? '系统'
|
|
2250
|
-
const accent = asp
|
|
2251
|
-
? asp.colorNum
|
|
2252
|
-
: (b?.color ?? 0x41c8f4)
|
|
2253
|
-
const text = (msg.kind === 'artifact' && !msg.text.startsWith('📦') ? `📦 ${msg.text}` : msg.text) || '…'
|
|
2254
|
-
const sprite = this.makeChatBubble(text, name, accent, msg.kind)
|
|
2255
|
-
const x = asp?.root.position.x ?? b!.x
|
|
2256
|
-
const z = asp?.root.position.z ?? b!.z
|
|
2257
|
-
// 放大气泡后锚点整体抬升(避免压到 48 处名牌)
|
|
2258
|
-
sprite.position.set(x, asp ? BUBBLE_Y + 22 + Math.max(0, asp.model.scale.y - 1) * 22 : 64, z)
|
|
2259
|
-
this.scene.add(sprite)
|
|
2260
|
-
if (!asp) {
|
|
2261
|
-
// 频道级(系统)气泡:展示期满自行移除
|
|
2262
|
-
window.setTimeout(() => {
|
|
2263
|
-
this.scene.remove(sprite)
|
|
2264
|
-
}, bubbleDisplayMs(msg.text) + 500)
|
|
2265
|
-
return
|
|
2266
|
-
}
|
|
2267
|
-
if (asp.bubble) {
|
|
2268
|
-
this.scene.remove(asp.bubble)
|
|
2269
|
-
this.disposeCanvasTextures(asp.bubble)
|
|
2270
|
-
asp.bubbleText = null
|
|
2271
|
-
}
|
|
2272
|
-
asp.bubble = sprite
|
|
2273
|
-
asp.bubbleText = text
|
|
2274
|
-
this.popInSprite(sprite)
|
|
2275
|
-
this.dirty = true
|
|
2276
|
-
}
|
|
2277
|
-
|
|
2278
|
-
/** 2.5D 聊天气泡:工业 HMI 风格 —— 头部身份条(等宽名牌 + 类型印章)+ 多行正文 + 尾角 */
|
|
2279
|
-
private makeChatBubble(text: string, name: string, accent: number, kind: TownBubbleKind): THREE.Sprite {
|
|
2280
|
-
const accentHex = `#${accent.toString(16).padStart(6, '0')}`
|
|
2281
|
-
const canvas = document.createElement('canvas')
|
|
2282
|
-
const ctx = canvas.getContext('2d')!
|
|
2283
|
-
// 超大字号会话气泡:正文 18px / 头名 13px / 最多 6 行 / 宽 ≤ 680px —— 实时信息一眼可读
|
|
2284
|
-
const bodyFont = '18px Geist, "PingFang SC", sans-serif'
|
|
2285
|
-
const headFont = '600 13px "Geist Mono", Geist, "PingFang SC", monospace'
|
|
2286
|
-
const tagFont = '600 10px "Geist Mono", Geist, monospace'
|
|
2287
|
-
const maxTextW = 470
|
|
2288
|
-
const padX = 18
|
|
2289
|
-
const padY = 12
|
|
2290
|
-
const nameH = 26
|
|
2291
|
-
const lineH = 24
|
|
2292
|
-
const tailH = 13
|
|
2293
|
-
ctx.font = bodyFont
|
|
2294
|
-
const lines = this.wrapBubbleLines(ctx, text.replace(/\s+/g, ' ').trim() || '…', maxTextW, 6)
|
|
2295
|
-
const textW = Math.max(...lines.map(l => ctx.measureText(l).width))
|
|
2296
|
-
const bw = Math.min(680, Math.max(140, Math.ceil(textW) + padX * 2))
|
|
2297
|
-
const bodyH = nameH + lines.length * lineH + padY * 2
|
|
2298
|
-
const bh = bodyH + tailH
|
|
2299
|
-
canvas.width = bw
|
|
2300
|
-
canvas.height = bh
|
|
2301
|
-
ctx.clearRect(0, 0, bw, bh)
|
|
2302
|
-
// 方角矩形 + 底部中央尾角(指向说话人;HMI 直角配方)
|
|
2303
|
-
const r = 4
|
|
2304
|
-
const cx = bw / 2
|
|
2305
|
-
const t = Math.min(24, bw * 0.32)
|
|
2306
|
-
const path = () => {
|
|
2307
|
-
ctx.beginPath()
|
|
2308
|
-
ctx.moveTo(r, 0)
|
|
2309
|
-
ctx.lineTo(bw - r, 0)
|
|
2310
|
-
ctx.quadraticCurveTo(bw, 0, bw, r)
|
|
2311
|
-
ctx.lineTo(bw, bodyH - r)
|
|
2312
|
-
ctx.quadraticCurveTo(bw, bodyH, bw - r, bodyH)
|
|
2313
|
-
ctx.lineTo(cx + t / 2, bodyH)
|
|
2314
|
-
ctx.lineTo(cx, bodyH + tailH)
|
|
2315
|
-
ctx.lineTo(cx - t / 2, bodyH)
|
|
2316
|
-
ctx.lineTo(r, bodyH)
|
|
2317
|
-
ctx.quadraticCurveTo(0, bodyH, 0, bodyH - r)
|
|
2318
|
-
ctx.lineTo(0, r)
|
|
2319
|
-
ctx.quadraticCurveTo(0, 0, r, 0)
|
|
2320
|
-
ctx.closePath()
|
|
2321
|
-
}
|
|
2322
|
-
// 柔和投影 → 深色面板底
|
|
2323
|
-
ctx.save()
|
|
2324
|
-
ctx.shadowColor = 'rgba(0,0,0,0.55)'
|
|
2325
|
-
ctx.shadowBlur = 14
|
|
2326
|
-
ctx.shadowOffsetY = 4
|
|
2327
|
-
path()
|
|
2328
|
-
ctx.fillStyle = 'rgba(14,18,27,0.94)'
|
|
2329
|
-
ctx.fill()
|
|
2330
|
-
ctx.restore()
|
|
2331
|
-
// 头部数据带(轻微抬升 + 发丝底边)
|
|
2332
|
-
ctx.save()
|
|
2333
|
-
path()
|
|
2334
|
-
ctx.clip()
|
|
2335
|
-
ctx.fillStyle = 'rgba(255,255,255,0.045)'
|
|
2336
|
-
ctx.fillRect(0, 0, bw, padY + nameH)
|
|
2337
|
-
ctx.restore()
|
|
2338
|
-
ctx.globalAlpha = 0.28
|
|
2339
|
-
ctx.fillStyle = '#fff'
|
|
2340
|
-
ctx.fillRect(padX, padY + 18, bw - padX * 2, 1)
|
|
2341
|
-
ctx.globalAlpha = 1
|
|
2342
|
-
// 发丝描边 + 左缘身份色数据条
|
|
2343
|
-
path()
|
|
2344
|
-
ctx.lineWidth = 1.2
|
|
2345
|
-
ctx.strokeStyle = 'rgba(140,170,195,0.4)'
|
|
2346
|
-
ctx.stroke()
|
|
2347
|
-
ctx.save()
|
|
2348
|
-
path()
|
|
2349
|
-
ctx.clip()
|
|
2350
|
-
ctx.fillStyle = accentHex
|
|
2351
|
-
ctx.fillRect(0, 0, 3, bodyH)
|
|
2352
|
-
ctx.restore()
|
|
2353
|
-
// 头牌:等宽名牌(身份色)+ 右侧类型印章(交付/异常)
|
|
2354
|
-
ctx.font = headFont
|
|
2355
|
-
ctx.fillStyle = accentHex
|
|
2356
|
-
ctx.textBaseline = 'middle'
|
|
2357
|
-
ctx.textAlign = 'left'
|
|
2358
|
-
ctx.fillText(name, padX, padY + 10)
|
|
2359
|
-
const tag = kind === 'artifact' ? 'ARTIFACT' : kind === 'error' ? 'FAULT' : ''
|
|
2360
|
-
if (tag) {
|
|
2361
|
-
const tw = ctx.measureText(tag).width + 12
|
|
2362
|
-
ctx.font = tagFont
|
|
2363
|
-
ctx.fillStyle = kind === 'error' ? '#ff8d80' : '#9fc3e8'
|
|
2364
|
-
ctx.strokeStyle = kind === 'error' ? 'rgba(255,141,128,0.55)' : 'rgba(159,195,232,0.45)'
|
|
2365
|
-
ctx.lineWidth = 1
|
|
2366
|
-
const tx = bw - padX - tw
|
|
2367
|
-
const ty = padY + 2
|
|
2368
|
-
const th = 16
|
|
2369
|
-
ctx.beginPath()
|
|
2370
|
-
ctx.rect(tx, ty, tw, th)
|
|
2371
|
-
ctx.fill()
|
|
2372
|
-
ctx.stroke()
|
|
2373
|
-
ctx.textAlign = 'center'
|
|
2374
|
-
ctx.fillText(tag, tx + tw / 2, ty + th / 2 + 0.5)
|
|
2375
|
-
}
|
|
2376
|
-
// 正文(错误微红)
|
|
2377
|
-
ctx.font = bodyFont
|
|
2378
|
-
ctx.fillStyle = kind === 'error' ? '#ff9d9d' : '#eef2fb'
|
|
2379
|
-
ctx.textAlign = 'left'
|
|
2380
|
-
lines.forEach((l, i) => ctx.fillText(l, padX, padY + nameH + 10 + i * lineH))
|
|
2381
|
-
// Sprite(纹理按 1/2.4 缩放到世界单位 —— 放大气泡后保持更大可见尺度)
|
|
2382
|
-
const tex = new THREE.CanvasTexture(canvas)
|
|
2383
|
-
tex.colorSpace = THREE.SRGBColorSpace
|
|
2384
|
-
const sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map: tex, transparent: true, depthTest: false, depthWrite: false }))
|
|
2385
|
-
const k = 2.4
|
|
2386
|
-
sprite.scale.set(bw / k, bh / k, 1)
|
|
2387
|
-
return sprite
|
|
2388
|
-
}
|
|
2389
|
-
|
|
2390
|
-
/** 气泡弹入动画:0.62 → 1.0 缓出(180ms,2.5D 手感) */
|
|
2391
|
-
private popInSprite(sprite: THREE.Sprite): void {
|
|
2392
|
-
const tx = sprite.scale.x
|
|
2393
|
-
const ty = sprite.scale.y
|
|
2394
|
-
sprite.scale.set(tx * 0.62, ty * 0.62, 1)
|
|
2395
|
-
const start = performance.now()
|
|
2396
|
-
const dur = 180
|
|
2397
|
-
const step = () => {
|
|
2398
|
-
if (this.disposed) return
|
|
2399
|
-
const t = Math.min(1, (performance.now() - start) / dur)
|
|
2400
|
-
const s = 0.62 + 0.38 * (1 - Math.pow(1 - t, 3))
|
|
2401
|
-
sprite.scale.set(tx * s, ty * s, 1)
|
|
2402
|
-
if (t < 1) this.rafAnims.push(step)
|
|
2403
|
-
}
|
|
2404
|
-
this.rafAnims.push(step)
|
|
2405
|
-
}
|
|
2406
|
-
|
|
2407
|
-
/** 按宽度逐字符换行(兼容中英混排);超 maxLines 行末加省略号 */
|
|
2408
|
-
private wrapBubbleLines(ctx: CanvasRenderingContext2D, text: string, maxW: number, maxLines: number): string[] {
|
|
2409
|
-
const lines: string[] = []
|
|
2410
|
-
let line = ''
|
|
2411
|
-
for (const ch of text) {
|
|
2412
|
-
if (ctx.measureText(line + ch).width > maxW && line) {
|
|
2413
|
-
lines.push(line)
|
|
2414
|
-
line = ch
|
|
2415
|
-
if (lines.length >= maxLines) {
|
|
2416
|
-
lines[maxLines - 1] = `${lines[maxLines - 1] ?? ''}…`
|
|
2417
|
-
return lines
|
|
2418
|
-
}
|
|
2419
|
-
}
|
|
2420
|
-
else {
|
|
2421
|
-
line += ch
|
|
2422
|
-
}
|
|
2423
|
-
}
|
|
2424
|
-
if (line) lines.push(line)
|
|
2425
|
-
if (lines.length === 0) lines.push('…')
|
|
2426
|
-
return lines
|
|
526
|
+
drainReceivers(now: number): void {
|
|
527
|
+
drainReceivers(this, now)
|
|
2427
528
|
}
|
|
2428
529
|
|
|
2429
530
|
// ================================================================
|
|
@@ -2435,491 +536,87 @@ export class TownScene3D {
|
|
|
2435
536
|
return this.renderer.domElement
|
|
2436
537
|
}
|
|
2437
538
|
|
|
539
|
+
// ==== 相机公开面(实现见 scene/town-scene3d-camera.ts;薄壳保持签名与语义不变) ====
|
|
540
|
+
|
|
2438
541
|
/** 世界坐标 → 屏幕像素(数据标注/悬浮 callout 投影;返回 null = 在相机背后) */
|
|
2439
|
-
worldToScreen(x: number, y: number, z: number): { x: number, y: number } | null {
|
|
2440
|
-
const rect = this.renderer.domElement.getBoundingClientRect()
|
|
2441
|
-
// 相机背后的点 project() 会按负 w 翻转到错误的屏幕位置 → 先用前向点积剔除
|
|
2442
|
-
const dir = TownScene3D._wDir
|
|
2443
|
-
this.camera.getWorldDirection(dir)
|
|
2444
|
-
const rel = TownScene3D._wRel.set(x - this.camera.position.x, y - this.camera.position.y, z - this.camera.position.z)
|
|
2445
|
-
if (rel.dot(dir) <= 0.1) return null
|
|
2446
|
-
const v = TownScene3D._wVec.set(x, y, z).project(this.camera)
|
|
2447
|
-
if (v.z > 1) return null
|
|
2448
|
-
// 视锥外过远(NDC ±1.6)的点投影无意义(可能落到数千 px 外)→ 剔除;边缘 ±1 内保留供夹取
|
|
2449
|
-
if (Math.abs(v.x) > 1.6 || Math.abs(v.y) > 1.6) return null
|
|
2450
|
-
return {
|
|
2451
|
-
x: rect.left + (v.x + 1) / 2 * rect.width,
|
|
2452
|
-
y: rect.top + (1 - v.y) / 2 * rect.height,
|
|
2453
|
-
}
|
|
2454
|
-
}
|
|
542
|
+
worldToScreen(x: number, y: number, z: number): { x: number, y: number } | null { return worldToScreen(this, x, y, z) }
|
|
2455
543
|
|
|
2456
544
|
/** 页面坐标 → 世界 xz(经 canvas rect + 相机射线打在 y=0 平面) */
|
|
2457
|
-
screenToWorld(clientX: number, clientY: number): { x: number, z: number } {
|
|
2458
|
-
const rect = this.renderer.domElement.getBoundingClientRect()
|
|
2459
|
-
const ndcX = ((clientX - rect.left) / rect.width) * 2 - 1
|
|
2460
|
-
const ndcY = -((clientY - rect.top) / rect.height) * 2 + 1
|
|
2461
|
-
const raycaster = TownScene3D._raycaster
|
|
2462
|
-
raycaster.setFromCamera(TownScene3D._wVec2.set(ndcX, ndcY), this.camera)
|
|
2463
|
-
const pt = TownScene3D._wVec
|
|
2464
|
-
raycaster.ray.intersectPlane(TownScene3D._groundPlane, pt)
|
|
2465
|
-
return pt ? { x: pt.x, z: pt.z } : { x: this.camera.position.x, z: this.camera.position.z }
|
|
2466
|
-
}
|
|
545
|
+
screenToWorld(clientX: number, clientY: number): { x: number, z: number } { return screenToWorld(this, clientX, clientY) }
|
|
2467
546
|
|
|
2468
547
|
/** 平移相机(拖拽):移动 camTarget(相机位置由 loop 据 target+dolly 推导) */
|
|
2469
|
-
panBy(dxWorld: number, dzWorld: number): void {
|
|
2470
|
-
this.camTarget.x -= dxWorld
|
|
2471
|
-
this.camTarget.z -= dzWorld
|
|
2472
|
-
}
|
|
2473
|
-
|
|
2474
|
-
/** 自动取景目标 dolly(缩放拖拽期间按范围尺寸计算;渲染循环平滑跟随,拖拽结束清除) */
|
|
2475
|
-
private autoDolly: number | null = null
|
|
2476
|
-
|
|
2477
|
-
/** 范围半轴 → 容纳它的 dolly(基线几何:水平半幅 ≈ 613*aspect*d,地面纵深 ≈ 950*d) */
|
|
2478
|
-
private fitDollyFor(rx: number, rz: number): number {
|
|
2479
|
-
const aspect = this.camera.aspect || 1.6
|
|
2480
|
-
return Math.max(rx / (0.82 * 613 * aspect), rz / (0.82 * 950))
|
|
2481
|
-
}
|
|
2482
|
-
|
|
2483
|
-
/** 缩放过程中调用:范围超出当前视野 → 相机自动拉远(只放大不缩小,收敛于拖拽结束) */
|
|
2484
|
-
private autoFrameTo(rx: number, rz: number): void {
|
|
2485
|
-
const fit = this.fitDollyFor(rx, rz)
|
|
2486
|
-
if (fit > this.dolly) this.autoDolly = Math.min(40, fit * 1.18)
|
|
2487
|
-
}
|
|
2488
|
-
|
|
2489
|
-
/** 变换模式(Blender 规范):translate=移动 G / rotate=旋转 R / scale=缩放 S。
|
|
2490
|
-
* 轴约束:移动仅 XZ(模型贴地),旋转仅 Y(2.5D 朝向),缩放 XYZ。 */
|
|
2491
|
-
setTransformMode(mode: 'translate' | 'rotate' | 'scale'): void {
|
|
2492
|
-
if (!this.tControls) return
|
|
2493
|
-
this.tControls.setMode(mode)
|
|
2494
|
-
this.tControls.showX = mode !== 'rotate'
|
|
2495
|
-
this.tControls.showY = mode !== 'translate'
|
|
2496
|
-
this.tControls.showZ = mode !== 'rotate'
|
|
2497
|
-
}
|
|
548
|
+
panBy(dxWorld: number, dzWorld: number): void { panBy(this, dxWorld, dzWorld) }
|
|
2498
549
|
|
|
2499
|
-
/**
|
|
2500
|
-
|
|
2501
|
-
return !!this.tControls && (this.tControls.dragging || !!this.tControls.axis)
|
|
2502
|
-
}
|
|
550
|
+
/** 缩放过程中调用:范围超出当前视野 → 相机自动拉远(只放大不缩小,收敛于拖拽结束)。宿主契约面:editing 模块回调。 */
|
|
551
|
+
autoFrameTo(rx: number, rz: number): void { autoFrameTo(this, rx, rz) }
|
|
2503
552
|
|
|
2504
|
-
/**
|
|
2505
|
-
|
|
2506
|
-
* 轨道参数:yaw 方位 / pitch 仰角(rad) / radius 半径(再乘 dolly)。 */
|
|
2507
|
-
setViewPreset(p: 'std' | 'top' | 'front' | 'side'): void {
|
|
2508
|
-
const presets: Record<string, { yaw: number, pitch: number, radius: number }> = {
|
|
2509
|
-
std: { yaw: 0, pitch: 0.70, radius: 1178 },
|
|
2510
|
-
top: { yaw: 0, pitch: 1.51, radius: 2400 },
|
|
2511
|
-
front: { yaw: 0, pitch: 0.09, radius: 1510 },
|
|
2512
|
-
side: { yaw: Math.PI / 2, pitch: 0.33, radius: 1320 },
|
|
2513
|
-
}
|
|
2514
|
-
const t = presets[p] ?? presets.std!
|
|
2515
|
-
this.viewTarget = { ...t }
|
|
2516
|
-
// 预设切换走平滑趋近;环绕拖拽则是即时双写
|
|
2517
|
-
}
|
|
553
|
+
/** 视角预设(设计稿 angle-chip):std 标准 / top 俯视 / front 前视 / side 侧视。 */
|
|
554
|
+
setViewPreset(p: 'std' | 'top' | 'front' | 'side'): void { setViewPreset(this, p) }
|
|
2518
555
|
|
|
2519
|
-
/**
|
|
2520
|
-
|
|
2521
|
-
/** 环绕(设计稿 OrbitLite 1:1):theta -= dx·0.0052;仰角 += dy·0.0052
|
|
2522
|
-
* (设计稿 phi -= dy·0.0052,phi 为极角 → 仰角随 dy 增大:往下拖 = 相机升向俯视) */
|
|
2523
|
-
orbitBy(dxPx: number, dyPx: number): void {
|
|
2524
|
-
const yaw = this.viewCur.yaw - dxPx * 0.0052
|
|
2525
|
-
const pitch = Math.min(1.52, Math.max(0.06, this.viewCur.pitch + dyPx * 0.0052))
|
|
2526
|
-
this.viewCur = { yaw, pitch, radius: this.viewCur.radius }
|
|
2527
|
-
this.viewTarget = { ...this.viewCur }
|
|
2528
|
-
this.dirty = true
|
|
2529
|
-
}
|
|
556
|
+
/** 环绕(设计稿 OrbitLite 1:1):左键拖拽即时改 yaw/pitch,夹在仰角安全区间 */
|
|
557
|
+
orbitBy(dxPx: number, dyPx: number): void { orbitBy(this, dxPx, dyPx) }
|
|
2530
558
|
|
|
2531
559
|
/** 自动环绕(设计稿 tOrbit):渲染循环缓转 yaw;orbitBy 拖拽时暂停 */
|
|
2532
|
-
setAutoOrbit(on: boolean): void {
|
|
2533
|
-
this.autoOrbit = on
|
|
2534
|
-
}
|
|
560
|
+
setAutoOrbit(on: boolean): void { setAutoOrbit(this, on) }
|
|
2535
561
|
|
|
2536
|
-
getAutoOrbit(): boolean {
|
|
2537
|
-
return this.autoOrbit
|
|
2538
|
-
}
|
|
562
|
+
getAutoOrbit(): boolean { return getAutoOrbit(this) }
|
|
2539
563
|
|
|
2540
|
-
/**
|
|
2541
|
-
|
|
2542
|
-
/** 平移(设计稿 OrbitLite 1:1):target += (−right·dx + up·dy)·s。
|
|
2543
|
-
* right=(cosY,0,−sinY),up 地面分量 = −sinP·(sinY,cosY);panBy 内部取负 → 注视点
|
|
2544
|
-
* 拖右时沿 −right 移动 = 相机左扫 = 内容跟随光标(抓取语义,与设计稿逐项一致)。 */
|
|
2545
|
-
panByScreen(dxPx: number, dyPx: number): void {
|
|
2546
|
-
const r = this.viewCur.radius * this.dolly
|
|
2547
|
-
const k = r * 0.0011
|
|
2548
|
-
const sinY = Math.sin(this.viewCur.yaw)
|
|
2549
|
-
const cosY = Math.cos(this.viewCur.yaw)
|
|
2550
|
-
const sinP = Math.sin(this.viewCur.pitch)
|
|
2551
|
-
const dxw = (cosY * dxPx + sinP * sinY * dyPx) * k
|
|
2552
|
-
const dzw = (-sinY * dxPx + sinP * cosY * dyPx) * k
|
|
2553
|
-
this.panBy(dxw, dzw)
|
|
2554
|
-
}
|
|
564
|
+
/** 平移(设计稿 OrbitLite 1:1):target += (−right·dx + up·dy)·s */
|
|
565
|
+
panByScreen(dxPx: number, dyPx: number): void { panByScreen(this, dxPx, dyPx) }
|
|
2555
566
|
|
|
2556
567
|
/** 飞往预设视角(平滑;设计稿 flyTo) */
|
|
2557
|
-
flyToPreset(p: 'std' | 'top' | 'front' | 'side',
|
|
2558
|
-
this.setViewPreset(p)
|
|
2559
|
-
}
|
|
568
|
+
flyToPreset(p: 'std' | 'top' | 'front' | 'side', dur = 900): void { flyToPreset(this, p, dur) }
|
|
2560
569
|
|
|
2561
570
|
/** 相机即时状态(导航地图/callout 距离显隐;世界坐标) */
|
|
2562
|
-
getCameraPose(): { pos: { x: number, y: number, z: number }, target: { x: number, z: number }, yaw: number, dolly: number } {
|
|
2563
|
-
return {
|
|
2564
|
-
pos: { x: this.camera.position.x, y: this.camera.position.y, z: this.camera.position.z },
|
|
2565
|
-
target: { x: this.camTarget.x, z: this.camTarget.z },
|
|
2566
|
-
yaw: this.viewCur.yaw,
|
|
2567
|
-
dolly: this.dolly,
|
|
2568
|
-
}
|
|
2569
|
-
}
|
|
571
|
+
getCameraPose(): { pos: { x: number, y: number, z: number }, target: { x: number, z: number }, yaw: number, dolly: number } { return getCameraPose(this) }
|
|
2570
572
|
|
|
2571
573
|
/** 导航地图拖拽平移:世界位移直接作用于注视点与相机位置(设计稿 minimap drag) */
|
|
2572
|
-
panWorldBy(dxw: number, dzw: number): void {
|
|
2573
|
-
this.camTarget.x -= dxw
|
|
2574
|
-
this.camTarget.z -= dzw
|
|
2575
|
-
this.camera.position.x -= dxw
|
|
2576
|
-
this.camera.position.z -= dzw
|
|
2577
|
-
this.dirty = true
|
|
2578
|
-
}
|
|
2579
|
-
|
|
2580
|
-
/** 渲染曝光(场景控制坞"环境光照";0.2~2.2) */
|
|
2581
|
-
setExposure(v: number): void {
|
|
2582
|
-
this.renderer.toneMappingExposure = Math.min(2.2, Math.max(0.2, v))
|
|
2583
|
-
}
|
|
2584
|
-
|
|
2585
|
-
/** 领地染色浓度(平台透明度系数;0.05~1) */
|
|
2586
|
-
setTerritoryOpacity(v: number): void {
|
|
2587
|
-
const o = Math.min(1, Math.max(0.05, v))
|
|
2588
|
-
for (const b of this.blocks.values()) {
|
|
2589
|
-
;(b.platform.material as THREE.MeshStandardMaterial).opacity = 0.24 * o
|
|
2590
|
-
}
|
|
2591
|
-
this.dirty = true
|
|
2592
|
-
}
|
|
574
|
+
panWorldBy(dxw: number, dzw: number): void { panWorldBy(this, dxw, dzw) }
|
|
2593
575
|
|
|
2594
576
|
/** 重置视角(注视世界中心,标准轨道,基准 dolly) */
|
|
2595
|
-
resetView(): void {
|
|
2596
|
-
this.dolly = 1
|
|
2597
|
-
this.autoDolly = null
|
|
2598
|
-
this.tweenCamTo(WORLD_CX, WORLD_CZ)
|
|
2599
|
-
this.setViewPreset('std')
|
|
2600
|
-
}
|
|
577
|
+
resetView(): void { resetView(this) }
|
|
2601
578
|
|
|
2602
|
-
zoomBy(f: number): void {
|
|
2603
|
-
this.autoDolly = null
|
|
2604
|
-
this.dolly = Math.min(1000, Math.max(0.001, this.dolly + f))
|
|
2605
|
-
}
|
|
579
|
+
zoomBy(f: number): void { zoomBy(this, f) }
|
|
2606
580
|
|
|
2607
581
|
/** 缓动聚焦某世界点(移动 camTarget) */
|
|
2608
|
-
|
|
2609
|
-
focusTo(x: number, z: number): void {
|
|
2610
|
-
this.tweenCamTo(x, z)
|
|
2611
|
-
}
|
|
2612
|
-
|
|
2613
|
-
private tweenCamTo(x: number, z: number): void {
|
|
2614
|
-
const start = performance.now()
|
|
2615
|
-
const sx = this.camTarget.x
|
|
2616
|
-
const sz = this.camTarget.z
|
|
2617
|
-
const dur = 600
|
|
2618
|
-
const step = () => {
|
|
2619
|
-
const t = Math.min(1, (performance.now() - start) / dur)
|
|
2620
|
-
const e = 1 - Math.pow(1 - t, 3)
|
|
2621
|
-
this.camTarget.x = sx + (x - sx) * e
|
|
2622
|
-
this.camTarget.z = sz + (z - sz) * e
|
|
2623
|
-
if (t < 1) this.rafAnims.push(step)
|
|
2624
|
-
}
|
|
2625
|
-
this.rafAnims.push(step)
|
|
2626
|
-
}
|
|
582
|
+
focusTo(x: number, z: number): void { focusTo(this, x, z) }
|
|
2627
583
|
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
// ================================================================
|
|
584
|
+
/** 登记逐帧回调(渲染循环统一 flush;抽出模块经此驱动缓动/脉冲) */
|
|
585
|
+
pushAnim(f: () => void): void { this.rafAnims.push(f) }
|
|
2631
586
|
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
if (model?.kind === 'dev') {
|
|
2638
|
-
const twinId = this.spawnDeviceNode(x, z, texKey, model.file, model.name)
|
|
2639
|
-
return { mode: 'spawn', agentId: twinId, textureKey: texKey, x: Math.round(x), y: Math.round(z) }
|
|
2640
|
-
}
|
|
2641
|
-
if (near) {
|
|
2642
|
-
this.swapTexture(near, texKey)
|
|
2643
|
-
this.enqueueBubble(near.channelId, near.agentId, 'info', `换装 → ${model?.name ?? assetId}`, 2200)
|
|
2644
|
-
return { mode: 'rebind', agentId: near.agentId, textureKey: texKey, x: Math.round(near.root.position.x), y: Math.round(near.root.position.z) }
|
|
2645
|
-
}
|
|
2646
|
-
// 落点生成居民
|
|
2647
|
-
const info = model ?? { id: assetId, file: '', name: assetId }
|
|
2648
|
-
this.spawnResident(x, z, texKey, info.name)
|
|
2649
|
-
return { mode: 'spawn', textureKey: texKey, x: Math.round(x), y: Math.round(z) }
|
|
2650
|
-
}
|
|
2651
|
-
|
|
2652
|
-
private nearestAgent(x: number, z: number, maxDist: number): Agent3D | undefined {
|
|
2653
|
-
let best: Agent3D | undefined
|
|
2654
|
-
let bestD = maxDist
|
|
2655
|
-
for (const a of this.agents.values()) {
|
|
2656
|
-
const d = Math.hypot(a.root.position.x - x, a.root.position.z - z)
|
|
2657
|
-
if (d < bestD) {
|
|
2658
|
-
best = a
|
|
2659
|
-
bestD = d
|
|
2660
|
-
}
|
|
2661
|
-
}
|
|
2662
|
-
return best
|
|
2663
|
-
}
|
|
587
|
+
/** 自动取景目标 dolly(缩放拖拽期间按范围尺寸计算;渲染循环平滑跟随,拖拽结束清除) */
|
|
588
|
+
autoDolly: number | null = null
|
|
589
|
+
/** 变换模式(Blender 规范):translate=移动 G / rotate=旋转 R / scale=缩放 S。
|
|
590
|
+
* 轴约束:移动仅 XZ(模型贴地),旋转仅 Y(2.5D 朝向),缩放 XYZ。 */
|
|
591
|
+
setTransformMode(mode: 'translate' | 'rotate' | 'scale'): void { setTransformMode(this, mode) }
|
|
2664
592
|
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
const file = info?.file ?? this.resolveFile(texKey) ?? '/assets/game/character/hero-3d.glb'
|
|
2668
|
-
asp.modelRef = texKey
|
|
2669
|
-
asp.textureKey = texKey
|
|
2670
|
-
void this.mountModel(asp, file, info?.name ?? texKey)
|
|
2671
|
-
}
|
|
593
|
+
/** 手柄是否悬停/拖拽中(调用方应让出相机平移与点选) */
|
|
594
|
+
isGizmoBusy(): boolean { return isGizmoBusy(this) }
|
|
2672
595
|
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
root.position.set(x, GROUND_Y, z)
|
|
2676
|
-
const model = new THREE.Group()
|
|
2677
|
-
root.add(model)
|
|
2678
|
-
const nameSprite = this.makeLabel(name, x, 48, z, this.hexOf(0xffe9c4))
|
|
2679
|
-
this.scene.add(root)
|
|
2680
|
-
const resident = new Agent3D({
|
|
2681
|
-
channelId: '', agentId: `resident-${Date.now().toString(36)}`, name,
|
|
2682
|
-
role: 'worker', root, model, mixer: null, clips: [], colorNum: 0xffe9c4, nameSprite,
|
|
2683
|
-
bubble: null, bubbleText: null, bubbleTimer: null, state: 'idle', progress: null,
|
|
2684
|
-
dragging: false, homeX: x, homeZ: z, range: null, rangeLine: null, textureKey: texKey, modelRef: texKey,
|
|
2685
|
-
behavior: { mode: 'idle', roamTarget: null, targetId: null, waitUntil: 0, pauseUntil: 0, engaged: false },
|
|
2686
|
-
})
|
|
2687
|
-
resident.host = this
|
|
2688
|
-
resident.attachAura(0xffe9c4, 'worker')
|
|
2689
|
-
this.agents.set(resident.agentId, resident)
|
|
2690
|
-
const info = this.modelsById.get(texKey)
|
|
2691
|
-
void this.mountModel(resident, info?.file ?? '/assets/game/character/hero-3d.glb', info?.name ?? name)
|
|
2692
|
-
}
|
|
596
|
+
/** 渲染曝光(场景控制坞"环境光照";0.2~2.2) */
|
|
597
|
+
setExposure(v: number): void { setExposure(this, v) }
|
|
2693
598
|
|
|
2694
|
-
/**
|
|
2695
|
-
|
|
2696
|
-
private makeDeviceRing(): { ring: THREE.Mesh, arc: THREE.Mesh } {
|
|
2697
|
-
const ring = new THREE.Mesh(
|
|
2698
|
-
new THREE.RingGeometry(20, 26, 32),
|
|
2699
|
-
new THREE.MeshBasicMaterial({ color: 0xf6c453, transparent: true, opacity: 0.8, side: THREE.DoubleSide }),
|
|
2700
|
-
)
|
|
2701
|
-
ring.rotation.x = -Math.PI / 2
|
|
2702
|
-
ring.position.y = 0.3
|
|
2703
|
-
const arc = new THREE.Mesh(
|
|
2704
|
-
new THREE.RingGeometry(20, 26, 32, 1, 0, Math.PI * 1.5),
|
|
2705
|
-
new THREE.MeshBasicMaterial({ color: 0x35e0a0, transparent: true, opacity: 0.55, side: THREE.DoubleSide, depthWrite: false, blending: THREE.AdditiveBlending }),
|
|
2706
|
-
)
|
|
2707
|
-
arc.rotation.x = -Math.PI / 2
|
|
2708
|
-
arc.position.y = 0.34
|
|
2709
|
-
arc.visible = false
|
|
2710
|
-
return { ring, arc }
|
|
2711
|
-
}
|
|
599
|
+
/** 领地染色浓度(平台透明度系数;0.05~1) */
|
|
600
|
+
setTerritoryOpacity(v: number): void { setTerritoryOpacity(this, v) }
|
|
2712
601
|
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
* - 数据:经 devices.create 落一个 device twin,与 modelRef 绑定;
|
|
2717
|
-
* - state 驱动:alarm→红环,running→青环,offline→灰环,idle→亮环。
|
|
2718
|
-
*/
|
|
2719
|
-
private spawnDeviceNode(x: number, z: number, texKey: string, file: string, name: string): string {
|
|
2720
|
-
const tempId = `dev-${Date.now().toString(36)}`
|
|
2721
|
-
const root = new THREE.Group()
|
|
2722
|
-
root.position.set(x, GROUND_Y, z)
|
|
2723
|
-
const { ring, arc } = this.makeDeviceRing()
|
|
2724
|
-
root.add(ring, arc)
|
|
2725
|
-
const holder = new THREE.Group()
|
|
2726
|
-
root.add(holder)
|
|
2727
|
-
holder.userData.modelFile = file
|
|
2728
|
-
const label = this.makeLabel(`⚙ ${name}`, x, 60, z)
|
|
2729
|
-
this.scene.add(root)
|
|
2730
|
-
const twin = new DeviceNode({ twinId: tempId, name, modelRef: texKey, root, holder, ring, label, state: 'idle', telemetry: {} })
|
|
2731
|
-
twin.host = this
|
|
2732
|
-
twin.arc = arc
|
|
2733
|
-
twin.updateRing()
|
|
2734
|
-
this.deviceNodes.set(tempId, twin)
|
|
2735
|
-
this.registerScalable('device', tempId, holder)
|
|
2736
|
-
void this.loadGltfToGroup(file, holder, UNITS * 1.6)
|
|
2737
|
-
const st = this.scalables.get(`device:${tempId}`)
|
|
2738
|
-
const createPromise = this.devices?.create({
|
|
2739
|
-
name, modelRef: texKey, kind: 'device', controls: ['power_on', 'power_off', 'set_speed'],
|
|
2740
|
-
posX: Math.round(x * 10) / 10,
|
|
2741
|
-
posZ: Math.round(z * 10) / 10,
|
|
2742
|
-
scale: st ? Math.round(st.userScale * 100) / 100 : 1,
|
|
2743
|
-
})
|
|
2744
|
-
if (!createPromise) return tempId
|
|
2745
|
-
this.pendingDeviceCreates.set(tempId, { name, modelRef: texKey, posX: x, posZ: z })
|
|
2746
|
-
void createPromise
|
|
2747
|
-
.then((created) => {
|
|
2748
|
-
const cancelled = this.cancelledDeviceCreates.delete(tempId)
|
|
2749
|
-
this.pendingDeviceCreates.delete(tempId)
|
|
2750
|
-
if (cancelled) {
|
|
2751
|
-
void this.devices?.remove?.(created.id).catch(() => {})
|
|
2752
|
-
return
|
|
2753
|
-
}
|
|
2754
|
-
if (this.disposed) return
|
|
2755
|
-
this.adoptDeviceNode(tempId, created.id)
|
|
2756
|
-
})
|
|
2757
|
-
.catch(() => {
|
|
2758
|
-
this.cancelledDeviceCreates.delete(tempId)
|
|
2759
|
-
this.pendingDeviceCreates.delete(tempId)
|
|
2760
|
-
})
|
|
2761
|
-
return tempId
|
|
2762
|
-
}
|
|
602
|
+
// ================================================================
|
|
603
|
+
// 拖拽换装 / 生成居民(drop 入口;实现见 scene/town-scene3d-agents.ts)
|
|
604
|
+
// ================================================================
|
|
2763
605
|
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
this.pendingDeviceCreates.delete(tempId)
|
|
2767
|
-
const node = this.deviceNodes.get(tempId)
|
|
2768
|
-
if (!node) return
|
|
2769
|
-
const duplicate = this.deviceNodes.get(realId)
|
|
2770
|
-
if (duplicate && duplicate !== node) {
|
|
2771
|
-
this.removeDeviceNode(tempId)
|
|
2772
|
-
if (this.selected?.kind === 'device' && this.selected.id === tempId) this.setSelected({ kind: 'device', id: realId })
|
|
2773
|
-
return
|
|
2774
|
-
}
|
|
2775
|
-
this.deviceNodes.delete(tempId)
|
|
2776
|
-
node.twinId = realId
|
|
2777
|
-
this.deviceNodes.set(realId, node)
|
|
2778
|
-
const scalable = this.scalables.get(`device:${tempId}`)
|
|
2779
|
-
this.scalables.delete(`device:${tempId}`)
|
|
2780
|
-
if (scalable) {
|
|
2781
|
-
scalable.id = realId
|
|
2782
|
-
this.scalables.set(`device:${realId}`, scalable)
|
|
2783
|
-
}
|
|
2784
|
-
const timer = this.pendingSaveTimers.get(tempId)
|
|
2785
|
-
this.pendingSaveTimers.delete(tempId)
|
|
2786
|
-
if (timer) this.pendingSaveTimers.set(realId, timer)
|
|
2787
|
-
if (this.pointerDrag?.kind === 'device' && this.pointerDrag.id === tempId) this.pointerDrag.id = realId
|
|
2788
|
-
if (this.selected?.kind === 'device' && this.selected.id === tempId) this.setSelected({ kind: 'device', id: realId })
|
|
2789
|
-
this.persistDeviceTransform(realId)
|
|
606
|
+
dropModelOnWorld(x: number, z: number, assetId: string): { mode: 'rebind' | 'spawn', agentId?: string, textureKey: string, x: number, y: number } {
|
|
607
|
+
return dropModelOnWorld(this, x, z, assetId)
|
|
2790
608
|
}
|
|
2791
609
|
|
|
2792
|
-
/**
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
const gltf = await this.loadGltf(file)
|
|
2796
|
-
const box = new THREE.Box3().setFromObject(gltf.scene)
|
|
2797
|
-
const h = Math.max(0.5, box.max.y - box.min.y)
|
|
2798
|
-
const scale = targetH / h
|
|
2799
|
-
gltf.scene.scale.setScalar(scale)
|
|
2800
|
-
gltf.scene.position.y = 0
|
|
2801
|
-
// 真实投影 + PBR 材质增强(设备:金属机身材质反射拉满,像真实工业设备)
|
|
2802
|
-
gltf.scene.traverse((o) => {
|
|
2803
|
-
if ((o as THREE.Mesh).isMesh) o.castShadow = true
|
|
2804
|
-
})
|
|
2805
|
-
this.enhancePbrMaterials(gltf.scene, 'device')
|
|
2806
|
-
// 静态子树矩阵冻结:设备模型加载后局部变换恒定,子孙 matrixAutoUpdate=false +
|
|
2807
|
-
// 一次性 updateMatrix,免每帧全树 updateMatrix 合成(拖拽/缩放只改祖先链,
|
|
2808
|
-
// world 矩阵经 force 传播仍正确;设备无动画,mixer 仅角色路径使用)
|
|
2809
|
-
gltf.scene.traverse((o) => {
|
|
2810
|
-
if (o === gltf.scene) return
|
|
2811
|
-
o.matrixAutoUpdate = false
|
|
2812
|
-
o.updateMatrix()
|
|
2813
|
-
})
|
|
2814
|
-
group.add(gltf.scene)
|
|
2815
|
-
}
|
|
2816
|
-
catch {
|
|
2817
|
-
// 加载失败:空 Group,静默(节点仍存在,只是无网格)
|
|
2818
|
-
}
|
|
610
|
+
/** 数字孪生设备节点:拖 dev 模型进场景生成(实现见 scene/town-scene3d-devices.ts) */
|
|
611
|
+
spawnDeviceNode(x: number, z: number, texKey: string, file: string, name: string): string {
|
|
612
|
+
return spawnDeviceNode(this, x, z, texKey, file, name)
|
|
2819
613
|
}
|
|
2820
614
|
|
|
2821
|
-
/**
|
|
2822
|
-
|
|
2823
|
-
* 根因修复:角色与产线设备 GLB 大量使用 KHR_materials_unlit(three 加载为
|
|
2824
|
-
* MeshBasicMaterial),不受灯光/阴影约束,夜景里像自发光贴片 —— 这既是
|
|
2825
|
-
* 「角色过亮」也是设备「假金属光泽」的来源。统一就地转标准材质接入光照
|
|
2826
|
-
* 体系,再分级调参:
|
|
2827
|
-
* - 角色:哑光 + 极轻环境反射(无清漆/自发光,保持原作贴图观感);
|
|
2828
|
-
* - 设备:LED 指示灯保持无光照语义并提 HDR(bloom 起晕,幂等防重);
|
|
2829
|
-
* 透光件微自发光 / 金属件适度镜面(0.85,克制)/ 烤漆件轻清漆。
|
|
2830
|
-
*/
|
|
2831
|
-
private enhancePbrMaterials(root: THREE.Object3D, kind: 'device' | 'character'): void {
|
|
2832
|
-
root.traverse((o) => {
|
|
2833
|
-
const mesh = o as THREE.Mesh
|
|
2834
|
-
if (!mesh.isMesh) return
|
|
2835
|
-
mesh.receiveShadow = true
|
|
2836
|
-
const tune = (raw: THREE.Material): THREE.Material => {
|
|
2837
|
-
const b = raw as THREE.MeshBasicMaterial & { isMeshBasicMaterial?: boolean }
|
|
2838
|
-
let m: THREE.MeshStandardMaterial
|
|
2839
|
-
if (b?.isMeshBasicMaterial) {
|
|
2840
|
-
if (kind === 'device' && /led|lamp|light/i.test(b.name ?? '')) {
|
|
2841
|
-
// 设备指示灯:保持无光照语义;提为 HDR(bloom 起晕)。clone 共享同一材质,
|
|
2842
|
-
// userData 幂等护桥避免多实例挂载时倍率叠乘。
|
|
2843
|
-
const flag = (b.userData ?? (b.userData = {})) as { hdrBoost?: boolean }
|
|
2844
|
-
if (!flag.hdrBoost) {
|
|
2845
|
-
b.color.multiplyScalar(4.5)
|
|
2846
|
-
flag.hdrBoost = true
|
|
2847
|
-
}
|
|
2848
|
-
return b
|
|
2849
|
-
}
|
|
2850
|
-
m = new THREE.MeshStandardMaterial({
|
|
2851
|
-
map: b.map ?? null,
|
|
2852
|
-
// unlit 贴图按"全亮"绘制,直接接入 ~8 倍总光的灯光系会过曝成白炽;
|
|
2853
|
-
// 反照率按类型压暗:角色 0.4 / 设备 0.55(ACES 肩部回落到合理亮度)
|
|
2854
|
-
color: b.color.clone().multiplyScalar(kind === 'character' ? 0.38 : 0.5),
|
|
2855
|
-
transparent: b.transparent,
|
|
2856
|
-
opacity: b.opacity,
|
|
2857
|
-
alphaTest: b.alphaTest,
|
|
2858
|
-
side: b.side,
|
|
2859
|
-
roughness: kind === 'character' ? 0.92 : 0.6,
|
|
2860
|
-
metalness: kind === 'character' ? 0 : 0.25,
|
|
2861
|
-
})
|
|
2862
|
-
m.name = b.name ?? ''
|
|
2863
|
-
b.dispose()
|
|
2864
|
-
}
|
|
2865
|
-
else {
|
|
2866
|
-
const s = raw as THREE.MeshStandardMaterial
|
|
2867
|
-
if (!s || !('envMapIntensity' in s)) return raw
|
|
2868
|
-
m = s
|
|
2869
|
-
}
|
|
2870
|
-
if (kind === 'character') {
|
|
2871
|
-
// 角色:哑光 + 极轻环境反射(曾因 emissive 底光 + 清漆层整体过亮)
|
|
2872
|
-
m.envMapIntensity = 0.45
|
|
2873
|
-
if (m.metalness > 0.3) m.metalness = 0
|
|
2874
|
-
return m
|
|
2875
|
-
}
|
|
2876
|
-
// 设备分级
|
|
2877
|
-
if (m.transparent) {
|
|
2878
|
-
// 灯罩/屏幕/指示窗:轻微自发光,像通电的设备部件
|
|
2879
|
-
m.envMapIntensity = 0.42
|
|
2880
|
-
if (m.emissive) m.emissive.setScalar(Math.max(m.emissive.r, 0.06))
|
|
2881
|
-
return m
|
|
2882
|
-
}
|
|
2883
|
-
if (m.metalness >= 0.5) {
|
|
2884
|
-
m.envMapIntensity = 0.85 // 金属机身:适可而止的镜面(1.35 曾过亮)
|
|
2885
|
-
return m
|
|
2886
|
-
}
|
|
2887
|
-
// 涂装/塑料外壳 → 物理材质轻清漆层(已是物理材质只调参,避免 clone 共享材质重复替换)
|
|
2888
|
-
if ((m as unknown as { isMeshPhysicalMaterial?: boolean }).isMeshPhysicalMaterial) {
|
|
2889
|
-
const p = m as THREE.MeshPhysicalMaterial
|
|
2890
|
-
p.clearcoat = 0.35
|
|
2891
|
-
p.clearcoatRoughness = 0.4
|
|
2892
|
-
p.envMapIntensity = 0.7
|
|
2893
|
-
p.needsUpdate = true
|
|
2894
|
-
return p
|
|
2895
|
-
}
|
|
2896
|
-
const p = new THREE.MeshPhysicalMaterial({
|
|
2897
|
-
color: m.color,
|
|
2898
|
-
map: m.map,
|
|
2899
|
-
normalMap: m.normalMap,
|
|
2900
|
-
roughnessMap: m.roughnessMap,
|
|
2901
|
-
aoMap: m.aoMap,
|
|
2902
|
-
emissive: m.emissive,
|
|
2903
|
-
emissiveMap: m.emissiveMap,
|
|
2904
|
-
emissiveIntensity: m.emissiveIntensity,
|
|
2905
|
-
roughness: Math.min(m.roughness, 0.55),
|
|
2906
|
-
metalness: m.metalness,
|
|
2907
|
-
clearcoat: 0.35,
|
|
2908
|
-
clearcoatRoughness: 0.4,
|
|
2909
|
-
})
|
|
2910
|
-
p.name = m.name
|
|
2911
|
-
p.side = m.side
|
|
2912
|
-
m.dispose()
|
|
2913
|
-
return p
|
|
2914
|
-
}
|
|
2915
|
-
if (Array.isArray(mesh.material)) mesh.material = mesh.material.map(tune)
|
|
2916
|
-
else mesh.material = tune(mesh.material)
|
|
2917
|
-
})
|
|
2918
|
-
this.dirty = true
|
|
2919
|
-
}
|
|
615
|
+
/** 模型 PBR 材质增强(设备/角色分治;实现见 scene/town-scene3d-models.ts) */
|
|
616
|
+
enhancePbrMaterials(root: THREE.Object3D, kind: 'device' | 'character'): void { enhancePbrMaterials(this, root, kind) }
|
|
2920
617
|
|
|
2921
618
|
// ================================================================
|
|
2922
|
-
// 调试 / HUD 数据
|
|
619
|
+
// 调试 / HUD 数据(实现见 scene/town-scene3d-hud.ts)
|
|
2923
620
|
// ================================================================
|
|
2924
621
|
|
|
2925
622
|
getDebugState(): {
|
|
@@ -2928,39 +625,7 @@ export class TownScene3D {
|
|
|
2928
625
|
bubbles: Array<{ text: string, at: number }>
|
|
2929
626
|
activity: { channelId: string, agentName: string, text: string } | null
|
|
2930
627
|
player: { x: number, y: number }
|
|
2931
|
-
} {
|
|
2932
|
-
return {
|
|
2933
|
-
blocks: this.blocks.size,
|
|
2934
|
-
agents: [...this.agents.values()].map(a => ({
|
|
2935
|
-
agentId: a.agentId,
|
|
2936
|
-
name: a.name,
|
|
2937
|
-
role: a.role,
|
|
2938
|
-
channelId: a.channelId,
|
|
2939
|
-
state: a.state,
|
|
2940
|
-
progress: a.progress,
|
|
2941
|
-
x: Math.round(a.root.position.x),
|
|
2942
|
-
y: Math.round(a.root.position.z),
|
|
2943
|
-
visible: true,
|
|
2944
|
-
draggable: true,
|
|
2945
|
-
auraColor: a.colorNum,
|
|
2946
|
-
behavior: a.behavior.mode,
|
|
2947
|
-
targetId: a.behavior.targetId,
|
|
2948
|
-
homeX: Math.round(a.homeX),
|
|
2949
|
-
homeY: Math.round(a.homeZ),
|
|
2950
|
-
textureKey: a.textureKey,
|
|
2951
|
-
modelRef: a.modelRef,
|
|
2952
|
-
decorated: !a.channelId,
|
|
2953
|
-
range: a.range
|
|
2954
|
-
? { x: Math.round(a.range.x), z: Math.round(a.range.z), radiusX: Math.round(a.range.radiusX), radiusZ: Math.round(a.range.radiusZ), shape: a.range.shape }
|
|
2955
|
-
: null,
|
|
2956
|
-
bubbleText: a.bubbleText,
|
|
2957
|
-
anim: a.animState,
|
|
2958
|
-
})),
|
|
2959
|
-
bubbles: this.dbgBubbles.slice(-8),
|
|
2960
|
-
activity: this.lastActivity,
|
|
2961
|
-
player: { x: Math.round(this.camera.position.x), y: Math.round(this.camera.position.z) },
|
|
2962
|
-
}
|
|
2963
|
-
}
|
|
628
|
+
} { return getDebugState(this) }
|
|
2964
629
|
|
|
2965
630
|
getMinimapState(): {
|
|
2966
631
|
world: { w: number, h: number }
|
|
@@ -2968,711 +633,146 @@ export class TownScene3D {
|
|
|
2968
633
|
agents: Array<{ x: number, y: number, color: number, busy: boolean }>
|
|
2969
634
|
devices: Array<{ x: number, y: number, color: number, state: string }>
|
|
2970
635
|
player: { x: number, y: number }
|
|
2971
|
-
} {
|
|
2972
|
-
const stateColor = (state: 'idle' | 'running' | 'offline' | 'alarm'): number =>
|
|
2973
|
-
state === 'alarm' ? 0xff6b6b : state === 'offline' ? 0x8496a5 : state === 'running' ? 0x35e0a0 : 0xf6c453
|
|
2974
|
-
return {
|
|
2975
|
-
world: { w: WORLD_W, h: WORLD_H },
|
|
2976
|
-
// 领地含真实形状/半轴/朝向(归一化;镜头居中小地图按形状绘制)
|
|
2977
|
-
blocks: [...this.blocks.values()].map(b => ({
|
|
2978
|
-
x: b.x / WORLD_W,
|
|
2979
|
-
y: b.z / WORLD_H,
|
|
2980
|
-
color: b.color,
|
|
2981
|
-
name: b.name,
|
|
2982
|
-
shape: b.shape,
|
|
2983
|
-
rx: b.radiusX / WORLD_W,
|
|
2984
|
-
rz: b.radiusZ / WORLD_H,
|
|
2985
|
-
rot: b.rotationY,
|
|
2986
|
-
})),
|
|
2987
|
-
agents: [...this.agents.values()].map(a => ({ x: a.root.position.x / WORLD_W, y: a.root.position.z / WORLD_H, color: a.colorNum, busy: a.state === 'busy' })),
|
|
2988
|
-
devices: [...this.deviceNodes.values()].map(d => ({
|
|
2989
|
-
twinId: d.twinId,
|
|
2990
|
-
x: d.root.position.x / WORLD_W,
|
|
2991
|
-
y: d.root.position.z / WORLD_H,
|
|
2992
|
-
color: stateColor(d.state),
|
|
2993
|
-
state: d.state,
|
|
2994
|
-
daq: d.modelRef.startsWith('daq-') || d.modelRef.includes('daq'),
|
|
2995
|
-
bound: false,
|
|
2996
|
-
})),
|
|
2997
|
-
// 镜头 = camTarget(画面注视中心;小地图准星锚点)
|
|
2998
|
-
player: { x: this.camTarget.x / WORLD_W, y: this.camTarget.z / WORLD_H },
|
|
2999
|
-
}
|
|
3000
|
-
}
|
|
636
|
+
} { return getMinimapState(this) }
|
|
3001
637
|
|
|
3002
|
-
getRecentActivity(): Array<{ channelId: string, agentName: string, text: string }> {
|
|
3003
|
-
return [...this.recentActivity]
|
|
3004
|
-
}
|
|
638
|
+
getRecentActivity(): Array<{ channelId: string, agentName: string, text: string }> { return getRecentActivity(this) }
|
|
3005
639
|
|
|
3006
|
-
/** 更新设备节点状态/遥测(
|
|
640
|
+
/** 更新设备节点状态/遥测(数据驱动渲染状态环;实现见 scene/town-scene3d-devices.ts) */
|
|
3007
641
|
updateDeviceNode(twinId: string, state: DeviceNode['state'], telemetry?: Record<string, number | string | boolean>): void {
|
|
3008
|
-
|
|
3009
|
-
if (!dev) return
|
|
3010
|
-
if (state) {
|
|
3011
|
-
dev.state = state
|
|
3012
|
-
dev.updateRing()
|
|
3013
|
-
}
|
|
3014
|
-
if (telemetry) dev.telemetry = { ...dev.telemetry, ...telemetry }
|
|
3015
|
-
this.dirty = true
|
|
642
|
+
updateDeviceNode(this, twinId, state, telemetry)
|
|
3016
643
|
}
|
|
3017
644
|
|
|
3018
645
|
/** 设备改名(重建名牌 Sprite + 落库由 TownView 走 devices.update) */
|
|
3019
|
-
renameDevice(id: string, name: string): void {
|
|
3020
|
-
const dev = this.deviceNodes.get(id)
|
|
3021
|
-
if (!dev || !name.trim() || name.trim() === dev.name) return
|
|
3022
|
-
this.renameDeviceSprite(dev, name)
|
|
3023
|
-
}
|
|
646
|
+
renameDevice(id: string, name: string): void { renameDevice(this, id, name) }
|
|
3024
647
|
|
|
3025
648
|
/** 设备换模型(按 modelRef 重挂 GLB 到 holder;持久化由 TownView 走 devices.update) */
|
|
3026
|
-
swapDeviceModel(id: string, modelRef: string): void {
|
|
3027
|
-
const dev = this.deviceNodes.get(id)
|
|
3028
|
-
if (!dev) return
|
|
3029
|
-
this.swapDeviceModelSprite(dev, modelRef)
|
|
3030
|
-
}
|
|
649
|
+
swapDeviceModel(id: string, modelRef: string): void { swapDeviceModel(this, id, modelRef) }
|
|
3031
650
|
|
|
3032
651
|
/** 设备当前名称(供属性面板初始化) */
|
|
3033
|
-
getDeviceName(id: string): string {
|
|
3034
|
-
return this.deviceNodes.get(id)?.name ?? ''
|
|
3035
|
-
}
|
|
652
|
+
getDeviceName(id: string): string { return getDeviceName(this, id) }
|
|
3036
653
|
|
|
3037
654
|
/** 设备当前绑定模型 id(供模型下拉高亮) */
|
|
3038
|
-
getDeviceModelRef(id: string): string {
|
|
3039
|
-
return this.deviceNodes.get(id)?.modelRef ?? ''
|
|
3040
|
-
}
|
|
655
|
+
getDeviceModelRef(id: string): string { return getDeviceModelRef(this, id) }
|
|
3041
656
|
|
|
3042
657
|
/** 删除设备实例:落库删除 + 移除场景节点(由 TownView 触发;失败仅移除本地节点)。 */
|
|
3043
|
-
async removeDevice(id: string): Promise<void> {
|
|
3044
|
-
const dev = this.deviceNodes.get(id)
|
|
3045
|
-
if (!dev) return
|
|
3046
|
-
if (this.pendingDeviceCreates.has(id)) {
|
|
3047
|
-
this.cancelledDeviceCreates.add(id)
|
|
3048
|
-
this.removeDeviceNode(id)
|
|
3049
|
-
return
|
|
3050
|
-
}
|
|
3051
|
-
try {
|
|
3052
|
-
await this.devices?.remove?.(id)
|
|
3053
|
-
}
|
|
3054
|
-
catch { /* 服务端失败仍移除本地节点(尽力同步) */ }
|
|
3055
|
-
this.removeDeviceNode(id)
|
|
3056
|
-
}
|
|
658
|
+
async removeDevice(id: string): Promise<void> { await removeDevice(this, id) }
|
|
3057
659
|
|
|
3058
660
|
/** 设备节点列表(供 HUD/E2E;topY = 模型顶面世界高度,callout 锚定用) */
|
|
3059
661
|
getDeviceNodes(): Array<{ twinId: string, name: string, x: number, z: number, topY: number, state: string, telemetry: Record<string, number | string | boolean> }> {
|
|
3060
|
-
return
|
|
3061
|
-
twinId: d.twinId,
|
|
3062
|
-
name: d.name,
|
|
3063
|
-
x: Math.round(d.root.position.x),
|
|
3064
|
-
z: Math.round(d.root.position.z),
|
|
3065
|
-
topY: Math.round(this.deviceTopY(d)),
|
|
3066
|
-
state: d.state,
|
|
3067
|
-
telemetry: d.telemetry,
|
|
3068
|
-
}))
|
|
662
|
+
return getDeviceNodes(this)
|
|
3069
663
|
}
|
|
3070
664
|
|
|
3071
665
|
/** 动作绑定:为某 Agent 指定动画 clip(按名称 idle/walk/work 或索引)。供 AssetLibrary 选择动作后调用。 */
|
|
3072
|
-
setAnimPref(agentId: string, clipName: string): void {
|
|
3073
|
-
const asp = this.agents.get(agentId)
|
|
3074
|
-
if (!asp || asp.clips.length === 0 || !asp.mixer) return
|
|
3075
|
-
// 解析:优先名称匹配,否则 idle→0/walk→1/work→末帧
|
|
3076
|
-
let idx = asp.clips.findIndex(c => c.name.toLowerCase().includes(clipName.toLowerCase()))
|
|
3077
|
-
if (idx < 0) idx = clipName === 'work' ? asp.clips.length - 1 : clipName === 'walk' ? Math.min(1, asp.clips.length - 1) : 0
|
|
3078
|
-
const clip = asp.clips[idx]
|
|
3079
|
-
if (!clip) return
|
|
3080
|
-
asp.mixer.stopAllAction()
|
|
3081
|
-
asp.mixer.clipAction(clip).reset().play()
|
|
3082
|
-
}
|
|
666
|
+
setAnimPref(agentId: string, clipName: string): void { setAnimPref(this, agentId, clipName) }
|
|
3083
667
|
|
|
3084
668
|
/** 角色当前绑定模型 id(供模型选择器高亮) */
|
|
3085
|
-
getAgentModel(agentId: string): string | null {
|
|
3086
|
-
const asp = this.agents.get(agentId)
|
|
3087
|
-
return asp?.modelRef ?? null
|
|
3088
|
-
}
|
|
669
|
+
getAgentModel(agentId: string): string | null { return getAgentModel(this, agentId) }
|
|
3089
670
|
|
|
3090
671
|
/** 角色名字(供模型选择器标题) */
|
|
3091
|
-
getAgentName(agentId: string): string {
|
|
3092
|
-
return this.agents.get(agentId)?.name ?? agentId.slice(0, 8)
|
|
3093
|
-
}
|
|
672
|
+
getAgentName(agentId: string): string { return getAgentName(this, agentId) }
|
|
3094
673
|
|
|
3095
674
|
/** 角色动画 clip 名列表(供 UI 展示可绑定动作;无则空) */
|
|
3096
|
-
getAgentClips(agentId: string): Array<{ name: string, duration: number }> {
|
|
3097
|
-
const asp = this.agents.get(agentId)
|
|
3098
|
-
if (!asp) return []
|
|
3099
|
-
return asp.clips.map(c => ({ name: c.name, duration: Math.round(c.duration * 10) / 10 }))
|
|
3100
|
-
}
|
|
675
|
+
getAgentClips(agentId: string): Array<{ name: string, duration: number }> { return getAgentClips(this, agentId) }
|
|
3101
676
|
|
|
3102
677
|
// ================================================================
|
|
3103
678
|
// 选中 + 缩放(客制化:场景内点选 Agent/设备 → 弹滑杆调大小)
|
|
3104
679
|
// ================================================================
|
|
3105
680
|
|
|
3106
681
|
/** 以世界坐标 hit 一个 Agent 或设备节点(近似距离阈值;供点击选中) */
|
|
3107
|
-
|
|
3108
|
-
let best: { kind: 'agent' | 'device', id: string, d: number } | null = null
|
|
3109
|
-
for (const a of this.agents.values()) {
|
|
3110
|
-
const d = Math.hypot(a.root.position.x - x, a.root.position.z - z)
|
|
3111
|
-
if (d < 90 && (!best || d < best.d)) best = { kind: 'agent', id: a.agentId, d }
|
|
3112
|
-
}
|
|
3113
|
-
for (const dev of this.deviceNodes.values()) {
|
|
3114
|
-
const d = Math.hypot(dev.root.position.x - x, dev.root.position.z - z)
|
|
3115
|
-
if (d < 90 && (!best || d < best.d)) best = { kind: 'device', id: dev.twinId, d }
|
|
3116
|
-
}
|
|
3117
|
-
return best ? { kind: best.kind, id: best.id } : null
|
|
3118
|
-
}
|
|
682
|
+
pickAt(x: number, z: number): { kind: 'agent' | 'device', id: string } | null { return pickAt(this, x, z) }
|
|
3119
683
|
|
|
3120
684
|
/** 点选频道:返回包含该点的频道 id(边界内;供编辑模式打开边界编辑面板) */
|
|
3121
|
-
|
|
3122
|
-
for (const b of this.blocks.values()) {
|
|
3123
|
-
const layout: ChannelLayout = {
|
|
3124
|
-
channelId: b.channelId, x: b.x, z: b.z,
|
|
3125
|
-
radiusX: b.radiusX, radiusZ: b.radiusZ,
|
|
3126
|
-
shape: b.shape, rotationY: b.rotationY,
|
|
3127
|
-
}
|
|
3128
|
-
if (pointInBoundary(layout, x, z)) return b.channelId
|
|
3129
|
-
}
|
|
3130
|
-
return null
|
|
3131
|
-
}
|
|
685
|
+
pickChannel(x: number, z: number): string | null { return pickChannel(this, x, z) }
|
|
3132
686
|
|
|
3133
|
-
/** 命中频道边界线(世界点落在某领地边界带 ±42 单位内 → 边界拖拽等比缩放)
|
|
3134
|
-
|
|
3135
|
-
for (const b of this.blocks.values()) {
|
|
3136
|
-
const lxz = toLocal(b, x, z)
|
|
3137
|
-
const rd = Math.hypot(lxz.x / b.radiusX, lxz.z / b.radiusZ)
|
|
3138
|
-
const tol = 42 / Math.min(b.radiusX, b.radiusZ)
|
|
3139
|
-
if (Math.abs(rd - 1) <= tol) {
|
|
3140
|
-
return { cid: b.channelId, rx0: b.radiusX, rz0: b.radiusZ, rd0: rd }
|
|
3141
|
-
}
|
|
3142
|
-
}
|
|
3143
|
-
return null
|
|
3144
|
-
}
|
|
687
|
+
/** 命中频道边界线(世界点落在某领地边界带 ±42 单位内 → 边界拖拽等比缩放)。宿主契约面:editing 模块回调。 */
|
|
688
|
+
pickChannelEdge(x: number, z: number): { cid: string, rx0: number, rz0: number, rd0: number } | null { return pickChannelEdge(this, x, z) }
|
|
3145
689
|
|
|
3146
690
|
/** 设置选中(点击后由 Vue 弹缩放/旋转滑杆) */
|
|
3147
|
-
|
|
3148
|
-
// 选中切换/取消 → 退出框选绘制(清理预览)
|
|
3149
|
-
if (!sel || sel.kind !== 'agent' || sel.id !== this.rangeDrawAgent) this.cancelRangeDraw()
|
|
3150
|
-
this.selected = sel
|
|
3151
|
-
// 变换手柄跟随选中设备(Blender 规范:选中即出手柄;运行模式只读 → 不出手柄)
|
|
3152
|
-
if (this.tControls) {
|
|
3153
|
-
if (sel?.kind === 'device' && this.mode === 'edit' && this.deviceNodes.has(sel.id)) this.tControls.attach(this.deviceNodes.get(sel.id)!.root)
|
|
3154
|
-
else this.tControls.detach()
|
|
3155
|
-
}
|
|
3156
|
-
this.refreshAgentRangeLines()
|
|
3157
|
-
this.refreshAgentRangeHandles()
|
|
3158
|
-
if (!sel) {
|
|
3159
|
-
this.emit('select', null)
|
|
3160
|
-
return
|
|
3161
|
-
}
|
|
3162
|
-
const st = this.scalables.get(`${sel.kind}:${sel.id}`)
|
|
3163
|
-
if (st) {
|
|
3164
|
-
this.emit('select', {
|
|
3165
|
-
kind: sel.kind,
|
|
3166
|
-
id: sel.id,
|
|
3167
|
-
scale: st.userScale,
|
|
3168
|
-
rotation: Math.round(this.getModelRotation(sel.id, sel.kind) * 10) / 10,
|
|
3169
|
-
})
|
|
3170
|
-
}
|
|
3171
|
-
}
|
|
691
|
+
setSelected(sel: { kind: 'agent' | 'device', id: string } | null): void { setSelected(this, sel) }
|
|
3172
692
|
|
|
3173
693
|
/** 场景内缩放:为 Agent(id)或设备(id)设定用户缩放倍率(0.2~5;1=默认归一化) */
|
|
3174
|
-
setModelScale(id: string, scale: number, kind?: 'agent' | 'device'): void {
|
|
3175
|
-
const k = kind ?? (this.scalables.has(`device:${id}`) ? 'device' : 'agent')
|
|
3176
|
-
const key = `${k}:${id}`
|
|
3177
|
-
const st = this.scalables.get(key)
|
|
3178
|
-
if (!st) return
|
|
3179
|
-
st.userScale = scale
|
|
3180
|
-
// 施加于模型 holder(相对已归一化的默认尺寸)
|
|
3181
|
-
st.holder.scale.setScalar(st.userScale)
|
|
3182
|
-
if (k === 'device') {
|
|
3183
|
-
const dev = this.deviceNodes.get(id)
|
|
3184
|
-
if (dev) dev.topYCache = null
|
|
3185
|
-
}
|
|
3186
|
-
this.dirty = true
|
|
3187
|
-
}
|
|
694
|
+
setModelScale(id: string, scale: number, kind?: 'agent' | 'device'): void { setModelScale(this, id, scale, kind) }
|
|
3188
695
|
|
|
3189
696
|
/** 当前选中对象(供 Vue/滑杆初始化) */
|
|
3190
|
-
getSelectedScale(): { kind: 'agent' | 'device', id: string, scale: number, rotation: number } | null {
|
|
3191
|
-
if (!this.selected) return null
|
|
3192
|
-
const st = this.scalables.get(`${this.selected.kind}:${this.selected.id}`)
|
|
3193
|
-
if (!st) return null
|
|
3194
|
-
return {
|
|
3195
|
-
kind: this.selected.kind,
|
|
3196
|
-
id: this.selected.id,
|
|
3197
|
-
scale: st.userScale,
|
|
3198
|
-
rotation: Math.round(this.getModelRotation(this.selected.id, this.selected.kind) * 10) / 10,
|
|
3199
|
-
}
|
|
3200
|
-
}
|
|
697
|
+
getSelectedScale(): { kind: 'agent' | 'device', id: string, scale: number, rotation: number } | null { return getSelectedScale(this) }
|
|
3201
698
|
|
|
3202
699
|
/** 组装可缩放目标并恢复初始化缩放(initialScale 优先=服务端持久化;否则 localStorage) */
|
|
3203
|
-
|
|
3204
|
-
const saved = initialScale !== undefined
|
|
3205
|
-
? initialScale
|
|
3206
|
-
: (typeof localStorage !== 'undefined' ? Number(localStorage.getItem(`town.scale.${kind}:${id}`) ?? 1) : 1)
|
|
3207
|
-
const userScale = Number.isFinite(saved) && saved > 0 ? saved : 1
|
|
3208
|
-
this.scalables.set(`${kind}:${id}`, { kind, id, userScale, holder })
|
|
3209
|
-
holder.scale.setScalar(userScale)
|
|
3210
|
-
if (kind === 'device') {
|
|
3211
|
-
const dev = this.deviceNodes.get(id)
|
|
3212
|
-
if (dev) dev.topYCache = null
|
|
3213
|
-
}
|
|
3214
|
-
}
|
|
700
|
+
registerScalable(kind: 'agent' | 'device', id: string, holder: THREE.Group, initialScale?: number): void { registerScalable(this, kind, id, holder, initialScale) }
|
|
3215
701
|
|
|
3216
702
|
/** 持久化指定对象的缩放(滑杆松手时调用;设备同时落库,角色仅本地) */
|
|
3217
|
-
persistScale(kind: 'agent' | 'device', id: string): void {
|
|
3218
|
-
const st = this.scalables.get(`${kind}:${id}`)
|
|
3219
|
-
if (!st || typeof localStorage === 'undefined') return
|
|
3220
|
-
localStorage.setItem(`town.scale.${kind}:${id}`, String(st.userScale))
|
|
3221
|
-
if (kind === 'device') this.persistDeviceTransform(id)
|
|
3222
|
-
}
|
|
703
|
+
persistScale(kind: 'agent' | 'device', id: string): void { persistScale(this, kind, id) }
|
|
3223
704
|
|
|
3224
705
|
// ================================================================
|
|
3225
706
|
// 编辑/浏览模式 + 场景对象拖拽(设备↔地面,角色↔落点)
|
|
707
|
+
// 实现见 scene/town-scene3d-editing.ts
|
|
3226
708
|
// ================================================================
|
|
3227
709
|
|
|
3228
710
|
/** 模式切换:浏览(只读:相机+点选) / 编辑(可拖拽设备/调整角色落点/旋转/频道整体移动/边界手柄/Agent 活动范围) */
|
|
3229
|
-
setMode(mode: TownScene3DMode): void {
|
|
3230
|
-
if (this.mode === mode) return
|
|
3231
|
-
this.mode = mode
|
|
3232
|
-
if (mode === 'browse') {
|
|
3233
|
-
if (this.pointerDrag) this.endPointerDrag()
|
|
3234
|
-
this.cancelRangeDraw()
|
|
3235
|
-
this.setSelected(null)
|
|
3236
|
-
// 运行模式只读:收起变换手柄(拖拽已在 tryStartPointerDrag 门禁)
|
|
3237
|
-
this.tControls?.detach()
|
|
3238
|
-
}
|
|
3239
|
-
this.refreshChannelHandles()
|
|
3240
|
-
this.refreshAgentRangeHandles()
|
|
3241
|
-
// 编辑模式范围线框可见性(range 线框在编辑模式全部显示,浏览模式仅选中显示)
|
|
3242
|
-
for (const a of this.agents.values()) a.renderRangeLine()
|
|
3243
|
-
this.dirty = true
|
|
3244
|
-
}
|
|
711
|
+
setMode(mode: TownScene3DMode): void { setMode(this, mode) }
|
|
3245
712
|
|
|
3246
|
-
getMode(): TownScene3DMode {
|
|
3247
|
-
return this.mode
|
|
3248
|
-
}
|
|
713
|
+
getMode(): TownScene3DMode { return getMode(this) }
|
|
3249
714
|
|
|
3250
715
|
/** 网格吸附开关(编辑拖拽落点;默认开) */
|
|
3251
|
-
setSnap(enabled: boolean): void {
|
|
3252
|
-
this.snapEnabled = enabled
|
|
3253
|
-
}
|
|
716
|
+
setSnap(enabled: boolean): void { setSnap(this, enabled) }
|
|
3254
717
|
|
|
3255
|
-
getSnap(): boolean {
|
|
3256
|
-
return this.snapEnabled
|
|
3257
|
-
}
|
|
718
|
+
getSnap(): boolean { return getSnap(this) }
|
|
3258
719
|
|
|
3259
720
|
/**
|
|
3260
721
|
* 尝试开始场景拖拽(编辑模式;指针按下命中设备/角色 → 占用该手势)。
|
|
3261
722
|
* 返回 true 表示场景已接管指针(调用方应跳过相机平移)。
|
|
3262
723
|
*/
|
|
3263
|
-
tryStartPointerDrag(clientX: number, clientY: number): boolean {
|
|
3264
|
-
if (this.mode !== 'edit' || this.isGizmoBusy()) return false
|
|
3265
|
-
const w = this.screenToWorld(clientX, clientY)
|
|
3266
|
-
// 1) Agent 活动范围手柄(编辑模式选中带范围角色时 → 拖拽调整该 Agent 范围大小)
|
|
3267
|
-
const ar = this.pickAgentRangeHandle(w.x, w.z)
|
|
3268
|
-
if (ar) {
|
|
3269
|
-
this.pointerDrag = { kind: 'agentRangeResize', id: ar.agentId, handle: ar.handle }
|
|
3270
|
-
this.emitSaveState('dirty')
|
|
3271
|
-
return true
|
|
3272
|
-
}
|
|
3273
|
-
// 2) 边界缩放手柄(编辑模式选中频道的 4 个手柄 → 调整频道范围大小)
|
|
3274
|
-
const h = this.pickResizeHandle(w.x, w.z)
|
|
3275
|
-
if (h) {
|
|
3276
|
-
this.pointerDrag = { kind: 'resize', id: h.cid, handle: h.handle }
|
|
3277
|
-
this.setSelected(null)
|
|
3278
|
-
this.emitSaveState('dirty')
|
|
3279
|
-
return true
|
|
3280
|
-
}
|
|
3281
|
-
// 3) 框选绘制:rangeDraw 模式激活 → 以按下点为框角拉动(优先于点选,便于从角色身上起手)
|
|
3282
|
-
if (this.rangeDrawAgent) {
|
|
3283
|
-
this.pointerDrag = { kind: 'rangeDraw', id: this.rangeDrawAgent, x0: w.x, z0: w.z }
|
|
3284
|
-
this.rangeDrawPreviewState = null
|
|
3285
|
-
return true
|
|
3286
|
-
}
|
|
3287
|
-
// 4) 设备 / 角色
|
|
3288
|
-
const hit = this.pickAt(w.x, w.z)
|
|
3289
|
-
if (hit) {
|
|
3290
|
-
if (hit.kind === 'device') {
|
|
3291
|
-
if (!this.deviceNodes.has(hit.id)) return false
|
|
3292
|
-
this.pointerDrag = hit
|
|
3293
|
-
this.setSelected(hit)
|
|
3294
|
-
this.emitSaveState('dirty')
|
|
3295
|
-
return true
|
|
3296
|
-
}
|
|
3297
|
-
// 角色:仅频道角色可调整落点(装饰居民不持久化、不可布局)
|
|
3298
|
-
const asp = this.agents.get(hit.id)
|
|
3299
|
-
if (!asp || !asp.channelId) return false
|
|
3300
|
-
if (asp.bubbleTimer) {
|
|
3301
|
-
clearTimeout(asp.bubbleTimer)
|
|
3302
|
-
asp.bubbleTimer = null
|
|
3303
|
-
}
|
|
3304
|
-
asp.dragging = true
|
|
3305
|
-
asp.behavior.mode = 'idle'
|
|
3306
|
-
asp.behavior.targetId = null
|
|
3307
|
-
this.pointerDrag = hit
|
|
3308
|
-
this.setSelected(hit)
|
|
3309
|
-
this.emitSaveState('dirty')
|
|
3310
|
-
return true
|
|
3311
|
-
}
|
|
3312
|
-
// 5) 拖某成员活动范围边界线 → 整框平移(频道整体移动之前;频道移动先排除已按范围接管)
|
|
3313
|
-
const rangeHit = this.hitAgentRangeBoundary(w.x, w.z)
|
|
3314
|
-
if (rangeHit && rangeHit.range) {
|
|
3315
|
-
this.pointerDrag = { kind: 'agentRange', id: rangeHit.agentId, dx: rangeHit.range.x - w.x, dz: rangeHit.range.z - w.z }
|
|
3316
|
-
this.setSelected({ kind: 'agent', id: rangeHit.agentId })
|
|
3317
|
-
this.emitSaveState('dirty')
|
|
3318
|
-
return true
|
|
3319
|
-
}
|
|
3320
|
-
// 6) 频道边界线拖拽:抓住边界附近 → 整体等比缩放(手柄之外,自由拉边调节范围)
|
|
3321
|
-
const edge = this.pickChannelEdge(w.x, w.z)
|
|
3322
|
-
if (edge) {
|
|
3323
|
-
this.pointerDrag = { kind: 'channelEdge', id: edge.cid, rx0: edge.rx0, rz0: edge.rz0, rd0: edge.rd0 }
|
|
3324
|
-
this.setSelected(null)
|
|
3325
|
-
this.selectChannel(edge.cid)
|
|
3326
|
-
this.emitSaveState('dirty')
|
|
3327
|
-
return true
|
|
3328
|
-
}
|
|
3329
|
-
// 7) 频道信标:非拖拽目标(点击定位语义交给 pointerup;避免编辑模式下点信标变成整体拖动)
|
|
3330
|
-
if (this.pickBeacon(w.x, w.z)) return false
|
|
3331
|
-
// 8) 频道领地整体拖拽:点中领地空白处 → 平移整个频道(平台/边界/名牌/成员落点)
|
|
3332
|
-
const cid = this.pickChannel(w.x, w.z)
|
|
3333
|
-
if (cid) {
|
|
3334
|
-
const b = this.blocks.get(cid)
|
|
3335
|
-
if (!b) return false
|
|
3336
|
-
this.pointerDrag = { kind: 'channel', id: cid, dx: b.x - w.x, dz: b.z - w.z }
|
|
3337
|
-
this.setSelected(null)
|
|
3338
|
-
this.selectChannel(cid)
|
|
3339
|
-
this.emitSaveState('dirty')
|
|
3340
|
-
return true
|
|
3341
|
-
}
|
|
3342
|
-
return false
|
|
3343
|
-
}
|
|
724
|
+
tryStartPointerDrag(clientX: number, clientY: number): boolean { return tryStartPointerDrag(this, clientX, clientY) }
|
|
3344
725
|
|
|
3345
|
-
isPointerDragging(): boolean {
|
|
3346
|
-
return this.pointerDrag !== null
|
|
3347
|
-
}
|
|
726
|
+
isPointerDragging(): boolean { return isPointerDragging(this) }
|
|
3348
727
|
|
|
3349
|
-
|
|
3350
|
-
return this.snapEnabled ? Math.round(v / SNAP_SIZE) * SNAP_SIZE : Math.round(v * 10) / 10
|
|
3351
|
-
}
|
|
728
|
+
snapWorld(v: number): number { return snapWorld(this, v) }
|
|
3352
729
|
|
|
3353
730
|
/** 拖拽中:对象跟随指针指向的 xz 平面(仅改内存;落库在 endPointerDrag) */
|
|
3354
|
-
movePointerDrag(clientX: number, clientY: number): void {
|
|
3355
|
-
if (!this.pointerDrag) return
|
|
3356
|
-
const w = this.screenToWorld(clientX, clientY)
|
|
3357
|
-
// 无边界画布:仅网格吸附,不钳制世界范围(模型可摆放到任意位置)
|
|
3358
|
-
const x = this.snapWorld(w.x)
|
|
3359
|
-
const z = this.snapWorld(w.z)
|
|
3360
|
-
const pd = this.pointerDrag
|
|
3361
|
-
if (pd.kind === 'device') {
|
|
3362
|
-
const dev = this.deviceNodes.get(pd.id)
|
|
3363
|
-
if (dev) {
|
|
3364
|
-
dev.root.position.x = x
|
|
3365
|
-
dev.root.position.z = z
|
|
3366
|
-
this.dirty = true
|
|
3367
|
-
}
|
|
3368
|
-
}
|
|
3369
|
-
else if (pd.kind === 'agent') {
|
|
3370
|
-
const asp = this.agents.get(pd.id)
|
|
3371
|
-
if (asp) {
|
|
3372
|
-
// 频道角色落点钳制在所属领地边界内(不可拖出频道)
|
|
3373
|
-
let px = x
|
|
3374
|
-
let pz = z
|
|
3375
|
-
const layout = this.blockLayout(asp.channelId)
|
|
3376
|
-
if (layout) {
|
|
3377
|
-
const clamped = clampToBoundary(layout, x, z, 20)
|
|
3378
|
-
px = clamped.x
|
|
3379
|
-
pz = clamped.z
|
|
3380
|
-
}
|
|
3381
|
-
asp.root.position.x = px
|
|
3382
|
-
asp.root.position.z = pz
|
|
3383
|
-
this.dirty = true
|
|
3384
|
-
}
|
|
3385
|
-
}
|
|
3386
|
-
else if (pd.kind === 'rangeDraw') {
|
|
3387
|
-
// 框选绘制:当前点为对角终点(钳入频道边界),实时更新预览矩形
|
|
3388
|
-
const asp = this.agents.get(pd.id)
|
|
3389
|
-
if (!asp) return
|
|
3390
|
-
const layout = this.blockLayout(asp.channelId)
|
|
3391
|
-
let cx = w.x
|
|
3392
|
-
let cz = w.z
|
|
3393
|
-
if (layout) {
|
|
3394
|
-
const clamped = clampToBoundary(layout, cx, cz, 12)
|
|
3395
|
-
cx = clamped.x
|
|
3396
|
-
cz = clamped.z
|
|
3397
|
-
}
|
|
3398
|
-
this.rangeDrawPreviewState = { x0: pd.x0, z0: pd.z0, x1: cx, z1: cz }
|
|
3399
|
-
this.updateRangeDrawPreview(pd.x0, pd.z0, cx, cz)
|
|
3400
|
-
this.dirty = true
|
|
3401
|
-
}
|
|
3402
|
-
else if (pd.kind === 'agentRange') {
|
|
3403
|
-
// 拖 Agent 活动范围边界 → 整框平移(钳入频道边界;范围线框/手柄跟随)
|
|
3404
|
-
const asp = this.agents.get(pd.id)
|
|
3405
|
-
if (!asp || !asp.range) return
|
|
3406
|
-
const nx = x + pd.dx
|
|
3407
|
-
const nz = z + pd.dz
|
|
3408
|
-
asp.range = { ...asp.range, x: nx, z: nz }
|
|
3409
|
-
asp.renderRangeLine()
|
|
3410
|
-
this.refreshAgentRangeHandles()
|
|
3411
|
-
this.dirty = true
|
|
3412
|
-
}
|
|
3413
|
-
else if (pd.kind === 'agentRangeResize') {
|
|
3414
|
-
// 拖 Agent 范围手柄 → 实时调整 radiusX/radiusZ(矩形角点双轴 / 椭圆轴向轴点)
|
|
3415
|
-
const asp = this.agents.get(pd.id)
|
|
3416
|
-
if (!asp || !asp.range) return
|
|
3417
|
-
asp.range = this.applyAgentRangeResize(asp.range, pd.handle, w.x, w.z)
|
|
3418
|
-
asp.renderRangeLine()
|
|
3419
|
-
this.refreshAgentRangeHandles()
|
|
3420
|
-
this.autoFrameTo(asp.range.radiusX, asp.range.radiusZ)
|
|
3421
|
-
this.dirty = true
|
|
3422
|
-
}
|
|
3423
|
-
else if (pd.kind === 'channel') {
|
|
3424
|
-
const b = this.blocks.get(pd.id)
|
|
3425
|
-
if (b) this.applyBlockMove(b, x + pd.dx, z + pd.dz)
|
|
3426
|
-
}
|
|
3427
|
-
else if (pd.kind === 'resize') {
|
|
3428
|
-
const b = this.blocks.get(pd.id)
|
|
3429
|
-
if (b) this.applyResize(b, pd.handle, w.x, w.z)
|
|
3430
|
-
}
|
|
3431
|
-
else if (pd.kind === 'channelEdge') {
|
|
3432
|
-
// 边界线等比缩放:指针沿"中心→边界"射线的归一化距离比例 → 双轴同倍率
|
|
3433
|
-
const b = this.blocks.get(pd.id)
|
|
3434
|
-
if (b) {
|
|
3435
|
-
const lxz = toLocal(b, x, z)
|
|
3436
|
-
const rd = Math.hypot(lxz.x / pd.rx0, lxz.z / pd.rz0)
|
|
3437
|
-
const factor = pd.rd0 > 0.05 ? rd / pd.rd0 : 1
|
|
3438
|
-
b.radiusX = Math.max(60, Math.round(pd.rx0 * factor))
|
|
3439
|
-
b.radiusZ = Math.max(40, Math.round(pd.rz0 * factor))
|
|
3440
|
-
this.applyLayoutToBlock(b)
|
|
3441
|
-
this.autoFrameTo(b.radiusX, b.radiusZ)
|
|
3442
|
-
}
|
|
3443
|
-
}
|
|
3444
|
-
}
|
|
731
|
+
movePointerDrag(clientX: number, clientY: number): void { movePointerDrag(this, clientX, clientY) }
|
|
3445
732
|
|
|
3446
733
|
/** 拖拽结束:设备 → 防抖落库;角色 → home 更新 + 持久化;频道 → 布局落库 */
|
|
3447
|
-
endPointerDrag(): void {
|
|
3448
|
-
if (!this.pointerDrag) return
|
|
3449
|
-
const pd = this.pointerDrag
|
|
3450
|
-
this.pointerDrag = null
|
|
3451
|
-
if (pd.kind === 'device') {
|
|
3452
|
-
const dev = this.deviceNodes.get(pd.id)
|
|
3453
|
-
if (dev) this.persistDeviceTransform(pd.id)
|
|
3454
|
-
}
|
|
3455
|
-
else if (pd.kind === 'agent') {
|
|
3456
|
-
const asp = this.agents.get(pd.id)
|
|
3457
|
-
if (asp) {
|
|
3458
|
-
asp.homeX = asp.root.position.x
|
|
3459
|
-
asp.homeZ = asp.root.position.z
|
|
3460
|
-
asp.dragging = false
|
|
3461
|
-
this.emitSaveState('saving')
|
|
3462
|
-
void this.agentApi?.updateHome(pd.id, asp.channelId, asp.homeX, asp.homeZ)
|
|
3463
|
-
.then(() => this.emitSaveState('saved', Date.now()))
|
|
3464
|
-
.catch(() => this.emitSaveState('error'))
|
|
3465
|
-
}
|
|
3466
|
-
}
|
|
3467
|
-
else if (pd.kind === 'rangeDraw') {
|
|
3468
|
-
// 框选结束:以拖框中点为中心、半宽为半径生成矩形范围(过小拖动视为取消)
|
|
3469
|
-
const asp = this.agents.get(pd.id)
|
|
3470
|
-
if (asp && this.rangeDrawPreviewState) {
|
|
3471
|
-
const d = this.rangeDrawPreviewState
|
|
3472
|
-
const range: AgentRangeLayout = {
|
|
3473
|
-
x: (d.x0 + d.x1) / 2,
|
|
3474
|
-
z: (d.z0 + d.z1) / 2,
|
|
3475
|
-
radiusX: Math.abs(d.x1 - d.x0) / 2,
|
|
3476
|
-
radiusZ: Math.abs(d.z1 - d.z0) / 2,
|
|
3477
|
-
shape: 'rect',
|
|
3478
|
-
rotationY: 0,
|
|
3479
|
-
}
|
|
3480
|
-
if (range.radiusX >= 40 && range.radiusZ >= 40) {
|
|
3481
|
-
asp.range = range
|
|
3482
|
-
asp.renderRangeLine()
|
|
3483
|
-
// 把角色落点收进新范围(home 若在范围外会导致回归/漫游卡死)
|
|
3484
|
-
const c = clampToAgentRange(asp.range, asp.homeX, asp.homeZ, 0)
|
|
3485
|
-
if (c.x !== asp.homeX || c.z !== asp.homeZ) {
|
|
3486
|
-
asp.homeX = c.x
|
|
3487
|
-
asp.homeZ = c.z
|
|
3488
|
-
asp.root.position.x = c.x
|
|
3489
|
-
asp.root.position.z = c.z
|
|
3490
|
-
void this.agentApi?.updateHome(pd.id, asp.channelId, c.x, c.z).catch(() => {})
|
|
3491
|
-
}
|
|
3492
|
-
this.emitSaveState('saving')
|
|
3493
|
-
void this.agentApi?.updateRange(pd.id, asp.channelId, asp.range)
|
|
3494
|
-
.then(() => this.emitSaveState('saved', Date.now()))
|
|
3495
|
-
.catch(() => this.emitSaveState('error'))
|
|
3496
|
-
}
|
|
3497
|
-
}
|
|
3498
|
-
this.cancelRangeDraw()
|
|
3499
|
-
this.refreshAgentRangeHandles()
|
|
3500
|
-
this.emitAgentRangeChanged(pd.id)
|
|
3501
|
-
}
|
|
3502
|
-
else if (pd.kind === 'agentRange' || pd.kind === 'agentRangeResize') {
|
|
3503
|
-
// 拖范围整框 / 拖范围手柄结束 → 落库 config.range
|
|
3504
|
-
const asp = this.agents.get(pd.id)
|
|
3505
|
-
if (asp && asp.range) {
|
|
3506
|
-
this.emitSaveState('saving')
|
|
3507
|
-
void this.agentApi?.updateRange(pd.id, asp.channelId, asp.range)
|
|
3508
|
-
.then(() => this.emitSaveState('saved', Date.now()))
|
|
3509
|
-
.catch(() => this.emitSaveState('error'))
|
|
3510
|
-
}
|
|
3511
|
-
this.refreshAgentRangeHandles()
|
|
3512
|
-
this.emitAgentRangeChanged(pd.id)
|
|
3513
|
-
}
|
|
3514
|
-
else {
|
|
3515
|
-
// channel / resize:把成员钳回新边界 + 布局落库 + 通知 Vue 刷新边界面板草稿
|
|
3516
|
-
const b = this.blocks.get(pd.id)
|
|
3517
|
-
if (b) {
|
|
3518
|
-
this.clampAgentsToBoundary(pd.id)
|
|
3519
|
-
this.refreshChannelHandles()
|
|
3520
|
-
const layout = this.getChannelLayout(pd.id)
|
|
3521
|
-
if (layout) {
|
|
3522
|
-
this.emitSaveState('saving')
|
|
3523
|
-
void this.channelApi?.save(pd.id, layout)
|
|
3524
|
-
.then(() => this.emitSaveState('saved', Date.now()))
|
|
3525
|
-
.catch(() => this.emitSaveState('error'))
|
|
3526
|
-
this.emit('channelResized', { channelId: pd.id, layout })
|
|
3527
|
-
}
|
|
3528
|
-
}
|
|
3529
|
-
}
|
|
3530
|
-
}
|
|
734
|
+
endPointerDrag(): void { endPointerDrag(this) }
|
|
3531
735
|
|
|
3532
736
|
// ================================================================
|
|
3533
737
|
// Agent 独立活动范围(编辑模式:框选绘制 / 整框平移 / 手柄收缩扩张)
|
|
738
|
+
// 实现见 scene/town-scene3d-ranges.ts
|
|
3534
739
|
// ================================================================
|
|
3535
740
|
|
|
3536
741
|
/** 活动范围线框可见性:编辑模式全部显示;浏览模式仅选中角色的范围显示 */
|
|
3537
|
-
|
|
3538
|
-
if (this.mode === 'edit') return true
|
|
3539
|
-
return this.selected?.kind === 'agent' && this.selected.id === asp.agentId
|
|
3540
|
-
}
|
|
742
|
+
rangeLineVisible(asp: Agent3D): boolean { return rangeLineVisible(this, asp) }
|
|
3541
743
|
|
|
3542
744
|
/** 通知 Vue 对象面板刷新活动范围草稿 */
|
|
3543
|
-
|
|
3544
|
-
this.emit('agentRangeChanged', { agentId })
|
|
3545
|
-
}
|
|
745
|
+
emitAgentRangeChanged(agentId: string): void { emitAgentRangeChanged(this, agentId) }
|
|
3546
746
|
|
|
3547
747
|
/** 同步全部范围线框可见性(选中/模式切换后调用) */
|
|
3548
|
-
|
|
3549
|
-
for (const a of this.agents.values()) {
|
|
3550
|
-
if (a.rangeLine) a.rangeLine.visible = this.rangeLineVisibleFor(a)
|
|
3551
|
-
}
|
|
3552
|
-
}
|
|
748
|
+
refreshAgentRangeLines(): void { refreshAgentRangeLines(this) }
|
|
3553
749
|
|
|
3554
750
|
/** 活动范围线框可见性(公众面,供 Agent3D.renderRangeLine):编辑模式全部显示;浏览模式仅选中显示 */
|
|
3555
|
-
rangeLineVisibleFor(asp: Agent3D): boolean {
|
|
3556
|
-
if (this.mode === 'edit') return true
|
|
3557
|
-
return this.selected?.kind === 'agent' && this.selected.id === asp.agentId
|
|
3558
|
-
}
|
|
751
|
+
rangeLineVisibleFor(asp: Agent3D): boolean { return rangeLineVisibleFor(this, asp) }
|
|
3559
752
|
|
|
3560
753
|
/** 刷新 Agent 活动范围手柄(编辑模式 + 选中带范围角色时显示;矩形四角 / 椭圆轴向四点) */
|
|
3561
|
-
|
|
3562
|
-
for (const hl of this.agentRangeHandles) hl.mesh.visible = false
|
|
3563
|
-
if (this.mode !== 'edit' || !this.selected || this.selected.kind !== 'agent') return
|
|
3564
|
-
const asp = this.agents.get(this.selected.id)
|
|
3565
|
-
if (!asp || !asp.range) return
|
|
3566
|
-
const rot = asp.range.rotationY * Math.PI / 180
|
|
3567
|
-
const pts = this.boundaryHandlePoints(asp.range)
|
|
3568
|
-
for (let i = 0; i < this.agentRangeHandles.length && i < pts.length; i++) {
|
|
3569
|
-
const [lx, lz] = pts[i]!
|
|
3570
|
-
const wx = asp.range.x + lx * Math.cos(rot) - lz * Math.sin(rot)
|
|
3571
|
-
const wz = asp.range.z + lx * Math.sin(rot) + lz * Math.cos(rot)
|
|
3572
|
-
this.agentRangeHandles[i]!.agentId = asp.agentId
|
|
3573
|
-
this.agentRangeHandles[i]!.mesh.position.set(wx, 1.2, wz)
|
|
3574
|
-
this.agentRangeHandles[i]!.mesh.visible = true
|
|
3575
|
-
}
|
|
3576
|
-
}
|
|
754
|
+
refreshAgentRangeHandles(): void { refreshAgentRangeHandles(this) }
|
|
3577
755
|
|
|
3578
756
|
/** 命中选中 Agent 的活动范围手柄(编辑模式;返回 agentId + 手柄号) */
|
|
3579
|
-
|
|
3580
|
-
if (this.mode !== 'edit') return null
|
|
3581
|
-
for (const hl of this.agentRangeHandles) {
|
|
3582
|
-
if (!hl.mesh.visible) continue
|
|
3583
|
-
if (Math.hypot(hl.mesh.position.x - x, hl.mesh.position.z - z) < 40) return { agentId: hl.agentId, handle: hl.handle }
|
|
3584
|
-
}
|
|
3585
|
-
return null
|
|
3586
|
-
}
|
|
757
|
+
pickAgentRangeHandle(x: number, z: number): { agentId: string, handle: number } | null { return pickAgentRangeHandle(this, x, z) }
|
|
3587
758
|
|
|
3588
759
|
/** 命中某成员活动范围边界线(供整框拖移;编辑模式;26 单位内视为命中) */
|
|
3589
|
-
|
|
3590
|
-
if (this.mode !== 'edit') return undefined
|
|
3591
|
-
for (const a of this.agents.values()) {
|
|
3592
|
-
if (!a.range || !a.channelId) continue
|
|
3593
|
-
if (distToRangeBoundary(a.range, x, z) < 26) return a
|
|
3594
|
-
}
|
|
3595
|
-
return undefined
|
|
3596
|
-
}
|
|
760
|
+
hitAgentRangeBoundary(x: number, z: number): Agent3D | undefined { return hitAgentRangeBoundary(this, x, z) }
|
|
3597
761
|
|
|
3598
762
|
/** 拖范围手柄 → 实时调整 radiusX/radiusZ(矩形:角点双轴;椭圆:对应轴向轴点),并收进频道边界 */
|
|
3599
|
-
|
|
3600
|
-
const rad = -range.rotationY * Math.PI / 180
|
|
3601
|
-
const cos = Math.cos(rad)
|
|
3602
|
-
const sin = Math.sin(rad)
|
|
3603
|
-
const dx = wx - range.x
|
|
3604
|
-
const dz = wz - range.z
|
|
3605
|
-
const lx = dx * cos - dz * sin
|
|
3606
|
-
const lz = dx * sin + dz * cos
|
|
3607
|
-
let next: AgentRangeLayout
|
|
3608
|
-
if (range.shape === 'rect') {
|
|
3609
|
-
next = { ...range, radiusX: Math.max(30, Math.abs(lx)), radiusZ: Math.max(30, Math.abs(lz)) }
|
|
3610
|
-
}
|
|
3611
|
-
else if (handle === 0 || handle === 1) {
|
|
3612
|
-
next = { ...range, radiusX: Math.max(30, Math.abs(lx)) }
|
|
3613
|
-
}
|
|
3614
|
-
else {
|
|
3615
|
-
next = { ...range, radiusZ: Math.max(30, Math.abs(lz)) }
|
|
3616
|
-
}
|
|
3617
|
-
return next
|
|
3618
|
-
}
|
|
763
|
+
applyAgentRangeResize(range: AgentRangeLayout, handle: number, wx: number, wz: number): AgentRangeLayout { return applyAgentRangeResize(range, handle, wx, wz) }
|
|
3619
764
|
|
|
3620
765
|
/** 框选预览:以 (x0,z0)-(x1,z1) 为对角生成矩形线框(实时跟随指针) */
|
|
3621
|
-
|
|
3622
|
-
const lx = Math.min(x0, x1)
|
|
3623
|
-
const hx = Math.max(x0, x1)
|
|
3624
|
-
const lz = Math.min(z0, z1)
|
|
3625
|
-
const hz = Math.max(z0, z1)
|
|
3626
|
-
const cx = (lx + hx) / 2
|
|
3627
|
-
const cz = (lz + hz) / 2
|
|
3628
|
-
const rx = Math.max(8, (hx - lx) / 2)
|
|
3629
|
-
const rz = Math.max(8, (hz - lz) / 2)
|
|
3630
|
-
if (!this.rangeDrawLine) {
|
|
3631
|
-
const line = makeBoundary('rect', 1, 1, 0x41c8f4)
|
|
3632
|
-
;(line.material as THREE.LineBasicMaterial).opacity = 0.95
|
|
3633
|
-
line.position.y = 0.5
|
|
3634
|
-
this.scene.add(line)
|
|
3635
|
-
this.rangeDrawLine = line
|
|
3636
|
-
}
|
|
3637
|
-
this.rangeDrawLine.position.set(cx, 0.5, cz)
|
|
3638
|
-
this.rangeDrawLine.rotation.y = 0
|
|
3639
|
-
const geo = new THREE.BufferGeometry().setFromPoints([
|
|
3640
|
-
new THREE.Vector3(-rx, 0, -rz), new THREE.Vector3(rx, 0, -rz),
|
|
3641
|
-
new THREE.Vector3(rx, 0, rz), new THREE.Vector3(-rx, 0, rz),
|
|
3642
|
-
])
|
|
3643
|
-
this.rangeDrawLine.geometry.dispose()
|
|
3644
|
-
this.rangeDrawLine.geometry = geo
|
|
3645
|
-
}
|
|
766
|
+
updateRangeDrawPreview(x0: number, z0: number, x1: number, z1: number): void { updateRangeDrawPreview(this, x0, z0, x1, z1) }
|
|
3646
767
|
|
|
3647
768
|
/** 退出框选绘制模式(清理预览与状态) */
|
|
3648
|
-
cancelRangeDraw(): void {
|
|
3649
|
-
if (this.rangeDrawAgent === null && !this.rangeDrawLine) return
|
|
3650
|
-
this.rangeDrawAgent = null
|
|
3651
|
-
this.rangeDrawPreviewState = null
|
|
3652
|
-
if (this.rangeDrawLine) {
|
|
3653
|
-
this.scene.remove(this.rangeDrawLine)
|
|
3654
|
-
this.rangeDrawLine = null
|
|
3655
|
-
}
|
|
3656
|
-
this.dirty = true
|
|
3657
|
-
}
|
|
769
|
+
cancelRangeDraw(): void { cancelRangeDraw(this) }
|
|
3658
770
|
|
|
3659
771
|
/** 进入「框选绘制」模式:为该角色拉动矩形框生成活动范围(编辑模式) */
|
|
3660
|
-
startRangeDraw(agentId: string): void {
|
|
3661
|
-
const asp = this.agents.get(agentId)
|
|
3662
|
-
if (!asp || !asp.channelId || this.mode !== 'edit') return
|
|
3663
|
-
this.setSelected({ kind: 'agent', id: agentId })
|
|
3664
|
-
this.cancelRangeDraw()
|
|
3665
|
-
this.rangeDrawAgent = agentId
|
|
3666
|
-
this.rangeDrawPreviewState = null
|
|
3667
|
-
this.refreshAgentRangeHandles()
|
|
3668
|
-
this.dirty = true
|
|
3669
|
-
}
|
|
772
|
+
startRangeDraw(agentId: string): void { startRangeDraw(this, agentId) }
|
|
3670
773
|
|
|
3671
774
|
/** 当前是否正在为某角色框选绘制 */
|
|
3672
|
-
isRangeDrawing(agentId?: string): boolean {
|
|
3673
|
-
if (!this.rangeDrawAgent) return false
|
|
3674
|
-
return agentId === undefined || this.rangeDrawAgent === agentId
|
|
3675
|
-
}
|
|
775
|
+
isRangeDrawing(agentId?: string): boolean { return isRangeDrawing(this, agentId) }
|
|
3676
776
|
|
|
3677
777
|
/** E2E/调试:强制某频道接收器跳过当前条目,立即消费下一条(FIFO 顺序断言用) */
|
|
3678
778
|
debugAdvanceReceiver(channelId: string): void {
|
|
@@ -3681,799 +781,92 @@ export class TownScene3D {
|
|
|
3681
781
|
}
|
|
3682
782
|
|
|
3683
783
|
/** Agent 当前活动范围(供面板初始化;未设置返回 null) */
|
|
3684
|
-
getAgentRange(agentId: string): AgentRangeLayout | null {
|
|
3685
|
-
return this.agents.get(agentId)?.range ?? null
|
|
3686
|
-
}
|
|
784
|
+
getAgentRange(agentId: string): AgentRangeLayout | null { return getAgentRange(this, agentId) }
|
|
3687
785
|
|
|
3688
786
|
/** 面板滑杆/形状即时调整:局部更新范围(无上限,信任用户设定) + 线框/手柄刷新;home 被迫位移时记档待提交 */
|
|
3689
|
-
setAgentRangeScene(agentId: string, patch: Partial<AgentRangeLayout>): void {
|
|
3690
|
-
const asp = this.agents.get(agentId)
|
|
3691
|
-
if (!asp) return
|
|
3692
|
-
if (!asp.range) {
|
|
3693
|
-
// 尚无范围:以落点为中心、频道半径 1/4 起手(面板直接给值时)
|
|
3694
|
-
asp.range = { x: asp.homeX, z: asp.homeZ, radiusX: 120, radiusZ: 90, shape: 'ellipse', rotationY: 0 }
|
|
3695
|
-
}
|
|
3696
|
-
const next = { ...asp.range, ...patch }
|
|
3697
|
-
asp.range = next
|
|
3698
|
-
// 范围收缩致 home 越界 → 落点收进范围(提交时随 updateRange 一并落库)
|
|
3699
|
-
const c = clampToAgentRange(asp.range, asp.homeX, asp.homeZ, 0)
|
|
3700
|
-
if (c.x !== asp.homeX || c.z !== asp.homeZ) {
|
|
3701
|
-
asp.homeX = c.x
|
|
3702
|
-
asp.homeZ = c.z
|
|
3703
|
-
asp.root.position.x = c.x
|
|
3704
|
-
asp.root.position.z = c.z
|
|
3705
|
-
this.rangeHomeMoved = { agentId, x: c.x, z: c.z }
|
|
3706
|
-
}
|
|
3707
|
-
asp.renderRangeLine()
|
|
3708
|
-
this.refreshAgentRangeHandles()
|
|
3709
|
-
this.dirty = true
|
|
3710
|
-
}
|
|
787
|
+
setAgentRangeScene(agentId: string, patch: Partial<AgentRangeLayout>): void { setAgentRangeScene(this, agentId, patch) }
|
|
3711
788
|
|
|
3712
789
|
/** 面板提交:落库范围(updateRange);home 若被迫位移一并落库(updateHome) */
|
|
3713
|
-
commitAgentRange(agentId: string): void {
|
|
3714
|
-
const asp = this.agents.get(agentId)
|
|
3715
|
-
if (!asp) return
|
|
3716
|
-
if (this.rangeHomeMoved?.agentId === agentId) {
|
|
3717
|
-
const { x, z } = this.rangeHomeMoved
|
|
3718
|
-
this.rangeHomeMoved = null
|
|
3719
|
-
void this.agentApi?.updateHome(agentId, asp.channelId, x, z).catch(() => {})
|
|
3720
|
-
}
|
|
3721
|
-
this.emitSaveState('saving')
|
|
3722
|
-
void this.agentApi?.updateRange(agentId, asp.channelId, asp.range)
|
|
3723
|
-
.then(() => this.emitSaveState('saved', Date.now()))
|
|
3724
|
-
.catch(() => this.emitSaveState('error'))
|
|
3725
|
-
this.emitAgentRangeChanged(agentId)
|
|
3726
|
-
}
|
|
790
|
+
commitAgentRange(agentId: string): void { commitAgentRange(this, agentId) }
|
|
3727
791
|
|
|
3728
792
|
/** 清除角色活动范围(局部 + 落库 null;回退频道边界) */
|
|
3729
|
-
clearAgentRange(agentId: string): void {
|
|
3730
|
-
const asp = this.agents.get(agentId)
|
|
3731
|
-
if (!asp) return
|
|
3732
|
-
if (asp.rangeLine) {
|
|
3733
|
-
this.scene.remove(asp.rangeLine)
|
|
3734
|
-
asp.rangeLine = null
|
|
3735
|
-
}
|
|
3736
|
-
asp.range = null
|
|
3737
|
-
this.rangeHomeMoved = null
|
|
3738
|
-
this.refreshAgentRangeHandles()
|
|
3739
|
-
this.emitSaveState('saving')
|
|
3740
|
-
void this.agentApi?.updateRange(agentId, asp.channelId, null)
|
|
3741
|
-
.then(() => this.emitSaveState('saved', Date.now()))
|
|
3742
|
-
.catch(() => this.emitSaveState('error'))
|
|
3743
|
-
this.emitAgentRangeChanged(agentId)
|
|
3744
|
-
this.dirty = true
|
|
3745
|
-
}
|
|
793
|
+
clearAgentRange(agentId: string): void { clearAgentRange(this, agentId) }
|
|
3746
794
|
|
|
3747
795
|
/** 对象朝向(度;编辑模式旋转滑杆实时) */
|
|
3748
|
-
setModelRotation(id: string, deg: number, kind?: 'agent' | 'device'): void {
|
|
3749
|
-
const k = kind ?? (this.deviceNodes.has(id) ? 'device' : 'agent')
|
|
3750
|
-
if (k === 'device') {
|
|
3751
|
-
const dev = this.deviceNodes.get(id)
|
|
3752
|
-
if (!dev) return
|
|
3753
|
-
dev.root.rotation.y = deg * Math.PI / 180
|
|
3754
|
-
}
|
|
3755
|
-
else {
|
|
3756
|
-
const asp = this.agents.get(id)
|
|
3757
|
-
if (!asp) return
|
|
3758
|
-
asp.root.rotation.y = deg * Math.PI / 180
|
|
3759
|
-
}
|
|
3760
|
-
this.dirty = true
|
|
3761
|
-
}
|
|
796
|
+
setModelRotation(id: string, deg: number, kind?: 'agent' | 'device'): void { setModelRotation(this, id, deg, kind) }
|
|
3762
797
|
|
|
3763
|
-
getModelRotation(id: string, kind?: 'agent' | 'device'): number {
|
|
3764
|
-
const k = kind ?? (this.deviceNodes.has(id) ? 'device' : 'agent')
|
|
3765
|
-
if (k === 'device') {
|
|
3766
|
-
const dev = this.deviceNodes.get(id)
|
|
3767
|
-
return dev ? THREE.MathUtils.radToDeg(dev.root.rotation.y) : 0
|
|
3768
|
-
}
|
|
3769
|
-
const asp = this.agents.get(id)
|
|
3770
|
-
return asp ? THREE.MathUtils.radToDeg(asp.root.rotation.y) : 0
|
|
3771
|
-
}
|
|
798
|
+
getModelRotation(id: string, kind?: 'agent' | 'device'): number { return getModelRotation(this, id, kind) }
|
|
3772
799
|
|
|
3773
800
|
// ================================================================
|
|
3774
801
|
// 场景保存状态(未保存/保存中/已保存/失败)+ 设备 transform 防抖落库
|
|
802
|
+
// 实现见 scene/town-scene3d-devices.ts
|
|
3775
803
|
// ================================================================
|
|
3776
804
|
|
|
3777
|
-
|
|
805
|
+
emitSaveState(state: 'idle' | 'dirty' | 'saving' | 'saved' | 'error', at = Date.now()): void {
|
|
3778
806
|
this.emit('saveState', { state, at })
|
|
3779
807
|
}
|
|
3780
808
|
|
|
3781
809
|
/** 持久化设备 transform(位置/朝向/缩放一次写入;防抖 350ms,不逐帧写库) */
|
|
3782
|
-
persistDeviceTransform(id: string): void {
|
|
3783
|
-
const dev = this.deviceNodes.get(id)
|
|
3784
|
-
if (!dev || !this.devices?.update || this.pendingDeviceCreates.has(id)) return
|
|
3785
|
-
const st = this.scalables.get(`device:${id}`)
|
|
3786
|
-
const prev = this.pendingSaveTimers.get(id)
|
|
3787
|
-
if (prev) clearTimeout(prev)
|
|
3788
|
-
this.pendingSaveTimers.set(id, setTimeout(() => {
|
|
3789
|
-
this.pendingSaveTimers.delete(id)
|
|
3790
|
-
if (this.disposed || this.pendingDeviceCreates.has(id)) return
|
|
3791
|
-
this.emitSaveState('saving')
|
|
3792
|
-
void this.devices!.update(id, {
|
|
3793
|
-
posX: Math.round(dev.root.position.x * 10) / 10,
|
|
3794
|
-
posZ: Math.round(dev.root.position.z * 10) / 10,
|
|
3795
|
-
rotationY: Math.round(THREE.MathUtils.radToDeg(dev.root.rotation.y) * 10) / 10,
|
|
3796
|
-
scale: st ? Math.round(st.userScale * 100) / 100 : undefined,
|
|
3797
|
-
})
|
|
3798
|
-
.then(() => this.emitSaveState('saved', Date.now()))
|
|
3799
|
-
.catch(() => this.emitSaveState('error'))
|
|
3800
|
-
}, 350))
|
|
3801
|
-
this.emitSaveState('dirty')
|
|
3802
|
-
}
|
|
810
|
+
persistDeviceTransform(id: string): void { persistDeviceTransform(this, id) }
|
|
3803
811
|
|
|
3804
812
|
/** 保存全部设备节点(「保存布局」按钮:强制全量落库) */
|
|
3805
|
-
persistAllDevices(): void {
|
|
3806
|
-
for (const id of [...this.deviceNodes.keys()]) this.persistDeviceTransform(id)
|
|
3807
|
-
}
|
|
813
|
+
persistAllDevices(): void { persistAllDevices(this) }
|
|
3808
814
|
|
|
3809
|
-
|
|
815
|
+
pendingSaveTimers = new Map<string, ReturnType<typeof setTimeout>>()
|
|
3810
816
|
|
|
3811
817
|
// ================================================================
|
|
3812
|
-
// 设备场景实例同步(服务端设备孪生 →
|
|
818
|
+
// 设备场景实例同步(服务端设备孪生 → 本地节点;多客户端一致;
|
|
819
|
+
// 实现见 scene/town-scene3d-devices.ts)
|
|
3813
820
|
// ================================================================
|
|
3814
821
|
|
|
3815
822
|
/**
|
|
3816
|
-
*
|
|
3817
|
-
*
|
|
3818
|
-
* - 已存在 → 收敛状态/遥测,并在未拖拽时收敛 transform(他人编辑即时跟随);
|
|
3819
|
-
* - 服务端已删除 → 移除本地节点。
|
|
3820
|
-
* TownView 在 device.* 事件与轮询时调用;轮询兜底多客户端同步。
|
|
823
|
+
* 与设备孪生清单对齐:服务端有落点且本地无节点 → 重建;已存在 → 收敛状态/遥测/transform;
|
|
824
|
+
* 服务端已删除 → 移除本地节点。TownView 在 device.* 事件与轮询时调用。
|
|
3821
825
|
*/
|
|
3822
|
-
syncDevices(twins: DeviceTwinSync[]): void {
|
|
3823
|
-
const serverIds = new Set(twins.map(t => t.id))
|
|
3824
|
-
for (const id of [...this.deviceNodes.keys()]) {
|
|
3825
|
-
if (!serverIds.has(id) && !this.pendingDeviceCreates.has(id)) this.removeDeviceNode(id)
|
|
3826
|
-
}
|
|
3827
|
-
for (const t of twins) {
|
|
3828
|
-
let existing = this.deviceNodes.get(t.id)
|
|
3829
|
-
if (!existing) {
|
|
3830
|
-
const pending = [...this.pendingDeviceCreates.entries()].find(([, p]) =>
|
|
3831
|
-
p.name === t.name && p.modelRef === t.modelRef,
|
|
3832
|
-
)
|
|
3833
|
-
if (pending) {
|
|
3834
|
-
this.adoptDeviceNode(pending[0], t.id)
|
|
3835
|
-
existing = this.deviceNodes.get(t.id)
|
|
3836
|
-
}
|
|
3837
|
-
}
|
|
3838
|
-
if (!existing) {
|
|
3839
|
-
// 数采/智控节点走程序化路径(无 GLB 也可实例化);设备需模型可解析 + 已入场景
|
|
3840
|
-
const isDaq = t.kind === 'daq' || (t.modelRef ?? '').startsWith('daq-') || (t.modelRef ?? '').startsWith('dcw-')
|
|
3841
|
-
const resolvable = isDaq || !!this.resolveDeviceModel(t.modelRef)?.file
|
|
3842
|
-
if (resolvable && typeof t.posX === 'number' && typeof t.posZ === 'number') this.recreateDeviceNode(t)
|
|
3843
|
-
else this.pendingDeviceTwins.set(t.id, t)
|
|
3844
|
-
continue
|
|
3845
|
-
}
|
|
3846
|
-
this.pendingDeviceTwins.delete(t.id)
|
|
3847
|
-
existing.applyTwin(t)
|
|
3848
|
-
if (this.pointerDrag?.id !== t.id) {
|
|
3849
|
-
const moved = (typeof t.posX === 'number' && t.posX !== existing.root.position.x)
|
|
3850
|
-
|| (typeof t.posZ === 'number' && t.posZ !== existing.root.position.z)
|
|
3851
|
-
if (typeof t.posX === 'number') existing.root.position.x = t.posX
|
|
3852
|
-
if (typeof t.posZ === 'number') existing.root.position.z = t.posZ
|
|
3853
|
-
if (typeof t.rotationY === 'number') existing.root.rotation.y = t.rotationY * Math.PI / 180
|
|
3854
|
-
const st = this.scalables.get(`device:${t.id}`)
|
|
3855
|
-
if (typeof t.scale === 'number' && st) {
|
|
3856
|
-
st.userScale = t.scale
|
|
3857
|
-
st.holder.scale.setScalar(t.scale)
|
|
3858
|
-
}
|
|
3859
|
-
if (moved) this.dirty = true
|
|
3860
|
-
}
|
|
3861
|
-
this.dirty = true
|
|
3862
|
-
}
|
|
3863
|
-
// 设备节点就位后重仲裁绑定链路(链路期望先于节点到达的时序兜底)
|
|
3864
|
-
if (this.daqLinksWanted.length) this.syncDaqLinks(this.daqLinksWanted)
|
|
3865
|
-
for (const id of [...this.pendingDeviceTwins.keys()]) {
|
|
3866
|
-
if (!serverIds.has(id)) this.pendingDeviceTwins.delete(id)
|
|
3867
|
-
}
|
|
3868
|
-
}
|
|
826
|
+
syncDevices(twins: DeviceTwinSync[]): void { syncDevices(this, twins) }
|
|
3869
827
|
|
|
3870
828
|
/** 按设备孪生记录重建场景节点(持久化恢复:pos/rotation/scale;模型缺失则跳过) */
|
|
3871
|
-
|
|
3872
|
-
private resolveDeviceModel(ref: string): { id: string, file: string, hFactor: number } | null {
|
|
3873
|
-
const direct = this.modelsById.get(ref)
|
|
3874
|
-
if (direct?.file) return { id: direct.id, file: direct.file, hFactor: direct.hFactor ?? 1 }
|
|
3875
|
-
for (const [id, m] of this.modelsById) {
|
|
3876
|
-
if (m.kind !== 'dev' || !m.file) continue
|
|
3877
|
-
if (id === `dev-folder-${ref}` || id.endsWith(`-${ref}`)) return { id, file: m.file, hFactor: m.hFactor ?? 1 }
|
|
3878
|
-
}
|
|
3879
|
-
return null
|
|
3880
|
-
}
|
|
3881
|
-
|
|
3882
|
-
private recreateDeviceNode(t: DeviceTwinSync): void {
|
|
3883
|
-
// 数采/智控节点:程序化传感网格(基座 + 传感头 + 信号环),不依赖 GLB 资产
|
|
3884
|
-
// (modelRef 前缀兜底:旧数据 kind 落成 'device' 的数采实例同样走程序化路径)
|
|
3885
|
-
if (t.kind === 'daq' || (t.modelRef ?? '').startsWith('daq-') || (t.modelRef ?? '').startsWith('dcw-')) {
|
|
3886
|
-
this.recreateDaqNode(t)
|
|
3887
|
-
return
|
|
3888
|
-
}
|
|
3889
|
-
const model = this.resolveDeviceModel(t.modelRef)
|
|
3890
|
-
const file = model?.file ?? ''
|
|
3891
|
-
if (!file) return
|
|
3892
|
-
const x = typeof t.posX === 'number' ? t.posX : WORLD_CX
|
|
3893
|
-
const z = typeof t.posZ === 'number' ? t.posZ : WORLD_CZ
|
|
3894
|
-
const root = new THREE.Group()
|
|
3895
|
-
root.position.set(x, GROUND_Y, z)
|
|
3896
|
-
root.rotation.y = typeof t.rotationY === 'number' ? t.rotationY * Math.PI / 180 : 0
|
|
3897
|
-
const { ring, arc } = this.makeDeviceRing()
|
|
3898
|
-
root.add(ring, arc)
|
|
3899
|
-
const holder = new THREE.Group()
|
|
3900
|
-
root.add(holder)
|
|
3901
|
-
const label = this.makeLabel(`⚙ ${t.name}`, x, 60, z)
|
|
3902
|
-
holder.userData.modelFile = file
|
|
3903
|
-
this.scene.add(root)
|
|
3904
|
-
const twin = new DeviceNode({ twinId: t.id, name: t.name, modelRef: t.modelRef, root, holder, ring, label, state: t.state ?? 'idle', telemetry: t.telemetry ?? {} })
|
|
3905
|
-
twin.host = this
|
|
3906
|
-
twin.arc = arc
|
|
3907
|
-
twin.updateRing()
|
|
3908
|
-
this.deviceNodes.set(t.id, twin)
|
|
3909
|
-
// 服务端缩放优先(持久化恢复);缺省回退 localStorage
|
|
3910
|
-
this.registerScalable('device', t.id, holder, typeof t.scale === 'number' ? t.scale : undefined)
|
|
3911
|
-
// 高度系数:产线设备按设计稿比例错落(基准高 × 模型系数),未知模型维持 1
|
|
3912
|
-
void this.loadGltfToGroup(file, holder, UNITS * 1.6 * (model?.hFactor ?? 1))
|
|
3913
|
-
this.dirty = true
|
|
3914
|
-
}
|
|
3915
|
-
|
|
3916
|
-
/** 数采节点传感头(设计稿 DAQ 预制件 1:1 移植,×34 世界单位):
|
|
3917
|
-
* 立杆基座 + 顶端 LED 环 + 每模板独立传感形态 —— 温度计/压力表/张力辊/编码盘/相机/电参天线。 */
|
|
3918
|
-
private makeDaqMesh(modelRef: string): { group: THREE.Group, ledRing: THREE.Mesh } {
|
|
3919
|
-
const S = 34
|
|
3920
|
-
const g = new THREE.Group()
|
|
3921
|
-
const steel = new THREE.MeshStandardMaterial({ color: 0x9fb2c8, metalness: 0.85, roughness: 0.32 })
|
|
3922
|
-
const body = new THREE.MeshStandardMaterial({ color: 0x74869c, metalness: 0.35, roughness: 0.5 })
|
|
3923
|
-
const body2 = new THREE.MeshStandardMaterial({ color: 0x55647a, metalness: 0.4, roughness: 0.55 })
|
|
3924
|
-
const dark = new THREE.MeshStandardMaterial({ color: 0x2b3442, metalness: 0.7, roughness: 0.45 })
|
|
3925
|
-
const chrome = new THREE.MeshStandardMaterial({ color: 0xdfe8f2, metalness: 1, roughness: 0.14 })
|
|
3926
|
-
const copper = new THREE.MeshStandardMaterial({ color: 0xc57a45, metalness: 0.9, roughness: 0.35 })
|
|
3927
|
-
const glow = new THREE.MeshBasicMaterial({ color: 0x41c8f4 })
|
|
3928
|
-
glow.color.multiplyScalar(5)
|
|
3929
|
-
const B = (w: number, h: number, d: number, m: THREE.Material): THREE.Mesh => {
|
|
3930
|
-
const mesh = new THREE.Mesh(new THREE.BoxGeometry(w * S, h * S, d * S), m)
|
|
3931
|
-
mesh.castShadow = true
|
|
3932
|
-
return mesh
|
|
3933
|
-
}
|
|
3934
|
-
const Cyl = (rt: number, rb: number, h: number, m: THREE.Material, seg = 20): THREE.Mesh => {
|
|
3935
|
-
const mesh = new THREE.Mesh(new THREE.CylinderGeometry(rt * S, rb * S, h * S, seg), m)
|
|
3936
|
-
mesh.castShadow = true
|
|
3937
|
-
return mesh
|
|
3938
|
-
}
|
|
3939
|
-
const at = (m: THREE.Mesh, x: number, y: number, z: number): THREE.Mesh => {
|
|
3940
|
-
m.position.set(x * S, y * S, z * S)
|
|
3941
|
-
g.add(m)
|
|
3942
|
-
return m
|
|
3943
|
-
}
|
|
3944
|
-
// 立杆基座(设计稿 daqPole)
|
|
3945
|
-
at(Cyl(0.17, 0.22, 0.09, dark), 0, 0.045, 0)
|
|
3946
|
-
at(Cyl(0.028, 0.028, 0.95, steel, 10), 0, 0.55, 0)
|
|
3947
|
-
const ledRing = new THREE.Mesh(new THREE.TorusGeometry(0.09 * S, 0.02 * S, 10, 28), new THREE.MeshBasicMaterial({ color: 0x35e0a0 }))
|
|
3948
|
-
// HDR 亮度:LED 环(及其复用材质的 halo/iris/tip)提到阈值之上,"真发光"元件(bloom 起晕)
|
|
3949
|
-
;(ledRing.material as THREE.MeshBasicMaterial).color.multiplyScalar(6)
|
|
3950
|
-
ledRing.rotation.x = Math.PI / 2
|
|
3951
|
-
g.add(ledRing)
|
|
3952
|
-
ledRing.position.y = 1.02 * S
|
|
3953
|
-
// 每模板传感头(modelRef = `daq-<tplId>`)
|
|
3954
|
-
if (modelRef.endsWith('temp-tc')) {
|
|
3955
|
-
at(B(0.24, 0.32, 0.24, body), 0, 1.2, 0)
|
|
3956
|
-
const probe = at(Cyl(0.014, 0.014, 0.4, steel, 8), 0.14, 0.85, 0)
|
|
3957
|
-
probe.rotation.z = -0.5
|
|
3958
|
-
}
|
|
3959
|
-
else if (modelRef.endsWith('pressure-tx')) {
|
|
3960
|
-
at(Cyl(0.13, 0.16, 0.3, steel), 0, 1.22, 0)
|
|
3961
|
-
at(Cyl(0.12, 0.12, 0.05, dark), 0, 1.4, 0)
|
|
3962
|
-
at(B(0.05, 0.05, 0.2, copper), 0, 1.1, 0.2)
|
|
3963
|
-
}
|
|
3964
|
-
else if (modelRef.endsWith('tension-cell')) {
|
|
3965
|
-
at(B(0.34, 0.2, 0.2, body), 0, 1.25, 0)
|
|
3966
|
-
for (const x of [-0.11, 0.11]) {
|
|
3967
|
-
const roll = at(Cyl(0.06, 0.06, 0.26, chrome), x, 1.25, 0)
|
|
3968
|
-
roll.rotation.x = Math.PI / 2
|
|
3969
|
-
}
|
|
3970
|
-
}
|
|
3971
|
-
else if (modelRef.endsWith('line-encoder')) {
|
|
3972
|
-
at(B(0.34, 0.05, 0.05, steel), 0.14, 1.28, 0)
|
|
3973
|
-
const disc = at(Cyl(0.16, 0.16, 0.05, dark), 0.32, 1.28, 0)
|
|
3974
|
-
disc.rotation.z = Math.PI / 2
|
|
3975
|
-
const halo = new THREE.Mesh(new THREE.TorusGeometry(0.1 * S, 0.015 * S, 10, 26), ledRing.material as THREE.Material)
|
|
3976
|
-
at(halo, 0.35, 1.28, 0)
|
|
3977
|
-
}
|
|
3978
|
-
else if (modelRef.endsWith('vision-cam')) {
|
|
3979
|
-
at(B(0.28, 0.2, 0.36, body2), 0, 1.3, 0)
|
|
3980
|
-
const lens = at(Cyl(0.07, 0.09, 0.14, dark), 0, 1.3, 0.24)
|
|
3981
|
-
lens.rotation.x = Math.PI / 2
|
|
3982
|
-
const iris = new THREE.Mesh(new THREE.TorusGeometry(0.075 * S, 0.012 * S, 10, 24), ledRing.material as THREE.Material)
|
|
3983
|
-
at(iris, 0, 1.3, 0.3)
|
|
3984
|
-
}
|
|
3985
|
-
else if (modelRef.endsWith('power-meter')) {
|
|
3986
|
-
at(B(0.32, 0.42, 0.18, body), 0, 1.3, 0)
|
|
3987
|
-
at(Cyl(0.012, 0.012, 0.42, steel, 8), 0.1, 1.7, 0)
|
|
3988
|
-
const tip = new THREE.Mesh(new THREE.SphereGeometry(0.028 * S, 10, 8), ledRing.material as THREE.Material)
|
|
3989
|
-
at(tip, 0.1, 1.92, 0)
|
|
3990
|
-
at(B(0.2, 0.06, 0.02, glow), 0, 1.42, 0.1)
|
|
3991
|
-
}
|
|
3992
|
-
return { group: g, ledRing }
|
|
3993
|
-
}
|
|
3994
|
-
|
|
3995
|
-
/** 数采/智控节点(程序化):模板分化传感网格 + **产线光晕**(同产线同色光环)+ 名牌 */
|
|
3996
|
-
private recreateDaqNode(t: DeviceTwinSync): void {
|
|
3997
|
-
const x = typeof t.posX === 'number' ? t.posX : WORLD_CX
|
|
3998
|
-
const z = typeof t.posZ === 'number' ? t.posZ : WORLD_CZ
|
|
3999
|
-
const root = new THREE.Group()
|
|
4000
|
-
root.position.set(x, GROUND_Y, z)
|
|
4001
|
-
root.rotation.y = typeof t.rotationY === 'number' ? t.rotationY * Math.PI / 180 : 0
|
|
4002
|
-
const { group: sensor, ledRing } = this.makeDaqMesh(t.modelRef)
|
|
4003
|
-
root.add(sensor)
|
|
4004
|
-
const sigRing = new THREE.Mesh(
|
|
4005
|
-
new THREE.RingGeometry(16, 21, 36),
|
|
4006
|
-
new THREE.MeshBasicMaterial({ color: 0x35e0a0, transparent: true, opacity: 0.5, side: THREE.DoubleSide, depthWrite: false }),
|
|
4007
|
-
)
|
|
4008
|
-
sigRing.rotation.x = -Math.PI / 2
|
|
4009
|
-
sigRing.position.y = 0.35
|
|
4010
|
-
root.add(sigRing)
|
|
4011
|
-
// 产线光晕:外圈加性光环(soft) + 内圈亮环;未分配 = 缺省绿
|
|
4012
|
-
const haloOuter = new THREE.Mesh(
|
|
4013
|
-
new THREE.RingGeometry(24, 34, 48),
|
|
4014
|
-
new THREE.MeshBasicMaterial({ color: 0x35e0a0, transparent: true, opacity: 0.16, side: THREE.DoubleSide, depthWrite: false, blending: THREE.AdditiveBlending }),
|
|
4015
|
-
)
|
|
4016
|
-
haloOuter.rotation.x = -Math.PI / 2
|
|
4017
|
-
haloOuter.position.y = 0.28
|
|
4018
|
-
root.add(haloOuter)
|
|
4019
|
-
const accent = t.lineColor || '#35e0a0'
|
|
4020
|
-
const label = this.makeLabel(t.name, x, 62, z, accent)
|
|
4021
|
-
this.scene.add(root)
|
|
4022
|
-
const node = new DeviceNode({
|
|
4023
|
-
twinId: t.id, name: t.name, modelRef: t.modelRef,
|
|
4024
|
-
root, holder: root, ring: sigRing, label,
|
|
4025
|
-
state: t.state ?? 'running', telemetry: t.telemetry ?? {},
|
|
4026
|
-
})
|
|
4027
|
-
node.host = this
|
|
4028
|
-
node.lineColor = t.lineColor ?? ''
|
|
4029
|
-
// 换产线/换色:重 tint 光环 + LED 环 + 名牌(applyTwin 检测 lineColor 变化时触发)
|
|
4030
|
-
node.applyLine = (color: string) => {
|
|
4031
|
-
const c = new THREE.Color(color || '#35e0a0')
|
|
4032
|
-
;(sigRing.material as THREE.MeshBasicMaterial).color.copy(c)
|
|
4033
|
-
;(haloOuter.material as THREE.MeshBasicMaterial).color.copy(c)
|
|
4034
|
-
;(ledRing.material as THREE.MeshBasicMaterial).color.copy(c).multiplyScalar(6) // 保持 HDR(LED 起晕)
|
|
4035
|
-
this.scene.remove(label)
|
|
4036
|
-
label.material.dispose()
|
|
4037
|
-
const fresh = this.makeLabel(node.name, root.position.x, 62, root.position.z, color || '#35e0a0')
|
|
4038
|
-
node.label = fresh
|
|
4039
|
-
this.scene.add(fresh)
|
|
4040
|
-
this.dirty = true
|
|
4041
|
-
}
|
|
4042
|
-
if (t.lineColor) node.applyLine(t.lineColor)
|
|
4043
|
-
node.updateRing()
|
|
4044
|
-
this.deviceNodes.set(t.id, node)
|
|
4045
|
-
this.daqLedRings.set(t.id, ledRing)
|
|
4046
|
-
this.registerScalable('device', t.id, root, typeof t.scale === 'number' ? t.scale : undefined)
|
|
4047
|
-
this.dirty = true
|
|
4048
|
-
}
|
|
4049
|
-
|
|
4050
|
-
/** 移除设备场景节点(服务端记录被删/本地重建清理) */
|
|
4051
|
-
private removeDeviceNode(id: string): void {
|
|
4052
|
-
const dev = this.deviceNodes.get(id)
|
|
4053
|
-
if (!dev) return
|
|
4054
|
-
this.scene.remove(dev.root)
|
|
4055
|
-
this.scene.remove(dev.label)
|
|
4056
|
-
this.disposeDeviceAssets(dev)
|
|
4057
|
-
if (this.selected?.kind === 'device' && this.selected.id === id) this.setSelected(null)
|
|
4058
|
-
this.scalables.delete(`device:${id}`)
|
|
4059
|
-
this.deviceNodes.delete(id)
|
|
4060
|
-
this.daqLedRings.delete(id)
|
|
4061
|
-
this.daqLinkSig = ''
|
|
4062
|
-
this.filmWebSig = ''
|
|
4063
|
-
this.dirty = true
|
|
4064
|
-
}
|
|
829
|
+
recreateDeviceNode(t: DeviceTwinSync): void { recreateDeviceNode(this, t) }
|
|
4065
830
|
|
|
4066
831
|
// ================================================================
|
|
4067
|
-
// 数采绑定链路 + 薄膜 web(设计稿 buildChanLine / rebuildWeb
|
|
832
|
+
// 数采绑定链路 + 薄膜 web(设计稿 buildChanLine / rebuildWeb 移植;
|
|
833
|
+
// 实现见 scene/town-scene3d-links.ts)
|
|
4068
834
|
// ================================================================
|
|
4069
835
|
|
|
4070
|
-
/** 设备顶端世界高度(holder 包围盒;链路/膜 web 的挂点)
|
|
4071
|
-
|
|
4072
|
-
* 设备贴地 y=0,高度只随缩放变化 → setModelScale/registerScalable 时失效即可。 */
|
|
4073
|
-
private deviceTopY(dev: DeviceNode): number {
|
|
4074
|
-
if (dev.topYCache != null) return dev.topYCache
|
|
4075
|
-
const box = new THREE.Box3().setFromObject(dev.holder)
|
|
4076
|
-
const y = Number.isFinite(box.max.y) ? Math.max(40, box.max.y) : 64
|
|
4077
|
-
dev.topYCache = y
|
|
4078
|
-
return y
|
|
4079
|
-
}
|
|
836
|
+
/** 设备顶端世界高度(holder 包围盒;链路/膜 web 的挂点) */
|
|
837
|
+
deviceTopY(dev: DeviceNode): number { return deviceTopY(dev) }
|
|
4080
838
|
|
|
4081
839
|
/** 同步数采→设备绑定链路(TownView 传入 [{daqId, deviceId}];端点移动时逐帧跟随重建) */
|
|
4082
|
-
syncDaqLinks(links: Array<{ daqId: string, deviceId: string }>): void {
|
|
4083
|
-
this.daqLinksWanted = links
|
|
4084
|
-
// 签名按"成功建链"的链路计算:节点未就绪被跳过的链路不计入 → 设备节点晚到时 syncDevices 末尾重仲裁补链
|
|
4085
|
-
const built = links.filter(l => this.deviceNodes.has(l.daqId) && this.deviceNodes.has(l.deviceId))
|
|
4086
|
-
const sig = built.map(l => `${l.daqId}>${l.deviceId}`).sort().join('|')
|
|
4087
|
-
if (sig === this.daqLinkSig) return
|
|
4088
|
-
this.daqLinkSig = sig
|
|
4089
|
-
for (const l of this.daqLinks) {
|
|
4090
|
-
this.daqLinkGroup.remove(l.line)
|
|
4091
|
-
this.daqLinkGroup.remove(l.pulse)
|
|
4092
|
-
l.line.geometry.dispose()
|
|
4093
|
-
}
|
|
4094
|
-
this.daqLinks = []
|
|
4095
|
-
for (const l of built) {
|
|
4096
|
-
const daq = this.deviceNodes.get(l.daqId)!
|
|
4097
|
-
const dev = this.deviceNodes.get(l.deviceId)!
|
|
4098
|
-
const a = new THREE.Vector3(daq.root.position.x, 42, daq.root.position.z)
|
|
4099
|
-
const b = new THREE.Vector3(dev.root.position.x, this.deviceTopY(dev) + 6, dev.root.position.z)
|
|
4100
|
-
const mid = a.clone().add(b).multiplyScalar(0.5)
|
|
4101
|
-
mid.y = Math.max(a.y, b.y) + 42
|
|
4102
|
-
const curve = new THREE.QuadraticBezierCurve3(a, mid, b)
|
|
4103
|
-
const geo = new THREE.BufferGeometry().setFromPoints(curve.getPoints(28))
|
|
4104
|
-
const line = new THREE.Line(geo, new THREE.LineDashedMaterial({ color: 0x35e0a0, transparent: true, opacity: 0.55, dashSize: 8, gapSize: 5 }))
|
|
4105
|
-
line.computeLineDistances()
|
|
4106
|
-
const pulse = new THREE.Mesh(new THREE.SphereGeometry(2.6, 10, 8), new THREE.MeshBasicMaterial({ color: 0x41c8f4 }))
|
|
4107
|
-
;(pulse.material as THREE.MeshBasicMaterial).color.multiplyScalar(5) // HDR:链路数据脉冲起晕
|
|
4108
|
-
this.daqLinkGroup.add(line, pulse)
|
|
4109
|
-
this.daqLinks.push({ ...l, line, pulse, curve, pt: Math.random(), ptSig: '' })
|
|
4110
|
-
}
|
|
4111
|
-
this.dirty = true
|
|
4112
|
-
}
|
|
840
|
+
syncDaqLinks(links: Array<{ daqId: string, deviceId: string }>): void { syncDaqLinks(this, links) }
|
|
4113
841
|
|
|
4114
842
|
/** 端点跟随:任一端点位移超阈值 → 重建该链路曲线(拖拽设备/数采时虚线实时跟随) */
|
|
4115
|
-
|
|
4116
|
-
private static readonly _lkA = new THREE.Vector3()
|
|
4117
|
-
private static readonly _lkB = new THREE.Vector3()
|
|
4118
|
-
private static readonly _lkM = new THREE.Vector3()
|
|
4119
|
-
|
|
4120
|
-
private refreshDaqLinks(): void {
|
|
4121
|
-
for (const l of this.daqLinks) {
|
|
4122
|
-
const daq = this.deviceNodes.get(l.daqId)
|
|
4123
|
-
const dev = this.deviceNodes.get(l.deviceId)
|
|
4124
|
-
if (!daq || !dev) continue
|
|
4125
|
-
const topY = this.deviceTopY(dev) + 6
|
|
4126
|
-
const sig = `${Math.round(daq.root.position.x)},${Math.round(daq.root.position.z)},${Math.round(dev.root.position.x)},${Math.round(dev.root.position.z)},${Math.round(topY)}`
|
|
4127
|
-
if (sig === l.ptSig) continue // 端点未动:曲线/geometry 不重建(脉冲仍逐帧走)
|
|
4128
|
-
l.ptSig = sig
|
|
4129
|
-
const a = TownScene3D._lkA.set(daq.root.position.x, 42, daq.root.position.z)
|
|
4130
|
-
const b = TownScene3D._lkB.set(dev.root.position.x, topY, dev.root.position.z)
|
|
4131
|
-
const mid = TownScene3D._lkM.copy(a).add(b).multiplyScalar(0.5)
|
|
4132
|
-
mid.y = Math.max(a.y, b.y) + 42
|
|
4133
|
-
l.curve.v0.copy(a)
|
|
4134
|
-
l.curve.v1.copy(mid)
|
|
4135
|
-
l.curve.v2.copy(b)
|
|
4136
|
-
const pts = l.curve.getPoints(28)
|
|
4137
|
-
l.line.geometry.setFromPoints(pts)
|
|
4138
|
-
l.line.computeLineDistances()
|
|
4139
|
-
}
|
|
4140
|
-
}
|
|
4141
|
-
|
|
4142
|
-
/** 产线设备型号识别(modelRef 含产线关键字 → 薄膜 web 连线成员) */
|
|
4143
|
-
private isLineDevice(modelRef: string): boolean {
|
|
4144
|
-
const ref = modelRef.toLowerCase()
|
|
4145
|
-
return /extruder|caster|mdo|tdo|winder/.test(ref)
|
|
4146
|
-
}
|
|
843
|
+
refreshDaqLinks(): void { refreshDaqLinks(this) }
|
|
4147
844
|
|
|
4148
845
|
/** 薄膜 web:按 X 序连接产线设备(挤出→流延→MD→TD→收卷),半透明膜面 —— 产线工艺连续性可视化 */
|
|
4149
|
-
|
|
4150
|
-
const list = [...this.deviceNodes.values()]
|
|
4151
|
-
.filter(d => this.isLineDevice(d.modelRef) && !d.modelRef.startsWith('daq-'))
|
|
4152
|
-
.sort((a, b) => a.root.position.x - b.root.position.x)
|
|
4153
|
-
const sig = list.map(d => `${d.twinId}:${Math.round(d.root.position.x)},${Math.round(d.root.position.z)}`).join('|')
|
|
4154
|
-
if (sig === this.filmWebSig) return
|
|
4155
|
-
this.filmWebSig = sig
|
|
4156
|
-
while (this.filmWebGroup.children.length) {
|
|
4157
|
-
const c = this.filmWebGroup.children[0] as THREE.Mesh
|
|
4158
|
-
this.filmWebGroup.remove(c)
|
|
4159
|
-
c.geometry.dispose()
|
|
4160
|
-
}
|
|
4161
|
-
if (!this.filmWebMat) {
|
|
4162
|
-
this.filmWebMat = new THREE.MeshStandardMaterial({
|
|
4163
|
-
color: 0xaad8ff, metalness: 0.1, roughness: 0.35, transparent: true, opacity: 0.28,
|
|
4164
|
-
emissive: 0x2b6b8f, emissiveIntensity: 0.25, side: THREE.DoubleSide, depthWrite: false,
|
|
4165
|
-
})
|
|
4166
|
-
}
|
|
4167
|
-
for (let i = 0; i < list.length - 1; i++) {
|
|
4168
|
-
const a = list[i]!.root.position
|
|
4169
|
-
const b = list[i + 1]!.root.position
|
|
4170
|
-
const len = Math.abs(b.x - a.x) - 110
|
|
4171
|
-
if (len < 30) continue
|
|
4172
|
-
const web = new THREE.Mesh(new THREE.BoxGeometry(len, 1.2, 46), this.filmWebMat)
|
|
4173
|
-
web.position.set((a.x + b.x) / 2, 48, (a.z + b.z) / 2)
|
|
4174
|
-
this.filmWebGroup.add(web)
|
|
4175
|
-
}
|
|
4176
|
-
this.dirty = true
|
|
4177
|
-
}
|
|
846
|
+
rebuildFilmWeb(): void { rebuildFilmWeb(this) }
|
|
4178
847
|
|
|
4179
848
|
// ================================================================
|
|
4180
849
|
// 生命周期 / 渲染循环
|
|
4181
850
|
// ================================================================
|
|
4182
851
|
|
|
4183
|
-
private loop(): void {
|
|
4184
|
-
const animate = () => {
|
|
4185
|
-
if (this.disposed) return
|
|
4186
|
-
this.raf = requestAnimationFrame(animate)
|
|
4187
|
-
this.rafCount += 1
|
|
4188
|
-
const rawDt = this.clock.getDelta()
|
|
4189
|
-
// 帧预算门控:数据消费与帧率解耦 —— WS 帧直写实时缓冲(消费层,不受此门控),
|
|
4190
|
-
// 渲染循环每帧只取「当前最新值」上屏(展示层);budget 0 = 不限制(用户可选 60/120/∞)。
|
|
4191
|
-
// 后台标签页 rAF 自动停摆;回前台 rawDt 巨大 → 钳制单步 ≤100ms 防动画跳变
|
|
4192
|
-
if (this.frameBudgetMs > 0) {
|
|
4193
|
-
this.frameAcc += rawDt * 1000
|
|
4194
|
-
if (this.frameAcc < this.frameBudgetMs - 0.5) return
|
|
4195
|
-
this.frameAcc = 0
|
|
4196
|
-
}
|
|
4197
|
-
const dt = Math.min(rawDt, 0.1)
|
|
4198
|
-
const t = this.clock.elapsedTime
|
|
4199
|
-
// 频道信息接收器:FIFO 逐条消费实时消息(每帧检查,展示期满取下一条)
|
|
4200
|
-
this.drainReceivers(performance.now())
|
|
4201
|
-
// 行为 FSM
|
|
4202
|
-
for (const asp of this.agents.values()) {
|
|
4203
|
-
if (asp.state !== 'stopped' && !asp.dragging) asp.update(dt)
|
|
4204
|
-
}
|
|
4205
|
-
// mixer 更新
|
|
4206
|
-
for (const asp of this.agents.values()) {
|
|
4207
|
-
if (asp.mixer) asp.mixer.update(dt)
|
|
4208
|
-
}
|
|
4209
|
-
// 频道信标缓转(顶标菱形旋转,中心定位销的生命感)
|
|
4210
|
-
for (const b of this.blocks.values()) b.beacon.rotation.y += dt * 0.4
|
|
4211
|
-
// 名字/气泡跟随 + 光环呼吸 + 逐帧动画(脉冲/光柱/缓动)
|
|
4212
|
-
for (const asp of this.agents.values()) {
|
|
4213
|
-
asp.nameSprite.position.set(asp.root.position.x, 48, asp.root.position.z)
|
|
4214
|
-
if (asp.bubble) {
|
|
4215
|
-
asp.bubble.position.set(asp.root.position.x, BUBBLE_Y + 22 + Math.max(0, asp.model.scale.y - 1) * 22, asp.root.position.z)
|
|
4216
|
-
}
|
|
4217
|
-
if (asp.auraMats.length) {
|
|
4218
|
-
const br = 0.8 + 0.2 * Math.sin(t * 2.2 + asp.auraPhase)
|
|
4219
|
-
for (const m of asp.auraMats) m.opacity = (m.userData.baseOp as number) * br
|
|
4220
|
-
}
|
|
4221
|
-
}
|
|
4222
|
-
// 设备节点:名牌跟随(状态环是 root 子节点自动跟随;颜色由 updateRing 在状态变化时刷新)+ 运行弧缓转
|
|
4223
|
-
for (const dev of this.deviceNodes.values()) {
|
|
4224
|
-
dev.label.position.set(dev.root.position.x, 60, dev.root.position.z)
|
|
4225
|
-
if (dev.arc?.visible) dev.arc.rotation.z += dt * 1.6
|
|
4226
|
-
}
|
|
4227
|
-
// 数采节点 LED 环:缓转 + 呼吸(绑定链路的信号生命感)
|
|
4228
|
-
for (const ring of this.daqLedRings.values()) {
|
|
4229
|
-
ring.rotation.z += dt * 1.2
|
|
4230
|
-
const s = 1 + Math.sin(t * 3.2) * 0.08
|
|
4231
|
-
ring.scale.setScalar(s)
|
|
4232
|
-
}
|
|
4233
|
-
// 绑定链路:端点位移时重建曲线(dirty 门控;脉冲仍逐帧行走)
|
|
4234
|
-
if (this.daqLinks.length) {
|
|
4235
|
-
if (this.dirty) this.refreshDaqLinks()
|
|
4236
|
-
for (const l of this.daqLinks) {
|
|
4237
|
-
l.pt = (l.pt + dt * 0.3) % 1
|
|
4238
|
-
l.pulse.position.copy(l.curve.getPoint(l.pt))
|
|
4239
|
-
}
|
|
4240
|
-
}
|
|
4241
|
-
// 薄膜 web:产线设备增删/移动时重建(dirty 门控;免每帧签名字符串分配)
|
|
4242
|
-
if (this.dirty) this.rebuildFilmWeb()
|
|
4243
|
-
// 选中高亮环:跟随选中目标(角色按用户缩放、设备按顶高定半径;慢转活性)
|
|
4244
|
-
if (this.selRing) {
|
|
4245
|
-
const sel = this.selected
|
|
4246
|
-
let sx = 0
|
|
4247
|
-
let sz = 0
|
|
4248
|
-
let sr = 0
|
|
4249
|
-
if (sel?.kind === 'agent') {
|
|
4250
|
-
const a = this.agents.get(sel.id)
|
|
4251
|
-
if (a) {
|
|
4252
|
-
sx = a.root.position.x
|
|
4253
|
-
sz = a.root.position.z
|
|
4254
|
-
sr = 46 * Math.max(0.6, a.model.scale.y)
|
|
4255
|
-
}
|
|
4256
|
-
}
|
|
4257
|
-
else if (sel?.kind === 'device') {
|
|
4258
|
-
const d = this.deviceNodes.get(sel.id)
|
|
4259
|
-
if (d) {
|
|
4260
|
-
sx = d.root.position.x
|
|
4261
|
-
sz = d.root.position.z
|
|
4262
|
-
sr = Math.max(34, this.deviceTopY(d) * 0.42)
|
|
4263
|
-
}
|
|
4264
|
-
}
|
|
4265
|
-
this.selRing.visible = sr > 0
|
|
4266
|
-
if (sr > 0) {
|
|
4267
|
-
this.selRing.position.set(sx, 0.5, sz)
|
|
4268
|
-
this.selRing.scale.setScalar(sr)
|
|
4269
|
-
this.selRing.rotation.z += dt * 0.9
|
|
4270
|
-
}
|
|
4271
|
-
}
|
|
4272
|
-
const anims = this.rafAnims
|
|
4273
|
-
this.rafAnims = []
|
|
4274
|
-
for (const f of anims) f()
|
|
4275
|
-
// 相机:围绕 camTarget 按 dolly 距离摆放(拖拽平移 camTarget,滚轮调 dolly,tween 平移 camTarget);
|
|
4276
|
-
// 基线 1250/760 + FOV55 = 全园区电影化 2.5D 框景;穹顶随镜头平移(无限视野观感)
|
|
4277
|
-
// 指数阻尼(帧率无关:1-e^(-λ·dt);60fps 下与旧每帧系数 0.12/0.08 等效,高刷屏不再加速)
|
|
4278
|
-
const kDolly = 1 - Math.exp(-dt * 7.2)
|
|
4279
|
-
if (this.autoDolly !== null) {
|
|
4280
|
-
this.dolly += (this.autoDolly - this.dolly) * kDolly
|
|
4281
|
-
if (Math.abs(this.autoDolly - this.dolly) < 0.01) this.autoDolly = null
|
|
4282
|
-
}
|
|
4283
|
-
const kView = 1 - Math.exp(-dt * 4.8)
|
|
4284
|
-
for (const k of ['yaw', 'pitch', 'radius'] as const) {
|
|
4285
|
-
this.viewCur[k] += (this.viewTarget[k] - this.viewCur[k]) * kView
|
|
4286
|
-
}
|
|
4287
|
-
// 自动环绕(设计稿 tOrbit):缓转方位角(dt 基 ≈ 24s/圈;指针按住场景时暂停)
|
|
4288
|
-
if (this.autoOrbit && !this.pointerDrag) {
|
|
4289
|
-
this.viewCur.yaw -= dt * 0.156
|
|
4290
|
-
this.viewTarget.yaw = this.viewCur.yaw
|
|
4291
|
-
}
|
|
4292
|
-
const r = this.viewCur.radius * this.dolly
|
|
4293
|
-
const cp = Math.cos(this.viewCur.pitch)
|
|
4294
|
-
this.camera.position.set(
|
|
4295
|
-
this.camTarget.x + r * cp * Math.sin(this.viewCur.yaw),
|
|
4296
|
-
20 + r * Math.sin(this.viewCur.pitch),
|
|
4297
|
-
this.camTarget.z + r * cp * Math.cos(this.viewCur.yaw),
|
|
4298
|
-
)
|
|
4299
|
-
this.camera.lookAt(this.camTarget.x, 20, this.camTarget.z)
|
|
4300
|
-
// 无级缩放:近/远裁剪面随 dolly 伸缩(贴脸到星野全程不裁剪)
|
|
4301
|
-
const near = Math.max(0.05, 1.2 * this.dolly)
|
|
4302
|
-
const far = 16000 * this.dolly
|
|
4303
|
-
if (Math.abs(this.camera.near - near) > 0.005 || Math.abs(this.camera.far - far) > 1) {
|
|
4304
|
-
this.camera.near = near
|
|
4305
|
-
this.camera.far = far
|
|
4306
|
-
this.camera.updateProjectionMatrix()
|
|
4307
|
-
}
|
|
4308
|
-
if (this.skyDome) {
|
|
4309
|
-
this.skyDome.position.set(this.camTarget.x, 0, this.camTarget.z)
|
|
4310
|
-
// 穹顶随 dolly 缩放:高空拉远时相机始终在穹内,无硬地平线
|
|
4311
|
-
this.skyDome.scale.setScalar(Math.max(1, this.dolly * 1.3))
|
|
4312
|
-
}
|
|
4313
|
-
// 地面跟随镜头滑动(重复纹理 = 无限地面;高空不见地面边缘);
|
|
4314
|
-
// UV 偏移反向补偿平面位移 → 网格钉死世界坐标(否则纹理跟平面一起滑,平移时有"冰面漂移"感)
|
|
4315
|
-
this.ground.position.set(this.camTarget.x, 0, this.camTarget.z)
|
|
4316
|
-
const gmap = (this.ground.material as THREE.MeshStandardMaterial).map
|
|
4317
|
-
if (gmap) {
|
|
4318
|
-
gmap.offset.set(
|
|
4319
|
-
this.camTarget.x / ((WORLD_W * 16) / gmap.repeat.x),
|
|
4320
|
-
-this.camTarget.z / ((WORLD_H * 16) / gmap.repeat.y),
|
|
4321
|
-
)
|
|
4322
|
-
}
|
|
4323
|
-
// 雾距/阴影范围随 dolly 缩放:任意 zoom 层级下取景内容都不被雾吞、投影不消失
|
|
4324
|
-
const fog = this.scene.fog
|
|
4325
|
-
if (fog instanceof THREE.Fog) {
|
|
4326
|
-
fog.near = 1400 * this.dolly
|
|
4327
|
-
fog.far = 8800 * this.dolly
|
|
4328
|
-
}
|
|
4329
|
-
const sc = this.keyLight.shadow.camera
|
|
4330
|
-
const ext = 1600 * Math.max(1, this.dolly)
|
|
4331
|
-
if (Math.abs(sc.right - ext) > 1) {
|
|
4332
|
-
sc.left = -ext
|
|
4333
|
-
sc.right = ext
|
|
4334
|
-
sc.top = 2300 * Math.max(1, this.dolly)
|
|
4335
|
-
sc.bottom = -2300 * Math.max(1, this.dolly)
|
|
4336
|
-
sc.updateProjectionMatrix()
|
|
4337
|
-
this.renderer.shadowMap.needsUpdate = true
|
|
4338
|
-
}
|
|
4339
|
-
// 阴影按需:内容变化(dirty)或有动画角色(mixer 驱动蒙皮位移)才重绘阴影贴图
|
|
4340
|
-
let shadowAnimated = false
|
|
4341
|
-
for (const asp of this.agents.values()) {
|
|
4342
|
-
if (asp.mixer) {
|
|
4343
|
-
shadowAnimated = true
|
|
4344
|
-
break
|
|
4345
|
-
}
|
|
4346
|
-
}
|
|
4347
|
-
this.renderer.shadowMap.needsUpdate = this.dirty || shadowAnimated
|
|
4348
|
-
// 后处理管线出图(RenderPass → Bloom → OutputPass;替代直渲染)
|
|
4349
|
-
this.renderer.info.reset()
|
|
4350
|
-
this.composer.render(dt)
|
|
4351
|
-
this.dirty = false
|
|
4352
|
-
// FPS(墙钟 1000ms 窗口:performance.now 真实时钟;原 dt 累计在长任务饿死 rAF 时虚高)
|
|
4353
|
-
this.frameCount += 1
|
|
4354
|
-
const wallNow = performance.now()
|
|
4355
|
-
if (this.fpsWinT0 === 0) this.fpsWinT0 = wallNow
|
|
4356
|
-
if (wallNow - this.fpsWinT0 >= 1000) {
|
|
4357
|
-
const fps = Math.round((this.frameCount * 1000) / (wallNow - this.fpsWinT0))
|
|
4358
|
-
this.emit('fps', fps)
|
|
4359
|
-
this.adaptQuality(fps)
|
|
4360
|
-
// 仪表化快照(1Hz;e2e/性能回归经 window.__townStats 读取,免再猜渲染真相)
|
|
4361
|
-
const info = this.renderer.info
|
|
4362
|
-
;(globalThis as typeof globalThis & { __townStats?: Record<string, number> }).__townStats = {
|
|
4363
|
-
fps,
|
|
4364
|
-
rafHz: this.rafCount,
|
|
4365
|
-
drawCalls: info.render.calls,
|
|
4366
|
-
triangles: info.render.triangles,
|
|
4367
|
-
geometries: info.memory.geometries,
|
|
4368
|
-
textures: info.memory.textures,
|
|
4369
|
-
tier: this.qTier,
|
|
4370
|
-
dpr: +this.renderer.getPixelRatio().toFixed(2),
|
|
4371
|
-
frameBudgetMs: +this.frameBudgetMs.toFixed(2),
|
|
4372
|
-
agents: this.agents.size,
|
|
4373
|
-
devices: this.deviceNodes.size,
|
|
4374
|
-
}
|
|
4375
|
-
this.frameCount = 0
|
|
4376
|
-
this.rafCount = 0
|
|
4377
|
-
this.fpsWinT0 = wallNow
|
|
4378
|
-
}
|
|
4379
|
-
}
|
|
4380
|
-
animate()
|
|
4381
|
-
}
|
|
4382
|
-
|
|
4383
|
-
/** 质量阶梯:实测 fps 连续 2s < 17 → 降一档;连续 4s ≥ 27(帧预算附近)→ 升一档。
|
|
4384
|
-
* 档位 = dprScale × 阴影贴图 × Bloom 开关的组合(Bloom+2048² 阴影是两大单项开销,
|
|
4385
|
-
* 重活机器 11fps = 每帧 ~90ms 主线程阻塞)。统一阶梯,避免多套自适应互相打架。 */
|
|
4386
|
-
private static readonly Q_TIERS = [
|
|
4387
|
-
{ scale: 1.0, shadow: 2048, bloom: true },
|
|
4388
|
-
{ scale: 0.8, shadow: 2048, bloom: true },
|
|
4389
|
-
{ scale: 0.65, shadow: 1024, bloom: true },
|
|
4390
|
-
{ scale: 0.55, shadow: 1024, bloom: false },
|
|
4391
|
-
] as const
|
|
852
|
+
private loop(): void { startRenderLoop(this) }
|
|
4392
853
|
|
|
4393
|
-
|
|
854
|
+
qTier = 0
|
|
4394
855
|
|
|
4395
|
-
|
|
856
|
+
qLowStreak = 0
|
|
4396
857
|
|
|
4397
|
-
|
|
858
|
+
qHighStreak = 0
|
|
4398
859
|
|
|
4399
860
|
/** 画质模式:auto = 质量阶梯自适应;manual 三档由用户固定(渲染配置不再自动变动) */
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
/** 用户画质三档(超清 = WebGL 全配置:满 DPR/2048² 阴影/Bloom/最大各向异性) */
|
|
4403
|
-
private static readonly USER_QUALITY = {
|
|
4404
|
-
ultra: { scale: 1.0, shadow: 2048, bloom: true },
|
|
4405
|
-
hd: { scale: 0.8, shadow: 2048, bloom: true },
|
|
4406
|
-
normal: { scale: 0.6, shadow: 1024, bloom: true },
|
|
4407
|
-
} as const
|
|
4408
|
-
|
|
4409
|
-
setQualityMode(mode: 'auto' | 'normal' | 'hd' | 'ultra'): void {
|
|
4410
|
-
this.qualityMode = mode
|
|
4411
|
-
if (mode === 'auto') {
|
|
4412
|
-
this.applyQuality()
|
|
4413
|
-
return
|
|
4414
|
-
}
|
|
4415
|
-
const q = TownScene3D.USER_QUALITY[mode]
|
|
4416
|
-
this.applyPixelRatio(Math.max(0.5, this.baseDpr * q.scale))
|
|
4417
|
-
const key = this.keyLight
|
|
4418
|
-
if ((key.shadow.mapSize.x ?? 0) !== q.shadow) {
|
|
4419
|
-
key.shadow.mapSize.set(q.shadow, q.shadow)
|
|
4420
|
-
key.shadow.map?.dispose()
|
|
4421
|
-
key.shadow.map = null
|
|
4422
|
-
this.renderer.shadowMap.needsUpdate = true
|
|
4423
|
-
}
|
|
4424
|
-
const bloomPass = this.composer.passes.find(p => p instanceof UnrealBloomPass)
|
|
4425
|
-
if (bloomPass) bloomPass.enabled = q.bloom
|
|
4426
|
-
}
|
|
861
|
+
qualityMode: 'auto' | 'normal' | 'hd' | 'ultra' = 'auto'
|
|
4427
862
|
|
|
4428
|
-
|
|
4429
|
-
if (this.qualityMode !== 'auto') return
|
|
4430
|
-
const maxTier = TownScene3D.Q_TIERS.length - 1
|
|
4431
|
-
if (fps < 17 && this.qTier < maxTier) {
|
|
4432
|
-
if (++this.qLowStreak >= 2) {
|
|
4433
|
-
this.qTier++
|
|
4434
|
-
this.applyQuality()
|
|
4435
|
-
this.qLowStreak = 0
|
|
4436
|
-
this.qHighStreak = 0
|
|
4437
|
-
}
|
|
4438
|
-
}
|
|
4439
|
-
else if (fps >= 27 && this.qTier > 0) {
|
|
4440
|
-
if (++this.qHighStreak >= 4) {
|
|
4441
|
-
this.qTier--
|
|
4442
|
-
this.applyQuality()
|
|
4443
|
-
this.qHighStreak = 0
|
|
4444
|
-
this.qLowStreak = 0
|
|
4445
|
-
}
|
|
4446
|
-
}
|
|
4447
|
-
else {
|
|
4448
|
-
this.qLowStreak = 0
|
|
4449
|
-
this.qHighStreak = 0
|
|
4450
|
-
}
|
|
4451
|
-
}
|
|
863
|
+
setQualityMode(mode: 'auto' | 'normal' | 'hd' | 'ultra'): void { setQualityMode(this, mode) }
|
|
4452
864
|
|
|
4453
|
-
|
|
4454
|
-
const tier = TownScene3D.Q_TIERS[this.qTier]!
|
|
4455
|
-
this.applyPixelRatio(Math.max(0.5, this.baseDpr * tier.scale))
|
|
4456
|
-
// 阴影贴图缩容:置空 map 让 three 按新尺寸重分配(渲染循环按需重绘阴影)
|
|
4457
|
-
const key = this.keyLight
|
|
4458
|
-
if ((key.shadow.mapSize.x ?? 0) !== tier.shadow) {
|
|
4459
|
-
key.shadow.mapSize.set(tier.shadow, tier.shadow)
|
|
4460
|
-
key.shadow.map?.dispose()
|
|
4461
|
-
key.shadow.map = null
|
|
4462
|
-
this.renderer.shadowMap.needsUpdate = true
|
|
4463
|
-
}
|
|
4464
|
-
// Bloom 开关:Pass.enabled=false 跳过该 pass(RenderPass→OutputPass 直通)
|
|
4465
|
-
const bloomPass = this.composer.passes.find(p => p instanceof UnrealBloomPass)
|
|
4466
|
-
if (bloomPass) bloomPass.enabled = tier.bloom
|
|
4467
|
-
}
|
|
865
|
+
adaptQuality(fps: number): void { adaptQuality(this, fps) }
|
|
4468
866
|
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
const w = this.el.clientWidth || 1100
|
|
4473
|
-
const h = this.el.clientHeight || 700
|
|
4474
|
-
this.renderer.setSize(w, h)
|
|
4475
|
-
this.composer.setSize(w, h)
|
|
4476
|
-
}
|
|
867
|
+
applyQuality(): void { applyQuality(this) }
|
|
868
|
+
|
|
869
|
+
applyPixelRatio(next: number): void { applyPixelRatio(this, next) }
|
|
4477
870
|
|
|
4478
871
|
// ================================================================
|
|
4479
872
|
// 实例本地资源释放(防长会话 GPU 内存只涨不跌)
|
|
@@ -4484,98 +877,17 @@ export class TownScene3D {
|
|
|
4484
877
|
// 名牌/气泡的 CanvasTexture(材质 map)、状态环/链路等自建几何与材质。
|
|
4485
878
|
|
|
4486
879
|
/** 释放对象树内所有 Canvas 纹理(名牌/气泡 sprite 的独占资源) */
|
|
4487
|
-
|
|
4488
|
-
if (!root) return
|
|
4489
|
-
root.traverse((o) => {
|
|
4490
|
-
const mesh = o as THREE.Mesh
|
|
4491
|
-
const mat = mesh.material as THREE.Material | THREE.Material[] | undefined
|
|
4492
|
-
const mats = Array.isArray(mat) ? mat : mat ? [mat] : []
|
|
4493
|
-
for (const m of mats) {
|
|
4494
|
-
const std = m as THREE.Material & { map?: THREE.Texture | null }
|
|
4495
|
-
if (std.map && std.map.image instanceof HTMLCanvasElement) std.map.dispose()
|
|
4496
|
-
m.dispose()
|
|
4497
|
-
}
|
|
4498
|
-
})
|
|
4499
|
-
}
|
|
880
|
+
disposeCanvasTextures(root: THREE.Object3D | null): void { disposeCanvasTextures(root) }
|
|
4500
881
|
|
|
4501
882
|
/** 释放单个设备节点的实例资源(状态环 + 运行弧 + LED 环 + 名牌) */
|
|
4502
|
-
|
|
4503
|
-
dev.ring.geometry.dispose()
|
|
4504
|
-
const rmat = dev.ring.material as THREE.Material & { map?: THREE.Texture | null }
|
|
4505
|
-
rmat.dispose()
|
|
4506
|
-
if (dev.arc) {
|
|
4507
|
-
dev.arc.geometry.dispose()
|
|
4508
|
-
;(dev.arc.material as THREE.Material).dispose()
|
|
4509
|
-
}
|
|
4510
|
-
this.disposeCanvasTextures(dev.label)
|
|
4511
|
-
}
|
|
883
|
+
disposeDeviceAssets(dev: DeviceNode): void { disposeDeviceAssets(dev) }
|
|
4512
884
|
|
|
4513
885
|
/** 释放单个 agent 的实例资源(名牌/气泡纹理 + 光环 + 活动范围线几何;GLB 克隆不动) */
|
|
4514
|
-
|
|
4515
|
-
this.disposeCanvasTextures(a.nameSprite ?? null)
|
|
4516
|
-
this.disposeCanvasTextures(a.bubble ?? null)
|
|
4517
|
-
a.rangeLine?.geometry.dispose()
|
|
4518
|
-
if (a.aura) {
|
|
4519
|
-
a.aura.traverse((o) => {
|
|
4520
|
-
const m = o as THREE.Mesh
|
|
4521
|
-
if (m.isMesh) {
|
|
4522
|
-
m.geometry.dispose()
|
|
4523
|
-
;(m.material as THREE.Material).dispose()
|
|
4524
|
-
}
|
|
4525
|
-
})
|
|
4526
|
-
a.aura = null
|
|
4527
|
-
}
|
|
4528
|
-
}
|
|
886
|
+
disposeAgentAssets(a: { nameSprite?: THREE.Sprite | null, bubble?: THREE.Sprite | null, rangeLine?: THREE.Line | null, aura?: THREE.Group | null }): void { disposeAgentAssets(a) }
|
|
4529
887
|
|
|
4530
888
|
/** 重置全部(rebuild 用) */
|
|
4531
|
-
|
|
4532
|
-
for (const a of this.agents.values()) {
|
|
4533
|
-
this.scene.remove(a.root)
|
|
4534
|
-
if (a.nameSprite) this.scene.remove(a.nameSprite)
|
|
4535
|
-
if (a.bubble) this.scene.remove(a.bubble)
|
|
4536
|
-
if (a.rangeLine) this.scene.remove(a.rangeLine)
|
|
4537
|
-
this.disposeAgentAssets(a)
|
|
4538
|
-
}
|
|
4539
|
-
for (const b of this.blocks.values()) {
|
|
4540
|
-
this.scene.remove(b.platform)
|
|
4541
|
-
this.scene.remove(b.padRing)
|
|
4542
|
-
this.scene.remove(b.beacon)
|
|
4543
|
-
this.scene.remove(b.boundary)
|
|
4544
|
-
this.scene.remove(b.label)
|
|
4545
|
-
this.disposeCanvasTextures(b.label)
|
|
4546
|
-
}
|
|
4547
|
-
for (const dev of this.deviceNodes.values()) {
|
|
4548
|
-
this.scene.remove(dev.root)
|
|
4549
|
-
this.scene.remove(dev.label)
|
|
4550
|
-
this.disposeDeviceAssets(dev)
|
|
4551
|
-
}
|
|
4552
|
-
this.deviceNodes.clear()
|
|
4553
|
-
this.scalables.clear()
|
|
4554
|
-
this.receivers.clear()
|
|
4555
|
-
this.pointerDrag = null
|
|
4556
|
-
this.cancelRangeDraw()
|
|
4557
|
-
this.selected = null
|
|
4558
|
-
this.selectedChannel = null
|
|
4559
|
-
for (const hl of this.resizeHandles) hl.mesh.visible = false
|
|
4560
|
-
for (const hl of this.agentRangeHandles) hl.mesh.visible = false
|
|
4561
|
-
this.agents.clear()
|
|
4562
|
-
this.blocks.clear()
|
|
4563
|
-
}
|
|
889
|
+
resetAll(): void { resetScene(this) }
|
|
4564
890
|
|
|
4565
891
|
/** 销毁(卸载时由 TownView 调用) */
|
|
4566
|
-
dispose(): void {
|
|
4567
|
-
this.disposed = true
|
|
4568
|
-
cancelAnimationFrame(this.raf)
|
|
4569
|
-
delete (globalThis as { __townScene3d?: unknown }).__townScene3d
|
|
4570
|
-
for (const t of this.pendingSaveTimers.values()) clearTimeout(t)
|
|
4571
|
-
this.pendingSaveTimers.clear()
|
|
4572
|
-
this.receivers.clear()
|
|
4573
|
-
this.resizeOb?.disconnect()
|
|
4574
|
-
this.resizeOb = null
|
|
4575
|
-
this.tControls?.dispose()
|
|
4576
|
-
this.tControls = null
|
|
4577
|
-
this.composer?.dispose()
|
|
4578
|
-
this.renderer.dispose()
|
|
4579
|
-
if (this.renderer.domElement.parentElement === this.el) this.renderer.domElement.remove()
|
|
4580
|
-
}
|
|
892
|
+
dispose(): void { disposeScene(this) }
|
|
4581
893
|
}
|