This commit is contained in:
2025-11-20 17:29:13 +08:00
parent 6644187c1f
commit afcd701edc
2 changed files with 596 additions and 6 deletions

View File

@@ -1,13 +1,303 @@
<!--
* @Description:模型漫游巡检业务
* @Description: 模型漫游巡检业务
* @Autor: 相卿
* @Date: 2025-11-17 16:07:33
* @LastEditors: 相卿
* @LastEditTime: 2025-11-20 11:44:48
* @LastEditTime: 2025-11-20 16:40:34
-->
<script lang="ts" setup>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { onMounted, onUnmounted, ref, shallowRef } from 'vue'
const threeRef = shallowRef<HTMLDivElement>()
const camera = shallowRef<THREE.PerspectiveCamera>()
const scene = shallowRef<THREE.Scene>()
const controls = shallowRef<OrbitControls>()
const renderer = shallowRef<THREE.WebGLRenderer>()
// 漫游状态
const tourMode = ref<'none' | 'slow' | 'fast'>('none')
let tourTime = 0
const initialCameraPosition = new THREE.Vector3(0, 5, 15)
const initialCameraTarget = new THREE.Vector3(0, 0, 0)
// 巡检路径点位 - 模拟进入建筑内部进行详细巡检
// 包含位置和对应的观察目标点
interface PathPoint {
position: THREE.Vector3
lookAt: THREE.Vector3
}
const inspectionPath: PathPoint[] = [
// 1. 外部俯视全景
{ position: new THREE.Vector3(0, 8, 15), lookAt: new THREE.Vector3(0, 0, 0) },
// 2. 从正面接近入口
{ position: new THREE.Vector3(0, 3, 10), lookAt: new THREE.Vector3(0, 2, 0) },
// 3. 进入前厅区域
{ position: new THREE.Vector3(0, 2, 5), lookAt: new THREE.Vector3(0, 2, -2) },
// 4. 右侧区域巡检
{ position: new THREE.Vector3(5, 2, 3), lookAt: new THREE.Vector3(8, 2, 0) },
// 5. 深入右侧内部
{ position: new THREE.Vector3(8, 2.5, 0), lookAt: new THREE.Vector3(10, 2, -3) },
// 6. 右后方区域
{ position: new THREE.Vector3(6, 2, -5), lookAt: new THREE.Vector3(3, 2, -8) },
// 7. 后方中央区域
{ position: new THREE.Vector3(0, 2.5, -8), lookAt: new THREE.Vector3(0, 2, -5) },
// 8. 左后方区域
{ position: new THREE.Vector3(-6, 2, -5), lookAt: new THREE.Vector3(-3, 2, -8) },
// 9. 深入左侧内部
{ position: new THREE.Vector3(-8, 2.5, 0), lookAt: new THREE.Vector3(-10, 2, -3) },
// 10. 左侧区域巡检
{ position: new THREE.Vector3(-5, 2, 3), lookAt: new THREE.Vector3(-8, 2, 0) },
// 11. 返回中央区域
{ position: new THREE.Vector3(0, 2, 0), lookAt: new THREE.Vector3(0, 3, -5) },
// 12. 中央上升视角
{ position: new THREE.Vector3(0, 5, 2), lookAt: new THREE.Vector3(0, 0, 0) },
// 13. 退出并俯视
{ position: new THREE.Vector3(0, 8, 12), lookAt: new THREE.Vector3(0, 0, 0) },
]
function initModel() {
const width = threeRef.value!.clientWidth
const height = threeRef.value!.clientHeight
// 创建场景
scene.value = new THREE.Scene()
// 创建天空盒
const skyboxGeometry = new THREE.BoxGeometry(500, 500, 500)
const skyboxMaterials = [
new THREE.MeshBasicMaterial({ color: 0x1A1A2E, side: THREE.BackSide }), // right
new THREE.MeshBasicMaterial({ color: 0x1A1A2E, side: THREE.BackSide }), // left
new THREE.MeshBasicMaterial({ color: 0x0F0F1E, side: THREE.BackSide }), // top
new THREE.MeshBasicMaterial({ color: 0x000000, side: THREE.BackSide }), // bottom
new THREE.MeshBasicMaterial({ color: 0x1A1A2E, side: THREE.BackSide }), // front
new THREE.MeshBasicMaterial({ color: 0x1A1A2E, side: THREE.BackSide }), // back
]
const skybox = new THREE.Mesh(skyboxGeometry, skyboxMaterials)
scene.value.add(skybox)
scene.value.background = new THREE.Color(0x000000)
// 添加雾效增强深度感
scene.value.fog = new THREE.Fog(0x000000, 10, 50)
// 创建透视相机 PerspectiveCamera(视野角度, 宽高比, 近截面, 远截面)
camera.value = new THREE.PerspectiveCamera(
60,
width / height,
0.1,
1000,
)
camera.value.position.copy(initialCameraPosition)
// 创建渲染器
renderer.value = new THREE.WebGLRenderer({ antialias: true })
renderer.value.setSize(width, height)
renderer.value.setPixelRatio(window.devicePixelRatio)
// 开启阴影贴图
renderer.value.shadowMap.enabled = true
renderer.value.shadowMap.type = THREE.PCFSoftShadowMap
threeRef.value!.appendChild(renderer.value.domElement)
// 创建轨道控制器
controls.value = new OrbitControls(camera.value, renderer.value.domElement)
controls.value.enableDamping = true // 启用阻尼(惯性)
controls.value.dampingFactor = 0.05
controls.value.minDistance = 2
controls.value.maxDistance = 50
controls.value.maxPolarAngle = Math.PI / 2 * 1.8 // 限制垂直旋转角度
// 添加环境光
const ambientLight = new THREE.AmbientLight(0xFFFFFF, 0.6)
scene.value.add(ambientLight)
// 添加主平行光(模拟太阳光)
const directionalLight = new THREE.DirectionalLight(0xFFFFFF, 1.2)
directionalLight.position.set(10, 15, 10)
directionalLight.castShadow = true
directionalLight.shadow.camera.left = -20
directionalLight.shadow.camera.right = 20
directionalLight.shadow.camera.top = 20
directionalLight.shadow.camera.bottom = -20
directionalLight.shadow.mapSize.width = 2048
directionalLight.shadow.mapSize.height = 2048
scene.value.add(directionalLight)
// 添加辅助光源(从另一个方向)
const fillLight = new THREE.DirectionalLight(0xFFFFFF, 0.5)
fillLight.position.set(-10, 10, -10)
scene.value.add(fillLight)
// 添加地面
const groundGeometry = new THREE.PlaneGeometry(100, 100)
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x999999,
roughness: 0.8,
metalness: 0.2,
})
const ground = new THREE.Mesh(groundGeometry, groundMaterial)
ground.rotation.x = -Math.PI / 2
ground.position.y = 0
ground.receiveShadow = true
scene.value.add(ground)
// 加载模型
const loader = new GLTFLoader()
loader.load('/mzjc_bansw.glb', (gltf) => {
const model = gltf.scene
// 遍历模型,启用阴影
model.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.castShadow = true
child.receiveShadow = true
}
})
scene.value!.add(model)
}, undefined, (error) => {
console.error('模型加载失败:', error)
})
function animate() {
requestAnimationFrame(animate)
// 巡检漫游效果
if (tourMode.value !== 'none' && camera.value && controls.value) {
const speed = tourMode.value === 'slow' ? 0.003 : 0.008 // 慢速或快速
tourTime += speed
const totalPoints = inspectionPath.length
const totalProgress = tourTime % totalPoints
const currentIndex = Math.floor(totalProgress)
const nextIndex = (currentIndex + 1) % totalPoints
const t = totalProgress - currentIndex
// 使用平滑的缓动函数
const smoothT = t * t * (3 - 2 * t) // smoothstep
// 获取当前和下一个路径点
const currentPoint = inspectionPath[currentIndex]!
const nextPoint = inspectionPath[nextIndex]!
// 插值位置
const newPosition = new THREE.Vector3()
newPosition.lerpVectors(currentPoint.position, nextPoint.position, smoothT)
camera.value.position.copy(newPosition)
// 插值观察目标
const newTarget = new THREE.Vector3()
newTarget.lerpVectors(currentPoint.lookAt, nextPoint.lookAt, smoothT)
controls.value.target.copy(newTarget)
// 禁用控制器的用户交互
controls.value.enabled = false
}
else if (controls.value) {
// 非漫游模式时启用控制器
controls.value.enabled = true
}
// 更新控制器
controls.value?.update()
// 执行渲染
if (renderer.value && scene.value && camera.value) {
renderer.value.render(scene.value, camera.value)
}
}
animate()
// 窗口自适应
window.addEventListener('resize', handleResize)
}
// 处理窗口大小变化
function handleResize() {
if (!threeRef.value || !camera.value || !renderer.value)
return
const width = threeRef.value.clientWidth
const height = threeRef.value.clientHeight
// 更新相机宽高比
camera.value.aspect = width / height
camera.value.updateProjectionMatrix()
// 更新渲染器尺寸
renderer.value.setSize(width, height)
renderer.value.setPixelRatio(window.devicePixelRatio)
}
// 慢速巡检漫游
function startSlowTour() {
tourMode.value = 'slow'
tourTime = 0
}
// 快速巡检漫游
function startFastTour() {
tourMode.value = 'fast'
tourTime = 0
}
// 停止漫游
function stopTour() {
tourMode.value = 'none'
tourTime = 0
if (camera.value && controls.value) {
camera.value.position.copy(initialCameraPosition)
controls.value.target.copy(initialCameraTarget)
controls.value.enabled = true
controls.value.update()
}
}
onMounted(initModel)
onUnmounted(() => {
// 清理事件监听
window.removeEventListener('resize', handleResize)
// 销毁控制器
controls.value?.dispose()
// 销毁渲染器
renderer.value?.dispose()
})
</script>
<template>
<div>7</div>
<div ref="threeRef" class="w-full h-full" />
<router-link to="/three/8" class="position-fixed left-20px top-20px">
漫游 修改材质设备告警业务
</router-link>
<div class="position-fixed right-20px top-20px flex flex-col gap-12px">
<div class="flex gap-12px">
<button
:class="{ 'bg-blue-500 text-white': tourMode === 'slow' }"
class="px-16px py-8px rounded hover:bg-blue-400"
@click="startSlowTour"
>
慢速巡检
</button>
<button
:class="{ 'bg-green-500 text-white': tourMode === 'fast' }"
class="px-16px py-8px rounded hover:bg-green-400"
@click="startFastTour"
>
快速巡检
</button>
<button
:class="{ 'bg-red-500 text-white': tourMode === 'none' }"
class="px-16px py-8px rounded hover:bg-red-400"
@click="stopTour"
>
停止漫游
</button>
</div>
<div v-if="tourMode !== 'none'" class="bg-white px-12px py-8px rounded shadow text-sm">
正在进行{{ tourMode === 'slow' ? '慢速' : '快速' }}巡检...
</div>
</div>
</template>