// main.js (最终版,包含UI缩放按钮和更新后的数据) import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { OBJLoader } from 'three/addons/loaders/OBJLoader.js'; import { MTLLoader } from 'three/addons/loaders/MTLLoader.js'; // --- 1. 数据中心 --- // 在这里管理所有的中药饮片信息。 const herbsData = [ { id: 'biejia', name: '鳖甲', path: './assets/biejia/', // 模型所在文件夹路径 objFile: 'biejia.obj', mtlFile: 'biejia.mtl', }, { id: 'wanglingzhi', // 已按要求修改 name: '醋五灵脂', path: './assets/wanglingzhi/', objFile: 'wanglingzhi.obj', mtlFile: 'wanglingzhi.mtl', }, { id: 'jiangcan', name: '僵蚕', path: './assets/jiangcan/', objFile: 'jiangchan-xiao.obj', mtlFile: 'jiangchan-xiao.mtl', } ]; // --- 2. 全局变量 --- let scene, camera, renderer, controls; let currentModel = null; const loadingOverlay = document.getElementById('loading-overlay'); const progressBar = document.getElementById('progress-bar'); const progressText = document.getElementById('progress-text'); // --- 3. 初始化函数 --- function init() { // 基础设置 const canvas = document.querySelector('#c'); renderer = new THREE.WebGLRenderer({ antialias: true, canvas }); scene = new THREE.Scene(); scene.background = new THREE.Color(0x333344); // 相机和控制器 camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 10, 30); controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.target.set(0, 5, 0); // 光照 scene.add(new THREE.AmbientLight(0xffffff, 0.8)); const dirLight = new THREE.DirectionalLight(0xffffff, 1.2); dirLight.position.set(10, 15, 10); scene.add(dirLight); // 渲染循环 function render() { // 响应式调整 const pixelRatio = window.devicePixelRatio; const width = canvas.clientWidth * pixelRatio | 0; const height = canvas.clientHeight * pixelRatio | 0; if (renderer.domElement.width !== width || renderer.domElement.height !== height) { renderer.setSize(width, height, false); camera.aspect = canvas.clientWidth / canvas.clientHeight; camera.updateProjectionMatrix(); } controls.update(); renderer.render(scene, camera); requestAnimationFrame(render); } requestAnimationFrame(render); // 创建UI界面 createUI(); // 添加UI按钮的事件监听 setupZoomControls(); // 默认加载第一个模型 if (herbsData.length > 0) { loadModel(herbsData[0]); document.querySelector(`#herb-list li[data-id='${herbsData[0].id}']`).classList.add('active'); } } // --- 4. UI创建与交互 --- function createUI() { const herbList = document.getElementById('herb-list'); herbsData.forEach(herb => { const li = document.createElement('li'); li.textContent = herb.name; li.dataset.id = herb.id; li.addEventListener('click', () => { herbList.querySelectorAll('li').forEach(item => item.classList.remove('active')); li.classList.add('active'); loadModel(herb); }); herbList.appendChild(li); }); } // --- 新增:设置缩放按钮的功能 --- // --- 修改后的函数:设置缩放按钮的功能 --- function setupZoomControls() { const zoomInBtn = document.getElementById('zoom-in-btn'); const zoomOutBtn = document.getElementById('zoom-out-btn'); const zoomScale = 1.2; // 定义一个缩放因子,大于1 zoomInBtn.addEventListener('click', () => { // --- 直接修改相机位置 --- // 获取从相机指向目标点的向量 const offset = new THREE.Vector3().subVectors(camera.position, controls.target); // 将这个向量缩短 (除以一个大于1的数) offset.multiplyScalar(1 / zoomScale); // 将新的向量加回到目标点,得到相机的新位置 camera.position.copy(controls.target).add(offset); // 我们不需要手动调用 controls.update(),因为 render 循环会做这件事 }); zoomOutBtn.addEventListener('click', () => { // --- 直接修改相机位置 --- // 获取从相机指向目标点的向量 const offset = new THREE.Vector3().subVectors(camera.position, controls.target); // 将这个向量拉长 (乘以一个大于1的数) offset.multiplyScalar(zoomScale); // 将新的向量加回到目标点,得到相机的新位置 camera.position.copy(controls.target).add(offset); }); } // --- 5. 模型加载函数 (带进度条) --- function loadModel(herb) { if (currentModel) { scene.remove(currentModel); } loadingOverlay.classList.add('visible'); progressBar.style.width = '0%'; progressText.textContent = '0%'; const onProgress = (xhr) => { if (xhr.lengthComputable) { const percentComplete = (xhr.loaded / xhr.total) * 100; const percent = Math.round(percentComplete); progressBar.style.width = percent + '%'; progressText.textContent = percent + '%'; } }; const onError = (error) => { console.error(`加载模型 ${herb.name} 时发生错误:`, error); loadingOverlay.classList.remove('visible'); }; const mtlLoader = new MTLLoader(); mtlLoader.setResourcePath(herb.path); mtlLoader.load( herb.path + herb.mtlFile, (materials) => { materials.preload(); const objLoader = new OBJLoader(); objLoader.setMaterials(materials); objLoader.load( herb.path + herb.objFile, (object) => { const box = new THREE.Box3().setFromObject(object); const center = box.getCenter(new THREE.Vector3()); const size = box.getSize(new THREE.Vector3()); object.position.sub(center); const maxDim = Math.max(size.x, size.y, size.z); if (maxDim > 0) { const scale = 15 / maxDim; object.scale.set(scale, scale, scale); } scene.add(object); currentModel = object; console.log(`成功加载: ${herb.name}`); loadingOverlay.classList.remove('visible'); }, onProgress, onError ); }, onProgress, onError ); } // --- 启动系统 --- init();