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;
}