ThreeModel.vue 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  1. <template>
  2. <div class="three-model" :style="'cursor: ' + (pointer ? 'pointer' : 'auto') + ';'" @click="clickEvent">
  3. <loading ref="pageLoading"></loading>
  4. <div class="three-html-layer">
  5. <div
  6. class="three-html-dom fan-info"
  7. :id="htmlLayer[0].id"
  8. v-show="htmlLayer[0].show"
  9. :style="'left: ' + htmlLayer[0].x + 'px; top: ' + htmlLayer[0].y + 'px;'"
  10. >
  11. <div class="fan-info-close" @click.stop="closeFanInfo">
  12. <i class="el-icon-close"></i>
  13. </div>
  14. <div class="fan-info-name gray-l">{{fanName}}</div>
  15. <div class="fan-info-item">
  16. <span class="svg-icon svg-icon-gray-l svg-icon-sm">
  17. <svg-icon svgid="svg-风机" />
  18. </span>
  19. <span class="gray-l mg-l-8">接入</span>
  20. <span class="green num">176</span>
  21. <span class="green mg-l-8">台</span>
  22. </div>
  23. <div class="fan-info-item">
  24. <span class="svg-icon svg-icon-gray-l svg-icon-sm">
  25. <svg-icon svgid="svg-normal-power" />
  26. </span>
  27. <span class="gray-l mg-l-8">运行</span>
  28. <span class="green num">1</span>
  29. <span class="green mg-l-8">台</span>
  30. </div>
  31. <div class="fan-info-item">
  32. <span class="svg-icon svg-icon-gray-l svg-icon-sm">
  33. <svg-icon svgid="svg-standby" />
  34. </span>
  35. <span class="gray-l mg-l-8">待机</span>
  36. <span class="green num">1</span>
  37. <span class="green mg-l-8">台</span>
  38. </div>
  39. <div class="fan-info-item">
  40. <span class="svg-icon svg-icon-gray-l svg-icon-sm">
  41. <svg-icon svgid="svg-limit-power" />
  42. </span>
  43. <span class="gray-l mg-l-8">限电</span>
  44. <span class="green num">1</span>
  45. <span class="green mg-l-8">台</span>
  46. </div>
  47. <div class="fan-info-item">
  48. <span class="svg-icon svg-icon-gray-l svg-icon-sm">
  49. <svg-icon svgid="svg-gz-downtime" />
  50. </span>
  51. <span class="gray-l mg-l-8">故障</span>
  52. <span class="green num">1</span>
  53. <span class="green mg-l-8">台</span>
  54. </div>
  55. <div class="fan-info-item">
  56. <span class="svg-icon svg-icon-gray-l svg-icon-sm">
  57. <svg-icon svgid="svg-jx-downtime" />
  58. </span>
  59. <span class="gray-l mg-l-8">检修</span>
  60. <span class="green num">1</span>
  61. <span class="green mg-l-8">台</span>
  62. </div>
  63. <div class="fan-info-item">
  64. <span class="svg-icon svg-icon-gray-l svg-icon-sm">
  65. <svg-icon svgid="svg-offline" />
  66. </span>
  67. <span class="gray-l mg-l-8">离线</span>
  68. <span class="green num">1</span>
  69. <span class="green mg-l-8">台</span>
  70. </div>
  71. <div class="fan-info-item">
  72. <span class="svg-icon svg-icon-gray-l svg-icon-sm">
  73. <svg-icon svgid="svg-intranet-involvement" />
  74. </span>
  75. <span class="gray-l mg-l-8">受累</span>
  76. <span class="green num">1</span>
  77. <span class="green mg-l-8">台</span>
  78. </div>
  79. </div>
  80. <div
  81. class="three-html-dom build-info"
  82. :id="htmlLayer[1].id"
  83. v-show="htmlLayer[1].show"
  84. :style="'left: ' + htmlLayer[1].x + 'px; top: ' + htmlLayer[1].y + 'px;'"
  85. >
  86. <div class="build-info-close" @click.stop="closeBuildInfo">
  87. <i class="el-icon-close"></i>
  88. </div>
  89. <div class="build-info-item blue" :style="'right: '+circleXY[0].x+'px; top: '+circleXY[0].y+'px'">
  90. <div class="build-info-item-num">97</div>
  91. <div class="build-info-item-text">综合厂</div>
  92. </div>
  93. <div class="build-info-item blue" :style="'right: '+circleXY[1].x+'px; top: '+circleXY[1].y+'px'">
  94. <div class="build-info-item-num">97</div>
  95. <div class="build-info-item-text">风能</div>
  96. </div>
  97. <div class="build-info-item green" :style="'right: '+circleXY[2].x+'px; top: '+circleXY[2].y+'px'">
  98. <div class="build-info-item-num">97</div>
  99. <div class="build-info-item-text">利用小时</div>
  100. </div>
  101. <div class="build-info-item purple" :style="'right: '+circleXY[3].x+'px; top: '+circleXY[3].y+'px'">
  102. <div class="build-info-item-num">97</div>
  103. <div class="build-info-item-text">评分</div>
  104. </div>
  105. <div class="build-info-item orange" :style="'right: '+circleXY[4].x+'px; top: '+circleXY[4].y+'px'">
  106. <div class="build-info-item-num">97</div>
  107. <div class="build-info-item-text">MTBF</div>
  108. </div>
  109. <div class="build-info-item yellow" :style="'right: '+circleXY[5].x+'px; top: '+circleXY[5].y+'px'">
  110. <div class="build-info-item-num">97</div>
  111. <div class="build-info-item-text">设备科</div>
  112. </div>
  113. <div class="build-info-item blue" :style="'right: '+circleXY[6].x+'px; top: '+circleXY[6].y+'px'">
  114. <div class="build-info-item-num">97</div>
  115. <div class="build-info-item-text">MTTR</div>
  116. </div>
  117. </div>
  118. </div>
  119. </div>
  120. </template>
  121. <script>
  122. import loading from "@com/coms/loading/loading.vue";
  123. import * as THREE from "three";
  124. import { GLTFLoader } from "@node/three/examples/jsm/loaders/GLTFLoader.js";
  125. import { OrbitControls } from "@node/three/examples/jsm/controls/OrbitControls.js";
  126. import { GeometryUtils } from "@node/three/examples/jsm/utils/GeometryUtils.js";
  127. import SvgIcon from "@com/coms/icon/svg-icon.vue";
  128. let camera, scene, renderer, controls;
  129. let mixers = [];
  130. let clock = new THREE.Clock();
  131. let fanAnimates = [];
  132. let fans = [];
  133. let cylinder = null;
  134. export default {
  135. // 名称
  136. name: "ThreeModel",
  137. // 使用组件
  138. components: {
  139. loading,
  140. SvgIcon,
  141. },
  142. // 传入参数
  143. props: {},
  144. // 自定义事件
  145. emits: {
  146. when: null,
  147. },
  148. // 数据
  149. data() {
  150. return {
  151. pointer: false,
  152. htmlLayer: [
  153. {
  154. id: "fan-info",
  155. x: 0,
  156. y: 0,
  157. show: false,
  158. ox: 50,
  159. oy: -50,
  160. position: null,
  161. },
  162. {
  163. id: "build-info",
  164. x: 0,
  165. y: 0,
  166. show: false,
  167. ox: -300,
  168. oy: -50,
  169. position: null,
  170. },
  171. ],
  172. circleXY: [
  173. { x: -(200*Math.sin(0/180*Math.PI)), y: -(200*Math.cos(0/180*Math.PI)) },
  174. { x: -(200*Math.sin(-30/180*Math.PI)), y: -(200*Math.cos(-30/180*Math.PI)) },
  175. { x: -(200*Math.sin(-60/180*Math.PI)), y: -(200*Math.cos(-60/180*Math.PI)) },
  176. { x: -(200*Math.sin(-90/180*Math.PI)), y: -(200*Math.cos(-90/180*Math.PI)) },
  177. { x: -(200*Math.sin(-120/180*Math.PI)), y: -(200*Math.cos(-120/180*Math.PI)) },
  178. { x: -(200*Math.sin(-150/180*Math.PI)), y: -(200*Math.cos(-150/180*Math.PI)) },
  179. { x: -(200*Math.sin(-180/180*Math.PI)), y: -(200*Math.cos(-180/180*Math.PI)) },
  180. ],
  181. colors: [
  182. { colorName: 'green', state: 'dj', stateName: '待机', color: 0x05bb4c, },
  183. { colorName: 'blue', state: 'yx', stateName: '运行', color: 0x4b55ae, },
  184. { colorName: 'pink', state: 'xd', stateName: '限电', color: 0xc531c7, },
  185. { colorName: 'red', state: 'gz', stateName: '故障', color: 0xBA3237, },
  186. { colorName: 'orange', state: 'jx', stateName: '检修', color: 0xe17e23, },
  187. { colorName: 'gray', state: 'lx', stateName: '离线', color: 0x606769, },
  188. { colorName: 'white', state: 'sl', stateName: '受累', color: 0xffffff, },
  189. ],
  190. fanName: "",
  191. playAnimation: true,
  192. };
  193. },
  194. // 函数
  195. methods: {
  196. // Vector3 to screen
  197. vector3ToScreen: function(position) {
  198. const centerX = this.$el.scrollWidth / 2;
  199. const centerY = this.$el.scrollHeight / 2;
  200. const v3 = new THREE.Vector3(position.x, position.y, position.z);
  201. const standardVec = v3.project(camera);
  202. const screenX = Math.round(centerX * standardVec.x + centerX);
  203. const screenY = Math.round(-centerY * standardVec.y + centerY);
  204. return { x: screenX, y: screenY };
  205. },
  206. // 场景
  207. initScene: function() {
  208. scene = new THREE.Scene();
  209. // scene.background = new THREE.Color(0xa0a0a0);
  210. },
  211. // 相机
  212. initCamera: function() {
  213. camera = new THREE.PerspectiveCamera(45, this.$el.scrollWidth / this.$el.scrollHeight, 0.1, 10000);
  214. camera.position.set(50, 60, 50);
  215. },
  216. // 坐标轴
  217. initAxesHelper: function() {
  218. const axesHelper = new THREE.AxesHelper(150);
  219. scene.add(axesHelper);
  220. axesHelper.position.set(0, 0, 0);
  221. },
  222. // 渲染器
  223. initRender: function() {
  224. renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
  225. // renderer.setClearAlpha(0);
  226. renderer.setSize(this.$el.scrollWidth, this.$el.scrollHeight);
  227. this.$el.append(renderer.domElement);
  228. },
  229. // 灯光
  230. initLight: function() {
  231. // let light = new THREE.PointLight(0xffffff, 2);
  232. let light = new THREE.AmbientLight(0xffffff, 4);
  233. // light.position.set(50, 50, 50);
  234. scene.add(light);
  235. },
  236. // 控制器
  237. initControls: function() {
  238. controls = new OrbitControls(camera, renderer.domElement);
  239. controls.enablePan = false;
  240. controls.maxPolarAngle = 45 / 180 * Math.PI;
  241. controls.minPolarAngle = 45 / 180 * Math.PI;
  242. controls.addEventListener("change", () => {
  243. this.setEveryHTML();
  244. });
  245. },
  246. // 初始化一个风机动画
  247. initFanAnimate: function(obj) {
  248. let fanAnimateObj = {
  249. speed: 0.05,
  250. fan1: obj.getObjectByName(`${obj.name}_leaf_1`),
  251. fan2: obj.getObjectByName(`${obj.name}_leaf_2`),
  252. fan3: obj.getObjectByName(`${obj.name}_leaf_3`),
  253. };
  254. let fanAnimateFunction = function() {
  255. fanAnimateObj.fan1.rotateZ(fanAnimateObj.speed);
  256. fanAnimateObj.fan2.rotateZ(fanAnimateObj.speed);
  257. fanAnimateObj.fan3.rotateZ(fanAnimateObj.speed);
  258. fanAnimateObj.animateId = window.requestAnimationFrame(fanAnimateFunction);
  259. };
  260. fanAnimateObj.stop = function() {
  261. window.cancelAnimationFrame(fanAnimateObj.animateId);
  262. };
  263. fanAnimateObj.start = function() {
  264. fanAnimateFunction();
  265. };
  266. fanAnimateFunction();
  267. fanAnimates.push(fanAnimateObj);
  268. return fanAnimateObj;
  269. },
  270. // 清空风机动画
  271. clearFanAnimate: function() {
  272. while (fanAnimates.length > 0) {
  273. let fanAnimateObj = fanAnimates.shift();
  274. fanAnimateObj.stop();
  275. }
  276. this.playAnimation = false;
  277. },
  278. // 开始风机动画
  279. startFanAnimate: function () {
  280. fans.forEach(fan => {
  281. this.initFanAnimate(fan);
  282. });
  283. this.playAnimation = true;
  284. },
  285. // 内容
  286. initContent: function() {
  287. // 加载3D地面
  288. let loaderGround = new GLTFLoader(); /*实例化加载器*/
  289. loaderGround.load(
  290. "static/3d/group/ng.gltf",
  291. (gltf) => {
  292. gltf.scene.position.set(5, 10, 0);
  293. scene.add(gltf.scene);
  294. let mixer = new THREE.AnimationMixer(gltf.scene.children[0]);
  295. mixer
  296. .clipAction(gltf.animations[0])
  297. .setDuration(3)
  298. .play();
  299. mixers.push(mixer);
  300. // console.log(gltf);
  301. // 移除风车
  302. let rootNode = scene.getObjectByName("RootNode");
  303. let fan1 = scene.getObjectByName("组840");
  304. let fan2 = scene.getObjectByName("组857");
  305. let fan3 = scene.getObjectByName("组838");
  306. rootNode.remove(fan1);
  307. rootNode.remove(fan2);
  308. rootNode.remove(fan3);
  309. // console.log(fan2.position);
  310. this.setFanPosition(rootNode, fan1);
  311. // 设置楼的name
  312. let build = scene.getObjectByName("组833");
  313. build.name = "build_headquarters";
  314. build.userData.name = "build_headquarters";
  315. build.children[build.children.length - 1].name = 'build_headquarters_chassis';
  316. build.children[build.children.length - 1].userData.name = 'build_headquarters_chassis';
  317. this.changeObjectColor(build, 'gz');
  318. // 移除光伏
  319. let light = scene.getObjectByName("组862");
  320. rootNode.remove(light);
  321. this.setLightPosition(rootNode, light);
  322. scene.onAfterRender = () => {
  323. this.$emit("when");
  324. this.$refs.pageLoading.hide();
  325. }
  326. },
  327. (xhr) => {
  328. // console.log(xhr.loaded, '/', xhr.total)
  329. // if (xhr.loaded == xhr.total) {
  330. // this.$emit("when");
  331. // setTimeout(() => {
  332. // this.$refs.pageLoading.hide();
  333. // }, 3000);
  334. // }
  335. },
  336. function(error) {
  337. console.error("load error!" + error.getWebGLErrorMessage());
  338. }
  339. );
  340. },
  341. // 改变元素底盘的颜色(带底盘的)
  342. changeObjectColor: function (obj, colorName) {
  343. let chassis = obj.getObjectByName(`${obj.name}_chassis`);
  344. let color = this.colors.find(t => t.colorName == colorName || t.state == colorName || t.stateName == colorName).color;
  345. chassis.material = new THREE.MeshBasicMaterial({
  346. color: color,
  347. opacity: 0.8,
  348. transparent: true,
  349. });
  350. },
  351. // 改变元素的颜色(光伏)
  352. changeElColor: function (obj, colorName) {
  353. let color = this.colors.find(t => t.colorName == colorName || t.state == colorName || t.stateName == colorName).color;
  354. for (let i = 1; i <= 6; i++) {
  355. let el = obj.getObjectByName(`${obj.name}_item_${i}`);
  356. el.material = new THREE.MeshBasicMaterial({
  357. color: color,
  358. opacity: 0.8,
  359. transparent: true,
  360. });
  361. }
  362. },
  363. // 设置风机name
  364. setFanName: function (obj, name) {
  365. obj.name = name;
  366. obj.userData.name = name;
  367. let fanBig1_1 = obj.getObjectByName("Box707");
  368. let fanBig1_2 = obj.getObjectByName("Box708");
  369. let fanBig1_3 = obj.getObjectByName("Box709");
  370. fanBig1_1.name = `${name}_leaf_1`;
  371. fanBig1_1.userData.name = `${name}_leaf_1`;
  372. fanBig1_2.name = `${name}_leaf_2`;
  373. fanBig1_2.userData.name = `${name}_leaf_2`;
  374. fanBig1_3.name = `${name}_leaf_3`;
  375. fanBig1_3.userData.name = `${name}_leaf_3`;
  376. obj.children[obj.children.length - 1].name = `${name}_chassis`;
  377. obj.children[obj.children.length - 1].userData.name = `${name}_chassis`;
  378. },
  379. // 设置风机位置 颜色
  380. setFanPosition: function (rootNode, obj) {
  381. // 麻黄山
  382. let fan_mhs = obj.clone(true);
  383. fan_mhs.position.set(8, 5, -6);
  384. this.setFanName(fan_mhs, "fan_mhs");
  385. rootNode.add(fan_mhs);
  386. // 牛首山
  387. let fan_nss = obj.clone(true);
  388. fan_nss.position.set(-4, 4, -15);
  389. this.setFanName(fan_nss, "fan_nss");
  390. rootNode.add(fan_nss);
  391. // 青山
  392. let fan_qs = obj.clone(true);
  393. fan_qs.position.set(12, 5, -11);
  394. this.setFanName(fan_qs, "fan_qs");
  395. rootNode.add(fan_qs);
  396. // 石板泉
  397. let fan_sbq = obj.clone(true);
  398. fan_sbq.position.set(4, 5, -13);
  399. this.setFanName(fan_sbq, "fan_sbq");
  400. rootNode.add(fan_sbq);
  401. // 香山
  402. let fan_xs = obj.clone(true);
  403. fan_xs.position.set(-9, 7, 21);
  404. this.setFanName(fan_xs, "fan_xs");
  405. rootNode.add(fan_xs);
  406. // 改一下颜色
  407. this.changeObjectColor(fan_mhs, 'green'); // 麻黄山
  408. this.changeObjectColor(fan_nss, 'xd'); // 牛首山
  409. this.changeObjectColor(fan_qs, '检修'); // 青山
  410. this.changeObjectColor(fan_sbq, 'white'); // 石板泉
  411. this.changeObjectColor(fan_xs, '运行'); // 香山
  412. // 风机存入数组
  413. fans.push(fan_mhs);
  414. fans.push(fan_nss);
  415. fans.push(fan_qs);
  416. fans.push(fan_sbq);
  417. fans.push(fan_xs);
  418. // 开始风机动画
  419. this.startFanAnimate();
  420. },
  421. // 设置光伏name
  422. setLightName: function (obj, name) {
  423. obj.name = name;
  424. obj.userData.name = name;
  425. let Box661 = obj.getObjectByName("Box661");
  426. let Box668 = obj.getObjectByName("Box668");
  427. let Box669 = obj.getObjectByName("Box669");
  428. let Box664 = obj.getObjectByName("Box664");
  429. let Box667 = obj.getObjectByName("Box667");
  430. let Box670 = obj.getObjectByName("Box670");
  431. Box661.name = `${name}_item_1`;
  432. Box661.userData.name = `${name}_item_1`;
  433. Box668.name = `${name}_item_2`;
  434. Box668.userData.name = `${name}_item_2`;
  435. Box669.name = `${name}_item_3`;
  436. Box669.userData.name = `${name}_item_3`;
  437. Box664.name = `${name}_item_4`;
  438. Box664.userData.name = `${name}_item_4`;
  439. Box667.name = `${name}_item_5`;
  440. Box667.userData.name = `${name}_item_5`;
  441. Box670.name = `${name}_item_6`;
  442. Box670.userData.name = `${name}_item_6`;
  443. },
  444. // 设置光伏位置 颜色
  445. setLightPosition: function (rootNode, obj) {
  446. let Box660 = obj.getObjectByName("Box660");
  447. let Box666 = obj.getObjectByName("Box666");
  448. let Box662 = obj.getObjectByName("Box662");
  449. let Box663 = obj.getObjectByName("Box663");
  450. let Box659 = obj.getObjectByName("Box659");
  451. let Box665 = obj.getObjectByName("Box665");
  452. obj.remove(Box660);
  453. obj.remove(Box666);
  454. obj.remove(Box662);
  455. obj.remove(Box663);
  456. obj.remove(Box659);
  457. obj.remove(Box665);
  458. // 大武口
  459. let light_dwk = obj.clone(true);
  460. light_dwk.position.set(0.28, 0.13, -35);
  461. this.setLightName(light_dwk, "light_dwk");
  462. rootNode.add(light_dwk);
  463. // 平罗
  464. let light_pl = obj.clone(true);
  465. light_pl.position.set(1.5, 0.13, -40);
  466. this.setLightName(light_pl, "light_pl");
  467. rootNode.add(light_pl);
  468. // 马场湖
  469. let light_mch = obj.clone(true);
  470. light_mch.position.set(-25, 2, -8);
  471. this.setLightName(light_mch, "light_mch");
  472. rootNode.add(light_mch);
  473. // 宣和
  474. let light_xh = obj.clone(true);
  475. light_xh.position.set(-18, 4, -3);
  476. this.setLightName(light_xh, "light_xh");
  477. rootNode.add(light_xh);
  478. // 海子井
  479. let light_hzj = obj.clone(true);
  480. light_hzj.position.set(-2, 2, -10);
  481. this.setLightName(light_hzj, "light_hzj");
  482. rootNode.add(light_hzj);
  483. // 改变颜色
  484. this.changeElColor(light_dwk, 'green'); // 大武口
  485. this.changeElColor(light_pl, '离线'); // 平罗
  486. this.changeElColor(light_mch, 'yx'); // 马场湖
  487. this.changeElColor(light_xh, 'red'); // 宣和
  488. this.changeElColor(light_hzj, '受累'); // 海子井
  489. },
  490. // 创建一个圆柱
  491. initCylinderGeometry: function(position, cr=2) {
  492. let geometry = new THREE.CylinderGeometry(cr, cr, 7, 64);
  493. //加载纹理
  494. let texture = new THREE.TextureLoader().load("static/3d/beam-texture.png");
  495. texture.wrapS = texture.wrapT = THREE.RepeatWrapping; //每个都重复
  496. texture.repeat.set(1, 1);
  497. texture.needsUpdate = true;
  498. let materials = [
  499. //圆柱侧面材质,使用纹理贴图
  500. new THREE.MeshBasicMaterial({
  501. map: texture,
  502. side: THREE.DoubleSide,
  503. transparent: true,
  504. }),
  505. //圆柱顶材质
  506. new THREE.MeshBasicMaterial({
  507. transparent: true,
  508. opacity: 0,
  509. side: THREE.DoubleSide,
  510. }),
  511. //圆柱底材质
  512. new THREE.MeshBasicMaterial({
  513. transparent: true,
  514. opacity: 0,
  515. side: THREE.DoubleSide,
  516. }),
  517. ];
  518. cylinder = new THREE.Mesh(geometry, materials);
  519. cylinder.position.set(position.x + 5.75, position.y + 8, position.z);
  520. scene.add(cylinder);
  521. },
  522. // 判断时候是可点击对象 返回null 或object
  523. getClickObject: function(intersects) {
  524. const names = ["fan", "build"];
  525. let x = true;
  526. let obj = null;
  527. for (let intersect of intersects) {
  528. let temObj = intersect.object;
  529. while (x) {
  530. for (let name of names) {
  531. if (temObj.name.indexOf(`${name}_`) == 0) {
  532. obj = temObj;
  533. x = false;
  534. break;
  535. }
  536. }
  537. if (x && temObj.parent) {
  538. temObj = temObj.parent;
  539. continue;
  540. }
  541. break;
  542. }
  543. }
  544. return obj;
  545. },
  546. // 鼠标浮动事件 // 这招不太行 鼠标每次移动都会发射射线 导致动画卡顿
  547. mοusemοveEvent: function(event) {
  548. event.preventDefault();
  549. let vector = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5);
  550. vector = vector.unproject(camera);
  551. let raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
  552. let intersects = raycaster.intersectObjects(scene.children, true);
  553. let obj = this.getClickObject(intersects);
  554. if (obj) {
  555. this.pointer = true;
  556. } else {
  557. this.pointer = false;
  558. }
  559. },
  560. // 设置每一个html的位置
  561. setEveryHTML: function() {
  562. this.htmlLayer.forEach(value => {
  563. if (value.show && value.position) {
  564. const screen = this.vector3ToScreen(value.position);
  565. value.x = screen.x + value.ox;
  566. value.y = screen.y + value.oy;
  567. }
  568. });
  569. },
  570. // 显示风机弹出层
  571. showFanInfo: function(position) {
  572. this.htmlLayer[0].position = {
  573. x: position.x,
  574. y: position.y,
  575. z: position.z,
  576. };
  577. this.htmlLayer[0].show = true;
  578. this.setEveryHTML();
  579. },
  580. // 隐藏风机弹出层
  581. hideFanInfo: function () {
  582. this.htmlLayer[0].show = false;
  583. },
  584. // 显示楼信息弹出层
  585. showBuildInfo: function(position) {
  586. this.htmlLayer[1].position = {
  587. x: position.x,
  588. y: position.y,
  589. z: position.z,
  590. };
  591. this.htmlLayer[1].show = true;
  592. this.setEveryHTML();
  593. },
  594. // 隐藏楼信息弹出层
  595. hideBuildInfo: function () {
  596. this.htmlLayer[1].show = false;
  597. },
  598. // 关闭弹出
  599. closeFanInfo: function () {
  600. this.htmlLayer[0].show = false;
  601. if (cylinder) {
  602. scene.remove(cylinder);
  603. }
  604. },
  605. // 关闭弹出
  606. closeBuildInfo: function () {
  607. this.htmlLayer[1].show = false;
  608. if (cylinder) {
  609. scene.remove(cylinder);
  610. }
  611. },
  612. // 点击事件
  613. clickEvent: function(event) {
  614. event.preventDefault();
  615. let vector = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5);
  616. vector = vector.unproject(camera);
  617. let raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
  618. let intersects = raycaster.intersectObjects(scene.children, true);
  619. // console.log(intersects); // 输出点击的对象信息
  620. // 找是否是可点击对象
  621. let obj = this.getClickObject(intersects);
  622. if (obj) {
  623. if (cylinder) {
  624. scene.remove(cylinder);
  625. }
  626. let isBuild = false;
  627. if (obj.name.indexOf('build_') == 0) {
  628. isBuild = true;
  629. }
  630. this.initCylinderGeometry(obj.position, isBuild ? 4 : 2);
  631. this.elClick(obj.name);
  632. if (isBuild) {
  633. this.showBuildInfo(obj.position);
  634. this.hideFanInfo();
  635. } else {
  636. this.showFanInfo(obj.position);
  637. this.hideBuildInfo();
  638. }
  639. }
  640. },
  641. // 元素点击事件 绑值
  642. elClick: function (name) {
  643. switch (name) {
  644. case "fan_mhs": // 麻黄山
  645. this.fanName = "麻黄山风场";
  646. break;
  647. case "fan_nss": // 牛首山
  648. this.fanName = "牛首山风场";
  649. break;
  650. case "fan_qs": // 青山
  651. this.fanName = "青山风场";
  652. break;
  653. case "fan_sbq": // 石板泉
  654. this.fanName = "石板泉风场";
  655. break;
  656. case "fan_xs": // 香山
  657. this.fanName = "香山风场";
  658. break;
  659. case "build_headquarters": // 大楼
  660. break;
  661. default:
  662. break;
  663. }
  664. },
  665. // 初始化云
  666. initCloud: function () {
  667. },
  668. // 初始化
  669. initThree: function() {
  670. this.initScene();
  671. // this.initAxesHelper();
  672. this.initCamera();
  673. this.initRender();
  674. this.initLight();
  675. this.initControls();
  676. this.initContent();
  677. this.initCloud();
  678. renderer.setAnimationLoop(this.animate);
  679. },
  680. // 动画
  681. animate: function() {
  682. if (this.playAnimation) {
  683. var delta = clock.getDelta();
  684. for (var i = 0; i < mixers.length; i++) {
  685. // 重复播放动画
  686. mixers[i].update(delta);
  687. }
  688. }
  689. renderer.render(scene, camera);
  690. },
  691. },
  692. // 生命周期钩子
  693. beforeCreate() {
  694. // 创建前
  695. },
  696. created() {
  697. // 创建后
  698. },
  699. beforeMount() {
  700. // 渲染前
  701. },
  702. mounted() {
  703. // 渲染后
  704. this.initThree();
  705. },
  706. beforeUpdate() {
  707. // 数据更新前
  708. },
  709. updated() {
  710. // 数据更新后
  711. },
  712. beforeUnmount() {
  713. // 销毁前
  714. renderer.setAnimationLoop(null);
  715. camera = null;
  716. scene = null;
  717. renderer = null;
  718. },
  719. };
  720. </script>
  721. <style lang="less" scoped>
  722. .three-model {
  723. position: relative;
  724. overflow: hidden;
  725. .three-html-layer {
  726. position: absolute;
  727. width: 0;
  728. height: 0;
  729. top: 0;
  730. left: 0;
  731. .three-html-dom {
  732. position: absolute;
  733. }
  734. .fan-info {
  735. padding: 14px;
  736. width: 149px;
  737. height: 48px;
  738. background: #1a1f2fd8;
  739. border: 1px solid #05bb4c;
  740. box-shadow: 0px 8px 17px 1px #05bb4c66;
  741. .fan-info-close {
  742. position: absolute;
  743. width: 20px;
  744. height: 20px;
  745. top: -10px;
  746. right: -10px;
  747. border-radius: 50%;
  748. background: #1a1f2fd8;
  749. border: 1px solid #05bb4c;
  750. box-shadow: 0px 8px 17px 1px #05bb4c66;
  751. cursor: pointer;
  752. color: @gray-l;
  753. transition: all 0.3s;
  754. display: flex;
  755. align-items: center;
  756. justify-content: center;
  757. font-size: 8px;
  758. &:hover {
  759. color: #05bb4c;
  760. }
  761. }
  762. .fan-info-name {
  763. text-align: center;
  764. }
  765. .fan-info-item {
  766. display: none;
  767. +.fan-info-item {
  768. margin-top: 13px;
  769. }
  770. .svg-icon {
  771. text-align: center;
  772. }
  773. .num {
  774. display: inline-block;
  775. width: 45px;
  776. text-align: right;
  777. }
  778. }
  779. }
  780. .build-info {
  781. width: 400px;
  782. height: 200px;
  783. position: relative;
  784. .build-info-close {
  785. position: absolute;
  786. width: 40px;
  787. height: 40px;
  788. top: 20px;
  789. right: 20px;
  790. border-radius: 50%;
  791. background: #1a1f2fd8;
  792. border: 1px solid #05bb4c;
  793. box-shadow: 0px 8px 17px 1px #05bb4c66;
  794. cursor: pointer;
  795. color: @gray-l;
  796. transition: all 0.3s;
  797. display: flex;
  798. align-items: center;
  799. justify-content: center;
  800. font-size: 14px;
  801. &:hover {
  802. color: #05bb4c;
  803. }
  804. }
  805. .build-info-item {
  806. width: 72px;
  807. height: 72px;
  808. border-radius: 50%;
  809. background: #1A1F2FE5;
  810. position: absolute;
  811. .build-info-item-num {
  812. font-size: 24px;
  813. font-weight: 500;
  814. color: #FFFFFF;
  815. text-align: center;
  816. line-height: 1.2;
  817. margin-top: 9px;
  818. }
  819. .build-info-item-text {
  820. font-size: 12px;
  821. color: #FFFFFF;
  822. font-weight: 400;
  823. text-align: center;
  824. }
  825. &.blue {
  826. border: 2px solid #1DA0D7;
  827. }
  828. &.green {
  829. border: 2px solid #05BB4C;
  830. }
  831. &.purple {
  832. border: 2px solid #323E6F;
  833. }
  834. &.orange {
  835. border: 2px solid #DB5520;
  836. }
  837. &.yellow {
  838. border: 2px solid #EDB32F;
  839. }
  840. }
  841. }
  842. }
  843. }
  844. </style>