|
41 | 41 | // 2.1 - PARTICLE INITIALIZATION |
42 | 42 | // Generate random radius within configured range |
43 | 43 | this.radius = randomInt(config.particleRadiusMin, config.particleRadiusMax); |
44 | | - |
| 44 | + |
45 | 45 | // Set random position ensuring particle is fully inside canvas bounds |
46 | 46 | this.x = random(this.radius, canvasWidth - this.radius); |
47 | 47 | this.y = random(this.radius, canvasHeight - this.radius); |
48 | | - |
| 48 | + |
49 | 49 | // Set random initial velocity with configured factor |
50 | 50 | this.vx = random(-0.5, 0.5) * config.particleVelocityFactor; |
51 | 51 | this.vy = random(-0.5, 0.5) * config.particleVelocityFactor; |
52 | | - |
| 52 | + |
53 | 53 | // Initialize push forces (for mouse interaction) to zero |
54 | 54 | this.pushX = 0; |
55 | 55 | this.pushY = 0; |
56 | | - |
| 56 | + |
57 | 57 | // Copy friction value from config |
58 | 58 | this.friction = config.particlePushFriction; |
59 | 59 | } |
|
75 | 75 | this.x = this.radius; // Reposition to boundary |
76 | 76 | this.vx *= -1; // Reverse velocity (bounce) |
77 | 77 | this.pushX *= -0.5; // Reduce and reverse push force |
78 | | - } |
| 78 | + } |
79 | 79 | // Right boundary collision |
80 | 80 | else if (this.x > canvasWidth - this.radius) { |
81 | 81 | this.x = canvasWidth - this.radius; |
82 | 82 | this.vx *= -1; |
83 | 83 | this.pushX *= -0.5; |
84 | 84 | } |
85 | | - |
| 85 | + |
86 | 86 | // Top boundary collision |
87 | 87 | if (this.y < this.radius) { |
88 | 88 | this.y = this.radius; |
89 | 89 | this.vy *= -1; |
90 | 90 | this.pushY *= -0.5; |
91 | | - } |
| 91 | + } |
92 | 92 | // Bottom boundary collision |
93 | 93 | else if (this.y > canvasHeight - this.radius) { |
94 | 94 | this.y = canvasHeight - this.radius; |
|
123 | 123 | console.error("Canvas element not found:", canvasSelector); |
124 | 124 | return; |
125 | 125 | } |
126 | | - |
| 126 | + |
127 | 127 | // Get 2D rendering context |
128 | 128 | this.ctx = this.canvas.getContext("2d"); |
129 | | - |
| 129 | + |
130 | 130 | // Initialize empty particle array |
131 | 131 | this.particles = []; |
132 | | - |
| 132 | + |
133 | 133 | // Initialize canvas dimensions (will be set properly in handleResize) |
134 | 134 | this.canvasWidth = 0; |
135 | 135 | this.canvasHeight = 0; |
|
191 | 191 | applyGradientStyle() { |
192 | 192 | // Create gradient from top-left to bottom-right |
193 | 193 | const gradient = this.ctx.createLinearGradient(0, 0, this.canvasWidth, this.canvasHeight); |
194 | | - |
| 194 | + |
195 | 195 | // Calculate step size for evenly distributing colors |
196 | 196 | const step = 1 / (config.gradientColors.length - 1); |
197 | | - |
| 197 | + |
198 | 198 | // Add each color at its calculated position |
199 | 199 | config.gradientColors.forEach((color, index) => { |
200 | 200 | gradient.addColorStop(Math.min(index * step, 1.0), color); // Ensure stop is <= 1 |
201 | 201 | }); |
202 | | - |
| 202 | + |
203 | 203 | // Apply gradient to both stroke (lines) and fill (particles) |
204 | 204 | this.ctx.strokeStyle = gradient; |
205 | 205 | this.ctx.fillStyle = gradient; |
|
208 | 208 | // ==================================================== |
209 | 209 | // STEP 4: PARTICLE MANAGEMENT |
210 | 210 | // ==================================================== |
211 | | - |
| 211 | + |
212 | 212 | // 4.1 - PARTICLE CREATION |
213 | 213 | // Creates particles based on screen size |
214 | 214 | createParticles() { |
215 | 215 | // Clear existing particles |
216 | 216 | this.particles = []; |
217 | | - |
| 217 | + |
218 | 218 | // Calculate particle count based on screen width |
219 | 219 | const count = this.canvasWidth > config.smallScreenWidthThreshold |
220 | 220 | ? config.particleBaseCount * config.particleCountLargeScreenFactor |
|
224 | 224 | for (let i = 0; i < count; i++) { |
225 | 225 | this.particles.push(new Particle(this.canvasWidth, this.canvasHeight)); |
226 | 226 | } |
227 | | - |
| 227 | + |
228 | 228 | // Initialize mouse position off-screen until first mouse movement |
229 | 229 | this.mouse.x = -config.mouseInteractionRadius * 2; |
230 | 230 | this.mouse.y = -config.mouseInteractionRadius * 2; |
|
241 | 241 | // Calculate vector from mouse to particle |
242 | 242 | const dx = particle.x - this.mouse.x; |
243 | 243 | const dy = particle.y - this.mouse.y; |
244 | | - |
| 244 | + |
245 | 245 | // Calculate distance using hypot (Pythagoras) |
246 | 246 | const distance = Math.hypot(dx, dy); |
247 | 247 |
|
|
250 | 250 | // Calculate unit vector for force direction |
251 | 251 | const forceDirectionX = dx / distance; |
252 | 252 | const forceDirectionY = dy / distance; |
253 | | - |
| 253 | + |
254 | 254 | // Calculate force magnitude - stronger near mouse, weaker at edge |
255 | 255 | const forceMagnitude = (1 - distance / this.mouse.radius); |
256 | 256 |
|
|
278 | 278 | if (connections >= config.connectionMaxPeers) break; |
279 | 279 |
|
280 | 280 | const p2 = this.particles[j]; |
281 | | - |
| 281 | + |
282 | 282 | // Calculate squared distance between particles |
283 | 283 | const dx = p1.x - p2.x; |
284 | 284 | const dy = p1.y - p2.y; |
|
287 | 287 | // If particles are close enough, draw connection |
288 | 288 | if (distSq < maxDistSq) { |
289 | 289 | this.ctx.save(); // Save current context state |
290 | | - |
| 290 | + |
291 | 291 | // Calculate actual distance for opacity |
292 | 292 | const distance = Math.sqrt(distSq); |
293 | | - |
| 293 | + |
294 | 294 | // Calculate opacity based on distance - closer = more opaque |
295 | 295 | const opacity = (1 - (distance / config.connectionMaxDistance)) * config.connectionOpacityFactor; |
296 | 296 | this.ctx.globalAlpha = Math.max(0, Math.min(opacity, 1)); // Clamp to valid range |
|
300 | 300 | this.ctx.moveTo(p1.x, p1.y); |
301 | 301 | this.ctx.lineTo(p2.x, p2.y); |
302 | 302 | this.ctx.stroke(); |
303 | | - |
| 303 | + |
304 | 304 | this.ctx.restore(); // Restore context state |
305 | 305 | connections++; // Increment connection count |
306 | 306 | } |
|
0 commit comments