<template><div class="obj-viewer-wrapper">< !-- 模型渲染容器 --><div ref="container" class="three-container"></div>< !-- 加载状态 --><div v-if="isLoading" class="loading-overlay"><div class="spinner"></div><p class="loading-text">加载模型中... {
		{
		loadProgress
	}
}

%</p></div>< !-- 错误提示 --><div v-if="errorMessage" class="error-overlay"><div class="error-card"><i class="fa fa-exclamation-triangle error-icon"></i><h3>加载失败</h3><p> {
		{
		errorMessage
	}
}

</p><button @click="retryLoad" class="retry-btn">重试</button></div></div></div></template><script>import {
	ref,
	onMounted,
	onBeforeUnmount,
	watch
}

from "vue";
import * as THREE from "three";

import {
	OBJLoader
}

from "three/examples/jsm/loaders/OBJLoader";

import {
	OrbitControls
}

from "three/examples/jsm/controls/OrbitControls";

export default {

	name: "ThreeObjViewer",
	props: {

		// 模型URL，支持外部传入
		modelUrl: {
			type: String,
				default: '/static/edb0684352085c56e4c378e958b905ea.obj'
		}

		,
		// 模型缩放比例
		modelScale: {
			type: Number,
				default: 0.6
		}

		,
		// 自动旋转速度
		rotationSpeed: {
			type: Number,
				default: 0.005
		}

		,
		// 容器高度
		containerHeight: {
			type: Number,
				default: 240
		}

		,
		// 模型颜色
		modelColor: {
			type: String,
				default: '#4287f5'
		}
	}

	,
	setup(props) {
		// 状态管理
		const container=ref(null);
		const isLoading=ref(true);
		const loadProgress=ref(0);
		const errorMessage=ref('');

		// Three.js核心对象
		let scene,
		camera,
		renderer,
		controls;
		let loadedObject=null;
		let animationId=null;
		let resizeHandler=null;

		// 初始化Three.js场景
		const initScene=()=> {
			if ( !container.value) return;

			const containerWidth=container.value.clientWidth;
			const containerHeight=container.value.clientHeight;

			// 创建场景
			scene=new THREE.Scene();
			scene.background=null;

			// 创建相机
			camera=new THREE.PerspectiveCamera(75,
				containerWidth / containerHeight,
				0.1,
				1000);
			camera.position.set(0, 5, 15);

			// 创建渲染器
			renderer=new THREE.WebGLRenderer({
				alpha: true,
				antialias: true,
				powerPreference: "high-performance" // 优先使用高性能GPU
			});
		renderer.setClearColor(0x000000, 0);
		renderer.setSize(containerWidth, containerHeight);
		renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); // 限制像素比，平衡性能和质量
		container.value.appendChild(renderer.domElement);

		// 添加光源
		const ambientLight=new THREE.AmbientLight(0xffffff, 0.7);
		scene.add(ambientLight);

		const directionalLight=new THREE.DirectionalLight(0xffffff, 1.0);
		directionalLight.position.set(10, 15, 10);
		scene.add(directionalLight);

		// 添加辅助光源，确保模型各角度都有光照
		const directionalLight2=new THREE.DirectionalLight(0xffffff, 0.5);
		directionalLight2.position.set(-10, 15, -10);
		scene.add(directionalLight2);

		// 添加控制器
		controls=new OrbitControls(camera, renderer.domElement);
		controls.enableDamping=true;
		controls.dampingFactor=0.05;
		controls.screenSpacePanning=false;
		controls.maxPolarAngle=Math.PI / 2;
		controls.minDistance=1;
		controls.maxDistance=100;
	}

	;

	// 加载模型
	const loadModel=()=> {
		if ( !scene) return;

		isLoading.value=true;
		errorMessage.value='';
		loadedObject=null;

		const objLoader=new OBJLoader();

		objLoader.load(props.modelUrl,
			(object)=> {

				// 清理可能存在的旧模型
				if (loadedObject) {
					scene.remove(loadedObject);
				}

				// 设置模型材质和属性
				object.traverse((child)=> {
						if (child.isMesh) {
							child.material=new THREE.MeshStandardMaterial({
								color: props.modelColor,
								roughness: 0.5,
								metalness: 0.5,
								transparent: false
							});
						child.material.needsUpdate=true;

						// 启用剔除，提升性能
						child.frustumCulled=true;
					}
				});

			// 设置模型位置和缩放
			object.position.set(0, 0, 0);
			object.scale.set(props.modelScale, props.modelScale, props.modelScale);

			// 添加到场景
			scene.add(object);
			loadedObject=object;

			// 自动调整相机位置以适配模型
			autoFitCamera();

			isLoading.value=false;
		}

		,
		(xhr)=> {
			// 更新加载进度
			const progress=Math.round((xhr.loaded / xhr.total) * 100);
			loadProgress.value=progress;
		}

		,
		(error)=> {
			console.error("模型加载错误:", error);
			isLoading.value=false;

			// 提供更具体的错误信息
			if (error.message.includes('404')) {
				errorMessage.value='模型文件未找到，请检查路径是否正确';
			}

			else if (error.message.includes('Failed to fetch')) {
				errorMessage.value='网络错误，无法加载模型文件';
			}

			else {
				errorMessage.value=`模型加载失败: ${error.message.substring(0, 50)}...`;
			}
		});
}

;

// 自动调整相机位置以适配模型
const autoFitCamera=()=> {
	if ( !loadedObject || !camera) return;

	// 计算模型包围盒
	const box=new THREE.Box3().setFromObject(loadedObject);
	const size=box.getSize(new THREE.Vector3());
	const center=box.getCenter(new THREE.Vector3());

	// 计算模型最大尺寸
	const maxDim=Math.max(size.x, size.y, size.z);
	const fov=camera.fov * (Math.PI / 180);
	let cameraZ=Math.abs(maxDim / 2 / Math.tan(fov / 2)) * 2;

	// 调整相机位置
	camera.position.z=cameraZ;
	controls.target.set(center.x, center.y, center.z);
	controls.update();
}

;

// 动画循环
const animate=()=> {
	animationId=requestAnimationFrame(animate);

	// 只有在模型加载完成且可见时才执行旋转
	if (loadedObject && !isLoading.value && !errorMessage.value) {
		loadedObject.rotation.y+=props.rotationSpeed;
	}

	if (controls) controls.update();

	if (renderer && scene && camera) {
		renderer.render(scene, camera);
	}
}

;

// 处理窗口大小变化
const handleResize=()=> {
	if ( !container.value || !camera || !renderer) return;

	const width=container.value.clientWidth;
	const height=container.value.clientHeight;

	camera.aspect=width / height;
	camera.updateProjectionMatrix();
	renderer.setSize(width, height);
}

;

// 重试加载
const retryLoad=()=> {
	if (scene) {
		loadModel();
	}

	else {
		initScene();
		loadModel();
	}
}

;

// 初始化
onMounted(()=> {

		// 确保DOM已加载
		setTimeout(()=> {
				initScene();
				loadModel();
				animate();

				// 监听窗口大小变化，使用防抖优化
				resizeHandler=()=> {
					clearTimeout(window.resizeTimeout);
					window.resizeTimeout=setTimeout(handleResize, 100);
				}

				;

				window.addEventListener('resize', resizeHandler);
			}

			, 0);
	});

// 清理资源
onBeforeUnmount(()=> {

		// 停止动画
		if (animationId) {
			cancelAnimationFrame(animationId);
		}

		// 移除事件监听
		if (resizeHandler) {
			window.removeEventListener('resize', resizeHandler);
		}

		// 释放控制器
		if (controls) {
			controls.dispose();
		}

		// 释放渲染器
		if (renderer) {
			renderer.dispose();
		}

		// 清空场景
		if (scene) {
			scene.clear();
		}

		// 移除DOM元素
		if (container.value && container.value.firstChild) {
			container.value.removeChild(container.value.firstChild);
		}
	});

// 监听模型URL变化，重新加载
watch(()=> props.modelUrl,
	(newUrl)=> {
		if (newUrl && scene) {
			loadModel();
		}
	});

return {
	container,
	isLoading,
	loadProgress,
	errorMessage,
	retryLoad
}

;
}
}

