diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c067710..f2f1f77 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -158,6 +158,12 @@ sunset scenes by about a fifth and night scenes barely, so night keeps its color Night must not use white or gray highlights, because OpenCode's text is white. Use saturated, tinted colors instead: cobalt and teal moonlight, a gold moon, amber lamps and windows, tinted stars, glowing plankton or fireflies. +Give night clear light structure so things stay readable: a lighter band at the horizon (or, underwater, toward the +surface) for silhouettes to stand against, a strong rim `light` from the moon's side, and a few local pools of warm or +glowing light. `lightPool()` in `src/light.ts` lights an ellipse in the light's own color, so ground and creatures +passing through it pick it up; draw it each frame after the creatures it should light. `campfire()` and +`fireflyLight()` there are ready-made pools. Keep the area behind text no brighter or busier than before. + ## Seasons and weather `settings.season` is spring, summer, autumn or winter, or missing for the classic look. Change what the season would diff --git a/src/api.ts b/src/api.ts index 1815efa..ac6fdad 100644 --- a/src/api.ts +++ b/src/api.ts @@ -2,6 +2,7 @@ export { Canvas, cap, ell, type Lighting, type Part } from "./canvas" export { eggWait } from "./egg" export { bird, flyAway, startle, type Flier } from "./flock" +export { campfire, fireflyLight, lightPool } from "./light" export { TAU, clamp, fbm1, fbm2, hash, hash2, hashString, hsv, lerp, noise1, noise2, rand, smoothstep, type RGB } from "./math" export { STAR_TINTS, driftClouds, makeClouds, makeStars, makeStorm, paintClouds, paintSky, paintStars, paintStorm, type Cloud, type Orb, type Star } from "./sky" export { diff --git a/src/light.ts b/src/light.ts new file mode 100644 index 0000000..bf1c6b7 --- /dev/null +++ b/src/light.ts @@ -0,0 +1,60 @@ +import { cap, type Part } from "./canvas" +import type { RGB } from "./math" + +// A pool of light from a lantern, lava, a window or glowing plankton, falling off over an ellipse (pixels) around +// (cx, cy). lift lights what is already there in the light's own color, in proportion to its brightest channel, so lit +// ground and creatures keep their shapes and take on the light's hue; glow adds a little haze of the color itself. +export function lightPool(hdr: Float32Array, W: number, H: number, cx: number, cy: number, rx: number, ry: number, color: RGB, lift: number, glow: number) { + const x0 = Math.max(0, Math.floor(cx - rx)) + const x1 = Math.min(W - 1, Math.ceil(cx + rx)) + const y0 = Math.max(0, Math.floor(cy - ry)) + const y1 = Math.min(H - 1, Math.ceil(cy + ry)) + const lr = color[0] * lift + const lg = color[1] * lift + const lb = color[2] * lift + const gr = color[0] * glow + const gg = color[1] * glow + const gb = color[2] * glow + for (let y = y0; y <= y1; y++) { + const dy = (y + 0.5 - cy) / ry + for (let x = x0; x <= x1; x++) { + const dx = (x + 0.5 - cx) / rx + const d = 1 - dx * dx - dy * dy + if (d <= 0) continue + const f = d * d + const o = (y * W + x) * 3 + // Capped, so foam, lamps and other things already glowing don't wash out to white. + const m = Math.min(0.2, Math.max(hdr[o], hdr[o + 1], hdr[o + 2])) + hdr[o] += (m * lr + gr) * f + hdr[o + 1] += (m * lg + gg) * f + hdr[o + 2] += (m * lb + gb) * f + } + } +} + +// The little pool of yellow-green light a firefly at (x, y) throws on the leaves and grass around it, k its blink. +export function fireflyLight(hdr: Float32Array, W: number, H: number, x: number, y: number, k: number) { + lightPool(hdr, W, H, x, y, 0.045 * H, 0.045 * H, [0.75, 1, 0.25], 4 * k, 0.012 * k) +} + +// A small campfire standing at (x, gy) in pixels, H pixels to a screen height: crossed logs and tongues of flame, each +// tongue its own part list so the hot core draws over the outer flames (flat, with shape light 0). flicker, about 0.8 +// to 1, scales the light the fire throws. +export function campfire(x: number, gy: number, H: number, t: number) { + const flicker = 0.85 + 0.1 * Math.sin(t * 7.1) + 0.05 * Math.sin(t * 13.3 + 1) + const log: RGB = [0.05, 0.025, 0.02] + const logs = [cap(x - 0.03 * H, gy, x + 0.025 * H, gy - 0.008 * H, 0.006 * H, 0.005 * H, log), cap(x + 0.03 * H, gy, x - 0.02 * H, gy - 0.01 * H, 0.006 * H, 0.005 * H, log)] + const flames: Part[][] = ( + [ + [-0.008, 0.036, 0.009, false], + [0.008, 0.03, 0.008, false], + [0, 0.046, 0.011, false], + [0, 0.026, 0.006, true], + ] as const + ).map(([dx, h, r, core]) => { + const sway = Math.sin(t * 3 + dx * 400) * 0.003 * H + const height = h * H * (0.8 + 0.2 * Math.sin(t * 5.3 + dx * 900 + h * 100)) * flicker + return [cap(x + dx * H, gy - 0.004 * H, x + dx * H + sway, gy - height, r * H, 0.0015 * H, core ? [2.6, 1.5, 0.3] : [2, 0.6, 0.08])] + }) + return { flicker, logs, flames } +} diff --git a/wallpapers/beach.ts b/wallpapers/beach.ts index f977204..b58e970 100644 --- a/wallpapers/beach.ts +++ b/wallpapers/beach.ts @@ -1,6 +1,7 @@ import { Canvas, cap, ell, type Lighting, type Part } from "../src/canvas" import { eggWait } from "../src/egg" import { bird, flyAway, startle, type Flier } from "../src/flock" +import { campfire, lightPool } from "../src/light" import { TAU, clamp, fbm1, hash, hash2, lerp, rand, smoothstep, type RGB } from "../src/math" import { driftClouds, makeClouds, makeStars, makeStorm, paintClouds, paintSky, paintStars, paintStorm, type Cloud, type Orb, type Star } from "../src/sky" import type { Activity, Season, Settings, Time, Wallpaper } from "../src/wallpaper" @@ -83,17 +84,18 @@ const LOOKS: Record = { }, night: { style: "rim", - light: [0.2, 0.45, 1], + light: [0.3, 0.62, 1.5], sky: [ [0, [0.004, 0.008, 0.03]], - [0.3, [0.012, 0.02, 0.06]], - [HORIZON, [0.04, 0.06, 0.15]], + [0.25, [0.014, 0.024, 0.07]], + [0.4, [0.035, 0.055, 0.14]], + [HORIZON, [0.07, 0.1, 0.24]], ], orb: { x: 0.7, y: 0.16, r: 0.03, core: [1, 0.72, 0.3], glow: [0.45, 0.3, 0.12], near: 0.2, wide: 0.08, moon: true }, stars: 140, clouds: { puffy: false, top: [0.015, 0.02, 0.05], bottom: [0.06, 0.1, 0.3], alpha: 0.4 }, sea: [ - [0.03, 0.06, 0.15], + [0.045, 0.085, 0.2], [0.01, 0.03, 0.07], ], sand: [ @@ -102,7 +104,7 @@ const LOOKS: Record = { ], foam: [0.1, 0.85, 1.1], glitter: 0.7, - tint: [0.025, 0.04, 0.09], + tint: [0.02, 0.032, 0.075], night: true, }, } @@ -262,6 +264,7 @@ class Beach extends Canvas { if (this.activity !== "calm" && !this.look.night) this.drawGulls() if (this.activity === "teeming") this.drawCrab() for (const b of this.startled) this.shape(bird(b, this.H, 0.03 * this.H, this.paint([0.75, 0.78, 0.82])), this.lighting, 0.5) + if (this.look.night) this.drawBonfire() // Trunks are drawn each frame, over the sea, since they cross the water. for (const p of this.palms) { this.drawTrunk(p) @@ -378,8 +381,8 @@ class Beach extends Canvas { } // At night the glowing foam lights the wet sand just beyond it. if (look.night) - for (let y = Math.floor(edge); y < Math.min(H, edge + 0.02 * H); y++) { - const k = Math.exp(-(y - edge) / 2.5) * 0.15 + for (let y = Math.floor(edge); y < Math.min(H, edge + 0.05 * H); y++) { + const k = Math.exp(-(y - edge) / (0.012 * H)) * 0.2 const o = (y * W + x) * 3 hdr[o] += look.foam[0] * k hdr[o + 1] += look.foam[1] * k @@ -388,6 +391,17 @@ class Beach extends Canvas { } } + // A bonfire on the sand at night, lighting the beach, the crab and the palm beside it. + private drawBonfire() { + const H = this.H + const x = 0.17 * this.A * H + const gy = 0.92 * H + const fire = campfire(x, gy, H * 1.2, this.time) + lightPool(this.hdr, this.W, H, x, gy - 0.03 * H, 0.36 * H, 0.13 * H, [1, 0.4, 0.07], 2.6 * fire.flicker, 0.04 * fire.flicker) + this.shape(fire.logs, { style: "rim", color: [1.4, 0.5, 0.1], x, y: gy - 0.03 * H }) + for (const flame of fire.flames) this.shape(flame, { style: "front", dir: [0, 0, 1] }, 0) + } + // A palm trunk curving from its base to the crown, ringed with old leaf scars, with coconuts at the top. private drawTrunk(p: Palm) { const H = this.H @@ -525,6 +539,11 @@ class Beach extends Canvas { this.polygon([[x - s * 0.6, y - s * 0.12], [x + s * 0.6, y - s * 0.12], [x + s * 0.42, y + s * 0.05], [x - s * 0.42, y + s * 0.05]], this.paint([0.3, 0.2, 0.15])) this.polygon([[x - s * 0.05, y - s * 0.15], [x - s * 0.05, y - s * 1.1], [x + s * 0.55 * b.dir, y - s * 0.15]], this.paint([0.92, 0.9, 0.84])) this.polygon([[x - s * 0.12, y - s * 0.15], [x - s * 0.12, y - s * 0.85], [x - s * 0.5 * b.dir, y - s * 0.15]], this.paint([0.85, 0.82, 0.76])) + // At night a lantern hangs at the stern, lighting the sails and the water around the boat. + if (!this.look.night) return + const lx = x - s * 0.45 * b.dir + lightPool(this.hdr, this.W, H, lx, y, s * 1.4, s * 0.9, [1, 0.55, 0.15], 2.5, 0.03) + this.disc(lx, y - s * 0.25, Math.max(1, s * 0.07), 1.6, 0.85, 0.2, 1) } // Each dolphin waits underwater, then arcs out of the sea and splashes back in. diff --git a/wallpapers/city.ts b/wallpapers/city.ts index c2fb6c8..a4b14fa 100644 --- a/wallpapers/city.ts +++ b/wallpapers/city.ts @@ -1,6 +1,7 @@ import { Canvas, cap, ell, type Lighting, type Part } from "../src/canvas" import { eggWait } from "../src/egg" import { bird, flyAway, startle, type Flier } from "../src/flock" +import { lightPool } from "../src/light" import { TAU, clamp, fbm1, hash, hash2, lerp, rand, smoothstep, type RGB } from "../src/math" import { driftClouds, makeClouds, makeStars, makeStorm, paintClouds, paintSky, paintStars, paintStorm, type Cloud, type Orb, type Star } from "../src/sky" import type { Activity, Settings, Time, Wallpaper } from "../src/wallpaper" @@ -93,11 +94,12 @@ const LOOKS: Record = { }, night: { style: "rim", - light: [0.25, 0.3, 1], + light: [0.35, 0.42, 1.4], sky: [ [0, [0.004, 0.006, 0.03]], - [0.32, [0.014, 0.016, 0.06]], - [BANK, [0.1, 0.035, 0.14]], + [0.32, [0.016, 0.018, 0.07]], + [0.45, [0.05, 0.025, 0.12]], + [BANK, [0.15, 0.05, 0.2]], ], orb: { x: 0.82, y: 0.12, r: 0.03, core: [1, 0.72, 0.3], glow: [0.45, 0.3, 0.12], near: 0.2, wide: 0.08, moon: true }, stars: 50, @@ -310,6 +312,8 @@ class City extends Canvas { const a = Math.floor((ROAD + 0.012) * H) * W * 3 this.hdr.set(this.background.subarray(a, Math.ceil((ROAD + 0.026) * H) * W * 3), a) for (const w of this.walkers) this.drawWalker(w) + // At night the lamps under the highway light the sidewalk and the people passing beneath them. + if (look.night) for (let i = 0; this.pillarX(i) - 0.21 < this.A + 0.1; i++) lightPool(this.hdr, W, H, (this.pillarX(i) - 0.21) * H, 0.96 * H, 0.14 * H, 0.08 * H, [1, 0.5, 0.12], 3, 0) for (const b of this.pigeons) this.shape(bird(b, H, 0.022 * H, this.paint([0.42, 0.44, 0.5])), this.lighting, 0.4) this.drawRain() this.weather.draw(this.hdr, W, H) diff --git a/wallpapers/desert.ts b/wallpapers/desert.ts index 570ddf3..43e9f57 100644 --- a/wallpapers/desert.ts +++ b/wallpapers/desert.ts @@ -2,6 +2,7 @@ import { Canvas, cap, ell, type Lighting, type Part } from "../src/canvas" import { eggWait } from "../src/egg" import { bird, flyAway, startle, type Flier } from "../src/flock" import { TAU, clamp, fbm1, fbm2, hash, lerp, rand, smoothstep, type RGB } from "../src/math" +import { campfire, lightPool } from "../src/light" import { driftClouds, makeStorm, paintStorm } from "../src/sky" import type { Activity, Season, Settings, Time, Wallpaper } from "../src/wallpaper" import { WeatherLayer } from "../src/weather" @@ -139,12 +140,12 @@ const LOOKS: Record = { }, night: { style: "rim", - light: [0.18, 0.42, 1], + light: [0.28, 0.6, 1.45], sky: [ [0, [0.004, 0.006, 0.02]], - [0.3, [0.01, 0.018, 0.05]], - [0.5, [0.025, 0.04, 0.09]], - [HORIZON, [0.05, 0.07, 0.13]], + [0.3, [0.012, 0.02, 0.06]], + [0.5, [0.035, 0.055, 0.13]], + [HORIZON, [0.08, 0.11, 0.24]], ], orb: { x: 0.3, y: 0.16, r: 0.035, core: [1, 0.72, 0.3], glow: [0.45, 0.3, 0.12], near: 0.2, wide: 0.08, moon: true }, stars: 160, @@ -153,11 +154,11 @@ const LOOKS: Record = { farMesa: [0.03, 0.04, 0.08], nearMesa: [0.012, 0.016, 0.035], floor: [ - [0.05, 0.06, 0.1], - [0.015, 0.018, 0.035], + [0.07, 0.09, 0.17], + [0.025, 0.03, 0.06], ], - backDune: [0.025, 0.03, 0.055], - frontDune: [0.012, 0.014, 0.028], + backDune: [0.035, 0.045, 0.095], + frontDune: [0.02, 0.025, 0.055], cactus: [0.01, 0.02, 0.02], bone: [0.1, 0.2, 0.42], horn: [0.08, 0.13, 0.28], @@ -175,7 +176,7 @@ const LOOKS: Record = { ], twig: [0.06, 0.09, 0.2], night: true, - plumage: [0.08, 0.075, 0.08], + plumage: [0.05, 0.06, 0.12], plumageLight: [0.08, 0.1, 0.22], flock: [0.02, 0.02, 0.03], dust: [0, 0, 0], @@ -349,6 +350,7 @@ class Desert extends Canvas { if (this.roadrunner.t >= 0) this.drawRoadrunner() for (const b of this.startled) this.silhouette(bird(b, this.H, 0.022 * this.H, this.look.flock), 0.4) this.drawTumbleweed() + if (this.look.night) this.drawCampfire() this.drawDust() this.weather.draw(this.hdr, this.W, this.H) this.finish() @@ -657,6 +659,17 @@ class Desert extends Canvas { this.shape(parts, lighting, light) } + // A small campfire on the dunes at night, its warm light falling on the sand and the saguaro beside it. + private drawCampfire() { + const H = this.H + const x = 0.21 * this.A * H + const gy = this.ground(0.21) + 0.01 * H + const fire = campfire(x, gy, H, this.time) + lightPool(this.hdr, this.W, H, x, gy - 0.03 * H, 0.32 * H, 0.14 * H, [1, 0.4, 0.07], 2.6 * fire.flicker, 0.04 * fire.flicker) + this.shape(fire.logs, { style: "rim", color: [1.4, 0.5, 0.1], x, y: gy - 0.03 * H }) + for (const flame of fire.flames) this.shape(flame, { style: "front", dir: [0, 0, 1] }, 0) + } + // A saguaro: a ribbed trunk with two upturned arms. haze fades distant ones toward the dunes behind them. private drawSaguaro(x: number, gy: number, h: number, haze: number) { const c = mix(this.look.cactus, this.look.backDune, haze * 0.6) diff --git a/wallpapers/farm.ts b/wallpapers/farm.ts index 868c944..3ebd14a 100644 --- a/wallpapers/farm.ts +++ b/wallpapers/farm.ts @@ -1,5 +1,6 @@ import { Canvas, cap, ell, type Lighting, type Part } from "../src/canvas" import { eggWait } from "../src/egg" +import { fireflyLight, lightPool } from "../src/light" import { TAU, clamp, fbm1, hash, hash2, lerp, rand, smoothstep, type RGB } from "../src/math" import { driftClouds, makeClouds, makeStars, makeStorm, paintClouds, paintSky, paintStars, paintStorm, type Cloud, type Orb, type Star } from "../src/sky" import type { Activity, Season, Settings, Time, Wallpaper } from "../src/wallpaper" @@ -68,20 +69,21 @@ const LOOKS: Record = { }, night: { style: "rim", - light: [0.2, 0.45, 1], + light: [0.3, 0.62, 1.5], sky: [ [0, [0.004, 0.008, 0.03]], - [0.3, [0.012, 0.02, 0.06]], - [HORIZON, [0.04, 0.06, 0.15]], + [0.3, [0.014, 0.024, 0.07]], + [0.45, [0.035, 0.055, 0.14]], + [HORIZON, [0.07, 0.1, 0.24]], ], orb: { x: 0.74, y: 0.15, r: 0.032, core: [1, 0.72, 0.3], glow: [0.45, 0.3, 0.12], near: 0.2, wide: 0.08, moon: true }, stars: 140, clouds: { puffy: false, top: [0.015, 0.02, 0.05], bottom: [0.06, 0.1, 0.3], alpha: 0.45 }, hills: [ - [0.02, 0.035, 0.07], - [0.008, 0.016, 0.03], + [0.035, 0.06, 0.13], + [0.016, 0.03, 0.065], ], - tint: [0.025, 0.04, 0.08], + tint: [0.02, 0.032, 0.065], windows: 1, fireflies: true, }, @@ -273,6 +275,8 @@ class Farm extends Canvas { for (const { w, kind } of herd) kind === "cow" ? this.drawCow(w) : this.drawPig(w) this.drawEgg() if (this.activity === "teeming") this.drawFarmer() + // At night the open barn door spills warm light across the yard and whatever is in it. + if (this.look.windows === 1) lightPool(this.hdr, this.W, this.H, 0.74 * this.A * this.H, 0.67 * this.H, 0.24 * this.H, 0.07 * this.H, [1, 0.55, 0.15], 3, 0.02) this.drawCornRow(3) this.drawFireflies() this.drawLeaves() @@ -414,7 +418,7 @@ class Farm extends Canvas { this.polygon(P([[c - 0.077, b - 0.157], [c, b - 0.188], [c + 0.077, b - 0.157], [c + 0.072, b - 0.148], [c, b - 0.176], [c - 0.072, b - 0.148]]), snow) this.shape([ell((c - 0.13) * H, (b - 0.175) * H, 0.023 * H, 0.013 * H, 0, snow)], this.lighting, 0.4) } - this.polygon(P([[c - 0.035, b - 0.065], [c + 0.035, b - 0.065], [c + 0.035, b], [c - 0.035, b]]), this.paint([0.42, 0.07, 0.05])) + this.polygon(P([[c - 0.035, b - 0.065], [c + 0.035, b - 0.065], [c + 0.035, b], [c - 0.035, b]]), this.look.windows === 1 ? [1.1, 0.6, 0.18] : this.paint([0.42, 0.07, 0.05])) const trim = this.paint([0.88, 0.86, 0.8]) const t = 0.0025 * H this.shape( @@ -871,6 +875,7 @@ class Farm extends Canvas { for (const f of this.fireflies) { const k = Math.pow(Math.max(0, Math.sin(this.time * 1.3 + f.phase * 7)), 6) if (k < 0.02) continue + fireflyLight(this.hdr, this.W, H, f.x * H, f.y * H, k) this.disc(f.x * H, f.y * H, 1.3, 1.8 * k, 2.4 * k, 0.6 * k, 0.6) this.add(f.x * H, f.y * H, 0.9 * k, 1.2 * k, 0.3 * k) } diff --git a/wallpapers/jungle.ts b/wallpapers/jungle.ts index 850f37c..f57cf9a 100644 --- a/wallpapers/jungle.ts +++ b/wallpapers/jungle.ts @@ -1,5 +1,6 @@ import { Canvas, cap, ell, type Lighting, type Part } from "../src/canvas" import { eggWait } from "../src/egg" +import { fireflyLight } from "../src/light" import { TAU, clamp, fbm1, hash, lerp, rand, smoothstep, type RGB } from "../src/math" import type { Activity, Settings, Time, Wallpaper } from "../src/wallpaper" import { WeatherLayer } from "../src/weather" @@ -58,11 +59,11 @@ const LOOKS: Record = { }, night: { style: "rim", - light: [0.1, 0.7, 0.62], + light: [0.14, 0.95, 0.85], mist: [ - [0.02, 0.08, 0.12], - [0.008, 0.035, 0.05], - [0.002, 0.008, 0.01], + [0.02, 0.085, 0.13], + [0.014, 0.065, 0.09], + [0.003, 0.012, 0.016], ], far: [0.015, 0.05, 0.06], water: [0.04, 0.38, 0.5], @@ -672,6 +673,7 @@ class Jungle extends Canvas { for (const f of this.glowers) { const k = Math.max(together, Math.pow(Math.max(0, Math.sin(this.time * 1.3 + f.phase * 7)), 6)) if (k < 0.02) continue + fireflyLight(this.hdr, this.W, H, f.x * H, f.y * H, k) this.disc(f.x * H, f.y * H, 1.3, 0.9 * k * 2, 1.2 * k * 2, 0.3 * k * 2, 0.6) this.add(f.x * H, f.y * H, 0.9 * k, 1.2 * k, 0.3 * k) } diff --git a/wallpapers/ocean.ts b/wallpapers/ocean.ts index ec5e136..c4af1ee 100644 --- a/wallpapers/ocean.ts +++ b/wallpapers/ocean.ts @@ -1,6 +1,7 @@ import { Canvas, cap, ell, type Lighting } from "../src/canvas" import { eggWait } from "../src/egg" import { TAU, clamp, fbm1, hash, hash2, hashString, hsv, lerp, noise1, rand, smoothstep, type RGB } from "../src/math" +import { lightPool } from "../src/light" import type { Activity, Settings, Time, Wallpaper } from "../src/wallpaper" // Depth of the water surface as a fraction of the height. @@ -127,6 +128,11 @@ class Ocean extends Canvas { private octopus: Octopus | undefined private kelp: Kelp[] = [] private particles: Particle[] = [] + // At night: the water alone, before anything swims over it; how much of each pixel is something in front of the + // water; and the night water color per row. + private water = new Float32Array(0) + private mask = new Float32Array(0) + private nightRow = new Float32Array(0) private snow: { x: number; y: number; z: number; s: number }[] = [] private whale = { x: -9, dir: 1, next: 14, y: 0.4 } // The easter egg: a little yellow submarine putters across, propeller turning. x < -5 while it waits. @@ -289,10 +295,13 @@ class Ocean extends Canvas { } render() { + const night = this.timeOfDay === "night" this.drawWater() + if (night) this.water.set(this.hdr) this.drawWhale() this.drawSub() - this.drawSnow(true) + // At night gradeNight turns the marine snow into glowing plankton. + if (!night) this.drawSnow(true) // Nearer creatures (lower z) draw later, over farther ones. const fish = this.fish.toSorted((a, b) => b.z - a.z) for (const f of fish) if (f.z >= 0.5) this.drawCreature(f) @@ -302,11 +311,11 @@ class Ocean extends Canvas { if (this.turtle) this.drawTurtle(this.turtle) for (const f of fish) if (f.z < 0.5) this.drawCreature(f) this.drawParticles() - this.drawSnow(false) + if (!night) this.drawSnow(false) if (this.timeOfDay === "sunset") this.gradeSunset() - if (this.timeOfDay === "night") this.gradeNight() + if (night) this.gradeNight() // At night the portholes glow amber through the dark water. - if (this.timeOfDay === "night" && this.sub.x > -5) + if (night && this.sub.x > -5) for (const u of [-0.16, 0.02, 0.2]) { const [px, py] = this.subPoint(u, 0) this.disc(px, py, 0.0135 * this.H, 1.4, 0.75, 0.18, 0.85) @@ -328,20 +337,28 @@ class Ocean extends Canvas { } } - // Moonlit water: the scene dims to deep blue, then light sources draw on top. The moon glows through the surface, - // marine snow becomes glowing plankton, and the diver's torch cuts through the dark. + // Moonlit water: lighter toward the surface, with everything in front of it dark and rimmed in teal on the side + // facing the moon. Light sources draw on top: marine snow becomes glowing plankton, which gathers in two glowing + // clouds low at the sides, and the diver's torch cuts through the dark. private gradeNight() { const { W, H, hdr } = this const mx = 0.3 * this.A * H const my = SURFACE * H - for (let y = 0; y < H; y++) - for (let x = 0; x < W; x++) { - const o = (y * W + x) * 3 - const moon = Math.exp(-Math.hypot(x - mx, (y - my) * 1.6) / (0.16 * H)) - hdr[o] = hdr[o] * 0.04 + moon * 0.05 - hdr[o + 1] = hdr[o + 1] * 0.16 + moon * 0.28 - hdr[o + 2] = hdr[o + 2] * 0.4 + moon * 0.6 + gradeNightWater(hdr, this.water, this.mask, this.rowCol, this.nightRow, W, H, mx, my) + for (const [i, u, v] of [[0, 0.09, 0.74], [1, 0.9, 0.84]]) { + const cx = (u + Math.sin(this.time * 0.05 + i * 2) * 0.02) * this.A * H + const cy = (v + Math.sin(this.time * 0.07 + i) * 0.015) * H + const breathe = 0.85 + 0.15 * Math.sin(this.time * 0.4 + i * 3) + lightPool(hdr, W, H, cx, cy, 0.26 * H, 0.12 * H, [0.12, 1, 0.75], 1.4 * breathe, 0.11 * breathe) + for (let j = 0; j < 16; j++) { + const a = hash2(i, j) * TAU + const r = Math.sqrt(hash2(j, i + 7)) + const px = cx + Math.cos(a + this.time * 0.03) * r * 0.24 * H + const py = cy + Math.sin(a + this.time * 0.03) * r * 0.12 * H + const k = 0.35 * Math.pow(0.5 + 0.5 * Math.sin(this.time * 1.2 + j * 2.3 + i), 2) * (1 - r * 0.6) + this.add(px, py, 0.1 * k, 0.95 * k, 0.65 * k) } + } for (const p of this.snow) { const pulse = Math.pow(0.5 + 0.5 * Math.sin(this.time * 1.5 + p.s * 50), 3) const k = (0.12 + p.s * 0.45) * pulse @@ -368,6 +385,20 @@ class Ocean extends Canvas { for (let c = 0; c < 3; c++) this.rowCol[y * 3 + c] = lerp(from[c], to[c], k) this.rayFade[y] = Math.pow(1 - smoothstep(0.02, 0.92, v), 1.6) * 0.42 } + if (this.timeOfDay === "night") { + this.water = new Float32Array(W * H * 3) + this.mask = new Float32Array(W * H) + this.nightRow = new Float32Array(H * 3) + const surface: RGB = [0.01, 0.13, 0.32] + const mid: RGB = [0.004, 0.045, 0.13] + const deep: RGB = [0.003, 0.022, 0.075] + for (let y = 0; y < H; y++) { + const v = y / H + const upper = v < 0.45 + const k = upper ? Math.pow(smoothstep(0.03, 0.45, v), 0.8) : smoothstep(0.45, 1, v) + for (let c = 0; c < 3; c++) this.nightRow[y * 3 + c] = lerp(upper ? surface[c] : mid[c], upper ? mid[c] : deep[c], k) + } + } // An invisible seabed that keeps creatures off the bottom edge. this.floorPx = new Float32Array(W) for (let x = 0; x < W; x++) { @@ -1410,6 +1441,50 @@ class Ocean extends Canvas { // Turning is a constant-rate sweep toward the side the creature wants to go, decided by intent rather than current // speed so it never stalls mid-turn. The drawn width follows a sine, so the edge-on moment is brief and smooth. +// Night water: each pixel of the water keeps its daytime detail (ridges, rays, the bright surface) relative to its row, +// rescaled to the night color of that row; whatever differs from the bare water is something in front of it, drawn as +// a dark silhouette with a teal rim on the side facing the moon at (mx, my). +function gradeNightWater(hdr: Float32Array, water: Float32Array, mask: Float32Array, rowCol: Float32Array, nightRow: Float32Array, W: number, H: number, mx: number, my: number) { + for (let i = 0; i < W * H; i++) { + const o = i * 3 + mask[i] = clamp((Math.abs(hdr[o] - water[o]) + Math.abs(hdr[o + 1] - water[o + 1]) + Math.abs(hdr[o + 2] - water[o + 2])) * 10, 0, 1) + } + for (let y = 0; y < H; y++) { + const rowSum = rowCol[y * 3] + rowCol[y * 3 + 1] + rowCol[y * 3 + 2] || 1e-6 + const nr = nightRow[y * 3] + const ng = nightRow[y * 3 + 1] + const nb = nightRow[y * 3 + 2] + const reach = 0.75 - 0.4 * (y / H) + for (let x = 0; x < W; x++) { + const i = y * W + x + const o = i * 3 + const detail = clamp((water[o] + water[o + 1] + water[o + 2]) / rowSum, 0.3, 2.5) + const dx = mx - x + const dy = my - y + const dist = Math.hypot(dx, dy * 1.6) || 1 + const moon = Math.exp(-dist / (0.16 * H)) + const wr = nr * detail + moon * 0.02 + const wg = ng * detail + moon * 0.13 + const wb = nb * detail + moon * 0.3 + const m = mask[i] + if (m <= 0) { + hdr[o] = wr + hdr[o + 1] = wg + hdr[o + 2] = wb + continue + } + const l = Math.hypot(dx, dy) || 1 + const sx = clamp(Math.round(x + (dx / l) * 2), 0, W - 1) + const sy = clamp(Math.round(y + (dy / l) * 2), 0, H - 1) + const rim = m * (1 - mask[sy * W + sx]) * reach + const edge = m * (1 - Math.min(mask[Math.max(0, i - 1)], mask[Math.min(W * H - 1, i + 1)], mask[Math.max(0, i - W)], mask[Math.min(W * H - 1, i + W)])) * 0.12 + hdr[o] = lerp(wr, hdr[o] * 0.05, m) + rim * 0.04 + edge * 0.02 + hdr[o + 1] = lerp(wg, hdr[o + 1] * 0.11, m) + rim * 0.42 + edge * 0.24 + hdr[o + 2] = lerp(wb, hdr[o + 2] * 0.22, m) + rim * 0.55 + edge * 0.3 + } + } +} + function steerTurn(c: { turn: number; heading: number; face: number }, want: number, deadzone: number, rate: number, dt: number) { if (Math.abs(want) > deadzone) c.heading = Math.sign(want) const step = rate * dt diff --git a/wallpapers/prehistoric.ts b/wallpapers/prehistoric.ts index d37610a..eb0a9cc 100644 --- a/wallpapers/prehistoric.ts +++ b/wallpapers/prehistoric.ts @@ -1,6 +1,7 @@ import { Canvas, cap, ell, type Lighting, type Part } from "../src/canvas" import { eggWait } from "../src/egg" import { bird, flyAway, startle, type Flier } from "../src/flock" +import { lightPool } from "../src/light" import { TAU, clamp, fbm1, hash, hash2, lerp, rand, smoothstep, type RGB } from "../src/math" import { driftClouds, makeClouds, makeStars, makeStorm, paintClouds, paintSky, paintStars, paintStorm, type Cloud, type Orb, type Star } from "../src/sky" import type { Activity, Settings, Time, Wallpaper } from "../src/wallpaper" @@ -102,11 +103,12 @@ const LOOKS: Record = { }, night: { style: "rim", - light: [0.2, 0.45, 1], + light: [0.3, 0.62, 1.5], sky: [ [0, [0.004, 0.008, 0.03]], - [0.3, [0.012, 0.02, 0.06]], - [HORIZON, [0.04, 0.06, 0.15]], + [0.3, [0.014, 0.024, 0.07]], + [0.5, [0.035, 0.055, 0.14]], + [HORIZON, [0.07, 0.1, 0.24]], ], orb: { x: 0.18, y: 0.15, r: 0.03, core: [1, 0.72, 0.3], glow: [0.45, 0.3, 0.12], near: 0.2, wide: 0.08, moon: true }, stars: 140, @@ -114,8 +116,8 @@ const LOOKS: Record = { ridge: [0.02, 0.03, 0.08], forest: [0.006, 0.012, 0.028], ground: [ - [0.02, 0.035, 0.07], - [0.008, 0.016, 0.03], + [0.035, 0.06, 0.13], + [0.012, 0.022, 0.05], ], rock: [0.02, 0.018, 0.045], lava: [ @@ -126,7 +128,7 @@ const LOOKS: Record = { smoke: [[0.8, 0.12, 0.03], [0.1, 0.025, 0.07], 0.6], water: [0.02, 0.04, 0.1], ripple: [0.1, 0.35, 0.9], - tint: [0.025, 0.04, 0.09], + tint: [0.02, 0.032, 0.075], }, } @@ -166,6 +168,7 @@ class Prehistoric extends Canvas { private creatureTime = 0 private readonly activity: Activity private readonly look: Look + private readonly night: boolean private lighting: Lighting = { style: "front", dir: DAYLIGHT } private background = new Float32Array(0) private allStars: Star[] @@ -192,6 +195,7 @@ class Prehistoric extends Canvas { super() this.activity = settings.activity this.look = LOOKS[settings.time] + this.night = settings.time === "night" this.weather = new WeatherLayer(settings.weather ?? "clear", settings.time, HORIZON) this.allStars = makeStars(this.look.stars, 0.5) this.clouds = makeClouds(this.look.clouds.puffy ? 4 : 5, this.look.clouds.puffy, 0.06, 0.3) @@ -253,6 +257,8 @@ class Prehistoric extends Canvas { this.drawDust() this.drawRex() const A = this.A + // At night the lava lights the ground, the lake and anything walking near the volcano's foot. + if (this.night) lightPool(this.hdr, this.W, this.H, (VENT_X * A + 0.08) * this.H, 0.63 * this.H, 0.45 * this.H, 0.13 * this.H, [1, 0.36, 0.06], 2.4 + 0.4 * Math.sin(this.time * 0.6), 0.03) for (const [i, x] of [0.01, 0.2, 0.76, 0.99].entries()) this.drawFern(x * A, 1.03, 0.17 + hash(i) * 0.04, i, true) if (this.activity === "teeming") for (const d of this.dragonflies) this.drawDragonfly(d) if (this.delorean.t >= 0) this.drawDelorean() diff --git a/wallpapers/space.ts b/wallpapers/space.ts index 6c39a47..4cff352 100644 --- a/wallpapers/space.ts +++ b/wallpapers/space.ts @@ -1,5 +1,6 @@ import { Canvas, cap, ell, type Lighting, type Part } from "../src/canvas" import { eggWait } from "../src/egg" +import { lightPool } from "../src/light" import { TAU, clamp, fbm1, fbm2, hash, hash2, lerp, rand, smoothstep, type RGB } from "../src/math" import type { Activity, Settings, Time, Wallpaper } from "../src/wallpaper" @@ -73,10 +74,10 @@ const LOOKS: Record = { sun: normalize(0.6, 0.6, -0.5), starK: 1, nebula: [ - [0.06, 0.02, 0.1], - [0.02, 0.04, 0.09], + [0.08, 0.025, 0.14], + [0.025, 0.055, 0.12], ], - milkyWay: 1, + milkyWay: 1.3, atmosphere: [0.2, 0.35, 0.8], ring: 0.12, lighting: "night", @@ -246,7 +247,7 @@ class Space extends Canvas { ? { style: "front", dir: look.sun } : look.lighting === "limb" ? { style: "rim", color: [1, 0.6, 0.3], x: this.starPos[0], y: this.starPos[1] } - : { style: "rim", color: [0.2, 0.4, 1], x: cx, y: cy } + : { style: "rim", color: [0.3, 0.6, 1.5], x: cx, y: cy } const [n1, n2] = look.nebula for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) { @@ -344,7 +345,8 @@ class Space extends Canvas { const facing = (dx * lsx + dy * lsy) / (r || 1) const glowLit = look.lighting === "night" ? 0.25 : look.lighting === "limb" ? 0.2 + 1.6 * Math.max(0, facing) ** 4 : 0.3 + 0.7 * Math.max(0, facing) if (r > 1) { - const halo = Math.exp(-(r - 1) / 0.025) * glowLit * 0.6 + // At night a wide band of airglow rises off the limb, so whatever passes in front of it reads dark. + const halo = Math.exp(-(r - 1) / 0.025) * glowLit * 0.6 + (look.lighting === "night" ? Math.exp(-(r - 1) / 0.09) * 0.07 : 0) hdr[o] += look.atmosphere[0] * halo hdr[o + 1] += look.atmosphere[1] * halo hdr[o + 2] += look.atmosphere[2] * halo @@ -425,6 +427,8 @@ class Space extends Canvas { const cx = (0.24 * this.A + 0.06 * Math.cos(a)) * H const cy = (0.36 + 0.025 * Math.sin(a)) * H const R = 0.035 * H + // Cobalt rather than grey at night. + const [mr, mg, mb] = look.lighting === "night" ? look.rockColor : [0.9, 0.9, 0.95] for (let y = Math.max(0, Math.floor(cy - R - 1)); y <= Math.min(H - 1, cy + R + 1); y++) for (let x = Math.max(0, Math.floor(cx - R - 1)); x <= Math.min(W - 1, cx + R + 1); x++) { const dx = (x + 0.5 - cx) / R @@ -435,7 +439,7 @@ class Space extends Canvas { const lit = Math.max(0, dx * look.sun[0] + dy * look.sun[1] + nz * look.sun[2]) const craters = 0.75 + 0.35 * fbm2(dx * 3 + 5, dy * 3 + 2, 4) const k = (0.015 + lit * 0.9) * craters - this.blend((y * W + x) * 3, k * 0.9, k * 0.9, k * 0.95, clamp((1 - Math.sqrt(r2)) * R + 0.5, 0, 1)) + this.blend((y * W + x) * 3, k * mr, k * mg, k * mb, clamp((1 - Math.sqrt(r2)) * R + 0.5, 0, 1)) } } @@ -558,6 +562,11 @@ class Space extends Canvas { for (const u of [-0.42, -0.27, 0.27, 0.42]) parts.push(cap(...P(u, -0.22), ...P(u, -0.04), 0.055 * S, 0.055 * S, panel, 4), cap(...P(u, 0.04), ...P(u, 0.22), 0.055 * S, 0.055 * S, panel, 4)) parts.push(cap(...P(-0.13, 0), ...P(0.13, 0), 0.045 * S, 0.045 * S, white), cap(...P(0, -0.12), ...P(0, 0.1), 0.035 * S, 0.035 * S, white), cap(...P(0.13, 0.02), ...P(0.2, 0.08), 0.02 * S, 0.02 * S, white)) this.shape(parts, this.lighting, 0.8) + // At night the modules' windows glow amber and light the panels beside them. + if (this.look.lighting === "night") { + lightPool(this.hdr, this.W, H, ...P(0, 0), 0.4 * S, 0.25 * S, [1, 0.6, 0.15], 3, 0.008) + for (const u of [-0.08, 0, 0.08]) this.disc(...P(u, 0), 0.018 * S, 1.6, 0.85, 0.2, 0.9) + } const blink = Math.sin(this.time * 4) > 0.6 ? 1.5 : 0 this.add(...P(0.5, 0), blink, 0.1 * blink, 0.1 * blink) this.add(...P(-0.5, 0), 0.1 * blink, blink, 0.2 * blink) @@ -595,7 +604,9 @@ class Space extends Canvas { const lit = Math.max(0, (dx / edge) * look.sun[0] + (dy / edge) * look.sun[1] + nz * look.sun[2]) const pits = 0.7 + 0.45 * fbm2(dx * 2.5 + Math.cos(r.angle) * 2 + r.seed, dy * 2.5 + Math.sin(r.angle) * 2, 4) const ambient = look.lighting === "night" ? 0.01 : 0.02 - const k = (ambient + lit * 0.7) * pits + // At night the planet's glow below rims the side of the rock facing it. + const rim = look.lighting === "night" ? Math.pow(1 - nz, 2) * Math.max(0, ((dx / edge) * (PLANET.x * W - cx) + (dy / edge) * (PLANET.y * H - cy)) / Math.hypot(PLANET.x * W - cx, PLANET.y * H - cy)) * 0.6 : 0 + const k = (ambient + lit * 0.7 + rim) * pits this.blend((y * W + x) * 3, k * look.rockColor[0], k * look.rockColor[1], k * look.rockColor[2], clamp((1.05 - rr) * R, 0, 1)) } } @@ -628,6 +639,10 @@ class Space extends Canvas { 0.8, ) if (this.look.lighting !== "night") this.add(...P(0.14, -0.36), 1.2, 1.1, 0.9) + // At night the helmet lamp lights the suit and the dark around it amber. + if (this.look.lighting !== "night") return + lightPool(this.hdr, this.W, H, ...P(0.2, -0.33), 0.6 * S, 0.6 * S, [1, 0.6, 0.15], 2.5, 0.01) + this.disc(...P(0.12, -0.33), 0.06 * S, 1.2, 0.6, 0.12, 0.8) } // Now and then a flying saucer glides across the top of the sky, its rim lights chasing round. diff --git a/wallpapers/tundra.ts b/wallpapers/tundra.ts index f7d1dd3..edbfeb8 100644 --- a/wallpapers/tundra.ts +++ b/wallpapers/tundra.ts @@ -1,5 +1,6 @@ import { Canvas, cap, ell, type Lighting, type Part } from "../src/canvas" import { eggWait } from "../src/egg" +import { lightPool } from "../src/light" import { TAU, clamp, fbm1, hash, lerp, rand, smoothstep, type RGB } from "../src/math" import { driftClouds, makeClouds, makeStars, makeStorm, paintClouds, paintSky, paintStars, paintStorm, type Cloud, type Orb, type Star } from "../src/sky" import type { Activity, Settings, Time, Wallpaper } from "../src/wallpaper" @@ -80,11 +81,12 @@ const LOOKS: Record = { }, night: { style: "rim", - light: [0.2, 0.5, 1], + light: [0.3, 0.65, 1.5], sky: [ [0, [0.004, 0.01, 0.035]], - [0.3, [0.01, 0.025, 0.07]], - [HORIZON, [0.03, 0.07, 0.16]], + [0.3, [0.012, 0.03, 0.08]], + [0.45, [0.025, 0.055, 0.14]], + [HORIZON, [0.05, 0.1, 0.24]], ], orb: { x: 0.2, y: 0.14, r: 0.03, core: [1, 0.72, 0.3], glow: [0.45, 0.3, 0.12], near: 0.2, wide: 0.08, moon: true }, stars: 120, @@ -95,7 +97,7 @@ const LOOKS: Record = { peaks: [0.08, 0.16, 0.36], ice: [0.04, 0.12, 0.25], flakes: [0.25, 0.45, 0.9], - tint: [0.04, 0.08, 0.18], + tint: [0.03, 0.06, 0.14], aurora: 1, glow: 1, }, @@ -107,6 +109,12 @@ const DAYLIGHT = (() => { return [0.5 / l, -0.55 / l, 0.65 / l] as const })() +// Igloos: x as a fraction of the width, base and radius in screen heights. +const IGLOOS = [ + [0.86, 0.7, 0.055], + [0.2, 0.75, 0.075], +] as const + // Daytime colors. const PINE: RGB = [0.1, 0.25, 0.18] const FUR: RGB = [0.93, 0.91, 0.86] @@ -232,6 +240,9 @@ class Tundra extends Canvas { if (this.activity === "teeming") this.drawFisher() for (const p of [...this.penguins].sort((a, b) => a.y - b.y)) this.drawPenguin(p) if (this.fox) this.drawFox(this.fox) + // At night the igloo doors' warm light falls across the snow and anything passing them. + if (this.look.glow === 1) + for (const [fx, base, R] of IGLOOS) lightPool(this.hdr, this.W, this.H, (fx * this.A - R * 0.35) * this.H, (base + 0.01) * this.H, R * 3.2 * this.H, R * 1.1 * this.H, [1, 0.42, 0.08], 1, 0.03) this.drawFlakes() this.weather.draw(this.hdr, this.W, this.H) this.finish() @@ -262,8 +273,7 @@ class Tundra extends Canvas { } this.drawLake(0.5 * A, 0.72, 0.3, 0.045) for (const [fx, h] of [[0.3, 0.1], [0.36, 0.08], [0.7, 0.09], [0.76, 0.11]] as const) this.drawPine(fx * A, 0.625, h, 0.5) - this.drawIgloo(0.86 * A, 0.7, 0.055) - this.drawIgloo(0.2 * A, 0.75, 0.075) + for (const [fx, base, R] of IGLOOS) this.drawIgloo(fx * A, base, R) if (this.activity === "teeming") this.drawSnowman(0.3 * A, 0.85, 0.75, [0.2, 0.35, 0.8], false) this.drawSnowman(0.62 * A, 0.87, 1, [0.8, 0.12, 0.1], true) for (const [fx, h] of [[0.04, 0.46], [0.11, 0.34], [0.955, 0.42], [0.995, 0.3]] as const) this.drawPine(fx * A, 1.02, h, 0) diff --git a/wallpapers/zen.ts b/wallpapers/zen.ts index c1f2340..a99cddc 100644 --- a/wallpapers/zen.ts +++ b/wallpapers/zen.ts @@ -1,6 +1,7 @@ import { Canvas, cap, ell, type Lighting, type Part } from "../src/canvas" import { eggWait } from "../src/egg" import { bird, flyAway, startle, type Flier } from "../src/flock" +import { fireflyLight, lightPool } from "../src/light" import { TAU, clamp, fbm1, hash, hash2, lerp, rand, smoothstep, type RGB } from "../src/math" import { driftClouds, makeClouds, makeStars, makeStorm, paintClouds, paintSky, paintStars, paintStorm, type Cloud, type Orb, type Star } from "../src/sky" import type { Activity, Season, Settings, Time, Wallpaper } from "../src/wallpaper" @@ -100,11 +101,12 @@ const LOOKS: Record = { }, night: { style: "rim", - light: [0.2, 0.45, 1], + light: [0.3, 0.62, 1.5], sky: [ [0, [0.004, 0.008, 0.03]], - [0.3, [0.012, 0.02, 0.06]], - [HORIZON, [0.04, 0.06, 0.15]], + [0.3, [0.014, 0.024, 0.07]], + [0.42, [0.035, 0.055, 0.14]], + [HORIZON, [0.07, 0.1, 0.24]], ], orb: { x: 0.6, y: 0.16, r: 0.032, core: [1, 0.72, 0.3], glow: [0.45, 0.3, 0.12], near: 0.2, wide: 0.08, moon: true }, stars: 130, @@ -114,7 +116,7 @@ const LOOKS: Record = { [0.008, 0.016, 0.035], ], snow: [0.07, 0.12, 0.3], - mist: [0.04, 0.07, 0.18], + mist: [0.05, 0.09, 0.22], moss: [0.01, 0.024, 0.035], gravel: [0.045, 0.07, 0.15], water: [0.014, 0.045, 0.12], @@ -123,7 +125,7 @@ const LOOKS: Record = { ripple: [0.06, 0.25, 0.55], petal: [0.45, 0.12, 0.35], lamps: 1, - tint: [0.025, 0.04, 0.09], + tint: [0.02, 0.032, 0.075], night: true, }, } @@ -318,6 +320,8 @@ class Zen extends Canvas { if (this.tanuki.t >= 0) this.drawTanuki() for (const p of this.petals) if (p.state !== 1) this.drawPetal(p) for (const b of this.sparrows) this.shape(bird(b, this.H, 0.02 * this.H, this.paint([0.45, 0.33, 0.22])), this.lighting, 0.4) + // At night the lanterns light the gravel, the bank and whatever stands near them. + if (this.look.night) for (const l of this.lanterns()) lightPool(this.hdr, this.W, this.H, l.x * this.H, l.base * this.H, l.scale * 1.6 * this.H, l.scale * 0.6 * this.H, [1, 0.5, 0.15], 2, 0.01) this.drawFireflies() this.weather.draw(this.hdr, this.W, this.H) this.finish() @@ -1219,6 +1223,7 @@ class Zen extends Canvas { for (const f of this.fireflies) { const k = Math.pow(Math.max(0, Math.sin(this.time * 1.3 + f.phase * 7)), 6) if (k < 0.02) continue + fireflyLight(this.hdr, this.W, H, f.x * H, f.y * H, k) this.disc(f.x * H, f.y * H, 1.3, 1.8 * k, 2.4 * k, 0.6 * k, 0.6) this.add(f.x * H, f.y * H, 0.9 * k, 1.2 * k, 0.3 * k) }