Populations of bacteria
Log in to post a comment.
// You can find the Turtle API reference here: https://turtletoy.net/syntax Canvas.setpenopacity(1); const specs = 160; //min=1 max=300 step=1 const petriRadius = 80; //min=50 max=100 step=1 const evenDistribution = 1; //min=0 max=1 step=1 (No, Yes) const pi2 = Math.PI * 2; const minPopSize = 2; //min=1 max=10 step=1 const maxPopSize = 9; //min=5 max=20 step=1 // Global code will be evaluated once. const turtle = new Turtle(); let blobs = []; for(let i = 0; i < specs; i++) { let r = (evenDistribution == 1? Math.sqrt(Math.random()): Math.random()) * petriRadius; let a = Math.random() * pi2; blobs.push([ [Math.cos(a) * r, Math.sin(a) * r], minPopSize+(Math.random()**2 * Math.max(minPopSize, maxPopSize - minPopSize)), [[null, 0]], [[null, pi2]] ]); } turtle.jump(0,-petriRadius); turtle.circle(petriRadius); function processBlobs(blobs) { function rot2(a) { return [Math.cos(a), -Math.sin(a), Math.sin(a), Math.cos(a)]; } function trans2(m, a) { return [m[0]*a[0]+m[2]*a[1], m[1]*a[0]+m[3]*a[1]]; } const deepClone = (o) => JSON.parse(JSON.stringify(o)); const isPointInBlob = (pt, blob) => ((blob[0][0] - pt[0])**2 + (blob[0][1] - pt[1])**2) <= (blob[1]**2); function radClip(a) { while(a < 0) { a += pi2; } while(a > pi2) { a -= pi2; } return a; } for(let i = 0; i < blobs.length; i++) { let currentBlob = deepClone(blobs[i]); for(let j = 0; j < blobs.length; j++) { if(i == j) continue; let otherBlob = deepClone(blobs[j]); otherBlob[0][0] -= currentBlob[0][0]; otherBlob[0][1] -= currentBlob[0][1]; let angle = 0; let dx = otherBlob[0][0]; let dy = otherBlob[0][1]; if(dx == 0) { angle = Math.PI * (dy > 0? .5: 1.5); } else { let dydx = dy/dx; angle = Math.atan(dy/dx); if(dx < 0) { angle += Math.PI } } otherBlob[0] = trans2(rot2(angle), otherBlob[0]); dx = otherBlob[0][0]; dy = otherBlob[0][1]; if(dx == 0) continue; //blob has same center coordinates let x = (dx**2 - otherBlob[1]**2 + currentBlob[1]**2) / (2 * dx); if(Math.abs(x) > currentBlob[1] ) continue; // blob is further away than currentBlob //let y = Math.sqrt(currentBlob[1]**2 - x**2); if(Math.acos(x/currentBlob[1]) == NaN) continue; if(blobs[i][2].length == 1 && blobs[i][2][0][0] == null) { blobs[i][2] = []; } if(blobs[i][3].length == 1 && blobs[i][3][0][0] == null) { blobs[i][3] = []; } //inangle blobs[i][2].push([j, radClip(Math.acos(x/currentBlob[1]) + angle)]); //outangle blobs[i][3].push([j, radClip((Math.PI * 2 - Math.acos(x/currentBlob[1])) + angle)]); } } //remove all ins and outs from blobs that are in a blob that is not the blob itself or the intersection blob //for every blob for(let i = 0; i < blobs.length; i++) { //for every draw-start (in) for(let ins = 0; ins < blobs[i][2].length; ins++) { let deleted = false; //for every other blob for(let j = 0; j < blobs.length; j++) { //than the 'current' one (i) if(i == j) continue; //or the one associated with the draw-start (in) if(blobs[i][2][ins][0] == j) continue; //todo: check if point is in circle j, and if so, remove it from the list if(isPointInBlob([ blobs[i][0][0] + (Math.cos(blobs[i][2][ins][1]) * blobs[i][1]), blobs[i][0][1] + (Math.sin(blobs[i][2][ins][1]) * blobs[i][1]) ], blobs[j])) { blobs[i][2] = blobs[i][2].filter((elm, idx) => idx != ins); ins--; deleted = true; j = blobs.length; } } //check if 'in' is outside of petri dish if( !deleted && //blobs[i][2][ins][0] == -1 && (blobs[i][0][0] + (Math.cos(blobs[i][2][ins][1]) * blobs[i][1]))**2+ (blobs[i][0][1] + (Math.sin(blobs[i][2][ins][1]) * blobs[i][1]))**2 > petriRadius**2 ) { blobs[i][2] = blobs[i][2].filter((elm, idx) => idx != ins); ins--; } } } //Add ins and outs for petri dish itself for(let i = 0; i < blobs.length; i++) { let currentBlob = deepClone(blobs[i]); let otherBlob = [[0,0], petriRadius, [null, 0], [null, pi2]]; otherBlob[0][0] -= currentBlob[0][0]; otherBlob[0][1] -= currentBlob[0][1]; let angle = 0; let dx = otherBlob[0][0]; let dy = otherBlob[0][1]; if(dx == 0) { angle = Math.PI * (dy > 0? .5: 1.5); } else { let dydx = dy/dx; angle = Math.atan(dy/dx); if(dx < 0) { angle += Math.PI } } otherBlob[0] = trans2(rot2(angle), otherBlob[0]); dx = otherBlob[0][0]; dy = otherBlob[0][1]; if(dx == 0) continue; //blob has same center coordinates let x = (dx**2 - otherBlob[1]**2 + currentBlob[1]**2) / (2 * dx); if(Math.abs(x) > currentBlob[1] ) continue; // blob is further away than currentBlob //let y = Math.sqrt(currentBlob[1]**2 - x**2); if(Math.acos(x/currentBlob[1]) == NaN) continue; if(blobs[i][2].length == 1 && blobs[i][2][0][0] == null) { blobs[i][2] = []; } if(blobs[i][3].length == 1 && blobs[i][3][0][0] == null) { blobs[i][3] = []; } //inangle blobs[i][3].push([-1, radClip(Math.acos(x/currentBlob[1]) + angle)]); //outangle blobs[i][2].push([-1, radClip((pi2 - Math.acos(x/currentBlob[1])) + angle)]); } //remove all ins and outs from blobs that are in a blob that is not the blob itself or the intersection blob //for every blob for(let i = 0; i < blobs.length; i++) { //for every draw-start (in) for(let ins = 0; ins < blobs[i][2].length; ins++) { let deleted = false; //for every other blob for(let j = 0; j < blobs.length; j++) { //than the 'current' one (i) if(i == j) continue; //or the one associated with the draw-start (in) if(blobs[i][2][ins][0] == j) continue; //todo: check if point is in circle j, and if so, remove it from the list if(isPointInBlob([ blobs[i][0][0] + (Math.cos(blobs[i][2][ins][1]) * blobs[i][1]), blobs[i][0][1] + (Math.sin(blobs[i][2][ins][1]) * blobs[i][1]) ], blobs[j])) { blobs[i][2] = blobs[i][2].filter((elm, idx) => idx != ins && elm[0] != -1); ins--; deleted = true; j = blobs.length; } } } } for(let i = 0; i < blobs.length; i++) { blobs[i][2].sort((a, b) => (a[1] < b[1]) ? -1 : 1) blobs[i][3].sort((a, b) => (a[1] < b[1]) ? -1 : 1) } return blobs; } let circles = processBlobs(blobs); // The walk function will be called until it returns false. function walk(i) { //return; for(let j = 0; j < circles[i][2].length; j++) { drawCirclePart(turtle, circles[i], circles[i][2][j][1], circles[i][3][getFirstOutAfter(circles[i][3], circles[i][2][j][1])][1] ); } return i < circles.length - 1; } function getFirstOutAfter(outs, start) { let min = 2000; let idx = null; for(let i = 0; i < outs.length; i++) { let test = outs[i][1] + (outs[i][1] < start? pi2: 0); if(test < min) { min = test; idx = i; } } return idx; } function drawCirclePart(turtle, blob, from = 0, to = pi2) { if(to < from) { to += pi2; } turtle.jump(getBlobCoordinateAt(blob, from)); let step = pi2 / Math.ceil(Math.PI / Math.asin(.5 / blob[1])); for(let a = from + step; a <= to; a+=step) { turtle.goto(getBlobCoordinateAt(blob, a)); } turtle.goto(getBlobCoordinateAt(blob, to)); } const getBlobCoordinateAt = (blob, angle) => [ blob[0][0] + (Math.cos(angle) * blob[1]), blob[0][1] + (Math.sin(angle) * blob[1]) ];