@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/rays.ts
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Light coming down through the surface.
|
|
3
|
+
*
|
|
4
|
+
* The simulation behind the shafts in a seascape, kept free of the DOM the way
|
|
5
|
+
* `shoal.ts` and `drift.ts` are.
|
|
6
|
+
*
|
|
7
|
+
* A shaft is what makes water have a top. Without one the picture is a dark
|
|
8
|
+
* field with animals in it and no reason for the dark to be dark; with one
|
|
9
|
+
* there is a surface somewhere up there, the murk is depth rather than colour,
|
|
10
|
+
* and everything else in the scene acquires a distance from the light. It is
|
|
11
|
+
* the cheapest thing in the whole seascape and it changes the most.
|
|
12
|
+
*
|
|
13
|
+
* ## What a shaft is here
|
|
14
|
+
*
|
|
15
|
+
* A wedge, hung from the surface, leaning slightly. It is not drawn as a beam
|
|
16
|
+
* with edges: it is widest and faintest at the bottom, narrow and strongest at
|
|
17
|
+
* the top, and it fades out before it reaches anything, so it never lands on
|
|
18
|
+
* the seabed and never picks out an object. A shaft that lit something would be
|
|
19
|
+
* a spotlight, and the argument for it stops the moment it starts to look like
|
|
20
|
+
* one.
|
|
21
|
+
*
|
|
22
|
+
* Where it ends is `fade`, and the curve matters more than the length. Light
|
|
23
|
+
* thinning at a steady rate still stops at a rate, and an eye reads that as an
|
|
24
|
+
* edge across the water; what it wants is a shaft already almost gone by the
|
|
25
|
+
* time it runs out.
|
|
26
|
+
*
|
|
27
|
+
* ## Why they breathe
|
|
28
|
+
*
|
|
29
|
+
* Real shafts come from a moving surface, so they wander and they pulse, and
|
|
30
|
+
* the pulse is slow. Each one here holds its own phase and its own rate, which
|
|
31
|
+
* is the same trick `shoal.ts` uses on its fish and for the same reason: a set
|
|
32
|
+
* of shafts brightening together is a light being turned up, and a set that
|
|
33
|
+
* never agrees is a surface with waves on it.
|
|
34
|
+
*
|
|
35
|
+
* The rates are drawn low enough that nothing here is ever the fastest thing on
|
|
36
|
+
* the screen. Snow falls, fish swim, and the light takes its time.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { makeRandom } from "./perlin.ts"
|
|
40
|
+
|
|
41
|
+
export interface Ray {
|
|
42
|
+
/**
|
|
43
|
+
* How bright it is now, as a share of the caller's own ceiling.
|
|
44
|
+
*
|
|
45
|
+
* A share rather than a colour or an alpha, because how dark ornament is
|
|
46
|
+
* allowed to be is the renderer's business and differs between a website that
|
|
47
|
+
* owes a contrast ratio to the words on top of it and a desktop that does
|
|
48
|
+
* not.
|
|
49
|
+
*/
|
|
50
|
+
glow: number
|
|
51
|
+
/** How far down it reaches before it is gone, in px. */
|
|
52
|
+
readonly reach: number
|
|
53
|
+
/** Its width where it meets the surface, in px. */
|
|
54
|
+
readonly span: number
|
|
55
|
+
/** How far off vertical it leans, in radians. */
|
|
56
|
+
readonly tilt: number
|
|
57
|
+
/** Where it meets the surface, in px across the box. */
|
|
58
|
+
x: number
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface Rays {
|
|
62
|
+
readonly rays: readonly Ray[]
|
|
63
|
+
resize(width: number, height: number, count?: number): void
|
|
64
|
+
step(seconds: number): void
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface RaysOptions {
|
|
68
|
+
count: number
|
|
69
|
+
height: number
|
|
70
|
+
seed: number
|
|
71
|
+
width: number
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* How much wider a shaft is where it fades out than where it enters the water.
|
|
76
|
+
*
|
|
77
|
+
* Exported because it is a fact about the shape rather than a taste, and there
|
|
78
|
+
* is more than one renderer drawing these. A canvas and a scene graph disagreeing
|
|
79
|
+
* about how a shaft opens would be the same light in two different seas.
|
|
80
|
+
*
|
|
81
|
+
* It widens rather than narrows, which is worth saying because a beam drawn
|
|
82
|
+
* from a lamp does the opposite. The light here has already been through a
|
|
83
|
+
* rough surface, so what is spreading it is the water.
|
|
84
|
+
*/
|
|
85
|
+
export const SPREAD = 2.4
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* What is left of a shaft's brightness partway down it, where `down` is the
|
|
89
|
+
* share of its reach travelled: the surface at 0, the end at 1.
|
|
90
|
+
*
|
|
91
|
+
* Exported alongside `SPREAD`, and for the same reason: how a shaft ends is a
|
|
92
|
+
* fact about the light rather than a taste of whichever renderer is drawing it.
|
|
93
|
+
*
|
|
94
|
+
* The curve leaves the surface flat and arrives flat, so a shaft holds its
|
|
95
|
+
* strength where it has some and is already almost nothing by the time it is
|
|
96
|
+
* nothing. A straight ramp cannot do that, and in water it does worse than it
|
|
97
|
+
* sounds: the sea it is drawn on darkens with depth as well, so the same light
|
|
98
|
+
* stands out further the further it falls, and a shaft thinning at a steady
|
|
99
|
+
* rate is a shaft holding its own all the way down and then stopping at a line.
|
|
100
|
+
*/
|
|
101
|
+
export function fade(down: number): number {
|
|
102
|
+
const along = Math.min(Math.max(down, 0), 1)
|
|
103
|
+
|
|
104
|
+
return (1 - along ** 2) ** 2
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* How wide a shaft is at the surface, as a share of the box's width.
|
|
109
|
+
*
|
|
110
|
+
* Slim. The first pass drew them wide enough to overlap into slabs, and a slab
|
|
111
|
+
* of tinted water is not a shaft of light: what the eye wants is something
|
|
112
|
+
* narrower than it expects, because the width it remembers belongs to the glow
|
|
113
|
+
* around a shaft rather than to the shaft.
|
|
114
|
+
*/
|
|
115
|
+
const SPAN_LEAST = 0.018
|
|
116
|
+
|
|
117
|
+
const SPAN_SPAN = 0.055
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* How far down one reaches, as a share of the box's height.
|
|
121
|
+
*
|
|
122
|
+
* Shallow enough that the deepest of them is spent in open water. The ground
|
|
123
|
+
* furthest away sits a little over half way down, so a shaft drawn to the foot
|
|
124
|
+
* of the box is a shaft lying on a hill.
|
|
125
|
+
*/
|
|
126
|
+
const REACH_LEAST = 0.34
|
|
127
|
+
|
|
128
|
+
const REACH_SPAN = 0.28
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* How far off vertical a shaft may lean, in radians.
|
|
132
|
+
*
|
|
133
|
+
* Shallow, and all of them leaning the same way. Light refracting through a
|
|
134
|
+
* surface arrives from wherever the sun is, so shafts in the same water are
|
|
135
|
+
* near enough parallel; a set fanning out in all directions reads as a stage
|
|
136
|
+
* rather than a sea. The spread that is left is what stops them looking ruled.
|
|
137
|
+
*/
|
|
138
|
+
const TILT = 0.16
|
|
139
|
+
|
|
140
|
+
const TILT_SPREAD = 0.07
|
|
141
|
+
|
|
142
|
+
/** The ends of a shaft's own brightness, as shares of the caller's ceiling. */
|
|
143
|
+
const GLOW_LEAST = 0.25
|
|
144
|
+
|
|
145
|
+
const GLOW_MOST = 1
|
|
146
|
+
|
|
147
|
+
/** How fast one breathes, slowest to fastest, in cycles a second. */
|
|
148
|
+
const BREATH_SLOWEST = 0.045
|
|
149
|
+
|
|
150
|
+
const BREATH_FASTEST = 0.11
|
|
151
|
+
|
|
152
|
+
/** How far a shaft wanders along the surface, in px, and how fast. */
|
|
153
|
+
const WANDER = 26
|
|
154
|
+
|
|
155
|
+
const WANDER_RATE = 0.06
|
|
156
|
+
|
|
157
|
+
const MIN_SPAN = 1
|
|
158
|
+
|
|
159
|
+
interface Breath {
|
|
160
|
+
readonly at: number
|
|
161
|
+
readonly rate: number
|
|
162
|
+
readonly seat: number
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function createRays(options: RaysOptions): Rays {
|
|
166
|
+
const random = makeRandom(options.seed ^ 0x33b7)
|
|
167
|
+
|
|
168
|
+
let width = Math.max(MIN_SPAN, options.width)
|
|
169
|
+
let height = Math.max(MIN_SPAN, options.height)
|
|
170
|
+
let clock = 0
|
|
171
|
+
|
|
172
|
+
const breaths: Breath[] = []
|
|
173
|
+
const rays: Ray[] = []
|
|
174
|
+
|
|
175
|
+
function born(): Ray {
|
|
176
|
+
breaths.push({
|
|
177
|
+
at: random() * Math.PI * 2,
|
|
178
|
+
rate: BREATH_SLOWEST + random() * (BREATH_FASTEST - BREATH_SLOWEST),
|
|
179
|
+
seat: random() * Math.PI * 2,
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
glow: GLOW_LEAST,
|
|
184
|
+
reach: height * (REACH_LEAST + random() * REACH_SPAN),
|
|
185
|
+
span: width * (SPAN_LEAST + random() * SPAN_SPAN),
|
|
186
|
+
tilt: TILT + (random() - 0.5) * TILT_SPREAD,
|
|
187
|
+
x: random() * width,
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
for (let made = 0; made < Math.max(0, options.count); made++) rays.push(born())
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
rays,
|
|
195
|
+
|
|
196
|
+
resize(nextWidth, nextHeight, count) {
|
|
197
|
+
const scaleX = Math.max(MIN_SPAN, nextWidth) / width
|
|
198
|
+
width = Math.max(MIN_SPAN, nextWidth)
|
|
199
|
+
height = Math.max(MIN_SPAN, nextHeight)
|
|
200
|
+
|
|
201
|
+
for (const one of rays) one.x *= scaleX
|
|
202
|
+
|
|
203
|
+
if (count == null) return
|
|
204
|
+
while (rays.length > count) {
|
|
205
|
+
rays.pop()
|
|
206
|
+
breaths.pop()
|
|
207
|
+
}
|
|
208
|
+
while (rays.length < count) rays.push(born())
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
step(seconds) {
|
|
212
|
+
clock += Math.min(Math.max(seconds, 0), 0.1)
|
|
213
|
+
|
|
214
|
+
for (let at = 0; at < rays.length; at++) {
|
|
215
|
+
const one = rays[at]
|
|
216
|
+
const breath = breaths[at]
|
|
217
|
+
if (!one || !breath) continue
|
|
218
|
+
|
|
219
|
+
const swell = (Math.sin(breath.at + clock * breath.rate * Math.PI * 2) + 1) / 2
|
|
220
|
+
one.glow = GLOW_LEAST + swell * (GLOW_MOST - GLOW_LEAST)
|
|
221
|
+
one.x += Math.cos(breath.seat + clock * WANDER_RATE * Math.PI * 2) * WANDER * seconds
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
}
|