Secret Places of my mind

Increased transcription of a drug transporter gene following an induced transgenerational mutation. Activity measurements pending.

Log in to post a comment.

// Secret Places of my mind

Canvas.setpenopacity(1);
console.clear();

const turtle = new Turtle();
turtle.penup();

const TAU = Math.PI * 2;
const EPS = 1e-9;
let MARK = 1;

function clamp(x, a, b) { return x < a ? a : x > b ? b : x; }
function normAngle(a) {
	a = a % TAU;
	return a < 0 ? a + TAU : a;
}

// ---------- Hash Grid ----------
class HashGrid {
	constructor(cellSize) {
		this.s = cellSize;
		this.map = new Map();
	}
	key(ix, iy) { return (ix << 16) ^ (iy & 0xffff); }
	cell(v) { return Math.floor(v / this.s); }

	insertDisk(d) {
		const s = this.s;
		const ix0 = this.cell(d.x - d.r);
		const ix1 = this.cell(d.x + d.r);
		const iy0 = this.cell(d.y - d.r);
		const iy1 = this.cell(d.y + d.r);
		for (let iy = iy0; iy <= iy1; iy++) {
			for (let ix = ix0; ix <= ix1; ix++) {
				const k = this.key(ix, iy);
				let arr = this.map.get(k);
				if (!arr) { arr = []; this.map.set(k, arr); }
				arr.push(d);
			}
		}
	}

    queryDisk(x, y, r) {
    	// new marker for this query
    	MARK++;

    	const ix0 = this.cell(x - r);
    	const ix1 = this.cell(x + r);
    	const iy0 = this.cell(y - r);
    	const iy1 = this.cell(y + r);
    
    	const out = [];
    
    	for (let iy = iy0; iy <= iy1; iy++) {
    		for (let ix = ix0; ix <= ix1; ix++) {
    			const k = this.key(ix, iy);
    			const arr = this.map.get(k);
    			if (!arr) continue;
    
    			for (let i = 0; i < arr.length; i++) {
    				const d = arr[i];
    
    				// already seen in this query
    				if (d.mark === MARK) continue;
    
    				d.mark = MARK;
    				out.push(d);
    			}
    		}
    	}
    	return out;
    }
}

// ---------- Interval merging ----------
function addInterval(list, a, b) {
	if (b < a) {
		list.push([a, TAU]);
		list.push([0, b]);
	} else {
		list.push([a, b]);
	}
}

function mergeIntervals(list) {
	if (list.length === 0) return list;
	list.sort((p, q) => p[0] - q[0]);
	const out = [list[0].slice()];
	for (let i = 1; i < list.length; i++) {
		const cur = list[i];
		const last = out[out.length - 1];
		if (cur[0] <= last[1] + 1e-6) {
			if (cur[1] > last[1]) last[1] = cur[1];
		} else {
			out.push(cur.slice());
		}
	}
	return out;
}

// ---------- Disk with hidden arcs ----------
let DISK_ID = 1;

class Disk {
	constructor(x, y, r) {
		this.x = x;
		this.y = y;
		this.r = r;
		this.id = DISK_ID++;
		this.mark = 0;
	}

	// compute masked angular intervals by neighbours
	maskedIntervals(neighbours) {
		const x0 = this.x, y0 = this.y, R = this.r;
		const R2 = R * R;
		const mask = [];

		for (let i = 0; i < neighbours.length; i++) {
			const o = neighbours[i];
			if (o.id === this.id) continue;

			const dx = o.x - x0;
			const dy = o.y - y0;
			const d2 = dx * dx + dy * dy;

			// quick reject: if circles too far, no masking of circumference
			const Rp = R + o.r;
			if (d2 >= Rp * Rp) continue;

			// if neighbor fully contains the whole circle boundary, just mask all
			// condition: distance + R <= o.r
			const d = Math.sqrt(d2) + EPS;
			if (d + R <= o.r) {
				return [[0, TAU]];
			}

			// Compute angular half width alpha on the circumference of this disk
			// cos(alpha) = (d^2 + R^2 - o.r^2) / (2 d R)
			const cosA = clamp((d2 + R2 - o.r * o.r) / (2 * d * R), -1, 1);
			const alpha = Math.acos(cosA);
			const theta = Math.atan2(dy, dx);

			let a = normAngle(theta - alpha);
			let b = normAngle(theta + alpha);
			addInterval(mask, a, b);
		}

		return mergeIntervals(mask);
	}

	drawHidden(grid, drawOffsetX, drawOffsetY) {
		const neighbours = grid.queryDisk(this.x, this.y, this.r * 2.2);
		const masked = this.maskedIntervals(neighbours);

		// visible intervals = complement of masked
		// if fully masked, skip
		if (masked.length === 1 && masked[0][0] <= 0 && masked[0][1] >= TAU - 1e-6) {
			return false;
		}

		const visible = [];
		if (masked.length === 0) {
			visible.push([0, TAU]);
		} else {
			let start = 0;
			for (let i = 0; i < masked.length; i++) {
				const a = masked[i][0];
				const b = masked[i][1];
				if (a > start + 1e-6) visible.push([start, a]);
				start = Math.max(start, b);
			}
			if (start < TAU - 1e-6) visible.push([start, TAU]);
		}

		// adaptive step: control chord length roughly constant
		const R = this.r;
		const step = Math.min(0.20 / Math.max(1, R), 0.06);

		// slight overdraw to avoid cracks at boundaries
		const pad = step * 0.5;

		for (let k = 0; k < visible.length; k++) {
			let a = visible[k][0] - pad;
			let b = visible[k][1] + pad;
			if (a < 0) a = 0;
			if (b > TAU) b = TAU;

			const xA = this.x + R * Math.cos(a);
			const yA = this.y + R * Math.sin(a);

			turtle.up();
			turtle.goto(xA + drawOffsetX, yA + drawOffsetY);
			turtle.down();

			for (let t = a + step; t <= b + 1e-9; t += step) {
				const x = this.x + R * Math.cos(t);
				const y = this.y + R * Math.sin(t);
				turtle.goto(x + drawOffsetX, y + drawOffsetY);
			}
			turtle.up();
		}

		return true;
	}
}

