|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="div3D" ref="div3D" v-loading="loading" element-loading-text="渲染模型..."></div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 固定位置的控制按钮 -->
|
|
|
|
|
|
<div class="control-buttons">
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="warning"
|
|
|
|
|
|
@click="tranlate(new Vector3(28, 10, -10), new Vector3(0, 0, 0))"
|
|
|
|
|
|
:icon="Refresh"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { ref, onMounted, onUnmounted } from 'vue';
|
|
|
|
|
|
//导入 threejs
|
|
|
|
|
|
import {
|
|
|
|
|
|
AxesHelper,
|
|
|
|
|
|
Color,
|
|
|
|
|
|
PerspectiveCamera,
|
|
|
|
|
|
Scene,
|
|
|
|
|
|
WebGLRenderer,
|
|
|
|
|
|
EquirectangularReflectionMapping,
|
|
|
|
|
|
Group,
|
|
|
|
|
|
Vector3,
|
|
|
|
|
|
AmbientLight,
|
|
|
|
|
|
DirectionalLight,
|
|
|
|
|
|
PointLight
|
|
|
|
|
|
} from 'three';
|
|
|
|
|
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
|
|
|
|
|
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
|
|
|
|
|
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader';
|
|
|
|
|
|
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
|
|
|
|
|
import gsap from 'gsap';
|
|
|
|
|
|
import { Star, Bell, MagicStick, Refresh, Compass } from '@element-plus/icons-vue';
|
|
|
|
|
|
|
|
|
|
|
|
const loading = ref(true); // 添加 loading 状态
|
|
|
|
|
|
const div3D = ref<HTMLDivElement>();
|
|
|
|
|
|
//场景
|
|
|
|
|
|
const scene = new Scene();
|
|
|
|
|
|
|
|
|
|
|
|
//相机
|
|
|
|
|
|
const camera = new PerspectiveCamera(
|
|
|
|
|
|
45,
|
|
|
|
|
|
div3D.value?.clientWidth! / div3D.value?.clientHeight!,
|
|
|
|
|
|
0.05,
|
|
|
|
|
|
200
|
|
|
|
|
|
); // 初始比例设为1
|
|
|
|
|
|
camera.position.set(30, 15, -15);
|
|
|
|
|
|
camera.lookAt(0, 0, 0);
|
|
|
|
|
|
scene.add(camera);
|
|
|
|
|
|
|
|
|
|
|
|
//渲染器
|
|
|
|
|
|
const render = new WebGLRenderer({
|
|
|
|
|
|
antialias: true, // 关闭抗锯齿提升性能
|
|
|
|
|
|
powerPreference: 'high-performance', // 使用高性能GPU
|
|
|
|
|
|
stencil: false, // 如果不需要模板缓冲区
|
|
|
|
|
|
depth: true,
|
|
|
|
|
|
logarithmicDepthBuffer: true // 对大场景有帮助
|
|
|
|
|
|
});
|
|
|
|
|
|
render.setClearColor(new Color('#131519'));
|
|
|
|
|
|
// 启用渲染优化
|
|
|
|
|
|
render.shadowMap.enabled = false; // 如果不需要阴影
|
|
|
|
|
|
|
|
|
|
|
|
//添加控制器
|
|
|
|
|
|
const controls = new OrbitControls(camera, render.domElement);
|
|
|
|
|
|
// 禁用滚轮缩放
|
|
|
|
|
|
// controls.enableZoom = false;
|
|
|
|
|
|
// controls.autoRotate = false;
|
|
|
|
|
|
// controls.enablePan = false; // 禁用右键平移
|
|
|
|
|
|
controls.autoRotateSpeed = 1;
|
|
|
|
|
|
controls.dampingFactor = 0.2;
|
|
|
|
|
|
controls.rotateSpeed = 0.3; // 降低旋转速度(默认为 1.0)
|
|
|
|
|
|
controls.enableDamping = true;
|
|
|
|
|
|
controls.enableRotate = true;
|
|
|
|
|
|
controls.minDistance = 1; // 允许更近距离观察模型细节
|
|
|
|
|
|
// 设置视角限制:向上45度,向下45度
|
|
|
|
|
|
// 从正前方开始计算,向上最大45度,向下最大45度
|
|
|
|
|
|
controls.minPolarAngle = Math.PI / 4; // 向上最多45度 (π/4)
|
|
|
|
|
|
controls.maxPolarAngle = (Math.PI * 3) / 4; // 向下最多45度 (3π/4)
|
|
|
|
|
|
|
|
|
|
|
|
// 大场景优化配置
|
|
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
|
const loader = new GLTFLoader();
|
|
|
|
|
|
const dracoLoader = new DRACOLoader();
|
|
|
|
|
|
loader.setDRACOLoader(dracoLoader);
|
|
|
|
|
|
let meshArr: Group[] = [];
|
|
|
|
|
|
// public\models\nitrogenInjection\nitrogenInjection.gltf
|
|
|
|
|
|
loader.load('/models/substation/substation.gltf', (gltf: any) => {
|
|
|
|
|
|
// 保存模型引用
|
|
|
|
|
|
loadedModel = gltf.scene;
|
|
|
|
|
|
scene.add(gltf.scene);
|
|
|
|
|
|
const loadTime = Date.now() - startTime;
|
|
|
|
|
|
|
|
|
|
|
|
// 立即渲染一帧
|
|
|
|
|
|
// controls.update();
|
|
|
|
|
|
render.render(scene, camera);
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`大场景模型已加载并显示,总耗时:${loadTime}ms`);
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// // 环境光:增强整体亮度
|
|
|
|
|
|
// const ambientLight = new AmbientLight('#ffffff', 1.2); // 强度从 0.6 提高到 1.0
|
|
|
|
|
|
// scene.add(ambientLight);
|
|
|
|
|
|
|
|
|
|
|
|
// // 平行光
|
|
|
|
|
|
// const directionalLight = new DirectionalLight('#ffffff', 1.4); // 强度从 0.2 提高到 0.8
|
|
|
|
|
|
// scene.add(directionalLight);
|
|
|
|
|
|
// directionalLight.position.set(20, 20, 10);
|
|
|
|
|
|
|
|
|
|
|
|
// // // 点光源
|
|
|
|
|
|
// const pointLight = new PointLight('#ffffff', 1.4, 1800); // 强度从 0.5 提高到 1.0
|
|
|
|
|
|
// scene.add(pointLight);
|
|
|
|
|
|
// pointLight.position.set(0, 40, 0);
|
|
|
|
|
|
|
|
|
|
|
|
// 环境贴图(保存引用以便清理)
|
|
|
|
|
|
const rgbELoader = new RGBELoader().load(
|
|
|
|
|
|
'/models/texture/Alex_Hart-Nature_Lab_Bones_2k.hdr',
|
|
|
|
|
|
(envMap: any) => {
|
|
|
|
|
|
envMap.mapping = EquirectangularReflectionMapping;
|
|
|
|
|
|
scene.environment = envMap;
|
|
|
|
|
|
envMapTexture = envMap; // 保存引用
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
//补间动画
|
|
|
|
|
|
const timeline1 = gsap.timeline();
|
|
|
|
|
|
const timeline2 = gsap.timeline();
|
|
|
|
|
|
|
|
|
|
|
|
// 保存模型引用以便清理
|
|
|
|
|
|
let loadedModel: any = null;
|
|
|
|
|
|
let envMapTexture: any = null;
|
|
|
|
|
|
|
|
|
|
|
|
//相机移动
|
|
|
|
|
|
function tranlate(position: Vector3, target: Vector3) {
|
|
|
|
|
|
timeline1.to(camera.position, {
|
|
|
|
|
|
x: position.x,
|
|
|
|
|
|
y: position.y,
|
|
|
|
|
|
z: position.z,
|
|
|
|
|
|
duration: 1,
|
|
|
|
|
|
ease: 'power2.inOut'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
timeline2.to(controls.target, {
|
|
|
|
|
|
x: target.x,
|
|
|
|
|
|
y: target.y,
|
|
|
|
|
|
z: target.z,
|
|
|
|
|
|
duration: 1,
|
|
|
|
|
|
ease: 'power2.inOut'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//物体
|
|
|
|
|
|
// const axes = new AxesHelper(20);
|
|
|
|
|
|
// scene.add(axes);
|
|
|
|
|
|
|
|
|
|
|
|
// 监听容器大小变化
|
|
|
|
|
|
function handleResize() {
|
|
|
|
|
|
if (!div3D.value) return;
|
|
|
|
|
|
|
|
|
|
|
|
const container = div3D.value;
|
|
|
|
|
|
const width = container.clientWidth;
|
|
|
|
|
|
const height = container.clientHeight;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新相机比例
|
|
|
|
|
|
camera.aspect = width / height;
|
|
|
|
|
|
camera.updateProjectionMatrix();
|
|
|
|
|
|
|
|
|
|
|
|
// 更新渲染器大小
|
|
|
|
|
|
render.setSize(width, height);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function init() {
|
|
|
|
|
|
requestAnimationFrame(init);
|
|
|
|
|
|
controls.update();
|
|
|
|
|
|
render.render(scene, camera);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
// 初始化时设置正确大小
|
|
|
|
|
|
handleResize();
|
|
|
|
|
|
|
|
|
|
|
|
// 启动渲染循环
|
|
|
|
|
|
init();
|
|
|
|
|
|
|
|
|
|
|
|
// 将渲染器添加到容器
|
|
|
|
|
|
div3D.value?.appendChild(render.domElement);
|
|
|
|
|
|
|
|
|
|
|
|
// 添加容器大小监听
|
|
|
|
|
|
const resizeObserver = new ResizeObserver(handleResize);
|
|
|
|
|
|
if (div3D.value) {
|
|
|
|
|
|
resizeObserver.observe(div3D.value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保存观察器引用以便销毁
|
|
|
|
|
|
(window as any)._resizeObserver = resizeObserver;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
|
console.log('开始清理 Three.js 资源...');
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 停止渲染循环(通过标记)
|
|
|
|
|
|
// requestAnimationFrame 会自动停止
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 清理 ResizeObserver
|
|
|
|
|
|
if ((window as any)._resizeObserver) {
|
|
|
|
|
|
(window as any)._resizeObserver.disconnect();
|
|
|
|
|
|
delete (window as any)._resizeObserver;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 停止 GSAP 动画
|
|
|
|
|
|
timeline1.pause();
|
|
|
|
|
|
timeline1.clear();
|
|
|
|
|
|
timeline2.pause();
|
|
|
|
|
|
timeline2.clear();
|
|
|
|
|
|
gsap.globalTimeline.clear();
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 移除 DOM 元素
|
|
|
|
|
|
if (div3D.value && render.domElement.parentNode === div3D.value) {
|
|
|
|
|
|
div3D.value.removeChild(render.domElement);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 清理控制器
|
|
|
|
|
|
controls.dispose();
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 清理模型
|
|
|
|
|
|
if (loadedModel) {
|
|
|
|
|
|
loadedModel.traverse((child: any) => {
|
|
|
|
|
|
if (child.isMesh) {
|
|
|
|
|
|
// 清理几何体
|
|
|
|
|
|
if (child.geometry) {
|
|
|
|
|
|
child.geometry.dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清理材质
|
|
|
|
|
|
if (child.material) {
|
|
|
|
|
|
const materials = Array.isArray(child.material) ? child.material : [child.material];
|
|
|
|
|
|
|
|
|
|
|
|
materials.forEach((material: any) => {
|
|
|
|
|
|
// 清理材质属性
|
|
|
|
|
|
[
|
|
|
|
|
|
'map',
|
|
|
|
|
|
'normalMap',
|
|
|
|
|
|
'roughnessMap',
|
|
|
|
|
|
'metalnessMap',
|
|
|
|
|
|
'emissiveMap',
|
|
|
|
|
|
'aoMap',
|
|
|
|
|
|
'displacementMap'
|
|
|
|
|
|
].forEach((key) => {
|
|
|
|
|
|
if (material[key]) {
|
|
|
|
|
|
material[key].dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 清理材质本身
|
|
|
|
|
|
material.dispose();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 从场景中移除模型
|
|
|
|
|
|
scene.remove(loadedModel);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 7. 清理环境贴图
|
|
|
|
|
|
if (envMapTexture) {
|
|
|
|
|
|
envMapTexture.dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 8. 清理灯光
|
|
|
|
|
|
scene.traverse((child: any) => {
|
|
|
|
|
|
if (child.isLight) {
|
|
|
|
|
|
child.dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 9. 清理场景
|
|
|
|
|
|
while (scene.children.length > 0) {
|
|
|
|
|
|
scene.remove(scene.children[0]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 10. 清理渲染器
|
|
|
|
|
|
render.dispose();
|
|
|
|
|
|
render.forceContextLoss();
|
|
|
|
|
|
render.domElement.remove();
|
|
|
|
|
|
|
|
|
|
|
|
// 11. 清空场景引用
|
|
|
|
|
|
scene.clear();
|
|
|
|
|
|
|
|
|
|
|
|
console.log('Three.js 资源清理完成');
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.div3D {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
/* width: 500px;
|
|
|
|
|
|
height: 500px; */
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.control-buttons {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 50%;
|
|
|
|
|
|
left: 2%;
|
|
|
|
|
|
transform: translateY(-50%); /* 垂直居中 */
|
|
|
|
|
|
transform: translateZ(0);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 20px;
|
|
|
|
|
|
z-index: 100;
|
|
|
|
|
|
align-items: flex-start; /* 确保所有按钮左对齐 */
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|