diff --git a/src/index.js b/src/index.js index b7bae2f..c17ad7b 100644 --- a/src/index.js +++ b/src/index.js @@ -9,6 +9,23 @@ let scene, camera, renderer, controls; let cube; let cameraConfig = null; +// FPV Mode variables +let fpvMode = false; +let fpvControls = { + moveForward: false, + moveBackward: false, + moveLeft: false, + moveRight: false, + canJump: false, + velocity: new THREE.Vector3(), + direction: new THREE.Vector3(), + rotation: { x: 0, y: 0 } +}; +let modelBounds = null; +let groundLevel = 0; +let ceilingLevel = 0; +let eyeHeight = 0; + // Load camera configuration from config file async function loadCameraConfig() { try { @@ -120,6 +137,9 @@ async function init() { // Setup manual print button setupPrintButton(); + // Setup FPV controls + setupFPVControls(); + // Start render loop animate(); } @@ -677,6 +697,17 @@ function centerModelAndControls(object) { // Get the size of the bounding box const size = boundingBox.getSize(new THREE.Vector3()); + // Store model bounds for FPV mode + modelBounds = { boundingBox, center, size }; + groundLevel = boundingBox.min.y; + ceilingLevel = boundingBox.max.y; + eyeHeight = groundLevel + (ceilingLevel - groundLevel) * 0.5; + + console.log('FPV bounds calculated:'); + console.log('Ground level:', groundLevel); + console.log('Ceiling level:', ceilingLevel); + console.log('Eye height (50%):', eyeHeight); + // Set the orbit controls target to the model's center controls.target.copy(center); @@ -776,11 +807,176 @@ function setupRotationLogging() { console.log('Rotation logging enabled - camera state will be logged 1 second after movement stops'); } +function setupFPVControls() { + // Keyboard event handlers + document.addEventListener('keydown', onKeyDown); + document.addEventListener('keyup', onKeyUp); + + // Mouse event handlers for FPV mode + document.addEventListener('mousemove', onMouseMove); + document.addEventListener('click', onMouseClick); + + console.log('FPV controls setup complete. Press F to enter FPV mode'); +} + +function onKeyDown(event) { + switch (event.code) { + case 'KeyF': + if (!fpvMode) { + enterFPVMode(); + } + break; + case 'Escape': + if (fpvMode) { + event.preventDefault(); + event.stopPropagation(); + exitFPVMode(); + return false; + } + break; + case 'KeyW': + fpvControls.moveForward = true; + break; + case 'KeyA': + fpvControls.moveLeft = true; + break; + case 'KeyS': + fpvControls.moveBackward = true; + break; + case 'KeyD': + fpvControls.moveRight = true; + break; + } +} + +function onKeyUp(event) { + switch (event.code) { + case 'KeyW': + fpvControls.moveForward = false; + break; + case 'KeyA': + fpvControls.moveLeft = false; + break; + case 'KeyS': + fpvControls.moveBackward = false; + break; + case 'KeyD': + fpvControls.moveRight = false; + break; + } +} + +function onMouseMove(event) { + if (!fpvMode) return; + + const movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0; + const movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0; + + fpvControls.rotation.y -= movementX * 0.002; // Horizontal rotation (yaw) + fpvControls.rotation.x -= movementY * 0.002; // Vertical rotation (pitch) + + // Limit vertical look angle + fpvControls.rotation.x = Math.max(-Math.PI/2, Math.min(Math.PI/2, fpvControls.rotation.x)); +} + +function onMouseClick(event) { + if (fpvMode) { + // Request pointer lock for mouse control + renderer.domElement.requestPointerLock(); + } +} + +function enterFPVMode() { + if (!modelBounds) { + console.warn('Model not loaded yet, cannot enter FPV mode'); + return; + } + + console.log('🚶 Entering FPV mode'); + fpvMode = true; + + // Disable orbit controls + controls.enabled = false; + + // Position camera at eye height on the ground level (center of model in X/Z) + const centerX = modelBounds.center.x; + const centerZ = modelBounds.center.z; + camera.position.set(centerX, eyeHeight, centerZ); + + // Reset rotation + fpvControls.rotation.x = 0; + fpvControls.rotation.y = 0; + fpvControls.velocity.set(0, 0, 0); + + // Request pointer lock + renderer.domElement.requestPointerLock(); + + console.log('FPV mode active - WASD to move, mouse to look, ESC to exit'); +} + +function exitFPVMode() { + console.log('🔄 Exiting FPV mode'); + fpvMode = false; + + // Re-enable orbit controls + controls.enabled = true; + + // Exit pointer lock + document.exitPointerLock(); + + console.log('Orbit controls restored'); +} + +function updateFPVMovement() { + if (!fpvMode) return; + + const delta = 0.016; // Approximate 60fps + const moveSpeed = 500.0; // Units per second (1000x faster) + + // Reset direction + fpvControls.direction.set(0, 0, 0); + + // Calculate movement direction based on current rotation + const forward = new THREE.Vector3(0, 0, -1); + const right = new THREE.Vector3(1, 0, 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 + 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 + if (fpvControls.direction.length() > 0) { + fpvControls.direction.normalize(); + fpvControls.direction.multiplyScalar(moveSpeed * delta); + + // Apply movement to camera position + camera.position.add(fpvControls.direction); + } + + // Keep camera at eye height (simple ground collision - no falling through) + // But allow walking off edges (no invisible walls) + camera.position.y = eyeHeight; + + // Apply rotation to camera + camera.rotation.set(fpvControls.rotation.x, fpvControls.rotation.y, 0, 'YXZ'); +} + function animate() { requestAnimationFrame(animate); - // Update controls - controls.update(); + // Update FPV movement if in FPV mode + updateFPVMovement(); + + // Update controls (only if not in FPV mode) + if (!fpvMode) { + controls.update(); + } // Update camera lights to follow camera position if (window.cameraLight) {