import { useThree } from '@react-three/fiber' import React, { useCallback, useEffect, useRef, useState } from 'react' import * as THREE from 'three' import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' function Model() { const groupRef = useRef(null) const [loadingProgress, setLoadingProgress] = useState(0) const [model, setModel] = useState(null) const [modelParts, setModelParts] = useState<{ [name: string]: THREE.Object3D }>({}) const [activePart, setActivePart] = useState(null) const { camera, scene } = useThree() const handleModelLoaded = useCallback( (loadedModel: THREE.Group, parts: { [name: string]: THREE.Object3D }) => { setModel(loadedModel) setModelParts(parts) // 初始状态下显示所有模型 Object.values(parts).forEach(part => { part.visible = true }) scene.add(loadedModel) }, [scene] ) useEffect(() => { const manager = new THREE.LoadingManager() manager.onProgress = (url, itemsLoaded, itemsTotal) => { setLoadingProgress((itemsLoaded / itemsTotal) * 100) } const dracoLoader = new DRACOLoader() dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/') dracoLoader.setDecoderConfig({ type: 'js' }) const loader = new GLTFLoader(manager) loader.setDRACOLoader(dracoLoader) loader.load( `${serverUrl}/0/model.glb`, // 替换为你的GLB模型路径 gltf => { const model = gltf.scene // 设置模型初始位置 (x, y, z) model.position.set(0, 0, 0) // 设置为场景原点 // // 设置模型初始缩放 (x, y, z) model.scale.set(0.003, 0.003, 0.003) // 原始大小设 // // 设置模型初始旋转(单位:弧度)(x, y, z) model.rotation.set(0, 0, 0) // 例如,不旋转。绕Y轴旋转90度可设为(0, Math.PI / 2, 0) model.traverse((child: any) => { if (child.isMesh) { child.castShadow = true child.receiveShadow = true } }) // 3. 假设模型中的三个部分分别命名为'bs1', 'bs2', 'bs3' const parts: { [name: string]: THREE.Object3D } = {} model.children.forEach(child => { if (['bs1', 'bs2', 'bs3'].includes(child.name)) { parts[child.name] = child } }) setModelParts(parts) handleModelLoaded(model, parts) }, xhr => { // 加载进度回调已在LoadingManager中处理 }, error => { console.error('加载模型出错:', error) } ) }, [handleModelLoaded]) // 点击不同部位 const focusOnPart = useCallback( (partName: string) => { if (!model || !modelParts[partName]) return // 隐藏所有部分,然后显示选中的部分 Object.entries(modelParts).forEach(([name, part]) => { part.visible = name === partName }) // 获取选中部分的包围盒并居中相机 const bbox = new THREE.Box3().setFromObject(modelParts[partName]) const center = bbox.getCenter(new THREE.Vector3()) const size = bbox.getSize(new THREE.Vector3()) // 计算相机位置,使其能够完整看到模型 const maxDim = Math.max(size.x, size.y, size.z) const fov = camera instanceof THREE.PerspectiveCamera ? (camera.fov * Math.PI) / 180 : 0 const cameraDistance = maxDim / (2 * Math.tan(fov / 2)) camera.position.copy( center.clone().add(new THREE.Vector3(0, 0, cameraDistance * 1.5)) ) camera.lookAt(center) setActivePart(partName) }, [camera, model, modelParts] ) // 显示全部模型 const showAllModels = useCallback(() => { if (!model) return // 显示所有模型部分 Object.values(modelParts).forEach(part => { part.visible = true }) // 将相机定位到整个模型的中心 const bbox = new THREE.Box3().setFromObject(model) const center = bbox.getCenter(new THREE.Vector3()) const size = bbox.getSize(new THREE.Vector3()) const maxDim = Math.max(size.x, size.y, size.z) const fov = camera instanceof THREE.PerspectiveCamera ? (camera.fov * Math.PI) / 180 : 0 const cameraDistance = maxDim / (2 * Math.tan(fov / 2)) camera.position.copy( center.clone().add(new THREE.Vector3(0, 0, cameraDistance * 1.5)) ) camera.lookAt(center) setActivePart(null) }, [camera, model, modelParts]) return <>{groupRef.current ? : null} } const MemoModel = React.memo(Model) export default MemoModel