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])
];