@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/perlin.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ken Perlin's gradient noise, in two dimensions, as first published.
|
|
3
|
+
*
|
|
4
|
+
* Perlin wrote it in 1983 because the computer graphics he was working on
|
|
5
|
+
* looked machine-made, and the film he was working on was Tron. That is the
|
|
6
|
+
* whole reason this file exists here: the one surface that draws it is the page
|
|
7
|
+
* of the world that plays that film's game, and the joke only lands if the
|
|
8
|
+
* noise is really his.
|
|
9
|
+
*
|
|
10
|
+
* The shape of the algorithm: every integer lattice point owns a fixed random
|
|
11
|
+
* unit gradient. To sample a point, take the four lattice corners around it,
|
|
12
|
+
* dot each corner's gradient with the vector running from that corner to the
|
|
13
|
+
* point, and blend the four results together. The blend runs through Perlin's
|
|
14
|
+
* S-curve rather than straight lerps, and that is the part that matters; with
|
|
15
|
+
* linear blending the lattice shows up as visible creases on every cell edge.
|
|
16
|
+
*
|
|
17
|
+
* Kept deliberately small: one seedable 2D field, no octaves, no simplex.
|
|
18
|
+
* Nothing else in the codebase wants coherent noise, and a general noise
|
|
19
|
+
* library would be a lot of unused surface. If a second caller ever needs
|
|
20
|
+
* fractal detail, sum a few of these at doubling frequencies rather than
|
|
21
|
+
* growing this file.
|
|
22
|
+
*
|
|
23
|
+
* Because the gradients come from a fixed 256-entry table, the field repeats
|
|
24
|
+
* every 256 units. That is the algorithm's well-known limitation, and it costs
|
|
25
|
+
* nothing here: the flow field samples a few dozen units across at most.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const SIZE = 256
|
|
29
|
+
const MASK = SIZE - 1
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Two-dimensional gradient noise, returning roughly [-1, 1].
|
|
33
|
+
*
|
|
34
|
+
* "Roughly", because the theoretical bound is what a sample can reach when it
|
|
35
|
+
* lands exactly where two opposed gradients line up, which almost never
|
|
36
|
+
* happens. Callers that need a hard range must clamp; the flow field does not,
|
|
37
|
+
* since it only reads the value as an angle.
|
|
38
|
+
*/
|
|
39
|
+
export type Noise2 = (x: number, y: number) => number
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Gradient noise in 2D peaks at sqrt(2)/2 rather than 1, so the raw dot
|
|
43
|
+
* products come back short. Scaling by its reciprocal puts the field back on
|
|
44
|
+
* the [-1, 1] every caller expects to be handed.
|
|
45
|
+
*/
|
|
46
|
+
const NORMALISE = Math.SQRT2
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* xorshift32, so the field is identical on every load and in every browser.
|
|
50
|
+
*
|
|
51
|
+
* `Math.random()` would do the job and would also mean the page looked subtly
|
|
52
|
+
* different every time it was opened, which makes an ornament feel like a
|
|
53
|
+
* glitch rather than a decision. A fixed seed makes the field a drawing
|
|
54
|
+
* somebody chose.
|
|
55
|
+
*
|
|
56
|
+
* Exported because the flow field wants the same guarantee for its riders'
|
|
57
|
+
* starting positions. Noise is the wrong tool for that: its values cluster
|
|
58
|
+
* around zero rather than spreading evenly, so twelve riders placed by noise
|
|
59
|
+
* all start in a knot in the middle of the screen.
|
|
60
|
+
*/
|
|
61
|
+
export function makeRandom(seed: number): () => number {
|
|
62
|
+
let state = seed | 0 || 1
|
|
63
|
+
return () => {
|
|
64
|
+
state ^= state << 13
|
|
65
|
+
state ^= state >>> 17
|
|
66
|
+
state ^= state << 5
|
|
67
|
+
return (state >>> 0) / 0x1_0000_0000
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* One number scattered into another.
|
|
73
|
+
*
|
|
74
|
+
* Two seeds that differ in one low bit are two xorshift states that differ in
|
|
75
|
+
* one low bit, and those walk two neighbouring paths for their first few
|
|
76
|
+
* numbers: a day and the day after it laying out the same sea nudged sideways,
|
|
77
|
+
* or a plan that says the same thing on the first of the month as on the
|
|
78
|
+
* second. This is the avalanche step that stops it, one multiply and two folds,
|
|
79
|
+
* so a bit anywhere reaches every bit.
|
|
80
|
+
*
|
|
81
|
+
* Wanted wherever a seed is derived from something that counts rather than
|
|
82
|
+
* something arbitrary: a date, a day of the month, the id of an element. A seed
|
|
83
|
+
* somebody picked out of the air needs none of it.
|
|
84
|
+
*/
|
|
85
|
+
export function stir(from: number): number {
|
|
86
|
+
let seed = from >>> 0
|
|
87
|
+
seed = Math.imul(seed ^ (seed >>> 16), 0x45d9f3b) >>> 0
|
|
88
|
+
return (seed ^ (seed >>> 16)) >>> 0
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function makeNoise2(seed: number): Noise2 {
|
|
92
|
+
const random = makeRandom(seed)
|
|
93
|
+
|
|
94
|
+
const permutation = new Int32Array(SIZE + SIZE + 2)
|
|
95
|
+
const gradientX = new Float64Array(SIZE + SIZE + 2)
|
|
96
|
+
const gradientY = new Float64Array(SIZE + SIZE + 2)
|
|
97
|
+
|
|
98
|
+
for (let i = 0; i < SIZE; i++) {
|
|
99
|
+
permutation[i] = i
|
|
100
|
+
const angle = random() * Math.PI * 2
|
|
101
|
+
gradientX[i] = Math.cos(angle)
|
|
102
|
+
gradientY[i] = Math.sin(angle)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (let i = SIZE - 1; i > 0; i--) {
|
|
106
|
+
const j = Math.floor(random() * (i + 1))
|
|
107
|
+
const held = permutation[i]!
|
|
108
|
+
permutation[i] = permutation[j]!
|
|
109
|
+
permutation[j] = held
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (let i = 0; i < SIZE + 2; i++) {
|
|
113
|
+
permutation[SIZE + i] = permutation[i]!
|
|
114
|
+
gradientX[SIZE + i] = gradientX[i]!
|
|
115
|
+
gradientY[SIZE + i] = gradientY[i]!
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const dot = (gradient: number, x: number, y: number): number =>
|
|
119
|
+
gradientX[gradient]! * x + gradientY[gradient]! * y
|
|
120
|
+
|
|
121
|
+
return (x, y) => {
|
|
122
|
+
const cellX = Math.floor(x)
|
|
123
|
+
const cellY = Math.floor(y)
|
|
124
|
+
|
|
125
|
+
const dx0 = x - cellX
|
|
126
|
+
const dy0 = y - cellY
|
|
127
|
+
const dx1 = dx0 - 1
|
|
128
|
+
const dy1 = dy0 - 1
|
|
129
|
+
|
|
130
|
+
const bx0 = cellX & MASK
|
|
131
|
+
const by0 = cellY & MASK
|
|
132
|
+
const bx1 = (bx0 + 1) & MASK
|
|
133
|
+
const by1 = (by0 + 1) & MASK
|
|
134
|
+
|
|
135
|
+
const i = permutation[bx0]!
|
|
136
|
+
const j = permutation[bx1]!
|
|
137
|
+
|
|
138
|
+
const g00 = permutation[i + by0]!
|
|
139
|
+
const g10 = permutation[j + by0]!
|
|
140
|
+
const g01 = permutation[i + by1]!
|
|
141
|
+
const g11 = permutation[j + by1]!
|
|
142
|
+
|
|
143
|
+
const sx = sCurve(dx0)
|
|
144
|
+
const sy = sCurve(dy0)
|
|
145
|
+
|
|
146
|
+
const top = lerp(sx, dot(g00, dx0, dy0), dot(g10, dx1, dy0))
|
|
147
|
+
const bottom = lerp(sx, dot(g01, dx0, dy1), dot(g11, dx1, dy1))
|
|
148
|
+
|
|
149
|
+
return lerp(sy, top, bottom) * NORMALISE
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Perlin's original ease, 3t² - 2t³: an S from (0,0) to (1,1) whose first
|
|
155
|
+
* derivative is zero at both ends, which is what hides the lattice.
|
|
156
|
+
*
|
|
157
|
+
* His 1997 revision uses 6t⁵ - 15t⁴ + 10t³ instead, whose *second* derivative
|
|
158
|
+
* also vanishes at the ends and so survives being differentiated for normal
|
|
159
|
+
* maps. The field here is only ever read as an angle, never differentiated, so
|
|
160
|
+
* the cheaper original is the honest choice as well as the historical one.
|
|
161
|
+
*/
|
|
162
|
+
function sCurve(t: number): number {
|
|
163
|
+
return t * t * (3 - 2 * t)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function lerp(t: number, a: number, b: number): number {
|
|
167
|
+
return a + t * (b - a)
|
|
168
|
+
}
|
package/src/plenty.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How much of everything a day grew.
|
|
3
|
+
*
|
|
4
|
+
* Every count in this scene is worked out from the size of the box: so many
|
|
5
|
+
* plants per thousand pixels, so many fish per so much area. That is right, and
|
|
6
|
+
* it has one consequence nobody wanted. The box does not change, so neither
|
|
7
|
+
* does the sea. A desktop reseeded at midnight puts a different bed in a
|
|
8
|
+
* different place and grows exactly as much of it as yesterday did, and the
|
|
9
|
+
* thing a reader actually notices about a week of water is not where the kelp
|
|
10
|
+
* is, it is whether there is much of it.
|
|
11
|
+
*
|
|
12
|
+
* So the day picks a number and everything counted is counted through it. Two
|
|
13
|
+
* days apart are a thin sand flat and a bed you cannot see the floor through,
|
|
14
|
+
* and neither one is a bug.
|
|
15
|
+
*
|
|
16
|
+
* ## Why it is not evenly spread
|
|
17
|
+
*
|
|
18
|
+
* A range applied flat makes the ends as common as the middle, and then the
|
|
19
|
+
* spectacular day comes round often enough to stop being spectacular. Weighted
|
|
20
|
+
* the way it is here, most water is ordinary or a little under, a lush week is
|
|
21
|
+
* something you notice, and the bed you cannot see the floor through is
|
|
22
|
+
* something you wait for. Rare is the whole point of the top of the range.
|
|
23
|
+
*
|
|
24
|
+
* ## What it does not decide
|
|
25
|
+
*
|
|
26
|
+
* How much a renderer can afford. Each one caps what it asks for, and the caps
|
|
27
|
+
* are different: the desktop's bed is tessellated on the processor every frame
|
|
28
|
+
* and the native one uploads it once and bends it on the card, so the second
|
|
29
|
+
* can hold ten thousand plants where the first cannot hold one. This number is
|
|
30
|
+
* the day's appetite, not anybody's budget.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { makeRandom } from "./perlin.ts"
|
|
34
|
+
|
|
35
|
+
/** The leanest day, as a share of an ordinary one. */
|
|
36
|
+
const LEAN = 0.55
|
|
37
|
+
|
|
38
|
+
/** How far above that the richest day reaches. */
|
|
39
|
+
const RANGE = 1.75
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* How the range is leaned on.
|
|
43
|
+
*
|
|
44
|
+
* Above one, and the low end is where most days land. It was picked against a
|
|
45
|
+
* decade of seeds, for a middle day that grows about what this scene grew
|
|
46
|
+
* before there was a number at all, and a top of the range you wait weeks for.
|
|
47
|
+
*/
|
|
48
|
+
const BIAS = 2.0
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A multiplier for anything counted, given the day's seed.
|
|
52
|
+
*
|
|
53
|
+
* `at` separates one kind of count from another where a caller wants them
|
|
54
|
+
* loose: the same seed and the same `at` always give the same number, so it can
|
|
55
|
+
* be asked for again at a refit without the sea changing under the reader.
|
|
56
|
+
*/
|
|
57
|
+
export function thriving(seed: number, at = 0): number {
|
|
58
|
+
const random = makeRandom((seed ^ 0x5eed) + at * 0x9e37)
|
|
59
|
+
// Two draws thrown away first: `makeRandom` is a small integer generator and
|
|
60
|
+
// its opening number tracks its seed closely, which for seeds a day apart is
|
|
61
|
+
// a week of water sliding steadily from lean to lush.
|
|
62
|
+
random()
|
|
63
|
+
random()
|
|
64
|
+
return LEAN + RANGE * random() ** BIAS
|
|
65
|
+
}
|