2024-03-03 15:22:03 +00:00
|
|
|
import { Vector2 } from "osu-classes";
|
|
|
|
|
import p5 from "p5";
|
|
|
|
|
import { loadImageAsync } from "@/utils";
|
2024-03-03 16:26:45 +00:00
|
|
|
import { Md5 } from "ts-md5";
|
2024-03-03 17:17:14 +00:00
|
|
|
import {OsuRenderer} from "@/osu/OsuRenderer";
|
2024-03-03 15:22:03 +00:00
|
|
|
|
|
|
|
|
export class Drawer {
|
2024-03-03 15:59:19 +00:00
|
|
|
|
2024-03-03 16:26:45 +00:00
|
|
|
private static imageCache: Record<string, p5.Graphics> = {};
|
|
|
|
|
|
2024-03-03 15:22:03 +00:00
|
|
|
static images = {
|
|
|
|
|
cursor: undefined as any as p5.Image,
|
2024-03-04 14:26:11 +00:00
|
|
|
cursor2: undefined as any as p5.Image,
|
2024-03-03 15:22:03 +00:00
|
|
|
cursortrail: undefined as any as p5.Image,
|
|
|
|
|
hitcircle: undefined as any as p5.Image,
|
|
|
|
|
hitcircleoverlay: undefined as any as p5.Image,
|
|
|
|
|
radix: undefined as any as p5.Image,
|
|
|
|
|
sliderfollowcircle: undefined as any as p5.Image,
|
|
|
|
|
sliderb0: undefined as any as p5.Image,
|
|
|
|
|
default0: undefined as any as p5.Image,
|
|
|
|
|
default1: undefined as any as p5.Image,
|
|
|
|
|
default2: undefined as any as p5.Image,
|
|
|
|
|
default3: undefined as any as p5.Image,
|
|
|
|
|
default4: undefined as any as p5.Image,
|
|
|
|
|
default5: undefined as any as p5.Image,
|
|
|
|
|
default6: undefined as any as p5.Image,
|
|
|
|
|
default7: undefined as any as p5.Image,
|
|
|
|
|
default8: undefined as any as p5.Image,
|
|
|
|
|
default9: undefined as any as p5.Image,
|
|
|
|
|
};
|
|
|
|
|
private static p: p5;
|
|
|
|
|
|
|
|
|
|
static setP(_p: p5) {
|
|
|
|
|
this.p = _p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static async loadDefaultImages() {
|
|
|
|
|
for (const imageName of Object.keys(Drawer.images)) {
|
|
|
|
|
Drawer.images[imageName as keyof typeof Drawer.images] =
|
|
|
|
|
await loadImageAsync(`/${imageName}.png`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static setImages(images: typeof this.images) {
|
|
|
|
|
this.images = images;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static setDrawingOpacity(opacity: number) {
|
|
|
|
|
//@ts-ignore
|
|
|
|
|
this.p.drawingContext.globalAlpha = opacity;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static drawCircleJudgement(
|
|
|
|
|
position: Vector2,
|
|
|
|
|
radius: number,
|
|
|
|
|
judgement: string
|
|
|
|
|
) {
|
|
|
|
|
Drawer.p.push();
|
|
|
|
|
Drawer.p.strokeWeight(2);
|
|
|
|
|
if (judgement === "OK") {
|
|
|
|
|
Drawer.p.stroke(`rgb(106, 176, 76)`);
|
|
|
|
|
Drawer.p.fill(`rgb(106, 176, 76)`);
|
|
|
|
|
}
|
|
|
|
|
if (judgement === "MEH") {
|
|
|
|
|
Drawer.p.stroke(`rgb(241, 196, 15)`);
|
|
|
|
|
Drawer.p.fill(`rgb(241, 196, 15)`);
|
|
|
|
|
}
|
|
|
|
|
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.pop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static drawSliderFollowPoint(position: Vector2, radius: number) {
|
|
|
|
|
Drawer.p.image(
|
|
|
|
|
this.images.sliderfollowcircle,
|
|
|
|
|
position.x,
|
|
|
|
|
position.y,
|
|
|
|
|
radius * 2,
|
|
|
|
|
radius * 2
|
|
|
|
|
);
|
|
|
|
|
Drawer.p.push();
|
|
|
|
|
Drawer.p.fill(255, 255, 255, 18);
|
|
|
|
|
Drawer.p.circle(position.x, position.y, radius * 4);
|
|
|
|
|
Drawer.p.pop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static drawHitCircle(position: Vector2, radius: number, comboNumber: number) {
|
|
|
|
|
Drawer.p.push();
|
|
|
|
|
Drawer.p.noStroke();
|
|
|
|
|
Drawer.p.fill(160);
|
|
|
|
|
Drawer.p.image(
|
|
|
|
|
this.images.hitcircle,
|
|
|
|
|
position.x,
|
|
|
|
|
position.y,
|
|
|
|
|
radius * 2,
|
|
|
|
|
radius * 2
|
|
|
|
|
);
|
|
|
|
|
Drawer.p.image(
|
|
|
|
|
this.images.hitcircleoverlay,
|
|
|
|
|
position.x,
|
|
|
|
|
position.y,
|
|
|
|
|
radius * 2,
|
|
|
|
|
radius * 2
|
|
|
|
|
);
|
|
|
|
|
this.drawNumberWithSprites(
|
|
|
|
|
comboNumber + 1,
|
|
|
|
|
new Vector2(position.x - 0.5, position.y),
|
|
|
|
|
radius * 0.4
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Drawer.p.pop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static drawApproachCircle(
|
|
|
|
|
position: Vector2,
|
|
|
|
|
radius: number,
|
|
|
|
|
arScale: number
|
|
|
|
|
) {
|
|
|
|
|
if (arScale == 1) return;
|
|
|
|
|
Drawer.p.push();
|
|
|
|
|
Drawer.p.noFill();
|
|
|
|
|
Drawer.p.stroke("white");
|
|
|
|
|
Drawer.p.strokeWeight(3);
|
|
|
|
|
Drawer.p.circle(position.x, position.y, radius * 2 * arScale);
|
|
|
|
|
Drawer.p.pop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static drawSliderBody(origin: Vector2, path: Vector2[], radius: number) {
|
|
|
|
|
Drawer.p.push();
|
|
|
|
|
|
2024-03-03 16:26:45 +00:00
|
|
|
const cacheKey = Md5.hashStr(JSON.stringify(origin) + JSON.stringify(path) + JSON.stringify(radius));
|
|
|
|
|
if (!this.imageCache[cacheKey]) {
|
|
|
|
|
const g = Drawer.p.createGraphics(512 * 4, 384 * 4);
|
|
|
|
|
g.scale(2);
|
|
|
|
|
g.translate(512 - 256, 384 - 192);
|
|
|
|
|
//@ts-ignore
|
|
|
|
|
const ctx = g.drawingContext;
|
|
|
|
|
ctx.lineCap = "round";
|
|
|
|
|
ctx.lineJoin = "round";
|
|
|
|
|
g.translate(origin.x, origin.y);
|
|
|
|
|
g.noFill();
|
|
|
|
|
|
|
|
|
|
g.strokeWeight(radius * 2 - 10);
|
|
|
|
|
|
|
|
|
|
g.stroke(255);
|
|
|
|
|
g.beginShape();
|
|
|
|
|
for (const node of path) {
|
|
|
|
|
g.vertex(node.x, node.y);
|
|
|
|
|
}
|
|
|
|
|
g.endShape();
|
2024-03-03 15:59:19 +00:00
|
|
|
|
2024-03-03 16:26:45 +00:00
|
|
|
g.strokeWeight(radius * 2 - 17);
|
|
|
|
|
g.stroke(10);
|
2024-03-03 15:22:03 +00:00
|
|
|
g.beginShape();
|
|
|
|
|
for (const node of path) {
|
|
|
|
|
g.vertex(node.x, node.y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g.endShape();
|
2024-03-03 15:59:19 +00:00
|
|
|
|
2024-03-03 16:26:45 +00:00
|
|
|
for (let i = 0; i < radius * 2 - 17; i += 2) {
|
|
|
|
|
g.strokeWeight(radius * 2 - 17 - i);
|
|
|
|
|
g.stroke(Math.round((i / (radius * 2 - 17)) * 45));
|
|
|
|
|
g.beginShape();
|
|
|
|
|
for (const node of path) {
|
|
|
|
|
g.vertex(node.x, node.y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g.endShape();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.imageCache[cacheKey] = g;
|
|
|
|
|
}
|
2024-03-03 15:22:03 +00:00
|
|
|
Drawer.p.imageMode(Drawer.p.CORNER);
|
2024-03-03 16:26:45 +00:00
|
|
|
Drawer.p.image(this.imageCache[cacheKey], -256, -192, 512 * 2, 384 * 2);
|
2024-03-03 15:22:03 +00:00
|
|
|
|
|
|
|
|
Drawer.p.pop();
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-03 16:26:45 +00:00
|
|
|
|
2024-03-03 15:22:03 +00:00
|
|
|
static drawCursorPath(
|
2024-03-04 14:26:11 +00:00
|
|
|
cursorImage: p5.Image,
|
2024-03-03 15:22:03 +00:00
|
|
|
path: {
|
|
|
|
|
position: Vector2;
|
2024-03-03 17:17:14 +00:00
|
|
|
time: number;
|
2024-03-03 15:22:03 +00:00
|
|
|
button: {
|
|
|
|
|
mouseLeft1: boolean;
|
|
|
|
|
mouseLeft2: boolean;
|
|
|
|
|
mouseRight1: boolean;
|
|
|
|
|
mouseRight2: boolean;
|
|
|
|
|
};
|
|
|
|
|
}[],
|
|
|
|
|
cursor: {
|
|
|
|
|
position: Vector2;
|
|
|
|
|
button: {
|
|
|
|
|
mouseLeft1: boolean;
|
|
|
|
|
mouseLeft2: boolean;
|
|
|
|
|
mouseRight1: boolean;
|
|
|
|
|
mouseRight2: boolean;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
) {
|
|
|
|
|
Drawer.p.push();
|
|
|
|
|
//@ts-ignore
|
|
|
|
|
const ctx = Drawer.p.drawingContext;
|
|
|
|
|
ctx.lineCap = "round";
|
|
|
|
|
ctx.lineJoin = "round";
|
|
|
|
|
Drawer.p.noFill();
|
|
|
|
|
Drawer.p.strokeWeight(1.5);
|
|
|
|
|
Drawer.p.stroke("white");
|
|
|
|
|
|
|
|
|
|
for (let i = 1; i < path.length; i++) {
|
|
|
|
|
const lastFrame = path[i - 1];
|
|
|
|
|
const frame = path[i];
|
|
|
|
|
|
2024-03-03 17:17:14 +00:00
|
|
|
if(!OsuRenderer.settings.showFutureCursorPath && frame.time > OsuRenderer.time) {
|
|
|
|
|
continue;
|
2024-03-03 15:22:03 +00:00
|
|
|
}
|
2024-03-03 17:17:14 +00:00
|
|
|
|
2024-03-04 16:22:06 +00:00
|
|
|
if(OsuRenderer.settings.showCursorPath) {
|
|
|
|
|
Drawer.p.stroke("white");
|
|
|
|
|
Drawer.p.line(
|
|
|
|
|
lastFrame.position.x,
|
|
|
|
|
lastFrame.position.y,
|
|
|
|
|
frame.position.x,
|
|
|
|
|
frame.position.y
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-03 17:17:14 +00:00
|
|
|
if(OsuRenderer.settings.showKeyPress) {
|
|
|
|
|
|
2024-03-03 17:27:44 +00:00
|
|
|
if(!OsuRenderer.settings.showFutureCursorPath && lastFrame.time > OsuRenderer.time) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-04 16:22:06 +00:00
|
|
|
let size = 4;
|
2024-03-03 17:17:14 +00:00
|
|
|
if (lastFrame.button.mouseLeft1 || lastFrame.button.mouseLeft2) {
|
|
|
|
|
Drawer.p.stroke("#BB6BD9");
|
2024-03-04 16:22:06 +00:00
|
|
|
// 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);
|
2024-03-03 17:17:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (lastFrame.button.mouseRight1 || lastFrame.button.mouseRight2) {
|
|
|
|
|
Drawer.p.stroke("#F2994A");
|
2024-03-04 16:22:06 +00:00
|
|
|
// 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);
|
2024-03-03 17:17:14 +00:00
|
|
|
}
|
|
|
|
|
|
2024-03-03 15:22:03 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cursor.position)
|
|
|
|
|
Drawer.p.image(
|
2024-03-04 14:26:11 +00:00
|
|
|
cursorImage,
|
2024-03-03 15:22:03 +00:00
|
|
|
cursor.position.x,
|
|
|
|
|
cursor.position.y,
|
|
|
|
|
55,
|
|
|
|
|
55
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Drawer.p.pop();
|
|
|
|
|
}
|
|
|
|
|
static drawNumberWithSprites(
|
|
|
|
|
number: number,
|
|
|
|
|
position: Vector2,
|
|
|
|
|
size: number
|
|
|
|
|
) {
|
|
|
|
|
Drawer.p.push();
|
|
|
|
|
Drawer.p.imageMode(Drawer.p.CORNER);
|
|
|
|
|
const digits = number.toString().split("");
|
|
|
|
|
const digitWidth = size;
|
|
|
|
|
const digitHeight = size * 1.2;
|
|
|
|
|
const digitSpacing = -size * 0.1;
|
|
|
|
|
const totalWidth = digits.length * (digitWidth + digitSpacing);
|
|
|
|
|
const x = position.x - totalWidth / 2;
|
|
|
|
|
const y = position.y - digitHeight / 2;
|
|
|
|
|
digits.forEach((digit, index) => {
|
|
|
|
|
const indexer = `default${digit}`;
|
|
|
|
|
const image = this.images[indexer as keyof typeof this.images];
|
|
|
|
|
Drawer.p.image(
|
|
|
|
|
image,
|
|
|
|
|
x + index * (digitWidth + digitSpacing),
|
|
|
|
|
y,
|
|
|
|
|
digitWidth,
|
|
|
|
|
digitHeight
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
Drawer.p.pop();
|
|
|
|
|
}
|
|
|
|
|
static drawField() {
|
|
|
|
|
Drawer.p.noFill();
|
|
|
|
|
Drawer.p.stroke(255, 255, 255, 60);
|
|
|
|
|
Drawer.p.rect(0, 0, 512, 384, 4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static beginDrawing() {
|
|
|
|
|
Drawer.p.push();
|
|
|
|
|
}
|
|
|
|
|
static endDrawing() {
|
|
|
|
|
Drawer.p.pop();
|
|
|
|
|
}
|
|
|
|
|
}
|