From 2f5dccc3ef7e492354836d79123354c91e27c5c9 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Mon, 9 Feb 2026 09:29:16 -0600 Subject: [PATCH] Texture loading --- assets/init.lua | 2 +- assets/scene/minesweeper.lua | 18 ++- src/asset/type/assetpaletteimage.h | 4 +- src/display/tileset/tileset.c | 16 ++ src/display/tileset/tileset.h | 26 ++- src/script/module/CMakeLists.txt | 1 - src/script/module/asset/CMakeLists.txt | 10 -- src/script/module/asset/moduleasset.c | 59 ------- src/script/module/asset/moduleasset.h | 20 --- src/script/module/display/CMakeLists.txt | 2 + src/script/module/display/moduleglm.c | 151 +++++++++++++++++- src/script/module/display/moduleglm.h | 34 +++- src/script/module/display/modulespritebatch.c | 15 +- src/script/module/display/moduletexture.c | 108 +++++++++++++ src/script/module/display/moduletexture.h | 15 ++ src/script/module/display/moduletileset.c | 149 +++++++++++++++++ src/script/module/display/moduletileset.h | 56 +++++++ src/script/scriptmodule.c | 6 +- tools/asset/process/tileset.py | 1 + 19 files changed, 590 insertions(+), 103 deletions(-) delete mode 100644 src/script/module/asset/CMakeLists.txt delete mode 100644 src/script/module/asset/moduleasset.c delete mode 100644 src/script/module/asset/moduleasset.h create mode 100644 src/script/module/display/moduletexture.c create mode 100644 src/script/module/display/moduletexture.h create mode 100644 src/script/module/display/moduletileset.c create mode 100644 src/script/module/display/moduletileset.h diff --git a/assets/init.lua b/assets/init.lua index 4e14b46..5b325a4 100644 --- a/assets/init.lua +++ b/assets/init.lua @@ -52,4 +52,4 @@ else end localeSet(DUSK_LOCALE_EN_US) -sceneSet('scene/initial.dsf') \ No newline at end of file +sceneSet('scene/minesweeper.dsf') \ No newline at end of file diff --git a/assets/scene/minesweeper.lua b/assets/scene/minesweeper.lua index d2c5820..198ff35 100644 --- a/assets/scene/minesweeper.lua +++ b/assets/scene/minesweeper.lua @@ -5,10 +5,16 @@ module('ui') module('screen') module('time') module('glm') +module('text') +module('tileset') +module('texture') screenSetBackground(colorBlack()) camera = cameraCreate(CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) +tileset = tilesetGetByName("ui") +ui = textureLoad(tileset.texture) + function sceneDispose() end @@ -21,7 +27,17 @@ function sceneRender() camera.bottom = screenGetHeight() camera.right = screenGetWidth() - + slice = tilesetTileGetUV(tileset, 0) + spriteBatchPush(ui, + 0, 0, + ui.width * slice.u1, ui.height * slice.v1, + colorRainbow(), + slice.u0, slice.v0, + slice.u1, slice.v1 + ) + spriteBatchFlush() + + -- textDraw(0, 0, "Hello World", colorRainbow()) cameraPopMatrix() end \ No newline at end of file diff --git a/src/asset/type/assetpaletteimage.h b/src/asset/type/assetpaletteimage.h index 2254d07..d5d45ae 100644 --- a/src/asset/type/assetpaletteimage.h +++ b/src/asset/type/assetpaletteimage.h @@ -8,8 +8,8 @@ #pragma once #include "error/error.h" -#define ASSET_PALETTE_IMAGE_WIDTH_MAX 128 -#define ASSET_PALETTE_IMAGE_HEIGHT_MAX 128 +#define ASSET_PALETTE_IMAGE_WIDTH_MAX 256 +#define ASSET_PALETTE_IMAGE_HEIGHT_MAX 256 #define ASSET_PALETTE_IMAGE_SIZE_MAX ( \ ASSET_PALETTE_IMAGE_WIDTH_MAX * ASSET_PALETTE_IMAGE_HEIGHT_MAX \ ) diff --git a/src/display/tileset/tileset.c b/src/display/tileset/tileset.c index 5dc79d7..96949bc 100644 --- a/src/display/tileset/tileset.c +++ b/src/display/tileset/tileset.c @@ -6,6 +6,9 @@ */ #include "tileset.h" +#include "display/tileset/tilesetlist.h" +#include "assert/assert.h" +#include "util/string.h" void tilesetTileGetUV( const tileset_t *tileset, @@ -23,8 +26,21 @@ void tilesetPositionGetUV( const uint16_t row, vec4 outUV ) { + assertNotNull(tileset, "Tileset cannot be NULL"); + assertTrue(column < tileset->columns, "Column index out of bounds"); + assertTrue(row < tileset->rows, "Row index out of bounds"); + outUV[0] = column * tileset->uv[0]; outUV[1] = row * tileset->uv[1]; outUV[2] = outUV[0] + tileset->uv[0]; outUV[3] = outUV[1] + tileset->uv[1]; +} + +const tileset_t * tilesetGetByName(const char_t *name) { + assertStrLenMin(name, 1, "Tileset name cannot be empty"); + for(uint32_t i = 0; i < TILESET_LIST_COUNT; i++) { + if(stringCompare(TILESET_LIST[i]->name, name) != 0) continue; + return TILESET_LIST[i]; + } + return NULL; } \ No newline at end of file diff --git a/src/display/tileset/tileset.h b/src/display/tileset/tileset.h index 6ec5588..d8ffce6 100644 --- a/src/display/tileset/tileset.h +++ b/src/display/tileset/tileset.h @@ -9,6 +9,7 @@ #include "dusk.h" typedef struct tileset_s { + const char_t *name; const uint16_t tileWidth; const uint16_t tileHeight; const uint16_t tileCount; @@ -18,15 +19,38 @@ typedef struct tileset_s { const char_t *image; } tileset_t; +/** + * Gets the UV coordinates for a tile index in the tileset. + * + * @param tileset The tileset to get the UV coordinates from. + * @param tileIndex The index of the tile to get the UV coordinates for. + * @param outUV The output UV coordinates (vec4). + */ void tilesetTileGetUV( const tileset_t *tileset, const uint16_t tileIndex, vec4 outUV ); +/** + * Gets the UV coordinates for a tile position in the tileset. + * + * @param tileset The tileset to get the UV coordinates from. + * @param column The column of the tile to get the UV coordinates for. + * @param row The row of the tile to get the UV coordinates for. + * @param outUV The output UV coordinates (vec4). + */ void tilesetPositionGetUV( const tileset_t *tileset, const uint16_t column, const uint16_t row, vec4 outUV -); \ No newline at end of file +); + +/** + * Gets a tileset by its name. + * + * @param name The name of the tileset to get. + * @return The tileset with the given name, or NULL if not found. + */ +const tileset_t* tilesetGetByName(const char_t *name); \ No newline at end of file diff --git a/src/script/module/CMakeLists.txt b/src/script/module/CMakeLists.txt index 412bb8f..f3086ae 100644 --- a/src/script/module/CMakeLists.txt +++ b/src/script/module/CMakeLists.txt @@ -4,7 +4,6 @@ # https://opensource.org/licenses/MIT # Subdirectories -add_subdirectory(asset) add_subdirectory(display) add_subdirectory(event) add_subdirectory(input) diff --git a/src/script/module/asset/CMakeLists.txt b/src/script/module/asset/CMakeLists.txt deleted file mode 100644 index 811d366..0000000 --- a/src/script/module/asset/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Sources -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - moduleasset.c -) \ No newline at end of file diff --git a/src/script/module/asset/moduleasset.c b/src/script/module/asset/moduleasset.c deleted file mode 100644 index 1ab0f52..0000000 --- a/src/script/module/asset/moduleasset.c +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "moduleasset.h" -#include "asset/asset.h" -#include "display/texture.h" -#include "assert/assert.h" -#include "util/memory.h" - -void moduleAsset(scriptcontext_t *context) { - assertNotNull(context, "Script context cannot be null"); - - lua_State *l = context->luaState; - assertNotNull(l, "Lua state cannot be null"); - - // Create metatable for texture structure. - - lua_register(context->luaState, "assetTextureLoad", moduleAssetTextureLoad); - lua_register(context->luaState, "assetTextureDispose", moduleAssetTextureDispose); -} - -int moduleAssetTextureLoad(lua_State *l) { - assertNotNull(l, "Lua state cannot be NULL."); - - const char_t *filename = luaL_checkstring(l, 2); - assertStrLenMin(filename, 1, "Filename cannot be empty."); - - // Create texture owned to lua - texture_t *tex = (texture_t *)lua_newuserdata(l, sizeof(texture_t)); - memoryZero(tex, sizeof(texture_t)); - - errorret_t ret = assetLoad(filename, tex); - if(ret.code != ERROR_OK) { - errorCatch(errorPrint(ret)); - luaL_error(l, "Failed to load texture asset: %s", filename); - return 0; - } - - // Set metatable - luaL_getmetatable(l, "texture_mt"); - lua_setmetatable(l, -2); - - // Return the texture - return 1; -} - -int moduleAssetTextureDispose(lua_State *l) { - assertNotNull(l, "Lua state cannot be NULL."); - - texture_t *tex = (texture_t *)luaL_checkudata(l, 1, "texture_mt"); - assertNotNull(tex, "Texture pointer cannot be NULL."); - - textureDispose(tex); - return 0; -} \ No newline at end of file diff --git a/src/script/module/asset/moduleasset.h b/src/script/module/asset/moduleasset.h deleted file mode 100644 index 13bf8b0..0000000 --- a/src/script/module/asset/moduleasset.h +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "script/scriptcontext.h" - -/** - * Register asset functions to the given script context. - * - * @param context The script context to register asset functions to. - */ -void moduleAsset(scriptcontext_t *context); - -int moduleAssetTextureLoad(lua_State *l); - -int moduleAssetTextureDispose(lua_State *l); \ No newline at end of file diff --git a/src/script/module/display/CMakeLists.txt b/src/script/module/display/CMakeLists.txt index 45f8362..60a7877 100644 --- a/src/script/module/display/CMakeLists.txt +++ b/src/script/module/display/CMakeLists.txt @@ -13,4 +13,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} modulecolor.c moduletext.c modulescreen.c + moduletileset.c + moduletexture.c ) \ No newline at end of file diff --git a/src/script/module/display/moduleglm.c b/src/script/module/display/moduleglm.c index 14f0368..0a5af92 100644 --- a/src/script/module/display/moduleglm.c +++ b/src/script/module/display/moduleglm.c @@ -22,12 +22,24 @@ void moduleGLM(scriptcontext_t *context) { lua_setfield(context->luaState, -2, "__newindex"); lua_pushcfunction(context->luaState, moduleVec3ToString); lua_setfield(context->luaState, -2, "__tostring"); - lua_pop(context->luaState, 1); } + lua_pop(context->luaState, 1); + + if(luaL_newmetatable(context->luaState, "vec4_mt")) { + // Metatable methods + lua_pushcfunction(context->luaState, moduleVec4Index); + lua_setfield(context->luaState, -2, "__index"); + lua_pushcfunction(context->luaState, moduleVec4NewIndex); + lua_setfield(context->luaState, -2, "__newindex"); + lua_pushcfunction(context->luaState, moduleVec4ToString); + lua_setfield(context->luaState, -2, "__tostring"); + } + lua_pop(context->luaState, 1); lua_register(context->luaState, "vec3", moduleVec3Create); } + int moduleVec3Create(lua_State *l) { assertNotNull(l, "Lua state cannot be NULL."); @@ -130,5 +142,142 @@ int moduleVec3ToString(lua_State *l) { ); lua_pushstring(l, buf); + return 1; +} + + + +int moduleVec4Create(lua_State *l) { + assertNotNull(l, "Lua state cannot be NULL."); + + vec4 *v = (vec4 *)lua_newuserdata(l, sizeof(vec4)); + memoryZero(v, sizeof(vec4)); + + // May be expecting between 1 and 4 values. + int top = lua_gettop(l); + if(top >= 1) { + if(!lua_isnumber(l, 1)) { + luaL_error(l, "Vec4 x component must be a number."); + } + (*v)[0] = (float_t)lua_tonumber(l, 1); + } + if(top >= 2) { + if(!lua_isnumber(l, 2)) { + luaL_error(l, "Vec4 y component must be a number."); + } + (*v)[1] = (float_t)lua_tonumber(l, 2); + } + if(top >= 3) { + if(!lua_isnumber(l, 3)) { + luaL_error(l, "Vec4 z component must be a number."); + } + (*v)[2] = (float_t)lua_tonumber(l, 3); + } + if(top >= 4) { + if(!lua_isnumber(l, 4)) { + luaL_error(l, "Vec4 w component must be a number."); + } + (*v)[3] = (float_t)lua_tonumber(l, 4); + } + + // Set metatable + luaL_getmetatable(l, "vec4_mt"); + lua_setmetatable(l, -2); + + return 1; +} + +int moduleVec4Index(lua_State *l) { + assertNotNull(l, "Lua state cannot be NULL."); + + const char_t *key = lua_tostring(l, 2); + assertStrLenMin(key, 1, "Key cannot be empty."); + + vec4 *vec = (vec4 *)luaL_checkudata(l, 1, "vec4_mt"); + assertNotNull(vec, "Vec4 pointer cannot be NULL."); + + if(stringCompare(key, "x") == 0) { + lua_pushnumber(l, (*vec)[0]); + return 1; + } else if(stringCompare(key, "y") == 0) { + lua_pushnumber(l, (*vec)[1]); + return 1; + } else if(stringCompare(key, "z") == 0) { + lua_pushnumber(l, (*vec)[2]); + return 1; + } else if(stringCompare(key, "w") == 0) { + lua_pushnumber(l, (*vec)[3]); + return 1; + } else if(stringCompare(key, "u0") == 0) { + lua_pushnumber(l, (*vec)[0]); + return 1; + } else if(stringCompare(key, "v0") == 0) { + lua_pushnumber(l, (*vec)[1]); + return 1; + } else if(stringCompare(key, "u1") == 0) { + lua_pushnumber(l, (*vec)[2]); + return 1; + } else if(stringCompare(key, "v1") == 0) { + lua_pushnumber(l, (*vec)[3]); + return 1; + } + + lua_pushnil(l); + return 1; +} + +int moduleVec4NewIndex(lua_State *l) { + assertNotNull(l, "Lua state cannot be NULL."); + + const char_t *key = luaL_checkstring(l, 2); + assertStrLenMin(key, 1, "Key cannot be empty."); + + vec4 *vec = (vec4 *)luaL_checkudata(l, 1, "vec4_mt"); + assertNotNull(vec, "Vec4 pointer cannot be NULL."); + + if(stringCompare(key, "x") == 0) { + if(!lua_isnumber(l, 3)) { + luaL_error(l, "Vec4 x component must be a number."); + } + (*vec)[0] = (float_t)lua_tonumber(l, 3); + return 0; + } else if(stringCompare(key, "y") == 0) { + if(!lua_isnumber(l, 3)) { + luaL_error(l, "Vec4 y component must be a number."); + } + (*vec)[1] = (float_t)lua_tonumber(l, 3); + return 0; + } else if(stringCompare(key, "z") == 0) { + if(!lua_isnumber(l, 3)) { + luaL_error(l, "Vec4 z component must be a number."); + } + (*vec)[2] = (float_t)lua_tonumber(l, 3); + return 0; + } else if(stringCompare(key, "w") == 0) { + if(!lua_isnumber(l, 3)) { + luaL_error(l, "Vec4 w component must be a number."); + } + (*vec)[3] = (float_t)lua_tonumber(l, 3); + return 0; + } + + luaL_error(l, "Invalid key for vec4: %s", key); + return 0; +} + +int moduleVec4ToString(lua_State *l) { + assertNotNull(l, "Lua state cannot be NULL."); + + vec4 *vec = (vec4 *)luaL_checkudata(l, 1, "vec4_mt"); + assertNotNull(vec, "Vec4 pointer cannot be NULL."); + + char buf[128]; + snprintf( + buf, sizeof(buf), + "vec4(%.3f, %.3f, %.3f, %.3f)", + (*vec)[0], (*vec)[1], (*vec)[2], (*vec)[3] + ); + lua_pushstring(l, buf); + return 1; } \ No newline at end of file diff --git a/src/script/module/display/moduleglm.h b/src/script/module/display/moduleglm.h index a60f990..12eb85e 100644 --- a/src/script/module/display/moduleglm.h +++ b/src/script/module/display/moduleglm.h @@ -45,4 +45,36 @@ int moduleVec3NewIndex(lua_State *l); * @param l The Lua state. * @return Number of return values on the Lua stack. */ -int moduleVec3ToString(lua_State *l); \ No newline at end of file +int moduleVec3ToString(lua_State *l); + +/** + * Creates a new vec4 structure in Lua. + * + * @param l The Lua state. + * @return Number of return values on the Lua stack. + */ +int moduleVec4Create(lua_State *l); + +/** + * Lua __index metamethod for vec4 structure. + * + * @param l The Lua state. + * @return Number of return values on the Lua stack. + */ +int moduleVec4Index(lua_State *l); + +/** + * Lua __newindex metamethod for vec4 structure. + * + * @param l The Lua state. + * @return Number of return values on the Lua stack. + */ +int moduleVec4NewIndex(lua_State *l); + +/** + * Lua __tostring metamethod for vec4 structure. + * + * @param l The Lua state. + * @return Number of return values on the Lua stack. + */ +int moduleVec4ToString(lua_State *l); \ No newline at end of file diff --git a/src/script/module/display/modulespritebatch.c b/src/script/module/display/modulespritebatch.c index 00c95b5..3b52784 100644 --- a/src/script/module/display/modulespritebatch.c +++ b/src/script/module/display/modulespritebatch.c @@ -32,9 +32,16 @@ int moduleSpriteBatchClear(lua_State *L) { int moduleSpriteBatchPush(lua_State *L) { assertNotNull(L, "Lua state is null"); - // Texture ID must be Nil for now - if(!lua_isnil(L, 1)) { - return luaL_error(L, "Texture parameter must be nil"); + // Texture pointer or Nil for no texture + if(!lua_isuserdata(L, 1) && !lua_isnil(L, 1)) { + return luaL_error(L, "First argument must be a texture or nil"); + } + + // If texture is not nil, check it's a texture userdata + texture_t *tex = NULL; + if(lua_isuserdata(L, 1)) { + tex = (texture_t *)luaL_checkudata(L, 1, "texture_mt"); + assertNotNull(tex, "Texture pointer cannot be NULL"); } // MinX, MinY, MaxX, MaxY @@ -85,7 +92,7 @@ int moduleSpriteBatchPush(lua_State *L) { float_t maxY = (float_t)lua_tonumber(L, 5); spriteBatchPush( - NULL, + tex, minX, minY, maxX, diff --git a/src/script/module/display/moduletexture.c b/src/script/module/display/moduletexture.c new file mode 100644 index 0000000..b016a3b --- /dev/null +++ b/src/script/module/display/moduletexture.c @@ -0,0 +1,108 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduletexture.h" +#include "asset/asset.h" +#include "display/texture.h" +#include "assert/assert.h" +#include "util/memory.h" +#include "util/string.h" + +void moduleTexture(scriptcontext_t *ctx) { + assertNotNull(ctx, "Script context cannot be null"); + + lua_State *l = ctx->luaState; + assertNotNull(l, "Lua state cannot be null"); + + // Create metatable for texture structure. + if(luaL_newmetatable(l, "texture_mt") == 1) { + lua_pushcfunction(l, moduleTextureIndex); + lua_setfield(l, -2, "__index"); + + lua_pushcfunction(l, moduleTextureToString); + lua_setfield(l, -2, "__tostring"); + + lua_pushcfunction(l, moduleTextureGC); + lua_setfield(l, -2, "__gc"); + } + + lua_register(ctx->luaState, "textureLoad", moduleTextureLoad); +} + +int moduleTextureIndex(lua_State *l) { + assertNotNull(l, "Lua state cannot be NULL."); + + texture_t *tex = (texture_t *)luaL_checkudata(l, 1, "texture_mt"); + assertNotNull(tex, "Texture pointer cannot be NULL."); + + const char *key = luaL_checkstring(l, 2); + assertNotNull(key, "Key cannot be NULL."); + + if(stringCompare(key, "width") == 0) { + lua_pushinteger(l, tex->width); + return 1; + } else if(stringCompare(key, "height") == 0) { + lua_pushinteger(l, tex->height); + return 1; + } + + lua_pushnil(l); + return 1; +} + +int moduleTextureToString(lua_State *l) { + assertNotNull(l, "Lua state cannot be NULL."); + + texture_t *tex = (texture_t *)luaL_checkudata(l, 1, "texture_mt"); + assertNotNull(tex, "Texture pointer cannot be NULL."); + + char buffer[64]; + snprintf(buffer, sizeof(buffer), "Texture(%dx%d)", tex->width, tex->height); + lua_pushstring(l, buffer); + return 1; +} + +int moduleTextureGC(lua_State *l) { + assertNotNull(l, "Lua state cannot be NULL."); + + texture_t *tex = (texture_t *)luaL_checkudata(l, 1, "texture_mt"); + assertNotNull(tex, "Texture pointer cannot be NULL."); + + textureDispose(tex); + return 0; +} + +int moduleTextureLoad(lua_State *l) { + assertNotNull(l, "Lua state cannot be NULL."); + + if(!lua_isstring(l, 1)) { + luaL_error(l, "First argument must be a string filename."); + return 0; + } + + const char_t *filename = lua_tostring(l, 1); + assertNotNull(filename, "Filename cannot be NULL."); + assertStrLenMin(filename, 1, "Filename cannot be empty."); + + // Create texture owned to lua + texture_t *tex = (texture_t *)lua_newuserdata(l, sizeof(texture_t)); + memoryZero(tex, sizeof(texture_t)); + + errorret_t ret = assetLoad(filename, tex); + if(ret.code != ERROR_OK) { + errorCatch(errorPrint(ret)); + luaL_error(l, "Failed to load texture asset: %s", filename); + return 0; + } + + // Set metatable + luaL_getmetatable(l, "texture_mt"); + lua_setmetatable(l, -2); + + // Return the texture + return 1; +} \ No newline at end of file diff --git a/src/script/module/display/moduletexture.h b/src/script/module/display/moduletexture.h new file mode 100644 index 0000000..5d8e179 --- /dev/null +++ b/src/script/module/display/moduletexture.h @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/scriptcontext.h" + +void moduleTexture(scriptcontext_t *ctx); +int moduleTextureIndex(lua_State *l); +int moduleTextureToString(lua_State *l); +int moduleTextureGC(lua_State *l); +int moduleTextureLoad(lua_State *l); \ No newline at end of file diff --git a/src/script/module/display/moduletileset.c b/src/script/module/display/moduletileset.c new file mode 100644 index 0000000..9e7e732 --- /dev/null +++ b/src/script/module/display/moduletileset.c @@ -0,0 +1,149 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "moduletileset.h" +#include "assert/assert.h" +#include "display/tileset/tileset.h" +#include "util/memory.h" +#include "util/string.h" + +void moduleTileset(scriptcontext_t *ctx) { + assertNotNull(ctx, "Script context cannot be NULL"); + + // Tileset metatable + if(luaL_newmetatable(ctx->luaState, "tileset_mt")) { + lua_pushcfunction(ctx->luaState, moduleTilesetIndex); + lua_setfield(ctx->luaState, -2, "__index"); + + lua_pushcfunction(ctx->luaState, moduleTilesetToString); + lua_setfield(ctx->luaState, -2, "__tostring"); + } + lua_pop(ctx->luaState, 1); // Pop the metatable + + lua_register(ctx->luaState, "tilesetGetByName", moduleTilesetGetByName); + lua_register(ctx->luaState, "tilesetTileGetUV", moduleTilesetTileGetUV); + lua_register( + ctx->luaState, "tilesetPositionGetUV", moduleTilesetPositionGetUV + ); +} + +int moduleTilesetIndex(lua_State *l) { + assertNotNull(l, "Lua state cannot be NULL."); + + const char_t *key = luaL_checkstring(l, 2); + assertNotNull(key, "Key cannot be NULL."); + + tileset_t *ts = (tileset_t *)luaL_checkudata(l, 1, "tileset_mt"); + assertNotNull(ts, "Tileset pointer cannot be NULL."); + + if(stringCompare(key, "name") == 0) { + lua_pushstring(l, ts->name); + return 1; + } else if(stringCompare(key, "texture") == 0) { + lua_pushstring(l, ts->image); + return 1; + } + + lua_pushnil(l); + return 1; +} + +int moduleTilesetToString(lua_State *l) { + tileset_t *ts = (tileset_t *)luaL_checkudata(l, 1, "tileset_mt"); + assertNotNull(ts, "Tileset pointer cannot be NULL."); + + lua_pushfstring(l, "Tileset: %s", ts->name); + return 1; +} + +int moduleTilesetGetByName(lua_State *l) { + assertNotNull(l, "Lua state cannot be NULL."); + + if(!lua_isstring(l, 1)) { + lua_pushnil(l); + return 1; + } + + const char_t *name = lua_tostring(l, 1); + if(name == NULL || name[0] == '\0') { + luaL_error(l, "Invalid tileset name."); + return 1; + } + + const tileset_t *ts = tilesetGetByName(name); + if(ts == NULL) { + lua_pushnil(l); + return 1; + } + + // Lua does not own this reference + lua_pushlightuserdata(l, (void *)ts); + luaL_getmetatable(l, "tileset_mt"); + lua_setmetatable(l, -2); + + return 1; +} + +int moduleTilesetTileGetUV(lua_State *l) { + assertNotNull(l, "Lua state cannot be NULL."); + + if(!lua_isuserdata(l, 1)) { + luaL_error(l, "First argument must be a tileset userdata."); + return 0; + } + + tileset_t *ts = (tileset_t *)luaL_checkudata(l, 1, "tileset_mt"); + assertNotNull(ts, "Tileset pointer cannot be NULL."); + + if(!lua_isnumber(l, 2)) { + luaL_error(l, "Second arguments must be tile index."); + return 0; + } + uint16_t tileIndex = (uint16_t)lua_tonumber(l, 2); + + // Create vec4 that lua owns + vec4 *uv = (vec4 *)lua_newuserdata(l, sizeof(vec4)); + tilesetTileGetUV(ts, tileIndex, *uv); + + // Set metatable + luaL_getmetatable(l, "vec4_mt"); + lua_setmetatable(l, -2); + return 1; +} + +int moduleTilesetPositionGetUV(lua_State *l) { + assertNotNull(l, "Lua state cannot be NULL."); + + if(!lua_isuserdata(l, 1)) { + luaL_error(l, "First argument must be a tileset userdata."); + return 0; + } + + tileset_t *ts = (tileset_t *)luaL_checkudata(l, 1, "tileset_mt"); + assertNotNull(ts, "Tileset pointer cannot be NULL."); + + if(!lua_isnumber(l, 2)) { + luaL_error(l, "Second arguments must be column number."); + return 0; + } + uint16_t column = (uint16_t)lua_tonumber(l, 2); + + if(!lua_isnumber(l, 3)) { + luaL_error(l, "Third arguments must be row number."); + return 0; + } + uint16_t row = (uint16_t)lua_tonumber(l, 3); + + // Create vec4 that lua owns + vec4 *uv = (vec4 *)lua_newuserdata(l, sizeof(vec4)); + tilesetPositionGetUV(ts, column, row, *uv); + + // Set metatable + luaL_getmetatable(l, "vec4_mt"); + lua_setmetatable(l, -2); + return 1; +} \ No newline at end of file diff --git a/src/script/module/display/moduletileset.h b/src/script/module/display/moduletileset.h new file mode 100644 index 0000000..ad46692 --- /dev/null +++ b/src/script/module/display/moduletileset.h @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/scriptcontext.h" + +/** + * Registers the tileset module in the scripting context. + * + * @param ctx The scripting context to register the module in. + */ +void moduleTileset(scriptcontext_t *ctx); + +/** + * __index metamethod for tileset userdata. + * + * @param l The Lua state. + * @return The number of return values on the Lua stack. + */ +int moduleTilesetIndex(lua_State *l); + +/** + * __tostring metamethod for tileset userdata. + * + * @param l The Lua state. + * @return The number of return values on the Lua stack. + */ +int moduleTilesetToString(lua_State *l); + +/** + * Lua function to get a tileset by name. + * + * @param l The Lua state. + * @return The number of return values on the Lua stack. + */ +int moduleTilesetGetByName(lua_State *l); + +/** + * Lua function to get the UV coordinates for a tile index in a tileset. + * + * @param l The Lua state. + * @return The number of return values on the Lua stack. + */ +int moduleTilesetTileGetUV(lua_State *l); + +/** + * Lua function to get the UV coordinates for a tile position in a tileset. + * + * @param l The Lua state. + * @return The number of return values on the Lua stack. + */ +int moduleTilesetPositionGetUV(lua_State *l); \ No newline at end of file diff --git a/src/script/scriptmodule.c b/src/script/scriptmodule.c index dd4fd2f..a4df4ea 100644 --- a/src/script/scriptmodule.c +++ b/src/script/scriptmodule.c @@ -23,7 +23,8 @@ #include "script/module/display/modulescreen.h" #include "script/module/story/modulestoryflag.h" #include "script/module/map/modulemap.h" -#include "script/module/asset/moduleasset.h" +#include "script/module/display/moduletexture.h" +#include "script/module/display/moduletileset.h" #include "util/string.h" const scriptmodule_t SCRIPT_MODULE_LIST[] = { @@ -44,7 +45,8 @@ const scriptmodule_t SCRIPT_MODULE_LIST[] = { { .name = "screen", .callback = moduleScreen }, { .name = "storyflag", .callback = moduleStoryFlag }, { .name = "map", .callback = moduleMap }, - { .name = "asset", .callback = moduleAsset }, + { .name = "texture", .callback = moduleTexture }, + { .name = "tileset", .callback = moduleTileset }, }; #define SCRIPT_MODULE_COUNT ( \ diff --git a/tools/asset/process/tileset.py b/tools/asset/process/tileset.py index 27e9c1d..bb36e98 100644 --- a/tools/asset/process/tileset.py +++ b/tools/asset/process/tileset.py @@ -126,6 +126,7 @@ def processTileset(asset): data += f"#pragma once\n" data += f"#include \"display/tileset/tileset.h\"\n\n" data += f"static const tileset_t TILESET_{tilesetNameUpper} = {{\n" + data += f" .name = {json.dumps(tilesetName)},\n" data += f" .tileWidth = {tilesetData['tileWidth']},\n" data += f" .tileHeight = {tilesetData['tileHeight']},\n" data += f" .tileCount = {tilesetData['columns'] * tilesetData['rows']},\n"