토론

다중 볼 칠각형 물리 시뮬레이션

Wikiprompt, 무료 프롬프트 백과사전에서

Alex Prompter

2025년 8월 15일

다중 볼 칠각형 물리 시뮬레이션 I'll create a single-file HTML/CSS/JS simulation with 20 numbered, colored balls bouncing inside a rotating heptagon with gravity and wall collisions. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Bouncing Balls in Rotating Heptagon</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #1a1a2e; display: flex; justify-content: center; align-items: center; min-height: 100vh; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; overflow: hidden; } canvas { display: block; border-radius: 12px; box-shadow: 0 0 30px rgba(0, 0, 0, 0.5); background: #16213e; } .info { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); color: #e0e0e0; font-size: 14px; background: rgba(0, 0, 0, 0.6); padding: 8px 16px; border-radius: 20px; letter-spacing: 0.5px; backdrop-filter: blur(4px); } </style> </head> <body> <canvas id="simulationCanvas" width="800" height="600"></canvas> <div class="info">20 balls • rotating heptagon • gravity & collisions</div> <script> (function() { const canvas = document.getElementById('simulationCanvas'); const ctx = canvas.getContext('2d'); const W = canvas.width; const H = canvas.height; // Heptagon parameters const heptagonRadius = 260; const heptagonCenterX = W / 2; const heptagonCenterY = H / 2; const heptagonRotationSpeed = 0.002; // radians per frame let heptagonAngle = 0; // Gravity const gravity = 0.15; // Ball properties const ballCount = 20; const ballRadius = 12; const ballColors = [ '#e74c3c', '#e67e22', '#f1c40f', '#2ecc71', '#1abc9c', '#3498db', '#9b59b6', '#e84393', '#fd79a8', '#00b894', '#0984e3', '#6c5ce7', '#fdcb6e', '#e17055', '#00cec9', '#fab1a0', '#74b9ff', '#a29bfe', '#ff7675', '#55efc4' ]; // Ball class class Ball { constructor(id) { this.id = id; this.radius = ballRadius; this.x = heptagonCenterX + (Math.random() - 0.5) * 200; this.y = heptagonCenterY + (Math.random() - 0.5) * 200; this.vx = (Math.random() - 0.5) * 4; this.vy = (Math.random() - 0.5) * 4; this.color = ballColors[id % ballColors.length]; this.mass = 1; } update() { // Apply gravity this.vy += gravity; // Move this.x += this.vx; this.y += this.vy; // Collision with heptagon walls this.collideWithHeptagon(); } collideWithHeptagon() { // Get heptagon vertices in world coordinates const vertices = getHeptagonVertices(); // For each edge, check collision for (let i = 0; i < vertices.length; i++) { const v1 = vertices[i]; const v2 = vertices[(i + 1) % vertices.length]; // Find closest point on segment to ball center const closest = closestPointOnSegment(this.x, this.y, v1.x, v1.y, v2.x, v2.y); const dx = this.x - closest.x; const dy = this.y - closest.y; const distSq = dx * dx + dy * dy; if (distSq < this.radius * this.radius) { const dist = Math.sqrt(distSq); const nx = dx / dist; const ny = dy / dist; // Push ball out of wall this.x = closest.x + nx * this.radius; this.y = closest.y + ny * this.radius; // Reflect velocity const dot = this.vx * nx + this.vy * ny; if (dot < 0) { this.vx -= 2 * dot * nx; this.vy -= 2 * dot * ny; } // Small damping for stability this.vx *= 0.98; this.vy *= 0.98; } } } draw() { // Draw ball ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); ctx.fillStyle = this.color; ctx.fill(); ctx.strokeStyle = 'rgba(0,0,0,0.3)'; ctx.lineWidth = 1; ctx.stroke(); // Draw number ctx.fillStyle = '#ffffff'; ctx.font = 'bold 10px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(this.id + 1, this.x, this.y + 1); } } // Helper functions function getHeptagonVertices() { const vertices = []; const sides = 7; for (let i = 0; i < sides; i++) { const angle = heptagonAngle + (i * 2 * Math.PI) / sides; const x = heptagonCenterX + heptagonRadius * Math.cos(angle); const y = heptagonCenterY + heptagonRadius * Math.sin(angle); vertices.push({ x, y }); } return vertices; } function closestPointOnSegment(px, py, x1, y1, x2, y2) { const dx = x2 - x1; const dy = y2 - y1; const lenSq = dx * dx + dy * dy; if (lenSq === 0) return { x: x1, y: y1 }; let t = ((px - x1) * dx + (py - y1) * dy) / lenSq; t = Math.max(0, Math.min(1, t)); return { x: x1 + t * dx, y: y1 + t * dy }; } // Ball-to-ball collision function handleBallCollisions(balls) { for (let i = 0; i < balls.length; i++) { for (let j = i + 1; j < balls.length; j++) { const a = balls[i]; const b = balls[j]; const dx = b.x - a.x; const dy = b.y - a.y; const distSq = dx * dx + dy * dy; const minDist = a.radius + b.radius; if (distSq < minDist * minDist && distSq > 0.0001) { const dist = Math.sqrt(distSq); const nx = dx / dist; const ny = dy / dist; // Separate balls const overlap = minDist - dist; a.x -= nx * overlap / 2; a.y -= ny * overlap / 2; b.x += nx * overlap / 2; b.y += ny * overlap / 2; // Elastic collision (equal mass) const dvx = a.vx - b.vx; const dvy = a.vy - b.vy; const dot = dvx * nx + dvy * ny; if (dot > 0) { a.vx -= dot * nx; a.vy -= dot * ny; b.vx += dot * nx; b.vy += dot * ny; } } } } } // Initialize balls const balls = []; for (let i = 0; i < ballCount; i++) { balls.push(new Ball(i)); } // Draw heptagon function drawHeptagon() { const vertices = getHeptagonVertices(); ctx.beginPath(); ctx.moveTo(vertices[0].x, vertices[0].y); for (let i = 1; i < vertices.length; i++) { ctx.lineTo(vertices[i].x, vertices[i].y); } ctx.closePath(); // Fill with subtle gradient const gradient = ctx.createRadialGradient( heptagonCenterX, heptagonCenterY, 50, heptagonCenterX, heptagonCenterY, heptagonRadius ); gradient.addColorStop(0, 'rgba(255, 255, 255, 0.05)'); gradient.addColorStop(1, 'rgba(255, 255, 255, 0.02)'); ctx.fillStyle = gradient; ctx.fill(); // Stroke ctx.strokeStyle = '#4a69bd'; ctx.lineWidth = 3; ctx.stroke(); // Draw vertices as small dots ctx.fillStyle = '#4a69bd'; vertices.forEach(v => { ctx.beginPath(); ctx.arc(v.x, v.y, 4, 0, Math.PI * 2); ctx.fill(); }); } // Animation loop function animate() { // Clear canvas ctx.clearRect(0, 0, W, H); // Rotate heptagon heptagonAngle += heptagonRotationSpeed; // Update balls balls.forEach(ball => ball.update()); // Handle ball-to-ball collisions handleBallCollisions(balls); // Draw heptagon drawHeptagon(); // Draw balls balls.forEach(ball => ball.draw()); requestAnimationFrame(animate); } // Start animation animate(); })(); </script> </body> </html> ``` This simulation features: - 20 numbered balls with distinct colors - A rotating heptagon (7-sided polygon) that spins continuously - Gravity pulling balls downward - Collision detection with the heptagon's walls (reflection and position correction) - Ball-to-ball elastic collisions - Smooth animation using requestAnimationFrame - Visual styling with a dark theme and glowing heptagon edges The balls bounce realistically inside the rotating heptagon, creating an engaging physics simulation. The rotation adds dynamic complexity as the walls move, causing balls to interact with the changing geometry.

