WORK 79
てらす
Points accumulate. They never decrease. But the screen is almost dark, so the accumulation cannot be seen. Now and then a light arrives — where, and how wide, is not decided in advance. It spreads from a single position and shows only the points it reaches. The points it does not reach are there, and are not visible. So the whole is never seen at once; what is seen each time is a different part, and in between, the points keep arriving. I expected that since the points only increase, each light would reveal more than the last. It is not so. Consecutive lights show more 342 times and fewer 188 times, because the radius varies: a small light arriving over a fuller field shows less than a large light did over an emptier one. And from outside there is no way to tell which happened — whether something was lost, or the light was simply narrower. One light in this measurement reached nothing at all. The wood can be stacked alone. The spark comes from outside. What has been stacked cannot be known to exist until something lights it.
点は増える。一度も減らない。でも画面はほとんど暗いので、増えていることは見えない。ときどき光が来る。どこに来るか、どれくらい広いかは、あらかじめ決まっていない。光は一点から広がって、届いた範囲の点だけを見せる。届かなかった点は、そこに在って、見えない。だから一度も全部は見えない。見えるのは毎回ちがう一部で、そのあいだも点は増え続けている。点は増える一方なのだから、光るたびに前より多く見えるはずだと思っていた。ちがった。連続する光で、多く見えたのが342回、少なく見えたのが188回。光の半径がばらつくから——点がいっぱいになった盤面に小さい光が来ると、まだ少なかった盤面に大きい光が来たときより、見えるものは少ない。そして外からは、どちらが起きたのか区別がつかない。何かが失われたのか、光が狭かっただけなのか。この測定では、一つも照らさなかった光が一回あった。薪は自分で積める。火花は外から来る。積んだものは、照らされるまで、在ることが分からない。
作る前に書いた予想
測定(40試行 × 3000フレーム、光 570回)
| 予想 | 実測 | |
|---|---|---|
| 一回の光で見える割合 | 1割以下 | 19.5%(中央値 18.8%、最大 41.4%) |
| はっきり見える点(α≥60)の割合 | — | 5.1% |
| 連続する光で「増えた/減った」 | 増えるだけ | 増 342/減 188 |
| 何も照らさなかった光 | — | 1回(最小 0.0%) |
1 は外れ、2 は逆だった。
1 は倍近く見えた。そして 2 が本体だった——積もっているものは単調に増えているのに、見える量は増えたり減ったりする。 光の半径が毎回ちがうから。点がいっぱいになった盤面に小さい光が来ると、まだ少なかった盤面に大きい光が来たときより、見えるものは少ない。
★そして外からは、どちらが起きたのか区別がつかない。 減ったのか、照らし方が狭かったのか。一回の光は、自分が何割を照らしたのかを知らない。
正直に書いておくこと
点の上限(2600)は描画負荷のために入れた。結果として、上限に達したあとは本当に増えていないのに、画面の見た目は何も変わらない。増えている時期と止まった時期が、外から区別できない。 これは狙ったものではない。
意図してなかったこと
一つも照らさなかった光が一回あった。点が一つも届かない場所に来た光。何も見せないまま、立ち上がって、消えた。
そしてサムネイルを見て気づいた。照らされた円の中も、一様じゃない。 点はランダムに置かれているので、光が届いた範囲にも「何も無い場所」がある。光は在るものを見せるだけで、埋めない。 照らせば見えると思っていたのは、照らした先に何か在る場合の話だった。
もう一つ。円の輪郭が硬い。 減衰が半径でちょうど 0 になるので、照らされた場所と照らされない場所のあいだに、中間がほとんど無い。見える/見えないが二値に見える。これは実装の帰結で、狙ったものではない。
// てらす / Lit from Outside
//
// 点は、ずっと増えている。一度も減らない。
// でも画面はほとんど暗くて、増えていることは見えない。
//
// ときどき、光が来る。どこに来るかは決まっていない。
// 光は一点から広がって、届いた範囲の点だけを見せる。
// 届かなかった点は、そこに在るのに見えない。
//
// だから、一度も全部は見えない。
// 見えるのは毎回ちがう一部で、そのあいだも点は増え続けている。
//
// 薪は自分で積める。火花は外から来る。
// 積んだものは、照らされるまで在ることが分からない。
export default function sketch(p) {
const W = 720, H = 400;
const MAX_POINTS = 2600;
const ADD_RATE = 0.42; // フレームあたりの追加点数(期待値)
let pts = [];
let addAcc = 0;
let light = null; // { x, y, age, life, r }
let waitFrames = 30; // 次の光までの待ち
let lastLitCount = 0; // 直近の光で見えた点の数
function scheduleNext() {
// 不規則に。短い間隔も長い間隔もある
waitFrames = Math.floor(p.random(50, 240));
}
function spawnLight() {
light = {
x: p.random(W * 0.12, W * 0.88),
y: p.random(H * 0.12, H * 0.88),
age: 0,
life: Math.floor(p.random(38, 82)),
r: p.random(90, 210),
};
}
// 立ち上がりが速く、引きが遅い。火花のエンベロープ
function envelope(t) {
if (t < 0.12) return t / 0.12;
const u = (t - 0.12) / 0.88;
return Math.pow(1 - u, 1.9);
}
p.setup = function () {
p.createCanvas(W, H);
p.noStroke();
p.background(8, 8, 10);
scheduleNext();
};
p.draw = function () {
// 消しきらない。残像がすこし残る
p.fill(8, 8, 10, 30);
p.rect(0, 0, W, H);
// 点は光と関係なく増え続ける
addAcc += ADD_RATE;
while (addAcc >= 1 && pts.length < MAX_POINTS) {
addAcc -= 1;
pts.push({
x: p.random(W),
y: p.random(H),
b: p.random(0.35, 1.0),
});
}
// 光の管理
if (light) {
light.age++;
if (light.age > light.life) light = null;
} else {
waitFrames--;
if (waitFrames <= 0) {
spawnLight();
lastLitCount = 0;
scheduleNext();
}
}
if (!light) return;
const t = light.age / light.life;
const strength = envelope(t);
if (strength <= 0.001) return;
const r2 = light.r * light.r;
let lit = 0;
for (let i = 0; i < pts.length; i++) {
const q = pts[i];
const dx = q.x - light.x;
const dy = q.y - light.y;
const d2 = dx * dx + dy * dy;
if (d2 > r2) continue; // 届かない点は、在っても見えない
const fall = 1 - Math.sqrt(d2) / light.r;
const a = q.b * Math.pow(fall, 1.7) * strength * 255;
if (a < 2) continue;
lit++;
p.fill(232, 236, 244, a);
p.circle(q.x, q.y, 2.0);
}
// 光そのものは描かない。見えるのは照らされたものだけ
if (light.age === Math.floor(light.life * 0.12)) lastLitCount = lit;
};
}