traul / index.html
aryansrk's picture
Add 2 files
309513a verified
<!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 Circular Boundary</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
overflow: hidden;
background-color: #1a202c;
}
#canvas {
display: block;
margin: 0 auto;
background-color: #2d3748;
}
.controls {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(255, 255, 255, 0.1);
padding: 10px;
border-radius: 8px;
backdrop-filter: blur(5px);
}
.ball-count {
position: absolute;
top: 20px;
left: 20px;
color: white;
font-family: 'Arial', sans-serif;
background-color: rgba(0, 0, 0, 0.5);
padding: 8px 12px;
border-radius: 20px;
}
.fps-counter {
position: absolute;
top: 20px;
right: 20px;
color: white;
font-family: 'Arial', sans-serif;
background-color: rgba(0, 0, 0, 0.5);
padding: 8px 12px;
border-radius: 20px;
}
</style>
</head>
<body class="flex items-center justify-center h-screen">
<div class="relative">
<canvas id="canvas"></canvas>
<div class="ball-count">Balls: <span id="ballCount">8</span></div>
<div class="fps-counter">FPS: <span id="fpsCounter">0</span></div>
<div class="controls">
<button id="addBall" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded mr-2 transition">Add Ball</button>
<button id="removeBall" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded mr-2 transition">Remove Ball</button>
<button id="reset" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded transition">Reset</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const ballCountDisplay = document.getElementById('ballCount');
const fpsCounter = document.getElementById('fpsCounter');
const addBallBtn = document.getElementById('addBall');
const removeBallBtn = document.getElementById('removeBall');
const resetBtn = document.getElementById('reset');
// Set canvas size
canvas.width = 800;
canvas.height = 800;
// Simulation parameters
const boundaryRadius = 350;
const boundaryCenter = { x: canvas.width / 2, y: canvas.height / 2 };
const minRadius = 15;
const maxRadius = 25;
const minSpeed = 1;
const maxSpeed = 4;
const colors = [
'#FF5252', '#FF4081', '#E040FB', '#7C4DFF',
'#536DFE', '#448AFF', '#40C4FF', '#18FFFF',
'#64FFDA', '#69F0AE', '#B2FF59', '#EEFF41',
'#FFFF00', '#FFD740', '#FFAB40', '#FF6E40'
];
// Physics parameters
const friction = 0.995; // Slight friction to make it more realistic
const bounceFactor = 0.95; // Energy loss on bounce
// Ball class
class Ball {
constructor(x, y, radius, color, dx, dy) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.dx = dx;
this.dy = dy;
this.mass = radius * radius; // Mass proportional to area
}
update() {
// Apply friction
this.dx *= friction;
this.dy *= friction;
// Update position
this.x += this.dx;
this.y += this.dy;
// Check boundary collision
const distanceFromCenter = Math.sqrt(
Math.pow(this.x - boundaryCenter.x, 2) +
Math.pow(this.y - boundaryCenter.y, 2)
);
if (distanceFromCenter + this.radius > boundaryRadius) {
// Calculate normal vector from boundary center to ball
const nx = (this.x - boundaryCenter.x) / distanceFromCenter;
const ny = (this.y - boundaryCenter.y) / distanceFromCenter;
// Calculate dot product of velocity and normal
const dotProduct = this.dx * nx + this.dy * ny;
// Reflect velocity vector
this.dx = (this.dx - 2 * dotProduct * nx) * bounceFactor;
this.dy = (this.dy - 2 * dotProduct * ny) * bounceFactor;
// Reposition ball to be exactly at boundary
const correction = boundaryRadius - this.radius;
this.x = boundaryCenter.x + nx * correction;
this.y = boundaryCenter.y + ny * correction;
}
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
// Add highlight for 3D effect
ctx.beginPath();
ctx.arc(this.x - this.radius/3, this.y - this.radius/3, this.radius/3, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.fill();
ctx.closePath();
}
}
// Ball collection
let balls = [];
// Initialize balls
function initBalls(count) {
balls = [];
for (let i = 0; i < count; i++) {
addRandomBall();
}
ballCountDisplay.textContent = balls.length;
}
// Add a random ball
function addRandomBall() {
const radius = minRadius + Math.random() * (maxRadius - minRadius);
// Random position inside circle
let angle = Math.random() * Math.PI * 2;
let distance = Math.random() * (boundaryRadius - radius);
const x = boundaryCenter.x + Math.cos(angle) * distance;
const y = boundaryCenter.y + Math.sin(angle) * distance;
// Random velocity
const speed = minSpeed + Math.random() * (maxSpeed - minSpeed);
angle = Math.random() * Math.PI * 2;
const dx = Math.cos(angle) * speed;
const dy = Math.sin(angle) * speed;
// Random color
const color = colors[Math.floor(Math.random() * colors.length)];
balls.push(new Ball(x, y, radius, color, dx, dy));
ballCountDisplay.textContent = balls.length;
}
// Check and handle collisions between balls
function checkCollisions() {
for (let i = 0; i < balls.length; i++) {
for (let j = i + 1; j < balls.length; j++) {
const ball1 = balls[i];
const ball2 = balls[j];
const dx = ball2.x - ball1.x;
const dy = ball2.y - ball1.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < ball1.radius + ball2.radius) {
// Collision detected
const angle = Math.atan2(dy, dx);
// Calculate velocities in direction of collision
const velocity1 = Math.sqrt(ball1.dx * ball1.dx + ball1.dy * ball1.dy);
const velocity2 = Math.sqrt(ball2.dx * ball2.dx + ball2.dy * ball2.dy);
const direction1 = Math.atan2(ball1.dy, ball1.dx);
const direction2 = Math.atan2(ball2.dy, ball2.dx);
const velocityX1 = velocity1 * Math.cos(direction1 - angle);
const velocityY1 = velocity1 * Math.sin(direction1 - angle);
const velocityX2 = velocity2 * Math.cos(direction2 - angle);
const velocityY2 = velocity2 * Math.sin(direction2 - angle);
// Final velocities after collision (conservation of momentum)
const finalVelocityX1 = ((ball1.mass - ball2.mass) * velocityX1 + 2 * ball2.mass * velocityX2) / (ball1.mass + ball2.mass);
const finalVelocityX2 = ((ball2.mass - ball1.mass) * velocityX2 + 2 * ball1.mass * velocityX1) / (ball1.mass + ball2.mass);
// Update ball velocities
ball1.dx = Math.cos(angle) * finalVelocityX1 + Math.cos(angle + Math.PI/2) * velocityY1;
ball1.dy = Math.sin(angle) * finalVelocityX1 + Math.sin(angle + Math.PI/2) * velocityY1;
ball2.dx = Math.cos(angle) * finalVelocityX2 + Math.cos(angle + Math.PI/2) * velocityY2;
ball2.dy = Math.sin(angle) * finalVelocityX2 + Math.sin(angle + Math.PI/2) * velocityY2;
// Move balls apart to prevent sticking
const overlap = (ball1.radius + ball2.radius - distance) / 2;
ball1.x -= overlap * Math.cos(angle);
ball1.y -= overlap * Math.sin(angle);
ball2.x += overlap * Math.cos(angle);
ball2.y += overlap * Math.sin(angle);
}
}
}
}
// Animation loop
let lastTime = 0;
let fps = 0;
function animate(currentTime) {
// Calculate FPS
if (lastTime) {
fps = Math.round(1000 / (currentTime - lastTime));
}
lastTime = currentTime;
fpsCounter.textContent = fps;
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw boundary
ctx.beginPath();
ctx.arc(boundaryCenter.x, boundaryCenter.y, boundaryRadius, 0, Math.PI * 2);
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
ctx.lineWidth = 2;
ctx.stroke();
// Draw center point
ctx.beginPath();
ctx.arc(boundaryCenter.x, boundaryCenter.y, 3, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.fill();
// Update and draw balls
checkCollisions();
balls.forEach(ball => {
ball.update();
ball.draw();
});
requestAnimationFrame(animate);
}
// Initialize with 8 balls
initBalls(8);
// Start animation
animate();
// Event listeners
addBallBtn.addEventListener('click', () => {
if (balls.length < 30) { // Limit to 30 balls
addRandomBall();
}
});
removeBallBtn.addEventListener('click', () => {
if (balls.length > 1) { // Keep at least 1 ball
balls.pop();
ballCountDisplay.textContent = balls.length;
}
});
resetBtn.addEventListener('click', () => {
initBalls(8);
});
// Make canvas responsive
window.addEventListener('resize', () => {
const size = Math.min(window.innerWidth, window.innerHeight) * 0.9;
canvas.width = size;
canvas.height = size;
boundaryCenter.x = canvas.width / 2;
boundaryCenter.y = canvas.height / 2;
});
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=aryansrk/traul" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>