Model.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import { useThree } from '@react-three/fiber'
  2. import React, { useCallback, useEffect, useRef, useState } from 'react'
  3. import * as THREE from 'three'
  4. import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
  5. import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
  6. function Model() {
  7. const groupRef = useRef<THREE.Group>(null)
  8. const [loadingProgress, setLoadingProgress] = useState(0)
  9. const [model, setModel] = useState<THREE.Group | null>(null)
  10. const [modelParts, setModelParts] = useState<{ [name: string]: THREE.Object3D }>({})
  11. const [activePart, setActivePart] = useState<string | null>(null)
  12. const { camera, scene } = useThree()
  13. const handleModelLoaded = useCallback(
  14. (loadedModel: THREE.Group, parts: { [name: string]: THREE.Object3D }) => {
  15. setModel(loadedModel)
  16. setModelParts(parts)
  17. // 初始状态下显示所有模型
  18. Object.values(parts).forEach(part => {
  19. part.visible = true
  20. })
  21. scene.add(loadedModel)
  22. },
  23. [scene]
  24. )
  25. useEffect(() => {
  26. const manager = new THREE.LoadingManager()
  27. manager.onProgress = (url, itemsLoaded, itemsTotal) => {
  28. setLoadingProgress((itemsLoaded / itemsTotal) * 100)
  29. }
  30. const dracoLoader = new DRACOLoader()
  31. dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/')
  32. dracoLoader.setDecoderConfig({ type: 'js' })
  33. const loader = new GLTFLoader(manager)
  34. loader.setDRACOLoader(dracoLoader)
  35. loader.load(
  36. `${serverUrl}/0/model.glb`, // 替换为你的GLB模型路径
  37. gltf => {
  38. const model = gltf.scene
  39. // 设置模型初始位置 (x, y, z)
  40. model.position.set(0, 0, 0) // 设置为场景原点
  41. // // 设置模型初始缩放 (x, y, z)
  42. model.scale.set(0.003, 0.003, 0.003) // 原始大小设
  43. // // 设置模型初始旋转(单位:弧度)(x, y, z)
  44. model.rotation.set(0, 0, 0) // 例如,不旋转。绕Y轴旋转90度可设为(0, Math.PI / 2, 0)
  45. model.traverse((child: any) => {
  46. if (child.isMesh) {
  47. child.castShadow = true
  48. child.receiveShadow = true
  49. }
  50. })
  51. // 3. 假设模型中的三个部分分别命名为'bs1', 'bs2', 'bs3'
  52. const parts: { [name: string]: THREE.Object3D } = {}
  53. model.children.forEach(child => {
  54. if (['bs1', 'bs2', 'bs3'].includes(child.name)) {
  55. parts[child.name] = child
  56. }
  57. })
  58. setModelParts(parts)
  59. handleModelLoaded(model, parts)
  60. },
  61. xhr => {
  62. // 加载进度回调已在LoadingManager中处理
  63. },
  64. error => {
  65. console.error('加载模型出错:', error)
  66. }
  67. )
  68. }, [handleModelLoaded])
  69. // 点击不同部位
  70. const focusOnPart = useCallback(
  71. (partName: string) => {
  72. if (!model || !modelParts[partName]) return
  73. // 隐藏所有部分,然后显示选中的部分
  74. Object.entries(modelParts).forEach(([name, part]) => {
  75. part.visible = name === partName
  76. })
  77. // 获取选中部分的包围盒并居中相机
  78. const bbox = new THREE.Box3().setFromObject(modelParts[partName])
  79. const center = bbox.getCenter(new THREE.Vector3())
  80. const size = bbox.getSize(new THREE.Vector3())
  81. // 计算相机位置,使其能够完整看到模型
  82. const maxDim = Math.max(size.x, size.y, size.z)
  83. const fov =
  84. camera instanceof THREE.PerspectiveCamera ? (camera.fov * Math.PI) / 180 : 0
  85. const cameraDistance = maxDim / (2 * Math.tan(fov / 2))
  86. camera.position.copy(
  87. center.clone().add(new THREE.Vector3(0, 0, cameraDistance * 1.5))
  88. )
  89. camera.lookAt(center)
  90. setActivePart(partName)
  91. },
  92. [camera, model, modelParts]
  93. )
  94. // 显示全部模型
  95. const showAllModels = useCallback(() => {
  96. if (!model) return
  97. // 显示所有模型部分
  98. Object.values(modelParts).forEach(part => {
  99. part.visible = true
  100. })
  101. // 将相机定位到整个模型的中心
  102. const bbox = new THREE.Box3().setFromObject(model)
  103. const center = bbox.getCenter(new THREE.Vector3())
  104. const size = bbox.getSize(new THREE.Vector3())
  105. const maxDim = Math.max(size.x, size.y, size.z)
  106. const fov =
  107. camera instanceof THREE.PerspectiveCamera ? (camera.fov * Math.PI) / 180 : 0
  108. const cameraDistance = maxDim / (2 * Math.tan(fov / 2))
  109. camera.position.copy(
  110. center.clone().add(new THREE.Vector3(0, 0, cameraDistance * 1.5))
  111. )
  112. camera.lookAt(center)
  113. setActivePart(null)
  114. }, [camera, model, modelParts])
  115. return <>{groupRef.current ? <primitive object={groupRef.current} /> : null}</>
  116. }
  117. const MemoModel = React.memo(Model)
  118. export default MemoModel