@codincod/ornament 0.0.0-stage → 0.2.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 +62 -2
- package/package.json +35 -3
- package/src/biome.ts +157 -0
- package/src/canvas/flat.ts +336 -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/colour.ts +86 -0
- package/src/paint/frame.ts +204 -0
- package/src/paint/ink.ts +134 -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/svg.ts +326 -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/reef.ts
ADDED
|
@@ -0,0 +1,945 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A reef: one mound of rock, and everything that has taken a place on it.
|
|
3
|
+
*
|
|
4
|
+
* The simulation behind a reef, kept free of the DOM the way `flora.ts` is, and
|
|
5
|
+
* built on the same drawing contract: every coral on it is a handful of
|
|
6
|
+
* tapering strokes from one base, so anything that can already draw a `Twig`
|
|
7
|
+
* can draw this without being told what a reef is.
|
|
8
|
+
*
|
|
9
|
+
* ## Why a reef is not more plants
|
|
10
|
+
*
|
|
11
|
+
* `flora.ts` sows the bed: corals scattered along the sand, each standing on
|
|
12
|
+
* its own and none of them aware of the others. That is what a bed looks like,
|
|
13
|
+
* and it is not what a reef looks like. A reef is a place. It has a body of its
|
|
14
|
+
* own that stands up out of the ground, it has a top and a face and a foot, and
|
|
15
|
+
* every living thing on it is somewhere particular on that body, because where
|
|
16
|
+
* you are on a reef decides whether you can live there at all.
|
|
17
|
+
*
|
|
18
|
+
* So the mound comes first and the corals are placed against it. Each one is
|
|
19
|
+
* handed the rock and asks it what settles where anything grows: how high am I,
|
|
20
|
+
* and how hard does the water run here. A whip wants the swept crest and a
|
|
21
|
+
* leather coral wants the sheltered foot, and neither of them is put there by
|
|
22
|
+
* this file. `SORTS` says what each kind can live with, and the coral finds its
|
|
23
|
+
* own spot or does not grow.
|
|
24
|
+
*
|
|
25
|
+
* ## Health is how many kinds, not how many corals
|
|
26
|
+
*
|
|
27
|
+
* A reef covered in one coral is a reef in trouble, whatever it looks like from
|
|
28
|
+
* a distance. What a healthy one has is a great many kinds crowded together,
|
|
29
|
+
* each holding the ground it can hold, and that is also the only thing that
|
|
30
|
+
* makes a drawing of one read as alive: a hundred copies of one silhouette is
|
|
31
|
+
* wallpaper at any density. Every kind in `SORTS` gets first refusal on the
|
|
32
|
+
* rock before any kind is placed twice, and `Reef.health` is the share of them
|
|
33
|
+
* that found somewhere. Nearly every reef comes out whole. A cramped one does
|
|
34
|
+
* not, and the figure is there to say so rather than to be tuned away.
|
|
35
|
+
*
|
|
36
|
+
* ## What sits in front of the rock
|
|
37
|
+
*
|
|
38
|
+
* A mound drawn in profile has a skyline, and a coral placed on the skyline is
|
|
39
|
+
* a coral on the top. Real ones are all over the front of it as well, which in
|
|
40
|
+
* this projection means standing inside the silhouette and nearer the glass.
|
|
41
|
+
* That is `Sort.cling`: fans and whips take the face, brains and tables take
|
|
42
|
+
* the top, and the reef comes out with a front and a back rather than a fringe
|
|
43
|
+
* along one line.
|
|
44
|
+
*
|
|
45
|
+
* ## The anemone is the bed's animal, standing on rock
|
|
46
|
+
*
|
|
47
|
+
* There is one anemone in this ornament and `flora.ts` owns it. It sows its own
|
|
48
|
+
* on open sand; the reef places the same creature on the mound and hands a
|
|
49
|
+
* clownfish the address. Two drawings of one animal in one picture is the
|
|
50
|
+
* repeat this codebase argues against everywhere else, and the reader who sees
|
|
51
|
+
* both would notice they were not the same creature before working out why.
|
|
52
|
+
*
|
|
53
|
+
* What that costs here is a second way of being drawn. Coral arrives as a
|
|
54
|
+
* stencil, because a skeleton is a shape that holds. An anemone arrives as a
|
|
55
|
+
* stalk and a crown of tentacles, recut every step, because it is an animal and
|
|
56
|
+
* an animal that held still would be a dead one.
|
|
57
|
+
*
|
|
58
|
+
* ## A coral takes its size from its own distance
|
|
59
|
+
*
|
|
60
|
+
* The mound keeps its size whatever distance it stands at, because it is ground
|
|
61
|
+
* and ground does not shrink: a hill at the back of a seabed is not drawn
|
|
62
|
+
* smaller, it is drawn higher up the box and hazier, and `seabed.ts` does that
|
|
63
|
+
* already. What stands on the ground is the other thing entirely, and it has to
|
|
64
|
+
* shrink or the picture has no distance in it.
|
|
65
|
+
*
|
|
66
|
+
* So a coral is sized by the mound it grew on and then by how far back it is
|
|
67
|
+
* standing, straight through, the way `flora.ts` sizes the corals it sows on
|
|
68
|
+
* the sand. Anything softer than straight through is what put a reef at the
|
|
69
|
+
* back of the water with corals on it the size of the ones at the front, which
|
|
70
|
+
* is the one fault in a scene that everybody notices and nobody can name.
|
|
71
|
+
*
|
|
72
|
+
* ## Nothing hard ever moves
|
|
73
|
+
*
|
|
74
|
+
* Stony coral is a skeleton, so it holds whatever shape it grew into, and the
|
|
75
|
+
* step here skips it entirely. What moves is the soft half: fans, whips, soft
|
|
76
|
+
* corals and the anemones, all leaning together as the swell crosses the reef
|
|
77
|
+
* and each on its own phase, so the crest breathes rather than pulsing.
|
|
78
|
+
*
|
|
79
|
+
* Everything is seeded, so the same seed builds the same reef twice.
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
import {
|
|
83
|
+
type Blade,
|
|
84
|
+
crown,
|
|
85
|
+
crownAt,
|
|
86
|
+
crownSwept,
|
|
87
|
+
gathered,
|
|
88
|
+
type Tentacle,
|
|
89
|
+
type Twig,
|
|
90
|
+
} from "./flora.ts"
|
|
91
|
+
import { makeNoise2, makeRandom, stir } from "./perlin.ts"
|
|
92
|
+
import type { Point } from "./seabed.ts"
|
|
93
|
+
|
|
94
|
+
export type HeadKind =
|
|
95
|
+
| "anemone"
|
|
96
|
+
| "brain"
|
|
97
|
+
| "elkhorn"
|
|
98
|
+
| "fan"
|
|
99
|
+
| "leather"
|
|
100
|
+
| "plate"
|
|
101
|
+
| "staghorn"
|
|
102
|
+
| "table"
|
|
103
|
+
| "tube"
|
|
104
|
+
| "whip"
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* One thing growing on the reef, wherever it found room.
|
|
108
|
+
*
|
|
109
|
+
* The same shape of fact as a `Plant`, on purpose. A renderer strokes `twigs`
|
|
110
|
+
* at `scale` about `(x, y)`, turned by `lean` and then by `bend`, and needs to
|
|
111
|
+
* know nothing else about what it is drawing.
|
|
112
|
+
*/
|
|
113
|
+
export interface Head {
|
|
114
|
+
/** How far it is leaning in the swell, in radians. */
|
|
115
|
+
bend: number
|
|
116
|
+
/**
|
|
117
|
+
* Whatever else it is made of. Empty for a stencil.
|
|
118
|
+
*
|
|
119
|
+
* A `Plant`'s field, and the same idea: a renderer strokes every line a head
|
|
120
|
+
* hands it at the head's own `girth` and has no business knowing which of
|
|
121
|
+
* them is a tentacle. What has one of these is an animal rather than a
|
|
122
|
+
* skeleton, and it is drawn the way the bed's are rather than being stamped.
|
|
123
|
+
*/
|
|
124
|
+
blades: Blade[]
|
|
125
|
+
/**
|
|
126
|
+
* How many times this head has been drawn again.
|
|
127
|
+
*
|
|
128
|
+
* The only thing a renderer has to watch, and `Plant.cut` for the rock. A
|
|
129
|
+
* skeleton bumps it once and never again, which is most of what grows here:
|
|
130
|
+
* the kinds with no `sway` are stone and have nothing that moves at all.
|
|
131
|
+
*/
|
|
132
|
+
cut: number
|
|
133
|
+
/** 0 is as far off as this water goes, 1 is up against the glass. */
|
|
134
|
+
readonly depth: number
|
|
135
|
+
/** How thick to stroke `points` and `blades`, in the drawing's own units. */
|
|
136
|
+
readonly girth: number
|
|
137
|
+
readonly kind: HeadKind
|
|
138
|
+
/** Its own phase in the swell, so neighbours never breathe together. */
|
|
139
|
+
readonly lane: number
|
|
140
|
+
/** How far off upright it grew, in radians. Nothing on a reef is plumb. */
|
|
141
|
+
readonly lean: number
|
|
142
|
+
/** Its stalk, root to mouth, for a head that has one. Empty for a stencil. */
|
|
143
|
+
readonly points: readonly Point[]
|
|
144
|
+
/** px per unit of its drawing. */
|
|
145
|
+
readonly scale: number
|
|
146
|
+
/** How wide it stands, in px. What its neighbours were asked about. */
|
|
147
|
+
readonly span: number
|
|
148
|
+
/** Its drawing, from `SHAPES`. */
|
|
149
|
+
readonly twigs: readonly Twig[]
|
|
150
|
+
readonly x: number
|
|
151
|
+
readonly y: number
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface Reef {
|
|
155
|
+
/** The rock's own skyline, left to right, for a renderer to close and fill. */
|
|
156
|
+
readonly crest: readonly Point[]
|
|
157
|
+
/** How far back the mound stands. Everything on it is at least this far. */
|
|
158
|
+
readonly depth: number
|
|
159
|
+
readonly heads: readonly Head[]
|
|
160
|
+
/** The share of the kinds there are that found somewhere to live. */
|
|
161
|
+
readonly health: number
|
|
162
|
+
/**
|
|
163
|
+
* Whether a point is over this reef, clear of the rock, and near enough to
|
|
164
|
+
* count as being on it.
|
|
165
|
+
*
|
|
166
|
+
* The question anything that lives here asks before it goes anywhere. It is
|
|
167
|
+
* the reef's to answer rather than the animal's, because the reef is the one
|
|
168
|
+
* that knows where it ends. `depth` is how far forward the asker is, and it
|
|
169
|
+
* matters for the reason `surfaceAt` gives.
|
|
170
|
+
*/
|
|
171
|
+
holds(x: number, y: number, depth?: number): boolean
|
|
172
|
+
resize(width: number, height: number, floor: (x: number, depth?: number) => number): void
|
|
173
|
+
/** Where the rock reaches, left and right, in px. */
|
|
174
|
+
readonly span: readonly [number, number]
|
|
175
|
+
step(seconds: number): void
|
|
176
|
+
/**
|
|
177
|
+
* The top of the rock at x, or the ground where the reef does not reach.
|
|
178
|
+
*
|
|
179
|
+
* `depth` is how far forward the thing standing there is, and it is not a
|
|
180
|
+
* decoration: the mound has a near face that falls away towards the glass, so
|
|
181
|
+
* the rock is lower in front of the crest than at it, and anything standing
|
|
182
|
+
* further forward than the reef itself has less of it overhead. It defaults
|
|
183
|
+
* to the reef's own distance, which is the skyline `crest` is cut from.
|
|
184
|
+
*/
|
|
185
|
+
surfaceAt(x: number, depth?: number): number
|
|
186
|
+
/** A long stretch of swell in one go, for a caller winding a scene forward. */
|
|
187
|
+
wind(seconds: number): void
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface ReefOptions {
|
|
191
|
+
/** The ground at x, at a given distance; `seabed.floorAt`. */
|
|
192
|
+
floor: (x: number, depth?: number) => number
|
|
193
|
+
/** How many corals to try to grow, once every kind has had its one. */
|
|
194
|
+
heads?: number
|
|
195
|
+
height: number
|
|
196
|
+
seed: number
|
|
197
|
+
/**
|
|
198
|
+
* How far a head may move from its last drawing before it is drawn again, in
|
|
199
|
+
* px.
|
|
200
|
+
*
|
|
201
|
+
* `FloraOptions.tolerance` for the rock, and the same bargain. Most of what
|
|
202
|
+
* grows here is a skeleton that never moves, so most of the reef costs one
|
|
203
|
+
* comparison a step once it has been drawn once; what sways pays for the
|
|
204
|
+
* frames it is actually using. At nothing every head is recut on every step,
|
|
205
|
+
* which is how this behaved before.
|
|
206
|
+
*/
|
|
207
|
+
tolerance?: number
|
|
208
|
+
width: number
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* What each kind of coral needs, and what it does with what it gets.
|
|
213
|
+
*
|
|
214
|
+
* The whole of the placement is in this table. There is one routine that grows
|
|
215
|
+
* a coral and it reads these numbers; a kind that wanted its own branch would
|
|
216
|
+
* be a second reef wearing the first one's interface.
|
|
217
|
+
*
|
|
218
|
+
* What decides everything is `perch` and `flow`. A reef is banded, and the
|
|
219
|
+
* bands are not a matter of taste: the crest is swept by every wave that
|
|
220
|
+
* arrives and only the strong-boned and the flexible hold there, while the foot
|
|
221
|
+
* is still and shaded and belongs to the soft and the spreading. Get those
|
|
222
|
+
* right and the reef looks studied from across a room without any one coral
|
|
223
|
+
* being worth looking at.
|
|
224
|
+
*/
|
|
225
|
+
interface Sort {
|
|
226
|
+
/**
|
|
227
|
+
* How far down the face it stands, as a share of the mound's rise.
|
|
228
|
+
*
|
|
229
|
+
* Taken against how much rock there is where it landed, so it comes to
|
|
230
|
+
* nothing out on the apron. Only a face can be clung to, and the ground
|
|
231
|
+
* beside a reef is not one: a sponge out there is standing on the flat, and
|
|
232
|
+
* one that had kept its share of the face would be standing in the water
|
|
233
|
+
* below the reef with nothing under it.
|
|
234
|
+
*/
|
|
235
|
+
readonly cling: number
|
|
236
|
+
/** The run of water it will take, still to swept. */
|
|
237
|
+
readonly flow: readonly [number, number]
|
|
238
|
+
/** The band of rock it will take, foot to crest. */
|
|
239
|
+
readonly perch: readonly [number, number]
|
|
240
|
+
/** How much clear water it wants around it, against its own width. */
|
|
241
|
+
readonly room: number
|
|
242
|
+
/** How big it grows against what `GROWTH` allows, smallest to largest. */
|
|
243
|
+
readonly size: readonly [number, number]
|
|
244
|
+
/** How far it leans in the swell, in radians. A skeleton is 0. */
|
|
245
|
+
readonly sway: number
|
|
246
|
+
/** How many of it there are, once every kind has its one. */
|
|
247
|
+
readonly weight: number
|
|
248
|
+
/** How wide its drawing is, in its own units. */
|
|
249
|
+
readonly width: number
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const SORTS: Readonly<Record<HeadKind, Sort>> = {
|
|
253
|
+
/** Soft, sheltered, and the only thing here anything lives inside. */
|
|
254
|
+
anemone: {
|
|
255
|
+
cling: 0.3,
|
|
256
|
+
flow: [0, 0.78],
|
|
257
|
+
perch: [0.15, 0.9],
|
|
258
|
+
room: 1.6,
|
|
259
|
+
size: [1, 1.5],
|
|
260
|
+
sway: 0.07,
|
|
261
|
+
weight: 3,
|
|
262
|
+
width: 28,
|
|
263
|
+
},
|
|
264
|
+
/** A dome that holds anywhere, and the one shape here with a body. */
|
|
265
|
+
brain: {
|
|
266
|
+
cling: 0.15,
|
|
267
|
+
flow: [0, 1],
|
|
268
|
+
perch: [0.1, 1],
|
|
269
|
+
room: 1.1,
|
|
270
|
+
size: [0.6, 1.5],
|
|
271
|
+
sway: 0,
|
|
272
|
+
weight: 5,
|
|
273
|
+
width: 30,
|
|
274
|
+
},
|
|
275
|
+
/** Broad blades into the surge, which is the one place it beats anything. */
|
|
276
|
+
elkhorn: {
|
|
277
|
+
cling: 0,
|
|
278
|
+
flow: [0.55, 1],
|
|
279
|
+
perch: [0.6, 1],
|
|
280
|
+
room: 1.4,
|
|
281
|
+
size: [0.7, 1.2],
|
|
282
|
+
sway: 0,
|
|
283
|
+
weight: 3,
|
|
284
|
+
width: 42,
|
|
285
|
+
},
|
|
286
|
+
/** A mesh held across the current, so it stands where the current runs. */
|
|
287
|
+
fan: {
|
|
288
|
+
cling: 0.55,
|
|
289
|
+
flow: [0.45, 1],
|
|
290
|
+
perch: [0.3, 0.95],
|
|
291
|
+
room: 1.3,
|
|
292
|
+
size: [0.6, 1.3],
|
|
293
|
+
sway: 0.1,
|
|
294
|
+
weight: 4,
|
|
295
|
+
width: 34,
|
|
296
|
+
},
|
|
297
|
+
/** Soft and lobed, down at the foot where nothing is pulling at it. */
|
|
298
|
+
leather: {
|
|
299
|
+
cling: 0.4,
|
|
300
|
+
flow: [0, 0.45],
|
|
301
|
+
perch: [0, 0.5],
|
|
302
|
+
room: 1.2,
|
|
303
|
+
size: [0.7, 1.2],
|
|
304
|
+
sway: 0.07,
|
|
305
|
+
weight: 3,
|
|
306
|
+
width: 26,
|
|
307
|
+
},
|
|
308
|
+
/** Spread flat to catch what light gets down there. */
|
|
309
|
+
plate: {
|
|
310
|
+
cling: 0.35,
|
|
311
|
+
flow: [0, 0.55],
|
|
312
|
+
perch: [0, 0.55],
|
|
313
|
+
room: 1.5,
|
|
314
|
+
size: [0.7, 1.3],
|
|
315
|
+
sway: 0,
|
|
316
|
+
weight: 3,
|
|
317
|
+
width: 46,
|
|
318
|
+
},
|
|
319
|
+
/** Fine branches, which snap in surge, so never at the crest. */
|
|
320
|
+
staghorn: {
|
|
321
|
+
cling: 0.1,
|
|
322
|
+
flow: [0, 0.6],
|
|
323
|
+
perch: [0.35, 0.9],
|
|
324
|
+
room: 1.3,
|
|
325
|
+
size: [0.6, 1.1],
|
|
326
|
+
sway: 0,
|
|
327
|
+
weight: 4,
|
|
328
|
+
width: 36,
|
|
329
|
+
},
|
|
330
|
+
/** A floor of its own, held up into the light at the top of the mound. */
|
|
331
|
+
table: {
|
|
332
|
+
cling: 0,
|
|
333
|
+
flow: [0.2, 0.9],
|
|
334
|
+
perch: [0.7, 1],
|
|
335
|
+
room: 1.7,
|
|
336
|
+
size: [0.7, 1.2],
|
|
337
|
+
sway: 0,
|
|
338
|
+
weight: 2,
|
|
339
|
+
width: 46,
|
|
340
|
+
},
|
|
341
|
+
/** Sponge, not coral, and it takes the shaded foot nothing else wants. */
|
|
342
|
+
tube: {
|
|
343
|
+
cling: 0.5,
|
|
344
|
+
flow: [0, 0.4],
|
|
345
|
+
perch: [0, 0.45],
|
|
346
|
+
room: 1.2,
|
|
347
|
+
size: [0.5, 0.9],
|
|
348
|
+
sway: 0.05,
|
|
349
|
+
weight: 3,
|
|
350
|
+
width: 24,
|
|
351
|
+
},
|
|
352
|
+
/** One line into the flow, and the thing on this reef that moves most. */
|
|
353
|
+
whip: {
|
|
354
|
+
cling: 0.25,
|
|
355
|
+
flow: [0.5, 1],
|
|
356
|
+
perch: [0.4, 1],
|
|
357
|
+
room: 0.9,
|
|
358
|
+
size: [0.7, 1.4],
|
|
359
|
+
sway: 0.22,
|
|
360
|
+
weight: 3,
|
|
361
|
+
width: 12,
|
|
362
|
+
},
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Every kind there is, which is what `Reef.health` is measured against. */
|
|
366
|
+
export const KINDS = Object.keys(SORTS) as readonly HeadKind[]
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* What each kind is, as strokes from one base.
|
|
370
|
+
*
|
|
371
|
+
* `flora.ts` argues for the idiom and it holds here: round caps on tapering
|
|
372
|
+
* strokes read as a growing thing without a single closed outline being drawn,
|
|
373
|
+
* and a kind is told apart from its neighbour by how wide it gets against how
|
|
374
|
+
* tall and how thick the strokes are. Nose-up, base at the origin, in the same
|
|
375
|
+
* units the bed's corals are drawn in, so one renderer serves both.
|
|
376
|
+
*/
|
|
377
|
+
export const SHAPES: Readonly<Record<HeadKind, readonly Twig[]>> = {
|
|
378
|
+
/** None. It is the one animal on the rock, and an animal is not a stamp. */
|
|
379
|
+
anemone: [],
|
|
380
|
+
brain: [
|
|
381
|
+
{ d: "M-13 0 C-13 -8 -8 -13 -1 -14", width: 7 },
|
|
382
|
+
{ d: "M-6 0 C-7 -7 -4 -11 0 -12", width: 7 },
|
|
383
|
+
{ d: "M6 0 C7 -7 5 -11 1 -13", width: 7 },
|
|
384
|
+
{ d: "M13 -1 C13 -8 8 -13 1 -14", width: 7 },
|
|
385
|
+
],
|
|
386
|
+
elkhorn: [
|
|
387
|
+
{ d: "M0 0 C0 -5 -1 -9 -2 -12", width: 7 },
|
|
388
|
+
{ d: "M0 0 C-1 -10 -6 -16 -14 -24", width: 6 },
|
|
389
|
+
{ d: "M0 0 C1 -12 4 -20 10 -30", width: 5.5 },
|
|
390
|
+
{ d: "M-8 -14 C-14 -18 -20 -20 -26 -22", width: 5 },
|
|
391
|
+
{ d: "M5 -16 C11 -21 17 -23 24 -25", width: 4.5 },
|
|
392
|
+
],
|
|
393
|
+
fan: [
|
|
394
|
+
{ d: "M0 0 C0 -8 -2 -12 -3 -16", width: 3.5 },
|
|
395
|
+
{ d: "M-3 -16 C-9 -20 -13 -24 -15 -30", width: 2 },
|
|
396
|
+
{ d: "M-3 -16 C-6 -22 -7 -27 -8 -32", width: 2 },
|
|
397
|
+
{ d: "M-3 -16 C0 -22 1 -27 1 -33", width: 2 },
|
|
398
|
+
{ d: "M-3 -16 C3 -21 7 -25 10 -30", width: 2 },
|
|
399
|
+
{ d: "M-3 -16 C6 -19 11 -21 15 -25", width: 2 },
|
|
400
|
+
],
|
|
401
|
+
leather: [
|
|
402
|
+
{ d: "M0 0 C-2 -6 -6 -9 -9 -15", width: 8 },
|
|
403
|
+
{ d: "M0 0 C0 -7 1 -12 2 -18", width: 8 },
|
|
404
|
+
{ d: "M0 0 C3 -6 7 -9 10 -14", width: 7 },
|
|
405
|
+
],
|
|
406
|
+
plate: [
|
|
407
|
+
{ d: "M0 0 C0 -2 0 -3 0 -4", width: 6 },
|
|
408
|
+
{ d: "M0 0 C-6 -2 -14 -4 -22 -5", width: 5 },
|
|
409
|
+
{ d: "M0 0 C6 -2 14 -4 21 -6", width: 5 },
|
|
410
|
+
{ d: "M-14 -4 C-15 -7 -14 -8 -12 -10", width: 3 },
|
|
411
|
+
{ d: "M15 -5 C17 -7 17 -9 16 -11", width: 3 },
|
|
412
|
+
],
|
|
413
|
+
staghorn: [
|
|
414
|
+
{ d: "M0 0 C-1 -11 -3 -20 -7 -31", width: 4 },
|
|
415
|
+
{ d: "M0 0 C2 -9 5 -17 7 -28", width: 3.5 },
|
|
416
|
+
{ d: "M-4 -19 C-9 -24 -13 -26 -18 -33", width: 2.4 },
|
|
417
|
+
{ d: "M5 -18 C9 -24 13 -25 17 -30", width: 2.4 },
|
|
418
|
+
{ d: "M-2 -8 C-7 -11 -11 -12 -15 -13", width: 2.2 },
|
|
419
|
+
{ d: "M3 -10 C8 -13 11 -15 15 -17", width: 2.2 },
|
|
420
|
+
],
|
|
421
|
+
table: [
|
|
422
|
+
{ d: "M0 0 C-1 -6 -1 -10 0 -13", width: 6 },
|
|
423
|
+
{ d: "M0 -13 C-8 -14 -16 -15 -23 -15", width: 4 },
|
|
424
|
+
{ d: "M0 -13 C8 -14 16 -15 23 -16", width: 4 },
|
|
425
|
+
{ d: "M-12 -15 C-13 -17 -13 -19 -12 -20", width: 2.4 },
|
|
426
|
+
{ d: "M11 -15 C12 -17 12 -19 11 -21", width: 2.4 },
|
|
427
|
+
],
|
|
428
|
+
tube: [
|
|
429
|
+
{ d: "M-8 0 C-9 -8 -9 -14 -8 -19", width: 6 },
|
|
430
|
+
{ d: "M0 0 C-1 -9 -1 -18 0 -25", width: 6.5 },
|
|
431
|
+
{ d: "M8 -1 C9 -7 9 -12 8 -16", width: 5.5 },
|
|
432
|
+
],
|
|
433
|
+
whip: [
|
|
434
|
+
{ d: "M0 0 C-2 -9 -4 -16 -4 -24", width: 2.2 },
|
|
435
|
+
{ d: "M0 0 C1 -12 -1 -24 2 -38", width: 2.6 },
|
|
436
|
+
],
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** Where the mound sits across the box, and how much of it it may take. */
|
|
440
|
+
const SEAT_LEAST = 0.26
|
|
441
|
+
|
|
442
|
+
const SEAT_SPAN = 0.48
|
|
443
|
+
|
|
444
|
+
const HALF_LEAST = 0.15
|
|
445
|
+
|
|
446
|
+
const HALF_SPAN = 0.12
|
|
447
|
+
|
|
448
|
+
/** How high it stands off the ground, as a share of the box. */
|
|
449
|
+
const RISE_LEAST = 0.1
|
|
450
|
+
|
|
451
|
+
const RISE_SPAN = 0.08
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* How tall the mound may stand against its own width.
|
|
455
|
+
*
|
|
456
|
+
* A reef is a mound and not a tower. On a tall narrow window the height it
|
|
457
|
+
* would take from the box is more than the width there is to put it on, and
|
|
458
|
+
* what that builds is a spire with a coral balanced on the top of it.
|
|
459
|
+
*/
|
|
460
|
+
const TALLEST = 0.62
|
|
461
|
+
|
|
462
|
+
/** How far back it stands. Never up against the glass; it is scenery. */
|
|
463
|
+
const DEPTH_FAR = 0.5
|
|
464
|
+
|
|
465
|
+
const DEPTH_NEAR = 0.82
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* The mound's profile: how sharply its sides fall, and how flat its top is.
|
|
469
|
+
*
|
|
470
|
+
* A reef is not a hill. What it does is stand up out of the ground at a steep
|
|
471
|
+
* face and then run nearly level along the top, because the top is where it has
|
|
472
|
+
* grown up to and stopped, and a dome would say the opposite: that the middle
|
|
473
|
+
* grew fastest, which is what a pile of sand does and not what a reef does.
|
|
474
|
+
*/
|
|
475
|
+
const BROW = 2.6
|
|
476
|
+
|
|
477
|
+
const BULGE = 0.5
|
|
478
|
+
|
|
479
|
+
/** How much taller one end is than the other, so it has a windward side. */
|
|
480
|
+
const LIST = 0.22
|
|
481
|
+
|
|
482
|
+
/** How many lumps fit across the mound, and how much of its rise they take. */
|
|
483
|
+
const LUMP_CELLS = 3.4
|
|
484
|
+
|
|
485
|
+
const LUMP_SHARE = 0.24
|
|
486
|
+
|
|
487
|
+
/** How many points the skyline is cut into. */
|
|
488
|
+
const CREST_STEPS = 96
|
|
489
|
+
|
|
490
|
+
/** How many corals to grow once every kind has had its one. */
|
|
491
|
+
const HEADS = 52
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* How far past the rock the reef's own rubble reaches, against its half-width.
|
|
495
|
+
*
|
|
496
|
+
* A reef does not stop where the rock stops. What surrounds one is an apron of
|
|
497
|
+
* its own broken pieces, and the things that want shelter and shade live out
|
|
498
|
+
* there rather than on the mound: it is the only ground on the whole reef that
|
|
499
|
+
* is flat, still and low. Without it every soft coral competes for the foot of
|
|
500
|
+
* the face and most of them lose, which is a reef with a bare skirt around it.
|
|
501
|
+
*/
|
|
502
|
+
const APRON = 1.28
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* How many places a coral will try before it gives up and does not grow.
|
|
506
|
+
*
|
|
507
|
+
* It gives up rather than settling, which is the whole of why the reef comes
|
|
508
|
+
* out banded. A whip that took the first spot it was offered would end up at
|
|
509
|
+
* the foot as often as at the crest, and a reef where everything is anywhere is
|
|
510
|
+
* a reef with no bands in it, which is to say a heap.
|
|
511
|
+
*/
|
|
512
|
+
const TRIES = 40
|
|
513
|
+
|
|
514
|
+
/** How far the reef's own water reaches past the rock, against its half-width. */
|
|
515
|
+
const REACH = 0.34
|
|
516
|
+
|
|
517
|
+
/** How far apart in height counts as a shelf of its own, against a width. */
|
|
518
|
+
const TIER = 0.55
|
|
519
|
+
|
|
520
|
+
/** How far above the crest still counts as being over the reef. */
|
|
521
|
+
const HEADROOM = 0.9
|
|
522
|
+
|
|
523
|
+
/** How far a coral may have grown off upright, in radians. */
|
|
524
|
+
const LEAN = 0.22
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* How big a coral grows against the mound it is on, in scale per px of rise.
|
|
528
|
+
*
|
|
529
|
+
* Against the mound rather than in px, because the mound is the only thing on
|
|
530
|
+
* screen a coral can be judged against. Sized absolutely, the same coral is a
|
|
531
|
+
* boulder on a narrow window and a pebble on a wide one, and the reef stops
|
|
532
|
+
* being a reef at either end: a handful of shapes filling it, or a bald rock
|
|
533
|
+
* with a rash on it.
|
|
534
|
+
*/
|
|
535
|
+
const GROWTH = 0.0072
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* How tall an anemone's column stands, in the units the drawings are cut in.
|
|
539
|
+
*
|
|
540
|
+
* Short and stout, which is what `flora.ts` argues for at greater length: what
|
|
541
|
+
* an anemone has instead of height is a crown, and a tall one is a plant.
|
|
542
|
+
*/
|
|
543
|
+
const COLUMN = 16
|
|
544
|
+
|
|
545
|
+
/** How thick a crown is stroked, as a share of its column. `flora.ts` again. */
|
|
546
|
+
const GIRTH = 0.15
|
|
547
|
+
|
|
548
|
+
/** How fast a crown works through its own feeling. */
|
|
549
|
+
const CROWN_RATE = 1.1
|
|
550
|
+
|
|
551
|
+
/** How fast the swell crosses the reef, and how many of it fit across. */
|
|
552
|
+
const SWELL_RATE = 0.55
|
|
553
|
+
|
|
554
|
+
const SWELL_CELLS = 1.8
|
|
555
|
+
|
|
556
|
+
/** How much of the lean is the swell passing and how much is the head's own. */
|
|
557
|
+
const SWELL_SHARE = 0.6
|
|
558
|
+
|
|
559
|
+
/** Where an anemone's stalk leaves the rock, and where its crown opens. */
|
|
560
|
+
const ROOT: Point = { x: 0, y: 0 }
|
|
561
|
+
|
|
562
|
+
const MOUTH: Point = { x: 0, y: -COLUMN }
|
|
563
|
+
|
|
564
|
+
/** How far a head moves before it is worth drawing again, in px, by default. */
|
|
565
|
+
const TOLERANCE = 0.25
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* How far each kind's drawing reaches from where it is rooted, in its own units.
|
|
569
|
+
*
|
|
570
|
+
* Read off the drawings rather than written down beside them. A shape nudged by
|
|
571
|
+
* eye and a number kept by hand part company on the first edit, and nothing
|
|
572
|
+
* anywhere says so; this cannot go stale because there is only the one copy.
|
|
573
|
+
*
|
|
574
|
+
* Every path in `SHAPES` is an absolute move followed by absolute curves, so
|
|
575
|
+
* the numbers in it are points. A curve stays inside the hull of its own
|
|
576
|
+
* control points, which makes the furthest of them a bound on the whole drawing
|
|
577
|
+
* rather than a sample of it, and a bound is what an estimate of movement wants
|
|
578
|
+
* to be wrong in the safe direction.
|
|
579
|
+
*
|
|
580
|
+
* An anemone has no drawing here, and its reach is asked of the crown it was
|
|
581
|
+
* dealt instead, since a crown is dealt per animal rather than per kind.
|
|
582
|
+
*/
|
|
583
|
+
const REACHES = ((): Readonly<Record<HeadKind, number>> => {
|
|
584
|
+
const reaches = {} as Record<HeadKind, number>
|
|
585
|
+
for (const kind of KINDS) reaches[kind] = furthest(SHAPES[kind])
|
|
586
|
+
return reaches
|
|
587
|
+
})()
|
|
588
|
+
|
|
589
|
+
function furthest(twigs: readonly Twig[]): number {
|
|
590
|
+
let worst = 0
|
|
591
|
+
|
|
592
|
+
for (const twig of twigs) {
|
|
593
|
+
const numbers = twig.d.match(/-?\d+(?:\.\d+)?/g) ?? []
|
|
594
|
+
for (let at = 0; at + 1 < numbers.length; at += 2) {
|
|
595
|
+
worst = Math.max(worst, Math.hypot(Number(numbers[at]), Number(numbers[at + 1])))
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return worst
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/** A floor for the box, so a sliver of a window never divides by nothing. */
|
|
603
|
+
const MIN_SPAN = 1
|
|
604
|
+
|
|
605
|
+
export function createReef(options: ReefOptions): Reef {
|
|
606
|
+
const noise = makeNoise2(options.seed ^ 0x2b91)
|
|
607
|
+
const gust = makeNoise2(options.seed ^ 0x5ce3)
|
|
608
|
+
|
|
609
|
+
let width = Math.max(MIN_SPAN, options.width)
|
|
610
|
+
let height = Math.max(MIN_SPAN, options.height)
|
|
611
|
+
let floor = options.floor
|
|
612
|
+
let clock = 0
|
|
613
|
+
|
|
614
|
+
let middle = width / 2
|
|
615
|
+
let half = width * HALF_LEAST
|
|
616
|
+
let rise = height * RISE_LEAST
|
|
617
|
+
let depth = DEPTH_FAR
|
|
618
|
+
|
|
619
|
+
const heads: Head[] = []
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* The tentacles each head was dealt, or nothing for anything that is a
|
|
623
|
+
* stencil.
|
|
624
|
+
*
|
|
625
|
+
* Beside the heads rather than on them, the way `flora.ts` keeps a plant's
|
|
626
|
+
* sway off the `Plant`. What a renderer wants is the crown cut at this
|
|
627
|
+
* moment, which is `Head.blades`; how it was dealt is this file's business
|
|
628
|
+
* and stays here.
|
|
629
|
+
*/
|
|
630
|
+
const crowns: (readonly Tentacle[] | null)[] = []
|
|
631
|
+
|
|
632
|
+
const tolerance = Math.max(0, options.tolerance ?? TOLERANCE)
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* How far each head's drawing reaches from where it is rooted, in px.
|
|
636
|
+
*
|
|
637
|
+
* Its kind's reach at its own scale, except for an anemone, whose drawing is
|
|
638
|
+
* the column it stands on and the longest tentacle it happened to be dealt.
|
|
639
|
+
*/
|
|
640
|
+
const reaches: number[] = []
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* A head as it stood when it was last drawn.
|
|
644
|
+
*
|
|
645
|
+
* The two numbers a drawing is made from and nothing else. Against the last
|
|
646
|
+
* drawing rather than the last step, because a swell that pushed a whip over
|
|
647
|
+
* and let it back has not moved it, and a sum of steps says it has.
|
|
648
|
+
*/
|
|
649
|
+
const held: ({ readonly bend: number; readonly phase: number } | null)[] = []
|
|
650
|
+
|
|
651
|
+
let bounds: readonly [number, number] = [0, 0]
|
|
652
|
+
let health = 0
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* How high the rock stands at a place along it, from nothing to all of it.
|
|
656
|
+
*
|
|
657
|
+
* The one function everything about this reef is measured in. The crest is
|
|
658
|
+
* cut from it, a coral's `perch` is read off it, and a fish's ceiling is it
|
|
659
|
+
* again, so the rock cannot disagree with itself about where its own top is.
|
|
660
|
+
*/
|
|
661
|
+
function climb(t: number): number {
|
|
662
|
+
if (Math.abs(t) >= 1) return 0
|
|
663
|
+
const arch = (1 - Math.abs(t) ** BROW) ** BULGE
|
|
664
|
+
const lump = 1 + noise(t * LUMP_CELLS, 0) * LUMP_SHARE
|
|
665
|
+
return Math.min(1, Math.max(0, arch * (1 + LIST * t) * lump))
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* How hard the water runs at a place along it, still to swept.
|
|
670
|
+
*
|
|
671
|
+
* Height is most of it, since the crest is what meets the water first, and
|
|
672
|
+
* the rest is which way the reef faces: one end takes the weather and the
|
|
673
|
+
* other lies behind the mound in its own shelter.
|
|
674
|
+
*/
|
|
675
|
+
function swept(t: number): number {
|
|
676
|
+
return Math.min(1, climb(t) * 0.62 + ((t + 1) / 2) * 0.38)
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* How much of the rock is still overhead at a distance, all of it to none.
|
|
681
|
+
*
|
|
682
|
+
* The mound is a body rather than a wall, so its front slopes away from the
|
|
683
|
+
* crest towards the viewer, and something standing a little forward of it is
|
|
684
|
+
* standing on the part of that slope which has already come down. At the
|
|
685
|
+
* glass there is no rock left at all, which is what open water in front of a
|
|
686
|
+
* reef is.
|
|
687
|
+
*/
|
|
688
|
+
const standing = (stand: number): number => {
|
|
689
|
+
if (stand <= depth) return 1
|
|
690
|
+
return 1 - Math.min(1, (stand - depth) / Math.max(1 - depth, 1e-6))
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const surfaceAt = (x: number, stand: number = depth): number => {
|
|
694
|
+
const t = (x - middle) / Math.max(MIN_SPAN, half)
|
|
695
|
+
return floor(x, stand) - rise * climb(t) * standing(stand)
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* A coral, if it can find anywhere on this rock it can live.
|
|
700
|
+
*
|
|
701
|
+
* It is handed the reef and nothing else. Where it ends up is its own answer
|
|
702
|
+
* to what it is standing on and who is already standing there, which is why
|
|
703
|
+
* no caller here ever names a place.
|
|
704
|
+
*/
|
|
705
|
+
function grow(kind: HeadKind, roll: () => number): Head | null {
|
|
706
|
+
const sort = SORTS[kind]
|
|
707
|
+
|
|
708
|
+
for (let tries = 0; tries < TRIES; tries++) {
|
|
709
|
+
const t = (-1 + roll() * 2) * APRON
|
|
710
|
+
const perch = climb(t)
|
|
711
|
+
const flow = swept(t)
|
|
712
|
+
|
|
713
|
+
if (perch < sort.perch[0] || perch > sort.perch[1]) continue
|
|
714
|
+
if (flow < sort.flow[0] || flow > sort.flow[1]) continue
|
|
715
|
+
|
|
716
|
+
const x = middle + t * half
|
|
717
|
+
const face = sort.cling * perch
|
|
718
|
+
const stand = depth + face * (1 - depth) * roll()
|
|
719
|
+
const grown = sort.size[0] + roll() * (sort.size[1] - sort.size[0])
|
|
720
|
+
const scale = grown * rise * GROWTH * stand
|
|
721
|
+
const span = sort.width * scale
|
|
722
|
+
|
|
723
|
+
const y = surfaceAt(x, stand)
|
|
724
|
+
if (!room(x, y, span * sort.room)) continue
|
|
725
|
+
|
|
726
|
+
const tentacles = kind === "anemone" ? crown(stir(roll() * 0xffffff), COLUMN * scale) : null
|
|
727
|
+
|
|
728
|
+
const head: Head = {
|
|
729
|
+
bend: 0,
|
|
730
|
+
blades: [],
|
|
731
|
+
cut: 0,
|
|
732
|
+
depth: stand,
|
|
733
|
+
girth: tentacles ? COLUMN * GIRTH : 0,
|
|
734
|
+
kind,
|
|
735
|
+
lane: roll() * Math.PI * 2,
|
|
736
|
+
lean: (roll() - 0.5) * LEAN * 2,
|
|
737
|
+
points: tentacles ? [ROOT, MOUTH] : [],
|
|
738
|
+
scale,
|
|
739
|
+
span,
|
|
740
|
+
twigs: gathered(SHAPES[kind]),
|
|
741
|
+
x,
|
|
742
|
+
y,
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
heads.push(head)
|
|
746
|
+
crowns.push(tentacles)
|
|
747
|
+
held.push(null)
|
|
748
|
+
reaches.push(
|
|
749
|
+
tentacles
|
|
750
|
+
? COLUMN * scale * (1 + Math.max(...tentacles.map((one) => one.span)))
|
|
751
|
+
: REACHES[kind] * scale,
|
|
752
|
+
)
|
|
753
|
+
return head
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
return null
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Whether a coral of that size would be standing on one already there.
|
|
761
|
+
*
|
|
762
|
+
* Height is half the question, and leaving it out was what made the first
|
|
763
|
+
* reef a row. A face has shelves up it, and a fan on one shelf is no argument
|
|
764
|
+
* against a sponge on the shelf below however nearly they share a column, so
|
|
765
|
+
* two corals only crowd each other if they are at much the same height as
|
|
766
|
+
* well as much the same place.
|
|
767
|
+
*/
|
|
768
|
+
function room(x: number, y: number, want: number): boolean {
|
|
769
|
+
for (const other of heads) {
|
|
770
|
+
const near = (want + other.span) / 2
|
|
771
|
+
if (Math.abs(other.y - y) > near * TIER) continue
|
|
772
|
+
if (Math.abs(other.x - x) < near) return false
|
|
773
|
+
}
|
|
774
|
+
return true
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* The reef this seed builds, from its own generator started afresh.
|
|
779
|
+
*
|
|
780
|
+
* Its own, and restarted whenever the box changes, for the reason `relics.ts`
|
|
781
|
+
* gives: what a sea holds has to be a pure function of its seed, or a window
|
|
782
|
+
* dragged wider deals a second reef and nobody can look at the same one twice.
|
|
783
|
+
*/
|
|
784
|
+
function raise(): void {
|
|
785
|
+
const roll = makeRandom(options.seed ^ 0x91af)
|
|
786
|
+
|
|
787
|
+
middle = width * (SEAT_LEAST + roll() * SEAT_SPAN)
|
|
788
|
+
half = width * (HALF_LEAST + roll() * HALF_SPAN)
|
|
789
|
+
rise = Math.min(height * (RISE_LEAST + roll() * RISE_SPAN), half * TALLEST)
|
|
790
|
+
depth = DEPTH_FAR + roll() * (DEPTH_NEAR - DEPTH_FAR)
|
|
791
|
+
|
|
792
|
+
heads.length = 0
|
|
793
|
+
crowns.length = 0
|
|
794
|
+
held.length = 0
|
|
795
|
+
reaches.length = 0
|
|
796
|
+
crest.length = 0
|
|
797
|
+
bounds = [middle - half, middle + half]
|
|
798
|
+
|
|
799
|
+
for (let step = 0; step <= CREST_STEPS; step++) {
|
|
800
|
+
const x = middle - half + ((half * 2) / CREST_STEPS) * step
|
|
801
|
+
crest.push({ x, y: surfaceAt(x) })
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
for (const kind of dealt(roll)) grow(kind, roll)
|
|
805
|
+
|
|
806
|
+
const rest = spread()
|
|
807
|
+
for (let made = 0; made < Math.max(0, options.heads ?? HEADS); made++) {
|
|
808
|
+
grow(rest[Math.floor(roll() * rest.length)] ?? "brain", roll)
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
health = new Set(heads.map((one) => one.kind)).size / KINDS.length
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Every kind, in an order this seed made up.
|
|
816
|
+
*
|
|
817
|
+
* The first pass gives each kind of coral first refusal on a bare rock, and
|
|
818
|
+
* whoever asks first gets the best of the band they share. Asking in the
|
|
819
|
+
* order they happen to be written down means the same kind is last on every
|
|
820
|
+
* reef ever built, and the last one is the one that goes without.
|
|
821
|
+
*/
|
|
822
|
+
function dealt(roll: () => number): HeadKind[] {
|
|
823
|
+
const order = [...KINDS]
|
|
824
|
+
for (let at = order.length - 1; at > 0; at--) {
|
|
825
|
+
const swap = Math.floor(roll() * (at + 1))
|
|
826
|
+
const held = order[at] as HeadKind
|
|
827
|
+
order[at] = order[swap] as HeadKind
|
|
828
|
+
order[swap] = held
|
|
829
|
+
}
|
|
830
|
+
return order
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/** Every kind repeated by its weight, which is the cheapest weighting there is. */
|
|
834
|
+
function spread(): HeadKind[] {
|
|
835
|
+
const bag: HeadKind[] = []
|
|
836
|
+
for (const kind of KINDS) {
|
|
837
|
+
for (let each = 0; each < SORTS[kind].weight; each++) bag.push(kind)
|
|
838
|
+
}
|
|
839
|
+
return bag
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/** How far a head is leaning at the moment, from the swell and its own phase. */
|
|
843
|
+
function lean(one: Head): number {
|
|
844
|
+
const sway = SORTS[one.kind].sway
|
|
845
|
+
if (sway === 0) return 0
|
|
846
|
+
|
|
847
|
+
const passing = gust(
|
|
848
|
+
((one.x - middle) / Math.max(MIN_SPAN, half)) * SWELL_CELLS,
|
|
849
|
+
clock * SWELL_RATE,
|
|
850
|
+
)
|
|
851
|
+
const own = Math.sin(clock * Math.PI * 2 * SWELL_RATE + one.lane)
|
|
852
|
+
return sway * (passing * SWELL_SHARE + own * (1 - SWELL_SHARE))
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* How far a head can have moved since it was last drawn.
|
|
857
|
+
*
|
|
858
|
+
* Two things move it and both are in here, because one drawing covers the
|
|
859
|
+
* two. The whole of it turns in the swell, and the furthest part of a drawing
|
|
860
|
+
* turned about its root travels its reach for a radian of turn. An anemone's
|
|
861
|
+
* tentacles curl on their own clock besides, whether the water is doing
|
|
862
|
+
* anything or not, and `flora.ts` owns the estimate of that for the reason it
|
|
863
|
+
* owns the crown.
|
|
864
|
+
*
|
|
865
|
+
* Neither is measured off the drawing, because making one to find out is the
|
|
866
|
+
* bill this is here to avoid.
|
|
867
|
+
*/
|
|
868
|
+
function wander(at: number, bend: number, phase: number): number {
|
|
869
|
+
const one = heads[at]
|
|
870
|
+
const was = held[at]
|
|
871
|
+
if (!one || !was) return Number.POSITIVE_INFINITY
|
|
872
|
+
|
|
873
|
+
return (
|
|
874
|
+
(reaches[at] ?? 0) * Math.abs(bend - was.bend) +
|
|
875
|
+
crownSwept(COLUMN * one.scale, Math.abs(phase - was.phase))
|
|
876
|
+
)
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/** Draw again every head the swell has moved far enough to be worth it. */
|
|
880
|
+
function breathe(): void {
|
|
881
|
+
for (const [at, one] of heads.entries()) {
|
|
882
|
+
const tentacles = crowns[at]
|
|
883
|
+
const bend = lean(one)
|
|
884
|
+
const phase = tentacles ? clock * CROWN_RATE + one.lane : 0
|
|
885
|
+
|
|
886
|
+
if (wander(at, bend, phase) < tolerance) continue
|
|
887
|
+
|
|
888
|
+
one.bend = bend
|
|
889
|
+
if (tentacles) one.blades = crownAt(MOUTH, tentacles, COLUMN, phase)
|
|
890
|
+
one.cut++
|
|
891
|
+
held[at] = { bend, phase }
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
const crest: Point[] = []
|
|
896
|
+
|
|
897
|
+
raise()
|
|
898
|
+
breathe()
|
|
899
|
+
|
|
900
|
+
return {
|
|
901
|
+
crest,
|
|
902
|
+
|
|
903
|
+
get depth() {
|
|
904
|
+
return depth
|
|
905
|
+
},
|
|
906
|
+
|
|
907
|
+
heads,
|
|
908
|
+
|
|
909
|
+
get health() {
|
|
910
|
+
return health
|
|
911
|
+
},
|
|
912
|
+
|
|
913
|
+
holds(x, y, stand = depth) {
|
|
914
|
+
const reach = half * (1 + REACH)
|
|
915
|
+
if (x < middle - reach || x > middle + reach) return false
|
|
916
|
+
|
|
917
|
+
const rock = surfaceAt(x, stand)
|
|
918
|
+
return y >= rock - rise * HEADROOM && y <= rock
|
|
919
|
+
},
|
|
920
|
+
|
|
921
|
+
resize(nextWidth, nextHeight, nextFloor) {
|
|
922
|
+
width = Math.max(MIN_SPAN, nextWidth)
|
|
923
|
+
height = Math.max(MIN_SPAN, nextHeight)
|
|
924
|
+
floor = nextFloor
|
|
925
|
+
raise()
|
|
926
|
+
breathe()
|
|
927
|
+
},
|
|
928
|
+
|
|
929
|
+
get span() {
|
|
930
|
+
return bounds
|
|
931
|
+
},
|
|
932
|
+
|
|
933
|
+
step(seconds) {
|
|
934
|
+
clock += Math.min(Math.max(seconds, 0), 0.1)
|
|
935
|
+
breathe()
|
|
936
|
+
},
|
|
937
|
+
|
|
938
|
+
surfaceAt,
|
|
939
|
+
|
|
940
|
+
wind(seconds) {
|
|
941
|
+
clock += Math.max(0, seconds)
|
|
942
|
+
breathe()
|
|
943
|
+
},
|
|
944
|
+
}
|
|
945
|
+
}
|