Basic error bar
This commit is contained in:
parent
4ddd80bae0
commit
55e4b745ef
BIN
nise-replay-viewer/public/hit100.png
Normal file
BIN
nise-replay-viewer/public/hit100.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
BIN
nise-replay-viewer/public/hit300.png
Normal file
BIN
nise-replay-viewer/public/hit300.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
BIN
nise-replay-viewer/public/hit50.png
Normal file
BIN
nise-replay-viewer/public/hit50.png
Normal file
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 |
BIN
nise-replay-viewer/public/hitmiss.png
Normal file
BIN
nise-replay-viewer/public/hitmiss.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user