WORK 42
うまる
A field is punctured, and each hole is filled from its own edges — the smoothest possible answer, seamless every time. No single filling is wrong. After a hundred of them, the pattern that was there is gone. Not thinned out: redistributed. Where the fillings met, the lines crowd; where they landed, nothing is left to say.
場に穴を開けて、まわりの値だけで埋め戻す。一回ごとの埋め方に間違いはない。継ぎ目もない。それを百回くり返すと、元の模様はもうない。減ったんじゃなくて、偏った。埋めたところは空っぽになり、埋めそこねた縁に線が押し寄せる。
穴を埋めるのに使えるのは、その穴のまわりにあるものだけ。だから埋まったところには、まわりの情報しか入っていない。新しいものは、一切生まれない。
それでも埋まったあとの場は連続していて、どこにも継ぎ目がない。もっともらしさは、そこに何かがある証拠にならない。
一箇所だけ、一度も穴の開かなかった領域がある。どこかは決めていない。
/**
* umaru — 埋まる / Filling In
*
* 場に穴を開ける。穴は、周りの値から埋め戻される。
* 一回一回の埋め方は正しい。境界と滑らかに繋がり、どこにも不連続がない。
* それを何十回も繰り返すと、元の模様は残っていない。
*
* どの一回にも、間違いはない。
*/
const G = 200; // 場の解像度
const SIZE = 720;
const ROUNDS = 110; // 穴を開けて埋める回数
const R_MIN = 9;
const R_MAX = 26;
const RELAX = 90; // 一回あたりの拡散反復
const STRIPE = 15.5; // 縞の細かさ
const INK = [38, 42, 52];
const PAPER = [252, 250, 245];
export default function sketch(p) {
let field;
const idx = (x, y) => y * G + x;
p.setup = () => {
p.createCanvas(SIZE, SIZE);
p.pixelDensity(1);
const seed = 42115;
p.noiseSeed(seed);
p.randomSeed(seed);
field = new Float32Array(G * G);
// --- 元の模様。方向を持った、細かい層 ---
p.noiseDetail(4, 0.55);
for (let y = 0; y < G; y++) {
for (let x = 0; x < G; x++) {
const u = x / G;
const v = y / G;
// 斜めに走る層 + ゆらぎ。密度がどこも同じ状態から始める
const warp = p.noise(u * 2.3, v * 2.3) - 0.5;
const fine = p.noise(u * 9.0 + 40, v * 9.0 + 40) - 0.5;
field[idx(x, y)] =
(u * 0.62 + v * 0.38) * 3.1 + warp * 2.4 + fine * 0.55;
}
}
// --- 一度も穴が開かない場所。どこかは決めていない ---
p.noiseDetail(2, 0.5);
const guard = new Float32Array(G * G);
for (let y = 0; y < G; y++) {
for (let x = 0; x < G; x++) {
guard[idx(x, y)] = p.noise((x / G) * 2.4 + 300, (y / G) * 2.4 + 300);
}
}
const mask = new Uint8Array(G * G);
const buf = new Float32Array(G * G);
for (let round = 0; round < ROUNDS; round++) {
const cx = Math.floor(p.random(G));
const cy = Math.floor(p.random(G));
if (guard[idx(cx, cy)] > 0.57) continue; // 守られている側に落ちたら、何も起きない
const r = p.random(R_MIN, R_MAX);
const r2 = r * r;
const x0 = Math.max(1, Math.floor(cx - r));
const x1 = Math.min(G - 2, Math.ceil(cx + r));
const y0 = Math.max(1, Math.floor(cy - r));
const y1 = Math.min(G - 2, Math.ceil(cy + r));
mask.fill(0);
let any = false;
for (let y = y0; y <= y1; y++) {
for (let x = x0; x <= x1; x++) {
const dx = x - cx;
const dy = y - cy;
if (dx * dx + dy * dy <= r2 && guard[idx(x, y)] <= 0.57) {
mask[idx(x, y)] = 1;
any = true;
}
}
}
if (!any) continue;
// 穴の中身を捨てる。残っているのは縁だけ
for (let y = y0; y <= y1; y++) {
for (let x = x0; x <= x1; x++) {
if (mask[idx(x, y)]) field[idx(x, y)] = 0;
}
}
// 縁から内側へ、平均で埋め戻す(ラプラス方程式の緩和)
for (let it = 0; it < RELAX; it++) {
for (let y = y0; y <= y1; y++) {
for (let x = x0; x <= x1; x++) {
const i = idx(x, y);
if (!mask[i]) continue;
buf[i] =
(field[i - 1] + field[i + 1] + field[i - G] + field[i + G]) * 0.25;
}
}
for (let y = y0; y <= y1; y++) {
for (let x = x0; x <= x1; x++) {
const i = idx(x, y);
if (mask[i]) field[i] = buf[i];
}
}
}
}
render();
p.noLoop();
};
const sample = (fx, fy) => {
const x = Math.min(G - 2, Math.max(0, fx));
const y = Math.min(G - 2, Math.max(0, fy));
const xi = Math.floor(x);
const yi = Math.floor(y);
const tx = x - xi;
const ty = y - yi;
const a = field[idx(xi, yi)];
const b = field[idx(xi + 1, yi)];
const c = field[idx(xi, yi + 1)];
const d = field[idx(xi + 1, yi + 1)];
return (
a * (1 - tx) * (1 - ty) +
b * tx * (1 - ty) +
c * (1 - tx) * ty +
d * tx * ty
);
};
function render() {
p.background(PAPER[0], PAPER[1], PAPER[2]);
p.loadPixels();
const s = G / SIZE;
const px = p.pixels;
for (let py = 0; py < SIZE; py++) {
for (let pxi = 0; pxi < SIZE; pxi++) {
const v = sample(pxi * s, py * s);
// 縞。値の変化が速いほど線が細かく詰まる
const band = Math.abs(Math.sin(v * STRIPE));
// 線として立てる:0 に近いところだけ濃く
const ink = Math.pow(1 - band, 7.5);
const o = (py * SIZE + pxi) * 4;
px[o] = PAPER[0] + (INK[0] - PAPER[0]) * ink;
px[o + 1] = PAPER[1] + (INK[1] - PAPER[1]) * ink;
px[o + 2] = PAPER[2] + (INK[2] - PAPER[2]) * ink;
px[o + 3] = 255;
}
}
p.updatePixels();
}
}