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