@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
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One animal, alone, with the water held close enough to see it.
|
|
3
|
+
*
|
|
4
|
+
* A sea is drawn to be a sea: an animal in it is a few percent of the box
|
|
5
|
+
* across, because that is what a fish at eight metres actually looks like and
|
|
6
|
+
* `sizes.ts` is not going to lie about it for the sake of a nice picture. That
|
|
7
|
+
* is right for a background and useless for a plate, and a plate is what finds
|
|
8
|
+
* drawing faults. At the weight ornament ships at, a wreck that is upside down
|
|
9
|
+
* and a wreck that is working look identical.
|
|
10
|
+
*
|
|
11
|
+
* So a specimen is not a bigger animal. It is the same animal in the same
|
|
12
|
+
* water, with the camera brought in until it fills the frame, and `look` is
|
|
13
|
+
* where to point it. Nothing about the drawing changes, which is the whole
|
|
14
|
+
* value: a fault seen here is a fault the sea has.
|
|
15
|
+
*
|
|
16
|
+
* ## Why the water is bigger than the box it is shown in
|
|
17
|
+
*
|
|
18
|
+
* Because the animal has to have somewhere to be. A fish given a box the size
|
|
19
|
+
* of the view spends its life against an edge it can feel; given room, it swims
|
|
20
|
+
* about its business and the camera follows. What is shown is a window on a
|
|
21
|
+
* water that carries on past it.
|
|
22
|
+
*/
|
|
23
|
+
import type { Presence } from "../biome.ts"
|
|
24
|
+
import { createCephalopods } from "../cephalopods.ts"
|
|
25
|
+
import { createJellies, type JellyKind } from "../jellies.ts"
|
|
26
|
+
import { createPuffers, type Puffers } from "../puffers.ts"
|
|
27
|
+
import { createSeabed } from "../seabed.ts"
|
|
28
|
+
import { createShoal, type Kind as FishKind } from "../shoal.ts"
|
|
29
|
+
import { createWalkers } from "../walkers.ts"
|
|
30
|
+
import { paintInklings, paintJellies, paintPuffers, paintShoal, paintWalkers } from "./life.ts"
|
|
31
|
+
import { Pen } from "./pen.ts"
|
|
32
|
+
import type { Box } from "./sky.ts"
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The ones the design system shows, one from each family that stays in frame.
|
|
36
|
+
*
|
|
37
|
+
* The animals that cross rather than live here are deliberately not among
|
|
38
|
+
* them. A shark keeps a rest between crossings measured in minutes, so a plate
|
|
39
|
+
* of one is a plate of empty water for most of the time somebody is looking at
|
|
40
|
+
* it, and a shark wound on until it is there is a shark on a treadmill.
|
|
41
|
+
*/
|
|
42
|
+
export type Kind = "crab" | "jellyfish" | "octopus" | "pufferfish" | "swordfish"
|
|
43
|
+
|
|
44
|
+
export const KINDS: readonly Kind[] = ["swordfish", "jellyfish", "octopus", "crab", "pufferfish"]
|
|
45
|
+
|
|
46
|
+
/** What each one is, for a page that has to say. */
|
|
47
|
+
export const GLOSS: Readonly<Record<Kind, string>> = {
|
|
48
|
+
crab: "It goes where it decides to go, and the current has no say in it.",
|
|
49
|
+
jellyfish: "A moon jelly, which gets a second push out of its own refill.",
|
|
50
|
+
octopus: "The only thing down there with a repertoire rather than a rhythm.",
|
|
51
|
+
pufferfish: "It is a fish until something crowds it, and then it is a ball of spines.",
|
|
52
|
+
swordfish: "A billfish over a reef is a visitor from the water beyond it.",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* How much bigger the water is than the window on it.
|
|
57
|
+
*
|
|
58
|
+
* Enough that the animal is never against a wall, and no more: everything in
|
|
59
|
+
* this water is stepped whether it is in the frame or not, so room is paid for
|
|
60
|
+
* every frame.
|
|
61
|
+
*/
|
|
62
|
+
const ROOM = 5
|
|
63
|
+
|
|
64
|
+
/** How many numbers a plate's frame is given. One animal is not a sea. */
|
|
65
|
+
const BLOCK = 1 << 16
|
|
66
|
+
|
|
67
|
+
/** How much of the window the animal is meant to take up. */
|
|
68
|
+
const FILLS = 0.72
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* How far a drawing actually reaches, against the size the animal reports.
|
|
72
|
+
*
|
|
73
|
+
* An animal's size is the size of the animal, and a drawing of one is bigger
|
|
74
|
+
* than that in every case where something trails: an octopus is measured across
|
|
75
|
+
* its head and drawn with two head-lengths of arms under it, a crab is measured
|
|
76
|
+
* across its shell and stands on legs half again as wide, a pufferfish is
|
|
77
|
+
* measured deflated and spends its interesting seconds twice that with its
|
|
78
|
+
* spines out, and a moon jelly is a bell with a curtain hanging off it. `drop`
|
|
79
|
+
* is the other half of the same fact, because none of it trails evenly: what
|
|
80
|
+
* hangs, hangs downwards, so the middle of the drawing is below the middle of
|
|
81
|
+
* the animal.
|
|
82
|
+
*
|
|
83
|
+
* It is only ever a plate's business. A sea draws these at the size they are
|
|
84
|
+
* and never has to frame one.
|
|
85
|
+
*/
|
|
86
|
+
const REACH: Readonly<Record<Kind, { drop: number; wide: number }>> = {
|
|
87
|
+
crab: { drop: 0.06, wide: 2.05 },
|
|
88
|
+
jellyfish: { drop: 0.34, wide: 1.6 },
|
|
89
|
+
octopus: { drop: 0.28, wide: 1.9 },
|
|
90
|
+
pufferfish: { drop: 0, wide: 2.15 },
|
|
91
|
+
swordfish: { drop: 0, wide: 1.15 },
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* What crowds the pufferfish, and how often.
|
|
96
|
+
*
|
|
97
|
+
* A pufferfish at rest is a slightly lumpy fish sculling about, and the drawing
|
|
98
|
+
* worth judging is the other one. In a sea the thing that comes near is a shark
|
|
99
|
+
* or a hull; on a plate it is whoever is looking, so that is what this is:
|
|
100
|
+
* something several times the animal's size, with its whole attention on it,
|
|
101
|
+
* leaning in for a few seconds out of every `every` and going away again.
|
|
102
|
+
*
|
|
103
|
+
* It is the one thing on a plate that is not in the water. It is there because
|
|
104
|
+
* the drawing this plate exists to show is the ball of spines, and the animal
|
|
105
|
+
* will not make one to order: it swells at what the water is doing, and a plate
|
|
106
|
+
* with nothing in it is a plate of water doing nothing.
|
|
107
|
+
*/
|
|
108
|
+
const LOOKING = { every: 24, menace: 1, near: 3, size: 3 }
|
|
109
|
+
|
|
110
|
+
/** How light it is on a plate. */
|
|
111
|
+
const DAYLIGHT = 1
|
|
112
|
+
|
|
113
|
+
/** How many numbers stand in front of a frame's count; see `frame.ts`. */
|
|
114
|
+
const HEAD = 6
|
|
115
|
+
|
|
116
|
+
export interface PlateOptions {
|
|
117
|
+
height: number
|
|
118
|
+
seed?: number
|
|
119
|
+
width: number
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface Plate {
|
|
123
|
+
/** The window's own size, which is not the water's. */
|
|
124
|
+
readonly box: Box
|
|
125
|
+
readonly geometry: Float32Array
|
|
126
|
+
/** Where to point the camera, and how far in. */
|
|
127
|
+
look(): { scale: number; x: number; y: number }
|
|
128
|
+
over(): number
|
|
129
|
+
step(seconds: number): void
|
|
130
|
+
wind(seconds: number): void
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* A plate of one animal.
|
|
135
|
+
*
|
|
136
|
+
* Everything it does not need is left out rather than made and ignored. A crab
|
|
137
|
+
* and an octopus need a floor, because neither of them is ever off it. Nothing
|
|
138
|
+
* else here has a bed at all, and a jellyfish in open water is a jellyfish.
|
|
139
|
+
*/
|
|
140
|
+
export function createPlate(kind: Kind, options: PlateOptions): Plate {
|
|
141
|
+
const box = { height: options.height, width: options.width }
|
|
142
|
+
const water = { height: box.height * ROOM, width: box.width * ROOM }
|
|
143
|
+
const seed = options.seed ?? 1
|
|
144
|
+
const geometry = new Float32Array(BLOCK)
|
|
145
|
+
|
|
146
|
+
const grounded = kind === "crab" || kind === "octopus" || kind === "pufferfish"
|
|
147
|
+
const seabed = grounded ? createSeabed({ ...water, cliffs: 0, ranges: 1, seed, stones: 0 }) : null
|
|
148
|
+
|
|
149
|
+
const shoal =
|
|
150
|
+
kind === "swordfish"
|
|
151
|
+
? createShoal({
|
|
152
|
+
count: 1,
|
|
153
|
+
cruise: 0.5,
|
|
154
|
+
height: water.height,
|
|
155
|
+
seed,
|
|
156
|
+
species: { swordfish: 1 } as Partial<Record<FishKind, number>>,
|
|
157
|
+
width: water.width,
|
|
158
|
+
})
|
|
159
|
+
: null
|
|
160
|
+
|
|
161
|
+
const jellies =
|
|
162
|
+
kind === "jellyfish"
|
|
163
|
+
? createJellies({
|
|
164
|
+
count: 1,
|
|
165
|
+
height: water.height,
|
|
166
|
+
kinds: ["moon"] as readonly JellyKind[],
|
|
167
|
+
seed,
|
|
168
|
+
width: water.width,
|
|
169
|
+
})
|
|
170
|
+
: null
|
|
171
|
+
|
|
172
|
+
const inklings =
|
|
173
|
+
kind === "octopus" && seabed
|
|
174
|
+
? createCephalopods({
|
|
175
|
+
floor: seabed.floorAt,
|
|
176
|
+
height: water.height,
|
|
177
|
+
octopuses: 1,
|
|
178
|
+
seed,
|
|
179
|
+
squids: 0,
|
|
180
|
+
width: water.width,
|
|
181
|
+
})
|
|
182
|
+
: null
|
|
183
|
+
|
|
184
|
+
let watched = 0
|
|
185
|
+
|
|
186
|
+
const looking = (): readonly Presence[] => {
|
|
187
|
+
const one = puffers?.puffers[0]
|
|
188
|
+
if (!one || watched % LOOKING.every > LOOKING.near) return []
|
|
189
|
+
return [
|
|
190
|
+
{
|
|
191
|
+
depth: one.depth,
|
|
192
|
+
menace: LOOKING.menace,
|
|
193
|
+
size: one.size * LOOKING.size,
|
|
194
|
+
x: one.x,
|
|
195
|
+
y: one.y,
|
|
196
|
+
},
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const puffers: Puffers | null =
|
|
201
|
+
kind === "pufferfish" && seabed
|
|
202
|
+
? createPuffers({
|
|
203
|
+
about: looking,
|
|
204
|
+
count: 1,
|
|
205
|
+
floor: seabed.floorAt,
|
|
206
|
+
height: water.height,
|
|
207
|
+
seed,
|
|
208
|
+
width: water.width,
|
|
209
|
+
})
|
|
210
|
+
: null
|
|
211
|
+
|
|
212
|
+
const walkers =
|
|
213
|
+
kind === "crab" && seabed
|
|
214
|
+
? createWalkers({
|
|
215
|
+
crabs: 1,
|
|
216
|
+
floor: seabed.floorAt,
|
|
217
|
+
seed,
|
|
218
|
+
starfish: 0,
|
|
219
|
+
width: water.width,
|
|
220
|
+
})
|
|
221
|
+
: null
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Where the animal is, kept from the last frame it was in.
|
|
225
|
+
*
|
|
226
|
+
* A camera that snapped back to the middle of the water whenever the animal
|
|
227
|
+
* was between two states would be a camera nobody could watch. The last known
|
|
228
|
+
* place is a better answer than the middle, and it is always nearly right.
|
|
229
|
+
*/
|
|
230
|
+
let held = { x: water.width / 2, y: water.height / 2 }
|
|
231
|
+
let across = box.width / 6
|
|
232
|
+
|
|
233
|
+
const find = () => {
|
|
234
|
+
const one =
|
|
235
|
+
shoal?.fish[0] ??
|
|
236
|
+
jellies?.jellies[0] ??
|
|
237
|
+
inklings?.octopuses[0] ??
|
|
238
|
+
puffers?.puffers[0] ??
|
|
239
|
+
walkers?.walkers[0]
|
|
240
|
+
if (!one) return
|
|
241
|
+
|
|
242
|
+
held = { x: one.x, y: one.y }
|
|
243
|
+
if (one.size > 0) across = one.size
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
box,
|
|
248
|
+
|
|
249
|
+
geometry,
|
|
250
|
+
|
|
251
|
+
look() {
|
|
252
|
+
find()
|
|
253
|
+
const reach = REACH[kind]
|
|
254
|
+
const drawn = Math.max(1, across * reach.wide)
|
|
255
|
+
return {
|
|
256
|
+
scale: (Math.min(box.height, box.width) * FILLS) / drawn,
|
|
257
|
+
x: held.x,
|
|
258
|
+
y: held.y + across * reach.drop,
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
over(): number {
|
|
263
|
+
// The same six numbers a sea puts in front of its frame, so one reader
|
|
264
|
+
// walks either. A plate is not held by anybody: it is a drawing being
|
|
265
|
+
// looked at, and a drawing that wandered would be a drawing on a boat.
|
|
266
|
+
geometry[0] = 0
|
|
267
|
+
geometry[1] = 0
|
|
268
|
+
geometry[2] = 0
|
|
269
|
+
geometry[3] = 1
|
|
270
|
+
geometry[4] = 0
|
|
271
|
+
geometry[5] = DAYLIGHT
|
|
272
|
+
|
|
273
|
+
const pen = new Pen(geometry, HEAD)
|
|
274
|
+
if (shoal) paintShoal(pen, shoal)
|
|
275
|
+
// A plate is looked at rather than glanced past, so it is drawn in full
|
|
276
|
+
// daylight whatever the hour: everything in this water is the theme's own
|
|
277
|
+
// ink either way, and what the hour changes is a wash over the top, which
|
|
278
|
+
// on a plate is something to squint through.
|
|
279
|
+
if (jellies) paintJellies(pen, jellies, DAYLIGHT)
|
|
280
|
+
if (inklings) paintInklings(pen, inklings)
|
|
281
|
+
if (puffers) paintPuffers(pen, puffers)
|
|
282
|
+
if (walkers) paintWalkers(pen, walkers)
|
|
283
|
+
return pen.close()
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
step(seconds: number): void {
|
|
287
|
+
shoal?.step(seconds, null, null)
|
|
288
|
+
jellies?.step(seconds, null)
|
|
289
|
+
inklings?.step(seconds, null)
|
|
290
|
+
watched += seconds
|
|
291
|
+
puffers?.step(seconds)
|
|
292
|
+
walkers?.step(seconds)
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
wind(seconds: number): void {
|
|
296
|
+
const step = 1 / 10
|
|
297
|
+
for (let at = 0; at < Math.round(Math.max(0, seconds) / step); at++) this.step(step)
|
|
298
|
+
},
|
|
299
|
+
}
|
|
300
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A drawing, walked.
|
|
3
|
+
*
|
|
4
|
+
* The ornament writes its animals and its wreckage as paths, because that is
|
|
5
|
+
* what a browser and Qt both take: a string of moves and curves. This renderer
|
|
6
|
+
* takes triangles, so somewhere between the two the curves have to become
|
|
7
|
+
* points, and here is the cheapest place for it to happen. The alternative is a
|
|
8
|
+
* path parser on the other side of the wire and a string squeezed through an
|
|
9
|
+
* array of floats to reach it.
|
|
10
|
+
*
|
|
11
|
+
* Absolute commands only, and only the four the ornament writes: a move, a
|
|
12
|
+
* line, a cubic and a quadratic, with a close. Nothing here guesses at the rest
|
|
13
|
+
* of SVG. If a drawing ever grows an arc, this will draw it as the chord and
|
|
14
|
+
* somebody will see it.
|
|
15
|
+
*/
|
|
16
|
+
import type { Point } from "./pen.ts"
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* How fine a curve is cut, as a share of how big the whole drawing is.
|
|
20
|
+
*
|
|
21
|
+
* A share rather than a length, and that is the whole of the change this file
|
|
22
|
+
* once needed. The drawings in this ornament are not written at one scale: a
|
|
23
|
+
* fish is about forty units nose to tail and an octopus is written about the
|
|
24
|
+
* origin at less than one, and both are stood at whatever size they are
|
|
25
|
+
* actually drawn. Cut to a fixed length, the fish came out smooth and the
|
|
26
|
+
* octopus came out as a heptagon with legs, and nothing said so until one was
|
|
27
|
+
* magnified on a plate.
|
|
28
|
+
*
|
|
29
|
+
* So a curve is cut against the drawing it belongs to. The steps are still
|
|
30
|
+
* counted against how far the curve actually runs, because a long sweep down a
|
|
31
|
+
* whale's back and the notch in a tail fin are the same command and want a
|
|
32
|
+
* different number of pieces.
|
|
33
|
+
*/
|
|
34
|
+
const STEP = 0.035
|
|
35
|
+
const MOST = 24
|
|
36
|
+
|
|
37
|
+
/** What one path came to, kept, since a fish is the same drawing as the fish
|
|
38
|
+
* beating beside it and there are only so many steps in a beat. */
|
|
39
|
+
const walked = new Map<string, Point[][]>()
|
|
40
|
+
|
|
41
|
+
export function trace(d: string): Point[][] {
|
|
42
|
+
const held = walked.get(d)
|
|
43
|
+
if (held) return held
|
|
44
|
+
|
|
45
|
+
const cut = walk(d)
|
|
46
|
+
walked.set(d, cut)
|
|
47
|
+
return cut
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* How big the drawing is, off the numbers in it.
|
|
52
|
+
*
|
|
53
|
+
* The furthest any coordinate gets from the origin, which is a bound on the
|
|
54
|
+
* drawing rather than its size. It is read off the string because it has to be
|
|
55
|
+
* known before the walk it decides the fineness of, and because every drawing
|
|
56
|
+
* in this ornament is written about its own middle, where the two are close
|
|
57
|
+
* enough.
|
|
58
|
+
*/
|
|
59
|
+
function span(d: string): number {
|
|
60
|
+
let widest = 0
|
|
61
|
+
const numbers = d.match(/-?\d*\.?\d+(?:e-?\d+)?/g)
|
|
62
|
+
if (!numbers) return 1
|
|
63
|
+
for (const one of numbers) widest = Math.max(widest, Math.abs(Number(one)))
|
|
64
|
+
return Math.max(widest, 0.001)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function walk(d: string): Point[][] {
|
|
68
|
+
const step = span(d) * STEP
|
|
69
|
+
const parts: Point[][] = []
|
|
70
|
+
let here: Point[] = []
|
|
71
|
+
let at = { x: 0, y: 0 }
|
|
72
|
+
let start = { x: 0, y: 0 }
|
|
73
|
+
|
|
74
|
+
const tokens = d.match(/[MLCQZmlcqz]|-?\d*\.?\d+(?:e-?\d+)?/g) ?? []
|
|
75
|
+
let read = 0
|
|
76
|
+
const number = () => Number(tokens[read++])
|
|
77
|
+
|
|
78
|
+
while (read < tokens.length) {
|
|
79
|
+
const command = tokens[read++]
|
|
80
|
+
|
|
81
|
+
if (command === "M" || command === "m") {
|
|
82
|
+
if (here.length > 1) parts.push(here)
|
|
83
|
+
here = []
|
|
84
|
+
at = { x: number(), y: number() }
|
|
85
|
+
start = at
|
|
86
|
+
here.push(at)
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (command === "L" || command === "l") {
|
|
91
|
+
at = { x: number(), y: number() }
|
|
92
|
+
here.push(at)
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (command === "C" || command === "c") {
|
|
97
|
+
const one = { x: number(), y: number() }
|
|
98
|
+
const two = { x: number(), y: number() }
|
|
99
|
+
const end = { x: number(), y: number() }
|
|
100
|
+
cubic(here, at, one, two, end, step)
|
|
101
|
+
at = end
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (command === "Q" || command === "q") {
|
|
106
|
+
const hold = { x: number(), y: number() }
|
|
107
|
+
const end = { x: number(), y: number() }
|
|
108
|
+
quadratic(here, at, hold, end, step)
|
|
109
|
+
at = end
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (command === "Z" || command === "z") {
|
|
114
|
+
if (here.length > 1) parts.push(here)
|
|
115
|
+
here = []
|
|
116
|
+
at = start
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// A number where a command was expected is a repeat of the last one, which
|
|
121
|
+
// the ornament does not write. Skipping it is better than reading the rest
|
|
122
|
+
// of the drawing off by one.
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (here.length > 1) parts.push(here)
|
|
126
|
+
return parts
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** How many pieces a curve of this reach is worth cutting into. */
|
|
130
|
+
function pieces(reach: number, step: number): number {
|
|
131
|
+
return Math.max(2, Math.min(MOST, Math.ceil(reach / step)))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function cubic(into: Point[], from: Point, one: Point, two: Point, end: Point, step: number): void {
|
|
135
|
+
const reach =
|
|
136
|
+
Math.hypot(one.x - from.x, one.y - from.y) +
|
|
137
|
+
Math.hypot(two.x - one.x, two.y - one.y) +
|
|
138
|
+
Math.hypot(end.x - two.x, end.y - two.y)
|
|
139
|
+
const steps = pieces(reach, step)
|
|
140
|
+
|
|
141
|
+
for (let i = 1; i <= steps; i++) {
|
|
142
|
+
const t = i / steps
|
|
143
|
+
const u = 1 - t
|
|
144
|
+
into.push({
|
|
145
|
+
x: u * u * u * from.x + 3 * u * u * t * one.x + 3 * u * t * t * two.x + t * t * t * end.x,
|
|
146
|
+
y: u * u * u * from.y + 3 * u * u * t * one.y + 3 * u * t * t * two.y + t * t * t * end.y,
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function quadratic(into: Point[], from: Point, hold: Point, end: Point, step: number): void {
|
|
152
|
+
const reach =
|
|
153
|
+
Math.hypot(hold.x - from.x, hold.y - from.y) + Math.hypot(end.x - hold.x, end.y - hold.y)
|
|
154
|
+
const steps = pieces(reach, step)
|
|
155
|
+
|
|
156
|
+
for (let i = 1; i <= steps; i++) {
|
|
157
|
+
const t = i / steps
|
|
158
|
+
const u = 1 - t
|
|
159
|
+
into.push({
|
|
160
|
+
x: u * u * from.x + 2 * u * t * hold.x + t * t * end.x,
|
|
161
|
+
y: u * u * from.y + 2 * u * t * hold.y + t * t * end.y,
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
}
|