threeVehicleModel.vue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. <template>
  2. <div>
  3. <div id="containerThreeVehicleModel"></div>
  4. </div>
  5. </template>
  6. <script>
  7. import * as THREE from "three";
  8. import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
  9. import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
  10. import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
  11. // import {
  12. // showFullScreenLoading,
  13. // tryHideFullScreenLoading,
  14. // } from "../../../axios/filter";
  15. import { mapState } from "vuex";
  16. export default {
  17. name: "threeVehicleModel", // 动力学配置中的threeJS
  18. data() {
  19. return {
  20. publicPath: process.env.BASE_URL,
  21. scene: null,
  22. camera: null,
  23. renderer: null,
  24. light: null,
  25. geometryName: null,
  26. controls: null,
  27. mesh: null,
  28. cube: null,
  29. container: null,
  30. car: null,
  31. cubeTexture: null,
  32. raf: null,
  33. // scale: 2, // 物体加载换算倍数
  34. isLoading: false, // 判断是否正在记载车辆
  35. lastCarModel: "", // 记录上一次加载的carModel
  36. };
  37. },
  38. computed: {
  39. ...mapState(["scale", "scaleRate"]),
  40. },
  41. props: {
  42. carModel: {
  43. type: String,
  44. default: "",
  45. },
  46. },
  47. watch: {
  48. carModel(newVal, oldVal) {
  49. if (newVal != oldVal && !this.isLoading) {
  50. this.lastCarModel = newVal;
  51. this.initCar(newVal);
  52. }
  53. },
  54. },
  55. methods: {
  56. // 场景
  57. initScene() {
  58. this.scene = new THREE.Scene();
  59. const gridHelper = new THREE.GridHelper(1000, 100);
  60. gridHelper.material.opacity = 0.25;
  61. gridHelper.material.transparent = true;
  62. this.scene.add(gridHelper);
  63. },
  64. // 相机
  65. initCamera() {
  66. this.camera = new THREE.PerspectiveCamera(
  67. 75,
  68. this.container.clientWidth / this.container.clientHeight,
  69. 0.1,
  70. 1000
  71. );
  72. this.camera.position.set(200, 200, 200);
  73. this.scene.add(this.camera);
  74. },
  75. // 渲染器
  76. initRenderer() {
  77. this.renderer = new THREE.WebGLRenderer({
  78. antialias: true,
  79. alpha: true,
  80. });
  81. this.renderer.setSize(
  82. this.container.clientWidth,
  83. this.container.clientHeight
  84. );
  85. this.renderer.setClearColor("#272727");
  86. this.container.appendChild(this.renderer.domElement);
  87. },
  88. // 初始化灯光
  89. initLight() {
  90. var hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
  91. hemiLight.position.set(0, 20, 0);
  92. this.scene.add(hemiLight);
  93. // 环境光会均匀的照亮场景中的所有物体
  94. const light = new THREE.AmbientLight(0x5c5c5c, 0.4); // soft white light
  95. this.scene.add(light);
  96. // 平行光是沿着特定方向发射的光
  97. const dirLight = new THREE.DirectionalLight(0xffffff, 0.5);
  98. this.scene.add(dirLight);
  99. },
  100. // 初始化车模型
  101. initCar(model) {
  102. if (this.car) {
  103. this.scene.remove(this.car);
  104. this.removeObj(this.car);
  105. this.car = null;
  106. }
  107. if (!model) return;
  108. if (!model.includes(".glb")) return;
  109. let that = this;
  110. let loader = new GLTFLoader();
  111. const dracoLoader = new DRACOLoader(); //
  112. dracoLoader.setDecoderPath(`${this.publicPath}draco/`); // 设置public下的解码路径,注意最后面的/
  113. dracoLoader.setDecoderConfig({ type: "js" }); // 使用兼容性强的draco_decoder.js解码器
  114. dracoLoader.preload(); // 初始化_initDecoder 解码器
  115. loader.setDRACOLoader(dracoLoader); // gltfloader使用dracoLoader
  116. // showFullScreenLoading();
  117. this.$emit('loadingDone', 'carStart');
  118. this.isLoading = true;
  119. loader.load(
  120. model,
  121. function (obj) {
  122. // tryHideFullScreenLoading();
  123. obj.scene.rotation.set(
  124. (-90 * Math.PI) / 180,
  125. 0,
  126. (-180 * Math.PI) / 180
  127. );
  128. let s = 0;
  129. if (model.includes("928.glb")) {
  130. s = that.scale / that.scaleRate;
  131. } else {
  132. s = that.scale;
  133. }
  134. let scale = 30 * s;
  135. obj.scene.scale.set(scale, scale, scale);
  136. that.scene.add(obj.scene);
  137. that.car = obj.scene;
  138. that.$emit('loadingDone', 'carDone');
  139. that.isLoading = false;
  140. that.needCar();
  141. },
  142. (xhr) => {
  143. // console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
  144. },
  145. (error) => {
  146. // tryHideFullScreenLoading();
  147. that.$emit('loadingDone', 'carDone');
  148. that.isLoading = false;
  149. that.needCar();
  150. console.error(error);
  151. }
  152. );
  153. },
  154. // 判断是否继续加载car,为了应对上一个car正在加载中的时候,用户选择了新的car
  155. needCar() {
  156. if (this.carModel != this.lastCarModel) {
  157. this.lastCarModel = this.carModel;
  158. this.initCar(this.carModel);
  159. }
  160. },
  161. // 初始化
  162. init() {
  163. this.initScene();
  164. this.initCamera();
  165. this.initRenderer();
  166. this.initLight();
  167. this.controls = new OrbitControls(
  168. this.camera,
  169. this.renderer.domElement
  170. ); //创建控件对象
  171. this.controls.minDistance = 30;
  172. this.controls.maxDistance = 600;
  173. this.controls.update();
  174. },
  175. animate() {
  176. this.raf = requestAnimationFrame(this.animate);
  177. this.renderer.render(this.scene, this.camera);
  178. this.controls.update();
  179. },
  180. onWindowResize() {
  181. this.camera.aspect =
  182. this.container.clientWidth / this.container.clientHeight;
  183. this.camera.updateProjectionMatrix();
  184. this.renderer.setSize(
  185. this.container.clientWidth,
  186. this.container.clientHeight
  187. );
  188. },
  189. go() {
  190. this.container = document.getElementById(
  191. "containerThreeVehicleModel"
  192. );
  193. this.init();
  194. this.animate();
  195. window.addEventListener("resize", this.onWindowResize);
  196. },
  197. removeScene() {
  198. this.clearScene();
  199. },
  200. clearCache(item) {
  201. if (item.geometry && item.geometry.dispose) item.geometry.dispose();
  202. if (item.material && item.material.dispose) item.material.dispose();
  203. },
  204. clearScene() {
  205. this.removeObj(this.scene);
  206. },
  207. removeObj(obj) {
  208. let arr = obj.children.filter((x) => x);
  209. arr.forEach((item) => {
  210. if (item.children.length) {
  211. this.removeObj(item);
  212. } else {
  213. this.clearCache(item);
  214. item.clear();
  215. }
  216. }),
  217. obj.clear();
  218. arr = null;
  219. },
  220. },
  221. mounted() {
  222. this.go();
  223. },
  224. destroyed() {
  225. window.removeEventListener("resize", this.onWindowResize);
  226. cancelAnimationFrame(this.raf);
  227. if (this.renderer) {
  228. this.renderer.renderLists.dispose();
  229. this.renderer.dispose();
  230. this.renderer.forceContextLoss();
  231. this.renderer.domElement = null;
  232. this.renderer.content = null;
  233. this.renderer = null;
  234. }
  235. if (this.controls) {
  236. this.controls.dispose();
  237. this.controls = null;
  238. }
  239. this.clearScene();
  240. this.scene = null;
  241. this.camera = null;
  242. this.light = null;
  243. this.geometryName = null;
  244. this.mesh = null;
  245. this.cube = null;
  246. this.container = null;
  247. this.car = null;
  248. this.cubeTexture = null;
  249. this.raf = null;
  250. THREE.Cache.clear();
  251. },
  252. };
  253. </script>
  254. <style lang="less" scoped>
  255. #containerThreeVehicleModel {
  256. width: 100%;
  257. height: calc(100vh - 180px);
  258. }
  259. </style>