From e66e26764e448713c8c08185d62e2586792d759e Mon Sep 17 00:00:00 2001 From: sebseb7 Date: Tue, 26 Aug 2025 00:56:40 +0200 Subject: [PATCH] Implement fly mode in FPV controls: added vertical movement capabilities, toggle functionality with 'G' key, and updated control logic for jumping and crouching. Enhanced user instructions in index.html. Improved lighting setup in lighting.js with additional directional light and adjustments to existing lights for better illumination. Refined material handling in model-loader.js to ensure proper rendering of glass and non-glass materials. --- src/fpv-controls.js | 170 ++++++++++++++++++++++++++++++++------------ src/index.html | 3 +- src/lighting.js | 53 ++++++++++---- src/model-loader.js | 13 ++-- 4 files changed, 172 insertions(+), 67 deletions(-) diff --git a/src/fpv-controls.js b/src/fpv-controls.js index 034da61..4e60f22 100644 --- a/src/fpv-controls.js +++ b/src/fpv-controls.js @@ -2,6 +2,7 @@ import * as THREE from 'three'; // FPV Mode variables let fpvMode = false; +let flyMode = false; // New fly mode state let fpvControls = { moveForward: false, moveBackward: false, @@ -9,6 +10,8 @@ let fpvControls = { moveRight: false, jump: false, crouch: false, + moveUp: false, // For fly mode + moveDown: false, // For fly mode canJump: true, velocity: new THREE.Vector3(), direction: new THREE.Vector3(), @@ -69,6 +72,14 @@ function onKeyDown(event) { return false; } break; + case 'KeyG': + if (fpvMode) { + event.preventDefault(); + event.stopPropagation(); + toggleFlyMode(); + return false; + } + break; case 'KeyW': if (fpvMode) fpvControls.moveForward = true; break; @@ -84,15 +95,23 @@ function onKeyDown(event) { case 'Space': if (fpvMode) { event.preventDefault(); - fpvControls.jump = true; + if (flyMode) { + fpvControls.moveUp = true; + } else { + fpvControls.jump = true; + } } break; case 'ShiftLeft': case 'ShiftRight': if (fpvMode) { event.preventDefault(); - fpvControls.crouch = true; - fpvControls.isCrouching = true; + if (flyMode) { + fpvControls.moveDown = true; + } else { + fpvControls.crouch = true; + fpvControls.isCrouching = true; + } } break; } @@ -113,13 +132,23 @@ function onKeyUp(event) { if (fpvMode) fpvControls.moveRight = false; break; case 'Space': - if (fpvMode) fpvControls.jump = false; + if (fpvMode) { + if (flyMode) { + fpvControls.moveUp = false; + } else { + fpvControls.jump = false; + } + } break; case 'ShiftLeft': case 'ShiftRight': if (fpvMode) { - fpvControls.crouch = false; - fpvControls.isCrouching = false; + if (flyMode) { + fpvControls.moveDown = false; + } else { + fpvControls.crouch = false; + fpvControls.isCrouching = false; + } } break; } @@ -194,7 +223,29 @@ function enterFPVMode(renderer) { renderer.domElement.requestPointerLock(); } - console.log('FPV mode active - WASD to move, mouse to look, ESC to exit'); + console.log('FPV mode active - WASD to move, mouse to look, G to toggle fly mode, ESC to exit'); +} + +function toggleFlyMode() { + flyMode = !flyMode; + + // Reset vertical states when switching modes + fpvControls.verticalVelocity = 0; + fpvControls.isJumping = false; + fpvControls.isCrouching = false; + fpvControls.moveUp = false; + fpvControls.moveDown = false; + fpvControls.jump = false; + fpvControls.crouch = false; + + if (flyMode) { + console.log('🚁 Fly mode enabled - SPACE to ascend, SHIFT to descend, no gravity'); + } else { + console.log('🚶 Walk mode enabled - SPACE to jump, SHIFT to crouch, with gravity'); + // Set current height to maintain position when switching back to walk mode + fpvControls.currentHeight = window.camera.position.y; + fpvControls.targetHeight = fpvControls.currentHeight; + } } function exitFPVMode() { @@ -234,12 +285,17 @@ function exitFPVMode() { fpvControls.moveRight = false; fpvControls.jump = false; fpvControls.crouch = false; + fpvControls.moveUp = false; + fpvControls.moveDown = false; fpvControls.verticalVelocity = 0; fpvControls.isJumping = false; fpvControls.isCrouching = false; fpvControls.currentHeight = window.eyeHeight; fpvControls.targetHeight = window.eyeHeight; + // Reset fly mode + flyMode = false; + console.log('Orbit controls restored'); }, 50); } @@ -248,11 +304,12 @@ export function updateFPVMovement(camera) { if (!fpvMode) return; const delta = 0.016; // Approximate 60fps - const moveSpeed = 100.0; // Units per second (1000x faster) - const jumpSpeed = 220.0; // Jump initial velocity (4x higher) - const gravity = 420.0; // Gravity strength (4x faster) - const crouchHeight = window.eyeHeight * 0.5; // Crouch height (60% of normal eye height) + const moveSpeed = 100.0; // Units per second + const jumpSpeed = 220.0; // Jump initial velocity + const gravity = 420.0; // Gravity strength + const crouchHeight = window.eyeHeight * 0.5; // Crouch height const crouchSpeed = 100.0; // Speed of crouching transition + const flySpeed = 80.0; // Vertical fly speed // Reset direction fpvControls.direction.set(0, 0, 0); @@ -260,63 +317,82 @@ export function updateFPVMovement(camera) { // Calculate movement direction based on current rotation const forward = new THREE.Vector3(0, 0, -1); const right = new THREE.Vector3(1, 0, 0); + const up = new THREE.Vector3(0, 1, 0); // Apply horizontal rotation to movement vectors forward.applyAxisAngle(new THREE.Vector3(0, 1, 0), fpvControls.rotation.y); right.applyAxisAngle(new THREE.Vector3(0, 1, 0), fpvControls.rotation.y); - // Apply movement inputs + // Apply horizontal movement inputs if (fpvControls.moveForward) fpvControls.direction.add(forward); if (fpvControls.moveBackward) fpvControls.direction.sub(forward); if (fpvControls.moveLeft) fpvControls.direction.sub(right); if (fpvControls.moveRight) fpvControls.direction.add(right); - // Normalize and apply speed + // Normalize and apply horizontal speed if (fpvControls.direction.length() > 0) { fpvControls.direction.normalize(); fpvControls.direction.multiplyScalar(moveSpeed * delta); - // Apply movement to camera position + // Apply horizontal movement to camera position camera.position.add(fpvControls.direction); } - // Handle jumping - if (fpvControls.jump && fpvControls.canJump && !fpvControls.isJumping) { - fpvControls.verticalVelocity = jumpSpeed; - fpvControls.isJumping = true; - fpvControls.canJump = false; - } - - // Update target height based on crouching state - fpvControls.targetHeight = fpvControls.isCrouching ? crouchHeight : window.eyeHeight; - - // Apply gravity and vertical movement - if (fpvControls.isJumping) { - fpvControls.verticalVelocity -= gravity * delta; - camera.position.y += fpvControls.verticalVelocity * delta; + if (flyMode) { + // FLY MODE - No gravity, direct vertical control + let verticalMovement = 0; - // Check if landed back on ground - if (camera.position.y <= fpvControls.targetHeight) { - camera.position.y = fpvControls.targetHeight; - fpvControls.currentHeight = fpvControls.targetHeight; - fpvControls.verticalVelocity = 0; - fpvControls.isJumping = false; - fpvControls.canJump = true; + if (fpvControls.moveUp) { + verticalMovement += flySpeed * delta; + } + if (fpvControls.moveDown) { + verticalMovement -= flySpeed * delta; + } + + if (verticalMovement !== 0) { + camera.position.y += verticalMovement; } } else { - // Smooth crouching transition when not jumping - if (Math.abs(fpvControls.currentHeight - fpvControls.targetHeight) > 0.01) { - const direction = fpvControls.targetHeight > fpvControls.currentHeight ? 1 : -1; - fpvControls.currentHeight += direction * crouchSpeed * delta; - - // Clamp to target - if (direction > 0 && fpvControls.currentHeight >= fpvControls.targetHeight) { - fpvControls.currentHeight = fpvControls.targetHeight; - } else if (direction < 0 && fpvControls.currentHeight <= fpvControls.targetHeight) { - fpvControls.currentHeight = fpvControls.targetHeight; - } + // WALK MODE - Gravity, jumping, crouching + + // Handle jumping + if (fpvControls.jump && fpvControls.canJump && !fpvControls.isJumping) { + fpvControls.verticalVelocity = jumpSpeed; + fpvControls.isJumping = true; + fpvControls.canJump = false; + } + + // Update target height based on crouching state + fpvControls.targetHeight = fpvControls.isCrouching ? crouchHeight : window.eyeHeight; + + // Apply gravity and vertical movement + if (fpvControls.isJumping) { + fpvControls.verticalVelocity -= gravity * delta; + camera.position.y += fpvControls.verticalVelocity * delta; + + // Check if landed back on ground + if (camera.position.y <= fpvControls.targetHeight) { + camera.position.y = fpvControls.targetHeight; + fpvControls.currentHeight = fpvControls.targetHeight; + fpvControls.verticalVelocity = 0; + fpvControls.isJumping = false; + fpvControls.canJump = true; + } + } else { + // Smooth crouching transition when not jumping + if (Math.abs(fpvControls.currentHeight - fpvControls.targetHeight) > 0.01) { + const direction = fpvControls.targetHeight > fpvControls.currentHeight ? 1 : -1; + fpvControls.currentHeight += direction * crouchSpeed * delta; + + // Clamp to target + if (direction > 0 && fpvControls.currentHeight >= fpvControls.targetHeight) { + fpvControls.currentHeight = fpvControls.targetHeight; + } else if (direction < 0 && fpvControls.currentHeight <= fpvControls.targetHeight) { + fpvControls.currentHeight = fpvControls.targetHeight; + } + } + camera.position.y = fpvControls.currentHeight; } - camera.position.y = fpvControls.currentHeight; } // Apply rotation to camera diff --git a/src/index.html b/src/index.html index d745e19..252c50d 100644 --- a/src/index.html +++ b/src/index.html @@ -23,7 +23,8 @@

