Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save shricodev/9ac15a07a3a35473b689819c5f64cf91 to your computer and use it in GitHub Desktop.

Select an option

Save shricodev/9ac15a07a3a35473b689819c5f64cf91 to your computer and use it in GitHub Desktop.
<!doctype html>
<html>
<head>
<title>Physics Ball Sandbox</title>
<style>
body {
margin: 0;
overflow: hidden;
touch-action: none;
font-family: Arial, sans-serif;
}
canvas {
display: block;
background-color: #f0f0f0;
}
#controls {
position: absolute;
top: 10px;
left: 10px;
background: rgba(255, 255, 255, 0.7);
padding: 10px;
border-radius: 5px;
}
#instructions {
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(255, 255, 255, 0.7);
padding: 10px;
border-radius: 5px;
font-size: 14px;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div id="controls">
<div>
<label
>Ball Size:
<input type="range" id="sizeSlider" min="10" max="50" value="20"
/></label>
<span id="sizeValue">20</span>
</div>
<div>
<label
>Launch Speed:
<input type="range" id="speedSlider" min="0" max="20" value="5"
/></label>
<span id="speedValue">5</span>
</div>
<button id="clearBtn">Clear All Balls</button>
</div>
<div id="instructions">
<strong>Instructions:</strong><br />
- Click to place a ball<br />
- Click + drag to launch a ball in that direction<br />
- Drag an existing ball to reposition it<br />
- Adjust sliders to change ball size and launch speed
</div>
<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const sizeSlider = document.getElementById("sizeSlider");
const sizeValue = document.getElementById("sizeValue");
const speedSlider = document.getElementById("speedSlider");
const speedValue = document.getElementById("speedValue");
const clearBtn = document.getElementById("clearBtn");
// Set canvas to full window size
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Physics parameters
const gravity = 0.5;
const friction = 0.99;
const restitution = 0.8; // Bounciness
// Ball collection
let balls = [];
let currentBall = null;
let isDragging = false;
let dragStart = { x: 0, y: 0 };
let selectedBallIndex = -1;
// Initialize UI values
sizeValue.textContent = sizeSlider.value;
speedValue.textContent = speedSlider.value;
// Ball class
class Ball {
constructor(x, y, radius) {
this.x = x;
this.y = y;
this.radius = radius;
this.vx = 0;
this.vy = 0;
this.color = `hsl(${Math.random() * 360}, 70%, 60%)`;
}
update() {
// Apply gravity
this.vy += gravity;
// Apply friction
this.vx *= friction;
this.vy *= friction;
// Update position
this.x += this.vx;
this.y += this.vy;
// Boundary collision
if (this.x - this.radius < 0) {
this.x = this.radius;
this.vx *= -restitution;
} else if (this.x + this.radius > canvas.width) {
this.x = canvas.width - this.radius;
this.vx *= -restitution;
}
if (this.y - this.radius < 0) {
this.y = this.radius;
this.vy *= -restitution;
} else if (this.y + this.radius > canvas.height) {
this.y = canvas.height - this.radius;
this.vy *= -restitution;
}
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.strokeStyle = "#333";
ctx.stroke();
}
}
// Handle ball-ball collisions
function handleCollisions() {
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);
const sin = Math.sin(angle);
const cos = Math.cos(angle);
// Rotate velocities
const vx1 = ball1.vx * cos + ball1.vy * sin;
const vy1 = ball1.vy * cos - ball1.vx * sin;
const vx2 = ball2.vx * cos + ball2.vy * sin;
const vy2 = ball2.vy * cos - ball2.vx * sin;
// Conservation of momentum
const m1 = ball1.radius * ball1.radius; // mass proportional to area
const m2 = ball2.radius * ball2.radius;
const vx1Final = ((m1 - m2) * vx1 + 2 * m2 * vx2) / (m1 + m2);
const vx2Final = ((m2 - m1) * vx2 + 2 * m1 * vx1) / (m1 + m2);
// Update velocities
ball1.vx = vx1Final * cos - vy1 * sin;
ball1.vy = vy1 * cos + vx1Final * sin;
ball2.vx = vx2Final * cos - vy2 * sin;
ball2.vy = vy2 * cos + vx2Final * sin;
// Prevent overlap
const overlap = ball1.radius + ball2.radius - distance;
const moveX = overlap * cos * 0.5;
const moveY = overlap * sin * 0.5;
ball1.x -= moveX;
ball1.y -= moveY;
ball2.x += moveX;
ball2.y += moveY;
}
}
}
}
// Animation loop
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Update and draw all balls
balls.forEach((ball) => ball.update());
handleCollisions();
balls.forEach((ball) => ball.draw());
// Draw velocity vector when dragging
if (currentBall && isDragging) {
ctx.beginPath();
ctx.moveTo(currentBall.x, currentBall.y);
ctx.lineTo(dragStart.x, dragStart.y);
ctx.strokeStyle = "rgba(0, 0, 0, 0.5)";
ctx.lineWidth = 2;
ctx.stroke();
currentBall.draw();
}
requestAnimationFrame(animate);
}
// UI event handlers
sizeSlider.addEventListener("input", () => {
sizeValue.textContent = sizeSlider.value;
});
speedSlider.addEventListener("input", () => {
speedValue.textContent = speedSlider.value;
});
clearBtn.addEventListener("click", () => {
balls = [];
currentBall = null;
});
// Mouse/touch event handlers
function getMousePos(canvas, evt) {
const rect = canvas.getBoundingClientRect();
return {
x: (evt.clientX || evt.touches[0].clientX) - rect.left,
y: (evt.clientY || evt.touches[0].clientY) - rect.top,
};
}
canvas.addEventListener("mousedown", handleStart);
canvas.addEventListener("touchstart", handleStart, { passive: false });
canvas.addEventListener("mousemove", handleMove);
canvas.addEventListener("touchmove", handleMove, { passive: false });
canvas.addEventListener("mouseup", handleEnd);
canvas.addEventListener("touchend", handleEnd);
canvas.addEventListener("touchcancel", handleEnd);
function handleStart(e) {
e.preventDefault();
const pos = getMousePos(canvas, e);
// Check if we're clicking on an existing ball
for (let i = 0; i < balls.length; i++) {
const ball = balls[i];
const dx = pos.x - ball.x;
const dy = pos.y - ball.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < ball.radius) {
// Start dragging existing ball
selectedBallIndex = i;
isDragging = true;
return;
}
}
// Otherwise, create a new ball
const radius = parseInt(sizeSlider.value);
currentBall = new Ball(pos.x, pos.y, radius);
dragStart = { x: pos.x, y: pos.y };
isDragging = true;
}
function handleMove(e) {
if (!isDragging) return;
e.preventDefault();
const pos = getMousePos(canvas, e);
if (selectedBallIndex >= 0) {
// Move existing ball
balls[selectedBallIndex].x = pos.x;
balls[selectedBallIndex].y = pos.y;
balls[selectedBallIndex].vx = 0;
balls[selectedBallIndex].vy = 0;
} else {
// Update drag position for new ball
dragStart = { x: pos.x, y: pos.y };
}
}
function handleEnd(e) {
if (!isDragging) return;
isDragging = false;
if (selectedBallIndex >= 0) {
// Finished dragging existing ball
selectedBallIndex = -1;
return;
}
if (!currentBall) return;
// Calculate launch velocity
const speed = parseInt(speedSlider.value);
const angle = Math.atan2(
currentBall.y - dragStart.y,
currentBall.x - dragStart.x,
);
currentBall.vx = Math.cos(angle) * speed;
currentBall.vy = Math.sin(angle) * speed;
// Add to balls array
balls.push(currentBall);
currentBall = null;
}
// Handle window resize
window.addEventListener("resize", () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
// Start animation
animate();
</script>
</body>
</html>
@shricodev
Copy link
Copy Markdown
Author

@shricodev
Copy link
Copy Markdown
Author

Prompt: Develop a Python sandbox simulation where users can add balls of different sizes, launch them with adjustable velocity, and interact by dragging to reposition. Include basic physics to simulate gravity and collisions between balls and boundaries.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment