Mercurial
diff mrjunejune/src/public/raylib.js @ 100:65e5a5b89a4e
[Seobeo] Migrated everything to this page.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Sat, 03 Jan 2026 07:48:07 -0800 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/src/public/raylib.js Sat Jan 03 07:48:07 2026 -0800 @@ -0,0 +1,612 @@ +/** + * I Stole this from https://github.com/tsoding/c3-demo/blob/main/raylib.js and added few functions for functionality. + * It seems to be from raylib.com/examples + */ +function make_environment(env) { + return new Proxy(env, { + get(_target, prop, _receiver) { + if (env[prop] !== undefined) { + return env[prop].bind(env); + } + return (...args) => { + throw new Error(`NOT IMPLEMENTED: ${prop} ${args}`); + }; + }, + }); +} + +let iota = 0; +const LOG_ALL = iota++; // Display all logs +const LOG_TRACE = iota++; // Trace logging, intended for internal use only +const LOG_DEBUG = iota++; // Debug logging, used for internal debugging, it should be disabled on release builds +const LOG_INFO = iota++; // Info logging, used for program execution info +const LOG_WARNING = iota++; // Warning logging, used on recoverable failures +const LOG_ERROR = iota++; // Error logging, used on unrecoverable failures +const LOG_FATAL = iota++; // Fatal logging, used to abort program: exit(EXIT_FAILURE) +const LOG_NONE = iota++; // Disable logging + +class RaylibJs { + #FONT_SCALE_MAGIC = 0.65; + + #reset() { + this.previous = undefined; + this.wasm = undefined; + this.ctx = undefined; + this.dt = undefined; + this.targetFPS = 60; + this.entryFunction = undefined; + this.prevPressedKeyState = new Set(); + this.currentPressedKeyState = new Set(); + this.currentMouseWheelMoveState = 0; + /* -1 since 0 is left click, 1 middle, 2 is right click */ + this.currentIsMouseButtonPressed = -1; + this.currentMousePosition = { x: 0, y: 0 }; + this.images = []; + this.quit = false; + } + + constructor() { + this.#reset(); + } + + stop() { + this.quit = true; + } + + async start({ wasmPath, canvasId }) { + if (this.wasm !== undefined) { + console.error("The game is already running. Please stop() it first."); + return; + } + + const canvas = document.getElementById(canvasId); + this.ctx = canvas.getContext("2d"); + if (this.ctx === null) { + throw new Error("Could not create 2d canvas context"); + } + + this.wasm = await WebAssembly.instantiateStreaming(fetch(wasmPath), { + env: make_environment(this), + }); + + const keyDown = (e) => { + this.currentPressedKeyState.add(glfwKeyMapping[e.code]); + }; + const keyUp = (e) => { + this.currentPressedKeyState.delete(glfwKeyMapping[e.code]); + }; + const wheelMove = (e) => { + this.currentMouseWheelMoveState = Math.sign(-e.deltaY); + }; + const mouseMove = (e) => { + this.currentMousePosition = { x: e.clientX, y: e.clientY }; + }; + const mouseClick = (e) => { + this.currentIsMouseButtonPressed = e.button; + }; + const touchStarted = () => { + this.currentIsMouseButtonPressed = 0; + }; + const touchOrClickEnded = () => { + this.currentIsMouseButtonPressed = -1; + }; + window.addEventListener("keydown", keyDown); + window.addEventListener("keyup", keyUp); + window.addEventListener("wheel", wheelMove); + window.addEventListener("mousemove", mouseMove); + window.addEventListener("mousedown", mouseClick); + window.addEventListener("mouseup", touchOrClickEnded); + + /* For phones */ + window.addEventListener("touchstart", touchStarted); + window.addEventListener("touchend", touchOrClickEnded); + window.addEventListener("touchcancel", touchOrClickEnded); + + this.wasm.instance.exports.main(); + const next = (timestamp) => { + if (this.quit) { + this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); + window.removeEventListener("keydown", keyDown); + this.#reset(); + return; + } + this.dt = (timestamp - this.previous) / 1000.0; + this.previous = timestamp; + this.entryFunction(); + window.requestAnimationFrame(next); + }; + window.requestAnimationFrame((timestamp) => { + this.previous = timestamp; + window.requestAnimationFrame(next); + }); + } + + InitWindow(width, height, title_ptr) { + this.ctx.canvas.width = width; + this.ctx.canvas.height = height; + const buffer = this.wasm.instance.exports.memory.buffer; + document.title = cstr_by_ptr(buffer, title_ptr); + } + + WindowShouldClose() { + return false; + } + + SetTargetFPS(fps) { + console.log( + `The game wants to run at ${fps} FPS, but in Web we gonna just ignore it.`, + ); + this.targetFPS = fps; + } + + GetScreenWidth() { + return this.ctx.canvas.width; + } + + GetScreenHeight() { + return this.ctx.canvas.height; + } + + GetFrameTime() { + // TODO: This is a stopgap solution to prevent sudden jumps in dt when the user switches to a differen tab. + // We need a proper handling of Target FPS here. + return Math.min(this.dt, 1.0 / this.targetFPS); + } + + BeginDrawing() {} + + EndDrawing() { + this.prevPressedKeyState.clear(); + this.prevPressedKeyState = new Set(this.currentPressedKeyState); + this.currentMouseWheelMoveState = 0.0; + } + + DrawCircleV(center_ptr, radius, color_ptr) { + const buffer = this.wasm.instance.exports.memory.buffer; + const [x, y] = new Float32Array(buffer, center_ptr, 2); + const [r, g, b, a] = new Uint8Array(buffer, color_ptr, 4); + const color = color_hex_unpacked(r, g, b, a); + this.ctx.beginPath(); + this.ctx.arc(x, y, radius, 0, 2 * Math.PI, false); + this.ctx.fillStyle = color; + this.ctx.fill(); + } + + ClearBackground(color_ptr) { + this.ctx.fillStyle = getColorFromMemory( + this.wasm.instance.exports.memory.buffer, + color_ptr, + ); + this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); + } + + // RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) + DrawText(text_ptr, posX, posY, fontSize, color_ptr) { + const buffer = this.wasm.instance.exports.memory.buffer; + const text = cstr_by_ptr(buffer, text_ptr); + const color = getColorFromMemory(buffer, color_ptr); + fontSize *= this.#FONT_SCALE_MAGIC; + this.ctx.fillStyle = color; + // TODO: since the default font is part of Raylib the css that defines it should be located in raylib.js and not in index.html + this.ctx.font = `${fontSize}px grixel`; + + const lines = text.split("\n"); + for (var i = 0; i < lines.length; i++) { + this.ctx.fillText(lines[i], posX, posY + fontSize + i * fontSize); + } + } + + // RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle + DrawRectangle(posX, posY, width, height, color_ptr) { + const buffer = this.wasm.instance.exports.memory.buffer; + const color = getColorFromMemory(buffer, color_ptr); + this.ctx.fillStyle = color; + this.ctx.fillRect(posX, posY, width, height); + } + + DrawRectangleV(position_ptr, size_ptr, color_ptr) { + const buffer = this.wasm.instance.exports.memory.buffer; + const color = getColorFromMemory(buffer, color_ptr); + const position = new Float32Array(buffer, position_ptr, 2); + const size = new Float32Array(buffer, size_ptr, 2); + this.ctx.fillStyle = color; + this.ctx.fillRect(position[0], position[1], size[0], size[1]); + } + + IsKeyPressed(key) { + return ( + !this.prevPressedKeyState.has(key) && this.currentPressedKeyState.has(key) + ); + } + IsKeyDown(key) { + return this.currentPressedKeyState.has(key); + } + GetMouseWheelMove() { + return this.currentMouseWheelMoveState; + } + IsGestureDetected() { + return false; + } + IsMouseButtonPressed(key) { + return this.currentIsMouseButtonPressed == key; + } + + TextFormat(...args) { + // TODO: Implement printf style formatting for TextFormat + return args[0]; + } + + TraceLog(logLevel, text_ptr, ...args) { + // TODO: Implement printf style formatting for TraceLog + const buffer = this.wasm.instance.exports.memory.buffer; + const text = cstr_by_ptr(buffer, text_ptr); + switch (logLevel) { + case LOG_ALL: + console.log(`ALL: ${text} ${args}`); + break; + case LOG_TRACE: + console.log(`TRACE: ${text} ${args}`); + break; + case LOG_DEBUG: + console.log(`DEBUG: ${text} ${args}`); + break; + case LOG_INFO: + console.log(`INFO: ${text} ${args}`); + break; + case LOG_WARNING: + console.log(`WARNING: ${text} ${args}`); + break; + case LOG_ERROR: + console.log(`ERROR: ${text} ${args}`); + break; + case LOG_FATAL: + throw new Error(`FATAL: ${text}`); + case LOG_NONE: + console.log(`NONE: ${text} ${args}`); + break; + } + } + + GetMousePosition(result_ptr) { + const bcrect = this.ctx.canvas.getBoundingClientRect(); + const x = this.currentMousePosition.x - bcrect.left; + const y = this.currentMousePosition.y - bcrect.top; + + const buffer = this.wasm.instance.exports.memory.buffer; + new Float32Array(buffer, result_ptr, 2).set([x, y]); + } + + CheckCollisionPointRec(point_ptr, rec_ptr) { + const buffer = this.wasm.instance.exports.memory.buffer; + const [x, y] = new Float32Array(buffer, point_ptr, 2); + const [rx, ry, rw, rh] = new Float32Array(buffer, rec_ptr, 4); + return x >= rx && x <= rx + rw && y >= ry && y <= ry + rh; + } + + Fade(result_ptr, color_ptr, alpha) { + const buffer = this.wasm.instance.exports.memory.buffer; + const [r, g, b, _] = new Uint8Array(buffer, color_ptr, 4); + const newA = Math.max(0, Math.min(255, 255.0 * alpha)); + new Uint8Array(buffer, result_ptr, 4).set([r, g, b, newA]); + } + + DrawRectangleRec(rec_ptr, color_ptr) { + const buffer = this.wasm.instance.exports.memory.buffer; + const [x, y, w, h] = new Float32Array(buffer, rec_ptr, 4); + const color = getColorFromMemory(buffer, color_ptr); + this.ctx.fillStyle = color; + this.ctx.fillRect(x, y, w, h); + } + + DrawRectangleLinesEx(rec_ptr, lineThick, color_ptr) { + const buffer = this.wasm.instance.exports.memory.buffer; + const [x, y, w, h] = new Float32Array(buffer, rec_ptr, 4); + const color = getColorFromMemory(buffer, color_ptr); + this.ctx.strokeStyle = color; + this.ctx.lineWidth = lineThick; + this.ctx.strokeRect( + x + lineThick / 2, + y + lineThick / 2, + w - lineThick, + h - lineThick, + ); + } + + MeasureText(text_ptr, fontSize) { + const buffer = this.wasm.instance.exports.memory.buffer; + const text = cstr_by_ptr(buffer, text_ptr); + fontSize *= this.#FONT_SCALE_MAGIC; + this.ctx.font = `${fontSize}px grixel`; + return this.ctx.measureText(text).width; + } + + TextSubtext(text_ptr, position, length) { + const buffer = this.wasm.instance.exports.memory.buffer; + const text = cstr_by_ptr(buffer, text_ptr); + const subtext = text.substring(position, length); + + var bytes = new Uint8Array(buffer, 0, subtext.length + 1); + for (var i = 0; i < subtext.length; i++) { + bytes[i] = subtext.charCodeAt(i); + } + bytes[subtext.length] = 0; + + return bytes; + } + + // RLAPI Texture2D LoadTexture(const char *fileName); + LoadTexture(result_ptr, filename_ptr) { + const buffer = this.wasm.instance.exports.memory.buffer; + const filename = cstr_by_ptr(buffer, filename_ptr); + + var result = new Uint32Array(buffer, result_ptr, 5); + var img = new Image(); + const isLocalhost = window.location.hostname === "localhost"; + const baseUrl = isLocalhost + ? "http://localhost:6969/" + : `https://${window.location.hostname}/`; + img.src = `${baseUrl}${filename}`; + this.images.push(img); + + result[0] = this.images.indexOf(img); + // TODO: get the true width and height of the image + result[1] = 256; // width + result[2] = 256; // height + result[3] = 1; // mipmaps + result[4] = 7; // format PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + + return result; + } + + // RLAPI void DrawTexture(Texture2D texture, int posX, int posY, Color tint); + DrawTexture(texture_ptr, posX, posY, color_ptr) { + const buffer = this.wasm.instance.exports.memory.buffer; + const [id, width, height, mipmaps, format] = new Uint32Array( + buffer, + texture_ptr, + 5, + ); + // // TODO: implement tinting for DrawTexture + // const tint = getColorFromMemory(buffer, color_ptr); + + this.ctx.drawImage(this.images[id], posX, posY); + } + + // TODO: codepoints are not implemented + LoadFontEx( + result_ptr, + fileName_ptr /*, fontSize, codepoints, codepointCount*/, + ) { + const buffer = this.wasm.instance.exports.memory.buffer; + const fileName = cstr_by_ptr(buffer, fileName_ptr); + // TODO: dynamically generate the name for the font + // Support more than one custom font + const font = new FontFace("myfont", `url(${fileName})`); + document.fonts.add(font); + font.load(); + } + + GenTextureMipmaps() {} + SetTextureFilter() {} + + MeasureTextEx(result_ptr, font, text_ptr, fontSize, spacing) { + const buffer = this.wasm.instance.exports.memory.buffer; + const text = cstr_by_ptr(buffer, text_ptr); + const result = new Float32Array(buffer, result_ptr, 2); + this.ctx.font = fontSize + "px myfont"; + const metrics = this.ctx.measureText(text); + result[0] = metrics.width; + result[1] = fontSize; + } + + DrawTextEx(font, text_ptr, position_ptr, fontSize, spacing, tint_ptr) { + const buffer = this.wasm.instance.exports.memory.buffer; + const text = cstr_by_ptr(buffer, text_ptr); + const [posX, posY] = new Float32Array(buffer, position_ptr, 2); + const tint = getColorFromMemory(buffer, tint_ptr); + this.ctx.fillStyle = tint; + this.ctx.font = fontSize + "px myfont"; + this.ctx.fillText(text, posX, posY + fontSize); + } + + GetRandomValue(min, max) { + return min + Math.floor(Math.random() * (max - min + 1)); + } + + ColorFromHSV(result_ptr, hue, saturation, value) { + const buffer = this.wasm.instance.exports.memory.buffer; + const result = new Uint8Array(buffer, result_ptr, 4); + + // Red channel + let k = (5.0 + hue / 60.0) % 6; + let t = 4.0 - k; + k = t < k ? t : k; + k = k < 1 ? k : 1; + k = k > 0 ? k : 0; + result[0] = Math.floor((value - value * saturation * k) * 255.0); + + // Green channel + k = (3.0 + hue / 60.0) % 6; + t = 4.0 - k; + k = t < k ? t : k; + k = k < 1 ? k : 1; + k = k > 0 ? k : 0; + result[1] = Math.floor((value - value * saturation * k) * 255.0); + + // Blue channel + k = (1.0 + hue / 60.0) % 6; + t = 4.0 - k; + k = t < k ? t : k; + k = k < 1 ? k : 1; + k = k > 0 ? k : 0; + result[2] = Math.floor((value - value * saturation * k) * 255.0); + + result[3] = 255; + } + + raylib_js_set_entry(entry) { + this.entryFunction = + this.wasm.instance.exports.__indirect_function_table.get(entry); + } +} + +const glfwKeyMapping = { + Space: 32, + Quote: 39, + Comma: 44, + Minus: 45, + Period: 46, + Slash: 47, + Digit0: 48, + Digit1: 49, + Digit2: 50, + Digit3: 51, + Digit4: 52, + Digit5: 53, + Digit6: 54, + Digit7: 55, + Digit8: 56, + Digit9: 57, + Semicolon: 59, + Equal: 61, + KeyA: 65, + KeyB: 66, + KeyC: 67, + KeyD: 68, + KeyE: 69, + KeyF: 70, + KeyG: 71, + KeyH: 72, + KeyI: 73, + KeyJ: 74, + KeyK: 75, + KeyL: 76, + KeyM: 77, + KeyN: 78, + KeyO: 79, + KeyP: 80, + KeyQ: 81, + KeyR: 82, + KeyS: 83, + KeyT: 84, + KeyU: 85, + KeyV: 86, + KeyW: 87, + KeyX: 88, + KeyY: 89, + KeyZ: 90, + BracketLeft: 91, + Backslash: 92, + BracketRight: 93, + Backquote: 96, + // GLFW_KEY_WORLD_1 161 /* non-US #1 */ + // GLFW_KEY_WORLD_2 162 /* non-US #2 */ + Escape: 256, + Enter: 257, + Tab: 258, + Backspace: 259, + Insert: 260, + Delete: 261, + ArrowRight: 262, + ArrowLeft: 263, + ArrowDown: 264, + ArrowUp: 265, + PageUp: 266, + PageDown: 267, + Home: 268, + End: 269, + CapsLock: 280, + ScrollLock: 281, + NumLock: 282, + PrintScreen: 283, + Pause: 284, + F1: 290, + F2: 291, + F3: 292, + F4: 293, + F5: 294, + F6: 295, + F7: 296, + F8: 297, + F9: 298, + F10: 299, + F11: 300, + F12: 301, + F13: 302, + F14: 303, + F15: 304, + F16: 305, + F17: 306, + F18: 307, + F19: 308, + F20: 309, + F21: 310, + F22: 311, + F23: 312, + F24: 313, + F25: 314, + NumPad0: 320, + NumPad1: 321, + NumPad2: 322, + NumPad3: 323, + NumPad4: 324, + NumPad5: 325, + NumPad6: 326, + NumPad7: 327, + NumPad8: 328, + NumPad9: 329, + NumpadDecimal: 330, + NumpadDivide: 331, + NumpadMultiply: 332, + NumpadSubtract: 333, + NumpadAdd: 334, + NumpadEnter: 335, + NumpadEqual: 336, + ShiftLeft: 340, + ControlLeft: 341, + AltLeft: 342, + MetaLeft: 343, + ShiftRight: 344, + ControlRight: 345, + AltRight: 346, + MetaRight: 347, + ContextMenu: 348, + // GLFW_KEY_LAST GLFW_KEY_MENU +}; + +function cstrlen(mem, ptr) { + let len = 0; + while (mem[ptr] != 0) { + len++; + ptr++; + } + return len; +} + +function cstr_by_ptr(mem_buffer, ptr) { + const mem = new Uint8Array(mem_buffer); + const len = cstrlen(mem, ptr); + const bytes = new Uint8Array(mem_buffer, ptr, len); + return new TextDecoder().decode(bytes); +} + +function color_hex_unpacked(r, g, b, a) { + r = r.toString(16).padStart(2, "0"); + g = g.toString(16).padStart(2, "0"); + b = b.toString(16).padStart(2, "0"); + a = a.toString(16).padStart(2, "0"); + return "#" + r + g + b + a; +} + +function color_hex(color) { + const r = ((color >> (0 * 8)) & 0xff).toString(16).padStart(2, "0"); + const g = ((color >> (1 * 8)) & 0xff).toString(16).padStart(2, "0"); + const b = ((color >> (2 * 8)) & 0xff).toString(16).padStart(2, "0"); + const a = ((color >> (3 * 8)) & 0xff).toString(16).padStart(2, "0"); + return "#" + r + g + b + a; +} + +function getColorFromMemory(buffer, color_ptr) { + const [r, g, b, a] = new Uint8Array(buffer, color_ptr, 4); + return color_hex_unpacked(r, g, b, a); +}