@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
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What stands between the light and the sand, put on the sand.
|
|
3
|
+
*
|
|
4
|
+
* The one thing this water had no answer for. Every other cue in the scene says
|
|
5
|
+
* how far back a thing is: it is smaller, it is hazier, it is behind something.
|
|
6
|
+
* None of them says how far *up* it is, so a fish a hand off the sand and a fish
|
|
7
|
+
* halfway to the surface are the same drawing at two heights of the picture, and
|
|
8
|
+
* the eye has nothing to read the gap with.
|
|
9
|
+
*
|
|
10
|
+
* A shadow is the whole of the answer, and it is cheap: the light in this sea
|
|
11
|
+
* comes from the surface, so what a thing puts on the ground is directly under
|
|
12
|
+
* it. What the reader is actually reading is the two of them together. Close to
|
|
13
|
+
* the sand the patch is small, dark and right under the animal; further up it is
|
|
14
|
+
* wide, faint and further from it, and somewhere above that it is gone.
|
|
15
|
+
*
|
|
16
|
+
* Nothing here is a shape. A round light squashed flat is what a soft-edged
|
|
17
|
+
* patch on the ground is, and a shadow with an outline is a cutout laid on the
|
|
18
|
+
* sand rather than an absence of light on it.
|
|
19
|
+
*/
|
|
20
|
+
import { OWN, type Pen, TONE } from "./pen.ts"
|
|
21
|
+
|
|
22
|
+
/** Anything that can come between the light and the ground. */
|
|
23
|
+
export interface Caster {
|
|
24
|
+
readonly depth: number
|
|
25
|
+
readonly size: number
|
|
26
|
+
readonly x: number
|
|
27
|
+
readonly y: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* How dark the patch is where a thing is sitting on the sand, and the least
|
|
32
|
+
* that is left of it at the far end of the water.
|
|
33
|
+
*
|
|
34
|
+
* The haze takes it the same way it takes everything else. A shadow at the back
|
|
35
|
+
* of the picture that stayed as dark as one at the front would be doing the
|
|
36
|
+
* opposite of its job: it would say near.
|
|
37
|
+
*/
|
|
38
|
+
const SHADE_INK = 0.85
|
|
39
|
+
|
|
40
|
+
const HAZE = 0.06
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* How far above the sand a thing still puts anything on it, as a share of the
|
|
44
|
+
* box, and how much wider the patch has got by then.
|
|
45
|
+
*
|
|
46
|
+
* Very short, and it was three times this to begin with. The ground here is
|
|
47
|
+
* seen almost edge on, so a hundred fish shadows spread over it do not read as
|
|
48
|
+
* a hundred shadows: they overlap into one dark band along the sand and the
|
|
49
|
+
* picture has gained a smear rather than a dimension. What does read is contact,
|
|
50
|
+
* a thing close enough to the sand that the patch is still tight and still under
|
|
51
|
+
* it, so this is set to the height at which that is true and nothing further up
|
|
52
|
+
* puts anything down at all.
|
|
53
|
+
*/
|
|
54
|
+
const DROP = 0.09
|
|
55
|
+
|
|
56
|
+
const SPREAD = 1.3
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* How flat the patch is, and how it gives out from the middle.
|
|
60
|
+
*
|
|
61
|
+
* Flat because the ground is being seen almost edge on: a circle of shade on
|
|
62
|
+
* sand that is nearly side on to the reader is an ellipse, and one this shallow
|
|
63
|
+
* before it is anything else. The falloff is soft, since the light making it
|
|
64
|
+
* comes from a whole surface rather than from a point.
|
|
65
|
+
*/
|
|
66
|
+
const FLAT = 0.17
|
|
67
|
+
|
|
68
|
+
const FALL = 1.2
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* What each of them puts on the ground, in one pass.
|
|
72
|
+
*
|
|
73
|
+
* Handed the whole crowd rather than called per animal, because these belong
|
|
74
|
+
* under everything that swims and there is exactly one moment in the scene
|
|
75
|
+
* where that is true.
|
|
76
|
+
*/
|
|
77
|
+
export function paintShade(
|
|
78
|
+
pen: Pen,
|
|
79
|
+
things: readonly Caster[],
|
|
80
|
+
floor: (x: number, depth?: number) => number,
|
|
81
|
+
height: number,
|
|
82
|
+
): void {
|
|
83
|
+
const reach = Math.max(1, height * DROP)
|
|
84
|
+
|
|
85
|
+
for (const one of things) {
|
|
86
|
+
const sand = floor(one.x, one.depth)
|
|
87
|
+
const up = (sand - one.y) / reach
|
|
88
|
+
if (up < 0 || up >= 1) continue
|
|
89
|
+
|
|
90
|
+
// Steeper than straight, so a thing loses its shadow over the first part of
|
|
91
|
+
// the climb rather than at the end of it. Light off a surface spreads as it
|
|
92
|
+
// goes, and what spreads is gone long before it has run out of distance.
|
|
93
|
+
const held = (1 - up) ** 1.4
|
|
94
|
+
|
|
95
|
+
pen.light(one.x, sand, {
|
|
96
|
+
alpha: (HAZE + (SHADE_INK - HAZE) * Math.min(1, Math.max(0, one.depth))) * held,
|
|
97
|
+
fall: FALL,
|
|
98
|
+
lane: one.depth,
|
|
99
|
+
// Read where the patch is rather than where the animal is: a shadow is
|
|
100
|
+
// the ground with the light taken out of it, and the ground is what it
|
|
101
|
+
// has to be dark against.
|
|
102
|
+
shade: OWN,
|
|
103
|
+
thin: FLAT,
|
|
104
|
+
tone: TONE.shadow,
|
|
105
|
+
weight: 1,
|
|
106
|
+
width: one.size * (1 + SPREAD * up),
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
}
|
package/src/paint/sky.ts
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What is above the water: the two bodies, the light off them, and the wash the
|
|
3
|
+
* hour puts over everything.
|
|
4
|
+
*
|
|
5
|
+
* The hour comes from `sun.ts`, so the sun sets on a desktop at the minute it
|
|
6
|
+
* sets in the porthole on the website, and the moon is the shape tonight's moon
|
|
7
|
+
* actually is.
|
|
8
|
+
*/
|
|
9
|
+
import type { Clouds } from "../cloud.ts"
|
|
10
|
+
import { type Passage, type Phase, sunlit, sunNow } from "../sun.ts"
|
|
11
|
+
import { OWN, type Pen, type Point, TONE } from "./pen.ts"
|
|
12
|
+
|
|
13
|
+
/** The ends of a crossing, the top and bottom of the arc between them, and how
|
|
14
|
+
* big a body is drawn. */
|
|
15
|
+
const EAST = 0.12
|
|
16
|
+
const WEST = 0.88
|
|
17
|
+
const HIGH = 0.055
|
|
18
|
+
const LOW = 0.17
|
|
19
|
+
const DISC = 0.028
|
|
20
|
+
|
|
21
|
+
/** How far the shaft's foot swings either side of the body, and how far it has
|
|
22
|
+
* opened by the bottom of the box. */
|
|
23
|
+
const SWING = 0.34
|
|
24
|
+
const SPREAD = 0.085
|
|
25
|
+
|
|
26
|
+
/** How far down the box the shaft goes, how heavy it is at its mouth, and how
|
|
27
|
+
* fast it gives up. */
|
|
28
|
+
const FALL = 0.34
|
|
29
|
+
const DISC_INK = 0.012
|
|
30
|
+
const DISC_GIVE = 2.6
|
|
31
|
+
|
|
32
|
+
/** The three lights off a body: the veil out past everything, the halo that is
|
|
33
|
+
* most of the light there is, and the streak a lens gives a bright thing. */
|
|
34
|
+
const BLOOM = { fall: 2.4, ink: 0.016, reach: 13, thin: 1 }
|
|
35
|
+
const HALO = { fall: 1.6, ink: 0.07, reach: 4.2, thin: 1 }
|
|
36
|
+
const STREAK = { fall: 2.1, ink: 0.06, reach: 12, thin: 0.1 }
|
|
37
|
+
|
|
38
|
+
/** How much of the box a dusk reaches down, and how heavy the two washes are. */
|
|
39
|
+
const DUSK_REACH = 0.62
|
|
40
|
+
const DUSK_INK = 0.09
|
|
41
|
+
const NIGHT_INK = 0.3
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Where the bodies sit against everything else, and where the washes do.
|
|
45
|
+
*
|
|
46
|
+
* Behind the whole sea, cliffs included. A reader of this picture is under the
|
|
47
|
+
* water looking up, so everything in it is between them and the sky: a moon
|
|
48
|
+
* drawn in front of the far wall is a moon in the room. What that costs is a
|
|
49
|
+
* low moon going behind the rock at the edge of the picture, which is what a
|
|
50
|
+
* low moon does.
|
|
51
|
+
*
|
|
52
|
+
* The clouds are the one thing between the bodies and the water, since a cloud
|
|
53
|
+
* is the only thing here that is above the sea and below the sky.
|
|
54
|
+
*/
|
|
55
|
+
const LANE = { body: -4.2, cloud: -4, dusk: 3, night: 3.1 }
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The weather, as the light it holds back.
|
|
59
|
+
*
|
|
60
|
+
* `ink` is how much of the water's own colour one lobe lays over what is behind
|
|
61
|
+
* it, `give` is how quickly that runs out towards the lobe's edge, and `squat`
|
|
62
|
+
* is how much shorter than it is wide a lobe is drawn.
|
|
63
|
+
*
|
|
64
|
+
* Painted in the water rather than in a colour of its own, because from under
|
|
65
|
+
* here a cloud is not a white shape on a night sky. It is the light not
|
|
66
|
+
* arriving, and the water is what is left where it does not.
|
|
67
|
+
*/
|
|
68
|
+
const CLOUD = { give: 1.4, ink: 0.62, lit: 0.5, rim: 0.82, shade: 0.34, squat: 0.5 }
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* How much of a body's own light the thickest cloud takes.
|
|
72
|
+
*
|
|
73
|
+
* The shape covers what it covers, and this is the rest of it: a moon behind
|
|
74
|
+
* cloud throws a fainter shaft into the water than a clear one, and the shaft
|
|
75
|
+
* leaves the moon a long way from any cloud. Not all of it, since a covered
|
|
76
|
+
* moon still lights the cloud it is behind.
|
|
77
|
+
*/
|
|
78
|
+
const MUTE = 0.8
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The craters, off centre and unevenly sized on purpose.
|
|
82
|
+
*
|
|
83
|
+
* Three round spots spaced evenly on a disc is a face, and the moment anybody
|
|
84
|
+
* sees a face there they stop seeing a moon. In units of the moon's own radius,
|
|
85
|
+
* with the size third.
|
|
86
|
+
*/
|
|
87
|
+
const CRATERS = [
|
|
88
|
+
[-0.34, -0.22, 0.22],
|
|
89
|
+
[0.19, -0.45, 0.1],
|
|
90
|
+
[0.31, 0.3, 0.15],
|
|
91
|
+
] as const
|
|
92
|
+
|
|
93
|
+
/** How dark a crater is against the moon it is on. */
|
|
94
|
+
const CRATER_INK = 0.34
|
|
95
|
+
|
|
96
|
+
/** How many sides a disc is cut into. Enough that the edge of a moon a
|
|
97
|
+
* fingernail across is a curve rather than a decision. */
|
|
98
|
+
const ROUND = 72
|
|
99
|
+
|
|
100
|
+
export interface Box {
|
|
101
|
+
height: number
|
|
102
|
+
width: number
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* A body, as everything else in the sky needs it: where it is, how much light
|
|
107
|
+
* it is throwing, how far that light carries and what colour it is.
|
|
108
|
+
*/
|
|
109
|
+
interface Lamp {
|
|
110
|
+
cx: number
|
|
111
|
+
cy: number
|
|
112
|
+
glow: number
|
|
113
|
+
reach: number
|
|
114
|
+
tone: number
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* An hour asked for rather than read off the clock, for a harness holding one
|
|
119
|
+
* renderer against another.
|
|
120
|
+
*
|
|
121
|
+
* Two renderers can only be compared on the same sky, and the sky is a function
|
|
122
|
+
* of the minute the still was taken in. Every harness over this ornament takes
|
|
123
|
+
* the same four numbers for the same reason.
|
|
124
|
+
*/
|
|
125
|
+
export interface Asked {
|
|
126
|
+
daylight: number
|
|
127
|
+
dusk: number
|
|
128
|
+
lit: number
|
|
129
|
+
march: number
|
|
130
|
+
waxing: boolean
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The sky, drawn.
|
|
135
|
+
*
|
|
136
|
+
* Both bodies every time, not the one that happens to be up: a single disc that
|
|
137
|
+
* swapped identity at dusk would set in the west and reappear a moment later in
|
|
138
|
+
* the east. Fading a setting sun out where it set while a moon comes up on the
|
|
139
|
+
* other side is what actually happens, and it costs one more circle.
|
|
140
|
+
*/
|
|
141
|
+
export function paintSky(
|
|
142
|
+
pen: Pen,
|
|
143
|
+
box: Box,
|
|
144
|
+
clouds: Clouds | null = null,
|
|
145
|
+
asked: Asked | null = null,
|
|
146
|
+
): { daylight: number; dusk: number } {
|
|
147
|
+
const sky = asked
|
|
148
|
+
? {
|
|
149
|
+
daylight: asked.daylight,
|
|
150
|
+
dusk: asked.dusk,
|
|
151
|
+
moon: {
|
|
152
|
+
arc: Math.sin(asked.march * Math.PI),
|
|
153
|
+
lit: asked.lit,
|
|
154
|
+
march: asked.march,
|
|
155
|
+
up: 1,
|
|
156
|
+
waxing: asked.waxing,
|
|
157
|
+
},
|
|
158
|
+
sun: { arc: Math.sin(asked.march * Math.PI), march: asked.march, up: 1 },
|
|
159
|
+
}
|
|
160
|
+
: sunNow()
|
|
161
|
+
const bodies = [
|
|
162
|
+
{
|
|
163
|
+
moon: false,
|
|
164
|
+
passage: sky.sun,
|
|
165
|
+
phase: null as Phase | null,
|
|
166
|
+
show: sky.daylight,
|
|
167
|
+
tone: TONE.sun,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
moon: true,
|
|
171
|
+
passage: sky.moon,
|
|
172
|
+
phase: sky.moon as Phase,
|
|
173
|
+
show: (1 - sky.daylight) * sky.moon.up,
|
|
174
|
+
tone: TONE.moon,
|
|
175
|
+
},
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
const lamps: Lamp[] = []
|
|
179
|
+
for (const body of bodies) {
|
|
180
|
+
if (body.show <= 0.004) continue
|
|
181
|
+
lamps.push(paintBody(pen, box, body.passage, body.phase, body.show, body.tone, clouds))
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// After both of them, because what a cloud does is cover a light that has
|
|
185
|
+
// already been drawn, and what light it has of its own is theirs.
|
|
186
|
+
if (clouds) paintClouds(pen, clouds, lamps)
|
|
187
|
+
|
|
188
|
+
// Dusk falls off with depth and night does not, which is the difference
|
|
189
|
+
// between the two. A sunset is a colour arriving through the surface and it
|
|
190
|
+
// gets no further down than the light it came in on; laid flat over the whole
|
|
191
|
+
// box it lifts the floor off black and the water goes to one olive haze.
|
|
192
|
+
if (sky.dusk > 0) {
|
|
193
|
+
pen.wash({
|
|
194
|
+
alpha: DUSK_INK * sky.dusk,
|
|
195
|
+
fade: [0, box.height * DUSK_REACH],
|
|
196
|
+
lane: LANE.dusk,
|
|
197
|
+
tone: TONE.dusk,
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (sky.daylight < 1) {
|
|
202
|
+
pen.wash({ alpha: NIGHT_INK * (1 - sky.daylight), lane: LANE.night, tone: TONE.surface })
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return { daylight: sky.daylight, dusk: sky.dusk }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* One body: the light around it, the shaft under it, and the disc itself.
|
|
210
|
+
*
|
|
211
|
+
* The disc is drawn at full strength whatever shape it is in, because a
|
|
212
|
+
* crescent is as bright per inch as a full moon and there is simply less of it.
|
|
213
|
+
* What there is less of is everything around it, so the halo, the bloom, the
|
|
214
|
+
* streak and the shaft all take `lit`. A new moon throws nothing, which is what
|
|
215
|
+
* a new moon does.
|
|
216
|
+
*/
|
|
217
|
+
function paintBody(
|
|
218
|
+
pen: Pen,
|
|
219
|
+
box: Box,
|
|
220
|
+
passage: Passage,
|
|
221
|
+
phase: Phase | null,
|
|
222
|
+
show: number,
|
|
223
|
+
tone: number,
|
|
224
|
+
clouds: Clouds | null,
|
|
225
|
+
): Lamp {
|
|
226
|
+
const r = box.height * DISC
|
|
227
|
+
const cx = box.width * (EAST + passage.march * (WEST - EAST))
|
|
228
|
+
const cy = box.height * (LOW + passage.arc * (HIGH - LOW))
|
|
229
|
+
const veiled = clouds ? 1 - MUTE * clouds.cover(cx, cy) : 1
|
|
230
|
+
const glow = show * (phase ? phase.lit : 1) * veiled
|
|
231
|
+
|
|
232
|
+
// A body over on the left throws its shaft down and to the right, and one
|
|
233
|
+
// directly overhead throws it straight down.
|
|
234
|
+
const lean = (0.5 - passage.march) * SWING
|
|
235
|
+
|
|
236
|
+
for (const light of [BLOOM, HALO, STREAK]) {
|
|
237
|
+
pen.light(cx, cy, {
|
|
238
|
+
alpha: light.ink * glow,
|
|
239
|
+
fall: light.fall,
|
|
240
|
+
lane: LANE.body,
|
|
241
|
+
shade: OWN,
|
|
242
|
+
thin: light.thin,
|
|
243
|
+
tone,
|
|
244
|
+
width: r * light.reach,
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// The shaft, its mouth the disc's own diameter at the disc's own centre, so
|
|
249
|
+
// the two edges are tangent to it and the lower half of the disc sits inside
|
|
250
|
+
// its own light. Started anywhere below that line the mouth is wider than the
|
|
251
|
+
// disc is at that height, and the light appears to come out of its sides.
|
|
252
|
+
pen.fill(
|
|
253
|
+
[
|
|
254
|
+
[
|
|
255
|
+
{ x: cx - r, y: cy },
|
|
256
|
+
{ x: cx + r, y: cy },
|
|
257
|
+
{ x: cx + box.width * (lean + SPREAD), y: box.height },
|
|
258
|
+
{ x: cx + box.width * (lean - SPREAD), y: box.height },
|
|
259
|
+
],
|
|
260
|
+
],
|
|
261
|
+
{
|
|
262
|
+
alpha: DISC_INK * glow,
|
|
263
|
+
fade: [cy, Math.max(1, box.height * FALL - cy)],
|
|
264
|
+
fall: DISC_GIVE,
|
|
265
|
+
lane: LANE.body,
|
|
266
|
+
tone,
|
|
267
|
+
},
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
pen.fill([phase ? crescent(cx, cy, r, phase) : disc(cx, cy, r)], {
|
|
271
|
+
alpha: show,
|
|
272
|
+
lane: LANE.body,
|
|
273
|
+
tone,
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
if (!phase) return { cx, cy, glow, reach: r * HALO.reach, tone }
|
|
277
|
+
|
|
278
|
+
// A crater sitting where there is no moon under it is a grey spot on the sky,
|
|
279
|
+
// so one is drawn only once it is clear of the terminator by its own width,
|
|
280
|
+
// and fades in over the next. Through the month they go out one after another
|
|
281
|
+
// as the shadow crosses them, which is what a terminator is for looking at.
|
|
282
|
+
for (const [x, y, size] of CRATERS) {
|
|
283
|
+
const clear = sunlit(x, y, phase) / size - 1
|
|
284
|
+
if (clear <= 0) continue
|
|
285
|
+
|
|
286
|
+
pen.fill([disc(cx + r * x, cy + r * y, r * size)], {
|
|
287
|
+
alpha: CRATER_INK * show * Math.min(1, clear),
|
|
288
|
+
lane: LANE.body,
|
|
289
|
+
tone: TONE.surface,
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return { cx, cy, glow, reach: r * HALO.reach, tone }
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* The weather, as lobes of water laid over whatever light is behind them.
|
|
298
|
+
*
|
|
299
|
+
* A round light rather than an outline, which is the one shape in this pen
|
|
300
|
+
* that has a soft edge: a cloud cut as a polygon has a rim, and a rim is the
|
|
301
|
+
* one thing a cloud does not have. The mass is the lobes overlapping, so it is
|
|
302
|
+
* densest where it is thickest and gives out at the edges without being told
|
|
303
|
+
* to.
|
|
304
|
+
*/
|
|
305
|
+
function paintClouds(pen: Pen, clouds: Clouds, lamps: readonly Lamp[]): void {
|
|
306
|
+
for (const one of clouds.clouds) {
|
|
307
|
+
for (const lobe of one.lobes) {
|
|
308
|
+
const x = one.x + lobe.dx
|
|
309
|
+
const y = one.y + lobe.dy
|
|
310
|
+
|
|
311
|
+
pen.light(x, y, {
|
|
312
|
+
alpha: CLOUD.ink * one.thick,
|
|
313
|
+
fall: CLOUD.give,
|
|
314
|
+
lane: LANE.cloud,
|
|
315
|
+
thin: CLOUD.squat,
|
|
316
|
+
tone: TONE.shadow,
|
|
317
|
+
weight: CLOUD.shade * one.thick,
|
|
318
|
+
width: lobe.r,
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
// What the mass gives back, which is the light it just took. A lobe near
|
|
322
|
+
// the body is standing in that body's halo and is lit by it; one at the
|
|
323
|
+
// other end of the sky is a shadow and nothing else. Smaller than the
|
|
324
|
+
// mass that took the light, so what shows is the light around the edge
|
|
325
|
+
// of a cloud rather than a cloud turned into a lamp.
|
|
326
|
+
for (const lamp of lamps) {
|
|
327
|
+
const near = 1 - Math.hypot(x - lamp.cx, y - lamp.cy) / lamp.reach
|
|
328
|
+
if (near <= 0) continue
|
|
329
|
+
|
|
330
|
+
pen.light(x, y, {
|
|
331
|
+
alpha: CLOUD.lit * lamp.glow * near * one.thick,
|
|
332
|
+
fall: CLOUD.give,
|
|
333
|
+
lane: LANE.cloud,
|
|
334
|
+
thin: CLOUD.squat,
|
|
335
|
+
tone: lamp.tone,
|
|
336
|
+
width: lobe.r * CLOUD.rim,
|
|
337
|
+
})
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** A disc, as a polygon. */
|
|
344
|
+
function disc(cx: number, cy: number, r: number): Point[] {
|
|
345
|
+
const points: Point[] = []
|
|
346
|
+
for (let i = 0; i < ROUND; i++) {
|
|
347
|
+
const turn = (i / ROUND) * Math.PI * 2
|
|
348
|
+
points.push({ x: cx + r * Math.cos(turn), y: cy + r * Math.sin(turn) })
|
|
349
|
+
}
|
|
350
|
+
return points
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* The lit part of the moon, as a polygon.
|
|
355
|
+
*
|
|
356
|
+
* The same two arcs `crescent` in the ornament writes as a path, walked instead
|
|
357
|
+
* of written: the limb, which is the disc's own edge on the side the sun is,
|
|
358
|
+
* and the terminator, which is a circle around the moon seen edge on and so is
|
|
359
|
+
* an ellipse squeezed by how much of the disc is in the light.
|
|
360
|
+
*/
|
|
361
|
+
function crescent(cx: number, cy: number, r: number, { lit, waxing }: Phase): Point[] {
|
|
362
|
+
const side = waxing ? 1 : -1
|
|
363
|
+
const waist = r * (2 * lit - 1)
|
|
364
|
+
const points: Point[] = []
|
|
365
|
+
const half = ROUND / 2
|
|
366
|
+
|
|
367
|
+
for (let i = 0; i <= half; i++) {
|
|
368
|
+
const turn = (i / half) * Math.PI
|
|
369
|
+
points.push({ x: cx + side * r * Math.sin(turn), y: cy - r * Math.cos(turn) })
|
|
370
|
+
}
|
|
371
|
+
for (let i = half; i >= 0; i--) {
|
|
372
|
+
const turn = (i / half) * Math.PI
|
|
373
|
+
points.push({ x: cx - side * waist * Math.sin(turn), y: cy - r * Math.cos(turn) })
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return points
|
|
377
|
+
}
|