// ----- RNG 16807 -----

let seed = 1;

function randomSeed(s) {
	seed = (s | 0) % 2147483647;
	if (seed <= 0) seed += 2147483646;
}

function random01() {
	seed = (seed * 16807) % 2147483647;
	return (seed - 1) / 2147483646;
}

function random(a = 1, b) {
	const t = random01();
	return (b === undefined) ? t * a : a + (b - a) * t;
}

// ---- Noise 1D ----
class Perlin1D {
	constructor() {
		const p = new Uint8Array(256);
		for (let i = 0; i < 256; i++) p[i] = i;

		for (let i = 255; i > 0; i--) {
			const j = (random01() * (i + 1)) | 0;
			const tmp = p[i]; p[i] = p[j]; p[j] = tmp;
		}

		this.perm = new Uint8Array(512);
		for (let i = 0; i < 512; i++) this.perm[i] = p[i & 255];
	}

	grad(h, x) {
		const hh = h & 15;
		const g = 1 + (hh & 7);
		return (hh & 8) ? -g * x : g * x;
	}

	getValue(x) {
		const i0 = Math.floor(x) | 0;
		const i1 = i0 + 1;
		const x0 = x - i0;
		const x1 = x0 - 1;

		let t0 = 1 - x0 * x0;
		t0 *= t0;
		let t1 = 1 - x1 * x1;
		t1 *= t1;

		const n0 = t0 * t0 * this.grad(this.perm[i0 & 255], x0);
		const n1 = t1 * t1 * this.grad(this.perm[i1 & 255], x1);

		return 0.395 * (n0 + n1);
	}
}

class Line {
	constructor(x, y, a, n, w, t) {
		this.x = x;
		this.y = y;
		this.w = w;
		this.n = n;
		this.ang = a;
		this.angVel = 0;
		this.p = t;
		this.type = t;
		this.st = t === 1 ? 4 : t === 2 ? 3 : 2.5;
		this.as = t === 1 ? 0.02 : 0.025;
		this.avs = 0.2;
	}
	draw(coords, rs) {
		const r = (rs * this.w) / 2;
		coords.push(new Disk(this.x * rs, this.y * rs, r));
	}
	anim(frame, coords, rs, ge, lines, sb) {
		this.x += this.st * Math.cos(this.ang);
		this.y += this.st * Math.sin(this.ang);
		this.p += this.as;
		this.angVel += this.avs * perlin.getValue(this.p);
		this.angVel *= 0.5;
		this.ang += this.angVel;


		if (this.type === 1) {
			let dx = this.x - 400;
			let dy = this.y - 400;
			this.x -= dx / 300;
			this.y -= dy / 300;
			const d = Math.sqrt(dx * dx + dy * dy);
			const t = Math.max(0, Math.min(1, (d - 200) / (400 - 200)));
			this.angVel += t * t * (3.0 - 2.0 * t) * 0.2;
		}

		this.draw(coords, rs);

		if (this.n > 0) {
			this.n--;
			if ((frame > 850 && this.type === 2 && ge === true) || (this.type === 3 && this.n < 16)) {
				this.w += 1;
			}
			if (this.n <= 1) lines.delete(this);
		}
	}
}

//////////////////// init ///////////////////////

randomSeed(Math.round(Math.random() * 1000))
const perlin = new Perlin1D();

const rs = 0.25;
const ge = random() > 0.75;
const sb = random() > 0.03;

const coords = [];
const lines = new Set();
const head = new Line(400, 400, random() * TAU, -1, 140, 1);

function anim(frame) {
	head.anim(frame, coords, rs, ge, lines, sb);

	for (const l of Array.from(lines)) l.anim(frame, coords, rs, ge, lines, sb);


	if (sb === true) {
		lines.add(new Line(head.x, head.y, head.ang, Math.round(100 + random() * 100), 4, 3));
	}
	if (random() > 0.96) {
		lines.add(new Line(head.x, head.y, head.ang, 1000, 20, 2));
	}
}

for (let frame = 0; frame < 900; frame++) anim(frame);

coords.reverse();

// grid sizing: choose from expected max radius
let rMax = 0;
for (let i = 0; i < coords.length; i++) if (coords[i].r > rMax) rMax = coords[i].r;

const CELL = clamp(rMax * 2.2, 3, 8);
const grid = new HashGrid(CELL);

const ox = -100;
const oy = -100;

// Turtle walk
let id = 0;
let frame = 0; 
function walk() {
    frame++;
    // slow the animation down !!
    if (frame % 120 === 0) {
	    const d = coords[id];
	    const drawn = d.drawHidden(grid, ox, oy);
	    if (drawn) grid.insertDisk(d);
	    id++;
    }
	return id < coords.length - 1;
}