|
|
|
|
|
// 全局变量
|
|
|
|
|
|
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 = 200; // 相机高度
|
|
|
|
|
|
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(); // 创建海洋
|
|
|
|
|
|
loadBuoys(); // 加载浮标模型
|
|
|
|
|
|
|
|
|
|
|
|
// 监听窗口大小变化
|
|
|
|
|
|
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: 0.8, // 水面透明度
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 修改浮标加载函数,确保浮标更深地嵌入水下并增加调试信息
|
|
|
|
|
|
function loadBuoys() {
|
|
|
|
|
|
console.log('开始加载科考浮标模型');
|
|
|
|
|
|
|
|
|
|
|
|
// 确保已引入 GLTFLoader
|
|
|
|
|
|
if (typeof THREE.GLTFLoader === 'undefined') {
|
|
|
|
|
|
console.warn('GLTFLoader未定义,请确认已正确引入GLTFLoader');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const loader = new THREE.GLTFLoader();
|
|
|
|
|
|
|
|
|
|
|
|
// 只加载一个浮标,放置在场景中心
|
|
|
|
|
|
const position = { x: 250, z: 100 };
|
|
|
|
|
|
|
|
|
|
|
|
let loadedCount = 0;
|
|
|
|
|
|
|
|
|
|
|
|
loader.load(
|
|
|
|
|
|
'./models/科考船3.glb',
|
|
|
|
|
|
function (gltf) {
|
|
|
|
|
|
console.log('科考浮标模型加载成功');
|
|
|
|
|
|
|
|
|
|
|
|
const object = gltf.scene;
|
|
|
|
|
|
|
|
|
|
|
|
// 调整模型缩放和位置 - 根据GLB模型特点调整
|
|
|
|
|
|
object.scale.set(10, 10, 10); // 保持合适的缩放比例
|
|
|
|
|
|
|
|
|
|
|
|
// 将浮标位置设置在水下(y为负值表示在水面下方)
|
|
|
|
|
|
object.position.set(position.x,-120, 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|