Spaces:
Running
Running
let boundaries = []; | |
let balls = []; | |
let drawingBoundary = false; | |
let boundaryDrawn = false; | |
let w, h; | |
let entropyGraph = []; | |
let startTime; | |
function setup() { | |
w = windowWidth; | |
h = windowHeight; | |
canvas = createCanvas(w, h); | |
startTime = millis(); | |
} | |
function draw() { | |
background(0); | |
// Draw the boundaries | |
stroke(255); | |
strokeWeight(2); | |
if (boundaries.length >= 3) { | |
beginShape(); | |
for (let v of boundaries) { | |
vertex(v.x, v.y); | |
} | |
endShape(CLOSE); | |
} | |
// Update and draw the balls | |
for (let ball of balls) { | |
ball.move(); | |
ball.checkBoundaryCollision(); | |
ball.display(); | |
} | |
// Calculate and display entropy | |
if (boundaryDrawn && balls.length > 0) { | |
let entropy = calculateEntropy(); | |
entropyGraph.push(entropy); | |
displayEntropyGraph(); | |
} | |
// Display instructions | |
displayInstructions(); | |
} | |
function mouseDragged() { | |
if (!drawingBoundary && !boundaryDrawn) { | |
boundaries = []; | |
drawingBoundary = true; | |
} | |
if (drawingBoundary) { | |
boundaries.push({ x: mouseX, y: mouseY }); | |
} | |
} | |
function mouseReleased() { | |
drawingBoundary = false; | |
boundaryDrawn = true; | |
} | |
function mouseClicked() { | |
if (boundaryDrawn) { | |
balls.push(new Ball()); | |
} | |
} | |
class Ball { | |
constructor() { | |
this.x = mouseX; | |
this.y = mouseY; | |
this.vx = random(-2, 2); | |
this.vy = random(-2, 2); | |
this.radius = 2; | |
this.color = color(random(100, 255), random(100, 255), random(100, 255)); | |
} | |
move() { | |
this.x += this.vx; | |
this.y += this.vy; | |
} | |
checkBoundaryCollision() { | |
let isOutside = !this.isPointInPolygon(boundaries, this.x, this.y); | |
if (isOutside) { | |
let closestEdge = this.findClosestEdge(boundaries); | |
let edgeNormal = p5.Vector.fromAngle(closestEdge.angle + HALF_PI); | |
let reflectedVelocity = this.reflectVector(createVector(this.vx, this.vy), edgeNormal); | |
this.vx = reflectedVelocity.x; | |
this.vy = reflectedVelocity.y; | |
// Move the ball back inside the boundary | |
this.x = constrain(this.x, closestEdge.x1, closestEdge.x2); | |
this.y = constrain(this.y, closestEdge.y1, closestEdge.y2); | |
} | |
} | |
// findClosestEdge(polygon) { | |
// // ... (keep the existing implementation) | |
// } | |
// distanceToLine(edge) { | |
// // ... (keep the existing implementation) | |
// } | |
// reflectVector(vector, normal) { | |
// // ... (keep the existing implementation) | |
// } | |
// isPointInPolygon(polygon, px, py) { | |
// // ... (keep the existing implementation) | |
// } | |
findClosestEdge(polygon) { | |
let closestEdge = null; | |
let closestDistance = Infinity; | |
for (let i = 0; i < polygon.length; i++) { | |
let j = (i + 1) % polygon.length; | |
let x1 = polygon[i].x; | |
let y1 = polygon[i].y; | |
let x2 = polygon[j].x; | |
let y2 = polygon[j].y; | |
let edge = { x1, y1, x2, y2 }; | |
let distance = this.distanceToLine(edge); | |
if (distance < closestDistance) { | |
closestDistance = distance; | |
closestEdge = edge; | |
} | |
} | |
let dx = closestEdge.x2 - closestEdge.x1; | |
let dy = closestEdge.y2 - closestEdge.y1; | |
closestEdge.angle = atan2(dy, dx); | |
return closestEdge; | |
} | |
distanceToLine(edge) { | |
let x1 = edge.x1; | |
let y1 = edge.y1; | |
let x2 = edge.x2; | |
let y2 = edge.y2; | |
let dx = x2 - x1; | |
let dy = y2 - y1; | |
let a = dy; | |
let b = -dx; | |
let c = dx * y1 - dy * x1; | |
let dist = Math.abs(a * this.x + b * this.y + c) / Math.sqrt(a * a + b * b); | |
return dist; | |
} | |
reflectVector(vector, normal) { | |
let dotProduct = vector.x * normal.x + vector.y * normal.y; | |
let reflectedVector = p5.Vector.sub(vector, p5.Vector.mult(normal, 2 * dotProduct)); | |
return reflectedVector; | |
} | |
isPointInPolygon(polygon, px, py) { | |
const epsilon = 0.01; // Adjust this value as needed | |
const radius = this.radius; | |
let isInside = false; | |
let j = polygon.length - 1; | |
for (let i = 0; i < polygon.length; i++) { | |
let x1 = polygon[i].x; | |
let y1 = polygon[i].y; | |
let x2 = polygon[j].x; | |
let y2 = polygon[j].y; | |
if ((y1 > py + radius + epsilon) !== (y2 > py + radius + epsilon) && | |
px + radius + epsilon < ((x2 - x1) * (py + radius + epsilon - y1)) / (y2 - y1) + x1) { | |
isInside = !isInside; | |
} | |
j = i; | |
} | |
return isInside; | |
} | |
display() { | |
fill(this.color); | |
noStroke(); | |
ellipse(this.x, this.y, this.radius * 2, this.radius * 2); | |
} | |
} | |
function calculateEntropy() { | |
const gridSize = 10; | |
const grid = {}; | |
const totalBalls = balls.length; | |
for (let ball of balls) { | |
const gridX = Math.floor(ball.x / gridSize); | |
const gridY = Math.floor(ball.y / gridSize); | |
const key = `${gridX},${gridY}`; | |
grid[key] = (grid[key] || 0) + 1; | |
} | |
let entropy = 0; | |
for (let count of Object.values(grid)) { | |
const probability = count / totalBalls; | |
entropy -= probability * Math.log2(probability); | |
} | |
return entropy; | |
} | |
function displayEntropyGraph() { | |
const graphWidth = 200; | |
const graphHeight = 100; | |
const x = width - graphWidth - 10; | |
const y = height - graphHeight - 10; | |
fill(0, 150); | |
rect(x, y, graphWidth, graphHeight); | |
stroke(255); | |
noFill(); | |
beginShape(); | |
for (let i = 0; i < entropyGraph.length; i++) { | |
const px = map(i, 0, entropyGraph.length - 1, x, x + graphWidth); | |
const py = map(entropyGraph[i], 0, 5, y + graphHeight, y); | |
vertex(px, py); | |
} | |
endShape(); | |
fill(255); | |
noStroke(); | |
textAlign(RIGHT); | |
text(`Entropy: ${entropyGraph[entropyGraph.length - 1].toFixed(2)}`, x + graphWidth, y - 5); | |
} | |
function displayInstructions() { | |
fill(255); | |
noStroke(); | |
textAlign(LEFT); | |
textSize(14); | |
text("1. Draw a boundary by dragging the mouse", 10, 20); | |
text("2. Click inside the boundary to add balls", 10, 40); | |
text("3. Observe the entropy graph in the bottom right", 10, 60); | |
} | |
function windowResized() { | |
w = windowWidth; | |
h = windowHeight; | |
resizeCanvas(w, h); | |
} | |