kipphi 2.0.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/easing.ts ADDED
@@ -0,0 +1,543 @@
1
+ import { type CustomEasingData, type EasingDataKPA2, EasingType, EventType, type SegmentedEasingData, type NormalEasingData, type BezierEasingData, type TemplateEasingData } from "./chartTypes";
2
+ import { EventNodeSequence } from "./event";
3
+ import { type TupleCoord } from "./util";
4
+ import Environment from "./env";
5
+
6
+
7
+ /// #declaration:global
8
+
9
+ const easeOutElastic = (x: number): number => {
10
+ const c4 = (2 * Math.PI) / 3;
11
+
12
+ return x === 0
13
+ ? 0
14
+ : x === 1
15
+ ? 1
16
+ : Math.pow(2, -10 * x) * Math.sin((x * 10 - 0.75) * c4) + 1;
17
+ }
18
+
19
+ const easeOutBounce = (x: number): number => {
20
+ const n1 = 7.5625;
21
+ const d1 = 2.75;
22
+
23
+ if (x < 1 / d1) {
24
+ return n1 * x * x;
25
+ } else if (x < 2 / d1) {
26
+ return n1 * (x -= 1.5 / d1) * x + 0.75;
27
+ } else if (x < 2.5 / d1) {
28
+ return n1 * (x -= 2.25 / d1) * x + 0.9375;
29
+ } else {
30
+ return n1 * (x -= 2.625 / d1) * x + 0.984375;
31
+ }
32
+ }
33
+
34
+ const easeOutExpo = (x: number): number =>{
35
+ return x === 1 ? 1 : 1 - Math.pow(2, -10 * x);
36
+ }
37
+
38
+ const easeOutBack = (x: number): number =>{
39
+ const c1 = 1.70158;
40
+ const c3 = c1 + 1;
41
+
42
+ return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2);
43
+ }
44
+
45
+ const linear = (x: number): number => x
46
+ const linearLine: CurveDrawer = (context: CanvasRenderingContext2D, startX: number, startY: number, endX: number , endY: number) =>
47
+ drawLine(context, startX, startY, endX, endY);
48
+
49
+
50
+
51
+ const easeOutSine = (x: number): number => Math.sin((x * Math.PI) / 2);
52
+
53
+
54
+ const easeInQuad = (x: number): number => Math.pow(x, 2)
55
+
56
+
57
+ const easeInCubic = (x: number): number => Math.pow(x, 3)
58
+
59
+
60
+ const easeInQuart = (x: number): number => Math.pow(x, 4)
61
+
62
+
63
+ const easeInQuint = (x: number): number => Math.pow(x, 5)
64
+
65
+
66
+ const easeInCirc = (x: number): number => 1 - Math.sqrt(1 - Math.pow(x, 2))
67
+
68
+
69
+ function mirror(easeOut: (x: number) => number) {
70
+ return (x: number) => 1 - easeOut(1 - x);
71
+ }
72
+
73
+ function toEaseInOut(easeIn: (x: number) => number, easeOut: (x: number) => number) {
74
+ return (x: number) => x < 0.5 ? easeIn(2 * x) / 2 : (1 + easeOut(2 * x - 1)) / 2
75
+ }
76
+
77
+ const easeOutQuad = mirror(easeInQuad);
78
+ const easeInSine = mirror(easeOutSine);
79
+ const easeOutQuart = mirror(easeInQuart);
80
+ const easeOutCubic = mirror(easeInCubic);
81
+ const easeOutQuint = mirror(easeInQuint);
82
+ const easeOutCirc = mirror(easeInCirc);
83
+ const easeInExpo = mirror(easeOutExpo);
84
+ const easeInElastic = mirror(easeOutElastic);
85
+ const easeInBounce = mirror(easeOutBounce);
86
+ const easeInBack = mirror(easeOutBack);
87
+ const easeInOutSine = toEaseInOut(easeInSine, easeOutSine);
88
+ const easeInOutQuad = toEaseInOut(easeInQuad, easeOutQuad);
89
+ const easeInOutCubic = toEaseInOut(easeInCubic, easeOutCubic);
90
+ const easeInOutQuart = toEaseInOut(easeInQuart, easeOutQuart);
91
+ const easeInOutQuint = toEaseInOut(easeInQuint, easeOutQuint);
92
+ const easeInOutExpo = toEaseInOut(easeInExpo, easeOutExpo);
93
+ const easeInOutCirc = toEaseInOut(easeInCirc, easeOutCirc);
94
+ const easeInOutBack = toEaseInOut(easeInBack, easeOutBack);
95
+ const easeInOutElastic = toEaseInOut(easeInElastic, easeOutElastic);
96
+ const easeInOutBounce = toEaseInOut(easeInBounce, easeOutBounce);
97
+
98
+ export const easingFnMap = {
99
+ "linear": [linear, linear, linear],
100
+ "sine": [easeInSine, easeOutSine, toEaseInOut(easeInSine, easeOutSine)],
101
+ "quad": [easeInQuad, easeOutQuad, toEaseInOut(easeInQuad, easeOutQuad)],
102
+ "cubic": [easeInCubic, easeOutCubic, toEaseInOut(easeInCubic, easeOutCubic)],
103
+ "quart": [easeInQuart, easeOutQuart, toEaseInOut(easeInQuart, easeOutQuart)],
104
+ "quint": [easeInQuint, easeOutQuint, toEaseInOut(easeInQuint, easeOutQuint)],
105
+ "expo": [easeInExpo, easeOutExpo, toEaseInOut(easeInExpo, easeOutExpo)],
106
+ "circ": [easeInCirc, easeOutCirc, toEaseInOut(easeInCirc, easeOutCirc)],
107
+ "back": [easeInBack, easeOutBack, toEaseInOut(easeInBack, easeOutBack)],
108
+ "elastic": [easeInElastic, easeOutElastic, toEaseInOut(easeInElastic, easeOutElastic)],
109
+ "bounce": [easeInBounce, easeOutBounce, toEaseInOut(easeInBounce, easeOutBounce)]
110
+ }
111
+ /**
112
+ * 缓动基类
113
+ * Easings are used to describe the rate of change of a parameter over time.
114
+ * They are used in events, curve note filling, etc.
115
+ */
116
+ export abstract class Easing {
117
+ constructor() {
118
+
119
+ }
120
+ /**
121
+ * 返回当前变化量与变化量之比
122
+ * 或者当前数值。(参数方程)
123
+ * @param t 一个0-1的浮点数,代表当前经过时间与总时间之比
124
+ */
125
+ abstract getValue(t: number): number;
126
+ abstract dump(): EasingDataKPA2;
127
+ segmentedValueGetter(easingLeft: number, easingRight: number) {
128
+ const leftValue = this.getValue(easingLeft);
129
+ const rightValue = this.getValue(easingRight);
130
+ const timeDelta = easingRight - easingLeft;
131
+ const delta = rightValue - leftValue;
132
+ if (delta === 0) {
133
+ throw new Error('Easing delta cannot be zero.');
134
+ }
135
+ return (t: number) => (this.getValue(easingLeft + timeDelta * t) - leftValue) / delta;
136
+ }
137
+ drawCurve(context: CanvasRenderingContext2D, startX: number, startY: number, endX: number , endY: number): void {
138
+ const delta = endY - startY;
139
+ const timeDelta = endX - startX;
140
+ let last = startY;
141
+ context.beginPath()
142
+ context.moveTo(startX, last)
143
+ for (let t = 4; t <= timeDelta; t += 4) {
144
+ const ratio = t / timeDelta
145
+ const curPosY = this.getValue(ratio) * delta + startY;
146
+ context.lineTo(startX + t, curPosY);
147
+ last = curPosY;
148
+ }
149
+ context.stroke();
150
+ }
151
+ }
152
+
153
+
154
+ type CurveDrawer = (context: CanvasRenderingContext2D, startX: number, startY: number, endX: number , endY: number) => void
155
+
156
+
157
+ /**
158
+ * @immutable
159
+ */
160
+ export class SegmentedEasing extends Easing {
161
+ getter: (t: number) => number;
162
+ constructor(public readonly easing: Easing, public readonly left: number, public readonly right: number) {
163
+ super()
164
+ this.getter = easing.segmentedValueGetter(left, right)
165
+ }
166
+ getValue(t: number): number {
167
+ return this.getter(t)
168
+ }
169
+ replace(easing: Easing): Easing {
170
+ return new SegmentedEasing(easing, this.left, this.right)
171
+ }
172
+ dump(): SegmentedEasingData {
173
+ return {
174
+ left: this.left,
175
+ right: this.right,
176
+ inner: this.easing.dump(),
177
+ type: EasingType.segmented
178
+ }
179
+ }
180
+ }
181
+
182
+
183
+ /**
184
+ * 普通缓动
185
+ * See https://easings.net/zh-cn to learn about the basic types of easing.
186
+ *
187
+ */
188
+ export class NormalEasing extends Easing {
189
+ rpeId: number;
190
+ id: number;
191
+ funcType: string;
192
+ easeType: string;
193
+ _getValue: (t: number) => number;
194
+ _drawCurve: CurveDrawer;
195
+ constructor(fn: (t: number) => number);
196
+ constructor(fn: (t: number) => number, curveDrawer?: CurveDrawer);
197
+ constructor(fn: (t: number) => number, curveDrawer?: CurveDrawer) {
198
+ super()
199
+ this._getValue = fn;
200
+ if (curveDrawer) {
201
+ this._drawCurve = curveDrawer;
202
+ }
203
+ }
204
+ getValue(t: number): number {
205
+ if (t > 1 || t < 0) {
206
+ console.warn("缓动超出定义域!")
207
+ // debugger;
208
+ }
209
+ // console.log("t:", t, "rat", this._getValue(t))
210
+ return this._getValue(t)
211
+ }
212
+ private dumpCache: NormalEasingData;
213
+ dump(): NormalEasingData {
214
+ return this.dumpCache ??= {
215
+ type: EasingType.normal,
216
+ identifier: this.rpeId
217
+ }
218
+ }
219
+ drawCurve(context: CanvasRenderingContext2D, startX: number, startY: number, endX: number , endY: number) {
220
+ if (this._drawCurve) {
221
+ this._drawCurve(context, startX, startY, endX, endY)
222
+ } else {
223
+ super.drawCurve(context, startX, startY, endX, endY);
224
+ }
225
+ }
226
+ }
227
+
228
+
229
+
230
+
231
+
232
+ /**
233
+ * 贝塞尔曲线缓动
234
+ * uses the Bezier curve formula to describe an easing.
235
+ */
236
+ export class BezierEasing extends Easing {
237
+ readonly xs: readonly number[];
238
+ readonly ys: readonly number[];
239
+ readonly jumper: readonly number[];
240
+ constructor(public readonly cp1: TupleCoord, public readonly cp2: TupleCoord) {
241
+ super()
242
+ const BEZIER_INTERPOLATION_DENSITY = Environment.BEZIER_INTERPOLATION_DENSITY;
243
+ const BEZIER_INTERPOLATION_STEP = 1 / BEZIER_INTERPOLATION_DENSITY;
244
+ // 插值,把贝塞尔曲线近似成256段折线
245
+ const xs: number[] = new Array(BEZIER_INTERPOLATION_DENSITY - 1);
246
+ const ys: number[] = new Array(BEZIER_INTERPOLATION_DENSITY - 1);
247
+ /** 一把尺子,刻度均匀,从`插值步长*下标`映射到xs里面的下标 */
248
+ const jumper: number[] = new Array(BEZIER_INTERPOLATION_DENSITY);
249
+ let nextToFill = 0;
250
+ for (let i = 1; i < BEZIER_INTERPOLATION_DENSITY; i++) {
251
+ // 这个t是贝塞尔曲线生成参数
252
+ const t = i * BEZIER_INTERPOLATION_STEP;
253
+ const s = 1 - t;
254
+ const x = 3 * cp1[0] * Math.pow(s, 2) * t + 3 * cp2[0] * Math.pow(t, 2) * s + Math.pow(t, 3);
255
+ xs[i - 1] = x
256
+ ys[i - 1] = 3 * cp1[1] * Math.pow(s, 2) * t + 3 * cp2[1] * Math.pow(t, 2) * s + Math.pow(t, 3);
257
+ for (; x > nextToFill * BEZIER_INTERPOLATION_STEP; nextToFill++) {
258
+ jumper[nextToFill] = i - 1;
259
+ }
260
+ }
261
+ for (; 1 > nextToFill * BEZIER_INTERPOLATION_STEP; nextToFill++) {
262
+ jumper[nextToFill] = BEZIER_INTERPOLATION_DENSITY - 1;
263
+ }
264
+ this.xs = Object.freeze(xs);
265
+ this.ys = Object.freeze(ys);
266
+ this.jumper = Object.freeze(jumper);
267
+ }
268
+ /**
269
+ * 从横坐标获得纵坐标
270
+ * @param t 并不是贝塞尔曲线的参数,它对应一个横坐标数值,范围[0, 1]
271
+ * @returns
272
+ */
273
+ getValue(t: number): number {
274
+ if (t === 0 || t === 1) return t;
275
+ const BEZIER_INTERPOLATION_DENSITY = Environment.BEZIER_INTERPOLATION_DENSITY;
276
+ let index = this.jumper[Math.floor(t * BEZIER_INTERPOLATION_DENSITY)];
277
+ const xs = this.xs;
278
+ const ys = this.ys;
279
+ let next!: number;
280
+ for (; index < BEZIER_INTERPOLATION_DENSITY - 1; index++) {
281
+ next = xs[index + 1];
282
+ if (t < next) {
283
+ break;
284
+ }
285
+ }
286
+ const atLastSegment = index === BEZIER_INTERPOLATION_DENSITY - 1;
287
+ const here = atLastSegment ? 1 : xs[index];
288
+ const yhere = atLastSegment ? 1 : ys[index];
289
+ const yprev = ys[index - 1] || 0;
290
+ const k = (yprev - yhere) / ((xs[index - 1] || 0) - here);
291
+ return k * (t - here) + yhere;
292
+ }
293
+ dump(): BezierEasingData {
294
+ return {
295
+ type: EasingType.bezier,
296
+ bezier: [this.cp1[0], this.cp1[1], this.cp2[0], this.cp2[1]]
297
+ }
298
+ }
299
+ drawCurve(context: CanvasRenderingContext2D, startX: number, startY: number, endX: number , endY: number): void {
300
+ const [cp1x, cp1y] = this.cp1;
301
+ const [cp2x, cp2y] = this.cp2
302
+ const delta = endY - startY;
303
+ const timeDelta = endX - startX;
304
+ drawBezierCurve(
305
+ context,
306
+ startX, startY,
307
+ endX, endY,
308
+ startX + cp1x * timeDelta, startY + cp1y * delta,
309
+ startX + cp2x * timeDelta, startY + cp2y * delta,
310
+ )
311
+ }
312
+ }
313
+
314
+ /**
315
+ * 模板缓动
316
+ * to implement an easing with an eventNodeSequence.
317
+ * 这是受wikitext的模板概念启发的。
318
+ * This is inspired by the "template" concept in wikitext.
319
+ */
320
+ export class TemplateEasing extends Easing {
321
+ eventNodeSequence: EventNodeSequence;
322
+ name: string;
323
+ constructor(name: string, sequence: EventNodeSequence) {
324
+ super()
325
+ this.eventNodeSequence = sequence;
326
+ this.name = name;
327
+ }
328
+ getValue(t: number) {
329
+ const seq = this.eventNodeSequence;
330
+ const delta = this.valueDelta;
331
+ if (delta === 0) {
332
+ throw new Error('Easing delta cannot be zero.');
333
+ }
334
+ const frac = seq.getValueAt(t * seq.effectiveBeats, true) - this.headValue
335
+ return delta === 0 ? frac : frac / delta;
336
+ }
337
+ dump(): TemplateEasingData {
338
+ return {
339
+ type: EasingType.template,
340
+ identifier: this.name
341
+ }
342
+ }
343
+ get valueDelta(): number {
344
+ let seq = this.eventNodeSequence;
345
+ return seq.tail.previous.value - seq.head.next.value;
346
+ }
347
+ get headValue(): number {
348
+ return this.eventNodeSequence.head.next.value;
349
+ }
350
+ }
351
+
352
+
353
+
354
+
355
+ /**
356
+ * 缓动库
357
+ * 用于管理模板缓动
358
+ * for template easing management
359
+ * 谱面的一个属性
360
+ * a property of chart
361
+ * 加载谱面时,先加载事件序列,所需的模板缓动会被加入到缓动库,但并不立即实现,在读取模板缓动时,才实现缓动。
362
+ * To load a chart, the eventNodeSquences will be first loaded, during which process
363
+ * the easings will be added to the easing library but not implemented immediately.
364
+ * They will be implemented when the template easings are read from data.
365
+ *
366
+ */
367
+ export class TemplateEasingLib {
368
+ easings: {
369
+ [name: string]: TemplateEasing
370
+ }
371
+ constructor() {
372
+ this.easings = {};
373
+ }
374
+ getOrNew(name: string): TemplateEasing {
375
+ const DEFAULT_TEMPLATE_LENGTH = Environment.DEFAULT_TEMPLATE_LENGTH;
376
+ if (this.easings[name]) {
377
+ return this.easings[name];
378
+ } else {
379
+ const easing = new TemplateEasing(name, EventNodeSequence.newSeq(EventType.easing, DEFAULT_TEMPLATE_LENGTH));
380
+ easing.eventNodeSequence.id = "*" + name;
381
+ return this.easings[name] = easing;
382
+ }
383
+ }
384
+ /**
385
+ * 注册一个模板缓动,但不会实现它
386
+ * register a template easing when reading eventNodeSequences, but does not implement it immediately
387
+ */
388
+ require(name: string) {
389
+ this.easings[name] = new TemplateEasing(name, null);
390
+ }
391
+ implement(name: string, sequence: EventNodeSequence) {
392
+ this.easings[name].eventNodeSequence = sequence;
393
+ }
394
+ /**
395
+ * 检查所有模板缓动是否实现
396
+ * check if all easings are implemented
397
+ * 应当在读取完所有模板缓动后调用
398
+ * should be invoked after all template easings are read
399
+ */
400
+ check() {
401
+ for (let key in this.easings) {
402
+ if (!this.easings[key].eventNodeSequence) {
403
+ console.warn(`未实现的缓动:${key}`);
404
+ }
405
+ }
406
+ }
407
+ get(key: string): TemplateEasing | undefined {
408
+ return this.easings[key];
409
+ }
410
+
411
+ dump(eventNodeSequences: Set<EventNodeSequence>): CustomEasingData[] {
412
+ const customEasingDataList: CustomEasingData[] = [];
413
+ for (let key in this.easings) {
414
+ const templateEasing = this.easings[key];
415
+ const eventNodeSequence = templateEasing.eventNodeSequence;
416
+ if (eventNodeSequences.has(eventNodeSequence)) {
417
+ continue;
418
+ }
419
+ eventNodeSequences.add(eventNodeSequence);
420
+ customEasingDataList.push({
421
+ name: key,
422
+ content: eventNodeSequence.id, // 这里只存储编号,具体内容在保存时再编码
423
+ usedBy: [],
424
+ dependencies: []
425
+ });
426
+ }
427
+ return customEasingDataList;
428
+ }
429
+ }
430
+
431
+ export const linearEasing = new NormalEasing(linear, linearLine);
432
+ export const fixedEasing = new NormalEasing((x: number): number => (x === 1 ? 1 : 0));
433
+
434
+ export const easingMap = {
435
+ "fixed": {out: fixedEasing, in: fixedEasing, inout: fixedEasing},
436
+ "linear": {out: linearEasing, in: linearEasing, inout: linearEasing},
437
+ "sine": {in: new NormalEasing(easeInSine), out: new NormalEasing(easeOutSine), inout: new NormalEasing(easeInOutSine)},
438
+ "quad": {in: new NormalEasing(easeInQuad), out: new NormalEasing(easeOutQuad), inout: new NormalEasing(easeInOutQuad)},
439
+ "cubic": {in: new NormalEasing(easeInCubic), out: new NormalEasing(easeOutCubic), inout: new NormalEasing(easeInOutCubic)},
440
+ "quart": {in: new NormalEasing(easeInQuart), out: new NormalEasing(easeOutQuart), inout: new NormalEasing(easeInOutQuart)},
441
+ "quint": {in: new NormalEasing(easeInQuint), out: new NormalEasing(easeOutQuint), inout: new NormalEasing(easeInOutQuint)},
442
+ "expo": {in: new NormalEasing(easeInExpo), out: new NormalEasing(easeOutExpo), inout: new NormalEasing(easeInOutExpo)},
443
+ "circ": {in: new NormalEasing(easeInCirc), out: new NormalEasing(easeOutCirc), inout: new NormalEasing(easeInOutCirc)},
444
+ "back": {in: new NormalEasing(easeInBack), out: new NormalEasing(easeOutBack), inout: new NormalEasing(easeInOutBack)},
445
+ "elastic": {in: new NormalEasing(easeInElastic), out: new NormalEasing(easeOutElastic), inout: new NormalEasing(easeInOutElastic)},
446
+ "bounce": {in: new NormalEasing(easeInBounce), out: new NormalEasing(easeOutBounce), inout: new NormalEasing(easeInOutBounce)}
447
+ }
448
+
449
+ for (let funcType in easingMap) {
450
+ for (let easeType in easingMap[funcType]) {
451
+ const easing = easingMap[funcType][easeType];
452
+ easing.funcType = funcType;
453
+ easing.easeType = easeType;
454
+ }
455
+ }
456
+ fixedEasing.funcType = "fixed";
457
+ fixedEasing.easeType = "in"
458
+
459
+ /**
460
+ * 按照KPA的编号
461
+ */
462
+ export const easingArray = [
463
+ fixedEasing,
464
+ linearEasing,
465
+ easingMap.sine.out,
466
+ easingMap.sine.in,
467
+ easingMap.sine.inout,
468
+ easingMap.quad.out,
469
+ easingMap.quad.in,
470
+ easingMap.quad.inout,
471
+ easingMap.cubic.out,
472
+ easingMap.cubic.in,
473
+ easingMap.cubic.inout,
474
+ easingMap.quart.out,
475
+ easingMap.quart.in,
476
+ easingMap.quart.inout,
477
+ easingMap.quint.out,
478
+ easingMap.quint.in,
479
+ easingMap.quint.inout,
480
+ easingMap.circ.out,
481
+ easingMap.circ.in,
482
+ easingMap.circ.inout,
483
+ easingMap.expo.out,
484
+ easingMap.expo.in,
485
+ easingMap.expo.inout,
486
+ easingMap.back.out,
487
+ easingMap.back.in,
488
+ easingMap.back.inout,
489
+ easingMap.elastic.out,
490
+ easingMap.elastic.in,
491
+ easingMap.elastic.inout,
492
+ easingMap.bounce.out,
493
+ easingMap.bounce.in,
494
+ easingMap.bounce.inout
495
+ ]
496
+
497
+ easingArray.forEach((easing, index) => {
498
+ easing.id = index;
499
+ })
500
+
501
+ export const rpeEasingArray = [
502
+ null,
503
+ linearEasing, // 1
504
+ easingMap.sine.out, // 2
505
+ easingMap.sine.in, // 3
506
+ easingMap.quad.out, // 4
507
+ easingMap.quad.in, // 5
508
+ easingMap.sine.inout, // 6
509
+ easingMap.quad.inout, // 7
510
+ easingMap.cubic.out, // 8
511
+ easingMap.cubic.in, // 9
512
+ easingMap.quart.out, // 10
513
+ easingMap.quart.in, // 11
514
+ easingMap.cubic.inout, // 12
515
+ easingMap.quart.inout, // 13
516
+ easingMap.quint.out, // 14
517
+ easingMap.quint.in, // 15
518
+ // easingMap.quint.inout,
519
+ easingMap.expo.out, // 16
520
+ easingMap.expo.in, // 17
521
+ // easingMap.expo.inout,
522
+ easingMap.circ.out, // 18
523
+ easingMap.circ.in, // 19
524
+ easingMap.back.out, // 20
525
+ easingMap.back.in, // 21
526
+ easingMap.circ.inout, // 22
527
+ easingMap.back.inout, // 23
528
+ easingMap.elastic.out, // 24
529
+ easingMap.elastic.in, // 25
530
+ easingMap.bounce.out, // 26
531
+ easingMap.bounce.in, // 27
532
+ easingMap.bounce.inout, //28
533
+ easingMap.elastic.inout // 29
534
+ ]
535
+
536
+ rpeEasingArray.forEach((easing, index) => {
537
+ if (!easing) {
538
+ return;
539
+ }
540
+ easing.rpeId = index;
541
+ })
542
+
543
+ /// #enddeclaration
package/env.ts ADDED
@@ -0,0 +1,7 @@
1
+
2
+ export default {
3
+ DEFAULT_TEMPLATE_LENGTH: 16,
4
+ BEZIER_INTERPOLATION_DENSITY: 256,
5
+ NNLIST_Y_OFFSET_HALF_SPAN: 100,
6
+ freeze() { Object.freeze(this); }
7
+ }