canvasMap.vue 41 KB

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