function resize() { let vw = window.innerWidth; let vh = window.innerHeight; let scale = Math.min(vw / refW, vh / refH); canvas.style.width = vw + 'px'; canvas.style.height = vh + 'px'; canvas.width = vw * dpr; canvas.height = vh * dpr; ctx.setTransform(dpr * scale, 0, 0, dpr * scale, 0, 0); cx = refW / 2; cy = refH / 2; } window.addEventListener('resize', resize); resize();
function leftAt(y) { let frac; if (y < 0) { frac = (y + hh) / hh; } else { frac = (hh - y) / hh; } let w = max_w - (max_w - min_w) * frac; return -w / 2; } function rightAt(y) { return -leftAt(y); }
function init() { particles = []; let attempts = 0; while (particles.length < num_particles && attempts < 10000) { attempts++; let y = -hh + Math.random() * (hh - r * 2); let l = leftAt(y); let rt = rightAt(y); let rang = rt - l - 2 * r; if (rang <= 0) continue; let x = l + r + Math.random() * rang; let overlap = false; for (let p of particles) { let dx = x - p.x; let dy = y - p.y; if (dx * dx + dy * dy < 4 * r * r) { overlap = true; break; } } if (!overlap) { particles.push({x, y, vx: 0, vy: 0}); } } } init();
function update() { let angle = currentAngle; let gx_val = g * Math.sin(angle); let gy_val = g * Math.cos(angle); particles.forEach(p => { p.vx += gx_val; p.vy += gy_val; p.x += p.vx; p.y += p.vy; }); particles.forEach(p => { let l = leftAt(p.y); let rt = rightAt(p.y); if (p.x - r < l) { p.x = l + r; p.vx = -p.vx * elasticity; } else if (p.x + r > rt) { p.x = rt - r; p.vx = -p.vx * elasticity; } if (p.y - r < -hh) { p.y = -hh + r; p.vy = -p.vy * elasticity; p.vx *= 0.8; } else if (p.y + r > hh) { p.y = hh - r; p.vy = -p.vy * elasticity; p.vx *= 0.8; } }); for (let i = 0; i < particles.length; i++) { for (let j = i + 1; j < particles.length; j++) { let pi = particles[i]; let pj = particles[j]; let dx = pj.x - pi.x; let dy = pj.y - pi.y; let dist = Math.sqrt(dx * dx + dy * dy); if (dist < 2 * r && dist > 0) { let nx = dx / dist; let ny = dy / dist; let overlap = 2 * r - dist; let push = overlap / 2; pi.x -= nx * push; pi.y -= ny * push; pj.x += nx * push; pj.y += ny * push; let vn_i = pi.vx * nx + pi.vy * ny; let vn_j = pj.vx * nx + pj.vy * ny; let impulse = (1 + elasticity) * (vn_j - vn_i) / 2; pi.vx += impulse * nx; pi.vy += impulse * ny; pj.vx -= impulse * nx; pj.vy -= impulse * ny; } } } particles.forEach(p => { if (Math.abs(p.vx) < 0.01) p.vx = 0; if (Math.abs(p.vy) < 0.01) p.vy = 0; }); }
function draw() { ctx.clearRect(0, 0, refW, refH); ctx.save(); ctx.translate(cx, cy); ctx.rotate(currentAngle); ctx.translate(-cx, -cy); ctx.strokeStyle = 'black'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(cx + leftAt(-hh), cy + (-hh)); ctx.lineTo(cx + leftAt(0), cy + 0); ctx.lineTo(cx + leftAt(hh), cy + hh); ctx.lineTo(cx + rightAt(hh), cy + hh); ctx.lineTo(cx + rightAt(0), cy + 0); ctx.lineTo(cx + rightAt(-hh), cy + (-hh)); ctx.closePath(); ctx.stroke(); ctx.fillStyle = 'sandybrown'; particles.forEach(p => { ctx.beginPath(); ctx.arc(cx + p.x, cy + p.y, r, 0, 2 * Math.PI); ctx.fill(); }); ctx.restore(); }
function anim() { if (rotating) { let t = (Date.now() - startTime) / duration; if (t >= 1) { t = 1; rotating = false; currentAngle = targetAngle; } else { currentAngle = startAngle + t * (targetAngle - startAngle); } } update(); draw(); requestAnimationFrame(anim); } anim();
canvas.addEventListener('click', () => { if (!rotating) { rotating = true; startTime = Date.now(); startAngle = currentAngle; targetAngle = startAngle + Math.PI; } }); </script>
</body> </html>_