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.

832 lines
30 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'; // 电影级漫游模式
// 在全局变量部分添加
let shipModels = [];
// 修改初始化函数中的相机设置
function init() {
console.log('开始初始化Three.js场景');
// 创建场景
scene = new THREE.Scene();
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);
// 调整相机位置,使其能够完整展示浮标并位于中心
const cameraRadius = 800; // 距离浮标的距离
const cameraHeight = 100; // 相机高度
const cameraRotationAngle = THREE.MathUtils.degToRad(0); // 正面视角
const cameraX = cameraRadius * Math.sin(cameraRotationAngle);
const cameraZ = cameraRadius * Math.cos(cameraRotationAngle);
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,
antialias: true,
stencil: true,
powerPreference: 'high-performance',
physicalCorrectLights: true,
localClippingEnabled: true,
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 = 300; // 增加最小距离,确保能看到整个浮标
controls.maxDistance = 2000; // 最大距离
controls.maxPolarAngle = Math.PI / 2 - 0.1; // 限制垂直旋转角度,防止看到水面以下
controls.target.set(0, 0, 0);
controls.addEventListener('change', function () {
// 限制相机高度,防止进入水面以下
if (camera.position.y < 10) {
camera.position.y = 10;
}
});
console.log('相机控制器创建完成');
} else {
console.warn('OrbitControls未加载跳过相机控制器');
}
}
addLights();
createOcean(); // 创建海洋
loadShips(); // 加载船只模型
// 监听窗口大小变化
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);
}
// 修改船只加载函数加载GLB格式的科考船模型
function loadShips() {
console.log('开始加载科考船模型');
// 确保已引入 GLTFLoader
if (typeof THREE.GLTFLoader === 'undefined') {
console.warn('GLTFLoader未定义请确认已正确引入GLTFLoader');
return;
}
const loader = new THREE.GLTFLoader();
// 只加载一艘船,放置在场景中心
const position = { x: -300, z: 0 };
let loadedCount = 0;
loader.load(
'./models/h385-航洋浮标_a2.glb',
function (gltf) {
console.log('科考船模型加载成功');
const object = gltf.scene;
// 调整模型缩放和位置 - 根据GLB模型特点调整
object.scale.set(100, 100, 100); // 保持合适的缩放比例
// 将船只位置设置在水面上y=0
object.position.set(position.x, -5, position.z);
// // 调整模型材质
// object.traverse(function (child) {
// if (child.isMesh) {
// child.castShadow = true;
// child.receiveShadow = true;
// child.frustumCulled = false;
// // 如果材质存在,调整其属性
// if (child.material) {
// // // 增强材质亮度和自发光效果
// // child.material.emissive = new THREE.Color(0x555555); // 显著增加自发光强度
// // child.material.emissiveIntensity = 0.5; // 增加自发光强度
// // // 调整材质颜色,使其更亮
// // if (child.material.color) {
// // // 提亮原始颜色
// // child.material.color.multiplyScalar(1.5);
// // }
// // // 确保材质响应光照
// // if (child.material.type === 'MeshBasicMaterial') {
// // // 如果是基础材质,替换为标准材质
// // child.material = new THREE.MeshStandardMaterial({
// // color: child.material.color || 0xffffff,
// // map: child.material.map,
// // metalness: 0.2,
// // roughness: 0.5,
// // emissive: new THREE.Color(0x444444), // 添加自发光
// // emissiveIntensity: 0.4
// // });
// // } else if (child.material.type === 'MeshStandardMaterial') {
// // // 调整标准材质参数使其更亮
// // child.material.metalness = Math.max(0.1, child.material.metalness || 0.2);
// // child.material.roughness = Math.min(0.7, child.material.roughness || 0.5);
// // child.material.emissive = new THREE.Color(0x444444);
// // child.material.emissiveIntensity = 0.4; // 增加自发光强度
// // } else if (child.material.type === 'MeshLambertMaterial') {
// // // 对于Lambert材质也添加自发光
// // child.material.emissive = new THREE.Color(0x444444);
// // child.material.emissiveIntensity = 0.4;
// // }
// // // 确保材质更新
// // child.material.needsUpdate = true;
// }
// }
// });
// 添加到场景和数组
scene.add(object);
shipModels.push(object);
loadedCount++;
console.log('科考船模型加载完成!');
},
function (progress) {
if (progress.total > 0) {
const percent = (progress.loaded / progress.total * 100).toFixed(1);
console.log(`科考船模型加载进度: ${percent}%`);
}
},
function (error) {
console.error('科考船模型加载失败:', error);
loadedCount++;
}
);
}
// 修改船只动画函数,调整船只在水面上的位置
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 && shipModels.length > 0) {
shipModels.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.5;
}
}
});
});
}
// 更新水面动画 - 增强水面波动效果
if (water && water.material.uniforms) {
// 通过改变time值来控制水面波动动画
water.material.uniforms['time'].value += 1.0 / 60.0;
}
// 船只动画(如果有需要的话)
if (shipModels.length > 0) {
shipModels.forEach((model) => {
// 可以为船只添加浮动动画效果
const time = clock.getElapsedTime();
// 轻微的上下浮动效果
model.position.y = Math.sin(time * 0.5) * 0.5;
// 轻微的摇摆效果
model.rotation.z = Math.sin(time * 0.3) * 0.02;
model.rotation.x = Math.cos(time * 0.4) * 0.015;
});
}
// 渲染场景
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;
}
}