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 { loadImageAsync } from "@/utils";
|
||||
import { Md5 } from "ts-md5";
|
||||
import {OsuRenderer} from "@/osu/OsuRenderer";
|
||||
import {Judgements, OsuRenderer} from "@/osu/OsuRenderer";
|
||||
|
||||
export class Drawer {
|
||||
|
||||
@ -27,6 +27,9 @@ export class Drawer {
|
||||
default7: undefined as any as p5.Image,
|
||||
default8: 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;
|
||||
|
||||
@ -50,27 +53,127 @@ export class Drawer {
|
||||
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,
|
||||
radius: number,
|
||||
judgement: string
|
||||
judgement: string,
|
||||
opacity: number
|
||||
) {
|
||||
Drawer.p.push();
|
||||
Drawer.p.strokeWeight(2);
|
||||
if (judgement === "OK") {
|
||||
Drawer.p.stroke(`rgb(106, 176, 76)`);
|
||||
Drawer.p.fill(`rgb(106, 176, 76)`);
|
||||
//@ts-ignore
|
||||
Drawer.p.drawingContext.globalAlpha = opacity;
|
||||
if (judgement === "ONE_HUNDRED") {
|
||||
Drawer.p.image(
|
||||
this.images.hit100,
|
||||
position.x,
|
||||
position.y,
|
||||
this.images.hit100.width,
|
||||
this.images.hit100.height
|
||||
);
|
||||
}
|
||||
if (judgement === "MEH") {
|
||||
Drawer.p.stroke(`rgb(241, 196, 15)`);
|
||||
Drawer.p.fill(`rgb(241, 196, 15)`);
|
||||
if (judgement === "FIFTY") {
|
||||
Drawer.p.image(
|
||||
this.images.hit50,
|
||||
position.x,
|
||||
position.y,
|
||||
this.images.hit50.width,
|
||||
this.images.hit50.height
|
||||
);
|
||||
}
|
||||
if (judgement === "MISS") {
|
||||
Drawer.p.stroke(`rgb(231, 76, 60)`);
|
||||
Drawer.p.fill(`rgb(231, 76, 60)`);
|
||||
}
|
||||
if (judgement !== "GREAT") {
|
||||
Drawer.p.circle(position.x, position.y, radius * 2);
|
||||
Drawer.p.image(
|
||||
this.images.hitmiss,
|
||||
position.x,
|
||||
position.y,
|
||||
this.images.hitmiss.width,
|
||||
this.images.hitmiss.height
|
||||
);
|
||||
}
|
||||
Drawer.p.pop();
|
||||
}
|
||||
@ -259,14 +362,12 @@ export class Drawer {
|
||||
let size = 4;
|
||||
if (lastFrame.button.mouseLeft1 || lastFrame.button.mouseLeft2) {
|
||||
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);
|
||||
}
|
||||
|
||||
if (lastFrame.button.mouseRight1 || lastFrame.button.mouseRight2) {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ export enum OsuRendererEvents {
|
||||
SETTINGS = "SETTINGS",
|
||||
}
|
||||
|
||||
interface Judgements {
|
||||
export interface Judgements {
|
||||
x: number;
|
||||
y: number;
|
||||
time: number;
|
||||
@ -141,6 +141,8 @@ export class OsuRenderer {
|
||||
this.updateStatistics();
|
||||
}
|
||||
|
||||
Drawer.drawErrorBar(this.judgements.filter(j => j.time < this.time && j.time - this.time > -10000));
|
||||
|
||||
this.lastRender = Date.now();
|
||||
|
||||
this.renderPath(this.replay.replay!, Drawer.images.cursor);
|
||||
@ -475,15 +477,16 @@ export class OsuRenderer {
|
||||
hitObject.currentComboIndex
|
||||
);
|
||||
|
||||
// if (GameplayAnalyzer.renderJudgements[hitObject.startTime]) {
|
||||
// Drawer.setDrawingOpacity(opacity / 2);
|
||||
//
|
||||
// Drawer.drawCircleJudgement(
|
||||
// hitObject.stackedStartPosition,
|
||||
// hitObject.radius,
|
||||
// GameplayAnalyzer.renderJudgements[hitObject.startTime]
|
||||
// );
|
||||
// }
|
||||
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();
|
||||
return arScale;
|
||||
}
|
||||
@ -531,6 +534,16 @@ export class OsuRenderer {
|
||||
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();
|
||||
return arScale;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user