329 lines
12 KiB
JavaScript
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
|
|
};
|
|
}
|