|
|
// 全局变量
|
|
|
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();
|
|
|
|
|
|
// 初始化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场景初始化完成');
|
|
|
}
|
|
|
|
|
|
// 加载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, 0, 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);
|
|
|
|
|
|
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, 0, 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);
|
|
|
|
|
|
console.log(`备用风电机组模型 ${index + 1} 创建完成`);
|
|
|
}
|
|
|
|
|
|
// 动画循环
|
|
|
function animate() {
|
|
|
requestAnimationFrame(animate);
|
|
|
|
|
|
const time = clock.getElapsedTime();
|
|
|
|
|
|
// 漫游控制逻辑
|
|
|
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 toggleControls() {
|
|
|
if (pointerLockControls) {
|
|
|
if (pointerLockControls.isLocked) {
|
|
|
pointerLockControls.unlock();
|
|
|
} else {
|
|
|
pointerLockControls.lock();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 键盘事件处理函数
|
|
|
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;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
}
|
|
|
}
|