kipphi 2.0.0 → 2.0.1
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/bpm.ts +213 -0
- package/chart.ts +172 -80
- package/chartTypes.ts +62 -20
- package/easing.ts +89 -83
- package/env.ts +173 -0
- package/evaluator.ts +35 -11
- package/event.ts +273 -225
- package/index.ts +14 -11
- package/judgeline.ts +381 -84
- package/jumparray.ts +11 -11
- package/note.ts +31 -54
- package/operation.ts +1378 -0
- package/package.json +1 -1
- package/rpeChartCompiler.ts +133 -98
- package/time.ts +35 -223
- package/tsconfig.json +1 -2
- package/util.ts +21 -1
- package/version.ts +1 -1
package/judgeline.ts
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
import type { Chart, JudgeLineGroup } from "./chart";
|
|
2
|
-
import { EventType, NoteType, type EventDataRPELike, type EventLayerDataKPA, type JudgeLineDataKPA, type JudgeLineDataRPE, type NNListDataKPA, type NoteDataRPE, type RGB, type TimeT, type ValueTypeOfEventType } from "./chartTypes";
|
|
2
|
+
import { EventType, EventValueType, JudgeLineDataKPA2, NoteType, type EventDataRPELike, type EventLayerDataKPA, type JudgeLineDataKPA, type JudgeLineDataRPE, type NNListDataKPA, type NoteDataRPE, type RGB, type TimeT, type ValueTypeOfEventType } from "./chartTypes";
|
|
3
3
|
import type { TemplateEasingLib } from "./easing";
|
|
4
|
-
import { EventEndNode, EventNodeLike, EventNodeSequence, EventStartNode } from "./event";
|
|
4
|
+
import { EventEndNode, EventNode, EventNodeLike, EventNodeSequence, EventStartNode, Monotonicity, SpeedENS } from "./event";
|
|
5
5
|
import { HNList, NNList, Note, NoteNode, NNNList } from "./note";
|
|
6
|
-
import { TimeCalculator } from "./
|
|
6
|
+
import { type TimeCalculator } from "./bpm";
|
|
7
|
+
import TC from "./time";
|
|
7
8
|
import { NodeType } from "./util";
|
|
8
|
-
import Environment from "./env";
|
|
9
|
+
import Environment, { err } from "./env";
|
|
9
10
|
/// #declaration:global
|
|
10
|
-
const TC = TimeCalculator
|
|
11
11
|
|
|
12
12
|
export interface EventLayer {
|
|
13
13
|
moveX?: EventNodeSequence;
|
|
14
14
|
moveY?: EventNodeSequence;
|
|
15
15
|
rotate?: EventNodeSequence;
|
|
16
16
|
alpha?: EventNodeSequence;
|
|
17
|
-
speed?: EventNodeSequence;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
export interface ExtendedLayer {
|
|
@@ -34,6 +33,9 @@ const getRangeMedian = (yOffset: number) => {
|
|
|
34
33
|
const NNLIST_Y_OFFSET_HALF_SPAN = Environment.NNLIST_Y_OFFSET_HALF_SPAN
|
|
35
34
|
return (Math.floor((Math.abs(yOffset) - NNLIST_Y_OFFSET_HALF_SPAN) / NNLIST_Y_OFFSET_HALF_SPAN / 2) * (NNLIST_Y_OFFSET_HALF_SPAN * 2) + NNLIST_Y_OFFSET_HALF_SPAN * 2) * Math.sign(yOffset);
|
|
36
35
|
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
37
39
|
export class JudgeLine {
|
|
38
40
|
texture: string;
|
|
39
41
|
group: JudgeLineGroup;
|
|
@@ -41,6 +43,7 @@ export class JudgeLine {
|
|
|
41
43
|
hnLists = new Map<string, HNList>();
|
|
42
44
|
nnLists = new Map<string, NNList>();
|
|
43
45
|
eventLayers: EventLayer[] = [];
|
|
46
|
+
speedSequence: SpeedENS;
|
|
44
47
|
extendedLayer: ExtendedLayer = {};
|
|
45
48
|
// notePosition: Float64Array;
|
|
46
49
|
// noteSpeeds: NoteSpeeds;
|
|
@@ -63,8 +66,11 @@ export class JudgeLine {
|
|
|
63
66
|
|
|
64
67
|
/**
|
|
65
68
|
* 每帧渲染时所用的变换矩阵,缓存下来用于之后的UI绑定渲染
|
|
69
|
+
*
|
|
70
|
+
* 已移除,谱面NPM包不需要这个,播放器侧如要使用,可以用同名接口扩展此类
|
|
71
|
+
* @removed since 2.0.1
|
|
66
72
|
*/
|
|
67
|
-
renderMatrix: Matrix;
|
|
73
|
+
// renderMatrix: Matrix;
|
|
68
74
|
|
|
69
75
|
rotatesWithFather: boolean = false;
|
|
70
76
|
|
|
@@ -79,7 +85,7 @@ export class JudgeLine {
|
|
|
79
85
|
// this.noteSpeeds = {};
|
|
80
86
|
}
|
|
81
87
|
static fromRPEJSON(chart: Chart, id: number, data: JudgeLineDataRPE, templates: TemplateEasingLib, timeCalculator: TimeCalculator) {
|
|
82
|
-
|
|
88
|
+
const line = new JudgeLine(chart)
|
|
83
89
|
line.id = id;
|
|
84
90
|
line.name = data.Name;
|
|
85
91
|
chart.judgeLineGroups[data.Group].add(line);
|
|
@@ -102,15 +108,15 @@ export class JudgeLine {
|
|
|
102
108
|
if (data.notes) {
|
|
103
109
|
const holdLists = line.hnLists;
|
|
104
110
|
const noteLists = line.nnLists;
|
|
105
|
-
|
|
111
|
+
const notes = data.notes;
|
|
106
112
|
notes.sort((n1: NoteDataRPE, n2: NoteDataRPE) => {
|
|
107
|
-
if (
|
|
108
|
-
return
|
|
113
|
+
if (TC.ne(n1.startTime, n2.startTime)) {
|
|
114
|
+
return TC.gt(n1.startTime, n2.startTime) ? 1 : -1
|
|
109
115
|
}
|
|
110
|
-
return
|
|
116
|
+
return TC.gt(n1.endTime, n2.endTime) ? -1 : 1 // 这里曾经排反了(
|
|
111
117
|
})
|
|
112
118
|
const len = notes.length;
|
|
113
|
-
let lastTime: TimeT = [-1, 0, 1];
|
|
119
|
+
// let lastTime: TimeT = [-1, 0, 1];
|
|
114
120
|
// let comboInfoEntity: ComboInfoEntity;
|
|
115
121
|
|
|
116
122
|
for (let i = 0; i < len; i++) {
|
|
@@ -119,7 +125,7 @@ export class JudgeLine {
|
|
|
119
125
|
const tree = line.getNNList(note.speed, note.yOffset, note.type === NoteType.hold, false)
|
|
120
126
|
const cur = tree.currentPoint
|
|
121
127
|
const lastHoldTime: TimeT = cur.type === NodeType.HEAD ? [-1, 0, 1] : cur.startTime
|
|
122
|
-
if (
|
|
128
|
+
if (TC.eq(lastHoldTime, note.startTime)) {
|
|
123
129
|
(tree.currentPoint as NoteNode).add(note)
|
|
124
130
|
} else {
|
|
125
131
|
const node = new NoteNode(note.startTime)
|
|
@@ -130,8 +136,8 @@ export class JudgeLine {
|
|
|
130
136
|
}
|
|
131
137
|
tree.timesWithNotes++
|
|
132
138
|
}
|
|
133
|
-
for (
|
|
134
|
-
for (const [_, list] of
|
|
139
|
+
for (const lists of [holdLists, noteLists]) {
|
|
140
|
+
for (const [_, list] of lists) {
|
|
135
141
|
NoteNode.connect(list.currentPoint, list.tail)
|
|
136
142
|
list.initJump();
|
|
137
143
|
// tree.initPointers()
|
|
@@ -142,20 +148,23 @@ export class JudgeLine {
|
|
|
142
148
|
const length = eventLayers.length;
|
|
143
149
|
const createSequence = (type: EventType, events: EventDataRPELike[], index: number) => {
|
|
144
150
|
if (events) {
|
|
145
|
-
const
|
|
146
|
-
sequence
|
|
151
|
+
const seqId = `#${id}.${index}.${EventType[type]}`;
|
|
152
|
+
const sequence = EventNodeSequence.fromRPEJSON(type, events, chart, seqId);
|
|
153
|
+
sequence.id = seqId
|
|
147
154
|
chart.sequenceMap.set(sequence.id, sequence);
|
|
148
155
|
return sequence;
|
|
149
156
|
}
|
|
150
157
|
}
|
|
151
158
|
const createExtendedSequence = <T extends EventType>(type: T, events: EventDataRPELike<ValueTypeOfEventType<T>>[]) => {
|
|
152
159
|
if (events) {
|
|
153
|
-
const
|
|
154
|
-
sequence
|
|
160
|
+
const seqId = `#${id}.ex.${EventType[type]}`;
|
|
161
|
+
const sequence = EventNodeSequence.fromRPEJSON(type, events, chart, seqId);
|
|
162
|
+
sequence.id = seqId;
|
|
155
163
|
chart.sequenceMap.set(sequence.id, sequence);
|
|
156
164
|
return sequence;
|
|
157
165
|
}
|
|
158
166
|
}
|
|
167
|
+
const speedSequences: SpeedENS[] = [];
|
|
159
168
|
for (let index = 0; index < length; index++) {
|
|
160
169
|
const layerData = eventLayers[index];
|
|
161
170
|
if (!layerData) {
|
|
@@ -165,11 +174,20 @@ export class JudgeLine {
|
|
|
165
174
|
moveX: createSequence(EventType.moveX, layerData.moveXEvents, index),
|
|
166
175
|
moveY: createSequence(EventType.moveY, layerData.moveYEvents, index),
|
|
167
176
|
rotate: createSequence(EventType.rotate, layerData.rotateEvents, index),
|
|
168
|
-
alpha: createSequence(EventType.alpha, layerData.alphaEvents, index)
|
|
169
|
-
speed: createSequence(EventType.speed, layerData.speedEvents, index)
|
|
177
|
+
alpha: createSequence(EventType.alpha, layerData.alphaEvents, index)
|
|
170
178
|
};
|
|
179
|
+
if (layerData.speedEvents) {
|
|
180
|
+
const seq = createSequence(EventType.speed, layerData.speedEvents, index);
|
|
181
|
+
speedSequences.push(seq as SpeedENS);
|
|
182
|
+
}
|
|
171
183
|
line.eventLayers[index] = layer;
|
|
172
184
|
}
|
|
185
|
+
if (speedSequences.length > 1) {
|
|
186
|
+
line.speedSequence = EventNodeSequence.mergeSequences(speedSequences) as SpeedENS;
|
|
187
|
+
line.speedSequence.updateFloorPositionAfter(line.speedSequence.head.next, timeCalculator);
|
|
188
|
+
} else {
|
|
189
|
+
line.speedSequence = chart.createEventNodeSequence(EventType.speed, `#${id}.speed`) as SpeedENS;
|
|
190
|
+
}
|
|
173
191
|
if (data.extended) {
|
|
174
192
|
if (data.extended.scaleXEvents) {
|
|
175
193
|
line.extendedLayer.scaleX = createExtendedSequence(EventType.scaleX, data.extended.scaleXEvents);
|
|
@@ -192,58 +210,118 @@ export class JudgeLine {
|
|
|
192
210
|
// line.computeNotePositionY(timeCalculator);
|
|
193
211
|
return line;
|
|
194
212
|
}
|
|
195
|
-
static fromKPAJSON(
|
|
196
|
-
|
|
213
|
+
static fromKPAJSON(
|
|
214
|
+
version: number,
|
|
215
|
+
chart: Chart,
|
|
216
|
+
id: number,
|
|
217
|
+
data: JudgeLineDataKPA | JudgeLineDataKPA2,
|
|
218
|
+
templates: TemplateEasingLib,
|
|
219
|
+
timeCalculator: TimeCalculator
|
|
220
|
+
)
|
|
221
|
+
{
|
|
222
|
+
const withNewEventStructure = version >= 150;
|
|
223
|
+
const independentSpeedENS = version >= 201;
|
|
224
|
+
const lowerCaseNameAndTexture = version >= 201;
|
|
225
|
+
|
|
226
|
+
const line = new JudgeLine(chart)
|
|
197
227
|
line.id = id;
|
|
198
|
-
line.name = data.Name;
|
|
228
|
+
line.name = lowerCaseNameAndTexture ? (data as JudgeLineDataKPA2).name : (data as JudgeLineDataKPA).Name;
|
|
199
229
|
line.rotatesWithFather = data.rotatesWithFather;
|
|
200
230
|
line.anchor = data.anchor ?? [0.5, 0.5];
|
|
201
|
-
line.texture = data.Texture
|
|
231
|
+
line.texture = (lowerCaseNameAndTexture ? (data as JudgeLineDataKPA2).texture : (data as JudgeLineDataKPA).Texture) ?? "line.png";
|
|
202
232
|
line.cover = data.cover ?? true;
|
|
203
233
|
line.zOrder = data.zOrder ?? 0;
|
|
204
234
|
|
|
205
235
|
|
|
236
|
+
|
|
206
237
|
chart.judgeLineGroups[data.group].add(line);
|
|
207
238
|
const nnnList = chart.nnnList;
|
|
208
|
-
for (
|
|
239
|
+
for (const isHold of [false, true]) {
|
|
209
240
|
const key: "hnLists" | "nnLists" = `${isHold ? "hn" : "nn"}Lists`
|
|
210
|
-
const
|
|
211
|
-
for (
|
|
212
|
-
const listData =
|
|
213
|
-
if (
|
|
241
|
+
const listsData: Record<string, NNListDataKPA>= data[key];
|
|
242
|
+
for (const name in listsData) {
|
|
243
|
+
const listData = listsData[name];
|
|
244
|
+
if (withNewEventStructure) {
|
|
214
245
|
|
|
215
246
|
const list = NNList.fromKPAJSON(isHold, chart.effectiveBeats, listData, nnnList, timeCalculator);
|
|
216
247
|
list.parentLine = line;
|
|
217
|
-
list.id = name
|
|
248
|
+
list.id = name;
|
|
249
|
+
// isHold为真则lists为Map<string, HNList>,且list为HNList,能够匹配
|
|
250
|
+
// @ts-expect-error 这里我也不知道怎么让它知道这里是匹配的
|
|
218
251
|
line[key].set(name, list);
|
|
219
252
|
} else {
|
|
220
253
|
line.getNNListFromOldKPAJSON(line[key], name, isHold, chart.effectiveBeats, listData, nnnList, timeCalculator);
|
|
221
254
|
}
|
|
222
255
|
}
|
|
223
256
|
}
|
|
224
|
-
for (
|
|
225
|
-
line.children.add(JudgeLine.fromKPAJSON(
|
|
257
|
+
for (const child of data.children) {
|
|
258
|
+
line.children.add(JudgeLine.fromKPAJSON(version, chart, child.id, child, templates, timeCalculator));
|
|
259
|
+
}
|
|
260
|
+
const unwrap = <VT>(sequence: EventNodeSequence<unknown>, predicate: (value: unknown) => boolean, typeStr: keyof typeof EventValueType) => {
|
|
261
|
+
const value = sequence.head.next.value
|
|
262
|
+
if (!predicate(value)) {
|
|
263
|
+
throw err.EXPECTED_TYPED_ENS(typeStr, sequence.id, value);
|
|
264
|
+
}
|
|
265
|
+
return sequence as EventNodeSequence<VT>;
|
|
226
266
|
}
|
|
227
|
-
for (
|
|
228
|
-
|
|
229
|
-
for (
|
|
267
|
+
for (const eventLayerData of data.eventLayers) {
|
|
268
|
+
const eventLayer: EventLayer = {} as EventLayer;
|
|
269
|
+
for (const key in eventLayerData) {
|
|
230
270
|
// use "fromRPEJSON" for they have the same logic
|
|
231
|
-
eventLayer[key] = chart.sequenceMap.get(eventLayerData[key]);
|
|
271
|
+
eventLayer[key] = unwrap(chart.sequenceMap.get(eventLayerData[key]), v => typeof v === "number", "numeric");
|
|
232
272
|
}
|
|
233
273
|
line.eventLayers.push(eventLayer);
|
|
234
274
|
}
|
|
275
|
+
if (independentSpeedENS) {
|
|
276
|
+
const seq = unwrap(
|
|
277
|
+
chart.sequenceMap.get((data as JudgeLineDataKPA2).speedEventNodeSeq),
|
|
278
|
+
v => typeof v === "number",
|
|
279
|
+
"numeric"
|
|
280
|
+
);
|
|
281
|
+
line.speedSequence = seq as SpeedENS;
|
|
282
|
+
} else {
|
|
283
|
+
// 合并多个层级上的速度序列,合并完以后置空
|
|
284
|
+
const oldSequences: SpeedENS[] = [];
|
|
285
|
+
for (const layer of line.eventLayers) {
|
|
286
|
+
const ly = layer as {speed: SpeedENS};
|
|
287
|
+
if (ly.speed) {
|
|
288
|
+
oldSequences.push(ly.speed);
|
|
289
|
+
ly.speed = null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
let seq: EventNodeSequence<number>;
|
|
293
|
+
if (oldSequences.length > 0) {
|
|
294
|
+
|
|
295
|
+
seq = EventNodeSequence.mergeSequences(
|
|
296
|
+
oldSequences
|
|
297
|
+
);
|
|
298
|
+
(seq as SpeedENS).updateFloorPositionAfter((seq as SpeedENS).head.next, timeCalculator);
|
|
299
|
+
} else {
|
|
300
|
+
seq = chart.createEventNodeSequence(EventType.speed, `#${line.id}.speed`);
|
|
301
|
+
|
|
302
|
+
}
|
|
303
|
+
line.speedSequence = seq as SpeedENS;
|
|
304
|
+
}
|
|
235
305
|
line.extendedLayer.scaleX = data.extended?.scaleXEvents
|
|
236
|
-
? chart.sequenceMap.get(
|
|
306
|
+
? unwrap(chart.sequenceMap.get(
|
|
307
|
+
data.extended.scaleXEvents),
|
|
308
|
+
v => typeof v === "number",
|
|
309
|
+
"numeric"
|
|
310
|
+
)
|
|
237
311
|
: chart.createEventNodeSequence(EventType.scaleX, `#${line.id}.ex.scaleX`);
|
|
238
312
|
line.extendedLayer.scaleY = data.extended?.scaleYEvents
|
|
239
|
-
? chart.sequenceMap.get(
|
|
313
|
+
? unwrap(chart.sequenceMap.get(
|
|
314
|
+
data.extended.scaleYEvents),
|
|
315
|
+
v => typeof v === "number",
|
|
316
|
+
"numeric"
|
|
317
|
+
)
|
|
240
318
|
: chart.createEventNodeSequence(EventType.scaleY, `#${line.id}.ex.scaleY`);
|
|
241
319
|
if (data.extended) {
|
|
242
320
|
if (data.extended.textEvents) {
|
|
243
|
-
line.extendedLayer.text = chart.sequenceMap.get(data.extended.textEvents);
|
|
321
|
+
line.extendedLayer.text = unwrap(chart.sequenceMap.get(data.extended.textEvents), v => typeof v === "string", "text");
|
|
244
322
|
}
|
|
245
323
|
if (data.extended.colorEvents) {
|
|
246
|
-
line.extendedLayer.color = chart.sequenceMap.get(data.extended.colorEvents);
|
|
324
|
+
line.extendedLayer.color = unwrap(chart.sequenceMap.get(data.extended.colorEvents), v => Array.isArray(v), "color");
|
|
247
325
|
}
|
|
248
326
|
}
|
|
249
327
|
|
|
@@ -302,27 +380,48 @@ export class JudgeLine {
|
|
|
302
380
|
return this.eventLayers[index];
|
|
303
381
|
}
|
|
304
382
|
}
|
|
383
|
+
/*
|
|
384
|
+
应该是古老代码,现在不用了,暂时不移除
|
|
305
385
|
updateSpeedIntegralFrom(beats: number, timeCalculator: TimeCalculator) {
|
|
306
|
-
for (
|
|
386
|
+
for (const eventLayer of this.eventLayers) {
|
|
307
387
|
eventLayer?.speed?.updateNodesIntegralFrom(beats, timeCalculator);
|
|
308
388
|
}
|
|
309
389
|
}
|
|
390
|
+
//*/
|
|
310
391
|
/**
|
|
392
|
+
* 判定线当前所在的FloorPosition
|
|
393
|
+
*
|
|
394
|
+
* 可以理解为:有一个假想的充满音符的瀑布流,判定线在其中或前进或后退
|
|
395
|
+
*/
|
|
396
|
+
currentFloorPosition: number;
|
|
397
|
+
cachedFloorPositions: Float64Array;
|
|
398
|
+
computeCurrentFloorPosition(beats: number, timeCalculator: TimeCalculator) {
|
|
399
|
+
this.currentFloorPosition = this.speedSequence.getFloorPositionAt(beats, timeCalculator);
|
|
400
|
+
}
|
|
401
|
+
getRelativeFloorPositionAt(beats: number, timeCalculator: TimeCalculator) {
|
|
402
|
+
return this.speedSequence.getFloorPositionAt(beats, timeCalculator) - this.currentFloorPosition;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* 通过速度序列的FloorPosition反解出一个时间范围。
|
|
406
|
+
*
|
|
407
|
+
* KPA内核代码中最大的一坨史山,没有之一。
|
|
408
|
+
*
|
|
409
|
+
* 谱面渲染时最耗时的函数
|
|
410
|
+
*
|
|
311
411
|
* startY and endY must not be negative
|
|
312
412
|
* @param beats
|
|
313
413
|
* @param timeCalculator
|
|
314
414
|
* @param startY
|
|
315
415
|
* @param endY
|
|
316
416
|
* @returns
|
|
317
|
-
|
|
417
|
+
* /
|
|
318
418
|
computeTimeRange(beats: number, timeCalculator: TimeCalculator , startY: number, endY: number): [number, number][] {
|
|
319
|
-
console.log("invoked")
|
|
320
419
|
//return [[0, Infinity]]
|
|
321
420
|
//*
|
|
322
421
|
// 提取所有有变化的时间点
|
|
323
422
|
let times: number[] = [];
|
|
324
|
-
|
|
325
|
-
for (
|
|
423
|
+
const result: [number, number][] = [];
|
|
424
|
+
for (const eventLayer of this.eventLayers) {
|
|
326
425
|
const sequence = eventLayer?.speed;
|
|
327
426
|
if (!sequence) {
|
|
328
427
|
continue;
|
|
@@ -330,7 +429,7 @@ export class JudgeLine {
|
|
|
330
429
|
let node: EventStartNode = sequence.getNodeAt(beats);
|
|
331
430
|
let endNode: EventEndNode | EventNodeLike<NodeType.TAIL>
|
|
332
431
|
while (true) {
|
|
333
|
-
times.push(
|
|
432
|
+
times.push(TC.toBeats(node.time))
|
|
334
433
|
if ((endNode = node.next).type === NodeType.TAIL) {
|
|
335
434
|
break;
|
|
336
435
|
}
|
|
@@ -341,7 +440,7 @@ export class JudgeLine {
|
|
|
341
440
|
times = [...new Set(times)].sort((a, b) => a - b)
|
|
342
441
|
const len = times.length;
|
|
343
442
|
let nextTime = times[0]
|
|
344
|
-
let nextPosY = this.
|
|
443
|
+
let nextPosY = this.getStackedFloorPosition(nextTime, timeCalculator)
|
|
345
444
|
let nextSpeed = this.getStackedValue("speed", nextTime, true)
|
|
346
445
|
let range: [number, number] = [undefined, undefined];
|
|
347
446
|
// console.log(times)
|
|
@@ -354,14 +453,14 @@ export class JudgeLine {
|
|
|
354
453
|
thisSpeed = 0; // 不这样做可能导致下面异号判断为真从而死循环
|
|
355
454
|
}
|
|
356
455
|
nextTime = times[i + 1]
|
|
357
|
-
nextPosY = this.
|
|
456
|
+
nextPosY = this.getStackedFloorPosition(nextTime, timeCalculator);
|
|
358
457
|
nextSpeed = this.getStackedValue("speed", nextTime, true)
|
|
359
458
|
// console.log(thisSpeed, nextSpeed, thisSpeed * nextSpeed < 0, i, [...result])
|
|
360
459
|
if (thisSpeed * nextSpeed < 0) { // 有变号零点,再次切断,保证处理的每个区间单调性
|
|
361
460
|
//debugger;
|
|
362
461
|
nextTime = (nextTime - thisTime) * (0 - thisSpeed) / (nextSpeed - thisSpeed) + thisTime;
|
|
363
462
|
nextSpeed = 0
|
|
364
|
-
nextPosY = this.
|
|
463
|
+
nextPosY = this.getStackedFloorPosition(nextTime, timeCalculator)
|
|
365
464
|
//debugger
|
|
366
465
|
} else {
|
|
367
466
|
// console.log("i++")
|
|
@@ -377,7 +476,7 @@ export class JudgeLine {
|
|
|
377
476
|
或a > e >= b
|
|
378
477
|
全部在区间内
|
|
379
478
|
s <= a <= b
|
|
380
|
-
|
|
479
|
+
* /
|
|
381
480
|
if (thisPosY < startY && startY <= nextPosY
|
|
382
481
|
|| thisPosY > endY && endY >= nextPosY) {
|
|
383
482
|
range[0] = thisSpeed !== nextSpeed ? thisTime : computeTime(
|
|
@@ -393,12 +492,6 @@ export class JudgeLine {
|
|
|
393
492
|
range[1] = thisSpeed !== nextSpeed ? nextTime : computeTime(
|
|
394
493
|
thisSpeed,
|
|
395
494
|
(thisPosY > nextPosY ? startY : endY) - thisPosY, thisTime)
|
|
396
|
-
if (range[0] > range[1]){
|
|
397
|
-
console.error("range start should be smaller than range end.")
|
|
398
|
-
console.log("\nRange is:", range, "thisTime:", thisTime, "thisSpeed:", thisSpeed, "thisPosY:", thisPosY,
|
|
399
|
-
"\nstartY:", startY, "endY:", endY, "nextTime:", nextTime, "nextPosY:", nextPosY, "nextSpeed:", nextSpeed,
|
|
400
|
-
"\njudgeLine:", this)
|
|
401
|
-
}
|
|
402
495
|
result.push(range)
|
|
403
496
|
range = [undefined, undefined];
|
|
404
497
|
}
|
|
@@ -432,16 +525,203 @@ export class JudgeLine {
|
|
|
432
525
|
result.push(range)
|
|
433
526
|
}
|
|
434
527
|
}
|
|
528
|
+
return result;
|
|
529
|
+
//* /
|
|
530
|
+
}*/
|
|
531
|
+
/**
|
|
532
|
+
* 通过速度序列的FloorPosition反解出一个时间范围。
|
|
533
|
+
*
|
|
534
|
+
* KPA内核代码中最大的一坨史山,没有之一。
|
|
535
|
+
*
|
|
536
|
+
* 谜面渲染时最耗时的函数
|
|
537
|
+
*
|
|
538
|
+
* 调用此方法前需要先更新判定线当前的FP。
|
|
539
|
+
*
|
|
540
|
+
* startY and endY must not be negative
|
|
541
|
+
* @param beats
|
|
542
|
+
* @param timeCalculator
|
|
543
|
+
* @param startY
|
|
544
|
+
* @param endY
|
|
545
|
+
* @returns
|
|
546
|
+
*/
|
|
547
|
+
computeTimeRange(beats: number, timeCalculator: TimeCalculator, startY: number, endY: number): [number, number][] {
|
|
548
|
+
//return [[0, Infinity]]
|
|
549
|
+
//*
|
|
550
|
+
const result: [number, number][] = [];
|
|
551
|
+
|
|
552
|
+
// 直接使用speedSequence,不再遍历所有事件层
|
|
553
|
+
if (!this.speedSequence) {
|
|
554
|
+
return result;
|
|
555
|
+
}
|
|
556
|
+
const speedSequence = this.speedSequence;
|
|
557
|
+
const lineMonotonicity = speedSequence.monotonicity ?? Monotonicity.swinging;
|
|
558
|
+
|
|
559
|
+
const currentJudgeLineFloorPos = this.currentFloorPosition;
|
|
560
|
+
|
|
561
|
+
// 获取起始节点
|
|
562
|
+
let startNode: EventStartNode<number> = this.speedSequence.getNodeAt(beats);
|
|
563
|
+
let range: [number, number] = [undefined, undefined];
|
|
564
|
+
// 启用遮罩时,两个Y边界都是正数,直接返回空数组
|
|
565
|
+
if (lineMonotonicity === Monotonicity.decreasing && startY >= 0 && endY > 0) {
|
|
566
|
+
|
|
567
|
+
return result;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
const computeTime = (speed: number, currentPos: number, fore: number) =>
|
|
572
|
+
timeCalculator.secondsToBeats(currentPos / (speed * 120) + timeCalculator.toSeconds(fore));
|
|
573
|
+
|
|
574
|
+
// 遍历所有事件节点直到结尾
|
|
575
|
+
while (true) {
|
|
576
|
+
const thisTime = TC.toBeats(startNode.time);
|
|
577
|
+
const endNode = startNode.next;
|
|
578
|
+
const nextStart = endNode.next;
|
|
579
|
+
if (endNode.type === NodeType.TAIL) {
|
|
580
|
+
// 处理最后一个节点到无穷大的情况
|
|
581
|
+
const thisPosY = startNode.floorPosition - currentJudgeLineFloorPos;
|
|
582
|
+
const thisSpeed = startNode.value;
|
|
583
|
+
|
|
584
|
+
const inf = thisSpeed > 0 ? Infinity : (thisSpeed < 0 ? -Infinity : thisPosY);
|
|
585
|
+
|
|
586
|
+
if (range[0] === undefined) {
|
|
587
|
+
if (thisPosY < startY && startY <= inf || thisPosY >= endY && endY > inf) {
|
|
588
|
+
range[0] = computeTime(
|
|
589
|
+
thisSpeed,
|
|
590
|
+
(thisPosY < inf ? startY : endY) - thisPosY,
|
|
591
|
+
thisTime)
|
|
592
|
+
} else if (thisSpeed === 0) {
|
|
593
|
+
range[0] = 0;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (range[0] !== undefined) {
|
|
598
|
+
if (thisPosY < endY && endY <= inf || thisPosY >= startY && startY > inf) {
|
|
599
|
+
range[1] = computeTime(
|
|
600
|
+
thisSpeed,
|
|
601
|
+
(thisPosY > inf ? startY : endY) - thisPosY,
|
|
602
|
+
thisTime)
|
|
603
|
+
result.push(range)
|
|
604
|
+
} else if (thisSpeed === 0) {
|
|
605
|
+
range[1] = Infinity;
|
|
606
|
+
result.push(range)
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
const nextTime = TC.toBeats(nextStart.time);
|
|
614
|
+
|
|
615
|
+
const thisPosY = startNode.floorPosition - currentJudgeLineFloorPos;
|
|
616
|
+
const nextPosY = nextStart.floorPosition - currentJudgeLineFloorPos;
|
|
617
|
+
|
|
618
|
+
let thisSpeed = startNode.value;
|
|
619
|
+
let nextSpeed = nextStart.value;
|
|
620
|
+
|
|
621
|
+
if (Math.abs(thisSpeed) < 1e-8) {
|
|
622
|
+
thisSpeed = 0;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (Math.abs(nextSpeed) < 1e-8) {
|
|
626
|
+
nextSpeed = 0;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// 处理速度变号的情况
|
|
630
|
+
if (thisSpeed * nextSpeed < 0) {
|
|
631
|
+
// 计算速度为0的时间点
|
|
632
|
+
const zeroTime = (nextTime - thisTime) * (0 - thisSpeed) / (nextSpeed - thisSpeed) + thisTime;
|
|
633
|
+
const zeroPosY = speedSequence.getFloorPositionAt(zeroTime, timeCalculator) - currentJudgeLineFloorPos;
|
|
634
|
+
const zeroSpeed = 0;
|
|
635
|
+
|
|
636
|
+
// 处理第一段(开始到零点)
|
|
637
|
+
if (range[0] === undefined) {
|
|
638
|
+
if (thisPosY < startY && startY <= zeroPosY || thisPosY > endY && endY >= zeroPosY) {
|
|
639
|
+
range[0] = thisSpeed !== zeroSpeed ? thisTime : computeTime(
|
|
640
|
+
thisSpeed,
|
|
641
|
+
(thisPosY < zeroPosY ? startY : endY) - thisPosY, thisTime
|
|
642
|
+
);
|
|
643
|
+
} else if (startY <= thisPosY && thisPosY <= endY) {
|
|
644
|
+
range[0] = thisTime;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (range[0] !== undefined) {
|
|
649
|
+
if (thisPosY < endY && endY <= zeroPosY || thisPosY > startY && startY >= zeroPosY) {
|
|
650
|
+
range[1] = thisSpeed !== zeroSpeed ? zeroTime : computeTime(
|
|
651
|
+
thisSpeed,
|
|
652
|
+
(thisPosY > zeroPosY ? startY : endY) - thisPosY, thisTime)
|
|
653
|
+
result.push(range);
|
|
654
|
+
if (lineMonotonicity !== Monotonicity.swinging) {
|
|
655
|
+
// 单调的FloorPosition函数只能产生一个符合条件的区间,可以提前返回达到优化目的
|
|
656
|
+
return result;
|
|
657
|
+
}
|
|
658
|
+
range = [undefined, undefined];
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// 处理第二段(零点到结束)
|
|
663
|
+
if (range[0] === undefined) {
|
|
664
|
+
if (zeroPosY < startY && startY <= nextPosY || zeroPosY > endY && endY >= nextPosY) {
|
|
665
|
+
range[0] = zeroSpeed !== nextSpeed ? zeroTime : computeTime(
|
|
666
|
+
nextSpeed,
|
|
667
|
+
(zeroPosY < nextPosY ? startY : endY) - zeroPosY, zeroTime)
|
|
668
|
+
} else if (startY <= zeroPosY && zeroPosY <= endY) {
|
|
669
|
+
range[0] = zeroTime;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (range[0] !== undefined) {
|
|
674
|
+
if (zeroPosY < endY && endY <= nextPosY || zeroPosY > startY && startY >= nextPosY) {
|
|
675
|
+
range[1] = zeroSpeed !== nextSpeed ? nextTime : computeTime(
|
|
676
|
+
nextSpeed,
|
|
677
|
+
(zeroPosY > nextPosY ? startY : endY) - zeroPosY, zeroTime)
|
|
678
|
+
result.push(range)
|
|
679
|
+
if (lineMonotonicity !== Monotonicity.swinging) {
|
|
680
|
+
// 单调的FloorPosition函数只能产生一个符合条件的区间,可以提前返回达到优化目的
|
|
681
|
+
return result;
|
|
682
|
+
}
|
|
683
|
+
range = [undefined, undefined];
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
} else {
|
|
687
|
+
// 正常情况处理
|
|
688
|
+
if (range[0] === undefined) {
|
|
689
|
+
if (thisPosY < startY && startY <= nextPosY || thisPosY > endY && endY >= nextPosY) {
|
|
690
|
+
range[0] = thisSpeed !== nextSpeed ? thisTime : computeTime(
|
|
691
|
+
thisSpeed,
|
|
692
|
+
(thisPosY < nextPosY ? startY : endY) - thisPosY, thisTime)
|
|
693
|
+
} else if (startY <= thisPosY && thisPosY <= endY) {
|
|
694
|
+
range[0] = thisTime;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (range[0] !== undefined) {
|
|
699
|
+
if (thisPosY < endY && endY <= nextPosY || thisPosY > startY && startY >= nextPosY) {
|
|
700
|
+
range[1] = thisSpeed !== nextSpeed ? nextTime : computeTime(
|
|
701
|
+
thisSpeed,
|
|
702
|
+
(thisPosY > nextPosY ? startY : endY) - thisPosY, thisTime)
|
|
703
|
+
result.push(range);
|
|
704
|
+
if (lineMonotonicity !== Monotonicity.swinging) {
|
|
705
|
+
// 单调的FloorPosition函数只能产生一个符合条件的区间,可以提前返回达到优化目的
|
|
706
|
+
return result;
|
|
707
|
+
}
|
|
708
|
+
range = [undefined, undefined];
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
// 我挺希望能够显式内联上面的……复用性也太低了
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// 移动到下一个节点
|
|
715
|
+
startNode = nextStart;
|
|
716
|
+
}
|
|
717
|
+
|
|
435
718
|
return result;
|
|
436
719
|
//*/
|
|
437
720
|
}
|
|
438
|
-
|
|
439
|
-
computeLinePositionY(beats: number, timeCalculator: TimeCalculator) {
|
|
440
|
-
return this.getStackedIntegral(beats, timeCalculator)
|
|
441
|
-
}
|
|
442
|
-
*/
|
|
721
|
+
|
|
443
722
|
/**
|
|
444
723
|
*
|
|
724
|
+
* @deprecated 1.7.0
|
|
445
725
|
* @param beats
|
|
446
726
|
* @param usePrev 如果取到节点,将使用EndNode的值。默认为FALSE
|
|
447
727
|
* @returns
|
|
@@ -454,10 +734,6 @@ export class JudgeLine {
|
|
|
454
734
|
this.getStackedValue("alpha", beats, usePrev),
|
|
455
735
|
]
|
|
456
736
|
}
|
|
457
|
-
getMatrix(beats: number, usePrev = false) {
|
|
458
|
-
const base = this.father.getMatrix(beats, usePrev);
|
|
459
|
-
const x = this
|
|
460
|
-
}
|
|
461
737
|
getStackedValue(type: keyof EventLayer, beats: number, usePrev: boolean = false) {
|
|
462
738
|
const length = this.eventLayers.length;
|
|
463
739
|
let current = 0;
|
|
@@ -470,7 +746,17 @@ export class JudgeLine {
|
|
|
470
746
|
}
|
|
471
747
|
return current
|
|
472
748
|
}
|
|
473
|
-
|
|
749
|
+
/**
|
|
750
|
+
* 获取指定时间点的FloorPosition。
|
|
751
|
+
*
|
|
752
|
+
* <del>为了向后兼容,保留了多层速度事件的机制。</del>
|
|
753
|
+
*
|
|
754
|
+
* 已经删除了多层速度事件
|
|
755
|
+
* @param beats
|
|
756
|
+
* @param timeCalculator
|
|
757
|
+
* @returns
|
|
758
|
+
* /
|
|
759
|
+
getStackedFloorPosition(beats: number, timeCalculator: TimeCalculator) {
|
|
474
760
|
|
|
475
761
|
const length = this.eventLayers.length;
|
|
476
762
|
let current = 0;
|
|
@@ -479,11 +765,11 @@ export class JudgeLine {
|
|
|
479
765
|
if (!layer || !layer.speed) {
|
|
480
766
|
break;
|
|
481
767
|
}
|
|
482
|
-
current += layer.speed.
|
|
768
|
+
current += layer.speed.getFloorPositionAt(beats, timeCalculator);
|
|
483
769
|
}
|
|
484
770
|
// console.log("integral", current)
|
|
485
771
|
return current;
|
|
486
|
-
}
|
|
772
|
+
}//*/
|
|
487
773
|
/**
|
|
488
774
|
* 获取对应速度和类型的Note树,没有则创建
|
|
489
775
|
*/
|
|
@@ -516,17 +802,17 @@ export class JudgeLine {
|
|
|
516
802
|
* @param eventNodeSequences To Collect the sequences used in this line
|
|
517
803
|
* @returns
|
|
518
804
|
*/
|
|
519
|
-
dumpKPA(eventNodeSequences: Set<EventNodeSequence<any>>, judgeLineGroups: JudgeLineGroup[]):
|
|
520
|
-
const children:
|
|
521
|
-
for (
|
|
805
|
+
dumpKPA(eventNodeSequences: Set<EventNodeSequence<any>>, judgeLineGroups: JudgeLineGroup[]): JudgeLineDataKPA2 {
|
|
806
|
+
const children: JudgeLineDataKPA2[] = [];
|
|
807
|
+
for (const line of this.children) {
|
|
522
808
|
children.push(line.dumpKPA(eventNodeSequences, judgeLineGroups))
|
|
523
809
|
}
|
|
524
810
|
const eventLayers: EventLayerDataKPA[] = [];
|
|
525
811
|
for (let i = 0; i < this.eventLayers.length; i++) {
|
|
526
812
|
const layer = this.eventLayers[i];
|
|
527
813
|
if (!layer) continue;
|
|
528
|
-
|
|
529
|
-
for (
|
|
814
|
+
const layerData = {}
|
|
815
|
+
for (const type in layer) {
|
|
530
816
|
const sequence = layer[type as keyof EventLayer];
|
|
531
817
|
if (!sequence) continue;
|
|
532
818
|
eventNodeSequences.add(sequence);
|
|
@@ -536,10 +822,10 @@ export class JudgeLine {
|
|
|
536
822
|
}
|
|
537
823
|
const hnListsData = {};
|
|
538
824
|
const nnListsData = {};
|
|
539
|
-
for (
|
|
825
|
+
for (const [id, list] of this.hnLists) {
|
|
540
826
|
hnListsData[id] = list.dumpKPA();
|
|
541
827
|
}
|
|
542
|
-
for (
|
|
828
|
+
for (const [id, list] of this.nnLists) {
|
|
543
829
|
nnListsData[id] = list.dumpKPA();
|
|
544
830
|
}
|
|
545
831
|
const extended = {
|
|
@@ -560,12 +846,13 @@ export class JudgeLine {
|
|
|
560
846
|
return {
|
|
561
847
|
group: judgeLineGroups.indexOf(this.group),
|
|
562
848
|
id: this.id,
|
|
563
|
-
|
|
564
|
-
|
|
849
|
+
name: this.name,
|
|
850
|
+
texture: this.texture,
|
|
565
851
|
anchor: this.anchor,
|
|
566
852
|
rotatesWithFather: this.rotatesWithFather,
|
|
567
853
|
children: children,
|
|
568
854
|
eventLayers: eventLayers,
|
|
855
|
+
speedEventNodeSeq: this.speedSequence?.id,
|
|
569
856
|
hnLists: hnListsData,
|
|
570
857
|
nnLists: nnListsData,
|
|
571
858
|
cover: this.cover,
|
|
@@ -575,26 +862,36 @@ export class JudgeLine {
|
|
|
575
862
|
}
|
|
576
863
|
}
|
|
577
864
|
|
|
865
|
+
/*
|
|
866
|
+
暂时不用此方法
|
|
867
|
+
updateNNListFloorPositions() {
|
|
868
|
+
for (const lists of [this.nnLists, this.hnLists]) {
|
|
869
|
+
for (const list of lists) {
|
|
870
|
+
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
*/
|
|
578
875
|
|
|
579
876
|
updateEffectiveBeats(EB: number) {
|
|
580
877
|
for (let i = 0; i < this.eventLayers.length; i++) {
|
|
581
878
|
const layer = this.eventLayers[i];
|
|
582
|
-
for (
|
|
879
|
+
for (const type in layer) {
|
|
583
880
|
const sequence = layer[type as keyof EventLayer];
|
|
584
881
|
sequence.effectiveBeats = EB;
|
|
585
882
|
}
|
|
586
883
|
}
|
|
587
|
-
for (
|
|
588
|
-
for (
|
|
884
|
+
for (const lists of [this.nnLists, this.hnLists]) {
|
|
885
|
+
for (const [_, list] of lists) {
|
|
589
886
|
list.effectiveBeats = EB;
|
|
590
887
|
}
|
|
591
888
|
}
|
|
592
889
|
}
|
|
593
890
|
static checkinterdependency(judgeLine: JudgeLine, toBeFather: JudgeLine) {
|
|
594
|
-
|
|
891
|
+
const descendantsAndSelf = new Set<JudgeLine>();
|
|
595
892
|
const add = (line: JudgeLine) => {
|
|
596
893
|
descendantsAndSelf.add(line);
|
|
597
|
-
for (
|
|
894
|
+
for (const child of line.children) {
|
|
598
895
|
add(child);
|
|
599
896
|
}
|
|
600
897
|
}
|