Files
obj_boom/index.html
nicomacbookpro c0e405b34c init
2025-12-12 16:21:46 +08:00

288 lines
9.0 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D模型拆解动画 - ExplosionManager</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
overflow: hidden;
}
#box {
width: 100vw;
height: 100vh;
position: relative;
}
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 24px;
z-index: 100;
text-align: center;
}
.loading-spinner {
border: 4px solid rgba(255, 255, 255, 0.3);
border-top: 4px solid white;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.info {
position: absolute;
top: 20px;
left: 20px;
color: white;
background: rgba(0, 0, 0, 0.5);
padding: 15px;
border-radius: 8px;
font-size: 14px;
z-index: 10;
max-width: 300px;
}
.info h2 {
margin-bottom: 10px;
font-size: 18px;
}
.info p {
margin: 5px 0;
line-height: 1.6;
}
.hidden {
display: none;
}
</style>
</head>
<body>
<div id="box"></div>
<div id="loading" class="loading">
<div class="loading-spinner"></div>
<div>正在加载模型...</div>
</div>
<div class="info">
<h2>3D模型拆解动画</h2>
<p>使用鼠标拖拽旋转视角</p>
<p>使用滚轮缩放</p>
<p>右侧控制面板可控制动画</p>
</div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/",
"gsap": "https://cdn.jsdelivr.net/npm/gsap@3.12.5/index.js",
"dat.gui": "https://cdn.jsdelivr.net/npm/dat.gui@0.7.9/build/dat.gui.module.js"
}
}
</script>
<script type="module">
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import * as dat from 'dat.gui'
import { ExplosionManager } from './src/ExplosionManager.js'
const box = document.getElementById('box')
const loading = document.getElementById('loading')
// 创建场景
const scene = new THREE.Scene()
scene.background = new THREE.Color(0x1a1a2e)
// 创建相机
const camera = new THREE.PerspectiveCamera(75, box.clientWidth / box.clientHeight, 0.1, 1000)
camera.position.set(5, 5, 5)
// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, logarithmicDepthBuffer: true })
renderer.setSize(box.clientWidth, box.clientHeight)
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
box.appendChild(renderer.domElement)
// 轨道控制器
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.dampingFactor = 0.05
controls.minDistance = 2
controls.maxDistance = 50
// 窗口大小调整
window.addEventListener('resize', () => {
renderer.setSize(box.clientWidth, box.clientHeight)
camera.aspect = box.clientWidth / box.clientHeight
camera.updateProjectionMatrix()
})
// 添加光照
scene.add(new THREE.AmbientLight(0xffffff, 0.6))
const pointLight = new THREE.PointLight(0xffffff, 1.5, 0, 2)
pointLight.position.set(5, 5, 5)
pointLight.castShadow = true
scene.add(pointLight)
const directionalLight = new THREE.DirectionalLight(0xffffff, 2)
directionalLight.position.set(-5, 5, -5)
directionalLight.castShadow = true
scene.add(directionalLight)
// 添加坐标轴辅助线
scene.add(new THREE.AxesHelper(1000))
// 创建爆炸管理器
const explosionManager = new ExplosionManager(scene)
// 加载模型
async function loadModel() {
try {
// 加载OBJ模型public目录下的模型
await explosionManager.loadOBJ(
'./public/model/刘家大堰室内示例.obj',
'./public/model/刘家大堰室内示例.mtl',
(progress) => {
if (progress.lengthComputable) {
const percent = (progress.loaded / progress.total) * 100
console.log(`加载进度: ${percent.toFixed(2)}%`)
}
}
)
console.log('模型加载成功')
loading.classList.add('hidden')
// 自动调整相机位置以适应模型
const model = explosionManager.getModel()
if (model) {
const box = new THREE.Box3().setFromObject(model)
const center = box.getCenter(new THREE.Vector3())
const size = box.getSize(new THREE.Vector3())
const maxDim = Math.max(size.x, size.y, size.z)
const distance = maxDim * 2
camera.position.set(center.x + distance, center.y + distance, center.z + distance)
controls.target.copy(center)
controls.update()
}
} catch (error) {
console.error('模型加载失败:', error)
loading.innerHTML = '<div style="color: #ff6b6b;">模型加载失败,请检查文件路径</div>'
}
}
// 创建GUI控制面板
const gui = new dat.GUI({ name: '控制面板' })
gui.domElement.style.position = 'absolute'
gui.domElement.style.top = '20px'
gui.domElement.style.right = '20px'
gui.domElement.style.zIndex = '100'
// 动画控制
const controlsObj = {
'拆解动画': () => {
explosionManager.explode({
duration: params.duration,
distance: params.distance,
ease: params.ease,
onComplete: () => {
console.log('拆解动画完成')
}
})
},
'还原动画': () => {
explosionManager.restore({
duration: params.duration,
ease: params.ease,
onComplete: () => {
console.log('还原动画完成')
}
})
},
'切换状态': () => {
explosionManager.toggle({
duration: params.duration,
distance: params.distance,
ease: params.ease
})
}
}
gui.add(controlsObj, '拆解动画').name('💥 拆解')
gui.add(controlsObj, '还原动画').name('🔄 还原')
gui.add(controlsObj, '切换状态').name('🔄 切换')
// 动画参数
const params = {
duration: 1,
distance: 1.5,
ease: 'power2.inOut'
}
const paramsFolder = gui.addFolder('动画参数')
paramsFolder.add(params, 'duration', 0.1, 3).name('时长(秒)').onChange((value) => {
explosionManager.setAnimationParams({ duration: value })
})
paramsFolder.add(params, 'distance', 0.5, 15).name('距离').onChange((value) => {
explosionManager.setAnimationParams({ distance: value })
})
paramsFolder.add(params, 'ease', [
'power1.inOut',
'power2.inOut',
'power3.inOut',
'power4.inOut',
'back.inOut',
'elastic.inOut',
'bounce.inOut'
]).name('缓动函数').onChange((value) => {
explosionManager.setAnimationParams({ ease: value })
})
paramsFolder.open()
// 渲染循环
function animate() {
requestAnimationFrame(animate)
controls.update()
renderer.render(scene, camera)
}
// 开始加载模型和动画
loadModel()
animate()
</script>
</body>
</html>