index.vue 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174
  1. <template>
  2. <div class="bigBox">
  3. <div class="left" v-show="isShow">
  4. <div class="leftOne">
  5. <div class="one">
  6. <lineChart v-if="flag" :speed="agentSpeed" :indexData="propsIndex" :propsName="propsName" :times="times" :stop="isAnimation" />
  7. </div>
  8. <div class="two">
  9. <lineChartTwo v-if="flag" :speed="targetSpeed" :indexData="propsIndex" :propsName="propsName" :times="times" :stop="isAnimation" />
  10. </div>
  11. <div class="three">
  12. <lineChartThree v-if="flag" :speed="propsDistance" :reportShow="reportShow" :times="times" :stop="isAnimation" />
  13. </div>
  14. </div>
  15. <div class="leftTwo">
  16. <div class="twoD" ref="twoD" v-if="flag">
  17. <lineChartFive v-if="flag" :propsData="propsCoordinates" :indexData="propsIndex" :propsName="propsName" :stop="isAnimation" />
  18. </div>
  19. <div class="four">
  20. <lineChartFour v-if="flag" :propsName="propsName" :direction="propsDirection" :indexData="propsIndex" :times="times" :stop="isAnimation" />
  21. <!-- <discChart
  22. v-if="flag"
  23. :propsData="1"
  24. /> -->
  25. </div>
  26. </div>
  27. </div>
  28. <div id="container" ref="box">
  29. <div class="infoTag" ref="infoTag"></div>
  30. <div class="SVShow" v-show="isShow">
  31. <!-- <div class="modelInfo">场景详细信息!!!!</div> -->
  32. <!-- <p class="modelInfo">{{ senceInfo }}</p> -->
  33. <p v-for="(item, index) in senceInfo" :key="index">{{ item }}</p>
  34. </div>
  35. <div class="tool">
  36. <button class="btn" style="width: 120px;" @click="goback">返 回 上 一 级</button>
  37. <el-select v-model="fileName" :disabled="selectDisabled" placeholder="请选择文件" @change="getFileContext">
  38. <el-option v-for="item in fileList" :key="item.value" :label="item.label" :value="item.value"> </el-option>
  39. </el-select>
  40. <el-select v-model="playSpeedData" @change="playSpeed">
  41. <el-option v-for="item in speedOptions" :key="item.value" :label="item.label" :value="item.value"> </el-option>
  42. </el-select>
  43. <el-select v-model="algoData" placeholder="请选择算法">
  44. <el-option v-for="item in algoOptions" :key="item.value" :label="item.label" :value="item.value"> </el-option>
  45. </el-select>
  46. <button class="btn" @click="Suspend">暂 停</button>
  47. <button class="btn" @click="Begin">开 始</button>
  48. <button class="btn" @click="playBack" :disabled="!isShow">回 放</button>
  49. <input type="file" ref="fileInput" style="display: none" @change="importData" accept=".json" />
  50. </div>
  51. <div class="small" id="small" ref="small" v-show="isShow"></div>
  52. </div>
  53. <div class="right">
  54. <div class="textShow" v-show="isShow">
  55. <div class="textShowBox">
  56. <p v-for="(item, index) in eventText" :key="index">{{ item }}</p>
  57. <div class="report" v-show="reportShow">
  58. <p style="color: black" v-for="(item, index) in reportInfo" :key="index">
  59. {{ item }}
  60. </p>
  61. </div>
  62. </div>
  63. </div>
  64. </div>
  65. </div>
  66. </template>
  67. <script>
  68. import * as THREE from 'three'
  69. import TWEEN from '@tweenjs/tween.js'
  70. // 引入轨道控制器
  71. import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
  72. // import { BufferGeometryUtils } from 'three';
  73. // import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
  74. import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'
  75. import lineChart from '@/views/3dDemo/lineChart'
  76. import lineChartTwo from '@/views/3dDemo/lineChartTwo'
  77. import lineChartThree from '@/views/3dDemo/lineChartThree'
  78. import lineChartFour from '@/views/3dDemo/lineChartFour'
  79. import lineChartFive from '@/views/3dDemo/lineChartFive'
  80. // import discChart from "@/views/3dDemo/discChart";
  81. import '@/assets/css/global.css'
  82. let light
  83. let scene = null
  84. let sceneSmall = null
  85. let camera = null
  86. const SHADOW_MAP_WIDTH = 2048,
  87. SHADOW_MAP_HEIGHT = 1024
  88. let containerWidth, containerHeight
  89. const raycaster = new THREE.Raycaster()
  90. const mouse = new THREE.Vector2()
  91. export default {
  92. name: 'threeDemo',
  93. components: {
  94. lineChart,
  95. lineChartTwo,
  96. lineChartThree,
  97. lineChartFour,
  98. lineChartFive
  99. // discChart
  100. },
  101. data() {
  102. return {
  103. jsonData: null,
  104. // 场景
  105. // scene: null,
  106. // sceneSmall: null,
  107. // 渲染器
  108. renderer: null,
  109. smallRenderer: null,
  110. // 摄像机
  111. // camera: null,
  112. OrbitControl: null,
  113. // 物体形状
  114. geometry: null,
  115. // 物体材质
  116. material: [],
  117. // 坐标轴
  118. axes: null,
  119. // container
  120. container: null,
  121. // 网格模型
  122. mesh: null,
  123. // 模型
  124. models: [],
  125. // 时间和事件
  126. allTimes: [],
  127. // 所有事件
  128. allEvents: [],
  129. eventText: [],
  130. // 改变的距离
  131. distance: [],
  132. // 所有距离
  133. allDistance: [],
  134. particleSystem: null,
  135. // 倍速
  136. playSpeedData: 1,
  137. speedOptions: [
  138. { value: 1, label: '1倍速' },
  139. { value: 4, label: '4倍速' },
  140. { value: 8, label: '8倍速' },
  141. { value: 16, label: '16倍速' },
  142. { value: 32, label: '32倍速' },
  143. { value: 64, label: '64倍速' },
  144. { value: 128, label: '128倍速' }
  145. ],
  146. // 暂停和开始
  147. isAnimation: false,
  148. animationId: null,
  149. waveAnimateId: null,
  150. // 保存动画值
  151. animationValues: {},
  152. waveImg: require('../../assets/界面.jpg'),
  153. // 保存导入的文件
  154. file: null,
  155. fileList: [],
  156. fileName: null,
  157. isFile: false,
  158. isShow: false,
  159. flag: false,
  160. // props的值
  161. agentSpeed: [],
  162. targetSpeed: [],
  163. times: [],
  164. propsDistance: [],
  165. mixer: null,
  166. propsDirection: [],
  167. propsName: [],
  168. propsIndex: null,
  169. propsCoordinates: [],
  170. // 场景信息
  171. senceInfo: '',
  172. // 评估报告展示
  173. reportShow: false,
  174. reportInfo: null,
  175. selectDisabled: false,
  176. tagArray: [],
  177. waweShow: false,
  178. tween: null,
  179. tween1: null,
  180. tweenTargetPosition: null,
  181. maxNum: null,
  182. // 选择算法
  183. algoData: '',
  184. // 算法选项
  185. algoOptions: [
  186. {
  187. value: '选项1',
  188. label: '算法1'
  189. },
  190. {
  191. value: '选项2',
  192. label: '算法2'
  193. },
  194. {
  195. value: '选项3',
  196. label: '算法3'
  197. }
  198. ]
  199. }
  200. },
  201. mounted() {
  202. this.getFile()
  203. this.fileName=this.$route.params.fileName
  204. this.getFileContext()
  205. this.initContainer()
  206. this.initSmall()
  207. this.initOrbitControls()
  208. // this.createCube();
  209. this.createWave()
  210. this.waveModel()
  211. },
  212. beforeDestroy() {
  213. window.removeEventListener('mousemove', this.onMouseMove);
  214. this.clearSence()
  215. cancelAnimationFrame(this.animationId);
  216. },
  217. methods: {
  218. getFile() {
  219. this.$axios.get(`/api/`).then((res) => {
  220. console.log('res', res)
  221. if (res.status === 200) {
  222. if (res.data.length === 0) {
  223. this.$message.error('文件夹下暂无数据,请添加数据')
  224. }
  225. res.data.forEach((item, index) => {
  226. this.fileList.push({
  227. label: item,
  228. value: item
  229. })
  230. })
  231. } else {
  232. this.$message.warning('读文件列表有误')
  233. }
  234. })
  235. containerWidth = document.getElementById('container').clientWidth
  236. containerHeight = document.getElementById('container').clientHeight
  237. },
  238. goback(){
  239. window.removeEventListener('mousemove', this.onMouseMove);
  240. this.isAnimation = false
  241. cancelAnimationFrame(this.animationId);
  242. this.renderer.dispose();
  243. this.renderer.forceContextLoss();
  244. this.renderer.content = null;
  245. this.renderer = null;
  246. this.clearSence()
  247. this.$router.push({ name:'simulationSystem'})
  248. },
  249. // 初始化场景
  250. initContainer() {
  251. let container = this.$refs.box
  252. this.container = container
  253. // 创建场景
  254. scene = new THREE.Scene()
  255. // 创建相机、相机位置、设置相机方向(指向的场景对象)
  256. camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 1, 20000)
  257. camera.position.set(1000, 0, 1000)
  258. camera.lookAt(scene.position)
  259. // this.camera.lookAt(0, 0, 0)
  260. //创建渲染器
  261. this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
  262. // this.renderer.setClearAlpha(0.1);
  263. // this.renderer.setClearColor(0x000000,0)
  264. this.renderer.setSize(container.clientWidth, container.clientHeight)
  265. // 将渲染器添加到页面
  266. container.appendChild(this.renderer.domElement)
  267. // 环境光
  268. var ambienLight = new THREE.AmbientLight(0xcccccc, 0.5)
  269. scene.add(ambienLight)
  270. light = new THREE.DirectionalLight(0xffffff, 3)
  271. light.position.set(0, 1500, 1000)
  272. light.castShadow = true
  273. light.shadow.camera.top = 2000
  274. light.shadow.camera.bottom = -2000
  275. light.shadow.camera.left = -2000
  276. light.shadow.camera.right = 2000
  277. light.shadow.camera.near = 1200
  278. light.shadow.camera.far = 2500
  279. light.shadow.bias = 0.0001
  280. light.shadow.mapSize.width = SHADOW_MAP_WIDTH
  281. light.shadow.mapSize.height = SHADOW_MAP_HEIGHT
  282. scene.add(light)
  283. // this.axes = new THREE.AxesHelper(500)
  284. // this.scene.add(this.axes)
  285. },
  286. // 初始化小窗口
  287. initSmall() {
  288. let small = this.$refs.small
  289. sceneSmall = new THREE.Scene()
  290. const geometry = new THREE.CircleGeometry()
  291. const material = new THREE.MeshStandardMaterial({})
  292. const mesh = new THREE.Mesh(geometry, material)
  293. sceneSmall.add(mesh)
  294. //创建渲染器
  295. this.smallRenderer = new THREE.WebGLRenderer()
  296. small.appendChild(this.smallRenderer.domElement)
  297. this.axes = new THREE.AxesHelper(2500)
  298. sceneSmall.background = new THREE.Color('#000')
  299. sceneSmall.add(this.axes)
  300. },
  301. // 初始化控制器
  302. initOrbitControls() {
  303. // 创建轨道控制器 相机围绕模型看到的角度
  304. this.OrbitControl = new OrbitControls(camera, this.renderer.domElement)
  305. // 设置轨道自然
  306. this.OrbitControl.enableDamping = true
  307. this.OrbitControl.enableRotate = false //禁止旋转
  308. this.OrbitControl.keys = {
  309. LEFT: 'ArrowLeft', // left arrow
  310. UP: 'ArrowUp', // up arrow
  311. RIGHT: 'ArrowRight', // right arrow
  312. BOTTOM: 'ArrowBottom' // down arrow
  313. }
  314. // 设置惯性
  315. this.OrbitControl.update()
  316. this.addEvent(window, 'keydown', this.onKeyDown)
  317. },
  318. onKeyDown(e) {
  319. // const camera = camera
  320. const controls = this.OrbitControl
  321. if (!camera || !controls) {
  322. return
  323. }
  324. const sensitivity = 3 // you may want to put it into settings
  325. const p = new THREE.Vector3() // camera's position
  326. camera.getWorldPosition(p)
  327. const t = controls.target // target point
  328. const newTarget = t.clone()
  329. if (e.code === 'ArrowLeft' || e.code === 'ArrowRight') {
  330. const ANGLE = sensitivity
  331. let theta = (Math.PI * ANGLE) / 180
  332. if (e.code === 'ArrowLeft') {
  333. theta = -theta
  334. }
  335. const newPos = new THREE.Vector3()
  336. newPos.x = p.x * Math.cos(theta) - p.z * Math.sin(theta)
  337. newPos.z = p.z * Math.cos(theta) + p.x * Math.sin(theta)
  338. camera.position.set(newPos.x, p.y, newPos.z)
  339. controls.target = newTarget
  340. controls.update()
  341. } else if (e.code === 'ArrowUp' || e.code === 'ArrowDown') {
  342. const ANGLE2 = sensitivity
  343. let theta2 = (Math.PI * ANGLE2) / 180
  344. const distVec = new THREE.Vector3(t.x - p.x, t.y - p.y, t.z - p.z)
  345. const dist = distVec.length()
  346. const deltaY = t.y - p.y
  347. if (e.code === 'ArrowDown') {
  348. theta2 = -theta2
  349. }
  350. const angle = Math.asin(deltaY / dist) + theta2
  351. if (angle < -Math.PI / 2 || angle > Math.PI / 2) {
  352. return // cannot rotate that much
  353. }
  354. const newDeltaY = Math.sin(angle) * dist
  355. newTarget.y = t.y + (newDeltaY - deltaY)
  356. controls.target = newTarget
  357. controls.update()
  358. }
  359. },
  360. addEvent(target, type, handler) {
  361. target.addEventListener(type, handler)
  362. },
  363. // 创建立方体
  364. createCube() {
  365. const size = this.maxNum * 2
  366. this.geometry = new THREE.BoxGeometry(size, size, size)
  367. // this.geometry = new THREE.SphereGeometry(500, 60, 40);
  368. this.geometry.scale(1, 1, 1)
  369. // 创建矩形网格
  370. this.mesh = new THREE.Mesh(this.geometry, this.material)
  371. this.mesh.geometry.scale(1, 1, 1)
  372. scene.add(this.mesh) //网格模型添加到场景中
  373. },
  374. // 动画
  375. animate(modelInfo) {
  376. console.log("222");
  377. if (this.isAnimation) {
  378. setTimeout(() => {
  379. this.animationId = requestAnimationFrame(() => this.animate(modelInfo))
  380. this.modelMove(modelInfo)
  381. // 渲染场景
  382. this.tween?.update()
  383. // this.tween1?.update();
  384. this.renderer?.render(scene, camera)
  385. this.smallRenderer.render(sceneSmall, camera)
  386. }, 150)
  387. }
  388. },
  389. // 创建扩散波
  390. createWave() {
  391. // var textureLoader = new THREE.TextureLoader();
  392. // 底部半径、顶部半径、高度和面的数量
  393. const geometry = new THREE.CylinderGeometry(150, 150, 10, 500)
  394. const geometry2 = new THREE.CylinderGeometry(200, 200, 10, 500)
  395. const material = new THREE.MeshBasicMaterial({
  396. color: 0x0078d4,
  397. side: THREE.DoubleSide,
  398. transparent: true
  399. // map: textureLoader.load(this.waveImg),
  400. })
  401. const circle = new THREE.Mesh(geometry, [material]) //用数组只贴边面
  402. const circle2 = new THREE.Mesh(geometry2, [material]) //用数组只贴边面
  403. circle.name = 'circle'
  404. circle2.name = 'circle2'
  405. circle.position.set(-1000, 1000, -1000)
  406. circle2.position.set(-1000, 1000, -1000)
  407. scene.add(circle)
  408. scene.add(circle2)
  409. let s = 0,
  410. p = 0
  411. const scaleFactor = 3 // 调整这个值以控制波纹的扩大速度
  412. const render1 = () => {
  413. if (s > 160) {
  414. s = 0
  415. p = 160
  416. }
  417. circle.scale.set(1 + (s / 60) * scaleFactor, 1, 1 + (s / 60) * scaleFactor)
  418. circle2.scale.set(1 + (s / 60) * scaleFactor, 1, 1 + (s / 60) * scaleFactor)
  419. circle.material[0].opacity = p / 160
  420. circle2.material[0].opacity = p / 160
  421. s++
  422. p--
  423. this.waveAnimateId = requestAnimationFrame(render1)
  424. }
  425. render1()
  426. },
  427. // 引入发射扩散波模型
  428. waveModel() {
  429. const objLoader = new OBJLoader()
  430. const material = new THREE.MeshStandardMaterial({ color: 0xfbc31d })
  431. objLoader.load('/models/obj.obj', (model) => {
  432. model.scale.set(0.03, 0.03, 0.03)
  433. model.name = 'waveModel'
  434. model.traverse(function (child) {
  435. if (child instanceof THREE.Mesh) {
  436. child.material = material // 设置模型的材质
  437. // child.name='waveModel'
  438. }
  439. })
  440. model.position.set(-1000, 1000, -1000)
  441. scene.add(model)
  442. })
  443. },
  444. // 模型加载
  445. createRole(data, callback) {
  446. const [index, modelInfo] = data
  447. const material = new THREE.MeshStandardMaterial({
  448. color: modelInfo.color
  449. })
  450. const objLoader = new OBJLoader()
  451. objLoader.load(
  452. this.models[index].path,
  453. (model) => {
  454. const size = this.models[index].scale
  455. model.scale.set(size, size, size)
  456. model.position.copy(this.models[index].route[0])
  457. model.traverse(function (child) {
  458. if (child instanceof THREE.Mesh) {
  459. child.material = material // 设置模型的材质
  460. }
  461. })
  462. this.models[index].model = model
  463. scene.add(model)
  464. // 在模型加载成功后执行回调
  465. if (typeof callback === 'function') {
  466. callback(model)
  467. }
  468. },
  469. undefined,
  470. (error) => {
  471. console.error(error)
  472. }
  473. )
  474. },
  475. // 控制模型运动
  476. modelMove(modelInfo) {
  477. if (modelInfo.pathIndex === modelInfo.route.length && this.eventText.length === this.allEvents.length) {
  478. this.reportShow = true
  479. return
  480. }
  481. if (this.playSpeedData <= modelInfo.route.length - modelInfo.pathIndex) {
  482. let index = modelInfo.pathIndex
  483. const currentPoint = modelInfo.route[index]
  484. if (modelInfo.route.length - modelInfo.pathIndex > 1) {
  485. const nextPoint = modelInfo.route[index + this.playSpeedData]
  486. const direction = new THREE.Vector3().subVectors(nextPoint, currentPoint).normalize()
  487. // 计算模型当前位置向下一个位置的方向向量来确定模型的朝向
  488. modelInfo.model.lookAt(modelInfo.model.position.clone().add(direction))
  489. modelInfo.movedRoute.push(modelInfo.model?.position.clone())
  490. modelInfo.model.position.copy(modelInfo.route[index])
  491. // 调用生成轨迹函数
  492. this.makeCurve(modelInfo)
  493. }
  494. if (modelInfo.name === 'agent' || modelInfo.name === 'agent1') {
  495. // this.agentSpeed.push(modelInfo.speed[index]);
  496. // this.times.push(this.allTimes[index]);
  497. this.times.push(...this.allTimes.slice(modelInfo.prevIndex, index))
  498. // this.propsDistance.push(...this.distance.slice(modelInfo.prevIndex,index));
  499. this.propsIndex = index
  500. // 展示生成事件文字
  501. this.showText(this.allTimes[index])
  502. }
  503. if (modelInfo.name.startsWith('agent')) {
  504. this.agentSpeed[modelInfo.name]?.push(...modelInfo.speed.slice(modelInfo.prevIndex, index))
  505. // this.propsDistance.push(...this.distance.slice(modelInfo.prevIndex,index));
  506. // 距离
  507. this.showDistance(this.allTimes[index], modelInfo.name)
  508. if (this.allDistance[modelInfo.name][index] <= 3) {
  509. // && !this.waweShow
  510. for (let i = scene.children.length - 1; i >= 0; i--) {
  511. const child = scene.children[i]
  512. if (child.name === 'circle2' || child.name === 'circle' || child.name === 'waveModel') {
  513. scene.remove(child)
  514. child.children.forEach((child) => {
  515. child.remove(child)
  516. if (child instanceof THREE.Mesh) {
  517. child.geometry.dispose()
  518. child.material.dispose()
  519. }
  520. })
  521. // cancelAnimationFrame(this.waveAnimateId);
  522. // this.waveAnimateId = null;
  523. }
  524. }
  525. // this.waweShow = true;
  526. let position = {
  527. x: modelInfo.route[index].x - 30,
  528. y: modelInfo.route[index].y + 30,
  529. z: modelInfo.route[index].z - 30
  530. }
  531. this.tween = new TWEEN.Tween(camera.position)
  532. .to(position, 2000)
  533. .onUpdate(() => {
  534. camera.lookAt(this.tweenTargetPosition.x, this.tweenTargetPosition.y, this.tweenTargetPosition.z)
  535. this.OrbitControl.target = new THREE.Vector3(this.tweenTargetPosition.x, this.tweenTargetPosition.y, this.tweenTargetPosition.z)
  536. })
  537. .start()
  538. }
  539. }
  540. this.propsDirection[modelInfo.name]?.push(...modelInfo.direction.slice(modelInfo.prevIndex, index))
  541. if (modelInfo.name === 'target') {
  542. // this.targetSpeed.push(modelInfo.speed[index]);
  543. this.targetSpeed.push(...modelInfo.speed.slice(modelInfo.prevIndex, index))
  544. this.tweenTargetPosition = {
  545. x: modelInfo.route[index].x,
  546. y: modelInfo.route[index].y,
  547. z: modelInfo.route[index].z
  548. }
  549. }
  550. modelInfo.prevIndex = index
  551. modelInfo.pathIndex += this.playSpeedData
  552. } else {
  553. this.playSpeedData = 1
  554. }
  555. },
  556. makeCurve(modelInfo) {
  557. const particleGeometry = new THREE.BufferGeometry()
  558. const particleMaterial = new THREE.PointsMaterial({
  559. color: modelInfo.curveColor,
  560. size: 3
  561. })
  562. const routePoint = modelInfo.route.length
  563. if (modelInfo.movedRoute.length >= 2) {
  564. const curve = new THREE.CatmullRomCurve3(modelInfo.movedRoute)
  565. curve.curveType = 'catmullrom'
  566. curve.closed = false
  567. curve.tension = 0.5
  568. const points = curve.getPoints(routePoint) // 获取曲线上的n个点
  569. particleGeometry.setFromPoints(points)
  570. this.particleSystem = new THREE.Points(particleGeometry, particleMaterial)
  571. if (modelInfo.name.startsWith('agent')) {
  572. this.particleSystem.userData.distance = this.allDistance[modelInfo.name][modelInfo.pathIndex]
  573. this.particleSystem.userData.time = this.allTimes[modelInfo.pathIndex]
  574. this.particleSystem.userData.name = modelInfo.name
  575. }
  576. scene.add(this.particleSystem)
  577. this.tagArray.push(this.particleSystem)
  578. } else {
  579. // console.error('Not enough points to create curve.')
  580. return
  581. }
  582. },
  583. onMouseMove(event) {
  584. const infoTag = this.$refs.infoTag
  585. mouse.x = (event.layerX / containerWidth) * 2 - 1
  586. mouse.y = -(event.layerY / containerHeight) * 2 + 1
  587. raycaster.setFromCamera(mouse, camera)
  588. var intersects = raycaster.intersectObjects(this.tagArray)
  589. if (intersects.length === 0) {
  590. infoTag.style.display = 'none'
  591. return
  592. } //有相交物体时
  593. if (intersects.length > 0) {
  594. let object = intersects[0].object
  595. if (object.userData.name?.startsWith('agent')) {
  596. infoTag.innerHTML = `名称:雷${object.userData.name.substr(-1)}
  597. <br>时间:${object.userData.time}
  598. <br>距离:${object.userData.distance}km`
  599. infoTag.style.display = 'block'
  600. infoTag.style.cursor = 'pointer'
  601. infoTag.style.left = event.layerX + 10 + 'px' //一定要拼接 px
  602. infoTag.style.top = event.layerY + 10 + 'px'
  603. } else {
  604. return
  605. }
  606. }
  607. },
  608. getFileContext() {
  609. if(!this.fileName){
  610. this.$message.warning('请手动选择回放的数据')
  611. return
  612. }
  613. const data = { fileName: this.fileName }
  614. this.$axios.post('/api/fileContent', data).then((res) => {
  615. // console.log("11",res);
  616. if (res.status === 200 && res.data.code === 200) {
  617. this.jsonData = res.data.data
  618. this.handelData(this.jsonData)
  619. this.selectDisabled = true
  620. } else {
  621. this.$message.warning('文件读取内容失败')
  622. }
  623. })
  624. // this.$axios.get('/api/readTxt').then((res)=>{
  625. // console.log("11",res);
  626. // })
  627. },
  628. // 触发文件上传输入框的点击事件
  629. importBtn() {
  630. this.$refs.fileInput.click()
  631. },
  632. // 导入数据
  633. importData(event) {
  634. // importData(file) {
  635. const file = event?.target.files[0] // 获取上传的文件
  636. // console.log("导入",file);
  637. if (file) {
  638. const reader = new FileReader() // 创建一个文件阅读器对象
  639. reader.onload = (e) => {
  640. try {
  641. const content = e.target.result // 获取文件内容
  642. const jsonContent = JSON.parse(content) // 解析 JSON
  643. this.jsonData = jsonContent // 将 JSON 数据存储在组件的数据中
  644. this.jsonData = jsonContent.filter((item, index) => index % 3 === 0 || item[1].events.length !== 0)
  645. this.handelData(this.jsonData)
  646. } catch (error) {
  647. console.error('Error reading the file:', error)
  648. }
  649. }
  650. reader.readAsText(file) // 以文本形式读取文件内容
  651. }
  652. },
  653. handelData(rawData1) {
  654. const rawData = rawData1.filter((item, index) => {
  655. return index % 10 === 0 || item[1].events.length !== 0 || item[1].report
  656. })
  657. // console.log('rawData', rawData)
  658. const color = [0xc1232b, 0x5470c6, 0xfac858, 0xee6666]
  659. this.models = []
  660. this.allTimes = []
  661. this.allEvents = []
  662. this.direction = []
  663. this.distance = []
  664. let beginCoordinate = null
  665. let maxData = 0
  666. const agents = {}
  667. const target = {
  668. path: '/models/qianting.obj',
  669. name: 'target',
  670. route: [],
  671. pathIndex: 0,
  672. model: null,
  673. scale: 15,
  674. isMoving: false,
  675. movedRoute: [],
  676. color: 0x3cf44b,
  677. curveColor: 0x3cf44b,
  678. speed: [],
  679. direction: [],
  680. nowSpeed: null,
  681. coordinate: [],
  682. prevIndex: 0
  683. }
  684. this.senceInfo = rawData[0][1].info
  685. rawData.forEach((data) => {
  686. if (data[1].events.length !== 0) {
  687. this.allEvents.push({
  688. time: data[0],
  689. event: data[1].events
  690. })
  691. }
  692. if (data[1].report) {
  693. this.reportInfo = data[1].report
  694. }
  695. this.allTimes.push(data[0])
  696. Object.keys(data[1].distance).forEach((key, index) => {
  697. const dis = Number(parseFloat(data[1].distance[key]).toFixed(2))
  698. if (!this.distance[key]) {
  699. this.distance[key] = [[parseFloat(data[0]), dis]]
  700. this.allDistance[key] = []
  701. }
  702. this.allDistance[key].push(dis)
  703. if (this.distance[key][this.distance[key].length - 1][1] !== dis) {
  704. this.distance[key].push([parseFloat(data[0]), dis])
  705. }
  706. if (!this.propsDistance[key]) {
  707. this.propsDistance[key] = []
  708. this.propsDistance[key].push(this.distance[key][0])
  709. }
  710. })
  711. // 处理代理数据
  712. Object.keys(data[1]).forEach((key, index) => {
  713. if (key.startsWith('agent')) {
  714. if (!agents[key]) {
  715. agents[key] = {
  716. path: '/models/yulei2.obj',
  717. name: key,
  718. route: [],
  719. pathIndex: 0,
  720. model: null,
  721. scale: 15,
  722. isMoving: false,
  723. movedRoute: [],
  724. color: 0xe6194b,
  725. curveColor: color[index],
  726. speed: [],
  727. direction: [],
  728. nowSpeed: null,
  729. coordinate: [],
  730. prevIndex: 0
  731. }
  732. }
  733. if (!beginCoordinate) {
  734. beginCoordinate = [data[1][key].agent_center_x, data[1][key].agent_center_y]
  735. }
  736. agents[key].route.push(new THREE.Vector3(data[1][key].agent_center_x / 2, 100, data[1][key].agent_center_y / 2))
  737. agents[key].speed.push(parseInt(data[1][key].agent_speed))
  738. agents[key].direction.push(
  739. data[1][key].agent_direction > 180 ? -(360 - parseInt(data[1][key].agent_direction)) : parseInt(data[1][key].agent_direction)
  740. )
  741. agents[key].coordinate.push([data[1][key].agent_center_x - beginCoordinate[0], data[1][key].agent_center_y - beginCoordinate[1]])
  742. maxData = Math.max(data[1][key].agent_center_x, data[1][key].agent_center_y, maxData)
  743. }
  744. })
  745. // 处理目标数据
  746. const targetX = data[1].target.target_center_x
  747. const targetY = data[1].target.target_center_y
  748. target.route.push(new THREE.Vector3(targetX / 2, 100, targetY / 2))
  749. target.speed.push(parseInt(data[1].target.target_speed))
  750. target.direction.push(
  751. data[1].target.target_direction > 180 ? -(360 - parseInt(data[1].target.target_direction)) : parseInt(data[1].target.target_direction)
  752. )
  753. target.coordinate.push([targetX - beginCoordinate[0], targetY - beginCoordinate[1]])
  754. maxData = Math.max(targetX, targetY, maxData)
  755. })
  756. this.maxNum = Math.ceil(maxData)
  757. this.models = [].concat(...[Object.values(agents), target])
  758. // console.log("this.models", this.models);
  759. // console.log("this.distance",this.distance);
  760. // console.log("this.allDistance",this.allDistance);
  761. this.models.forEach((modelInfo, index) => {
  762. // this.propsDirection.push(modelInfo.direction);
  763. this.propsDirection[modelInfo.name] = []
  764. this.propsName.push(modelInfo.name)
  765. this.propsCoordinates[modelInfo.name] = modelInfo.coordinate
  766. if (modelInfo.name.startsWith('agent')) {
  767. this.agentSpeed[modelInfo.name] = []
  768. }
  769. this.createRole([index, modelInfo], () => {
  770. this.isAnimation = true
  771. this.animate(modelInfo)
  772. this.isFile = true
  773. })
  774. })
  775. this.isShow = true
  776. this.flag = true
  777. this.createCube()
  778. window.addEventListener('mousemove', this.onMouseMove)
  779. },
  780. // 倍速播放
  781. playSpeed() {
  782. this.playSpeedData = parseInt(this.playSpeedData)
  783. },
  784. // 暂停按钮
  785. Suspend() {
  786. this.isAnimation = false
  787. },
  788. // 开始按钮
  789. Begin() {
  790. if (!this.isAnimation) {
  791. this.isAnimation = true
  792. this.models.forEach((modelInfo) => {
  793. this.animate(modelInfo) // 重新开始动画循环
  794. })
  795. }
  796. },
  797. showDistance(time, name) {
  798. const filterData = this.distance[name]?.filter((item) => item[0] <= parseFloat(time))
  799. if (filterData?.length !== this.propsDistance[name]?.length) {
  800. this.propsDistance[name] = filterData
  801. }
  802. //console.log('propsDistance', this.propsDistance)
  803. // if(this.distance[name].some(item => item[0] === parseFloat(time)) && !(this.propsDistance[name].some(item => item[0] === parseFloat(time)))){
  804. // this.propsDistance[name].push(this.distance[name].find(item => item[0] === parseFloat(time)));
  805. // console.log("this.propsDistance",this.propsDistance);
  806. // }
  807. },
  808. // 显示文本解释
  809. showText(index) {
  810. // allEvent 所有有事件的时间事件集合
  811. this.allEvents?.forEach((item) => {
  812. if (parseFloat(index) >= parseFloat(item.time)) {
  813. if (!this.eventText?.includes(item.event)) {
  814. this.eventText.push(item.event)
  815. }
  816. }
  817. })
  818. },
  819. // 回放!!!!
  820. playBack() {
  821. // this.clearSence()
  822. this.Suspend()
  823. this.clearSence()
  824. this.flag = false
  825. if (this.waweShow) {
  826. this.createWave()
  827. this.waweShow = false
  828. }
  829. setTimeout(() => {
  830. this.clearSence()
  831. cancelAnimationFrame(this.animationId)
  832. this.animationId = null
  833. this.waveModel()
  834. this.handelData(this.jsonData)
  835. }, 100)
  836. },
  837. // 清除场景中的信息
  838. clearSence() {
  839. for (let i = scene.children.length - 1; i >= 0; i--) {
  840. const child = scene.children[i]
  841. if (child instanceof THREE.Group) {
  842. scene.remove(child)
  843. child.children.forEach((child) => {
  844. child.remove(child)
  845. // 如果子对象是 Mesh,则需要手动释放几何体和材质
  846. if (child instanceof THREE.Mesh) {
  847. child.geometry.dispose()
  848. child.material.dispose()
  849. }
  850. })
  851. }
  852. if (child instanceof THREE.Points) {
  853. scene.remove(child)
  854. if (child && child.geometry) {
  855. child.geometry.dispose()
  856. } else if (child && child.material) {
  857. child.material.dispose()
  858. } else {
  859. console.log('未找到数据')
  860. }
  861. }
  862. if (child === this.particleSystem) {
  863. scene.remove(child)
  864. if (child.geometry) {
  865. child.geometry.dispose()
  866. }
  867. if (child.material) {
  868. child.material.dispose()
  869. }
  870. // 可以在这里将 this.particleSystem 设为 null,以防止后续误用
  871. this.particleSystem = null
  872. }
  873. //清除数据
  874. this.models = []
  875. this.allTimes = []
  876. this.allEvents = []
  877. this.distance = []
  878. this.direction = []
  879. this.eventText = []
  880. this.agentSpeed = []
  881. this.targetSpeed = []
  882. this.times = []
  883. this.propsDistance = []
  884. this.propsDirection = []
  885. }
  886. }
  887. }
  888. }
  889. </script>
  890. <style scoped>
  891. /* *{
  892. padding: 0;
  893. margin: 0;
  894. box-sizing:content-box
  895. } */
  896. .bigBox {
  897. width: 100%;
  898. height: 100%;
  899. /* width: 5760px;
  900. height: 1080px; */
  901. display: flex;
  902. position: relative;
  903. }
  904. #container {
  905. width: 1920px;
  906. height: 1080px;
  907. position: absolute;
  908. left: 1920px;
  909. top: 0;
  910. }
  911. .infoTag {
  912. display: none;
  913. position: absolute;
  914. top: 0;
  915. left: 0;
  916. background-color: #fff;
  917. border: 1px solid #ccc;
  918. padding: 5px;
  919. /* margin: 4px; */
  920. }
  921. .right {
  922. width: 1800px;
  923. height: 1080px;
  924. position: absolute;
  925. right: 0;
  926. top: 0;
  927. }
  928. .left {
  929. width: 1920px;
  930. height: 1080px;
  931. display: flex;
  932. flex-direction: row;
  933. }
  934. .leftOne {
  935. width: 900px;
  936. height: 1080px;
  937. position: absolute;
  938. top: 0;
  939. left: 50px;
  940. display: flex;
  941. flex-direction: column;
  942. justify-content: space-around;
  943. }
  944. .leftTwo {
  945. width: 800px;
  946. height: 1080px;
  947. position: absolute;
  948. top: 0;
  949. left: 1050px;
  950. display: flex;
  951. flex-direction: column;
  952. justify-content: space-around;
  953. }
  954. .one {
  955. background: url('../../assets/鱼雷速度.png') center no-repeat;
  956. }
  957. .two {
  958. background: url('../../assets/目标速度.jpg') center no-repeat;
  959. }
  960. .three {
  961. background: url('../../assets/雷目距离.jpg') center no-repeat;
  962. /* background: url("../../assets/目标速度.jpg") center no-repeat; */
  963. }
  964. .one,
  965. .two,
  966. .three {
  967. width: 900px;
  968. height: 300px;
  969. background-size: 900px 300px;
  970. position: relative;
  971. /* top: 50%; */
  972. /* margin: 1.875rem 1.25rem 0.9375rem 0.9375rem; */
  973. }
  974. .four {
  975. background: url('../../assets/航向角.jpg') center no-repeat;
  976. }
  977. /* .two,
  978. .four {
  979. left: 700px;
  980. } */
  981. .four {
  982. width: 900px;
  983. height: 300px;
  984. background-size: 900px 300px;
  985. position: relative;
  986. margin: 25px 0 0 -50px;
  987. }
  988. .twoD {
  989. background: url('../../assets/右框.jpg') center no-repeat;
  990. }
  991. .twoD {
  992. width: 850px;
  993. height: 650px;
  994. background-size: 850px 650px;
  995. margin-top: 25px;
  996. margin-left: -20px;
  997. z-index: 9999;
  998. }
  999. .SVShow {
  1000. background: url('../../assets/右框.jpg') center no-repeat;
  1001. }
  1002. .SVShow {
  1003. width: 430px;
  1004. height: 300px;
  1005. background-size: 430px 300px;
  1006. display: flex;
  1007. align-items: center;
  1008. flex-direction: column;
  1009. position: absolute;
  1010. z-index: 9999;
  1011. top: 8%;
  1012. }
  1013. .SVShow >>> p {
  1014. width: 380px;
  1015. color: white;
  1016. font-size: 1.6rem;
  1017. /* margin-left: 30px; */
  1018. margin: 0;
  1019. padding: 10px;
  1020. overflow: hidden;
  1021. }
  1022. .modelInfo {
  1023. /* height: 50px;
  1024. line-height: 50px; */
  1025. margin: 20px;
  1026. color: white;
  1027. font-size: 1.75rem;
  1028. }
  1029. .report {
  1030. width: 1630px;
  1031. background-color: white;
  1032. margin: 30px;
  1033. padding-top: 15px;
  1034. font-family: Simsun;
  1035. }
  1036. .tool {
  1037. width: 1100px;
  1038. height: 80px;
  1039. line-height: 80px;
  1040. position: absolute;
  1041. bottom: 0;
  1042. font-size: 16px;
  1043. margin: 20px;
  1044. }
  1045. .btn {
  1046. width: 80px;
  1047. height: 40px;
  1048. background-color: #01192a;
  1049. /* background: url("../../assets/界面.jpg") center no-repeat;
  1050. background-size: 80px 40px; */
  1051. color: #02d1fd;
  1052. font-weight: 700;
  1053. box-shadow: inset 0 0 7px 3px #215c76;
  1054. margin: 0 15px;
  1055. /* inset 表示内阴影 */
  1056. }
  1057. .textShow {
  1058. background: url('../../assets/右框.jpg') center no-repeat;
  1059. }
  1060. .textShow {
  1061. width: 1700px;
  1062. height: 1000px;
  1063. background-size: 1700px 1000px;
  1064. /* opacity: 0.4; */
  1065. position: absolute;
  1066. z-index: 9999;
  1067. top: 40px;
  1068. overflow: hidden;
  1069. }
  1070. .textShowBox {
  1071. width: 1800px;
  1072. height: 950px;
  1073. overflow: hidden;
  1074. overflow-y: scroll;
  1075. margin-top: 25px;
  1076. }
  1077. .textShow >>> p {
  1078. width: 1600px;
  1079. color: white;
  1080. font-size: 2.125rem;
  1081. padding: 0px 30px;
  1082. /* margin-left: 30px; */
  1083. overflow: hidden;
  1084. }
  1085. .small {
  1086. width: 9.375rem;
  1087. height: 9.375rem;
  1088. overflow: hidden;
  1089. border: 1px solid gray;
  1090. border-radius: 50%;
  1091. position: absolute;
  1092. background-color: rgb(17, 17, 17);
  1093. bottom: 3.125rem;
  1094. right: 0;
  1095. }
  1096. .small >>> canvas {
  1097. transform: translateX(-73px);
  1098. }
  1099. ::v-deep .el-select {
  1100. width: 150px;
  1101. margin: 0 20px;
  1102. }
  1103. ::v-deep .el-input__inner {
  1104. background-color: transparent;
  1105. color: white;
  1106. }
  1107. ::v-deep .el-select-dropdown__item {
  1108. color: #fff;
  1109. }
  1110. ::v-deep .el-scrollbar,
  1111. ::v-deep .el-select-dropdown {
  1112. background-color: transparent !important;
  1113. color: #fff !important;
  1114. }
  1115. ::v-deep .el-scrollbar__wrap,
  1116. ::v-deep .el-select-dropdown__list {
  1117. background-color: #0b1a37;
  1118. color: #fff !important;
  1119. }
  1120. ::v-deep .el-select-dropdown__item.hover,
  1121. .el-select-dropdown__item:hover {
  1122. background-color: rgba(0, 0, 0, 0.3);
  1123. color: #fff;
  1124. }
  1125. ::v-deep .el-select .el-input.is-disabled .el-input__inner {
  1126. background-color: transparent;
  1127. }
  1128. </style>