From 5e5ecc4f8810aad111b6442a73d0a83029b5f59b Mon Sep 17 00:00:00 2001 From: IcedDog <34804287+IcedDog@users.noreply.github.com> Date: Mon, 22 Dec 2025 19:47:37 +0800 Subject: [PATCH 1/5] feat: integral calculation --- src/lib/player/utils.ts | 230 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/src/lib/player/utils.ts b/src/lib/player/utils.ts index 06430e2..7f5ec16 100644 --- a/src/lib/player/utils.ts +++ b/src/lib/player/utils.ts @@ -90,6 +90,172 @@ const EASINGS: ((x: number) => number)[] = [ (x) => (x < 0.5 ? (1 - EASINGS[25](1 - 2 * x)) / 2 : (1 + EASINGS[25](2 * x - 1)) / 2), ]; +const EASING_INTEGRALS: ((x: number) => number)[] = [ + (x) => (x * x) / 2, + (x) => (2 / Math.PI) * (1 - Math.cos((x * Math.PI) / 2)), + (x) => x - (2 / Math.PI) * Math.sin((x * Math.PI) / 2), + (x) => x * x - (x * x * x) / 3, + (x) => (x * x * x) / 3, + (x) => x / 2 - Math.sin(Math.PI * x) / (2 * Math.PI), + (x) => (x < 0.5 ? (2 / 3) * Math.pow(x, 3) : 2 * x * x - (2 / 3) * Math.pow(x, 3) - x + 1 / 6), + (x) => (3 / 2) * x * x - x * x * x + Math.pow(x, 4) / 4, + (x) => Math.pow(x, 4) / 4, + (x) => 2 * x * x - 2 * Math.pow(x, 3) + Math.pow(x, 4) - Math.pow(x, 5) / 5, + (x) => Math.pow(x, 5) / 5, + (x) => + x < 0.5 ? Math.pow(x, 4) : -3 * x + 6 * x * x - 4 * Math.pow(x, 3) + Math.pow(x, 4) + 0.5, + (x) => + x < 0.5 + ? (8 / 5) * Math.pow(x, 5) + : -7 * x + + 16 * x * x - + 16 * Math.pow(x, 3) + + 8 * Math.pow(x, 4) - + (8 / 5) * Math.pow(x, 5) + + 11 / 10, + (x) => + (5 / 2) * x * x - + (10 / 3) * Math.pow(x, 3) + + (5 / 2) * Math.pow(x, 4) - + Math.pow(x, 5) + + Math.pow(x, 6) / 6, + (x) => Math.pow(x, 6) / 6, + (x) => x - (1 - Math.pow(2, -10 * x)) / (10 * Math.LN2), + (x) => (Math.pow(2, 10 * x - 10) - Math.pow(2, -10)) / (10 * Math.LN2), + (x) => + 0.5 * ((x - 1) * Math.sqrt(Math.max(0, 1 - Math.pow(x - 1, 2))) + Math.asin(x - 1)) + + Math.PI / 4, + (x) => + x - 0.5 * (x * Math.sqrt(Math.max(0, 1 - x * x)) + Math.asin(Math.max(-1, Math.min(1, x)))), + (x) => { + const a = 2.70158; + const b = 1.70158; + return ( + (1 - a + b) * x + + ((3 * a - 2 * b) / 2) * x * x + + ((-3 * a + b) / 3) * Math.pow(x, 3) + + (a / 4) * Math.pow(x, 4) + ); + }, + (x) => (2.70158 / 4) * Math.pow(x, 4) - (1.70158 / 3) * Math.pow(x, 3), + (x) => + x < 0.5 + ? 0.5 * x - + 0.25 * x * Math.sqrt(Math.max(0, 1 - 4 * x * x)) - + 0.125 * Math.asin(Math.max(-1, Math.min(1, 2 * x))) + : 0.5 * x - + 0.25 * (1 - x) * Math.sqrt(Math.max(0, 1 - 4 * (1 - x) * (1 - x))) - + 0.125 * Math.asin(Math.max(-1, Math.min(1, 2 * (1 - x)))), + (x) => { + const s = 2.59491; + x = clamp(x, 0, 1); + if (x <= 0.5) return (s + 1) * Math.pow(x, 4) - (2 * s * Math.pow(x, 3)) / 3; + const Ihalf = (s + 1) * Math.pow(0.5, 4) - (2 * s * Math.pow(0.5, 3)) / 3; + const F = (t: number) => + (s + 1) * Math.pow(t, 4) - + ((10 * s + 12) / 3) * Math.pow(t, 3) + + ((8 * s + 12) / 2) * t * t - + (2 * s + 3) * t; + return Ihalf + (F(x) - F(0.5)); + }, + // 23 + (x) => { + x = clamp(x, 0, 1); + const K = 10 * Math.LN2; + const A = ((2 * Math.PI) / 3) * 10; + const B = -0.75 * ((2 * Math.PI) / 3); + const H = (t: number) => + (Math.exp(-K * t) * (-K * Math.sin(A * t + B) - A * Math.cos(A * t + B))) / (A * A + K * K); + return x + (H(x) - H(0)); + }, + (x) => { + x = clamp(x, 0, 1); + const K = 10 * Math.LN2; + const A = ((2 * Math.PI) / 3) * 10; + const B = -10.75 * ((2 * Math.PI) / 3); + const C = Math.pow(2, -10); + const G = (t: number) => + (-C * Math.exp(K * t) * (K * Math.sin(A * t + B) - A * Math.cos(A * t + B))) / + (A * A + K * K); + return G(x) - G(0); + }, + (x) => { + x = clamp(x, 0, 1); + const A = 7.5625; + const b1 = 1 / 2.75; + const b2 = 2 / 2.75; + const b3 = 2.5 / 2.75; + const c1 = 1.5 / 2.75; + const c2 = 2.25 / 2.75; + const c3 = 2.625 / 2.75; + const I_b1 = (A * Math.pow(b1, 3)) / 3; + const I_b2 = I_b1 + (A / 3) * (Math.pow(b2 - c1, 3) - Math.pow(b1 - c1, 3)) + 0.75 * (b2 - b1); + const I_b3 = + I_b2 + (A / 3) * (Math.pow(b3 - c2, 3) - Math.pow(b2 - c2, 3)) + 0.9375 * (b3 - b2); + if (x < b1) return (A * Math.pow(x, 3)) / 3; + if (x < b2) + return I_b1 + (A / 3) * (Math.pow(x - c1, 3) - Math.pow(b1 - c1, 3)) + 0.75 * (x - b1); + if (x < b3) + return I_b2 + (A / 3) * (Math.pow(x - c2, 3) - Math.pow(b2 - c2, 3)) + 0.9375 * (x - b2); + return I_b3 + (A / 3) * (Math.pow(x - c3, 3) - Math.pow(b3 - c3, 3)) + 0.984375 * (x - b3); + }, + (x) => { + x = clamp(x, 0, 1); + const A = 7.5625; + const b1 = 1 / 2.75; + const b2 = 2 / 2.75; + const b3 = 2.5 / 2.75; + const c1 = 1.5 / 2.75; + const c2 = 2.25 / 2.75; + const c3 = 2.625 / 2.75; + const outIntegral = (u: number) => { + u = clamp(u, 0, 1); + const I_b1 = (A * Math.pow(b1, 3)) / 3; + const I_b2 = + I_b1 + (A / 3) * (Math.pow(b2 - c1, 3) - Math.pow(b1 - c1, 3)) + 0.75 * (b2 - b1); + const I_b3 = + I_b2 + (A / 3) * (Math.pow(b3 - c2, 3) - Math.pow(b2 - c2, 3)) + 0.9375 * (b3 - b2); + if (u < b1) return (A * Math.pow(u, 3)) / 3; + if (u < b2) + return I_b1 + (A / 3) * (Math.pow(u - c1, 3) - Math.pow(b1 - c1, 3)) + 0.75 * (u - b1); + if (u < b3) + return I_b2 + (A / 3) * (Math.pow(u - c2, 3) - Math.pow(b2 - c2, 3)) + 0.9375 * (u - b2); + return I_b3 + (A / 3) * (Math.pow(u - c3, 3) - Math.pow(b3 - c3, 3)) + 0.984375 * (u - b3); + }; + const I1 = outIntegral(1); + return x - (I1 - outIntegral(1 - x)); + }, + (x) => { + x = clamp(x, 0, 1); + const A = 7.5625; + const b1 = 1 / 2.75; + const b2 = 2 / 2.75; + const b3 = 2.5 / 2.75; + const c1 = 1.5 / 2.75; + const c2 = 2.25 / 2.75; + const c3 = 2.625 / 2.75; + const outIntegral = (u: number) => { + u = clamp(u, 0, 1); + const I_b1 = (A * Math.pow(b1, 3)) / 3; + const I_b2 = + I_b1 + (A / 3) * (Math.pow(b2 - c1, 3) - Math.pow(b1 - c1, 3)) + 0.75 * (b2 - b1); + const I_b3 = + I_b2 + (A / 3) * (Math.pow(b3 - c2, 3) - Math.pow(b2 - c2, 3)) + 0.9375 * (b3 - b2); + if (u < b1) return (A * Math.pow(u, 3)) / 3; + if (u < b2) + return I_b1 + (A / 3) * (Math.pow(u - c1, 3) - Math.pow(b1 - c1, 3)) + 0.75 * (u - b1); + if (u < b3) + return I_b2 + (A / 3) * (Math.pow(u - c2, 3) - Math.pow(b2 - c2, 3)) + 0.9375 * (u - b2); + return I_b3 + (A / 3) * (Math.pow(u - c3, 3) - Math.pow(b3 - c3, 3)) + 0.984375 * (u - b3); + }; + const I1 = outIntegral(1); + if (x <= 0.5) { + return x / 2 + (I1 - outIntegral(1 - 2 * x)) / 4; + } + return x / 2 + (I1 + outIntegral(2 * x - 1)) / 4; + }, +]; + const calculateEasingValue = ( func: (x: number) => number, x: number, @@ -121,6 +287,35 @@ const EASING_DERIVATIVE_ENDS = EASINGS.map((func) => [ calculateDerivativeValue(func, 1), ]); +const calculateEasingIntegral = ( + type: number, + x: number, + easingLeft = 0, + easingRight = 1, +): number => { + if (easingLeft === easingRight) { + const val = EASINGS[type](easingLeft); + return val * x; + } + const t = easingLeft + (easingRight - easingLeft) * x; + const integralTotal = EASING_INTEGRALS[type](t); + const integralLeft = EASING_INTEGRALS[type](easingLeft); + const easingLeftVal = EASINGS[type](easingLeft); + const easingRightVal = EASINGS[type](easingRight); + const scale = 1 / (easingRightVal - easingLeftVal); + return scale * (integralTotal - integralLeft - easingLeftVal * (t - easingLeft)); +}; + +const getEasingEndpoint = ( + type: number, + x: number, // 0 或 1 + easingLeft = 0, + easingRight = 1, +): number => { + const t = easingLeft + (easingRight - easingLeft) * x; + return EASINGS[type](t); +}; + const sanitizeEasingParams = (type: number, x: number, easingLeft: number, easingRight: number) => { return { type: type > 0 && type <= EASINGS.length ? type : 1, @@ -673,6 +868,18 @@ export const integrate = ( return k * calculateEasingValue(EASINGS[p.type - 1], p.x, p.easingLeft, p.easingRight) + b * p.x; }; +export const integrate2 = ( + type: number, + x: number, + k: number, + c: number, + easingLeft = 0, + easingRight = 1, +): number => { + const integral = calculateEasingIntegral(type, x, easingLeft, easingRight); + return k * integral + c * x; +}; + export const getIntegral = ( event: SpeedEvent | undefined, bpmList: Bpm[], @@ -699,6 +906,29 @@ export const getIntegral = ( return ((event.start + (getEventValue(beat, event) as number)) * progressedSec) / 2; }; +export const getIntegral2 = ( + event: SpeedEvent | undefined, + bpmList: Bpm[], + beat: number | undefined = undefined, +): number => { + if (!event) return 0; + if (beat === undefined || beat >= event.endBeat) beat = event.endBeat; + const lengthSec = getTimeSec(bpmList, event.endBeat) - getTimeSec(bpmList, event.startBeat); + if (!('easingType' in event) || event.easingType <= 1) { + return ((event.start + (getEventValue(beat, event) as number)) * lengthSec) / 2; + } + const lengthBeat = event.endBeat - event.startBeat; + const easingLeft = 'easingLeft' in event ? event.easingLeft : 0; + const easingRight = 'easingRight' in event ? event.easingRight : 1; + const e0 = getEasingEndpoint(event.easingType, 0, easingLeft, easingRight); + const e1 = getEasingEndpoint(event.easingType, 1, easingLeft, easingRight); + const k = (event.end - event.start) / (e1 - e0); + const c = event.start - k * e0; + const x = (beat - event.startBeat) / (event.endBeat - event.startBeat); + const progress = integrate2(event.easingType, x, k, c, easingLeft, easingRight); + return (progress * lengthSec) / lengthBeat; +}; + export const getJudgmentPosition = (input: PointerTap | PointerDrag, line: Line) => { const vector = line.vector; vector.scale(dot([input.position.x - line.x, input.position.y - line.y], [vector.x, vector.y])); From d0fe792b60621788e299ddc8b435eff736c92c71 Mon Sep 17 00:00:00 2001 From: IcedDog <34804287+IcedDog@users.noreply.github.com> Date: Thu, 15 Jan 2026 02:04:23 +0800 Subject: [PATCH 2/5] fix: integral now WORKS --- src/lib/player/utils.ts | 38 +++++++++++--------------------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/src/lib/player/utils.ts b/src/lib/player/utils.ts index 7f5ec16..ab1b942 100644 --- a/src/lib/player/utils.ts +++ b/src/lib/player/utils.ts @@ -293,27 +293,14 @@ const calculateEasingIntegral = ( easingLeft = 0, easingRight = 1, ): number => { - if (easingLeft === easingRight) { - const val = EASINGS[type](easingLeft); - return val * x; - } - const t = easingLeft + (easingRight - easingLeft) * x; - const integralTotal = EASING_INTEGRALS[type](t); - const integralLeft = EASING_INTEGRALS[type](easingLeft); - const easingLeftVal = EASINGS[type](easingLeft); - const easingRightVal = EASINGS[type](easingRight); - const scale = 1 / (easingRightVal - easingLeftVal); - return scale * (integralTotal - integralLeft - easingLeftVal * (t - easingLeft)); -}; + const scaledX = easingLeft + (easingRight - easingLeft) * x; -const getEasingEndpoint = ( - type: number, - x: number, // 0 或 1 - easingLeft = 0, - easingRight = 1, -): number => { - const t = easingLeft + (easingRight - easingLeft) * x; - return EASINGS[type](t); + if (type < 1 || type > EASING_INTEGRALS.length) { + return (scaledX * scaledX) / 2; + } + const func = EASING_INTEGRALS[type - 1]; + // ∫[left to scaledX] f(t) dt = F(scaledX) - F(left) + return func(scaledX) - func(easingLeft); }; const sanitizeEasingParams = (type: number, x: number, easingLeft: number, easingRight: number) => { @@ -917,16 +904,13 @@ export const getIntegral2 = ( if (!('easingType' in event) || event.easingType <= 1) { return ((event.start + (getEventValue(beat, event) as number)) * lengthSec) / 2; } - const lengthBeat = event.endBeat - event.startBeat; const easingLeft = 'easingLeft' in event ? event.easingLeft : 0; const easingRight = 'easingRight' in event ? event.easingRight : 1; - const e0 = getEasingEndpoint(event.easingType, 0, easingLeft, easingRight); - const e1 = getEasingEndpoint(event.easingType, 1, easingLeft, easingRight); - const k = (event.end - event.start) / (e1 - e0); - const c = event.start - k * e0; const x = (beat - event.startBeat) / (event.endBeat - event.startBeat); - const progress = integrate2(event.easingType, x, k, c, easingLeft, easingRight); - return (progress * lengthSec) / lengthBeat; + const easingIntegral = calculateEasingIntegral(event.easingType, x, easingLeft, easingRight); + const scaledIntegral = + event.start * x + ((event.end - event.start) * easingIntegral) / (easingRight - easingLeft); + return scaledIntegral * lengthSec; }; export const getJudgmentPosition = (input: PointerTap | PointerDrag, line: Line) => { From 077c5325905bdc20e7689841cbc90edb8bda18f2 Mon Sep 17 00:00:00 2001 From: IcedDog <34804287+IcedDog@users.noreply.github.com> Date: Thu, 15 Jan 2026 02:11:08 +0800 Subject: [PATCH 3/5] feat: apply integral func according to RPEVersion --- src/lib/player/objects/Line.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/player/objects/Line.ts b/src/lib/player/objects/Line.ts index 76bb74e..1ff5ce4 100644 --- a/src/lib/player/objects/Line.ts +++ b/src/lib/player/objects/Line.ts @@ -11,6 +11,7 @@ import { LongNote } from './LongNote'; import { PlainNote } from './PlainNote'; import { getIntegral, + getIntegral2, getLineColor, getTimeSec, getEventValue, @@ -418,9 +419,10 @@ export class Line { cur[layerIndex] = 0; this._lastHeight = 0; } + const integral = this._scene.chart.META.RPEVersion >= 170 ? getIntegral2 : getIntegral; while (cur[layerIndex] < events.length - 1 && beat > events[cur[layerIndex] + 1].startBeat) { this._lastHeight += - getIntegral(events[cur[layerIndex]], this._scene.bpmList) + + integral(events[cur[layerIndex]], this._scene.bpmList) + events[cur[layerIndex]].end * (getTimeSec(this._scene.bpmList, events[cur[layerIndex] + 1].startBeat) - getTimeSec(this._scene.bpmList, events[cur[layerIndex]].endBeat)); @@ -428,10 +430,10 @@ export class Line { } let height = this._lastHeight; if (beat <= events[cur[layerIndex]].endBeat) { - height += getIntegral(events[cur[layerIndex]], this._scene.bpmList, beat); + height += integral(events[cur[layerIndex]], this._scene.bpmList, beat); } else { height += - getIntegral(events[cur[layerIndex]], this._scene.bpmList) + + integral(events[cur[layerIndex]], this._scene.bpmList) + events[cur[layerIndex]].end * (getTimeSec(this._scene.bpmList, beat) - getTimeSec(this._scene.bpmList, events[cur[layerIndex]].endBeat)); From 27639911b901d5a9b0b5e7a6c701636dc4c4e7c0 Mon Sep 17 00:00:00 2001 From: Naptie Date: Thu, 15 Jan 2026 03:33:13 +0800 Subject: [PATCH 4/5] refactor: merge two getIntegral functions into one and introduce a boolean to switch between two logics --- src/lib/player/objects/Line.ts | 16 +++++++++----- src/lib/player/utils.ts | 38 +++++++++++++--------------------- src/lib/types.ts | 1 + 3 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/lib/player/objects/Line.ts b/src/lib/player/objects/Line.ts index 1576be4..0be7ae0 100644 --- a/src/lib/player/objects/Line.ts +++ b/src/lib/player/objects/Line.ts @@ -11,7 +11,6 @@ import { LongNote } from './LongNote'; import { PlainNote } from './PlainNote'; import { getIntegral, - getIntegral2, getLineColor, getTimeSec, getEventValue, @@ -45,6 +44,7 @@ export class Line { private _rotationModifier: 1 | -1 = 1; private _rotationOffset: 0 | 180 = 0; private _rotateWithParent: boolean = false; + private _integrateSpeedEasings: boolean = false; private _curX = []; private _curY = []; @@ -110,6 +110,8 @@ export class Line { this._hasAttach = !!this._data.attachUI; this._rotateWithParent = this._data.rotateWithFather ?? false; + this._integrateSpeedEasings = + this._data.integrateSpeedEasings ?? this._scene.chart.META.RPEVersion >= 170; this._line.setScale( this._scene.p(1) * (this._scaleX ?? 1), (this._hasCustomTexture @@ -429,10 +431,9 @@ export class Line { cur[layerIndex] = 0; this._lastHeight = 0; } - const integral = this._scene.chart.META.RPEVersion >= 170 ? getIntegral2 : getIntegral; while (cur[layerIndex] < events.length - 1 && beat > events[cur[layerIndex] + 1].startBeat) { this._lastHeight += - integral(events[cur[layerIndex]], this._scene.bpmList) + + getIntegral(events[cur[layerIndex]], this._scene.bpmList, this._integrateSpeedEasings) + events[cur[layerIndex]].end * (getTimeSec(this._scene.bpmList, events[cur[layerIndex] + 1].startBeat) - getTimeSec(this._scene.bpmList, events[cur[layerIndex]].endBeat)); @@ -440,10 +441,15 @@ export class Line { } let height = this._lastHeight; if (beat <= events[cur[layerIndex]].endBeat) { - height += integral(events[cur[layerIndex]], this._scene.bpmList, beat); + height += getIntegral( + events[cur[layerIndex]], + this._scene.bpmList, + this._integrateSpeedEasings, + beat, + ); } else { height += - integral(events[cur[layerIndex]], this._scene.bpmList) + + getIntegral(events[cur[layerIndex]], this._scene.bpmList, this._integrateSpeedEasings) + events[cur[layerIndex]].end * (getTimeSec(this._scene.bpmList, beat) - getTimeSec(this._scene.bpmList, events[cur[layerIndex]].endBeat)); diff --git a/src/lib/player/utils.ts b/src/lib/player/utils.ts index ab1b942..97a4d1d 100644 --- a/src/lib/player/utils.ts +++ b/src/lib/player/utils.ts @@ -870,15 +870,19 @@ export const integrate2 = ( export const getIntegral = ( event: SpeedEvent | undefined, bpmList: Bpm[], + integrateEasings: boolean, beat: number | undefined = undefined, ): number => { if (!event) return 0; if (beat === undefined || beat >= event.endBeat) beat = event.endBeat; const startSec = getTimeSec(bpmList, event.startBeat); const progressedSec = getTimeSec(bpmList, beat) - startSec; - if ('easingType' in event && event.easingType > 1) { - const easingLeft = 'easingLeft' in event ? event.easingLeft : 0; - const easingRight = 'easingRight' in event ? event.easingRight : 1; + if (!('easingType' in event) || event.easingType <= 1) { + return ((event.start + (getEventValue(beat, event) as number)) * progressedSec) / 2; + } + const easingLeft = 'easingLeft' in event ? event.easingLeft : 0; + const easingRight = 'easingRight' in event ? event.easingRight : 1; + if (!integrateEasings) { const df0 = derivative(event.easingType, 0, easingLeft, easingRight); const df1 = derivative(event.easingType, 1, easingLeft, easingRight); const k = (event.end - event.start) / (df1 - df0); @@ -889,28 +893,14 @@ export const getIntegral = ( (integrate(event.easingType, x, k, b, easingLeft, easingRight) * lengthSec) / (event.endBeat - event.startBeat) ); + } else { + const x = (beat - event.startBeat) / (event.endBeat - event.startBeat); + const easingIntegral = calculateEasingIntegral(event.easingType, x, easingLeft, easingRight); + const scaledIntegral = + event.start * x + ((event.end - event.start) * easingIntegral) / (easingRight - easingLeft); + const lengthSec = getTimeSec(bpmList, event.endBeat) - startSec; + return scaledIntegral * lengthSec; } - return ((event.start + (getEventValue(beat, event) as number)) * progressedSec) / 2; -}; - -export const getIntegral2 = ( - event: SpeedEvent | undefined, - bpmList: Bpm[], - beat: number | undefined = undefined, -): number => { - if (!event) return 0; - if (beat === undefined || beat >= event.endBeat) beat = event.endBeat; - const lengthSec = getTimeSec(bpmList, event.endBeat) - getTimeSec(bpmList, event.startBeat); - if (!('easingType' in event) || event.easingType <= 1) { - return ((event.start + (getEventValue(beat, event) as number)) * lengthSec) / 2; - } - const easingLeft = 'easingLeft' in event ? event.easingLeft : 0; - const easingRight = 'easingRight' in event ? event.easingRight : 1; - const x = (beat - event.startBeat) / (event.endBeat - event.startBeat); - const easingIntegral = calculateEasingIntegral(event.easingType, x, easingLeft, easingRight); - const scaledIntegral = - event.start * x + ((event.end - event.start) * easingIntegral) / (easingRight - easingLeft); - return scaledIntegral * lengthSec; }; export const getJudgmentPosition = (input: PointerTap | PointerDrag, line: Line) => { diff --git a/src/lib/types.ts b/src/lib/types.ts index 73d2125..878d74d 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -111,6 +111,7 @@ export interface JudgeLine { extended?: Extended; father: number; rotateWithFather?: boolean; + integrateSpeedEasings?: boolean; isCover: number; isGif?: boolean; notes?: Note[]; From 3894bc974ae8c0029570a2076bd2f3e653f5a7ce Mon Sep 17 00:00:00 2001 From: Naptie Date: Thu, 15 Jan 2026 04:02:04 +0800 Subject: [PATCH 5/5] docs(readme): introduce integrateSpeedEasings --- README.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b0c78b8..b0c5d4f 100644 --- a/README.md +++ b/README.md @@ -79,15 +79,16 @@ The Z indexes of judgment lines whose `zIndex` is not present (see [Chart enhanc Aside from adding support for RPE features, we've also designed some original properties for judgment lines & notes. -| Property | Value(s) | Example | Description | -| -------------------- | --------------------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `scaleOnNotes` | `0`: none; `1`: scale; `2`: clip | `"scaleOnNotes": 2` | Belongs to a judgment line. Decides how `scaleX` events affect notes. Defaults to `0`. | -| `appearanceOnAttach` | `0`: hidden; `1`: white colored; `2`: FC/AP colored | `"appearanceOnAttach": 2` | Belongs to a judgment line. Decides how the line will be displayed when a UI component or any video is attached to it. Color events will override the color defined by these options. Defaults to `0`. | -| `zIndex` | an integer or a float | `"zIndex": 3.5` | Belongs to a judgment line or note. Sets the Z index for the object. For a judgment line, this property, if set, overrides the `zOrder` property, allowing for more control over on which layer the line should be displayed. For default values, see [Z indexes](#z-indexes). | -| `zIndexHitEffects` | an integer or a float | `"zIndexHitEffects": 6.5` | Belongs to a note. Sets the Z index for the hit effects of the note. Defaults to `7`. | -| `tint` | [R, G, B], as seen in `colorEvents`; `null` | `"tint": [255, 0, 0]` | Belongs to a note. Sets the tint for the note. Defaults to `null`. | -| `tintHitEffects` | [R, G, B], as seen in `colorEvents`; `null` | `"tintHitEffects": [255, 0, 0]` | Belongs to a note. Sets the tint for the hit effects of the note. Defaults to `null`. | -| `judgeSize` | a positive number or 0 | `"judgeSize": 1.5` | Belongs to a note. Determines the width of the judgment area of the note. Defaults to `size`. | +| Property | Value(s) | Example | Description | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `scaleOnNotes` | `0`: none; `1`: scale; `2`: clip | `"scaleOnNotes": 2` | Belongs to a judgment line. Decides how `scaleX` events affect notes. Defaults to `0`. | +| `appearanceOnAttach` | `0`: hidden; `1`: white colored; `2`: FC/AP colored | `"appearanceOnAttach": 2` | Belongs to a judgment line. Decides how the line will be displayed when a UI component or any video is attached to it. Color events will override the color defined by these options. Defaults to `0`. | +| `integrateSpeedEasings` | `false`: treat non-linear speed easings directly as height over time; `true`: integrate speed easings to obtain height functions | `"integrateSpeedEasings": true` | Belongs to a judgment line. Decides how the easing functions of all speed events of the line will be interpreted. Defaults to `false` for pre-1.7 charts and `true` for charts on 1.7 and later versions. | +| `zIndex` | an integer or a float | `"zIndex": 3.5` | Belongs to a judgment line or note. Sets the Z index for the object. For a judgment line, this property, if set, overrides the `zOrder` property, allowing for more control over on which layer the line should be displayed. For default values, see [Z indexes](#z-indexes). | +| `zIndexHitEffects` | an integer or a float | `"zIndexHitEffects": 6.5` | Belongs to a note. Sets the Z index for the hit effects of the note. Defaults to `7`. | +| `tint` | [R, G, B], as seen in `colorEvents`; `null` | `"tint": [255, 0, 0]` | Belongs to a note. Sets the tint for the note. Defaults to `null`. | +| `tintHitEffects` | [R, G, B], as seen in `colorEvents`; `null` | `"tintHitEffects": [255, 0, 0]` | Belongs to a note. Sets the tint for the hit effects of the note. Defaults to `null`. | +| `judgeSize` | a positive number or 0 | `"judgeSize": 1.5` | Belongs to a note. Determines the width of the judgment area of the note. Defaults to `size`. | ### Video enhancements