<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>沉浸式3D水库大坝模拟</title>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/GLTFLoader.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
overflow: hidden;
background: linear-gradient(to bottom, #1a2980, #26d0ce);
color: #fff;
}
#container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
#info-panel {
position: absolute;
top: 20px;
left: 20px;
background: rgba(0, 0, 30, 0.7);
border-radius: 15px;
padding: 20px;
max-width: 320px;
backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.1);
z-index: 100;
}
h1 {
font-size: 2.2rem;
margin-bottom: 15px;
color: #4facfe;
text-shadow: 0 0 10px rgba(79, 172, 254, 0.5);
}
.controls {
margin: 15px 0;
}
.control-item {
display: flex;
margin: 10px 0;
align-items: center;
}
.key {
display: inline-block;
width: 40px;
height: 40px;
background: rgba(0, 30, 60, 0.8);
border: 2px solid #4facfe;
border-radius: 8px;
text-align: center;
line-height: 36px;
margin-right: 15px;
font-weight: bold;
box-shadow: 0 0 10px rgba(79, 172, 254, 0.3);
}
.status-bar {
position: absolute;
bottom: 20px;
left: 20px;
background: rgba(0, 0, 30, 0.7);
border-radius: 15px;
padding: 15px;
font-size: 1.1rem;
backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.1);
z-index: 100;
}
.water-controls {
position: absolute;
top: 20px;
right: 20px;
background: rgba(0, 0, 30, 0.7);
border-radius: 15px;
padding: 20px;
backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.1);
z-index: 100;
}
button {
background: linear-gradient(to right, #4facfe, #00f2fe);
border: none;
color: white;
padding: 10px 20px;
margin: 5px;
border-radius: 30px;
cursor: pointer;
font-weight: bold;
transition: all 0.3s;
}
button:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0, 242, 254, 0.4);
}
#loading {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #0b1a30;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 1000;
transition: opacity 1s;
}
.progress {
width: 300px;
height: 20px;
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
margin-top: 20px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background: linear-gradient(to right, #4facfe, #00f2fe);
width: 0%;
transition: width 0.5s;
}
.logo {
font-size: 3rem;
margin-bottom: 20px;
text-align: center;
color: #4facfe;
text-shadow: 0 0 20px rgba(79, 172, 254, 0.7);
}
.logo span {
color: #00f2fe;
}
</style>
</head>
<body>
<div id="loading">
<div class="logo">3D<span>大坝</span>模拟系统</div>
<p>正在加载场景资源...</p>
<div class="progress">
<div class="progress-bar" id="progress-bar"></div>
</div>
</div>
<div id="container"></div>
<div id="info-panel">
<h1>水库大坝模拟系统</h1>
<p>本模拟展示了水力发电站的核心结构,包含大坝主体、发电机组、泄洪道和水库水体。</p>
<div class="controls">
<h3>控制说明:</h3>
<div class="control-item">
<div class="key">W</div>
<span>前进</span>
</div>
<div class="control-item">
<div class="key">S</div>
<span>后退</span>
</div>
<div class="control-item">
<div class="key">A</div>
<span>左移</span>
</div>
<div class="control-item">
<div class="key">D</div>
<span>右移</span>
</div>
<div class="control-item">
<div class="key">Q</div>
<span>上升</span>
</div>
<div class="control-item">
<div class="key">E</div>
<span>下降</span>
</div>
<div class="control-item">
<div class="key">R</div>
<span>重置视角</span>
</div>
<div class="control-item">
<div class="key">F</div>
<span>飞行模式</span>
</div>
<div class="control-item">
<div class="key">鼠标</div>
<span>视角控制</span>
</div>
</div>
</div>
<div class="status-bar">
<div>坐标: <span id="position">0, 0, 0</span></div>
<div>视角模式: <span id="mode">行走模式</span></div>
<div>时间: <span id="time">白天</span></div>
</div>
<div class="water-controls">
<h3>水体控制</h3>
<button id="normal-waves">正常波浪</button>
<button id="storm-waves">风暴模式</button>
<button id="calm-water">平静水面</button>
<button id="toggle-flow">切换水流</button>
<button id="toggle-time">切换时间</button>
</div>
<script>
// 主要变量
let scene, camera, renderer, controls;
let dam, water, terrain, river, waterfall;
let directionalLight, ambientLight, hemisphereLight;
let clock = new THREE.Clock();
let moveForward = false, moveBackward = false;
let moveLeft = false, moveRight = false;
let moveUp = false, moveDown = false;
let velocity = new THREE.Vector3();
let flyMode = false;
let isDay = true;
let waterLevel = 50;
let waterFlow = true;
// 初始化场景
function init() {
// 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB);
scene.fog = new THREE.Fog(0x87CEEB, 500, 2000);
// 创建相机
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 5000);
camera.position.set(0, 100, 200);
camera.lookAt(0, 0, 0);
// 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setPixelRatio(window.devicePixelRatio);
document.getElementById('container').appendChild(renderer.domElement);
// 添加轨道控制
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 50;
controls.maxDistance = 1000;
controls.maxPolarAngle = Math.PI / 2 - 0.1;
// 添加光源
setupLights();
// 创建地形
createTerrain();
// 创建大坝
createDam();
// 创建水体
createWater();
// 创建环境
createEnvironment();
// 添加事件监听器
setupEventListeners();
// 开始动画
animate();
// 隐藏加载界面
setTimeout(() => {
document.getElementById('loading').style.opacity = '0';
setTimeout(() => {
document.getElementById('loading').style.display = 'none';
}, 1000);
}, 2000);
// 模拟加载进度
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 10;
if (progress >= 100) {
progress = 100;
clearInterval(interval);
}
document.getElementById('progress-bar').style.width = progress + '%';
}, 100);
}
// 设置光源
function setupLights() {
// 环境光
ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambientLight);
// 半球光
hemisphereLight = new THREE.HemisphereLight(0xffffbb, 0x080820, 0.6);
scene.add(hemisphereLight);
// 方向光(太阳)
directionalLight = new THREE.DirectionalLight(0xffffff, isDay ? 0.8 : 0.3);
directionalLight.position.set(-100, 200, 100);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
scene.add(directionalLight);
// 大坝灯光
const damLight = new THREE.PointLight(0xffaa00, 1, 200);
damLight.position.set(0, 60, 0);
scene.add(damLight);
}
// 创建地形
function createTerrain() {
// 地面
const groundGeometry = new THREE.PlaneGeometry(2000, 2000, 50, 50);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x3d8c40,
roughness: 0.9,
metalness: 0.1
});
// 创建高度变化
const vertices = groundGeometry.attributes.position.array;
for (let i = 0; i < vertices.length; i += 3) {
const x = vertices[i];
const z = vertices[i + 2];
// 在河流区域创建凹陷
if (Math.abs(z) < 200) {
vertices[i + 1] = -5;
} else {
// 地形高度变化
const height = Math.sin(x * 0.01) * 20 + Math.cos(z * 0.01) * 20;
vertices[i + 1] = height;
}
}
groundGeometry.computeVertexNormals();
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -5;
ground.receiveShadow = true;
scene.add(ground);
// 添加岩石纹理
const rockGeometry = new THREE.BoxGeometry(800, 100, 2000);
const rockMaterial = new THREE.MeshStandardMaterial({
color: 0x7a7a7a,
roughness: 0.8
});
const rocks = new THREE.Mesh(rockGeometry, rockMaterial);
rocks.position.set(0, -60, 0);
rocks.receiveShadow = true;
scene.add(rocks);
}
// 创建大坝结构
function createDam() {
const damGroup = new THREE.Group();
// 坝体
const damGeometry = new THREE.BoxGeometry(300, 100, 40);
const damMaterial = new THREE.MeshStandardMaterial({
color: 0xaaaaaa,
roughness: 0.7,
metalness: 0.3
});
dam = new THREE.Mesh(damGeometry, damMaterial);
dam.position.set(0, 50, 0);
dam.castShadow = true;
dam.receiveShadow = true;
damGroup.add(dam);
// 坝顶
const topGeometry = new THREE.BoxGeometry(320, 5, 50);
const topMaterial = new THREE.MeshStandardMaterial({
color: 0xcccccc,
roughness: 0.5,
metalness: 0.5
});
const top = new THREE.Mesh(topGeometry, topMaterial);
top.position.set(0, 102, 0);
top.castShadow = true;
top.receiveShadow = true;
damGroup.add(top);
// 闸门
const gateMaterial = new THREE.MeshStandardMaterial({
color: 0x3366cc,
roughness: 0.4,
metalness: 0.6
});
for (let i = -120; i <= 120; i += 60) {
const gateGeometry = new THREE.BoxGeometry(10, 40, 5);
const gate = new THREE.Mesh(gateGeometry, gateMaterial);
gate.position.set(i, 70, 20);
gate.castShadow = true;
damGroup.add(gate);
// 闸门支撑结构
const supportGeometry = new THREE.BoxGeometry(5, 50, 5);
const support = new THREE.Mesh(supportGeometry, damMaterial);
support.position.set(i, 60, 15);
support.castShadow = true;
damGroup.add(support);
}
// 发电站
const powerStationGeometry = new THREE.BoxGeometry(100, 60, 80);
const powerStation = new THREE.Mesh(powerStationGeometry, new THREE.MeshStandardMaterial({
color: 0xdddddd,
roughness: 0.6
}));
powerStation.position.set(0, 30, -60);
powerStation.castShadow = true;
powerStation.receiveShadow = true;
damGroup.add(powerStation);
// 发电站屋顶
const roofGeometry = new THREE.ConeGeometry(60, 20, 4);
const roof = new THREE.Mesh(roofGeometry, new THREE.MeshStandardMaterial({
color: 0xaa2222,
roughness: 0.8
}));
roof.position.set(0, 65, -60);
roof.rotation.y = Math.PI / 4;
roof.castShadow = true;
damGroup.add(roof);
scene.add(damGroup);
// 创建瀑布
createWaterfall();
}
// 创建水体
function createWater() {
const waterGeometry = new THREE.PlaneGeometry(1000, 500, 50, 50);
// 自定义水材质
const waterMaterial = new THREE.MeshStandardMaterial({
color: 0x0077be,
transparent: true,
opacity: 0.9,
roughness: 0.1,
metalness: 0.9
});
water = new THREE.Mesh(waterGeometry, waterMaterial);
water.rotation.x = -Math.PI / 2;
water.position.set(0, waterLevel, -250);
water.receiveShadow = true;
scene.add(water);
// 河流
const riverGeometry = new THREE.PlaneGeometry(150, 1000, 20, 100);
const riverMaterial = new THREE.MeshStandardMaterial({
color: 0x0066aa,
transparent: true,
opacity: 0.9,
roughness: 0.1,
metalness: 0.8
});
river = new THREE.Mesh(riverGeometry, riverMaterial);
river.rotation.x = -Math.PI / 2;
river.position.set(0, 0, 250);
river.receiveShadow = true;
scene.add(river);
}
// 创建瀑布
function createWaterfall() {
// 瀑布粒子系统
const particleCount = 2000;
const particles = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount * 3; i += 3) {
positions[i] = (Math.random() - 0.5) * 60; // x
positions[i + 1] = Math.random() * 40 + 60; // y
positions[i + 2] = Math.random() * 5 + 20; // z
}
particles.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const particleMaterial = new THREE.PointsMaterial({
color: 0x88ccff,
size: 2,
transparent: true,
opacity: 0.7
});
waterfall = new THREE.Points(particles, particleMaterial);
scene.add(waterfall);
}
// 创建环境
function createEnvironment() {
// 添加山脉
for (let i = -1; i < 2; i += 2) {
const mountainGeometry = new THREE.ConeGeometry(300, 200, 4);
const mountainMaterial = new THREE.MeshStandardMaterial({
color: 0x556b2f,
roughness: 0.9
});
const mountain = new THREE.Mesh(mountainGeometry, mountainMaterial);
mountain.position.set(i * 600, 0, -300);
mountain.rotation.y = i * Math.PI / 4;
mountain.castShadow = true;
mountain.receiveShadow = true;
scene.add(mountain);
}
// 添加树木
const treeMaterial = new THREE.MeshStandardMaterial({ color: 0x2e8b57 });
const trunkMaterial = new THREE.MeshStandardMaterial({ color: 0x8b4513 });
for (let i = 0; i < 50; i++) {
const x = (Math.random() - 0.5) * 1800;
const z = (Math.random() - 0.5) * 1800 + 400;
const y = -5;
if (Math.abs(x) < 400 && Math.abs(z) < 400) continue;
// 树干
const trunkGeometry = new THREE.CylinderGeometry(2, 3, 10, 8);
const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
trunk.position.set(x, y, z);
trunk.castShadow = true;
trunk.receiveShadow = true;
scene.add(trunk);
// 树冠
const treeGeometry = new THREE.ConeGeometry(8, 20, 6);
const tree = new THREE.Mesh(treeGeometry, treeMaterial);
tree.position.set(x, y + 15, z);
tree.castShadow = true;
scene.add(tree);
}
// 添加道路
const roadGeometry = new THREE.PlaneGeometry(100, 1000);
const roadMaterial = new THREE.MeshStandardMaterial({
color: 0x444444,
roughness: 0.9
});
const road = new THREE.Mesh(roadGeometry, roadMaterial);
road.rotation.x = -Math.PI / 2;
road.position.set(-200, 0.1, 0);
road.receiveShadow = true;
scene.add(road);
}
// 设置事件监听器
function setupEventListeners() {
// 键盘控制
document.addEventListener('keydown', onKeyDown);
document.addEventListener('keyup', onKeyUp);
// 窗口大小调整
window.addEventListener('resize', onWindowResize);
// 控制按钮
document.getElementById('normal-waves').addEventListener('click', () => setWaterMode('normal'));
document.getElementById('storm-waves').addEventListener('click', () => setWaterMode('storm'));
document.getElementById('calm-water').addEventListener('click', () => setWaterMode('calm'));
document.getElementById('toggle-flow').addEventListener('click', toggleWaterFlow);
document.getElementById('toggle-time').addEventListener('click', toggleTime);
}
// 键盘按下事件
function onKeyDown(event) {
switch (event.key.toLowerCase()) {
case 'w': moveForward = true; break;
case 's': moveBackward = true; break;
case 'a': moveLeft = true; break;
case 'd': moveRight = true; break;
case 'q': moveUp = true; break;
case 'e': moveDown = true; break;
case 'r': resetView(); break;
case 'f': toggleFlyMode(); break;
}
}
// 键盘释放事件
function onKeyUp(event) {
switch (event.key.toLowerCase()) {
case 'w': moveForward = false; break;
case 's': moveBackward = false; break;
case 'a': moveLeft = false; break;
case 'd': moveRight = false; break;
case 'q': moveUp = false; break;
case 'e': moveDown = false; break;
}
}
// 重置视角
function resetView() {
camera.position.set(0, 100, 200);
camera.lookAt(0, 0, 0);
flyMode = false;
document.getElementById('mode').textContent = '行走模式';
}
// 切换飞行模式
function toggleFlyMode() {
flyMode = !flyMode;
document.getElementById('mode').textContent = flyMode ? '飞行模式' : '行走模式';
controls.enableRotate = flyMode;
}
// 设置水体模式
function setWaterMode(mode) {
water.material.roughness =
mode === 'calm' ? 0.1 :
mode === 'storm' ? 0.5 : 0.3;
}
// 切换水流
function toggleWaterFlow() {
waterFlow = !waterFlow;
}
// 切换时间
function toggleTime() {
isDay = !isDay;
document.getElementById('time').textContent = isDay ? '白天' : '夜晚';
// 更新光照
directionalLight.intensity = isDay ? 0.8 : 0.3;
scene.background = new THREE.Color(isDay ? 0x87CEEB : 0x0a1122);
ambientLight.intensity = isDay ? 0.4 : 0.1;
hemisphereLight.intensity = isDay ? 0.6 : 0.2;
}
// 窗口大小调整
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// 动画循环
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
// 更新控制
controls.update();
// 处理键盘移动
velocity.x = 0;
velocity.z = 0;
velocity.y = 0;
const speed = flyMode ? 100 : 50;
if (moveForward) velocity.z -= speed * delta;
if (moveBackward) velocity.z += speed * delta;
if (moveLeft) velocity.x -= speed * delta;
if (moveRight) velocity.x += speed * delta;
if (moveUp) velocity.y += speed * delta;
if (moveDown) velocity.y -= speed * delta;
camera.translateX(velocity.x);
camera.translateZ(velocity.z);
camera.position.y += velocity.y;
// 限制高度(非飞行模式)
if (!flyMode && camera.position.y < 10) {
camera.position.y = 10;
}
// 更新水体效果
updateWaterEffect(delta);
// 更新瀑布
updateWaterfall(delta);
// 更新状态显示
updateStatus();
// 渲染场景
renderer.render(scene, camera);
}
// 更新水体效果
function updateWaterEffect(delta) {
if (!waterFlow) return;
// 获取顶点位置
const positions = water.geometry.attributes.position.array;
const count = positions.length / 3;
// 更新时间
const time = Date.now() * 0.001;
// 创建波浪效果
for (let i = 0; i < count; i++) {
const idx = i * 3;
const x = positions[idx];
const z = positions[idx + 2];
// 创建波浪运动
const waveHeight = Math.sin(x * 0.05 + time * 2) * 0.5 +
Math.cos(z * 0.03 + time * 1.5) * 0.7;
positions[idx + 1] = waveHeight;
}
water.geometry.attributes.position.needsUpdate = true;
water.geometry.computeVertexNormals();
}
// 更新瀑布
function updateWaterfall(delta) {
if (!waterFlow) return;
const positions = waterfall.geometry.attributes.position.array;
const count = positions.length / 3;
const time = Date.now() * 0.001;
for (let i = 0; i < count; i++) {
const idx = i * 3;
// 粒子下落
positions[idx + 1] -= 40 * delta;
// 如果粒子落到底部,重置位置
if (positions[idx + 1] < 0) {
positions[idx] = (Math.random() - 0.5) * 60;
positions[idx + 1] = Math.random() * 10 + 80;
positions[idx + 2] = Math.random() * 5 + 20;
}
// 添加一些随机运动
positions[idx] += (Math.random() - 0.5) * 0.5;
positions[idx + 2] += (Math.random() - 0.5) * 0.3;
}
waterfall.geometry.attributes.position.needsUpdate = true;
}
// 更新状态显示
function updateStatus() {
const pos = camera.position;
document.getElementById('position').textContent =
`${Math.round(pos.x)}, ${Math.round(pos.y)}, ${Math.round(pos.z)}`;
}
// 初始化应用
init();
</script>
</body>
</html>