import { filterNull } from "./utils"; import * as THREE from "three"; import Stats from "three/addons/libs/stats.module.js"; import { ZipReader, BlobWriter } from "@zip.js/zip.js"; import rosuInit, * as rosu from "rosu-pp-js"; import rosuWasm from "rosu-pp-js/rosu_pp_js_bg.wasm?url"; import { Beatmap, BeatmapSet } from "./osu"; import { BeatmapDecoder } from "osu-parsers"; const rosuLoad = rosuInit(rosuWasm); interface OsuPlayerOpts { showControls: boolean; } class OsuPlayer { el: HTMLElement | null = null; canvasEl: HTMLCanvasElement | null = null; opts: OsuPlayerOpts = { showControls: true }; beatmapSet: BeatmapSet = new BeatmapSet(); diffsByStarRating: [number, number][] = []; diffs: Map = new Map(); constructor(opts?: OsuPlayerOpts) { if (opts) this.opts = opts; } init(el: HTMLElement) { this.el = el; this.el.style.width = "1024px"; this.el.style.height = "768px"; this.el.style.backgroundColor = "lightgray"; // Initialize dragging this.el.addEventListener("drop", async (evt) => { evt.preventDefault(); const files = filterNull( [...(evt.dataTransfer?.items ?? [])].map((item) => item.getAsFile()), ); if (!files.length) return; const firstFile = files[0]; const zipReader = new ZipReader(firstFile.stream()); const entries = await zipReader.getEntries(); await rosuLoad; const perf = new rosu.Difficulty(); const osuParser = new BeatmapDecoder(); for (const entry of entries) { const writer = new BlobWriter(); const data = await entry.getData?.(writer); if (!data) continue; if (!entry.filename.endsWith(".osu")) { this.beatmapSet.assets.set(entry.filename, data); continue; } const text = await data.text(); const map = new rosu.Beatmap(text); const osuMap = osuParser.decodeFromString(text); const result = perf.calculate(map); const beatmap = new Beatmap(map, osuMap, this.beatmapSet); const id = beatmap.id(); this.diffs.set(id, beatmap); this.diffsByStarRating.push([result.stars, id]); } this.diffsByStarRating.sort((a, b) => a[0] - b[0]); /* const listEl = document.createElement("ul"); listEl.style.margin = "0"; for (const [stars, id] of this.diffsByStarRating) { // biome-ignore lint/style/noNonNullAssertion: const diff = this.diffs.get(id)!; const liEl = document.createElement("li"); const aEl = document.createElement("a"); aEl.href = "javascript:void(0)"; aEl.text = `${diff.osuInner.metadata.version} (${ Math.round(stars * 100) / 100 }*)`; aEl.addEventListener("click", (evt) => { this.setBeatmap(id); }); liEl.appendChild(aEl); listEl.appendChild(liEl); } console.log([...this.beatmapSet.assets.entries()]); this.el?.replaceChildren(listEl); */ this.setBeatmap( this.diffsByStarRating[this.diffsByStarRating.length - 1][1], ); }); this.el.addEventListener("dragover", (evt) => { evt.preventDefault(); }); } setBeatmap(id: number) { this.canvasEl = document.createElement("canvas"); this.canvasEl.width = 1024; this.canvasEl.height = 768; // biome-ignore lint/style/noNonNullAssertion: const context = this.canvasEl.getContext("webgl2")!; // biome-ignore lint/style/noNonNullAssertion: const beatmap = this.diffs.get(id)!; const backgroundUrl = URL.createObjectURL(beatmap.background()); const scene = new THREE.Scene(); const camera = new THREE.OrthographicCamera(-512, 512, -384, 384, 1, 1000); scene.add(camera); scene.add(new THREE.AmbientLight()); const loader = new THREE.TextureLoader(); let backgroundPlane: THREE.Mesh; { const backgroundTexture = loader.load(backgroundUrl); const backgroundMaterial = new THREE.MeshBasicMaterial({ map: backgroundTexture, color: new THREE.Color(0.25, 0.25, 0.25), }); backgroundPlane = new THREE.Mesh( new THREE.PlaneGeometry(1024, 768), backgroundMaterial, ); backgroundPlane.position.set(1, 0, 0); backgroundPlane.rotation.set(Math.PI, Math.PI / 2, 0); camera.lookAt(backgroundPlane.position); camera.up.set(0, 1, 0); scene.add(backgroundPlane); } const renderer = new THREE.WebGLRenderer({ canvas: this.canvasEl }); const stats = new Stats(); this.el?.replaceChildren(this.canvasEl, stats.dom); function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); } animate(); } } // biome-ignore lint/style/noNonNullAssertion: const el = document.getElementById("root")!; const player = new OsuPlayer(); player.init(el);