import * as THREE from 'three'; export function createStarfield(scene) { const starCount = 16000; const starDistance = 300; // Create geometry for stars const starGeometry = new THREE.BufferGeometry(); const starPositions = new Float32Array(starCount * 3); const starSizes = new Float32Array(starCount); // Store original positions, current positions, and sizes const originalPositions = new Float32Array(starCount * 3); const currentPositions = new Float32Array(starCount * 3); const originalSizes = new Float32Array(starCount); const currentSizes = new Float32Array(starCount); // Generate random positions in a sphere around the scene for (let i = 0; i < starCount; i++) { const i3 = i * 3; const radius = Math.random() * starDistance + 50; const theta = Math.random() * Math.PI * 2; const phi = Math.acos(2 * Math.random() - 1); const x = radius * Math.sin(phi) * Math.cos(theta); const y = radius * Math.sin(phi) * Math.sin(theta); const z = radius * Math.cos(phi); // Store both original and current positions originalPositions[i3] = x; originalPositions[i3 + 1] = y; originalPositions[i3 + 2] = z; currentPositions[i3] = x; currentPositions[i3 + 1] = y; currentPositions[i3 + 2] = z; starPositions[i3] = x; starPositions[i3 + 1] = y; starPositions[i3 + 2] = z; // Store original and current sizes const baseSize = Math.random() * 0.2 + 0.1; originalSizes[i] = baseSize; currentSizes[i] = baseSize; starSizes[i] = baseSize; } starGeometry.setAttribute('position', new THREE.BufferAttribute(starPositions, 3)); starGeometry.setAttribute('size', new THREE.BufferAttribute(starSizes, 1)); // Star material with size attenuation const starMaterial = new THREE.PointsMaterial({ color: 0xffffff, size: 0.3, sizeAttenuation: true, transparent: true, opacity: 0.8, vertexColors: false }); const stars = new THREE.Points(starGeometry, starMaterial); scene.add(stars); // Distant stars layer const distantStarCount = 4000; const distantStarGeometry = new THREE.BufferGeometry(); const distantStarPositions = new Float32Array(distantStarCount * 3); const distantStarSizes = new Float32Array(distantStarCount); const distantOriginalPositions = new Float32Array(distantStarCount * 3); const distantCurrentPositions = new Float32Array(distantStarCount * 3); const distantOriginalSizes = new Float32Array(distantStarCount); const distantCurrentSizes = new Float32Array(distantStarCount); for (let i = 0; i < distantStarCount; i++) { const i3 = i * 3; const radius = Math.random() * 200 + starDistance; const theta = Math.random() * Math.PI * 2; const phi = Math.acos(2 * Math.random() - 1); const x = radius * Math.sin(phi) * Math.cos(theta); const y = radius * Math.sin(phi) * Math.sin(theta); const z = radius * Math.cos(phi); distantOriginalPositions[i3] = x; distantOriginalPositions[i3 + 1] = y; distantOriginalPositions[i3 + 2] = z; distantCurrentPositions[i3] = x; distantCurrentPositions[i3 + 1] = y; distantCurrentPositions[i3 + 2] = z; distantStarPositions[i3] = x; distantStarPositions[i3 + 1] = y; distantStarPositions[i3 + 2] = z; // Store original and current sizes for distant stars const baseSize = Math.random() * 0.1 + 0.05; distantOriginalSizes[i] = baseSize; distantCurrentSizes[i] = baseSize; distantStarSizes[i] = baseSize; } distantStarGeometry.setAttribute('position', new THREE.BufferAttribute(distantStarPositions, 3)); distantStarGeometry.setAttribute('size', new THREE.BufferAttribute(distantStarSizes, 1)); const distantStarMaterial = new THREE.PointsMaterial({ color: 0xccccff, size: 0.15, sizeAttenuation: true, transparent: true, opacity: 0.4 }); const distantStars = new THREE.Points(distantStarGeometry, distantStarMaterial); scene.add(distantStars); // Animation parameters const movementAmplitude = 2; const repulsionRadius = 400; const repulsionStrength = 5; const interpolationSpeed = 5; // NEW: Cursor brightness parameters const brightnessRadius = 600; // Radius for size increase effect const maxSizeMultiplier = 400.0; // Maximum size increase (4x original size) const sizeInterpolationSpeed = 100.0; // Speed of size changes // Raycaster for mouse position in 3D space const raycaster = new THREE.Raycaster(); const mouseWorldPos = new THREE.Vector3(); function animateStars(camera, mouse, deltaTime) { const time = Date.now() * 0.0003; // Get mouse position in world space if (mouse && camera) { raycaster.setFromCamera(mouse, camera); // Project mouse to a plane at distance 0 from camera const distance = 100; mouseWorldPos.copy(raycaster.ray.direction).multiplyScalar(distance).add(raycaster.ray.origin); } // Update close stars const positions = starGeometry.attributes.position.array; const sizes = starGeometry.attributes.size.array; for (let i = 0; i < starCount; i++) { const i3 = i * 3; // Get original position const origX = originalPositions[i3]; const origY = originalPositions[i3 + 1]; const origZ = originalPositions[i3 + 2]; // Add gentle oscillating movement const offsetX = Math.sin(time + i * 0.01) * movementAmplitude; const offsetY = Math.cos(time * 0.7 + i * 0.02) * movementAmplitude; const offsetZ = Math.sin(time * 0.5 + i * 0.015) * movementAmplitude; let targetX = origX + offsetX; let targetY = origY + offsetY; let targetZ = origZ + offsetZ; // Cursor repulsion if (mouse) { const dx = targetX - mouseWorldPos.x; const dy = targetY - mouseWorldPos.y; const dz = targetZ - mouseWorldPos.z; const distance = Math.sqrt(dx * dx + dy * dy + dz * dz); if (distance < repulsionRadius && distance > 0) { const force = (1 - distance / repulsionRadius) * repulsionStrength; const nx = dx / distance; const ny = dy / distance; const nz = dz / distance; targetX += nx * force; targetY += ny * force; targetZ += nz * force; } } // Smooth interpolation to target position const currentX = currentPositions[i3]; const currentY = currentPositions[i3 + 1]; const currentZ = currentPositions[i3 + 2]; const lerpFactor = Math.min(interpolationSpeed * deltaTime, 1.0); currentPositions[i3] = THREE.MathUtils.lerp(currentX, targetX, lerpFactor); currentPositions[i3 + 1] = THREE.MathUtils.lerp(currentY, targetY, lerpFactor); currentPositions[i3 + 2] = THREE.MathUtils.lerp(currentZ, targetZ, lerpFactor); // Update geometry positions positions[i3] = currentPositions[i3]; positions[i3 + 1] = currentPositions[i3 + 1]; positions[i3 + 2] = currentPositions[i3 + 2]; // NEW: Calculate size based on cursor proximity let targetSize = originalSizes[i]; if (mouse) { const finalX = currentPositions[i3]; const finalY = currentPositions[i3 + 1]; const finalZ = currentPositions[i3 + 2]; const dx = finalX - mouseWorldPos.x; const dy = finalY - mouseWorldPos.y; const dz = finalZ - mouseWorldPos.z; const distance = Math.sqrt(dx * dx + dy * dy + dz * dz); if (distance < brightnessRadius) { // Calculate size multiplier based on distance (closer = bigger) const proximityFactor = 1 - (distance / brightnessRadius); const sizeMultiplier = 1 + (proximityFactor * (maxSizeMultiplier - 1)); targetSize = originalSizes[i] * sizeMultiplier; } } // Smooth interpolation for size changes const sizeLerpFactor = Math.min(sizeInterpolationSpeed * deltaTime, 1.0); currentSizes[i] = THREE.MathUtils.lerp(currentSizes[i], targetSize, sizeLerpFactor); sizes[i] = currentSizes[i]; } // Update distant stars (less affected by cursor) const distantPositions = distantStarGeometry.attributes.position.array; const distantSizes = distantStarGeometry.attributes.size.array; for (let i = 0; i < distantStarCount; i++) { const i3 = i * 3; const origX = distantOriginalPositions[i3]; const origY = distantOriginalPositions[i3 + 1]; const origZ = distantOriginalPositions[i3 + 2]; // Gentler movement for distant stars const offsetX = Math.sin(time * 0.5 + i * 0.005) * movementAmplitude * 0.3; const offsetY = Math.cos(time * 0.3 + i * 0.008) * movementAmplitude * 0.3; const offsetZ = Math.sin(time * 0.4 + i * 0.006) * movementAmplitude * 0.3; let targetX = origX + offsetX; let targetY = origY + offsetY; let targetZ = origZ + offsetZ; // Weaker cursor repulsion for distant stars if (mouse) { const dx = targetX - mouseWorldPos.x; const dy = targetY - mouseWorldPos.y; const dz = targetZ - mouseWorldPos.z; const distance = Math.sqrt(dx * dx + dy * dy + dz * dz); if (distance < repulsionRadius * 1.5 && distance > 0) { const force = (1 - distance / (repulsionRadius * 1.5)) * repulsionStrength * 0.3; const nx = dx / distance; const ny = dy / distance; const nz = dz / distance; targetX += nx * force; targetY += ny * force; targetZ += nz * force; } } // Smooth interpolation for positions const currentX = distantCurrentPositions[i3]; const currentY = distantCurrentPositions[i3 + 1]; const currentZ = distantCurrentPositions[i3 + 2]; const lerpFactor = Math.min(interpolationSpeed * deltaTime * 0.7, 1.0); distantCurrentPositions[i3] = THREE.MathUtils.lerp(currentX, targetX, lerpFactor); distantCurrentPositions[i3 + 1] = THREE.MathUtils.lerp(currentY, targetY, lerpFactor); distantCurrentPositions[i3 + 2] = THREE.MathUtils.lerp(currentZ, targetZ, lerpFactor); distantPositions[i3] = distantCurrentPositions[i3]; distantPositions[i3 + 1] = distantCurrentPositions[i3 + 1]; distantPositions[i3 + 2] = distantCurrentPositions[i3 + 2]; // NEW: Size effect for distant stars (weaker) let targetSize = distantOriginalSizes[i]; if (mouse) { const finalX = distantCurrentPositions[i3]; const finalY = distantCurrentPositions[i3 + 1]; const finalZ = distantCurrentPositions[i3 + 2]; const dx = finalX - mouseWorldPos.x; const dy = finalY - mouseWorldPos.y; const dz = finalZ - mouseWorldPos.z; const distance = Math.sqrt(dx * dx + dy * dy + dz * dz); if (distance < brightnessRadius * 1.2) { // Weaker effect for distant stars const proximityFactor = 1 - (distance / (brightnessRadius * 1.2)); const sizeMultiplier = 1 + (proximityFactor * (maxSizeMultiplier * 0.5 - 1)); targetSize = distantOriginalSizes[i] * sizeMultiplier; } } // Smooth interpolation for distant star sizes const sizeLerpFactor = Math.min(sizeInterpolationSpeed * deltaTime * 0.8, 1.0); distantCurrentSizes[i] = THREE.MathUtils.lerp(distantCurrentSizes[i], targetSize, sizeLerpFactor); distantSizes[i] = distantCurrentSizes[i]; } // Mark geometry for update starGeometry.attributes.position.needsUpdate = true; starGeometry.attributes.size.needsUpdate = true; distantStarGeometry.attributes.position.needsUpdate = true; distantStarGeometry.attributes.size.needsUpdate = true; // Subtle twinkling starMaterial.opacity = 0.6 + Math.sin(time * 2) * 0.2; distantStarMaterial.opacity = 0.3 + Math.sin(time * 1.5 + 1) * 0.1; } return { stars, distantStars, animateStars, starMaterial, distantStarMaterial }; }