Viewer.vue 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. <template>
  2. <div :id="id" class="container">
  3. <SimpleLoading v-if="loading" />
  4. </div>
  5. </template>
  6. <script setup lang="ts">
  7. import { RandomUtils } from '@imengyu/imengyu-utils';
  8. import * as THREE from 'three';
  9. import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
  10. import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
  11. import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
  12. import SimpleLoading from '../small/SimpleLoading.vue';
  13. const id = RandomUtils.genNonDuplicateIDHEX(12);
  14. let renderer : THREE.WebGLRenderer|undefined;
  15. let scene : THREE.Scene|undefined;
  16. let camera : THREE.Camera|undefined;
  17. let controls : OrbitControls|undefined;
  18. const loader = new GLTFLoader();
  19. const props = defineProps({
  20. path: {
  21. type: String,
  22. default: ''
  23. }
  24. })
  25. function init() {
  26. const container = document.getElementById(id);
  27. if (!container) {
  28. console.error('container not found');
  29. return;
  30. }
  31. scene = new THREE.Scene();
  32. camera = new THREE.PerspectiveCamera( 75, container.clientWidth / container.clientHeight, 0.1, 1000 );
  33. camera.position.set(10, 10, 10);
  34. const color = 0xFFFFFF;
  35. const light0 = new THREE.AmbientLight(color, 1);
  36. scene.add(light0);
  37. const light = new THREE.DirectionalLight(color, 5);
  38. light.position.set(0, 15, -20);
  39. light.target.position.set(0, 0, 0);
  40. scene.add(light);
  41. scene.add(light.target);
  42. const light2 = new THREE.DirectionalLight(color, 5);
  43. light2.position.set(0, 15, 20);
  44. light2.target.position.set(0, 0, 0);
  45. scene.add(light2);
  46. scene.add(light2.target);
  47. function animate() {
  48. if (controls)
  49. controls.update();
  50. if (renderer && scene && camera)
  51. renderer.render(scene, camera);
  52. }
  53. renderer = new THREE.WebGLRenderer({ alpha: true });
  54. renderer.setAnimationLoop(animate);
  55. renderer.setSize(container.clientWidth, container.clientHeight);
  56. controls = new OrbitControls(camera, renderer.domElement);
  57. controls.autoRotate = true;
  58. controls.autoRotateSpeed = 1;
  59. controls.update();
  60. container.appendChild(renderer.domElement);
  61. setTimeout(() => {
  62. if (props.path)
  63. loadPath(props.path);
  64. else {
  65. const geometry = new THREE.BoxGeometry( 1, 1, 1 );
  66. const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
  67. const cube = new THREE.Mesh( geometry, material );
  68. scene!.add( cube );
  69. }
  70. }, 200);
  71. }
  72. const loading = ref(false);
  73. function loadPath(path: string) {
  74. console.log('load model', path);
  75. loading.value = true;
  76. return new Promise<void>((resolve, reject) => {
  77. if (!scene) {
  78. reject('scene not init');
  79. return;
  80. }
  81. loader.load(path, function ( gltf ) {
  82. if (!scene) {
  83. reject('scene not init');
  84. return;
  85. }
  86. const model = gltf.scene;
  87. if (!camera) {
  88. reject('camera not init');
  89. return;
  90. }
  91. scene.add(model)
  92. // 计算包围盒
  93. const box = new THREE.Box3().setFromObject(model);
  94. const size = box.getSize(new THREE.Vector3());
  95. const center = box.getCenter(new THREE.Vector3());
  96. // 调整模型位置(可选:让模型居中)
  97. model.position.sub(center);
  98. // 计算摄像机距离
  99. const maxDim = Math.max(size.x, size.y, size.z);
  100. const fov = 60 * (Math.PI / 180);
  101. let cameraZ = Math.abs(maxDim / 2 / Math.tan(fov / 2));
  102. // 设置摄像机
  103. camera.position.set(0, 0, cameraZ);
  104. camera.lookAt(0, 0, 0);
  105. if (controls) {
  106. controls.target.set(0, 0, 0);
  107. controls.update();
  108. }
  109. resolve();
  110. }, undefined, reject);
  111. }).finally(() => {
  112. loading.value = false;
  113. });
  114. }
  115. defineExpose({
  116. loadPath,
  117. })
  118. onBeforeUnmount(() => {
  119. if (renderer) {
  120. renderer.setAnimationLoop(null);
  121. renderer = undefined;
  122. }
  123. });
  124. onMounted(() => {
  125. nextTick(init);
  126. });
  127. </script>
  128. <style scoped lang="scss">
  129. .container {
  130. width: 100%;
  131. height: 100%;
  132. position: relative;
  133. :deep(.simple-loading) {
  134. position: absolute;
  135. top: 50%;
  136. left: 50%;
  137. transform: translate(-50%, -50%);
  138. }
  139. }
  140. </style>