ornstein-saber-eval / canvas_physics_sandbox.html
KyleHessling1's picture
Initial eval upload: thinking-on full bench, tool-calling, report
ed47c9c verified
Raw
History Blame Contribute Delete
6.12 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Soft-Body Physics Sandbox</title>
<style>
body { margin: 0; overflow: hidden; background: #000; }
canvas { display: block; background: #f5f5f5; }
.toolbar { position: absolute; top: 10px; left: 10px; display: flex; gap: 8px; }
.toolbar button { padding: 8px 16px; border: none; border-radius: 6px; background: #4CAF50; color: white; cursor: pointer; }
.toolbar button:hover { background: #45a049; }
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div class="toolbar">
<button id="add-circle">Add Circle</button>
<button id="clear">Clear</button>
<button id="toggle-walls">Toggle Walls</button>
<button id="gravity-dir">Gravity Direction</button>
</div>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let width = window.innerWidth;
let height = window.innerHeight;
canvas.width = width;
canvas.height = height;
let circles = [];
let gravity = { x: 0, y: 0.3 };
let walls = true;
let dragCircle = null;
let dragOffset = { x: 0, y: 0 };
const pastelColors = ['#FFB3BA', '#FFDFBA', '#FFFFBA', '#BAFFC9', '#BAE1FF', '#E6BAFF'];
class Circle {
constructor(x, y, radius, color) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.vx = 0;
this.vy = 0;
this.deformation = 0;
}
update(dt) {
this.vy += gravity.y * dt;
this.vx += gravity.x * dt;
this.x += this.vx * dt;
this.y += this.vy * dt;
if (walls) {
this.handleWallCollisions();
}
this.deformation = Math.max(0, this.deformation - 0.1 * dt);
}
handleWallCollisions() {
if (this.x - this.radius < 0) {
this.x = this.radius;
this.vx *= -0.8;
}
if (this.x + this.radius > width) {
this.x = width - this.radius;
this.vx *= -0.8;
}
if (this.y - this.radius < 0) {
this.y = this.radius;
this.vy *= -0.8;
}
if (this.y + this.radius > height) {
this.y = height - this.radius;
this.vy *= -0.8;
}
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius + this.deformation, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
}
}
function handleCircleCollisions() {
for (let i = 0; i < circles.length; i++) {
for (let j = i + 1; j < circles.length; j++) {
const dx = circles[j].x - circles[i].x;
const dy = circles[j].y - circles[i].y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < circles[i].radius + circles[j].radius) {
const nx = dx / dist;
const ny = dy / dist;
const overlap = (circles[i].radius + circles[j].radius) - dist;
const separation = overlap / 2;
circles[i].x -= nx * separation;
circles[i].y -= ny * separation;
circles[j].x += nx * separation;
circles[j].y += ny * separation;
const v1 = { x: circles[i].vx, y: circles[i].vy };
const v2 = { x: circles[j].vx, y: circles[j].vy };
const dot = v1.x * nx + v1.y * ny;
const dot2 = v2.x * nx + v2.y * ny;
const impulse = 2 * (dot - dot2) / (circles[i].radius + circles[j].radius);
circles[i].vx -= impulse * nx;
circles[i].vy -= impulse * ny;
circles[j].vx += impulse * nx;
circles[j].vy += impulse * ny;
circles[i].deformation += 5;
circles[j].deformation += 5;
}
}
}
}
function addCircle() {
const radius = 20 + Math.random() * 15;
const color = pastelColors[Math.floor(Math.random() * pastelColors.length)];
const x = Math.random() * (width - radius * 2) + radius;
const y = Math.random() * (height - radius * 2) + radius;
circles.push(new Circle(x, y, radius, color));
}
function clearCircles() {
circles = [];
}
function toggleWalls() {
walls = !walls;
}
function rotateGravity() {
const angle = Math.atan2(gravity.y, gravity.x) + Math.PI / 2;
gravity.x = Math.cos(angle) * 0.3;
gravity.y = Math.sin(angle) * 0.3;
}
canvas.addEventListener('mousedown', (e) => {
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
for (let circle of circles) {
if (Math.hypot(mx - circle.x, my - circle.y) < circle.radius) {
dragCircle = circle;
dragOffset.x = circle.x - mx;
dragOffset.y = circle.y - my;
break;
}
}
});
canvas.addEventListener('mousemove', (e) => {
if (dragCircle) {
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
dragCircle.x = mx + dragOffset.x;
dragCircle.y = my + dragOffset.y;
dragCircle.vx = 0;
dragCircle.vy = 0;
}
});
canvas.addEventListener('mouseup', () => {
if (dragCircle) {
dragCircle.vx = (dragCircle.x - dragCircle.x) * 50;
dragCircle.vy = (dragCircle.y - dragCircle.y) * 50;
dragCircle = null;
}
});
document.getElementById('add-circle').addEventListener('click', addCircle);
document.getElementById('clear').addEventListener('click', clearCircles);
document.getElementById('toggle-walls').addEventListener('click', toggleWalls);
document.getElementById('gravity-dir').addEventListener('click', rotateGravity);
function animate(timestamp) {
ctx.clearRect(0, 0, width, height);
for (let circle of circles) {
circle.update(0.016);
}
handleCircleCollisions();
for (let circle of circles) {
circle.draw();
}
requestAnimationFrame(animate);
}
function init() {
for (let i = 0; i < 80; i++) {
addCircle();
}
animate();
}
window.addEventListener('resize', () => {
width = window.innerWidth;
height = window.innerHeight;
canvas.width = width;
canvas.height = height;
});
init();
</script>
</body>
</html>