|
@@ -1,16 +1,73 @@
|
|
|
-import { useThree } from '@react-three/fiber'
|
|
|
+import { useFrame, 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'
|
|
|
+import { forwardRef, useImperativeHandle } from 'react'
|
|
|
+import { OrbitControls } from '@react-three/drei'
|
|
|
+import store from '@/store'
|
|
|
+import { envFlag } from '@/utils/http'
|
|
|
+
|
|
|
+function Model(_: any, ref: any) {
|
|
|
+ // 轨道控制器
|
|
|
+ const controlsRef = useRef<any>(null)
|
|
|
+ useFrame(() => {
|
|
|
+ if (controlsRef.current) {
|
|
|
+ controlsRef.current.update() // 启用阻尼效果必须每帧更新控制器
|
|
|
+ }
|
|
|
+ })
|
|
|
+ // 轨道控制器
|
|
|
|
|
|
-function Model() {
|
|
|
const groupRef = useRef<THREE.Group>(null)
|
|
|
- const [loadingProgress, setLoadingProgress] = useState(0)
|
|
|
+ const { camera, scene } = useThree()
|
|
|
+
|
|
|
+ // 移动模型到中心位置
|
|
|
+ const moveCentenFu = useCallback(
|
|
|
+ (partName: string, selectedPart: any) => {
|
|
|
+ // 计算选中部分的包围盒
|
|
|
+ const bbox = new THREE.Box3().setFromObject(selectedPart)
|
|
|
+
|
|
|
+ // 检查包围盒是否有效
|
|
|
+ if (bbox.isEmpty()) {
|
|
|
+ console.warn(`Selected part "${partName}" has an empty bounding box.`)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取包围盒的中心点
|
|
|
+ const center = new THREE.Vector3()
|
|
|
+ bbox.getCenter(center)
|
|
|
+
|
|
|
+ // 获取包围盒的尺寸(可用于计算相机距离)
|
|
|
+ const size = new THREE.Vector3()
|
|
|
+ bbox.getSize(size)
|
|
|
+
|
|
|
+ // 计算相机应该移动到的位置(可以根据需要调整偏移量)
|
|
|
+ const maxDim = Math.max(size.x, size.y, size.z)
|
|
|
+ const distance = maxDim * 1.5 // 根据模型大小计算相机距离
|
|
|
+
|
|
|
+ // 设置相机位置并看向中心点
|
|
|
+ camera.position.copy(center.clone().add(new THREE.Vector3(0, distance, 0)))
|
|
|
+ camera.lookAt(center)
|
|
|
+
|
|
|
+ // 如果有轨道控制器,更新其目标点
|
|
|
+
|
|
|
+ if (controlsRef.current) {
|
|
|
+ controlsRef.current.target.copy(center)
|
|
|
+ controlsRef.current.update()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ [camera]
|
|
|
+ )
|
|
|
+
|
|
|
const [model, setModel] = useState<THREE.Group | null>(null)
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if (model) {
|
|
|
+ moveCentenFu('all', model)
|
|
|
+ }
|
|
|
+ }, [model, moveCentenFu])
|
|
|
+
|
|
|
const [modelParts, setModelParts] = useState<{ [name: string]: THREE.Object3D }>({})
|
|
|
- const [activePart, setActivePart] = useState<string | null>(null)
|
|
|
- const { camera, scene } = useThree()
|
|
|
|
|
|
const handleModelLoaded = useCallback(
|
|
|
(loadedModel: THREE.Group, parts: { [name: string]: THREE.Object3D }) => {
|
|
@@ -25,12 +82,10 @@ function Model() {
|
|
|
[scene]
|
|
|
)
|
|
|
|
|
|
+ // 存储加载过程中遇到的最大 itemsTotal
|
|
|
+
|
|
|
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' })
|
|
@@ -39,19 +94,13 @@ function Model() {
|
|
|
loader.setDRACOLoader(dracoLoader)
|
|
|
|
|
|
loader.load(
|
|
|
- `${serverUrl}/0/model.glb`, // 替换为你的GLB模型路径
|
|
|
+ envFlag
|
|
|
+ ? 'https://houseoss.4dkankan.com/project/chuGuoYuJian/staticData/0/model.glb'
|
|
|
+ : `${serverUrl}model/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
|
|
@@ -59,10 +108,10 @@ function Model() {
|
|
|
}
|
|
|
})
|
|
|
|
|
|
- // 3. 假设模型中的三个部分分别命名为'bs1', 'bs2', 'bs3'
|
|
|
+ // 3. 假设模型中的三个部分分别命名为'yu_01', 'jian_02', 'yu_03'
|
|
|
const parts: { [name: string]: THREE.Object3D } = {}
|
|
|
model.children.forEach(child => {
|
|
|
- if (['bs1', 'bs2', 'bs3'].includes(child.name)) {
|
|
|
+ if (['yu_01', 'jian_02', 'yu_03'].includes(child.name)) {
|
|
|
parts[child.name] = child
|
|
|
}
|
|
|
})
|
|
@@ -71,7 +120,12 @@ function Model() {
|
|
|
handleModelLoaded(model, parts)
|
|
|
},
|
|
|
xhr => {
|
|
|
- // 加载进度回调已在LoadingManager中处理
|
|
|
+ if (xhr.lengthComputable) {
|
|
|
+ const percentComplete = (xhr.loaded / xhr.total) * 100
|
|
|
+ const baiNum = percentComplete.toFixed(0)
|
|
|
+
|
|
|
+ store.dispatch({ type: 'A1/plan', payload: Number(baiNum) })
|
|
|
+ }
|
|
|
},
|
|
|
error => {
|
|
|
console.error('加载模型出错:', error)
|
|
@@ -89,25 +143,13 @@ function Model() {
|
|
|
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)
|
|
|
+ // 获取选中部分的引用
|
|
|
+ const selectedPart = modelParts[partName]
|
|
|
|
|
|
- setActivePart(partName)
|
|
|
+ // 移动到中心点
|
|
|
+ moveCentenFu(partName, selectedPart)
|
|
|
},
|
|
|
- [camera, model, modelParts]
|
|
|
+ [model, modelParts, moveCentenFu]
|
|
|
)
|
|
|
|
|
|
// 显示全部模型
|
|
@@ -119,27 +161,32 @@ function Model() {
|
|
|
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 ? <primitive object={groupRef.current} /> : null}</>
|
|
|
+ // 移动到中心点
|
|
|
+ moveCentenFu('all', model)
|
|
|
+ }, [model, modelParts, moveCentenFu])
|
|
|
+
|
|
|
+ // 可以让父组件调用子组件的方法
|
|
|
+ useImperativeHandle(ref, () => ({
|
|
|
+ focusOnPart,
|
|
|
+ showAllModels
|
|
|
+ }))
|
|
|
+
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ <ambientLight intensity={3} />
|
|
|
+ <directionalLight position={[10, 10, 10]} intensity={2} castShadow />
|
|
|
+ <OrbitControls
|
|
|
+ ref={controlsRef}
|
|
|
+ enableDamping={true} // 启用阻尼效果
|
|
|
+ dampingFactor={0.05} // 阻尼系数[7](@ref)
|
|
|
+ maxDistance={2} // 根据初始缩放比例调整
|
|
|
+ minDistance={0.3} // 根据初始缩放比例调整[9](@ref)
|
|
|
+ screenSpacePanning={true} // 移动端友好的平移方式
|
|
|
+ />
|
|
|
+
|
|
|
+ {groupRef.current ? <primitive object={groupRef.current} /> : null}
|
|
|
+ </>
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
-const MemoModel = React.memo(Model)
|
|
|
-
|
|
|
-export default MemoModel
|
|
|
+export default forwardRef(Model)
|