@codincod/ornament 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +52 -2
- package/package.json +35 -3
- package/src/biome.ts +157 -0
- package/src/caustics.ts +81 -0
- package/src/census.ts +262 -0
- package/src/cephalopods.ts +1041 -0
- package/src/cloud.ts +308 -0
- package/src/crags.ts +974 -0
- package/src/drift.ts +435 -0
- package/src/eels.ts +667 -0
- package/src/fish_shape.ts +410 -0
- package/src/flora.ts +1955 -0
- package/src/flow_field.ts +491 -0
- package/src/jellies.ts +1794 -0
- package/src/nemos.ts +606 -0
- package/src/paint/bed.ts +231 -0
- package/src/paint/frame.ts +204 -0
- package/src/paint/life.ts +611 -0
- package/src/paint/light.ts +132 -0
- package/src/paint/pen.ts +225 -0
- package/src/paint/rock.ts +325 -0
- package/src/paint/sea.ts +809 -0
- package/src/paint/shade.ts +109 -0
- package/src/paint/sky.ts +377 -0
- package/src/paint/specimen.ts +300 -0
- package/src/paint/trace.ts +164 -0
- package/src/passers.ts +730 -0
- package/src/perlin.ts +168 -0
- package/src/plenty.ts +65 -0
- package/src/puffers.ts +510 -0
- package/src/rays.ts +225 -0
- package/src/reef.ts +945 -0
- package/src/relics.ts +451 -0
- package/src/seabed.ts +525 -0
- package/src/shoal.ts +1658 -0
- package/src/sizes.ts +86 -0
- package/src/sun.ts +500 -0
- package/src/swarm.ts +587 -0
- package/src/visitors.ts +1936 -0
- package/src/walkers.ts +861 -0
package/src/drift.ts
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the water carries: snow falling through it, and bubbles going up.
|
|
3
|
+
*
|
|
4
|
+
* The simulation behind the drifting half of a seascape, kept free of the DOM
|
|
5
|
+
* the way `shoal.ts` is, so the drawing half stays a drawing half.
|
|
6
|
+
*
|
|
7
|
+
* This exists because of what a shoal on its own looks like. Fish are large,
|
|
8
|
+
* few and far apart, so between them is water with nothing in it, and water
|
|
9
|
+
* with nothing in it does not read as water. It reads as a background colour
|
|
10
|
+
* that happens to have animals on top. The fix is not more fish: a screen busy
|
|
11
|
+
* with animals is a screen that wants looking at, which is the one thing an
|
|
12
|
+
* ornament may not ask for. The fix is smaller things.
|
|
13
|
+
*
|
|
14
|
+
* ## Snow
|
|
15
|
+
*
|
|
16
|
+
* Marine snow is the real thing this is named after, and the real thing is what
|
|
17
|
+
* makes it work. It is mostly falling, slowly, and it is everywhere at once, so
|
|
18
|
+
* the eye reads a volume of water rather than a plane. Each mote is a couple of
|
|
19
|
+
* pixels at most and faint with it, which is what lets there be enough of them
|
|
20
|
+
* to fill a screen without any one of them being a thing you notice.
|
|
21
|
+
*
|
|
22
|
+
* They fall at their own rate and slide with the same Perlin field that steers
|
|
23
|
+
* the fish, which is the whole reason they belong in this file and not in a
|
|
24
|
+
* stylesheet. Snow that fell straight down would be rain. Snow that agrees with
|
|
25
|
+
* the fish about which way the water is moving is the water becoming visible.
|
|
26
|
+
*
|
|
27
|
+
* ## Bubbles
|
|
28
|
+
*
|
|
29
|
+
* Bubbles come off a vent, which is a fixed point on the floor, in strings
|
|
30
|
+
* rather than singly, and then the vent goes quiet for a while. That rhythm is
|
|
31
|
+
* the point. A steady stream is a machine; a burst and a pause is something
|
|
32
|
+
* down there breathing.
|
|
33
|
+
*
|
|
34
|
+
* A bubble grows as it rises, because the pressure on it drops, and it wobbles
|
|
35
|
+
* because it is not rigid. Both are cheap and both are what the eye checks
|
|
36
|
+
* against without knowing it. What it does at the top is nothing: it reaches
|
|
37
|
+
* the surface and is gone, since a bubble that faded out in mid-water would be
|
|
38
|
+
* asking why.
|
|
39
|
+
*
|
|
40
|
+
* ## A mote is redrawn when it has fallen far enough to be worth it
|
|
41
|
+
*
|
|
42
|
+
* Snow is the cheapest thing in this water to simulate and among the dearest
|
|
43
|
+
* to hand over. A mote has no geometry, so nothing here is cut; what a renderer
|
|
44
|
+
* pays is the copy and the tessellation, for every mote on every frame, and
|
|
45
|
+
* most of them have gone nowhere a screen could show. So a mote holds the place
|
|
46
|
+
* it was last drawn at until it has fallen `tolerance` away from it, and `cut`
|
|
47
|
+
* is how a renderer knows. Two subtractions and a hypotenuse, on numbers the
|
|
48
|
+
* fall has already produced.
|
|
49
|
+
*
|
|
50
|
+
* `docs/decisions/0027-let-an-element-decide-when-its-drawing-is-stale.md` is
|
|
51
|
+
* the argument. Bubbles are left out of it. There are few of them, none lasts
|
|
52
|
+
* more than seconds, and one rises further between frames than any tolerance
|
|
53
|
+
* worth setting would hold it back by.
|
|
54
|
+
*
|
|
55
|
+
* Everything is seeded, so the same seed gives the same weather twice.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
import { makeNoise2, makeRandom, type Noise2 } from "./perlin.ts"
|
|
59
|
+
|
|
60
|
+
export interface Bubble {
|
|
61
|
+
/** 0 is as far off as this water goes, 1 is up against the glass. */
|
|
62
|
+
readonly depth: number
|
|
63
|
+
/** Its own lane through the wobble, so no two in a string weave alike. */
|
|
64
|
+
readonly lane: number
|
|
65
|
+
/** Radius in px, which grows the whole way up; see `SWELL`. */
|
|
66
|
+
r: number
|
|
67
|
+
/** How long it has been up, in seconds, which is what drives the wobble. */
|
|
68
|
+
rose: number
|
|
69
|
+
x: number
|
|
70
|
+
y: number
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface Mote {
|
|
74
|
+
/**
|
|
75
|
+
* How many times this mote has been redrawn.
|
|
76
|
+
*
|
|
77
|
+
* The only thing a renderer has to watch. While it holds, the mote has not
|
|
78
|
+
* fallen far enough for another drawing to differ from the one already on the
|
|
79
|
+
* screen, so a renderer that leaves it alone leaves a picture that is still
|
|
80
|
+
* true to within the tolerance.
|
|
81
|
+
*/
|
|
82
|
+
cut: number
|
|
83
|
+
readonly depth: number
|
|
84
|
+
/** Its own lane through the field, so neighbours do not fall as a sheet. */
|
|
85
|
+
readonly lane: number
|
|
86
|
+
/** Radius in px, with depth already taken out of it. */
|
|
87
|
+
readonly r: number
|
|
88
|
+
/** Its own share of the fall, so a drift is not a curtain coming down. */
|
|
89
|
+
readonly rate: number
|
|
90
|
+
x: number
|
|
91
|
+
y: number
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface Drift {
|
|
95
|
+
readonly bubbles: readonly Bubble[]
|
|
96
|
+
readonly motes: readonly Mote[]
|
|
97
|
+
/** Re-fit to a resized box, carrying what is in it across in proportion. */
|
|
98
|
+
resize(width: number, height: number, motes?: number): void
|
|
99
|
+
step(seconds: number): void
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface DriftOptions {
|
|
103
|
+
/**
|
|
104
|
+
* Where the floor is at a given x, for anything that has to sit on it.
|
|
105
|
+
*
|
|
106
|
+
* A function rather than a number because a seabed is not level, and a vent
|
|
107
|
+
* that hangs in the water a little above the sand is worse than no vent. The
|
|
108
|
+
* default is the bottom of the box, which is what a surface with no seabed
|
|
109
|
+
* drawn on it wants.
|
|
110
|
+
*/
|
|
111
|
+
floor?: (x: number) => number
|
|
112
|
+
height: number
|
|
113
|
+
motes: number
|
|
114
|
+
seed: number
|
|
115
|
+
/**
|
|
116
|
+
* How far a mote may fall from its last drawing before it is drawn again, in
|
|
117
|
+
* px.
|
|
118
|
+
*
|
|
119
|
+
* `FloraOptions.tolerance` for the snow, and the same bargain. At nothing
|
|
120
|
+
* every mote is redrawn on every step, which is how this behaved before. At a
|
|
121
|
+
* quarter of a pixel most of them are left alone on most frames, and what is
|
|
122
|
+
* given up is a fall that arrives in quarter-pixel steps on a dot drawn a
|
|
123
|
+
* couple of px across and faint with it.
|
|
124
|
+
*/
|
|
125
|
+
tolerance?: number
|
|
126
|
+
/** How many places on the floor bubbles come from. */
|
|
127
|
+
vents?: number
|
|
128
|
+
width: number
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* How many of the field's features fit across the box.
|
|
133
|
+
*
|
|
134
|
+
* Deliberately coarser than the shoal's, so the snow is answering a broader
|
|
135
|
+
* swell than the fish are. Matching them exactly was the first attempt and it
|
|
136
|
+
* looked wrong in a way that took a while to name: every mote near a fish was
|
|
137
|
+
* doing what the fish did, so the snow read as the fish's wake rather than as
|
|
138
|
+
* the water both of them are in.
|
|
139
|
+
*/
|
|
140
|
+
const FIELD_CELLS = 2.2
|
|
141
|
+
|
|
142
|
+
/** How fast the field slides, so nothing ever settles into a loop. */
|
|
143
|
+
const DRIFT = 0.04
|
|
144
|
+
|
|
145
|
+
/** How far the field can push a mote sideways, in px a second at depth 1. */
|
|
146
|
+
const SWAY = 13
|
|
147
|
+
|
|
148
|
+
/** How fast snow falls, slowest to fastest, in px a second at depth 1. */
|
|
149
|
+
const FALL_SLOWEST = 4
|
|
150
|
+
|
|
151
|
+
const FALL_FASTEST = 13
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The spread of mote sizes in px, at depth 1, and how hard the draw leans small.
|
|
155
|
+
*
|
|
156
|
+
* Leaning hard, because size here is doing the work distance does. A field of
|
|
157
|
+
* evenly sized dots is a texture and the eye dismisses it; a field where most
|
|
158
|
+
* are at the limit of being visible and a few are clearly nearer is depth, and
|
|
159
|
+
* depth is the only reason to draw any of this.
|
|
160
|
+
*
|
|
161
|
+
* It is the one thing in this water not written down in metres, and that is
|
|
162
|
+
* deliberate. Snow is a fleck of a millimetre or two adrift within reach of the
|
|
163
|
+
* glass, and `sizes.ts` weighs a length against a distance: something that
|
|
164
|
+
* small that close comes out of the sum larger than the fish behind it. What is
|
|
165
|
+
* being drawn is a texture on the water rather than an animal in it.
|
|
166
|
+
*/
|
|
167
|
+
const MOTE_SMALLEST = 0.4
|
|
168
|
+
|
|
169
|
+
const MOTE_LARGEST = 1.9
|
|
170
|
+
|
|
171
|
+
const MOTE_BIAS = 2.6
|
|
172
|
+
|
|
173
|
+
/** How far a mote falls before it is worth drawing again, in px, by default. */
|
|
174
|
+
const TOLERANCE = 0.25
|
|
175
|
+
|
|
176
|
+
/** How much of a mote's size and speed is its depth. */
|
|
177
|
+
const DEPTH_SIZE = 0.75
|
|
178
|
+
|
|
179
|
+
/** The nearest and furthest anything drifting is ever put. */
|
|
180
|
+
const DEPTH_NEAR = 1
|
|
181
|
+
|
|
182
|
+
const DEPTH_FAR = 0.08
|
|
183
|
+
|
|
184
|
+
/** How fast a bubble rises, slowest to fastest, in px a second at depth 1. */
|
|
185
|
+
const RISE_SLOWEST = 38
|
|
186
|
+
|
|
187
|
+
const RISE_FASTEST = 66
|
|
188
|
+
|
|
189
|
+
/** A bubble's radius where it leaves the vent, in px, and how much it gains. */
|
|
190
|
+
const BUBBLE_SMALLEST = 1.1
|
|
191
|
+
|
|
192
|
+
const BUBBLE_LARGEST = 2.9
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* How much bigger a bubble is at the surface than at the floor.
|
|
196
|
+
*
|
|
197
|
+
* Modest. The honest figure for a shallow sea is close to nothing, and the
|
|
198
|
+
* honest figure for a deep one is a great deal more than this, but a bubble
|
|
199
|
+
* that arrives at the top four times the size it left is a balloon. What this
|
|
200
|
+
* has to do is stop the string reading as a column of identical dots.
|
|
201
|
+
*/
|
|
202
|
+
const SWELL = 0.6
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* How far a bubble weaves off its line, in px, and how fast it weaves.
|
|
206
|
+
*
|
|
207
|
+
* Wide enough to be seen from across a room. A vent draws the eye because it is
|
|
208
|
+
* the one thing in the water moving against everything else, so a string that
|
|
209
|
+
* goes up dead straight is the most obviously ruled line in the scene. What
|
|
210
|
+
* stops it being a line is that no two bubbles in the string are at the same
|
|
211
|
+
* point of the same weave, which is what `Bubble.lane` is for.
|
|
212
|
+
*/
|
|
213
|
+
const WOBBLE = 9
|
|
214
|
+
|
|
215
|
+
const WOBBLE_RATE = 1.9
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* How long a vent runs, and how long it rests, in seconds.
|
|
219
|
+
*
|
|
220
|
+
* The rest is much the longer of the two, which is what keeps a seascape from
|
|
221
|
+
* turning into an aquarium ornament. Most of the time nothing is coming out of
|
|
222
|
+
* the floor at all, and then something is.
|
|
223
|
+
*/
|
|
224
|
+
const VENT_RUN_LEAST = 0.7
|
|
225
|
+
|
|
226
|
+
const VENT_RUN_SPAN = 1.6
|
|
227
|
+
|
|
228
|
+
const VENT_REST_LEAST = 5
|
|
229
|
+
|
|
230
|
+
const VENT_REST_SPAN = 14
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* How long between bubbles while a vent is running, in seconds.
|
|
234
|
+
*
|
|
235
|
+
* A wide spread rather than a tight one, because the spacing up the string is
|
|
236
|
+
* the tell. Bubbles let go on a near-constant interval arrive evenly spaced and
|
|
237
|
+
* the whole column reads as a dotted line drawn on the water.
|
|
238
|
+
*/
|
|
239
|
+
const VENT_GAP_LEAST = 0.07
|
|
240
|
+
|
|
241
|
+
const VENT_GAP_SPAN = 0.5
|
|
242
|
+
|
|
243
|
+
/** A floor for the box, so a sliver of a window never divides by nothing. */
|
|
244
|
+
const MIN_SPAN = 1
|
|
245
|
+
|
|
246
|
+
interface Vent {
|
|
247
|
+
/** Seconds until the next bubble, or until this vent changes its mind. */
|
|
248
|
+
next: number
|
|
249
|
+
/** Whether it is running or resting. */
|
|
250
|
+
running: boolean
|
|
251
|
+
/** Seconds left in the run or the rest it is in. */
|
|
252
|
+
until: number
|
|
253
|
+
readonly x: number
|
|
254
|
+
readonly y: number
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function createDrift(options: DriftOptions): Drift {
|
|
258
|
+
const noise = makeNoise2(options.seed ^ 0x1d3f)
|
|
259
|
+
const random = makeRandom(options.seed ^ 0x7a21)
|
|
260
|
+
|
|
261
|
+
let width = Math.max(MIN_SPAN, options.width)
|
|
262
|
+
let height = Math.max(MIN_SPAN, options.height)
|
|
263
|
+
let slide = 0
|
|
264
|
+
|
|
265
|
+
const floor = options.floor ?? (() => height)
|
|
266
|
+
|
|
267
|
+
const tolerance = Math.max(0, options.tolerance ?? TOLERANCE)
|
|
268
|
+
|
|
269
|
+
const mote = (): Mote => {
|
|
270
|
+
const depth = DEPTH_FAR + random() * (DEPTH_NEAR - DEPTH_FAR)
|
|
271
|
+
const shrink = 1 - DEPTH_SIZE + DEPTH_SIZE * depth
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
cut: 0,
|
|
275
|
+
depth,
|
|
276
|
+
lane: random() * 200,
|
|
277
|
+
r: (MOTE_SMALLEST + random() ** MOTE_BIAS * (MOTE_LARGEST - MOTE_SMALLEST)) * shrink,
|
|
278
|
+
rate: (FALL_SLOWEST + random() * (FALL_FASTEST - FALL_SLOWEST)) * shrink,
|
|
279
|
+
x: random() * width,
|
|
280
|
+
y: random() * height,
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const motes: Mote[] = []
|
|
285
|
+
|
|
286
|
+
/** Where each mote stood when it was last drawn, alongside the mote itself. */
|
|
287
|
+
const held: { x: number; y: number }[] = []
|
|
288
|
+
|
|
289
|
+
function sow(): void {
|
|
290
|
+
const one = mote()
|
|
291
|
+
motes.push(one)
|
|
292
|
+
held.push({ x: one.x, y: one.y })
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Say a mote is where it is now, so a renderer comes and reads it again. */
|
|
296
|
+
function draw(at: number): void {
|
|
297
|
+
const one = motes[at]
|
|
298
|
+
const was = held[at]
|
|
299
|
+
if (!one || !was) return
|
|
300
|
+
|
|
301
|
+
was.x = one.x
|
|
302
|
+
was.y = one.y
|
|
303
|
+
one.cut++
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
for (let made = 0; made < Math.max(0, options.motes); made++) sow()
|
|
307
|
+
|
|
308
|
+
const vent = (): Vent => {
|
|
309
|
+
const x = random() * width
|
|
310
|
+
return {
|
|
311
|
+
next: 0,
|
|
312
|
+
running: false,
|
|
313
|
+
until: random() * (VENT_REST_LEAST + VENT_REST_SPAN),
|
|
314
|
+
x,
|
|
315
|
+
y: floor(x),
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const vents: Vent[] = Array.from({ length: Math.max(0, options.vents ?? 0) }, vent)
|
|
320
|
+
const bubbles: Bubble[] = []
|
|
321
|
+
|
|
322
|
+
function release(from: Vent): void {
|
|
323
|
+
const depth = DEPTH_FAR + random() * (DEPTH_NEAR - DEPTH_FAR)
|
|
324
|
+
|
|
325
|
+
bubbles.push({
|
|
326
|
+
depth,
|
|
327
|
+
lane: random() * 200,
|
|
328
|
+
r: (BUBBLE_SMALLEST + random() * (BUBBLE_LARGEST - BUBBLE_SMALLEST)) * depth,
|
|
329
|
+
rose: 0,
|
|
330
|
+
x: from.x + (random() - 0.5) * WOBBLE,
|
|
331
|
+
y: from.y,
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
bubbles,
|
|
337
|
+
motes,
|
|
338
|
+
|
|
339
|
+
resize(nextWidth, nextHeight, count) {
|
|
340
|
+
const scaleX = Math.max(MIN_SPAN, nextWidth) / width
|
|
341
|
+
const scaleY = Math.max(MIN_SPAN, nextHeight) / height
|
|
342
|
+
width = Math.max(MIN_SPAN, nextWidth)
|
|
343
|
+
height = Math.max(MIN_SPAN, nextHeight)
|
|
344
|
+
|
|
345
|
+
for (let at = 0; at < motes.length; at++) {
|
|
346
|
+
const one = motes[at]
|
|
347
|
+
if (!one) continue
|
|
348
|
+
one.x *= scaleX
|
|
349
|
+
one.y *= scaleY
|
|
350
|
+
draw(at)
|
|
351
|
+
}
|
|
352
|
+
for (const one of bubbles) {
|
|
353
|
+
one.x *= scaleX
|
|
354
|
+
one.y *= scaleY
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (count == null) return
|
|
358
|
+
while (motes.length > count) {
|
|
359
|
+
motes.pop()
|
|
360
|
+
held.pop()
|
|
361
|
+
}
|
|
362
|
+
while (motes.length < count) sow()
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
step(seconds) {
|
|
366
|
+
const dt = Math.min(Math.max(seconds, 0), 0.1)
|
|
367
|
+
slide += DRIFT * dt
|
|
368
|
+
const scale = FIELD_CELLS / width
|
|
369
|
+
|
|
370
|
+
for (let at = 0; at < motes.length; at++) {
|
|
371
|
+
const one = motes[at]
|
|
372
|
+
const was = held[at]
|
|
373
|
+
if (!one || !was) continue
|
|
374
|
+
|
|
375
|
+
const push = noise(one.x * scale + one.lane, one.y * scale + one.lane + slide)
|
|
376
|
+
one.x += push * SWAY * one.depth * dt
|
|
377
|
+
one.y += one.rate * dt
|
|
378
|
+
|
|
379
|
+
if (one.y > height + one.r) {
|
|
380
|
+
one.y = -one.r
|
|
381
|
+
one.x = random() * width
|
|
382
|
+
}
|
|
383
|
+
one.x = around(one.x, width, one.r)
|
|
384
|
+
|
|
385
|
+
if (Math.hypot(one.x - was.x, one.y - was.y) >= tolerance) draw(at)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
for (const from of vents) {
|
|
389
|
+
from.until -= dt
|
|
390
|
+
if (from.until <= 0) {
|
|
391
|
+
from.running = !from.running
|
|
392
|
+
from.until = from.running
|
|
393
|
+
? VENT_RUN_LEAST + random() * VENT_RUN_SPAN
|
|
394
|
+
: VENT_REST_LEAST + random() * VENT_REST_SPAN
|
|
395
|
+
from.next = 0
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (!from.running) continue
|
|
399
|
+
|
|
400
|
+
from.next -= dt
|
|
401
|
+
if (from.next > 0) continue
|
|
402
|
+
|
|
403
|
+
from.next = VENT_GAP_LEAST + random() * VENT_GAP_SPAN
|
|
404
|
+
release(from)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
for (let at = bubbles.length - 1; at >= 0; at--) {
|
|
408
|
+
const one = bubbles[at]
|
|
409
|
+
if (!one) continue
|
|
410
|
+
|
|
411
|
+
const climb = (RISE_SLOWEST + one.depth * (RISE_FASTEST - RISE_SLOWEST)) * one.depth
|
|
412
|
+
one.rose += dt
|
|
413
|
+
one.y -= climb * dt
|
|
414
|
+
one.r += one.r * SWELL * (climb / Math.max(height, MIN_SPAN)) * dt
|
|
415
|
+
one.x += Math.cos(one.rose * WOBBLE_RATE + one.lane) * WOBBLE * dt
|
|
416
|
+
|
|
417
|
+
if (one.y + one.r < 0) bubbles.splice(at, 1)
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Off one side and back on the other.
|
|
425
|
+
*
|
|
426
|
+
* Snow wraps sideways and respawns at the top rather than wrapping there, which
|
|
427
|
+
* is not an inconsistency. A mote that left the bottom and arrived at the top in
|
|
428
|
+
* the same column would draw a visible seam down the picture, because the eye
|
|
429
|
+
* follows a falling thing and finds it again. Sideways nothing is following it.
|
|
430
|
+
*/
|
|
431
|
+
function around(value: number, span: number, margin: number): number {
|
|
432
|
+
if (value > span + margin) return -margin
|
|
433
|
+
if (value < -margin) return span + margin
|
|
434
|
+
return value
|
|
435
|
+
}
|