A burst of lines get spawned in a circle. They move in a direction and bounce off the sides and other lines for a few times.
I vibe coded this turtle ๐
Log in to post a comment.
const circleRadius = 75; // min=20 max=80 step=5 Circle size for spawn area const numLines = 600; // min=50 max=1000 step=1 Number of bouncing lines const maxBounces = 25; // min=1 max=50 step=1 Maximum bounces before stopping const angleSnap = 8; // min=1 max=16 step=1 Number of angle divisions (1=no snap, 8=45ยฐ increments) const startAngleRandom = 0.2; // min=0 max=2 step=0.01 const moveDistance = 4; // min=1 max=5 step=0.1 Movement speed per step const areaSize = 90; // min=70 max=120 step=10 Canvas area size const maxIterations = 100000; const lines = []; const allSegments = []; function lineSegmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4) { var a_dx = x2 - x1; var a_dy = y2 - y1; var b_dx = x4 - x3; var b_dy = y4 - y3; var denominator = (-b_dx * a_dy + a_dx * b_dy); if (Math.abs(denominator) < 1e-10) return false; // Lines are parallel var s = (-a_dy * (x1 - x3) + a_dx * (y1 - y3)) / denominator; var t = (+b_dx * (y1 - y3) - b_dy * (x1 - x3)) / denominator; if (s >= 0 && s <= 1 && t >= 0 && t <= 1) { let intersectionX = x1 + t * a_dx; let intersectionY = y1 + t * a_dy; // Calculate reflection angle let wallDx = x4 - x3; let wallDy = y4 - y3; let wallLength = Math.sqrt(wallDx * wallDx + wallDy * wallDy); if (wallLength > 0) { // Normal vector to the wall (perpendicular) let nx = -wallDy / wallLength; let ny = wallDx / wallLength; // Incoming velocity let vx = a_dx; let vy = a_dy; // Reflect velocity: v' = v - 2(vยทn)n let dot = 2 * (vx * nx + vy * ny); let reflectedVx = vx - dot * nx; let reflectedVy = vy - dot * ny; let reflectedAngle = Math.atan2(reflectedVy, reflectedVx); return { x: intersectionX, y: intersectionY, angle: reflectedAngle }; } } return false; } class BouncingLine { constructor(x, y, angle, isStatic = false) { this.turtle = new Turtle(); this.x = x; this.y = y; this.baseAngle = angle; this.angle = angle; this.bounceCount = 0; this.isStatic = isStatic; this.active = !isStatic; this.segments = []; this.turtle.jump(x, y); } addSegment(x1, y1, x2, y2) { let segment = {x1, y1, x2, y2, line: this}; this.segments.push(segment); allSegments.push(segment); if (!this.isStatic) this.turtle.goto(x2, y2); } move() { if (!this.active) return; if (this.x < -areaSize || this.y < -areaSize || this.x > areaSize || this.y > areaSize) { this.active = false; return; } let startX = this.x; let startY = this.y; let endX = this.x + Math.cos(this.angle) * moveDistance; let endY = this.y + Math.sin(this.angle) * moveDistance; let closestIntersection = null; let closestDistance = Infinity; for (let segment of allSegments) { // avoid self-intersection if (segment.line === this && segment === this.segments.at(-1)) continue; let intersection = lineSegmentsIntersect( startX, startY, endX, endY, segment.x1, segment.y1, segment.x2, segment.y2 ); if (intersection) { let distance = Math.sqrt( (intersection.x - startX) ** 2 + (intersection.y - startY) ** 2 ); if (distance < closestDistance && distance > 0.01) { closestDistance = distance; closestIntersection = intersection; } } } if (closestIntersection) { // on hit something - draw to intersection point and bounce this.addSegment(startX, startY, closestIntersection.x, closestIntersection.y); this.x = closestIntersection.x; this.y = closestIntersection.y; this.angle = snapAngle(closestIntersection.angle + this.baseAngle, angleSnap) - this.baseAngle + (0.15 + Math.random() * 0.3); this.baseAngle = snapAngle(this.baseAngle, angleSnap); this.bounceCount++; if (this.bounceCount >= maxBounces) { this.active = false; } } else { // No collision - move normally this.addSegment(startX, startY, endX, endY); this.x = endX; this.y = endY; } } } function snapAngle(angle, angleSnap = 0) { if (angleSnap > 1) { const snapIncrement = (Math.PI * 2) / angleSnap; return Math.round(angle / snapIncrement) * snapIncrement; } return angle; } function createBoundaries() { let boundary = new BouncingLine(-areaSize, -areaSize, 0, true); boundary.addSegment(-areaSize, -areaSize, areaSize, -areaSize); boundary.addSegment(areaSize, -areaSize, areaSize, areaSize); boundary.addSegment(areaSize, areaSize, -areaSize, areaSize); boundary.addSegment(-areaSize, areaSize, -areaSize, -areaSize); lines.push(boundary); } function addLine(i, circlePos) { let angle = (Math.PI * 2 * i) / numLines + Math.random() * 0.5; let radius = (Math.random()**0.5) * circleRadius; let x = circlePos[0] + Math.cos(angle) * radius; let y = circlePos[1] + Math.sin(angle) * radius; x = Math.max(-areaSize + 5, Math.min(areaSize - 5, x)); y = Math.max(-areaSize + 5, Math.min(areaSize - 5, y)); let direction = (i / numLines) * Math.PI * 2; direction = snapAngle(direction, angleSnap) + (-startAngleRandom + Math.random() * startAngleRandom); lines.push(new BouncingLine(x, y, direction)); } const circlePos = [ -areaSize/2 + Math.random() * areaSize, -areaSize/2 + Math.random() * areaSize ]; /* // Draw spawn circle (visual reference) const circleTurtle = new Turtle(); circleTurtle.jump(circlePos[0] , circlePos[1] - circleRadius); circleTurtle.circle(circleRadius); */ createBoundaries(); function walk(i) { let c = 20; while (c-- > 0 && lines.length < numLines) { addLine(lines.length, circlePos); } let activeCount = 0; for (let line of lines) { if (line.active) { line.move(); activeCount++; } } return activeCount > 0 && i < maxIterations; }