From e502f647ae77c7f907be9dfd3726fb1736a08b1c Mon Sep 17 00:00:00 2001 From: Kionell Date: Sun, 24 Jan 2021 19:54:55 +0200 Subject: [PATCH 01/10] Rewritten timing points calculation to get proper combo and duration of sliders --- src/Beatmap.js | 71 ++++++++++++++++++++++++------ src/Rulesets/Osu/Objects/Slider.js | 27 +----------- 2 files changed, 58 insertions(+), 40 deletions(-) diff --git a/src/Beatmap.js b/src/Beatmap.js index 14c7000..b255567 100644 --- a/src/Beatmap.js +++ b/src/Beatmap.js @@ -282,11 +282,11 @@ class Beatmap { hitObject.pathPoints.push(point); }); - let sliderPath = new SliderPath( + hitObject.path = new SliderPath( hitObject.pathPoints, hitObject.pixelLength ); - let endPoint = sliderPath.positionAt(1); + let endPoint = hitObject.path.positionAt(1); if (endPoint && endPoint.x && endPoint.y) { hitObject.endPos = hitObject.pos.add(endPoint); @@ -363,24 +363,67 @@ class Beatmap { break; } } - let parentPoint = beatmap.TimingPoints.find(tp => !tp.inherited); - for (const tp of beatmap.TimingPoints) { - if (!tp.inherited) parentPoint = tp; + let tpIndex = -1, nextTime = Number.NEGATIVE_INFINITY; + let parentPoint, timingPoint, pixelsPerBeat = 0; - let bpm = Math.round(60000 / tp.beatLength); + for (const hitObject of beatmap.HitObjects) { + if (!(hitObject.hitType & HitType.Slider)) { + continue; + } + + while (hitObject.startTime >= nextTime) { + ++tpIndex; + + if (beatmap.TimingPoints.length > tpIndex + 1) { + nextTime = beatmap.TimingPoints[tpIndex + 1].time; + } + else { + nextTime = Number.POSITIVE_INFINITY; + } + + timingPoint = beatmap.TimingPoints[tpIndex]; + + if (timingPoint.inherited) { + parentPoint = timingPoint; + } + + let bpm = Math.round(60000 / timingPoint.beatLength); - if (bpm > 0) { - beatmap.General.MinBPM = Math.min(beatmap.General.MinBPM, bpm) || bpm; - beatmap.General.MaxBPM = Math.max(beatmap.General.MaxBPM, bpm) || bpm; + if (bpm > 0) { + beatmap.General.MinBPM = + Math.min(beatmap.General.MinBPM, bpm) || bpm; + beatmap.General.MaxBPM = + Math.max(beatmap.General.MaxBPM, bpm) || bpm; + } + + let velocityMultiplier = 1; + + if (!timingPoint.inherited && timingPoint.beatLength < 0) { + velocityMultiplier = -100 / timingPoint.beatLength; + } + + pixelsPerBeat = beatmap.Difficulty.SliderMultiplier * 100; + + if (beatmap.Version >= 8) { + pixelsPerBeat *= velocityMultiplier; + } } + + let beats = (hitObject.path.distance * hitObject.repeat) / pixelsPerBeat; + + let ticks = Math.ceil((beats - 0.01) / hitObject.repeat + * beatmap.Difficulty.SliderTickRate) - 1; - for (let hitObject of beatmap.HitObjects.filter( - ho => ho.startTime >= tp.time - )) { - if (hitObject.finalize) hitObject.finalize(tp, parentPoint, beatmap); - } + ticks *= hitObject.repeat; + ticks += hitObject.repeat + 1; + + hitObject.duration = beats * parentPoint.beatLength; + hitObject.endTime = hitObject.startTime + hitObject.duration; + + hitObject.combo = Math.max(0, ticks); } + return beatmap; } diff --git a/src/Rulesets/Osu/Objects/Slider.js b/src/Rulesets/Osu/Objects/Slider.js index 9fb1598..abd24e6 100644 --- a/src/Rulesets/Osu/Objects/Slider.js +++ b/src/Rulesets/Osu/Objects/Slider.js @@ -6,6 +6,7 @@ class Slider extends HitObject { this.curveType = hitObject.curveType; this.curvePoints = hitObject.curvePoints; this.repeat = hitObject.repeat; + this.path = hitObject.path; this.pixelLength = hitObject.pixelLength; if (hitObject.edgeHitSounds) { this.edgeHitSounds = hitObject.edgeHitSounds; @@ -13,32 +14,6 @@ class Slider extends HitObject { } } - finalize(timingPoint, parentTimingPoint, beatmap) { - let velocityMultiplier = 1; - let difficulty = beatmap.Difficulty; - - if (!timingPoint.inherited && timingPoint.beatLength < 0) { - velocityMultiplier = -100 / timingPoint.beatLength; - } - - let pixelsPerBeat = difficulty.SliderMultiplier * 100; - - if (beatmap.Version >= 8) { - pixelsPerBeat *= velocityMultiplier; - } - - let beats = (this.pixelLength * this.repeat) / pixelsPerBeat; - let parentBeatLength = parentTimingPoint ? parentTimingPoint.beatLength : 1; - let duration = Math.ceil(beats * parentBeatLength); - - this.endTime = this.startTime + duration; - this.combo = - Math.ceil((beats - 0.1) / this.repeat * difficulty.SliderTickRate) - 1; - - this.combo *= this.repeat; - this.combo += this.repeat + 1; - } - toOsu() { let arrayBuilder = []; arrayBuilder.push( From 91b7c5c9e174a4a0c109f8ff583436739796530e Mon Sep 17 00:00:00 2001 From: Kionell Date: Sun, 24 Jan 2021 19:55:30 +0200 Subject: [PATCH 02/10] Minor improvements to the descriptions and new method to check equality of two vectors --- src/Utils/Vector2.d.ts | 40 +++++++++++++++++++++++++++------------- src/Utils/Vector2.js | 4 ++++ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/Utils/Vector2.d.ts b/src/Utils/Vector2.d.ts index a4a782d..403ae1f 100644 --- a/src/Utils/Vector2.d.ts +++ b/src/Utils/Vector2.d.ts @@ -5,60 +5,74 @@ export default class Vector2 { public constructor(x: number, y: number); /** - * Adds a vector to current and returns a new instance + * Adds a vector to the current and returns a new instance. + * @param vec vector to add. */ public add(vec: Vector2): Vector2; /** - * Adds a vector to current and returns a new instance with single precision + * Adds a vector to the current and returns a new instance with single precision. + * @param vec vector to add. */ public fadd(vec: Vector2): Vector2; /** - * Subtracts a vector to current and returns a new instance + * Subtracts a vector from the current and returns a new instance. + * @param vec vector to substract. */ public subtract(vec: Vector2): Vector2; /** - * Subtracts a vector to current and returns a new instance with single precision + * Subtracts a vector from the current and returns a new instance with single precision. + * @param vec vector to substract. */ public fsubtract(vec: Vector2): Vector2; /** - * Scales the vector and returns a new instance + * Scales the current vector and returns a new instance. + * @param multiplier vector multiplier. */ public scale(multiplier: number): Vector2; /** - * Returns a new instance with a dot product of a vector. + * Divides the current vector and returns a new instance. + * @param multiplier vector divisor. */ - public dot(vec: Vector2): Vector2; + public divide(divisor: number): Vector2; /** - * Divides the vector and returns a new instance. + * Returns a new instance with a dot product of two vectors. + * @param vec second vector. */ - public divide(divisor: number): Vector2; + public dot(vec: Vector2): Vector2; /** - * Returns the length of the 2 points in the vector + * Returns a length of two points in a vector. */ public length(): number; /** - * Returns the single precision length of the 2 points in the vector + * Returns a single precision length of two points in a vector. */ public flength(): number; /** - * Returns the distance between 2 vectors + * Returns a distance between two vectors. + * @param vec second vector. */ public distance(vec: Vector2): number; /** - * Returns normaliled vector + * Returns a normalized vector. */ public normalize(): Vector2; + /** + * Checks if two vectors are equal. + * @param vec second vector. + */ + public equals(vec: Vector2): boolean; + /** * Clones the current vector and returns it * Kinda useless but ¯\_(ツ)_/¯ diff --git a/src/Utils/Vector2.js b/src/Utils/Vector2.js index 654771a..4a20a3c 100644 --- a/src/Utils/Vector2.js +++ b/src/Utils/Vector2.js @@ -4,6 +4,10 @@ class Vector2 { this.y = y; } + equals(vec) { + return this.x === vec.x && this.y === vec.y; + } + add(vec) { return new Vector2(this.x + vec.x, this.y + vec.y); } From c7496f9fd344de8201e66615b13d784ae7a40327 Mon Sep 17 00:00:00 2001 From: Kionell Date: Sun, 24 Jan 2021 19:57:55 +0200 Subject: [PATCH 03/10] Fix for catmull sliders --- src/Utils/PathApproximator.js | 2 +- src/Utils/SliderPath.js | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Utils/PathApproximator.js b/src/Utils/PathApproximator.js index f3f3b64..6816810 100644 --- a/src/Utils/PathApproximator.js +++ b/src/Utils/PathApproximator.js @@ -76,7 +76,7 @@ class PathApproximator static approximateCatmull(controlPoints) { let result = []; - let controlPointsLength = controlPoints.Length; + let controlPointsLength = controlPoints.length; for (let i = 0; i < controlPointsLength - 1; i++) { let v1 = i > 0 ? controlPoints[i - 1] : controlPoints[i]; diff --git a/src/Utils/SliderPath.js b/src/Utils/SliderPath.js index 1912974..c7a79ac 100644 --- a/src/Utils/SliderPath.js +++ b/src/Utils/SliderPath.js @@ -94,14 +94,14 @@ class SliderPath _calculatePath() { + this.calculatedPath = []; + let controlPointsLength = this.controlPoints.length; if (controlPointsLength === 0) { return; } - this.calculatedPath = []; - let vertices = []; for (let i = 0; i < controlPointsLength; i++) { @@ -120,8 +120,9 @@ class SliderPath let segmentType = this.controlPoints[start].type || 'L'; for (let t of this._calculateSubPath(segmentVertices, segmentType)) { - if (this.calculatedPath.length === 0 - || this.calculatedPath[this.calculatedPath.length - 1] != t) { + let last = this.calculatedPath[this.calculatedPath.length - 1]; + + if (this.calculatedPath.length === 0 || !last.equals(t)) { this.calculatedPath.push(t); } } From 61cf7c386a88e3c97a816297cc83a5ee406f3e85 Mon Sep 17 00:00:00 2001 From: Kionell Date: Sun, 24 Jan 2021 19:59:36 +0200 Subject: [PATCH 04/10] Updated package version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8a0244a..c28e2c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "osu-bpdpc", - "version": "0.2.3", + "version": "0.3.0", "description": "Osu beatmap parser, difficulty and performance calculator", "main": "index.js", "engines": { From 5fe03c3ca26b6dfd68bbb67687a2df9cda8f230b Mon Sep 17 00:00:00 2001 From: Kionell Date: Sun, 24 Jan 2021 21:29:36 +0200 Subject: [PATCH 05/10] A new method for determining the progress of the slider based on the current span --- src/Utils/SliderPath.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Utils/SliderPath.js b/src/Utils/SliderPath.js index c7a79ac..6fc26e8 100644 --- a/src/Utils/SliderPath.js +++ b/src/Utils/SliderPath.js @@ -66,6 +66,18 @@ class SliderPath path.push(this._interpolateVertices(i, d1)); } + /** + * Computes the progress along the curve relative to how much of the hit object has been completed. + * @param obj the curve. + * @param progress where 0 is the start time of the hit object and 1 is the end time of the hit object. + */ + progressAt(obj, progress) + { + let p = progress * obj.repeat % 1; + + return Math.trunc(progress * obj.repeat) % 2 ? 1 - p : p; + } + /** * Computes the position on the slider at a given progress that ranges from 0 (beginning of the path) * to 1 (end of the path). From 5b5618f2f763b68364350185fc013db5910420c7 Mon Sep 17 00:00:00 2001 From: Kionell Date: Sun, 24 Jan 2021 21:34:05 +0200 Subject: [PATCH 06/10] Fix for zero values and number of spans --- src/Beatmap.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Beatmap.js b/src/Beatmap.js index b255567..d4b9e45 100644 --- a/src/Beatmap.js +++ b/src/Beatmap.js @@ -282,13 +282,15 @@ class Beatmap { hitObject.pathPoints.push(point); }); - hitObject.path = new SliderPath( + let path = new SliderPath( hitObject.pathPoints, hitObject.pixelLength ); - let endPoint = hitObject.path.positionAt(1); + let endPoint = path.positionAt(path.progressAt(hitObject, 1)); - if (endPoint && endPoint.x && endPoint.y) { + hitObject.path = path; + + if (endPoint && !isNaN(endPoint.x) && !isNaN(endPoint.y)) { hitObject.endPos = hitObject.pos.add(endPoint); } else { // If endPosition could not be calculated, approximate it by setting it to the last point From c37496b263e5e0b99b66a0efa1aa89b3260585dd Mon Sep 17 00:00:00 2001 From: Kionell Date: Mon, 1 Feb 2021 18:26:39 +0200 Subject: [PATCH 07/10] Replace isNaN() with isFinite() to avoid non-number values --- src/Beatmap.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Beatmap.js b/src/Beatmap.js index d4b9e45..7ea2f48 100644 --- a/src/Beatmap.js +++ b/src/Beatmap.js @@ -290,11 +290,13 @@ class Beatmap { hitObject.path = path; - if (endPoint && !isNaN(endPoint.x) && !isNaN(endPoint.y)) { + if (Number.isFinite(endPoint.x) && Number.isFinite(endPoint.y)) { hitObject.endPos = hitObject.pos.add(endPoint); } else { - // If endPosition could not be calculated, approximate it by setting it to the last point - hitObject.endPos = hitObject.curvePoints[hitObject.curvePoints.length - 1]; + // If endPosition could not be calculated, + // approximate it by setting it to the last point + hitObject.endPos = + hitObject.curvePoints[hitObject.curvePoints.length - 1]; } if (edgeHitSounds) { @@ -302,6 +304,7 @@ class Beatmap { .split("|") .map(v => parseInt(v, 10)); } + if (edgeAdditions) { hitObject.edgeAdditions = edgeAdditions .split("|") From 185b550e586b9c2ab0d6b7b85e4d38dee279f7ac Mon Sep 17 00:00:00 2001 From: Kionell Date: Sat, 6 Feb 2021 02:57:15 +0200 Subject: [PATCH 08/10] Added missing properties in beatmap declaration file --- src/Beatmap.d.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Beatmap.d.ts b/src/Beatmap.d.ts index 44440e5..f0f182b 100644 --- a/src/Beatmap.d.ts +++ b/src/Beatmap.d.ts @@ -4,6 +4,8 @@ import HitSound from "./Enum/HitSound"; import Colour from "./Colour"; export default class Beatmap { + public Version: number; + public General: { AudioFilename: string; AudioLeadin: number; @@ -65,14 +67,20 @@ export default class Beatmap { public static fromOsu(data: string): Beatmap; /** - * Takes a JSON string and returns a new Beatmap instance + * Returns a string for an .osu file */ - public static fromJSON(data: string): Beatmap; + public toOsu(): string; /** - * Returns a string for an .osu file + * Takes a JSON string and returns a new Beatmap instance */ - public toOsu(): string; + public static fromJSON(data: string): Beatmap; + + public get countNormal(): number; + public get countSlider(): number; + public get countSpinner(): number; + public get countObjects(): number; + public get maxCombo(): number; } export interface Break { From 5ed1177723c05f9049d70e184ce487a2d5f1b628 Mon Sep 17 00:00:00 2001 From: Kionell Date: Tue, 9 Feb 2021 23:44:41 +0200 Subject: [PATCH 09/10] Beatmap length calculation --- src/Beatmap.d.ts | 1 + src/Beatmap.js | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/Beatmap.d.ts b/src/Beatmap.d.ts index f0f182b..478def9 100644 --- a/src/Beatmap.d.ts +++ b/src/Beatmap.d.ts @@ -81,6 +81,7 @@ export default class Beatmap { public get countSpinner(): number; public get countObjects(): number; public get maxCombo(): number; + public get length(): number; } export interface Break { diff --git a/src/Beatmap.js b/src/Beatmap.js index 7ea2f48..3886d50 100644 --- a/src/Beatmap.js +++ b/src/Beatmap.js @@ -574,6 +574,13 @@ class Beatmap { get maxCombo() { return this.HitObjects.reduce((a, c) => a + c.combo, 0); } + + get length() { + let first = this.HitObjects[0]; + let last = this.HitObjects[this.HitObjects.length - 1]; + + return last.endTime - first.startTime; + } } module.exports = Beatmap; From f4158b5075a808b5999a53de6634d67e09f15b08 Mon Sep 17 00:00:00 2001 From: Kionell Date: Sun, 14 Feb 2021 02:03:25 +0200 Subject: [PATCH 10/10] Finding the end time of a beatmap --- src/Beatmap.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Beatmap.js b/src/Beatmap.js index 3886d50..0b53ac1 100644 --- a/src/Beatmap.js +++ b/src/Beatmap.js @@ -576,10 +576,12 @@ class Beatmap { } get length() { - let first = this.HitObjects[0]; - let last = this.HitObjects[this.HitObjects.length - 1]; + let startTime = this.HitObjects[0].startTime; + let endTime = this.HitObjects.reduce((time, ho) => { + return Math.max(time, ho.endTime || ho.startTime); + }, 0); - return last.endTime - first.startTime; + return endTime - startTime; } }