@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/cloud.ts
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weather over the water, for the light to come through.
|
|
3
|
+
*
|
|
4
|
+
* The simulation behind the clouds in a seascape, kept free of the DOM the way
|
|
5
|
+
* `rays.ts` and `drift.ts` are.
|
|
6
|
+
*
|
|
7
|
+
* ## What a cloud is for here
|
|
8
|
+
*
|
|
9
|
+
* Not scenery. A reader looking up through the column sees the sun or the moon
|
|
10
|
+
* and the light it throws down, and a sky that is the same brightness every
|
|
11
|
+
* hour of every night is a lamp rather than weather. A cloud is the thing that
|
|
12
|
+
* takes some of that away for a while and gives it back: the moon dims as one
|
|
13
|
+
* crosses it, the shaft under it thins, and both come back once it has passed.
|
|
14
|
+
*
|
|
15
|
+
* So it is drawn in the water's own colour rather than a colour of its own.
|
|
16
|
+
* What a renderer paints is the absence of light, which is the only thing a
|
|
17
|
+
* cloud is from under here; a white shape over a night sky would be a cloud
|
|
18
|
+
* seen from a field.
|
|
19
|
+
*
|
|
20
|
+
* ## How they move
|
|
21
|
+
*
|
|
22
|
+
* Every one of them differently, and none of them quickly. A cloud carries a
|
|
23
|
+
* heading of its own and a swell of its own, so what crosses the picture is
|
|
24
|
+
* anything from a mass tracking flat across it to one that barely moves
|
|
25
|
+
* sideways and rises and falls in place, with most of them somewhere between
|
|
26
|
+
* and so going diagonally. Wind is one direction and clouds in it are not, and
|
|
27
|
+
* a sky where everything slides the same way at the same rate is a sky on a
|
|
28
|
+
* conveyor.
|
|
29
|
+
*
|
|
30
|
+
* The sideways drift wraps, since a cloud that has left the picture has nothing
|
|
31
|
+
* to say and the water it left is as good a place as any to come back at. The
|
|
32
|
+
* rise and fall does not: it is a swell about a seat, so nothing ever jumps
|
|
33
|
+
* from one edge to the other where somebody can see it happen.
|
|
34
|
+
*
|
|
35
|
+
* ## What it is not
|
|
36
|
+
*
|
|
37
|
+
* There is no wind here, no front, and nothing evaporates. A cloud is a mass of
|
|
38
|
+
* lobes with a thickness, and the only question it answers is how much of the
|
|
39
|
+
* light gets past it at a point.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import { makeRandom } from "./perlin.ts"
|
|
43
|
+
|
|
44
|
+
/** One of the swells a cloud is a mass of, about the cloud's own middle. */
|
|
45
|
+
export interface Lobe {
|
|
46
|
+
/** Across, in px. */
|
|
47
|
+
readonly dx: number
|
|
48
|
+
/** And down, in px. */
|
|
49
|
+
readonly dy: number
|
|
50
|
+
readonly r: number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface Cloud {
|
|
54
|
+
readonly lobes: readonly Lobe[]
|
|
55
|
+
/** How far it reaches from its middle, in px, which is what a wrap needs. */
|
|
56
|
+
readonly span: number
|
|
57
|
+
/**
|
|
58
|
+
* How much of the light it holds back where it is thickest.
|
|
59
|
+
*
|
|
60
|
+
* A share rather than an alpha, because how dark a renderer is allowed to
|
|
61
|
+
* draw is its own business: a website owes a contrast ratio to the words in
|
|
62
|
+
* front of it and a desktop owes nothing to anybody.
|
|
63
|
+
*/
|
|
64
|
+
readonly thick: number
|
|
65
|
+
x: number
|
|
66
|
+
y: number
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface Clouds {
|
|
70
|
+
readonly clouds: readonly Cloud[]
|
|
71
|
+
/**
|
|
72
|
+
* How much cloud is over a point: nothing in the clear, and at its highest
|
|
73
|
+
* under the middle of the thickest mass there is.
|
|
74
|
+
*
|
|
75
|
+
* This is what mutes a body rather than the drawing. A shape painted over the
|
|
76
|
+
* moon dims the moon where the shape is, and the shaft under it goes on
|
|
77
|
+
* arriving at full strength out of a moon that is no longer there.
|
|
78
|
+
*/
|
|
79
|
+
cover(x: number, y: number): number
|
|
80
|
+
resize(width: number, height: number, count?: number): void
|
|
81
|
+
step(seconds: number): void
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface CloudsOptions {
|
|
85
|
+
count: number
|
|
86
|
+
height: number
|
|
87
|
+
seed: number
|
|
88
|
+
/**
|
|
89
|
+
* How much of the box is sky, as a share of its height, for a picture whose
|
|
90
|
+
* sky is not the usual band.
|
|
91
|
+
*
|
|
92
|
+
* A round window holds a fraction of what a wall of water does and the light
|
|
93
|
+
* in it sits lower down the box, so the band it wants is deeper. Left out, a
|
|
94
|
+
* cloud stays in the top of the picture, which is where a wide box keeps its
|
|
95
|
+
* light.
|
|
96
|
+
*/
|
|
97
|
+
sky?: number
|
|
98
|
+
width: number
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* How far down the box a cloud may sit, as a share of the height.
|
|
103
|
+
*
|
|
104
|
+
* The sky is the top of this picture and the light comes from up there, so
|
|
105
|
+
* anything lower is a shadow lying in open water with nothing casting it. The
|
|
106
|
+
* bodies cross well inside this band, which is the point: a cloud has to be
|
|
107
|
+
* able to get in front of one.
|
|
108
|
+
*/
|
|
109
|
+
const SKY = 0.3
|
|
110
|
+
|
|
111
|
+
/** How wide one is, least and most, as shares of the box's width. */
|
|
112
|
+
const SPAN_LEAST = 0.14
|
|
113
|
+
|
|
114
|
+
const SPAN_SPAN = 0.3
|
|
115
|
+
|
|
116
|
+
/** How far the lobes wander off the line they sit on, against how wide the
|
|
117
|
+
* mass is, which is what keeps it a cloud rather than a heap. */
|
|
118
|
+
const SQUAT = 0.14
|
|
119
|
+
|
|
120
|
+
/** How much of the light the thinnest and the thickest hold back. */
|
|
121
|
+
const THICK_LEAST = 0.3
|
|
122
|
+
|
|
123
|
+
const THICK_MOST = 0.78
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* How fast one travels, least and most, in widths a second.
|
|
127
|
+
*
|
|
128
|
+
* Slow enough that nothing here is ever the fastest thing on the screen, and
|
|
129
|
+
* slow enough that a cloud crossing the moon is an evening rather than a
|
|
130
|
+
* moment. Snow falls, fish swim, and the weather takes longer than either.
|
|
131
|
+
*/
|
|
132
|
+
const PACE_LEAST = 0.004
|
|
133
|
+
|
|
134
|
+
const PACE_SPAN = 0.011
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* How far off flat a cloud's heading may lean, in radians.
|
|
138
|
+
*
|
|
139
|
+
* The lean is the whole of which way one goes: it splits the pace between
|
|
140
|
+
* along and up, so a mass at nothing is tracking flat across the picture and
|
|
141
|
+
* one at the far end of this is climbing as fast as it travels. A quarter turn
|
|
142
|
+
* is as steep as it gets, since what rises faster than it drifts is weather
|
|
143
|
+
* nobody has seen.
|
|
144
|
+
*/
|
|
145
|
+
const LEAN = Math.PI / 4
|
|
146
|
+
|
|
147
|
+
/** How much of the sky one may take up rising and falling, and how long a rise
|
|
148
|
+
* and a fall takes, in cycles a second. */
|
|
149
|
+
const SWELL_MOST = 0.3
|
|
150
|
+
|
|
151
|
+
const SWELL_SLOWEST = 0.006
|
|
152
|
+
|
|
153
|
+
const SWELL_FASTEST = 0.022
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The lobes: how many a mass is made of, and how wide the smallest and the
|
|
157
|
+
* largest are against the mass they are in.
|
|
158
|
+
*
|
|
159
|
+
* Enough of them, and wide enough, that consecutive ones overlap. A row of
|
|
160
|
+
* lobes that do not touch is not a cloud with gaps in it, it is a row of
|
|
161
|
+
* lobes.
|
|
162
|
+
*/
|
|
163
|
+
const LOBES_LEAST = 5
|
|
164
|
+
|
|
165
|
+
const LOBES_SPAN = 4
|
|
166
|
+
|
|
167
|
+
const LOBE_LEAST = 0.24
|
|
168
|
+
|
|
169
|
+
const LOBE_SPAN = 0.2
|
|
170
|
+
|
|
171
|
+
const MIN_SPAN = 1
|
|
172
|
+
|
|
173
|
+
/** Where a cloud is going, and how far up and down it goes on the way. */
|
|
174
|
+
interface Drift {
|
|
175
|
+
readonly at: number
|
|
176
|
+
readonly lean: number
|
|
177
|
+
readonly pace: number
|
|
178
|
+
readonly rate: number
|
|
179
|
+
readonly seat: number
|
|
180
|
+
readonly swell: number
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function createClouds(options: CloudsOptions): Clouds {
|
|
184
|
+
const random = makeRandom(options.seed ^ 0x5ce7)
|
|
185
|
+
const overhead = options.sky ?? SKY
|
|
186
|
+
|
|
187
|
+
let width = Math.max(MIN_SPAN, options.width)
|
|
188
|
+
let height = Math.max(MIN_SPAN, options.height)
|
|
189
|
+
let clock = 0
|
|
190
|
+
|
|
191
|
+
const drifts: Drift[] = []
|
|
192
|
+
const clouds: Cloud[] = []
|
|
193
|
+
|
|
194
|
+
function shape(span: number): Lobe[] {
|
|
195
|
+
const lobes: Lobe[] = []
|
|
196
|
+
const many = LOBES_LEAST + Math.floor(random() * (LOBES_SPAN + 1))
|
|
197
|
+
|
|
198
|
+
for (let made = 0; made < many; made++) {
|
|
199
|
+
const along = many < 2 ? 0.5 : made / (many - 1)
|
|
200
|
+
const r = span * (LOBE_LEAST + random() * LOBE_SPAN) * 0.5
|
|
201
|
+
|
|
202
|
+
lobes.push({
|
|
203
|
+
dx: (along - 0.5) * span,
|
|
204
|
+
dy: (random() - 0.5) * span * SQUAT,
|
|
205
|
+
r,
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return lobes
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* One of them, made where it will be able to swell without leaving the sky.
|
|
214
|
+
*
|
|
215
|
+
* How far it rises and falls comes out of its heading rather than being drawn
|
|
216
|
+
* on its own: at the top of the swell it is going up as fast as its lean says
|
|
217
|
+
* it should, which is what makes a steep cloud a climbing one and a flat
|
|
218
|
+
* cloud a travelling one. Its seat is then drawn from what is left of the
|
|
219
|
+
* band, so nothing here is ever held against the top of the picture.
|
|
220
|
+
*/
|
|
221
|
+
function born(): Cloud {
|
|
222
|
+
const band = height * overhead
|
|
223
|
+
const span = width * (SPAN_LEAST + random() * SPAN_SPAN)
|
|
224
|
+
const lean = (random() - 0.5) * 2 * LEAN
|
|
225
|
+
const pace = width * (PACE_LEAST + random() * PACE_SPAN)
|
|
226
|
+
const rate = SWELL_SLOWEST + random() * (SWELL_FASTEST - SWELL_SLOWEST)
|
|
227
|
+
const swell = Math.min(
|
|
228
|
+
band * SWELL_MOST,
|
|
229
|
+
(Math.abs(Math.sin(lean)) * pace) / (2 * Math.PI * rate),
|
|
230
|
+
)
|
|
231
|
+
const seat = swell + random() * Math.max(0, band - 2 * swell)
|
|
232
|
+
|
|
233
|
+
drifts.push({ at: random() * Math.PI * 2, lean, pace, rate, seat, swell })
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
lobes: shape(span),
|
|
237
|
+
span,
|
|
238
|
+
thick: THICK_LEAST + random() * (THICK_MOST - THICK_LEAST),
|
|
239
|
+
x: random() * width,
|
|
240
|
+
y: seat,
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
for (let made = 0; made < Math.max(0, options.count); made++) clouds.push(born())
|
|
245
|
+
|
|
246
|
+
function settle(one: Cloud, drift: Drift): void {
|
|
247
|
+
one.y = drift.seat + Math.sin(drift.at + clock * drift.rate * Math.PI * 2) * drift.swell
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
clouds,
|
|
252
|
+
|
|
253
|
+
cover(x, y) {
|
|
254
|
+
let held = 0
|
|
255
|
+
|
|
256
|
+
for (const one of clouds) {
|
|
257
|
+
if (Math.abs(x - one.x) > one.span) continue
|
|
258
|
+
|
|
259
|
+
let under = 0
|
|
260
|
+
for (const lobe of one.lobes) {
|
|
261
|
+
const from = Math.hypot(x - (one.x + lobe.dx), y - (one.y + lobe.dy)) / lobe.r
|
|
262
|
+
if (from >= 1) continue
|
|
263
|
+
under = Math.max(under, (1 - from * from) ** 2)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
held = Math.max(held, under * one.thick)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return held
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* A box of another shape is another sky.
|
|
274
|
+
*
|
|
275
|
+
* Everything about a cloud is a share of the box it is in: how wide it is,
|
|
276
|
+
* how far it travels in a second, how far down it may sit. Carrying the
|
|
277
|
+
* old ones over would be masses of the old box's width drifting at the old
|
|
278
|
+
* box's pace, so they are made again. Nothing here is watched closely
|
|
279
|
+
* enough for that to show.
|
|
280
|
+
*/
|
|
281
|
+
resize(nextWidth, nextHeight, count) {
|
|
282
|
+
width = Math.max(MIN_SPAN, nextWidth)
|
|
283
|
+
height = Math.max(MIN_SPAN, nextHeight)
|
|
284
|
+
|
|
285
|
+
const many = count ?? clouds.length
|
|
286
|
+
clouds.length = 0
|
|
287
|
+
drifts.length = 0
|
|
288
|
+
for (let made = 0; made < Math.max(0, many); made++) clouds.push(born())
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
step(seconds) {
|
|
292
|
+
clock += Math.min(Math.max(seconds, 0), 0.1)
|
|
293
|
+
|
|
294
|
+
for (let at = 0; at < clouds.length; at++) {
|
|
295
|
+
const one = clouds[at]
|
|
296
|
+
const drift = drifts[at]
|
|
297
|
+
if (!one || !drift) continue
|
|
298
|
+
|
|
299
|
+
one.x += Math.cos(drift.lean) * drift.pace * seconds
|
|
300
|
+
const over = width + one.span * 2
|
|
301
|
+
if (one.x > width + one.span) one.x -= over
|
|
302
|
+
if (one.x < -one.span) one.x += over
|
|
303
|
+
|
|
304
|
+
settle(one, drift)
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
}
|
|
308
|
+
}
|