| | import * as THREE from 'three'; |
| | import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; |
| |
|
| | const config = { |
| | pointCount: 800, |
| | radius: 2, |
| | pulseSpeed: 1.2, |
| | amplitude: 0.5, |
| | color: { |
| | primary: [0.5, 0.3, 1.0], |
| | secondary: [0.8, 0.2, 1.0], |
| | accent: [0.3, 0.5, 1.0] |
| | } |
| | }; |
| |
|
| | let scene, camera, renderer, controls; |
| | let pointCloud, particles; |
| | let clock; |
| |
|
| | init(); |
| | animate(); |
| |
|
| | function init() { |
| | scene = new THREE.Scene(); |
| | scene.background = new THREE.Color(0x0d0d2b); |
| |
|
| | const canvas = document.querySelector('#webgl'); |
| | renderer = new THREE.WebGLRenderer({ |
| | canvas, |
| | antialias: true, |
| | alpha: true |
| | }); |
| | renderer.setSize(window.innerWidth, window.innerHeight); |
| | renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); |
| |
|
| | camera = new THREE.PerspectiveCamera( |
| | 75, |
| | window.innerWidth / window.innerHeight, |
| | 0.1, |
| | 50 |
| | ); |
| | camera.position.set(0, 0, 5); |
| |
|
| | controls = new OrbitControls(camera, canvas); |
| | controls.enableDamping = true; |
| | controls.dampingFactor = 0.05; |
| | controls.rotateSpeed = 0.5; |
| |
|
| | createParticles(); |
| | setupLighting(); |
| | setupEventListeners(); |
| | } |
| |
|
| | function createParticles() { |
| | const positions = new Float32Array(config.pointCount * 3); |
| | const colors = new Float32Array(config.pointCount * 3); |
| | const sizes = new Float32Array(config.pointCount); |
| | |
| | particles = { |
| | positions: new Float32Array(config.pointCount), |
| | targetPositions: new Float32Array(config.pointCount), |
| | speeds: new Float32Array(config.pointCount), |
| | phases: new Float32Array(config.pointCount) |
| | }; |
| |
|
| | for (let i = 0; i < config.pointCount; i++) { |
| | const index = i * 3; |
| | const phi = Math.acos(-1 + (2 * i) / config.pointCount); |
| | const theta = Math.sqrt(config.pointCount * Math.PI) * phi; |
| | |
| | const x = config.radius * Math.sin(phi) * Math.cos(theta); |
| | const y = config.radius * Math.sin(phi) * Math.sin(theta); |
| | const z = config.radius * Math.cos(phi); |
| | |
| | positions[index] = x; |
| | positions[index + 1] = y; |
| | positions[index + 2] = z; |
| |
|
| | const dist = Math.sqrt(x * x + y * y + z * z) / config.radius; |
| | const r = THREE.MathUtils.lerp(config.color.primary[0], config.color.accent[0], dist); |
| | const g = THREE.MathUtils.lerp(config.color.primary[1], config.color.secondary[1], dist); |
| | const b = THREE.MathUtils.lerp(config.color.primary[2], 1.0, dist); |
| | |
| | colors[index] = r; |
| | colors[index + 1] = g; |
| | colors[index + 2] = b; |
| | |
| | sizes[i] = Math.random() * 0.1 + 0.05; |
| | |
| | particles.positions[i] = 0; |
| | particles.targetPositions[i] = 0; |
| | particles.speeds[i] = Math.random() * 0.5 + 0.5; |
| | particles.phases[i] = Math.random() * Math.PI * 2; |
| | } |
| |
|
| | const geometry = new THREE.BufferGeometry(); |
| | geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); |
| | geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); |
| | geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1)); |
| |
|
| | const material = new THREE.PointsMaterial({ |
| | size: 0.1, |
| | vertexColors: true, |
| | sizeAttenuation: true, |
| | transparent: true, |
| | opacity: 0.9, |
| | blending: THREE.AdditiveBlending |
| | }); |
| |
|
| | pointCloud = new THREE.Points(geometry, material); |
| | scene.add(pointCloud); |
| |
|
| | clock = new THREE.Clock(); |
| | scheduleNextPulse(); |
| | } |
| |
|
| | function setupLighting() { |
| | const ambientLight = new THREE.AmbientLight(0x404060, 0.5); |
| | scene.add(ambientLight); |
| |
|
| | const pointLight = new THREE.PointLight(0x7f5af0, 1, 10); |
| | pointLight.position.set(2, 3, 4); |
| | scene.add(pointLight); |
| | } |
| |
|
| | function setupEventListeners() { |
| | window.addEventListener('resize', () => { |
| | camera.aspect = window.innerWidth / window.innerHeight; |
| | camera.updateProjectionMatrix(); |
| | renderer.setSize(window.innerWidth, window.innerHeight); |
| | }); |
| | } |
| |
|
| | function scheduleNextPulse() { |
| | const pulseDelay = Math.random() * 3000 + 1500; |
| | setTimeout(() => { |
| | triggerPulse(); |
| | }, pulseDelay); |
| | } |
| |
|
| | function triggerPulse() { |
| | const waveCenter = Math.floor(Math.random() * config.pointCount); |
| | |
| | const positions = pointCloud.geometry.attributes.position.array; |
| | |
| | for (let i = 0; i < config.pointCount; i++) { |
| | const index = i * 3; |
| | const x = positions[index]; |
| | const y = positions[index + 1]; |
| | const z = positions[index + 2]; |
| | |
| | const distToCenter = Math.sqrt( |
| | (x - positions[waveCenter * 3]) ** 2 + |
| | (y - positions[waveCenter * 3 + 1]) ** 2 + |
| | (z - positions[waveCenter * 3 + 2]) ** 2 |
| | ); |
| | |
| | const normalizedDist = distToCenter / (config.radius * 2); |
| | const waveFactor = 1 - Math.min(Math.max(normalizedDist, 0), 1); |
| | |
| | particles.targetPositions[i] = config.amplitude * waveFactor; |
| | } |
| | |
| | scheduleNextPulse(); |
| | } |
| |
|
| | function updateParticles(delta) { |
| | const positions = pointCloud.geometry.attributes.position.array; |
| | const originalPositions = new Float32Array(positions); |
| | |
| | for (let i = 0; i < config.pointCount; i++) { |
| | const index = i * 3; |
| | |
| | particles.positions[i] += (particles.targetPositions[i] - particles.positions[i]) * delta * particles.speeds[i]; |
| | |
| | const pulseEffect = particles.positions[i] * Math.sin(clock.getElapsedTime() * particles.speeds[i] + particles.phases[i]); |
| | |
| | const displacement = pulseEffect; |
| | |
| | positions[index] = originalPositions[index] * (1 + displacement); |
| | positions[index + 1] = originalPositions[index + 1] * (1 + displacement); |
| | positions[index + 2] = originalPositions[index + 2] * (1 + displacement); |
| | |
| | particles.targetPositions[i] *= 0.95; |
| | } |
| | |
| | pointCloud.geometry.attributes.position.needsUpdate = true; |
| | } |
| |
|
| | function animate() { |
| | const delta = Math.min(clock.getDelta(), 0.1); |
| | |
| | controls.update(); |
| | updateParticles(delta); |
| | |
| | renderer.render(scene, camera); |
| | requestAnimationFrame(animate); |
| | } |