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/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 "./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,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
- let line = new JudgeLine(chart)
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
- let notes = data.notes;
111
+ const notes = data.notes;
106
112
  notes.sort((n1: NoteDataRPE, n2: NoteDataRPE) => {
107
- if (TimeCalculator.ne(n1.startTime, n2.startTime)) {
108
- return TimeCalculator.gt(n1.startTime, n2.startTime) ? 1 : -1
113
+ if (TC.ne(n1.startTime, n2.startTime)) {
114
+ return TC.gt(n1.startTime, n2.startTime) ? 1 : -1
109
115
  }
110
- return TimeCalculator.gt(n1.endTime, n2.endTime) ? -1 : 1 // 这里曾经排反了(
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 (TimeCalculator.eq(lastHoldTime, note.startTime)) {
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 (let trees of [holdLists, noteLists]) {
134
- for (const [_, list] of trees) {
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 sequence = EventNodeSequence.fromRPEJSON(type, events, chart);
146
- sequence.id = `#${id}.${index}.${EventType[type]}`;
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 sequence = EventNodeSequence.fromRPEJSON(type, events, chart);
154
- sequence.id = `#${id}.ex.${EventType[type]}`;
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(isOld: boolean, chart: Chart, id: number, data: JudgeLineDataKPA, templates: TemplateEasingLib, timeCalculator: TimeCalculator) {
196
- let line = new JudgeLine(chart)
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 || "line.png";
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 (let isHold of [false, true]) {
239
+ for (const isHold of [false, true]) {
209
240
  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) {
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 (let child of data.children) {
225
- line.children.add(JudgeLine.fromKPAJSON(isOld, chart, child.id, child, templates, timeCalculator));
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 (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: 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(data.extended.scaleXEvents)
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(data.extended.scaleYEvents)
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 (let eventLayer of this.eventLayers) {
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
- let result: [number, number][] = [];
325
- for (let eventLayer of this.eventLayers) {
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(TimeCalculator.toBeats(node.time))
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.getStackedIntegral(nextTime, timeCalculator)
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.getStackedIntegral(nextTime, timeCalculator);
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.getStackedIntegral(nextTime, timeCalculator)
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
- getStackedIntegral(beats: number, timeCalculator: TimeCalculator) {
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.getIntegral(beats, timeCalculator);
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[]): JudgeLineDataKPA {
520
- const children: JudgeLineDataKPA[] = [];
521
- for (let line of this.children) {
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
- let layerData = {}
529
- for (let type in layer) {
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 (let [id, list] of this.hnLists) {
825
+ for (const [id, list] of this.hnLists) {
540
826
  hnListsData[id] = list.dumpKPA();
541
827
  }
542
- for (let [id, list] of this.nnLists) {
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
- Name: this.name,
564
- Texture: this.texture,
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 (let type in layer) {
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 (let lists of [this.nnLists, this.hnLists]) {
588
- for (let [_, list] of lists) {
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
- let descendantsAndSelf = new Set<JudgeLine>();
891
+ const descendantsAndSelf = new Set<JudgeLine>();
595
892
  const add = (line: JudgeLine) => {
596
893
  descendantsAndSelf.add(line);
597
- for (let child of line.children) {
894
+ for (const child of line.children) {
598
895
  add(child);
599
896
  }
600
897
  }