관련 미디어

프롬프트 내용저장

🌐
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>spinning heptagon · 20 bouncing balls</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #0b0e1a; min-height: 100vh; display: flex; justify-content: center; align-items: center; font-family: 'Segoe UI', Roboto, system-ui, sans-serif; } .canvas-wrapper { background: #11162b; border-radius: 32px; padding: 12px; box-shadow: 0 20px 40px rgba(0,0,0,0.6); } canvas { display: block; width: 800px; height: 800px; border-radius: 24px; background: radial-gradient(circle at 30% 30%, #1b2340, #0c1020); box-shadow: inset 0 0 40px rgba(0,0,0,0.7); } </style> </head> <body> <div class="canvas-wrapper"> <canvas id="sceneCanvas" width="800" height="800"></canvas> </div> <script> (function() { const canvas = document.getElementById('sceneCanvas'); const ctx = canvas.getContext('2d'); // ---------- heptagon parameters ---------- const centerX = 400; const centerY = 400; const baseRadius = 340; // distance from center to vertex const rotationSpeed = 0.0012; // radians per frame (slow spin) // ---------- ball parameters ---------- const BALL_COUNT = 20; const GRAVITY = 0.15; // px/frame^2 const BALL_RADIUS = 13; // base radius (visual) const WALL_BOUNCE = 0.72; // energy loss on wall collision const FLOOR_BOUNCE = 0.68; // not used – walls only, but keep for safety // unique colors (vivid, distinguishable) const colorPalette = [ '#FF6B6B', '#FFA07A', '#FFD93D', '#6BCB77', '#4D96FF', '#9B5DE5', '#F15BB5', '#FEE440', '#00BBF9', '#00F5D4', '#F95738', '#8AC926', '#6A4C93', '#FF9F1C', '#2EC4B6', '#E71D36', '#A3E048', '#3A86FF', '#FB5607', '#8338EC' ]; // ---------- ball factory ---------- class Ball { constructor(id, x, y, vx, vy, color) { this.id = id; this.x = x; this.y = y; this.vx = vx; this.vy = vy; this.radius = BALL_RADIUS; this.color = color; this.mass = 1; // equal mass } // apply gravity update() { this.vy += GRAVITY; this.x += this.vx; this.y += this.vy; } // draw ball with number label draw(ctx) { // shadow / glow ctx.shadowColor = this.color + '88'; ctx.shadowBlur = 14; ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI); ctx.fillStyle = this.color; ctx.fill(); ctx.shadowBlur = 0; // border ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 1.8; ctx.stroke(); // number label (white, bold, centered) ctx.font = 'bold 11px "Segoe UI", monospace'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillStyle = '#ffffff'; ctx.shadowColor = 'rgba(0,0,0,0.8)'; ctx.shadowBlur = 6; ctx.fillText(this.id, this.x, this.y + 1); ctx.shadowBlur = 0; } } // ---------- create 20 balls with random positions inside heptagon ---------- function randomInsideHeptagon(radius) { // simple rejection sampling (safe, fast enough) for (let i = 0; i < 200; i++) { const angle = Math.random() * 2 * Math.PI; const r = Math.random() * (radius * 0.8); // keep away from edges const x = centerX + r * Math.cos(angle); const y = centerY + r * Math.sin(angle); // rough check: inside heptagon (we'll refine with actual polygon check later) if (isPointInsideHeptagon(x, y, 0)) { // rotation 0 for initial placement return { x, y }; } } // fallback (should never happen) return { x: centerX, y: centerY }; } // heptagon vertices (with rotation offset) function getHeptagonVertices(rotation) { const vertices = []; for (let i = 0; i < 7; i++) { const angle = rotation + (i * 2 * Math.PI) / 7 - Math.PI / 2; // top vertex start const x = centerX + baseRadius * Math.cos(angle); const y = centerY + baseRadius * Math.sin(angle); vertices.push({ x, y }); } return vertices; } // point-in-polygon (ray casting) for heptagon function isPointInsideHeptagon(px, py, rotation) { const vertices = getHeptagonVertices(rotation); let inside = false; for (let i = 0, j = vertices.length - 1; i < vertices.length; j = i++) { const xi = vertices[i].x, yi = vertices[i].y; const xj = vertices[j].x, yj = vertices[j].y; if (((yi > py) !== (yj > py)) && (px < (xj - xi) * (py - yi) / (yj - yi) + xi)) { inside = !inside; } } return inside; } // ---------- initialize balls ---------- let balls = []; for (let i = 0; i < BALL_COUNT; i++) { const color = colorPalette[i % colorPalette.length]; const pos = randomInsideHeptagon(baseRadius * 0.7); // random velocity (moderate) const angle = Math.random() * 2 * Math.PI; const speed = 1.2 + Math.random() * 2.0; const vx = Math.cos(angle) * speed; const vy = Math.sin(angle) * speed - 1.0; // slight upward bias balls.push(new Ball(i + 1, pos.x, pos.y, vx, vy, color)); } // ---------- collision with rotating heptagon walls ---------- function handleWallCollisions(ball, rotation) { const vertices = getHeptagonVertices(rotation); const edges = []; for (let i = 0; i < vertices.length; i++) { const p1 = vertices[i]; const p2 = vertices[(i + 1) % vertices.length]; edges.push({ p1, p2 }); } // for each edge, find closest point on segment to ball center for (let edge of edges) { const { p1, p2 } = edge; const dx = p2.x - p1.x; const dy = p2.y - p1.y; const lenSq = dx * dx + dy * dy; if (lenSq === 0) continue; // projection of ball center onto line let t = ((ball.x - p1.x) * dx + (ball.y - p1.y) * dy) / lenSq; t = Math.max(0, Math.min(1, t)); // clamp to segment const closestX = p1.x + t * dx; const closestY = p1.y + t * dy; const distX = ball.x - closestX; const distY = ball.y - closestY; const dist = Math.hypot(distX, distY); if (dist < ball.radius) { // collision! compute normal (from edge to ball) let normalX = 0, normalY = 0; if (dist > 0.0001) { normalX = distX / dist; normalY = distY / dist; } else { // ball center exactly on edge – push outward (fallback) const midX = (p1.x + p2.x) / 2; const midY = (p1.y + p2.y) / 2; const toBallX = ball.x - midX; const toBallY = ball.y - midY; const len = Math.hypot(toBallX, toBallY); if (len > 0.0001) { normalX = toBallX / len; normalY = toBallY / len; } else { normalX = 0; normalY = -1; // fallback upward } } // reposition ball outside the wall ball.x = closestX + normalX * ball.radius; ball.y = closestY + normalY * ball.radius; // reflect velocity const dot = ball.vx * normalX + ball.vy * normalY; if (dot < 0) { // only reflect if moving toward wall ball.vx = ball.vx - (1 + WALL_BOUNCE) * dot * normalX; ball.vy = ball.vy - (1 + WALL_BOUNCE) * dot * normalY; } // small extra push to avoid sticking ball.x += normalX * 0.2; ball.y += normalY * 0.2; } } } // ---------- ball-to-ball collision (simple impulse) ---------- function handleBallCollisions() { for (let i = 0; i < balls.length; i++) { for (let j = i + 1; j < balls.length; j++) { const a = balls[i]; const b = balls[j]; const dx = b.x - a.x; const dy = b.y - a.y; const dist = Math.hypot(dx, dy); const minDist = a.radius + b.radius; if (dist < minDist && dist > 0.0001) { // normal const nx = dx / dist; const ny = dy / dist; // separate positions (equal mass) const overlap = (minDist - dist) / 2; a.x -= nx * overlap; a.y -= ny * overlap; b.x += nx * overlap; b.y += ny * overlap; // relative velocity const relVx = a.vx - b.vx; const relVy = a.vy - b.vy; const relVelNormal = relVx * nx + relVy * ny; // only resolve if approaching if (relVelNormal < 0) { const restitution = 0.9; // bouncy const impulse = -(1 + restitution) * relVelNormal / 2; // equal mass a.vx += impulse * nx; a.vy += impulse * ny; b.vx -= impulse * nx; b.vy -= impulse * ny; } } } } } // ---------- keep balls inside heptagon (safety net) ---------- function clampInsideHeptagon(ball, rotation) { if (!isPointInsideHeptagon(ball.x, ball.y, rotation)) { // push toward center const dx = centerX - ball.x; const dy = centerY - ball.y; const len = Math.hypot(dx, dy); if (len > 0.0001) { const pushX = (dx / len) * 2.0; const pushY = (dy / len) * 2.0; ball.x += pushX; ball.y += pushY; } else { ball.x = centerX + (Math.random() - 0.5) * 20; ball.y = centerY + (Math.random() - 0.5) * 20; } // also dampen velocity if outside ball.vx *= 0.9; ball.vy *= 0.9; } } // ---------- animation loop ---------- let rotation = 0; function animate() { // update rotation rotation += rotationSpeed; // update balls (gravity + move) for (let ball of balls) { ball.update(); } // collisions with rotating heptagon walls for (let ball of balls) { handleWallCollisions(ball, rotation); } // ball-to-ball collisions (multiple iterations for stability) for (let iter = 0; iter < 2; iter++) { handleBallCollisions(); } // safety: ensure all balls are inside heptagon (due to extreme edge cases) for (let ball of balls) { clampInsideHeptagon(ball, rotation); // also clamp velocity magnitude to avoid explosion const speed = Math.hypot(ball.vx, ball.vy); const maxSpeed = 12; if (speed > maxSpeed) { ball.vx = (ball.vx / speed) * maxSpeed; ball.vy = (ball.vy / speed) * maxSpeed; } } // ---------- draw everything ---------- ctx.clearRect(0, 0, 800, 800); // draw heptagon (translucent fill + bright edges) const vertices = getHeptagonVertices(rotation); ctx.beginPath(); ctx.moveTo(vertices[0].x, vertices[0].y); for (let i = 1; i < vertices.length; i++) { ctx.lineTo(vertices[i].x, vertices[i].y); } ctx.closePath(); ctx.fillStyle = 'rgba(60, 80, 140, 0.15)'; ctx.fill(); ctx.strokeStyle = '#b8c7ff'; ctx.lineWidth = 3.5; ctx.shadowColor = '#7f9fff'; ctx.shadowBlur = 20; ctx.stroke(); ctx.shadowBlur = 0; // draw vertex dots (optional) ctx.fillStyle = '#d0dcff'; for (let v of vertices) { ctx.beginPath(); ctx.arc(v.x, v.y, 5, 0, 2 * Math.PI); ctx.fill(); } // draw balls for (let ball of balls) { ball.draw(ctx); } requestAnimationFrame(animate); } // start animation animate(); })(); </script> </body> </html>

전체 프롬프트를 보려면 로그인하세요

Continue with:

By logging in, you agree to our Terms of Use and Privacy Policy

사용법

이 프롬프트는 coding와 함께 사용하도록 설계되었습니다. 위의 프롬프트 내용을 복사하여 원하는 AI 도구에 붙여넣으세요.

최상의 결과를 얻으려면 자리 표시자(대괄호 또는 대문자로 표시)를 특정 요구 사항으로 사용자 지정할 수 있습니다.

참고 자료

분류:coding| twitter| html| css

토론