Mercurial
view mrjunejune/src/public/raylib.js @ 174:1ba8c1df082c hg-web
Remove playground stuff.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 19 Jan 2026 18:59:23 -0800 |
| parents | 65e5a5b89a4e |
| children |
line wrap: on
line source
/** * 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); }