newIndex.vue 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932
  1. <!-- 模型地图 -->
  2. <template>
  3. <div>
  4. <div class="canvasMap" ref="container"></div>
  5. </div>
  6. </template>
  7. <script>
  8. import * as THREE from 'three';
  9. import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
  10. import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
  11. import TWEEN from 'tween.js';
  12. import { CSS2DRenderer, CSS2DObject, } from "three/examples/jsm/renderers/CSS2DRenderer";
  13. export default {
  14. data() {
  15. return {
  16. animationId: null, // 存储动画帧ID
  17. model:null,
  18. rotateType:true,
  19. subModels: [],
  20. houses:[],
  21. textures: [], // 预加载的贴图选项
  22. scene: null,
  23. camera: null,
  24. renderer: null,
  25. controls: null,
  26. labelRenderer:null,
  27. // initialCameraPosition: new THREE.Vector3(0.265357272970017,1.0753467424793826,0.29965000584249346),//旋转-距离-俯角
  28. initialCameraPosition: new THREE.Vector3(0.5,0.60,0.5),//旋转-距离-俯角
  29. /*
  30. {"x":0.2509298883560245,"y":1.2088432847403026,"z":0.19985043873282318}
  31. {"x":0.265357272970017,"y":1.0753467424793826,"z":0.29965000584249346}
  32. */
  33. initialControlsTarget: new THREE.Vector3(0, 0, 0),
  34. /*本地管理数据*/
  35. modelBuildingList:[
  36. {school:'B', name:'5号楼', buildId:'101093843117592833',centerValue:'',valueList:[],alarmType:false,},
  37. {school:'B', name:'6B教学楼', buildId:'101093843117592815',centerValue:'',valueList:[],alarmType:false,},
  38. {school:'B', name:'中心实验楼', buildId:'101093843117592829',centerValue:'',valueList:[],alarmType:false,},
  39. {school:'B', name:'信息工程学院教学楼', buildId:'101093843117592812',centerValue:'101093843117592812',valueList:['101093843117592812'],alarmType:false,},
  40. {school:'B', name:'养虫楼', buildId:'101093843117592805',centerValue:'',valueList:[],alarmType:false,},
  41. {school:'B', name:'农业机械实验室平房', buildId:'101093843117592807',centerValue:'',valueList:[],alarmType:false,},
  42. {school:'B', name:'农药研究所', buildId:'101093843117592804',centerValue:'101093843117592804',valueList:['101093843117592804'],alarmType:false,},
  43. {school:'B', name:'动物中心B座', buildId:'101093843117592785',centerValue:'',valueList:[],alarmType:false,},
  44. {school:'B', name:'动物中心实验楼A', buildId:'101093843117592765',centerValue:'101093843117592765',valueList:['101093843117592765'],alarmType:false,},
  45. {school:'B', name:'动物中心实验楼B', buildId:'101093843117592764',centerValue:'101093843117592764',valueList:['101093843117592764'],alarmType:false,},
  46. {school:'B', name:'动物科技学院楼', buildId:'101093843117592831',centerValue:'',valueList:[],alarmType:false,},
  47. {school:'B', name:'动科楼', buildId:'101093843117592832',centerValue:'101093843117592832',valueList:['101093843117592832'],alarmType:false,},
  48. {school:'B', name:'北1号教学楼', buildId:'101093843117592802',centerValue:'101093843117592802',valueList:['101093843117592802'],alarmType:false,},
  49. {school:'B', name:'北2号教学楼', buildId:'101093843117592813',centerValue:'101093843117592813',valueList:['101093843117592813'],alarmType:false,},
  50. {school:'B', name:'北3号教学楼', buildId:'101093843117592782',centerValue:'101093843117592782',valueList:['101093843117592782'],alarmType:false,},
  51. {school:'B', name:'北4号教学楼', buildId:'101093843117592781',centerValue:'101093843117592781',valueList:['101093843117592781'],alarmType:false,},
  52. {school:'B', name:'北5号教学楼', buildId:'101093843117592783',centerValue:'101093843117592833',valueList:['101093843117592833'],alarmType:false,},
  53. {school:'B', name:'北6号教学楼', buildId:'101093843117592814',centerValue:'101093843117592814',valueList:['101093843117592814'],alarmType:false,},
  54. {school:'B', name:'北7号教学楼', buildId:'101093843117592816',centerValue:'101093843117592816',valueList:['101093843117592816'],alarmType:false,},
  55. {school:'B', name:'危化品服务中心', buildId:'101093843117592826',centerValue:'101093843117592826-03',valueList:['101093843117592826-01','101093843117592826-02','101093843117592826-03','101093843117592826-04','101093843117592826-05'],alarmType:false,},
  56. {school:'B', name:'原农一站办公楼', buildId:'101093843117592811',centerValue:'',valueList:[],alarmType:false,},
  57. {school:'B', name:'工程训练中心楼', buildId:'101093843117592808',centerValue:'',valueList:[],alarmType:false,},
  58. {school:'B', name:'教学办公楼', buildId:'101093843117592806',centerValue:'',valueList:[],alarmType:false,},
  59. {school:'B', name:'未知实验室 ',buildId:'1855791848753147906',centerValue:'',valueList:[],alarmType:false,},
  60. {school:'B', name:'机械及液压实验室平房', buildId:'101093843117592810',centerValue:'',valueList:[],alarmType:false,},
  61. {school:'B', name:'机电锻铸车间', buildId:'101093843117592809',centerValue:'101093843117592809',valueList:['101093843117592809'],alarmType:false,},
  62. {school:'B', name:'水利与建筑工程学院', buildId:'101093843117592788',centerValue:'',valueList:[],alarmType:false,},
  63. {school:'B', name:'水利与建筑工程学院A', buildId:'101093843117592766',centerValue:'',valueList:[],alarmType:false,},
  64. {school:'B', name:'水利与建筑工程学院B', buildId:'101093843117592768',centerValue:'',valueList:[],alarmType:false,},
  65. {school:'B', name:'水利与建筑工程学院C', buildId:'101093843117592769',centerValue:'',valueList:[],alarmType:false,},
  66. {school:'B', name:'水利与建筑工程学院D', buildId:'101093843117592767',centerValue:'',valueList:[],alarmType:false,},
  67. {school:'B', name:'水工厅', buildId:'1871736666456633346',centerValue:'1871736666456633346',valueList:['1871736666456633346'],alarmType:false,},
  68. {school:'B', name:'水工水力学实验大厅', buildId:'1871729400227606529',centerValue:'1871729400227606529-01',valueList:['1871729400227606529-01','1871729400227606529-02'],alarmType:false,},
  69. {school:'B', name:'理科综合实验楼A', buildId:'101093843117592774',centerValue:'101093843117592774',valueList:['101093843117592774'],alarmType:false,},
  70. {school:'B', name:'理科综合实验楼B', buildId:'101093843117592779',centerValue:'',valueList:[],alarmType:false,},
  71. {school:'B', name:'理科综合实验楼C', buildId:'101093843117592775',centerValue:'',valueList:[],alarmType:false,},
  72. {school:'B', name:'理科综合实验楼D', buildId:'101093843117592776',centerValue:'',valueList:[],alarmType:false,},
  73. {school:'B', name:'理科综合实验楼E', buildId:'101093843117592780',centerValue:'',valueList:[],alarmType:false,},
  74. {school:'B', name:'老昆虫博物馆', buildId:'101093843117592786',centerValue:'101093843117592786',valueList:['101093843117592786'],alarmType:false,},
  75. {school:'B', name:'食品楼', buildId:'101093843117592789',centerValue:'101093843117592789',valueList:['101093843117592789'],alarmType:false,},
  76. {school:'B', name:'食品楼A', buildId:'101093843117592771',centerValue:'',valueList:[],alarmType:false,},
  77. {school:'B', name:'食品楼B', buildId:'101093843117592770',centerValue:'',valueList:[],alarmType:false,},
  78. {school:'B', name:'食品楼C', buildId:'101093843117592772',centerValue:'',valueList:[],alarmType:false,},
  79. {school:'B', name:'食品楼D', buildId:'101093843117592773',centerValue:'',valueList:[],alarmType:false,},
  80. {school:'N', name:'农科楼(农学院)', buildId:'101084081109864872',centerValue:'101084081109864872',valueList:['101084081109864872'],alarmType:false,},
  81. {school:'N', name:'农科楼(园艺学院)', buildId:'101084081109864874',centerValue:'',valueList:[],alarmType:false,},
  82. {school:'N', name:'农科楼(植物保护学院)', buildId:'101084081109864873',centerValue:'',valueList:[],alarmType:false,},
  83. {school:'N', name:'农科楼(资源环境学院)', buildId:'101093843117592803',centerValue:'',valueList:[],alarmType:false,},
  84. {school:'N', name:'农科院子校教学楼(初中)', buildId:'101093843117592823',centerValue:'',valueList:[],alarmType:false,},
  85. {school:'N', name:'南2号教学楼', buildId:'101093843117592817',centerValue:'101093843117592817',valueList:['101093843117592817'],alarmType:false,},
  86. {school:'N', name:'南3号教学楼', buildId:'101093843117592820',centerValue:'101093843117592820',valueList:['101093843117592820'],alarmType:false,},
  87. {school:'N', name:'南校子校楼', buildId:'101093843117592800',centerValue:'',valueList:[],alarmType:false,},
  88. {school:'N', name:'南校水保楼', buildId:'101093843117592801',centerValue:'',valueList:[],alarmType:false,},
  89. {school:'N', name:'家畜生物学重点实验室楼', buildId:'101093843117592787',centerValue:'101093843117592787',valueList:['101093843117592787'],alarmType:false,},
  90. {school:'N', name:'木艺坊', buildId:'101093843117592819',centerValue:'101093843117592819',valueList:['101093843117592819'],alarmType:false,},
  91. {school:'N', name:'林学院实验楼', buildId:'101093843117592818',centerValue:'',valueList:[],alarmType:false,},
  92. {school:'N', name:'科研主楼', buildId:'101093843117592784',centerValue:'101093843117592784',valueList:['101093843117592784'],alarmType:false,},
  93. {school:'N', name:'科研楼', buildId:'101093843117592822',centerValue:'',valueList:[],alarmType:false,},
  94. {school:'N', name:'经管园林楼(文科楼)A', buildId:'101093843117592777',centerValue:'101093843117592777',valueList:['101093843117592777'],alarmType:false,},
  95. {school:'N', name:'经管园林楼(文科楼)C', buildId:'101093843117592778',centerValue:'',valueList:[],alarmType:false,},
  96. {school:'N', name:'草业与草原楼', buildId:'101093843117592825',centerValue:'101093843117592825',valueList:['101093843117592825'],alarmType:false,},
  97. {school:'N', name:'林学院实验楼', buildId:'101093843117592821',centerValue:'101093843117592821',valueList:['101093843117592821'],alarmType:false,},
  98. {school:'旱研院校区', name:'农一站科研楼', buildId:'101093843117592827',centerValue:'',valueList:[],alarmType:false,},
  99. {school:'旱研院校区', name:'农机一站平房', buildId:'101093843117592828',centerValue:'',valueList:[],alarmType:false,},
  100. {school:'校外', name:'农三站科研办公楼', buildId:'101093843117592790',centerValue:'',valueList:[],alarmType:false,},
  101. {school:'校外', name:'器材楼', buildId:'101093843117592792',centerValue:'',valueList:[],alarmType:false,},
  102. {school:'水保所校区', name:'人工降雨实验大厅', buildId:'101093843117592793',centerValue:'',valueList:[],alarmType:false,},
  103. {school:'水保所校区', name:'南校区农科楼', buildId:'101093843117592799',centerValue:'',valueList:[],alarmType:false,},
  104. {school:'水保所校区', name:'国家重点实验室西楼', buildId:'101093843117592798',centerValue:'',valueList:[],alarmType:false,},
  105. {school:'水保所校区', name:'水保所人工干旱环境气候室工程', buildId:'101093843117592795',centerValue:'',valueList:[],alarmType:false,},
  106. {school:'水保所校区', name:'水保所国家重点实验室楼', buildId:'101093843117592794',centerValue:'',valueList:[],alarmType:false,},
  107. {school:'水保所校区', name:'水保所科研大楼', buildId:'101093843117592797',centerValue:'',valueList:[],alarmType:false,},
  108. {school:'水保所校区', name:'水保所西区科研楼', buildId:'101093843117592796',centerValue:'',valueList:[],alarmType:false,},
  109. {school:'老附中校区', name:'附中实验楼', buildId:'101093843117592830',centerValue:'',valueList:[],alarmType:false,},
  110. {school:'老附中校区', name:'附中教学楼', buildId:'101093843117592791',centerValue:'',valueList:[],alarmType:false,},
  111. ],
  112. // 本地
  113. // modelsUrlN:'/models/NxiaoQu.glb',
  114. // modelsUrlB:'/models/BxiaoQu.glb',
  115. // alarmUrl:'/png/alarm.png',
  116. // noAlarmUrl:'/png/noAlarm.png',
  117. // 本地部署
  118. // modelsUrlN:this.judgmentNetworkReturnAddress()?'/v3/largeScreen/models/NxiaoQu.glb':'/labAppTest/largeScreen/models/NxiaoQu.glb',
  119. // modelsUrlB:this.judgmentNetworkReturnAddress()?'/v3/largeScreen/models/BxiaoQu.glb':'/labAppTest/largeScreen/models/BxiaoQu.glb',
  120. // alarmUrl:this.judgmentNetworkReturnAddress()?'/v3/largeScreen/png/alarm.png':'/labAppTest/largeScreen/png/alarm.png',
  121. // noAlarmUrl:this.judgmentNetworkReturnAddress()?'/v3/largeScreen/png/noAlarm.png':'/labAppTest/largeScreen/png/noAlarm.png',
  122. // 线上部署
  123. modelsUrlN:'/largeScreen/models/NxiaoQu.glb',
  124. modelsUrlB:'/largeScreen/models/BxiaoQu.glb',
  125. alarmUrl:'/largeScreen/png/alarm.png',
  126. noAlarmUrl:'/largeScreen/png/noAlarm.png',
  127. //当前校区状态
  128. schoolType:'',
  129. };
  130. },
  131. mounted() {
  132. this.initThree();
  133. this.loadModel('N');
  134. this.loadAssets();
  135. window.addEventListener('resize', this.onWindowResize);
  136. },
  137. beforeDestroy() {
  138. // 停止动画循环
  139. if (this.animationId) {
  140. cancelAnimationFrame(this.animationId);
  141. }
  142. // 销毁Three.js相关资源
  143. this.disposeThreeResources();
  144. // 停止所有补间动画
  145. TWEEN.removeAll();
  146. // 移除事件监听器
  147. window.removeEventListener('resize', this.onWindowResize);
  148. this.renderer.dispose();
  149. },
  150. methods: {
  151. //初始化
  152. initThree() {
  153. // 初始化场景
  154. this.scene = new THREE.Scene();
  155. //场景背景色
  156. this.scene.background = new THREE.Color(0x020716);
  157. // 初始化渲染器
  158. this.renderer = new THREE.WebGLRenderer({ antialias: true });
  159. // 主渲染器尺寸设置
  160. this.renderer = new THREE.WebGLRenderer({ antialias: true });
  161. this.renderer.setSize(5927, 2160); // 固定尺寸
  162. this.$refs.container.appendChild(this.renderer.domElement);
  163. // 初始化相机时使用存储的位置
  164. this.camera = new THREE.PerspectiveCamera(
  165. 75,
  166. // window.innerWidth / window.innerHeight,
  167. 5927 / 2160,
  168. 0.1,
  169. 1000
  170. );
  171. this.camera.position.copy(this.initialCameraPosition);
  172. // 初始化控制器时存储初始目标
  173. this.controls = new OrbitControls(this.camera, this.renderer.domElement);
  174. this.controls.target.copy(this.initialControlsTarget);
  175. this.labelRenderer = new CSS2DRenderer();
  176. this.labelRenderer.setSize(5927, 2160);
  177. this.labelRenderer.domElement.style.position = 'absolute';
  178. this.labelRenderer.domElement.style.top = '0';
  179. this.labelRenderer.domElement.style.pointerEvents = 'none';//注释后 锁死鼠标视角
  180. this.$refs.container.appendChild(this.labelRenderer.domElement);
  181. // 初始化控制器
  182. this.controls = new OrbitControls(this.camera, this.renderer.domElement);
  183. this.controls.enableDamping = true;
  184. this.controls.dampingFactor = 0.05;
  185. // 添加灯光
  186. const ambientLight = new THREE.AmbientLight(0xffffff, 1);
  187. this.scene.add(ambientLight);
  188. const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
  189. directionalLight.position.set(-1.5, 2, -1.5);
  190. this.scene.add(directionalLight);
  191. // 启动动画循环
  192. this.animate();
  193. },
  194. //模型加载
  195. loadModel(type) {
  196. let self = this;
  197. this.$set(this,'schoolType',type);
  198. if(type == 'N'){
  199. //南校区
  200. const loaderN = new GLTFLoader();
  201. loaderN.load(self.modelsUrlN, (gltf) => {
  202. const model = gltf.scene;
  203. this.model = gltf.scene;
  204. this.scene.add(model);
  205. // 递归查找所有Group
  206. const traverseGroups = (obj) => {
  207. if (obj.isGroup) {
  208. this.registerHouse(obj);
  209. }
  210. obj.children.forEach(child => traverseGroups(child));
  211. };
  212. traverseGroups(gltf.scene);
  213. // 获取所有子模型并计算中心点
  214. model.traverse((child) => {
  215. if (child.isGroup) {
  216. const center = this.getBoundingBoxCenter(child);
  217. this.subModels.push({
  218. name: child.name,
  219. center: center,
  220. originalPosition: child.position.clone(),
  221. mesh: child // 直接引用mesh对象 用于材质修改
  222. });
  223. }
  224. });
  225. });
  226. }else if(type == 'B'){
  227. //北校区
  228. const loaderB = new GLTFLoader();
  229. loaderB.load(self.modelsUrlB, (gltf) => {
  230. const model = gltf.scene;
  231. this.model = gltf.scene;
  232. this.scene.add(model);
  233. // 递归查找所有Group
  234. const traverseGroups = (obj) => {
  235. if (obj.isGroup) {
  236. this.registerHouse(obj);
  237. }
  238. obj.children.forEach(child => traverseGroups(child));
  239. };
  240. traverseGroups(gltf.scene);
  241. // 获取所有子模型并计算中心点
  242. model.traverse((child) => {
  243. if (child.isGroup) {
  244. const center = this.getBoundingBoxCenter(child);
  245. this.subModels.push({
  246. name: child.name,
  247. center: center,
  248. originalPosition: child.position.clone(),
  249. mesh: child // 直接引用mesh对象 用于材质修改
  250. });
  251. }
  252. });
  253. });
  254. }
  255. setTimeout(function(){
  256. self.buildingApplyTexture([]);
  257. // self.buildingColor([]);
  258. },500);
  259. },
  260. /****** 新模型相关方法-开始 *******/
  261. // 修改后的 registerHouse 方法
  262. registerHouse(group) {
  263. const bbox = new THREE.Box3().setFromObject(group);
  264. const center = bbox.getCenter(new THREE.Vector3());
  265. // 记录原始材质
  266. const originalMaterials = [];
  267. group.traverse(child => {
  268. if (child.isMesh) {
  269. originalMaterials.push({
  270. mesh: child,
  271. material: child.material.clone() // 重要:必须克隆原始材质
  272. });
  273. }
  274. });
  275. this.houses.push({
  276. name:group.name,
  277. uuid: group.uuid,
  278. group: group,
  279. screenPosition: new THREE.Vector2(),
  280. worldPosition: center,
  281. originalMaterials: originalMaterials // 新增原始材质存储
  282. });
  283. },
  284. // 预加载材质和贴图
  285. loadAssets() {
  286. const textureLoader = new THREE.TextureLoader();
  287. this.textures = [
  288. textureLoader.load(this.alarmUrl)
  289. ];
  290. },
  291. // 报警样式
  292. changeTexture(house) {
  293. const texture = this.textures[Math.floor(Math.random() * this.textures.length)];
  294. texture.needsUpdate = true;
  295. house.group.traverse(child => {
  296. if (child.isMesh) {
  297. child.material.map = texture;
  298. child.material.needsUpdate = true;
  299. }
  300. });
  301. },
  302. // 恢复正常显示
  303. restoreOriginalMaterial(house) {
  304. house.originalMaterials.forEach(entry => {
  305. entry.mesh.material.dispose(); // 清理当前材质
  306. entry.mesh.material = entry.material.clone();
  307. entry.mesh.material.needsUpdate = true;
  308. });
  309. },
  310. /****** 新模型相关方法-结束 *******/
  311. getBoundingBoxCenter(obj) {
  312. const box = new THREE.Box3().setFromObject(obj);
  313. return box.getCenter(new THREE.Vector3());
  314. },
  315. onWindowResize() {
  316. this.camera.aspect = 5927 / 2160;
  317. this.camera.updateProjectionMatrix();
  318. this.renderer.setSize(5927, 2160);
  319. this.labelRenderer.setSize(5927, 2160);
  320. },
  321. //动画刷新
  322. animate() {
  323. if (this.model&&this.rotateType) {
  324. this.model.rotation.y += 0.001; // 添加简单旋转动画
  325. }else if(this.model&&!this.rotateType){
  326. this.model.rotation.y = 0;
  327. }
  328. // requestAnimationFrame(this.animate);
  329. // TWEEN.update();
  330. // this.controls.update();
  331. // this.renderer.render(this.scene, this.camera);
  332. // this.labelRenderer.render(this.scene, this.camera);
  333. this.animationId = requestAnimationFrame(this.animate);
  334. TWEEN.update();
  335. this.controls.update();
  336. this.renderer.render(this.scene, this.camera);
  337. this.labelRenderer.render(this.scene, this.camera);
  338. },
  339. //预案关闭
  340. alarmOff(){
  341. let self = this;
  342. self.$set(this,'rotateType',true)
  343. if(this.schoolType == 'N'){
  344. self.resetCamera();
  345. self.del2dText();
  346. // self.buildingColor([]);
  347. self.buildingApplyTexture([]);
  348. }else{
  349. //删除所有文字
  350. for(let i=0;i<self.scene.children.length;i++){
  351. if(self.scene.children[i].isCSS2DObject){
  352. self.scene.remove(self.scene.children[i]);
  353. i--
  354. }
  355. }
  356. //删除模型数据
  357. this.$set(this,'subModels',[]);
  358. this.$set(this,'houses',[]);
  359. this.scene.children.forEach((item)=>{
  360. if(item.type == 'Group'){
  361. this.scene.remove(item);
  362. }
  363. })
  364. self.resetCamera();
  365. this.delLoadModel('N');
  366. }
  367. },
  368. // 新增恢复视角方法
  369. resetCamera() {
  370. this.controls.enabled = false;
  371. new TWEEN.Tween(this.camera.position)
  372. .to(this.initialCameraPosition, 1500)
  373. .easing(TWEEN.Easing.Quadratic.InOut)
  374. .start();
  375. new TWEEN.Tween(this.controls.target)
  376. .to(this.initialControlsTarget, 1500)
  377. .easing(TWEEN.Easing.Quadratic.InOut)
  378. .onComplete(() => {
  379. this.controls.enabled = true;
  380. })
  381. .start();
  382. this.del2dText();
  383. },
  384. //删除所有2d文字
  385. del2dText(){
  386. let self = this;
  387. for(let i=0;i<self.scene.children.length;i++){
  388. if(self.scene.children[i].isCSS2DObject){
  389. this.scene.remove(self.scene.children[i]);
  390. i--
  391. }
  392. }
  393. },
  394. //预案触发
  395. alarmTrigger(list,obj,type){
  396. let self = this;
  397. self.$set(this,'rotateType',false)
  398. //list=所有报警数据 obj=需要聚焦的报警数据 type=报警的校区
  399. if(this.schoolType == type){
  400. //当前校区
  401. self.buildingApplyTexture(list);
  402. // self.buildingColor(list);
  403. self.focusingLens(obj);
  404. //删除所有文字
  405. for(let i=0;i<self.scene.children.length;i++){
  406. if(self.scene.children[i].isCSS2DObject){
  407. self.scene.remove(self.scene.children[i]);
  408. i--
  409. }
  410. }
  411. //生成新文字
  412. this.alarmTextLabel(list);
  413. }else{
  414. //其他校区
  415. //删除所有文字
  416. for(let i=0;i<self.scene.children.length;i++){
  417. if(self.scene.children[i].isCSS2DObject){
  418. self.scene.remove(self.scene.children[i]);
  419. i--
  420. }
  421. }
  422. //删除模型数据
  423. this.$set(this,'subModels',[]);
  424. this.$set(this,'houses',[]);
  425. this.scene.children.forEach((item)=>{
  426. if(item.type == 'Group'){
  427. self.scene.remove(item);
  428. }
  429. })
  430. //加载新模型
  431. this.newLoadModel(list,obj,type);
  432. }
  433. },
  434. //新模型加载
  435. newLoadModel(list,obj,type) {
  436. let self = this;
  437. this.$set(this,'schoolType',type);
  438. if(type == 'N'){
  439. //南校区
  440. const loaderN = new GLTFLoader();
  441. loaderN.load(self.modelsUrlN, (gltf) => {
  442. const model = gltf.scene;
  443. this.model = gltf.scene;
  444. this.scene.add(model);
  445. // 递归查找所有Group
  446. const traverseGroups = (obj) => {
  447. if (obj.isGroup) {
  448. this.registerHouse(obj);
  449. }
  450. obj.children.forEach(child => traverseGroups(child));
  451. };
  452. traverseGroups(gltf.scene);
  453. // 获取所有子模型并计算中心点
  454. model.traverse((child) => {
  455. if (child.isGroup) {
  456. const center = this.getBoundingBoxCenter(child);
  457. this.subModels.push({
  458. name: child.name,
  459. center: center,
  460. originalPosition: child.position.clone(),
  461. mesh: child // 直接引用mesh对象 用于材质修改
  462. });
  463. }
  464. });
  465. });
  466. }else if(type == 'B'){
  467. //北校区
  468. const loaderB = new GLTFLoader();
  469. loaderB.load(self.modelsUrlB, (gltf) => {
  470. const model = gltf.scene;
  471. this.model = gltf.scene;
  472. this.scene.add(model);
  473. // 递归查找所有Group
  474. const traverseGroups = (obj) => {
  475. if (obj.isGroup) {
  476. this.registerHouse(obj);
  477. }
  478. obj.children.forEach(child => traverseGroups(child));
  479. };
  480. traverseGroups(gltf.scene);
  481. // 获取所有子模型并计算中心点
  482. model.traverse((child) => {
  483. if (child.isGroup) {
  484. const center = this.getBoundingBoxCenter(child);
  485. this.subModels.push({
  486. name: child.name,
  487. center: center,
  488. originalPosition: child.position.clone(),
  489. mesh: child // 直接引用mesh对象 用于材质修改
  490. });
  491. }
  492. });
  493. });
  494. }
  495. setTimeout(function(){
  496. self.focusingLens(obj);
  497. self.buildingApplyTexture(list);
  498. // self.buildingColor(list);
  499. self.alarmTextLabel(list);
  500. },1000);
  501. },
  502. //预案停止模型加载
  503. delLoadModel(type) {
  504. let self = this;
  505. this.$set(this,'schoolType',type);
  506. //南校区
  507. const loaderN = new GLTFLoader();
  508. loaderN.load(self.modelsUrlN, (gltf) => {
  509. const model = gltf.scene;
  510. this.model = gltf.scene;
  511. this.scene.add(model);
  512. // 递归查找所有Group
  513. const traverseGroups = (obj) => {
  514. if (obj.isGroup) {
  515. this.registerHouse(obj);
  516. }
  517. obj.children.forEach(child => traverseGroups(child));
  518. };
  519. traverseGroups(gltf.scene);
  520. // 获取所有子模型并计算中心点
  521. model.traverse((child) => {
  522. if (child.isGroup) {
  523. const center = this.getBoundingBoxCenter(child);
  524. this.subModels.push({
  525. name: child.name,
  526. center: center,
  527. originalPosition: child.position.clone(),
  528. mesh: child // 直接引用mesh对象 用于材质修改
  529. });
  530. }
  531. });
  532. });
  533. setTimeout(function(){
  534. self.buildingApplyTexture([]);
  535. // self.buildingColor([]);
  536. },1000);
  537. },
  538. //初始化楼栋标记(报警时调用)
  539. buildingColor(list){
  540. let self = this;
  541. //处理基础数据
  542. self.modelBuildingList.forEach((maxItem)=>{
  543. let num = 0;
  544. list.forEach((item)=>{
  545. if(maxItem.buildId == item.buildId){
  546. num++
  547. }
  548. })
  549. maxItem.alarmType = num != 0;
  550. })
  551. //处理预警与正常贴图
  552. self.modelBuildingList.forEach((maxItem)=>{
  553. maxItem.valueList.forEach((bigItem)=>{
  554. this.scene.children.forEach((item)=>{
  555. if(item.type == 'Group'){
  556. item.children.forEach((minItem)=>{
  557. if(minItem.name == bigItem){
  558. if(maxItem.alarmType){
  559. self.applyTexture(self.alarmUrl,minItem);
  560. }else{
  561. self.applyTexture(self.noAlarmUrl,minItem);
  562. }
  563. }
  564. })
  565. }
  566. })
  567. })
  568. })
  569. },
  570. buildingApplyTexture(list){
  571. let self = this;
  572. //处理基础数据
  573. self.modelBuildingList.forEach((maxItem)=>{
  574. let num = 0;
  575. list.forEach((item)=>{
  576. if(maxItem.buildId == item.buildId){
  577. num++
  578. }
  579. })
  580. maxItem.alarmType = num != 0;
  581. })
  582. this.$forceUpdate()
  583. //处理预警与正常贴图
  584. self.modelBuildingList.forEach((maxItem)=>{
  585. maxItem.valueList.forEach((bigItem)=>{
  586. self.houses.forEach((minItem)=>{
  587. if(minItem.name == bigItem){
  588. if(maxItem.alarmType){
  589. self.changeTexture(minItem);
  590. }else{
  591. self.restoreOriginalMaterial(minItem);
  592. }
  593. }
  594. })
  595. })
  596. })
  597. },
  598. //图片材质替换
  599. applyTexture(textureName,obj) {
  600. const textureLoader = new THREE.TextureLoader();
  601. textureLoader.load(
  602. textureName, // 图片放在public目录下
  603. (texture) => {
  604. texture.flipY = false; // 根据图片格式调整
  605. obj.traverse((child) => {
  606. if (child.isMesh) {
  607. // 释放旧纹理资源
  608. if (child.material.map) {
  609. child.material.map.dispose();
  610. }
  611. child.material.map = texture;
  612. child.material.needsUpdate = true;
  613. }
  614. });
  615. },
  616. undefined,
  617. (error) => {
  618. console.error('材质加载失败:', error);
  619. }
  620. );
  621. },
  622. //材质方法调用
  623. changeMaterial(options) {
  624. const {
  625. modelSelector, // 支持三种选择方式:名称(name)/索引(index)/'all'
  626. materialParams, // 新材质参数对象
  627. keepOriginal = false // 是否保留原始材质
  628. } = options;
  629. const targetMeshes = this.getTargetMeshes(modelSelector);
  630. targetMeshes.forEach(mesh => {
  631. // 创建新材质
  632. const newMaterial = this.createMaterial(materialParams);
  633. // 处理旧材质
  634. if (!keepOriginal && mesh.userData.originalMaterial) {
  635. mesh.material.dispose();
  636. }
  637. // 应用新材质
  638. mesh.material = newMaterial;
  639. });
  640. },
  641. // 辅助方法:获取目标网格
  642. getTargetMeshes(selector) {
  643. if (selector === 'all') {
  644. return this.subModels.map(m => m.mesh);
  645. }
  646. if (typeof selector === 'number') {
  647. return [this.subModels[selector].mesh];
  648. }
  649. if (typeof selector === 'string') {
  650. return this.subModels
  651. .filter(m => m.name === selector)
  652. .map(m => m.mesh);
  653. }
  654. return [];
  655. },
  656. // 材质工厂方法
  657. createMaterial(params) {
  658. const type = params.type || 'Standard';
  659. const baseParams = {
  660. color: new THREE.Color(params.color || 0xffffff),
  661. map: params.texture ? this.loadTexture(params.texture) : null,
  662. transparent: params.transparent || false,
  663. opacity: params.opacity || 1.0,
  664. ...params
  665. };
  666. switch(type.toLowerCase()) {
  667. case 'basic':
  668. return new THREE.MeshBasicMaterial(baseParams);
  669. case 'phong':
  670. return new THREE.MeshPhongMaterial(baseParams);
  671. case 'standard':
  672. return new THREE.MeshStandardMaterial(baseParams);
  673. case 'physical':
  674. return new THREE.MeshPhysicalMaterial(baseParams);
  675. default:
  676. return new THREE.MeshStandardMaterial(baseParams);
  677. }
  678. },
  679. //聚焦镜头查询
  680. focusingLens(obj){
  681. let self = this;
  682. this.modelBuildingList.forEach((maxItem)=>{
  683. if(maxItem.buildId == obj.buildId){
  684. self.subModels.forEach((bigItem)=>{
  685. if(bigItem.name == maxItem.centerValue){
  686. self.moveCameraToModel(bigItem,bigItem.name);
  687. }
  688. })
  689. }
  690. })
  691. },
  692. //镜头聚焦方法
  693. moveCameraToModel(model,name) {
  694. let self = this;
  695. // 禁用控制器
  696. this.controls.enabled = false;
  697. // 计算目标位置(模型中心前上方)
  698. const targetPosition = new THREE.Vector3()
  699. .copy(model.center)
  700. .add(new THREE.Vector3(0.2,0.2, 0.2));
  701. // 动画参数
  702. const duration = 1500;
  703. const easing = TWEEN.Easing.Quadratic.InOut;
  704. // 相机位置动画
  705. new TWEEN.Tween(this.camera.position)
  706. .to(targetPosition, duration)
  707. .easing(easing)
  708. .start();
  709. // 控制器目标点动画
  710. new TWEEN.Tween(this.controls.target)
  711. .to(model.center, duration)
  712. .easing(easing)
  713. .onComplete(() => {
  714. this.controls.enabled = true; // 恢复控制
  715. })
  716. .start();
  717. },
  718. //预警文本生成
  719. alarmTextLabel(list){
  720. let self = this;
  721. let newList = [];
  722. self.modelBuildingList.forEach((maxItem)=>{
  723. list.forEach((item)=>{
  724. if(maxItem.buildId == item.buildId){
  725. let num = 0;
  726. newList.forEach((minItem)=>{
  727. if(minItem.buildId == item.buildId){
  728. num++
  729. if(!minItem.textList[1]){
  730. minItem.textList.push(
  731. "<div style='border-top:1px dashed #E90104;margin-top:35px;'>" +
  732. "<p style='color:#FF8400;font-size:34px;line-height:49px;margin-top:29px;'>"+item.riskPlanName+"</p>"+
  733. "<p style='font-size:34px;line-height:49px;margin-top:15px;'>"+this.parseTime(item.eventStartTime,"{y}-{m}-{d} {h}:{i}:{s}")+"</p>"+
  734. "<p style='font-size:34px;line-height:49px;margin-top:15px;'>"+item.floorName+"-"+item.roomNum+"-"+item.deptName+"</p>"+
  735. "<p style='font-size:34px;line-height:49px;margin-top:15px;'><span style='border:1px solid "+item.classLevelColor+";padding:0 10px;margin-right:20px;border-radius:20px;color:"+item.classLevelColor+";'>"+item.classLevelName+"</span>"+item.subName+"</p>"+
  736. "</div>"
  737. )
  738. }else{
  739. minItem.textList.splice(0,1)
  740. minItem.textList.push(
  741. "<div style='border-top:1px dashed #E90104;margin-top:35px;'>" +
  742. "<p style='color:#FF8400;font-size:34px;line-height:49px;margin-top:29px;'>"+item.riskPlanName+"</p>"+
  743. "<p style='font-size:34px;line-height:49px;margin-top:15px;'>"+this.parseTime(item.eventStartTime,"{y}-{m}-{d} {h}:{i}:{s}")+"</p>"+
  744. "<p style='font-size:34px;line-height:49px;margin-top:15px;'>"+item.floorName+"-"+item.roomNum+"-"+item.deptName+"</p>"+
  745. "<p style='font-size:34px;line-height:49px;margin-top:15px;'><span style='border:1px solid "+item.classLevelColor+";padding:0 10px;margin-right:20px;border-radius:20px;color:"+item.classLevelColor+";'>"+item.classLevelName+"</span>"+item.subName+"</p>"+
  746. "</div>"
  747. )
  748. }
  749. }
  750. })
  751. if(num == 0){
  752. newList.push(
  753. {
  754. buildId:item.buildId,
  755. centerValue:maxItem.centerValue,
  756. text:"",
  757. //校区-楼栋
  758. titleName:item.schoolName+' - '+item.buildName,
  759. textList:[
  760. "<div>" +
  761. "<p style='color:#FF8400;font-size:34px;line-height:49px;margin-top:29px;'>"+item.riskPlanName+"</p>"+
  762. "<p style='font-size:34px;line-height:49px;margin-top:15px;'>"+this.parseTime(item.eventStartTime,"{y}-{m}-{d} {h}:{i}:{s}")+"</p>"+
  763. "<p style='font-size:34px;line-height:49px;margin-top:15px;'>"+item.floorName+"-"+item.roomNum+"-"+item.deptName+"</p>"+
  764. "<p style='font-size:34px;line-height:49px;margin-top:15px;'><span style='border:1px solid "+item.classLevelColor+";padding:0 10px;margin-right:20px;border-radius:20px;color:"+item.classLevelColor+";'>"+item.classLevelName+"</span>"+item.subName+"</p>"+
  765. "</div>"
  766. ]
  767. }
  768. )
  769. }
  770. }
  771. })
  772. })
  773. //循环定位模型位置并生成textTable
  774. newList.forEach((maxItem,maxIndex)=>{
  775. maxItem.text = "<div style='z-index:"+maxIndex+";border-radius:20px;padding:30px;color:#ffffff;border:1px solid #E90104;box-shadow: inset 0 0 10px rgba(176, 0, 0, 1);background-color: rgba(187,0,0,0.5)'>"
  776. maxItem.text = maxItem.text+"<p style='border-bottom:1px solid #E90104;line-height:104px;'>"+maxItem.titleName+"</p>";
  777. maxItem.textList.forEach((html)=>{
  778. maxItem.text = maxItem.text + html
  779. })
  780. maxItem.text = maxItem.text+"</div>"
  781. self.subModels.forEach((bigItem)=>{
  782. if(bigItem.name == maxItem.centerValue){
  783. forScene(bigItem,maxItem.text);
  784. }
  785. })
  786. })
  787. function forScene(model,text){
  788. self.scene.children.forEach((item)=>{
  789. if(item.type == 'Group'){
  790. item.children.forEach((minItem)=>{
  791. if(minItem.name == model.name){
  792. self.add2dFont(minItem,model.center,text);
  793. }
  794. })
  795. }
  796. })
  797. }
  798. },
  799. //生成css2d文字
  800. add2dFont(model,center,text){
  801. const labelDiv = document.createElement('div');
  802. labelDiv.style.fontSize = '40px';
  803. labelDiv.style.width = '800px';
  804. labelDiv.style.borderRadius = '10px';
  805. labelDiv.style.whiteSpace = "pre-line";
  806. labelDiv.innerHTML = text;
  807. // 创建 CSS2D 对象并绑定到目标
  808. const label = new CSS2DObject(labelDiv);
  809. const targetPosition = new THREE.Vector3()
  810. .copy(center)
  811. .add(new THREE.Vector3(0.09,0.08,-0.1));
  812. label.position.set(targetPosition.x,targetPosition.y,targetPosition.z); // 在对象上方 1 单位位置
  813. this.scene.add(label);
  814. },
  815. //内外网地址判定
  816. judgmentNetworkReturnAddress() {
  817. /*判断是否是内网IP*/
  818. // 获取当前页面url
  819. var curPageUrl = window.location.href;
  820. var reg1 = /(http|ftp|https|www):\/\//g;//去掉前缀
  821. curPageUrl =curPageUrl.replace(reg1,'');
  822. var reg2 = /\:+/g;//替换冒号为一点
  823. curPageUrl =curPageUrl.replace(reg2,'.');
  824. curPageUrl = curPageUrl.split('.');//通过一点来划分数组
  825. var ipAddress = curPageUrl[0]+'.'+curPageUrl[1]+'.'+curPageUrl[2]+'.'+curPageUrl[3];
  826. var isInnerIp = false;//默认给定IP不是内网IP
  827. var ipNum = getIpNum(ipAddress);
  828. /**
  829. * 私有IP:A类 10.0.0.0 -10.255.255.255
  830. * B类 172.16.0.0 -172.31.255.255
  831. * C类 192.168.0.0 -192.168.255.255
  832. * D类 127.0.0.0 -127.255.255.255(环回地址)
  833. **/
  834. var aBegin = getIpNum("10.0.0.0");
  835. var aEnd = getIpNum("10.255.255.255");
  836. var bBegin = getIpNum("172.16.0.0");
  837. var bEnd = getIpNum("172.31.255.255");
  838. var cBegin = getIpNum("192.168.0.0");
  839. var cEnd = getIpNum("192.168.255.255");
  840. var dBegin = getIpNum("127.0.0.0");
  841. var dEnd = getIpNum("127.255.255.255");
  842. isInnerIp = isInner(ipNum,aBegin,aEnd) || isInner(ipNum,bBegin,bEnd) || isInner(ipNum,cBegin,cEnd) || isInner(ipNum,dBegin,dEnd);
  843. return isInnerIp?true:false;
  844. /*获取IP数*/
  845. function getIpNum(ipAddress) {
  846. var ip = ipAddress.split(".");
  847. var a = parseInt(ip[0]);
  848. var b = parseInt(ip[1]);
  849. var c = parseInt(ip[2]);
  850. var d = parseInt(ip[3]);
  851. var ipNum = a * 256 * 256 * 256 + b * 256 * 256 + c * 256 + d;
  852. return ipNum;
  853. }
  854. function isInner(userIp,begin,end){
  855. return (userIp>=begin) && (userIp<=end);
  856. }
  857. },
  858. // 资源销毁方法
  859. disposeThreeResources() {
  860. // 释放几何体和材质
  861. this.scene.traverse(child => {
  862. if (child.isMesh) {
  863. child.geometry.dispose();
  864. if (Array.isArray(child.material)) {
  865. child.material.forEach(m => m.dispose());
  866. } else {
  867. child.material.dispose();
  868. }
  869. }
  870. });
  871. // 释放渲染器
  872. this.renderer.dispose();
  873. this.labelRenderer.domElement.remove();
  874. this.labelRenderer = null;
  875. // 释放场景和相机
  876. this.scene = null;
  877. this.camera = null;
  878. // 释放控制器
  879. this.controls.dispose();
  880. this.controls = null;
  881. },
  882. }
  883. };
  884. </script>
  885. <style scoped lang="scss">
  886. .canvasMap{
  887. position: fixed;
  888. top: 0;
  889. left: 0;
  890. width: 100%;
  891. height: 100%;
  892. }
  893. </style>