init
This commit is contained in:
288
index.html
Normal file
288
index.html
Normal file
@@ -0,0 +1,288 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user