yp-rubix/src/starfield.js
2025-09-04 09:06:21 +05:30

329 lines
12 KiB
JavaScript

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
};
}