Basic error bar

This commit is contained in:
nise.moe 2024-03-06 11:41:19 +01:00
parent 4ddd80bae0
commit 55e4b745ef
7 changed files with 142 additions and 28 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -2,7 +2,7 @@ import { Vector2 } from "osu-classes";
import p5 from "p5"; import p5 from "p5";
import { loadImageAsync } from "@/utils"; import { loadImageAsync } from "@/utils";
import { Md5 } from "ts-md5"; import { Md5 } from "ts-md5";
import {OsuRenderer} from "@/osu/OsuRenderer"; import {Judgements, OsuRenderer} from "@/osu/OsuRenderer";
export class Drawer { export class Drawer {
@ -27,6 +27,9 @@ export class Drawer {
default7: undefined as any as p5.Image, default7: undefined as any as p5.Image,
default8: undefined as any as p5.Image, default8: undefined as any as p5.Image,
default9: undefined as any as p5.Image, default9: undefined as any as p5.Image,
hitmiss: undefined as any as p5.Image,
hit50: undefined as any as p5.Image,
hit100: undefined as any as p5.Image
}; };
private static p: p5; private static p: p5;
@ -50,27 +53,127 @@ export class Drawer {
this.p.drawingContext.globalAlpha = opacity; this.p.drawingContext.globalAlpha = opacity;
} }
static drawCircleJudgement( /**
* Draws the error bar for the given judgements.
* @param judgements
*/
static drawErrorBar(judgements: Judgements[]) {
let width = 512;
let height = 384;
// TODO: Calculate real values
// @https://osu.ppy.sh/wiki/en/Beatmap/Overall_difficulty
let hitWindow300 = 50;
let hitWindow100 = 100;
let hitWindow50 = 150;
let barWidth = 512 * 0.8; // Width of the error bar, 80% of canvas width
let barHeight = 20; // The height of the error bar
let barX = (width - barWidth) / 2; // Center the bar on the canvas
let barY = height + 52;
// Draw the background for the 50's hit window (biggest)
Drawer.p.fill(217, 174, 71);
Drawer.p.rect(barX + barWidth / 2 - (hitWindow50), barY, hitWindow50 * 2, barHeight);
// Draw the background for the 100's hit window (middle)
Drawer.p.fill(87, 225, 19);
Drawer.p.rect(barX + barWidth / 2 - (hitWindow100), barY, hitWindow100 * 2, barHeight);
// Draw the background for the 300's hit window (smallest/closer to the center)
Drawer.p.fill(50, 188, 231);
Drawer.p.rect(barX + barWidth / 2 - (hitWindow300), barY, hitWindow300 * 2, barHeight);
let tickDensity = new Array(Math.ceil(barWidth / 5)).fill(0); // Example: Each segment is 5 pixels wide
judgements.forEach(judgement => {
let judgementX = barX + barWidth / 2 + judgement.error / 2;
this.drawJudgementTick(judgementX, barY, barHeight, tickDensity);
});
// Draw a vertical bar in the exact center of the hit error bar
// Drawer.p.push();
// Drawer.p.noFill();
// Drawer.p.stroke(255);
// Drawer.p.strokeWeight(2);
// Drawer.p.line(barX + barWidth / 2, barY - 5, barX + barWidth / 2, barY + barHeight + 5);
Drawer.p.pop();
}
//@ts-ignore
static drawJudgementTick(x, y, height, tickDensity) {
let barWidth = 512 * 0.8;
let barX = (512 - barWidth) / 2; // Center the bar on the canvas
const segmentIndex = Math.floor((x - barX) / 5); // Assuming 5-pixel segments
tickDensity[segmentIndex]++;
const opacity = this.calculateOpacity(tickDensity[segmentIndex]);
Drawer.p.stroke(255, 255, 255, 255);
Drawer.p.strokeWeight(2);
Drawer.p.strokeCap(Drawer.p.SQUARE); // Slightly thicker ends for clarity
Drawer.p.colorMode(Drawer.p.RGB);
Drawer.p.stroke(Drawer.p.color(255, 255, 255, 255 * opacity)); // Apply opacity
Drawer.p.line(x, y, x, y + height);
}
/**
* Calculate the opacity of the judgement tick. Lower opacity means less amount.
*
* @param count The amount of ticks
* @returns The opacity of the tick (0-1)
*/
static calculateOpacity(count: number): number {
const MIN_OPACITY = 0.55; // Minimum opacity enforced
const MAX_OPACITY = 1; // Maximum opacity
const MAX_DENSITY = 50; // Opacity reaches maximum at this tick count
// Calculate linear opacity based on count
let opacity = (count / MAX_DENSITY) * (MAX_OPACITY - MIN_OPACITY) + MIN_OPACITY;
// Ensure opacity is at least MIN_OPACITY and at most MAX_OPACITY
opacity = Math.max(opacity, MIN_OPACITY);
opacity = Math.min(opacity, MAX_OPACITY);
return opacity;
}
static drawJudgement(
position: Vector2, position: Vector2,
radius: number, judgement: string,
judgement: string opacity: number
) { ) {
Drawer.p.push(); Drawer.p.push();
Drawer.p.strokeWeight(2); //@ts-ignore
if (judgement === "OK") { Drawer.p.drawingContext.globalAlpha = opacity;
Drawer.p.stroke(`rgb(106, 176, 76)`); if (judgement === "ONE_HUNDRED") {
Drawer.p.fill(`rgb(106, 176, 76)`); Drawer.p.image(
this.images.hit100,
position.x,
position.y,
this.images.hit100.width,
this.images.hit100.height
);
} }
if (judgement === "MEH") { if (judgement === "FIFTY") {
Drawer.p.stroke(`rgb(241, 196, 15)`); Drawer.p.image(
Drawer.p.fill(`rgb(241, 196, 15)`); this.images.hit50,
position.x,
position.y,
this.images.hit50.width,
this.images.hit50.height
);
} }
if (judgement === "MISS") { if (judgement === "MISS") {
Drawer.p.stroke(`rgb(231, 76, 60)`); Drawer.p.image(
Drawer.p.fill(`rgb(231, 76, 60)`); this.images.hitmiss,
} position.x,
if (judgement !== "GREAT") { position.y,
Drawer.p.circle(position.x, position.y, radius * 2); this.images.hitmiss.width,
this.images.hitmiss.height
);
} }
Drawer.p.pop(); Drawer.p.pop();
} }
@ -259,14 +362,12 @@ export class Drawer {
let size = 4; let size = 4;
if (lastFrame.button.mouseLeft1 || lastFrame.button.mouseLeft2) { if (lastFrame.button.mouseLeft1 || lastFrame.button.mouseLeft2) {
Drawer.p.stroke("#BB6BD9"); Drawer.p.stroke("#BB6BD9");
// TODO: Draw an X instead
Drawer.p.line(lastFrame.position.x - size, lastFrame.position.y - size, lastFrame.position.x + size, lastFrame.position.y + size); Drawer.p.line(lastFrame.position.x - size, lastFrame.position.y - size, lastFrame.position.x + size, lastFrame.position.y + size);
Drawer.p.line(lastFrame.position.x + size, lastFrame.position.y - size, lastFrame.position.x - size, lastFrame.position.y + size); Drawer.p.line(lastFrame.position.x + size, lastFrame.position.y - size, lastFrame.position.x - size, lastFrame.position.y + size);
} }
if (lastFrame.button.mouseRight1 || lastFrame.button.mouseRight2) { if (lastFrame.button.mouseRight1 || lastFrame.button.mouseRight2) {
Drawer.p.stroke("#F2994A"); Drawer.p.stroke("#F2994A");
// TODO: Draw an X instead
Drawer.p.line(lastFrame.position.x - size, lastFrame.position.y - size, lastFrame.position.x + size, lastFrame.position.y + size); Drawer.p.line(lastFrame.position.x - size, lastFrame.position.y - size, lastFrame.position.x + size, lastFrame.position.y + size);
Drawer.p.line(lastFrame.position.x + size, lastFrame.position.y - size, lastFrame.position.x - size, lastFrame.position.y + size); Drawer.p.line(lastFrame.position.x + size, lastFrame.position.y - size, lastFrame.position.x - size, lastFrame.position.y + size);
} }

View File

@ -26,7 +26,7 @@ export enum OsuRendererEvents {
SETTINGS = "SETTINGS", SETTINGS = "SETTINGS",
} }
interface Judgements { export interface Judgements {
x: number; x: number;
y: number; y: number;
time: number; time: number;
@ -141,6 +141,8 @@ export class OsuRenderer {
this.updateStatistics(); this.updateStatistics();
} }
Drawer.drawErrorBar(this.judgements.filter(j => j.time < this.time && j.time - this.time > -10000));
this.lastRender = Date.now(); this.lastRender = Date.now();
this.renderPath(this.replay.replay!, Drawer.images.cursor); this.renderPath(this.replay.replay!, Drawer.images.cursor);
@ -475,15 +477,16 @@ export class OsuRenderer {
hitObject.currentComboIndex hitObject.currentComboIndex
); );
// if (GameplayAnalyzer.renderJudgements[hitObject.startTime]) { let judgementsToShow = this.judgements.filter(j => j.time < this.time && j.time > hitObject.startTime);
// Drawer.setDrawingOpacity(opacity / 2); if(judgementsToShow.length > 0) {
// let judgement = judgementsToShow[0];
// Drawer.drawCircleJudgement( Drawer.drawJudgement(
// hitObject.stackedStartPosition, hitObject.stackedStartPosition,
// hitObject.radius, judgement.type,
// GameplayAnalyzer.renderJudgements[hitObject.startTime] opacity
// ); );
// } }
Drawer.endDrawing(); Drawer.endDrawing();
return arScale; return arScale;
} }
@ -531,6 +534,16 @@ export class OsuRenderer {
Drawer.drawSliderFollowPoint(sliderPos, hitObject.radius); Drawer.drawSliderFollowPoint(sliderPos, hitObject.radius);
} }
let judgementsToShow = this.judgements.filter(j => j.time < this.time && j.time > hitObject.startTime);
if(judgementsToShow.length > 0) {
let judgement = judgementsToShow[0];
Drawer.drawJudgement(
hitObject.stackedStartPosition,
judgement.type,
opacity
);
}
Drawer.endDrawing(); Drawer.endDrawing();
return arScale; return arScale;
} }