Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Balls in Rotating Hexagon</title> | |
<style> | |
canvas { | |
border: 1px solid black; | |
display: block; | |
margin: auto; | |
} | |
</style> | |
</head> | |
<body> | |
<canvas id="myCanvas" width="800" height="600"></canvas> | |
<script> | |
const canvas = document.getElementById('myCanvas'); | |
const ctx = canvas.getContext('2d'); | |
// Ball class | |
class Ball { | |
constructor(x, y, radius, color) { | |
this.x = x; | |
this.y = y; | |
this.radius = radius; | |
this.color = color; | |
this.vx = Math.random() * 2 - 1; | |
this.vy = Math.random() * 2 - 1; | |
} | |
draw() { | |
ctx.beginPath(); | |
ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2); | |
ctx.fillStyle = this.color; | |
ctx.fill(); | |
ctx.closePath(); | |
} | |
move() { | |
this.x += this.vx; | |
this.y += this.vy; | |
this.vy += 0.05; // gravity | |
} | |
collideWithWall(hexagon) { | |
const vertices = hexagon.vertices; | |
for (let i = 0; i < vertices.length; i++) { | |
const p1 = vertices[i]; | |
const p2 = vertices[(i + 1) % vertices.length]; | |
if (this.lineCircle(p1.x, p1.y, p2.x, p2.y, this.x, this.y, this.radius)) { | |
const normal = {x: p2.y - p1.y, y: p1.x - p2.x}; | |
const distance = Math.sqrt(normal.x * normal.x + normal.y * normal.y); | |
normal.x /= distance; | |
normal.y /= distance; | |
const dotProduct = this.vx * normal.x + this.vy * normal.y; | |
this.vx -= 2 * dotProduct * normal.x; | |
this.vy -= 2 * dotProduct * normal.y; | |
// Move the ball outside the wall to avoid sticking | |
this.x += this.vx; | |
this.y += this.vy; | |
return; | |
} | |
} | |
} | |
lineCircle(x1, y1, x2, y2, cx, cy, r) { | |
const lineLength = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); | |
const dot = (((cx - x1) * (x2 - x1)) + ((cy - y1) * (y2 - y1))) / Math.pow(lineLength, 2); | |
const closestX = x1 + (dot * (x2 - x1)); | |
const closestY = y1 + (dot * (y2 - y1)); | |
if (this.distance(closestX, closestY, x1, y1) > lineLength || this.distance(closestX, closestY, x2, y2) > lineLength) { | |
return false; | |
} | |
const distance = this.distance(closestX, closestY, cx, cy); | |
return distance <= r; | |
} | |
distance(x1, y1, x2, y2) { | |
return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); | |
} | |
} | |
// Hexagon class | |
class Hexagon { | |
constructor(centerX, centerY, radius) { | |
this.centerX = centerX; | |
this.centerY = centerY; | |
this.radius = radius; | |
this.rotation = 0; | |
this.vertices = this.calculateVertices(); | |
} | |
calculateVertices() { | |
const vertices = []; | |
for (let i = 0; i < 6; i++) { | |
const angle = Math.PI / 3 * i + this.rotation; | |
vertices.push({ | |
x: this.centerX + this.radius * Math.cos(angle), | |
y: this.centerY + this.radius * Math.sin(angle) | |
}); | |
} | |
return vertices; | |
} | |
draw() { | |
ctx.beginPath(); | |
for (let vertex of this.vertices) { | |
ctx.lineTo(vertex.x, vertex.y); | |
} | |
ctx.closePath(); | |
ctx.strokeStyle = "black"; | |
ctx.stroke(); | |
} | |
update() { | |
this.rotation += 0.01; | |
this.vertices = this.calculateVertices(); | |
} | |
} | |
const hexagon = new Hexagon(canvas.width / 2, canvas.height / 2, 200); | |
const balls = []; | |
const colors = ["red", "blue", "green", "yellow", "purple", "orange", "pink", "brown", "cyan", "magenta"]; | |
for (let i = 0; i < 10; i++) { | |
const x = canvas.width / 2 + Math.random() * 100 - 50; | |
const y = canvas.height / 2 + Math.random() * 100 - 50; | |
balls.push(new Ball(x, y, 10, colors[i])); | |
} | |
function animate() { | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
hexagon.update(); | |
hexagon.draw(); | |
for (let ball of balls) { | |
ball.move(); | |
ball.collideWithWall(hexagon); | |
ball.draw(); | |
} | |
requestAnimationFrame(animate); | |
} | |
animate(); | |
</script> | |
</body> | |
</html> |