UNPKG

universe-simulation

Version:

A powerful JavaScript library for creating interactive universe simulations with three.js

152 lines (124 loc) 5.41 kB
import * as THREE from 'three'; /** * Smooth navigation system with natural movement and acceleration */ export class SmoothNavigation { constructor(camera) { this.camera = camera; // Movement states this.velocity = new THREE.Vector3(0, 0, 0); this.targetVelocity = new THREE.Vector3(0, 0, 0); this.rotationVelocity = { x: 0, y: 0 }; // Movement parameters this.acceleration = 0.5; this.deceleration = 0.9; this.rotationDamping = 0.85; this.maxSpeedMultiplier = 100; // Speed levels for different scales this.speedLevels = [ { distance: 0, speed: 10, name: "Planetary" }, // Near planets { distance: 1000, speed: 100, name: "Solar System" }, // Solar system scale { distance: 10000, speed: 1000, name: "Stellar" }, // Between stars { distance: 100000, speed: 10000, name: "Interstellar" }, // Star field { distance: 1e6, speed: 100000, name: "Galactic" }, // Galaxy scale { distance: 1e8, speed: 1e6, name: "Intergalactic" }, // Between galaxies { distance: 1e10, speed: 1e8, name: "Cosmic" }, // Universe scale ]; this.currentSpeedLevel = 0; } // Get appropriate speed based on distance from origin getBaseSpeed() { const distance = this.camera.position.length(); // Find appropriate speed level for (let i = this.speedLevels.length - 1; i >= 0; i--) { if (distance >= this.speedLevels[i].distance) { this.currentSpeedLevel = i; // Interpolate between speed levels for smooth transition if (i < this.speedLevels.length - 1) { const nextLevel = this.speedLevels[i + 1]; const t = (distance - this.speedLevels[i].distance) / (nextLevel.distance - this.speedLevels[i].distance); const smoothT = t * t * (3 - 2 * t); // Smooth step function return THREE.MathUtils.lerp( this.speedLevels[i].speed, nextLevel.speed, smoothT ); } return this.speedLevels[i].speed; } } return this.speedLevels[0].speed; } // Update movement based on input update(keys, deltaTime, pitch, yaw) { // Get movement vectors const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(this.camera.quaternion); const worldUp = new THREE.Vector3(0, 1, 0); // Calculate right vector using cross product to ensure it's always perpendicular const right = new THREE.Vector3().crossVectors(forward, worldUp).normalize(); // If looking straight up or down, forward and worldUp are parallel, so use camera's right if (right.lengthSq() < 0.001) { right.set(1, 0, 0).applyQuaternion(this.camera.quaternion); } // Calculate target velocity based on input this.targetVelocity.set(0, 0, 0); const baseSpeed = this.getBaseSpeed(); const speed = keys['shift'] ? baseSpeed * 10 : baseSpeed; if (keys['w']) this.targetVelocity.addScaledVector(forward, speed); if (keys['s']) this.targetVelocity.addScaledVector(forward, -speed); if (keys['a']) this.targetVelocity.addScaledVector(right, -speed); if (keys['d']) this.targetVelocity.addScaledVector(right, speed); if (keys[' ']) this.targetVelocity.addScaledVector(worldUp, speed); if (keys['control']) this.targetVelocity.addScaledVector(worldUp, -speed); // Smooth acceleration/deceleration if (this.targetVelocity.length() > 0) { // Accelerate towards target velocity this.velocity.lerp(this.targetVelocity, this.acceleration * deltaTime); } else { // Decelerate when no input this.velocity.multiplyScalar(Math.pow(this.deceleration, deltaTime * 60)); } // Apply velocity to position const moveDistance = deltaTime; this.camera.position.addScaledVector(this.velocity, moveDistance); // Handle rotation with damping if (this.rotationVelocity.x !== 0 || this.rotationVelocity.y !== 0) { const newYaw = yaw - this.rotationVelocity.x; // Negative for natural mouse movement const newPitch = pitch - this.rotationVelocity.y; // Clamp pitch to prevent flipping const clampedPitch = Math.max(-Math.PI / 2 + 0.1, Math.min(Math.PI / 2 - 0.1, newPitch)); // Apply rotation const quaternion = new THREE.Quaternion(); const euler = new THREE.Euler(clampedPitch, newYaw, 0, 'YXZ'); quaternion.setFromEuler(euler); this.camera.quaternion.copy(quaternion); // Damping this.rotationVelocity.x *= this.rotationDamping; this.rotationVelocity.y *= this.rotationDamping; return { pitch: clampedPitch, yaw: newYaw }; } return { pitch, yaw }; } // Add rotation input addRotation(x, y) { this.rotationVelocity.x += x; this.rotationVelocity.y += y; } // Get current speed info for UI getSpeedInfo() { return { currentSpeed: this.velocity.length(), baseSpeed: this.getBaseSpeed(), speedLevel: this.speedLevels[this.currentSpeedLevel].name, position: this.camera.position.clone() }; } // Reset velocity (used after teleport) reset() { this.velocity.set(0, 0, 0); this.targetVelocity.set(0, 0, 0); this.rotationVelocity.x = 0; this.rotationVelocity.y = 0; } }