| 627 | |
| 628 | // --- PARTICLE SYSTEM --- |
| 629 | function createParticleSystem() { |
| 630 | const geometry = new THREE.BufferGeometry(); |
| 631 | const positions = new Float32Array(params.particleCount * 3); |
| 632 | const colors = new Float32Array(params.particleCount * 3); |
| 633 | const sizes = new Float32Array(params.particleCount); |
| 634 | |
| 635 | const initialPattern = patterns[0]; |
| 636 | const initialPalette = colorPalettes[0]; |
| 637 | |
| 638 | for (let i = 0; i < params.particleCount; i++) { |
| 639 | |
| 640 | const pos = initialPattern(i, params.particleCount); |
| 641 | positions[i * 3] = pos.x; |
| 642 | positions[i * 3 + 1] = pos.y; |
| 643 | positions[i * 3 + 2] = pos.z; |
| 644 | |
| 645 | const baseColor = initialPalette[0]; |
| 646 | const variation = 1.0; // Add variation |
| 647 | |
| 648 | colors[i * 3] = baseColor.r * variation; |
| 649 | colors[i * 3 + 1] = baseColor.g * variation; |
| 650 | colors[i * 3 + 2] = baseColor.b * variation; |
| 651 | |
| 652 | sizes[i] = 1.0; // Assign individual size variation |
| 653 | } |
| 654 | |
| 655 | geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); |
| 656 | geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); |
| 657 | geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1)); // Store base sizes |
| 658 | geometry.userData.currentColors = new Float32Array(colors); // Store initial colors for transitions |
| 659 | |
| 660 | const material = new THREE.PointsMaterial({ |
| 661 | size: params.particleSize, |
| 662 | vertexColors: true, |
| 663 | transparent: true, |
| 664 | opacity: 0.5, |
| 665 | blending: THREE.AdditiveBlending, |
| 666 | sizeAttenuation: true, // Make distant particles smaller |
| 667 | |
| 668 | //map: createParticleTexture() |
| 669 | // depthWrite: false // Often needed with AdditiveBlending if particles overlap strangely |
| 670 | }); |
| 671 | return new THREE.Points(geometry, material); |
| 672 | |
| 673 | } |