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