|
|
|
|
|
|
|
|
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'; |
|
|
|
|
|
|
|
|
|
|
|
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', |
|
|
} |
|
|
]; |
|
|
|
|
|
|
|
|
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'); |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
createUI(); |
|
|
|
|
|
|
|
|
setupZoomControls(); |
|
|
|
|
|
|
|
|
if (herbsData.length > 0) { |
|
|
loadModel(herbsData[0]); |
|
|
document.querySelector(`#herb-list li[data-id='${herbsData[0].id}']`).classList.add('active'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
zoomInBtn.addEventListener('click', () => { |
|
|
|
|
|
|
|
|
const offset = new THREE.Vector3().subVectors(camera.position, controls.target); |
|
|
|
|
|
offset.multiplyScalar(1 / zoomScale); |
|
|
|
|
|
camera.position.copy(controls.target).add(offset); |
|
|
|
|
|
|
|
|
}); |
|
|
|
|
|
zoomOutBtn.addEventListener('click', () => { |
|
|
|
|
|
|
|
|
const offset = new THREE.Vector3().subVectors(camera.position, controls.target); |
|
|
|
|
|
offset.multiplyScalar(zoomScale); |
|
|
|
|
|
camera.position.copy(controls.target).add(offset); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
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(); |