✌️ Two finger drag: Pan around scene


🚶 (or press F key to toggle)

-

In FPV: WASD to move, mouse to look, SPACE to jump, SHIFT to crouch, F or ESC to exit

+

In FPV: WASD to move, mouse to look, G to toggle fly mode, F or ESC to exit

+

Walk mode: SPACE jump, SHIFT crouch | Fly mode: SPACE ascend, SHIFT descend

diff --git a/src/lighting.js b/src/lighting.js index 570d949..fe796f0 100644 --- a/src/lighting.js +++ b/src/lighting.js @@ -18,18 +18,25 @@ export function setupLighting(scene, camera) { pointLight.position.set(-10, 10, 10); scene.add(pointLight); - // Camera light - follows the camera to illuminate from viewing angle - window.cameraLight = new THREE.PointLight(0xffffff, 1.0, 200); + // Enhanced camera point light - follows the camera to illuminate from viewing angle + window.cameraLight = new THREE.PointLight(0xffffff, 2.0, 300); window.cameraLight.position.copy(camera.position); scene.add(window.cameraLight); - // Additional camera spotlight for focused illumination - window.cameraSpotlight = new THREE.SpotLight(0xffffff, 0.8, 100, Math.PI / 4, 0.3); + // Camera spotlight for focused illumination (like a headlamp) + window.cameraSpotlight = new THREE.SpotLight(0xffffff, 1.5, 150, Math.PI / 6, 0.2); window.cameraSpotlight.position.copy(camera.position); window.cameraSpotlight.target.position.set(0, 0, 0); // Point at origin initially scene.add(window.cameraSpotlight); scene.add(window.cameraSpotlight.target); + // Additional camera directional light (like a flashlight) + window.cameraDirectionalLight = new THREE.DirectionalLight(0xffffff, 1.0); + window.cameraDirectionalLight.position.copy(camera.position); + window.cameraDirectionalLight.target.position.set(0, 0, -1); + scene.add(window.cameraDirectionalLight); + scene.add(window.cameraDirectionalLight.target); + // Interior point lights (will be repositioned when model loads) - increased intensity window.interiorLight = new THREE.PointLight(0xffffff, 0.8, 50); window.interiorLight.position.set(0, 0, 0); // Will be moved to model center @@ -44,7 +51,7 @@ export function setupLighting(scene, camera) { const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.8); scene.add(hemiLight); - console.log('✨ Camera lights added - they will follow your view'); + console.log('✨ Enhanced camera lights added - headlamp-style illumination will follow your view'); // Create environment map for glass reflections setupEnvironmentMap(); @@ -91,10 +98,26 @@ export function updateCameraLights(camera, controls) { if (window.cameraSpotlight) { window.cameraSpotlight.position.copy(camera.position); - // Make spotlight point towards the orbit controls target (model center) - if (controls && controls.target) { - window.cameraSpotlight.target.position.copy(controls.target); - } + + // Calculate where the camera is looking + const lookDirection = new THREE.Vector3(0, 0, -1); + lookDirection.applyQuaternion(camera.quaternion); + + // Set spotlight target based on camera direction + const targetPosition = camera.position.clone().add(lookDirection.multiplyScalar(10)); + window.cameraSpotlight.target.position.copy(targetPosition); + } + + if (window.cameraDirectionalLight) { + window.cameraDirectionalLight.position.copy(camera.position); + + // Calculate where the camera is looking for directional light + const lookDirection = new THREE.Vector3(0, 0, -1); + lookDirection.applyQuaternion(camera.quaternion); + + // Set directional light target based on camera direction + const targetPosition = camera.position.clone().add(lookDirection.multiplyScalar(5)); + window.cameraDirectionalLight.target.position.copy(targetPosition); } } @@ -119,13 +142,17 @@ export function adjustLightsForModel(modelBounds) { window.interiorLight2.distance = maxDimension * 1.5; } - // Update camera lights to target the model center + // Update camera lights based on model size if (window.cameraLight) { - window.cameraLight.distance = maxDimension * 3; // Adjust range based on model size + window.cameraLight.distance = maxDimension * 4; // Adjust range based on model size } if (window.cameraSpotlight) { - window.cameraSpotlight.target.position.copy(center); - window.cameraSpotlight.distance = maxDimension * 2; + window.cameraSpotlight.distance = maxDimension * 3; + } + + if (window.cameraDirectionalLight) { + // Directional lights don't have distance, but we can adjust intensity + window.cameraDirectionalLight.intensity = 1.0; } } diff --git a/src/model-loader.js b/src/model-loader.js index 3623f04..79fc864 100644 --- a/src/model-loader.js +++ b/src/model-loader.js @@ -12,10 +12,11 @@ export function loadCubeModel(scene, camera, controls, onLoadComplete) { mtlLoader.load('cube.mtl', function(materials) { materials.preload(); - // Configure materials for double-sided rendering with proper transparency + // Configure materials with proper transparency Object.keys(materials.materials).forEach(function(key) { const material = materials.materials[key]; - material.side = THREE.DoubleSide; + // Only set DoubleSide for glass materials, others use FrontSide + material.side = key.toLowerCase().includes('glass') ? THREE.DoubleSide : THREE.FrontSide; // Handle glass materials specially if (key.toLowerCase().includes('glass')) { @@ -120,12 +121,12 @@ export function loadCubeModel(scene, camera, controls, onLoadComplete) { child.add(wireframeLines); } } else { - // Non-glass materials - child.material.side = THREE.DoubleSide; + // Non-glass materials - fix texture appearing on both sides + child.material.side = THREE.FrontSide; child.material.transparent = false; child.material.opacity = 1.0; - // Ensure proper lighting on both sides + // Ensure proper lighting if (child.material.type === 'MeshLambertMaterial' || child.material.type === 'MeshPhongMaterial') { child.material.emissive = new THREE.Color(0x222222); // Increased self-illumination for brightness } @@ -177,7 +178,7 @@ export function loadObjWithoutMaterials(scene, camera, controls, onLoadComplete) // Apply default material with better interior visibility const defaultMaterial = new THREE.MeshPhongMaterial({ color: 0xffffff, - side: THREE.DoubleSide, + side: THREE.FrontSide, // Fix texture appearing on both sides transparent: false, opacity: 1.0, emissive: new THREE.Color(0x222222), // Increased self-illumination for brightness