;

</script><style scoped>.obj-viewer-wrapper {
	position: relative;
	width: 100%;
	height: v-bind(containerHeight + 'px');
}

.three-container {
	width: 100%;
	height: 100%;
}

/* 加载状态样式 */
.loading-overlay {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	background-color: rgba(255, 255, 255, 0.8);
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: center;
	z-index: 10;
	transition: opacity 0.3s ease;
}

.spinner {
	width: 40px;
	height: 40px;
	border: 4px solid #f3f3f3;
	border-top: 4px solid #4287f5;
	border-radius: 50%;
	animation: spin 1s linear infinite;
	margin-bottom: 12px;
}

.loading-text {
	color: #333;
	font-size: 14px;
}

/* 错误提示样式 */
.error-overlay {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	background-color: rgba(0, 0, 0, 0.7);
	display: flex;
	justify-content: center;
	align-items: center;
	z-index: 10;
}

.error-card {
	background-color: white;
	padding: 20px;
	border-radius: 8px;
	max-width: 300px;
	text-align: center;
}

.error-icon {
	color: #ff9800;
	font-size: 24px;
	margin-bottom: 10px;
}

.error-card h3 {
	margin: 0 0 10px 0;
	color: #333;
}

.error-card p {
	margin: 0 0 15px 0;
	color: #666;
	font-size: 14px;
}

.retry-btn {
	background-color: #4287f5;
	color: white;
	border: none;
	padding: 8px 16px;
	border-radius: 4px;
	cursor: pointer;
	transition: background-color 0.2s;
}

.retry-btn:hover {
	background-color: #3078e6;
}

/* 动画 */
@keyframes spin {
	0% {
		transform: rotate(0deg);
	}

	100% {
		transform: rotate(360deg);
	}
}

/* 响应式调整 */
@media (max-width: 768px) {
	.error-card {
		width: 80%;
		padding: 15px;
	}
}

</style>