当前位置:   article > 正文

three.js+vue智慧城市web3d污水厂数字孪生_vue2+threejs的数组孪生3d

vue2+threejs的数组孪生3d

three.js+vue智慧城市blender建模web3d污水厂数字孪生源码

下面是三维场景主页面逻辑代码:

  1. <template>
  2. <div class="whole">
  3. <!-- threejs画布 -->
  4. <div id="threejs" ref="threejs"></div>
  5. <!-- 污水厂模型加载进度条 -->
  6. <a-progress
  7. :stroke-color="{
  8. from: '#00F5FF',
  9. to: '#4169E1',
  10. }"
  11. :percent="0.0"
  12. trailColor="#E8E8E8"
  13. status="active"
  14. class="progress"
  15. />
  16. <!-- 标签组件 -->
  17. <Label></Label>
  18. <!-- 巡检数据展示面板-->
  19. <div class="inspectPanel a-fadein" v-show="inspectPanelShow">
  20. <div class="panelTitle" id="panelTitle">曝气池</div>
  21. <div class="panelData">
  22. <div class="left">
  23. <div class="leftTitle">介绍</div>
  24. <div class="segment"></div>
  25. <div class="describe" id="describe"></div>
  26. </div>
  27. <div class="right">
  28. <div class="rightTitle">数据记录</div>
  29. <div class="segment"></div>
  30. <div class="record">
  31. <div class="main" id="panelData"></div>
  32. </div>
  33. </div>
  34. </div>
  35. </div>
  36. <!-- 巡检中 返回和状态按钮 -->
  37. <div class="inspect" v-show="props['selectedMenu'] === 'inspect'">
  38. <div class="common" @click="endInspect">
  39. <div class="return_icon" style=""></div>
  40. 返回
  41. </div>
  42. <div class="common" @click="inspectStateChange">
  43. <div :class="inspectState ? 'stop_icon' : 'continue_icon'"></div>
  44. {{ inspectState ? '暂停' : '继续' }}
  45. </div>
  46. </div>
  47. <!-- 巡检进度条 -->
  48. <progressBar v-show="props['selectedMenu'] === 'inspect'" :schedule="schedule" :inspectState="inspectState" @progressBarChange="progressBarChange"></progressBar>
  49. <!-- 巡检速度控制条 -->
  50. <speedControlBar v-show="props['selectedMenu'] === 'inspect'" :speed="speed" :inspectState="inspectState" @controlBarChange="controlBarChange"></speedControlBar>
  51. </div>
  52. </template>
  53. <script setup>
  54. import { ref, onMounted, watch } from 'vue';
  55. // 引入threejs
  56. import * as THREE from 'three';
  57. // 基础配置文件——场景、灯光、相机等
  58. import { scene, renderer, css2DRender, camera, controls } from './base/index.js';
  59. // 添加污水厂模型函数
  60. import { addSewageModel } from './addSewageModel/index.js';
  61. // 添加人物模型函数
  62. import { addPeopleModel, WalkAction } from './addPeopleModel/index.js';
  63. // 引入tween.js,用来创建动画
  64. import TWEEN from '@tweenjs/tween.js';
  65. // 引入标签组件
  66. import Label from './label/index.vue';
  67. // 引入人物2D标签、CSS2D渲染器、标签初始化函数和建筑标签组对象
  68. import { css2DPeopleLabel, initLabel, buildLabelGroup } from './label/index.js';
  69. // 引入创建水面函数
  70. import { createWaterPlane, waterPlaneGroup } from './waterPlane/index.js';
  71. import { inspectPathArr, inspectIndex, inspectPathIndex, inspectState, inspectPanelShow, inspectLinePointGroup, openInspection, inspectionParams } from './inspection/index.js';
  72. import progressBar from './progressBar/index.vue';
  73. import speedControlBar from './speedControlBar/index.vue';
  74. // 引入RGB加载器
  75. import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js';
  76. import { setPoolMaterial } from './poolMaterial/index';
  77. import Stats from 'three/examples/jsm/libs/stats.module.js';
  78. // 首页传值
  79. const props = defineProps([
  80. 'craftAnimationStatus', // 工艺动画状态,为true时开启播放相应的工艺动画
  81. 'craftAnimationType', // 工艺动画类型,根据此值决定动画的类型
  82. 'selectedMenu', // 首页底部菜单按钮选中项
  83. ]);
  84. // 传递事件
  85. const emit = defineEmits(['closeInspect', 'craftAnimationEnd']);
  86. // threejs容器
  87. const threejs = ref();
  88. // 污水厂模型
  89. let sewageModel = null;
  90. // 人物模型
  91. let people = null;
  92. // 人物动画播放器
  93. let animationMixer = null;
  94. // 当前巡检进度百分比值
  95. let schedule = ref(0);
  96. // 巡检的速度
  97. let speed = ref(0);
  98. let stats;
  99. onMounted(async () => {
  100. //创建stats对象
  101. stats = new Stats();
  102. threejs.value.appendChild(stats.domElement);
  103. threejs.value.appendChild(renderer.domElement);
  104. threejs.value.appendChild(css2DRender.domElement);
  105. const rgbeLoader = new RGBELoader();
  106. // 环境贴图
  107. let envMap = await rgbeLoader.loadAsync('./envMap.hdr');
  108. createEnvironment(envMap);
  109. // 异步加载污水厂模型
  110. sewageModel = await addSewageModel(envMap);
  111. // 添加人物模型、人物动画播放器
  112. const { peopleGroup, mixer } = await addPeopleModel();
  113. people = peopleGroup;
  114. // 相机添加到人物模型中
  115. people.add(camera);
  116. animationMixer = mixer;
  117. // 允许人物模型产生阴影
  118. people.castShadow = true;
  119. scene.add(sewageModel, people, inspectLinePointGroup);
  120. // 创建水面
  121. createWaterPlane(sewageModel, envMap);
  122. // 设置水池材质
  123. setPoolMaterial(sewageModel);
  124. // 开始循环渲染
  125. render();
  126. // 播放首次进入动画
  127. eventAnimation();
  128. });
  129. watch(
  130. () => props['craftAnimationStatus'],
  131. (e) => {
  132. if (e) {
  133. // 重置水面透明度
  134. waterPlaneGroup.children.map((obj) => {
  135. obj.material.uniforms.alpha.value = 1.0;
  136. });
  137. craftAnimation(props['craftAnimationType']);
  138. }
  139. }
  140. );
  141. watch(
  142. () => props['selectedMenu'],
  143. (e) => {
  144. // 巡检开启
  145. if (e === 'inspect') {
  146. // 相机角度重置
  147. camera.rotation.x = -0.9662198328141542;
  148. camera.rotation.y = 0.0004725006116027576;
  149. camera.rotation.z = 0.0006839146449353786;
  150. // 相机位置重置
  151. camera.position.set(0.103, 179.349, 123.908);
  152. // 相机观察点重置
  153. camera.lookAt(0, 1.7, 0);
  154. // 设置相机位置在人物模型后方
  155. camera.position.set(0, -5, -1);
  156. // camera.position.set(0, 1.4, -1);
  157. // 禁止相机控件旋转平移和缩放
  158. controls.enableRotate = false;
  159. controls.enablePan = false;
  160. controls.enableZoom = false;
  161. controls.target.set(0, 1.7, 0);
  162. controls.update();
  163. // 每次开启巡检时,将巡检项目索引和项目索引都重置,从第一个项目开始巡检
  164. inspectIndex.value = 0;
  165. inspectPathIndex.value = 0;
  166. // 人物步行动画开始播放
  167. WalkAction.play();
  168. // 人物标签开启显示
  169. css2DPeopleLabel.visible = true;
  170. // 巡检标线开启显示
  171. inspectLinePointGroup.children[inspectIndex.value].visible = true;
  172. // 建筑物标签关闭显示
  173. buildLabelGroup.children.map((item) => {
  174. item.visible = false;
  175. });
  176. } else if (e !== 'craft') {
  177. // 相机角度重置
  178. camera.rotation.x = -0.9662198328141542;
  179. camera.rotation.y = 0.0004725006116027576;
  180. camera.rotation.z = 0.0006839146449353786;
  181. // 相机位置重置
  182. camera.position.set(0.103, 179.349, 123.908);
  183. // 相机观察点重置
  184. camera.lookAt(0, 0, 0);
  185. controls.target.set(0, 0, 0);
  186. controls.update();
  187. // 重置水面透明度水面颜色
  188. waterPlaneGroup.children.map((obj) => {
  189. obj.material.uniforms.alpha.value = 1.0;
  190. obj.material.uniforms.waterColor.value = obj.color;
  191. });
  192. }
  193. }
  194. );
  195. const clock = new THREE.Clock();
  196. // 设置渲染帧率30FPS,默认情况下requestAnimationFrame在60帧左右,控制帧率优化性能
  197. const FPS = 30;
  198. // 间隔多长时间渲染一次
  199. const renderT = 1 / FPS;
  200. // 执行一次renderer.render,timeS重新置0
  201. let timeS = 0;
  202. // 渲染循环
  203. function render() {
  204. stats.update();
  205. // 循环渲染
  206. renderer.render(scene, camera);
  207. // 获取两帧渲染间隔时间
  208. const T = clock.getDelta();
  209. timeS = timeS + T;
  210. animationMixer.update(T);
  211. if (timeS > renderT) {
  212. TWEEN.update();
  213. // renderer.render每执行一次,timeS置0
  214. timeS = 0;
  215. // css2D标签渲染
  216. css2DRender.render(scene, camera);
  217. // 水面波纹动画渲染
  218. waterPlaneGroup.children.map((item) => {
  219. item.material.uniforms['time'].value += T / 6;
  220. });
  221. // 巡检时标线和拐点动画
  222. if (inspectLinePointGroup.children[inspectIndex.value] && props['selectedMenu'] === 'inspect') {
  223. inspectLinePointGroup.children[inspectIndex.value].children.map((item) => {
  224. if (item.name === '标线') {
  225. item.material.map.offset.x -= 0.03;
  226. } else if (item.name === '拐点') {
  227. item.rotation.y += 0.02;
  228. }
  229. });
  230. }
  231. // 巡检动画
  232. if (props['selectedMenu'] === 'inspect' && inspectState.value) {
  233. openInspection(people, controls);
  234. schedule.value = inspectPathIndex.value;
  235. // 巡检速度不断更新
  236. if (inspectPathArr[inspectIndex.value]) {
  237. speed.value = inspectPathArr[inspectIndex.value].speed;
  238. }
  239. console.log('巡检动画');
  240. }
  241. }
  242. requestAnimationFrame(render);
  243. }
  244. // 巡检状态变化事件
  245. function inspectStateChange() {
  246. // 巡检的状态切换
  247. inspectState.value = !inspectState.value;
  248. // 关闭巡检数据面板的显示
  249. inspectPanelShow.value = false;
  250. if (inspectState.value) {
  251. // 人物动画开始播放
  252. WalkAction.play();
  253. if (inspectPathIndex.value >= 100) {
  254. // 巡检项目索引加1
  255. inspectIndex.value += 1;
  256. // 巡检标记线组对象开启显示
  257. inspectLinePointGroup.children.map((item, index) => {
  258. if (index === inspectIndex.value) {
  259. item.visible = true;
  260. } else {
  261. item.visible = false;
  262. }
  263. });
  264. }
  265. } else {
  266. // 人物动画停止播放
  267. WalkAction.stop();
  268. }
  269. if (inspectPathIndex.value >= 100) {
  270. // 巡检项目路径索引重新置零
  271. inspectPathIndex.value = 0;
  272. }
  273. // 巡检项目索引值超过巡检路径数组时,表示已经巡检完最后一项,调用endInspect()结束巡检
  274. if (inspectIndex.value > inspectPathArr.length - 1) {
  275. endInspect();
  276. }
  277. }
  278. // 结束巡检
  279. function endInspect() {
  280. // 人物位置重置
  281. people.position.set(0, 0, 0);
  282. // 人物角度重置
  283. people.rotation.y = 0;
  284. people.rotation.x = 0;
  285. people.rotation.z = 0;
  286. // 相机位置重置
  287. camera.position.set(0.103, 179.349, 123.908);
  288. // 开启相机控件旋转平移和缩放
  289. controls.enableRotate = true;
  290. controls.enablePan = true;
  291. controls.enableZoom = true;
  292. // 相机控件观察点重置
  293. controls.target.set(0, 1.7, 0);
  294. // 相机控件更新
  295. controls.update();
  296. // 巡检状态重置为true
  297. inspectState.value = true;
  298. // 关闭巡检数据面板显示
  299. inspectPanelShow.value = false;
  300. // 人物标签隐藏显示
  301. css2DPeopleLabel.visible = false;
  302. // 巡检标记线组对象隐藏显示
  303. inspectLinePointGroup.children.map((item) => {
  304. item.visible = false;
  305. });
  306. // 建筑物标签开启显示
  307. buildLabelGroup.children.map((item) => {
  308. item.visible = true;
  309. });
  310. // 巡检速度重置
  311. inspectPathArr.map((item) => {
  312. item.speed = inspectionParams[item.name].speed;
  313. });
  314. // 关闭巡检
  315. emit('closeInspect');
  316. }
  317. // 巡检进度条变化事件
  318. function progressBarChange(e) {
  319. inspectPathIndex.value = e;
  320. }
  321. // 巡检速度条变化事件
  322. function controlBarChange(speed) {
  323. inspectPathArr[inspectIndex.value].speed = 0.4 * (speed * 0.01);
  324. }
  325. // 工艺动画
  326. function craftAnimation(type) {
  327. // 重置水面透明度水面颜色
  328. waterPlaneGroup.children.map((obj) => {
  329. obj.material.uniforms.alpha.value = 1.0;
  330. obj.material.uniforms.waterColor.value = obj.color;
  331. });
  332. // 禁止相机控件旋转平移和缩放
  333. // controls.enableRotate = false;
  334. // controls.enablePan = false;
  335. // controls.enableZoom = false;
  336. // 精确曝气动画
  337. if (type === 'aeration') {
  338. const name = '南北生物池水面';
  339. // 水面世界坐标位置
  340. const position = sewageModel.getObjectByName(name).getWorldPosition(new THREE.Vector3());
  341. // 开启动画,视角切换到水面处
  342. new TWEEN.Tween(camera.position)
  343. .to({ x: -113.85, y: 7.67, z: 43.59 }, 1500)
  344. .easing(TWEEN.Easing.Sinusoidal.InOut)
  345. .onUpdate(() => {
  346. controls.target.copy(new THREE.Vector3(-113, 2, 30));
  347. controls.update();
  348. })
  349. // 动画执行完成后
  350. .onComplete(() => {
  351. // 获取水面模型
  352. const waterPlane = waterPlaneGroup.getObjectByName(name);
  353. // 加载气泡纹理
  354. const texture = new THREE.TextureLoader().load('./bubbles.png');
  355. // 球体(气泡)材质,map气泡贴图模仿气泡效果
  356. const material = new THREE.MeshPhysicalMaterial({
  357. map: texture,
  358. color: '#fff',
  359. transparent: true,
  360. opacity: 0.6,
  361. });
  362. // 球体(气泡)组对象
  363. const sphereGroup = new THREE.Group();
  364. // 创建box3包围盒计算水面模型尺寸
  365. const box3 = new THREE.Box3();
  366. box3.expandByObject(waterPlane);
  367. // 根据水面尺寸计算出球体(气泡)出现的范围
  368. const x = ((box3.max.x - box3.min.x) / 2).toFixed(3) - '';
  369. const z = ((box3.max.z - box3.min.z) / 2).toFixed(3) - '' - 0.1;
  370. // 循环创建多个球体(气泡)
  371. for (let i = 0; i <= 2000; i++) {
  372. // 指定随机大小创建球形几何体
  373. const sphere = new THREE.SphereGeometry(Math.random() * 0.03 + 0.05);
  374. const mesh = new THREE.Mesh(sphere, material);
  375. // 随机旋转一定角度
  376. mesh.rotateX(Math.random() * Math.PI);
  377. // 设置位置
  378. mesh.position.copy(position);
  379. // y值置空
  380. mesh.position.y = 0;
  381. // 随机在增加一定值,使气泡在不同的位置出现
  382. mesh.position.x += Math.random() * (x - -x) + -x;
  383. mesh.position.y += Math.random() * 2;
  384. mesh.position.z += Math.random() * (z - -z) + -z;
  385. // 随机气泡上升的速度值
  386. mesh.speed = Math.random() * 0.04 + 0.04;
  387. sphereGroup.add(mesh);
  388. }
  389. scene.add(sphereGroup);
  390. // 此变量用作循环动画和销毁动画
  391. let bubbleRiseAnimationId;
  392. // 气泡上升动画
  393. function bubbleRise() {
  394. bubbleRiseAnimationId = requestAnimationFrame(bubbleRise);
  395. sphereGroup.children.map((item) => {
  396. item.position.y += item.speed;
  397. if (item.position.y >= position.y) item.position.y = 0;
  398. });
  399. }
  400. bubbleRise();
  401. // 水面默认的透明度
  402. let alpha = waterPlane.material.uniforms.alpha.value;
  403. const color1 = new THREE.Color('#87CEFA');
  404. const color2 = waterPlane.material.uniforms.waterColor.value;
  405. // 此变量用作循环动画和销毁动画
  406. let waterPlaneAnimationId;
  407. // 水面逐渐透明动画
  408. function waterPlaneTransparent() {
  409. waterPlaneAnimationId = requestAnimationFrame(waterPlaneTransparent);
  410. // 透明度大于0.3则不断降低透明度
  411. if (alpha >= 0.3) {
  412. alpha -= 0.01;
  413. waterPlane.material.uniforms.alpha.value = alpha;
  414. const newColor = color1.clone().lerp(color2.clone(), alpha);
  415. waterPlane.material.uniforms.waterColor.value = newColor;
  416. }
  417. // 透明度小于0.3
  418. else {
  419. // 延迟一定秒数后移除气泡组对象
  420. setTimeout(() => {
  421. // scene.remove(sphereGroup);
  422. }, 3000);
  423. // 传递事件告知动画执行完毕
  424. emit('craftAnimationEnd');
  425. // 销毁水面透明动画和气泡上升动画
  426. cancelAnimationFrame(waterPlaneAnimationId);
  427. // cancelAnimationFrame(bubbleRiseAnimationId);
  428. }
  429. }
  430. waterPlaneTransparent();
  431. })
  432. .start();
  433. }
  434. // 精确加药
  435. if (type === 'dosing') {
  436. const name = '东加药管2-2';
  437. // 加药管位置
  438. const position = sewageModel.getObjectByName(name).getWorldPosition(new THREE.Vector3());
  439. // 将位置偏移一下到出水口
  440. position.y -= 0.138;
  441. position.z += 0.22;
  442. // 开启Tweenjs动画,将视角切换到加药管处
  443. new TWEEN.Tween(camera.position)
  444. .to({ x: 57.16, y: 2.09, z: 6.53 }, 1500)
  445. .easing(TWEEN.Easing.Sinusoidal.InOut)
  446. .onUpdate(() => {
  447. controls.target.copy(position);
  448. controls.update();
  449. })
  450. // 视角切换完成后
  451. .onComplete(() => {
  452. // 创建一个位置数组,因为这个加药管有多个出水口,每个位置对应一个出水口
  453. const posArr = [];
  454. // 当前出水口位置先push到数组里去
  455. posArr.push(position);
  456. // 获取左侧出水口位置
  457. for (let i = 1; i <= 4; i++) {
  458. const pos = position.clone();
  459. pos.x += i * 0.765;
  460. posArr.push(pos);
  461. }
  462. // 获取右侧出水口位置
  463. for (let i = 1; i <= 4; i++) {
  464. const pos = position.clone();
  465. pos.x -= i * 0.765;
  466. posArr.push(pos);
  467. }
  468. // 创建球形几何体,模仿水滴
  469. const sphereGeometry = new THREE.SphereGeometry(0.005, 16, 16);
  470. const sphereMaterial = new THREE.MeshPhongMaterial({
  471. color: '#afeeee',
  472. });
  473. // 创建球体数组,存储所有的球体
  474. const sphereArr = [];
  475. // 每个出水管球体数量
  476. const numSpheres = 300;
  477. // 遍历posArr位置数组,给每个出水管创建球体
  478. posArr.map((pos) => {
  479. for (let i = 0; i < numSpheres; i++) {
  480. const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
  481. // 默认将其隐藏起来,因为随机高度球体会高于出水管
  482. sphere.visible = false;
  483. // 赋值出水管位置
  484. sphere.position.copy(pos);
  485. // 球体高度在加上一个随机值
  486. sphere.position.y += Math.random() * 2; // 不同的初始高度
  487. // 设置球体下落速度
  488. sphere.velocity = Math.random() * 0.02 + 0.01; // 随机下落速度
  489. sphereArr.push(sphere);
  490. scene.add(sphere);
  491. }
  492. });
  493. // 此变量用作循环动画和销毁动画
  494. let animationFrameId1;
  495. function animation1() {
  496. animationFrameId1 = requestAnimationFrame(animation1);
  497. // 遍历球体数组
  498. sphereArr.forEach((sphere) => {
  499. if (sphere.position.y <= position.y) {
  500. sphere.visible = true;
  501. }
  502. sphere.position.y -= sphere.velocity;
  503. if (sphere.position.y <= 0.3) {
  504. // 当球体下落到一定位置时
  505. sphere.position.y = position.y; // 重新置于顶端
  506. }
  507. });
  508. }
  509. animation1();
  510. const waterPlane = waterPlaneGroup.getObjectByName('东西生物池-东水面1');
  511. let alpha = waterPlane.material.uniforms.alpha.value;
  512. const color1 = new THREE.Color('#87CEFA');
  513. const color2 = waterPlane.material.uniforms.waterColor.value;
  514. // 此变量用作循环动画和销毁动画
  515. let animationFrameId2;
  516. function animation2() {
  517. animationFrameId2 = requestAnimationFrame(animation2);
  518. if (alpha >= 0.5) {
  519. alpha -= 0.006;
  520. waterPlane.material.uniforms.alpha.value = alpha;
  521. const newColor = color1.clone().lerp(color2.clone(), alpha);
  522. waterPlane.material.uniforms.waterColor.value = newColor;
  523. } else {
  524. emit('craftAnimationEnd');
  525. cancelAnimationFrame(animationFrameId2);
  526. }
  527. }
  528. animation2();
  529. })
  530. .start();
  531. }
  532. // 污泥回流
  533. if (type === 'sludge') {
  534. // 二沉池模型名称数组
  535. const sinkPoolNameArr = [
  536. '二沉池3水面',
  537. '二沉池3水面001',
  538. '二沉池4水面',
  539. '二沉池4水面001',
  540. // "初沉池水面1",
  541. // "初沉池水面1001",
  542. ];
  543. // 生物池模型名称数组
  544. const organismPoolNameArr = ['南北生物池水面', '东西生物池-东水面1', '东西生物池-东水面2', '东西生物池-西水面1', '东西生物池-西水面2'];
  545. // 获取二沉池模型
  546. const sinkPoolArr = [];
  547. sinkPoolNameArr.map((name) => {
  548. sinkPoolArr.push(waterPlaneGroup.getObjectByName(name));
  549. });
  550. // 获取生物池模型
  551. const organismPoolArr = [];
  552. organismPoolNameArr.map((name) => {
  553. const organismPool = waterPlaneGroup.getObjectByName(name);
  554. organismPool.material.uniforms.alpha.value = 0.3;
  555. organismPool.visible = false;
  556. organismPool.userData.y = organismPool.clone().position.y;
  557. organismPool.position.y = 0;
  558. organismPoolArr.push(organismPool);
  559. });
  560. // x: -10.84, y: 289.89, z: 276.17
  561. // 开启动画,视角切换到整个污水厂
  562. new TWEEN.Tween(camera.position)
  563. .to({ x: 100, y: 100, z: 180 }, 1500)
  564. .easing(TWEEN.Easing.Sinusoidal.InOut)
  565. .onUpdate(() => {
  566. controls.target.set(100, 0, -30);
  567. controls.update();
  568. })
  569. .onComplete(() => {
  570. // 此变量用作循环动画和销毁动画
  571. let waterPlaneAnimationId;
  572. // 水面逐渐透明动画
  573. function waterPlaneTransparent() {
  574. waterPlaneAnimationId = requestAnimationFrame(waterPlaneTransparent);
  575. sinkPoolArr.map((item) => {
  576. let alpha = item.material.uniforms.alpha.value;
  577. // 透明度大于0.3则不断降低透明度
  578. if (alpha >= 0.3) {
  579. alpha -= 0.01;
  580. item.material.uniforms.alpha.value = alpha;
  581. }
  582. });
  583. if (sinkPoolArr[sinkPoolArr.length - 1].material.uniforms.alpha.value < 0.3) {
  584. // 传递事件告知动画执行完毕
  585. // emit("craftAnimationEnd");
  586. cancelAnimationFrame(waterPlaneAnimationId);
  587. waterLevelRise();
  588. }
  589. }
  590. waterPlaneTransparent();
  591. // 此变量用作循环动画和销毁动画
  592. let waterLevelRiseAnimationId;
  593. function waterLevelRise() {
  594. waterLevelRiseAnimationId = requestAnimationFrame(waterLevelRise);
  595. organismPoolArr.map((item) => {
  596. item.visible = true;
  597. const yPos = item.userData.y;
  598. let alpha = item.material.uniforms.alpha.value;
  599. if (item.position.y < yPos) {
  600. item.position.y += 0.01;
  601. }
  602. if (alpha < 1) {
  603. alpha += 0.02;
  604. item.material.uniforms.alpha.value = alpha;
  605. }
  606. if (item.position.y >= yPos && alpha >= 1) {
  607. // 传递事件告知动画执行完毕
  608. emit('craftAnimationEnd');
  609. cancelAnimationFrame(waterLevelRiseAnimationId);
  610. }
  611. });
  612. }
  613. })
  614. .start();
  615. }
  616. }
  617. // 首次进入动画
  618. function eventAnimation() {
  619. new TWEEN.Tween(camera.position)
  620. .to({ x: 0.103, y: 179.349, z: 123.908 }, 2000)
  621. .easing(TWEEN.Easing.Sinusoidal.InOut)
  622. .onUpdate(() => {
  623. controls.target.set(0, 0, 0);
  624. controls.update();
  625. })
  626. .onComplete(() => {
  627. // 初始化标签
  628. initLabel(sewageModel);
  629. // 将人物标签添加到人物模型中
  630. people.children[0].add(css2DPeopleLabel);
  631. // 设置位置在人物模型头顶
  632. css2DPeopleLabel.position.set(0, 2.2, 0);
  633. // 设置合适大小
  634. css2DPeopleLabel.scale.set(0.1, 0.1, 0.1);
  635. // 人物标签默认隐藏显示
  636. css2DPeopleLabel.visible = false;
  637. })
  638. .start();
  639. }
  640. function createEnvironment(texture) {
  641. // scene.environment = texture;
  642. // hdr作为环境贴图生效,设置.mapping为EquirectangularReflectionMapping
  643. texture.mapping = THREE.EquirectangularReflectionMapping;
  644. // 创建一个巨大球体作为整个天空环境
  645. const sphere = new THREE.SphereGeometry(1000, 512, 512);
  646. const material = new THREE.MeshBasicMaterial({
  647. map: texture,
  648. side: THREE.DoubleSide,
  649. });
  650. const mesh = new THREE.Mesh(sphere, material);
  651. mesh.position.y -= 100;
  652. scene.add(mesh);
  653. }
  654. </script>
  655. <style lang='less'>
  656. @import './index.less';
  657. </style>

需要全部完整案例源码,访问百度网盘:

链接:https://pan.baidu.com/s/1Ib5nq3MueA-6-OfnVvmqJg 
提取码:pgml

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小舞很执着/article/detail/896536
推荐阅读
相关标签
  

闽ICP备14008679号