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/evaluator.ts ADDED
@@ -0,0 +1,173 @@
1
+ import {
2
+ Easing,
3
+ linearEasing,
4
+ NormalEasing,
5
+ rpeEasingArray,
6
+ } from "./easing";
7
+
8
+
9
+ import { TimeCalculator as TC } from "./time"
10
+
11
+ import type { Chart } from "./chart";
12
+ import type { EventEndNode, EventStartNode } from "./event";
13
+ import { EvaluatorType, EventValueType, InterpreteAs, type ColorEasedEvaluatorKPA2, type EasedEvaluatorDataOfType, type EvaluatorDataKPA2, type EventValueESType, type EventValueTypeOfType, type ExpressionEvaluatorDataKPA2, type NumericEasedEvaluatorKPA2, type RGB, type TextEasedEvaluatorKPA2 } from "./chartTypes";
14
+
15
+
16
+ /// #declaration:global
17
+
18
+ /**
19
+ * **求值器**
20
+ *
21
+ * 基于给定的事件和拍数,返回事件在此点的值。
22
+ *
23
+ * 被求值的事件必须是拥有下一个节点的事件。序列尾部的节点(与尾节点相连的节点)不能被求值。
24
+ * @immutable
25
+ * @since 2.0.0
26
+ */
27
+ export abstract class Evaluator<T> {
28
+ abstract eval(event: EventStartNode<T>, beats: number): T;
29
+ abstract dump(): EvaluatorDataKPA2<T>;
30
+ }
31
+
32
+
33
+ export abstract class EasedEvaluator<T> extends Evaluator<T> {
34
+ readonly easing: Easing;
35
+ constructor(easing: Easing) {
36
+ super();
37
+ this.easing = easing;
38
+ }
39
+ override eval(startNode: EventStartNode<T> & { next: EventEndNode }, beats: number): T {
40
+ const next = startNode.next;
41
+ const timeDelta = TC.getDelta(next.time, startNode.time)
42
+ const current = beats - TC.toBeats(startNode.time)
43
+ const nextValue = startNode.next.value;
44
+ const value = startNode.value;
45
+ if (nextValue === value) {
46
+ return value;
47
+ }
48
+ // 其他类型,包括普通缓动和非钩定模板缓动
49
+ return this.convert(value, nextValue, this.easing.getValue(current / timeDelta));
50
+ }
51
+ abstract convert(start: T, end: T, t: number): T;
52
+ }
53
+
54
+ export type EasedEvaluatorOfType<T extends EventValueESType> = T extends number ? NumericEasedEvaluator : T extends RGB ? ColorEasedEvaluator : TextEasedEvaluator;
55
+ export type EasedEvaluatorConstructorOfType<T extends EventValueESType> = T extends number ? typeof NumericEasedEvaluator : T extends RGB ? typeof ColorEasedEvaluator : typeof TextEasedEvaluator;
56
+
57
+ export class NumericEasedEvaluator extends EasedEvaluator<number> {
58
+ constructor(easing: Easing) {
59
+ super(easing);
60
+ }
61
+ private cache?: NumericEasedEvaluatorKPA2
62
+ override dump(): NumericEasedEvaluatorKPA2 {
63
+ return this.cache ??= {
64
+ type: EvaluatorType.eased,
65
+ easing: this.easing.dump()
66
+ }
67
+ }
68
+ override convert(start: number, end: number, progress: number): number {
69
+ return start + progress * (end - start);
70
+ }
71
+ static default = new NumericEasedEvaluator(linearEasing);
72
+ static evaluatorsOfNormalEasing: NumericEasedEvaluator[] = rpeEasingArray.map(easing => new NumericEasedEvaluator(easing));
73
+ }
74
+
75
+ export class ColorEasedEvaluator extends EasedEvaluator<RGB> {
76
+ constructor(easing: Easing) {
77
+ super(easing);
78
+ }
79
+ override dump(): ColorEasedEvaluatorKPA2 {
80
+ return {
81
+ type: EvaluatorType.eased,
82
+ easing: this.easing.dump()
83
+ }
84
+ }
85
+ override convert(start: RGB, end: RGB, progress: number): RGB {
86
+ const r = start[0] === end[0] ? start[0] : start[0] + (end[0] - start[0]) * progress;
87
+ const g = start[1] === end[1] ? start[1] : start[1] + (end[1] - start[1]) * progress;
88
+ const b = start[2] === end[2] ? start[2] : start[2] + (end[2] - start[2]) * progress;
89
+ return [r, g, b];
90
+ }
91
+ static default = new ColorEasedEvaluator(linearEasing);
92
+ static evaluatorsOfNormalEasing: ColorEasedEvaluator[] = rpeEasingArray.map(easing => new ColorEasedEvaluator(easing));
93
+ }
94
+
95
+
96
+ /**
97
+ * 文本缓动求值器
98
+ *
99
+ * 文本缓动求值器可以将文本解读为字符串、浮点数和整形。
100
+ *
101
+ * 行为与RPE的`%P%`相似
102
+ */
103
+ export class TextEasedEvaluator extends EasedEvaluator<string> {
104
+ constructor(easing: Easing,
105
+ public readonly interpretedAs: InterpreteAs = InterpreteAs.str,
106
+ public readonly font: string = "cmdysj.ttf"
107
+ )
108
+ {
109
+ super(easing);
110
+ }
111
+ override dump(): TextEasedEvaluatorKPA2 {
112
+ return {
113
+ type: EvaluatorType.eased,
114
+ easing: this.easing.dump(),
115
+ interpretedAs: this.interpretedAs,
116
+ font: this.font
117
+ }
118
+ }
119
+ override convert(value: string, nextValue: string, progress: number): string {
120
+ const interpretedAs = this.interpretedAs;
121
+ if (interpretedAs === InterpreteAs.float) {
122
+ const start = parseFloat(value);
123
+ const delta = parseFloat(nextValue as string) - start;
124
+ return start + progress * delta + "";
125
+ } else if (interpretedAs === InterpreteAs.int) {
126
+ const start = parseInt(value);
127
+ const delta = parseInt(nextValue as string) - start;
128
+ return start + Math.round(progress * delta) + "";
129
+ } else
130
+ if (value.startsWith(nextValue as string)) {
131
+ const startLen = (nextValue as string).length;
132
+ const deltaLen = value.length - startLen;
133
+ const len = startLen + Math.floor(deltaLen * progress);
134
+ return value.substring(0, len);
135
+ } else if ((nextValue as string).startsWith(value)) {
136
+ const startLen = value.length;
137
+ const deltaLen = (nextValue as string).length - startLen;
138
+ const len = startLen + Math.floor(deltaLen * progress);
139
+ return (nextValue as string).substring(0, len);
140
+ }
141
+ else {
142
+ return value;
143
+ }
144
+ }
145
+ static default = new TextEasedEvaluator(linearEasing, InterpreteAs.str);
146
+ static evaluatorsOfNoEzAndItpAs: TextEasedEvaluator[][] = rpeEasingArray.map(easing => [
147
+ new TextEasedEvaluator(easing, InterpreteAs.str),
148
+ new TextEasedEvaluator(easing, InterpreteAs.int),
149
+ new TextEasedEvaluator(easing, InterpreteAs.float),
150
+ ]);
151
+ }
152
+
153
+ export class ExpressionEvaluator<T> extends Evaluator<T> {
154
+ readonly func: (t: number) => T;
155
+ constructor(public readonly jsExpr: string) {
156
+ super();
157
+ this.func = new Function("t", "return " + jsExpr) as (t: number) => T;
158
+ }
159
+ override eval(startNode: EventStartNode<T> & { next: EventEndNode }, beats: number): T {
160
+ const next = startNode.next;
161
+ const timeDelta = TC.getDelta(next.time, startNode.time)
162
+ const current = beats - TC.toBeats(startNode.time)
163
+ return this.func(current / timeDelta);
164
+ }
165
+ override dump(): ExpressionEvaluatorDataKPA2 {
166
+ return {
167
+ type: EvaluatorType.expressionbased,
168
+ jsExpr: this.jsExpr
169
+ }
170
+ }
171
+ }
172
+
173
+ /// #enddeclaration