You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1216 lines
46 KiB
JavaScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 全局变量
let scene, camera, renderer, controls;
let water, sun, sky;
let windTurbineModels = []; // 改为数组存储多个风电机组
let clock = new THREE.Clock();
// 在全局变量部分添加
let pointerLockControls;
let moveForward = false;
let moveBackward = false;
let moveLeft = false;
let moveRight = false;
let moveUp = false;
let moveDown = false;
let velocity = new THREE.Vector3();
let direction = new THREE.Vector3();
let prevTime = performance.now();
// 在全局变量部分添加
let autoTourActive = false;
let tourStartTime = 0;
let currentTurbineIndex = 0;
let tourPhase = 0; // 0: 全景展示, 1: 拉近观察, 2: 环绕观察, 3: 拉远离开
let phaseStartTime = 0;
let tourMode = 'cinematic'; // 电影级漫游模式
// 初始化Three.js场景
function init() {
console.log('开始初始化Three.js场景');
// 创建场景
scene = new THREE.Scene();
//scene.fog = new THREE.Fog(0x006994, 1, 1000);
console.log('场景创建完成');
//天空盒
scene.background = new THREE.CubeTextureLoader()
.setPath('./assets/sky/')
.load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
//环境光
const ambientLight = new THREE.AmbientLight(0x666666, 20); // 柔和的白光
scene.add(ambientLight);
// 创建相机
// 增大远裁剪面距离,确保远处的缩小模型也能显示
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 5000);
// 向右旋转270度的相机位置设置根据你的代码
const cameraRotationAngle = THREE.MathUtils.degToRad(0);
const cameraRadius = 800; // 相机距离原点的距离
const cameraHeight = 50; // 降低相机高度,使其更接近水面
// 计算旋转后的位置(修复变量名错误)
const cameraX = cameraRadius * Math.sin(cameraRotationAngle);
const cameraZ = cameraRadius * Math.cos(cameraRotationAngle); // 修复使用cameraRotationAngle而不是cameraAngle
camera.position.set(cameraX, cameraHeight, cameraZ);
camera.lookAt(0, 0, 0); // 确保相机朝向场景中心
console.log('相机创建完成,位置:', camera.position.x, camera.position.y, camera.position.z);
//创建渲染器
renderer = new THREE.WebGLRenderer({
logarithmicDepthBuffer: true,//设置对数深度缓冲区,优化深度冲突问题
preserveDrawingBuffer: true,//把canvas画布上内容下载到本地,保存图片
antialias: true, // 正确开启抗锯齿
stencil: true,//开启模版缓存区,用于高级渲染技术,如阴影体积、遮挡剔除等
powerPreference: 'high-performance',//指定渲染器使用的 GPU 功率模式。默认 "default"
physicalCorrectLights: true,//是否使用物理上正确的照明模式;默认为 false
localClippingEnabled: true,//启用局部裁剪;默认为 false
alpha: true,//背景透明
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setClearColor(0x87CEEB, 1);
// 确保canvas添加到正确的容器
const container = document.getElementById('container');
if (container) {
container.appendChild(renderer.domElement);
console.log('渲染器canvas已添加到container');
} else {
document.body.appendChild(renderer.domElement);
console.log('渲染器canvas已添加到body');
}
// 设置canvas样式确保可见
renderer.domElement.style.display = 'block';
renderer.domElement.style.position = 'absolute';
renderer.domElement.style.top = '0';
renderer.domElement.style.left = '0';
renderer.domElement.style.zIndex = '1';
console.log('渲染器创建完成canvas尺寸:', renderer.domElement.width, 'x', renderer.domElement.height);
// 初始化指针锁定控制器
if (typeof THREE.PointerLockControls !== 'undefined') {
pointerLockControls = new THREE.PointerLockControls(camera, document.body);
// 添加点击进入指针锁定的事件
document.addEventListener('click', function () {
if (pointerLockControls) {
pointerLockControls.lock();
}
});
// 添加键盘控制事件
document.addEventListener('keydown', onKeyDown, false);
document.addEventListener('keyup', onKeyUp, false);
console.log('指针锁定控制器创建完成');
} else {
console.warn('PointerLockControls未加载使用OrbitControls');
// 保留原有的OrbitControls作为备选
initOrbitControls();
}
// 添加相机控制 (OrbitControls 作为备选)
function initOrbitControls() {
if (typeof THREE.OrbitControls !== 'undefined') {
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 10; // 减小最小距离
controls.maxDistance = 3000; // 最大距离
controls.maxPolarAngle = Math.PI / 2 - 0.1; // 限制垂直旋转角度,防止看到水面以下
controls.target.set(0, 0, 0);
controls.addEventListener('change', function () {
// 限制相机高度,防止进入水面以下
if (camera.position.y < 5) {
camera.position.y = 5;
}
});
console.log('相机控制器创建完成');
} else {
console.warn('OrbitControls未加载跳过相机控制器');
}
}
// const ambientLight = new THREE.AmbientLight(0x404040); // 环境光
// ambientLight.intensity = 0.5; // 光照强度
// scene.add(ambientLight);
addLights();
// loadHDRTexture()
// // 创建场景元素
// createSkybox(); // 创建天空盒
createOcean(); // 创建海洋
// 加载海上风电机组模型
loadWindTurbineField();
// 监听窗口大小变化
window.addEventListener('resize', onWindowResize, false);
console.log('Three.js场景初始化完成');
// 设置风机点击事件
setupTurbineClickEvents();
// 添加鼠标事件监听
document.addEventListener('mousedown', onDocumentMouseDown, false);
}
// 加载HDR环境纹理
function loadHDRTexture() {
// 检查RGBELoader是否可用
if (typeof THREE.RGBELoader === 'undefined') {
console.warn('RGBELoader未加载跳过HDR纹理加载');
return;
}
const hdrLoader = new THREE.RGBELoader();
hdrLoader.load("./textures/023.hdr", (texture) => {
// 设置背景和环境纹理
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.background = texture;
scene.environment = texture;
console.log('HDR纹理加载完成');
}, undefined, (error) => {
console.error('HDR纹理加载失败:', error);
});
}
// 修改后的 createSkybox 函数:// 创建天空盒
function createSkybox() {
const skyGeometry = new THREE.SphereGeometry(1800, 24, 18); // 增大天空盒半径
const textureLoader = new THREE.TextureLoader().load('./textures/sky.jpg');
textureLoader.colorSpace = THREE.SRGBColorSpace;
// 创建天空材质(通常使用全景纹理)
const skyMaterial = new THREE.MeshBasicMaterial({
map: textureLoader,
side: THREE.BackSide // 重要:让材质渲染在球体内侧
});
// 创建天空网格
const skySphere = new THREE.Mesh(skyGeometry, skyMaterial);
scene.add(skySphere);
}
// 创建天空效果
function createSky() {
// 注释掉Sky.js的高级天空效果
sky = new THREE.Sky();
sky.scale.setScalar(450000);
scene.add(sky);
// 创建太阳
sun = new THREE.Vector3();
// 配置天空参数(正午阳光设置)
const skyUniforms = sky.material.uniforms;
skyUniforms['turbidity'].value = 2; // 降低湍流度,营造清澈的正午天空
skyUniforms['rayleigh'].value = 3; // 增强瑞利散射,让天空更蓝
skyUniforms['mieCoefficient'].value = 0.005;
skyUniforms['mieDirectionalG'].value = 0.8;
// 设置太阳位置(正午阳光效果)
const phi = THREE.MathUtils.degToRad(90 - 85); // 仰角 - 太阳接近天顶
const theta = THREE.MathUtils.degToRad(0); // 方位角 - 正南方向
sun.setFromSphericalCoords(1, phi, theta);
skyUniforms['sunPosition'].value.copy(sun);
// 使用sky.jpg作为天空盒纹理
const textureLoader = new THREE.TextureLoader();
textureLoader.load('./textures/sky.jpg', function (texture) {
// 创建天空盒几何体
const skyBoxGeometry = new THREE.SphereGeometry(2000, 32, 32);
const skyBoxMaterial = new THREE.MeshBasicMaterial({
map: texture,
side: THREE.BackSide
});
const skyBox = new THREE.Mesh(skyBoxGeometry, skyBoxMaterial);
scene.add(skyBox);
console.log('Sky texture loaded as skybox');
}, undefined, function (error) {
console.error('Sky texture loading failed:', error);
// 创建备用天空颜色
const skyGeometry = new THREE.SphereGeometry(500, 32, 32);
const skyMaterial = new THREE.MeshBasicMaterial({
color: 0x87CEEB,
side: THREE.BackSide
});
const sky = new THREE.Mesh(skyGeometry, skyMaterial);
scene.add(sky);
console.log('Fallback sky color created');
});
console.log('Sky created using sky.jpg texture');
}
// 创建海洋效果
function createOcean() {
// 加载水面法线纹理
const textureLoader = new THREE.TextureLoader();
const waterNormals = textureLoader.load('./textures/waternormals.jpg', function (texture) {
// 设置法线纹理的重复模式,增强水面效果
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(2, 2); // 调整重复次数以控制波浪密度
console.log('水面法线纹理加载成功');
}, undefined, function (error) {
console.error('水面法线纹理加载失败:', error);
});
// 设置法线纹理的包装模式(即使纹理加载失败也要设置默认值)
waterNormals.wrapS = waterNormals.wrapT = THREE.RepeatWrapping;
waterNormals.repeat.set(2, 2); // 增加重复次数以获得更细腻的波浪效果
// 创建水面几何体,增大尺寸以匹配天空盒尺寸
const waterGeometry = new THREE.PlaneGeometry(10600, 10600); // 比天空盒稍大以确保完全覆盖
// 使用Water.js创建高级水面效果
// 其他碧绿色选项:
// waterColor: 0x0077be, // 经典碧绿色
// waterColor: 0x00a86b, // 绿松石色
// waterColor: 0x2a52be, // 地中海蓝
// waterColor: 0x008080, // 水鸭色
// waterColor: 0x40e0d0, // 青绿色
water = new THREE.Water(waterGeometry, {
textureWidth: 512,
textureHeight: 512,
waterNormals: waterNormals,
sunDirection: new THREE.Vector3(0.70707, 0.70707, 0.0),
sunColor: 0xffffff,
waterColor: 0x0077be,//0x001e0f 0x40e0d0
distortionScale: 3.7, // 控制波浪的扭曲程度
fog: scene.fog !== undefined,
alpha: 1.0, // 水面透明度
foam: 0.1 // 泡沫效果
});
water.rotation.x = -Math.PI / 2;
water.position.y = 0; // 确保水面在y=0位置
scene.add(water);
// 将water赋值给全局ocean变量以保持兼容性
ocean = water;
console.log('高级水面效果创建完成,使用法线纹理增强波浪凹凸效果');
}
// 修改 addLights 函数,增强光照效果// 修改 addLights 函数,进一步增强光照效果
function addLights() {
// 大幅增强环境光强度
const ambientLight = new THREE.AmbientLight(0xffffff, 1.2); // 使用白色光,强度更高
scene.add(ambientLight);
// 主方向光(太阳光)- 进一步调整位置和强度
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5); // 增加强度到1.5
directionalLight.position.set(100, 150, 100); // 调整到更合理的位置
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 2000; // 增加远裁剪面
directionalLight.shadow.camera.left = -800;
directionalLight.shadow.camera.right = 800;
directionalLight.shadow.camera.top = 800;
directionalLight.shadow.camera.bottom = -800;
scene.add(directionalLight);
// 添加更强的补光 - 减少阴影
const fillLight = new THREE.DirectionalLight(0xffffff, 0.8); // 增加补光强度到0.8
fillLight.position.set(-100, 100, -100);
scene.add(fillLight);
// 添加天光 - 模拟天空反射
const hemiLight = new THREE.HemisphereLight(0x80deea, 0x4caf50, 0.5); // 增加强度到0.5
hemiLight.position.set(0, 200, 0);
scene.add(hemiLight);
// 添加背面光,确保模型背面也有光照
const backLight = new THREE.DirectionalLight(0xffffff, 0.6);
backLight.position.set(0, 50, -200);
scene.add(backLight);
}
// 加载海上风电场20个风电机组
function loadWindTurbineField() {
console.log('开始加载海上风电场');
const loader = new THREE.GLTFLoader();
// 定义风电场布局 - 4行5列的排列增加两排
const positions = [
// 第一排
// { x: -400, z: -600 },
// { x: -200, z: -600 },
// { x: 0, z: -600 },
// { x: 200, z: -600 },
// { x: 400, z: -600 },
// // 第二排
// { x: -400, z: -400 },
// { x: -200, z: -400 },
// { x: 0, z: -400 },
// { x: 200, z: -400 },
// { x: 400, z: -400 },
// 第三排
{ x: -400, z: -200 },
{ x: -200, z: -200 },
{ x: 0, z: -200 },
{ x: 200, z: -200 },
{ x: 400, z: -200 },
// 第四排
{ x: -400, z: 200 },
{ x: -200, z: 200 },
{ x: 0, z: 200 },
{ x: 200, z: 200 },
{ x: 400, z: 200 }
];
// 旋转角度右转50度
const rotationAngle = THREE.MathUtils.degToRad(50); // 转换为弧度
const cosAngle = Math.cos(rotationAngle);
const sinAngle = Math.sin(rotationAngle);
let loadedCount = 0;
const totalCount = positions.length;
positions.forEach((position, index) => {
// 应用旋转变换到每个风电机组的位置
const rotatedX = position.x * cosAngle - position.z * sinAngle;
const rotatedZ = position.x * sinAngle + position.z * cosAngle;
loader.load(
'./models/风电组.glb',
function (gltf) {
console.log(`风电组.glb模型 ${index + 1} 加载成功`);
const model = gltf.scene.clone(); // 克隆模型以创建多个实例
// 调整模型缩放和位置(使用旋转后的位置)
model.scale.set(0.8, 0.8, 0.8);
// 将风机位置设置在水面上y=0
model.position.set(rotatedX, -5, rotatedZ);
// 设置模型的旋转角度(使风机面向正确的方向)
model.rotation.y = -rotationAngle; // 负号是因为我们希望风机正面朝向旋转方向
// 调整模型材质
// 调整模型材质,使其更亮
model.traverse(function (child) {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
child.frustumCulled = false;
// 如果材质存在,调整其属性
if (child.material) {
// 添加更强的自发光效果
child.material.emissive = new THREE.Color(0x444444); // 增加自发光强度
// 调整材质颜色,使其更亮
if (child.material.color) {
// 可以稍微提亮原始颜色
child.material.color.multiplyScalar(1.2);
}
// 确保材质响应光照
if (child.material.type === 'MeshBasicMaterial') {
// 如果是基础材质,替换为标准材质
child.material = new THREE.MeshStandardMaterial({
color: child.material.color || 0xffffff,
map: child.material.map,
metalness: 0.1,
roughness: 0.6,
emissive: new THREE.Color(0x333333), // 添加自发光
emissiveIntensity: 0.3
});
} else if (child.material.type === 'MeshStandardMaterial') {
// 调整标准材质参数使其更亮
child.material.metalness = Math.max(0.05, child.material.metalness || 0.1);
child.material.roughness = Math.min(0.8, child.material.roughness || 0.6);
child.material.emissive = new THREE.Color(0x333333);
child.material.emissiveIntensity = 0.3; // 添加自发光强度
} else if (child.material.type === 'MeshLambertMaterial') {
// 对于Lambert材质也添加自发光
child.material.emissive = new THREE.Color(0x333333);
}
}
}
});
// 添加到场景和数组
scene.add(model);
windTurbineModels.push(model);
// 为新添加的模型设置 userData
const turbineName = `GT${windTurbineModels.length.toString().padStart(3, '0')}`;
model.userData = { turbineName: turbineName };
model.traverse((child) => {
if (child.isMesh) {
child.userData = { turbineName: turbineName };
}
});
loadedCount++;
console.log(`风电场进度: ${loadedCount}/${totalCount} 个风电机组已加载`);
if (loadedCount === totalCount) {
console.log('海上风电场加载完成!');
}
},
function (progress) {
if (progress.total > 0) {
const percent = (progress.loaded / progress.total * 100).toFixed(1);
console.log(`风电机组 ${index + 1} 加载进度: ${percent}%`);
}
},
function (error) {
console.error(`风电机组 ${index + 1} 加载失败:`, error);
// 如果单个模型加载失败,创建备用模型(同样应用旋转变换)
createFallbackWindTurbine(rotatedX, rotatedZ, index);
loadedCount++;
}
);
});
}
// 修改备用风机创建函数,也应用旋转变换
function createFallbackWindTurbine(x = 0, z = 0, index = 0) {
console.log(`创建备用风电机组模型 ${index + 1}`);
// 创建风电机组主体(塔筒)
const towerGeometry = new THREE.CylinderGeometry(1.6, 2.4, 24, 8); // 缩小尺寸
const towerMaterial = new THREE.MeshLambertMaterial({ color: 0xcccccc });
const tower = new THREE.Mesh(towerGeometry, towerMaterial);
tower.position.set(0, 12, 0);
tower.castShadow = true;
tower.receiveShadow = true;
tower.frustumCulled = false; // 禁用视锥体剔除
// 创建机舱
const nacelleGeometry = new THREE.BoxGeometry(6.4, 3.2, 3.2); // 缩小尺寸
const nacelleMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
const nacelle = new THREE.Mesh(nacelleGeometry, nacelleMaterial);
nacelle.position.set(0, 24, 0);
nacelle.castShadow = true;
nacelle.frustumCulled = false; // 禁用视锥体剔除
// 创建叶片轮毂组(用于旋转)
const rotorGroup = new THREE.Group();
// 创建叶片轮毂
const hubGeometry = new THREE.SphereGeometry(1.2, 8, 8); // 缩小尺寸
const hubMaterial = new THREE.MeshLambertMaterial({ color: 0x666666 });
const hub = new THREE.Mesh(hubGeometry, hubMaterial);
hub.position.set(0, 0, 0); // 相对于rotorGroup的位置
hub.castShadow = true;
hub.frustumCulled = false; // 禁用视锥体剔除
// 创建叶片
const bladeGeometry = new THREE.BoxGeometry(0.4, 12, 0.8); // 缩小尺寸
const bladeMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
const blade1 = new THREE.Mesh(bladeGeometry, bladeMaterial);
blade1.position.set(0, 6, 0); // 相对于rotorGroup的位置
blade1.castShadow = true;
blade1.frustumCulled = false; // 禁用视锥体剔除
const blade2 = new THREE.Mesh(bladeGeometry, bladeMaterial);
blade2.position.set(0, 0, 6);
blade2.rotation.z = Math.PI * 2 / 3;
blade2.castShadow = true;
blade2.frustumCulled = false; // 禁用视锥体剔除
const blade3 = new THREE.Mesh(bladeGeometry, bladeMaterial);
blade3.position.set(0, 0, -6);
blade3.rotation.z = Math.PI * 4 / 3;
blade3.castShadow = true;
blade3.frustumCulled = false; // 禁用视锥体剔除
// 将轮毂和叶片添加到旋转组
rotorGroup.add(hub);
rotorGroup.add(blade1);
rotorGroup.add(blade2);
rotorGroup.add(blade3);
// 设置旋转组的位置
rotorGroup.position.set(3.2, 24, 0);
// 给旋转组添加一个标识,方便动画识别
rotorGroup.name = 'rotor_group';
// 组合风电机组
const windTurbine = new THREE.Group();
windTurbine.add(tower);
windTurbine.add(nacelle);
windTurbine.add(rotorGroup);
// 将风机放置在水面上y=0
windTurbine.position.set(x, -5, z);
windTurbine.scale.set(0.8, 0.8, 0.8); // 整体缩放
// 应用旋转(与主风电场一致)
const rotationAngle = THREE.MathUtils.degToRad(50);
windTurbine.rotation.y = -rotationAngle;
// 禁用整个组的视锥体剔除
windTurbine.traverse(function (child) {
child.frustumCulled = false;
});
scene.add(windTurbine);
windTurbineModels.push(windTurbine);
// 为备用风机模型设置 userData
const turbineName = `GT${windTurbineModels.length.toString().padStart(3, '0')}`;
windTurbine.userData = { turbineName: turbineName };
windTurbine.traverse((child) => {
if (child.isMesh) {
child.userData = { turbineName: turbineName };
}
});
console.log(`备用风电机组模型 ${index + 1} 创建完成`);
}
// 动画循环
function animate() {
requestAnimationFrame(animate);
const time = clock.getElapsedTime();
// 更新自动漫游(如果激活)
updateAutoTour();
// 原有的键盘控制逻辑(当自动漫游未激活时)
if (!autoTourActive) {
const delta = Math.min(0.1, (time - prevTime));
// 如果使用指针锁定控制器且已锁定
if (pointerLockControls && pointerLockControls.isLocked) {
// 计算移动方向
direction.z = Number(moveForward) - Number(moveBackward);
direction.x = Number(moveRight) - Number(moveLeft);
direction.y = Number(moveUp) - Number(moveDown);
direction.normalize();
// 设置移动速度
const speed = 100.0;
// 根据方向和速度更新速度向量
if (moveForward || moveBackward) velocity.z -= direction.z * speed * delta;
if (moveLeft || moveRight) velocity.x -= direction.x * speed * delta;
if (moveUp || moveDown) velocity.y -= direction.y * speed * delta;
// 应用阻尼(摩擦力)
velocity.x -= velocity.x * 10.0 * delta;
velocity.y -= velocity.y * 10.0 * delta;
velocity.z -= velocity.z * 10.0 * delta;
// 移动相机
pointerLockControls.moveRight(-velocity.x * delta);
pointerLockControls.moveForward(-velocity.z * delta);
// 垂直移动
camera.position.y -= velocity.y * delta;
// 简单的高度限制
if (camera.position.y < 5) {
camera.position.y = 5;
velocity.y = 0;
}
if (camera.position.y > 500) {
camera.position.y = 500;
velocity.y = 0;
}
}
// 如果使用OrbitControls
else if (controls) {
controls.update();
}
prevTime = time;
}
// 在前几秒强制更新材质(解决材质加载延迟问题)
if (time < 3 && windTurbineModels.length > 0) {
windTurbineModels.forEach(model => {
model.traverse(function (child) {
if (child.isMesh && child.material) {
if (child.material.needsUpdate !== undefined) {
child.material.needsUpdate = true;
}
// 持续调整材质亮度
if (child.material.emissive) {
child.material.emissiveIntensity = 0.3;
}
}
});
});
}
// 更新水面动画 - 增强水面波动效果
if (water && water.material.uniforms) {
// 通过改变time值来控制水面波动动画
water.material.uniforms['time'].value += 1.0 / 60.0;
// 可选:增加波动速度和强度
// water.material.uniforms['time'].value += 1.0 / 30.0;
}
// 风电机组动画
if (windTurbineModels.length > 0) {
windTurbineModels.forEach((model, index) => {
// 每个风电机组有轻微的相位差,创造更自然的效果
const phaseOffset = index * 0.2;
// model.position.y = -3 + Math.sin(time * 0.5 + phaseOffset) * 1.5;
// 叶片旋转动画
// 如果还没有缓存旋转中心和初始偏移,先进行初始化
if (!model.userData.rotorInitialized) {
let rotorCenter = null;
// 找到186部件作为旋转中心
model.traverse((child) => {
if (child.name && child.name.toLowerCase().includes('186')) {
rotorCenter = child;
}
});
// 缓存旋转中心和叶片的初始偏移
if (rotorCenter) {
model.userData.rotorCenter = rotorCenter;
model.userData.rotorCenterPosition = {
x: rotorCenter.position.x,
y: rotorCenter.position.y,
z: rotorCenter.position.z
};
// 为每个叶片计算并缓存初始偏移
model.traverse((child) => {
if (child.name && (child.name.toLowerCase().includes('200') ||
child.name.toLowerCase().includes('214') ||
child.name.toLowerCase().includes('228') ||
child.name.toLowerCase().includes('风轮'))) {
child.userData.initialOffset = {
x: child.position.x - rotorCenter.position.x,
y: child.position.y - rotorCenter.position.y,
z: child.position.z - rotorCenter.position.z
};
}
});
}
model.userData.rotorInitialized = true;
}
// 执行旋转动画
model.traverse((child) => {
// 如果是备用模型的旋转组
if (child.name === 'rotor_group') {
child.rotation.x = time * 2.0; // 绕X轴旋转
}
// 如果是GLB模型的叶片部件
else if (child.name && (child.name.toLowerCase().includes('200') ||
child.name.toLowerCase().includes('214') ||
child.name.toLowerCase().includes('228') ||
child.name.toLowerCase().includes('风轮'))) {
if (model.userData.rotorCenter && child.userData.initialOffset) {
// 使用缓存的旋转中心位置(固定不变)
const centerPos = model.userData.rotorCenterPosition;
// 输出第一个风电机组的centerPos到控制台
// if (index === 0 && child.name.toLowerCase().includes('200')) {
// console.log('第一个风电机组的centerPos:', centerPos);
// }
const offset = child.userData.initialOffset;
// 围绕固定的旋转中心做圆周运动
const angle = time * 2.0;
const cosAngle = Math.cos(angle);
const sinAngle = Math.sin(angle);
child.position.x = centerPos.x + (offset.x * cosAngle - offset.y * sinAngle);
child.position.y = centerPos.y + (offset.x * sinAngle + offset.y * cosAngle) + 4;
child.position.z = centerPos.z + offset.z - 4;
// 同时让叶片自身也旋转
child.rotation.z = angle;
} else {
// 如果没有找到186部件使用原来的旋转方式
child.rotation.z = time * 2.0;
}
}
});
});
}
// 渲染场景
if (scene && camera && renderer) {
renderer.render(scene, camera);
// 每隔一段时间输出渲染信息
if (Math.floor(time) % 5 === 0 && time % 1 < 0.1) {
console.log('正在渲染场景,时间:', Math.floor(time), '场景对象数量:', scene.children.length);
}
} else {
console.warn('场景、相机或渲染器未初始化');
}
}
// 窗口大小变化处理
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// 页面加载完成后初始化
window.addEventListener('DOMContentLoaded', function () {
console.log('DOM加载完成开始初始化Three.js场景');
// 检查Three.js是否加载
if (typeof THREE === 'undefined') {
console.error('Three.js未加载');
document.getElementById('loading').innerHTML = '错误Three.js库未加载';
return;
}
console.log('Three.js版本:', THREE.REVISION);
// 隐藏加载提示
const loadingElement = document.getElementById('loading');
if (loadingElement) {
loadingElement.style.display = 'none';
}
init();
animate();
});
// 添加切换自动漫游的函数
function toggleAutoTour() {
autoTourActive = !autoTourActive;
if (autoTourActive) {
tourStartTime = clock.getElapsedTime();
phaseStartTime = tourStartTime;
currentTurbineIndex = 0;
tourPhase = 0;
console.log('自动漫游已启动');
} else {
console.log('自动漫游已停止');
}
// 更新按钮文本
const button = document.getElementById('tourButton');
if (button) {
button.textContent = autoTourActive ? '停止漫游' : '开始漫游';
}
return autoTourActive;
}
// 获取当前风机位置
function getCurrentTurbinePosition() {
if (windTurbineModels.length > 0) {
// 如果有多个风机,可以循环访问
const turbine = windTurbineModels[currentTurbineIndex % windTurbineModels.length];
return turbine.position.clone();
}
// 默认位置
return new THREE.Vector3(0, 0, 0);
}
// 计算风机群的中心位置和边界
function getWindFarmBounds() {
if (windTurbineModels.length === 0) {
return { center: new THREE.Vector3(0, 0, 0), radius: 100 };
}
const positions = windTurbineModels.map(model => model.position);
let minX = Infinity, maxX = -Infinity;
let minZ = Infinity, maxZ = -Infinity;
positions.forEach(pos => {
minX = Math.min(minX, pos.x);
maxX = Math.max(maxX, pos.x);
minZ = Math.min(minZ, pos.z);
maxZ = Math.max(maxZ, pos.z);
});
const center = new THREE.Vector3(
(minX + maxX) / 2,
0,
(minZ + maxZ) / 2
);
const radius = Math.max(
Math.abs(maxX - minX) / 2,
Math.abs(maxZ - minZ) / 2
) * 1.5;
return { center, radius };
}
// 电影级自动漫游逻辑
function updateCinematicTour() {
if (!autoTourActive || windTurbineModels.length === 0) return;
const elapsedTime = clock.getElapsedTime() - tourStartTime;
const phaseElapsed = clock.getElapsedTime() - phaseStartTime;
// 每个阶段持续时间(秒)
const phaseDuration = 10; // 增加到10秒给更多时间展示
// 检查是否需要切换到下一个阶段
if (phaseElapsed > phaseDuration) {
tourPhase = (tourPhase + 1) % 4;
phaseStartTime = clock.getElapsedTime();
console.log(`切换到阶段 ${tourPhase}`);
// 如果完成一轮,切换到下一个风机
if (tourPhase === 0 && windTurbineModels.length > 1) {
currentTurbineIndex = (currentTurbineIndex + 1) % windTurbineModels.length;
console.log(`切换到风机 ${currentTurbineIndex + 1}`);
}
}
const currentPhaseElapsed = clock.getElapsedTime() - phaseStartTime;
const phaseProgress = Math.min(currentPhaseElapsed / phaseDuration, 1);
// 获取当前风机位置
const turbinePosition = getCurrentTurbinePosition();
// 根据阶段设置相机位置和目标
let cameraPosition, targetPosition;
switch (tourPhase) {
case 0: // 全景展示阶段 - 从高空俯视整个风机群,然后慢慢下降推进
const bounds = getWindFarmBounds();
// 初始位置在高空俯视整个风机群
const initialHeight = bounds.radius;
const finalHeight = bounds.radius * 0.3;
const overviewDistance = bounds.radius * 0.8;
// 相机从高空慢慢下降
const currentHeight = initialHeight + (finalHeight - initialHeight) * phaseProgress;
// 相机围绕风机群中心旋转并下降
const overviewAngle = elapsedTime * 0.2;
cameraPosition = new THREE.Vector3(
bounds.center.x + Math.cos(overviewAngle) * overviewDistance,
currentHeight,
bounds.center.z + Math.sin(overviewAngle) * overviewDistance
);
targetPosition = bounds.center.clone();
targetPosition.y = 30;
break;
case 1: // 拉近阶段 - 从远景慢慢拉近到当前风机叶片
// 初始距离和高度
const startDistance = 150;
const endDistance = 40; // 更近的距离
const startHeight = 40;
const endHeight = 35; // 更低的高度,更接近叶片
// 插值计算当前距离和高度
const currentDistance = startDistance + (endDistance - startDistance) * phaseProgress;
const currentHeightVal = startHeight + (endHeight - startHeight) * phaseProgress;
// 相机从侧面接近风机,逐渐移动到正面
const approachAngle = Math.PI / 2; // 从侧面到正面
cameraPosition = new THREE.Vector3(
turbinePosition.x + Math.cos(approachAngle) * currentDistance,
turbinePosition.y + currentHeightVal,
turbinePosition.z + Math.sin(approachAngle) * currentDistance
);
// 目标点逐渐从塔筒上部移动到叶片区域
const targetHeight = 35 + Math.sin(phaseProgress * Math.PI) * 10;
targetPosition = new THREE.Vector3(
turbinePosition.x,
turbinePosition.y + targetHeight,
turbinePosition.z
);
break;
case 2: // 叶片观察阶段 - 围绕风机叶片观察
// 围绕风机叶片的路径,重点观察叶片
const bladeOrbitRadius = 30; // 更小的半径,更接近叶片
const bladeOrbitHeight = 30 + Math.sin(phaseProgress * Math.PI * 2) * 15; // 在叶片高度附近上下移动
// 围绕风机做椭圆形运动,重点观察叶片
const bladeAngle = elapsedTime * 0.6;
cameraPosition = new THREE.Vector3(
turbinePosition.x + Math.cos(bladeAngle) * bladeOrbitRadius * 1.5, // 椭圆轨道
turbinePosition.y + bladeOrbitHeight,
turbinePosition.z + Math.sin(bladeAngle) * bladeOrbitRadius
);
// 目标点跟踪叶片运动
targetPosition = new THREE.Vector3(
turbinePosition.x + Math.cos(bladeAngle * 2) * 10,
turbinePosition.y + 35 + Math.sin(phaseProgress * Math.PI * 3) * 5,
turbinePosition.z + Math.sin(bladeAngle * 2) * 10
);
break;
case 3: // 拉远阶段 - 从风机拉远并准备切换到下一个
// 从当前接近位置开始拉远
const pullbackStartDistance = 40;
const pullbackEndDistance = 150;
const pullbackStartHeight = 35;
const pullbackEndHeight = 70;
// 插值计算当前距离和高度
const pullbackDistance = pullbackStartDistance + (pullbackEndDistance - pullbackStartDistance) * phaseProgress;
const pullbackHeight = pullbackStartHeight + (pullbackEndHeight - pullbackStartHeight) * phaseProgress;
// 相机逐渐拉远并升高,同时移动到下一个风机的方向
const pullbackAngle = Math.PI / 4 * phaseProgress; // 逐渐改变角度
cameraPosition = new THREE.Vector3(
turbinePosition.x + Math.cos(pullbackAngle) * pullbackDistance,
turbinePosition.y + pullbackHeight,
turbinePosition.z + Math.sin(pullbackAngle) * pullbackDistance
);
// 目标点逐渐移向下一个风机的方向
targetPosition = new THREE.Vector3(
turbinePosition.x,
turbinePosition.y + 30,
turbinePosition.z
);
break;
}
// 设置相机位置和朝向
camera.position.copy(cameraPosition);
camera.lookAt(targetPosition);
}
// 自动漫游逻辑(保持原有函数名以兼容调用)
function updateAutoTour() {
updateCinematicTour();
}
// 添加键盘快捷键来切换自动漫游
function onKeyDown(event) {
// 原有的键盘控制代码...
switch (event.code) {
case 'ArrowUp':
case 'KeyW':
moveForward = true;
break;
case 'ArrowLeft':
case 'KeyA':
moveLeft = true;
break;
case 'ArrowDown':
case 'KeyS':
moveBackward = true;
break;
case 'ArrowRight':
case 'KeyD':
moveRight = true;
break;
case 'KeyQ':
moveUp = true;
break;
case 'KeyE':
moveDown = true;
break;
case 'KeyT': // 按 T 键切换自动漫游
const isActive = toggleAutoTour();
console.log(`自动漫游 ${isActive ? '启动' : '停止'}`);
break;
}
}
function onKeyUp(event) {
// 原有的键盘释放代码...
switch (event.code) {
case 'ArrowUp':
case 'KeyW':
moveForward = false;
break;
case 'ArrowLeft':
case 'KeyA':
moveLeft = false;
break;
case 'ArrowDown':
case 'KeyS':
moveBackward = false;
break;
case 'ArrowRight':
case 'KeyD':
moveRight = false;
break;
case 'KeyQ':
moveUp = false;
break;
case 'KeyE':
moveDown = false;
break;
}
}
// 风机数据模拟
const turbineData = {
'GT001': { power: '430 kW', daily: '3,200 kWh', windspeed: '12.8 m/s', status: 'grid' },
'GT002': { power: '425 kW', daily: '3,180 kWh', windspeed: '12.7 m/s', status: 'grid' },
'GT003': { power: '435 kW', daily: '3,250 kWh', windspeed: '12.9 m/s', status: 'grid' },
'GT004': { power: '428 kW', daily: '3,190 kWh', windspeed: '12.6 m/s', status: 'grid' },
'GT005': { power: '432 kW', daily: '3,220 kWh', windspeed: '12.8 m/s', status: 'grid' },
'GT006': { power: '0 kW', daily: '0 kWh', windspeed: '12.8 m/s', status: 'maintenance' },
'GT007': { power: '0 kW', daily: '1,500 kWh', windspeed: '12.8 m/s', status: 'fault' },
'GT008': { power: '429 kW', daily: '3,210 kWh', windspeed: '12.7 m/s', status: 'grid' },
'GT009': { power: '431 kW', daily: '3,230 kWh', windspeed: '12.8 m/s', status: 'grid' },
'GT010': { power: '427 kW', daily: '3,170 kWh', windspeed: '12.6 m/s', status: 'grid' }
};
// 状态映射
const statusMap = {
'grid': { text: '并网发电', class: 'status-grid' },
'stopped': { text: '停机', class: 'status-stopped' },
'standby': { text: '待机', class: 'status-standby' },
'maintenance': { text: '维护中', class: 'status-maintenance' },
'fault': { text: '故障', class: 'status-fault' }
};
// 显示风机详情
function showTurbineDetail(turbineName) {
const data = turbineData[turbineName];
if (!data) return;
// 更新面板内容
document.getElementById('detail-name').textContent = turbineName;
document.getElementById('detail-power').textContent = data.power;
document.getElementById('detail-daily').textContent = data.daily;
document.getElementById('detail-windspeed').textContent = data.windspeed;
const statusElement = document.getElementById('detail-status');
const statusInfo = statusMap[data.status];
statusElement.textContent = statusInfo.text;
statusElement.className = 'value ' + statusInfo.class;
// 获取风机在屏幕上的位置
const turbineModel = windTurbineModels.find(model => {
let found = false;
model.traverse(child => {
if (child.userData && child.userData.turbineName === turbineName) {
found = true;
}
});
return found;
});
if (turbineModel) {
// 获取风机叶片位置(风机顶部)
const turbinePosition = turbineModel.position.clone();
// 叶片在风机顶部大约在y=30的位置
turbinePosition.y += 30;
// 将3D位置转换为屏幕坐标
const vector = turbinePosition.clone();
vector.project(camera);
const panel = document.getElementById('turbine-detail-panel');
const container = document.getElementById('container');
const containerRect = container.getBoundingClientRect();
// 计算屏幕坐标
const x = Math.round((vector.x * 0.5 + 0.5) * containerRect.width);
const y = Math.round((-vector.y * 0.5 + 0.5) * containerRect.height);
// 设置面板位置
panel.style.left = (x - panel.offsetWidth / 2) + 'px';
panel.style.top = (y - panel.offsetHeight - 20) + 'px';
panel.style.right = 'auto';
panel.style.transform = 'none';
// 确保面板在视口内
const rect = panel.getBoundingClientRect();
if (rect.left < 10) {
panel.style.left = '10px';
}
if (rect.right > window.innerWidth - 10) {
panel.style.left = (window.innerWidth - rect.width - 10) + 'px';
}
if (rect.top < 10) {
panel.style.top = '10px';
}
}
// 显示面板
document.getElementById('turbine-detail-panel').classList.remove('hidden');
}
// 关闭风机详情
function closeTurbineDetail() {
document.getElementById('turbine-detail-panel').classList.add('hidden');
}
// 修改 setupTurbineClickEvents 函数
function setupTurbineClickEvents() {
// 为每个风机模型设置 userData
windTurbineModels.forEach((model, index) => {
const turbineName = `GT${(index + 1).toString().padStart(3, '0')}`;
model.userData = { turbineName: turbineName };
// 为模型的所有子对象也设置 userData
model.traverse((child) => {
if (child.isMesh) {
child.userData = { turbineName: turbineName };
}
});
});
console.log('风机点击事件设置完成,共设置', windTurbineModels.length, '台风机');
}
// 修改 onDocumentMouseDown 函数
function onDocumentMouseDown(event) {
event.preventDefault();
// 只在非自动漫游模式下处理点击事件
if (autoTourActive) return;
const mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
// 只检测风机模型
const intersects = raycaster.intersectObjects(windTurbineModels, true);
if (intersects.length > 0) {
let object = intersects[0].object;
// 向上查找包含风机信息的对象
while (object && !object.userData.turbineName) {
object = object.parent;
}
if (object && object.userData.turbineName) {
console.log('点击了风机:', object.userData.turbineName);
showTurbineDetail(object.userData.turbineName);
}
} else {
// 点击空白处关闭面板
closeTurbineDetail();
}
}