
Balls bouncing inside a spinning hexagon
Prompt
Create an animation in JS with 10 balls bouncing inside a spinning hexagon.
A system prompt was added to support web rendering
# Bouncing Balls in a Spinning Hexagon
I'll create an animation with 10 balls bouncing inside a rotating hexagon using pure JavaScript and Canvas API.
```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 Spinning Hexagon</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, #1a1a2e, #16213e);
overflow: hidden;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.container {
position: relative;
width: 800px;
max-width: 95%;
text-align: center;
}
canvas {
background: rgba(0, 0, 0, 0.2);
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
}
h1 {
color: #fff;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
margin-bottom: 20px;
font-weight: 600;
letter-spacing: 1px;
}
.controls {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 20px;
flex-wrap: wrap;
}
button {
background: #4cc9f0;
color: #1a1a2e;
border: none;
padding: 10px 20px;
border-radius: 50px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
button:hover {
background: #4361ee;
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
}
button:active {
transform: translateY(1px);
}
.info {
color: rgba(255, 255, 255, 0.7);
margin-top: 15px;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<h1>Bouncing Balls in a Spinning Hexagon</h1>
<canvas id="canvas" width="700" height="500"></canvas>
<div class="controls">
<button id="speedUp">Speed Up</button>
<button id="slowDown">Slow Down</button>
<button id="reset">Reset</button>
</div>
<div class="info">Click anywhere to add more balls (max 20)</div>
</div>
<script>
// Canvas setup
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
// Hexagon properties
const hexRadius = 180;
let hexRotation = 0;
let rotationSpeed = 0.005;
// Ball properties
const balls = [];
const maxBalls = 20;
const colors = [
'#FF5252', '#FF4081', '#E040FB', '#7C4DFF',
'#536DFE', '#448AFF', '#40C4FF', '#18FFFF',
'#64FFDA', '#69F0AE', '#B2FF59', '#EEFF41'
];
// Initialize balls
function initBalls(count) {
for (let i = 0; i < count; i++) {
createBall();
}
}
// Create a new ball
function createBall() {
if (balls.length >= maxBalls) return;
const radius = 10 + Math.random() * 10;
const angle = Math.random() * Math.PI * 2;
const distance = Math.random() * (hexRadius - radius - 20);
const ball = {
x: centerX + Math.cos(angle) * distance,
y: centerY + Math.sin(angle) * distance,
radius: radius,
vx: (Math.random() - 0.5) * 4,
vy: (Math.random() - 0.5) * 4,
color: colors[Math.floor(Math.random() * colors.length)]
};
balls.push(ball);
}
// Get hexagon vertices
function getHexagonVertices() {
const vertices = [];
for (let i = 0; i < 6; i++) {
const angle = hexRotation + i * Math.PI / 3;
vertices.push({
x: centerX + hexRadius * Math.cos(angle),
y: centerY + hexRadius * Math.sin(angle)
});
}
return vertices;
}
// Get hexagon edges
function getHexagonEdges(vertices) {
const edges = [];
for (let i = 0; i < 6; i++) {
edges.push({
start: vertices[i],
end: vertices[(i + 1) % 6]
});
}
return edges;
}
// Calculate distance from point to line segment
function distanceToSegment(point, edge) {
const A = point.x - edge.start.x;
const B = point.y - edge.start.y;
const C = edge.end.x - edge.start.x;
const D = edge.end.y - edge.start.y;
const dot = A * C + B * D;
const lenSq = C * C + D * D;
let param = -1;
if (lenSq !== 0) {
param = dot / lenSq;
}
let xx, yy;
if (param < 0) {
xx = edge.start.x;
yy = edge.start.y;
} else if (param > 1) {
xx = edge.end.x;
yy = edge.end.y;
} else {
xx = edge.start.x + param * C;
yy = edge.start.y + param * D;
}
const dx = point.x - xx;
const dy = point.y - yy;
return Math.sqrt(dx * dx + dy * dy);
}
// Calculate normal vector for an edge
function getEdgeNormal(edge) {
const dx = edge.end.x - edge.start.x;
const dy = edge.end.y - edge.start.y;
const length = Math.sqrt(dx * dx + dy * dy);
// Normal vector (perpendicular to the edge)
return {
x: -dy / length,
y: dx / length
};
}
// Update ball positions and handle collisions
function updateBalls(edges) {
for (const ball of balls) {
// Update position
ball.x += ball.vx;
ball.y += ball.vy;
// Check collision with hexagon edges
for (const edge of edges) {
const distance = distanceToSegment(ball, edge);
if (distance < ball.radius) {
const normal = getEdgeNormal(edge);
// Reflect velocity vector
const dot = ball.vx * normal.x + ball.vy * normal.y;
ball.vx = ball.vx - 2 * dot * normal.x;
ball.vy = ball.vy - 2 * dot * normal.y;
// Move ball outside collision
const overlap = ball.radius - distance;
ball.x += normal.x * overlap;
ball.y += normal.y * overlap;
}
}
// Add some gravity effect
ball.vy += 0.05;
// Damping to prevent infinite bouncing
ball.vx *= 0.995;
ball.vy *= 0.995;
}
}
// Draw hexagon
function drawHexagon(vertices) {
ctx.beginPath();
ctx.moveTo(vertices[0].x, vertices[0].y);
for (let i = 1; i < 6; i++) {
ctx.lineTo(vertices[i].x, vertices[i].y);
}
ctx.closePath();
ctx.strokeStyle = 'rgba(76, 201, 240, 0.8)';
ctx.lineWidth = 3;
ctx.stroke();
// Draw center point
ctx.beginPath();
ctx.arc(centerX, centerY, 5, 0, Math.PI * 2);
ctx.fillStyle = '#4cc9f0';
ctx.fill();
}
// Draw balls
function drawBalls() {
for (const ball of balls) {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fillStyle = ball.color;
ctx.fill();
// Add shine effect
ctx.beginPath();
ctx.arc(ball.x - ball.radius/3, ball.y - ball.radius/3, ball.radius/3, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.fill();
}
}
// Draw background effects
function drawBackground() {
// Draw grid
ctx.strokeStyle = 'rgba(76, 201, 240, 0.05)';
ctx.lineWidth = 1;
// Vertical lines
for (let x = 0; x < canvas.width; x += 30) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
}
// Horizontal lines
for (let y = 0; y < canvas.height; y += 30) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
}
// Draw center circle
ctx.beginPath();
ctx.arc(centerX, centerY, hexRadius - 20, 0, Math.PI * 2);
ctx.strokeStyle = 'rgba(76, 201, 240, 0.1)';
ctx.stroke();
}
// Animation loop
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Update hexagon rotation
hexRotation += rotationSpeed;
// Get hexagon vertices and edges
const vertices = getHexagonVertices();
const edges = getHexagonEdges(vertices);
// Update and draw
drawBackground();
drawHexagon(vertices);
updateBalls(edges);
drawBalls();
requestAnimationFrame(animate);
}
// Event listeners
document.getElementById('speedUp').addEventListener('click', () => {
rotationSpeed = Math.min(rotationSpeed + 0.002, 0.02);
});
document.getElementById('slowDown').addEventListener('click', () => {
rotationSpeed = Math.max(rotationSpeed - 0.002, 0.001);
});
document.getElementById('reset').addEventListener('click', () => {
balls.length = 0;
initBalls(10);
rotationSpeed = 0.005;
});
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// Create a ball at click position
if (balls.length < maxBalls) {
balls.push({
x: x,
y: y,
radius: 8 + Math.random() * 12,
vx: (Math.random() - 0.5) * 4,
vy: (Math.random() - 0.5) * 4,
color: colors[Math.floor(Math.random() * colors.length)]
});
}
});
// Initialize and start animation
initBalls(10);
animate();
</script>
</body>
</html>
```
## Features of this Animation:
1. **Spinning Hexagon**: A blue hexagon rotates smoothly at the center of the canvas
2. **10 Bouncing Balls**: Colorful balls with random sizes and velocities bounce realistically inside the hexagon
3. **Physics Simulation**: Balls collide with the hexagon walls using proper reflection physics
4. **Interactive Controls**:
- Speed Up: Increases hexagon rotation speed
- Slow Down: Decreases hexagon rotation speed
- Reset: Clears all balls and resets to initial state
5. **Click to Add Balls**: Click anywhere on the canvas to add more balls (up to 20)
6. **Visual Effects**:
- Subtle grid background
- Ball shine effects
- Smooth animations
The balls bounce realistically off the rotating hexagon walls, with gravity and damping effects to create natural-looking motion. The interface is clean and modern with a dark gradient background that makes the colorful balls stand out.Drag to resize
```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 Spinning Hexagon</title>
<style>
body {
margin: 0;
padding: 0;
background: #1a1a2e;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: Arial, sans-serif;
}
canvas {
border: 2px solid #00ff88;
border-radius: 10px;
box-shadow: 0 0 30px rgba(0, 255, 136, 0.3);
}
</style>
</head>
<body>
<canvas id="canvas" width="800" height="600"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Hexagon properties
const hexCenter = { x: canvas.width / 2, y: canvas.height / 2 };
const hexRadius = 200;
let hexRotation = 0;
// Ball class
class Ball {
constructor() {
this.x = hexCenter.x + (Math.random() - 0.5) * hexRadius;
this.y = hexCenter.y + (Math.random() - 0.5) * hexRadius;
this.vx = (Math.random() - 0.5) * 8;
this.vy = (Math.random() - 0.5) * 8;
this.radius = 8 + Math.random() * 12;
this.color = `hsl(${Math.random() * 360}, 80%, 60%)`;
this.trail = [];
}
update() {
this.x += this.vx;
this.y += this.vy;
// Add current position to trail
this.trail.push({ x: this.x, y: this.y });
if (this.trail.length > 15) {
this.trail.shift();
}
// Check collision with hexagon walls
this.checkHexagonCollision();
}
checkHexagonCollision() {
const vertices = getHexagonVertices();
for (let i = 0; i < vertices.length; i++) {
const current = vertices[i];
const next = vertices[(i + 1) % vertices.length];
const collision = this.checkLineCollision(current, next);
if (collision) {
// Reflect velocity based on the wall normal
const wallVector = {
x: next.x - current.x,
y: next.y - current.y
};
const wallNormal = {
x: -wallVector.y,
y: wallVector.x
};
// Normalize the normal vector
const length = Math.sqrt(wallNormal.x * wallNormal.x + wallNormal.y * wallNormal.y);
wallNormal.x /= length;
wallNormal.y /= length;
// Reflect velocity
const dotProduct = this.vx * wallNormal.x + this.vy * wallNormal.y;
this.vx -= 2 * dotProduct * wallNormal.x;
this.vy -= 2 * dotProduct * wallNormal.y;
// Move ball away from wall to prevent sticking
this.x += wallNormal.x * 2;
this.y += wallNormal.y * 2;
break;
}
}
}
checkLineCollision(p1, p2) {
// Distance from point to line segment
const A = this.x - p1.x;
const B = this.y - p1.y;
const C = p2.x - p1.x;
const D = p2.y - p1.y;
const dot = A * C + B * D;
const lenSq = C * C + D * D;
if (lenSq === 0) return false;
let param = dot / lenSq;
let xx, yy;
if (param < 0) {
xx = p1.x;
yy = p1.y;
} else if (param > 1) {
xx = p2.x;
yy = p2.y;
} else {
xx = p1.x + param * C;
yy = p1.y + param * D;
}
const dx = this.x - xx;
const dy = this.y - yy;
const distance = Math.sqrt(dx * dx + dy * dy);
return distance < this.radius;
}
draw() {
// Draw trail
ctx.globalAlpha = 0.3;
for (let i = 0; i < this.trail.length - 1; i++) {
const alpha = i / this.trail.length;
ctx.globalAlpha = alpha * 0.3;
ctx.strokeStyle = this.color;
ctx.lineWidth = this.radius * alpha;
ctx.beginPath();
ctx.moveTo(this.trail[i].x, this.trail[i].y);
ctx.lineTo(this.trail[i + 1].x, this.trail[i + 1].y);
ctx.stroke();
}
// Draw ball
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
// Gradient for 3D effect
const gradient = ctx.createRadialGradient(
this.x - this.radius/3, this.y - this.radius/3, 0,
this.x, this.y, this.radius
);
gradient.addColorStop(0, 'white');
gradient.addColorStop(0.3, this.color);
gradient.addColorStop(1, this.color.replace('60%', '30%'));
ctx.fillStyle = gradient;
ctx.fill();
ctx.strokeStyle = this.color.replace('60%', '40%');
ctx.lineWidth = 2;
ctx.stroke();
}
}
// Get hexagon vertices based on current rotation
function getHexagonVertices() {
const vertices = [];
for (let i = 0; i < 6; i++) {
const angle = (i * Math.PI) / 3 + hexRotation;
vertices.push({
x: hexCenter.x + Math.cos(angle) * hexRadius,
y: hexCenter.y + Math.sin(angle) * hexRadius
});
}
return vertices;
}
// Draw hexagon
function drawHexagon() {
const vertices = getHexagonVertices();
// Draw hexagon fill with gradient
const gradient = ctx.createRadialGradient(
hexCenter.x, hexCenter.y, 0,
hexCenter.x, hexCenter.y, hexRadius
);
gradient.addColorStop(0, 'rgba(0, 255, 136, 0.1)');
gradient.addColorStop(1, 'rgba(0, 255, 136, 0.05)');
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 = gradient;
ctx.fill();
// Draw hexagon outline with glow effect
ctx.shadowColor = '#00ff88';
ctx.shadowBlur = 10;
ctx.strokeStyle = '#00ff88';
ctx.lineWidth = 3;
ctx.stroke();
ctx.shadowBlur = 0;
}
// Create balls
const balls = [];
for (let i = 0; i < 10; i++) {
balls.push(new Ball());
}
// Animation loop
function animate() {
// Clear canvas with fade effect
ctx.fillStyle = 'rgba(26, 26, 46, 0.1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Update hexagon rotation
hexRotation += 0.01;
// Draw hexagon
drawHexagon();
// Update and draw balls
balls.forEach(ball => {
ball.update();
ball.draw();
});
requestAnimationFrame(animate);
}
// Start animation
animate();
</script>
</body>
</html>
```Drag to resize
```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 a Spinning Hexagon</title>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #0c0c0c;
}
canvas {
display: block;
}
</style>
</head>
<body>
<canvas id="animationCanvas"></canvas>
<script>
const canvas = document.getElementById('animationCanvas');
const ctx = canvas.getContext('2d');
let width = canvas.width = window.innerWidth;
let height = canvas.height = window.innerHeight;
// --- CONFIGURATION ---
const NUM_BALLS = 10;
const HEX_SIZE = Math.min(width, height) * 0.4; // Radius from center to a vertex
const BALL_MAX_RADIUS = 18;
const BALL_MIN_RADIUS = 10;
const MAX_SPEED = 2.5;
const ROTATION_SPEED = 0.003; // Radians per frame
const HEX_LINE_WIDTH = 5;
let hexagonAngle = 0;
const balls = [];
const center = { x: width / 2, y: height / 2 };
// --- UTILITY FUNCTIONS ---
function getRandom(min, max) {
return Math.random() * (max - min) + min;
}
function getRandomColor() {
const hue = Math.random() * 360;
return `hsl(${hue}, 90%, 65%)`;
}
// --- BALL CLASS ---
class Ball {
constructor(x, y, vx, vy, radius, color) {
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.radius = radius;
this.color = color;
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
}
update(vertices) {
// Move the ball
this.x += this.vx;
this.y += this.vy;
// --- COLLISION DETECTION WITH HEXAGON WALLS ---
for (let i = 0; i < 6; i++) {
const p1 = vertices[i];
const p2 = vertices[(i + 1) % 6];
// Calculate the normal vector of the hexagon side
// The vector (p2.y - p1.y, p1.x - p2.x) is perpendicular to the side
let normalX = p2.y - p1.y;
let normalY = p1.x - p2.x;
// Normalize the vector (make it a unit vector)
const length = Math.sqrt(normalX * normalX + normalY * normalY);
normalX /= length;
normalY /= length;
// Ensure the normal points outwards from the hexagon center
// We check this by taking the dot product of the normal with a vector from the center to the wall
const vecToWallX = p1.x - center.x;
const vecToWallY = p1.y - center.y;
if (vecToWallX * normalX + vecToWallY * normalY < 0) {
normalX *= -1;
normalY *= -1;
}
// Calculate the signed distance from the ball's center to the line (wall)
const vecToBallX = this.x - p1.x;
const vecToBallY = this.y - p1.y;
const distance = vecToBallX * normalX + vecToBallY * normalY;
// If the distance is less than the ball's radius, a collision has occurred
if (distance > -this.radius) {
// 1. Correct position to prevent sinking into the wall
const overlap = this.radius + distance;
this.x -= overlap * normalX;
this.y -= overlap * normalY;
// 2. Reflect the velocity vector
const dotProduct = this.vx * normalX + this.vy * normalY;
this.vx -= 2 * dotProduct * normalX;
this.vy -= 2 * dotProduct * normalY;
}
}
}
}
// --- HEXAGON FUNCTIONS ---
function getHexagonVertices(angle) {
const vertices = [];
for (let i = 0; i < 6; i++) {
const currentAngle = angle + (i * Math.PI / 3); // 60 degrees in radians
vertices.push({
x: center.x + HEX_SIZE * Math.cos(currentAngle),
y: center.y + HEX_SIZE * Math.sin(currentAngle)
});
}
return vertices;
}
function drawHexagon(vertices) {
ctx.beginPath();
ctx.moveTo(vertices[0].x, vertices[0].y);
for (let i = 1; i < 6; i++) {
ctx.lineTo(vertices[i].x, vertices[i].y);
}
ctx.closePath();
ctx.lineWidth = HEX_LINE_WIDTH;
ctx.strokeStyle = '#66fcf1';
ctx.stroke();
// Add a subtle glow effect
ctx.shadowBlur = 20;
ctx.shadowColor = '#66fcf1';
ctx.stroke();
ctx.shadowBlur = 0;
}
// --- INITIALIZATION ---
function init() {
balls.length = 0; // Clear the existing balls array
const apothem = HEX_SIZE * Math.sqrt(3) / 2; // Inner radius of the hexagon
for (let i = 0; i < NUM_BALLS; i++) {
const radius = getRandom(BALL_MIN_RADIUS, BALL_MAX_RADIUS);
// Ensure balls are spawned inside the hexagon
const r = getRandom(0, apothem - radius);
const angle = getRandom(0, Math.PI * 2);
const x = center.x + r * Math.cos(angle);
const y = center.y + r * Math.sin(angle);
const vx = getRandom(-MAX_SPEED, MAX_SPEED);
const vy = getRandom(-MAX_SPEED, MAX_SPEED);
const color = getRandomColor();
balls.push(new Ball(x, y, vx, vy, radius, color));
}
}
// --- ANIMATION LOOP ---
function animate() {
requestAnimationFrame(animate);
ctx.fillStyle = 'rgba(12, 12, 12, 0.25)'; // Fading effect
ctx.fillRect(0, 0, width, height);
// Update hexagon rotation and get current vertices
hexagonAngle += ROTATION_SPEED;
const vertices = getHexagonVertices(hexagonAngle);
// Update and draw balls
balls.forEach(ball => {
ball.update(vertices);
ball.draw();
});
// Draw hexagon on top of balls
drawHexagon(vertices);
}
// --- EVENT LISTENERS ---
window.addEventListener('resize', () => {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
center.x = width / 2;
center.y = height / 2;
init(); // Re-initialize on resize
});
// --- S T A R T ---
init();
animate();
</script>
</body>
</html>
```Drag to resize
```html
<!DOCTYPE html>
<html>
<head>
<title>Spinning Hexagon with Bouncing Balls</title>
<style>
html, body {
margin: 0;
overflow: hidden;
background: #000;
width: 100vw;
height: 100vh;
}
svg {
display: block;
}
</style>
</head>
<body>
<svg id="svg" width="100%" height="100%"></svg>
<script>
const svg = document.getElementById('svg');
const W = window.innerWidth;
const H = window.innerHeight;
const CENTER_X = W / 2;
const CENTER_Y = H / 2;
const HEX_RADIUS = Math.min(W, H) * 0.4;
const BALL_COUNT = 10;
let lastTime = performance.now();
svg.setAttribute('viewBox', `0 0 ${W} ${H}`);
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
// Create hexagon
const hex = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
hex.setAttribute('stroke', 'white');
hex.setAttribute('fill', 'none');
hex.setAttribute('stroke-width', '2');
svg.appendChild(hex);
// Create ball elements
const balls = [];
const ballElements = [];
for (let i = 0; i < BALL_COUNT; i++) {
balls.push({
x: CENTER_X,
y: CENTER_Y,
vx: (Math.random() - 0.5) * 6,
vy: (Math.random() - 0.5) * 6,
r: 8
});
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
circle.setAttribute('fill', 'white');
svg.appendChild(circle);
ballElements.push(circle);
}
let theta = 0;
const HEX_DELTA = 0.6 * Math.PI / 180; // Rotation speed
function getHexagonVertices(cx, cy, r, theta) {
const vertices = [];
for (let i = 0; i < 6; i++) {
const angle = theta + i * (Math.PI / 3);
vertices.push({
x: cx + r * Math.cos(angle),
y: cy + r * Math.sin(angle)
});
}
return vertices;
}
function checkCollision(ball) {
const vertices = getHexagonVertices(CENTER_X, CENTER_Y, HEX_RADIUS, theta);
let maxPen = 0;
let normal = null;
for (let i = 0; i < 6; i++) {
const p1 = vertices[i];
const p2 = vertices[(i + 1) % 6];
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
// Normal pointing inward
let nx = -dy;
let ny = dx;
const len = Math.hypot(nx, ny);
if (len === 0) continue;
nx /= len;
ny /= len;
const dp = (ball.x - p1.x) * nx + (ball.y - p1.y) * ny;
const pen = ball.r + dp;
if (dp < 0 && pen > 0) {
if (pen > maxPen) {
maxPen = pen;
normal = { x: nx, y: ny };
}
}
}
if (normal) {
const dot = ball.vx * normal.x + ball.vy * normal.y;
ball.vx = ball.vx - 2 * dot * normal.x;
ball.vy = ball.vy - 2 * dot * normal.y;
// Push out
const adjust = 1.05;
ball.x += normal.x * maxPen * adjust;
ball.y += normal.y * maxPen * adjust;
}
}
function update() {
// Update timing
const now = performance.now();
const dt = now - lastTime;
lastTime = now;
// Rotate hexagon
theta += HEX_DELTA;
// Update ball positions
balls.forEach(ball => {
ball.x += ball.vx;
ball.y += ball.vy;
checkCollision(ball);
});
// Draw hexagon
const points = getHexagonVertices(CENTER_X, CENTER_Y, HEX_RADIUS, theta)
.map(p => `${p.x},${p.y}`).join(' ');
hex.setAttribute('points', points);
// Draw balls
balls.forEach((b, i) => {
ballElements[i].setAttribute('cx', b.x);
ballElements[i].setAttribute('cy', b.y);
ballElements[i].setAttribute('r', b.r);
});
requestAnimationFrame(update);
}
update();
</script>
</body>
</html>
```Drag to resize
Here's a complete HTML file with an animation of 10 balls bouncing inside a spinning hexagon using JavaScript and the HTML Canvas API:
```html
<!DOCTYPE html>
<html>
<head>
<title>Bouncing Balls in a Spinning Hexagon</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
overflow: hidden;
}
canvas {
border: 1px solid #000;
background-color: #fff;
}
</style>
</head>
<body>
<canvas id="canvas" width="600" height="600"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
// Hexagon properties
let hexagonRotation = 0;
const hexagonRadius = 200;
const hexagonSides = 6;
const hexagonRotationSpeed = 0.01;
// Ball properties
const balls = [];
const ballRadius = 10;
const ballColors = ['#FF5733', '#33FF57', '#3357FF', '#F3FF33', '#FF33F3', '#33FFF3', '#8A2BE2', '#FF7F50', '#6495ED', '#DC143C'];
// Initialize balls
for (let i = 0; i < 10; i++) {
const angle = (i * (2 * Math.PI / 10)) + (Math.random() * 0.1);
const distance = hexagonRadius * 0.7;
balls.push({
x: width / 2 + Math.cos(angle) * distance,
y: height / 2 + Math.sin(angle) * distance,
vx: Math.cos(angle) * 2,
vy: Math.sin(angle) * 2,
color: ballColors[i]
});
}
// Function to draw a hexagon
function drawHexagon() {
ctx.beginPath();
for (let i = 0; i < hexagonSides; i++) {
const angle = (i * (2 * Math.PI / hexagonSides)) + hexagonRotation;
const x = width / 2 + Math.cos(angle) * hexagonRadius;
const y = height / 2 + Math.sin(angle) * hexagonRadius;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.closePath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.stroke();
}
// Function to draw balls
function drawBalls() {
balls.forEach(ball => {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = ball.color;
ctx.fill();
ctx.closePath();
});
}
// Function to update ball positions
function updateBalls() {
balls.forEach(ball => {
ball.x += ball.vx;
ball.y += ball.vy;
// Check for collisions with hexagon edges
for (let i = 0; i < hexagonSides; i++) {
const angle1 = (i * (2 * Math.PI / hexagonSides)) + hexagonRotation;
const angle2 = (((i + 1) % hexagonSides) * (2 * Math.PI / hexagonSides)) + hexagonRotation;
const x1 = width / 2 + Math.cos(angle1) * hexagonRadius;
const y1 = height / 2 + Math.sin(angle1) * hexagonRadius;
const x2 = width / 2 + Math.cos(angle2) * hexagonRadius;
const y2 = height / 2 + Math.sin(angle2) * hexagonRadius;
// Check for collision with this edge
const collision = checkLineCollision(ball.x, ball.y, ballRadius, x1, y1, x2, y2);
if (collision) {
// Reflect the ball's velocity
const normal = { x: -(y2 - y1), y: x2 - x1 };
const length = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
normal.x /= length;
normal.y /= length;
const dot = ball.vx * normal.x + ball.vy * normal.y;
ball.vx = ball.vx - 2 * dot * normal.x;
ball.vy = ball.vy - 2 * dot * normal.y;
}
}
});
}
// Function to check collision between a ball and a line
function checkLineCollision(ballX, ballY, ballRadius, x1, y1, x2, y2) {
const lineLength = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
const dot = ((ballX - x1) * (x2 - x1) + (ballY - y1) * (y2 - y1)) / (lineLength * lineLength);
let closestX = x1 + dot * (x2 - x1);
let closestY = y1 + dot * (y2 - y1);
if (dot < 0) {
closestX = x1;
closestY = y1;
} else if (dot > 1) {
closestX = x2;
closestY = y2;
}
const distance = Math.sqrt((ballX - closestX) * (ballX - closestX) + (ballY - closestY) * (ballY - closestY));
return distance <= ballRadius;
}
// Animation loop
function animate() {
ctx.clearRect(0, 0, width, height);
drawHexagon();
drawBalls();
updateBalls();
hexagonRotation += hexagonRotationSpeed;
requestAnimationFrame(animate);
}
// Start animation
animate();
</script>
</body>
</html>
```
This code creates a canvas with a spinning hexagon and 10 bouncing balls inside it. The balls change direction when they collide with the edges of the hexagon. The animation is smooth and should work well in modern web browsers.Drag to resize
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Spinning Hexagon – Bouncing Balls</title>
<style>
html,body{
margin:0;
height:100%;
background:#111;
overflow:hidden;
display:flex;
align-items:center;
justify-content:center;
}
canvas{
background:#111;
}
</style>
</head>
<body>
<canvas id="stage" width="640" height="640"></canvas>
<script>
/* -------------------------------------------------
Utility helpers
---------------------------------------------------*/
function length(v){ return Math.hypot(v.x, v.y); }
function normalize(v){
const l = length(v);
return {x: v.x/l, y: v.y/l};
}
function dot(a,b){ return a.x*b.x + a.y*b.y; }
/* -------------------------------------------------
Core objects
---------------------------------------------------*/
const canvas = document.getElementById('stage');
const ctx = canvas.getContext('2d');
const W = canvas.width;
const H = canvas.height;
const CENTER = {x: W/2, y: H/2};
const HEX_RADIUS = 250;
const BALL_RADIUS = 8;
const BALL_COUNT = 10;
const ROT_SPEED = 0.012; // radians per frame
let hexAngle = 0; // current rotation
/* -------------------------------------------------
Build hexagon (unit, centered at 0/0)
---------------------------------------------------*/
function makeHexVertices(angle){
const verts = [];
for(let i=0;i<6;i++){
const a = angle + i*Math.PI/3; // 60° steps
verts.push({
x: CENTER.x + Math.cos(a)*HEX_RADIUS,
y: CENTER.y + Math.sin(a)*HEX_RADIUS
});
}
return verts;
}
/* -------------------------------------------------
Collision helpers
---------------------------------------------------*/
function reflectVelocity(vel, normal){
// v' = v - 2 (v·n) n
const vn = dot(vel,normal);
return {
x: vel.x - 2*vn*normal.x,
y: vel.y - 2*vn*normal.y
};
}
/* -------------------------------------------------
Ball definition
---------------------------------------------------*/
class Ball{
constructor(pos, vel){
this.pos = pos; // {x,y}
this.vel = vel; // {x,y}
this.r = BALL_RADIUS;
this.color = `hsl(${Math.random()*360},80%,60%)`;
}
update(){
this.pos.x += this.vel.x;
this.pos.y += this.vel.y;
}
draw(ctx){
ctx.beginPath();
ctx.arc(this.pos.x, this.pos.y, this.r, 0, Math.PI*2);
ctx.fillStyle = this.color;
ctx.fill();
}
}
/* -------------------------------------------------
Build initial set of balls
---------------------------------------------------*/
function randomPointInHex(){
let p;
while(true){
p = {
x: CENTER.x + (Math.random()*2-1)*HEX_RADIUS,
y: CENTER.y + (Math.random()*2-1)*HEX_RADIUS
};
if(pointInHex(p, makeHexVertices(hexAngle))) return p;
}
}
function pointInHex(p, verts){
for(let i=0;i<6;i++){
const v1 = verts[i];
const v2 = verts[(i+1)%6];
const edge = {x: v2.x - v1.x, y: v2.y - v1.y};
const rel = {x: p.x - v1.x, y: p.y - v1.y};
const cross = edge.x*rel.y - edge.y*rel.x;
if(cross < 0) return false;
}
return true;
}
const balls = [];
for(let i=0;i<BALL_COUNT;i++){
const p = randomPointInHex();
const speed = 2 + Math.random()*1.5;
const angle = Math.random()*Math.PI*2;
balls.push(new Ball(
p,
{x: Math.cos(angle)*speed, y: Math.sin(angle)*speed}
));
}
/* -------------------------------------------------
Animation loop
---------------------------------------------------*/
function tick(){
requestAnimationFrame(tick);
// advance rotation
hexAngle += ROT_SPEED;
const verts = makeHexVertices(hexAngle);
// update and collide balls ----------------------------------------
for(const ball of balls){
ball.update();
// check against each hex edge
for(let i=0;i<6;i++){
const v1 = verts[i];
const v2 = verts[(i+1)%6];
const edge = {x: v2.x - v1.x, y: v2.y - v1.y};
const len = length(edge);
// inward normal (make sure it points inside)
let normal = normalize({x: -edge.y, y: edge.x});
const toCenter = {x: CENTER.x - v1.x, y: CENTER.y - v1.y};
if(dot(normal, toCenter) < 0){
normal.x *= -1;
normal.y *= -1;
}
// signed distance from ball center to edge
const rel = {x: ball.pos.x - v1.x, y: ball.pos.y - v1.y};
const dist = dot(rel, normal);
if(dist < ball.r){ // collision / penetration
// slide out of wall
const penetration = ball.r - dist;
ball.pos.x += normal.x * penetration;
ball.pos.y += normal.y * penetration;
// reflect velocity if heading towards wall
if(dot(ball.vel, normal) < 0){
ball.vel = reflectVelocity(ball.vel, normal);
}
}
}
}
// --------------- render -----------------------------------------
ctx.clearRect(0,0,W,H);
// draw spinning hexagon
ctx.save();
ctx.translate(CENTER.x, CENTER.y);
ctx.rotate(hexAngle);
ctx.beginPath();
for(let i=0;i<6;i++){
const a = i*Math.PI/3;
const x = Math.cos(a)*HEX_RADIUS;
const y = Math.sin(a)*HEX_RADIUS;
if(i===0) ctx.moveTo(x,y);
else ctx.lineTo(x,y);
}
ctx.closePath();
ctx.lineWidth = 4;
ctx.strokeStyle = '#08f';
ctx.stroke();
ctx.restore();
// draw balls
for(const ball of balls) ball.draw(ctx);
}
tick();
</script>
</body>
</html>
```Drag to resize
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bouncing Balls in Spinning Hexagon</title>
<style>
html, body {
margin: 0;
padding: 0;
background: #202030;
overflow: hidden;
height: 100%;
}
canvas {
display: block;
background: #282844;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();
const HEX_RADIUS = Math.min(canvas.width, canvas.height) * 0.33;
const BALL_RADIUS = 18;
const NUM_BALLS = 10;
const SPIN_SPEED = 0.7 * Math.PI / 180; // radians per frame
function hexagonVertices(cx, cy, radius, rot) {
let verts = [];
for (let i = 0; i < 6; i++) {
let angle = rot + Math.PI/6 + i * Math.PI/3;
verts.push({
x: cx + radius * Math.cos(angle),
y: cy + radius * Math.sin(angle)
});
}
return verts;
}
function pointLineDist(px, py, x1, y1, x2, y2) {
const dx = x2 - x1;
const dy = y2 - y1;
const len2 = dx * dx + dy * dy;
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / len2));
const projx = x1 + t * dx;
const projy = y1 + t * dy;
const dist = Math.hypot(px - projx, py - projy);
return {dist, projx, projy};
}
function insideHex(point, hexVerts) {
// Use crossing number algorithm
let cnt = 0;
for (let i = 0; i < 6; i++) {
const v1 = hexVerts[i];
const v2 = hexVerts[(i+1)%6];
if ((v1.y > point.y) !== (v2.y > point.y)) {
let atX = (v2.x - v1.x) * (point.y - v1.y) / (v2.y - v1.y) + v1.x;
if (point.x < atX) cnt++;
}
}
return cnt % 2 === 1;
}
// Random pastel color generator
function randomColor() {
const base = 150;
const variance = 105;
const r = Math.floor(base + Math.random() * variance);
const g = Math.floor(base + Math.random() * variance);
const b = Math.floor(base + Math.random() * variance);
return `rgba(${r},${g},${b},0.82)`;
}
// Ball Object
class Ball {
constructor(x, y, vx, vy, color) {
this.x = x; this.y = y;
this.vx = vx; this.vy = vy;
this.color = color;
}
update() {
this.x += this.vx;
this.y += this.vy;
}
draw(ctx) {
ctx.beginPath();
ctx.arc(this.x, this.y, BALL_RADIUS, 0, 2*Math.PI);
ctx.fillStyle = this.color;
ctx.shadowColor = this.color;
ctx.shadowBlur = 16;
ctx.fill();
ctx.shadowBlur = 0;
}
}
// Place balls randomly inside hexagon
function randomPointInHex(cx, cy, r, rot) {
// Rejection method
while (true) {
let a = Math.random() * 2 * Math.PI;
let d = Math.random() * r * 0.92; // keep some margin
let px = cx + d * Math.cos(a);
let py = cy + d * Math.sin(a);
let verts = hexagonVertices(cx, cy, r - BALL_RADIUS, rot);
if (insideHex({x: px, y: py}, verts)) {
return {x: px, y: py};
}
}
}
let hexRot = 0;
let cx = canvas.width/2, cy = canvas.height/2;
let balls = [];
function initBalls() {
balls = [];
for (let i = 0; i < NUM_BALLS; i++) {
let pt = randomPointInHex(cx, cy, HEX_RADIUS - BALL_RADIUS, 0);
// velocity random direction & speed
let angle = Math.random()*2*Math.PI;
let speed = 2 + Math.random()*1.8;
balls.push(new Ball(
pt.x, pt.y,
Math.cos(angle)*speed, Math.sin(angle)*speed,
randomColor()
));
}
}
initBalls();
function handleResize() {
cx = canvas.width/2;
cy = canvas.height/2;
}
window.addEventListener('resize', () => {
handleResize();
initBalls();
});
function reflectVelocity(vx, vy, nx, ny) {
// nx,ny must be normalized
// Reflect v about normal n
let dot = vx*nx + vy*ny;
// bounce coefficient
const f = 0.98;
return {
vx: (vx - 2 * dot * nx) * f,
vy: (vy - 2 * dot * ny) * f
};
}
function animate() {
ctx.clearRect(0,0,canvas.width, canvas.height);
// Draw spinning hexagon
let verts = hexagonVertices(cx, cy, HEX_RADIUS, hexRot);
ctx.save();
ctx.beginPath();
verts.forEach((v, i) => {
if (i===0) ctx.moveTo(v.x, v.y);
else ctx.lineTo(v.x, v.y);
});
ctx.closePath();
ctx.lineWidth = 7;
ctx.shadowColor = "#acf";
ctx.shadowBlur = 14;
ctx.strokeStyle = "#40a0e0";
ctx.stroke();
ctx.shadowBlur = 0;
ctx.restore();
// Draw balls and update their physics
// Find collision with moving polygon
for (let ball of balls) {
// Store previous position
ball.update();
// For each edge: find if ball goes outside hex
let coll = false;
for (let i = 0; i < 6; i++) {
const v1 = verts[i];
const v2 = verts[(i+1)%6];
// Compute distance from ball center to edge
let {dist, projx, projy} = pointLineDist(ball.x, ball.y, v1.x, v1.y, v2.x, v2.y);
// To check if projection is within edge segment and inside hex-normal
let edgeDir = {x: v2.x-v1.x, y: v2.y-v1.y};
let edgeLen = Math.hypot(edgeDir.x, edgeDir.y);
let edgeNorm = {x: edgeDir.x/edgeLen, y: edgeDir.y/edgeLen};
let normal = {x: edgeNorm.y, y: -edgeNorm.x}; // outward normal per hex order
// Vector from edge to ball center
let fromEdge = {x: ball.x-projx, y: ball.y-projy};
// Check if ball center is pushing outwards
let outwards = (fromEdge.x * normal.x + fromEdge.y * normal.y) > 0.1;
// Projection must be on the segment, and pushing outside, and distance less than radius
let inSegment = ((projx - v1.x)*(projx-v2.x) + (projy-v1.y)*(projy-v2.y)) <= 0.01;
if (dist < BALL_RADIUS - 0.1 && inSegment && outwards) {
// Move it back to tangent point
let pushback = BALL_RADIUS - dist + 0.7;
ball.x += normal.x * pushback;
ball.y += normal.y * pushback;
// Reflect velocity
let reflected = reflectVelocity(ball.vx, ball.vy, normal.x, normal.y);
ball.vx = reflected.vx;
ball.vy = reflected.vy;
coll = true;
}
}
// If ball fully left hex (rare): bring back into center
if (!insideHex({x: ball.x, y: ball.y}, verts)) {
let towards = Math.atan2(cy - ball.y, cx - ball.x);
ball.x = cx + (HEX_RADIUS - BALL_RADIUS*1.2)*Math.cos(towards);
ball.y = cy + (HEX_RADIUS - BALL_RADIUS*1.2)*Math.sin(towards);
ball.vx = Math.cos(towards + Math.PI/2)*(2+Math.random()) * (Math.random()<.5 ? 1 : -1);
ball.vy = Math.sin(towards + Math.PI/2)*(2+Math.random()) * (Math.random()<.5 ? 1 : -1);
}
ball.draw(ctx);
}
hexRot += SPIN_SPEED;
requestAnimationFrame(animate);
}
animate();
</script>
</body>
</html>
```Drag to resize
Below is an HTML file with JavaScript code to create an animation of 10 balls bouncing inside a spinning hexagon. The animation uses the HTML5 Canvas API.
```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 Spinning Hexagon</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
canvas {
background-color: #fff;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<canvas id="canvas" width="600" height="600"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const canvasSize = Math.min(canvas.width, canvas.height);
const hexagonSize = canvasSize * 0.8;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const ballCount = 10;
const balls = [];
const rotationSpeed = 0.01;
class Ball {
constructor() {
this.radius = 10;
this.angle = Math.random() * Math.PI * 2;
this.distance = (hexagonSize / 2) * 0.8 * (0.5 + Math.random() * 0.5);
this.speed = 1 + Math.random() * 2;
this.color = `#${Math.floor(Math.random() * 16777215).toString(16)}`;
}
update() {
this.angle += this.speed / 100;
if (this.angle > Math.PI * 2) this.angle -= Math.PI * 2;
}
draw(rotation) {
const x = centerX + this.distance * Math.cos(this.angle + rotation);
const y = centerY + this.distance * Math.sin(this.angle + rotation);
ctx.beginPath();
ctx.arc(x, y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
}
}
function drawHexagon(rotation) {
ctx.beginPath();
for (let i = 0; i < 6; i++) {
const angle = Math.PI / 3 * i + rotation;
const x = centerX + hexagonSize * Math.cos(angle);
const y = centerY + hexagonSize * Math.sin(angle);
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.closePath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.stroke();
}
function initBalls() {
for (let i = 0; i < ballCount; i++) {
balls.push(new Ball());
}
}
function animate(timestamp) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const rotation = timestamp * rotationSpeed;
drawHexagon(rotation);
balls.forEach(ball => {
ball.update();
ball.draw(rotation);
});
requestAnimationFrame(animate);
}
initBalls();
animate(0);
</script>
</body>
</html>
```
This code creates a responsive animation with the following features:
1. A spinning hexagon that rotates slowly.
2. 10 balls bouncing inside the hexagon, each with a random color, size, and speed.
3. The balls move in a circular pattern, giving the illusion of bouncing.
The animation is created using the HTML5 Canvas API and is updated at each frame using the `requestAnimationFrame` function. The balls and hexagon are drawn using basic geometric shapes and transformed using rotation and translation.
To run this code, simply open the HTML file in a web browser. The animation will start automatically and continue indefinitely.Drag to resize
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bouncing Balls in Spinning Hexagon</title>
<style>
body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background: #f0f0f0; }
canvas { border: 1px solid #000; }
</style>
</head>
<body>
<canvas id="canvas" width="500" height="500"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const cx = 250;
const cy = 250;
const R = 200;
const numBalls = 10;
const ballRadius = 5;
const omega = Math.PI / 2; // radians per second, full spin in 4 seconds
const maxSpeed = 200; // pixels per second
let angle = 0;
let lastTime = 0;
let balls = [];
// Function to check if point is in polygon (ray casting)
function isPointInPolygon(x, y, verts) {
let inside = false;
let n = verts.length;
for (let i = 0, j = n - 1; i < n; j = i++) {
let xi = verts[i].x, yi = verts[i].y;
let xj = verts[j].x, yj = verts[j].y;
if ((yi > y) !== (yj > y) && (x < (xj - xi) * (y - yi) / (yj - yi + 1e-6) + xi)) {
inside = !inside;
}
}
return inside;
}
// Initialize balls
function initBalls() {
// Compute initial vertices at angle 0
let initVerts = [];
for (let i = 0; i < 6; i++) {
let theta = 2 * Math.PI * i / 6;
initVerts.push({
x: cx + R * Math.cos(theta),
y: cy + R * Math.sin(theta)
});
}
for (let b = 0; b < numBalls; b++) {
let x, y;
do {
let dist = R * 0.9 * Math.random();
let ang = Math.random() * 2 * Math.PI;
x = cx + dist * Math.cos(ang);
y = cy + dist * Math.sin(ang);
} while (!isPointInPolygon(x, y, initVerts));
let vx = (Math.random() - 0.5) * 2 * maxSpeed;
let vy = (Math.random() - 0.5) * 2 * maxSpeed;
let color = `hsl(${Math.random() * 360}, 100%, 50%)`;
balls.push({x, y, vx, vy, color});
}
}
// Get current vertices and sides
function getHexagon(angle) {
let verts = [];
let sides = [];
for (let i = 0; i < 6; i++) {
let theta = angle + 2 * Math.PI * i / 6;
verts.push({
x: cx + R * Math.cos(theta),
y: cy + R * Math.sin(theta)
});
}
for (let i = 0; i < 6; i++) {
let j = (i + 1) % 6;
sides.push({
x1: verts[i].x, y1: verts[i].y,
x2: verts[j].x, y2: verts[j].y
});
}
return {verts, sides};
}
// Animation loop
function animate(time) {
if (!lastTime) lastTime = time;
const dt = (time - lastTime) / 1000;
lastTime = time;
angle += omega * dt;
const {verts, sides} = getHexagon(angle);
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw hexagon
ctx.beginPath();
ctx.moveTo(verts[0].x, verts[0].y);
for (let i = 1; i < 6; i++) {
ctx.lineTo(verts[i].x, verts[i].y);
}
ctx.closePath();
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.stroke();
// Update and draw balls
balls.forEach(ball => {
const old_x = ball.x;
const old_y = ball.y;
let tent_x = old_x + ball.vx * dt;
let tent_y = old_y + ball.vy * dt;
let min_t = Infinity;
let intersect_side = -1;
let ix, iy;
for (let s = 0; s < 6; s++) {
const side = sides[s];
const dx = tent_x - old_x;
const dy = tent_y - old_y;
const dsx = side.x2 - side.x1;
const dsy = side.y2 - side.y1;
const den = dsx * dy - dsy * dx;
if (Math.abs(den) < 1e-6) continue;
const t = ((side.x1 - old_x) * dsy - (side.y1 - old_y) * dsx) / den;
const u = ((side.x1 - old_x) * dy - (side.y1 - old_y) * dx) / den;
if (t > 0 && t < min_t && u >= 0 && u <= 1) {
min_t = t;
intersect_side = s;
ix = old_x + t * dx;
iy = old_y + t * dy;
}
}
if (intersect_side !== -1) {
// Compute wall velocity at intersection
const rx = ix - cx;
const ry = iy - cy;
const wall_vx = -omega * ry;
const wall_vy = omega * rx;
// Compute inward normal
const side = sides[intersect_side];
const dsx = side.x2 - side.x1;
const dsy = side.y2 - side.y1;
let n1x = -dsy;
let n1y = dsx;
const mx = (side.x1 + side.x2) / 2;
const my = (side.y1 + side.y2) / 2;
const to_center_x = cx - mx;
const to_center_y = cy - my;
const dot1 = n1x * to_center_x + n1y * to_center_y;
let nx = dot1 > 0 ? n1x : dsy;
let ny = dot1 > 0 ? n1y : -dsx;
const len = Math.sqrt(nx * nx + ny * ny);
nx /= len;
ny /= len;
// Reflect with moving wall
const wall_n = wall_vx * nx + wall_vy * ny;
const ball_n = ball.vx * nx + ball.vy * ny;
const dot = ball_n - wall_n;
ball.vx -= 2 * dot * nx;
ball.vy -= 2 * dot * ny;
// Update position for remaining time
ball.x = ix + ball.vx * (1 - min_t) * dt;
ball.y = iy + ball.vy * (1 - min_t) * dt;
} else {
ball.x = tent_x;
ball.y = tent_y;
}
// Draw ball
ctx.beginPath();
ctx.arc(ball.x, ball.y, ballRadius, 0, 2 * Math.PI);
ctx.fillStyle = ball.color;
ctx.fill();
});
requestAnimationFrame(animate);
}
initBalls();
requestAnimationFrame(animate);
</script>
</body>
</html>
```Drag to resize
Here's a complete HTML file with JavaScript that creates an animation of 10 balls bouncing inside a spinning hexagon:
```html
<!DOCTYPE html>
<html>
<head>
<title>Bouncing Balls in Spinning Hexagon</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #111;
}
canvas {
display: block;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Set canvas size
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const hexagonRadius = Math.min(canvas.width, canvas.height) * 0.4;
let hexagonAngle = 0;
// Create balls
const balls = [];
const ballCount = 10;
const ballRadius = 15;
for (let i = 0; i < ballCount; i++) {
balls.push({
x: Math.random() * (canvas.width - 2 * ballRadius) + ballRadius,
y: Math.random() * (canvas.height - 2 * ballRadius) + ballRadius,
dx: (Math.random() - 0.5) * 8,
dy: (Math.random() - 0.5) * 8,
color: `hsl(${Math.random() * 360}, 80%, 60%)`
});
}
function drawHexagon(radius, angle) {
ctx.save();
ctx.translate(centerX, centerY);
ctx.rotate(angle);
ctx.beginPath();
for (let i = 0; i < 6; i++) {
const x = radius * Math.cos(i * Math.PI / 3);
const y = radius * Math.sin(i * Math.PI / 3);
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.closePath();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 3;
ctx.stroke();
ctx.restore();
}
function isPointInHexagon(x, y, radius, angle) {
// Transform point to hexagon's coordinate space
const dx = x - centerX;
const dy = y - centerY;
const cos = Math.cos(-angle);
const sin = Math.sin(-angle);
const tx = dx * cos - dy * sin;
const ty = dx * sin + dy * cos;
// Check if point is inside hexagon
const sideLength = radius;
const height = sideLength * Math.sqrt(3) / 2;
const width = sideLength * 2;
if (Math.abs(tx) > width / 2) return false;
if (Math.abs(ty) > height) return false;
const slope = height / (width / 2);
const boundary = height - slope * Math.abs(tx);
return Math.abs(ty) <= boundary;
}
function updateBalls() {
for (let ball of balls) {
// Update position
ball.x += ball.dx;
ball.y += ball.dy;
// Check collision with hexagon
const nextX = ball.x + ball.dx;
const nextY = ball.y + ball.dy;
if (!isPointInHexagon(nextX, ball.y, hexagonRadius, hexagonAngle)) {
ball.dx *= -1;
}
if (!isPointInHexagon(ball.x, nextY, hexagonRadius, hexagonAngle)) {
ball.dy *= -1;
}
// Check collision with other balls
for (let otherBall of balls) {
if (ball === otherBall) continue;
const dx = otherBall.x - ball.x;
const dy = otherBall.y - ball.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < ballRadius * 2) {
// Simple collision response
const angle = Math.atan2(dy, dx);
const targetX = ball.x - Math.cos(angle) * ballRadius * 2;
const targetY = ball.y - Math.sin(angle) * ballRadius * 2;
const ax = (targetX - otherBall.x) * 0.1;
const ay = (targetY - otherBall.y) * 0.1;
ball.dx -= ax;
ball.dy -= ay;
otherBall.dx += ax;
otherBall.dy += ay;
}
}
}
}
function drawBalls() {
for (let ball of balls) {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = ball.color;
ctx.fill();
ctx.closePath();
}
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Update hexagon rotation
hexagonAngle += 0.005;
// Draw hexagon
drawHexagon(hexagonRadius, hexagonAngle);
// Update and draw balls
updateBalls();
drawBalls();
requestAnimationFrame(animate);
}
// Handle window resize
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
centerX = canvas.width / 2;
centerY = canvas.height / 2;
hexagonRadius = Math.min(canvas.width, canvas.height) * 0.4;
});
animate();
</script>
</body>
</html>
```
This code creates:
1. A black background canvas
2. A white hexagon that rotates slowly
3. 10 colorful balls that bounce around inside the hexagon
4. Collision detection between balls and the hexagon walls
5. Simple collision response between balls
The animation uses requestAnimationFrame for smooth rendering. The hexagon rotates continuously while the balls bounce around, changing direction when they hit the hexagon walls or other balls.
You can adjust parameters like:
- Number of balls (ballCount)
- Ball size (ballRadius)
- Hexagon size (hexagonRadius multiplier)
- Rotation speed (hexagonAngle increment)
- Ball speed (dx/dy values)Drag to resize
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Bouncing Balls in Spinning Hexagon</title>
<style>
/* Add some basic styling to our canvas */
canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<canvas id="canvas" width="600" height="600"></canvas>
<script>
// Get the canvas element
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Define some constants
const NUM_BALLS = 10;
const HEXAGON_SIZE = 200;
const BALL_RADIUS = 10;
// Define the hexagon's center and rotation angle
let centerX = canvas.width / 2;
let centerY = canvas.height / 2;
let angle = 0;
// Define the balls' initial positions and velocities
let balls = [];
for (let i = 0; i < NUM_BALLS; i++) {
balls.push({
x: centerX + Math.random() * HEXAGON_SIZE - HEXAGON_SIZE / 2,
y: centerY + Math.random() * HEXAGON_SIZE - HEXAGON_SIZE / 2,
vx: Math.random() * 4 - 2,
vy: Math.random() * 4 - 2,
});
}
// Main animation loop
function animate() {
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw the spinning hexagon
ctx.save();
ctx.translate(centerX, centerY);
ctx.rotate(angle);
ctx.beginPath();
for (let i = 0; i < 6; i++) {
let a = i * Math.PI / 3;
let x = HEXAGON_SIZE / 2 * Math.cos(a);
let y = HEXAGON_SIZE / 2 * Math.sin(a);
ctx.lineTo(x, y);
}
ctx.closePath();
ctx.stroke();
ctx.restore();
// Update and draw the balls
for (let ball of balls) {
// Update the ball's position
ball.x += ball.vx;
ball.y += ball.vy;
// Bounce off the hexagon's edges
let dx = ball.x - centerX;
let dy = ball.y - centerY;
let dist = Math.sqrt(dx * dx + dy * dy);
let angle = Math.atan2(dy, dx);
if (dist > HEXAGON_SIZE / 2 - BALL_RADIUS) {
// Normalize the ball's position to be on the hexagon's edge
ball.x = centerX + (HEXAGON_SIZE / 2 - BALL_RADIUS) * Math.cos(angle);
ball.y = centerY + (HEXAGON_SIZE / 2 - BALL_RADIUS) * Math.sin(angle);
// Bounce the ball
let normalX = Math.cos(angle);
let normalY = Math.sin(angle);
let dotProduct = ball.vx * normalX + ball.vy * normalY;
ball.vx -= 2 * dotProduct * normalX;
ball.vy -= 2 * dotProduct * normalY;
}
// Draw the ball
ctx.beginPath();
ctx.arc(ball.x, ball.y, BALL_RADIUS, 0, 2 * Math.PI);
ctx.fill();
}
// Update the hexagon's rotation angle
angle += 0.01;
// Request the next frame
requestAnimationFrame(animate);
}
// Start the animation
animate();
</script>
</body>
</html>
```
This code creates a canvas element and gets a 2D drawing context. It defines some constants for the number of balls, the size of the hexagon, and the radius of the balls. The `animate` function is called repeatedly using `requestAnimationFrame`, and it updates the positions of the balls, draws the spinning hexagon, and draws the balls. The balls bounce off the edges of the hexagon.Drag to resize
```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 Spinning Hexagon</title>
<style>
#canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<canvas id="canvas" width="600" height="600"></canvas>
<script>
// Get the canvas element
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Set the canvas dimensions
canvas.width = 600;
canvas.height = 600;
// Define the hexagon properties
const hexagon = {
x: canvas.width / 2,
y: canvas.height / 2,
radius: 200,
rotation: 0,
rotationSpeed: 2
};
// Define the ball properties
const balls = [];
for (let i = 0; i < 10; i++) {
balls.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
radius: 10,
velocityX: Math.random() * 5 - 2.5,
velocityY: Math.random() * 5 - 2.5,
color: `rgb(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255})`
});
}
// Main animation loop
function animate() {
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw the spinning hexagon
ctx.save();
ctx.translate(hexagon.x, hexagon.y);
ctx.rotate(hexagon.rotation * Math.PI / 180);
ctx.beginPath();
for (let i = 0; i < 6; i++) {
const angle = i * Math.PI / 3;
ctx.lineTo(hexagon.radius * Math.cos(angle), hexagon.radius * Math.sin(angle));
}
ctx.closePath();
ctx.strokeStyle = 'black';
ctx.stroke();
ctx.restore();
// Update and draw the balls
for (let i = 0; i < balls.length; i++) {
const ball = balls[i];
// Update ball position
ball.x += ball.velocityX;
ball.y += ball.velocityY;
// Bounce off the edges
if (ball.x - ball.radius < 0 || ball.x + ball.radius > canvas.width) {
ball.velocityX *= -1;
}
if (ball.y - ball.radius < 0 || ball.y + ball.radius > canvas.height) {
ball.velocityY *= -1;
}
// Check collision with the hexagon
const distanceToCenter = Math.sqrt((ball.x - hexagon.x) ** 2 + (ball.y - hexagon.y) ** 2);
if (distanceToCenter < hexagon.radius) {
// Calculate the normal vector of the hexagon at the collision point
const angleToBall = Math.atan2(ball.y - hexagon.y, ball.x - hexagon.x);
const normalX = Math.cos(angleToBall);
const normalY = Math.sin(angleToBall);
// Reflect the ball's velocity over the normal vector
const dotProduct = ball.velocityX * normalX + ball.velocityY * normalY;
ball.velocityX = ball.velocityX - 2 * dotProduct * normalX;
ball.velocityY = ball.velocityY - 2 * dotProduct * normalY;
}
// Draw the ball
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, 2 * Math.PI);
ctx.fillStyle = ball.color;
ctx.fill();
}
// Update the hexagon rotation
hexagon.rotation += hexagon.rotationSpeed;
// Request the next animation frame
requestAnimationFrame(animate);
}
// Start the animation
animate();
</script>
</body>
</html>
```Drag to resize
```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 Spinning Hexagon</title>
<style>
canvas {
border: 1px solid black;
display: block;
margin: 0 auto;
}
</style>
</head>
<body>
<canvas id="myCanvas" width="500" height="500"></canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = 200; // Radius for the circular boundary and hexagon size
const numBalls = 10;
const ballRadius = 10;
const balls = [];
// Initialize balls with random positions and velocities
for (let i = 0; i < numBalls; i++) {
balls.push({
x: centerX + (Math.random() - 0.5) * radius * 0.8,
y: centerY + (Math.random() - 0.5) * radius * 0.8,
vx: (Math.random() - 0.5) * 5,
vy: (Math.random() - 0.5) * 5
});
}
let angle = 0; // Rotation angle for the hexagon
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw the spinning hexagon
ctx.save();
ctx.translate(centerX, centerY);
ctx.rotate(angle);
ctx.beginPath();
for (let i = 0; i < 6; i++) {
let hexagonAngle = i * Math.PI / 3;
let hx = radius * Math.cos(hexagonAngle);
let hy = radius * Math.sin(hexagonAngle);
if (i === 0) {
ctx.moveTo(hx, hy);
} else {
ctx.lineTo(hx, hy);
}
}
ctx.closePath();
ctx.strokeStyle = 'blue';
ctx.lineWidth = 2;
ctx.stroke();
ctx.restore();
// Update and draw balls
balls.forEach(ball => {
ball.x += ball.vx;
ball.y += ball.vy;
// Check collision with circular boundary
let dx = ball.x - centerX;
let dy = ball.y - centerY;
let distance = Math.sqrt(dx * dx + dy * dy);
if (distance + ballRadius > radius) {
// Simple bounce: Reverse velocity
ball.vx = -ball.vx;
ball.vy = -ball.vy;
// Adjust position to prevent sticking
let bounceAngle = Math.atan2(dy, dx);
ball.x = centerX + (radius - ballRadius) * Math.cos(bounceAngle);
ball.y = centerY + (radius - ballRadius) * Math.sin(bounceAngle);
}
// Draw the ball
ctx.beginPath();
ctx.arc(ball.x, ball.y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = 'red';
ctx.fill();
});
angle += 0.01; // Increment rotation angle
requestAnimationFrame(animate);
}
animate();
</script>
</body>
</html>
```Drag to resize